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/lib | |
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/lib')
-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 |
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); +} |