From 93078dd2b07edbb9c3ef76f6f21411235284a491 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 12:34:50 +0700 Subject: [PATCH] analog M4b-psp: the StrongARM comparator on PSP103 tables at 50 C against ngspice -- sign, times, flip point, imbalance sensitivity (Closes #10856) The golden comparator, M4b's multi-node Newton solver reused with the NMOS and PMOS tables as two arguments (sc_*_np; the old names delegate, every old pin unchanged), on the tt 50 C PSP103 tables (mos_psp103_50c_data.t27, deck psp103_tab_tt50.cir). Judged against ngspice-47 + PSP103 with the golden dynamic_comparator (charge swamped by 1e6, bulk tied or as in the deck) and with the real charge, bare and as dynamic_comparator_top. 13 tests, 0 vacuous. The isolated latch has no threshold of its own: flip point 0 in the model and in ngspice; the top's NAND gives -0.153 mV; an unequal pair gives the offset, predicted within 2.1 %. Co-Authored-By: Claude Sonnet 5.5 --- .../seals/analog_AnalogMosPsp10350cData.json | 37 + .../seals/analog_AnalogSarComparator.json | 12 +- .../seals/analog_AnalogSarComparatorPsp.json | 53 ++ .trinity/seals/analog_AnalogSarSwitch.json | 4 +- specs/analog/decks/psp103_tab_tt50.cir | 70 ++ specs/analog/decks/sar_cmp_psp_r2.cir | 69 ++ specs/analog/decks/sar_cmp_psp_r2b.cir | 90 +++ specs/analog/decks/sar_cmp_psp_real.cir | 72 ++ specs/analog/mos_psp103_50c_data.t27 | 268 +++++++ specs/analog/sar_comparator.t27 | 66 +- specs/analog/sar_comparator_psp.t27 | 689 ++++++++++++++++++ 11 files changed, 1407 insertions(+), 23 deletions(-) create mode 100644 .trinity/seals/analog_AnalogMosPsp10350cData.json create mode 100644 .trinity/seals/analog_AnalogSarComparatorPsp.json create mode 100644 specs/analog/decks/psp103_tab_tt50.cir create mode 100644 specs/analog/decks/sar_cmp_psp_r2.cir create mode 100644 specs/analog/decks/sar_cmp_psp_r2b.cir create mode 100644 specs/analog/decks/sar_cmp_psp_real.cir create mode 100644 specs/analog/mos_psp103_50c_data.t27 create mode 100644 specs/analog/sar_comparator_psp.t27 diff --git a/.trinity/seals/analog_AnalogMosPsp10350cData.json b/.trinity/seals/analog_AnalogMosPsp10350cData.json new file mode 100644 index 0000000000..e6c6dc1835 --- /dev/null +++ b/.trinity/seals/analog_AnalogMosPsp10350cData.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", + "closure": [ + { + "spec": "specs/analog/mos_psp103_data.t27", + "spec_hash": "sha256:d5f3244ff1936f2b96e95b06e022d6b42b489ac49e025ec99486d6d36e6c6580" + }, + { + "spec": "specs/analog/mos_table.t27", + "spec_hash": "sha256:9c0d1537ed5d8d60c4bdcb67d1e4c73ef80270e8cab224cb089c5b568fe11182" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:277bd41ef2e98b05b10cd80d83c632c20ff90f8ff50c5f8b0bd8710b9c2b6ff0", + "gen_hash_rust": "sha256:6fcf9d9f5e5c577b7a8a14ad729ea68108509ba7fed79106187abb1f589fbb39", + "gen_hash_verilog": "sha256:16aaa6a244e03f397b0b6bae08bfe89793319614845c8fba614704078a3d112f", + "gen_hash_zig": "sha256:1ac65693f2030507c79047418c8a74dc4b24656dacbd336dc53c02c55f58a9a1", + "module": "AnalogMosPsp10350cData", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T05:32:08Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:ed4680506d435cdb660ecfdf28ec4f7091fa3c40ce08dc4aeb5bc4570b404976", + "spec_path": "specs/analog/mos_psp103_50c_data.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 3, + "total": 3, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/analog_AnalogSarComparator.json b/.trinity/seals/analog_AnalogSarComparator.json index df7ea1875f..7a8c9a9031 100644 --- a/.trinity/seals/analog_AnalogSarComparator.json +++ b/.trinity/seals/analog_AnalogSarComparator.json @@ -11,16 +11,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:13f523685de5d6085818b938b8d9596938d50da2fbba1dae8b9978e50bfbd936", - "gen_hash_rust": "sha256:e58bde7196705be96ecd9c21fb54a3cbcea6323bd29a01f8abb46ee3b834b427", - "gen_hash_verilog": "sha256:b54771cd79b42bbba9ee294e993e3e096420931b1b2f832c3d00f46a28d5e8d8", - "gen_hash_zig": "sha256:21ae8f9401db2831f43303ca4b0f2b15ef9d3ed6cd00196b90bedb15ef15ed91", + "gen_hash_c": "sha256:ce2ff93091397b6a53ed56e1691d9ae73b6442e57ce4249b7e3a8cc3106b7436", + "gen_hash_rust": "sha256:bab770f55aaa082e158dfef79f5933ee3d3a8d30b587db42f01ce97801234e6f", + "gen_hash_verilog": "sha256:6ca0ddc1b0377e8fd141a237e3c278358da98bcb8761a75c0846f57914388844", + "gen_hash_zig": "sha256:24ab5cb2621640efe1b3163a87657c83c718fb084c415d87ece7a9dc00736093", "module": "AnalogSarComparator", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T02:37:52Z", + "sealed_at": "2026-10-19T05:32:25Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:b88d05d5d17937024580ab40e80b698b63962cab531164d4a94a5dddd05a72a8", + "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7", "spec_path": "specs/analog/sar_comparator.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/analog_AnalogSarComparatorPsp.json b/.trinity/seals/analog_AnalogSarComparatorPsp.json new file mode 100644 index 0000000000..a173c1e104 --- /dev/null +++ b/.trinity/seals/analog_AnalogSarComparatorPsp.json @@ -0,0 +1,53 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", + "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + }, + { + "spec": "specs/analog/mos_psp103.t27", + "spec_hash": "sha256:c5bd55a847953654fbef792dcfc61da7f6434b8023341df9ab4a53e5d082bbe0" + }, + { + "spec": "specs/analog/mos_psp103_50c_data.t27", + "spec_hash": "sha256:ed4680506d435cdb660ecfdf28ec4f7091fa3c40ce08dc4aeb5bc4570b404976" + }, + { + "spec": "specs/analog/mos_psp103_data.t27", + "spec_hash": "sha256:d5f3244ff1936f2b96e95b06e022d6b42b489ac49e025ec99486d6d36e6c6580" + }, + { + "spec": "specs/analog/mos_table.t27", + "spec_hash": "sha256:9c0d1537ed5d8d60c4bdcb67d1e4c73ef80270e8cab224cb089c5b568fe11182" + }, + { + "spec": "specs/analog/sar_comparator.t27", + "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:91ecc16d5235374b1f17b5a5abefb43338070178edc91b2df7771304ddf2038b", + "gen_hash_rust": "sha256:eb6ab9e0ef8df48cb8d94f0767ed30a0e739e3ba62593b7e74028f98c4c1cb9d", + "gen_hash_verilog": "sha256:829e870bd7e38baf62be311768fe416788566409d10f7fad74b435f0b4359621", + "gen_hash_zig": "sha256:fff44d18247a8915aefe4158dfedcaeaa4c94240daac2f6c1d25e4d4bf923deb", + "module": "AnalogSarComparatorPsp", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T05:32:49Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:03fa3e42e50de0a06436cc2e57636a6b59c1be04243a9b836920832e8dae0c40", + "spec_path": "specs/analog/sar_comparator_psp.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 13, + "total": 13, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/analog_AnalogSarSwitch.json b/.trinity/seals/analog_AnalogSarSwitch.json index d3b0d0bbcb..6dd0bdf823 100644 --- a/.trinity/seals/analog_AnalogSarSwitch.json +++ b/.trinity/seals/analog_AnalogSarSwitch.json @@ -15,7 +15,7 @@ }, { "spec": "specs/analog/sar_comparator.t27", - "spec_hash": "sha256:b88d05d5d17937024580ab40e80b698b63962cab531164d4a94a5dddd05a72a8" + "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -26,7 +26,7 @@ "module": "AnalogSarSwitch", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T02:37:55Z", + "sealed_at": "2026-10-19T05:33:07Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:122726502a51cb4c8980674b819ee3251bf8ee69a5586d6cd44a5eafcf284cb4", "spec_path": "specs/analog/sar_switch.t27", diff --git a/specs/analog/decks/psp103_tab_tt50.cir b/specs/analog/decks/psp103_tab_tt50.cir new file mode 100644 index 0000000000..84029274b7 --- /dev/null +++ b/specs/analog/decks/psp103_tab_tt50.cir @@ -0,0 +1,70 @@ +IHP SG13G2 PSP103 tables, corner mos_tt, 50 C +* specs/analog/decks/psp103_tab_tt50.cir -- table data for specs/analog/mos_psp103_50c_data.t27 (analog M4b-psp, #10856, epic #10486). A netlist plus a +* .control block that prints the numbers: psp103_tab_tt.cir (M3b, #10516) at the temperature of the golden SAR deck, nothing else changed. The +* golden deck says `.options TEMP = 50` (the converter's own run is at 50 C); M3b tabulated 27 C. The IHP corner file is read at run time and never +* copied here. Model: IHP-Open-PDK commit 21133f219b1a6da10d803e345761e77415ceff40, ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib (PSP 103.6, +* revision 200310), section mos_tt; the model code is libs.tech/verilog-a/psp103/psp103.va of the same commit, compiled with OpenVAF-Reloaded +* v24.0.2mob to psp103.osdi (and psp103_nqs.osdi). TNOM is 27 C and the circuit temperature 50 C: `.temp 50`. Devices: sg13_lv_nmos w=1.0u l=0.45u +* ng=1 m=1 and sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1. Solver options as in psp103_tab_tt.cir: reltol 1e-6, vntol 1e-9, abstol 1e-14. +* Paths are NOT in this file. ngspice reads two environment variables: IHP_PDK, the IHP-Open-PDK checkout, and IHP_OSDI, the directory that +* holds the compiled psp103.osdi and psp103_nqs.osdi. +* Run: IHP_PDK= IHP_OSDI= ngspice -b psp103_tab_tt50.cir > psp103_tab_tt50.out (ngspice-47) +* What it prints. Between "BEGIN name" and "END name" every row " TAB " is one number, in sweep order (the first sweep source runs +* fastest). The values are pasted into the spec in that order, none changed. +* grid_n Id of the nmos at Vgs = ix * 62.5 mV, Vds = iy * 62.5 mV, ix and iy 0..24, 625 rows, ix outer, in table units: nint(Id * 200 V/A * +* 2^24 units/V), the current times dt/C (200 V/A) in units of 2^-24 V, one unit 0.298 nA. Negative values (the gate current that leaves +* through the drain at Vds = 0) are held at 0: the table is unsigned. +* grid_p the same for the pmos at Vsg = ix * 62.5 mV, Vsd = iy * 62.5 mV (source and bulk on the 1.5 V rail; the current that leaves the drain). +* r2_n, r2_p Id in pA (nint(Id * 1e12)) at the first 200 held-out points of the R2 low-discrepancy sequence: point k is Vgs (Vsg) = 1.5 frac(k a1), +* Vds (Vsd) = 1.5 frac(k a2), a1 = 0.7548776662466927, a2 = 0.5698402909980532, k = 1..200: held-out points, never on a table node. +* nmos table device: drain VDN, gate VGN, source and bulk at 0 V +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +VDN dn 0 0 +VGN gn 0 0 +XMN dn gn 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +* pmos table device: source and bulk at the 1.5 V rail, drain VDP and gate VGP, both swept downward from 1.5 V +VSP sp 0 1.5 +VDP dp 0 1.5 +VGP gp 0 1.5 +XMP dp gp sp sp sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +* held-out points: B sources compute the R2 point from the sweep variable k; the zero-volt sources VRDN, VRDP measure the drain current +VK k 0 1 +BRGN rgn 0 V = 1.5*(v(k)*0.7548776662466927-floor(v(k)*0.7548776662466927)) +BRDN rdn0 0 V = 1.5*(v(k)*0.5698402909980532-floor(v(k)*0.5698402909980532)) +VRDN rdn0 rdn 0 +XRN rdn rgn 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +BRGP rgp 0 V = 1.5-1.5*(v(k)*0.7548776662466927-floor(v(k)*0.7548776662466927)) +BRDP rdp0 0 V = 1.5-1.5*(v(k)*0.5698402909980532-floor(v(k)*0.5698402909980532)) +VRDP rdp rdp0 0 +VRSP rsp 0 1.5 +XRP rdp rgp rsp rsp sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +.temp 50 +.options reltol=1e-6 vntol=1e-9 abstol=1e-14 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +echo BEGIN grid_n +dc VDN 0 1.5 0.0625 VGN 0 1.5 0.0625 +let u = nint(-i(VDN) * 200 * 16777216) +let u = (u + abs(u)) / 2 +print u +echo END grid_n +echo BEGIN grid_p +dc VDP 1.5 0 -0.0625 VGP 1.5 0 -0.0625 +let u = nint(i(VDP) * 200 * 16777216) +let u = (u + abs(u)) / 2 +print u +echo END grid_p +echo BEGIN r2_n +dc VK 1 200 1 +let u = nint(i(VRDN) * 1e12) +print u +echo END r2_n +echo BEGIN r2_p +let u = nint(i(VRDP) * 1e12) +print u +echo END r2_p +.endc +.end diff --git a/specs/analog/decks/sar_cmp_psp_r2.cir b/specs/analog/decks/sar_cmp_psp_r2.cir new file mode 100644 index 0000000000..0c0e4363a1 --- /dev/null +++ b/specs/analog/decks/sar_cmp_psp_r2.cir @@ -0,0 +1,69 @@ +SAR ADC dynamic comparator on PSP103, charge swamped by 1e6: backward Euler on a fixed 0.1 us grid +* specs/analog/decks/sar_cmp_psp_r2.cir -- reference data for specs/analog/sar_comparator_psp.t27 (analog M4b-psp, #10856, epic #10486). A netlist plus a +* .control block that prints the numbers. +* Circuit: the subcircuit dynamic_comparator of the uberWAVE golden SAR deck (wp2-parser/golden/top.spice; GPL-2.0, fetched at run time, never vendored), +* thirteen sg13_lv transistors and the inverter x1 that makes n_clkc, PSP103 (IHP-Open-PDK 21133f219b1a6da10d803e345761e77415ceff40, cornerMOSlv.lib +* mos_tt, through OSDI), at 50 C, the temperature of the golden deck. The deck reads the subcircuit text from the file named by the environment +* variable SAR_GOLDEN_SUBCKTS, the subcircuit section of the fetched deck without its .end and .global lines: +* sed -n '/^.subckt tracking_switch/,$p' top.spice | grep -v -i -E '^\.(end|global)([[:space:]]|$)' > $SAR_GOLDEN_SUBCKTS +* ngspice reads IHP_PDK (the IHP-Open-PDK checkout), IHP_OSDI (the directory with the compiled psp103.osdi and psp103_nqs.osdi) and SAR_GOLDEN_SUBCKTS. +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_cmp_psp_r2.cir > sar_cmp_psp_r2.out (ngspice-47) +* THE SCALE. PSP103 has charge: gate and junction capacitance of the thirteen devices (m up to 36, a few hundred fF on a node) which the current tables +* of the spec do not have. To compare the CURRENTS alone, the lumped node capacitances of the spec (4 fF on ap, an, dp, dn; 6 fF on net1; 2 fF on +* net2, net3, n_clkc) are multiplied by 1e6 (4 nF, 6 nF, 2 nF) and the step by the same 1e6 (0.1 ps becomes 0.1 us), so dt / C is the spec's, and the +* devices' own capacitances, a few hundred fF, are 1e-4 of the 4 nF at the largest. The currents are the real ones. Between 1e4 and 1e5 the decision +* time moved by 0.83 % and the resolution time by 1.3 %, between 1e5 and 1e6 by a tenth of that: the charge left at 1e6 is about 0.01 %. +* Inputs: VDD 1.5 V, common mode 0.75 V, VDP = 0.75 + sg h / 2^24 and VDN = 0.75 - sg h / 2^24 for h in units of 2^-24 V (the spec's words: h = 16777 is +* 2h = 33554 units = 2 mV less 0.4 uV) and sg = +1 or -1. The clock is a B source of nint(time/dt): 0 for the first 10 grid points, then a ramp of 16 +* steps of 1.5/16 V, then 1.5 V. Start: from .ic and uic, ap = an = net2 = net3 = n_clkc = 1.5 V, dp = dn = 0, net1 = 0.4009999632835388 V (5872026 units, the +* spec's max(VDP, VDN) - 0.35 V rounded to a word, an initial condition and not PSP103's own rest: net1 floats and an operating point would put it at +* 0.71 V on gmin). Integration: gear with maxord 1 is backward Euler, trtol 1e7 stops the truncation test from shrinking a step; reltol 1e-7, vntol 1e-10, +* abstol 1e-12, gmin 1e-12 (at 1e-9 / 1e-12 ngspice aborts at the first clock step). 150 grid points: step j of the spec is the row of the grid point j + 10. +* What it prints, per run, after "RUN ": tck, the clock through 0.75 V; tfirst, the first output through 0.75 V; tres, |dp - dn| through 0.75 V +* (those two are absent when the run does not reach it); dpend and dnend, the outputs at the last point; then eight rows " v(ap) v(an) v(net1) v(net2) +* v(net3) v(dp) v(dn) v(n_clkc)" at the rows 39, 59, 71, 79, 87, 95, 109, 149 (steps 20, 40, 52, 60, 68, 76, 90, 130: ngspice's opening ramp puts nine rows before the grid, so +* the row of step j is j + 19, at the time (j + 10) dt - 4 ns, which the printed time[row] shows). +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +Vdd vdd 0 1.5 +Vdp vdp 0 0.75 +Vdn vdn 0 0.75 +Bclk clkc 0 V = 1.5*min(max(nint(time/1e-7)-10,0),16)/16 +Xc vdp vdn clkc vdd 0 dp dn dynamic_comparator +Cap xc.ap 0 4n +Can xc.an 0 4n +Cn1 xc.net1 0 6n +Cn2 xc.net2 0 2n +Cn3 xc.net3 0 2n +Cdp dp 0 4n +Cdn dn 0 4n +Cnc xc.n_clkc 0 2n +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.ic v(xc.ap)=1.5 v(xc.an)=1.5 v(xc.net1)=0.4009999632835388 v(xc.net2)=1.5 v(xc.net3)=1.5 v(dp)=0 v(dn)=0 v(xc.n_clkc)=1.5 +.temp 50 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-12 gmin=1e-12 itl4=200 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +foreach h 16777 4194 1049 262 84 8 1 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 100n 15.5u 0 100n uic + echo RUN $h $sg + meas tran tck when v(clkc)=0.75 rise=1 + meas tran tfirst when v(mx)=0.75 rise=1 + meas tran tres when v(diff)=0.75 rise=1 + meas tran dpend find v(dp) at=15.5u + meas tran dnend find v(dn) at=15.5u + foreach r 39 59 71 79 87 95 109 149 + echo ROW $r + print time[$r] v(xc.ap)[$r] v(xc.an)[$r] v(xc.net1)[$r] v(xc.net2)[$r] v(xc.net3)[$r] v(dp)[$r] v(dn)[$r] v(xc.n_clkc)[$r] + end + end +end +.endc +.end diff --git a/specs/analog/decks/sar_cmp_psp_r2b.cir b/specs/analog/decks/sar_cmp_psp_r2b.cir new file mode 100644 index 0000000000..aa9d8d9321 --- /dev/null +++ b/specs/analog/decks/sar_cmp_psp_r2b.cir @@ -0,0 +1,90 @@ +SAR ADC dynamic comparator on PSP103 with every bulk tied to its source, charge swamped by 1e6: backward Euler on a fixed 0.1 us grid +* specs/analog/decks/sar_cmp_psp_r2b.cir -- reference data for specs/analog/sar_comparator_psp.t27 (analog M4b-psp, #10856, epic #10486). A netlist plus a +* .control block that prints the numbers. The same run as sar_cmp_psp_r2.cir (read its header for the scale, the inputs, the clock, the start, the +* integration options and the printed rows) on the same thirteen transistors and the inverter x1, with ONE change: the bulk of every device is tied to its own +* source, which is what a 2-D table Id(Vgs, Vds) of M3b models. The golden deck ties the NMOS bulks to ground and the PMOS bulks to VDD, so the input pair +* XM3, XM4 (source net1, 0.15 .. 0.4 V above its bulk) and the PMOS XM8, XM9 (sources net3, net2, below their bulk) carry a body effect which the +* tables cannot have; the difference between this deck and sar_cmp_psp_r2.cir is that body effect and nothing else. W, L and m are the golden deck's +* (facts of the circuit); the text of the golden deck is not copied. The deck reads no golden file: IHP_PDK and IHP_OSDI only. +* Run: IHP_PDK= IHP_OSDI= ngspice -b sar_cmp_psp_r2b.cir > sar_cmp_psp_r2b.out (ngspice-47) +* Second part, "MM ": the input pair made unequal, XM4 (the device whose gate is VDP) at m = 35 and 34 instead of 36, with VDP - VDN = d units of +* 2^-24 V; it prints dpend and dnend only. The flip point of the unequal pair is the d at which the winner changes: dn wins below it, dp above. The +* four d are the pairs of neighbours found by bisection (a 2-unit bracket): 71579 and 71581 for m = 35, 145772 and 145774 for m = 34. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +Vdd vdd 0 1.5 +Vdp vdp 0 0.75 +Vdn vdn 0 0.75 +Bclk clkc 0 V = 1.5*min(max(nint(time/1e-7)-10,0),16)/16 +XM1 xc.ap clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM2 xc.an clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM3 xc.ap vdn xc.net1 xc.net1 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM4 xc.an vdp xc.net1 xc.net1 sg13_lv_nmos w=1.0u l=0.45u ng=1 m={m4} +XM5 xc.net1 clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=18 +XM6 xc.net2 dp vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM7 xc.net3 dn vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM10 dn dp 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=12 +XM11 dp dn 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=12 +XM12 dn xc.n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM13 dp xc.n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM8 dp xc.an xc.net3 xc.net3 sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM9 dn xc.ap xc.net2 xc.net2 sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XIP xc.n_clkc clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN xc.n_clkc clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +Cap xc.ap 0 4n +Can xc.an 0 4n +Cn1 xc.net1 0 6n +Cn2 xc.net2 0 2n +Cn3 xc.net3 0 2n +Cdp dp 0 4n +Cdn dn 0 4n +Cnc xc.n_clkc 0 2n +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.ic v(xc.ap)=1.5 v(xc.an)=1.5 v(xc.net1)=0.4009999632835388 v(xc.net2)=1.5 v(xc.net3)=1.5 v(dp)=0 v(dn)=0 v(xc.n_clkc)=1.5 +.param m4=36 +.temp 50 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-12 gmin=1e-12 itl4=200 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +foreach h 16777 4194 1049 262 84 8 1 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 100n 15.5u 0 100n uic + echo RUN $h $sg + meas tran tck when v(clkc)=0.75 rise=1 + meas tran tfirst when v(mx)=0.75 rise=1 + meas tran tres when v(diff)=0.75 rise=1 + meas tran dpend find v(dp) at=15.5u + meas tran dnend find v(dn) at=15.5u + foreach r 39 59 71 79 87 95 109 149 + echo ROW $r + print time[$r] v(xc.ap)[$r] v(xc.an)[$r] v(xc.net1)[$r] v(xc.net2)[$r] v(xc.net3)[$r] v(dp)[$r] v(dn)[$r] v(xc.n_clkc)[$r] + end + end +end +alterparam m4 = 35 +reset +foreach d 71579 71581 + alter Vdp = 0.75 + ($d) / 2 / 16777216 + alter Vdn = 0.75 - ($d) / 2 / 16777216 + tran 100n 15.5u 0 100n uic + echo MM 35 $d + meas tran dpend find v(dp) at=15.5u + meas tran dnend find v(dn) at=15.5u +end +alterparam m4 = 34 +reset +foreach d 145772 145774 + alter Vdp = 0.75 + ($d) / 2 / 16777216 + alter Vdn = 0.75 - ($d) / 2 / 16777216 + tran 100n 15.5u 0 100n uic + echo MM 34 $d + meas tran dpend find v(dp) at=15.5u + meas tran dnend find v(dn) at=15.5u +end +.endc +.end diff --git a/specs/analog/decks/sar_cmp_psp_real.cir b/specs/analog/decks/sar_cmp_psp_real.cir new file mode 100644 index 0000000000..cbd890fa58 --- /dev/null +++ b/specs/analog/decks/sar_cmp_psp_real.cir @@ -0,0 +1,72 @@ +SAR ADC dynamic comparator on PSP103, real device charges, 50 C: the golden comparator bare and with its own loads +* specs/analog/decks/sar_cmp_psp_real.cir -- reference data for specs/analog/sar_comparator_psp.t27 (analog M4b-psp, #10856, epic #10486). A netlist plus a +* .control block that prints the numbers. +* Circuits, side by side in one netlist (they share only the two input sources, which are ideal), both from the uberWAVE golden SAR deck +* (wp2-parser/golden/top.spice; GPL-2.0, fetched at run time, never vendored): +* Xb the subcircuit dynamic_comparator with 4 fF on dp and dn (bdp, bdn): the latch alone, the circuit of the spec; +* Xtop the subcircuit dynamic_comparator_top: the same latch, 4 fF on each output, an inverter on each (x5, x6), 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. +* PSP103 (IHP-Open-PDK 21133f219b1a6da10d803e345761e77415ceff40, cornerMOSlv.lib mos_tt, through OSDI), 50 C, with ALL its charge: the gate, overlap +* and junction capacitance of every device. Nothing is scaled: this is the circuit as the converter has it, with an ideal 1.6 ps clock (the spec's) in +* place of the delay-cell clkc, and the inputs held. The subcircuit text comes from the file named by the environment variable SAR_GOLDEN_SUBCKTS +* (the sed line is in the header of sar_cmp_psp_r2.cir); IHP_PDK and IHP_OSDI as in all the PSP103 decks. +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_cmp_psp_real.cir > sar_cmp_psp_real.out (ngspice-47) +* Inputs: VDD 1.5 V, common mode 0.75 V. First list: VDP = 0.75 + sg h / 2^24 and VDN = 0.75 - sg h / 2^24 for h in units of 2^-24 V, sg = +1 or -1, as in +* sar_cmp_psp_r2.cir. Second list ("SCAN d"): VDP - VDN = d units, centred on 0.75 V, d from -2700 to -2400, around the flip point of Xtop. The clock is 0 +* until 200 ps, then rises over 1.6 ps (its 50 % point at 200.8 ps). Start: .ic with uic, 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. Integration: ngspice's default (trapezoidal), the step capped at 0.5 ps; reltol 1e-8, vntol 1e-11, abstol 1e-13 (the flip +* point of Xtop moved by 10 units between reltol 1e-6 and 1e-8: the tolerance is part of the number). +* What it prints, per run, after "RUN " or "SCAN ": tck, the clock through 0.75 V; tfirst, the first of dp and dn of Xb through 0.75 V; tres, +* |dp - dn| of Xb through 0.75 V; tvalid, Valid through 0.75 V (rising; only in the first list); dpend and dnend, dp and dn of Xb at 3 ns; tdpend and +* tdnend, the same for Xtop. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +Vdd vdd 0 1.5 +Vdp vdp 0 0.75 +Vdn vdn 0 0.75 +Bclk clkc 0 V = 1.5*min(max((time-200p)/1.6p,0),1) +Xb vdp vdn clkc vdd 0 bdp bdn dynamic_comparator +Cbdp bdp 0 4f +Cbdn bdn 0 4f +Xtop vocp vocn valid vdp vdn clkc vdd 0 dynamic_comparator_top +Bmx mx 0 V = max(v(bdp),v(bdn)) +Bdiff diff 0 V = abs(v(bdp)-v(bdn)) +.ic v(xb.ap)=1.5 v(xb.an)=1.5 v(xb.net1)=0.4009999632835388 v(xb.net2)=1.5 v(xb.net3)=1.5 v(bdp)=0 v(bdn)=0 v(xb.n_clkc)=1.5 ++ v(xtop.x1.ap)=1.5 v(xtop.x1.an)=1.5 v(xtop.x1.net1)=0.4009999632835388 v(xtop.x1.net2)=1.5 v(xtop.x1.net3)=1.5 v(xtop.dp)=0 v(xtop.dn)=0 v(xtop.x1.n_clkc)=1.5 ++ v(vocp)=1.5 v(vocn)=1.5 v(valid)=0 +.temp 50 +.options reltol=1e-8 vntol=1e-11 abstol=1e-13 itl4=200 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +foreach h 16777 4194 1049 262 84 8 1 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 0.1p 3n 0 0.5p uic + echo RUN $h $sg + meas tran tck when v(clkc)=0.75 rise=1 + meas tran tfirst when v(mx)=0.75 rise=1 + meas tran tres when v(diff)=0.75 rise=1 + meas tran tvalid when v(valid)=0.75 rise=1 + meas tran dpend find v(bdp) at=3n + meas tran dnend find v(bdn) at=3n + meas tran tdpend find v(xtop.dp) at=3n + meas tran tdnend find v(xtop.dn) at=3n + end +end +foreach d -2700 -2600 -2571 -2570 -2540 -2400 + alter Vdp = 0.75 + ($d) / 2 / 16777216 + alter Vdn = 0.75 - ($d) / 2 / 16777216 + tran 0.1p 3n 0 0.5p uic + echo SCAN $d + meas tran dpend find v(bdp) at=3n + meas tran dnend find v(bdn) at=3n + meas tran tdpend find v(xtop.dp) at=3n + meas tran tdnend find v(xtop.dn) at=3n +end +.endc +.end diff --git a/specs/analog/mos_psp103_50c_data.t27 b/specs/analog/mos_psp103_50c_data.t27 new file mode 100644 index 0000000000..7e9b45e723 --- /dev/null +++ b/specs/analog/mos_psp103_50c_data.t27 @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/mos_psp103_50c_data.t27 -- the IHP SG13G2 PSP103 current tables at 50 C, corner mos_tt: data and nothing else +; Analog M4b-psp (#10856, epic #10486). M3b (specs/analog/mos_psp103_data.t27) tabulated Id(Vgs, Vds) of sg13_lv_nmos (W 1.0 um, L 0.45 um) +; and sg13_lv_pmos (W 3.0 um, L 0.45 um) at 27 C. The uberWAVE golden SAR deck is simulated at 50 C (`.options TEMP = 50`), and its comparator +; is judged by this milestone against ngspice at that temperature, so the same two tables are printed again at 50 C, the same grid, the +; same units, the same recipe; M3b's file is not edited. Everything hand-written here is the wrapper around the numbers, the pins and the tests. +; - +; WHERE THE NUMBERS COME FROM. ngspice-47 and the IHP-Open-PDK checkout at commit 21133f219b1a6da10d803e345761e77415ceff40: the model file +; ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib (PSP 103.6, section mos_tt) with psp103.osdi compiled from the same commit. TNOM is 27 C, the +; circuit temperature 50 C (`.temp 50`). The deck is specs/analog/decks/psp103_tab_tt50.cir, psp103_tab_tt.cir of M3b with that one line changed +; and the held-out set cut to its first 200 points; run as `IHP_PDK= IHP_OSDI= ngspice -b psp103_tab_tt50.cir`. As a check +; of the toolchain, the same ngspice and checkout, run on psp103_tab_tt.cir (27 C), reproduce M3b's TT_N, TT_P and TT_R2_N word for word. +; - +; THE GRID AND THE UNITS are M3b's: 25 x 25 nodes, 24 cells of 62.5 mV over 0 .. 1.5 V on each axis, index ix * 25 + iy with ix along Vgs and iy +; along Vds, a unit 2^-24 V, an entry Id * (dt / C) with dt / C = 200 V/A, so one unit is 0.298 nA. For the PMOS the axes are Vsg and Vsd, the source +; and bulk on the 1.5 V rail, and the entry is the current that leaves the drain. Negative entries (the gate current out of the drain at Vds = 0) +; are held at 0. The held-out points are the first 200 of the R2 sequence of M3b (R2_A1, R2_A2 there): never nodes, current in pA. +; - +; WHAT 50 C DOES. The saturation current falls (NMOS 1349912 to 1271348 units, -5.8 %; PMOS 1011527 to 968691, -4.2 %) and the weak inversion +; current rises (NMOS at Vgs = 0.25 V, Vds = 1.5 V: 1725 to 2774 units), the two curves crossing near Vgs = 0.6 V (node 10): the threshold falls +; with temperature and the mobility falls with it. Both tables stay nondecreasing along both axes, zero at Vds = 0, with an off current of at +; most 3 units (the tests below). +; - +; WHAT A PASS SAYS. The numbers in this file are the numbers the deck printed, in order (the pins), the tables are monotone and zero at Vds = 0, +; and the temperature moves them the way it does in the model (the relations above). +; WHAT IT DOES NOT SAY. That these are the tables a board loads (no bitstream here), that the numbers are right (they are ngspice's, at reltol 1e-6, +; vntol 1e-9, abstol 1e-14), anything about another W, L, m, corner or temperature, or about charge: only current is tabulated. +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogMosPsp10350cData; + +use analog::mos_table::{monotone}; +use analog::mos_psp103_data::{TT_N, TT_P, fold_arr}; + +; --- what the numbers are ------------------------------------------------------------------------- +pub const KIND : str = "analog-table-data"; +pub const ID : str = "analog/mos_psp103_50c_data"; +pub const REFERENCE : str = "IHP-Open-PDK ihp-sg13g2 cornerMOSlv.lib (PSP 103.6) through ngspice-47 and OSDI, 50 C"; +/// The IHP-Open-PDK checkout every number was made from (the commit of M3b's tables). +pub const PDK_COMMIT : str = "21133f219b1a6da10d803e345761e77415ceff40"; +pub const TEMP50_C : u32 = 50; +/// The model's own nominal temperature. +pub const TNOM_C : u32 = 27; +/// How many held-out points a device has. +pub const R2_COUNT_50 : u32 = 200; + +; --- order-sensitive folds of the pasted numbers (fold of linear.t27) ---------------------------------------- +pub const PIN_TAB50 : u64 = 0xD9CE7524E09487AB; +pub const PIN_HELD50 : u64 = 0xC6BC11B1A4BBAAEF; + +; --- the numbers -------------------------------------------------------------------------------- +/// NMOS table, mos_tt at 50 C: Id(Vgs = ix * 62.5 mV, Vds = iy * 62.5 mV) at index ix * 25 + iy, units of dt/C = 200 V/A times 2^-24 V (deck psp103_tab_tt50.cir, block grid_n). +pub const TT50_N : [625]u32 = [ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 0, 5, 5, + 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, + 13, 14, 14, 14, 15, 15, 16, 16, 0, 27, 32, 36, 39, 43, + 46, 50, 53, 57, 60, 63, 66, 69, 72, 75, 78, 80, 83, 86, + 88, 90, 93, 95, 97, 0, 155, 187, 208, 229, 250, 271, 292, 312, + 332, 351, 369, 388, 405, 422, 439, 455, 471, 487, 502, 516, 531, 545, + 559, 572, 0, 806, 978, 1081, 1181, 1280, 1378, 1474, 1568, 1660, 1748, 1835, + 1919, 2001, 2080, 2157, 2233, 2306, 2378, 2448, 2516, 2582, 2648, 2711, 2774, 0, + 3084, 3838, 4180, 4483, 4778, 5065, 5344, 5614, 5876, 6129, 6374, 6612, 6843, 7066, + 7284, 7495, 7701, 7901, 8096, 8286, 8472, 8653, 8831, 9004, 0, 7922, 10579, 11507, + 12162, 12762, 13332, 13878, 14402, 14904, 15388, 15853, 16302, 16735, 17154, 17560, 17953, 18335, + 18706, 19066, 19417, 19759, 20092, 20418, 20735, 0, 15017, 21924, 24423, 25719, 26755, 27698, + 28582, 29419, 30215, 30974, 31701, 32398, 33067, 33712, 34335, 34935, 35517, 36080, 36626, 37156, + 37672, 38173, 38662, 39139, 0, 23572, 37103, 43130, 45837, 47587, 49042, 50357, 51580, 52728, + 53815, 54847, 55831, 56773, 57675, 58542, 59377, 60182, 60959, 61711, 62440, 63146, 63832, 64499, + 65149, 0, 32933, 54743, 66626, 72289, 75404, 77650, 79549, 81259, 82838, 84314, 85706, 87023, + 88277, 89473, 90617, 91714, 92769, 93786, 94766, 95713, 96630, 97518, 98380, 99218, 0, 42648, + 73668, 93275, 103943, 109654, 113268, 116033, 118393, 120512, 122460, 124276, 125982, 127593, 129123, 130580, + 131972, 133306, 134587, 135819, 137007, 138154, 139262, 140337, 141378, 0, 52415, 93062, 121549, 139114, + 149119, 155109, 159260, 162551, 165380, 167917, 170243, 172404, 174429, 176339, 178149, 179870, 181513, 183085, + 184594, 186044, 187441, 188788, 190091, 191352, 0, 62027, 112383, 150317, 176129, 192161, 201910, 208291, + 212988, 216803, 220102, 223058, 225762, 228270, 230615, 232823, 234913, 236898, 238791, 240601, 242336, 244003, + 245608, 247156, 248651, 0, 71348, 131276, 178821, 213654, 237111, 252118, 261862, 268682, 273914, 278242, + 282002, 285372, 288452, 291302, 293964, 296467, 298833, 301079, 303218, 305262, 307220, 309100, 310909, 312654, + 0, 80289, 149509, 206572, 250748, 282563, 304153, 318522, 328401, 335668, 341416, 346235, 350443, 354217, + 357663, 360848, 363820, 366611, 369246, 371746, 374125, 376397, 378573, 380660, 382669, 0, 88795, 166934, + 233260, 286795, 327466, 356631, 376814, 390808, 400892, 408595, 414829, 420114, 424749, 428912, 432711, 436221, + 439493, 442563, 445459, 448204, 450816, 453308, 455693, 457982, 0, 96834, 183466, 258697, 321404, 371097, + 408466, 435435, 454581, 468367, 478687, 486800, 493485, 499207, 504247, 508778, 512916, 516737, 520297, 523634, + 526781, 529762, 532596, 535300, 537886, 0, 104392, 199057, 282777, 354345, 412988, 458873, 493322, 518524, + 536905, 550587, 561145, 569639, 576739, 582865, 588283, 593164, 597622, 601739, 605572, 609165, 612551, 615757, + 618804, 621709, 0, 111468, 213694, 305451, 385491, 452857, 507322, 549673, 581634, 605424, 623235, 636873, + 647662, 656501, 663979, 670478, 676247, 681454, 686214, 690610, 694703, 698538, 702151, 705570, 708819, 0, + 118069, 227379, 326706, 414788, 490552, 553482, 603920, 643125, 673003, 695665, 713041, 726669, 737671, 746822, + 754642, 761480, 767573, 773083, 778126, 782785, 787122, 791186, 795013, 798635, 0, 124208, 240132, 346561, + 442233, 526008, 597165, 655692, 702420, 738894, 767040, 788791, 805827, 819462, 830656, 840084, 848212, 855359, + 861750, 867541, 872846, 877750, 882317, 886596, 890626, 0, 129901, 251983, 365049, 467854, 559220, 638286, + 704766, 759122, 802532, 836666, 863369, 884374, 901136, 914784, 926148, 935822, 944225, 951652, 958315, 964365, + 969914, 975047, 979829, 984311, 0, 135169, 262968, 382220, 491702, 590225, 676832, 751033, 812981, 863509, + 904003, 936143, 961638, 982021, 998555, 1012215, 1023728, 1033619, 1042267, 1049947, 1056858, 1063146, 1068921, 1074268, + 1079251, 0, 140032, 273125, 398127, 513842, 619085, 712840, 794463, 863859, 921556, 968647, 1006605, 1037045, + 1061517, 1081374, 1097712, 1111382, 1123022, 1133103, 1141970, 1149878, 1157014, 1163521, 1169505, 1175050, 0, 144511, + 282498, 412832, 534350, 645881, 746376, 835082, 911706, 976513, 1030319, 1074365, 1110125, 1139109, 1162709, 1182112, + 1198278, 1211952, 1223701, 1233948, 1243009, 1251122, 1258464, 1265171, 1271348 +]; +/// PMOS table, mos_tt at 50 C: Id(Vsg = ix * 62.5 mV, Vsd = iy * 62.5 mV) at index ix * 25 + iy, same units (block grid_p). +pub const TT50_P : [625]u32 = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 4, 4, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 0, 23, 26, 26, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 0, 136, 151, 154, 155, 156, 157, 158, 158, 159, 160, 160, + 161, 161, 162, 162, 163, 163, 164, 164, 164, 165, 165, 166, 166, 0, + 706, 799, 819, 830, 839, 845, 852, 857, 862, 866, 871, 875, 879, 882, + 886, 890, 893, 896, 899, 902, 906, 908, 911, 914, 0, 2681, 3191, 3314, + 3382, 3434, 3477, 3514, 3546, 3575, 3602, 3627, 3651, 3672, 3693, 3713, 3732, 3750, + 3767, 3784, 3800, 3816, 3831, 3846, 3861, 0, 6823, 8879, 9398, 9647, 9824, 9965, + 10085, 10189, 10282, 10366, 10444, 10515, 10582, 10645, 10704, 10760, 10813, 10865, 10914, 10961, + 11007, 11051, 11094, 11135, 0, 12810, 18421, 20184, 20880, 21308, 21631, 21895, 22121, 22320, + 22497, 22659, 22807, 22944, 23072, 23192, 23305, 23413, 23515, 23613, 23707, 23797, 23884, 23968, + 24049, 0, 19906, 31058, 35744, 37556, 38485, 39113, 39601, 40005, 40352, 40658, 40933, 41183, + 41413, 41626, 41825, 42012, 42188, 42355, 42515, 42667, 42812, 42952, 43087, 43217, 0, 27525, + 45521, 55101, 59377, 61388, 62573, 63413, 64075, 64626, 65103, 65524, 65903, 66248, 66565, 66860, + 67135, 67393, 67637, 67868, 68089, 68299, 68500, 68694, 68880, 0, 35279, 60764, 76754, 85262, + 89468, 91743, 93195, 94256, 95100, 95807, 96418, 96959, 97446, 97890, 98298, 98677, 99031, 99364, + 99679, 99977, 100260, 100531, 100790, 101039, 0, 42926, 76099, 99352, 113670, 121587, 125905, 128477, + 130209, 131501, 132536, 133404, 134156, 134821, 135420, 135966, 136468, 136934, 137370, 137779, 138166, 138532, + 138881, 139213, 139532, 0, 50324, 91110, 121956, 143129, 156252, 163907, 168437, 171326, 173349, 174884, + 176121, 177159, 178058, 178853, 179568, 180219, 180818, 181374, 181893, 182380, 182840, 183276, 183691, 184087, + 0, 57393, 105561, 143996, 172533, 191993, 204336, 211938, 216737, 219959, 222284, 224074, 225524, 226742, + 227797, 228728, 229564, 230325, 231024, 231672, 232277, 232844, 233380, 233887, 234369, 0, 64098, 119330, + 165154, 201176, 227637, 245790, 257660, 265340, 270443, 274010, 276649, 278706, 280380, 281790, 283009, 284084, + 285049, 285924, 286728, 287472, 288165, 288814, 289427, 290007, 0, 70427, 132365, 185276, 228660, 262387, + 287103, 304300, 315914, 323755, 329186, 333105, 336067, 338403, 340315, 341927, 343321, 344549, 345648, 346645, + 347558, 348402, 349187, 349922, 350614, 0, 76384, 144659, 204304, 254787, 295765, 327433, 350740, 367262, + 378771, 386827, 392600, 396879, 400171, 402796, 404956, 406783, 408362, 409752, 410995, 412119, 413148, 414096, + 414978, 415802, 0, 81984, 156228, 222238, 279487, 327525, 366245, 396136, 418337, 434396, 445903, 454211, + 460335, 464976, 468605, 471529, 473949, 476000, 477775, 479337, 480731, 481991, 483141, 484200, 485183, 0, + 87245, 167103, 239111, 302765, 357574, 403246, 439921, 468319, 489663, 505418, 516998, 525581, 532060, 537067, + 541037, 544266, 546955, 549241, 551222, 552965, 554520, 555923, 557201, 558377, 0, 92187, 177324, 254973, + 324667, 385911, 438309, 481760, 516632, 543796, 564485, 580054, 591757, 600631, 607467, 612839, 617153, 620694, + 623659, 626190, 628386, 630319, 632043, 633597, 635012, 0, 96832, 186930, 269883, 345261, 412586, 471416, + 521493, 562913, 596223, 622376, 642570, 658042, 669905, 679078, 686271, 692006, 696666, 700523, 703772, 706555, + 708975, 711107, 713008, 714721, 0, 101201, 195965, 283904, 364626, 437681, 502615, 559078, 606971, 646567, + 678539, 703871, 723695, 739134, 751184, 760662, 768208, 774305, 779310, 783486, 787024, 790066, 792717, 795056, + 797143, 0, 105314, 204469, 297097, 382843, 461290, 531991, 594551, 648739, 694614, 732594, 763429, 788094, + 807647, 823104, 835357, 845141, 853038, 859493, 864842, 869338, 873169, 876476, 879365, 881918, 0, 109190, + 212480, 309523, 399993, 483509, 559647, 627992, 688232, 740271, 784309, 820870, 850742, 874867, 894226, 909738, + 922207, 932297, 940539, 947347, 953039, 957856, 961982, 965558, 968691 +]; +/// NMOS drain current in pA at the first 200 held-out R2 points (block r2_n). +pub const TT50_R2_N : [200]u32 = [ + 200048112, 50126722, 7026533, 938, 222798891, 77658568, 10477240, 3088, 100084627, 92219380, 9997242, 8989, 217049387, 106014740, + 15019785, 8330, 253613133, 93213556, 20587875, 32020, 277166538, 117352448, 16539036, 98343, 187271842, 134231068, 26463033, 265622, + 276140417, 58700181, 34593214, 271214, 309816174, 139145340, 43057380, 782070, 80080260, 163074162, 39770506, 1727297, 266935049, 181769823, + 51931432, 1321294, 335832274, 129423270, 63027035, 3151538, 367847415, 189034542, 26081463, 5607540, 510, 213476503, 70848398, 8699383, + 1772, 42019674, 85753193, 7967766, 5287, 195451474, 99451312, 12628580, 3891, 242418935, 76128638, 17823956, 17856, 267372942, + 109440209, 5958614, 57709, 137941216, 126712850, 22904664, 162311, 258251621, 11655086, 30746619, 151549, 298474162, 125563850, 38881461, + 496237, 6198062, 154277841, 32479760, 1203774, 227713909, 173424891, 46880938, 2392177, 319912198, 90556178, 57794136, 2265618, 356421237, + 176590894, 68713035, 4371643, 121620325, 204011936, 63473418, 7110107, 1007, 224569252, 79378904, 6065992, 3094, 165784068, 93008799, + 10442362, 8714, 230297285, 49652815, 15264187, 9750, 257426297, 101208776, 20681419, 33387, 78691325, 119240006, 19502576, 97479, + 235254212, 135126489, 27094351, 80254, 286448233, 106332434, 34898739, 300911, 313500922, 145227715, 19165047, 806038, 177940648, 165085097, + 41950410, 1733992, 300814791, 41417209, 52723581, 1551337, 344448583, 161192852, 63391384, 3313757, 46859854, 194314677, 53873422, 5705161, + 565, 215572459, 73076816, 2952193, 1800, 125695800, 86687540, 8460908, 5161, 216476650, 12950284, 12908415, 5199, 247256309, + 92056358, 17950613, 19051, 11439468, 111792343, 16069993, 57747, 204424979, 127771105, 23637235, 3149, 273303081, 77800718, 31111604, + 174539, 303006781, 135634811, 39017049, 517945, 118007549, 156729886, 37070398, 1212487, 276612012, 174588281, 47815771, 1000447, 331655955, + 140055750, 58236851, 2427887, 360714290 +]; +/// PMOS drain current in pA at the first 200 held-out R2 points (block r2_p). +pub const TT50_R2_P : [200]u32 = [ + 141671084, 33143066, 1688061, 92, 155913435, 48218135, 2807729, 214, 73251062, 56247334, 3821528, 496, 158831505, 64175353, + 5780663, 1063, 184252726, 62705824, 8162362, 2510, 199664507, 76552417, 8744213, 5873, 138234060, 86333705, 13088917, 13730, + 206387124, 42070585, 17002608, 29932, 230550032, 96145796, 21298399, 69712, 59762125, 110516128, 23074507, 158905, 200755219, 121855333, + 29252362, 296158, 256326986, 93037535, 35153868, 644695, 280066244, 134079868, 17853145, 1232385, 58, 149394957, 44101884, 2146387, + 135, 30979616, 52211057, 2969023, 311, 142724324, 59971519, 4695935, 585, 176213542, 51978872, 6823769, 1563, 192530020, + 71526847, 3490289, 3657, 101619235, 81412135, 11220248, 8549, 192462522, 8476856, 14903965, 18598, 222069605, 87186759, 18942605, + 43682, 4664023, 104678244, 19277011, 100818, 169933093, 116152736, 26321091, 226593, 243492028, 65409536, 32027420, 435874, 271176701, + 125476774, 37990682, 875401, 91166448, 142842234, 39746525, 1600926, 85, 155725470, 48276706, 2222018, 196, 120776706, 55885799, + 3746321, 457, 167488642, 34612815, 5625781, 973, 185351642, 66358585, 7945700, 2278, 58175071, 76572249, 9471371, 5323, + 174542467, 85901744, 12949618, 11355, 213072231, 74391540, 16732355, 27267, 231615099, 98728303, 12069521, 63440, 132125458, 110512429, + 23497767, 145029, 227953838, 30200387, 29035698, 287563, 261868536, 114756527, 34767900, 604973, 35334430, 136187703, 34294463, 1162777, + 53, 149384125, 44431661, 1207458, 123, 91556799, 51918250, 2927243, 285, 157461965, 9232753, 4565693, 605, 178074055, + 60696231, 6632207, 1420, 8530360, 71801414, 7787378, 3316, 150864846, 81055199, 11138313, 508, 203177067, 55030709, 14667964, + 16977, 223789986, 92469902, 18616351, 39712, 87631741, 104921638, 20748152, 91783, 208308676, 115748586, 26178016, 185186, 251884412, + 99947686, 31682218, 407368, 272815709 +]; + +; --- the accessors and the tests ------------------------------------------------------------------------ + +/// The largest of the 25 entries at Vds = 0 (the first row of a gate column is index ix * 25). +pub fn at_vds0(t: []const u32) -> u32 { + var worst : u32 = 0; + var ix : usize = 0; + while (ix < 25) { + if (t[ix * 25] > worst) { worst = t[ix * 25]; } + ix = ix + 1; + } + return worst; +} + +test the_50c_numbers_are_the_numbers_ngspice_printed { + var s : u64 = fold_arr(&TT50_N, 625, 0); + s = fold_arr(&TT50_P, 625, s); + assert_eq(s, PIN_TAB50); + s = fold_arr(&TT50_R2_N, 200, 0); + s = fold_arr(&TT50_R2_P, 200, s); + assert_eq(s, PIN_HELD50); + // A pin sees one changed number: the NMOS table with its last entry one lower is another word. + var t : [625]u32 = [0] ** 625; + var i : usize = 0; + while (i < 625) { + t[i] = TT50_N[i]; + i = i + 1; + } + t[624] = t[624] - 1; + assert fold_arr(&t, 625, 0) != fold_arr(&TT50_N, 625, 0); + assert_eq(R2_COUNT_50, 200); +} + +test the_tables_are_monotone_and_vanish_at_vds_0 { + assert(monotone(&TT50_N)); + assert(monotone(&TT50_P)); + // Zero at Vds = 0 in every column (the deck holds the gate current out of the drain at 0), off current of at most 3 units (0.9 nA). + assert_eq(at_vds0(&TT50_N), 0); + assert_eq(at_vds0(&TT50_P), 0); + assert_eq(TT50_N[24], 3); + assert_eq(TT50_P[24], 0); + // The largest entry is at (1.5 V, 1.5 V), below the 2^23 the solver's u64 interpolation allows. + assert_eq(TT50_N[624], 1271348); + assert_eq(TT50_P[624], 968691); + assert TT50_N[624] < 8388608; + assert TT50_P[624] < 8388608; +} + +test fifty_degrees_move_the_tables_the_way_the_model_moves_them { + // Variables, not constants: a constant assert is folded by the compiler and counts as no assert at all. + var n50 : u32 = TT50_N[624]; + var n27 : u32 = TT_N[624]; + var p50 : u32 = TT50_P[624]; + var p27 : u32 = TT_P[624]; + // Saturation current falls: NMOS -5.8 %, PMOS -4.2 % against 27 C. + assert n50 < n27; + assert p50 < p27; + assert n50 * 1000 > n27 * 940; + assert n50 * 1000 < n27 * 945; + assert p50 * 1000 > p27 * 955; + assert p50 * 1000 < p27 * 960; + // Weak inversion rises (NMOS at Vgs = 0.25 V, Vds = 1.5 V: 1725 to 2774; Vgs = 0.3125 V: 6937 to 9004). + var w50 : u32 = TT50_N[4 * 25 + 24]; + var w27 : u32 = TT_N[4 * 25 + 24]; + assert_eq(w27, 1725); + assert_eq(w50, 2774); + assert w50 > w27; + var v50 : u32 = TT50_N[5 * 25 + 24]; + var v27 : u32 = TT_N[5 * 25 + 24]; + assert v50 > v27; + var pw50 : u32 = TT50_P[4 * 25 + 24]; + var pw27 : u32 = TT_P[4 * 25 + 24]; + assert pw50 > pw27; + var pv50 : u32 = TT50_P[5 * 25 + 24]; + var pv27 : u32 = TT_P[5 * 25 + 24]; + assert pv50 > pv27; + // The curves cross between Vgs = 0.5 V (node 8) and 0.75 V (node 12): above that the 50 C current is the lower. + var a50 : u32 = TT50_N[8 * 25 + 24]; + var a27 : u32 = TT_N[8 * 25 + 24]; + assert a50 > a27; + var b50 : u32 = TT50_N[12 * 25 + 24]; + var b27 : u32 = TT_N[12 * 25 + 24]; + assert b50 < b27; +} diff --git a/specs/analog/sar_comparator.t27 b/specs/analog/sar_comparator.t27 index c754f5604c..c16a641f8d 100644 --- a/specs/analog/sar_comparator.t27 +++ b/specs/analog/sar_comparator.t27 @@ -146,6 +146,11 @@ ; the table's range clamp (x and y to Vdd) applies in table mode only, the analytic law (mode 1) has no range; this comparator never leaves it, ; so no result here moves: the 20 tests and every pinned fold are unchanged. ; - +; M4B-PSP REUSES IT TOO (specs/analog/sar_comparator_psp.t27, #10856): the comparator on the PSP103 tables of M3b, whose NMOS (W 1 um) and PMOS (W 3 um) +; tables differ where the square law's one table serves both types. sc_stamp_dev_np, sc_stamp_np, sc_iter_np, sc_step_np and sc_run_np take the NMOS +; table and the PMOS table as two arguments; sc_stamp_dev, sc_stamp, sc_iter, sc_step and sc_run are one-line forms that pass the one table twice, so the +; 20 tests and every pinned fold are unchanged here as well. The real twin (sc_*_f) and sc_board_step keep their one table. +; - ; WHAT A PASS SAYS. A comparator of eight nodes and fifteen square-law devices, each read from M3a's bilinear table, is solved by ; backward Euler with three fixed Newton iterations of an 8 x 8 pivoted elimination in unsigned sign-magnitude words, last ; residual at most 2 units (0.12 uV) at all 840 steps of six runs; it decides +-2 mV and +-0.5 mV the right way, with the first @@ -345,12 +350,22 @@ pub fn sc_stamp_chan(t: []const u32, kind: u32, d: u32, g: u32, s: u32, vd: u32, } } -/// Stamp device e of the comparator's table: its terminals are resolved from the state, the rails, the clock and the two inputs. -pub fn sc_stamp_dev(t: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, m: u32, reg: []u64) { +/// Stamp device e of the comparator, an NMOS from the table tn and a PMOS from the table tp: its terminals are resolved from the state, +/// the rails, the clock and the two inputs. (M4b-psp, #10856: the PSP103 tables of the two types differ; the square law's one table serves both.) +pub fn sc_stamp_dev_np(tn: []const u32, tp: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, m: u32, reg: []u64) { const d : u32 = SC_DV_D[e]; const g : u32 = SC_DV_G[e]; const s : u32 = SC_DV_S[e]; - sc_stamp_chan(t, SC_DV_KIND[e], d, g, s, sc_term_v(d, v, clk, vdp, vdn), sc_term_v(g, v, clk, vdp, vdn), sc_term_v(s, v, clk, vdp, vdn), m, reg); + if (SC_DV_KIND[e] == 0) { + sc_stamp_chan(tn, 0, d, g, s, sc_term_v(d, v, clk, vdp, vdn), sc_term_v(g, v, clk, vdp, vdn), sc_term_v(s, v, clk, vdp, vdn), m, reg); + } else { + sc_stamp_chan(tp, 1, d, g, s, sc_term_v(d, v, clk, vdp, vdn), sc_term_v(g, v, clk, vdp, vdn), sc_term_v(s, v, clk, vdp, vdn), m, reg); + } +} + +/// Stamp device e of the comparator's table (one table for both types): sc_stamp_dev_np with tn = tp = t. +pub fn sc_stamp_dev(t: []const u32, e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, m: u32, reg: []u64) { + sc_stamp_dev_np(t, t, e, v, clk, vdp, vdn, m, reg); } /// Zero all the registers of a stamp. @@ -370,7 +385,8 @@ pub fn sc_stamp_cap(reg: []u64, n: u32, c: u64, v: u32, v0: u32) { } /// All registers of the Newton system at state v, for the step from v0: G = C (v - v0) + (currents out - currents in), J = dG/dv. -pub fn sc_stamp(t: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32, reg: []u64) { +/// The NMOS read the table tn, the PMOS the table tp. +pub fn sc_stamp_np(tn: []const u32, tp: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32, reg: []u64) { sc_clear(reg); var n : u32 = 0; while (n < SC_NN) { @@ -379,11 +395,16 @@ pub fn sc_stamp(t: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: } var e : usize = 0; while (e < (SC_NDEV as usize)) { - sc_stamp_dev(t, e, v, clk, vdp, vdn, mult[e], reg); + sc_stamp_dev_np(tn, tp, e, v, clk, vdp, vdn, mult[e], reg); e = e + 1; } } +/// The same with one table for both types: sc_stamp_np with tn = tp = t. +pub fn sc_stamp(t: []const u32, v: []const u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32, reg: []u64) { + sc_stamp_np(t, t, v, v0, clk, vdp, vdn, mult, reg); +} + /// The residual of node n in voltage units: round(|G| 2^20 / J_nn), the Newton move that is still owed there. pub fn sc_resid_at(reg: []const u64, n: u32) -> u32 { const gp : u64 = reg[(SC_RGP + n) as usize]; @@ -550,20 +571,25 @@ pub fn sc_apply(v: []u32, x: []const u64) { sc_apply_lim(v, x, VDD); } -/// One Newton iteration on the eight nodes at once: stamp, load, eliminate, back-substitute, apply. -pub fn sc_iter(t: []const u32, v: []u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32) { +/// One Newton iteration on the eight nodes at once: stamp, load, eliminate, back-substitute, apply (NMOS from tn, PMOS from tp). +pub fn sc_iter_np(tn: []const u32, tp: []const u32, v: []u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32) { var reg : [144]u64 = [0] ** 144; var a : [72]u64 = [0] ** 72; var x : [8]u64 = [0] ** 8; - sc_stamp(t, v, v0, clk, vdp, vdn, mult, ®); + sc_stamp_np(tn, tp, v, v0, clk, vdp, vdn, mult, ®); sc_load(®, &a); sc_eliminate(&a); sc_backsub(&a, &x); sc_apply(v, &x); } -/// One backward Euler step to step j from the state in v (updated in place): k Newton iterations, then the residual. -pub fn sc_step(t: []const u32, v: []u32, j: u32, vdp: u32, vdn: u32, k: u32, mult: []const u32) -> u32 { +/// The same with one table for both types. +pub fn sc_iter(t: []const u32, v: []u32, v0: []const u32, clk: u32, vdp: u32, vdn: u32, mult: []const u32) { + sc_iter_np(t, t, v, v0, clk, vdp, vdn, mult); +} + +/// One backward Euler step to step j from the state in v (updated in place): k Newton iterations, then the residual (NMOS from tn, PMOS from tp). +pub fn sc_step_np(tn: []const u32, tp: []const u32, v: []u32, j: u32, vdp: u32, vdn: u32, k: u32, mult: []const u32) -> u32 { var v0 : [8]u32 = [0] ** 8; var n : u32 = 0; while (n < SC_NN) { @@ -573,14 +599,19 @@ pub fn sc_step(t: []const u32, v: []u32, j: u32, vdp: u32, vdn: u32, k: u32, mul const clk : u32 = sc_clk_at(j); var i : u32 = 0; while (i < k) { - sc_iter(t, v, &v0, clk, vdp, vdn, mult); + sc_iter_np(tn, tp, v, &v0, clk, vdp, vdn, mult); i = i + 1; } var reg : [144]u64 = [0] ** 144; - sc_stamp(t, v, &v0, clk, vdp, vdn, mult, ®); + sc_stamp_np(tn, tp, v, &v0, clk, vdp, vdn, mult, ®); return sc_resid(®); } +/// The same with one table for both types. +pub fn sc_step(t: []const u32, v: []u32, j: u32, vdp: u32, vdn: u32, k: u32, mult: []const u32) -> u32 { + return sc_step_np(t, t, v, j, vdp, vdn, k, mult); +} + /// The step the board runs: SC_K Newton iterations. The trip count is a constant, so the loop unrolls (`t27b verilog` takes a /// `while` only with a static bound); sc_step takes k as an argument so that the tests can run K = 1 and K = 2. pub fn sc_board_step(t: []const u32, v: []u32, j: u32, vdp: u32, vdn: u32, mult: []const u32) -> u32 { @@ -616,8 +647,8 @@ pub fn sc_rest(vdp: u32, vdn: u32, v: []u32) { } /// The run: SC_N steps from rest, inputs VCM + h and VCM - h (exchanged when swap). out gets the 8 nodes of every state, step 0 first; -/// res the residual of every step. Returns the order-sensitive fold of every sample. -pub fn sc_run(t: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out: []u32, res: []u32) -> u64 { +/// res the residual of every step. Returns the order-sensitive fold of every sample. NMOS read tn, PMOS tp. +pub fn sc_run_np(tn: []const u32, tp: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out: []u32, res: []u32) -> u64 { var vdp : u32 = SC_VCM + h; var vdn : u32 = SC_VCM - h; if (swap) { @@ -634,7 +665,7 @@ pub fn sc_run(t: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out } var j : u32 = 1; while (j <= SC_N) { - res[(j - 1) as usize] = sc_step(t, &v, j, vdp, vdn, k, mult); + res[(j - 1) as usize] = sc_step_np(tn, tp, &v, j, vdp, vdn, k, mult); n = 0; while (n < SC_NN) { out[(j * SC_NN + n) as usize] = v[n as usize]; @@ -646,6 +677,11 @@ pub fn sc_run(t: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out return s; } +/// The run with one table for both types: sc_run_np with tn = tp = t. +pub fn sc_run(t: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out: []u32, res: []u32) -> u64 { + return sc_run_np(t, t, h, swap, k, mult, out, res); +} + /// The series of one node over the run (sel < 8), or of |dp - dn| (sel = 8): SC_N + 1 samples. pub fn sc_pick(out: []const u32, sel: u32, s: []u32) { var j : u32 = 0; diff --git a/specs/analog/sar_comparator_psp.t27 b/specs/analog/sar_comparator_psp.t27 new file mode 100644 index 0000000000..c367472d87 --- /dev/null +++ b/specs/analog/sar_comparator_psp.t27 @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/sar_comparator_psp.t27 -- the SAR ADC dynamic comparator on the IHP SG13G2 PSP103 current tables at 50 C, judged by ngspice +; Analog M4b-psp (#10856, epic #10486; M4b is #10583, the PSP103 tables are M3b, #10516; M4e, #10818, fits a decision offset of 6.7 mV and asks +; where it comes from). M4b (specs/analog/sar_comparator.t27) solves the golden deck's StrongARM latch, eight nodes, by a multi-node Newton step on a +; SQUARE-LAW table. Here the same solver runs on the foundry's model: Id(Vgs, Vds) of sg13_lv_nmos (W 1.0 um) and sg13_lv_pmos (W 3.0 um), L 0.45 um, +; PSP 103.6 through ngspice-47, tt, at 50 C, the temperature of the golden deck (specs/analog/mos_psp103_50c_data.t27, deck psp103_tab_tt50.cir; +; M3b tabulated 27 C). The solver is reused, not copied: in sar_comparator.t27 the device stamp, the stamp, the Newton iteration, the step and the +; run gain `_np` forms that take the NMOS table and the PMOS table as two arguments (the square law's one table serves both types; PSP103's two do +; not), and the old names delegate to them with one table twice: every old test and pin is unchanged. Everything else is here: the unit +; conversion, the runs, the flip-point bisection, the pinned ngspice numbers and the tests. The decks are in specs/analog/decks/ (sar_cmp_psp_*.cir, +; psp103_tab_tt50.cir); the golden deck itself is fetched at run time and never vendored. +; - +; WHAT WAS ASKED AND WHAT CAME OUT, first. M4e needs a 6.7 mV decision offset to match the golden run's codes 30 of 31; reading the golden run's +; decisions put the latch's decision point at +2.5 .. +3.1 mV. The issue asks the PSP103 latch to PREDICT that threshold. It does not, because the +; latch has none: the circuit is mirror-symmetric (the pair XM3, XM4, the pull-ups XM8, XM9 and XM6, XM7, the cross-coupled XM10, XM11, the +; resets XM12, XM13 and the loads), so with equal devices its flip point -- the input difference at which dp and dn trade places -- is ZERO, and +; the measurements say so: model +0.12 uV (rounding, 2 units of 2^-24 V), ngspice with PSP103 within 0.12 uV of zero, whether the charge is swamped +; or real. The golden comparator WITH ITS LOADS (dynamic_comparator_top: an inverter on dp and on dn, then the NAND "Valid") has a flip point +; of -0.153 mV, from the NAND's two inputs not being the same load; it favours dp, the golden run's reading is positive (dp needs VDP above VDN), +; so even the sign differs. The offset the converter shows is not the DC threshold of the latch: it is what the converter does around it (kickback +; at the clock edge moves VDP and VDN by 12 and 25 mV in 2 ns, per M4e) or an imbalance of the pair, which this file measures: +; flip point of an unequal pair (XM4 at m = 35 or 34 instead of 36), VDP - VDN: +; ngspice (bulks tied, charge swamped) model model against ngspice +; m = 35 +4.2665 mV (71579 .. 71581 units) +4.176 mV (70064) -2.1 % +; m = 34 +8.689 mV (145772 .. 145774 units) +8.513 mV (142816) -2.0 % +; so 6.7 mV is an imbalance of 4.4 % of the pair's current (m of XM4 = 34.42 of 36), a mismatch of the pair or of the input stages, and a predictor +; of the offset has to be given THAT, not the latch. The threshold is predicted within 2.1 % as a function of the imbalance, and is zero without. +; - +; THE UNIT. A table entry is Id * dt / C in units of 2^-24 V. M3b's tables have dt / C = 200 V/A (0.1 ps on 0.5 fF, or 2 ns on 10 pF); the +; comparator's charge unit is 0.125 fF (SC_CUNIT_AF) at dt = 0.1 ps, so its dt / C is 800 V/A (SC_STEP_V_PER_A): a word of the solver is exactly 4 +; times an M3b word (sp_scale; the rounding of M3b's 0.298 nA becomes 1.2 nA, a ten-thousandth of the 100 uA the devices work at). The largest word, 4 x +; 1271348 = 5085392 (tt50 NMOS), stays under the 2^23 the solver's u64 interpolation allows. THE DEVICE SIZES. The table devices are the golden +; comparator's: NMOS W 1.0 um and PMOS W 3.0 um at L 0.45 um, ng 1. The comparator's devices are those with a multiplier m (6 .. 36), and PSP103 +; multiplies every current by m exactly, so the table needs no width correction, only the multiplier SC_DV_M the solver already applies: nothing +; of narrow-width or edge effects is assumed because no width differs. THE TEMPERATURE is the deck's own, 50 C (`.options TEMP = 50`), tabulated +; with M3b's recipe; the same ngspice and checkout reproduce M3b's 27 C tables word for word. 50 C moves the tables: the saturation current falls 5.8 % +; (NMOS) and 4.2 % (PMOS), the weak-inversion current rises 1.6 times at Vgs = 0.25 V. The tables' held-out accuracy at 50 C (first 200 R2 points +; per device): absolute 4.97e-7 A (NMOS) and 3.92e-7 A (PMOS), relative at least 100 uA 0.34 % and 0.14 %, the size of M3b's (0.56 % and 0.41 %). +; - +; THREE REFERENCES, all ngspice-47 with PSP103 (IHP-Open-PDK 21133f21, mos_tt, OSDI), 50 C, the golden subcircuits read at run time from the file +; named by SAR_GOLDEN_SUBCKTS (the subcircuit section of the fetched deck: `sed -n '/^.subckt tracking_switch/,$p' top.spice | grep -v -i -E +; '^\.(end|global)([[:space:]]|$)'`; the pattern `^.(end|global)` of M4d's header drops the `.ends` lines too): +; R2 sar_cmp_psp_r2.cir the golden dynamic_comparator as it is, the node capacitances of M4b (4 fF on ap an dp dn, 6 fF on net1, 2 fF on net2, +; net3, n_clkc) times 1e6 and the step times 1e6 (0.1 us): the same dt / C, and PSP103's own charge (a few hundred fF) +; is 1e-4 of the lumped one. The CURRENTS alone are compared. Between scales 1e4, 1e5, 1e6 the decision time moved by +; 0.83 % and 0.09 %: the charge left is about 0.01 %. Fixed-step backward Euler on the model's grid (the M4b recipe). +; R2b sar_cmp_psp_r2b.cir R2 with every bulk tied to its source, the one thing a 2-D table Id(Vgs, Vds) models. The golden deck ties the +; NMOS bulks to ground and the PMOS bulks to VDD, so the input pair (source net1, 0.15 .. 0.4 V above ground) and XM8, XM9 +; (sources below VDD) have a body effect the tables cannot have. +; R1 sar_cmp_psp_real.cir the circuit as the converter has it: all of PSP103's charge, 4 fF on dp and dn, an ideal 1.6 ps clock, the inputs held, +; ngspice's own trapezoidal steps. Xb is the bare latch, Xtop is dynamic_comparator_top. +; The rest is M4b's (rest value, clock of 16 steps, 140 steps, the two outputs dp and dn). The rest of net1 is an initial condition, 0.401 V, both in the +; model and in the decks (PSP103's own operating point puts floating net1 at 0.71 V on gmin). +; - +; THE RESULT, K = 3, fixed point, tt 50 C tables, times in steps of 0.1 ps after the clock's 50 % point: +; decision (first output) resolution (|dp - dn| = Vdd / 2) +; model R2b (tied) error R2 (golden) error model R2b error R2 error sign +; +-2 mV 53.502 53.854 -0.65 % 57.198 -6.46 % 71.856 72.309 -0.63 % 74.476 -3.52 % right +; +-0.5 mV 54.621 54.937 -0.58 % 58.442 -6.54 % 81.255 81.726 -0.58 % 83.462 -2.64 % right +; and 0.125 mV, 31 uV, 10 uV, 1 uV (h = 1049, 262, 84, 8): decision 55.00, 55.13, 55.16, 55.17 (R2b 55.34, 55.46, 55.48, 55.49), resolution 90.63, 100.02, 107.70, 123.44 +; (R2b 91.16, 100.60, 108.34, 124.33); at 0.12 uV (1 unit of h) the sign is right and the resolution is not reached in 140 steps, in ngspice either. +; All 14 runs (7 inputs, 2 orientations) decide as R2 and R2b do. The bounds are set from these numbers: the decision time against R2b within 0.66 % +; and the resolution time within 0.72 %, against R2 within 6.7 % and 3.6 %, both from both sides. +; - +; THE ERRORS, kept apart (W10): +; arithmetic not re-measured here: the real twin of the iteration (sc_run_f) has no two-table form yet. What is checked: the last residual is at +; most 3 units (0.18 uV) at all 140 steps of all 14 runs (SP_RES_BOUND; M4b's square law had 2), K = 4 gives the same decision time to +; 1e-3 of a step and the same resolution time to 7e-3 of 100 steps, K = 2 leaves 155 units and K = 1 leaves 20750. +; table the model against R2b: -0.65 % (decision), -0.63 % (resolution); the eight nodes at the eight recorded steps within 15.9 mV (dp), the +; same size as the square law's 15 mV in M4b: the 62.5 mV cells and the constant slopes over a cell. +; body effect R2b against R2, ngspice alone: the golden deck is 6.2 .. 6.5 % slower to decide and 3.0 % .. 0.3 % slower to resolve. This is the +; largest error of the model against the golden deck (the nodes: 150 mV at net2 after the decision, 113 mV at dp); the tables carry no +; Vsb. A 3-D table, or a threshold-shift model, is what would take it out. The two compose: (1 - 0.0065) / (1 + 0.0621) - 1 = -6.46 %. +; charge R1 against R2: the bare golden comparator with its own charge decides 523.0 ps after the clock at +2 mV, the model 5.35 ps: THE +; MODEL IS 96 .. 98 TIMES TOO FAST in the decision and 155 .. 183 times in the resolution (1111.9 ps at +2 mV against 7.19 ps). The +; devices' own gate and junction capacitance (m up to 36 on W 1 .. 3 um) outweighs the 2 .. 6 fF of the model. Until the charge tables +; (#10777) exist the model's time is the time of the currents; the times above are judged against the charge-swamped decks. +; discretization shared with ngspice by construction in R2 and R2b (backward Euler on the same grid). +; - +; THE NEGATIVE CONTROLS, tests that pass by seeing the failure. The decision time is what tells the right tables from the wrong ones; the sign and the +; flip point cannot, because the symmetric latch decides the right way and flips at zero for ANY pair of tables (the test says so): +; ss at 27 C (M3b's slow corner) in the run 14.1 % late (decision), 11.5 % late (resolution) against R2b (bounds 0.66 %, 0.72 %); 7.5 % late against R2 +; tt at 27 C (the right corner, the wrong temperature) 5.0 % early and 6.0 % early: 7.5 and 8 times the bounds +; the PMOS table in the NMOS slot and the reverse 20.7 % late +; the inputs exchanged the sign check fails (dn wins for VDP > VDN) +; K = 1, K = 2 the residual bound of 3 units is broken by 6900 and 52 times +; - +; THE HARDWARE. The functions from sp_scale on are test-side (f64 and comparison); the board's step is M4b's (`sc_run_np` and below take bool and +; unsigned words), with two 625-word tables a board would load at run time. No board top, no clock, no LUT, DSP, BRAM, Fmax or UART line is in +; this file or claimed. +; - +; WHAT A PASS SAYS. The comparator of eight nodes and fifteen devices, each read from the tt 50 C PSP103 tables (NMOS and PMOS tables, M4b's solver +; with three Newton iterations, last residual at most 3 units), decides +-2 mV, +-0.5 mV and every smaller input down to 0.12 uV the way ngspice +; does; its decision time is within 0.66 % and its resolution time within 0.72 % of the PSP103 comparator with the bulks at the sources when the +; charge is swamped, and 6.7 % and 3.6 % early against the golden deck itself, 6.2 % of which is the body effect the tables do not carry; its flip point +; is zero to 0.12 uV and the pair-imbalance flip points are within 2.1 % of ngspice's; the tables of the wrong corner, the wrong temperature or the +; wrong slots, and the wrong number of Newton iterations, are refused. It does NOT predict the 6.7 mV of M4e (see the top). +; WHAT IT DOES NOT SAY. Anything about charge: the real comparator is 98 times slower than the model, and the model's picoseconds are the currents'. +; Anything about the body effect beyond its measure (6.2 % of the decision time). The converter's offset: the kickback at the clock edge, the +; input stages, the delay-cell clock and the DAC's settling are not here, and the flip point of the isolated latch says nothing about them. The +; top's -0.15 mV is measured, not modelled (the inverters, the NAND and their charge are not in the model). Another corner or temperature than +; tt 50 C, another W, L or supply, noise, or Monte Carlo mismatch (the imbalance is a deterministic m, not a distribution). The arithmetic error +; against a real twin (above). A board, power, or speed against SPICE. 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 AnalogSarComparatorPsp; + +use analog::linear::{fabs64}; +use analog::mos_table::{TBL_LEN}; +use analog::mos_psp103::{hold_stats, Hold}; +use analog::mos_psp103_data::{TT_N, TT_P, SS_N, SS_P, NMOS_W_NM, PMOS_W_NM, L_NM, DTC_V_PER_A}; +use analog::mos_psp103_50c_data::{TT50_N, TT50_P, TT50_R2_N, TT50_R2_P, TEMP50_C}; +use analog::sar_comparator::{SC_DV_M, SC_K, SC_N, SC_UPV, SC_STEP_V_PER_A, SC_W_N, SC_W_P, SC_L, SC_NONE, sc_run_np, sc_sign, sc_t_first, sc_t_res, sc_after_clock, sc_mult_pair, sc_dev_ng_u}; + +pub const KIND : str = "analog-cell-check"; +pub const ID : str = "analog/sar_comparator_psp"; +pub const REFERENCE : str = "uberWAVE wp2-parser/golden/top.spice, subckts dynamic_comparator and dynamic_comparator_top (GPL-2.0: read at run time, never vendored); IHP-Open-PDK 21133f21 PSP103 through ngspice-47, 50 C"; + +; --- the units: M3b's tables into M4b's solver --------------------------------------------------------------- +/// A table entry is Id * dt / C in units of 2^-24 V. M3b's tables have dt / C = 200 V/A (0.1 ps on 0.5 fF, or 2 ns on 10 pF); the comparator's +/// charge unit is 0.125 fF, so its dt / C is 800 V/A (SC_STEP_V_PER_A): every M3b word times 4 is the word the solver reads. +pub const SP_CONV : u32 = 4; +/// The largest differential half-input of the flip-point bisection, in units: 400000 is 23.8 mV on each side. +pub const SP_HMAX : u32 = 400000; +/// Newton residual bound with these tables: 3 units (0.18 uV); the square law's was 2. +pub const SP_RES_BOUND : u32 = 3; +/// The inputs of the decks' first list, h in units of 2^-24 V (VDP - VDN = 2h): 2 mV, 0.5 mV, 0.125 mV, 31 uV, 10 uV, 1 uV and 0.12 uV. Every +/// run but the last reaches a resolution time. +pub const SP_H : [7]u32 = [16777, 4194, 1049, 262, 84, 8, 1]; +/// M4e's fitted decision offset (mV) and the latch's own decision point seen in the golden run (mV), for the comparison only. +pub const M4E_FIT_MV : f64 = 6.7; +pub const M4E_RUN_LO_MV : f64 = 2.5; +pub const M4E_RUN_HI_MV : f64 = 3.1; + +/// dst = SP_CONV * src for a 625-word table. +pub fn sp_scale(src: []const u32, dst: []u32) { + var i : usize = 0; + while (i < TBL_LEN) { + dst[i] = src[i] * SP_CONV; + i = i + 1; + } +} + +/// The comparator's run (SC_N steps from rest, inputs VCM +- h, exchanged when swap) on M3b-unit tables: NMOS from tn_src, PMOS from tp_src, +/// each converted to the solver's unit. Returns the order-sensitive fold of every sample. +pub fn sp_run(tn_src: []const u32, tp_src: []const u32, h: u32, swap: bool, k: u32, mult: []const u32, out: []u32, res: []u32) -> u64 { + var tn : [625]u32 = [0] ** 625; + var tp : [625]u32 = [0] ** 625; + sp_scale(tn_src, &tn); + sp_scale(tp_src, &tp); + return sc_run_np(&tn, &tp, h, swap, k, mult, out, res); +} + +/// The decision time, in steps (0.1 ps) after the clock's 50 % point; -1.0 when the first output never reaches Vdd / 2. +pub fn sp_first(out: []const u32) -> f64 { + const q : u32 = sc_after_clock(sc_t_first(out)); + if (q == SC_NONE) { return 0.0 - 1.0; } + return (q as f64) / 4096.0; +} + +/// The resolution time (|dp - dn| through Vdd / 2), in steps after the clock; -1.0 when it is not reached in SC_N steps. +pub fn sp_res(out: []const u32) -> f64 { + const q : u32 = sc_after_clock(sc_t_res(out)); + if (q == SC_NONE) { return 0.0 - 1.0; } + return (q as f64) / 4096.0; +} + +/// (a - b) / b. +pub fn sp_rel(a: f64, b: f64) -> f64 { + return (a - b) / b; +} + +/// The largest residual of a run, in units. +pub fn sp_res_max(res: []const u32) -> u32 { + var worst : u32 = 0; + var i : usize = 0; + while (i < (SC_N as usize)) { + if (res[i] > worst) { worst = res[i]; } + i = i + 1; + } + return worst; +} + +/// Does dp win (dp above dn at the last step) for VDP - VDN = +2h units (swap false) or -2h (swap true)? +pub fn sp_wins(tn_src: []const u32, tp_src: []const u32, mult: []const u32, h: u32, swap: bool) -> bool { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + sp_run(tn_src, tp_src, h, swap, SC_K, mult, &out, &res); + return sc_sign(&out); +} + +/// The flip point of the circuit with the given multipliers, as the differential VDP - VDN in units of 2^-24 V at which the decision changes: +/// the smallest d (even; d = 2h) such that dp wins at d and above, found by bisection on h in [0, SP_HMAX] (the winner is monotone in h). +/// Positive when dp needs VDP above VDN, negative when dp wins at d = 0. +pub fn sp_flip(tn_src: []const u32, tp_src: []const u32, mult: []const u32) -> i64 { + if (sp_wins(tn_src, tp_src, mult, 0, false)) { + var lo : u32 = 0; + var hi : u32 = SP_HMAX; + while (hi - lo > 1) { + const mid : u32 = (lo + hi) / 2; + if (sp_wins(tn_src, tp_src, mult, mid, true)) { lo = mid; } else { hi = mid; } + } + return 0 - (2 * (lo as i64)); + } + var lo2 : u32 = 0; + var hi2 : u32 = SP_HMAX; + while (hi2 - lo2 > 1) { + const mid2 : u32 = (lo2 + hi2) / 2; + if (sp_wins(tn_src, tp_src, mult, mid2, false)) { hi2 = mid2; } else { lo2 = mid2; } + } + return 2 * (hi2 as i64); +} + +/// Absolute value of a signed 64-bit integer, as f64. +pub fn sp_abs_i(x: i64) -> f64 { + if (x < 0) { return ((0 - x) as f64); } + return (x as f64); +} + +; --- what ngspice printed: the decks in specs/analog/decks (ngspice-47, 2026-10-19) --------------------------------------------------- +/// The decision time (tfirst - tck) in steps of 0.1 ps for h = 16777 .. 1 of the first list, ngspice, the golden dynamic_comparator, PSP103, charge swamped by 1e6 (sar_cmp_psp_r2.cir). +pub const NG_R2_FIRST : [7]f64 = [57.1983, 58.4416, 58.8908, 59.0166, 59.0510, 59.0658, 59.0672]; +/// The resolution time (tres - tck) in steps, 0.0 where |dp - dn| does not reach Vdd / 2 in the 140 steps, ngspice, the golden dynamic_comparator, PSP103, charge swamped by 1e6 (sar_cmp_psp_r2.cir). +pub const NG_R2_RES : [7]f64 = [74.4761, 83.4620, 92.5650, 101.7020, 109.2020, 124.7090, 0.0000]; +/// dp at the last point (V), VDP - VDN = + 2h, ngspice, the golden dynamic_comparator, PSP103, charge swamped by 1e6 (sar_cmp_psp_r2.cir). +pub const NG_R2_DP_P : [7]f64 = [1.49997, 1.49991, 1.49964, 1.49844, 1.4948, 1.43877, 1.10887]; +/// dn at the last point (V), the same. +pub const NG_R2_DN_P : [7]f64 = [0.00281157, 0.00313156, 0.00388265, 0.00574544, 0.00997754, 0.0721324, 0.467656]; +/// dp at the last point (V), VDP - VDN = - 2h, ngspice, the golden dynamic_comparator, PSP103, charge swamped by 1e6 (sar_cmp_psp_r2.cir). +pub const NG_R2_DP_M : [7]f64 = [0.00281157, 0.00313156, 0.00388265, 0.00574544, 0.00997754, 0.0721324, 0.467656]; +/// dn at the last point (V), the same. +pub const NG_R2_DN_M : [7]f64 = [1.49997, 1.49991, 1.49964, 1.49844, 1.4948, 1.43877, 1.10887]; +/// The decision time (tfirst - tck) in steps of 0.1 ps for h = 16777 .. 1 of the first list, ngspice, the same with every bulk tied to its source (sar_cmp_psp_r2b.cir). +pub const NG_R2B_FIRST : [7]f64 = [53.8538, 54.9367, 55.3423, 55.4563, 55.4826, 55.4939, 55.4950]; +/// The resolution time (tres - tck) in steps, 0.0 where |dp - dn| does not reach Vdd / 2 in the 140 steps, ngspice, the same with every bulk tied to its source (sar_cmp_psp_r2b.cir). +pub const NG_R2B_RES : [7]f64 = [72.3091, 81.7257, 91.1590, 100.5990, 108.3390, 124.3340, 0.0000]; +/// dp at the last point (V), VDP - VDN = + 2h, ngspice, the same with every bulk tied to its source (sar_cmp_psp_r2b.cir). +pub const NG_R2B_DP_P : [7]f64 = [1.49998, 1.49993, 1.49971, 1.49872, 1.49555, 1.44328, 1.11351]; +/// dn at the last point (V), the same. +pub const NG_R2B_DN_P : [7]f64 = [0.00307869, 0.00349894, 0.00440576, 0.00649512, 0.0109783, 0.0743853, 0.472651]; +/// dp at the last point (V), VDP - VDN = - 2h, ngspice, the same with every bulk tied to its source (sar_cmp_psp_r2b.cir). +pub const NG_R2B_DP_M : [7]f64 = [0.00307869, 0.00349894, 0.00440576, 0.00649512, 0.0109783, 0.0743853, 0.472652]; +/// dn at the last point (V), the same. +pub const NG_R2B_DN_M : [7]f64 = [1.49998, 1.49993, 1.49971, 1.49872, 1.49555, 1.44328, 1.11351]; +/// ap an net1 net2 net3 dp dn n_clkc (V) at the eight checkpoint steps, sar_cmp_psp_r2.cir, VDP - VDN = +2 mV (h = 16777), 10 digits. +pub const NG_R2_CHK_2 : [64]f64 = [ + 1.321751806e+00, 1.318831079e+00, 1.779792671e-01, 1.499991510e+00, 1.499990872e+00, 1.705779482e-05, 1.640150461e-05, 1.323164582e+00, + 8.431106462e-01, 8.346779934e-01, 1.609569794e-01, 1.467464255e+00, 1.465936375e+00, 6.594137943e-02, 6.248237143e-02, 9.588429276e-01, + 5.657591817e-01, 5.546479181e-01, 1.549267554e-01, 1.387714782e+00, 1.384903555e+00, 3.146669462e-01, 3.019611411e-01, 7.531552274e-01, + 3.890144002e-01, 3.770876955e-01, 1.485302949e-01, 1.285090352e+00, 1.285433231e+00, 5.962807727e-01, 5.661190497e-01, 6.257763619e-01, + 2.363760775e-01, 2.283247700e-01, 1.268261250e-01, 1.101704162e+00, 1.145786673e+00, 8.098949443e-01, 7.129529861e-01, 5.098922048e-01, + 1.355719415e-01, 1.327223527e-01, 8.712274454e-02, 9.176799179e-01, 1.148954955e+00, 9.609288223e-01, 6.354953130e-01, 4.083269652e-01, + 5.009695758e-02, 4.984806717e-02, 3.563993465e-02, 6.823078064e-01, 1.427099920e+00, 1.371225198e+00, 1.576236617e-01, 2.681921650e-01, + 2.790678354e-03, 2.787466261e-03, 2.061700727e-03, 5.278912366e-01, 1.499864530e+00, 1.499758421e+00, 4.504640161e-03, 7.226276856e-02 +]; +/// The same, h = 4194 (+0.5 mV). +pub const NG_R2_CHK_05 : [64]f64 = [ + 1.320657823e+00, 1.319927678e+00, 1.779783590e-01, 1.499991277e+00, 1.499991118e+00, 1.680530437e-05, 1.664128523e-05, 1.323164582e+00, + 8.399508598e-01, 8.378428100e-01, 1.609566374e-01, 1.466895287e+00, 1.466513336e+00, 6.463171587e-02, 6.376701372e-02, 9.588429274e-01, + 5.615941116e-01, 5.588164311e-01, 1.549273846e-01, 1.386664031e+00, 1.385961252e+00, 3.098717191e-01, 3.066954967e-01, 7.531552278e-01, + 3.845313756e-01, 3.815495629e-01, 1.485409636e-01, 1.285226917e+00, 1.285312420e+00, 5.849981029e-01, 5.774577046e-01, 6.257763621e-01, + 2.333165558e-01, 2.313037729e-01, 1.268446547e-01, 1.117359032e+00, 1.128454908e+00, 7.740709699e-01, 7.497496849e-01, 5.098922070e-01, + 1.344756805e-01, 1.337638227e-01, 8.711677700e-02, 9.857874600e-01, 1.047217823e+00, 8.417614345e-01, 7.584683842e-01, 4.083269763e-01, + 4.998321552e-02, 4.993678097e-02, 3.563158123e-02, 7.604019716e-01, 1.252555606e+00, 1.109854056e+00, 4.725596505e-01, 2.681922837e-01, + 2.788867539e-03, 2.789159586e-03, 2.061660906e-03, 5.334607727e-01, 1.499463262e+00, 1.499044285e+00, 5.584539211e-03, 7.226274815e-02 +]; +/// The same for sar_cmp_psp_r2b.cir (bulks tied to sources), h = 16777. +pub const NG_R2B_CHK_2 : [64]f64 = [ + 1.301842635e+00, 1.298616982e+00, 1.916313979e-01, 1.499984777e+00, 1.499983538e+00, 2.284557280e-05, 2.157641206e-05, 1.323164582e+00, + 7.949990599e-01, 7.861370593e-01, 1.711575219e-01, 1.456029437e+00, 1.454141419e+00, 9.071360050e-02, 8.627243165e-02, 9.588433509e-01, + 5.029418347e-01, 4.915204013e-01, 1.635861182e-01, 1.349685980e+00, 1.346757927e+00, 4.035096857e-01, 3.870156048e-01, 7.531558020e-01, + 3.226078181e-01, 3.120120887e-01, 1.513986647e-01, 1.188196571e+00, 1.198144088e+00, 6.987520112e-01, 6.543109411e-01, 6.257766862e-01, + 1.875546436e-01, 1.826335311e-01, 1.143704811e-01, 9.631960962e-01, 1.069268072e+00, 8.625138648e-01, 7.175900845e-01, 5.098922463e-01, + 1.066998349e-01, 1.053070925e-01, 7.245264307e-02, 7.762768035e-01, 1.171435315e+00, 1.021706678e+00, 5.739555434e-01, 4.083269025e-01, + 3.928650940e-02, 3.916474220e-02, 2.834592207e-02, 5.395283181e-01, 1.447069098e+00, 1.406947748e+00, 1.194005678e-01, 2.681921177e-01, + 2.180814639e-03, 2.178677242e-03, 1.612301185e-03, 3.810345647e-01, 1.499902544e+00, 1.499826126e+00, 4.784865576e-03, 7.226275975e-02 +]; +/// The input pair made unequal (m of XM4 = 35, then 34), r2b deck: dp, dn at the last point for d = 71579, 71581 (m = 35) and 145772, 145774 (m = 34). +pub const NG_MM : [8]f64 = [0.588023, 1.00043, 0.920144, 0.666049, 0.741355, 0.843, 1.05863, 0.529989]; +/// REAL CHARGE (sar_cmp_psp_real.cir, Xb: the bare comparator, 4 fF on dp and dn): the first output through 0.75 V after the clock (ps), +2h. +pub const NG_RL_FIRST_PS : [7]f64 = [523.032, 528.007, 529.386, 529.741, 529.822, 529.857, 529.860]; +/// The resolution time (ps), the same. +pub const NG_RL_RES_PS : [7]f64 = [1111.910, 1319.320, 1526.820, 1734.590, 1904.960, 2257.140, 2568.600]; +/// Xtop (dynamic_comparator_top): Valid through 0.75 V after the clock (ps), VDP - VDN = +2h. +pub const NG_RL_VALID_PS_P : [7]f64 = [897.700, 922.560, 927.390, 928.410, 928.630, 928.720, 928.730]; +/// The same, VDP - VDN = -2h. +pub const NG_RL_VALID_PS_M : [7]f64 = [920.620, 932.190, 929.900, 929.040, 928.830, 928.740, 928.730]; +/// 1 where dp wins, Xb, VDP - VDN = +2h. +pub const NG_RL_BARE_WIN_P : [7]u32 = [1, 1, 1, 1, 1, 1, 1]; +/// 1 where dp wins, Xtop, VDP - VDN = +2h. +pub const NG_RL_TOP_WIN_P : [7]u32 = [1, 1, 1, 1, 1, 1, 1]; +/// 1 where dp wins, Xb, VDP - VDN = -2h. +pub const NG_RL_BARE_WIN_M : [7]u32 = [0, 0, 0, 0, 0, 0, 0]; +/// 1 where dp wins, Xtop, VDP - VDN = -2h. +pub const NG_RL_TOP_WIN_M : [7]u32 = [0, 0, 1, 1, 1, 1, 1]; +/// Xtop scan, VDP - VDN = d units: d = -2700, -2600, -2571, -2570, -2540, -2400. +/// 1 where dp wins, Xtop, over the scan list. +pub const NG_RL_SCAN_TOP_WIN : [6]u32 = [0, 0, 0, 0, 1, 1]; +/// 1 where dp wins, Xb, over the scan list. +pub const NG_RL_SCAN_BARE_WIN : [6]u32 = [0, 0, 0, 0, 0, 0]; +/// The clock 50 % point in the real deck, ps. +pub const NG_RL_TCK_PS : f64 = 200.8; + +; --- the tests -------------------------------------------------------------------------------------------- + +test the_unit_of_the_tables_is_converted_by_an_exact_factor_and_the_devices_are_the_tables_devices { + // Variables, not constants: a constant assert is folded by the compiler and counts as no assert at all. + var conv : u32 = SP_CONV; + var dtc : u32 = DTC_V_PER_A; + // M3b's tables have dt / C = 200 V/A; the solver's charge unit gives 800 V/A: a factor of exactly 4. + assert_eq(conv * dtc, 800); + assert_eq(SC_STEP_V_PER_A, 800.0); + var w_n : f64 = SC_W_N * 1000.0; + var w_p : f64 = SC_W_P * 1000.0; + var l : f64 = SC_L * 1000.0; + // The table devices are the golden comparator's: sg13_lv_nmos W 1.0 um, sg13_lv_pmos W 3.0 um, L 0.45 um, ng 1; a device of the comparator is + // m of them in parallel, and PSP103 scales every current by m exactly, so the table needs no width correction, only the multiplier. + assert_eq((w_n + 0.5) as u32, NMOS_W_NM); + assert_eq((w_p + 0.5) as u32, PMOS_W_NM); + assert_eq((l + 0.5) as u32, L_NM); + assert_eq(TEMP50_C, 50); + var tn : [625]u32 = [0] ** 625; + var tp : [625]u32 = [0] ** 625; + sp_scale(&TT50_N, &tn); + sp_scale(&TT50_P, &tp); + // The converted words are 4 times M3b's, the largest under the 2^23 the solver's u64 interpolation allows. + assert_eq(tn[624], 5085392); + assert_eq(tp[624], 3874764); + assert_eq(tn[624], 4 * TT50_N[624]); + assert tn[624] < 8388608; + assert tp[624] < 8388608; + var ss : [625]u32 = [0] ** 625; + sp_scale(&SS_N, &ss); + assert ss[624] < 8388608; + sp_scale(&TT_N, &ss); + assert ss[624] < 8388608; + // Per unit of m the PMOS (W 3 um) drives 0.76 of the NMOS (W 1 um) at 1.5 V, 1.5 V: the two types need two tables. + var ratio : f64 = (tp[624] as f64) / (tn[624] as f64); + assert ratio > 0.76; + assert ratio < 0.77; +} + +test the_50c_tables_at_the_held_out_points_against_ngspice { + const rn : Hold = hold_stats(&TT50_N, &TT50_R2_N, 1, 200); + const rp : Hold = hold_stats(&TT50_P, &TT50_R2_P, 1, 200); + // The first 200 R2 points of each device, never nodes. Absolute error 4.97e-7 A (NMOS, 0.13 % of its 378.9 uA) and 3.92e-7 A (PMOS): M3b's 27 C + // tables had 6.5e-7 and 4.4e-7 over 1000 points. Both sides. + assert rn.abs_a < 0.0000005; + assert rn.abs_a > 0.00000049; + assert rp.abs_a < 0.00000040; + assert rp.abs_a > 0.00000039; + // Relative error where the current is at least 100 uA: 0.34 % and 0.14 %; at least 10 uA: 3.15 % and 2.65 %; at least 1 uA: 13.6 % and 10.0 %. + assert rn.b4 < 0.0035; + assert rn.b4 > 0.0033; + assert rp.b4 < 0.0015; + assert rp.b4 > 0.0013; + assert rn.b5 < 0.0320; + assert rn.b5 > 0.0310; + assert rp.b5 < 0.0270; + assert rp.b5 > 0.0260; + assert rn.b6 < 0.137; + assert rn.b6 > 0.135; + assert rp.b6 < 0.101; + assert rp.b6 > 0.099; + assert rn.n4 > 70; + assert rp.n4 > 50; + assert rn.n6 > 160; + // The issue's relative measure with a floor of 1e-9 A is 53 % and 54 % here too: a chord over a 62.5 mV cell cannot do 1 % in weak inversion + // (M3b says so for 27 C); the bound that matters for the comparator is the one at 100 uA and above, where its devices work. + assert rn.rel9 > 0.5; + assert rp.rel9 > 0.5; +} + +test the_run_decides_the_right_way_at_every_input_down_to_one_unit { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + var k : usize = 0; + while (k < 7) { + const sp : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], false, SC_K, &SC_DV_M, &out, &res); + assert !(!sc_sign(&out)); + assert !((NG_R2_DP_P[k] > NG_R2_DN_P[k]) != sc_sign(&out)); + assert !((NG_R2B_DP_P[k] > NG_R2B_DN_P[k]) != sc_sign(&out)); + const sm : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], true, SC_K, &SC_DV_M, &out, &res); + assert !(sc_sign(&out)); + assert !((NG_R2_DP_M[k] > NG_R2_DN_M[k]) != sc_sign(&out)); + assert !((NG_R2B_DP_M[k] > NG_R2B_DN_M[k]) != sc_sign(&out)); + k = k + 1; + } +} + +test the_times_against_the_table_reference_with_the_bulks_tied_to_the_sources { + // sar_cmp_psp_r2b.cir: the same PSP103 comparator with every bulk tied to its source, which is what a 2-D table Id(Vgs, Vds) models, charge + // swamped by 1e6. The decision time (first output through Vdd / 2) is 0.57 .. 0.66 % early and the resolution time 0.57 .. 0.72 % early, for + // every input from 2 mV down to h = 8 (a 1 uV differential); at h = 1 (0.12 uV) the resolution is not reached in 140 steps, in ngspice or here. + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + var k : usize = 0; + while (k < 6) { + const s : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], false, SC_K, &SC_DV_M, &out, &res); + const f : f64 = sp_rel(sp_first(&out), NG_R2B_FIRST[k]); + const r : f64 = sp_rel(sp_res(&out), NG_R2B_RES[k]); + assert !(f > 0.0 - 0.0057); + assert !(f < 0.0 - 0.0066); + assert !(r > 0.0 - 0.0057); + assert !(r < 0.0 - 0.0072); + k = k + 1; + } + // At 1 unit: the first output as before, no resolution in either. + const s1 : u64 = sp_run(&TT50_N, &TT50_P, 1, false, SC_K, &SC_DV_M, &out, &res); + assert sp_rel(sp_first(&out), NG_R2B_FIRST[6]) < 0.0 - 0.0057; + assert sp_rel(sp_first(&out), NG_R2B_FIRST[6]) > 0.0 - 0.0066; + assert sp_res(&out) < 0.0; + assert NG_R2B_RES[6] == 0.0; +} + +test the_times_against_the_golden_deck_and_the_body_effect_is_the_difference { + // sar_cmp_psp_r2.cir: the golden subcircuit as it is, NMOS bulks at ground and PMOS bulks at VDD. The tables have no bulk: the input pair XM3, XM4 + // (source net1, 0.15 .. 0.4 V above ground) and XM8, XM9 (sources below VDD) lose current to the body effect in ngspice. The decision time is + // 6.5 % early against the golden deck (bound 6.7 %), the resolution time 3.5 % .. 1.0 % early (bound 3.6 %). + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + var k : usize = 0; + while (k < 6) { + const s : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], false, SC_K, &SC_DV_M, &out, &res); + const f : f64 = sp_rel(sp_first(&out), NG_R2_FIRST[k]); + const r : f64 = sp_rel(sp_res(&out), NG_R2_RES[k]); + assert !(f > 0.0 - 0.063); + assert !(f < 0.0 - 0.067); + assert !(r > 0.0 - 0.0100); + assert !(r < 0.0 - 0.0360); + // The body effect in ngspice alone, golden deck against the bulk-tied deck: the decision 6.2 .. 6.5 % later, the resolution 0.3 .. 3.0 % later. + const be_f : f64 = sp_rel(NG_R2_FIRST[k], NG_R2B_FIRST[k]); + const be_r : f64 = sp_rel(NG_R2_RES[k], NG_R2B_RES[k]); + assert !(be_f < 0.062); + assert !(be_f > 0.065); + assert !(be_r < 0.0029); + assert !(be_r > 0.0305); + // The two errors compose: model against the golden deck = (1 + table error) / (1 + body effect) - 1. + const comp : f64 = (1.0 + sp_rel(sp_first(&out), NG_R2B_FIRST[k])) / (1.0 + be_f) - 1.0; + const d : f64 = comp - f; + assert !(d * d > 0.00000001 * 0.00000001); + k = k + 1; + } +} + +test the_nodes_at_the_checkpoints_against_both_references { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + const sa : u64 = sp_run(&TT50_N, &TT50_P, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + // Against the bulk-tied deck the eight nodes at the eight steps are within 15.9 mV (dp at the early checkpoints; ap, an, net2, net3 10.8 .. 13.4 mV, + // net1 2.7 mV, n_clkc 0.35 mV): the table's error, the same size as the square law's 15 mV in M4b. + const nb : f64 = sc_dev_ng_u(&out, &NG_R2B_CHK_2); + assert nb < 0.0160; + assert nb > 0.0158; + // Against the golden deck: 150 mV, at net2 after the decision (ngspice's net2 stays at 0.53 V, the table's falls to 0.38 V: the PMOS XM9 in its + // body-effect weak inversion), 113 mV at dp in the middle of the regeneration. + const ng : f64 = sc_dev_ng_u(&out, &NG_R2_CHK_2); + assert ng < 0.1510; + assert ng > 0.1495; + const sb : u64 = sp_run(&TT50_N, &TT50_P, SP_H[1], false, SC_K, &SC_DV_M, &out, &res); + const nh : f64 = sc_dev_ng_u(&out, &NG_R2_CHK_05); + assert nh < 0.1485; + assert nh > 0.1478; +} + +test the_flip_point_of_the_isolated_latch_is_zero_in_the_model_and_in_ngspice { + // THE FLIP POINT: the input difference VDP - VDN at which dp and dn trade places. The comparator is symmetric (XM8, XM9, XM6, XM7 and the pair + // are mirror images, SC_DV_*), so with equal devices it is zero in exact arithmetic; the question is how far from zero each side puts it. + // The model: bisection on h in units of 2^-24 V (a differential of 2h, 0.12 uV per step of h). + const f : i64 = sp_flip(&TT50_N, &TT50_P, &SC_DV_M); + assert sp_abs_i(f) <= 2.0; + const f_mv : f64 = (f as f64) * 1000.0 / SC_UPV; + // ngspice, charge swamped (golden deck and bulk-tied deck): the right winner at h = 1, i.e. at +-2 units, in both orientations, so the flip lies + // inside (-2, 2) units; at d = 0 the two outputs agree to 1e-6 V (the sweep in the header). + assert(NG_R2_DP_P[6] > NG_R2_DN_P[6]); + assert(NG_R2_DP_M[6] < NG_R2_DN_M[6]); + assert(NG_R2B_DP_P[6] > NG_R2B_DN_P[6]); + assert(NG_R2B_DP_M[6] < NG_R2B_DN_M[6]); + // ngspice, real charge, the bare comparator (4 fF on dp, dn): the same, and dn wins on every point of the scan below zero. + var bare_ok : u32 = 0; + var k : usize = 0; + while (k < 7) { + bare_ok = bare_ok + NG_RL_BARE_WIN_P[k] + (1 - NG_RL_BARE_WIN_M[k]); + k = k + 1; + } + assert_eq(bare_ok, 14); + var scan_dn : u32 = 0; + k = 0; + while (k < 6) { + scan_dn = scan_dn + (1 - NG_RL_SCAN_BARE_WIN[k]); + k = k + 1; + } + assert_eq(scan_dn, 6); + // The model's flip against M4e's: 0.00012 mV against the fitted 6.7 mV and the golden run's +2.5 .. +3.1 mV. The isolated latch does not have the + // threshold M4e fits, and the model, which is the isolated latch, does not predict it. + assert sp_abs_i(f) * 1000.0 / SC_UPV < 0.001; + assert f_mv < M4E_RUN_LO_MV / 1000.0; + assert f_mv > 0.0 - M4E_RUN_LO_MV / 1000.0; +} + +test the_golden_top_with_its_inverters_and_nand_has_a_flip_point_of_minus_0_15_mv { + // dynamic_comparator_top: the same latch, an inverter on dp and on dn (x5, x6), and the NAND of the two inverter outputs ("Valid"), PSP103 with + // all its charge. Its flip point is NOT zero: the scan (tight tolerances, reltol 1e-8) has dn winning at d = -2700, -2600, -2571, -2570 units + // and dp winning at -2540 and -2400, so the flip lies in (-2570, -2540], i.e. VDP - VDN between -0.1532 and -0.1514 mV: dp is favoured by + // about 0.15 mV. The NAND's two inputs are not the same load (A and B sit differently in its stack), the inverters' gate-drain capacitance feeds + // that back to dp and dn, and the latch is asymmetric by that much. At loose tolerances (reltol 1e-6) the bracket is (-2571, -2570]: the 1 uV level + // is the tolerance's. + var scan_d : [6]u32 = [2700, 2600, 2571, 2570, 2540, 2400]; + assert_eq(NG_RL_SCAN_TOP_WIN[0] + NG_RL_SCAN_TOP_WIN[1] + NG_RL_SCAN_TOP_WIN[2] + NG_RL_SCAN_TOP_WIN[3], 0); + assert_eq(NG_RL_SCAN_TOP_WIN[4] + NG_RL_SCAN_TOP_WIN[5], 2); + // d is negative: dn wins down to -2570 (index 3) and dp wins at -2540 (index 4). + const lo_mv : f64 = 0.0 - (scan_d[3] as f64) * 1000.0 / SC_UPV; + const hi_mv : f64 = 0.0 - (scan_d[4] as f64) * 1000.0 / SC_UPV; + assert lo_mv < 0.0 - 0.1531; + assert lo_mv > 0.0 - 0.1533; + assert hi_mv < 0.0 - 0.1513; + assert hi_mv > 0.0 - 0.1515; + // The sign: the top favours dp (negative flip) and the golden run's reading of the latch is +2.5 .. +3.1 mV (dp needs VDP ABOVE VDN): the two + // have opposite signs, and the top's offset is 1/16 of the run's lower figure and 1/44 of M4e's fit. + assert lo_mv < 0.0; + assert 0.0 - lo_mv < M4E_RUN_LO_MV / 16.0; + assert 0.0 - lo_mv > M4E_FIT_MV / 45.0; + assert 0.0 - lo_mv < M4E_FIT_MV / 43.0; + // The same asymmetry in the time of Valid: at +-2 mV Valid rises 897.7 ps and 920.6 ps after the clock (a 22.9 ps difference between the two + // orientations), and 928.7 ps at 0.12 uV, where the input no longer matters. + const dv : f64 = NG_RL_VALID_PS_M[0] - NG_RL_VALID_PS_P[0]; + assert dv > 22.0; + assert dv < 24.0; + assert NG_RL_VALID_PS_P[6] > 928.0; + assert NG_RL_VALID_PS_P[6] < 929.5; + // The top at the h list: dp wins for every +2h, and for -2h dn wins only while 2h exceeds the offset (h = 16777 and 4194). + assert_eq(NG_RL_TOP_WIN_P[0] + NG_RL_TOP_WIN_P[1] + NG_RL_TOP_WIN_P[2] + NG_RL_TOP_WIN_P[3] + NG_RL_TOP_WIN_P[4] + NG_RL_TOP_WIN_P[5] + NG_RL_TOP_WIN_P[6], 7); + assert_eq(NG_RL_TOP_WIN_M[0] + NG_RL_TOP_WIN_M[1], 0); + assert_eq(NG_RL_TOP_WIN_M[2] + NG_RL_TOP_WIN_M[3] + NG_RL_TOP_WIN_M[4] + NG_RL_TOP_WIN_M[5] + NG_RL_TOP_WIN_M[6], 5); +} + +test an_unequal_input_pair_moves_the_flip_point_as_ngspice_says { + // The offset that a latch of this kind does have is the pair's imbalance. ngspice (the bulk-tied deck, charge swamped): XM4, whose gate is VDP, + // at m = 35 instead of 36 puts the flip at VDP - VDN = +4.2665 mV (dn wins at d = 71579 units, dp at 71581), at m = 34 at +8.689 mV (dn at 145772, + // dp at 145774). The model, the same pair through sc_mult_pair, bisects to 70064 and 142816 units. + assert(NG_MM[0] < NG_MM[1]); + assert(NG_MM[2] > NG_MM[3]); + assert(NG_MM[4] < NG_MM[5]); + assert(NG_MM[6] > NG_MM[7]); + var mm : [15]u32 = [0] ** 15; + sc_mult_pair(36, 35, &mm); + const f35 : i64 = sp_flip(&TT50_N, &TT50_P, &mm); + sc_mult_pair(36, 34, &mm); + const f34 : i64 = sp_flip(&TT50_N, &TT50_P, &mm); + const e35 : f64 = ((f35 as f64) - 71580.0) / 71580.0; + const e34 : f64 = ((f34 as f64) - 145773.0) / 145773.0; + // 2.1 % and 2.0 % low, from both sides. + assert e35 < 0.0 - 0.0195; + assert e35 > 0.0 - 0.0225; + assert e34 < 0.0 - 0.0185; + assert e34 > 0.0 - 0.0215; + // The flip point is a fixed-point number far above the 2-unit rounding of the nominal one. + assert f35 > 69000; + assert f34 > 140000; + // What imbalance would M4e's 6.7 mV be? 6.7 mV is 112400 units; between the two model points the m of XM4 that gives it is 34.42, an imbalance of + // 4.4 % of the pair's current (1 part in 23): a pair mismatch of that size, not an effect of the latch itself. + const units67 : f64 = M4E_FIT_MV * SC_UPV / 1000.0; + const m4 : f64 = 35.0 - (units67 - (f35 as f64)) / ((f34 as f64) - (f35 as f64)); + assert m4 > 34.40; + assert m4 < 34.44; + const imb : f64 = (36.0 - m4) / 36.0; + assert imb > 0.043; + assert imb < 0.045; +} + +test without_charge_tables_the_decision_is_98_times_too_fast { + // sar_cmp_psp_real.cir: the bare golden comparator with ALL of PSP103's charge, 4 fF on dp and dn, an ideal 1.6 ps clock. The first output + // reaches Vdd / 2 523.0 ps after the clock's 50 % point at +2 mV; the model, which has current and the lumped capacitances of M4b (4, 6, 2 fF) + // and no charge, 5.35 ps. The decision is 96 .. 98 times too fast, the resolution 155 .. 183 times: the devices' own gate and junction + // capacitance (m up to 36, a few hundred fF on a node) outweighs the 4 fF. This is what the charge tables (#10777) are for; until they exist the + // model's time is the time of the current alone, and the check against the charge-swamped deck above is the one that judges the tables. + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + var k : usize = 0; + while (k < 6) { + const s : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], false, SC_K, &SC_DV_M, &out, &res); + const f_ps : f64 = sp_first(&out) * 0.1; + const r_ps : f64 = sp_res(&out) * 0.1; + const rf : f64 = NG_RL_FIRST_PS[k] / f_ps; + const rr : f64 = NG_RL_RES_PS[k] / r_ps; + assert !(rf < 95.5); + assert !(rf > 98.0); + assert !(rr < 154.0); + assert !(rr > 183.5); + k = k + 1; + } + assert NG_RL_TCK_PS > 200.7; + assert NG_RL_TCK_PS < 200.9; +} + +test the_residual_is_bounded_and_three_iterations_are_enough { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + var k : usize = 0; + while (k < 7) { + const sp : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], false, SC_K, &SC_DV_M, &out, &res); + assert sp_res_max(&res) <= SP_RES_BOUND; + const sm : u64 = sp_run(&TT50_N, &TT50_P, SP_H[k], true, SC_K, &SC_DV_M, &out, &res); + assert sp_res_max(&res) <= SP_RES_BOUND; + k = k + 1; + } + // The bound is reached: h = 262 and 84 end a step 3 units from solved (0.18 uV). + const s262 : u64 = sp_run(&TT50_N, &TT50_P, 262, false, SC_K, &SC_DV_M, &out, &res); + assert_eq(sp_res_max(&res), 3); + // K = 4 gives the same times as K = 3 (the decision time to 1e-3 of a step, the resolution time to 7e-3 of 100 steps): three are enough. + const f3 : f64 = sp_first(&out); + const r3 : f64 = sp_res(&out); + const s4 : u64 = sp_run(&TT50_N, &TT50_P, 262, false, 4, &SC_DV_M, &out, &res); + assert fabs64(sp_first(&out) - f3) < 0.001; + assert fabs64(sp_res(&out) - r3) < 0.01; + // NEGATIVE CONTROLS: K = 1 leaves the Newton move owed at 20750 units (1.2 mV), 6900 times the bound, and K = 2 at 155 units (52 times). + const s1 : u64 = sp_run(&TT50_N, &TT50_P, 262, false, 1, &SC_DV_M, &out, &res); + assert sp_res_max(&res) > 20000; + assert sp_res_max(&res) < 21000; + const s2 : u64 = sp_run(&TT50_N, &TT50_P, 262, false, 2, &SC_DV_M, &out, &res); + assert sp_res_max(&res) > 50 * SP_RES_BOUND; + assert sp_res_max(&res) < 160; +} + +test the_wrong_tables_are_refused_by_the_time_check_not_by_the_sign_or_the_flip_point { + // NEGATIVE CONTROLS. The comparator is symmetric, so ANY pair of tables decides the right way and has a flip point at zero: the sign and the flip + // point cannot tell tt at 50 C from ss. The decision time can. The check is the bulk-tied deck's decision time at +2 mV (53.854 steps) to + // 0.66 % and the resolution time (72.309) to 0.72 %: the tt50 run is 0.65 % and 0.63 % early and passes (the test above); each wrong pair fails. + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + // ss at 27 C (M3b's slow corner) in the tt 50 C run: 14.1 % late (decision), 11.5 % late (resolution); against the golden deck 7.5 % late, over + // the 6.7 % bound of the true tables, by a margin that is small and is why the table reference is the bulk-tied deck. + const s_ss : u64 = sp_run(&SS_N, &SS_P, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + assert(sc_sign(&out)); + const fs : f64 = sp_rel(sp_first(&out), NG_R2B_FIRST[0]); + const rs : f64 = sp_rel(sp_res(&out), NG_R2B_RES[0]); + assert fs > 0.14; + assert fs < 0.143; + assert rs > 0.114; + assert rs < 0.117; + assert fs > 20.0 * 0.0066; + assert sp_rel(sp_first(&out), NG_R2_FIRST[0]) > 0.067; + assert sp_flip(&SS_N, &SS_P, &SC_DV_M) <= 2; + // tt at 27 C in the 50 C run: the temperature is part of the table. 5.0 % early (decision), 6.0 % early (resolution): 7.5 and 8 times the bound. + const s_27 : u64 = sp_run(&TT_N, &TT_P, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + assert(sc_sign(&out)); + const f27 : f64 = sp_rel(sp_first(&out), NG_R2B_FIRST[0]); + const r27 : f64 = sp_rel(sp_res(&out), NG_R2B_RES[0]); + assert f27 < 0.0 - 0.048; + assert f27 > 0.0 - 0.052; + assert r27 < 0.0 - 0.058; + assert r27 > 0.0 - 0.062; + assert f27 * f27 > 7.0 * 0.0066 * 7.0 * 0.0066; + // The two tables in each other's slots (the PMOS table for the NMOS): the sign is right, the decision is 20.7 % late. + const s_sw : u64 = sp_run(&TT50_P, &TT50_N, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + assert(sc_sign(&out)); + const fw : f64 = sp_rel(sp_first(&out), NG_R2B_FIRST[0]); + assert fw > 0.205; + assert fw < 0.21; + // The inputs wired the wrong way round (swap) with the right tables: the sign check fails, as in M4b. + const s_x : u64 = sp_run(&TT50_N, &TT50_P, SP_H[0], true, SC_K, &SC_DV_M, &out, &res); + assert(!sc_sign(&out)); +} + +test the_four_main_runs_are_pinned { + // The order-sensitive fold of all 1120 samples of each run (+2 mV and +0.5 mV, both orientations): any changed word of the tables, the solver or the + // run changes a pin. The two orientations are different words because the circuit is mirror-symmetric only in real arithmetic. + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + const a : u64 = sp_run(&TT50_N, &TT50_P, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + const b : u64 = sp_run(&TT50_N, &TT50_P, SP_H[0], true, SC_K, &SC_DV_M, &out, &res); + const c : u64 = sp_run(&TT50_N, &TT50_P, SP_H[1], false, SC_K, &SC_DV_M, &out, &res); + const d : u64 = sp_run(&TT50_N, &TT50_P, SP_H[1], true, SC_K, &SC_DV_M, &out, &res); + assert_eq(a, 16485561492764736872); + assert_eq(b, 17229961214508940637); + assert_eq(c, 15843583760717418062); + assert_eq(d, 5888890600074327155); + // One unit changed in one table word of the tt 50 C NMOS table (Vgs = 0.5 V, Vds = 0.75 V) moves the pin. + var t1 : [625]u32 = [0] ** 625; + var i : usize = 0; + while (i < 625) { + t1[i] = TT50_N[i]; + i = i + 1; + } + t1[212] = t1[212] + 1; + const e : u64 = sp_run(&t1, &TT50_P, SP_H[0], false, SC_K, &SC_DV_M, &out, &res); + assert e != a; +}