// // 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 window * 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_WINDOW_LOAD_COEFF_LAST_OFFSET * register to allow the unit test to gauge the proper operation of the * block controller. */ class window_mock_reg_iface_t : public mock_reg_iface_t { public: window_mock_reg_iface_t(size_t num_chans, std::vector max_num_coeffs) : last_coeff_write_pos(num_chans, 0) , coeffs(num_chans) , _num_chans(num_chans) , _max_num_coeffs(max_num_coeffs) { reset(); } virtual void _poke_cb( uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) { size_t chan = addr / window_block_control::REG_WINDOW_BLOCK_SIZE; size_t offset = addr % window_block_control::REG_WINDOW_BLOCK_SIZE; if (chan >= _num_chans) { throw uhd::assertion_error("Invalid channel index"); } if (offset == window_block_control::REG_WINDOW_MAX_LEN_OFFSET) { throw uhd::assertion_error("Invalid write to read-only register"); } else if (offset == window_block_control::REG_WINDOW_LOAD_COEFF_OFFSET) { coeffs.at(chan).push_back(uhd::narrow_cast(data)); } else if (offset == window_block_control::REG_WINDOW_LOAD_COEFF_LAST_OFFSET) { last_coeff_write_pos[chan] = coeffs.at(chan).size(); coeffs.at(chan).push_back(uhd::narrow_cast(data)); } else { throw uhd::assertion_error("Invalid write to out of bounds offset"); } } virtual void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) { size_t chan = addr / window_block_control::REG_WINDOW_BLOCK_SIZE; size_t offset = addr % window_block_control::REG_WINDOW_BLOCK_SIZE; if (chan >= _num_chans) { throw uhd::assertion_error("Invalid channel index"); } if (offset == window_block_control::REG_WINDOW_MAX_LEN_OFFSET) { read_memory[addr] = uhd::narrow_cast(_max_num_coeffs.at(chan)); } else { throw uhd::assertion_error("Invalid read from out of bounds address"); } } void reset() { for (size_t chan = 0; chan < _num_chans; chan++) { last_coeff_write_pos[chan] = 0; coeffs.at(chan).clear(); } } std::vector last_coeff_write_pos; std::vector> coeffs; private: const size_t _num_chans; const std::vector _max_num_coeffs; }; /* window_block_fixture is a class which is instantiated before each test * case is run. It sets up the block container, mock register interface, * and window_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_LEN = 3000; constexpr size_t DEFAULT_MTU = 8000; constexpr size_t NUM_CHANS = 4; static const std::vector MAX_LENS{3000, 2500, 15, 666}; struct window_block_fixture { window_block_fixture() : reg_iface(std::make_shared(NUM_CHANS, MAX_LENS)) , block_container(get_mock_block(WINDOW_BLOCK, NUM_CHANS, NUM_CHANS, uhd::device_addr_t(), DEFAULT_MTU, ANY_DEVICE, reg_iface)) , test_window(block_container.get_block()) { node_accessor.init_props(test_window.get()); } std::shared_ptr reg_iface; mock_block_container block_container; std::shared_ptr test_window; node_accessor_t node_accessor{}; }; /* * This test case ensures that the hardware is programmed correctly with * defaults when the window block is constructed. */ BOOST_FIXTURE_TEST_CASE(window_test_construction, window_block_fixture) { for (size_t chan = 0; chan < NUM_CHANS; chan++) { // Check that the number of coefficients is expected BOOST_CHECK_EQUAL(reg_iface->coeffs.at(chan).size(), MAX_LENS.at(chan)); // Check that all coefficients are the maximum positive int16_t // value (rectangular window) for (size_t i = 0; i < reg_iface->coeffs.at(chan).size(); i++) { BOOST_CHECK_EQUAL( reg_iface->coeffs.at(chan).at(i), std::numeric_limits::max()); } // 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.at(chan), MAX_LENS.at(chan) - 1); } } /* * This test case exercises the get_max_num_coefficients() API. */ BOOST_FIXTURE_TEST_CASE(window_test_max_num_coeffs, window_block_fixture) { for (size_t chan = 0; chan < NUM_CHANS; chan++) { BOOST_CHECK_EQUAL(test_window->get_max_num_coefficients(chan), MAX_LENS.at(chan)); } } /* * 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(window_test_set_get_coefficients, window_block_fixture) { // Reset state of mock window register interface reg_iface->reset(); for (size_t chan = 0; chan < NUM_CHANS; chan++) { // Generate some dummy data that is half the maximum length allowed by // the channel const size_t num_coeffs_chan = test_window->get_max_num_coefficients(chan) / 2; std::vector coeffs(num_coeffs_chan, chan); test_window->set_coefficients(coeffs, chan); // Check that all coefficients were written BOOST_CHECK_EQUAL(reg_iface->coeffs.at(chan).size(), num_coeffs_chan); // Check correctness of coefficients for (size_t i = 0; i < coeffs.size(); i++) { BOOST_CHECK_EQUAL(reg_iface->coeffs.at(chan).at(i), chan); } // 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.at(chan), num_coeffs_chan - 1); // Verify that get_coefficients() returns what we expect std::vector received_coeffs = test_window->get_coefficients(chan); BOOST_CHECK_EQUAL(received_coeffs.size(), num_coeffs_chan); // Check correctness of returned coefficients for (size_t i = 0; i < coeffs.size(); i++) { BOOST_CHECK_EQUAL(received_coeffs.at(i), coeffs.at(i)); } } } /* * This test case exercises the coefficient length checking of * set_coefficients(). */ BOOST_FIXTURE_TEST_CASE(window_test_length_error, window_block_fixture) { for (size_t chan = 0; chan < NUM_CHANS; chan++) { size_t num_coeffs = test_window->get_max_num_coefficients(chan); std::vector coeffs(num_coeffs * 2); BOOST_CHECK_THROW(test_window->set_coefficients(coeffs, chan), uhd::value_error); } } /* * This test case ensures that the window block can be added to * an RFNoC graph. */ BOOST_FIXTURE_TEST_CASE(window_test_graph, window_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_window.get(), edge_port_info); graph.connect(test_window.get(), &mock_sink_term, edge_port_info); UHD_LOG_INFO("TEST", "Committing graph..."); graph.commit(); UHD_LOG_INFO("TEST", "Commit complete."); }