aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests
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/tests
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/tests')
-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
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 */
+