diff options
| author | Wade Fife <wade.fife@ettus.com> | 2018-07-16 07:39:30 -0500 | 
|---|---|---|
| committer | Brent Stapleton <brent.stapleton@ettus.com> | 2018-08-29 15:45:55 -0700 | 
| commit | bed75f0ccbb6da139283a07c1442293bef95f26a (patch) | |
| tree | 54cda1bbb73bd291d264bf995d0b0bdf8de737d7 | |
| parent | 639797e5924b42764711b69b09e48ee07ed3d683 (diff) | |
| download | uhd-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.cpp | 367 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/blocks/replay.xml | 64 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/blocks/replay_x2.xml | 72 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/blocks/replay_x4.xml | 87 | ||||
| -rw-r--r-- | host/include/uhd/rfnoc/replay_block_ctrl.hpp | 67 | ||||
| -rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/lib/rfnoc/replay_block_ctrl_impl.cpp | 183 | 
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"); | 
