Simulation

Simulations are run by cicsim from the IP’s sim/<CELL>/ directory. This repository supplies the corner definitions every IP shares and the template a new testbench is scaffolded from.

Corner names

A run picks one value from each of four groups. The names are terse, so:

Group Names Means
General Gt Nothing; a placeholder so the position is always filled
Process Ktt Kss Kff Ksf Kfs Khh Khl Klh Kll One PDK corner name, see below
Temperature Tt 27 °C, Tl −40 °C, Th 125 °C, Tm 42.4 °C ngspice/temperature.spi
Supply Vt 1.8 V, Vl 1.7 V, Vh 1.9 V ngspice/supply.spi

The two letters after K are one sky130 corner name, not two independent choices, and they come in two families:

Corner FET models Resistance Capacitance
Ktt tt typical typical
Kss ss high high
Kff ff low low
Ksf sf typical typical
Kfs fs typical typical
Khh tt high high
Khl tt high low
Klh tt low high
Kll tt low low

So in Kss/Kff/Ksf/Kfs the letters name the device corner, nfet then pfet, and the parasitics come along for the ride: slow devices are shipped with high R and high C, fast devices with low R and low C, and the skewed corners keep typical RC. In Khh/Khl/Klh/Kll the devices are typical and the letters name the parasitic corner instead, resistance then capacitance.

Kss is therefore not “slow devices, typical parasitics” — it is slow devices with the pessimistic RC as well. If you want a device corner against a different RC set, there is no name for it; run the RC corner separately.

Add mm for mismatch (Kttmm, Kssmm, …), which sets mc_mm_switch=1 on top of the same includes. Kmc is different again: it includes only the Monte Carlo parameter files with mc_pr_switch=1, for process variation.

The A... names are the same corners written the short way: Att is a one-line .lib into the PDK’s own sky130.lib.spice, where K... spells out every include that section would pull in. Amctt is the PDK’s tt_mm section. Use whichever you prefer; they resolve to the same models.

Two more, from the local cicsim.yaml the testbench template writes: Sch and Lay. Both are empty corners. They add nothing to the netlist and exist so the view ends up in the result directory name, and so tran.spi can switch its include on Lay.

Run one directly:

cicsim run --name Sch_typical tran Sch Gt Ktt Tt Vt

Give a comma separated list in any position and cicsim runs the cross product:

cicsim run --name Sch_etc tran Sch Gt "Kss,Kff,Ksf,Kfs" "Th,Tl" "Vl,Vh"

That is 16 runs. You will not normally type this; the Makefile does.

Make a testbench

From sim/:

make cell LIB=MY_IP_SKY130A CELL=MY_CELL

which runs cicsim simcell over cicsim/cell_spice/template.yaml and creates sim/MY_CELL/ with seven files, already git added:

File Yours to edit
tran.spi The testbench. Stimulus, probes, analysis
tran.meas What to measure from the raw file
tran.py Optional python post-processing
tran.yaml Specification limits per measurement
summary.yaml Which result sets go in the table
Makefile TB, VIEW, and the corner targets
cicsim.yaml Local options; usually left alone

tran is just the testbench name. Copy the five tran.* files to ac.* and set TB=ac for a second one.

Run it

From sim/<CELL>/:

make            # typical, etc, mc, then summary
make typical    # one corner, quickest feedback
make tfs        # all 27 process/temperature/supply combinations
make mc         # 30 Monte Carlo runs
make temp       # temperature sweep only

Every target depends on netlist, so the netlist is regenerated first and cannot go stale.

Post-layout is the same command with a different view:

make typical VIEW=Lay

which requires make lpe to have run in work/ first.

Get a table out

Measurements are printed between MEAS_START and MEAS_END in tran.meas:

meas tran vout_final FIND v(OUT) AT=9n
print vout_final

cicsim parses those into the result yaml. Give a measurement limits in tran.yaml and it gets judged:

vout_final:
  name: Output voltage at 9 ns
  min: 0.8
  max: 1.0
  unit: V
  digits: 3

Then:

make summary    # writes README.md with the table

summary.yaml decides the columns, and its method decides how each result directory is reduced: the median over the typical set, the real min and max over the etc set, and mean ± 3σ over the Monte Carlo set. Add a Lay_typ entry pointing at results/tran_Lay_typical to get the post-layout column beside the schematic one. See summary.yaml for the exact reductions.

Only measurements that some entry in tran.yaml names in its src reach the table at all, so a measurement you added and cannot find is usually one you never gave a spec.

The README that comes out is what the documentation action publishes, so a simulation that is worth keeping should end with make summary.

Where a derived number belongs

If a figure of merit needs more than ngspice can do, an ENOB from a measured SNR or a yield from a Monte Carlo set, compute it in tran.py. Delete the early return, read the result yaml, add your key, write it back. It then behaves like any other measurement, including its specification limits.

Keeping corners in sync after a PDK update

The process corners are generated, not hand written:

cd py     && make parse && make process > /tmp/corners.yaml   # regenerate
cd ngspice && make corners                                    # expand to spice

py/genyaml walks the PDK’s sky130.lib.spice and prints the corner: blocks for cicsim/cicsim.yaml; ngspice/corners.py expands that yaml into ngspice/corners.spi for anyone simulating without cicsim. Commit both.