@@ -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"»' )
0 commit comments