Skip to content

Commit a8de1ed

Browse files
committed
allow verilog components
1 parent 04498ec commit a8de1ed

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

spicelib/editor/qsch_editor.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def set_attr(self, index: int, value: Union[str, int, tuple]):
319319
raise ValueError("Object not supported in set_attr")
320320
self.tokens[index] = value_str
321321

322-
def get_text(self, label: str, default: str = None) -> str:
322+
def get_text(self, label: str, default: Union[str, None] = None) -> str:
323323
"""
324324
Returns the text of the first child tag that matches the given label. The label can have up to 1 space in it.
325325
It will return the entire text of the tag, after the label.
@@ -336,7 +336,7 @@ def get_text(self, label: str, default: str = None) -> str:
336336
a = self.get_items(label + ':')
337337
if len(a) != 1:
338338
if default is None:
339-
raise IndexError(f"Label '{label}' not found in:{self}")
339+
raise IndexError(f"Label '{label}' not found in {self}")
340340
else:
341341
return default
342342
if len(a[0].tokens) >= 2:
@@ -386,6 +386,9 @@ def save_as(self, qsch_filename: Union[str, Path]) -> None:
386386
"""
387387
Saves the schematic to a QSCH file. The file is saved in cp1252 encoding.
388388
"""
389+
if not self.schematic:
390+
_logger.error("Empty Schematic information")
391+
return
389392
if self.updated or Path(qsch_filename) != self._qsch_file_path:
390393
with open(qsch_filename, 'w', encoding="cp1252") as qsch_file:
391394
_logger.info(f"Writing QSCH file {qsch_file}")
@@ -402,16 +405,22 @@ def save_as(self, qsch_filename: Union[str, Path]) -> None:
402405
if sub_circuit is not None and sub_circuit.updated:
403406
sub_circuit.save_as(sub_circuit._qsch_file_path)
404407

405-
def write_spice_to_file(self, netlist_file: TextIO):
408+
def write_spice_to_file(self, netlist_file: TextIO, verilog_config: dict[str, list[str]] = {}):
406409
"""
407410
Appends the netlist to a file buffer.
408411
409412
:param netlist_file: The file buffer to save the netlist
410413
:type netlist_file: TextIO
414+
:param verilog_config: Mandatory when using Ø components: Verilog modules in a DLL. Details: see `save_netlist()`
415+
:type verilog_config: dict
411416
:return: Nothing
412417
"""
413418
libraries_to_include = []
414419
subcircuits_to_write = OrderedDict()
420+
421+
if not self.schematic:
422+
_logger.error("Empty Schematic information")
423+
return
415424

416425
for refdes, component in self.components.items():
417426
component: SchematicComponent
@@ -426,8 +435,13 @@ def write_spice_to_file(self, netlist_file: TextIO):
426435
symbol = symbol_tag.get_text_attr(1)
427436
typ = symbol_tag.get_text('type')
428437
else:
438+
typ = symbol_tag.get_text('type', "X")
429439
symbol = 'X'
430-
typ = 'X'
440+
if not typ or typ[0] != 'Ø':
441+
typ = 'X'
442+
else:
443+
typ = 'Ø'
444+
symbol = component.value
431445

432446
if refdes[0] != typ[0]:
433447
refdes = typ[0] + '´' + refdes
@@ -440,9 +454,11 @@ def write_spice_to_file(self, netlist_file: TextIO):
440454

441455
ports = component.ports.copy()
442456
if typ in ('¥', 'Ã'):
457+
# these 2 types MUST have 16 ports
443458
if len(ports) < 16:
444459
ports += ['¥'] * (16 - len(ports))
445460

461+
# Default nets assignment: just a concatenation of the port names
446462
nets = " ".join(ports)
447463
model = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR)
448464

@@ -499,6 +515,40 @@ def write_spice_to_file(self, netlist_file: TextIO):
499515
netlist_file.write(f'{refdes} «{nets}» {model}{parameters}\n')
500516
elif typ in ('ZP', 'ZN'):
501517
netlist_file.write(f'{refdes} {nets} {model} {symbol}{parameters}\n')
518+
elif typ == 'Ø':
519+
# Verilog module. Group the pin configurations, and annmotate the pins
520+
basic_refdes = refdes[2:] if '´' in refdes else refdes
521+
if basic_refdes in verilog_config:
522+
pin_configs = verilog_config[basic_refdes]
523+
if len(pin_configs) < len(ports):
524+
_logger.error(f"Verilog component {basic_refdes} has insufficient pin configuration, expected {len(ports)} but got {len(pin_configs)}. Netlist will be wrong.")
525+
else:
526+
in_ports = ""
527+
out_ports = ""
528+
common_ports = ""
529+
for i in range(0, len(ports)):
530+
direction_type = pin_configs[i].split(',')
531+
if len(direction_type) != 2:
532+
_logger.error(f"Verilog component {basic_refdes} has invalid pin configuration '{pin_configs[i]}' for pin {i+1}. Expected format is 'direction,type'. Netlist will be wrong.")
533+
continue
534+
direction = direction_type[0].lower()
535+
port_type = direction_type[1]
536+
if direction in ('in', 'input'):
537+
in_ports += f" {ports[i]}´{port_type}"
538+
elif direction in ('out', 'output'):
539+
out_ports += f" {ports[i]}´{port_type}"
540+
elif direction in ('common', 'inout'):
541+
common_ports += f" {ports[i]}´{port_type}"
542+
else:
543+
_logger.error(f"Verilog component {basic_refdes} has unknown pin direction '{direction}' for pin {i+1}. Expected 'in', 'out' or 'common'. Netlist will be wrong.")
544+
in_ports = in_ports.strip()
545+
out_ports = out_ports.strip()
546+
common_ports = common_ports.strip()
547+
model = ""
548+
netlist_file.write(f'{refdes} «{in_ports}» «{out_ports}» «{common_ports}» {model} {symbol}{parameters}\n')
549+
else:
550+
_logger.error(f"Verilog component {basic_refdes} used without pin configuration. Netlist will be wrong.")
551+
502552
else:
503553
netlist_file.write(f'{refdes} {nets} {model}{parameters}\n')
504554

@@ -532,12 +582,23 @@ def write_spice_to_file(self, netlist_file: TextIO):
532582

533583
# Note: the .END or .ENDCKT must be inserted by the calling function
534584

535-
def save_netlist(self, run_netlist_file: Union[str, Path]) -> None:
585+
def save_netlist(self, run_netlist_file: Union[str, Path], verilog_config: dict[str, list[str]] = {}) -> None:
536586
"""
537587
Saves the current state of the netlist to a .qsh or to a .net or .cir file.
538588
539589
:param run_netlist_file: File name of the netlist file. Can be .qsch, .net or .cir
540590
:type run_netlist_file: pathlib.Path or str
591+
:param verilog_config: Mandatory when using Ø components: Verilog modules in a DLL.
592+
A dictionary with the component reference designators as keys and a
593+
list of pin configurations as values, starting at the first pin (port).
594+
Each entry must have the format: "direction,type" where
595+
596+
* direction is either "in"/"input", "out"/"output", "common"/"inout"
597+
* type is any of the verilog port types, for example "b", "uc", "f"
598+
599+
Example: `{"X1": ["in,b", "in,b", "in,uc", "out,uc", "out,b"]}`
600+
Here the component "X1" has 5 pins, pin 1 is an input of type "bit" and pin 4 is an output of type "unsigned char".
601+
:type verilog_config: dict[str, list[str]]
541602
:returns: Nothing
542603
"""
543604
if isinstance(run_netlist_file, str):
@@ -552,7 +613,7 @@ def save_netlist(self, run_netlist_file: Union[str, Path]) -> None:
552613
with open(run_netlist_file, 'w', encoding="cp1252") as netlist_file:
553614
_logger.info(f"Writing NET file {run_netlist_file}")
554615
netlist_file.write(f'* {os.path.abspath(self._qsch_file_path.as_posix())}\n')
555-
self.write_spice_to_file(netlist_file)
616+
self.write_spice_to_file(netlist_file, verilog_config)
556617
netlist_file.write('.end\n')
557618

558619
def _find_pin_position(self, comp_pos, orientation: int, pin: QschTag) -> tuple[int, int]:
@@ -698,6 +759,8 @@ def _parse_qsch_stream(self, stream):
698759

699760
self.components[refdes] = sch_comp
700761
if refdes.startswith('X'):
762+
if sch_comp.attributes['type'].startswith("Ø"):
763+
have_embedded_subcircuit = True
701764
if not have_embedded_subcircuit:
702765
sub_circuit_name = value + os.path.extsep + 'qsch'
703766
mydir = self.circuit_file.parent.absolute().as_posix()
@@ -1146,6 +1209,7 @@ def copy_from(self, editor: 'BaseSchematic') -> None:
11461209

11471210
for shape in self.shapes:
11481211
# TODO: Implement the line type and width conversion from LTSpice to QSpice.
1212+
shape_tag = None
11491213
if shape.name == "RECTANGLE" or shape.name == "rect":
11501214
shape_tag, _ = QschTag.parse('«rect (1850,1550) (3650,-400) 0 0 2 0xff0000 0x1000000 -1 0 -1»')
11511215
shape_tag.set_attr(QSCH_RECT_POS1, (shape.points[0].X, shape.points[0].Y))
@@ -1189,7 +1253,8 @@ def copy_from(self, editor: 'BaseSchematic') -> None:
11891253
else:
11901254
print(f"Invalid shape {shape.name}. Being ignored. Ask Developper to implement this")
11911255

1192-
self.schematic.items.append(shape_tag)
1256+
if shape_tag:
1257+
self.schematic.items.append(shape_tag)
11931258

11941259
for text in self.directives:
11951260
text_tag, _ = QschTag.parse('«text (0,0) 1 7 0 0x1000000 -1 -1 "text"»')

unittests/test_qsch_editor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ def test_hierarchical(self):
203203

204204
def test_all_elements(self):
205205
self.edt = spicelib.editor.qsch_editor.QschEditor(test_dir + "all_elements.qsch")
206-
self.edt.save_netlist(temp_dir + "all_elements.net")
206+
counter_config = {"X1": ["in,b", "in,b", "in,uc", "out,uc", "out,b"]}
207+
self.edt.save_netlist(temp_dir + "all_elements.net", counter_config)
207208
equalFiles(self, temp_dir + 'all_elements.net', golden_dir + "all_elements.net")
208209

209210

0 commit comments

Comments
 (0)