From 07a83d8d38db2946ddfff78fe8cc06e4824edd1a Mon Sep 17 00:00:00 2001 From: drslebedev Date: Sat, 5 Aug 2023 09:11:13 +0200 Subject: [PATCH] Add stride implementation for the node class. (#144) * added stride implementation for the node class. * added additional checks for ill-defined parameters. * update test.grc.expected due to the changes in node class. --- include/annotated.hpp | 5 ++ include/node.hpp | 104 +++++++++++++++------- test/grc/test.grc.expected | 32 +++---- test/qa_node.cpp | 172 ++++++++++++++++++++++++++++--------- 4 files changed, 226 insertions(+), 87 deletions(-) diff --git a/include/annotated.hpp b/include/annotated.hpp index 019c2b099..b942b02f8 100644 --- a/include/annotated.hpp +++ b/include/annotated.hpp @@ -67,6 +67,11 @@ struct BlockingIO {}; */ struct PerformDecimationInterpolation {}; +/** + * @brief Annotates node, indicating to perform stride + */ +struct PerformStride {}; + /** * @brief Annotates templated node, indicating which port data types are supported. */ diff --git a/include/node.hpp b/include/node.hpp index 67ffd9832..b8d92abd4 100644 --- a/include/node.hpp +++ b/include/node.hpp @@ -249,14 +249,15 @@ class node : protected std::tuple { using A = Annotated; public: - using base_t = node; - using derived_t = Derived; - using node_template_parameters = meta::typelist; - using Description = typename node_template_parameters::template find_or_default; - constexpr static tag_propagation_policy_t tag_policy = tag_propagation_policy_t::TPP_ALL_TO_ALL; - A> numerator = 1_UZ; - A> denominator = 1_UZ; - A> stride = 1_UZ; + using base_t = node; + using derived_t = Derived; + using node_template_parameters = meta::typelist; + using Description = typename node_template_parameters::template find_or_default; + constexpr static tag_propagation_policy_t tag_policy = tag_propagation_policy_t::TPP_ALL_TO_ALL; + A 1), no effect (fraction = 1)">> numerator = 1_UZ; + A 1), no effect (fraction = 1)">> denominator = 1_UZ; + A N), undefined-default (stride = 0)">> stride = 0_UZ; + std::size_t stride_counter = 0_UZ; const std::size_t unique_id = _unique_id_counter++; const std::string unique_name = fmt::format("{}#{}", fair::meta::type_name(), unique_id); A ::unique_name">> name{ std::string(fair::meta::type_name()) }; @@ -533,6 +534,7 @@ class node : protected std::tuple { constexpr bool is_source_node = input_types::size == 0; constexpr bool is_sink_node = output_types::size == 0; + // TODO: these checks can be moved to setting changed if constexpr (node_template_parameters::template contains) { static_assert(!is_sink_node, "Decimation/interpolation is not available for sink blocks. Remove 'PerformDecimationInterpolation' from the block definition."); static_assert(!is_source_node, "Decimation/interpolation is not available for source blocks. Remove 'PerformDecimationInterpolation' from the block definition."); @@ -545,6 +547,14 @@ class node : protected std::tuple { } } + if constexpr (node_template_parameters::template contains) { + static_assert(!is_source_node, "Stride is not available for source blocks. Remove 'PerformStride' from the block definition."); + } else { + if (stride != 0) { + throw std::runtime_error(fmt::format("Block is not defined as `PerformStride`, but stride = {}, it must equal to 0.", stride)); + } + } + update_ports_status(); if constexpr (is_source_node) { @@ -612,29 +622,33 @@ class node : protected std::tuple { return { requested_work, 0_UZ, ports_status.in_at_least_one_port_has_data ? work_return_status_t::INSUFFICIENT_INPUT_ITEMS : work_return_status_t::DONE }; } - if (numerator != 1. || denominator != 1.) { - bool is_ill_defined = (denominator > ports_status.in_max_samples); - assert(!is_ill_defined); - if (is_ill_defined) { - return { requested_work, 0_UZ, work_return_status_t::ERROR }; - } - - ports_status.in_samples = static_cast(ports_status.in_samples / denominator) * denominator; // remove reminder + if constexpr (node_template_parameters::template contains) { + if (numerator != 1. || denominator != 1.) { + // TODO: this ill-defined checks can be done only once after parameters were changed + const double ratio = static_cast(numerator) / static_cast(denominator); + bool is_ill_defined = (denominator > ports_status.in_max_samples) || (ports_status.in_min_samples * ratio > ports_status.out_max_samples) + || (ports_status.in_max_samples * ratio < ports_status.out_min_samples); + assert(!is_ill_defined); + if (is_ill_defined) { + return { requested_work, 0_UZ, work_return_status_t::ERROR }; + } - const std::size_t out_min_limit = ports_status.out_min_samples; - const std::size_t out_max_limit = std::min(ports_status.out_available, ports_status.out_max_samples); - const double ratio = static_cast(numerator) / static_cast(denominator); + ports_status.in_samples = static_cast(ports_status.in_samples / denominator) * denominator; // remove reminder - std::size_t in_min_samples = static_cast(static_cast(out_min_limit) / ratio); - if (in_min_samples % denominator != 0) in_min_samples += denominator; - std::size_t in_min_wo_reminder = static_cast(in_min_samples / denominator) * denominator; + const std::size_t out_min_limit = ports_status.out_min_samples; + const std::size_t out_max_limit = std::min(ports_status.out_available, ports_status.out_max_samples); - const std::size_t in_max_samples = static_cast(static_cast(out_max_limit) / ratio); - std::size_t in_max_wo_reminder = static_cast(in_max_samples / denominator) * denominator; + std::size_t in_min_samples = static_cast(static_cast(out_min_limit) / ratio); + if (in_min_samples % denominator != 0) in_min_samples += denominator; + std::size_t in_min_wo_reminder = static_cast(in_min_samples / denominator) * denominator; - if (ports_status.in_samples < in_min_wo_reminder) return { requested_work, 0_UZ, work_return_status_t::INSUFFICIENT_INPUT_ITEMS }; - ports_status.in_samples = std::clamp(ports_status.in_samples, in_min_wo_reminder, in_max_wo_reminder); - ports_status.out_samples = numerator * (ports_status.in_samples / denominator); + const std::size_t in_max_samples = static_cast(static_cast(out_max_limit) / ratio); + std::size_t in_max_wo_reminder = static_cast(in_max_samples / denominator) * denominator; + + if (ports_status.in_samples < in_min_wo_reminder) return { requested_work, 0_UZ, work_return_status_t::INSUFFICIENT_INPUT_ITEMS }; + ports_status.in_samples = std::clamp(ports_status.in_samples, in_min_wo_reminder, in_max_wo_reminder); + ports_status.out_samples = numerator * (ports_status.in_samples / denominator); + } } // TODO: special case for ports_status.in_samples == 0 ? @@ -708,6 +722,38 @@ class node : protected std::tuple { // case sources: HW triggered vs. generating data per invocation (generators via Port::MIN) // case sinks: HW triggered vs. fixed-size consumer (may block/never finish for insufficient input data and fixed Port::MIN>0) + std::size_t n_samples_to_consume = ports_status.in_samples; // default stride == 0 + if constexpr (node_template_parameters::template contains) { + if (stride != 0) { + const bool first_time_stride = stride_counter == 0; + if (first_time_stride) { + // sample processing are done as usual, ports_status.in_samples samples will be processed + if (stride.value > stride_counter + ports_status.in_available) { // stride can not be consumed at once -> start stride_counter + stride_counter += ports_status.in_available; + n_samples_to_consume = ports_status.in_available; + } else { // if the stride can be consumed at once -> no stride_counter is needed + stride_counter = 0; + n_samples_to_consume = stride.value; + } + } else { + // |====================|...|====================|==============----| -> ====== is the stride + // ^first ^we are here (1) or ^here (2) + // if it is not the "first time" stride -> just consume (1) all samples or (2) missing rest of the samples + // forward tags but no additional sample processing are done ->return + if (stride.value > stride_counter + ports_status.in_available) { + stride_counter += ports_status.in_available; + n_samples_to_consume = ports_status.in_available; + } else { // stride is at the end -> reset stride_counter + n_samples_to_consume = stride.value - stride_counter; + stride_counter = 0; + } + const bool success = consume_readers(self(), n_samples_to_consume); + forward_tags(); + return { requested_work, n_samples_to_consume, success ? work_return_status_t::OK : work_return_status_t::ERROR }; + } + } + } + const auto input_spans = meta::tuple_transform([in_samples = ports_status.in_samples](auto &input_port) noexcept { return input_port.streamReader().get(in_samples); }, input_ports(&self())); auto writers_tuple = meta::tuple_transform([out_samples = ports_status.out_samples](auto &output_port) noexcept { return output_port.streamWriter().reserve_output_range(out_samples); }, output_ports(&self())); @@ -719,7 +765,7 @@ class node : protected std::tuple { }(std::make_index_sequence::size>(), std::make_index_sequence::size>()); write_to_outputs(ports_status.out_samples, writers_tuple); - const bool success = consume_readers(self(), ports_status.in_samples); + const bool success = consume_readers(self(), n_samples_to_consume); forward_tags(); return { requested_work, ports_status.in_samples, success ? ret : work_return_status_t::ERROR }; } else if constexpr (HasProcessOneFunction) { @@ -759,7 +805,7 @@ class node : protected std::tuple { write_to_outputs(ports_status.out_samples, writers_tuple); - const bool success = consume_readers(self(), ports_status.in_samples); + const bool success = consume_readers(self(), n_samples_to_consume); #ifdef _DEBUG if (!success) { diff --git a/test/grc/test.grc.expected b/test/grc/test.grc.expected index b51207cb7..5b8703ebe 100644 --- a/test/grc/test.grc.expected +++ b/test/grc/test.grc.expected @@ -6,10 +6,10 @@ blocks: event_count: 100 name: main_source numerator: 1 - stride: 1 + stride: 0 unique_name: good::fixed_source#0 denominator::description: denominator - denominator::documentation: decimation/interpolation settings + denominator::documentation: "The bottom number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" denominator::unit: "" denominator::visible: 0 description: "" @@ -22,11 +22,11 @@ blocks: name::unit: "" name::visible: 0 numerator::description: numerator - numerator::documentation: decimation/interpolation settings + numerator::documentation: "The top number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" numerator::unit: "" numerator::visible: 0 stride::description: stride - stride::documentation: stride doc + stride::documentation: "Number of samples between two data processing: overlap (stride < N), skip (stride > N), undefined-default (stride = 0)" stride::unit: "" stride::visible: 0 unknown_property: 42 @@ -36,10 +36,10 @@ blocks: denominator: 1 name: multiplier numerator: 1 - stride: 1 + stride: 0 unique_name: good::multiply#0 denominator::description: denominator - denominator::documentation: decimation/interpolation settings + denominator::documentation: "The bottom number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" denominator::unit: "" denominator::visible: 0 description: "" @@ -52,11 +52,11 @@ blocks: name::unit: "" name::visible: 0 numerator::description: numerator - numerator::documentation: decimation/interpolation settings + numerator::documentation: "The top number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" numerator::unit: "" numerator::visible: 0 stride::description: stride - stride::documentation: stride doc + stride::documentation: "Number of samples between two data processing: overlap (stride < N), skip (stride > N), undefined-default (stride = 0)" stride::unit: "" stride::visible: 0 - name: counter @@ -65,10 +65,10 @@ blocks: denominator: 1 name: counter numerator: 1 - stride: 1 + stride: 0 unique_name: builtin_counter#0 denominator::description: denominator - denominator::documentation: decimation/interpolation settings + denominator::documentation: "The bottom number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" denominator::unit: "" denominator::visible: 0 description: "" @@ -81,11 +81,11 @@ blocks: name::unit: "" name::visible: 0 numerator::description: numerator - numerator::documentation: decimation/interpolation settings + numerator::documentation: "The top number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" numerator::unit: "" numerator::visible: 0 stride::description: stride - stride::documentation: stride doc + stride::documentation: "Number of samples between two data processing: overlap (stride < N), skip (stride > N), undefined-default (stride = 0)" stride::unit: "" stride::visible: 0 - name: sink @@ -94,11 +94,11 @@ blocks: denominator: 1 name: sink numerator: 1 - stride: 1 + stride: 0 total_count: 100 unique_name: good::cout_sink#0 denominator::description: denominator - denominator::documentation: decimation/interpolation settings + denominator::documentation: "The bottom number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" denominator::unit: "" denominator::visible: 0 description: "" @@ -111,11 +111,11 @@ blocks: name::unit: "" name::visible: 0 numerator::description: numerator - numerator::documentation: decimation/interpolation settings + numerator::documentation: "The top number of a fraction = numerator/denominator: decimation (fraction < 1), interpolation (fraction > 1), no effect (fraction = 1)" numerator::unit: "" numerator::visible: 0 stride::description: stride - stride::documentation: stride doc + stride::documentation: "Number of samples between two data processing: overlap (stride < N), skip (stride > N), undefined-default (stride = 0)" stride::unit: "" stride::visible: 0 unknown_property: 42 diff --git a/test/qa_node.cpp b/test/qa_node.cpp index aad2999cc..7c0fd8503 100644 --- a/test/qa_node.cpp +++ b/test/qa_node.cpp @@ -1,4 +1,5 @@ #include +#include #if defined(__clang__) && __clang_major__ >= 16 // clang 16 does not like ut's default reporter_junit due to some issues with stream buffers and output redirection @@ -15,20 +16,50 @@ auto boost::ut::cfg = boost::ut::runner in_vector{}; }; -struct TestData { +struct IntDecTestData { std::size_t n_samples{}; std::size_t numerator{}; std::size_t denominator{}; - int out_port_min_samples{ -1 }; // -1 for not used - int out_port_max_samples{ -1 }; // -1 for not used - std::size_t expected_in{}; - std::size_t expected_out{}; - std::size_t expected_counter{}; + int out_port_min{ -1 }; // -1 for not used + int out_port_max{ -1 }; // -1 for not used + std::size_t exp_in{}; + std::size_t exp_out{}; + std::size_t exp_counter{}; + + std::string + to_string() const { + return fmt::format("n_samples: {}, numerator: {}, denominator: {}, out_port_min: {}, out_port_max: {}, exp_in: {}, exp_out: {}, exp_counter: {}", n_samples, numerator, denominator, + out_port_min, out_port_max, exp_in, exp_out, exp_counter); + } +}; + +struct StrideTestData { + std::size_t n_samples{}; + std::size_t numerator{ 1 }; + std::size_t denominator{ 1 }; + std::size_t stride{}; + int in_port_min{ -1 }; // -1 for not used + int in_port_max{ -1 }; // -1 for not used + std::size_t exp_in{}; + std::size_t exp_out{}; + std::size_t exp_counter{}; + std::size_t exp_total_in{ 0 }; + std::size_t exp_total_out{ 0 }; + std::vector exp_in_vector{}; + + std::string + to_string() const { + return fmt::format("n_samples: {}, numerator: {}, denominator: {}, stride: {}, in_port_min: {}, in_port_max: {}, exp_in: {}, exp_out: {}, exp_counter: {}, exp_total_in: {}, exp_total_out: {}", + n_samples, numerator, denominator, stride, in_port_min, in_port_max, exp_in, exp_out, exp_counter, exp_total_in, exp_total_out); + } }; template @@ -50,17 +81,21 @@ struct CountSource : public fg::node> { }; template -struct IntDecBlock : public fg::node, fg::PerformDecimationInterpolation> { +struct IntDecBlock : public fg::node, fg::PerformDecimationInterpolation, fg::PerformStride> { fg::IN in{}; fg::OUT out{}; - ProcessStatus status; + ProcessStatus status{}; + bool write_to_vector{ false }; fg::work_return_status_t process_bulk(std::span input, std::span output) noexcept { status.n_inputs = input.size(); status.n_outputs = output.size(); status.process_counter++; + status.total_in += input.size(); + status.total_out += output.size(); + if (write_to_vector) status.in_vector.insert(status.in_vector.end(), input.begin(), input.end()); return fg::work_return_status_t::OK; } @@ -70,7 +105,7 @@ ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (CountSource), out, count, ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (IntDecBlock), in, out); void -interpolation_decimation_test(const TestData &data, std::shared_ptr thread_pool) { +interpolation_decimation_test(const IntDecTestData &data, std::shared_ptr thread_pool) { using namespace boost::ut; using scheduler = fair::graph::scheduler::simple<>; @@ -81,16 +116,49 @@ interpolation_decimation_test(const TestData &data, std::shared_ptr>(); int_dec_block.numerator = data.numerator; int_dec_block.denominator = data.denominator; - if (data.out_port_max_samples >= 0) int_dec_block.out.max_samples = static_cast(data.out_port_max_samples); - if (data.out_port_min_samples >= 0) int_dec_block.out.min_samples = static_cast(data.out_port_min_samples); + if (data.out_port_max >= 0) int_dec_block.out.max_samples = static_cast(data.out_port_max); + if (data.out_port_min >= 0) int_dec_block.out.min_samples = static_cast(data.out_port_min); + + std::ignore = flow.connect<"out">(source).to<"in">(int_dec_block); + auto sched = scheduler(std::move(flow), thread_pool); + sched.run_and_wait(); + + expect(eq(int_dec_block.status.process_counter, data.exp_counter)) << "process_bulk invokes counter, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.n_inputs, data.exp_in)) << "last number of input samples, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.n_outputs, data.exp_out)) << "last number of output samples, parameters = " << data.to_string(); +} + +void +stride_test(const StrideTestData &data, std::shared_ptr thread_pool) { + using namespace boost::ut; + using scheduler = fair::graph::scheduler::simple<>; + + const bool write_to_vector{ data.exp_in_vector.size() != 0 }; + + fg::graph flow; + auto &source = flow.make_node>(); + source.n_samples = static_cast(data.n_samples); + + auto &int_dec_block = flow.make_node>(); + int_dec_block.write_to_vector = write_to_vector; + int_dec_block.numerator = data.numerator; + int_dec_block.denominator = data.denominator; + int_dec_block.stride = data.stride; + if (data.in_port_max >= 0) int_dec_block.in.max_samples = static_cast(data.in_port_max); + if (data.in_port_min >= 0) int_dec_block.in.min_samples = static_cast(data.in_port_min); std::ignore = flow.connect<"out">(source).to<"in">(int_dec_block); auto sched = scheduler(std::move(flow), thread_pool); sched.run_and_wait(); - expect(eq(int_dec_block.status.process_counter, data.expected_counter)) << "process_bulk invokes counter"; - expect(eq(int_dec_block.status.n_inputs, data.expected_in)) << "number of input samples"; - expect(eq(int_dec_block.status.n_outputs, data.expected_out)) << "number of output samples"; + expect(eq(int_dec_block.status.process_counter, data.exp_counter)) << "process_bulk invokes counter, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.n_inputs, data.exp_in)) << "last number of input samples, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.n_outputs, data.exp_out)) << "last number of output samples, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.total_in, data.exp_total_in)) << "total number of input samples, parameters = " << data.to_string(); + expect(eq(int_dec_block.status.total_out, data.exp_total_out)) << "total number of output samples, parameters = " << data.to_string(); + if (write_to_vector) { + expect(eq(int_dec_block.status.in_vector, data.exp_in_vector)) << "in vector of samples, parameters = " << data.to_string(); + } } const boost::ut::suite _fft_tests = [] { @@ -100,31 +168,51 @@ const boost::ut::suite _fft_tests = [] { auto thread_pool = std::make_shared("custom pool", fair::thread_pool::CPU_BOUND, 2, 2); "Interpolation/Decimation tests"_test = [&thread_pool] { - interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 1 }, .denominator{ 1 }, .expected_in{ 1024 }, .expected_out{ 1024 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 1 }, .denominator{ 2 }, .expected_in{ 1024 }, .expected_out{ 512 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 2 }, .denominator{ 1 }, .expected_in{ 1024 }, .expected_out{ 2048 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 5 }, .denominator{ 6 }, .expected_in{ 996 }, .expected_out{ 830 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 549 }, .numerator{ 1 }, .denominator{ 50 }, .expected_in{ 500 }, .expected_out{ 10 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 3 }, .denominator{ 7 }, .expected_in{ 98 }, .expected_out{ 42 }, .expected_counter{ 1 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 100 }, .denominator{ 100 }, .expected_in{ 100 }, .expected_out{ 100 }, .expected_counter{ 1 } }, thread_pool); - - interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 10 }, .denominator{ 1100 }, .expected_in{ 0 }, .expected_out{ 0 }, .expected_counter{ 0 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 1 }, .denominator{ 1001 }, .expected_in{ 0 }, .expected_out{ 0 }, .expected_counter{ 0 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 100000 }, .denominator{ 1 }, .expected_in{ 0 }, .expected_out{ 0 }, .expected_counter{ 0 } }, thread_pool); - interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 101 }, .denominator{ 101 }, .expected_in{ 0 }, .expected_out{ 0 }, .expected_counter{ 0 } }, thread_pool); - - interpolation_decimation_test( - { .n_samples{ 100 }, .numerator{ 5 }, .denominator{ 11 }, .out_port_min_samples{ 10 }, .out_port_max_samples{ 41 }, .expected_in{ 88 }, .expected_out{ 40 }, .expected_counter{ 1 } }, - thread_pool); - interpolation_decimation_test( - { .n_samples{ 100 }, .numerator{ 7 }, .denominator{ 3 }, .out_port_min_samples{ 10 }, .out_port_max_samples{ 10 }, .expected_in{ 0 }, .expected_out{ 0 }, .expected_counter{ 0 } }, - thread_pool); - interpolation_decimation_test( - { .n_samples{ 80 }, .numerator{ 2 }, .denominator{ 4 }, .out_port_min_samples{ 20 }, .out_port_max_samples{ 20 }, .expected_in{ 40 }, .expected_out{ 20 }, .expected_counter{ 2 } }, - thread_pool); - interpolation_decimation_test( - { .n_samples{ 100 }, .numerator{ 7 }, .denominator{ 3 }, .out_port_min_samples{ 10 }, .out_port_max_samples{ 20 }, .expected_in{ 6 }, .expected_out{ 14 }, .expected_counter{ 16 } }, - thread_pool); + interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 1 }, .denominator{ 1 }, .exp_in{ 1024 }, .exp_out{ 1024 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 1 }, .denominator{ 2 }, .exp_in{ 1024 }, .exp_out{ 512 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 1024 }, .numerator{ 2 }, .denominator{ 1 }, .exp_in{ 1024 }, .exp_out{ 2048 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 5 }, .denominator{ 6 }, .exp_in{ 996 }, .exp_out{ 830 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 549 }, .numerator{ 1 }, .denominator{ 50 }, .exp_in{ 500 }, .exp_out{ 10 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 3 }, .denominator{ 7 }, .exp_in{ 98 }, .exp_out{ 42 }, .exp_counter{ 1 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 100 }, .denominator{ 100 }, .exp_in{ 100 }, .exp_out{ 100 }, .exp_counter{ 1 } }, thread_pool); + + interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 10 }, .denominator{ 1100 }, .exp_in{ 0 }, .exp_out{ 0 }, .exp_counter{ 0 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 1000 }, .numerator{ 1 }, .denominator{ 1001 }, .exp_in{ 0 }, .exp_out{ 0 }, .exp_counter{ 0 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 100000 }, .denominator{ 1 }, .exp_in{ 0 }, .exp_out{ 0 }, .exp_counter{ 0 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 101 }, .denominator{ 101 }, .exp_in{ 0 }, .exp_out{ 0 }, .exp_counter{ 0 } }, thread_pool); + + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 5 }, .denominator{ 11 }, .out_port_min{ 10 }, .out_port_max{ 41 }, .exp_in{ 88 }, .exp_out{ 40 }, .exp_counter{ 1 } }, + thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 7 }, .denominator{ 3 }, .out_port_min{ 10 }, .out_port_max{ 10 }, .exp_in{ 0 }, .exp_out{ 0 }, .exp_counter{ 0 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 80 }, .numerator{ 2 }, .denominator{ 4 }, .out_port_min{ 20 }, .out_port_max{ 20 }, .exp_in{ 40 }, .exp_out{ 20 }, .exp_counter{ 2 } }, thread_pool); + interpolation_decimation_test({ .n_samples{ 100 }, .numerator{ 7 }, .denominator{ 3 }, .out_port_min{ 10 }, .out_port_max{ 20 }, .exp_in{ 6 }, .exp_out{ 14 }, .exp_counter{ 16 } }, + thread_pool); + }; + + "Stride tests"_test = [&thread_pool] { + stride_test({ .n_samples{ 1024 }, .stride{ 0 }, .in_port_max{ 1024 }, .exp_in{ 1024 }, .exp_out{ 1024 }, .exp_counter{ 1 }, .exp_total_in{ 1024 }, .exp_total_out{ 1024 } }, thread_pool); + stride_test({ .n_samples{ 1000 }, .stride{ 100 }, .in_port_max{ 50 }, .exp_in{ 50 }, .exp_out{ 50 }, .exp_counter{ 10 }, .exp_total_in{ 500 }, .exp_total_out{ 500 } }, thread_pool); + stride_test({ .n_samples{ 1000 }, .stride{ 133 }, .in_port_max{ 50 }, .exp_in{ 50 }, .exp_out{ 50 }, .exp_counter{ 8 }, .exp_total_in{ 400 }, .exp_total_out{ 400 } }, thread_pool); + stride_test({ .n_samples{ 1000 }, .stride{ 50 }, .in_port_max{ 100 }, .exp_in{ 50 }, .exp_out{ 50 }, .exp_counter{ 20 }, .exp_total_in{ 1950 }, .exp_total_out{ 1950 } }, thread_pool); + stride_test({ .n_samples{ 1000 }, .stride{ 33 }, .in_port_max{ 100 }, .exp_in{ 10 }, .exp_out{ 10 }, .exp_counter{ 31 }, .exp_total_in{ 2929 }, .exp_total_out{ 2929 } }, thread_pool); + // clang-format off + stride_test({ .n_samples{ 1000 }, .numerator{ 2 }, .denominator{ 4 }, .stride{ 50 }, .in_port_max{ 100 }, .exp_in{ 48 }, .exp_out{ 24 }, .exp_counter{ 20 }, .exp_total_in{ 1948 }, .exp_total_out{ 974 } }, + thread_pool); + stride_test({ .n_samples{ 1000 }, .numerator{ 2 }, .denominator{ 4 }, .stride{ 50 }, .in_port_max{ 50 }, .exp_in{ 48 }, .exp_out{ 24 }, .exp_counter{ 20 }, .exp_total_in{ 960 }, .exp_total_out{ 480 } }, + thread_pool); + // clang-format on + + std::vector exp_v1 = { 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 6, 7, 8, 9, 10, 9, 10, 11, 12, 13, 12, 13, 14 }; + stride_test({ .n_samples{ 15 }, .stride{ 3 }, .in_port_max{ 5 }, .exp_in{ 3 }, .exp_out{ 3 }, .exp_counter{ 5 }, .exp_total_in{ 23 }, .exp_total_out{ 23 }, .exp_in_vector{ exp_v1 } }, + thread_pool); + + std::vector exp_v2 = { 0, 1, 2, 5, 6, 7, 10, 11, 12 }; + stride_test({ .n_samples{ 15 }, .stride{ 5 }, .in_port_max{ 3 }, .exp_in{ 3 }, .exp_out{ 3 }, .exp_counter{ 3 }, .exp_total_in{ 9 }, .exp_total_out{ 9 }, .exp_in_vector{ exp_v2 } }, + thread_pool); + + // assuming buffer size is approx 65k + stride_test({ .n_samples{ 1000000 }, .stride{ 250000 }, .in_port_max{ 100 }, .exp_in{ 100 }, .exp_out{ 100 }, .exp_counter{ 4 }, .exp_total_in{ 400 }, .exp_total_out{ 400 } }, thread_pool); + stride_test({ .n_samples{ 1000000 }, .stride{ 249900 }, .in_port_max{ 100 }, .exp_in{ 100 }, .exp_out{ 100 }, .exp_counter{ 5 }, .exp_total_in{ 500 }, .exp_total_out{ 500 } }, thread_pool); }; };