aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests
diff options
context:
space:
mode:
authorAaron Rossetto <aaron.rossetto@ni.com>2020-03-13 14:08:43 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-04-07 14:20:14 -0500
commit747eebe728b66c51b000ee471d0ca0bb8922c795 (patch)
tree93c2c75f8fc4d8a4b6445fff28f0156edf626869 /host/tests
parent11d70898d15fe9c42e191c166581d83c62354ca1 (diff)
downloaduhd-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.txt6
-rw-r--r--host/tests/fir_filter_block_test.cpp241
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.");
+}