// // Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include #include #include #include #include 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; //! Register address of the controlport information constexpr int CTRLPORT_CNT_ADDR = 4 * 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); const uint32_t cport_info_reg_val = regs().peek32(CTRLPORT_CNT_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((port_reg_val & 0x3FF00000) >> 20); _num_blocks = uhd::narrow_cast((port_reg_val & 0x000FFC00) >> 10); _num_stream_endpoints = uhd::narrow_cast((port_reg_val & 0x000003FF)); _num_ctrl_endpoints = uhd::narrow_cast(cport_info_reg_val & 0x3FF); // 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 < (1 + 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::_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 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((edge_reg_val & 0xFFC00000) >> 22), uhd::narrow_cast((edge_reg_val & 0x003F0000) >> 16), uhd::narrow_cast((edge_reg_val & 0x0000FFC0) >> 6), uhd::narrow_cast((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) + 4); } 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); } bool client_zero::complete_flush_all_blocks() { const size_t num_blocks = get_num_blocks(); const size_t first_block_port = 1 + get_num_stream_endpoints(); // Issue flush commands for (size_t portno = 0; portno < num_blocks; ++portno) { auto block_portno = portno + first_block_port; set_flush(block_portno); } // Poll for flush done bool all_ports_flushed = true; for (size_t portno = 0; portno < num_blocks; ++portno) { size_t block_portno = portno + first_block_port; all_ports_flushed = all_ports_flushed && poll_flush_done(block_portno); } return all_ports_flushed; } void client_zero::reset_ctrl(uint16_t portno) { _check_port_number(portno); // The flush and reset registers are the second write register // A reset gets triggered on the rising edge regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, 0); regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 1) /* 1st bit */); // We wait the requested time (see the RFNoC spec) before returning std::this_thread::sleep_for(100us); } void client_zero::reset_chdr(uint16_t portno) { _check_port_number(portno); // The flush and reset registers are the second write register // A reset gets triggered on the rising edge regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, 0); regs().poke32(_get_port_base_addr(portno) + FLUSH_RESET_ADDR, (1 << 2) /* 2nd bit */); // We wait the requested time (see the RFNoC spec) before returning std::this_thread::sleep_for(1ms); } client_zero::block_config_info client_zero::get_block_info(uint16_t portno) { _check_port_number(portno); // The block and ctrl information is in the port's first register uint32_t config_reg_val = regs().peek32(_get_port_base_addr(portno) + 0); // The block and ctrl information is in the port's third register uint32_t data_reg_val = regs().peek32(_get_port_base_addr(portno) + 8); return {uhd::narrow_cast((config_reg_val & 0x0000003F) >> 0), uhd::narrow_cast((config_reg_val & 0x00000FC0) >> 6), uhd::narrow_cast((config_reg_val & 0x0003F000) >> 12), uhd::narrow_cast((config_reg_val & 0x00FC0000) >> 18), uhd::narrow_cast((config_reg_val & 0xFF000000) >> 24), uhd::narrow_cast((data_reg_val & 0x000000FC) >> 2)}; } 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) + 8); } client_zero::sptr client_zero::make(chdr_ctrl_endpoint& chdr_ctrl_ep, sep_id_t dst_epid) { // Create a control port endpoint for client zero static constexpr uint16_t CLIENT_ZERO_PORT = 0; static constexpr size_t CLIENT_ZERO_BUFF_CAPACITY = 32; static constexpr size_t CLIENT_ZERO_MAX_ASYNC_MSGS = 0; // This clock_iface doesn't really matter because client_zero doesn't do timed // commands static clock_iface client_zero_clk("client_zero", 100e6, true); client_zero_clk.set_running(true); // Client zero clock must be always-on. return std::make_shared(chdr_ctrl_ep.get_ctrlport_ep(dst_epid, CLIENT_ZERO_PORT, CLIENT_ZERO_BUFF_CAPACITY, CLIENT_ZERO_MAX_ASYNC_MSGS, client_zero_clk, client_zero_clk)); }