diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2019-06-07 13:22:05 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 11:49:42 -0800 |
commit | 23f4f8cf4ea72d59740afcb5663e4541f93e821a (patch) | |
tree | c4ab11c76a7a37a197ab464e368002a91d7878c1 | |
parent | 027cf1fe9b484197bc7f2aeb2f1bb588442d0bdc (diff) | |
download | uhd-23f4f8cf4ea72d59740afcb5663e4541f93e821a.tar.gz uhd-23f4f8cf4ea72d59740afcb5663e4541f93e821a.tar.bz2 uhd-23f4f8cf4ea72d59740afcb5663e4541f93e821a.zip |
rfnoc: Add multi_usrp_rfnoc, modify multi_usrp
This adds a separate version of multi_usrp for RFNoC devices. It is
compatible with RFNoC devices only, and prefers C++ APIs over property
tree usage. The factory of multi_usrp is modified such that it picks the
correct version, users of multi_usrp don't care about this change.
This also introduces some API changes:
- Removing redundant GPIO functions. Now all GPIO control, setting, and
readback is done with uint32_t's.
- Adding getter/setter for GPIO source. This was done to simplify the
other GPIO settings, as the source for each pin is not always a
binary. The CTRL mode, for example, can either be ATR or GPIO.
However, the source can be controlled by various radios or "PS" or
some other source.
- Removing the mask from the RFNoC radio controllers' set_gpio_attr().
- Adding state caching to gpio_atr_3000, and a getter for it. Whenever
an attribute is set, that value is cached, and can now be retreieved.
- Remove low-level register API. Since UHD 3.10, there is no USRP that
implements that API.
Modifying the filter API in the following ways:
- Splitting filter API getter/setter/list into separate RX and TX
functions
- Adding channel numbers as an argument
- The filter name will no longer be a property tree path, but rather a
filter name. For RFNoC devices, this will take the form
`BLOCK_ID:FILTER_NAME`. For non-RFNoC devices, this will just be the
filter name (e.g. `HB_1`)
- Removing search mask from listing function. Users can do their own
searching
Co-Authored-By: Martin Braun <martin.braun@ettus.com>
-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); - ) -} |