diff options
| -rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/mb_controller.hpp | 352 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/noc_block_base.hpp | 19 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/noc_block_make_args.hpp | 7 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/registry.hpp | 25 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc_graph.hpp | 11 | ||||
| -rw-r--r-- | host/lib/include/uhdlib/rfnoc/factory.hpp | 5 | ||||
| -rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/lib/rfnoc/mb_controller.cpp | 73 | ||||
| -rw-r--r-- | host/lib/rfnoc/noc_block_base.cpp | 6 | ||||
| -rw-r--r-- | host/lib/rfnoc/registry_factory.cpp | 40 | ||||
| -rw-r--r-- | host/lib/rfnoc/rfnoc_graph.cpp | 13 | ||||
| -rw-r--r-- | host/tests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/tests/mb_controller_test.cpp | 155 | 
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); +} + | 
