diff options
author | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-06-16 13:06:59 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-06-29 13:41:15 -0500 |
commit | 2dbed2d90ebc763847a91f35aa7dc26a9ae72835 (patch) | |
tree | f87b929e05fea013ae9ee5b512aa5955df7b278d /host | |
parent | 619f9d637224627d5359e7d63e88a70660164a86 (diff) | |
download | uhd-2dbed2d90ebc763847a91f35aa7dc26a9ae72835.tar.gz uhd-2dbed2d90ebc763847a91f35aa7dc26a9ae72835.tar.bz2 uhd-2dbed2d90ebc763847a91f35aa7dc26a9ae72835.zip |
rfnoc: Add unit test for Window RFNoC block
Diffstat (limited to 'host')
-rw-r--r-- | host/tests/CMakeLists.txt | 4 | ||||
-rw-r--r-- | host/tests/rfnoc_block_tests/window_block_test.cpp | 248 |
2 files changed, 252 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 6626b9c3a..2c441083f 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -314,6 +314,10 @@ UHD_ADD_RFNOC_BLOCK_TEST( TARGET vector_iir_block_test.cpp ) +UHD_ADD_RFNOC_BLOCK_TEST( + TARGET window_block_test.cpp +) + UHD_ADD_NONAPI_TEST( TARGET "transport_test.cpp" EXTRA_SOURCES diff --git a/host/tests/rfnoc_block_tests/window_block_test.cpp b/host/tests/rfnoc_block_tests/window_block_test.cpp new file mode 100644 index 000000000..65e493c4b --- /dev/null +++ b/host/tests/rfnoc_block_tests/window_block_test.cpp @@ -0,0 +1,248 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../rfnoc_graph_mock_nodes.hpp" +#include <uhd/rfnoc/actions.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/mock_block.hpp> +#include <uhd/rfnoc/window_block_control.hpp> +#include <uhdlib/rfnoc/graph.hpp> +#include <uhdlib/rfnoc/node_accessor.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +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<size_t> 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<int16_t>(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<int16_t>(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<int32_t>(_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<size_t> last_coeff_write_pos; + std::vector<std::vector<int16_t>> coeffs; + +private: + const size_t _num_chans; + const std::vector<size_t> _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<size_t> MAX_LENS{3000, 2500, 15, 666}; + +struct window_block_fixture +{ + window_block_fixture() + : reg_iface(std::make_shared<window_mock_reg_iface_t>(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<window_block_control>()) + { + node_accessor.init_props(test_window.get()); + } + + std::shared_ptr<window_mock_reg_iface_t> reg_iface; + mock_block_container block_container; + std::shared_ptr<window_block_control> 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<int16_t>::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<int16_t> 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<int16_t> 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<int16_t> 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<std::string>( + "type", "sc16", {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property<std::string>( + "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."); +} |