uhd/host/lib/usrp/multi_usrp_rfnoc.cpp
Martin Braun 4cd86bbf2a multi_usrp: Amend get_usrp_{rx,tx}_info() to include cal keys
This adds two more keys to the dictionary return from
get_usrp_{rx,tx}_info() which can be used to query the calibration key
and serial.
2020-05-20 15:19:55 -05:00

2387 lines
95 KiB
C++

//
// 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/graph_edge.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 <uhd/utils/graph_utils.hpp>
#include <uhdlib/rfnoc/rfnoc_device.hpp>
#include <uhdlib/usrp/gpio_defs.hpp>
#include <uhdlib/utils/narrow.hpp>
#include <unordered_set>
#include <boost/format.hpp>
#include <algorithm>
#include <chrono>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
using namespace std::chrono_literals;
using namespace uhd;
using namespace uhd::usrp;
using namespace uhd::rfnoc;
//! Fan out (mux) an API call that is for all channels or all motherboards
#define MUX_API_CALL(max_index, api_call, mux_var, mux_cond, ...) \
if (mux_var == mux_cond) { \
for (size_t __index = 0; __index < max_index; ++__index) { \
api_call(__VA_ARGS__, __index); \
} \
return; \
}
//! Fan out (mux) an RX-specific API call that is for all channels
#define MUX_RX_API_CALL(api_call, ...) \
MUX_API_CALL(get_rx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
//! Fan out (mux) an TX-specific API call that is for all channels
#define MUX_TX_API_CALL(api_call, ...) \
MUX_API_CALL(get_tx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
//! Fan out (mux) a motherboard-specific API call that is for all boards
#define MUX_MB_API_CALL(api_call, ...) \
MUX_API_CALL(get_num_mboards(), api_call, mboard, ALL_MBOARDS, __VA_ARGS__)
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;
constexpr char LOG_ID[] = "MULTI_USRP";
//! A faux container for a UHD device
//
// Note that multi_usrp_rfnoc no longer gives access to the underlying device
// class. Legacy code might use multi_usrp->get_device()->get_tree() or
// similar functionalities; these can be faked with this redirector class.
//
// The only exception is recv_async_msg(), which depends on the streamer. It
// will print a warning once, and will attempt to access a Tx streamer if it
// has access to a Tx streamer. If there is only ever one Tx streamer, this will
// work as expected. For multiple streamers, only the last streamer's async
// messages will make it through.
class redirector_device : public uhd::device
{
public:
redirector_device(multi_usrp* musrp_ptr) : _musrp(musrp_ptr) {}
rx_streamer::sptr get_rx_stream(const stream_args_t& args)
{
return _musrp->get_rx_stream(args);
}
tx_streamer::sptr get_tx_stream(const stream_args_t& args)
{
auto streamer = _musrp->get_tx_stream(args);
_last_tx_streamer = streamer;
return streamer;
}
bool recv_async_msg(async_metadata_t& md, double timeout)
{
std::call_once(_async_warning_flag, []() {
UHD_LOG_WARNING(LOG_ID,
"Calling multi_usrp::recv_async_msg() is deprecated and can lead to "
"unexpected behaviour. Prefer calling tx_stream::recv_async_msg().");
});
auto streamer = _last_tx_streamer.lock();
if (streamer) {
return streamer->recv_async_msg(md, timeout);
}
return false;
}
uhd::property_tree::sptr get_tree(void) const
{
return _musrp->get_tree();
}
device_filter_t get_device_type() const
{
return USRP;
}
void set_tx_stream(tx_streamer::sptr streamer)
{
_last_tx_streamer = streamer;
}
private:
std::once_flag _async_warning_flag;
std::weak_ptr<tx_streamer> _last_tx_streamer;
multi_usrp* _musrp;
};
/*! 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;
std::vector<graph_edge_t> edge_list;
};
struct tx_chan_t
{
radio_control::sptr radio;
duc_block_control::sptr duc; // can be nullptr
size_t block_chan;
std::vector<graph_edge_t> edge_list;
};
/**************************************************************************
* Structors
*************************************************************************/
multi_usrp_rfnoc(rfnoc_graph::sptr graph, const device_addr_t& addr)
: _args(addr)
, _graph(graph)
, _tree(_graph->get_tree())
, _device(std::make_shared<redirector_device>(this))
{
// Discover all of the radios on our devices and create a mapping between
// radio chains and channel numbers. The result is sorted.
auto radio_blk_ids = _graph->find_blocks("Radio");
// 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);
for (size_t block_chan = 0; block_chan < radio_blk->get_num_output_ports();
++block_chan) {
// Create the RX chan
uhd::usrp::subdev_spec_t rx_radio_subdev;
rx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
radio_blk->get_slot_name(),
radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION)));
auto rx_chans =
_generate_mboard_rx_chans(rx_radio_subdev, radio_id.get_device_no());
// TODO: we're passing the same info around here; there has to be a
// cleaner way
for (auto rx_chan : rx_chans) {
_rx_chans.emplace(musrp_rx_channel, rx_chan);
++musrp_rx_channel; // Increment after logging so we print the correct
// value
}
}
for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports();
++block_chan) {
// Create the TX chan
uhd::usrp::subdev_spec_t tx_radio_subdev;
tx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
radio_blk->get_slot_name(),
radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION)));
auto tx_chans =
_generate_mboard_tx_chans(tx_radio_subdev, radio_id.get_device_no());
// TODO: we're passing the same info around here; there has to be a
// cleaner way
for (auto tx_chan : tx_chans) {
_tx_chans.emplace(musrp_tx_channel, tx_chan);
++musrp_tx_channel; // Increment after logging so we print the correct
// value
}
}
}
// Manually propagate radio block sample rates to DDC/DUC blocks in order to allow
// DDC/DUC blocks to have valid internal state before graph is (later) connected
for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); ++rx_chan) {
auto& rx_chain = _get_rx_chan(rx_chan);
if (rx_chain.ddc) {
rx_chain.ddc->set_input_rate(rx_chain.radio->get_rate(), rx_chain.block_chan);
}
}
for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); ++tx_chan) {
auto& tx_chain = _get_tx_chan(tx_chan);
if (tx_chain.duc) {
tx_chain.duc->set_output_rate(tx_chain.radio->get_rate(), tx_chain.block_chan);
}
}
_graph->commit();
}
~multi_usrp_rfnoc()
{
// nop
}
device::sptr get_device(void)
{
return _device;
}
uhd::property_tree::sptr get_tree() const
{
return _tree;
}
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);
// Make all of the connections in our chain
for (auto edge : rx_chain.edge_list) {
if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
break;
}
UHD_LOG_TRACE("MULTI_USRP",
boost::format("Connecting RX edge: %s:%d -> %s:%d") % edge.src_blockid
% edge.src_port % edge.dst_blockid % edge.dst_port);
_graph->connect(
edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
}
// Including the connection to the streamer
UHD_LOG_TRACE("MULTI_USRP",
boost::format("Connecting %s:%d -> RxStreamer:%d")
% rx_chain.edge_list.back().src_blockid
% rx_chain.edge_list.back().src_port % strm_port);
_graph->connect(rx_chain.edge_list.back().src_blockid,
rx_chain.edge_list.back().src_port,
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) {
if (rate > 1.0) {
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);
// Make all of the connections in our chain
for (auto edge : tx_chain.edge_list) {
if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
break;
}
UHD_LOG_TRACE("MULTI_USRP",
boost::format("Connecting TX edge %s:%d -> %s:%d") % edge.src_blockid
% edge.src_port % edge.dst_blockid % edge.dst_port);
_graph->connect(
edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
}
// Including the connection to the streamer
UHD_LOG_TRACE("MULTI_USRP",
boost::format("Connecting TxStreamer:%d -> %s:%d") % strm_port
% tx_chain.edge_list.back().dst_blockid
% tx_chain.edge_list.back().dst_port);
_graph->connect(tx_streamer,
strm_port,
tx_chain.edge_list.back().dst_blockid,
tx_chain.edge_list.back().dst_port);
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);
}
}
}
// For legacy purposes: This enables recv_async_msg(), which is considered
// deprecated, but as long as it's there, we need this to approximate
// previous behaviour.
_device->set_tx_stream(tx_streamer);
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")) : "";
const auto rx_power_ref_keys = rx_chain.radio->get_rx_power_ref_keys();
if (!rx_power_ref_keys.empty() && rx_power_ref_keys.size() == 2) {
usrp_info["rx_ref_power_key"] = rx_power_ref_keys.at(0);
usrp_info["rx_ref_power_serial"] = rx_power_ref_keys.at(1);
}
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")) : "";
const auto tx_power_ref_keys = tx_chain.radio->get_tx_power_ref_keys();
if (!tx_power_ref_keys.empty() && tx_power_ref_keys.size() == 2) {
usrp_info["tx_ref_power_key"] = tx_power_ref_keys.at(0);
usrp_info["tx_ref_power_serial"] = tx_power_ref_keys.at(1);
}
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)
{
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_tick_rate();
}
}
for (auto& chain : _tx_chans) {
auto radio = chain.second.radio;
if (radio->get_block_id().get_device_no() == mboard) {
return radio->get_tick_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)
{
MUX_MB_API_CALL(set_time_now, time_spec);
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)
{
MUX_MB_API_CALL(set_time_next_pps, time_spec);
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)
{
MUX_MB_API_CALL(set_command_time, time_spec);
// 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)
{
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)
{
MUX_MB_API_CALL(set_time_source, source);
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)
{
MUX_MB_API_CALL(set_clock_source, source);
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)
{
MUX_MB_API_CALL(set_sync_source, clock_source, time_source);
get_mbc(mboard)->set_sync_source(clock_source, time_source);
}
void set_sync_source(const device_addr_t& sync_source, const size_t mboard)
{
MUX_MB_API_CALL(set_sync_source, sync_source);
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)
{
MUX_MB_API_CALL(set_clock_source_out, enb);
get_mbc(mboard)->set_clock_source_out(enb);
}
void set_time_source_out(const bool enb, const size_t mboard)
{
MUX_MB_API_CALL(set_time_source_out, enb);
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);
auto radio_source_chain = get_block_chain(_graph, radio_id, block_chan, true);
// Find out if we have a DDC in the radio block chain
auto ddc_port_def = [this, radio_source_chain, radio_id, block_chan]() {
try {
for (auto edge : radio_source_chain) {
if (block_id_t(edge.dst_blockid).match("DDC")) {
if (edge.dst_port != block_chan) {
/* We don't expect this to happen very often. But in
* the case that port numbers don't match, we need to
* disable DDC control to ensure we're not controlling
* another channel's DDC
*/
UHD_LOGGER_WARNING("MULTI_USRP")
<< "DDC in radio chain " << radio_id << ":"
<< std::to_string(block_chan)
<< " not connected to the same port number! "
"Disabling DDC control.";
break;
}
auto ddc_blk = _graph->get_block<uhd::rfnoc::ddc_block_control>(
edge.dst_blockid);
return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(
ddc_blk, block_chan);
}
}
} catch (const uhd::exception&) {
UHD_LOGGER_DEBUG("MULTI_USRP")
<< "No DDC found for radio block " << radio_id << ":"
<< std::to_string(block_chan);
// Then just return a nullptr
}
return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(nullptr, 0);
}();
// Create the RX chan
return rx_chan_t(
{radio_blk, std::get<0>(ddc_port_def), block_chan, radio_source_chain});
}
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");
// 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)
{
// 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)
{
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
MUX_RX_API_CALL(set_rx_rate, rate);
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;
}
void set_rx_spp(const size_t spp, const size_t chan = ALL_CHANS)
{
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
MUX_RX_API_CALL(set_rx_spp, spp);
auto rx_chain = _get_rx_chan(chan);
rx_chain.radio->set_property<int>(
"spp", narrow_cast<int>(spp), rx_chain.block_chan);
}
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)
{
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_rates(rx_chain.block_chan);
}
return rx_chain.radio->get_rate_range();
}
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);
}
bool has_rx_power_reference(const size_t chan)
{
auto& rx_chain = _get_rx_chan(chan);
return rx_chain.radio->has_rx_power_reference(rx_chain.block_chan);
}
void set_rx_power_reference(const double power_dbm, const size_t chan)
{
auto& rx_chain = _get_rx_chan(chan);
rx_chain.radio->set_rx_power_reference(power_dbm, rx_chain.block_chan);
}
double get_rx_power_reference(const size_t chan)
{
auto& rx_chain = _get_rx_chan(chan);
return rx_chain.radio->get_rx_power_reference(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)
{
MUX_RX_API_CALL(set_rx_dc_offset, enb);
const auto rx_chain = _get_rx_chan(chan);
rx_chain.radio->set_rx_dc_offset(enb, rx_chain.block_chan);
}
void set_rx_dc_offset(const std::complex<double>& offset, size_t chan)
{
MUX_RX_API_CALL(set_rx_dc_offset, offset);
const auto rx_chain = _get_rx_chan(chan);
rx_chain.radio->set_rx_dc_offset(offset, rx_chain.block_chan);
}
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)
{
MUX_RX_API_CALL(set_rx_iq_balance, correction);
const auto rx_chain = _get_rx_chan(chan);
rx_chain.radio->set_rx_iq_balance(correction, rx_chain.block_chan);
}
/*******************************************************************
* 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);
// Now on to the DUC chain
auto radio_sink_chain = get_block_chain(_graph, radio_id, block_chan, false);
// Find out if we have a DUC in the radio block chain
auto duc_port_def = [this, radio_sink_chain, radio_id, block_chan]() {
try {
for (auto edge : radio_sink_chain) {
if (block_id_t(edge.src_blockid).match("DUC")) {
if (edge.src_port != block_chan) {
/* We don't expect this to happen very often. But in
* the case that port numbers don't match, we need to
* disable DUC control to ensure we're not controlling
* another channel's DDC
*/
UHD_LOGGER_WARNING("MULTI_USRP")
<< "DUC in radio chain " << radio_id << ":"
<< std::to_string(block_chan)
<< " not connected to the same port number! "
"Disabling DUC control.";
break;
}
auto ddc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(
edge.src_blockid);
return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(
ddc_blk, block_chan);
}
}
} catch (const uhd::exception&) {
UHD_LOGGER_DEBUG("MULTI_USRP")
<< "No DDC found for radio block " << radio_id << ":"
<< std::to_string(block_chan);
// Then just return a nullptr
}
return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(nullptr, 0);
}();
// Create the TX chan
return tx_chan_t(
{radio_blk, std::get<0>(duc_port_def), block_chan, radio_sink_chain});
}
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");
// 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)
{
/* 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)
{
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
MUX_TX_API_CALL(set_tx_rate, rate);
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)
{
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_rates(tx_chain.block_chan);
}
return tx_chain.radio->get_rate_range();
}
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);
}
bool has_tx_power_reference(const size_t chan)
{
auto& tx_chain = _get_tx_chan(chan);
return tx_chain.radio->has_rx_power_reference(tx_chain.block_chan);
}
void set_tx_power_reference(const double power_dbm, const size_t chan)
{
auto& tx_chain = _get_tx_chan(chan);
tx_chain.radio->set_tx_power_reference(power_dbm, tx_chain.block_chan);
}
double get_tx_power_reference(const size_t chan)
{
auto& tx_chain = _get_tx_chan(chan);
return tx_chain.radio->get_tx_power_reference(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)
{
MUX_TX_API_CALL(set_tx_dc_offset, offset);
const 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)
{
MUX_TX_API_CALL(set_tx_iq_balance, correction);
const auto tx_chain = _get_tx_chan(chan);
tx_chain.radio->set_tx_iq_balance(correction, tx_chain.block_chan);
}
/*******************************************************************
* GPIO methods
******************************************************************/
/*! Helper function to identify the radio and the bank on that radio.
*
* Background: Historically, multi_usrp has made up its own GPIO bank names,
* unrelated to the radios. Now, we need to be a bit more clever to get that
* legacy behaviour to work.
*
* Here's the algorithm:
* - If the bank ends with 'A' or 'B' we'll use that to identify the radio
* - Otherwise, we'll pick the first radio
* - If the bank ends with 'A' or 'B' we strip that suffix
*
* The returned radio will now have a GPIO bank with the returned name.
*/
std::pair<uhd::rfnoc::radio_control::sptr, std::string> _get_gpio_radio_bank(
const std::string& bank, const size_t mboard)
{
UHD_ASSERT_THROW(!bank.empty());
char suffix = bank[bank.size() - 1];
std::string slot_name;
if (suffix == 'A' || suffix == 'a') {
slot_name = "A";
}
if (suffix == 'B' || suffix == 'b') {
slot_name = "B";
}
uhd::rfnoc::radio_control::sptr radio = [bank, mboard, slot_name, this]() {
auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
std::to_string(mboard) + "/Radio");
for (auto radio_id : radio_blocks) {
auto radio = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
if (slot_name.empty() || radio->get_slot_name() == slot_name) {
return radio;
}
}
throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
+ " to a radio block controller.");
}();
const std::string normalized_bank = [radio, bank]() {
auto radio_banks = radio->get_gpio_banks();
for (auto& radio_bank : radio_banks) {
if (bank.find(radio_bank) == 0) {
return radio_bank;
}
}
throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
+ " to radio " + radio->get_unique_id());
}();
return {radio, normalized_bank};
}
std::vector<std::string> get_gpio_banks(const size_t mboard)
{
auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
std::to_string(mboard) + "/Radio");
std::vector<std::string> gpio_banks;
for (auto radio_id : radio_blocks) {
auto radio = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
auto radio_banks = radio->get_gpio_banks();
for (const auto& bank : radio_banks) {
gpio_banks.push_back(bank + radio->get_slot_name());
}
}
return 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)
{
auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
const uint32_t current =
radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
const uint32_t new_value = (current & ~mask) | (value & mask);
radio_bank_pair.first->set_gpio_attr(radio_bank_pair.second, attr, new_value);
}
uint32_t get_gpio_attr(
const std::string& bank, const std::string& attr, const size_t mboard)
{
auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
return radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
}
std::vector<std::string> get_gpio_src_banks(const size_t mboard)
{
return get_mbc(mboard)->get_gpio_banks();
}
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;
std::shared_ptr<redirector_device> _device;
};
/******************************************************************************
* 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 std::make_shared<multi_usrp_rfnoc>(graph, dev_addr);
}
}}} // namespace uhd::rfnoc::detail