diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index b4aaa7d3e..c8d327fb5 100644 --- a/.github/workflows/pyspice-test.yml +++ b/.github/workflows/pyspice-test.yml @@ -6,12 +6,9 @@ name: Pyspice Test # Trigger the workflow on on: push: - branches: - - master - - devel + branches: "*" pull_request: - branches: - - master + branches: [ "*" ] # page_build: # release: # types: # This configuration does not affect the page_build event above diff --git a/.gitignore b/.gitignore index d75b2e63f..cd4142876 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,8 @@ examples/p.diff examples/spice-library-backup/ examples/spice-library/db.pickle test.cir + +.venv/* +*.vscode/* +examples/spice-library/**/**/*.yaml +examples/spice-library/**/*.yaml diff --git a/PySpice/Plot/BodeDiagram.py b/PySpice/Plot/BodeDiagram.py index d31a864f6..54e0e519e 100644 --- a/PySpice/Plot/BodeDiagram.py +++ b/PySpice/Plot/BodeDiagram.py @@ -33,7 +33,7 @@ def bode_diagram_gain(axe, frequency, gain, **kwargs): - axe.semilogx(frequency, gain, basex=10, **kwargs) + axe.semilogx(frequency, gain, base=10, **kwargs) axe.grid(True) axe.grid(True, which='minor') axe.set_xlabel("Frequency [Hz]") diff --git a/PySpice/Spice/Element.py b/PySpice/Spice/Element.py index 1007a9aa9..cd50ee567 100644 --- a/PySpice/Spice/Element.py +++ b/PySpice/Spice/Element.py @@ -211,7 +211,7 @@ def __iadd__(self, obj): ############################################## - def add_current_probe(self, circuit): + def add_current_probe(self, circuit, name=None): """Add a current probe between the node and the pin. The ammeter is named *ElementName_PinName*. @@ -221,8 +221,29 @@ def add_current_probe(self, circuit): # Fixme: add it to a list if self.connected: node = self._node - self._node = '_'.join((self._element.name, self._name)) - circuit.V(self._node, node, self._node, '0') + self._node = '_'.join((self._element.name, str(node), str(self.position) )) + if name is None: + name = self._node + circuit.V(name, node, self._node, '0') + else: + raise NameError("Dangling pin") + + def add_esr(self, circuit, name= None, value=1e-3): + + """Add a series resistance between the node and the pin. + + The ammeter is named *ElementName_PinName*. + + """ + + # Fixme: require a reference to circuit + # Fixme: add it to a list + if self.connected: + node = self._node + self._node = '_'.join((self._element.name, str(self._node), str(self.position))) + if name is None: + name = self._node + return circuit.R(name, node, self._node, value) else: raise NameError("Dangling pin") diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index ad46046f9..bd86d1edd 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -70,16 +70,27 @@ class SpiceLibrary: ############################################## - def __init__(self, root_path: str | Path, scan: bool = False) -> None: + def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = False, section: bool = False) -> None: + # recurse will be removed in the future maybe. it's here because skidl uses it + # if recurse: + # scan = recurse self._path = PathTools.expand_path(root_path) if not self._path.exists(): self._path.mkdir(parents=True) self._logger.info(f"Created {self._path}") + # elif self._path.is_file(): + # self._path = self._path.parent self._subcircuits = {} self._models = {} + self._recurse = recurse + self._section = section if not scan: if self.has_db_path: self.load() + '''Check if the library has the our path in the subcircuits.''' + paths = {Path(sub_path) for sub_path in self._subcircuits.values()} + if self._path not in paths: + scan = True else: self._logger.info("Initialize library...") scan = True @@ -91,6 +102,9 @@ def __init__(self, root_path: str | Path, scan: bool = False) -> None: @property def db_path(self) -> Path: + if self._path.is_file(): + # If the db path is for a file, use the file's parent directory + return self._path.parent.joinpath('db.pickle') return self._path.joinpath('db.pickle') @property @@ -165,19 +179,36 @@ def list_categories(self) -> str: ############################################## def scan(self) -> None: + self._logger.info(f"Scan {self._path}...") + + # Handle the case where self._path is a file, not a directory + if self._path.is_file(): + # Check if the file has a valid extension + _ = self._path.suffix.lower() + if _ in self.EXTENSIONS: + try: + self._handle_library(self._path) + except Exception as e: + self._logger.warning(f"Failed to parse {self._path}: {e}") + return + + # Handle the case where self._path is a directory for path in PathTools.walk(self._path): _ = path.suffix.lower() if _ in self.EXTENSIONS: - self._handle_library(path) + try: + self._handle_library(path) + except Exception as e: + self._logger.warning(f"Failed to parse {path}: {e}") ############################################## def _handle_library(self, path: Path) -> None: - spice_include = SpiceInclude(path) + spice_include = SpiceInclude(path, recurse=self._recurse, section=self._section) # Fixme: check overwrite - self._models.update({_.name: path for _ in spice_include.models}) - self._subcircuits.update({_.name: path for _ in spice_include.subcircuits}) + self._models.update({_.name: str(_.path) for _ in spice_include.models}) + self._subcircuits.update({_.name: str(_.path) for _ in spice_include.subcircuits}) ############################################## @@ -192,16 +223,63 @@ def delete_yaml(self) -> None: def __getitem__(self, name: str) -> Subcircuit | Model: if not self: self._logger.warning("Empty library") + + # First, check if the requested item exists directly + path = None if name in self._subcircuits: path = self._subcircuits[name] elif name in self._models: path = self._models[name] else: - # print('Library {} not found in {}'.format(name, self._path)) - # self._logger.warn('Library {} not found in {}'.format(name, self._path)) + # Item not found directly - warn and raise KeyError + available = list(self._subcircuits.keys()) + list(self._models.keys()) + available_str = ", ".join(available[:10]) + if len(available) > 10: + available_str += f", ... ({len(available)-10} more)" + self._logger.warning(f"Library item '{name}' not found in {self._path}. Available: {available_str}") raise KeyError(name) - # Fixme: lazy ??? - return SpiceInclude(path)[name] + + # Create SpiceInclude with recursion enabled if requested + spice_include = SpiceInclude(path, recurse=self._recurse) + + try: + # Try to get the item directly from this SpiceInclude + return spice_include[name] + except KeyError: + # Item exists in index but not in the file - search in parent directory + self._logger.info(f"Item '{name}' referenced in {path} but not found there. Searching in sibling files...") + + # Try to find the component in sibling library files + original_path = Path(path) + parent_dir = original_path.parent + + # Try looking for the item in other library files in the same directory + for lib_ext in self.EXTENSIONS: + for sibling_file in parent_dir.glob(f"*{lib_ext}"): + if sibling_file == original_path: + continue # Skip the original file + + try: + self._logger.info(f"Checking {sibling_file} for {name}") + sibling_include = SpiceInclude(sibling_file, recurse=self._recurse) + # Check if this file contains our item + for subckt in sibling_include.subcircuits: + if subckt.name == name: + return subckt + for model in sibling_include.models: + if model.name == name: + return model + except Exception as e: + self._logger.warning(f"Error checking {sibling_file}: {e}") + + # If we still can't find it, try one last method - force reparse everything + try: + self._logger.info(f"Attempting one last search with forced reparse for {name}") + reparse_include = SpiceInclude(path, rewrite_yaml=True, recurse=True) + return reparse_include[name] + except KeyError: + # Detailed error message when all recovery attempts fail + raise KeyError(f"'{name}' referenced in library index but not found in any source file. Check your include hierarchy.") ############################################## diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index ae6231c8c..b67ca0b5a 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -32,7 +32,7 @@ import hashlib import logging import os - +import re import yaml from PySpice.Spice.Parser import SpiceFile, ParseError @@ -183,8 +183,39 @@ def _parse_nodes(self, nodes: list[str]) -> None: for index, node_str in enumerate(nodes): internal_node = None name = None + if isinstance(node_str, dict): + # if we are hear we probably have read the yaml file + internal_node = node_str['internal_node'] + node_str = node_str['name'] + if isinstance(node_str, str): + node_str = node_str.strip() + # make sure the node is a valid spice node identifier + reg_ex = r''' + (?i: # case-insensitive + (?:[+\-\%](?=[a-z_]))? # optional +, - or % at the start, only if followed by letter/underscore + [a-z0-9_]+ # one or more letters/digits/underscores + (?:\.[a-z0-9_]+)? # optionally a dot, followed by one or more letters/digits/underscores + (?:(?<=[a-z0-9])[+\-] # optionally a plus or minus if it's right after a letter/digit + (?![a-z0-9.]) # and not followed by letter/digit/dot + )? + )(?!:) # negative lookahead: do not allow a colon immediately after + ''' + # Using VERBOSE to ignore whitespace/comments in the regex + pattern = re.compile(reg_ex, re.VERBOSE) + match = pattern.fullmatch(node_str) + if match: + name = match.group(0) + if not internal_node: + internal_node = index + 1 + # continue + else: + self._logger.warning(f"Invalid pin format {node_str} for {self.name}") + # self._valid = False + # return + continue if isinstance(node_str, int): internal_node = node_str + name = f'{internal_node}' else: node_str = node_str.strip() i = node_str.find(' ') @@ -232,7 +263,7 @@ def pin_names(self) -> list[str]: def to_yaml(self) -> dict: _ = super().to_yaml() - _.update({'nodes': self._nodes}) + _.update({'nodes': [{'index': _.index, 'name': _.name, 'internal_node': _.internal_node} for _ in self._nodes]}) return _ ############################################## @@ -258,7 +289,7 @@ class SpiceInclude: ############################################## - def __init__(self, path: str | Path, rewrite_yaml: bool = False) -> None: + def __init__(self, path: str | Path, rewrite_yaml: bool = False, recurse: bool = False, section: str | None = None) -> None: self._path = Path(path) # .resolve() self._extension = None @@ -269,6 +300,8 @@ def __init__(self, path: str | Path, rewrite_yaml: bool = False) -> None: self._subcircuits = {} self._digest = None self._recursive_digest = None + self._recurse = recurse + self._section = section # Fixme: check still valid ! if not rewrite_yaml and self.has_yaml: @@ -277,9 +310,69 @@ def __init__(self, path: str | Path, rewrite_yaml: bool = False) -> None: else: self.parse() self.write_yaml() + if self._recurse: + self._process_inner_includes() ############################################## + def _process_inner_includes(self) -> None: + """Process inner includes recursively and add their models and subcircuits. + + This method processes all inner includes detected during parsing and + adds their models and subcircuits to this SpiceInclude instance. + """ + processed_paths = set() # Track processed paths to avoid circular includes + processed_paths.add(str(self._path.resolve())) + + # Create a list to store inner include instances + includes = [] + + # Process each inner include path + for path_str in self._inner_includes: + try: + path = Path(path_str) + # Ensure path is absolute by resolving it relative to parent directory if needed + if not path.is_absolute(): + path = (self._path.parent / path).resolve() + else: + path = path.resolve() + + # Skip if we've already processed this path + if str(path) in processed_paths: + self._logger.info(f"Skipping already processed include: {path}") + continue + + # Check if file exists + if not path.exists(): + self._logger.warning(f"Include file not found: {path}") + continue + + self._logger.info(f"Processing inner include {path}") + include = SpiceInclude(path, recurse=self._recurse) + includes.append(include) + processed_paths.add(str(path)) + + # Add models from this include + for model in include.models: + if model.name in self._models: + self._logger.warning(f"Duplicate model {model.name} from {path}, keeping original") + else: + self._models[model.name] = model + + # Add subcircuits from this include + for subcircuit in include.subcircuits: + if subcircuit.name in self._subcircuits: + self._logger.warning(f"Duplicate subcircuit {subcircuit.name} from {path}, keeping original") + else: + self._subcircuits[subcircuit.name] = subcircuit + + except Exception as e: + self._logger.error(f"Failed to process inner include {path_str}: {str(e)}") + + # Replace the path strings with actual SpiceInclude instances + # for recursive digest computation + self._inner_includes = includes + # def dump(self) -> None: # print(self._path) # for _ in self._models: @@ -362,12 +455,29 @@ def add_line(item): def parse(self) -> None: self._logger.info(f"Parse {self._path}") try: - spice_file = SpiceFile(self._path) + spice_file = SpiceFile(self._path, self._section) except ParseError as exception: # Parse problem with this file, so skip it and keep going. self._logger.warn(f"Parse error in Spice library {self._path}{NEWLINE}{exception}") - self._inner_includes = [Path(str(_)) for _ in spice_file.includes] - self._inner_libraries = [Path(str(_)) for _ in spice_file.libraries] + + # Convert include paths to absolute paths if they are relative + self._inner_includes = [] + for include_path in spice_file.includes: + path = Path(str(include_path)) + # If path is not absolute, make it absolute using self.path's parent directory + if not path.is_absolute(): + path = self._path.parent / path + self._inner_includes.append(path) + + # Process library files separately + # self._inner_libraries = [] + # for lib in spice_file.libraries: + # path = Path(str(lib.path)) + # # If path is not absolute, make it absolute using self.path's parent directory + # if not path.is_absolute(): + # path = self._path.parent / path + # self._inner_libraries.append(path) + for subcircuit in spice_file.subcircuits: # name = self._suffix_name(subcircuit.name) _ = Subcircuit(self, subcircuit.name, subcircuit.nodes) @@ -391,7 +501,7 @@ def write_yaml(self): with open(self.yaml_path, 'w', encoding='utf8') as fh: data = { # 'path': str(self._path), - 'path': self._path.name, + 'path': str(self.path), 'date': self.mtime.isoformat(), 'digest': self.digest, 'description': self._description, @@ -401,7 +511,7 @@ def write_yaml(self): if self._subcircuits: data['subcircuits'] = [_.to_yaml() for _ in self.subcircuits] if self._inner_includes: - data['inner_includes'] = self._inner_includes + data['inner_includes'] = [str(_) for _ in self._inner_includes] if self._inner_libraries: data['inner_libraries'] = self._inner_libraries # data['recursive_digest'] = self.recursive_digest @@ -426,7 +536,7 @@ def load_yaml(self) -> None: if 'subcircuits' in data: subcircuits = [Subcircuit.from_yaml(self, _) for _ in data['subcircuits']] self._subcircuits = {_.name: _ for _ in subcircuits} - if 'inner_libraries' in data: + if 'inner_includes' in data: self._inner_includes = data['inner_includes'] if 'inner_libraries' in data: self._inner_libraries = data['inner_libraries'] diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 5a420d0ca..e9f42da36 100644 --- a/PySpice/Spice/Netlist.py +++ b/PySpice/Spice/Netlist.py @@ -629,7 +629,7 @@ def include(self, path: Union[Path, str, 'Library.SubCircuit'], warn: bool = Tru # Fixme: str(path) ? # Fixme: circular import... from . import Library - if isinstance(path, Library.Subcircuit): + if isinstance(path, Library.Subcircuit) or isinstance(path, Library.Model): path = path.path path = Path(path).resolve() if path not in self._includes: diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 8d805f0a8..fc934a968 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -416,10 +416,22 @@ def setup_platform(cls): cls.LIBRARY_PATH = _ else: if ConfigInstall.OS.on_windows: - ngspice_path = Path(__file__).parent.joinpath('Spice64_dll') - cls.NGSPICE_PATH = ngspice_path - # path = ngspice_path.joinpath('dll-vs', 'ngspice-{version}{id}.dll') - path = ngspice_path.joinpath('dll-vs', 'ngspice{}.dll') + # Check for MSYSTEM environment first + msystem = os.environ.get('MSYSTEM') + mingw_prefix = os.environ.get('MINGW_PREFIX') + + if msystem and mingw_prefix: + # Use MINGW paths + path = str(Path(mingw_prefix) / 'bin' / 'libngspice-0{}.dll') + if 'SPICE_LIB_DIR' not in os.environ: + os.environ['SPICE_LIB_DIR'] = str(Path(mingw_prefix) / 'share' / 'ngspice' / 'scripts') + else: + # Fall back to original Windows paths + ngspice_path = Path(__file__).parent.joinpath('Spice64_dll') + cls.NGSPICE_PATH = ngspice_path + # path = ngspice_path.joinpath('dll-vs', 'ngspice-{version}{id}.dll') + path = str(ngspice_path.joinpath('dll-vs', 'ngspice{}.dll')) + elif ConfigInstall.OS.on_osx: path = 'libngspice{}.dylib' elif ConfigInstall.OS.on_linux: @@ -619,7 +631,24 @@ def _send_char(message_c, ngspice_id, user_data): func = self._logger.info elif content.startswith('Warning:'): func = self._logger.warning - # elif content.startswith('Warning:'): + elif content.startswith('Using'): # Ignore "Using ... as Direct Linear Solver" messages + func = self._logger.debug + elif content.startswith('doAnalyses:'): + func = self._logger.debug + if 'timestep too small' in content.lower(): + self._logger.warning(content) + elif content.startswith('run simulation interrupted'): + func = self._logger.debug + elif content.startswith('simulation interrupted'): + func = self._logger.debug + elif content.startswith('Note:'): + func = self._logger.info + elif content.startswith('Trying'): + func = self._logger.info + elif content.startswith('Supplies reduced'): + func = self._logger.info + elif content.startswith('run simulation(s) aborted'): + func = self._logger.error else: self._error_in_stderr = True func = self._logger.error @@ -630,10 +659,10 @@ def _send_char(message_c, ngspice_id, user_data): else: self._stdout.append(content) # Fixme: Ngspice writes error on stdout and stderr ... - if 'error' in content.lower(): - self._error_in_stdout = True - # if self._error_in_stdout: - # self._logger.warning(content) + # if 'error' in content.lower(): + # self._error_in_stdout = True + if self._error_in_stdout: + self._logger.warning(content) # Fixme: ??? return self.send_char(message, ngspice_id) @@ -695,6 +724,7 @@ def _background_thread_running(is_running, ngspice_id, user_data): self = ffi.from_handle(user_data) self._logger.debug(f'ngspice_id-{ngspice_id} background_thread_running {is_running}') self._is_running = is_running + return 0 ############################################## diff --git a/PySpice/Spice/NgSpice/SimulationType.py b/PySpice/Spice/NgSpice/SimulationType.py index 882e98089..2f9d068d1 100644 --- a/PySpice/Spice/NgSpice/SimulationType.py +++ b/PySpice/Spice/NgSpice/SimulationType.py @@ -82,7 +82,7 @@ 'charge', ) -LAST_VERSION = 42 # released on 2023-12-27 +LAST_VERSION = 45 # released on January 31st, 2021 for version in range(28, LAST_VERSION +1): SIMULATION_TYPE[version] = SIMULATION_TYPE[27] diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 6787dcdae..526379407 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -104,6 +104,8 @@ def ngspice(self): @property def version(self): return self._ngspice_shared.ngspice_version + def _run(self, analysis_method, *args, **kwargs): + background = kwargs.pop('background', False) ############################################## @@ -117,7 +119,6 @@ def run(self, simulation): self._ngspice_shared.load_circuit(str(simulation)) self._ngspice_shared.run() self._logger.debug(str(self._ngspice_shared.plot_names)) - plot_name = self._ngspice_shared.last_plot if plot_name == 'const': raise NameError('Simulation failed') diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 9b34e3239..cdd33a090 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -84,7 +84,7 @@ from ..Netlist import Node from ..StringTools import remove_multi_space from . import Ast -from . import ElementData +from .ElementData import ElementData from .Ast import AstNode from .Parser import SpiceParser from .SpiceSyntax import ElementLetters @@ -313,7 +313,14 @@ def __init__(self, line: SpiceLine, ast: AstNode) -> None: # Read nodes self._nodes = [] number_of_pins = 0 - data = ElementData.elements[self._letter] + from PySpice.Spice.Element import ElementParameterMetaClass + elements = {} + for letter, classes in ElementParameterMetaClass._classes.items(): + element_data = ElementData(letter, classes) + elements[letter] = element_data + elements[letter.lower()] = element_data + # data = ElementData.elements[self._letter] + data = elements[self._letter] if not data.has_variable_number_of_pins: number_of_pins = data.number_of_pins else: # Q or X @@ -989,6 +996,7 @@ def reset(self) -> None: self._subcircuits = [] self._circuit = Netlist() self._control = [] + self._section = None ############################################## @@ -1007,13 +1015,16 @@ def _split_command_comment(cls, line): ############################################## - def read(self, generator: Generator[tuple[int, str], None, None], title_line: bool=True) -> None: + def read(self, generator: Generator[tuple[int, str], None, None], title_line: bool=True, section: str=None ) -> None: """Preprocess lines. This method merges continuation lines and split command and comment. """ self.reset() last_line = None last_command = None + we_are_in_lib = False + self._section = section + libname = None for line_number, line in generator: # print(f'>>>{line_number}///{line.rstrip()}') ### line = line.strip() @@ -1048,6 +1059,14 @@ def read(self, generator: Generator[tuple[int, str], None, None], title_line: bo last_line.append(line_number, '', comment) else: last_line = SpiceLine(line_number, line_number, command, comment) + if command.startswith('.lib'): + libname = command.split('lib')[1].strip().lower() + we_are_in_lib = True + elif command.startswith('.endl'): + we_are_in_lib = False + libname = None + if self._section and libname != self._section.lower() and we_are_in_lib: + continue self._lines.append(last_line) if command: last_command = last_line @@ -1097,7 +1116,10 @@ def append(obj: Command) -> None: continue cls = Command.get_cls(line, get_state() == SpiceStates.CONTROL) obj = cls(line, ast) - self._obj_lines.append(obj) + + # don't append if in subcircuit. it will be added in the subcircuit anyway + if not_state(SpiceStates.SUBCIRCUIT): + self._obj_lines.append(obj) self._logger.debug(os.linesep + repr(obj)) match obj: case If(): @@ -1105,7 +1127,9 @@ def append(obj: Command) -> None: append(obj) raise NotImplementedError case Include(): - self._includes.append(obj) + self._includes.append(obj.path) + case Library(): + self._libs.append(obj) case Control(): state_stack.append(SpiceStates.CONTROL) control.append(obj) @@ -1157,10 +1181,10 @@ def parse_string(self, source: str, title_line: bool=True) -> None: ############################################## - def parse_file(self, path: str | Path) -> None: + def parse_file(self, path: str | Path, section: str=None) -> None: with open(path, 'r', encoding='utf-8') as fh: generator = enumerate(fh.readlines()) - self.read(generator, title_line=True) + self.read(generator, title_line=True, section=section) self._parse() ############################################## @@ -1269,7 +1293,7 @@ class SpiceFile(SpiceSource): ############################################## - def __init__(self, path: str | Path) -> None: + def __init__(self, path: str | Path, section: str=None) -> None: super().__init__() self._path = Path(path) - self.parse_file(path) + self.parse_file(path, section) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 9f452eed6..6478eaa35 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -70,6 +70,13 @@ class SpiceParser: ############################################## + # Declare an INCLUSIVE state called PARAM + states = ( + ('NODES', 'inclusive'), + ) + + ############################################### + # When building the master regular expression, rules are added in the following order: # - All tokens defined by functions are added in the same order as they appear in the lexer file. # - Tokens defined by strings are added next by sorting them in order of decreasing regular @@ -127,6 +134,18 @@ def t_error(self, token): # token.lexer.skip(1) raise NameError('Lexer error') + + def t_DOT_COMMAND(self, t): + r'\.(?i:[a-z]+)' + # Switch to the PARAM state on seeing a dot command + t.lexer.begin('NODES') + return t + + def t_NODES_SET(self, t): + r'=' + # Switch back to the DEFAULT state on seeing a = + t.lexer.begin('INITIAL') + return t ############################################## t_ignore = ' \t' @@ -167,7 +186,7 @@ def t_error(self, token): t_LEFT_BRACE = r'\{' t_RIGHT_BRACE = r'\}' - t_QUOTE = r"'" + t_QUOTE = r'[\'"]' t_SET = r'=' t_BRANCH = r'\#branch' @@ -175,15 +194,16 @@ def t_error(self, token): t_TILDE = r'~' - t_STRING = r'"((\\")|[^"])*"' + # t_STRING = r'"((\\")|[^"])*"' - t_DOT_COMMAND = r'\.(?i:[a-z]+)' + # t_DOT_COMMAND = r'\.(?i:[a-z]+)' # Note: # If ID > NUMBER, then it breaks float and yield A - B # If NUMBER < ID, then 2N2222A is split in two NUMBER tokens: Number 2 n, Number 2222 None a # Solution: use [a-z0-9]* for EXTRA_UNIT + def t_NUMBER(self, t): # Fixme: CONTEXTUAL SYNTAX !!! in_offset=[0.1 -0.2] r''' @@ -225,7 +245,6 @@ def t_NUMBER(self, t): else: t.value = Number(value, unit, extra_unit) return t - def t_ID(self, t): # Fixme: r''' @@ -235,6 +254,21 @@ def t_ID(self, t): )''' # t.value = Id(t.value) return t + + + def t_NODES_ID(self, t): + # Fixme: + r''' + (?i: + (?:[+\-\%](?=[a-z_]))? + [a-z0-9_][-a-z0-9_]* + (?:\.[a-z0-9_][-a-z0-9_]*)? + (?:(?<=[a-z0-9])[+\-](?![a-z0-9.-]))? + :? + )''' + # t.value = Id(t.value) + return t + ############################################## # @@ -270,6 +304,7 @@ def p_command(self, p): ''' return self._command(p, Command) + # Fixme: could be merged with command def p_dot_command(self, p): '''command : DOT_COMMAND expression_list_space @@ -303,7 +338,18 @@ def p_id(self, p): '''expression : ID ''' p[0] = Id(p[1]) - + + # -------------------------------------------------------------------------------------- + # CHANGED: Added this rule to handle patterns like "params:" in a .subckt line. + # Example: .subckt genopa1 in+ in- vcc vee out params: POLE=20 ... + # def p_NODES_id_colon(self, p): + # '''expression : ID COLON + # ''' + # # Treat "params:" (or any ID followed by a colon) as a single expression. + # p[0] = Id(p[1] + ':') + + # # --- + def p_uminus(self, p): '''expression : MINUS expression %prec UMINUS''' # %prec UMINUS overrides the default rule precedence-setting it to that of UMINUS in the precedence specifier. diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 58eb77db3..c02e2a6cb 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -74,7 +74,7 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER """ self._circuit = Circuit(spice_code.title) self._ground = ground - + for obj in spice_code.obj_lines: self.handle(obj) @@ -152,6 +152,7 @@ def handle_EndIf(self, obj: EndIf) -> None: def handle_EndSubcircuit(self, obj: EndSubcircuit) -> None: raise NotImplementedError + # pass ############################################## @@ -262,7 +263,15 @@ def handle_Sensitivity(self, obj: Sensitivity) -> None: def handle_Subcircuit(self, obj: Subcircuit, ground=Node.SPICE_GROUND_NUMBER) -> None: subcircuit = SubCircuit(obj._name, *obj._nodes) - SpiceParser._build_circuit(subcircuit, obj._statements, ground) + # Add all elements from obj._items to the subcircuit + for item in obj._items: + # Create a temporary Builder to handle each item in context of the subcircuit + temp_builder = Builder() + temp_builder._circuit = subcircuit + temp_builder._ground = ground + temp_builder.handle(item) + # Original SpiceParser._build_circuit(subcircuit, obj._statements, ground) + self._circuit.subcircuit(subcircuit) return subcircuit ############################################## diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index d4020afbf..311d1fbc2 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -738,7 +738,7 @@ def _run(self, analysis_method, *args, **kwargs): if 'probes' in kwargs: self.save(* kwargs.pop('probes')) - + # Execute analysis implementation analysis_method(self, *args, **kwargs) diff --git a/README.html b/README.html index 8d69b6c50..4f53b4c8b 100644 --- a/README.html +++ b/README.html @@ -616,12 +616,11 @@

PySpice : Simulate Electronic Circuit using Python and the Ngs

Anaconda last version Anaconda donwloads

PySpice build status @travis-ci.org

-

Pyspice Test

Quick Links

V1.4.3 2020-07-04

A huge effort, thanks to @stuarteberg Stuart Berg, has been made to make Ngspice and PySpice available on Anaconda (conda-forge) for the Window, OSX and Linux platforms. Thanks to the -conda-forge continuous integration platform, we can now run unit tests and the examples on these +conda-forge continuous integration platform, we can now run unit tests and the examples on theses platforms automatically. Hope this will make the software more robust and easier to run !

-
  • Fixed node order to not confuse users Now PySpice matches SPICE order for two ports elements !

  • +
  • Fixed node order so as to not confuse users Now PySpice matches SPICE order for two ports elements !

  • Fixed device shortcuts in Netlist class

  • -
  • Fixed model kwargs for BJT Notice: it must be passed exclusively as kwargs !

  • +
  • Fixed model kwarg for BJT Notice: it must be passed exclusively as kwarg !

  • Fixed subcircuit nesting

  • Outsourced documentation generator to Pyterate

  • Updated setup.py for wheel

  • @@ -892,7 +855,7 @@

    V0.4.2

    V0.4.0 2017-07-31

      -
    • Git repository clean-up: filtered generated doc and useless files to shrink the repository size.

    • +
    • Git repository cleanup: filtered generated doc and useless files so as to shrink the repository size.

    • Improved documentation generator: Implemented format for RST content and Tikz figure.

    • Improved unit support: It implements now the International System of Units. And we can now use unit helper like u_mV or compute the value of 1.2@u_kΩ / 2@u_mA. diff --git a/README.rst b/README.rst index 5151a7294..d3a3102fe 100644 --- a/README.rst +++ b/README.rst @@ -43,10 +43,6 @@ .. |Tavis CI master| image:: https://travis-ci.com/FabriceSalvaire/PySpice.svg?branch=master :target: https://travis-ci.com/FabriceSalvaire/PySpice :alt: PySpice build status @travis-ci.org - -.. |Pyspice Test Workflow| image:: https://github.com/FabriceSalvaire/PySpice/actions/workflows/pyspice-test.yml/badge.svg?branch=devel - :target: https://github.com/FabriceSalvaire/PySpice/actions/workflows/pyspice-test.yml - :alt: Pyspice Test .. -*- Mode: rst -*- .. _CFFI: http://cffi.readthedocs.org/en/latest/ @@ -79,9 +75,9 @@ .. |Tikz| replace:: Tikz .. |Xyce| replace:: Xyce -====================================================================================== +===================================================================================== PySpice : Simulate Electronic Circuit using Python and the Ngspice / Xyce Simulators -====================================================================================== +===================================================================================== |Pypi License| |Pypi Python Version| @@ -93,18 +89,14 @@ |Tavis CI master| -|Pyspice Test Workflow| - **Quick Links** -* `Devel Branch `_ * `Production Branch `_ -* `Travis CI `_ but need free credits... - +* `Devel Branch `_ +* `Travis CI `_ * `pyspice@conda-forge `_ * `conda-forge/pyspice `_ * `ngspice@conda-forge `_ - * `Ngspice `_ * `Ngspice Bug Tracker `_ * `Xyce of Sandia National Laboratories `_ @@ -117,7 +109,8 @@ The free Discourse forum was closed some time ago due to a lack of activity. A HTML backup is stored in the directory `pyspice-discourse-backup`. -**On HEAD** +**On Devel HEAD** + * fixed the ngspice library loading for recent cffi * fixed simulation aborting due to a message from newer ngspice * fixes for Spice parser @@ -183,7 +176,7 @@ pull requests blindly then there is a high risk this software will become a mess Credits ======= -Authors: `Fabrice SALVAIRE `_ and `contributors `_ +Authors: `Fabrice Salvaire `_ and `contributors `_ News ==== @@ -193,58 +186,17 @@ News .. no title here -Vx.y.0 (wishes) ----------------- - -* The circuit API is actually low level. It is fastidious to work with - and error-prone. Skidl has a very good approach to make the - connections between elements. A clever idea is to make the - connection through loop, e.g. `gnd & C1 & (R1 | R2) & D1 & vcc`. -* Improve Spice library handling, e.g. we have to read the library - code to know how to map the pins, etc. -* Unit should be provided by a third party. We need a library that works well with Spice. - V1.6.0 (development release) ---------------------------- -* **New Simulation API** - - .. code-block:: python - - # build a circuit - - # instantiate a simulator - simulator = Simulator.factory() - # or - simulator = Simulator.factory(simulator='ngspice') - # same as - simulator = Simulator.factory(simulator='ngspice-shared') - - # create a simulation, it corresponds to the Spice code part with lines starting with ".something ..." - simulation = simulator.simulation(circuit, temperature=25, nominal_temperature=25) - # define an analysis and run it - analysis = simulation.transient(step_time=ac_line.period/200, end_time=ac_line.period*50, log_desk=True) - # analysis is now Pickable - -* Simulation output is now Pickable - -* The **Spice parser** was rewritten from scratch using the `PLY `_ - library, which is an implementation of lex and yacc parsing tools for Python. The LALR parser - generates an AST from a BNF grammar written from scratch using the Ngspice manual. Up to now, it - only requires a hack to handle the grammar, cf. XSpice vector syntax :code:`[1 -1 -2]` which - interfere with mathematical expression. PySpice is now able to parse completely and properly all - the examples from the Ngspice manual. However, the processing of the AST does actually the bare - minimum. * **KiCadTools** a proof of concept module to read KiCad 6 `.kicad_sch` schema file and compute the netlist. *This module can - be used to perform any kind of processing on a KiCad schema. It is + be used to perform any kind of processings on a KiCad schema. It is actually hosted in the source but could become a standalone project.* For PySpice, it provides a very flexible way to draft a circuit with the help of KiCad and then generate the netlist without using the netlist export feature of KiCad. And thus leverage the - writing of fastidious circuit. -* The most common PySpice parts can be imported from :code:`from PySpice import ...` -* Logging setup code clean-up + writing of fastidious cicruit. V1.5.0 (production release) 2021-05-15 -------------------------------------- @@ -265,14 +217,14 @@ V1.5.0 (production release) 2021-05-15 * `Netlist.py`: Fix wrong method when joining parameters during netlist parse #245 (thanks to cyber-g) * Unit: add Pickle support * Add Parser code from #136 (thanks to jmgc) but not yet merged -* Unit: add :code:`np.mean` +* Unit: add np.mean V1.4.3 2020-07-04 ----------------- A huge effort, thanks to @stuarteberg Stuart Berg, has been made to make Ngspice and PySpice available on Anaconda (conda-forge) for the Window, OSX and Linux platforms. Thanks to the -conda-forge continuous integration platform, we can now run unit tests and the examples on these +conda-forge continuous integration platform, we can now run unit tests and the examples on theses platforms automatically. Hope this will make the software more robust and easier to run ! * PySpice is now available on Anaconda(conda-forge) as well as a wheel on PyPI @@ -280,24 +232,24 @@ platforms automatically. Hope this will make the software more robust and easie It should now simplify considerably the PySpice installation on Windows. * This tool can also download the examples and the Ngspice PDF manual. * On Linux and OSX, a Ngspice package is now available on Anaconda(conda-forge). - Note that theses two platforms do not download a binary from Ngspice since a compiler can easily be installed on these platforms. + Note that theses two platforms do not download a binary from Ngspice since a compiler can easily be installed on theses platforms. * Updated installation documentation for Linux, the main distributions now provide a ngspice shared package. -* Added a front-end website to keep older releases documentation available on the web. +* Added a front-end web site so as to keep older releases documentation available on the web. * fixed and rebuilt all examples (but mistakes could happen ...) * examples are now available as Python files and Jupyter notebooks (but some issues must be fixed, e.g. due to the way Jupyter handles Matplotlib plots) * support NgSpice 32 API (no change) -* removed :code:`@substitution@` in PySpice/__init__.py, beacause it breaks pip install from git +* removed @substitution@ in PySpice/__init__.py, beacause it breaks pip install from git * fixed some logging spams * fixed NonLinearVoltageSource * fixed Unicode issue with °C (° is Extended ASCII) * fixed ffi_string_utf8 for UnicodeDecodeError -* fixed logging formatter for OSX (removed ANSI codes) +* fixed logging formater for OSX (removed ANSI codes) * reworded "Invalid plot name" exception * removed diacritics in example filenames -* cir2py has been converted to an entry point to work on all platforms +* cir2py has been converted to an entry point so as to work on all platforms * updated Matplotlib subplots in examples * added a unit example * added a NMOS example (thanks to cyber-g) cf. #221 @@ -317,7 +269,7 @@ This release is yanked due to broken Windows support. * support NgSpice 31 API (no change) * added check for `CoupledInductor` #157 * added `check-installation` tool to help to fix broken installation -* added pole-zero, noise, distortion, transfer-function analyses (thanks to Peter Garrone) #191 +* added pole-zero, noise, distorsion, transfer-function analyses (thanks to Peter Garrone) #191 * added `.measure` support (thanks to ceprio) #160 * added `log_desk` parameter to `CircuitSimulator` * added `listing` command method to `NgSpiceShared` @@ -343,7 +295,7 @@ V1.2.0 2018-06-07 * Implemented missing transmission line devices * Implemented high level current sources **Notice: Some classes were renamed !** -* Implemented node kwargs e.g. :code:`circuit.Q(1, base=1, collector=2, emitter=3, model='npn')` +* Implemented node kwarg e.g. :code:`circuit.Q(1, base=1, collector=2, emitter=3, model='npn')` * Implemented raw spice pass through (see `User FAQ `_) * Implemented access to internal parameters (cf. :code:`save @device[parameter]`) * Implemented check for missing ground node @@ -356,9 +308,9 @@ V1.2.0 2018-06-07 * Added Numpy array support to unit, see `UnitValues` **Notice: this new feature could be buggy !!!** * Rebased `WaveForm` to `UnitValues` -* Fixed node order to not confuse users **Now PySpice matches SPICE order for two ports elements !** +* Fixed node order so as to not confuse users **Now PySpice matches SPICE order for two ports elements !** * Fixed device shortcuts in `Netlist` class -* Fixed model kwargs for BJT **Notice: it must be passed exclusively as kwargs !** +* Fixed model kwarg for BJT **Notice: it must be passed exclusively as kwarg !** * Fixed subcircuit nesting * Outsourced documentation generator to |Pyterate|_ * Updated `setup.py` for wheel @@ -387,7 +339,7 @@ V0.4.2 V0.4.0 2017-07-31 ----------------- -* Git repository clean-up: filtered generated doc and useless files to shrink the repository size. +* Git repository cleanup: filtered generated doc and useless files so as to shrink the repository size. * Improved documentation generator: Implemented :code:`format` for RST content and Tikz figure. * Improved unit support: It implements now the International System of Units. And we can now use unit helper like :code:`u_mV` or compute the value of :code:`1.2@u_kΩ / 2@u_mA`. diff --git a/TODO b/TODO new file mode 100644 index 000000000..e2bb67ff3 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +change spice library to be backwards compatible \ No newline at end of file diff --git a/clean.sh b/clean.sh new file mode 100755 index 000000000..3d0c872b9 --- /dev/null +++ b/clean.sh @@ -0,0 +1,6 @@ +#!/bin/bash +rm *.erc +rm *.log + +find examples/spice-library -name '*.yaml' -delete +find examples/spice-library -name '*.pickle' -delete diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst index 02fdf5892..45335fbd4 100644 --- a/doc/sphinx/source/index.rst +++ b/doc/sphinx/source/index.rst @@ -58,7 +58,7 @@ while |Xyce|_ is a SPICE compatible simulator developed by the `Sandia National .. rst-class:: small-text - (*) PySpice is licensed under GPLv3 therms. + (*) PySpice is licensed under GPLv3 terms. PySpice implements a Ngspice binding and provides an oriented object API on top of SPICE, the simulation output is converted to |Numpy|_ arrays for convenience. diff --git a/examples/diode/diode-characteristic-curve.py b/examples/diode/diode-characteristic-curve.py index 23d7f6a8d..a66524faa 100755 --- a/examples/diode/diode-characteristic-curve.py +++ b/examples/diode/diode-characteristic-curve.py @@ -96,9 +96,9 @@ circuit.V('input', 'in', circuit.gnd, 10@u_V) circuit.R(1, 'in', 'out', 1@u_Ω) # not required for simulation D1N4148 = spice_library['1N4148'] -# circuit.include(D1N4148) -# circuit.X('D1', '1N4148', 'out', circuit.gnd) -circuit.X('D1', D1N4148, cathode='out', anode=circuit.gnd) +circuit.include(D1N4148) +circuit.X('D1', '1N4148', 'out', circuit.gnd) +# circuit.X('D1', D1N4148, cathode='out', anode=circuit.gnd) #r# We simulate the circuit at these temperatures: 0, 25 and 100 °C. diff --git a/examples/diode/zener-characteristic-curve.py b/examples/diode/zener-characteristic-curve.py index 9ea1820ac..6ebd073e8 100755 --- a/examples/diode/zener-characteristic-curve.py +++ b/examples/diode/zener-characteristic-curve.py @@ -61,7 +61,7 @@ # U = RI R = U/I dynamic_resistance = np.diff(-analysis.out) / np.diff(analysis.Vinput) # ax2.plot(analysis.out[:-1], dynamic_resistance/1000) -ax2.semilogy(analysis.out[10:-1], dynamic_resistance[10:], basey=10) +ax2.semilogy(analysis.out[10:-1], dynamic_resistance[10:]) ax2.axvline(x=0, color='black') ax2.axvline(x=-5.6, color='red') ax2.legend(('Dynamic Resistance',), loc=(.1,.8)) diff --git a/examples/kicadrw/dump-netlist.py b/examples/kicadrw/dump-netlist.py index 1f4eddd9d..736a61db0 100644 --- a/examples/kicadrw/dump-netlist.py +++ b/examples/kicadrw/dump-netlist.py @@ -1,5 +1,4 @@ #################################################################################################### - from pathlib import Path from KiCadRW.Schema import KiCadSchema diff --git a/examples/operational-amplifier/OperationalAmplifier-api-brainstorming.py b/examples/operational-amplifier/OperationalAmplifier-api-brainstorming.py index 31a9c6eb5..e82b8472f 100644 --- a/examples/operational-amplifier/OperationalAmplifier-api-brainstorming.py +++ b/examples/operational-amplifier/OperationalAmplifier-api-brainstorming.py @@ -45,13 +45,14 @@ class BasicOperationalAmplifier(SubCircuit): # SubCircuitFactory NODES = ('non_inverting_input', 'inverting_input', 'output') - # Comment: R doesn't know its name, R prefix is redundant - Rinput = R('non_inverting_input', 'inverting_input', 10@u_MΩ) + def __init__(self): + # Comment: R doesn't know its name, R prefix is redundant + Rinput = self.R('non_inverting_input', 'inverting_input', 10@u_MΩ) - gain = VCVS('non_inverting_input', 'inverting_input', 1, self.gnd, kilo(100)) - RP1 = R(1, 2, 1@u_kΩ) - CP1 = C(2, self.gnd, 1.591@u_uF) + gain = self.VCVS('non_inverting_input', 'inverting_input', 1, self.gnd, kilo(100)) + RP1 = self.R(1, 2, 1@u_kΩ) + CP1 = self.C(2, self.gnd, 1.591@u_uF) - # Comment: buffer is a Python name - buffer = VCVS(2, self.gnd, 3, self.gnd, 1) - Rout = R(3, 'output', 10@u_Ω) + # Comment: buffer is a Python name + buffer = self.VCVS(2, self.gnd, 3, self.gnd, 1) + Rout = self.R(3, 'output', 10@u_Ω) diff --git a/examples/persistance/SimulateCircuit.py b/examples/persistance/SimulateCircuit.py index 772c460ac..da44d50ea 100644 --- a/examples/persistance/SimulateCircuit.py +++ b/examples/persistance/SimulateCircuit.py @@ -12,7 +12,7 @@ libraries_path = find_libraries() spice_library = SpiceLibrary(libraries_path) -1/0 +# 1/0 #################################################################################################### diff --git a/examples/run-examples b/examples/run-examples index a2bf27c70..780432b5b 100644 --- a/examples/run-examples +++ b/examples/run-examples @@ -24,6 +24,7 @@ #################################################################################################### from pathlib import Path +import argparse import glob import os import subprocess @@ -31,13 +32,74 @@ import sys #################################################################################################### +def run_example(file_name, no_gui=False): + """Run a single example file with proper error handling.""" + print(f"Running {file_name}{' (GUI and plots suppressed)' if no_gui else ''}") + + env = os.environ.copy() + if no_gui: + env['MPLBACKEND'] = 'Agg' # Non-interactive matplotlib backend + env['PYSPICE_NO_DISPLAY'] = '1' # Custom env var that can be checked in examples + env['PYTHONUNBUFFERED'] = '1' # Ensure output is displayed immediately + + try: + result = subprocess.run(['python', file_name], env=env, check=True) + return True + except subprocess.CalledProcessError as e: + print(f"Error running {file_name}: {e}") + return False + +# Parse command line arguments +parser = argparse.ArgumentParser(description='Run PySpice examples') +parser.add_argument('--no-gui', action='store_true', help='Run without GUI and plots') +args = parser.parse_args() + examples_path = Path(__file__).resolve().parent +successful_examples = [] +failed_examples = [] + for topic in os.listdir(examples_path): - python_files = glob.glob(str(examples_path.joinpath(topic, '*.py'))) + topic_path = examples_path.joinpath(topic) + if not topic_path.is_dir() or topic.startswith('.'): + continue + + python_files = glob.glob(str(topic_path.joinpath('*.py'))) for file_name in python_files: - if file_name.islower(): - print('Run {}'.format(file_name)) - subprocess.call(('python', file_name)) - print('To continue press Enter') - rc = sys.stdin.readline().strip() + + # Run automatically if --no-gui is specified, otherwise prompt the user + if args.no_gui: + success = run_example(file_name, args.no_gui) + if success: + successful_examples.append(file_name) + else: + failed_examples.append(file_name) + print("Example failed, but continuing with next example...") + else: + print(f'Would you like to run {file_name}? [y/N/q(uit)]') + choice = sys.stdin.readline().strip().lower() + + if choice == 'q': + print("Exiting the examples runner.") + sys.exit(0) + elif choice == 'y': + success = run_example(file_name, args.no_gui) + if success: + successful_examples.append(file_name) + else: + failed_examples.append(file_name) + print("Example failed, but continuing with next example...") + +# After all examples are run - always show the summary, regardless of mode +print("\n" + "="*80) +print(f"EXECUTION SUMMARY:") +print(f"Successfully executed: {len(successful_examples)} examples") + +if failed_examples: + print(f"\nFailed to execute: {len(failed_examples)} examples") + print("Failed examples:") + for example in failed_examples: + print(f" - {example}") +else: + print("\nAll examples executed successfully!") +print("="*80) diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py new file mode 100644 index 000000000..00bd63004 --- /dev/null +++ b/examples/skywater/print_subcircuit_nodes.py @@ -0,0 +1,54 @@ +from PySpice.Doc.ExampleTools import find_libraries +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +#################################################################################################### + +libraries_path = "../skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" + +spice_lib = SpiceLibrary(libraries_path, recurse=True, section="tt") + +#################################################################################################### + +circuit = Circuit('Circuit with Subcircuits') + + +# # Safely iterate through subcircuits with error handling +# print("\nSubcircuit details:") +# for subcirc in spice_lib.subcircuits: +# try: +# print(f"\nProcessing subcircuit: {subcirc}") + +# # Safely access the subcircuit +# subckt = spice_lib[subcirc] +# print(f" Successfully loaded subcircuit: {subcirc}") + +# # Check what attributes are available +# if hasattr(subckt, "path"): +# print(f" Path: {subckt.path}") + +# # Try to access nodes safely +# if hasattr(subckt, "_nodes"): +# print(f" Nodes: {len(subckt._nodes)} nodes found") +# for i, node in enumerate(subckt._nodes): +# print(f" Node {i+1}: {node}") +# else: +# print(" No _nodes attribute found") + +# # Check for additional attributes +# for attr in ["name", "pin_names", "description"]: +# if hasattr(subckt, attr): +# print(f" {attr}: {getattr(subckt, attr)}") + +# except KeyError as e: +# print(f" Error: Could not find subcircuit '{subcirc}' - {e}") +# except Exception as e: +# print(f" Error processing subcircuit '{subcirc}': {e}") + +# Print information about the library +print(f"Library loaded from: {libraries_path}") +print(f"Available subcircuits in library: {list(spice_lib.subcircuits)}") +print(f"Total number of subcircuits found: {len(list(spice_lib.subcircuits))}") +# Print the circuit +print("\nCircuit:") +print(circuit) \ No newline at end of file diff --git a/examples/spice-library/discrete-semiconductors/diodes/switching/1N4148.yaml b/examples/spice-library/discrete-semiconductors/diodes/switching/1N4148.yaml deleted file mode 100644 index 05a81834e..000000000 --- a/examples/spice-library/discrete-semiconductors/diodes/switching/1N4148.yaml +++ /dev/null @@ -1,15 +0,0 @@ -path: 1N4148.lib -date: '2019-03-09T21:58:34.063064' -digest: b00af8ae3a95d37162ce29f70e0dc62d234c742d -description: high-speed diodes -subcircuits: -- name: 1N4148 - description: high-speed diode - brand: NXP - parameters: - VRRM: 100V - IFRM: 450 mA - trr: 4ns - nodes: - - 1 cathode - - 2 anode diff --git a/examples/spice-library/generic_format.lib b/examples/spice-library/generic_format.lib new file mode 100644 index 000000000..bc34070f7 --- /dev/null +++ b/examples/spice-library/generic_format.lib @@ -0,0 +1,22 @@ +* generic OpAmp model +* gain, phase, offset, limits to power supply + +.model mcrdlm5 c tc1 = 0 tc2 = 0 cox = {crdlm5} capsw = {crdlm5sw} w = {wminrdl} tnom = 25.0 +*.model sky130_fd_pr__res_generic_nd r tc1r = {tc1rsn} tc2r = {tc2rsn} rsh = {rdn} dw = {"-tol_nfom/2-nfom_dw/2"} tnom = 30.0 + +*.include "../cells/nfet_01v8/sky130_fd_pr__nfet_01v8__tt.corner.spice" +*.include "temp.lib" + +.subckt generic_comp in+ in- vcc vee out params: POLE=20 GAIN=20k VOFF=10m ROUT=10 +Voff in+ inoff dc {VOFF} +G10 0 int inoff in- 100u +R1 int 0 {GAIN/100u} +C1 int 0 {1/(6.28*(GAIN/100u)*POLE)} +Eout 2 0 int 0 1 +Rout 2 out {ROUT} +Elow 3 0 vee 0 1 +Ehigh 8 0 vcc 0 1 +Dlow 3 int Dlimit +Dhigh int 8 Dlimit +.model Dlimit D N=0.01 +.ends diff --git a/examples/spice-library/semiconductors/NCP1117.lib b/examples/spice-library/semiconductors/NCP1117.lib new file mode 100644 index 000000000..543d2ca97 --- /dev/null +++ b/examples/spice-library/semiconductors/NCP1117.lib @@ -0,0 +1,882 @@ +* PSpice Model Editor - Version 10.0.0 + +*$ +.SUBCKT ncp1117_adj1-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +*Commercial Use or Resale Restricted * +* by Symmetry License Agreement * +************************************** +* Model generated on Feb 15, 95 +*Rev. Date: Dec. 20, 1994 +*Positive Adjustable Regulator +*External Node Designations +*node 1: VREF (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 2.88462e-06 +RST6 135 2 15000 +RIQX 133 132 2884.62 +.MODEL RQIX RES (TC1=-0) +RSET 19 2 1250 +.MODEL RSET RES (TC1=8e-05 TC2=5.28e-07) +RS1 10 12 0.0071 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 8 +RISC 30 2 10000 +.MODEL RISC RES (TC1=0) +ROV 34 2 2000 +VOV 33 34 18 +EOV2 2 40 34 2 1 +FIQD 3 2 VSENS1 0 +RY 52 54 2e+07 +RA 72 73 3.65905e+07 +RYR 71 72 4.46584e+06 +CRA 52 73 3.8765e-12 +HSTEP 76 2 VSENS1 0.071 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_adj1-x +*$ +.SUBCKT ncp1117_285-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 1.81818e-06 +RST6 135 2 10000 +RIQX 133 132 1818.18 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 2850 +.MODEL RSET RES( ++TC1=0 ++TC2=0) +RS1 10 12 0.0048 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 1.22684e+07 +RA 72 73 1e08 +RYR 71 72 2.51089e+06 +CRA 52 73 0.0001 +HSTEP 76 2 VSENS1 0.048 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_285-x +*$ +.SUBCKT ncp1117_120-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 3.33333e-06 +RST6 135 2 20000 +RIQX 133 132 3333.33 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 12000 +.MODEL RSET RES( ++TC1=0 ++TC2=0) +RS1 10 12 0.02 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 1.00402e+07 +RA 72 73 1e08 +RYR 71 72 500187 +CRA 52 73 0.0001 +HSTEP 76 2 VSENS1 0.2 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_120-x +*$ +.SUBCKT ncp1117_50-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 2.5e-06 +RST6 135 2 15000 +RIQX 133 132 2500 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 5000 +.MODEL RSET RES( ++TC1=0 ++TC2=0) +RS1 10 12 0.0084 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 1.11111e+07 +RA 72 73 1e08 +RYR 71 72 1.12102e+06 +CRA 52 73 0.0001 +HSTEP 76 2 VSENS1 0.084 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_50-x +*$ +.SUBCKT ncp1117_33-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 2.5e-06 +RST6 135 2 15000 +RIQX 133 132 2500 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 3300 +.MODEL RSET RES( ++TC1=0 ++TC2=0) +RS1 10 12 0.0054 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 1.25219e+07 +RA 72 73 1e08 +RYR 71 72 1.58389e+06 +CRA 52 73 0.0001 +HSTEP 76 2 VSENS1 0.054 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_33-x +*$ +.SUBCKT ncp1117_25-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 1.81818e-06 +RST6 135 2 10000 +RIQX 133 132 1818.18 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 2500 +.MODEL RSET RES( ++TC1=8e-05 ++TC2=5.28e-07) +RS1 10 12 0.004125 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 2e+07 +RA 72 73 2.30806e+07 +RYR 71 72 2.81738e+06 +CRA 52 73 6.14545e-13 +HSTEP 76 2 VSENS1 0.04125 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_25-x +*$ +.SUBCKT ncp1117_20-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 2.66667e-06 +RST6 135 2 12000 +RIQX 133 132 2666.67 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 2000 +.MODEL RSET RES( ++TC1=8e-05 ++TC2=5.28e-07) +RS1 10 12 0.00375 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 2e+06 +RA 72 73 2.5899e+07 +RYR 71 72 3.16128e+06 +CRA 52 73 5.47672e-13 +HSTEP 76 2 VSENS1 0.0375 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_20-x +*$ +.SUBCKT ncp1117_18-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 2.80952e-06 +RST6 135 2 11800 +RIQX 133 132 2809.52 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 1800 +.MODEL RSET RES( ++TC1=8e-05 ++TC2=5.28e-07) +RS1 10 12 0.00325 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 2.52525e+06 +RA 72 73 1.08219e+07 +RYR 71 72 4.46584e+06 +CRA 52 73 1.04107e-12 +HSTEP 76 2 VSENS1 0.0325 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_18-x +*$ +.SUBCKT ncp1117_15-x 3 1 2 +************************************** +* Model Generated by MODPEX * +*Copyright(c) Symmetry Design Systems* +* All Rights Reserved * +* UNPUBLISHED LICENSED SOFTWARE * +* Contains Proprietary Information * +* Which is The Property of * +* SYMMETRY OR ITS LICENSORS * +* Modeling services provided by * +* Interface Technologies * +* www.i-t.com * +************************************** +*Model generated on 12/19/2004 +*Rev. Date: Dec. 20, 1994 +*Positive Fixed Regulator +*External Node Designations +*node 1: VREG (OUTPUT) +*node 2: Ground (Common) +*node 3: Line Voltage +ECCX 131 2 135 2 1.0 +VXX 133 2 DC 0 +FSET6 2 135 VSENS2 1 +FPP 3 2 VXX 1.0 +R_YY 31 2 1e6 +R_XX 15 2 1e8 +R_ZZ 36 2 1e6 +R_QQ 65 2 1e8 +RXX 1 2 1e8 +VSENS1 10 1 DC 0 +ISET 2 15 DC 1e-3 +DON1 15 16 DMOD1 +VSENS2 16 19 DC 0 +DON2 15 17 DMOD1 +EON2 18 2 3 2 1 +FYY 3 2 VSENS1 1 +DON3 15 27 DMOD1 +VDROP3 28 27 DC 2 +EON3 28 2 3 2 4 +ELINE 13 42 66 2 1 +FSET2 2 36 VSENS2 1 +DSC1 36 35 DMOD1 +RCL1 36 37 10 +DSC2 37 38 DMOD1 +ESCCON 38 39 30 2 1 +VSCCON 39 40 DC 0 +FSC 19 2 VSCCON 1 +FSET3 2 31 VSENS2 1 +DOV1 31 32 DMOD1 +EOV1 32 2 3 1 1 +DOV2 31 33 DMOD1 +ISET4 2 30 DC 1e-3 +ELOAD 41 2 77 2 -1 +ERIPPLE 42 41 72 2 1 +EREF 12 13 19 2 1 +E3 52 2 3 2 1 +CBYPS 54 2 0.001 +VORB 54 60 DC 0 +RB 60 2 1e3 +RBR 72 2 1000 +CBS2 52 71 1 +RSTEP 77 2 1 +FRB 2 65 VORB 1 +DRB2 65 67 DMOD1 +VXRB 67 68 DC -1 +EXRB 68 2 1 2 1 +DRB1 65 66 DMOD1 +RB1 66 2 1000 +.MODEL DMOD1 D +*-- DMOD1 DEFAULT PARAMETERS +*IS=1e-14 RS=0 N=1 TT=0 CJO=0 +*VJ=1 M=0.5 EG=1.11 XTI=3 FC=0.5 +*KF=0 AF=1 BV=inf IBV=1e-3 TNOM=27 +EXX 132 131 3 131 3.19444e-06 +RST6 135 2 11500 +RIQX 133 132 3194.44 +.MODEL RQIX RES(TC1=-0.001163) +RSET 19 2 1500 +.MODEL RSET RES( ++TC1=8e-05 ++TC2=5.28e-07) +RS1 10 12 0.002875 +VDROPX 18 17 0.77 +HSENSE1 35 2 VSENS1 6.66667 +RISC 30 2 10000 +.MODEL RISC RES(TC1=0) +ROV 34 2 1000 +VOV 33 34 18 +EOV2 2 40 34 2 3.33333 +FIQD 3 2 VSENS1 0 +RY 52 54 3.33333e+06 +RA 72 73 1.08219e+07 +RYR 71 72 4.46584e+06 +CRA 52 73 1.04107e-12 +HSTEP 76 2 VSENS1 0.02875 +CSTEP 76 77 5.30516e-06 +.ENDS ncp1117_15-x +*$ diff --git a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib new file mode 100644 index 000000000..115d38361 --- /dev/null +++ b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib @@ -0,0 +1,15 @@ +* generic OpAmp model +* gain, phase, offset, limits to power supply +.subckt genopa1 in+ in- vcc vee out params: POLE=20 GAIN=20k VOFF=10m ROUT=10 +Voff in+ inoff dc {VOFF} +G10 0 int inoff in- 100u +R1 int 0 {GAIN/100u} +C1 int 0 {1/(6.28*(GAIN/100u)*POLE)} +Eout 2 0 int 0 1 +Rout 2 out {ROUT} +Elow 3 0 vee 0 1 +Ehigh 8 0 vcc 0 1 +Dlow 3 int Dlimit +Dhigh int 8 Dlimit +.model Dlimit D N=0.01 +.ends diff --git a/examples/spice-parser/parse-subcircuit-example.py b/examples/spice-parser/parse-subcircuit-example.py new file mode 100644 index 000000000..15d578f3a --- /dev/null +++ b/examples/spice-parser/parse-subcircuit-example.py @@ -0,0 +1,35 @@ + +import PySpice.Logging.Logging as Logging +logger = Logging.setup_logging() + +#################################################################################################### + +from PySpice import Circuit, SubCircuit, SubCircuitFactory +from PySpice.Spice.Parser.HighLevelParser import SpiceSource +from PySpice.Spice.Parser import Translator +from PySpice.Unit import * + +class ParallelResistor2(SubCircuit): + __nodes__ = ('n1', 'n2') + def __init__(self, name, R1=1@u_Ω, R2=2@u_Ω): + SubCircuit.__init__(self, name, *self.__nodes__) + self.R(1, 'n1', 'n2', R1) + self.R(2, 'n1', 'n2', R2) + +circuit = Circuit('Test') +circuit.R('1', 'input', 'n1', 1@u_Ω) +circuit.subcircuit(ParallelResistor2('pr1', R2=2@u_Ω)) +circuit.X('1', 'pr1', 1, circuit.gnd) +circuit.subcircuit(ParallelResistor2('pr2', R2=3@u_Ω)) +circuit.X('2', 'pr2', 1, circuit.gnd) + +source = str(circuit) +print(circuit) + +spice_source = SpiceSource(source=source, title_line=False) +bootstrap_circuit = Translator.Builder().translate(spice_source) +bootstrap_source = str(bootstrap_circuit) + +print(bootstrap_source) + +assert bootstrap_source == source diff --git a/examples/spice-parser/raw_spice_parser.py b/examples/spice-parser/raw_spice_parser.py new file mode 100644 index 000000000..2343bbc7a --- /dev/null +++ b/examples/spice-parser/raw_spice_parser.py @@ -0,0 +1,84 @@ + +import PySpice.Logging.Logging as Logging +logger = Logging.setup_logging() + +#################################################################################################### + +from PySpice import Circuit, SubCircuit, SubCircuitFactory +from PySpice.Spice.Parser.HighLevelParser import SpiceSource +from PySpice.Spice.Parser import Translator +from PySpice.Unit import * + +raw_spice = ''' +* Qucs 25.1.1 ... +*.INCLUDE "/usr/share/qucs-s/xspice_cmlib/include/ngspice_mathfunc.inc" + +.SUBCKT SpiceOpamp_LM358 gnd 1 2 3 4 5 +* +C1 11 12 5.544E-12 +C2 6 7 20.00E-12 +DC 5 53 DX +DE 54 5 DX +DLP 90 91 DX +DLN 92 90 DX +DP 4 3 DX +EGND 99 0 POLY(2) (3,0) (4,0) 0 .5 .5 +FB 7 99 POLY(5) VB VC VE VLP VLN 0 15.91E6 -20E6 20E6 20E6 -20E6 +GA 6 0 11 12 125.7E-6 +GCM 0 6 10 99 7.067E-9 +IEE 3 10 DC 10.04E-6 +HLIM 90 0 VLIM 1K +Q1 11 2 13 QX +Q2 12 1 14 QX +R2 6 9 100.0E3 +RC1 4 11 7.957E3 +RC2 4 12 7.957E3 +RE1 13 10 2.773E3 +RE2 14 10 2.773E3 +REE 10 99 19.92E6 +RO1 8 5 50 +RO2 7 99 50 +RP 3 4 30.31E3 +VB 9 0 DC 0 +VC 3 53 DC 2.100 +VE 54 4 DC .6 +VLIM 7 8 DC 0 +VLP 91 0 DC 40 +VLN 0 92 DC 40 +.MODEL DX D(IS=800.0E-18) +.MODEL QX PNP(IS=800.0E-18 BF=250) +.ENDS + +.SUBCKT Power_amp_arduino_subscheme Vout gnd Vin VCC +QX2N2222A_1 VCC Vopamp Vout QMOD_X2N2222A_1 AREA=1 +.MODEL QMOD_X2N2222A_1 npn (Is=14.34F Nf=1 Nr=1 Ikf=0.2847 Ikr=0 Vaf=74.03 Var=0 Ise=14.34F Ne=1.307 Isc=0 Nc=2 Bf=255.9 Br=6.092 Rbm=0 Irb=0 Rc=1 Re=0 Rb=10 Cje=22.01P Vje=0.75 Mje=0.377 Cjc=7.306P Vjc=0.75 Mjc=0.3416 Xcjc=1 Cjs=0 Vjs=0.75 Mjs=0 Fc=0.5 Tf=411.1P Xtf=3 Vtf=0 Itf=0.6 Tr=46.91N Kf=0 Af=1 Ptf=0 Xtb=1.5 Xti=3 Eg=1.11 Tnom=26.85 ) +RG 0 _net2 100K +XOP1 0 Vin _net2 VCC 0 Vopamp SpiceOpamp_LM358 +Rf _net2 Vout 1K +RG1 Vin 0 100K +.ENDS +Rsh Vin _net0 1K +V2 VCC 0 DC 8 +V4 Vin 0 DC 2 +XSUB1 _net4 0 _net0 VCC Power_amp_arduino_subscheme +QX2N2222A_1 _net5 Vbase 0 QMOD_X2N2222A_1 AREA=1 +.MODEL QMOD_X2N2222A_1 npn (Is=14.34F Nf=1 Nr=1 Ikf=0.2847 Ikr=0 Vaf=74.03 Var=0 Ise=14.34F Ne=1.307 Isc=0 Nc=2 Bf=255.9 Br=6.092 Rbm=0 Irb=0 Rc=1 Re=0 Rb=10 Cje=22.01P Vje=0.75 Mje=0.377 Cjc=7.306P Vjc=0.75 Mjc=0.3416 Xcjc=1 Cjs=0 Vjs=0.75 Mjs=0 Fc=0.5 Tf=411.1P Xtf=3 Vtf=0 Itf=0.6 Tr=46.91N Kf=0 Af=1 Ptf=0 Xtb=1.5 Xti=3 Eg=1.11 Tnom=26.85 ) +VIb _net4 OutV DC 0 +Rsh2 Vbase OutV 1K + +.endc +.END +''' + +raw_spice = 'EGND 99 0 POLY(2) (3,0) (4,0) 0 .5 .5' +source = raw_spice + +spice_source = SpiceSource(source=source, title_line=False) +for obj in spice_source.obj_lines: + print(obj) +# bootstrap_circuit = Translator.Builder().translate(spice_source) +# bootstrap_source = str(bootstrap_circuit) + +# print(bootstrap_source) + +# assert bootstrap_source == source diff --git a/examples/switched-power-supplies/buck-converter.py b/examples/switched-power-supplies/buck-converter.py index 5cba817af..472bf202e 100755 --- a/examples/switched-power-supplies/buck-converter.py +++ b/examples/switched-power-supplies/buck-converter.py @@ -23,7 +23,7 @@ #?# circuit_macros('buck-converter.m4') circuit = Circuit('Buck Converter') - +circuit.include(spice_library['genopa1']) circuit.include(spice_library['1N5822']) # Schottky diode circuit.include(spice_library['irf150']) @@ -90,27 +90,41 @@ circuit.PulseVoltageSource('pulse', 'clock', circuit.gnd, 0@u_V, 2.*Vin, duty_cycle, period) circuit.X('D', '1N5822', circuit.gnd, 'source') -circuit.L(1, 'source', 1, L) +inductor = circuit.L(1, 'source', 1, L) +# add a series resistor to model the ESR of the inductor. It helps convergence +inductor.pins[0].add_esr(circuit, value = 10@u_mOhm) +inductor.pins[0].add_current_probe(circuit, name = 'inductor_current') + circuit.R('L', 1, 'out', RL) circuit.C(1, 'out', circuit.gnd, Cout) # , initial_condition=0@u_V circuit.R('load', 'out', circuit.gnd, Rload) simulator = Simulator.factory() simulation = simulator.simulation(circuit, temperature=25, nominal_temperature=25) -analysis = simulation.transient(step_time=period/300, end_time=period*150) -figure, ax = plt.subplots(figsize=(20, 10)) +# I noticed that sometimes tmax is not calculated correctly. it's better to specify it manually using max_time +# for convergence issues, it's better to use a smaller timestep +# Also, sometimes you may use UIC to assist covnergence. +analysis = simulation.transient(step_time=period/300, end_time=period*150, start_time=0@u_ms, max_time=1@u_ns, use_initial_condition=True) -ax.plot(analysis.out) -ax.plot(analysis['source']) +figure, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), sharex=True) + +ax1.plot(analysis.out) +ax1.plot(analysis['source']) # ax.plot(analysis['source'] - analysis['out']) # ax.plot(analysis['gate']) -ax.axhline(y=float(Vout), color='red') -ax.legend(('Vout [V]', 'Vsource [V]'), loc=(.8,.8)) -ax.grid() -ax.set_xlabel('t [s]') -ax.set_ylabel('[V]') - +ax1.axhline(y=float(Vout), color='red') +ax1.legend(('Vout [V]', 'Vsource [V]'), loc=(.8,.8)) +ax1.grid() +ax1.set_xlabel('t [s]') +ax1.set_ylabel('[V]') + + +ax2.plot(analysis.branches['vinductor_current'], label='L2 [A]') +ax2.legend(('Inductor current [A]',), loc=(.8,.8)) +ax2.grid() +ax2.set_xlabel('t [s]') +ax2.set_ylabel('[A]') plt.tight_layout() plt.show() diff --git a/examples/switched-power-supplies/buck-converter_ngshared.py b/examples/switched-power-supplies/buck-converter_ngshared.py new file mode 100644 index 000000000..8db90283c --- /dev/null +++ b/examples/switched-power-supplies/buck-converter_ngshared.py @@ -0,0 +1,170 @@ +#################################################################################################### + +import matplotlib.pyplot as plt +import time +#################################################################################################### + +import PySpice.Logging.Logging as Logging +logger = Logging.setup_logging() + +#################################################################################################### + +from PySpice.Doc.ExampleTools import find_libraries +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +from PySpice.Spice.NgSpice.Shared import NgSpiceShared +ngspice = NgSpiceShared.new_instance() + +#################################################################################################### + +libraries_path = find_libraries() +spice_library = SpiceLibrary(libraries_path) + +#################################################################################################### + +#?# circuit_macros('buck-converter.m4') + +circuit = Circuit('Buck Converter') +circuit.include(spice_library['genopa1']) +circuit.include(spice_library['1N5822']) # Schottky diode +circuit.include(spice_library['irf150']) + +# From Microchip WebSeminars - Buck Converter Design Example + +Vin = 12@u_V +Vout = 5@u_V +ratio = Vout / Vin + +Iload = 2@u_A +Rload = Vout / (.8 * Iload) + +frequency = 400@u_kHz +period = frequency.period +duty_cycle = ratio * period + +ripple_current = .3 * Iload # typically 30 % +ripple_voltage = 50@u_mV + +print('ratio =', ratio) +print('RLoad =', Rload) +print('period =', period.canonise()) +print('duty_cycle =', duty_cycle.canonise()) +print('ripple_current =', ripple_current) + +#r# .. math: +#r# U = L \frac{dI}{dt} + +L = (Vin - Vout) * duty_cycle / ripple_current +RL = 37@u_mΩ + +#r# .. math: +#r# dV = dI (ESR + \frac{dt}{C} + \frac{ESL}{dt}) + +ESR = 30@u_mΩ +ESL = 0 +Cout = (ripple_current * duty_cycle) / (ripple_voltage - ripple_current * ESR) + +ripple_current_in = Iload / 2 +ripple_voltage_in = 200@u_mV +ESR_in = 120@u_mΩ +Cin = duty_cycle / (ripple_voltage_in / ripple_current_in - ESR_in) + +L = L.canonise() +Cout = Cout.canonise() +Cin = Cin.canonise() + +print('L =', L) +print('Cout =', Cout) +print('Cint =', Cin) + +circuit.V('in', 'in', circuit.gnd, Vin) +circuit.C('in', 'in', circuit.gnd, Cin) + +# Fixme: out drop from 12V to 4V +# circuit.VCS('switch', 'gate', circuit.gnd, 'in', 'source', model='Switch', initial_state='off') +# circuit.PulseVoltageSource('pulse', 'gate', circuit.gnd, 0@u_V, Vin, duty_cycle, period) +# circuit.model('Switch', 'SW', ron=1@u_mΩ, roff=10@u_MΩ) + +# Fixme: Vgate => Vout ??? +circuit.X('Q', 'irf150', 'in', 'gate', 'source') +# circuit.PulseVoltageSource('pulse', 'gate', 'source', 0@u_V, Vin, duty_cycle, period) +circuit.R('gate', 'gate', 'clock', 1@u_Ω) +circuit.PulseVoltageSource('pulse', 'clock', circuit.gnd, 0@u_V, 2.*Vin, duty_cycle, period) + +circuit.X('D', '1N5822', circuit.gnd, 'source') +inductor = circuit.L(1, 'source', 1, L) +# add a series resistor to model the ESR of the inductor. It helps convergence +inductor.pins[0].add_esr(circuit, value = 10@u_mOhm) +inductor.pins[0].add_current_probe(circuit, name = 'inductor_current') + +circuit.R('L', 1, 'out', RL) +circuit.C(1, 'out', circuit.gnd, Cout) # , initial_condition=0@u_V +circuit.R('load', 'out', circuit.gnd, Rload) + +#################################################################################################### + +end_time = 500e-6 +circ_str = str(circuit) +options = f'.options TEMP = 25C \n' +options += '.options TNOM = 25C \n' +options += '.options NOINIT \n' +options += '.options RSHUNT = 1e12 \n' +# options += '.ic v(opamp_out) = 0\n' +options += f'.tran 1us {end_time} 0 1ns uic\n' +options += '.end' + +circ_str += options + +ngspice.load_circuit(circ_str) +print('Loaded circuit:') +# print(ngspice.listing()) + +live = True +ngspice.run(background=live) +print('Plots:', ngspice.plot_names) +figure, (ax1, ax2) = plt.subplots(2, 1, figsize=(20, 10), sharex=True) +def update_plots(ax1, ax2, sim_time, analysis): + ax1.clear() + ax1.plot(sim_time * 1e6, analysis.out, label='Vout [V]') + ax1.plot(sim_time * 1e6, analysis['source'], label='Vsource [V]') + ax1.legend(loc='upper right') + ax1.grid() + ax1.set_ylabel('[V]') + ax1.set_xlabel('t [us]') + + ax2.clear() + ax2.plot(sim_time * 1e6, analysis.branches['vinductor_current'], label='L2 [A]') + ax2.legend(loc='upper right') + ax2.set_ylabel('[A]') + ax2.set_xlabel('t [us]') + ax2.grid() + plt.tight_layout() + plt.draw() + plt.pause(0.01) + +if not live: + print(ngspice.status()) + plot_data = ngspice.plot(simulation=None, plot_name=ngspice.last_plot) + analysis = plot_data.to_analysis() + update_plots(ax1, ax2, analysis.time, analysis) + plt.show(block=True) +else: + simulation_done = False + while not simulation_done: + time.sleep(0.1) + ngspice.halt() + plot_data = ngspice.plot(simulation=None, plot_name=ngspice.last_plot) + print(ngspice.status()) + + analysis = plot_data.to_analysis() + sim_time = analysis.time + if sim_time[-1]._value < end_time - 1e-6: + ngspice.resume() + else: + simulation_done = True + + update_plots(ax1, ax2, sim_time, analysis) + if simulation_done: + plt.show(block=True) + break diff --git a/examples/xspice/input_clipper.py b/examples/xspice/input_clipper.py new file mode 100644 index 000000000..ec448feb7 --- /dev/null +++ b/examples/xspice/input_clipper.py @@ -0,0 +1,57 @@ +#################################################################################################### + +import matplotlib.pyplot as plt + +#################################################################################################### + +import PySpice.Logging.Logging as Logging +logger = Logging.setup_logging() + +#################################################################################################### + +from PySpice.Doc.ExampleTools import find_libraries +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +#################################################################################################### + +libraries_path = find_libraries() +spice_library = SpiceLibrary(libraries_path) + +#################################################################################################### + +#?# circuit_macros('buck-converter.m4') + +circuit = Circuit('Xspice Control Limit Test') + +#add a sinwave source +circuit.SinusoidalVoltageSource('input', 'in', circuit.gnd, amplitude=10@u_V, frequency=100@u_kHz) +circuit.V('dd', 'vdd', circuit.gnd, 5@u_V) +circuit.V('ss', 'vss', circuit.gnd, -5@u_V) +# a6 in vdd vss out varlimit +circuit.A('cl', 'in', 'vdd', 'vss', 'out', model='varlimit') +# or you can use the following for diffrential inputs. +# circuit.A('cl', '%vd(in, gnd)', '%vd(vdd, gnd)', '%vd(vss, gnd)', '%vd(out, gnd)', model='varlimit') +# look at the xspice manual for more information about controlling the limit +circuit.model('varlimit', 'climit', in_offset=0.0, gain=1.0, upper_delta=0.0, lower_delta=0.0, limit_range=2, fraction=False) +# print(circuit) + +simulator = Simulator.factory() +simulation = simulator.simulation(circuit, temperature=25, nominal_temperature=25) +# add rshunt option to avoid the matrix singularity when using xspice models. a good value is 1e12 which is 1/gmin +simulation.options(rshunt=1e12) +print(simulation) +analysis = simulation.transient(step_time=1@u_us, end_time=20@u_us, start_time=0@u_ms, max_time = 1@u_ns) +figure, ax = plt.subplots(figsize=(20, 10)) +time = analysis.time +ax.plot(time * 1e6, analysis['in'], label='in') +ax.plot(time * 1e6, analysis['out'], label='out') +ax.grid() + +ax.set_xlabel('t [us]') +ax.set_ylabel('[V]') + +plt.tight_layout() +plt.show() + +#f# save_figure('figure', 'buck-converter.png') diff --git a/unit-test/Spice/test_Expression.py b/unit-test/Spice/test_Expression.py index bd76a03fc..659f039fa 100644 --- a/unit-test/Spice/test_Expression.py +++ b/unit-test/Spice/test_Expression.py @@ -24,7 +24,7 @@ #################################################################################################### -from PySpice.Spice.Expression.Parser import Parser +from PySpice.Spice.Parser.Parser import SpiceParser #################################################################################################### @@ -34,50 +34,46 @@ class TestParser(unittest.TestCase): def test_parser(self): - parser = Parser() - - parser.parse('1') - - parser.parse('.1') - parser.parse('.123') - parser.parse('1.') - parser.parse('1.1') - parser.parse('1.123') - parser.parse('1.e2') - parser.parse('1.e-2') - parser.parse('1.123e2') - parser.parse('1.123e-2') - parser.parse('1.123e23') - parser.parse('1.123e-23') - - parser.parse('-1') - parser.parse('-1.1') - - parser.parse('! rised') - - parser.parse('1 ** 2') - - parser.parse('1 * 2') - parser.parse('1 / 2') - parser.parse('1 % 2') - # parser.parse('1 \\ 2') - parser.parse('1 + 2') - - parser.parse('1 == 2') - parser.parse('1 != 2') - parser.parse('1 >= 2') - parser.parse('1 >= 2') - parser.parse('1 < 2') - parser.parse('1 > 2') - - parser.parse('x && y') - parser.parse('x || y') - - parser.parse('c ? x : y') - - parser.parse('1 * -2') - - parser.parse('x * -y + z') + parser = SpiceParser() + + # Test commands with numeric expressions + parser.parse('R1 1 0 1') + parser.parse('R2 1 0 .1') + parser.parse('R3 1 0 .123') + parser.parse('R4 1 0 1.') + parser.parse('R5 1 0 1.1') + parser.parse('R6 1 0 1.123') + parser.parse('R7 1 0 1.e2') + parser.parse('R8 1 0 1.e-2') + parser.parse('R9 1 0 1.123e2') + parser.parse('R10 1 0 1.123e-2') + parser.parse('R11 1 0 1.123e23') + parser.parse('R12 1 0 1.123e-23') + + parser.parse('R13 1 0 -1') + parser.parse('R14 1 0 -1.1') + + # Test behavioural sources with expressions + parser.parse('B1 1 0 V=1 ** 2') + parser.parse('B2 1 0 V=1 * 2') + parser.parse('B3 1 0 V=1 / 2') + parser.parse('B4 1 0 V=1 % 2') + parser.parse('B5 1 0 V=1 + 2') + + # Test if statements and comparisons in expressions + parser.parse('B6 1 0 V=1 == 2 ? 3 : 4') + parser.parse('B7 1 0 V=1 != 2 ? 3 : 4') + parser.parse('B8 1 0 V=1 >= 2 ? 3 : 4') + parser.parse('B9 1 0 V=1 < 2 ? 3 : 4') + parser.parse('B10 1 0 V=1 > 2 ? 3 : 4') + + # Test boolean operations + parser.parse('B11 1 0 V=x && y ? 1 : 0') + parser.parse('B12 1 0 V=x || y ? 1 : 0') + + # Test combination of operations + parser.parse('B13 1 0 V=1 * -2') + parser.parse('B14 1 0 V=x * -y + z') #################################################################################################### diff --git a/unit-test/Spice/test_Netlist.py b/unit-test/Spice/test_Netlist.py index 132fc2e95..8dc32b9bc 100644 --- a/unit-test/Spice/test_Netlist.py +++ b/unit-test/Spice/test_Netlist.py @@ -124,7 +124,7 @@ def test_basic(self): # for pin in circuit.out: # print(pin) - self.assertEqual(circuit.out.pins, set((circuit.R1.minus, circuit.R2.plus))) + self.assertEqual(set(circuit.out.pins), set((circuit.R1.minus, circuit.R2.plus))) self.assertEqual(circuit.R1.resistance, 9@u_kΩ) self.assertEqual(circuit['R2'].resistance, 1@u_kΩ) diff --git a/unit-test/SpiceParser/test_SpiceParser.py b/unit-test/SpiceParser/test_SpiceParser.py index 295a6acd0..db94ad09e 100644 --- a/unit-test/SpiceParser/test_SpiceParser.py +++ b/unit-test/SpiceParser/test_SpiceParser.py @@ -27,7 +27,7 @@ #################################################################################################### from PySpice.Spice.Netlist import Circuit -from PySpice.Spice.Parser import SpiceParser +from PySpice.Spice.Parser.Parser import SpiceParser ####################################################################################################