// // 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 #include #include #include #include #include #include #include #include namespace uhd { namespace rfnoc { namespace detail { //! Container for the logical graph within an uhd::rfnoc_graph class graph_t { public: using uptr = std::unique_ptr; //! A shorthand for a pointer to a node using node_ref_t = uhd::rfnoc::node_t*; //! Shorthand to existing graph_edge_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); // /*! Commit graph and run initial checks * * 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 commit(); /*! Opposite of commit() * * Calling this will disable property propagation until commit() has been * called an equal number of times. */ void release(); /*! Shutdown graph: Permenanently release * * This will release the graph permanently and safely. All ongoing property * and action handling is completed and then disabled (this means that * calling shutdown while blocks are still working will cause actions to not * get delivered). */ void shutdown(); /*! Return a list of all edges */ std::vector enumerate_edges(); 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; struct edge_property_t { enum { num = 4001 }; typedef boost::edge_property_tag kind; }; using RfnocEdgeProperty = boost::property; /*! 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; using vertex_list_t = std::list; template struct ForwardBackwardEdgePredicate { ForwardBackwardEdgePredicate() {} // Default ctor is required ForwardBackwardEdgePredicate(rfnoc_graph_t& graph) : _graph(&graph) {} template 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; using BackEdgePredicate = ForwardBackwardEdgePredicate; //! 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; /************************************************************************** * The Algorithm *************************************************************************/ /*! Implementation of the property propagation algorithm */ void resolve_all_properties(uhd::rfnoc::resolve_context context, rfnoc_graph_t::vertex_descriptor initial_node); /************************************************************************** * Action API *************************************************************************/ /*! Entrypoint for action delivery * * When a node invokes its node_t::post_action() function, eventually that * call lands here. This function acts as a mailman, that is, it figures out * which edge on which node is supposed to receive this action, and delivers * it via the node_t::receive_action() method. * Note since this is private, nodes can't directly access this functions. * We provide a lambda to nodes for that purpose. * * When an action is posted, that may trigger further actions. In order not * to go into infinite recursion, this function is also responsible for * serializing the actions. Even so, it is possible that, due to * misconfiguration of nodes and their behaviour, a cascade of actions is * posted that never stops. Therefore, another responsibility of this * function is to track the number of follow-up messages sent, and terminate * an infinite cycle of messages. * * \param src_node Reference to the node where the post_action() call is * originating from * \param src_edge The edge on that node where the action is being posted to. * Note that its the edge from the node's point of view, so * if src_edge.type == OUTPUT_EDGE, then the node posted to * its output edge. * * \throws uhd::runtime_error if it has to terminate a infinite cascade of * actions */ void enqueue_action( node_ref_t src_node, res_source_info src_edge, action_info::sptr action); /************************************************************************** * Private graph helpers *************************************************************************/ template std::vector _vertices_to_nodes(VertexContainerType&& vertex_container) { std::vector 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 origin. 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 _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); /*! Query all blocks on their topology * * \throws uhd::runtime_error if any of the blocks doesn't like its * configuration */ void _check_topology(); /************************************************************************** * 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; using action_tuple_t = std::tuple; //! FIFO for incoming actions std::deque _action_queue; //! Flag to ensure serialized handling of actions std::atomic_flag _action_handling_ongoing; //! Changes to the release/commit state of the graph are locked with this mutex std::recursive_mutex _release_mutex; //! This counter gets decremented everytime commit() is called. When zero, // the graph is committed. size_t _release_count{1}; //! A flag if the graph has shut down. Is protected by _release_mutex bool _shutdown{false}; }; }}} /* namespace uhd::rfnoc::detail */ #endif /* INCLUDED_LIBUHD_GRAPH_HPP */