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."); +} | 
