Friday, September 29, 2023

A Teeny Weeny SPICE Circuit Simulator

Ionsamhlóir Ciorcaid An-bheag.

What and why?

I wrote a small SPICE circuit simulator to get over my fears of `RELTOL`, `ABSTOL` and time-step-too-small errors.

I'm at version `v0.8.0` which has quite a nice set of basic features. It can read SPICE decks with circuit descriptions. It can execute some commands if they are listed in a `.control` block in the SPICE deck. It can do 2 types of analyses: DC Operating point and Transient. Circuit device-wise, it can imagine resistors, capacitors and diodes. Sources supported are voltage and current sources (DC or sinewave).

It's written in Rust, cos that's what I like to use instead of C when I can. The source code is on github here: tiny-spice-rs. See the README for details of how to simulate a circuit.

Subcircuits!

One of the things I'm most happy about is that it supports subcircuits! And the subcircuits can be parameterised! And parameters can be very simple one-identifier expressions!

My working example is 3 copies of a fullwave rectifier system with parameterised loads. The SPICE for this circuit is shown below, as is a cartoon of the circuit.


Full-Wave Rectifier with parameterised subcircuits

* 3 instances of a diode bridge + RC load
* cap load in each instances parameterised and overriden from
*   the toplevel

V1 vstack1 gnd     SIN(0 5 1e3) ; input voltage
V2 vstack2 vstack1 SIN(0 2 2e3)
V3 vstack2 IN_p    SIN(0 1 3e3) ; flip to differentiate between "multi_"

* full-wave rectifier
.subckt bridge bp bn ba bb

  D1 bp ba
  D2 bb bp
  D3 bn ba
  D4 bb bn

  * Small caps across the diodes to prevent time-step-too-small
  CD1 bp ba 12pF
  CD2 bb bp 12pF
  CD3 bn ba 12pF
  CD4 bb bn 12pF

.ends

.subckt system sinp sinn soutp soutn cval=10uF
  Xbridge sinp sinn midnode soutn bridge
  Rd midnode soutp 1
  Xload soutp soutn rc_load cvalo={cval}
.ends

* Load
.subckt rc_load in1 in2 cvalo=1nF
* Split R so we have internal nodes
  Rl1 in1 la 200
  Rl2 la lb 300
  Rl3 lb lc 400
  Rl4 lc in2 100
  Cload in1 in2 {cvalo}
.ends

Xsystem1 IN_p gnd vp1 vn1 system cval=1uF
Xsystem2 IN_p gnd vp2 vn2 system ; DEFAULT cval=10uF
Xsystem3 IN_p gnd vp3 vn3 system cval=100uF

.control
*  option reltol = 0.001
*  option abstol = 1e-12

  tran 100ns 5ms
  option ; ngspice only shows new values after analysis

  plot v(IN_p) v(vp1,vn1) v(vp2,vn2) v(vp3,vn3); (ngspice)
.endc
ALT-TEXT: Circuit diagram showing 3 instances of a subcircuit. The supply to all three is a stack of sinewave sources at different frequencies and amplitudes. The subcircuits themselves are subcircuits: a diode bridge rectifier, a series resistor and an RC load with a parameterised capacitor value. The capacitor value is passed down to the capacitor in the RC load subcircuit all the way from the toplevel instantiations.

These waveforms are the proof that it works.

ALT-TEXT: Waveforms from a transient simulation of the above 3-bridge circuit. The input 3-tone sinewave is shown, as are the voltages across the 3 RC load blocks. The different parameterised values for the three blocks result in different smoothing curves.

Where next?

Next, maybe something with reciprocity, that seems interesting. I think that reciprocity can be used in noise simulations to work out the contributors to noise at a certain node.

A simple waveform viewer would be nice, but I've no intention of writing one of those. Even though there's basic `.control` support, I don't do anything with `print` or `plot` commands.