// // Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "rfnoc_graph_mock_nodes.hpp" #include #include #include #include #include #include #include using uhd::rfnoc::detail::graph_t; using namespace uhd::rfnoc; namespace uhd { namespace rfnoc { namespace detail { /*! Helper class to access internals of detail::graph * * This is basically a cheat code to get around the 'private' part of graph_t. */ class graph_accessor_t { public: using vertex_descriptor = graph_t::rfnoc_graph_t::vertex_descriptor; graph_accessor_t(graph_t* graph_ptr) : _graph_ptr(graph_ptr) { /* nop */ } graph_t::rfnoc_graph_t& get_graph() { return _graph_ptr->_graph; } template graph_t::node_ref_t get_node_ref_from_iterator(VertexIterator it) { return boost::get(graph_t::vertex_property_t(), get_graph(), *it); } auto find_neighbour(vertex_descriptor origin, res_source_info port_info) { return _graph_ptr->_find_neighbour(origin, port_info); } auto find_dirty_nodes() { return _graph_ptr->_find_dirty_nodes(); } auto get_topo_sorted_nodes() { return _graph_ptr->_vertices_to_nodes(_graph_ptr->_get_topo_sorted_nodes()); } private: graph_t* _graph_ptr; }; }}}; // namespace uhd::rfnoc::detail BOOST_AUTO_TEST_CASE(test_graph) { graph_t graph{}; uhd::rfnoc::detail::graph_accessor_t graph_accessor(&graph); node_accessor_t node_accessor{}; auto& bgl_graph = graph_accessor.get_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_rx_radio); node_accessor.init_props(&mock_tx_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: graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info); // A whole bunch of low-level checks first: BOOST_CHECK_EQUAL(boost::num_vertices(bgl_graph), 2); auto vertex_iterators = boost::vertices(bgl_graph); auto vertex_iterator = vertex_iterators.first; auto rx_descriptor = *vertex_iterator; graph_t::node_ref_t node_ref = graph_accessor.get_node_ref_from_iterator(vertex_iterator++); BOOST_CHECK_EQUAL(node_ref->get_unique_id(), mock_rx_radio.get_unique_id()); auto tx_descriptor = *vertex_iterator; node_ref = graph_accessor.get_node_ref_from_iterator(vertex_iterator++); BOOST_CHECK_EQUAL(node_ref->get_unique_id(), mock_tx_radio.get_unique_id()); BOOST_CHECK(vertex_iterator == vertex_iterators.second); auto rx_neighbour_info = graph_accessor.find_neighbour(rx_descriptor, {res_source_info::OUTPUT_EDGE, 0}); BOOST_REQUIRE(rx_neighbour_info.first); BOOST_CHECK_EQUAL( rx_neighbour_info.first->get_unique_id(), mock_tx_radio.get_unique_id()); BOOST_CHECK(std::tie(rx_neighbour_info.second.src_port, rx_neighbour_info.second.dst_port, rx_neighbour_info.second.property_propagation_active) == std::tie(edge_info.src_port, edge_info.dst_port, edge_info.property_propagation_active)); auto tx_neighbour_info = graph_accessor.find_neighbour(tx_descriptor, {res_source_info::INPUT_EDGE, 0}); BOOST_REQUIRE(tx_neighbour_info.first); BOOST_CHECK_EQUAL( tx_neighbour_info.first->get_unique_id(), mock_rx_radio.get_unique_id()); BOOST_CHECK(std::tie(tx_neighbour_info.second.src_port, tx_neighbour_info.second.dst_port, tx_neighbour_info.second.property_propagation_active) == std::tie(edge_info.src_port, edge_info.dst_port, edge_info.property_propagation_active)); auto rx_upstream_neighbour_info = graph_accessor.find_neighbour(rx_descriptor, {res_source_info::INPUT_EDGE, 0}); BOOST_CHECK(rx_upstream_neighbour_info.first == nullptr); auto tx_downstream_neighbour_info = graph_accessor.find_neighbour(tx_descriptor, {res_source_info::OUTPUT_EDGE, 0}); BOOST_CHECK(tx_downstream_neighbour_info.first == nullptr); auto rx_wrongport_neighbour_info = graph_accessor.find_neighbour(rx_descriptor, {res_source_info::OUTPUT_EDGE, 1}); BOOST_CHECK(rx_wrongport_neighbour_info.first == nullptr); auto tx_wrongport_neighbour_info = graph_accessor.find_neighbour(tx_descriptor, {res_source_info::INPUT_EDGE, 1}); BOOST_CHECK(tx_wrongport_neighbour_info.first == nullptr); // Check there are no dirty nodes (init_props() will clean them all) BOOST_CHECK_EQUAL(graph_accessor.find_dirty_nodes().empty(), true); auto topo_sorted_nodes = graph_accessor.get_topo_sorted_nodes(); BOOST_CHECK_EQUAL(topo_sorted_nodes.size(), 2); BOOST_CHECK_EQUAL( topo_sorted_nodes.at(0)->get_unique_id(), mock_rx_radio.get_unique_id()); // Now initialize the graph (will force a call to resolve_all_properties()) graph.commit(); // This will be ignored graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info); BOOST_CHECK_EQUAL(boost::num_vertices(bgl_graph), 2); BOOST_REQUIRE_EQUAL(graph.enumerate_edges().size(), 1); auto edge0_info = graph.enumerate_edges().at(0); BOOST_CHECK_EQUAL(edge0_info.src_blockid, "MOCK_RADIO0"); BOOST_CHECK_EQUAL(edge0_info.src_port, 0); BOOST_CHECK_EQUAL(edge0_info.dst_blockid, "MOCK_RADIO1"); BOOST_CHECK_EQUAL(edge0_info.dst_port, 0); // Now attempt illegal connections (they must all fail) edge_info.src_port = 1; edge_info.dst_port = 0; BOOST_REQUIRE_THROW( graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info), uhd::rfnoc_error); edge_info.src_port = 0; edge_info.dst_port = 1; BOOST_REQUIRE_THROW( graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info), uhd::rfnoc_error); edge_info.src_port = 0; edge_info.dst_port = 0; edge_info.property_propagation_active = false; BOOST_REQUIRE_THROW( graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info), uhd::rfnoc_error); BOOST_CHECK_EQUAL(graph.enumerate_edges().size(), 1); } BOOST_AUTO_TEST_CASE(test_graph_unresolvable) { graph_t graph{}; node_accessor_t node_accessor{}; // 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_rx_radio); node_accessor.init_props(&mock_tx_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( 0, 0, graph_t::graph_edge_t::DYNAMIC, true); // Now create the graph and commit: graph.connect(&mock_rx_radio, &mock_tx_radio, edge_info); graph.commit(); // Now set a property that will cause the graph to fail to resolve: BOOST_REQUIRE_THROW(mock_tx_radio.set_property("master_clock_rate", 100e6, 0), uhd::resolve_error); // Now we add a back-edge edge_info.src_port = 0; edge_info.dst_port = 0; edge_info.property_propagation_active = false; graph.connect(&mock_tx_radio, &mock_rx_radio, edge_info); UHD_LOG_INFO("TEST", "Testing back edge error path"); mock_tx_radio.disable_samp_out_resolver = true; // The set_property would be valid if we hadn't futzed with the back-edge BOOST_REQUIRE_THROW(mock_tx_radio.set_property("master_clock_rate", 200e6, 0), uhd::resolve_error); UHD_LOG_INFO("TEST", "^^^ Expected ERROR here."); }