Command

cicsim run --help
Usage: cicsim run [OPTIONS] TESTBENCH [CORNER]...

  Run a ngspice simulation of TESTBENCH

Options:
  --run / --no-run        Run simulator
  --count INTEGER         Run each corner count times, useful for Monte-Carlo
  --name TEXT             Control name of run file
  --ignore / --no-ignore  Ignore error checks
  --sha / --no-sha        Check SHA of input files
  --replace TEXT          YAML file with replacements for netlist
  --help                  Show this message and exit.

Motivation

An analog simulation requires a testbench. A SPICE file where the conditions (voltages, currents, temperatures, process ), device under test (DUT), stimuli and measurements are defined.

Most simulators, ngspice included, take a single SPICE file as input, and the simulators expects that everything is defined within that spice file.

Take the spice netlist below as an example.

ngspice/basic/output_tran/tran_SchThVh.spi:

*cicsimgen tran

.param SEED=0

.option TEMP=125

.param vdda = 1.98

This is just an excerpt, but notice that both the vdda, the supply voltage, and the temperature is defined in the spice file.

When we design analog circuits, we must test over all temperatures (-40,25,125), all supply voltages (1.7,1.8,1.9), process corners (slow, typical, fast), statistical variation (local variation, global variation) and maybe much more.

The number of corners (one permuatation of the conditions above) we have to check can easily be in the ten’s to hundreds of corners.

Now imagine, that you have to change each of the 100 spice files, just to add the corner.

Hopefully, that’s enough to convince you that you should never manually write the corners into the SPICE file. You need automation!

Principles

cicsim takes a testbench template, and a set of corners as input.

The script proceeds to generate all the necessary SPICE files, and runs the corners

An example can be seen in the tests/sim/ directory.

For example, to run typical corner, over temperature/voltage and monte-carlo local variation I can do.

cd ngspice/basic ; make -n all
make[2]: Entering directory '/__w/cicsim/cicsim/tests/sim/ngspice/basic'
cicsim run --name Sch_typical tran  Sch  Tt Vt
cicsim run --name Sch_tempvall tran  Sch "Tt,Th,Tl" "Vt,Vh,Vl"
cicsim run --name Sch_mc --count 10 tran  Sch Tt Vt
cicsim summary --output "README.md"
make[2]: Leaving directory '/__w/cicsim/cicsim/tests/sim/ngspice/basic'

And I get a summary of the simulation results.

Name Parameter Description   Min Typ Max Unit
Divider voltage vx   Spec 0.80 0.90 1.00 V
      Sch_typical   0.89    
      Sch_tempvall 0.79 0.89 0.99  
      Sch_3std 0.85 0.90 0.94  

Why is this cool?

If you’re new to analog simulation, then you might not understand what just happened.

You might even say “I want a testbench in Xschem, I can just put the corners as a symbol”, or “This seems way too complicated, I just want something easy”.

If you’re new, then you might not listen to advice, but here is some advice anyway.

You may have to re-run all simulations in 5 years when your customer complains, and your circuit does not work. You will have forgotten how to run the testbenches, or indeed, what corners you simulated, so you better write it down. If you make sure that all the testbenches, and simulations, can be run with one command, for example make all, then your life will be easier.

It’s always worth the effort to go the extra mile to setup automatic simulations.

Warning: I wrote cicsim for me, no-one else. That means, it might not be for you.

I’ve forced approx 50 students to use cicsim, and in the end, I do think they understood why it’s necessary. You may also understand one day.

The cicsim.yaml and corners

The “corners” are the Sch "Tt,Th,Tl" "Vt,Vh,Vl" part of the command above. But somehow cicsim must know what those corners mean.

All corner information is in the cicsim.yaml file. cicsim will search the current directory, and the parent directory for a cicsim.yaml file.

The directory structure I use often looks like sun_pll_sky130nm/sim

ROSC          # ring oscillator simulation
- cicsim.yaml # local ring oscillator setup 
SUN_PLL_BUF   # buffer simulation
- cicsim.yaml # local buffer setup 
SUN_PLL       # top level simulation 
- cicsim.yaml # local top  setup
cicsim.yaml   # where all the magic happens 

The local setup files usually don’t contain much info. Something like this is sufficient

corner:
  Lay: ''
  Sch: ''
ngspice:
  cell: SUN_PLL_BUF

The corners “Lay” (Layout) and “Sch” (Schematic) are defined locally. For analog simulations we also need to re-run simulations after we’ve done the layout, and extracted parasitics. That’s why there are two “corners”, or “views” (Sch,Lay)

In the “magic” cicsim.yaml all the corners are defined.

An excerpt from the file above is shown below.

corner:
  Tt: |
    .lib "../../../tech/ngspice/temperature.spi" Tt
  Tl: |
    .lib "../../../tech/ngspice/temperature.spi" Tl
  Th: |
    .lib "../../../tech/ngspice/temperature.spi" Th
  Vt: |
    .lib "../../../tech/ngspice/supply.spi" Vt
  Vl: |
    .lib "../../../tech/ngspice/supply.spi" Vl
  Vh: |
    .lib "../../../tech/ngspice/supply.spi" Vh
  Att: |
    .lib  "$PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
  Asf: |
    .lib  "$PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice" sf

cicsim will insert the text defined in cicsim.yaml for a corner into the generated spice file

For example, the command cicsim run tran Sch Att Th Vh would read tran.spi and insert the following into the generated spice file output_tran\tran_SchAttThVh.spi


.lib "../../../tech/ngspice/temperature.spi" Th

.lib "../../../tech/ngspice/supply.spi" Vh

.lib  "$PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice" tt

The testbench (tran.spi)

An example testbench can be seen below (from ngspice/basic/tran.spi)

You can write anything in the SPICE file that ngspice understands. cicsim does have some advanced features (like the #ifdef), however, you don’t need to use them if you don’t want to.

ngspice/basic/tran.spi:

*Demo of cicsim
*-----------------------------------------------------------------
* OPTIONS
*-----------------------------------------------------------------
.option TNOM=27 GMIN=1e-15 reltol=1e-3
*-----------------------------------------------------------------
* PARAMETERS
*-----------------------------------------------------------------
.param TRF = 10p
.param AVDD = {vdda}
*-----------------------------------------------------------------
* FORCE
*-----------------------------------------------------------------
VSS  VSS  0     dc 0
VDD  VDD_1V8  VSS  dc {AVDD}
*-----------------------------------------------------------------
* DUT
*-----------------------------------------------------------------
R1 VDD_1V8 VX 1k m=agauss(1,0.1,3)
R2 VX VSS 1k m=agauss(1,0.1,3)
*----------------------------------------------------------------
* PROBE
*----------------------------------------------------------------
#ifdef Debug
.save all
#else
.save i(R1) v(VX)
#endif
*----------------------------------------------------------------
* NGSPICE control
*----------------------------------------------------------------
.control
set num_threads=8
set color0=white
set color1=black
unset askquit
optran 0 0 0 100p 2n 0
tran 10p 10n 1p
write
quit
.endc
.end

The output file

cicsim will always make a output_<testbench> directory, and write the corner files there. The simulation also runs from the output directory.

As such, if you have any reference to other spice files, for example the device under test, then you need to remember the extra “../”

For example, if you have the files

tran.spi
xdut.spi 

then, inside tran.spi you would reference the xdut.spi with

.include "../xdut.spi"

The simulation run

cicsim will tell you what it does. For example:

cicsim run --name Sch_typical tran  Sch  Tt Vt

First you will see the simulation running, we can also see the exact command that cicsim uses to run the output spice file.

Running tran_SchTtVt
cd output_tran; ngspice -b  tran_SchTtVt.spi -r tran_SchTtVt.raw 2>&1 |tee tran_SchTtVt.log
...
Corner simulation time : 0:00:00.077743

After the simulation has completed, cicsim will search for a <testbench>.meas file. A “meas” file is really just a SPICE file, but it contains only measurements.

Below is the output from the measurement section of the cicsim run

cd output_tran; ngspice -b tran_SchTtVt.meas  2>&1 | tee tran_SchTtVt.logm
...
vx                  =  8.971411e-01 from=  0.000000e+00 to=  1.000000e-08
...
Writing output_tran/tran_SchTtVt.yaml
Total simulation time : 0:00:00.131207

The measurements are stored into a YAML file for that particular corner

After all simualtions have completed, then cicsim will search for a <testbench.py> file. Here you could have a additional python post processing for the YAML file, or automatically generate plots, or whatever you want.

Running tran.py with output_tran/tran_SchTtVt
Output contains following parameters ['vx']

If there is a <testbench.yaml> file, then cicsim will generate a summary of the simulation results. In the YAML file the name, min, typ, max, scale, digits, units and others are defined.

|      vx | name         | time                     | type   |
|---------|--------------|--------------------------|--------|
| 0.89714 | tran_SchTtVt | Fri Jul 26 22:47:52 2024 | Sch    |
Writing CSV results/tran_Sch_typical.csv

The measurement file

It’s a good idea to separate long simulations and the measurements you do on the waveforms. It’s no fun to rerun hours of simualtion just because you got the measurement wrong.

During debugging on of the measurements it’s also an advantage to be able to not re-run the simulation. The option “–no-run” does just that

cicsim run --name Sch_typical tran  Sch  Tt Vt --no-run

The measurement file is shown below. The “{cicname}” will be replaced by what the corner is called.

Any measurement between “MEAS_START” and “MEAS_END” will be post processed by cicsim

ngspice/basic/tran.meas:

* Measure
.control

load {cicname}.raw

echo "MEAS_START"

meas tran vx avg v(vx)


echo "MEAS_END"
.endc

The python file

An example of a python script is shown below

ngspice/basic/tran.py:

import sys
import os
import cicsim
import yaml

@cicsim.SimCalcYaml
def main(fname,df):
    df.to_csv(fname + ".csv")
    print("Output contains following parameters " + str(list(df.columns)))


The attribute @cicsim.SimCalcYaml will read in the corner output YAML file and load it as a dataframe. The fname option is the name of the corner (tran_SchThVh).

The python script can do whatever you want.

Warning cicsim will happily execute all python code. So be careful running simulations from others.

Advanced features

ifdef/else

In the testbench you can use the #ifdef/else syntax

#ifdef Debug
.save all
#else
.save i(R1) v(VX)
#endif

For example,

cicsim run --name Sch_typical tran Debug Sch  Tt Vt

would include the

.save all

into the output SPICE file, while

cicsim run --name Sch_typical tran  Sch  Tt Vt

would include

.save i(R1) v(VX)

–replace

The replace option takes a YAML file with keywords to replace in the testbench.

ngspice does not support parameters in the .control section, and sometimes it’s useful to have parameters that span multiple testbenches.

Take the “replace.yaml” below

clock_period: 250e-9
nbpt: 6*(9+9+2)
temp_sweep: -25 0 25 50 75 100

And running

cicsim run  --replace replace.yaml --name Sch_typical tran Sch Gt Kss Tt Vl

In the testbench I have the line

tran [{clock_period}/50] [{clock_period}*(16+{nbpt})] [{clock_period}*16]

The first thing cicsim will do is to replace the keywords, resulting in

tran [250-9/50] [250e-9*(16+6*(9+9+2))] [250e-9*16]

As of yet, the above statement is not a valid ngspice line. cicism also has the ability to parse expressions

[expression]

In the testbench cicsim will attempt to evaulate any expression matching the regular expression pattern “\s+[([^]]+)]” (translated to human speak: anything that starts with a space, and is inside brakets [ ], for example “ [2*2]”)

There is a conflict with the pattern used by d_cosim, but so far it seems to be ignored (“adut [ din ] [ dout ] null dut”).

The previous line

tran [250-9/50] [250e-9*(16+6*(9+9+2))] [250e-9*16]

would in the output spice file be replaced by

tran 5e-09 3.4e-05 4e-06

Don’t run simulations if nothing has changed

A feature that is by default turned off is the hashing algorithm for all includes.

If you add the below to your cicsim.yaml the hashing will be enabled.

options:
  sha: True

As cicsim reads the testbench it will search for “.include” and “.lib” statements and find all referenced files. For each of the file it will generate a SHA1 hash, which will be saved to a “.sha” file in the “output_” directory.

The sha file may look like this

../../../tech/ngspice/supply.spi: 979119a85f3fabb437b7966ff0840ad719b7d206cd755226625b69abc60da697
../../../tech/ngspice/temperature.spi: ae70f497d3f601cb938b76585b4635003d9221716e3ea52c012fe76e609a052b
../../../work/lpe/SUNSAR_CDAC8_CV_lpe.spi: 9d54993102e1964767a62b1639da4fb0f507a7de2af961b788f1e546c591ec8a
../../../work/xsch/JNWG00_CORE.spice: 9461bb25fb0f2fd3fefcedc1376fdbba669f14ea6779e92c57de6db2efceb28b
../xdut.spi: be90e69e17ab34394c447c100c8a15bb2611138c390a57175b97e1ff565c5b3c
/opt/pdk/share/pdk/sky130A/libs.ref/sky130_fd_pr/spice/sky130_fd_pr__nfet_01v8__ff.pm3.spice: 7f5d15ae3bdc466cab33f6c85bedb7293e20b1e6932c924a0a288aa5aef00290
/opt/pdk/share/pdk/sky130A/libs.ref/sky130_fd_pr/spice/sky130_fd_pr__nfet_01v8__mismatch.corner.spice: 24a3b29d7d7f26f99098811c31eb5497950a3aed520ab83768a96f35467fe818
/opt/pdk/share/pdk/sky130A/libs.ref/sky130_fd_pr/spice/sky130_fd_pr__pfet_01v8__ff.corner.spice: 30ba71bde4024ff291a68352930bf2fc3e07f627efb090e1575b9dbd7b6e75da
/opt/pdk/share/pdk/sky130A/libs.ref/sky130_fd_pr/spice/sky130_fd_pr__pfet_01v8__mismatch.corner.spice: f4d560668712678ea60641de923fce7691e64acda951949f840986837c31741a
/opt/pdk/share/pdk/sky130A/libs.tech/ngspice/all.spice: ada86dba53017af314dfa04dac253c8424e0941158cf476a7ca191c55aea3bbb
/opt/pdk/share/pdk/sky130A/libs.tech/ngspice/corners/ff/nonfet.spice: d03e3604898888b64c71a6ddba5f03dd8a05b298ac6a10a5dc2ae1c01ab73a74
/opt/pdk/share/pdk/sky130A/libs.tech/ngspice/corners/ff/specialized_cells.spice: 3f6580d1d5c7997a747964fee6e145dddfc2aaa66d18e0707fe76972a436f127
/opt/pdk/share/pdk/sky130A/libs.tech/ngspice/r+c/res_typical__cap_typical.spice: 7c29723076a22fe5b080d245adb97451de5ad0812f0344f6bfe31c9697b71f0d
/opt/pdk/share/pdk/sky130A/libs.tech/ngspice/r+c/res_typical__cap_typical__lin.spice: 4deedaa4be27f2e1f083567a945cbaed02027e018fcd09431e57a02c08a117b1
stable_SchGtKffTtVh.meas: ff0710ed4624e7c3d15fb37cfef10afd60e5e530c40db85889fd066f177dd88f
stable_SchGtKffTtVh.spi: ca62315665f25645032688195273ac4c454d4b99274994e3bab23d8ab2412832

Should the SHA file already exist for the corner, then cicsim will read all the SHA’s and check if any of the input files have changed (compare new versus old hash). If none of the SHAs have changed, then cicsim will skip simulating again.

It does take a bit of time to calculate the SHA’s, but it’s much faster than running the simulation.

I find it a useful feature to check if I’ve modified anything. Also, I don’t need to worry about re-running simulations. If nothing has changed, then the simulation won’t run.

Further information

For a tutorial on cicsim I’d recommend rply_ex0_sky130nm