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 | |
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')
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/client_zero.hpp | 199 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/client_zero.cpp | 213 | ||||
-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 |
6 files changed, 766 insertions, 0 deletions
diff --git a/host/lib/include/uhdlib/rfnoc/client_zero.hpp b/host/lib/include/uhdlib/rfnoc/client_zero.hpp new file mode 100644 index 000000000..8eb2d6397 --- /dev/null +++ b/host/lib/include/uhdlib/rfnoc/client_zero.hpp @@ -0,0 +1,199 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_CLIENT_ZERO_HPP +#define INCLUDED_LIBUHD_CLIENT_ZERO_HPP + +#include <uhd/rfnoc/register_iface_holder.hpp> +#include <chrono> +#include <cstdint> +#include <string> +#include <vector> + +namespace uhd { namespace rfnoc { namespace detail { + +/*! + * Class that uses a register_iface to read important configuration information from the + * RFNoC backend registers + */ +class client_zero : public uhd::rfnoc::register_iface_holder +{ +public: + client_zero(register_iface::sptr reg); + + //! Definition of an edge in the static router + struct edge_def_t + { + uint16_t src_blk_index; + uint8_t src_blk_port; + uint16_t dst_blk_index; + uint8_t dst_blk_port; + }; + + //! Contents of the backend status block configuration register + struct block_config_info + { + uint8_t protover; + uint8_t num_inputs; + uint8_t num_outputs; + uint8_t ctrl_fifo_size; + uint8_t mtu; + }; + + //! Return the RFNoC protocol version for this motherboard + uint16_t get_proto_ver() + { + return _proto_ver; + }; + + //! Return the device type + uint16_t get_device_type() + { + return _device_type; + }; + + //! Return the number of blocks in our RFNoC graph + size_t get_num_blocks() + { + return _num_blocks; + }; + + //! Return the number of stream endpoints in our RFNoC graph + size_t get_num_stream_endpoints() + { + return _num_stream_endpoints; + }; + + //! Return the number of transports available + size_t get_num_transports() + { + return _num_transports; + }; + + //! Return whether or not the device includes a CHDR crossbar + bool has_chdr_crossbar() + { + return _has_chdr_crossbar; + }; + + //! Return the number of edges in our graph (the number of static connections) + size_t get_num_edges() + { + return _num_edges; + }; + + //! Return a vector containing the edge definitions + std::vector<edge_def_t>& get_adjacency_list() + { + return _adjacency_list; + }; + + /*! Return the NOC ID of the block located at `portno` + * + * \throws uhd::index_error if no NOC block is connected to the port + */ + uint32_t get_noc_id(uint16_t portno); + + /*! Return whether the port is actively flushing + * + * \throws uhd::index_error if no NOC block is connected to the port + * \return boolean status + */ + bool get_flush_active(uint16_t portno); + + /*! Return whether the port is done flushing + * + * \throws uhd::index_error if no NOC block is connected to the port + * \return boolean status + */ + bool get_flush_done(uint16_t portno); + + /*! Returns once the port is done flushing + * + * Note: this function queries the port once every millisecond + * + * \param portno Port number + * \param timeout time, in milliseconds, to poll before quitting + * \throws uhd::index_error if no NOC block is connected to the port + * \return boolean whether or not the flush had completed in the timeout period + */ + bool poll_flush_done(uint16_t portno, std::chrono::milliseconds timeout); + + /*! Set the port's hardware flush timeout + * + * \param timeout number of cycles the device waits for the flushing to complete + * \param portno Port number + * \throws uhd::index_error if no NOC block is connected to the port + */ + void set_flush_timeout(uint32_t timeout, uint16_t portno); + + /*! Send a request to flush a port + * + * \param portno Port number + * \throws uhd::index_error if no NOC block is connected to the port + */ + void set_flush(uint16_t portno); + + /*! Go through the entire flush process for a port + * + * \param portno Port number + * \throws uhd::index_error if no NOC block is connected to the port + * \return whether or not the flush succeeded + */ + bool complete_flush(uint16_t portno); + + /*! Reset a port's control logic + * + * It is recommended to flush a port calling this. + * + * \param portno Port number + * \throws uhd::index_error if no NOC block is connected to the port + */ + void reset_ctrl(uint16_t portno); + + /*! Reset a port's CHDR logic + * + * It is recommended to flush a port calling this. + * + * \param portno Port number + * \throws uhd::index_error if no NOC block is connected to the port + */ + void reset_chdr(uint16_t portno); + + /*! Get the port's configuration information + * + * \return Struct containing configuration information + */ + block_config_info get_block_info(uint16_t portno); + + // TODO: handle callbacks? + +private: + uint16_t _proto_ver; + uint16_t _device_type; + uint16_t _num_blocks; + uint16_t _num_stream_endpoints; + uint16_t _num_transports; + bool _has_chdr_crossbar; + uint16_t _num_edges; + std::vector<edge_def_t> _adjacency_list; + + std::vector<client_zero::edge_def_t> _get_adjacency_list(); + + /* Helper function to determine if the given port number has a block connected + * + * \throws uhd::index_error if no NOC block is connected to the port + */ + void _check_port_number(uint16_t portno); + //! Translate port number to base address for the register + uint32_t _get_port_base_addr(uint16_t portno); + //! Helper function to get the backend control flush status flags + uint32_t _get_flush_status_flags(uint16_t portno); +}; + +}}} /* namespace uhd::rfnoc::detail */ + +#endif /* INCLUDED_LIBUHD_CLIENT_ZERO_HPP */ diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index 0d989b83b..2c13c1819 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -19,6 +19,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/blockdef_xml_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/client_zero.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/graph.cpp diff --git a/host/lib/rfnoc/client_zero.cpp b/host/lib/rfnoc/client_zero.cpp new file mode 100644 index 000000000..91140f9e8 --- /dev/null +++ b/host/lib/rfnoc/client_zero.cpp @@ -0,0 +1,213 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/types/time_spec.hpp> +#include <uhdlib/rfnoc/client_zero.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <string> +#include <thread> + +using namespace uhd::rfnoc; +using namespace uhd::rfnoc::detail; +using namespace std::chrono_literals; + +namespace { +constexpr std::chrono::milliseconds DEFAULT_POLL_TIMEOUT = 1000ms; +constexpr std::chrono::milliseconds DEFAULT_POLL_PERIOD = 10ms; +constexpr uint32_t DEFAULT_FLUSH_TIMEOUT = 100; // num cycles (hardware-timed) + +// Read Register Addresses +//! Register address of the protocol version +constexpr int PROTOVER_ADDR = 0 * 4; +//! Register address of the port information +constexpr int PORT_CNT_ADDR = 1 * 4; +//! Register address of the edge information +constexpr int EDGE_CNT_ADDR = 2 * 4; +//! Register address of the device information +constexpr int DEVICE_INFO_ADDR = 3 * 4; +//! (Write) Register address of the flush and reset controls +constexpr int FLUSH_RESET_ADDR = 1 * 4; + +//! Base address of the adjacency list +constexpr size_t ADJACENCY_BASE_ADDR = 0x10000; +//! Each port is allocated this many registers in the backend register space +constexpr size_t REGS_PER_PORT = 16; +} // namespace + +client_zero::client_zero(register_iface::sptr reg) + : uhd::rfnoc::register_iface_holder(reg) +{ + // The info we need is static, so we can read it all up front, and store the + // parsed information. + const uint32_t proto_reg_val = regs().peek32(PROTOVER_ADDR); + const uint32_t port_reg_val = regs().peek32(PORT_CNT_ADDR); + const uint32_t edge_reg_val = regs().peek32(EDGE_CNT_ADDR); + const uint32_t device_info_reg_val = regs().peek32(DEVICE_INFO_ADDR); + + // Parse the PROTOVER_ADDR register + _proto_ver = proto_reg_val & 0xFFFF; + + // Parse the PORT_CNT_ADDR register + _has_chdr_crossbar = bool(port_reg_val & (1 << 31)); + _num_transports = uhd::narrow_cast<uint16_t>((port_reg_val & 0x3FF00000) >> 20); + _num_blocks = uhd::narrow_cast<uint16_t>((port_reg_val & 0x000FFC00) >> 10); + _num_stream_endpoints = uhd::narrow_cast<uint16_t>((port_reg_val & 0x000003FF)); + + // Parse the EDGE_CNT_ADDR register + // The only non-zero entry here is _num_edges + _num_edges = edge_reg_val; + + // Parse the DEVICE_INFO_ADDR register + _device_type = (device_info_reg_val & 0xFFFF0000) >> 16; + + // Read the adjacency list + _adjacency_list = _get_adjacency_list(); + + // Set the default flushing timeout for each block + for (uint16_t portno = 1 + get_num_stream_endpoints(); + portno < (get_num_blocks() + get_num_stream_endpoints()); + ++portno) { + set_flush_timeout(DEFAULT_FLUSH_TIMEOUT, portno); + } +} + +//! Helper function to read the adjacency list +std::vector<client_zero::edge_def_t> client_zero::_get_adjacency_list() +{ + // Read the header, which includes the number of entries in the list + size_t num_entries = regs().peek32(ADJACENCY_BASE_ADDR) & 0x3FFF; + + // Construct the adjacency list by iterating through and reading each entry + std::vector<edge_def_t> adj_list; + adj_list.reserve(num_entries); + + // The first entry is at offset 1 + auto edge_reg_vals = regs().block_peek32(ADJACENCY_BASE_ADDR + 4, num_entries); + + // Unpack the struct + // Note: we construct the adjacency list with empty block IDs. We'll fill them in + // when we make the block controllers + for (uint32_t edge_reg_val : edge_reg_vals) { + adj_list.push_back({uhd::narrow_cast<uint16_t>((edge_reg_val & 0xFFC00000) >> 22), + uhd::narrow_cast<uint8_t>((edge_reg_val & 0x003F0000) >> 16), + uhd::narrow_cast<uint16_t>((edge_reg_val & 0x0000FFC0) >> 6), + uhd::narrow_cast<uint8_t>((edge_reg_val & 0x0000003F) >> 0)}); + } + return adj_list; +} + +uint32_t client_zero::get_noc_id(uint16_t portno) +{ + _check_port_number(portno); + // The NOC ID is the second entry in the port's register space + return regs().peek32(_get_port_base_addr(portno) + 1); +} + +bool client_zero::get_flush_active(uint16_t portno) +{ + // The flush active flag is in the 0th (bottom) bit + return bool(_get_flush_status_flags(portno) & 1); +} + +bool client_zero::get_flush_done(uint16_t portno) +{ + // The flush done flag is in the 1st bit + return bool(_get_flush_status_flags(portno) & (1 << 1)); +} + +bool client_zero::poll_flush_done( + uint16_t portno, std::chrono::milliseconds timeout = DEFAULT_POLL_TIMEOUT) +{ + _check_port_number(portno); + auto start = std::chrono::steady_clock::now(); + while (!get_flush_done(portno)) { + if (std::chrono::steady_clock::now() > (start + timeout)) { + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(DEFAULT_POLL_PERIOD)); + } + return true; +} + +void client_zero::set_flush_timeout(uint32_t timeout, uint16_t portno) +{ + _check_port_number(portno); + // The flush timeout register is the first write register + regs().poke32(_get_port_base_addr(portno), timeout); +} + +void client_zero::set_flush(uint16_t portno) +{ + _check_port_number(portno); + // The flush and reset registers are the second write register + regs().poke32( + _get_port_base_addr(portno) + FLUSH_RESET_ADDR, 1 /* 0th (bottom) bit */); +} + +bool client_zero::complete_flush(uint16_t portno) +{ + _check_port_number(portno); + set_flush(portno); + return poll_flush_done(portno); +} + +void client_zero::reset_ctrl(uint16_t portno) +{ + _check_port_number(portno); + // The flush and reset registers are the second write register + regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 1) /* 1st bit */); + std::this_thread::sleep_for(100us); + regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 1)); +} + +void client_zero::reset_chdr(uint16_t portno) +{ + _check_port_number(portno); + // The flush and reset registers are the second write register + regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 2) /* 2nd bit */); + std::this_thread::sleep_for(1ms); + regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 2)); +} + +client_zero::block_config_info client_zero::get_block_info(uint16_t portno) +{ + _check_port_number(portno); + // The configuration information is in the port's first register + uint32_t config_reg_val = regs().peek32(_get_port_base_addr(portno)); + return {uhd::narrow_cast<uint8_t>((config_reg_val & 0x000000FF) >> 0), + uhd::narrow_cast<uint8_t>((config_reg_val & 0x00003F00) >> 8), + uhd::narrow_cast<uint8_t>((config_reg_val & 0x000FC000) >> 14), + uhd::narrow_cast<uint8_t>((config_reg_val & 0x03F00000) >> 20), + uhd::narrow_cast<uint8_t>((config_reg_val & 0xFC000000) >> 26)}; +} + + +uint32_t client_zero::_get_port_base_addr(uint16_t portno) +{ + return REGS_PER_PORT * portno * 4; +} + +void client_zero::_check_port_number(uint16_t portno) +{ + auto num_ports = get_num_blocks() + get_num_stream_endpoints() + 1; + + if (portno >= num_ports) { + throw uhd::index_error( + std::string("Client zero attempted to query unconnected port: ") + + std::to_string(portno)); + } else if (portno <= get_num_stream_endpoints()) { + throw uhd::index_error( + std::string("Client zero attempted to query stream endpoint: ") + + std::to_string(portno)); + } +} + +uint32_t client_zero::_get_flush_status_flags(uint16_t portno) +{ + _check_port_number(portno); + // The flush status flags are in the third register of the port + return regs().peek32(_get_port_base_addr(portno) + 2); +} 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 */ + |