aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWade Fife <wade.fife@ettus.com>2018-07-16 07:39:30 -0500
committerBrent Stapleton <brent.stapleton@ettus.com>2018-08-29 15:45:55 -0700
commitbed75f0ccbb6da139283a07c1442293bef95f26a (patch)
tree54cda1bbb73bd291d264bf995d0b0bdf8de737d7
parent639797e5924b42764711b69b09e48ee07ed3d683 (diff)
downloaduhd-bed75f0ccbb6da139283a07c1442293bef95f26a.tar.gz
uhd-bed75f0ccbb6da139283a07c1442293bef95f26a.tar.bz2
uhd-bed75f0ccbb6da139283a07c1442293bef95f26a.zip
RFNoC: Add Replay API and example
-rw-r--r--host/examples/replay_samples_from_file.cpp367
-rw-r--r--host/include/uhd/rfnoc/CMakeLists.txt1
-rw-r--r--host/include/uhd/rfnoc/blocks/replay.xml64
-rw-r--r--host/include/uhd/rfnoc/blocks/replay_x2.xml72
-rw-r--r--host/include/uhd/rfnoc/blocks/replay_x4.xml87
-rw-r--r--host/include/uhd/rfnoc/replay_block_ctrl.hpp67
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/replay_block_ctrl_impl.cpp183
8 files changed, 842 insertions, 0 deletions
diff --git a/host/examples/replay_samples_from_file.cpp b/host/examples/replay_samples_from_file.cpp
new file mode 100644
index 000000000..70c0272c5
--- /dev/null
+++ b/host/examples/replay_samples_from_file.cpp
@@ -0,0 +1,367 @@
+//
+// Copyright 2018 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+//
+// Description:
+//
+// This example demonstrates using the Replay block to replay data from a file.
+// It streams the file data to the Replay block, where it is recorded, then it
+// is played back to the radio.
+
+#include <uhd/utils/safe_main.hpp>
+#include <uhd/device3.hpp>
+#include <uhd/rfnoc/radio_ctrl.hpp>
+#include <uhd/rfnoc/replay_block_ctrl.hpp>
+#include <boost/program_options.hpp>
+#include <boost/format.hpp>
+#include <fstream>
+#include <csignal>
+#include <thread>
+
+
+
+namespace po = boost::program_options;
+
+using std::cout;
+using std::endl;
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+static volatile bool stop_signal_called = false;
+
+// Ctrl+C handler
+void sig_int_handler(int)
+{
+ stop_signal_called = true;
+}
+
+
+
+int UHD_SAFE_MAIN(int argc, char *argv[])
+{
+ // We use sc16 in this example, but the replay block only uses 64-bit words
+ // and is not aware of the CPU or wire format.
+ std::string wire_format("sc16");
+ std::string cpu_format("sc16");
+
+ // Constants related to the Replay block
+ const size_t replay_word_size = 8; // Size of words used by replay block
+ const size_t bytes_per_sample = 4; // Complex signed 16-bit is 32 bits per sample
+ const size_t samples_per_word = 2; // Number of sc16 samples per word
+ const size_t replay_spp = 2000; // SC16 Samples per packet generated by Replay block
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Handle command line options
+
+ std::string args, radio_args, file, ant, ref;
+ double rate, freq, gain, bw;
+ size_t radio_id, radio_chan, replay_id, replay_chan, nsamps;
+
+ po::options_description desc("Allowed Options");
+ desc.add_options()
+ ("help", "help message")
+ ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args")
+ ("radio-id", po::value<size_t>(&radio_id)->default_value(0), "radio block to use (e.g., 0 or 1).")
+ ("radio-chan", po::value<size_t>(&radio_chan)->default_value(0), "radio channel to use")
+ ("radio-args", po::value<std::string>(&radio_args), "radio arguments")
+ ("replay-id", po::value<size_t>(&replay_id)->default_value(0), "replay block to use (e.g., 0 or 1)")
+ ("replay_chan", po::value<size_t>(&replay_chan)->default_value(0), "replay channel to use")
+ ("nsamps", po::value<uint64_t>(&nsamps)->default_value(0), "number of samples to play (0 for infinite)")
+ ("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "name of the file to read binary samples from")
+ ("freq", po::value<double>(&freq), "RF center frequency in Hz")
+ ("rate", po::value<double>(&rate), "rate of radio block")
+ ("gain", po::value<double>(&gain), "gain for the RF chain")
+ ("ant", po::value<std::string>(&ant), "antenna selection")
+ ("bw", po::value<double>(&bw), "analog front-end filter bandwidth in Hz")
+ ("ref", po::value<std::string>(&ref)->default_value("internal"), "reference source (internal, external, mimo)")
+ ;
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ // Print help message
+ if (vm.count("help")) {
+ cout << boost::format("UHD/RFNoC Replay samples from file %s") % desc << endl;
+ cout
+ << "This application uses the Replay block to playback data from a file to a radio" << endl
+ << endl;
+ return EXIT_FAILURE;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Create USRP device and block controls
+
+ cout << "Creating the USRP device with: " << args << ". . .\n" << endl;
+ uhd::device3::sptr usrp = uhd::device3::make(args);
+
+ // Create handle for radio object
+ uhd::rfnoc::block_id_t radio_ctrl_id(0, "Radio", radio_id);
+ uhd::rfnoc::radio_ctrl::sptr radio_ctrl;
+ radio_ctrl = usrp->get_block_ctrl<uhd::rfnoc::radio_ctrl>(radio_ctrl_id);
+ std::cout << "Using radio " << radio_id << ", channel " << radio_chan << std::endl;
+
+
+ // Check if the replay block exists on this device
+ uhd::rfnoc::block_id_t replay_ctrl_id(0, "Replay", replay_id);
+ uhd::rfnoc::replay_block_ctrl::sptr replay_ctrl;
+ if (!usrp->has_block(replay_ctrl_id)) {
+ cout << "Unable to find block \"" << replay_ctrl_id << "\"" << endl;
+ return EXIT_FAILURE;
+ }
+ replay_ctrl = usrp->get_block_ctrl<uhd::rfnoc::replay_block_ctrl>(replay_ctrl_id);
+ std::cout << "Using replay block " << replay_id << ", channel " << replay_chan << std::endl;
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Configure radio
+
+ // Lock clocks
+ radio_ctrl->set_clock_source(ref);
+
+ // Apply any radio arguments provided
+ radio_ctrl->set_args(radio_args);
+
+ // Set the center frequency
+ if (not vm.count("freq")){
+ std::cerr << "Please specify the center frequency with --freq" << std::endl;
+ return EXIT_FAILURE;
+ }
+ std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl;
+ radio_ctrl->set_tx_frequency(freq, radio_chan);
+ std::cout << boost::format("Actual TX Freq: %f MHz...") % (radio_ctrl->get_tx_frequency(radio_chan)/1e6) << std::endl << std::endl;
+
+ // Set the sample rate
+ if (vm.count("rate")) {
+ std::cout << boost::format("Setting TX Rate: %f Msps...") % (rate/1e6) << std::endl;
+ radio_ctrl->set_rate(rate);
+ std::cout << boost::format("Actual TX Rate: %f Msps...") % (radio_ctrl->get_rate()/1e6) << std::endl << std::endl;
+ }
+
+ // Set the RF gain
+ if (vm.count("gain")){
+ std::cout << boost::format("Setting TX Gain: %f dB...") % gain << std::endl;
+ radio_ctrl->set_tx_gain(gain, radio_chan);
+ std::cout << boost::format("Actual TX Gain: %f dB...") % radio_ctrl->get_tx_gain(radio_chan) << std::endl << std::endl;
+ }
+
+ // Set the analog front-end filter bandwidth
+ if (vm.count("bw")){
+ std::cout << boost::format("Setting TX Bandwidth: %f MHz...")
+ % (bw / 1e6)
+ << std::endl;
+ radio_ctrl->set_tx_bandwidth(bw, radio_chan);
+ std::cout << boost::format("Actual TX Bandwidth: %f MHz...")
+ % (radio_ctrl->get_tx_bandwidth(radio_chan) / 1e6)
+ << std::endl << std::endl;
+ }
+
+ // Set the antenna
+ if (vm.count("ant")) {
+ radio_ctrl->set_tx_antenna(ant, radio_chan);
+ }
+
+ // Allow for some setup time
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Connect Replay block to radio
+
+ uhd::rfnoc::graph::sptr replay_graph = usrp->create_graph("rfnoc_replay");
+ usrp->clear();
+ std::cout << "Connecting " << replay_ctrl->get_block_id() << " ==> " << radio_ctrl->get_block_id() << std::endl;
+ replay_graph->connect(replay_ctrl->get_block_id(), replay_chan, radio_ctrl->get_block_id(), radio_chan, replay_spp);
+
+ // Inform replay block that it has an RX streamer connected to it
+ replay_ctrl->set_rx_streamer(true, replay_chan);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup streamer to Replay block
+
+ uint64_t noc_id;
+ uhd::device_addr_t streamer_args;
+ uhd::stream_args_t stream_args(cpu_format, wire_format);
+ uhd::tx_streamer::sptr tx_stream;
+ uhd::tx_metadata_t tx_md;
+
+ streamer_args["block_id"] = replay_ctrl->get_block_id().to_string();
+ streamer_args["block_port"] = str(boost::format("%d") % replay_chan);
+ stream_args.args = streamer_args;
+ tx_stream = usrp->get_tx_stream(stream_args);
+
+ // Make sure that streamer SPP is a multiple of the Replay block word size
+ size_t tx_spp = tx_stream->get_max_num_samps();
+ if(tx_spp % samples_per_word != 0) {
+ // Round SPP down to a multiple of the word size
+ tx_spp = (tx_spp / samples_per_word) * samples_per_word;
+ tx_stream.reset();
+ streamer_args["spp"] = boost::lexical_cast<std::string>(tx_spp);
+ stream_args.args = streamer_args;
+ tx_stream = usrp->get_tx_stream(stream_args);
+ }
+
+ cout << "Using streamer args: " << stream_args.args.to_string() << endl;
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Read the data to replay
+
+ // Open the file
+ std::ifstream infile(file.c_str(), std::ifstream::binary);
+ if (!infile.is_open()) {
+ std::cerr << "Could not open specified file" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ // Get the file size
+ infile.seekg(0, std::ios::end);
+ size_t file_size = infile.tellg();
+ infile.seekg(0, std::ios::beg);
+
+ // Calculate the number of 64-bit words and samples to replay
+ size_t words_to_replay = file_size / replay_word_size;
+ size_t samples_to_replay = words_to_replay * replay_word_size / bytes_per_sample;
+
+ // Create buffer
+ std::vector<char> tx_buffer(words_to_replay * replay_word_size);
+ char* tx_buf_ptr = &tx_buffer[0];
+
+ // Read file into buffer, rounded down to number of words
+ infile.read(tx_buf_ptr, words_to_replay * replay_word_size);
+ infile.close();
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Configure replay block
+
+ // Configure a buffer in the on-board memory at address 0 that's equal in
+ // size to the file we want to play back (rounded down to a multiple of
+ // 64-bit words). Note that it is allowed to playback a different size or
+ // location from what was recorded.
+ replay_ctrl->config_record(0, words_to_replay * replay_word_size, replay_chan);
+ replay_ctrl->config_play(0, words_to_replay * replay_word_size, replay_chan);
+
+ // Set samples per packet for Replay block playback
+ replay_ctrl->set_words_per_packet(replay_spp / samples_per_word, replay_chan);
+
+ // Display replay configuration
+ cout << boost::format("Replay file size: %d bytes (%d qwords, %d samples)")
+ % (words_to_replay * replay_word_size) % words_to_replay % samples_to_replay
+ << endl;
+
+ cout << boost::format("Record base address: 0x%X") % replay_ctrl->get_record_addr(replay_chan) << endl;
+ cout << boost::format("Record buffer size: %d bytes") % replay_ctrl->get_record_size(replay_chan) << endl;
+ cout << boost::format("Record fullness: %d") % replay_ctrl->get_record_fullness(replay_chan) << endl;
+ cout << boost::format("Play base address: 0x%X") % replay_ctrl->get_play_addr(replay_chan) << endl;
+ cout << boost::format("Play buffer size: %d bytes") % replay_ctrl->get_play_size(replay_chan) << endl;
+
+ // Restart record buffer repeatedly until no new data appears on the Replay
+ // block's input. This will flush any data that was buffered on the input.
+ uint32_t fullness;
+ cout << boost::format("Restarting record buffer...") << endl;
+ do {
+ std::chrono::system_clock::time_point start_time;
+ std::chrono::system_clock::duration time_diff;
+
+ replay_ctrl->record_restart(replay_chan);
+
+ // Make sure the record buffer doesn't start to fill again
+ start_time = std::chrono::system_clock::now();
+ do {
+ fullness = replay_ctrl->get_record_fullness(replay_chan);
+ if (fullness != 0) break;
+ time_diff = std::chrono::system_clock::now() - start_time;
+ time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(time_diff);
+ } while (time_diff.count() < 250);
+ } while (fullness);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Send data to replay (record the data)
+
+ cout << "Sending data to be recorded..." << endl;
+ tx_md.start_of_burst = true;
+ tx_md.end_of_burst = true;
+ size_t num_tx_samps = tx_stream->send(tx_buf_ptr, samples_to_replay, tx_md);
+
+ if (num_tx_samps != samples_to_replay) {
+ cout << boost::format("ERROR: Unable to send %d samples") % samples_to_replay << endl;
+ return EXIT_FAILURE;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Wait for data to be stored in on-board memory
+
+ cout << "Waiting for recording to complete..." << endl;
+ while (replay_ctrl->get_record_fullness(replay_chan) < words_to_replay*replay_word_size);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Start replay of data
+
+ uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
+
+ if (nsamps <= 0) {
+ // Replay the entire buffer over and over
+ stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
+ stream_cmd.num_samps = words_to_replay;
+ cout << boost::format("Issuing replay command for %d words in continuous mode...") % stream_cmd.num_samps << endl;
+ }
+ else {
+ // Replay nsamps, wrapping back to the start of the buffer if nsamps is
+ // larger than the buffer size.
+ stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE;
+ stream_cmd.num_samps = nsamps / samples_per_word;
+ cout << boost::format("Issuing replay command for %d words...") % stream_cmd.num_samps << endl;
+ }
+ stream_cmd.stream_now = true;
+ replay_ctrl->issue_stream_cmd(stream_cmd, replay_chan);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Wait until user says to stop
+
+ // Setup SIGINT handler (Ctrl+C)
+ std::signal(SIGINT, &sig_int_handler);
+ cout << "Replaying data (Press Ctrl+C to stop)..." << endl;
+
+ while (not stop_signal_called);
+
+ // Remove SIGINT handler
+ std::signal(SIGINT, SIG_DFL);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Issue stop command
+
+ stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
+ cout << endl << "Stopping replay..." << endl;
+ replay_ctrl->issue_stream_cmd(stream_cmd);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Wait for the replay data to flush out
+
+ uint16_t prev_packet_count, packet_count;
+
+ cout << "Waiting for replay data to flush... ";
+ prev_packet_count = replay_ctrl->sr_read64(uhd::rfnoc::SR_READBACK_REG_GLOBAL_PARAMS, 0) >> 32;
+ while(true) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ packet_count = replay_ctrl->sr_read64(uhd::rfnoc::SR_READBACK_REG_GLOBAL_PARAMS, 0) >> 32;
+ if (packet_count == prev_packet_count) break;
+ prev_packet_count = packet_count;
+ }
+
+ cout << endl;
+
+ return EXIT_SUCCESS;
+} \ No newline at end of file
diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt
index 44d0490f4..137ec3024 100644
--- a/host/include/uhd/rfnoc/CMakeLists.txt
+++ b/host/include/uhd/rfnoc/CMakeLists.txt
@@ -31,6 +31,7 @@ IF(ENABLE_RFNOC)
fir_block_ctrl.hpp
null_block_ctrl.hpp
radio_ctrl.hpp
+ replay_block_ctrl.hpp
siggen_block_ctrl.hpp
window_block_ctrl.hpp
DESTINATION ${INCLUDE_DIR}/uhd/rfnoc
diff --git a/host/include/uhd/rfnoc/blocks/replay.xml b/host/include/uhd/rfnoc/blocks/replay.xml
new file mode 100644
index 000000000..3ffc5c01a
--- /dev/null
+++ b/host/include/uhd/rfnoc/blocks/replay.xml
@@ -0,0 +1,64 @@
+<!--This defines one NoC-Block.-->
+<nocblock>
+ <name>Replay</name>
+ <blockname>Replay</blockname>
+ <key>Replay</key>
+ <!--There can be several of these:-->
+ <ids>
+ <id revision="0">4E91A00000000000</id>
+ </ids>
+ <!-- Registers -->
+ <registers>
+ <setreg>
+ <name>REC_BASE_ADDR</name>
+ <address>128</address>
+ </setreg>
+ <setreg>
+ <name>REC_BUFFER_SIZE</name>
+ <address>129</address>
+ </setreg>
+ <setreg>
+ <name>REC_RESTART</name>
+ <address>130</address>
+ </setreg>
+ <readback>
+ <name>REC_FULLNESS</name>
+ <address>131</address>
+ </readback>
+ <setreg>
+ <name>PLAY_BASE_ADDR</name>
+ <address>132</address>
+ </setreg>
+ <setreg>
+ <name>PLAY_BUFFER_SIZE</name>
+ <address>133</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_COMMAND</name>
+ <address>152</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_HALT</name>
+ <address>155</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_MAXLEN</name>
+ <address>156</address>
+ </setreg>
+ </registers>
+ <!-- Args -->
+ <args>
+ </args>
+ <!--All the connections to the outside world are listed in 'ports':-->
+ <ports>
+ <sink>
+ <name>in0</name>
+ <type>sc16</type>
+ </sink>
+ <source>
+ <name>out0</name>
+ <type>sc16</type>
+ </source>
+ </ports>
+</nocblock>
+
diff --git a/host/include/uhd/rfnoc/blocks/replay_x2.xml b/host/include/uhd/rfnoc/blocks/replay_x2.xml
new file mode 100644
index 000000000..a267a85de
--- /dev/null
+++ b/host/include/uhd/rfnoc/blocks/replay_x2.xml
@@ -0,0 +1,72 @@
+<!--This defines one NoC-Block.-->
+<nocblock>
+ <name>Replay</name>
+ <blockname>Replay</blockname>
+ <key>Replay</key>
+ <!--There can be several of these:-->
+ <ids>
+ <id revision="0">4E91A00000000002</id>
+ </ids>
+ <!-- Registers -->
+ <registers>
+ <setreg>
+ <name>REC_BASE_ADDR</name>
+ <address>128</address>
+ </setreg>
+ <setreg>
+ <name>REC_BUFFER_SIZE</name>
+ <address>129</address>
+ </setreg>
+ <setreg>
+ <name>REC_RESTART</name>
+ <address>130</address>
+ </setreg>
+ <readback>
+ <name>REC_FULLNESS</name>
+ <address>131</address>
+ </readback>
+ <setreg>
+ <name>PLAY_BASE_ADDR</name>
+ <address>132</address>
+ </setreg>
+ <setreg>
+ <name>PLAY_BUFFER_SIZE</name>
+ <address>133</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_COMMAND</name>
+ <address>152</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_HALT</name>
+ <address>155</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_MAXLEN</name>
+ <address>156</address>
+ </setreg>
+ </registers>
+ <!-- Args -->
+ <args>
+ </args>
+ <!--All the connections to the outside world are listed in 'ports':-->
+ <ports>
+ <sink>
+ <name>in0</name>
+ <type>sc16</type>
+ </sink>
+ <sink>
+ <name>in1</name>
+ <type>sc16</type>
+ </sink>
+ <source>
+ <name>out0</name>
+ <type>sc16</type>
+ </source>
+ <source>
+ <name>out1</name>
+ <type>sc16</type>
+ </source>
+ </ports>
+</nocblock>
+
diff --git a/host/include/uhd/rfnoc/blocks/replay_x4.xml b/host/include/uhd/rfnoc/blocks/replay_x4.xml
new file mode 100644
index 000000000..e12b5f0df
--- /dev/null
+++ b/host/include/uhd/rfnoc/blocks/replay_x4.xml
@@ -0,0 +1,87 @@
+<!--This defines one NoC-Block.-->
+<nocblock>
+ <name>Replay</name>
+ <blockname>Replay</blockname>
+ <key>Replay</key>
+ <!--There can be several of these:-->
+ <ids>
+ <id revision="0">4E91A00000000004</id>
+ </ids>
+ <!-- Registers -->
+ <registers>
+ <setreg>
+ <name>REC_BASE_ADDR</name>
+ <address>128</address>
+ </setreg>
+ <setreg>
+ <name>REC_BUFFER_SIZE</name>
+ <address>129</address>
+ </setreg>
+ <setreg>
+ <name>REC_RESTART</name>
+ <address>130</address>
+ </setreg>
+ <readback>
+ <name>REC_FULLNESS</name>
+ <address>131</address>
+ </readback>
+ <setreg>
+ <name>PLAY_BASE_ADDR</name>
+ <address>132</address>
+ </setreg>
+ <setreg>
+ <name>PLAY_BUFFER_SIZE</name>
+ <address>133</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_COMMAND</name>
+ <address>152</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_HALT</name>
+ <address>155</address>
+ </setreg>
+ <setreg>
+ <name>RX_CTRL_MAXLEN</name>
+ <address>156</address>
+ </setreg>
+ </registers>
+ <!-- Args -->
+ <args>
+ </args>
+ <!--All the connections to the outside world are listed in 'ports':-->
+ <ports>
+ <sink>
+ <name>in0</name>
+ <type>sc16</type>
+ </sink>
+ <sink>
+ <name>in1</name>
+ <type>sc16</type>
+ </sink>
+ <sink>
+ <name>in2</name>
+ <type>sc16</type>
+ </sink>
+ <sink>
+ <name>in3</name>
+ <type>sc16</type>
+ </sink>
+ <source>
+ <name>out0</name>
+ <type>sc16</type>
+ </source>
+ <source>
+ <name>out1</name>
+ <type>sc16</type>
+ </source>
+ <source>
+ <name>out2</name>
+ <type>sc16</type>
+ </source>
+ <source>
+ <name>out3</name>
+ <type>sc16</type>
+ </source> </ports>
+</nocblock>
+
diff --git a/host/include/uhd/rfnoc/replay_block_ctrl.hpp b/host/include/uhd/rfnoc/replay_block_ctrl.hpp
new file mode 100644
index 000000000..bd5c47739
--- /dev/null
+++ b/host/include/uhd/rfnoc/replay_block_ctrl.hpp
@@ -0,0 +1,67 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_REPLAY_BLOCK_HPP
+#define INCLUDED_LIBUHD_RFNOC_REPLAY_BLOCK_HPP
+
+#include <uhd/rfnoc/source_block_ctrl_base.hpp>
+#include <uhd/rfnoc/sink_block_ctrl_base.hpp>
+
+namespace uhd {
+ namespace rfnoc {
+
+/*! \brief Replay block controller
+ *
+ * The replay block has the following features:
+ * - One input and one output
+ * - The ability to record and playback data
+ * - Configurable base addresses and buffer sizes
+ * - Independent record and playback controls
+ * - Radio-like playback interface
+ * - The storage for the replay data can be any
+ * memory, usually an off-chip DRAM.
+ *
+ */
+class UHD_RFNOC_API replay_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base
+{
+public:
+ UHD_RFNOC_BLOCK_OBJECT(replay_block_ctrl)
+
+ //! Configure the base address and size of the record buffer region (in bytes).
+ virtual void config_record(const uint32_t base_addr, const uint32_t size, const size_t chan) = 0;
+
+ //! Configure the base address and size of the playback buffer region (in bytes).
+ virtual void config_play(const uint32_t base_addr, const uint32_t size, const size_t chan) = 0;
+
+ //! Restarts recording at the beginning of the record buffer
+ virtual void record_restart(const size_t chan) = 0;
+
+ //! Returns the base address of the record buffer (in bytes).
+ virtual uint32_t get_record_addr(const size_t chan) = 0;
+
+ //! Returns the base address of the playback buffer (in bytes).
+ virtual uint32_t get_play_addr(const size_t chan) = 0;
+
+ //! Returns the size of the record buffer (in bytes).
+ virtual uint32_t get_record_size(const size_t chan) = 0;
+
+ //! Returns the current fullness of the record buffer (in bytes).
+ virtual uint32_t get_record_fullness(const size_t chan) = 0;
+
+ //! Returns the size of the playback buffer (in bytes).
+ virtual uint32_t get_play_size(const size_t chan) = 0;
+
+ //! Sets the size of the packets played by the Replay block (in 64-bit words)
+ virtual void set_words_per_packet(const uint32_t num_words, const size_t chan) = 0;
+
+ //! Returns the size of the packets played by the Replay block (in 64-bit words)
+ virtual uint32_t get_words_per_packet(const size_t chan) = 0;
+
+}; /* class replay_block_ctrl*/
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_REPLAY_BLOCK_HPP */
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt
index 527c99e14..f98518272 100644
--- a/host/lib/rfnoc/CMakeLists.txt
+++ b/host/lib/rfnoc/CMakeLists.txt
@@ -40,6 +40,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/siggen_block_ctrl_impl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/replay_block_ctrl_impl.cpp
)
INCLUDE_SUBDIRECTORY(nocscript)
diff --git a/host/lib/rfnoc/replay_block_ctrl_impl.cpp b/host/lib/rfnoc/replay_block_ctrl_impl.cpp
new file mode 100644
index 000000000..a78632bf9
--- /dev/null
+++ b/host/lib/rfnoc/replay_block_ctrl_impl.cpp
@@ -0,0 +1,183 @@
+//
+// Copyright 2016 Ettus Research LLC
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/rfnoc/replay_block_ctrl.hpp>
+#include <mutex>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+class replay_block_ctrl_impl : public replay_block_ctrl
+{
+public:
+ static const uint32_t REPLAY_WORD_SIZE = 8; // In bytes
+ static const uint32_t SAMPLES_PER_WORD = 2;
+ static const uint32_t BYTES_PER_SAMPLE = 4;
+ static const uint32_t WORDS_PER_PACKET = 128;
+ static const uint32_t DEFAULT_BUFFER_SIZE = 32*1024*1024;
+ static const uint32_t DEFAULT_WPP = 182;
+ static const uint32_t DEFAULT_SPP = DEFAULT_WPP * SAMPLES_PER_WORD;
+
+
+ UHD_RFNOC_BLOCK_CONSTRUCTOR(replay_block_ctrl)
+ {
+ _num_channels = get_input_ports().size();
+ _params.resize(_num_channels);
+ for(size_t chan = 0; chan < _params.size(); chan++) {
+ _params[chan].words_per_packet = DEFAULT_WPP;
+ sr_write("RX_CTRL_MAXLEN", WORDS_PER_PACKET, chan);
+
+ // Configure replay channels to be adjacent DEFAULT_BUFFER_SIZE'd blocks
+ _params[chan].rec_base_addr = chan*DEFAULT_BUFFER_SIZE;
+ _params[chan].play_base_addr = chan*DEFAULT_BUFFER_SIZE;
+ _params[chan].rec_buffer_size = DEFAULT_BUFFER_SIZE;
+ _params[chan].play_buffer_size = DEFAULT_BUFFER_SIZE;
+ sr_write("REC_BASE_ADDR", _params[chan].rec_base_addr, chan);
+ sr_write("REC_BUFFER_SIZE", _params[chan].rec_buffer_size, chan);
+ sr_write("PLAY_BASE_ADDR", _params[chan].play_base_addr, chan);
+ sr_write("PLAY_BUFFER_SIZE", _params[chan].play_buffer_size, chan);
+ }
+ }
+
+
+ /**************************************************************************
+ * API Calls
+ **************************************************************************/
+
+ void config_record(const uint32_t base_addr, const uint32_t size, const size_t chan) {
+ std::lock_guard<std::mutex> lock(_mutex);
+ _params[chan].rec_base_addr = base_addr;
+ _params[chan].rec_buffer_size = size;
+ sr_write("REC_BASE_ADDR", base_addr, chan);
+ sr_write("REC_BUFFER_SIZE", size, chan);
+ sr_write("REC_RESTART", 0, chan);
+ }
+
+ void config_play(const uint32_t base_addr, const uint32_t size, const size_t chan) {
+ std::lock_guard<std::mutex> lock(_mutex);
+ _params[chan].play_base_addr = base_addr;
+ _params[chan].play_buffer_size = size;
+ sr_write("PLAY_BASE_ADDR", base_addr, chan);
+ sr_write("PLAY_BUFFER_SIZE", size, chan);
+ }
+
+ void record_restart(const size_t chan) {
+ std::lock_guard<std::mutex> lock(_mutex);
+ sr_write("REC_RESTART", 0, chan);
+ }
+
+ uint32_t get_record_addr(const size_t chan) {
+ return _params[chan].rec_base_addr;
+ }
+
+ uint32_t get_record_size(const size_t chan) {
+ return _params[chan].rec_buffer_size;
+ }
+
+ uint32_t get_record_fullness(const size_t chan) {
+ return user_reg_read32("REC_FULLNESS", chan);
+ }
+
+ uint32_t get_play_addr(const size_t chan) {
+ return _params[chan].play_base_addr;
+ }
+
+ uint32_t get_play_size(const size_t chan) {
+ return _params[chan].play_buffer_size;
+ }
+
+ void set_words_per_packet(const uint32_t num_words, const size_t chan) {
+ std::lock_guard<std::mutex> lock(_mutex);
+ _params[chan].words_per_packet = num_words;
+ sr_write("RX_CTRL_MAXLEN", num_words, chan);
+ }
+
+ uint32_t get_words_per_packet(const size_t chan) {
+ return _params[chan].words_per_packet;
+ }
+
+
+ /***************************************************************************
+ * Radio-like Streamer
+ **************************************************************************/
+
+ void issue_stream_cmd(
+ const uhd::stream_cmd_t &stream_cmd,
+ const size_t chan
+ ) {
+ std::lock_guard<std::mutex> lock(_mutex);
+ UHD_RFNOC_BLOCK_TRACE()
+ << "replay_block_ctrl_impl::issue_stream_cmd() " << chan
+ << " " << char(stream_cmd.stream_mode) ;
+
+ if (not (_rx_streamer_active.count(chan) and _rx_streamer_active.at(chan))) {
+ UHD_RFNOC_BLOCK_TRACE()
+ << "replay_block_ctrl_impl::issue_stream_cmd() called on inactive "
+ "channel. Skipping.";
+ return;
+ }
+
+ constexpr size_t max_num_samps = 0x0fffffff;
+ if (stream_cmd.num_samps > max_num_samps) {
+ UHD_LOG_ERROR("REPLAY",
+ "Requesting too many samples in a single burst! "
+ "Requested " + std::to_string(stream_cmd.num_samps) + ", maximum "
+ "is " + std::to_string(max_num_samps) + ".");
+ throw uhd::value_error("Requested too many samples in a single burst.");
+ }
+
+ // Setup the mode to instruction flags
+ typedef std::tuple<bool, bool, bool, bool> inst_t;
+ static const std::map<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst {
+ // reload, chain, samps, stop
+ {stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false)},
+ {stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true)},
+ {stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false)},
+ {stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false)}
+ };
+
+ // Setup the instruction flag values
+ bool inst_reload, inst_chain, inst_samps, inst_stop;
+ std::tie(inst_reload, inst_chain, inst_samps, inst_stop) =
+ mode_to_inst.at(stream_cmd.stream_mode);
+
+ // Calculate how many words to transfer at a time in CONTINUOUS mode
+ uint32_t cont_burst_size =
+ (_params[chan].play_buffer_size > WORDS_PER_PACKET) ?
+ WORDS_PER_PACKET : _params[chan].play_buffer_size;
+
+ // Calculate the number of words to transfer in NUM_SAMPS mode
+ uint32_t num_words = stream_cmd.num_samps / SAMPLES_PER_WORD;
+
+ // Calculate the word from flags and length
+ const uint32_t cmd_word = 0
+ | (uint32_t(stream_cmd.stream_now ? 1 : 0) << 31)
+ | (uint32_t(inst_chain ? 1 : 0) << 30)
+ | (uint32_t(inst_reload ? 1 : 0) << 29)
+ | (uint32_t(inst_stop ? 1 : 0) << 28)
+ | (inst_samps ? num_words : (inst_stop ? 0 : cont_burst_size));
+
+ // Issue the stream command
+ sr_write("RX_CTRL_COMMAND", cmd_word, chan);
+ }
+
+private:
+ struct replay_params_t {
+ size_t words_per_packet;
+ uint32_t rec_base_addr;
+ uint32_t rec_buffer_size;
+ uint32_t play_base_addr;
+ uint32_t play_buffer_size;
+ };
+
+ size_t _num_channels;
+ std::vector<replay_params_t> _params;
+
+ std::mutex _mutex;
+};
+
+UHD_RFNOC_BLOCK_REGISTER(replay_block_ctrl, "Replay");