Affects: 8.3.681 (4432d7ec, current HEAD).
Introduced by: b266ec6.
Not affected: 8.3.637 (d157eea7) and everything before b266ec6.
Summary
b266ec6 implemented the resistor "pi" model, which splits a resistor's body
capacitance between its two terminals. In the flat netlist writer
(ext2spice/ext2spice.c) the spcdevResPi() call is guarded — it sits inside
if (dev->dev_nterm > 2) and is not reached for DEV_MSUBCKT. In the
hierarchical writer (ext2spice/ext2hier.c) the corresponding call is
unguarded:
if ((dev->dev_class == DEV_RSUBCKT) && esDoResistorTee)
{
/* ... tee handling ... */
}
else
{
spcdevResPi(hc->hc_hierName, gate->dterm_node->efnode_name->efnn_hier,
source->dterm_node->efnode_name->efnn_hier,
drain->dterm_node->efnode_name->efnn_hier,
"subckt");
}
That else is reached for the whole case group — DEV_SUBCKT, DEV_CSUBCKT,
DEV_DSUBCKT, DEV_MSUBCKT and DEV_RSUBCKT alike. spcdevResPi() then
takes dev_terms[0]'s node capacitance, halves it onto the source and drain
terminals, and zeroes the original node:
rescap = nngate->efnn_node->efnode_cap / 2.0;
nnsource->efnn_node->efnode_cap += rescap;
nndrain->efnn_node->efnode_cap += rescap;
nngate->efnn_node->efnode_cap = 0;
For a MOSFET, dev_terms[0] is the gate, so gate-to-substrate capacitance
is removed and redistributed onto drain and source. For a two-terminal diode
subcircuit whose other terminal is the substrate, all of it lands on the
substrate node and leaves the netlist entirely.
ext2spice resistor tee on does not avoid this: for a non-DEV_RSUBCKT device
the (dev->dev_class == DEV_RSUBCKT) && esDoResistorTee condition is false, so
the else branch still runs.
Reproducer
Four files, sky130A the only external dependency. The two .ext inputs are
committed, so the same input file is fed to every case — no re-extraction,
no layout, no version-dependent front end. run.sh emits two tcl scripts that
are identical except for the single line ext2spice hierarchy off.
SKY130A_MAGICRC=/path/to/sky130A/libs.tech/magic/sky130A.magicrc \
MAGIC=/path/to/magic ./run.sh
Run it against any revision before b266ec6 and against 8.3.681.
Inputs (paste these two files next to run.sh)
sky130a_diode.ext
timestamp 0
version 8.3.637
tech sky130A
style ngspice()
scale 1000 1 500000
resistclasses 4400000 2200000 950000 3050000 120000 197000 114000 191000 120000 197000 114000 191000 48200 319800 2000000 48200 48200 12800 125 125 47 47 29 5
parameters sky130_fd_pr__diode_pw2nd_05v5 a=area*1e+12 p=perim*1e+06
port "D1" 2 0 0 0 0 ndic
port "D2" 1 0 -130 0 -130 psc
node "D1" 19 174.293 0 0 ndic 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6468 328 7020 336 0 0 0 0 0 0 0 0 0 0
substrate "D2" 0 0 0 -130 psc 0 0 0 0 0 0 0 0 0 0 35360 2080 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 35360 2080 0 0 0 0 0 0 0 0 0 0 0 0
device msubckt sky130_fd_pr__diode_pw2nd_05v5 -45 -45 -44 -44 a=8100 p=360 "None" "D1" 132 0 "D2" 0 0
sky130a_inverter.ext
timestamp 2083213696
version 8.3.637
tech sky130A
style ngspice()
scale 1000 1 500000
resistclasses 4400000 2200000 950000 3050000 120000 197000 114000 191000 120000 197000 114000 191000 48200 319800 2000000 48200 48200 12800 125 125 47 47 29 5
parameters sky130_fd_pr__nfet_01v8 l=l w=w a1=as p1=ps a2=ad p2=pd
parameters sky130_fd_pr__pfet_01v8 l=l w=w a1=as p1=ps a2=ad p2=pd
port "vss" 1 35 45 35 45 li
port "vout" 4 137 45 137 45 li
port "vdd" 2 35 345 35 345 li
port "vin" 3 86 -55 86 -55 li
port "vdd" 2 267 345 267 345 li
port "vss" 1 267 45 267 45 li
node "vss" 155 28.0344 35 45 li 0 0 0 0 0 0 0 0 5964 310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4356 264 0 0 0 0 0 0 0 0 0 0 0 0
node "vout" 450 208.751 137 45 li 0 0 0 0 0 0 0 0 5964 310 5964 310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8712 528 20764 832 0 0 0 0 0 0 0 0 0 0
node "vdd" 246 27.5363 35 345 li 0 0 0 0 0 0 0 0 0 0 5964 310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4356 264 0 0 0 0 0 0 0 0 0 0 0 0
node "vin" 847 295.611 86 -55 li 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18348 1152 0 0 9372 548 0 0 0 0 0 0 0 0 0 0 0 0
node "vdd" 2285 226.443 267 345 li 0 0 0 0 63840 1096 0 0 6888 332 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4356 264 0 0 0 0 0 0 0 0 0 0 0 0
substrate "vss" 0 0 267 45 li 0 0 0 0 0 0 0 0 0 0 6888 332 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4356 264 0 0 0 0 0 0 0 0 0 0 0 0
cap "vin" "vss" 12.5929
cap "vout" "vss" 32.22
cap "vout" "vin" 48.0889
cap "vss" "vdd" 0.76403
cap "vin" "vdd" 42.8718
cap "vss" "vdd" 5.23853
cap "vin" "vdd" 9.77407
cap "vout" "vdd" 37.7022
cap "vout" "vdd" 32.4047
cap "vdd" "vdd" 3.56562
device msubckt sky130_fd_pr__nfet_01v8 71 0 72 1 l=30 w=84 "vss" "vin" 60 0 "vss" 84 5964,310 "vout" 84 5964,310
device msubckt sky130_fd_pr__pfet_01v8 71 300 72 301 l=30 w=84 "vdd" "vin" 60 0 "vdd" 84 5964,310 "vout" 84 5964,310
run.sh
#!/usr/bin/env bash
# Same magic binary, same .ext input, same ext2spice options -- the only
# variable is `ext2spice hierarchy off`. Run this against 8.3.637 (or any
# revision before b266ec6) and against 8.3.681; the flat column agrees across
# all of them, the hierarchical column does not.
set -eu
MAGIC="${MAGIC:-magic}"
RC="${SKY130A_MAGICRC:?set SKY130A_MAGICRC to sky130A.magicrc}"
emit_tcl() { # $1 = cell, $2 = hier|flat
cat <<TCL
ext2spice lvs
ext2spice subcircuit on
ext2spice merge conservative
ext2spice cthresh 0
$([ "$2" = flat ] && echo "ext2spice hierarchy off")
ext2spice -o out_$2.spice $1
quit -noprompt
TCL
}
echo "magic: $("$MAGIC" --version 2>&1 | head -1)"
for cell in sky130a_diode sky130a_inverter; do
echo "=== $cell ==="
for mode in hier flat; do
emit_tcl "$cell" "$mode" > "run_${mode}.tcl"
"$MAGIC" -dnull -noconsole -rcfile "$RC" "run_${mode}.tcl" >/dev/null 2>&1
printf ' %-5s writer:\n' "$mode"
caps=$(grep -E '^C' "out_${mode}.spice" || true)
if [ -n "$caps" ]; then
printf '%s\n' "$caps" | sed 's/^/ /'
else
echo " (NO capacitance emitted)"
fi
done
done
Case 1 — diode, capacitance lost entirely
magic: 8.3.637.637 magic: 8.3.681.681
=== sky130a_diode === === sky130a_diode ===
hier writer: hier writer:
C0 D1 D2 0.17429f (NO capacitance emitted)
flat writer: flat writer:
C0 D1 D2 0.17429f C0 D1 D2 0.17429f
Three of the four cases agree. Only 8.3.681's hierarchical writer differs —
and it differs from its own flat writer on the same input file.
Case 2 — MOSFET, capacitance moved off the gate
magic 8.3.681.681, flat magic 8.3.681.681, hier
C3 vout vss 0.24097f C3 vout vss 0.39507f
C4 vdd vss 0.25998f C4 vdd vss 0.41408f
C5 vin vss 0.3082f <no `vin vss` element>
vin is the gate net. The arithmetic is exact:
0.39507 - 0.24097 = 0.15410
0.41408 - 0.25998 = 0.15410
0.15410 * 2 = 0.30820 == the removed `vin vss` element
Total capacitance is conserved (0.98000 fF either way), so this is invisible to
any check on total extracted capacitance — the capacitance is on the wrong
terminals rather than missing.
Expected
The hierarchical writer should agree with the flat writer: the diode's C0
retained, and C5 vin vss 0.3082f retained with drain and source unchanged.
Impact
Gate-to-bulk capacitance is one of the parasitics that matters most for analog
extraction; moving it to drain and source understates input loading and
overstates junction capacitance. The diode case loses the value outright.
This is not confined to one cell. Across a 121-witness corpus extracted through
both versions, reading each netlist's own gate nets from pin 1 of its four-pin
devices, 70 of 74 eligible witnesses lose more than half the capacitance
attached to a gate net. (74 rather than the full set: 29 witnesses were
excluded because a separate condition, present identically in both versions and
unrelated to this report, makes their capacitance sums unsuitable for this
comparison.)
Suggested fix
Restrict the spcdevResPi() call in ext2hier.c to the resistor classes, so
the hierarchical writer carries the guard the flat writer already has.
Note on the pi model itself
The pi model is a real improvement where it applies, and this report is not an
argument for reverting b266ec6. Comparing the capacitance recorded in each
.ext against the capacitance emitted in the corresponding netlist, across the
same 121-cell corpus, b266ec6 reduces unaccounted capacitance from 4.7% to
2.3% overall — resistor cells that previously lost their body capacitance now
account for it exactly. Only the classes the guard should have excluded are at
issue here.
Relationship to #250
b266ec6 appears to be the fix for #250 (floating node from generic resistor
parasitics), and it does resolve that: on the corpus above, resistor cells now
account for their body capacitance exactly. This report is not a request to
revert it — only to restore the class guard in the hierarchical writer so the
fix stays confined to resistors.
Affects: 8.3.681 (
4432d7ec, current HEAD).Introduced by:
b266ec6.Not affected: 8.3.637 (
d157eea7) and everything beforeb266ec6.Summary
b266ec6implemented the resistor "pi" model, which splits a resistor's bodycapacitance between its two terminals. In the flat netlist writer
(
ext2spice/ext2spice.c) thespcdevResPi()call is guarded — it sits insideif (dev->dev_nterm > 2)and is not reached forDEV_MSUBCKT. In thehierarchical writer (
ext2spice/ext2hier.c) the corresponding call isunguarded:
That
elseis reached for the whole case group —DEV_SUBCKT,DEV_CSUBCKT,DEV_DSUBCKT,DEV_MSUBCKTandDEV_RSUBCKTalike.spcdevResPi()thentakes
dev_terms[0]'s node capacitance, halves it onto the source and drainterminals, and zeroes the original node:
For a MOSFET,
dev_terms[0]is the gate, so gate-to-substrate capacitanceis removed and redistributed onto drain and source. For a two-terminal diode
subcircuit whose other terminal is the substrate, all of it lands on the
substrate node and leaves the netlist entirely.
ext2spice resistor tee ondoes not avoid this: for a non-DEV_RSUBCKTdevicethe
(dev->dev_class == DEV_RSUBCKT) && esDoResistorTeecondition is false, sothe
elsebranch still runs.Reproducer
Four files, sky130A the only external dependency. The two
.extinputs arecommitted, so the same input file is fed to every case — no re-extraction,
no layout, no version-dependent front end.
run.shemits two tcl scripts thatare identical except for the single line
ext2spice hierarchy off.Run it against any revision before
b266ec6and against 8.3.681.Inputs (paste these two files next to
run.sh)sky130a_diode.extsky130a_inverter.extrun.shCase 1 — diode, capacitance lost entirely
Three of the four cases agree. Only 8.3.681's hierarchical writer differs —
and it differs from its own flat writer on the same input file.
Case 2 — MOSFET, capacitance moved off the gate
vinis the gate net. The arithmetic is exact:Total capacitance is conserved (0.98000 fF either way), so this is invisible to
any check on total extracted capacitance — the capacitance is on the wrong
terminals rather than missing.
Expected
The hierarchical writer should agree with the flat writer: the diode's
C0retained, and
C5 vin vss 0.3082fretained with drain and source unchanged.Impact
Gate-to-bulk capacitance is one of the parasitics that matters most for analog
extraction; moving it to drain and source understates input loading and
overstates junction capacitance. The diode case loses the value outright.
This is not confined to one cell. Across a 121-witness corpus extracted through
both versions, reading each netlist's own gate nets from pin 1 of its four-pin
devices, 70 of 74 eligible witnesses lose more than half the capacitance
attached to a gate net. (74 rather than the full set: 29 witnesses were
excluded because a separate condition, present identically in both versions and
unrelated to this report, makes their capacitance sums unsuitable for this
comparison.)
Suggested fix
Restrict the
spcdevResPi()call inext2hier.cto the resistor classes, sothe hierarchical writer carries the guard the flat writer already has.
Note on the pi model itself
The pi model is a real improvement where it applies, and this report is not an
argument for reverting
b266ec6. Comparing the capacitance recorded in each.extagainst the capacitance emitted in the corresponding netlist, across thesame 121-cell corpus,
b266ec6reduces unaccounted capacitance from 4.7% to2.3% overall — resistor cells that previously lost their body capacitance now
account for it exactly. Only the classes the guard should have excluded are at
issue here.
Relationship to #250
b266ec6appears to be the fix for #250 (floating node from generic resistorparasitics), and it does resolve that: on the corpus above, resistor cells now
account for their body capacitance exactly. This report is not a request to
revert it — only to restore the class guard in the hierarchical writer so the
fix stays confined to resistors.