aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authormattprost <matt.prost@ni.com>2020-04-08 16:14:43 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-08-04 15:40:30 -0500
commit73e6eb5ab8d8f745c60f875d2a906f37aea315ef (patch)
tree8d3da58f2b717e9b1f1c3c3c6ca59a545f2a5a56 /host
parentef16cea9bd951a83e8d2818f80d43c80db1beb96 (diff)
downloaduhd-73e6eb5ab8d8f745c60f875d2a906f37aea315ef.tar.gz
uhd-73e6eb5ab8d8f745c60f875d2a906f37aea315ef.tar.bz2
uhd-73e6eb5ab8d8f745c60f875d2a906f37aea315ef.zip
tests: Add Replay Block controller unit test
Signed-off-by: mattprost <matt.prost@ni.com>
Diffstat (limited to 'host')
-rw-r--r--host/tests/CMakeLists.txt5
-rw-r--r--host/tests/rfnoc_block_tests/replay_block_test.cpp759
2 files changed, 764 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 097a5e88c..48c28db1f 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -316,9 +316,14 @@ UHD_ADD_RFNOC_BLOCK_TEST(
)
UHD_ADD_RFNOC_BLOCK_TEST(
+ TARGET replay_block_test.cpp
+)
+
+UHD_ADD_RFNOC_BLOCK_TEST(
TARGET siggen_block_test.cpp
)
+
UHD_ADD_RFNOC_BLOCK_TEST(
TARGET split_stream_block_test.cpp
)
diff --git a/host/tests/rfnoc_block_tests/replay_block_test.cpp b/host/tests/rfnoc_block_tests/replay_block_test.cpp
new file mode 100644
index 000000000..4e4e7847d
--- /dev/null
+++ b/host/tests/rfnoc_block_tests/replay_block_test.cpp
@@ -0,0 +1,759 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "../rfnoc_graph_mock_nodes.hpp"
+#include <uhd/convert.hpp>
+#include <uhd/rfnoc/actions.hpp>
+#include <uhd/rfnoc/defaults.hpp>
+#include <uhd/rfnoc/mock_block.hpp>
+#include <uhd/rfnoc/replay_block_control.hpp>
+#include <uhdlib/rfnoc/graph.hpp>
+#include <uhdlib/rfnoc/node_accessor.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::rfnoc;
+
+// Redeclare this here, since it's only defined outside of UHD_API
+noc_block_base::make_args_t::~make_args_t() = default;
+
+/*
+ * This class extends mock_reg_iface_t by adding a constructor that initializes some of
+ * the read memory to contain the memory size for the replay block. This is important,
+ * because upon construction in software, the Replay Block will read from the
+ * REG_MEM_SIZE_ADDR to determine the word size and memory address width. These constant
+ * read-only values are crucial for the initialization of the other properties.
+ * Additionally, the record fullness is initialized here. This read-only value changes
+ * during recording.
+ */
+class replay_mock_reg_iface_t : public mock_reg_iface_t
+{
+public:
+ replay_mock_reg_iface_t(size_t mem_addr_size, size_t word_size, size_t num_channels)
+ {
+ for (size_t chan = 0; chan < num_channels; chan++) {
+ const uint32_t reg_compat =
+ replay_block_control::REG_COMPAT_ADDR
+ + chan * replay_block_control::REPLAY_BLOCK_OFFSET;
+ read_memory[reg_compat] = (replay_block_control::MINOR_COMPAT
+ | (replay_block_control::MAJOR_COMPAT << 16));
+ }
+ for (size_t chan = 0; chan < num_channels; chan++) {
+ const uint32_t reg_mem_size =
+ replay_block_control::REG_MEM_SIZE_ADDR
+ + chan * replay_block_control::REPLAY_BLOCK_OFFSET;
+ read_memory[reg_mem_size] = (mem_addr_size | (word_size << 16));
+ }
+ for (size_t chan = 0; chan < num_channels; chan++) {
+ const uint32_t reg_rec_fullness =
+ replay_block_control::REG_REC_FULLNESS_LO_ADDR
+ + chan * replay_block_control::REPLAY_BLOCK_OFFSET;
+ read_memory[reg_rec_fullness] = 0x0010;
+ read_memory[reg_rec_fullness + 4] = 0x0000;
+ }
+ }
+};
+
+/*
+ * replay_block_fixture is a class which is instantiated before each test
+ * case is run. It sets up the block container, mock register interface,
+ * and replay_block_control object, all of which are accessible to the test
+ * case. The instance of the object is destroyed at the end of each test
+ * case.
+ */
+constexpr size_t DEFAULT_MTU = 8000;
+
+struct replay_block_fixture
+{
+ replay_block_fixture()
+ : num_channels(4)
+ , num_input_ports(num_channels)
+ , num_output_ports(num_channels)
+ , mem_addr_size(20)
+ , max_buffer_size(1 << mem_addr_size)
+ , default_item_size(4)
+ , word_size(8)
+ , reg_iface(std::make_shared<replay_mock_reg_iface_t>(
+ mem_addr_size, (word_size * 8), num_channels))
+ , block_container(get_mock_block(REPLAY_BLOCK,
+ num_channels,
+ num_channels,
+ uhd::device_addr_t("foo=bar"),
+ DEFAULT_MTU,
+ ANY_DEVICE,
+ reg_iface))
+ , test_replay(block_container.get_block<replay_block_control>())
+ {
+ node_accessor.init_props(test_replay.get());
+ }
+
+ size_t num_channels;
+ size_t num_input_ports;
+ size_t num_output_ports;
+ size_t mem_addr_size;
+ uint64_t max_buffer_size; // in bytes
+ size_t default_item_size; // in bytes
+ size_t word_size; // in bytes
+ std::shared_ptr<replay_mock_reg_iface_t> reg_iface;
+ mock_block_container block_container;
+ std::shared_ptr<replay_block_control> test_replay;
+ node_accessor_t node_accessor{};
+};
+
+inline uint32_t get_addr(uint32_t offset, size_t chan)
+{
+ return offset + chan * replay_block_control::REPLAY_BLOCK_OFFSET;
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly with
+ * defaults when the replay block is constructed.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_construction, replay_block_fixture)
+{
+ BOOST_REQUIRE(test_replay);
+ BOOST_CHECK_EQUAL(test_replay->get_block_args().get("foo"), "bar");
+
+ BOOST_CHECK_EQUAL(test_replay->get_mem_size(), max_buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_word_size(), word_size);
+
+ for (size_t chan = 0; chan < num_channels; chan++) {
+ const uint32_t reg_compat = get_addr(replay_block_control::REG_COMPAT_ADDR, chan);
+ const uint32_t reg_mem_size =
+ get_addr(replay_block_control::REG_MEM_SIZE_ADDR, chan);
+ const uint32_t reg_rec_buff_size =
+ get_addr(replay_block_control::REG_REC_BUFFER_SIZE_LO_ADDR, chan);
+ const uint32_t reg_rec_base_addr =
+ get_addr(replay_block_control::REG_REC_BASE_ADDR_LO_ADDR, chan);
+ const uint32_t reg_play_buff_size =
+ get_addr(replay_block_control::REG_PLAY_BUFFER_SIZE_LO_ADDR, chan);
+ const uint32_t reg_play_base_addr =
+ get_addr(replay_block_control::REG_PLAY_BASE_ADDR_LO_ADDR, chan);
+ const uint32_t reg_words_per_pkt =
+ get_addr(replay_block_control::REG_PLAY_WORDS_PER_PKT_ADDR, chan);
+ const uint32_t reg_play_item_size =
+ get_addr(replay_block_control::REG_PLAY_ITEM_SIZE_ADDR, chan);
+ BOOST_CHECK_EQUAL(reg_iface->read_memory[reg_compat] & 0xFFFF,
+ replay_block_control::MINOR_COMPAT);
+ BOOST_CHECK_EQUAL((reg_iface->read_memory[reg_compat] >> 16) & 0xFFFF,
+ replay_block_control::MAJOR_COMPAT);
+ BOOST_CHECK_EQUAL(reg_iface->read_memory[reg_mem_size] & 0xFFFF, mem_addr_size);
+ BOOST_CHECK_EQUAL(
+ (reg_iface->read_memory[reg_mem_size] >> 16) & 0xFFFF, word_size * 8);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_rec_buff_size], max_buffer_size & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_rec_buff_size + 4],
+ (max_buffer_size >> 32) & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_rec_base_addr], 0);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_rec_base_addr + 4], 0);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_play_buff_size], max_buffer_size & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_buff_size + 4],
+ (max_buffer_size >> 32) & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_base_addr], 0);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_base_addr + 4], 0);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_words_per_pkt],
+ (DEFAULT_MTU - CHDR_MAX_LEN_HDR) / word_size);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_item_size], default_item_size);
+ }
+}
+
+
+/**************************************************************************
+ * Record Buffer tests
+ *************************************************************************/
+
+/*
+ * This test case ensures that the hardware is programmed correctly when a record restart
+ * occurs. Any value written to REG_REC_RESTART_ADDR triggers a record restart.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_record_restart, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_input_ports; port++) {
+ const uint32_t reg_rec_restart =
+ get_addr(replay_block_control::REG_REC_RESTART_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory.count(reg_rec_restart), 0);
+ test_replay->record_restart(port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory.count(reg_rec_restart), 1);
+ }
+}
+
+/*
+ * This test case ensures that the get_record_fullness() API call reads correctly from
+ * hardware.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_record_fullness, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_input_ports; port++) {
+ const uint64_t fullness = 0x123456789ABCDEF0 | port;
+ const uint32_t reg_rec_fullness =
+ get_addr(replay_block_control::REG_REC_FULLNESS_LO_ADDR, port);
+ reg_iface->read_memory[reg_rec_fullness] = fullness & 0xFFFFFFFF;
+ reg_iface->read_memory[reg_rec_fullness + 4] = (fullness >> 32) & 0xFFFFFFFF;
+
+ BOOST_CHECK_EQUAL(test_replay->get_record_fullness(port), fullness);
+ }
+}
+
+/*
+ * This test case ensures that the record type API calls interact correctly.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_record_type, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_input_ports; port++) {
+ // Test the defaults
+ const io_type_t default_type = IO_TYPE_SC16;
+ BOOST_CHECK_EQUAL(test_replay->get_record_type(port), default_type);
+ BOOST_CHECK_EQUAL(test_replay->get_record_item_size(port),
+ uhd::convert::get_bytes_per_item(default_type));
+ }
+
+ for (size_t port = 0; port < num_input_ports; port++) {
+ const io_type_t type = IO_TYPE_U8;
+ test_replay->set_record_type(type, port);
+ BOOST_CHECK_EQUAL(test_replay->get_record_type(port), type);
+ BOOST_CHECK_EQUAL(test_replay->get_record_item_size(port),
+ uhd::convert::get_bytes_per_item(type));
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly when the record buffer
+ * is configured. This includes testing that a record restart takes place. The test
+ * also ensures that any configuration of the base address and buffer size are word
+ * addressable. Additionally, it exercises the get_record_size() and
+ * get_record_offset() API calls.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_record, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_input_ports; port++) {
+ // Test the defaults
+ BOOST_CHECK_EQUAL(test_replay->get_record_size(port), max_buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_record_offset(port), 0);
+ }
+
+ for (size_t port = 0; port < num_input_ports; port++) {
+ const uint32_t reg_rec_restart =
+ get_addr(replay_block_control::REG_REC_RESTART_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory.count(reg_rec_restart), 0);
+
+ const uint64_t base_addr = 16 + max_buffer_size / num_input_ports * port;
+ const uint64_t buffer_size = max_buffer_size / num_input_ports / 2;
+ test_replay->record(base_addr, buffer_size, port);
+
+ const uint32_t reg_rec_buff_size =
+ get_addr(replay_block_control::REG_REC_BUFFER_SIZE_LO_ADDR, port);
+ const uint32_t reg_rec_base_addr =
+ get_addr(replay_block_control::REG_REC_BASE_ADDR_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_rec_buff_size], buffer_size & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_rec_buff_size + 4],
+ (buffer_size >> 32) & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_rec_base_addr], base_addr & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_rec_base_addr + 4],
+ (base_addr >> 32) & 0xFFFFFFFF);
+
+ BOOST_CHECK_EQUAL(test_replay->get_record_size(port), buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_record_offset(port), base_addr);
+ // There should be a record restart on config
+ // (with any value written to the register)
+ BOOST_CHECK_EQUAL(reg_iface->write_memory.count(reg_rec_restart), 1);
+
+ // Valid base address and buffer size values are multiples of the word size
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->record(base_addr + offset, buffer_size, port),
+ uhd::value_error);
+ }
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->record(base_addr, buffer_size + offset, port),
+ uhd::value_error);
+ }
+ // The play buffer must be within the bounds of the Replay memory
+ BOOST_CHECK_THROW(
+ test_replay->record(max_buffer_size, buffer_size, port), uhd::value_error);
+ BOOST_CHECK_THROW(
+ test_replay->record(base_addr, max_buffer_size, port), uhd::value_error);
+ }
+}
+
+/**************************************************************************
+ * Playback tests
+ *************************************************************************/
+
+/*
+ * This test case ensures that the hardware is programmed correctly when the play buffer
+ * is configured. The test also ensures that any configuration of the base address and
+ * buffer size are word addressable. Additionally, it exercises the get_play_size() and
+ * get_play_offset() API calls.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_configure_play, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ // Test the defaults
+ BOOST_CHECK_EQUAL(test_replay->get_play_size(port), max_buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_play_offset(port), 0);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr = word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = max_buffer_size / num_output_ports / 2;
+ test_replay->config_play(base_addr, buffer_size, port);
+
+ const uint32_t reg_play_buff_size =
+ get_addr(replay_block_control::REG_PLAY_BUFFER_SIZE_LO_ADDR, port);
+ const uint32_t reg_play_base_addr =
+ get_addr(replay_block_control::REG_PLAY_BASE_ADDR_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_play_buff_size], buffer_size & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_buff_size + 4],
+ (buffer_size >> 32) & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_play_base_addr], base_addr & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_base_addr + 4],
+ (base_addr >> 32) & 0xFFFFFFFF);
+
+ BOOST_CHECK_EQUAL(test_replay->get_play_size(port), buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_play_offset(port), base_addr);
+
+ // Valid base address and buffer size values are multiples of the word size
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(
+ test_replay->config_play(base_addr + offset, buffer_size, port),
+ uhd::value_error);
+ }
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(
+ test_replay->config_play(base_addr, buffer_size + offset, port),
+ uhd::value_error);
+ }
+ // Valid base address and buffer size values are multiples of the item size for
+ // playback
+ size_t item_size = test_replay->get_play_item_size(port);
+ for (uint64_t offset = 1; offset <= item_size - 1; offset++) {
+ BOOST_CHECK_THROW(
+ test_replay->config_play(base_addr + offset, buffer_size, port),
+ uhd::value_error);
+ }
+ for (uint64_t offset = 1; offset <= item_size - 1; offset++) {
+ BOOST_CHECK_THROW(
+ test_replay->config_play(base_addr, buffer_size + offset, port),
+ uhd::value_error);
+ }
+ // The play buffer must be within the bounds of the Replay memory
+ BOOST_CHECK_THROW(test_replay->config_play(max_buffer_size, buffer_size, port),
+ uhd::value_error);
+ BOOST_CHECK_THROW(
+ test_replay->config_play(base_addr, max_buffer_size, port), uhd::value_error);
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly throough the playback
+ * packet API calls.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_packet_size, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ // Test the defaults
+ const uint32_t default_ipp = DEFAULT_SPP;
+ BOOST_CHECK_EQUAL(test_replay->get_max_items_per_packet(port), default_ipp);
+
+ const uint32_t item_size = test_replay->get_play_item_size(port);
+ const uint32_t default_packet_size = default_ipp * item_size + CHDR_MAX_LEN_HDR;
+ BOOST_CHECK_EQUAL(test_replay->get_max_packet_size(port), default_packet_size);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint32_t ipp = 1024;
+ test_replay->set_max_items_per_packet(ipp, port);
+ BOOST_CHECK_EQUAL(test_replay->get_max_items_per_packet(port), ipp);
+
+ const uint32_t item_size = test_replay->get_play_item_size(port);
+ const uint32_t packet_size = ipp * item_size + CHDR_MAX_LEN_HDR;
+ BOOST_CHECK_EQUAL(test_replay->get_max_packet_size(port), packet_size);
+
+ const uint32_t wpp = ipp * item_size / word_size;
+ const uint32_t reg_words_per_pkt =
+ get_addr(replay_block_control::REG_PLAY_WORDS_PER_PKT_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_words_per_pkt], wpp);
+ }
+}
+
+/*
+ * This test case ensures that the play type and item size API calls interact correctly
+ * and program the hardware.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_play_type, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ // Test the defaults
+ const io_type_t default_type = IO_TYPE_SC16;
+ BOOST_CHECK_EQUAL(test_replay->get_play_type(port), default_type);
+ BOOST_CHECK_EQUAL(test_replay->get_play_item_size(port),
+ uhd::convert::get_bytes_per_item(default_type));
+ const uint32_t reg_play_item_size =
+ get_addr(replay_block_control::REG_PLAY_ITEM_SIZE_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_item_size],
+ uhd::convert::get_bytes_per_item(default_type));
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const io_type_t type = IO_TYPE_U8;
+ test_replay->set_play_type(type, port);
+ BOOST_CHECK_EQUAL(test_replay->get_play_type(port), type);
+ BOOST_CHECK_EQUAL(test_replay->get_play_item_size(port),
+ uhd::convert::get_bytes_per_item(type));
+ const uint32_t reg_play_item_size =
+ get_addr(replay_block_control::REG_PLAY_ITEM_SIZE_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_item_size],
+ uhd::convert::get_bytes_per_item(type));
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly when a stream command
+ * is issued. Note that there is not a distinction between STREAM_MODE_NUM_SAMPS_AND_DONE
+ * and STREAM_MODE_NUM_SAMPS_AND_MORE in USRP3 devices and newer.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_issue_stream_cmd, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_stop(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_STOP_CONTINUOUS);
+ test_replay->issue_stream_cmd(cmd_stop, port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_stream_cmd], replay_block_control::PLAY_CMD_STOP);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_cont(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_START_CONTINUOUS);
+ test_replay->issue_stream_cmd(cmd_cont, port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_CONTINUOUS);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_finite(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
+ const uint64_t num_words = 0x00123ABC;
+ cmd_finite.num_samps = num_words * word_size / 4;
+ test_replay->issue_stream_cmd(cmd_finite, port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_finite(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_MORE);
+ const uint64_t num_words = 0x00DEF456;
+ cmd_finite.num_samps = num_words * word_size / 4;
+ test_replay->issue_stream_cmd(cmd_finite, port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly when a stream command
+ * is issued with delay. Note that there is not a distinction between
+ * STREAM_MODE_NUM_SAMPS_AND_DONE and STREAM_MODE_NUM_SAMPS_AND_MORE in USRP3 devices and
+ * newer.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_issue_stream_cmd_timed, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_cont(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_START_CONTINUOUS);
+ cmd_cont.stream_now = false;
+ test_replay->issue_stream_cmd(cmd_cont, port);
+
+ const uint32_t cmd_time_mask = 1 << 31;
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_CONTINUOUS | cmd_time_mask);
+
+ const uint64_t num_ticks = 0;
+ const uint32_t reg_cmd_time =
+ get_addr(replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_cmd_time], num_ticks & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_cmd_time + 4], (num_ticks >> 32) & 0xFFFFFFFF);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_finite(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
+ const uint64_t num_words = 0x00123ABC;
+ cmd_finite.num_samps = num_words * word_size / 4;
+ cmd_finite.stream_now = false;
+ test_replay->issue_stream_cmd(cmd_finite, port);
+
+ const uint32_t cmd_time_mask = 1 << 31;
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE | cmd_time_mask);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+
+ const uint64_t num_ticks = 0;
+ const uint32_t reg_cmd_time =
+ get_addr(replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_cmd_time], num_ticks & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_cmd_time + 4], (num_ticks >> 32) & 0xFFFFFFFF);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ uhd::stream_cmd_t cmd_finite(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_MORE);
+ const uint64_t num_words = 0x00DEF456;
+ cmd_finite.num_samps = num_words * word_size / 4;
+ cmd_finite.stream_now = false;
+ test_replay->issue_stream_cmd(cmd_finite, port);
+
+ const uint32_t cmd_time_mask = 1 << 31;
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE | cmd_time_mask);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+
+ const uint64_t num_ticks = 0;
+ const uint32_t reg_cmd_time =
+ get_addr(replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_cmd_time], num_ticks & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_cmd_time + 4], (num_ticks >> 32) & 0xFFFFFFFF);
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly when a stop command is
+ * issued to the replay block via an API call.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_stop, replay_block_fixture)
+{
+ for (size_t port = 0; port < num_output_ports; port++) {
+ test_replay->stop(port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_stream_cmd], replay_block_control::PLAY_CMD_STOP);
+ }
+}
+
+/*
+ * This test case ensures that the hardware is programmed correctly when the record buffer
+ * is configured. This includes testing that a record restart takes place. The test
+ * also ensures that any configuration of the base address and buffer size are word
+ * addressable. Additionally, it exercises the get_play_size() and
+ * get_play_offset() API calls.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_play, replay_block_fixture)
+{
+ // Configure play buffer
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr = word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = max_buffer_size / num_output_ports / 2;
+ test_replay->play(base_addr, buffer_size, port);
+
+ const uint32_t reg_play_buff_size =
+ get_addr(replay_block_control::REG_PLAY_BUFFER_SIZE_LO_ADDR, port);
+ const uint32_t reg_play_base_addr =
+ get_addr(replay_block_control::REG_PLAY_BASE_ADDR_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_play_buff_size], buffer_size & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_buff_size + 4],
+ (buffer_size >> 32) & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_play_base_addr], base_addr & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_play_base_addr + 4],
+ (base_addr >> 32) & 0xFFFFFFFF);
+
+ BOOST_CHECK_EQUAL(test_replay->get_play_size(port), buffer_size);
+ BOOST_CHECK_EQUAL(test_replay->get_play_offset(port), base_addr);
+
+ // Valid base address and buffer size values are multiples of the word size
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->play(base_addr + offset, buffer_size, port),
+ uhd::value_error);
+ }
+ for (uint64_t offset = 1; offset <= word_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->play(base_addr, buffer_size + offset, port),
+ uhd::value_error);
+ }
+ // Valid base address and buffer size values are multiples of the item size for
+ // playback
+ size_t item_size = test_replay->get_play_item_size(port);
+ for (uint64_t offset = 1; offset <= item_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->play(base_addr + offset, buffer_size, port),
+ uhd::value_error);
+ }
+ for (uint64_t offset = 1; offset <= item_size - 1; offset++) {
+ BOOST_CHECK_THROW(test_replay->play(base_addr, buffer_size + offset, port),
+ uhd::value_error);
+ }
+ // The play buffer must be within the bounds of the Replay memory
+ BOOST_CHECK_THROW(
+ test_replay->play(max_buffer_size, buffer_size, port), uhd::value_error);
+ BOOST_CHECK_THROW(
+ test_replay->play(base_addr, max_buffer_size, port), uhd::value_error);
+ }
+
+ // Non-timed commands
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr =
+ 2 * word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = max_buffer_size / num_output_ports / 4;
+ uhd::stream_cmd_t cmd_cont(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_START_CONTINUOUS);
+ test_replay->play(base_addr, buffer_size, port, 0.0, true);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_CONTINUOUS);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr =
+ 3 * word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = 0x1230;
+ test_replay->play(base_addr, buffer_size, port);
+
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint64_t num_words = buffer_size / word_size;
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+ }
+
+ // Timed Commands
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr =
+ 4 * word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = max_buffer_size / num_output_ports / 4;
+ test_replay->play(base_addr, buffer_size, port, 1.23, true);
+
+ const uint32_t cmd_time_mask = 1 << 31;
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_CONTINUOUS | cmd_time_mask);
+
+ const uint64_t num_ticks = 0;
+ const uint32_t reg_cmd_time =
+ get_addr(replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_cmd_time], num_ticks & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_cmd_time + 4], (num_ticks >> 32) & 0xFFFFFFFF);
+ }
+
+ for (size_t port = 0; port < num_output_ports; port++) {
+ const uint64_t base_addr =
+ 5 * word_size + max_buffer_size / num_output_ports * port;
+ const uint64_t buffer_size = 0xABC0;
+ test_replay->play(base_addr, buffer_size, port, 4.56, false);
+
+ const uint32_t cmd_time_mask = 1 << 31;
+ const uint32_t reg_stream_cmd =
+ get_addr(replay_block_control::REG_PLAY_CMD_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_stream_cmd],
+ replay_block_control::PLAY_CMD_FINITE | cmd_time_mask);
+ // PLAY_CMD_FINITE writes the number of words to hardware
+ const uint64_t num_words = buffer_size / word_size;
+ const uint32_t reg_num_words =
+ get_addr(replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_num_words], num_words & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_num_words + 4], (num_words >> 32) & 0xFFFFFFFF);
+
+ const uint64_t num_ticks = 0;
+ const uint32_t reg_cmd_time =
+ get_addr(replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR, port);
+ BOOST_CHECK_EQUAL(reg_iface->write_memory[reg_cmd_time], num_ticks & 0xFFFFFFFF);
+ BOOST_CHECK_EQUAL(
+ reg_iface->write_memory[reg_cmd_time + 4], (num_ticks >> 32) & 0xFFFFFFFF);
+ }
+}
+
+/*
+ * This test case ensures that the Replay Block can be added to an RFNoC graph.
+ */
+BOOST_FIXTURE_TEST_CASE(replay_test_graph, replay_block_fixture)
+{
+ detail::graph_t graph{};
+ detail::graph_t::graph_edge_t edge_port_info;
+ edge_port_info.src_port = 0;
+ edge_port_info.dst_port = 0;
+ edge_port_info.property_propagation_active = true;
+ edge_port_info.edge = detail::graph_t::graph_edge_t::DYNAMIC;
+
+ mock_radio_node_t mock_radio_block{0};
+ mock_ddc_node_t mock_ddc_block{};
+ mock_terminator_t mock_sink_term(1, {}, "MOCK_SINK");
+
+ UHD_LOG_INFO("TEST", "Priming mock block properties");
+ node_accessor.init_props(&mock_radio_block);
+ node_accessor.init_props(&mock_ddc_block);
+ mock_sink_term.set_edge_property<std::string>(
+ "type", "sc16", {res_source_info::INPUT_EDGE, 0});
+ mock_sink_term.set_edge_property<std::string>(
+ "type", "sc16", {res_source_info::INPUT_EDGE, 1});
+
+ UHD_LOG_INFO("TEST", "Creating graph...");
+ graph.connect(&mock_radio_block, &mock_ddc_block, edge_port_info);
+ graph.connect(&mock_ddc_block, test_replay.get(), edge_port_info);
+ graph.connect(test_replay.get(), &mock_sink_term, edge_port_info);
+ UHD_LOG_INFO("TEST", "Committing graph...");
+ graph.commit();
+ UHD_LOG_INFO("TEST", "Commit complete.");
+}