Skip to content

analog: PSP103 drain and gate charge tables (tt, ss), the charge-aware table solver, the 500 fF inverter within 0.02 % of ngspice (Closes #10880) - #10893

Draft
gHashTag wants to merge 1 commit into
masterfrom
claude/analog-m3c-charge-10777
Draft

gHashTag wants to merge 1 commit into
masterfrom
claude/analog-m3c-charge-10777

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

What

M3c of #10486, the charge half of #10777. The IHP SG13G2 sg13_lv_nmos / sg13_lv_pmos PSP103 drain and gate charge as tables on the M3b grid (corners tt and ss), the M3a table solver generalized to use them, and the CMOS inverter at 500 fF checked against ngspice backward Euler on the same grid: both 50 percent delays are within 0.020 percent and 0.0041 percent (the issue asked for 1 percent; today, without charge, 3.09 and 3.63 percent).

Closes #10880. Part of #10777. Part of #10486. The weak-inversion read is not done here (see #10881); charge for ff, fs, sf is #10882.

The tables

model IHP-Open-PDK commit 21133f219b1a6da10d803e345761e77415ceff40, cornerMOSlv.lib (PSP 103.6 rev 200310), psp103.va compiled by OpenVAF-Reloaded v24.0.2mob; ngspice-47
corners mos_tt, mos_ss, 27 C (TNOM 27 C)
devices sg13_lv_nmos W 1.0 um, sg13_lv_pmos W 3.0 um, L 0.45 um, ng 1, m 1 (the same netlist lines as psp103_tab_tt.cir)
grid 25 x 25 nodes of 62.5 mV over 0 .. 1.5 V on Vgs (Vsg) and Vds (Vsd), index ix * 25 + iy: M3a's reader as it is
tables TT_QD_N TT_QG_N TT_QD_P TT_QG_P and the SS_ four: drain and gate charge of each device
unit Q / 500 fF in units of 2^-24 V (the voltage a 500 fF node moves; one unit is 2.98e-20 C), relative to the origin Vgs = Vds = 0, plus 2^21 so that no entry is negative; the PMOS tables are negated so every drain table grows with Vds and every gate table with Vgs
decks specs/analog/decks/psp103_q_tt.cir, psp103_q_ss.cir (new); psp103_inv_be_500f.cir now also prints the 401-sample waveform (its two .meas numbers are unchanged)

Which variables. PSP103 reports no terminal charge as an output variable: show on the OSDI instance lists currents (ids ige ...), conductances (gm gmb gds) and capacitances (cdd cdg cgg cgd ..., with the overlaps cgsol cgdol and the junctions cjs cjd each reported apart), and no q*. So the deck takes the whole terminal charge (channel, overlap, fringe, junction) from the terminal current of one backward Euler step, which is what ngspice integrates: i(B) = Idc(B) + (Q(B) - Q(A)) / dt. A staircase B source visits each grid node B from the origin A and goes back; the return step is the one measured (its end state is the off origin, so the subtracted DC current is picoamps, not the 400 uA of a node in strong inversion). dt = 10 ns, long against the model's gate resistance; the same tables at dt = 1 ns differ by at most 52 units of a 277901-unit range (checked offline, not a test).

FR-001, one run. The same deck run prints grid_n / grid_p, which are byte for byte TT_N, TT_P (and SS_N, SS_P for the ss deck): the current tables and the charge tables come from one run of one netlist. All pasted arrays were regenerated from the committed decks and compared with the spec, identical (12 tables, the 401-sample waveform, the six op-point numbers).

An independent check. The tables are made from currents and never saw ngspice's own capacitance numbers. At Vgs = 0.9 V, Vds = 0.75 V (the deck's op_n block prints cdd cdg cgg cgsol cgdol cjd) the slopes of TT_QD_N and TT_QG_N are within 0.16 %, 0.29 % and 0.19 % of cdd + cjd + cgdol, -(cdg + cgdol) and cgg + cgsol + cgdol (1024, -2013, 4739 aF). The ss table's gate-drain coupling at the same point is 2.6 % off tt's, which the 1 % test sees.

The solver (FR-002, FR-003)

mos_table.t27 is generalized, not copied. The output node holds C y + Qd_n - Qd_p, so the step is

(y - y0) + (qn(A, y) - qn(A0, y0)) - (qp(Vdd - A, Vdd - y) - qp(Vdd - A0, Vdd - y0)) + In - Ip = 0

with A0 the gate of the previous step (a gate edge is a jump of charge, the Miller kick). The Newton derivative adds the Vds slope of each charge table, read off the same table as the value (gds_cell; a test over all 576 cells of all eight tables). New: gq_pos gq_neg gq_deriv g_resid_q newton_iter_q inv_step_q board_step_q run_fix_q (u32 words, no float) and newton_iter_qf run_tabf_q (f64 side), monotone_along, NOQ. The current-only functions (newton_iter, inv_step, board_step, run_fix, g_resid, newton_iter_f, run_tabf) are now the charge-aware ones with the zero tables NOQ: same signatures, same values; all 14 M3a tests and M3a's pinned run (EXP_S) pass unchanged. python3 -I tools/dupe_scan.py: no new duplicate body.

Three new tests in mos_table.t27: zero charge tables give the current-only path (77 asserts); a synthetic plane of charge with no device current gives the closed-form gate-edge kick and load (y0 - K1' Vdd / (1 + K2'), within 3 units, for an NMOS-side and a PMOS-side charge; a still gate with any charge does not move the node); the mirror holds bit for bit with the four tables swapped (63 states). That last one pins the sign convention of the negated PMOS table.

The inverter against ngspice (tt, 500 fF, dt = 100 ps, 400 steps, same grid)

ngspice backward Euler model with charge gap without charge (M3b)
tpLH (steps of 100 ps) 13.4352 13.4379 0.020 % (0.27 ps longer) 3.09 %
tpHL 10.1830 10.1826 0.0041 % (0.04 ps shorter) 3.63 %
waveform, worst of 401 samples 6693 units (0.40 mV, 0.027 % of Vdd) 438317 units (26 mV)
after step 0 / after the falling edge (step 200) 27 units (1.6 uV) / 2566 units (0.15 mV)

The tests hold: both delays within 1 % (the issue's bar), then the measured values from both sides (0.020 %: between 0.019 and 0.021; 0.0041 %: between 0.0039 and 0.0044), the waveform under 6700 units, the fold of the 400 fixed-point samples pinned (EXP_S_TT_Q), the last Newton residual at most 2 units (the bound) at every step, K = 1 leaves 11636.

The three errors, apart.

error what it compares waveform delay tpLH / tpHL
arithmetic fixed point vs the f64 run of the same iteration 4.03 units (0.24 uV) none (< 1e-5 step)
table + model f64 model vs ngspice backward Euler, same grid 6693 units (0.40 mV) 0.020 % / 0.0041 % (3.09 % / 3.63 % without charge)
discretization ngspice backward Euler at 100 ps vs ngspice converged (<= 20 ps steps) 0.52 % / 0.67 % longer

All three together, the fixed-point model against ngspice's converged run: 0.54 % and 0.67 %: now all of it is the scheme's, not the model's. The charge removes 99.4 % (tpLH) and 99.9 % (tpHL) of the delay gap and 98.5 % of the waveform's.

Negative controls (tests that pass by seeing the failure)

The check is the tt 500 fF check: both delays and the waveform against ngspice tt backward Euler, bounds 0.05 % and 6700 units. The tt current tables with the tt charge pass.

  • ss charge tables in the tt run: fail. 0.127 % and 0.167 % of the delay, 24012 units (1.4 mV) on the waveform: 2.5, 3.3 and 3.6 times over the bound. The ss and tt charge tables differ by only 4.3 .. 5.3 % of their range (a test), which moves the delays by 0.13 % and 0.17 %; the issue's 1 % bar would not see it, which is why the check holds 0.05 %, 20 times tighter, and the test says so (the ss delays are asserted under 1 %).
  • No charge: fails by 62 and 73 times. NMOS and PMOS charge tables in each other's slots: 11 to 14 times. Either device's charge alone: fails (2.0 / 2.9 % and 1.0 / 0.8 %). The ss current tables with the tt charge: fail.
  • Mutation run by hand (not committed): reading the PMOS charge at the current gate instead of the previous one breaks 4 tests (2 in each spec).

Part 3, the weak-inversion read: not met, and why

The relative error with a 1e-9 A floor stays at the measured 0.55 .. 0.73. A test records two facts of the tables that no read of them can undo: a table word is 298 pA, so rounding alone is up to 149 pA, 15 % of the 1 nA floor where 1 % allows 10 pA; and the NMOS current grows 6.5 .. 7 times per 62.5 mV cell in weak inversion (ngspice's own midpoint currents), where a chord is more than 41 % above the exponential at the middle. A table in 0.3 pA units has 1.3e9 for its largest word, over the 2^23 the u64 interpolation allows. The reader that meets it is a different reader (a log-domain table in finer units with an integer exp), filed as #10881 with its own criteria. I did not measure a log-domain read; no number about one is claimed.

Checks run

  • t27c test-report: mos_table 17/17 (FAIL 0, vacuous passes 0 of 17), mos_psp103_data 7/7 (0 of 7), mos_psp103 17/17 (0 of 17); t27b test the same.
  • t27c seal --save then --verify: all hashes MATCH for mos_table, mos_psp103_data, mos_psp103; sar_comparator and sar_switch import mos_table, so their closure hash moved and their seals are renewed (their text is untouched; --verify MATCH). No home path and no blocked verdict in any of the five. The built_by field shows the compiler build I used.
  • python3 -I tools/dupe_scan.py: ok, no new duplicate body. tools/check_assertionless_spec_tests.py: ok. t27c check-calls --specs-dir specs/analog: 0.
  • tri types ratchet: CLEAN on this branch's own changes (the only new struct is CheckQ, unique in the corpus). On top of current master it reports one new name, Range, which is sar_adc.t27's (master's, not this PR's).
  • The branch was rebased onto master 2c35ace22 after the PR opened: master had re-sealed analog_AnalogSarSwitch.json (its sar_cdac.t27 moved), so that one seal was minted again on the merged state; the other four are as first sealed.
  • All pasted arrays regenerate from the committed decks (see above). ngspice ran on the Mac, per the lane brief.
  • The rows were wrapped onto lines, and the pins computed, with throwaway python3 scripts under $TMPDIR (not committed). ngspice itself rounds each value to table units (nint in the deck).

What this does not claim

  • Charge for tt and ss only (ff, fs, sf: analog M3c-3: PSP103 charge tables for ff, fs, sf and the gate charge in a circuit #10882); a charge table is for C = 500 fF (and the 10 pF run has none: its device capacitance is 0.2 % of the load). It is a TABLE MODEL of PSP103, bilinear, at 27 C for two device sizes, no body effect, not "the IHP transistor model".
  • The drain charge is the only one the inverter reads. The gate charge is tabulated and checked (shape, slopes, ngspice's op-point capacitances) but no circuit here reads it; source and bulk charge are not tabulated (they sit on the rails).
  • The transient is checked for tt only, at 500 fF and 100 ps, one input (two levels, an ideal step). The 0.020 % is a measured distance to ngspice backward Euler on the same grid, not to the continuous circuit (that is 0.54 % / 0.67 %, the scheme's).
  • Nothing about a board (no bitstream, BRAM, LUT, DSP, Fmax, UART line), nothing about power, no speed claim against SPICE (may_claim_speedup in bench.t27 stays no). board_step_q is the exact-integer step; no board top.
  • The 1 % with a 1e-9 A floor in weak inversion is not met (above).

🤖 Generated with Claude Code

…e table solver, the 500 fF inverter within 0.02 % of ngspice (Closes #10880)

M3c of #10486, the charge half of #10777 (Part of #10777).

- specs/analog/decks/psp103_q_tt.cir, psp103_q_ss.cir: the drain and gate charge of sg13_lv_nmos / sg13_lv_pmos on the M3b grid, taken from the terminal currents of one backward Euler step (PSP103 reports no terminal charge), with the current tables and ngspice's own op-point capacitances printed in the same run.
- mos_psp103_data.t27: TT_QD_N TT_QG_N TT_QD_P TT_QG_P and the SS_ four, the 401-sample ngspice backward Euler waveform at 500 fF (INV500_BE, printed by psp103_inv_be_500f.cir), pins.
- mos_table.t27: the solver generalized with the two drain-charge tables and the previous step's gate (the *_q functions); the current-only functions are those with zero charge tables, nothing is copied, M3a's pinned run is untouched. Three tests: zero charge is the old path, a plane of charge gives the closed form of the gate-edge kick, the mirror holds with four tables swapped.
- mos_psp103.t27: tt inverter at 500 fF and dt = 100 ps against ngspice backward Euler on the same grid, tpLH 0.020 % and tpHL 0.0041 % (3.09 % and 3.63 % without charge); the three errors apart; the ss charge tables in the tt run fail the check; the table slopes agree with ngspice's op-point capacitances within 0.3 %; the weak-inversion 1 % bound stated with its reasons (follow-up #10881). Corners ff, fs, sf: #10882.

Part of #10486

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 06:22:51 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 45
PRs with All Checks Green 5
READY 2
FAILING 45
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 45 + 0 + 0 = 47, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=88b8cb73ceda != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag enabled auto-merge (squash) October 19, 2026 06:38
@gHashTag
gHashTag disabled auto-merge October 19, 2026 07:11
@gHashTag
gHashTag marked this pull request as draft October 19, 2026 07:11

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

analog M3c-1: PSP103 drain and gate charge tables (tt, ss) and the 500 fF inverter against ngspice backward Euler

2 participants