diff options
author | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-03-13 14:08:43 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-04-07 14:20:14 -0500 |
commit | 747eebe728b66c51b000ee471d0ca0bb8922c795 (patch) | |
tree | 93c2c75f8fc4d8a4b6445fff28f0156edf626869 /host/tests | |
parent | 11d70898d15fe9c42e191c166581d83c62354ca1 (diff) | |
download | uhd-747eebe728b66c51b000ee471d0ca0bb8922c795.tar.gz uhd-747eebe728b66c51b000ee471d0ca0bb8922c795.tar.bz2 uhd-747eebe728b66c51b000ee471d0ca0bb8922c795.zip |
tests: Add FIR filter block controller unit test
Diffstat (limited to 'host/tests')
-rw-r--r-- | host/tests/CMakeLists.txt | 6 | ||||
-rw-r--r-- | host/tests/fir_filter_block_test.cpp | 241 |
2 files changed, 247 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 33bcdb734..f9f94e023 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -252,6 +252,12 @@ UHD_ADD_NONAPI_TEST( ) UHD_ADD_NONAPI_TEST( + TARGET fir_filter_block_test.cpp + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp +) + +UHD_ADD_NONAPI_TEST( TARGET "transport_test.cpp" EXTRA_SOURCES ${CMAKE_SOURCE_DIR}/lib/transport/inline_io_service.cpp diff --git a/host/tests/fir_filter_block_test.cpp b/host/tests/fir_filter_block_test.cpp new file mode 100644 index 000000000..3bfce4b2d --- /dev/null +++ b/host/tests/fir_filter_block_test.cpp @@ -0,0 +1,241 @@ +// +// 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/fir_filter_block_control.hpp> +#include <uhd/rfnoc/mock_block.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 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<int16_t>(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<int16_t>(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<int32_t>(_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<int16_t> 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<fir_filter_mock_reg_iface_t>(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<fir_filter_block_control>()) + { + node_accessor.init_props(test_fir_filter.get()); + } + + std::shared_ptr<fir_filter_mock_reg_iface_t> reg_iface; + mock_block_container block_container; + std::shared_ptr<fir_filter_block_control> 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<int16_t> 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<int16_t> 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<int16_t> 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<int16_t> 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<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_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."); +} |