diff options
Diffstat (limited to 'host')
| -rw-r--r-- | host/include/uhd/rfnoc/radio_control.hpp | 3 | ||||
| -rw-r--r-- | host/include/uhd/usrp/multi_usrp.hpp | 189 | ||||
| -rw-r--r-- | host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp | 3 | ||||
| -rw-r--r-- | host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp | 10 | ||||
| -rw-r--r-- | host/lib/rfnoc/radio_control_impl.cpp | 2 | ||||
| -rw-r--r-- | host/lib/usrp/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/lib/usrp/cores/gpio_atr_3000.cpp | 70 | ||||
| -rw-r--r-- | host/lib/usrp/multi_usrp.cpp | 439 | ||||
| -rw-r--r-- | host/lib/usrp/multi_usrp_python.hpp | 24 | ||||
| -rw-r--r-- | host/lib/usrp/multi_usrp_rfnoc.cpp | 2217 | ||||
| -rw-r--r-- | host/lib/usrp/usrp_c.cpp | 47 | 
11 files changed, 2470 insertions, 535 deletions
diff --git a/host/include/uhd/rfnoc/radio_control.hpp b/host/include/uhd/rfnoc/radio_control.hpp index f966379e1..eb948cb3c 100644 --- a/host/include/uhd/rfnoc/radio_control.hpp +++ b/host/include/uhd/rfnoc/radio_control.hpp @@ -586,8 +586,7 @@ public:       */      virtual void set_gpio_attr(const std::string& bank,          const std::string& attr, -        const uint32_t value, -        const uint32_t mask) = 0; +        const uint32_t value) = 0;      /*!       * Get a GPIO attribute on a particular GPIO bank. diff --git a/host/include/uhd/usrp/multi_usrp.hpp b/host/include/uhd/usrp/multi_usrp.hpp index b7710e036..99a966b7a 100644 --- a/host/include/uhd/usrp/multi_usrp.hpp +++ b/host/include/uhd/usrp/multi_usrp.hpp @@ -127,20 +127,6 @@ public:       */      virtual device::sptr get_device(void) = 0; -    /*! Returns true if this is a generation-3 device. -     */ -    virtual bool is_device3(void) = 0; - -    /*! -     * Get the underlying device3 object. Only works for generation-3 (or later) devices. -     * -     * This is needed to get access to the streaming API and properties. -     * -     * \return The uhd::device3 object for this USRP. -     * \throws uhd::type_error if this device is not actually a generation-3 device. -     */ -    virtual boost::shared_ptr<uhd::device3> get_device3(void) = 0; -      //! Convenience method to get a RX streamer. See also uhd::device::get_rx_stream().      virtual rx_streamer::sptr get_rx_stream(const stream_args_t& args) = 0; @@ -1488,42 +1474,6 @@ public:          const size_t mboard = 0) = 0;      /*! -     * Set a GPIO attribute on a particular GPIO bank. -     * Possible attribute names: -     *  - SRC  - "PS" for handling by processing system -     *         - "RADIO_N/M" for handling by radio block with N is in [0..Number of -     * Radio]; M is in [0..Number of port per Radio] -     *  - CTRL - "ATR"  for ATR mode -     *         - "GPIO" for GPIO mode -     *  - DDR  - "OUT" for output -     *         - "IN"  for input -     *  - OUT -  a string of numbers representing GPIO output level (not ATR mode) -     *        - "HIGH"or "LOW" as GPIO output level that apply for each bit mask that is 1 -     *  - ATR_0X - a string of numbers representing a value of the ATR idle state register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR idle state -     * register -     *  - ATR_RX - a string of numbers representing a value of a ATR receive only state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR receive only -     * state register -     *  - ATR_TX - a string of numbers representing a value of the ATR transmit only state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR transmit only -     * state register -     *  - ATR_XX - a string of numbers representing a value of the ATR full duplex state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR full duplex -     * state register \param bank the name of a GPIO bank \param attr the name of a GPIO -     * attribute \param value the new value for this GPIO bank \param mask the bit mask to -     * effect which pins are changed \param mboard the motherboard index 0 to M-1 -     */ -    virtual void set_gpio_attr(const std::string& bank, -        const std::string& attr, -        const std::string& value, -        const uint32_t mask = 0xffffffff, -        const size_t mboard = 0) = 0; - -    /*!       * Get a GPIO attribute on a particular GPIO bank.       * Possible attribute names:       *  - CTRL - 1 for ATR mode 0 for GPIO mode @@ -1543,122 +1493,85 @@ public:          const std::string& bank, const std::string& attr, const size_t mboard = 0) = 0;      /*! -     * Get a GPIO attribute on a particular GPIO bank. -     * Possible attribute names: -     *  - SRC  - "PS" for handling by processing system -     *         - "RADIO_N/M" for handling by radio block with N is in [0..Number of -     * Radio]; M is in [0..Number of port per Radio] -     *  - CTRL - "ATR"  for ATR mode -     *         - "GPIO" for GPIO mode -     *  - DDR  - "OUT" for output -     *         - "IN"  for input -     *  - OUT -  a string of numbers representing GPIO output level (not ATR mode) -     *        - "HIGH"or "LOW" as GPIO output level that apply for each bit mask that is 1 -     *  - ATR_0X - a string of numbers representing a value of the ATR idle state register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR idle state -     * register -     *  - ATR_RX - a string of numbers representing a value of a ATR receive only state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR receive only -     * state register -     *  - ATR_TX - a string of numbers representing a value of the ATR transmit only state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR transmit only -     * state register -     *  - ATR_XX - a string of numbers representing a value of the ATR full duplex state -     * register -     *           - "HIGH" or "LOW" as a value set on each bit on of the ATR full duplex -     * state register -     *  - READBACK - readback input GPIOs -     * \param bank the name of a GPIO bank -     * \param attr the name of a GPIO attribute -     * \param mboard the motherboard index 0 to M-1 -     * \return the value set for this attribute in vector of strings +     * Enumerate sources for a gpio bank on the specified device. Each of the pins in the +     * chosen bank can be driven from one of the returned sources. \param bank the name of +     * a GPIO bank \param mboard the motherboard index 0 to M-1 \return a list of strings +     * with each valid source for the chosen bank       */ -    virtual std::vector<std::string> get_gpio_string_attr( -        const std::string& bank, const std::string& attr, const size_t mboard = 0) = 0; - -    /******************************************************************* -     * Register IO methods -     ******************************************************************/ -    struct register_info_t -    { -        size_t bitwidth; -        bool readable; -        bool writable; -    }; +    virtual std::vector<std::string> get_gpio_srcs( +        const std::string& bank, const size_t mboard = 0) = 0;      /*! -     * Enumerate the full paths of all low-level USRP registers accessible to read/write +     * Get the current source for each pin in a GPIO bank. +     * \param bank the name of a GPIO bank       * \param mboard the motherboard index 0 to M-1 -     * \return a vector of register paths +     * \return a list of strings for current source of each GPIO pin in the chosen bank       */ -    virtual std::vector<std::string> enumerate_registers(const size_t mboard = 0) = 0; +    virtual std::vector<std::string> get_gpio_src( +        const std::string& bank, const size_t mboard = 0) = 0;      /*! -     * Get more information about a low-level device register -     * \param path the full path to the register +     * Set the current source for each pin in a GPIO bank. +     * \param bank the name of a GPIO bank +     * \param src a list of strings specifying the source of each pin in a GPIO bank       * \param mboard the motherboard index 0 to M-1 -     * \return the info struct which contains the bitwidth and read-write access -     * information       */ -    virtual register_info_t get_register_info( -        const std::string& path, const size_t mboard = 0) = 0; +    virtual void set_gpio_src(const std::string& bank, +        const std::vector<std::string>& src, +        const size_t mboard = 0) = 0; +    /******************************************************************* +     * Filter API methods +     ******************************************************************/ +    // TODO: This should be a const function, but I don't want to wrestle with the compiler right now      /*! -     * Write a low-level register field for a register in the USRP hardware -     * \param path the full path to the register -     * \param field the identifier of bitfield to be written (all other bits remain -     * unchanged) \param value the value to write to the register field \param mboard the -     * motherboard index 0 to M-1 +     * Enumerate the available filters in the RX signal path. +     * \param chan RX channel index 0 to N-1 +     * \return a vector of strings representing the selected filter names. +     * \returnblock Filter names will follow the pattern BLOCK_ID:FILTER_NAME. For example, "0/Radio#0:HB_0"       */ -    virtual void write_register(const std::string& path, -        const uint32_t field, -        const uint64_t value, -        const size_t mboard = 0) = 0; +    virtual std::vector<std::string> get_rx_filter_names(const size_t chan) = 0;      /*! -     * Read a low-level register field from a register in the USRP hardware -     * \param path the full path to the register -     * \param field the identifier of bitfield to be read -     * \param mboard the motherboard index 0 to M-1 -     * \return the value of the register field +     * Return the filter object for the given RX filter name. +     * \param name the name of the filter as returned from get_rx_filter_names(). +     * \return a filter_info_base::sptr.       */ -    virtual uint64_t read_register( -        const std::string& path, const uint32_t field, const size_t mboard = 0) = 0; +    virtual uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan) = 0; -    /******************************************************************* -     * Filter API methods -     ******************************************************************/ +    /*! +     * Write back a filter obtained by get_rx_filter() to the signal path. +     * This filter can be a modified version of the originally returned one. +     * \param name the name of the filter as returned from get_rx_filter_names(). +     * \param filter the filter_info_base::sptr of the filter object to be written +     */ +    virtual void set_rx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) = 0; +    // TODO: This should be a const function, but I don't want to wrestle with the compiler right now      /*! -     * Enumerate the available filters in the signal path. -     * \param search_mask -     * \parblock -     * Select only certain filter names by specifying this search mask. -     * -     * E.g. if search mask is set to "rx_frontends/A" only filter names including that -     * string will be returned. \endparblock \return a vector of strings representing the -     * selected filter names. +     * Enumerate the available filters in the TX signal path. +     * \param chan TX channel index 0 to N-1 +     * \return a vector of strings representing the selected filter names. +     * \returnblock Filter names will follow the pattern BLOCK_ID:FILTER_NAME. For example, "0/Radio#0:HB_0"       */ -    virtual std::vector<std::string> get_filter_names( -        const std::string& search_mask = "") = 0; +    virtual std::vector<std::string> get_tx_filter_names(const size_t chan) = 0;      /*! -     * Return the filter object for the given name. -     * \param path the name of the filter as returned from get_filter_names(). +     * Return the filter object for the given TX filter name. +     * \param name the name of the filter as returned from get_tx_filter_names().       * \return a filter_info_base::sptr.       */ -    virtual filter_info_base::sptr get_filter(const std::string& path) = 0; +    virtual uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan) = 0;      /*! -     * Write back a filter obtained by get_filter() to the signal path. +     * Write back a filter obtained by get_tx_filter() to the signal path.       * This filter can be a modified version of the originally returned one. -     * The information about Rx or Tx is contained in the path parameter. -     * \param path the name of the filter as returned from get_filter_names(). +     * \param name the name of the filter as returned from get_tx_filter_names().       * \param filter the filter_info_base::sptr of the filter object to be written       */ -    virtual void set_filter(const std::string& path, filter_info_base::sptr filter) = 0; +    virtual void set_tx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) = 0;  };  } // namespace usrp diff --git a/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp b/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp index fc0d19a0a..b3ef2db0b 100644 --- a/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp +++ b/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp @@ -147,8 +147,7 @@ public:      virtual std::vector<std::string> get_gpio_banks() const;      virtual void set_gpio_attr(const std::string& bank,          const std::string& attr, -        const uint32_t value, -        const uint32_t mask); +        const uint32_t value);      virtual uint32_t get_gpio_attr(const std::string& bank, const std::string& attr);      /************************************************************************** diff --git a/host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp b/host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp index c9585449f..2ab0cf4cf 100644 --- a/host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp +++ b/host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp @@ -97,6 +97,16 @@ public:      virtual uint32_t read_gpio() = 0;      /*! +     * Get a GPIO attribute +     * This will likely returned a cached value, and not read the state from the physical +     * GPIO controller. +     * +     * \param attr the attribute to read +     * \return the current value of that attribute +     */ +    virtual uint32_t get_attr_reg(const gpio_attr_t attr) = 0; + +    /*!       * Set a GPIO attribute       *       * \param attr the attribute to set diff --git a/host/lib/rfnoc/radio_control_impl.cpp b/host/lib/rfnoc/radio_control_impl.cpp index 2a730e8df..b5907cf03 100644 --- a/host/lib/rfnoc/radio_control_impl.cpp +++ b/host/lib/rfnoc/radio_control_impl.cpp @@ -728,7 +728,7 @@ std::vector<std::string> radio_control_impl::get_gpio_banks() const  }  void radio_control_impl::set_gpio_attr( -    const std::string&, const std::string&, const uint32_t, const uint32_t) +    const std::string&, const std::string&, const uint32_t)  {      throw uhd::not_implemented_error("set_gpio_attr() not implemented on this radio!");  } diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 8f3ccc9ff..0b839a835 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -18,6 +18,7 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_CURRENT_SOURCE_DIR}/dboard_manager.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/gps_ctrl.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp_rfnoc.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/fe_connection.cpp  ) diff --git a/host/lib/usrp/cores/gpio_atr_3000.cpp b/host/lib/usrp/cores/gpio_atr_3000.cpp index a74da2697..d48211fd4 100644 --- a/host/lib/usrp/cores/gpio_atr_3000.cpp +++ b/host/lib/usrp/cores/gpio_atr_3000.cpp @@ -8,6 +8,7 @@  #include <uhd/types/dict.hpp>  #include <uhd/utils/soft_register.hpp>  #include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <unordered_map>  using namespace uhd;  using namespace usrp; @@ -53,6 +54,14 @@ public:          _atr_fdx_reg.initialize(*_iface, true);          _ddr_reg.initialize(*_iface, true);          _atr_disable_reg.initialize(*_iface, true); +        for (const gpio_attr_map_t::value_type attr : gpio_attr_map) { +            if (attr.first == usrp::gpio_atr::GPIO_SRC +                || attr.first == usrp::gpio_atr::GPIO_READBACK) { +                // Don't set the SRC and READBACK, they're handled elsewhere. +                continue; +            } +            _attr_reg_state.emplace(attr.first, 0); +        }      }      virtual void set_atr_mode(const gpio_atr_mode_t mode, const uint32_t mask) @@ -62,20 +71,23 @@ public:          // bit position, a 1 means that the bit is static and 0 means that the bit is          // driven by the ATR state machine. This setting will only get applied to all bits          // in the "mask" that are 1. All other bits will retain their old value. -        _atr_disable_reg.set_with_mask( -            (mode == MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); +        auto value = (mode == MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL; +        _atr_disable_reg.set_with_mask(value, mask);          _atr_disable_reg.flush(); +        _update_attr_state(GPIO_CTRL, value, mask);      }      virtual void set_gpio_ddr(const gpio_ddr_t dir, const uint32_t mask)      {          // Each bit in the "DDR" register determines whether the respective bit in the -        // GPIO bus is an input or an output. For each bit position, a 1 means that the bit -        // is an output and 0 means that the bit is an input. This setting will only get -        // applied to all bits in the "mask" that are 1. All other bits will retain their -        // old value. -        _ddr_reg.set_with_mask((dir == DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); +        // GPIO bus is an input or an output. For each bit position, a 1 means that the +        // bit is an output and 0 means that the bit is an input. This setting will only +        // get applied to all bits in the "mask" that are 1. All other bits will retain +        // their old value. +        auto value = (dir == DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL; +        _ddr_reg.set_with_mask(value, mask);          _ddr_reg.flush(); +        _update_attr_state(GPIO_DDR, value, mask);      }      virtual void set_atr_reg(const gpio_atr_reg_t atr, @@ -87,27 +99,38 @@ public:          // applied to all bits in the "mask" that are 1. All other bits will retain their          // old value.          masked_reg_t* reg = NULL; +        gpio_attr_t attr;          switch (atr) {              case ATR_REG_IDLE: -                reg = &_atr_idle_reg; +                reg  = &_atr_idle_reg; +                attr = GPIO_ATR_0X; +                  break;              case ATR_REG_RX_ONLY: -                reg = &_atr_rx_reg; +                reg  = &_atr_rx_reg; +                attr = GPIO_ATR_RX; +                  break;              case ATR_REG_TX_ONLY: -                reg = &_atr_tx_reg; +                reg  = &_atr_tx_reg; +                attr = GPIO_ATR_TX; +                  break;              case ATR_REG_FULL_DUPLEX: -                reg = &_atr_fdx_reg; +                reg  = &_atr_fdx_reg; +                attr = GPIO_ATR_XX; +                  break;              default: -                reg = &_atr_idle_reg; +                reg  = &_atr_idle_reg; +                attr = GPIO_ATR_0X;                  break;          }          // For protection we only write to bits that have the mode ATR by masking the user          // specified "mask" with ~atr_disable.          reg->set_with_mask(value, mask);          reg->flush(); +        _update_attr_state(attr, value, mask);      }      virtual void set_gpio_out(const uint32_t value, const uint32_t mask = MASK_SET_ALL) @@ -120,6 +143,7 @@ public:          // user specified "mask" with atr_disable.          _atr_idle_reg.set_gpio_out_with_mask(value, mask);          _atr_idle_reg.flush(); +        _update_attr_state(GPIO_OUT, value, mask);      }      virtual uint32_t read_gpio() @@ -134,6 +158,21 @@ public:          }      } +    virtual uint32_t get_attr_reg(const gpio_attr_t attr) +    { +        if (attr == GPIO_SRC) { +            throw uhd::runtime_error("Can't get GPIO source by GPIO ATR interface."); +        } +        if (attr == GPIO_READBACK) { +            return read_gpio(); +        } +        if (!_attr_reg_state.count(attr)) { +            throw uhd::runtime_error("Invalid GPIO attr!"); +        } +        // Return the cached value of the requested attr +        return _attr_reg_state.at(attr); +    } +      inline virtual void set_gpio_attr(const gpio_attr_t attr, const uint32_t value)      {          // An attribute based API to configure all settings for the GPIO bus in one @@ -253,6 +292,7 @@ protected:          masked_reg_t& _atr_disable_reg;      }; +    std::unordered_map<gpio_attr_t, uint32_t, std::hash<size_t>> _attr_reg_state;      wb_iface::sptr _iface;      wb_iface::wb_addr_type _rb_addr;      atr_idle_reg_t _atr_idle_reg; @@ -261,6 +301,12 @@ protected:      masked_reg_t _atr_fdx_reg;      masked_reg_t _ddr_reg;      masked_reg_t _atr_disable_reg; + +    void _update_attr_state( +        const gpio_attr_t attr, const uint32_t val, const uint32_t mask) +    { +        _attr_reg_state[attr] = (_attr_reg_state.at(attr) & ~mask) | (val & mask); +    }  };  gpio_atr_3000::sptr gpio_atr_3000::make(wb_iface::sptr iface, diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 5789c8a72..227ba4212 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -1,6 +1,7 @@  //  // Copyright 2010-2016 Ettus Research LLC  // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand  //  // SPDX-License-Identifier: GPL-3.0-or-later  // @@ -20,15 +21,27 @@  #include <uhd/utils/soft_register.hpp>  #include <uhdlib/usrp/gpio_defs.hpp>  #include <uhdlib/rfnoc/legacy_compat.hpp> +#include <uhdlib/rfnoc/rfnoc_device.hpp>  #include <boost/assign/list_of.hpp>  #include <boost/format.hpp>  #include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp>  #include <algorithm>  #include <cmath>  #include <bitset>  #include <chrono>  #include <thread> +namespace uhd { namespace rfnoc { + +//! Factory function for RFNoC devices specifically +uhd::usrp::multi_usrp::sptr make_rfnoc_device( +    uhd::rfnoc::detail::rfnoc_device::sptr rfnoc_device, +    const uhd::device_addr_t& dev_addr); + +}} /* namespace uhd::rfnoc */ + +  using namespace uhd;  using namespace uhd::usrp; @@ -384,31 +397,16 @@ static double derive_freq_from_xx_subdev_and_dsp(   **********************************************************************/  class multi_usrp_impl : public multi_usrp{  public: -    multi_usrp_impl(const device_addr_t &addr){ -        _dev = device::make(addr, device::USRP); +    multi_usrp_impl(device::sptr dev, const device_addr_t& addr) +        : _dev(dev) +    {          _tree = _dev->get_tree(); -        _is_device3 = bool(boost::dynamic_pointer_cast<uhd::device3>(_dev)); - -        if (is_device3() and not addr.has_key("recover_mb_eeprom")) { -            _legacy_compat = rfnoc::legacy_compat::make(get_device3(), addr); -        }      }      device::sptr get_device(void){          return _dev;      } -    bool is_device3(void) { -        return _is_device3; -    } - -    device3::sptr get_device3(void) { -        if (not is_device3()) { -            throw uhd::type_error("Cannot call get_device3() on a non-generation 3 device."); -        } -        return boost::dynamic_pointer_cast<uhd::device3>(_dev); -    } -      dict<std::string, std::string> get_usrp_rx_info(size_t chan){          mboard_chan_pair mcp = rx_chan_to_mcp(chan);          dict<std::string, std::string> usrp_info; @@ -684,12 +682,7 @@ public:      void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){          if (chan != ALL_CHANS){ -            if (is_device3()) { -                mboard_chan_pair mcp = rx_chan_to_mcp(chan); -                _legacy_compat->issue_stream_cmd(stream_cmd, mcp.mboard, mcp.chan); -            } else { -                _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); -            } +            _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd);              return;          }          for (size_t c = 0; c < get_rx_num_channels(); c++){ @@ -970,9 +963,6 @@ public:       ******************************************************************/      rx_streamer::sptr get_rx_stream(const stream_args_t &args) {          _check_link_rate(args, false); -        if (is_device3()) { -            return _legacy_compat->get_rx_stream(args); -        }          return this->get_device()->get_rx_stream(args);      } @@ -1020,18 +1010,6 @@ public:      }      void set_rx_rate(double rate, size_t chan){ -        if (is_device3()) { -            _legacy_compat->set_rx_rate(rate, chan); -            if (chan == ALL_CHANS) { -                for (size_t c = 0; c < get_rx_num_channels(); c++){ -                    do_samp_rate_warning_message(rate, get_rx_rate(c), "RX"); -                } -            } else { -                do_samp_rate_warning_message(rate, get_rx_rate(chan), "RX"); -            } -            return; -        } -          if (chan != ALL_CHANS){              _tree->access<double>(rx_dsp_root(chan) / "rate" / "value").set(rate);              do_samp_rate_warning_message(rate, get_rx_rate(chan), "RX"); @@ -1725,85 +1703,108 @@ public:          }      } -    std::vector<std::string> get_filter_names(const std::string &search_mask) +    std::vector<std::string> get_rx_filter_names(const size_t chan)      { +        if (chan >= get_rx_num_channels()) { +            throw uhd::index_error("Attempting to get non-existent RX filter names"); +        }          std::vector<std::string> ret; -        for (size_t chan = 0; chan < get_rx_num_channels(); chan++){ - -            if (_tree->exists(rx_rf_fe_root(chan) / "filters")) { -                std::vector<std::string> names = _tree->list(rx_rf_fe_root(chan) / "filters"); -                for(size_t i = 0; i < names.size(); i++) -                { -                    std::string name = rx_rf_fe_root(chan) / "filters" / names[i]; -                    if((search_mask.empty()) or boost::contains(name, search_mask)) { -                        ret.push_back(name); -                    } -                } +        if (_tree->exists(rx_rf_fe_root(chan) / "filters")) { +            std::vector<std::string> names = _tree->list(rx_rf_fe_root(chan) / "filters"); +            for (size_t i = 0; i < names.size(); i++) { +                std::string name = rx_rf_fe_root(chan) / "filters" / names[i]; +                ret.push_back(name);              } -            if (_tree->exists(rx_dsp_root(chan) / "filters")) { -                std::vector<std::string> names = _tree->list(rx_dsp_root(chan) / "filters"); -                for(size_t i = 0; i < names.size(); i++) -                { -                    std::string name = rx_dsp_root(chan) / "filters" / names[i]; -                    if((search_mask.empty()) or (boost::contains(name, search_mask))) { -                        ret.push_back(name); -                    } -                } +        } +        if (_tree->exists(rx_dsp_root(chan) / "filters")) { +            std::vector<std::string> names = _tree->list(rx_dsp_root(chan) / "filters"); +            for (size_t i = 0; i < names.size(); i++) { +                std::string name = rx_dsp_root(chan) / "filters" / names[i]; +                ret.push_back(name);              } +        } + +        return ret; +    } +    uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan) +    { +        std::vector<std::string> possible_names = get_rx_filter_names(chan); +        std::vector<std::string>::iterator it; +        it = find(possible_names.begin(), possible_names.end(), name); +        if (it == possible_names.end()) { +            throw uhd::runtime_error("Attempting to get non-existing filter: " + name);          } -        for (size_t chan = 0; chan < get_tx_num_channels(); chan++){ +        return _tree->access<filter_info_base::sptr>(rx_rf_fe_root(chan) / name / "value") +            .get(); +    } -            if (_tree->exists(tx_rf_fe_root(chan) / "filters")) { -                std::vector<std::string> names = _tree->list(tx_rf_fe_root(chan) / "filters"); -                for(size_t i = 0; i < names.size(); i++) -                { -                    std::string name = tx_rf_fe_root(chan) / "filters" / names[i]; -                    if((search_mask.empty()) or (boost::contains(name, search_mask))) { -                        ret.push_back(name); -                    } -                } +    void set_rx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +    { +        std::vector<std::string> possible_names = get_rx_filter_names(chan); +        std::vector<std::string>::iterator it; +        it = find(possible_names.begin(), possible_names.end(), name); +        if (it == possible_names.end()) { +            throw uhd::runtime_error("Attempting to set non-existing filter: " + name); +        } + +        _tree->access<filter_info_base::sptr>(rx_rf_fe_root(chan) / name / "value") +            .set(filter); +    } + +    std::vector<std::string> get_tx_filter_names(const size_t chan) +    { +        if (chan >= get_tx_num_channels()) { +            throw uhd::index_error("Attempting to get non-existent TX filter names"); +        } +        std::vector<std::string> ret; + +        if (_tree->exists(tx_rf_fe_root(chan) / "filters")) { +            std::vector<std::string> names = _tree->list(tx_rf_fe_root(chan) / "filters"); +            for (size_t i = 0; i < names.size(); i++) { +                std::string name = tx_rf_fe_root(chan) / "filters" / names[i]; +                ret.push_back(name);              } -            if (_tree->exists(rx_dsp_root(chan) / "filters")) { -                std::vector<std::string> names = _tree->list(tx_dsp_root(chan) / "filters"); -                for(size_t i = 0; i < names.size(); i++) -                { -                    std::string name = tx_dsp_root(chan) / "filters" / names[i]; -                    if((search_mask.empty()) or (boost::contains(name, search_mask))) { -                        ret.push_back(name); -                    } -                } +        } +        if (_tree->exists(rx_dsp_root(chan) / "filters")) { +            std::vector<std::string> names = _tree->list(tx_dsp_root(chan) / "filters"); +            for (size_t i = 0; i < names.size(); i++) { +                std::string name = tx_dsp_root(chan) / "filters" / names[i]; +                ret.push_back(name);              } -          }          return ret;      } -    filter_info_base::sptr get_filter(const std::string &path) +    uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan)      { -        std::vector<std::string> possible_names = get_filter_names(""); +        std::vector<std::string> possible_names = get_tx_filter_names(chan);          std::vector<std::string>::iterator it; -        it = find(possible_names.begin(), possible_names.end(), path); +        it = find(possible_names.begin(), possible_names.end(), name);          if (it == possible_names.end()) { -            throw uhd::runtime_error("Attempting to get non-existing filter: "+path); +            throw uhd::runtime_error("Attempting to get non-existing filter: " + name);          } -        return _tree->access<filter_info_base::sptr>(path / "value").get(); +        return _tree->access<filter_info_base::sptr>(tx_rf_fe_root(chan) / name / "value") +            .get();      } -    void set_filter(const std::string &path, filter_info_base::sptr filter) +    void set_tx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan)      { -        std::vector<std::string> possible_names = get_filter_names(""); +        std::vector<std::string> possible_names = get_tx_filter_names(chan);          std::vector<std::string>::iterator it; -        it = find(possible_names.begin(), possible_names.end(), path); +        it = find(possible_names.begin(), possible_names.end(), name);          if (it == possible_names.end()) { -            throw uhd::runtime_error("Attempting to set non-existing filter: "+path); +            throw uhd::runtime_error("Attempting to set non-existing filter: " + name);          } -        _tree->access<filter_info_base::sptr>(path / "value").set(filter); +        _tree->access<filter_info_base::sptr>(tx_rf_fe_root(chan) / name / "value") +            .set(filter);      }      /******************************************************************* @@ -1811,9 +1812,6 @@ public:       ******************************************************************/      tx_streamer::sptr get_tx_stream(const stream_args_t &args) {          _check_link_rate(args, true); -        if (is_device3()) { -            return _legacy_compat->get_tx_stream(args); -        }          return this->get_device()->get_tx_stream(args);      } @@ -1861,18 +1859,6 @@ public:      }      void set_tx_rate(double rate, size_t chan){ -        if (is_device3()) { -            _legacy_compat->set_tx_rate(rate, chan); -            if (chan == ALL_CHANS) { -                for (size_t c = 0; c < get_tx_num_channels(); c++){ -                    do_samp_rate_warning_message(rate, get_tx_rate(c), "TX"); -                } -            } else { -                do_samp_rate_warning_message(rate, get_tx_rate(chan), "TX"); -            } -            return; -        } -          if (chan != ALL_CHANS){              _tree->access<double>(tx_dsp_root(chan) / "rate" / "value").set(rate);              do_samp_rate_warning_message(rate, get_tx_rate(chan), "TX"); @@ -2196,71 +2182,6 @@ public:          ));      } -    void set_gpio_attr( -            const std::string &bank, -            const std::string &attr, -            const std::string &str_value, -            const uint32_t mask, -            const size_t mboard -    ) { -        const auto attr_type = gpio_atr::gpio_attr_rev_map.at(attr); -        if (_tree->exists(mb_root(mboard) / "gpio" / bank)) { -            if (_tree->exists(mb_root(mboard) / "gpio" / bank / attr)) { -                switch (attr_type){ -                    case gpio_atr::GPIO_SRC: -                    case gpio_atr::GPIO_CTRL: -                    case gpio_atr::GPIO_DDR:{ -                        auto attr_value = -                            _tree->access<std::vector<std::string>>( -                                mb_root(mboard) / "gpio" / bank / attr).get(); -                        UHD_ASSERT_THROW(attr_value.size() <= 32); -                        std::bitset<32> bit_mask = std::bitset<32>(mask); -                        for (size_t i = 0 ; i < bit_mask.size(); i++) { -                            if (bit_mask[i] == 1) { -                                attr_value[i] = str_value; -                            } -                        } -                        _tree->access<std::vector<std::string>>( -                               mb_root(mboard) / "gpio" / bank / attr -                        ).set(attr_value); -                    } -                    break; -                    default: { -                        const uint32_t value = -                            gpio_atr::gpio_attr_value_pair.at(attr).at(str_value) == 0 ? -1 : 0; -                        const uint32_t current = _tree->access<uint32_t>( -                            mb_root(mboard) / "gpio" / bank / attr).get(); -                        const uint32_t new_value = -                            (current & ~mask) | (value & mask); -                        _tree->access<uint32_t>( -                            mb_root(mboard) / "gpio" / bank / attr -                        ).set(new_value); -                    } -                    break; -                } -                return; -            } else { -                throw uhd::runtime_error(str( -                    boost::format("The hardware has no gpio attribute `%s'") -                    % attr -                )); -            } -        } -        // If the bank is not in the prop tree, convert string value to integer -        // value and have it handled by the other set_gpio_attr() -        const uint32_t value = -            gpio_atr::gpio_attr_value_pair.at(attr).at(str_value) == 0 -            ? -1 -            : 0; -        set_gpio_attr( -            bank, -            attr, -            value, -            mask, -            mboard -        ); -    } -      uint32_t get_gpio_attr(              const std::string &bank,              const std::string &attr, @@ -2316,149 +2237,28 @@ public:          ));      } -    std::vector<std::string> get_gpio_string_attr( -        const std::string &bank, -        const std::string &attr, -        const size_t mboard -    ) { -        const auto attr_type = gpio_atr::gpio_attr_rev_map.at(attr); -        auto str_val = std::vector<std::string>(32, gpio_atr::default_attr_value_map.at(attr_type)); -        if (_tree->exists(mb_root(mboard) / "gpio" / bank)) { -            if (_tree->exists(mb_root(mboard) / "gpio" / bank / attr)) { -                const auto attr_type = gpio_atr::gpio_attr_rev_map.at(attr); -                switch (attr_type){ -                    case gpio_atr::GPIO_SRC: -                    case gpio_atr::GPIO_CTRL: -                    case gpio_atr::GPIO_DDR: -                        return _tree->access<std::vector<std::string>>(mb_root(mboard) / "gpio" / bank / attr).get(); -                    default: { -                        uint32_t value = uint32_t(_tree->access<uint32_t>(mb_root(mboard) / "gpio" / bank / attr).get()); -                        std::bitset<32> bit_value = std::bitset<32>(value); -                        for (size_t i = 0; i < bit_value.size(); i++) -                        { -                            str_val[i] = bit_value[i] == 0 ? "LOW" : "HIGH"; -                        } -                        return str_val; -                    } -                } -            } -            else { -                throw uhd::runtime_error(str( -                    boost::format("The hardware has no gpio attribute: `%s'") -                    % attr -                )); -            } -        } -        throw uhd::runtime_error(str( -            boost::format("The hardware has no support for given gpio bank name `%s'") -            % bank -        )); -    } - -    void write_register(const std::string &path, const uint32_t field, const uint64_t value, const size_t mboard) -    { -        if (_tree->exists(mb_root(mboard) / "registers")) -        { -            uhd::soft_regmap_accessor_t::sptr accessor = -                _tree->access<uhd::soft_regmap_accessor_t::sptr>(mb_root(mboard) / "registers").get(); -            uhd::soft_register_base& reg = accessor->lookup(path); - -            if (not reg.is_writable()) { -                throw uhd::runtime_error("multi_usrp::write_register - register not writable: " + path); -            } - -            switch (reg.get_bitwidth()) { -            case 32: -                if (reg.is_readable()) -                    uhd::soft_register_base::cast<uhd::soft_reg32_rw_t>(reg).write(field, static_cast<uint32_t>(value)); -                else -                    uhd::soft_register_base::cast<uhd::soft_reg32_wo_t>(reg).write(field, static_cast<uint32_t>(value)); -            break; - -            case 64: -                if (reg.is_readable()) -                    uhd::soft_register_base::cast<uhd::soft_reg64_rw_t>(reg).write(field, value); -                else -                    uhd::soft_register_base::cast<uhd::soft_reg64_wo_t>(reg).write(field, value); -            break; - -            default: -                throw uhd::assertion_error("multi_usrp::write_register - register has invalid bitwidth"); -            } - -        } else { -            throw uhd::not_implemented_error("multi_usrp::write_register - register IO not supported for this device"); -        } -    } - -    uint64_t read_register(const std::string &path, const uint32_t field, const size_t mboard) +    // The next three methods are only for RFNoC devices +    std::vector<std::string> get_gpio_srcs(const std::string&, const size_t)      { -        if (_tree->exists(mb_root(mboard) / "registers")) -        { -            uhd::soft_regmap_accessor_t::sptr accessor = -                _tree->access<uhd::soft_regmap_accessor_t::sptr>(mb_root(mboard) / "registers").get(); -            uhd::soft_register_base& reg = accessor->lookup(path); - -            if (not reg.is_readable()) { -                throw uhd::runtime_error("multi_usrp::read_register - register not readable: " + path); -            } - -            switch (reg.get_bitwidth()) { -            case 32: -                if (reg.is_writable()) -                    return static_cast<uint64_t>(uhd::soft_register_base::cast<uhd::soft_reg32_rw_t>(reg).read(field)); -                else -                    return static_cast<uint64_t>(uhd::soft_register_base::cast<uhd::soft_reg32_ro_t>(reg).read(field)); -            break; - -            case 64: -                if (reg.is_writable()) -                    return uhd::soft_register_base::cast<uhd::soft_reg64_rw_t>(reg).read(field); -                else -                    return uhd::soft_register_base::cast<uhd::soft_reg64_ro_t>(reg).read(field); -            break; - -            default: -                throw uhd::assertion_error("multi_usrp::read_register - register has invalid bitwidth: " + path); -            } -        } -        throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); +        throw uhd::not_implemented_error( +            "get_gpio_srcs() not implemented for this motherboard!");      } -    std::vector<std::string> enumerate_registers(const size_t mboard) +    std::vector<std::string> get_gpio_src(const std::string&, const size_t)      { -        if (_tree->exists(mb_root(mboard) / "registers")) -        { -            uhd::soft_regmap_accessor_t::sptr accessor = -                _tree->access<uhd::soft_regmap_accessor_t::sptr>(mb_root(mboard) / "registers").get(); -            return accessor->enumerate(); -        } else { -            return std::vector<std::string>(); -        } +        throw uhd::not_implemented_error( +            "get_gpio_src() not implemented for this motherboard!");      } -    register_info_t get_register_info(const std::string &path, const size_t mboard = 0) +    void set_gpio_src(const std::string&, const std::vector<std::string>&, const size_t)      { -        if (_tree->exists(mb_root(mboard) / "registers")) -        { -            uhd::soft_regmap_accessor_t::sptr accessor = -                _tree->access<uhd::soft_regmap_accessor_t::sptr>(mb_root(mboard) / "registers").get(); -            uhd::soft_register_base& reg = accessor->lookup(path); - -            register_info_t info; -            info.bitwidth = reg.get_bitwidth(); -            info.readable = reg.is_readable(); -            info.writable = reg.is_writable(); -            return info; -        } else { -            throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); -        } +        throw uhd::not_implemented_error( +            "set_gpio_src() not implemented for this motherboard!");      }  private:      device::sptr _dev;      property_tree::sptr _tree; -    bool _is_device3;      uhd::rfnoc::legacy_compat::sptr _legacy_compat;      struct mboard_chan_pair{ @@ -2516,10 +2316,6 @@ private:      fs_path rx_dsp_root(const size_t chan)      {          mboard_chan_pair mcp = rx_chan_to_mcp(chan); -        if (is_device3()) { -            return _legacy_compat->rx_dsp_root(mcp.mboard, mcp.chan); -        } -          if (_tree->exists(mb_root(mcp.mboard) / "rx_chan_dsp_mapping")) {              std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "rx_chan_dsp_mapping").get();              UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -2544,10 +2340,6 @@ private:      fs_path tx_dsp_root(const size_t chan)      {          mboard_chan_pair mcp = tx_chan_to_mcp(chan); -        if (is_device3()) { -            return _legacy_compat->tx_dsp_root(mcp.mboard, mcp.chan); -        } -          if (_tree->exists(mb_root(mcp.mboard) / "tx_chan_dsp_mapping")) {              std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "tx_chan_dsp_mapping").get();              UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -2571,9 +2363,6 @@ private:      fs_path rx_fe_root(const size_t chan)      {          mboard_chan_pair mcp = rx_chan_to_mcp(chan); -        if (is_device3()) { -            return _legacy_compat->rx_fe_root(mcp.mboard, mcp.chan); -        }          try          {              const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -2588,9 +2377,6 @@ private:      fs_path tx_fe_root(const size_t chan)      {          mboard_chan_pair mcp = tx_chan_to_mcp(chan); -        if (is_device3()) { -            return _legacy_compat->tx_fe_root(mcp.mboard, mcp.chan); -        }          try          {              const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -2709,10 +2495,27 @@ multi_usrp::~multi_usrp(void){      /* NOP */  } +  /***********************************************************************   * The Make Function   **********************************************************************/ -multi_usrp::sptr multi_usrp::make(const device_addr_t &dev_addr){ -    UHD_LOGGER_TRACE("MULTI_USRP") << "multi_usrp::make with args " << dev_addr.to_pp_string() ; -    return sptr(new multi_usrp_impl(dev_addr)); +namespace uhd { namespace rfnoc { namespace detail { +// Forward declare +multi_usrp::sptr make_rfnoc_device( +    detail::rfnoc_device::sptr rfnoc_device, const uhd::device_addr_t& dev_addr); +}}} // namespace uhd::rfnoc::detail + + +multi_usrp::sptr multi_usrp::make(const device_addr_t& dev_addr) +{ +    UHD_LOGGER_TRACE("MULTI_USRP") +        << "multi_usrp::make with args " << dev_addr.to_pp_string(); + +    device::sptr dev = device::make(dev_addr, device::USRP); + +    auto rfnoc_dev = boost::dynamic_pointer_cast<rfnoc::detail::rfnoc_device>(dev); +    if (rfnoc_dev) { +        return rfnoc::detail::make_rfnoc_device(rfnoc_dev, dev_addr); +    } +    return boost::make_shared<multi_usrp_impl>(dev, dev_addr);  } diff --git a/host/lib/usrp/multi_usrp_python.hpp b/host/lib/usrp/multi_usrp_python.hpp index 48da794fb..c9d89888b 100644 --- a/host/lib/usrp/multi_usrp_python.hpp +++ b/host/lib/usrp/multi_usrp_python.hpp @@ -14,18 +14,11 @@  void export_multi_usrp(py::module& m)  {      using multi_usrp      = uhd::usrp::multi_usrp; -    using register_info_t = multi_usrp::register_info_t;      const auto ALL_MBOARDS = multi_usrp::ALL_MBOARDS;      const auto ALL_CHANS = multi_usrp::ALL_CHANS;      const auto ALL_LOS = multi_usrp::ALL_LOS; -    py::class_<register_info_t>(m, "register_info") -        .def_readwrite("bitwidth", ®ister_info_t::bitwidth) -        .def_readwrite("readable", ®ister_info_t::readable) -        .def_readwrite("writable", ®ister_info_t::writable) -        ; -      py::class_<multi_usrp, multi_usrp::sptr>(m, "multi_usrp")          // Factory @@ -172,18 +165,19 @@ void export_multi_usrp(py::module& m)          // GPIO methods          .def("get_gpio_banks"          , &multi_usrp::get_gpio_banks) -        .def("set_gpio_attr"           , (void (multi_usrp::*)(const std::string&, const std::string&, const std::string&, const uint32_t, const size_t)) &multi_usrp::set_gpio_attr, py::arg("bank"), py::arg("attr"), py::arg("value"), py::arg("mask") = 0xffffffff, py::arg("mboard") = 0)          .def("set_gpio_attr"           , (void (multi_usrp::*)(const std::string&, const std::string&, const uint32_t, const uint32_t, const size_t)) &multi_usrp::set_gpio_attr, py::arg("bank"), py::arg("attr"), py::arg("value"), py::arg("mask") = 0xffffffff, py::arg("mboard") = 0)          .def("get_gpio_attr"           , &multi_usrp::get_gpio_attr, py::arg("bank"), py::arg("attr"), py::arg("mboard") = 0) -        .def("enumerate_registers"     , &multi_usrp::enumerate_registers, py::arg("mboard") = 0) -        .def("get_register_info"       , &multi_usrp::get_register_info, py::arg("path"), py::arg("mboard") = 0) -        .def("write_register"          , &multi_usrp::write_register, py::arg("path"), py::arg("field"), py::arg("value"), py::arg("mboard") = 0) -        .def("read_register"           , &multi_usrp::read_register, py::arg("path"), py::arg("field"), py::arg("mboard") = 0) +        .def("get_gpio_srcs"           , &multi_usrp::get_gpio_srcs, py::arg("bank"), py::arg("mboard") = 0) +        .def("get_gpio_src"            , &multi_usrp::get_gpio_src, py::arg("bank"), py::arg("mboard") = 0) +        .def("set_gpio_src"            , &multi_usrp::set_gpio_src, py::arg("bank"), py::arg("src"), py::arg("mboard") = 0)          // Filter API methods -        .def("get_filter_names"        , &multi_usrp::get_filter_names, py::arg("search_mask") = "") -        .def("get_filter"              , &multi_usrp::get_filter) -        .def("set_filter"              , &multi_usrp::set_filter) +        .def("get_rx_filter_names"     , &multi_usrp::get_rx_filter_names) +        .def("get_rx_filter"           , &multi_usrp::get_rx_filter) +        .def("set_rx_filter"           , &multi_usrp::set_rx_filter) +        .def("get_tx_filter_names"     , &multi_usrp::get_tx_filter_names) +        .def("get_tx_filter"           , &multi_usrp::get_tx_filter) +        .def("set_tx_filter"           , &multi_usrp::set_tx_filter)          ;  } diff --git a/host/lib/usrp/multi_usrp_rfnoc.cpp b/host/lib/usrp/multi_usrp_rfnoc.cpp new file mode 100644 index 000000000..757872632 --- /dev/null +++ b/host/lib/usrp/multi_usrp_rfnoc.cpp @@ -0,0 +1,2217 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/ddc_block_control.hpp> +#include <uhd/rfnoc/duc_block_control.hpp> +#include <uhd/rfnoc/filter_node.hpp> +#include <uhd/rfnoc/radio_control.hpp> +#include <uhd/rfnoc_graph.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhdlib/rfnoc/rfnoc_device.hpp> +#include <uhdlib/usrp/gpio_defs.hpp> +#include <unordered_set> +#include <boost/make_shared.hpp> +#include <boost/pointer_cast.hpp> +#include <algorithm> +#include <chrono> +#include <mutex> +#include <thread> +#include <vector> + +using namespace std::chrono_literals; +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { +constexpr char DEFAULT_CPU_FORMAT[] = "fc32"; +constexpr char DEFAULT_OTW_FORMAT[] = "sc16"; +constexpr double RX_SIGN            = +1.0; +constexpr double TX_SIGN            = -1.0; + +/*! Make sure the stream args are valid and can be used by get_tx_stream() + * and get_rx_stream(). + * + */ +stream_args_t sanitize_stream_args(const stream_args_t args_) +{ +    stream_args_t args = args_; +    if (args.cpu_format.empty()) { +        UHD_LOG_DEBUG("MULTI_USRP", +            "get_xx_stream(): cpu_format not specified, defaulting to " +                << DEFAULT_CPU_FORMAT); +        args.cpu_format = DEFAULT_CPU_FORMAT; +    } +    if (args.otw_format.empty()) { +        UHD_LOG_DEBUG("MULTI_USRP", +            "get_xx_stream(): otw_format not specified, defaulting to " +                << DEFAULT_OTW_FORMAT); +        args.otw_format = DEFAULT_OTW_FORMAT; +    } +    if (args.channels.empty()) { +        UHD_LOG_DEBUG( +            "MULTI_USRP", "get_xx_stream(): channels not specified, defaulting to [0]"); +        args.channels = {0}; +    } +    return args; +} + +std::string bytes_to_str(std::vector<uint8_t> str_b) +{ +    return std::string(str_b.cbegin(), str_b.cend()); +} + +} // namespace + +class multi_usrp_rfnoc : public multi_usrp +{ +public: +    struct rx_chan_t +    { +        radio_control::sptr radio; +        ddc_block_control::sptr ddc; // can be nullptr +        size_t block_chan; +    }; + +    struct tx_chan_t +    { +        radio_control::sptr radio; +        duc_block_control::sptr duc; // can be nullptr +        size_t block_chan; +    }; + +    /************************************************************************** +     * Structors +     *************************************************************************/ +    multi_usrp_rfnoc(rfnoc_graph::sptr graph, const device_addr_t& addr) +        : _args(addr), _graph(graph), _tree(_graph->get_tree()) +    { +        // Discover all of the radios on our devices and create a mapping between radio +        // chains and channel numbers +        auto radio_blk_ids = _graph->find_blocks("Radio"); +        // find_blocks doesn't sort, so we need to +        std::sort(radio_blk_ids.begin(), +            radio_blk_ids.end(), +            [](uhd::rfnoc::block_id_t i, uhd::rfnoc::block_id_t j) -> bool { +                if (i.get_device_no() != j.get_device_no()) { +                    return i.get_device_no() < j.get_device_no(); +                } else { +                    return i.get_block_count() < j.get_block_count(); +                } +            }); + +        // If we don't find any radios, we don't have a multi_usrp object +        if (radio_blk_ids.empty()) { +            throw uhd::runtime_error( +                "[multi_usrp] No radios found in connected devices."); +        } +        // Next, we assign block controllers to RX channels +        // Note that we don't want to connect blocks now; we will wait until we create and +        // connect a streamer. This gives us a little more time to figure out the desired +        // values of our properties (such as master clock) +        size_t musrp_rx_channel = 0; +        size_t musrp_tx_channel = 0; +        for (auto radio_id : radio_blk_ids) { +            auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id); +            // We assume that the DDC connected to this radio block has the same mboard, +            // instance, and port number +            auto ddc_id = +                block_id_t(radio_id.get_device_no(), "DDC", radio_id.get_block_count()); +            uhd::rfnoc::ddc_block_control::sptr ddc_blk; +            try { +                ddc_blk = _graph->get_block<uhd::rfnoc::ddc_block_control>(ddc_id); +            } catch (const uhd::exception&) { +                UHD_LOGGER_TRACE("MULTI_USRP") +                    << boost::format("No DDC found: %s") % ddc_id.to_string(); +            } +            for (size_t block_chan = 0; block_chan < radio_blk->get_num_output_ports(); +                 ++block_chan) { +                // Figure out if this channel has a DDC available +                auto this_chan_ddc = +                    ddc_blk +                            && _graph->is_connectable( +                                   radio_id, block_chan, ddc_id, block_chan) +                        ? ddc_blk +                        : nullptr; +                _rx_chans.emplace( +                    musrp_rx_channel, rx_chan_t({radio_blk, this_chan_ddc, block_chan})); +                if (!this_chan_ddc) { +                    UHD_LOGGER_DEBUG("MULTI_USRP") +                        << boost::format( +                               "Radio %s unable to connect to DDC %s on channel %d") +                               % radio_id.to_string() % ddc_id.to_string() % block_chan; +                } else { +                    UHD_LOG_DEBUG("MULTI_USRP", +                        "RX Channel " << musrp_rx_channel << " has " +                                      << radio_id.to_string() << " and DDC " +                                      << ddc_id.to_string()); +                } +                ++musrp_rx_channel; // Increment after logging so we print the correct +                                    // value +            } +            // We assume that the DUC connected to this radio block has the same mboard, +            // instance, and port number +            auto duc_id = +                block_id_t(radio_id.get_device_no(), "DUC", radio_id.get_block_count()); +            uhd::rfnoc::duc_block_control::sptr duc_blk; +            try { +                duc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(duc_id); +            } catch (const uhd::exception&) { +                UHD_LOGGER_TRACE("MULTI_USRP") +                    << boost::format("No DUC found: %s") % duc_id.to_string(); +            } +            for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports(); +                 ++block_chan) { +                auto this_chan_duc = +                    duc_blk +                            && _graph->is_connectable( +                                   duc_id, block_chan, radio_id, block_chan) +                        ? duc_blk +                        : nullptr; +                _tx_chans.emplace( +                    musrp_tx_channel, tx_chan_t({radio_blk, this_chan_duc, block_chan})); +                if (!this_chan_duc) { +                    UHD_LOGGER_DEBUG("MULTI_USRP") +                        << boost::format( +                               "Radio %s unable to connect to DUC %s on channel %d") +                               % radio_id.to_string() % duc_id.to_string() % block_chan; +                } else { +                    UHD_LOG_DEBUG("MULTI_USRP", +                        "TX Channel " << musrp_tx_channel << " has " +                                      << radio_id.to_string() << " and DUC " +                                      << duc_id.to_string()); +                } +                ++musrp_tx_channel; // Increment after logging so we print the correct +                                    // value +            } +        } +        _graph->commit(); +    } + +    ~multi_usrp_rfnoc() +    { +        // nop +    } + +    // Direct device access makes no sense with RFNoC +    device::sptr get_device(void) +    { +        return nullptr; +    } + +    rx_streamer::sptr get_rx_stream(const stream_args_t& args_) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        stream_args_t args = sanitize_stream_args(args_); +        // Note that we don't release the graph, which means that property +        // propagation is possible. This is necessary so we don't disrupt +        // existing streamers. We use the _graph_mutex to try and avoid any +        // property propagation where possible. +        double rate = 1.0; +        // This will create an unconnected streamer +        auto rx_streamer = _graph->create_rx_streamer(args.channels.size(), args); +        for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) { +            auto rx_channel = args.channels.at(strm_port); +            auto rx_chain   = _get_rx_chan(rx_channel); +            if (rx_chain.ddc) { +                _graph->connect(rx_chain.radio->get_block_id(), +                    rx_chain.block_chan, +                    rx_chain.ddc->get_block_id(), +                    rx_chain.block_chan); +            } +            _graph->connect((rx_chain.ddc) ? rx_chain.ddc->get_block_id() +                                           : rx_chain.radio->get_block_id(), +                rx_chain.block_chan, +                rx_streamer, +                strm_port); +            const double chan_rate = +                _rx_rates.count(rx_channel) ? _rx_rates.at(rx_channel) : 1.0; +            if (chan_rate > 1.0 && rate != chan_rate) { +                UHD_LOG_DEBUG("MULTI_USRP", +                    "Inconsistent RX rates when creating streamer! Harmonizing " +                    "to " +                        << chan_rate); +                rate = chan_rate; +            } +        } +        // Now everything is connected, commit() again so we can have stream +        // commands go through the graph +        _graph->commit(); + +        // Before we return the streamer, we may need to reapply the rate. This +        // is necessary whenever the blocks were configured before the streamer +        // was created, because we don't know what state the graph is in after +        // commit() was called in that case.. +        if (rate > 1.0) { +            UHD_LOG_TRACE("MULTI_USRP", +                "Now reapplying RX rate " << (rate / 1e6) +                                          << " MHz to all streamer channels"); +            for (auto rx_channel : args.channels) { +                auto rx_chain = _get_rx_chan(rx_channel); +                if (rx_chain.ddc) { +                    rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan); +                } else { +                    rx_chain.radio->set_rate(rate); +                } +            } +        } +        return rx_streamer; +    } + +    tx_streamer::sptr get_tx_stream(const stream_args_t& args_) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        stream_args_t args = sanitize_stream_args(args_); +        // Note that we don't release the graph, which means that property +        // propagation is possible. This is necessary so we don't disrupt +        // existing streamers. We use the _graph_mutex to try and avoid any +        // property propagation where possible. +        double rate = 1.0; +        // This will create an unconnected streamer +        auto tx_streamer = _graph->create_tx_streamer(args.channels.size(), args); +        for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) { +            auto tx_channel = args.channels.at(strm_port); +            auto tx_chain   = _get_tx_chan(tx_channel); +            if (tx_chain.duc) { +                _graph->connect(tx_chain.duc->get_block_id(), +                    tx_chain.block_chan, +                    tx_chain.radio->get_block_id(), +                    tx_chain.block_chan); +            } +            _graph->connect(tx_streamer, +                strm_port, +                (tx_chain.duc) ? tx_chain.duc->get_block_id() +                               : tx_chain.radio->get_block_id(), +                tx_chain.block_chan); +            const double chan_rate = +                _tx_rates.count(tx_channel) ? _tx_rates.at(tx_channel) : 1.0; +            if (chan_rate > 1.0 && rate != chan_rate) { +                UHD_LOG_DEBUG("MULTI_USRP", +                    "Inconsistent TX rates when creating streamer! Harmonizing " +                    "to " +                        << chan_rate); +                rate = chan_rate; +            } +        } +        // Now everything is connected, commit() again so we can have stream +        // commands go through the graph +        _graph->commit(); + +        // Before we return the streamer, we may need to reapply the rate. This +        // is necessary whenever the blocks were configured before the streamer +        // was created, because we don't know what state the graph is in after +        // commit() was called in that case, or we could have configured blocks +        // to run at different rates (see the warning above). +        if (rate > 1.0) { +            UHD_LOG_TRACE("MULTI_USRP", +                "Now reapplying TX rate " << (rate / 1e6) +                                          << " MHz to all streamer channels"); +            for (auto tx_channel : args.channels) { +                auto tx_chain = _get_tx_chan(tx_channel); +                if (tx_chain.duc) { +                    tx_chain.duc->set_input_rate(rate, tx_chain.block_chan); +                } else { +                    tx_chain.radio->set_rate(rate); +                } +            } +        } +        return tx_streamer; +    } + + +    /*********************************************************************** +     * Helper methods +     **********************************************************************/ +    /*! The CORDIC can be used to shift the baseband below / past the tunable +     * limits of the actual RF front-end. The baseband filter, located on the +     * daughterboard, however, limits the useful instantaneous bandwidth. We +     * allow the user to tune to the edge of the filter, where the roll-off +     * begins.  This prevents the user from tuning past the point where less +     * than half of the spectrum would be useful. +     */ +    static meta_range_t make_overall_tune_range( +        const meta_range_t& fe_range, const meta_range_t& dsp_range, const double bw) +    { +        meta_range_t range; +        for (const range_t& sub_range : fe_range) { +            range.push_back( +                range_t(sub_range.start() + std::max(dsp_range.start(), -bw / 2), +                    sub_range.stop() + std::min(dsp_range.stop(), bw / 2), +                    dsp_range.step())); +        } +        return range; +    } + +    dict<std::string, std::string> get_usrp_rx_info(size_t chan) +    { +        auto& rx_chain      = _get_rx_chan(chan); +        const size_t mb_idx = rx_chain.radio->get_block_id().get_device_no(); +        auto mbc            = get_mbc(mb_idx); +        auto mb_eeprom      = mbc->get_eeprom(); + +        dict<std::string, std::string> usrp_info; +        usrp_info["mboard_id"]      = mbc->get_mboard_name(); +        usrp_info["mboard_name"]    = mb_eeprom.get("name", "n/a"); +        usrp_info["mboard_serial"]  = mb_eeprom.get("serial", "n/a"); +        usrp_info["rx_subdev_name"] = get_rx_subdev_name(chan); +        usrp_info["rx_subdev_spec"] = get_rx_subdev_spec(mb_idx).to_string(); +        usrp_info["rx_antenna"]     = get_rx_antenna(chan); + +        const auto db_eeprom = rx_chain.radio->get_db_eeprom(); +        usrp_info["rx_serial"] = +            db_eeprom.count("rx_serial") ? bytes_to_str(db_eeprom.at("rx_serial")) : ""; +        usrp_info["rx_id"] = +            db_eeprom.count("rx_id") ? bytes_to_str(db_eeprom.at("rx_id")) : ""; + +        return usrp_info; +    } + +    dict<std::string, std::string> get_usrp_tx_info(size_t chan) +    { +        auto& tx_chain      = _get_tx_chan(chan); +        const size_t mb_idx = tx_chain.radio->get_block_id().get_device_no(); +        auto mbc            = get_mbc(mb_idx); +        auto mb_eeprom      = mbc->get_eeprom(); + +        dict<std::string, std::string> usrp_info; +        usrp_info["mboard_id"]      = mbc->get_mboard_name(); +        usrp_info["mboard_name"]    = mb_eeprom.get("name", "n/a"); +        usrp_info["mboard_serial"]  = mb_eeprom.get("serial", "n/a"); +        usrp_info["tx_subdev_name"] = get_tx_subdev_name(chan); +        usrp_info["tx_subdev_spec"] = get_tx_subdev_spec(mb_idx).to_string(); +        usrp_info["tx_antenna"]     = get_tx_antenna(chan); + +        const auto db_eeprom = tx_chain.radio->get_db_eeprom(); +        usrp_info["tx_serial"] = +            db_eeprom.count("tx_serial") ? bytes_to_str(db_eeprom.at("tx_serial")) : ""; +        usrp_info["tx_id"] = +            db_eeprom.count("tx_id") ? bytes_to_str(db_eeprom.at("tx_id")) : ""; + +        return usrp_info; +    } + +    /*! Tune the appropriate radio chain to the requested frequency. +     *  The general algorithm is the same for RX and TX, so we can pass in lambdas to do +     * the setting/getting for us. +     */ +    tune_result_t tune_xx_subdev_and_dsp(const double xx_sign, +        freq_range_t tune_range, +        freq_range_t rf_freq_range, +        freq_range_t dsp_freq_range, +        std::function<void(double)> set_rf_freq, +        std::function<double()> get_rf_freq, +        std::function<void(double)> set_dsp_freq, +        std::function<double()> get_dsp_freq, +        const tune_request_t& tune_request) +    { +        double clipped_requested_freq = tune_range.clip(tune_request.target_freq); +        UHD_LOGGER_TRACE("MULTI_USRP") +            << boost::format("Frequency Range %.3fMHz->%.3fMHz") +                   % (tune_range.start() / 1e6) % (tune_range.stop() / 1e6); +        UHD_LOGGER_TRACE("MULTI_USRP") +            << "Clipped RX frequency requested: " +                   + std::to_string(clipped_requested_freq / 1e6) + "MHz"; + +        //------------------------------------------------------------------ +        //-- set the RF frequency depending upon the policy +        //------------------------------------------------------------------ +        double target_rf_freq = 0.0; +        switch (tune_request.rf_freq_policy) { +            case tune_request_t::POLICY_AUTO: +                target_rf_freq = clipped_requested_freq; +                break; + +            case tune_request_t::POLICY_MANUAL: +                target_rf_freq = rf_freq_range.clip(tune_request.rf_freq); +                break; + +            case tune_request_t::POLICY_NONE: +                break; // does not set +        } +        UHD_LOGGER_TRACE("MULTI_USRP") +            << "Target RF Freq: " + std::to_string(target_rf_freq / 1e6) + "MHz"; + +        //------------------------------------------------------------------ +        //-- Tune the RF frontend +        //------------------------------------------------------------------ +        if (tune_request.rf_freq_policy != tune_request_t::POLICY_NONE) { +            set_rf_freq(target_rf_freq); +        } +        const double actual_rf_freq = get_rf_freq(); + +        //------------------------------------------------------------------ +        //-- Set the DSP frequency depending upon the DSP frequency policy. +        //------------------------------------------------------------------ +        double target_dsp_freq = 0.0; +        switch (tune_request.dsp_freq_policy) { +            case tune_request_t::POLICY_AUTO: +                /* If we are using the AUTO tuning policy, then we prevent the +                 * CORDIC from spinning us outside of the range of the baseband +                 * filter, regardless of what the user requested. This could happen +                 * if the user requested a center frequency so far outside of the +                 * tunable range of the FE that the CORDIC would spin outside the +                 * filtered baseband. */ +                target_dsp_freq = actual_rf_freq - clipped_requested_freq; + +                // invert the sign on the dsp freq for transmit (spinning up vs down) +                target_dsp_freq *= xx_sign; + +                break; + +            case tune_request_t::POLICY_MANUAL: +                /* If the user has specified a manual tune policy, we will allow +                 * tuning outside of the baseband filter, but will still clip the +                 * target DSP frequency to within the bounds of the CORDIC to +                 * prevent undefined behavior (likely an overflow). */ +                target_dsp_freq = dsp_freq_range.clip(tune_request.dsp_freq); +                break; + +            case tune_request_t::POLICY_NONE: +                break; // does not set +        } +        UHD_LOGGER_TRACE("MULTI_USRP") +            << "Target DSP Freq: " + std::to_string(target_dsp_freq / 1e6) + "MHz"; + +        //------------------------------------------------------------------ +        //-- Tune the DSP +        //------------------------------------------------------------------ +        if (tune_request.dsp_freq_policy != tune_request_t::POLICY_NONE) { +            set_dsp_freq(target_dsp_freq); +        } +        const double actual_dsp_freq = get_dsp_freq(); + +        //------------------------------------------------------------------ +        //-- Load and return the tune result +        //------------------------------------------------------------------ +        tune_result_t tune_result; +        tune_result.clipped_rf_freq = clipped_requested_freq; +        tune_result.target_rf_freq  = target_rf_freq; +        tune_result.actual_rf_freq  = actual_rf_freq; +        tune_result.target_dsp_freq = target_dsp_freq; +        tune_result.actual_dsp_freq = actual_dsp_freq; +        return tune_result; +    } + +    /******************************************************************* +     * Mboard methods +     ******************************************************************/ +    void set_master_clock_rate(double rate, size_t mboard = ALL_MBOARDS) +    { +        for (auto& chain : _rx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard +                || mboard == ALL_MBOARDS) { +                radio->set_rate(rate); +            } +        } +        for (auto& chain : _tx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard +                || mboard == ALL_MBOARDS) { +                radio->set_rate(rate); +            } +        } +    } + +    double get_master_clock_rate(size_t mboard) +    { +        // We pick the first radio we can find on this mboard, and hope that all +        // radios have the same range. +        for (auto& chain : _rx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard) { +                return radio->get_rate(); +            } +        } +        for (auto& chain : _tx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard) { +                return radio->get_rate(); +            } +        } +        throw uhd::key_error("Invalid mboard index!"); +    } + +    meta_range_t get_master_clock_rate_range(const size_t mboard = 0) +    { +        // We pick the first radio we can find on this mboard, and hope that all +        // radios have the same range. +        for (auto& chain : _rx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard) { +                return radio->get_rate_range(); +            } +        } +        for (auto& chain : _tx_chans) { +            auto radio = chain.second.radio; +            if (radio->get_block_id().get_device_no() == mboard) { +                return radio->get_rate_range(); +            } +        } +        throw uhd::key_error("Invalid mboard index!"); +    } + +    std::string get_pp_string(void) +    { +        std::string buff = str(boost::format("%s USRP:\n" +                                             "  Device: %s\n") +                               % ((get_num_mboards() > 1) ? "Multi" : "Single") +                               % (_tree->access<std::string>("/name").get())); +        for (size_t m = 0; m < get_num_mboards(); m++) { +            buff += str( +                boost::format("  Mboard %d: %s\n") % m % get_mbc(m)->get_mboard_name()); +        } + + +        //----------- rx side of life ---------------------------------- +        for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) { +            buff += str(boost::format("  RX Channel: %u\n" +                                      "    RX DSP: %s\n" +                                      "    RX Dboard: %s\n" +                                      "    RX Subdev: %s\n") +                        % rx_chan +                        % (_rx_chans.at(rx_chan).ddc ? std::to_string(rx_chan) : "n/a") +                        % _rx_chans.at(rx_chan).radio->get_slot_name() +                        % get_rx_subdev_name(rx_chan)); +        } + +        //----------- tx side of life ---------------------------------- +        for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) { +            buff += str(boost::format("  TX Channel: %u\n" +                                      "    TX DSP: %s\n" +                                      "    TX Dboard: %s\n" +                                      "    TX Subdev: %s\n") +                        % tx_chan +                        % (_tx_chans.at(tx_chan).duc ? std::to_string(tx_chan) : "n/a") +                        % _tx_chans.at(tx_chan).radio->get_slot_name() +                        % get_tx_subdev_name(tx_chan)); +        } + +        return buff; +    } + +    std::string get_mboard_name(size_t mboard = 0) +    { +        return get_mbc(mboard)->get_mboard_name(); +    } + +    time_spec_t get_time_now(size_t mboard = 0) +    { +        return get_mbc(mboard)->get_timekeeper(0)->get_time_now(); +    } + +    time_spec_t get_time_last_pps(size_t mboard = 0) +    { +        return get_mbc(mboard)->get_timekeeper(0)->get_time_last_pps(); +    } + +    void set_time_now(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_time_now(time_spec, i); +            } +            return; +        } +        get_mbc(mboard)->get_timekeeper(0)->set_time_now(time_spec); +    } + +    void set_time_next_pps(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_time_next_pps(time_spec, i); +            } +            return; +        } +        get_mbc(mboard)->get_timekeeper(0)->set_time_next_pps(time_spec); +    } + +    void set_time_unknown_pps(const time_spec_t& time_spec) +    { +        UHD_LOGGER_INFO("MULTI_USRP") << "    1) catch time transition at pps edge"; +        auto end_time                   = std::chrono::steady_clock::now() + 1100ms; +        time_spec_t time_start_last_pps = get_time_last_pps(); +        while (time_start_last_pps == get_time_last_pps()) { +            if (std::chrono::steady_clock::now() > end_time) { +                throw uhd::runtime_error("Board 0 may not be getting a PPS signal!\n" +                                         "No PPS detected within the time interval.\n" +                                         "See the application notes for your device.\n"); +            } +            std::this_thread::sleep_for(1ms); +        } + +        UHD_LOGGER_INFO("MULTI_USRP") << "    2) set times next pps (synchronously)"; +        set_time_next_pps(time_spec, ALL_MBOARDS); +        std::this_thread::sleep_for(1s); + +        // verify that the time registers are read to be within a few RTT +        for (size_t m = 1; m < get_num_mboards(); m++) { +            time_spec_t time_0 = this->get_time_now(0); +            time_spec_t time_i = this->get_time_now(m); +            // 10 ms: greater than RTT but not too big +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) { +                UHD_LOGGER_WARNING("MULTI_USRP") +                    << boost::format( +                           "Detected time deviation between board %d and board 0.\n" +                           "Board 0 time is %f seconds.\n" +                           "Board %d time is %f seconds.\n") +                           % m % time_0.get_real_secs() % m % time_i.get_real_secs(); +            } +        } +    } + +    bool get_time_synchronized(void) +    { +        for (size_t m = 1; m < get_num_mboards(); m++) { +            time_spec_t time_0 = this->get_time_now(0); +            time_spec_t time_i = this->get_time_now(m); +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) { +                return false; +            } +        } +        return true; +    } + +    void set_command_time(const uhd::time_spec_t& time_spec, size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_command_time(time_spec, i); +            } +            return; +        } + +        // Set command time on all the blocks that are connected +        for (auto& chain : _rx_chans) { +            chain.second.radio->set_command_time(time_spec, chain.second.block_chan); +            if (chain.second.ddc) { +                chain.second.ddc->set_command_time(time_spec, chain.second.block_chan); +            } +        } +        for (auto& chain : _tx_chans) { +            chain.second.radio->set_command_time(time_spec, chain.second.block_chan); +            if (chain.second.duc) { +                chain.second.duc->set_command_time(time_spec, chain.second.block_chan); +            } +        } +    } + +    void clear_command_time(size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                clear_command_time(i); +            } +            return; +        } + +        // Set command time on all the blocks that are connected +        for (auto& chain : _rx_chans) { +            chain.second.radio->clear_command_time(chain.second.block_chan); +            if (chain.second.ddc) { +                chain.second.ddc->clear_command_time(chain.second.block_chan); +            } +        } +        for (auto& chain : _tx_chans) { +            chain.second.radio->clear_command_time(chain.second.block_chan); +            if (chain.second.duc) { +                chain.second.duc->clear_command_time(chain.second.block_chan); +            } +        } +    } + +    void issue_stream_cmd(const stream_cmd_t& stream_cmd, size_t chan = ALL_CHANS) +    { +        if (chan != ALL_CHANS) { +            auto& rx_chain = _get_rx_chan(chan); +            if (rx_chain.ddc) { +                rx_chain.ddc->issue_stream_cmd(stream_cmd, rx_chain.block_chan); +            } else { +                rx_chain.radio->issue_stream_cmd(stream_cmd, rx_chain.block_chan); +            } +            return; +        } +        for (size_t c = 0; c < get_rx_num_channels(); c++) { +            issue_stream_cmd(stream_cmd, c); +        } +    } + +    void set_time_source(const std::string& source, const size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_time_source(source, i); +            } +            return; +        } +        get_mbc(mboard)->set_time_source(source); +    } + +    std::string get_time_source(const size_t mboard) +    { +        return get_mbc(mboard)->get_time_source(); +    } + +    std::vector<std::string> get_time_sources(const size_t mboard) +    { +        return get_mbc(mboard)->get_time_sources(); +    } + +    void set_clock_source(const std::string& source, const size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_clock_source(source, i); +            } +            return; +        } +        get_mbc(mboard)->set_clock_source(source); +    } + +    std::string get_clock_source(const size_t mboard) +    { +        return get_mbc(mboard)->get_clock_source(); +    } + +    std::vector<std::string> get_clock_sources(const size_t mboard) +    { +        return get_mbc(mboard)->get_clock_sources(); +    } + +    void set_sync_source(const std::string& clock_source, +        const std::string& time_source, +        const size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_sync_source(clock_source, time_source, i); +            } +            return; +        } +        get_mbc(mboard)->set_sync_source(clock_source, time_source); +    } + +    void set_sync_source( +        const device_addr_t& sync_source, const size_t mboard = ALL_MBOARDS) +    { +        if (mboard == ALL_MBOARDS) { +            for (size_t i = 0; i < get_num_mboards(); ++i) { +                set_sync_source(sync_source, i); +            } +            return; +        } +        get_mbc(mboard)->set_sync_source(sync_source); +    } + +    device_addr_t get_sync_source(const size_t mboard) +    { +        return get_mbc(mboard)->get_sync_source(); +    } + +    std::vector<device_addr_t> get_sync_sources(const size_t mboard) +    { +        return get_mbc(mboard)->get_sync_sources(); +    } + +    void set_clock_source_out(const bool enb, const size_t mboard = ALL_MBOARDS) +    { +        get_mbc(mboard)->set_clock_source_out(enb); +    } + +    void set_time_source_out(const bool enb, const size_t mboard = ALL_MBOARDS) +    { +        get_mbc(mboard)->set_time_source_out(enb); +    } + +    size_t get_num_mboards(void) +    { +        return _graph->get_num_mboards(); +    } + +    sensor_value_t get_mboard_sensor(const std::string& name, size_t mboard = 0) +    { +        return get_mbc(mboard)->get_sensor(name); +    } + +    std::vector<std::string> get_mboard_sensor_names(size_t mboard = 0) +    { +        return get_mbc(mboard)->get_sensor_names(); +    } + +    // This only works on the USRP2 and B100, both of which are not rfnoc_device +    void set_user_register(const uint8_t, const uint32_t, size_t) +    { +        throw uhd::not_implemented_error( +            "set_user_register(): Not implemented on this device!"); +    } + +    // This only works on the B200, which is not an rfnoc_device +    uhd::wb_iface::sptr get_user_settings_iface(const size_t) +    { +        return nullptr; +    } + +    /******************************************************************* +     * RX methods +     ******************************************************************/ +    rx_chan_t _generate_rx_radio_chan(block_id_t radio_id, size_t block_chan) +    { +        auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id); +        // We assume that the DDC connected to this radio block has the same mboard, +        // instance, and port number +        auto ddc_id = +            block_id_t(radio_id.get_device_no(), "DDC", radio_id.get_block_count()); +        uhd::rfnoc::ddc_block_control::sptr ddc_blk; +        try { +            ddc_blk = _graph->get_block<uhd::rfnoc::ddc_block_control>(ddc_id); +        } catch (const uhd::lookup_error&) { +            UHD_LOGGER_TRACE("MULTI_USRP") << "No DDC found: " << ddc_id.to_string(); +        } +        // Figure out if this channel has a DDC available +        auto this_chan_ddc = +            ddc_blk && _graph->is_connectable(radio_id, block_chan, ddc_id, block_chan) +                ? ddc_blk +                : nullptr; +        return {radio_blk, this_chan_ddc, block_chan}; +    } + +    std::vector<rx_chan_t> _generate_mboard_rx_chans( +        const uhd::usrp::subdev_spec_t& spec, size_t mboard) +    { +        // Discover all of the radios on our devices and create a mapping between radio +        // chains and channel numbers +        auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio"); +        // find_blocks doesn't sort, so we need to +        std::sort(radio_blk_ids.begin(), +            radio_blk_ids.end(), +            [](uhd::rfnoc::block_id_t i, uhd::rfnoc::block_id_t j) -> bool { +                if (i.get_device_no() != j.get_device_no()) { +                    return i.get_device_no() < j.get_device_no(); +                } else { +                    return i.get_block_count() < j.get_block_count(); +                } +            }); + +        // If we don't find any radios, we don't have a multi_usrp object +        if (radio_blk_ids.empty()) { +            throw uhd::runtime_error( +                "[multi_usrp] No radios found in the requested mboard: " +                + std::to_string(mboard)); +        } + +        // Iterate through the subdev pairs, and try to find a radio that matches +        std::vector<rx_chan_t> new_chans; +        for (auto chan_subdev_pair : spec) { +            bool subdev_found = false; +            for (auto radio_id : radio_blk_ids) { +                auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id); +                size_t block_chan; +                try { +                    block_chan = radio_blk->get_chan_from_dboard_fe( +                        chan_subdev_pair.sd_name, RX_DIRECTION); +                } catch (const uhd::lookup_error&) { +                    // This is OK, since we're probing all radios, this +                    // particular radio may not have the requested frontend name +                    // so it's not one that we want in this list. +                    continue; +                } +                subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(), +                    radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION)); +                if (chan_subdev_pair == radio_subdev) { +                    new_chans.push_back(_generate_rx_radio_chan(radio_id, block_chan)); +                    subdev_found = true; +                } +            } +            if (!subdev_found) { +                std::string err_msg("Could not find radio on mboard " +                                    + std::to_string(mboard) + " that matches subdev " +                                    + chan_subdev_pair.db_name + ":" +                                    + chan_subdev_pair.sd_name); +                UHD_LOG_ERROR("MULTI_USRP", err_msg); +                throw uhd::lookup_error(err_msg); +            } +        } +        UHD_LOG_TRACE("MULTI_USRP", +            std::string("Using RX subdev " + spec.to_string() + ", found ") +                + std::to_string(new_chans.size()) + " channels for mboard " +                + std::to_string(mboard)); +        return new_chans; +    } + +    void set_rx_subdev_spec( +        const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS) +    { +        // First, generate a vector of the RX channels that we need to register +        auto new_rx_chans = [this, spec, mboard]() { +            /* When setting the subdev spec in multiple mboard scenarios, there are two +             * cases we need to handle: +             * 1. Setting all mboard to the same subdev spec. This is the easy case. +             * 2. Setting a single mboard's subdev spec. In this case, we need to update +             * the requested mboard's subdev spec, and keep the old subdev spec for the +             * other mboards. +             */ +            std::vector<rx_chan_t> new_rx_chans; +            for (size_t current_mboard = 0; current_mboard < get_num_mboards(); +                 ++current_mboard) { +                auto current_spec = [this, spec, mboard, current_mboard]() { +                    if (mboard == ALL_MBOARDS || mboard == current_mboard) { +                        // Update all mboards to the same subdev spec OR +                        // only update this mboard to the new subdev spec +                        return spec; +                    } else { +                        // Keep the old subdev spec for this mboard +                        return get_rx_subdev_spec(current_mboard); +                    } +                }(); +                auto new_mboard_chans = +                    _generate_mboard_rx_chans(current_spec, current_mboard); +                new_rx_chans.insert( +                    new_rx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end()); +            } +            return new_rx_chans; +        }(); + +        // Now register them +        _rx_chans.clear(); +        for (size_t rx_chan = 0; rx_chan < new_rx_chans.size(); ++rx_chan) { +            _rx_chans.emplace(rx_chan, new_rx_chans.at(rx_chan)); +        } +    } + +    uhd::usrp::subdev_spec_t get_rx_subdev_spec(size_t mboard) +    { +        uhd::usrp::subdev_spec_t result; +        for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) { +            auto& rx_chain = _rx_chans.at(rx_chan); +            if (rx_chain.radio->get_block_id().get_device_no() == mboard) { +                result.push_back( +                    uhd::usrp::subdev_spec_pair_t(rx_chain.radio->get_slot_name(), +                        rx_chain.radio->get_dboard_fe_from_chan( +                            rx_chain.block_chan, uhd::RX_DIRECTION))); +            } +        } + +        return result; +    } + +    size_t get_rx_num_channels(void) +    { +        return _rx_chans.size(); +    } + +    std::string get_rx_subdev_name(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_fe_name(rx_chain.block_chan, uhd::RX_DIRECTION); +    } + +    void set_rx_rate(double rate, size_t chan = ALL_CHANS) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        if (chan == ALL_CHANS) { +            for (size_t chan = 0; chan < _rx_chans.size(); ++chan) { +                set_rx_rate(rate, chan); +            } +            return; +        } +        const double actual_rate = [&]() { +            auto rx_chain = _get_rx_chan(chan); +            if (rx_chain.ddc) { +                return rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan); +            } else { +                return rx_chain.radio->set_rate(rate); +            } +        }(); +        if (actual_rate != rate) { +            UHD_LOGGER_WARNING("MULTI_USRP") +                << boost::format( +                       "Could not set RX rate to %.3f MHz. Actual rate is %.3f MHz") +                       % (rate / 1.0e6) % (actual_rate / 1.0e6); +        } +        _rx_rates[chan] = actual_rate; +    } + +    double get_rx_rate(size_t chan) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        auto& rx_chain = _get_rx_chan(chan); +        if (rx_chain.ddc) { +            return rx_chain.ddc->get_output_rate(rx_chain.block_chan); +        } +        return rx_chain.radio->get_rate(); +    } + +    meta_range_t get_rx_rates(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return (rx_chain.ddc) +                   ? make_overall_tune_range( +                         rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan), +                         rx_chain.ddc->get_frequency_range(rx_chain.block_chan), +                         rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan)) +                   : rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan); +    } + +    tune_result_t set_rx_freq(const tune_request_t& tune_request, size_t chan) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        // TODO: Add external LO warning + +        auto rx_chain = _get_rx_chan(chan); + +        rx_chain.radio->set_rx_tune_args(tune_request.args, rx_chain.block_chan); +        //------------------------------------------------------------------ +        //-- calculate the tunable frequency ranges of the system +        //------------------------------------------------------------------ +        freq_range_t tune_range = +            (rx_chain.ddc) +                ? make_overall_tune_range( +                      rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan), +                      rx_chain.ddc->get_frequency_range(rx_chain.block_chan), +                      rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan)) +                : rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan); + +        freq_range_t rf_range = +            rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan); +        freq_range_t dsp_range = +            (rx_chain.ddc) ? rx_chain.ddc->get_frequency_range(rx_chain.block_chan) +                           : meta_range_t(0, 0); +        // Create lambdas to feed to tune_xx_subdev_and_dsp() +        // Note: If there is no DDC present, register empty lambdas for the DSP functions +        auto set_rf_freq = [rx_chain](double freq) { +            rx_chain.radio->set_rx_frequency(freq, rx_chain.block_chan); +        }; +        auto get_rf_freq = [rx_chain](void) { +            return rx_chain.radio->get_rx_frequency(rx_chain.block_chan); +        }; +        auto set_dsp_freq = [rx_chain](double freq) { +            (rx_chain.ddc) ? rx_chain.ddc->set_freq(freq, rx_chain.block_chan) : 0; +        }; +        auto get_dsp_freq = [rx_chain](void) { +            return (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0; +        }; +        return tune_xx_subdev_and_dsp(RX_SIGN, +            tune_range, +            rf_range, +            dsp_range, +            set_rf_freq, +            get_rf_freq, +            set_dsp_freq, +            get_dsp_freq, +            tune_request); +    } + +    double get_rx_freq(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); + +        // extract actual dsp and IF frequencies +        const double actual_rf_freq = +            rx_chain.radio->get_rx_frequency(rx_chain.block_chan); +        const double actual_dsp_freq = +            (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0; + +        // invert the sign on the dsp freq for transmit +        return actual_rf_freq - actual_dsp_freq * RX_SIGN; +    } + +    freq_range_t get_rx_freq_range(size_t chan) +    { +        auto fe_freq_range = get_fe_rx_freq_range(chan); + +        auto rx_chain = _get_rx_chan(chan); +        uhd::freq_range_t dsp_freq_range = +            (rx_chain.ddc) ? make_overall_tune_range(get_fe_rx_freq_range(chan), +                                 rx_chain.ddc->get_frequency_range(rx_chain.block_chan), +                                 rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan)) +                           : get_fe_rx_freq_range(chan); +        return dsp_freq_range; +    } + +    freq_range_t get_fe_rx_freq_range(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan); +    } + +    /************************************************************************** +     * LO controls +     *************************************************************************/ +    std::vector<std::string> get_rx_lo_names(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_names(rx_chain.block_chan); +    } + +    void set_rx_lo_source(const std::string& src, const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_lo_source(src, name, rx_chain.block_chan); +    } + +    const std::string get_rx_lo_source(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_source(name, rx_chain.block_chan); +    } + +    std::vector<std::string> get_rx_lo_sources(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_sources(name, chan); +    } + +    void set_rx_lo_export_enabled(bool enabled, const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->set_rx_lo_export_enabled( +            enabled, name, rx_chain.block_chan); +    } + +    bool get_rx_lo_export_enabled(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_export_enabled(name, rx_chain.block_chan); +    } + +    double set_rx_lo_freq(double freq, const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->set_rx_lo_freq(freq, name, rx_chain.block_chan); +    } + +    double get_rx_lo_freq(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_freq(name, rx_chain.block_chan); +    } + +    freq_range_t get_rx_lo_freq_range(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_lo_freq_range(name, rx_chain.block_chan); +    } + +    /*** TX LO API ***/ +    std::vector<std::string> get_tx_lo_names(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_names(tx_chain.block_chan); +    } + +    void set_tx_lo_source( +        const std::string& src, const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_lo_source(src, name, tx_chain.block_chan); +    } + +    const std::string get_tx_lo_source(const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_source(name, tx_chain.block_chan); +    } + +    std::vector<std::string> get_tx_lo_sources(const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_sources(name, tx_chain.block_chan); +    } + +    void set_tx_lo_export_enabled( +        const bool enabled, const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_lo_export_enabled(enabled, name, tx_chain.block_chan); +    } + +    bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_export_enabled(name, tx_chain.block_chan); +    } + +    double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->set_tx_lo_freq(freq, name, tx_chain.block_chan); +    } + +    double get_tx_lo_freq(const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_freq(name, tx_chain.block_chan); +    } + +    freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_lo_freq_range(name, tx_chain.block_chan); +    } + +    /************************************************************************** +     * Gain controls +     *************************************************************************/ +    void set_rx_gain(double gain, const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_gain(gain, name, rx_chain.block_chan); +    } + +    std::vector<std::string> get_rx_gain_profile_names(const size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_gain_profile_names(rx_chain.block_chan); +    } + +    void set_rx_gain_profile(const std::string& profile, const size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_gain_profile(profile, rx_chain.block_chan); +    } + +    std::string get_rx_gain_profile(const size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_gain_profile(rx_chain.block_chan); +    } + +    void set_normalized_rx_gain(double gain, size_t chan = 0) +    { +        if (gain > 1.0 || gain < 0.0) { +            throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1]."); +        } +        gain_range_t gain_range = get_rx_gain_range(ALL_GAINS, chan); +        double abs_gain = +            (gain * (gain_range.stop() - gain_range.start())) + gain_range.start(); +        set_rx_gain(abs_gain, ALL_GAINS, chan); +    } + +    void set_rx_agc(bool enable, size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_agc(enable, rx_chain.block_chan); +    } + +    double get_rx_gain(const std::string& name, size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_gain(name, rx_chain.block_chan); +    } + +    double get_normalized_rx_gain(size_t chan) +    { +        gain_range_t gain_range       = get_rx_gain_range(ALL_GAINS, chan); +        const double gain_range_width = gain_range.stop() - gain_range.start(); +        // In case we have a device without a range of gains: +        if (gain_range_width == 0.0) { +            return 0; +        } +        const double norm_gain = +            (get_rx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width; +        // Avoid rounding errors: +        return std::max(std::min(norm_gain, 1.0), 0.0); +    } + +    gain_range_t get_rx_gain_range(const std::string& name, size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_gain_range(name, rx_chain.block_chan); +    } + +    std::vector<std::string> get_rx_gain_names(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_gain_names(rx_chain.block_chan); +    } + +    void set_rx_antenna(const std::string& ant, size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_antenna(ant, rx_chain.block_chan); +    } + +    std::string get_rx_antenna(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_antenna(rx_chain.block_chan); +    } + +    std::vector<std::string> get_rx_antennas(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_antennas(rx_chain.block_chan); +    } + +    void set_rx_bandwidth(double bandwidth, size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        rx_chain.radio->set_rx_bandwidth(bandwidth, rx_chain.block_chan); +    } + +    double get_rx_bandwidth(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan); +    } + +    meta_range_t get_rx_bandwidth_range(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_bandwidth_range(rx_chain.block_chan); +    } + +    dboard_iface::sptr get_rx_dboard_iface(size_t chan) +    { +        auto& rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get(); +    } + +    sensor_value_t get_rx_sensor(const std::string& name, size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_sensor(name, rx_chain.block_chan); +    } + +    std::vector<std::string> get_rx_sensor_names(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_sensor_names(rx_chain.block_chan); +    } + +    void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS) +    { +        if (chan != ALL_CHANS) { +            auto rx_chain = _get_rx_chan(chan); +            rx_chain.radio->set_rx_dc_offset(enb, rx_chain.block_chan); +            return; +        } +        for (size_t ch = 0; ch < get_rx_num_channels(); ++ch) { +            set_rx_dc_offset(enb, ch); +        } +    } + +    void set_rx_dc_offset(const std::complex<double>& offset, size_t chan = ALL_CHANS) +    { +        if (chan != ALL_CHANS) { +            auto rx_chain = _get_rx_chan(chan); +            rx_chain.radio->set_rx_dc_offset(offset, rx_chain.block_chan); +            return; +        } +        for (size_t ch = 0; ch < get_rx_num_channels(); ++ch) { +            set_rx_dc_offset(offset, ch); +        } +    } + +    meta_range_t get_rx_dc_offset_range(size_t chan) +    { +        auto rx_chain = _get_rx_chan(chan); +        return rx_chain.radio->get_rx_dc_offset_range(rx_chain.block_chan); +    } + +    void set_rx_iq_balance(const bool enb, size_t chan) +    { +        if (chan != ALL_CHANS) { +            auto rx_chain = _get_rx_chan(chan); +            rx_chain.radio->set_rx_iq_balance(enb, rx_chain.block_chan); +            return; +        } +        for (size_t ch = 0; ch < get_rx_num_channels(); ++ch) { +            set_rx_iq_balance(enb, ch); +        } +    } + +    void set_rx_iq_balance( +        const std::complex<double>& correction, size_t chan = ALL_CHANS) +    { +        if (chan != ALL_CHANS) { +            auto rx_chain = _get_rx_chan(chan); +            rx_chain.radio->set_rx_iq_balance(correction, rx_chain.block_chan); +            return; +        } +        for (size_t ch = 0; ch < get_rx_num_channels(); ++ch) { +            set_rx_iq_balance(correction, ch); +        } +    } + +    /******************************************************************* +     * TX methods +     ******************************************************************/ +    tx_chan_t _generate_tx_radio_chan(block_id_t radio_id, size_t block_chan) +    { +        auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id); +        // We assume that the duc connected to this radio block has the same mboard, +        // instance, and port number +        auto duc_id = +            block_id_t(radio_id.get_device_no(), "DUC", radio_id.get_block_count()); +        uhd::rfnoc::duc_block_control::sptr duc_blk; +        try { +            duc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(duc_id); +        } catch (const uhd::exception&) { +            UHD_LOGGER_TRACE("MULTI_USRP_RFNOC") +                << boost::format("No DUC found: %s") % duc_id.to_string(); +        } +        // Figure out if this channel has a DUC available +        auto this_chan_duc = +            duc_blk && _graph->is_connectable(duc_id, block_chan, radio_id, block_chan) +                ? duc_blk +                : nullptr; +        return {radio_blk, this_chan_duc, block_chan}; +    } + +    std::vector<tx_chan_t> _generate_mboard_tx_chans( +        const uhd::usrp::subdev_spec_t& spec, size_t mboard) +    { +        // Discover all of the radios on our devices and create a mapping between radio +        // chains and channel numbers +        auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio"); +        // find_blocks doesn't sort, so we need to +        std::sort(radio_blk_ids.begin(), +            radio_blk_ids.end(), +            [](uhd::rfnoc::block_id_t i, uhd::rfnoc::block_id_t j) -> bool { +                if (i.get_device_no() != j.get_device_no()) { +                    return i.get_device_no() < j.get_device_no(); +                } else { +                    return i.get_block_count() < j.get_block_count(); +                } +            }); + +        // If we don't find any radios, we don't have a multi_usrp object +        if (radio_blk_ids.empty()) { +            throw uhd::runtime_error( +                "[multi_usrp] No radios found in the requested mboard: " +                + std::to_string(mboard)); +        } + +        // Iterate through the subdev pairs, and try to find a radio that matches +        std::vector<tx_chan_t> new_chans; +        for (auto chan_subdev_pair : spec) { +            bool subdev_found = false; +            for (auto radio_id : radio_blk_ids) { +                auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id); +                size_t block_chan; +                try { +                    block_chan = radio_blk->get_chan_from_dboard_fe( +                        chan_subdev_pair.sd_name, TX_DIRECTION); +                } catch (const uhd::lookup_error&) { +                    // This is OK, since we're probing all radios, this +                    // particular radio may not have the requested frontend name +                    // so it's not one that we want in this list. +                    continue; +                } +                subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(), +                    radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION)); +                if (chan_subdev_pair == radio_subdev) { +                    new_chans.push_back(_generate_tx_radio_chan(radio_id, block_chan)); +                    subdev_found = true; +                } +            } +            if (!subdev_found) { +                std::string err_msg("Could not find radio on mboard " +                                    + std::to_string(mboard) + " that matches subdev " +                                    + chan_subdev_pair.db_name + ":" +                                    + chan_subdev_pair.sd_name); +                UHD_LOG_ERROR("MULTI_USRP", err_msg); +                throw uhd::lookup_error(err_msg); +            } +        } +        UHD_LOG_TRACE("MULTI_USRP", +            std::string("Using TX subdev " + spec.to_string() + ", found ") +                + std::to_string(new_chans.size()) + " channels for mboard " +                + std::to_string(mboard)); +        return new_chans; +    } + +    void set_tx_subdev_spec( +        const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS) +    { +        /* TODO: Refactor with get_rx_subdev_spec- the algorithms are the same, just the +         * types are different +         */ +        // First, generate a vector of the tx channels that we need to register +        auto new_tx_chans = [this, spec, mboard]() { +            /* When setting the subdev spec in multiple mboard scenarios, there are two +             * cases we need to handle: +             * 1. Setting all mboard to the same subdev spec. This is the easy case. +             * 2. Setting a single mboard's subdev spec. In this case, we need to update +             * the requested mboard's subdev spec, and keep the old subdev spec for the +             * other mboards. +             */ +            std::vector<tx_chan_t> new_tx_chans; +            for (size_t current_mboard = 0; current_mboard < get_num_mboards(); +                 ++current_mboard) { +                auto current_spec = [this, spec, mboard, current_mboard]() { +                    if (mboard == ALL_MBOARDS || mboard == current_mboard) { +                        // Update all mboards to the same subdev spec OR +                        // only update this mboard to the new subdev spec +                        return spec; +                    } else { +                        // Keep the old subdev spec for this mboard +                        return get_tx_subdev_spec(current_mboard); +                    } +                }(); +                auto new_mboard_chans = +                    _generate_mboard_tx_chans(current_spec, current_mboard); +                new_tx_chans.insert( +                    new_tx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end()); +            } +            return new_tx_chans; +        }(); + +        // Now register them +        _tx_chans.clear(); +        for (size_t tx_chan = 0; tx_chan < new_tx_chans.size(); ++tx_chan) { +            _tx_chans.emplace(tx_chan, new_tx_chans.at(tx_chan)); +        } +    } + +    uhd::usrp::subdev_spec_t get_tx_subdev_spec(size_t mboard) +    { +        uhd::usrp::subdev_spec_t result; +        for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) { +            auto& tx_chain = _tx_chans.at(tx_chan); +            if (tx_chain.radio->get_block_id().get_device_no() == mboard) { +                result.push_back( +                    uhd::usrp::subdev_spec_pair_t(tx_chain.radio->get_slot_name(), +                        tx_chain.radio->get_dboard_fe_from_chan( +                            tx_chain.block_chan, uhd::TX_DIRECTION))); +            } +        } + +        return result; +    } + +    size_t get_tx_num_channels(void) +    { +        return _tx_chans.size(); +    } + +    std::string get_tx_subdev_name(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_fe_name(tx_chain.block_chan, uhd::TX_DIRECTION); +    } + +    void set_tx_rate(double rate, size_t chan = ALL_CHANS) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        if (chan == ALL_CHANS) { +            for (size_t chan = 0; chan < _tx_chans.size(); ++chan) { +                set_tx_rate(rate, chan); +            } +            return; +        } +        const double actual_rate = [&]() { +            auto tx_chain = _get_tx_chan(chan); +            if (tx_chain.duc) { +                return tx_chain.duc->set_input_rate(rate, tx_chain.block_chan); +            } else { +                return tx_chain.radio->set_rate(rate); +            } +        }(); +        if (actual_rate != rate) { +            UHD_LOGGER_WARNING("MULTI_USRP") +                << boost::format( +                       "Could not set TX rate to %.3f MHz. Actual rate is %.3f MHz") +                       % (rate / 1.0e6) % (actual_rate / 1.0e6); +        } +        _tx_rates[chan] = actual_rate; +    } + +    double get_tx_rate(size_t chan) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        auto& tx_chain = _get_tx_chan(chan); +        if (tx_chain.duc) { +            return tx_chain.duc->get_input_rate(tx_chain.block_chan); +        } +        return tx_chain.radio->get_rate(); +    } + +    meta_range_t get_tx_rates(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return (tx_chain.duc) +                   ? make_overall_tune_range( +                         tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan), +                         tx_chain.duc->get_frequency_range(tx_chain.block_chan), +                         tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan)) +                   : tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan); +    } + +    tune_result_t set_tx_freq(const tune_request_t& tune_request, size_t chan) +    { +        std::lock_guard<std::recursive_mutex> l(_graph_mutex); +        auto tx_chain = _get_tx_chan(chan); + +        tx_chain.radio->set_tx_tune_args(tune_request.args, tx_chain.block_chan); +        //------------------------------------------------------------------ +        //-- calculate the tunable frequency ranges of the system +        //------------------------------------------------------------------ +        freq_range_t tune_range = +            (tx_chain.duc) +                ? make_overall_tune_range( +                      tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan), +                      tx_chain.duc->get_frequency_range(tx_chain.block_chan), +                      tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan)) +                : tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan); + +        freq_range_t rf_range = +            tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan); +        freq_range_t dsp_range = +            (tx_chain.duc) ? tx_chain.duc->get_frequency_range(tx_chain.block_chan) +                           : meta_range_t(0, 0); +        // Create lambdas to feed to tune_xx_subdev_and_dsp() +        // Note: If there is no DDC present, register empty lambdas for the DSP functions +        auto set_rf_freq = [tx_chain](double freq) { +            tx_chain.radio->set_tx_frequency(freq, tx_chain.block_chan); +        }; +        auto get_rf_freq = [tx_chain](void) { +            return tx_chain.radio->get_tx_frequency(tx_chain.block_chan); +        }; +        auto set_dsp_freq = [tx_chain](double freq) { +            (tx_chain.duc) ? tx_chain.duc->set_freq(freq, tx_chain.block_chan) : 0; +        }; +        auto get_dsp_freq = [tx_chain](void) { +            return (tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0; +        }; +        return tune_xx_subdev_and_dsp(TX_SIGN, +            tune_range, +            rf_range, +            dsp_range, +            set_rf_freq, +            get_rf_freq, +            set_dsp_freq, +            get_dsp_freq, +            tune_request); +    } + +    double get_tx_freq(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_frequency(tx_chain.block_chan); +    } + +    freq_range_t get_tx_freq_range(size_t chan) +    { +        auto tx_chain = _tx_chans.at(chan); +        return (tx_chain.duc) +                   ? make_overall_tune_range(get_fe_rx_freq_range(chan), +                         tx_chain.duc->get_frequency_range(tx_chain.block_chan), +                         tx_chain.radio->get_rx_bandwidth(tx_chain.block_chan)) +                   : get_fe_rx_freq_range(chan); +    } + +    freq_range_t get_fe_tx_freq_range(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan); +    } + +    void set_tx_gain(double gain, const std::string& name, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_gain(gain, name, tx_chain.block_chan); +    } + +    std::vector<std::string> get_tx_gain_profile_names(const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_gain_profile_names(tx_chain.block_chan); +    } + +    void set_tx_gain_profile(const std::string& profile, const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_gain_profile(profile, tx_chain.block_chan); +    } + +    std::string get_tx_gain_profile(const size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_gain_profile(tx_chain.block_chan); +    } + +    void set_normalized_tx_gain(double gain, size_t chan = 0) +    { +        if (gain > 1.0 || gain < 0.0) { +            throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1]."); +        } +        gain_range_t gain_range = get_tx_gain_range(ALL_GAINS, chan); +        double abs_gain = +            (gain * (gain_range.stop() - gain_range.start())) + gain_range.start(); +        set_tx_gain(abs_gain, ALL_GAINS, chan); +    } + +    double get_tx_gain(const std::string& name, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_gain(name, tx_chain.block_chan); +    } + +    double get_normalized_tx_gain(size_t chan) +    { +        gain_range_t gain_range       = get_tx_gain_range(ALL_GAINS, chan); +        const double gain_range_width = gain_range.stop() - gain_range.start(); +        // In case we have a device without a range of gains: +        if (gain_range_width == 0.0) { +            return 0; +        } +        const double norm_gain = +            (get_tx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width; +        // Avoid rounding errors: +        return std::max(std::min(norm_gain, 1.0), 0.0); +    } + +    gain_range_t get_tx_gain_range(const std::string& name, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_gain_range(name, tx_chain.block_chan); +    } + +    std::vector<std::string> get_tx_gain_names(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_gain_names(tx_chain.block_chan); +    } + +    void set_tx_antenna(const std::string& ant, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_antenna(ant, tx_chain.block_chan); +    } + +    std::string get_tx_antenna(size_t chan) +    { +        auto& tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_antenna(tx_chain.block_chan); +    } + +    std::vector<std::string> get_tx_antennas(size_t chan) +    { +        auto& tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_antennas(tx_chain.block_chan); +    } + +    void set_tx_bandwidth(double bandwidth, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_bandwidth(bandwidth, tx_chain.block_chan); +    } + +    double get_tx_bandwidth(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan); +    } + +    meta_range_t get_tx_bandwidth_range(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_bandwidth_range(tx_chain.block_chan); +    } + +    dboard_iface::sptr get_tx_dboard_iface(size_t chan) +    { +        auto& tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get(); +    } + +    sensor_value_t get_tx_sensor(const std::string& name, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_sensor(name, tx_chain.block_chan); +    } + +    std::vector<std::string> get_tx_sensor_names(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_sensor_names(tx_chain.block_chan); +    } + +    void set_tx_dc_offset(const std::complex<double>& offset, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_dc_offset(offset, tx_chain.block_chan); +    } + +    meta_range_t get_tx_dc_offset_range(size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        return tx_chain.radio->get_tx_dc_offset_range(tx_chain.block_chan); +    } + +    void set_tx_iq_balance(const std::complex<double>& correction, size_t chan) +    { +        auto tx_chain = _get_tx_chan(chan); +        tx_chain.radio->set_tx_iq_balance(correction, tx_chain.block_chan); +    } + +    /******************************************************************* +     * GPIO methods +     ******************************************************************/ +    /* Helper function to get the radio block controller which controls the GPIOs for a +     * given motherboard +     */ +    uhd::rfnoc::radio_control::sptr _get_gpio_radio(const size_t mboard) +    { +        // We assume that the first radio block on each board controls the GPIO banks +        return _graph->get_block<uhd::rfnoc::radio_control>( +            block_id_t(mboard, "Radio", 0)); +    } + +    std::vector<std::string> get_gpio_banks(const size_t mboard) +    { +        return _get_gpio_radio(mboard)->get_gpio_banks(); +    } + +    void set_gpio_attr(const std::string& bank, +        const std::string& attr, +        const uint32_t value, +        const uint32_t mask = 0xffffffff, +        const size_t mboard = 0) +    { +        const uint32_t current   = get_gpio_attr(bank, attr, mboard); +        const uint32_t new_value = (current & ~mask) | (value & mask); +        return _get_gpio_radio(mboard)->set_gpio_attr(bank, attr, new_value); +    } + +    uint32_t get_gpio_attr( +        const std::string& bank, const std::string& attr, const size_t mboard) +    { +        return _get_gpio_radio(mboard)->get_gpio_attr(bank, attr); +    } + +    std::vector<std::string> get_gpio_srcs(const std::string& bank, const size_t mboard) +    { +        return get_mbc(mboard)->get_gpio_srcs(bank); +    } + +    std::vector<std::string> get_gpio_src(const std::string& bank, const size_t mboard) +    { +        return get_mbc(mboard)->get_gpio_src(bank); +    } + +    void set_gpio_src( +        const std::string& bank, const std::vector<std::string>& src, const size_t mboard) +    { +        get_mbc(mboard)->set_gpio_src(bank, src); +    } + +    /******************************************************************* +     * Filter API methods +     ******************************************************************/ +    std::vector<std::string> get_rx_filter_names(const size_t chan) +    { +        std::vector<std::string> filter_names; +        // Grab the Radio's filters +        auto rx_chan    = _get_rx_chan(chan); +        auto radio_id   = rx_chan.radio->get_block_id(); +        auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.radio); +        if (radio_ctrl) { +            auto radio_filters = radio_ctrl->get_rx_filter_names(rx_chan.block_chan); +            // Prepend the radio's block ID to each filter name +            std::transform(radio_filters.begin(), +                radio_filters.end(), +                radio_filters.begin(), +                [radio_id]( +                    std::string name) { return radio_id.to_string() + ":" + name; }); +            // Add the radio's filter names to the return vector +            filter_names.insert( +                filter_names.end(), radio_filters.begin(), radio_filters.end()); +        } else { +            UHD_LOG_DEBUG("MULTI_USRP", +                "Radio block " + radio_id.to_string() + " does not support filters"); +        } +        // Grab the DDC's filter +        auto ddc_id   = rx_chan.ddc->get_block_id(); +        auto ddc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.ddc); +        if (ddc_ctrl) { +            auto ddc_filters = ddc_ctrl->get_rx_filter_names(rx_chan.block_chan); +            // Prepend the DDC's block ID to each filter name +            std::transform(ddc_filters.begin(), +                ddc_filters.end(), +                ddc_filters.begin(), +                [ddc_id](std::string name) { return ddc_id.to_string() + ":" + name; }); +            // Add the radio's filter names to the return vector +            filter_names.insert( +                filter_names.end(), ddc_filters.begin(), ddc_filters.end()); +        } else { +            UHD_LOG_DEBUG("MULTI_USRP", +                "DDC block " + ddc_id.to_string() + " does not support filters"); +        } +        return filter_names; +    } + +    uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan) +    { +        try { +            // The block_id_t constructor is pretty smart; let it handle the parsing. +            block_id_t block_id(name); +            auto rx_chan = _get_rx_chan(chan); +            // The filter name is the `name` after the BLOCK_ID and a `:` +            std::string filter_name = name.substr(block_id.to_string().size() + 1); +            // Try to dynamic cast either the radio or the DDC to a filter_node, and call +            // its filter function +            auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr { +                if (block_id == rx_chan.radio->get_block_id()) { +                    return rx_chan.radio; +                } else if (block_id == rx_chan.ddc->get_block_id()) { +                    return rx_chan.ddc; +                } else { +                    throw uhd::runtime_error("Requested block " + block_id.to_string() +                                             + " does not match block ID in channel " +                                             + std::to_string(chan)); +                } +            }(); +            auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl); +            if (filter_ctrl) { +                return filter_ctrl->get_rx_filter(filter_name, rx_chan.block_chan); +            } +            std::string err_msg = +                block_ctrl->get_block_id().to_string() + " does not support filters"; +            UHD_LOG_ERROR("MULTI_USRP", err_msg); +            throw uhd::runtime_error(err_msg); +        } catch (const uhd::value_error&) { +            // Catch the error from the block_id_t constructor and add better logging +            UHD_LOG_ERROR("MULTI_USRP", +                "Invalid filter name; could not determine block controller from name: " +                    + name); +            throw; +        } +    } + +    void set_rx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +    { +        try { +            // The block_id_t constructor is pretty smart; let it handle the parsing. +            block_id_t block_id(name); +            auto rx_chan = _get_rx_chan(chan); +            // The filter name is the `name` after the BLOCK_ID and a `:` +            std::string filter_name = name.substr(block_id.to_string().size() + 1); +            // Try to dynamic cast either the radio or the DDC to a filter_node, and call +            // its filter function +            auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr { +                if (block_id == rx_chan.radio->get_block_id()) { +                    return rx_chan.radio; +                } else if (block_id == rx_chan.ddc->get_block_id()) { +                    return rx_chan.ddc; +                } else { +                    throw uhd::runtime_error("Requested block " + block_id.to_string() +                                             + " does not match block ID in channel " +                                             + std::to_string(chan)); +                } +            }(); +            auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl); +            if (filter_ctrl) { +                return filter_ctrl->set_rx_filter( +                    filter_name, filter, rx_chan.block_chan); +            } +            std::string err_msg = +                block_ctrl->get_block_id().to_string() + " does not support filters"; +            UHD_LOG_ERROR("MULTI_USRP", err_msg); +            throw uhd::runtime_error(err_msg); +        } catch (const uhd::value_error&) { +            // Catch the error from the block_id_t constructor and add better logging +            UHD_LOG_ERROR("MULTI_USRP", +                "Invalid filter name; could not determine block controller from name: " +                    + name); +            throw; +        } +    } + +    std::vector<std::string> get_tx_filter_names(const size_t chan) +    { +        std::vector<std::string> filter_names; +        // Grab the Radio's filters +        auto tx_chan    = _get_tx_chan(chan); +        auto radio_id   = tx_chan.radio->get_block_id(); +        auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.radio); +        if (radio_ctrl) { +            auto radio_filters = radio_ctrl->get_tx_filter_names(tx_chan.block_chan); +            // Prepend the radio's block ID to each filter name +            std::transform(radio_filters.begin(), +                radio_filters.end(), +                radio_filters.begin(), +                [radio_id]( +                    std::string name) { return radio_id.to_string() + ":" + name; }); +            // Add the radio's filter names to the return vector +            filter_names.insert( +                filter_names.end(), radio_filters.begin(), radio_filters.end()); +        } else { +            UHD_LOG_DEBUG("MULTI_USRP", +                "Radio block " + radio_id.to_string() + " does not support filters"); +        } +        // Grab the DUC's filter +        auto duc_id   = tx_chan.duc->get_block_id(); +        auto duc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.duc); +        if (duc_ctrl) { +            auto duc_filters = duc_ctrl->get_tx_filter_names(tx_chan.block_chan); +            // Prepend the DUC's block ID to each filter name +            std::transform(duc_filters.begin(), +                duc_filters.end(), +                duc_filters.begin(), +                [duc_id](std::string name) { return duc_id.to_string() + ":" + name; }); +            // Add the radio's filter names to the return vector +            filter_names.insert( +                filter_names.end(), duc_filters.begin(), duc_filters.end()); +        } else { +            UHD_LOG_DEBUG("MULTI_USRP", +                "DUC block " + duc_id.to_string() + " does not support filters"); +        } +        return filter_names; +    } + +    uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan) +    { +        try { +            // The block_id_t constructor is pretty smart; let it handle the parsing. +            block_id_t block_id(name); +            auto tx_chan = _get_tx_chan(chan); +            // The filter name is the `name` after the BLOCK_ID and a `:` +            std::string filter_name = name.substr(block_id.to_string().size() + 1); +            // Try to dynamic cast either the radio or the DUC to a filter_node, and call +            // its filter function +            auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr { +                if (block_id == tx_chan.radio->get_block_id()) { +                    return tx_chan.radio; +                } else if (block_id == tx_chan.duc->get_block_id()) { +                    return tx_chan.duc; +                } else { +                    throw uhd::runtime_error("Requested block " + block_id.to_string() +                                             + " does not match block ID in channel " +                                             + std::to_string(chan)); +                } +            }(); +            auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl); +            if (filter_ctrl) { +                return filter_ctrl->get_tx_filter(filter_name, tx_chan.block_chan); +            } +            std::string err_msg = +                block_ctrl->get_block_id().to_string() + " does not support filters"; +            UHD_LOG_ERROR("MULTI_USRP", err_msg); +            throw uhd::runtime_error(err_msg); +        } catch (const uhd::value_error&) { +            // Catch the error from the block_id_t constructor and add better logging +            UHD_LOG_ERROR("MULTI_USRP", +                "Invalid filter name; could not determine block controller from name: " +                    + name); +            throw; +        } +    } + +    void set_tx_filter( +        const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +    { +        try { +            // The block_id_t constructor is pretty smart; let it handle the parsing. +            block_id_t block_id(name); +            auto tx_chan = _get_tx_chan(chan); +            // The filter name is the `name` after the BLOCK_ID and a `:` +            std::string filter_name = name.substr(block_id.to_string().size() + 1); +            // Try to dynamic cast either the radio or the DUC to a filter_node, and call +            // its filter function +            auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr { +                if (block_id == tx_chan.radio->get_block_id()) { +                    return tx_chan.radio; +                } else if (block_id == tx_chan.duc->get_block_id()) { +                    return tx_chan.duc; +                } else { +                    throw uhd::runtime_error("Requested block " + block_id.to_string() +                                             + " does not match block ID in channel " +                                             + std::to_string(chan)); +                } +            }(); +            auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl); +            if (filter_ctrl) { +                return filter_ctrl->set_tx_filter( +                    filter_name, filter, tx_chan.block_chan); +            } +            std::string err_msg = +                block_ctrl->get_block_id().to_string() + " does not support filters"; +            UHD_LOG_ERROR("MULTI_USRP", err_msg); +            throw uhd::runtime_error(err_msg); +        } catch (const uhd::value_error&) { +            // Catch the error from the block_id_t constructor and add better logging +            UHD_LOG_ERROR("MULTI_USRP", +                "Invalid filter name; could not determine block controller from name: " +                    + name); +            throw; +        } +    } + +private: +    /************************************************************************** +     * Private Helpers +     *************************************************************************/ +    mb_controller::sptr get_mbc(const size_t mb_idx) +    { +        if (mb_idx >= get_num_mboards()) { +            throw uhd::key_error( +                std::string("No such mboard: ") + std::to_string(mb_idx)); +        } +        return _graph->get_mb_controller(mb_idx); +    } + +    rx_chan_t& _get_rx_chan(const size_t chan) +    { +        if (!_rx_chans.count(chan)) { +            throw uhd::key_error( +                std::string("Invalid RX channel: ") + std::to_string(chan)); +        } +        return _rx_chans.at(chan); +    } + +    tx_chan_t& _get_tx_chan(const size_t chan) +    { +        if (!_tx_chans.count(chan)) { +            throw uhd::key_error( +                std::string("Invalid TX channel: ") + std::to_string(chan)); +        } +        return _tx_chans.at(chan); +    } + +    /************************************************************************** +     * Private Attributes +     *************************************************************************/ +    //! Devices args used to spawn this multi_usrp +    const uhd::device_addr_t _args; +    //! Reference to rfnoc_graph +    rfnoc_graph::sptr _graph; +    //! Reference to the prop tree +    property_tree::sptr _tree; +    //! Mapping between channel number and the RFNoC blocks in that RX chain +    std::unordered_map<size_t, rx_chan_t> _rx_chans; +    //! Mapping between channel number and the RFNoC blocks in that TX chain +    std::unordered_map<size_t, tx_chan_t> _tx_chans; +    //! Cache the requested RX rates +    std::unordered_map<size_t, double> _rx_rates; +    //! Cache the requested TX rates +    std::unordered_map<size_t, double> _tx_rates; + +    std::recursive_mutex _graph_mutex; +}; + +/****************************************************************************** + * Factory + *****************************************************************************/ +namespace uhd { namespace rfnoc { namespace detail { +// Forward declare +rfnoc_graph::sptr make_rfnoc_graph( +    detail::rfnoc_device::sptr dev, const uhd::device_addr_t& device_addr); + +multi_usrp::sptr make_rfnoc_device( +    detail::rfnoc_device::sptr rfnoc_device, const uhd::device_addr_t& dev_addr) +{ +    auto graph = uhd::rfnoc::detail::make_rfnoc_graph(rfnoc_device, dev_addr); +    return boost::make_shared<multi_usrp_rfnoc>(graph, dev_addr); +} + +}}} // namespace uhd::rfnoc::detail diff --git a/host/lib/usrp/usrp_c.cpp b/host/lib/usrp/usrp_c.cpp index f625113e4..9d1704181 100644 --- a/host/lib/usrp/usrp_c.cpp +++ b/host/lib/usrp/usrp_c.cpp @@ -1515,50 +1515,3 @@ uhd_error uhd_usrp_get_gpio_attr(      )  } -uhd_error uhd_usrp_enumerate_registers( -    uhd_usrp_handle h, -    size_t mboard, -    uhd_string_vector_handle *registers_out -){ -    UHD_SAFE_C_SAVE_ERROR(h, -        (*registers_out)->string_vector_cpp = USRP(h)->enumerate_registers(mboard); -    ) -} - -uhd_error uhd_usrp_get_register_info( -    uhd_usrp_handle h, -    const char* path, -    size_t mboard, -    uhd_usrp_register_info_t *register_info_out -){ -    UHD_SAFE_C_SAVE_ERROR(h, -        uhd::usrp::multi_usrp::register_info_t register_info_cpp = USRP(h)->get_register_info(path, mboard); -        register_info_out->bitwidth = register_info_cpp.bitwidth; -        register_info_out->readable = register_info_cpp.readable; -        register_info_out->writable = register_info_cpp.writable; -    ) -} - -uhd_error uhd_usrp_write_register( -    uhd_usrp_handle h, -    const char* path, -    uint32_t field, -    uint64_t value, -    size_t mboard -){ -    UHD_SAFE_C_SAVE_ERROR(h, -        USRP(h)->write_register(path, field, value, mboard); -    ) -} - -uhd_error uhd_usrp_read_register( -    uhd_usrp_handle h, -    const char* path, -    uint32_t field, -    size_t mboard, -    uint64_t *value_out -){ -    UHD_SAFE_C_SAVE_ERROR(h, -        *value_out = USRP(h)->read_register(path, field, mboard); -    ) -}  | 
