aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
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/lib
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/lib')
-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
3 files changed, 413 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);
+}