diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2019-05-07 17:33:36 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 11:49:16 -0800 |
commit | cd36d9d2d3dfdecae42b715d734547e48347cf4a (patch) | |
tree | a80ad2f1d44da87887fb7c21bb5fc2835a7651e7 /host/tests | |
parent | 53becb55e1211a85b27a3d862afec4e7ffd0818e (diff) | |
download | uhd-cd36d9d2d3dfdecae42b715d734547e48347cf4a.tar.gz uhd-cd36d9d2d3dfdecae42b715d734547e48347cf4a.tar.bz2 uhd-cd36d9d2d3dfdecae42b715d734547e48347cf4a.zip |
rfnoc: adding client_zero
- Adding client_zero class, which gathers information about our device
form the global registers on port 0 of the RFNoC backend registers.
- adding unit tests to exercise client_zero
- mock_reg_iface class: adding fake register_iface so we can run
unit tests in software only
Co-authored-by: Martin Braun <martin.braun@ettus.com>
Diffstat (limited to 'host/tests')
-rw-r--r-- | host/tests/CMakeLists.txt | 5 | ||||
-rw-r--r-- | host/tests/client_zero_test.cpp | 224 | ||||
-rw-r--r-- | host/tests/rfnoc_mock_reg_iface.hpp | 124 |
3 files changed, 353 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index b6ce21173..88b673bb1 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -235,6 +235,11 @@ UHD_ADD_NONAPI_TEST( ${CMAKE_SOURCE_DIR}/lib/rfnoc/chdr/ ) +UHD_ADD_NONAPI_TEST( + TARGET client_zero_test.cpp + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/rfnoc/client_zero.cpp +) ######################################################################## # demo of a loadable module diff --git a/host/tests/client_zero_test.cpp b/host/tests/client_zero_test.cpp new file mode 100644 index 000000000..d7f50ec52 --- /dev/null +++ b/host/tests/client_zero_test.cpp @@ -0,0 +1,224 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/rfnoc/register_iface.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/client_zero.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <unordered_map> +#include <boost/test/unit_test.hpp> +#include <cstdint> +#include <cstring> +#include <memory> + +#include "rfnoc_mock_reg_iface.hpp" + +using namespace uhd; +using namespace uhd::rfnoc; + + +class client_zero_test_iface : public mock_reg_iface_t +{ +public: + client_zero_test_iface(uint16_t device_id) : mock_reg_iface_t() + { + read_memory[0x0000] = (0x12C6 << 16) | GLOBAL_PROTOVER; + read_memory[0x0008] = NUM_EDGES; + read_memory[0x0004] = (STATIC_ROUTER_PRESENT << 31) | (CHDR_XBAR_PRESENT << 30) + | (NUM_XPORTS << 20) | ((NUM_BLOCKS & 0X3FF) << 10) + | NUM_STREAM_ENDPOINTS; + read_memory[0x000C] = device_id | (DEVICE_TYPE << 16); + } + + virtual ~client_zero_test_iface() = default; + + /************************************************************************** + * Test API + *************************************************************************/ + size_t register_block(const size_t num_in, + const size_t num_out, + const size_t ctrl_fifo_size, + const size_t mtu, + const uint32_t noc_id) + { + const uint32_t block_offset = + (1 + NUM_STREAM_ENDPOINTS + num_registered_blocks) * SLOT_OFFSET; + read_memory[block_offset] = BLOCK_PROTOVER | (num_in & 0x3F) << 8 + | (num_out & 0x3F) << 14 + | (ctrl_fifo_size & 0x3F) << 20 | (mtu & 0x3F) << 26; + read_memory[block_offset + 1] = noc_id; + read_memory[block_offset + 2] = 0x2; // flush flags: active is low, done is high + num_registered_blocks++; + set_port_cnt_reg(num_registered_blocks); + return num_registered_blocks - 1; + } + + void add_connection( + uint16_t src_blk, uint8_t src_port, uint16_t dst_blk, uint8_t dst_port) + { + num_connections++; + read_memory[EDGE_TABLE_OFFSET] = num_connections & 0x3FF; + read_memory[EDGE_TABLE_OFFSET + num_connections * 4] = + (src_blk & 0x3F) << 22 | (src_port & 0x3F) << 16 | (dst_blk & 0x3FF) << 6 + | (dst_port & 0x3F) << 0; + read_memory[0x0008] = num_connections; + } + + void set_port_cnt_reg(uint16_t num_blocks) + { + read_memory[0x0004] = (STATIC_ROUTER_PRESENT << 31) | (CHDR_XBAR_PRESENT << 30) + | (NUM_XPORTS << 20) | ((num_blocks & 0x3FF) << 10) + | NUM_STREAM_ENDPOINTS; + } + + void _poke_cb(uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) + { + if (addr < (1 + NUM_STREAM_ENDPOINTS) * SLOT_OFFSET + || (addr % SLOT_OFFSET != 0 && addr % SLOT_OFFSET != 4)) { + UHD_LOG_WARNING("MOCK_REG_IFACE", + "Client Zero only requires pokes to block flush and reset addresses! " + "Address " + << addr << " is not supposed to be poked."); + UHD_THROW_INVALID_CODE_PATH(); + } + + if ((addr & SLOT_OFFSET) == 0) { + UHD_LOG_INFO("MOCK_REG_IFACE", "Set flush timeout to " << data); + last_flush_timeout = data; + return; + } + if ((addr & SLOT_OFFSET) == 1) { + UHD_LOG_INFO("MOCK_REG_IFACE", + "Set flush/reset bits to flush=" << (data & 0x1) << ",ctrl_rst=" + << ((data >> 1) & 0x1 >> 1) + << ",chdr_rst=" << ((data >> 2) & 0x1)); + } + } + + void _peek_cb(uint32_t addr, time_spec_t /*time*/) + { + if (read_memory.count(addr) == 0) { + std::cout << "Bad peek32, addr=" << addr << std::endl; + throw uhd::index_error("Bad peek32 in unittest"); + } + } + + uint32_t last_flush_timeout = 0; + + /************************************************************************** + * Memory banks and defaults + *************************************************************************/ + uint16_t GLOBAL_PROTOVER = 23; // 16 bits + uint8_t BLOCK_PROTOVER = 42; // 8 bits + uint8_t STATIC_ROUTER_PRESENT = 1; // 1 bit + uint8_t CHDR_XBAR_PRESENT = 1; // 1 bit + uint16_t NUM_XPORTS = 3 & 0x3FF; // 10 bits + uint16_t NUM_STREAM_ENDPOINTS = 2 & 0x3FF; // 10 bits + uint16_t NUM_EDGES = 6 & 0xFFF; // 12 bits, just a default + uint16_t NUM_BLOCKS = 4 & 0xFFF; // 12 bits, just a default + uint16_t DEVICE_TYPE = 0xABCD; + + static constexpr uint32_t SLOT_OFFSET = 512 / 8; // 512 bits per slot + static constexpr uint32_t EDGE_TABLE_OFFSET = 0x10000; + +private: + size_t num_registered_blocks = 0; + uint32_t num_connections = 0; +}; + +constexpr uint32_t client_zero_test_iface::EDGE_TABLE_OFFSET; +constexpr uint32_t client_zero_test_iface::SLOT_OFFSET; + + +BOOST_AUTO_TEST_CASE(simple_read_if_chdr_pkt) +{ + constexpr uint16_t DEVICE_ID = 0xBEEF; + constexpr uint16_t CTRL_FIFO_SIZE = 5; // in words + constexpr uint16_t MTU = 40; // FIXME in words? + auto mock_reg_iface = std::make_shared<client_zero_test_iface>(DEVICE_ID); + + // Prime the pump: We add some blocks and connections + size_t sep0_id = 0; + size_t sep1_id = 1; + size_t radio0_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0x12AD1000); + size_t ddc0_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0xDDC00000); + size_t duc0_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0xD11C0000); + size_t radio1_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0x12AD1000); + size_t ddc1_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0xDDC00000); + size_t duc1_id = + mock_reg_iface->register_block(1, 1, CTRL_FIFO_SIZE, MTU, 0xD11C0000); + // Connect SEP -> DUC -> RADIO -> DDC -> SEP + mock_reg_iface->add_connection(sep0_id, 0, duc0_id, 0); + mock_reg_iface->add_connection(duc0_id, 0, radio0_id, 0); + mock_reg_iface->add_connection(radio0_id, 0, ddc0_id, 0); + mock_reg_iface->add_connection(ddc0_id, 0, sep0_id, 0); + mock_reg_iface->add_connection(sep1_id, 0, duc1_id, 0); + mock_reg_iface->add_connection(duc1_id, 0, radio1_id, 0); + mock_reg_iface->add_connection(radio1_id, 0, ddc1_id, 0); + mock_reg_iface->add_connection(ddc1_id, 0, sep1_id, 0); + constexpr size_t num_edges = 8; // Number of lines above + + auto mock_client0 = std::make_shared<uhd::rfnoc::detail::client_zero>(mock_reg_iface); + + BOOST_CHECK_EQUAL(mock_client0->get_proto_ver(), mock_reg_iface->GLOBAL_PROTOVER); + BOOST_CHECK_EQUAL(mock_client0->get_device_type(), mock_reg_iface->DEVICE_TYPE); + BOOST_CHECK_EQUAL(mock_client0->get_num_blocks(), 6); + BOOST_CHECK_EQUAL( + mock_client0->get_num_stream_endpoints(), mock_reg_iface->NUM_STREAM_ENDPOINTS); + BOOST_CHECK_EQUAL(mock_client0->get_num_transports(), mock_reg_iface->NUM_XPORTS); + BOOST_CHECK_EQUAL( + mock_client0->has_chdr_crossbar(), mock_reg_iface->CHDR_XBAR_PRESENT); + BOOST_CHECK_EQUAL(mock_client0->get_num_edges(), num_edges); + + auto adj_list = mock_client0->get_adjacency_list(); + BOOST_CHECK_EQUAL(adj_list.size(), num_edges); + auto edge0 = adj_list.at(0); + BOOST_CHECK_EQUAL(edge0.src_blk_index, sep0_id); + BOOST_CHECK_EQUAL(edge0.src_blk_port, 0); + BOOST_CHECK_EQUAL(edge0.dst_blk_index, duc0_id); + BOOST_CHECK_EQUAL(edge0.dst_blk_port, 0); + + // get_noc_id() + BOOST_CHECK_THROW(mock_client0->get_noc_id(0), uhd::index_error); // Client0 + BOOST_CHECK_THROW(mock_client0->get_noc_id(1), uhd::index_error); // sep0 + BOOST_CHECK_THROW(mock_client0->get_noc_id(2), uhd::index_error); // sep1 + // Check NOC IDs of all of our blocks + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(3), 0x12AD1000); + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(4), 0xDDC00000); + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(5), 0xD11C0000); + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(6), 0x12AD1000); + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(7), 0xDDC00000); + BOOST_CHECK_EQUAL(mock_client0->get_noc_id(8), 0xD11C0000); + // Check an out-of-bounds query + BOOST_CHECK_THROW(mock_client0->get_noc_id(9), uhd::index_error); + + // Flush flags: by default, we set active is low, done is high + BOOST_CHECK_EQUAL(mock_client0->get_flush_active(3), false); + BOOST_CHECK_EQUAL(mock_client0->get_flush_done(3), true); + // Flushing and Reset + BOOST_CHECK_THROW(mock_client0->set_flush(0), uhd::index_error); + BOOST_CHECK_NO_THROW(mock_client0->set_flush(3)); + BOOST_CHECK_THROW(mock_client0->set_flush(9), uhd::index_error); + BOOST_CHECK_THROW(mock_client0->reset_ctrl(0), uhd::index_error); + BOOST_CHECK_NO_THROW(mock_client0->reset_ctrl(3)); + BOOST_CHECK_THROW(mock_client0->reset_ctrl(9), uhd::index_error); + BOOST_CHECK_THROW(mock_client0->reset_chdr(0), uhd::index_error); + BOOST_CHECK_NO_THROW(mock_client0->reset_chdr(3)); + BOOST_CHECK_THROW(mock_client0->reset_chdr(9), uhd::index_error); + + // Block Config + auto mock_config = mock_client0->get_block_info(3); + BOOST_CHECK_EQUAL(mock_config.protover, mock_reg_iface->BLOCK_PROTOVER); + BOOST_CHECK_EQUAL(mock_config.num_inputs, 1); + BOOST_CHECK_EQUAL(mock_config.num_outputs, 1); + BOOST_CHECK_EQUAL(mock_config.ctrl_fifo_size, CTRL_FIFO_SIZE); + BOOST_CHECK_EQUAL(mock_config.mtu, MTU); +} diff --git a/host/tests/rfnoc_mock_reg_iface.hpp b/host/tests/rfnoc_mock_reg_iface.hpp new file mode 100644 index 000000000..3ceb95893 --- /dev/null +++ b/host/tests/rfnoc_mock_reg_iface.hpp @@ -0,0 +1,124 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_TESTS_MOCK_REG_IFACE_HPP +#define INCLUDED_LIBUHD_TESTS_MOCK_REG_IFACE_HPP + +#include <uhd/rfnoc/register_iface.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/types/time_spec.hpp> +#include <boost/format.hpp> +#include <unordered_map> +#include <vector> + +class mock_reg_iface_t : public uhd::rfnoc::register_iface +{ +public: + mock_reg_iface_t() = default; + virtual ~mock_reg_iface_t() = default; + + /************************************************************************** + * API + *************************************************************************/ + void poke32(uint32_t addr, uint32_t data, uhd::time_spec_t time, bool ack) + { + write_memory[addr] = data; + _poke_cb(addr, data, time, ack); + } + + void multi_poke32(const std::vector<uint32_t> addrs, + const std::vector<uint32_t> data, + uhd::time_spec_t time, + bool ack) + { + if (addrs.size() != data.size()) { + throw uhd::value_error("addrs and data vectors must be of the same length"); + } + for (size_t i = 0; i < addrs.size(); i++) { + poke32(addrs[i], data[i], time, ack); + } + } + + void block_poke32(uint32_t first_addr, + const std::vector<uint32_t> data, + uhd::time_spec_t timestamp, + bool ack) + { + for (size_t i = 0; i < data.size(); i++) { + poke32(first_addr + 4 * i, data[i], timestamp, ack); + } + } + + uint32_t peek32(uint32_t addr, uhd::time_spec_t time) + { + _peek_cb(addr, time); + try { + return read_memory.at(addr); + } catch (const std::out_of_range&) { + throw uhd::runtime_error(str(boost::format("No data defined for address: 0x%04X") % addr)); + } + } + + std::vector<uint32_t> block_peek32( + uint32_t first_addr, size_t length, uhd::time_spec_t time) + { + std::vector<uint32_t> result(length, 0); + for (size_t i = 0; i < length; ++i) { + result[i] = peek32(first_addr + i * 4, time); + } + return result; + } + + void poll32(uint32_t addr, + uint32_t data, + uint32_t mask, + uhd::time_spec_t /* timeout */, + uhd::time_spec_t time = uhd::time_spec_t::ASAP, + bool /* ack */ = false) + { + if (force_timeout) { + throw uhd::op_timeout("timeout"); + } + + if ((peek32(addr, time) & mask) == data) { + UHD_LOG_INFO("MOCK_REG_IFACE", "poll32() successful at addr " << addr); + } else { + UHD_LOG_INFO("MOCK_REG_IFACE", "poll32() not successful at addr " << addr); + } + } + + void sleep(uhd::time_spec_t /*duration*/, bool /*ack*/) + { + // nop + } + + void register_async_msg_handler(async_msg_callback_t /*callback_f*/) + { + // nop + } + + void set_policy(const std::string& name, const uhd::device_addr_t& args) + { + UHD_LOG_INFO("MOCK_REG_IFACE", + "Requested to set policy for " << name << " to " << args.to_string()); + } + + + bool force_timeout = false; + + std::unordered_map<uint32_t, uint32_t> read_memory; + std::unordered_map<uint32_t, uint32_t> write_memory; + +protected: + virtual void _poke_cb(uint32_t /*addr*/, uint32_t /*data*/, uhd::time_spec_t /*time*/, bool /*ack*/) + { + } + virtual void _peek_cb(uint32_t /*addr*/, uhd::time_spec_t /*time*/) {} +}; + + +#endif /* INCLUDED_LIBUHD_TESTS_MOCK_REG_IFACE_HPP */ + |