diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-03-24 16:54:11 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 11:49:13 -0800 |
commit | efb1d5a4729ea892ea03b8d0265aae9e8fadfff1 (patch) | |
tree | 23cd4dbf85c635d381aaf7b0f6e9bd15eeca08fc /host | |
parent | 44f9bca2c5b561fa3f5f08d0d616c91d2142cbf9 (diff) | |
download | uhd-efb1d5a4729ea892ea03b8d0265aae9e8fadfff1.tar.gz uhd-efb1d5a4729ea892ea03b8d0265aae9e8fadfff1.tar.bz2 uhd-efb1d5a4729ea892ea03b8d0265aae9e8fadfff1.zip |
rfnoc: Add properties, nodes, and accessors
Adds the following classes:
- uhd::rfnoc::node_t, the base class for RFNoC nodes
- uhd::rfnoc::node_accessor_t, a class to access private properties
- uhd::rfnoc::res_source_info, a struct that identifies where properties
come from
- uhd::rfnoc::property_t, and property_base_t (its parent)
- uhd::rfnoc::prop_accessor_t, a class to access properties
Add always dirty property (dirtifier).
Also adds unit tests for properties.
Diffstat (limited to 'host')
-rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 3 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/dirtifier.hpp | 54 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/node.hpp | 206 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/node.ipp | 78 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/property.hpp | 257 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/property.ipp | 25 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/res_source_info.hpp | 89 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/node_accessor.hpp | 84 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/prop_accessor.hpp | 96 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/node.cpp | 119 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | host/tests/rfnoc_node_test.cpp | 108 | ||||
-rw-r--r-- | host/tests/rfnoc_property_test.cpp | 152 |
14 files changed, 1274 insertions, 0 deletions
diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt index 7b5b69f4b..bc5d7f054 100644 --- a/host/include/uhd/rfnoc/CMakeLists.txt +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -13,9 +13,12 @@ if(ENABLE_RFNOC) blockdef.hpp block_id.hpp constants.hpp + dirtifier.hpp graph.hpp node_ctrl_base.hpp node_ctrl_base.ipp + node.hpp + node.ipp rate_node_ctrl.hpp scalar_node_ctrl.hpp sink_block_ctrl_base.hpp diff --git a/host/include/uhd/rfnoc/dirtifier.hpp b/host/include/uhd/rfnoc/dirtifier.hpp new file mode 100644 index 000000000..cc4430d3d --- /dev/null +++ b/host/include/uhd/rfnoc/dirtifier.hpp @@ -0,0 +1,54 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_DIRTIFYIER_HPP +#define INCLUDED_LIBUHD_DIRTIFYIER_HPP + +#include <uhd/rfnoc/property.hpp> + +namespace uhd { namespace rfnoc { + +/*! This is a special class for property that is always dirty. This is useful + * to force property resolutions in certain cases. + * + * Note: This has nothing to do with 'dirtify' in the CGI/graphics sense. + */ +class dirtifier_t : public property_base_t +{ +public: + dirtifier_t() + : property_base_t("__ALWAYS_DIRTY__", res_source_info(res_source_info::FRAMEWORK)) + { + // nop + } + + //! This property is always dirty + bool is_dirty() const { return true; } + + //! This property is never equal to anything else + bool equal(property_base_t*) const { return false; } + + //! Always dirty, so this can be called as often as we like + void force_dirty() {} + +private: + //! This property cannot be marked clean, but nothing happens if you try + void mark_clean() {} + + //! The value from this property cannot be forwarded + void forward(property_base_t*) + { + throw uhd::type_error("Cannot forward to or from dirtifier property!"); + } + + //! This property never has the same type as another type + bool is_type_equal(property_base_t*) const { return false; } +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_DIRTIFYIER_HPP */ + diff --git a/host/include/uhd/rfnoc/node.hpp b/host/include/uhd/rfnoc/node.hpp new file mode 100644 index 000000000..5f8a15f4f --- /dev/null +++ b/host/include/uhd/rfnoc/node.hpp @@ -0,0 +1,206 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NODE_HPP +#define INCLUDED_LIBUHD_RFNOC_NODE_HPP + +#include <uhd/rfnoc/property.hpp> +#include <uhd/utils/scope_exit.hpp> +#include <uhd/utils/log.hpp> +#include <unordered_map> +#include <unordered_set> +#include <boost/optional.hpp> +#include <functional> +#include <memory> +#include <mutex> +#include <string> +#include <tuple> +#include <vector> + +namespace uhd { namespace rfnoc { + +/*! The base class for all nodes within an RFNoC graph + * + * The block supports the following types of data access: + * - High-level property access + * - Action execution + */ +class UHD_API node_t +{ +public: + using resolver_fn_t = std::function<void(void)>; + + /****************************************** + * Basic Operations + ******************************************/ + //! Return a unique identifier string for this node. In every RFNoC graph, + // no two nodes cannot have the same ID. + // + // \returns The unique ID as a string + virtual std::string get_unique_id() const; + + /*! Return the number of input ports for this block. + * + * This function needs to be overridden. + * + * \return noc_id The number of ports + */ + virtual size_t get_num_input_ports() const = 0; + + /*! Return the number of output ports for this block. + * + * This function needs to be overridden. + * + * \return noc_id The number of ports + */ + virtual size_t get_num_output_ports() const = 0; + + /****************************************** + * Property Specific + ******************************************/ + + /*! Return the names of all possible user properties that can be + * accessed for this block. + * + * Note that the type of the property is not auto-detectable. + * + * \returns A vector of all possible IDs of user properties supported by + * this block. + */ + std::vector<std::string> get_property_ids() const; + + /*! Set a specific user property that belongs to this block. + * + * Setting a user property will trigger a property resolution. This means + * that changing this block can have effects on other nodes. + * + * If the property does not exist, or if the property can be determined to + * be of a different type than \p prop_data_t due to the usage of runtime + * type information (RTTI), a lookup_error is thrown. + * + * \param prop_data_t The data type of the property + * \param id The identifier of the property to write. To find out which + * values of \p id are valid, call get_property_ids() + * \param instance The instance number of this property + * \param val The new value of the property. + */ + template <typename prop_data_t> + void set_property( + const std::string& id, const prop_data_t& val, const size_t instance = 0); + + /*! Get the value of a specific block argument. \p The type of an argument + * must be known at compile time. + * + * If the property does not exist, or if the property can be determined to + * be of a different type than \p prop_data_t due to the usage of runtime + * type information (RTTI), a lookup_error is thrown. + * + * Note: Despite this being a "getter", this function is not declared const. + * This is because internally, it can resolve properties, which may cause + * changes within the object. + * + * \param prop_data_t The data type of the property + * \param id The identifier of the property to write. + * \param instance The instance number of this property + * \return The value of the property. + * \throws uhd::lookup_error if the property can't be found. + */ + template <typename prop_data_t> + const prop_data_t& get_property( + const std::string& id, const size_t instance = 0) /* mutable */; + + /****************************************** + * Action Specific + ******************************************/ + // TBW + +protected: + /****************************************** + * Internal Registration Functions + ******************************************/ + + /*! Register a property for this block + * + * \param prop A reference to the property + * + * \throws uhd::key_error if another property with the same ID and source + * type is already registered + */ + void register_property(property_base_t* prop); + + /*! 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. + * + * \param inputs The properties that this resolver will read + * \param outputs The properties that this resolver will write to + * \param resolver_fn The resolver function + */ + void add_property_resolver(std::set<property_base_t*>&& inputs, + std::set<property_base_t*>&& outputs, + resolver_fn_t&& resolver_fn); + + /*! 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) + //> handler); + + /****************************************** + * Internal action forwarding + ******************************************/ + // TBW + // + +private: + + /*! Return a reference to a property, if it exists. + * + * \returns A reference to the property, if it exists, or nullptr otherwise + */ + property_base_t* _find_property( + res_source_info src_info, const std::string& id) const; + + /*! RAII-Style property access + */ + uhd::utils::scope_exit::uptr _request_property_access( + property_base_t* prop, property_base_t::access_t access) const; + + /****** Attributes *******************************************************/ + //! Mutex to lock access to the property registry + mutable std::mutex _prop_mutex; + + //! Stores a reference to every registered property (Property Registry) + std::unordered_map<res_source_info::source_t, + std::vector<property_base_t*>, + 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 the list of property resolvers + std::vector<property_resolver_t> _prop_resolvers; + +}; // class node_t + +}} /* namespace uhd::rfnoc */ + +#include <uhd/rfnoc/node.ipp> + +#endif /* INCLUDED_LIBUHD_RFNOC_NODE_HPP */ diff --git a/host/include/uhd/rfnoc/node.ipp b/host/include/uhd/rfnoc/node.ipp new file mode 100644 index 000000000..7de69063c --- /dev/null +++ b/host/include/uhd/rfnoc/node.ipp @@ -0,0 +1,78 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NODE_IPP +#define INCLUDED_LIBUHD_RFNOC_NODE_IPP + +#include <boost/format.hpp> +#include <boost/units/detail/utility.hpp> + +namespace { + +template <typename prop_data_t> +uhd::rfnoc::property_t<prop_data_t>* _assert_prop( + uhd::rfnoc::property_base_t* prop_base_ptr, + const std::string& node_id, + const std::string& prop_id) +{ + // First check if the pointer is valid at all: + if (prop_base_ptr == nullptr) { + throw uhd::lookup_error( + str(boost::format("[%s] Unknown user property: `%s'") % node_id % prop_id)); + } + + // Next, check if we can cast the pointer to the desired type: + auto prop_ptr = + dynamic_cast<uhd::rfnoc::property_t<prop_data_t>*>(prop_base_ptr); + if (!prop_ptr) { + throw uhd::type_error(str( + boost::format( + "[%s] Found user property `%s', but could not cast to requested type `%s'!") + % node_id % prop_id % boost::units::detail::demangle(typeid(prop_data_t).name()) )); + } + + // All is good, we now return the raw pointer that has been validated. + return prop_ptr; +} + +} // namespace + +namespace uhd { namespace rfnoc { + +template <typename prop_data_t> +void node_t::set_property( + const std::string& id, const prop_data_t& val, const size_t instance) +{ + res_source_info src_info{res_source_info::USER, instance}; + UHD_LOG_TRACE(get_unique_id(), "Setting property `" << id << "`"); + auto prop_ptr = + _assert_prop<prop_data_t>(_find_property(src_info, id), get_unique_id(), id); + { + auto prop_access = _request_property_access(prop_ptr, property_base_t::RW); + prop_ptr->set(val); + } + + // resolve_all() TODO +} + +template <typename prop_data_t> +const prop_data_t& node_t::get_property(const std::string& id, const size_t instance) +{ + res_source_info src_info{res_source_info::USER, instance}; + + // resolve_all() TODO + auto prop_ptr = _assert_prop<prop_data_t>( + _find_property(src_info, id), get_unique_id(), id); + + auto prop_access = _request_property_access(prop_ptr, property_base_t::RO); + return prop_ptr->get(); +} + + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NODE_IPP */ + diff --git a/host/include/uhd/rfnoc/property.hpp b/host/include/uhd/rfnoc/property.hpp new file mode 100644 index 000000000..a85c978e9 --- /dev/null +++ b/host/include/uhd/rfnoc/property.hpp @@ -0,0 +1,257 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_PROPERTY_HPP +#define INCLUDED_LIBUHD_PROPERTY_HPP + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/res_source_info.hpp> +#include <uhd/utils/dirty_tracked.hpp> +#include <string> +#include <memory> + + +namespace uhd { namespace rfnoc { + +// Forward declaration, separates includes +class prop_accessor_t; + +/*! Base class for properties + * + */ +class UHD_API property_base_t +{ +public: + enum access_t { + NONE, //!< Neither reading nor writing to this property is permitted + RO = 0x1, //!< Read-Only + RW = 0x3, //!< Read-Write + RWLOCKED = 0x5 //!< Write is locked. This lets you call set(), but only if the value is unchanged. + }; + + property_base_t(const std::string& id, const res_source_info& source_info) + : _id(id), _source_info(source_info) + { + } + + //! Gets the ID (name) of this property + const std::string& get_id() const + { + return _id; + } + + //! Return the source info for this property + const res_source_info& get_src_info() const + { + return _source_info; + } + + //! Query this property's dirty flag. + // + // If it's true, that means this property was recently changed, but changes + // have not propagated yet and still need resolving. + virtual bool is_dirty() const = 0; + + //! Returns true if this property can be read. + bool read_access_granted() const + { + return static_cast<uint8_t>(_access_mode) & 0x1; + } + + //! Returns true if this property can be written to. + bool write_access_granted() const + { + return static_cast<uint8_t>(_access_mode) & 0x2; + } + + //! Return the current access mode + access_t get_access_mode() const + { + return _access_mode; + } + + //! Return true if rhs has the same type and value + virtual bool equal(property_base_t* rhs) const = 0; + + //! Create a copy of this property + // + // The copy must have the same type, value, and ID. However, it is often + // desirable to have a new source information, so that can be overridden. + // + // The cleanliness state of \p original is not preserved. The new property + // will have the same cleanliness state as any other new property. + // + virtual std::unique_ptr<property_base_t> clone(res_source_info) + { + throw uhd::not_implemented_error("Cloning is not available for this property."); + } + + virtual void force_dirty() = 0; + +private: + friend class prop_accessor_t; + + //! Reset the dirty bit. See also dirty_tracked::mark_clean() + virtual void mark_clean() = 0; + + //! Forward the value of this property to another one + virtual void forward(property_base_t* next_prop) = 0; + + //! Compare property types + // + // Note: This uses RTTI to evaluate type equality + virtual bool is_type_equal(property_base_t* other_prop) const = 0; + + /*** Attributes **********************************************************/ + //! Stores an ID string for this property. They don't need to be unique. + const std::string _id; + + //! Stores the source info for this property. + const res_source_info _source_info; + + //! Access mode. Note that the prop_accessor_t is the only one who can + // write this. + access_t _access_mode = RO; +}; + +/*! + * An encapsulation class for a block property. + */ +template <typename data_t> +class property_t : public property_base_t +{ +public: + //! We want to be good C++ citizens + using value_type = data_t; + + property_t(const std::string& id, data_t&& value, const res_source_info& source_info); + + property_t( + const std::string& id, const data_t& value, const res_source_info& source_info); + + property_t(const property_t<data_t>& prop) = default; + + //! Returns the dirty state of this property + // + // If true, this means the value was recently changed, but it wasn't marked + // clean yet. + bool is_dirty() const + { + return _data.is_dirty(); + } + + bool equal(property_base_t* rhs) const + { + if (!is_type_equal(rhs)) { + return false; + } + return get() == dynamic_cast<property_t<data_t>*>(rhs)->get(); + } + + std::unique_ptr<property_base_t> clone(res_source_info new_src_info) override + { + return std::unique_ptr<property_base_t>(new property_t<data_t>( + get_id(), get(), new_src_info)); + } + + //! Returns the source info for the property + // const res_source_info& get_src_info() const = 0; + + //! Set the value of this property + // + // \throws uhd::access_error if the current access mode is not RW or RWLOCKED + // \throws uhd::resolve_error if the property is RWLOCKED but the new value + // doesn't match + void set(const data_t& value) + { + if (write_access_granted()) { + _data = value; + } else if (get_access_mode() == RWLOCKED) { + if (_data.get() != value) { + throw uhd::resolve_error(std::string("Attempting to overwrite property `") + + get_id() + "@" + get_src_info().to_string() + + "' with a new value after it was locked!"); + } + } else { + throw uhd::access_error(std::string("Attempting to write to property `") + + get_id() + "' without access privileges!"); + } + } + + void force_dirty() + { + if (write_access_granted()) { + _data.force_dirty(); + } else if (get_access_mode() == RWLOCKED) { + if (!_data.is_dirty()) { + throw uhd::resolve_error(std::string("Attempting to overwrite property `") + + get_id() + + "' with dirty flag after it was locked!"); + } + } else { + throw uhd::access_error(std::string("Attempting to flag dirty property `") + + get_id() + "' without access privileges!"); + } + } + + //! Get the value of this property + const data_t& get() const + { + if (read_access_granted()) { + return _data; + } + throw uhd::access_error(std::string("Attempting to read property `") + get_id() + + "' without access privileges!"); + } + + operator const data_t&() const + { + return get(); + } + + bool operator==(const data_t& rhs) + { + return get() == rhs; + } + + property_t<data_t>& operator=(const data_t& value) + { + set(value); + return *this; + } + +private: + void mark_clean() + { + _data.mark_clean(); + } + + void forward(property_base_t* next_prop) + { + property_t<data_t>* prop_ptr = dynamic_cast<property_t<data_t>*>(next_prop); + if (prop_ptr == nullptr) { + throw uhd::type_error(std::string("Unable to cast property ") + + next_prop->get_id() + " to the same type as property " + + get_id()); + } + + prop_ptr->set(get()); + } + + bool is_type_equal(property_base_t* other_prop) const + { + return dynamic_cast<property_t<data_t>*>(other_prop) != nullptr; + } + + dirty_tracked<data_t> _data; +}; // class property_t + +}} /* namespace uhd::rfnoc */ + +#include <uhd/rfnoc/property.ipp> + +#endif /* INCLUDED_LIBUHD_PROPERTY_HPP */ + diff --git a/host/include/uhd/rfnoc/property.ipp b/host/include/uhd/rfnoc/property.ipp new file mode 100644 index 000000000..101aa5d7e --- /dev/null +++ b/host/include/uhd/rfnoc/property.ipp @@ -0,0 +1,25 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_PROPERTY_IPP +#define INCLUDED_LIBUHD_PROPERTY_IPP + +template <typename data_t> +uhd::rfnoc::property_t<data_t>::property_t(const std::string& id, data_t&& data, const uhd::rfnoc::res_source_info& source_info) + : uhd::rfnoc::property_base_t(id, source_info), _data(std::forward<data_t>(data)) +{ + // nop +} + +template <typename data_t> +uhd::rfnoc::property_t<data_t>::property_t(const std::string& id, const data_t& data, const uhd::rfnoc::res_source_info& source_info) + : uhd::rfnoc::property_base_t(id, source_info), _data(data) +{ + // nop +} + +#endif /* INCLUDED_LIBUHD_PROPERTY_IPP */ + diff --git a/host/include/uhd/rfnoc/res_source_info.hpp b/host/include/uhd/rfnoc/res_source_info.hpp new file mode 100644 index 000000000..37371009d --- /dev/null +++ b/host/include/uhd/rfnoc/res_source_info.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RES_SRC_INFO_HPP +#define INCLUDED_LIBUHD_RES_SRC_INFO_HPP + +#include <uhd/exception.hpp> +#include <cstdint> +#include <string> + +namespace uhd { namespace rfnoc { + +/*! Describes the source of a particular resource (property or action) +*/ +struct res_source_info { + /*! Source type + */ + enum source_t { + USER, ///< The user API sources this resource + INPUT_EDGE, ///< An input edge sources this resource + OUTPUT_EDGE, ///< An input edge sources this resource + FRAMEWORK ///< This is a special resource, only accessed by the framework + }; + + // No default ctor: The source type must be specified + res_source_info() = delete; + + res_source_info(source_t source_type, size_t instance_ = 0) + : type(source_type), instance(instance_) + { + // nop + } + + //! The type of source (user or edge) + source_t type; + + //! The instance of the source. For resource that is sourced by a edge, it + // corresponds to the port number + size_t instance = 0; + + bool operator==(const res_source_info& rhs) const + { + return rhs.type == type && rhs.instance == instance; + } + + //! Returns a string representation of the source + std::string to_string() const + { + const std::string type_repr = type == USER ? "USER" : + type == INPUT_EDGE ? "INPUT_EDGE" : + type == OUTPUT_EDGE ? "OUTPUT_EDGE" : "INVALID"; + return type_repr + ":" + std::to_string(instance); + } + + /*! Convenience function to invert the type value if it's an edge. + * + * - Will assert that the edge is actually either INPUT_EDGE or OUTPUT_EDGE + * - Then, returns the opposite of what it was before + * + * \throws uhd::assertion_error if \p edge_direction is neither INPUT_EDGE + * nor OUTPUT_EDGE + */ + static source_t invert_edge(const source_t edge_direction) + { + UHD_ASSERT_THROW(edge_direction == INPUT_EDGE || edge_direction == OUTPUT_EDGE); + return edge_direction == INPUT_EDGE ? OUTPUT_EDGE : INPUT_EDGE; + } +}; + +}} /* namespace uhd::rfnoc */ + +namespace std { +template <> +struct hash<uhd::rfnoc::res_source_info> +{ + size_t operator()(const uhd::rfnoc::res_source_info& src_info) const + { + const size_t hash_type = std::hash<size_t>{}(src_info.type); + const size_t hash_inst = std::hash<size_t>{}(src_info.instance); + return hash_type ^ (hash_inst << 1); + } +}; +} // namespace std + +#endif /* INCLUDED_LIBUHD_RES_SRC_INFO_HPP */ + diff --git a/host/lib/include/uhdlib/rfnoc/node_accessor.hpp b/host/lib/include/uhdlib/rfnoc/node_accessor.hpp new file mode 100644 index 000000000..26e6a5607 --- /dev/null +++ b/host/lib/include/uhdlib/rfnoc/node_accessor.hpp @@ -0,0 +1,84 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_NODE_ACCESSOR_HPP +#define INCLUDED_LIBUHD_NODE_ACCESSOR_HPP + +#include <uhd/rfnoc/node.hpp> +#include <functional> + +namespace uhd { namespace rfnoc { + +//! Special class which may access nodes +// +// For the sake of property resolution, we require access to certain private +// members of nodes. Instead of giving the entire graph +// access to everything, we create this accessor class which is not available +// in the public API. +class node_accessor_t +{ +public: + using prop_ptrs_t = node_t::prop_ptrs_t; + + /*! Initializes the properties of a node. See node_t::init_props() for + * details. + */ + void init_props(node_t* node) + { + node->init_props(); + } + + /*! Does a local resolution of properties on \p node. + * + * See node_t::resolve_props for details. + */ + void resolve_props(node_t* node) + { + node->resolve_props(); + } + + /*! Returns a filtered list of properties. + * + * The return list contains all properties that match a given predicate. + */ + template <typename PredicateType> + node_t::prop_ptrs_t filter_props(node_t* node, PredicateType&& predicate) + { + return node->filter_props(std::forward<PredicateType>(predicate)); + } + + /*! Mark all properties on this node as clean + * + * See node_t::clean_props() for details. + */ + void clean_props(node_t* node) + { + node->clean_props(); + } + + /*! Set a resolver callback for the node + * + * See node_t::set_resolve_all_callback() for details. + */ + void set_resolve_all_callback(node_t* node, node_t::resolve_callback_t&& resolver) + { + node->set_resolve_all_callback(std::move(resolver)); + } + + /*! Forward an edge property to \p dst_node + * + * See node_t::forward_edge_property() for details. + */ + void forward_edge_property(node_t* dst_node, property_base_t* incoming_prop) + { + dst_node->forward_edge_property(incoming_prop); + } +}; + + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_NODE_ACCESSOR_HPP */ diff --git a/host/lib/include/uhdlib/rfnoc/prop_accessor.hpp b/host/lib/include/uhdlib/rfnoc/prop_accessor.hpp new file mode 100644 index 000000000..a62f54620 --- /dev/null +++ b/host/lib/include/uhdlib/rfnoc/prop_accessor.hpp @@ -0,0 +1,96 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_PROP_ACCESSOR_HPP +#define INCLUDED_LIBUHD_PROP_ACCESSOR_HPP + +#include <uhd/rfnoc/property.hpp> +#include <uhd/utils/scope_exit.hpp> +#include <functional> + +namespace uhd { namespace rfnoc { + +//! Special class which may access properties +// +// For the sake of property resolution, we require access to certain private +// members of properties. Instead of giving the entire graph access to +// everything, we create this accessor class which is not available +// in the public API. +class prop_accessor_t +{ +public: + //! Clear the dirty bit on a property + void mark_clean(property_base_t& prop) + { + prop.mark_clean(); + } + + //! Set the access mode on a property + void set_access(property_base_t& prop, const property_base_t::access_t access) + { + prop._access_mode = access; + } + + //! Set the access mode on a property + void set_access(property_base_t* prop, const property_base_t::access_t access) + { + prop->_access_mode = access; + } + + //! RAII-Style access mode setter + // + // This will return an object which will set the access mode on a property + // only for the duration of its own lifetime. Use this in situations where + // you want to guarantee a certain read-write mode of a property, even when + // an exception is thrown. + // + // \param prop A reference to the property + // \param access The temporary access mode which will be set as long as the + // scope_exit object is alive + // \param default_access The access mode which will be set once the + // scope_exit object will be destroyed + uhd::utils::scope_exit::uptr get_scoped_prop_access(property_base_t& prop, + property_base_t::access_t access, + property_base_t::access_t default_access = property_base_t::RO) + { + prop._access_mode = access; + return uhd::utils::scope_exit::make( + [&prop, default_access]() { prop._access_mode = default_access; }); + } + + /*! Forward the value from \p source to \p dst + * + * Note: This method will grant temporary write access to the destination + * property! + * If \p safe is set to true, it'll only allow RWLOCKED forwarding, i.e., + * the new value cannot be different. + * + * \throws uhd::type_error if types mismatch + */ + template <bool safe> + void forward(property_base_t* source, property_base_t* dst) + { + const auto w_access_type = (safe && dst->is_dirty()) ? property_base_t::RWLOCKED + : property_base_t::RW; + auto read_access = get_scoped_prop_access( + *source, property_base_t::RO, source->get_access_mode()); + auto write_access = + get_scoped_prop_access(*dst, w_access_type, dst->get_access_mode()); + source->forward(dst); + } + + /*! Returns true if \p source and \p dst are of the same type + */ + bool are_compatible(property_base_t* source, property_base_t* dst) + { + return source->is_type_equal(dst); + } +}; + + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_PROP_ACCESSOR_HPP */ diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index af58e8953..dce1f286b 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -21,6 +21,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rate_node_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_stream_terminator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/scalar_node_ctrl.cpp diff --git a/host/lib/rfnoc/node.cpp b/host/lib/rfnoc/node.cpp new file mode 100644 index 000000000..68ba5e283 --- /dev/null +++ b/host/lib/rfnoc/node.cpp @@ -0,0 +1,119 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/rfnoc/node.hpp> +#include <uhdlib/rfnoc/prop_accessor.hpp> +#include <boost/format.hpp> + +using namespace uhd::rfnoc; + + +std::string node_t::get_unique_id() const +{ + // TODO return something better + return str(boost::format("%08X") % this); +} + +std::vector<std::string> node_t::get_property_ids() const +{ + std::lock_guard<std::mutex> _l(_prop_mutex); + if (_props.count(res_source_info::USER) == 0) { + return {}; + } + + auto& user_props = _props.at(res_source_info::USER); + // TODO use a range here, we're not savages + std::vector<std::string> return_value(user_props.size()); + for (size_t i = 0; i < user_props.size(); ++i) { + return_value[i] = user_props[i]->get_id(); + } + + return return_value; +} + +/*** Protected methods *******************************************************/ +void node_t::register_property(property_base_t* prop) +{ + std::lock_guard<std::mutex> _l(_prop_mutex); + + const auto src_type = prop->get_src_info().type; + auto prop_already_registered = [prop](const property_base_t* existing_prop) { + return (prop->get_src_info() == existing_prop->get_src_info() + && prop->get_id() == existing_prop->get_id()) + || (prop == existing_prop); + }; + + // If the map is empty for this source type, create an empty vector + if (_props.count(src_type) == 0) { + _props[src_type] = {}; + } + + // Now go and make sure no one has registered this property before + auto& props = _props[src_type]; + for (const auto& existing_prop : props) { + if (prop_already_registered(existing_prop)) { + throw uhd::runtime_error("Attempting to double-register prop"); + } + } + + _props[src_type].push_back(prop); +} + +void node_t::add_property_resolver(std::set<property_base_t*>&& inputs, + std::set<property_base_t*>&& outputs, + resolver_fn_t&& resolver_fn) +{ + std::lock_guard<std::mutex> _l(_prop_mutex); + + // Sanity check: All inputs and outputs must be registered properties + auto prop_is_registered = [this](property_base_t* prop) -> bool { + return bool(this->_find_property(prop->get_src_info(), prop->get_id())); + }; + for (const auto& prop : inputs) { + if (!prop_is_registered(prop)) { + throw uhd::runtime_error( + std::string("Cannot add property resolver, input property ") + + prop->get_id() + " is not registered!"); + } + } + for (const auto& prop : outputs) { + if (!prop_is_registered(prop)) { + throw uhd::runtime_error( + std::string("Cannot add property resolver, output property ") + + prop->get_id() + " is not registered!"); + } + } + + // 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))); +} + +/*** Private methods *********************************************************/ +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) { + continue; + } + for (const auto& prop : type_prop_pair.second) { + if (prop->get_id() == id && prop->get_src_info() == src_info) { + return prop; + } + } + } + + return nullptr; +} + +uhd::utils::scope_exit::uptr node_t::_request_property_access( + property_base_t* prop, property_base_t::access_t access) const +{ + return prop_accessor_t{}.get_scoped_prop_access(*prop, access); +} + diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index e4255196b..eab27833b 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -53,6 +53,7 @@ set(test_sources expert_test.cpp fe_conn_test.cpp rfnoc_chdr_test.cpp + rfnoc_node_test.cpp ) set(benchmark_sources @@ -72,6 +73,7 @@ if(ENABLE_RFNOC) rate_node_test.cpp stream_sig_test.cpp tick_node_test.cpp + rfnoc_property_test.cpp ) endif(ENABLE_RFNOC) diff --git a/host/tests/rfnoc_node_test.cpp b/host/tests/rfnoc_node_test.cpp new file mode 100644 index 000000000..046f8ab33 --- /dev/null +++ b/host/tests/rfnoc_node_test.cpp @@ -0,0 +1,108 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/rfnoc/node.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +class test_node_t : public node_t +{ +public: + test_node_t(size_t num_inputs, size_t num_outputs) + : _num_input_ports(num_inputs), _num_output_ports(num_outputs) + { + register_property(&_double_prop_user); + register_property(&_double_prop_in); + register_property(&_double_prop_out); + + // A property with a simple 1:1 dependency + add_property_resolver( + {&_double_prop_user}, {&_double_prop_out}, []() { std::cout << "foo" << std::endl; }); + } + + //! Register a property for the second time, with the goal of triggering an + // exception + void double_register() + { + register_property(&_double_prop_user); + } + + //! Register an identical property for the first time, with the goal of + //triggering an exception + void double_register_input() + { + property_t<double> double_prop_in{"double_prop", 0.0, {res_source_info::INPUT_EDGE, 0}}; + register_property(&double_prop_in); + } + + //! This should throw an error because the property in the output isn't + // registered + void add_unregistered_resolver_in() { + property_t<double> temp{"temp", 0.0, {res_source_info::INPUT_EDGE, 5}}; + add_property_resolver( + {&temp}, {}, []() { std::cout << "foo" << std::endl; }); + } + + //! This should throw an error because the property in the output isn't + // registered + void add_unregistered_resolver_out() { + property_t<double> temp{"temp", 0.0, {res_source_info::INPUT_EDGE, 5}}; + add_property_resolver( + {&_double_prop_user}, {&temp}, []() { std::cout << "foo" << std::endl; }); + } + + size_t get_num_input_ports() const { return _num_input_ports; } + size_t get_num_output_ports() const { return _num_output_ports; } + +private: + property_t<double> _double_prop_user{"double_prop", 0.0, {res_source_info::USER}}; + property_t<double> _double_prop_in{"double_prop", 0.0, {res_source_info::INPUT_EDGE, 0}}; + property_t<double> _double_prop_out{ + "double_prop", 0.0, {res_source_info::OUTPUT_EDGE, 1}}; + + const size_t _num_input_ports; + const size_t _num_output_ports; +}; + +BOOST_AUTO_TEST_CASE(test_node_prop_access) +{ + test_node_t TN1(2, 3); + test_node_t TN2(1, 1); + + BOOST_REQUIRE_THROW(TN2.double_register(), uhd::runtime_error); + BOOST_REQUIRE_THROW(TN2.double_register_input(), uhd::runtime_error); + BOOST_REQUIRE_THROW(TN2.add_unregistered_resolver_in(), uhd::runtime_error); + BOOST_REQUIRE_THROW(TN2.add_unregistered_resolver_out(), uhd::runtime_error); + + BOOST_CHECK_EQUAL(TN1.get_num_input_ports(), 2); + BOOST_CHECK_EQUAL(TN1.get_num_output_ports(), 3); + + std::cout << TN1.get_unique_id() << std::endl; + BOOST_CHECK(TN1.get_unique_id() != TN2.get_unique_id()); + + auto user_prop_ids = TN1.get_property_ids(); + BOOST_REQUIRE_EQUAL(user_prop_ids.size(), 1); + BOOST_CHECK_EQUAL(user_prop_ids[0], "double_prop"); + + BOOST_REQUIRE_THROW(TN1.get_property<int>("nonexistant_prop"), uhd::lookup_error); + // If this next test fails, RTTI is not available. There might be cases when + // that's expected, and when we encounter those we'll reconsider the test. + BOOST_REQUIRE_THROW(TN1.get_property<int>("double_prop"), uhd::type_error); + BOOST_REQUIRE_THROW(TN1.get_property<double>("double_prop", 5), uhd::lookup_error); + + BOOST_CHECK_EQUAL(TN1.get_property<double>("double_prop"), 0.0); + + BOOST_REQUIRE_THROW(TN1.set_property<int>("nonexistant_prop", 5), uhd::lookup_error); + // If this next test fails, RTTI is not available. There might be cases when + // that's expected, and when we encounter those we'll reconsider the test. + BOOST_REQUIRE_THROW(TN1.set_property<int>("double_prop", 5), uhd::type_error); + + TN1.set_property<double>("double_prop", 4.2); + BOOST_CHECK_EQUAL(TN1.get_property<double>("double_prop"), 4.2); +} + diff --git a/host/tests/rfnoc_property_test.cpp b/host/tests/rfnoc_property_test.cpp new file mode 100644 index 000000000..13ba1d4eb --- /dev/null +++ b/host/tests/rfnoc_property_test.cpp @@ -0,0 +1,152 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/rfnoc/property.hpp> +#include <uhd/rfnoc/dirtifier.hpp> +#include <uhdlib/rfnoc/prop_accessor.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_res_source_info) +{ + res_source_info S1(res_source_info::USER); + + BOOST_CHECK_EQUAL(S1.type, res_source_info::USER); + BOOST_CHECK_EQUAL(S1.instance, 0); + BOOST_CHECK_EQUAL(S1.to_string(), "USER:0"); + + res_source_info S2{res_source_info::USER, 5}; + BOOST_CHECK_EQUAL(S2.type, res_source_info::USER); + BOOST_CHECK_EQUAL(S2.instance, 5); + + // Check initializer + auto src_info_printer = [](res_source_info rsi){ + std::cout << static_cast<int>(rsi.type) << "::" << rsi.instance << std::endl; + }; + src_info_printer({res_source_info::OUTPUT_EDGE, 5}); + src_info_printer({res_source_info::OUTPUT_EDGE}); + + res_source_info S3{res_source_info::USER, 5}; + BOOST_CHECK(S2 == S3); +} + +BOOST_AUTO_TEST_CASE(test_get_set) +{ + constexpr int prop_i_val = 5; + // This is a legitimate way to initialize a property: + property_t<int> prop_i{"int_prop", prop_i_val, {res_source_info::USER, 2}}; + BOOST_CHECK_EQUAL(prop_i.get_src_info().instance, 2); + BOOST_CHECK_EQUAL(prop_i.get_src_info().type, res_source_info::USER); + prop_accessor_t prop_accessor; + + prop_accessor.mark_clean(prop_i); + auto access_lock = prop_accessor.get_scoped_prop_access(prop_i, property_base_t::RW); + + BOOST_CHECK(!prop_i.is_dirty()); + BOOST_CHECK_EQUAL(prop_i.get_id(), "int_prop"); + BOOST_CHECK_EQUAL(prop_i.get_src_info().type, res_source_info::USER); + + BOOST_CHECK_EQUAL(prop_i.get(), prop_i_val); + BOOST_CHECK_EQUAL(int(prop_i), prop_i_val); + BOOST_CHECK(prop_i == 5); + + prop_i.set(42); + BOOST_CHECK_EQUAL(prop_i.get(), 42); + BOOST_CHECK(prop_i.is_dirty()); + prop_accessor.mark_clean(prop_i); + BOOST_CHECK(!prop_i.is_dirty()); + prop_i = 23; + BOOST_CHECK_EQUAL(prop_i.get(), 23); + BOOST_CHECK(prop_i.is_dirty()); +} + +BOOST_AUTO_TEST_CASE(test_lock) +{ + prop_accessor_t prop_accessor; + constexpr int prop_i_val = 5; + property_t<int> prop_i{"int_prop", prop_i_val, {res_source_info::USER}}; + prop_accessor.mark_clean(prop_i); + prop_accessor.set_access(prop_i, property_base_t::RWLOCKED); + prop_i.set(5); + BOOST_REQUIRE_THROW(prop_i.set(42), uhd::resolve_error); +} + +BOOST_AUTO_TEST_CASE(test_access) +{ + constexpr int prop_i_val = 5; + property_t<int> prop_i{"int_prop", prop_i_val, {res_source_info::USER}}; + prop_accessor_t prop_accessor; + + prop_accessor.mark_clean(prop_i); + BOOST_REQUIRE_THROW(prop_i.set(23), uhd::access_error); + prop_accessor.set_access(prop_i, property_base_t::RO); + BOOST_CHECK_EQUAL(prop_i.get(), prop_i_val); + BOOST_REQUIRE_THROW(prop_i.set(23), uhd::access_error); + prop_accessor.set_access(prop_i, property_base_t::RW); + prop_i.set(23); + BOOST_CHECK_EQUAL(prop_i.get(), 23); + prop_accessor.set_access(prop_i, property_base_t::NONE); + + // Now test the scoped access mode + { + auto access_lock = prop_accessor.get_scoped_prop_access(prop_i, property_base_t::RW, property_base_t::NONE); + prop_i.set(42); + BOOST_CHECK_EQUAL(prop_i.get(), 42); + } + BOOST_REQUIRE_THROW(prop_i.get(), uhd::access_error); + BOOST_REQUIRE_THROW(prop_i.set(23), uhd::access_error); + + // Now test a different default access mode + { + auto access_lock = prop_accessor.get_scoped_prop_access( + prop_i, property_base_t::RW, property_base_t::RO); + prop_i.set(42); + } + BOOST_CHECK_EQUAL(prop_i.get(), 42); + // The prop is still in RO mode now! + BOOST_CHECK_EQUAL(prop_i.get_access_mode(), property_base_t::RO); + + res_source_info new_src_info{res_source_info::INPUT_EDGE, 1}; + auto cloned_prop = prop_i.clone(new_src_info); + BOOST_CHECK(cloned_prop->get_src_info() == new_src_info); + BOOST_CHECK(prop_i.equal(cloned_prop.get())); +} + +BOOST_AUTO_TEST_CASE(test_forward) +{ + prop_accessor_t prop_accessor; + property_t<int> prop_i1{"int_prop", 5, {res_source_info::USER}}; + property_t<int> prop_i2{"int_prop", 0, {res_source_info::USER}}; + property_t<double> prop_d{"double_prop", 0.0, {res_source_info::USER}}; + prop_accessor.mark_clean(prop_i1); + prop_accessor.mark_clean(prop_i2); + + BOOST_CHECK(prop_accessor.are_compatible(&prop_i1, &prop_i2)); + prop_accessor.forward<false>(&prop_i1, &prop_i2); + prop_accessor.set_access(prop_i1, property_base_t::RO); + prop_accessor.set_access(prop_i2, property_base_t::RW); + BOOST_CHECK_EQUAL(prop_i2.get(), prop_i1.get()); + BOOST_CHECK(!prop_i1.is_dirty()); + BOOST_CHECK(prop_i2.is_dirty()); + BOOST_CHECK(!prop_accessor.are_compatible(&prop_i1, &prop_d)); + BOOST_REQUIRE_THROW(prop_accessor.forward<false>(&prop_i1, &prop_d), uhd::type_error); +} + +BOOST_AUTO_TEST_CASE(test_dirtifier) +{ + prop_accessor_t prop_accessor{}; + dirtifier_t dirtifier; + BOOST_CHECK(dirtifier.is_dirty()); + prop_accessor.mark_clean(dirtifier); + BOOST_CHECK(dirtifier.is_dirty()); + property_t<int> prop_i{"int_prop", 5, {res_source_info::USER}}; + BOOST_REQUIRE_THROW(prop_accessor.forward<false>(&dirtifier, &prop_i), uhd::type_error); + BOOST_REQUIRE_THROW(prop_accessor.forward<false>(&prop_i, &dirtifier), uhd::type_error); + BOOST_CHECK(!prop_accessor.are_compatible(&prop_i, &dirtifier)); + BOOST_CHECK(!prop_accessor.are_compatible(&dirtifier, &prop_i)); +} |