diff options
Diffstat (limited to 'host/lib')
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/graph.hpp | 271 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/node_accessor.hpp | 5 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/graph.cpp | 460 | ||||
-rw-r--r-- | host/lib/rfnoc/node.cpp | 312 |
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()); +} + |