From 8990b81aadde442fa8157aef60076dca6971edd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 02:00:57 +0700 Subject: [PATCH 1/2] analog: VACASK decks for the SAR comparator (M4b-psp) and the capacitive DAC (M4a) Two netlists in VACASK's own syntax, run on the remote host next to the ngspice decks they repeat (sar_cmp_psp_real.cir, sar_cdac_half.cir), with the same PSP103 OSDI file as ngspice: the golden subcircuits are translated at run time and never vendored. Data only; the comparison spec follows. Updates #10911 Co-Authored-By: Claude Sonnet 5.5 --- specs/analog/decks/vacask_cdac_half.sim | 49 +++++++++++++++++ specs/analog/decks/vacask_cmp_real.sim | 71 +++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 specs/analog/decks/vacask_cdac_half.sim create mode 100644 specs/analog/decks/vacask_cmp_real.sim diff --git a/specs/analog/decks/vacask_cdac_half.sim b/specs/analog/decks/vacask_cdac_half.sim new file mode 100644 index 0000000000..9b1fc37c5a --- /dev/null +++ b/specs/analog/decks/vacask_cdac_half.sim @@ -0,0 +1,49 @@ +SAR ADC split capacitive DAC, one half, ideal capacitors, 16 codes: the VACASK twin of sar_cdac_half.cir +// specs/analog/decks/vacask_cdac_half.sim -- second-simulator data for specs/analog/vacask_compare.t27 (analog M4a, #10911, epic #10486). A netlist in VACASK's +// own syntax plus a control block; the numbers are read from its raw file (see "What is read"). +// Topology of Diff_cap_DAC as in sar_cdac_half.cir (the ngspice deck of this repository, which this deck repeats): the LSB array, 31 units from node l to the +// plates p1..p5 (1, 2, 4, 8, 16 units), the bridge, 1 unit between l and m (m is Vout, the top plate), and the MSB array, 31 units from m to the plates p6..p10 +// (1, 2, 4, 8, 16 units). Unit = 100 fF, ideal. VACASK's capacitor device has one value per instance: a plate of u units is ONE capacitor of u * 100 fF (the +// ngspice deck has u of 100 fF; parallel capacitors are one capacitor of the sum). No golden-deck text is used: the topology is the repository's own deck's. +// The tracking switch is not modelled: l and m start at the sampled 0.75 V (icmode "uic" and the ic list; every capacitor starts uncharged because every plate +// rests at VCM = 0.75 V: the ic list names the ten plate nodes too, at 0.75 V, because in uic mode a node that is not listed starts at 0 V, also a node a +// source drives, so a capacitor between l and an unlisted plate would start charged to 0.75 V and shift every result by 0.75 V; the ngspice deck's IC=0 on +// each capacitor says the same thing). The plates rest at 0.75 V for 10 ns; then, over 1 ps, plate j goes to VDD = 1.5 V when bit j-1 of the circuit variable code is 1 and to +// 0 V when it is 0 (the ngspice deck's 0.75 + 0.75 (2 bit - 1)): the code is `code`. The network is algebraic (capacitors only), so the settled voltage does not +// depend on the time grid. Sweep "cs" (analysis cdac): the 16 codes of the spec's NG_SPOT list. Integration: trapezoidal (VACASK's default), the step capped at 1 ns, +// stop at 30 ns. +// What is read (outside this file, from cdac.raw): v(m) and v(l) at 29 ns, interpolated linearly between the two nearest points. Run: vacask -sp -qp +// vacask_cdac_half.sim (cwd holds nothing else; only the OSDI file capacitor.osdi of VACASK's own device library is loaded). + +load "capacitor.osdi" +model v vsource +model cap capacitor + +cl1 (l p1) cap c=100f +cl2 (l p2) cap c=200f +cl3 (l p3) cap c=400f +cl4 (l p4) cap c=800f +cl5 (l p5) cap c=1600f +cbr (l m) cap c=100f +cm6 (m p6) cap c=100f +cm7 (m p7) cap c=200f +cm8 (m p8) cap c=400f +cm9 (m p9) cap c=800f +cm10 (m p10) cap c=1600f +vp1 (p1 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/1), 2)-1)] +vp2 (p2 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/2), 2)-1)] +vp3 (p3 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/4), 2)-1)] +vp4 (p4 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/8), 2)-1)] +vp5 (p5 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/16), 2)-1)] +vp6 (p6 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/32), 2)-1)] +vp7 (p7 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/64), 2)-1)] +vp8 (p8 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/128), 2)-1)] +vp9 (p9 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/256), 2)-1)] +vp10 (p10 0) v type="pwl" wave=[0, 0.75, 10n, 0.75, 10.001n, 0.75+0.75*(2*fmod(floor(code*1.0/512), 2)-1)] + +control + var code=0 + save v(m) v(l) + sweep cs variable="code" values=[0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 511, 513, 341, 682, 1023] continuation=0 + analysis cdac tran step=1n stop=30n maxstep=1n icmode="uic" ic={"l", 0.75, "m", 0.75, "p1", 0.75, "p2", 0.75, "p3", 0.75, "p4", 0.75, "p5", 0.75, "p6", 0.75, "p7", 0.75, "p8", 0.75, "p9", 0.75, "p10", 0.75} +endc diff --git a/specs/analog/decks/vacask_cmp_real.sim b/specs/analog/decks/vacask_cmp_real.sim new file mode 100644 index 0000000000..553d77efac --- /dev/null +++ b/specs/analog/decks/vacask_cmp_real.sim @@ -0,0 +1,71 @@ +SAR ADC dynamic comparator on PSP103, real device charges, 50 C: the VACASK twin of sar_cmp_psp_real.cir +// specs/analog/decks/vacask_cmp_real.sim -- second-simulator data for specs/analog/vacask_compare.t27 (analog, #10911, epic #10486). +// A netlist in VACASK's own syntax plus a control block; the numbers are read from its raw files (see "What is read"). +// Circuits, side by side in one netlist, as in sar_cmp_psp_real.cir (the ngspice deck this one repeats, element for element): +// xb the subcircuit dynamic_comparator with 4 fF on dp and dn (bdp, bdn): the latch alone; +// xtop the subcircuit dynamic_comparator_top: the same latch, 4 fF on each output, an inverter on each, 4 fF on each inverter output +// and the NAND of the two inverter outputs, "Valid". The converter's comparator with the loads it has there. +// The subcircuits are the golden SAR deck's (uberWAVE wp2-parser/golden/top.spice, GPL-2.0). They are fetched at run time and NEVER vendored: +// the file vacask_golden.inc is made from the very subcircuit text that SAR_GOLDEN_SUBCKTS names for the ngspice decks, by one awk program that +// changes only the syntax and stops with an error line on any line it does not know: +// .subckt NAME p1 p2 .. -> subckt NAME (p1 p2 ..) .ends -> ends +// XMk d g s b MASTER params.. -> XMk (d g s b) MASTER params.. (MASTER = sg13_lv_nmos or sg13_lv_pmos; w, l, ng, m unchanged) +// xk n1 n2 .. SUBCKT -> xk (n1 n2 ..) SUBCKT +// Ck n1 n2 VALUE m=1 -> Ck (n1 n2) cap c=VALUE +// * comment lines are dropped. Only dynamic_comparator, dynamic_comparator_top, inverter and nand are taken. +// PSP103 (IHP-Open-PDK 21133f219b1a6da10d803e345761e77415ceff40, cornerMOSlv mos_tt) through OSDI, 50 C, with ALL its charge: the gate, overlap +// and junction capacitance of every device. Nothing is scaled; an ideal 1.6 ps clock stands for the delay-cell clkc; the inputs are held. +// THE MODELS are the PDK's own, converted to VACASK's syntax by VACASK's converter (python3 -m vacask.sg13g2tovc from VACASK's source tree, +// PDK_ROOT a COPY of the checkout because the converter writes into it): sg13g2_vacask_common.lib and cornerMOSlv.lib in libs.tech/vacask/models of +// the copy, and the instance wrappers sg13_lv_nmos and sg13_lv_pmos of the PDK's own sg13g2_moslv_mod.lib, with its formulas for as, ad, ps, pd. +// THE OSDI FILE is psp103.osdi, the one the ngspice decks load: the ngspice image's /opt/osdi directory is first on module_path_prefix in a +// .vacaskrc.toml next to the deck, so both simulators run the SAME compiled model file. (The converter compiles its own copy, which differs in bytes.) +// Run: PDK_ROOT= PDK=ihp-sg13g2 vacask -sp vacask_cmp_real.sim (cwd holds vacask_golden.inc and the .vacaskrc.toml) +// INPUTS: VDD 1.5 V, common mode 0.75 V. The swept variable sd is the signed half input in units of 2^-24 V: VDP = 0.75 + sd h, VDN = 0.75 - sd h, +// so VDP - VDN = 2 sd units. Write sd and the 16777216 as REALS: an integer sd divides to 0. +// sweep hs (analysis run1) sd = +-16777, +-4194, +-1049, +-262, +-84, +-8, +-1: the ngspice deck's first list. +// sweep ds (analysis scan) VDP - VDN = d units for d = -2700, -2600, -2571, -2570, -2540, -2400 (sd = d / 2): its second list. +// sweep fs (analysis fine) d = -2598, -2594, -2590, -2586, -2582, -2578, -2574, -2572, around VACASK's own flip point of xtop. +// The clock is 0 until 200 ps, then rises over 1.6 ps (its 50 % point at 200.8 ps). +// THE START: icmode "uic" and the spec's rest (ap = an = net2 = net3 = n_clkc = 1.5 V, dp = dn = 0, net1 = 0.4009999632835388 V) and the +// inverters' and NAND's outputs at their logic values; nodes not named start at 0 and settle in the 200 ps before the clock (the ngspice deck's +// .ic with uic). Both simulators leave this start the same way: vdd, vdp, vdn and clkc are not in the list either and start at 0 V, the state is +// not a solution, and the outputs jump from 0 to about 1.2 V in the first 0.05 ps, then fall back through 0.75 V at about 150 ps, before the clock. +// Their pre-clock waveforms agree within 0.4 mV from 1 ps on and within 0.15 mV from 30 ps on (specs/analog/vacask_compare.t27). +// INTEGRATION: trapezoidal (VACASK's default, tran_method), the step capped at 0.5 ps, reltol 1e-8, vntol 1e-11, abstol 1e-13 as in the ngspice deck. +// WHAT IS READ (outside this file, from run1.raw, scan.raw and fine.raw): tck, the clock through 0.75 V; tfirst, the first of dp and dn of xb +// through 0.75 V rising at or after tck (the early rise of the start is not a decision; the ngspice deck's meas does not see it either); tres, +// |dp - dn| of xb through 0.75 V; tvalid, Valid through 0.75 V rising after tck; xb's dp and dn and xtop's dp and dn at 3 ns. Crossings are linear +// interpolations between consecutive output points, as ngspice's meas does; times are given after tck. + +include "sg13g2_vacask_common.lib" +include "cornerMOSlv.lib" section=mos_tt +include "vacask_golden.inc" + +load "capacitor.osdi" +model v vsource +model cap capacitor + +vdd (vdd 0) v dc=1.5 +vdp (vdp 0) v dc=0.75+sd/16777216.0 +vdn (vdn 0) v dc=0.75-sd/16777216.0 +vclk (clkc 0) v type="pwl" wave=[0, 0, 200p, 0, 201.6p, 1.5] +xb (vdp vdn clkc vdd 0 bdp bdn) dynamic_comparator +cbdp (bdp 0) cap c=4f +cbdn (bdn 0) cap c=4f +xtop (vocp vocn valid vdp vdn clkc vdd 0) dynamic_comparator_top + +control + var sd=0 + options temp=50 reltol=1e-8 vntol=1e-11 abstol=1e-13 + save v(clkc) v(bdp) v(bdn) v(valid) v('xtop:dp') v('xtop:dn') + var ic0={"xb:ap", 1.5, "xb:an", 1.5, "xb:net1", 0.4009999632835388, "xb:net2", 1.5, "xb:net3", 1.5, "bdp", 0, "bdn", 0, "xb:n_clkc", 1.5, + "xtop:x1:ap", 1.5, "xtop:x1:an", 1.5, "xtop:x1:net1", 0.4009999632835388, "xtop:x1:net2", 1.5, "xtop:x1:net3", 1.5, "xtop:dp", 0, "xtop:dn", 0, + "xtop:x1:n_clkc", 1.5, "vocp", 1.5, "vocn", 1.5, "valid", 0} + sweep hs variable="sd" values=[16777, -16777, 4194, -4194, 1049, -1049, 262, -262, 84, -84, 8, -8, 1, -1] continuation=0 + analysis run1 tran step=0.1p stop=3n maxstep=0.5p icmode="uic" ic=ic0 + sweep ds variable="sd" values=[-1350, -1300, -1285.5, -1285, -1270, -1200] continuation=0 + analysis scan tran step=0.1p stop=3n maxstep=0.5p icmode="uic" ic=ic0 + sweep fs variable="sd" values=[-1299.0, -1297.0, -1295.0, -1293.0, -1291.0, -1289.0, -1287.0, -1286.0] continuation=0 + analysis fine tran step=0.1p stop=3n maxstep=0.5p icmode="uic" ic=ic0 +endc From 47d5cecb5f6acde4f16f7d69c6a56d1806348acc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 02:06:48 +0700 Subject: [PATCH 2/2] analog: vacask_compare -- VACASK next to ngspice for the SAR comparator and the capacitive DAC A comparison spec pinning what VACASK 0.3.4.dev3 and ngspice-47 measured on the same circuits with the same compiled PSP103 file (decks in specs/analog/decks/vacask_*.sim): the golden comparator's decision time agrees to 0.125 ps (0.024 %), its resolution time to 0.065 ps, all 28 winners, the top's flip point to 46 units of 2^-24 V, the DAC at 16 codes to 1.4e-12 V; our models are the 98 and 155 times too fast that sar_comparator_psp.t27 says, now against two simulators. A stated tolerance checked from both sides and six negative controls (a changed integrator, a wrong start, moved pins, exchanged inputs). t27c test-report: 7 tests, 0 FAIL, 0 vacuous. Closes #10911 Co-Authored-By: Claude Sonnet 5.5 --- specs/analog/vacask_compare.t27 | 517 ++++++++++++++++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100644 specs/analog/vacask_compare.t27 diff --git a/specs/analog/vacask_compare.t27 b/specs/analog/vacask_compare.t27 new file mode 100644 index 0000000000..336fd48b7b --- /dev/null +++ b/specs/analog/vacask_compare.t27 @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/vacask_compare.t27 -- VACASK as a second reference simulator for the SAR blocks, next to ngspice: the comparator (M4b-psp) and the capacitive DAC (M4a) +; Analog, #10911 (epic #10486; the comparator is #10856, the DAC is #10540). An external EDA team suggested VACASK (an OSDI circuit simulator) as an independent +; validator. This file pins what both simulators measured on the SAME circuits with the SAME compiled PSP103 file, tabulates them against our models, and holds the +; tolerance at which they agree. A disagreement between our model and ngspice can now be attributed: to the simulator when VACASK sides with the model, to the model +; when VACASK sides with ngspice. It does not change the block specs; it `use`s them for the model column. +; - +; THE SECOND SIMULATOR. VACASK 0.3.4.dev3 (the PyPI wheel vacask-bin, built 2026-09-17, manylinux x86_64, pip-installed into a job directory inside a container on the +; remote host; the metadata says 0.3.4.dev3, the banner "This is vacask unknown."). The container image the external team's host carries has 0.3.2-103, a debug build that +; rejects the main-branch demo gilbert.sim at line 42 (a syntax error). 0.3.4.dev3 parses it and runs its op, dc-sweep, transient (0.43 s) and harmonic-balance (21.6 s) +; analyses, then stops in hbac1 with "Parameter 'truncharm' not found" and "Function 'sort' not found": the demo (fetched at run time, never stored here) is newer than the +; wheel (main was at f61a1637 on 2026-10-19). The 0.3.4 release tarball (the other candidate) does not start in that image: libopenblas.so.0 is missing and nothing may be +; installed there; the wheel carries its libraries. The comparator and the DAC need only transients, which this version runs. The wheel's copy of VACASK's IHP converter lacks +; one module (ng2vclib/m_inst_m.py), so the conversion of the PDK ran from the python package of a main-branch clone (python3 -m vacask.sg13g2tovc, the PDK COPIED first, +; because the converter writes into it, compiled with the wheel's own openvaf-r). The conversion took 12 s, no hand edit. +; - +; THE SAME MODEL FILE. The ngspice image's psp103.osdi (802248 bytes) is put first on VACASK's module path, so both simulators load that very file; the converter's own +; compile of the same Verilog-A source differs in bytes (826848) and was not used. PDK revision, corner (mos_tt), temperature (50 C) and the instance wrappers +; sg13_lv_nmos and sg13_lv_pmos are the PDK's own in both (the converter turns ngspice's .subckt into VACASK's subckt, same formulas, same parameters). +; - +; THE DECKS, data in specs/analog/decks/ (the golden SAR deck's subcircuits are GPL-2.0 and are translated at run time by an awk program described in the deck, never stored): +; vacask_cmp_real.sim the twin of sar_cmp_psp_real.cir: xb = the bare golden comparator with 4 fF on dp and dn, xtop = dynamic_comparator_top, an ideal 1.6 ps clock, 14 inputs +; (+-2 mV down to +-0.12 uV), the 6-point scan around xtop's flip point, and 8 finer points around VACASK's own +; vacask_cdac_half.sim the twin of sar_cdac_half.cir: one half of the split DAC, ideal capacitors, the 16 spot codes of sar_cdac.t27 (NG_SPOT) +; ngspice-47 ran the repository's own decks sar_cmp_psp_real.cir and sar_cdac_half.cir unchanged (the DAC deck with a 15-digit print) on the same host, the same day, +; 2026-10-20, in the image t27-ngspice:47-osdi (4 threads at most); its comparator numbers reproduce the committed pins of sar_comparator_psp.t27 digit for digit. +; - +; THE RESULT. Comparator, VDP - VDN = +2 mV (h = 16777), times after the clock's 50 % point (200.8 ps), 50 C, all PSP103 charge: +; quantity ngspice-47 VACASK our model VACASK - ngspice model against the simulators +; decision (first output, 0.75 V) 523.032 ps 522.910 ps 5.350 ps -0.122 ps (-0.023 %) 97.7 times too fast (no charge in the model) +; resolution (|dp - dn|, 0.75 V) 1111.910 ps 1111.845 ps 7.186 ps -0.065 ps (-0.006 %) 154.7 times too fast +; Valid of xtop (0.75 V) 897.700 ps 897.567 ps not modelled -0.133 ps (-0.015 %) +; and over all 14 inputs (7 magnitudes, both signs): decision within 0.125 ps, resolution within 0.065 ps, Valid within 0.138 ps of ngspice; the winner at 3 ns the same in +; all 14 runs for xb and for xtop. The flip points (VDP - VDN at which dp and dn trade places): +; xb, the bare latch ngspice: inside +-2 units (+-0.12 uV) VACASK: inside +-2 units model: +0.12 uV (sp_flip, rounding) all three at zero to 0.12 uV +; xtop, with its loads ngspice: -2570 .. -2540 units VACASK: -2586 .. -2582 model: none (the loads are not modelled) VACASK is 12 to 46 units +; (-0.15318 .. -0.15139 mV) (-0.15414 .. -0.15390 mV) (0.7 to 2.7 uV, 0.5 to 1.8 %) lower +; The pre-clock waveform of xb's dp (a start that is not a solution, the same in both): at 0.05 ps 1.2197 V against 1.2509 V (31 mV apart: the first step), then within 0.34 mV +; from 1 ps and within 0.11 mV from 30 ps on (0.856297 V against 0.856334 V at 100 ps, 0.750878 V against 0.750903 V at 150 ps). +; Capacitive DAC, the top plate Vout, 16 codes, volts (ngspice's is the host run; ideal capacitors, V = 1.5 c / 1023): +; code 1 ngspice 1.466275659813511e-3 VACASK 1.46627565982399e-3 closed form 1.466275659824047e-3 model within 1.74 units of 2^-24 V (1.04e-7 V) of it +; code 341 ngspice 0.4999999999998767 VACASK 0.5 closed form 0.5 +; code 1023 ngspice 1.499999999998824 VACASK 1.5 closed form 1.5 (code 0: ngspice -1.05e-14, VACASK 0, model -1 unit = -5.96e-8 V) +; VACASK sits within 2e-15 V of the closed form at all 16 codes; ngspice's long run drifts up to 1.18e-12 V (at code 1023), the 1.29e-12 V drift sar_cdac.t27 saw. The VACASK +; runs are 30 ns per code, ngspice's one 10 us run for all 1024 codes: the two are not the same experiment, so the drift is NOT attributed to ngspice, only not seen. +; - +; THE TOLERANCE, stated and kept apart from the difference it bounds. Decision and Valid time 0.15 ps, resolution time 0.08 ps (the observed maxima are 0.125, 0.138 +; and 0.065, and each bound is checked from both sides: the data are not copies); the winners equal; the flip point of xtop within 46 units (2.7 uV, 1.8 % of the offset) and +; of the bare latch within 2 units; the pre-clock waveform within 0.4 mV from 1 ps; the DAC within 1.4e-12 V of ngspice, 2e-15 V of the closed form, and 1.2e-7 V of the model. +; THE DIFFERENCE EXPLAINED. The two flip points of xtop are 12 to 46 units apart. ngspice's own flip point moved by 10 units between reltol 1e-6 and 1e-8 (sar_comparator_psp.t27, +; "the tolerance is part of the number"); the two simulators control the local truncation error differently, and the gap is of that size, 1 % of the 0.153 mV offset. The decision +; and resolution times agree to 0.02 % and 0.006 %. The sign and the size of the offset, the thing the model cannot predict, are the circuit's: both simulators put the top's +; flip point 0.15 mV on dn's side. +; - +; WHAT THE SECOND SIMULATOR SETTLES. (1) The model's decision time is 97.7 times and its resolution time 154.7 times faster than the real-charge comparator, and VACASK says +; the same as ngspice to 0.03 %: the error is in the model (the devices' own charge is not there; the charge tables are #10777), not in ngspice. (2) The top's flip point +; of about -0.153 mV is the circuit's, not an ngspice artefact, and the model (an isolated latch) cannot have it. (3) The DAC's closed form is what an independent simulator +; gets too. +; - +; THE NEGATIVE CONTROLS, tests that pass by seeing the failure: +; VACASK with tran_xmu = 0.25 (a more Euler-like integrator) resolution time 0.65 to 1.87 ps early at all 7 inputs: at least 8 times the 0.08 ps bound +; the plates left out of the ic list (uic starts an unlisted node at 0 V) every DAC code 0.75 V high (the plate-to-top-plate capacitors start charged): 5e11 times the bound +; a decision time moved by 1 ps; a flip point claimed equal to ngspice's bracket; the inputs exchanged (all 14 winners wrong) each refused +; - +; WHAT A PASS SAYS. VACASK 0.3.4.dev3 and ngspice-47, on the same compiled PSP103 file, agree on the golden comparator's decision time to 0.125 ps (0.024 %), its resolution +; time to 0.065 ps (0.006 %), the Valid time of the top to 0.138 ps, every winner of 14 + 14 runs, the flip point of the bare latch to 0.12 uV and the top's to 2.7 uV, the pre-clock +; waveform to 0.34 mV, and the DAC to 1.2e-12 V at 16 codes; our models are the 98 and 155 times too fast and the 1.74-unit fixed-point DAC that sar_comparator_psp.t27 and +; sar_cdac.t27 say, against two simulators now. +; WHAT IT DOES NOT SAY. Anything about silicon: both are simulators of the same PSP103 file; a bug in that file or in the PDK parameters is in both. Another corner, temperature, +; W, L, supply or Monte Carlo mismatch. The DAC with real capacitors, the switches or the clock edge. The first 0.05 ps of the comparator start, where the two differ by 31 mV +; (a uic start from a state that is not a solution; the decision does not depend on it, the times agree). VACASK's own flip point of xtop was bracketed by 8 points, 4 units +; wide, not refined. A speed comparison: no wall time is claimed (VACASK took 72 s for its 28 runs, ngspice 311 s for its 20, the same host, 4 threads at most: different +; tolerance handling, step control and output, so not comparable). Harmonic balance, noise, or any analysis but the transient. The golden deck is read at run time and is not +; in this repository. +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogVacaskCompare; + +use analog::linear::{fabs64, sm_f64}; +use analog::mos_psp103_50c_data::{TT50_N, TT50_P}; +use analog::sar_comparator::{SC_DV_M, SC_K}; +use analog::sar_comparator_psp::{sp_run, sp_first, sp_res, sp_flip, SP_H, NG_RL_FIRST_PS, NG_RL_RES_PS, NG_RL_VALID_PS_P, NG_RL_VALID_PS_M, NG_RL_BARE_WIN_P, NG_RL_BARE_WIN_M, NG_RL_TOP_WIN_P, NG_RL_TOP_WIN_M, NG_RL_SCAN_TOP_WIN, NG_RL_SCAN_BARE_WIN, NG_RL_TCK_PS}; +use analog::sar_cdac::{convert, CdacNodes, VS_HALF, NG_HALF, NG_SPOT}; + +pub const KIND : str = "analog-cell-check"; +pub const ID : str = "analog/vacask_compare"; +pub const REFERENCE : str = "VACASK 0.3.4.dev3 (vacask-bin wheel) and ngspice-47 on the same PSP103 OSDI file; IHP-Open-PDK 21133f21 mos_tt, 50 C; the golden SAR deck (uberWAVE wp2-parser/golden/top.spice, GPL-2.0) read at run time and not vendored"; + +; --- the stated tolerances --------------------------------------------------------------------------------- +/// Decision time (first output through 0.75 V) and Valid time: VACASK against ngspice, ps. Observed maxima 0.1250 and 0.1375. +pub const TOL_FIRST_PS : f64 = 0.15; +/// Resolution time (|dp - dn| through 0.75 V), ps. Observed maximum 0.0653. +pub const TOL_RES_PS : f64 = 0.08; +/// The pre-clock waveform of xb's dp from 1 ps on, volts. Observed maximum 0.344 mV. +pub const TOL_PRE_V : f64 = 0.0004; +/// The DAC, VACASK against ngspice (the long run's drift), volts. Observed maximum 1.18e-12. +pub const TOL_DAC_NG_V : f64 = 0.0000000000014; +/// The DAC, VACASK against the closed form 1.5 c / 1023, volts. +pub const TOL_DAC_CF_V : f64 = 0.000000000000002; +/// The DAC, the fixed-point model against VACASK, volts (1.74 units of 2^-24 V is 1.04e-7). +pub const TOL_DAC_MODEL_V : f64 = 0.00000012; +/// The flip point of xtop, VACASK against ngspice, at most this many units of 2^-24 V apart (46 = 2.7 uV, from the two brackets). +pub const TOL_FLIP_UNITS : u32 = 46; +/// One unit of 2^-24 V in volts. +pub const UNIT_V : f64 = 0.0000000596046448; + +; --- the comparator: VACASK on the same deck (vacask_cmp_real.sim), 2026-10-20, ps after the clock's 50 % point ------------- +; The seven magnitudes are SP_H: h = 16777, 4194, 1049, 262, 84, 8, 1 (VDP - VDN = +-2h units of 2^-24 V). +/// VACASK: first output of xb through 0.75 V, VDP - VDN = +2h. +pub const VK_FIRST_PS_P : [7]f64 = [522.9102, 527.9542, 529.2613, 529.6864, 529.7967, 529.8407, 529.7350]; +/// The same, VDP - VDN = -2h. +pub const VK_FIRST_PS_M : [7]f64 = [522.9378, 527.8860, 529.3662, 529.6769, 529.6977, 529.8045, 529.7355]; +/// VACASK: |dp - dn| of xb through 0.75 V, +2h. +pub const VK_RES_PS_P : [7]f64 = [1111.8447, 1319.2983, 1526.7626, 1734.5592, 1904.9464, 2257.1358, 2568.5385]; +/// The same, -2h. +pub const VK_RES_PS_M : [7]f64 = [1111.8593, 1319.2636, 1526.8152, 1734.5543, 1904.8965, 2257.1180, 2568.5385]; +/// VACASK: Valid of xtop through 0.75 V, +2h. +pub const VK_VALID_PS_P : [7]f64 = [897.5670, 922.4904, 927.2539, 928.3460, 928.5942, 928.6954, 928.5925]; +/// The same, -2h. +pub const VK_VALID_PS_M : [7]f64 = [920.5484, 932.0610, 929.8724, 928.9647, 928.6940, 928.6775, 928.5949]; +/// VACASK: xb's dp and dn at 3 ns, +2h. +pub const VK_BDP_P : [7]f64 = [1.49984059, 1.49978363, 1.49964599, 1.49925605, 1.49837799, 1.48891231, 1.42120527]; +pub const VK_BDN_P : [7]f64 = [0.000741135896, 0.000951879802, 0.00129252825, 0.00190086366, 0.002842571, 0.0103997799, 0.0840895348]; +/// The same, -2h. +pub const VK_BDP_M : [7]f64 = [0.000741139179, 0.000951798397, 0.00129255635, 0.00190084428, 0.00284219383, 0.0103988352, 0.0840894953]; +pub const VK_BDN_M : [7]f64 = [1.49984059, 1.49978365, 1.49964594, 1.49925607, 1.49837839, 1.48891347, 1.4212053]; +/// VACASK: xtop's dp and dn at 3 ns, +2h. +pub const VK_XDP_P : [7]f64 = [1.49983785, 1.49978787, 1.49972094, 1.49967122, 1.4996533, 1.49964436, 1.49964358]; +pub const VK_XDN_P : [7]f64 = [0.000750670283, 0.00093448629, 0.0011203639, 0.00123378064, 0.00127121805, 0.00128927043, 0.00129082424]; +/// The same, -2h: dn wins for the two largest, dp for the five smaller (the top's flip point is at -2570 .. -2586 units). +pub const VK_XDP_M : [7]f64 = [0.000770937301, 0.00106809134, 1.49908097, 1.49960346, 1.49963234, 1.49964239, 1.49964333]; +pub const VK_XDN_M : [7]f64 = [1.49983302, 1.49974157, 0.0021063717, 0.00136842588, 0.00131323776, 0.00129320841, 0.00129132581]; +/// The scan, VDP - VDN = -2700, -2600, -2571, -2570, -2540, -2400 units (SCAN_D magnitudes below): xb (bare) at 3 ns, VACASK. +pub const SCAN_D : [6]u32 = [2700, 2600, 2571, 2570, 2540, 2400]; +pub const VK_SCAN_BDP : [6]f64 = [0.00121621688, 0.001226965, 0.00123032858, 0.00123040619, 0.0012339489, 0.0012508078]; +pub const VK_SCAN_BDN : [6]f64 = [1.49968198, 1.49967704, 1.49967553, 1.49967549, 1.49967387, 1.49966603]; +/// xtop at 3 ns over the same scan, VACASK. +pub const VK_SCAN_XDP : [6]f64 = [0.003775053, 0.0150945121, 1.48396623, 1.48519593, 1.49416856, 1.49815821]; +pub const VK_SCAN_XDN : [6]f64 = [1.49731821, 1.48364094, 0.0148569469, 0.0137651134, 0.00631170698, 0.00304138646]; +/// VACASK's finer scan around its own flip point of xtop, VDP - VDN = -FINE_D units. +pub const FINE_D : [8]u32 = [2598, 2594, 2590, 2586, 2582, 2578, 2574, 2572]; +pub const VK_FINE_XDP : [8]f64 = [0.0162561739, 0.0256447924, 0.0536440117, 0.27007936, 1.44267951, 1.46834847, 1.47965714, 1.48323505]; +pub const VK_FINE_XDN : [8]f64 = [1.48235043, 1.47256948, 1.44746308, 1.28965161, 0.0598955305, 0.0301678265, 0.018821465, 0.0155145795]; +/// ngspice (the same host run, 2026-10-20): xtop at 3 ns over the scan. +pub const NG_SCAN_XDP : [6]f64 = [0.00356195, 0.00827421, 0.257699, 0.7464, 1.49166, 1.49803]; +pub const NG_SCAN_XDN : [6]f64 = [1.49757, 1.49169, 1.29889, 0.837048, 0.0083183, 0.00316033]; +/// The pre-clock waveform of xb's dp, +2 mV, at 12 times (ps): ngspice (the host run of the same deck with a short control block) and VACASK. +pub const PRE_T_PS : [12]f64 = [1.0, 5.0, 10.0, 30.0, 50.0, 100.0, 150.0, 190.0, 199.0, 200.0, 220.0, 250.0]; +pub const NG_PRE_V : [12]f64 = [0.982627, 0.947065, 0.928321, 0.897843, 0.896599, 0.856297, 0.750878, 0.639498, 0.615343, 0.612733, 0.582903, 0.493434]; +pub const VK_PRE_V : [12]f64 = [0.982971, 0.947227, 0.928465, 0.89794, 0.896667, 0.856334, 0.750903, 0.639519, 0.615363, 0.612752, 0.582794, 0.493412]; +/// The first step, at 0.05 ps: ngspice and VACASK. +pub const NG_PRE_005_V : f64 = 1.21967; +pub const VK_PRE_005_V : f64 = 1.25093; +/// Negative control: VACASK with tran_xmu = 0.25, the resolution time at +2h (the first extraction; the deck otherwise as above). +pub const VK_XMU_RES_PS_P : [7]f64 = [1111.2592, 1318.5304, 1525.8577, 1733.4621, 1903.6770, 2255.5742, 2566.7307]; + +; --- the DAC: the 16 spot codes NG_SPOT (vacask_cdac_half.sim, sar_cdac_half.cir), volts at the end of the interval --------- +/// VACASK, 30 ns per code, v(m) at 29 ns. +pub const VK_DAC_M : [16]f64 = [0.0, 0.00146627565982399, 0.00293255131964784, 0.00586510263929613, 0.0117302052785922, 0.0234604105571847, 0.0469208211143697, 0.0938416422287389, 0.187683284457478, 0.375366568914956, 0.750733137829912, 0.749266862170088, 0.752199413489736, 0.5, 1.0, 1.5]; +/// ngspice, the host run of sar_cdac_half.cir (one 10 us run, 15 digits, the last point of each 10 ns interval). +pub const NG_DAC_M : [16]f64 = [-1.05315062226552e-14, 0.001466275659813511, 0.002932551319637533, 0.00586510263928559, 0.01173020527858186, 0.02346041055717397, 0.04692082111435731, 0.09384164222872363, 0.1876832844574534, 0.375366568914892, 0.7507331378296765, 0.7492668621698523, 0.7521994134895007, 0.4999999999998767, 0.9999999999995066, 1.499999999998824]; +/// Negative control: the first VACASK run, the ten plate nodes not in the ic list: every code 0.75 V high. +pub const VK_DAC_UNLISTED : [16]f64 = [0.75, 0.751466275659824, 0.752932551319648, 0.755865102639296, 0.761730205278593, 0.773460410557185, 0.79692082111437, 0.84384164222874, 0.937683284457478, 1.12536656891496, 1.50073313782991, 1.49926686217009, 1.50219941348974, 1.25, 1.75, 2.25]; + +; --- helpers ----------------------------------------------------------------------------------------------- +/// The largest |a[i] - b[i]| over the first n entries. +pub fn vc_maxdiff(a: []const f64, b: []const f64, n: usize) -> f64 { + var worst : f64 = 0.0; + var i : usize = 0; + while (i < n) { + const d : f64 = fabs64(a[i] - b[i]); + if (d > worst) { worst = d; } + i = i + 1; + } + return worst; +} + +/// The smallest |a[i] - b[i]| over the first n entries. +pub fn vc_mindiff(a: []const f64, b: []const f64, n: usize) -> f64 { + var best : f64 = fabs64(a[0] - b[0]); + var i : usize = 1; + while (i < n) { + const d : f64 = fabs64(a[i] - b[i]); + if (d < best) { best = d; } + i = i + 1; + } + return best; +} + +/// |x - y| <= tol. +pub fn vc_within(x: f64, y: f64, tol: f64) -> bool { + return fabs64(x - y) <= tol; +} + +/// 1 when dp is above dn. +pub fn vc_win(dp: f64, dn: f64) -> u32 { + if (dp > dn) { return 1; } + return 0; +} + +/// How many of the first n runs have a winner different from the flags (1 = dp wins). +pub fn vc_mismatch(dp: []const f64, dn: []const f64, flags: []const u32, n: usize) -> u32 { + var bad : u32 = 0; + var i : usize = 0; + while (i < n) { + if (vc_win(dp[i], dn[i]) != flags[i]) { bad = bad + 1; } + i = i + 1; + } + return bad; +} + +/// The magnitude (units) of the last scan point where dn still wins, scanning from the most negative d to the least: the lower end of the flip bracket. 0 if dp wins everywhere. +pub fn vc_bracket_lo(d: []const u32, dp: []const f64, dn: []const f64, n: usize) -> u32 { + var lo : u32 = 0; + var i : usize = 0; + while (i < n) { + if (vc_win(dp[i], dn[i]) == 0) { lo = d[i]; } + i = i + 1; + } + return lo; +} + +/// The magnitude of the first scan point where dp wins (the upper end of the flip bracket); 0 if dn wins everywhere. +pub fn vc_bracket_hi(d: []const u32, dp: []const f64, dn: []const f64, n: usize) -> u32 { + var i : usize = 0; + while (i < n) { + if (vc_win(dp[i], dn[i]) == 1) { return d[i]; } + i = i + 1; + } + return 0; +} + +/// True when the winner changes once only along the scan (dn, dn, .., dp, dp): the flip is a single point. +pub fn vc_monotone(dp: []const f64, dn: []const f64, n: usize) -> bool { + var seen_dp : bool = false; + var i : usize = 0; + while (i < n) { + if (vc_win(dp[i], dn[i]) == 1) { seen_dp = true; } + if (seen_dp) { + if (vc_win(dp[i], dn[i]) == 0) { return false; } + } + i = i + 1; + } + return true; +} + +/// The model's first output and resolution time (ps after the clock) for the input h (units), orientation swap. +pub fn vc_model_first_ps(h: u32, swap: bool) -> f64 { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + sp_run(&TT50_N, &TT50_P, h, swap, SC_K, &SC_DV_M, &out, &res); + return sp_first(&out) * 0.1; +} + +/// The model's resolution time, the same run. +pub fn vc_model_res_ps(h: u32, swap: bool) -> f64 { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + sp_run(&TT50_N, &TT50_P, h, swap, SC_K, &SC_DV_M, &out, &res); + return sp_res(&out) * 0.1; +} + +; --- the tests: the comparator -------------------------------------------------------------------------------- + +test vacask_and_ngspice_agree_on_the_comparators_times_within_the_stated_tolerance { + // sar_cmp_psp_real.cir against vacask_cmp_real.sim, the same PSP103 file: the decision, resolution and Valid times at the 7 magnitudes and both signs. ngspice's numbers are + // the committed pins of sar_comparator_psp.t27 (the host run of 2026-10-20 reproduces them to the last digit); VACASK's are this file's. + const f_p : f64 = vc_maxdiff(&VK_FIRST_PS_P, &NG_RL_FIRST_PS, 7); + const f_m : f64 = vc_maxdiff(&VK_FIRST_PS_M, &NG_RL_FIRST_PS, 7); + const r_p : f64 = vc_maxdiff(&VK_RES_PS_P, &NG_RL_RES_PS, 7); + const r_m : f64 = vc_maxdiff(&VK_RES_PS_M, &NG_RL_RES_PS, 7); + const v_p : f64 = vc_maxdiff(&VK_VALID_PS_P, &NG_RL_VALID_PS_P, 7); + const v_m : f64 = vc_maxdiff(&VK_VALID_PS_M, &NG_RL_VALID_PS_M, 7); + // Inside the tolerance ... + assert f_p < TOL_FIRST_PS; + assert f_m < TOL_FIRST_PS; + assert v_p < TOL_FIRST_PS; + assert v_m < TOL_FIRST_PS; + assert r_p < TOL_RES_PS; + assert r_m < TOL_RES_PS; + // ... and the observed maxima from below, so a copied column or a loosened bound is noticed: 0.1250 and 0.1245, 0.1375 and 0.1360, 0.0653 and 0.0635. + assert f_p > 0.124; + assert f_m > 0.124; + assert v_p > 0.136; + assert v_m > 0.135; + assert r_p > 0.065; + assert r_m > 0.063; + // The first point is the headline: 523.032 ps against 522.910 ps, -0.023 %. + const rel : f64 = (VK_FIRST_PS_P[0] - NG_RL_FIRST_PS[0]) / NG_RL_FIRST_PS[0]; + assert rel < 0.0 - 0.00022; + assert rel > 0.0 - 0.00025; + // The clock's 50 % point is the deck's: 200.8 ps in both. + assert NG_RL_TCK_PS > 200.7; + assert NG_RL_TCK_PS < 200.9; +} + +test every_winner_of_both_comparators_is_the_same_in_vacask_and_ngspice { + // 14 runs for xb (the bare latch) and 14 for xtop (with its loads): dp wins for VDP > VDN and dn for VDP < VDN in the bare latch; the top favours dp by 0.15 mV, so for -2h dn + // wins only while 2h exceeds that (h = 16777 and 4194). VACASK has the same winner as ngspice in every one of the 28 runs. + assert_eq(vc_mismatch(&VK_BDP_P, &VK_BDN_P, &NG_RL_BARE_WIN_P, 7), 0); + assert_eq(vc_mismatch(&VK_BDP_M, &VK_BDN_M, &NG_RL_BARE_WIN_M, 7), 0); + assert_eq(vc_mismatch(&VK_XDP_P, &VK_XDN_P, &NG_RL_TOP_WIN_P, 7), 0); + assert_eq(vc_mismatch(&VK_XDP_M, &VK_XDN_M, &NG_RL_TOP_WIN_M, 7), 0); + // The bare latch over the scan: dn wins at all six negative differences, in both. + assert_eq(vc_mismatch(&VK_SCAN_BDP, &VK_SCAN_BDN, &NG_RL_SCAN_BARE_WIN, 6), 0); + // The top over the scan differs at exactly the two points between the two flip points (-2571 and -2570 units): dn wins there in ngspice, dp in VACASK. + assert_eq(vc_mismatch(&VK_SCAN_XDP, &VK_SCAN_XDN, &NG_RL_SCAN_TOP_WIN, 6), 2); + assert_eq(vc_win(VK_SCAN_XDP[2], VK_SCAN_XDN[2]), 1); + assert_eq(vc_win(VK_SCAN_XDP[3], VK_SCAN_XDN[3]), 1); + assert_eq(NG_RL_SCAN_TOP_WIN[2], 0); + assert_eq(NG_RL_SCAN_TOP_WIN[3], 0); + // The end values are decided, not marginal: the loser is below 0.1 V at every input but the smallest (h = 1: 0.084 V, 7 times as large as at h = 8, still decided). + var k : usize = 0; + var decided : u32 = 0; + while (k < 7) { + if (VK_BDN_P[k] < 0.1) { decided = decided + 1; } + if (VK_BDP_M[k] < 0.1) { decided = decided + 1; } + k = k + 1; + } + assert_eq(decided, 14); +} + +test the_flip_points_the_bare_latch_at_zero_and_the_tops_within_46_units_of_ngspices { + // xb, the bare latch: no flip point to speak of. The right winner at h = 1 (VDP - VDN = +-2 units) in both orientations, in both simulators, so the flip is inside (-2, 2) units; the + // model's flip (sp_flip, bisection on the fixed-point comparator) is at most 2 units from zero, 0.12 uV. + assert_eq(vc_win(VK_BDP_P[6], VK_BDN_P[6]), 1); + assert_eq(vc_win(VK_BDP_M[6], VK_BDN_M[6]), 0); + assert_eq(NG_RL_BARE_WIN_P[6], 1); + assert_eq(NG_RL_BARE_WIN_M[6], 0); + const fm : i64 = sp_flip(&TT50_N, &TT50_P, &SC_DV_M); + const fa : f64 = fabs64((fm as f64)); + assert fa <= 2.0; + // xtop: ngspice's flip is in (-2570, -2540] units from its scan, VACASK's in (-2586, -2582] from the finer scan; the winner changes once along each scan. + assert vc_monotone(&VK_FINE_XDP, &VK_FINE_XDN, 8); + assert vc_monotone(&VK_SCAN_XDP, &VK_SCAN_XDN, 6); + const vk_lo : u32 = vc_bracket_lo(&FINE_D, &VK_FINE_XDP, &VK_FINE_XDN, 8); + const vk_hi : u32 = vc_bracket_hi(&FINE_D, &VK_FINE_XDP, &VK_FINE_XDN, 8); + assert_eq(vk_lo, 2586); + assert_eq(vk_hi, 2582); + const ng_lo : u32 = vc_bracket_lo(&SCAN_D, &NG_SCAN_XDP, &NG_SCAN_XDN, 6); + const ng_hi : u32 = vc_bracket_hi(&SCAN_D, &NG_SCAN_XDP, &NG_SCAN_XDN, 6); + assert_eq(ng_lo, 2570); + assert_eq(ng_hi, 2540); + // The flip bracket of VACASK is below ngspice's (a larger magnitude: the top favours dp by more), and the two are 12 to 46 units apart: at least vk_hi - ng_lo, at most + // vk_lo - ng_hi. 46 units is 2.7 uV, 1.8 % of the 0.153 mV offset, the size of ngspice's own dependence on its tolerance (10 units between reltol 1e-6 and 1e-8). + const gap_min : u32 = vk_hi - ng_lo; + const gap_max : u32 = vk_lo - ng_hi; + assert_eq(gap_min, 12); + assert_eq(gap_max, 46); + assert gap_max <= TOL_FLIP_UNITS; + const gap_uv : f64 = (gap_max as f64) * UNIT_V * 1000000.0; + assert gap_uv > 2.7; + assert gap_uv < 2.8; + // The offset itself, in mV: -0.1541 .. -0.1539 (VACASK) against -0.1532 .. -0.1514 (ngspice): the same sign, the same 0.15 mV. + const vk_mv : f64 = (vk_hi as f64) * UNIT_V * 1000.0; + const ng_mv : f64 = (ng_lo as f64) * UNIT_V * 1000.0; + assert vk_mv > 0.1538; + assert vk_mv < 0.1541; + assert ng_mv > 0.1531; + assert ng_mv < 0.1533; + // The same asymmetry in the Valid time at -+2 mV: VACASK 920.548 - 897.567 = 22.98 ps, ngspice 22.92 ps. + const dv_vk : f64 = VK_VALID_PS_M[0] - VK_VALID_PS_P[0]; + const dv_ng : f64 = NG_RL_VALID_PS_M[0] - NG_RL_VALID_PS_P[0]; + assert vc_within(dv_vk, dv_ng, 0.1); + assert dv_vk > 22.9; + assert dv_vk < 23.1; +} + +test the_pre_clock_waveforms_of_the_two_simulators_agree_from_one_picosecond_on { + // The comparator's start is a uic start from a state that is not a solution; in both simulators the outputs jump to about 1.2 V in the first step and fall back through 0.75 V at + // about 150 ps. The first step differs by 31 mV (1.2197 V against 1.2509 V); from 1 ps on the two waveforms agree within 0.4 mV, from 30 ps on within 0.15 mV. + const first : f64 = fabs64(VK_PRE_005_V - NG_PRE_005_V); + assert first > 0.031; + assert first < 0.032; + const all : f64 = vc_maxdiff(&VK_PRE_V, &NG_PRE_V, 12); + assert all < TOL_PRE_V; + assert all > 0.00034; + var late : f64 = 0.0; + var k : usize = 3; + while (k < 12) { + const d : f64 = fabs64(VK_PRE_V[k] - NG_PRE_V[k]); + if (d > late) { late = d; } + k = k + 1; + } + assert late < 0.00015; + assert late > 0.0001; + // The falling crossing of 0.75 V is between 100 and 190 ps, near 150 ps, in both: the values at 100 ps are above and at 190 ps below. + assert NG_PRE_V[5] > 0.75; + assert VK_PRE_V[5] > 0.75; + assert NG_PRE_V[7] < 0.75; + assert VK_PRE_V[7] < 0.75; + assert PRE_T_PS[5] > 99.0; + assert PRE_T_PS[5] < 101.0; + // The 0.75 V crossing itself, by linear interpolation between 100 and 150 ps: ngspice puts it at 150.417 ps, VACASK at 150.428 ps (the waveform falls at 2 mV/ps there). + const cn : f64 = PRE_T_PS[5] + (PRE_T_PS[6] - PRE_T_PS[5]) * (NG_PRE_V[5] - 0.75) / (NG_PRE_V[5] - NG_PRE_V[6]); + const cv : f64 = PRE_T_PS[5] + (PRE_T_PS[6] - PRE_T_PS[5]) * (VK_PRE_V[5] - 0.75) / (VK_PRE_V[5] - VK_PRE_V[6]); + assert vc_within(cn, cv, 0.05); +} + +test our_comparator_model_is_98_and_155_times_too_fast_against_both_simulators { + // The model (specs/analog/sar_comparator_psp.t27, currents only: no charge) decides in 5.350 ps and resolves in 7.186 ps at +2 mV; ngspice's real-charge comparator takes 523.032 ps + // and 1111.910 ps. VACASK's 522.910 ps and 1111.845 ps give the same ratio to 0.03 %: the discrepancy is the model's (the charge tables, #10777, are what is missing). + var k : usize = 0; + while (k < 6) { + const f_ps : f64 = vc_model_first_ps(SP_H[k], false); + const r_ps : f64 = vc_model_res_ps(SP_H[k], false); + const rf_ng : f64 = NG_RL_FIRST_PS[k] / f_ps; + const rf_vk : f64 = VK_FIRST_PS_P[k] / f_ps; + const rr_ng : f64 = NG_RL_RES_PS[k] / r_ps; + const rr_vk : f64 = VK_RES_PS_P[k] / r_ps; + // The same band as sar_comparator_psp.t27's own check, now for both simulators. + assert !(rf_ng < 95.5); + assert !(rf_ng > 98.0); + assert !(rf_vk < 95.5); + assert !(rf_vk > 98.0); + assert !(rr_ng < 154.0); + assert !(rr_ng > 183.5); + assert !(rr_vk < 154.0); + assert !(rr_vk > 183.5); + // VACASK and ngspice judge the model alike: the two ratios differ by under 0.03 % (decision) and 0.007 % (resolution). + assert vc_within(rf_vk / rf_ng, 1.0, 0.0003); + assert vc_within(rr_vk / rr_ng, 1.0, 0.00007); + k = k + 1; + } + const f0 : f64 = vc_model_first_ps(SP_H[0], false); + const r0 : f64 = vc_model_res_ps(SP_H[0], false); + assert f0 > 5.34; + assert f0 < 5.36; + assert r0 > 7.18; + assert r0 < 7.19; + assert NG_RL_FIRST_PS[0] / f0 > 97.7; + assert VK_FIRST_PS_P[0] / f0 < 97.8; +} + +; --- the tests: the capacitive DAC ---------------------------------------------------------------------------- + +test the_dac_top_plate_voltage_is_the_same_in_vacask_ngspice_and_the_closed_form { + // 16 codes (NG_SPOT) of one half, ideal capacitors, sampled at 0.75 V: ngspice's one long run (its host run and the committed table NG_HALF), VACASK's 30 ns per code, the closed + // form 1.5 c / 1023 (sar_cdac.t27: the bridge's gain error, no bend), and the fixed-point model (ten M1 steps per code). + var cf : [16]f64 = [0.0] ** 16; + var half : [16]f64 = [0.0] ** 16; + var mdl : [16]f64 = [0.0] ** 16; + var k : usize = 0; + while (k < 16) { + const code : u32 = NG_SPOT[k]; + cf[k] = 1.5 * (code as f64) / 1023.0; + half[k] = NG_HALF[code as usize]; + const n : CdacNodes = convert(code, VS_HALF); + mdl[k] = sm_f64(n.m); + k = k + 1; + } + const vk_cf : f64 = vc_maxdiff(&VK_DAC_M, &cf, 16); + const vk_ng : f64 = vc_maxdiff(&VK_DAC_M, &NG_DAC_M, 16); + const vk_half : f64 = vc_maxdiff(&VK_DAC_M, &half, 16); + const ng_cf : f64 = vc_maxdiff(&NG_DAC_M, &cf, 16); + const vk_mdl : f64 = vc_maxdiff(&VK_DAC_M, &mdl, 16); + // VACASK is at the closed form to 2e-15 V; ngspice's long run is up to 1.18e-12 V from it (1.0 .. 1.4e-12), so VACASK is within 1.4e-12 V of ngspice's host run and of the committed table. + assert vk_cf < TOL_DAC_CF_V; + assert vk_ng < TOL_DAC_NG_V; + assert vk_half < TOL_DAC_NG_V; + assert ng_cf < TOL_DAC_NG_V; + assert ng_cf > 0.000000000001; + assert vk_ng > 0.000000000001; + // The model is the fixed-point one: within 1.74 units (1.04e-7 V) of the closed form and so of VACASK; its code 0 is one unit below 0 V. + assert vk_mdl < TOL_DAC_MODEL_V; + assert vk_mdl > 0.00000005; + assert mdl[0] < 0.0; + assert mdl[0] > 0.0 - 0.00000006; + // The codes' order and meaning: 0, the single bits, 511, 513 and the two alternating patterns; the top plate rises by 1.5 / 1023 per code. + assert VK_DAC_M[11] < VK_DAC_M[10]; + assert VK_DAC_M[12] > VK_DAC_M[10]; + assert vc_within(VK_DAC_M[13], 0.5, 0.000000000000002); + assert vc_within(VK_DAC_M[14], 1.0, 0.000000000000002); + assert vc_within(VK_DAC_M[15], 1.5, 0.000000000000002); +} + +; --- the negative controls -------------------------------------------------------------------------------------- + +test the_checks_refuse_a_changed_integrator_a_wrong_start_and_wrong_numbers { + // 1. VACASK with tran_xmu = 0.25 (a more Euler-like integration): the resolution time is 0.65 to 1.87 ps early at all 7 inputs, at least 8 times the 0.08 ps bound. + const x_min : f64 = vc_mindiff(&VK_XMU_RES_PS_P, &NG_RL_RES_PS, 7); + const x_max : f64 = vc_maxdiff(&VK_XMU_RES_PS_P, &NG_RL_RES_PS, 7); + assert x_min > 0.65; + assert x_min > 8.0 * TOL_RES_PS; + assert x_max < 1.88; + assert x_max > 1.86; + var k : usize = 0; + var refused : u32 = 0; + while (k < 7) { + if (!vc_within(VK_XMU_RES_PS_P[k], NG_RL_RES_PS[k], TOL_RES_PS)) { refused = refused + 1; } + k = k + 1; + } + assert_eq(refused, 7); + // 2. The plates left out of the ic list: every DAC code is 0.75 V high (all 16), 5e11 times the 1.4e-12 V bound. + const u_min : f64 = vc_mindiff(&VK_DAC_UNLISTED, &NG_DAC_M, 16); + const u_max : f64 = vc_maxdiff(&VK_DAC_UNLISTED, &NG_DAC_M, 16); + assert u_min > 0.7499; + assert u_max < 0.7501; + assert u_min > 500000000000.0 * TOL_DAC_NG_V; + // 3. A decision time moved by 1 ps, and by 0.3 ps, is refused; 0.1 ps still passes (a bound that is not a rubber band). + assert !vc_within(VK_FIRST_PS_P[0] + 1.0, NG_RL_FIRST_PS[0], TOL_FIRST_PS); + assert !vc_within(VK_FIRST_PS_P[0] + 0.3, NG_RL_FIRST_PS[0], TOL_FIRST_PS); + assert vc_within(VK_FIRST_PS_P[0] + 0.1, NG_RL_FIRST_PS[0], TOL_FIRST_PS); + // 4. The inputs exchanged (VDP and VDN swapped): the +2h list against the -2h flags and the reverse: every one of the 14 bare-latch winners is wrong. + assert_eq(vc_mismatch(&VK_BDP_P, &VK_BDN_P, &NG_RL_BARE_WIN_M, 7), 7); + assert_eq(vc_mismatch(&VK_BDP_M, &VK_BDN_M, &NG_RL_BARE_WIN_P, 7), 7); + // 5. A claim that VACASK's flip point of xtop is ngspice's bracket (-2570 .. -2540): the finer scan puts VACASK's dp winning at -2582 and dn at -2586, outside it. + const vk_hi : u32 = vc_bracket_hi(&FINE_D, &VK_FINE_XDP, &VK_FINE_XDN, 8); + const ng_lo : u32 = vc_bracket_lo(&SCAN_D, &NG_SCAN_XDP, &NG_SCAN_XDN, 6); + assert vk_hi > ng_lo; + assert vc_win(VK_SCAN_XDP[2], VK_SCAN_XDN[2]) != NG_RL_SCAN_TOP_WIN[2]; + // 6. A wrong DAC pin: the values of code 511 and code 513 exchanged (they are 2.93 mV apart, two codes of 1.466 mV) are refused by the 1.4e-12 V bound. + const sw : f64 = fabs64(VK_DAC_M[11] - VK_DAC_M[12]); + assert sw > 0.0029; + assert !vc_within(VK_DAC_M[12], NG_DAC_M[11], TOL_DAC_NG_V); +}