From 0c359810fa776f0150792992976f13de8d762bcb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 00:42:13 +0700 Subject: [PATCH] analog: sar_cdac.t27 -- the SAR ADC split capacitive DAC with its 1-unit bridge, charge redistribution against ngspice (Closes #10540) M4a of the SAR ADC milestone (#10524, epic #10486). One half of Diff_cap_DAC of the golden SAR ADC deck: 31 LSB units on net1, a 1-unit bridge, 31 MSB units on Vout, unit counts and deck lines (529-591) pinned in the spec. Each bottom-plate switch event is M1's step (specs/analog/linear.t27) with D = 0 and N = K = C^-1 C_b, the Q.24 charge-transfer ratios derived in the tests from the counts two ways (adjugate and closed form) and stored as words. A code is ten events, Bit_10 first, plates moving +-VCM from the sampled state. Result for all 1024 codes of one half: - the deck's 1-unit bridge is the ideal bridge for its 31-unit LSB array (no dummy unit): every Vout ratio is exactly 2^(j-1)/1023, INL 0 - bridge-induced error is a gain term: (2c-1023)/2046 LSB against the nominal 10-bit half, max 0.5 LSB at codes 0 and 1023 (first at code 0) - arithmetic error vs the f64 closed form: 1.74 units of 2^-24 V (7.1e-5 LSB) - ngspice-47 with ideal capacitors (decks sar_cdac_half.cir, sar_cdac_diff.cir): 1.29e-12 V from the closed form, 1.74 units from the fixed point, every code - differential pair checked at 16 codes - the 32/31 bridge is ideal only with a dummy 32nd unit: with it bend 0 and gain error 0; on the deck's array it bends 0.94 LSB. Bit_8 with 3 units fails 7 of 12 tests (28.9 LSB of bend, 42 mV from ngspice) 12 tests, 0 vacuous, sealed (all hashes MATCH). Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/analog_AnalogSarCdac.json | 33 + specs/analog/decks/sar_cdac_diff.cir | 159 ++++ specs/analog/decks/sar_cdac_half.cir | 87 ++ specs/analog/sar_cdac.t27 | 1026 ++++++++++++++++++++++ 4 files changed, 1305 insertions(+) create mode 100644 .trinity/seals/analog_AnalogSarCdac.json create mode 100644 specs/analog/decks/sar_cdac_diff.cir create mode 100644 specs/analog/decks/sar_cdac_half.cir create mode 100644 specs/analog/sar_cdac.t27 diff --git a/.trinity/seals/analog_AnalogSarCdac.json b/.trinity/seals/analog_AnalogSarCdac.json new file mode 100644 index 0000000000..4150b7e5c3 --- /dev/null +++ b/.trinity/seals/analog_AnalogSarCdac.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", + "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:a4fb3d480089e4e90b488d2470ac9e1c592434c4264457ba3be4655c1237ec9c", + "gen_hash_rust": "sha256:76e21ceaecfeac4d922f02d53f9a9a877b2404bacc43797bc1c3ccb674d919ea", + "gen_hash_verilog": "sha256:780bcc67575db05dade43bfbe1b3d37431adb427076b86b0958269568a62fcbd", + "gen_hash_zig": "sha256:511cd3ff1f9ed0df9ffc60e32b4a45a4bac154a42e6a3299b38d1dfd42bdad10", + "module": "AnalogSarCdac", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T17:35:26Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:0fa41f4de9f797a091d30fdf51896c81971b6d0504587431267172179a711309", + "spec_path": "specs/analog/sar_cdac.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/specs/analog/decks/sar_cdac_diff.cir b/specs/analog/decks/sar_cdac_diff.cir new file mode 100644 index 0000000000..0ce9191508 --- /dev/null +++ b/specs/analog/decks/sar_cdac_diff.cir @@ -0,0 +1,159 @@ +SAR ADC differential split capacitive DAC, x17 (VDN) and x18 (VDP) of the golden deck, ideal capacitors, 1024 codes in one run +* specs/analog/decks/sar_cdac_diff.cir -- test data for specs/analog/sar_cdac.t27 (#10540). A plain netlist, no .control block. +* Two copies of the Diff_cap_DAC topology of sar_cdac_half.cir (see there for the unit counts and the deck lines they come from). +* P half (x18, top plate mp, net1 lp, plates pp1..pp10): sampled at 0.875 V. N half (x17, top plate mn, net1 ln, plates pn1..pn10): sampled at 0.625 V. +* Every plate rests at VCM = 0.75 V until 10 ns. Interval n (n = 0..1023, 10 ns each): P plate j is at VDD = 1.5 V when bit j-1 of n is 1 and at 0 V +* when it is 0; the N plates are the complement (neg_bottom_plate_logic: Bit_n = not Bit once the bit is decided), so the N code is 1023 - n. +* The network is algebraic (capacitors only), so the settled voltage of an interval does not depend on the time grid. +* Read the LAST printed point inside each interval. Run: ngspice -b sar_cdac_diff.cir (the spec records v(mp) and v(mn) for 16 codes; +* .print shows 7 digits, the spec's values were read with set numdgt=15 in a control block wrapped around this same netlist). +Cp1_1 lp pp1 100f IC=0.125 +Cp2_1 lp pp2 100f IC=0.125 +Cp2_2 lp pp2 100f IC=0.125 +Cp3_1 lp pp3 100f IC=0.125 +Cp3_2 lp pp3 100f IC=0.125 +Cp3_3 lp pp3 100f IC=0.125 +Cp3_4 lp pp3 100f IC=0.125 +Cp4_1 lp pp4 100f IC=0.125 +Cp4_2 lp pp4 100f IC=0.125 +Cp4_3 lp pp4 100f IC=0.125 +Cp4_4 lp pp4 100f IC=0.125 +Cp4_5 lp pp4 100f IC=0.125 +Cp4_6 lp pp4 100f IC=0.125 +Cp4_7 lp pp4 100f IC=0.125 +Cp4_8 lp pp4 100f IC=0.125 +Cp5_1 lp pp5 100f IC=0.125 +Cp5_2 lp pp5 100f IC=0.125 +Cp5_3 lp pp5 100f IC=0.125 +Cp5_4 lp pp5 100f IC=0.125 +Cp5_5 lp pp5 100f IC=0.125 +Cp5_6 lp pp5 100f IC=0.125 +Cp5_7 lp pp5 100f IC=0.125 +Cp5_8 lp pp5 100f IC=0.125 +Cp5_9 lp pp5 100f IC=0.125 +Cp5_10 lp pp5 100f IC=0.125 +Cp5_11 lp pp5 100f IC=0.125 +Cp5_12 lp pp5 100f IC=0.125 +Cp5_13 lp pp5 100f IC=0.125 +Cp5_14 lp pp5 100f IC=0.125 +Cp5_15 lp pp5 100f IC=0.125 +Cp5_16 lp pp5 100f IC=0.125 +Cp6_1 mp pp6 100f IC=0.125 +Cp7_1 mp pp7 100f IC=0.125 +Cp7_2 mp pp7 100f IC=0.125 +Cp8_1 mp pp8 100f IC=0.125 +Cp8_2 mp pp8 100f IC=0.125 +Cp8_3 mp pp8 100f IC=0.125 +Cp8_4 mp pp8 100f IC=0.125 +Cp9_1 mp pp9 100f IC=0.125 +Cp9_2 mp pp9 100f IC=0.125 +Cp9_3 mp pp9 100f IC=0.125 +Cp9_4 mp pp9 100f IC=0.125 +Cp9_5 mp pp9 100f IC=0.125 +Cp9_6 mp pp9 100f IC=0.125 +Cp9_7 mp pp9 100f IC=0.125 +Cp9_8 mp pp9 100f IC=0.125 +Cp10_1 mp pp10 100f IC=0.125 +Cp10_2 mp pp10 100f IC=0.125 +Cp10_3 mp pp10 100f IC=0.125 +Cp10_4 mp pp10 100f IC=0.125 +Cp10_5 mp pp10 100f IC=0.125 +Cp10_6 mp pp10 100f IC=0.125 +Cp10_7 mp pp10 100f IC=0.125 +Cp10_8 mp pp10 100f IC=0.125 +Cp10_9 mp pp10 100f IC=0.125 +Cp10_10 mp pp10 100f IC=0.125 +Cp10_11 mp pp10 100f IC=0.125 +Cp10_12 mp pp10 100f IC=0.125 +Cp10_13 mp pp10 100f IC=0.125 +Cp10_14 mp pp10 100f IC=0.125 +Cp10_15 mp pp10 100f IC=0.125 +Cp10_16 mp pp10 100f IC=0.125 +Cbrp lp mp 100f IC=0 +Cn1_1 ln pn1 100f IC=-0.125 +Cn2_1 ln pn2 100f IC=-0.125 +Cn2_2 ln pn2 100f IC=-0.125 +Cn3_1 ln pn3 100f IC=-0.125 +Cn3_2 ln pn3 100f IC=-0.125 +Cn3_3 ln pn3 100f IC=-0.125 +Cn3_4 ln pn3 100f IC=-0.125 +Cn4_1 ln pn4 100f IC=-0.125 +Cn4_2 ln pn4 100f IC=-0.125 +Cn4_3 ln pn4 100f IC=-0.125 +Cn4_4 ln pn4 100f IC=-0.125 +Cn4_5 ln pn4 100f IC=-0.125 +Cn4_6 ln pn4 100f IC=-0.125 +Cn4_7 ln pn4 100f IC=-0.125 +Cn4_8 ln pn4 100f IC=-0.125 +Cn5_1 ln pn5 100f IC=-0.125 +Cn5_2 ln pn5 100f IC=-0.125 +Cn5_3 ln pn5 100f IC=-0.125 +Cn5_4 ln pn5 100f IC=-0.125 +Cn5_5 ln pn5 100f IC=-0.125 +Cn5_6 ln pn5 100f IC=-0.125 +Cn5_7 ln pn5 100f IC=-0.125 +Cn5_8 ln pn5 100f IC=-0.125 +Cn5_9 ln pn5 100f IC=-0.125 +Cn5_10 ln pn5 100f IC=-0.125 +Cn5_11 ln pn5 100f IC=-0.125 +Cn5_12 ln pn5 100f IC=-0.125 +Cn5_13 ln pn5 100f IC=-0.125 +Cn5_14 ln pn5 100f IC=-0.125 +Cn5_15 ln pn5 100f IC=-0.125 +Cn5_16 ln pn5 100f IC=-0.125 +Cn6_1 mn pn6 100f IC=-0.125 +Cn7_1 mn pn7 100f IC=-0.125 +Cn7_2 mn pn7 100f IC=-0.125 +Cn8_1 mn pn8 100f IC=-0.125 +Cn8_2 mn pn8 100f IC=-0.125 +Cn8_3 mn pn8 100f IC=-0.125 +Cn8_4 mn pn8 100f IC=-0.125 +Cn9_1 mn pn9 100f IC=-0.125 +Cn9_2 mn pn9 100f IC=-0.125 +Cn9_3 mn pn9 100f IC=-0.125 +Cn9_4 mn pn9 100f IC=-0.125 +Cn9_5 mn pn9 100f IC=-0.125 +Cn9_6 mn pn9 100f IC=-0.125 +Cn9_7 mn pn9 100f IC=-0.125 +Cn9_8 mn pn9 100f IC=-0.125 +Cn10_1 mn pn10 100f IC=-0.125 +Cn10_2 mn pn10 100f IC=-0.125 +Cn10_3 mn pn10 100f IC=-0.125 +Cn10_4 mn pn10 100f IC=-0.125 +Cn10_5 mn pn10 100f IC=-0.125 +Cn10_6 mn pn10 100f IC=-0.125 +Cn10_7 mn pn10 100f IC=-0.125 +Cn10_8 mn pn10 100f IC=-0.125 +Cn10_9 mn pn10 100f IC=-0.125 +Cn10_10 mn pn10 100f IC=-0.125 +Cn10_11 mn pn10 100f IC=-0.125 +Cn10_12 mn pn10 100f IC=-0.125 +Cn10_13 mn pn10 100f IC=-0.125 +Cn10_14 mn pn10 100f IC=-0.125 +Cn10_15 mn pn10 100f IC=-0.125 +Cn10_16 mn pn10 100f IC=-0.125 +Cbrn ln mn 100f IC=0 +Bp1 pp1 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/1)-2*floor(floor((time-10n)/10n)/2))-1) +Bp2 pp2 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/2)-2*floor(floor((time-10n)/10n)/4))-1) +Bp3 pp3 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/4)-2*floor(floor((time-10n)/10n)/8))-1) +Bp4 pp4 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/8)-2*floor(floor((time-10n)/10n)/16))-1) +Bp5 pp5 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/16)-2*floor(floor((time-10n)/10n)/32))-1) +Bp6 pp6 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/32)-2*floor(floor((time-10n)/10n)/64))-1) +Bp7 pp7 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/64)-2*floor(floor((time-10n)/10n)/128))-1) +Bp8 pp8 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/128)-2*floor(floor((time-10n)/10n)/256))-1) +Bp9 pp9 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/256)-2*floor(floor((time-10n)/10n)/512))-1) +Bp10 pp10 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/512)-2*floor(floor((time-10n)/10n)/1024))-1) +Bn1 pn1 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/1)-2*floor(floor((time-10n)/10n)/2))-1) +Bn2 pn2 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/2)-2*floor(floor((time-10n)/10n)/4))-1) +Bn3 pn3 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/4)-2*floor(floor((time-10n)/10n)/8))-1) +Bn4 pn4 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/8)-2*floor(floor((time-10n)/10n)/16))-1) +Bn5 pn5 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/16)-2*floor(floor((time-10n)/10n)/32))-1) +Bn6 pn6 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/32)-2*floor(floor((time-10n)/10n)/64))-1) +Bn7 pn7 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/64)-2*floor(floor((time-10n)/10n)/128))-1) +Bn8 pn8 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/128)-2*floor(floor((time-10n)/10n)/256))-1) +Bn9 pn9 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/256)-2*floor(floor((time-10n)/10n)/512))-1) +Bn10 pn10 0 V = 0.75 - 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/512)-2*floor(floor((time-10n)/10n)/1024))-1) +.ic v(mp)=0.875 v(lp)=0.875 v(mn)=0.625 v(ln)=0.625 +.tran 5n 10250n 0 5n uic +.print tran v(mp) v(mn) +.end diff --git a/specs/analog/decks/sar_cdac_half.cir b/specs/analog/decks/sar_cdac_half.cir new file mode 100644 index 0000000000..cb74af73ef --- /dev/null +++ b/specs/analog/decks/sar_cdac_half.cir @@ -0,0 +1,87 @@ +SAR ADC split capacitive DAC, one half (x18 of the golden deck), ideal capacitors, 1024 codes in one run +* specs/analog/decks/sar_cdac_half.cir -- test data for specs/analog/sar_cdac.t27 (#10540). A plain netlist, no .control block. +* Topology of Diff_cap_DAC (the golden SAR ADC deck, subckt Diff_cap_DAC, lines 529-591): 31 unit capacitors from net1 (node l) to the LSB plates +* p1..p5 (1, 2, 4, 8, 16 units), one unit between net1 and Vout (node m), 31 unit capacitors from Vout to the MSB plates p6..p10 (1, 2, 4, 8, 16). +* Unit = 100 fF, ideal. Plate p_j is Bit_j. The tracking switch is not modelled: Vout and net1 start at the sampled 0.75 V (.ic, and IC=0 on every +* capacitor because every plate rests at VCM = 0.75 V and the bridge is uncharged). From 10 ns, interval n (n = 0..1023, 10 ns each) puts plate j +* at VDD = 1.5 V when bit j-1 of n is 1 and at 0 V when it is 0: the code is n, and the plates go straight from code n-1 to code n. The network is +* algebraic (capacitors only), so the settled voltage of an interval does not depend on the time grid; a B source declares no breakpoint. +* Read the LAST printed point inside each interval. Run: ngspice -b sar_cdac_half.cir (.print shows 7 digits; the spec's table was read with +* set numdgt=15 in a control block wrapped around this same netlist, so it holds the full float64 values). +C1_1 l p1 100f IC=0 +C2_1 l p2 100f IC=0 +C2_2 l p2 100f IC=0 +C3_1 l p3 100f IC=0 +C3_2 l p3 100f IC=0 +C3_3 l p3 100f IC=0 +C3_4 l p3 100f IC=0 +C4_1 l p4 100f IC=0 +C4_2 l p4 100f IC=0 +C4_3 l p4 100f IC=0 +C4_4 l p4 100f IC=0 +C4_5 l p4 100f IC=0 +C4_6 l p4 100f IC=0 +C4_7 l p4 100f IC=0 +C4_8 l p4 100f IC=0 +C5_1 l p5 100f IC=0 +C5_2 l p5 100f IC=0 +C5_3 l p5 100f IC=0 +C5_4 l p5 100f IC=0 +C5_5 l p5 100f IC=0 +C5_6 l p5 100f IC=0 +C5_7 l p5 100f IC=0 +C5_8 l p5 100f IC=0 +C5_9 l p5 100f IC=0 +C5_10 l p5 100f IC=0 +C5_11 l p5 100f IC=0 +C5_12 l p5 100f IC=0 +C5_13 l p5 100f IC=0 +C5_14 l p5 100f IC=0 +C5_15 l p5 100f IC=0 +C5_16 l p5 100f IC=0 +C6_1 m p6 100f IC=0 +C7_1 m p7 100f IC=0 +C7_2 m p7 100f IC=0 +C8_1 m p8 100f IC=0 +C8_2 m p8 100f IC=0 +C8_3 m p8 100f IC=0 +C8_4 m p8 100f IC=0 +C9_1 m p9 100f IC=0 +C9_2 m p9 100f IC=0 +C9_3 m p9 100f IC=0 +C9_4 m p9 100f IC=0 +C9_5 m p9 100f IC=0 +C9_6 m p9 100f IC=0 +C9_7 m p9 100f IC=0 +C9_8 m p9 100f IC=0 +C10_1 m p10 100f IC=0 +C10_2 m p10 100f IC=0 +C10_3 m p10 100f IC=0 +C10_4 m p10 100f IC=0 +C10_5 m p10 100f IC=0 +C10_6 m p10 100f IC=0 +C10_7 m p10 100f IC=0 +C10_8 m p10 100f IC=0 +C10_9 m p10 100f IC=0 +C10_10 m p10 100f IC=0 +C10_11 m p10 100f IC=0 +C10_12 m p10 100f IC=0 +C10_13 m p10 100f IC=0 +C10_14 m p10 100f IC=0 +C10_15 m p10 100f IC=0 +C10_16 m p10 100f IC=0 +Cbr l m 100f IC=0 +B1 p1 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/1)-2*floor(floor((time-10n)/10n)/2))-1) +B2 p2 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/2)-2*floor(floor((time-10n)/10n)/4))-1) +B3 p3 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/4)-2*floor(floor((time-10n)/10n)/8))-1) +B4 p4 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/8)-2*floor(floor((time-10n)/10n)/16))-1) +B5 p5 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/16)-2*floor(floor((time-10n)/10n)/32))-1) +B6 p6 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/32)-2*floor(floor((time-10n)/10n)/64))-1) +B7 p7 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/64)-2*floor(floor((time-10n)/10n)/128))-1) +B8 p8 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/128)-2*floor(floor((time-10n)/10n)/256))-1) +B9 p9 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/256)-2*floor(floor((time-10n)/10n)/512))-1) +B10 p10 0 V = 0.75 + 0.75*u(time-10n)*(2*(floor(floor((time-10n)/10n)/512)-2*floor(floor((time-10n)/10n)/1024))-1) +.ic v(m)=0.75 v(l)=0.75 +.tran 5n 10250n 0 5n uic +.print tran v(m) +.end diff --git a/specs/analog/sar_cdac.t27 b/specs/analog/sar_cdac.t27 new file mode 100644 index 0000000000..37b827f692 --- /dev/null +++ b/specs/analog/sar_cdac.t27 @@ -0,0 +1,1026 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/sar_cdac.t27 -- the SAR ADC's split capacitive DAC with its 1-unit bridge, by charge redistribution, against ngspice +; M4a of the SAR ADC milestone (#10524, epic #10486, issue #10540). The DAC is Diff_cap_DAC of the golden SAR ADC deck (uberWAVE, +; wp2-parser/golden/top.spice, subckt Diff_cap_DAC, lines 517-592; GPL-2.0, read at run time and never vendored). Everything +; hand-written is this file and the two ngspice decks in specs/analog/decks/ (data). It `use`s specs/analog/linear.t27 (M1): the +; switch event IS M1's step, called by name, and nothing of M1 is copied. +; - +; THE NETWORK, read from the deck (one cap_cmim unit per line; LINE_FIRST / LINE_LAST below pin the line numbers): +; net1 - Bit_1 .. Bit_5 1, 2, 4, 8, 16 units lines 529-559 31 units, the LSB array (there is no dummy unit) +; net1 - Vout 1 unit line 560 the bridge +; Vout - Bit_6 .. Bit_10 1, 2, 4, 8, 16 units lines 561-591 31 units, the MSB array, on the top plate +; 63 units. Two floating nodes (net1, Vout) and ten plates. The differential DAC is two copies: x18 on VDP with Bit_*, x17 on VDN +; with Bit_*_n. The absolute value of a unit cancels: only the counts matter here (the IHP capacitor value, the 4 fF loads on VDP +; and VDN and the comparator input are parasitics, M3b #10516). +; - +; WHAT THE BOTTOM PLATES DO, and what is modelled (DAC_control_top, DAC_control, neg_bottom_plate_logic: lines 425-511, 725-764). +; Sampling: the tracking switch follows the input onto Vout; every DAC_control passes VCM (0.75 V) to its plate while its clock +; CK_k is low, so all ten plates rest at VCM. Conversion: SAR_logic shifts one token down CK11, CK10, ... CK2, CK1 at each +; comparison; when CK_k rises its DAC_control latches the comparator output and its inverter drives the plate with the NOT of it: +; VDD (1.5 V) or 0 V. CK11 drives Bit_10 (the first decision), CK10 Bit_9, ... CK2 Bit_1; CK1 drives Bit_0, which has no capacitor +; (the eleventh decision selects nothing in the DAC). A plate switches once, from VCM, and stays. The N half gets Bit_k_n = +; Bit_k until the bit is decided (both VCM) and NOT Bit_k after, so every N move is the P move reversed. Modelled: the top plate +; starts at a sampled voltage, net1 at the same voltage (the bridge uncharged), every plate at VCM; a code is ten plate moves, +; Bit_10 first (the SAR order), +VCM for a plate that ends at VDD (bit set) and -VCM for one that ends at 0 V. The comparator is +; not modelled: the code is an input. Which comparator polarity sets a bit is a relabelling c <-> 1023 - c and changes no error. +; - +; THE METHOD. Charge conservation at the two floating nodes: C dV = C_b dVb, with C = [[CL + Cb, -Cb], [-Cb, CM + Cb]] (CL and CM +; the unit counts hanging from net1 and Vout, Cb the bridge) and C_b the plate's units on its own node. So a switch event moves +; the nodes by K dVb with K = C^-1 C_b, constant for the plate, and the network has no other state. The tests derive K in f64 +; from UNITS and SIDE by the adjugate and, separately, by the closed form D = CL CM + Cb (CL + CM) (a plate of u units on net1 +; moves net1 by u (CM + Cb) / D and Vout by u Cb / D; a plate on Vout moves net1 by u Cb / D and Vout by u (CL + Cb) / D); the two +; agree to 1e-15. The stored K_Q are the Q.24 roundings of K, asserted equal, no value within 1e-4 of a rounding tie. A switch +; event is M1's step x' = x + rnd(D x + N u) with D = 0, N = the plate's two ratios and u = +-VCM as a word: one rounding per +; node per event, half away from zero. A code is ten events. A move does not depend on the state, so the order of the ten events +; does not change the result (asserted: Bit_1 first gives the same words) and the complement code mirrors it about the sample. +; - +; THE BRIDGE, MEASURED (FR-002: reported, not absorbed). For this deck D = 31 * 31 + 1 * (31 + 31) = 1023 and EVERY Vout ratio is +; exactly 2^(j - 1) / 1023 (net1's are 32 times that for a plate of its own array). The weights are exactly binary: the 1-unit +; bridge is the IDEAL bridge for a 31-unit LSB array. The ideal bridge is CL / 31: 1 unit for CL = 31 (the deck), 32/31 for CL = 32, +; an array with a 32nd unit (a dummy to ground on net1), the textbook split array. Issue #10540 expected the deck to carry a +; half-to-half mismatch against 32/31; it does not, because the deck has no dummy unit. What the 1-unit bridge does leave is a +; GAIN error. It is reported as two numbers, each over all 1024 codes of one half: +; bend (INL about the line through codes 0 and 1023, in LSB of that line) 0, below 1e-9 LSB, at every code +; gain error against the nominal half (LSB = VDD / 1024 = 1.46484375 mV = 24576 units, V = VCM (2 c - 1023) / 1024) +; (2 c - 1023) / 2046 LSB: -0.5 at code 0, zero between codes 511 and 512, +0.5 at code 1023. Max |error| 0.5 LSB, first at code 0. +; The cause is the series of the bridge with the LSB array: 1 unit in series with 31 is 31/32 of a unit, so the top plate sees +; 1023/32 units where a nominal half has 32, and the staircase spans exactly VDD (code 0 to code 1023) where a nominal half spans +; 1023/1024 of it: one LSB of span. +; - +; THE ARITHMETIC ERROR, kept apart: fixed point against the f64 sum of the same ten events (same K, unrounded): at most 1.74 units of +; 2^-24 V (1.04e-7 V, 7.1e-5 LSB of the half) on Vout, first at code 224, and 1.74 on net1, first at code 1016; bounds from both +; sides. Code 0 lands one unit below 0 V (a negative word) and code 1023 one unit above 1.5 V, on both nodes: ten rounded moves +; add up to a hair more than the 0.75 V the sample can give. The fold S of both nodes of all 1024 codes is pinned for a board. +; - +; THE NGSPICE REFERENCE. ngspice-47 (Homebrew), ideal capacitors, the topology above, one 100 fF capacitor per deck unit (63 lines), +; plates driven by B sources. specs/analog/decks/sar_cdac_half.cir: one half sampled at 0.75 V; interval n (10 ns, n = 0 .. 1023) +; puts the plates at code n; the last printed point of each interval is the table NG_HALF, all 1024 codes, float64 values. +; specs/analog/decks/sar_cdac_diff.cir: both halves, P sampled at 0.875 V with code n, N sampled at 0.625 V with code 1023 - n; +; 16 codes (the ends, every single-bit code, 511, 512, 513 and the two alternating patterns) are recorded. The capacitor network is +; algebraic, so the settled voltage of an interval does not depend on the time grid: this is not the fixed-step recipe of bench.t27 +; (#10505), which exists for dynamics. Points inside one interval agree to 3.1e-15 V (half deck) and 1.0e-14 V (diff deck). The +; table was read with `set numdgt=15` in a control block wrapped around the committed netlist (the decks stay plain netlists; +; `.print` shows 7 digits); the ngspice output reader is M2 (#10509). The test cannot run ngspice: like bench.t27 this file holds a +; measurement, made on 2026-10-18 on the same machine as bench.t27, and the closed form (1e-12 V) is what keeps a bad table out. +; ngspice against the f64 closed form at most 1.29e-12 V, at code 1023 (bounds 1.2e-12 .. 1.4e-12): a drift that grows over the 10 us +; run, zero at the first codes; its cause was not investigated +; ngspice against the fixed point at most 1.74 units (1.04e-7 V, 7.1e-5 LSB), first at code 224, asserted for every code +; differential pair (16 codes) each half within 2 units of ngspice, the difference VP - VN within 4 units of +; (VSP - VSN) + VDD (2 c - 1023) / 1023 and within 3e-12 V of ngspice's +; - +; THE NEGATIVE CONTROLS, each a test that passes by seeing the failure: +; Bit_8 with 3 units every one of the 20 stored words differs; the staircase bends 28.9 LSB (first at code 128); ngspice is 42 mV off +; bridge 32/31, no dummy the deck's array with the textbook bridge bends 0.94 LSB (first at code 31); ngspice is 1.37 mV from it +; bridge 32/31, dummy unit the textbook array: bend 0, gain error 0 (the error does vanish, but only for this array, which the +; deck is not); ngspice is 0.73 mV from it +; bridge 1, dummy unit bend 0.91 LSB, gain error 0.93 LSB +; Bit_6 on net1, a 2-unit bridge, Bit_10 with 15 units stored words differ +; Also the tie: Bit_3's net1 move is 1574401.5 units and rounds away from zero both ways. +; - +; WHAT A PASS SAYS. The 63-unit inventory and its deck lines; the K derived from it two ways and stored as Q.24; 1024 codes of one +; half converted by ten M1 steps each, within 1.74 units (7.1e-5 LSB) of the f64 closed form VDD c / 1023 and of ngspice with +; ideal capacitors; the bridge's gain error of (2 c - 1023) / 2046 LSB with no bend; the differential pair at 16 codes; the +; textbook bridge ideal only with a dummy unit; a changed count caught. +; WHAT IT DOES NOT SAY. Nothing about the real capacitor: no IHP cap_cmim value or mismatch, no bottom-plate or routing parasitic, +; no 4 fF loads on VDP / VDN, no comparator input capacitance (they scale the gain by a fraction of a percent, M3b). Nothing about +; the switches: no tracking-switch charge injection or sampling noise, no on-resistance, settling or leakage; the plates move +; instantly and the nodes hold charge for ever. Nothing about the comparator or a conversion: the SAR logic, the eleventh decision +; and the output code are not modelled; the sample plus the swing leaves the rails at the extremes for the differential spot +; codes (-0.125 V, 1.625 V), which the real conversion never reaches. Nothing about a board: no bitstream, no clock, no +; LUT or DSP. Nothing about power or about speed against ngspice. The ngspice table is one day's measurement, not re-run here. +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogSarCdac; + +use analog::linear::{step, sm_f64, q_sm, tie_gap, fabs64, fold, NEG, SCALE}; + +pub const KIND : str = "analog-cell"; +pub const ID : str = "analog/sar_cdac"; +pub const REFERENCE : str = "uberWAVE wp2-parser/golden/top.spice, subckt Diff_cap_DAC (read at run time, GPL-2.0, not vendored); specs/analog/linear.t27"; + +; --- the inventory read from the deck: Diff_cap_DAC, one cap_cmim unit per line -------------------------------------- +; Plate j (index j - 1) is Bit_j. Bit_1..Bit_5 hang from net1 (the LSB array), Bit_6..Bit_10 from Vout (the MSB array, the top plate). +/// Plates in one half of the DAC: Bit_1 .. Bit_10. +pub const PLATES : usize = 10; +/// cap_cmim units on each plate, Bit_1 .. Bit_10. +pub const UNITS : [10]u32 = [1, 2, 4, 8, 16, 1, 2, 4, 8, 16]; +pub const SIDE_L : u8 = 0; +pub const SIDE_M : u8 = 1; +/// The node a plate's units hang from: 0 = net1 (the LSB array), 1 = Vout (the MSB array and the top plate). +pub const SIDE : [10]u8 = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]; +/// The deck's lines of each plate's units, first and last (top.spice of the golden SAR ADC deck). +pub const LINE_FIRST : [10]u32 = [529, 530, 532, 536, 544, 561, 562, 564, 568, 576]; +pub const LINE_LAST : [10]u32 = [529, 531, 535, 543, 559, 561, 563, 567, 575, 591]; +/// The bridge: one unit between net1 and Vout, line 560 (XC1 net1 Vout). +pub const BRIDGE_UNITS : u32 = 1; +pub const BRIDGE_LINE : u32 = 560; +/// Units in one half: 31 + 1 + 31. +pub const TOTAL_UNITS : u32 = 63; + +; --- the numbers: M1's words, unsigned sign-magnitude at 2^-24 V ----------------------------------------------------- +/// 1.5 V. +pub const VDD : u32 = 25165824; +/// 0.75 V: every plate rests here until its bit is decided (DAC_control passes VCM while its clock is low). +pub const VCM : u32 = 12582912; +/// The sampled top-plate voltage of the 1024-code sweep: 0.75 V, so the plates reach 0 V .. 1.5 V. +pub const VS_HALF : u32 = 12582912; +/// The differential spot check: P half sampled at 0.875 V, N half at 0.625 V (exact at 2^-24). +pub const VS_P : u32 = 14680064; +pub const VS_N : u32 = 10485760; +pub const CODES : u32 = 1024; +/// One LSB of the 10-bit half, VDD / 1024 = 1.46484375 mV, in units of 2^-24 V. +pub const LSB : u32 = 24576; +pub const LSB_F : f64 = 24576.0; +pub const VCM_V : f64 = 0.75; +pub const VDD_V : f64 = 1.5; +pub const VSP_V : f64 = 0.875; +pub const VSN_V : f64 = 0.625; + +; --- the charge-transfer ratios, derived in the tests from UNITS and SIDE, stored here as Q.24 words ------------------- +; K[2j] is the change of net1 and K[2j + 1] the change of Vout per volt of plate index j (j = 0 is Bit_1, j = 9 is Bit_10). For the +; deck the Vout ratio is 2^j / 1023, and net1's is 32 times that for a plate of its own array and 1 / 32 of it for the other, so +; the stored words are 2^24 times 1 / 1023 or 32 / 1023, times 1, 2, 4, 8, 16. The matrix for the step is D = 0: a switch event +; moves the charge and nothing else. +pub const K_Q : [20]u32 = [ + 524801, 16400, 1049601, 32800, 2099202, 65600, 4198404, 131200, 8396808, 262400, + 16400, 524801, 32800, 1049601, 65600, 2099202, 131200, 4198404, 262400, 8396808 +]; +/// D of M1's step: zero, two states. +pub const ZD : [4]u32 = [0, 0, 0, 0]; + +; --- the ngspice-47 reference: ideal capacitors, the topology above, 100 fF a unit --------------------------------------- +; specs/analog/decks/sar_cdac_half.cir: one half sampled at 0.75 V, interval n (10 ns) puts the plates at code n; the voltage +; of Vout read at the end of every interval, n = 0 .. 1023, float64 values. +pub const NG_HALF : [1024]f64 = [ + -1.15255027743899e-14, 0.001466275659812518, 0.002932551319636546, 0.004398826979460579, + 0.005865102639284636, 0.007331378299108668, 0.008797653958932703, 0.01026392961875678, + 0.01173020527858084, 0.0131964809384049, 0.01466275659822901, 0.01612903225805298, + 0.01759530791787692, 0.0190615835777009, 0.02052785923752483, 0.02199413489734876, + 0.02346041055717273, 0.02492668621699667, 0.02639296187682064, 0.02785923753664456, + 0.02932551319646851, 0.03079178885629255, 0.03225806451611647, 0.03372434017594039, + 0.03519061583576433, 0.03665689149558828, 0.03812316715541218, 0.03958944281523606, + 0.04105571847505987, 0.04252199413488366, 0.04398826979470736, 0.04545454545453131, + 0.0469208211143553, 0.04838709677417949, 0.04985337243400363, 0.05131964809382795, + 0.05278592375365215, 0.05425219941347595, 0.0557184750732998, 0.05718475073312401, + 0.05865102639294787, 0.06011730205277202, 0.06158357771259618, 0.06304985337241979, + 0.06451612903224367, 0.06598240469206762, 0.0674486803518913, 0.068914956011715, + 0.07038123167153913, 0.0718475073313628, 0.0733137829911871, 0.07478005865101109, + 0.07624633431083501, 0.0777126099706587, 0.07917888563048277, 0.08064516129030716, + 0.08211143695013098, 0.08357771260995495, 0.08504398826977899, 0.08651026392960284, + 0.08797653958942653, 0.08944281524925025, 0.09090909090907424, 0.09237536656889836, + 0.09384164222872228, 0.0953079178885463, 0.09677419354837025, 0.09824046920819386, + 0.0997067448680175, 0.1011730205278414, 0.1026392961876651, 0.1041055718474888, + 0.1055718475073128, 0.1070381231671369, 0.1085043988269609, 0.1099706744867848, + 0.1114369501466085, 0.1129032258064329, 0.1143695014662568, 0.1158357771260805, + 0.1173020527859045, 0.1187683284457288, 0.1202346041055526, 0.1217008797653766, + 0.1231671554252005, 0.1246334310850247, 0.1260997067448486, 0.1275659824046725, + 0.1290322580644964, 0.1304985337243202, 0.1319648093841441, 0.1334310850439683, + 0.134897360703792, 0.1363636363636157, 0.1378299120234397, 0.1392961876832635, + 0.1407624633430873, 0.1422287390029111, 0.1436950146627348, 0.1451612903225588, + 0.1466275659823829, 0.148093841642207, 0.1495601173020307, 0.1510263929618545, + 0.1524926686216782, 0.1539589442815019, 0.1554252199413255, 0.1568914956011493, + 0.158357771260973, 0.1598240469207964, 0.1612903225806204, 0.1627565982404447, + 0.1642228739002686, 0.1656891495600928, 0.167155425219916, 0.1686217008797395, + 0.1700879765395636, 0.1715542521993874, 0.1730205278592108, 0.1744868035190344, + 0.1759530791788582, 0.1774193548386815, 0.1788856304985057, 0.1803519061583302, + 0.1818181818181541, 0.1832844574779781, 0.1847507331378015, 0.1862170087976253, + 0.1876832844574486, 0.1891495601172725, 0.190615835777096, 0.1920821114369206, + 0.1935483870967447, 0.1950146627565684, 0.1964809384163922, 0.1979472140762157, + 0.1994134897360395, 0.2008797653958634, 0.2023460410556869, 0.203812316715511, + 0.2052785923753349, 0.2067448680351593, 0.2082111436949829, 0.2096774193548067, + 0.2111436950146302, 0.212609970674454, 0.2140762463342775, 0.2155425219941011, + 0.2170087976539244, 0.218475073313748, 0.2199413489735711, 0.2214076246333948, + 0.2228739002932182, 0.2243401759530426, 0.2258064516128664, 0.2272727272726907, + 0.2287390029325146, 0.2302052785923393, 0.231671554252163, 0.2331378299119875, + 0.2346041055718112, 0.2360703812316348, 0.2375366568914584, 0.2390029325512819, + 0.2404692082111057, 0.2419354838709293, 0.2434017595307531, 0.2448680351905775, + 0.2463343108504012, 0.2478005865102247, 0.2492668621700478, 0.2507331378298712, + 0.2521994134896946, 0.2536656891495191, 0.2551319648093428, 0.2565982404691661, + 0.25806451612899, 0.2595307917888137, 0.2609970674486373, 0.2624633431084608, + 0.2639296187682846, 0.2653958944281091, 0.2668621700879324, 0.2683284457477556, + 0.2697947214075798, 0.2712609970674031, 0.2727272727272264, 0.2741935483870505, + 0.2756598240468741, 0.2771260997066979, 0.2785923753665215, 0.2800586510263449, + 0.2815249266861685, 0.2829912023459932, 0.2844574780058174, 0.2859237536656414, + 0.287390029325464, 0.2888563049852882, 0.2903225806451119, 0.2917888563049356, + 0.2932551319647587, 0.2947214076245822, 0.2961876832844053, 0.2976539589442287, + 0.2991202346040518, 0.3005865102638762, 0.3020527859236988, 0.3035190615835218, + 0.304985337243345, 0.3064516129031697, 0.3079178885629938, 0.3093841642228177, + 0.3108504398826422, 0.3123167155424663, 0.3137829912022899, 0.3152492668621124, + 0.3167155425219351, 0.3181818181817581, 0.3196480938415812, 0.3211143695014052, + 0.322580645161228, 0.3240469208210514, 0.3255131964808746, 0.3269794721406977, + 0.3284457478005209, 0.329912023460344, 0.3313782991201671, 0.3328445747799901, + 0.3343108504398132, 0.3357771260996372, 0.3372434017594608, 0.338709677419284, + 0.3401759530791073, 0.3416422287389304, 0.3431085043987538, 0.344574780058577, + 0.3460410557184006, 0.3475073313782244, 0.3489736070380474, 0.3504398826978701, + 0.351906158357693, 0.3533724340175159, 0.3548387096773407, 0.3563049853371654, + 0.3577712609969891, 0.359237536656813, 0.3607038123166379, 0.3621700879764611, + 0.363636363636285, 0.3651026392961086, 0.3665689149559321, 0.3680351906157547, + 0.3695014662755778, 0.370967741935401, 0.3724340175952242, 0.3739002932550475, + 0.3753665689148711, 0.3768328445746941, 0.3782991202345175, 0.3797653958943415, + 0.3812316715541643, 0.3826979472139883, 0.3841642228738115, 0.3856304985336346, + 0.3870967741934593, 0.3885630498532842, 0.3900293255131087, 0.3914956011729324, + 0.3929618768327562, 0.3944281524925791, 0.3958944281524021, 0.3973607038122264, + 0.3988269794720499, 0.4002932551318725, 0.4017595307916955, 0.4032258064515197, + 0.4046920821113427, 0.4061583577711657, 0.4076246334309883, 0.4090909090908118, + 0.4105571847506351, 0.4120234604104581, 0.4134897360702807, 0.4149560117301045, + 0.4164222873899281, 0.417888563049751, 0.4193548387095738, 0.4208211143693956, + 0.4222873900292186, 0.4237536656890424, 0.4252199413488657, 0.4266862170086892, + 0.4281524926685125, 0.4296187683283363, 0.4310850439881601, 0.4325513196479834, + 0.4340175953078063, 0.4354838709676301, 0.436950146627453, 0.4384164222872761, + 0.4398826979470999, 0.4413489736069242, 0.4428152492667472, 0.4442815249265701, + 0.4457478005863931, 0.4472140762462168, 0.4486803519060402, 0.4501466275658654, + 0.4516129032256903, 0.4530791788855127, 0.4545454545453346, 0.4560117302051571, + 0.4574780058649805, 0.458944281524804, 0.4604105571846283, 0.4618768328444516, + 0.463343108504276, 0.4648093841641004, 0.4662756598239239, 0.4677419354837475, + 0.4692082111435713, 0.4706744868033938, 0.4721407624632168, 0.4736070381230389, + 0.4750733137828611, 0.4765395894426852, 0.4780058651025081, 0.4794721407623314, + 0.4809384164221539, 0.4824046920819765, 0.483870967741798, 0.4853372434016201, + 0.4868035190614443, 0.4882697947212682, 0.4897360703810925, 0.491202346040915, + 0.4926686217007381, 0.4941348973605623, 0.4956011730203857, 0.4970674486802084, + 0.4985337243400318, 0.4999999999998554, 0.5014662756596792, 0.5029325513195013, + 0.5043988269793239, 0.5058651026391475, 0.5073313782989713, 0.5087976539587948, + 0.5102639296186187, 0.5117302052784419, 0.5131964809382648, 0.5146627565980878, + 0.5161290322579125, 0.5175953079177367, 0.5190615835775602, 0.5205278592373835, + 0.5219941348972068, 0.5234604105570299, 0.5249266862168527, 0.526392961876676, + 0.527859237536499, 0.5293255131963231, 0.5307917888561463, 0.5322580645159697, + 0.5337243401757928, 0.5351906158356164, 0.5366568914954397, 0.5381231671552629, + 0.5395894428150859, 0.5410557184749096, 0.5425219941347331, 0.5439882697945565, + 0.5454545454543798, 0.5469208211142027, 0.5483870967740254, 0.5498533724338496, + 0.5513196480936732, 0.5527859237534968, 0.5542521994133188, 0.5557184750731416, + 0.5571847507329646, 0.5586510263927877, 0.5601173020526112, 0.5615835777124341, + 0.5630498533722571, 0.5645161290320807, 0.5659824046919055, 0.5674486803517302, + 0.5689149560115535, 0.5703812316713759, 0.5718475073311988, 0.5733137829910233, + 0.5747800586508479, 0.5762463343106725, 0.5777126099704987, 0.5791788856303227, + 0.580645161290145, 0.5821114369499677, 0.5835777126097911, 0.5850439882696138, + 0.5865102639294363, 0.587976539589259, 0.5894428152490856, 0.5909090909089086, + 0.5923753665687304, 0.593841642228553, 0.5953079178883761, 0.596774193548201, + 0.5982404692080246, 0.5997067448678481, 0.6011730205276702, 0.6026392961874936, + 0.6041055718473183, 0.6055718475071438, 0.6070381231669665, 0.608504398826791, + 0.6099706744866147, 0.611436950146439, 0.6129032258062617, 0.6143695014660854, + 0.6158357771259091, 0.6173020527857319, 0.6187683284455546, 0.6202346041053782, + 0.6217008797652018, 0.6231671554250253, 0.6246334310848486, 0.626099706744672, + 0.6275659824044951, 0.6290322580643193, 0.6304985337241408, 0.6319648093839632, + 0.633431085043787, 0.6348973607036121, 0.6363636363634337, 0.6378299120232561, + 0.6392961876830798, 0.6407624633429035, 0.6422287390027261, 0.6436950146625492, + 0.6451612903223725, 0.6466275659821974, 0.6480938416420188, 0.649560117301841, + 0.651026392961664, 0.6524926686214869, 0.65395894428131, 0.655425219941134, + 0.6568914956009565, 0.6583577712607788, 0.659824046920602, 0.6612903225804253, + 0.6627565982402464, 0.6642228739000694, 0.665689149559892, 0.6671554252197159, + 0.6686217008795393, 0.6700879765393624, 0.6715542521991851, 0.6730205278590111, + 0.6744868035188345, 0.6759530791786589, 0.6774193548384821, 0.6788856304983052, + 0.6803519061581285, 0.6818181818179524, 0.683284457477775, 0.6847507331375969, + 0.6862170087974201, 0.6876832844572455, 0.6891495601170718, 0.6906158357768949, + 0.6920821114367178, 0.6935483870965408, 0.6950146627563658, 0.6964809384161886, + 0.6979472140760118, 0.6994134897358351, 0.7008797653956571, 0.7023460410554808, + 0.7038123167153039, 0.7052785923751291, 0.7067448680349526, 0.7082111436947762, + 0.7096774193545977, 0.71114369501442, 0.7126099706742441, 0.7140762463340672, + 0.7155425219938902, 0.7170087976537154, 0.7184750733135381, 0.7199413489733605, + 0.7214076246331825, 0.7228739002930066, 0.7243401759528291, 0.7258064516126519, + 0.7272727272724756, 0.7287390029322981, 0.7302052785921197, 0.7316715542519413, + 0.7331378299117639, 0.7346041055715884, 0.7360703812314123, 0.7375366568912365, + 0.7390029325510626, 0.740469208210886, 0.7419354838707086, 0.7434017595305313, + 0.7448680351903537, 0.7463343108501774, 0.7478005865100007, 0.749266862169823, + 0.7507331378296446, 0.7521994134894675, 0.7536656891492896, 0.7551319648091119, + 0.7565982404689336, 0.7580645161287558, 0.7595307917885783, 0.7609970674484003, + 0.7624633431082251, 0.7639296187680471, 0.7653958944278696, 0.7668621700876916, + 0.7683284457475142, 0.7697947214073364, 0.7712609970671586, 0.77272727272698, + 0.774193548386803, 0.7756598240466283, 0.777126099706451, 0.7785923753662732, + 0.7800586510260946, 0.7815249266859158, 0.7829912023457384, 0.78445747800556, + 0.7859237536653815, 0.7873900293252032, 0.7888563049850251, 0.7903225806448472, + 0.7917888563046699, 0.7932551319644924, 0.7947214076243142, 0.7961876832841362, + 0.7976539589439584, 0.7991202346037819, 0.8005865102636038, 0.8020527859234261, + 0.8035190615832483, 0.8049853372430699, 0.8064516129028918, 0.8079178885627141, + 0.8093841642225384, 0.8108504398823624, 0.8123167155421839, 0.8137829912020051, + 0.8152492668618271, 0.8167155425216492, 0.8181818181814706, 0.8196480938412921, + 0.8211143695011149, 0.8225806451609379, 0.8240469208207604, 0.8255131964805826, + 0.8269794721404047, 0.8284457478002268, 0.8299120234600481, 0.8313782991198706, + 0.8328445747796951, 0.8343108504395176, 0.8357771260993403, 0.8372434017591628, + 0.8387096774189857, 0.8401759530788073, 0.8416422287386292, 0.8431085043984514, + 0.8445747800582741, 0.8460410557180955, 0.8475073313779187, 0.8489736070377415, + 0.8504398826975647, 0.8519061583573869, 0.8533724340172089, 0.854838709677032, + 0.856304985336855, 0.8577712609966781, 0.8592375366565009, 0.8607038123163226, + 0.8621700879761445, 0.8636363636359662, 0.8651026392957882, 0.86656891495561, + 0.8680351906154324, 0.8695014662752546, 0.870967741935077, 0.8724340175948995, + 0.873900293254722, 0.8753665689145449, 0.8768328445743685, 0.8782991202341912, + 0.8797653958940147, 0.8812316715538373, 0.8826979472136592, 0.8841642228734814, + 0.8856304985333033, 0.8870967741931263, 0.8885630498529494, 0.8900293255127723, + 0.8914956011725943, 0.8929618768324183, 0.8944281524922423, 0.8958944281520644, + 0.897360703811886, 0.8988269794717075, 0.9002932551315296, 0.9017595307913526, + 0.9032258064511757, 0.9046920821109976, 0.9061583577708193, 0.9076246334306423, + 0.9090909090904645, 0.9105571847502864, 0.912023460410109, 0.9134897360699321, + 0.9149560117297553, 0.9164222873895785, 0.9178885630494004, 0.9193548387092221, + 0.9208211143690446, 0.9222873900288672, 0.9237536656886883, 0.9252199413485096, + 0.926686217008332, 0.9281524926681526, 0.9296187683279744, 0.9310850439877958, + 0.932551319647618, 0.9340175953074406, 0.9354838709672622, 0.9369501466270831, + 0.9384164222869072, 0.9398826979467304, 0.9413489736065532, 0.942815249266374, + 0.944281524926196, 0.9457478005860174, 0.9472140762458383, 0.9486803519056598, + 0.9501466275654838, 0.9516129032253067, 0.9530791788851272, 0.9545454545449485, + 0.9560117302047704, 0.9574780058645926, 0.9589442815244158, 0.9604105571842384, + 0.9618768328440626, 0.9633431085038859, 0.9648093841637079, 0.966275659823531, + 0.9677419354833517, 0.9692082111431736, 0.9706744868029948, 0.9721407624628162, + 0.9736070381226368, 0.975073313782459, 0.9765395894422804, 0.9780058651021012, + 0.9794721407619228, 0.9809384164217442, 0.9824046920815659, 0.9838709677413883, + 0.9853372434012119, 0.9868035190610347, 0.9882697947208562, 0.9897360703806787, + 0.9912023460405011, 0.9926686217003228, 0.994134897360144, 0.9956011730199644, + 0.9970674486797869, 0.9985337243396079, 0.9999999999994288, 1.001466275659251, + 1.002932551319071, 1.004398826978894, 1.005865102638715, 1.007331378298537, + 1.00879765395836, 1.010263929618184, 1.011730205278006, 1.013196480937827, + 1.014662756597649, 1.01612903225747, 1.017595307917293, 1.019061583577115, + 1.020527859236938, 1.021994134896762, 1.023460410556583, 1.024926686216405, + 1.026392961876226, 1.027859237536048, 1.029325513195869, 1.030791788855691, + 1.032258064515515, 1.03372434017534, 1.03519061583516, 1.036656891494983, + 1.038123167154805, 1.039589442814627, 1.041055718474449, 1.04252199413427, + 1.043988269794096, 1.045454545453917, 1.046920821113739, 1.04838709677356, + 1.049853372433381, 1.051319648093204, 1.052785923753026, 1.054252199412847, + 1.055718475072671, 1.057184750732496, 1.05865102639232, 1.060117302052141, + 1.061583577711963, 1.063049853371786, 1.064516129031607, 1.065982404691429, + 1.067448680351252, 1.068914956011075, 1.070381231670897, 1.07184750733072, + 1.073313782990542, 1.074780058650364, 1.076246334310187, 1.077712609970009, + 1.079178885629831, 1.080645161289654, 1.082111436949475, 1.083577712609297, + 1.085043988269118, 1.086510263928939, 1.087976539588761, 1.089442815248583, + 1.090909090908406, 1.09237536656823, 1.093841642228052, 1.095307917887875, + 1.096774193547697, 1.098240469207519, 1.09970674486734, 1.101173020527161, + 1.102639296186987, 1.104105571846814, 1.105571847506636, 1.107038123166458, + 1.108504398826279, 1.109970674486101, 1.111436950145923, 1.112903225805744, + 1.114369501465567, 1.115835777125389, 1.11730205278521, 1.118768328445033, + 1.120234604104855, 1.121700879764677, 1.123167155424499, 1.124633431084321, + 1.126099706744145, 1.127565982403971, 1.129032258063792, 1.130498533723613, + 1.131964809383434, 1.133431085043256, 1.134897360703077, 1.136363636362899, + 1.137829912022721, 1.139296187682542, 1.140762463342364, 1.142228739002186, + 1.143695014662008, 1.14516129032183, 1.146627565981651, 1.148093841641473, + 1.149560117301293, 1.151026392961116, 1.152492668620938, 1.15395894428076, + 1.155425219940581, 1.156891495600403, 1.158357771260225, 1.159824046920047, + 1.161290322579869, 1.162756598239693, 1.164222873899514, 1.165689149559335, + 1.167155425219157, 1.16862170087898, 1.170087976538802, 1.171554252198623, + 1.173020527858444, 1.174486803518266, 1.175953079178087, 1.17741935483791, + 1.178885630497731, 1.180351906157552, 1.181818181817374, 1.183284457477195, + 1.184750733137016, 1.186217008796838, 1.187683284456659, 1.189149560116481, + 1.190615835776303, 1.192082111436123, 1.193548387095944, 1.195014662755764, + 1.196480938415584, 1.197947214075404, 1.199413489735225, 1.200879765395045, + 1.202346041054866, 1.203812316714687, 1.205278592374509, 1.20674486803433, + 1.208211143694152, 1.209677419353973, 1.211143695013795, 1.212609970673616, + 1.214076246333436, 1.215542521993257, 1.217008797653079, 1.218475073312901, + 1.219941348972723, 1.221407624632546, 1.222873900292368, 1.224340175952189, + 1.22580645161201, 1.227272727271831, 1.228739002931653, 1.230205278591475, + 1.231671554251297, 1.23313782991112, 1.234604105570942, 1.236070381230764, + 1.237536656890585, 1.239002932550406, 1.240469208210228, 1.24193548387005, + 1.243401759529871, 1.244868035189695, 1.246334310849517, 1.247800586509339, + 1.24926686216916, 1.250733137828982, 1.252199413488804, 1.253665689148626, + 1.255131964808448, 1.256598240468271, 1.258064516128092, 1.259530791787914, + 1.260997067447736, 1.262463343107557, 1.263929618767379, 1.265395894427201, + 1.266862170087023, 1.268328445746845, 1.269794721406667, 1.271260997066488, + 1.27272727272631, 1.274193548386131, 1.275659824045951, 1.277126099705772, + 1.278592375365593, 1.280058651025415, 1.281524926685236, 1.282991202345058, + 1.28445747800488, 1.285923753664701, 1.287390029324523, 1.288856304984345, + 1.290322580644166, 1.291788856303992, 1.293255131963814, 1.294721407623636, + 1.296187683283457, 1.297653958943279, 1.2991202346031, 1.300586510262921, + 1.302052785922743, 1.303519061582563, 1.304985337242385, 1.306451612902207, + 1.307917888562027, 1.309384164221849, 1.31085043988167, 1.312316715541491, + 1.313782991201315, 1.315249266861137, 1.316715542520958, 1.318181818180779, + 1.319648093840599, 1.321114369500419, 1.32258064516024, 1.32404692082006, + 1.32551319647988, 1.326979472139704, 1.328445747799524, 1.329912023459345, + 1.331378299119166, 1.332844574778988, 1.334310850438809, 1.33577712609863, + 1.33724340175845, 1.33870967741827, 1.340175953078092, 1.341642228737913, + 1.343108504397734, 1.344574780057555, 1.346041055717376, 1.347507331377197, + 1.348973607037023, 1.350439882696844, 1.351906158356665, 1.353372434016487, + 1.354838709676311, 1.356304985336136, 1.357771260995957, 1.359237536655782, + 1.360703812315606, 1.362170087975433, 1.363636363635255, 1.365102639295079, + 1.366568914954901, 1.368035190614724, 1.369501466274545, 1.370967741934368, + 1.372434017594194, 1.373900293254021, 1.375366568913844, 1.376832844573669, + 1.378299120233491, 1.379765395893312, 1.381231671553133, 1.382697947212956, + 1.38416422287278, 1.385630498532603, 1.387096774192423, 1.388563049852247, + 1.390029325512068, 1.39149560117189, 1.392961876831712, 1.394428152491537, + 1.395894428151362, 1.397360703811184, 1.39882697947101, 1.400293255130835, + 1.40175953079066, 1.403225806450483, 1.404692082110303, 1.406158357770128, + 1.407624633429952, 1.409090909089778, 1.4105571847496, 1.412023460409422, + 1.413489736069242, 1.414956011729064, 1.416422287388885, 1.417888563048706, + 1.419354838708532, 1.420821114368358, 1.422287390028179, 1.423753665687999, + 1.42521994134782, 1.426686217007641, 1.428152492667463, 1.429618768327285, + 1.431085043987105, 1.432551319646927, 1.434017595306748, 1.435483870966569, + 1.43695014662639, 1.438416422286211, 1.439882697946031, 1.441348973605852, + 1.442815249265673, 1.444281524925495, 1.445747800585316, 1.447214076245137, + 1.448680351904958, 1.450146627564779, 1.4516129032246, 1.453079178884421, + 1.454545454544244, 1.456011730204066, 1.457478005863887, 1.458944281523708, + 1.460410557183529, 1.46187683284335, 1.46334310850317, 1.464809384162991, + 1.466275659822813, 1.467741935482639, 1.469208211142461, 1.470674486802282, + 1.472140762462103, 1.473607038121923, 1.475073313781744, 1.476539589441565, + 1.478005865101386, 1.479472140761206, 1.480938416421026, 1.482404692080848, + 1.48387096774067, 1.48533724340049, 1.486803519060311, 1.48826979472013, + 1.489736070379952, 1.491202346039774, 1.492668621699594, 1.494134897359416, + 1.495601173019237, 1.497067448679062, 1.498533724338885, 1.499999999998708 +]; +; specs/analog/decks/sar_cdac_diff.cir: both halves, P sampled at 0.875 V with code n, N sampled at 0.625 V with code 1023 - n. +pub const NG_SPOT : [16]u32 = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 511, 513, 341, 682, 1023]; +pub const NG_DP : [16]f64 = [ + 0.124999999999986, 0.1264662756598099, 0.1279325513196334, 0.1308651026392824, + 0.1367302052785786, 0.1484604105571704, 0.1719208211143477, 0.2188416422287064, + 0.3126832844574255, 0.5003665689148518, 0.8757331378296106, 0.8742668621697842, + 0.877199413489435, 0.6249999999998301, 1.124999999999512, 1.624999999998603 +]; +pub const NG_DN : [16]f64 = [ + 1.374999999999988, 1.373533724340162, 1.372067448680335, 1.369134897360693, + 1.363269794721399, 1.351539589442803, 1.328079178885578, 1.281158357771151, + 1.187316715542322, 0.9996334310846838, 0.6242668621694816, 0.6257331378293038, + 0.6228005865096573, 0.8749999999995404, 0.3749999999992978, -0.125000000000772 +]; + +; --- the hardware: unsigned words only ---------------------------------------------------------------------------- +; Everything from bit_of to convert_k takes and returns bool and unsigned words and has no float. The step is M1's, called +; with a zero D matrix and the charge-transfer ratios of one plate as N, so a switch event is x' = x + rnd(N u) per node. + +/// Bit j of a code (j = 0 is Bit_1, the LSB). +pub fn bit_of(code: u32, j: usize) -> bool { + return ((code >> (j as u32)) & 1) == 1; +} + +/// A plate leaves VCM for VDD when its bit is set and for 0 V when it is not: +VCM or -VCM. +pub fn plate_step(bit: bool) -> u32 { + if (bit) { return VCM; } + return NEG | VCM; +} + +/// One switch event: plate j moves by u (a word), the two nodes move by M1's step with D = 0 and N = the plate's ratios in k. +pub fn event_k(k: []const u32, j: usize, u: u32, x: []const u32, out: []u32) { + const nv : [2]u32 = [k[2 * j], k[2 * j + 1]]; + step(2, &ZD, &nv, x, u, out); +} + +pub struct CdacNodes { l: u32, m: u32 } + +/// The two node voltages after one code: from the sampled state, ten events, Bit_10 first (the SAR order), ratios k. +pub fn convert_k(k: []const u32, code: u32, vs: u32) -> CdacNodes { + var x : [2]u32 = [vs, vs]; + var y : [2]u32 = [0, 0]; + var j : usize = PLATES; + while (j > 0) { + j = j - 1; + event_k(k, j, plate_step(bit_of(code, j)), &x, &y); + x[0] = y[0]; + x[1] = y[1]; + } + return CdacNodes { l: x[0], m: x[1] }; +} + +/// The deck's DAC: convert_k with the stored ratios. +pub fn convert(code: u32, vs: u32) -> CdacNodes { + return convert_k(&K_Q, code, vs); +} + +; --- the f64 side: references that only the tests reach -------------------------------------------------------------- + +/// The units on one side of the bridge. +pub fn side_sum(units: []const u32, side: []const u8, which: u8) -> f64 { + var s : f64 = 0.0; + var j : usize = 0; + while (j < PLATES) { + if (side[j] == which) { s = s + (units[j] as f64); } + j = j + 1; + } + return s; +} + +/// The ratios by the matrix route: C = [[CL + Cb, -Cb], [-Cb, CM + Cb]] (dummy units to ground join CL), C_b the column of +/// the plate's units on its own node, K = C^-1 C_b by the adjugate. Unit capacitance cancels. Layout as K_Q. +pub fn derive_k(units: []const u32, side: []const u8, bridge: f64, dummy: f64) -> [20]f64 { + var k : [20]f64 = [0.0] ** 20; + const cl : f64 = side_sum(units, side, SIDE_L) + dummy; + const cm : f64 = side_sum(units, side, SIDE_M); + const c00 : f64 = cl + bridge; + const c11 : f64 = cm + bridge; + const c01 : f64 = 0.0 - bridge; + const det : f64 = c00 * c11 - c01 * c01; + var j : usize = 0; + while (j < PLATES) { + const u : f64 = units[j] as f64; + var b0 : f64 = 0.0; + var b1 : f64 = 0.0; + if (side[j] == SIDE_L) { b0 = u; } else { b1 = u; } + k[2 * j] = (c11 * b0 - c01 * b1) / det; + k[2 * j + 1] = (c00 * b1 - c01 * b0) / det; + j = j + 1; + } + return k; +} + +/// The same ratios by the closed form of the split array: with D = CL CM + Cb (CL + CM), a plate of u units on net1 moves +/// net1 by u (CM + Cb) / D and Vout by u Cb / D; a plate on Vout moves net1 by u Cb / D and Vout by u (CL + Cb) / D. +pub fn closed_k(units: []const u32, side: []const u8, bridge: f64, dummy: f64) -> [20]f64 { + var k : [20]f64 = [0.0] ** 20; + const cl : f64 = side_sum(units, side, SIDE_L) + dummy; + const cm : f64 = side_sum(units, side, SIDE_M); + const d : f64 = cl * cm + bridge * (cl + cm); + var j : usize = 0; + while (j < PLATES) { + const u : f64 = units[j] as f64; + if (side[j] == SIDE_L) { + k[2 * j] = u * (cm + bridge) / d; + k[2 * j + 1] = u * bridge / d; + } else { + k[2 * j] = u * bridge / d; + k[2 * j + 1] = u * (cl + bridge) / d; + } + j = j + 1; + } + return k; +} + +/// The bridge that makes the split array exactly binary: CL / (2^5 - 1) for a 5-bit LSB array of CL units. +pub fn ideal_bridge(cl: f64) -> f64 { + return cl / 31.0; +} + +/// Volts at one node (0 = net1, 1 = Vout) after a code, from the sampled vs: vs + sum_j K[2j + node] (+-VCM). +pub fn node_volts(k: []const f64, node: usize, code: u32, vs: f64) -> f64 { + var v : f64 = vs; + var j : usize = 0; + while (j < PLATES) { + if (bit_of(code, j)) { v = v + k[2 * j + node] * VCM_V; } else { v = v - k[2 * j + node] * VCM_V; } + j = j + 1; + } + return v; +} + +/// The integral nonlinearity of the transfer about the sample: the distance of Vout from the straight line through code 0 and code +/// 1023, in LSB of that line (its span / 1023). It is the part of the error that bends the staircase. It is zero exactly when every +/// weight is 2^(j - 1) times the first. +pub fn inl_lsb(k: []const f64, code: u32) -> f64 { + const v0 : f64 = node_volts(k, 1, 0, 0.0); + const lsb : f64 = (node_volts(k, 1, 1023, 0.0) - v0) / 1023.0; + return (node_volts(k, 1, code, 0.0) - v0 - lsb * (code as f64)) / lsb; +} + +/// The deviation from the nominal 10-bit DAC, V = VCM (2 c - 1023) / 1024 about the sampled voltage, in LSB = VDD / 1024. +/// It is the gain error: a straight line through code 511.5. +pub fn gain_lsb(k: []const f64, code: u32) -> f64 { + const nominal : f64 = VCM_V * (((2 * code) as f64) - 1023.0) / 1024.0; + return (node_volts(k, 1, code, 0.0) - nominal) * 1024.0 / VDD_V; +} + +/// A new maximum must beat the old one by more than this, so the first code of a tie keeps the place (f64 noise is below it). +pub const TOL : f64 = 0.000000001; + +/// The same for volts, where the differences are 1e-12. +pub const TOL_V : f64 = 0.0000000000000001; + +pub struct CdacWorst { err: f64, at: u32 } + +/// The largest |inl_lsb| over the 1024 codes and the first code that reaches it. +pub fn worst_inl(k: []const f64) -> CdacWorst { + var w : CdacWorst = CdacWorst { err: 0.0, at: 0 }; + var c : u32 = 0; + while (c < CODES) { + const e : f64 = fabs64(inl_lsb(k, c)); + if (e > w.err + TOL) { w = CdacWorst { err: e, at: c }; } + c = c + 1; + } + return w; +} + +/// The largest |gain_lsb| over the 1024 codes and the first code that reaches it. +pub fn worst_gain(k: []const f64) -> CdacWorst { + var w : CdacWorst = CdacWorst { err: 0.0, at: 0 }; + var c : u32 = 0; + while (c < CODES) { + const e : f64 = fabs64(gain_lsb(k, c)); + if (e > w.err + TOL) { w = CdacWorst { err: e, at: c }; } + c = c + 1; + } + return w; +} + +/// The largest |fixed point - f64| over the 1024 codes, in units of 2^-24 V, for one node (0 = net1, 1 = Vout): the arithmetic +/// error alone. kq are the words, kf the real ratios. +pub fn worst_arith(kq: []const u32, kf: []const f64, node: usize) -> CdacWorst { + var w : CdacWorst = CdacWorst { err: 0.0, at: 0 }; + var c : u32 = 0; + while (c < CODES) { + const n : CdacNodes = convert_k(kq, c, VS_HALF); + var word : u32 = n.m; + if (node == 0) { word = n.l; } + const e : f64 = fabs64(sm_f64(word) - node_volts(kf, node, c, VCM_V)) * SCALE; + if (e > w.err + TOL) { w = CdacWorst { err: e, at: c }; } + c = c + 1; + } + return w; +} + +/// The largest |ngspice - f64 closed form| of Vout over the 1024 codes, in volts. +pub fn worst_ng_vs_f64(kf: []const f64) -> CdacWorst { + var w : CdacWorst = CdacWorst { err: 0.0, at: 0 }; + var c : u32 = 0; + while (c < CODES) { + const e : f64 = fabs64(NG_HALF[c as usize] - node_volts(kf, 1, c, VCM_V)); + if (e > w.err + TOL_V) { w = CdacWorst { err: e, at: c }; } + c = c + 1; + } + return w; +} + +/// The largest |fixed point - ngspice| of Vout over the 1024 codes, in units of 2^-24 V. +pub fn worst_fixed_vs_ng(kq: []const u32) -> CdacWorst { + var w : CdacWorst = CdacWorst { err: 0.0, at: 0 }; + var c : u32 = 0; + while (c < CODES) { + const e : f64 = fabs64(sm_f64(convert_k(kq, c, VS_HALF).m) - NG_HALF[c as usize]) * SCALE; + if (e > w.err + TOL) { w = CdacWorst { err: e, at: c }; } + c = c + 1; + } + return w; +} + +/// An order-sensitive fold of both node words of every code, as M1 folds its states. +pub fn sweep_fold() -> u64 { + var s : u64 = 0; + var c : u32 = 0; + while (c < CODES) { + const n : CdacNodes = convert(c, VS_HALF); + s = fold(s, n.m); + s = fold(s, n.l); + c = c + 1; + } + return s; +} + +/// How many stored words differ from round(real * 2^24). +pub fn mismatches(kq: []const u32, kf: []const f64) -> usize { + var bad : usize = 0; + var i : usize = 0; + while (i < 20) { + if (q_sm(kf[i]) != kq[i]) { bad = bad + 1; } + i = i + 1; + } + return bad; +} + +/// Is every real ratio at least 1e-4 LSB away from a rounding tie? +pub fn ties_clear(kf: []const f64) -> bool { + var ok : bool = true; + var i : usize = 0; + while (i < 20) { + if (tie_gap(kf[i]) <= 0.0001) { ok = false; } + i = i + 1; + } + return ok; +} + +; --- the tests ------------------------------------------------------------------------------------------------------ + + +test the_inventory_is_the_decks_63_units_in_two_arrays_of_31_and_one_bridge_unit { + var j : usize = 0; + var total : u32 = 0; + var lsb : u32 = 0; + var msb : u32 = 0; + while (j < PLATES) { + assert_eq(LINE_LAST[j] - LINE_FIRST[j] + 1, UNITS[j]); + total = total + UNITS[j]; + if (SIDE[j] == SIDE_L) { lsb = lsb + UNITS[j]; } else { msb = msb + UNITS[j]; } + j = j + 1; + } + assert_eq(lsb, 31); + assert_eq(msb, 31); + assert_eq(total + BRIDGE_UNITS, TOTAL_UNITS); + // the lines run on without a gap inside each array, and the bridge is the line between the arrays + j = 1; + while (j < 5) { + assert_eq(LINE_FIRST[j], LINE_LAST[j - 1] + 1); + assert_eq(UNITS[j], 2 * UNITS[j - 1]); + j = j + 1; + } + assert_eq(BRIDGE_LINE, LINE_LAST[4] + 1); + assert_eq(LINE_FIRST[5], BRIDGE_LINE + 1); + j = 6; + while (j < 10) { + assert_eq(LINE_FIRST[j], LINE_LAST[j - 1] + 1); + assert_eq(UNITS[j], 2 * UNITS[j - 1]); + j = j + 1; + } + assert_eq(UNITS[0], 1); + assert_eq(UNITS[5], 1); + assert_eq(SIDE[4], SIDE_L); + assert_eq(SIDE[5], SIDE_M); + assert_eq(LINE_LAST[9] - LINE_FIRST[0] + 1, 63); + assert_eq(CODES, 1024); + assert_eq(LSB * CODES, VDD); + assert_eq(VCM * 2, VDD); +} + +test the_matrix_route_and_the_closed_form_agree_and_the_deck_array_is_exactly_binary { + const km : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + const kc : [20]f64 = closed_k(&UNITS, &SIDE, 1.0, 0.0); + var worst : f64 = 0.0; + var i : usize = 0; + while (i < 20) { + if (fabs64(km[i] - kc[i]) > worst) { worst = fabs64(km[i] - kc[i]); } + i = i + 1; + } + assert worst < 0.000000000000001; + // D = CL CM + Cb (CL + CM) = 31 * 31 + 62 = 1023; every Vout ratio is 2^(j - 1) / 1023, in both arrays + var j : usize = 0; + var sum_m : f64 = 0.0; + var sum_l : f64 = 0.0; + const one : u32 = 1; + while (j < PLATES) { + const pw : f64 = (one << (j as u32)) as f64; + assert fabs64(km[2 * j + 1] * 1023.0 - pw) < 0.000000000001; + sum_m = sum_m + km[2 * j + 1]; + sum_l = sum_l + km[2 * j]; + j = j + 1; + } + // net1 moves 32 times as far as Vout for a plate of its own array and 1 / 32 as far for a plate of the other + assert fabs64(km[0] - 32.0 / 1023.0) < 0.000000000000001; + assert fabs64(km[1] - 1.0 / 1023.0) < 0.000000000000001; + assert fabs64(km[10] - 1.0 / 1023.0) < 0.000000000000001; + assert fabs64(km[11] - 32.0 / 1023.0) < 0.000000000000001; + // full scale: all ten plates together move Vout and net1 by exactly one plate swing + assert fabs64(sum_m - 1.0) < 0.000000000000001; + assert fabs64(sum_l - 1.0) < 0.000000000000001; + // the capacitance the top plate sees: (CM + Cb) - Cb^2 / (CL + Cb) = 1023 / 32 units, not 32 + const ctop : f64 = 32.0 - 1.0 / 32.0; + assert fabs64(ctop - 1023.0 / 32.0) < 0.000000000000001; + assert fabs64(1.0 / ctop - 32.0 / 1023.0) < 0.000000000000001; + // the ideal bridge is CL / 31: 1 unit for the deck's 31-unit array, 32/31 for an array that has a 32nd (dummy) unit + assert fabs64(ideal_bridge(31.0) - 1.0) < 0.000000000000001; + assert fabs64(ideal_bridge(32.0) - 32.0 / 31.0) < 0.000000000000001; +} + +test the_stored_words_are_the_rounding_of_the_derived_ratios { + const kf : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + assert_eq(mismatches(&K_Q, &kf), 0); + assert(ties_clear(&kf)); + // the closest to a tie is 32 / 1023 * 2^24 = 524800.5005, which rounds up to 524801 + assert tie_gap(kf[0]) > 0.0004; + assert tie_gap(kf[0]) < 0.0005; + assert_eq(K_Q[0], 524801); + // the words of each node add to exactly one plate swing (2^24) + var i : usize = 0; + var sl : u32 = 0; + var sm : u32 = 0; + while (i < PLATES) { + sl = sl + K_Q[2 * i]; + sm = sm + K_Q[2 * i + 1]; + i = i + 1; + } + assert_eq(sl, 16777216); + assert_eq(sm, 16777216); +} + +test one_switch_event_is_m1s_step_with_a_zero_matrix { + const x : [2]u32 = [VS_HALF, VS_HALF]; + var y : [2]u32 = [0, 0]; + // Bit_10 up: K * 0.75 is exact (K is a multiple of 8), net1 + 196800 and Vout + 6297606 + event_k(&K_Q, 9, VCM, &x, &y); + assert_eq(y[0], VS_HALF + 196800); + assert_eq(y[1], VS_HALF + 6297606); + event_k(&K_Q, 9, NEG | VCM, &x, &y); + assert_eq(y[0], VS_HALF - 196800); + assert_eq(y[1], VS_HALF - 6297606); + // a tie: Bit_3's net1 ratio 2099202 * 3 / 4 = 1574401.5 rounds away from zero, up and down + event_k(&K_Q, 2, VCM, &x, &y); + assert_eq(y[0], VS_HALF + 1574402); + event_k(&K_Q, 2, NEG | VCM, &x, &y); + assert_eq(y[0], VS_HALF - 1574402); + // 524801 * 3 / 4 = 393600.75 rounds to 393601 + event_k(&K_Q, 0, VCM, &x, &y); + assert_eq(y[0], VS_HALF + 393601); + // a plate that does not move moves nothing + event_k(&K_Q, 5, 0, &x, &y); + assert_eq(y[0], VS_HALF); + assert_eq(y[1], VS_HALF); + // from a node at 0 V a downward event gives a negative word + const z : [2]u32 = [0, 0]; + event_k(&K_Q, 9, NEG | VCM, &z, &y); + assert_eq(y[1], NEG | 6297606); + // the same numbers through M1's own step, called by name with D = 0 and Bit_10's ratios as N + const nv : [2]u32 = [262400, 8396808]; + var w : [2]u32 = [0, 0]; + step(2, &ZD, &nv, &x, VCM, &w); + assert_eq(w[0], VS_HALF + 196800); + assert_eq(w[1], VS_HALF + 6297606); + assert_eq(plate_step(true), VCM); + assert_eq(plate_step(false), NEG | VCM); + assert(bit_of(5, 0)); + assert(!bit_of(5, 1)); + assert(bit_of(5, 2)); +} + +test a_code_is_ten_events_whose_order_does_not_matter_and_whose_complement_mirrors_it { + var c : u32 = 0; + while (c < CODES) { + const a : CdacNodes = convert(c, VS_HALF); + // Bit_1 first instead of Bit_10 first: the same state, because each move is rounded alone + var x : [2]u32 = [VS_HALF, VS_HALF]; + var y : [2]u32 = [0, 0]; + var j : usize = 0; + while (j < PLATES) { + event_k(&K_Q, j, plate_step(bit_of(c, j)), &x, &y); + x[0] = y[0]; + x[1] = y[1]; + j = j + 1; + } + assert_eq(x[0], a.l); + assert_eq(x[1], a.m); + // the complement code moves every plate the other way by the same rounded amount: mirror image about the sample + const b : CdacNodes = convert(1023 - c, VS_HALF); + assert fabs64(sm_f64(a.m) + sm_f64(b.m) - 2.0 * VCM_V) < 0.000000000000001; + assert fabs64(sm_f64(a.l) + sm_f64(b.l) - 2.0 * VCM_V) < 0.000000000000001; + c = c + 1; + } + // the ends of the staircase: code 0 is one unit below 0 V (a negative word) and code 1023 one unit above 1.5 V, on both nodes: + // ten rounded moves of 0.75 V that add up to a hair over the 0.75 V the sample can give + const lo : CdacNodes = convert(0, VS_HALF); + const hi : CdacNodes = convert(1023, VS_HALF); + assert_eq(lo.m, NEG | 1); + assert_eq(lo.l, NEG | 1); + assert_eq(hi.m, VDD + 1); + assert_eq(hi.l, VDD + 1); +} + +test all_1024_codes_match_the_closed_form_within_the_arithmetic_error { + const kf : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + // the closed form of the whole transfer: Vout = sample + VDD c / 1023 - VCM, by no matrix at all + var c : u32 = 0; + while (c < CODES) { + const ex : f64 = VDD_V * (c as f64) / 1023.0; + assert fabs64(node_volts(&kf, 1, c, VCM_V) - ex) < 0.00000000000001; + c = c + 1; + } + // the arithmetic error alone, fixed point against the f64 sum of the same ten events, in units of 2^-24 V + const a1 : CdacWorst = worst_arith(&K_Q, &kf, 1); + const a0 : CdacWorst = worst_arith(&K_Q, &kf, 0); + assert a1.err < 1.8; + assert a1.err > 1.7; + assert_eq(a1.at, 224); + assert a0.err < 1.8; + assert a0.err > 1.7; + assert_eq(a0.at, 1016); + // that is under one hundred-thousandth of the 10-bit half's LSB (24576 units) + assert a1.err / LSB_F < 0.0001; + assert a0.err / LSB_F < 0.0001; +} + +test the_bridge_error_is_a_gain_term_of_half_an_lsb_and_the_staircase_is_straight { + const kf : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + // shape: every weight is 2^(j - 1) times the first, so the staircase is straight + const sh : CdacWorst = worst_inl(&kf); + assert sh.err < 0.000000001; + // gain: against the nominal 10-bit half (VDD / 1024 a code) the deck's Vout moves VDD c / 1023: (2 c - 1023) / 2046 LSB + const ga : CdacWorst = worst_gain(&kf); + assert ga.err < 0.50001; + assert ga.err > 0.49999; + assert_eq(ga.at, 0); + assert gain_lsb(&kf, 0) < -0.49999; + assert gain_lsb(&kf, 1023) > 0.49999; + var c : u32 = 0; + while (c < CODES) { + const ex : f64 = (((2 * c) as f64) - 1023.0) / 2046.0; + assert fabs64(gain_lsb(&kf, c) - ex) < 0.000000000001; + c = c + 1; + } + // the gain error crosses zero in the middle of the staircase: codes 511 and 512 straddle it + assert gain_lsb(&kf, 511) < 0.0; + assert gain_lsb(&kf, 512) > 0.0; + // the fixed-point converter shows the same line: its deviation from the nominal half, in LSB, is within 2e-4 of (2 c - 1023) / 2046 + c = 0; + var worst : f64 = 0.0; + while (c < CODES) { + const m : CdacNodes = convert(c, VS_HALF); + const nominal : f64 = VCM_V + VCM_V * (((2 * c) as f64) - 1023.0) / 1024.0; + const dev : f64 = (sm_f64(m.m) - nominal) * 1024.0 / VDD_V; + const e : f64 = fabs64(dev - (((2 * c) as f64) - 1023.0) / 2046.0); + if (e > worst) { worst = e; } + c = c + 1; + } + assert worst < 0.0002; + // full scale: the top plate spans exactly VDD from code 0 to code 1023, a nominal 10-bit half spans 1023 / 1024 of it + const span : f64 = node_volts(&kf, 1, 1023, 0.0) - node_volts(&kf, 1, 0, 0.0); + assert fabs64(span - VDD_V) < 0.00000000000001; +} + +test ngspice_with_ideal_capacitors_agrees_for_all_1024_codes { + const kf : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + // ngspice against the f64 closed form: its own drift, 1.3e-12 V over the 10 us run, the largest at the last code + const g : CdacWorst = worst_ng_vs_f64(&kf); + assert g.err < 0.0000000000014; + assert g.err > 0.0000000000012; + assert_eq(g.at, 1023); + // ngspice against the fixed-point converter, in units of 2^-24 V + const f : CdacWorst = worst_fixed_vs_ng(&K_Q); + assert f.err < 1.8; + assert f.err > 1.7; + assert_eq(f.at, 224); + assert f.err / LSB_F < 0.0001; + // and every one of the 1024 codes on its own, so a single bad code cannot hide behind the maximum + var c : u32 = 0; + while (c < CODES) { + assert fabs64(NG_HALF[c as usize] - node_volts(&kf, 1, c, VCM_V)) < 0.0000000000014; + assert fabs64(sm_f64(convert(c, VS_HALF).m) - NG_HALF[c as usize]) * SCALE < 1.8; + c = c + 1; + } + // the table is the deck's: code 0 is 0 V, code 1023 is 1.5 V, and the single-bit codes are 1.5 V * 2^k / 1023 + assert fabs64(NG_HALF[0]) < 0.000000000001; + assert fabs64(NG_HALF[1023] - VDD_V) < 0.000000000002; + var k : u32 = 0; + const one : u32 = 1; + while (k < 10) { + const code : u32 = one << k; + assert fabs64(NG_HALF[code as usize] - VDD_V * (code as f64) / 1023.0) < 0.000000000002; + k = k + 1; + } +} + +test the_differential_pair_spot_codes_agree_with_ngspice_and_the_closed_form { + var i : usize = 0; + while (i < 16) { + const c : u32 = NG_SPOT[i]; + const p : CdacNodes = convert(c, VS_P); + const n : CdacNodes = convert(1023 - c, VS_N); + // each half against ngspice's float64 value, in units of 2^-24 V + assert fabs64(sm_f64(p.m) - NG_DP[i]) * SCALE < 2.0; + assert fabs64(sm_f64(n.m) - NG_DN[i]) * SCALE < 2.0; + // the differential output, closed form: (sample P - sample N) + VDD (2 c - 1023) / 1023 + const ex : f64 = (VSP_V - VSN_V) + VDD_V * (((2 * c) as f64) - 1023.0) / 1023.0; + assert fabs64((sm_f64(p.m) - sm_f64(n.m)) - ex) * SCALE < 4.0; + assert fabs64((NG_DP[i] - NG_DN[i]) - ex) < 0.000000000003; + // the N half is the P half mirrored: its move is the P move reversed, exactly, whatever the sample + assert fabs64(sm_f64(p.m) + sm_f64(n.m) - (VSP_V + VSN_V)) < 0.000000000000001; + i = i + 1; + } + // the spot list holds the ends, the single-bit codes, the middle and its two neighbours + assert_eq(NG_SPOT[0], 0); + assert_eq(NG_SPOT[15], 1023); + assert_eq(NG_SPOT[9], 256); + assert_eq(NG_SPOT[10], 512); +} + +test the_fold_of_both_nodes_for_every_code_is_pinned { + assert_eq(sweep_fold(), 0x7EB25FED224C52E8); +} + +test one_capacitor_count_changed_breaks_the_stored_constants_and_the_ngspice_agreement { + // Bit_8 with 3 units instead of 4: every stored word changes, the staircase bends by 28.9 LSB at code 128, ngspice is 42 mV off + var bad : [10]u32 = UNITS; + bad[7] = 3; + const kb : [20]f64 = derive_k(&bad, &SIDE, 1.0, 0.0); + assert_eq(mismatches(&K_Q, &kb), 20); + const sh : CdacWorst = worst_inl(&kb); + assert sh.err > 28.8; + assert sh.err < 29.0; + assert_eq(sh.at, 128); + const g : CdacWorst = worst_ng_vs_f64(&kb); + assert g.err > 0.042; + assert g.err < 0.043; + // the closed form follows the change too (the checker is not blind to it) + const kc : [20]f64 = closed_k(&bad, &SIDE, 1.0, 0.0); + assert fabs64(kb[15] - kc[15]) < 0.000000000000001; + // a plate hung on the wrong array: Bit_6 on net1 + var ws : [10]u8 = SIDE; + ws[5] = SIDE_L; + const kw : [20]f64 = derive_k(&UNITS, &ws, 1.0, 0.0); + assert(mismatches(&K_Q, &kw) > 0); + // a bridge of two units + const k2 : [20]f64 = derive_k(&UNITS, &SIDE, 2.0, 0.0); + assert(mismatches(&K_Q, &k2) > 0); + // and one unit fewer on the top plate's biggest plate + var small : [10]u32 = UNITS; + small[9] = 15; + const k15 : [20]f64 = derive_k(&small, &SIDE, 1.0, 0.0); + assert(mismatches(&K_Q, &k15) > 0); +} + +test the_textbook_bridge_is_ideal_only_with_a_dummy_unit_and_the_deck_has_none { + // the 32/31 unit bridge on the deck's 31-unit net1 array bends the staircase by 0.94 LSB (first at code 31, the first carry + // of the MSB array); ngspice, which ran the deck's 1-unit bridge, is 1.37 mV from it + const kt : [20]f64 = derive_k(&UNITS, &SIDE, 32.0 / 31.0, 0.0); + const st : CdacWorst = worst_inl(&kt); + assert st.err > 0.93; + assert st.err < 0.94; + assert_eq(st.at, 31); + const gt : CdacWorst = worst_ng_vs_f64(&kt); + assert gt.err > 0.0013; + assert gt.err < 0.0015; + assert_eq(gt.at, 31); + // with the 32nd unit (a dummy to ground on net1) the same 32/31 bridge is exactly ideal: no bend, no gain error + const kd : [20]f64 = derive_k(&UNITS, &SIDE, 32.0 / 31.0, 1.0); + const sd : CdacWorst = worst_inl(&kd); + const gd : CdacWorst = worst_gain(&kd); + assert sd.err < 0.000000001; + assert gd.err < 0.000000001; + // and ngspice is 0.73 mV from that ideal DAC, which the deck does not build + const ng_d : CdacWorst = worst_ng_vs_f64(&kd); + assert ng_d.err > 0.0007; + assert ng_d.err < 0.00075; + // the deck's 1-unit bridge on the array with the dummy is not ideal: 0.91 LSB of bend and 0.93 LSB of gain error + const kw : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 1.0); + const sw : CdacWorst = worst_inl(&kw); + const gw : CdacWorst = worst_gain(&kw); + assert sw.err > 0.91; + assert sw.err < 0.92; + assert gw.err > 0.92; + assert gw.err < 0.93; + // the deck itself: no bend, half an LSB of gain error at the ends + const kk : [20]f64 = derive_k(&UNITS, &SIDE, 1.0, 0.0); + assert worst_inl(&kk).err < 0.000000001; + assert worst_gain(&kk).err > 0.49999; +}