aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorAaron Rossetto <aaron.rossetto@ni.com>2020-06-16 13:06:59 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-06-29 13:41:15 -0500
commit2dbed2d90ebc763847a91f35aa7dc26a9ae72835 (patch)
treef87b929e05fea013ae9ee5b512aa5955df7b278d /host
parent619f9d637224627d5359e7d63e88a70660164a86 (diff)
downloaduhd-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.txt4
-rw-r--r--host/tests/rfnoc_block_tests/window_block_test.cpp248
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.");
+}