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()); +} +  | 
