aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/multi_usrp_rfnoc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/usrp/multi_usrp_rfnoc.cpp')
-rw-r--r--host/lib/usrp/multi_usrp_rfnoc.cpp292
1 files changed, 206 insertions, 86 deletions
diff --git a/host/lib/usrp/multi_usrp_rfnoc.cpp b/host/lib/usrp/multi_usrp_rfnoc.cpp
index c71668c33..00e386b71 100644
--- a/host/lib/usrp/multi_usrp_rfnoc.cpp
+++ b/host/lib/usrp/multi_usrp_rfnoc.cpp
@@ -1,22 +1,26 @@
//
-// Copyright 2019 Ettus Research, a National Instruments Brand
+// 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 <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>
@@ -157,6 +161,8 @@ std::string bytes_to_str(std::vector<uint8_t> str_b)
class multi_usrp_rfnoc : public multi_usrp
{
+ using replay_config_t = rfnoc_tx_streamer_replay_buffered::replay_config_t;
+
public:
struct rx_chan_t
{
@@ -172,6 +178,7 @@ public:
duc_block_control::sptr duc; // can be nullptr
size_t block_chan;
std::vector<graph_edge_t> edge_list;
+ replay_config_t replay;
};
/**************************************************************************
@@ -184,60 +191,62 @@ public:
, _device(std::make_shared<redirector_device>(this))
{
// Discover all of the radios on our devices and create a mapping between
- // radio chains and channel numbers. The result is sorted.
- auto radio_blk_ids = _graph->find_blocks("Radio");
- // If we don't find any radios, we don't have a multi_usrp object
- if (radio_blk_ids.empty()) {
- throw uhd::runtime_error(
- "[multi_usrp] No radios found in connected devices.");
- }
- // Next, we assign block controllers to RX channels
- // Note that we don't want to connect blocks now; we will wait until we create and
- // connect a streamer. This gives us a little more time to figure out the desired
- // values of our properties (such as master clock)
+ // 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 (auto radio_id : radio_blk_ids) {
- auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
-
- // Store radio blocks per mboard for quick retrieval
- _radios[radio_id.get_device_no()].push_back(radio_blk);
-
- for (size_t block_chan = 0; block_chan < radio_blk->get_num_output_ports();
- ++block_chan) {
- // Create the RX chan
- uhd::usrp::subdev_spec_t rx_radio_subdev;
- rx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
- radio_blk->get_slot_name(),
- radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION)));
- auto rx_chans =
- _generate_mboard_rx_chans(rx_radio_subdev, radio_id.get_device_no());
- // TODO: we're passing the same info around here; there has to be a
- // cleaner way
- for (auto rx_chan : rx_chans) {
- _rx_chans.emplace(musrp_rx_channel, rx_chan);
- ++musrp_rx_channel; // Increment after logging so we print the correct
- // value
+ for (size_t 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);
+
+ // 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) {
+ 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) {
- // Create the TX chan
- uhd::usrp::subdev_spec_t tx_radio_subdev;
- tx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
- radio_blk->get_slot_name(),
- radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION)));
- auto tx_chans =
- _generate_mboard_tx_chans(tx_radio_subdev, radio_id.get_device_no());
- // TODO: we're passing the same info around here; there has to be a
- // cleaner way
- for (auto tx_chan : tx_chans) {
- _tx_chans.emplace(musrp_tx_channel, tx_chan);
- ++musrp_tx_channel; // Increment after logging so we print the correct
- // value
+
+ for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports();
+ ++block_chan) {
+ 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) {
@@ -254,6 +263,7 @@ public:
tx_chain.radio->get_rate(), tx_chain.block_chan);
}
}
+
_graph->commit();
}
@@ -362,6 +372,8 @@ public:
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
@@ -369,41 +381,63 @@ public:
// property propagation where possible.
// Connect the chains
- auto edges = _connect_tx_chains(args.channels);
- std::weak_ptr<rfnoc_graph> graph_ref(_graph);
+ 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.
- auto tx_streamer = std::make_shared<rfnoc_tx_streamer>(
- args.channels.size(), args, [=](const std::string& id) {
+ 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 : edges) {
- graph->disconnect(edge.src_blockid,
- edge.src_port,
- edge.dst_blockid,
- edge.dst_port);
+ 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 {
+ 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 tx_chain = _get_tx_chan(tx_channel);
- if (tx_chain.edge_list.empty()) {
+ 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 << " -> "
- << tx_chain.edge_list.back().dst_blockid << ":"
- << tx_chain.edge_list.back().dst_port);
+ << edge_list.back().dst_blockid << ":"
+ << edge_list.back().dst_port);
_graph->connect(tx_streamer,
strm_port,
- tx_chain.edge_list.back().dst_blockid,
- tx_chain.edge_list.back().dst_port);
+ 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) {
@@ -1585,15 +1619,15 @@ public:
"Disabling DUC control.";
break;
}
- auto ddc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(
+ 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>(
- ddc_blk, block_chan);
+ duc_blk, block_chan);
}
}
} catch (const uhd::exception&) {
UHD_LOGGER_DEBUG("MULTI_USRP")
- << "No DDC found for radio block " << radio_id << ":"
+ << "No DUC found for radio block " << radio_id << ":"
<< std::to_string(block_chan);
// Then just return a nullptr
}
@@ -1602,13 +1636,14 @@ public:
// Create the TX chan
return tx_chan_t(
- {radio_blk, std::get<0>(duc_port_def), block_chan, radio_sink_chain});
+ {radio_blk, 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 our devices and create a mapping between radio
+ // 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
@@ -1618,6 +1653,19 @@ public:
+ 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) {
@@ -1636,8 +1684,43 @@ public:
}
subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(),
radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION));
+
if (chan_subdev_pair == radio_subdev) {
- new_chans.push_back(_generate_tx_radio_chan(radio_id, block_chan));
+ 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;
}
}
@@ -1673,7 +1756,7 @@ public:
{
uhd::usrp::subdev_spec_t result;
for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) {
- auto& tx_chain = _tx_chans.at(tx_chan);
+ 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(),
@@ -2403,24 +2486,61 @@ private:
return edges;
}
- std::vector<graph_edge_t> _connect_tx_chains(std::vector<size_t> chans)
+ std::vector<graph_edge_t> _connect_tx_chain(const size_t chan)
{
std::vector<graph_edge_t> edges;
- for (auto chan : chans) {
- UHD_LOG_TRACE(
- "MULTI_USRP", std::string("Connecting TX chain for channel ") + std::to_string(chan));
- auto chain = _tx_chans.at(chan);
- for (auto edge : chain.edge_list) {
- 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);
- edges.push_back(edge);
+
+ 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;
}