In [1]:
from IPython.core.display import HTML
HTML(open('custom.css', 'r').read())
Out[1]:

Spicing It Up! (Sorry)

SKiDL is a Python package for describing the interconnection of electronic devices using text (instead of schematics). PySpice is an interface for controlling an external SPICE circuit simulator from Python. This document demonstrates how circuits described using SKiDL can be simulated under a variety of conditions using PySpice with the results displayed in an easily-shared Jupyter notebook.

SKiDL + PyCircuit + ngspice + matplotlib data flow.

Why?

There are existing SPICE simulators that analyze schematics created by CAD packages like KiCad. There are also versions with their own GUI like LTSpice. What advantages does a combination of SKiDL, PySpice and ngspice offer?

  • The circuit description is completely textual, so it's easy to share with others who may not have a schematic editor or GUI.
  • It can be archived in a Git repository for the purpose of tracking versions as modifications are made.
  • The documentation of the circuitry is embedded with the circuitry, so it's more likely to be kept current.
  • It makes the entire Python ecosystem of tools available for optimizing, analyzing, and visualizing the behavior of a circuit under a variety of conditions.

Installation

For Ubuntu linux, first install ngspice:

sudo apt-get update
sudo apt-get install ngspice

If you're using Windows, download ngspice and copy the Spice directory from the archive over to C:\Program Files.

Next, for either OS, install SKiDL:

pip install skidl

After that, you'll have to manually install PySpice (it must be version 1.2.0 or higher):

pip install "PySpice>=1.2.0"

If the 1.2.0 version is not available on PyPi, you'll have to install the development version like this:

pip install git+https://github.com/FabriceSalvaire/PySpice

Examples

The following examples demonstrate some of the ways of using SKiDL and PySpice to simulate electronics. While shown using the Jupyter notebook, these examples will also work by placing the Python code into a file and executing it with a Python interpreter.

The following code snippet is needed at the beginning of every example. It loads the matplotlib package for generating graphs, and SKiDL + PySpice packages for describing and simulating circuitry.

In [2]:
# Load the package for drawing graphs.
import matplotlib.pyplot as plt
# Omit the following line if you're not using a Jupyter notebook.
%matplotlib inline

# Load the SKiDL + PySpice packages and initialize them for doing circuit simulations.
from skidl.pyspice import *

Current Through a Resistor

The following example connects a 1 K$\Omega$ resistor to a voltage source whose value is ramped from 0 to 1 volts. The current through the resistor is plotted versus the applied voltage.

In [3]:
reset()  # This will clear any previously defined circuitry.

# Create and interconnect the components.
vs = V(ref='VS', dc_value = 1 @ u_V)  # Create a voltage source named "VS" with an initial value of 1 volt.
r1 = R(value = 1 @ u_kOhm)            # Create a 1 Kohm resistor.
vs['p'] += r1[1]       # Connect one end of the resistor to the positive terminal of the voltage source.
gnd += vs['n'], r1[2]  # Connect the other end of the resistor and the negative terminal of the source to ground.

# Simulate the circuit.
circ = generate_netlist()              # Translate the SKiDL code into a PyCircuit Circuit object.
sim = circ.simulator()                 # Create a simulator for the Circuit object.
dc_vals = sim.dc(VS=slice(0, 1, 0.1))  # Run a DC simulation where the voltage ramps from 0 to 1V by 0.1V increments.

# Get the voltage applied to the resistor and the current coming out of the voltage source.
#voltage = dc_vals[node(vs['p'])].as_ndarray()  # Get the voltage applied by the positive terminal of the source.
#current = -dc_vals['VS'].as_ndarray() # Get the current coming out of the positive terminal of the voltage source.
voltage = dc_vals[node(vs['p'])]       # Get the voltage applied by the positive terminal of the source.
current = -dc_vals['VS']               # Get the current coming out of the positive terminal of the voltage source.

# Print a table showing the current through the resistor for the various applied voltages.
print('{:^7s}{:^7s}'.format('V', ' I (mA)'))
print('='*15)
for v, i in zip(voltage.as_ndarray(), current.as_ndarray()*1000):
    print('{:6.2f} {:6.2f}'.format(v, i))

# Create a plot of the current (Y coord) versus the applied voltage (X coord).
figure = plt.figure(1)
plt.title('Resistor Current vs. Applied Voltage')
plt.xlabel('Voltage (V)')
plt.ylabel('Current (mA)')
plt.plot(voltage, current*1000) # Plot X=voltage and Y=current (in milliamps, so multiply it by 1000).
plt.show()
No errors or warnings found during netlist generation.

Note: can't find init file.
   V    I (mA)
===============
  0.00  -0.00
  0.10   0.10
  0.20   0.20
  0.30   0.30
  0.40   0.40
  0.50   0.50
  0.60   0.60
  0.70   0.70
  0.80   0.80
  0.90   0.90
  1.00   1.00

Transient Response of an R-C Filter

This example shows the time-varying voltage of a capacitor charged through a resistor by a pulsed voltage source.

In [4]:
reset()  # Clear out the existing circuitry from the previous example.

# Create a pulsed voltage source, a resistor, and a capacitor.
vs = PULSEV(initial_value=0, pulsed_value=5@u_V, pulse_width=1@u_ms, period=2@u_ms)  # 1ms ON, 1ms OFF pulses.
r = R(value=1@u_kOhm)    # 1 Kohm resistor.
c = C(value=1@u_uF)      # 1 uF capacitor.
r['+', '-'] += vs['p'], c['+']  # Connect the resistor between the positive source terminal and one of the capacitor terminals.
gnd += vs['n'], c['-']     # Connect the negative battery terminal and the other capacitor terminal to ground.

# Simulate the circuit.
circ = generate_netlist()            # Create the PySpice Circuit object from the SKiDL code.
sim = circ.simulator()               # Get a simulator for the Circuit object.
waveforms = sim.transient(step_time=0.01@u_ms, end_time=10@u_ms)  # Run a transient simulation from 0 to 10 msec.

# Get the simulation data.
time = waveforms.time                # Time values for each point on the waveforms.
pulses = waveforms[node(vs['p'])]    # Voltage on the positive terminal of the pulsed voltage source.
cap_voltage = waveforms[node(c['+'])]  # Voltage on the capacitor.

# Plot the pulsed source and capacitor voltage values versus time.
figure = plt.figure(1)
plt.title('Capacitor Voltage vs. Source Pulses')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.plot(time*1000, pulses)       # Plot pulsed source waveform.
plt.plot(time*1000, cap_voltage)  # Plot capacitor charging waveform.
plt.legend(('Source Pulses', 'Capacitor Voltage'), loc=(1.1, 0.5))
plt.show()
No errors or warnings found during netlist generation.

Warning: v1: no DC value, transient time 0 value used

A Voltage-Controlled Voltage Source

A voltage source whose output is controlled by another voltage source is demonstrated in this example.

In [5]:
reset()  # Clear out the existing circuitry from the previous example.

# Connect a sine wave to the control input of a voltage-controlled voltage source.
vs = SINEV(amplitude=1@u_V, frequency=100@u_Hz)  # 1V sine wave source at 100 Hz.
vs['n'] += gnd  # Connect the negative terminal of the sine wave to ground.
vc = VCVS(gain=2.5)  # Voltage-controlled voltage source with a gain of 2.5.
vc['ip', 'in'] += vs['p'], gnd  # Connect the sine wave to the input port of the controlled source.
vc['op', 'on'] += Net(), gnd    # Connect the output port of the controlled source to a net and ground.
rl = R(value=1@u_kOhm)
rl[1,2] += vc['op'], gnd
r = R(value=1@u_kOhm)
r[1,2] += vs['p'], gnd

# Simulate the circuit.
circ = generate_netlist()  # Create the PySpice Circuit object from the SKiDL code.
print(circ)
sim = circ.simulator()  # Get a simulator for the Circuit object.
waveforms = sim.transient(step_time=0.01@u_ms, end_time=20@u_ms) # Run a transient simulation from 0 to 20 msec.

# Get the time-varying waveforms of the sine wave source and the voltage-controlled source.
time = waveforms.time
vin = waveforms[node(vs['p'])]
vout = waveforms[node(vc['op'])]

# Plot the input and output waveforms. Note that the output voltage is 2.5x the input voltage.
figure = plt.figure(1)
plt.title('Input and Output Sine Waves')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.plot(time*1000, vin)
plt.plot(time*1000, vout)
plt.legend(('Input Voltage', 'Output Voltage'), loc=(1.1, 0.5))
plt.show()
.title 
E1 N2 0 N1 0 2.5
R1 N2 0 1kOhm
R2 N1 0 1kOhm
V1 N1 0 DC 0V AC SIN(0V 1V 100Hz 0s 0Hz)

No errors or warnings found during netlist generation.

A Current-Controlled Current Source

This example shows a current source controlled by the current driven through a resistor by a voltage source.

In [6]:
reset()  # Clear out the existing circuitry from the previous example.

# Use the current driven through a resistor to control another current source.
vs = SINEV(amplitude=1@u_V, frequency=100@u_Hz) # 100 Hz sine wave voltage source.
rs = R(value=1@u_kOhm)  # Resistor connected to the voltage source.
rs[1,2] += vs['p'], gnd  # Connect resistor from positive terminal of voltage source to ground.
vs['n'] += gnd  # Connect the negative terminal of the voltage source to ground.
vc = CCCS(control=vs, gain=2.5)  # Current source controlled by the current entering the vs voltage source.
rc = R(value=1@u_Ohm)  # Resistor connected to the current source.
rc[1,2] += vc['p'], gnd  # Connect resistor from the positive terminal of the current source to ground.
vc['n'] += gnd  # Connect the negative terminal of the current source to ground.

# Simulate the circuit.
circ = generate_netlist()
sim = circ.simulator()
waveforms = sim.transient(step_time=0.01@u_ms, end_time=20@u_ms)

# Get the time-varying waveforms of the currents from the sine wave source and the current-controlled current source.
time = waveforms.time
i_vs = waveforms[vs.ref]
i_vc = waveforms[node(rc[1])] / rc.value  # Current-source current is the voltage across the resistor / resistance.

# Plot the waveforms. Note the input and output currents are out of phase since the output current is calculated
# based on the current *leaving* the positive terminal of the controlled current source and entering the resistor, 
# whereas the current in the controlling voltage source is calculated based on what is *entering* the positive terminal.
figure = plt.figure(1)
plt.title('Control Current vs. Output Current')
plt.xlabel('Time (ms)')
plt.ylabel('Current (mA)')
plt.plot(time*1000, i_vs)
plt.plot(time*1000, i_vc)
plt.legend(('Control Current', 'Output Current'), loc=(1.1, 0.5))
plt.show()
No errors or warnings found during netlist generation.

A Transmission Line

The voltages at the beginning and end of an ideal transmission line are shown in this example.

In [7]:
reset()  # Clear out the existing circuitry from the previous example.

# Create a 1 GHz sine wave source, drive it through a 70 ohm ideal transmission line, and terminate it with a 140 ohm resistor.
vs = SINEV(amplitude=1@u_V, frequency=1@u_GHz)
t1 = T(impedance=70@u_Ohm, frequency=1@u_GHz, normalized_length=10.0) # Trans. line is 10 wavelengths long.
rload = R(value=140@u_Ohm)
vs['p'] += t1['ip']  # Connect source to positive input terminal of trans. line.
rload[1] += t1['op'] # Connect resistor to positive output terminal of trans. line.
gnd += vs['n'], t1['in','on'], rload[2] # Connect remaining terminals to ground.

# Simulate the transmission line.
circ = generate_netlist()
sim = circ.simulator()
waveforms = sim.transient(step_time=0.01@u_ns, end_time=20@u_ns)

# Get the waveforms at the beginning and end of the trans. line.
time = waveforms.time * 10**9
vin = waveforms[node(vs['p'])]      # Input voltage at the beginning of the trans. line.
vout = waveforms[node(rload['1'])]  # Output voltage at the terminating resistor of the trans. line.

# Plot the input and output waveforms. Note that it takes 10 nsec for the input to reach the end of the
# transmission line, and there is a 33% "bump" in the output voltage due to the mismatch between the
# 140 ohm load resistor and the 70 ohm transmission line impedances.
figure = plt.figure(1)
plt.title('Output Voltage vs. Input Voltage')
plt.xlabel('Time (ns)')
plt.ylabel('Voltage (V)')
plt.plot(time, vin)
plt.plot(time, vout)
plt.legend(('Input Voltage', 'Output Voltage'), loc=(1.1, 0.5))
plt.show()
No errors or warnings found during netlist generation.

A Transistor Amplifier

The use of SPICE models is demonstrated in this example of a common-emitter transistor amplifier. For this example, a subdirectory called SpiceLib was created with a single file 2N2222A.lib that holds the .MODEL statement for that particular type of transistor.

In [8]:
reset()  # Clear out the existing circuitry from the previous example.

# Create a transistor, power supply, bias resistors, collector resistor, and an input sine wave source.
q = BJT(model='2n2222a')    # 2N2222A NPN transistor. The model is stored in a directory of SPICE .lib files.
vdc = V(dc_value=5@u_V)     # 5V power supply.
rs = R(value=5@u_kOhm)      # Source resistor in series with sine wave input voltage.
rb = R(value=25@u_kOhm)     # Bias resistor from 5V to base of transistor.
rc = R(value=1@u_kOhm)      # Load resistor connected to collector of transistor.
vs = SINEV(amplitude=0.01@u_V, frequency=1@u_kHz)  # 1 KHz sine wave input source.
q['c', 'b', 'e'] += rc[1], rb[1], gnd  # Connect transistor CBE pins to load & bias resistors and ground.
vdc['p'] += rc[2], rb[2]    # Connect other end of load and bias resistors to power supply's positive terminal.
vdc['n'] += gnd             # Connect negative terminal of power supply to ground.
rs[1,2] += vs['p'], q['b']  # Connect source resistor from input source to base of transistor.
vs['n'] += gnd              # Connect negative terminal of input source to ground.

# Simulate the transistor amplifier. This requires a SPICE library containing a model of the 2N2222A transistor.
circ = generate_netlist(libs='SpiceLib')  # Pass the directory to the SPICE model library when creating circuit.
print(circ)
sim = circ.simulator() 
waveforms = sim.transient(step_time=0.01@u_ms, end_time=5@u_ms)

# Get the input source and amplified output waveforms.
time = waveforms.time
vin = waveforms[node(vs['p'])]  # Input source voltage.
vout = waveforms[node(q['c'])]  # Amplified output voltage at collector of the transistor.

# Plot the input and output waveforms.
figure = plt.figure(1)
plt.title('Output Voltage vs. Input Voltage')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.plot(time*1000, vin)
plt.plot(time*1000, vout)
plt.legend(('Input Voltage', 'Output Voltage'), loc=(1.1, 0.5))
plt.show()
.title 
.include C:\xesscorp\KiCad\tools\skidl\examples\spice-sim-intro\SpiceLib\2N2222A.lib
Q1 N1 N2 0 2n2222a
R1 N4 N2 5kOhm
R2 N2 N3 25kOhm
R3 N1 N3 1kOhm
V1 N3 0 5V
V2 N4 0 DC 0V AC SIN(0V 0.01V 1kHz 0s 0Hz)

No errors or warnings found during netlist generation.

A Hierarchical Circuit

SKiDL lets you describe a circuit inside a function, and then call that function to create hierarchical designs that can be analyzed with SPICE. This example defines a simple transistor inverter and then cascades several of them.

In [9]:
reset()  # You know what this does by now, right?

# Create a power supply for all the following circuitry.
pwr = V(dc_value=5@u_V)
pwr['n'] += gnd
vcc = pwr['p']

# Create a logic inverter using a transistor and a few resistors.
@subcircuit
def inverter(inp, outp):
    '''When inp is driven high, outp is pulled low by transistor. When inp is driven low, outp is pulled high by resistor.'''
    q = BJT(model='2n2222a')  # NPN transistor.
    rc = R(value=1@u_kOhm)    # Resistor attached between transistor collector and VCC.
    rc[1,2] += vcc, q['c']    
    rb = R(value=10@u_kOhm)   # Resistor attached between transistor base and input.
    rb[1,2] += inp, q['b']
    q['e'] += gnd             # Transistor emitter attached to ground.
    outp += q['c']            # Inverted output comes from junction of the transistor collector and collector resistor.
    
# Create a pulsed voltage source to drive the input of the inverters. I set the rise and fall times to make
# it easier to distinguish the input and output waveforms in the plot.
vs = PULSEV(initial_value=0, pulsed_value=5@u_V, pulse_width=0.8@u_ms, period=2@u_ms, rise_time=0.2@u_ms, fall_time=0.2@u_ms)  # 1ms ON, 1ms OFF pulses.
vs['n'] += gnd

# Create three inverters and cascade the output of one to the input of the next.
outp = Net() * 3  # Create three nets to attach to the outputs of each inverter.
inverter(vs['p'], outp[0])  # First inverter is driven by the pulsed voltage source.
inverter(outp[0], outp[1])  # Second inverter is driven by the output of the first.
inverter(outp[1], outp[2])  # Third inverter is driven by the output of the second.

# Simulate the cascaded inverters.
circ = generate_netlist(libs='SpiceLib') # Pass-in the library where the transistor model is stored.
sim = circ.simulator()
waveforms = sim.transient(step_time=0.01@u_ms, end_time=5@u_ms)

# Get the waveforms for the input and output.
time = waveforms.time
inp = waveforms[node(vs['p'])]
outp = waveforms[node(outp[2])]  # Get the output waveform for the final inverter in the cascade.

# Plot the input and output waveforms. The output will be the inverse of the input since it passed
# through three inverters.
figure = plt.figure(1)
plt.title('Output Voltage vs. Input Voltage')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.plot(time*1000, inp)
plt.plot(time*1000, outp)
plt.legend(('Input Voltage', 'Output Voltage'), loc=(1.1, 0.5))
plt.show()
No errors or warnings found during netlist generation.

Warning: v2: no DC value, transient time 0 value used

Using SPICE Subcircuits

Using @subcircuit lets you do hierarchical design directly in SKiDL, but SPICE has long had another option: subcircuits. These are encapsulations of device behavior stored in SPICE library files. Many thousands of these have been created over the years, both by SPICE users and semiconductor companies. A simulation of the NCP1117 voltage regulator from ON Semiconductor is shown below.

In [14]:
reset()

lib_search_paths[SPICE].append('SpiceLib')

vin = V(ref='VIN', dc_value=8@u_V)      # Input power supply.
vreg = Part('NCP1117', 'ncp1117_33-x')  # Voltage regulator from ON Semi part lib.
print(vreg)                             # Print vreg pin names.
r = R(value=470 @ u_Ohm)                # Load resistor on regulator output.
vreg['IN', 'OUT'] += vin['p'], r[1]     # Connect vreg input to vin and output to load resistor.
gnd += vin['n'], r[2], vreg['GND']      # Ground connections for everybody.

# Simulate the voltage regulator subcircuit.
circ = generate_netlist(libs='SpiceLib') # Pass-in the library where the voltage regulator subcircuit is stored.
sim = circ.simulator()
dc_vals = sim.dc(VIN=slice(0,10,0.1)) # Ramp vin from 0->10V and observe regulator output voltage.

# Get the input and output voltages.
inp = dc_vals[node(vin['p'])]
outp = dc_vals[node(vreg['OUT'])]

# Plot the regulator output voltage vs. the input supply voltage. Note that the regulator
# starts to operate once the input exceeds 4V and the output voltage clamps at 3.3V.
figure = plt.figure(1)
plt.title('NCP1117-3.3 Regulator Output Voltage vs. Input Voltage')
plt.xlabel('Input Voltage (V)')
plt.ylabel('Output Voltage (V)')
plt.plot(inp, outp)
plt.show()
 ncp1117_33-x (): 1.0a low-dropout positive fixed and adjustable voltage regulators
    Pin X1/3/in/UNSPECIFIED
    Pin X1/1/out/UNSPECIFIED
    Pin X1/2/gnd/UNSPECIFIED
No errors or warnings found during netlist generation.

SKiDL can work with SPICE subcircuits intended for PSPICE and LTSpice. All you need to do is add the top-level directories where the subcircuit libraries are stored and SKiDL will recursively search for the library files. When it reads a subcircuit library file (indicated by a .lib file extension), SKiDL will also look for a symbol file that provides names for the subcircuit I/O signals. For PSPICE, the symbol file has a .slb extension while the .asy extension is used for LTSpice.

WARNING: Even though SKiDL will read the PSPICE and LTSpice library files, that doesn't mean that ngspice can process them. Each SPICE simulator seems to support a different set of optional parameters for the various circuit elements (the nonlinear current source, G, for example). You will probably have to modify the library file to satisfy ngspice. PSPICE libraries seem to need the least modification. I wish it was easier, but it's not.

The Details

The examples section gave you some idea of what the combination of SKiDL, PySpice, ngspice, and matplotlib can do. You should read the stand-alone documentation for each of those packages to get the full extent of their capabilities. This section will address the features and functions that come in to play at their intersection.

Units

You may have noticed the use of units in the examples above, such as 10 @ u_kOhm to denote a resistance of 10 K$\Omega$. This is a feature of the PySpice package. If you want to see all the available units, just do this:

In [11]:
import PySpice.Unit
', '.join(dir(PySpice.Unit))
Out[11]:
'Frequency, FrequencyValue, FrequencyValues, Period, PeriodValue, PeriodValues, SiUnits, U_A, U_Bq, U_C, U_Degree, U_F, U_GA, U_GBq, U_GC, U_GDegree, U_GF, U_GGy, U_GH, U_GHz, U_GJ, U_GK, U_GN, U_GOhm, U_GPa, U_GS, U_GSv, U_GV, U_GW, U_GWb, U_Gcd, U_Gkat, U_Glm, U_Glx, U_Gm, U_Gmol, U_Grad, U_Gs, U_Gsr, U_Gy, U_GΩ, U_H, U_Hz, U_J, U_K, U_MA, U_MBq, U_MC, U_MDegree, U_MF, U_MGy, U_MH, U_MHz, U_MJ, U_MK, U_MN, U_MOhm, U_MPa, U_MS, U_MSv, U_MV, U_MW, U_MWb, U_Mcd, U_Mkat, U_Mlm, U_Mlx, U_Mm, U_Mmol, U_Mrad, U_Ms, U_Msr, U_MΩ, U_N, U_Ohm, U_Pa, U_S, U_Sv, U_TA, U_TBq, U_TC, U_TDegree, U_TF, U_TGy, U_TH, U_THz, U_TJ, U_TK, U_TN, U_TOhm, U_TPa, U_TS, U_TSv, U_TV, U_TW, U_TWb, U_Tcd, U_Tkat, U_Tlm, U_Tlx, U_Tm, U_Tmol, U_Trad, U_Ts, U_Tsr, U_TΩ, U_V, U_W, U_Wb, U_cd, U_kA, U_kBq, U_kC, U_kDegree, U_kF, U_kGy, U_kH, U_kHz, U_kJ, U_kK, U_kN, U_kOhm, U_kPa, U_kS, U_kSv, U_kV, U_kW, U_kWb, U_kat, U_kcd, U_kkat, U_klm, U_klx, U_km, U_kmol, U_krad, U_ks, U_ksr, U_kΩ, U_lm, U_lx, U_m, U_mA, U_mBq, U_mC, U_mDegree, U_mF, U_mGy, U_mH, U_mHz, U_mJ, U_mK, U_mN, U_mOhm, U_mPa, U_mS, U_mSv, U_mV, U_mW, U_mWb, U_mcd, U_mkat, U_mlm, U_mlx, U_mm, U_mmol, U_mol, U_mrad, U_ms, U_msr, U_mΩ, U_nA, U_nBq, U_nC, U_nDegree, U_nF, U_nGy, U_nH, U_nHz, U_nJ, U_nK, U_nN, U_nOhm, U_nPa, U_nS, U_nSv, U_nV, U_nW, U_nWb, U_ncd, U_nkat, U_nlm, U_nlx, U_nm, U_nmol, U_nrad, U_ns, U_nsr, U_nΩ, U_pA, U_pBq, U_pC, U_pDegree, U_pF, U_pGy, U_pH, U_pHz, U_pJ, U_pK, U_pN, U_pOhm, U_pPa, U_pS, U_pSv, U_pV, U_pW, U_pWb, U_pcd, U_pkat, U_plm, U_plx, U_pm, U_pmol, U_prad, U_ps, U_psr, U_pΩ, U_rad, U_s, U_sr, U_uA, U_uBq, U_uC, U_uDegree, U_uF, U_uGy, U_uH, U_uHz, U_uJ, U_uK, U_uN, U_uOhm, U_uPa, U_uS, U_uSv, U_uV, U_uW, U_uWb, U_ucd, U_ukat, U_ulm, U_ulx, U_um, U_umol, U_urad, U_us, U_usr, U_Ω, U_μA, U_μBq, U_μC, U_μF, U_μGy, U_μH, U_μHz, U_μJ, U_μK, U_μN, U_μPa, U_μS, U_μSv, U_μV, U_μW, U_μWb, U_μcd, U_μkat, U_μlm, U_μlx, U_μm, U_μmol, U_μrad, U_μs, U_μsr, U_μΩ, Unit, UnitValueShorcut, _SiUnits, _Unit, __builtins__, __cached__, __doc__, __file__, __loader__, __name__, __package__, __path__, __spec__, _build_as_unit_shortcut, _build_prefix_shortcut, _build_unit_prefix_shortcut, _build_unit_shortcut, _build_unit_type_shortcut, _exec_body, _has_matmul, _module_logger, _to_ascii, _version_info, as_A, as_Bq, as_C, as_Degree, as_F, as_Gy, as_H, as_Hz, as_J, as_K, as_N, as_Ohm, as_Pa, as_S, as_Sv, as_V, as_W, as_Wb, as_cd, as_kat, as_lm, as_lx, as_m, as_mol, as_rad, as_s, as_sr, as_Ω, atto, deca, define_shortcut, exa, femto, giga, hecto, kilo, logging, mega, micro, milli, nano, peta, pico, sys, tera, u_A, u_Bq, u_C, u_Degree, u_F, u_GA, u_GBq, u_GC, u_GDegree, u_GF, u_GGy, u_GH, u_GHz, u_GJ, u_GK, u_GN, u_GOhm, u_GPa, u_GS, u_GSv, u_GV, u_GW, u_GWb, u_Gcd, u_Gkat, u_Glm, u_Glx, u_Gm, u_Gmol, u_Grad, u_Gs, u_Gsr, u_Gy, u_GΩ, u_H, u_Hz, u_J, u_K, u_MA, u_MBq, u_MC, u_MDegree, u_MF, u_MGy, u_MH, u_MHz, u_MJ, u_MK, u_MN, u_MOhm, u_MPa, u_MS, u_MSv, u_MV, u_MW, u_MWb, u_Mcd, u_Mkat, u_Mlm, u_Mlx, u_Mm, u_Mmol, u_Mrad, u_Ms, u_Msr, u_MΩ, u_N, u_Ohm, u_Pa, u_S, u_Sv, u_TA, u_TBq, u_TC, u_TDegree, u_TF, u_TGy, u_TH, u_THz, u_TJ, u_TK, u_TN, u_TOhm, u_TPa, u_TS, u_TSv, u_TV, u_TW, u_TWb, u_Tcd, u_Tkat, u_Tlm, u_Tlx, u_Tm, u_Tmol, u_Trad, u_Ts, u_Tsr, u_TΩ, u_V, u_W, u_Wb, u_cd, u_kA, u_kBq, u_kC, u_kDegree, u_kF, u_kGy, u_kH, u_kHz, u_kJ, u_kK, u_kN, u_kOhm, u_kPa, u_kS, u_kSv, u_kV, u_kW, u_kWb, u_kat, u_kcd, u_kkat, u_klm, u_klx, u_km, u_kmol, u_krad, u_ks, u_ksr, u_kΩ, u_lm, u_lx, u_m, u_mA, u_mBq, u_mC, u_mDegree, u_mF, u_mGy, u_mH, u_mHz, u_mJ, u_mK, u_mN, u_mOhm, u_mPa, u_mS, u_mSv, u_mV, u_mW, u_mWb, u_mcd, u_mkat, u_mlm, u_mlx, u_mm, u_mmol, u_mol, u_mrad, u_ms, u_msr, u_mΩ, u_nA, u_nBq, u_nC, u_nDegree, u_nF, u_nGy, u_nH, u_nHz, u_nJ, u_nK, u_nN, u_nOhm, u_nPa, u_nS, u_nSv, u_nV, u_nW, u_nWb, u_ncd, u_nkat, u_nlm, u_nlx, u_nm, u_nmol, u_nrad, u_ns, u_nsr, u_nΩ, u_pA, u_pBq, u_pC, u_pDegree, u_pF, u_pGy, u_pH, u_pHz, u_pJ, u_pK, u_pN, u_pOhm, u_pPa, u_pS, u_pSv, u_pV, u_pW, u_pWb, u_pcd, u_pkat, u_plm, u_plx, u_pm, u_pmol, u_prad, u_ps, u_psr, u_pΩ, u_rad, u_s, u_sr, u_uA, u_uBq, u_uC, u_uDegree, u_uF, u_uGy, u_uH, u_uHz, u_uJ, u_uK, u_uN, u_uOhm, u_uPa, u_uS, u_uSv, u_uV, u_uW, u_uWb, u_ucd, u_ukat, u_ulm, u_ulx, u_um, u_umol, u_urad, u_us, u_usr, u_Ω, u_μA, u_μBq, u_μC, u_μF, u_μGy, u_μH, u_μHz, u_μJ, u_μK, u_μN, u_μPa, u_μS, u_μSv, u_μV, u_μW, u_μWb, u_μcd, u_μkat, u_μlm, u_μlx, u_μm, u_μmol, u_μrad, u_μs, u_μsr, u_μΩ, unit, unit_prefix, unit_value, yocto, yotta, zepto, zetta'

The following units are the ones you'll probably use most:

  • Potential: u_TV (terravolt), u_GV (gigavolt), u_MV (megavolt), u_kV (kilovolt), u_V (volt), u_mV (millivolt), u_uV (microvolt), u_nV (nanovolt), u_pV (picovolt).
  • Current: u_TA (terraamp), u_GA (gigaamp), u_MA (megaamp), u_kA (kiloamp), u_A (amp), u_mA (milliamp), u_uA (microamp), u_nA (nanoamp), u_pA (picoamp).
  • Resistance: u_TOhm (terraohm), u_GOhm (gigaohm), u_MOhm (megaohm), u_kOhm (kiloohm), u_Ohm (ohm), u_mOhm (milliohm), u_uOhm (microohm), u_nOhm (nanoohm), u_pOhm (picoohm).
  • Capacitance: u_TF (terrafarad) u_GF (gigafarad), u_MF (megafarad), u_kF (kilofarad), u_F (farad), u_mF (millifarad), u_uF (microfarad), u_nF (nanofarad), u_pF (picofarad).
  • Inductance: u_TH (terrahenry), u_GH (gigahenry), u_MH (megahenry), u_kH (kilohenry), u_H (henry), u_mH (millihenry), u_uH (microhenry), u_nH (nanohenry), u_pH (picohenry).
  • Time: u_Ts (terrasecond), u_Gs (gigasecond), u_Ms (megasecond), u_ks (kilosecond), u_s (second), u_ms (millisecond), u_us (microsecond), u_ns (nanosecond), u_ps (picosecond).
  • Frequency: u_THz (terrahertz), u_GHz (gigahertz), u_MHz (megahertz), u_kHz (kilohertz), u_Hz (hertz), u_mHz (millihertz), u_uHz (microhertz), u_nHz (nanohertz), u_pHz (picohertz).

Available Parts

The following is a list of parts (and their aliases) that are available for use in a SPICE simulation. Many parts (like resistors) have only two pins denoted p and n, while some parts (like transmission lines) have two ports composed of pins ip, in (the input port) and op, on (the output port). The parts also have attributes that modify their characteristics. These attributes can be set when a part is instantiated:

r = R(value=1@u_kOhm)

or they can be set after instantiation like this:

r.value = 1 @ u_kOhm

You can go here for more information about what device characteristics the attributes control.

In [12]:
from skidl.libs.pyspice_sklib import pyspice_lib

for part in pyspice_lib.parts:
    print('{name} ({aliases}): {desc}\n\tPins: {pins}\n\tAttributes: {attributes}\n'.format(
        name=getattr(part, 'name', ''),
        aliases=', '.join(getattr(part, 'aliases','')), 
        desc=getattr(part, 'description'),
        pins=', '.join([p.name for p in part.pins]),
        attributes=', '.join([a for a in list(part.pyspice.get('pos',[])) + list(part.pyspice.get('kw',[]))]),
    ))
A (): XSPICE code module
	Pins: 
	Attributes: model

B (behavsrc, BEHAVSRC, behavioralsource, BEHAVIORALSOURCE): Behavioral (arbitrary) source
	Pins: p, n
	Attributes: i, i_expression, v, v_expression, tc1, tc2, temp, temperature, dtemp, device_temperature, p, n

C (cap, CAP): Capacitor
	Pins: p, n
	Attributes: value, capacitance, model, multiplier, m, scale, temp, temperature, dtemp, device_temperature, ic, initial_condition, p, n

BEHAVCAP (behavcap, behavioralcap, BEHAVIORALCAP): Behavioral capacitor
	Pins: p, n
	Attributes: expression, tc1, tc2, p, n

SEMICAP (semicap, semiconductorcap, SEMICONDUCTORCAP): Semiconductor capacitor
	Pins: p, n
	Attributes: value, model, length, l, width, w, multiplier, m, scale, temp, temperature, dtemp, device_temperature, ic, initial_condition, p, n

D (diode, DIODE): Diode
	Pins: p, n
	Attributes: model, area, multiplier, m, pj, off, ic, initial_condition, temp, temperature, dtemp, device_temperature, p, n

E (VCVS, vcvs): Voltage-controlled voltage source
	Pins: ip, in, op, on
	Attributes: gain, voltage_gain, op, on, ip, in

NONLINV (nonlinv, nonlinearvoltagesource, NONLINEARVOLTAGESOURCE): Nonlinear voltage source
	Pins: p, n
	Attributes: expression, table, p, n

F (CCCS, cccs): Current-controlled current source
	Pins: p, n
	Attributes: control, source, gain, current_gain, multiplier, m, p, n

G (): Voltage-controlled current source
	Pins: ip, in, op, on
	Attributes: gain, current_gain, multiplier, m, op, on, ip, in

NONLINI (nonlinvi, nonlinearcurrentsource, NONLINEARCURRENTSOURCE): Nonlinear current source
	Pins: p, n
	Attributes: expression, table, p, n

H (CCVS, ccvs): Current-controlled voltage source
	Pins: p, n
	Attributes: control, source, transresistance, p, n

I (i, cs, CS): Current source
	Pins: p, n
	Attributes: value, dc_value, p, n

J (JFET, jfet): Junction field-effect transistor
	Pins: d, g, s
	Attributes: model, area, multiplier, m, off, ic, initial_condition, temp, temperature, d, g, s

K (): Coupled (mutual) inductors
	Pins: 
	Attributes: ind1, ind2, coupling

L (): Inductor
	Pins: p, n
	Attributes: value, inductance, model, nt, multiplier, m, scale, temp, temperature, dtemp, device_temperature, ic, initial_condition, p, n

BEHAVIND (behavind, behavioralind, BEHAVIORALIND): Behavioral inductor
	Pins: p, n
	Attributes: expression, tc1, tc2, p, n

M (MOSFET, mosfet, FET, fet): Metal-oxide field-effect transistor
	Pins: d, g, s, b
	Attributes: model, multiplier, m, l, length, w, width, drain_area, source_area, drain_perimeter, source_perimeter, drain_number_square, source_number_square, off, ic, initial_condition, temp, temperature, d, g, s, b

N (): Numerical device for GSS
	Pins: 
	Attributes: 

O (): Lossy transmission line
	Pins: ip, in, op, on
	Attributes: model, op, on, ip, in

P (): Coupled multiconductor line
	Pins: 
	Attributes: model, length, l, op, on, ip, in

Q (BJT, bjt): Bipolar Junction Transistor
	Pins: c, b, e, s
	Attributes: model, area, areab, areac, multiplier, m, off, ic, initial_condition, temp, temperature, dtemp, device_temperature, c, b, e, s

R (): Resistor
	Pins: p, n
	Attributes: value, resistance, ac, multiplier, m, scale, temp, temperature, dtemp, device_temperature, noisy, p, n

BEHAVRES (behavres, behavioralresistor, BEHAVIORALRESISTOR): Behavioral resistor
	Pins: p, n
	Attributes: expression, tc1, tc2, p, n

SEMIRES (semires, semiconductorresistor, SEMICONDUCTORRESISTOR): Semiconductor resistor
	Pins: p, n
	Attributes: value, capacitance, model, ac, length, l, width, w, multiplier, m, scale, temp, temperature, dtemp, device_temperature, noisy, p, n

S (VCS, vcs): Voltage-controlled switch
	Pins: ip, in, op, on
	Attributes: model, initial_state, op, on, ip, in

T (transmissionline, TRANSMISSIONLINE): Transmission line
	Pins: ip, in, op, on
	Attributes: impedance, Z0, time_delay, TD, frequency, F, normalized_length, NL, op, on, ip, in

U (): Uniformly-distributed RC line
	Pins: o, i, cn
	Attributes: model, length, l, number_of_lumps, m, o, i, cn

V (v, VS, vs, AMMETER, ammeter): Voltage source
	Pins: p, n
	Attributes: value, dc_value, p, n

W (CCS, ccs): Current-controlled switch
	Pins: p, n
	Attributes: control, source, model, initial_state, p, n

Y (): Single lossy transmission line
	Pins: ip, in, op, on
	Attributes: model, length, l, op, on, ip, in

Z (MESFET, mesfet): Metal-semiconductor field-effect transistor
	Pins: d, g, s
	Attributes: model, area, multiplier, m, off, ic, initial_condition, d, g, s

SINEV (sinev, sinusoidalvoltage, SINUSOIDALVOLTAGE): Sinusoidal voltage source
	Pins: p, n
	Attributes: dc_offset, offset, amplitude, frequency, delay, damping_factor, p, n

SINEI (sinei, sinusoidalcurrent, SINUSOIDALCURRENT): Sinusoidal current source
	Pins: p, n
	Attributes: dc_offset, offset, amplitude, frequency, delay, damping_factor, p, n

PULSEV (pulsev, pulsevoltage, PULSEVOLTAGE): Pulsed voltage source
	Pins: p, n
	Attributes: initial_value, pulsed_value, delay_time, rise_time, fall_time, pulse_width, period, p, n

PULSEI (pulsei, pulsecurrent, PULSECURRENT): Pulsed current source
	Pins: p, n
	Attributes: initial_value, pulsed_value, delay_time, rise_time, fall_time, pulse_width, period, p, n

EXPV (expv, exponentialvoltage, EXPONENTIALVOLTAGE): Exponential voltage source
	Pins: p, n
	Attributes: initial_value, pulsed_value, rise_delay_time, rise_time_constant, fall_delay_time, fall_time_constant, p, n

EXPI (expi, exponentialcurrent, EXPONENTIALCURRENT): Exponential current source
	Pins: p, n
	Attributes: initial_value, pulsed_value, rise_delay_time, rise_time_constant, fall_delay_time, fall_time_constant, p, n

PWLV (pwlv, piecewiselinearvoltage, PIECEWISELINEARVOLTAGE): Piecewise linear voltage source
	Pins: p, n
	Attributes: values, repeate_time, delay_time, p, n

PWLI (pwli, piecewiselinearcurrent, PIECEWISELINEARCURRENT): Piecewise linear current source
	Pins: p, n
	Attributes: values, repeate_time, delay_time, p, n

FMV (fmv, SFFMV, sffmv, SINGLEFREQUENCYFMVOLTAGE, singlefrequencyfmvoltage): Single-frequency FM-modulated voltage source
	Pins: p, n
	Attributes: offset, amplitude, carrier_frequency, modulation_index, signal_frequency, p, n

FMI (fmi, SFFMI, sffmi, SINGLEFREQUENCYFMCURRENT, singlefrequencyfmcurrent): Single-frequency FM-modulated current source
	Pins: p, n
	Attributes: offset, amplitude, carrier_frequency, modulation_index, signal_frequency, p, n

AMV (amv, AMPLITUDEMODULATEDVOLTAGE, amplitudemodulatedvoltage): Amplitude-modulated voltage source
	Pins: p, n
	Attributes: offset, amplitude, carrier_frequency, modulating_frequency, signal_delay, p, n

AMI (ami, AMPLITUDEMODULATEDCURRENT, amplitudemodulatedcurrent): Amplitude-modulated current source
	Pins: p, n
	Attributes: offset, amplitude, carrier_frequency, modulating_frequency, signal_delay, p, n

RNDV (rndv, RANDOMVOLTAGE, randomvoltage): Random voltage source
	Pins: p, n
	Attributes: random_type, duration, time_delay, parameter1, parameter2, p, n

RNDI (rndi, RANDOMCURRENT, randomcurrent): Random current source
	Pins: p, n
	Attributes: random_type, duration, time_delay, parameter1, parameter2, p, n

Startup

When you import the PySpice functions into SKiDL:

from skidl.pyspice import *

several things occur:

  • The parts and utilities defined in skidl.libs.pyspice.py are imported.
  • The default CAD tool is set to SKIDL.
  • A ground net named gnd or GND is created.

In addition, when the parts are imported, their names and aliases are instantiated in the namespace of the calling module to make it easier to create parts. So, rather than using the following standard SKiDL method of creating a part:

c = Part('pyspice', 'C', value=1@u_uF)

you can just do:

c = C(value=1@u_uF)

Miscellaneous

You can use the node() function if you need the name of a circuit node in order to extract its data from the results returned by a simulation. The argument to node() can be either a pin of a part or a net:

If you need the actual SPICE deck for a circuit so you can simulate it outside of Python, just print the Circuit object returned by generate_pyspice_circuit() or store it in a file:

In [13]:
reset()

# Create and interconnect the components.
vs = V(ref='VS', dc_value = 1 @ u_V)  # Create a voltage source named "VS" with an initial value of 1 volt.
r1 = R(value = 1 @ u_kOhm)            # Create a 1 Kohm resistor.
vs['p'] += r1[1]       # Connect one end of the resistor to the positive terminal of the voltage source.
gnd += vs['n'], r1[2]  # Connect the other end of the resistor and the negative terminal of the source to ground.

# Output the SPICE deck for the circuit.
circ = generate_netlist()      # Translate the SKiDL code into a PyCircuit Circuit object.
print(circ)                            # Print the SPICE deck for the circuit.
.title 
R1 N1 0 1kOhm
VS N1 0 1V

No errors or warnings found during netlist generation.

Future Work

There are two immediate features that need to be added:

  1. Instantiation of subcircuits defined by the .SUBCKT command.
  2. Creation of schematics from the SKiDL code.

Acknowledgements

Thanks to Fabrice Salvaire for inventing PySpice, and to Steve Armour whose Jupyter notebook of PySpice examples led the way.