aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorBrent Stapleton <brent.stapleton@ettus.com>2019-05-07 17:33:36 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 11:49:16 -0800
commitcd36d9d2d3dfdecae42b715d734547e48347cf4a (patch)
treea80ad2f1d44da87887fb7c21bb5fc2835a7651e7 /host
parent53becb55e1211a85b27a3d862afec4e7ffd0818e (diff)
downloaduhd-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.hpp199
-rw-r--r--host/lib/rfnoc/CMakeLists.txt1
-rw-r--r--host/lib/rfnoc/client_zero.cpp213
-rw-r--r--host/tests/CMakeLists.txt5
-rw-r--r--host/tests/client_zero_test.cpp224
-rw-r--r--host/tests/rfnoc_mock_reg_iface.hpp124
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 */
+