From feffecca447e51f98a684992d1e3fa8914915eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Denkinger?= <42833463+benoitdenkinger@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:44:05 +0200 Subject: [PATCH] FPGA support (#77) Co-authored-by: Benoit Denkinger --- cmake/firmware/add_tests.cmake | 25 ++++-- .../firmware/toolchains/riscv_toolchain.cmake | 4 +- .../uart_programmer/uart_programmer.cmake | 33 ++++++- cmake/fpga/uart_programmer/uart_programmer.py | 24 ++--- cmake/fpga/vivado/vivado.cmake | 9 +- cmake/utils/format_string_spacing.cmake | 87 +++++++++++++++++++ 6 files changed, 153 insertions(+), 29 deletions(-) create mode 100644 cmake/utils/format_string_spacing.cmake diff --git a/cmake/firmware/add_tests.cmake b/cmake/firmware/add_tests.cmake index bc7d3a9..9f61f5c 100644 --- a/cmake/firmware/add_tests.cmake +++ b/cmake/firmware/add_tests.cmake @@ -1,3 +1,5 @@ +# Iterates through the DIRECTORY sub-directories and create targets +# to start simulating each test. function(add_tests EXECUTABLE DIRECTORY) cmake_parse_arguments(ARG "USE_PLUSARGS" "WIDTH" "ARGS;DEPS" ${ARGN}) if(ARG_UNPARSED_ARGUMENTS) @@ -6,6 +8,7 @@ function(add_tests EXECUTABLE DIRECTORY) include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../utils/subdirectory_search.cmake") include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../utils/colours.cmake") + include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../utils/format_string_spacing.cmake") include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/fw_utils.cmake") SUBDIRLIST(TEST_SUBDIRS ${DIRECTORY}) @@ -19,13 +22,15 @@ function(add_tests EXECUTABLE DIRECTORY) set(PREFIX --) endif() - unset(msg) + unset(_msg) list(APPEND _msg "-------------------------------------------------------------------------\n") string(REPLACE "__" "::" ALIAS_NAME ${SOC_NAME}) - list(APPEND _msg "------------ Adding tests for SoC: \"${ALIAS_NAME}\", tb executable: \"${EXECUTABLE}\"\n") + list(APPEND _msg "Adding tests for SoC:\n") + list(APPEND _msg " ${ALIAS_NAME}, tb executable: ${EXECUTABLE}\n") list(APPEND _msg "Added tests:\n") enable_testing() + unset(_test_msg) foreach(test ${TEST_SUBDIRS}) add_subdirectory("${DIRECTORY}/${test}" "${test}_test") if(SOCMAKE_DONT_ADD_TEST) @@ -33,7 +38,7 @@ function(add_tests EXECUTABLE DIRECTORY) continue() endif() foreach(fw_prj ${FW_PROJECT_NAME}) - list(APPEND _msg " ${fw_prj}: ${${fw_prj}_DESCRIPTION}\n") + list(APPEND _test_msg " ${fw_prj}: ${${fw_prj}_DESCRIPTION}") list(APPEND test_list ${fw_prj}) get_target_property(HEX_FILE ${fw_prj} HEX_${ARG_WIDTH}bit_FILE) get_target_property(HEX_TEXT_FILE ${fw_prj} HEX_TEXT_${ARG_WIDTH}bit_FILE) @@ -45,7 +50,7 @@ function(add_tests EXECUTABLE DIRECTORY) ${PREFIX}firmware_text=${HEX_TEXT_FILE} ${PREFIX}firmware_data=${HEX_DATA_FILE} ${ARG_ARGS} - ) + ) add_custom_target(run_${fw_prj} COMMAND ./${EXECUTABLE} @@ -54,7 +59,7 @@ function(add_tests EXECUTABLE DIRECTORY) ${PREFIX}firmware_data=${HEX_DATA_FILE} ${ARG_ARGS} DEPENDS ${EXECUTABLE} ${fw_prj} ${ARG_DEPS} - ) + ) endforeach() endforeach() @@ -63,12 +68,16 @@ function(add_tests EXECUTABLE DIRECTORY) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -j${NPROC} DEPENDS ${test_list} ${EXECUTABLE} - ) + ) + + format_string_spacing(formatted_test_msg "${_test_msg}" " ; ") + + list(APPEND _msg "${formatted_test_msg}") list(APPEND _msg "\nTo run ctest on all of the tests run:\n") - list(APPEND _msg " make check\n") + list(APPEND _msg " make check\n") list(APPEND _msg "To run any of the added tests execute:\n") - list(APPEND _msg " make run_\n") + list(APPEND _msg " make run_\n") list(APPEND _msg "-------------------------------------------------------------------------") string(REPLACE ";" "" _msg "${_msg}") msg("${_msg}" Blue) diff --git a/cmake/firmware/toolchains/riscv_toolchain.cmake b/cmake/firmware/toolchains/riscv_toolchain.cmake index dbaaa9f..b7362a8 100644 --- a/cmake/firmware/toolchains/riscv_toolchain.cmake +++ b/cmake/firmware/toolchains/riscv_toolchain.cmake @@ -31,6 +31,7 @@ find_program(RISCV_STRIP ${TOOLCHAIN_PREFIX}-strip HINTS ${RISCV_GNU_PA set(CMAKE_C_COMPILER ${RISCV_C_COMPILER}) set(CMAKE_CXX_COMPILER ${RISCV_CXX_COMPILER}) +set(CMAKE_ASM_COMPILER ${RISCV_C_COMPILER}) set(CMAKE_ASM ${RISCV_ASM}) set(CMAKE_AR ${RISCV_AR}) set(CMAKE_LINKER ${RISCV_LINKER}) @@ -85,8 +86,9 @@ string(APPEND CMAKE_C_FLAGS " -mabi=ilp32") # Optimize for size by default string(APPEND CMAKE_C_FLAGS " -Os") -# Pass common flags for c++ compilation flow +# Pass common flags for c++ anD assembly compilation flow set(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS}) +set(CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS}) ####################### # Options for Linking # diff --git a/cmake/fpga/uart_programmer/uart_programmer.cmake b/cmake/fpga/uart_programmer/uart_programmer.cmake index 4342c60..71c6e59 100644 --- a/cmake/fpga/uart_programmer/uart_programmer.cmake +++ b/cmake/fpga/uart_programmer/uart_programmer.cmake @@ -1,19 +1,37 @@ +# Iterates through the DIRECTORY sub-directories and create targets +# to start uart transactions for each test. function(uart_programmer DIRECTORY BAUDRATE DEV) cmake_parse_arguments(ARG "" "" "" ${ARGN}) if(ARG_UNPARSED_ARGUMENTS) message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION} passed unrecognized argument " "${ARG_UNPARSED_ARGUMENTS}") endif() - + include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/find_python.cmake") include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/subdirectory_search.cmake") + include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/format_string_spacing.cmake") + include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../utils/colours.cmake") SUBDIRLIST(TEST_SUBDIRS ${DIRECTORY}) - + find_python3() set(UART_PROGRAMMER ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/uart_programmer.py) + unset(_msg) + list(APPEND _msg "-------------------------------------------------------------------------\n") + string(REPLACE "__" "::" ALIAS_NAME ${SOC_NAME}) + list(APPEND _msg "Adding tests for SoC:\n") + list(APPEND _msg " ${ALIAS_NAME}\n") + list(APPEND _msg "Added tests:\n") + + unset(_test_msg) foreach(test ${TEST_SUBDIRS}) add_subdirectory("${DIRECTORY}/${test}" "${test}_test") + if(SOCMAKE_DONT_ADD_TEST) + unset(SOCMAKE_DONT_ADD_TEST) + continue() + endif() foreach(fw_prj ${FW_PROJECT_NAME}) + list(APPEND _test_msg " ${fw_prj}: ${${fw_prj}_DESCRIPTION}") + list(APPEND test_list ${fw_prj}) get_target_property(HEX_FILE ${fw_prj} HEX_32bit_FILE) get_target_property(HEX_TEXT_FILE ${fw_prj} HEX_TEXT_32bit_FILE) get_target_property(HEX_DATA_FILE ${fw_prj} HEX_DATA_32bit_FILE) @@ -28,6 +46,17 @@ function(uart_programmer DIRECTORY BAUDRATE DEV) ) endforeach() endforeach() + + format_string_spacing(formatted_test_msg "${_test_msg}" " ; ") + + list(APPEND _msg "${formatted_test_msg}") + + list(APPEND _msg "To run any of the added tests on the FPGA:\n") + list(APPEND _msg " make program_\n") + list(APPEND _msg "-------------------------------------------------------------------------") + string(REPLACE ";" "" _msg "${_msg}") + msg("${_msg}" Blue) + endfunction() diff --git a/cmake/fpga/uart_programmer/uart_programmer.py b/cmake/fpga/uart_programmer/uart_programmer.py index eeca090..578af09 100644 --- a/cmake/fpga/uart_programmer/uart_programmer.py +++ b/cmake/fpga/uart_programmer/uart_programmer.py @@ -20,10 +20,10 @@ def program( text_bytes = b'' for line in text_lines: line = line.strip() - + if line.startswith('@') or line == '': continue - + words = line.split() for word in words: byte_data = bytes.fromhex(word) @@ -34,10 +34,10 @@ def program( data_bytes = b'' for line in data_lines: line = line.strip() - + if line.startswith('@') or line == '': continue - + words = line.split() for word in words: byte_data = bytes.fromhex(word) @@ -48,7 +48,7 @@ def program( num_text_bytes = len(text_bytes).to_bytes(4, byteorder='little') num_data_bytes = (len(data_bytes)).to_bytes(4, byteorder='little') - + print("-----------------------------------------------------") print(f"----- Programming {dev} at baudrate {baudrate} ---") @@ -65,31 +65,31 @@ def program( def getport(dev): ports = serial.tools.list_ports.comports() port_names = [port.device for port in ports] - + default_port = port_names[0] if dev in port_names: default_port = dev elif "/dev/ttyUSB0" in port_names: default_port = "/dev/ttyUSB0" - + print(f"Available ports:") for index,port in enumerate(port_names): print(f"{index+1}: {port}", end="") if port == default_port: print(" (default)", end="") - + print() - + input_port = input(f"Enter port number (default {default_port}): ") try: port = port_names[int(input_port)-1] except: port = default_port - + print(f"Selected port: {port}") return port - - + + def main(): parser = argparse.ArgumentParser( diff --git a/cmake/fpga/vivado/vivado.cmake b/cmake/fpga/vivado/vivado.cmake index 873a32e..8519509 100644 --- a/cmake/fpga/vivado/vivado.cmake +++ b/cmake/fpga/vivado/vivado.cmake @@ -65,16 +65,13 @@ function(vivado IP_LIB) --outdir ${OUTDIR} --verilog-defs ${ARG_VERILOG_DEFINES} ${CMP_DEFS_ARG} - COMMAND touch ${STAMP_FILE} + COMMAND /bin/sh -c date > ${STAMP_FILE} DEPENDS ${SOURCES} ${XDC_FILES} ${IP_LIB} COMMENT "Running ${CMAKE_CURRENT_FUNCTION} on ${IP_LIB}" - ) + ) add_custom_target( ${IP_LIB}_vivado DEPENDS ${BITSTREAM} ${STAMP_FILE} - ) + ) endfunction() - - - diff --git a/cmake/utils/format_string_spacing.cmake b/cmake/utils/format_string_spacing.cmake new file mode 100644 index 0000000..e500b69 --- /dev/null +++ b/cmake/utils/format_string_spacing.cmake @@ -0,0 +1,87 @@ +include_guard(GLOBAL) + +# Take an input list of strings with single space words and replace each +# space with the corresponding formatting character (e.g., tab(s), space(s)). +# If a space is not given a replacement character, a single space is kept +# +# Example usage: +# set(input_list " test1: Test1 does that." " test2: Test2 does this and that.") +# set(replacements " ;\t") +# +# format_string(FORMATTED_STRING "${input_list}" "${replacements}") +# Output: +# test1: Test1 does that. +# test2: Test2 does this and that. + +function(format_string_spacing OUTPUT_VAR input_list replacements) + # Split the replacement characters into a list. + string(REPLACE ";" ";" replacement_list "${replacements}") + + set(formatted_string "") # To store the final formatted result + set(max_lengths "") # To store the maximum length of each part before spaces + + + # Step 1: Calculate maximum lengths for the sections before each space + foreach(line IN LISTS input_list) + string(REPLACE " " ";" split_line "${line}") + set(index 0) + foreach(section IN LISTS split_line) + string(LENGTH "${section}" section_length) + + # Expand max_lengths if it is the first pass + list(LENGTH max_lengths list_size) + if(list_size LESS_EQUAL index) + list(APPEND max_lengths 0) + endif() + + # Update max length for each column section + list(GET max_lengths ${index} current_max_length) + if(section_length GREATER current_max_length) + list(REMOVE_AT max_lengths ${index}) + list(INSERT max_lengths ${index} ${section_length}) + endif() + + math(EXPR index "${index} + 1") + endforeach() + endforeach() + + # Step 2: Format each line based on the calculated max lengths + foreach(line IN LISTS input_list) + string(REPLACE " " ";" split_line "${line}") + + set(formatted_line "") + set(index 0) + foreach(section IN LISTS split_line) + # Get the length of the section + string(LENGTH "${section}" section_length) + + # Get the max length for this section + list(GET max_lengths ${index} max_length) + + # Pad the section with spaces to match the maximum length + math(EXPR padding_length "${max_length} - ${section_length}") + string(REPEAT " " ${padding_length} padding) + + # Append the section to the formatted line + set(formatted_line "${formatted_line}${section}") + + # Add the replacement string if available, otherwise keep original space + list(LENGTH replacement_list replacement_count) + if(${index} LESS replacement_count) + list(GET replacement_list ${index} replacement_char) + set(formatted_line "${formatted_line}${padding}${replacement_char}") + else() + # Add original space back if no replacement is available + set(formatted_line "${formatted_line} ") + endif() + + math(EXPR index "${index} + 1") + endforeach() + + # Add the formatted line to the final formatted string + string(APPEND formatted_string "${formatted_line}\n") + endforeach() + + # Step 3: Return the formatted string + set(${OUTPUT_VAR} "${formatted_string}" PARENT_SCOPE) +endfunction()