Profile
Back to NewsBack
GitHub Trending 32 min
Reader Mode
nunobrum/spicelib: Python library to interact with spice simulators such as LTSpice, QSPICE, NGSpice and others.

nunobrum/spicelib: Python library to interact with spice simulators such as LTSpice, QSPICE, NGSpice and others.

6 hours ago

README

_current version: 1.6.4_

spicelib is a toolchain of python utilities design to interact with spice simulators, as for example:

  • LTspice
  • Ngspice
  • QSPICE
  • Xyce
The Original Spice language in its beginning had as targets circuits with a small number of components and had scarce computing power available. Fast-forward to today, a modern SPICE user needs a solution that allows: * Exploiting all the processor cores available on the local machine; * Keep simulation files small, because the bigger the raw files slow the simulation considerably; * Have no limitations on the number of ".STEP" dimensions; * Make advanced calculations on simulation files that surpass the capabilities of .MEAS primitives; * Correlate information from different simulation runs; * Execute simulations on several machines at the same time; * Have a timeout and kill stalled simulation processes and * Integrate simulations with advanced mathematical and machine learning algorithms. The spicelib and its front end libraries (PyLTspice and Qspice) try to address all these points using python scripting language.

-- Nuno Brum, creator of this library.

[Ad] For finding the best values for passive components I've created the WizEIA Calculator. Try it. It's awesome.

Table of Contents

- Main Tools - Main Classes - Updating spicelib - Using GitHub - SpiceEditor, AscEditor, QschEditor and SimRunner - Overview - Integration of the simulators - LTspice - Ngspice - QSPICE - Xyce - Other simulators - Simulators and the Executable paths - Simulator Runner log redirection - Symbol and Library paths - SpiceEditor: Limitations and specifics - AscEditor: Limitations and specifics - Hierarchical circuits: reading and editing - RawRead - RawWrite - SimStepper - Simulation Analysis Toolkit - ltsteps - ltsteps - histogram - raw\_convert - rawplot - run\_server - asc\_to\_qsch - log\\semi\_dev\_op\_reader.opLogReader - Single Module Logging

What is contained in this repository

Main Tools

  • __Reading Simulation Wave Files__
A python class that reads .raw files generated by LTSpice, NGSpice, QSPICE and other Spice like simulators that are inspired on the original Spice .raw format from Berkeley university. See RawRead
  • __Netlist Editors__
Python classes that can read and edit Spice netlists.

This allows the user to update component values, simulation primitives and parameters. It is also possible to directly edit LTspice or Qspice schematics.

  • __Simulation Batching__
Python classes that allows to launch simulations in parallel, thus better exploiting the available computing power.
  • __Analysis Toolkit__
A set of tools that prepare an LTspice netlist for a montecarlo or Worst Case Analysis. The device tolerances are set by the user and the netlist is updated accordingly. The netlist can then be used with the SimRunner to run a batch of simulations or with the LTspice GUI.
  • __ltsteps__
A command line tool that extracts from LTspice output files data, and formats it for import in a spreadsheet, such like Excel or Calc.
  • __histogram__
A command line tool that uses numpy and matplotlib to create a histogram and calculate the sigma deviations. This is useful for Monte-Carlo analysis.
  • __asc_to_qsch__
A command line tool that converts LTspice schematic format [.asc] into Qspice schematic format [.qsch]

(Note that in Windows operating system the command line has the extension '.exe'.)

Main Classes

  • __AscEditor/QschEditor/SpiceEditor__
Classes for the manipulation of respectively:

* LTspice .asc files * QSPICE .qsch files * SPICE netlists (from no matter what simulator)

without having to open the schematic in a GUI. The simulations can then be run in batch mode (see SimRunner). Examples of functions provided:

from spicelib.editor import SpiceEditor

netlist = SpiceEditor("example.net") netlist.set_element_model('D1', '1N4148') # Replaces the Diode D1 with the model 1N4148 netlist.set_component_value('R2', '33k') # Replaces the value of R2 by 33k netlist['R2'].value = 33000 # Same as above netlist.set_component_value('V1', '5') # Replaces the value of V1 by 5 netlist['V1'].value = 5 # Same as above netlist.set_parameters(run=1, TEMP=80) # Creates or updates the netlist to have .PARAM run=1 or .PARAM TEMP=80 netlist.add_instructions(".STEP run -1 1023 1", ".dc V1 -5 5") netlist.remove_instruction(".STEP run -1 1023 1") # Removes previously added instruction netlist.reset_netlist() # Resets all edits done to the netlist. netlist.set_component_parameters('R1', temp=25, pwr=None) # Sets or removes additional parameters netlist['R1'].set_params(temp=25, pwr=None) # Same as above

The two equivalent instructions below manipulate X1 instance of a subcircuit.

netlist.get_subcircuit('X1').set_component_parameters('R1', temp=25, pwr=None) # Sets or removes on a component netlist['X1:R1'].params = dict(temp=25, pwr=None) # Same as above

the instructions below update a subcircuit, which will impact all its instances

subc = netlist.get_subcircuit_named("MYSUBCKT") subc.set_component_parameters('R1', 'R1', temp=25, pwr=None) # sets temp to 25 and removes pwr subc['R1'].params = dict(temp=25, pwr=None) # same as the above instruction

The next two equivalent instructions set the R1 value on .SUBCKT MYSUBCKT R1 to 1k

subc.set_component_value('R1', 1000) subc['R1'].value = 1000 # Same as the above

  • __SimRunner__
A class that can be used to run LTspice/QSPICE/Ngspice/Xyce simulations in batch mode without having to open the corresponding GUI. This, in cooperation with the above-mentioned xxxEditor classes, is useful because:

* It can overcome the limitation of only stepping 3 parameters * Different types of simulations .TRAN .AC .NOISE can be run in a single batch * The RAW Files are smaller and easier to handle * When used with RawRead and ltsteps, validation of the circuit can be done automatically * Different models can be simulated in a single batch

  • __RawRead__
A class that serves to read raw files into a python class. Multiple result sets (also called plots) in one raw file are supported.
  • __RawWrite__
A class to write RAW files that can be read by the LTspice Wave Application or other comparable tools.

How to Install

pip install spicelib

Updating spicelib

pip install --upgrade spicelib

Using GitHub

git clone https://github.com/nunobrum/spicelib.git

If using this method it would be good to add the path where you cloned the site to python path.

import sys sys.path.append()

How to use

Here follows a quick outlook on how to use each of the tools.

More comprehensive documentation can be found in

LICENSE

GNU V3 License (refer to the LICENSE file)

Main modules

SpiceEditor, AscEditor, QschEditor and SimRunner

Overview

These modules are used to prepare and launch SPICE simulations.

The editors can be used change component values, parameters or simulation commands. After the simulation is run, the results then can be processed with either the RawRead or with the LTSpiceLogReader module to read the log file which can contain .MEAS results.

Here follows an example of operation.

from spicelib import SimRunner
from spicelib import SpiceEditor

from spicelib.simulators.ltspice_simulator import LTspice

select spice model

runner = SimRunner(simulator=LTspice, output_folder='./temp_runner') netlist = SpiceEditor('./testfiles/Batch_Test.net')

set default arguments

netlist.set_parameters(res=0, cap=100e-6) netlist['R2'].value = '2k' # Modifying the value of a resistor netlist.set_component_value('R1', '4k') # Alternative way of modifying the value of a resistor.

Set component temperature, Tc 50ppm, remove power rating :

netlist.set_component_parameters('R1', temp=100, tc=0.000050, pwr=None) netlist['R1'].set_params(temp=100, tc=0.000050, pwr=None) # Alternative way of setting parameters. Same as the above.

Modifying the behavior of the voltage source

netlist.set_element_model('V3', "SINE(0 1 3k 0 0 0)") netlist['V3'].model = "SINE(0 1 3k 0 0 0)" # Alternative way of modifying the behaviour. Same as the above. netlist.set_component_value('XU1:C2', 20e-12) # modifying a component in the subcircuit XU1 instance netlist.get_subcircuit_named('AD820_ALT')['C13'].value = '2p' # This changes the value of C13 inside the subcircuit AD820.

Applies to all instances of the subcircuit

netlist.add_instructions( "; Simulation settings", ";.param run = 0" ) netlist.set_parameter('run', 0) alt_solver = True for opamp in ('AD712', 'AD820_ALT'): # When updating an instance, the instance name gets appended to the subcircuit netlist['XU1'].model = opamp # or netlist.set_element_model('XU1', opamp) for supply_voltage in (5, 10, 15): netlist['V1'].value = supply_voltage netlist['V2'].value = -supply_voltage print("simulating OpAmp", opamp, "Voltage", supply_voltage)

# small example on how to use options, here how to force the solver opts = [] if alt_solver: opts.append('-alt') else: opts.append('-norm')

runner.run(netlist, switches=opts, exe_log=True) # run, and log console output fo file for raw, log in runner: print(f"Raw file: {raw}, Log file: {log}") # do something with the data # raw_data = RawRead(raw) # log_data = LTSpiceLogReader(log) # ...

Sim Statistics

print(f'Successful/Total Simulations: {runner.okSim}/{runner.runno}')

enter = input("Press enter to delete created files") if enter == '': runner.cleanup_files()

-- in examples/sim_runner_example.py

The example above is using the SpiceEditor to modify a spice netlist, but it is also possible to use the AscEditor to directly modify a .asc file. The edited .asc file can be opened by the LTspice GUI and the simulation can be run from there. It is also possible to open a .asc file and to generate a spice netlist from it.

Integration of the simulators

##### LTspice

LTspice runs under Windows and macOS, and can also run on macOS and linux via wine.

The LTspice class tries to detect the correct path of the LTspice installation depending on the platform. On Linux it expects LTspice to be installed under wine. On macOS, it first looks for LTspice installed under wine, and when it cannot be found, it will look for native LTspice. The reason is that the command line interface of the native LTspice is severely limited.

If you use LTspice, please make sure that you have installed the libraries via Settings: Operation tab, Model Update button.

##### Ngspice

Ngspice runs natively under Windows, Linux and macOS (via brew).

The NGspiceSimulator class works with Ngspice CLI, it cannot (yet) work with the shared library version of Ngspice that is delivered with for example KiCad, you will need to install the CLI version. You can however use KiCad as the schematic editor and subsequently save the Ngspice netlist to use it with this library.

The NGspiceSimulator class tries to detect the correct executable path, no matter the platform.

Please note that Ngspice does not support the .step command, so you would need to use SimStepper, or a .control section in your netlist.

Ngspice .control sections can be manipulated with the SpiceEditor class.

If you use Ngspice .control sections, know that there are some limitations:

  • you cannot use interactive/GUI elements like plot.
  • you must do your own writing to the raw file, using the write command (without parameters), or the write $rawfile [...] command. $rawfile will be filled by Ngspice, from the -r command line parameter that spicelib will provide.
  • you must also add quit at the end of the control section (This is not specific to spicelib, it is a consequence of the Ngspice's batch mode and .control sections not really collaborating).
If you want to create something similar to the missing .step directive, you can use loops and set appendwrite inside a .control section to create multiple plots in the same raw file. RawRead knows how to read this, but you will need to use raw.plots[step].get_wave('name') to access the different plots, instead of raw.get_wave('name', step), which would be needed with LTspice. See examples/testfiles/ngsteps.net and examples/ngsteps.py for an example of how to do this and how to edit .control sections.

If you read binary RAW files generated by old versions of Ngspice (before 44), you need to specify 'ngspice' as dialect for RawRead. Earlier versions of Ngspice didn't declare in the RAW file its name and Ngspice format is slightly different from the other simulators.

##### QSPICE

QSPICE only runs under Windows. It is not compatible with macOS nor Linux, and does not run under wine (although various efforts have been made to make it compatible).

The Qspice class tries to automatically detect the correct executable path.

##### Xyce

Xyce runs natively under Windows, Linux and macOS.

The XyceSimulator class tries to automatically detect the correct executable path, but it may require manual configuration in some cases.

If you read binary RAW files generated by xyce, you may need to specify 'xyce' as dialect for RawRead. Xyce format is slightly different from the other simulators and Xyce doesn't declare its name in the created the raw file. Work is ongoing in Xyce development to improve this.

##### Other simulators

Although spicelib does not have runners for other simulators than those mentioned above, it is relatively easy to support for more.

SpiceEditor should natively support editing of netlists for other simulators as well.

##### Simulators and the Executable paths

A large variety of standard paths are automatically detected. To see what paths are detected:

from spicelib.sim.sim_runner import SimRunner
from spicelib.simulators.ltspice_simulator import LTspice

runner = SimRunner(output_folder='./tmp', simulator=LTspice)

Show the executable path

print(runner.simulator.spice_exe) print(runner.simulator.process_name)

Show the default library paths of that simulator. This is deduced from spice_exe

print(runner.simulator.get_default_library_paths())

If you want, you can set your own executable paths, via the two variables shown above:

  • spice_exe: a list of with the commands that invoke the simulator. Do not include command line options to the
simulator here.
  • process_name: the process name as visible to the OS.
You can also use simulator.create_from().

Example:

# ** Simulator executable paths
from spicelib.simulators.ltspice_simulator import LTspice
from spicelib.sim.sim_runner import SimRunner
from spicelib.editor.asc_editor import AscEditor

OPTION 1: via subclassing

class MySpiceInstallation(LTspice): spice_exe = ['wine', '/custompath/LTspice.exe'] process_name = 'wine'

runner = SimRunner(output_folder='./tmp', simulator=MySpiceInstallation)

OPTION 2: or via direct creation. If you do not specify the process_name,

it will be guessed via simulator.guess_process_name().

runner = SimRunner(output_folder='./tmp', simulator=LTspice.create_from('wine /custompath/LTspice.exe') )

##### Simulator Runner log redirection

When you use wine (on Linux or macOS) or a simulator like Ngspice, or if you run simultaneous simulators, you may want to redirect the output of run() or run_now() or create_netlist(), as it prints a lot of console messages without much value. Real time redirecting to the logger is unfortunately not easy, especially with the simultaneous runner. You can redirect the output for example with:

# force command console output to a separate file. 

The filename is like the netlist file, but with extension ".exe.log"

runner.run(netlist, exe_log=True)

This is supported on both the SimRunner and directly on the various simulators (LTspice, ...). The runner client server function (see SimClient) does not (yet) support this, but it is less bothersome there.

Symbol and Library paths

The library paths are needed for the editor. However, the default library paths depend on the simulator used, its installation path, and if that simulator runs under wine or not. The function editor.prepare_for_simulator() allows you to tell the editor what simulator is used, and its library paths. This not always needed however:

  • AscEditor and SpiceEditor presume that LTspice is used.
  • QschEditor presumes that QSPICE is used.
This will of course not work out if you use the editors on other simulators (as can be the case with SpiceEditor), or if you have manually set the simulator's executable path. In those cases you will want to inform your editor of that change via editor.prepare_for_simulator().

In some cases you need to reference libraries or symbols that are not included in the standard library paths, for example when sharing non-native libraries and symbols between different projects. The spicelib supports this feature by using the set_custom_library_paths() class method.

Example:

from spicelib.simulators.ltspice_simulator import LTspice
from spicelib.editor.asc_editor import AscEditor

** Editor library paths

Example with an LTspice installation on a non-standard path

class MySimulator(LTspice): spice_exe = ['wine', '/custompath/LTspice.exe'] process_name = 'wine'

In case of non standard paths, or if you use another simulator than ltspice, it is preferred to

inform your editor of it, so it can better guess the library paths.

AscEditor.prepare_for_simulator(MySimulator)

** Editor custom search paths

You can also add your own library paths to the search paths

AscEditor.set_custom_library_paths("/mypath/lib/sub", "/mypath/lib/sym", "/mypath/lib/sym/OpAmps", "/mypath/lib/cmp")

The user can specify one or more search paths. Note that each call to this method will invalidate previously set search paths. Also, note that this is a class method in all available editors (SpiceEditor, AscEditor and QschEditor), this means that updating one instantiation, will update all other instances of the same class.

SpiceEditor: Limitations and specifics

Not all elements support value editing or parameter editing, and not all elements are supported by all Spice variants.

| Type | Description | Form | Value editing | Parameter editing | |:-----------------------:|:------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------:|:-----------------:| | A | Special Functions | | no | no | | B | Arbitrary Behavioral Voltage or Current Sources | Bxxx n+ n- (i\|v\|r\|p)=value [parmkey=parmvalue]... | yes (4) | yes | | C | Capacitor | Cxxx n1 n2 value [parmkey=parmvalue]... | yes (1)(5) | yes | | D | Diode | Dxxx anode cathode value [parmkey=parmvalue]... | holds model | yes (2) | | E | Voltage Dependent Voltage Source | Exxx n+ n- [nc+ nc-] value... | (6) | not separately | | F | Current Dependent Current Source | Fxxx n+ n- value... | includes parameters | not separately | | G | Voltage Dependent Current Source | Gxxx n+ n- [nc+ nc-] value... | (6) | not separately | | H | Current Dependent Voltage Source | Hxxx n+ n- value... | includes parameters | not separately | | I | Current Source | Ixxx n+ n- value [parmkey=parmvalue]... | yes, can be value or expression | yes | | J | JFET | Jxxx n+ n- value [parmkey=parmvalue]... | holds model | yes (2) | | K | Mutual Inductance | Kxxx L1 L2 [L3 ...] value | yes | no | | L | Inductor | Lxxx n1 n2 value [parmkey=parmvalue]... | yes (1) | yes | | M | MOSFET | Mxxx Nd Ng Ns [Nb] value [parmkey=parmvalue]... | holds model | yes (2) | | N
(ngspice) | Verilog-A Compact Device | Nxxx n1 n2...nX model [parmkey=parmvalue]... | holds model | yes | | O | Lossy Transmission Line | Oxxx L+ L- R+ R- value [parmkey=parmvalue]... | holds model | yes | | P
(ngspice) | Coupled Multiconductor Line | Pxxx NI1 NI2...NIX GND1 NO1 NO2...NOX GND2 value [parmkey=parmvalue]... | holds model | yes | | P
(xyce) | Port Device | Pxxx NI1 NI2 value [parmkey=parmvalue]... | value (3) | yes | | Q | Bipolar Transistor | Qxxx Collector Base Emitter [Substrate] [Junction] value [parmkey=parmvalue]... | holds model (2) | yes | | R | Resistor | Rxxx n1 n2 value [parmkey=parmvalue]... | yes (1) | yes | | S | Voltage Controlled Switch | Sxxx n1 n2 nc+ nc- value [on\|off] | holds model and state | no | | T | Lossless Transmission Line | Txxx L+ L- R+ R- [parmkey=parmvalue]... | no | yes | | U
(ltspice, ngspice) | Uniform RC-line | Uxxx n1 n2 ncom value... | includes parameters | not separately | | U
(xyce) | Digital Devices | Uxxx type (2..99 nodes) value [parmkey=parmvalue]... | (6) | not separately | | V | Voltage Source | Vxxx n+ n- value [parmkey=parmvalue]... | yes, can be value or expression | yes | | W | Current Controlled Switch | Wxxx n1 n2 Vref value [on\|off] | holds model and state | no | | X | Subcircuit | Xxxx n1 n2 n3... value [parmkey=parmvalue]... | holds subcircuit name | yes | | Y
(ngspice) | Single Lossy Transmission Line | Yxxx n1 n2 n3 n4 value [parmkey=parmvalue]... | holds model | yes (2) | | Y
(qspice) | Piezoelectric Crystal | Yxxx n+ n- value [parmkey=parmvalue]... | holds frequency | yes | | Y
(xyce) | various, deprecated | | no | no | | Z | MESFET, IGBT | Zxxx Nd Ng Ns value [parmkey=parmvalue]... | holds model | yes (2) | | Ã
(qspice) | MultGmAmp, RRopAmp | Ãxxx (16 nodes) value [parmkey=parmvalue]... | holds model | yes | | ¥
(qspice) | various | ¥xxx (16 nodes) value [parmkey=parmvalue]... | holds model | yes | | €
(qspice) | DAC | €xxx (32 nodes) value [parmkey=parmvalue]... | holds model | yes | | £
(qspice) | Dual Gate Driver | £xxx (64 nodes) value [parmkey=parmvalue]... | holds model | yes | | Ø
(qspice) | DLL | Øxxx «(1..99 input nodes)» «(0..99 output nodes)» «(0..99 common nodes)» value [TYPE parmkey=parmvalue [...]] [parmkey=parmvalue]... (7) | holds model | yes (7) | | ×
(qspice) | Transformer | ×xxx «(4..100 nodes)» [parmkey=parmvalue]... | no | yes | | Ö
(ltspice) | Specialised OTA | Öxxx (1..99 nodes) value [parmkey=parmvalue]... | yes | yes |


Notes:

For all parameters, composite parameter values (like ic=vbe, vce or turns=1 .5 .5 .5) are allowed, except for V and I.

  1. Can hold either the value, model name, or a formula. Formulas must be enclosed by "" or '' or {} or contain no spaces.
  2. There is no proper individual support for area, on, off or thermal if they are not part of a key-value pair.
  3. The format specification is [[DC] ], but the parser only supports 1 value. So value must be specified, and DC will be ignored, if present.
  4. Can be a value or a formula. Formulas with embedded = signs are not supported, use < or >.
  5. Charge formulated expressions (Q=...) are not supported.
  6. Includes everything after first 2 nodes.
  7. Several issues:
* When using save_netlist(), you must specify the pin configuration of the Ø component, as spicelib is not equipped to read that configuration from the DLL. See the documentation of QschEditor.save_netlist(). * Editing of parameters is limited for now. You cannot edit the TYPE parmkey=parmvalue parameters (TYPE is int, uint, float, ...), except for the value of the last one, and only if it has a unique key. You can however edit all parameters that are not related to a TYPE. * When doing simulations, make sure that the simulator can find the DLL: place it in the same directory as the netlist, or in the simulation output directory, depending on how the simulator is called.

For a detailed reference to the elements, see amongst others:

AscEditor: Limitations and specifics

AscEditor has some limitations and differences in regard to SpiceEditor.

  • As is visible in the LTspice GUI, it groups all component properties/parameters in different 'attributes' like '
Value', 'Value2', 'SpiceLine', 'SpiceLine2'. Netlists do not have that concept, and place everything in one big list, that SpiceEditor subsequently separates in 'value' and 'parameters' for most components. To complicate things, LTspice distributes the parameters over all 4 attributes, with varying syntax. You must be aware of how LTspice handles the parameter placement if you use AscEditor.

AscEditor.get_component_parameters() will show the native attributes, and tries to disect 'SpiceLine' and 'SpiceLine2', just like SpiceEditor.get_component_parameters() would do. This means for example for a Voltage source of DC 2V, with small signal analysis AC amplitude of 1V and a series resistance of 3 ohm: * AscEditor.get_component_value() and SpiceEditor.get_component_value() -> '2 AC 1' * AscEditor.get_component_parameters() -> {'Value': '2', 'Value2': 'AC 1', 'SpiceLine': 'Rser=3', 'Rser': 3} * SpiceEditor.get_component_parameters() -> {'Rser': 3} * Please note that if you want to remove the small signal analysis AC amplitude, you MUST use * AscEditor.set_component_parameters(..,'Value2',''), as set_component_value() will only affect 'Value' * SpiceEditor.set_component_value(..,'2') * with both editors, you can use ...set_component_parameters(.., Rser=5)

  • When adressing components, SpiceEditor requires you to include the prefix in the component name, like XU1 for an
OpAmp. AscEditor will require U1.
  • AscEditor and SpiceEditor only work with the information in their respective schema/circuit files. The problem is that
LTspice does not store any of the underlying symbol's default parameter values in the .asc files. SpiceEditor works on netlists, and netlists do contain all parameters.

This can affect the behaviour when using symbols like OpAmps/UniversalOpAmp2. Although the LTspice GUI shows the parameters like Avol, GBW and Vos, even when they have the default values, AscEditor.get_component_parameters() will not return these parameters unless they have been modified. SpiceEditor.get_component_parameters() on the contrary will show all parameters, regardless of if they were modified. It is however possible for AscEditor to set or modify the parameters with AscEditor.set_component_parameters(). Example: set_component_parameters("U1", Value2="Avol=2Meg GBW=10Meg Slew=10Meg").

Note here that you must know the correct attribute holding that parameter, and make sure that you know and set all the other parameters in that attribute. If the attribute is in 'SpiceLine' however (as with the majority of the simpler components), you may address the parameter individually (see the voltage source example above).

Resumed, it is better to use SpiceEditor than AscEditor, as it is more straightforward. On macOS, it is recommended to use LTspice under wine, or to export the netlist manually, as macOS's LTspice does not support automated export of netlists.

Hierarchical circuits: reading and editing

  • Circuits can refer to other circuits (subcircuits) and to components, be it from other circuit or netlist files, or
from libraries.
  • Subcircuits can contain other subcircuits
  • Internal components in components/subcircuits that are loaded from libraries can be read, but not modified.
Examples:

Imagine a top circuit that refers to a subcircuit 'X1' that is not in a library, but in a separate '.asc' or '.net' file (depending on your editor). That subcircuit has a compoment 'L1'.

The following is all possible:

import spicelib

my_edt = spicelib.AscEditor("top_circuit.asc")

my_edt = spicelib.SpiceEditor("top_circuit.net") # or from a netlist...

print(my_edt.get_subcircuit("X1").get_components()) # prints ['C1', 'X2', 'L1']

The following are equivalent:

l1_value0 = my_edt.get_component_value("X1:L1") l1_value1 = my_edt.get_subcircuit("X1").get_component_value("L1") l1_value2 = my_edt["X1:L1"].value

Likewise, the following are equivalent:

Note that this will not work if the component X1 is from a library. See note 3 below.

my_edt.set_component_value("X1:L1", 2e-6) # sets L1 in X1 instance to 2uH my_edt["X1:L1"].value = 2e-6 # Same as the instruction above

Likewise, for accessing parameters the following are equivalent:

x1_c1_prms0 = my_edt.get_subcircuit("X1").get_component_parameters('C1') x1_c1_prms1 = my_edt["X1:C1"].params

Likewise, the following are equivalent:

Note that this will not work if the component X1 is from a library. See note 3 below.

my_edt.get_subcircuit("X1").set_component_parameters("C1", Rser=1) my_edt["X1:C1"].set_params(Rser=1) my_edt["X1:C1"].params = dict(Rser=1)

The same goes for SpiceEditor, only that you should use 'XX1' instead of 'X1'

*NOTE 1: The code above sets only the instance of a subcircuit. A copy of it is done prior to making edits. To update all instances of a subcircuit, the subcircuit needs to be manipulated directly, as is done below.*

NOTE 2: This implementation changes on the AscEditor and QschEditor.

*NOTE 3: You cannot modify values or parameters of components/subcircuits from a library. An exception will occur in that case. If you want to modify, you should therefore include the component/subcircuit in your file. It may be best to rename that subcircuit, since ltspice 24+ will not allow a 'local' subcircuit and a lib to refer to the same subcircuit name. You can only avoid renaming it if you no longer use the subcircuit under its original name. Know that executing any of the 'write' commands creates a new subcircuit under a new name, called {subcircuit_model_name}_{component_name}, like AD820_X1, and sets the model of X1 to AD820_X1.*

import spicelib

my_edt = spicelib.SpiceEditor("top_circuit.net") my_sub = my_edt.get_subcircuit_named("MYSUBCKT")

print(my_sub.get_components()) # prints ['C1', 'X2', 'L1']

The following are equivalent:

l1_value0 = my_sub.get_component_value("L1") l1_value1 = my_sub["L1"].value

Note that this will not work if the component X1 is from a library. An exception will occur in that case.

my_sub.set_component_value("L1", 2e-6) # sets L1 in X1 instance to 2uH my_sub["L1"].value = 2e-6 # Same as the instructionn above

Likewise, for accessing parameters the following are equivalent:

c1_value0 = my_sub.get_component_parameters('C1') c1_value1 = my_sub["C1"].params

Likewise, the following are equivalent:

Note that this will not work if the component X1 is from a library. An exception will occur in that case.

my_sub.set_component_parameters("C1", Rser=1) my_sub["C1"].set_params(Rser=1) my_sub["C1"].params = dict(Rser=1)

RawRead

The example below reads the data from a Spice Simulation called "TRAN - STEP.raw" and displays all steps of the "I(R1)" trace in a matplotlib plot

from spicelib import RawRead

from matplotlib import pyplot as plt

read a raw file that has only 1 data set/plot in it, but has multiple steps

rawfile = RawRead("./testfiles/TRAN - STEP.raw")

print(rawfile.get_trace_names()) print(rawfile.get_raw_property())

IR1 = rawfile.get_trace("I(R1)") x = rawfile.get_trace('time') # Gets the time axis steps = rawfile.get_steps() for step in range(len(steps)): # print(steps[step]) plt.plot(x.get_wave(step), IR1.get_wave(step), label=steps[step])

plt.legend() # order a legend plt.show()

read a raw file that has multiple data sets/plots in it

raw = RawRead("./testfiles/noise_multi.bin.raw") print(raw.get_plot_names()) # names of all the plots in the file print(raw.get_trace_names()) # names of all the traces of the first plot in the file print(raw.plots[0].get_trace_names()) # same as above print(raw.plots[1].get_trace_names()) # names of all the traces of the second plot in the file

x = raw.get_trace('frequency') # could have used raw.get_axis() as well here y = raw.get_trace('onoise_spectrum') plt.plot(x.get_wave(), y.get_wave(), label='noise spectrum') plt.xlabel('Frequency (Hz)') plt.ylabel('Noise (V/√Hz)') plt.yscale('log') plt.xscale('log') plt.legend() plt.show()

and get the integrated noise from the second part in the file

total = raw.plots[1].get_trace('v(onoise_total)') print(f"Total Integral noise: {total.get_wave()[0]} V")

-- in examples/raw_read_example.py

RawWrite

The following example writes a RAW file with a 3 milliseconds transient simulation sine with a 10kHz and a cosine with 9.997kHz

import numpy as np
from spicelib import Trace, RawWrite
LW = RawWrite(fastacces=False)
tx = Trace('time', np.arange(0.0, 3e-3, 997E-11))
vy = Trace('N001', np.sin(2  np.pi  tx.data * 10000))
vz = Trace('N002', np.cos(2  np.pi  tx.data * 9970))
LW.add_trace(tx)
LW.add_trace(vy)
LW.add_trace(vz)
LW.save("./testfiles/teste_snippet1.raw")

-- in examples/raw_write_example.py [Example 1]

SimStepper

To avoid having loops inside loops spicelib can handle the work of making multidimensional sweeps using the SimStepper class. The code in the previous section can be writen as shown here.

import os

from spicelib import SpiceEditor, SimRunner from spicelib.simulators.ltspice_simulator import LTspice from spicelib.sim.sim_stepping import SimStepper

def processing_data(raw_file, log_file): print("Handling the simulation data of %s" % log_file)

runner = SimRunner(parallel_sims=4, output_folder='./temp2', simulator=LTspice)

select spice model

Stepper = SimStepper(SpiceEditor("./testfiles/Batch_Test.net"), runner)

set default arguments

Stepper.set_parameters(res=0, cap=100e-6) Stepper.set_component_value('R2', '2k') Stepper.set_component_value('R1', '4k') Stepper.set_element_model('V3', "SINE(0 1 3k 0 0 0)")

define simulation

Stepper.add_instructions( "; Simulation settings", ";.param run = 0", ".lib ADI1.lib", # This is needed for accessing AD712 ) Stepper.set_parameter('run', 0) Stepper.set_parameter('test_param2', 20) Stepper.add_model_sweep('XU1', ('AD712', 'AD820_ALT')) Stepper.add_value_sweep('V1', (5, 10, 15))

Stepper.add_value_sweep('V1', (-5, -10, -15))

run_netlist_file = "run_OPAMP_{XU1}_VDD_{V1}.net" Stepper.run_all(callback=processing_data, filenamer=run_netlist_file.format)

Sim Statistics

print(f'Successful/Total Simulations: {Stepper.okSim}/{Stepper.runno}') Stepper.export_step_info("./temp2/export.csv") runner.cleanup_files()

-- in examples/sim_stepper_example.py

The SimStepper methods

* add_value_sweep(ref, iterable) * add_model_sweep(ref, iterable) * add_param_sweep(name, iterable)

receive as first argument the component or parameter reference as the first argument and an iterable object such as a list or a generator as a second argument.

When the run_all() method is called, it will make run a simulation per each combination of values. On the example above it will make the simulations:

(XU1, V1) in (("AD712", 5), ("AD712", 10), ("AD712", 15), ("AD820_ALT", 5), ("AD820_ALT", 10), ("AD820_ALT", 15))

It should be noted that for each sweep method added it will add a new dimension simulation space. In other words, the total number of simulations will be the product of each vector length. There is no restriction to the number of simulations to be done, however, a huge number of simulation will take a long time to execute and may occupy a considerable amount of space on the disk.

Simulation Analysis Toolkit

The AscEditor can be used with the Simulation Analysis Toolkit to perform Monte Carlo or Wost Case simulations. These simulations can either be done on the LTSpice GUI or using the Runner Class described above.

Let's consider the following circuit:

!Sallen-Key Amplifier

When performing a Monte Carlo simulation on this circuit, we need to manually modify the value of each component, and then add the .step command for making several runs on the same circuit. To simplify this process, the AscEditor class can be used as exemplified below:

from spicelib import AscEditor, SimRunner  # Imports the class that manipulates the asc file
from spicelib.sim.tookit.montecarlo import Montecarlo  # Imports the Montecarlo toolkit class
from spicelib.simulators.ltspice_simulator import LTspice

sallenkey = AscEditor("./testfiles/sallenkey.asc") # Reads the asc file into memory runner = SimRunner(simulator=LTspice, output_folder='./temp_mc', verbose=True) # Instantiates the runner with a temp folder set mc = Montecarlo(sallenkey, runner) # Instantiates the Montecarlo class, with the asc file already in memory

The following lines set the default tolerances for the components

mc.set_tolerance('R', 0.01) # 1% tolerance, default distribution is uniform mc.set_tolerance('C', 0.1, distribution='uniform') # 10% tolerance, explicit uniform distribution mc.set_tolerance('V', 0.1, distribution='normal') # 10% tolerance, but using a normal distribution

Some components can have a different tolerance

mc.set_tolerance('R1', 0.05) # 5% tolerance for R1 only. This only overrides the default tolerance for R1

Tolerances can be set for parameters as well

mc.set_parameter_deviation('Vos', 3e-4, 5e-3, 'uniform') # The keyword 'distribution' is optional mc.prepare_testbench(num_runs=1000) # Prepares the testbench for 1000 simulations

manually_simulating_in_LTspice = False

if manually_simulating_in_LTspice: # Finally the netlist is saved to a file. This file contains all the instructions to run the simulation in LTspice mc.save_netlist('./testfiles/temp/sallenkey_mc.asc')

-- in examples/run_montecarlo.py [Example 1]

When opening the created sallenkey_mc.net file, we can see that the following circuit.

!Sallen-Key Amplifier with Montecarlo

The following updates were made to the circuit:

  • The value of each component was replaced by a function that generates a random value within the specified tolerance.
  • The .step param run command was added to the netlist. Starts at -1 which it's the nominal value simulation, and
finishes that the number of simulations specified in the prepare_testbench() method.
  • A default value for the run parameter was added. This is useful if the .step param run is commented out.
  • The R1 tolerance is different from the other resistors. This is because the tolerance was explicitly set for R1.
  • The Vos parameter was added to the .param list. This is because the parameter was explicitly set using the
set_parameter_deviation method.
  • Functions utol, ntol and urng were added to the .func list. These functions are used to generate random values.
Uniform distributions use the LTSpice built-in mc(x, tol) and flat(x) functions, while normal distributions use the gauss(x) function.

Similarly, the worst case analysis can also be setup by using the class WorstCaseAnalysis, as exemplified below:

import logging

import spicelib from spicelib import AscEditor, SimRunner # Imports the class that manipulates the asc file from spicelib.sim.tookit.worst_case import WorstCaseAnalysis from spicelib.simulators.ltspice_simulator import LTspice

spicelib.set_log_level(logging.INFO)

sallenkey = AscEditor("./testfiles/sallenkey.asc") # Reads the asc file into memory runner = SimRunner(simulator=LTspice, output_folder='./temp_wca', verbose=True) # Instantiates the runner with a temp folder set wca = WorstCaseAnalysis(sallenkey, runner) # Instantiates the Worst Case Analysis class

The following lines set the default tolerances for the components

wca.set_tolerance('R', 0.01) # 1% tolerance wca.set_tolerance('C', 0.1) # 10% tolerance

wca.set_tolerance('V', 0.1) # 10% tolerance. For Worst Case analysis, the distribution is irrelevant

wca.set_tolerance('I', 0.1) # 10% tolerance. For Worst Case analysis, the distribution is irrelevant

Some components can have a different tolerance

wca.set_tolerance('R1', 0.05) # 5% tolerance for R1 only. This only overrides the default tolerance for R1 wca.set_tolerance('R4', 0.0) # 5% tolerance for R1 only. This only overrides the default tolerance for R1

Tolerances can be set for parameters as well.

wca.set_parameter_deviation('Vos', 3e-4, 5e-3)

Finally the netlist is saved to a file

wca.save_netlist('./testfiles/sallenkey_wc.asc')

-- in examples/run_worst_case.py [Example 1]

When opening the created sallenkey_wc.net file, we can see that the following circuit.

!Sallen-Key Amplifier with WCA

The following updates were made to the circuit:

  • The value of each component was replaced by a function that generates a nominal, minimum and maximum value depending
on the run parameter and is assigned a unique index number. (R1=0, Vos=1, R2=2, ... V2=7, VIN=8) The unique number corresponds to the bit position of the run parameter. Bit 0 corresponds to the minimum value and bit 1 corresponds to the maximum value. Calculating all possible permutations of maximum and minimum values for each component, we get 2**9 = 512 possible combinations. This maps into a 9 bit binary number, which is the

... (README truncated for length)

Chat with me