Add a synthesis-friendly option and yet another formatting

- If ENABLE_SOFTWARE is true, the behavior is the same than before. All
  tests and functions relying on std::complex are obfuscated otherwise.
  This is required for Xilinx tools.
This commit is contained in:
Camille Monière 2022-03-14 17:55:16 +01:00
parent 4a0236baea
commit 7707d12e98
Signed by: moniere
GPG key ID: 188DD5B072181C0F
8 changed files with 108 additions and 28 deletions

View file

@ -41,6 +41,9 @@ option (EXPORT_COMMANDS "export compile commands, for use with clangd for exampl
option (ENABLE_XILINX "use Xilinx provided proprietary headers." OFF)
option (ENABLE_TESTING "use Catch2 in conjunction with CTest as a test suite." ON)
option (ENABLE_SOFTWARE
"use C++ standard library types (like std::complex). Unsuitable for synthesis." ON
)
option (PEDANTIC "use -Wall and -pedantic." ON)
@ -94,6 +97,10 @@ if ((NOT EXISTS ${AP_INCLUDE_DIR}/ap_int.h) OR (NOT EXISTS ${AP_INCLUDE_DIR}/ap_
)
endif ()
if (ENABLE_SOFTWARE)
add_compile_definitions (SOFTWARE=1)
endif ()
set (
ROM_TYPE
"ml"

View file

@ -28,20 +28,21 @@
#include <cassert>
#include "definitions.hpp"
template <unsigned In_W, unsigned NStages, unsigned Tq, unsigned divider = 2>
class CRomGeneratorConst {
static_assert(In_W > 0, "Inputs can't be on zero bits.");
static_assert(NStages < 8, "7 stages of CORDIC is the maximum supported.");
static_assert(NStages > 1, "2 stages of CORDIC is the minimum.");
static_assert(((divider - 1) & divider) == 0, "divider must be a power of 2.");
static_assert(is_pow_2(divider), "divider must be a power of 2.");
public:
static constexpr double pi = 3.14159265358979323846;
static constexpr double two_pi = 2 * pi;
static constexpr double half_pi = pi * 0.5;
static constexpr double rotation = pi / divider;
static constexpr double q = Tq;
static constexpr uint32_t max_length = 2 * divider * Tq; // 2pi / (pi / divider) * q
static constexpr unsigned max_length = 2 * divider * Tq; // 2pi / (pi / divider) * q
static constexpr unsigned addr_length = needed_bits(max_length - 1);
static constexpr int64_t scale_factor = int64_t(1U << (In_W - 1));
static constexpr double atanDbl[28] {
@ -140,9 +141,13 @@ void generate_rom_header_cst(const char * filename) {
char rom_name[64];
snprintf(rom_name, 64, "cst_%u_%u_%u_%u", In_W, NStages, Tq, divider);
fprintf(rom_file, "/** @file %s\n * THIS FILE IS GENERATED AUTOMATICALY, DO NOT EDIT IT!\n */\n", filename);
fprintf(rom_file, "#ifndef %s\n#define %s\n\n", upper_file_def, upper_file_def);
fprintf(rom_file, "#include <cstdint>\n\n");
fprintf(rom_file, "namespace cordic_roms {\n");
fprintf(rom_file, "namespace cordic_roms {\n\n");
fprintf(rom_file, "constexpr uint64_t %s_size = %d;\n\n", rom_name, rom.max_length);
fprintf(rom_file, "constexpr uint8_t %s[%d] = {\n ", rom_name, rom.max_length);
for (uint16_t u = 0; u < rom.max_length - 1; u++) {

View file

@ -25,18 +25,22 @@
#include <complex>
#include <cstdint>
#include "definitions.hpp"
template <unsigned In_W, unsigned NStages, unsigned Tq, unsigned divider = 2>
class CRomGeneratorML {
static_assert(In_W > 0, "Inputs can't be on zero bits.");
static_assert(NStages < 8, "7 stages of CORDIC is the maximum supported.");
static_assert(NStages > 1, "2 stages of CORDIC is the minimum.");
static_assert(NStages > 1, "2 stages of CORDIC is the minimum.");
static_assert(((divider - 1) & divider) == 0, "divider must be a power of 2.");
static_assert(is_pow_2(divider), "divider must be a power of 2.");
public:
static constexpr double rotation = M_PI / divider;
static constexpr double rotation = pi / divider;
static constexpr double q = Tq;
static constexpr uint32_t max_length = 2 * divider * Tq; // 2pi / (pi / divider) * q
static constexpr unsigned max_length = 2 * divider * Tq; // 2pi / (pi / divider) * q
static constexpr unsigned addr_length = needed_bits(max_length - 1);
static constexpr int64_t scale_factor = int64_t(1U << (In_W - 1));
private:
@ -115,9 +119,13 @@ void generate_rom_header_ml(const char * filename) {
char rom_name[64];
snprintf(rom_name, 64, "ml_%u_%u_%u_%u", In_W, NStages, Tq, divider);
fprintf(rom_file, "/** @file %s\n * THIS FILE IS GENERATED AUTOMATICALY, DO NOT EDIT IT!\n */\n", filename);
fprintf(rom_file, "#ifndef %s\n#define %s\n\n", upper_file_def, upper_file_def);
fprintf(rom_file, "#include <cstdint>\n\n");
fprintf(rom_file, "namespace cordic_roms {\n");
fprintf(rom_file, "namespace cordic_roms {\n\n");
fprintf(rom_file, "constexpr uint64_t %s_size = %d;\n\n", rom_name, rom.max_length);
fprintf(rom_file, "constexpr uint8_t %s[%d] = {\n ", rom_name, rom.max_length);
for (uint16_t u = 0; u < rom.max_length - 1; u++) {

View file

@ -0,0 +1,50 @@
/*
*
* Copyright 2022 Camille "DrasLorus" Monière.
*
* This file is part of CORDIC_Rotate_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
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef _DEFINITIONS_HPP_
#define _DEFINITIONS_HPP_
#include <cstddef>
#include <cstdint>
#ifdef M_PI
constexpr double pi = M_PI;
#else
constexpr double pi = 3.14159265358979323846;
#endif
constexpr double half_pi = pi / 2;
constexpr double inv_pi = 1 / pi;
constexpr double two_pi = 2 * pi;
constexpr double inv_2pi = 0.5 * inv_pi;
constexpr uint32_t needed_bits(uint32_t value) {
uint32_t result = 0;
while (value > 0) {
result++;
value >>= 1;
}
return result;
}
constexpr bool is_pow_2(uint32_t value) {
return (1U << (needed_bits(value) - 1)) == value;
}
#endif // _DEFINITIONS_HPP_

View file

@ -37,7 +37,7 @@ class CCordicRotateConstexpr {
static_assert(TIn_W > 0, "Inputs can't be on zero bits.");
static_assert(Tnb_stages < 8, "7 stages of CORDIC is the maximum supported.");
static_assert(Tnb_stages > 1, "2 stages of CORDIC is the minimum.");
static_assert(((divider - 1) & divider) == 0, "divider must be a power of 2.");
static_assert(is_pow_2(divider), "divider must be a power of 2.");
public:
// ``` GNU Octave
@ -54,18 +54,20 @@ public:
static constexpr unsigned Out_I = In_I + 2;
static constexpr unsigned nb_stages = Tnb_stages;
static constexpr uint64_t kn_i = uint64_t(kn_values[nb_stages - 1] * double(1U << 3)); // 3 bits are enough
static constexpr uint64_t in_scale_factor = uint64_t(1U << (In_W - In_I));
static constexpr uint64_t out_scale_factor = uint64_t(1U << (Out_W - Out_I));
static constexpr unsigned kn_i = unsigned(kn_values[nb_stages - 1] * double(1U << 3)); // 3 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 double rotation = CRomGeneratorConst<TIn_W, Tnb_stages, Tq, divider>::rotation;
static constexpr unsigned addr_length = CRomGeneratorConst<TIn_W, Tnb_stages, Tq, divider>::addr_length;
static constexpr int64_t scale_cordic(int64_t in) {
return in * kn_i / 8U;
}
#if !defined(__SYNTHESIS__) && defined(SOFTWARE)
static constexpr std::complex<int64_t> cordic(std::complex<int64_t> x_in,
uint8_t counter) {
uint64_t counter) {
int64_t A = x_in.real();
int64_t B = x_in.imag();
@ -90,13 +92,12 @@ public:
return {(A), (B)};
}
#ifndef __SYNTHESIS__
static constexpr double scale_cordic(double in) {
return in * kn_values[nb_stages - 1];
}
static constexpr std::complex<double> cordic(std::complex<double> x_in,
uint8_t counter) {
uint64_t counter) {
const std::complex<int64_t> fx_x_in(int64_t(x_in.real() * double(in_scale_factor)),
int64_t(x_in.imag() * double(in_scale_factor)));
@ -112,7 +113,7 @@ public:
}
static void cordic(const ap_int<In_W> & re_in, const ap_int<In_W> & im_in,
const ap_uint<8> & counter,
const ap_uint<addr_length> & counter,
ap_int<Out_W> & re_out, ap_int<Out_W> & im_out) {
const ap_uint<nb_stages + 1> R = rom_cordic.rom[counter];

View file

@ -32,6 +32,7 @@
#include "CCordicRotateRomTemplate.hpp"
#include "CordicRoms/cordic_rom_@ROM_TYPE@_@CORDIC_W@_@CORDIC_STAGES@_@CORDIC_Q@_@CORDIC_DIVIDER@.hpp"
#include "definitions.hpp"
#ifndef KN_STATIC_TABLE_DEFINED
#define KN_STATIC_TABLE_DEFINED 1
@ -48,7 +49,7 @@ class CCordicRotateRom<TIn_I, @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDIC_Q
static_assert(@CORDIC_W@ > 0, "Inputs can't be on zero bits.");
static_assert(@CORDIC_STAGES@ < 8, "7 stages of CORDIC is the maximum supported.");
static_assert(@CORDIC_STAGES@ > 1, "2 stages of CORDIC is the minimum.");
static_assert(((@CORDIC_DIVIDER@ - 1) & @CORDIC_DIVIDER@) == 0, "divider must be a power of 2.");
static_assert(is_pow_2(@CORDIC_DIVIDER@), "divider must be a power of 2.");
public:
static constexpr unsigned In_W = @CORDIC_W@;
@ -62,12 +63,14 @@ public:
static constexpr uint64_t in_scale_factor = uint64_t(1U << (In_W - In_I));
static constexpr uint64_t out_scale_factor = uint64_t(1U << (Out_W - Out_I));
static constexpr double rotation = M_PI / @CORDIC_DIVIDER@;
static constexpr double rotation = pi / @CORDIC_DIVIDER@;
static constexpr unsigned addr_length = cordic_roms::@ROM_TYPE@_@CORDIC_W@_@CORDIC_STAGES@_@CORDIC_Q@_@CORDIC_DIVIDER@_size;
static constexpr int64_t scale_cordic(int64_t in) {
return in * kn_i / 8U;
}
#if !defined(__SYNTHESIS__) && defined(SOFTWARE)
static constexpr std::complex<int64_t> cordic(std::complex<int64_t> x_in,
uint8_t counter) {
@ -94,7 +97,6 @@ public:
return {(A), (B)};
}
#ifndef __SYNTHESIS__
static constexpr double scale_cordic(double in) {
return in * kn_values[nb_stages - 1];
}

View file

@ -27,9 +27,9 @@ using namespace std;
using Catch::Matchers::Floating::WithinAbsMatcher;
typedef CCordicRotateRom<4, @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDIC_Q@, @CORDIC_DIVIDER@> cordic_rom;
#if defined(SOFTWARE)
TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDIC_Q@, @CORDIC_DIVIDER@) works with C-Types", "[CORDIC]") {
SECTION("W:@CORDIC_W@ - I:4 - Stages:@CORDIC_STAGES@ - q:@CORDIC_Q@ - div:@CORDIC_DIVIDER@") {
static constexpr cordic_rom cordic {};
@ -75,6 +75,7 @@ TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDI
}
}
}
#endif
TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDIC_Q@, @CORDIC_DIVIDER@) works with AP-Types", "[CORDIC]") {
constexpr unsigned n_lines = 100000;
@ -195,6 +196,7 @@ TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDI
}
}
#if defined(SOFTWARE)
TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDIC_Q@, @CORDIC_DIVIDER@) constexpr are evaluated during compilation.", "[CORDIC]") {
SECTION("W:@CORDIC_W@ - I:4 - Stages:@CORDIC_STAGES@ - q:@CORDIC_Q@ - div:@CORDIC_DIVIDER@ - C-Types") {
@ -208,3 +210,4 @@ TEST_CASE("ROM-based Cordic (TPL @ROM_TYPE@, @CORDIC_W@, @CORDIC_STAGES@, @CORDI
REQUIRE(res1 == cordic_rom::cordic(value_in, angle));
}
}
#endif

View file

@ -92,6 +92,7 @@ TEST_CASE("Adaptive CORDIC work as intended", "[!hide][WIP]") {
// Return 0 if the test passed
}
#if defined(SOFTWARE)
TEST_CASE("ROM-based Cordic works with C-Types", "[CORDIC]") {
SECTION("W:16 - I:4 - Stages:6 - q:64") {
typedef CCordicRotateConstexpr<16, 4, 6, 64> cordic_rom;
@ -156,6 +157,7 @@ TEST_CASE("ROM-based Cordic 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;
@ -480,6 +482,7 @@ TEST_CASE("ROM-based Cordic works with AP-Types", "[CORDIC]") {
}
}
#if defined(SOFTWARE)
TEST_CASE("ROM-based Cordic constexpr are evaluated during compilation.", "[CORDIC]") {
SECTION("W:16 - I:4 - Stages:6 - q:64 - C-Types") {
typedef CCordicRotateConstexpr<16, 4, 6, 64> cordic_rom;
@ -507,3 +510,4 @@ TEST_CASE("ROM-based Cordic constexpr are evaluated during compilation.", "[CORD
REQUIRE(res1 == cordic_rom::cordic(value_in, angle));
}
}
#endif