mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-15 21:01:26 +00:00
Unless the user specifies an spp value in a Tx streamer by means of a stream argument, the Tx streamer now holds additional logic that aligns the spp value with the CHDR width. For example, in an X410 100 GbE image, the CHDR width is typically 512 bits, which corresponds to 16 samples (sc16), so spp will default to value that's a multiple of 16. Previously, the default was to simply use the maximum MTU. However, there have been circumstances observed where using a non-aligned spp value causes suboptimal behaviour. For example, when the CHDR width is 512 bits, and the discovered MTU is 8016 bytes, the default spp value would be 1988 samples per packet. However, a value of 1984 samples per packet causes the CHDR packet to be of a length that is a multiple of 64 bytes (== 512 bits) which can improve performance. This change does not affect applications where the spp size is already manually configured, and has almost no influence on use cases where the CHDR width is 64, because that just means the optimal number of samples per packet should be even. Under the hood, the following changes were made: - The CHDR width is passed into the streamer as a reserved key (__chdr_width) from the infrastructure. - After connecting the Tx streamer to the graph, when the MTU is known, we add a check for the spp value to make it an integer multiple of the CHDR width if not specifically given (like a "soft atomic item size"). Co-authored-by: Jörg Hofrichter <joerg.hofrichter@emerson.com>
2894 lines
116 KiB
C++
2894 lines
116 KiB
C++
//
|
|
// Copyright 2019-2022 Ettus Research, a National Instruments Brand
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
#include <uhd/convert.hpp>
|
|
#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/replay_block_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 <uhd/utils/math.hpp>
|
|
#include <uhd/utils/string.hpp>
|
|
#include <uhdlib/extension/extension_factory.hpp>
|
|
#include <uhdlib/rfnoc/rfnoc_device.hpp>
|
|
#include <uhdlib/rfnoc/rfnoc_rx_streamer.hpp>
|
|
#include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp>
|
|
#include <uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp>
|
|
#include <uhdlib/usrp/gpio_defs.hpp>
|
|
#include <uhdlib/usrp/multi_usrp_utils.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)
|
|
{
|
|
_tree = musrp_ptr->get_tree();
|
|
}
|
|
|
|
rx_streamer::sptr get_rx_stream(const stream_args_t& args) override
|
|
{
|
|
return _musrp->get_rx_stream(args);
|
|
}
|
|
|
|
tx_streamer::sptr get_tx_stream(const stream_args_t& args) override
|
|
{
|
|
auto streamer = _musrp->get_tx_stream(args);
|
|
_last_tx_streamer = streamer;
|
|
return streamer;
|
|
}
|
|
|
|
bool recv_async_msg(async_metadata_t& md, double timeout) override
|
|
{
|
|
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;
|
|
}
|
|
|
|
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
|
|
{
|
|
using replay_config_t = rfnoc_tx_streamer_replay_buffered::replay_config_t;
|
|
|
|
public:
|
|
struct rx_chan_t
|
|
{
|
|
radio_control::sptr radio;
|
|
uhd::extension::extension::sptr extension;
|
|
uhd::rfnoc::rf_control::core_iface::sptr rf_core;
|
|
uhd::rfnoc::rf_control::power_reference_iface::sptr rf_power_reference;
|
|
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;
|
|
uhd::extension::extension::sptr extension;
|
|
uhd::rfnoc::rf_control::core_iface::sptr rf_core;
|
|
uhd::rfnoc::rf_control::power_reference_iface::sptr rf_power_reference;
|
|
duc_block_control::sptr duc; // can be nullptr
|
|
size_t block_chan;
|
|
std::vector<graph_edge_t> edge_list;
|
|
replay_config_t replay;
|
|
};
|
|
|
|
std::map<std::tuple<block_id_t, uhd::direction_t, size_t>,
|
|
uhd::extension::extension::sptr>
|
|
_extensions;
|
|
|
|
/**************************************************************************
|
|
* 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. Do this one motherboard at a time
|
|
// because each device can have a different subdev spec.
|
|
size_t musrp_rx_channel = 0;
|
|
size_t musrp_tx_channel = 0;
|
|
|
|
for (size_t mboard = 0; mboard < get_num_mboards(); mboard++) {
|
|
auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio");
|
|
|
|
// Generate the RX and TX subdev specs
|
|
uhd::usrp::subdev_spec_t rx_radio_subdev;
|
|
uhd::usrp::subdev_spec_t tx_radio_subdev;
|
|
for (auto radio_id : radio_blk_ids) {
|
|
auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
|
|
|
|
uhd::extension::extension::sptr extension;
|
|
if (_args.has_key("extension")) {
|
|
auto extension_factory =
|
|
uhd::extension::extension_factory::get_extension_factory(
|
|
_args.get("extension"));
|
|
if (extension_factory) {
|
|
uhd::extension::extension::factory_args fargs{
|
|
radio_blk, _get_mbc(mboard)};
|
|
extension = extension_factory(fargs);
|
|
} else {
|
|
UHD_LOG_THROW(uhd::value_error,
|
|
"Multi_USRP_RFNoC",
|
|
"Unrecognized extension: " << _args.get("extension"));
|
|
}
|
|
}
|
|
|
|
// Store radio blocks per mboard for quick retrieval
|
|
_radios[mboard].push_back(radio_blk);
|
|
|
|
for (size_t block_chan = 0;
|
|
block_chan < radio_blk->get_num_output_ports();
|
|
++block_chan) {
|
|
if (extension) {
|
|
_extensions[std::make_tuple(radio_id, RX_DIRECTION, block_chan)] =
|
|
extension;
|
|
}
|
|
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)));
|
|
}
|
|
|
|
for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports();
|
|
++block_chan) {
|
|
if (extension) {
|
|
_extensions[std::make_tuple(radio_id, TX_DIRECTION, block_chan)] =
|
|
extension;
|
|
}
|
|
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)));
|
|
}
|
|
}
|
|
|
|
// Generate the TX and RX chans
|
|
// 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)
|
|
auto rx_chans = _generate_mboard_rx_chans(rx_radio_subdev, mboard);
|
|
for (auto rx_chan : rx_chans) {
|
|
_rx_chans.emplace(musrp_rx_channel, rx_chan);
|
|
++musrp_rx_channel;
|
|
}
|
|
auto tx_chans = _generate_mboard_tx_chans(tx_radio_subdev, mboard);
|
|
for (auto tx_chan : tx_chans) {
|
|
_tx_chans.emplace(musrp_tx_channel, tx_chan);
|
|
++musrp_tx_channel;
|
|
}
|
|
}
|
|
|
|
if (_rx_chans.empty() and _tx_chans.empty()) {
|
|
// If we don't find any valid radio chains, we don't have a multi_usrp
|
|
// object
|
|
throw uhd::runtime_error(
|
|
"[multi_usrp] No valid radio channels found in connected devices.");
|
|
}
|
|
|
|
// 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() override
|
|
{
|
|
// nop
|
|
}
|
|
|
|
device::sptr get_device(void) override
|
|
{
|
|
return _device;
|
|
}
|
|
|
|
uhd::property_tree::sptr get_tree() const override
|
|
{
|
|
return _tree;
|
|
}
|
|
|
|
rx_streamer::sptr get_rx_stream(const stream_args_t& args_) override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
|
|
stream_args_t args = sanitize_stream_args(args_);
|
|
double rate = 1.0;
|
|
|
|
// 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.
|
|
|
|
// Connect the chains
|
|
auto edges = _connect_rx_chains(args.channels);
|
|
std::weak_ptr<rfnoc_graph> graph_ref(_graph);
|
|
|
|
// Create the streamer
|
|
// The disconnect callback must disconnect the entire chain because the radio
|
|
// relies on the connections to determine what is enabled.
|
|
auto rx_streamer = std::make_shared<rfnoc_rx_streamer>(
|
|
args.channels.size(), args, [=](const std::string& id) {
|
|
if (auto graph = graph_ref.lock()) {
|
|
graph->disconnect(id);
|
|
for (auto edge : edges) {
|
|
graph->disconnect(edge.src_blockid,
|
|
edge.src_port,
|
|
edge.dst_blockid,
|
|
edge.dst_port);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Connect the streamer
|
|
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.edge_list.empty()) {
|
|
throw uhd::runtime_error("Graph edge list is empty for rx channel "
|
|
+ std::to_string(rx_channel));
|
|
}
|
|
UHD_LOG_TRACE("MULTI_USRP",
|
|
"Connecting " << rx_chain.edge_list.back().src_blockid << ":"
|
|
<< rx_chain.edge_list.back().src_port
|
|
<< " -> RxStreamer:" << 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_) override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
|
|
stream_args_t args = sanitize_stream_args(args_);
|
|
double rate = 1.0;
|
|
bool replay_buffered = (args.args.has_key("streamer")
|
|
and args.args["streamer"] == "replay_buffered");
|
|
|
|
// 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.
|
|
|
|
// Connect the chains
|
|
std::map<size_t, std::vector<graph_edge_t>> edge_lists;
|
|
std::vector<replay_config_t> replay_configs;
|
|
for (auto channel : args.channels) {
|
|
if (replay_buffered) {
|
|
edge_lists[channel] = _connect_tx_chain_with_replay(channel);
|
|
replay_configs.push_back(_get_tx_chan(channel).replay);
|
|
} else {
|
|
edge_lists[channel] = _connect_tx_chain(channel);
|
|
}
|
|
}
|
|
|
|
// Create a streamer
|
|
// The disconnect callback must disconnect the entire chain because the radio
|
|
// relies on the connections to determine what is enabled.
|
|
tx_streamer::sptr tx_streamer;
|
|
std::weak_ptr<rfnoc_graph> graph_ref(_graph);
|
|
auto disconnect = [=](const std::string& id) {
|
|
if (auto graph = graph_ref.lock()) {
|
|
graph->disconnect(id);
|
|
for (auto edge_list : edge_lists) {
|
|
for (auto edge : edge_list.second) {
|
|
if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)
|
|
or block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
|
|
continue;
|
|
}
|
|
graph->disconnect(edge.src_blockid,
|
|
edge.src_port,
|
|
edge.dst_blockid,
|
|
edge.dst_port);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
if (replay_buffered) {
|
|
tx_streamer = std::make_shared<rfnoc_tx_streamer_replay_buffered>(
|
|
args.channels.size(), args, disconnect, replay_configs);
|
|
} else {
|
|
args.args["__chdr_width"] =
|
|
std::to_string(chdr_w_to_bits(_graph->get_chdr_width()));
|
|
tx_streamer = std::make_shared<rfnoc_tx_streamer>(
|
|
args.channels.size(), args, disconnect);
|
|
}
|
|
|
|
// Connect the streamer
|
|
for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) {
|
|
auto tx_channel = args.channels.at(strm_port);
|
|
auto edge_list = edge_lists[tx_channel];
|
|
if (edge_list.empty()) {
|
|
throw uhd::runtime_error("Graph edge list is empty for tx channel "
|
|
+ std::to_string(tx_channel));
|
|
}
|
|
UHD_LOG_TRACE("MULTI_USRP",
|
|
"Connecting TxStreamer:" << strm_port << " -> "
|
|
<< edge_list.back().dst_blockid << ":"
|
|
<< edge_list.back().dst_port);
|
|
_graph->connect(tx_streamer,
|
|
strm_port,
|
|
edge_list.back().dst_blockid,
|
|
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;
|
|
}
|
|
|
|
|
|
dict<std::string, std::string> get_usrp_rx_info(size_t chan) override
|
|
{
|
|
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["module_serial"] =
|
|
mb_eeprom.get("module_serial", mb_eeprom.get("serial", "n/a"));
|
|
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"))
|
|
: db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial"))
|
|
: "";
|
|
usrp_info["rx_id"] = db_eeprom.count("rx_id")
|
|
? bytes_to_str(db_eeprom.at("rx_id"))
|
|
: db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid"))
|
|
: "";
|
|
|
|
// TODO: Determine if rx_power_ref_keys may interact with RF extensions
|
|
const auto rx_power_ref_keys =
|
|
rx_chain.rf_power_reference->get_rx_power_ref_keys(rx_chain.block_chan);
|
|
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);
|
|
}
|
|
|
|
if (rx_chain.extension) {
|
|
usrp_info["rx_extension"] = rx_chain.extension->get_name();
|
|
}
|
|
|
|
return usrp_info;
|
|
}
|
|
|
|
dict<std::string, std::string> get_usrp_tx_info(size_t chan) override
|
|
{
|
|
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["module_serial"] =
|
|
mb_eeprom.get("module_serial", mb_eeprom.get("serial", "n/a"));
|
|
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"))
|
|
: db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial"))
|
|
: "";
|
|
usrp_info["tx_id"] = db_eeprom.count("tx_id")
|
|
? bytes_to_str(db_eeprom.at("tx_id"))
|
|
: db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid"))
|
|
: "";
|
|
|
|
// TODO: Determine if tx_power_ref_keys may interact with RF extensions
|
|
const auto tx_power_ref_keys =
|
|
tx_chain.radio->get_tx_power_ref_keys(tx_chain.block_chan);
|
|
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);
|
|
}
|
|
|
|
if (tx_chain.extension) {
|
|
usrp_info["tx_extension"] = tx_chain.extension->get_name();
|
|
}
|
|
|
|
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:
|
|
if ((tune_request.dsp_freq_policy == tune_request_t::POLICY_AUTO)
|
|
&& (dsp_freq_range.size() == 1) && dsp_freq_range.stop() == 0) {
|
|
/* Hardware does not incl. DSP chain
|
|
* (dsp_freq_range only has single item, with value 0),
|
|
* requested dsp frequency will be combined with rf frequency.
|
|
* The case to handle uses MANUAL rf_freq_policy and
|
|
* AUTOMATIC dsp_freq_policy */
|
|
UHD_LOGGER_WARNING("MULTI_USRP")
|
|
<< boost::format("No DSP capabilities detected. Combining offset "
|
|
"into target frequency of %.3fMHz")
|
|
% (clipped_requested_freq / 1e6);
|
|
target_rf_freq = clipped_requested_freq;
|
|
} else {
|
|
/* Normal manual mode observing individual tune requests*/
|
|
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();
|
|
|
|
UHD_LOGGER_TRACE("MULTI_USRP")
|
|
<< "Actual RF Freq: " + std::to_string(actual_rf_freq / 1e6) + "MHz";
|
|
|
|
//------------------------------------------------------------------
|
|
//-- 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();
|
|
UHD_LOGGER_TRACE("MULTI_USRP")
|
|
<< "Actual DSP Freq: " + std::to_string(actual_dsp_freq / 1e6) + "MHz";
|
|
|
|
//------------------------------------------------------------------
|
|
//-- 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) override
|
|
{
|
|
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) override
|
|
{
|
|
// We pick the first radio we can find on this mboard and get its master clock
|
|
// rate. A warning is thrown if there are different rates in different radios.
|
|
std::vector<double> mcrs;
|
|
for (auto& chain : _rx_chans) {
|
|
auto radio = chain.second.radio;
|
|
radio->get_unique_id();
|
|
if (radio->get_block_id().get_device_no() == mboard) {
|
|
mcrs.push_back(radio->get_tick_rate());
|
|
}
|
|
}
|
|
for (auto& chain : _tx_chans) {
|
|
auto radio = chain.second.radio;
|
|
if (radio->get_block_id().get_device_no() == mboard) {
|
|
mcrs.push_back(radio->get_tick_rate());
|
|
}
|
|
}
|
|
if (!mcrs.empty()) {
|
|
if (!std::equal(mcrs.begin() + 1, mcrs.end(), mcrs.begin())) {
|
|
UHD_LOGGER_WARNING("MULTI_USRP")
|
|
<< "Different master clock rates on different radio blocks. "
|
|
"Returning value for radio block #0 only. Consider using "
|
|
"get_tick_rate() on the RFNoC radio block to retrieve the master "
|
|
"clock rates per radio block.";
|
|
}
|
|
return mcrs[0];
|
|
}
|
|
throw uhd::key_error("Invalid mboard index!");
|
|
}
|
|
|
|
meta_range_t get_master_clock_rate_range(const size_t mboard) override
|
|
{
|
|
// We pick the first radio we can find on this mboard, and get its range. Since
|
|
// most devices don't support changing the MCR at runtime, the range thus consists
|
|
// of a single value. A warning is thrown if there are different rate ranges in
|
|
// different radios.
|
|
std::vector<meta_range_t> ranges;
|
|
for (auto& chain : _rx_chans) {
|
|
auto radio = chain.second.radio;
|
|
if (radio->get_block_id().get_device_no() == mboard) {
|
|
ranges.push_back(radio->get_rate_range());
|
|
}
|
|
}
|
|
for (auto& chain : _tx_chans) {
|
|
auto radio = chain.second.radio;
|
|
if (radio->get_block_id().get_device_no() == mboard) {
|
|
ranges.push_back(radio->get_rate_range());
|
|
}
|
|
}
|
|
if (!ranges.empty()) {
|
|
if (!std::equal(ranges.begin() + 1, ranges.end(), ranges.begin())) {
|
|
UHD_LOGGER_WARNING("MULTI_USRP")
|
|
<< "Different master clock rate ranges on different radio blocks. "
|
|
"Returning value for radio block #0 only. Consider using "
|
|
"get_rate_range() on the RFNoC radio block to retrieve the master "
|
|
"clock rate ranges per radio block.";
|
|
}
|
|
return ranges[0];
|
|
}
|
|
throw uhd::key_error("Invalid mboard index!");
|
|
}
|
|
|
|
std::string get_pp_string(void) override
|
|
{
|
|
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));
|
|
|
|
if (_rx_chans.at(rx_chan).extension) {
|
|
buff += str(boost::format(" RX Extension: %s\n")
|
|
% _rx_chans.at(rx_chan).extension->get_name());
|
|
}
|
|
}
|
|
|
|
//----------- 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));
|
|
|
|
if (_tx_chans.at(tx_chan).extension) {
|
|
buff += str(boost::format(" TX Extension: %s\n")
|
|
% _rx_chans.at(tx_chan).extension->get_name());
|
|
}
|
|
}
|
|
|
|
return buff;
|
|
}
|
|
|
|
std::string get_mboard_name(size_t mboard = 0) override
|
|
{
|
|
return _get_mbc(mboard)->get_mboard_name();
|
|
}
|
|
|
|
time_spec_t get_time_now(size_t mboard = 0) override
|
|
{
|
|
return _get_time_now(mboard);
|
|
}
|
|
|
|
time_spec_t get_time_last_pps(size_t mboard = 0) override
|
|
{
|
|
return _get_time_last_pps(mboard);
|
|
}
|
|
|
|
void set_time_now(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS) override
|
|
{
|
|
MUX_MB_API_CALL(set_time_now, time_spec);
|
|
for (size_t tk = 0; tk < _get_mbc(mboard)->get_num_timekeepers(); tk++) {
|
|
_get_mbc(mboard)->get_timekeeper(tk)->set_time_now(time_spec);
|
|
}
|
|
}
|
|
|
|
void set_time_next_pps(
|
|
const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS) override
|
|
{
|
|
MUX_MB_API_CALL(set_time_next_pps, time_spec);
|
|
for (size_t tk = 0; tk < _get_mbc(mboard)->get_num_timekeepers(); tk++) {
|
|
_get_mbc(mboard)->get_timekeeper(tk)->set_time_next_pps(time_spec);
|
|
}
|
|
}
|
|
|
|
void set_time_unknown_pps(const time_spec_t& time_spec) override
|
|
{
|
|
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
|
|
// TODO: This code is similar to the check implemented
|
|
// in mb_controller::synchronize >> sync_tks
|
|
// maybe we can find a way to single source this
|
|
for (size_t m = 0; m < get_num_mboards(); m++) {
|
|
for (size_t tk = 0; tk < _get_mbc(m)->get_num_timekeepers(); tk++) {
|
|
time_spec_t time_0 = this->_get_time_now(0, 0);
|
|
time_spec_t time_i = this->_get_time_now(m, tk);
|
|
// 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 %1%/TK "
|
|
"%2% and board 0.\n"
|
|
"Board 0/TK 0 time is %3% seconds.\n"
|
|
"Board %1%/TK %2% time is %4% seconds.\n")
|
|
% m % tk % time_0.get_real_secs() % time_i.get_real_secs();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool get_time_synchronized(void) override
|
|
{
|
|
// verify that the time registers are read to be within a few RTT
|
|
for (size_t m = 0; m < get_num_mboards(); m++) {
|
|
for (size_t tk = 0; tk < _get_mbc(m)->get_num_timekeepers(); tk++) {
|
|
time_spec_t time_0 = this->_get_time_now(0, 0);
|
|
time_spec_t time_i = this->_get_time_now(m, tk);
|
|
// 10 ms: greater than RTT but not too big
|
|
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) override
|
|
{
|
|
MUX_MB_API_CALL(set_command_time, time_spec);
|
|
|
|
// Set command time on all the blocks that are connected
|
|
for (auto& chain : _rx_chans) {
|
|
if (chain.second.radio->get_block_id().get_device_no() != mboard) {
|
|
continue;
|
|
}
|
|
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) {
|
|
if (chain.second.radio->get_block_id().get_device_no() != mboard) {
|
|
continue;
|
|
}
|
|
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) override
|
|
{
|
|
if (mboard == ALL_MBOARDS) {
|
|
for (size_t i = 0; i < get_num_mboards(); ++i) {
|
|
clear_command_time(i);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Clear command time on all the blocks that are connected
|
|
for (auto& chain : _rx_chans) {
|
|
if (chain.second.radio->get_block_id().get_device_no() != mboard) {
|
|
continue;
|
|
}
|
|
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) {
|
|
if (chain.second.radio->get_block_id().get_device_no() != mboard) {
|
|
continue;
|
|
}
|
|
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) override
|
|
{
|
|
MUX_RX_API_CALL(issue_stream_cmd, stream_cmd);
|
|
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);
|
|
}
|
|
}
|
|
|
|
void set_time_source(
|
|
const std::string& source, const size_t mboard = ALL_MBOARDS) override
|
|
{
|
|
MUX_MB_API_CALL(set_time_source, source);
|
|
_get_mbc(mboard)->set_time_source(source);
|
|
}
|
|
|
|
std::string get_time_source(const size_t mboard) override
|
|
{
|
|
return _get_mbc(mboard)->get_time_source();
|
|
}
|
|
|
|
std::vector<std::string> get_time_sources(const size_t mboard) override
|
|
{
|
|
return _get_mbc(mboard)->get_time_sources();
|
|
}
|
|
|
|
void set_clock_source(
|
|
const std::string& source, const size_t mboard = ALL_MBOARDS) override
|
|
{
|
|
MUX_MB_API_CALL(set_clock_source, source);
|
|
_get_mbc(mboard)->set_clock_source(source);
|
|
}
|
|
|
|
std::string get_clock_source(const size_t mboard) override
|
|
{
|
|
return _get_mbc(mboard)->get_clock_source();
|
|
}
|
|
|
|
std::vector<std::string> get_clock_sources(const size_t mboard) override
|
|
{
|
|
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) override
|
|
{
|
|
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 = ALL_MBOARDS) override
|
|
{
|
|
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) override
|
|
{
|
|
return _get_mbc(mboard)->get_sync_source();
|
|
}
|
|
|
|
std::vector<device_addr_t> get_sync_sources(const size_t mboard) override
|
|
{
|
|
return _get_mbc(mboard)->get_sync_sources();
|
|
}
|
|
|
|
void set_clock_source_out(const bool enb, const size_t mboard = ALL_MBOARDS) override
|
|
{
|
|
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 = ALL_MBOARDS) override
|
|
{
|
|
MUX_MB_API_CALL(set_time_source_out, enb);
|
|
_get_mbc(mboard)->set_time_source_out(enb);
|
|
}
|
|
|
|
size_t get_num_mboards(void) override
|
|
{
|
|
return _graph->get_num_mboards();
|
|
}
|
|
|
|
sensor_value_t get_mboard_sensor(const std::string& name, size_t mboard = 0) override
|
|
{
|
|
return _get_mbc(mboard)->get_sensor(name);
|
|
}
|
|
|
|
std::vector<std::string> get_mboard_sensor_names(size_t mboard = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
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) override
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
uhd::rfnoc::radio_control& get_radio_control(const size_t chan = 0) override
|
|
{
|
|
return *_get_rx_chan(chan).radio;
|
|
}
|
|
|
|
uhd::extension::extension::sptr get_extension(
|
|
const direction_t trx, const size_t chan) override
|
|
{
|
|
if (trx == RX_DIRECTION) {
|
|
return _get_rx_chan(chan).extension;
|
|
} else if (trx == TX_DIRECTION) {
|
|
return _get_tx_chan(chan).extension;
|
|
} else {
|
|
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
|
|
uhd::extension::extension::sptr extension =
|
|
_extensions[std::make_tuple(radio_id, RX_DIRECTION, block_chan)];
|
|
uhd::rfnoc::rf_control::core_iface::sptr rf_core =
|
|
extension
|
|
? std::dynamic_pointer_cast<uhd::rfnoc::rf_control::core_iface>(extension)
|
|
: std::dynamic_pointer_cast<uhd::rfnoc::rf_control::core_iface>(
|
|
radio_blk);
|
|
uhd::rfnoc::rf_control::power_reference_iface::sptr power_ref =
|
|
extension ? std::dynamic_pointer_cast<
|
|
uhd::rfnoc::rf_control::power_reference_iface>(extension)
|
|
: std::dynamic_pointer_cast<
|
|
uhd::rfnoc::rf_control::power_reference_iface>(radio_blk);
|
|
|
|
return rx_chan_t({radio_blk,
|
|
extension,
|
|
rf_core,
|
|
power_ref,
|
|
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 = ALL_MBOARDS) override
|
|
{
|
|
_set_subdev_spec(
|
|
_rx_chans,
|
|
[this](size_t current_mboard) {
|
|
return this->get_rx_subdev_spec(current_mboard);
|
|
},
|
|
[this](uhd::usrp::subdev_spec_t current_spec, size_t current_mboard) {
|
|
return this->_generate_mboard_rx_chans(current_spec, current_mboard);
|
|
},
|
|
[](uhd::rfnoc::graph_edge_t edge) {
|
|
return block_id_t(edge.dst_blockid).match(NODE_ID_SEP);
|
|
},
|
|
spec,
|
|
mboard);
|
|
}
|
|
|
|
uhd::usrp::subdev_spec_t get_rx_subdev_spec(size_t mboard = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
return _rx_chans.size();
|
|
}
|
|
|
|
std::string get_rx_subdev_name(size_t chan = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
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) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
|
|
|
|
// TODO: Add external LO warning
|
|
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
|
|
rx_chain.rf_core->set_rx_tune_args(tune_request.args, rx_chain.block_chan);
|
|
//------------------------------------------------------------------
|
|
//-- calculate the tunable frequency ranges of the system
|
|
//------------------------------------------------------------------
|
|
freq_range_t tune_range_nonmono =
|
|
(rx_chain.ddc)
|
|
? make_overall_tune_range(
|
|
rx_chain.rf_core->get_rx_frequency_range(rx_chain.block_chan),
|
|
rx_chain.ddc->get_frequency_range(rx_chain.block_chan),
|
|
rx_chain.rf_core->get_rx_bandwidth(rx_chain.block_chan))
|
|
: rx_chain.rf_core->get_rx_frequency_range(rx_chain.block_chan);
|
|
freq_range_t tune_range = tune_range_nonmono.as_monotonic();
|
|
|
|
freq_range_t rf_range =
|
|
rx_chain.rf_core->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, 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.rf_core->set_rx_frequency(freq, rx_chain.block_chan);
|
|
};
|
|
auto get_rf_freq = [rx_chain](void) {
|
|
return rx_chain.rf_core->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 = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
|
|
// extract actual dsp and IF frequencies
|
|
const double actual_rf_freq =
|
|
rx_chain.rf_core->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;
|
|
|
|
return actual_rf_freq - actual_dsp_freq * RX_SIGN;
|
|
}
|
|
|
|
freq_range_t get_rx_freq_range(size_t chan = 0) override
|
|
{
|
|
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.rf_core->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 = 0) override
|
|
{
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_frequency_range(rx_chain.block_chan);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* LO controls
|
|
*************************************************************************/
|
|
std::vector<std::string> get_rx_lo_names(size_t chan = 0) override
|
|
{
|
|
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 = ALL_LOS,
|
|
size_t chan = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_lo_source, src, name);
|
|
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 = ALL_LOS, size_t chan = 0) override
|
|
{
|
|
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 = ALL_LOS, size_t chan = 0) override
|
|
{
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.radio->get_rx_lo_sources(name, rx_chain.block_chan);
|
|
}
|
|
|
|
void set_rx_lo_export_enabled(
|
|
bool enabled, const std::string& name = ALL_LOS, size_t chan = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_lo_export_enabled, enabled, name);
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
rx_chain.radio->set_rx_lo_export_enabled(enabled, name, rx_chain.block_chan);
|
|
}
|
|
|
|
bool get_rx_lo_export_enabled(
|
|
const std::string& name = ALL_LOS, size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = ALL_LOS,
|
|
const size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_lo_source, src, name);
|
|
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 = ALL_LOS, const size_t chan = 0) override
|
|
{
|
|
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 = ALL_LOS, const size_t chan = 0) override
|
|
{
|
|
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 = ALL_LOS,
|
|
const size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_lo_export_enabled, enabled, name);
|
|
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 = ALL_LOS, const size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_gain, gain, name);
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
rx_chain.rf_core->set_rx_gain(gain, name, rx_chain.block_chan);
|
|
}
|
|
|
|
std::vector<std::string> get_rx_gain_profile_names(const size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_gain_profile, profile);
|
|
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 = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
if (gain > 1.0 || gain < 0.0) {
|
|
throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
|
|
}
|
|
MUX_RX_API_CALL(set_normalized_rx_gain, gain);
|
|
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 = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_agc, enable);
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
rx_chain.rf_core->set_rx_agc(enable, rx_chain.block_chan);
|
|
}
|
|
|
|
double get_rx_gain(const std::string& name, size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_gain(name, rx_chain.block_chan);
|
|
}
|
|
|
|
double get_normalized_rx_gain(size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_gain_range(name, rx_chain.block_chan);
|
|
}
|
|
|
|
std::vector<std::string> get_rx_gain_names(size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_gain_names(rx_chain.block_chan);
|
|
}
|
|
|
|
bool has_rx_power_reference(const size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_power_reference->has_rx_power_reference(rx_chain.block_chan);
|
|
}
|
|
|
|
void set_rx_power_reference(const double power_dbm, const size_t chan = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_power_reference, power_dbm);
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
rx_chain.rf_power_reference->set_rx_power_reference(
|
|
power_dbm, rx_chain.block_chan);
|
|
}
|
|
|
|
double get_rx_power_reference(const size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_power_reference->get_rx_power_reference(rx_chain.block_chan);
|
|
}
|
|
|
|
meta_range_t get_rx_power_range(const size_t chan) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_power_reference->get_rx_power_range(rx_chain.block_chan);
|
|
}
|
|
|
|
void set_rx_antenna(const std::string& ant, size_t chan = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_antenna, ant);
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
rx_chain.rf_core->set_rx_antenna(ant, rx_chain.block_chan);
|
|
}
|
|
|
|
std::string get_rx_antenna(size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_antenna(rx_chain.block_chan);
|
|
}
|
|
|
|
std::vector<std::string> get_rx_antennas(size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_antennas(rx_chain.block_chan);
|
|
}
|
|
|
|
void set_rx_bandwidth(double bandwidth, size_t chan = 0) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_bandwidth, bandwidth);
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
rx_chain.rf_core->set_rx_bandwidth(bandwidth, rx_chain.block_chan);
|
|
}
|
|
|
|
double get_rx_bandwidth(size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_bandwidth(rx_chain.block_chan);
|
|
}
|
|
|
|
meta_range_t get_rx_bandwidth_range(size_t chan = 0) override
|
|
{
|
|
auto& rx_chain = _get_rx_chan(chan);
|
|
return rx_chain.rf_core->get_rx_bandwidth_range(rx_chain.block_chan);
|
|
}
|
|
|
|
dboard_iface::sptr get_rx_dboard_iface(size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
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 = ALL_CHANS) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_iq_balance, enb);
|
|
auto rx_chain = _get_rx_chan(chan);
|
|
rx_chain.radio->set_rx_iq_balance(enb, rx_chain.block_chan);
|
|
}
|
|
|
|
void set_rx_iq_balance(
|
|
const std::complex<double>& correction, size_t chan = ALL_CHANS) override
|
|
{
|
|
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 duc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(
|
|
edge.src_blockid);
|
|
return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(
|
|
duc_blk, block_chan);
|
|
}
|
|
}
|
|
} catch (const uhd::exception&) {
|
|
UHD_LOGGER_DEBUG("MULTI_USRP")
|
|
<< "No DUC 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
|
|
uhd::extension::extension::sptr extension =
|
|
_extensions[std::make_tuple(radio_id, TX_DIRECTION, block_chan)];
|
|
uhd::rfnoc::rf_control::core_iface::sptr rf_core =
|
|
extension
|
|
? std::dynamic_pointer_cast<uhd::rfnoc::rf_control::core_iface>(extension)
|
|
: std::dynamic_pointer_cast<uhd::rfnoc::rf_control::core_iface>(
|
|
radio_blk);
|
|
uhd::rfnoc::rf_control::power_reference_iface::sptr power_ref =
|
|
extension ? std::dynamic_pointer_cast<
|
|
uhd::rfnoc::rf_control::power_reference_iface>(extension)
|
|
: std::dynamic_pointer_cast<
|
|
uhd::rfnoc::rf_control::power_reference_iface>(radio_blk);
|
|
return tx_chan_t({radio_blk,
|
|
extension,
|
|
rf_core,
|
|
power_ref,
|
|
std::get<0>(duc_port_def),
|
|
block_chan,
|
|
radio_sink_chain,
|
|
replay_config_t()});
|
|
}
|
|
|
|
// Generate the TX chains. Must call with a complete subdev spec for the mboard.
|
|
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 the device 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));
|
|
}
|
|
|
|
// Set up to map Replay blocks and ports to channel in case the user
|
|
// requests Replay buffering on the TX streamer
|
|
const auto replay_blk_ids =
|
|
_graph->find_blocks(std::to_string(mboard) + "/Replay");
|
|
size_t replay_index = 0;
|
|
size_t replay_port = 0;
|
|
size_t num_replay_ports = 0;
|
|
for (const auto& replay_id : replay_blk_ids) {
|
|
auto replay = _graph->get_block<uhd::rfnoc::replay_block_control>(replay_id);
|
|
num_replay_ports +=
|
|
std::min(replay->get_num_input_ports(), replay->get_num_output_ports());
|
|
}
|
|
|
|
// 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) {
|
|
tx_chan_t tx_chan(_generate_tx_radio_chan(radio_id, block_chan));
|
|
|
|
// Map a Replay block and port to the channel if there are
|
|
// enough Replay ports to cover all the channels
|
|
if (num_replay_ports >= spec.size()) {
|
|
auto replay_id = replay_blk_ids[replay_index];
|
|
auto replay = _graph->get_block<uhd::rfnoc::replay_block_control>(
|
|
replay_id);
|
|
size_t num_ports = std::min(replay->get_num_input_ports(),
|
|
replay->get_num_output_ports());
|
|
while (replay_port >= num_ports) {
|
|
// All ports on the current Replay block are allocated.
|
|
// Get the next Replay block
|
|
replay_index++;
|
|
replay_port = 0;
|
|
replay_id = replay_blk_ids[replay_index];
|
|
replay = _graph->get_block<uhd::rfnoc::replay_block_control>(
|
|
replay_id);
|
|
num_ports = std::min(replay->get_num_input_ports(),
|
|
replay->get_num_output_ports());
|
|
}
|
|
|
|
// Update the Replay configuration (including memory
|
|
// allocation)
|
|
const auto mem_per_block =
|
|
replay->get_mem_size() / replay_blk_ids.size();
|
|
tx_chan.replay.ctrl = replay;
|
|
tx_chan.replay.port = replay_port;
|
|
tx_chan.replay.mem_size = mem_per_block / num_ports;
|
|
tx_chan.replay.start_address =
|
|
(replay_index * mem_per_block)
|
|
+ (tx_chan.replay.mem_size * replay_port);
|
|
|
|
// Get the next Replay port
|
|
replay_port++;
|
|
} else {
|
|
tx_chan.replay.ctrl = nullptr;
|
|
}
|
|
|
|
new_chans.push_back(tx_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) override
|
|
{
|
|
_set_subdev_spec(
|
|
_tx_chans,
|
|
[this](size_t current_mboard) {
|
|
return this->get_tx_subdev_spec(current_mboard);
|
|
},
|
|
[this](uhd::usrp::subdev_spec_t current_spec, size_t current_mboard) {
|
|
return this->_generate_mboard_tx_chans(current_spec, current_mboard);
|
|
},
|
|
[](uhd::rfnoc::graph_edge_t edge) {
|
|
return block_id_t(edge.src_blockid).match(NODE_ID_SEP);
|
|
},
|
|
spec,
|
|
mboard);
|
|
}
|
|
|
|
uhd::usrp::subdev_spec_t get_tx_subdev_spec(size_t mboard = 0) override
|
|
{
|
|
uhd::usrp::subdev_spec_t result;
|
|
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.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) override
|
|
{
|
|
return _tx_chans.size();
|
|
}
|
|
|
|
std::string get_tx_subdev_name(size_t chan = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> l(_graph_mutex);
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
|
|
tx_chain.rf_core->set_tx_tune_args(tune_request.args, tx_chain.block_chan);
|
|
//------------------------------------------------------------------
|
|
//-- calculate the tunable frequency ranges of the system
|
|
//------------------------------------------------------------------
|
|
freq_range_t tune_range_nonmono =
|
|
(tx_chain.duc)
|
|
? make_overall_tune_range(
|
|
tx_chain.rf_core->get_tx_frequency_range(tx_chain.block_chan),
|
|
tx_chain.duc->get_frequency_range(tx_chain.block_chan),
|
|
tx_chain.rf_core->get_tx_bandwidth(tx_chain.block_chan))
|
|
: tx_chain.rf_core->get_tx_frequency_range(tx_chain.block_chan);
|
|
freq_range_t tune_range = tune_range_nonmono.as_monotonic();
|
|
|
|
freq_range_t rf_range =
|
|
tx_chain.rf_core->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, 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.rf_core->set_tx_frequency(freq, tx_chain.block_chan);
|
|
};
|
|
auto get_rf_freq = [tx_chain](void) {
|
|
return tx_chain.rf_core->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 = 0) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
// extract actual dsp and IF frequencies
|
|
const double actual_rf_freq =
|
|
tx_chain.rf_core->get_tx_frequency(tx_chain.block_chan);
|
|
const double actual_dsp_freq =
|
|
(tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0;
|
|
|
|
return actual_rf_freq - actual_dsp_freq * TX_SIGN;
|
|
}
|
|
|
|
freq_range_t get_tx_freq_range(size_t chan = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return (tx_chain.duc) ? make_overall_tune_range(get_fe_tx_freq_range(chan),
|
|
tx_chain.duc->get_frequency_range(tx_chain.block_chan),
|
|
tx_chain.rf_core->get_tx_bandwidth(tx_chain.block_chan))
|
|
: get_fe_tx_freq_range(chan);
|
|
}
|
|
|
|
freq_range_t get_fe_tx_freq_range(size_t chan = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_frequency_range(tx_chain.block_chan);
|
|
}
|
|
|
|
void set_tx_gain(double gain, const std::string& name, size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_gain, gain, name);
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
tx_chain.rf_core->set_tx_gain(gain, name, tx_chain.block_chan);
|
|
}
|
|
|
|
// TODO: Figure out if gain profiles fit with RF extensions
|
|
std::vector<std::string> get_tx_gain_profile_names(const size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_gain_profile, profile);
|
|
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 = 0) override
|
|
{
|
|
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) override
|
|
{
|
|
if (gain > 1.0 || gain < 0.0) {
|
|
throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
|
|
}
|
|
MUX_TX_API_CALL(set_normalized_tx_gain, gain);
|
|
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 = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_gain(name, tx_chain.block_chan);
|
|
}
|
|
|
|
double get_normalized_tx_gain(size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_gain_range(name, tx_chain.block_chan);
|
|
}
|
|
|
|
std::vector<std::string> get_tx_gain_names(size_t chan = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_gain_names(tx_chain.block_chan);
|
|
}
|
|
|
|
bool has_tx_power_reference(const size_t chan = 0) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_power_reference->has_tx_power_reference(tx_chain.block_chan);
|
|
}
|
|
|
|
void set_tx_power_reference(const double power_dbm, const size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_power_reference, power_dbm);
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
tx_chain.rf_power_reference->set_tx_power_reference(
|
|
power_dbm, tx_chain.block_chan);
|
|
}
|
|
|
|
double get_tx_power_reference(const size_t chan = 0) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_power_reference->get_tx_power_reference(tx_chain.block_chan);
|
|
}
|
|
|
|
meta_range_t get_tx_power_range(const size_t chan) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_power_reference->get_tx_power_range(tx_chain.block_chan);
|
|
}
|
|
|
|
void set_tx_antenna(const std::string& ant, size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_antenna, ant);
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
tx_chain.rf_core->set_tx_antenna(ant, tx_chain.block_chan);
|
|
}
|
|
|
|
std::string get_tx_antenna(size_t chan = 0) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_antenna(tx_chain.block_chan);
|
|
}
|
|
|
|
std::vector<std::string> get_tx_antennas(size_t chan = 0) override
|
|
{
|
|
auto& tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_antennas(tx_chain.block_chan);
|
|
}
|
|
|
|
void set_tx_bandwidth(double bandwidth, size_t chan = 0) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_bandwidth, bandwidth);
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
tx_chain.rf_core->set_tx_bandwidth(bandwidth, tx_chain.block_chan);
|
|
}
|
|
|
|
double get_tx_bandwidth(size_t chan = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_bandwidth(tx_chain.block_chan);
|
|
}
|
|
|
|
meta_range_t get_tx_bandwidth_range(size_t chan = 0) override
|
|
{
|
|
auto tx_chain = _get_tx_chan(chan);
|
|
return tx_chain.rf_core->get_tx_bandwidth_range(tx_chain.block_chan);
|
|
}
|
|
|
|
dboard_iface::sptr get_tx_dboard_iface(size_t chan = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = ALL_CHANS) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = ALL_CHANS) override
|
|
{
|
|
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]() {
|
|
for (const auto& radio : _radios[mboard]) {
|
|
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) override
|
|
{
|
|
std::vector<std::string> gpio_banks;
|
|
for (const auto& radio : _radios[mboard]) {
|
|
auto radio_banks = radio->get_gpio_banks();
|
|
const std::string suffix =
|
|
_radios[mboard].size() == 1 ? "" : radio->get_slot_name();
|
|
for (const auto& bank : radio_banks) {
|
|
gpio_banks.push_back(bank + suffix);
|
|
}
|
|
}
|
|
|
|
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) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
return _get_mbc(mboard)->get_gpio_banks();
|
|
}
|
|
|
|
std::vector<std::string> get_gpio_srcs(
|
|
const std::string& bank, const size_t mboard = 0) override
|
|
{
|
|
return _get_mbc(mboard)->get_gpio_srcs(bank);
|
|
}
|
|
|
|
std::vector<std::string> get_gpio_src(
|
|
const std::string& bank, const size_t mboard = 0) override
|
|
{
|
|
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 = 0) override
|
|
{
|
|
_get_mbc(mboard)->set_gpio_src(bank, src);
|
|
}
|
|
|
|
/*******************************************************************
|
|
* Filter API methods
|
|
******************************************************************/
|
|
// TODO: Check if/how RF extensions fit into the filter API
|
|
std::vector<std::string> get_rx_filter_names(const size_t chan) override
|
|
{
|
|
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) override
|
|
{
|
|
try {
|
|
// Get the blockid and filtername separated from the name string
|
|
const auto names = string::split(name, ":");
|
|
const auto& blockid = names.first;
|
|
const auto& filter_name = names.second;
|
|
// The block_id_t constructor is pretty smart; let it handle the parsing.
|
|
block_id_t block_id(blockid);
|
|
const auto rx_chan = _get_rx_chan(chan);
|
|
// 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) override
|
|
{
|
|
MUX_RX_API_CALL(set_rx_filter, name, filter);
|
|
try {
|
|
const auto names = string::split(name, ":");
|
|
const auto& blockid = names.first;
|
|
const auto& filter_name = names.second;
|
|
// The block_id_t constructor is pretty smart; let it handle the parsing.
|
|
block_id_t block_id(blockid);
|
|
const auto rx_chan = _get_rx_chan(chan);
|
|
// 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) override
|
|
{
|
|
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) override
|
|
{
|
|
try {
|
|
const auto names = string::split(name, ":");
|
|
const auto& blockid = names.first;
|
|
const auto& filter_name = names.second;
|
|
// The block_id_t constructor is pretty smart; let it handle the parsing.
|
|
block_id_t block_id(blockid);
|
|
const auto tx_chan = _get_tx_chan(chan);
|
|
// 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) override
|
|
{
|
|
MUX_TX_API_CALL(set_tx_filter, name, filter);
|
|
try {
|
|
const auto names = string::split(name, ":");
|
|
const auto& blockid = names.first;
|
|
const auto& filter_name = names.second;
|
|
// The block_id_t constructor is pretty smart; let it handle the parsing.
|
|
block_id_t block_id(blockid);
|
|
const auto tx_chan = _get_tx_chan(chan);
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
mb_controller& get_mb_controller(const size_t mboard) override
|
|
{
|
|
return *_get_mbc(mboard);
|
|
}
|
|
|
|
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 function to query multiple timekeepers
|
|
time_spec_t _get_time_now(size_t mboard = 0, size_t timekeeper = 0)
|
|
{
|
|
// second parameter is called slot in definition of _radio
|
|
// on most devices the timekeeper in all slots/db are the same
|
|
return _radios[mboard][timekeeper]->get_time_now();
|
|
}
|
|
|
|
// private function to query multiple timekeepers
|
|
time_spec_t _get_time_last_pps(size_t mboard = 0, size_t timekeeper = 0)
|
|
{
|
|
return _get_mbc(mboard)->get_timekeeper(timekeeper)->get_time_last_pps();
|
|
}
|
|
|
|
std::vector<graph_edge_t> _connect_rx_chains(std::vector<size_t> chans)
|
|
{
|
|
std::vector<graph_edge_t> edges;
|
|
for (auto chan : chans) {
|
|
UHD_LOG_TRACE("MULTI_USRP",
|
|
std::string("Connecting RX chain for channel ") + std::to_string(chan));
|
|
auto chain = _rx_chans.at(chan);
|
|
for (auto edge : chain.edge_list) {
|
|
if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
|
|
break;
|
|
}
|
|
UHD_LOG_TRACE(
|
|
"MULTI_USRP", std::string("Connecting RX edge: ") + edge.to_string());
|
|
_graph->connect(
|
|
edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
|
|
edges.push_back(edge);
|
|
}
|
|
}
|
|
return edges;
|
|
}
|
|
|
|
std::vector<graph_edge_t> _connect_tx_chain(const size_t chan)
|
|
{
|
|
std::vector<graph_edge_t> edges;
|
|
|
|
UHD_LOG_TRACE("MULTI_USRP",
|
|
std::string("Connecting TX chain for channel ") + std::to_string(chan));
|
|
auto chain = _get_tx_chan(chan);
|
|
for (auto edge : chain.edge_list) {
|
|
edges.push_back(edge);
|
|
if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
|
|
break;
|
|
}
|
|
UHD_LOG_TRACE(
|
|
"MULTI_USRP", std::string("Connecting TX edge: ") + edge.to_string());
|
|
_graph->connect(
|
|
edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
|
|
}
|
|
|
|
return edges;
|
|
}
|
|
|
|
std::vector<graph_edge_t> _connect_tx_chain_with_replay(size_t chan)
|
|
{
|
|
std::vector<graph_edge_t> edges;
|
|
auto tx_chan = _get_tx_chan(chan);
|
|
if (not tx_chan.replay.ctrl) {
|
|
throw uhd::runtime_error(
|
|
"[multi_usrp] No Replay block found to buffer TX stream");
|
|
}
|
|
auto replay_id = tx_chan.replay.ctrl->get_block_id();
|
|
auto replay_port = tx_chan.replay.port;
|
|
|
|
// Connect Replay out to Radio in
|
|
try {
|
|
edges = connect_through_blocks(_graph,
|
|
replay_id,
|
|
replay_port,
|
|
tx_chan.radio->get_block_id(),
|
|
tx_chan.block_chan);
|
|
} catch (uhd::runtime_error& e) {
|
|
throw uhd::runtime_error(
|
|
std::string("[multi_usrp] Unable to connect Replay block to Radio: ")
|
|
+ e.what());
|
|
}
|
|
|
|
// Add Replay input edge (for streamer connection)
|
|
auto replay_edges_in = get_block_chain(_graph, replay_id, replay_port, false);
|
|
if (replay_edges_in.size() > 1) {
|
|
throw uhd::runtime_error(
|
|
"[multi_usrp] Unable to connect TX streamer to Replay block: "
|
|
"Unexpected block found before Replay block");
|
|
}
|
|
edges.push_back(replay_edges_in.at(0));
|
|
|
|
return edges;
|
|
}
|
|
|
|
template <typename ChanType,
|
|
typename GetSubdevSpecFn,
|
|
typename GenChansFn,
|
|
typename CheckEdgeForSepFn>
|
|
void _set_subdev_spec(std::map<size_t, ChanType>& chans,
|
|
GetSubdevSpecFn&& get_subdev_spec,
|
|
GenChansFn&& generate_chans,
|
|
CheckEdgeForSepFn&& check_edge_for_sep,
|
|
const uhd::usrp::subdev_spec_t& spec,
|
|
size_t mboard)
|
|
{
|
|
// First, generate a vector of the channels that we need to register
|
|
auto new_chans = [&]() {
|
|
/* 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<ChanType> new_chans;
|
|
for (size_t current_mboard = 0; current_mboard < get_num_mboards();
|
|
++current_mboard) {
|
|
auto current_spec = [&]() {
|
|
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_subdev_spec(current_mboard);
|
|
}
|
|
}();
|
|
auto new_mboard_chans = generate_chans(current_spec, current_mboard);
|
|
new_chans.insert(
|
|
new_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end());
|
|
}
|
|
return new_chans;
|
|
}();
|
|
|
|
// Disconnect and clear existing chains
|
|
UHD_LOG_TRACE("MULTI_USRP", "Disconnecting chains");
|
|
for (auto entry : chans) {
|
|
for (auto edge : entry.second.edge_list) {
|
|
if (check_edge_for_sep(edge)) {
|
|
break;
|
|
}
|
|
_graph->disconnect(
|
|
edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
|
|
}
|
|
}
|
|
chans.clear();
|
|
|
|
// Register new chains
|
|
size_t musrp_channel = 0;
|
|
for (auto chan : new_chans) {
|
|
chans.emplace(musrp_channel++, 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 device number and the radio blocks
|
|
std::unordered_map<size_t, std::vector<uhd::rfnoc::radio_control::sptr>> _radios;
|
|
//! Mapping between channel number and the RFNoC blocks in that RX chain
|
|
// Using map instead of unordered map to have a guaranteed order when iterating over
|
|
// it, e.g. for getting the master clock rate.
|
|
std::map<size_t, rx_chan_t> _rx_chans;
|
|
//! Mapping between channel number and the RFNoC blocks in that TX chain
|
|
std::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);
|
|
|
|
// The serialization of this function is intended to protect against
|
|
// multiple threads instantiating multi_usrp objects for the same
|
|
// device in parallel.
|
|
static std::mutex _map_mutex;
|
|
static std::map<std::weak_ptr<rfnoc_graph>,
|
|
std::weak_ptr<multi_usrp>,
|
|
std::owner_less<std::weak_ptr<rfnoc_graph>>>
|
|
graph_to_musrp;
|
|
multi_usrp::sptr musrp;
|
|
|
|
// Check if a multi_usrp was already created for this device
|
|
std::lock_guard<std::mutex> lock(_map_mutex);
|
|
if (graph_to_musrp.count(graph) and not graph_to_musrp[graph].expired()) {
|
|
musrp = graph_to_musrp[graph].lock();
|
|
if (musrp) {
|
|
return musrp;
|
|
}
|
|
}
|
|
|
|
// Create a new musrp
|
|
musrp = std::make_shared<multi_usrp_rfnoc>(graph, dev_addr);
|
|
graph_to_musrp[graph] = musrp;
|
|
return musrp;
|
|
}
|
|
|
|
}}} // namespace uhd::rfnoc::detail
|