aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests/rfnoc_propprop_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'host/tests/rfnoc_propprop_test.cpp')
-rw-r--r--host/tests/rfnoc_propprop_test.cpp366
1 files changed, 366 insertions, 0 deletions
diff --git a/host/tests/rfnoc_propprop_test.cpp b/host/tests/rfnoc_propprop_test.cpp
new file mode 100644
index 000000000..1b9b94b05
--- /dev/null
+++ b/host/tests/rfnoc_propprop_test.cpp
@@ -0,0 +1,366 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "rfnoc_graph_mock_nodes.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhdlib/rfnoc/graph.hpp>
+#include <uhdlib/rfnoc/node_accessor.hpp>
+#include <uhdlib/rfnoc/prop_accessor.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+/*! Mock invalid node
+ *
+ * This block has an output prop that is always twice the input prop. This block
+ * is invalid because the defaults don't work.
+ */
+class mock_invalid_node1_t : public node_t
+{
+public:
+ mock_invalid_node1_t()
+ {
+ register_property(&_in);
+ register_property(&_out);
+
+ add_property_resolver({&_in}, {&_out}, [this]() { _out = _in * 2; });
+ add_property_resolver({&_out}, {&_in}, [this]() { _in = _out / 2; });
+ }
+
+ std::string get_unique_id() const
+ {
+ return "MOCK_INVALID_NODE1";
+ }
+
+ size_t get_num_input_ports() const
+ {
+ return 1;
+ }
+
+ size_t get_num_output_ports() const
+ {
+ return 1;
+ }
+
+private:
+ property_t<double> _in{"in", 1.0, {res_source_info::INPUT_EDGE}};
+ // This has an invalid default value: It would have to be 2.0 for the block
+ // to be able to initialize
+ property_t<double> _out{"out", 1.0 /* SIC */, {res_source_info::OUTPUT_EDGE}};
+};
+
+/*! Mock invalid node
+ *
+ * This block will write conflicting values to the output at resolution time.
+ */
+class mock_invalid_node2_t : public node_t
+{
+public:
+ mock_invalid_node2_t()
+ {
+ register_property(&_in);
+ register_property(&_out);
+
+ add_property_resolver({&_in}, {&_out}, [this]() {
+ UHD_LOG_INFO("MOCK2", "Calling resolver 1/2 for _out");
+ _out = _in * 2.0;
+ });
+ // If this->factor != 2.0, then this resolver will contradict the
+ // previous one:
+ add_property_resolver({&_in}, {&_out}, [this]() {
+ UHD_LOG_INFO("MOCK2", "Calling resolver 2/2 for _out");
+ _out = _in * this->factor;
+ });
+ add_property_resolver({&_out}, {&_in}, [this]() {
+ UHD_LOG_INFO("MOCK2", "Calling resolver for _in");
+ _in = _out / 2.0;
+ });
+ }
+
+ void mark_in_dirty()
+ {
+ prop_accessor_t prop_accessor{};
+ auto access_lock = prop_accessor.get_scoped_prop_access(_in, property_base_t::RW);
+ double old_val = _in.get();
+ _in.set(old_val * 2.0);
+ _in.set(old_val);
+ }
+
+ size_t get_num_input_ports() const
+ {
+ return 1;
+ }
+
+ size_t get_num_output_ports() const
+ {
+ return 1;
+ }
+
+ std::string get_unique_id() const
+ {
+ return "MOCK_INVALID_NODE2";
+ }
+
+ // When we change this, we break resolver #2.
+ double factor = 2.0;
+
+private:
+ property_t<double> _in{"in", 1.0, {res_source_info::INPUT_EDGE}};
+ property_t<double> _out{"out", 2.0, {res_source_info::OUTPUT_EDGE}};
+};
+
+// Do some sanity checks on the mock just so we don't get surprised later
+BOOST_AUTO_TEST_CASE(test_mock)
+{
+ BOOST_CHECK_EQUAL(1, mock_ddc_node_t::coerce_decim(1));
+ BOOST_CHECK_EQUAL(2, mock_ddc_node_t::coerce_decim(2));
+ BOOST_CHECK_EQUAL(512, mock_ddc_node_t::coerce_decim(1212));
+ BOOST_CHECK_EQUAL(512, mock_ddc_node_t::coerce_decim(513));
+ BOOST_CHECK_EQUAL(2, mock_ddc_node_t::coerce_decim(3));
+
+ mock_ddc_node_t mock{};
+ BOOST_CHECK(mock._decim.is_dirty());
+ BOOST_CHECK(mock._samp_rate_out.is_dirty());
+ BOOST_CHECK(mock._samp_rate_in.is_dirty());
+ BOOST_CHECK_EQUAL(mock._decim.get(), DEFAULT_DECIM);
+ BOOST_CHECK_EQUAL(mock._samp_rate_out.get(), DEFAULT_RATE);
+ BOOST_CHECK_EQUAL(mock._samp_rate_in.get(), DEFAULT_RATE);
+}
+
+BOOST_AUTO_TEST_CASE(test_init_and_resolve)
+{
+ mock_ddc_node_t mock_ddc{};
+ mock_radio_node_t mock_radio(0);
+ node_accessor_t node_accessor{};
+
+ node_accessor.init_props(&mock_ddc);
+ node_accessor.init_props(&mock_radio);
+
+ BOOST_CHECK(!mock_ddc._decim.is_dirty());
+ BOOST_CHECK(!mock_ddc._samp_rate_out.is_dirty());
+ BOOST_CHECK(!mock_ddc._samp_rate_in.is_dirty());
+ BOOST_CHECK_EQUAL(mock_ddc._decim.get(), DEFAULT_DECIM);
+ BOOST_CHECK_EQUAL(mock_ddc._samp_rate_out.get(), DEFAULT_RATE);
+ BOOST_CHECK_EQUAL(mock_ddc._samp_rate_in.get(), DEFAULT_RATE);
+
+ BOOST_CHECK_EQUAL(mock_ddc.get_property<int>("decim", 0), DEFAULT_DECIM);
+
+ mock_ddc.set_property("decim", 2, 0);
+ BOOST_CHECK(!mock_ddc._decim.is_dirty());
+ node_accessor.resolve_props(&mock_ddc);
+
+ BOOST_CHECK_EQUAL(mock_ddc.get_property<int>("decim", 0), 2);
+ BOOST_CHECK_EQUAL(mock_ddc._samp_rate_in.get(), DEFAULT_RATE);
+ BOOST_CHECK_EQUAL(mock_ddc._samp_rate_in.get() / 2, mock_ddc._samp_rate_out.get());
+}
+
+BOOST_AUTO_TEST_CASE(test_failures)
+{
+ node_accessor_t node_accessor{};
+
+ UHD_LOG_INFO("TEST", "We expect an ERROR log message next:");
+ mock_invalid_node1_t mock1{};
+ // BOOST_REQUIRE_THROW(
+ // node_accessor.init_props(&mock1),
+ // uhd::runtime_error);
+
+ mock_invalid_node2_t mock2{};
+ node_accessor.init_props(&mock2);
+ mock2.factor = 1.0;
+ mock2.mark_in_dirty();
+ BOOST_REQUIRE_THROW(node_accessor.resolve_props(&mock2), uhd::resolve_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_graph_resolve_ddc_radio)
+{
+ node_accessor_t node_accessor{};
+ uhd::rfnoc::detail::graph_t graph{};
+ // Define some mock nodes:
+ mock_ddc_node_t mock_ddc{};
+ // Source radio
+ mock_radio_node_t mock_rx_radio(0);
+ // Sink radio
+ mock_radio_node_t mock_tx_radio(1);
+
+ // These init calls would normally be done by the framework
+ node_accessor.init_props(&mock_ddc);
+ node_accessor.init_props(&mock_tx_radio);
+ node_accessor.init_props(&mock_rx_radio);
+
+ // In this simple graph, all connections are identical from an edge info
+ // perspective, so we're lazy and share an edge_info object:
+ uhd::rfnoc::detail::graph_t::graph_edge_t edge_info;
+ edge_info.src_port = 0;
+ edge_info.dst_port = 0;
+ edge_info.property_propagation_active = true;
+ edge_info.edge = uhd::rfnoc::detail::graph_t::graph_edge_t::DYNAMIC;
+
+ // Now create the graph and commit:
+ graph.connect(&mock_rx_radio, &mock_ddc, edge_info);
+ graph.connect(&mock_ddc, &mock_tx_radio, edge_info);
+ graph.initialize();
+ BOOST_CHECK_EQUAL(mock_ddc._decim.get(), 1);
+
+ mock_tx_radio.set_property<double>("master_clock_rate", 100e6, 0);
+ BOOST_CHECK_EQUAL(mock_ddc._decim.get(), 2);
+
+ UHD_LOG_INFO("TEST", "Now tempting DDC to invalid prop value...");
+ mock_ddc.set_property<int>("decim", 42, 0);
+ // It will bounce back:
+ BOOST_CHECK_EQUAL(mock_ddc._decim.get(), 2);
+}
+
+
+BOOST_AUTO_TEST_CASE(test_graph_catch_invalid_graph)
+{
+ node_accessor_t node_accessor{};
+ uhd::rfnoc::detail::graph_t graph{};
+ // Define some mock nodes:
+ // Source radio
+ mock_radio_node_t mock_rx_radio(0);
+ // Sink radio
+ mock_radio_node_t mock_tx_radio(1);
+
+ // These init calls would normally be done by the framework
+ node_accessor.init_props(&mock_tx_radio);
+ node_accessor.init_props(&mock_rx_radio);
+ mock_tx_radio.set_property<double>("master_clock_rate", 100e6, 0);
+
+ // In this simple graph, all connections are identical from an edge info
+ // perspective, so we're lazy and share an edge_info object:
+ uhd::rfnoc::detail::graph_t::graph_edge_t edge_info;
+ edge_info.src_port = 0;
+ edge_info.dst_port = 0;
+ edge_info.property_propagation_active = true;
+ edge_info.edge = uhd::rfnoc::detail::graph_t::graph_edge_t::DYNAMIC;
+
+ // Now create the graph and commit:
+ graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info);
+ BOOST_REQUIRE_THROW(graph.initialize(), uhd::resolve_error);
+ UHD_LOG_INFO("TEST", "^^^ Expected an error message.");
+}
+
+BOOST_AUTO_TEST_CASE(test_graph_ro_prop)
+{
+ node_accessor_t node_accessor{};
+ uhd::rfnoc::detail::graph_t graph{};
+ // Define some mock nodes:
+ // Source radio
+ mock_radio_node_t mock_rx_radio(0);
+ // Sink radio
+ mock_radio_node_t mock_tx_radio(1);
+
+ // These init calls would normally be done by the framework
+ node_accessor.init_props(&mock_tx_radio);
+ node_accessor.init_props(&mock_rx_radio);
+ BOOST_CHECK_EQUAL(mock_tx_radio.rssi_resolver_count, 1);
+ BOOST_CHECK_EQUAL(mock_rx_radio.rssi_resolver_count, 1);
+
+ // In this simple graph, all connections are identical from an edge info
+ // perspective, so we're lazy and share an edge_info object:
+ uhd::rfnoc::detail::graph_t::graph_edge_t edge_info;
+ edge_info.src_port = 0;
+ edge_info.dst_port = 0;
+ edge_info.property_propagation_active = true;
+ edge_info.edge = uhd::rfnoc::detail::graph_t::graph_edge_t::DYNAMIC;
+
+ // Now create the graph and commit:
+ graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info);
+ graph.initialize();
+
+ const size_t rx_rssi_resolver_count = mock_rx_radio.rssi_resolver_count;
+ UHD_LOG_DEBUG("TEST", "RX RSSI: " << mock_rx_radio.get_property<double>("rssi"));
+ // The next value must match the value in graph.cpp
+ constexpr size_t MAX_NUM_ITERATIONS = 2;
+ BOOST_CHECK_EQUAL(
+ rx_rssi_resolver_count + MAX_NUM_ITERATIONS, mock_rx_radio.rssi_resolver_count);
+}
+
+BOOST_AUTO_TEST_CASE(test_graph_double_connect)
+{
+ node_accessor_t node_accessor{};
+ using uhd::rfnoc::detail::graph_t;
+ graph_t graph{};
+ using edge_t = graph_t::graph_edge_t;
+ // Define some mock nodes:
+ mock_radio_node_t mock_rx_radio0(0);
+ mock_radio_node_t mock_rx_radio1(1);
+ mock_radio_node_t mock_tx_radio0(2);
+ mock_radio_node_t mock_tx_radio1(3);
+
+ // These init calls would normally be done by the framework
+ node_accessor.init_props(&mock_tx_radio0);
+ node_accessor.init_props(&mock_tx_radio1);
+ node_accessor.init_props(&mock_rx_radio0);
+ node_accessor.init_props(&mock_rx_radio1);
+
+ graph.connect(&mock_rx_radio0, &mock_tx_radio0, {0, 0, edge_t::DYNAMIC, true});
+ // Twice is also OK:
+ UHD_LOG_INFO("TEST", "Testing double-connect with same edges");
+ graph.connect(&mock_rx_radio0, &mock_tx_radio0, {0, 0, edge_t::DYNAMIC, true});
+ UHD_LOG_INFO("TEST", "Testing double-connect with same edges, different attributes");
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio0, &mock_tx_radio0, {0, 0, edge_t::DYNAMIC, false}),
+ uhd::rfnoc_error);
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio0, &mock_tx_radio0, {0, 0, edge_t::STATIC, false}),
+ uhd::rfnoc_error);
+ UHD_LOG_INFO("TEST", "Testing double-connect output port, new dest node");
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio0, &mock_tx_radio1, {0, 0, edge_t::DYNAMIC, true}),
+ uhd::rfnoc_error);
+ UHD_LOG_INFO("TEST", "Testing double-connect input port, new source node");
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio1, &mock_tx_radio0, {0, 0, edge_t::DYNAMIC, true}),
+ uhd::rfnoc_error);
+ // Add another valid connection
+ graph.connect(&mock_rx_radio1, &mock_tx_radio1, {0, 0, edge_t::DYNAMIC, true});
+ UHD_LOG_INFO("TEST", "Testing double-connect output port, existing dest node");
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio0, &mock_tx_radio1, {0, 0, edge_t::DYNAMIC, true}),
+ uhd::rfnoc_error);
+ UHD_LOG_INFO("TEST", "Testing double-connect input port, existing source node");
+ BOOST_REQUIRE_THROW(
+ graph.connect(&mock_rx_radio1, &mock_tx_radio0, {0, 0, edge_t::DYNAMIC, true}),
+ uhd::rfnoc_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_graph_crisscross_fifo)
+{
+ node_accessor_t node_accessor{};
+ uhd::rfnoc::detail::graph_t graph{};
+ // Define some mock nodes:
+ // Source radios
+ mock_radio_node_t mock_rx_radio0(0); // -> 2
+ mock_radio_node_t mock_rx_radio1(1); // -> 3
+ // Sink radios
+ mock_radio_node_t mock_tx_radio0(2);
+ mock_radio_node_t mock_tx_radio1(3);
+ // FIFO
+ mock_fifo_t mock_fifo(2);
+
+ // These init calls would normally be done by the framework
+ node_accessor.init_props(&mock_rx_radio0);
+ node_accessor.init_props(&mock_rx_radio1);
+ node_accessor.init_props(&mock_tx_radio0);
+ node_accessor.init_props(&mock_tx_radio1);
+ node_accessor.init_props(&mock_fifo);
+
+ mock_rx_radio0.set_property<double>("master_clock_rate", 200e6, 0);
+ mock_rx_radio1.set_property<double>("master_clock_rate", 100e6, 0);
+ mock_tx_radio0.set_property<double>("master_clock_rate", 100e6, 0);
+ mock_tx_radio1.set_property<double>("master_clock_rate", 200e6, 0);
+
+ using graph_edge_t = uhd::rfnoc::detail::graph_t::graph_edge_t;
+
+ // Now create the graph and commit:
+ graph.connect(&mock_rx_radio0, &mock_fifo, {0, 0, graph_edge_t::DYNAMIC, true});
+ graph.connect(&mock_rx_radio1, &mock_fifo, {0, 1, graph_edge_t::DYNAMIC, true});
+ // Notice how we swap the TX radios
+ graph.connect(&mock_fifo, &mock_tx_radio0, {1, 0, graph_edge_t::DYNAMIC, true});
+ graph.connect(&mock_fifo, &mock_tx_radio1, {0, 0, graph_edge_t::DYNAMIC, true});
+ UHD_LOG_INFO("TEST", "Now testing criss-cross prop resolution");
+ graph.initialize();
+}