From 151086eee393bab2236d76e87f57bdffe531a0e9 Mon Sep 17 00:00:00 2001 From: hans boot Date: Wed, 19 Feb 2025 18:23:19 +0100 Subject: [PATCH 01/16] add get_all_parameter_names and repair file search for qsch --- spicelib/editor/qsch_editor.py | 27 ++++++++++++++++++++++++--- unittests/test_qsch_editor.py | 1 + 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 7b1fa49..6eaaa57 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -666,7 +666,8 @@ def _parse_qsch_stream(self, stream): self.components[refdes] = sch_comp if refdes.startswith('X'): sub_circuit_name = value + os.path.extsep + 'qsch' - sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name) + mydir = self.circuit_file.parent.absolute().as_posix() + sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name, mydir) if sub_circuit_schematic_file: sub_schematic = QschEditor(sub_circuit_schematic_file) sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. @@ -720,9 +721,29 @@ def _get_param_named(self, param_name): return tag, match else: return None, None + + def get_all_parameter_names(self) -> List[str]: + """ + Returns all parameter names from the netlist. - def _qsch_file_find(self, filename) -> Optional[str]: - containers = ['.'] + self.custom_lib_paths + self.simulator_lib_paths + :return: A list of parameter names found in the netlist + :rtype: List[str] + """ + param_names = [] + param_regex = re.compile(PARAM_REGEX(r"\w+"), re.IGNORECASE) + text_tags = self.schematic.get_items('text') + for tag in text_tags: + line = tag.get_attr(QSCH_TEXT_STR_ATTR) + line = line.lstrip(QSCH_TEXT_INSTR_QUALIFIER) + if line.upper().startswith('.PARAM'): + matches = param_regex.finditer(line) + for match in matches: + param_name = match.group('name') + param_names.append(param_name.upper()) + return sorted(param_names) + + def _qsch_file_find(self, filename: str, work_dir: str = '.') -> Optional[str]: + containers = [work_dir] + self.custom_lib_paths + self.simulator_lib_paths # '.' is the directory where the script is located return search_file_in_containers(filename, *containers) diff --git a/unittests/test_qsch_editor.py b/unittests/test_qsch_editor.py index 480a77b..4c16267 100644 --- a/unittests/test_qsch_editor.py +++ b/unittests/test_qsch_editor.py @@ -94,6 +94,7 @@ def test_component_editing_obj(self): self.assertEqual(r1_params[key], value, f"Tested R1 {key} Parameter") def test_parameter_edit(self): + self.assertEqual(self.edt.get_all_parameter_names(), ['RES', 'TEMP']) self.assertEqual(self.edt.get_parameter('TEMP'), '0', "Tested TEMP Parameter") # add assertion here self.edt.set_parameter('TEMP', 25) self.assertEqual(self.edt.get_parameter('TEMP'), '25', "Tested TEMP Parameter") # add assertion here From 4ec70afb4149d9f0300519eab61d4a646f89bbbb Mon Sep 17 00:00:00 2001 From: hans boot Date: Wed, 19 Feb 2025 18:23:37 +0100 Subject: [PATCH 02/16] add get_all_parameter_names to spice_editor --- spicelib/editor/spice_editor.py | 18 ++++++++++++++++++ unittests/test_spice_editor.py | 1 + 2 files changed, 19 insertions(+) diff --git a/spicelib/editor/spice_editor.py b/spicelib/editor/spice_editor.py index 5e0c491..a5d7921 100644 --- a/spicelib/editor/spice_editor.py +++ b/spicelib/editor/spice_editor.py @@ -394,6 +394,24 @@ def _get_param_named(self, param_name) -> Tuple[int, Union[re.Match, None]]: line_no += 1 return -1, None # If it fails, it returns an invalid line number and No match + def get_all_parameter_names(self) -> List[str]: + """ + Returns all parameter names from the netlist. + + :return: A list of parameter names found in the netlist + :rtype: List[str] + """ + param_names = [] + search_expression = re.compile(PARAM_REGEX(r"\w+"), re.IGNORECASE) + for line in self.netlist: + cmd = get_line_command(line) + if cmd == '.PARAM': + matches = search_expression.finditer(line) + for match in matches: + param_name = match.group('name') + param_names.append(param_name.upper()) + return sorted(param_names) + def get_subcircuit_names(self) -> List[str]: """ Returns a list of the names of the sub-circuits in the netlist. diff --git a/unittests/test_spice_editor.py b/unittests/test_spice_editor.py index 0af225b..6c94c55 100644 --- a/unittests/test_spice_editor.py +++ b/unittests/test_spice_editor.py @@ -122,6 +122,7 @@ def test_component_editing_2(self): self.equalFiles(temp_dir + 'opamptest_output_1.net', golden_dir + 'opamptest_output_1.net') def test_parameter_edit(self): + self.assertEqual(self.edt.get_all_parameter_names(), ['RES', 'TEMP']) self.assertEqual(self.edt.get_parameter('TEMP'), '0', "Tested TEMP Parameter") # add assertion here self.edt.set_parameter('TEMP', 25) self.assertEqual(self.edt.get_parameter('TEMP'), '25', "Tested TEMP Parameter") # add assertion here From afa584510f9a544d01bfe9b3825d3311c362eec3 Mon Sep 17 00:00:00 2001 From: hans boot Date: Wed, 19 Feb 2025 18:33:52 +0100 Subject: [PATCH 03/16] Added get_all_parameter_names to remaining classes, and completed doc --- spicelib/editor/asc_editor.py | 14 +++++++++++++- spicelib/editor/base_editor.py | 10 ++++++++++ spicelib/editor/qsch_editor.py | 7 +------ spicelib/editor/spice_editor.py | 7 +------ unittests/test_asc_editor.py | 1 + 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/spicelib/editor/asc_editor.py b/spicelib/editor/asc_editor.py index b58b5b7..b1bf70e 100644 --- a/spicelib/editor/asc_editor.py +++ b/spicelib/editor/asc_editor.py @@ -332,7 +332,19 @@ def _get_param_named(self, param_name): if match.group("name").upper() == param_name_uppercase: return match, directive return None, None - + + def get_all_parameter_names(self) -> List[str]: + # docstring inherited from BaseEditor + param_names = [] + search_expression = re.compile(PARAM_REGEX(r"\w+"), re.IGNORECASE) + for directive in self.directives: + if directive.text.upper().startswith(".PARAM"): + matches = search_expression.finditer(directive.text) + for match in matches: + param_name = match.group('name') + param_names.append(param_name.upper()) + return sorted(param_names) + def get_parameter(self, param: str) -> str: match, directive = self._get_param_named(param) if match: diff --git a/spicelib/editor/base_editor.py b/spicelib/editor/base_editor.py index 3109196..eca45ec 100644 --- a/spicelib/editor/base_editor.py +++ b/spicelib/editor/base_editor.py @@ -478,6 +478,16 @@ def get_parameter(self, param: str) -> str: :raises: ParameterNotFoundError - In case the component is not found """ ... + + @abstractmethod + def get_all_parameter_names(self, param: str) -> str: + """ + Returns all parameter names from the netlist. + + :return: A list of parameter names found in the netlist + :rtype: List[str] + """ + ... def set_parameter(self, param: str, value: Union[str, int, float]) -> None: """Adds a parameter to the SPICE netlist. diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 6eaaa57..b4eec10 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -723,12 +723,7 @@ def _get_param_named(self, param_name): return None, None def get_all_parameter_names(self) -> List[str]: - """ - Returns all parameter names from the netlist. - - :return: A list of parameter names found in the netlist - :rtype: List[str] - """ + # docstring inherited from BaseEditor param_names = [] param_regex = re.compile(PARAM_REGEX(r"\w+"), re.IGNORECASE) text_tags = self.schematic.get_items('text') diff --git a/spicelib/editor/spice_editor.py b/spicelib/editor/spice_editor.py index a5d7921..37dcf0a 100644 --- a/spicelib/editor/spice_editor.py +++ b/spicelib/editor/spice_editor.py @@ -395,12 +395,7 @@ def _get_param_named(self, param_name) -> Tuple[int, Union[re.Match, None]]: return -1, None # If it fails, it returns an invalid line number and No match def get_all_parameter_names(self) -> List[str]: - """ - Returns all parameter names from the netlist. - - :return: A list of parameter names found in the netlist - :rtype: List[str] - """ + # docstring inherited from BaseEditor param_names = [] search_expression = re.compile(PARAM_REGEX(r"\w+"), re.IGNORECASE) for line in self.netlist: diff --git a/unittests/test_asc_editor.py b/unittests/test_asc_editor.py index fb5cb59..a54cde4 100644 --- a/unittests/test_asc_editor.py +++ b/unittests/test_asc_editor.py @@ -78,6 +78,7 @@ def test_component_legacy_editing(self): self.equalFiles(temp_dir + 'test_components_output_1.asc', golden_dir + 'test_components_output_1.asc') def test_parameter_edit(self): + self.assertEqual(self.edt.get_all_parameter_names(), ['RES', 'TEMP']) self.assertEqual(self.edt.get_parameter('TEMP'), '0', "Tested TEMP Parameter") # add assertion here self.edt.set_parameter('TEMP', 25) self.assertEqual(self.edt.get_parameter('TEMP'), '25', "Tested TEMP Parameter") # add assertion here From 69c5527369106fc82b1acd73d8c8a2f4262c8142 Mon Sep 17 00:00:00 2001 From: hans boot Date: Wed, 19 Feb 2025 19:48:25 +0100 Subject: [PATCH 04/16] make qsch and asceditor more robust --- spicelib/editor/asc_editor.py | 4 +++- spicelib/editor/qsch_editor.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spicelib/editor/asc_editor.py b/spicelib/editor/asc_editor.py index b1bf70e..0edbe9c 100644 --- a/spicelib/editor/asc_editor.py +++ b/spicelib/editor/asc_editor.py @@ -304,7 +304,9 @@ def _get_subcircuit(self, symbol: AsyReader) -> Union[SpiceEditor, 'AscEditor']: def get_subcircuit(self, reference: str) -> 'AscEditor': """Returns an AscEditor file corresponding to the symbol""" sub = self.get_component(reference) - return sub.attributes['_SUBCKT'] + if '_SUBCKT' in sub.attributes: + return sub.attributes['_SUBCKT'] + raise AttributeError(f"An associated subcircuit was not found for {reference}") def get_component_info(self, reference) -> dict: """Returns the reference information as a dictionary""" diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index b4eec10..48a7ebf 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -433,12 +433,13 @@ def write_spice_to_file(self, netlist_file: TextIO): # schedule to write .SUBCKT clauses at the end if model not in subcircuits_to_write: - pins = symbol_tag.get_items("pin") - sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) - subcircuits_to_write[model] = ( - component.attributes['_SUBCKT'], # the subcircuit schematic is saved - sub_ports, # and also storing the port position now, so to save time later. - ) + if '_SUBCKT' in component.attributes: + pins = symbol_tag.get_items("pin") + sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) + subcircuits_to_write[model] = ( + component.attributes['_SUBCKT'], # the subcircuit schematic is saved + sub_ports, # and also storing the port position now, so to save time later. + ) nets = " ".join(component.ports) netlist_file.write(f'{refdes} {nets} {model}{parameters}\n') @@ -671,6 +672,8 @@ def _parse_qsch_stream(self, stream): if sub_circuit_schematic_file: sub_schematic = QschEditor(sub_circuit_schematic_file) sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. + else: + _logger.debug(f"Subcircuit {sub_circuit_name} not found") for text_tag in self.schematic.get_items('text'): x, y = text_tag.get_attr(QSCH_TEXT_POS) @@ -743,6 +746,7 @@ def _qsch_file_find(self, filename: str, work_dir: str = '.') -> Optional[str]: return search_file_in_containers(filename, *containers) def get_subcircuit(self, reference: str) -> 'QschEditor': + """Returns an QschEditor file corresponding to the symbol""" subcircuit = self.get_component(reference) if '_SUBCKT' in subcircuit.attributes: # Optimization: if it was already stored, return it return subcircuit.attributes['_SUBCKT'] From 9417cff2cbbf4cf346140ce481e779512d8b4e21 Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 10:15:07 +0100 Subject: [PATCH 05/16] revert qsch SUBCKT changes --- spicelib/editor/qsch_editor.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 48a7ebf..0b1fbf2 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -433,13 +433,12 @@ def write_spice_to_file(self, netlist_file: TextIO): # schedule to write .SUBCKT clauses at the end if model not in subcircuits_to_write: - if '_SUBCKT' in component.attributes: - pins = symbol_tag.get_items("pin") - sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) - subcircuits_to_write[model] = ( - component.attributes['_SUBCKT'], # the subcircuit schematic is saved - sub_ports, # and also storing the port position now, so to save time later. - ) + pins = symbol_tag.get_items("pin") + sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) + subcircuits_to_write[model] = ( + component.attributes['_SUBCKT'], # the subcircuit schematic is saved + sub_ports, # and also storing the port position now, so to save time later. + ) nets = " ".join(component.ports) netlist_file.write(f'{refdes} {nets} {model}{parameters}\n') @@ -667,13 +666,10 @@ def _parse_qsch_stream(self, stream): self.components[refdes] = sch_comp if refdes.startswith('X'): sub_circuit_name = value + os.path.extsep + 'qsch' - mydir = self.circuit_file.parent.absolute().as_posix() - sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name, mydir) + sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name) if sub_circuit_schematic_file: sub_schematic = QschEditor(sub_circuit_schematic_file) sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. - else: - _logger.debug(f"Subcircuit {sub_circuit_name} not found") for text_tag in self.schematic.get_items('text'): x, y = text_tag.get_attr(QSCH_TEXT_POS) From 0dc947ae1946b2b7509260acae21cc0a3f663537 Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 10:18:14 +0100 Subject: [PATCH 06/16] missing revert on qsch --- spicelib/editor/qsch_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 0b1fbf2..a690f1d 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -736,8 +736,8 @@ def get_all_parameter_names(self) -> List[str]: param_names.append(param_name.upper()) return sorted(param_names) - def _qsch_file_find(self, filename: str, work_dir: str = '.') -> Optional[str]: - containers = [work_dir] + self.custom_lib_paths + self.simulator_lib_paths + def _qsch_file_find(self, filename) -> Optional[str]: + containers = ['.'] + self.custom_lib_paths + self.simulator_lib_paths # '.' is the directory where the script is located return search_file_in_containers(filename, *containers) From c87d5b22ea847ccacab0c2adec1c2c165855399f Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 10:27:25 +0100 Subject: [PATCH 07/16] improve qspice subcircuit file search --- spicelib/editor/qsch_editor.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 7b1fa49..65fb6b3 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -433,12 +433,13 @@ def write_spice_to_file(self, netlist_file: TextIO): # schedule to write .SUBCKT clauses at the end if model not in subcircuits_to_write: - pins = symbol_tag.get_items("pin") - sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) - subcircuits_to_write[model] = ( - component.attributes['_SUBCKT'], # the subcircuit schematic is saved - sub_ports, # and also storing the port position now, so to save time later. - ) + if '_SUBCKT' in component.attributes: + pins = symbol_tag.get_items("pin") + sub_ports = " ".join(pin.get_attr(QSCH_SYMBOL_PIN_NET) for pin in pins) + subcircuits_to_write[model] = ( + component.attributes['_SUBCKT'], # the subcircuit schematic is saved + sub_ports, # and also storing the port position now, so to save time later. + ) nets = " ".join(component.ports) netlist_file.write(f'{refdes} {nets} {model}{parameters}\n') @@ -499,7 +500,8 @@ def write_spice_to_file(self, netlist_file: TextIO): netlist_file.write(line.strip() + '\n') for library in libraries_to_include: - library_path = self._qsch_file_find(library) + mydir = self.circuit_file.parent.absolute().as_posix() + library_path = self._qsch_file_find(library, mydir) if library_path is None: netlist_file.write(f'.lib {library}\n') else: @@ -666,10 +668,13 @@ def _parse_qsch_stream(self, stream): self.components[refdes] = sch_comp if refdes.startswith('X'): sub_circuit_name = value + os.path.extsep + 'qsch' - sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name) + mydir = self.circuit_file.parent.absolute().as_posix() + sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name, mydir) if sub_circuit_schematic_file: sub_schematic = QschEditor(sub_circuit_schematic_file) sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. + else: + _logger.warning(f"Subcircuit '{sub_circuit_name}' not found. Have you set the correct search paths?") for text_tag in self.schematic.get_items('text'): x, y = text_tag.get_attr(QSCH_TEXT_POS) @@ -721,9 +726,11 @@ def _get_param_named(self, param_name): else: return None, None - def _qsch_file_find(self, filename) -> Optional[str]: + def _qsch_file_find(self, filename: str, work_dir: str = None) -> Optional[str]: containers = ['.'] + self.custom_lib_paths + self.simulator_lib_paths # '.' is the directory where the script is located + if (work_dir is not None) and work_dir != ".": + containers = [work_dir] + containers # put work directory first return search_file_in_containers(filename, *containers) def get_subcircuit(self, reference: str) -> 'QschEditor': From eb47507d7012a945f50d663ff1a7afc490bd6102 Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 11:02:30 +0100 Subject: [PATCH 08/16] unit test set up, not working yet --- .../testfiles/Qspice_bug_embedded_subckt.qsch | 63 +++++++++++++++++++ unittests/golden/qsch_embedded_subckt.net | 9 +++ unittests/test_qsch_editor.py | 8 +++ 3 files changed, 80 insertions(+) create mode 100644 examples/testfiles/Qspice_bug_embedded_subckt.qsch create mode 100644 unittests/golden/qsch_embedded_subckt.net diff --git a/examples/testfiles/Qspice_bug_embedded_subckt.qsch b/examples/testfiles/Qspice_bug_embedded_subckt.qsch new file mode 100644 index 0000000..f11c7f9 --- /dev/null +++ b/examples/testfiles/Qspice_bug_embedded_subckt.qsch @@ -0,0 +1,63 @@ +ÿØÿÛ«schematic + «component (-6700,-2000) 0 0 + «symbol V + «type: V» + «description: Independent Voltage Source» + «shorted pins: false» + «line (0,-130) (0,-200) 0 0 0x1000000 -1 -1» + «line (0,200) (0,130) 0 0 0x1000000 -1 -1» + «rect (-25,77) (25,73) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-2,50) (2,100) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-25,-73) (25,-77) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «ellipse (-130,130) (130,-130) 0 0 0 0x1000000 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "V1"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "1"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "+"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "-"» + » + » + «component (-3000,-1800) 0 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "R1"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "1Meg"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «component (-4800,-1300) 0 0 + «symbol Proportional + «type: X» + «description: Proportional» + «library file: |.subckt P OUT IN\nE1 OUT 0 IN 0 {P}\n.ends P» + «shorted pins: false» + «triangle (-100,200) (-100,-200) (300,0) 0 0 0x1000000 0x2000000 -1 -1» + «text (-100,250) 1 6 2 0x1000000 -1 -1 "X2"» + «text (0,0) 1 0 0 0x1000000 -1 -1 "P"» + «text (0,-300) 1 0 0 0x1000000 -1 -1 "P=2"» + «pin (300,0) (30,150) 0.63 13 0 0x0 -1 "OUT"» + «pin (-100,0) (-100,10) 0.63 14 0 0x0 -1 "IN"» + » + » + «net (-6700,-2500) 1 13 0 "GND"» + «net (-6100,-1300) 1 14 0 "IN"» + «net (-3300,-1300) 1 14 0 "OUT"» + «junction (-6700,-2400)» + «wire (-6700,-2400) (-6700,-2200) "GND"» + «wire (-6700,-1800) (-6700,-1300) "IN"» + «wire (-3300,-1300) (-3000,-1300) "OUT"» + «wire (-3000,-2000) (-3000,-2400) "GND"» + «wire (-3000,-2400) (-6700,-2400) "GND"» + «wire (-6700,-2500) (-6700,-2400) "GND"» + «wire (-6700,-1300) (-6100,-1300) "IN"» + «wire (-3000,-1300) (-3000,-1600) "OUT"» + «wire (-4500,-1300) (-3300,-1300) "OUT"» + «wire (-6100,-1300) (-4900,-1300) "IN"» + «text (-9180,470) 1 7 0 0x1000000 -1 -1 ".tran 0 1m 0 100n uic"» +» + diff --git a/unittests/golden/qsch_embedded_subckt.net b/unittests/golden/qsch_embedded_subckt.net new file mode 100644 index 0000000..f36de12 --- /dev/null +++ b/unittests/golden/qsch_embedded_subckt.net @@ -0,0 +1,9 @@ +* /workspace/spicelib/examples/testfiles/Qspice_bug_embedded_subckt.qsch +V1 IN 0 1 +R1 OUT 0 1Meg +.subckt X2•P OUT IN +E1 OUT 0 IN 0 {P} +.ends P +X2 OUT IN X2•P P=2 +.tran 0 1m 0 100n uic +.end diff --git a/unittests/test_qsch_editor.py b/unittests/test_qsch_editor.py index 480a77b..d5746fd 100644 --- a/unittests/test_qsch_editor.py +++ b/unittests/test_qsch_editor.py @@ -170,5 +170,13 @@ def test_floating_net(self): equalFiles(self, temp_dir + 'qsch_floating_net.net', golden_dir + "qsch_floating_net.net") +class QschEditorEmbeddedSubckt(unittest.TestCase): + + def test_embedded_subckt(self): + self.edt = spicelib.editor.qsch_editor.QschEditor(test_dir + "Qspice_bug_embedded_subckt.qsch") + self.edt.save_netlist(temp_dir + 'qsch_embedded_subckt.net') + # equalFiles(self, temp_dir + 'qsch_embedded_subckt.net', golden_dir + "qsch_embedded_subckt.net") + + if __name__ == '__main__': unittest.main() From c6d4b6d59ac299a18aff68d1ec1ac57884c7365f Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 11:04:46 +0100 Subject: [PATCH 09/16] add readme info --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c25a806..9dae265 100644 --- a/README.md +++ b/README.md @@ -821,12 +821,13 @@ For support and improvement requests please open an Issue in [GitHub spicelib is * Version TBD * Fixed Issue #139 - support xyce raw files + * Added `get_all_parameter_names()` function to all editors (#159) * Version 1.4.0 (Python 3.9+ only) * Fixed Issue #152 - python version compatibility too limited on PyPi. * Version 1.3.8 * Solving deprecation in GitHub artifact actions v3 -> v4 * Version 1.3.7 - * Fixed Issue #143 - ltsteps example fixed + * Fixed Issue #143 - ltsteps example fixed * Fixed Issue #141 - Raw file reader cannot handle complex values (AC analysis) in ASCII RAW files * Fixed Issue #140 and #131 - Compatibility with LTspice 24+ * Fixed Issue #145 - Allow easy hiding of simulator's console message From e127014e933d9afdf41de6bbf5bc4e484bbc67f7 Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 13:59:59 +0100 Subject: [PATCH 10/16] allow embedded subcircuits --- README.md | 1 + spicelib/__init__.py | 2 + spicelib/editor/qsch_editor.py | 93 ++++++++++++++++++----- spicelib/scripts/asc_to_qsch.py | 2 +- spicelib/utils/file_search.py | 1 + unittests/golden/qsch_embedded_subckt.net | 6 +- unittests/test_qsch_editor.py | 2 +- 7 files changed, 81 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c25a806..cfb9401 100644 --- a/README.md +++ b/README.md @@ -820,6 +820,7 @@ For support and improvement requests please open an Issue in [GitHub spicelib is ## History * Version TBD + * Fixed Issue #154 - support embedded subcircuits in Qspice * Fixed Issue #139 - support xyce raw files * Version 1.4.0 (Python 3.9+ only) * Fixed Issue #152 - python version compatibility too limited on PyPi. diff --git a/spicelib/__init__.py b/spicelib/__init__.py index 3913ea0..8905026 100644 --- a/spicelib/__init__.py +++ b/spicelib/__init__.py @@ -18,12 +18,14 @@ def all_loggers(): """ return [ "spicelib.AscEditor", + "spicelib.AscToQsch", "spicelib.AsyReader", "spicelib.BaseEditor", "spicelib.BaseSchematic", "spicelib.LTSpiceSimulator", "spicelib.LTSteps", "spicelib.NGSpiceSimulator", + "spicelib.QschEditor", "spicelib.qspice_log_reader", "spicelib.QSpiceSimulator", "spicelib.RawRead", diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 65fb6b3..265648d 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -37,7 +37,7 @@ __all__ = ('QschEditor', 'QschTag', 'QschReadingError') -_logger = logging.getLogger("qspice.QschEditor") +_logger = logging.getLogger("spicelib.QschEditor") QSCH_HEADER = (255, 216, 255, 219) @@ -255,10 +255,18 @@ def out(self, level): def tag(self) -> str: """Returns the tag id of the object. The tag id is the first token in the tag.""" return self.tokens[0] + + @property + def compound_tag(self) -> str: + """Returns the tag id of the object. The tag id is a concatenation of the first and second token in the tag.""" + return self.tokens[0] + " " + self.tokens[1] def get_items(self, item) -> List['QschTag']: """Returns a list of children tags that match the given tag id.""" - answer = [tag for tag in self.items if tag.tag == item] + if " " in item: + answer = [tag for tag in self.items if tag.compound_tag == item] + else: + answer = [tag for tag in self.items if tag.tag == item] return answer def get_attr(self, index: int): @@ -312,9 +320,19 @@ def set_attr(self, index: int, value): raise ValueError("Object not supported in set_attr") self.tokens[index] = value_str - def get_text(self, label, default: str = None) -> str: + def get_text(self, label: str, default: str = None) -> str: """ - Returns the text of the first child tag that matches the given label. + Returns the text of the first child tag that matches the given label. The label can have up to 1 space in it. + It will return the entire text of the tag, after the label. + If the label is not found, it returns the default value. + + :param label: label to be found. Can have up to 1 space (e.g. "library file" or "shorted pins") + :type label: str + :param default: Default value, defaults to None + :type default: str, optional + :raises IndexError: When the label is not found and the default value is None + :return: the found text or the default value + :rtype: str """ a = self.get_items(label + ':') if len(a) != 1: @@ -322,8 +340,12 @@ def get_text(self, label, default: str = None) -> str: raise IndexError(f"Label '{label}' not found in:{self}") else: return default - if len(a[0].tokens) >= 2: - return a[0].tokens[1] + if " " in label: + start_pos = 2 + else: + start_pos = 1 + if len(a[0].tokens) > start_pos: + return ' '.join(a[0].tokens[start_pos:]) else: return default @@ -352,7 +374,7 @@ class QschEditor(BaseSchematic): :meta hide-value: """ - + def __init__(self, qsch_file: str, create_blank: bool = False): super().__init__() self._qsch_file_path = Path(qsch_file) @@ -427,9 +449,26 @@ def write_spice_to_file(self, netlist_file: TextIO): ports += ['Â¥'] * (16 - len(ports)) nets = " ".join(ports) + + have_embedded_subcircuit = False + # Check the libraries and embedded subcircuits + library_name = symbol_tag.get_text('library file', default="") + if library_name and (library_name not in libraries_to_include): + marker = "|.subckt" + if library_name.startswith(marker): + # This is an embedded subcircuit, print it here, not at the end. It must be printed before the component + sub_circuit_content = library_name[len(marker):].strip() # The section after "|.subckt" + sub_circuit_content = sub_circuit_content.replace("\\n", "\n") + netlist_file.write(f".subckt {refdes}•{sub_circuit_content}\n") + have_embedded_subcircuit = True + else: + # List the libraries at the end + libraries_to_include.append(library_name) if typ == 'X': model = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" # schedule to write .SUBCKT clauses at the end if model not in subcircuits_to_write: @@ -445,6 +484,8 @@ def write_spice_to_file(self, netlist_file: TextIO): elif typ in ('QP', 'QN'): model = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" if symbol == 'NPNS' or symbol == 'PNPS' or symbol == 'LPNP': ports[3] = '[' + ports[3] + ']' nets = ' '.join(ports) @@ -454,6 +495,8 @@ def write_spice_to_file(self, netlist_file: TextIO): netlist_file.write(f'{refdes} {nets} [0] {model} {symbol}{parameters}\n') elif typ in ('MN', 'MP'): model = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" if symbol == 'NMOSB' or symbol == 'PMOSB': symbol = symbol[0:4] if len(ports) == 3: @@ -462,17 +505,25 @@ def write_spice_to_file(self, netlist_file: TextIO): netlist_file.write(f'{refdes} {nets} {model} {symbol}{parameters}\n') elif typ == 'T': model = decap(texts[1].get_text_attr(QSCH_TEXT_STR_ATTR)) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" netlist_file.write(f'{refdes} {nets} {model}{parameters}\n') elif typ in ('JN', 'JP'): model = decap(texts[1].get_text_attr(QSCH_TEXT_STR_ATTR)) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" if symbol.startswith('Pwr'): # Hack alert. I don't know why the symbol is Pwr symbol = symbol[3:] # remove the Pwr from the symbol netlist_file.write(f'{refdes} {nets} {model} {symbol}{parameters}\n') elif typ == '×': model = decap(texts[1].get_text_attr(QSCH_TEXT_STR_ATTR)) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" netlist_file.write(f'{refdes} «{nets}» {model}{parameters}\n') elif typ in ('ZP', 'ZN'): model = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR) + if have_embedded_subcircuit: + model = f"{refdes}•{model}" netlist_file.write(f'{refdes} {nets} {model} {symbol}{parameters}\n') else: value = texts[1].get_text_attr(QSCH_TEXT_STR_ATTR) @@ -480,12 +531,6 @@ def write_spice_to_file(self, netlist_file: TextIO): # else: # netlist_file.write(f'{symbol}†{refdes} {nets} {value}\n') - library_tags = symbol_tag.get_items('library') - for lib in library_tags: - library_name = lib.get_text_attr(2) - if library_name not in libraries_to_include: - libraries_to_include.append(library_name) - for sub_circuit in subcircuits_to_write: sub_circuit_schematic, ports = subcircuits_to_write[sub_circuit] netlist_file.write("\n") @@ -621,6 +666,7 @@ def _parse_qsch_stream(self, stream): components = self.schematic.get_items('component') for component in components: + have_embedded_subcircuit = False symbol: QschTag = component.get_items('symbol')[0] texts = symbol.get_items('text') if len(texts) < 2: @@ -634,6 +680,10 @@ def _parse_qsch_stream(self, stream): sch_comp.position = Point(x, y) sch_comp.rotation = orientation * 45 sch_comp.attributes['type'] = symbol.get_text('type', "X") # Assuming a sub-circuit + # a bit complicated way to detect embedded subcircuits: they are in the library tag, + lib = symbol.get_text('library file', "-") + if lib.startswith("|.subckt"): + have_embedded_subcircuit = True sch_comp.attributes['description'] = symbol.get_text('description', "No Description") sch_comp.attributes['value'] = value sch_comp.attributes['tag'] = component @@ -667,14 +717,15 @@ def _parse_qsch_stream(self, stream): self.components[refdes] = sch_comp if refdes.startswith('X'): - sub_circuit_name = value + os.path.extsep + 'qsch' - mydir = self.circuit_file.parent.absolute().as_posix() - sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name, mydir) - if sub_circuit_schematic_file: - sub_schematic = QschEditor(sub_circuit_schematic_file) - sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. - else: - _logger.warning(f"Subcircuit '{sub_circuit_name}' not found. Have you set the correct search paths?") + if not have_embedded_subcircuit: + sub_circuit_name = value + os.path.extsep + 'qsch' + mydir = self.circuit_file.parent.absolute().as_posix() + sub_circuit_schematic_file = self._qsch_file_find(sub_circuit_name, mydir) + if sub_circuit_schematic_file: + sub_schematic = QschEditor(sub_circuit_schematic_file) + sch_comp.attributes['_SUBCKT'] = sub_schematic # Store it for future use. + else: + _logger.warning(f"Subcircuit '{sub_circuit_name}' not found. Have you set the correct search paths?") for text_tag in self.schematic.get_items('text'): x, y = text_tag.get_attr(QSCH_TEXT_POS) diff --git a/spicelib/scripts/asc_to_qsch.py b/spicelib/scripts/asc_to_qsch.py index 8d75e4a..58ce5a4 100644 --- a/spicelib/scripts/asc_to_qsch.py +++ b/spicelib/scripts/asc_to_qsch.py @@ -26,7 +26,7 @@ from spicelib.editor.qsch_editor import QschEditor from spicelib.utils.file_search import find_file_in_directory -_logger = logging.getLogger() +_logger = logging.getLogger("spicelib.AscToQsch") def main(): diff --git a/spicelib/utils/file_search.py b/spicelib/utils/file_search.py index ddb9954..132d2d4 100644 --- a/spicelib/utils/file_search.py +++ b/spicelib/utils/file_search.py @@ -77,4 +77,5 @@ def search_file_in_containers(filename, *containers) -> Optional[str]: if filefound is not None: _logger.debug(f"Found '{filefound}'") return filefound + _logger.debug(f"Searching for '{filename}': NOT Found") return None diff --git a/unittests/golden/qsch_embedded_subckt.net b/unittests/golden/qsch_embedded_subckt.net index f36de12..443cad3 100644 --- a/unittests/golden/qsch_embedded_subckt.net +++ b/unittests/golden/qsch_embedded_subckt.net @@ -1,9 +1,9 @@ -* /workspace/spicelib/examples/testfiles/Qspice_bug_embedded_subckt.qsch +* C:\Users\hans\Documents\workspace\spicelib\reproducing_error\154\bug_test.qsch V1 IN 0 1 R1 OUT 0 1Meg -.subckt X2•P OUT IN +.subckt X2•P OUT IN E1 OUT 0 IN 0 {P} .ends P -X2 OUT IN X2•P P=2 +X2 OUT IN X2•P P=2 .tran 0 1m 0 100n uic .end diff --git a/unittests/test_qsch_editor.py b/unittests/test_qsch_editor.py index d5746fd..37a3a25 100644 --- a/unittests/test_qsch_editor.py +++ b/unittests/test_qsch_editor.py @@ -175,7 +175,7 @@ class QschEditorEmbeddedSubckt(unittest.TestCase): def test_embedded_subckt(self): self.edt = spicelib.editor.qsch_editor.QschEditor(test_dir + "Qspice_bug_embedded_subckt.qsch") self.edt.save_netlist(temp_dir + 'qsch_embedded_subckt.net') - # equalFiles(self, temp_dir + 'qsch_embedded_subckt.net', golden_dir + "qsch_embedded_subckt.net") + equalFiles(self, temp_dir + 'qsch_embedded_subckt.net', golden_dir + "qsch_embedded_subckt.net") if __name__ == '__main__': From f24c16290f1f4b6e8d5a2521da54039bbf27d099 Mon Sep 17 00:00:00 2001 From: hans boot Date: Thu, 20 Feb 2025 14:24:47 +0100 Subject: [PATCH 11/16] improved test files --- examples/testfiles/Qspice_bug_embedded_subckt.qsch | 4 ++-- unittests/golden/qsch_embedded_subckt.net | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/testfiles/Qspice_bug_embedded_subckt.qsch b/examples/testfiles/Qspice_bug_embedded_subckt.qsch index f11c7f9..7c37256 100644 --- a/examples/testfiles/Qspice_bug_embedded_subckt.qsch +++ b/examples/testfiles/Qspice_bug_embedded_subckt.qsch @@ -34,11 +34,11 @@ «symbol Proportional «type: X» «description: Proportional» - «library file: |.subckt P OUT IN\nE1 OUT 0 IN 0 {P}\n.ends P» + «library file: |.subckt mycomp OUT IN\nE1 OUT 0 IN 0 {P}\n.ends mycomp» «shorted pins: false» «triangle (-100,200) (-100,-200) (300,0) 0 0 0x1000000 0x2000000 -1 -1» «text (-100,250) 1 6 2 0x1000000 -1 -1 "X2"» - «text (0,0) 1 0 0 0x1000000 -1 -1 "P"» + «text (0,0) 1 0 0 0x1000000 -1 -1 "mycomp"» «text (0,-300) 1 0 0 0x1000000 -1 -1 "P=2"» «pin (300,0) (30,150) 0.63 13 0 0x0 -1 "OUT"» «pin (-100,0) (-100,10) 0.63 14 0 0x0 -1 "IN"» diff --git a/unittests/golden/qsch_embedded_subckt.net b/unittests/golden/qsch_embedded_subckt.net index 443cad3..104f2bb 100644 --- a/unittests/golden/qsch_embedded_subckt.net +++ b/unittests/golden/qsch_embedded_subckt.net @@ -1,9 +1,9 @@ * C:\Users\hans\Documents\workspace\spicelib\reproducing_error\154\bug_test.qsch V1 IN 0 1 R1 OUT 0 1Meg -.subckt X2•P OUT IN +.subckt X2•mycomp OUT IN E1 OUT 0 IN 0 {P} -.ends P -X2 OUT IN X2•P P=2 +.ends mycomp +X2 OUT IN X2•mycomp P=2 .tran 0 1m 0 100n uic .end From e269d520d1a58c7db02e876426344e9e375f9ec3 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sun, 23 Feb 2025 00:43:19 +0100 Subject: [PATCH 12/16] Work in progress making corrections and starting a proper testbench --- examples/testfiles/Qspice_top.qsch | 96 ++++++++++++++++++++++ examples/testfiles/sub_circuit2.qsch | 116 +++++++++++++++++++++++++++ spicelib/editor/qsch_editor.py | 73 ++++++++++------- unittests/test_qsch_editor.py | 8 ++ 4 files changed, 262 insertions(+), 31 deletions(-) create mode 100644 examples/testfiles/Qspice_top.qsch create mode 100644 examples/testfiles/sub_circuit2.qsch diff --git a/examples/testfiles/Qspice_top.qsch b/examples/testfiles/Qspice_top.qsch new file mode 100644 index 0000000..2f5d1c8 --- /dev/null +++ b/examples/testfiles/Qspice_top.qsch @@ -0,0 +1,96 @@ +ÿØÿÛ«schematic + «component (-1000,0) 0 0 + «symbol V + «type: V» + «description: Independent Voltage Source» + «shorted pins: false» + «line (0,-130) (0,-200) 0 0 0x1000000 -1 -1» + «line (0,200) (0,130) 0 0 0x1000000 -1 -1» + «rect (-25,77) (25,73) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-2,50) (2,100) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-25,-73) (25,-77) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «ellipse (-130,130) (130,-130) 0 0 0 0x1000000 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "V1"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "PWL 0 0 1m 5"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "+"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "-"» + » + » + «component (1400,200) 0 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "R1"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "10K"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «component (-800,800) 0 0 + «symbol V + «type: V» + «description: Independent Voltage Source» + «shorted pins: false» + «line (0,-130) (0,-200) 0 0 0x1000000 -1 -1» + «line (0,200) (0,130) 0 0 0x1000000 -1 -1» + «rect (-25,77) (25,73) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-2,50) (2,100) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-25,-73) (25,-77) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «ellipse (-130,130) (130,-130) 0 0 0 0x1000000 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "V2"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "5"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "+"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "-"» + » + » + «component (300,400) 0 0 + «symbol + «type: » + «description: MyDiff» + «shorted pins: false» + «rect (-500,-400) (500,400) 0 0 0 0xff0000 0xc8c8c8 -1 1 -1» + «text (-100,0) 1 12 0 0x1000000 -1 -1 "X2"» + «text (-300,-400) 0.681 13 0 0x1000000 -1 -1 "sub_circuit2"» + «pin (-500,-100) (0,0) 1 7 0 0x0 -1 "INP"» + «pin (-500,100) (0,0) 1 7 0 0x0 -1 "INN"» + «pin (0,400) (180,-190) 1 14 0 0x0 -1 "VDD"» + «pin (0,-400) (150,0) 1 14 0 0x0 -1 "VSS"» + «pin (500,0) (-150,0) 1 11 0 0x0 -1 "OUT"» + » + » + «component (-1700,300) 0 0 + «symbol V + «type: V» + «description: Independent Voltage Source» + «shorted pins: false» + «line (0,-130) (0,-200) 0 0 0x1000000 -1 -1» + «line (0,200) (0,130) 0 0 0x1000000 -1 -1» + «rect (-25,77) (25,73) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-2,50) (2,100) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «rect (-25,-73) (25,-77) 0 0 0 0x1000000 0x3000000 -1 0 -1» + «ellipse (-130,130) (130,-130) 0 0 0 0x1000000 0x1000000 -1 -1» + «text (150,50) 1 7 0 0x1000000 -1 -1 "V3"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "1"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "+"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "-"» + » + » + «net (-1000,-200) 1 13 0 "GND"» + «net (1400,0) 1 13 0 "GND"» + «net (-800,600) 1 13 0 "GND"» + «net (300,-500) 1 13 0 "GND"» + «net (-1700,100) 1 13 0 "GND"» + «wire (800,400) (1400,400) "N01"» + «wire (-800,1000) (300,1000) "N02"» + «wire (300,1000) (300,800) "N02"» + «wire (300,0) (300,-500) "GND"» + «wire (-200,500) (-1700,500) "N03"» + «wire (-1000,200) (-1000,300) "N04"» + «wire (-1000,300) (-200,300) "N04"» + «text (-1400,-350) 1 7 0 0x1000000 -1 -1 ".tran 1m"» +» + diff --git a/examples/testfiles/sub_circuit2.qsch b/examples/testfiles/sub_circuit2.qsch new file mode 100644 index 0000000..c7d4096 --- /dev/null +++ b/examples/testfiles/sub_circuit2.qsch @@ -0,0 +1,116 @@ +ÿØÿÛ«schematic + «component (-200,0) 0 0 + «symbol OpAmp + «type: û + «description: Generic Rail-to-Rail Output OpAmp» + «shorted pins: false» + «line (-100,450) (-100,-450) 0 0 0x1000000 -1 -1» + «line (500,0) (-100,450) 0 0 0x1000000 -1 -1» + «line (-50,200) (50,200) 0 0 0x1000000 -1 3» + «line (-50,-200) (50,-200) 0 0 0x1000000 -1 4» + «line (0,-250) (0,-150) 0 0 0x1000000 -1 4» + «line (500,0) (-100,-450) 0 0 0x1000000 -1 -1» + «text (202,300) 1 7 0 0x1000000 -1 -1 "Ã1"» + «text (200,0) 0.5 0 2 0x1000000 -1 -1 "RRopAmp"» + «text (454,-71) 0.5 7 0 0x1000000 -1 -1 "Avol=<100K>"» + «text (334,-151) 0.5 7 0 0x1000000 -1 -1 "GBW=<5Meg>"» + «text (244,-221) 0.5 7 0 0x1000000 -1 -1 "Slew=<5Meg>"» + «text (244,-321) 0.5 7 0 0x1000000 -1 -1 "Rload=<2K>"» + «text (244,-421) 0.5 7 0 0x1000000 -1 -1 "Phi=<60>"» + «pin (100,300) (0,0) 1 0 0 0x1000000 -1 "Vdd"» + «pin (100,-300) (0,0) 1 0 0 0x1000000 -1 "Vss"» + «pin (500,0) (0,0) 1 15 0 0x1000000 -1 "OUT"» + «pin (-100,200) (50,0) 1 15 0 0x1000000 -1 "IN-"» + «pin (-100,-200) (60,0) 1 15 0 0x1000000 -1 "IN+"» + «pin (0,100) (0,0) 1 15 0 0x1000000 -1 "MULT+" "¥"» + «pin (0,-100) (0,0) 1 15 0 0x1000000 -1 "MULT-" "¥"» + «pin (300,0) (60,0) 1 15 0 0x1000000 -1 "IN--" "¥"» + «pin (400,0) (20,0) 0.794 15 0 0x1000000 -1 "IN++" "¥"» + «pin (-100,0) (20,0) 0.794 7 0 0x1000000 -1 "EN" "¥"» + » + » + «component (-1300,200) 2 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (30,350) 1 110 0 0x1000000 -1 -1 "R1"» + «text (320,0) 1 109 0 0x1000000 -1 -1 "10K"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «component (-1300,-200) 2 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (30,350) 1 110 0 0x1000000 -1 -1 "R2"» + «text (-80,0) 1 109 0 0x1000000 -1 -1 "10K"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «component (-700,-600) 4 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (130,-150) 1 75 0 0x1000000 -1 -1 "R3"» + «text (130,150) 1 75 0 0x1000000 -1 -1 "22K"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «component (100,1200) 6 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (-80,0) 1 46 0 0x1000000 -1 -1 "R4"» + «text (80,0) 1 45 0 0x1000000 -1 -1 "22K"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » + «net (-2100,200) 1 11 1 "INN"» + «net (-100,700) 1 14 1 "VDD"» + «net (-100,-1200) 1 13 1 "VSS"» + «net (900,0) 1 7 1 "OUT"» + «net (-2100,-200) 1 11 1 "INP"» + «junction (800,0)» + «junction (-100,-1000)» + «junction (-700,200)» + «junction (-700,-200)» + «wire (800,0) (900,0) "OUT"» + «wire (-100,300) (-100,700) "VDD"» + «wire (-100,-1000) (-100,-1200) "VSS"» + «wire (-1500,-200) (-2100,-200) "INP"» + «wire (-1500,200) (-2100,200) "INN"» + «wire (300,1200) (800,1200) "OUT"» + «wire (800,1200) (800,0) "OUT"» + «wire (300,0) (800,0) "OUT"» + «wire (-700,1200) (-700,200) "N01"» + «wire (-300,200) (-700,200) "N01"» + «wire (-700,-200) (-1100,-200) "N02"» + «wire (-700,200) (-1100,200) "N01"» + «wire (-700,1200) (-100,1200) "N01"» + «wire (-700,-400) (-700,-200) "N02"» + «wire (-300,-200) (-700,-200) "N02"» + «wire (-700,-800) (-700,-1000) "VSS"» + «wire (-100,-300) (-100,-1000) "VSS"» + «wire (-700,-1000) (-100,-1000) "VSS"» +» + diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 265648d..e969098 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -207,30 +207,46 @@ def parse(cls, stream: str, start: int = 0) -> Tuple['QschTag', int]: self.items.append(child) elif stream[i] == '»': stop = i + 1 + break + elif stream[i] == '\n': if i > i0: - self.tokens.append(stream[i0:i]) - return self, stop - elif stream[i] == ' ' or stream[i] == '\n': - if i > i0: - self.tokens.append(stream[i0:i]) + tokens = stream[i0:i].split(' ') # No text here, so we can make it simpler + self.tokens.extend(tokens) i0 = i + 1 - elif stream[i] == '"': - # get all characters until the next " sign - i += 1 - while stream[i] != '"': - i += 1 - elif stream[i] == '(': - # todo: support also [] and {} - nested = 1 - while nested > 0: - i += 1 - if stream[i] == '(': - nested += 1 - elif stream[i] == ')': - nested -= 1 i += 1 else: raise IOError("Missing » when reading file") + line = stream[i0:i] + # Now dividing the + if ': ' in line: + tokens = line.split(': ') + self.tokens.extend(tokens) + else: + i = i0 = 0 + while i < len(line): + c = line[i] + if c == ' ' or c == '\n': + if i > i0: + self.tokens.append(line[i0:i]) + i0 = i + 1 + elif c == '"': + # get all characters until the next " sign + i += 1 + while c != '"': + i += 1 + # elif c == '(': + # # todo: support also [] and {} + # nested = 1 + # while nested > 0: + # i += 1 + # if line[i] == '(': + # nested += 1 + # elif line[i] == ')': + # nested -= 1 + i += 1 + if i > i0: + self.tokens.append(line[i0:i]) + return self, stop def __str__(self): """Returns only the first line of the tag. The children are not shown.""" @@ -256,17 +272,9 @@ def tag(self) -> str: """Returns the tag id of the object. The tag id is the first token in the tag.""" return self.tokens[0] - @property - def compound_tag(self) -> str: - """Returns the tag id of the object. The tag id is a concatenation of the first and second token in the tag.""" - return self.tokens[0] + " " + self.tokens[1] - def get_items(self, item) -> List['QschTag']: """Returns a list of children tags that match the given tag id.""" - if " " in item: - answer = [tag for tag in self.items if tag.compound_tag == item] - else: - answer = [tag for tag in self.items if tag.tag == item] + answer = [tag for tag in self.items if tag.tag == item] return answer def get_attr(self, index: int): @@ -293,10 +301,13 @@ def get_attr(self, index: int): try: value = int(a) except ValueError: - value = float(a) + try: + value = float(a) + except ValueError: + value = a return value - def set_attr(self, index: int, value): + def set_attr(self, index: int, value: Union[str, int, tuple]): """Sets the attribute at the given index. The attribute can be a string, an integer or a tuple. Integer values are written as integers, strings are written between quotes unless it starts with "0x" and tuples are written between parenthesis. @@ -304,7 +315,7 @@ def set_attr(self, index: int, value): :param index: The index of the attribute to be set :type index: int :param value: The value to be set - :type value: Union[str, int, tuple] + :type value: Union[str, int, Tuple[Any, Any]] :return: Nothing """ if isinstance(value, int): diff --git a/unittests/test_qsch_editor.py b/unittests/test_qsch_editor.py index 37a3a25..288ee7b 100644 --- a/unittests/test_qsch_editor.py +++ b/unittests/test_qsch_editor.py @@ -177,6 +177,14 @@ def test_embedded_subckt(self): self.edt.save_netlist(temp_dir + 'qsch_embedded_subckt.net') equalFiles(self, temp_dir + 'qsch_embedded_subckt.net', golden_dir + "qsch_embedded_subckt.net") + def test_sub_circuit(self): + self.edt = spicelib.editor.qsch_editor.QschEditor(test_dir + "Qspice_top.qsch") + # get sub-circuit + sub = self.edt.get_subcircuit("X2") + sub_desc = self.edt.get_component_value('X2') + self.assertEqual("sub_circuit2", sub_desc, "Value mismatch") + self.assertEqual('10K', sub.get_component_value("R1")) + if __name__ == '__main__': unittest.main() From 855b095ffa0ff4af07cad2cb23db8c9ffac7af03 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sun, 23 Feb 2025 02:01:02 +0100 Subject: [PATCH 13/16] Cleaning all errors from present testbench --- spicelib/editor/qsch_editor.py | 75 +++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index e969098..6964010 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -171,6 +171,36 @@ def decap(s: str) -> str: return regex.sub(r"\1=\2", s) +def smart_split(line: str) -> List[str]: + """Splits a string into chunks based on spaces. What is inside "" is not divided.""" + i = i0 = 0 + tokens = [] + while i < len(line): + c = line[i] + if c == '"': + # get all characters until the next " sign + i += 1 + while line[i] != '"': + i += 1 + elif c == ' ' or c == '\n': + if i > i0: + tokens.append(line[i0:i]) + i0 = i + 1 + # elif c == '(': + # # todo: support also [] and {} + # nested = 1 + # while nested > 0: + # i += 1 + # if line[i] == '(': + # nested += 1 + # elif line[i] == ')': + # nested -= 1 + i += 1 + if i > i0: + tokens.append(line[i0:i]) + return tokens + + class QschReadingError(IOError): ... @@ -205,12 +235,17 @@ def parse(cls, stream: str, start: int = 0) -> Tuple['QschTag', int]: child, i = QschTag.parse(stream, i) i0 = i + 1 self.items.append(child) + elif stream[i] == '"': + # get all characters until the next " sign + i += 1 + while stream[i] != '"': + i += 1 elif stream[i] == '»': stop = i + 1 break elif stream[i] == '\n': if i > i0: - tokens = stream[i0:i].split(' ') # No text here, so we can make it simpler + tokens = smart_split(stream[i0:i]) self.tokens.extend(tokens) i0 = i + 1 i += 1 @@ -219,33 +254,11 @@ def parse(cls, stream: str, start: int = 0) -> Tuple['QschTag', int]: line = stream[i0:i] # Now dividing the if ': ' in line: - tokens = line.split(': ') - self.tokens.extend(tokens) + name, text = line.split(': ') + self.tokens.append(name + ":") + self.tokens.append(text) else: - i = i0 = 0 - while i < len(line): - c = line[i] - if c == ' ' or c == '\n': - if i > i0: - self.tokens.append(line[i0:i]) - i0 = i + 1 - elif c == '"': - # get all characters until the next " sign - i += 1 - while c != '"': - i += 1 - # elif c == '(': - # # todo: support also [] and {} - # nested = 1 - # while nested > 0: - # i += 1 - # if line[i] == '(': - # nested += 1 - # elif line[i] == ')': - # nested -= 1 - i += 1 - if i > i0: - self.tokens.append(line[i0:i]) + self.tokens.extend(smart_split(line)) return self, stop def __str__(self): @@ -351,12 +364,8 @@ def get_text(self, label: str, default: str = None) -> str: raise IndexError(f"Label '{label}' not found in:{self}") else: return default - if " " in label: - start_pos = 2 - else: - start_pos = 1 - if len(a[0].tokens) > start_pos: - return ' '.join(a[0].tokens[start_pos:]) + if len(a[0].tokens) >= 2: + return a[0].tokens[1] else: return default From 70a8666bd1c6c16c6833743c8dd541c07c90b559 Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sun, 23 Feb 2025 02:10:39 +0100 Subject: [PATCH 14/16] Smarter smart_split function --- spicelib/editor/qsch_editor.py | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/spicelib/editor/qsch_editor.py b/spicelib/editor/qsch_editor.py index 6964010..6e33d44 100644 --- a/spicelib/editor/qsch_editor.py +++ b/spicelib/editor/qsch_editor.py @@ -171,34 +171,9 @@ def decap(s: str) -> str: return regex.sub(r"\1=\2", s) -def smart_split(line: str) -> List[str]: +def smart_split(s): """Splits a string into chunks based on spaces. What is inside "" is not divided.""" - i = i0 = 0 - tokens = [] - while i < len(line): - c = line[i] - if c == '"': - # get all characters until the next " sign - i += 1 - while line[i] != '"': - i += 1 - elif c == ' ' or c == '\n': - if i > i0: - tokens.append(line[i0:i]) - i0 = i + 1 - # elif c == '(': - # # todo: support also [] and {} - # nested = 1 - # while nested > 0: - # i += 1 - # if line[i] == '(': - # nested += 1 - # elif line[i] == ')': - # nested -= 1 - i += 1 - if i > i0: - tokens.append(line[i0:i]) - return tokens + return re.findall(r'[^"\s]+|"[^"]*"', s) class QschReadingError(IOError): From 2025dcc9c10a8502276237873164a8c93817522e Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sun, 23 Feb 2025 18:11:53 +0100 Subject: [PATCH 15/16] A slightly better unittest for the sub_circuits integration. --- examples/testfiles/sub_circuit2.qsch | 109 ++++++++++++++++++--------- unittests/test_qsch_editor.py | 4 + 2 files changed, 79 insertions(+), 34 deletions(-) diff --git a/examples/testfiles/sub_circuit2.qsch b/examples/testfiles/sub_circuit2.qsch index c7d4096..78513d7 100644 --- a/examples/testfiles/sub_circuit2.qsch +++ b/examples/testfiles/sub_circuit2.qsch @@ -1,34 +1,4 @@ ÿØÿÛ«schematic - «component (-200,0) 0 0 - «symbol OpAmp - «type: û - «description: Generic Rail-to-Rail Output OpAmp» - «shorted pins: false» - «line (-100,450) (-100,-450) 0 0 0x1000000 -1 -1» - «line (500,0) (-100,450) 0 0 0x1000000 -1 -1» - «line (-50,200) (50,200) 0 0 0x1000000 -1 3» - «line (-50,-200) (50,-200) 0 0 0x1000000 -1 4» - «line (0,-250) (0,-150) 0 0 0x1000000 -1 4» - «line (500,0) (-100,-450) 0 0 0x1000000 -1 -1» - «text (202,300) 1 7 0 0x1000000 -1 -1 "Ã1"» - «text (200,0) 0.5 0 2 0x1000000 -1 -1 "RRopAmp"» - «text (454,-71) 0.5 7 0 0x1000000 -1 -1 "Avol=<100K>"» - «text (334,-151) 0.5 7 0 0x1000000 -1 -1 "GBW=<5Meg>"» - «text (244,-221) 0.5 7 0 0x1000000 -1 -1 "Slew=<5Meg>"» - «text (244,-321) 0.5 7 0 0x1000000 -1 -1 "Rload=<2K>"» - «text (244,-421) 0.5 7 0 0x1000000 -1 -1 "Phi=<60>"» - «pin (100,300) (0,0) 1 0 0 0x1000000 -1 "Vdd"» - «pin (100,-300) (0,0) 1 0 0 0x1000000 -1 "Vss"» - «pin (500,0) (0,0) 1 15 0 0x1000000 -1 "OUT"» - «pin (-100,200) (50,0) 1 15 0 0x1000000 -1 "IN-"» - «pin (-100,-200) (60,0) 1 15 0 0x1000000 -1 "IN+"» - «pin (0,100) (0,0) 1 15 0 0x1000000 -1 "MULT+" "¥"» - «pin (0,-100) (0,0) 1 15 0 0x1000000 -1 "MULT-" "¥"» - «pin (300,0) (60,0) 1 15 0 0x1000000 -1 "IN--" "¥"» - «pin (400,0) (20,0) 0.794 15 0 0x1000000 -1 "IN++" "¥"» - «pin (-100,0) (20,0) 0.794 7 0 0x1000000 -1 "EN" "¥"» - » - » «component (-1300,200) 2 0 «symbol R «type: R» @@ -85,17 +55,79 @@ «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» » » + «component (-200,0) 0 0 + «symbol OpAmp + «type: û + «description: Generic Rail-to-Rail Output OpAmp» + «shorted pins: false» + «line (-100,450) (-100,-450) 0 0 0x1000000 -1 -1» + «line (500,0) (-100,450) 0 0 0x1000000 -1 -1» + «line (-50,200) (50,200) 0 0 0x1000000 -1 3» + «line (-50,-200) (50,-200) 0 0 0x1000000 -1 4» + «line (0,-250) (0,-150) 0 0 0x1000000 -1 4» + «line (500,0) (-100,-450) 0 0 0x1000000 -1 -1» + «text (202,300) 1 7 0 0x1000000 -1 -1 "Ã1"» + «text (200,0) 0.5 0 2 0x1000000 -1 -1 "RRopAmp"» + «text (454,-71) 0.5 7 0 0x1000000 -1 -1 "Avol=<100K>"» + «text (334,-151) 0.5 7 0 0x1000000 -1 -1 "GBW=<5Meg>"» + «text (244,-221) 0.5 7 0 0x1000000 -1 -1 "Slew=<5Meg>"» + «text (244,-321) 0.5 7 0 0x1000000 -1 -1 "Rload=<2K>"» + «text (244,-421) 0.5 7 0 0x1000000 -1 -1 "Phi=<60>"» + «pin (100,300) (0,0) 1 0 0 0x1000000 -1 "Vdd"» + «pin (100,-300) (0,0) 1 0 0 0x1000000 -1 "Vss"» + «pin (500,0) (0,0) 1 15 0 0x1000000 -1 "OUT"» + «pin (-100,200) (50,0) 1 15 0 0x1000000 -1 "IN-"» + «pin (-100,-200) (60,0) 1 15 0 0x1000000 -1 "IN+"» + «pin (0,100) (0,0) 1 15 0 0x1000000 -1 "MULT+" "¥"» + «pin (0,-100) (0,0) 1 15 0 0x1000000 -1 "MULT-" "¥"» + «pin (300,0) (60,0) 1 15 0 0x1000000 -1 "IN--" "¥"» + «pin (400,0) (20,0) 0.794 15 0 0x1000000 -1 "IN++" "¥"» + «pin (-100,0) (20,0) 0.794 7 0 0x1000000 -1 "EN" "¥"» + » + » + «component (1500,1000) 0 0 + «symbol Zener + «type: D» + «description: Zener Diode» + «library file: Zener.txt» + «shorted pins: false» + «line (80,80) (-80,80) 0 0 0x1000000 -1 -1» + «line (0,200) (0,80) 0 0 0x1000000 -1 -1» + «line (0,-200) (0,-70) 0 0 0x1000000 -1 -1» + «line (80,80) (130,130) 0 0 0x1000000 -1 -1» + «line (-130,30) (-80,80) 0 0 0x1000000 -1 -1» + «triangle (0,80) (100,-70) (-100,-70) 0 0 0x1000000 0x2000000 -1 -1» + «text (100,200) 1 7 0 0x1000000 -1 -1 "D1"» + «text (100,-200) 1 7 0 0x1000000 -1 -1 "MM3Z5V1T1G"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "A"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "K"» + » + » + «component (1500,1700) 0 0 + «symbol R + «type: R» + «description: Resistor(USA Style Symbol)» + «shorted pins: false» + «line (0,200) (0,180) 0 0 0x1000000 -1 -1» + «line (0,-180) (0,-200) 0 0 0x1000000 -1 -1» + «zigzag (-80,180) (80,-180) 0 0 0 0x1000000 -1 -1» + «text (100,150) 1 7 0 0x1000000 -1 -1 "R5"» + «text (100,-150) 1 7 0 0x1000000 -1 -1 "100"» + «pin (0,200) (0,0) 1 0 0 0x0 -1 "1"» + «pin (0,-200) (0,0) 1 0 0 0x0 -1 "2"» + » + » «net (-2100,200) 1 11 1 "INN"» - «net (-100,700) 1 14 1 "VDD"» + «net (1500,2100) 1 14 1 "VDD"» «net (-100,-1200) 1 13 1 "VSS"» - «net (900,0) 1 7 1 "OUT"» + «net (1800,0) 1 7 1 "OUT"» «net (-2100,-200) 1 11 1 "INP"» + «junction (1500,1300)» «junction (800,0)» «junction (-100,-1000)» «junction (-700,200)» «junction (-700,-200)» - «wire (800,0) (900,0) "OUT"» - «wire (-100,300) (-100,700) "VDD"» + «wire (800,0) (1800,0) "OUT"» «wire (-100,-1000) (-100,-1200) "VSS"» «wire (-1500,-200) (-2100,-200) "INP"» «wire (-1500,200) (-2100,200) "INN"» @@ -112,5 +144,14 @@ «wire (-700,-800) (-700,-1000) "VSS"» «wire (-100,-300) (-100,-1000) "VSS"» «wire (-700,-1000) (-100,-1000) "VSS"» + «wire (-100,300) (-100,800) "N03"» + «wire (-100,800) (1100,800) "N03"» + «wire (-100,-1000) (1500,-1000) "VSS"» + «wire (1500,-1000) (1500,800) "VSS"» + «wire (1500,1900) (1500,2100) "VDD"» + «wire (1100,800) (1100,1300) "N03"» + «wire (1100,1300) (1500,1300) "N03"» + «wire (1500,1200) (1500,1300) "N03"» + «wire (1500,1300) (1500,1500) "N03"» » diff --git a/unittests/test_qsch_editor.py b/unittests/test_qsch_editor.py index 288ee7b..66afb4a 100644 --- a/unittests/test_qsch_editor.py +++ b/unittests/test_qsch_editor.py @@ -184,6 +184,10 @@ def test_sub_circuit(self): sub_desc = self.edt.get_component_value('X2') self.assertEqual("sub_circuit2", sub_desc, "Value mismatch") self.assertEqual('10K', sub.get_component_value("R1")) + self.assertEqual('22K', sub.get_component_value("R4")) + sub.set_component_value('R5', 220) + self.edt.save_netlist(temp_dir + 'Qspice_top_edit.net') + equalFiles(self, golden_dir + 'Qspice_top_edit.net', temp_dir + 'Qspice_top_edit.net') if __name__ == '__main__': From 78b67e74a82e46df686dc51039b748e7c1ddc25b Mon Sep 17 00:00:00 2001 From: Nuno Brum Date: Sun, 23 Feb 2025 18:13:48 +0100 Subject: [PATCH 16/16] Adding missing golden file --- unittests/golden/Qspice_top_edit.net | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 unittests/golden/Qspice_top_edit.net diff --git a/unittests/golden/Qspice_top_edit.net b/unittests/golden/Qspice_top_edit.net new file mode 100644 index 0000000..e30ebe3 --- /dev/null +++ b/unittests/golden/Qspice_top_edit.net @@ -0,0 +1,20 @@ +* C:\sandbox\spicelib_dev\examples\testfiles\Qspice_top.qsch +V1 N04 0 PWL 0 0 1m 5 +R1 N01 0 10K +V2 N02 0 5 +X2 N04 N03 N02 0 N01 sub_circuit2 +V3 N03 0 1 + +.subckt sub_circuit2 INP INN VDD VSS OUT +R1 INN N01 10K +R2 INP N02 10K +R3 VSS N02 22K +R4 OUT N01 22K +Ã1 N03 VSS OUT N01 N02 ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ ¥ RRopAmp Avol=100K GBW=5Meg Slew=5Meg Rload=2K Phi=60 +D1 VSS N03 MM3Z5V1T1G +R5 VDD N03 220 +.lib C:\PROGRA~1\QSPICE\Zener.txt +.ends sub_circuit2 + +.tran 1m +.end