aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authormattprost <matt.prost@ni.com>2020-03-04 16:29:39 -0600
committerAaron Rossetto <aaron.rossetto@ni.com>2020-08-04 15:40:30 -0500
commitef16cea9bd951a83e8d2818f80d43c80db1beb96 (patch)
treefb4478e117f2f5fc82e01f80e84107cbf6fb6e54 /host
parent6d92a1828121ca4b57d496bbf522820f961244b9 (diff)
downloaduhd-ef16cea9bd951a83e8d2818f80d43c80db1beb96.tar.gz
uhd-ef16cea9bd951a83e8d2818f80d43c80db1beb96.tar.bz2
uhd-ef16cea9bd951a83e8d2818f80d43c80db1beb96.zip
rfnoc: Add RFNoC replay block
Signed-off-by: mattprost <matt.prost@ni.com>
Diffstat (limited to 'host')
-rw-r--r--host/include/uhd/rfnoc/defaults.hpp3
-rw-r--r--host/include/uhd/rfnoc/replay_block_control.hpp324
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/replay_block_control.cpp528
-rw-r--r--host/lib/rfnoc/replay_block_ctrl_impl.cpp201
5 files changed, 855 insertions, 202 deletions
diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp
index 781beb696..f96dad8a0 100644
--- a/host/include/uhd/rfnoc/defaults.hpp
+++ b/host/include/uhd/rfnoc/defaults.hpp
@@ -82,10 +82,11 @@ static const noc_id_t FIR_FILTER_BLOCK = 0xF1120000;
static const noc_id_t FOSPHOR_BLOCK = 0x666F0000;
static const noc_id_t LOGPWR_BLOCK = 0x4C500000;
static const noc_id_t MOVING_AVERAGE_BLOCK = 0xAAD20000;
+static const noc_id_t RADIO_BLOCK = 0x12AD1000;
+static const noc_id_t REPLAY_BLOCK = 0x4E91A000;
static const noc_id_t SIGGEN_BLOCK = 0x51663110;
static const noc_id_t SPLIT_STREAM_BLOCK = 0x57570000;
static const noc_id_t SWITCHBOARD_BLOCK = 0xBE110000;
-static const noc_id_t RADIO_BLOCK = 0x12AD1000;
static const noc_id_t VECTOR_IIR_BLOCK = 0x11120000;
static const noc_id_t WINDOW_BLOCK = 0xD0530000;
diff --git a/host/include/uhd/rfnoc/replay_block_control.hpp b/host/include/uhd/rfnoc/replay_block_control.hpp
new file mode 100644
index 000000000..a3f2ce90d
--- /dev/null
+++ b/host/include/uhd/rfnoc/replay_block_control.hpp
@@ -0,0 +1,324 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/config.hpp>
+#include <uhd/rfnoc/noc_block_base.hpp>
+
+namespace uhd { namespace rfnoc {
+
+/*! Replay Block Control CLass
+ *
+ * The Replay block records data to memory and plays back data from memory.
+ *
+ * It is the responsibility of the user to manage the memory. Care must be taken to avoid
+ * unintentional overlaps in the memory across blocks and ports. Each port of the Replay
+ * block has access to the full memory space. This allows recording data on any input port
+ * of any Replay block and playback on any output port of any Replay block. The section of
+ * memory used by a record or play operation is controlled by setting the offset and size.
+ *
+ * For both record and playback, the offset and the size must be aligned to the memory's
+ * word size. For playback, the size must also be aligned to the size of an item. An item
+ * is a single unit of data as defined by the data type of a port. For record, the data
+ * type is automatically determined by the connection to the upstream block by using the
+ * "type" edge property for the corresponding input port. For playback, this is
+ * automatically determined by the connection to the downstream block by using the "type"
+ * edge property for the corresponding output port. These can be explicitly set by the
+ * user, if necessary. It is the user's responsibility to manage the types of the
+ * individual record and the playback ports. Methods to get the word size and item sizes
+ * are provided to help calculate proper alignment.
+ *
+ * One key configuration of playback is the packet size. Larger packet sizes provide for
+ * better throughput while smaller packet sizes provide for lower latency. By default,
+ * the "mtu" edge property of the output port is used to define the maximum packet size to
+ * achieve the best throughput to the downstream block without exceeding the supported
+ * packet size. The maximum packet size can be explicitly set in terms of bytes or
+ * number of items to allow users to tune the performance to suit their application.
+ */
+class UHD_API replay_block_control : public noc_block_base
+{
+public:
+ RFNOC_DECLARE_BLOCK(replay_block_control)
+
+ static const uint16_t MINOR_COMPAT;
+ static const uint16_t MAJOR_COMPAT;
+
+ static const uint32_t REPLAY_ADDR_W;
+ static const uint32_t REPLAY_BLOCK_OFFSET;
+
+ static const uint32_t REG_COMPAT_ADDR;
+ static const uint32_t REG_MEM_SIZE_ADDR;
+ static const uint32_t REG_REC_RESTART_ADDR;
+ static const uint32_t REG_REC_BASE_ADDR_LO_ADDR;
+ static const uint32_t REG_REC_BASE_ADDR_HI_ADDR;
+ static const uint32_t REG_REC_BUFFER_SIZE_LO_ADDR;
+ static const uint32_t REG_REC_BUFFER_SIZE_HI_ADDR;
+ static const uint32_t REG_REC_FULLNESS_LO_ADDR;
+ static const uint32_t REG_REC_FULLNESS_HI_ADDR;
+ static const uint32_t REG_PLAY_BASE_ADDR_LO_ADDR;
+ static const uint32_t REG_PLAY_BASE_ADDR_HI_ADDR;
+ static const uint32_t REG_PLAY_BUFFER_SIZE_LO_ADDR;
+ static const uint32_t REG_PLAY_BUFFER_SIZE_HI_ADDR;
+ static const uint32_t REG_PLAY_CMD_NUM_WORDS_LO_ADDR;
+ static const uint32_t REG_PLAY_CMD_NUM_WORDS_HI_ADDR;
+ static const uint32_t REG_PLAY_CMD_TIME_LO_ADDR;
+ static const uint32_t REG_PLAY_CMD_TIME_HI_ADDR;
+ static const uint32_t REG_PLAY_CMD_ADDR;
+ static const uint32_t REG_PLAY_WORDS_PER_PKT_ADDR;
+ static const uint32_t REG_PLAY_ITEM_SIZE_ADDR;
+
+ static const uint32_t PLAY_CMD_STOP;
+ static const uint32_t PLAY_CMD_FINITE;
+ static const uint32_t PLAY_CMD_CONTINUOUS;
+
+ /**************************************************************************
+ * Replay Control API calls
+ *************************************************************************/
+ /*! Record
+ *
+ * Begin recording. The offset sets the starting location in memory and the size
+ * limits the length of the recording. The flow of data is controlled by upstream
+ * RFNoC blocks.
+ *
+ * \param offset Memory offset where to start recording the data. This value
+ * must be aligned to the memory word size. Use get_word_size() to get the
+ * size of the memory word.
+ * \param size Size limit, in bytes. This value must be aligned to the memory
+ * word size and the item size. Use get_word_size() to get the size of the
+ * memory word and get_item_size() to get the item size. A value of 0 means
+ * to use all available space.
+ * \param port Which input port of the replay block to use
+ */
+ virtual void record(
+ const uint64_t offset, const uint64_t size, const size_t port = 0) = 0;
+
+ /*! Restarts recording from the record offset
+ *
+ * \param port Which input port of the replay block to use
+ */
+ virtual void record_restart(const size_t port = 0) = 0;
+
+ /*! Play
+ *
+ * Play back data. The offset and size define what data is played back on an output
+ * port. The data can be played once or repeated continuously until a stop command is
+ * issued. If a time_spec is supplied, it will be placed in the header of the first
+ * packet. Typically, this is used to tell a downstream Radio block when to start
+ * transmitting the data. If the data type on the output port is not defined, this
+ * function will throw an error.
+ *
+ * \param offset Memory offset of the data to be played. This value must be
+ * aligned to the size of the word in memory. Use get_word_size() to get the
+ * memory word size.
+ * \param size Size of data to play back. This value must be aligned to the
+ * size of the memory word and item size. Use get_word_size() to get the
+ * memory word size and get_output_item_size() to get the item size.
+ * \param port Which output port of the replay block to use
+ * \param time_spec Set the time for the first item. Any non-zero value is used to
+ * set the time in the header of the first packet. Most commonly, this is used to
+ * set the start time of a transmission.
+ * \param repeat Determines whether the data should be played repeatedly or
+ * just once. If set to true, stop() must be called to stop the play back.
+ */
+ virtual void play(const uint64_t offset,
+ const uint64_t size,
+ const size_t port = 0,
+ const uhd::time_spec_t time_spec = uhd::time_spec_t(0.0),
+ const bool repeat = false) = 0;
+
+ /*! Stops playback
+ *
+ * Halts any currently executing play commands and cancels any other play commands
+ * that are waiting to be executed for that output port.
+ *
+ * \param port Which output port of the replay block to use
+ */
+ virtual void stop(const size_t port = 0) = 0;
+
+ /*! Get the size of the memory
+ *
+ * Returns the size of the shared memory space. Any record or playback buffers must
+ * be configured in this memory space.
+ *
+ * \returns the size of the shared Replay memory
+ */
+ virtual uint64_t get_mem_size() const = 0;
+
+ /*! Get the size of a memory word
+ *
+ * \returns the size of a memory word
+ */
+ virtual uint64_t get_word_size() const = 0;
+
+ /**************************************************************************
+ * Record State API calls
+ *************************************************************************/
+ /*! Get the starting offset of the current record buffer
+ *
+ * \param port Which input port of the replay block to use
+ * \returns starting offset of the record buffer
+ */
+ virtual uint64_t get_record_offset(const size_t port = 0) const = 0;
+
+ /*! Get the current size of the record space
+ *
+ * \param port Which input port of the replay block to use
+ * \returns size of the record buffer
+ */
+ virtual uint64_t get_record_size(const size_t port = 0) const = 0;
+
+ /*! Get the fullness of the current record buffer
+ *
+ * Returns the number of bytes that have been recorded in the record buffer. A record
+ * restart will reset this number back to 0.
+ *
+ * \param port Which input port of the replay block to use
+ * \returns fullness of the record buffer
+ */
+ virtual uint64_t get_record_fullness(const size_t port = 0) = 0;
+
+ /*! Get the current record data type
+ *
+ * \param port Which input port of the replay block to use
+ * \returns the current record data type
+ */
+ virtual io_type_t get_record_type(const size_t port = 0) const = 0;
+
+ /*! Get the current record item size
+ *
+ * \param port Which input port of the replay block to use
+ * \returns the size of an item in the current record buffer
+ */
+ virtual size_t get_record_item_size(const size_t port = 0) const = 0;
+
+ /**************************************************************************
+ * Playback State API calls
+ *************************************************************************/
+ /*! Get the offset of the current playback buffer
+ *
+ * \param port Which output port of the replay block to use
+ * \returns the offset of the current playback buffer
+ */
+ virtual uint64_t get_play_offset(const size_t port = 0) const = 0;
+
+ /*! Get the current size of the playback space
+ *
+ * \param port Which output port of the replay block to use
+ * \returns size of the playback buffer
+ */
+ virtual uint64_t get_play_size(const size_t port = 0) const = 0;
+
+ /*! Get the maximum number of items in a packet
+ *
+ * \param port Which output port of the replay block to use
+ * \returns the maximum number of items in a packet
+ */
+ virtual uint32_t get_max_items_per_packet(const size_t port = 0) const = 0;
+
+ /*! Get the maximum size of a packet
+ *
+ * Returns the maximum size of a packet, inclusive of headers and payload.
+ *
+ * \param port Which output port of the replay block to use
+ * \returns the maximum size of a packet
+ */
+ virtual uint32_t get_max_packet_size(const size_t port = 0) const = 0;
+
+ /*! Get the current play data type
+ *
+ * \param port Which output port of the replay block to use
+ * \returns the current play data type
+ */
+ virtual io_type_t get_play_type(const size_t port = 0) const = 0;
+
+ /*! Get the current play item size
+ *
+ * \param port Which output port of the replay block to use
+ * \returns the size of an item in the current play buffer
+ */
+ virtual size_t get_play_item_size(const size_t port = 0) const = 0;
+
+ /**************************************************************************
+ * Advanced Record Control API calls
+ *************************************************************************/
+ /*! Explicitly set the current record data type
+ *
+ * Sets the data type for items in the current record buffer for the given input port.
+ *
+ * \param type The data type
+ * \param port Which input port of the replay block to use
+ */
+ virtual void set_record_type(const io_type_t type, const size_t port = 0) = 0;
+
+ /**************************************************************************
+ * Advanced Playback Control API calls
+ *************************************************************************/
+ /*! Configure the offsets and size of the playback buffer region
+ *
+ * Specifies a buffer area in the memory for playback. In order to begin
+ * playback on this region, a stream command must be issued.
+ *
+ * \param offset Memory offset of the data to be played. This value must be
+ * aligned to the size of the word in memory. Use get_word_size() to get the
+ * memory word size.
+ * \param size Size of data to play back. This value must be aligned to the
+ * size of the memory word and item size. Use get_word_size() to get the
+ * memory word size and get_output_item_size() to get the item size.
+ * \param port Which output port of the replay block to use
+ */
+ virtual void config_play(
+ const uint64_t offset, const uint64_t size, const size_t port = 0) = 0;
+
+ /*! Explicitly set the current play data type
+ *
+ * Sets the data type for items in the current play buffer for the given output port.
+ *
+ * \param type The data type
+ * \param port Which output port of the replay block to use
+ */
+ virtual void set_play_type(const io_type_t type, const size_t port = 0) = 0;
+
+ /*! Set the maximum number of items in a packet
+ *
+ * Sets the maximum number of items that can be in a packet's payload. An actual
+ * packet may be smaller in order to coerce to mtu values, or to align with memory
+ * word size.
+ *
+ * \param ipp Number of items per packet
+ * \param port Which output port of the replay block to use
+ */
+ virtual void set_max_items_per_packet(const uint32_t ipp, const size_t port = 0) = 0;
+
+ /*! Set the maximum size of a packet
+ *
+ * Sets the maximum packet size, inclusive of headers and payload.
+ *
+ * \param size The size of the packet
+ * \param port Which output port of the replay block to use
+ */
+ virtual void set_max_packet_size(const uint32_t size, const size_t port = 0) = 0;
+
+ /*! Issue a stream command to the replay block
+ *
+ * Issue stream commands to start or stop playback from the configured playback
+ * buffer. Supports
+ * STREAM_MODE_START_CONTINUOUS to start continuous repeating playback,
+ * STREAM_MODE_START_NUM_SAMPS_AND_DONE to play the given number of samples once, and
+ * STREAM_MODE_STOP_CONTINUOUS to stop all playback immediately.
+ * If a time_spec is supplied, it is placed in the header of the first packet produced
+ * for that command. Commands are queued and executed in order. A
+ * STREAM_MODE_STOP_CONTINUOUS command will halt all playback and purge all commands
+ * in the queue for a given output port.
+ *
+ * \param stream_cmd The command to execute
+ * \param port Which output port of the replay block to use
+ */
+ virtual void issue_stream_cmd(
+ const uhd::stream_cmd_t& stream_cmd, const size_t port = 0) = 0;
+};
+
+}} /* namespace uhd::rfnoc */
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt
index 233c536bf..6c7b8c726 100644
--- a/host/lib/rfnoc/CMakeLists.txt
+++ b/host/lib/rfnoc/CMakeLists.txt
@@ -52,6 +52,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/moving_average_block_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/null_block_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/radio_control_impl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/replay_block_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/siggen_block_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/split_stream_block_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/switchboard_block_control.cpp
diff --git a/host/lib/rfnoc/replay_block_control.cpp b/host/lib/rfnoc/replay_block_control.cpp
new file mode 100644
index 000000000..6067650e0
--- /dev/null
+++ b/host/lib/rfnoc/replay_block_control.cpp
@@ -0,0 +1,528 @@
+//
+// Copyright 2020 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/defaults.hpp>
+#include <uhd/rfnoc/multichan_register_iface.hpp>
+#include <uhd/rfnoc/property.hpp>
+#include <uhd/rfnoc/registry.hpp>
+#include <uhd/rfnoc/replay_block_control.hpp>
+#include <uhd/types/stream_cmd.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/utils/compat_check.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <string>
+
+using namespace uhd::rfnoc;
+
+// Block compatability version
+const uint16_t replay_block_control::MINOR_COMPAT = 0;
+const uint16_t replay_block_control::MAJOR_COMPAT = 1;
+
+// NoC block address space
+const uint32_t replay_block_control::REPLAY_ADDR_W = 8;
+const uint32_t replay_block_control::REPLAY_BLOCK_OFFSET =
+ 1 << replay_block_control::REPLAY_ADDR_W; // 256 bytes
+
+// Register offsets
+const uint32_t replay_block_control::REG_COMPAT_ADDR = 0x00;
+const uint32_t replay_block_control::REG_MEM_SIZE_ADDR = 0x04;
+const uint32_t replay_block_control::REG_REC_RESTART_ADDR = 0x08;
+const uint32_t replay_block_control::REG_REC_BASE_ADDR_LO_ADDR = 0x10;
+const uint32_t replay_block_control::REG_REC_BASE_ADDR_HI_ADDR = 0x14;
+const uint32_t replay_block_control::REG_REC_BUFFER_SIZE_LO_ADDR = 0x18;
+const uint32_t replay_block_control::REG_REC_BUFFER_SIZE_HI_ADDR = 0x1C;
+const uint32_t replay_block_control::REG_REC_FULLNESS_LO_ADDR = 0x20;
+const uint32_t replay_block_control::REG_REC_FULLNESS_HI_ADDR = 0x24;
+const uint32_t replay_block_control::REG_PLAY_BASE_ADDR_LO_ADDR = 0x28;
+const uint32_t replay_block_control::REG_PLAY_BASE_ADDR_HI_ADDR = 0x2C;
+const uint32_t replay_block_control::REG_PLAY_BUFFER_SIZE_LO_ADDR = 0x30;
+const uint32_t replay_block_control::REG_PLAY_BUFFER_SIZE_HI_ADDR = 0x34;
+const uint32_t replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR = 0x38;
+const uint32_t replay_block_control::REG_PLAY_CMD_NUM_WORDS_HI_ADDR = 0x3C;
+const uint32_t replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR = 0x40;
+const uint32_t replay_block_control::REG_PLAY_CMD_TIME_HI_ADDR = 0x44;
+const uint32_t replay_block_control::REG_PLAY_CMD_ADDR = 0x48;
+const uint32_t replay_block_control::REG_PLAY_WORDS_PER_PKT_ADDR = 0x4C;
+const uint32_t replay_block_control::REG_PLAY_ITEM_SIZE_ADDR = 0x50;
+
+// Stream commands
+const uint32_t replay_block_control::PLAY_CMD_STOP = 0;
+const uint32_t replay_block_control::PLAY_CMD_FINITE = 1;
+const uint32_t replay_block_control::PLAY_CMD_CONTINUOUS = 2;
+
+// Mask bits
+constexpr uint32_t PLAY_COMMAND_TIMED_BIT = 31;
+constexpr uint32_t PLAY_COMMAND_TIMED_MASK = uint32_t(1) << PLAY_COMMAND_TIMED_BIT;
+constexpr uint32_t PLAY_COMMAND_MASK = 3;
+
+// User property names
+const char* const PROP_KEY_RECORD_OFFSET = "record_offset";
+const char* const PROP_KEY_RECORD_SIZE = "record_size";
+const char* const PROP_KEY_PLAY_OFFSET = "play_offset";
+const char* const PROP_KEY_PLAY_SIZE = "play_size";
+const char* const PROP_KEY_PKT_SIZE = "packet_size";
+
+class replay_block_control_impl : public replay_block_control
+{
+public:
+ RFNOC_BLOCK_CONSTRUCTOR(replay_block_control),
+ _replay_reg_iface(*this, 0, REPLAY_BLOCK_OFFSET),
+ _num_input_ports(get_num_input_ports()),
+ _num_output_ports(get_num_output_ports()),
+ _fpga_compat(_replay_reg_iface.peek32(REG_COMPAT_ADDR)),
+ _word_size(
+ uint16_t((_replay_reg_iface.peek32(REG_MEM_SIZE_ADDR) >> 16) & 0xFFFF) / 8),
+ _mem_size(uint64_t(1 << (_replay_reg_iface.peek32(REG_MEM_SIZE_ADDR) & 0xFFFF)))
+ {
+ UHD_ASSERT_THROW(get_num_input_ports() == get_num_output_ports());
+ uhd::assert_fpga_compat(MAJOR_COMPAT,
+ MINOR_COMPAT,
+ _fpga_compat,
+ get_unique_id(),
+ get_unique_id(),
+ false /* Let it slide if minors mismatch */
+ );
+
+ // Initialize record properties
+ _record_type.reserve(_num_input_ports);
+ _record_offset.reserve(_num_input_ports);
+ _record_size.reserve(_num_input_ports);
+ for (size_t port = 0; port < _num_input_ports; port++) {
+ _register_input_props(port);
+ _replay_reg_iface.poke64(
+ REG_REC_BASE_ADDR_LO_ADDR, _record_offset.at(port).get(), port);
+ _replay_reg_iface.poke64(
+ REG_REC_BUFFER_SIZE_LO_ADDR, _record_size.at(port).get(), port);
+ }
+
+ // Initialize playback properties
+ _play_type.reserve(_num_output_ports);
+ _play_offset.reserve(_num_output_ports);
+ _play_size.reserve(_num_output_ports);
+ _packet_size.reserve(_num_output_ports);
+ for (size_t port = 0; port < _num_output_ports; port++) {
+ _register_output_props(port);
+ _replay_reg_iface.poke32(REG_PLAY_ITEM_SIZE_ADDR,
+ uhd::convert::get_bytes_per_item(_play_type.at(port).get()),
+ port);
+ _replay_reg_iface.poke64(
+ REG_PLAY_BASE_ADDR_LO_ADDR, _play_offset.at(port).get(), port);
+ _replay_reg_iface.poke64(
+ REG_PLAY_BUFFER_SIZE_LO_ADDR, _play_size.at(port).get(), port);
+ _replay_reg_iface.poke32(REG_PLAY_WORDS_PER_PKT_ADDR,
+ (_packet_size.at(port).get() - CHDR_MAX_LEN_HDR) / _word_size,
+ port);
+ }
+ }
+
+ /**************************************************************************
+ * Replay Control API
+ **************************************************************************/
+ void record(const uint64_t offset, const uint64_t size, const size_t port)
+ {
+ set_property<uint64_t>(
+ PROP_KEY_RECORD_OFFSET, offset, {res_source_info::USER, port});
+ set_property<uint64_t>(PROP_KEY_RECORD_SIZE, size, {res_source_info::USER, port});
+
+ // The pointers to the new record buffer space must be set
+ record_restart(port);
+ }
+
+ void record_restart(const size_t port)
+ {
+ // Ensure that the buffer is properly configured before recording
+ _validate_record_buffer(port);
+ // Any value written to this register causes a record restart
+ _replay_reg_iface.poke32(REG_REC_RESTART_ADDR, 0, port);
+ }
+
+ void play(const uint64_t offset,
+ const uint64_t size,
+ const size_t port,
+ const uhd::time_spec_t time_spec,
+ const bool repeat)
+ {
+ config_play(offset, size, port);
+ uhd::stream_cmd_t play_cmd =
+ repeat ? uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS)
+ : uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
+ play_cmd.num_samps = size / get_play_item_size(port);
+ play_cmd.time_spec = time_spec;
+ play_cmd.stream_now = (time_spec == 0.0);
+ issue_stream_cmd(play_cmd, port);
+ }
+
+ void stop(const size_t port)
+ {
+ uhd::stream_cmd_t stop_cmd =
+ uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
+ issue_stream_cmd(stop_cmd, port);
+ }
+
+ uint64_t get_mem_size() const
+ {
+ return _mem_size;
+ }
+
+ uint64_t get_word_size() const
+ {
+ return _word_size;
+ }
+
+ /**************************************************************************
+ * Record Buffer State API
+ **************************************************************************/
+ uint64_t get_record_offset(const size_t port) const
+ {
+ return _record_offset.at(port).get();
+ }
+
+ uint64_t get_record_size(const size_t port) const
+ {
+ return _record_size.at(port).get();
+ }
+
+ uint64_t get_record_fullness(const size_t port)
+ {
+ return _replay_reg_iface.peek64(REG_REC_FULLNESS_LO_ADDR, port);
+ }
+
+ io_type_t get_record_type(const size_t port) const
+ {
+ return _record_type.at(port).get();
+ }
+
+ virtual size_t get_record_item_size(const size_t port) const
+ {
+ return uhd::convert::get_bytes_per_item(get_record_type(port));
+ }
+
+ /**************************************************************************
+ * Playback State API
+ **************************************************************************/
+ uint64_t get_play_offset(const size_t port) const
+ {
+ return _play_offset.at(port).get();
+ }
+
+ uint64_t get_play_size(const size_t port) const
+ {
+ return _play_size.at(port).get();
+ }
+
+ uint32_t get_max_items_per_packet(const size_t port) const
+ {
+ return (_packet_size.at(port).get() - CHDR_MAX_LEN_HDR)
+ / get_play_item_size(port);
+ }
+
+ uint32_t get_max_packet_size(const size_t port) const
+ {
+ return _packet_size.at(port).get();
+ }
+
+ io_type_t get_play_type(const size_t port) const
+ {
+ return _play_type.at(port).get();
+ }
+
+ size_t get_play_item_size(const size_t port) const
+ {
+ return uhd::convert::get_bytes_per_item(get_play_type(port));
+ }
+
+ /**************************************************************************
+ * Advanced Record Control API calls
+ *************************************************************************/
+ void set_record_type(const io_type_t type, const size_t port)
+ {
+ set_property<std::string>(
+ PROP_KEY_TYPE, type, {res_source_info::INPUT_EDGE, port});
+ }
+
+ /**************************************************************************
+ * Advanced Playback Control API
+ **************************************************************************/
+ void config_play(const uint64_t offset, const uint64_t size, const size_t port)
+ {
+ set_property<uint64_t>(
+ PROP_KEY_PLAY_OFFSET, offset, {res_source_info::USER, port});
+ set_property<uint64_t>(PROP_KEY_PLAY_SIZE, size, {res_source_info::USER, port});
+ _validate_play_buffer(port);
+ }
+
+ void set_play_type(const io_type_t type, const size_t port)
+ {
+ set_property<std::string>(
+ PROP_KEY_TYPE, type, {res_source_info::OUTPUT_EDGE, port});
+ }
+
+ void set_max_items_per_packet(const uint32_t ipp, const size_t port)
+ {
+ set_max_packet_size(CHDR_MAX_LEN_HDR + ipp * get_play_item_size(port), port);
+ }
+
+ void set_max_packet_size(const uint32_t size, const size_t port)
+ {
+ set_property<uint32_t>(PROP_KEY_PKT_SIZE, size, {res_source_info::USER, port});
+ }
+
+ void issue_stream_cmd(const uhd::stream_cmd_t& stream_cmd, const size_t port)
+ {
+ // Ensure that the buffer is properly configured before issuing a stream command
+ _validate_play_buffer(port);
+ RFNOC_LOG_TRACE("replay_block_control_impl::issue_stream_cmd(port="
+ << port << ", mode=" << char(stream_cmd.stream_mode) << ")");
+
+ // Setup the mode to instruction flags
+ const uint8_t play_cmd = [stream_cmd]() -> uint8_t {
+ switch (stream_cmd.stream_mode) {
+ case uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS:
+ return PLAY_CMD_STOP; // Stop playing back data
+ case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE:
+ case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE:
+ return PLAY_CMD_FINITE; // Play NUM_SAMPS then stop
+ case uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS:
+ return PLAY_CMD_CONTINUOUS; // Playback continuously over the play
+ // buffer until stopped
+ default:
+ throw uhd::value_error("Requested invalid stream command.");
+ }
+ }();
+
+ // Calculate the number of words to transfer in NUM_SAMPS mode
+ if (play_cmd == PLAY_CMD_FINITE) {
+ uint64_t num_words =
+ stream_cmd.num_samps * get_play_item_size(port) / get_word_size();
+ _replay_reg_iface.poke64(REG_PLAY_CMD_NUM_WORDS_LO_ADDR, num_words, port);
+ }
+
+ // Set the time for the command
+ const uint32_t timed_flag = (stream_cmd.stream_now) ? 0 : PLAY_COMMAND_TIMED_MASK;
+ if (!stream_cmd.stream_now) {
+ const double tick_rate = get_tick_rate();
+ UHD_LOG_DEBUG("REPLAY",
+ "Using tick rate " << (tick_rate / 1e6) << " MHz to set stream command.");
+ const uint64_t ticks = stream_cmd.time_spec.to_ticks(tick_rate);
+ _replay_reg_iface.poke64(REG_PLAY_CMD_TIME_LO_ADDR, ticks, port);
+ }
+
+ // Issue the stream command
+ uint32_t command_word = (play_cmd & PLAY_COMMAND_MASK) | timed_flag;
+ _replay_reg_iface.poke32(REG_PLAY_CMD_ADDR, command_word, port);
+ }
+
+protected:
+ // Block-specific register interface
+ multichan_register_iface _replay_reg_iface;
+
+private:
+ void _register_input_props(const size_t port)
+ {
+ // Get default property values
+ const io_type_t default_type = IO_TYPE_SC16;
+ const uint64_t record_offset = 0;
+ const uint64_t record_size = _mem_size;
+
+ // Initialize properties
+ _record_type.emplace_back(property_t<std::string>(
+ PROP_KEY_TYPE, default_type, {res_source_info::INPUT_EDGE, port}));
+ _record_offset.push_back(property_t<uint64_t>(
+ PROP_KEY_RECORD_OFFSET, record_offset, {res_source_info::USER, port}));
+ _record_size.push_back(property_t<uint64_t>(
+ PROP_KEY_RECORD_SIZE, record_size, {res_source_info::USER, port}));
+ UHD_ASSERT_THROW(_record_type.size() == port + 1);
+ UHD_ASSERT_THROW(_record_offset.size() == port + 1);
+ UHD_ASSERT_THROW(_record_size.size() == port + 1);
+
+ // Register user properties
+ register_property(&_record_type.at(port));
+ register_property(&_record_offset.at(port));
+ register_property(&_record_size.at(port));
+
+ // Add property resolvers
+ add_property_resolver({&_record_offset.at(port)}, {}, [this, port]() {
+ _set_record_offset(_record_offset.at(port).get(), port);
+ });
+ add_property_resolver({&_record_size.at(port)},
+ {&_record_size.at(port)},
+ [this, port]() { _set_record_size(_record_size.at(port).get(), port); });
+ }
+
+ void _register_output_props(const size_t port)
+ {
+ // Get default property values
+ const io_type_t default_type = IO_TYPE_SC16;
+ const uint64_t play_offset = 0;
+ const uint64_t play_size = _mem_size;
+ const uint32_t packet_size = get_mtu({res_source_info::OUTPUT_EDGE, port});
+
+ // Initialize properties
+ _play_type.emplace_back(property_t<std::string>(
+ PROP_KEY_TYPE, default_type, {res_source_info::OUTPUT_EDGE, port}));
+ _play_offset.push_back(property_t<uint64_t>(
+ PROP_KEY_PLAY_OFFSET, play_offset, {res_source_info::USER, port}));
+ _play_size.push_back(property_t<uint64_t>(
+ PROP_KEY_PLAY_SIZE, play_size, {res_source_info::USER, port}));
+ _packet_size.push_back(property_t<uint32_t>(
+ PROP_KEY_PKT_SIZE, packet_size, {res_source_info::USER, port}));
+ UHD_ASSERT_THROW(_play_type.size() == port + 1);
+ UHD_ASSERT_THROW(_play_offset.size() == port + 1);
+ UHD_ASSERT_THROW(_play_size.size() == port + 1);
+ UHD_ASSERT_THROW(_packet_size.size() == port + 1);
+
+ // Register user properties
+ register_property(&_play_type.at(port));
+ register_property(&_play_offset.at(port));
+ register_property(&_play_size.at(port));
+ register_property(&_packet_size.at(port));
+
+ // Add property resolvers
+ add_property_resolver({&_play_type.at(port)}, {}, [this, port]() {
+ _set_play_type(_play_type.at(port).get(), port);
+ });
+ add_property_resolver({&_play_offset.at(port)}, {}, [this, port]() {
+ _set_play_offset(_play_offset.at(port).get(), port);
+ });
+ add_property_resolver({&_play_size.at(port)},
+ {&_play_size.at(port)},
+ [this, port]() { _set_play_size(_play_size.at(port).get(), port); });
+ add_property_resolver({&_packet_size.at(port)}, {}, [this, port]() {
+ _set_packet_size(_packet_size.at(port).get(), port);
+ });
+ }
+
+ void _set_play_type(const io_type_t type, const size_t port)
+ {
+ uint32_t play_item_size = uhd::convert::get_bytes_per_item(type);
+ _replay_reg_iface.poke32(REG_PLAY_ITEM_SIZE_ADDR, play_item_size, port);
+ }
+
+ void _set_record_offset(const uint64_t record_offset, const size_t port)
+ {
+ if ((record_offset % _word_size) != 0) {
+ throw uhd::value_error("Record offset must be a multiple of word size.");
+ }
+ if (record_offset > _mem_size) {
+ throw uhd::value_error("Record offset is out of bounds.");
+ }
+ _replay_reg_iface.poke64(REG_REC_BASE_ADDR_LO_ADDR, record_offset, port);
+ }
+
+ void _set_record_size(const uint64_t record_size, const size_t port)
+ {
+ if ((record_size % _word_size) != 0) {
+ _record_size.at(port) = record_size - (record_size % _word_size);
+ throw uhd::value_error("Record buffer size must be a multiple of word size.");
+ }
+ _replay_reg_iface.poke64(REG_REC_BUFFER_SIZE_LO_ADDR, record_size, port);
+ }
+
+ void _set_play_offset(const uint64_t play_offset, const size_t port)
+ {
+ if ((play_offset % _word_size) != 0) {
+ throw uhd::value_error("Play offset must be a multiple of word size.");
+ }
+ if (play_offset > _mem_size) {
+ throw uhd::value_error("Play offset is out of bounds.");
+ }
+ _replay_reg_iface.poke64(REG_PLAY_BASE_ADDR_LO_ADDR, play_offset, port);
+ }
+
+ void _set_play_size(const uint64_t play_size, const size_t port)
+ {
+ if ((play_size % _word_size) != 0) {
+ _play_size.at(port) = play_size - (play_size % _word_size);
+ throw uhd::value_error("Play buffer size must be a multiple of word size.");
+ }
+ if ((play_size % get_play_item_size(port)) != 0) {
+ _play_size.at(port) = play_size - (play_size % get_play_item_size(port));
+ throw uhd::value_error("Play buffer size must be a multiple of item size.");
+ }
+ _replay_reg_iface.poke64(REG_PLAY_BUFFER_SIZE_LO_ADDR, play_size, port);
+ }
+
+ void _set_packet_size(const uint32_t packet_size, const size_t port)
+ {
+ // MTU is max payload size, header with timestamp is already accounted for
+ const size_t mtu = get_mtu({res_source_info::OUTPUT_EDGE, port});
+ const uint32_t item_size = get_play_item_size(port);
+ const uint32_t mtu_payload = mtu - CHDR_MAX_LEN_HDR;
+ const uint32_t mtu_items = mtu_payload / item_size;
+ const uint32_t ipc = _word_size / item_size; // items per cycle
+ const uint32_t max_ipp_per_mtu = mtu_items - (mtu_items % ipc);
+ const uint32_t payload_size = packet_size - CHDR_MAX_LEN_HDR;
+ uint32_t ipp = payload_size / item_size;
+ if (ipp > max_ipp_per_mtu) {
+ RFNOC_LOG_WARNING("ipp value " << ipp << " exceeds MTU of " << mtu
+ << "! Coercing to " << max_ipp_per_mtu);
+ ipp = max_ipp_per_mtu;
+ }
+ if ((ipp % ipc) != 0) {
+ ipp = ipp - (ipp % ipc);
+ RFNOC_LOG_WARNING(
+ "ipp must be a multiple of the block bus width! Coercing to " << ipp);
+ }
+ if (ipp <= 0) {
+ ipp = DEFAULT_SPP;
+ RFNOC_LOG_WARNING("ipp must be greater than zero! Coercing to " << ipp);
+ }
+ // Packet size must be a multiple of word size
+ if ((packet_size % _word_size) != 0) {
+ throw uhd::value_error("Packet size must be a multiple of word size.");
+ }
+ const uint16_t words_per_packet =
+ uhd::narrow_cast<uint16_t>(ipp * item_size / _word_size);
+ _replay_reg_iface.poke32(
+ REG_PLAY_WORDS_PER_PKT_ADDR, uint32_t(words_per_packet), port);
+ }
+
+ void _validate_record_buffer(const size_t port)
+ {
+ // The entire record buffer must be within the bounds of memory
+ if ((get_record_offset(port) + get_record_size(port)) > get_mem_size()) {
+ throw uhd::value_error("Record buffer goes out of bounds.");
+ }
+ }
+
+ void _validate_play_buffer(const size_t port)
+ {
+ // Streaming requires that the buffer size is a multiple of item size
+ if ((get_play_size(port) % get_play_item_size(port)) != 0) {
+ throw uhd::value_error("Play size must be must be a multiple of item size.");
+ }
+ // The entire play buffer must be within the bounds of memory
+ if ((get_play_offset(port) + get_play_size(port)) > get_mem_size()) {
+ throw uhd::value_error("Play buffer goes out of bounds.");
+ }
+ }
+
+ /**************************************************************************
+ * Attributes
+ *************************************************************************/
+ const size_t _num_input_ports;
+ const size_t _num_output_ports;
+
+ // Block compat number
+ const uint32_t _fpga_compat;
+
+ // These size params are configurable in the FPGA
+ const uint16_t _word_size;
+ const uint64_t _mem_size;
+
+ std::vector<property_t<std::string>> _record_type;
+ std::vector<property_t<uint64_t>> _record_offset;
+ std::vector<property_t<uint64_t>> _record_size;
+ std::vector<property_t<std::string>> _play_type;
+ std::vector<property_t<uint64_t>> _play_offset;
+ std::vector<property_t<uint64_t>> _play_size;
+ std::vector<property_t<uint32_t>> _packet_size;
+};
+
+UHD_RFNOC_BLOCK_REGISTER_DIRECT(
+ replay_block_control, REPLAY_BLOCK, "Replay", CLOCK_KEY_GRAPH, "bus_clk")
diff --git a/host/lib/rfnoc/replay_block_ctrl_impl.cpp b/host/lib/rfnoc/replay_block_ctrl_impl.cpp
deleted file mode 100644
index db3721d37..000000000
--- a/host/lib/rfnoc/replay_block_ctrl_impl.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-//
-// 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 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", DEFAULT_WPP, 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;
- }
-
- void play_halt(const size_t chan)
- {
- sr_write("RX_CTRL_HALT", 1, chan);
- }
-
-
- /***************************************************************************
- * 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 > _params[chan].words_per_packet)
- ? _params[chan].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");