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/network.py
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
# Copyright (c) 2024 Zero ASIC Corporation
|
|
2
|
+
# This code is licensed under Apache License 2.0 (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
from typing import Set
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from itertools import count
|
|
9
|
+
from numbers import Integral
|
|
10
|
+
|
|
11
|
+
from .sbdut import SbDut
|
|
12
|
+
from .axi import axi_uris
|
|
13
|
+
from .apb import apb_uris
|
|
14
|
+
from .autowrap import (directions_are_compatible, normalize_intf_type,
|
|
15
|
+
type_is_umi, type_is_sb, create_intf_objs, type_is_axi, type_is_axil, type_is_apb,
|
|
16
|
+
autowrap, flip_intf, normalize_direction, WireExpr, types_are_compatible)
|
|
17
|
+
from .cmdline import get_cmdline_args
|
|
18
|
+
from .sbtcp import start_tcp_bridge
|
|
19
|
+
from .util import ProcessCollection
|
|
20
|
+
|
|
21
|
+
from _switchboard import delete_queues
|
|
22
|
+
|
|
23
|
+
from siliconcompiler import Design
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SbIntf:
|
|
27
|
+
def __init__(self, inst, name, width=None, indices=None):
|
|
28
|
+
self.inst = inst
|
|
29
|
+
self.name = name
|
|
30
|
+
self.width = width
|
|
31
|
+
|
|
32
|
+
if (indices is None) and (width is not None):
|
|
33
|
+
indices = slice(width - 1, 0, 1)
|
|
34
|
+
|
|
35
|
+
self.slice = indices
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def intf_def(self):
|
|
39
|
+
return self.inst.block.intf_defs[self.name]
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def wire_name(self):
|
|
43
|
+
return f'{self.inst.name}_{self.name}'
|
|
44
|
+
|
|
45
|
+
def __getitem__(self, key):
|
|
46
|
+
if not isinstance(key, slice):
|
|
47
|
+
key = slice(key, key)
|
|
48
|
+
|
|
49
|
+
return SbIntf(inst=self.inst, name=self.name, indices=key)
|
|
50
|
+
|
|
51
|
+
def slice_as_str(self):
|
|
52
|
+
if self.slice is None:
|
|
53
|
+
return ''
|
|
54
|
+
else:
|
|
55
|
+
return f'[{self.slice.start}:{self.slice.stop}]'
|
|
56
|
+
|
|
57
|
+
def compute_slice_width(self):
|
|
58
|
+
if self.slice.start is not None:
|
|
59
|
+
start = self.slice.start
|
|
60
|
+
else:
|
|
61
|
+
start = self.width - 1
|
|
62
|
+
|
|
63
|
+
if self.slice.stop is not None:
|
|
64
|
+
stop = self.slice.stop
|
|
65
|
+
else:
|
|
66
|
+
stop = 0
|
|
67
|
+
|
|
68
|
+
return start - stop + 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ConstIntf:
|
|
72
|
+
def __init__(self, value):
|
|
73
|
+
self.value = value
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def intf_def(self):
|
|
77
|
+
return dict(
|
|
78
|
+
type='const',
|
|
79
|
+
direction='output'
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def value_as_str(self, width=None, format='decimal'):
|
|
83
|
+
if width is None:
|
|
84
|
+
width = ''
|
|
85
|
+
else:
|
|
86
|
+
width = str(width)
|
|
87
|
+
|
|
88
|
+
if format.lower() == 'decimal':
|
|
89
|
+
return f"{width}'d{self.value}"
|
|
90
|
+
elif format.lower() == 'hex':
|
|
91
|
+
return f"{width}'h{hex(self.value)[2:]}"
|
|
92
|
+
elif format.lower() == 'hex':
|
|
93
|
+
return f"{width}'b{bin(self.value)[2:]}"
|
|
94
|
+
else:
|
|
95
|
+
raise Exception(f'Unsupported format: {format}')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TcpIntf:
|
|
99
|
+
def __init__(self, intf_def=None, destination=None, **kwargs):
|
|
100
|
+
self.intf_def = intf_def
|
|
101
|
+
self.destination = destination
|
|
102
|
+
self.kwargs = kwargs
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def wire_name(self):
|
|
106
|
+
if 'port' in self.kwargs:
|
|
107
|
+
retval = f'port_{self.kwargs["port"]}'
|
|
108
|
+
if self.destination is not None:
|
|
109
|
+
retval += f'_{self.destination}'
|
|
110
|
+
return retval
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SbInst:
|
|
114
|
+
def __init__(self, name, block):
|
|
115
|
+
self.name = name
|
|
116
|
+
self.block = block
|
|
117
|
+
self.mapping = {}
|
|
118
|
+
self.external = set()
|
|
119
|
+
|
|
120
|
+
for name, value in block.intf_defs.items():
|
|
121
|
+
if value['type'] != 'plusarg':
|
|
122
|
+
self.mapping[name] = dict(uri=None, wire=None)
|
|
123
|
+
width = block.intf_defs[name].get('width', None)
|
|
124
|
+
self.__setattr__(name, SbIntf(inst=self, name=name, width=width))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class SingleNetlistNetwork(Design):
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
unique_blocks: Set[SbInst],
|
|
131
|
+
wrapper_filename: str,
|
|
132
|
+
fileset: str,
|
|
133
|
+
insts
|
|
134
|
+
):
|
|
135
|
+
super().__init__("SNDesign")
|
|
136
|
+
unique_blocks = unique_blocks
|
|
137
|
+
|
|
138
|
+
# Pickle each block in the network
|
|
139
|
+
pickled_sources = [block.package() for block in unique_blocks]
|
|
140
|
+
|
|
141
|
+
# populate the interfaces dictionary
|
|
142
|
+
interfaces = {}
|
|
143
|
+
|
|
144
|
+
for inst_name, inst in insts.items():
|
|
145
|
+
# make a copy of the interface definitions for this block
|
|
146
|
+
intf_defs = deepcopy(inst.block.intf_defs)
|
|
147
|
+
|
|
148
|
+
# wiring
|
|
149
|
+
for intf_name, props in inst.mapping.items():
|
|
150
|
+
intf_defs[intf_name]['wire'] = props['wire']
|
|
151
|
+
intf_defs[intf_name]['external'] = intf_name in inst.external
|
|
152
|
+
|
|
153
|
+
# prepend instance name to init interfaces
|
|
154
|
+
for value in intf_defs.values():
|
|
155
|
+
if value['type'] == 'plusarg':
|
|
156
|
+
value['wire'] = f"{inst_name}_{value['wire']}"
|
|
157
|
+
value['plusarg'] = f"{inst_name}_{value['plusarg']}"
|
|
158
|
+
|
|
159
|
+
interfaces[inst_name] = intf_defs
|
|
160
|
+
|
|
161
|
+
# generate netlist that connects everything together, and input() it
|
|
162
|
+
top_lvl = autowrap(
|
|
163
|
+
instances={inst.name: inst.block.get_topmodule_name() for inst in insts.values()},
|
|
164
|
+
toplevel='testbench',
|
|
165
|
+
parameters={inst.name: inst.block.parameters for inst in insts.values()},
|
|
166
|
+
interfaces=interfaces,
|
|
167
|
+
clocks={inst.name: inst.block.clocks for inst in insts.values()},
|
|
168
|
+
resets={inst.name: inst.block.resets for inst in insts.values()},
|
|
169
|
+
tieoffs={inst.name: inst.block.tieoffs for inst in insts.values()},
|
|
170
|
+
filename=wrapper_filename
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
sources = pickled_sources + [str(top_lvl)]
|
|
174
|
+
|
|
175
|
+
from switchboard.verilog.sim.switchboard_sim import SwitchboardSim
|
|
176
|
+
|
|
177
|
+
with self.active_fileset(fileset):
|
|
178
|
+
self.set_topmodule("testbench")
|
|
179
|
+
self.add_depfileset(SwitchboardSim())
|
|
180
|
+
for file in sources:
|
|
181
|
+
self.add_file(file)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class SbNetwork:
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
cmdline=False,
|
|
188
|
+
tool: str = 'verilator',
|
|
189
|
+
trace: bool = False,
|
|
190
|
+
trace_type: str = 'vcd',
|
|
191
|
+
frequency: float = 100e6,
|
|
192
|
+
period: float = None,
|
|
193
|
+
max_rate: float = -1,
|
|
194
|
+
start_delay: float = None,
|
|
195
|
+
fast: bool = False,
|
|
196
|
+
extra_args: dict = None,
|
|
197
|
+
cleanup: bool = True,
|
|
198
|
+
args=None,
|
|
199
|
+
single_netlist: bool = False,
|
|
200
|
+
threads: int = None,
|
|
201
|
+
name: str = None
|
|
202
|
+
):
|
|
203
|
+
|
|
204
|
+
self.insts = {}
|
|
205
|
+
|
|
206
|
+
self.inst_name_set = set()
|
|
207
|
+
self.inst_name_counters = {}
|
|
208
|
+
|
|
209
|
+
self.uri_set = set()
|
|
210
|
+
self.uri_counters = {}
|
|
211
|
+
|
|
212
|
+
self.tcp_intfs = {}
|
|
213
|
+
|
|
214
|
+
if cmdline:
|
|
215
|
+
self.args = get_cmdline_args(tool=tool, trace=trace, trace_type=trace_type,
|
|
216
|
+
frequency=frequency, period=period, fast=fast, max_rate=max_rate,
|
|
217
|
+
start_delay=start_delay, single_netlist=single_netlist, threads=threads,
|
|
218
|
+
extra_args=extra_args)
|
|
219
|
+
elif args is not None:
|
|
220
|
+
self.args = args
|
|
221
|
+
|
|
222
|
+
if hasattr(self, 'args'):
|
|
223
|
+
trace = self.args.trace
|
|
224
|
+
trace_type = self.args.trace_type
|
|
225
|
+
fast = self.args.fast
|
|
226
|
+
tool = self.args.tool
|
|
227
|
+
frequency = self.args.frequency
|
|
228
|
+
period = self.args.period
|
|
229
|
+
max_rate = self.args.max_rate
|
|
230
|
+
start_delay = self.args.start_delay
|
|
231
|
+
single_netlist = self.args.single_netlist
|
|
232
|
+
else:
|
|
233
|
+
# create args object to pass down to SbDut
|
|
234
|
+
from types import SimpleNamespace
|
|
235
|
+
self.args = SimpleNamespace(
|
|
236
|
+
trace=trace,
|
|
237
|
+
trace_type=trace_type,
|
|
238
|
+
fast=fast,
|
|
239
|
+
tool=tool,
|
|
240
|
+
frequency=frequency,
|
|
241
|
+
period=period,
|
|
242
|
+
max_rate=max_rate,
|
|
243
|
+
start_delay=start_delay,
|
|
244
|
+
threads=threads
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# save settings
|
|
248
|
+
|
|
249
|
+
self.tool = tool
|
|
250
|
+
self.trace = trace
|
|
251
|
+
self.trace_type = trace_type
|
|
252
|
+
self.fast = fast
|
|
253
|
+
|
|
254
|
+
if (period is None) and (frequency is not None):
|
|
255
|
+
period = 1 / frequency
|
|
256
|
+
self.period = period
|
|
257
|
+
self.max_rate = max_rate
|
|
258
|
+
self.start_delay = start_delay
|
|
259
|
+
|
|
260
|
+
self.single_netlist = single_netlist
|
|
261
|
+
|
|
262
|
+
if single_netlist:
|
|
263
|
+
self.single_netlist_dut = SbDut(design="single_netlist_network", args=self.args)
|
|
264
|
+
else:
|
|
265
|
+
self._intf_defs = {}
|
|
266
|
+
|
|
267
|
+
self.name = name
|
|
268
|
+
|
|
269
|
+
# keep track of processes started
|
|
270
|
+
self.process_collection = ProcessCollection()
|
|
271
|
+
|
|
272
|
+
if cleanup:
|
|
273
|
+
import atexit
|
|
274
|
+
|
|
275
|
+
def cleanup_func(uri_set=self.uri_set):
|
|
276
|
+
if len(uri_set) > 0:
|
|
277
|
+
delete_queues(list(uri_set))
|
|
278
|
+
|
|
279
|
+
atexit.register(cleanup_func)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def intf_defs(self):
|
|
283
|
+
if self.single_netlist:
|
|
284
|
+
return self.single_netlist_dut.intf_defs
|
|
285
|
+
else:
|
|
286
|
+
return self._intf_defs
|
|
287
|
+
|
|
288
|
+
def instantiate(self, block, name: str = None):
|
|
289
|
+
# generate a name if needed
|
|
290
|
+
if name is None:
|
|
291
|
+
if isinstance(block, SbDut):
|
|
292
|
+
prefix = block.design.name
|
|
293
|
+
else:
|
|
294
|
+
prefix = block.name
|
|
295
|
+
|
|
296
|
+
assert prefix is not None, ('Cannot generate name for this instance.'
|
|
297
|
+
' When block is an SbNetwork, make sure that its constructor set'
|
|
298
|
+
' "name" if you want name generation to work here.')
|
|
299
|
+
|
|
300
|
+
name = self.generate_inst_name(prefix=prefix)
|
|
301
|
+
|
|
302
|
+
# make sure the name hasn't been used already
|
|
303
|
+
assert name not in self.inst_name_set
|
|
304
|
+
|
|
305
|
+
# add the name to the set of names in use
|
|
306
|
+
self.inst_name_set.add(name)
|
|
307
|
+
|
|
308
|
+
# create the instance object
|
|
309
|
+
self.insts[name] = SbInst(name=name, block=block)
|
|
310
|
+
|
|
311
|
+
# return the instance object
|
|
312
|
+
return self.insts[name]
|
|
313
|
+
|
|
314
|
+
def connect(self, a, b, uri=None, wire=None):
|
|
315
|
+
# convert integer inputs into constant datatype
|
|
316
|
+
if isinstance(a, Integral):
|
|
317
|
+
a = ConstIntf(value=a)
|
|
318
|
+
if isinstance(b, Integral):
|
|
319
|
+
b = ConstIntf(value=b)
|
|
320
|
+
|
|
321
|
+
# retrieve the two interface definitions
|
|
322
|
+
intf_def_a = a.intf_def
|
|
323
|
+
intf_def_b = b.intf_def
|
|
324
|
+
|
|
325
|
+
if intf_def_a is None:
|
|
326
|
+
assert intf_def_b is not None, 'Cannot infer interface type'
|
|
327
|
+
intf_def_a = flip_intf(intf_def_b)
|
|
328
|
+
|
|
329
|
+
if intf_def_b is None:
|
|
330
|
+
assert intf_def_a is not None, 'Cannot infer interface type'
|
|
331
|
+
intf_def_b = flip_intf(intf_def_a)
|
|
332
|
+
|
|
333
|
+
# make sure that the interfaces are compatible
|
|
334
|
+
type_a = normalize_intf_type(intf_def_a['type'])
|
|
335
|
+
type_b = normalize_intf_type(intf_def_b['type'])
|
|
336
|
+
assert types_are_compatible(type_a, type_b)
|
|
337
|
+
|
|
338
|
+
# make sure that the directions are compatible
|
|
339
|
+
direction_a = normalize_direction(type_a, intf_def_a['direction'])
|
|
340
|
+
direction_b = normalize_direction(type_b, intf_def_b['direction'])
|
|
341
|
+
assert directions_are_compatible(
|
|
342
|
+
type_a=type_a, a=direction_a,
|
|
343
|
+
type_b=type_b, b=direction_b
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# indicate which is input vs. output. we have to look at both type a and
|
|
347
|
+
# type b since one may be a constant
|
|
348
|
+
if (type_a == 'gpio') or (type_b == 'gpio'):
|
|
349
|
+
if (direction_a == 'input') or (direction_b == 'output'):
|
|
350
|
+
input, output = a, b
|
|
351
|
+
direction_a, direction_b = 'input', 'output'
|
|
352
|
+
elif (direction_b == 'input') or (direction_a == 'output'):
|
|
353
|
+
input, output = b, a
|
|
354
|
+
direction_b, direction_a = 'input', 'output'
|
|
355
|
+
else:
|
|
356
|
+
raise Exception(f'Cannot infer connection direction with direction_a={direction_a}'
|
|
357
|
+
f' and direction_b={direction_b}')
|
|
358
|
+
|
|
359
|
+
intf_def_a['direction'] = direction_a
|
|
360
|
+
intf_def_b['direction'] = direction_b
|
|
361
|
+
|
|
362
|
+
# determine what the queue will be called that connects the two
|
|
363
|
+
|
|
364
|
+
if wire is None:
|
|
365
|
+
if (type_a != 'gpio') and (type_b != 'gpio'):
|
|
366
|
+
wire = f'{a.wire_name}_conn_{b.wire_name}'
|
|
367
|
+
elif not isinstance(output, ConstIntf):
|
|
368
|
+
wire = f'{output.inst.name}_{output.name}'
|
|
369
|
+
|
|
370
|
+
if (uri is None) and (wire is not None):
|
|
371
|
+
uri = wire
|
|
372
|
+
|
|
373
|
+
if type_is_sb(type_a) or type_is_umi(type_a):
|
|
374
|
+
uri = uri + '.q'
|
|
375
|
+
|
|
376
|
+
if (not self.single_netlist) and (type_a != 'gpio') and (type_b != 'gpio'):
|
|
377
|
+
self.register_uri(type=type_a, uri=uri)
|
|
378
|
+
|
|
379
|
+
# tell both instances what they are connected to
|
|
380
|
+
|
|
381
|
+
if (type_a != 'gpio') and (type_b != 'gpio'):
|
|
382
|
+
if not isinstance(a, TcpIntf):
|
|
383
|
+
a.inst.mapping[a.name]['wire'] = wire
|
|
384
|
+
a.inst.mapping[a.name]['uri'] = uri
|
|
385
|
+
|
|
386
|
+
if not isinstance(b, TcpIntf):
|
|
387
|
+
b.inst.mapping[b.name]['wire'] = wire
|
|
388
|
+
b.inst.mapping[b.name]['uri'] = uri
|
|
389
|
+
else:
|
|
390
|
+
if input.inst.mapping[input.name]['wire'] is None:
|
|
391
|
+
expr = WireExpr(input.intf_def['width'])
|
|
392
|
+
input.inst.mapping[input.name]['wire'] = expr
|
|
393
|
+
|
|
394
|
+
if isinstance(output, ConstIntf):
|
|
395
|
+
input.inst.mapping[input.name]['wire'].bind(
|
|
396
|
+
input.slice, output.value_as_str(width=input.compute_slice_width()))
|
|
397
|
+
else:
|
|
398
|
+
input.inst.mapping[input.name]['wire'].bind(
|
|
399
|
+
input.slice, f'{wire}{output.slice_as_str()}')
|
|
400
|
+
output.inst.mapping[output.name]['wire'] = wire
|
|
401
|
+
|
|
402
|
+
# make a note of TCP bridges that need to be started
|
|
403
|
+
|
|
404
|
+
for intf, intf_def in [(a, intf_def_a), (b, intf_def_b)]:
|
|
405
|
+
if isinstance(intf, TcpIntf):
|
|
406
|
+
self.add_tcp_intf(intf=intf, intf_def=intf_def, uri=uri)
|
|
407
|
+
|
|
408
|
+
def add_tcp_intf(self, intf, intf_def, uri):
|
|
409
|
+
tcp_kwargs = deepcopy(intf.kwargs)
|
|
410
|
+
|
|
411
|
+
if 'host' not in tcp_kwargs:
|
|
412
|
+
tcp_kwargs['host'] = 'localhost'
|
|
413
|
+
|
|
414
|
+
if 'port' not in tcp_kwargs:
|
|
415
|
+
tcp_kwargs['port'] = 5555
|
|
416
|
+
|
|
417
|
+
if 'mode' not in tcp_kwargs:
|
|
418
|
+
tcp_kwargs['mode'] = 'auto'
|
|
419
|
+
|
|
420
|
+
if 'quiet' not in tcp_kwargs:
|
|
421
|
+
tcp_kwargs['quiet'] = True
|
|
422
|
+
|
|
423
|
+
if 'max_rate' not in intf.kwargs:
|
|
424
|
+
tcp_kwargs['max_rate'] = self.max_rate
|
|
425
|
+
|
|
426
|
+
if 'run_once' not in tcp_kwargs:
|
|
427
|
+
tcp_kwargs['run_once'] = False
|
|
428
|
+
|
|
429
|
+
tcp_intfs_key = (tcp_kwargs['host'], tcp_kwargs['port'], tcp_kwargs['mode'])
|
|
430
|
+
|
|
431
|
+
if tcp_intfs_key not in self.tcp_intfs:
|
|
432
|
+
self.tcp_intfs[tcp_intfs_key] = tcp_kwargs
|
|
433
|
+
else:
|
|
434
|
+
for key, val in self.tcp_intfs[tcp_intfs_key].items():
|
|
435
|
+
if key not in ['inputs', 'outputs']:
|
|
436
|
+
if val != tcp_kwargs[key]:
|
|
437
|
+
raise ValueError(f'Mismatch on TCP interface property "{key}".'
|
|
438
|
+
f' New value of property is "{tcp_kwargs[key]}"'
|
|
439
|
+
f' but it was previously set to "{val}".')
|
|
440
|
+
|
|
441
|
+
tcp_intf = self.tcp_intfs[tcp_intfs_key]
|
|
442
|
+
|
|
443
|
+
tcp_direction = intf_def['direction']
|
|
444
|
+
|
|
445
|
+
if tcp_direction == 'input':
|
|
446
|
+
assert 'outputs' not in tcp_intf
|
|
447
|
+
|
|
448
|
+
if 'inputs' not in tcp_intf:
|
|
449
|
+
tcp_intf['inputs'] = []
|
|
450
|
+
|
|
451
|
+
if intf.destination is None:
|
|
452
|
+
input = uri
|
|
453
|
+
else:
|
|
454
|
+
input = (intf.destination, uri)
|
|
455
|
+
|
|
456
|
+
tcp_intf['inputs'].append(input)
|
|
457
|
+
elif tcp_direction == 'output':
|
|
458
|
+
assert 'inputs' not in tcp_intf
|
|
459
|
+
|
|
460
|
+
if 'outputs' not in tcp_intf:
|
|
461
|
+
tcp_intf['outputs'] = []
|
|
462
|
+
|
|
463
|
+
if intf.destination is None:
|
|
464
|
+
destination = '*'
|
|
465
|
+
else:
|
|
466
|
+
destination = intf.destination
|
|
467
|
+
|
|
468
|
+
output = (destination, uri)
|
|
469
|
+
|
|
470
|
+
tcp_intf['outputs'].append(output)
|
|
471
|
+
else:
|
|
472
|
+
raise Exception(f'Unsupported direction: {tcp_direction}')
|
|
473
|
+
|
|
474
|
+
def build(self):
|
|
475
|
+
unique_blocks: Set[SbInst] = set(inst.block for inst in self.insts.values())
|
|
476
|
+
|
|
477
|
+
if self.single_netlist:
|
|
478
|
+
|
|
479
|
+
filename = Path(
|
|
480
|
+
self.single_netlist_dut.option.get_builddir()
|
|
481
|
+
).resolve() / 'testbench.sv'
|
|
482
|
+
filename.parent.mkdir(exist_ok=True, parents=True)
|
|
483
|
+
|
|
484
|
+
self.single_netlist_dut.set_design(
|
|
485
|
+
SingleNetlistNetwork(
|
|
486
|
+
unique_blocks=unique_blocks,
|
|
487
|
+
fileset=self.tool,
|
|
488
|
+
wrapper_filename=filename,
|
|
489
|
+
insts=self.insts
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
self.single_netlist_dut.add_fileset(self.tool)
|
|
493
|
+
|
|
494
|
+
# build the single-netlist simulation
|
|
495
|
+
self.single_netlist_dut.build()
|
|
496
|
+
else:
|
|
497
|
+
for block in unique_blocks:
|
|
498
|
+
block.build()
|
|
499
|
+
|
|
500
|
+
def external(self, intf, name=None, txrx=None, uri=None, wire=None):
|
|
501
|
+
# make a copy of the interface definition since we will be modifying it
|
|
502
|
+
|
|
503
|
+
assert intf.intf_def is not None, 'Cannot infer interface type'
|
|
504
|
+
intf_def = deepcopy(intf.intf_def)
|
|
505
|
+
|
|
506
|
+
# generate URI if needed
|
|
507
|
+
|
|
508
|
+
type = intf_def['type']
|
|
509
|
+
|
|
510
|
+
if wire is None:
|
|
511
|
+
wire = intf.wire_name
|
|
512
|
+
|
|
513
|
+
intf_def['wire'] = wire
|
|
514
|
+
|
|
515
|
+
if uri is None:
|
|
516
|
+
uri = wire
|
|
517
|
+
|
|
518
|
+
if type_is_sb(type) or type_is_umi(type):
|
|
519
|
+
uri = uri + '.q'
|
|
520
|
+
|
|
521
|
+
intf_def['uri'] = uri
|
|
522
|
+
|
|
523
|
+
# register the URI to make sure it doesn't collide with anything else
|
|
524
|
+
|
|
525
|
+
self.register_uri(type=type, uri=uri)
|
|
526
|
+
|
|
527
|
+
# propagate information about the URI mapping
|
|
528
|
+
|
|
529
|
+
if not isinstance(intf, TcpIntf):
|
|
530
|
+
intf.inst.mapping[intf.name] = dict(uri=uri, wire=wire)
|
|
531
|
+
intf.inst.external.add(intf.name)
|
|
532
|
+
|
|
533
|
+
# set txrx
|
|
534
|
+
|
|
535
|
+
intf_def['txrx'] = txrx
|
|
536
|
+
|
|
537
|
+
# set max rate
|
|
538
|
+
|
|
539
|
+
if self.max_rate is not None:
|
|
540
|
+
intf_def['max_rate'] = self.max_rate
|
|
541
|
+
|
|
542
|
+
# save interface
|
|
543
|
+
|
|
544
|
+
if name is None:
|
|
545
|
+
name = intf.wire_name
|
|
546
|
+
|
|
547
|
+
assert name not in self.intf_defs, \
|
|
548
|
+
f'Network already contains an external interface called "{name}".'
|
|
549
|
+
|
|
550
|
+
self.intf_defs[name] = intf_def
|
|
551
|
+
|
|
552
|
+
# make a note of TCP bridges that need to be started
|
|
553
|
+
|
|
554
|
+
if isinstance(intf, TcpIntf):
|
|
555
|
+
self.add_tcp_intf(intf=intf, intf_def=intf_def, uri=uri)
|
|
556
|
+
|
|
557
|
+
return name
|
|
558
|
+
|
|
559
|
+
def simulate(self, start_delay=None, run=None, intf_objs=True, plusargs=None):
|
|
560
|
+
|
|
561
|
+
# set defaults
|
|
562
|
+
|
|
563
|
+
if start_delay is None:
|
|
564
|
+
start_delay = self.start_delay
|
|
565
|
+
|
|
566
|
+
if plusargs is None:
|
|
567
|
+
plusargs = []
|
|
568
|
+
|
|
569
|
+
# create interface objects
|
|
570
|
+
|
|
571
|
+
if self.single_netlist:
|
|
572
|
+
if isinstance(plusargs, dict):
|
|
573
|
+
plusargs_processed = []
|
|
574
|
+
for inst_name, inst_plusargs in plusargs.items():
|
|
575
|
+
if inst_name == '*':
|
|
576
|
+
plusargs_processed += inst_plusargs
|
|
577
|
+
else:
|
|
578
|
+
for inst_plusarg, value in inst_plusargs:
|
|
579
|
+
plusargs_processed.append((f"{inst_name}_{inst_plusarg}", value))
|
|
580
|
+
plusargs = plusargs_processed
|
|
581
|
+
|
|
582
|
+
process = self.single_netlist_dut.simulate(
|
|
583
|
+
start_delay=start_delay,
|
|
584
|
+
run=run,
|
|
585
|
+
intf_objs=intf_objs,
|
|
586
|
+
plusargs=plusargs
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
self.process_collection.add(process)
|
|
590
|
+
|
|
591
|
+
if intf_objs:
|
|
592
|
+
self.intfs = self.single_netlist_dut.intfs
|
|
593
|
+
else:
|
|
594
|
+
if intf_objs:
|
|
595
|
+
self.intfs = create_intf_objs(self.intf_defs)
|
|
596
|
+
|
|
597
|
+
if start_delay is not None:
|
|
598
|
+
import time
|
|
599
|
+
start = time.time()
|
|
600
|
+
|
|
601
|
+
insts = self.insts.values()
|
|
602
|
+
|
|
603
|
+
if len(insts) > 1:
|
|
604
|
+
try:
|
|
605
|
+
from tqdm import tqdm
|
|
606
|
+
insts = tqdm(insts)
|
|
607
|
+
except ModuleNotFoundError:
|
|
608
|
+
pass
|
|
609
|
+
|
|
610
|
+
for inst in insts:
|
|
611
|
+
block = inst.block
|
|
612
|
+
|
|
613
|
+
for intf_name, props in inst.mapping.items():
|
|
614
|
+
block.intf_defs[intf_name]['uri'] = props['uri']
|
|
615
|
+
|
|
616
|
+
# calculate the start delay for this process by measuring the
|
|
617
|
+
# time left until the start delay for the whole network is over
|
|
618
|
+
|
|
619
|
+
if start_delay is not None:
|
|
620
|
+
now = time.time()
|
|
621
|
+
dt = now - start
|
|
622
|
+
if dt < start_delay:
|
|
623
|
+
start_delay = start_delay - dt
|
|
624
|
+
else:
|
|
625
|
+
start_delay = None
|
|
626
|
+
else:
|
|
627
|
+
start_delay = None
|
|
628
|
+
|
|
629
|
+
# launch an instance of simulation
|
|
630
|
+
|
|
631
|
+
if isinstance(plusargs, dict):
|
|
632
|
+
inst_plusargs = []
|
|
633
|
+
inst_plusargs += plusargs.get('*', [])
|
|
634
|
+
inst_plusargs += plusargs.get(inst.name, [])
|
|
635
|
+
else:
|
|
636
|
+
inst_plusargs = plusargs
|
|
637
|
+
|
|
638
|
+
process = block.simulate(start_delay=start_delay, run=inst.name,
|
|
639
|
+
intf_objs=False, plusargs=inst_plusargs)
|
|
640
|
+
|
|
641
|
+
self.process_collection.add(process)
|
|
642
|
+
|
|
643
|
+
# start TCP bridges as needed
|
|
644
|
+
for tcp_kwargs in self.tcp_intfs.values():
|
|
645
|
+
process = start_tcp_bridge(**tcp_kwargs)
|
|
646
|
+
self.process_collection.add(process)
|
|
647
|
+
|
|
648
|
+
return self.process_collection
|
|
649
|
+
|
|
650
|
+
def terminate(
|
|
651
|
+
self,
|
|
652
|
+
stop_timeout=10,
|
|
653
|
+
use_sigint=False
|
|
654
|
+
):
|
|
655
|
+
self.process_collection.terminate(stop_timeout=stop_timeout, use_sigint=use_sigint)
|
|
656
|
+
|
|
657
|
+
def generate_inst_name(self, prefix):
|
|
658
|
+
if prefix not in self.inst_name_counters:
|
|
659
|
+
self.inst_name_counters[prefix] = count(0)
|
|
660
|
+
|
|
661
|
+
counter = self.inst_name_counters[prefix]
|
|
662
|
+
|
|
663
|
+
while True:
|
|
664
|
+
name = f'{prefix}_{next(counter)}'
|
|
665
|
+
|
|
666
|
+
if name not in self.inst_name_set:
|
|
667
|
+
break
|
|
668
|
+
|
|
669
|
+
return name
|
|
670
|
+
|
|
671
|
+
def register_uri(self, type, uri, fresh=True):
|
|
672
|
+
if type_is_axi(type) or type_is_axil(type):
|
|
673
|
+
uris = axi_uris(uri)
|
|
674
|
+
elif type_is_apb(type):
|
|
675
|
+
uris = apb_uris(uri)
|
|
676
|
+
else:
|
|
677
|
+
uris = [uri]
|
|
678
|
+
|
|
679
|
+
uris = set(uris)
|
|
680
|
+
intersection = self.uri_set.intersection(uris)
|
|
681
|
+
|
|
682
|
+
if len(intersection) > 0:
|
|
683
|
+
raise ValueError('Failed to add the following URI(s) that'
|
|
684
|
+
f' are already in use: {list(intersection)}')
|
|
685
|
+
|
|
686
|
+
self.uri_set.update(uris)
|
|
687
|
+
|
|
688
|
+
if fresh:
|
|
689
|
+
delete_queues(list(uris))
|
|
690
|
+
|
|
691
|
+
def make_dut(self, *args, **kwargs):
|
|
692
|
+
# argument customizations
|
|
693
|
+
|
|
694
|
+
cfg = {}
|
|
695
|
+
|
|
696
|
+
cfg['args'] = self.args
|
|
697
|
+
|
|
698
|
+
if self.single_netlist:
|
|
699
|
+
cfg['autowrap'] = False
|
|
700
|
+
cfg['subcomponent'] = True
|
|
701
|
+
else:
|
|
702
|
+
cfg['autowrap'] = True
|
|
703
|
+
cfg['subcomponent'] = False
|
|
704
|
+
|
|
705
|
+
# add to keyword arguments without clobbering
|
|
706
|
+
# existing entries
|
|
707
|
+
|
|
708
|
+
kwargs = deepcopy(kwargs)
|
|
709
|
+
|
|
710
|
+
for k, v in cfg.items():
|
|
711
|
+
if k not in kwargs:
|
|
712
|
+
kwargs[k] = v
|
|
713
|
+
|
|
714
|
+
return SbDut(*args, **kwargs)
|