diff options
author | mattprost <matt.prost@ni.com> | 2020-04-08 16:14:43 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-08-04 15:40:30 -0500 |
commit | 73e6eb5ab8d8f745c60f875d2a906f37aea315ef (patch) | |
tree | 8d3da58f2b717e9b1f1c3c3c6ca59a545f2a5a56 /host | |
parent | ef16cea9bd951a83e8d2818f80d43c80db1beb96 (diff) | |
download | uhd-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.txt | 5 | ||||
-rw-r--r-- | host/tests/rfnoc_block_tests/replay_block_test.cpp | 759 |
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."); +} |