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 */ +  | 
