aboutsummaryrefslogtreecommitdiffstats
path: root/host/include
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2019-04-24 18:23:31 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:14 -0800
commitc97bdc6c94c98753215a90cf499af4bdf06db8e2 (patch)
tree67f623ae84acb045d145bd22036df60a1724b789 /host/include
parentf0371292a43c3e4e3c68d8631c57d64ab10faf4c (diff)
downloaduhd-c97bdc6c94c98753215a90cf499af4bdf06db8e2.tar.gz
uhd-c97bdc6c94c98753215a90cf499af4bdf06db8e2.tar.bz2
uhd-c97bdc6c94c98753215a90cf499af4bdf06db8e2.zip
rfnoc: Add property propagation, Boost.Graph storage
- Adds a detail::graph_t class, which handles the propagation - Adds methods to node_t to aid with propagation - Adds unit tests - Adds dynamic property forwarding: Nodes are now able to forward properties they don't know about by providing a forwarding policy. A good example is the FIFO block which simply forwards most properties verbatim. - node: Temporarily disabling consistency check at init
Diffstat (limited to 'host/include')
-rw-r--r--host/include/uhd/rfnoc/node.hpp265
-rw-r--r--host/include/uhd/rfnoc/node.ipp8
2 files changed, 247 insertions, 26 deletions
diff --git a/host/include/uhd/rfnoc/node.hpp b/host/include/uhd/rfnoc/node.hpp
index 5f8a15f4f..1e634ecea 100644
--- a/host/include/uhd/rfnoc/node.hpp
+++ b/host/include/uhd/rfnoc/node.hpp
@@ -8,11 +8,12 @@
#define INCLUDED_LIBUHD_RFNOC_NODE_HPP
#include <uhd/rfnoc/property.hpp>
+#include <uhd/rfnoc/dirtifier.hpp>
#include <uhd/utils/scope_exit.hpp>
#include <uhd/utils/log.hpp>
+#include <boost/graph/adjacency_list.hpp>
#include <unordered_map>
#include <unordered_set>
-#include <boost/optional.hpp>
#include <functional>
#include <memory>
#include <mutex>
@@ -32,6 +33,27 @@ class UHD_API node_t
{
public:
using resolver_fn_t = std::function<void(void)>;
+ using resolve_callback_t = std::function<void(void)>;
+
+ //! Types of property/action forwarding for those not defined by the block itself
+ enum class forwarding_policy_t {
+ //! Forward the property/action to the opposite port with the same index
+ //(e.g., if it comes from input port 0, forward it to output port 0).
+ ONE_TO_ONE,
+ //! Fan-out forwarding: Forward to all opposite ports
+ ONE_TO_FAN,
+ //! Forward the property to all input ports
+ ONE_TO_ALL_IN,
+ //! Forward the property to all output ports
+ ONE_TO_ALL_OUT,
+ //! Forward the property to all ports
+ ONE_TO_ALL,
+ //! Property propagation ends here
+ DROP
+ };
+
+
+ node_t();
/******************************************
* Basic Operations
@@ -121,44 +143,77 @@ protected:
/******************************************
* Internal Registration Functions
******************************************/
+ using prop_ptrs_t = std::unordered_set<property_base_t*>;
/*! Register a property for this block
*
* \param prop A reference to the property
+ * \param clean_callback A callback that gets executed whenever this property
+ * is dirty and gets marked clean
*
* \throws uhd::key_error if another property with the same ID and source
* type is already registered
*/
- void register_property(property_base_t* prop);
+ void register_property(
+ property_base_t* prop, resolve_callback_t&& clean_callback = nullptr);
- /*! Add a resolver function to this block. A resolver function is used to
- * reconcile state changes in the block, and is triggered by a
- * user or other upstream/downstream blocks. A block may have multiple
- * resolvers.
+ /*! Add a resolver function to this block.
+ *
+ * A resolver function is used to reconcile state changes in the block, and
+ * is triggered by a user or other upstream/downstream blocks. A block may
+ * have multiple resolvers.
*
- * NOTE: Multiple resolvers may share properties for reading and a
- * resolver may read multiple properties
- * NOTE: The framework will perform run-time validation to
- * ensure read/write property access is not violated. This means the
- * resolver function can only read values that are specified in the
- * \p inputs list, and only write values that are specified in the
- * \p outputs list.
+ * Notes on resolvers:
+ * - Multiple resolvers may share properties for reading and a resolver may
+ * read multiple properties
+ * - A resolver may assume the properties are in a consistent state before
+ * it executes, but it must leave the properties in a consistent state
+ * when it completes.
+ * - The framework will perform run-time validation to ensure read/write
+ * property access is not violated. All properties can be read during
+ * execution, but only properties in the \p outputs list can be written
+ * to.
*
- * \param inputs The properties that this resolver will read
+ * \param inputs The properties that will cause this resolver to run
* \param outputs The properties that this resolver will write to
* \param resolver_fn The resolver function
+ * \throws uhd::runtime_error if any of the properties listed is not
+ * registered
*/
- void add_property_resolver(std::set<property_base_t*>&& inputs,
- std::set<property_base_t*>&& outputs,
- resolver_fn_t&& resolver_fn);
+ void add_property_resolver(
+ prop_ptrs_t&& inputs, prop_ptrs_t&& outputs, resolver_fn_t&& resolver_fn);
+
+ /**************************************************************************
+ * Property forwarding
+ *************************************************************************/
+ /*! Set a property forwarding policy for dynamic properties
+ *
+ * Whenever this node is asked to handle a property that is not registered,
+ * this is how the node knows what to do with the property. For example, the
+ * FIFO block controller will almost always want to pass on properties to
+ * the next block.
+ *
+ * This method can be called more than once, and it will overwrite previous
+ * policies. However, once a property has been registered with this block,
+ * the policy is set.
+ * Typically, this function should only ever be called from within the
+ * constructor.
+ *
+ * \param policy The policy that is applied (see also forwarding_policy_t).
+ * \param prop_id The property ID that this forwarding policy is applied to.
+ * If \p prop_id is not given, it will apply to all properties,
+ * unless a different policy was given with a matching ID.
+ */
+ void set_prop_forwarding_policy(
+ forwarding_policy_t policy, const std::string& prop_id = "");
/*! Handle a request to perform an action. The default action handler
* ignores user action and forwards port actions.
*
* \param handler The function that is called to handle the action
*/
- //void register_action_handler(std::function<
- //void(const action_info& info, const res_source_info& src)
+ // void register_action_handler(std::function<
+ // void(const action_info& info, const res_source_info& src)
//> handler);
/******************************************
@@ -167,7 +222,11 @@ protected:
// TBW
//
+ //! A dirtifyer object, useful for properties that always need updating.
+ static dirtifier_t ALWAYS_DIRTY;
+
private:
+ friend class node_accessor_t;
/*! Return a reference to a property, if it exists.
*
@@ -177,12 +236,148 @@ private:
res_source_info src_info, const std::string& id) const;
/*! RAII-Style property access
+ *
+ * Returns an object which will grant temporary \p access to the property
+ * \p prop until the returned object goes out of scope.
*/
uhd::utils::scope_exit::uptr _request_property_access(
property_base_t* prop, property_base_t::access_t access) const;
+ /*! Return a set of properties that match a predicate
+ *
+ * Will return an empty set if none match.
+ */
+ template <typename PredicateType>
+ prop_ptrs_t filter_props(PredicateType&& predicate)
+ {
+ prop_ptrs_t filtered_props{};
+ for (const auto& type_prop_pair : _props) {
+ for (const auto& prop : type_prop_pair.second) {
+ if (predicate(prop)) {
+ filtered_props.insert(prop);
+ }
+ }
+ }
+
+ return filtered_props;
+ }
+
+ /*! Set up a new, unknown edge property
+ *
+ * This function is called when forward_edge_property() receives a new
+ * property it doesn't know about. Using the policy set in
+ * set_prop_forwarding_policy(), we figure our which resolvers to set, and
+ * install them.
+ */
+ property_base_t* inject_edge_property(
+ property_base_t* blueprint, res_source_info new_src_info);
+
+ /*! This will run all the resolvers once to put the block into a valid
+ * state. It will execute the following algorithm:
+ *
+ * - Iterate through all resolvers
+ * - For all resolvers, mark its output properties as RWLOCKED (the
+ * assumption is that all other properties are RO).
+ * - Run the resolver. If the default values were inconsistent, this can
+ * cause a uhd::resolve_error.
+ * - Reset the properties to RO and continue with the next resolver.
+ * - When all resolvers have been run, mark all properties as clean.
+ *
+ * \throws uhd::resolve_error if the default values were inconsistent
+ */
+ void init_props();
+
+ /*! This will find dirty properties, and call their respective resolvers.
+ *
+ * It will execute the following algorithm:
+ * - Create set D of all dirty properties
+ * - Create empty set W
+ * - Create a set R of resolvers which have the dirty properties in their
+ * input list
+ * - For ever resolver:
+ * - For all outputs, set the access mode to RWLOCKED if it's in W, or to
+ * RW if it's not
+ * - Run the resolver
+ * - Reset the access modes on all outputs to RO
+ * - Add all outputs to W
+ *
+ * The assumption is that the properties are internally consistent before
+ * this function was called.
+ *
+ * Note: This does not mark any properties as cleaned! All modified outputs
+ * will be marked dirty.
+ *
+ * \throws uhd::resolve_error if the properties could not be resolved. This
+ * typically indicates that the resolvers were set up inconsistently.
+ */
+ void resolve_props();
+
+ /*! This will trigger a graph-wide property resolution
+ */
+ void resolve_all();
+
+ /*! Mark all properties as clean
+ *
+ * When dirty properties have a clean-callback registered, that will also
+ * get triggered.
+ */
+ void clean_props();
+
+ /*! Sets a callback that the framework can call when it needs to trigger a
+ * property resolution.
+ */
+ void set_resolve_all_callback(resolve_callback_t&& resolver)
+ {
+ _resolve_all_cb = resolver;
+ }
+
+ /*! Forward the value of an edge property into this node
+ *
+ * Note that \p incoming_prop is a reference to the neighbouring node's
+ * property. That means if incoming_prop.get_src_info().type == OUTPUT_EDGE,
+ * then this will update a property on this node with the same ID, port
+ * number, but one that has source type INPUT_EDGE.
+ *
+ * This method is meant to be called by the framework during resolution of
+ * properties, and shouldn't be called by the class itself.
+ *
+ * If this method is called with an unknown property, a new dynamic property
+ * is created. Then, the forwarding policy is looked up to make a decision
+ * what to do next:
+ * - forwarding_policy_t::DROP: Nothing happens.
+ * - forwarding_policy_t::ONE_TO_ONE: A new property on the opposite
+ * port is created if it doesn't yet exist. A resolver is registered that
+ * copies the value from one property to another.
+ * If there is no opposite port, then we continue as if the policy had
+ * been DROP.
+ * - forwarding_policy_t::ONE_TO_ALL_IN: New properties on all input
+ * ports are created if they don't yet exist. A resolver is created that
+ * copies from this new property to all inputs.
+ * - forwarding_policy_t::ONE_TO_ALL_OUT: Same as before, except the
+ * property is forwarded to the outputs.
+ * - forwarding_policy_t::ONE_TO_ALL: Same as before, except the
+ * property is forwarded to all ports.
+ *
+ * \param incoming_prop Pointer to the other node's property that is being
+ * forwarded. We read the value from that property, and
+ * check the types match.
+ * \param incoming_port The port on which this property is incoming.
+ *
+ * \throws uhd::type_error if the properties do not have the same type
+ */
+ void forward_edge_property(
+ property_base_t* incoming_prop, const size_t incoming_port);
+
+ /**************************************************************************
+ * Private helpers
+ *************************************************************************/
+ //! Return true if this node has a port that matches \p port_info
+ bool _has_port(const res_source_info& port_info) const;
+
/****** Attributes *******************************************************/
- //! Mutex to lock access to the property registry
+ //! Mutex to lock access to the property registry. Note: This is not the
+ // global property mutex, this only write-protects access to the property-
+ // related containers in this class.
mutable std::mutex _prop_mutex;
//! Stores a reference to every registered property (Property Registry)
@@ -191,13 +386,35 @@ private:
std::hash<size_t> >
_props;
- using property_resolver_t = std::tuple<std::set<property_base_t*>,
- std::set<property_base_t*>,
- resolver_fn_t> ;
+ //! Stores a clean callback for some properties
+ std::unordered_map<property_base_t*, resolve_callback_t> _clean_cb_registry;
+
+ using property_resolver_t = std::tuple<prop_ptrs_t, prop_ptrs_t, resolver_fn_t>;
//! Stores the list of property resolvers
std::vector<property_resolver_t> _prop_resolvers;
-}; // class node_t
+ //! A callback that can be called to notify the graph manager that something
+ // has changed, and that a property resolution needs to be performed.
+ resolve_callback_t _resolve_all_cb = [this]() {
+ resolve_props();
+ clean_props();
+ };
+
+ //! This is permanent storage for all properties that don't get stored
+ // explicitly.
+ //
+ // Dynamic properties include properties defined in the block descriptor
+ // file, as well as new properties that get passed in during property
+ // propagation.
+ std::unordered_set<std::unique_ptr<property_base_t>> _dynamic_props;
+
+ //! Forwarding policy for specific properties
+ //
+ // The entry with the empty-string-key is the default policy.
+ std::unordered_map<std::string, forwarding_policy_t> _prop_fwd_policies{{
+ "", forwarding_policy_t::ONE_TO_ONE}};
+
+}; // class node_t
}} /* namespace uhd::rfnoc */
diff --git a/host/include/uhd/rfnoc/node.ipp b/host/include/uhd/rfnoc/node.ipp
index 7de69063c..62db98243 100644
--- a/host/include/uhd/rfnoc/node.ipp
+++ b/host/include/uhd/rfnoc/node.ipp
@@ -55,7 +55,9 @@ void node_t::set_property(
prop_ptr->set(val);
}
- // resolve_all() TODO
+ // Now trigger a property resolution. If other properties depend on this one,
+ // they will be updated.
+ resolve_all();
}
template <typename prop_data_t>
@@ -63,7 +65,9 @@ const prop_data_t& node_t::get_property(const std::string& id, const size_t inst
{
res_source_info src_info{res_source_info::USER, instance};
- // resolve_all() TODO
+ // First, trigger a property resolution to make sure this property is
+ // updated (if necessary) before reading it out
+ resolve_all();
auto prop_ptr = _assert_prop<prop_data_t>(
_find_property(src_info, id), get_unique_id(), id);