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