From 67918462210cd16c20075c939bdcf06992481a2c Mon Sep 17 00:00:00 2001 From: mattprost Date: Wed, 8 Apr 2020 17:29:06 -0500 Subject: tests: migrated rfnoc block tests to dedicated subdirectory This separates the rfnoc block tests into files for each specific block. This was done to improve the readability of these files and declutter the tests directory. Signed-off-by: mattprost --- host/tests/CMakeLists.txt | 32 +- host/tests/fir_filter_block_test.cpp | 241 ------------- host/tests/rfnoc_block_tests/ddc_block_test.cpp | 131 ++++++++ host/tests/rfnoc_block_tests/duc_block_test.cpp | 177 ++++++++++ .../rfnoc_block_tests/fir_filter_block_test.cpp | 243 ++++++++++++++ host/tests/rfnoc_block_tests/null_block_test.cpp | 117 +++++++ host/tests/rfnoc_blocks_test.cpp | 371 --------------------- 7 files changed, 693 insertions(+), 619 deletions(-) delete mode 100644 host/tests/fir_filter_block_test.cpp create mode 100644 host/tests/rfnoc_block_tests/ddc_block_test.cpp create mode 100644 host/tests/rfnoc_block_tests/duc_block_test.cpp create mode 100644 host/tests/rfnoc_block_tests/fir_filter_block_test.cpp create mode 100644 host/tests/rfnoc_block_tests/null_block_test.cpp delete mode 100644 host/tests/rfnoc_blocks_test.cpp (limited to 'host') diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 828729b79..5fbdf04dd 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -116,6 +116,19 @@ macro(UHD_ADD_NONAPI_TEST) COMPONENT tests) endmacro(UHD_ADD_NONAPI_TEST) +############################################################################### +# Add a unit test for an RFNoC block controller +############################################################################### +macro(UHD_ADD_RFNOC_BLOCK_TEST) + cmake_parse_arguments(test "NOAUTORUN" "TARGET" "INCLUDE_DIRS;EXTRA_SOURCES;EXTRA_LIBS" ${ARGN}) + UHD_ADD_NONAPI_TEST( + TARGET rfnoc_block_tests/${test_TARGET} + EXTRA_SOURCES + ${test_EXTRA_SOURCES} + ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp + ) +endmacro(UHD_ADD_RFNOC_BLOCK_TEST) + ############################################################################### # Now add all unit tests that require special linkage ############################################################################### @@ -245,16 +258,21 @@ set_source_files_properties( PROPERTIES COMPILE_DEFINITIONS "HAVE_MICROSEC_CLOCK" ) -UHD_ADD_NONAPI_TEST( - TARGET rfnoc_blocks_test.cpp - EXTRA_SOURCES - ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp + +UHD_ADD_RFNOC_BLOCK_TEST( + TARGET ddc_block_test.cpp ) -UHD_ADD_NONAPI_TEST( +UHD_ADD_RFNOC_BLOCK_TEST( + TARGET duc_block_test.cpp +) + +UHD_ADD_RFNOC_BLOCK_TEST( TARGET fir_filter_block_test.cpp - EXTRA_SOURCES - ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp +) + +UHD_ADD_RFNOC_BLOCK_TEST( + TARGET null_block_test.cpp ) UHD_ADD_NONAPI_TEST( diff --git a/host/tests/fir_filter_block_test.cpp b/host/tests/fir_filter_block_test.cpp deleted file mode 100644 index 3bfce4b2d..000000000 --- a/host/tests/fir_filter_block_test.cpp +++ /dev/null @@ -1,241 +0,0 @@ -// -// Copyright 2020 Ettus Research, a National Instruments Brand -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rfnoc_graph_mock_nodes.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace uhd::rfnoc; - -// Redeclare this here, since it's only defined outside of UHD_API -noc_block_base::make_args_t::~make_args_t() = default; - -/* - * This class extends mock_reg_iface_t by adding poke and peek hooks that - * monitor writes and reads to the registers implemented within the FIR - * filter RFNoC block hardware and emulating the expected behavior of the - * hardware when those registers are read and written. For instance, writes - * to the coefficient registers store the coefficients in a vector and - * track the position of the last write to the REG_FIR_LOAD_COEFF_LAST_ADDR - * register to allow the unit test to gauge the proper operation of the FIR - * filter block controller. - */ -class fir_filter_mock_reg_iface_t : public mock_reg_iface_t -{ -public: - fir_filter_mock_reg_iface_t(size_t max_num_coeffs) : _max_num_coeffs(max_num_coeffs) {} - - virtual void _poke_cb( - uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) - { - if (addr == fir_filter_block_control::REG_FIR_MAX_NUM_COEFFS_ADDR) { - throw uhd::assertion_error("Invalid write to read-only register"); - } else if (addr == fir_filter_block_control::REG_FIR_LOAD_COEFF_ADDR) { - coeffs.push_back(uhd::narrow_cast(data)); - } else if (addr == fir_filter_block_control::REG_FIR_LOAD_COEFF_LAST_ADDR) { - last_coeff_write_pos = coeffs.size(); - coeffs.push_back(uhd::narrow_cast(data)); - } else { - throw uhd::assertion_error("Invalid write to out of bounds address"); - } - } - - virtual void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) - { - if (addr == fir_filter_block_control::REG_FIR_MAX_NUM_COEFFS_ADDR) { - read_memory[addr] = uhd::narrow_cast(_max_num_coeffs); - } else { - throw uhd::assertion_error("Invalid read from out of bounds address"); - } - } - - void reset() - { - last_coeff_write_pos = 0; - coeffs.clear(); - } - - size_t last_coeff_write_pos = 0; - std::vector coeffs{}; - -private: - const size_t _max_num_coeffs; -}; - - -/* fir_filter_block_fixture is a class which is instantiated before each test - * case is run. It sets up the block container, mock register interface, - * and fir_filter_block_control object, all of which are accessible to the test - * case. The instance of the object is destroyed at the end of each test - * case. - */ -constexpr size_t MAX_NUM_COEFFS = 3000; -constexpr size_t DEFAULT_MTU = 8000; - -struct fir_filter_block_fixture -{ - fir_filter_block_fixture() - : reg_iface(std::make_shared(MAX_NUM_COEFFS)) - , block_container(get_mock_block(FIR_FILTER_BLOCK, - 1, - 2, - uhd::device_addr_t(), - DEFAULT_MTU, - ANY_DEVICE, - reg_iface)) - , test_fir_filter(block_container.get_block()) - { - node_accessor.init_props(test_fir_filter.get()); - } - - std::shared_ptr reg_iface; - mock_block_container block_container; - std::shared_ptr test_fir_filter; - node_accessor_t node_accessor{}; -}; - -/* - * This test case ensures that the hardware is programmed correctly with - * defaults when the fir_filter block is constructed. - */ -BOOST_FIXTURE_TEST_CASE(fir_filter_test_construction, fir_filter_block_fixture) -{ - // Check that the number of coefficients is expected - BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); - // Check that the first coefficient is the only non-zero value - // (impulse response) - BOOST_CHECK_NE(reg_iface->coeffs.at(0), 0); - for (size_t i = 1; i < reg_iface->coeffs.size(); i++) { - BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); - } - // Check that the LOAD_COEFF_LAST register was written at the right - // time (i.e. with the last value) - BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); -} - -/* - * This test case exercises the get_max_num_coefficients() API. - */ -BOOST_FIXTURE_TEST_CASE(fir_filter_test_max_num_coeffs, fir_filter_block_fixture) -{ - BOOST_CHECK_EQUAL(test_fir_filter->get_max_num_coefficients(), MAX_NUM_COEFFS); -} - -/* - * This test case exercises the set_coefficients() API and get_coefficients() - * APIs and ensures that the hardware registers are programmed appropriately - * when new coefficients are specified. - */ -BOOST_FIXTURE_TEST_CASE(fir_filter_test_set_get_coefficients, fir_filter_block_fixture) -{ - // Reset state of mock FIR filter register interface - reg_iface->reset(); - - // First test: 10 coefficients - std::vector coeffs1{1, 2, 3, 4, 5, -1, -2, -3, -4, -5}; - test_fir_filter->set_coefficients(coeffs1); - - // Check that all coefficients were written - BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); - - // Check correctness of coefficients - for (size_t i = 0; i < coeffs1.size(); i++) { - BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), coeffs1.at(i)); - } - for (size_t i = coeffs1.size(); i < MAX_NUM_COEFFS; i++) { - BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); - } - // Check that the LOAD_COEFF_LAST register was written at the right - // time (i.e. with the last value) - BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); - - // Verify that get_coefficients() returns what we expect. Note that - // get_coefficients() returns the padded set of coefficients. - std::vector received_coeffs = test_fir_filter->get_coefficients(); - - BOOST_CHECK_EQUAL(received_coeffs.size(), MAX_NUM_COEFFS); - - // Check correctness of returned coefficients - for (size_t i = 0; i < coeffs1.size(); i++) { - BOOST_CHECK_EQUAL(received_coeffs.at(i), coeffs1.at(i)); - } - for (size_t i = coeffs1.size(); i < MAX_NUM_COEFFS; i++) { - BOOST_CHECK_EQUAL(received_coeffs.at(i), 0); - } - - reg_iface->reset(); - - // Now update the coefficients with a smaller set, and ensure that - // the hardware gets the correct coefficients - std::vector coeffs2{1, 3, 5, 7}; - test_fir_filter->set_coefficients(coeffs2); - - // Check that all coefficients were written - BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); - - // Check correctness of coefficients - for (size_t i = 0; i < coeffs2.size(); i++) { - BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), coeffs2.at(i)); - } - for (size_t i = coeffs2.size(); i < MAX_NUM_COEFFS; i++) { - BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); - } - // Check that the LOAD_COEFF_LAST register was written at the right - // time (i.e. with the last value) - BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); -} - -/* - * This test case exercises the coefficient length checking of - * set_coefficients(). - */ -BOOST_FIXTURE_TEST_CASE(fir_filter_test_length_error, fir_filter_block_fixture) -{ - size_t num_coeffs = test_fir_filter->get_max_num_coefficients(); - std::vector coeffs(num_coeffs * 2); - BOOST_CHECK_THROW(test_fir_filter->set_coefficients(coeffs), uhd::value_error); -} - -/* - * This test case ensures that the FIR filter block can be added to - * an RFNoC graph. - */ -BOOST_FIXTURE_TEST_CASE(fir_filter_test_graph, fir_filter_block_fixture) -{ - detail::graph_t graph{}; - detail::graph_t::graph_edge_t edge_port_info; - edge_port_info.src_port = 0; - edge_port_info.dst_port = 0; - edge_port_info.property_propagation_active = true; - edge_port_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; - - mock_radio_node_t mock_radio_block{0}; - mock_ddc_node_t mock_ddc_block{}; - mock_terminator_t mock_sink_term(1, {}, "MOCK_SINK"); - - UHD_LOG_INFO("TEST", "Priming mock block properties"); - node_accessor.init_props(&mock_radio_block); - node_accessor.init_props(&mock_ddc_block); - mock_sink_term.set_edge_property( - "type", "sc16", {res_source_info::INPUT_EDGE, 0}); - mock_sink_term.set_edge_property( - "type", "sc16", {res_source_info::INPUT_EDGE, 1}); - - UHD_LOG_INFO("TEST", "Creating graph..."); - graph.connect(&mock_radio_block, &mock_ddc_block, edge_port_info); - graph.connect(&mock_ddc_block, test_fir_filter.get(), edge_port_info); - graph.connect(test_fir_filter.get(), &mock_sink_term, edge_port_info); - UHD_LOG_INFO("TEST", "Committing graph..."); - graph.commit(); - UHD_LOG_INFO("TEST", "Commit complete."); -} diff --git a/host/tests/rfnoc_block_tests/ddc_block_test.cpp b/host/tests/rfnoc_block_tests/ddc_block_test.cpp new file mode 100644 index 000000000..d32dd0aec --- /dev/null +++ b/host/tests/rfnoc_block_tests/ddc_block_test.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../rfnoc_graph_mock_nodes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace uhd::rfnoc; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +namespace { + +constexpr size_t DEFAULT_MTU = 8000; + +} // namespace + +BOOST_AUTO_TEST_CASE(test_ddc_block) +{ + node_accessor_t node_accessor{}; + constexpr uint32_t num_hb = 2; + constexpr uint32_t max_cic = 128; + constexpr size_t num_chans = 4; + constexpr noc_id_t noc_id = DDC_BLOCK; + constexpr int TEST_DECIM = 20; + + auto block_container = + get_mock_block(noc_id, num_chans, num_chans, uhd::device_addr_t("foo=bar")); + auto& ddc_reg_iface = block_container.reg_iface; + ddc_reg_iface->read_memory[ddc_block_control::RB_COMPAT_NUM] = + (ddc_block_control::MAJOR_COMPAT << 16) | ddc_block_control::MINOR_COMPAT; + ddc_reg_iface->read_memory[ddc_block_control::RB_NUM_HB] = num_hb; + ddc_reg_iface->read_memory[ddc_block_control::RB_CIC_MAX_DECIM] = max_cic; + auto test_ddc = block_container.get_block(); + BOOST_REQUIRE(test_ddc); + BOOST_CHECK_EQUAL(test_ddc->get_block_args().get("foo"), "bar"); + + node_accessor.init_props(test_ddc.get()); + UHD_LOG_DEBUG("TEST", "Init done."); + test_ddc->set_property("decim", TEST_DECIM, 0); + + BOOST_REQUIRE(ddc_reg_iface->write_memory.count(ddc_block_control::SR_DECIM_ADDR)); + BOOST_CHECK_EQUAL( + ddc_reg_iface->write_memory.at(ddc_block_control::SR_DECIM_ADDR), 2 << 8 | 5); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU); + + // Now plop it in a graph + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_info; + edge_info.src_port = 0; + edge_info.dst_port = 0; + edge_info.property_propagation_active = true; + edge_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_terminator_t mock_source_term(1); + mock_terminator_t mock_sink_term(1); + + UHD_LOG_INFO("TEST", "Priming mock source node props"); + mock_source_term.set_edge_property( + "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property( + "scaling", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property( + "samp_rate", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + constexpr size_t NEW_MTU = 4000; + mock_source_term.set_edge_property( + "mtu", NEW_MTU, {res_source_info::OUTPUT_EDGE, 0}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + graph.connect(&mock_source_term, test_ddc.get(), edge_info); + graph.connect(test_ddc.get(), &mock_sink_term, edge_info); + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + UHD_LOG_INFO("TEST", "Commit complete."); + // We need to set the decimation again, because the rates will screw it + // change it w.r.t. to the previous setting + test_ddc->set_property("decim", TEST_DECIM, 0); + BOOST_CHECK_EQUAL(test_ddc->get_property("decim", 0), TEST_DECIM); + BOOST_CHECK(mock_source_term.get_edge_property( + "samp_rate", {res_source_info::OUTPUT_EDGE, 0}) + == mock_sink_term.get_edge_property( + "samp_rate", {res_source_info::INPUT_EDGE, 0}) + * TEST_DECIM); + BOOST_CHECK(mock_sink_term.get_edge_property( + "scaling", {res_source_info::INPUT_EDGE, 0}) + != 1.0); + + UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate"); + constexpr double TEST_FREQ = 1.0 / 8; + test_ddc->set_property("freq", TEST_FREQ, 0); + const uint32_t freq_word_1 = + ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); + BOOST_REQUIRE(freq_word_1 != 0); + UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)"); + // Now this should change the freq word, but not the absolute frequency + mock_source_term.set_edge_property( + "samp_rate", 2.0, {res_source_info::OUTPUT_EDGE, 0}); + const double freq_word_2 = + ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); + // The frequency word is the phase increment, which will halve. We skirt + // around fixpoint/floating point accuracy issues by using CLOSE. + BOOST_CHECK_CLOSE(double(freq_word_1) / double(freq_word_2), 2.0, 1e-6); + + UHD_LOG_INFO("TEST", "Testing DDC MTU propagation"); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), NEW_MTU); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 0}), NEW_MTU); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 1}), DEFAULT_MTU); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 1}), DEFAULT_MTU); + mock_source_term.set_edge_property( + "mtu", NEW_MTU / 2, {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), NEW_MTU / 2); + BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 0}), NEW_MTU / 2); + + // Now reset the props using set_properties + test_ddc->set_properties(uhd::device_addr_t("decim=1,freq=0.0,foo=bar"), 0); + BOOST_CHECK_EQUAL(test_ddc->get_property("decim", 0), 1); + BOOST_CHECK_EQUAL(test_ddc->get_property("freq", 0), 0.0); +} diff --git a/host/tests/rfnoc_block_tests/duc_block_test.cpp b/host/tests/rfnoc_block_tests/duc_block_test.cpp new file mode 100644 index 000000000..78e988713 --- /dev/null +++ b/host/tests/rfnoc_block_tests/duc_block_test.cpp @@ -0,0 +1,177 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../rfnoc_graph_mock_nodes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace uhd::rfnoc; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +namespace { + +constexpr size_t DEFAULT_MTU = 8000; + +} // namespace + +BOOST_AUTO_TEST_CASE(test_duc_block) +{ + node_accessor_t node_accessor{}; + constexpr uint32_t num_hb = 2; + constexpr uint32_t max_cic = 128; + constexpr size_t num_chans = 4; + constexpr noc_id_t noc_id = DUC_BLOCK; + constexpr int TEST_INTERP = 20; // 2 halfbands, CIC==5 + + auto block_container = get_mock_block(noc_id, num_chans, num_chans); + auto& duc_reg_iface = block_container.reg_iface; + duc_reg_iface->read_memory[duc_block_control::RB_COMPAT_NUM] = + (duc_block_control::MAJOR_COMPAT << 16) | duc_block_control::MINOR_COMPAT; + duc_reg_iface->read_memory[duc_block_control::RB_NUM_HB] = num_hb; + duc_reg_iface->read_memory[duc_block_control::RB_CIC_MAX_INTERP] = max_cic; + auto test_duc = block_container.get_block(); + BOOST_REQUIRE(test_duc); + + node_accessor.init_props(test_duc.get()); + UHD_LOG_DEBUG("TEST", "Init done."); + test_duc->set_property("interp", TEST_INTERP, 0); + + BOOST_REQUIRE(duc_reg_iface->write_memory.count(duc_block_control::SR_INTERP_ADDR)); + BOOST_CHECK_EQUAL( + duc_reg_iface->write_memory.at(duc_block_control::SR_INTERP_ADDR), 2 << 8 | 5); + BOOST_CHECK_EQUAL(test_duc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU); + + // Now plop it in a graph + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_info; + edge_info.src_port = 0; + edge_info.dst_port = 0; + edge_info.property_propagation_active = true; + edge_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_terminator_t mock_source_term(1, {ACTION_KEY_STREAM_CMD}); + mock_terminator_t mock_sink_term(1, {ACTION_KEY_STREAM_CMD}); + + UHD_LOG_INFO("TEST", "Priming mock source node props"); + mock_source_term.set_edge_property( + "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property( + "scaling", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property( + "samp_rate", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + UHD_LOG_INFO("TEST", "Priming mock sink node props"); + mock_sink_term.set_edge_property( + "type", "sc16", {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property( + "scaling", 1.0, {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property( + "samp_rate", 1.0, {res_source_info::INPUT_EDGE, 0}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + graph.connect(&mock_source_term, test_duc.get(), edge_info); + graph.connect(test_duc.get(), &mock_sink_term, edge_info); + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + UHD_LOG_INFO("TEST", "Commit complete."); + // We need to set the interpation again, because the rates will screw it + // change it w.r.t. to the previous setting + test_duc->set_property("interp", TEST_INTERP, 0); + BOOST_CHECK_EQUAL(test_duc->get_property("interp", 0), TEST_INTERP); + BOOST_CHECK(mock_source_term.get_edge_property( + "samp_rate", {res_source_info::OUTPUT_EDGE, 0}) + * TEST_INTERP + == mock_sink_term.get_edge_property( + "samp_rate", {res_source_info::INPUT_EDGE, 0})); + const double initial_input_scaling = mock_source_term.get_edge_property( + "scaling", {res_source_info::OUTPUT_EDGE, 0}); + const double initial_output_scaling = mock_sink_term.get_edge_property( + "scaling", {res_source_info::INPUT_EDGE, 0}); + // Our chosen interpolation value will cause some scaling issues, so + // this value needs to be off from 1.0 + BOOST_CHECK(initial_input_scaling != 1.0); + BOOST_CHECK(initial_output_scaling == 1.0); + + // The DUC will not let us set the scaling on its input, so the following + // call to set property should have no effect + mock_source_term.set_edge_property( + "scaling", 42.0, {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK(initial_input_scaling + == mock_source_term.get_edge_property( + "scaling", {res_source_info::OUTPUT_EDGE, 0})); + BOOST_CHECK(initial_output_scaling + == mock_sink_term.get_edge_property( + "scaling", {res_source_info::INPUT_EDGE, 0})); + // However, if we change the scaling on the DUC's output, that will + // propagate to its input + UHD_LOG_INFO("TEST", "Testing doubling the output scaling..."); + mock_sink_term.set_edge_property( + "scaling", 2.0, {res_source_info::INPUT_EDGE, 0}); + const double doubled_input_scaling = mock_source_term.get_edge_property( + "scaling", {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(doubled_input_scaling, 2 * initial_input_scaling); + + UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate"); + constexpr double TEST_FREQ = 1.0 / 8; + test_duc->set_property("freq", TEST_FREQ, 0); + const uint32_t freq_word_1 = + duc_reg_iface->write_memory.at(duc_block_control::SR_FREQ_ADDR); + BOOST_REQUIRE(freq_word_1 != 0); + UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)"); + // Now this should change the freq word, but not the absolute frequency + mock_sink_term.set_edge_property("samp_rate", + 2 + * mock_sink_term.get_edge_property( + "samp_rate", {res_source_info::INPUT_EDGE, 0}), + {res_source_info::INPUT_EDGE, 0}); + const double freq_word_2 = + duc_reg_iface->write_memory.at(duc_block_control::SR_FREQ_ADDR); + // The frequency word is the phase increment, which will halve. We skirt + // around fixpoint/floating point accuracy issues by using CLOSE. + BOOST_CHECK_CLOSE(double(freq_word_1) / double(freq_word_2), 2.0, 1e-6); + + // Reset the interpolation + test_duc->set_property("interp", TEST_INTERP, 0); + BOOST_REQUIRE_EQUAL(test_duc->get_property("interp", 0), TEST_INTERP); + UHD_LOG_INFO("TEST", "DUC: Testing action forwarding"); + auto new_stream_cmd_action = + stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + new_stream_cmd_action->stream_cmd.num_samps = 1000; + node_accessor.post_action( + &mock_sink_term, {res_source_info::INPUT_EDGE, 0}, new_stream_cmd_action); + BOOST_REQUIRE(!mock_source_term.received_actions.empty()); + auto stream_cmd_recv_by_src = std::dynamic_pointer_cast( + mock_source_term.received_actions.back()); + BOOST_CHECK(stream_cmd_recv_by_src); + BOOST_CHECK_EQUAL(stream_cmd_recv_by_src->stream_cmd.num_samps, 1000 / TEST_INTERP); + auto new_stream_cmd_action2 = + stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + node_accessor.post_action( + &mock_sink_term, {res_source_info::INPUT_EDGE, 0}, new_stream_cmd_action2); + BOOST_REQUIRE(!mock_source_term.received_actions.empty()); + auto stream_cmd_recv_by_src2 = std::dynamic_pointer_cast( + mock_source_term.received_actions.back()); + BOOST_CHECK_EQUAL(stream_cmd_recv_by_src2->stream_cmd.stream_mode, + uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + auto new_stream_cmd_action3 = + stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + new_stream_cmd_action3->stream_cmd.num_samps = 100; + node_accessor.post_action( + &mock_source_term, {res_source_info::OUTPUT_EDGE, 0}, new_stream_cmd_action3); + BOOST_REQUIRE(!mock_sink_term.received_actions.empty()); + auto stream_cmd_recv_by_src3 = std::dynamic_pointer_cast( + mock_sink_term.received_actions.back()); + BOOST_CHECK(stream_cmd_recv_by_src3); + BOOST_CHECK_EQUAL(stream_cmd_recv_by_src3->stream_cmd.num_samps, 100 * TEST_INTERP); +} diff --git a/host/tests/rfnoc_block_tests/fir_filter_block_test.cpp b/host/tests/rfnoc_block_tests/fir_filter_block_test.cpp new file mode 100644 index 000000000..0ef725599 --- /dev/null +++ b/host/tests/rfnoc_block_tests/fir_filter_block_test.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../rfnoc_graph_mock_nodes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace uhd::rfnoc; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +/* + * This class extends mock_reg_iface_t by adding poke and peek hooks that + * monitor writes and reads to the registers implemented within the FIR + * filter RFNoC block hardware and emulating the expected behavior of the + * hardware when those registers are read and written. For instance, writes + * to the coefficient registers store the coefficients in a vector and + * track the position of the last write to the REG_FIR_LOAD_COEFF_LAST_ADDR + * register to allow the unit test to gauge the proper operation of the FIR + * filter block controller. + */ +class fir_filter_mock_reg_iface_t : public mock_reg_iface_t +{ +public: + fir_filter_mock_reg_iface_t(size_t max_num_coeffs) : _max_num_coeffs(max_num_coeffs) + { + } + + virtual void _poke_cb( + uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) + { + if (addr == fir_filter_block_control::REG_FIR_MAX_NUM_COEFFS_ADDR) { + throw uhd::assertion_error("Invalid write to read-only register"); + } else if (addr == fir_filter_block_control::REG_FIR_LOAD_COEFF_ADDR) { + coeffs.push_back(uhd::narrow_cast(data)); + } else if (addr == fir_filter_block_control::REG_FIR_LOAD_COEFF_LAST_ADDR) { + last_coeff_write_pos = coeffs.size(); + coeffs.push_back(uhd::narrow_cast(data)); + } else { + throw uhd::assertion_error("Invalid write to out of bounds address"); + } + } + + virtual void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) + { + if (addr == fir_filter_block_control::REG_FIR_MAX_NUM_COEFFS_ADDR) { + read_memory[addr] = uhd::narrow_cast(_max_num_coeffs); + } else { + throw uhd::assertion_error("Invalid read from out of bounds address"); + } + } + + void reset() + { + last_coeff_write_pos = 0; + coeffs.clear(); + } + + size_t last_coeff_write_pos = 0; + std::vector coeffs{}; + +private: + const size_t _max_num_coeffs; +}; + + +/* fir_filter_block_fixture is a class which is instantiated before each test + * case is run. It sets up the block container, mock register interface, + * and fir_filter_block_control object, all of which are accessible to the test + * case. The instance of the object is destroyed at the end of each test + * case. + */ +constexpr size_t MAX_NUM_COEFFS = 3000; +constexpr size_t DEFAULT_MTU = 8000; + +struct fir_filter_block_fixture +{ + fir_filter_block_fixture() + : reg_iface(std::make_shared(MAX_NUM_COEFFS)) + , block_container(get_mock_block(FIR_FILTER_BLOCK, + 1, + 2, + uhd::device_addr_t(), + DEFAULT_MTU, + ANY_DEVICE, + reg_iface)) + , test_fir_filter(block_container.get_block()) + { + node_accessor.init_props(test_fir_filter.get()); + } + + std::shared_ptr reg_iface; + mock_block_container block_container; + std::shared_ptr test_fir_filter; + node_accessor_t node_accessor{}; +}; + +/* + * This test case ensures that the hardware is programmed correctly with + * defaults when the fir_filter block is constructed. + */ +BOOST_FIXTURE_TEST_CASE(fir_filter_test_construction, fir_filter_block_fixture) +{ + // Check that the number of coefficients is expected + BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); + // Check that the first coefficient is the only non-zero value + // (impulse response) + BOOST_CHECK_NE(reg_iface->coeffs.at(0), 0); + for (size_t i = 1; i < reg_iface->coeffs.size(); i++) { + BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); + } + // Check that the LOAD_COEFF_LAST register was written at the right + // time (i.e. with the last value) + BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); +} + +/* + * This test case exercises the get_max_num_coefficients() API. + */ +BOOST_FIXTURE_TEST_CASE(fir_filter_test_max_num_coeffs, fir_filter_block_fixture) +{ + BOOST_CHECK_EQUAL(test_fir_filter->get_max_num_coefficients(), MAX_NUM_COEFFS); +} + +/* + * This test case exercises the set_coefficients() API and get_coefficients() + * APIs and ensures that the hardware registers are programmed appropriately + * when new coefficients are specified. + */ +BOOST_FIXTURE_TEST_CASE(fir_filter_test_set_get_coefficients, fir_filter_block_fixture) +{ + // Reset state of mock FIR filter register interface + reg_iface->reset(); + + // First test: 10 coefficients + std::vector coeffs1{1, 2, 3, 4, 5, -1, -2, -3, -4, -5}; + test_fir_filter->set_coefficients(coeffs1); + + // Check that all coefficients were written + BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); + + // Check correctness of coefficients + for (size_t i = 0; i < coeffs1.size(); i++) { + BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), coeffs1.at(i)); + } + for (size_t i = coeffs1.size(); i < MAX_NUM_COEFFS; i++) { + BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); + } + // Check that the LOAD_COEFF_LAST register was written at the right + // time (i.e. with the last value) + BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); + + // Verify that get_coefficients() returns what we expect. Note that + // get_coefficients() returns the padded set of coefficients. + std::vector received_coeffs = test_fir_filter->get_coefficients(); + + BOOST_CHECK_EQUAL(received_coeffs.size(), MAX_NUM_COEFFS); + + // Check correctness of returned coefficients + for (size_t i = 0; i < coeffs1.size(); i++) { + BOOST_CHECK_EQUAL(received_coeffs.at(i), coeffs1.at(i)); + } + for (size_t i = coeffs1.size(); i < MAX_NUM_COEFFS; i++) { + BOOST_CHECK_EQUAL(received_coeffs.at(i), 0); + } + + reg_iface->reset(); + + // Now update the coefficients with a smaller set, and ensure that + // the hardware gets the correct coefficients + std::vector coeffs2{1, 3, 5, 7}; + test_fir_filter->set_coefficients(coeffs2); + + // Check that all coefficients were written + BOOST_CHECK_EQUAL(reg_iface->coeffs.size(), MAX_NUM_COEFFS); + + // Check correctness of coefficients + for (size_t i = 0; i < coeffs2.size(); i++) { + BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), coeffs2.at(i)); + } + for (size_t i = coeffs2.size(); i < MAX_NUM_COEFFS; i++) { + BOOST_CHECK_EQUAL(reg_iface->coeffs.at(i), 0); + } + // Check that the LOAD_COEFF_LAST register was written at the right + // time (i.e. with the last value) + BOOST_CHECK_EQUAL(reg_iface->last_coeff_write_pos, MAX_NUM_COEFFS - 1); +} + +/* + * This test case exercises the coefficient length checking of + * set_coefficients(). + */ +BOOST_FIXTURE_TEST_CASE(fir_filter_test_length_error, fir_filter_block_fixture) +{ + size_t num_coeffs = test_fir_filter->get_max_num_coefficients(); + std::vector coeffs(num_coeffs * 2); + BOOST_CHECK_THROW(test_fir_filter->set_coefficients(coeffs), uhd::value_error); +} + +/* + * This test case ensures that the FIR filter block can be added to + * an RFNoC graph. + */ +BOOST_FIXTURE_TEST_CASE(fir_filter_test_graph, fir_filter_block_fixture) +{ + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_port_info; + edge_port_info.src_port = 0; + edge_port_info.dst_port = 0; + edge_port_info.property_propagation_active = true; + edge_port_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_radio_node_t mock_radio_block{0}; + mock_ddc_node_t mock_ddc_block{}; + mock_terminator_t mock_sink_term(1, {}, "MOCK_SINK"); + + UHD_LOG_INFO("TEST", "Priming mock block properties"); + node_accessor.init_props(&mock_radio_block); + node_accessor.init_props(&mock_ddc_block); + mock_sink_term.set_edge_property( + "type", "sc16", {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property( + "type", "sc16", {res_source_info::INPUT_EDGE, 1}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + graph.connect(&mock_radio_block, &mock_ddc_block, edge_port_info); + graph.connect(&mock_ddc_block, test_fir_filter.get(), edge_port_info); + graph.connect(test_fir_filter.get(), &mock_sink_term, edge_port_info); + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + UHD_LOG_INFO("TEST", "Commit complete."); +} diff --git a/host/tests/rfnoc_block_tests/null_block_test.cpp b/host/tests/rfnoc_block_tests/null_block_test.cpp new file mode 100644 index 000000000..b6eddd62a --- /dev/null +++ b/host/tests/rfnoc_block_tests/null_block_test.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../rfnoc_graph_mock_nodes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace uhd::rfnoc; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +namespace { + +constexpr size_t DEFAULT_MTU = 8000; + +} // namespace + +BOOST_AUTO_TEST_CASE(test_null_block) +{ + node_accessor_t node_accessor{}; + constexpr size_t num_chans = 2; + constexpr uint32_t nipc = 2; + constexpr uint32_t item_width = 32; + constexpr noc_id_t noc_id = 0x00000001; + + auto block_container = get_mock_block(noc_id, num_chans, num_chans); + // Shorthand to save typing + auto& reg_iface = block_container.reg_iface; + auto set_mem = [&](const uint32_t addr, const uint32_t data) { + reg_iface->read_memory[addr] = data; + }; + auto get_mem = [&](const uint32_t addr) { return reg_iface->write_memory[addr]; }; + auto copy_mem = [&](const uint32_t addr) { set_mem(addr, get_mem(addr)); }; + set_mem(null_block_control::REG_CTRL_STATUS, (nipc << 24) | (item_width << 16)); + + auto test_null = block_container.get_block(); + BOOST_REQUIRE(test_null); + + using uhd::stream_cmd_t; + node_accessor.init_props(test_null.get()); + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + test_null->issue_stream_cmd(stream_cmd); + stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE; + BOOST_REQUIRE_THROW(test_null->issue_stream_cmd(stream_cmd), uhd::runtime_error); + + constexpr uint64_t snk_count = 1000000000; + constexpr uint64_t snk_count_pkts = 5; + constexpr uint64_t src_count = 2323232323; + constexpr uint64_t loop_count = 4242424242; + set_mem(null_block_control::REG_SNK_LINE_CNT_LO, + uhd::narrow_cast(snk_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_LINE_CNT_HI, + uhd::narrow_cast((snk_count >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_PKT_CNT_LO, + uhd::narrow_cast(snk_count_pkts & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_PKT_CNT_HI, + uhd::narrow_cast((snk_count_pkts >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SRC_LINE_CNT_LO, + uhd::narrow_cast(src_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SRC_LINE_CNT_HI, + uhd::narrow_cast((src_count >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_LOOP_LINE_CNT_LO, + uhd::narrow_cast(loop_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_LOOP_LINE_CNT_HI, + uhd::narrow_cast((loop_count >> 32) & 0xFFFFFFFF)); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SINK, null_block_control::LINES), + snk_count); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SINK, null_block_control::PACKETS), + snk_count_pkts); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SOURCE, null_block_control::LINES), + src_count); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::LOOP, null_block_control::LINES), + loop_count); + + constexpr uint32_t lpp = 3; + constexpr uint32_t bpp = nipc * item_width / 8 * lpp; + test_null->set_bytes_per_packet(bpp); + copy_mem(null_block_control::REG_SRC_LINES_PER_PKT); + copy_mem(null_block_control::REG_SRC_BYTES_PER_PKT); + BOOST_CHECK_EQUAL(test_null->get_lines_per_packet(), lpp); + BOOST_CHECK_EQUAL(test_null->get_bytes_per_packet(), bpp); + + auto sca = stream_cmd_action_info::make(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + node_accessor.send_action(test_null.get(), {res_source_info::OUTPUT_EDGE, 0}, sca); + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); + BOOST_REQUIRE_THROW(node_accessor.send_action( + test_null.get(), {res_source_info::OUTPUT_EDGE, 1}, sca), + uhd::runtime_error); + BOOST_REQUIRE_THROW( + node_accessor.send_action(test_null.get(), {res_source_info::INPUT_EDGE, 0}, sca), + uhd::runtime_error); + + stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + test_null->issue_stream_cmd(stream_cmd); + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); + node_accessor.shutdown(test_null.get()); + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x0); + test_null->issue_stream_cmd(stream_cmd); + UHD_LOG_INFO("TEST", "Expected error message here ^^^"); + // The last issue_stream_cmd should do nothing b/c we called shutdown + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x0); +} diff --git a/host/tests/rfnoc_blocks_test.cpp b/host/tests/rfnoc_blocks_test.cpp deleted file mode 100644 index 72268b79f..000000000 --- a/host/tests/rfnoc_blocks_test.cpp +++ /dev/null @@ -1,371 +0,0 @@ -// -// Copyright 2019 Ettus Research, a National Instruments Brand -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rfnoc_graph_mock_nodes.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace uhd::rfnoc; - -// Redeclare this here, since it's only defined outside of UHD_API -noc_block_base::make_args_t::~make_args_t() = default; - -namespace { - -constexpr size_t DEFAULT_MTU = 8000; - -} // namespace - -BOOST_AUTO_TEST_CASE(test_null_block) -{ - node_accessor_t node_accessor{}; - constexpr size_t num_chans = 2; - constexpr uint32_t nipc = 2; - constexpr uint32_t item_width = 32; - constexpr noc_id_t noc_id = 0x00000001; - - auto block_container = get_mock_block(noc_id, num_chans, num_chans); - // Shorthand to save typing - auto& reg_iface = block_container.reg_iface; - auto set_mem = [&](const uint32_t addr, const uint32_t data) { - reg_iface->read_memory[addr] = data; - }; - auto get_mem = [&](const uint32_t addr) { return reg_iface->write_memory[addr]; }; - auto copy_mem = [&](const uint32_t addr) { set_mem(addr, get_mem(addr)); }; - set_mem(null_block_control::REG_CTRL_STATUS, (nipc << 24) | (item_width << 16)); - - auto test_null = block_container.get_block(); - BOOST_REQUIRE(test_null); - - using uhd::stream_cmd_t; - node_accessor.init_props(test_null.get()); - uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - test_null->issue_stream_cmd(stream_cmd); - stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE; - BOOST_REQUIRE_THROW(test_null->issue_stream_cmd(stream_cmd), uhd::runtime_error); - - constexpr uint64_t snk_count = 1000000000; - constexpr uint64_t snk_count_pkts = 5; - constexpr uint64_t src_count = 2323232323; - constexpr uint64_t loop_count = 4242424242; - set_mem(null_block_control::REG_SNK_LINE_CNT_LO, - uhd::narrow_cast(snk_count & 0xFFFFFFFF)); - set_mem(null_block_control::REG_SNK_LINE_CNT_HI, - uhd::narrow_cast((snk_count >> 32) & 0xFFFFFFFF)); - set_mem(null_block_control::REG_SNK_PKT_CNT_LO, - uhd::narrow_cast(snk_count_pkts & 0xFFFFFFFF)); - set_mem(null_block_control::REG_SNK_PKT_CNT_HI, - uhd::narrow_cast((snk_count_pkts >> 32) & 0xFFFFFFFF)); - set_mem(null_block_control::REG_SRC_LINE_CNT_LO, - uhd::narrow_cast(src_count & 0xFFFFFFFF)); - set_mem(null_block_control::REG_SRC_LINE_CNT_HI, - uhd::narrow_cast((src_count >> 32) & 0xFFFFFFFF)); - set_mem(null_block_control::REG_LOOP_LINE_CNT_LO, - uhd::narrow_cast(loop_count & 0xFFFFFFFF)); - set_mem(null_block_control::REG_LOOP_LINE_CNT_HI, - uhd::narrow_cast((loop_count >> 32) & 0xFFFFFFFF)); - BOOST_CHECK_EQUAL( - test_null->get_count(null_block_control::SINK, null_block_control::LINES), - snk_count); - BOOST_CHECK_EQUAL( - test_null->get_count(null_block_control::SINK, null_block_control::PACKETS), - snk_count_pkts); - BOOST_CHECK_EQUAL( - test_null->get_count(null_block_control::SOURCE, null_block_control::LINES), - src_count); - BOOST_CHECK_EQUAL( - test_null->get_count(null_block_control::LOOP, null_block_control::LINES), - loop_count); - - constexpr uint32_t lpp = 3; - constexpr uint32_t bpp = nipc * item_width / 8 * lpp; - test_null->set_bytes_per_packet(bpp); - copy_mem(null_block_control::REG_SRC_LINES_PER_PKT); - copy_mem(null_block_control::REG_SRC_BYTES_PER_PKT); - BOOST_CHECK_EQUAL(test_null->get_lines_per_packet(), lpp); - BOOST_CHECK_EQUAL(test_null->get_bytes_per_packet(), bpp); - - auto sca = stream_cmd_action_info::make(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - node_accessor.send_action(test_null.get(), {res_source_info::OUTPUT_EDGE, 0}, sca); - BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); - BOOST_REQUIRE_THROW(node_accessor.send_action( - test_null.get(), {res_source_info::OUTPUT_EDGE, 1}, sca), - uhd::runtime_error); - BOOST_REQUIRE_THROW( - node_accessor.send_action(test_null.get(), {res_source_info::INPUT_EDGE, 0}, sca), - uhd::runtime_error); - - stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_START_CONTINUOUS; - test_null->issue_stream_cmd(stream_cmd); - BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); - node_accessor.shutdown(test_null.get()); - BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x0); - test_null->issue_stream_cmd(stream_cmd); - UHD_LOG_INFO("TEST", "Expected error message here ^^^"); - // The last issue_stream_cmd should do nothing b/c we called shutdown - BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x0); -} - -BOOST_AUTO_TEST_CASE(test_ddc_block) -{ - node_accessor_t node_accessor{}; - constexpr uint32_t num_hb = 2; - constexpr uint32_t max_cic = 128; - constexpr size_t num_chans = 4; - constexpr noc_id_t noc_id = DDC_BLOCK; - constexpr int TEST_DECIM = 20; - - auto block_container = - get_mock_block(noc_id, num_chans, num_chans, uhd::device_addr_t("foo=bar")); - auto& ddc_reg_iface = block_container.reg_iface; - ddc_reg_iface->read_memory[ddc_block_control::RB_COMPAT_NUM] = - (ddc_block_control::MAJOR_COMPAT << 16) | ddc_block_control::MINOR_COMPAT; - ddc_reg_iface->read_memory[ddc_block_control::RB_NUM_HB] = num_hb; - ddc_reg_iface->read_memory[ddc_block_control::RB_CIC_MAX_DECIM] = max_cic; - auto test_ddc = block_container.get_block(); - BOOST_REQUIRE(test_ddc); - BOOST_CHECK_EQUAL(test_ddc->get_block_args().get("foo"), "bar"); - - node_accessor.init_props(test_ddc.get()); - UHD_LOG_DEBUG("TEST", "Init done."); - test_ddc->set_property("decim", TEST_DECIM, 0); - - BOOST_REQUIRE(ddc_reg_iface->write_memory.count(ddc_block_control::SR_DECIM_ADDR)); - BOOST_CHECK_EQUAL( - ddc_reg_iface->write_memory.at(ddc_block_control::SR_DECIM_ADDR), 2 << 8 | 5); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU); - - // Now plop it in a graph - detail::graph_t graph{}; - detail::graph_t::graph_edge_t edge_info; - edge_info.src_port = 0; - edge_info.dst_port = 0; - edge_info.property_propagation_active = true; - edge_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; - - mock_terminator_t mock_source_term(1); - mock_terminator_t mock_sink_term(1); - - UHD_LOG_INFO("TEST", "Priming mock source node props"); - mock_source_term.set_edge_property( - "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); - mock_source_term.set_edge_property( - "scaling", 1.0, {res_source_info::OUTPUT_EDGE, 0}); - mock_source_term.set_edge_property( - "samp_rate", 1.0, {res_source_info::OUTPUT_EDGE, 0}); - constexpr size_t NEW_MTU = 4000; - mock_source_term.set_edge_property( - "mtu", NEW_MTU, {res_source_info::OUTPUT_EDGE, 0}); - - UHD_LOG_INFO("TEST", "Creating graph..."); - graph.connect(&mock_source_term, test_ddc.get(), edge_info); - graph.connect(test_ddc.get(), &mock_sink_term, edge_info); - UHD_LOG_INFO("TEST", "Committing graph..."); - graph.commit(); - UHD_LOG_INFO("TEST", "Commit complete."); - // We need to set the decimation again, because the rates will screw it - // change it w.r.t. to the previous setting - test_ddc->set_property("decim", TEST_DECIM, 0); - BOOST_CHECK_EQUAL(test_ddc->get_property("decim", 0), TEST_DECIM); - BOOST_CHECK(mock_source_term.get_edge_property( - "samp_rate", {res_source_info::OUTPUT_EDGE, 0}) - == mock_sink_term.get_edge_property( - "samp_rate", {res_source_info::INPUT_EDGE, 0}) - * TEST_DECIM); - BOOST_CHECK(mock_sink_term.get_edge_property( - "scaling", {res_source_info::INPUT_EDGE, 0}) - != 1.0); - - UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate"); - constexpr double TEST_FREQ = 1.0 / 8; - test_ddc->set_property("freq", TEST_FREQ, 0); - const uint32_t freq_word_1 = - ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); - BOOST_REQUIRE(freq_word_1 != 0); - UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)"); - // Now this should change the freq word, but not the absolute frequency - mock_source_term.set_edge_property( - "samp_rate", 2.0, {res_source_info::OUTPUT_EDGE, 0}); - const double freq_word_2 = - ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); - // The frequency word is the phase increment, which will halve. We skirt - // around fixpoint/floating point accuracy issues by using CLOSE. - BOOST_CHECK_CLOSE(double(freq_word_1) / double(freq_word_2), 2.0, 1e-6); - - UHD_LOG_INFO("TEST", "Testing DDC MTU propagation"); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), NEW_MTU); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 0}), NEW_MTU); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 1}), DEFAULT_MTU); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 1}), DEFAULT_MTU); - mock_source_term.set_edge_property( - "mtu", NEW_MTU / 2, {res_source_info::OUTPUT_EDGE, 0}); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), NEW_MTU / 2); - BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::OUTPUT_EDGE, 0}), NEW_MTU / 2); - - // Now reset the props using set_properties - test_ddc->set_properties(uhd::device_addr_t("decim=1,freq=0.0,foo=bar"), 0); - BOOST_CHECK_EQUAL(test_ddc->get_property("decim", 0), 1); - BOOST_CHECK_EQUAL(test_ddc->get_property("freq", 0), 0.0); -} - -BOOST_AUTO_TEST_CASE(test_duc_block) -{ - node_accessor_t node_accessor{}; - constexpr uint32_t num_hb = 2; - constexpr uint32_t max_cic = 128; - constexpr size_t num_chans = 4; - constexpr noc_id_t noc_id = DUC_BLOCK; - constexpr int TEST_INTERP = 20; // 2 halfbands, CIC==5 - - auto block_container = get_mock_block(noc_id, num_chans, num_chans); - auto& duc_reg_iface = block_container.reg_iface; - duc_reg_iface->read_memory[duc_block_control::RB_COMPAT_NUM] = - (duc_block_control::MAJOR_COMPAT << 16) | duc_block_control::MINOR_COMPAT; - duc_reg_iface->read_memory[duc_block_control::RB_NUM_HB] = num_hb; - duc_reg_iface->read_memory[duc_block_control::RB_CIC_MAX_INTERP] = max_cic; - auto test_duc = block_container.get_block(); - BOOST_REQUIRE(test_duc); - - node_accessor.init_props(test_duc.get()); - UHD_LOG_DEBUG("TEST", "Init done."); - test_duc->set_property("interp", TEST_INTERP, 0); - - BOOST_REQUIRE(duc_reg_iface->write_memory.count(duc_block_control::SR_INTERP_ADDR)); - BOOST_CHECK_EQUAL( - duc_reg_iface->write_memory.at(duc_block_control::SR_INTERP_ADDR), 2 << 8 | 5); - BOOST_CHECK_EQUAL(test_duc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU); - - // Now plop it in a graph - detail::graph_t graph{}; - detail::graph_t::graph_edge_t edge_info; - edge_info.src_port = 0; - edge_info.dst_port = 0; - edge_info.property_propagation_active = true; - edge_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; - - mock_terminator_t mock_source_term(1, {ACTION_KEY_STREAM_CMD}); - mock_terminator_t mock_sink_term(1, {ACTION_KEY_STREAM_CMD}); - - UHD_LOG_INFO("TEST", "Priming mock source node props"); - mock_source_term.set_edge_property( - "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); - mock_source_term.set_edge_property( - "scaling", 1.0, {res_source_info::OUTPUT_EDGE, 0}); - mock_source_term.set_edge_property( - "samp_rate", 1.0, {res_source_info::OUTPUT_EDGE, 0}); - UHD_LOG_INFO("TEST", "Priming mock sink node props"); - mock_sink_term.set_edge_property( - "type", "sc16", {res_source_info::INPUT_EDGE, 0}); - mock_sink_term.set_edge_property( - "scaling", 1.0, {res_source_info::INPUT_EDGE, 0}); - mock_sink_term.set_edge_property( - "samp_rate", 1.0, {res_source_info::INPUT_EDGE, 0}); - - UHD_LOG_INFO("TEST", "Creating graph..."); - graph.connect(&mock_source_term, test_duc.get(), edge_info); - graph.connect(test_duc.get(), &mock_sink_term, edge_info); - UHD_LOG_INFO("TEST", "Committing graph..."); - graph.commit(); - UHD_LOG_INFO("TEST", "Commit complete."); - // We need to set the interpation again, because the rates will screw it - // change it w.r.t. to the previous setting - test_duc->set_property("interp", TEST_INTERP, 0); - BOOST_CHECK_EQUAL(test_duc->get_property("interp", 0), TEST_INTERP); - BOOST_CHECK(mock_source_term.get_edge_property( - "samp_rate", {res_source_info::OUTPUT_EDGE, 0}) - * TEST_INTERP - == mock_sink_term.get_edge_property( - "samp_rate", {res_source_info::INPUT_EDGE, 0})); - const double initial_input_scaling = mock_source_term.get_edge_property( - "scaling", {res_source_info::OUTPUT_EDGE, 0}); - const double initial_output_scaling = mock_sink_term.get_edge_property( - "scaling", {res_source_info::INPUT_EDGE, 0}); - // Our chosen interpolation value will cause some scaling issues, so - // this value needs to be off from 1.0 - BOOST_CHECK(initial_input_scaling != 1.0); - BOOST_CHECK(initial_output_scaling == 1.0); - - // The DUC will not let us set the scaling on its input, so the following - // call to set property should have no effect - mock_source_term.set_edge_property( - "scaling", 42.0, {res_source_info::OUTPUT_EDGE, 0}); - BOOST_CHECK(initial_input_scaling - == mock_source_term.get_edge_property( - "scaling", {res_source_info::OUTPUT_EDGE, 0})); - BOOST_CHECK(initial_output_scaling - == mock_sink_term.get_edge_property( - "scaling", {res_source_info::INPUT_EDGE, 0})); - // However, if we change the scaling on the DUC's output, that will - // propagate to its input - UHD_LOG_INFO("TEST", "Testing doubling the output scaling..."); - mock_sink_term.set_edge_property( - "scaling", 2.0, {res_source_info::INPUT_EDGE, 0}); - const double doubled_input_scaling = mock_source_term.get_edge_property( - "scaling", {res_source_info::OUTPUT_EDGE, 0}); - BOOST_CHECK_EQUAL(doubled_input_scaling, 2 * initial_input_scaling); - - UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate"); - constexpr double TEST_FREQ = 1.0 / 8; - test_duc->set_property("freq", TEST_FREQ, 0); - const uint32_t freq_word_1 = - duc_reg_iface->write_memory.at(duc_block_control::SR_FREQ_ADDR); - BOOST_REQUIRE(freq_word_1 != 0); - UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)"); - // Now this should change the freq word, but not the absolute frequency - mock_sink_term.set_edge_property("samp_rate", - 2 - * mock_sink_term.get_edge_property( - "samp_rate", {res_source_info::INPUT_EDGE, 0}), - {res_source_info::INPUT_EDGE, 0}); - const double freq_word_2 = - duc_reg_iface->write_memory.at(duc_block_control::SR_FREQ_ADDR); - // The frequency word is the phase increment, which will halve. We skirt - // around fixpoint/floating point accuracy issues by using CLOSE. - BOOST_CHECK_CLOSE(double(freq_word_1) / double(freq_word_2), 2.0, 1e-6); - - // Reset the interpolation - test_duc->set_property("interp", TEST_INTERP, 0); - BOOST_REQUIRE_EQUAL(test_duc->get_property("interp", 0), TEST_INTERP); - UHD_LOG_INFO("TEST", "DUC: Testing action forwarding"); - auto new_stream_cmd_action = - stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); - new_stream_cmd_action->stream_cmd.num_samps = 1000; - node_accessor.post_action( - &mock_sink_term, {res_source_info::INPUT_EDGE, 0}, new_stream_cmd_action); - BOOST_REQUIRE(!mock_source_term.received_actions.empty()); - auto stream_cmd_recv_by_src = std::dynamic_pointer_cast( - mock_source_term.received_actions.back()); - BOOST_CHECK(stream_cmd_recv_by_src); - BOOST_CHECK_EQUAL(stream_cmd_recv_by_src->stream_cmd.num_samps, 1000 / TEST_INTERP); - auto new_stream_cmd_action2 = - stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - node_accessor.post_action( - &mock_sink_term, {res_source_info::INPUT_EDGE, 0}, new_stream_cmd_action2); - BOOST_REQUIRE(!mock_source_term.received_actions.empty()); - auto stream_cmd_recv_by_src2 = std::dynamic_pointer_cast( - mock_source_term.received_actions.back()); - BOOST_CHECK_EQUAL(stream_cmd_recv_by_src2->stream_cmd.stream_mode, - uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - auto new_stream_cmd_action3 = - stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); - new_stream_cmd_action3->stream_cmd.num_samps = 100; - node_accessor.post_action( - &mock_source_term, {res_source_info::OUTPUT_EDGE, 0}, new_stream_cmd_action3); - BOOST_REQUIRE(!mock_sink_term.received_actions.empty()); - auto stream_cmd_recv_by_src3 = std::dynamic_pointer_cast( - mock_sink_term.received_actions.back()); - BOOST_CHECK(stream_cmd_recv_by_src3); - BOOST_CHECK_EQUAL(stream_cmd_recv_by_src3->stream_cmd.num_samps, 100 * TEST_INTERP); -} -- cgit v1.2.3