aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/rfnoc/client_zero.cpp
blob: ce39569ba40db01dd673c9976555ac7c64c47e12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//
// 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;
//! 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<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));
    _num_ctrl_endpoints   = uhd::narrow_cast<uint16_t>(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::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) + 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<uint8_t>((config_reg_val & 0x0000003F) >> 0),
        uhd::narrow_cast<uint8_t>((config_reg_val & 0x00000FC0) >> 6),
        uhd::narrow_cast<uint8_t>((config_reg_val & 0x0003F000) >> 12),
        uhd::narrow_cast<uint8_t>((config_reg_val & 0x00FC0000) >> 18),
        uhd::narrow_cast<uint8_t>((config_reg_val & 0xFF000000) >> 24),
        uhd::narrow_cast<uint8_t>((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<client_zero>(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));
}