diff options
Diffstat (limited to 'host')
-rw-r--r-- | host/docs/configuration.dox | 4 | ||||
-rw-r--r-- | host/docs/general.dox | 12 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp | 78 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/rfnoc_tx_streamer_replay_buffered.cpp | 181 | ||||
-rw-r--r-- | host/lib/usrp/multi_usrp_rfnoc.cpp | 292 |
6 files changed, 482 insertions, 86 deletions
diff --git a/host/docs/configuration.dox b/host/docs/configuration.dox index d9a121505..b65714a29 100644 --- a/host/docs/configuration.dox +++ b/host/docs/configuration.dox @@ -212,6 +212,10 @@ The following parameters are supported: - `noclear` (applies to B100 and N2xx only) - `port` and `addr` (N2xx only) These settings specify an alternate receiver streamer destination. +- `streamer` Specify the type of streamer to use. "replay_buffered" (applies + to RFNoC enabled devices with a Replay block in the FPGA image) Adds data + buffering in DRAM using the Replay block for TX streamers when using the + multi_usrp API. \subsubsection config_stream_args_transport Transport-related Stream Arguments diff --git a/host/docs/general.dox b/host/docs/general.dox index 2dab0a9ea..4e1d0cfb9 100644 --- a/host/docs/general.dox +++ b/host/docs/general.dox @@ -189,6 +189,18 @@ Underflow occurs when the host does not produce data fast enough. When UHD software detects the underflow, it prints a "U" to stdout, and pushes a message packet into the async message stream. +Some underruns may be mitigated by buffering data in DRAM using the Replay +block. The Replay block is only available in devices that support RFNoC. Some +RFNoC devices include the Replay block in the default FPGA image. Use the +'uhd_usrp_probe' utility and look for the "RFNoC blocks on this device:" +section of the output to check if the Replay block is present. If using +the multi_usrp API, simply add the stream argument "streamer=replay_buffered" +to enable the buffering. This buffering adds latency and will likely not work +at the highest streaming rates. A limited number of buffers can be stored +in the Replay block, so larger buffers supplied to the tx_streamer::send() +call will produce the best results. Buffers that are too small will result +in gaps in the transmitted signal. + <b>Note:</b> "O" and "U" message are generally harmless, and just mean the host machine can't keep up with the requested rates. diff --git a/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp b/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp new file mode 100644 index 000000000..23386d8c3 --- /dev/null +++ b/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp @@ -0,0 +1,78 @@ +// +// Copyright 2022 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/rfnoc_graph.hpp> +#include <uhd/rfnoc/replay_block_control.hpp> +#include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp> + +namespace uhd { namespace rfnoc { + +/*! + * Extends the rfnoc_tx_streamer so it can use a Replay block to + * buffer TX data. + */ +class rfnoc_tx_streamer_replay_buffered : public rfnoc_tx_streamer +{ +public: + struct replay_config_t + { + replay_block_control::sptr ctrl = nullptr; // Replay block control + size_t port = 0; // Replay port to use + uint64_t start_address = 0; // Start address in memory + uint64_t mem_size = 0; // Size of memory block to use + }; + + struct replay_status_t { + const replay_config_t config; + uint64_t record_offset = 0; + uint64_t play_offset = 0; + uint64_t play_end = 0; + }; + + /*! Constructor + * + * \param num_ports The number of ports + * \param stream_args Arguments to aid the construction of the streamer + * \param disconnect_cb Callback function to disconnect the streamer when + * the object is destroyed + * \param replay_chans Vector of Replay configurations to use (one per channel) + */ + rfnoc_tx_streamer_replay_buffered( + const size_t num_ports, + const uhd::stream_args_t stream_args, + std::function<void(const std::string&)> disconnect_cb, + std::vector<replay_config_t> replay_configs); + + /*! Destructor + */ + ~rfnoc_tx_streamer_replay_buffered(); + + /*! Send + * + * Sends data by recording to the Replay block and playing it. + * + * \param buffs a vector of read-only memory containing samples + * \param nsamps_per_buff the number of samples to send, per buffer + * \param metadata data describing the buffer's contents + * \param timeout the timeout in seconds to wait on a packet + * \return the number of samples sent + */ + size_t send(const buffs_type& buffs, + const size_t nsamps_per_buff, + const tx_metadata_t& metadata, + const double timeout = 0.1) override; + +private: + // Size of item + size_t _bytes_per_otw_item; + + // Status of Replay channels + std::vector<replay_status_t> _replay_chans; +}; + +}} // namespace uhd::rfnoc diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index 53bca9b45..50eb9dd97 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -37,6 +37,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/mgmt_portal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_rx_streamer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_tx_streamer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_tx_streamer_replay_buffered.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_async_msg_queue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mock_block.cpp # Default block control classes: diff --git a/host/lib/rfnoc/rfnoc_tx_streamer_replay_buffered.cpp b/host/lib/rfnoc/rfnoc_tx_streamer_replay_buffered.cpp new file mode 100644 index 000000000..847d53234 --- /dev/null +++ b/host/lib/rfnoc/rfnoc_tx_streamer_replay_buffered.cpp @@ -0,0 +1,181 @@ +// +// Copyright 2022 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhdlib/rfnoc/rfnoc_tx_streamer_replay_buffered.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/utils/safe_call.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +rfnoc_tx_streamer_replay_buffered::rfnoc_tx_streamer_replay_buffered( + const size_t num_ports, + const uhd::stream_args_t stream_args, + std::function<void(const std::string&)> disconnect_cb, + std::vector<replay_config_t> replay_configs) : + rfnoc_tx_streamer(num_ports, stream_args, disconnect_cb), + _bytes_per_otw_item(uhd::convert::get_bytes_per_item(stream_args.otw_format)) +{ + if(replay_configs.size() != num_ports) { + throw uhd::value_error("[TX Streamer] Number of Replay configurations " + "does not match the number of channels"); + } + + for (auto config : replay_configs) + { + _replay_chans.push_back({config, 0, 0, 0}); + config.ctrl->set_play_type(stream_args.otw_format); + } +} + +rfnoc_tx_streamer_replay_buffered::~rfnoc_tx_streamer_replay_buffered() +{ + // Stop all playback + for (auto chan : _replay_chans) + { + UHD_SAFE_CALL(chan.config.ctrl->stop(chan.config.port);) + } +} + +size_t rfnoc_tx_streamer_replay_buffered::send( + const buffs_type& buffs, + const size_t nsamps_per_buff, + const tx_metadata_t& metadata, + const double timeout) +{ + uint64_t record_size = nsamps_per_buff * _bytes_per_otw_item; + + // Make sure there is space in the buffer + auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::microseconds(long(timeout * 1000000)); + for (auto& chan : _replay_chans) { + const auto& config = chan.config; + const auto& replay = config.ctrl; + auto& play_offset = chan.play_offset; + const auto& play_end = chan.play_end; + + // Make sure the send does not exceed the memory space + if (record_size > config.mem_size) { + throw uhd::runtime_error("[multi_usrp] Unable to buffer more than " + + std::to_string(config.mem_size) + " bytes"); + } + + // Make sure nsamps_per_buff is properly aligned to the DRAM + if (record_size % replay->get_word_size() != 0) { + throw uhd::runtime_error( + "[multi_usrp] Number of samples for send() call must be a " + "multiple of " + std::to_string(uhd::math::lcm<uint64_t>( + replay->get_word_size(), uint64_t(_bytes_per_otw_item))) + + " for DRAM alignment"); + } + + // The buffer can be full or empty when play_offset and play_end + // are the same, so subtract one from the calculated room to make + // sure the buffer is never absolutely full and it can be assumed + // the buffer is empty when they are the same. + auto room = (play_end == play_offset ? config.mem_size - 1 : + play_end < play_offset ? play_offset - play_end - 1 : + std::max<uint64_t>(config.mem_size - play_end - 1, + play_offset - 1)); + while (room < record_size) { + // Return 0 if timeout + if (std::chrono::steady_clock::now() > timeout_time) { + UHD_LOG_TRACE("MULTI_USRP", + "send() timed out waiting for room in buffer"); + return 0; + } + try { + play_offset = replay->get_play_position(config.port) - + config.start_address; + } catch (uhd::op_timeout&) { + // Internal timeout trying to read the register + continue; + } + room = (play_end == play_offset ? config.mem_size - 1 : + play_end < play_offset ? play_offset - play_end - 1 : + std::max<uint64_t>(config.mem_size - play_end - 1, + play_offset - 1)); + } + } + + // Set up replay blocks to record + for (auto& chan : _replay_chans) { + const auto& config = chan.config; + const auto& replay = config.ctrl; + const auto& play_end = chan.play_end; + auto& record_offset = chan.record_offset; + + // Default to using space at end of last playback + record_offset = play_end; + // Change to beginning of memory block if not enough room at end + if (config.mem_size - record_offset < record_size) { + record_offset = 0; + } + while (1) { + try { + replay->record(config.start_address + record_offset, record_size, + config.port); + break; + } catch(uhd::op_timeout& e) { + // Internal timeout trying to write the registers + // Return 0 if timeout + if (std::chrono::steady_clock::now() > timeout_time) { + UHD_LOG_TRACE("MULTI_USRP", + std::string("send() timed out while setting up to record: ") + + e.what()); + return 0; + } + } + } + } + + // Send data to replay blocks + auto num_samps = rfnoc_tx_streamer::send( + buffs, nsamps_per_buff, metadata, timeout); + + if (num_samps) { + // Play data + for (auto& chan : _replay_chans) { + const auto& config = chan.config; + const auto& replay = config.ctrl; + const auto& record_offset = chan.record_offset; + const uint64_t play_start = config.start_address + record_offset; + const uint64_t play_size = num_samps * _bytes_per_otw_item; + auto& play_end = chan.play_end; + + while (1) { + try { + replay->play(play_start, play_size, config.port, + metadata.has_time_spec ? metadata.time_spec : + uhd::time_spec_t(0.0)); + break; + } catch(uhd::op_failed& e) { + // Too many play commands in queue + // Return 0 if timeout. + if (std::chrono::steady_clock::now() > timeout_time) { + UHD_LOG_TRACE("MULTI_USRP", + std::string("send() timed out issuing play command: ") + + e.what()); + return 0; + } + } catch (uhd::op_timeout& e) { + // Internal timeout trying to write the registers + // Return 0 if timeout + if (std::chrono::steady_clock::now() > timeout_time) { + UHD_LOG_TRACE("MULTI_USRP", + std::string("send() timed out issuing play command: ") + + e.what()); + return 0; + } + } + } + + play_end = record_offset + play_size; + } + } + return num_samps; +}
\ No newline at end of file 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; } |