Skip to content

Commit

Permalink
Cocotb add test (#19)
Browse files Browse the repository at this point in the history
* Initial tmake cmake function file added.

* Useless message print cleaned.

* Cleaned debugging code.

* Added copy cmake function.

* list-files without args.out creates a circular deps problem.

* copy func updated.

* duplicated tmake supported removed.

* TOP verilog files parsing commented for now.

* rtl copy cmake func typo fixed.

* Save commit.

* added tmrg verilog elaborate to keep only needed files.

* Parsing rtl files with TMRG VerilogParser class to keep only necessary files.

* Removed verbose SV2V print

* Added cmake add_test for cocotb tb.

* executable first to enable add_test for cocotb modules.

* Passing each ENV var for each add_test() for correct syntax.'

* Added regex to detect test failure.

* Added more failure detection for cocotb tests.

* Removed useless else confition.

---------

Co-authored-by: Benoit Denkinger <[email protected]>
Co-authored-by: Marco Andorno <[email protected]>
  • Loading branch information
3 people authored Aug 20, 2024
1 parent b2a0d94 commit 927c844
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 26 deletions.
2 changes: 2 additions & 0 deletions SoCMakeConfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/cmake/utils/add_subdirs.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/utils/graphviz.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/utils/multi_option.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/utils/find_python.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/utils/copy_rtl_files/copy_rtl_files.cmake")

# ====================================
# ======== Simulation ================
Expand Down Expand Up @@ -37,6 +38,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/cmake/sim/verisc/verisc_install.cmake")

# ----- Cocotb --------
include("${CMAKE_CURRENT_LIST_DIR}/cmake/sim/cocotb/cocotb_iverilog.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/sim/cocotb/add_cocotb_iverilog_tests.cmake")

# ====================================
# ======== PeakRDL ===================
Expand Down
3 changes: 0 additions & 3 deletions cmake/docs/doxygen/doxygen.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ function(doxygen IP_LIB)
string(REGEX MATCH "${regex_pattern}" extracted_path ${dir})

if(CMAKE_MATCH_1)
message("${dir} ${CMAKE_MATCH_1}")
list(APPEND INCDIRS ${CMAKE_MATCH_1})
else()
# list(APPEND INCDIRS ${dir})
endif()
endforeach()
string(REPLACE ";" "," INCDIRS "${INCDIRS}")
Expand Down
62 changes: 62 additions & 0 deletions cmake/sim/cocotb/add_cocotb_iverilog_tests.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
function(add_cocotb_iverilog_tests IP_LIB DIRECTORY)
include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/subdirectory_search.cmake")
include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/colours.cmake")

SUBDIRLIST(TEST_SUBDIRS ${DIRECTORY})

# Assume the IP library is the latest one provided if full name is not given
ip_assume_last(IP_LIB ${IP_LIB})

unset(msg)
list(APPEND _msg "-------------------------------------------------------------------------\n")
string(REPLACE "__" "::" ALIAS_NAME ${IP_LIB})
list(APPEND _msg "------------ Adding tests for IP_LIB: \"${ALIAS_NAME}\"\n")
list(APPEND _msg "Added tests:\n")

enable_testing()
foreach(test ${TEST_SUBDIRS})
add_subdirectory("${DIRECTORY}/${test}" "${test}_test")
if(SOCMAKE_DONT_ADD_TEST)
unset(SOCMAKE_DONT_ADD_TEST)
continue()
endif()
foreach(cocotb_test ${COCOTB_TB_NAME})
list(APPEND _msg " ${cocotb_test}: ${${cocotb_test}_DESCRIPTION}\n")
list(APPEND test_list ${cocotb_test})
string(TOUPPER ${cocotb_test} COCOTB_TEST_PROP)
get_target_property(COCOTB_IVERILOG_TEST_CMD ${IP_LIB} COCOTB_IVERILOG_${COCOTB_TEST_PROP}_CMD)
get_target_property(COCOTB_IVERILOG_TEST_ENV ${IP_LIB} COCOTB_IVERILOG_${COCOTB_TEST_PROP}_ENV)
add_test(
NAME ${cocotb_test}
COMMAND ${COCOTB_IVERILOG_TEST_CMD}
)
# The ENVIRONMENT property expect the variables in a specific format so its safer to
# set them one by one and let the function do the correct formating
foreach(prop ${COCOTB_IVERILOG_TEST_ENV})
set_property(TEST ${cocotb_test} APPEND PROPERTY ENVIRONMENT ${prop})
endforeach()
# vvp (iverilog) always returns 0 (pass) so check the output to detect a failure
# Also detect if python env is not correct otherwise we have a silent failure
# In general Error is not expected
set_property(TEST ${cocotb_test} PROPERTY
FAIL_REGULAR_EXPRESSION "[^a-z]FAIL;Error;ERROR;error"
)
endforeach()
endforeach()

include(ProcessorCount)
ProcessorCount(NPROC)
add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND} -j${NPROC}
DEPENDS ${test_list} ${IP_LIB}
)

list(APPEND _msg "\nTo run ctest on all of the tests run:\n")
list(APPEND _msg " make check\n")
list(APPEND _msg "To run any of the added tests execute:\n")
list(APPEND _msg " make run_<test_name>\n")
list(APPEND _msg "-------------------------------------------------------------------------")
string(REPLACE ";" "" _msg "${_msg}")
msg("${_msg}" Blue)
endfunction()

42 changes: 27 additions & 15 deletions cmake/sim/cocotb/cocotb_iverilog.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ include_guard(GLOBAL)
# ]]]
function(cocotb_iverilog IP_LIB)
# Parse the function arguments
cmake_parse_arguments(ARG "" "TOP_MODULE;OUTDIR;EXECUTABLE;IVERILOG_CLI_FLAGS;TIMEUNIT;TIMEPRECISION;TOPLEVEL_LANG;TESTCASE;PATH_MODULE;MODULE" "SIM_ARGS;PLUSARGS" ${ARGN})
cmake_parse_arguments(ARG "" "TOP_MODULE;OUTDIR;EXECUTABLE;IVERILOG_CLI_FLAGS;TIMEUNIT;TIMEPRECISION;TOPLEVEL_LANG;TESTCASE;PATH_MODULE;COCOTB_TEST" "SIM_ARGS;PLUSARGS" ${ARGN})
# Check for any unrecognized arguments
if(ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} passed unrecognized argument " "${ARG_UNPARSED_ARGUMENTS}")
Expand Down Expand Up @@ -64,10 +64,10 @@ function(cocotb_iverilog IP_LIB)

# Cocotb simulation options
# Cocotb module to run (python script)
if(ARG_MODULE)
set(MODULE ${ARG_MODULE})
if(ARG_COCOTB_TEST)
set(COCOTB_TEST ${ARG_COCOTB_TEST})
else()
message(FATAL_ERROR "No cocotb module provided. Use the function argument MODULE.")
message(FATAL_ERROR "No cocotb module provided. Use the function argument COCOTB_TEST.")
endif()
if(ARG_PATH_MODULE)
set(PATH_MODULE ${ARG_PATH_MODULE})
Expand Down Expand Up @@ -154,7 +154,7 @@ function(cocotb_iverilog IP_LIB)
# Add a custom target that depends on the executable and stamp file
add_custom_target(
# Add cocotb module name to be able to create multiple targets
${MODULE}_${IP_LIB}_${CMAKE_CURRENT_FUNCTION}
${COCOTB_TEST}
DEPENDS ${ARG_EXECUTABLE} ${STAMP_FILE} ${IP_LIB}
)

Expand Down Expand Up @@ -186,30 +186,42 @@ function(cocotb_iverilog IP_LIB)
# Remove the line feed of the variable otherwise if breaks the below command
string(STRIP ${COCOTB_LIB_VPI_ICARUS} COCOTB_LIB_VPI_ICARUS)

# Add a custom command to run cocotb
add_custom_command(
OUTPUT ${COCOTB_RESULTS_FILE}
COMMAND PYTHONPATH=${PATH_MODULE}
MODULE=${MODULE}
set(COCOTB_ENV_VARS
PYTHONPATH=${PATH_MODULE}
MODULE=${COCOTB_TEST}
TESTCASE=${TESTCASE}
TOPLEVEL=${TOP_MODULE}
TOPLEVEL_LANG=${TOPLEVEL_LANG}
# sim command prefix, e.g., for debugging: 'gdb --args'
${ARG_SIM_CMD_PREFIX}
)
set(COCOTB_IVERILOG_CMD
# iverilog run-time engine must be in your path
vvp -M${COCOTB_LIB_DIR} -m${COCOTB_LIB_VPI_ICARUS}
# Arguments to pass to execution of compiled simulation
${ARG_SIM_ARGS}
${ARG_EXECUTABLE}
# Plusargs to pass to the simulator
${ARG_PLUSARGS}
DEPENDS ${MODULE}_${IP_LIB}_${CMAKE_CURRENT_FUNCTION}
COMMENT "Running cocotb simulation on ${IP_LIB}"
)

# Add a custom command to run cocotb
add_custom_command(
OUTPUT ${COCOTB_RESULTS_FILE}
COMMAND ${COCOTB_ENV_VARS}
# sim command prefix, e.g., for debugging: 'gdb --args'
${ARG_SIM_CMD_PREFIX}
${COCOTB_IVERILOG_CMD}
DEPENDS ${COCOTB_TEST}
COMMENT "Running cocotb simulation on ${IP_LIB}"
)

# Set the command as a property to be easily found by add_test()
string(TOUPPER ${COCOTB_TEST} COCOTB_TEST_PROP)
set_target_properties(${IP_LIB} PROPERTIES COCOTB_IVERILOG_${COCOTB_TEST_PROP}_CMD "${COCOTB_IVERILOG_CMD}")
set_target_properties(${IP_LIB} PROPERTIES COCOTB_IVERILOG_${COCOTB_TEST_PROP}_ENV "${COCOTB_ENV_VARS}")

# Add a custom target that depends on the executable and stamp file
add_custom_target(
run_${MODULE}_${IP_LIB}_${CMAKE_CURRENT_FUNCTION}
run_${COCOTB_TEST}
DEPENDS ${COCOTB_RESULTS_FILE}
)

Expand Down
2 changes: 0 additions & 2 deletions cmake/synth/sv2v.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ function(sv2v IP_LIB)

get_sv2v_sources(SV2V_SRC ${IP_LIB})
foreach(vfile ${SV2V_SRC})
message("SV2V source: ${vfile}")
get_filename_component(V_SOURCE_WO_EXT ${vfile} NAME_WE)
if(NOT ${V_SOURCE_WO_EXT} MATCHES ".*regblock_pkg$")
list(APPEND V_GEN "${OUTDIR}/${V_SOURCE_WO_EXT}.v")
message("V_GEN source: ${OUTDIR}/${V_SOURCE_WO_EXT}.v")
endif()
endforeach()
set_source_files_properties(${V_GEN} PROPERTIES GENERATED TRUE)
Expand Down
15 changes: 9 additions & 6 deletions cmake/tmrg/tmrg/lib_module_strip.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ def generate_implementation_lib(fname_in, fname_out):
# Additional check to skip packages
if line_stripped.startswith("package %s" % module_name):
in_package = True
break
if line_stripped.startswith("module %s" % module_name):
fout.write(line)
elif line_stripped.startswith("module %s" % module_name):
in_module_header = True
in_module = True
if in_module_header:

if in_package and line_stripped.startswith("endpackage"):
fout.write(line)
in_package = False
elif in_module_header:
fout.write(line)
if line_stripped.endswith(");"):
in_module_header = False
Expand All @@ -38,9 +42,6 @@ def generate_implementation_lib(fname_in, fname_out):
elif in_module and line_stripped.startswith("endmodule"):
fout.write(line)
in_module = False
# If we have a package file just copy it
if in_package:
shutil.copyfile(fname_in, fname_out)

parser = argparse.ArgumentParser(description='Systemverilog module stripping')

Expand All @@ -65,4 +66,6 @@ def generate_implementation_lib(fname_in, fname_out):
# Take the file basename
basename = os.path.basename(file)
stripped_file = f'{outdir}/{basename}'
# print(f'{file}')
# print(f'-> {stripped_file}')
generate_implementation_lib(file, stripped_file)
51 changes: 51 additions & 0 deletions cmake/utils/copy_rtl_files/copy_rtl_files.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
function(copy_rtl_files IP_LIB)
cmake_parse_arguments(ARG "" "OUTDIR;TOP_MODULE" "" ${ARGN})
if(ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} passed unrecognized argument " "${ARG_UNPARSED_ARGUMENTS}")
endif()

include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../hwip.cmake")
include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../find_python.cmake")

ip_assume_last(IP_LIB ${IP_LIB})

if(NOT ARG_OUTDIR)
set(OUTDIR ${CMAKE_BINARY_DIR}/ip_sources)
else()
set(OUTDIR ${ARG_OUTDIR})
endif()

# Check if a top module is provided. In this case only the modules in its hierarchy are kept
if(ARG_TOP_MODULE)
set(TOP_MODULE --top-module ${ARG_TOP_MODULE})
endif()

# Get the list of RTL sources
get_ip_rtl_sources(RTL_SOURCES ${IP_LIB})
# Create a list to hold the RTL files as arguments for the Python script
set(RTL_FILES_ARGS)
# Add each RTL file to the list of arguments
foreach(file ${RTL_SOURCES})
list(APPEND RTL_FILES_ARGS ${file})
endforeach()

find_python3()
set(__CMD ${Python3_EXECUTABLE} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/copy_rtl_files.py
${TOP_MODULE} --stats --outdir ${OUTDIR} ${RTL_FILES_ARGS}
)

# Call the Python script with the output directory and the RTL files
set(STAMP_FILE "${CMAKE_BINARY_DIR}/${IP_LIB}_${CMAKE_CURRENT_FUNCTION}.stamp")
add_custom_command(
OUTPUT ${STAMP_FILE}
COMMAND ${__CMD}
COMMENT "Copying RTL files to ${OUTDIR}"
VERBATIM
)

# Create a target to run the custom command
add_custom_target(
${IP_LIB}_copy_rtl
ALL DEPENDS ${IP_LIB} ${STAMP_FILE}
)
endfunction()
120 changes: 120 additions & 0 deletions cmake/utils/copy_rtl_files/copy_rtl_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import os
import shutil
import argparse
from tmrg.verilog_elaborator import VerilogElaborator

def remove_common_path(file_paths):
# Find the common path
common_path = os.path.commonpath(file_paths)

# Get the last common directory
last_common_directory = os.path.basename(common_path)

# Iterate over each file path and remove the common path
updated_paths = []
for path in file_paths:
relative_path = os.path.relpath(path, common_path)
updated_path = os.path.join(last_common_directory, relative_path)
updated_paths.append(updated_path)

return updated_paths

def parse_input_files(velab, input_files):

def _enterH(velab, module, i="", done=[], hierarchy_files=[], hierarchy_missing_files=[]):
i += " |"
for instName, inst in velab.global_namespace.modules[module].instances:
if inst.module_name in velab.global_namespace.modules:
print(i+"- "+instName+":"+inst.module_name)
hierarchy_files.append(velab.global_namespace.modules[inst.module_name].file.filename)
if id(inst) in done:
continue
else:
done.append(id(inst))
_enterH(velab, inst.module_name, i, done, hierarchy_files, hierarchy_missing_files)
else:
print(i+"- [!] "+instName+":"+inst.module_name)
if inst.module_name not in hierarchy_missing_files:
hierarchy_missing_files.append(inst.module_name)

done = []
hierarchy_files = []
hierarchy_missing_files = []
_enterH(velab, velab.topModule, "", done, hierarchy_files, hierarchy_missing_files)

# for module in hierarchy_files:
# print(f"Module {module} found")

for module in hierarchy_missing_files:
print(f"WARNING: Module {module} missing")

return hierarchy_files


def main():
parser = argparse.ArgumentParser(description="Filter RTL files based on module hierarchy.")
# parser.add_argument('--top', help='Top module name')
parser.add_argument('--outdir', help='Output directory where files will be copied')
parser.add_argument('--list-files', action="store_true", help="Don't generate files, but instead just list the files that will be generated")
parser.add_argument("--inc-dir", dest="inc_dir", action="append", default=[],
help="Directory where to look for include files (use option --include to actualy include the files during preprocessing)")
parser.add_argument("--include", dest="include",
action="store_true", default=False, help="Include include files")
parser.add_argument("--generate-report", dest="generateBugReport",
action="store_true", default=False, help="Generate bug report")
parser.add_argument("--stats", dest="stats", action="store_true", help="Print statistics")
parser.add_argument("--top-module", dest="top_module", action="store", default="", help="Specify top module name")
parser.add_argument('files', metavar='F', type=str, nargs='+', help='List of RTL file paths')

args = parser.parse_args()

try:
files = args.files

# We manually add some attributes to be compliant with the expected options
args.libs = []
# We use TMRG libraries to elaborate our design
velab = VerilogElaborator(args, files, "tmrg")
# First files have to be parsed
velab.parse()
# Then the design can be elaborated
velab.elaborate(allowMissingModules=True)
# Get on the files from the hierarchy if top module is provided
if args.top_module:
files = parse_input_files(velab, files)


# Remove common path of the files
files_striped = remove_common_path(files)
# Get absolute path of the dst directory
abs_path = os.path.abspath(args.outdir)
# Change file paths to dst directory
files_dst = []
for f_dst in files_striped:
files_dst.append(abs_path + '/' + f_dst)

# Only print the files if argument is passed
if args.list_files:
print(*files_dst)
else:
file_list_path = abs_path + '/file_list.txt'
# Check if the file list exist, otherwise create it
os.makedirs(os.path.dirname(file_list_path), exist_ok=True)
# Write the files to the file list
with open(file_list_path, 'w') as outfile:
for f_src, f_dst in zip(files, files_dst):
# Write the path to the list of path file
outfile.write(f_dst + '\n')
# Create the folder hierarchy
os.makedirs(os.path.dirname(f_dst), exist_ok=True)
# Copy the file to the new location
shutil.copy2(f_src, f_dst)

# Add empty line at the end of the file
outfile.write('\n')
print(f"Filtered file list written to {file_list_path}")
except ValueError as e:
print(e)

if __name__ == "__main__":
main()

0 comments on commit 927c844

Please sign in to comment.