From eaa9177962afe04f68ea03a4e46997391fc16db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camille=20Moni=C3=A8re?= Date: Tue, 19 Apr 2022 18:16:58 +0200 Subject: [PATCH] Modify code to comply to buggy Vivado_HLS 2019.1 - For whatever reasons, v2019.1 can C-Simulate CCordicAbs, but cannot C-Synthetize it. So the process member function have been mirrored to a static process function in the top level, called directly. It just works. - The TCL script have been amended to support both Vivado_HLS 2019.1 and Vitis_HLS 2020.2 (maybe .1 also, but hasn't been tested). - The CMake project works and is validated for G++ > 6.2, so can't be used with vivado less than 2020.1 (which use 4.6.3). This vivado version must be used with the TCL script directly. - A custom target `run_hls` has been created to call vitis_hls 2020.2 if ENABLE_XILINX and ENABLE_TESTING have been specified. It is called by ctest by default. It can produce an IP (option IP_XILINX) and run Vivado implementation design flow (option IMPL_XILINX). --- CMakeLists.txt | 95 ++++++++++++++-- README.md | 2 + hls_files/cordicabs_16_4_6/script_16_4_6.tcl | 109 +++++++++++++++++-- sources/CCordicAbs/CCordicAbs.hpp | 11 +- sources/top_level/top_level_cordic.cpp | 54 ++++++++- sources/top_level/top_level_cordic_tb.cpp | 19 ++-- 6 files changed, 259 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba0cde0..04b84d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,27 +18,44 @@ cmake_minimum_required (VERSION 3.16.0 FATAL_ERROR) # setting this is required -set (CMAKE_CXX_STANDARD 14) -set (CMAKE_CXX_STANDARD_REQUIRED ON) -set (CMAKE_CXX_EXTENSIONS OFF) -set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../lib) -set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../lib) -set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin) - -set (CMAKE_EXPORT_COMPILE_COMMANDS true) - project ( CordicAbs LANGUAGES CXX VERSION 0.1 ) +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.2)) + message ( + FATAL_ERROR + "This project require a GNU compiler version greater than 6.2." + "\nNote: If you tried to use g++-4.6.3 as provided by Xilinx Vivado v2019.1, use directly the TCL script " + "in the folder `hls_files/cordicabs_16_4_6`." + ) +endif () + +set (CMAKE_CXX_STANDARD 14) +set (CMAKE_CXX_STANDARD_REQUIRED ON) +set (CMAKE_CXX_EXTENSIONS OFF) + +set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../lib) +set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../lib) +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin) + # ################################################################################################## # Options # ################################################################################################## option (EXPORT_COMMANDS "export compile commands, for use with clangd for example." ON) option (ENABLE_XILINX "use Xilinx provided proprietary headers." OFF) +if (ENABLE_XILINX) + option (XILINX_COSIM "use TCL Vivado/Vitis HLS script to co-simulate the design." OFF) + if (XILINX_COSIM) + option (XILINX_IP "use TCL Vivado/Vitis HLS script to generate an IP." OFF) + if (XILINX_IP) + option (XILINX_IMPL "use TCL Vivado/Vitis HLS script to run design implementation." OFF) + endif () + endif () +endif () option (ENABLE_TESTING "use Catch2 in conjunction with CTest as a test suite." ON) option (ENABLE_SOFTWARE @@ -70,11 +87,16 @@ if (ENABLE_XILINX) ) if (XILINX_VER VERSION_GREATER_EQUAL "2020.1") - set (AP_INCLUDE_DIR ${XILINX_HOME}/Vitis_HLS/${XILINX_VER}/include) + set (AP_INCLUDE_DIR ${XILINX_HOME}/Vitis_HLS/${XILINX_VER}/include CACHE INTERNAL "Path to Xilinx includes" FORCE) + find_program (XILINX_HLS vitis_hls HINTS ${XILINX_HOME}/Vitis_HLS/${XILINX_VER}/bin NO_CACHE REQUIRED) else () - set (AP_INCLUDE_DIR ${XILINX_HOME}/Vivado/${XILINX_VER}/include) + message ( + FATAL_ERROR + "Xilinx versions less than 2020.1 are not supported due to the outdated compiler version in use" + ) endif () message (STATUS "AP headers must lie under ${AP_INCLUDE_DIR}") + else () set ( AP_TYPES_HINT @@ -127,4 +149,55 @@ if (ENABLE_TESTING) include (CTest) include (Catch) catch_discover_tests (cordicabs_tb) + + if (ENABLE_XILINX) + find_file (INPUT_DAT_TB input.dat HINTS ${CMAKE_SOURCE_DIR}/data REQUIRED) + if (CMAKE_VERSION VERSION_LESS 3.19.0) + cmake_policy (SET CMP0110 OLD) + add_test (NAME "\"Xilinx C-Simulation Testbench\"" COMMAND cordicabs_xilinx_tb + ${INPUT_DAT_TB} + ) + else () + cmake_policy (SET CMP0110 NEW) + add_test (NAME "Xilinx C-Simulation Testbench" COMMAND cordicabs_xilinx_tb ${INPUT_DAT_TB}) + endif () + + if (XILINX_COSIM) + set ( + XILINX_TESTLINES + "1000" + CACHE STRING "Number of co-simulation passes" + ) + + if (XILINX_IP) + set ( + EXPORT_IP + "1" + CACHE INTERNAL "EXPORT_IP" + ) + if (XILINX_IMPL) + set ( + RUN_IMPL + "1" + CACHE INTERNAL "RUN_IMPL" + ) + endif () + endif () + add_custom_target ( + run_hls + COMMAND ${XILINX_HLS} ${CMAKE_SOURCE_DIR}/hls_files/cordicabs_16_4_6/script_16_4_6.tcl + ${XILINX_TESTLINES} ${EXPORT_IP} ${RUN_IMPL} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hls_files/cordicabs_16_4_6 + USES_TERMINAL + ) + + if (CMAKE_VERSION VERSION_LESS 3.190.0) + cmake_policy (SET CMP0110 OLD) + add_test (NAME "\"Xilinx HLS TCL Flow\"" COMMAND run_hls) + else () + cmake_policy (SET CMP0110 NEW) + add_test (NAME "Xilinx HLS TCL Flow" COMMAND run_hls) + endif () + endif () + endif () endif () diff --git a/README.md b/README.md index b318b5b..ee21ff4 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ The [Catch2](https://github.com/catchorg/Catch) test framework has been used in - Uses Catch v2.13.7, - Depends on Xilinx HLS arbitrary precision types, available as FOSS [here provided by Xilinx](https://github.com/Xilinx/HLS_arbitrary_Precision_Types) or [here patched by myself](https://github.com/DrasLorus/HLS_arbitrary_Precision_Types). Note: Xilinx also provides proprietary versions of those headers, suitable for synthesis and implementation, bundled with their products. +A Xilinx C++ HLS testbench is also available, as well as a TCL script to run simulation, synthesis, co-simulation and IP export and implementation if wanted. Xilinx HLS v2020.2 can be directly called from CMake by the target `run_hls` if the options `ENABLE_TESTING`, `ENABLE_XILINX` and `COSIM_XILINX` are enabled. If CMake feels like black magic, It is advice to use tools like `ccmake` (NCurses terminal interface to cmake) or `cmake-gui`. + ## License and copyright Copyright 2022 Camille "DrasLorus" Monière. diff --git a/hls_files/cordicabs_16_4_6/script_16_4_6.tcl b/hls_files/cordicabs_16_4_6/script_16_4_6.tcl index 9679d10..c54adbc 100644 --- a/hls_files/cordicabs_16_4_6/script_16_4_6.tcl +++ b/hls_files/cordicabs_16_4_6/script_16_4_6.tcl @@ -1,9 +1,41 @@ +set VERSION [version -short] + set SCRIPT_DIR [file normalize [file dirname [info script]]] set ROOT_DIR "${SCRIPT_DIR}/../.." -set CFLAGS "-std=c++14 -Wno-unknown-pragmas -Wno-unused-label -Wall -DNDEBUG -I${ROOT_DIR}/sources" +# if {[expr {$VERSION < 2020.1}]} { +# error "Version less than 2020.1 is not supported." +# } -open_project -reset cordicabs_16_4_6 +if { [expr {$argc > 0}] } { + set NLINES [lindex $argv 0] +} else { + set NLINES 10000 +} + +if { [expr {$argc > 1}] } { + set EXPORT_IP [lindex $argv 1] +} else { + set EXPORT_IP 1 +} + +if { [expr {$argc > 2}] && $EXPORT_IP } { + set RUN_IMPL [lindex $argv 2] +} else { + set RUN_IMPL 0 +} + + +set XILINX_MAJOR [expr {int($VERSION)}] +if {[expr {$VERSION < 2020.1}]} { + set CFLAGS "-std=c++0x -DXILINX_MAJOR=${XILINX_MAJOR} -Wno-unknown-pragmas -Wno-unused-label -Wall -DNDEBUG -I${ROOT_DIR}/sources" +} else { + set CFLAGS "-std=c++14 -Wno-unknown-pragmas -Wno-unused-label -Wall -DNDEBUG -I${ROOT_DIR}/sources" +} + +set PROJECT_NAME "cordicabs_16_4_6" + +open_project -reset ${PROJECT_NAME} set_top cordic_abs_16_4_6 add_files -cflags "$CFLAGS" "${ROOT_DIR}/sources/CCordicAbs/CCordicAbs.cpp" add_files -cflags "$CFLAGS" "${ROOT_DIR}/sources/CCordicAbs/CCordicAbs.hpp" @@ -11,12 +43,75 @@ add_files -cflags "$CFLAGS" "${ROOT_DIR}/sources/hls_abs/hls_abs.hpp" add_files -cflags "$CFLAGS" "${ROOT_DIR}/sources/top_level/top_level_cordic.cpp" add_files -cflags "$CFLAGS" -tb "${ROOT_DIR}/sources/top_level/top_level_cordic_tb.cpp" -open_solution "solution_spartan7" -flow_target vivado -set_part {xc7s100fgga484-1} +if {[expr {$VERSION < 2020.1}]} { + open_solution -reset "solution_spartan7" +} else { + open_solution -reset "solution_spartan7" -flow_target vivado +} +set_part {xc7s100-fgga484-1} create_clock -period 10 -name default set_clock_uncertainty 2 -#source "./cordicabs_16_4_6/solution/directives.tcl" +set_directive_pipeline cordic_abs_16_4_6 +set_directive_interface cordic_abs_16_4_6 -mode ap_ctrl_none csim_design -argv "${ROOT_DIR}/data/input.dat" -clean -O csynth_design -cosim_design -O -argv "${ROOT_DIR}/data/input.dat" -export_design -format ip_catalog +cosim_design -O -argv "${ROOT_DIR}/data/input.dat ${NLINES}" + +if { $EXPORT_IP } { + config_export -format ip_catalog \ + -display_name "${PROJECT_NAME}" \ + -ipname "${PROJECT_NAME}_spartan7" \ + -version "0.1.${XILINX_MAJOR}" + if { [expr {$VERSION >= 2020.1}] } { + config_export -output "${ROOT_DIR}/hls_files/cordicabs_16_4_6/ip/${XILINX_MAJOR}_spartan7" + } + if { $RUN_IMPL } { + export_design -flow impl + } else { + export_design + } + if { [expr {$VERSION < 2020.1}] } { + set IP_FILE [glob -directory "${SCRIPT_DIR}/${PROJECT_NAME}/solution_spartan7/impl/ip" *.zip] + file copy -force "${IP_FILE}" "${ROOT_DIR}/hls_files/cordicabs_16_4_6/ip/${XILINX_MAJOR}_spartan7.zip" + } +} + +close_solution + +if {[expr {$VERSION < 2020.1}]} { + open_solution -reset "solution_genesys2" +} else { + open_solution -reset "solution_genesys2" -flow_target vivado +} +set_part {xc7k325tffg900-2} +create_clock -period 10 -name default +set_clock_uncertainty 2 +set_directive_pipeline cordic_abs_16_4_6 +set_directive_interface cordic_abs_16_4_6 -mode ap_ctrl_none +csim_design -argv "${ROOT_DIR}/data/input.dat" -clean -O +csynth_design +cosim_design -O -argv "${ROOT_DIR}/data/input.dat ${NLINES}" + +if { $EXPORT_IP } { + config_export -format ip_catalog \ + -display_name "${PROJECT_NAME}" \ + -ipname "${PROJECT_NAME}_genesys2" \ + -version "0.1.${XILINX_MAJOR}" + if { [expr {$VERSION >= 2020.1}] } { + config_export -output "${ROOT_DIR}/hls_files/cordicabs_16_4_6/ip/${XILINX_MAJOR}_genesys2" + } + if { $RUN_IMPL } { + export_design -flow impl + } else { + export_design + } + if { [expr {$VERSION < 2020.1}] } { + set IP_FILE [glob -directory "${SCRIPT_DIR}/${PROJECT_NAME}/solution_genesys2/impl/ip" *.zip] + file copy -force "${IP_FILE}" "${ROOT_DIR}/hls_files/cordicabs_16_4_6/ip/${XILINX_MAJOR}_genesys2.zip" + } +} + +close_solution +close_project + +exit 0 diff --git a/sources/CCordicAbs/CCordicAbs.hpp b/sources/CCordicAbs/CCordicAbs.hpp index 0e63cc4..8911546 100644 --- a/sources/CCordicAbs/CCordicAbs.hpp +++ b/sources/CCordicAbs/CCordicAbs.hpp @@ -50,18 +50,24 @@ public: static constexpr const unsigned Out_I = In_I + 3; static constexpr const unsigned nb_stages = Tnb_stages; - static constexpr unsigned kn_i = unsigned(kn_values[nb_stages - 1] * double(1U << 3)); // 3 bits are enough + static constexpr unsigned kn_i = unsigned(kn_values[nb_stages - 1] * double(1U << 4)); // 4 bits are enough static constexpr unsigned in_scale_factor = unsigned(1U << (In_W - In_I)); static constexpr unsigned out_scale_factor = unsigned(1U << (Out_W - Out_I)); static constexpr int64_t scale_cordic(int64_t in) { - return in * kn_i / 8U; + return in * kn_i / 16U; } static constexpr double scale_cordic(double in) { return in * kn_values[nb_stages - 1]; } + static constexpr ap_uint scale_cordic(ap_uint in) { + return ap_uint(ap_uint(in * ap_uint<4>(kn_i)) >> 4U); + } + + +#if !defined (XILINX_MAJOR) || XILINX_MAJOR >= 2020 static constexpr ap_uint process(ap_int re_in, ap_int im_in) { ap_int A[nb_stages + 1]; ap_int B[nb_stages + 1]; @@ -88,6 +94,7 @@ public: return ap_uint(A[nb_stages]); } +#endif #if !defined(__SYNTHESIS__) && defined(SOFTWARE) static constexpr uint64_t process(int64_t re_in, int64_t im_in) { diff --git a/sources/top_level/top_level_cordic.cpp b/sources/top_level/top_level_cordic.cpp index c6bdd2e..bef7627 100644 --- a/sources/top_level/top_level_cordic.cpp +++ b/sources/top_level/top_level_cordic.cpp @@ -19,11 +19,11 @@ #include "top_level_cordic.hpp" -#include "hls_stream.h" +#if !defined (XILINX_MAJOR) || XILINX_MAJOR >= 2020 void cordic_abs_16_4_6( - ap_int re_in, - ap_int im_in, + ap_int re_in, + ap_int im_in, ap_uint & module_out) { static constexpr const cordic_abs_t cordic_abs {}; @@ -35,3 +35,51 @@ void cordic_abs_16_4_6( module_out = m; } + +#else + +constexpr unsigned In_W = cordic_abs_t::In_W; +constexpr unsigned Out_W = cordic_abs_t::Out_W; +constexpr unsigned nb_stages = cordic_abs_t::nb_stages; + +static ap_uint process(ap_int re_in, ap_int im_in) { + ap_int A[nb_stages + 1]; + ap_int B[nb_stages + 1]; + + A[0] = hls_abs::abs(re_in); + B[0] = hls_abs::abs(im_in); + + for (uint16_t u = 0; u < nb_stages; u++) { + const bool sign_B = B[u] > 0; + + const ap_int step_A = B[u] >> u; + const ap_int step_B = A[u] >> u; + + const ap_int tmp_B = sign_B + ? ap_int(B[u] - step_B) + : ap_int(B[u] + step_B); + const ap_int tmp_A = sign_B + ? ap_int(A[u] + step_A) + : ap_int(A[u] - step_A); + + A[u + 1] = tmp_A; + B[u + 1] = tmp_B; + } + + return ap_uint(A[nb_stages]); +} + +void cordic_abs_16_4_6( + ap_int re_in, + ap_int im_in, + ap_uint & module_out) { + + const ap_int x = re_in; + const ap_int y = im_in; + + const ap_uint m = process(x, y); + + module_out = m; +} + +#endif diff --git a/sources/top_level/top_level_cordic_tb.cpp b/sources/top_level/top_level_cordic_tb.cpp index e4ecb77..5b13a1e 100644 --- a/sources/top_level/top_level_cordic_tb.cpp +++ b/sources/top_level/top_level_cordic_tb.cpp @@ -40,17 +40,21 @@ int main(int argc, char ** argv) { string input_fn = "../data/input.dat"; // _8_14_4_17_5_19_7_12 - if (argc == 2) { + if (argc > 1) { input_fn = string(argv[1]); } - constexpr unsigned n_lines = 100; + constexpr unsigned max_lines = 10000; + unsigned n_lines = max_lines; + if (argc > 2) { + n_lines = unsigned(std::stoi(string(argv[2]))); + } - ap_int re_values_in[n_lines]; - ap_int im_values_in[n_lines]; - ap_uint values_out[n_lines]; + ap_int re_values_in[max_lines]; + ap_int im_values_in[max_lines]; + ap_uint values_out[max_lines]; - double results[n_lines]; + double results[max_lines]; FILE * INPUT = fopen(input_fn.c_str(), "r"); @@ -81,7 +85,6 @@ int main(int argc, char ** argv) { cordic_abs_16_4_6(re_values_in[iter], im_values_in[iter], values_out[iter]); - // Display the results // cout << "Series " << iter; // cout << " Outcome: "; @@ -91,7 +94,7 @@ int main(int argc, char ** argv) { // << cordic_abs_t::scale_cordic(values_out[iter].to_double()) / cordic_abs_t::out_scale_factor << " " // << results[iter] << std::endl; - const double dbl_res = cordic_abs_t::scale_cordic(values_out[iter].to_double() ) / cordic_abs_t::out_scale_factor; + const double dbl_res = cordic_abs_t::scale_cordic(values_out[iter].to_double()) / cordic_abs_t::out_scale_factor; if (std::abs(dbl_res - results[iter]) > abs_margin) { counted_errors++;