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.
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
Binary file
@@ -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()