From 793445183d6fda2ab436bf53783c21e1ff841163 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 09:17:59 +0700 Subject: [PATCH] analog M4c: the SAR ADC bootstrapped sampling switch -- M4b's solver reused, square law against ngspice LEVEL=1 (Closes #10753) specs/analog/sar_switch.t27 (AnalogSarSwitch): the tracking_switch of the golden SAR deck (ten MOS, the inverter, the 603.2 fF boost capacitor XC3 and the 4 fF node capacitors; Vout loaded with 4 fF plus the M4a DAC's top plate, 3930.2 fF from M4a's own counts and the IHP cap_cmim density). THE DEVICE LEVEL IS THE SQUARE LAW (MOSFET LEVEL=1), NOT PSP103: devices are M3a's table as M4b reads it, 3125 in parallel for dt = 250 ps. Backward Euler on eight nodes (seven and a dummy) by M4b's pivoted Gauss in unsigned words, with a damped Newton that stops when every node is settled (the clock edges need up to 120 iterations). Measured against ngspice LEVEL=1 on the same grid, each with a named bound: gate overdrive within 0.26 mV (bound 0.4 mV) at Vin 0.2, 0.75, 1.3 V and four sine phases; tracking error within 36.9 uV of a 1.27 mV lag (bound 3 uV + 5 %); injection step within 0.36 uV (1 uV); droop 0 (1 nV). The three errors apart: arithmetic 247 units, table 39 mV (the knee), model 0.5 nV; discretization from a 50 ps ngspice deck. Negative controls: no boost capacitor, K too small, Vth 0.30, XM9 removed, XM6 halved; a broken constant makes seal --save refuse. M4b (specs/analog/sar_comparator.t27) is split, not copied: sc_stamp_chan, sc_clear, sc_stamp_cap, sc_apply_lim, sc_stamp_chan_f, sc_stamp_caps_f, sc_assemble_f, sc_resid_f; the law mode no longer clamps to the table range. Its 20 tests and folds are unchanged; its seal is renewed. Nine decks under specs/analog/decks/ (data). dupe_scan ok. Co-Authored-By: Claude Sonnet 5.5 --- .../seals/analog_AnalogSarComparator.json | 14 +- .trinity/seals/analog_AnalogSarSwitch.json | 45 + specs/analog/decks/sar_sw_dc02.cir | 62 + specs/analog/decks/sar_sw_dc075.cir | 62 + specs/analog/decks/sar_sw_dc13.cir | 62 + specs/analog/decks/sar_sw_fine.cir | 64 + specs/analog/decks/sar_sw_nocb.cir | 62 + specs/analog/decks/sar_sw_sn0.cir | 62 + specs/analog/decks/sar_sw_sn180.cir | 62 + specs/analog/decks/sar_sw_sn270.cir | 62 + specs/analog/decks/sar_sw_sn90.cir | 62 + specs/analog/sar_comparator.t27 | 213 ++- specs/analog/sar_switch.t27 | 1479 +++++++++++++++++ 13 files changed, 2220 insertions(+), 91 deletions(-) create mode 100644 .trinity/seals/analog_AnalogSarSwitch.json create mode 100644 specs/analog/decks/sar_sw_dc02.cir create mode 100644 specs/analog/decks/sar_sw_dc075.cir create mode 100644 specs/analog/decks/sar_sw_dc13.cir create mode 100644 specs/analog/decks/sar_sw_fine.cir create mode 100644 specs/analog/decks/sar_sw_nocb.cir create mode 100644 specs/analog/decks/sar_sw_sn0.cir create mode 100644 specs/analog/decks/sar_sw_sn180.cir create mode 100644 specs/analog/decks/sar_sw_sn270.cir create mode 100644 specs/analog/decks/sar_sw_sn90.cir create mode 100644 specs/analog/sar_switch.t27 diff --git a/.trinity/seals/analog_AnalogSarComparator.json b/.trinity/seals/analog_AnalogSarComparator.json index 62ec02f8a1..1b747056eb 100644 --- a/.trinity/seals/analog_AnalogSarComparator.json +++ b/.trinity/seals/analog_AnalogSarComparator.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", "closure": [ { "spec": "specs/analog/linear.t27", @@ -11,16 +11,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:e6f3677d7f54078eabbdd3e3786cd6028cb0445eb4cf8f05b313a3d4ff618225", - "gen_hash_rust": "sha256:0b7f0fe869197f808776e207bceee2075fd591d253ced3f2ac9f6c6c6cd62f97", - "gen_hash_verilog": "sha256:bab3512235c19a5adbe9a9149e589999fc93bbac49484ff62864ed7dae37e140", - "gen_hash_zig": "sha256:ac367deed0470ab29568a4caa691a067ad8d341b9f6e5d356be531a026506510", + "gen_hash_c": "sha256:13f523685de5d6085818b938b8d9596938d50da2fbba1dae8b9978e50bfbd936", + "gen_hash_rust": "sha256:e58bde7196705be96ecd9c21fb54a3cbcea6323bd29a01f8abb46ee3b834b427", + "gen_hash_verilog": "sha256:b54771cd79b42bbba9ee294e993e3e096420931b1b2f832c3d00f46a28d5e8d8", + "gen_hash_zig": "sha256:21ae8f9401db2831f43303ca4b0f2b15ef9d3ed6cd00196b90bedb15ef15ed91", "module": "AnalogSarComparator", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T18:44:35Z", + "sealed_at": "2026-10-19T02:13:04Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:2df42ed0afe41add0b43762dfa05c8aa38ecbc022bea8f7bbd72f55917bcb806", + "spec_hash": "sha256:b88d05d5d17937024580ab40e80b698b63962cab531164d4a94a5dddd05a72a8", "spec_path": "specs/analog/sar_comparator.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/analog_AnalogSarSwitch.json b/.trinity/seals/analog_AnalogSarSwitch.json new file mode 100644 index 0000000000..dc277789cb --- /dev/null +++ b/.trinity/seals/analog_AnalogSarSwitch.json @@ -0,0 +1,45 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", + "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + }, + { + "spec": "specs/analog/mos_table.t27", + "spec_hash": "sha256:168cd93374cec92ece02ddab1be51f49308d9efd3288ac0427f912d4a7521782" + }, + { + "spec": "specs/analog/sar_cdac.t27", + "spec_hash": "sha256:0fa41f4de9f797a091d30fdf51896c81971b6d0504587431267172179a711309" + }, + { + "spec": "specs/analog/sar_comparator.t27", + "spec_hash": "sha256:b88d05d5d17937024580ab40e80b698b63962cab531164d4a94a5dddd05a72a8" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:6e492ec2213718c6c5ff75ccb37596f928458a6a8060f2fc000207523bac2bb9", + "gen_hash_rust": "sha256:e8fb7bae8ce6f39a0b03e44d31689454e5f8bdbcfcd3827b323ec7b062ea6bb3", + "gen_hash_verilog": "sha256:f31945ee45a0caad66a141d1053dd9de2d67552dcd79f640014961ea8d1b24cc", + "gen_hash_zig": "sha256:4acac8aa8b54c017bf36f38aad67346a2d8abca7fdfe8c0dbf7d25f26f083aae", + "module": "AnalogSarSwitch", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T02:17:07Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:122726502a51cb4c8980674b819ee3251bf8ee69a5586d6cd44a5eafcf284cb4", + "spec_path": "specs/analog/sar_switch.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 14, + "total": 14, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/specs/analog/decks/sar_sw_dc02.cir b/specs/analog/decks/sar_sw_dc02.cir new file mode 100644 index 0000000000..920c1415e1 --- /dev/null +++ b/specs/analog/decks/sar_sw_dc02.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, dc02: Vin = 0.2 V +* specs/analog/decks/sar_sw_dc02.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is the constant 0.2 V (nint(0.2 * 2^24) / 2^24, the spec's own word). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_dc02.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint(0.2*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.09999996423721313 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_dc075.cir b/specs/analog/decks/sar_sw_dc075.cir new file mode 100644 index 0000000000..9d33c1c562 --- /dev/null +++ b/specs/analog/decks/sar_sw_dc075.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, dc075: Vin = 0.75 V +* specs/analog/decks/sar_sw_dc075.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is the constant 0.75 V (nint(0.75 * 2^24) / 2^24, the spec's own word). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_dc075.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint(0.75*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.6499999761581421 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_dc13.cir b/specs/analog/decks/sar_sw_dc13.cir new file mode 100644 index 0000000000..7621d86b92 --- /dev/null +++ b/specs/analog/decks/sar_sw_dc13.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, dc13: Vin = 1.3 V +* specs/analog/decks/sar_sw_dc13.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is the constant 1.3 V (nint(1.3 * 2^24) / 2^24, the spec's own word). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_dc13.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint(1.3*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=1.199999988079071 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_fine.cir b/specs/analog/decks/sar_sw_fine.cir new file mode 100644 index 0000000000..fc5009dd54 --- /dev/null +++ b/specs/analog/decks/sar_sw_fine.cir @@ -0,0 +1,64 @@ +SAR ADC tracking switch, fine: the sn0 stimulus on a 50 ps grid, five times finer (discretization) +* specs/analog/decks/sar_sw_fine.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 50 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 40 steps (2 ns), +* 1600 steps at 1.5 V (the track phase), a fall of 40 steps, then 400 steps low (the hold), 2080 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 1640) dt) with phase = 0.0 rad, rounded to a multiple of 2^-24 V as the spec's word is (the deck's source is two such sines about a 0.75 V common mode, VDD3 and VDD8). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* THIS DECK IS FIVE TIMES FINER than the spec's grid (50 ps instead of 250 ps; the rise, the plateau, the fall and the hold last as long, in 2080 steps): it measures the +* discretization error of backward Euler at 250 ps, and the spec does not run it. Its rows are at nint(time/dt) = j + 10 with j the step of THIS grid. +* ngspice opens with a ramp of small steps and then takes 50 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 2 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 1640, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 1640; vo_f, vo_h, vo_e = V(Vout) at steps 1640, 1720, 2080: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_fine.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/50p)-10,40),1680-(nint(time/50p)-10)),0),40)/40 +Bvin vin 0 V = nint((0.75+0.6*sin(0.0+2*pi*62.5k*(max(nint(time/50p)-10,0)-1640)*50p))*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.6306825280189514 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 50p 104.525n 0 50p uic +.meas tran od find v(od) at=82.5n +.meas tran te find v(te) at=82.5n +.meas tran vo_f find v(vout) at=82.5n +.meas tran vo_h find v(vout) at=86.5n +.meas tran vo_e find v(vout) at=104.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_nocb.cir b/specs/analog/decks/sar_sw_nocb.cir new file mode 100644 index 0000000000..c3f4312014 --- /dev/null +++ b/specs/analog/decks/sar_sw_nocb.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, nocb: Vin = 0.75 V, the boost capacitor removed (negative control) +* specs/analog/decks/sar_sw_nocb.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* THIS DECK HAS NO C3: the boost capacitor is removed (a negative control of the spec); everything else is the sar_sw_dc075 deck. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is the constant 0.75 V (nint(0.75 * 2^24) / 2^24, the spec's own word). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_nocb.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint(0.75*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.6499999761581421 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_sn0.cir b/specs/analog/decks/sar_sw_sn0.cir new file mode 100644 index 0000000000..56385e61a7 --- /dev/null +++ b/specs/analog/decks/sar_sw_sn0.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, sn0: 62.5 kHz sine, zero crossing up at the end of the track phase +* specs/analog/decks/sar_sw_sn0.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 328) dt) with phase = 0.0 rad, rounded to a multiple of 2^-24 V as the spec's word is (the deck's source is two such sines about a 0.75 V common mode, VDD3 and VDD8). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_sn0.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint((0.75+0.6*sin(0.0+2*pi*62.5k*(max(nint(time/250p)-10,0)-328)*250p))*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.6306825280189514 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_sn180.cir b/specs/analog/decks/sar_sw_sn180.cir new file mode 100644 index 0000000000..b294e4ccfc --- /dev/null +++ b/specs/analog/decks/sar_sw_sn180.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, sn180: 62.5 kHz sine, zero crossing down at the end of the track phase +* specs/analog/decks/sar_sw_sn180.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 328) dt) with phase = 3.141592653589793 rad, rounded to a multiple of 2^-24 V as the spec's word is (the deck's source is two such sines about a 0.75 V common mode, VDD3 and VDD8). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_sn180.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint((0.75+0.6*sin(3.141592653589793+2*pi*62.5k*(max(nint(time/250p)-10,0)-328)*250p))*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.6693174242973328 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_sn270.cir b/specs/analog/decks/sar_sw_sn270.cir new file mode 100644 index 0000000000..98e22e3a5e --- /dev/null +++ b/specs/analog/decks/sar_sw_sn270.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, sn270: 62.5 kHz sine, negative peak at the end of the track phase +* specs/analog/decks/sar_sw_sn270.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 328) dt) with phase = 4.71238898038469 rad, rounded to a multiple of 2^-24 V as the spec's word is (the deck's source is two such sines about a 0.75 V common mode, VDD3 and VDD8). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_sn270.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint((0.75+0.6*sin(4.71238898038469+2*pi*62.5k*(max(nint(time/250p)-10,0)-328)*250p))*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=0.05031102895736694 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/decks/sar_sw_sn90.cir b/specs/analog/decks/sar_sw_sn90.cir new file mode 100644 index 0000000000..d292bde3d0 --- /dev/null +++ b/specs/analog/decks/sar_sw_sn90.cir @@ -0,0 +1,62 @@ +SAR ADC tracking switch, sn90: 62.5 kHz sine, positive peak at the end of the track phase +* specs/analog/decks/sar_sw_sn90.cir -- test data for specs/analog/sar_switch.t27 (#10753). A plain netlist, no .control block. +* Circuit: the tracking_switch subcircuit of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, read at run time and never +* vendored) -- ten transistors M1..M10 here for its XM1..XM10, with the W, L and m of that deck (n: 1u / 0.45u, p: 3u / 0.45u, m = 1) -- plus the +* inverter x1 that makes nclk (the deck's n_clks) from clks (Mip, Min), the boost capacitor C3 between net2 and net4, and the 4 fF node capacitors. +* M6 is the switch (Vout - Vin, gate net3); the boost capacitor lifts net3 about Vdd above Vin while clks is high. Square law: MOSFET LEVEL=1 +* (Shichman-Hodges), KP 300 uA/V^2 (n) and 100 uA/V^2 (p), VTO +-0.35 V, LAMBDA 0, GAMMA 0, and the bulk junctions off (IS = JS = CBD = CBS = 0); +* no TOX, CGSO, CGDO, CGBO or CJ is set, so the only capacitance is the explicit one. This is the SQUARE LAW, not PSP103. +* Capacitors: C3 (boost) = cap_cmim 20 um x 20 um = 603.2 fF: the IHP PDK at 21133f21 sets C = CJ L W + 2 CJSW (L + W) with CJ = 1.5e-15 and +* CJSW = 40e-18 in units of m^2 and m (ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58,72), which is +* 600 fF + 3.2 fF; its 55 mOhm series resistor is left out. Vout carries C2 = 4 fF plus the M4a DAC's top plate: 31.96875 units of the 9 um x 9 um +* cap_cmim (122.94 fF) = 3930.2 fF, in all 3934.2 fF. net1, net2, net4, net5: 4 fF; net3: 8 fF (C6 and C8). nclk: 1 fF, which the deck does NOT have: +* the square law has no gate capacitance, and a node with no capacitor and two devices in saturation has no Newton slope. Vdd 1.5 V. +* Time: backward Euler on a fixed 250 ps grid. clks is the deck's PULSE(0 1.5 0 2n 2n 80n 500n) cut after one pulse: a rise of 8 steps (2 ns), +* 320 steps at 1.5 V (the track phase), a fall of 8 steps, then 80 steps low (the hold), 416 steps in all; it is a B source in the grid index +* nint(time/dt) - 10 (no breakpoint). Vin is 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 328) dt) with phase = 1.5707963267948966 rad, rounded to a multiple of 2^-24 V as the spec's word is (the deck's source is two such sines about a 0.75 V common mode, VDD3 and VDD8). +* The circuit rests until clks moves: net1, net4, nclk at 1.5 V, net2, net3, net5 at 0, Vout 0.1 V (1677722 units of 2^-24 V) under Vin. +* ngspice opens with a ramp of small steps and then takes 250 ps steps on the grid; 10 idle grid points come first, so step j of the spec is the row with +* nint(time/dt) = j + 10 (time (j + 10) dt - 10 ps). Options: gear with maxord 1 is backward Euler; trtol 1e7 keeps the truncation test from shrinking a step; +* reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice still takes every step whole (tighter and its Newton splits the steps at the clock +* edges, which is then not one backward Euler step); gmin 1e-18 so that no leak is added. +* Measured (the .meas lines, 6 digits; the spec pins 10 digits read from `ngspice -b -r deck.raw`, a float64 raw file): od = V(net3) - V(Vin) at step 328, the gate +* overdrive while tracking; te = V(Vout) - V(Vin) at step 328; vo_f, vo_h, vo_e = V(Vout) at steps 328, 344, 416: the injection step is vo_h - vo_f and the droop vo_e - vo_h. +* Run: ngspice -b sar_sw_sn90.cir (do not add -r: ngspice refuses .meas together with a raw file) +.model nmos1 nmos level=1 vto=0.35 kp=300u lambda=0 gamma=0 is=0 js=0 cbd=0 cbs=0 +.model pmos1 pmos level=1 vto=-0.35 kp=100u lambda=0 is=0 js=0 cbd=0 cbs=0 +Vdd vdd 0 1.5 +Bclk clks 0 V = 1.5*min(max(min(min(nint(time/250p)-10,8),336-(nint(time/250p)-10)),0),8)/8 +Bvin vin 0 V = nint((0.75+0.6*sin(1.5707963267948966+2*pi*62.5k*(max(nint(time/250p)-10,0)-328)*250p))*16777216)/16777216 +M1 net1 clks vdd vdd pmos1 w=3u l=0.45u +M2 net1 clks net2 0 nmos1 w=1u l=0.45u +M3 net1 net3 net2 0 nmos1 w=1u l=0.45u +M4 net2 nclk 0 0 nmos1 w=1u l=0.45u +M5 vin net3 net2 0 nmos1 w=1u l=0.45u +M6 vout net3 vin 0 nmos1 w=1u l=0.45u +M7 vdd net3 net4 net4 pmos1 w=3u l=0.45u +M8 net3 vdd net5 0 nmos1 w=1u l=0.45u +M9 net5 nclk 0 0 nmos1 w=1u l=0.45u +M10 net3 net1 net4 net4 pmos1 w=3u l=0.45u +Mip nclk clks vdd vdd pmos1 w=3u l=0.45u +Min nclk clks 0 0 nmos1 w=1u l=0.45u +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 3934.2f +C4 net1 0 4f +C6 net3 0 4f +C8 net3 0 4f +C7 net5 0 4f +C5 net2 0 4f +Cnc nclk 0 1f +Bod od 0 V = v(net3)-v(vin) +Bte te 0 V = v(vout)-v(vin) +.ic v(net1)=1.5 v(net2)=0 v(net3)=0 v(net4)=1.5 v(net5)=0 v(nclk)=1.5 v(vout)=1.2496889233589172 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 250p 106.625n 0 250p uic +.meas tran od find v(od) at=84.5n +.meas tran te find v(te) at=84.5n +.meas tran vo_f find v(vout) at=84.5n +.meas tran vo_h find v(vout) at=88.5n +.meas tran vo_e find v(vout) at=106.5n +.print tran v(vout) v(net1) v(net2) v(net3) v(net4) v(net5) v(nclk) v(od) v(te) +.end diff --git a/specs/analog/sar_comparator.t27 b/specs/analog/sar_comparator.t27 index 27bc593e96..c754f5604c 100644 --- a/specs/analog/sar_comparator.t27 +++ b/specs/analog/sar_comparator.t27 @@ -139,6 +139,13 @@ ; mismatch the pair. No board top and no clock are in this file; the cost of the 8 x 9 solve (the divisions, the multipliers, ; the table memory) is measured when there is one. sc_board_step is the step with a constant trip count. ; - +; M4C REUSES THIS SOLVER (specs/analog/sar_switch.t27, #10753), so a few functions are split here instead of copied there: sc_stamp_dev is +; sc_stamp_chan (one channel, its terminals already resolved to voltages, so a caller may use its own rails and offset) behind the +; comparator's device table; sc_stamp has sc_clear and sc_stamp_cap; sc_apply is sc_apply_lim with the top at Vdd; on the real side sc_stamp_f has +; sc_stamp_chan_f and sc_stamp_caps_f, sc_iter_f has sc_assemble_f, sc_step_f has sc_resid_f. The one change of behaviour is in sc_stamp_chan_f: +; the table's range clamp (x and y to Vdd) applies in table mode only, the analytic law (mode 1) has no range; this comparator never leaves it, +; so no result here moves: the 20 tests and every pinned fold are unchanged. +; - ; WHAT A PASS SAYS. A comparator of eight nodes and fifteen square-law devices, each read from M3a's bilinear table, is solved by ; backward Euler with three fixed Newton iterations of an 8 x 8 pivoted elimination in unsigned sign-magnitude words, last ; residual at most 2 units (0.12 uV) at all 840 steps of six runs; it decides +-2 mV and +-0.5 mV the right way, with the first @@ -299,15 +306,11 @@ pub fn sc_add_pair(reg: []u64, hi: u32, lo: u32, c: u32, mag: u64, neg: bool) { sc_add_j(reg, lo, c, mag, !neg); } -/// Stamp device e: its channel current m * T(x, y) leaves the high end and enters the low end, and its partials go into J. -pub fn sc_stamp_dev(t: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, m: u32, reg: []u64) { - const kind : u32 = SC_DV_KIND[e]; - const d : u32 = SC_DV_D[e]; - const g : u32 = SC_DV_G[e]; - const s : u32 = SC_DV_S[e]; - const vd : u32 = sc_term_v(d, v, clk, vdp, vdn); - const vg : u32 = sc_term_v(g, v, clk, vdp, vdn); - const vs : u32 = sc_term_v(s, v, clk, vdp, vdn); +/// Stamp one channel whose three terminals are already resolved to voltages (vd, vg, vs, in words; only their differences matter, +/// so any common offset is allowed): its current m * T(x, y) leaves the high end and enters the low end, and its partials go +/// into J. d, g, s are the terminal ids (an id below SC_NN has a row and a column, any other id has none). M4c's switch calls +/// this with its own device table and rails. +pub fn sc_stamp_chan(t: []const u32, kind: u32, d: u32, g: u32, s: u32, vd: u32, vg: u32, vs: u32, m: u32, reg: []u64) { var hi : u32 = d; var lo : u32 = s; var vhi : u32 = vd; @@ -342,19 +345,36 @@ pub fn sc_stamp_dev(t: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32 } } -/// All registers of the Newton system at state v, for the step from v0: G = C (v - v0) + (currents out - currents in), J = dG/dv. -pub fn sc_stamp(t: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32, reg: []u64) { +/// Stamp device e of the comparator's table: its terminals are resolved from the state, the rails, the clock and the two inputs. +pub fn sc_stamp_dev(t: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, m: u32, reg: []u64) { + const d : u32 = SC_DV_D[e]; + const g : u32 = SC_DV_G[e]; + const s : u32 = SC_DV_S[e]; + sc_stamp_chan(t, SC_DV_KIND[e], d, g, s, sc_term_v(d, v, clk, vdp, vdn), sc_term_v(g, v, clk, vdp, vdn), sc_term_v(s, v, clk, vdp, vdn), m, reg); +} + +/// Zero all the registers of a stamp. +pub fn sc_clear(reg: []u64) { var i : usize = 0; while (i < SC_REG_LEN) { reg[i] = 0; i = i + 1; } +} + +/// Add the backward Euler term of a capacitor of c charge units from node n to ground: c (v - v0) into G, c 2^20 into J_nn. +pub fn sc_stamp_cap(reg: []u64, n: u32, c: u64, v: u32, v0: u32) { + reg[(SC_RGP + n) as usize] = reg[(SC_RGP + n) as usize] + c * (v as u64); + reg[(SC_RGN + n) as usize] = reg[(SC_RGN + n) as usize] + c * (v0 as u64); + reg[(SC_RJP + n * SC_NN + n) as usize] = reg[(SC_RJP + n * SC_NN + n) as usize] + c * (CELL as u64); +} + +/// All registers of the Newton system at state v, for the step from v0: G = C (v - v0) + (currents out - currents in), J = dG/dv. +pub fn sc_stamp(t: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32, reg: []u64) { + sc_clear(reg); var n : u32 = 0; while (n < SC_NN) { - const c : u64 = (SC_CAP[n as usize]) as u64; - reg[(SC_RGP + n) as usize] = c * (v[n as usize] as u64); - reg[(SC_RGN + n) as usize] = c * (v0[n as usize] as u64); - reg[(SC_RJP + n * SC_NN + n) as usize] = c * (CELL as u64); + sc_stamp_cap(reg, n, (SC_CAP[n as usize]) as u64, v[n as usize], v0[n as usize]); n = n + 1; } var e : usize = 0; @@ -510,8 +530,8 @@ pub fn sc_backsub(a: []const u64, x: []u64) { } } -/// v + x for the eight nodes, held to [0, Vdd]. -pub fn sc_apply(v: []u32, x: []const u64) { +/// v + x for the eight nodes, held to [0, top] (a word; 0 is the bottom of the range whatever offset the caller gives its ground). +pub fn sc_apply_lim(v: []u32, x: []const u64, top: u32) { var i : u32 = 0; while (i < SC_NN) { const m : u64 = sc_mag(x[i as usize]); @@ -519,12 +539,17 @@ pub fn sc_apply(v: []u32, x: []const u64) { if (sc_neg(x[i as usize])) { if (m >= cur) { v[i as usize] = 0; } else { v[i as usize] = (cur - m) as u32; } } else { - if (m >= (VDD as u64) - cur) { v[i as usize] = VDD; } else { v[i as usize] = (cur + m) as u32; } + if (m >= (top as u64) - cur) { v[i as usize] = top; } else { v[i as usize] = (cur + m) as u32; } } i = i + 1; } } +/// v + x for the eight nodes, held to [0, Vdd]. +pub fn sc_apply(v: []u32, x: []const u64) { + sc_apply_lim(v, x, VDD); +} + /// One Newton iteration on the eight nodes at once: stamp, load, eliminate, back-substitute, apply. pub fn sc_iter(t: []const u32, v: []u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32) { var reg : [144]u64 = [0] ** 144; @@ -745,13 +770,62 @@ pub fn sc_addf_pair(jg: []f64, hi: u32, lo: u32, c: u32, val: f64) { sc_addf(jg, lo, c, 0.0 - val); } -/// The Newton system in real arithmetic. mode 0 reads the table (the real-valued twin of sc_stamp), mode 1 the analytic law. -/// drv is the clock, VDP and VDN in units, in that order. out gets J (row-major, a pure number per entry) in words 0..63 and G -/// in words 64..71. -pub fn sc_stamp_f(mode: u32, t: []const u32, v: []const f64, v0: []const f64, drv: []const f64, mult: []const u32, jg: []f64) { +/// One channel of the real Newton system, its terminals already resolved to voltages (vd, vg, vs in units; only differences +/// matter). mode 0 reads the table (the real-valued twin of sc_stamp_chan), mode 1 the analytic law. The channel current of +/// multiplier mu goes into the G words of jg (from word SC_FG) and its partials into J. +pub fn sc_stamp_chan_f(mode: u32, t: []const u32, kind: u32, d: u32, g: u32, s: u32, vd: f64, vg: f64, vs: f64, mu: u32, jg: []f64) { + const m : f64 = (mu) as f64; + var hi : u32 = d; + var lo : u32 = s; + var vhi : f64 = vd; + var vlo : f64 = vs; + if (vd < vs) { + hi = s; + lo = d; + vhi = vs; + vlo = vd; + } + var x : f64 = 0.0; + if (kind == 0) { + if (vg > vlo) { x = vg - vlo; } + } else { + if (vhi > vg) { x = vhi - vg; } + } + var y : f64 = vhi - vlo; + var cur : f64 = 0.0; + var gmv : f64 = 0.0; + var gdv : f64 = 0.0; + if (mode == 0) { + // The table ends at Vdd on both axes; the analytic law (mode 1) has no such end. + if (x > VDD_F) { x = VDD_F; } + if (y > VDD_F) { y = VDD_F; } + cur = m * idf(t, x, y); + gmv = m * sc_gmf(t, x, y) / CELL_F; + gdv = m * gdsf(t, x, y) / CELL_F; + } else { + cur = m * sc_law_t(x, y); + gmv = m * sc_law_gm_t(x, y); + gdv = m * sc_law_gds_t(x, y); + } + if (hi < SC_NN) { jg[(SC_FG + hi) as usize] = jg[(SC_FG + hi) as usize] + cur; } + if (lo < SC_NN) { jg[(SC_FG + lo) as usize] = jg[(SC_FG + lo) as usize] - cur; } + if (kind == 0) { + sc_addf_pair(jg, hi, lo, hi, gdv); + sc_addf_pair(jg, hi, lo, g, gmv); + sc_addf_pair(jg, hi, lo, lo, 0.0 - (gmv + gdv)); + } else { + sc_addf_pair(jg, hi, lo, hi, gmv + gdv); + sc_addf_pair(jg, hi, lo, g, 0.0 - gmv); + sc_addf_pair(jg, hi, lo, lo, 0.0 - gdv); + } +} + +/// The real system's start: J cleared, then the backward Euler term of every grounded capacitor (cap, in charge units): c (v - v0) +/// into the G words of jg and c on the diagonal of J. +pub fn sc_stamp_caps_f(cap: []const u32, v: []const f64, v0: []const f64, jg: []f64) { var i : u32 = 0; while (i < SC_NN) { - const c : f64 = (SC_CAP[i as usize]) as f64; + const c : f64 = (cap[i as usize]) as f64; jg[(SC_FG + i) as usize] = c * (v[i as usize] - v0[i as usize]); var j : u32 = 0; while (j < SC_NN) { @@ -761,58 +835,19 @@ pub fn sc_stamp_f(mode: u32, t: []const u32, v: []const f64, v0: []const f64, dr jg[(i * SC_NN + i) as usize] = c; i = i + 1; } +} + +/// The Newton system in real arithmetic. mode 0 reads the table (the real-valued twin of sc_stamp), mode 1 the analytic law. +/// drv is the clock, VDP and VDN in units, in that order. out gets J (row-major, a pure number per entry) in words 0..63 and G +/// in words 64..71. +pub fn sc_stamp_f(mode: u32, t: []const u32, v: []const f64, v0: []const f64, drv: []const f64, mult: []const u32, jg: []f64) { + sc_stamp_caps_f(&SC_CAP, v, v0, jg); var e : usize = 0; while (e < (SC_NDEV as usize)) { - const kind : u32 = SC_DV_KIND[e]; const d : u32 = SC_DV_D[e]; const g : u32 = SC_DV_G[e]; const s : u32 = SC_DV_S[e]; - const m : f64 = (mult[e]) as f64; - const vd : f64 = sc_term_vf(d, v, drv[0], drv[1], drv[2]); - const vg : f64 = sc_term_vf(g, v, drv[0], drv[1], drv[2]); - const vs : f64 = sc_term_vf(s, v, drv[0], drv[1], drv[2]); - var hi : u32 = d; - var lo : u32 = s; - var vhi : f64 = vd; - var vlo : f64 = vs; - if (vd < vs) { - hi = s; - lo = d; - vhi = vs; - vlo = vd; - } - var x : f64 = 0.0; - if (kind == 0) { - if (vg > vlo) { x = vg - vlo; } - } else { - if (vhi > vg) { x = vhi - vg; } - } - if (x > VDD_F) { x = VDD_F; } - var y : f64 = vhi - vlo; - if (y > VDD_F) { y = VDD_F; } - var cur : f64 = 0.0; - var gmv : f64 = 0.0; - var gdv : f64 = 0.0; - if (mode == 0) { - cur = m * idf(t, x, y); - gmv = m * sc_gmf(t, x, y) / CELL_F; - gdv = m * gdsf(t, x, y) / CELL_F; - } else { - cur = m * sc_law_t(x, y); - gmv = m * sc_law_gm_t(x, y); - gdv = m * sc_law_gds_t(x, y); - } - if (hi < SC_NN) { jg[(SC_FG + hi) as usize] = jg[(SC_FG + hi) as usize] + cur; } - if (lo < SC_NN) { jg[(SC_FG + lo) as usize] = jg[(SC_FG + lo) as usize] - cur; } - if (kind == 0) { - sc_addf_pair(jg, hi, lo, hi, gdv); - sc_addf_pair(jg, hi, lo, g, gmv); - sc_addf_pair(jg, hi, lo, lo, 0.0 - (gmv + gdv)); - } else { - sc_addf_pair(jg, hi, lo, hi, gmv + gdv); - sc_addf_pair(jg, hi, lo, g, 0.0 - gmv); - sc_addf_pair(jg, hi, lo, lo, 0.0 - gdv); - } + sc_stamp_chan_f(mode, t, SC_DV_KIND[e], d, g, s, sc_term_vf(d, v, drv[0], drv[1], drv[2]), sc_term_vf(g, v, drv[0], drv[1], drv[2]), sc_term_vf(s, v, drv[0], drv[1], drv[2]), mult[e], jg); e = e + 1; } } @@ -861,12 +896,8 @@ pub fn sc_solve_f(a: []f64, x: []f64) { } } -/// One real Newton iteration on v: the same system, the same clamp to [0, Vdd], no rounding. -pub fn sc_iter_f(mode: u32, t: []const u32, v: []f64, v0: []const f64, drv: []const f64, mult: []const u32) { - var jg : [72]f64 = [0.0] ** 72; - var a : [72]f64 = [0.0] ** 72; - var x : [8]f64 = [0.0] ** 8; - sc_stamp_f(mode, t, v, v0, drv, mult, &jg); +/// The 8 x 9 augmented matrix [J | -G] of the real system, from the stamp's words, row-major. +pub fn sc_assemble_f(jg: []const f64, a: []f64) { var i : u32 = 0; while (i < SC_NN) { var j : u32 = 0; @@ -877,8 +908,17 @@ pub fn sc_iter_f(mode: u32, t: []const u32, v: []f64, v0: []const f64, drv: []co a[(i * 9 + 8) as usize] = 0.0 - jg[(SC_FG + i) as usize]; i = i + 1; } +} + +/// One real Newton iteration on v: the same system, the same clamp to [0, Vdd], no rounding. +pub fn sc_iter_f(mode: u32, t: []const u32, v: []f64, v0: []const f64, drv: []const f64, mult: []const u32) { + var jg : [72]f64 = [0.0] ** 72; + var a : [72]f64 = [0.0] ** 72; + var x : [8]f64 = [0.0] ** 8; + sc_stamp_f(mode, t, v, v0, drv, mult, &jg); + sc_assemble_f(&jg, &a); sc_solve_f(&a, &x); - i = 0; + var i : u32 = 0; while (i < SC_NN) { var z : f64 = v[i as usize] + x[i as usize]; if (z < 0.0) { z = 0.0; } @@ -888,6 +928,17 @@ pub fn sc_iter_f(mode: u32, t: []const u32, v: []f64, v0: []const f64, drv: []co } } +/// The residual of a real stamp in voltage units: max_n |G_n| / J_nn, the Newton move still owed. +pub fn sc_resid_f(jg: []const f64) -> f64 { + var worst : f64 = 0.0; + var n : u32 = 0; + while (n < SC_NN) { + worst = fmax64(worst, fabs64(jg[(SC_FG + n) as usize]) / jg[(n * SC_NN + n) as usize]); + n = n + 1; + } + return worst; +} + /// One real backward Euler step to step j: k iterations; returns the residual in voltage units, max_n |G_n| / J_nn. pub fn sc_step_f(mode: u32, t: []const u32, v: []f64, j: u32, vdp: f64, vdn: f64, k: u32, mult: []const u32) -> f64 { var v0 : [8]f64 = [0.0] ** 8; @@ -907,13 +958,7 @@ pub fn sc_step_f(mode: u32, t: []const u32, v: []f64, j: u32, vdp: f64, vdn: f64 } var jg : [72]f64 = [0.0] ** 72; sc_stamp_f(mode, t, v, &v0, &drv, mult, &jg); - var worst : f64 = 0.0; - n = 0; - while (n < SC_NN) { - worst = fmax64(worst, fabs64(jg[(SC_FG + n) as usize]) / jg[(n * SC_NN + n) as usize]); - n = n + 1; - } - return worst; + return sc_resid_f(&jg); } /// The real run, mode 0 (table) or 1 (law), k iterations a step: out gets the 8 nodes of every state in units, res the residual. diff --git a/specs/analog/sar_switch.t27 b/specs/analog/sar_switch.t27 new file mode 100644 index 0000000000..002b1176db --- /dev/null +++ b/specs/analog/sar_switch.t27 @@ -0,0 +1,1479 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/sar_switch.t27 -- the SAR ADC bootstrapped sampling switch: damped multi-node Newton with a floating capacitor, square law, against ngspice LEVEL=1 +; M4c of the SAR ADC milestone (#10524, epic #10486, issue #10753). M4b (specs/analog/sar_comparator.t27) solved a latch of eight nodes +; with grounded capacitors; the tracking switch is the circuit that samples the input onto the DAC, and it needs three things M4b did not +; have: a floating capacitor (the boost capacitor), nodes that go above the supply and below ground, and a Newton that survives the +; clock edges, where a node jumps by a volt in one step. THE DEVICE LEVEL IS THE SQUARE LAW (SPICE MOSFET LEVEL=1, as in M4b), NOT PSP103: +; IHP SG13G2 tables replace the law in M3b (#10516) and change nothing in the solver. Everything hand-written is this file and the nine +; decks specs/analog/decks/sar_sw_*.cir (plain SPICE netlists, data); the solver is M4b's, reached by `use`. +; - +; THE CIRCUIT. The subckt tracking_switch of uberWAVE's golden SAR deck (wp2-parser/golden/top.spice, GPL-2.0: read at run time, never +; vendored; this file holds the W, L and m of its devices, which are facts of the circuit, and none of its text). Ten MOS and an +; inverter, a 20 um x 20 um cap_cmim (XC3, net2 to net4) and 4 fF capacitors. (#10524 said seven transistors; the deck has ten plus +; the inverter's two.) XM6 is the switch (Vout - Vin, gate net3). While clks is low, XM4 and XM9 (gate n_clks) hold net2 and net5 at 0, XM8 +; (gate Vdd) pulls the gate net3 to net5, XM1 holds net1 at Vdd and XM7 charges net4 to Vdd: the boost capacitor sits charged to Vdd +; and the switch is off. When clks rises, XM2 pulls net1 down toward net2, XM10 (gate net1) lets net4 drive net3 up, XM5 and XM3 (gate +; net3) put Vin on net2, and net2 rising pushes net4, and so net3, up with it: the gate settles at Vin + about 1.44 V whatever Vin is, +; so XM6's on-resistance hardly depends on the input. It is a regenerative loop; the step in which it fires is the hard step. +; Seven nodes carry state (Vout, net1, net2, net3, net4, net5, n_clks) and an eighth is nothing: M4b's solver is an 8 x 8 solver, and +; node 7 is a 0.1 fF capacitor to ground with no device on it, which stays where it starts. +; - +; THE DEVICES. M4b's: the square law, ov = Vgs - 0.35 V, no current below it, beta = KP W / L = 666.7 uA/V^2 for both types (n: 1 um / +; 0.45 um at KP 300 uA/V^2, p: 3 um / 0.45 um at KP 100 uA/V^2, m = 1), LAMBDA 0, no body effect (XM7 and XM10 have their bulk on net4; +; with GAMMA 0 it does not matter), one 25 x 25 table of M3a read by exact-integer bilinear interpolation, built by M4b's sc_build_table +; for 800 V/A, unchanged. This PR splits a few functions of M4b so that this file calls them instead of copying them (sc_stamp_chan out of +; sc_stamp_dev, sc_clear and sc_stamp_cap out of sc_stamp, sc_apply_lim under sc_apply, and on the real side sc_stamp_chan_f, sc_stamp_caps_f, +; sc_assemble_f, sc_resid_f; the analytic law mode no longer clamps to the table's range, which M4b's runs never leave); M4b's 20 tests and its +; folds are unchanged. The switch's device table (SW_DV_*) and its stamp (sw_stamp) are the only topology-specific code here. +; - +; THE UNITS. A voltage word is V * 2^24 + SW_OFF with SW_OFF = 0.5 V: ground sits at 8388608, Vdd at 2^25, and net1 and net2 go to -0.17 V +; (the boost capacitor pushes them under ground while net4 is pulled down), which an unsigned word with ground at 0 cannot hold. Every +; stamp reads differences, so the offset cancels; the table is read at (Vg - Vlow, Vhigh - Vlow). The top of the range is 3.2 V +; (net3 and net4 reach Vin + Vdd, 2.9 V at the sine's peak). A charge unit is 0.1 fF, so every capacitor of the deck is a whole +; number of units (4 fF = 40, 603.2 fF = 6032, the load 3934.2 fF = 39342). The step is dt = 250 ps (M4b's 0.1 ps would take 5 million steps +; for one 500 ns period). A table entry is the voltage a node moves in one step under the unit device, in units; for dt / C_unit = 2.5e6 V/A +; that is 3125 times M4b's 800 V/A table, so each device here is 3125 devices in parallel (SW_DV_M): the same table, the multiplier carries dt. +; - +; THE LOAD. Vout carries the deck's 4 fF (C2) and the M4a DAC's top plate. M4a (specs/analog/sar_cdac.t27) leaves the unit's value open +; ("only the counts matter"); this file closes it: the DAC's 63 units of 9 um x 9 um cap_cmim, and the top plate sees the 31 units of the MSB +; array plus the bridge unit in series with the 31 of the LSB array, 1023/32 = 31.96875 units (sw_dac_top_units, from M4a's UNITS, SIDE +; and BRIDGE_UNITS). cap_cmim is C = CJ l w + 2 CJSW (l + w) with CJ = 1.5e-15 and CJSW = 40e-18 in m^2 and m (IHP PDK at 21133f21: +; ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 `.param cap_carea = 1.5E-15` under cap_typ, capacitors_mod.lib:58 the .model line, +; :72 the instance; ngspice 47 charges a 1 uA step into the model to 603.2 fF at 20 x 20 and 122.94 fF at 9 x 9). So 603.2 fF for XC3, +; 122.94 fF a unit, 3930.238 fF for the top plate, 3934.2 fF with C2 (rounded to 0.1 fF: 0.04 fF). The 55 mOhm series resistor of cap_cmim +; and every other parasitic of the top level (the 4 fF on VDP and VDN, the comparator's input) are not modelled. One node of the deck has +; no capacitor, n_clks; the model gives it 1 fF, because a node with no capacitor and two devices in saturation has no Newton slope. The +; ngspice decks carry exactly these numbers. +; - +; THE STEP. Backward Euler on all eight nodes at once, every term as in M4b: G_n = c_n (v_n - v0_n) + channel currents out - in, the +; boost capacitor adding c_b ((v2 - v4) - (v0_2 - v0_4)) to node 2 and the opposite to node 4 and +-c_b into the four entries of J (sw_stamp_cfloat); +; J x = -G by M4b's pivoted Gauss on sign-magnitude u64 words (sc_load, sc_eliminate, sc_backsub), v <- v + x clamped to [-0.5 V, 3.2 V] +; (sc_apply_lim, M4b's sc_apply with the top as an argument). M4b ran a fixed K = 3 Newton iterations because its steps are 0.1 ps; here a node moves +; by up to a volt in the step in which the loop fires (a clock edge), and a plain Newton cycles between two cells of the table there. +; The iteration is therefore DAMPED and STOPS WHEN SETTLED: iteration i stamps, reads r_i = the largest Newton move still owed (M4b's +; residual, in units), stops if every node is within SW_STOP (Vout 1 unit = 60 nV, the others 64 units = 3.8 uV) or i = K; otherwise it halves the +; move once more if r_i did not fall below r_(i-1) and doubles it back (at most 1) if it did, solves, and applies the move shifted right by that many bits. +; The stop is part of the scheme, not an optimization: the node currents are integers of 3125 charge units, so a small node dithers at the +; table's resolution and never reaches a zero move. K = SW_K = 128 is enough for every run here (the worst step needs 120: the clock +; fall at step 333 of the sine trough; 119 leaves it unsettled, a test); of the 416 steps of the DC 0.75 V run 114 need no iteration, all but 5 +; need at most 3, only 2 need more than 10, and the average is 1.1. A +; board would run this loop with a bound of K and a comparator on the residual; nothing about a board is measured here. +; - +; THE RUN. Stimulus sel: 0..2 hold Vin at 0.2, 0.75, 1.3 V; 3..6 are 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz (j - 328) dt), the deck's +; sine, with phase 0, pi/2, pi, 3 pi/2 at the end of the track phase (zero crossing up, peak, zero crossing down, trough); each a cold start of +; its own from the rest state. The rest state: net1, net4 and n_clks at Vdd, net2, net3, net5 at 0, Vout 0.1 V under Vin: an exact equilibrium +; of the circuit (every channel off or at 0 V across it; a test steps it ten times and it does not move). Step j = 1..416 (250 ps each, 104 ns +; in all) has clks from the deck's PULSE(0 1.5 0 2n 2n 80n 500n): a rise of 8 steps (2 ns), 320 steps at 1.5 V (80 ns, the track phase), +; a fall of 8 steps, then 80 steps low (20 ns of the deck's 416 ns hold). The run is one pulse, not the 500 ns period. +; - +; THE FOUR MEASUREMENTS, each against ngspice MOSFET LEVEL=1 on the same grid (volts; the stimuli in the order of the decks): +; OD = V(net3) - Vin at 328 TE = V(Vout) - Vin at 328 INJ = V(Vout) at 344 - at 328 droop 416 - 344 +; Vin 0.2 V ng 1.457888 emu - ng -252.8 uV ng -33.9 nV emu - ng -1.93 uV ng +5.8 nV emu - ng +0.17 uV 0 (ng 6e-17) +; Vin 0.75 V ng 1.447160 emu - ng -252.6 uV ng -39.2 nV emu - ng -1.93 uV ng +6.6 nV emu - ng +0.17 uV 0 +; Vin 1.3 V ng 1.436432 emu - ng -252.6 uV ng -45.3 nV emu - ng -1.92 uV ng +7.5 nV emu - ng +0.17 uV 0 +; sine, 0 deg ng 1.447151 emu - ng -248.9 uV ng -1266.6 uV emu - ng -36.9 uV ng +239.6 uV emu - ng -0.36 uV 0 +; sine, 90 deg ng 1.435456 emu - ng -250.1 uV ng -2.87 uV emu - ng -0.23 uV ng +0.468 uV emu - ng +0.009 uV 0 +; sine, 180 deg ng 1.447169 emu - ng -257.1 uV ng +1268.0 uV emu - ng +36.8 uV ng -239.6 uV emu - ng +0.31 uV 0 +; sine, 270 deg ng 1.458863 emu - ng -256.3 uV ng +2.67 uV emu - ng +0.19 uV ng -0.447 uV emu - ng -0.03 uV 0 +; THE BOUNDS (named below, each asserted for every row): |OD - ng| <= SW_B_OD_V = 0.4 mV; |TE - ng| <= SW_B_TE_ABS_V = 3 uV + SW_B_TE_REL = 5 % +; of |ng|; |INJ - ng| <= SW_B_INJ_V = 1 uV; |droop - ng| <= SW_B_DROOP_V = 1 nV. The worst overdrive deviation, 0.2571 mV, is a factor 1.6 inside it, the +; worst tracking error (the sine's zero crossing) 36.9 uV of a 66.3 uV bound, the DC floor 1.93 uV of 3 uV. What the numbers are: +; OD the table's knee (THE TABLE below): XM8 settles net5 at Vdd - 0.3125 V in the table and Vdd - 0.35 V in the law, and the gate sits 0.25 mV lower. +; TE R C dVin/dt: the lag of Vout behind a sine of 2.36e5 V/s through the switch's on-resistance (1.37 kOhm at an overdrive of 1.1 V) into 3.93 pF +; (5.4 ns). The table's conductance at Vds -> 0 is the chord over the first 62.5 mV cell, 2.85 % under the law's, so the lag is 2.9 % +; longer (a test reads the table and gets 0.972). At the DC levels TE is the settling floor: Vout - Vin is 33 units (1.93 uV) when +; the step's move rounds to zero, where ngspice has 0.04 uV. +; INJ the square law has no gate capacitance (MOSFET LEVEL=1 with no TOX, CGSO, CGDO): nothing couples the gate to Vout, and there is no +; charge injection at all. What the step measures is Vout following the input until the gate collapses at step 333, five steps (1.25 ns) +; after the end of the track phase: 239.6 uV at the sine's zero crossing, 6.6 nV at DC. This is not the charge injection of the real switch. +; droop zero: no leak in the square law (ngspice with IS = 0 has 1e-16 V of round-off). It is not a measurement of the PSP103 switch's leakage. +; - +; THE THREE ERRORS, kept apart (W10), at the DC level 0.75 V and at the sine's zero crossing: +; arithmetic fixed point against the REAL run of the same scheme (the same table words, the same damped Newton, the same stop, no rounding): +; 186.8 and 164.8 units (11.1 and 9.8 uV) at the worst node and step; against the real run converged to 1e-6 units, 237.2 and 247.4 units +; (14.1 and 14.7 uV, bound SW_B_ARITH_U = 260), at step 416 on net2 (DC) and step 332 on net4 (sine): the slow tail of the hold and the edge, +; where each step stops with up to 64 units owed. At the four measurements: 97 nV and 4.1 uV in OD, 1.91 uV and 0.33 uV in TE, 0.17 uV and 0.015 uV in INJ. +; table the real table run against the analytic law (the law's exact derivatives, converged), same grid: 653576 units (39.0 mV, bound SW_B_TABLE_U +; = 660000) at step 35 on net5, for both stimuli: the knee of XM8. At the measurements: 252.7 uV and 253.0 uV in OD, 18 nV and 36.6 uV in TE, +; -3 nV and 0.38 uV in INJ. This is the largest error here, and the one M3b removes. +; model the analytic law against ngspice with the same W, L, KP, VTO, capacitors and grid: 0.50 nV and 0.47 nV at the 56 recorded node values +; of each of two decks (bound SW_B_LAW_V = 1 nV), under 2 nV in OD, TE and INJ. The law is ngspice's circuit. The reason for the loose +; reltol: ngspice's Newton splits the step at the clock edges when it is asked for 1e-10, and then it is not one backward Euler step. +; discretization backward Euler at 250 ps against a grid five times finer, both ngspice (decks sar_sw_sn0 and sar_sw_fine): OD 2.96 mV higher on the coarse +; grid (the bootstrap's charge sharing is a stiff step), TE 3.4 uV less negative (0.27 %), INJ 11.2 uV (4.5 %) smaller. The emulator and ngspice +; share the 250 ps grid by construction, so it is not an error of the comparison above; it is the error of the circuit at this step, and +; it is larger than the table's 0.25 mV in OD. Against the fine grid the emulator's OD is 2.71 mV off. +; - +; THE NEGATIVE CONTROLS, each a test that passes by seeing the failure: +; no boost capacitor cb = 0: the overdrive is 0.437 V (ngspice's own deck without XC3: 0.435 V, the emulator is 2.85 mV from it), 1.01 V from the real +; circuit's against a bound of 0.4 mV, and the tracking error is 25.4 mV (ngspice's no-boost deck 23.4 mV) against 3 uV. +; K too small K = 119 leaves step 333 of the sine trough unsettled; K = 4 leaves 4 steps of the DC run with up to 8.3e6 units owed (0.5 V). +; Vth = 0.30 a table built with the wrong threshold: the overdrive is 4.2 mV off ngspice (bound 0.4 mV). +; XM9 removed net3 cannot discharge when clks falls, the gate stays high, XM6 stays on: the sine's "droop" is 4.1 mV against 1 nV. +; XM6 at half width twice the on-resistance, twice the lag: the tracking error is 1.40 mV from ngspice against 66 uV. +; And `t27c seal --save` refuses a spec whose constant is broken (the seal's test run fails, exit 1, no seal written): SW_CB = 0 fails 10 of the 14 +; tests, SW_STOP loosened to 6400 fails 6. +; - +; THE DECKS. specs/analog/decks/sar_sw_dc02.cir, _dc075, _dc13, _sn0, _sn90, _sn180, _sn270 (one per stimulus), _nocb (Vin = 0.75 V, no XC3), _fine +; (the sn0 stimulus on a 50 ps grid, 2080 steps): plain netlists. The recipe of bench.t27 for a fixed-step backward-Euler reference: `.options +; method=gear maxord=1 trtol=1e7`, the clock and Vin as B sources in nint(time/dt) (no breakpoint), the circuit at rest from .ic. Tolerances: +; reltol 1e-7, vntol 1e-10, abstol 1e-13 are the tightest at which ngspice-47 takes every step whole; at 1e-10 / 1e-13 / 1e-16 (M4b's) its +; Newton splits the steps at the clock edges (437 rows become 664) and the run is not one backward Euler step per grid point. Vin is +; rounded to a multiple of 2^-24 V in the deck as it is here. Run `ngspice -b `: the .meas lines print od, te, vo_f, vo_h, vo_e (6 digits). +; The pinned values SW_NG_* are read from `ngspice -b -r deck.raw ` (a float64 raw file; ngspice evaluates no .meas with -r), 10 digits, by a +; reader that is not in the repo. This file does not run ngspice: it holds one run's numbers (ngspice-47, Homebrew, SPARSE 1.3, 2026-10-19). +; - +; THE TABLE, and why 39 mV. The table has 62.5 mV cells and the threshold, 0.35 V, is inside cell 5 (5.6 cells): a chord from 0 at +; 0.3125 V to 2796 at 0.375 V, so XM8 (Vgs = Vdd - V(net5)) keeps conducting until Vgs = 0.3125 V and charges net5 to 1.1875 V where the law stops at +; 1.15 V. The same soft knee made M4b's rest state sag by 0.1 mV. The range ends at 1.5 V: XM7 has Vsg = 1.53 V for a few steps at the clock rise and is read at +; 1.5 V (the law mode has no range, which is why it matches ngspice to a nanovolt). Neither is a defect of the switch; both are the table's, and +; both are measured above. +; - +; THE HARDWARE. Everything from sw_clk_at to sw_run takes and returns bool and unsigned words and has no float: it is what a board would run. +; The bounded loop with an exit is `while (go)` with a flag, which `t27b verilog` may refuse (it takes a `while` only with a static bound); +; nothing here has been through it. `fold` and `fabs64` come from M1, the table reader from M3a, the generic solver from M4b. The f64 side (the +; table builder, the real twin, the analytic law, the stimulus, the measurements and every comparison) is reached only by the tests. The sine +; is two Taylor series, not `@sin`, so that t27b (the native JIT) runs the file; the argument is never more than 0.033 rad. The words have +; headroom: the widest word of any stage, at the clock edges, is 7.2e16, 2^56, in a u64 (bound 2^62 asked, 2^63 the limit). No board top, +; no clock, no LUT, DSP or BRAM, no division cost. +; - +; WHAT A PASS SAYS. A ten-transistor bootstrapped switch with a floating 603.2 fF boost capacitor and a 3.93 pF load, each device read from M3a's +; bilinear table, is solved by backward Euler at 250 ps with M4b's 8 x 8 pivoted elimination in unsigned sign-magnitude words and a damped +; Newton that settles every step (at most 120 iterations, 1.1 on average, last residual at most 64 units = 3.8 uV, Vout 1 unit); its gate +; overdrive while tracking is within 0.26 mV of ngspice MOSFET LEVEL=1 at Vin = 0.2, 0.75 and 1.3 V and at the sine's four phases, its +; tracking error within 36.9 uV (2.9 %) of the 1.27 mV lag and 1.93 uV of the settled floor, its injection step within 0.36 uV and its droop +; zero; its arithmetic is within 247 units (14.7 uV) of a real run of the same table, its table within 39 mV of the analytic law (0.25 mV in +; overdrive), and the analytic law is ngspice to 0.5 nV. Without the boost capacitor, with a wrong threshold, with XM9 removed, with XM6 halved +; or with K too small, a check fails. +; WHAT IT DOES NOT SAY. Nothing about a transistor: the devices are the square law, not PSP103 (M3b), and nothing is a PDK corner; the charge +; injection of a real switch (gate charge through the channel capacitance) and its leakage are not in the model at all, so the injection step is Vout +; still following the input and the droop is zero by construction. Nothing about the continuous circuit: the 250 ps grid moves the overdrive by 3.0 mV and +; the injection step by 4.5 %. Nothing about the full conversion period: one 104 ns window of the 500 ns period, one pulse, the hold 20 ns of 416; +; the load is the DAC's top plate as one linear capacitor (no bottom-plate switching, no differential half, no comparator input, no 4 fF +; on VDP, no 55 mOhm); n_clks has a 1 fF the deck does not have. Nothing about noise, mismatch, supply or temperature. Nothing about a board: no +; bitstream, no LUT, DSP or BRAM, no UART line, and the iteration count is a model count, up to 120 per step, not a hardware figure. +; Nothing about power or about speed against SPICE. +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogSarSwitch; + +use analog::linear::{fold, fabs64}; +use analog::mos_table::{fmax64, id_of, gds_cell, CELL, CELL_F, VDD, VDD_F}; +use analog::sar_comparator::{SC_NN, SC_T_VDD, SC_T_GND, SC_T_CLK, SC_FG, SC_RGP, SC_RGN, SC_RJP, SC_RJN, SC_UPV, SC_VTH_V, sc_stamp_chan, sc_stamp_cap, sc_add_j, sc_clear, sc_resid, sc_load, sc_eliminate, sc_backsub, sc_apply_lim, sc_pack, sc_neg, sc_mag, sc_resid_at, sc_max_u, sc_max_mag, sc_law_gds_t, sc_build_table, sc_stamp_chan_f, sc_stamp_caps_f, sc_addf, sc_assemble_f, sc_solve_f, sc_resid_f}; +use analog::sar_cdac::{UNITS, SIDE, SIDE_L, SIDE_M, BRIDGE_UNITS, side_sum}; + +pub const KIND : str = "analog-cell"; +pub const ID : str = "analog/sar_switch"; +pub const REFERENCE : str = "uberWAVE wp2-parser/golden/top.spice, subckt tracking_switch (GPL-2.0: read at run time, never vendored); specs/analog/sar_comparator.t27"; + +; --- the units: the voltage word is V * 2^24 + SW_OFF ------------------------------------------------- +/// Ground sits at 0.5 V in the word, so that a node can go below ground (the boost capacitor pushes net1 and net2 to -0.17 V). +pub const SW_OFF : u32 = 8388608; +pub const SW_OFF_F : f64 = 8388608.0; +/// The supply in the word: 0.5 V + 1.5 V = 2^25. +pub const SW_VDDW : u32 = 33554432; +/// The top of the range a node may take: 0.5 V + 3.2 V (net3 and net4 are bootstrapped to Vin + Vdd, at most 2.9 V here). +pub const SW_TOP : u32 = 62075699; +pub const SW_TOP_F : f64 = 62075699.0; +/// dt = 250 ps, the charge unit 0.1 fF: dt / C_unit = 2.5e6 V/A = 3125 * 800, and M4b's table is built for 800 V/A, so every +/// device here is 3125 table devices in parallel. +pub const SW_SCALE : u32 = 3125; +pub const SW_DT_PS : u32 = 250; +pub const SW_CUNIT_AF : u32 = 100; + +; --- the circuit: seven nodes and an eighth that is nothing ---------------------------------------------- +pub const SW_VOUT : u32 = 0; +pub const SW_N1 : u32 = 1; +pub const SW_N2 : u32 = 2; +pub const SW_N3 : u32 = 3; +pub const SW_N4 : u32 = 4; +pub const SW_N5 : u32 = 5; +pub const SW_NCLK : u32 = 6; +pub const SW_SPARE : u32 = 7; +/// Node capacitance in 0.1 fF: Vout 4 fF + the DAC's top plate 3930.2 fF, net1 4, net2 4, net3 4 + 4, net4 4, net5 4, n_clks 1 +/// (not in the deck: see the header), and the spare node 0.1. +pub const SW_CAP : [8]u32 = [39342, 40, 40, 80, 40, 40, 10, 1]; +/// The boost capacitor between net2 and net4: cap_cmim 20 um x 20 um = 603.2 fF. +pub const SW_CB : u32 = 6032; +pub const SW_NDEV : u32 = 12; +/// 0 = NMOS, 1 = PMOS. XM1 .. XM10, then the inverter's PMOS and NMOS. +pub const SW_DV_KIND : [12]u32 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0]; +/// Drain, gate and source of each device (nodes 0..7; 8 = Vdd, 9 = ground, 10 = clks, 11 = Vin). +pub const SW_DV_D : [12]u32 = [1, 1, 1, 2, 11, 0, 8, 3, 5, 3, 6, 6]; +pub const SW_DV_G : [12]u32 = [10, 10, 3, 6, 3, 3, 3, 8, 6, 1, 10, 10]; +pub const SW_DV_S : [12]u32 = [8, 2, 2, 9, 2, 11, 4, 5, 9, 4, 8, 9]; +pub const SW_DV_M : [12]u32 = [3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125]; + +; --- the run ------------------------------------------------------------------------------------- +pub const SW_R : u32 = 8; +pub const SW_P : u32 = 320; +pub const SW_H : u32 = 80; +/// The last step with clks at Vdd: the end of the track phase. +pub const SW_JF : u32 = 328; +/// The step at which the hold is read: two rise times after the end of the track phase. +pub const SW_JH : u32 = 344; +pub const SW_N : u32 = 416; +pub const SW_LEN : usize = 3336; +pub const SW_CLK_STEP : u32 = 3145728; +/// The most the damping can halve a move: 6 times, a sixty-fourth. +pub const SW_SMAX : u32 = 6; +/// The most iterations a step may take. The worst of the seven runs needs 120. +pub const SW_K : u32 = 128; +/// A step is done when the Newton move still owed at each node is at most this many units: Vout is held to 1 unit (60 nV), the +/// small nodes to 64 (3.8 uV), the floor their integer table currents dither at. +pub const SW_STOP : [8]u32 = [1, 64, 64, 64, 64, 64, 64, 64]; +pub const SW_DROP_UNITS : u32 = 1677722; +/// The order-sensitive fold of all 3328 node words of each of the seven runs, in the order of the stimuli: what a board must print. +pub const SW_EXP_S : [7]u64 = [0x64D016E84F498375, 0xFECEED2525D5EDC3, 0x5EA4A5DF88B41F83, 0x0A519E4617A8B0E3, 0x7443F0272B850F7F, 0x678A504EC04F2F37, 0x1349B6B205AAD0B6]; + +; --- the hardware side: unsigned words only --------------------------------------------------------------- + +/// The clock at step j in volts-units (no offset): a rise of SW_R steps from step 0, SW_P steps at Vdd, a fall of SW_R steps. +pub fn sw_clk_at(j: u32) -> u32 { + if (j >= SW_JF + SW_R) { return 0; } + var m : u32 = j; + if (m > SW_R) { m = SW_R; } + const f : u32 = SW_JF + SW_R - j; + if (f < m) { m = f; } + return m * SW_CLK_STEP; +} + +/// The word on terminal tm: a node from v, or the supply, ground, the clock (clkw) or Vin (vinw), the last two already offset. +pub fn sw_term_v(tm: u32, v: []const u32, clkw: u32, vinw: u32) -> u32 { + if (tm < SC_NN) { return v[tm as usize]; } + if (tm == SC_T_VDD) { return SW_VDDW; } + if (tm == SC_T_GND) { return SW_OFF; } + if (tm == SC_T_CLK) { return clkw; } + return vinw; +} + +/// A capacitor of c charge units between nodes a and b: c ((va - vb) - (va0 - vb0)) leaves a and enters b; J gets c on both diagonals +/// and -c off the diagonal. +pub fn sw_stamp_cfloat(reg: []u64, a: u32, b: u32, c: u64, va: u32, vb: u32, va0: u32, vb0: u32) { + reg[(SC_RGP + a) as usize] = reg[(SC_RGP + a) as usize] + c * ((va as u64) + (vb0 as u64)); + reg[(SC_RGN + a) as usize] = reg[(SC_RGN + a) as usize] + c * ((vb as u64) + (va0 as u64)); + reg[(SC_RGP + b) as usize] = reg[(SC_RGP + b) as usize] + c * ((vb as u64) + (va0 as u64)); + reg[(SC_RGN + b) as usize] = reg[(SC_RGN + b) as usize] + c * ((va as u64) + (vb0 as u64)); + sc_add_j(reg, a, a, c * (CELL as u64), false); + sc_add_j(reg, b, b, c * (CELL as u64), false); + sc_add_j(reg, a, b, c * (CELL as u64), true); + sc_add_j(reg, b, a, c * (CELL as u64), true); +} + +/// Every register of the Newton system at state v, for the step from v0, with the boost capacitor cb (charge units). +pub fn sw_stamp(t: []const u32, v: []const u32, v0: []const u32, clkw: u32, vinw: u32, cb: u32, mult: []const u32, reg: []u64) { + sc_clear(reg); + var n : u32 = 0; + while (n < SC_NN) { + sc_stamp_cap(reg, n, (SW_CAP[n as usize]) as u64, v[n as usize], v0[n as usize]); + n = n + 1; + } + sw_stamp_cfloat(reg, SW_N2, SW_N4, cb as u64, v[SW_N2 as usize], v[SW_N4 as usize], v0[SW_N2 as usize], v0[SW_N4 as usize]); + var e : usize = 0; + while (e < (SW_NDEV as usize)) { + const d : u32 = SW_DV_D[e]; + const g : u32 = SW_DV_G[e]; + const s : u32 = SW_DV_S[e]; + sc_stamp_chan(t, SW_DV_KIND[e], d, g, s, sw_term_v(d, v, clkw, vinw), sw_term_v(g, v, clkw, vinw), sw_term_v(s, v, clkw, vinw), mult[e], reg); + e = e + 1; + } +} + +/// The magnitude of a sign-magnitude word halved s times (toward zero), sign kept: the damped Newton move. +pub fn sw_damp(w: u64, s: u32) -> u64 { + return sc_pack(sc_mag(w) >> s, sc_neg(w)); +} + +/// Is the Newton move still owed at every node within SW_STOP? +pub fn sw_settled(reg: []const u64) -> bool { + var n : u32 = 0; + while (n < SC_NN) { + if (sc_resid_at(reg, n) > SW_STOP[n as usize]) { return false; } + n = n + 1; + } + return true; +} + +/// One backward Euler step to step j from the state in v (updated in place): up to k damped Newton iterations. Each iteration stamps +/// and reads the residual r (the largest Newton move still owed, in units). If every node is within SW_STOP the step is done. Otherwise it halves the +/// damping if r did not fall below the last iteration's and doubles it back (never above 1) if it fell, solves, and +/// applies the damped move. info[0] gets the number of iterations that moved the state. Returns the residual of the final state. +pub fn sw_step(t: []const u32, v: []u32, j: u32, vin: u32, cb: u32, k: u32, mult: []const u32, info: []u32) -> u32 { + var v0 : [8]u32 = [0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + v0[n as usize] = v[n as usize]; + n = n + 1; + } + const clkw : u32 = SW_OFF + sw_clk_at(j); + const vinw : u32 = SW_OFF + vin; + var s : u32 = 0; + var rprev : u32 = 0xFFFFFFFF; + var used : u32 = 0; + var r : u32 = 0; + var i : u32 = 0; + var go : bool = true; + while (go) { + var reg : [144]u64 = [0] ** 144; + var a : [72]u64 = [0] ** 72; + var x : [8]u64 = [0] ** 8; + sw_stamp(t, v, &v0, clkw, vinw, cb, mult, ®); + r = sc_resid(®); + if (sw_settled(®) or i >= k) { + go = false; + } else { + if (r >= rprev) { + if (s < SW_SMAX) { s = s + 1; } + } else { + if (s > 0) { s = s - 1; } + } + sc_load(®, &a); + sc_eliminate(&a); + sc_backsub(&a, &x); + n = 0; + while (n < SC_NN) { + x[n as usize] = sw_damp(x[n as usize], s); + n = n + 1; + } + sc_apply_lim(v, &x, SW_TOP); + used = i + 1; + rprev = r; + i = i + 1; + } + } + info[0] = used; + return r; +} + +/// The state at rest with the clock low: Vout one tenth of a volt under Vin, net1 and net4 and n_clks at Vdd, everything else at ground. +pub fn sw_rest(vin0: u32, v: []u32) { + v[0] = SW_OFF + vin0 - SW_DROP_UNITS; + v[1] = SW_VDDW; + v[2] = SW_OFF; + v[3] = SW_OFF; + v[4] = SW_VDDW; + v[5] = SW_OFF; + v[6] = SW_VDDW; + v[7] = SW_OFF; +} + +/// The run: SW_N steps from rest, Vin from vin (units, SW_N + 1 samples). out gets the 8 nodes of every state, step 0 first; res the +/// residual of every step; nit the number of the last moving iteration of every step. Returns the order-sensitive fold of every sample. +pub fn sw_run(t: []const u32, vin: []const u32, cb: u32, k: u32, mult: []const u32, out: []u32, res: []u32, nit: []u32) -> u64 { + var v : [8]u32 = [0] ** 8; + var info : [1]u32 = [0] ** 1; + sw_rest(vin[0], &v); + var s : u64 = 0; + var n : u32 = 0; + while (n < SC_NN) { + out[n as usize] = v[n as usize]; + n = n + 1; + } + var j : u32 = 1; + while (j <= SW_N) { + res[(j - 1) as usize] = sw_step(t, &v, j, vin[j as usize], cb, k, mult, &info); + nit[(j - 1) as usize] = info[0]; + n = 0; + while (n < SC_NN) { + out[(j * SC_NN + n) as usize] = v[n as usize]; + s = fold(s, v[n as usize]); + n = n + 1; + } + j = j + 1; + } + return s; +} + +; --- the f64 side: references that only the tests reach --------------------------------------------------------- +pub const SW_PI : f64 = 3.141592653589793; +/// The three DC levels of Vin, V. +pub const SW_LEVEL : [3]f64 = [0.2, 0.75, 1.3]; +/// The phases of the sine at the end of the track phase: zero crossing up, peak, zero crossing down, trough. +pub const SW_PHASE : [4]f64 = [0.0, 1.5707963267948966, 3.141592653589793, 4.71238898038469]; +pub const SW_DAMP_F : [7]f64 = [1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625]; + +/// sin d for |d| < 0.05, the Taylor series to d^9 (error under 1e-20): the argument here is never more than 0.033 rad, and `@sin` is +/// not in t27b's native subset, so the file avoids it. +pub fn sw_sin_small(d: f64) -> f64 { + const d2 : f64 = d * d; + return d * (1.0 - d2 / 6.0 * (1.0 - d2 / 20.0 * (1.0 - d2 / 42.0 * (1.0 - d2 / 72.0)))); +} + +/// cos d for |d| < 0.05, the Taylor series to d^10. +pub fn sw_cos_small(d: f64) -> f64 { + const d2 : f64 = d * d; + return 1.0 - d2 / 2.0 * (1.0 - d2 / 12.0 * (1.0 - d2 / 30.0 * (1.0 - d2 / 56.0 * (1.0 - d2 / 90.0)))); +} + +/// Vin at step j of stimulus sel, in units (no offset): sel 0..2 hold SW_LEVEL, sel 3..6 are 0.75 V + 0.6 V sin(phase + 2 pi 62.5 kHz +/// (j - SW_JF) dt), the phase one of 0, pi/2, pi, 3 pi/2 so that the sine is +-sin or +-cos of the small angle d. +pub fn sw_vin_units(sel: u32, j: u32) -> u32 { + if (sel < 3) { return (SW_LEVEL[sel as usize] * SC_UPV + 0.5) as u32; } + const d : f64 = 2.0 * SW_PI * 62500.0 * (((j) as f64) - 328.0) * 0.00000000025; + var sn : f64 = 0.0; + if (sel == 3) { sn = sw_sin_small(d); } + if (sel == 4) { sn = sw_cos_small(d); } + if (sel == 5) { sn = 0.0 - sw_sin_small(d); } + if (sel == 6) { sn = 0.0 - sw_cos_small(d); } + return ((0.75 + 0.6 * sn) * SC_UPV + 0.5) as u32; +} + +/// The stimulus as SW_N + 1 samples. +pub fn sw_make_vin(sel: u32, vin: []u32) { + var j : u32 = 0; + while (j <= SW_N) { + vin[j as usize] = sw_vin_units(sel, j); + j = j + 1; + } +} + +pub fn sw_term_vf(tm: u32, v: []const f64, clkw: f64, vinw: f64) -> f64 { + if (tm < SC_NN) { return v[tm as usize]; } + if (tm == SC_T_VDD) { return SW_OFF_F + VDD_F; } + if (tm == SC_T_GND) { return SW_OFF_F; } + if (tm == SC_T_CLK) { return clkw; } + return vinw; +} + +/// The real Newton system: mode 0 reads the table, mode 1 the analytic law; cbf is the boost capacitor in charge units; drv is the +/// clock and Vin as words. +pub fn sw_stamp_f(mode: u32, t: []const u32, v: []const f64, v0: []const f64, drv: []const f64, cbf: f64, mult: []const u32, jg: []f64) { + sc_stamp_caps_f(&SW_CAP, v, v0, jg); + const q : f64 = cbf * ((v[SW_N2 as usize] - v[SW_N4 as usize]) - (v0[SW_N2 as usize] - v0[SW_N4 as usize])); + jg[(SC_FG + SW_N2) as usize] = jg[(SC_FG + SW_N2) as usize] + q; + jg[(SC_FG + SW_N4) as usize] = jg[(SC_FG + SW_N4) as usize] - q; + sc_addf(jg, SW_N2, SW_N2, cbf); + sc_addf(jg, SW_N4, SW_N4, cbf); + sc_addf(jg, SW_N2, SW_N4, 0.0 - cbf); + sc_addf(jg, SW_N4, SW_N2, 0.0 - cbf); + var e : usize = 0; + while (e < (SW_NDEV as usize)) { + const d : u32 = SW_DV_D[e]; + const g : u32 = SW_DV_G[e]; + const s : u32 = SW_DV_S[e]; + sc_stamp_chan_f(mode, t, SW_DV_KIND[e], d, g, s, sw_term_vf(d, v, drv[0], drv[1]), sw_term_vf(g, v, drv[0], drv[1]), sw_term_vf(s, v, drv[0], drv[1]), mult[e], jg); + e = e + 1; + } +} + +/// Is the real Newton move owed at every node within the stop? tight asks for 1e-6 units; otherwise the node's SW_STOP + 0.5 (the +/// fixed-point residual rounds to the nearest unit). +pub fn sw_settled_f(jg: []const f64, tight: bool) -> bool { + var n : u32 = 0; + while (n < SC_NN) { + var lim : f64 = 0.000001; + if (!tight) { lim = ((SW_STOP[n as usize]) as f64) + 0.5; } + if (fabs64(jg[(SC_FG + n) as usize]) / jg[(n * SC_NN + n) as usize] >= lim) { return false; } + n = n + 1; + } + return true; +} + +/// The real twin of sw_step: the same damped Newton on real numbers, no rounding, stopping when every node is within its stop +/// (tight: within 1e-6 units, a fully converged step). info[0] gets the number of iterations that moved. +pub fn sw_step_f(mode: u32, t: []const u32, v: []f64, j: u32, vin: u32, cbf: f64, k: u32, tight: bool, mult: []const u32, info: []f64) -> f64 { + var v0 : [8]f64 = [0.0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + v0[n as usize] = v[n as usize]; + n = n + 1; + } + var drv : [2]f64 = [0.0] ** 2; + drv[0] = SW_OFF_F + ((sw_clk_at(j)) as f64); + drv[1] = SW_OFF_F + ((vin) as f64); + var s : u32 = 0; + var rprev : f64 = 1.0e30; + var used : u32 = 0; + var r : f64 = 0.0; + var i : u32 = 0; + var go : bool = true; + while (go) { + var jg : [72]f64 = [0.0] ** 72; + var a : [72]f64 = [0.0] ** 72; + var x : [8]f64 = [0.0] ** 8; + sw_stamp_f(mode, t, v, &v0, &drv, cbf, mult, &jg); + r = sc_resid_f(&jg); + if (sw_settled_f(&jg, tight) or i >= k) { + go = false; + } else { + if (r >= rprev) { + if (s < SW_SMAX) { s = s + 1; } + } else { + if (s > 0) { s = s - 1; } + } + sc_assemble_f(&jg, &a); + sc_solve_f(&a, &x); + n = 0; + while (n < SC_NN) { + var z : f64 = v[n as usize] + x[n as usize] * SW_DAMP_F[s as usize]; + if (z < 0.0) { z = 0.0; } + if (z > SW_TOP_F) { z = SW_TOP_F; } + v[n as usize] = z; + n = n + 1; + } + used = i + 1; + rprev = r; + i = i + 1; + } + } + info[0] = (used) as f64; + return r; +} + +/// The real run: out gets the 8 nodes of every state in units (offset included); res the residual of every step; nit the last moving iteration. +pub fn sw_run_f(mode: u32, t: []const u32, vin: []const u32, cbf: f64, k: u32, tight: bool, mult: []const u32, out: []f64, res: []f64, nit: []f64) { + var vi : [8]u32 = [0] ** 8; + sw_rest(vin[0], &vi); + var v : [8]f64 = [0.0] ** 8; + var info : [1]f64 = [0.0] ** 1; + var n : u32 = 0; + while (n < SC_NN) { + v[n as usize] = (vi[n as usize]) as f64; + out[n as usize] = v[n as usize]; + n = n + 1; + } + var j : u32 = 1; + while (j <= SW_N) { + res[(j - 1) as usize] = sw_step_f(mode, t, &v, j, vin[j as usize], cbf, k, tight, mult, &info); + nit[(j - 1) as usize] = info[0]; + n = 0; + while (n < SC_NN) { + out[(j * SC_NN + n) as usize] = v[n as usize]; + n = n + 1; + } + j = j + 1; + } +} + +/// A fixed-point run's states as reals. +pub fn sw_widen(out: []const u32, w: []f64) { + var i : usize = 0; + while (i < SW_LEN) { + w[i] = (out[i]) as f64; + i = i + 1; + } +} + +/// Node n at step j of a real run, in volts relative to ground. +pub fn sw_node_v(w: []const f64, j: u32, n: u32) -> f64 { + return (w[(j * SC_NN + n) as usize] - SW_OFF_F) / SC_UPV; +} + +/// V(net3) - Vin at step SW_JF, volts: the gate overdrive of the switch while tracking. +pub fn sw_od(w: []const f64, vin: []const u32) -> f64 { + return sw_node_v(w, SW_JF, SW_N3) - ((vin[SW_JF as usize]) as f64) / SC_UPV; +} + +/// V(Vout) - Vin at step SW_JF, volts: the tracking error at the end of the track phase. +pub fn sw_te(w: []const f64, vin: []const u32) -> f64 { + return sw_node_v(w, SW_JF, SW_VOUT) - ((vin[SW_JF as usize]) as f64) / SC_UPV; +} + +/// V(Vout) at step SW_JH minus at step SW_JF, volts: what the falling clock does to the held voltage. +pub fn sw_inj(w: []const f64) -> f64 { + return sw_node_v(w, SW_JH, SW_VOUT) - sw_node_v(w, SW_JF, SW_VOUT); +} + +/// V(Vout) at the last step minus at step SW_JH, volts: the droop during the hold. +pub fn sw_droop(w: []const f64) -> f64 { + return sw_node_v(w, SW_N, SW_VOUT) - sw_node_v(w, SW_JH, SW_VOUT); +} + +; --- ngspice, MOSFET LEVEL=1, the same square law: what the decks in specs/analog/decks print ------------------- +; The seven decks, in the order of the stimuli: sar_sw_dc02, dc075, dc13, sn0, sn90, sn180, sn270. Read from `ngspice -b -r deck.raw` +; (ngspice-47, SPARSE 1.3, 2026-10-19), 10 digits; the .meas lines of the decks give the same to 6. +/// V(net3) - V(Vin) at step 328, V. +pub const SW_NG_OD : [7]f64 = [1.4578881314e+00, 1.4471599125e+00, 1.4364316936e+00, 1.4471511769e+00, 1.4354563998e+00, 1.4471686481e+00, 1.4588634251e+00]; +/// V(Vout) - V(Vin) at step 328, V. +pub const SW_NG_TE : [7]f64 = [-3.3926372461e-08, -3.9185442113e-08, -4.5279025862e-08, -1.2666115587e-03, -2.8659693674e-06, 1.2680339297e-03, 2.6710503599e-06]; +/// V(Vout) at step 344 minus at step 328, V. +pub const SW_NG_INJ : [7]f64 = [5.7760660144e-09, 6.6156126133e-09, 7.5362203145e-09, 2.3961730489e-04, 4.6806728538e-07, -2.3961981425e-04, -4.4727044246e-07]; +/// V(Vout) at step 416 minus at step 344, V: float64 round-off, ngspice with no leak. +pub const SW_NG_DROOP : [7]f64 = [5.5511151231e-17, 2.2204460493e-16, 4.4408920985e-16, -1.3322676296e-15, 4.4408920985e-16, 2.2204460493e-16, 2.7755575616e-17]; +/// The same four for the mutant without the boost capacitor (deck sar_sw_nocb, Vin = 0.75 V) and for the five times finer grid (deck sar_sw_fine, sn0). +pub const SW_NG_NOCB_OD : f64 = 4.3462148942e-01; +pub const SW_NG_NOCB_TE : f64 = -2.3428771002e-02; +pub const SW_NG_FINE_OD : f64 = 1.4441896077e+00; +pub const SW_NG_FINE_TE : f64 = -1.2700276014e-03; +pub const SW_NG_FINE_INJ : f64 = 2.5082115803e-04; +/// The steps at which the checkpoints below are recorded. +pub const SW_CHK : [8]u32 = [4, 5, 6, 20, 328, 332, 333, 340]; +/// Vout net1 net2 net3 net4 net5 nclk in volts at the eight steps, deck sar_sw_dc075 (Vin = 0.75 V), 10 digits. +pub const SW_NG_CK_DC075 : [56]f64 = [ + 6.499999762e-01, 1.295557076e+00, 3.956065768e-02, 4.726546923e-15, 1.527923540e+00, 2.764169448e-15, 1.160058980e+00, + 6.537449244e-01, 6.732461580e-01, 6.344295109e-01, 1.970268214e+00, 2.086736821e+00, 9.378305693e-01, 5.205259514e-02, + 6.580877801e-01, 7.319073130e-01, 7.328770030e-01, 2.169390438e+00, 2.181039844e+00, 1.070268941e+00, 8.004301088e-04, + 7.022309571e-01, 7.499926746e-01, 7.499926953e-01, 2.197170550e+00, 2.197177818e+00, 1.146010650e+00, 1.956521739e-15, + 7.499999608e-01, 7.499999897e-01, 7.499999897e-01, 2.197159913e+00, 2.197159923e+00, 1.149848484e+00, 1.956521739e-15, + 7.499999674e-01, 8.884071171e-01, 8.150077622e-01, 2.258829036e+00, 2.260926932e+00, 1.149850368e+00, 3.399410195e-01, + 7.499999674e-01, 1.451902179e+00, 4.771597073e-02, 2.178948807e-01, 1.498956573e+00, 1.181329386e-01, 1.447947405e+00, + 7.499999674e-01, 1.500000000e+00, 8.775605666e-03, 8.197721709e-09, 1.491224394e+00, 4.603453333e-09, 1.500000000e+00 +]; +/// The same, deck sar_sw_sn0 (the sine with a zero crossing up at step 328). +pub const SW_NG_CK_SN0 : [56]f64 = [ + 6.306825280e-01, 1.295557076e+00, 3.956065768e-02, 4.726546923e-15, 1.527923540e+00, 2.764169448e-15, 1.160058980e+00, + 6.344526307e-01, 6.569488896e-01, 6.177645530e-01, 1.954700431e+00, 2.070386754e+00, 9.378305693e-01, 5.205259514e-02, + 6.388136703e-01, 7.132766827e-01, 7.141814874e-01, 2.151177925e+00, 2.162707442e+00, 1.070268941e+00, 8.004301088e-04, + 6.833559089e-01, 7.318454295e-01, 7.318463815e-01, 2.179375708e+00, 2.179385497e+00, 1.146010650e+00, 1.956521739e-15, + 7.487333884e-01, 7.499926794e-01, 7.499936235e-01, 2.197151177e+00, 2.197153714e+00, 1.149848484e+00, 1.956521739e-15, + 7.489730057e-01, 8.886361703e-01, 8.152377031e-01, 2.259051788e+00, 2.261152423e+00, 1.149850368e+00, 3.399410195e-01, + 7.489730057e-01, 1.451913864e+00, 4.772256103e-02, 2.179159436e-01, 1.498959445e+00, 1.181425829e-01, 1.447947405e+00, + 7.489730057e-01, 1.500000000e+00, 8.776279687e-03, 8.198503997e-09, 1.491223720e+00, 4.603892629e-09, 1.500000000e+00 +]; + +; --- the bounds, each named ------------------------------------------------------------------------------ +/// |OD - ngspice| for the fixed-point run: the table's soft knee leaves 0.2528 mV at the worst of the seven stimuli. +pub const SW_B_OD_V : f64 = 0.0004; +/// |TE - ngspice|: a floor of 3 uV (the settling the integer words stop at, 1.93 uV at the DC levels) plus 5 % of the ngspice value +/// (the table's on-resistance is 2.85 % high: 36.9 uV of 1266.7 uV at the zero crossing). +pub const SW_B_TE_ABS_V : f64 = 0.000003; +pub const SW_B_TE_REL : f64 = 0.05; +/// |injection step - ngspice|: 1 uV (the worst is 0.36 uV). +pub const SW_B_INJ_V : f64 = 0.000001; +/// |droop|: 1 nV. The square law has no leak, so both are round-off. +pub const SW_B_DROOP_V : f64 = 0.000000001; +/// The residual after any step of any run: SW_STOP's largest entry. +pub const SW_B_RES : u32 = 64; +/// The most iterations any step of the seven runs needs (the clock fall at step 333 of the sine trough). +pub const SW_NIT_MAX : u32 = 120; +/// Arithmetic error: fixed point against the fully converged real run of the same table, any node, any step, in units (247.4 measured). +pub const SW_B_ARITH_U : f64 = 260.0; +/// Table error: the real table run against the analytic law, any node, any step, in units (653576 measured: 39.0 mV at net5, step 35). +pub const SW_B_TABLE_U : f64 = 660000.0; +/// Model error: the analytic law against ngspice at the 8 x 7 recorded checkpoints, V (0.50 nV measured). +pub const SW_B_LAW_V : f64 = 0.000000001; +/// Discretization: ngspice on the 250 ps grid against ngspice on the 50 ps grid, for the sn0 stimulus. +pub const SW_B_DISC_OD_V : f64 = 0.004; +pub const SW_B_DISC_TE_V : f64 = 0.000005; +pub const SW_B_DISC_INJ_V : f64 = 0.000015; +/// The no-boost mutant against ngspice's own no-boost deck (2.85 mV measured). +pub const SW_B_NOCB_V : f64 = 0.005; + +; --- helpers of the tests ---------------------------------------------------------------------------------- +/// How many of the first n words exceed thr. +pub fn sw_cnt_gt(r: []const u32, n: u32, thr: u32) -> u32 { + var c : u32 = 0; + var i : u32 = 0; + while (i < n) { + if (r[i as usize] > thr) { c = c + 1; } + i = i + 1; + } + return c; +} + +/// The largest |a - b| over two runs' states, in units. +pub fn sw_maxdiff(a: []const f64, b: []const f64) -> f64 { + var m : f64 = 0.0; + var i : usize = 0; + while (i < SW_LEN) { + m = fmax64(m, fabs64(a[i] - b[i])); + i = i + 1; + } + return m; +} + +/// Where the largest |a - b| sits: step * 8 + node. +pub fn sw_argmax(a: []const f64, b: []const f64) -> usize { + var m : f64 = 0.0; + var at : usize = 0; + var i : usize = 0; + while (i < SW_LEN) { + const d : f64 = fabs64(a[i] - b[i]); + if (d > m) { + m = d; + at = i; + } + i = i + 1; + } + return at; +} + +/// The largest |model - ngspice| over the 7 nodes at the 8 checkpoints, V; the model's states are real, in units. +pub fn sw_ck_dev(w: []const f64, ng: []const f64) -> f64 { + var m : f64 = 0.0; + var c : u32 = 0; + while (c < 8) { + var n : u32 = 0; + while (n < 7) { + m = fmax64(m, fabs64(sw_node_v(w, SW_CHK[c as usize], n) - ng[(c * 7 + n) as usize])); + n = n + 1; + } + c = c + 1; + } + return m; +} + +/// Is the value within the named bounds of ngspice's: abs + rel |ng|? +pub fn sw_within(v: f64, ng: f64, abs_b: f64, rel_b: f64) -> bool { + return fabs64(v - ng) <= abs_b + rel_b * fabs64(ng); +} + +/// cap_cmim of l um x w um, fF: C = CJ l w + 2 CJSW (l + w) with CJ = 1.5 fF/um^2 and CJSW = 0.04 fF/um (the IHP PDK at 21133f21, +/// ihp-sg13g2/libs.tech/ngspice/models/cornerCAP.lib:22 and capacitors_mod.lib:58, instance line 72). +pub fn sw_cmim_ff(l: f64, w: f64) -> f64 { + return 1.5 * l * w + 0.08 * (l + w); +} + +/// The M4a DAC's capacitance at the top plate, in cap_cmim units: the 31 units of the MSB array plus the bridge unit in series with +/// the 31 units of the LSB array (the other plates sit at fixed voltages), from M4a's own counts. +pub fn sw_dac_top_units() -> f64 { + const cl : f64 = side_sum(&UNITS, &SIDE, SIDE_L); + const cm : f64 = side_sum(&UNITS, &SIDE, SIDE_M); + const cb : f64 = (BRIDGE_UNITS) as f64; + return cm + cb * cl / (cb + cl); +} + +/// The largest |dG_i / dv_j (central difference, +-16 units) - J_ij| of the real system at v, for the step from v0, drv the clock and +/// Vin as words. With transposed set it compares against J_ji, which is wrong on purpose. +pub fn sw_fd_err(mode: u32, t: []const u32, v: []const f64, v0: []const f64, drv: []const f64, cbf: f64, mult: []const u32, transposed: bool) -> f64 { + var j0 : [72]f64 = [0.0] ** 72; + var jp : [72]f64 = [0.0] ** 72; + var jq : [72]f64 = [0.0] ** 72; + var vp : [8]f64 = [0.0] ** 8; + var vq : [8]f64 = [0.0] ** 8; + sw_stamp_f(mode, t, v, v0, drv, cbf, mult, &j0); + var worst : f64 = 0.0; + var j : u32 = 0; + while (j < SC_NN) { + var n : u32 = 0; + while (n < SC_NN) { + vp[n as usize] = v[n as usize]; + vq[n as usize] = v[n as usize]; + n = n + 1; + } + vp[j as usize] = v[j as usize] + 16.0; + vq[j as usize] = v[j as usize] - 16.0; + sw_stamp_f(mode, t, &vp, v0, drv, cbf, mult, &jp); + sw_stamp_f(mode, t, &vq, v0, drv, cbf, mult, &jq); + var i : u32 = 0; + while (i < SC_NN) { + const fd : f64 = (jp[(SC_FG + i) as usize] - jq[(SC_FG + i) as usize]) / 32.0; + var an : f64 = j0[(i * SC_NN + j) as usize]; + if (transposed) { an = j0[(j * SC_NN + i) as usize]; } + worst = fmax64(worst, fabs64(fd - an)); + i = i + 1; + } + j = j + 1; + } + return worst; +} + +/// The widest word the fixed-point solve forms at the first iteration of the clock edges (steps 5, 6, 7, 331, 332, 333, 334, where the +/// Newton move is largest), as a real: the largest register, the largest matrix word before and after elimination, the elimination's +/// widest product (a multiplier of 2^20 at most times the largest word before), and the two sums of every row of the back substitution, +/// recomputed from the eliminated matrix and the solution exactly as sc_backsub forms them. +pub fn sw_headroom(t: []const u32, vin: []const u32, cb: u32, mult: []const u32) -> f64 { + var v : [8]u32 = [0] ** 8; + var info : [1]u32 = [0] ** 1; + sw_rest(vin[0], &v); + var wide : f64 = 0.0; + var j : u32 = 1; + while (j <= 335) { + if (j == 5 or j == 6 or j == 7 or j == 331 or j == 332 or j == 333 or j == 334) { + var v0 : [8]u32 = [0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + v0[n as usize] = v[n as usize]; + n = n + 1; + } + var reg : [144]u64 = [0] ** 144; + var a : [72]u64 = [0] ** 72; + var x : [8]u64 = [0] ** 8; + sw_stamp(t, &v, &v0, SW_OFF + sw_clk_at(j), SW_OFF + vin[j as usize], cb, mult, ®); + var q : usize = 0; + while (q < 144) { + wide = fmax64(wide, (reg[q]) as f64); + q = q + 1; + } + sc_load(®, &a); + const before : f64 = (sc_max_mag(&a, 72)) as f64; + wide = fmax64(wide, before); + wide = fmax64(wide, before * CELL_F); + sc_eliminate(&a); + sc_backsub(&a, &x); + wide = fmax64(wide, (sc_max_mag(&a, 72)) as f64); + var i : u32 = 0; + while (i < SC_NN) { + var pos : f64 = 0.0; + var neg : f64 = 0.0; + const b : u64 = a[(i * 9 + 8) as usize]; + if (sc_neg(b)) { neg = neg + ((sc_mag(b)) as f64) * CELL_F; } else { pos = pos + ((sc_mag(b)) as f64) * CELL_F; } + var c : u32 = i + 1; + while (c < SC_NN) { + const aij : u64 = a[(i * 9 + c) as usize]; + const xj : u64 = x[c as usize]; + const pr : f64 = ((sc_mag(aij)) as f64) * ((sc_mag(xj)) as f64); + if (sc_neg(aij) != sc_neg(xj)) { pos = pos + pr; } else { neg = neg + pr; } + c = c + 1; + } + wide = fmax64(wide, fmax64(pos, neg)); + i = i + 1; + } + } + const r0 : u32 = sw_step(t, &v, j, vin[j as usize], cb, SW_K, mult, &info); + j = j + 1; + } + return wide; +} + +/// The largest |G_fixed - G_real| over the eight nodes at the fixed-point state v (both stamps on the same words, the table system), +/// in charge units x voltage units. +pub fn sw_gap_g(t: []const u32, v: []const u32, v0: []const u32, clkw: u32, vinw: u32, cb: u32, mult: []const u32) -> f64 { + var reg : [144]u64 = [0] ** 144; + sw_stamp(t, v, v0, clkw, vinw, cb, mult, ®); + var vf : [8]f64 = [0.0] ** 8; + var v0f : [8]f64 = [0.0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + vf[n as usize] = (v[n as usize]) as f64; + v0f[n as usize] = (v0[n as usize]) as f64; + n = n + 1; + } + var drv : [2]f64 = [0.0] ** 2; + drv[0] = (clkw) as f64; + drv[1] = (vinw) as f64; + var jg : [72]f64 = [0.0] ** 72; + sw_stamp_f(0, t, &vf, &v0f, &drv, (cb) as f64, mult, &jg); + var worst : f64 = 0.0; + n = 0; + while (n < SC_NN) { + const gx : f64 = (reg[(SC_RGP + n) as usize] as f64) - (reg[(SC_RGN + n) as usize] as f64); + worst = fmax64(worst, fabs64(gx - jg[(SC_FG + n) as usize])); + n = n + 1; + } + return worst; +} + +/// The largest |J_fixed / 2^20 - J_real| over the 64 entries at the same state. +pub fn sw_gap_j(t: []const u32, v: []const u32, v0: []const u32, clkw: u32, vinw: u32, cb: u32, mult: []const u32) -> f64 { + var reg : [144]u64 = [0] ** 144; + sw_stamp(t, v, v0, clkw, vinw, cb, mult, ®); + var vf : [8]f64 = [0.0] ** 8; + var v0f : [8]f64 = [0.0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + vf[n as usize] = (v[n as usize]) as f64; + v0f[n as usize] = (v0[n as usize]) as f64; + n = n + 1; + } + var drv : [2]f64 = [0.0] ** 2; + drv[0] = (clkw) as f64; + drv[1] = (vinw) as f64; + var jg : [72]f64 = [0.0] ** 72; + sw_stamp_f(0, t, &vf, &v0f, &drv, (cb) as f64, mult, &jg); + var worst : f64 = 0.0; + var k : usize = 0; + while (k < 64) { + const jx : f64 = ((reg[(SC_RJP as usize) + k] as f64) - (reg[(SC_RJN as usize) + k] as f64)) / CELL_F; + worst = fmax64(worst, fabs64(jx - jg[k])); + k = k + 1; + } + return worst; +} + +/// The errors of stimulus sel, kept apart. m gets, in order: 0 fixed point against the real run of the same scheme (units, any node, +/// any step); 1 fixed point against the fully converged real table run; 2, 3, 4 the OD, TE and injection step of the fixed-point run +/// minus the converged real table run's (V); 5 the converged real table run against the analytic law (units, any node, any step); +/// 6 and 7 its step and node; 8, 9, 10 the OD, TE and injection step of the law minus the table's (V); 11 the law against ngspice's +/// checkpoints (V; sel 1 and 3 only); 12, 13, 14 the OD, TE and injection step of the law minus ngspice's (V). +pub fn sw_errors(sel: u32, m: []f64) { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + var ws : [3336]f64 = [0.0] ** 3336; + var wt : [3336]f64 = [0.0] ** 3336; + var wl : [3336]f64 = [0.0] ** 3336; + var rf : [416]f64 = [0.0] ** 416; + var nf : [416]f64 = [0.0] ** 416; + sw_make_vin(sel, &vin); + const s : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + sw_run_f(0, &t, &vin, 6032.0, SW_K, false, &SW_DV_M, &ws, &rf, &nf); + sw_run_f(0, &t, &vin, 6032.0, 600, true, &SW_DV_M, &wt, &rf, &nf); + sw_run_f(1, &t, &vin, 6032.0, 600, true, &SW_DV_M, &wl, &rf, &nf); + m[0] = sw_maxdiff(&w, &ws); + m[1] = sw_maxdiff(&w, &wt); + m[2] = sw_od(&w, &vin) - sw_od(&wt, &vin); + m[3] = sw_te(&w, &vin) - sw_te(&wt, &vin); + m[4] = sw_inj(&w) - sw_inj(&wt); + m[5] = sw_maxdiff(&wl, &wt); + const at : usize = sw_argmax(&wl, &wt); + m[6] = (((at / 8) as u32)) as f64; + m[7] = (((at % 8) as u32)) as f64; + m[8] = sw_od(&wl, &vin) - sw_od(&wt, &vin); + m[9] = sw_te(&wl, &vin) - sw_te(&wt, &vin); + m[10] = sw_inj(&wl) - sw_inj(&wt); + m[11] = 0.0; + if (sel == 1) { m[11] = sw_ck_dev(&wl, &SW_NG_CK_DC075); } + if (sel == 3) { m[11] = sw_ck_dev(&wl, &SW_NG_CK_SN0); } + m[12] = sw_od(&wl, &vin) - SW_NG_OD[sel as usize]; + m[13] = sw_te(&wl, &vin) - SW_NG_TE[sel as usize]; + m[14] = sw_inj(&wl) - SW_NG_INJ[sel as usize]; +} + +; --- the tests ---------------------------------------------------------------------------------- + +test the_units_the_devices_and_the_circuit_are_as_stated { + // Variables, not constants: a constant assert is folded by the compiler and counts as no assert at all. + var off : u32 = SW_OFF; + var vddw : u32 = SW_VDDW; + var vdd : u32 = VDD; + var top : u32 = SW_TOP; + var scale : u32 = SW_SCALE; + var step : u32 = SW_CLK_STEP; + var r : u32 = SW_R; + var jf : u32 = SW_JF; + // Ground is 0.5 V = 2^23 in the word, the supply 0.5 V + 1.5 V = 2^25; the top of the range is 3.2 V above ground. + assert_eq(off, 8388608); + assert_eq(vddw, 33554432); + assert_eq(vddw, off + vdd); + const topd : f64 = ((top - off) as f64) / SC_UPV - 3.2; + assert topd * topd < 0.0000001 * 0.0000001; + // dt = 250 ps over the 0.1 fF charge unit is 2.5e6 V/A = 3125 * 800: a device is 3125 of M4b's table devices. + const dtc : f64 = ((SW_DT_PS as f64) * 0.000000000001) / ((SW_CUNIT_AF as f64) * 0.000000000000000001) - (scale as f64) * 800.0; + assert dtc * dtc < 0.000001 * 0.000001; + var e : usize = 0; + var mok : bool = true; + while (e < 12) { + if (SW_DV_M[e] != 3125) { mok = false; } + e = e + 1; + } + assert(mok); + // The clock: a rise of 8 steps of exactly Vdd / 8, 320 steps at Vdd, a fall of 8 steps, then low; 2 ns is 8 steps of 250 ps. + assert_eq(step * r, VDD); + var n0 : u32 = 0; + var n4 : u32 = 4; + assert_eq(sw_clk_at(n0), 0); + assert_eq(sw_clk_at(n4), vdd / 2); + assert_eq(sw_clk_at(r), vdd); + assert_eq(sw_clk_at(jf), vdd); + assert_eq(sw_clk_at(jf + n4), vdd / 2); + assert_eq(sw_clk_at(jf + r), 0); + assert_eq(sw_clk_at(jf + r + 50), 0); + var p : u32 = SW_P; + var h : u32 = SW_H; + var nn : u32 = SW_N; + var jh : u32 = SW_JH; + var ln : usize = SW_LEN; + assert_eq(jf, r + p); + assert_eq(nn, jf + r + h); + assert_eq(jh, jf + 2 * r); + assert_eq((nn + 1) * SC_NN, ln as u32); + // The device table: ten transistors and the inverter's two, four PMOS (XM1, XM7, XM10, the inverter's), every terminal one of the + // 12 names (nodes 0..7, Vdd 8, ground 9, clks 10, Vin 11), no device shorted, no device between two rails. + var pmos : u32 = 0; + var ok : bool = true; + e = 0; + while (e < 12) { + pmos = pmos + SW_DV_KIND[e]; + if (SW_DV_D[e] >= 12 or SW_DV_G[e] >= 12 or SW_DV_S[e] >= 12) { ok = false; } + if (SW_DV_D[e] == SW_DV_S[e] or SW_DV_G[e] == SW_DV_D[e] or SW_DV_G[e] == SW_DV_S[e]) { ok = false; } + if (SW_DV_D[e] >= SC_NN and SW_DV_S[e] >= SC_NN) { ok = false; } + e = e + 1; + } + assert(ok); + assert_eq(pmos, 4); + // XM6 is the switch: Vout - Vin, gate net3; XM5 shares its gate and puts Vin on net2; XM3 does the same from net1. + assert_eq(SW_DV_D[5], SW_VOUT); + assert_eq(SW_DV_G[5], SW_N3); + assert_eq(SW_DV_S[5], 11); + assert_eq(SW_DV_G[4], SW_N3); + assert_eq(SW_DV_D[4], 11); + assert_eq(SW_DV_S[4], SW_N2); + assert_eq(SW_DV_G[2], SW_N3); + // The pull-downs of net2 (XM4) and net5 (XM9) are gated by the inverter's output; XM8 (gate Vdd) links net3 to net5. + assert_eq(SW_DV_G[3], SW_NCLK); + assert_eq(SW_DV_G[8], SW_NCLK); + assert_eq(SW_DV_G[7], 8); + assert_eq(SW_DV_D[7], SW_N3); + assert_eq(SW_DV_S[7], SW_N5); + // XM7 (gate net3) and XM10 (gate net1) are the PMOS from net4 to Vdd and to net3: the bootstrap's charge path. + assert_eq(SW_DV_S[6], SW_N4); + assert_eq(SW_DV_S[9], SW_N4); + assert_eq(SW_DV_D[9], SW_N3); + assert_eq(SW_DV_G[9], SW_N1); + // The inverter drives n_clks (node 6) from clks. + assert_eq(SW_DV_D[10], SW_NCLK); + assert_eq(SW_DV_D[11], SW_NCLK); + assert_eq(SW_DV_G[10], 10); + assert_eq(SW_DV_G[11], 10); + // 0.1 fF units: Vout 3934.2, net1 4, net2 4, net3 8, net4 4, net5 4, n_clks 1 (not in the deck), the spare 0.1 fF. + assert_eq(SW_CAP[0], 39342); + assert_eq(SW_CAP[3], 80); + assert_eq(SW_CAP[6], 10); + assert_eq(SW_CB, 6032); +} + +test the_capacitors_come_from_the_pdk_and_the_dac_counts { + // cap_cmim: C = CJ l w + 2 CJSW (l + w) with CJ = 1.5 fF/um^2 and CJSW = 0.04 fF/um: 20 x 20 um is 600 + 3.2 = 603.2 fF (the boost + // capacitor XC3), 9 x 9 um is 121.5 + 1.44 = 122.94 fF (one unit of the DAC). Checked in ngspice: a 1 uA step into the deck's + // model charged the 20 x 20 and the 9 x 9 capacitor to 603.2 fF and 122.94 fF. + var l20 : f64 = 20.0; + var l9 : f64 = 9.0; + const c20 : f64 = sw_cmim_ff(l20, l20); + const c9 : f64 = sw_cmim_ff(l9, l9); + assert (c20 - 603.2) * (c20 - 603.2) < 0.000000001 * 0.000000001; + assert (c9 - 122.94) * (c9 - 122.94) < 0.000000001 * 0.000000001; + var cbw : u32 = SW_CB; + const cbd : f64 = (cbw as f64) - c20 * 10.0; + assert cbd * cbd < 0.000001 * 0.000001; + // The DAC (M4a, specs/analog/sar_cdac.t27): 31 + 31 + 1 units; the top plate sees the 31 units of the MSB array and the bridge unit + // in series with the LSB array: 31 + 1 * 31 / (1 + 31) = 31 + 31/32 = 1023/32 units. M4a states the counts and leaves the unit's value to this file. + const top : f64 = sw_dac_top_units(); + const topd : f64 = top - 1023.0 / 32.0; + assert topd * topd < 0.000000000001 * 0.000000000001; + const dac_ff : f64 = top * c9; + const dacd : f64 = dac_ff - 3930.238125; + assert dacd * dacd < 0.000001 * 0.000001; + // Vout's word: the deck's 4 fF plus the DAC, in 0.1 fF units, rounded: 40 + 39302.38 -> 39342. + var capv : u32 = SW_CAP[0]; + const stored : f64 = (capv as f64) / 10.0; + const outd : f64 = stored - (4.0 + dac_ff); + assert outd * outd < 0.05 * 0.05; + assert_eq(((4.0 + dac_ff) * 10.0 + 0.5) as u32, capv); + // Nothing else in the deck moves with the 20 um x 20 um capacitor: the node capacitors are 4 fF (C1 net4, C4 net1, C5 net2, C7 net5) + // and 4 + 4 fF on net3 (C6, C8). + var c1 : u32 = SW_CAP[1]; + var c3 : u32 = SW_CAP[3]; + assert_eq(c1, 40); + assert_eq(c3, 2 * c1); + assert_eq(SW_CAP[2], c1); + assert_eq(SW_CAP[4], c1); + assert_eq(SW_CAP[5], c1); +} + +test a_device_conducts_from_its_high_end_to_its_low_end_and_the_table_is_the_comparators { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + // The comparator's table, unchanged: its largest entry is the law at (1.5 V, 1.5 V), the knee sits inside cell 5. + assert_eq(t[624], 5916765); + var v : [8]u32 = [0] ** 8; + var reg : [144]u64 = [0] ** 144; + // XM6 (index 5): NMOS, drain Vout, gate net3, source Vin. Vout above Vin by 20 mV, the gate 1.45 V above Vin (Vin = 0.75 V): + // the current leaves Vout and enters the source side, which is the node Vin, a terminal with no row. + v[0] = SW_OFF + 12582912 + 335544; + v[3] = SW_OFF + 12582912 + 24326963; + sw_stamp(&t, &v, &v, SW_OFF, SW_OFF + 12582912, 0, &SW_DV_M, ®); + const x : u32 = 24326963; + const y : u32 = 335544; + const cur : u64 = 3125 * (id_of(&t, x, y) as u64); + assert cur > 0; + // Vout's G+ register holds the current of XM6 (the capacitor terms cancel, v == v0): exactly the table's current times 3125. + assert_eq(reg[(SC_RGP + 0) as usize], 3125 * (id_of(&t, x, y) as u64) + 39342 * ((SW_OFF + 12582912 + 335544) as u64)); + // The same device with Vout 20 mV BELOW Vin: the channel is symmetric, the current now leaves Vin and enters Vout, and the gate + // overdrive is read from the lower end (Vout), so the magnitude is the table at (Vg - Vout, 20 mV). + var w : [8]u32 = [0] ** 8; + var reg2 : [144]u64 = [0] ** 144; + w[0] = SW_OFF + 12582912 - 335544; + w[3] = SW_OFF + 12582912 + 24326963 - 335544; + sw_stamp(&t, &w, &w, SW_OFF, SW_OFF + 12582912, 0, &SW_DV_M, ®2); + const cur2 : u64 = 3125 * (id_of(&t, 24326963, y) as u64); + assert cur2 > 0; + assert_eq(reg2[(SC_RGN + 0) as usize], 39342 * ((SW_OFF + 12582912 - 335544) as u64) + cur2); + // The table's on-resistance is high: at Vds -> 0 the table's slope is the chord over the first 62.5 mV cell, beta (ov - 31.25 mV), + // against the law's beta ov. At Vgs = 1.45 V (ov = 1.1 V) that is 2.85 % low in conductance, which is the 2.9 % in the tracking error. + const gt : f64 = ((gds_cell(&t, x, 0)) as f64) / CELL_F; + const gl : f64 = sc_law_gds_t((x) as f64, 0.0); + const ratio : f64 = gt / gl; + assert ratio > 0.970; + assert ratio < 0.973; +} + +test the_jacobian_is_the_derivative_of_the_residual_and_the_fixed_system_is_the_real_system_to_rounding { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + sw_make_vin(1, &vin); + const s : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + // Four states of the Vin = 0.75 V run, each the end state of a step with the state before it as v0: step 6 and step 333, in the + // middle of the two bootstrap events (the gate net3 at 2.17 V on the rise, collapsing on the fall), and steps 20 and 100, in the + // settling of the track phase. The central differences are taken at the settling states: at step 6 the table is read past its end + // (XM7 has Vsg = 1.53 V, the table stops at 1.5 V) and its slope there is not the derivative of what it returns. + var steps : [4]u32 = [6, 20, 100, 333]; + var k : usize = 0; + while (k < 4) { + const j : u32 = steps[k]; + var vf : [8]f64 = [0.0] ** 8; + var v0f : [8]f64 = [0.0] ** 8; + var vu : [8]u32 = [0] ** 8; + var v0u : [8]u32 = [0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + vf[n as usize] = w[(j * SC_NN + n) as usize]; + v0f[n as usize] = w[((j - 1) * SC_NN + n) as usize]; + vu[n as usize] = out[(j * SC_NN + n) as usize]; + v0u[n as usize] = out[((j - 1) * SC_NN + n) as usize]; + n = n + 1; + } + var drv : [2]f64 = [0.0] ** 2; + drv[0] = SW_OFF_F + ((sw_clk_at(j)) as f64); + drv[1] = SW_OFF_F + ((vin[j as usize]) as f64); + if (j == 20 or j == 100) { + // The Jacobian is dG/dv: a central difference of G over +-16 units, in the table system and in the law (a pure number + // per entry, entries up to 4e4); the transposed matrix is far off. + const e0 : f64 = sw_fd_err(0, &t, &vf, &v0f, &drv, 6032.0, &SW_DV_M, false); + const e1 : f64 = sw_fd_err(1, &t, &vf, &v0f, &drv, 6032.0, &SW_DV_M, false); + const et : f64 = sw_fd_err(0, &t, &vf, &v0f, &drv, 6032.0, &SW_DV_M, true); + assert e0 < 0.01; + assert e1 < 0.001; + assert et > 1.0; + } + // The fixed-point system is the real table system at the same words, up to the table's integer rounding (half a table unit + // times 3125 per device, at most 6 devices on a node) in G and the cell rounding of the slopes in J. + const gg : f64 = sw_gap_g(&t, &vu, &v0u, SW_OFF + sw_clk_at(j), SW_OFF + vin[j as usize], SW_CB, &SW_DV_M); + const gj : f64 = sw_gap_j(&t, &vu, &v0u, SW_OFF + sw_clk_at(j), SW_OFF + vin[j as usize], SW_CB, &SW_DV_M); + assert gg < 10000.0; + assert gj < 0.05; + k = k + 1; + } +} + +test the_circuit_rests_until_the_clock_moves { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + sw_make_vin(3, &vin); + var v : [8]u32 = [0] ** 8; + var v0 : [8]u32 = [0] ** 8; + var info : [1]u32 = [0] ** 1; + sw_rest(vin[0], &v); + sw_rest(vin[0], &v0); + // Past the clock's fall (steps 336 on) clks is low: every channel is off or has no voltage across it, so the state is a fixed point + // of the step whatever Vin does (the sine moves 60 uV a step here). Ten steps, the state is the rest state bit for bit, the + // residual is zero and no iteration moves anything. + var j : u32 = 340; + var same : bool = true; + var rmax : u32 = 0; + var umax : u32 = 0; + while (j < 350) { + const r : u32 = sw_step(&t, &v, j, vin[j as usize], SW_CB, SW_K, &SW_DV_M, &info); + if (r > rmax) { rmax = r; } + if (info[0] > umax) { umax = info[0]; } + var n : u32 = 0; + while (n < SC_NN) { + if (v[n as usize] != v0[n as usize]) { same = false; } + n = n + 1; + } + j = j + 1; + } + assert(same); + assert_eq(rmax, 0); + assert_eq(umax, 0); + // And the clock at its first step of rise moves the circuit: step 1 puts clks at 0.1875 V, below the NMOS threshold, so nothing + // turns on but the inverter's PMOS pulls n_clks 40 uV down at step 2 (clks at 0.375 V). + sw_rest(vin[0], &v); + const r1 : u32 = sw_step(&t, &v, 1, vin[1], SW_CB, SW_K, &SW_DV_M, &info); + assert_eq(info[0], 0); + assert_eq(v[SW_NCLK as usize], SW_VDDW); + const r2 : u32 = sw_step(&t, &v, 2, vin[2], SW_CB, SW_K, &SW_DV_M, &info); + assert info[0] > 0; + assert v[SW_NCLK as usize] < SW_VDDW; + assert v[SW_N1 as usize] < SW_VDDW; +} + +test the_gate_overdrive_the_tracking_error_the_injection_and_the_droop_at_the_three_dc_levels { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + var sel : u32 = 0; + var odprev : f64 = 9.0; + var worst_od : f64 = 0.0; + var worst_te : f64 = 0.0; + while (sel < 3) { + sw_make_vin(sel, &vin); + const s : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const od : f64 = sw_od(&w, &vin); + const te : f64 = sw_te(&w, &vin); + const inj : f64 = sw_inj(&w); + const dr : f64 = sw_droop(&w); + // (1) The gate overdrive while tracking: within the named bound of ngspice at each level, and a real overdrive (the threshold + // is 0.35 V, so 1.4 V of Vgs is 1.05 V of overdrive at Vin = 0.2, 0.75 and 1.3 V alike), a little lower at higher Vin. + assert fabs64(od - SW_NG_OD[sel as usize]) <= SW_B_OD_V; + assert od > 1.4; + assert od < odprev; + odprev = od; + worst_od = fmax64(worst_od, fabs64(od - SW_NG_OD[sel as usize])); + // (2) The tracking error at the end of the track phase: the settled remainder, microvolts. + assert sw_within(te, SW_NG_TE[sel as usize], SW_B_TE_ABS_V, SW_B_TE_REL); + worst_te = fmax64(worst_te, fabs64(te - SW_NG_TE[sel as usize])); + // (3) The injection step and (4) the droop: the square law has no gate capacitance and no leak, so both are round-off. + assert sw_within(inj, SW_NG_INJ[sel as usize], SW_B_INJ_V, 0.0); + assert sw_within(dr, SW_NG_DROOP[sel as usize], SW_B_DROOP_V, 0.0); + // Every step is settled and none needs more iterations than SW_NIT_MAX; the 3328 words fold to the pinned S. + assert sc_max_u(&res, 416) <= SW_B_RES; + assert sc_max_u(&nit, 416) <= SW_NIT_MAX; + assert_eq(s, SW_EXP_S[sel as usize]); + sel = sel + 1; + } + // The measured deviations, from both sides: 0.2528 mV of overdrive (the knee, see THE TABLE) and 1.93 uV of tracking error. + assert worst_od > 0.00025; + assert worst_od < 0.00026; + assert worst_te > 0.0000019; + assert worst_te < 0.00000195; +} + +test the_tracking_error_the_overdrive_the_injection_and_the_droop_at_four_phases_of_the_sine { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + var sel : u32 = 3; + while (sel < 7) { + sw_make_vin(sel, &vin); + const s : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const od : f64 = sw_od(&w, &vin); + const te : f64 = sw_te(&w, &vin); + const inj : f64 = sw_inj(&w); + const dr : f64 = sw_droop(&w); + assert fabs64(od - SW_NG_OD[sel as usize]) <= SW_B_OD_V; + assert sw_within(te, SW_NG_TE[sel as usize], SW_B_TE_ABS_V, SW_B_TE_REL); + assert sw_within(inj, SW_NG_INJ[sel as usize], SW_B_INJ_V, 0.0); + assert sw_within(dr, SW_NG_DROOP[sel as usize], SW_B_DROOP_V, 0.0); + assert sc_max_u(&res, 416) <= SW_B_RES; + assert sc_max_u(&nit, 416) <= SW_NIT_MAX; + assert_eq(s, SW_EXP_S[sel as usize]); + // The input at the end of the track phase, 0.75 V + 0.6 V sin(phase): the zero crossings and the two peaks. + const vend : f64 = ((vin[SW_JF as usize]) as f64) / SC_UPV; + if (sel == 3 or sel == 5) { + assert fabs64(vend - 0.75) < 0.00001; + } else { + assert fabs64(fabs64(vend - 0.75) - 0.6) < 0.0001; + } + sel = sel + 1; + } + // The tracking error is R C dVin/dt: negative while Vin rises, positive while it falls, and tiny at the peaks, where the slope is 0. + sw_make_vin(3, &vin); + const s3 : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const te3 : f64 = sw_te(&w, &vin); + const inj3 : f64 = sw_inj(&w); + assert te3 < 0.0 - 0.0012; + // The table is 2.85 % low in conductance (a_device_conducts...): the lag is 2.9 % longer than the law's and ngspice's, 1.2666 mV. + const ratio : f64 = te3 / SW_NG_TE[3]; + assert ratio > 1.028; + assert ratio < 1.030; + // The gate closes at step 333; Vout follows the input until then, so the injection step is dVin/dt times about 1 ns, 0.24 mV. + assert inj3 > 0.00023; + assert inj3 < 0.00025; +} + +test the_three_errors_kept_apart_at_the_dc_level { + var m : [15]f64 = [0.0] ** 15; + sw_errors(1, &m); + // ARITHMETIC: the fixed-point run against the real run of the same scheme and against the fully converged real run of the same + // table. The largest difference of any node at any step is in the slow tail of the hold (step 416, net2): each step stops with + // up to 64 units owed on the small nodes, and the stops add up to 237 units (14 uV). At the measurements it is under 5 uV. + assert m[0] > 180.0; + assert m[0] < 195.0; + assert m[1] > 230.0; + assert m[1] < SW_B_ARITH_U; + assert fabs64(m[2]) < 0.000001; + assert fabs64(m[3]) < 0.0000025; + assert fabs64(m[4]) < 0.0000003; + // TABLE: the converged real table run against the analytic law. The largest difference is the knee: XM8 (gate Vdd) charges net5 until + // its Vgs is the threshold, 0.35 V in the law, 0.3125 V in the table (the chord inside cell 5), so net5 settles 37.5 mV higher + // and 39.0 mV is the difference at step 35. At the measurements: 0.253 mV of overdrive. + assert m[5] > 650000.0; + assert m[5] < SW_B_TABLE_U; + assert_eq((m[6] + 0.5) as u32, 35); + assert_eq((m[7] + 0.5) as u32, 5); + assert m[8] > 0.00025; + assert m[8] < 0.000255; + assert fabs64(m[9]) < 0.0000001; + assert fabs64(m[10]) < 0.00000001; + // MODEL: the analytic law against ngspice with the same W, L, KP, VTO, capacitors and grid: under a nanovolt at the 56 recorded + // states and in the three measurements. + assert m[11] < SW_B_LAW_V; + assert m[11] > 0.0000000003; + assert fabs64(m[12]) < 0.000000002; + assert fabs64(m[13]) < 0.000000002; + assert fabs64(m[14]) < 0.000000002; +} + +test the_three_errors_kept_apart_at_the_sine_zero_crossing { + var m : [15]f64 = [0.0] ** 15; + sw_errors(3, &m); + assert m[0] > 160.0; + assert m[0] < 170.0; + assert m[1] > 240.0; + assert m[1] < SW_B_ARITH_U; + assert fabs64(m[2]) < 0.000005; + assert fabs64(m[3]) < 0.0000005; + assert fabs64(m[4]) < 0.00000003; + // The knee is the same (the sine only moves Vin by 0.4 mV in the first 35 steps); here the table's on-resistance shows in the + // tracking error: the law lags 36.6 uV less than the table. + assert m[5] > 650000.0; + assert m[5] < SW_B_TABLE_U; + assert_eq((m[6] + 0.5) as u32, 35); + assert_eq((m[7] + 0.5) as u32, 5); + assert m[8] > 0.000252; + assert m[8] < 0.000255; + assert m[9] > 0.000036; + assert m[9] < 0.000037; + assert m[10] > 0.00000035; + assert m[10] < 0.0000004; + assert m[11] < SW_B_LAW_V; + assert m[11] > 0.0000000003; + assert fabs64(m[12]) < 0.000000002; + assert fabs64(m[13]) < 0.000000002; + assert fabs64(m[14]) < 0.000000002; +} + +test the_discretization_error_is_the_grids_and_the_emulator_shares_it_with_ngspice { + // The same circuit, the same sine, ngspice on the 250 ps grid (the emulator's, and so the comparison above is free of it) and on a + // grid five times finer (deck sar_sw_fine): backward Euler at 250 ps is a different circuit from the continuous one. The gate + // overdrive is 3.0 mV higher on the coarse grid (the bootstrap's charge sharing is a stiff step), the tracking error 3.4 uV (0.27 %) + // less negative, and the injection step 11.2 uV (4.5 %) smaller. + var sn0 : usize = 3; + const dod : f64 = SW_NG_OD[sn0] - SW_NG_FINE_OD; + const dte : f64 = SW_NG_TE[sn0] - SW_NG_FINE_TE; + const dinj : f64 = SW_NG_INJ[sn0] - SW_NG_FINE_INJ; + assert dod > 0.00296; + assert dod < SW_B_DISC_OD_V; + assert dte > 0.0000034; + assert dte < SW_B_DISC_TE_V; + assert dinj < 0.0 - 0.0000111; + assert dinj > 0.0 - SW_B_DISC_INJ_V; + // Against the finer grid the emulator carries both: the grid's 2.96 mV and the table's 0.25 mV of overdrive. + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + sw_make_vin(3, &vin); + const s : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const dfine : f64 = sw_od(&w, &vin) - SW_NG_FINE_OD; + assert dfine > 0.0027; + assert dfine < 0.0028; +} + +test k_too_small_is_a_negative_control_and_the_cap_is_exactly_enough { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + // The sine trough (stimulus 6) has the hardest step, the clock fall at step 333, which needs SW_NIT_MAX = 120 iterations. K = 128 + // and K = 600 give the same 3336 words; K = 119 leaves that step unsettled; K = 4 leaves several unsettled on the DC run. + sw_make_vin(6, &vin); + const sa : u64 = sw_run(&t, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + assert_eq(sc_max_u(&nit, 416), SW_NIT_MAX); + assert_eq(nit[332], SW_NIT_MAX); + const sb : u64 = sw_run(&t, &vin, SW_CB, 600, &SW_DV_M, &out, &res, &nit); + assert_eq(sa, sb); + const sc : u64 = sw_run(&t, &vin, SW_CB, SW_NIT_MAX - 1, &SW_DV_M, &out, &res, &nit); + assert sc_max_u(&res, 416) > SW_B_RES; + assert res[332] > SW_B_RES; + sw_make_vin(1, &vin); + const sd : u64 = sw_run(&t, &vin, SW_CB, 4, &SW_DV_M, &out, &res, &nit); + assert sw_cnt_gt(&res, 416, SW_B_RES) >= 4; + assert sc_max_u(&res, 416) > 1000000; +} + +test the_boost_capacitor_removed_fails_the_overdrive_check { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + sw_make_vin(1, &vin); + // The mutant: cb = 0. The gate is no longer lifted above Vin: net3 stays near the 1.5 V that XM10 can pass (the overdrive is 0.437 V). + const s : u64 = sw_run(&t, &vin, 0, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const od : f64 = sw_od(&w, &vin); + const te : f64 = sw_te(&w, &vin); + // The check of the DC run, as the real circuit's, FAILS for the mutant: |OD - ngspice| is 1.0 V against the bound of 0.4 mV, and + // the tracking error is 23 mV against a few microvolts. + const bad : f64 = fabs64(od - SW_NG_OD[1]); + assert bad > SW_B_OD_V; + assert bad > 1.0; + assert !sw_within(te, SW_NG_TE[1], SW_B_TE_ABS_V, SW_B_TE_REL); + // And the mutant is not just wrong: it is ngspice's own circuit without the capacitor (deck sar_sw_nocb) to 2.85 mV of overdrive, so + // the emulator tells a circuit with a boost capacitor from one without. + const dn : f64 = od - SW_NG_NOCB_OD; + assert fabs64(dn) < SW_B_NOCB_V; + assert dn > 0.0028; + assert dn < 0.0029; + // The tracking error of the mutant is 25.4 mV, ngspice's no-boost deck 23.4 mV: the table's knee is worst at 80 mV of overdrive. + assert fabs64(te - SW_NG_NOCB_TE) > 0.0019; + assert fabs64(te - SW_NG_NOCB_TE) < 0.0021; + assert sc_max_u(&res, 416) <= SW_B_RES; +} + +test a_wrong_threshold_table_a_stuck_pull_down_and_a_weak_switch_are_caught { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + // A table built with Vth = 0.30 V: the gate overdrive of the DC 0.75 V run is 4.2 mV off ngspice, against the bound of 0.4 mV. + var t2 : [625]u32 = [0] ** 625; + sc_build_table(&t2, 0.30); + sw_make_vin(1, &vin); + const s1 : u64 = sw_run(&t2, &vin, SW_CB, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + const dv : f64 = sw_od(&w, &vin) - SW_NG_OD[1]; + assert fabs64(dv) > SW_B_OD_V; + assert fabs64(dv) > 0.004; + assert fabs64(dv) < 0.0045; + // XM9 (the pull-down of net5, index 8) removed: net3 can no longer discharge through XM8 and XM9 when clks falls, the gate stays high, + // XM6 stays on, and Vout follows the sine through the hold: 4.1 mV of "droop" against the bound of 1 nV. + var mult : [12]u32 = [3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125, 3125]; + mult[8] = 0; + sw_make_vin(3, &vin); + const s2 : u64 = sw_run(&t, &vin, SW_CB, SW_K, &mult, &out, &res, &nit); + sw_widen(&out, &w); + const dr : f64 = sw_droop(&w); + assert fabs64(dr) > SW_B_DROOP_V; + assert dr > 0.0040; + assert dr < 0.0042; + // XM6 at half width: twice the on-resistance, twice the lag: 1.40 mV more than ngspice's 1.2666 mV, against a few microvolts. + mult[8] = 3125; + mult[5] = 1562; + const s3 : u64 = sw_run(&t, &vin, SW_CB, SW_K, &mult, &out, &res, &nit); + sw_widen(&out, &w); + const te : f64 = sw_te(&w, &vin); + assert !sw_within(te, SW_NG_TE[3], SW_B_TE_ABS_V, SW_B_TE_REL); + assert fabs64(te - SW_NG_TE[3]) > 0.0013; + assert fabs64(te - SW_NG_TE[3]) < 0.0015; +} + +test the_words_have_headroom { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + sw_make_vin(1, &vin); + // The widest word any stage of the solve forms at the first iteration of the clock edges (steps 5, 6, 7, 331, 332, 333, 334), + // where the Newton move is largest: 7.23e16 (2^56), below the 2^62 the comparator asks for, 2^63 being the u64's limit. + const wide : f64 = sw_headroom(&t, &vin, SW_CB, &SW_DV_M); + assert wide < 4611686018427387904.0; + assert wide > 7.0e16; + assert wide < 7.5e16; +}