aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2019-03-24 16:54:11 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:13 -0800
commitefb1d5a4729ea892ea03b8d0265aae9e8fadfff1 (patch)
tree23cd4dbf85c635d381aaf7b0f6e9bd15eeca08fc /host
parent44f9bca2c5b561fa3f5f08d0d616c91d2142cbf9 (diff)
downloaduhd-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.txt3
-rw-r--r--host/include/uhd/rfnoc/dirtifier.hpp54
-rw-r--r--host/include/uhd/rfnoc/node.hpp206
-rw-r--r--host/include/uhd/rfnoc/node.ipp78
-rw-r--r--host/include/uhd/rfnoc/property.hpp257
-rw-r--r--host/include/uhd/rfnoc/property.ipp25
-rw-r--r--host/include/uhd/rfnoc/res_source_info.hpp89
-rw-r--r--host/lib/include/uhdlib/rfnoc/node_accessor.hpp84
-rw-r--r--host/lib/include/uhdlib/rfnoc/prop_accessor.hpp96
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/node.cpp119
-rw-r--r--host/tests/CMakeLists.txt2
-rw-r--r--host/tests/rfnoc_node_test.cpp108
-rw-r--r--host/tests/rfnoc_property_test.cpp152
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));
+}