From 7ae64a111688bee030e954d6733a9609c3ece0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 28 Feb 2025 13:14:13 -0700 Subject: [PATCH 01/87] temporary fix for the spice module import --- .gitignore | 2 ++ PySpice/Spice/Parser/HighLevelParser.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d75b2e63..efbe3e55 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,5 @@ examples/p.diff examples/spice-library-backup/ examples/spice-library/db.pickle test.cir + +.venv/* diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 9b34e323..ae9149e9 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -313,7 +313,15 @@ 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 + from PySpice.Spice.Parser.ElementData import ElementData + 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 From dbe476be36de507914161f20d22714b92ed3b970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 28 Feb 2025 13:14:30 -0700 Subject: [PATCH 02/87] temp fix --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index efbe3e55..e42da4a4 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,4 @@ examples/spice-library/db.pickle test.cir .venv/* +*.vscode/* From c17b8bcabc96e29cad48e39dcb763160282baac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 28 Feb 2025 16:47:45 -0700 Subject: [PATCH 03/87] refactor: only impot ElemetData class at the top --- PySpice/Spice/Parser/HighLevelParser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index ae9149e9..3777e9e5 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 @@ -314,7 +314,6 @@ def __init__(self, line: SpiceLine, ast: AstNode) -> None: self._nodes = [] number_of_pins = 0 from PySpice.Spice.Element import ElementParameterMetaClass - from PySpice.Spice.Parser.ElementData import ElementData elements = {} for letter, classes in ElementParameterMetaClass._classes.items(): element_data = ElementData(letter, classes) From edd0d22b8ffd3f9b764bae22f09c8d029991302d Mon Sep 17 00:00:00 2001 From: Fabrice SALVAIRE Date: Sat, 15 May 2021 23:18:29 +0200 Subject: [PATCH 04/87] example: skip astable From 9f6ca3d1a0f9e9351ec6ad2b2a57adbf058d2ac0 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:31:04 +0100 Subject: [PATCH 05/87] readme --- README.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.html b/README.html index 8d69b6c5..8e98fdde 100644 --- a/README.html +++ b/README.html @@ -746,9 +746,9 @@

V1.6.0 (development release)

  • The most common PySpice parts can be imported from from PySpice import ...

  • Logging setup code clean-up

  • - -
    -

    V1.5.0 (production release) 2021-05-15

    + +
    +

    V1.5.0 (production release) 2021-05-15

    • Support Ngspice up to version 34

    • Renamed custom dunders "__dunder__" to "CONSTANT" or "_private" class attributes

    • From f19faad02d3ee3b4c00d6bf7f3279b4d6eabad30 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:32:15 +0100 Subject: [PATCH 06/87] readme # Conflicts: # README.txt --- README.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.html b/README.html index 8e98fdde..8d69b6c5 100644 --- a/README.html +++ b/README.html @@ -746,9 +746,9 @@

      V1.6.0 (development release)

    • The most common PySpice parts can be imported from from PySpice import ...

    • Logging setup code clean-up

    -
    -
    -

    V1.5.0 (production release) 2021-05-15

    +
    +
    +

    V1.5.0 (production release) 2021-05-15

    • Support Ngspice up to version 34

    • Renamed custom dunders "__dunder__" to "CONSTANT" or "_private" class attributes

    • From c854d6fdb5f0179855c94448f86029dfee870f82 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:33:38 +0100 Subject: [PATCH 07/87] readme # Conflicts: # README.txt --- README.html | 91 ++++++++++++++++------------------------------------- README.rst | 90 +++++++++++++--------------------------------------- 2 files changed, 48 insertions(+), 133 deletions(-) diff --git a/README.html b/README.html index 8d69b6c5..4f53b4c8 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

        -
      • Devel Branch

      • Production Branch

      • -
      • Travis CI but need free credits...

      • +
      • Devel Branch

      • +
      • Travis CI

      • pyspice@conda-forge

      • conda-forge/pyspice

      • ngspice@conda-forge

      • @@ -634,13 +633,15 @@

        2024 Update

        Disclaimer: PySpice is developed on my free time actually, so I could be busy with other tasks and less reactive.

        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 -* fixed the ngspice library loading for recent cffi -* fixed simulation aborting due to a message from newer ngspice -* fixes for Spice parser -* added support for Pint unit library -* implemented SpiceLibrary -* code cleanup but must check for typo...

        +

        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

        • +
        • added support for Pint unit library

        • +
        • implemented SpiceLibrary

        • +
        • code cleanup but must check for typo...

        • +

        An issue was found with NgSpice Shared, we must setlocale(LC_NUMERIC, "C"); see https://sourceforge.net/p/ngspice/bugs/490/

        @@ -690,61 +691,23 @@

        Pull Request Recommendation

    Credits

    -

    Authors: Fabrice SALVAIRE and contributors

    +

    Authors: Fabrice Salvaire and contributors

    News

    -
    -

    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

      -
      # 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 [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 from PySpice import ...

      • -
      • Logging setup code clean-up

      • +writing of fastidious cicruit.

    @@ -766,14 +729,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 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

    • @@ -781,22 +744,22 @@

      V1.4.3 2020-07-04

      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 @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

    • @@ -816,7 +779,7 @@

      V1.4.0 2020-05-05

    • 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

    • @@ -844,7 +807,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. circuit.Q(1, base=1, collector=2, emitter=3, model='npn')

    • +
    • Implemented node kwarg e.g. 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. save @device[parameter])

    • Implemented check for missing ground node

    • @@ -858,9 +821,9 @@

      V1.2.0 2018-06-07

    • 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

  • @@ -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 5151a729..d3a3102f 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`. From 8b24d4b4085343ff1320ecd7eafea26488884d18 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:35:12 +0100 Subject: [PATCH 08/87] Smaller diff with original # Conflicts: # PySpice/Spice/NgSpice/Shared.py --- PySpice/Spice/NgSpice/Shared.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 8d805f0a..17271b71 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: @@ -428,6 +440,8 @@ def setup_platform(cls): raise NotImplementedError cls.LIBRARY_PATH = str(path) + + ############################################## @classmethod @@ -509,6 +523,7 @@ def _load_library(self, verbose): # https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027 # When environment variable SPICE_LIB_DIR is empty, ngspice looks in C:\Spice64\share\ngspice\scripts # Else it tries %SPICE_LIB_DIR%\scripts\spinit + if 'SPICE_LIB_DIR' not in os.environ: _ = str(Path(self.NGSPICE_PATH).joinpath('share', 'ngspice')) os.environ['SPICE_LIB_DIR'] = _ @@ -619,7 +634,8 @@ 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 else: self._error_in_stderr = True func = self._logger.error From c16688b78c851ccc9c06ba146cce9816f28ffa16 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 23 Nov 2024 18:38:20 +0100 Subject: [PATCH 09/87] Making it work for Mingw for the msys2 project --- PySpice/Spice/NgSpice/Shared.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 17271b71..7ac78f71 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -440,8 +440,6 @@ def setup_platform(cls): raise NotImplementedError cls.LIBRARY_PATH = str(path) - - ############################################## @classmethod @@ -523,7 +521,6 @@ def _load_library(self, verbose): # https://sourceforge.net/p/ngspice/discussion/133842/thread/1cece652/#4e32/5ab8/9027 # When environment variable SPICE_LIB_DIR is empty, ngspice looks in C:\Spice64\share\ngspice\scripts # Else it tries %SPICE_LIB_DIR%\scripts\spinit - if 'SPICE_LIB_DIR' not in os.environ: _ = str(Path(self.NGSPICE_PATH).joinpath('share', 'ngspice')) os.environ['SPICE_LIB_DIR'] = _ @@ -635,7 +632,7 @@ def _send_char(message_c, ngspice_id, user_data): elif content.startswith('Warning:'): func = self._logger.warning elif content.startswith('Using'): # Ignore "Using ... as Direct Linear Solver" messages - func = self._logger.debug + func = self._logger.debug else: self._error_in_stderr = True func = self._logger.error From 0185f53cf1b55637eb4b74729d8cb399433bdc42 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:36:52 +0100 Subject: [PATCH 10/87] Sanity test # Conflicts: # .github/workflows/pyspice-test.yml --- .github/workflows/pyspice-test.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index b4aaa7d3..6ea3fc93 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 From e513c8ed5bbff330f7d89773d9c76a78d467f6b1 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:37:26 +0100 Subject: [PATCH 11/87] Update pyspice-test.yml # Conflicts: # .github/workflows/pyspice-test.yml --- .github/workflows/pyspice-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index 6ea3fc93..98489e8f 100644 --- a/.github/workflows/pyspice-test.yml +++ b/.github/workflows/pyspice-test.yml @@ -6,7 +6,7 @@ name: Pyspice Test # Trigger the workflow on on: push: - branches: [ * ] + branches: "*" pull_request: branches: [ * ] # page_build: From 862731b71e25d8fc3cb07df8a0d89466ed402450 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:43:36 +0100 Subject: [PATCH 12/87] Add Background simulations # Conflicts: # PySpice/Spice/NgSpice/Simulator.py --- PySpice/Spice/NgSpice/Simulator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 6787dcda..e74d1b7d 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) ############################################## @@ -115,7 +117,7 @@ def run(self, simulation): # load circuit and simulation # Fixme: Error: circuit not parsed. self._ngspice_shared.load_circuit(str(simulation)) - self._ngspice_shared.run() + self._ngspice_shared.run(background=background) self._logger.debug(str(self._ngspice_shared.plot_names)) plot_name = self._ngspice_shared.last_plot From f261e1bef3d7e7850a8ce8c7b4ca1cb74a1ab885 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sat, 1 Mar 2025 22:44:15 +0100 Subject: [PATCH 13/87] Bumping NgSpice version # Conflicts: # PySpice/Spice/NgSpice/SimulationType.py # PySpice/Spice/NgSpice/Simulator.py --- PySpice/Spice/NgSpice/SimulationType.py | 2 +- PySpice/Spice/NgSpice/Simulator.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/NgSpice/SimulationType.py b/PySpice/Spice/NgSpice/SimulationType.py index 882e9808..2f9d068d 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 e74d1b7d..433da5f8 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -119,9 +119,16 @@ def run(self, simulation): self._ngspice_shared.load_circuit(str(simulation)) self._ngspice_shared.run(background=background) self._logger.debug(str(self._ngspice_shared.plot_names)) - - plot_name = self._ngspice_shared.last_plot - if plot_name == 'const': - raise NameError('Simulation failed') +<<<<<<< HEAD:PySpice/Spice/NgSpice/Simulator.py +======= + if not background: + self.reset_analysis() +>>>>>>> 7f9b8a6 (Bumping NgSpice version):PySpice/Spice/NgSpice/Simulation.py + + plot_name = self._ngspice_shared.last_plot + if plot_name == 'const': + raise NameError('Simulation failed') + else: + return True #Nothing to show yet! return self._ngspice_shared.plot(simulation, plot_name).to_analysis() From 6155e97986304636027a553d5a04d026cfbe8ff9 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sun, 24 Nov 2024 21:28:28 +0100 Subject: [PATCH 14/87] Exception ignored from cffi callback , trying to convert the result back to C: --- PySpice/Spice/NgSpice/Shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 7ac78f71..06269ff5 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -708,6 +708,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 ############################################## From 5da5b1c77b92711eb13309015a62799e3e8340e6 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Mon, 25 Nov 2024 08:47:39 +0100 Subject: [PATCH 15/87] Don't error on halt --- PySpice/Spice/NgSpice/Shared.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 06269ff5..c8c8bd4c 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -633,6 +633,10 @@ def _send_char(message_c, ngspice_id, user_data): func = self._logger.warning elif content.startswith('Using'): # Ignore "Using ... as Direct Linear Solver" messages func = self._logger.debug + elif content.startswith('doAnalyses:'): + func = self._logger.debug + elif content.startswith('run simulation interrupted'): + func = self._logger.debug else: self._error_in_stderr = True func = self._logger.error From 62bb90cc670bd61827f0ddf93ee6ac08cf144ced Mon Sep 17 00:00:00 2001 From: FlyingStitchman Date: Sun, 24 Nov 2024 23:38:49 +0000 Subject: [PATCH 16/87] Docs: Spelling error therms -> terms --- doc/sphinx/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst index 02fdf589..45335fbd 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. From d41085827a9a265f2af3396caf5a7b11b6bff664 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Wed, 26 Feb 2025 15:17:41 +0100 Subject: [PATCH 17/87] Do handle ngspice notes I saw this issue but I didn't thought it would affect me... well IT DID! See https://github.com/PySpice-org/PySpice/pull/313 or https://github.com/PySpice-org/PySpice/pull/328 truly a problem --- PySpice/Spice/NgSpice/Shared.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index c8c8bd4c..f0820f03 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -637,6 +637,12 @@ def _send_char(message_c, ngspice_id, user_data): func = self._logger.debug elif content.startswith('run 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 else: self._error_in_stderr = True func = self._logger.error From 3ea40784ef0d47e054ba9a1352c4a89b6e7ce909 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Sun, 2 Mar 2025 00:17:57 +0100 Subject: [PATCH 18/87] oops --- PySpice/Spice/NgSpice/Simulator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 433da5f8..1793e205 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -119,11 +119,8 @@ def run(self, simulation): self._ngspice_shared.load_circuit(str(simulation)) self._ngspice_shared.run(background=background) self._logger.debug(str(self._ngspice_shared.plot_names)) -<<<<<<< HEAD:PySpice/Spice/NgSpice/Simulator.py -======= if not background: self.reset_analysis() ->>>>>>> 7f9b8a6 (Bumping NgSpice version):PySpice/Spice/NgSpice/Simulation.py plot_name = self._ngspice_shared.last_plot if plot_name == 'const': From 725efd9d79eac83f910501c1db5034695189f4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 2 Mar 2025 22:15:34 -0700 Subject: [PATCH 19/87] Fix background simulation handling in NgSpiceSharedSimulator --- PySpice/Spice/NgSpice/Simulator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 1793e205..4fc4e1ef 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -117,10 +117,11 @@ def run(self, simulation): # load circuit and simulation # Fixme: Error: circuit not parsed. self._ngspice_shared.load_circuit(str(simulation)) + background = False self._ngspice_shared.run(background=background) self._logger.debug(str(self._ngspice_shared.plot_names)) if not background: - self.reset_analysis() + simulation.reset_analysis() plot_name = self._ngspice_shared.last_plot if plot_name == 'const': From c3bbde493a2e917bcd586cc4b8852110af16efb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 2 Mar 2025 22:16:29 -0700 Subject: [PATCH 20/87] Add SPICE model files for 1N5822 diode and IRF150 MOSFET --- .../discrete-semiconductors/diodes/schottky/1N5822.yaml | 8 ++++++++ .../transistors/MOSFET/irf150.yaml | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml create mode 100644 examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml diff --git a/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml b/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml new file mode 100644 index 00000000..4ba3e16a --- /dev/null +++ b/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml @@ -0,0 +1,8 @@ +path: 1N5822.lib +date: '2025-03-02T21:57:43.952971' +digest: 1e33a914cf68f96c4d3c065b7f5954fcb4c21d84 +description: '' +subcircuits: +- name: 1N5822 + description: '' + nodes: [] diff --git a/examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml b/examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml new file mode 100644 index 00000000..49909dfb --- /dev/null +++ b/examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml @@ -0,0 +1,8 @@ +path: irf150.lib +date: '2025-03-02T21:57:43.952971' +digest: fba6d4de74dfd6e8c1f6abfd9dc5022e6d99b110 +description: '' +subcircuits: +- name: irf150 + description: '' + nodes: [] From 7801240f156e72bb4d15e5afcf4ce6b7510dc92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 07:33:53 -0700 Subject: [PATCH 21/87] Enhance Pin class: add optional name parameter to add_current_probe and added a new add_esr methods to enhance convergence --- PySpice/Spice/Element.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/PySpice/Spice/Element.py b/PySpice/Spice/Element.py index 1007a9aa..968e27cd 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") From c3c2c027926b8cb2894c56dca84772c3d3e2dfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 10:39:07 -0700 Subject: [PATCH 22/87] Add background simulation handling and include additional SPICE model in buck converter example --- PySpice/Spice/NgSpice/Simulator.py | 2 +- PySpice/Spice/Simulation.py | 4 + examples/switched-power-supplies/bg_test.py | 374 ++++++++++++++++++ .../switched-power-supplies/buck-converter.py | 1 + 4 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 examples/switched-power-supplies/bg_test.py diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 4fc4e1ef..569f218b 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -117,7 +117,7 @@ def run(self, simulation): # load circuit and simulation # Fixme: Error: circuit not parsed. self._ngspice_shared.load_circuit(str(simulation)) - background = False + background = simulation.background if hasattr(simulation, 'background') else False self._ngspice_shared.run(background=background) self._logger.debug(str(self._ngspice_shared.plot_names)) if not background: diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index d4020afb..999457f3 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -738,6 +738,10 @@ def _run(self, analysis_method, *args, **kwargs): if 'probes' in kwargs: self.save(* kwargs.pop('probes')) + + if 'background' in kwargs: + background = kwargs.pop('background') + self.background = background # Execute analysis implementation analysis_method(self, *args, **kwargs) diff --git a/examples/switched-power-supplies/bg_test.py b/examples/switched-power-supplies/bg_test.py new file mode 100644 index 00000000..96edbc89 --- /dev/null +++ b/examples/switched-power-supplies/bg_test.py @@ -0,0 +1,374 @@ +import anywidget +import traitlets +import json +import time +from collections import defaultdict + +class AnimatedFnWidget(anywidget.AnyWidget): + # Trait for sending new data points to JavaScript + new_data_points = traitlets.Unicode('{}').tag(sync=True) + + # Trait for initial configuration + config = traitlets.Unicode('{"xMin": 0, "xMax": 10, "yMin": -1.5, "yMax": 1.5, "series": {}}').tag(sync=True) + + _esm = """ + function render({ model, el }) { + const container = document.createElement('div'); + container.style.width = '100%'; + container.style.height = '400px'; + container.style.border = '1px solid #ccc'; + container.style.position = 'relative'; + el.appendChild(container); + + const canvas = document.createElement('canvas'); + canvas.style.width = '100%'; + canvas.style.height = '100%'; + container.appendChild(canvas); + + function resizeCanvas() { + const rect = container.getBoundingClientRect(); + canvas.width = rect.width * window.devicePixelRatio; + canvas.height = rect.height * window.devicePixelRatio; + redrawPlot(); + } + + window.addEventListener('resize', resizeCanvas); + + let allData = {}; + let config = JSON.parse(model.get('config')); + const colors = ['#3498db', '#e74c3c', '#2ecc71', '#9b59b6', '#f1c40f', '#e67e22']; + + model.on('change:config', () => { + config = JSON.parse(model.get('config')); + allData = {}; + Object.keys(config.series).forEach(series => { + allData[series] = { x: [], y: [] }; + }); + redrawPlot(); + }); + + model.on('change:new_data_points', () => { + const newData = JSON.parse(model.get('new_data_points')); + if (Object.keys(newData).length === 0) return; + + Object.entries(newData).forEach(([series, points]) => { + if (!allData[series]) { + allData[series] = { x: [], y: [] }; + } + allData[series].x = allData[series].x.concat(points.x); + allData[series].y = allData[series].y.concat(points.y); + + if (allData[series].x.length > 0) { + const maxX = Math.max(...Object.values(allData).map(d => Math.max(...d.x))); + if (maxX > config.xMax) { + config.xMin = Math.max(0, maxX - 10); + config.xMax = maxX; + } + } + }); + + redrawPlot(); + }); + + function redrawPlot() { + const ctx = canvas.getContext('2d'); + const width = canvas.width; + const height = canvas.height; + + // Compute yMin and yMax dynamically if not specified + let yMin, yMax; + if (typeof config.yMin === 'number' && typeof config.yMax === 'number') { + yMin = config.yMin; + yMax = config.yMax; + } else { + // Auto-scale y-axis + let allYValues = []; + Object.values(allData).forEach(series => { + allYValues = allYValues.concat(series.y); + }); + if (allYValues.length === 0) { + yMin = -1; + yMax = 1; + } else { + let dataMin = Math.min(...allYValues); + let dataMax = Math.max(...allYValues); + if (dataMin === dataMax) { + yMin = dataMin - 1; + yMax = dataMax + 1; + } else { + let padding = (dataMax - dataMin) * 0.1; + yMin = dataMin - padding; + yMax = dataMax + padding; + } + } + } + + ctx.clearRect(0, 0, width, height); + + // Draw grid + ctx.strokeStyle = '#ccc'; + ctx.lineWidth = 1 * window.devicePixelRatio; + ctx.beginPath(); + const gridStep = width / 10; + for (let i = 0; i <= 10; i++) { + const x = i * gridStep; + ctx.moveTo(x, 0); + ctx.lineTo(x, height); + } + const yGridStep = height / 6; + for (let i = 0; i <= 6; i++) { + const y = i * yGridStep; + ctx.moveTo(0, y); + ctx.lineTo(width, y); + } + ctx.stroke(); + + // Draw axes + ctx.strokeStyle = '#000'; + const yZero = height * (1 - (0 - yMin) / (yMax - yMin)); + ctx.beginPath(); + ctx.moveTo(0, yZero); + ctx.lineTo(width, yZero); + ctx.stroke(); + + const xZero = width * (0 - config.xMin) / (config.xMax - config.xMin); + ctx.beginPath(); + ctx.moveTo(xZero, 0); + ctx.lineTo(xZero, height); + ctx.stroke(); + + function dataToCanvas(x, y) { + return { + x: width * (x - config.xMin) / (config.xMax - config.xMin), + y: height * (1 - (y - yMin) / (yMax - yMin)) + }; + } + + // Draw all series (no config.series check) + Object.entries(allData).forEach(([series, data], index) => { + if (data.x.length > 1) { + const visibleData = data.x.map((x, i) => ({x, y: data.y[i]})) + .filter(point => point.x >= config.xMin && point.x <= config.xMax); + + if (visibleData.length > 0) { + ctx.beginPath(); + const startPoint = dataToCanvas(visibleData[0].x, visibleData[0].y); + ctx.moveTo(startPoint.x, startPoint.y); + + for (let i = 1; i < visibleData.length; i++) { + const point = dataToCanvas(visibleData[i].x, visibleData[i].y); + ctx.lineTo(point.x, point.y); + } + + ctx.strokeStyle = colors[index % colors.length]; + ctx.lineWidth = 2 * window.devicePixelRatio; + ctx.stroke(); + + // Draw legend with fallback label + ctx.fillStyle = colors[index % colors.length]; + ctx.fillRect(10, 10 + index * 20, 10, 10); + ctx.fillStyle = '#000'; + ctx.font = `${12 * window.devicePixelRatio}px Arial`; + const label = config.series[series] ? config.series[series].label : series; + ctx.fillText(label, 25, 20 + index * 20); + } + } + }); + + // Draw axes labels + ctx.fillStyle = '#000'; + ctx.font = `${12 * window.devicePixelRatio}px Arial`; + ctx.fillText(`t`, width - 15, yZero - 5); + ctx.fillText(`${config.xMin.toFixed(1)}`, 5, yZero - 5); + ctx.fillText(`${config.xMax.toFixed(1)}`, width - 30, yZero - 5); + ctx.fillText(`${yMax.toFixed(1)}`, xZero + 5, 15); + ctx.fillText(`${yMin.toFixed(1)}`, xZero + 5, height - 5); + } + + setTimeout(resizeCanvas, 0); +} + +export default { render }; + """ + + def __init__(self, update_interval_ms=100, **kwargs): + super().__init__(**kwargs) + self.update_interval = update_interval_ms / 1000 + self.last_update_time = 0 + self.buffer = defaultdict(lambda: {'x': [], 'y': []}) + + def update_config(self, config_dict): + """Update the plot configuration""" + self.config = json.dumps(config_dict) + + def add_points(self, series_name, x_values, y_values): + """Add points for a specific series""" + if len(x_values) != len(y_values): + raise ValueError("x_values and y_values must have the same length") + + self.buffer[series_name]['x'].extend(x_values) + self.buffer[series_name]['y'].extend(y_values) + + current_time = time.time() + if current_time - self.last_update_time >= self.update_interval: + self._flush_buffer() + self.last_update_time = current_time + + def _flush_buffer(self): + """Send buffered data to JavaScript""" + if not self.buffer: + return + + self.new_data_points = json.dumps(dict(self.buffer)) + for series in self.buffer: + self.buffer[series]['x'].clear() + self.buffer[series]['y'].clear() + +# Example usage with sine wave +def test_sine_wave(widget): + import numpy as np + config = { + "xMin": 0, + "xMax": 10, + "yMin": -1.5, + "yMax": 1.5, + "series": { + "sine": {"label": "Sine Wave"} + } + } + widget.update_config(config) + + t = 0 + dt = 0.05 + points_per_update = 5 + + while t < 20: + x_values = [t + i * dt for i in range(points_per_update)] + y_values = [np.sin(2 * np.pi * x) for x in x_values] + widget.add_points("sine", x_values, y_values) + t += dt * points_per_update + time.sleep(widget.update_interval) + + +# - + +anim_widget = AnimatedFnWidget(update_interval_ms=100) +anim_widget + +test_sine_wave(anim_widget) #if this doesn't work then forget it + +# + +from PySpice.Spice.Netlist import Circuit +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +# Create a circuit +circuit = Circuit('My Circuit') + +# Add components to the circuit +circuit.R('res1', 'k', 'y', 1@u_Ω) # Resistor R1 between nodes 'k' and 'y' with 1 Ω +circuit.C('cap1', 0, 'k', 1@u_F, ic=0.0) # Capacitor C1 between nodes 0 and 'k' with 1 F, initial condition 0.0 +circuit.V('vdc', 0, 'y', 1@u_V) # Voltage source V1 between nodes 0 and 'y' with 1 V + +# Print the circuit to verify +print(circuit) + +# - + +def couple_with_ngspice(widget, simulator, start, end, step, y_range=(-2, 2)): + """ + Couple the AnimatedFnWidget with an NGSpice simulation, plotting all available signals. + + Args: + widget: AnimatedFnWidget instance + simulator: PySpice simulator object + start: Simulation start time + end: Simulation end time + step: Simulation time step + y_range: Tuple of (min, max) values for y-axis + """ + # Initial config with empty series; labels will use signal names + config = { + "xMin": start, + "xMax": end, + "yMin": y_range[0], + "yMax": y_range[1], + "series": {} + } + widget.update_config(config) + + def data_listener(actual_vector_values, number_of_vectors, ngspice_id): + time_value = float(actual_vector_values['time'].real) + for signal, value in actual_vector_values.items(): + if signal != 'time': + widget.add_points(signal, [time_value], [float(value.real)]) + + simulator.simulator._ngspice_shared.addListener("send_data", data_listener) + # display(widget) + simulator.transient(step, end, start, use_initial_condition=True, background=True) + + +# + +from PySpice.Spice.Netlist import Circuit +from PySpice.Unit import * +from PySpice.Spice.NgSpice.Shared import NgSpiceShared +class MyNgSpiceShared(NgSpiceShared): + def __init__(self, ngspice_id=0, send_data=False, verbose=False): + super().__init__(ngspice_id=ngspice_id, send_data=send_data, verbose=verbose) + self._listeners = { + "send_data": [], + "send_init_data": [] + } + + def addListener(self, event, listener): + if event in self._listeners: + self._listeners[event].append(listener) + else: + raise ValueError(f"Ev'ent '{event}' is not supported.") + + def removeListener(self, event, listener): + if event in self._listeners: + if listener in self._listeners[event]: + self._listeners[event].remove(listener) + else: + raise ValueError(f"Listener '{listener.__name__}' is not registered for event '{event}'.") + else: + raise ValueError(f"Event '{event}' is not supported.") + + def _notify_listeners(self, event, *args, **kwargs): + if event in self._listeners: + for listener in self._listeners[event]: + listener(*args, **kwargs) + + def send_data(self, actual_vector_values, number_of_vectors, ngspice_id): + # Notify listeners for the 'send_data' event + self._notify_listeners("send_data", actual_vector_values, number_of_vectors, ngspice_id) + return 0 + + def send_init_data(self, data, ngspice_id): + # Notify listeners for the 'send_init_data' event + self._notify_listeners("send_init_data", data, ngspice_id) + return 0 + + + +ngshared = MyNgSpiceShared.new_instance(send_data=True,ngspice_id=0) +simulator =Simulator.factory().simulation(circuit,temperature=25, nominal_temperature=25 + ,ngspice_shared=MyNgSpiceShared.new_instance(send_data=True) + ) +analysis = simulator.dc(Vvdc=slice(-2, 5, .01)) + +# simulator = circuit.simulator( +# temperature=25, +# nominal_temperature=25, +# ngspice_shared=MyNgSpiceShared.new_instance(send_data=True) +# ) +anim_widget = AnimatedFnWidget(update_interval_ms=100) +couple_with_ngspice(anim_widget, simulator, 0, 12, 0.0001)# If your computer is too fast make this smaller.... if your computer is too slow make this bigger. +# - +simulator.simulator._ngspice_shared.halt() ## Execute these instructions when they are on the middle. + +simulator.simulator._ngspice_shared.alter_device(device='Vvdc', dc=-1) + + +simulator.simulator._ngspice_shared.resume() \ No newline at end of file diff --git a/examples/switched-power-supplies/buck-converter.py b/examples/switched-power-supplies/buck-converter.py index 5cba817a..ac6e888e 100755 --- a/examples/switched-power-supplies/buck-converter.py +++ b/examples/switched-power-supplies/buck-converter.py @@ -26,6 +26,7 @@ circuit.include(spice_library['1N5822']) # Schottky diode circuit.include(spice_library['irf150']) +circuit.include(spice_library['genopa1']) # From Microchip WebSeminars - Buck Converter Design Example From 7b2305780ff8de615099518155a6aee9a1cc028b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 13:33:05 -0700 Subject: [PATCH 23/87] fix: don't error on the colon that appears after params when parsing a library. --- PySpice/Spice/Parser/Parser.py | 10 ++++++++++ .../operational-amplifier/generic_opamp.lib | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 9f452eed..a1263c56 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -270,6 +270,16 @@ def p_command(self, p): ''' return self._command(p, Command) + # -------------------------------------------------------------------------------------- + # 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_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] + ':') + # --- + # Fixme: could be merged with command def p_dot_command(self, p): '''command : DOT_COMMAND expression_list_space 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 00000000..f05dfe25 --- /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 inp inn vcc vee out params: POLE=20 GAIN=20k VOFF=10m ROUT=10 +Voff inp inoff dc {VOFF} +G10 0 int inoff inn 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 From 125fcbee545e30c17879653b910cd5c5b309fe39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 15:00:34 -0700 Subject: [PATCH 24/87] fix: update parser to handle ID followed by a + and - in subckt definitions. This is common in spice netlist node names. --- PySpice/Spice/Parser/Parser.py | 29 +++++++++---------- .../operational-amplifier/generic_opamp.lib | 2 +- .../switched-power-supplies/buck-converter.py | 3 +- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index a1263c56..7b4a427e 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -228,11 +228,7 @@ def t_NUMBER(self, t): def t_ID(self, t): # Fixme: - r''' - (?i: - [a-z_0-9]+ - (\.[a-z_0-9.]+) ? - )''' + r'''(?i:[a-z0-9_+\-]+(\.[a-z0-9_+\-]+)*)''' # t.value = Id(t.value) return t @@ -270,16 +266,7 @@ def p_command(self, p): ''' return self._command(p, Command) - # -------------------------------------------------------------------------------------- - # 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_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] + ':') - # --- - + # Fixme: could be merged with command def p_dot_command(self, p): '''command : DOT_COMMAND expression_list_space @@ -313,7 +300,17 @@ 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_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/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib index f05dfe25..f85d6413 100644 --- a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib +++ b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib @@ -1,6 +1,6 @@ * generic OpAmp model * gain, phase, offset, limits to power supply -.subckt genopa1 inp inn vcc vee out params: POLE=20 GAIN=20k VOFF=10m ROUT=10 +.subckt genopa1 in+ in- vcc vee out params: POLE=20 GAIN=20k VOFF=10m ROUT=10 Voff inp inoff dc {VOFF} G10 0 int inoff inn 100u R1 int 0 {GAIN/100u} diff --git a/examples/switched-power-supplies/buck-converter.py b/examples/switched-power-supplies/buck-converter.py index ac6e888e..e711b94f 100755 --- a/examples/switched-power-supplies/buck-converter.py +++ b/examples/switched-power-supplies/buck-converter.py @@ -23,10 +23,9 @@ #?# 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']) -circuit.include(spice_library['genopa1']) # From Microchip WebSeminars - Buck Converter Design Example From 90965bd65b083f6db6755dd63520bdbfbd56c0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 16:26:22 -0700 Subject: [PATCH 25/87] fix: update ID regex in parser to support '+' and '-' in subckt definitions and add new diode and transistor model files --- PySpice/Spice/Parser/Parser.py | 7 ++++++- .../semiconductors/operational-amplifier/generic_opamp.lib | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 7b4a427e..40867ca1 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -228,7 +228,12 @@ def t_NUMBER(self, t): def t_ID(self, t): # Fixme: - r'''(?i:[a-z0-9_+\-]+(\.[a-z0-9_+\-]+)*)''' + r''' + (?i: + [a-z_0-9]+ + (?:\.[a-z_0-9.]+)? + (?:(?<=[a-z0-9_])[+\-](?![a-z0-9_.]))? + )''' # t.value = Id(t.value) return t diff --git a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib index f85d6413..115d3836 100644 --- a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib +++ b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.lib @@ -1,8 +1,8 @@ * 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 inp inoff dc {VOFF} -G10 0 int inoff inn 100u +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 From 18c988c4ec0a0aa628b116f7b344d30199612efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 16:31:59 -0700 Subject: [PATCH 26/87] fix: update .gitignore to exclude YAML files in spice-library examples --- .gitignore | 1 + .../operational-amplifier/generic_opamp.yaml | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml diff --git a/.gitignore b/.gitignore index e42da4a4..972ce18a 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,4 @@ test.cir .venv/* *.vscode/* +example/spice-library/*.yaml diff --git a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml new file mode 100644 index 00000000..ac9e6477 --- /dev/null +++ b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml @@ -0,0 +1,8 @@ +path: generic_opamp.lib +date: '2025-03-03T16:25:39.919192' +digest: 943aafc69db4fb676ec22d6d03c864b2f5da80ed +description: '' +subcircuits: +- name: genopa1 + description: '' + nodes: [] From c26e19791fb29206a8dd591e3327b3c70b15ebe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 17:57:08 -0700 Subject: [PATCH 27/87] fix: update .gitignore to include all YAML files in spice-library examples and comment out lowercase file check in run-examples script --- .gitignore | 2 +- examples/run-examples | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 972ce18a..84617431 100644 --- a/.gitignore +++ b/.gitignore @@ -180,4 +180,4 @@ test.cir .venv/* *.vscode/* -example/spice-library/*.yaml +examples/spice-library/**/*.yaml diff --git a/examples/run-examples b/examples/run-examples index a2bf27c7..47b42935 100644 --- a/examples/run-examples +++ b/examples/run-examples @@ -36,7 +36,7 @@ examples_path = Path(__file__).resolve().parent for topic in os.listdir(examples_path): python_files = glob.glob(str(examples_path.joinpath(topic, '*.py'))) for file_name in python_files: - if file_name.islower(): + # if file_name.islower(): print('Run {}'.format(file_name)) subprocess.call(('python', file_name)) print('To continue press Enter') From 6d07a425f806e315e0eb6b49a87f0e23e6c10e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 19:25:54 -0700 Subject: [PATCH 28/87] fix: enable DEBUG logging level and handle None values in parser expressions. removed the 'params:' id from when it's parsing the spice file --- PySpice/Config/logging.yml | 4 ++-- PySpice/Spice/Parser/Parser.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/PySpice/Config/logging.yml b/PySpice/Config/logging.yml index 781c90c8..b8d1481b 100644 --- a/PySpice/Config/logging.yml +++ b/PySpice/Config/logging.yml @@ -46,6 +46,6 @@ root: loggers: PySpice: - # level: DEBUG - level: INFO + level: DEBUG + # level: INFO # level: WARNING diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 40867ca1..dcb2b619 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -313,7 +313,8 @@ def p_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] + ':') + # p[0] = Id(p[1] + ':') + p[0] = None # --- def p_uminus(self, p): @@ -376,13 +377,16 @@ def p_quote(self, p): def p_expression_list_space(self, p): '''expression_list_space : expression - | expression_list_space expression + | expression_list_space expression ''' if len(p) == 3: - p[1].append(p[2]) + # Only append if p[2] is not None. + if p[2] is not None: + p[1].append(p[2]) p[0] = p[1] else: - p[0] = SpaceList(p[1]) + p[0] = SpaceList(p[1]) if p[1] is not None else SpaceList() + def p_expression_list_comma(self, p): '''expression_list_comma : expression From b7d611f6a1834807537cd4e37c878ca6afc8e75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 19:41:20 -0700 Subject: [PATCH 29/87] fix: remove obsolete diode and transistor YAML files from spice-library --- .../diodes/schottky/1N5822.yaml | 8 -------- .../diodes/switching/1N4148.yaml | 15 --------------- .../transistors/MOSFET/irf150.yaml | 8 -------- .../operational-amplifier/generic_opamp.yaml | 8 -------- 4 files changed, 39 deletions(-) delete mode 100644 examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml delete mode 100644 examples/spice-library/discrete-semiconductors/diodes/switching/1N4148.yaml delete mode 100644 examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml delete mode 100644 examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml diff --git a/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml b/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml deleted file mode 100644 index 4ba3e16a..00000000 --- a/examples/spice-library/discrete-semiconductors/diodes/schottky/1N5822.yaml +++ /dev/null @@ -1,8 +0,0 @@ -path: 1N5822.lib -date: '2025-03-02T21:57:43.952971' -digest: 1e33a914cf68f96c4d3c065b7f5954fcb4c21d84 -description: '' -subcircuits: -- name: 1N5822 - description: '' - nodes: [] 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 05a81834..00000000 --- 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/discrete-semiconductors/transistors/MOSFET/irf150.yaml b/examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml deleted file mode 100644 index 49909dfb..00000000 --- a/examples/spice-library/discrete-semiconductors/transistors/MOSFET/irf150.yaml +++ /dev/null @@ -1,8 +0,0 @@ -path: irf150.lib -date: '2025-03-02T21:57:43.952971' -digest: fba6d4de74dfd6e8c1f6abfd9dc5022e6d99b110 -description: '' -subcircuits: -- name: irf150 - description: '' - nodes: [] diff --git a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml b/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml deleted file mode 100644 index ac9e6477..00000000 --- a/examples/spice-library/semiconductors/operational-amplifier/generic_opamp.yaml +++ /dev/null @@ -1,8 +0,0 @@ -path: generic_opamp.lib -date: '2025-03-03T16:25:39.919192' -digest: 943aafc69db4fb676ec22d6d03c864b2f5da80ed -description: '' -subcircuits: -- name: genopa1 - description: '' - nodes: [] From 588f1d248d568b8f6af62044722c1d1ae991fb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 3 Mar 2025 19:41:32 -0700 Subject: [PATCH 30/87] fix: update .gitignore to include all YAML files in spice-library examples --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 84617431..cd414287 100644 --- a/.gitignore +++ b/.gitignore @@ -180,4 +180,5 @@ test.cir .venv/* *.vscode/* +examples/spice-library/**/**/*.yaml examples/spice-library/**/*.yaml From eb8953774ddec2a2daf649be00728d4df57e767b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 4 Mar 2025 09:43:43 -0700 Subject: [PATCH 31/87] removed a test case. not needed. Improved simulation setting for a buck converter. added a line to show how to use add_esr and add_current_probe methods. --- PySpice/Config/logging.yml | 4 +- examples/switched-power-supplies/bg_test.py | 374 ------------------ .../switched-power-supplies/buck-converter.py | 36 +- 3 files changed, 27 insertions(+), 387 deletions(-) delete mode 100644 examples/switched-power-supplies/bg_test.py diff --git a/PySpice/Config/logging.yml b/PySpice/Config/logging.yml index b8d1481b..781c90c8 100644 --- a/PySpice/Config/logging.yml +++ b/PySpice/Config/logging.yml @@ -46,6 +46,6 @@ root: loggers: PySpice: - level: DEBUG - # level: INFO + # level: DEBUG + level: INFO # level: WARNING diff --git a/examples/switched-power-supplies/bg_test.py b/examples/switched-power-supplies/bg_test.py deleted file mode 100644 index 96edbc89..00000000 --- a/examples/switched-power-supplies/bg_test.py +++ /dev/null @@ -1,374 +0,0 @@ -import anywidget -import traitlets -import json -import time -from collections import defaultdict - -class AnimatedFnWidget(anywidget.AnyWidget): - # Trait for sending new data points to JavaScript - new_data_points = traitlets.Unicode('{}').tag(sync=True) - - # Trait for initial configuration - config = traitlets.Unicode('{"xMin": 0, "xMax": 10, "yMin": -1.5, "yMax": 1.5, "series": {}}').tag(sync=True) - - _esm = """ - function render({ model, el }) { - const container = document.createElement('div'); - container.style.width = '100%'; - container.style.height = '400px'; - container.style.border = '1px solid #ccc'; - container.style.position = 'relative'; - el.appendChild(container); - - const canvas = document.createElement('canvas'); - canvas.style.width = '100%'; - canvas.style.height = '100%'; - container.appendChild(canvas); - - function resizeCanvas() { - const rect = container.getBoundingClientRect(); - canvas.width = rect.width * window.devicePixelRatio; - canvas.height = rect.height * window.devicePixelRatio; - redrawPlot(); - } - - window.addEventListener('resize', resizeCanvas); - - let allData = {}; - let config = JSON.parse(model.get('config')); - const colors = ['#3498db', '#e74c3c', '#2ecc71', '#9b59b6', '#f1c40f', '#e67e22']; - - model.on('change:config', () => { - config = JSON.parse(model.get('config')); - allData = {}; - Object.keys(config.series).forEach(series => { - allData[series] = { x: [], y: [] }; - }); - redrawPlot(); - }); - - model.on('change:new_data_points', () => { - const newData = JSON.parse(model.get('new_data_points')); - if (Object.keys(newData).length === 0) return; - - Object.entries(newData).forEach(([series, points]) => { - if (!allData[series]) { - allData[series] = { x: [], y: [] }; - } - allData[series].x = allData[series].x.concat(points.x); - allData[series].y = allData[series].y.concat(points.y); - - if (allData[series].x.length > 0) { - const maxX = Math.max(...Object.values(allData).map(d => Math.max(...d.x))); - if (maxX > config.xMax) { - config.xMin = Math.max(0, maxX - 10); - config.xMax = maxX; - } - } - }); - - redrawPlot(); - }); - - function redrawPlot() { - const ctx = canvas.getContext('2d'); - const width = canvas.width; - const height = canvas.height; - - // Compute yMin and yMax dynamically if not specified - let yMin, yMax; - if (typeof config.yMin === 'number' && typeof config.yMax === 'number') { - yMin = config.yMin; - yMax = config.yMax; - } else { - // Auto-scale y-axis - let allYValues = []; - Object.values(allData).forEach(series => { - allYValues = allYValues.concat(series.y); - }); - if (allYValues.length === 0) { - yMin = -1; - yMax = 1; - } else { - let dataMin = Math.min(...allYValues); - let dataMax = Math.max(...allYValues); - if (dataMin === dataMax) { - yMin = dataMin - 1; - yMax = dataMax + 1; - } else { - let padding = (dataMax - dataMin) * 0.1; - yMin = dataMin - padding; - yMax = dataMax + padding; - } - } - } - - ctx.clearRect(0, 0, width, height); - - // Draw grid - ctx.strokeStyle = '#ccc'; - ctx.lineWidth = 1 * window.devicePixelRatio; - ctx.beginPath(); - const gridStep = width / 10; - for (let i = 0; i <= 10; i++) { - const x = i * gridStep; - ctx.moveTo(x, 0); - ctx.lineTo(x, height); - } - const yGridStep = height / 6; - for (let i = 0; i <= 6; i++) { - const y = i * yGridStep; - ctx.moveTo(0, y); - ctx.lineTo(width, y); - } - ctx.stroke(); - - // Draw axes - ctx.strokeStyle = '#000'; - const yZero = height * (1 - (0 - yMin) / (yMax - yMin)); - ctx.beginPath(); - ctx.moveTo(0, yZero); - ctx.lineTo(width, yZero); - ctx.stroke(); - - const xZero = width * (0 - config.xMin) / (config.xMax - config.xMin); - ctx.beginPath(); - ctx.moveTo(xZero, 0); - ctx.lineTo(xZero, height); - ctx.stroke(); - - function dataToCanvas(x, y) { - return { - x: width * (x - config.xMin) / (config.xMax - config.xMin), - y: height * (1 - (y - yMin) / (yMax - yMin)) - }; - } - - // Draw all series (no config.series check) - Object.entries(allData).forEach(([series, data], index) => { - if (data.x.length > 1) { - const visibleData = data.x.map((x, i) => ({x, y: data.y[i]})) - .filter(point => point.x >= config.xMin && point.x <= config.xMax); - - if (visibleData.length > 0) { - ctx.beginPath(); - const startPoint = dataToCanvas(visibleData[0].x, visibleData[0].y); - ctx.moveTo(startPoint.x, startPoint.y); - - for (let i = 1; i < visibleData.length; i++) { - const point = dataToCanvas(visibleData[i].x, visibleData[i].y); - ctx.lineTo(point.x, point.y); - } - - ctx.strokeStyle = colors[index % colors.length]; - ctx.lineWidth = 2 * window.devicePixelRatio; - ctx.stroke(); - - // Draw legend with fallback label - ctx.fillStyle = colors[index % colors.length]; - ctx.fillRect(10, 10 + index * 20, 10, 10); - ctx.fillStyle = '#000'; - ctx.font = `${12 * window.devicePixelRatio}px Arial`; - const label = config.series[series] ? config.series[series].label : series; - ctx.fillText(label, 25, 20 + index * 20); - } - } - }); - - // Draw axes labels - ctx.fillStyle = '#000'; - ctx.font = `${12 * window.devicePixelRatio}px Arial`; - ctx.fillText(`t`, width - 15, yZero - 5); - ctx.fillText(`${config.xMin.toFixed(1)}`, 5, yZero - 5); - ctx.fillText(`${config.xMax.toFixed(1)}`, width - 30, yZero - 5); - ctx.fillText(`${yMax.toFixed(1)}`, xZero + 5, 15); - ctx.fillText(`${yMin.toFixed(1)}`, xZero + 5, height - 5); - } - - setTimeout(resizeCanvas, 0); -} - -export default { render }; - """ - - def __init__(self, update_interval_ms=100, **kwargs): - super().__init__(**kwargs) - self.update_interval = update_interval_ms / 1000 - self.last_update_time = 0 - self.buffer = defaultdict(lambda: {'x': [], 'y': []}) - - def update_config(self, config_dict): - """Update the plot configuration""" - self.config = json.dumps(config_dict) - - def add_points(self, series_name, x_values, y_values): - """Add points for a specific series""" - if len(x_values) != len(y_values): - raise ValueError("x_values and y_values must have the same length") - - self.buffer[series_name]['x'].extend(x_values) - self.buffer[series_name]['y'].extend(y_values) - - current_time = time.time() - if current_time - self.last_update_time >= self.update_interval: - self._flush_buffer() - self.last_update_time = current_time - - def _flush_buffer(self): - """Send buffered data to JavaScript""" - if not self.buffer: - return - - self.new_data_points = json.dumps(dict(self.buffer)) - for series in self.buffer: - self.buffer[series]['x'].clear() - self.buffer[series]['y'].clear() - -# Example usage with sine wave -def test_sine_wave(widget): - import numpy as np - config = { - "xMin": 0, - "xMax": 10, - "yMin": -1.5, - "yMax": 1.5, - "series": { - "sine": {"label": "Sine Wave"} - } - } - widget.update_config(config) - - t = 0 - dt = 0.05 - points_per_update = 5 - - while t < 20: - x_values = [t + i * dt for i in range(points_per_update)] - y_values = [np.sin(2 * np.pi * x) for x in x_values] - widget.add_points("sine", x_values, y_values) - t += dt * points_per_update - time.sleep(widget.update_interval) - - -# - - -anim_widget = AnimatedFnWidget(update_interval_ms=100) -anim_widget - -test_sine_wave(anim_widget) #if this doesn't work then forget it - -# + -from PySpice.Spice.Netlist import Circuit -from PySpice import SpiceLibrary, Circuit, Simulator, plot -from PySpice.Unit import * - -# Create a circuit -circuit = Circuit('My Circuit') - -# Add components to the circuit -circuit.R('res1', 'k', 'y', 1@u_Ω) # Resistor R1 between nodes 'k' and 'y' with 1 Ω -circuit.C('cap1', 0, 'k', 1@u_F, ic=0.0) # Capacitor C1 between nodes 0 and 'k' with 1 F, initial condition 0.0 -circuit.V('vdc', 0, 'y', 1@u_V) # Voltage source V1 between nodes 0 and 'y' with 1 V - -# Print the circuit to verify -print(circuit) - -# - - -def couple_with_ngspice(widget, simulator, start, end, step, y_range=(-2, 2)): - """ - Couple the AnimatedFnWidget with an NGSpice simulation, plotting all available signals. - - Args: - widget: AnimatedFnWidget instance - simulator: PySpice simulator object - start: Simulation start time - end: Simulation end time - step: Simulation time step - y_range: Tuple of (min, max) values for y-axis - """ - # Initial config with empty series; labels will use signal names - config = { - "xMin": start, - "xMax": end, - "yMin": y_range[0], - "yMax": y_range[1], - "series": {} - } - widget.update_config(config) - - def data_listener(actual_vector_values, number_of_vectors, ngspice_id): - time_value = float(actual_vector_values['time'].real) - for signal, value in actual_vector_values.items(): - if signal != 'time': - widget.add_points(signal, [time_value], [float(value.real)]) - - simulator.simulator._ngspice_shared.addListener("send_data", data_listener) - # display(widget) - simulator.transient(step, end, start, use_initial_condition=True, background=True) - - -# + -from PySpice.Spice.Netlist import Circuit -from PySpice.Unit import * -from PySpice.Spice.NgSpice.Shared import NgSpiceShared -class MyNgSpiceShared(NgSpiceShared): - def __init__(self, ngspice_id=0, send_data=False, verbose=False): - super().__init__(ngspice_id=ngspice_id, send_data=send_data, verbose=verbose) - self._listeners = { - "send_data": [], - "send_init_data": [] - } - - def addListener(self, event, listener): - if event in self._listeners: - self._listeners[event].append(listener) - else: - raise ValueError(f"Ev'ent '{event}' is not supported.") - - def removeListener(self, event, listener): - if event in self._listeners: - if listener in self._listeners[event]: - self._listeners[event].remove(listener) - else: - raise ValueError(f"Listener '{listener.__name__}' is not registered for event '{event}'.") - else: - raise ValueError(f"Event '{event}' is not supported.") - - def _notify_listeners(self, event, *args, **kwargs): - if event in self._listeners: - for listener in self._listeners[event]: - listener(*args, **kwargs) - - def send_data(self, actual_vector_values, number_of_vectors, ngspice_id): - # Notify listeners for the 'send_data' event - self._notify_listeners("send_data", actual_vector_values, number_of_vectors, ngspice_id) - return 0 - - def send_init_data(self, data, ngspice_id): - # Notify listeners for the 'send_init_data' event - self._notify_listeners("send_init_data", data, ngspice_id) - return 0 - - - -ngshared = MyNgSpiceShared.new_instance(send_data=True,ngspice_id=0) -simulator =Simulator.factory().simulation(circuit,temperature=25, nominal_temperature=25 - ,ngspice_shared=MyNgSpiceShared.new_instance(send_data=True) - ) -analysis = simulator.dc(Vvdc=slice(-2, 5, .01)) - -# simulator = circuit.simulator( -# temperature=25, -# nominal_temperature=25, -# ngspice_shared=MyNgSpiceShared.new_instance(send_data=True) -# ) -anim_widget = AnimatedFnWidget(update_interval_ms=100) -couple_with_ngspice(anim_widget, simulator, 0, 12, 0.0001)# If your computer is too fast make this smaller.... if your computer is too slow make this bigger. -# - -simulator.simulator._ngspice_shared.halt() ## Execute these instructions when they are on the middle. - -simulator.simulator._ngspice_shared.alter_device(device='Vvdc', dc=-1) - - -simulator.simulator._ngspice_shared.resume() \ No newline at end of file diff --git a/examples/switched-power-supplies/buck-converter.py b/examples/switched-power-supplies/buck-converter.py index e711b94f..472bf202 100755 --- a/examples/switched-power-supplies/buck-converter.py +++ b/examples/switched-power-supplies/buck-converter.py @@ -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) + +figure, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), sharex=True) -ax.plot(analysis.out) -ax.plot(analysis['source']) +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() From 720a15152e4ddb88cf8712332ee5a4986f08150d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 4 Mar 2025 16:22:14 -0700 Subject: [PATCH 32/87] reverting back the changes to the background simulation. background simulation is now enabled when you use ngshared. This functionality is now available in the ngshared module. No need for the previous changes. --- PySpice/Spice/NgSpice/Simulator.py | 14 ++++---------- PySpice/Spice/Simulation.py | 4 ---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/PySpice/Spice/NgSpice/Simulator.py b/PySpice/Spice/NgSpice/Simulator.py index 569f218b..52637940 100644 --- a/PySpice/Spice/NgSpice/Simulator.py +++ b/PySpice/Spice/NgSpice/Simulator.py @@ -117,16 +117,10 @@ def run(self, simulation): # load circuit and simulation # Fixme: Error: circuit not parsed. self._ngspice_shared.load_circuit(str(simulation)) - background = simulation.background if hasattr(simulation, 'background') else False - self._ngspice_shared.run(background=background) + self._ngspice_shared.run() self._logger.debug(str(self._ngspice_shared.plot_names)) - if not background: - simulation.reset_analysis() - - plot_name = self._ngspice_shared.last_plot - if plot_name == 'const': - raise NameError('Simulation failed') - else: - return True #Nothing to show yet! + plot_name = self._ngspice_shared.last_plot + if plot_name == 'const': + raise NameError('Simulation failed') return self._ngspice_shared.plot(simulation, plot_name).to_analysis() diff --git a/PySpice/Spice/Simulation.py b/PySpice/Spice/Simulation.py index 999457f3..311d1fbc 100644 --- a/PySpice/Spice/Simulation.py +++ b/PySpice/Spice/Simulation.py @@ -739,10 +739,6 @@ def _run(self, analysis_method, *args, **kwargs): if 'probes' in kwargs: self.save(* kwargs.pop('probes')) - if 'background' in kwargs: - background = kwargs.pop('background') - self.background = background - # Execute analysis implementation analysis_method(self, *args, **kwargs) From f7739a9525ce4ecb49e6f382bd208323b9b4f1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 4 Mar 2025 18:34:26 -0700 Subject: [PATCH 33/87] fix: correct parameter name in semilogx and restore expression handling in parser. change the name from basex to base --- PySpice/Plot/BodeDiagram.py | 2 +- PySpice/Spice/Parser/Parser.py | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/PySpice/Plot/BodeDiagram.py b/PySpice/Plot/BodeDiagram.py index d31a864f..54e0e519 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/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index dcb2b619..40867ca1 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -313,8 +313,7 @@ def p_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] + ':') - p[0] = None + p[0] = Id(p[1] + ':') # --- def p_uminus(self, p): @@ -377,16 +376,13 @@ def p_quote(self, p): def p_expression_list_space(self, p): '''expression_list_space : expression - | expression_list_space expression + | expression_list_space expression ''' if len(p) == 3: - # Only append if p[2] is not None. - if p[2] is not None: - p[1].append(p[2]) + p[1].append(p[2]) p[0] = p[1] else: - p[0] = SpaceList(p[1]) if p[1] is not None else SpaceList() - + p[0] = SpaceList(p[1]) def p_expression_list_comma(self, p): '''expression_list_comma : expression From da448270e172ec2d6655a0462505bcc21f9485b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 5 Mar 2025 08:06:05 -0700 Subject: [PATCH 34/87] fix: improve regex pattern for identifier parsing in SpiceParser. Now it should match +in and -in or +_in or -_in for the ID --- PySpice/Spice/Parser/Parser.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 40867ca1..71758ee6 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -229,10 +229,13 @@ def t_NUMBER(self, t): def t_ID(self, t): # Fixme: r''' - (?i: - [a-z_0-9]+ - (?:\.[a-z_0-9.]+)? - (?:(?<=[a-z0-9_])[+\-](?![a-z0-9_.]))? + (?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 + )? )''' # t.value = Id(t.value) return t From 8b2536b073373a40dd1a4eb8cc6680d83fcb58c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 5 Mar 2025 09:41:17 -0700 Subject: [PATCH 35/87] feat: add input clipper example for Xspice control limit test with transient analysis --- examples/xspice/input_clipper.py | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/xspice/input_clipper.py diff --git a/examples/xspice/input_clipper.py b/examples/xspice/input_clipper.py new file mode 100644 index 00000000..a0e1f196 --- /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') + +circuit.model('varlimit', 'climit', in_offset=0.0, gain=1.0, upper_delta=0.0, lower_delta=0.0, limit_range=0.1, 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') From 82200573299a15b8e2100f956750e6081f9abb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 5 Mar 2025 11:30:37 -0700 Subject: [PATCH 36/87] sometimes there is error in stdout which might come form xspice but simulation is still successful. --- PySpice/Spice/NgSpice/Shared.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index f0820f03..82718ce6 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -653,10 +653,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) From 75a31f49ee418a20042bc563221defee4ab24fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 5 Mar 2025 11:54:04 -0700 Subject: [PATCH 37/87] fix: update regex pattern in SpiceParser to allow '%' as a valid prefix for identifiers for ID in a netlist. in xspice %v or %vd and etc is common. --- PySpice/Spice/Parser/Parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 71758ee6..d4e7ade0 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -230,7 +230,7 @@ def t_ID(self, t): # Fixme: r''' (?i: # case-insensitive - (?:[+\-](?=[a-z_]))? # optional + or - at the start, only if followed by letter/underscore + (?:[+\-\%](?=[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 From 1707e45bd2f9b3a8c017718995c3439ca83553a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 5 Mar 2025 12:38:08 -0700 Subject: [PATCH 38/87] fix: enhance logging for NgSpiceShared to warn on small timestep and error on aborted simulations; update input clipper model limit range --- PySpice/Spice/NgSpice/Shared.py | 6 +++++- examples/xspice/input_clipper.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 82718ce6..6d437ca4 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -635,6 +635,8 @@ def _send_char(message_c, ngspice_id, user_data): 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('Note:'): @@ -642,7 +644,9 @@ def _send_char(message_c, ngspice_id, user_data): elif content.startswith('Trying'): func = self._logger.info elif content.startswith('Supplies reduced'): - func = self._logger.info + 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 diff --git a/examples/xspice/input_clipper.py b/examples/xspice/input_clipper.py index a0e1f196..ec448feb 100644 --- a/examples/xspice/input_clipper.py +++ b/examples/xspice/input_clipper.py @@ -30,10 +30,10 @@ 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 +# 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') - -circuit.model('varlimit', 'climit', in_offset=0.0, gain=1.0, upper_delta=0.0, lower_delta=0.0, limit_range=0.1, fraction=False) +# 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() From 0daa4bfaf2e643f2038302cf33dff19a165a8077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Thu, 6 Mar 2025 09:40:00 -0700 Subject: [PATCH 39/87] fix: add error handling in SpiceLibrary to log warnings for failed library parsing but keep going. if you can load a library, you should be able to load the rest of the libraries. --- PySpice/Spice/Library/Library.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index ad46046f..e4c19a22 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -169,7 +169,10 @@ def scan(self) -> None: for path in PathTools.walk(self._path): _ = path.suffix.lower() if _ in self.EXTENSIONS: - self._handle_library(path) + try: + self._handle_library(path) + except: + self._logger.warning(f"Failed to parse {path}") ############################################## From ee333240772eed275fe643ae60bec48da347c0f0 Mon Sep 17 00:00:00 2001 From: tapegoji <155494152+tapegoji@users.noreply.github.com> Date: Sat, 8 Mar 2025 11:52:23 -0700 Subject: [PATCH 40/87] Create TODO --- TODO | 1 + 1 file changed, 1 insertion(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 00000000..e2bb67ff --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +change spice library to be backwards compatible \ No newline at end of file From 916fa97bdf03d414860b1c8b2e8dd10e283fe415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 8 Mar 2025 21:58:04 -0700 Subject: [PATCH 41/87] fix: add optional recurse parameter to SpiceLibrary constructor for compatibility with skidl --- PySpice/Spice/Library/Library.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index e4c19a22..86a03612 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -70,7 +70,10 @@ 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) -> 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) From 3c190f00c24d69626c8776a62421319739f28c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 9 Mar 2025 08:36:31 -0600 Subject: [PATCH 42/87] fix: adjust SpiceLibrary path handling. If a file is given instead of a folder find the parnt folder instead. this way the rest of the code remains unchanged and it does not give error add cleanup script for temporary files --- PySpice/Spice/Library/Library.py | 2 ++ clean.sh | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100755 clean.sh diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 86a03612..990687d4 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -78,6 +78,8 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa 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 = {} if not scan: diff --git a/clean.sh b/clean.sh new file mode 100755 index 00000000..3d0c872b --- /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 From 1c25effb86ee032ead3ca20cf22024ceb4f44cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 9 Mar 2025 09:03:58 -0600 Subject: [PATCH 43/87] fix: add optional section parameter to SpiceLibrary constructor for backward compatibility --- PySpice/Spice/Library/Library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 990687d4..75252f60 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -70,7 +70,7 @@ class SpiceLibrary: ############################################## - def __init__(self, root_path: str | Path, scan: bool = False, recurse: 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 From d4c509318d15ca75dc5c6184c55c31ea6c228f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 9 Mar 2025 15:58:57 -0600 Subject: [PATCH 44/87] feat: add buck converter example circuit simulation using ngshared --- .../buck-converter_ngshared.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 examples/switched-power-supplies/buck-converter_ngshared.py 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 00000000..19157c86 --- /dev/null +++ b/examples/switched-power-supplies/buck-converter_ngshared.py @@ -0,0 +1,167 @@ +#################################################################################################### + +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 * + +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) + +#################################################################################################### + + +# simulator = Simulator.factory() +# simulation = simulator.simulation(circuit, temperature=25, nominal_temperature=25) + +# 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) + +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 2000us 0 1ns uic\n' +options += '.end' + +circ_str += options + + +ngspice.load_circuit(circ_str) +print('Loaded circuit:') +# print(ngspice.listing()) + +background = True +ngspice.run(background=True) +print('Plots:', ngspice.plot_names) + +# print(ngspice.ressource_usage()) +import time +time.sleep(2) +if background: + print(ngspice.halt()) +print(ngspice.status()) + +plot = ngspice.plot(simulation=None, plot_name=ngspice.last_plot) +analysis = plot.to_analysis() + +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']) +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() + +#f# save_figure('figure', 'buck-converter.png') From 5946f3bdfd51c7fa01aa99938bbc87384be89470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 9 Mar 2025 20:09:49 -0600 Subject: [PATCH 45/87] fix:fix pin issues and saving and loading from yaml file for pins. now subcircuits have pins. --- PySpice/Spice/Library/SpiceInclude.py | 35 +++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index ae6231c8..59b914c5 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 _ ############################################## From 655688790056bcead05ea2e5a36c78f8fd64c30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 10 Mar 2025 13:15:20 -0600 Subject: [PATCH 46/87] feat: fixed a bug when running a simulation in background in background simulation when we halt and resume we get an simulationa intterupted in stdout. This should not cause the simulation to break. --- PySpice/Spice/NgSpice/Shared.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PySpice/Spice/NgSpice/Shared.py b/PySpice/Spice/NgSpice/Shared.py index 6d437ca4..fc934a96 100644 --- a/PySpice/Spice/NgSpice/Shared.py +++ b/PySpice/Spice/NgSpice/Shared.py @@ -639,6 +639,8 @@ def _send_char(message_c, ngspice_id, user_data): 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'): From f13f1363ca34e24401a526aa2c81f5189c8ab45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 10 Mar 2025 14:06:31 -0600 Subject: [PATCH 47/87] feat: improved buck converter simulation example with live plotting using background feature in ngspice --- .../buck-converter_ngshared.py | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/examples/switched-power-supplies/buck-converter_ngshared.py b/examples/switched-power-supplies/buck-converter_ngshared.py index 19157c86..8db90283 100644 --- a/examples/switched-power-supplies/buck-converter_ngshared.py +++ b/examples/switched-power-supplies/buck-converter_ngshared.py @@ -1,7 +1,7 @@ #################################################################################################### import matplotlib.pyplot as plt - +import time #################################################################################################### import PySpice.Logging.Logging as Logging @@ -104,64 +104,67 @@ #################################################################################################### - -# simulator = Simulator.factory() -# simulation = simulator.simulation(circuit, temperature=25, nominal_temperature=25) - -# 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) - +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 2000us 0 1ns uic\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()) -background = True -ngspice.run(background=True) +live = True +ngspice.run(background=live) print('Plots:', ngspice.plot_names) - -# print(ngspice.ressource_usage()) -import time -time.sleep(2) -if background: - print(ngspice.halt()) -print(ngspice.status()) - -plot = ngspice.plot(simulation=None, plot_name=ngspice.last_plot) -analysis = plot.to_analysis() - -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']) -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() - -#f# save_figure('figure', 'buck-converter.png') +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 From bd99c254584ac6ef0d07002e05668e35462f811c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 10 Mar 2025 14:11:38 -0600 Subject: [PATCH 48/87] chore: update GitHub Actions workflows to use 'development' branch and upgrade action versions --- .github/workflows/codeql-analysis.yml | 10 +++++----- .github/workflows/pyspice-test.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 05215197..b6c441a2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [ development ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ development ] schedule: - cron: '45 23 * * 1' @@ -39,11 +39,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,4 +68,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index 98489e8f..802ccb07 100644 --- a/.github/workflows/pyspice-test.yml +++ b/.github/workflows/pyspice-test.yml @@ -6,9 +6,9 @@ name: Pyspice Test # Trigger the workflow on on: push: - branches: "*" + branches: ["*"] pull_request: - branches: [ * ] + branches: ["*"] # page_build: # release: # types: # This configuration does not affect the page_build event above From 8bc043a7719769d1f527de4f20344a32137bb3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 10 Mar 2025 14:15:56 -0600 Subject: [PATCH 49/87] chore: update GitHub Actions workflows to comment out unused sections for clarity --- .github/workflows/codeql-analysis.yml | 122 ++++++++++++------------- .github/workflows/pyspice-test.yml | 124 +++++++++++++------------- 2 files changed, 123 insertions(+), 123 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b6c441a2..e73955fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,71 +1,71 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" +# # For most projects, this workflow file will not need changing; you simply need +# # to commit it to your repository. +# # +# # You may wish to alter this file to override the set of languages analyzed, +# # or to provide custom queries or build logic. +# # +# # ******** NOTE ******** +# # We have attempted to detect the languages in your repository. Please check +# # the `language` matrix defined below to confirm you have the correct set of +# # supported CodeQL languages. +# # +# name: "CodeQL" -on: - push: - branches: [ development ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ development ] - schedule: - - cron: '45 23 * * 1' +# on: +# push: +# branches: [ development ] +# pull_request: +# # The branches below must be a subset of the branches above +# branches: [ development ] +# schedule: +# - cron: '45 23 * * 1' -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write +# jobs: +# analyze: +# name: Analyze +# runs-on: ubuntu-latest +# permissions: +# actions: read +# contents: read +# security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed +# strategy: +# fail-fast: false +# matrix: +# language: [ 'python' ] +# # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] +# # Learn more: +# # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - steps: - - name: Checkout repository - uses: actions/checkout@v3 +# steps: +# - name: Checkout repository +# uses: actions/checkout@v3 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main +# # Initializes the CodeQL tools for scanning. +# - name: Initialize CodeQL +# uses: github/codeql-action/init@v2 +# with: +# languages: ${{ matrix.language }} +# # If you wish to specify custom queries, you can do so here or in a config file. +# # By default, queries listed here will override any specified in a config file. +# # Prefix the list here with "+" to use these queries and those in the config file. +# # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 +# # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). +# # If this step fails, then you should remove it and run the build manually (see below) +# - name: Autobuild +# uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl +# # ℹ️ Command-line programs to run using the OS shell. +# # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language +# # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines +# # and modify them (or add more) to build your code if your project +# # uses a compiled language - #- run: | - # make bootstrap - # make release +# #- run: | +# # make bootstrap +# # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 +# - name: Perform CodeQL Analysis +# uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index 802ccb07..2ece4e5e 100644 --- a/.github/workflows/pyspice-test.yml +++ b/.github/workflows/pyspice-test.yml @@ -1,74 +1,74 @@ -# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -# https://docs.github.com/en/actions/guides/building-and-testing-python +# # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +# # https://docs.github.com/en/actions/guides/building-and-testing-python -name: Pyspice Test +# name: Pyspice Test -# Trigger the workflow on -on: - push: - branches: ["*"] - pull_request: - branches: ["*"] - # page_build: - # release: - # types: # This configuration does not affect the page_build event above - # - created +# # Trigger the workflow on +# on: +# push: +# branches: ["*"] +# pull_request: +# branches: ["*"] +# # page_build: +# # release: +# # types: # This configuration does not affect the page_build event above +# # - created -# A map of environment variables -# env: -# SERVER: production +# # A map of environment variables +# # env: +# # SERVER: production -# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell -defaults: - run: - shell: bash -# working-directory: scripts +# # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell +# defaults: +# run: +# shell: bash +# # working-directory: scripts -jobs: - install-test: - name: Install and Test +# jobs: +# install-test: +# name: Install and Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - # python-version: [3.8, 3.9] - python-version: [3.8] +# runs-on: ${{ matrix.os }} +# strategy: +# matrix: +# os: [ubuntu-latest, macos-latest, windows-latest] +# # python-version: [3.8, 3.9] +# python-version: [3.8] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} +# steps: +# - uses: actions/checkout@v2 +# - name: Set up Python ${{ matrix.python-version }} +# uses: actions/setup-python@v2 +# with: +# python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip3 install --upgrade pip - pip3 install -r requirements.txt - pip3 install beautifulsoup4 invoke requests - pip3 install pytest - pip3 install pyterate - pip3 install pint +# - name: Install dependencies +# run: | +# pip3 install --upgrade pip +# pip3 install -r requirements.txt +# pip3 install beautifulsoup4 invoke requests +# pip3 install pytest +# pip3 install pyterate +# pip3 install pint - - name: Install PySpice - run: pip3 install . +# - name: Install PySpice +# run: pip3 install . - - name: Run Unit-Tests - run: pytest unit-test +# - name: Run Unit-Tests +# run: pytest unit-test - - name: Test on Linux - if: matrix.os == 'ubuntu-latest' - # https://packages.ubuntu.com/search?keywords=ngspice - run: | - sudo apt-get install ngspice ngspice-dev libngspice0 libngspice0-dev - pyspice-post-installation --check-install - invoke test.run-examples +# - name: Test on Linux +# if: matrix.os == 'ubuntu-latest' +# # https://packages.ubuntu.com/search?keywords=ngspice +# run: | +# sudo apt-get install ngspice ngspice-dev libngspice0 libngspice0-dev +# pyspice-post-installation --check-install +# invoke test.run-examples - - name: Test on Windows - if: matrix.os == 'windows-latest' - run: | - pyspice-post-installation --install-ngspice-dll - pyspice-post-installation --check-install - export PYTHONIOENCODING="utf_8" - invoke test.run-examples +# - name: Test on Windows +# if: matrix.os == 'windows-latest' +# run: | +# pyspice-post-installation --install-ngspice-dll +# pyspice-post-installation --check-install +# export PYTHONIOENCODING="utf_8" +# invoke test.run-examples From 8750a15bf7ef7b93ecb37ea84575db44c5e3e9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Mon, 10 Mar 2025 14:20:29 -0600 Subject: [PATCH 50/87] chore: update GitHub Actions workflows for CodeQL analysis and PySpice tests to improve clarity and functionality --- .github/workflows/codeql-analysis.yml | 122 ++++++++++++------------- .github/workflows/pyspice-test.yml | 124 +++++++++++++------------- 2 files changed, 123 insertions(+), 123 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e73955fb..05215197 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,71 +1,71 @@ -# # For most projects, this workflow file will not need changing; you simply need -# # to commit it to your repository. -# # -# # You may wish to alter this file to override the set of languages analyzed, -# # or to provide custom queries or build logic. -# # -# # ******** NOTE ******** -# # We have attempted to detect the languages in your repository. Please check -# # the `language` matrix defined below to confirm you have the correct set of -# # supported CodeQL languages. -# # -# name: "CodeQL" +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" -# on: -# push: -# branches: [ development ] -# pull_request: -# # The branches below must be a subset of the branches above -# branches: [ development ] -# schedule: -# - cron: '45 23 * * 1' +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '45 23 * * 1' -# jobs: -# analyze: -# name: Analyze -# runs-on: ubuntu-latest -# permissions: -# actions: read -# contents: read -# security-events: write +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write -# strategy: -# fail-fast: false -# matrix: -# language: [ 'python' ] -# # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] -# # Learn more: -# # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed -# steps: -# - name: Checkout repository -# uses: actions/checkout@v3 + steps: + - name: Checkout repository + uses: actions/checkout@v2 -# # Initializes the CodeQL tools for scanning. -# - name: Initialize CodeQL -# uses: github/codeql-action/init@v2 -# with: -# languages: ${{ matrix.language }} -# # If you wish to specify custom queries, you can do so here or in a config file. -# # By default, queries listed here will override any specified in a config file. -# # Prefix the list here with "+" to use these queries and those in the config file. -# # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main -# # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). -# # If this step fails, then you should remove it and run the build manually (see below) -# - name: Autobuild -# uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 -# # ℹ️ Command-line programs to run using the OS shell. -# # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl -# # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines -# # and modify them (or add more) to build your code if your project -# # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language -# #- run: | -# # make bootstrap -# # make release + #- run: | + # make bootstrap + # make release -# - name: Perform CodeQL Analysis -# uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/pyspice-test.yml b/.github/workflows/pyspice-test.yml index 2ece4e5e..c8d327fb 100644 --- a/.github/workflows/pyspice-test.yml +++ b/.github/workflows/pyspice-test.yml @@ -1,74 +1,74 @@ -# # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -# # https://docs.github.com/en/actions/guides/building-and-testing-python +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +# https://docs.github.com/en/actions/guides/building-and-testing-python -# name: Pyspice Test +name: Pyspice Test -# # Trigger the workflow on -# on: -# push: -# branches: ["*"] -# pull_request: -# branches: ["*"] -# # page_build: -# # release: -# # types: # This configuration does not affect the page_build event above -# # - created +# Trigger the workflow on +on: + push: + branches: "*" + pull_request: + branches: [ "*" ] + # page_build: + # release: + # types: # This configuration does not affect the page_build event above + # - created -# # A map of environment variables -# # env: -# # SERVER: production +# A map of environment variables +# env: +# SERVER: production -# # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell -# defaults: -# run: -# shell: bash -# # working-directory: scripts +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell +defaults: + run: + shell: bash +# working-directory: scripts -# jobs: -# install-test: -# name: Install and Test +jobs: + install-test: + name: Install and Test -# runs-on: ${{ matrix.os }} -# strategy: -# matrix: -# os: [ubuntu-latest, macos-latest, windows-latest] -# # python-version: [3.8, 3.9] -# python-version: [3.8] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # python-version: [3.8, 3.9] + python-version: [3.8] -# steps: -# - uses: actions/checkout@v2 -# - name: Set up Python ${{ matrix.python-version }} -# uses: actions/setup-python@v2 -# with: -# python-version: ${{ matrix.python-version }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} -# - name: Install dependencies -# run: | -# pip3 install --upgrade pip -# pip3 install -r requirements.txt -# pip3 install beautifulsoup4 invoke requests -# pip3 install pytest -# pip3 install pyterate -# pip3 install pint + - name: Install dependencies + run: | + pip3 install --upgrade pip + pip3 install -r requirements.txt + pip3 install beautifulsoup4 invoke requests + pip3 install pytest + pip3 install pyterate + pip3 install pint -# - name: Install PySpice -# run: pip3 install . + - name: Install PySpice + run: pip3 install . -# - name: Run Unit-Tests -# run: pytest unit-test + - name: Run Unit-Tests + run: pytest unit-test -# - name: Test on Linux -# if: matrix.os == 'ubuntu-latest' -# # https://packages.ubuntu.com/search?keywords=ngspice -# run: | -# sudo apt-get install ngspice ngspice-dev libngspice0 libngspice0-dev -# pyspice-post-installation --check-install -# invoke test.run-examples + - name: Test on Linux + if: matrix.os == 'ubuntu-latest' + # https://packages.ubuntu.com/search?keywords=ngspice + run: | + sudo apt-get install ngspice ngspice-dev libngspice0 libngspice0-dev + pyspice-post-installation --check-install + invoke test.run-examples -# - name: Test on Windows -# if: matrix.os == 'windows-latest' -# run: | -# pyspice-post-installation --install-ngspice-dll -# pyspice-post-installation --check-install -# export PYTHONIOENCODING="utf_8" -# invoke test.run-examples + - name: Test on Windows + if: matrix.os == 'windows-latest' + run: | + pyspice-post-installation --install-ngspice-dll + pyspice-post-installation --check-install + export PYTHONIOENCODING="utf_8" + invoke test.run-examples From 650e84d3a6c09d3066dc2d6a7a42e9db1c73b606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 11 Mar 2025 10:29:39 -0600 Subject: [PATCH 51/87] fix: simplify node naming in Pin class by removing redundant element name in _node assignment --- PySpice/Spice/Element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Element.py b/PySpice/Spice/Element.py index 968e27cd..19805955 100644 --- a/PySpice/Spice/Element.py +++ b/PySpice/Spice/Element.py @@ -221,7 +221,7 @@ def add_current_probe(self, circuit, name=None): # Fixme: add it to a list if self.connected: node = self._node - self._node = '_'.join((self._element.name, str(node),str(self.position) )) + self._node = '_'.join((self._element.name,str(self.position) )) if name is None: name = self._node circuit.V(name, node, self._node, '0') @@ -240,7 +240,7 @@ def add_esr(self, circuit, name= None, value=1e-3): # Fixme: add it to a list if self.connected: node = self._node - self._node = '_'.join((self._element.name, str(self._node), str(self.position))) + self._node = '_'.join((self._element.name, str(self.position))) if name is None: name = self._node return circuit.R(name, node, self._node, value) From 9dafb79501f8f89e19caa7539d9f4bfcc0a81b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 11 Mar 2025 11:07:06 -0600 Subject: [PATCH 52/87] reverting back the node name change. it does not generate unique net names --- PySpice/Spice/Element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Element.py b/PySpice/Spice/Element.py index 19805955..cd50ee56 100644 --- a/PySpice/Spice/Element.py +++ b/PySpice/Spice/Element.py @@ -221,7 +221,7 @@ def add_current_probe(self, circuit, name=None): # Fixme: add it to a list if self.connected: node = self._node - self._node = '_'.join((self._element.name,str(self.position) )) + 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') @@ -240,7 +240,7 @@ def add_esr(self, circuit, name= None, value=1e-3): # Fixme: add it to a list if self.connected: node = self._node - self._node = '_'.join((self._element.name, str(self.position))) + 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) From 4fa14c2b983d4a71885c6c3da0fd5241ade13952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 1 Apr 2025 12:13:25 -0600 Subject: [PATCH 53/87] add hyphen to the parser. sometimes there is a '-' in the t_ID of the spice. --- PySpice/Spice/Parser/Parser.py | 6 +- .../spice-library/semiconductors/NCP1117.lib | 882 ++++++++++++++++++ 2 files changed, 885 insertions(+), 3 deletions(-) create mode 100644 examples/spice-library/semiconductors/NCP1117.lib diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index d4e7ade0..da693bc2 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -231,10 +231,10 @@ def t_ID(self, t): 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_][-a-z0-9_]* # start with letter/digit/underscore, then allow hyphens and other chars + (?:\.[a-z0-9_][-a-z0-9_]*)? # optionally a dot, followed by more characters including hyphens (?:(?<=[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 + (?![a-z0-9.-]) # and not followed by letter/digit/dot/hyphen )? )''' # t.value = Id(t.value) diff --git a/examples/spice-library/semiconductors/NCP1117.lib b/examples/spice-library/semiconductors/NCP1117.lib new file mode 100644 index 00000000..543d2ca9 --- /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 +*$ From 06eb9d5ce1dd3beeac7245eabefc953f3bb0aab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 4 Apr 2025 11:34:56 -0600 Subject: [PATCH 54/87] fix: rename 'inner_libraries' to 'inner_includes' for consistency in SpiceInclude class adding files to test skywater pdk --- PySpice/Spice/Library/SpiceInclude.py | 2 +- examples/skywater/simple_nand.py | 23 +++++++++++++++++++++++ examples/spice-library/generic_format.lib | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 examples/skywater/simple_nand.py create mode 100644 examples/spice-library/generic_format.lib diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index 59b914c5..4d58bb76 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -457,7 +457,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/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py new file mode 100644 index 00000000..44b0a723 --- /dev/null +++ b/examples/skywater/simple_nand.py @@ -0,0 +1,23 @@ +from PySpice.Doc.ExampleTools import find_libraries +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +#################################################################################################### + +# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +# spice_library = SpiceLibrary(libraries_path) + +#################################################################################################### + +#?# circuit_macros('buck-converter.m4') +libraries_path = find_libraries() +spice_library = SpiceLibrary(libraries_path) + +#################################################################################################### + +#?# circuit_macros('buck-converter.m4') + +circuit = Circuit('Buck Converter') +circuit.include(spice_library['generic_comp']) + +print(circuit) \ No newline at end of file diff --git a/examples/spice-library/generic_format.lib b/examples/spice-library/generic_format.lib new file mode 100644 index 00000000..0c93d59c --- /dev/null +++ b/examples/spice-library/generic_format.lib @@ -0,0 +1,17 @@ +* generic OpAmp model +* gain, phase, offset, limits to power supply +*.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 From 009d3ee1d08b4ee9e9b6b3f3ce0944e887b265bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 4 Apr 2025 15:23:00 -0600 Subject: [PATCH 55/87] fix: convert inner includes to strings in SpiceInclude and update include paths in HighLevelParser this is a fix for the issue when there is an .include in a library. a fix in --- PySpice/Spice/Library/SpiceInclude.py | 2 +- PySpice/Spice/Parser/HighLevelParser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index 4d58bb76..706983bc 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -432,7 +432,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 diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 3777e9e5..108c9578 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -1112,7 +1112,7 @@ def append(obj: Command) -> None: append(obj) raise NotImplementedError case Include(): - self._includes.append(obj) + self._includes.append(obj.path) case Control(): state_stack.append(SpiceStates.CONTROL) control.append(obj) From e7298638d2f23b86e0dbf9f0c444f9e725a908fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 4 Apr 2025 15:49:35 -0600 Subject: [PATCH 56/87] fix: uncomment libraries_path and spice_library initialization in simple_nand.py for proper library loading --- examples/skywater/simple_nand.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py index 44b0a723..6f7ac7aa 100644 --- a/examples/skywater/simple_nand.py +++ b/examples/skywater/simple_nand.py @@ -4,14 +4,14 @@ #################################################################################################### -# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" -# spice_library = SpiceLibrary(libraries_path) +libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +spice_library = SpiceLibrary(libraries_path) #################################################################################################### -#?# circuit_macros('buck-converter.m4') -libraries_path = find_libraries() -spice_library = SpiceLibrary(libraries_path) +# #?# circuit_macros('buck-converter.m4') +# libraries_path = find_libraries() +# spice_library = SpiceLibrary(libraries_path) #################################################################################################### From ac607b3a7f7b490b1613b1624557c45ffe2fd586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 4 Apr 2025 23:14:17 -0600 Subject: [PATCH 57/87] fix: comment out unused code and update library paths in examples for clarity --- PySpice/Spice/Library/Library.py | 4 ++-- PySpice/Spice/Parser/Parser.py | 9 +++++++-- examples/skywater/simple_nand.py | 5 +++-- examples/spice-library/generic_format.lib | 7 ++++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 75252f60..afdb86c9 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -72,8 +72,8 @@ class SpiceLibrary: 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 + # if recurse: + # scan = recurse self._path = PathTools.expand_path(root_path) if not self._path.exists(): self._path.mkdir(parents=True) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index da693bc2..feb95f8f 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -167,7 +167,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,7 +175,7 @@ def t_error(self, token): t_TILDE = r'~' - t_STRING = r'"((\\")|[^"])*"' + # t_STRING = r'"((\\")|[^"])*"' t_DOT_COMMAND = r'\.(?i:[a-z]+)' @@ -237,6 +237,11 @@ def t_ID(self, t): (?![a-z0-9.-]) # and not followed by letter/digit/dot/hyphen )? )''' + # r''' + # (?i: + # [a-z_0-9]+ + # (\.[a-z_0-9.]+) ? + # )''' # t.value = Id(t.value) return t diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py index 6f7ac7aa..e016a644 100644 --- a/examples/skywater/simple_nand.py +++ b/examples/skywater/simple_nand.py @@ -4,7 +4,8 @@ #################################################################################################### -libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" spice_library = SpiceLibrary(libraries_path) #################################################################################################### @@ -18,6 +19,6 @@ #?# circuit_macros('buck-converter.m4') circuit = Circuit('Buck Converter') -circuit.include(spice_library['generic_comp']) +# circuit.include(spice_library['generic_comp']) print(circuit) \ No newline at end of file diff --git a/examples/spice-library/generic_format.lib b/examples/spice-library/generic_format.lib index 0c93d59c..bc34070f 100644 --- a/examples/spice-library/generic_format.lib +++ b/examples/spice-library/generic_format.lib @@ -1,7 +1,12 @@ * 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" +*.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 From e82ec8a148b333360ebe59514e2493129e37c114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 5 Apr 2025 09:41:49 -0600 Subject: [PATCH 58/87] fix: implement Nodes state in SpiceParser to have a more relax regex when dealing with nodes names and other names --- PySpice/Spice/Parser/Parser.py | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index feb95f8f..51861363 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' @@ -177,13 +196,14 @@ def t_error(self, token): # 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,8 +245,17 @@ def t_NUMBER(self, t): else: t.value = Number(value, unit, extra_unit) return t - def t_ID(self, t): + r''' + (?i: + [a-z_0-9]+ + (\.[a-z_0-9.]+) ? + )''' + # t.value = Id(t.value) + return t + + + def t_NODES_ID(self, t): # Fixme: r''' (?i: # case-insensitive @@ -237,14 +266,10 @@ def t_ID(self, t): (?![a-z0-9.-]) # and not followed by letter/digit/dot/hyphen )? )''' - # r''' - # (?i: - # [a-z_0-9]+ - # (\.[a-z_0-9.]+) ? - # )''' # t.value = Id(t.value) return t + ############################################## # # Grammar From 73e0479f546c3e5f031ac943198b3d92ae9fa173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 5 Apr 2025 10:22:17 -0600 Subject: [PATCH 59/87] fix: update libraries_path in simple_nand.py for correct library reference --- PySpice/Spice/Parser/Parser.py | 2 ++ examples/skywater/simple_nand.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 51861363..fa40d78e 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -246,6 +246,7 @@ def t_NUMBER(self, t): t.value = Number(value, unit, extra_unit) return t def t_ID(self, t): + # Fixme: r''' (?i: [a-z_0-9]+ @@ -266,6 +267,7 @@ def t_NODES_ID(self, t): (?![a-z0-9.-]) # and not followed by letter/digit/dot/hyphen )? )''' + # t.value = Id(t.value) return t diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py index e016a644..56a63bee 100644 --- a/examples/skywater/simple_nand.py +++ b/examples/skywater/simple_nand.py @@ -4,8 +4,8 @@ #################################################################################################### -# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" -libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" +libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +# libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" spice_library = SpiceLibrary(libraries_path) #################################################################################################### From 86bc3384ef2879b33914baad3a28280c98b41f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 6 Apr 2025 21:59:23 -0600 Subject: [PATCH 60/87] fix: added recurseive include resolving in the code --- examples/skywater/simple_nand.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py index 56a63bee..ce825419 100644 --- a/examples/skywater/simple_nand.py +++ b/examples/skywater/simple_nand.py @@ -4,9 +4,9 @@ #################################################################################################### -libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" -# libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" -spice_library = SpiceLibrary(libraries_path) +# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" +libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" +spice_library = SpiceLibrary(libraries_path, recurse=False) #################################################################################################### @@ -19,6 +19,6 @@ #?# circuit_macros('buck-converter.m4') circuit = Circuit('Buck Converter') -# circuit.include(spice_library['generic_comp']) +circuit.include(spice_library['tt']) print(circuit) \ No newline at end of file From b6efd3ab0d4bbd27b6d1749cd4dc84504b6f6728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 6 Apr 2025 21:59:43 -0600 Subject: [PATCH 61/87] fix: add recursive processing for inner includes in SpiceInclude --- PySpice/Spice/Library/Library.py | 5 ++- PySpice/Spice/Library/SpiceInclude.py | 60 ++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index afdb86c9..79ca22bd 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -82,6 +82,7 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa self._path = self._path.parent self._subcircuits = {} self._models = {} + self._recurse = recurse if not scan: if self.has_db_path: self.load() @@ -182,7 +183,7 @@ def scan(self) -> None: ############################################## def _handle_library(self, path: Path) -> None: - spice_include = SpiceInclude(path) + spice_include = SpiceInclude(path, recurse=self._recurse) # Fixme: check overwrite self._models.update({_.name: path for _ in spice_include.models}) self._subcircuits.update({_.name: path for _ in spice_include.subcircuits}) @@ -209,7 +210,7 @@ def __getitem__(self, name: str) -> Subcircuit | Model: # self._logger.warn('Library {} not found in {}'.format(name, self._path)) raise KeyError(name) # Fixme: lazy ??? - return SpiceInclude(path)[name] + return SpiceInclude(path, recurse=self._recurse)[name] ############################################## diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index 706983bc..2d9fbb79 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -289,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) -> None: self._path = Path(path) # .resolve() self._extension = None @@ -300,6 +300,7 @@ def __init__(self, path: str | Path, rewrite_yaml: bool = False) -> None: self._subcircuits = {} self._digest = None self._recursive_digest = None + self._recurse = recurse # Fixme: check still valid ! if not rewrite_yaml and self.has_yaml: @@ -308,9 +309,57 @@ 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) + # Skip if we've already processed this path + if str(path.resolve()) in processed_paths: + continue + + self._logger.info(f"Processing inner include {path}") + include = SpiceInclude(path, recurse=self._recurse) + includes.append(include) + processed_paths.add(str(path.resolve())) + + # 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}, ignoring") + 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}, ignoring") + 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 + # so they can be used for recursive digest computation + self._inner_includes = includes + # def dump(self) -> None: # print(self._path) # for _ in self._models: @@ -397,7 +446,14 @@ def parse(self) -> None: 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] + # 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) self._inner_libraries = [Path(str(_)) for _ in spice_file.libraries] for subcircuit in spice_file.subcircuits: # name = self._suffix_name(subcircuit.name) From 973f40e9d0b656601a3e64b08ec5fbad5f3e5216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sun, 6 Apr 2025 22:39:35 -0600 Subject: [PATCH 62/87] fix: enable recursive processing for SpiceLibrary in simple_nand.py --- examples/skywater/simple_nand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py index ce825419..f2e07056 100644 --- a/examples/skywater/simple_nand.py +++ b/examples/skywater/simple_nand.py @@ -6,7 +6,7 @@ # libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" -spice_library = SpiceLibrary(libraries_path, recurse=False) +spice_library = SpiceLibrary(libraries_path, recurse=True) #################################################################################################### From 52938752672de8cad69b6c2f2e1839ffd8979e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 9 Apr 2025 21:29:04 -0600 Subject: [PATCH 63/87] fix: enhance recursive include processing in SpiceLibrary and SpiceInclude --- PySpice/Spice/Library/Library.py | 55 +++++++++++++++++++++++++-- PySpice/Spice/Library/SpiceInclude.py | 22 ++++++++--- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 79ca22bd..4c261035 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -201,16 +201,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, recurse=self._recurse)[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 2d9fbb79..8bfc3ea4 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -330,26 +330,38 @@ def _process_inner_includes(self) -> None: 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.resolve()) in processed_paths: + 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.resolve())) + 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}, ignoring") + 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}, ignoring") + self._logger.warning(f"Duplicate subcircuit {subcircuit.name} from {path}, keeping original") else: self._subcircuits[subcircuit.name] = subcircuit @@ -357,7 +369,7 @@ def _process_inner_includes(self) -> None: self._logger.error(f"Failed to process inner include {path_str}: {str(e)}") # Replace the path strings with actual SpiceInclude instances - # so they can be used for recursive digest computation + # for recursive digest computation self._inner_includes = includes # def dump(self) -> None: From e098c12dce7cda6a210d6661438cb8b17b559cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 9 Apr 2025 23:14:45 -0600 Subject: [PATCH 64/87] feat: add print_subcircuit_nodes example with recursive subcircuit processing --- examples/skywater/print_subcircuit_nodes.py | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/skywater/print_subcircuit_nodes.py diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py new file mode 100644 index 00000000..977cc107 --- /dev/null +++ b/examples/skywater/print_subcircuit_nodes.py @@ -0,0 +1,53 @@ +from PySpice.Doc.ExampleTools import find_libraries +from PySpice import SpiceLibrary, Circuit, Simulator, plot +from PySpice.Unit import * + +#################################################################################################### + +# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" +libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" +spice_lib = SpiceLibrary(libraries_path, recurse=True) + +#################################################################################################### + +circuit = Circuit('Circuit with Subcircuits') + +# Print information about the library +print(f"Library loaded from: {libraries_path}") +print(f"Available subcircuits in library: {list(spice_lib.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 the circuit +print("\nCircuit:") +print(circuit) \ No newline at end of file From 502893b419453a1dad642a4b0517a886cb239c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 9 Apr 2025 23:15:46 -0600 Subject: [PATCH 65/87] fix: update path handling in SpiceLibrary and SpiceInclude for loading recursive include and complicated spice files. --- PySpice/Spice/Library/Library.py | 4 ++-- PySpice/Spice/Library/SpiceInclude.py | 14 ++++++++++++-- PySpice/Spice/Parser/HighLevelParser.py | 2 ++ examples/skywater/simple_nand.py | 24 ------------------------ 4 files changed, 16 insertions(+), 28 deletions(-) delete mode 100644 examples/skywater/simple_nand.py diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 4c261035..b86e84a5 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -185,8 +185,8 @@ def scan(self) -> None: def _handle_library(self, path: Path) -> None: spice_include = SpiceInclude(path, recurse=self._recurse) # 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}) ############################################## diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index 8bfc3ea4..ab13aac8 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -458,6 +458,7 @@ def parse(self) -> None: 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}") + # Convert include paths to absolute paths if they are relative self._inner_includes = [] for include_path in spice_file.includes: @@ -466,7 +467,16 @@ def parse(self) -> None: if not path.is_absolute(): path = self._path.parent / path self._inner_includes.append(path) - self._inner_libraries = [Path(str(_)) for _ in spice_file.libraries] + + # 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) @@ -490,7 +500,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, diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 108c9578..0ffcf611 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -1113,6 +1113,8 @@ def append(obj: Command) -> None: raise NotImplementedError case Include(): self._includes.append(obj.path) + case Library(): + self._libs.append(obj) case Control(): state_stack.append(SpiceStates.CONTROL) control.append(obj) diff --git a/examples/skywater/simple_nand.py b/examples/skywater/simple_nand.py deleted file mode 100644 index f2e07056..00000000 --- a/examples/skywater/simple_nand.py +++ /dev/null @@ -1,24 +0,0 @@ -from PySpice.Doc.ExampleTools import find_libraries -from PySpice import SpiceLibrary, Circuit, Simulator, plot -from PySpice.Unit import * - -#################################################################################################### - -# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" -libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" -spice_library = SpiceLibrary(libraries_path, recurse=True) - -#################################################################################################### - -# #?# circuit_macros('buck-converter.m4') -# libraries_path = find_libraries() -# spice_library = SpiceLibrary(libraries_path) - -#################################################################################################### - -#?# circuit_macros('buck-converter.m4') - -circuit = Circuit('Buck Converter') -circuit.include(spice_library['tt']) - -print(circuit) \ No newline at end of file From 1b11dd9e4631225e8f4bb0e548c0cac70ca31c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Thu, 10 Apr 2025 11:28:01 -0600 Subject: [PATCH 66/87] fix: update library path in print_subcircuit_nodes example for handling files and folder in the spice_library path handling. --- PySpice/Spice/Library/Library.py | 25 +++++++++++++++++---- examples/skywater/print_subcircuit_nodes.py | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index b86e84a5..07823586 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -78,8 +78,8 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa 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 + # elif self._path.is_file(): + # self._path = self._path.parent self._subcircuits = {} self._models = {} self._recurse = recurse @@ -97,6 +97,9 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa @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 @@ -171,14 +174,28 @@ 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: try: self._handle_library(path) - except: - self._logger.warning(f"Failed to parse {path}") + except Exception as e: + self._logger.warning(f"Failed to parse {path}: {e}") ############################################## diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py index 977cc107..203925e1 100644 --- a/examples/skywater/print_subcircuit_nodes.py +++ b/examples/skywater/print_subcircuit_nodes.py @@ -5,7 +5,7 @@ #################################################################################################### # libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" -libraries_path = "/home/asepahvand/repos/spice_libraries/generic_format.lib" +libraries_path = "/home/asepahvand/repos/spice_libraries/" spice_lib = SpiceLibrary(libraries_path, recurse=True) #################################################################################################### From c1cb050d71fe7824984f96e6fa5a6c12c35aa38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 11 Apr 2025 09:01:29 -0600 Subject: [PATCH 67/87] fix: update regex in t_NODES_ID for improved node ID parsing and comment out unused p_id_colon method. it was causing issues when reading skywater files. :0 was matching id. it should have not --- PySpice/Spice/Parser/Parser.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index fa40d78e..9e319386 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -259,15 +259,15 @@ def t_ID(self, t): def t_NODES_ID(self, t): # Fixme: r''' - (?i: # case-insensitive - (?:[+\-\%](?=[a-z_]))? # optional +, - or % at the start, only if followed by letter/underscore - [a-z0-9_][-a-z0-9_]* # start with letter/digit/underscore, then allow hyphens and other chars - (?:\.[a-z0-9_][-a-z0-9_]*)? # optionally a dot, followed by more characters including hyphens - (?:(?<=[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/hyphen - )? + (?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 @@ -344,12 +344,13 @@ def p_id(self, p): # -------------------------------------------------------------------------------------- # 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_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_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''' From 32d7d4f4743a7c8072170ca140acaef2c934a9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 11 Apr 2025 09:05:42 -0600 Subject: [PATCH 68/87] fix: update libraries_path in print_subcircuit_nodes example and enhance output for available subcircuits --- PySpice/Spice/Parser/Parser.py | 2 - examples/skywater/print_subcircuit_nodes.py | 64 +++++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/PySpice/Spice/Parser/Parser.py b/PySpice/Spice/Parser/Parser.py index 9e319386..6478eaa3 100644 --- a/PySpice/Spice/Parser/Parser.py +++ b/PySpice/Spice/Parser/Parser.py @@ -266,8 +266,6 @@ def t_NODES_ID(self, t): (?:(?<=[a-z0-9])[+\-](?![a-z0-9.-]))? :? )''' - - # t.value = Id(t.value) return t diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py index 203925e1..8399faeb 100644 --- a/examples/skywater/print_subcircuit_nodes.py +++ b/examples/skywater/print_subcircuit_nodes.py @@ -4,50 +4,52 @@ #################################################################################################### -# libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" -libraries_path = "/home/asepahvand/repos/spice_libraries/" +libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" +# libraries_path = '/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/cells/res_high_po/sky130_fd_pr__res_high_po.model.spice' +# libraries_path = "/home/asepahvand/repos/spice_libraries/" spice_lib = SpiceLibrary(libraries_path, recurse=True) #################################################################################################### circuit = Circuit('Circuit with Subcircuits') -# Print information about the library -print(f"Library loaded from: {libraries_path}") -print(f"Available subcircuits in library: {list(spice_lib.subcircuits)}") -# Safely iterate through subcircuits with error handling -print("\nSubcircuit details:") -for subcirc in spice_lib.subcircuits: - try: - print(f"\nProcessing subcircuit: {subcirc}") +# # 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}") +# # 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}") +# # 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") +# # 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)}") +# # 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}") +# 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 From 0fafd6dd1965c2a84a2cececd36d69b6aabf2f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 11 Apr 2025 13:17:23 -0600 Subject: [PATCH 69/87] fix: add section parameter to SpiceLibrary and SpiceInclude for improved library handling in parsing --- PySpice/Spice/Library/Library.py | 3 ++- PySpice/Spice/Library/SpiceInclude.py | 5 +++-- PySpice/Spice/Parser/HighLevelParser.py | 13 ++++++++++++- examples/skywater/print_subcircuit_nodes.py | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 07823586..8d67fb3b 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -83,6 +83,7 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa self._subcircuits = {} self._models = {} self._recurse = recurse + self._section = section if not scan: if self.has_db_path: self.load() @@ -200,7 +201,7 @@ def scan(self) -> None: ############################################## def _handle_library(self, path: Path) -> None: - spice_include = SpiceInclude(path, recurse=self._recurse) + spice_include = SpiceInclude(path, recurse=self._recurse, section=self._section) # Fixme: check overwrite self._models.update({_.name: str(_.path) for _ in spice_include.models}) self._subcircuits.update({_.name: str(_.path) for _ in spice_include.subcircuits}) diff --git a/PySpice/Spice/Library/SpiceInclude.py b/PySpice/Spice/Library/SpiceInclude.py index ab13aac8..b67ca0b5 100644 --- a/PySpice/Spice/Library/SpiceInclude.py +++ b/PySpice/Spice/Library/SpiceInclude.py @@ -289,7 +289,7 @@ class SpiceInclude: ############################################## - def __init__(self, path: str | Path, rewrite_yaml: bool = False, recurse: 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 @@ -301,6 +301,7 @@ def __init__(self, path: str | Path, rewrite_yaml: bool = False, recurse: bool = 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: @@ -454,7 +455,7 @@ 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}") diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 0ffcf611..68b3445f 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -1021,6 +1021,8 @@ def read(self, generator: Generator[tuple[int, str], None, None], title_line: bo self.reset() last_line = None last_command = None + we_are_in_lib = False + libname = None for line_number, line in generator: # print(f'>>>{line_number}///{line.rstrip()}') ### line = line.strip() @@ -1055,6 +1057,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 @@ -1278,7 +1288,8 @@ class SpiceFile(SpiceSource): ############################################## - def __init__(self, path: str | Path) -> None: + def __init__(self, path: str | Path, section: str) -> None: super().__init__() self._path = Path(path) + self._section = section self.parse_file(path) diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py index 8399faeb..ba0cf8da 100644 --- a/examples/skywater/print_subcircuit_nodes.py +++ b/examples/skywater/print_subcircuit_nodes.py @@ -4,10 +4,10 @@ #################################################################################################### -libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib_custom.spice" +libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" # libraries_path = '/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/cells/res_high_po/sky130_fd_pr__res_high_po.model.spice' # libraries_path = "/home/asepahvand/repos/spice_libraries/" -spice_lib = SpiceLibrary(libraries_path, recurse=True) +spice_lib = SpiceLibrary(libraries_path, recurse=True, section="tt") #################################################################################################### From b661275fecfc865c08dc117a5cd04577d0053751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Fri, 11 Apr 2025 22:37:27 -0600 Subject: [PATCH 70/87] fix: update parser import paths and enhance parser tests for unit test --- unit-test/Spice/test_Expression.py | 86 +++++++++++------------ unit-test/Spice/test_Netlist.py | 2 +- unit-test/SpiceParser/test_SpiceParser.py | 2 +- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/unit-test/Spice/test_Expression.py b/unit-test/Spice/test_Expression.py index bd76a03f..659f039f 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 132fc2e9..8dc32b9b 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 295a6acd..db94ad09 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 #################################################################################################### From 3cddb5027f6368c98fd7e2500d5a0eed94482e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 12 Apr 2025 15:57:07 -0600 Subject: [PATCH 71/87] fix: extend path handling in Circuit class to support Library.Model instances --- PySpice/Spice/Netlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/Netlist.py b/PySpice/Spice/Netlist.py index 5a420d0c..e9f42da3 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: From dbedc972e38cedf466f7d5d31fd616d84aa83183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 12 Apr 2025 17:43:51 -0600 Subject: [PATCH 72/87] fix: enhance example runner to support command line arguments and improve error handling --- examples/run-examples | 74 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/examples/run-examples b/examples/run-examples index 47b42935..780432b5 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) From e94976f67a6b95422f532a9797b34f5b98015b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 12 Apr 2025 17:44:18 -0600 Subject: [PATCH 73/87] fix: update examples to run with all the new updates --- PySpice/Spice/Parser/HighLevelParser.py | 3 ++- examples/diode/diode-characteristic-curve.py | 6 +++--- examples/diode/zener-characteristic-curve.py | 2 +- examples/kicadrw/dump-netlist.py | 1 - .../OperationalAmplifier-api-brainstorming.py | 17 +++++++++-------- examples/persistance/SimulateCircuit.py | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 68b3445f..334f738c 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -996,6 +996,7 @@ def reset(self) -> None: self._subcircuits = [] self._circuit = Netlist() self._control = [] + self._section = None ############################################## @@ -1288,7 +1289,7 @@ class SpiceFile(SpiceSource): ############################################## - def __init__(self, path: str | Path, section: str) -> None: + def __init__(self, path: str | Path, section: str=None) -> None: super().__init__() self._path = Path(path) self._section = section diff --git a/examples/diode/diode-characteristic-curve.py b/examples/diode/diode-characteristic-curve.py index 23d7f6a8..a66524fa 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 9ea1820a..6ebd073e 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 1f4eddd9..736a61db 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 31a9c6eb..e82b8472 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 772c460a..da44d50e 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 #################################################################################################### From 0e0ebfeaee4c9eb0da0f491ac733504eca472912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Sat, 12 Apr 2025 20:03:24 -0600 Subject: [PATCH 74/87] fix: add path check for subcircuits in SpiceLibrary when loading a db.pickle. sometimes the db is there and the newly added or serached library is not in it. --- PySpice/Spice/Library/Library.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PySpice/Spice/Library/Library.py b/PySpice/Spice/Library/Library.py index 8d67fb3b..bd86d1ed 100644 --- a/PySpice/Spice/Library/Library.py +++ b/PySpice/Spice/Library/Library.py @@ -87,6 +87,10 @@ def __init__(self, root_path: str | Path, scan: bool = False, recurse: bool = Fa 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 From bcb0ef1fc366c5d9774b4358c6416cac39c74087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Tue, 15 Apr 2025 08:49:36 -0600 Subject: [PATCH 75/87] fix: update libraries_path to use relative path for better portability --- examples/skywater/print_subcircuit_nodes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py index ba0cf8da..5ec45913 100644 --- a/examples/skywater/print_subcircuit_nodes.py +++ b/examples/skywater/print_subcircuit_nodes.py @@ -4,9 +4,8 @@ #################################################################################################### -libraries_path = "/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" -# libraries_path = '/home/asepahvand/repos/skywater-pdk/libraries/sky130_fd_pr/latest/cells/res_high_po/sky130_fd_pr__res_high_po.model.spice' -# libraries_path = "/home/asepahvand/repos/spice_libraries/" +libraries_path = "../../../skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" + spice_lib = SpiceLibrary(libraries_path, recurse=True, section="tt") #################################################################################################### From b6b6d4d773e827057cedf751744f19d2f790c7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 13:22:53 -0600 Subject: [PATCH 76/87] feat: add example for ParallelResistor2 subcircuit with logging and validation --- examples/basic-usages/subcircuit2.py | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/basic-usages/subcircuit2.py diff --git a/examples/basic-usages/subcircuit2.py b/examples/basic-usages/subcircuit2.py new file mode 100644 index 00000000..a66b78b1 --- /dev/null +++ b/examples/basic-usages/subcircuit2.py @@ -0,0 +1,33 @@ + +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.Translator import Builder +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.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) +circuit_builder = Builder() +parser = SpiceSource(source=source) +bootstrap_circuit = circuit_builder.translate(spice_code=parser) +bootstrap_source = str(bootstrap_circuit) + +print(bootstrap_source) + +assert bootstrap_source == source From 6a8a45c2080ed6cc3da52aa0e4ef35a40d4778c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 13:31:38 -0600 Subject: [PATCH 77/87] fix: update Builder class to handle spice_code.title correctly and improve subcircuit handling --- PySpice/Spice/Parser/Translator.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 58eb77db..6b0d44a5 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -72,7 +72,12 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER Use the *ground* parameter to specify the node which must be translated to 0 (SPICE ground node). """ - self._circuit = Circuit(spice_code.title) + # Remove '.title' from spice_code.title if present + if spice_code.title and spice_code.title.startswith('.title'): + _ = spice_code.title[6:] + else: + _ = spice_code.title + self._circuit = Circuit(_) self._ground = ground for obj in spice_code.obj_lines: @@ -151,7 +156,8 @@ def handle_EndIf(self, obj: EndIf) -> None: ############################################## def handle_EndSubcircuit(self, obj: EndSubcircuit) -> None: - raise NotImplementedError + # raise NotImplementedError + pass ############################################## @@ -262,7 +268,10 @@ 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) + for element in obj._items: + subcircuit._add_element(element) + # SpiceParser._build_circuit(subcircuit, obj._statements, ground) + self._circuit.subcircuit(subcircuit) return subcircuit ############################################## From b065bf4c2602dac3b8f7d44e89b824a4f21bbcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 15:24:38 -0600 Subject: [PATCH 78/87] fix: improve handling of subcircuits in Builder and update example for clarity --- PySpice/Spice/Parser/Translator.py | 18 ++++++++++++------ examples/basic-usages/subcircuit2.py | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 6b0d44a5..982caa8e 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -74,13 +74,14 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER """ # Remove '.title' from spice_code.title if present if spice_code.title and spice_code.title.startswith('.title'): - _ = spice_code.title[6:] + _ = str(spice_code.title[6:]).strip() else: _ = spice_code.title self._circuit = Circuit(_) self._ground = ground - - for obj in spice_code.obj_lines: + for obj in spice_code.circuit: + self.handle(obj) + for obj in spice_code._subcircuits: self.handle(obj) return self._circuit @@ -268,9 +269,14 @@ 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) - for element in obj._items: - subcircuit._add_element(element) - # 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/examples/basic-usages/subcircuit2.py b/examples/basic-usages/subcircuit2.py index a66b78b1..30f456ca 100644 --- a/examples/basic-usages/subcircuit2.py +++ b/examples/basic-usages/subcircuit2.py @@ -17,14 +17,17 @@ def __init__(self, name, R1=1@u_Ω, R2=2@u_Ω): 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) -circuit_builder = Builder() +print(circuit) + parser = SpiceSource(source=source) +circuit_builder = Builder() bootstrap_circuit = circuit_builder.translate(spice_code=parser) bootstrap_source = str(bootstrap_circuit) From 1df99ded776d3d0fd06667cd1792eab6fec9f341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 15:50:21 -0600 Subject: [PATCH 79/87] fix: add section parameter to read and parse_file methods in SpiceSource for improved functionality --- PySpice/Spice/Parser/HighLevelParser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 334f738c..8500b09b 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -1015,7 +1015,7 @@ 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. """ @@ -1023,6 +1023,7 @@ def read(self, generator: Generator[tuple[int, str], None, None], title_line: bo 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()}') @@ -1177,10 +1178,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() ############################################## @@ -1292,5 +1293,4 @@ class SpiceFile(SpiceSource): def __init__(self, path: str | Path, section: str=None) -> None: super().__init__() self._path = Path(path) - self._section = section - self.parse_file(path) + self.parse_file(path, section) From 2381f7935b861a09c886431928d26bdbb3938e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 15:50:35 -0600 Subject: [PATCH 80/87] fix: update libraries_path to use a more appropriate relative path for improved accessibility --- examples/skywater/print_subcircuit_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/skywater/print_subcircuit_nodes.py b/examples/skywater/print_subcircuit_nodes.py index 5ec45913..00bd6300 100644 --- a/examples/skywater/print_subcircuit_nodes.py +++ b/examples/skywater/print_subcircuit_nodes.py @@ -4,7 +4,7 @@ #################################################################################################### -libraries_path = "../../../skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" +libraries_path = "../skywater-pdk/libraries/sky130_fd_pr/latest/models/sky130.lib.spice" spice_lib = SpiceLibrary(libraries_path, recurse=True, section="tt") From a4c2c4659dcec44c26e38484f4ae39e8516d5ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 16:26:54 -0600 Subject: [PATCH 81/87] feat: add example for parsing subcircuits with ParallelResistor2 class --- .../spice-parser/parse-subcircuit-example.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/spice-parser/parse-subcircuit-example.py diff --git a/examples/spice-parser/parse-subcircuit-example.py b/examples/spice-parser/parse-subcircuit-example.py new file mode 100644 index 00000000..15d578f3 --- /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 From 6b3409c02e35783abed772c788dc82feef7b4d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 16:27:00 -0600 Subject: [PATCH 82/87] fix: enhance Builder class to handle includes and models in translation process --- PySpice/Spice/Parser/Translator.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 982caa8e..c287bc7e 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -79,8 +79,17 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER _ = spice_code.title self._circuit = Circuit(_) self._ground = ground + + # for obj in spice_code.obj_lines: + # self.handle(obj) + for obj in spice_code._titles: + self.handle(obj) for obj in spice_code.circuit: self.handle(obj) + for obj in spice_code.includes: + self.handle_Include(obj) + for obj in spice_code.models: + self.handle(obj) for obj in spice_code._subcircuits: self.handle(obj) @@ -183,7 +192,7 @@ def handle_If(self, obj: If) -> None: ############################################## def handle_Include(self, obj: Include) -> None: - self._circuit.include(obj.path) + self._circuit.include(obj) ############################################## From 1568de41a9f5b2c869d5718e112b4509af71ca3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 16:27:22 -0600 Subject: [PATCH 83/87] refactor: renamed subcircuit2 --- examples/basic-usages/subcircuit2.py | 36 ---------------------------- 1 file changed, 36 deletions(-) delete mode 100644 examples/basic-usages/subcircuit2.py diff --git a/examples/basic-usages/subcircuit2.py b/examples/basic-usages/subcircuit2.py deleted file mode 100644 index 30f456ca..00000000 --- a/examples/basic-usages/subcircuit2.py +++ /dev/null @@ -1,36 +0,0 @@ - -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.Translator import Builder -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) - -parser = SpiceSource(source=source) -circuit_builder = Builder() -bootstrap_circuit = circuit_builder.translate(spice_code=parser) -bootstrap_source = str(bootstrap_circuit) - -print(bootstrap_source) - -assert bootstrap_source == source From 81dd5abfd4eb0b254ee71c1140c082044c58e0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 18:14:11 -0600 Subject: [PATCH 84/87] fix: prevent appending objects in subcircuit state and streamline object handling in Builder class --- PySpice/Spice/Parser/HighLevelParser.py | 5 ++++- PySpice/Spice/Parser/Translator.py | 12 +----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/PySpice/Spice/Parser/HighLevelParser.py b/PySpice/Spice/Parser/HighLevelParser.py index 8500b09b..cdd33a09 100644 --- a/PySpice/Spice/Parser/HighLevelParser.py +++ b/PySpice/Spice/Parser/HighLevelParser.py @@ -1116,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(): diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index c287bc7e..28d387bf 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -80,17 +80,7 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER self._circuit = Circuit(_) self._ground = ground - # for obj in spice_code.obj_lines: - # self.handle(obj) - for obj in spice_code._titles: - self.handle(obj) - for obj in spice_code.circuit: - self.handle(obj) - for obj in spice_code.includes: - self.handle_Include(obj) - for obj in spice_code.models: - self.handle(obj) - for obj in spice_code._subcircuits: + for obj in spice_code.obj_lines: self.handle(obj) return self._circuit From bee8a0b2d0170d6d2410f78d3c311c6b6618fe99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 21:12:02 -0600 Subject: [PATCH 85/87] fix: update handle_Include method to use obj.path for circuit inclusion --- PySpice/Spice/Parser/Translator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 28d387bf..772e8f9a 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -182,7 +182,7 @@ def handle_If(self, obj: If) -> None: ############################################## def handle_Include(self, obj: Include) -> None: - self._circuit.include(obj) + self._circuit.include(obj.path) ############################################## From 392afb0430d8ddf01e3bf53d3bd0f5a7e3cda3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Wed, 16 Apr 2025 21:19:04 -0600 Subject: [PATCH 86/87] reverting back some of the changes made in the previous commit --- PySpice/Spice/Parser/Translator.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 772e8f9a..2ccbfa51 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -72,14 +72,8 @@ def translate(self, spice_code:SpiceSource, ground: int=Node.SPICE_GROUND_NUMBER Use the *ground* parameter to specify the node which must be translated to 0 (SPICE ground node). """ - # Remove '.title' from spice_code.title if present - if spice_code.title and spice_code.title.startswith('.title'): - _ = str(spice_code.title[6:]).strip() - else: - _ = spice_code.title - self._circuit = Circuit(_) + self._circuit = Circuit(spice_code.title) self._ground = ground - for obj in spice_code.obj_lines: self.handle(obj) @@ -156,8 +150,8 @@ def handle_EndIf(self, obj: EndIf) -> None: ############################################## def handle_EndSubcircuit(self, obj: EndSubcircuit) -> None: - # raise NotImplementedError - pass + raise NotImplementedError + # pass ############################################## From f951d0ea604efc2c23ec1b6ddf4415099b088b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAlihossein?= Date: Thu, 17 Apr 2025 15:37:19 -0600 Subject: [PATCH 87/87] feat: add raw_spice_parser example for translating SPICE code --- PySpice/Spice/Parser/Translator.py | 1 + examples/spice-parser/raw_spice_parser.py | 84 +++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 examples/spice-parser/raw_spice_parser.py diff --git a/PySpice/Spice/Parser/Translator.py b/PySpice/Spice/Parser/Translator.py index 2ccbfa51..c02e2a6c 100644 --- a/PySpice/Spice/Parser/Translator.py +++ b/PySpice/Spice/Parser/Translator.py @@ -74,6 +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) diff --git a/examples/spice-parser/raw_spice_parser.py b/examples/spice-parser/raw_spice_parser.py new file mode 100644 index 00000000..2343bbc7 --- /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