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
|
Binary file
|
switchboard/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright (c) 2024 Zero ASIC Corporation
|
|
2
|
+
# This code is licensed under Apache License 2.0 (see LICENSE for details)
|
|
3
|
+
|
|
4
|
+
# in the future, there may be some functions implemented directly in Python
|
|
5
|
+
# for now, though, all of the functionality is implemented in C++
|
|
6
|
+
|
|
7
|
+
from _switchboard import (PySbPacket, delete_queue, umi_opcode_to_str,
|
|
8
|
+
PySbTx, PySbRx, UmiCmd, PySbTxPcie, PySbRxPcie, PyUmiPacket, umi_pack,
|
|
9
|
+
umi_opcode, umi_size, umi_len, umi_atype, umi_qos, umi_prot, umi_eom,
|
|
10
|
+
umi_eof, umi_ex, UmiAtomic, delete_queues)
|
|
11
|
+
|
|
12
|
+
from .umi import UmiTxRx, random_umi_packet
|
|
13
|
+
from .util import binary_run, ProcessCollection
|
|
14
|
+
from .icarus import icarus_build_vpi, icarus_run
|
|
15
|
+
from .sbdut import SbDut
|
|
16
|
+
from .loopback import umi_loopback
|
|
17
|
+
from .bitvector import BitVector
|
|
18
|
+
from .uart_xactor import uart_xactor
|
|
19
|
+
from .sbtcp import start_tcp_bridge
|
|
20
|
+
from .axil import AxiLiteTxRx
|
|
21
|
+
from .axi import AxiTxRx
|
|
22
|
+
from .network import SbNetwork, TcpIntf
|
|
23
|
+
from .autowrap import flip_intf
|
|
24
|
+
from .switchboard import path as sb_path
|
switchboard/ams.py
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
# Utilities for AMS simulation
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2024 Zero ASIC Corporation
|
|
4
|
+
# This code is licensed under Apache License 2.0 (see LICENSE for details)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Dict, Any, Tuple, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_spice_subckts(filename: str) -> List[Dict[str, Any]]:
|
|
13
|
+
"""
|
|
14
|
+
Reads the contents of a SPICE file and returns a list of the subcircuit
|
|
15
|
+
definitions that are found. Parsing capability is currently limited and
|
|
16
|
+
does not take into account .INCLUDE statements.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
filename: str
|
|
21
|
+
The path of the file to read from.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
list of dictionaries
|
|
26
|
+
A list of dictionaries, each representing a SPICE subcircuit. The keys
|
|
27
|
+
in each dictionary are "name" and "pins". The "name" key maps to a
|
|
28
|
+
string that contains the name of the circuit, while "pins" maps to a
|
|
29
|
+
list of strings, each representing a pin in the subcircuit. The order
|
|
30
|
+
of pins in that list matches the order in the subcircuit definition.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
subckts = []
|
|
34
|
+
|
|
35
|
+
with open(filename, 'r') as f:
|
|
36
|
+
contents = f.read()
|
|
37
|
+
|
|
38
|
+
# deal with line continuation
|
|
39
|
+
contents = contents.replace('\n+', ' ')
|
|
40
|
+
|
|
41
|
+
for line in contents.split('\n'):
|
|
42
|
+
# example subcircuit definition
|
|
43
|
+
# .SUBCKT CktName pin0 pin1 pin2 .PARAMS a=1.23 b=2.34
|
|
44
|
+
|
|
45
|
+
line = line.strip().lower()
|
|
46
|
+
|
|
47
|
+
if line.startswith('.subckt'):
|
|
48
|
+
tokens = line.split()
|
|
49
|
+
|
|
50
|
+
if len(tokens) < 2:
|
|
51
|
+
# subcircuit definition is empty
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
name = tokens[1]
|
|
55
|
+
|
|
56
|
+
pins = []
|
|
57
|
+
for pin in tokens[2:]:
|
|
58
|
+
if pin == '.params':
|
|
59
|
+
break
|
|
60
|
+
else:
|
|
61
|
+
pins.append(pin)
|
|
62
|
+
|
|
63
|
+
subckts.append(dict(name=name, pins=pins))
|
|
64
|
+
|
|
65
|
+
return subckts
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_bus(name: str) -> Optional[Tuple[str, int, int]]:
|
|
69
|
+
"""
|
|
70
|
+
Tries to parse a signal name as a bus, returning a tuple representing
|
|
71
|
+
the bus: (signalName, msb, lsb). If the signal name isn't a bus, the
|
|
72
|
+
function returns None.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
parse_bus("mySignal[42:34]") -> ("mySignal", 42, 34)
|
|
76
|
+
parse_bus("anotherSignal") -> None
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
name: str
|
|
81
|
+
The signal name to parse.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
tuple with three values, or None
|
|
86
|
+
A tuple with three values: (signalName, msb, lsb). "signalName" is a
|
|
87
|
+
string, while "msb" and "lsb" are integers. If the provided signal
|
|
88
|
+
name isn't a bus, the function returns None.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# example: signalName[msb:lsb] -> returns (signalName, msb, lsb)
|
|
92
|
+
|
|
93
|
+
matches = re.match(r'(\w+)\[(\d+):(\d+)\]', name)
|
|
94
|
+
|
|
95
|
+
if matches is not None:
|
|
96
|
+
groups = matches.groups()
|
|
97
|
+
|
|
98
|
+
prefix = groups[0]
|
|
99
|
+
msb = int(groups[1])
|
|
100
|
+
lsb = int(groups[2])
|
|
101
|
+
|
|
102
|
+
return (prefix, msb, lsb)
|
|
103
|
+
else:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def split_apart_bus(signal: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
108
|
+
"""
|
|
109
|
+
Converts a dictionary representing a signal into a list of signals,
|
|
110
|
+
each representing one bit in the bus. If the signal isn't a bus,
|
|
111
|
+
the function returns a list with one element, the original signal.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
signal: Dict[str, Any]
|
|
116
|
+
The signal to split apart into bits.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
list of dictionaries
|
|
121
|
+
A list of dictionaries, with each dictionary representing a
|
|
122
|
+
single bit in the bus. The format of each of these dictionaries
|
|
123
|
+
is the same as the format of the dictionary provided as input.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
assert 'name' in signal, 'AMS signal must have a name.'
|
|
127
|
+
|
|
128
|
+
name = signal['name']
|
|
129
|
+
|
|
130
|
+
bus = parse_bus(name)
|
|
131
|
+
|
|
132
|
+
if bus is not None:
|
|
133
|
+
prefix, msb, lsb = bus
|
|
134
|
+
|
|
135
|
+
retval = []
|
|
136
|
+
|
|
137
|
+
for k in range(lsb, msb + 1):
|
|
138
|
+
subsignal = signal.copy()
|
|
139
|
+
subsignal['name'] = prefix
|
|
140
|
+
subsignal['index'] = k
|
|
141
|
+
if signal.get('initial', None) is not None:
|
|
142
|
+
subsignal['initial'] = (signal['initial'] >> k) & 1
|
|
143
|
+
retval.append(subsignal)
|
|
144
|
+
|
|
145
|
+
return retval
|
|
146
|
+
else:
|
|
147
|
+
signal = signal.copy()
|
|
148
|
+
signal['index'] = None
|
|
149
|
+
return [signal]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def regularize_ams_input(
|
|
153
|
+
input: Dict[str, Any],
|
|
154
|
+
vol: float = 0.0,
|
|
155
|
+
voh: float = 1.0,
|
|
156
|
+
tr: float = 5e-9,
|
|
157
|
+
tf: float = 5e-9
|
|
158
|
+
) -> Dict[str, Any]:
|
|
159
|
+
"""
|
|
160
|
+
Fills in missing values in a dictionary representing an input signal.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
input: Dict[str, Any]
|
|
165
|
+
The signal to regularize.
|
|
166
|
+
vol: float
|
|
167
|
+
Default real-number voltage to pass to the SPICE subcircuit input
|
|
168
|
+
when the digital value provided is "0". If already defined in
|
|
169
|
+
"input", this argument is ignored.
|
|
170
|
+
voh: float
|
|
171
|
+
Default real-number voltage to pass to the SPICE subcircuit input
|
|
172
|
+
when the digital value provided is "1". If already defined in
|
|
173
|
+
"input", this argument is ignored.
|
|
174
|
+
tr: float
|
|
175
|
+
Default time taken in the SPICE simulation to transition from a low
|
|
176
|
+
value to a high value. If already defined in "input", this argument
|
|
177
|
+
is ignored.
|
|
178
|
+
tf: float
|
|
179
|
+
Default time taken in the SPICE simulation to transition from a high
|
|
180
|
+
value to a low value. If already defined in "input", this argument
|
|
181
|
+
is ignored.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
dictionary
|
|
186
|
+
A dictionary with missing values filled in with defaults.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
assert 'name' in input, 'AMS input must have a name.'
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
'name': input['name'],
|
|
193
|
+
'vol': input.get('vol', vol),
|
|
194
|
+
'voh': input.get('voh', voh),
|
|
195
|
+
'tr': input.get('tr', tr),
|
|
196
|
+
'tf': input.get('tf', tf),
|
|
197
|
+
'index': input.get('index', None)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def regularize_ams_output(output: str, vil: float = 0.2, vih: float = 0.8):
|
|
202
|
+
"""
|
|
203
|
+
Fills in missing values in a dictionary representing an output signal.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
output: Dict[str, Any]
|
|
208
|
+
The signal to regularize.
|
|
209
|
+
vil: float
|
|
210
|
+
Default low voltage threshold, below which the real-number voltage
|
|
211
|
+
from the SPICE simulation is considered to be a logical "0". If
|
|
212
|
+
already defined in "output", this argument is ignored.
|
|
213
|
+
vih: float
|
|
214
|
+
Default high voltage threshold, above which the real-number voltage
|
|
215
|
+
from the SPICE simulation is considered to be a logical "1". If
|
|
216
|
+
already defined in "output", this argument is ignored.
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
dictionary
|
|
221
|
+
A dictionary with missing values filled in with defaults.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
assert 'name' in output, 'AMS output must have a name.'
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
'name': output['name'],
|
|
228
|
+
'vil': output.get('vil', vil),
|
|
229
|
+
'vih': output.get('vih', vih),
|
|
230
|
+
'initial': output.get('initial', None),
|
|
231
|
+
'index': output.get('index', None)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def regularize_ams_inputs(inputs: List[Dict[str, Any]], **kwargs) -> List[Dict[str, Any]]:
|
|
236
|
+
"""
|
|
237
|
+
Fills in missing values in a list of signals, splitting apart buses into
|
|
238
|
+
individual bits as they are encountered.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
inputs: List[Dict[str, Any]]
|
|
243
|
+
List of signals, represented as dictionaries.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
list of dictionaries
|
|
248
|
+
List of signals, represented as dictionaries.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
return [
|
|
252
|
+
regularize_ams_input(subinput, **kwargs)
|
|
253
|
+
for input in inputs
|
|
254
|
+
for subinput in split_apart_bus(input)
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def regularize_ams_outputs(outputs: List[Dict[str, Any]], **kwargs) -> List[Dict[str, Any]]:
|
|
259
|
+
"""
|
|
260
|
+
Fills in missing values in a list of signals, splitting apart buses into
|
|
261
|
+
individual bits as they are encountered.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
outputs: List[Dict[str, Any]]
|
|
266
|
+
List of signals, represented as dictionaries.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
list of dictionaries
|
|
271
|
+
List of signals, represented as dictionaries.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
return [
|
|
275
|
+
regularize_ams_output(suboutput, **kwargs)
|
|
276
|
+
for output in outputs
|
|
277
|
+
for suboutput in split_apart_bus(output)
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def spice_ext_name(signal: Dict[str, Any]) -> str:
|
|
282
|
+
"""
|
|
283
|
+
Returns the name of a signal as it should be referenced as
|
|
284
|
+
a port on a SPICE subcircuit.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
signal: Dict[str, Any]
|
|
289
|
+
Signal represented as a dictionary
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
string
|
|
294
|
+
Signal name to be used in a SPICE file.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
if signal['index'] is None:
|
|
298
|
+
return signal['name']
|
|
299
|
+
else:
|
|
300
|
+
return f'{signal["name"]}[{signal["index"]}]'
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def spice_int_name(signal: Dict[str, Any]) -> str:
|
|
304
|
+
"""
|
|
305
|
+
Returns the name of a signal as it should be referenced as
|
|
306
|
+
a signal within a SPICE subcircuit, avoiding special bus
|
|
307
|
+
characters.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
signal: Dict[str, Any]
|
|
312
|
+
Signal represented as a dictionary
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
string
|
|
317
|
+
Signal name to be used in a SPICE file.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
if signal['index'] is None:
|
|
321
|
+
return signal['name']
|
|
322
|
+
else:
|
|
323
|
+
return f'{signal["name"]}_IDX_{signal["index"]}'
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def vlog_ext_name(signal: Dict[str, Any]) -> str:
|
|
327
|
+
"""
|
|
328
|
+
Returns the name of a signal as it should be referenced as
|
|
329
|
+
a port on a Verilog module.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
signal: Dict[str, Any]
|
|
334
|
+
Signal represented as a dictionary
|
|
335
|
+
|
|
336
|
+
Returns
|
|
337
|
+
-------
|
|
338
|
+
string
|
|
339
|
+
Signal name to be used in a Verilog file.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
if signal['index'] is None:
|
|
343
|
+
return signal['name']
|
|
344
|
+
else:
|
|
345
|
+
return f'{signal["name"]}[{signal["index"]}]'
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def vlog_int_name(signal: Dict[str, Any]) -> str:
|
|
349
|
+
"""
|
|
350
|
+
Returns the name of a signal as it should be referenced as
|
|
351
|
+
a signal within a Verilog module, avoiding special bus
|
|
352
|
+
characters.
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
signal: Dict[str, Any]
|
|
357
|
+
Signal represented as a dictionary
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
string
|
|
362
|
+
Signal name to be used in a Verilog file.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
if signal['index'] is None:
|
|
366
|
+
return signal['name']
|
|
367
|
+
else:
|
|
368
|
+
return f'{signal["name"]}_IDX_{signal["index"]}'
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def make_ams_spice_wrapper(
|
|
372
|
+
name: str,
|
|
373
|
+
filename: str,
|
|
374
|
+
pins: List[Dict[str, Any]],
|
|
375
|
+
dir: str,
|
|
376
|
+
nl: str = '\n'
|
|
377
|
+
):
|
|
378
|
+
"""
|
|
379
|
+
Writes a SPICE file that wraps the SPICE subcircuit defined in filename.
|
|
380
|
+
The wrapper contains ADCs and DACs for interaction between a Verilog
|
|
381
|
+
simulation and SPICE simulation.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
name: str
|
|
386
|
+
Name of the SPICE subcircuit to be wrapped
|
|
387
|
+
filename: str
|
|
388
|
+
Path to the file where the SPICE subcircuit is defined
|
|
389
|
+
pins: List[Dict[str, Any]]
|
|
390
|
+
List of pins on the SPICE subcircuit, each represented as a dictionary.
|
|
391
|
+
dir: str
|
|
392
|
+
Path to the directory where the SPICE wrapper should be written.
|
|
393
|
+
nl: str
|
|
394
|
+
String/character to be used for indicating newlines.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
# split apart pins into inputs, outputs, and constants
|
|
398
|
+
|
|
399
|
+
inputs = [pin for pin in pins if pin.get('type', None) == 'input']
|
|
400
|
+
outputs = [pin for pin in pins if pin.get('type', None) == 'output']
|
|
401
|
+
constants = [pin for pin in pins if pin.get('type', None) == 'constant']
|
|
402
|
+
|
|
403
|
+
# start building up the wrapper text
|
|
404
|
+
|
|
405
|
+
text = []
|
|
406
|
+
|
|
407
|
+
text += [f'* SPICE wrapper for module "{name}"']
|
|
408
|
+
|
|
409
|
+
# instantiate DUT
|
|
410
|
+
|
|
411
|
+
subckts = parse_spice_subckts(filename)
|
|
412
|
+
|
|
413
|
+
for subckt in subckts:
|
|
414
|
+
if subckt['name'].lower() == name.lower():
|
|
415
|
+
break
|
|
416
|
+
else:
|
|
417
|
+
raise Exception(f'Could not find subckt "{name}"')
|
|
418
|
+
|
|
419
|
+
text += [
|
|
420
|
+
'',
|
|
421
|
+
f'*** Start of file "{Path(filename).resolve()}"',
|
|
422
|
+
''
|
|
423
|
+
]
|
|
424
|
+
|
|
425
|
+
with open(filename) as f:
|
|
426
|
+
subcircuit_definition = f.read().splitlines()
|
|
427
|
+
|
|
428
|
+
text += subcircuit_definition
|
|
429
|
+
|
|
430
|
+
text += [
|
|
431
|
+
'',
|
|
432
|
+
f'*** End of file "{Path(filename).resolve()}"',
|
|
433
|
+
''
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
text += [' '.join(
|
|
437
|
+
['Xdut'] + [pin.lower() for pin in subckt['pins']] + [subckt['name'].lower()])]
|
|
438
|
+
|
|
439
|
+
if len(inputs) > 0:
|
|
440
|
+
inputs = regularize_ams_inputs(inputs)
|
|
441
|
+
|
|
442
|
+
# consolidate DAC models
|
|
443
|
+
|
|
444
|
+
dac_counter = 0
|
|
445
|
+
dac_models = {}
|
|
446
|
+
dac_mapping = {}
|
|
447
|
+
|
|
448
|
+
text += ['']
|
|
449
|
+
text += ['* DAC models']
|
|
450
|
+
|
|
451
|
+
for input in inputs:
|
|
452
|
+
input_name = spice_ext_name(input)
|
|
453
|
+
tr = input['tr']
|
|
454
|
+
tf = input['tf']
|
|
455
|
+
|
|
456
|
+
if (tr, tf) not in dac_models:
|
|
457
|
+
dac_name = f'amsDac{dac_counter}'
|
|
458
|
+
text += [f'.model {dac_name} DAC (tr={tr} tf={tf})']
|
|
459
|
+
dac_models[(tr, tf)] = dac_name
|
|
460
|
+
dac_counter += 1
|
|
461
|
+
else:
|
|
462
|
+
dac_name = dac_models[(tr, tf)]
|
|
463
|
+
|
|
464
|
+
dac_mapping[input_name] = dac_name
|
|
465
|
+
|
|
466
|
+
text += ['']
|
|
467
|
+
text += ['* Voltage inputs']
|
|
468
|
+
|
|
469
|
+
for input in inputs:
|
|
470
|
+
int_name = spice_int_name(input)
|
|
471
|
+
ext_name = spice_ext_name(input)
|
|
472
|
+
tr = input['tr']
|
|
473
|
+
tf = input['tf']
|
|
474
|
+
|
|
475
|
+
dac_model = dac_models[(tr, tf)]
|
|
476
|
+
text += [f'YDAC SB_DAC_{int_name.upper()} {ext_name} 0 {dac_model}']
|
|
477
|
+
|
|
478
|
+
if len(outputs) > 0:
|
|
479
|
+
text += ['']
|
|
480
|
+
text += ['* Voltage outputs']
|
|
481
|
+
|
|
482
|
+
outputs = regularize_ams_outputs(outputs)
|
|
483
|
+
|
|
484
|
+
for output in outputs:
|
|
485
|
+
int_name = spice_int_name(output)
|
|
486
|
+
ext_name = spice_ext_name(output)
|
|
487
|
+
text += [f'.MEASURE TRAN SB_ADC_{int_name.upper()} EQN V({ext_name})']
|
|
488
|
+
|
|
489
|
+
if len(constants) > 0:
|
|
490
|
+
text += ['']
|
|
491
|
+
text += ['* Constant signals']
|
|
492
|
+
|
|
493
|
+
for constant in constants:
|
|
494
|
+
const_name = constant['name']
|
|
495
|
+
value = constant['value']
|
|
496
|
+
text += [f'V{const_name} {const_name} 0 {value}']
|
|
497
|
+
|
|
498
|
+
text += ['']
|
|
499
|
+
text += ['.TRAN 0 1']
|
|
500
|
+
|
|
501
|
+
text += ['']
|
|
502
|
+
text += ['.END']
|
|
503
|
+
|
|
504
|
+
text = nl.join(text)
|
|
505
|
+
|
|
506
|
+
outfile = Path(dir) / f'{name}.wrapper.cir'
|
|
507
|
+
|
|
508
|
+
with open(outfile, 'w') as f:
|
|
509
|
+
f.write(text)
|
|
510
|
+
|
|
511
|
+
return outfile.resolve()
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def make_ams_verilog_wrapper(
|
|
515
|
+
name: str,
|
|
516
|
+
filename: str,
|
|
517
|
+
pins: List[Dict[str, Any]],
|
|
518
|
+
dir: str,
|
|
519
|
+
nl: str = '\n',
|
|
520
|
+
tab: str = ' '
|
|
521
|
+
):
|
|
522
|
+
"""
|
|
523
|
+
Writes a Verilog file that contains a module definition matching the SPICE
|
|
524
|
+
subcircuit in the provided file. The module definition uses VPI/DPI to
|
|
525
|
+
interact with the Xyce.
|
|
526
|
+
|
|
527
|
+
Parameters
|
|
528
|
+
----------
|
|
529
|
+
name: str
|
|
530
|
+
Name of the SPICE subcircuit to be wrapped
|
|
531
|
+
filename: str
|
|
532
|
+
Path to the file where the SPICE subcircuit is defined
|
|
533
|
+
pins: List[Dict[str, Any]]
|
|
534
|
+
List of pins on the SPICE subcircuit, each represented as a dictionary.
|
|
535
|
+
dir: str
|
|
536
|
+
Path to the directory where the SPICE wrapper should be written.
|
|
537
|
+
nl: str
|
|
538
|
+
String/character to be used to for indicating newlines.
|
|
539
|
+
nl: str
|
|
540
|
+
String/character to be used for tabs.
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
# start building up the wrapper text
|
|
544
|
+
|
|
545
|
+
text = []
|
|
546
|
+
|
|
547
|
+
text += [f'// Verilog wrapper for module "{name}"']
|
|
548
|
+
|
|
549
|
+
text += ['']
|
|
550
|
+
text += [f'module {name} (']
|
|
551
|
+
|
|
552
|
+
ios = []
|
|
553
|
+
|
|
554
|
+
for pin in pins:
|
|
555
|
+
type = pin.get('type', None)
|
|
556
|
+
if type in {'input', 'output'}:
|
|
557
|
+
pin_name = pin['name']
|
|
558
|
+
|
|
559
|
+
bus = parse_bus(pin_name)
|
|
560
|
+
|
|
561
|
+
if bus is not None:
|
|
562
|
+
prefix, msb, lsb = bus
|
|
563
|
+
ios += [f'{type} [{msb}:{lsb}] {prefix}']
|
|
564
|
+
else:
|
|
565
|
+
ios += [f'{type} {pin_name}']
|
|
566
|
+
|
|
567
|
+
ios += ['input SB_CLK']
|
|
568
|
+
|
|
569
|
+
# split apart pins into inputs and outputs. constants are ignored here,
|
|
570
|
+
# since they are tied off in the spice netlist
|
|
571
|
+
|
|
572
|
+
inputs = [pin for pin in pins if pin.get('type', None) == 'input']
|
|
573
|
+
outputs = [pin for pin in pins if pin.get('type', None) == 'output']
|
|
574
|
+
|
|
575
|
+
inputs = regularize_ams_inputs(inputs)
|
|
576
|
+
outputs = regularize_ams_outputs(outputs)
|
|
577
|
+
|
|
578
|
+
ios = [tab + io for io in ios]
|
|
579
|
+
ios = [io + ',' for io in ios[:-1]] + [ios[-1]]
|
|
580
|
+
|
|
581
|
+
text += ios
|
|
582
|
+
|
|
583
|
+
text += [');']
|
|
584
|
+
|
|
585
|
+
text += [
|
|
586
|
+
tab + 'xyce_intf x ();',
|
|
587
|
+
'',
|
|
588
|
+
tab + 'initial begin',
|
|
589
|
+
tab + tab + f'x.init("{filename}");',
|
|
590
|
+
tab + 'end'
|
|
591
|
+
]
|
|
592
|
+
|
|
593
|
+
for input in inputs:
|
|
594
|
+
int_name = vlog_int_name(input)
|
|
595
|
+
ext_name = vlog_ext_name(input)
|
|
596
|
+
|
|
597
|
+
vol = input['vol']
|
|
598
|
+
voh = input['voh']
|
|
599
|
+
|
|
600
|
+
analog = f'SB_DAC_{int_name.upper()}'
|
|
601
|
+
text += [
|
|
602
|
+
'',
|
|
603
|
+
tab + f'real {analog};',
|
|
604
|
+
tab + f'always @({analog}) begin',
|
|
605
|
+
tab + tab + f'x.put("{analog.upper()}", {analog});',
|
|
606
|
+
tab + 'end',
|
|
607
|
+
tab + f'assign {analog} = {ext_name} ? {voh} : {vol};'
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
if len(outputs) > 0:
|
|
611
|
+
for output in outputs:
|
|
612
|
+
int_name = vlog_int_name(output)
|
|
613
|
+
ext_name = vlog_ext_name(output)
|
|
614
|
+
|
|
615
|
+
analog = f'SB_ADC_{int_name.upper()}'
|
|
616
|
+
cmp = f'SB_CMP_{int_name.upper()}'
|
|
617
|
+
|
|
618
|
+
if output.get('initial', None) is not None:
|
|
619
|
+
initial = f" = 1'b{output['initial']}"
|
|
620
|
+
else:
|
|
621
|
+
initial = ''
|
|
622
|
+
|
|
623
|
+
text += [
|
|
624
|
+
'',
|
|
625
|
+
tab + f'real {analog};',
|
|
626
|
+
tab + f'reg {cmp}{initial};',
|
|
627
|
+
tab + f'assign {ext_name} = {cmp};'
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
text += [
|
|
631
|
+
'',
|
|
632
|
+
tab + 'always @(posedge SB_CLK) begin',
|
|
633
|
+
tab + tab + '/* verilator lint_off BLKSEQ */'
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
for output in outputs:
|
|
637
|
+
int_name = vlog_int_name(output)
|
|
638
|
+
|
|
639
|
+
vil = output['vil']
|
|
640
|
+
vih = output['vih']
|
|
641
|
+
|
|
642
|
+
analog = f'SB_ADC_{int_name.upper()}'
|
|
643
|
+
cmp = f'SB_CMP_{int_name.upper()}'
|
|
644
|
+
|
|
645
|
+
text += [
|
|
646
|
+
tab + tab + f'x.get("{analog}", {analog});',
|
|
647
|
+
tab + tab + f'if ({analog} >= {vih}) begin',
|
|
648
|
+
tab + tab + tab + f'{cmp} = 1;',
|
|
649
|
+
tab + tab + f'end else if ({analog} <= {vil}) begin',
|
|
650
|
+
tab + tab + tab + f'{cmp} = 0;',
|
|
651
|
+
tab + tab + 'end'
|
|
652
|
+
]
|
|
653
|
+
|
|
654
|
+
text += [
|
|
655
|
+
tab + tab + '/* verilator lint_on BLKSEQ */',
|
|
656
|
+
tab + 'end'
|
|
657
|
+
]
|
|
658
|
+
|
|
659
|
+
text += ['endmodule']
|
|
660
|
+
|
|
661
|
+
text = nl.join(text)
|
|
662
|
+
|
|
663
|
+
outfile = Path(dir) / f'{name}.wrapper.sv'
|
|
664
|
+
|
|
665
|
+
with open(outfile, 'w') as f:
|
|
666
|
+
f.write(text)
|
|
667
|
+
|
|
668
|
+
return outfile.resolve()
|