aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib')
-rw-r--r--host/lib/include/uhdlib/rfnoc/graph.hpp271
-rw-r--r--host/lib/include/uhdlib/rfnoc/node_accessor.hpp5
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/graph.cpp460
-rw-r--r--host/lib/rfnoc/node.cpp312
5 files changed, 1038 insertions, 11 deletions
diff --git a/host/lib/include/uhdlib/rfnoc/graph.hpp b/host/lib/include/uhdlib/rfnoc/graph.hpp
new file mode 100644
index 000000000..f9fb7ac41
--- /dev/null
+++ b/host/lib/include/uhdlib/rfnoc/graph.hpp
@@ -0,0 +1,271 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_GRAPH_HPP
+#define INCLUDED_LIBUHD_GRAPH_HPP
+
+#include <uhd/rfnoc/node.hpp>
+#include <boost/graph/adjacency_list.hpp>
+#include <tuple>
+#include <memory>
+
+namespace uhd { namespace rfnoc {
+
+/*! A container that holds information about a graph edge
+ */
+struct graph_edge_t
+{
+ enum edge_t {
+ STATIC, ///< A static connection between two blocks in the FPGA
+ DYNAMIC, ///< A user (dynamic) connection between two blocks in the FPGA
+ RX_STREAM, ///< A connection from an FPGA block to a software RX streamer
+ TX_STREAM ///< A connection from a software TX streamer and an FPGA block
+ };
+
+ graph_edge_t() = default;
+
+ graph_edge_t(const size_t src_port_,
+ const size_t dst_port_,
+ const edge_t edge_,
+ const bool ppa)
+ : src_port(src_port_)
+ , dst_port(dst_port_)
+ , edge(edge_)
+ , property_propagation_active(ppa)
+ {
+ }
+
+ //! The block ID of the source block for this edge
+ std::string src_blockid;
+ //! The port number of the source block for this edge
+ size_t src_port = 0;
+ //! The block ID of the destination block for this edge
+ std::string dst_blockid;
+ //! The port number of the destination block for this edge
+ size_t dst_port = 0;
+ //! The type of edge
+ edge_t edge = DYNAMIC;
+ //! When true, the framework will use this edge for property propagation
+ bool property_propagation_active = true;
+
+ bool operator==(const graph_edge_t& rhs) const
+ {
+ return std::tie(src_blockid,
+ src_port,
+ dst_blockid,
+ dst_port,
+ edge,
+ property_propagation_active)
+ == std::tie(rhs.src_blockid,
+ rhs.src_port,
+ rhs.dst_blockid,
+ rhs.dst_port,
+ rhs.edge,
+ rhs.property_propagation_active);
+ }
+};
+
+
+namespace detail {
+
+//! Container for the logical graph within an uhd::rfnoc_graph
+class graph_t
+{
+public:
+ using uptr = std::unique_ptr<graph_t>;
+ //! A shorthand for a pointer to a node
+ using node_ref_t = uhd::rfnoc::node_t*;
+
+ using graph_edge_t = uhd::rfnoc::graph_edge_t;
+
+ /*! Add a connection to the graph
+ *
+ * After this function returns, the nodes will be considered connected
+ * along the ports specified in \p edge_info.
+ *
+ * \param src_node A reference to the source node
+ * \param dst_node A reference to the destination node
+ * \param edge_info Information about the type of edge
+ */
+ void connect(node_ref_t src_node, node_ref_t dst_node, graph_edge_t edge_info);
+
+ //void disconnect(node_ref_t src_node,
+ //node_ref_t dst_node,
+ //const size_t src_port,
+ //const size_t dst_port);
+ //
+
+ /*! Run initial checks for graph
+ *
+ * This method can be called anytime, but it's intended to be called when
+ * the graph has been committed. It will run checks on the graph and run a
+ * property propagation.
+ *
+ * \throws uhd::resolve_error if the properties fail to resolve.
+ */
+ void initialize();
+
+
+private:
+ friend class graph_accessor_t;
+
+ /**************************************************************************
+ * Graph-related types
+ *************************************************************************/
+ // Naming conventions:
+ // - 'vertex' and 'node' are generally ambiguous in a graph context, but
+ // we'll use vertex for BGL related items, and node for RFNoC nodes
+ // - We may use CamelCase occasionally if it fits the BGL examples and/or
+ // reference designs, in case someone needs to learn BGL to understand
+ // this code
+
+ struct vertex_property_t
+ {
+ enum { num = 4000 };
+ typedef boost::vertex_property_tag kind;
+ };
+ using RfnocVertexProperty = boost::property<vertex_property_t, node_ref_t>;
+
+ struct edge_property_t
+ {
+ enum { num = 4001 };
+ typedef boost::edge_property_tag kind;
+ };
+ using RfnocEdgeProperty = boost::property<edge_property_t, graph_edge_t>;
+
+ /*! The type of the BGL graph we're using
+ *
+ * - It is bidirectional because we need to access both in_edges and
+ * out_edges
+ * - All container types are according to the BGL manual recommendations for
+ * this kind of graph
+ */
+ using rfnoc_graph_t = boost::adjacency_list<boost::vecS,
+ boost::vecS,
+ boost::bidirectionalS,
+ RfnocVertexProperty,
+ RfnocEdgeProperty>;
+
+ using vertex_list_t = std::list<rfnoc_graph_t::vertex_descriptor>;
+
+ template <bool forward_edges_only = true>
+ struct ForwardBackwardEdgePredicate
+ {
+ ForwardBackwardEdgePredicate() {} // Default ctor is required
+ ForwardBackwardEdgePredicate(rfnoc_graph_t& graph) : _graph(&graph) {}
+
+ template <typename Edge>
+ bool operator()(const Edge& e) const
+ {
+ graph_edge_t edge_info = boost::get(edge_property_t(), *_graph, e);
+ return edge_info.property_propagation_active == forward_edges_only;
+ }
+
+ private:
+ // Don't make any attribute const, because default assignment operator
+ // is also required
+ rfnoc_graph_t* _graph;
+ };
+
+ using ForwardEdgePredicate = ForwardBackwardEdgePredicate<true>;
+ using BackEdgePredicate = ForwardBackwardEdgePredicate<false>;
+
+ //! Vertex predicate, only selects nodes with dirty props
+ struct DirtyNodePredicate;
+
+ //! Vertex predicate, returns specific existing nodes
+ struct FindNodePredicate;
+
+ /**************************************************************************
+ * Other private types
+ *************************************************************************/
+ using node_map_t = std::map<node_ref_t, rfnoc_graph_t::vertex_descriptor>;
+
+ /**************************************************************************
+ * The Algorithm
+ *************************************************************************/
+ /*! Implementation of the property propagation algorithm
+ */
+ void resolve_all_properties();
+
+ /**************************************************************************
+ * Private graph helpers
+ *************************************************************************/
+ template <typename VertexContainerType>
+ std::vector<node_ref_t> _vertices_to_nodes(VertexContainerType&& vertex_container)
+ {
+ std::vector<node_ref_t> result{};
+ result.reserve(vertex_container.size());
+ for (const auto& vertex_descriptor : vertex_container) {
+ result.push_back(boost::get(vertex_property_t(), _graph, vertex_descriptor));
+ }
+ return result;
+ }
+
+ /*! Returns a list of all nodes that have dirty properties.
+ */
+ vertex_list_t _find_dirty_nodes();
+
+ /*! Returns nodes in topologically sorted order
+ *
+ *
+ * \throws uhd::runtime_error if the graph was not sortable
+ */
+ vertex_list_t _get_topo_sorted_nodes();
+
+ /*! Add a node, but only if it's not already in the graph.
+ *
+ * If it's already there, do nothing.
+ */
+ void _add_node(node_ref_t node);
+
+ /*! Find the neighbouring node for \p origin based on \p port_info
+ *
+ * This function will check port_info to identify the port number and the
+ * direction (input or output) from \p port_info. It will then return a
+ * reference to the node that is attached to the node \p origin if such a
+ * node exists, and the edge info.
+ *
+ * If port_info.type == res_source_info::INPUT_EDGE, then port_info.instance
+ * will equal the return value's dst_port value.
+ *
+ * \returns A valid reference to the neighbouring node, or nullptr if no
+ * such node exists, and the corresponding edge info.
+ */
+ std::pair<node_ref_t, graph_edge_t> _find_neighbour(
+ rfnoc_graph_t::vertex_descriptor origin, res_source_info port_info);
+
+ /*! Forward all edge properties from this node (\p origin) to the
+ * neighbouring ones
+ *
+ */
+ void _forward_edge_props(rfnoc_graph_t::vertex_descriptor origin);
+
+ /*! Check that the edge properties on both sides of the edge are equal
+ *
+ * \returns false if edge properties are not consistent
+ */
+ bool _assert_edge_props_consistent(rfnoc_graph_t::edge_descriptor edge);
+
+ /**************************************************************************
+ * Attributes
+ *************************************************************************/
+ //! Storage for the actual graph
+ rfnoc_graph_t _graph;
+
+ //! Map to do a lookup node_ref_t -> vertex descriptor.
+ //
+ // This is technically redundant, but helps us check quickly and easily if
+ // a node is already in the graph, and to yank out the appropriate node
+ // descriptor without having to traverse the graph. The rfnoc_graph_t is not
+ // efficient for lookups of vertices.
+ node_map_t _node_map;
+};
+
+
+}}} /* namespace uhd::rfnoc::detail */
+
+#endif /* INCLUDED_LIBUHD_GRAPH_HPP */
diff --git a/host/lib/include/uhdlib/rfnoc/node_accessor.hpp b/host/lib/include/uhdlib/rfnoc/node_accessor.hpp
index 26e6a5607..554cc8f4f 100644
--- a/host/lib/include/uhdlib/rfnoc/node_accessor.hpp
+++ b/host/lib/include/uhdlib/rfnoc/node_accessor.hpp
@@ -72,9 +72,10 @@ public:
*
* See node_t::forward_edge_property() for details.
*/
- void forward_edge_property(node_t* dst_node, property_base_t* incoming_prop)
+ void forward_edge_property(
+ node_t* dst_node, const size_t dst_port, property_base_t* incoming_prop)
{
- dst_node->forward_edge_property(incoming_prop);
+ dst_node->forward_edge_property(incoming_prop, dst_port);
}
};
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt
index bc3e7309f..9b14f3456 100644
--- a/host/lib/rfnoc/CMakeLists.txt
+++ b/host/lib/rfnoc/CMakeLists.txt
@@ -20,6 +20,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/graph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/noc_block_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp
diff --git a/host/lib/rfnoc/graph.cpp b/host/lib/rfnoc/graph.cpp
new file mode 100644
index 000000000..f90f70b43
--- /dev/null
+++ b/host/lib/rfnoc/graph.cpp
@@ -0,0 +1,460 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhdlib/rfnoc/graph.hpp>
+#include <uhdlib/rfnoc/node_accessor.hpp>
+#include <boost/graph/topological_sort.hpp>
+#include <boost/graph/filtered_graph.hpp>
+#include <utility>
+
+using namespace uhd::rfnoc;
+using namespace uhd::rfnoc::detail;
+
+namespace {
+
+const std::string LOG_ID = "RFNOC::GRAPH::DETAIL";
+
+/*! Helper function to pretty-print edge info
+ */
+std::string print_edge(
+ graph_t::node_ref_t src, graph_t::node_ref_t dst, graph_t::graph_edge_t edge_info)
+{
+ return src->get_unique_id() + ":" + std::to_string(edge_info.src_port) + " -> "
+ + dst->get_unique_id() + ":" + std::to_string(edge_info.dst_port);
+}
+
+/*! Return a list of dirty properties from a node
+ */
+auto get_dirty_props(graph_t::node_ref_t node_ref)
+{
+ using namespace uhd::rfnoc;
+ node_accessor_t node_accessor{};
+ return node_accessor.filter_props(node_ref, [](property_base_t* prop) {
+ return prop->is_dirty()
+ && prop->get_src_info().type != res_source_info::FRAMEWORK;
+ });
+}
+
+/*! Check that \p new_edge_info does not conflict with \p existing_edge_info
+ *
+ * \throws uhd::rfnoc_error if it does.
+ */
+void assert_edge_new(const graph_t::graph_edge_t& new_edge_info,
+ const graph_t::graph_edge_t& existing_edge_info)
+{
+ if (existing_edge_info == new_edge_info) {
+ UHD_LOG_INFO(LOG_ID,
+ "Ignoring repeated call to connect "
+ << new_edge_info.src_blockid << ":" << new_edge_info.src_port << " -> "
+ << new_edge_info.dst_blockid << ":" << new_edge_info.dst_port);
+ return;
+ } else if (existing_edge_info.src_port == new_edge_info.src_port
+ && existing_edge_info.src_blockid == new_edge_info.src_blockid
+ && existing_edge_info.dst_port == new_edge_info.dst_port
+ && existing_edge_info.dst_blockid == new_edge_info.dst_blockid) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Caught attempt to modify properties of edge "
+ << existing_edge_info.src_blockid << ":" << existing_edge_info.src_port
+ << " -> " << existing_edge_info.dst_blockid << ":"
+ << existing_edge_info.dst_port);
+ throw uhd::rfnoc_error("Caught attempt to modify properties of edge!");
+ } else if (new_edge_info.src_blockid == existing_edge_info.src_blockid
+ && new_edge_info.src_port == existing_edge_info.src_port) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Attempting to reconnect output port " << existing_edge_info.src_blockid
+ << ":" << existing_edge_info.src_port);
+ throw uhd::rfnoc_error("Attempting to reconnect output port!");
+ } else if (new_edge_info.dst_blockid == existing_edge_info.dst_blockid
+ && new_edge_info.dst_port == existing_edge_info.dst_port) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Attempting to reconnect output port " << existing_edge_info.dst_blockid
+ << ":" << existing_edge_info.dst_port);
+ throw uhd::rfnoc_error("Attempting to reconnect input port!");
+ }
+}
+
+} // namespace
+
+/*! Graph-filtering predicate to find dirty nodes only
+ */
+struct graph_t::DirtyNodePredicate
+{
+ DirtyNodePredicate() {} // Default ctor is required
+ DirtyNodePredicate(graph_t::rfnoc_graph_t& graph) : _graph(&graph) {}
+
+ template <typename Vertex>
+ bool operator()(const Vertex& v) const
+ {
+ return !get_dirty_props(boost::get(graph_t::vertex_property_t(), *_graph, v))
+ .empty();
+ }
+
+private:
+ // Don't make any attribute const, because default assignment operator
+ // is also required
+ graph_t::rfnoc_graph_t* _graph;
+};
+
+/******************************************************************************
+ * Public API calls
+ *****************************************************************************/
+void graph_t::connect(node_ref_t src_node, node_ref_t dst_node, graph_edge_t edge_info)
+{
+ node_accessor_t node_accessor{};
+ UHD_LOG_TRACE(LOG_ID,
+ "Connecting block " << src_node->get_unique_id() << ":" << edge_info.src_port
+ << " -> " << dst_node->get_unique_id() << ":"
+ << edge_info.dst_port);
+
+ // Correctly populate edge_info
+ edge_info.src_blockid = src_node->get_unique_id();
+ edge_info.dst_blockid = dst_node->get_unique_id();
+
+ // Set resolver callbacks:
+ node_accessor.set_resolve_all_callback(
+ src_node, [this]() { this->resolve_all_properties(); });
+ node_accessor.set_resolve_all_callback(
+ dst_node, [this]() { this->resolve_all_properties(); });
+
+ // Add nodes to graph, if not already in there:
+ _add_node(src_node);
+ _add_node(dst_node);
+ // Find vertex descriptors
+ auto src_vertex_desc = _node_map.at(src_node);
+ auto dst_vertex_desc = _node_map.at(dst_node);
+
+ // Check if connection exists
+ // This can be optimized: Edges can appear in both out_edges and in_edges,
+ // and we could skip double-checking them.
+ auto out_edge_range = boost::out_edges(src_vertex_desc, _graph);
+ for (auto edge_it = out_edge_range.first; edge_it != out_edge_range.second;
+ ++edge_it) {
+ assert_edge_new(edge_info, boost::get(edge_property_t(), _graph, *edge_it));
+ }
+ auto in_edge_range = boost::in_edges(dst_vertex_desc, _graph);
+ for (auto edge_it = in_edge_range.first; edge_it != in_edge_range.second; ++edge_it) {
+ assert_edge_new(edge_info, boost::get(edge_property_t(), _graph, *edge_it));
+ }
+
+ // Create edge
+ auto edge_descriptor =
+ boost::add_edge(src_vertex_desc, dst_vertex_desc, edge_info, _graph);
+ UHD_ASSERT_THROW(edge_descriptor.second);
+
+ // Now make sure we didn't add an unintended cycle
+ try {
+ _get_topo_sorted_nodes();
+ } catch (const uhd::rfnoc_error&) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Adding edge " << src_node->get_unique_id() << ":" << edge_info.src_port
+ << " -> " << dst_node->get_unique_id() << ":"
+ << edge_info.dst_port
+ << " without disabling property_propagation_active will lead "
+ "to unresolvable graph!");
+ boost::remove_edge(edge_descriptor.first, _graph);
+ throw uhd::rfnoc_error(
+ "Adding edge without disabling property_propagation_active will lead "
+ "to unresolvable graph!");
+ }
+}
+
+void graph_t::initialize()
+{
+ UHD_LOG_DEBUG(LOG_ID, "Initializing graph.");
+ resolve_all_properties();
+}
+
+
+/******************************************************************************
+ * Private methods to be called by friends
+ *****************************************************************************/
+void graph_t::resolve_all_properties()
+{
+ if (boost::num_vertices(_graph) == 0) {
+ return;
+ }
+ node_accessor_t node_accessor{};
+
+ // First, find the node on which we'll start.
+ auto initial_dirty_nodes = _find_dirty_nodes();
+ if (initial_dirty_nodes.size() > 1) {
+ UHD_LOGGER_WARNING(LOG_ID)
+ << "Found " << initial_dirty_nodes.size()
+ << " dirty nodes in initial search (expected one or zero). "
+ "Property propagation may resolve this.";
+ for (auto& vertex : initial_dirty_nodes) {
+ node_ref_t node = boost::get(vertex_property_t(), _graph, vertex);
+ UHD_LOG_WARNING(LOG_ID, "Dirty: " << node->get_unique_id());
+ }
+ }
+ if (initial_dirty_nodes.empty()) {
+ UHD_LOG_DEBUG(LOG_ID,
+ "In resolve_all_properties(): No dirty properties found. Starting on "
+ "arbitrary node.");
+ initial_dirty_nodes.push_back(*boost::vertices(_graph).first);
+ }
+ UHD_ASSERT_THROW(!initial_dirty_nodes.empty());
+ auto initial_node = initial_dirty_nodes.front();
+
+ // Now get all nodes in topologically sorted order, and the appropriate
+ // iterators.
+ auto topo_sorted_nodes = _get_topo_sorted_nodes();
+ auto node_it = topo_sorted_nodes.begin();
+ auto begin_it = topo_sorted_nodes.begin();
+ auto end_it = topo_sorted_nodes.end();
+ while (*node_it != initial_node) {
+ // We know *node_it must be == initial_node at some point, because
+ // otherwise, initial_dirty_nodes would have been empty
+ node_it++;
+ }
+
+ // Start iterating over nodes
+ bool forward_dir = true;
+ int num_iterations = 0;
+ // If all edge properties were known at the beginning, a single iteration
+ // would suffice. However, usually during the first time the property
+ // propagation is run, blocks create new (dynamic) edge properties that
+ // default to dirty. If we had a way of knowing when that happens, we could
+ // dynamically increase the number of iterations during the loop. For now,
+ // we simply hard-code the number of iterations to 2 so that we catch that
+ // case without any additional complications.
+ constexpr int MAX_NUM_ITERATIONS = 2;
+ while (true) {
+ node_ref_t current_node = boost::get(vertex_property_t(), _graph, *node_it);
+ UHD_LOG_TRACE(
+ LOG_ID, "Now resolving next node: " << current_node->get_unique_id());
+
+ // On current node, call local resolution. This may cause other
+ // properties to become dirty.
+ node_accessor.resolve_props(current_node);
+
+ // Forward all edge props in all directions from current node. We make
+ // sure to skip properties if the edge is flagged as
+ // !property_propagation_active
+ _forward_edge_props(*node_it);
+
+ // Now mark all properties on this node as clean
+ node_accessor.clean_props(current_node);
+
+ // The rest of the code in this loop is to figure out who's the next
+ // node. First, increment (or decrement) iterator:
+ if (forward_dir) {
+ node_it++;
+ // If we're at the end, flip the direction
+ if (node_it == end_it) {
+ forward_dir = false;
+ // Back off from the sentinel:
+ node_it--;
+ }
+ }
+ if (!forward_dir) {
+ if (topo_sorted_nodes.size() > 1) {
+ node_it--;
+ // If we're back at the front, flip direction
+ if (node_it == begin_it) {
+ forward_dir = true;
+ }
+ } else {
+ forward_dir = true;
+ }
+ }
+ // If we're going forward, and the next node is the initial node,
+ // we've gone full circle (one full iteration).
+ if (forward_dir && (*node_it == initial_node)) {
+ num_iterations++;
+ if (num_iterations == MAX_NUM_ITERATIONS) {
+ UHD_LOG_TRACE(LOG_ID,
+ "Terminating graph resolution after iteration " << num_iterations);
+ break;
+ }
+ }
+ }
+
+ // Post-iteration sanity checks:
+ // First, we make sure that there are no dirty properties left. If there are,
+ // that means our algorithm couldn't converge and we have a problem.
+ auto remaining_dirty_nodes = _find_dirty_nodes();
+ if (!remaining_dirty_nodes.empty()) {
+ UHD_LOG_ERROR(LOG_ID, "The following properties could not be resolved:");
+ for (auto& vertex : remaining_dirty_nodes) {
+ node_ref_t node = boost::get(vertex_property_t(), _graph, vertex);
+ const std::string node_id = node->get_unique_id();
+ auto dirty_props = get_dirty_props(node);
+ for (auto& prop : dirty_props) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Dirty: " << node_id << "[" << prop->get_src_info().to_string() << " "
+ << prop->get_id() << "]");
+ }
+ }
+ throw uhd::resolve_error("Could not resolve properties.");
+ }
+
+ // Second, go through edges marked !property_propagation_active and make
+ // sure that they match up
+ BackEdgePredicate back_edge_filter(_graph);
+ auto e_iterators =
+ boost::edges(boost::filtered_graph<rfnoc_graph_t, BackEdgePredicate>(
+ _graph, back_edge_filter));
+ bool back_edges_valid = true;
+ for (auto e_it = e_iterators.first; e_it != e_iterators.second; ++e_it) {
+ back_edges_valid = back_edges_valid && _assert_edge_props_consistent(*e_it);
+ }
+ if (!back_edges_valid) {
+ throw uhd::resolve_error(
+ "Error during property resultion: Back-edges inconsistent!");
+ }
+}
+
+/******************************************************************************
+ * Private methods
+ *****************************************************************************/
+graph_t::vertex_list_t graph_t::_find_dirty_nodes()
+{
+ // Create a view on the graph that doesn't include the back-edges
+ DirtyNodePredicate vertex_filter(_graph);
+ boost::filtered_graph<rfnoc_graph_t, boost::keep_all, DirtyNodePredicate> fg(
+ _graph, boost::keep_all(), vertex_filter);
+
+ auto v_iterators = boost::vertices(fg);
+ return vertex_list_t(v_iterators.first, v_iterators.second);
+}
+
+graph_t::vertex_list_t graph_t::_get_topo_sorted_nodes()
+{
+ // Create a view on the graph that doesn't include the back-edges
+ ForwardEdgePredicate edge_filter(_graph);
+ boost::filtered_graph<rfnoc_graph_t, ForwardEdgePredicate> fg(_graph, edge_filter);
+
+ // Topo-sort and return
+ vertex_list_t sorted_nodes;
+ try {
+ boost::topological_sort(fg, std::front_inserter(sorted_nodes));
+ } catch (boost::not_a_dag&) {
+ throw uhd::rfnoc_error("Cannot resolve graph because it has at least one cycle!");
+ }
+ return sorted_nodes;
+}
+
+void graph_t::_add_node(node_ref_t new_node)
+{
+ if (_node_map.count(new_node)) {
+ return;
+ }
+
+ _node_map.emplace(new_node, boost::add_vertex(new_node, _graph));
+}
+
+
+void graph_t::_forward_edge_props(graph_t::rfnoc_graph_t::vertex_descriptor origin)
+{
+ node_accessor_t node_accessor{};
+ node_ref_t origin_node = boost::get(vertex_property_t(), _graph, origin);
+
+ auto edge_props = node_accessor.filter_props(origin_node, [](property_base_t* prop) {
+ return (prop->get_src_info().type == res_source_info::INPUT_EDGE
+ || prop->get_src_info().type == res_source_info::OUTPUT_EDGE);
+ });
+ UHD_LOG_TRACE(LOG_ID,
+ "Forwarding up to " << edge_props.size() << " edge properties from node "
+ << origin_node->get_unique_id());
+
+ for (auto prop : edge_props) {
+ auto neighbour_node_info = _find_neighbour(origin, prop->get_src_info());
+ if (neighbour_node_info.first != nullptr
+ && neighbour_node_info.second.property_propagation_active) {
+ const size_t neighbour_port = prop->get_src_info().type
+ == res_source_info::INPUT_EDGE
+ ? neighbour_node_info.second.src_port
+ : neighbour_node_info.second.dst_port;
+ node_accessor.forward_edge_property(
+ neighbour_node_info.first, neighbour_port, prop);
+ }
+ }
+}
+
+bool graph_t::_assert_edge_props_consistent(rfnoc_graph_t::edge_descriptor edge)
+{
+ node_ref_t src_node =
+ boost::get(vertex_property_t(), _graph, boost::source(edge, _graph));
+ node_ref_t dst_node =
+ boost::get(vertex_property_t(), _graph, boost::target(edge, _graph));
+ graph_edge_t edge_info = boost::get(edge_property_t(), _graph, edge);
+
+ // Helper function to get properties as maps
+ auto get_prop_map = [](const size_t port,
+ res_source_info::source_t edge_type,
+ node_ref_t node) {
+ node_accessor_t node_accessor{};
+ // Create a set of all properties
+ auto props_set = node_accessor.filter_props(
+ node, [port, edge_type, node](property_base_t* prop) {
+ return prop->get_src_info().instance == port
+ && prop->get_src_info().type == edge_type;
+ });
+ std::unordered_map<std::string, property_base_t*> prop_map;
+ for (auto prop_it = props_set.begin(); prop_it != props_set.end(); ++prop_it) {
+ prop_map.emplace((*prop_it)->get_id(), *prop_it);
+ }
+
+ return prop_map;
+ };
+
+ // Create two maps ID -> prop_ptr, so we have an easier time comparing them
+ auto src_prop_map =
+ get_prop_map(edge_info.src_port, res_source_info::OUTPUT_EDGE, src_node);
+ auto dst_prop_map =
+ get_prop_map(edge_info.dst_port, res_source_info::INPUT_EDGE, dst_node);
+
+ // Now iterate through all properties, and make sure they match
+ bool props_match = true;
+ for (auto src_prop_it = src_prop_map.begin(); src_prop_it != src_prop_map.end();
+ ++src_prop_it) {
+ auto src_prop = src_prop_it->second;
+ auto dst_prop = dst_prop_map.at(src_prop->get_id());
+ if (!src_prop->equal(dst_prop)) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Edge property " << src_prop->get_id() << " inconsistent on edge "
+ << print_edge(src_node, dst_node, edge_info));
+ props_match = false;
+ }
+ }
+
+ return props_match;
+}
+
+std::pair<graph_t::node_ref_t, graph_t::graph_edge_t> graph_t::_find_neighbour(
+ rfnoc_graph_t::vertex_descriptor origin, res_source_info port_info)
+{
+ if (port_info.type == res_source_info::INPUT_EDGE) {
+ auto it_range = boost::in_edges(origin, _graph);
+ for (auto it = it_range.first; it != it_range.second; ++it) {
+ graph_edge_t edge_info = boost::get(edge_property_t(), _graph, *it);
+ if (edge_info.dst_port == port_info.instance) {
+ return {
+ boost::get(vertex_property_t(), _graph, boost::source(*it, _graph)),
+ edge_info};
+ }
+ }
+ return {nullptr, {}};
+ }
+ if (port_info.type == res_source_info::OUTPUT_EDGE) {
+ auto it_range = boost::out_edges(origin, _graph);
+ for (auto it = it_range.first; it != it_range.second; ++it) {
+ graph_edge_t edge_info = boost::get(edge_property_t(), _graph, *it);
+ if (edge_info.src_port == port_info.instance) {
+ return {
+ boost::get(vertex_property_t(), _graph, boost::target(*it, _graph)),
+ edge_info};
+ }
+ }
+ return {nullptr, {}};
+ }
+
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
diff --git a/host/lib/rfnoc/node.cpp b/host/lib/rfnoc/node.cpp
index 68ba5e283..0b724b889 100644
--- a/host/lib/rfnoc/node.cpp
+++ b/host/lib/rfnoc/node.cpp
@@ -4,12 +4,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
+#include <uhd/exception.hpp>
#include <uhd/rfnoc/node.hpp>
+#include <uhd/utils/log.hpp>
#include <uhdlib/rfnoc/prop_accessor.hpp>
#include <boost/format.hpp>
+#include <algorithm>
+#include <iostream>
using namespace uhd::rfnoc;
+dirtifier_t node_t::ALWAYS_DIRTY{};
+
+
+node_t::node_t()
+{
+ register_property(&ALWAYS_DIRTY);
+}
std::string node_t::get_unique_id() const
{
@@ -35,7 +46,7 @@ std::vector<std::string> node_t::get_property_ids() const
}
/*** Protected methods *******************************************************/
-void node_t::register_property(property_base_t* prop)
+void node_t::register_property(property_base_t* prop, resolve_callback_t&& clean_callback)
{
std::lock_guard<std::mutex> _l(_prop_mutex);
@@ -60,11 +71,13 @@ void node_t::register_property(property_base_t* prop)
}
_props[src_type].push_back(prop);
+ if (clean_callback) {
+ _clean_cb_registry[prop] = std::move(clean_callback);
+ }
}
-void node_t::add_property_resolver(std::set<property_base_t*>&& inputs,
- std::set<property_base_t*>&& outputs,
- resolver_fn_t&& resolver_fn)
+void node_t::add_property_resolver(
+ prop_ptrs_t&& inputs, prop_ptrs_t&& outputs, resolver_fn_t&& resolver_fn)
{
std::lock_guard<std::mutex> _l(_prop_mutex);
@@ -88,14 +101,21 @@ void node_t::add_property_resolver(std::set<property_base_t*>&& inputs,
}
// All good, we can store it
- _prop_resolvers.push_back(std::make_tuple(
- std::forward<std::set<property_base_t*>>(inputs),
- std::forward<std::set<property_base_t*>>(outputs),
- std::forward<resolver_fn_t>(resolver_fn)));
+ _prop_resolvers.push_back(std::make_tuple(std::forward<prop_ptrs_t>(inputs),
+ std::forward<prop_ptrs_t>(outputs),
+ std::forward<resolver_fn_t>(resolver_fn)));
+}
+
+
+void node_t::set_prop_forwarding_policy(
+ forwarding_policy_t policy, const std::string& prop_id)
+{
+ _prop_fwd_policies[prop_id] = policy;
}
/*** Private methods *********************************************************/
-property_base_t* node_t::_find_property(res_source_info src_info, const std::string& id) const
+property_base_t* node_t::_find_property(
+ res_source_info src_info, const std::string& id) const
{
for (const auto& type_prop_pair : _props) {
if (type_prop_pair.first != src_info.type) {
@@ -117,3 +137,277 @@ uhd::utils::scope_exit::uptr node_t::_request_property_access(
return prop_accessor_t{}.get_scoped_prop_access(*prop, access);
}
+
+property_base_t* node_t::inject_edge_property(
+ property_base_t* blueprint, res_source_info new_src_info)
+{
+ // Check if a property already exists which matches the new property
+ // requirements. If so, we can return early:
+ auto new_prop = _find_property(new_src_info, blueprint->get_id());
+ if (new_prop) {
+ return new_prop;
+ }
+
+ // We need to create a new property and stash it away:
+ new_prop = [&]() -> property_base_t* {
+ auto prop = blueprint->clone(new_src_info);
+ auto ptr = prop.get();
+ _dynamic_props.emplace(std::move(prop));
+ return ptr;
+ }();
+ register_property(new_prop);
+
+ // Collect some info on how to do the forwarding:
+ const auto fwd_policy = [&](const std::string& id) {
+ if (_prop_fwd_policies.count(id)) {
+ return _prop_fwd_policies.at(id);
+ }
+ return _prop_fwd_policies.at("");
+ }(new_prop->get_id());
+ const size_t port_idx = new_prop->get_src_info().instance;
+ const auto port_type = new_prop->get_src_info().type;
+ UHD_ASSERT_THROW(port_type == res_source_info::INPUT_EDGE
+ || port_type == res_source_info::OUTPUT_EDGE);
+
+ // Now comes the hard part: Figure out which other properties need to be
+ // created, and which resolvers need to be instantiated
+ if (fwd_policy == forwarding_policy_t::ONE_TO_ONE) {
+ // Figure out if there's an opposite port
+ const auto opposite_port_type = res_source_info::invert_edge(port_type);
+ if (_has_port({opposite_port_type, port_idx})) {
+ // Make sure that the other side's property exists:
+ // This is a safe recursion, because we've already created and
+ // registered this property.
+ auto opposite_prop =
+ inject_edge_property(new_prop, {opposite_port_type, port_idx});
+ // Now add a resolver that will always forward the value from this
+ // property to the other one.
+ add_property_resolver(
+ {new_prop}, {opposite_prop}, [new_prop, opposite_prop]() {
+ prop_accessor_t{}.forward<false>(new_prop, opposite_prop);
+ });
+ }
+ }
+ if (fwd_policy == forwarding_policy_t::ONE_TO_FAN) {
+ const auto opposite_port_type = res_source_info::invert_edge(port_type);
+ const size_t num_ports = opposite_port_type == res_source_info::INPUT_EDGE
+ ? get_num_input_ports()
+ : get_num_output_ports();
+ for (size_t i = 0; i < num_ports; i++) {
+ auto opposite_prop =
+ inject_edge_property(new_prop, {opposite_port_type, i});
+ // Now add a resolver that will always forward the value from this
+ // property to the other one.
+ add_property_resolver(
+ {new_prop}, {opposite_prop}, [new_prop, opposite_prop]() {
+ prop_accessor_t{}.forward<false>(new_prop, opposite_prop);
+ });
+ }
+ }
+ if (fwd_policy == forwarding_policy_t::ONE_TO_ALL
+ || fwd_policy == forwarding_policy_t::ONE_TO_ALL_IN) {
+ // Loop through all other ports, make sure those properties exist
+ for (size_t other_port_idx = 0; other_port_idx < get_num_input_ports();
+ other_port_idx++) {
+ if (port_type == res_source_info::INPUT_EDGE && other_port_idx == port_idx) {
+ continue;
+ }
+ inject_edge_property(new_prop, {res_source_info::INPUT_EDGE, other_port_idx});
+ }
+ // Now add a dynamic resolver that will update all input properties.
+ // In order to keep this code simple, we bypass the write list and
+ // get access via the prop_accessor.
+ add_property_resolver({new_prop}, {/* empty */}, [this, new_prop, port_idx]() {
+ for (size_t other_port_idx = 0; other_port_idx < get_num_input_ports();
+ other_port_idx++) {
+ if (other_port_idx == port_idx) {
+ continue;
+ }
+ auto prop =
+ _find_property({res_source_info::INPUT_EDGE, other_port_idx},
+ new_prop->get_id());
+ if (prop) {
+ prop_accessor_t{}.forward<false>(new_prop, prop);
+ }
+ }
+ });
+ }
+ if (fwd_policy == forwarding_policy_t::ONE_TO_ALL
+ || fwd_policy == forwarding_policy_t::ONE_TO_ALL_OUT) {
+ // Loop through all other ports, make sure those properties exist
+ for (size_t other_port_idx = 0; other_port_idx < get_num_output_ports();
+ other_port_idx++) {
+ if (port_type == res_source_info::OUTPUT_EDGE && other_port_idx == port_idx) {
+ continue;
+ }
+ inject_edge_property(new_prop, {res_source_info::OUTPUT_EDGE, other_port_idx});
+ }
+ // Now add a dynamic resolver that will update all input properties.
+ // In order to keep this code simple, we bypass the write list and
+ // get access via the prop_accessor.
+ add_property_resolver({new_prop}, {/* empty */}, [this, new_prop, port_idx]() {
+ for (size_t other_port_idx = 0; other_port_idx < get_num_input_ports();
+ other_port_idx++) {
+ if (other_port_idx == port_idx) {
+ continue;
+ }
+ auto prop =
+ _find_property({res_source_info::OUTPUT_EDGE, other_port_idx},
+ new_prop->get_id());
+ if (prop) {
+ prop_accessor_t{}.forward<false>(new_prop, prop);
+ }
+ }
+ });
+ }
+
+ return new_prop;
+}
+
+
+void node_t::init_props()
+{
+ std::lock_guard<std::mutex> _l(_prop_mutex);
+
+ prop_accessor_t prop_accessor{};
+
+ for (auto& resolver_tuple : _prop_resolvers) {
+ // 1) Set all outputs to RWLOCKED
+ auto& outputs = std::get<1>(resolver_tuple);
+ for (auto& output : outputs) {
+ prop_accessor.set_access(output, property_base_t::RWLOCKED);
+ }
+
+ // 2) Run the resolver
+ try {
+ std::get<2>(resolver_tuple)();
+ } catch (const uhd::resolve_error& ex) {
+ UHD_LOGGER_WARNING(get_unique_id())
+ << "Failed to initialize node. Most likely cause: Inconsistent default "
+ "values. Resolver threw this error: "
+ << ex.what();
+ //throw uhd::runtime_error(std::string("Failed to initialize node ") + get_unique_id());
+ }
+
+ // 3) Set outputs back to RO
+ for (auto& output : outputs) {
+ prop_accessor.set_access(output, property_base_t::RO);
+ }
+ }
+
+ // 4) Mark properties as clean
+ clean_props();
+}
+
+
+void node_t::resolve_props()
+{
+ prop_accessor_t prop_accessor{};
+ const prop_ptrs_t dirty_props =
+ filter_props([](property_base_t* prop) { return prop->is_dirty(); });
+ prop_ptrs_t written_props{};
+ UHD_LOG_TRACE(get_unique_id(),
+ "Locally resolving " << dirty_props.size() << " dirty properties.");
+
+ // Helper to determine if any element from inputs is in dirty_props
+ auto in_dirty_props = [&dirty_props](const prop_ptrs_t inputs) {
+ return std::any_of(
+ inputs.cbegin(), inputs.cend(), [&dirty_props](property_base_t* prop) {
+ return dirty_props.count(prop) != 1;
+ });
+ };
+
+ for (auto& resolver_tuple : _prop_resolvers) {
+ auto& inputs = std::get<0>(resolver_tuple);
+ auto& outputs = std::get<1>(resolver_tuple);
+ if (in_dirty_props(inputs)) {
+ continue;
+ }
+
+ // Enable outputs
+ std::vector<uhd::utils::scope_exit::uptr> access_holder;
+ access_holder.reserve(outputs.size());
+ for (auto& output : outputs) {
+ access_holder.emplace_back(prop_accessor.get_scoped_prop_access(*output,
+ written_props.count(output) ? property_base_t::access_t::RWLOCKED
+ : property_base_t::access_t::RW));
+ }
+
+ // Run resolver
+ std::get<2>(resolver_tuple)();
+
+ // Take note of outputs
+ written_props.insert(outputs.cbegin(), outputs.cend());
+
+ // RW or RWLOCKED gets released here as access_holder goes out of scope.
+ }
+}
+
+void node_t::resolve_all()
+{
+ _resolve_all_cb();
+}
+
+
+void node_t::clean_props()
+{
+ prop_accessor_t prop_accessor{};
+ for (const auto& type_prop_pair : _props) {
+ for (const auto& prop : type_prop_pair.second) {
+ if (prop->is_dirty() && _clean_cb_registry.count(prop)) {
+ _clean_cb_registry.at(prop)();
+ }
+ prop_accessor.mark_clean(*prop);
+ }
+ }
+}
+
+
+void node_t::forward_edge_property(
+ property_base_t* incoming_prop, const size_t incoming_port)
+{
+ UHD_ASSERT_THROW(
+ incoming_prop->get_src_info().type == res_source_info::INPUT_EDGE
+ || incoming_prop->get_src_info().type == res_source_info::OUTPUT_EDGE);
+ UHD_LOG_TRACE(get_unique_id(),
+ "Incoming edge property: `" << incoming_prop->get_id() << "`, source info: "
+ << incoming_prop->get_src_info().to_string());
+
+ // The source type of my local prop (it's the opposite of the source type
+ // of incoming_prop)
+ const auto prop_src_type =
+ res_source_info::invert_edge(incoming_prop->get_src_info().type);
+ // Set of local properties that match incoming_prop. It can be an empty set,
+ // or, if the node is misconfigured, a set with more than one entry. Or, if
+ // all is as expected, it's a set with a single entry.
+ auto local_prop_set = filter_props([prop_src_type, incoming_prop, incoming_port](
+ property_base_t* prop) -> bool {
+ return prop->get_src_info().type == prop_src_type
+ && prop->get_src_info().instance == incoming_port
+ && prop->get_id() == incoming_prop->get_id();
+ });
+
+ // If there is no such property, we're forwarding a new property
+ if (local_prop_set.empty()) {
+ UHD_LOG_TRACE(get_unique_id(),
+ "Received unknown incoming edge prop: " << incoming_prop->get_id());
+ local_prop_set.emplace(
+ inject_edge_property(incoming_prop, {prop_src_type, incoming_port}));
+ }
+ // There must be either zero results, or one
+ UHD_ASSERT_THROW(local_prop_set.size() == 1);
+
+ auto local_prop = *local_prop_set.begin();
+
+ prop_accessor_t prop_accessor{};
+ prop_accessor.forward<false>(incoming_prop, local_prop);
+}
+
+bool node_t::_has_port(const res_source_info& port_info) const
+{
+ return (port_info.type == res_source_info::INPUT_EDGE
+ && port_info.instance <= get_num_input_ports())
+ || (port_info.type == res_source_info::OUTPUT_EDGE
+ && port_info.instance <= get_num_output_ports());
+}
+