aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrent Stapleton <brent.stapleton@ettus.com>2019-06-07 13:22:05 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:42 -0800
commit23f4f8cf4ea72d59740afcb5663e4541f93e821a (patch)
treec4ab11c76a7a37a197ab464e368002a91d7878c1
parent027cf1fe9b484197bc7f2aeb2f1bb588442d0bdc (diff)
downloaduhd-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.hpp3
-rw-r--r--host/include/uhd/usrp/multi_usrp.hpp189
-rw-r--r--host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp3
-rw-r--r--host/lib/include/uhdlib/usrp/cores/gpio_atr_3000.hpp10
-rw-r--r--host/lib/rfnoc/radio_control_impl.cpp2
-rw-r--r--host/lib/usrp/CMakeLists.txt1
-rw-r--r--host/lib/usrp/cores/gpio_atr_3000.cpp70
-rw-r--r--host/lib/usrp/multi_usrp.cpp439
-rw-r--r--host/lib/usrp/multi_usrp_python.hpp24
-rw-r--r--host/lib/usrp/multi_usrp_rfnoc.cpp2217
-rw-r--r--host/lib/usrp/usrp_c.cpp47
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", &register_info_t::bitwidth)
- .def_readwrite("readable", &register_info_t::readable)
- .def_readwrite("writable", &register_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);
- )
-}