From ee535621f94d8d6a28194985594988341637646c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camille=20Moni=C3=A8re?= Date: Thu, 14 Apr 2022 17:21:16 +0200 Subject: [PATCH] Working unit tests --- sources/CCordicAbs/CCordicAbs.hpp | 48 +++- sources/hls_abs/hls_abs.hpp | 47 ++++ sources/tb/cordicabs_tb.cpp | 401 +++++------------------------- 3 files changed, 161 insertions(+), 335 deletions(-) create mode 100644 sources/hls_abs/hls_abs.hpp diff --git a/sources/CCordicAbs/CCordicAbs.hpp b/sources/CCordicAbs/CCordicAbs.hpp index eac9a71..0aa0b90 100644 --- a/sources/CCordicAbs/CCordicAbs.hpp +++ b/sources/CCordicAbs/CCordicAbs.hpp @@ -2,7 +2,7 @@ * * Copyright 2022 Camille "DrasLorus" Monière. * - * This file is part of CORDIC_ABS_APFX. + * This file is part of CORDIC_Abs_APFX. * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * Lesser General Public License as published by the Free Software Foundation, either version 3 of @@ -30,6 +30,8 @@ #include #include +#include "hls_abs/hls_abs.hpp" + #define cst_abs(x) (x > 0 ? x : -x) template @@ -53,8 +55,50 @@ public: return in * kn_i / 8U; } + static constexpr int64_t process(int64_t re_in, int64_t im_in) { + + const int64_t re_x = re_in; + const int64_t im_x = im_in; + + int64_t A = cst_abs(re_x); + int64_t B = cst_abs(im_x); + + for (uint16_t u = 1; u < nb_stages + 1; u++) { + + const bool sign_B = B > 0; + + const int64_t step_A = +B / int64_t(1U << (u - 1)); + const int64_t step_B = -A / int64_t(1U << (u - 1)); + + B = sign_B ? B + step_B : B - step_B; + A = sign_B ? A + step_A : A - step_A; + } + + return A; + } + + static constexpr ap_int process(ap_int re_in, ap_int im_in) { + + ap_int A = hls_abs::abs(re_in); + ap_int B = hls_abs::abs(im_in); + + for (uint16_t u = 1; u < nb_stages + 1; u++) { + + const bool sign_B = B > 0; + + const int64_t step_A = (+B) >> (u - 1); + const int64_t step_B = (-A) >> (u - 1); + + B = sign_B ? B + step_B : B - step_B; + A = sign_B ? A + step_A : A - step_A; + } + + return A; + } + #if !defined(__SYNTHESIS__) && defined(SOFTWARE) - static constexpr int64_t process(const std::complex & x_in) { + static constexpr int64_t + process(const std::complex & x_in) { const int64_t re_x = x_in.real(); const int64_t im_x = x_in.imag(); diff --git a/sources/hls_abs/hls_abs.hpp b/sources/hls_abs/hls_abs.hpp new file mode 100644 index 0000000..87e65fb --- /dev/null +++ b/sources/hls_abs/hls_abs.hpp @@ -0,0 +1,47 @@ +#ifndef _HLS_ABS_HPP_ +#define _HLS_ABS_HPP_ + +#include + +template +struct hls_abs { + template + static ap_uint<_Tsize> abs(ap_int<_Tsize> value); + + template + static ap_uint<_Tsize> abs(ap_uint<_Tsize> value) { + return value; + } +}; + +template <> +struct hls_abs { + template + static ap_uint<_Tsize> abs(ap_int<_Tsize> value) { + const ap_uint<_Tsize - 1> u_value = value.range(_Tsize - 2, 0); + const bool sign_value = value.bit(_Tsize - 1); + + const ap_uint<_Tsize> a_value = sign_value + ? ap_uint<_Tsize>((~u_value) + 1U) + : ap_uint<_Tsize>(u_value); + + return a_value; + } +}; + +template <> +struct hls_abs { + template + static ap_uint<_Tsize + 1> abs(ap_int<_Tsize> value) { + const ap_uint<_Tsize - 1> u_value = value.range(_Tsize - 2, 0); + const bool sign_value = value.bit(_Tsize - 1); + + const ap_uint<_Tsize + 1> a_value = sign_value + ? ap_uint<_Tsize + 1>((~u_value) + true) + : ap_uint<_Tsize + 1>(u_value); + + return a_value; + } +}; + +#endif \ No newline at end of file diff --git a/sources/tb/cordicabs_tb.cpp b/sources/tb/cordicabs_tb.cpp index cb64e51..7664e4b 100644 --- a/sources/tb/cordicabs_tb.cpp +++ b/sources/tb/cordicabs_tb.cpp @@ -28,8 +28,8 @@ using namespace std; using Catch::Matchers::Floating::WithinAbsMatcher; #if defined(SOFTWARE) -TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDIC]") { - SECTION("W:16 - I:4 - Stages:6 - q:64") { +TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDICABS]") { + SECTION("W:16 - I:4 - Stages:6") { typedef CCordicAbs<16, 4, 6> cordic_abs; string input_fn = "../data/input.dat"; // _8_14_4_17_5_19_7_12 @@ -48,7 +48,7 @@ TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDIC]") { for (unsigned i = 0; i < n_lines; i++) { double a, b, r; fscanf(INPUT, "%lf,%lf,%lf\n", &a, &b, &r); - + const complex c {a, b}; values_in[i] = c; @@ -58,6 +58,61 @@ TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDIC]") { fclose(INPUT); + // Save the results to a file + // ofstream outfile("results.dat"); + + constexpr double abs_margin = double(1 << (cordic_abs::Out_I - 1)) * 2. / 100.; + + // Executing the encoder + for (unsigned iter = 0; iter < n_lines; iter++) { + // Execute + + values_out[iter] = cordic_abs::process(values_in[iter]); + + // Display the results + // cout << "Series " << iter; + // cout << " Outcome: "; + + // outfile << values_in[iter].real() << " " << values_in[iter].imag() << " " << values_out[iter] << " " << results[iter] << endl; + + REQUIRE_THAT(values_out[iter], WithinAbsMatcher(results[iter], abs_margin)); + } + // outfile.close(); + } +} +#endif + +TEST_CASE("Constexpr CordicAbs works with AP-Types", "[CORDICABS]") { + SECTION("W:16 - I:4 - Stages:6") { + typedef CCordicAbs<16, 4, 6> cordic_abs; + + string input_fn = "../data/input.dat"; // _8_14_4_17_5_19_7_12 + string output_fn = "../data/output.dat"; // _8_14_4_17_5_19_7_12 + + constexpr unsigned n_lines = 100000; + + ap_int re_values_in[n_lines]; + ap_int im_values_in[n_lines]; + ap_uint values_out[n_lines]; + + double results[n_lines]; + + FILE * INPUT = fopen(input_fn.c_str(), "r"); + + // Init test vector + for (unsigned i = 0; i < n_lines; i++) { + double a, b, r; + fscanf(INPUT, "%lf,%lf,%lf\n", &a, &b, &r); + + re_values_in[i] = int64_t(floor(a * cordic_abs::in_scale_factor)); + im_values_in[i] = int64_t(floor(b * cordic_abs::in_scale_factor)); + + const double ac = std::abs(complex {a, b}); + results[i] = ac; + } + + fclose(INPUT); + // Save the results to a file ofstream outfile("results.dat"); @@ -67,15 +122,20 @@ TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDIC]") { for (unsigned iter = 0; iter < n_lines; iter++) { // Execute - values_out[iter] = cordic_abs::process(values_in[iter]); + values_out[iter] = cordic_abs::process(re_values_in[iter], im_values_in[iter]); // Display the results // cout << "Series " << iter; // cout << " Outcome: "; - outfile << values_in[iter].real() << " " << values_in[iter].imag() << " " << values_out[iter] << " " << results[iter] << endl; + outfile << re_values_in[iter].to_double() / cordic_abs::in_scale_factor << " " + << im_values_in[iter].to_double() / cordic_abs::in_scale_factor << " " + << cordic_abs::scale_cordic(values_out[iter].to_double()) / cordic_abs::out_scale_factor << " " + << results[iter] << endl; - REQUIRE_THAT(values_out[iter], WithinAbsMatcher(results[iter], abs_margin)); + const double dbl_res = cordic_abs::scale_cordic(values_out[iter].to_double()) / cordic_abs::out_scale_factor; + + REQUIRE_THAT(dbl_res, WithinAbsMatcher(results[iter], abs_margin)); } outfile.close(); @@ -84,334 +144,10 @@ TEST_CASE("Constexpr CordicAbs works with C-Types", "[CORDIC]") { // Return 0 if the test passed } } -#endif - -// TEST_CASE("ROM-based Cordic works with AP-Types", "[CORDIC]") { -// constexpr unsigned n_lines = 100000; - -// SECTION("W:16 - I:4 - Stages:6 - q:64") { -// typedef CCordicAbs<16, 4, 6> cordic_abs; - -// static constexpr cordic_abs cordic {}; - -// string input_fn = "../data/input.dat"; - -// constexpr double rotation = cordic_abs::rotation; -// constexpr double q = cordic_abs::rom_cordic.q; -// constexpr uint64_t cnt_mask = 0xFF; // Value dependant of the way the ROM is initialized - -// constexpr unsigned Out_W = cordic_abs::Out_W; -// constexpr unsigned In_W = cordic_abs::In_W; - -// ap_int values_re_in[n_lines]; -// ap_int values_im_in[n_lines]; -// ap_int values_re_out[n_lines]; -// ap_int values_im_out[n_lines]; - -// double results_re[n_lines]; -// double results_im[n_lines]; - -// // ofstream out_stream; - -// ifstream INPUT(input_fn); - -// // Init test vector -// for (unsigned i = 0; i < n_lines; i++) { -// double a, b, r; -// INPUT >> a >> b >> r; - -// const complex c {a, b}; -// values_re_in[i] = int64_t(a * double(cordic_abs::in_scale_factor)); -// values_im_in[i] = int64_t(b * double(cordic_abs::in_scale_factor)); - -// const complex e = c * exp(complex(0., rotation / q * (i & cnt_mask))); -// results_re[i] = e.real(); -// results_im[i] = e.imag(); -// } - -// INPUT.close(); - -// // Save the results to a file -// // out_stream.open("results_ap.dat"); -// // FILE * romf = fopen("rom.dat", "w"); - -// constexpr double abs_margin = double(1 << (cordic.Out_I - 1)) * 2. / 100.; - -// // Executing the encoder -// for (unsigned iter = 0; iter < n_lines; iter++) { -// // Execute -// const uint8_t counter = uint8_t(iter & cnt_mask); - -// // if (iter < cnt_mask + 1) -// // fprintf(romf, "%03d\n", (uint16_t) cordic.rom_cordic.rom[counter]); - -// cordic_abs::process( -// values_re_in[iter], values_im_in[iter], -// counter, -// values_re_out[iter], values_im_out[iter]); - -// // Display the results -// // cout << "Series " << iter; -// // cout << " Outcome: "; - -// // out_stream << values_re_out[iter].to_int64() << " " << values_im_out[iter].to_int64() << " " << results_re[iter] << " " << results_im[iter] << endl; - -// REQUIRE_THAT(values_re_out[iter].to_double() * 5. / 8. / cordic_abs::out_scale_factor, WithinAbsMatcher(results_re[iter], abs_margin)); -// REQUIRE_THAT(values_im_out[iter].to_double() * 5. / 8. / cordic_abs::out_scale_factor, WithinAbsMatcher(results_im[iter], abs_margin)); -// } -// // out_stream.close(); -// // fclose(romf); - -// // Compare the results file with the golden results -// // int retval = 0; -// // Return 0 if the test passed -// } - -// SECTION("W:16 - I:4 - Stages:6 - q:64 - internal scaling") { -// typedef CCordicAbs<16, 4, 6> cordic_abs; -// static constexpr cordic_abs cordic {}; - -// string input_fn = "../data/input.dat"; - -// constexpr double rotation = cordic_abs::rotation; -// constexpr double q = cordic_abs::rom_cordic.q; -// constexpr uint64_t cnt_mask = 0xFF; // Value dependant of the way the ROM is initialized - -// constexpr unsigned Out_W = cordic_abs::Out_W; -// constexpr unsigned In_W = cordic_abs::In_W; - -// ap_int values_re_in[n_lines]; -// ap_int values_im_in[n_lines]; -// ap_int values_re_out[n_lines]; -// ap_int values_im_out[n_lines]; - -// double results_re[n_lines]; -// double results_im[n_lines]; - -// // ofstream out_stream; - -// ifstream INPUT(input_fn); - -// // Init test vector -// for (unsigned i = 0; i < n_lines; i++) { -// double a, b, r; -// INPUT >> a >> b >> r; - -// const complex c {a, b}; -// values_re_in[i] = int64_t(a * double(cordic_abs::in_scale_factor)); -// values_im_in[i] = int64_t(b * double(cordic_abs::in_scale_factor)); - -// const complex e = c * exp(complex(0., rotation / q * (i & cnt_mask))); -// results_re[i] = e.real(); -// results_im[i] = e.imag(); -// } - -// INPUT.close(); - -// // Save the results to a file -// // out_stream.open("results_ap.dat"); -// // FILE * romf = fopen("rom.dat", "w"); - -// constexpr double abs_margin = double(1 << (cordic.Out_I - 1)) * 3. / 100.; // Internal scaling create noise - -// // Executing the encoder -// for (unsigned iter = 0; iter < n_lines; iter++) { -// // Execute -// const uint8_t counter = uint8_t(iter & cnt_mask); - -// // if (iter < cnt_mask + 1) -// // fprintf(romf, "%03d\n", (uint16_t) cordic.rom_cordic.rom[counter]); - -// cordic_abs::process( -// values_re_in[iter], values_im_in[iter], -// counter, -// values_re_out[iter], values_im_out[iter]); - -// // Display the results -// // cout << "Series " << iter; -// // cout << " Outcome: "; - -// // out_stream << values_re_out[iter].to_int64() << " " << values_im_out[iter].to_int64() << " " << results_re[iter] << " " << results_im[iter] << endl; - -// REQUIRE_THAT(cordic_abs::scale_cordic(values_re_out[iter]).to_double() / cordic_abs::out_scale_factor, -// WithinAbsMatcher(results_re[iter], -// abs_margin)); -// REQUIRE_THAT(cordic_abs::scale_cordic(values_im_out[iter]).to_double() / cordic_abs::out_scale_factor, -// WithinAbsMatcher(results_im[iter], -// abs_margin)); -// } -// // out_stream.close(); -// // fclose(romf); - -// // Compare the results file with the golden results -// // int retval = 0; -// // Return 0 if the test passed -// } - -// SECTION("W:16 - I:4 - Stages:6 - q:64 - divider:4") { -// typedef CCordicAbs<16, 4, 6> cordic_abs; - -// static constexpr cordic_abs cordic {}; - -// string input_fn = "../data/input.dat"; - -// constexpr double rotation = cordic_abs::rotation; -// constexpr double q = cordic_abs::rom_cordic.q; -// constexpr uint64_t cnt_mask = 0xFF; // Value dependant of the way the ROM is initialized - -// constexpr unsigned Out_W = cordic_abs::Out_W; -// constexpr unsigned In_W = cordic_abs::In_W; - -// ap_int values_re_in[n_lines]; -// ap_int values_im_in[n_lines]; -// ap_int values_re_out[n_lines]; -// ap_int values_im_out[n_lines]; - -// double results_re[n_lines]; -// double results_im[n_lines]; - -// // ofstream out_stream; - -// ifstream INPUT(input_fn); - -// // Init test vector -// for (unsigned i = 0; i < n_lines; i++) { -// double a, b, r; -// INPUT >> a >> b >> r; - -// const complex c {a, b}; -// values_re_in[i] = int64_t(a * double(cordic_abs::in_scale_factor)); -// values_im_in[i] = int64_t(b * double(cordic_abs::in_scale_factor)); - -// const complex e = c * exp(complex(0., rotation / q * (i & cnt_mask))); -// results_re[i] = e.real(); -// results_im[i] = e.imag(); -// } - -// INPUT.close(); - -// // Save the results to a file -// // out_stream.open("results_ap.dat"); -// // FILE * romf = fopen("rom.dat", "w"); - -// constexpr double abs_margin = double(1 << (cordic.Out_I - 1)) * 2. / 100.; - -// // Executing the encoder -// for (unsigned iter = 0; iter < n_lines; iter++) { -// // Execute -// const uint8_t counter = uint8_t(iter & cnt_mask); - -// // if (iter < cnt_mask + 1) -// // fprintf(romf, "%03d\n", (uint16_t) cordic.rom_cordic.rom[counter]); - -// cordic_abs::process( -// values_re_in[iter], values_im_in[iter], -// counter, -// values_re_out[iter], values_im_out[iter]); - -// // Display the results -// // cout << "Series " << iter; -// // cout << " Outcome: "; - -// // out_stream << values_re_out[iter].to_int64() << " " << values_im_out[iter].to_int64() << " " << results_re[iter] << " " << results_im[iter] << endl; - -// REQUIRE_THAT(values_re_out[iter].to_double() * 5. / 8. / cordic_abs::out_scale_factor, WithinAbsMatcher(results_re[iter], abs_margin)); -// REQUIRE_THAT(values_im_out[iter].to_double() * 5. / 8. / cordic_abs::out_scale_factor, WithinAbsMatcher(results_im[iter], abs_margin)); -// } -// // out_stream.close(); -// // fclose(romf); - -// // Compare the results file with the golden results -// // int retval = 0; -// // Return 0 if the test passed -// } - -// SECTION("W:16 - I:4 - Stages:6 - q:64 - divider:4 - internal scaling") { -// typedef CCordicAbs<16, 4, 6> cordic_abs; -// static constexpr cordic_abs cordic {}; - -// string input_fn = "../data/input.dat"; - -// constexpr double rotation = cordic_abs::rotation; -// constexpr double q = cordic_abs::rom_cordic.q; -// constexpr uint64_t cnt_mask = 0xFF; // Value dependant of the way the ROM is initialized - -// constexpr unsigned Out_W = cordic_abs::Out_W; -// constexpr unsigned In_W = cordic_abs::In_W; - -// ap_int values_re_in[n_lines]; -// ap_int values_im_in[n_lines]; -// ap_int values_re_out[n_lines]; -// ap_int values_im_out[n_lines]; - -// double results_re[n_lines]; -// double results_im[n_lines]; - -// ofstream out_stream; - -// ifstream INPUT(input_fn); - -// // Init test vector -// for (unsigned i = 0; i < n_lines; i++) { -// double a, b, r; -// INPUT >> a >> b >> r; - -// const complex c {a, b}; -// values_re_in[i] = int64_t(a * double(cordic_abs::in_scale_factor)); -// values_im_in[i] = int64_t(b * double(cordic_abs::in_scale_factor)); - -// const complex e = c * exp(complex(0., rotation / q * (i & cnt_mask))); -// results_re[i] = e.real(); -// results_im[i] = e.imag(); -// } - -// INPUT.close(); - -// // Save the results to a file -// // out_stream.open("results_ap.dat"); -// // FILE * romf = fopen("rom.dat", "w"); - -// constexpr double abs_margin = double(1 << (cordic.Out_I - 1)) * 3. / 100.; // Internal scaling creates noise - -// // Executing the encoder -// for (unsigned iter = 0; iter < n_lines; iter++) { -// // Execute -// const uint8_t counter = uint8_t(iter & cnt_mask); - -// // if (iter < cnt_mask + 1) -// // fprintf(romf, "%03d\n", (uint16_t) cordic.rom_cordic.rom[counter]); - -// cordic_abs::process( -// values_re_in[iter], values_im_in[iter], -// counter, -// values_re_out[iter], values_im_out[iter]); - -// // Display the results -// // cout << "Series " << iter; -// // cout << " Outcome: "; - -// // out_stream << cordic_abs::scale_cordic(values_re_out[iter]).to_double() / cordic_abs::out_scale_factor << " " -// // << cordic_abs::scale_cordic(values_im_out[iter]).to_double() / cordic_abs::out_scale_factor << " " -// // << results_re[iter] << " " -// // << results_im[iter] << endl; - -// REQUIRE_THAT(cordic_abs::scale_cordic(values_re_out[iter]).to_double() / cordic_abs::out_scale_factor, -// WithinAbsMatcher(results_re[iter], abs_margin)); -// REQUIRE_THAT(cordic_abs::scale_cordic(values_im_out[iter]).to_double() / cordic_abs::out_scale_factor, -// WithinAbsMatcher(results_im[iter], abs_margin)); -// } -// // out_stream.close(); -// // fclose(romf); - -// // Compare the results file with the golden results -// // int retval = 0; -// // Return 0 if the test passed -// } -// } #if defined(SOFTWARE) -TEST_CASE("Constexpr CordicAbs are evaluated during compilation.", "[CORDIC]") { - SECTION("W:16 - I:4 - Stages:6 - q:64 - C-Types") { +TEST_CASE("Constexpr CordicAbs are evaluated during compilation.", "[CORDICABS]") { + SECTION("W:16 - I:4 - Stages:6 - C-Types") { typedef CCordicAbs<16, 4, 6> cordic_abs; constexpr const complex value_in[3] = {(1U << 12) * 97, -(1U << 12) * 33, (1U << 3) * 12}; @@ -433,7 +169,6 @@ TEST_CASE("Constexpr CordicAbs are evaluated during compilation.", "[CORDIC]") { static_assert(res12 == res22, "Test"); REQUIRE_FALSE(res12 == cordic_abs::process(complex(1, 0))); REQUIRE(res12 == cordic_abs::process(value_in[2])); - } } #endif \ No newline at end of file