diff options
-rw-r--r-- | host/include/uhd/rfnoc/defaults.hpp | 3 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/replay_block_control.hpp | 324 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/replay_block_control.cpp | 528 | ||||
-rw-r--r-- | host/lib/rfnoc/replay_block_ctrl_impl.cpp | 201 |
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"); |