switchboard-hw 0.3.0__cp314-cp314-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _switchboard.cpython-314-darwin.so +0 -0
- switchboard/__init__.py +24 -0
- switchboard/ams.py +668 -0
- switchboard/apb.py +278 -0
- switchboard/autowrap.py +1000 -0
- switchboard/axi.py +571 -0
- switchboard/axil.py +348 -0
- switchboard/bitvector.py +112 -0
- switchboard/cmdline.py +142 -0
- switchboard/cpp/Makefile +13 -0
- switchboard/cpp/bitutil.h +39 -0
- switchboard/cpp/pagemap.h +91 -0
- switchboard/cpp/pciedev.h +86 -0
- switchboard/cpp/router.cc +89 -0
- switchboard/cpp/spsc_queue.h +267 -0
- switchboard/cpp/switchboard.hpp +257 -0
- switchboard/cpp/switchboard_pcie.hpp +234 -0
- switchboard/cpp/switchboard_tlm.hpp +98 -0
- switchboard/cpp/umilib.h +144 -0
- switchboard/cpp/umilib.hpp +113 -0
- switchboard/cpp/umisb.hpp +364 -0
- switchboard/cpp/xyce.hpp +90 -0
- switchboard/deps/__init__.py +0 -0
- switchboard/deps/verilog_axi.py +23 -0
- switchboard/dpi/__init__.py +0 -0
- switchboard/dpi/switchboard_dpi.cc +119 -0
- switchboard/dpi/switchboard_dpi.py +13 -0
- switchboard/dpi/xyce_dpi.cc +43 -0
- switchboard/gpio.py +108 -0
- switchboard/icarus.py +85 -0
- switchboard/loopback.py +157 -0
- switchboard/network.py +714 -0
- switchboard/pytest_plugin.py +11 -0
- switchboard/sbdesign.py +55 -0
- switchboard/sbdut.py +744 -0
- switchboard/sbtcp.py +345 -0
- switchboard/sc/__init__.py +0 -0
- switchboard/sc/morty/__init__.py +0 -0
- switchboard/sc/morty/uniquify.py +67 -0
- switchboard/sc/sed/__init__.py +0 -0
- switchboard/sc/sed/sed_remove.py +47 -0
- switchboard/sc/standalone_netlist_flow.py +25 -0
- switchboard/switchboard.py +53 -0
- switchboard/test_util.py +46 -0
- switchboard/uart_xactor.py +66 -0
- switchboard/umi.py +793 -0
- switchboard/util.py +131 -0
- switchboard/verilator/__init__.py +0 -0
- switchboard/verilator/config.vlt +13 -0
- switchboard/verilator/testbench.cc +143 -0
- switchboard/verilator/verilator.py +13 -0
- switchboard/verilator_run.py +31 -0
- switchboard/verilog/__init__.py +0 -0
- switchboard/verilog/common/__init__.py +0 -0
- switchboard/verilog/common/common.py +26 -0
- switchboard/verilog/common/switchboard.vh +429 -0
- switchboard/verilog/common/uart_xactor.sv +247 -0
- switchboard/verilog/common/umi_gpio.v +236 -0
- switchboard/verilog/fpga/__init__.py +0 -0
- switchboard/verilog/fpga/axi_reader.sv +82 -0
- switchboard/verilog/fpga/axi_writer.sv +111 -0
- switchboard/verilog/fpga/config_registers.sv +249 -0
- switchboard/verilog/fpga/fpga.py +21 -0
- switchboard/verilog/fpga/include/sb_queue_regmap.vh +21 -0
- switchboard/verilog/fpga/include/spsc_queue.vh +7 -0
- switchboard/verilog/fpga/memory_fault.sv +40 -0
- switchboard/verilog/fpga/sb_fpga_queues.sv +416 -0
- switchboard/verilog/fpga/sb_rx_fpga.sv +303 -0
- switchboard/verilog/fpga/sb_tx_fpga.sv +294 -0
- switchboard/verilog/fpga/umi_fpga_queues.sv +146 -0
- switchboard/verilog/sim/__init__.py +0 -0
- switchboard/verilog/sim/auto_stop_sim.sv +25 -0
- switchboard/verilog/sim/perf_meas_sim.sv +97 -0
- switchboard/verilog/sim/queue_to_sb_sim.sv +176 -0
- switchboard/verilog/sim/queue_to_umi_sim.sv +66 -0
- switchboard/verilog/sim/sb_apb_m.sv +146 -0
- switchboard/verilog/sim/sb_axi_m.sv +199 -0
- switchboard/verilog/sim/sb_axil_m.sv +180 -0
- switchboard/verilog/sim/sb_axil_s.sv +180 -0
- switchboard/verilog/sim/sb_clk_gen.sv +89 -0
- switchboard/verilog/sim/sb_jtag_rbb_sim.sv +148 -0
- switchboard/verilog/sim/sb_rx_sim.sv +55 -0
- switchboard/verilog/sim/sb_to_queue_sim.sv +196 -0
- switchboard/verilog/sim/sb_tx_sim.sv +55 -0
- switchboard/verilog/sim/switchboard_sim.py +49 -0
- switchboard/verilog/sim/umi_rx_sim.sv +61 -0
- switchboard/verilog/sim/umi_to_queue_sim.sv +66 -0
- switchboard/verilog/sim/umi_tx_sim.sv +61 -0
- switchboard/verilog/sim/xyce_intf.sv +67 -0
- switchboard/vpi/switchboard_vpi.cc +431 -0
- switchboard/vpi/xyce_vpi.cc +200 -0
- switchboard/warn.py +14 -0
- switchboard/xyce.py +27 -0
- switchboard_hw-0.3.0.dist-info/METADATA +303 -0
- switchboard_hw-0.3.0.dist-info/RECORD +99 -0
- switchboard_hw-0.3.0.dist-info/WHEEL +6 -0
- switchboard_hw-0.3.0.dist-info/entry_points.txt +6 -0
- switchboard_hw-0.3.0.dist-info/licenses/LICENSE +190 -0
- switchboard_hw-0.3.0.dist-info/top_level.txt +2 -0
switchboard/umi.py
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
# Python interface for UMI reads, writes, and atomic operations
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2024 Zero ASIC Corporation
|
|
4
|
+
# This code is licensed under Apache License 2.0 (see LICENSE for details)
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from numbers import Integral
|
|
10
|
+
from typing import Iterable, Union, Dict
|
|
11
|
+
|
|
12
|
+
from _switchboard import (PyUmi, PyUmiPacket, umi_pack, UmiCmd, UmiAtomic)
|
|
13
|
+
from .gpio import UmiGpio
|
|
14
|
+
|
|
15
|
+
# note: it was convenient to implement some of this in Python, rather
|
|
16
|
+
# than have everything in C++, because it was easier to provide
|
|
17
|
+
# flexibility with numpy types
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UmiTxRx:
|
|
21
|
+
def __init__(self, tx_uri: str = None, rx_uri: str = None,
|
|
22
|
+
srcaddr: Union[int, Dict[str, int]] = 0, posted: bool = False,
|
|
23
|
+
max_bytes: int = None, fresh: bool = False, error: bool = True,
|
|
24
|
+
max_rate: float = -1):
|
|
25
|
+
"""
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
tx_uri: str, optional
|
|
29
|
+
Name of the switchboard queue that
|
|
30
|
+
write() and send() will send UMI packets to. Defaults to
|
|
31
|
+
None, meaning "unused".
|
|
32
|
+
rx_uri: str, optional
|
|
33
|
+
Name of the switchboard queue that
|
|
34
|
+
read() and recv() will receive UMI packets from. Defaults
|
|
35
|
+
to None, meaning "unused".
|
|
36
|
+
srcaddr: int, optional
|
|
37
|
+
Default srcaddr to use for reads,
|
|
38
|
+
ack'd writes, and atomics. Defaults to 0. Can also be
|
|
39
|
+
provided as a dictionary with separate defaults for each
|
|
40
|
+
type of transaction: srcaddr={'read': 0x1234, 'write':
|
|
41
|
+
0x2345, 'atomic': 0x3456}. When the defaults are provided
|
|
42
|
+
with a dictionary, all keys are optional. Transactions
|
|
43
|
+
that are not specified in the dictionary will default
|
|
44
|
+
to a srcaddr of 0.
|
|
45
|
+
posted: bool, optional
|
|
46
|
+
If True, default to using posted
|
|
47
|
+
(i.e., non-ack'd) writes. This can be overridden on a
|
|
48
|
+
transaction-by-transaction basis. Defaults to False.
|
|
49
|
+
max_bytes: int, optional
|
|
50
|
+
Default maximum number of bytes
|
|
51
|
+
to use in each UMI transaction. Can be overridden on a
|
|
52
|
+
transaction-by-transaction basis. Defaults to 32 bytes.
|
|
53
|
+
fresh: bool, optional
|
|
54
|
+
If True, the queue specified by the uri parameter will get
|
|
55
|
+
cleared before executing the simulation.
|
|
56
|
+
error: bool, optional
|
|
57
|
+
If True, error out upon receiving an unexpected UMI response.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
if tx_uri is None:
|
|
61
|
+
tx_uri = ""
|
|
62
|
+
|
|
63
|
+
if rx_uri is None:
|
|
64
|
+
rx_uri = ""
|
|
65
|
+
|
|
66
|
+
self.umi = PyUmi(tx_uri, rx_uri, fresh, max_rate=max_rate)
|
|
67
|
+
|
|
68
|
+
if srcaddr is not None:
|
|
69
|
+
# convert srcaddr default to a dictionary if necessary
|
|
70
|
+
if isinstance(srcaddr, int):
|
|
71
|
+
srcaddr = {
|
|
72
|
+
'read': srcaddr,
|
|
73
|
+
'write': srcaddr,
|
|
74
|
+
'atomic': srcaddr
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if isinstance(srcaddr, dict):
|
|
78
|
+
self.def_read_srcaddr = int(srcaddr.get('read', 0))
|
|
79
|
+
self.def_write_srcaddr = int(srcaddr.get('write', 0))
|
|
80
|
+
self.def_atomic_srcaddr = int(srcaddr.get('atomic', 0))
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError(f'Unsupported default srcaddr specification: {srcaddr}')
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError('Default value of "srcaddr" cannot be None.')
|
|
85
|
+
|
|
86
|
+
if posted is not None:
|
|
87
|
+
self.default_posted = bool(posted)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError('Default value of "posted" cannot be None.')
|
|
90
|
+
|
|
91
|
+
if max_bytes is None:
|
|
92
|
+
max_bytes = 32
|
|
93
|
+
|
|
94
|
+
self.default_max_bytes = max_bytes
|
|
95
|
+
self.default_error = error
|
|
96
|
+
|
|
97
|
+
def gpio(
|
|
98
|
+
self,
|
|
99
|
+
iwidth: int = 32,
|
|
100
|
+
owidth: int = 32,
|
|
101
|
+
init: int = 0,
|
|
102
|
+
dstaddr: int = 0,
|
|
103
|
+
srcaddr: int = 0,
|
|
104
|
+
posted: bool = False,
|
|
105
|
+
max_bytes: int = 32
|
|
106
|
+
) -> UmiGpio:
|
|
107
|
+
"""
|
|
108
|
+
Returns an object for communicating with umi_gpio modules.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
iwidth: int
|
|
113
|
+
Width of GPIO input (bits). Defaults to 32.
|
|
114
|
+
owidth: int
|
|
115
|
+
Width of GPIO output (bits). Defaults to 32.
|
|
116
|
+
init: int
|
|
117
|
+
Default value of GPIO output. Defaults to 0.
|
|
118
|
+
dstaddr: int
|
|
119
|
+
Base address of the GPIO device. Defaults to 0.
|
|
120
|
+
srcaddr: int
|
|
121
|
+
Source address to which responses should be routed. Defaults to 0.
|
|
122
|
+
posted: bool
|
|
123
|
+
Whether writes should be sent as posted. Defaults to False.
|
|
124
|
+
max_bytes: int
|
|
125
|
+
Maximum number of bytes in a single transaction to umi_gpio.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
UmiGpio
|
|
130
|
+
UmiGpio object with .i (input) and .o (output) attributes
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
return UmiGpio(
|
|
134
|
+
iwidth=iwidth,
|
|
135
|
+
owidth=owidth,
|
|
136
|
+
init=init,
|
|
137
|
+
dstaddr=dstaddr,
|
|
138
|
+
srcaddr=srcaddr,
|
|
139
|
+
posted=posted,
|
|
140
|
+
max_bytes=max_bytes,
|
|
141
|
+
umi=self
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def init_queues(self, tx_uri: str = None, rx_uri: str = None, fresh: bool = False):
|
|
145
|
+
"""
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
tx_uri: str, optional
|
|
149
|
+
Name of the switchboard queue that
|
|
150
|
+
write() and send() will send UMI packets to. Defaults to
|
|
151
|
+
None, meaning "unused".
|
|
152
|
+
rx_uri: str, optional
|
|
153
|
+
Name of the switchboard queue that
|
|
154
|
+
read() and recv() will receive UMI packets from. Defaults
|
|
155
|
+
to None, meaning "unused".
|
|
156
|
+
fresh: bool, optional
|
|
157
|
+
If True, the queue specified by the uri parameter will get
|
|
158
|
+
cleared before executing the simulation.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
if tx_uri is None:
|
|
162
|
+
tx_uri = ""
|
|
163
|
+
|
|
164
|
+
if rx_uri is None:
|
|
165
|
+
rx_uri = ""
|
|
166
|
+
|
|
167
|
+
self.umi.init(tx_uri, rx_uri, fresh)
|
|
168
|
+
|
|
169
|
+
def send(self, p, blocking=True) -> bool:
|
|
170
|
+
"""
|
|
171
|
+
Sends (or tries to send if burst=False) a UMI transaction (PyUmiPacket)
|
|
172
|
+
Returns True if the packet was sent successfully, else False.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
p: PyUmiPacket
|
|
177
|
+
The UMI packet that will be sent
|
|
178
|
+
blocking: bool, optional
|
|
179
|
+
If True, the program will pause execution until a response to the write request
|
|
180
|
+
is received.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
bool
|
|
185
|
+
Returns true if the `p` was sent successfully
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
return self.umi.send(p, blocking)
|
|
189
|
+
|
|
190
|
+
def recv(self, blocking=True) -> PyUmiPacket:
|
|
191
|
+
"""
|
|
192
|
+
Wait for and return a UMI packet if blocking=True, otherwise return a
|
|
193
|
+
UMI packet if one can be read immediately, and None otherwise.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
blocking: bool, optional
|
|
198
|
+
If True, the function will wait until a UMI packet can be read.
|
|
199
|
+
If False, a None type will be returned if no UMI packet can be read
|
|
200
|
+
immediately.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
PyUmiPacket
|
|
205
|
+
If `blocking` is True, a PyUmiPacket is always returned. If `blocking` is
|
|
206
|
+
False, a PyUmiPacket object will be returned if one can be read immediately.
|
|
207
|
+
Otherwise, a None type will be returned.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
return self.umi.recv(blocking)
|
|
211
|
+
|
|
212
|
+
def write(self, addr, data, srcaddr=None, max_bytes=None,
|
|
213
|
+
posted=None, qos=0, prot=0, progressbar=False, check_alignment=True,
|
|
214
|
+
error=None):
|
|
215
|
+
"""
|
|
216
|
+
Writes the provided data to the given 64-bit address.
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
addr: int
|
|
221
|
+
64-bit address that will be written to
|
|
222
|
+
|
|
223
|
+
data: np.uint8, np.uint16, np.uint32, np.uint64, or np.array
|
|
224
|
+
Can be either a numpy integer type (e.g., np.uint32) or an numpy
|
|
225
|
+
array of integer types (np.uint8, np.uin16, np.uint32, np.uint64, etc.).
|
|
226
|
+
The `data` input may contain more than "max_bytes", in which case
|
|
227
|
+
the write will automatically be split into multiple transactions.
|
|
228
|
+
|
|
229
|
+
srcaddr: int, optional
|
|
230
|
+
UMI source address used for the write transaction. This is sometimes needed to make
|
|
231
|
+
the write response gets routed to the right place.
|
|
232
|
+
|
|
233
|
+
max_bytes: int, optional
|
|
234
|
+
Indicates the maximum number of bytes that can be used for any individual UMI
|
|
235
|
+
transaction. If not specified, this defaults to the value of `max_bytes`
|
|
236
|
+
provided in the UmiTxRx constructor, which in turn defaults to 32.
|
|
237
|
+
|
|
238
|
+
posted: bool, optional
|
|
239
|
+
If True, a write response will be received.
|
|
240
|
+
|
|
241
|
+
qos: int, optional
|
|
242
|
+
4-bit Quality of Service field in UMI Command
|
|
243
|
+
|
|
244
|
+
prot: int, optional
|
|
245
|
+
2-bit protection mode field in UMI command
|
|
246
|
+
|
|
247
|
+
progressbar: bool, optional
|
|
248
|
+
If True, the number of packets written will be displayed via a progressbar
|
|
249
|
+
in the terminal.
|
|
250
|
+
|
|
251
|
+
check_alignment: bool, optional
|
|
252
|
+
If true, an exception will be raised if the `addr` parameter cannot be aligned based
|
|
253
|
+
on the size of the `data` parameter
|
|
254
|
+
|
|
255
|
+
error: bool, optional
|
|
256
|
+
If True, error out upon receiving an unexpected UMI response.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
# set defaults
|
|
260
|
+
|
|
261
|
+
if max_bytes is None:
|
|
262
|
+
max_bytes = self.default_max_bytes
|
|
263
|
+
|
|
264
|
+
max_bytes = int(max_bytes)
|
|
265
|
+
|
|
266
|
+
if srcaddr is None:
|
|
267
|
+
srcaddr = self.def_write_srcaddr
|
|
268
|
+
|
|
269
|
+
srcaddr = int(srcaddr)
|
|
270
|
+
|
|
271
|
+
if posted is None:
|
|
272
|
+
posted = self.default_posted
|
|
273
|
+
|
|
274
|
+
posted = bool(posted)
|
|
275
|
+
|
|
276
|
+
if error is None:
|
|
277
|
+
error = self.default_error
|
|
278
|
+
|
|
279
|
+
error = bool(error)
|
|
280
|
+
|
|
281
|
+
# format the data to be written
|
|
282
|
+
|
|
283
|
+
if isinstance(data, np.ndarray):
|
|
284
|
+
if data.ndim == 0:
|
|
285
|
+
write_data = np.atleast_1d(data)
|
|
286
|
+
elif data.ndim == 1:
|
|
287
|
+
write_data = data
|
|
288
|
+
else:
|
|
289
|
+
raise ValueError(f'Can only write 1D arrays (got ndim={data.ndim})')
|
|
290
|
+
|
|
291
|
+
if not np.issubdtype(write_data.dtype, np.integer):
|
|
292
|
+
raise ValueError('Can only write integer dtypes such as uint8, uint16, etc.'
|
|
293
|
+
f' (got dtype "{data.dtype}")')
|
|
294
|
+
elif isinstance(data, np.integer):
|
|
295
|
+
write_data = np.array(data, ndmin=1)
|
|
296
|
+
else:
|
|
297
|
+
raise TypeError(f"Unknown data type: {type(data)}")
|
|
298
|
+
|
|
299
|
+
if check_alignment:
|
|
300
|
+
size = dtype2size(write_data.dtype)
|
|
301
|
+
if not addr_aligned(addr=addr, align=size):
|
|
302
|
+
raise ValueError(f'addr=0x{addr:x} misaligned for size={size}')
|
|
303
|
+
|
|
304
|
+
# perform write
|
|
305
|
+
self.umi.write(addr, write_data, srcaddr, max_bytes,
|
|
306
|
+
posted, qos, prot, progressbar, error)
|
|
307
|
+
|
|
308
|
+
def write_readback(self, addr, value, mask=None, srcaddr=None, dtype=None,
|
|
309
|
+
posted=True, write_srcaddr=None, check_alignment=True, error=None):
|
|
310
|
+
"""
|
|
311
|
+
Writes the provided value to the given 64-bit address, and blocks
|
|
312
|
+
until that value is read back from the provided address.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
addr: int
|
|
317
|
+
The destination address to write to and read from
|
|
318
|
+
|
|
319
|
+
value: int, np.uint8, np.uint16, np.uint32, or np.uint64
|
|
320
|
+
The data written to `addr`
|
|
321
|
+
|
|
322
|
+
mask: int, optional
|
|
323
|
+
argument (optional) allows the user to mask off some bits
|
|
324
|
+
in the comparison of the data written vs. data read back. For example,
|
|
325
|
+
if a user only cares that bit "5" is written to "1", and does not care
|
|
326
|
+
about the value of other bits read back, they could use mask=1<<5.
|
|
327
|
+
|
|
328
|
+
srcaddr: int, optional
|
|
329
|
+
The UMI source address used for the read transaction. This is
|
|
330
|
+
sometimes needed to make sure that reads get routed to the right place.
|
|
331
|
+
|
|
332
|
+
dtype: np.uint8, np.uint16, np.uint32, or np.uint64, optional
|
|
333
|
+
If `value` is specified as plain integer, then dtype must be specified,
|
|
334
|
+
indicating a particular numpy integer type. This is so that the size of
|
|
335
|
+
the UMI transaction can be set appropriately.
|
|
336
|
+
|
|
337
|
+
posted: bool, optional
|
|
338
|
+
By default, the write is performed as a posted write, however it is
|
|
339
|
+
is possible to use an ack'd write by setting posted=False.
|
|
340
|
+
|
|
341
|
+
write_srcaddr: int, optional
|
|
342
|
+
If `posted`=True, write_srcaddr specifies the srcaddr used for that
|
|
343
|
+
transaction. If write_srcaddr is None, the default srcaddr for writes
|
|
344
|
+
will be used.
|
|
345
|
+
|
|
346
|
+
check_alignment: bool, optional
|
|
347
|
+
If true, an exception will be raised if the `addr` parameter cannot be aligned based
|
|
348
|
+
on the size of the `data` parameter
|
|
349
|
+
|
|
350
|
+
error: bool, optional
|
|
351
|
+
If True, error out upon receiving an unexpected UMI response.
|
|
352
|
+
|
|
353
|
+
Raises
|
|
354
|
+
------
|
|
355
|
+
TypeError
|
|
356
|
+
If `value` is not an integer type, if `mask` is not an integer type
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
# set defaults
|
|
360
|
+
|
|
361
|
+
if srcaddr is None:
|
|
362
|
+
srcaddr = self.def_read_srcaddr
|
|
363
|
+
|
|
364
|
+
srcaddr = int(srcaddr)
|
|
365
|
+
|
|
366
|
+
if write_srcaddr is None:
|
|
367
|
+
write_srcaddr = self.def_write_srcaddr
|
|
368
|
+
|
|
369
|
+
write_srcaddr = int(write_srcaddr)
|
|
370
|
+
|
|
371
|
+
# convert value to a numpy datatype if it is not already
|
|
372
|
+
if not isinstance(value, np.integer):
|
|
373
|
+
if dtype is not None:
|
|
374
|
+
value = dtype(value)
|
|
375
|
+
else:
|
|
376
|
+
raise TypeError("Must provide value as a numpy integer type, or specify dtype.")
|
|
377
|
+
|
|
378
|
+
# set the mask to all ones if it is None
|
|
379
|
+
if mask is None:
|
|
380
|
+
nbits = (np.dtype(value.dtype).itemsize * 8)
|
|
381
|
+
mask = (1 << nbits) - 1
|
|
382
|
+
|
|
383
|
+
# convert mask to a numpy datatype if it is not already
|
|
384
|
+
if not isinstance(mask, np.integer):
|
|
385
|
+
if dtype is not None:
|
|
386
|
+
mask = dtype(mask)
|
|
387
|
+
else:
|
|
388
|
+
raise TypeError("Must provide mask as a numpy integer type, or specify dtype.")
|
|
389
|
+
|
|
390
|
+
# write, then read repeatedly until the value written is observed
|
|
391
|
+
self.write(addr, value, srcaddr=write_srcaddr, posted=posted,
|
|
392
|
+
check_alignment=check_alignment, error=error)
|
|
393
|
+
rdval = self.read(addr, value.dtype, srcaddr=srcaddr,
|
|
394
|
+
check_alignment=check_alignment, error=error)
|
|
395
|
+
while ((rdval & mask) != (value & mask)):
|
|
396
|
+
rdval = self.read(addr, value.dtype, srcaddr=srcaddr,
|
|
397
|
+
check_alignment=check_alignment, error=error)
|
|
398
|
+
|
|
399
|
+
def read(self, addr, num_or_dtype, dtype=np.uint8, srcaddr=None,
|
|
400
|
+
max_bytes=None, qos=0, prot=0, check_alignment=True, error=None):
|
|
401
|
+
"""
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
addr: int
|
|
405
|
+
The 64-bit address read from
|
|
406
|
+
|
|
407
|
+
num_or_dtype: int or numpy integer datatype
|
|
408
|
+
If a plain int, `num_or_datatype` specifies the number of bytes to be read.
|
|
409
|
+
If a numpy integer datatype (np.uint8, np.uint16, etc.), num_or_datatype
|
|
410
|
+
specifies the data type to be returned.
|
|
411
|
+
|
|
412
|
+
dtype: numpy integer datatype, optional
|
|
413
|
+
If num_or_dtype is a plain integer, the value returned by this function
|
|
414
|
+
will be a numpy array of type "dtype". On the other hand, if num_or_dtype
|
|
415
|
+
is a numpy datatype, the value returned will be a scalar of that datatype.
|
|
416
|
+
|
|
417
|
+
srcaddr: int, optional
|
|
418
|
+
The UMI source address used for the read transaction. This
|
|
419
|
+
is sometimes needed to make sure that reads get routed to the right place.
|
|
420
|
+
|
|
421
|
+
max_bytes: int, optional
|
|
422
|
+
Indicates the maximum number of bytes that can be used for any individual UMI
|
|
423
|
+
transaction. If not specified, this defaults to the value of `max_bytes`
|
|
424
|
+
provided in the UmiTxRx constructor, which in turn defaults to 32.
|
|
425
|
+
|
|
426
|
+
qos: int, optional
|
|
427
|
+
4-bit Quality of Service field used in the UMI command
|
|
428
|
+
|
|
429
|
+
prot: int, optional
|
|
430
|
+
2-bit Protection mode field used in the UMI command
|
|
431
|
+
|
|
432
|
+
error: bool, optional
|
|
433
|
+
If True, error out upon receiving an unexpected UMI response.
|
|
434
|
+
|
|
435
|
+
Returns
|
|
436
|
+
-------
|
|
437
|
+
numpy integer array
|
|
438
|
+
An array of `num_or_dtype` bytes read from `addr`. The array will have the type
|
|
439
|
+
specified by `dtype` or `num_or_dtype`
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
# set defaults
|
|
443
|
+
|
|
444
|
+
if max_bytes is None:
|
|
445
|
+
max_bytes = self.default_max_bytes
|
|
446
|
+
|
|
447
|
+
max_bytes = int(max_bytes)
|
|
448
|
+
|
|
449
|
+
if srcaddr is None:
|
|
450
|
+
srcaddr = self.def_read_srcaddr
|
|
451
|
+
|
|
452
|
+
srcaddr = int(srcaddr)
|
|
453
|
+
|
|
454
|
+
if error is None:
|
|
455
|
+
error = self.default_error
|
|
456
|
+
|
|
457
|
+
error = bool(error)
|
|
458
|
+
|
|
459
|
+
if isinstance(num_or_dtype, (type, np.dtype)):
|
|
460
|
+
num = 1
|
|
461
|
+
bytes_per_elem = np.dtype(num_or_dtype).itemsize
|
|
462
|
+
else:
|
|
463
|
+
num = num_or_dtype
|
|
464
|
+
bytes_per_elem = np.dtype(dtype).itemsize
|
|
465
|
+
|
|
466
|
+
if check_alignment:
|
|
467
|
+
size = nbytes2size(bytes_per_elem)
|
|
468
|
+
if not addr_aligned(addr=addr, align=size):
|
|
469
|
+
raise ValueError(f'addr=0x{addr:x} misaligned for size={size}')
|
|
470
|
+
|
|
471
|
+
result = self.umi.read(addr, num, bytes_per_elem, srcaddr, max_bytes,
|
|
472
|
+
qos, prot, error)
|
|
473
|
+
|
|
474
|
+
if isinstance(num_or_dtype, (type, np.dtype)):
|
|
475
|
+
return result.view(num_or_dtype)[0]
|
|
476
|
+
else:
|
|
477
|
+
return result
|
|
478
|
+
|
|
479
|
+
def atomic(self, addr, data, opcode, srcaddr=None, qos=0, prot=0, error=None):
|
|
480
|
+
"""
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
addr: int
|
|
484
|
+
64-bit address atomic operation will be applied to.
|
|
485
|
+
|
|
486
|
+
data: np.uint8, np.uint16, np.uint32, np.uint64
|
|
487
|
+
must so that the size of the atomic operation can be determined.
|
|
488
|
+
|
|
489
|
+
opcode: str or switchboard.UmiAtomic value
|
|
490
|
+
Supported string values are 'add', 'and', 'or', 'xor', 'max', 'min',
|
|
491
|
+
'minu', 'maxu', and 'swap' (case-insensitive).
|
|
492
|
+
|
|
493
|
+
srcaddr: int, optional
|
|
494
|
+
The UMI source address used for the atomic transaction. This
|
|
495
|
+
is sometimes needed to make sure the response get routed to the right place.
|
|
496
|
+
|
|
497
|
+
qos: int, optional
|
|
498
|
+
4-bit Quality of Service field used in the UMI command
|
|
499
|
+
|
|
500
|
+
prot: int, optional
|
|
501
|
+
2-bit Protection mode field used in the UMI command
|
|
502
|
+
|
|
503
|
+
error: bool, optional
|
|
504
|
+
If True, error out upon receiving an unexpected UMI response.
|
|
505
|
+
|
|
506
|
+
Raises
|
|
507
|
+
------
|
|
508
|
+
TypeError
|
|
509
|
+
If `value` is not a numpy integer datatype
|
|
510
|
+
|
|
511
|
+
Returns
|
|
512
|
+
-------
|
|
513
|
+
np.uint8, np.uint16, np.uint32, np.uint64
|
|
514
|
+
The value returned by this function is the original value at addr,
|
|
515
|
+
immediately before the atomic operation is applied. The numpy dtype of the
|
|
516
|
+
returned value will be the same as for "data".
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
# set defaults
|
|
520
|
+
|
|
521
|
+
if srcaddr is None:
|
|
522
|
+
srcaddr = self.def_atomic_srcaddr
|
|
523
|
+
|
|
524
|
+
srcaddr = int(srcaddr)
|
|
525
|
+
|
|
526
|
+
if error is None:
|
|
527
|
+
error = self.default_error
|
|
528
|
+
|
|
529
|
+
error = bool(error)
|
|
530
|
+
|
|
531
|
+
# resolve the opcode to an enum if needed
|
|
532
|
+
if isinstance(opcode, str):
|
|
533
|
+
opcode = getattr(UmiAtomic, f'UMI_REQ_ATOMIC{opcode.upper()}')
|
|
534
|
+
|
|
535
|
+
# format the data for sending
|
|
536
|
+
if isinstance(data, np.integer):
|
|
537
|
+
atomic_data = np.array(data, ndmin=1).view(np.uint8)
|
|
538
|
+
result = self.umi.atomic(addr, atomic_data, opcode, srcaddr, qos, prot, error)
|
|
539
|
+
return result.view(data.dtype)[0]
|
|
540
|
+
else:
|
|
541
|
+
raise TypeError("The data provided to atomic should be of a numpy integer type"
|
|
542
|
+
" so that the transaction size can be determined")
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def size2dtype(size: int, signed: bool = False, float: bool = False):
|
|
546
|
+
if float:
|
|
547
|
+
dtypes = [None, np.float16, np.float32, np.float64, np.float128]
|
|
548
|
+
elif signed:
|
|
549
|
+
dtypes = [np.int8, np.int16, np.int32, np.int64]
|
|
550
|
+
else:
|
|
551
|
+
dtypes = [np.uint8, np.uint16, np.uint32, np.uint64]
|
|
552
|
+
|
|
553
|
+
dtype = None
|
|
554
|
+
|
|
555
|
+
if size < len(dtypes):
|
|
556
|
+
dtype = dtypes[size]
|
|
557
|
+
|
|
558
|
+
if dtype is None:
|
|
559
|
+
raise ValueError(f'Size {size} unsupported with signed={signed} and float={float}')
|
|
560
|
+
|
|
561
|
+
return dtype
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def nbytes2size(nbytes: Integral):
|
|
565
|
+
if not isinstance(nbytes, Integral):
|
|
566
|
+
raise ValueError(f'Number of bytes must be an integer (got {nbytes})')
|
|
567
|
+
elif nbytes <= 0:
|
|
568
|
+
raise ValueError(f'Number of bytes must be positive (got {nbytes})')
|
|
569
|
+
|
|
570
|
+
nbytes = int(nbytes)
|
|
571
|
+
|
|
572
|
+
if bin(nbytes).count('1') != 1:
|
|
573
|
+
raise ValueError(f'Number of bytes must be a power of two (got {nbytes})')
|
|
574
|
+
|
|
575
|
+
size = nbytes.bit_length() - 1
|
|
576
|
+
|
|
577
|
+
if size < 0:
|
|
578
|
+
raise ValueError(f'size cannot be negative (got {size})')
|
|
579
|
+
|
|
580
|
+
return size
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def dtype2size(dtype: np.dtype):
|
|
584
|
+
if isinstance(dtype, np.dtype):
|
|
585
|
+
return nbytes2size(dtype.itemsize)
|
|
586
|
+
else:
|
|
587
|
+
raise ValueError(f'dtype must be of type np.dtype (got {type(dtype)})')
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def addr_aligned(addr: Integral, align: Integral) -> bool:
|
|
591
|
+
return ((addr >> align) << align) == addr
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def random_int_value(name, value, min, max, align=None):
|
|
595
|
+
# determine the length of the transaction
|
|
596
|
+
|
|
597
|
+
if isinstance(value, range) or (value is None):
|
|
598
|
+
if isinstance(value, range):
|
|
599
|
+
a = value.start
|
|
600
|
+
b = value.stop - 1
|
|
601
|
+
else:
|
|
602
|
+
a = min
|
|
603
|
+
b = max
|
|
604
|
+
|
|
605
|
+
value = random.randint(a, b)
|
|
606
|
+
|
|
607
|
+
if align is not None:
|
|
608
|
+
value >>= align
|
|
609
|
+
value <<= align
|
|
610
|
+
elif isinstance(value, (list, tuple, np.ndarray)):
|
|
611
|
+
value = random.choice(value)
|
|
612
|
+
|
|
613
|
+
if isinstance(value, (range, list, tuple)):
|
|
614
|
+
# if we happen to pick a range object from the list/tuple, then run this
|
|
615
|
+
# function on the range object. this allows users to specify a collection
|
|
616
|
+
# of values and ranges to efficiently represent a discontinuous space
|
|
617
|
+
# of options. it is also possible to have lists of lists of ranges, to
|
|
618
|
+
# adjust the probabilities of drawing from each range
|
|
619
|
+
return random_int_value(name=name, value=value, min=min, max=max, align=align)
|
|
620
|
+
|
|
621
|
+
# validate result
|
|
622
|
+
|
|
623
|
+
check_int_in_range(name, value, min=min, max=max)
|
|
624
|
+
|
|
625
|
+
value = int(value)
|
|
626
|
+
|
|
627
|
+
if align is not None:
|
|
628
|
+
if not addr_aligned(addr=value, align=align):
|
|
629
|
+
raise ValueError(f'misaligned {name}: 0x{value:x}')
|
|
630
|
+
|
|
631
|
+
# return result
|
|
632
|
+
|
|
633
|
+
return value
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def check_int_in_range(name, value, min=None, max=None):
|
|
637
|
+
if not np.issubdtype(type(value), np.integer):
|
|
638
|
+
raise ValueError(f'{name} is not an integer')
|
|
639
|
+
|
|
640
|
+
if (min is not None) and (value < min):
|
|
641
|
+
raise ValueError(f'{name} is less than {min}')
|
|
642
|
+
|
|
643
|
+
if (max is not None) and (value > max):
|
|
644
|
+
raise ValueError(f'{name} is greater than {max}')
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def random_umi_packet(
|
|
648
|
+
opcode=None,
|
|
649
|
+
len=None,
|
|
650
|
+
size=None,
|
|
651
|
+
dstaddr=None,
|
|
652
|
+
srcaddr=None,
|
|
653
|
+
data=None,
|
|
654
|
+
qos=0,
|
|
655
|
+
prot=0,
|
|
656
|
+
ex=0,
|
|
657
|
+
atype=0,
|
|
658
|
+
eom=1,
|
|
659
|
+
eof=1,
|
|
660
|
+
max_bytes=32
|
|
661
|
+
):
|
|
662
|
+
"""
|
|
663
|
+
Generates a Random UMI packet. All parameters are optional. Parameters that
|
|
664
|
+
are not explicitly specified will be assigned randomly.
|
|
665
|
+
|
|
666
|
+
For more information on the meanings of each parameter, reference
|
|
667
|
+
`the UMI specification <https://github.com/zeroasiccorp/umi/blob/main/README.md>`_
|
|
668
|
+
|
|
669
|
+
Parameters
|
|
670
|
+
----------
|
|
671
|
+
opcode: int, optional
|
|
672
|
+
Command opcode
|
|
673
|
+
|
|
674
|
+
len: int, optional
|
|
675
|
+
Word transfers per message. (`len`-1 words will be transferred
|
|
676
|
+
per message)
|
|
677
|
+
|
|
678
|
+
size: int, optional
|
|
679
|
+
Word size ((2^size)-1 bits per word)
|
|
680
|
+
|
|
681
|
+
dstaddr: int, optional
|
|
682
|
+
64-bit destination address used in the UMI packet
|
|
683
|
+
|
|
684
|
+
srcaddr: int, optional
|
|
685
|
+
64-bit source address used in the UMI packet
|
|
686
|
+
|
|
687
|
+
data: numpy integer array, optional
|
|
688
|
+
Values used in the Data field for the UMI packet
|
|
689
|
+
|
|
690
|
+
qos: int, optional
|
|
691
|
+
4-bit Quality of Service field used in the UMI command
|
|
692
|
+
|
|
693
|
+
prot: int, optional
|
|
694
|
+
2-bit Protection mode field used in the UMI command
|
|
695
|
+
|
|
696
|
+
ex: int, optional
|
|
697
|
+
1-bit Exclusive access indicator in the UMI command
|
|
698
|
+
|
|
699
|
+
atype: int, optional
|
|
700
|
+
8-bit field specifying the type of atomic transaction used
|
|
701
|
+
in the UMI command for an atomic operation
|
|
702
|
+
|
|
703
|
+
eom: int, optional
|
|
704
|
+
1-bit End of Message indicator in UMI command, used to track
|
|
705
|
+
the transfer of the last word in a message
|
|
706
|
+
|
|
707
|
+
eof: int, optional
|
|
708
|
+
1-bit End of Frame bit in UMI command, used to indicate the
|
|
709
|
+
last message in a sequence of related UMI transactions
|
|
710
|
+
|
|
711
|
+
max_bytes: int, optional
|
|
712
|
+
The maximum number of bytes included in each UMI packet
|
|
713
|
+
|
|
714
|
+
Returns
|
|
715
|
+
-------
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
# input validation
|
|
719
|
+
|
|
720
|
+
check_int_in_range("max_bytes", max_bytes, min=0, max=32)
|
|
721
|
+
|
|
722
|
+
# TODO: make these parameters flexible, or more centrally-defined
|
|
723
|
+
|
|
724
|
+
MAX_SUMI_SIZE = 3
|
|
725
|
+
AW = 64
|
|
726
|
+
|
|
727
|
+
# determine the opcode
|
|
728
|
+
|
|
729
|
+
if opcode is None:
|
|
730
|
+
opcode = random.choice([
|
|
731
|
+
UmiCmd.UMI_REQ_WRITE,
|
|
732
|
+
UmiCmd.UMI_REQ_POSTED,
|
|
733
|
+
UmiCmd.UMI_REQ_READ,
|
|
734
|
+
UmiCmd.UMI_RESP_WRITE,
|
|
735
|
+
UmiCmd.UMI_RESP_READ,
|
|
736
|
+
UmiCmd.UMI_REQ_ATOMIC
|
|
737
|
+
])
|
|
738
|
+
elif isinstance(opcode, Iterable):
|
|
739
|
+
opcode = random.choice(opcode)
|
|
740
|
+
|
|
741
|
+
# determine the size of the transaction
|
|
742
|
+
|
|
743
|
+
if (size is None) and (data is not None):
|
|
744
|
+
size = dtype2size(data.dtype)
|
|
745
|
+
|
|
746
|
+
size = random_int_value('size', size, 0, MAX_SUMI_SIZE)
|
|
747
|
+
|
|
748
|
+
# determine the length of the transaction
|
|
749
|
+
|
|
750
|
+
if (len is None) and (data is not None):
|
|
751
|
+
len = data.size - 1
|
|
752
|
+
|
|
753
|
+
len = random_int_value('len', len, 0, (max_bytes >> size) - 1)
|
|
754
|
+
|
|
755
|
+
# generate other fields
|
|
756
|
+
|
|
757
|
+
atype = random_int_value('atype', atype, 0x00, 0x08)
|
|
758
|
+
qos = random_int_value('qos', qos, 0b0000, 0b1111)
|
|
759
|
+
prot = random_int_value('prot', prot, 0b00, 0b11)
|
|
760
|
+
eom = random_int_value('eom', eom, 0b0, 0b1)
|
|
761
|
+
eof = random_int_value('eof', eof, 0b0, 0b1)
|
|
762
|
+
ex = random_int_value('ex', ex, 0b0, 0b1)
|
|
763
|
+
|
|
764
|
+
# construct the command field
|
|
765
|
+
|
|
766
|
+
cmd = umi_pack(opcode, atype, size, len, eom, eof, qos, prot, ex)
|
|
767
|
+
|
|
768
|
+
# generate destination address
|
|
769
|
+
|
|
770
|
+
dstaddr = random_int_value('dstaddr', dstaddr, 0, (1 << AW) - 1, align=size)
|
|
771
|
+
srcaddr = random_int_value('srcaddr', srcaddr, 0, (1 << AW) - 1, align=size)
|
|
772
|
+
|
|
773
|
+
# generate data if needed
|
|
774
|
+
|
|
775
|
+
if opcode in [
|
|
776
|
+
UmiCmd.UMI_REQ_WRITE,
|
|
777
|
+
UmiCmd.UMI_REQ_POSTED,
|
|
778
|
+
UmiCmd.UMI_RESP_READ,
|
|
779
|
+
UmiCmd.UMI_REQ_ATOMIC
|
|
780
|
+
]:
|
|
781
|
+
if data is None:
|
|
782
|
+
dtype = size2dtype(size)
|
|
783
|
+
iinfo = np.iinfo(dtype)
|
|
784
|
+
if opcode != UmiCmd.UMI_REQ_ATOMIC:
|
|
785
|
+
nelem = len + 1
|
|
786
|
+
else:
|
|
787
|
+
nelem = 1
|
|
788
|
+
data = np.random.randint(iinfo.min, iinfo.max - 1,
|
|
789
|
+
size=nelem, dtype=dtype)
|
|
790
|
+
|
|
791
|
+
# return the packet
|
|
792
|
+
|
|
793
|
+
return PyUmiPacket(cmd=cmd, dstaddr=dstaddr, srcaddr=srcaddr, data=data)
|