aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--host/include/uhd/rfnoc/CMakeLists.txt1
-rw-r--r--host/include/uhd/rfnoc/mb_controller.hpp352
-rw-r--r--host/include/uhd/rfnoc/noc_block_base.hpp19
-rw-r--r--host/include/uhd/rfnoc/noc_block_make_args.hpp7
-rw-r--r--host/include/uhd/rfnoc/registry.hpp25
-rw-r--r--host/include/uhd/rfnoc_graph.hpp11
-rw-r--r--host/lib/include/uhdlib/rfnoc/factory.hpp5
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/mb_controller.cpp73
-rw-r--r--host/lib/rfnoc/noc_block_base.cpp6
-rw-r--r--host/lib/rfnoc/registry_factory.cpp40
-rw-r--r--host/lib/rfnoc/rfnoc_graph.cpp13
-rw-r--r--host/tests/CMakeLists.txt1
-rw-r--r--host/tests/mb_controller_test.cpp155
14 files changed, 708 insertions, 1 deletions
diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt
index c8308953f..6251a555d 100644
--- a/host/include/uhd/rfnoc/CMakeLists.txt
+++ b/host/include/uhd/rfnoc/CMakeLists.txt
@@ -18,6 +18,7 @@ if(ENABLE_RFNOC)
dirtifier.hpp
graph.hpp
graph_edge.hpp
+ mb_controller.hpp
noc_block_make_args.hpp
node_ctrl_base.hpp
node_ctrl_base.ipp
diff --git a/host/include/uhd/rfnoc/mb_controller.hpp b/host/include/uhd/rfnoc/mb_controller.hpp
new file mode 100644
index 000000000..a1c0f21aa
--- /dev/null
+++ b/host/include/uhd/rfnoc/mb_controller.hpp
@@ -0,0 +1,352 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_MB_CONTROLLER_HPP
+#define INCLUDED_LIBUHD_MB_CONTROLLER_HPP
+
+#include <uhd/config.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <uhd/usrp/mboard_eeprom.hpp>
+#include <uhd/utils/noncopyable.hpp>
+#include <unordered_map>
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace uhd { namespace rfnoc {
+
+/*! A default block controller for blocks that can't be found in the registry
+ */
+class UHD_API mb_controller : public uhd::noncopyable
+{
+public:
+ using sptr = std::shared_ptr<mb_controller>;
+
+ virtual ~mb_controller() {}
+
+ /**************************************************************************
+ * Timebase API
+ *************************************************************************/
+ /*! Interface to interact with timekeepers
+ *
+ * Timekeepers are objects separate from RFNoC blocks. This class is meant
+ * to be subclassed by motherboards implementing it.
+ */
+ class UHD_API timekeeper
+ {
+ public:
+ using sptr = std::shared_ptr<timekeeper>;
+ using write_period_fn_t = std::function<void(uint64_t)>;
+
+ timekeeper();
+
+ virtual ~timekeeper() {}
+
+ /*! Return the current time as a time spec
+ *
+ * Note that there is no control over when this command gets executed,
+ * it will read the time "as soon as possible", and then return that
+ * value. Calling this on two synchronized clocks sequentially will
+ * definitely return two different values.
+ *
+ * \returns the current time
+ */
+ uhd::time_spec_t get_time_now(void);
+
+ /*! Return the current time as a tick count
+ *
+ * See also get_time_now().
+ *
+ * \returns the current time
+ */
+ virtual uint64_t get_ticks_now() = 0;
+
+ /*! Return the time from the last PPS as a time spec
+ *
+ * Note that there is no control over when this command gets executed,
+ * it will read the time "as soon as possible", and then return that
+ * value. Calling this on two synchronized clocks sequentially will
+ * definitely return two different values.
+ */
+ uhd::time_spec_t get_time_last_pps(void);
+
+ /*! Return the time from the last PPS as a tick count
+ *
+ * See also get_time_last_pps()
+ */
+ virtual uint64_t get_ticks_last_pps() = 0;
+
+ /*! Set the time "now" from a time spec
+ */
+ void set_time_now(const uhd::time_spec_t &time);
+
+ /*! Set the ticks "now"
+ */
+ virtual void set_ticks_now(const uint64_t ticks) = 0;
+
+ /*! Set the time at next PPS from a time spec
+ */
+ void set_time_next_pps(const uhd::time_spec_t &time);
+
+ /*! Set the ticks at next PPS
+ */
+ virtual void set_ticks_next_pps(const uint64_t ticks) = 0;
+
+ /*! Return the current tick rate
+ */
+ double get_tick_rate() { return _tick_rate; }
+
+ protected:
+ /*! Set the tick rate
+ *
+ * This doesn't change the input clock to the timekeeper, but does two
+ * things:
+ * - Update the local value of the tick rate, so the time-spec based API
+ * calls work
+ * - Convert the tick rate to a period and call set_period()
+ */
+ void set_tick_rate(const double rate);
+
+ /*! Set the time period as a 64-bit Q32 value
+ *
+ * \param period_ns The period as nanoseconds per tick, in Q32 format
+ */
+ virtual void set_period(const uint64_t period_ns) = 0;
+
+ private:
+ //! Ticks/Second
+ double _tick_rate = 1.0;
+ };
+
+ //! Returns the number of timekeepers, which equals the number of timebases
+ // on this device.
+ size_t get_num_timekeepers() const;
+
+ //! Return a reference to the \p tk_idx-th timekeeper on this motherboard
+ //
+ // \throws uhd::index_error if \p tk_idx is not valid
+ timekeeper::sptr get_timekeeper(const size_t tk_idx) const;
+
+ /**************************************************************************
+ * Motherboard Control
+ *************************************************************************/
+ /*! Get canonical name for this USRP motherboard
+ *
+ * \return a string representing the name
+ */
+ virtual std::string get_mboard_name() const = 0;
+
+ /*! Set the time source for the USRP device
+ *
+ * This sets the method of time synchronization, typically a pulse per
+ * second signal. In order to time-align multiple USRPs, it is necessary to
+ * connect all of them to a common reference and provide them with the same
+ * time source.
+ * Typical values for \p source are 'internal', 'external'. Refer to the
+ * specific device manual for a full list of options.
+ *
+ * If the value for for \p source is not available for this device, it will
+ * throw an exception. Calling get_time_sources() will return a valid list
+ * of options for this method.
+ *
+ * Side effects: Some devices only support certain combinations of time and
+ * clock source. It is possible that the underlying device implementation
+ * will change the clock source when the time source changes and vice versa.
+ * Reading back the current values of clock and time source using
+ * get_clock_source() and get_time_source() is the only certain way of
+ * knowing which clock and time source are currently selected.
+ *
+ * This function does not force a re-initialization of the underlying
+ * hardware when the value does not change. Consider the following snippet:
+ * ~~~{.cpp}
+ * auto usrp = uhd::usrp::multi_usrp::make(device_args);
+ * // This may or may not cause the hardware to reconfigure, depending on
+ * // the default state of the device
+ * usrp->set_time_source("internal");
+ * // Now, the time source is definitely set to "internal"!
+ * // The next call probably won't do anything but will return immediately,
+ * // because the time source was already set to "internal"
+ * usrp->set_time_source("internal");
+ * // The time source is still guaranteed to be "internal" at this point
+ * ~~~
+ *
+ * See also:
+ * - set_clock_source()
+ * - set_sync_source()
+ *
+ * \param source a string representing the time source
+ * \throws uhd::value_error if \p source is an invalid option
+ */
+ virtual void set_time_source(const std::string& source) = 0;
+
+ /*! Get the currently set time source
+ *
+ * \return the string representing the time source
+ */
+ virtual std::string get_time_source() const = 0;
+
+ /*!
+ * Get a list of possible time sources.
+ * \return a vector of strings for possible settings
+ */
+ virtual std::vector<std::string> get_time_sources() const = 0;
+
+ /*! Set the clock source for the USRP device
+ *
+ * This sets the source of the frequency reference, typically a 10 MHz
+ * signal. In order to frequency-align multiple USRPs, it is necessary to
+ * connect all of them to a common reference and provide them with the same
+ * clock source.
+ * Typical values for \p source are 'internal', 'external'. Refer to the
+ * specific device manual for a full list of options.
+ *
+ * If the value for for \p source is not available for this device, it will
+ * throw an exception. Calling get_clock_sources() will return a valid list
+ * of options for this method.
+ *
+ * Side effects: Some devices only support certain combinations of time and
+ * clock source. It is possible that the underlying device implementation
+ * will change the time source when the clock source changes and vice versa.
+ * Reading back the current values of clock and time source using
+ * get_clock_source() and get_time_source() is the only certain way of
+ * knowing which clock and time source are currently selected.
+ *
+ * This function does not force a re-initialization of the underlying
+ * hardware when the value does not change. Consider the following snippet:
+ * ~~~{.cpp}
+ * auto usrp = uhd::usrp::multi_usrp::make(device_args);
+ * // This may or may not cause the hardware to reconfigure, depending on
+ * // the default state of the device
+ * usrp->set_clock_source("internal");
+ * // Now, the clock source is definitely set to "internal"!
+ * // The next call probably won't do anything but will return immediately,
+ * // because the clock source was already set to "internal"
+ * usrp->set_clock_source("internal");
+ * // The clock source is still guaranteed to be "internal" at this point
+ * ~~~
+ *
+ * See also:
+ * - set_time_source()
+ * - set_sync_source()
+ *
+ * \param source a string representing the time source
+ * \throws uhd::value_error if \p source is an invalid option
+ */
+ virtual void set_clock_source(const std::string& source) = 0;
+
+ /*! Get the currently set clock source
+ *
+ * \return the string representing the clock source
+ */
+ virtual std::string get_clock_source() const = 0;
+
+ /*! Get a list of possible clock sources
+ *
+ * \return a vector of strings for possible settings
+ */
+ virtual std::vector<std::string> get_clock_sources() const = 0;
+
+ /*! Set the reference/synchronization sources for the USRP device
+ *
+ * This is a shorthand for calling
+ * `set_sync_source(device_addr_t("clock_source=$CLOCK_SOURCE,time_source=$TIME_SOURCE"))`
+ *
+ * \param clock_source A string representing the clock source
+ * \param time_source A string representing the time source
+ * \throws uhd::value_error if the sources don't actually exist
+ */
+ virtual void set_sync_source(
+ const std::string& clock_source, const std::string& time_source) = 0;
+
+ /*! Set the reference/synchronization sources for the USRP device
+ *
+ * Typically, this will set both clock and time source in a single call. For
+ * some USRPs, this may be significantly faster than calling
+ * set_time_source() and set_clock_source() individually.
+ *
+ * Example:
+ * ~~~{.cpp}
+ * auto usrp = uhd::usrp::multi_usrp::make("");
+ * usrp->set_sync_source(
+ * device_addr_t("clock_source=external,time_source=external"));
+ * ~~~
+ *
+ * This function does not force a re-initialization of the underlying
+ * hardware when the value does not change. See also set_time_source() and
+ * set_clock_source() for more details.
+ *
+ * \param sync_source A dictionary representing the various source settings.
+ * \throws uhd::value_error if the sources don't actually exist or if the
+ * combination of clock and time source is invalid.
+ */
+ virtual void set_sync_source(const uhd::device_addr_t& sync_source) = 0;
+
+ /*! Get the currently set sync source
+ *
+ * \return the dictionary representing the sync source settings
+ */
+ virtual uhd::device_addr_t get_sync_source() const = 0;
+
+ /*! Get a list of available sync sources
+ *
+ * \return the dictionary representing the sync source settings
+ */
+ virtual std::vector<uhd::device_addr_t> get_sync_sources() = 0;
+
+ /*! Send the clock source to an output connector
+ *
+ * This call is only applicable on devices with reference outputs.
+ * By default, the reference output will be enabled for ease of use.
+ * This call may be used to enable or disable the output.
+ * \param enb true to output the clock source.
+ */
+ virtual void set_clock_source_out(const bool enb) = 0;
+
+ /*! Send the time source to an output connector
+ *
+ * This call is only applicable on devices with PPS outputs.
+ * By default, the PPS output will be enabled for ease of use.
+ * This call may be used to enable or disable the output.
+ * \param enb true to output the time source.
+ */
+ virtual void set_time_source_out(const bool enb) = 0;
+
+ /*! Get a motherboard sensor value
+ *
+ * \param name the name of the sensor
+ * \return a sensor value object
+ */
+ virtual uhd::sensor_value_t get_sensor(const std::string& name) = 0;
+
+ /*! Get a list of possible motherboard sensor names
+ *
+ * \return a vector of sensor names
+ */
+ virtual std::vector<std::string> get_sensor_names() = 0;
+
+ /*! Return the motherboard EEPROM data
+ */
+ virtual uhd::usrp::mboard_eeprom_t get_eeprom() = 0;
+
+protected:
+ /*! Stash away a timekeeper. This needs to be called by the implementer of
+ * mb_controller.
+ */
+ void register_timekeeper(const size_t idx, timekeeper::sptr tk);
+
+private:
+ /**************************************************************************
+ * Attributes
+ *************************************************************************/
+ std::unordered_map<size_t, timekeeper::sptr> _timekeepers;
+};
+
+}} // namespace uhd::rfnoc
+
+#endif /* INCLUDED_LIBUHD_MB_CONTROLLER_HPP */
+
diff --git a/host/include/uhd/rfnoc/noc_block_base.hpp b/host/include/uhd/rfnoc/noc_block_base.hpp
index 0959e5bdd..4f3f2a2bc 100644
--- a/host/include/uhd/rfnoc/noc_block_base.hpp
+++ b/host/include/uhd/rfnoc/noc_block_base.hpp
@@ -23,6 +23,7 @@
namespace uhd { namespace rfnoc {
class clock_iface;
+class mb_controller;
/*!
* The primary interface to a NoC block in the FPGA
@@ -101,6 +102,19 @@ protected:
*/
void set_tick_rate(const double tick_rate);
+ /*! Get access to the motherboard controller for this block's motherboard
+ *
+ * This will return a nullptr if this block doesn't have access to the
+ * motherboard. In order to gain access to the motherboard, the block needs
+ * to have requested access to the motherboard during the registration
+ * procedure. See also registry.hpp.
+ *
+ * Even if this block requested access to the motherboard controller, there
+ * is no guarantee that UHD will honour that request. It is therefore
+ * important to verify that the returned pointer is valid.
+ */
+ std::shared_ptr<mb_controller> get_mb_controller();
+
private:
/*! Update the tick rate of this block
*
@@ -130,8 +144,13 @@ private:
//! The actual tick rate of the current time base
double _tick_rate;
+ //! Reference to the clock_iface object shared with the register_iface
std::shared_ptr<clock_iface> _clock_iface;
+ //! Stores a reference to this block's motherboard's controller, if this
+ // block had requested and was granted access
+ std::shared_ptr<mb_controller> _mb_controller;
+
}; // class noc_block_base
}} /* namespace uhd::rfnoc */
diff --git a/host/include/uhd/rfnoc/noc_block_make_args.hpp b/host/include/uhd/rfnoc/noc_block_make_args.hpp
index d3679f973..2d42d476e 100644
--- a/host/include/uhd/rfnoc/noc_block_make_args.hpp
+++ b/host/include/uhd/rfnoc/noc_block_make_args.hpp
@@ -14,6 +14,7 @@
namespace uhd { namespace rfnoc {
class clock_iface;
+class mb_controller;
/*! Data structure to hold the arguments passed into the noc_block_base ctor
*
@@ -42,6 +43,12 @@ struct noc_block_base::make_args_t
//! Clock interface object that is shared with the reg_iface
std::shared_ptr<clock_iface> clk_iface;
+ //! Reference to the motherboard controller associated with this block.
+ //
+ // Note that this may not be populated -- most blocks do not gain access to
+ // the motherboard controller.
+ std::shared_ptr<mb_controller> mb_control;
+
//! The subtree for this block
uhd::property_tree::sptr tree;
};
diff --git a/host/include/uhd/rfnoc/registry.hpp b/host/include/uhd/rfnoc/registry.hpp
index 18d896205..cc07d5c65 100644
--- a/host/include/uhd/rfnoc/registry.hpp
+++ b/host/include/uhd/rfnoc/registry.hpp
@@ -27,6 +27,12 @@
NOC_ID, BLOCK_NAME, &CLASS_NAME##_make); \
}
+#define UHD_RFNOC_BLOCK_REQUEST_MB_ACCESS(NOC_ID) \
+ UHD_STATIC_BLOCK(rfnoc_block_##NOC_ID##_request_mb_access) \
+ { \
+ uhd::rfnoc::registry::request_mb_access(NOC_ID); \
+ }
+
namespace uhd { namespace rfnoc {
/*! RFNoC Block Registry
@@ -71,6 +77,25 @@ public:
*/
static void register_block_descriptor(const std::string& block_key,
factory_t factory_fn);
+
+ /*! Call this after registering a block if it requires access to the
+ * mb_controller
+ *
+ * Note: This is a request to the framework, and may be denied.
+ *
+ * \param noc_id Noc-ID of the block that requires access to the mb_controller
+ */
+ static void request_mb_access(noc_block_base::noc_id_t noc_id);
+
+ /*! Call this after registering a block if it requires access to the
+ * mb_controller
+ *
+ * Note: This is a request to the framework, and may be denied.
+ *
+ * \param noc_id Noc-ID of the block that requires access to the mb_controller
+ */
+ static void request_mb_access(const std::string& block_key);
+
};
}} /* namespace uhd::rfnoc */
diff --git a/host/include/uhd/rfnoc_graph.hpp b/host/include/uhd/rfnoc_graph.hpp
index bdbac69f5..630f79751 100644
--- a/host/include/uhd/rfnoc_graph.hpp
+++ b/host/include/uhd/rfnoc_graph.hpp
@@ -20,6 +20,8 @@
namespace uhd { namespace rfnoc {
+class mb_controller;
+
/*! The core class for a UHD session with (an) RFNoC device(s)
*
* This class is a superset of uhd::device. It does not only hold a device
@@ -226,6 +228,15 @@ public:
* \return a shared pointer to a new streamer
*/
//virtual tx_streamer::sptr create_tx_streamer(const stream_args_t& args) = 0;
+
+ /**************************************************************************
+ * Hardware Control
+ *************************************************************************/
+ //! Return a reference to a motherboard controller
+ //
+ // See also uhd::rfnoc::mb_controller
+ virtual std::shared_ptr<mb_controller> get_mb_controller(
+ const size_t mb_index = 0) = 0;
}; // class rfnoc_graph
}}; // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/rfnoc/factory.hpp b/host/lib/include/uhdlib/rfnoc/factory.hpp
index 3305dda3e..8d1fb27a0 100644
--- a/host/lib/include/uhdlib/rfnoc/factory.hpp
+++ b/host/lib/include/uhdlib/rfnoc/factory.hpp
@@ -24,8 +24,11 @@ public:
*/
static std::pair<registry::factory_t, std::string>
get_block_factory(noc_block_base::noc_id_t noc_id);
-};
+ /*! Check if this block has requested access to the motherboard controller
+ */
+ static bool has_requested_mb_access(noc_block_base::noc_id_t noc_id);
+};
}} /* namespace uhd::rfnoc */
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt
index 127ad6ffd..d62489484 100644
--- a/host/lib/rfnoc/CMakeLists.txt
+++ b/host/lib/rfnoc/CMakeLists.txt
@@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/link_stream_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/graph_stream_manager.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mb_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/noc_block_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/node.cpp
diff --git a/host/lib/rfnoc/mb_controller.cpp b/host/lib/rfnoc/mb_controller.cpp
new file mode 100644
index 000000000..10d5ebe47
--- /dev/null
+++ b/host/lib/rfnoc/mb_controller.cpp
@@ -0,0 +1,73 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/rfnoc/mb_controller.hpp>
+#include <uhd/exception.hpp>
+#include <atomic>
+
+using namespace uhd::rfnoc;
+
+
+/******************************************************************************
+ * Timekeeper API
+ *****************************************************************************/
+mb_controller::timekeeper::timekeeper()
+{
+ // nop
+}
+
+uhd::time_spec_t mb_controller::timekeeper::get_time_now()
+{
+ return time_spec_t::from_ticks(get_ticks_now(), _tick_rate);
+}
+
+uhd::time_spec_t mb_controller::timekeeper::get_time_last_pps()
+{
+ return time_spec_t::from_ticks(get_ticks_last_pps(), _tick_rate);
+}
+
+void mb_controller::timekeeper::set_time_now(const uhd::time_spec_t &time)
+{
+ set_ticks_now(time.to_ticks(_tick_rate));
+}
+
+void mb_controller::timekeeper::set_time_next_pps(const uhd::time_spec_t &time)
+{
+ set_ticks_next_pps(time.to_ticks(_tick_rate));
+}
+
+void mb_controller::timekeeper::set_tick_rate(const double tick_rate)
+{
+ if (_tick_rate == tick_rate) {
+ return;
+ }
+ _tick_rate = tick_rate;
+
+ // The period is the inverse of the tick rate, normalized by nanoseconds,
+ // and represented as Q32 (e.g., period == 1ns means period_ns == 1<<32)
+ const uint64_t period_ns = static_cast<uint64_t>(1e9 / tick_rate * (uint64_t(1) << 32));
+ set_period(period_ns);
+}
+
+size_t mb_controller::get_num_timekeepers() const
+{
+ return _timekeepers.size();
+}
+
+mb_controller::timekeeper::sptr mb_controller::get_timekeeper(const size_t tk_idx) const
+{
+ if (!_timekeepers.count(tk_idx)) {
+ throw uhd::index_error(
+ std::string("No timekeeper with index ") + std::to_string(tk_idx));
+ }
+
+ return _timekeepers.at(tk_idx);
+}
+
+void mb_controller::register_timekeeper(const size_t idx, timekeeper::sptr tk)
+{
+ _timekeepers.emplace(idx, std::move(tk));
+}
diff --git a/host/lib/rfnoc/noc_block_base.cpp b/host/lib/rfnoc/noc_block_base.cpp
index 2bbf52928..0b2d456b2 100644
--- a/host/lib/rfnoc/noc_block_base.cpp
+++ b/host/lib/rfnoc/noc_block_base.cpp
@@ -24,6 +24,7 @@ noc_block_base::noc_block_base(make_args_ptr make_args)
, _num_input_ports(make_args->num_input_ports)
, _num_output_ports(make_args->num_output_ports)
, _clock_iface(make_args->clk_iface)
+ , _mb_controller(std::move(make_args->mb_control))
{
// First, create one tick_rate property for every port
_tick_rate_props.reserve(get_num_input_ports() + get_num_output_ports());
@@ -87,3 +88,8 @@ void noc_block_base::_set_tick_rate(const double tick_rate)
_clock_iface->set_freq(tick_rate);
_tick_rate = tick_rate;
}
+
+std::shared_ptr<mb_controller> noc_block_base::get_mb_controller()
+{
+ return _mb_controller;
+}
diff --git a/host/lib/rfnoc/registry_factory.cpp b/host/lib/rfnoc/registry_factory.cpp
index e9ad4f89c..d03cb183a 100644
--- a/host/lib/rfnoc/registry_factory.cpp
+++ b/host/lib/rfnoc/registry_factory.cpp
@@ -35,6 +35,17 @@ using block_descriptor_reg_t =
UHD_SINGLETON_FCN(block_descriptor_reg_t, get_descriptor_block_registry);
///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// These registries are for blocks that have requested motherboard access
+using block_direct_mb_access_req_t = std::unordered_set<noc_block_base::noc_id_t>;
+UHD_SINGLETON_FCN(block_direct_mb_access_req_t, get_direct_block_mb_access_requested);
+//
+// This is the descriptor registry:
+using block_descriptor_mb_access_req_t = std::unordered_set<std::string>;
+UHD_SINGLETON_FCN(
+ block_descriptor_mb_access_req_t, get_descriptor_block_mb_access_requested);
+///////////////////////////////////////////////////////////////////////////////
+
/******************************************************************************
* Registry functions
*
@@ -68,6 +79,20 @@ void registry::register_block_descriptor(
get_descriptor_block_registry().emplace(block_key, std::move(factory_fn));
}
+void registry::request_mb_access(noc_block_base::noc_id_t noc_id)
+{
+ if (!get_direct_block_mb_access_requested().count(noc_id)) {
+ get_direct_block_mb_access_requested().emplace(noc_id);
+ }
+}
+
+void registry::request_mb_access(const std::string& block_key)
+{
+ if (!get_descriptor_block_mb_access_requested().count(block_key)) {
+ get_descriptor_block_mb_access_requested().emplace(block_key);
+ }
+}
+
/******************************************************************************
* Factory functions
*****************************************************************************/
@@ -87,3 +112,18 @@ std::pair<registry::factory_t, std::string> factory::get_block_factory(
auto& block_info = get_direct_block_registry().at(noc_id);
return {std::get<1>(block_info), std::get<0>(block_info)};
}
+
+bool factory::has_requested_mb_access(noc_block_base::noc_id_t noc_id)
+{
+ if (get_direct_block_mb_access_requested().count(noc_id)) {
+ return true;
+ }
+
+ // FIXME tbw:
+ // - Map noc_id to block key
+ // - Check that key's descriptor
+ // - If that block has requested MB access, stash the noc ID in
+ // get_direct_block_mb_access_requested() for faster lookups in the future
+
+ return false;
+}
diff --git a/host/lib/rfnoc/rfnoc_graph.cpp b/host/lib/rfnoc/rfnoc_graph.cpp
index fef2ccccb..22c9b7294 100644
--- a/host/lib/rfnoc/rfnoc_graph.cpp
+++ b/host/lib/rfnoc/rfnoc_graph.cpp
@@ -6,6 +6,7 @@
#include <uhd/rfnoc/node.hpp>
#include <uhd/rfnoc_graph.hpp>
+#include <uhd/rfnoc/mb_controller.hpp>
#include <uhdlib/rfnoc/block_container.hpp>
#include <uhdlib/rfnoc/graph.hpp>
#include <uhdlib/rfnoc/rfnoc_device.hpp>
@@ -90,6 +91,16 @@ public:
throw uhd::not_implemented_error("");
}
+ std::shared_ptr<mb_controller> get_mb_controller(const size_t mb_index = 0)
+ {
+ if (!_mb_controllers.count(mb_index)) {
+ throw uhd::index_error(
+ std::string("Could not get mb controller for motherboard index ")
+ + std::to_string(mb_index));
+ }
+ return _mb_controllers.at(mb_index);
+ }
+
private:
/**************************************************************************
* Device Setup
@@ -148,6 +159,8 @@ private:
//! Reference to the graph
std::unique_ptr<detail::graph_t> _graph;
+ //! Stash a list of motherboard controllers
+ std::unordered_map<size_t, mb_controller::sptr> _mb_controllers;
}; /* class rfnoc_graph_impl */
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index d443a4db1..484eeb903 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -37,6 +37,7 @@ set(test_sources
isatty_test.cpp
log_test.cpp
math_test.cpp
+ mb_controller_test.cpp
narrow_cast_test.cpp
property_test.cpp
ranges_test.cpp
diff --git a/host/tests/mb_controller_test.cpp b/host/tests/mb_controller_test.cpp
new file mode 100644
index 000000000..48baadf29
--- /dev/null
+++ b/host/tests/mb_controller_test.cpp
@@ -0,0 +1,155 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/rfnoc/mb_controller.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+class mock_timekeeper : public mb_controller::timekeeper
+{
+public:
+ uint64_t get_ticks_now() { return _ticks; }
+
+ uint64_t get_ticks_last_pps() { return _ticks; }
+
+ void set_ticks_now(const uint64_t ticks) { _ticks = ticks; }
+
+ void set_ticks_next_pps(const uint64_t ticks) { _ticks = ticks; }
+
+ uint64_t _ticks;
+ uint64_t _period;
+
+ void update_tick_rate(const double tick_rate)
+ {
+ set_tick_rate(tick_rate);
+ }
+
+private:
+ void set_period(const uint64_t period_ns) { _period = period_ns; }
+};
+
+class mock_mb_controller : public mb_controller
+{
+public:
+ mock_mb_controller()
+ {
+ auto tk = std::make_shared<mock_timekeeper>();
+ register_timekeeper(0, tk);
+ }
+
+ /**************************************************************************
+ * Motherboard Control API (see mb_controller.hpp)
+ *************************************************************************/
+ std::string get_mboard_name() const
+ {
+ return "MOCK-MB";
+ }
+
+ void set_time_source(const std::string& source)
+ {
+ time_source = source;
+ }
+
+ std::string get_time_source() const
+ {
+ return time_source;
+ }
+
+ std::vector<std::string> get_time_sources() const
+ {
+ return {"internal", "external"};
+ }
+
+ void set_clock_source(const std::string& source)
+ {
+ clock_source = source;
+ }
+
+ std::string get_clock_source() const
+ {
+ return clock_source;
+ }
+
+ std::vector<std::string> get_clock_sources() const
+ {
+ return {"internal", "external"};
+ }
+
+ void set_sync_source(
+ const std::string& /*clock_source*/, const std::string& /*time_source*/)
+ {
+ }
+
+ void set_sync_source(const device_addr_t& /*sync_source*/) {}
+
+ device_addr_t get_sync_source() const
+ {
+ return {};
+ }
+
+ std::vector<device_addr_t> get_sync_sources()
+ {
+ return {};
+ }
+
+ void set_clock_source_out(const bool enb)
+ {
+ clock_source_out = enb;
+ }
+
+ void set_time_source_out(const bool enb)
+ {
+ time_source_out = enb;
+ }
+
+ sensor_value_t get_sensor(const std::string& /*name*/)
+ {
+ return sensor_value_t("Ref", false, "locked", "unlocked");
+ }
+
+ std::vector<std::string> get_sensor_names()
+ {
+ return {"mock_sensor"};
+ }
+
+ uhd::usrp::mboard_eeprom_t get_eeprom()
+ {
+ return {};
+ }
+
+ std::string clock_source = "internal";
+ std::string time_source = "internal";
+ bool clock_source_out = false;
+ bool time_source_out = false;
+};
+
+BOOST_AUTO_TEST_CASE(test_mb_controller)
+{
+ auto mmbc = std::make_shared<mock_mb_controller>();
+
+ BOOST_REQUIRE_EQUAL(mmbc->get_num_timekeepers(), 1);
+ auto tk = mmbc->get_timekeeper(0);
+ auto tk_mock = std::dynamic_pointer_cast<mock_timekeeper>(tk);
+ BOOST_REQUIRE(tk);
+
+ constexpr double TICK_RATE = 200e6;
+ constexpr double PERIOD_NS = 5;
+ // This will call set_tick_rate() and thus set_period()
+ tk_mock->update_tick_rate(TICK_RATE);
+ BOOST_CHECK_EQUAL(tk->get_tick_rate(), TICK_RATE);
+ BOOST_CHECK_EQUAL(tk_mock->_period, PERIOD_NS * (uint64_t(1) << 32));
+
+ constexpr double TIME_0 = 1.0;
+ tk->set_time_now(uhd::time_spec_t(TIME_0));
+ BOOST_CHECK_EQUAL(tk->get_ticks_now(), TICK_RATE * TIME_0);
+ constexpr double TIME_1 = 0.5;
+ tk->set_time_next_pps(uhd::time_spec_t(TIME_1));
+ BOOST_CHECK_EQUAL(tk->get_ticks_last_pps(), TIME_1 * TICK_RATE);
+}
+