switchboard-hw 0.3.0__cp314-cp314-macosx_10_15_x86_64.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.
Files changed (99) hide show
  1. _switchboard.cpython-314-darwin.so +0 -0
  2. switchboard/__init__.py +24 -0
  3. switchboard/ams.py +668 -0
  4. switchboard/apb.py +278 -0
  5. switchboard/autowrap.py +1000 -0
  6. switchboard/axi.py +571 -0
  7. switchboard/axil.py +348 -0
  8. switchboard/bitvector.py +112 -0
  9. switchboard/cmdline.py +142 -0
  10. switchboard/cpp/Makefile +13 -0
  11. switchboard/cpp/bitutil.h +39 -0
  12. switchboard/cpp/pagemap.h +91 -0
  13. switchboard/cpp/pciedev.h +86 -0
  14. switchboard/cpp/router.cc +89 -0
  15. switchboard/cpp/spsc_queue.h +267 -0
  16. switchboard/cpp/switchboard.hpp +257 -0
  17. switchboard/cpp/switchboard_pcie.hpp +234 -0
  18. switchboard/cpp/switchboard_tlm.hpp +98 -0
  19. switchboard/cpp/umilib.h +144 -0
  20. switchboard/cpp/umilib.hpp +113 -0
  21. switchboard/cpp/umisb.hpp +364 -0
  22. switchboard/cpp/xyce.hpp +90 -0
  23. switchboard/deps/__init__.py +0 -0
  24. switchboard/deps/verilog_axi.py +23 -0
  25. switchboard/dpi/__init__.py +0 -0
  26. switchboard/dpi/switchboard_dpi.cc +119 -0
  27. switchboard/dpi/switchboard_dpi.py +13 -0
  28. switchboard/dpi/xyce_dpi.cc +43 -0
  29. switchboard/gpio.py +108 -0
  30. switchboard/icarus.py +85 -0
  31. switchboard/loopback.py +157 -0
  32. switchboard/network.py +714 -0
  33. switchboard/pytest_plugin.py +11 -0
  34. switchboard/sbdesign.py +55 -0
  35. switchboard/sbdut.py +744 -0
  36. switchboard/sbtcp.py +345 -0
  37. switchboard/sc/__init__.py +0 -0
  38. switchboard/sc/morty/__init__.py +0 -0
  39. switchboard/sc/morty/uniquify.py +67 -0
  40. switchboard/sc/sed/__init__.py +0 -0
  41. switchboard/sc/sed/sed_remove.py +47 -0
  42. switchboard/sc/standalone_netlist_flow.py +25 -0
  43. switchboard/switchboard.py +53 -0
  44. switchboard/test_util.py +46 -0
  45. switchboard/uart_xactor.py +66 -0
  46. switchboard/umi.py +793 -0
  47. switchboard/util.py +131 -0
  48. switchboard/verilator/__init__.py +0 -0
  49. switchboard/verilator/config.vlt +13 -0
  50. switchboard/verilator/testbench.cc +143 -0
  51. switchboard/verilator/verilator.py +13 -0
  52. switchboard/verilator_run.py +31 -0
  53. switchboard/verilog/__init__.py +0 -0
  54. switchboard/verilog/common/__init__.py +0 -0
  55. switchboard/verilog/common/common.py +26 -0
  56. switchboard/verilog/common/switchboard.vh +429 -0
  57. switchboard/verilog/common/uart_xactor.sv +247 -0
  58. switchboard/verilog/common/umi_gpio.v +236 -0
  59. switchboard/verilog/fpga/__init__.py +0 -0
  60. switchboard/verilog/fpga/axi_reader.sv +82 -0
  61. switchboard/verilog/fpga/axi_writer.sv +111 -0
  62. switchboard/verilog/fpga/config_registers.sv +249 -0
  63. switchboard/verilog/fpga/fpga.py +21 -0
  64. switchboard/verilog/fpga/include/sb_queue_regmap.vh +21 -0
  65. switchboard/verilog/fpga/include/spsc_queue.vh +7 -0
  66. switchboard/verilog/fpga/memory_fault.sv +40 -0
  67. switchboard/verilog/fpga/sb_fpga_queues.sv +416 -0
  68. switchboard/verilog/fpga/sb_rx_fpga.sv +303 -0
  69. switchboard/verilog/fpga/sb_tx_fpga.sv +294 -0
  70. switchboard/verilog/fpga/umi_fpga_queues.sv +146 -0
  71. switchboard/verilog/sim/__init__.py +0 -0
  72. switchboard/verilog/sim/auto_stop_sim.sv +25 -0
  73. switchboard/verilog/sim/perf_meas_sim.sv +97 -0
  74. switchboard/verilog/sim/queue_to_sb_sim.sv +176 -0
  75. switchboard/verilog/sim/queue_to_umi_sim.sv +66 -0
  76. switchboard/verilog/sim/sb_apb_m.sv +146 -0
  77. switchboard/verilog/sim/sb_axi_m.sv +199 -0
  78. switchboard/verilog/sim/sb_axil_m.sv +180 -0
  79. switchboard/verilog/sim/sb_axil_s.sv +180 -0
  80. switchboard/verilog/sim/sb_clk_gen.sv +89 -0
  81. switchboard/verilog/sim/sb_jtag_rbb_sim.sv +148 -0
  82. switchboard/verilog/sim/sb_rx_sim.sv +55 -0
  83. switchboard/verilog/sim/sb_to_queue_sim.sv +196 -0
  84. switchboard/verilog/sim/sb_tx_sim.sv +55 -0
  85. switchboard/verilog/sim/switchboard_sim.py +49 -0
  86. switchboard/verilog/sim/umi_rx_sim.sv +61 -0
  87. switchboard/verilog/sim/umi_to_queue_sim.sv +66 -0
  88. switchboard/verilog/sim/umi_tx_sim.sv +61 -0
  89. switchboard/verilog/sim/xyce_intf.sv +67 -0
  90. switchboard/vpi/switchboard_vpi.cc +431 -0
  91. switchboard/vpi/xyce_vpi.cc +200 -0
  92. switchboard/warn.py +14 -0
  93. switchboard/xyce.py +27 -0
  94. switchboard_hw-0.3.0.dist-info/METADATA +303 -0
  95. switchboard_hw-0.3.0.dist-info/RECORD +99 -0
  96. switchboard_hw-0.3.0.dist-info/WHEEL +6 -0
  97. switchboard_hw-0.3.0.dist-info/entry_points.txt +6 -0
  98. switchboard_hw-0.3.0.dist-info/licenses/LICENSE +190 -0
  99. 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)