Spicing It Up! (Sorry)
Contents
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.
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#
This notebook assumes you’re using ngspice version 30. To install ngspice for linux, do:
sudo apt-get update
sudo apt-get install ngspice
For Windows:
Download ngspice-30_dll_64.zip
Unpack the zip file into C:\Program Files. The top-level folder should be named Spice64_dll so PySpice can find it.
Change subdirectory dll-vs to bin_dll.
Make sure to run 64-bit Python. Otherwise, it will be unable to run the 64-bit DLLs.
Next, for either OS, install SKiDL:
pip install skidl
After that, you’ll have to manually install PySpice (it must be version 1.3.2 or higher):
pip install "PySpice>=1.3.2"
Finally, place a spinit
file in the same folder as the notebook you’re trying to run. This contains the ngspice initialization commands as discussed in the ngspice manual. Typically, I just enable the use of Pspice models and set the number of processing threads as follows:
set ngbehavior=ps
set num_thread=4
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.
from skidl import *
print(lib_search_paths)
WARNING: KICAD_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched.
{'kicad': ['.'], 'skidl': ['.', '/media/devb/Main/xesscorp/KiCad/tools/skidl/skidl/libs'], 'spice': ['.']}
# 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 *
print(lib_search_paths)
{'kicad': ['.'], 'skidl': ['.', '/media/devb/Main/xesscorp/KiCad/tools/skidl/skidl/libs'], 'spice': ['.']}
Current Through a Resistor#
The following example connects a 1K 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.
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'])] # 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()
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.
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()
A Voltage-Controlled Voltage Source#
A voltage source whose output is controlled by another voltage source is demonstrated in this example.
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
V1 N_1 0 DC 0V AC 1V SIN(0V 1V 100Hz 0s 0Hz)
E1 N_2 0 N_1 0 2.5
R1 N_2 0 1kOhm
R2 N_1 0 1kOhm
A Current-Controlled Current Source#
This example shows a current source controlled by the current driven through a resistor by a voltage source.
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()
A Transmission Line#
The voltages at the beginning and end of an ideal transmission line are shown in this example.
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()
A Transformer#
This example demonstrates a transformer composed of two coupled inductors.
reset() # Clear out existing circuitry from previous example.
turns_ratio = 10 # Voltage gain from primary to secondary.
primary_inductance = 1 @ u_uH
secondary_inductance = primary_inductance * turns_ratio**2
# Create a transformer from two coupled inductors.
vs = SINEV(amplitude=1@u_V, frequency=100@u_Hz) # AC input voltage.
rs = R(value=0 @ u_Ohm) # Source resistor.
primary = L(value=primary_inductance) # Inductor for transformer primary.
secondary = L(value=secondary_inductance) # Inductor for transformer secondary.
rload = R(value=100 @ u_Ohm) # Load resistor.
# This is the coupler between the inductors that transfers the
# voltage from the primary to the secondary.
coupler_prim_sec = K(ind1=primary, ind2=secondary, coupling=0.99)
# Connect the voltage source to the primary through the source resistor.
gnd & vs['n,p'] & rs & primary[1,2] & gnd
# Connect the secondary to the load resistor.
gnd & secondary[2,1] & rload & gnd
# Simulate the transformer.
sim=generate_netlist().simulator()
waveforms = sim.transient(step_time=0.1 @ u_ms, end_time=100@u_ms)
# Get the waveforms from the primary and secondary.
time = waveforms.time * 10**3
v_pri = waveforms[node(primary[1])] # Input voltage at the transformer primary.
v_sec = waveforms[node(secondary[1])] # Output voltage at transformer secondary.
# Plot the input and output waveforms.
figure = plt.figure(1)
plt.title('Primary Voltage vs. Secondary Voltage')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.plot(time, v_pri)
plt.plot(time, v_sec)
plt.legend(('Primary Voltage', 'Secondary Voltage'), loc=(1.1, 0.5))
plt.show()
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.
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() # 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 /media/devb/Main/xesscorp/KiCad/tools/skidl/examples/spice-sim-intro/SpiceLib/2N2222A.lib
Q1 N_1 N_2 0 2n2222a
V1 N_3 0 5V
R1 N_4 N_2 5kOhm
R2 N_2 N_3 25kOhm
R3 N_1 N_3 1kOhm
V2 N_4 0 DC 0V AC 1V SIN(0V 0.01V 1kHz 0s 0Hz)
XSPICE Parts#
XSPICE parts can model a variety of functions (ADCs, DACs, etc.) having different I/O requirements, so SKiDL handles them a bit differently:
An io parameter is needed to specify the input and output pins. This parameter is either a comma-separated string or an array of strings listing the pin names in the order required for the particular XSPICE part. XSPICE I/O ports can be scalar (indicated by names which are simple strings) or vectors (indicated by names ending with “[]”).
A model parameter is required that specifies the parameters affecting the behavior of the given XSPICE part. This is passed as an
XspiceModel
object.
reset() # Clear out the existing circuitry from the previous example.
# Sinusoidal voltage source.
vin = sinev(offset=1.65 @ u_V, amplitude=1.65 @ u_V, frequency=100e6)
# Component declarations showing various XSPICE styles.
# Creating an XSPICE part from the pyspice library.
adc = Part(
"pyspice",
"A",
io="anlg_in[], dig_out[]", # Two vector I/O ports in a string.
model=XspiceModel(
"adc", # The name assigned to this particular model instance.
"adc_bridge", # The name of the XSPICE part associated with this model.
# The rest of the arguments are keyword parameters for the model.
in_low=0.05 @ u_V,
in_high=0.1 @ u_V,
rise_delay=1e-9 @ u_s,
fall_delay=1e-9 @ u_s,
),
tool=SKIDL
)
# Creating an XSPICE part using the SPICE abbreviation 'A'.
buf = A(
io="buf_in, buf_out", # Two scalar I/O ports in a string.
model=XspiceModel(
"buf",
"d_buffer",
rise_delay=1e-9 @ u_s,
fall_delay=1e-9 @ u_s,
input_load=1e-12 @ u_s,
),
)
# Creating an XSPICE part using the XSPICE alias.
dac = XSPICE(
io=["dig_in[]", "anlg_out[]"], # Two vector ports in an array.
model=XspiceModel("dac", "dac_bridge", out_low=1.0 @ u_V, out_high=3.3 @ u_V),
)
r = R(value=1 @ u_kOhm) # Load resistor.
# Connections: sine wave -> ADC -> buffer -> DAC.
vin["p, n"] += adc["anlg_in"][0], gnd # Attach to first pin in ADC anlg_in vector of pins.
adc["dig_out"][0] += buf["buf_in"] # Attach first pin of ADC dig_out vector to buffer.
buf["buf_out"] += dac["dig_in"][0] # Attach buffer output to first pin of DAC dig_in vector of pins.
r["p,n"] += dac["anlg_out"][0], gnd # Attach first pin of DAC anlg_out vector to load resistor.
circ = generate_netlist(libs="SpiceLib")
print(circ)
sim = circ.simulator()
waveforms = sim.transient(step_time=0.1 @ u_ns, end_time=50 @ u_ns)
time = waveforms.time
vin = waveforms[node(vin["p"])]
vout = waveforms[node(r["p"])]
print('{:^7s}{:^7s}'.format('vin', 'vout'))
print('='*15)
for v1, v2 in zip(vin.as_ndarray(), vout.as_ndarray()):
print('{:6.2f} {:6.2f}'.format(v1, v2))
.title
V1 N_1 0 DC 0V AC 1V SIN(1.65V 1.65V 100000000.0Hz 0s 0Hz)
A1 [N_1] [N_2] adc
A2 N_2 N_3 buf
A3 [N_3] [N_4] dac
R1 N_4 0 1kOhm
.model adc adc_bridge (fall_delay=1e-09s in_high=0.1V in_low=0.05V rise_delay=1e-09s)
.model buf d_buffer (fall_delay=1e-09s input_load=1e-12s rise_delay=1e-09s)
.model dac dac_bridge (out_high=3.3V out_low=1.0V)
vin vout
===============
1.65 3.30
1.65 3.30
1.65 3.30
1.65 3.30
1.66 3.30
1.67 3.30
1.68 3.30
1.72 3.30
1.78 3.30
1.89 3.30
...
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.
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() # 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()
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.
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() # 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
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
. This is a feature of the PySpice package. If you want to see all the available units, just do this:
import PySpice.Unit
', '.join(dir(PySpice.Unit))
'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.
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, xspice): XSPICE code module Pins: Attributes: modelB
(BEHAVIORALSOURCE, behavioralsource, behavsrc, BEHAVSRC): Behavioral (arbitrary) source Pins: p, n Attributes: i, i_expression, v, v_expression, tc1, tc2, temp, temperature, dtemp, device_temperature, p, nC
(CAP, cap): Capacitor Pins: p, n Attributes: value, capacitance, model, multiplier, m, scale, temp, temperature, dtemp, device_temperature, ic, initial_condition, p, nBEHAVCAP
(behavioralcap, BEHAVIORALCAP, behavcap): Behavioral capacitor Pins: p, n Attributes: expression, tc1, tc2, p, nSEMICAP
(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, nD
(diode, DIODE): Diode Pins: p, n Attributes: model, area, multiplier, m, pj, off, ic, initial_condition, temp, temperature, dtemp, device_temperature, p, nE
(vcvs, VCVS): Voltage-controlled voltage source Pins: ip, in, op, on Attributes: gain, voltage_gain, op, on, ip, inNONLINV
(NONLINEARVOLTAGESOURCE, nonlinv, nonlinearvoltagesource): Nonlinear voltage source Pins: p, n Attributes: expression, table, p, nF
(cccs, CCCS): Current-controlled current source Pins: p, n Attributes: control, source, gain, current_gain, multiplier, m, p, nG
(): Voltage-controlled current source Pins: ip, in, op, on Attributes: gain, current_gain, multiplier, m, op, on, ip, inNONLINI
(nonlinearcurrentsource, NONLINEARCURRENTSOURCE, nonlinvi): Nonlinear current source Pins: p, n Attributes: expression, table, p, nH
(ccvs, CCVS): Current-controlled voltage source Pins: p, n Attributes: control, source, transresistance, p, nI
(CS, cs, i): Current source Pins: p, n Attributes: value, dc_value, p, nJ
(jfet, JFET): Junction field-effect transistor Pins: d, g, s Attributes: model, area, multiplier, m, off, ic, initial_condition, temp, temperature, d, g, sK
(): Coupled (mutual) inductors Pins: Attributes: ind1, ind2, couplingL
(): Inductor Pins: p, n Attributes: value, inductance, model, nt, multiplier, m, scale, temp, temperature, dtemp, device_temperature, ic, initial_condition, p, nBEHAVIND
(behavind, behavioralind, BEHAVIORALIND): Behavioral inductor Pins: p, n Attributes: expression, tc1, tc2, p, nM
(fet, MOSFET, mosfet, 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, bN
(): Numerical device for GSS Pins: Attributes:O
(): Lossy transmission line Pins: ip, in, op, on Attributes: model, op, on, ip, inP
(): Coupled multiconductor line Pins: Attributes: model, length, l, op, on, ip, inQ
(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, sR
(): Resistor Pins: p, n Attributes: value, resistance, ac, multiplier, m, scale, temp, temperature, dtemp, device_temperature, noisy, p, nBEHAVRES
(behavioralresistor, BEHAVIORALRESISTOR, behavres): Behavioral resistor Pins: p, n Attributes: expression, tc1, tc2, p, nSEMIRES
(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, nS
(VCS, vcs): Voltage-controlled switch Pins: ip, in, op, on Attributes: model, initial_state, op, on, ip, inT
(TRANSMISSIONLINE, transmissionline): Transmission line Pins: ip, in, op, on Attributes: impedance, Z0, time_delay, TD, frequency, F, normalized_length, NL, op, on, ip, inU
(): Uniformly-distributed RC line Pins: o, i, cn Attributes: model, length, l, number_of_lumps, m, o, i, cnV
(ammeter, vs, VS, AMMETER, v): Voltage source Pins: p, n Attributes: value, dc_value, p, nW
(CCS, ccs): Current-controlled switch Pins: p, n Attributes: control, source, model, initial_state, p, nY
(): Single lossy transmission line Pins: ip, in, op, on Attributes: model, length, l, op, on, ip, inZ
(MESFET, mesfet): Metal-semiconductor field-effect transistor Pins: d, g, s Attributes: model, area, multiplier, m, off, ic, initial_condition, d, g, sSINEV
(sinev, SINUSOIDALVOLTAGE, sinusoidalvoltage): Sinusoidal voltage source Pins: p, n Attributes: dc_offset, ac_magnitude, ac_phase, offset, amplitude, frequency, delay, damping_factor, p, nSINEI
(SINUSOIDALCURRENT, sinei, sinusoidalcurrent): Sinusoidal current source Pins: p, n Attributes: dc_offset, ac_magnitude, ac_phase, offset, amplitude, frequency, delay, damping_factor, p, nPULSEV
(pulsevoltage, PULSEVOLTAGE, pulsev): Pulsed voltage source Pins: p, n Attributes: initial_value, pulsed_value, delay_time, rise_time, fall_time, pulse_width, period, p, nPULSEI
(pulsecurrent, PULSECURRENT, pulsei): Pulsed current source Pins: p, n Attributes: initial_value, pulsed_value, delay_time, rise_time, fall_time, pulse_width, period, p, nEXPV
(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, nEXPI
(exponentialcurrent, EXPONENTIALCURRENT, expi): Exponential current source Pins: p, n Attributes: initial_value, pulsed_value, rise_delay_time, rise_time_constant, fall_delay_time, fall_time_constant, p, nPWLV
(PIECEWISELINEARVOLTAGE, pwlv, piecewiselinearvoltage): Piecewise linear voltage source Pins: p, n Attributes: values, repeate_time, delay_time, p, nPWLI
(pwli, piecewiselinearcurrent, PIECEWISELINEARCURRENT): Piecewise linear current source Pins: p, n Attributes: values, repeate_time, delay_time, p, n
FMV (SFFMV, SINGLEFREQUENCYFMVOLTAGE, sffmv, singlefrequencyfmvoltage, fmv): Single-frequency FM-modulated voltage source Pins: p, n Attributes: offset, amplitude, carrier_frequency, modulation_index, signal_frequency, p, n
FMI
(singlefrequencyfmcurrent, fmi, sffmi, SFFMI, SINGLEFREQUENCYFMCURRENT): Single-frequency FM-modulated current source Pins: p, n Attributes: offset, amplitude, carrier_frequency, modulation_index, signal_frequency, p, nAMV
(AMPLITUDEMODULATEDVOLTAGE, amplitudemodulatedvoltage, amv): Amplitude-modulated voltage source Pins: p, n Attributes: offset, amplitude, carrier_frequency, modulating_frequency, signal_delay, p, nAMI
(AMPLITUDEMODULATEDCURRENT, ami, amplitudemodulatedcurrent): Amplitude-modulated current source Pins: p, n Attributes: offset, amplitude, carrier_frequency, modulating_frequency, signal_delay, p, nRNDV
(randomvoltage, rndv, RANDOMVOLTAGE): Random voltage source Pins: p, n Attributes: random_type, duration, time_delay, parameter1, parameter2, p, nRNDI
(RANDOMCURRENT, randomcurrent, rndi): 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_netlist()
or store it in a file:
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
VS N_1 0 1V
R1 N_1 0 1kOhm
Future Work#
There are two immediate features that need to be added:
Instantiation of subcircuits defined by the .SUBCKT command.
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.