diff --git a/.trinity/seals/analog_AnalogAcceptSar.json b/.trinity/seals/analog_AnalogAcceptSar.json index a1dece0599..cf20340e91 100644 --- a/.trinity/seals/analog_AnalogAcceptSar.json +++ b/.trinity/seals/analog_AnalogAcceptSar.json @@ -9,10 +9,6 @@ "spec": "specs/analog/mos_psp103.t27", "spec_hash": "sha256:e04b69e5c43188a3d46a4c0949cfc727f239721de73fb46e92c8a845bbbbc98e" }, - { - "spec": "specs/analog/mos_psp103_50c_data.t27", - "spec_hash": "sha256:9e68629797a29516836ca3e0b5e4881b0e751d847c58fd52b63e8f69746dbbe1" - }, { "spec": "specs/analog/mos_psp103_data.t27", "spec_hash": "sha256:86e7c154c88a325e41ff4a154a3b766cb8d65e89e2ea82176b46ee4fc523dd74" @@ -31,12 +27,16 @@ }, { "spec": "specs/analog/sar_comparator.t27", - "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7" + "spec_hash": "sha256:456f3c0ffd82592df2a925a1de6c4f294effbee809f8443e0b93ec6fdd2f5de5" }, { "spec": "specs/analog/sar_comparator_psp.t27", "spec_hash": "sha256:03fa3e42e50de0a06436cc2e57636a6b59c1be04243a9b836920832e8dae0c40" }, + { + "spec": "specs/analog/sar_comparator_q.t27", + "spec_hash": "sha256:6fad103672e13c04173fe1c21f502304c5a60012549f2631330a38f78c82cf27" + }, { "spec": "specs/analog/sar_kick.t27", "spec_hash": "sha256:b38a9a0bd22837f7ca1d5609756c522cd791f7b0f11115b7863e04cd4e15bf10" @@ -55,23 +55,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:3f6992a682a03e5d9ce66aa9dc261de35315b020aa2dd125cf9fc4ba5ba06e6b", - "gen_hash_rust": "sha256:491dfc6f9418e5650b2f66d415df6683edf68ced544648f0310c4b6452c8c9cd", - "gen_hash_verilog": "sha256:bc2099c4b2bf4d4f8d191946936ec071c4f87091fff777ccdcd4298461f07cb3", - "gen_hash_zig": "sha256:81a184a65723aa373c8ec78d9405d2501d38623d9102fcf8f41979494c4cff57", + "gen_hash_c": "sha256:b66af5af3cc53c27fc14dfb1317d2134c9adf18900e43bbe57dbf9a794aa9a5f", + "gen_hash_rust": "sha256:b942a24880e75b0d0b69639af85d1bb2868aa138ceecee19933630474018237a", + "gen_hash_verilog": "sha256:88b09e15ce920440b641b1419d00c13f0d917872b484db9c14cd5e82925a05e8", + "gen_hash_zig": "sha256:927eb1ca460416a20320b84189450c287f5e8eeb884216b4e14fc6062c8244b2", "module": "AnalogAcceptSar", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T09:07:24Z", + "sealed_at": "2026-10-20T10:59:07Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:6304ef41504fb584a1a7ac3af7779acaa2a468be46df1055217f4fb4206867e2", + "spec_hash": "sha256:b70fef8d2eedf99f3423a2fa8ad06844da5891959a3c7500db7e29c7a2130f18", "spec_path": "specs/analog/accept_sar.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 14, - "total": 14, + "passed": 15, + "total": 15, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/analog_AnalogSarComparator.json b/.trinity/seals/analog_AnalogSarComparator.json index 328c3528b2..4b4a27c8d8 100644 --- a/.trinity/seals/analog_AnalogSarComparator.json +++ b/.trinity/seals/analog_AnalogSarComparator.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+c890dd8f2", + "built_by": "t27c-bootstrap@0.5.2+2d3203e90", "closure": [ { "spec": "specs/analog/linear.t27", @@ -7,20 +7,20 @@ }, { "spec": "specs/analog/mos_table.t27", - "spec_hash": "sha256:9c0d1537ed5d8d60c4bdcb67d1e4c73ef80270e8cab224cb089c5b568fe11182" + "spec_hash": "sha256:cbebd820d2284174fd88fd6e5c36fb4f0a7778c9977c9742fe215b177d623bd8" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:ce2ff93091397b6a53ed56e1691d9ae73b6442e57ce4249b7e3a8cc3106b7436", - "gen_hash_rust": "sha256:bab770f55aaa082e158dfef79f5933ee3d3a8d30b587db42f01ce97801234e6f", - "gen_hash_verilog": "sha256:20f2ee1f416c8f9ae3e5a179a68985fe60449df645ff53432836468e0ea159a4", - "gen_hash_zig": "sha256:24ab5cb2621640efe1b3163a87657c83c718fb084c415d87ece7a9dc00736093", + "gen_hash_c": "sha256:f3ffc3d68d1d920cbcc2d6cea5cd50c8867494e1743674e4a3ba2bb170f90702", + "gen_hash_rust": "sha256:931d31e46b756533ccd21e066d3c02ef3896b2077ed4b72fe7af809e2d431045", + "gen_hash_verilog": "sha256:cac710b24f878c5af60613c01db40a9db9baf2d37d44a21d8c3a0c764bd5acc2", + "gen_hash_zig": "sha256:86eda8319ff3871be36f50cb5f8a0650d6f135eaa0b2c959dbd33577250b1f23", "module": "AnalogSarComparator", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T06:13:22Z", + "sealed_at": "2026-10-20T10:58:54Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7", + "spec_hash": "sha256:456f3c0ffd82592df2a925a1de6c4f294effbee809f8443e0b93ec6fdd2f5de5", "spec_path": "specs/analog/sar_comparator.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/analog_AnalogSarComparatorQ.json b/.trinity/seals/analog_AnalogSarComparatorQ.json new file mode 100644 index 0000000000..a4fe320afb --- /dev/null +++ b/.trinity/seals/analog_AnalogSarComparatorQ.json @@ -0,0 +1,53 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+2d3203e90", + "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + }, + { + "spec": "specs/analog/mos_psp103_data.t27", + "spec_hash": "sha256:86e7c154c88a325e41ff4a154a3b766cb8d65e89e2ea82176b46ee4fc523dd74" + }, + { + "spec": "specs/analog/mos_psp103_vsb_data.t27", + "spec_hash": "sha256:1084104fc91192606d207a0bb45084c5fb62f097d1850d1875fca25f72387cfe" + }, + { + "spec": "specs/analog/mos_table.t27", + "spec_hash": "sha256:cbebd820d2284174fd88fd6e5c36fb4f0a7778c9977c9742fe215b177d623bd8" + }, + { + "spec": "specs/analog/sar_comparator.t27", + "spec_hash": "sha256:456f3c0ffd82592df2a925a1de6c4f294effbee809f8443e0b93ec6fdd2f5de5" + }, + { + "spec": "specs/analog/sar_comparator_psp.t27", + "spec_hash": "sha256:03fa3e42e50de0a06436cc2e57636a6b59c1be04243a9b836920832e8dae0c40" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:393a5b708addaa38fcbb8e62ba0639ac753a424a97e101172bb748637aad2f27", + "gen_hash_rust": "sha256:87559259c8e4f59878f43588dd62cbe805767b3590eb0abe5c76e6313b91b427", + "gen_hash_verilog": "sha256:174af9c16fcddf7070c2df531831b616bbffb8db5a0a2767609fd9a160fd205f", + "gen_hash_zig": "sha256:36d5461b2c6d89e03e9db80d1fc5abb678cd671b7dc3d44286a239d42269e6ab", + "module": "AnalogSarComparatorQ", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T10:58:49Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:6fad103672e13c04173fe1c21f502304c5a60012549f2631330a38f78c82cf27", + "spec_path": "specs/analog/sar_comparator_q.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 8, + "total": 8, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/docs/analog/accept-sar-2026-10-20.md b/docs/analog/accept-sar-2026-10-20.md index 81371e341c..1cd679a7f7 100644 --- a/docs/analog/accept-sar-2026-10-20.md +++ b/docs/analog/accept-sar-2026-10-20.md @@ -1,6 +1,6 @@ # The external team's per-block checks applied to our SAR ADC blocks, 2026-10-20 -Issue #11212, milestone M5c of the analog epic #10486, night loop #11063; M5d (issue #11263) adds the transistor-level rows of the nand, nor, tgate and tracking_switch, cross-checked against ngspice; M3e (issue #11283) adds a source-to-bulk axis to the PSP103 tables, which turns the tgate's two resistances from NOT MODELLED to PASS (section 3c). English, ASCII only. +Issue #11212, milestone M5c of the analog epic #10486, night loop #11063; M5d (issue #11263) adds the transistor-level rows of the nand, nor, tgate and tracking_switch, cross-checked against ngspice; M3e (issue #11283) adds a source-to-bulk axis to the PSP103 tables, which turns the tgate's two resistances from NOT MODELLED to PASS (section 3c); M4b-q (issue #11312) gives the dynamic_comparator the devices' own charge and the body effect at 27 C, which turns its decision time from FAIL to PASS and its reset from NOT MODELLED to PASS, so the block is a PASS (section 3d). English, ASCII only. The external team's report of 2026-10-16 lists, for each of the 13 blocks of the golden SAR ADC, a measured value and the limit that its check enforces (conditions: tt corner, 27 C, VDD 1.5 V, cells taken from the integrated converter's netlist). The team confirmed on 2026-10-20 that the checks and their limits may be used and published here. `specs/analog/accept_sar.t27` holds those limits as named constants, one pure check function per quantity, and one function per block that measures the quantity on OUR model by `use` of the spec that holds it. This document is the result. Every "our value" below is computed by a named function of that spec and pinned by a test; every "report" value is the report's, and is also in the spec as the positive control of its check (the report's own number must pass the report's own check). @@ -18,17 +18,19 @@ A block is FAIL when any row fails, PASS only when it has rows and every row is The cross-check rule (M5d). A row from a transistor-level model is a verdict only when the same cell, run in ngspice-47 with the foundry's PSP103 (`specs/analog/decks/accept_*.cir`, on the reference host: a container with 2 threads), agrees with our number within a bar that is stated with the number: 10 uV for a level, 10 percent for a resistance or a current. A value without an ngspice run, or that does not agree with it, is shown in the table and is NOT MODELLED, whatever the limit says. The bars are far below the limits (50 mV, 30 kohm, 1 nA), so they decide only whether our number is the cell's number, never whether it passes the report's check. +The comparator's rows (M4b-q, issue #11312) are worded as the issue words them: a row is PASS only inside the report's limit AND within the bar of ngspice's run of the same comparator at 27 C (10 percent for the decision time, 5 mV, a tenth of the report's 50 mV, for a level), and a model value that is produced but disagrees with ngspice's by more than the bar is the model's FAIL, not an unmeasured row. That is what makes the negative control of section 3d, the same model with its charge removed, a FAIL and not a NOT MODELLED. + ## 2. The counts Over the 13 blocks of the report (inverter, nand, nor, tgate, dff, dynamic_comparator, tracking_switch, clock_gen, SAR_logic, DAC_control, DAC_control_top, neg_bottom_plate_logic, the 11-bit integration run): | verdict | blocks | which | |---|---|---| -| PASS | 6 | clock_gen, SAR_logic, integration, nand, nor, tgate | -| FAIL | 1 | dynamic_comparator (decision time) | +| PASS | 7 | clock_gen, SAR_logic, integration, nand, nor, tgate, dynamic_comparator | +| FAIL | 0 | | | NOT MODELLED | 6 | inverter, dff, tracking_switch, DAC_control, DAC_control_top, neg_bottom_plate_logic | -Over the 30 rows: 21 PASS, 1 FAIL, 8 NOT MODELLED (a test pins both counts: `as_all_blocks`, `as_all_rows`). Before M5d the counts were 3 / 1 / 9 blocks and 16 / 1 / 13 rows; after M5d and before M3e they were 5 / 1 / 7 blocks and 19 / 1 / 10 rows. The M5d PASS rows are the nand's and the nor's levels (2) and the tgate's off leakage (1); the M3e PASS rows are the tgate's two resistances (2), read on tables with a source-to-bulk axis (section 3c), so the tgate block is a PASS. Three of the six NOT MODELLED blocks have at least one measured, passing row (inverter 1, dff 2, tracking_switch 1); three have none (DAC_control, DAC_control_top, neg_bottom_plate_logic). The PASS of clock_gen and SAR_logic is the PASS of the gate-level model named in section 4, that of integration the PASS of a behavioural converter fitted to the golden deck, and that of the nand, the nor and the tgate the PASS of a DC solve on the PSP103 tables, each nand and nor level within 3 uV of ngspice's and the tgate's Ron within 0.1 percent of ngspice's real cell. +Over the 30 rows: 23 PASS, 0 FAIL, 7 NOT MODELLED (a test pins both counts: `as_all_blocks`, `as_all_rows`). Before M5d the counts were 3 / 1 / 9 blocks and 16 / 1 / 13 rows; after M5d and before M3e they were 5 / 1 / 7 blocks and 19 / 1 / 10 rows; after M3e and before M4b-q 6 / 1 / 6 blocks and 21 / 1 / 8 rows. The M4b-q rows are the comparator's four, all PASS: its decision time (FAIL before), its reset (NOT MODELLED before), its sign and its end levels (PASS before, on the model without charge; now on the model with it). The M5d PASS rows are the nand's and the nor's levels (2) and the tgate's off leakage (1); the M3e PASS rows are the tgate's two resistances (2), read on tables with a source-to-bulk axis (section 3c), so the tgate block is a PASS. Three of the six NOT MODELLED blocks have at least one measured, passing row (inverter 1, dff 2, tracking_switch 1); three have none (DAC_control, DAC_control_top, neg_bottom_plate_logic). The PASS of clock_gen and SAR_logic is the PASS of the gate-level model named in section 4, that of integration the PASS of a behavioural converter fitted to the golden deck, that of the nand, the nor and the tgate the PASS of a DC solve on the PSP103 tables, each nand and nor level within 3 uV of ngspice's and the tgate's Ron within 0.1 percent of ngspice's real cell, and that of the dynamic_comparator the PASS of a transient of the eight-node latch on the PSP103 tables with the devices' charge, its decision time 1.75 percent from ngspice's and its levels within 0.5 mV (section 3d). The report's corners smoke suite (SAR inverter delay 62 to 124 ps, comparator decision 467 to 829 ps over 5 corners at -40, 27 and 125 C) is a test run, not a block, and is not in the 13. It is NOT MODELLED (last row of the table). @@ -51,9 +53,9 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | dff | levels of the 8 observation points | not produced | all right | +-50 mV | NOT MODELLED | | | dff | clock-to-Q | 546 ps | 524 ps | 1 to 3000 ps | PASS | `as_sar_run` | | dynamic_comparator | sign at +-2 mV, 0.75 V common mode | +2 mV: dp wins; -2 mV: dn wins | right | right | PASS | `as_cmp` | -| dynamic_comparator | levels after the decision | winner 1.49996 V, loser 3.6 mV | right | +-50 mV | PASS | `as_cmp` | -| dynamic_comparator | reset | not produced | right | +-50 mV | NOT MODELLED | | -| dynamic_comparator | decision time at +-2 mV | 5.35 ps | 607 ps | 10 to 10000 ps | **FAIL** | `as_cmp` | +| dynamic_comparator | levels after the decision (2 ns) | winner 1.49933 V, loser 2.26 mV; ngspice 1.49884 V, 2.283 mV (0.49 and 0.02 mV) | right | +-50 mV | PASS (within 5 mV of ngspice) | `as_cmp` | +| dynamic_comparator | reset (1.4 ns after the clock falls at 2 ns) | dp 3.08 mV, dn -0.17 mV; ngspice 3.265 and -0.247 mV (0.19 and 0.08 mV) | right | +-50 mV | PASS (within 5 mV of ngspice) | `as_cmp` | +| dynamic_comparator | decision time at +-2 mV | 613.07 ps (+2 mV), 613.15 ps (-2 mV); ngspice 602.54 ps (+1.75 %) | 607 ps | 10 to 10000 ps | PASS (within 10 % of ngspice) | `as_cmp` | | tracking_switch | tracking error, DC (our levels 0.2 V and 1.3 V into 3.93 pF) | -1.97 uV at both; ngspice PSP103 +0.29 / -0.17 uV | < 0.001 mV at 0.3 V and 1.2 V into 2 pF | +-5 mV | PASS (within 2.3 uV of ngspice) | `as_sw` | | tracking_switch | hold step | not produced; ngspice PSP103 cell -1.60 / -1.73 mV at the report's conditions | -1.6 / -1.7 mV | +-30 mV | NOT MODELLED | | | tracking_switch | droop | not produced; ngspice PSP103 cell under 10 uV over 413 ns | none | +-5 mV | NOT MODELLED | | @@ -135,13 +137,51 @@ What the data say, besides: the current of both devices falls with Vsb at every What this does not say: the tgate at another corner, temperature or size (tt, 27 C, W 1.0 um and 3.0 um, L 0.45 um only); a bulk forward-biased against its source (Vsb below 0); the comparator, the switch or any cell other than the tgate, which are not read on the axis. +## 3d. The comparator with its charge and the body effect (M4b-q, issue #11312) + +The comparator of the first versions of this table was the model of `sar_comparator_psp.t27`: the PSP103 current tables at 50 C and no charge, which decided 5.35 ps after the clock against the 523 ps of ngspice's run of the golden comparator, 98 times too fast, and FAILED the 10 ps lower limit. `specs/analog/sar_comparator_q.t27` is the same eight-node latch with the three things that were missing, at the report's 27 C: the devices' own drain, gate and source charge (the M3c tables), the log-domain current tables (M3c-2) and their source-to-bulk axis (M3e), which the input pair (source on net1) and the PMOS XM8, XM9 need. It is backward Euler of 1.6 ps, the clock a step of the grid, four Newton iterations, and reuses the solver of `sar_comparator.t27` (whose `sc_stamp_chan` is split in two so that the stamp is shared; its 20 tests and pinned folds are unchanged). + +THE REFERENCE. The comparator is written out in `sar_cmp_q_ref.cir` (the thirteen transistors and the inverter, the W, L and m of `sar_cmp_psp_r2b.cir`, the golden deck's bulks, 4 fF on dp and dn; none of the golden deck's text) and run in ngspice-47 with the PSP103 on the reference host. It IS the golden comparator: at 50 C and the golden deck's start it gives the first output through 0.75 V 523.032, 528.006, 529.386 and 529.741 ps after the clock (`sar_cmp_q_ref50.cir`), the four numbers the golden subcircuit gave in `sar_cmp_psp_real.cir`. A test pins the match to a femtosecond. + +THE START matters, and is the one finding of this lane beyond the model. `sar_cmp_psp_real.cir` starts from its initial conditions with `uic` and a clock at 200 ps; the nodes inside the PSP103 devices start at 0 V and their first charging disturbs the circuit for longer than the 200 ps (n_clkc falls to 0.56 V in 10 ps, ap, an, net2, net3 are kicked over the supply, dp and dn rise to 0.9 V, and at the clock n_clkc is 1.30 V and dp and dn 0.62 V). Its decision time, 508.1 ps at 27 C and 523.0 ps at 50 C, is a number of that start. Three starts, all in ngspice-47: + +| start | what it is | 27 C | 50 C | +|---|---|---|---| +| golden deck's | `.ic` and `uic`, clock at 200 ps | 508.1 ps | 523.0 ps | +| settled | the spec's rest (ap = an = net2 = net3 = n_clkc = 1.5 V, dp = dn = 0, net1 = 0.401 V) as `.ic` without `uic`: the DC operating point with the eight nodes forced, then released; clock at 1 ps | 602.54 ps | 632.56 ps | +| free | the operating point with the clock low and net1 where the leakage of the pair and the tail balance (0.7073 V) | 607.145 ps | | + +The external team's report has 607 ps: the free start gives it to 0.02 percent and the settled start to 0.7 percent, so the settled start is the right reference and the disturbed start is not. The model starts from the settled rest; the reference of the verdict is the settled start. The 98 times of the old no-charge model were against the disturbed start; against the settled start the 5.35 ps is 118 times too fast. + +THE NUMBERS (decision time = the first output through 0.75 V after the clock's 50 percent point; "BE" is ngspice's backward Euler on the model's own 1.6 ps grid, `sar_cmp_q_be.cir`): + +| input | model | ngspice | gap | ngspice BE | gap | +|---|---|---|---|---|---| +| +2 mV | 613.07 ps | 602.54 ps | +1.75 % | 603.32 ps | +1.62 % | +| -2 mV | 613.15 ps | 602.54 ps | +1.76 % | | | +| +0.5 mV | 624.14 ps | 608.02 ps | +2.65 % | 609.01 ps | +2.48 % | +| 31 uV | 628.69 ps | 610.01 ps | +3.06 % | | | +| 1 uV | 629.11 ps | 610.15 ps | +3.11 % | | | +| free start, +2 mV | 614.86 ps | 607.145 ps | +1.27 % | | (the report's 607 ps: +1.29 %) | +| levels at 2 ns (winner, loser) | 1.49933 V, 2.26 mV | 1.49884 V, 2.283 mV | 0.49 mV, 0.02 mV | | | +| reset, 1.4 ns after the fall (dp, dn) | 3.08 mV, -0.17 mV | 3.265 mV, -0.247 mV | 0.19 mV, 0.08 mV | | | +| resolution time (not a row) | 1040.7 ps | 1094.3 ps | -4.9 % | | | + +The decision time is between 1.75 and 3.11 percent over ngspice's at every input from 2 mV down to 1 unit of 2^-24 V, the same sign, a third of the bar of 10 percent and 61 times the report's lower limit. The scheme is not the error: ngspice's backward Euler on a grid of 0.2, 0.4, 0.8 and 1.6 ps is within 0.01, 0.02, 0.10 and 0.13 percent of its own trapezoidal run. The nodes follow ngspice's to 27 mV from 50 to 500 ps (the discharge of ap and an is 1 to 2 percent early) and to 83 mV through the regeneration (the resolution edge, 4.9 percent early); at the clock edge ap is 81 mV off (the kick is 0.39 V in ngspice and 0.45 V in the model), and in the 400 ps after the clock falls the model is up to 51 mV off on the outputs, 0.18 V on ap and 0.31 V on net2, while by 1.4 ns every node is within 2.6 mV. Only the decision time, the end levels and the reset levels are rows; the rest is stated so that the PASS is not read as more. + +THE NEGATIVE CONTROLS. The same model with the devices' charge removed (the lumped capacitors of the square-law model on every node) decides at 5.40 ps, 111 times too fast, under the report's 10 ps and 99 percent under ngspice: the row FAILS, and so does the block (a test). A decision time inside the limit but 25 percent from ngspice's is a FAIL too, and so is a level 5 mV from ngspice's. Without the body effect (the source-to-bulk voltage forced to 0) the decision is 570.6 ps, 5.30 percent under ngspice's, inside the 10 percent bar (so the bar of the verdict does not see it; a test in `sar_comparator_q.t27` with a 3 percent bar does: the body effect is worth 7.4 percent of the decision time). Without the closure of the source charge (below) the Newton step does not converge (last residual 1.88e6 units, 0.11 V); with the charge tables in the wrong slots it runs off the range; K = 2 leaves 2.76e5 units of residual although its time is within 0.1 ps (the residual bound is what refuses it). The Jacobian is the derivative of the residual to 1.5 percent and its transpose is not. + +THE ONE STEP BEYOND THE ISSUE. The issue asks for the drain and gate charge from the tables; the source terminal is the remainder (the four terminal charges add to zero) and the bulk's charge, 4 to 7 percent of the gate's, is not tabulated. Put on the source node, it jumped whenever the two ends of a channel exchanged their roles and the Newton step did not converge. The source charge is therefore closed by its two edges, Qb(x, y) = Qb(x, 0) + Qb(0, y), which the tables give exactly (Qs = Qd at Vds = 0 and Qs = 0 at a gate drive of 0, a test for both devices); the interior is the tables' charge without the cross term of the bulk. A deck that prints the source terminal's charge would test it; it was not made. + +What this does not say: the body effect on the charge (the charge tables have the bulk tied to the source), another corner or temperature, the flip point of the latch below 10 uV (the model's decision at 10 uV and below depends on the Newton count and the least bit of the tables), noise, mismatch, the converter's comparator (its delay-cell clock, the kickback of its input stages, the NAND "Valid" and the inverters on dp and dn), the reset timing of the report (read here 1.4 ns after the clock falls), hardware. + ## 4. What each model is, and what that means for the verdict - inverter. The PSP103 tables of the SG13G2 NMOS (W 1.0 um) and PMOS (W 3.0 um), tt, 27 C, current in the log domain (`mos_psp103.t27`, M3c-2). The threshold is the input at which the DC transfer falls through Vdd / 2: 0.689243 V on the log tables, 0.689252 V on the linear ones; ngspice's trip point is 0.689 V and the report's 0.69 V. The delays into 20 fF are NOT MODELLED: the model's transient is validated at 500 fF (tpLH 1.344 ns, tpHL 1.018 ns) and 10 pF only, and a gate edge kicks the output 5 times further at 20 fF than at the 100 fF that `mos_psp103.t27` already says leaves the tables' 0 to Vdd range. Those 500 fF delays are not compared with the 1 to 500 ps limit: they are a different load. - nand, nor. Two rows each. The truth table is at gate level (`sar_logic.t27`: a gate is a delay and a truth table, a value is 0, 1 or X and never a voltage). The levels in volts are the DC solution of the two-input gate on the same PSP103 log tables as the inverter (`mos_psp103_gates.t27`, M5d): the nand is two NMOS in series against two PMOS in parallel, the nor the same seen from the supply, each device the W and L of the golden netlist's (NMOS 1.0 um, PMOS 3.0 um, L 0.45 um), so no new table was made. The solver is the inverter's bisection with the stack's middle node solved inside it (a second bisection); the currents are read by the integer log-domain reader in steps of 1.164 pA and the output has a resolution of 59.6 nV. Every level of both gates is within 3 uV of ngspice's run of the same cell, and microvolts from its rail. - tgate. The same tables and the same reader (`mos_psp103_gates.t27`): conducting, the NMOS reads (Vdd - Vout, 10 mV) and the PMOS (Vin, 10 mV), and R is 10 mV over the sum, at 27 outputs from 0.10 V to 1.40 V (the measurement of `sar_off_ron.cir`); off, both devices sit at Vgs = 0, Vds = 1.5 V and the leakage is the sum of their two table words. The leakage is a row, and so are the two resistances since M3e: the NMOS bulk is on ground and the PMOS bulk on the supply, so while the gate conducts the NMOS source (the output) is Vout above its bulk and the PMOS source (the input, Vout + 10 mV) is 1.5 V - (Vout + 10 mV) below its; each device is read on the tables at that source-to-bulk voltage (`gt_tg_i` with body true, section 3c). Without the axis (body false) the 2-D tables are made with source and bulk on one rail, and ngspice's real cell is 30 and 24 percent above the model (section 3b); with each bulk tied to its own source ngspice and the 2-D model agree to 0.06 percent, which is how the cause was found. The 2592 ohm of a golden tgate in `sar_kick.t27` is a different measurement (50 C, the golden deck's own bias) and is not compared. - dff. Gate level (`sar_logic.t27`): capture, hold, set, reset and the clock-to-Q delay are measured on it; the levels in volts are NOT MODELLED. The dff clock-to-Q is the first stage of the sequencer, Valid rise to CK11 rise with D = VDD, 546 ps (ngspice's own is 550 ps). -- dynamic_comparator. The PSP103 current tables at 50 C, the golden deck's temperature, with M4b's solver (`sar_comparator_psp.t27`). It has no charge: the devices' own capacitance is not in it, and against ngspice on the golden comparator with its charge its decision comes 98 times too fast (5.35 ps against 523 ps at +2 mV; the model is within 6.5 percent of ngspice with the charge swamped, which is the comparison the tables were built for). So the decision time FAILS the 10 ps lower limit by 4.65 ps, and the FAIL is the model's: ngspice's 523 ps on the same comparator with its charge is inside 10 to 10000 ps. The same run on the 27 C tables, the report's temperature, gives 5.12 ps and fails too. The reset row is NOT MODELLED because the run is one evaluation phase of 14 ps and the clock never falls. +- dynamic_comparator. The model of `sar_comparator_q.t27` (section 3d): the eight-node latch of M4b on the PSP103 log tables at 27 C, read on the source-to-bulk axis, with the devices' own charge (M3c tables, the source terminal closed by its two edges), backward Euler of 1.6 ps. All four rows are produced and PASS: the sign (dp wins at +2 mV, dn at -2 mV), the end levels at 2 ns (1.49933 V and 2.26 mV, 0.49 and 0.02 mV from ngspice's), the reset levels 1.4 ns after the clock falls (3.08 and -0.17 mV, 0.19 and 0.08 mV from ngspice's) and the decision time, 613.07 ps, 1.75 percent over ngspice's 602.54 ps on the golden comparator at 27 C and inside the report's 10 to 10000 ps. Before it, the model was the no-charge latch on the 50 C tables of `sar_comparator_psp.t27`, whose decision came 98 times too fast (5.35 ps against 523 ps at +2 mV, within 6.5 percent of ngspice with the charge swamped, the comparison its tables were built for) and FAILED the 10 ps lower limit; that FAIL was the model's, and the model with the charge removed still fails (the negative control of section 3d). - tracking_switch. A SQUARE-LAW bootstrapped switch (`sar_switch.t27`, MOSFET level 1). The tracking error at DC is measured: 1.97 uV, which is the fixed-point arithmetic floor (ngspice's own on the square-law deck is 0.03 to 0.05 uV), and it is a row because ngspice's PSP103 run of the same cell at the same conditions (`accept_switch.cir`) gives +0.29 and -0.17 uV, 2.3 uV from it. The hold step and the droop are NOT MODELLED because the square law has no gate capacitance, so no charge injection, and no leakage: the model returns a step of 0.18 uV and a droop of exactly 0, which sit far inside +-30 mV and +-5 mV by having no effect at all. That is not a measurement of the effect the report's -1.6 and -1.7 mV show, so the rows are not passes; the same cell in ngspice with the foundry's model has the effect (-1.60 and -1.73 mV) and the doc shows it beside the rows. A model with the PSP103 switch is what the two rows need. - clock_gen, SAR_logic. Gate level with delay tables measured on the golden gates in ngspice (`sar_logic.t27`, 113 gates, the delay cell with its history); the delays are the model's own run, within 8 percent of ngspice's for every clkc edge. The slow delay is 16.145 ns (ngspice 16.147 ns), the fast one 9.822 ns (ngspice 9.669 ns, 1.6 percent), the ratio 1.644 (the report's 1.64). The stop time is taken from the eleventh Valid rise to the last clkc fall at the golden pace (40 ns): 5.704 ns (ngspice 5.807 ns); the report's 10.7 ns is not reproduced by this definition and the report's definition is not in the material this was made from, so the two are not compared. SAR_logic is driven by eleven Valid pulses of 10 ns every 20 ns, so CK11 to CK1 spans ten periods plus the 126 ps by which CK1's load makes it later than CK11. - DAC_control, DAC_control_top, neg_bottom_plate_logic. NOT MODELLED. The DAC (`sar_cdac.t27`) has ideal capacitors driven by ideal plate sources: the level a plate reaches is its input, not an output. The eleven latches with their plate lines and the N half's follow-then-complement logic are not circuits in any spec. The polarity (the plate goes to the complement of the decision) is exercised end to end by `sar_adc.t27`, whose behavioural converter matches the golden deck's codes 30 of 31 times; that is not these blocks' check. @@ -163,7 +203,7 @@ The four numbers agree within a few LSB. Without the measured offset the end cod ## 5. Conditions that differ from the report - Tracking switch: the report drives 2 pF at 0.3 V and 1.2 V; ours drives 3.93 pF (the DAC's top plate) at 0.2 V and 1.3 V. The tracking error scales with R C dVin/dt and is zero at DC in both; the number is ours at our conditions. ngspice's PSP103 cell is run at both sets (`accept_switch.cir`): the tracking error is under 0.4 uV at all four, and the hold step at the report's conditions is the report's. Our model was not run at 0.3 V, 1.2 V and 2 pF: the load and the levels are constants of `sar_switch.t27`, which M5d does not touch. -- Comparator: the report is at 27 C, the validated model is at 50 C (the golden deck's temperature). Both temperatures fail. +- Comparator: the model is at 27 C, the report's temperature (it was at 50 C, the golden deck's, before M4b-q). The clock is an ideal 1.6 ps step, the inputs are held, the loads are the 4 fF on dp and dn: the converter's delay-cell clock, the kickback of its input stages and the NAND "Valid" are not in it. The reset level is read 1.4 ns after the clock falls at 2 ns; the report's own timing of it is not in the material this was made from. - Inverter threshold: taken as the input at which the output crosses Vdd / 2 (the quantity ngspice's trip points in `mos_psp103.t27` are). The other usual definition, the input at which the output equals the input, is not computed here. - Integration: see above. The report's sample points are not in the material this was made from. - The gate-level rows run on delays measured in ngspice at tt, 50 C (the golden deck's temperature), not the report's 27 C; the square-law switch has no temperature. The M5d rows are at tt, 27 C, the report's temperature. @@ -179,6 +219,11 @@ Every check function accepts the report's own value and refuses a broken one, so | inverter threshold | PMOS table zeroed | the output never falls through Vdd / 2: the reader's no-crossing value, -0.031 V, refused by 0.5 V | | comparator sign | the inputs exchanged | the sign flips, refused | | comparator time | a missed decision, 20 ns | refused on both sides | +| comparator time (M4b-q) | the devices' charge removed | 5.40 ps, 111 times too fast, 99 percent under ngspice's: refused by the 10 ps limit and by the 10 percent bar, row and block FAIL | +| comparator time (M4b-q) | a time inside the limit but away from ngspice's (450 ps, 25 percent) | FAIL: a produced value that disagrees is the model's FAIL | +| comparator levels (M4b-q) | a winner at 1.49 V (right by the report's 50 mV, 4 mV from ngspice's) | refused by the 5 mV bar | +| comparator time (M4b-q) | the body effect removed (Vsb forced to 0) | 570.6 ps, 5.30 percent under ngspice's: inside the 10 percent bar, refused by the 3 percent test of `sar_comparator_q.t27` | +| comparator solver (M4b-q) | the source charge without its closure / the charge tables in the wrong slots / K = 2 | residual 1.88e6 units / 4.2e9 units / 2.76e5 units, against a bound of 1600: refused | | tracking error | boost capacitor removed | -25.4 mV, refused by 5 mV and, against ngspice's PSP103 cell, by the 10 uV bar | | clock ratio | the trim code ignored | 1.0, refused by 1.2 | | SAR_logic counts and order | the fifth dff with Set and Reset exchanged | 2, 5, 11 high instead of 1, 4, 11, wrong order, refused | @@ -200,10 +245,10 @@ A limit of the control: some of the report's limits are wide, and a model error ## 7. Not done, and why -- The 20 fF inverter delays, the comparator's reset, the tracking switch's hold step and droop, and the three DAC blocks are NOT MODELLED (the tgate's two resistances were too, until M3e gave the tables a source-to-bulk axis). Each needs a model that does not exist: a charge-aware inverter at 20 fF, a second clock phase for the comparator, a switch with gate capacitance and leakage (ngspice's PSP103 numbers for that cell, -1.60 and -1.73 mV and no droop, are in section 3b as the target), circuit-level plate drivers and latches. They are not approximated here. +- The 20 fF inverter delays, the tracking switch's hold step and droop, and the three DAC blocks are NOT MODELLED (the tgate's two resistances were too, until M3e gave the tables a source-to-bulk axis, and the comparator's reset until M4b-q ran a second clock phase). Each needs a model that does not exist: a charge-aware inverter at 20 fF, a switch with gate capacitance and leakage (ngspice's PSP103 numbers for that cell, -1.60 and -1.73 mV and no droop, are in section 3b as the target), circuit-level plate drivers and latches. They are not approximated here. - The nand's, the nor's and the tgate's delays into a load, and their levels in a transient: M5d does the DC levels only. - The tracking error of our model at the report's own conditions (0.3 V and 1.2 V into 2 pF), see section 5. -- The comparator's FAIL stays a FAIL. The cause is known and measured (no charge: 98 times too fast); the charge tables for the comparator are not made. +- The comparator's body effect on the CHARGE, and a deck that prints the source terminal's charge (to test the closure of section 3d), are not done; neither changes a verdict, the decision time being 1.75 percent from ngspice's. - The corners suite needs tables at -40 and 125 C. - A defect of the Zig emitter, found while making this, was worked around by renaming one identifier in `specs/analog/sar_kick.t27` (the field and local `half` of `KickThr` to `hw`, no value changed): the emitter types a local by the NAME of a variable or field in any spec of the closure, so a `half: f64` field in one spec made `(half as u64)` in `mos_table.t27`'s `blend1_w` (where `half` is a `u32`) a float conversion, and no spec could use both. It does not appear in any earlier closure because none had both. The repro is any spec that `use`s a function of `sar_kick.t27` and one of `mos_table.t27`; to be filed on #5980. @@ -212,10 +257,11 @@ A limit of the control: some of the report's limits are wide, and a model error ``` ./target/release/t27c test-report specs/analog/accept_sar.t27 ./target/release/t27b test specs/analog/accept_sar.t27 --check +./target/release/t27b test specs/analog/sar_comparator_q.t27 --check ./target/release/t27b test specs/analog/mos_psp103_gates.t27 --check ./target/release/t27b test specs/analog/mos_psp103_vsb_data.t27 --check ``` -`accept_sar.t27` has 14 tests (the verdict rule, one for each block or group of blocks, the reference run against the report, the corners line and the counts of blocks and of rows); `mos_psp103_gates.t27` has 9 (the nand, the nor, the gates' negative controls, the tgate without the body effect, the real cell against the 2-D read, the leakage, and three of M3e: the Ron with the body effect, the held-out currents between slices, the controls); `mos_psp103_vsb_data.t27` has 3 (the pins and the layout of the 12 slices, the fall of the current with Vsb, the read at Vsb = 0 against the 2-D read); `mos_table.t27` has 21 (the one of M3e: the Vsb read on two synthetic slices). Every number in sections 3, 3b and 3c is pinned in them. `t27b test --check` exits 0 with no jit/interp mismatch. +`accept_sar.t27` has 15 tests (the verdict rule, one for each block or group of blocks, the reference run against the report, the corners line and the counts of blocks and of rows); `mos_psp103_gates.t27` has 9 (the nand, the nor, the gates' negative controls, the tgate without the body effect, the real cell against the 2-D read, the leakage, and three of M3e: the Ron with the body effect, the held-out currents between slices, the controls); `mos_psp103_vsb_data.t27` has 3 (the pins and the layout of the 12 slices, the fall of the current with Vsb, the read at Vsb = 0 against the 2-D read); `mos_table.t27` has 21 (the one of M3e: the Vsb read on two synthetic slices). `sar_comparator_q.t27` has 8 (the units, the closure and the extensions of the charge, the Jacobian, the decision time at +-2 mV, the input sweep and the free start, the long run with the levels and the reset, the negative controls, the reference data). Every number in sections 3, 3b, 3c and 3d is pinned in them. `t27b test --check` exits 0 with no jit/interp mismatch. -The ngspice numbers are the printed output of three decks, none changed after printing: `IHP_PDK= IHP_OSDI= ngspice -b specs/analog/decks/accept_gates.cir` (the eight gate levels and the off leakage), `accept_tgate.cir` (the conducting tgate, the real cell and the cell with its bulks on its sources, 27 points) and `accept_switch.cir` (the PSP103 tracking switch, four copies: tracking error, hold step, droop); ngspice-47 with the PSP103 of IHP-Open-PDK 21133f21, mos_tt, 27 C. M3e adds a fourth: `psp103_idpa_vsb_tt.cir` (the log-domain current tables of both devices at seven source-to-bulk voltages and 1000 held-out currents between them, section 3c). The decks contain the W, L and m of the cells (facts of the circuit) and none of the golden netlist's text. +The ngspice numbers are the printed output of three decks, none changed after printing: `IHP_PDK= IHP_OSDI= ngspice -b specs/analog/decks/accept_gates.cir` (the eight gate levels and the off leakage), `accept_tgate.cir` (the conducting tgate, the real cell and the cell with its bulks on its sources, 27 points) and `accept_switch.cir` (the PSP103 tracking switch, four copies: tracking error, hold step, droop); ngspice-47 with the PSP103 of IHP-Open-PDK 21133f21, mos_tt, 27 C. M4b-q adds four: `sar_cmp_q_ref.cir` (the 27 C comparator: the settled start, the golden deck's start, the node rows and the reset), `sar_cmp_q_ref50.cir` (the proof that the written-out netlist is the golden comparator), `sar_cmp_q_free.cir` (the free start) and `sar_cmp_q_be.cir` (ngspice's backward Euler on a fixed grid of 0.2 to 1.6 ps), each run once on the reference host in 51 to 53 seconds and none reading the golden deck. M3e adds `psp103_idpa_vsb_tt.cir` (the log-domain current tables of both devices at seven source-to-bulk voltages and 1000 held-out currents between them, section 3c). The decks contain the W, L and m of the cells (facts of the circuit) and none of the golden netlist's text. diff --git a/specs/analog/accept_sar.t27 b/specs/analog/accept_sar.t27 index 79c114a726..dcc477822c 100644 --- a/specs/analog/accept_sar.t27 +++ b/specs/analog/accept_sar.t27 @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 ; specs/analog/accept_sar.t27 -- the per-block acceptance checks of the external team's report of 2026-10-16, as an evaluator, applied to our SAR ADC blocks ; M5c of the analog emulation epic (#10486, issue #11212, night loop #11063); M5d (issue #11263) adds the transistor-level rows of the nand, nor, tgate and tracking_switch; M3e (issue #11283) adds the source-to-bulk axis that turns the tgate's -; two resistances from NOT MODELLED to PASS. The +; two resistances from NOT MODELLED to PASS; M4b-q (issue #11312) gives the dynamic_comparator the devices' own charge and the body effect at 27 C (sar_comparator_q.t27), which turns its decision time from FAIL to PASS and +; its reset from NOT MODELLED to PASS. The ; external team's report of 2026-10-16 lists, for the 13 blocks of the golden SAR ADC, a ; measured value and the limit that its check enforces (conditions: tt corner, 27 C, VDD 1.5 V, cells taken from the integrated converter's netlist). The team ; confirmed on 2026-10-20 that the checks and their limits may be used and published in this repository. This file holds those limits as named constants, one @@ -29,9 +30,13 @@ ; percent), inside the report's 30 and 50 kohm: all three rows PASS and the block PASSes. Before M3e the tables had no body effect and read 1718 and 2109 ohm, 30 and 24 ; percent under the real cell, past the 10 percent bar, and the two rows were NOT MODELLED; that read is kept (body false) as the negative control and still gives NOT MODELLED. ; dff the gate level (inside SAR_logic): capture, hold, set, reset and the clock-to-Q delay are measured on it; the levels in volts are NOT MODELLED. -; dynamic_comparator PSP103 current tables at 50 C, the golden deck's temperature (sar_comparator_psp.t27), the NO-CHARGE model: the devices' own capacitance is not in it, so -; its decision comes 98 times too fast against ngspice (5.35 ps against 523 ps at +2 mV). The sign and the end levels are measured; the reset (a second clock -; phase) is not run; the decision time is measured and FAILS the 10 ps lower limit. +; dynamic_comparator the model of sar_comparator_q.t27 (M4b-q): the PSP103 log tables of the two devices at 27 C, the report's temperature, read on the source-to-bulk axis (the input pair and XM8, XM9 have +; their source off their bulk), the devices' own drain, gate and source charge from the M3c tables, backward Euler of 1.6 ps. Before it the comparator was the NO-CHARGE +; model on the 50 C tables, which decided 98 times too fast (5.35 ps against the 523 ps of ngspice's golden-deck start at +2 mV) and FAILED the 10 ps lower limit. Now all four +; rows are produced: the sign at +-2 mV, the end levels at 2 ns, the reset levels 1.4 ns after the clock has fallen (at 2 ns), and the decision time, 613.07 ps. A row is PASS +; only inside the report's limit AND within the bar of ngspice-47's run of the same comparator at 27 C (the settled start, sar_cmp_q_ref.cir: the decision 602.537 ps, the levels +; 1.49884 V and 2.283 mV at 2 ns, 3.265 mV and -0.247 mV in the reset): 10 percent for the time (it is 1.75 percent over), 5 mV for a level (0.49 mV and 0.19 mV off); a value +; that disagrees is the model's FAIL. All four rows PASS, so the block PASSes. NEGATIVE CONTROL: the same model with the devices' charge removed decides at 5.40 ps and FAILS. ; tracking_switch SQUARE LAW (MOSFET level 1) bootstrapped switch (sar_switch.t27): the tracking error at DC is measured (1.97 uV, the arithmetic floor) and agrees with ngspice's ; PSP103 run of the same cell to 2.3 uV; the hold step and the droop are NOT MODELLED, because the square law has no gate capacitance (no charge injection) and no ; leakage: the model's 0.18 uV and 0 V would "pass" a 30 mV and a 5 mV limit by having no effect at all, which is not a measurement of the effect. ngspice's PSP103 @@ -45,15 +50,16 @@ ; - ; WHAT THE ROWS ARE NOT. The conditions differ in places and each is stated in the doc: the report's tracking switch drives 2 pF at 0.3 and 1.2 V, ours drives 3.93 pF at ; 0.2 and 1.3 V (ngspice's PSP103 cell is run at both: the tracking error is under 0.4 uV at all four, and the hold step at the report's conditions is the report's); the levels of the -; nand and nor are the DC solution, which is what a gate settles to into 10 fF, not a transient; the report's comparator is at 27 C, ours at 50 C (the 27 C tables give 5.12 ps and -; fail too); the report's ramp for the converter has 8 conversions +; nand and nor are the DC solution, which is what a gate settles to into 10 fF, not a transient; the comparator is at 27 C as the report's is, on a 1.6 ps backward Euler grid, with the inputs held +; and the clock an ideal step, not the converter's delay-cell clock, its input stages' kickback or its NAND "Valid"; the reset level is read 1.4 ns after the clock falls +; (the report's own timing of it is not in the material this file was made from); the report's ramp for the converter has 8 conversions ; whose ideal end codes are 517 and 1579, ours is the literal -1 to +1 V ramp, and the report's own ramp is rebuilt as context only. The stop time of the clock is taken ; from the eleventh Valid rise to the last clkc fall at the golden pace of 40 ns; the report's 10.7 ns is not reproduced by that definition (ours is 5.70 ns) and its ; definition is not in the material this file was made from. ; - ; THE NEGATIVE CONTROLS (FR-004), each a test that passes by seeing the failure. Every check function refuses a broken value (a delay out of range, a swapped bit, a wrong ; code) and accepts the report's own measured value, so no check is always-FAIL or always-PASS. Where a model can be broken to leave the limit, that is done too: the -; PMOS table zeroed (the output never falls through Vdd / 2: the reader returns -0.031 V, no crossing), the inputs of the comparator exchanged (the sign flips), the boost capacitor removed (tracking error 25.4 mV, refused by the limit and by the cross-check), the trim code +; PMOS table zeroed (the output never falls through Vdd / 2: the reader returns -0.031 V, no crossing), the inputs of the comparator exchanged (the sign flips), the comparator's charge removed (5.40 ps, refused by the 10 ps limit and by the cross-check), the boost capacitor removed (tracking error 25.4 mV, refused by the limit and by the cross-check), the trim code ; ignored (ratio 1.0), a dff with Set and Reset exchanged (the order, the counts and the set and reset rows fail), a converter whose MSB capacitor is halved (237 LSB), a gate with ; its PMOS or NMOS table zeroed (the 1 levels at 0 V, the 0 level at 1.5 V), the tgate with the NMOS table in both slots (521 pA, inside the report's 1 nA and 82 percent over ngspice's, so the cross-check refuses it), the tgate read with Vsb forced to 0 (M3e: 1718 and 2109 ohm, inside the report's limits and 30 and 24 percent under ngspice's real cell, so the cross-check refuses it and the rows are NOT MODELLED). ; Honest limit of the control: a few of the report's limits are wide (the inverter threshold 0.5 to 1.0 V, a bridge of 2 units gives 45.5 LSB against 64), and a model @@ -64,10 +70,11 @@ ; (the report 2.44 kohm) and 2776.1 ohm worst (2.78 kohm), 0.2856 nA off (0.29 nA), and the PSP103 tracking switch -1.596 and -1.730 mV of hold step (-1.6 and -1.7 mV), so the ; numbers a model is judged by are the report's cells; a test pins each. ; - -; WHAT A PASS SAYS. For the clock_gen, SAR_logic, integration, nand, nor and tgate blocks: every quantity the report's check enforces is produced by our model and is inside the report's +; WHAT A PASS SAYS. For the clock_gen, SAR_logic, integration, nand, nor, tgate and dynamic_comparator blocks: every quantity the report's check enforces is produced by our model and is inside the report's ; limit. For a row marked PASS inside a NOT MODELLED block: that quantity, at the model's level above, is inside the limit. WHAT IT DOES NOT SAY. Nothing about a transistor-level ; result for the gate-level blocks (dff, clock_gen, SAR_logic), nothing about voltage levels of any digital node but the nand's and nor's, nothing about the delay of any gate into a load, -; nothing about the tgate's resistance beyond the 27 outputs at 10 mV across it (the body effect is in the tables from M3e, at 27 C), nothing about power, noise, mismatch or another corner or temperature (the report's smoke +; nothing about the tgate's resistance beyond the 27 outputs at 10 mV across it (the body effect is in the tables from M3e, at 27 C), nothing about the comparator's nodes between the end levels +; (up to 83 mV off ngspice's in the regeneration, 0.31 V in the reset) or its resolution time (4.9 percent early), nothing about power, noise, mismatch or another corner or temperature (the report's smoke ; suite runs five corners at -40, 27 and 125 C; our tables have no -40 or 125 C and the line is NOT MODELLED), nothing about speed. The report's numbers are the report's; ours ; are computed here by the named functions. ASCII only, English only. ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -78,9 +85,9 @@ use analog::linear::{fabs64, q_sm}; use analog::mos_psp103::{dc_out, cross_idx, rel_gap, DC_POINTS, DC_STEP}; use analog::mos_psp103_data::{TT_LG_N, TT_LG_P, TT_N, TT_P}; use analog::mos_psp103_gates::{GT_B_LEVEL_V, GT_B_REL, GT_NG_RTG, GT_NG_LEAK_OUT_A, GT_NG_LEAK_IN_A, Lv, gt_levels, gt_tg_ron, gt_tg_v, gt_tg_worst, gt_tg_off, gt_ref_max}; -use analog::mos_psp103_50c_data::{TT50_N, TT50_P}; -use analog::sar_comparator::{SC_DV_M, SC_K, SC_N, SC_NN, SC_DP, SC_DN, SC_UPV, SC_VCM, SC_VTH_V, SC_H2, sc_sign, sc_build_table}; -use analog::sar_comparator_psp::{sp_run, sp_first}; +use analog::sar_comparator::{SC_NN, SC_DP, SC_DN, SC_VCM, SC_VTH_V, SC_H2, sc_build_table}; +use analog::sar_comparator_q::{SQ_K, SQ_NS, SQ_NL, SQ_JF, SQ_M_NOCHARGE, SQ_M_NOBODY, SQ_BAR_LEVEL_V, NG_S_FIRST_PS, NG_NODES_TRAP, NG_RESET_TRAP, sq_run, sq_t_first, sq_ps, sq_volts, sq_sign}; +use analog::sar_comparator_psp::{sp_rel}; use analog::sar_switch::{SW_CB, SW_K, SW_DV_M, sw_make_vin, sw_run, sw_widen, sw_te, sw_inj, sw_droop}; use analog::sar_logic::{SL_VAL, SL_S0, SL_S1, SL_TMIN, SL_NG_CLKS_F, sl_build, sl_put, sl_run, sl_edge, sl_nand_code, sl_nor_code}; use analog::sar_adc::{convert_sar, model_k, CP_FIT}; @@ -109,7 +116,6 @@ pub const AS_N_ROWS : u32 = 30; pub const AS_WHY_INV_DELAY : str = "the transient is validated at 500 fF and 10 pF only; at 20 fF the gate edge kicks the output outside the tables' 0 to Vdd range"; pub const AS_WHY_LEVELS : str = "the model is gate level: a node is 0, 1 or X, never a voltage"; pub const AS_WHY_TG_RON : str = "a read with no body effect (Vsb forced to 0) puts ours 30 percent under ngspice's real cell at 0.75 V and 24 percent under at the worst point, past the 10 percent bar; with each bulk tied to its source ngspice agrees with ours to 0.06 percent; with the source-to-bulk axis (M3e) the rows are measured and PASS"; -pub const AS_WHY_CMP_RESET : str = "the comparator run is one evaluation phase of 14 ps; the clock never falls, so there is no reset phase to measure"; pub const AS_WHY_SW_HOLD : str = "square law: no gate capacitance, so no charge injection; the hold step the model returns is a following artifact of 0.18 uV"; pub const AS_WHY_SW_DROOP : str = "square law: no leakage; the droop the model returns is exactly zero by construction"; pub const AS_WHY_DAC_CONTROL : str = "the DAC takes its plate levels as ideal sources: the level is the input, not a result"; @@ -418,23 +424,39 @@ pub fn as_inv_vth_of(tn: []const u32, tp: []const u32) -> f64 { return cross_idx(&model, (SC_VCM as f64), false, 0, 48) * 0.03125; } -/// The comparator at +2 mV (win_p, the end levels, the decision time) and at -2 mV (win_m), 0.75 V common mode, current tables tn, tp. The decision time is in ps. -pub struct Cmp { win_p: u32, win_m: u32, hi_v: f64, lo_v: f64, dt_ps: f64 } - -pub fn as_cmp(tn: []const u32, tp: []const u32) -> Cmp { - var out : [1128]u32 = [0] ** 1128; - var res : [140]u32 = [0] ** 140; - const sa : u64 = sp_run(tn, tp, SC_H2, false, SC_K, &SC_DV_M, &out, &res); +/// The comparator of the report on the model of sar_comparator_q.t27 (M4b-q: tt 27 C, the PSP103 log tables on the source-to-bulk axis, the devices' own charge, backward Euler of 1.6 ps), mode 0, or one of +/// the broken forms of that file (SQ_M_NOCHARGE, SQ_M_NOBODY: the negative controls). The +2 mV run is the long one, the clock falling at 2 ns (step SQ_JF) and the run ending 1.4 ns later (step SQ_NL): win_p, +/// whether dp is above dn at 2 ns (the step before the clock falls), the winner hi_v and the loser lo_v there, the two outputs 1.4 ns into the reset (rst_p_v, rst_n_v) and the decision time dt_ps (the first output +/// through 0.75 V after the clock's 50 % point, ps); the -2 mV run is the short one, 438 steps: win_m, whether dp is above dn at its end. The CROSS-CHECK against ngspice-47's own run of the same comparator at +/// 27 C, the settled start (specs/analog/decks/sar_cmp_q_ref.cir): g_dt, the relative gap of the decision time to ngspice's 602.537 ps; g_lv, the larger distance in V of the winner and the loser at 2 ns from +/// ngspice's at the same instant; g_rv, the same for the two outputs 1.4 ns into the reset. +pub struct Cmp { win_p: u32, win_m: u32, hi_v: f64, lo_v: f64, rst_p_v: f64, rst_n_v: f64, dt_ps: f64, g_dt: f64, g_lv: f64, g_rv: f64 } + +pub fn as_cmp(mode: u32) -> Cmp { + var out : [17008]u32 = [0] ** 17008; + var res : [2125]u32 = [0] ** 2125; + const sa : u64 = sq_run(SC_H2, false, SQ_K, SQ_NL, mode, 0, SQ_JF, &out, &res); + const k2 : u32 = (SQ_JF - 1) * SC_NN; + const dp : f64 = sq_volts(out[(k2 + SC_DP) as usize]); + const dn : f64 = sq_volts(out[(k2 + SC_DN) as usize]); var wp : u32 = 0; - if (sc_sign(&out)) { wp = 1; } - const last : u32 = SC_N * SC_NN; - const dp : f64 = ((out[(last + SC_DP) as usize]) as f64) / SC_UPV; - const dn : f64 = ((out[(last + SC_DN) as usize]) as f64) / SC_UPV; - const dt : f64 = sp_first(&out) * 0.1; - const sb : u64 = sp_run(tn, tp, SC_H2, true, SC_K, &SC_DV_M, &out, &res); + if (dp > dn) { wp = 1; } + const kr : u32 = SQ_NL * SC_NN; + const rp : f64 = sq_volts(out[(kr + SC_DP) as usize]); + const rn : f64 = sq_volts(out[(kr + SC_DN) as usize]); + const dt : f64 = sq_ps(sq_t_first(&out, SQ_NL)); + var short : [3512]u32 = [0] ** 3512; + var sres : [438]u32 = [0] ** 438; + const sb : u64 = sq_run(SC_H2, true, SQ_K, SQ_NS, mode, 0, 0, &short, &sres); var wm : u32 = 0; - if (sc_sign(&out)) { wm = 1; } - return Cmp { win_p: wp, win_m: wm, hi_v: dp, lo_v: dn, dt_ps: dt }; + if (sq_sign(&short, SQ_NS)) { wm = 1; } + var g_lv : f64 = fabs64(dp - NG_NODES_TRAP[12 * 8 + 5]); + const g_lo : f64 = fabs64(dn - NG_NODES_TRAP[12 * 8 + 6]); + if (g_lo > g_lv) { g_lv = g_lo; } + var g_rv : f64 = fabs64(rp - NG_RESET_TRAP[7 * 8 + 5]); + const g_rn : f64 = fabs64(rn - NG_RESET_TRAP[7 * 8 + 6]); + if (g_rn > g_rv) { g_rv = g_rn; } + return Cmp { win_p: wp, win_m: wm, hi_v: dp, lo_v: dn, rst_p_v: rp, rst_n_v: rn, dt_ps: dt, g_dt: fabs64(sp_rel(dt, NG_S_FIRST_PS[0])), g_lv: g_lv, g_rv: g_rv }; } /// The tracking switch's run for stimulus sel (0 to 2: DC at 0.2, 0.75, 1.3 V) with boost capacitor cb (SW_CB, or 0 for the negative control): the tracking error at the @@ -750,17 +772,23 @@ pub fn as_blk_dff() -> Blk { return b; } -/// dynamic_comparator: the sign at +-2 mV, the end levels, the decision time, all on the PSP103 50 C tables; the reset is not run. -pub fn as_blk_comparator() -> Blk { +/// dynamic_comparator: the four rows of a measured comparator c (as_cmp). The model produces every quantity, so no row is NOT MODELLED; and a row is a PASS only inside the report's limit AND within the +/// bar of ngspice's run of the same comparator (the decision time within 10 percent, a level within 5 mV, SQ_BAR_LEVEL_V): a value that disagrees with ngspice is the model's FAIL, as the issue (#11312) +/// states it, not an unmeasured row. The sign is ngspice's own (dp wins at +2 mV and dn at -2 mV in the run it is checked against). +pub fn as_blk_cmp(c: Cmp) -> Blk { var b : Blk = as_blk0(); - const c : Cmp = as_cmp(&TT50_N, &TT50_P); b = as_add(b, as_verdict(true, as_chk_cmp_sign(c.win_p, c.win_m))); - b = as_add(b, as_verdict(true, as_chk_cmp_levels(c.hi_v, c.lo_v))); - b = as_add(b, as_nm()); - b = as_add(b, as_verdict(true, as_chk_cmp_time(c.dt_ps))); + b = as_add(b, as_verdict(true, as_chk_cmp_levels(c.hi_v, c.lo_v) and c.g_lv < SQ_BAR_LEVEL_V)); + b = as_add(b, as_verdict(true, as_chk_cmp_reset(c.rst_p_v, c.rst_n_v) and c.g_rv < SQ_BAR_LEVEL_V)); + b = as_add(b, as_verdict(true, as_chk_cmp_time(c.dt_ps) and c.g_dt < GT_B_REL)); return b; } +/// dynamic_comparator on the model of sar_comparator_q.t27. +pub fn as_blk_comparator() -> Blk { + return as_blk_cmp(as_cmp(0)); +} + /// tracking_switch: the tracking error at 0.2 V and 1.3 V (3.93 pF), a row because both are within 10 uV of ngspice's PSP103 run of the same cell at the same conditions /// (accept_switch.cir); the hold step and the droop are not modelled. pub fn as_blk_switch() -> Blk { @@ -1129,33 +1157,43 @@ test the_dff_behaviour_and_clock_to_q_are_measured_at_gate_level_and_a_dff_with_ assert_eq(as_block_verdict(b), AS_NM); } -test the_comparator_decides_the_right_way_to_the_rails_and_its_decision_time_is_refused_by_the_10_ps_lower_limit { - const c : Cmp = as_cmp(&TT50_N, &TT50_P); - // the sign at +-2 mV: dp wins at +2 mV and dn at -2 mV +test the_comparator_rows_are_measured_on_the_27_c_charge_model_and_all_four_pass_inside_the_limits_and_within_the_bars_of_ngspice { + const c : Cmp = as_cmp(0); + // the sign at +-2 mV: dp wins at +2 mV and dn at -2 mV, as in ngspice's run of the same comparator assert_eq(c.win_p, 1); assert_eq(c.win_m, 0); assert(as_chk_cmp_sign(c.win_p, c.win_m)); - // the end levels after 14 ps: the winner at 1.49996 V, the loser at 3.6 mV - assert(c.hi_v > 1.4999); - assert(c.hi_v < 1.5); - assert(c.lo_v > 0.003); - assert(c.lo_v < 0.004); + // THE LEVELS after the decision, 2 ns after the start (sar_comparator_q.t27): the winner 1.49933 V, the loser 2.26 mV; ngspice's 1.49884 V and 2.283 mV at the same instant, 0.49 mV and 0.02 mV from ours, + // the bar 5 mV (SQ_BAR_LEVEL_V, a tenth of the report's 50 mV) + assert(c.hi_v > 1.4993); + assert(c.hi_v < 1.4994); + assert(c.lo_v > 0.00225); + assert(c.lo_v < 0.00227); assert(as_chk_cmp_levels(c.hi_v, c.lo_v)); - // THE DECISION TIME: 5.35 ps (53.50 steps of 0.1 ps after the clock's 50 % point, sar_comparator_psp.t27), below the 10 ps limit: FAIL, by 4.65 ps - assert(c.dt_ps > 5.349); - assert(c.dt_ps < 5.351); - assert(!as_chk_cmp_time(c.dt_ps)); - assert(c.dt_ps < L_CMP_DT_LO_PS); - // the same on the 27 C tables (the report's temperature): 5.12 ps, also refused - const c27 : Cmp = as_cmp(&TT_N, &TT_P); - assert(c27.dt_ps > 5.11); - assert(c27.dt_ps < 5.13); - assert(!as_chk_cmp_time(c27.dt_ps)); - assert_eq(c27.win_p, 1); - assert_eq(c27.win_m, 0); - // the check is not always-FAIL: the report's 607 ps passes, and so would ngspice's own 523 ps on the golden comparator with its charge; 20 ns and a missed decision (-0.1) are refused + assert(c.g_lv > 0.00048); + assert(c.g_lv < 0.00050); + assert(c.g_lv < SQ_BAR_LEVEL_V); + // THE RESET (the clock falls at 2 ns; both outputs within 50 mV of 0 V): 1.4 ns later dp is 3.08 mV and dn -0.17 mV, ngspice's 3.265 mV and -0.247 mV, 0.19 mV and 0.08 mV away + assert(c.rst_p_v > 0.00307); + assert(c.rst_p_v < 0.00309); + assert(c.rst_n_v < 0.0 - 0.00016); + assert(c.rst_n_v > 0.0 - 0.00018); + assert(as_chk_cmp_reset(c.rst_p_v, c.rst_n_v)); + assert(c.g_rv > 0.00017); + assert(c.g_rv < 0.00020); + assert(c.g_rv < SQ_BAR_LEVEL_V); + // THE DECISION TIME: 613.07 ps (the first output through 0.75 V after the clock's 50 % point), inside the report's 10 ps to 10 ns, 1.75 percent over ngspice's 602.537 ps on the golden comparator at 27 C + // (the settled start, sar_cmp_q_ref.cir) and 1.00 percent over the report's 607 ps; the bar is 10 percent + assert(c.dt_ps > 613.07); + assert(c.dt_ps < 613.08); + assert(as_chk_cmp_time(c.dt_ps)); + assert(c.g_dt > 0.0174); + assert(c.g_dt < 0.0176); + assert(c.g_dt < GT_B_REL); + assert(fabs64(sp_rel(c.dt_ps, R_CMP_DT_PS)) < 0.0101); + // the check is not always-FAIL or always-PASS: the report's 607 ps passes, ngspice's 602.5 ps does, 20 ns and a missed decision (-0.1) are refused assert(as_chk_cmp_time(R_CMP_DT_PS)); - var ng : f64 = 523.032; + var ng : f64 = NG_S_FIRST_PS[0]; var long : f64 = 20000.0; var none : f64 = 0.0 - 0.1; assert(as_chk_cmp_time(ng)); @@ -1163,21 +1201,66 @@ test the_comparator_decides_the_right_way_to_the_rails_and_its_decision_time_is_ assert(!as_chk_cmp_time(none)); // NEGATIVE CONTROL on the model, the sign: the inputs wired the wrong way round is the same run read with the roles exchanged, and the sign check refuses it assert(!as_chk_cmp_sign(c.win_m, c.win_p)); - // the levels check refuses a latch that stops half way, and a loser that does not fall + // the levels check refuses a latch that stops half way, and a loser that does not fall; the reset check refuses an output left high var half_hi : f64 = 1.2; var half_lo : f64 = 0.3; assert(!as_chk_cmp_levels(half_hi, c.lo_v)); assert(!as_chk_cmp_levels(c.hi_v, half_lo)); - // the reset row is not run: the report's reset level (0 V, within 50 mV) is checkable but nothing of ours is fed to it var z : f64 = 0.0; var up : f64 = 1.5; assert(as_chk_cmp_reset(z, z)); assert(!as_chk_cmp_reset(up, z)); + // the block: all four rows measured and PASS, so the block is a PASS (it was two PASS, one FAIL and one NOT MODELLED on the no-charge model at 50 C) const b : Blk = as_blk_comparator(); - assert_eq(b.pass, 2); - assert_eq(b.fail, 1); - assert_eq(b.nm, 1); - assert_eq(as_block_verdict(b), AS_FAIL); + assert_eq(b.pass, 4); + assert_eq(b.fail, 0); + assert_eq(b.nm, 0); + assert_eq(as_block_verdict(b), AS_PASS); +} + +test the_comparator_with_its_charge_removed_still_fails_and_a_time_inside_the_limit_but_away_from_ngspice_fails_too { + // NEGATIVE CONTROL (FR-004 of #11312): the same model with the devices' own charge removed (SQ_M_NOCHARGE: the lumped capacitors of the square-law model on every node) decides at 5.40 ps, 111 times too + // fast, 99 percent under ngspice's, under the report's 10 ps lower limit: the decision time row FAILS, and so does the block. (At the 1.6 ps step the no-charge latch does not regenerate, both outputs end at + // 0.786 V, so its sign is the rounding's and no claim is made about it; the control is a control of the time.) + const z : Cmp = as_cmp(SQ_M_NOCHARGE); + assert(z.dt_ps > 5.400); + assert(z.dt_ps < 5.402); + assert(!as_chk_cmp_time(z.dt_ps)); + assert(z.g_dt > 0.99); + assert(z.g_dt > 9.0 * GT_B_REL); + assert_eq(as_verdict(true, as_chk_cmp_time(z.dt_ps) and z.g_dt < GT_B_REL), AS_FAIL); + const bz : Blk = as_blk_cmp(z); + assert(bz.fail >= 1); + assert_eq(as_block_verdict(bz), AS_FAIL); + // the cross-check is part of the row: a decision time INSIDE the report's limit (450 ps) but 25 percent from ngspice's is a FAIL too, not a PASS and not an unmeasured row + const c : Cmp = as_cmp(0); + var late : f64 = 450.0; + assert(as_chk_cmp_time(late)); + const w : Cmp = Cmp { win_p: c.win_p, win_m: c.win_m, hi_v: c.hi_v, lo_v: c.lo_v, rst_p_v: c.rst_p_v, rst_n_v: c.rst_n_v, dt_ps: late, g_dt: fabs64(sp_rel(late, NG_S_FIRST_PS[0])), g_lv: c.g_lv, g_rv: c.g_rv }; + assert(w.g_dt > 0.25); + assert(w.g_dt > GT_B_REL); + const bw : Blk = as_blk_cmp(w); + assert_eq(bw.pass, 3); + assert_eq(bw.fail, 1); + assert_eq(as_block_verdict(bw), AS_FAIL); + // a level that is right by the report's 50 mV and 4 mV from ngspice's is refused by the 5 mV bar: the winner at 1.495 V + var near : f64 = 1.495; + assert(as_chk_cmp_levels(near, c.lo_v)); + const l : Cmp = Cmp { win_p: c.win_p, win_m: c.win_m, hi_v: near, lo_v: c.lo_v, rst_p_v: c.rst_p_v, rst_n_v: c.rst_n_v, dt_ps: c.dt_ps, g_dt: c.g_dt, g_lv: fabs64(near - NG_NODES_TRAP[12 * 8 + 5]), g_rv: c.g_rv }; + assert(l.g_lv > 0.003); + assert(l.g_lv < SQ_BAR_LEVEL_V); + var far : f64 = 1.49; + const l2 : Cmp = Cmp { win_p: c.win_p, win_m: c.win_m, hi_v: far, lo_v: c.lo_v, rst_p_v: c.rst_p_v, rst_n_v: c.rst_n_v, dt_ps: c.dt_ps, g_dt: c.g_dt, g_lv: fabs64(far - NG_NODES_TRAP[12 * 8 + 5]), g_rv: c.g_rv }; + assert(as_chk_cmp_levels(far, c.lo_v)); + assert(l2.g_lv > SQ_BAR_LEVEL_V); + assert_eq(as_blk_cmp(l2).fail, 1); + // the body effect removed (SQ_M_NOBODY) is 5.30 percent under ngspice's decision time: INSIDE the 10 percent bar, which does not see it (sar_comparator_q.t27 has a 3 percent test that does) + const nb : Cmp = as_cmp(SQ_M_NOBODY); + assert(nb.dt_ps > 570.55); + assert(nb.dt_ps < 570.65); + assert(nb.g_dt > 0.0528); + assert(nb.g_dt < 0.0532); + assert(nb.g_dt < GT_B_REL); } test the_tracking_error_is_measured_on_the_square_law_switch_and_the_hold_step_and_droop_are_not_modelled_because_it_has_no_charge_and_no_leak { @@ -1421,7 +1504,7 @@ test the_integration_ramp_is_measured_on_the_behavioural_converter_and_a_halved_ } test the_corners_smoke_suite_is_not_modelled_and_its_checks_refuse_a_delay_out_of_range { - // the model has the inverter in five corners at 27 C and the comparator in tt at 27 C and 50 C, none at -40 or 125 C: the grid of the report cannot be run + // the model has the inverter in five corners at 27 C and the comparator in tt at 27 C, none at -40 or 125 C and no other corner of the comparator: the grid of the report cannot be run var inv_lo : f64 = 62.0; var inv_hi : f64 = 124.0; var cmp_lo : f64 = 467.0; @@ -1434,20 +1517,22 @@ test the_corners_smoke_suite_is_not_modelled_and_its_checks_refuse_a_delay_out_o var slow : f64 = 200.0; assert(!as_chk_corner_inv(fast)); assert(!as_chk_corner_inv(slow)); - // the no-charge comparator's 5.35 ps is two orders of magnitude under the lower limit - const c : Cmp = as_cmp(&TT50_N, &TT50_P); - assert(!as_chk_corner_cmp(c.dt_ps)); + // the tt 27 C comparator's 613 ps is inside the corners' 467 to 829 ps, which says nothing about the other 14 points of the grid; the charge-removed control's 5.4 ps is two orders of magnitude under it + const c : Cmp = as_cmp(0); + assert(as_chk_corner_cmp(c.dt_ps)); + const z : Cmp = as_cmp(SQ_M_NOCHARGE); + assert(!as_chk_corner_cmp(z.dt_ps)); assert_eq(as_nm(), AS_NM); } -test the_thirteen_blocks_count_6_pass_1_fail_and_6_not_modelled_and_their_30_rows_21_pass_1_fail_and_8_not_modelled { +test the_thirteen_blocks_count_7_pass_0_fail_and_6_not_modelled_and_their_30_rows_23_pass_0_fail_and_7_not_modelled { // the blocks, one by one, each by the strict rule assert_eq(as_block_verdict(as_blk_inverter()), AS_NM); assert_eq(as_block_verdict(as_blk_nand()), AS_PASS); assert_eq(as_block_verdict(as_blk_nor()), AS_PASS); assert_eq(as_block_verdict(as_blk_tgate()), AS_PASS); assert_eq(as_block_verdict(as_blk_dff()), AS_NM); - assert_eq(as_block_verdict(as_blk_comparator()), AS_FAIL); + assert_eq(as_block_verdict(as_blk_comparator()), AS_PASS); assert_eq(as_block_verdict(as_blk_switch()), AS_NM); assert_eq(as_block_verdict(as_blk_clock()), AS_PASS); assert_eq(as_block_verdict(as_blk_sar_logic()), AS_PASS); @@ -1457,15 +1542,16 @@ test the_thirteen_blocks_count_6_pass_1_fail_and_6_not_modelled_and_their_30_row assert_eq(as_block_verdict(as_blk_integration()), AS_PASS); // the counts over the 13 blocks, and they add up: nothing is counted twice and no block is left out const t : Blk = as_all_blocks(); - assert_eq(t.pass, 6); - assert_eq(t.fail, 1); + assert_eq(t.pass, 7); + assert_eq(t.fail, 0); assert_eq(t.nm, 6); assert_eq(t.pass + t.fail + t.nm, AS_N_BLOCKS); - // and over the rows of those blocks (the corners line is not a block and has no row here): 30 rows, of which 21 PASS, 1 FAIL and 8 NOT MODELLED (before M3e's source-to-bulk axis the - // tgate's two resistances were NOT MODELLED: 19, 1 and 10 rows, 5, 1 and 7 blocks) + // and over the rows of those blocks (the corners line is not a block and has no row here): 30 rows, of which 23 PASS, 0 FAIL and 7 NOT MODELLED (before M4b-q's charge the comparator's + // decision time FAILED and its reset was NOT MODELLED: 21, 1 and 8 rows, 6, 1 and 6 blocks; before M3e's source-to-bulk axis the tgate's two resistances were NOT MODELLED: 19, 1 and 10 + // rows, 5, 1 and 7 blocks) const r : Blk = as_all_rows(); - assert_eq(r.pass, 21); - assert_eq(r.fail, 1); - assert_eq(r.nm, 8); + assert_eq(r.pass, 23); + assert_eq(r.fail, 0); + assert_eq(r.nm, 7); assert_eq(r.pass + r.fail + r.nm, AS_N_ROWS); } diff --git a/specs/analog/decks/sar_cmp_q_be.cir b/specs/analog/decks/sar_cmp_q_be.cir new file mode 100644 index 0000000000..73c18827b5 --- /dev/null +++ b/specs/analog/decks/sar_cmp_q_be.cir @@ -0,0 +1,194 @@ +SAR ADC dynamic comparator on PSP103 at 27 C with all its charge: backward Euler on a fixed grid of 0.2, 0.4, 0.8 and 1.6 ps, the scheme of sar_comparator_q.t27 +* specs/analog/decks/sar_cmp_q_be.cir -- reference data for specs/analog/sar_comparator_q.t27 (analog M4b-q, #11312, epic #10486). The netlist and the settled start of sar_cmp_q_ref.cir (read +* its header: the .ic WITHOUT uic, the DC operating point with the eight nodes forced), integrated by the scheme the model uses: `.options method=gear maxord=1` is backward Euler and trtol 1e7 +* stops the truncation test from shrinking a step (the recipe of sar_cmp_psp_r2.cir), the step fixed by tran's tstep and tmax, the clock a B source of nint(time / dt) (no breakpoint: ngspice's +* time points are a few fs before a multiple of dt, and nint puts each on its grid index): 0 at index 0, then a ramp of R steps (R dt = 1.6 ps, the spec's clock) to 1.5 V. Index j of this deck is +* step j of the model. The same inputs as sar_cmp_q_ref.cir. Run: IHP_PDK= IHP_OSDI= ngspice -b sar_cmp_q_be.cir > sar_cmp_q_be.out (ngspice-47) +* What it prints, per run, after "RUN
": tck (the clock through 0.75 V), tfirst, tres, dpend, dnend (at 2 ns); the +2 mV run of each dt adds "NODES
": the eight nodes +* ap an net1 net2 net3 dp dn n_clkc at the times of sar_cmp_q_ref.cir. The last block ("RESETRUN", 1.6 ps grid, the clock falling at index 1250, 2 ns) is the reset phase of sar_cmp_q_ref.cir on this grid. +.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 +.param dtp=0.8p +.param rr=2 +.param nf=1000000000 +Bclk clkc 0 V = 1.5*min(max(nint(time/{dtp}),0),{rr})/{rr}*(1-min(max(nint(time/{dtp})-{nf},0),{rr})/{rr}) +XM1 ap clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM2 an clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM3 ap vdn net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM4 an vdp net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM5 net1 clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=18 +XM6 net2 dp vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM7 net3 dn vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM8 dp an net3 vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM9 dn ap net2 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 n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM13 dp n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XIP n_clkc clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN n_clkc clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +Cbdp dp 0 4f +Cbdn dn 0 4f +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.ic v(ap)=1.5 v(an)=1.5 v(net1)=0.4009999632835388 v(net2)=1.5 v(net3)=1.5 v(dp)=0 v(dn)=0 v(n_clkc)=1.5 +.temp 27 +.options method=gear maxord=1 trtol=1e7 reltol=1e-7 vntol=1e-10 abstol=1e-12 gmin=1e-12 itl4=200 +.control +set num_threads=2 +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +alterparam dtp = 0.2p +alterparam rr = 8 +reset +foreach h 16777 4194 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 0.2p 2n 0 0.2p + echo RUN 200 $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=2n + meas tran dnend find v(dn) at=2n + end +end +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 0.2p 2.3n 0 0.2p +meas tran tck when v(clkc)=0.75 rise=1 +foreach dtn 0p 10p 25p 50p 100p 200p 300p 400p 450p 500p 1000p 1500p 2000p + let tt = tck + $dtn + echo NODES 200 $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +alterparam dtp = 0.4p +alterparam rr = 4 +reset +foreach h 16777 4194 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 0.4p 2n 0 0.4p + echo RUN 400 $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=2n + meas tran dnend find v(dn) at=2n + end +end +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 0.4p 2.3n 0 0.4p +meas tran tck when v(clkc)=0.75 rise=1 +foreach dtn 0p 10p 25p 50p 100p 200p 300p 400p 450p 500p 1000p 1500p 2000p + let tt = tck + $dtn + echo NODES 400 $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +alterparam dtp = 0.8p +alterparam rr = 2 +reset +foreach h 16777 4194 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 0.8p 2n 0 0.8p + echo RUN 800 $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=2n + meas tran dnend find v(dn) at=2n + end +end +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 0.8p 2.3n 0 0.8p +meas tran tck when v(clkc)=0.75 rise=1 +foreach dtn 0p 10p 25p 50p 100p 200p 300p 400p 450p 500p 1000p 1500p 2000p + let tt = tck + $dtn + echo NODES 800 $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +alterparam dtp = 1.6p +alterparam rr = 1 +reset +foreach h 16777 4194 + foreach sg 1 -1 + alter Vdp = 0.75 + $sg * $h / 16777216 + alter Vdn = 0.75 - $sg * $h / 16777216 + tran 1.6p 2n 0 1.6p + echo RUN 1600 $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=2n + meas tran dnend find v(dn) at=2n + end +end +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 1.6p 2.3n 0 1.6p +meas tran tck when v(clkc)=0.75 rise=1 +foreach dtn 0p 10p 25p 50p 100p 200p 300p 400p 450p 500p 1000p 1500p 2000p + let tt = tck + $dtn + echo NODES 1600 $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +alterparam dtp = 1.6p +alterparam rr = 1 +alterparam nf = 1250 +reset +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 1.6p 3.5n 0 1.6p +echo RESETRUN +foreach dtn 1900p 2000p 2050p 2100p 2200p 2400p 2800p 3400p + let tt = $dtn + echo RESET $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +.endc +.end diff --git a/specs/analog/decks/sar_cmp_q_free.cir b/specs/analog/decks/sar_cmp_q_free.cir new file mode 100644 index 0000000000..0456522ea4 --- /dev/null +++ b/specs/analog/decks/sar_cmp_q_free.cir @@ -0,0 +1,64 @@ +SAR ADC dynamic comparator on PSP103 at 27 C, net1 left to float: the DC operating point of the circuit with the clock low +* specs/analog/decks/sar_cmp_q_free.cir -- reference data for specs/analog/sar_comparator_q.t27 (analog M4b-q, #11312, epic #10486). The netlist of sar_cmp_q_ref.cir (read its header) with +* the .ic replaced by a .nodeset (a guess for the operating point, which does not force anything): ngspice solves the DC operating point of the circuit with the clock low, where +* net1, which no switch resets, settles where the leakage of the input pair equals the leakage of the tail (printed as "NET1 "), the clock rises at 1 ps and the transient is +* the trapezoidal one of sar_cmp_q_ref.cir. This is the comparator as a clocked circuit finds itself at the clock edge when the reset phase has been long enough; the spec's rest puts +* net1 at max(VDP, VDN) - 0.35 V. Run: IHP_PDK= IHP_OSDI= ngspice -b sar_cmp_q_free.cir > sar_cmp_q_free.out (ngspice-47) +* What it prints: "NET1 ", the operating point value of net1 with h = 16777; then, per run, after "RUN F ", tck, tfirst, tres, dpend and dnend as sar_cmp_q_ref.cir does. +.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 +.param tclk=200p +.param tfall=1 +Bclk clkc 0 V = 1.5*min(max((time-{tclk})/1.6p,0),1)*(1-min(max((time-{tfall})/1.6p,0),1)) +XM1 ap clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM2 an clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM3 ap vdn net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM4 an vdp net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM5 net1 clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=18 +XM6 net2 dp vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM7 net3 dn vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM8 dp an net3 vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM9 dn ap net2 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 n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM13 dp n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XIP n_clkc clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN n_clkc clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +Cbdp dp 0 4f +Cbdn dn 0 4f +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.nodeset v(ap)=1.5 v(an)=1.5 v(net2)=1.5 v(net3)=1.5 v(dp)=0 v(dn)=0 v(n_clkc)=1.5 v(net1)=0.7 +.temp 27 +.options reltol=1e-8 vntol=1e-11 abstol=1e-13 itl4=200 +.control +set num_threads=2 +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +alterparam tclk = 1p +reset +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +op +echo NET1 +print v(net1) +foreach h 16777 4194 262 + 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 + echo RUN F $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=3n + meas tran dnend find v(dn) at=3n + end +end +.endc +.end diff --git a/specs/analog/decks/sar_cmp_q_ref.cir b/specs/analog/decks/sar_cmp_q_ref.cir new file mode 100644 index 0000000000..7ba66fbc5e --- /dev/null +++ b/specs/analog/decks/sar_cmp_q_ref.cir @@ -0,0 +1,123 @@ +SAR ADC dynamic comparator on PSP103 at 27 C with all its charge and the golden bulks: the reference of sar_comparator_q.t27 +* specs/analog/decks/sar_cmp_q_ref.cir -- reference data for specs/analog/sar_comparator_q.t27 (analog M4b-q, #11312, epic #10486). A netlist plus a .control block that prints the +* numbers. The circuit is the one of sar_cmp_psp_real.cir (read its header), written out: the thirteen sg13_lv transistors of the golden dynamic_comparator and its inverter, with the +* W, L and m of sar_cmp_psp_r2b.cir (facts of the circuit; the text of the golden deck is not copied and not read), and the bulks as the golden deck has them: every NMOS bulk on ground +* and every PMOS bulk on VDD (r2b ties each bulk to its source instead; the input pair XM3, XM4 and the PMOS XM8, XM9 are the four devices whose source is not at their bulk). +* PSP103 (IHP-Open-PDK 21133f219b1a6da10d803e345761e77415ceff40, cornerMOSlv.lib mos_tt, through OSDI) at 27 C with ALL its charge, 4 fF on dp and dn as the real deck has them, an ideal +* 1.6 ps clock in place of the delay cell, the inputs held. The same netlist at 50 C is sar_cmp_q_ref50.cir, which reproduces sar_cmp_psp_real.cir's decision time of the golden +* subcircuit: that run is the proof that this netlist is that circuit. +* Run: IHP_PDK= IHP_OSDI= ngspice -b sar_cmp_q_ref.cir > sar_cmp_q_ref.out (ngspice-47; no golden file is read) +* 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 (h = 16777 is 2h = 33554 units = 2 mV less 0.4 uV), sg = +1 or -1. +* Integration: ngspice's default (trapezoidal), the step capped at 0.5 ps (tstep 0.1 ps, tmax 0.5 ps); reltol 1e-8, vntol 1e-11, abstol 1e-13. +* TWO STARTS, which differ by a transient that is not the circuit's. "RUN S": the SETTLED start, the one the model has: the .ic of the spec's rest (ap = an = net2 = net3 = n_clkc = 1.5 V, +* dp = dn = 0, net1 = 0.4009999632835388 V, max(VDP, VDN) - 0.35 V) WITHOUT uic, so that ngspice first solves the DC operating point with those eight nodes forced and the devices' own +* internal nodes consistent, then releases them; the clock rises at 1 ps. "RUN U": the start of sar_cmp_psp_real.cir, the same .ic WITH uic and the clock at 200 ps: with uic the nodes +* inside the PSP103 devices start at 0 V and their first charging disturbs the external nodes (n_clkc falls to 0.56 V in 10 ps, ap, an, net2 and net3 are kicked above VDD, dp and dn +* rise to 0.9 V), and 200 ps do not settle it (at the clock edge n_clkc is 1.30 V, dp and dn 0.62 V, net1 0.94 V): the decision time of that start is a number of that start. It is +* printed for the record, and the model is judged against the settled start. +* What it prints, per run, after "RUN ": tck, the clock through 0.75 V; tfirst, the first of dp and dn through 0.75 V; tres, |dp - dn| through 0.75 V; dpend and dnend, +* dp and dn at 3 ns. Then, for the +2 mV run of the settled start, "NODES S ": the eight nodes ap an net1 net2 net3 dp dn n_clkc at tck + 0, 10, 25, 50, 100, 200, 300, 400, 450, 500, 1000, 1500 and 2000 ps. +* The last block, "RESETRUN", is the +2 mV run of the settled start with the clock FALLING at 2 ns (over 1.6 ps, as it rose): "RESET " prints the eight nodes at 1.9, 2.0, 2.05, 2.1, 2.2, 2.4, 2.8 +* and 3.4 ns, the evaluation phase's end and the reset phase. +.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 +.param tclk=200p +.param tfall=1 +Bclk clkc 0 V = 1.5*min(max((time-{tclk})/1.6p,0),1)*(1-min(max((time-{tfall})/1.6p,0),1)) +XM1 ap clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM2 an clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM3 ap vdn net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM4 an vdp net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM5 net1 clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=18 +XM6 net2 dp vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM7 net3 dn vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM8 dp an net3 vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM9 dn ap net2 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 n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM13 dp n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XIP n_clkc clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN n_clkc clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +Cbdp dp 0 4f +Cbdn dn 0 4f +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.ic v(ap)=1.5 v(an)=1.5 v(net1)=0.4009999632835388 v(net2)=1.5 v(net3)=1.5 v(dp)=0 v(dn)=0 v(n_clkc)=1.5 +.temp 27 +.options reltol=1e-8 vntol=1e-11 abstol=1e-13 itl4=200 +.control +set num_threads=2 +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +alterparam tclk = 1p +reset +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 + echo RUN S $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=3n + meas tran dnend find v(dn) at=3n + end +end +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 0.1p 3n 0 0.5p +meas tran tck when v(clkc)=0.75 rise=1 +foreach dtn 0p 10p 25p 50p 100p 200p 300p 400p 450p 500p 1000p 1500p 2000p + let tt = tck + $dtn + echo NODES S $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +alterparam tclk = 200p +reset +foreach h 16777 4194 + 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 U $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=3n + meas tran dnend find v(dn) at=3n + end +end +alterparam tclk = 1p +alterparam tfall = 2n +reset +alter Vdp = 0.75 + 16777 / 16777216 +alter Vdn = 0.75 - 16777 / 16777216 +tran 0.1p 3.5n 0 0.5p +echo RESETRUN +foreach dtn 1900p 2000p 2050p 2100p 2200p 2400p 2800p 3400p + let tt = $dtn + echo RESET $dtn + meas tran vap find v(ap) at=$&tt + meas tran van find v(an) at=$&tt + meas tran vn1 find v(net1) at=$&tt + meas tran vn2 find v(net2) at=$&tt + meas tran vn3 find v(net3) at=$&tt + meas tran vdp find v(dp) at=$&tt + meas tran vdn find v(dn) at=$&tt + meas tran vnc find v(n_clkc) at=$&tt +end +.endc +.end diff --git a/specs/analog/decks/sar_cmp_q_ref50.cir b/specs/analog/decks/sar_cmp_q_ref50.cir new file mode 100644 index 0000000000..9416260661 --- /dev/null +++ b/specs/analog/decks/sar_cmp_q_ref50.cir @@ -0,0 +1,74 @@ +SAR ADC dynamic comparator on PSP103 at 50 C with all its charge and the golden bulks: the proof that sar_cmp_q_ref.cir is the golden comparator +* specs/analog/decks/sar_cmp_q_ref50.cir -- the check of specs/analog/decks/sar_cmp_q_ref.cir (analog M4b-q, #11312, epic #10486), read its header: the same netlist (the thirteen +* sg13_lv transistors of the golden dynamic_comparator and its inverter, W, L and m of sar_cmp_psp_r2b.cir, the bulks as the golden deck has them, 4 fF on dp and dn, an ideal 1.6 ps clock) +* at 50 C, the golden deck's temperature. "RUN U" is the start of sar_cmp_psp_real.cir (uic, the clock at 200 ps), which, read from the file named by SAR_GOLDEN_SUBCKTS, measured the first output +* through 0.75 V at 523.032 ps (h = 16777), 528.007, 529.386 and 529.741 ps (h = 4194, 1049, 262) after the clock's 50 % point (NG_RL_FIRST_PS of sar_comparator_psp.t27): this written-out +* netlist is that circuit if it prints the same times. No golden file is read. "RUN S" is the settled start of sar_cmp_q_ref.cir, at 50 C, for the comparison of the two starts. +* Run: IHP_PDK= IHP_OSDI= ngspice -b sar_cmp_q_ref50.cir > sar_cmp_q_ref50.out (ngspice-47) +.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 +.param tclk=200p +.param tfall=1 +Bclk clkc 0 V = 1.5*min(max((time-{tclk})/1.6p,0),1)*(1-min(max((time-{tfall})/1.6p,0),1)) +XM1 ap clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM2 an clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=9 +XM3 ap vdn net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM4 an vdp net1 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=36 +XM5 net1 clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=18 +XM6 net2 dp vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM7 net3 dn vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM8 dp an net3 vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=30 +XM9 dn ap net2 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 n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XM13 dp n_clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=6 +XIP n_clkc clkc vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN n_clkc clkc 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +Cbdp dp 0 4f +Cbdn dn 0 4f +Bmx mx 0 V = max(v(dp),v(dn)) +Bdiff diff 0 V = abs(v(dp)-v(dn)) +.ic v(ap)=1.5 v(an)=1.5 v(net1)=0.4009999632835388 v(net2)=1.5 v(net3)=1.5 v(dp)=0 v(dn)=0 v(n_clkc)=1.5 +.temp 50 +.options reltol=1e-8 vntol=1e-11 abstol=1e-13 itl4=200 +.control +set num_threads=2 +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=10 +set noprintscale +alterparam tclk = 200p +reset +foreach h 16777 4194 1049 262 + 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 U $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=3n + meas tran dnend find v(dn) at=3n + end +end +alterparam tclk = 1p +reset +foreach h 16777 4194 + 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 + echo RUN S $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=3n + meas tran dnend find v(dn) at=3n + end +end +.endc +.end diff --git a/specs/analog/sar_comparator.t27 b/specs/analog/sar_comparator.t27 index c16a641f8d..aac7d09ed5 100644 --- a/specs/analog/sar_comparator.t27 +++ b/specs/analog/sar_comparator.t27 @@ -311,6 +311,32 @@ pub fn sc_add_pair(reg: []u64, hi: u32, lo: u32, c: u32, mag: u64, neg: bool) { sc_add_j(reg, lo, c, mag, !neg); } +/// Stamp one channel whose current and slopes are already read (M4b-q, #11312: the comparator with the devices' charge and the source-to-bulk axis reads them off other tables, and +/// the stamp is shared, not copied). The channel runs from the end hi to the end lo, its current cur (words, the multiplier already in) leaves hi and enters lo, and its partials go into J: +/// a = the slope along the gate drive, b = along Vds, cb = the magnitude of the slope along the source-to-bulk voltage (0 for a device with its bulk on its source, which every table of +/// the square law and every old caller is). An NMOS has its source at the low end, so a rise of the low end lowers the current by a + b + cb; a PMOS has its source at the high end, so a rise +/// of the high end raises it by a + b + cb. The partials of a device are (b, a, -(a + b + cb)) on (high, gate, low) for an NMOS and (a + b + cb, -a, -b) for a PMOS; row high takes them, +/// row low takes them negated. +pub fn sc_stamp_chan_w(kind: u32, hi: u32, lo: u32, g: u32, cur: u64, a: u64, b: u64, cb: u64, reg: []u64) { + if (hi < SC_NN) { + const k : usize = (SC_RGP + hi) as usize; + reg[k] = reg[k] + cur; + } + if (lo < SC_NN) { + const k : usize = (SC_RGN + lo) as usize; + reg[k] = reg[k] + cur; + } + if (kind == 0) { + sc_add_pair(reg, hi, lo, hi, b, false); + sc_add_pair(reg, hi, lo, g, a, false); + sc_add_pair(reg, hi, lo, lo, a + b + cb, true); + } else { + sc_add_pair(reg, hi, lo, hi, a + b + cb, false); + sc_add_pair(reg, hi, lo, g, a, true); + sc_add_pair(reg, hi, lo, lo, b, true); + } +} + /// Stamp one channel whose three terminals are already resolved to voltages (vd, vg, vs, in words; only their differences matter, /// so any common offset is allowed): its current m * T(x, y) leaves the high end and enters the low end, and its partials go /// into J. d, g, s are the terminal ids (an id below SC_NN has a row and a column, any other id has none). M4c's switch calls @@ -328,26 +354,7 @@ pub fn sc_stamp_chan(t: []const u32, kind: u32, d: u32, g: u32, s: u32, vd: u32, } const x : u32 = sc_drive(kind, vg, vhi, vlo); const y : u32 = vhi - vlo; - const cur : u64 = (m as u64) * (id_of(t, x, y) as u64); - const a : u64 = (m as u64) * (gm_cell(t, x, y) as u64); - const b : u64 = (m as u64) * (gds_cell(t, x, y) as u64); - if (hi < SC_NN) { - const k : usize = (SC_RGP + hi) as usize; - reg[k] = reg[k] + cur; - } - if (lo < SC_NN) { - const k : usize = (SC_RGN + lo) as usize; - reg[k] = reg[k] + cur; - } - if (kind == 0) { - sc_add_pair(reg, hi, lo, hi, b, false); - sc_add_pair(reg, hi, lo, g, a, false); - sc_add_pair(reg, hi, lo, lo, a + b, true); - } else { - sc_add_pair(reg, hi, lo, hi, a + b, false); - sc_add_pair(reg, hi, lo, g, a, true); - sc_add_pair(reg, hi, lo, lo, b, true); - } + sc_stamp_chan_w(kind, hi, lo, g, (m as u64) * (id_of(t, x, y) as u64), (m as u64) * (gm_cell(t, x, y) as u64), (m as u64) * (gds_cell(t, x, y) as u64), 0, reg); } /// 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, diff --git a/specs/analog/sar_comparator_q.t27 b/specs/analog/sar_comparator_q.t27 new file mode 100644 index 0000000000..188a407ad6 --- /dev/null +++ b/specs/analog/sar_comparator_q.t27 @@ -0,0 +1,1231 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/sar_comparator_q.t27 -- the SAR ADC dynamic comparator with the devices' own charge and the body effect: PSP103 tables at 27 C, judged by ngspice +; M4b-q of the analog emulation epic (#10486, issue #11312). M4b (specs/analog/sar_comparator.t27) solves the golden SAR deck's StrongARM latch, eight nodes, by a multi-node Newton step; M4b-psp +; (sar_comparator_psp.t27) ran it on the PSP103 current tables at 50 C and found the decision 98 times too fast against ngspice with all its charge, "until the charge tables exist". They exist now: the drain and +; gate charge of both devices (M3c, TT_QD_N, TT_QG_N, TT_QD_P, TT_QG_P), the log-domain current tables (M3c-2) and their source-to-bulk axis (M3e). This file puts the three into the comparator at 27 C, the +; report's temperature, with the same backward Euler charge step as the 500 fF inverter of M3c, and judges the decision time against ngspice-47 running the same comparator. The solver is reused, not copied: +; sar_comparator.t27's sc_stamp_chan is split in two and the second half, `sc_stamp_chan_w` (the stamp of a channel whose current and slopes are already read), is the stamp used here, with a fourth slope added (along +; the source-to-bulk voltage; 0 for every old caller, so the 20 tests and the pinned folds of sar_comparator.t27 are unchanged); the elimination, the back substitution, the registers and the sign-magnitude +; words are M4b's. Everything else is here. +; - +; WHAT WAS ASKED AND WHAT CAME OUT, first. The issue asks for a 27 C comparator with the devices' charge and the body effect whose decision time at +2 mV and -2 mV is within 10 percent of ngspice's own run of the same +; comparator at 27 C. It is: 613.07 ps and 613.15 ps against ngspice's 602.54 ps, +1.75 and +1.76 percent (the bar is 10); +1.62 percent against ngspice's backward Euler on the model's own 1.6 ps grid; +1.00 percent against the +; external team's report of 2026-10-16 (607 ps). The model of M4b-psp, with no charge, was 5.4 ps. The first model built to the letter of the issue (the drain and gate charge from the tables, the source terminal's the +; remainder so that the four charges add to zero) did not converge: the bulk's charge, 4 to 7 percent of the gate's, landed on the source node and jumped when the two ends of a channel exchanged their parts, and the +; last Newton residual stays at 1.9e6 units (0.11 V). The source terminal is therefore closed by its two edges (THE SOURCE CHARGE below), which the tables give exactly, and that is the one step beyond the issue. +; - +; THE REFERENCE. The comparator is written out, not read: the thirteen sg13_lv transistors of the golden dynamic_comparator and its inverter, with the W, L and m of sar_cmp_psp_r2b.cir (facts of the circuit, none of the +; golden deck's text), the bulks as the golden deck has them (every NMOS on ground, every PMOS on VDD), 4 fF on dp and dn, an ideal 1.6 ps clock, PSP103 of the IHP-Open-PDK 21133f21 (mos_tt) through ngspice-47 and OSDI, on the +; reference host. THAT NETLIST IS THE GOLDEN COMPARATOR: at 50 C and the golden deck's start it gives the first output through 0.75 V 523.032, 528.006, 529.386 and 529.741 ps after the clock, the four numbers +; the golden subcircuit gave in sar_cmp_psp_real.cir (sar_cmp_q_ref50.cir; a test). Three starts, because the start is not nothing: +; the golden deck's the initial conditions with uic and the clock at 200 ps: the nodes inside the PSP103 devices start at 0 V, their first charging disturbs the others (n_clkc falls to 0.56 V in 10 ps, ap, an, net2, net3 are +; kicked over the supply, dp and dn rise to 0.9 V) and 200 ps do not settle it (at the clock n_clkc is 1.30 V, dp and dn 0.62 V). Its decision time is 508.1 ps at 27 C, 523.0 ps at 50 C: numbers of that +; start. The 98 times of M4b-psp are against it. +; the SETTLED start sar_cmp_q_ref.cir RUN S: the spec's rest (ap = an = net2 = net3 = n_clkc = 1.5 V, dp = dn = 0, net1 = max(VDP, VDN) - 0.35 V) as .ic WITHOUT uic, so that ngspice solves the DC operating point with the +; eight nodes forced and the devices' internal nodes consistent, then releases them, the clock at 1 ps. This is the model's start and the reference of the issue: 602.54 ps at 27 C, 632.56 ps at 50 C. +; the FREE start sar_cmp_q_free.cir: the operating point with the clock low and net1, which no switch resets, where the leakage of the pair and of the tail balance (0.7073 V): 607.145 ps, the external team's report +; to 0.02 percent. The model can start from it too (sq_rest takes a word for net1). +; The trapezoidal run is ngspice's default with the step capped at 0.5 ps; sar_cmp_q_be.cir is its backward Euler (gear, maxord 1) on a fixed grid of 0.2, 0.4, 0.8 and 1.6 ps with the clock a step of the grid, the scheme of the model: +; the decision time is within 0.01, 0.02, 0.10 and 0.13 percent of the trapezoidal one, so the model's one step of 1.6 ps is not an error of the scheme at the resolution of this comparison. +; - +; THE MODEL. The state is the eight nodes of sar_comparator.t27 (ap, an, net1, net2, net3, dp, dn, n_clkc), voltages in words of 2^-24 V with ground 0.5 V up the range (SQ_OFF), so that a node can go 0.5 V below ground +; (n_clkc and net1 do, by 0.36 and 0.23 V in ngspice and 0.40 and 0.28 V in the model, when the clock falls) and 1 V above the supply (ap and an do, by 0.39 V, when it rises). The step is 1.6 ps and the clock is a step of the grid: 438 steps cover 700 ps. The charge unit is 2 fF +; and a current word is the current in IUNIT (1.164 pA) over 64, so dt / C is the 800 V/A of M4b. A device of the fifteen is read at its two channel ends (the higher is the drain of an NMOS and the source of a PMOS), its gate +; drive x and its Vds y, the bulk of an NMOS on ground and of a PMOS on the supply (the golden netlist's), so the source-to-bulk voltage Vsb is the low end above ground for an NMOS and the supply above the high end for a PMOS. +; the current the log-domain tables TT_LG_N and TT_LG_P read on the source-to-bulk axis (vsb_fine of mos_psp103_vsb_data.t27, seven slices, the log word blended between two); the slopes are forward differences of the same +; reads over 1 mV along the gate, Vds and Vsb (a rise of the low end of an NMOS lowers the current by gm + gds + gmb, a rise of the high end of a PMOS raises it by the same), so that value and slope are one +; object (FR-002 of M3c); a device whose gate is beyond its source has no gate slope. +; the charge the terminal charges of the device at (x, y) from the M3c tables, read by the bilinear read of M3a and sloped off the same cell (the drain charge QD, the gate charge QG, the PMOS tables negated); the +; node charge is the sum of the terminals at the node, and the backward Euler equation of a node is the M3c one, G = (Q(v) - Q(v0)) + (currents out - currents in), with Q(v0) of the previous step's state and clock +; (the clock edge is a jump of charge, as the gate edge of the 500 fF inverter). The tables stop at 0 and 1.5 V on both axes; beyond them the charge goes on linearly with the slope of the edge cell, so the charge +; and the slope the Newton step uses are one function (a node kicked 0.39 V over the supply, a gate drive of 1.75 V, a gate beyond its source: the gate overlap and the accumulation). The lumped capacitors are the +; real circuit's, 4 fF on dp and dn, and nothing else. +; THE SOURCE CHARGE the tables hold the drain and the gate terminal, with source and bulk tied; the source terminal is the remainder, Qs = -(Qd + Qg) - Qb, and the bulk's charge Qb is not tabulated. It is closed by its two edges, +; Qb(x, y) = Qb(x, 0) + Qb(0, y) with Qb(x, 0) = -(Qg + 2 Qd)(x, 0) (the device is symmetric at Vds = 0, so Qs = Qd there) and Qb(0, y) = -(Qd + Qg)(0, y) (the source is untouched while the channel is off): +; Qs(x, 0) = Qd(x, 0) and Qs(0, y) = 0 EXACTLY, a test for both devices, so the charge does not jump when the two ends of a channel exchange their roles, and Qs in the interior is the tables' charge without the +; bulk's variation along both axes at once (the cross term, which is small: Qb is 4 percent of Qg at a gate drive of 1.5 V on an NMOS, 7 percent on a PMOS). Without it (SQ_M_NOCLOSURE, Qs = -(Qd + Qg)) the last Newton residual +; is 1.88e6 units (0.11 V) and the time it reads is 544 ps, nothing. +; the step backward Euler on the eight nodes at once, K = 4 Newton iterations from the previous state, the eight-by-eight pivoted elimination in sign-magnitude words of sar_comparator.t27 (sc_load, sc_eliminate, +; sc_backsub, sc_apply_lim with the top of the range at 1 V over the supply), the residual after the last iteration computed at every step as the Newton move still owed, in units (59.6 nV). +; - +; THE RESULT, K = 4, tt 27 C, times in ps after the clock's 50 percent point (the first output through 0.75 V), ngspice the settled start: +; input model ngspice gap ngspice BE 1.6 ps gap +; +2 mV 613.074 602.537 +1.75 % 603.316 +1.62 % +; -2 mV 613.151 602.537 +1.76 % +; +0.5 mV 624.138 608.023 +2.65 % 609.012 +2.48 % +; 0.125 mV 627.716 609.602 +2.97 % +; 31 uV 628.686 610.014 +3.06 % +; 10 uV 629.046 610.108 +3.10 % +; 1 uV, 0.12 uV 629.114, 629.090 610.148, 610.152 +3.11 %, +3.10 % +; free start 614.860 607.145 +1.27 % (the report, 607 ps: +1.29 %) 0.5 mV: +2.15 %, 31 uV: +2.56 % +; so the decision time is between 1.75 and 3.11 percent over ngspice's at every input, the same sign, a third of the 10 percent. The sign is right at +-2 mV; below 10 uV the model's decision is the rounding's (it +; depends on K and on the least bit of the tables), which is the flip point of a symmetric latch at the resolution of 59.6 nV and 1 mV forward differences, and no claim is made there. THE LEVELS after the decision, at 2 ns: +; dp 1.49933 V and dn 2.26 mV against ngspice's 1.49884 V and 2.283 mV (0.49 mV and 0.02 mV). THE RESET, the clock falling at 2 ns: 1.4 ns later (3.4 ns) every node is within 2.6 mV of ngspice's, dp and dn +; are 3.08 mV and -0.17 mV against 3.265 mV and -0.247 mV (0.19 mV and 0.08 mV); in the 400 ps after the fall, while the nodes swing through volts, the model is 51 mV off on the outputs at the worst, 0.18 V on ap +; and 0.31 V on net2 (which recovers faster in the model), and the fall step itself kicks the nodes 10 to 40 percent further than ngspice's grid does (ap -0.24 V against -0.17 V, n_clkc -0.40 V against +; -0.36 V). THE RESOLUTION TIME (|dp - dn| through 0.75 V), not a row of the report, is 1040.7 ps against 1094.3 ps, 4.9 percent early: the regeneration is faster in the model. +; - +; THE ERRORS, kept apart. +; arithmetic the last Newton residual is at most 706 units (42 uV) over the 438 steps of a short run and 1545 units (0.09 mV) over the 2125 steps of the long run, the clock-fall step included. K = 3 to 8 give the +; decision time 613.07 to 613.15 ps, so the iteration count does not matter above 3; K = 2 leaves 2.76e5 units at the clock edge (a test refuses it by the residual, not by the time, which is 612.99 ps) and K = 1 +; 3.3e6. There is no real-arithmetic twin of the iteration in this file: the claim is the residual and the stability of the answer. +; the Jacobian is the derivative of the residual to 1.5 percent of the diagonal of its row (a central difference of the residual over 1 mV, the largest of the 64 entries, at the clock edge and at four states of the +; run), the transposed matrix is off by more than 0.2 (tests); the forward differences of the current and the chords of the cells are the 1.5 percent. +; table/model the model against ngspice's backward Euler on the same 1.6 ps grid, all the nodes: at the 13 times of the rows, from 50 to 500 ps (ahead of the decision) the largest distance is 27.1 mV (ap, an: +; the model discharges them 1 to 2 percent early; net1 is within 3 mV, n_clkc within 10 mV, dp and dn within 8 mV), up to 1.5 ns (through the regeneration) 83.4 mV (dp and dn at the resolution edge, 4.9 percent +; early in time); at the clock edge ap is 80.9 mV off at the 50 percent point of the clock and 58 mV at the top of its kick, 10 ps later (the kick is 0.39 V in ngspice and 0.45 V in the model; the cause is not +; isolated: the same grid and the same step as ngspice's backward Euler, the model's charge is the tables' and their closure). +; body effect the source-to-bulk axis is worth 7.4 percent of the decision time: without it (SQ_M_NOBODY, Vsb forced to 0, the 2-D read) the decision is 570.6 ps, 5.30 percent UNDER ngspice's, inside the bar of 10 percent +; and outside a bar of 3: the control is the model's gap with the body effect, +1.75 percent, against -5.30 without, three times as far. The body effect on the CHARGE is not in the model (the charge tables have the bulk +; tied to the source): the junction charge of the source ends of XM3, XM4 (net1 is 0.15 to 0.5 V above their bulk) and of XM8, XM9, and the shift of their threshold, are in no table. +; scheme ngspice backward Euler on the 1.6 ps grid against its trapezoidal run: +0.13 percent (decision), -0.33 percent (resolution); shared by construction. +; - +; THE NEGATIVE CONTROLS, tests that pass by seeing the failure: +; charge removed the devices' own charge off and the lumped capacitors of the square-law model (4, 4, 6, 2, 2, 4, 4, 2 fF) on every node: 5.40 ps, 111 times too fast, under the report's lower limit of 10 ps and 99 percent +; under ngspice. At this step the no-charge latch does not even regenerate (both outputs sit at 0.786 V from 50 ps on: the backward Euler step of 1.6 ps is several times the time +; constant of a circuit without its charge), so the 5.40 ps is the common-mode rise, which is what the old model's 5.35 ps was too; the control is judged by the time. +; no body effect 570.6 ps, above. +; no closure the Newton step does not converge (1.88e6 units), above. +; charge tables swapped the PMOS tables for the NMOS devices and the reverse: the nodes run to the top of the range, 4.2e9 units of residual, 7.8 ps. +; K = 2, K = 1 2.76e5 and 3.3e6 units of residual. +; inputs exchanged the sign flips and the time is the same to 0.08 ps. +; - +; WHAT A PASS SAYS. The comparator of eight nodes and fifteen devices, each read from the tt 27 C PSP103 log tables on the source-to-bulk axis with its drain, gate and source charge from the M3c tables and the closure above, solved by +; backward Euler of 1.6 ps with four Newton iterations (last residual at most 1545 units), decides +-2 mV the right way; its decision time is 613.07 ps and 613.15 ps, within 1.76 percent of ngspice-47 on the golden comparator at +; the settled start (the bar is 10 percent) and within 3.11 percent over the whole range of inputs, inside the report's 10 ps to 10 ns; its end levels at 2 ns and its reset level at 3.4 ns (the clock falling at 2 ns) are within +; 0.5 mV of ngspice's; the charge removed, the closure removed, the tables in the wrong slots and too few iterations are each refused. WHAT IT DOES NOT SAY. The resolution time (4.9 percent early) and the nodes in the +; regeneration (up to 83 mV) and during the reset (30 to 60 mV) are the model's own and are not within the bar of the decision time. Nothing about the body effect on the charge, the closure beyond its two exact edges +; (a deck that prints the source terminal's charge would test it), another corner or temperature (tt 27 C only: the 50 C tables have no charge and no source-to-bulk axis), noise, mismatch, another W, L or supply; the +; converter's comparator (its delay-cell clock, the kickback of its input stages, the NAND "Valid" and the inverters x5, x6 are not here); the flip point of the isolated latch below 10 uV; hardware: the functions from sq_clk_at +; to sq_run take and return unsigned words and sign-magnitude words and have no float, and no board top, clock, LUT, DSP, BRAM, Fmax or UART line is claimed. The golden deck is not in this repository (its comparator is +; written out in the decks); the reference host's runs are the numbers below. ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogSarComparatorQ; + +use analog::linear::{fold, fabs64}; +use analog::mos_table::{VDD, CELL_BITS, lim, cell_of, frac_of, node_at, dpos, blend1, id_of, mag_diff}; +use analog::mos_psp103_data::{TT_LG_N, TT_LG_P, TT_QD_N, TT_QG_N, TT_QD_P, TT_QG_P}; +use analog::mos_psp103_vsb_data::{vsb_fine}; +use analog::sar_comparator_psp::{NG_RL_FIRST_PS, sp_rel}; +use analog::sar_comparator::{SC_CAP, SC_NN, SC_DP, SC_DN, SC_NDEV, SC_RJP, SC_RJN, SC_DV_KIND, SC_DV_D, SC_DV_G, SC_DV_S, SC_DV_M, SC_T_VDD, SC_T_GND, SC_T_CLK, SC_T_VDP, SC_T_VDN, SC_RGP, SC_RGN, SC_VCM, SC_VTH_U, SC_QB, SC_NONE, sc_stamp_chan_w, sc_add_j, sc_clear, sc_stamp_cap, sc_resid, sc_load, sc_eliminate, sc_backsub, sc_apply_lim, sc_pack, sc_neg, sc_mag, sc_sadd, sc_flip}; + +pub const KIND : str = "analog-cell"; +pub const ID : str = "analog/sar_comparator_q"; +pub const REFERENCE : str = "the golden SAR deck's dynamic_comparator (GPL-2.0, read at run time, never vendored; written out in specs/analog/decks/sar_cmp_q_ref.cir); IHP-Open-PDK 21133f21 PSP103 through ngspice-47, 27 C"; + +; --- the units --------------------------------------------------------------------------------------------- +/// Ground, in words: every voltage of the run carries 2^23 units (0.5 V) of offset, so that a node may go below ground (the outputs do by 70 mV when the clock rises and the nodes by 0.36 V when it falls) +/// and above the supply (the precharge nodes go 0.39 V above it). +pub const SQ_OFF : u32 = 8388608; +/// The supply: 1.5 V above ground. +pub const SQ_VDD : u32 = 33554432; +/// The highest a node may go: 1 V above the supply. +pub const SQ_TOP : u32 = 50331648; +/// The step: 1.6 ps; the charge unit: 2 fF; so dt / C_unit = 800 V/A, a current word is the current in IUNIT over 64. +pub const SQ_DT_FS : u32 = 1600; +pub const SQ_CU_AF : u32 = 2000; +/// A charge table word is Q / 500 fF in units of 2^-24 V, so it is 500 fF / 2 fF = 250 words of this solver's charge. +pub const SQ_QS : u64 = 250; +/// 4 fF on dp and dn, in charge units: the real circuit's only lumped capacitors. +pub const SQ_C4 : u64 = 2; +/// The lumped capacitances of the square-law model of sar_comparator.t27 in charge units of 2 fF (4, 4, 6, 2, 2, 4, 4, 2 fF on ap an net1 net2 net3 dp dn n_clkc): the model of the devices without their charge. +pub const SQ_CAP_M4B : [8]u64 = [2, 2, 3, 1, 1, 2, 2, 1]; +/// The origin offset of the charge tables, 2^21. +pub const SQ_QOFF : u32 = 2097152; +/// The step, in units, of the difference that reads a slope of a current off the table: 2^14 (1 mV). A slope per cell (2^20 units) in current words is then the difference of the two reads in IUNIT itself +/// (2^20 / 2^14 = 64 is the factor between an IUNIT and a current word). +pub const SQ_DV : u32 = 16384; +/// The clock rises in SQ_R steps (1.6 ps, one step: the edge is a step of the grid, and ngspice's backward Euler on the same grid is the reference of the scheme). +pub const SQ_R : u32 = 1; +/// Newton iterations of a step: K = 3 to 8 give the decision time 613.07 to 613.15 ps; four leave at most 1545 units (0.09 mV) at all 2125 steps of the long run. +pub const SQ_K : u32 = 4; +/// The short run: 438 steps, 700.8 ps, past the decision of every input down to 1 unit. The long run: the clock falls at step SQ_JF (2 ns) and the run goes on to step SQ_NL (3.4 ns), 1.4 ns into the reset. +pub const SQ_NS : u32 = 438; +pub const SQ_JF : u32 = 1250; +pub const SQ_NL : u32 = 2125; + +/// The bar of the cross-check with ngspice: a decision time within 10 percent of ngspice's own run of the same circuit (mos_psp103_gates.t27's GT_B_REL); a level within 5 mV, a tenth of the report's 50 mV. +pub const SQ_BAR : f64 = 0.10; +pub const SQ_BAR_LEVEL_V : f64 = 0.005; + +; --- the modes of a run: the model, and the four ways of breaking it that the negative controls use ------------------------------------------------ +pub const SQ_M_NOCHARGE : u32 = 1; +pub const SQ_M_NOBODY : u32 = 2; +pub const SQ_M_NOCLOSURE : u32 = 4; +pub const SQ_M_SWAPQ : u32 = 8; + +; --- the circuit ----------------------------------------------------------------------------------------------------- +/// The clock at step j, in words: low at step 0, rising over SQ_R steps, and when jf is not 0 falling over SQ_R steps from step jf (the reset phase). +pub fn sq_clk_at(j: u32, jf: u32) -> u32 { + if (jf > 0 and j >= jf) { + const i : u32 = j - jf + 1; + if (i >= SQ_R) { return SQ_OFF; } + return SQ_VDD - i * (VDD / SQ_R); + } + if (j >= SQ_R) { return SQ_VDD; } + return SQ_OFF + j * (VDD / SQ_R); +} + +pub fn sq_term_v(tm: u32, v: []const u32, clk: u32, vdp: u32, vdn: u32) -> u32 { + if (tm < SC_NN) { return v[tm as usize]; } + if (tm == SC_T_VDD) { return SQ_VDD; } + if (tm == SC_T_GND) { return SQ_OFF; } + if (tm == SC_T_CLK) { return clk; } + if (tm == SC_T_VDP) { return vdp; } + return vdn; +} + +pub fn sq_cur_tab(kind: u32) -> []const u32 { + if (kind == 0) { return &TT_LG_N; } + return &TT_LG_P; +} + +pub fn sq_qd_tab(kind: u32) -> []const u32 { + if (kind == 0) { return &TT_QD_N; } + return &TT_QD_P; +} + +pub fn sq_qg_tab(kind: u32) -> []const u32 { + if (kind == 0) { return &TT_QG_N; } + return &TT_QG_P; +} + +; --- signed words --------------------------------------------------------------------------------------------------- +/// w * k. +pub fn sq_mul(w: u64, k: u64) -> u64 { + return sc_pack(sc_mag(w) * k, sc_neg(w)); +} + +/// The signed charge of a table word relative to the table's origin: the word less 2^21 for an NMOS table, the negation of that for a PMOS table (stored negated). +pub fn sq_qw(w: u32, pmos: bool) -> u64 { + if (w >= SQ_QOFF) { return sc_pack((w - SQ_QOFF) as u64, pmos); } + return sc_pack((SQ_QOFF - w) as u64, !pmos); +} + +/// The slope of a table along an axis (0: the first argument, 1: the second) in words per cell, its rising part (neg false) or its falling part (neg true), read off the same cell as `id_of` +/// reads the value: the cell's difference along the axis, blended along the other. +pub fn sq_tslope(t: []const u32, vg: u32, vd: u32, axis: u32, fall: bool) -> u64 { + const gv : u32 = lim(vg); + const dv : u32 = lim(vd); + const cx : u32 = cell_of(gv); + const cy : u32 = cell_of(dv); + var a0 : u64 = 0; + var a1 : u64 = 0; + var b0 : u64 = 0; + var b1 : u64 = 0; + var f : u32 = 0; + if (axis == 0) { + a0 = node_at(t, cx, cy); + a1 = node_at(t, cx + 1, cy); + b0 = node_at(t, cx, cy + 1); + b1 = node_at(t, cx + 1, cy + 1); + f = frac_of(dv); + } else { + a0 = node_at(t, cx, cy); + a1 = node_at(t, cx, cy + 1); + b0 = node_at(t, cx + 1, cy); + b1 = node_at(t, cx + 1, cy + 1); + f = frac_of(gv); + } + if (fall) { return blend1(dpos(a0, a1), dpos(b0, b1), f) as u64; } + return blend1(dpos(a1, a0), dpos(b1, b0), f) as u64; +} + +/// The signed slope of the charge of a device along an axis, words per cell: the rising part less the falling part of the table, negated for a PMOS table. +pub fn sq_slope(t: []const u32, vg: u32, vd: u32, axis: u32, pmos: bool) -> u64 { + const up : u64 = sc_pack(sq_tslope(t, vg, vd, axis, false), pmos); + const dn : u64 = sc_pack(sq_tslope(t, vg, vd, axis, true), !pmos); + return sc_sadd(up, dn); +} + +/// The signed charge of a device at the gate drive x (xn units below the source when negative, so the table is read at 0) and Vds y, from the table t (words relative to the origin, negated for a PMOS +/// table). The table covers 0 .. 1.5 V on both axes; the charge goes on beyond its edges linearly with the slope of the edge cell, so that the charge and the slope the Newton step uses are one function: +/// below the source (xn > 0, the gate beyond a channel end: the overlap and the accumulation), above the supply (a node kicked over the rail, a gate drive of more than 1.5 V: x or y above VDD). +pub fn sq_qt(t: []const u32, x: u32, xn: u32, y: u32, pmos: bool) -> u64 { + var q : u64 = sq_qw(id_of(t, x, y), pmos); + if (xn > 0) { + const s0 : u64 = sq_slope(t, 0, y, 0, pmos); + q = sc_sadd(q, sc_pack((sc_mag(s0) * (xn as u64)) >> CELL_BITS, !sc_neg(s0))); + } + if (x > VDD) { + const sx : u64 = sq_slope(t, VDD, y, 0, pmos); + q = sc_sadd(q, sc_pack((sc_mag(sx) * ((x - VDD) as u64)) >> CELL_BITS, sc_neg(sx))); + } + if (y > VDD) { + const sy : u64 = sq_slope(t, x, VDD, 1, pmos); + q = sc_sadd(q, sc_pack((sc_mag(sy) * ((y - VDD) as u64)) >> CELL_BITS, sc_neg(sy))); + } + return q; +} + +/// The source terminal's charge (or slope) from the drain's and the gate's, the four terminal charges adding to zero and the bulk's charge closed by its two edges: Qb(x, y) = Qb(x, 0) + Qb(0, y) with +/// Qb(x, 0) = -(Qg + 2 Qd)(x, 0) (the device symmetric at Vds = 0, so Qs = Qd there) and Qb(0, y) = -(Qd + Qg)(0, y) (the source untouched while the channel is off). It gives Qs(x, 0) = Qd(x, 0) and +/// Qs(0, y) = 0 exactly, so the charge does not jump when the two ends of a channel exchange their parts, and it is the two-table closure of Qs; qd, qg are read at (x, y), qd0x, qg0x at (x, 0), qd0y, qg0y at (0, y). +pub fn sq_qsrc(qd: u64, qg: u64, qd0x: u64, qg0x: u64, qd0y: u64, qg0y: u64) -> u64 { + return sc_sadd(sc_sadd(sc_flip(sc_sadd(qd, qg)), sc_sadd(sc_sadd(qd0x, qd0x), qg0x)), sc_sadd(qd0y, qg0y)); +} + +/// The node charges: add the signed word w to the positive or negative accumulator of node n (terminals above the eight nodes have none). +pub fn sq_acc(qa: []u64, n: u32, w: u64) { + if (n >= SC_NN) { return; } + if (sc_neg(w)) { + qa[(SC_NN + n) as usize] = qa[(SC_NN + n) as usize] + sc_mag(w); + } else { + qa[n as usize] = qa[n as usize] + sc_mag(w); + } +} + +/// Add the signed word w to the Jacobian entry (r, c). +pub fn sq_jadd(reg: []u64, r: u32, c: u32, w: u64) { + sc_add_j(reg, r, c, sc_mag(w), sc_neg(w)); +} + +/// The charge slopes of one terminal of a device into row r: (sx, sy) per cell along the gate drive and along Vds, scaled. An NMOS reads x = Vg - Vlow, y = Vhigh - Vlow; a PMOS x = Vhigh - Vg, y = Vhigh - Vlow. +pub fn sq_jq(reg: []u64, kind: u32, r: u32, g: u32, hi: u32, lo: u32, sx: u64, sy: u64) { + if (kind == 0) { + sq_jadd(reg, r, g, sx); + sq_jadd(reg, r, hi, sy); + sq_jadd(reg, r, lo, sc_flip(sc_sadd(sx, sy))); + } else { + sq_jadd(reg, r, g, sc_flip(sx)); + sq_jadd(reg, r, hi, sc_sadd(sx, sy)); + sq_jadd(reg, r, lo, sc_flip(sy)); + } +} + +; --- the current --------------------------------------------------------------------------------------------------------- +/// The current of a device of kind, in IUNIT, read on the source-to-bulk axis. +pub fn sq_i(kind: u32, x: u32, y: u32, vsb: u32) -> u64 { + return vsb_fine(kind, sq_cur_tab(kind), x, y, vsb); +} + +/// The rise of the current over SQ_DV along an axis (0: gate drive, 1: Vds) in IUNIT, forward when there is room below the supply, backward otherwise; axis 2: the fall of the current over a rise of +/// the source-to-bulk voltage. +pub fn sq_fd(kind: u32, x: u32, y: u32, vsb: u32, i0: u64, axis: u32) -> u64 { + if (axis == 0) { + if (x + SQ_DV <= VDD) { return dpos(sq_i(kind, x + SQ_DV, y, vsb), i0); } + return dpos(i0, sq_i(kind, x - SQ_DV, y, vsb)); + } + if (axis == 1) { + if (y + SQ_DV <= VDD) { return dpos(sq_i(kind, x, y + SQ_DV, vsb), i0); } + return dpos(i0, sq_i(kind, x, y - SQ_DV, vsb)); + } + if (vsb + SQ_DV <= VDD) { return dpos(i0, sq_i(kind, x, y, vsb + SQ_DV)); } + return dpos(sq_i(kind, x, y, vsb - SQ_DV), i0); +} + +; --- one device --------------------------------------------------------------------------------------------------------- +/// Device e at the state v with the clock clk: its terminal charges go into qa (positive terms in words 0 .. 7, negative in words 8 .. 15); with jac its current and the slopes of its current and of +/// its charges go into reg as well. The channel runs between its two ends, the higher end being the drain of an NMOS and the source of a PMOS; the NMOS bulk is ground and the PMOS bulk is the +/// supply (the golden netlist's), so the source-to-bulk voltage is the low end above ground for an NMOS and the supply above the high end for a PMOS. The charge is read at the gate drive and +/// Vds: the drain and the gate from the tables of mos_psp103_data.t27, the source terminal closed by its two edges (`sq_qsrc`; mode SQ_M_NOCLOSURE: the remainder, the bulk's charge taken as zero). The modes: see SQ_M_*. +pub fn sq_dev(e: usize, v: []const u32, clk: u32, vdp: u32, vdn: u32, mode: u32, jac: bool, reg: []u64, qa: []u64) { + const kind : u32 = SC_DV_KIND[e]; + const d : u32 = SC_DV_D[e]; + const g : u32 = SC_DV_G[e]; + const s : u32 = SC_DV_S[e]; + const m : u64 = (SC_DV_M[e]) as u64; + const vd : u32 = sq_term_v(d, v, clk, vdp, vdn); + const vg : u32 = sq_term_v(g, v, clk, vdp, vdn); + const vs : u32 = sq_term_v(s, v, clk, vdp, vdn); + var hi : u32 = d; + var lo : u32 = s; + var vhi : u32 = vd; + var vlo : u32 = vs; + if (vd < vs) { + hi = s; + lo = d; + vhi = vs; + vlo = vd; + } + var x : u32 = 0; + var xn : u32 = 0; + var vsb : u32 = 0; + var forward : bool = false; + if (kind == 0) { + if (vg > vlo) { x = vg - vlo; } else { xn = vlo - vg; } + if (vlo > SQ_OFF) { vsb = vlo - SQ_OFF; } else { forward = true; } + } else { + if (vhi > vg) { x = vhi - vg; } else { xn = vg - vhi; } + if (vhi < SQ_VDD) { vsb = SQ_VDD - vhi; } else { forward = true; } + } + if ((mode & SQ_M_NOBODY) != 0) { + vsb = 0; + forward = true; + } + const y : u32 = vhi - vlo; + if (jac) { + const i0 : u64 = sq_i(kind, x, y, vsb); + var a : u64 = 0; + if (xn == 0) { a = m * sq_fd(kind, x, y, vsb, i0, 0); } + const b : u64 = m * sq_fd(kind, x, y, vsb, i0, 1); + var cb : u64 = 0; + if (!forward) { cb = m * sq_fd(kind, x, y, vsb, i0, 2); } + sc_stamp_chan_w(kind, hi, lo, g, (m * i0 + 32) >> 6, a, b, cb, reg); + } + if ((mode & SQ_M_NOCHARGE) != 0) { return; } + var kq : u32 = kind; + if ((mode & SQ_M_SWAPQ) != 0) { kq = 1 - kind; } + const pm : bool = kq == 1; + const tqd : []const u32 = sq_qd_tab(kq); + const tqg : []const u32 = sq_qg_tab(kq); + const closed : bool = (mode & SQ_M_NOCLOSURE) == 0; + const qd : u64 = sq_qt(tqd, x, xn, y, pm); + const qg : u64 = sq_qt(tqg, x, xn, y, pm); + var qs : u64 = sc_flip(sc_sadd(qd, qg)); + if (closed) { qs = sq_qsrc(qd, qg, sq_qt(tqd, x, xn, 0, pm), sq_qt(tqg, x, xn, 0, pm), sq_qt(tqd, 0, 0, y, pm), sq_qt(tqg, 0, 0, y, pm)); } + const mq : u64 = m * SQ_QS; + var nd : u32 = hi; + var ns : u32 = lo; + if (kind == 1) { + nd = lo; + ns = hi; + } + sq_acc(qa, nd, sq_mul(qd, mq)); + sq_acc(qa, g, sq_mul(qg, mq)); + sq_acc(qa, ns, sq_mul(qs, mq)); + if (!jac) { return; } + const dxd : u64 = sq_mul(sq_slope(tqd, x, y, 0, pm), mq); + const dyd : u64 = sq_mul(sq_slope(tqd, x, y, 1, pm), mq); + const dxg : u64 = sq_mul(sq_slope(tqg, x, y, 0, pm), mq); + const dyg : u64 = sq_mul(sq_slope(tqg, x, y, 1, pm), mq); + var sxs : u64 = sc_flip(sc_sadd(dxd, dxg)); + var sys : u64 = sc_flip(sc_sadd(dyd, dyg)); + if (closed) { + sxs = sq_mul(sq_qsrc(sq_slope(tqd, x, y, 0, pm), sq_slope(tqg, x, y, 0, pm), sq_slope(tqd, x, 0, 0, pm), sq_slope(tqg, x, 0, 0, pm), 0, 0), mq); + sys = sq_mul(sq_qsrc(sq_slope(tqd, x, y, 1, pm), sq_slope(tqg, x, y, 1, pm), 0, 0, sq_slope(tqd, 0, y, 1, pm), sq_slope(tqg, 0, y, 1, pm)), mq); + } + sq_jq(reg, kind, nd, g, hi, lo, dxd, dyd); + sq_jq(reg, kind, g, g, hi, lo, dxg, dyg); + sq_jq(reg, kind, ns, g, hi, lo, sxs, sys); +} + +; --- the step ------------------------------------------------------------------------------------------------------------- +/// The node charges of the state v with the clock clk: words 0 .. 7 positive, 8 .. 15 negative. +pub fn sq_charges(v: []const u32, clk: u32, vdp: u32, vdn: u32, mode: u32, qa: []u64) { + var reg : [144]u64 = [0] ** 144; + var i : usize = 0; + while (i < 16) { + qa[i] = 0; + i = i + 1; + } + var e : usize = 0; + while (e < (SC_NDEV as usize)) { + sq_dev(e, v, clk, vdp, vdn, mode, false, ®, qa); + e = e + 1; + } +} + +/// All registers of the Newton system at v for the step from v0 (whose node charges are q0): G = (charge(v) - charge(v0)) + (currents out - currents in), J = dG/dv. +pub fn sq_stamp(v: []const u32, v0: []const u32, q0: []const u64, clk: u32, vdp: u32, vdn: u32, mode: u32, reg: []u64) { + sc_clear(reg); + var qa : [16]u64 = [0] ** 16; + var e : usize = 0; + while (e < (SC_NDEV as usize)) { + sq_dev(e, v, clk, vdp, vdn, mode, true, reg, &qa); + e = e + 1; + } + var n : u32 = 0; + while (n < SC_NN) { + reg[(SC_RGP + n) as usize] = reg[(SC_RGP + n) as usize] + qa[n as usize] + q0[(SC_NN + n) as usize]; + reg[(SC_RGN + n) as usize] = reg[(SC_RGN + n) as usize] + qa[(SC_NN + n) as usize] + q0[n as usize]; + n = n + 1; + } + if ((mode & SQ_M_NOCHARGE) != 0) { + n = 0; + while (n < SC_NN) { + sc_stamp_cap(reg, n, SQ_CAP_M4B[n as usize], v[n as usize], v0[n as usize]); + n = n + 1; + } + return; + } + sc_stamp_cap(reg, SC_DP, SQ_C4, v[SC_DP as usize], v0[SC_DP as usize]); + sc_stamp_cap(reg, SC_DN, SQ_C4, v[SC_DN as usize], v0[SC_DN as usize]); +} + +/// One Newton iteration on the eight nodes at once. +pub fn sq_iter(v: []u32, v0: []const u32, q0: []const u64, clk: u32, vdp: u32, vdn: u32, mode: u32) { + var reg : [144]u64 = [0] ** 144; + var a : [72]u64 = [0] ** 72; + var x : [8]u64 = [0] ** 8; + sq_stamp(v, v0, q0, clk, vdp, vdn, mode, ®); + sc_load(®, &a); + sc_eliminate(&a); + sc_backsub(&a, &x); + sc_apply_lim(v, &x, SQ_TOP); +} + +/// One backward Euler step to step j from the state in v (updated in place): k Newton iterations, then the residual. +pub fn sq_step(v: []u32, j: u32, vdp: u32, vdn: u32, k: u32, mode: u32, jf: u32) -> u32 { + var v0 : [8]u32 = [0] ** 8; + var n : u32 = 0; + while (n < SC_NN) { + v0[n as usize] = v[n as usize]; + n = n + 1; + } + const clk : u32 = sq_clk_at(j, jf); + var q0 : [16]u64 = [0] ** 16; + sq_charges(&v0, sq_clk_at(j - 1, jf), vdp, vdn, mode, &q0); + var i : u32 = 0; + while (i < k) { + sq_iter(v, &v0, &q0, clk, vdp, vdn, mode); + i = i + 1; + } + var reg : [144]u64 = [0] ** 144; + sq_stamp(v, &v0, &q0, clk, vdp, vdn, mode, ®); + return sc_resid(®); +} + +/// The state at rest with the clock low: the supply on ap, an, net2, net3 and n_clkc, ground on dp and dn, net1 one threshold below the higher input (the spec's rest) or, when n1 is not 0, at the +/// word n1 (the node no switch resets floats where the leakage of the pair and of the tail balance). +pub fn sq_rest(vdp: u32, vdn: u32, n1: u32, v: []u32) { + var top : u32 = vdp; + if (vdn > vdp) { top = vdn; } + v[0] = SQ_VDD; + v[1] = SQ_VDD; + v[2] = top - SC_VTH_U; + if (n1 > 0) { v[2] = n1; } + v[3] = SQ_VDD; + v[4] = SQ_VDD; + v[5] = SQ_OFF; + v[6] = SQ_OFF; + v[7] = SQ_VDD; +} + +/// The run: n steps from rest, the inputs 0.75 V + h and 0.75 V - 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 sq_run(h: u32, swap: bool, k: u32, n: u32, mode: u32, n1: u32, jf: u32, out: []u32, res: []u32) -> u64 { + var vdp : u32 = SQ_OFF + SC_VCM + h; + var vdn : u32 = SQ_OFF + SC_VCM - h; + if (swap) { + vdp = SQ_OFF + SC_VCM - h; + vdn = SQ_OFF + SC_VCM + h; + } + var v : [8]u32 = [0] ** 8; + sq_rest(vdp, vdn, n1, &v); + var s : u64 = 0; + var i : u32 = 0; + while (i < SC_NN) { + out[i as usize] = v[i as usize]; + i = i + 1; + } + var j : u32 = 1; + while (j <= n) { + res[(j - 1) as usize] = sq_step(&v, j, vdp, vdn, k, mode, jf); + i = 0; + while (i < SC_NN) { + out[(j * SC_NN + i) as usize] = v[i as usize]; + s = fold(s, v[i as usize]); + i = i + 1; + } + j = j + 1; + } + return s; +} + +/// The time, in steps with SC_QB fractional bits, of the first rising crossing of the level lvl (words) by node sel over n steps, linear between samples; SC_NONE when there is none. +pub fn sq_cross(out: []const u32, sel: u32, n: u32, lvl: u32) -> u32 { + var j : u32 = 1; + while (j <= n) { + const y0 : u32 = out[((j - 1) * SC_NN + sel) as usize]; + const y1 : u32 = out[(j * SC_NN + sel) as usize]; + if (y0 < lvl and y1 >= lvl) { + const frac : u64 = (((lvl - y0) as u64) << SC_QB) / ((y1 - y0) as u64); + return ((j - 1) << SC_QB) + (frac as u32); + } + j = j + 1; + } + return SC_NONE; +} + +/// The decision time: the first output through 0.75 V, in steps with SC_QB fractional bits after the clock's 50 % point; SC_NONE when neither output gets there. +pub fn sq_t_first(out: []const u32, n: u32) -> u32 { + const lvl : u32 = SQ_OFF + SC_VCM; + const tp : u32 = sq_cross(out, SC_DP, n, lvl); + const tn : u32 = sq_cross(out, SC_DN, n, lvl); + var t : u32 = tp; + if (tn < tp) { t = tn; } + if (t == SC_NONE) { return SC_NONE; } + return t - ((SQ_R << SC_QB) >> 1); +} + +/// Is dp above dn in the last state: the decision "VDP > VDN". +pub fn sq_sign(out: []const u32, n: u32) -> bool { + return out[(n * SC_NN + SC_DP) as usize] > out[(n * SC_NN + SC_DN) as usize]; +} + +/// The resolution time: |dp - dn| through 0.75 V, in steps with SC_QB fractional bits after the clock's 50 % point; SC_NONE when it does not get there. +pub fn sq_t_res(out: []const u32, n: u32) -> u32 { + var j : u32 = 1; + while (j <= n) { + const y0 : u32 = mag_diff(out[((j - 1) * SC_NN + SC_DP) as usize], out[((j - 1) * SC_NN + SC_DN) as usize]); + const y1 : u32 = mag_diff(out[(j * SC_NN + SC_DP) as usize], out[(j * SC_NN + SC_DN) as usize]); + if (y0 < SC_VCM and y1 >= SC_VCM) { + const frac : u64 = (((SC_VCM - y0) as u64) << SC_QB) / ((y1 - y0) as u64); + return ((j - 1) << SC_QB) + (frac as u32) - ((SQ_R << SC_QB) >> 1); + } + j = j + 1; + } + return SC_NONE; +} + +/// A time in steps with SC_QB fractional bits, in ps. +pub fn sq_ps(tq: u32) -> f64 { + return ((tq as f64) / 4096.0) * ((SQ_DT_FS as f64) / 1000.0); +} + +/// A node voltage of a run in V: the word less the ground offset. +pub fn sq_volts(w: u32) -> f64 { + return ((w as f64) - (SQ_OFF as f64)) / 16777216.0; +} + +/// The voltage of node i, in V, tps picoseconds after the clock's 50 % point (half a step into the run), linear between the steps of a run. +pub fn sq_node_at(out: []const u32, tps: f64, i: u32) -> f64 { + const dt : f64 = (SQ_DT_FS as f64) / 1000.0; + const x : f64 = (tps + dt * (SQ_R as f64) / 2.0) / dt; + const j0 : u32 = x as u32; + const f : f64 = x - (j0 as f64); + const a : f64 = sq_volts(out[(j0 * SC_NN + i) as usize]); + const b : f64 = sq_volts(out[((j0 + 1) * SC_NN + i) as usize]); + return a + f * (b - a); +} + +/// The largest distance, in V, of the run's nodes from the reference rows ng (row r: the eight nodes ap an net1 net2 net3 dp dn n_clkc at tps[r] ps after the clock's 50 % point), rows first .. last - 1. +pub fn sq_node_err(out: []const u32, ng: []const f64, tps: []const f64, first: u32, last: u32) -> f64 { + var worst : f64 = 0.0; + var r : u32 = first; + while (r < last) { + var i : u32 = 0; + while (i < SC_NN) { + const d : f64 = fabs64(sq_node_at(out, tps[r as usize], i) - ng[(r * SC_NN + i) as usize]); + if (d > worst) { worst = d; } + i = i + 1; + } + r = r + 1; + } + return worst; +} + +/// A signed word as a real. +pub fn sq_wf(w: u64) -> f64 { + if (sc_neg(w)) { return 0.0 - (sc_mag(w) as f64); } + return (sc_mag(w) as f64); +} + +/// The residual of node n of a stamp in words (positive terms less negative), and the Jacobian entry (r, c). +pub fn sq_gword(reg: []const u64, n: u32) -> f64 { + return ((reg[(SC_RGP + n) as usize]) as f64) - ((reg[(SC_RGN + n) as usize]) as f64); +} + +pub fn sq_jword(reg: []const u64, r: u32, c: u32) -> f64 { + return ((reg[(SC_RJP + r * SC_NN + c) as usize]) as f64) - ((reg[(SC_RJN + r * SC_NN + c) as usize]) as f64); +} + +/// The Jacobian of the stamp at v (for the step from v0, the clock going from clk0 to clk) against the central differences of its residual over dl units of each node: the largest +/// |J - difference| over the 64 entries, each as a fraction of the diagonal of its row (with transposed the Jacobian is read as its transpose: the negative control). +pub fn sq_jac_err(v: []const u32, v0: []const u32, clk0: u32, clk: u32, vdp: u32, vdn: u32, mode: u32, dl: u32, transposed: bool) -> f64 { + var q0 : [16]u64 = [0] ** 16; + sq_charges(v0, clk0, vdp, vdn, mode, &q0); + var reg : [144]u64 = [0] ** 144; + sq_stamp(v, v0, &q0, clk, vdp, vdn, mode, ®); + var worst : f64 = 0.0; + var c : u32 = 0; + while (c < SC_NN) { + var vp : [8]u32 = [0] ** 8; + var vm : [8]u32 = [0] ** 8; + var i : usize = 0; + while (i < 8) { + vp[i] = v[i]; + vm[i] = v[i]; + i = i + 1; + } + vp[c as usize] = v[c as usize] + dl; + vm[c as usize] = v[c as usize] - dl; + var rp : [144]u64 = [0] ** 144; + var rm : [144]u64 = [0] ** 144; + sq_stamp(&vp, v0, &q0, clk, vdp, vdn, mode, &rp); + sq_stamp(&vm, v0, &q0, clk, vdp, vdn, mode, &rm); + var r : u32 = 0; + while (r < SC_NN) { + const fd : f64 = ((sq_gword(&rp, r) - sq_gword(&rm, r)) / (2.0 * (dl as f64))) * 1048576.0; + var ja : f64 = sq_jword(®, r, c); + if (transposed) { ja = sq_jword(®, c, r); } + const e : f64 = fabs64(ja - fd) / fabs64(sq_jword(®, r, r)); + if (e > worst) { worst = e; } + r = r + 1; + } + c = c + 1; + } + return worst; +} + +; --- what ngspice-47 printed --------------------------------------------------------------------------------------------------------- +; The decks are specs/analog/decks/sar_cmp_q_ref.cir, sar_cmp_q_ref50.cir, sar_cmp_q_free.cir and sar_cmp_q_be.cir, run on the reference host (a container, 2 threads), 2026-10-20, ngspice-47, PSP103 +; of the IHP-Open-PDK 21133f21, mos_tt; the numbers are the printed ones, none changed (a time is tfirst less tck, the 6 digits printed). +/// The decision time of the golden comparator, ngspice-47 PSP103 tt 27 C, the SETTLED start (sar_cmp_q_ref.cir RUN S): the first output through 0.75 V after the clock's 50 percent point, ps, for h = 16777 4194 1049 262 84 8 1. +pub const NG_S_FIRST_PS : [7]f64 = [ + 602.537, 608.023, 609.602, 610.014, 610.108, 610.148, 610.152 +]; +/// The resolution time (|dp - dn| through 0.75 V), ps, the same runs. +pub const NG_S_RES_PS : [7]f64 = [ + 1094.320, 1289.830, 1485.350, 1681.080, 1841.580, 2173.360, 2466.760 +]; +/// The same at the start of sar_cmp_psp_real.cir (uic, the clock at 200 ps; sar_cmp_q_ref.cir RUN U), h = 16777 and 4194: a number of that start, kept for the record. +pub const NG_U_FIRST_PS : [2]f64 = [ + 508.121, 513.298 +]; +/// The free start (sar_cmp_q_free.cir: the DC operating point of the circuit with the clock low, net1 where it settles), h = 16777 4194 262. +pub const NG_F_FIRST_PS : [3]f64 = [ + 607.145, 612.615, 614.599 +]; +pub const NG_F_NET1_V : f64 = 0.70731907851; +/// sar_cmp_q_ref50.cir RUN U: the written-out netlist at 50 C with the golden start, h = 16777 4194 1049 262: the decision times the golden subcircuit gave (NG_RL_FIRST_PS of sar_comparator_psp.t27). +pub const NG_U50_FIRST_PS : [4]f64 = [ + 523.032, 528.006, 529.386, 529.741 +]; +/// The same netlist at 50 C, the settled start (RUN S), h = 16777 and 4194. +pub const NG_S50_FIRST_PS : [2]f64 = [ + 632.562, 638.004 +]; +/// sar_cmp_q_be.cir: backward Euler on a fixed grid of 0.2, 0.4, 0.8 and 1.6 ps, the decision time for h = 16777 and 4194 on each (8 numbers). +pub const NG_BE_FIRST_PS : [8]f64 = [ + 602.579, 608.090, + 602.595, 608.131, + 603.142, 608.731, + 603.316, 609.012 +]; +/// The resolution time, the same runs. +pub const NG_BE_RES_PS : [8]f64 = [ + 1093.864, 1289.244, + 1093.348, 1288.588, + 1092.818, 1287.778, + 1090.732, 1285.132 +]; +/// The times of the node rows, ps after the clock's 50 percent point. +pub const NG_NODE_T : [13]f64 = [ + 0.0, 10.0, 25.0, 50.0, 100.0, 200.0, 300.0, 400.0, 450.0, 500.0, 1000.0, 1500.0, 2000.0 +]; +/// ap an net1 net2 net3 dp dn n_clkc (V) at the 13 times of NG_NODE_T after the clock's 50 percent point, the +2 mV run of the settled start, trapezoidal (sar_cmp_q_ref.cir NODES S). +pub const NG_NODES_TRAP : [104]f64 = [ + 1.720630, 1.720530, 0.529299, 1.559340, 1.559330, 0.039470, 0.039470, 1.616610, + 1.888030, 1.888010, 0.389083, 1.543490, 1.543500, 0.038148, 0.038150, 1.622510, + 1.874000, 1.873710, 0.212811, 1.506720, 1.506690, 0.016367, 0.016370, 1.534010, + 1.782380, 1.780740, 0.168958, 1.487400, 1.487300, 0.0 - 0.001790, 0.0 - 0.001748, 1.395540, + 1.539880, 1.534880, 0.163569, 1.482800, 1.482670, 0.0 - 0.020713, 0.0 - 0.020546, 1.129130, + 1.072370, 1.064980, 0.157440, 1.471850, 1.471050, 0.0 - 0.055880, 0.0 - 0.055524, 0.618895, + 0.798181, 0.788695, 0.152678, 1.447260, 1.444920, 0.0 - 0.068594, 0.0 - 0.070891, 0.200934, + 0.557609, 0.545645, 0.147401, 1.408180, 1.404100, 0.071733, 0.062635, 0.029158, + 0.442741, 0.430410, 0.144040, 1.377720, 1.373250, 0.214490, 0.201227, 0.027871, + 0.330594, 0.320043, 0.137959, 1.333360, 1.329830, 0.396411, 0.379621, 0.032471, + 0.0 - 0.025202, 0.047926, 0.005670, 0.771112, 1.146480, 1.004980, 0.573464, 0.000038, + 0.006888, 0.020718, 0.009245, 0.561946, 1.485350, 1.477860, 0.018290, 0.0 - 0.000055, + 0.0 - 0.000973, 0.000250, 0.0 - 0.000214, 0.504301, 1.499260, 1.498840, 0.002283, 0.0 - 0.000002 +]; +/// The same on the backward Euler grid of 1.6 ps, the scheme of the model (sar_cmp_q_be.cir NODES 1600). +pub const NG_NODES_BE : [104]f64 = [ + 1.700510, 1.700460, 0.583839, 1.552610, 1.552610, 0.035408, 0.035408, 1.613420, + 1.890520, 1.890500, 0.386015, 1.545570, 1.545580, 0.039152, 0.039153, 1.620760, + 1.874320, 1.874000, 0.216803, 1.508350, 1.508330, 0.017098, 0.017103, 1.532150, + 1.783910, 1.782250, 0.170001, 1.488190, 1.488100, 0.0 - 0.001341, 0.0 - 0.001299, 1.393600, + 1.542010, 1.537000, 0.163603, 1.482860, 1.482730, 0.0 - 0.020540, 0.0 - 0.020373, 1.127200, + 1.074010, 1.066590, 0.157469, 1.472180, 1.471370, 0.0 - 0.055560, 0.0 - 0.055211, 0.617820, + 0.799416, 0.789931, 0.152700, 1.447370, 1.445040, 0.0 - 0.068070, 0.0 - 0.070384, 0.202580, + 0.558899, 0.546941, 0.147431, 1.408350, 1.404270, 0.072719, 0.063590, 0.030016, + 0.444024, 0.431698, 0.144075, 1.377880, 1.373420, 0.215548, 0.202256, 0.028088, + 0.331739, 0.321195, 0.138015, 1.333430, 1.329900, 0.397171, 0.380346, 0.032416, + 0.0 - 0.025260, 0.048887, 0.005911, 0.768517, 1.150600, 1.009710, 0.568783, 0.000024, + 0.006751, 0.020424, 0.009103, 0.561527, 1.485520, 1.478120, 0.018131, 0.0 - 0.000056, + 0.0 - 0.000968, 0.000255, 0.0 - 0.000211, 0.504242, 1.499260, 1.498830, 0.002280, 0.0 - 0.000002 +]; +pub const NG_RESET_T : [8]f64 = [1900.0, 2000.0, 2050.0, 2100.0, 2200.0, 2400.0, 2800.0, 3400.0]; +/// The eight nodes at 1.9, 2.0, 2.05, 2.1, 2.2, 2.4, 2.8 and 3.4 ns after the start with the clock falling at 2 ns, the +2 mV run, trapezoidal (sar_cmp_q_ref.cir RESET): the reset phase. +pub const NG_RESET_TRAP : [64]f64 = [ + 0.0 - 0.000771, 0.001061, 0.000137, 0.511675, 1.498730, 1.498010, 0.003029, 0.000004, + 0.0 - 0.000972, 0.000260, 0.0 - 0.000210, 0.504420, 1.499260, 1.498830, 0.002294, 0.0 - 0.000003, + 0.154886, 0.102140, 0.057228, 0.587684, 1.548420, 1.561780, 0.036928, 0.229912, + 0.588480, 0.327170, 0.247928, 0.759225, 1.588380, 1.631900, 0.054237, 0.547147, + 1.307310, 0.735388, 0.387742, 0.989177, 1.585800, 1.631590, 0.025071, 0.938912, + 1.480500, 1.329690, 0.461079, 0.865522, 1.508530, 1.111150, 0.0 - 0.017064, 1.323940, + 1.502160, 1.488800, 0.501906, 1.476410, 1.500730, 0.188921, 0.0 - 0.009451, 1.447900, + 1.499820, 1.499520, 0.524594, 1.499620, 1.500040, 0.003265, 0.0 - 0.000247, 1.496280 +]; +/// The same on the backward Euler grid of 1.6 ps (the clock falling at step 1250; sar_cmp_q_be.cir RESET). +pub const NG_RESET_BE : [64]f64 = [ + 0.0 - 0.000771, 0.001050, 0.000133, 0.511510, 1.498730, 1.498020, 0.003014, 0.000004, + 0.0 - 0.000967, 0.000259, 0.0 - 0.000209, 0.504293, 1.499260, 1.498830, 0.002285, 0.0 - 0.000002, + 0.157258, 0.104447, 0.058892, 0.588678, 1.548210, 1.561530, 0.036324, 0.234923, + 0.592995, 0.329066, 0.248036, 0.760571, 1.587840, 1.630980, 0.053709, 0.549064, + 1.305760, 0.736910, 0.387074, 0.988521, 1.585330, 1.630620, 0.025052, 0.939786, + 1.480230, 1.328590, 0.460716, 0.864451, 1.508650, 1.109120, 0.0 - 0.016943, 1.323360, + 1.502180, 1.488780, 0.501742, 1.476340, 1.500730, 0.189429, 0.0 - 0.009462, 1.447840, + 1.499820, 1.499510, 0.524502, 1.499610, 1.500040, 0.003346, 0.0 - 0.000253, 1.496220 +]; + +; --- the tests ---------------------------------------------------------------------------------------------------------------- +; Variables, not constants, carry the data of the asserts: an assert on a constant is folded by the compiler and counts as no assert at all. + +test the_units_the_clock_and_the_rest_state_are_as_stated { + var dt : f64 = (SQ_DT_FS as f64); + var cu : f64 = (SQ_CU_AF as f64); + // dt / C_unit = 1.6 ps / 2 fF = 800 V/A, the ratio of sar_comparator.t27, so a current word is the current in IUNIT over 64 and a charge-table word (Q / 500 fF) is 250 words of this solver + assert fabs64(dt / cu * 1000.0 - 800.0) < 0.000001; + var qs : u64 = SQ_QS; + var cua : u64 = (SQ_CU_AF as u64); + assert_eq(qs * cua, 500000); + var c4 : u64 = SQ_C4; + assert_eq(c4 * cua, 4000); + // the lumped capacitors of the square-law model, in these units, are sar_comparator.t27's SC_CAP in its 0.125 fF + var n : usize = 0; + while (n < 8) { + var cm : u64 = SQ_CAP_M4B[n]; + var sc : u64 = (SC_CAP[n]) as u64; + assert_eq(cm * cua, sc * 125); + n = n + 1; + } + // the offset: ground is 0.5 V up the words, the supply 1.5 V above it, the top of the range 1 V above that + var off : u32 = SQ_OFF; + var vdd : u32 = SQ_VDD; + var top : u32 = SQ_TOP; + assert_eq(off, 8388608); + assert_eq(vdd, off + VDD); + assert_eq(top, vdd + 16777216); + // the clock: low at step 0, high from step 1 (a step of the grid), and with a fall step it is low again from that step on + assert_eq(sq_clk_at(0, 0), off); + assert_eq(sq_clk_at(1, 0), vdd); + assert_eq(sq_clk_at(2125, 0), vdd); + assert_eq(sq_clk_at(1249, 1250), vdd); + assert_eq(sq_clk_at(1250, 1250), off); + assert_eq(sq_clk_at(2125, 1250), off); + // the rest: the spec's, the supply on five nodes and ground on dp, dn and net1 one threshold below the higher input; or net1 at a word of its own + var hh : u32 = 16777; + var vp : u32 = off + SC_VCM + hh; + var vn : u32 = off + SC_VCM - hh; + var v : [8]u32 = [0] ** 8; + sq_rest(vp, vn, 0, &v); + assert_eq(v[0], vdd); + assert_eq(v[1], vdd); + assert_eq(v[2], vp - SC_VTH_U); + assert_eq(v[3], vdd); + assert_eq(v[4], vdd); + assert_eq(v[5], off); + assert_eq(v[6], off); + assert_eq(v[7], vdd); + sq_rest(vn, vp, 0, &v); + assert_eq(v[2], vp - SC_VTH_U); + var n1 : u32 = 20255453; + sq_rest(vp, vn, n1, &v); + assert_eq(v[2], n1); + assert_eq(v[0], vdd); + // the free start's word is ngspice's operating point of net1, 0.7073 V + var nv : f64 = sq_volts(n1); + assert fabs64(nv - NG_F_NET1_V) < 0.000001; +} + +test the_source_charge_closure_is_exact_at_the_two_edges_and_the_extensions_and_the_slopes_are_the_charges_own { + // The closure Qs = -(Qd + Qg) + the edges makes the source terminal's charge equal the drain's at Vds = 0 and zero at a gate drive of 0, exactly, for both devices, so that the charge + // does not jump when the two ends of a channel exchange their parts (the NMOS tables are read as they are, the PMOS tables negated). + var xs : [5]u32 = [0, 1000000, 5242880, 12582912, 25165824]; + var kind : u32 = 0; + while (kind < 2) { + const pm : bool = kind == 1; + const tqd : []const u32 = sq_qd_tab(kind); + const tqg : []const u32 = sq_qg_tab(kind); + var i : usize = 0; + while (i < 5) { + const qd0 : u64 = sq_qt(tqd, xs[i], 0, 0, pm); + const qg0 : u64 = sq_qt(tqg, xs[i], 0, 0, pm); + const qs : u64 = sq_qsrc(qd0, qg0, qd0, qg0, sq_qt(tqd, 0, 0, 0, pm), sq_qt(tqg, 0, 0, 0, pm)); + assert_eq(qs, qd0); + const qdy : u64 = sq_qt(tqd, 0, 0, xs[i], pm); + const qgy : u64 = sq_qt(tqg, 0, 0, xs[i], pm); + const qz : u64 = sq_qsrc(qdy, qgy, sq_qt(tqd, 0, 0, 0, pm), sq_qt(tqg, 0, 0, 0, pm), qdy, qgy); + assert_eq(qz, 0); + i = i + 1; + } + kind = kind + 1; + } + // the origin of both tables is the zero of the charge, and a gate drive of 1.5 V on an NMOS holds 7.4 fC of gate charge per um of width, three times a PMOS' ... of the opposite sign + assert_eq(sq_qt(&TT_QG_N, 0, 0, 0, false), 0); + assert_eq(sq_qt(&TT_QG_P, 0, 0, 0, true), 0); + const gn : f64 = sq_wf(sq_qt(&TT_QG_N, VDD, 0, 0, false)); + const gp : f64 = sq_wf(sq_qt(&TT_QG_P, VDD, 0, 0, true)); + assert gn > 0.0; + assert gp < 0.0; + // THE EXTENSIONS. Beyond the supply the charge goes on with the slope of the last cell, below a gate drive of 0 with the slope of the first (a gate beyond a channel end, a node kicked over the + // rail): a step of one cell (2^20 units) past the edge adds exactly the slope of the edge cell, on the gate axis above and below and on the Vds axis above. + var cell : u32 = 1048576; + var ypt : u32 = 7000000; + const s_top : u64 = sq_slope(&TT_QG_N, VDD, ypt, 0, false); + const q_top : u64 = sc_sadd(sq_qt(&TT_QG_N, VDD + cell, 0, ypt, false), sc_flip(sq_qt(&TT_QG_N, VDD, 0, ypt, false))); + assert_eq(q_top, s_top); + const s_bot : u64 = sq_slope(&TT_QG_N, 0, ypt, 0, false); + const q_bot : u64 = sc_sadd(sq_qt(&TT_QG_N, 0, cell, ypt, false), sc_flip(sq_qt(&TT_QG_N, 0, 0, ypt, false))); + assert_eq(q_bot, sc_flip(s_bot)); + const s_y : u64 = sq_slope(&TT_QD_P, 9000000, VDD, 1, true); + const q_y : u64 = sc_sadd(sq_qt(&TT_QD_P, 9000000, 0, VDD + cell, true), sc_flip(sq_qt(&TT_QD_P, 9000000, 0, VDD, true))); + assert_eq(q_y, s_y); + assert sc_mag(s_top) > 0; + assert sc_mag(s_bot) > 0; + // THE SLOPE IS THE INTERPOLANT'S (FR-002 of M3c, for all four tables): inside a cell the charge moves by exactly slope * step / 2^20, to the 2 words of the two rounded reads + var x0 : u32 = 2621440; + var y0 : u32 = 3500000; + var dx : u32 = 262144; + var k : u32 = 0; + var worst : f64 = 0.0; + while (k < 4) { + var tab : []const u32 = &TT_QD_N; + var pmk : bool = false; + if (k == 1) { tab = &TT_QG_N; } + if (k == 2) { tab = &TT_QD_P; pmk = true; } + if (k == 3) { tab = &TT_QG_P; pmk = true; } + const sx : f64 = sq_wf(sq_slope(tab, x0, y0, 0, pmk)); + const dqx : f64 = sq_wf(sc_sadd(sq_qt(tab, x0 + dx, 0, y0, pmk), sc_flip(sq_qt(tab, x0, 0, y0, pmk)))); + const sy : f64 = sq_wf(sq_slope(tab, x0, y0, 1, pmk)); + const dqy : f64 = sq_wf(sc_sadd(sq_qt(tab, x0, 0, y0 + dx, pmk), sc_flip(sq_qt(tab, x0, 0, y0, pmk)))); + assert fabs64(dqx - sx / 4.0) <= 2.0; + assert fabs64(dqy - sy / 4.0) <= 2.0; + worst = worst + fabs64(sx) + fabs64(sy); + k = k + 1; + } + // the slopes are not all zero: a charge table that did not move would pass the test above + assert worst > 31000.0; + assert worst < 31300.0; +} + +test the_jacobian_is_the_derivative_of_the_residual_at_the_clock_edge_and_along_the_run { + // The Jacobian of the Newton step against the central differences of its own residual over 1 mV of each node (the fraction of the diagonal of its row that the largest of the 64 entries + // is off by), at the clock edge and at four states of the run. The current slopes are forward differences of the reads and the charge slopes the cells' own, so the bound is 2 percent, + // not rounding. + var out : [8008]u32 = [0] ** 8008; + var res : [1000]u32 = [0] ** 1000; + var hh : u32 = 16777; + const s : u64 = sq_run(hh, false, SQ_K, 400, 0, 0, 0, &out, &res); + var vp : u32 = SQ_OFF + SC_VCM + hh; + var vn : u32 = SQ_OFF + SC_VCM - hh; + var dl : u32 = 16384; + var steps : [5]usize = [1, 10, 63, 150, 380]; + var worst : f64 = 0.0; + var lo : f64 = 1.0; + var i : usize = 0; + while (i < 5) { + const j : usize = steps[i]; + var v : [8]u32 = [0] ** 8; + var v0 : [8]u32 = [0] ** 8; + var n : usize = 0; + while (n < 8) { + v[n] = out[j * 8 + n]; + v0[n] = out[(j - 1) * 8 + n]; + n = n + 1; + } + var clk0 : u32 = SQ_VDD; + if (j == 1) { clk0 = SQ_OFF; } + const e : f64 = sq_jac_err(&v, &v0, clk0, SQ_VDD, vp, vn, 0, dl, false); + if (e > worst) { worst = e; } + if (e < lo) { lo = e; } + i = i + 1; + } + assert worst < 0.02; + assert worst > 0.012; + // and it is not the identity of two copies of one function: even the best of the five states is off by a part in a thousand, the forward differences of the current and the chords of the cells + assert lo > 0.001; + // NEGATIVE CONTROL: the Jacobian read as its transpose is far from the slopes of the residual (the coupling of the eight nodes is not symmetric) + var v1 : [8]u32 = [0] ** 8; + var v10 : [8]u32 = [0] ** 8; + var n2 : usize = 0; + while (n2 < 8) { + v1[n2] = out[63 * 8 + n2]; + v10[n2] = out[62 * 8 + n2]; + n2 = n2 + 1; + } + const wrong : f64 = sq_jac_err(&v1, &v10, SQ_VDD, SQ_VDD, vp, vn, 0, dl, true); + assert wrong > 0.2; +} + +test the_decision_time_at_plus_and_minus_2_mv_is_within_2_percent_of_ngspice_and_inside_the_reports_limit { + // The short run, 438 steps of 1.6 ps from rest (the clock rises at step 1), the inputs 0.75 V +- 2 mV (h = 16777 units). THE DECISION TIME is the first output through 0.75 V counted from the + // clock's 50 % point: 613.074 ps at +2 mV and 613.151 ps at -2 mV (the circuit is mirror-symmetric; the 0.08 ps is rounding), against ngspice-47's 602.537 ps on the golden comparator at 27 C + // (the settled start), 603.316 ps on its backward Euler grid of the same 1.6 ps, and the external team's report 607 ps. + var out : [3512]u32 = [0] ** 3512; + var res : [438]u32 = [0] ** 438; + var hh : u32 = 16777; + const sp : u64 = sq_run(hh, false, SQ_K, SQ_NS, 0, 0, 0, &out, &res); + const tp : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + assert tp > 613.07; + assert tp < 613.08; + assert(sq_sign(&out, SQ_NS)); + var rmax : u32 = 0; + var i : usize = 0; + while (i < (SQ_NS as usize)) { + if (res[i] > rmax) { rmax = res[i]; } + i = i + 1; + } + // THE ARITHMETIC: the last Newton residual of all 438 steps is at most 706 units (42 uV), under the bound of 1600 (0.10 mV) the long run needs + assert rmax <= 750; + assert rmax > 700; + const sm : u64 = sq_run(hh, true, SQ_K, SQ_NS, 0, 0, 0, &out, &res); + const tm : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + assert tm > 613.15; + assert tm < 613.16; + // NEGATIVE CONTROL, the sign: the inputs exchanged decide the other way + assert(!sq_sign(&out, SQ_NS)); + assert fabs64(tm - tp) < 0.1; + // THE GAP against ngspice, in percent: +1.749 and +1.762 against the trapezoidal run, +1.617 against the backward Euler run on the model's own grid, +1.001 against the report's 607 ps; the + // bar is 10 percent + const gp : f64 = sp_rel(tp, NG_S_FIRST_PS[0]); + const gm : f64 = sp_rel(tm, NG_S_FIRST_PS[0]); + assert gp > 0.0174; + assert gp < 0.0176; + assert gm > 0.0175; + assert gm < 0.0177; + const gb : f64 = sp_rel(tp, NG_BE_FIRST_PS[6]); + assert gb > 0.0161; + assert gb < 0.0163; + const gr : f64 = sp_rel(tp, 607.0); + assert gr > 0.0099; + assert gr < 0.0101; + assert gp < SQ_BAR; + assert gm < SQ_BAR; + // the report's lower limit is 10 ps and its upper 10 ns: the decision time is inside, 61 times the lower limit + assert tp > 10.0; + assert tp < 10000.0; + assert tp > 60.0 * 10.0; + // THE PINS: the order-sensitive fold of all 438 x 8 samples of each run; one changed word of a table, of the solver or of the run moves them + assert_eq(sp, 6095366964604702313); + assert_eq(sm, 8962847652799617542); +} + +test the_decision_time_follows_ngspice_over_the_input_sweep_and_the_free_start_and_the_longer_inputs_stay_within_3_2_percent { + // The input down to 1 unit (0.06 uV a side). ngspice's decision time rises from 602.5 ps at 2 mV to 610.15 ps at the limit (the logarithm of the input, saturating: the first output is the common-mode + // rise of both), the model's from 613.1 to 629.1 ps; the gap grows from 1.75 to 3.10 percent and is the same sign at every input. Three inputs here, the others in the header. + var out : [3512]u32 = [0] ** 3512; + var res : [438]u32 = [0] ** 438; + var hs : [3]u32 = [4194, 262, 1]; + var ng : [3]f64 = [NG_S_FIRST_PS[1], NG_S_FIRST_PS[3], NG_S_FIRST_PS[6]]; + var lo : [3]f64 = [0.02640, 0.03050, 0.03090]; + var hi : [3]f64 = [0.02660, 0.03070, 0.03110]; + var i : usize = 0; + while (i < 3) { + const s : u64 = sq_run(hs[i], false, SQ_K, SQ_NS, 0, 0, 0, &out, &res); + const t : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + const g : f64 = sp_rel(t, ng[i]); + assert g > lo[i]; + assert g < hi[i]; + assert g < SQ_BAR; + assert(sq_sign(&out, SQ_NS)); + i = i + 1; + } + // THE FREE START: net1 at the DC operating point of ngspice (0.7073 V, where the leakage of the pair and of the tail balance) instead of the spec's 0.401 V. The model moves by +1.8 ps, ngspice by +4.6 ps; + // the model against ngspice's own free start is +1.27 percent (the report: 607 ps, ngspice 607.145 ps). + var n1 : u32 = 20255453; + var hh : u32 = 16777; + const sf : u64 = sq_run(hh, false, SQ_K, SQ_NS, 0, n1, 0, &out, &res); + const tf : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + assert tf > 614.85; + assert tf < 614.87; + const gf : f64 = sp_rel(tf, NG_F_FIRST_PS[0]); + assert gf > 0.0126; + assert gf < 0.0128; + assert gf < SQ_BAR; + const gfr : f64 = sp_rel(tf, 607.0); + assert gfr > 0.0129; + assert gfr < 0.0130; +} + +test the_long_run_reaches_the_rails_at_2_ns_and_the_reset_phase_at_3_4_ns_and_the_nodes_follow_ngspice_before_the_decision { + // The long run: the +2 mV input, the clock falling at step 1250 (2 ns) and the run to step 2125 (3.4 ns, 1.4 ns into the reset). 2125 steps: the last Newton residual is at most 1545 units (0.09 mV) + // at all of them, the clock-fall step included (the nodes are kicked to -0.28 V on ap, -0.40 V on n_clkc in one step, and the floor of the words is -0.5 V). + var out : [17008]u32 = [0] ** 17008; + var res : [2125]u32 = [0] ** 2125; + var hh : u32 = 16777; + const s : u64 = sq_run(hh, false, SQ_K, SQ_NL, 0, 0, SQ_JF, &out, &res); + var rmax : u32 = 0; + var i : usize = 0; + while (i < (SQ_NL as usize)) { + if (res[i] > rmax) { rmax = res[i]; } + i = i + 1; + } + assert rmax <= 1600; + assert rmax > 1500; + // THE LEVELS the report asks for after the decision (winner within 50 mV of 1.5 V, loser within 50 mV of 0 V), read at step 1249 (2 ns after the start, the step before the clock falls): dp 1.49932 V, + // dn 2.27 mV; ngspice at 2 ns after the clock's 50 % point: 1.49884 V and 2.283 mV (trapezoidal), 1.49883 V and 2.280 mV (backward Euler on the 1.6 ps grid). The gaps are 0.48 mV and 0.01 mV. + const dp : f64 = sq_volts(out[1249 * 8 + 5]); + const dn : f64 = sq_volts(out[1249 * 8 + 6]); + assert dp > 1.4993; + assert dp < 1.4994; + assert dn > 0.00225; + assert dn < 0.00227; + const lv_dp : f64 = NG_NODES_TRAP[12 * 8 + 5]; + const lv_dn : f64 = NG_NODES_TRAP[12 * 8 + 6]; + assert fabs64(dp - lv_dp) > 0.00048; + assert fabs64(dp - lv_dp) < 0.00050; + assert fabs64(dn - lv_dn) < 0.00003; + assert fabs64(dp - lv_dp) < SQ_BAR_LEVEL_V; + assert fabs64(dn - lv_dn) < SQ_BAR_LEVEL_V; + // THE RESET the report asks for (both outputs within 50 mV of 0 V): at step 2125 dp is 3.06 mV and dn -0.17 mV; ngspice at 3.4 ns, trapezoidal: 3.265 mV and -0.247 mV, backward Euler 3.346 mV and + // -0.253 mV. The gaps are 0.21 mV and 0.08 mV. In between (the outputs falling through 1 V at 2.4 ns) the model is 30 to 60 mV off: the time of a falling edge, not a level. + const rp : f64 = sq_volts(out[2125 * 8 + 5]); + const rn : f64 = sq_volts(out[2125 * 8 + 6]); + assert rp > 0.00307; + assert rp < 0.00309; + assert rn < 0.0 - 0.00016; + assert rn > 0.0 - 0.00018; + assert fabs64(rp - NG_RESET_TRAP[7 * 8 + 5]) < 0.00020; + assert fabs64(rp - NG_RESET_TRAP[7 * 8 + 5]) > 0.00017; + assert fabs64(rn - NG_RESET_TRAP[7 * 8 + 6]) < 0.00009; + assert fabs64(rp - NG_RESET_TRAP[7 * 8 + 5]) < SQ_BAR_LEVEL_V; + assert fabs64(rn - NG_RESET_TRAP[7 * 8 + 6]) < SQ_BAR_LEVEL_V; + // THE RESOLUTION TIME (|dp - dn| through 0.75 V), not a row of the report: 1040.5 ps against ngspice's 1094.3 ps, 4.9 percent early. The regeneration of the model is faster than the circuit's by that; the + // decision (the first output, before the regeneration) is not. + const tr : f64 = sq_ps(sq_t_res(&out, SQ_NL)); + assert tr > 1040.6; + assert tr < 1040.8; + const gr : f64 = sp_rel(tr, NG_S_RES_PS[0]); + assert gr < 0.0 - 0.0489; + assert gr > 0.0 - 0.0491; + // THE NODES at the 13 times of the rows (the eight nodes each) against ngspice on the model's own 1.6 ps grid: from 50 ps to 500 ps, with the decision still to come, the largest distance is 27.1 mV + // (ap, an: the model discharges them 1 to 2 percent early), and 25.0 mV against the trapezoidal run; up to 1.5 ns, through the regeneration, 83.9 mV against the grid (the dp and dn of the resolution + // edge, 4.9 percent early in time) and 88.6 mV against the trapezoidal run. The clock edge itself, the first row, is 80.9 mV off on ap (the kick is 0.2 V and the model has it 8 percent large). + const e_pre : f64 = sq_node_err(&out, &NG_NODES_BE, &NG_NODE_T, 3, 10); + assert e_pre > 0.0270; + assert e_pre < 0.0272; + const e_pre_t : f64 = sq_node_err(&out, &NG_NODES_TRAP, &NG_NODE_T, 3, 10); + assert e_pre_t > 0.0249; + assert e_pre_t < 0.0251; + const e_all : f64 = sq_node_err(&out, &NG_NODES_BE, &NG_NODE_T, 1, 12); + assert e_all > 0.0833; + assert e_all < 0.0836; + const e_all_t : f64 = sq_node_err(&out, &NG_NODES_TRAP, &NG_NODE_T, 1, 12); + assert e_all_t > 0.0881; + assert e_all_t < 0.0883; + const e_edge : f64 = fabs64(sq_node_at(&out, 0.0, 0) - NG_NODES_BE[0]); + assert e_edge > 0.0808; + assert e_edge < 0.0810; + assert_eq(s, 7506095865634310143); +} + +test the_negative_controls_the_charge_removed_fails_and_so_do_the_wrong_closure_the_wrong_tables_and_too_few_newton_iterations { + var out : [3512]u32 = [0] ** 3512; + var res : [438]u32 = [0] ** 438; + var hh : u32 = 16777; + // CHARGE REMOVED (the devices' own charge off, the lumped capacitors of the square-law model on every node, 4, 4, 6, 2, 2, 4, 4, 2 fF): the decision comes at 5.40 ps, 111 times too fast (ngspice 602.5 ps), + // below the report's 10 ps lower limit. It is the old 5.35 ps of the no-charge model at 0.1 ps, reached here at 1.6 ps. At this step the no-charge latch does not regenerate: both outputs end at 0.786 V, + // 1.5 uV apart, so the 5.40 ps is the first output's common-mode rise and the control is a control of the time, not of the sign. + const s1 : u64 = sq_run(hh, false, SQ_K, SQ_NS, SQ_M_NOCHARGE, 0, 0, &out, &res); + const t1 : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + assert t1 > 5.400; + assert t1 < 5.402; + assert t1 < 10.0; + const e1 : f64 = sq_volts(out[(SQ_NS * SC_NN + SC_DP) as usize]) - sq_volts(out[(SQ_NS * SC_NN + SC_DN) as usize]); + assert fabs64(e1) < 0.00001; + assert sq_volts(out[(SQ_NS * SC_NN + SC_DP) as usize]) > 0.7859; + assert sq_volts(out[(SQ_NS * SC_NN + SC_DP) as usize]) < 0.7860; + assert sp_rel(t1, NG_S_FIRST_PS[0]) < 0.0 - 0.99; + assert NG_S_FIRST_PS[0] / t1 > 111.0; + assert NG_S_FIRST_PS[0] / t1 < 112.0; + // BODY EFFECT REMOVED (the source-to-bulk voltage forced to 0, the 2-D read of the tables): 570.6 ps, 5.31 percent under ngspice where the model is 1.75 percent over, and inside the 10 percent bar: + // the body effect is worth 7.5 percent of the decision time without it and a bar of 3 percent sees it, one of 10 does not. + const s2 : u64 = sq_run(hh, false, SQ_K, SQ_NS, SQ_M_NOBODY, 0, 0, &out, &res); + const t2 : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + assert t2 > 570.55; + assert t2 < 570.65; + const g2 : f64 = sp_rel(t2, NG_S_FIRST_PS[0]); + assert g2 < 0.0 - 0.0528; + assert g2 > 0.0 - 0.0532; + assert fabs64(g2) > 3.0 * 0.0175; + // THE SOURCE CHARGE WITHOUT ITS CLOSURE (Qs = -(Qd + Qg), the bulk's charge dropped on the source node): the Newton step does not converge, the last residual is 1.88e6 units (0.11 V), and the time it + // reads, 544 ps, is nothing: the closure of the two edges is what lets the system be solved + const s3 : u64 = sq_run(hh, false, SQ_K, SQ_NS, SQ_M_NOCLOSURE, 0, 0, &out, &res); + var r3 : u32 = 0; + var i : usize = 0; + while (i < (SQ_NS as usize)) { + if (res[i] > r3) { r3 = res[i]; } + i = i + 1; + } + assert r3 > 1800000; + assert r3 < 2000000; + // THE CHARGE TABLES IN THE WRONG SLOTS (the PMOS tables for the NMOS devices and the reverse): the run runs off the range, 4.27e9 units of residual + const s4 : u64 = sq_run(hh, false, SQ_K, SQ_NS, SQ_M_SWAPQ, 0, 0, &out, &res); + var r4 : u32 = 0; + i = 0; + while (i < (SQ_NS as usize)) { + if (res[i] > r4) { r4 = res[i]; } + i = i + 1; + } + assert r4 > 4000000000; + // TOO FEW NEWTON ITERATIONS: K = 2 leaves 2.76e5 units (16 mV) at the clock edge and reads 612.99 ps, within 0.1 ps of the converged 613.07 ps: the time is right and the residual is 180 times the + // bound, so it is the residual bound and not the time that refuses K = 2. K = 1 leaves 3.3e6 units and reads 614.16 ps. + const s5 : u64 = sq_run(hh, false, 2, SQ_NS, 0, 0, 0, &out, &res); + const t5 : f64 = sq_ps(sq_t_first(&out, SQ_NS)); + var r5 : u32 = 0; + i = 0; + while (i < (SQ_NS as usize)) { + if (res[i] > r5) { r5 = res[i]; } + i = i + 1; + } + assert r5 > 275000; + assert r5 < 277000; + assert r5 > 150 * 1600; + assert fabs64(t5 - 613.074) < 0.15; + assert s1 != s2; + assert s2 != s3; + assert s3 != s4; + assert s4 != s5; +} + +test the_reference_netlist_is_the_golden_comparator_the_settled_start_is_the_reports_and_the_scheme_error_is_a_tenth_of_a_percent { + // THE NETLIST: the written-out comparator (sar_cmp_q_ref50.cir) at 50 C and the golden start gives the first output through 0.75 V 523.032, 528.006, 529.386 and 529.741 ps after the clock, the decision times + // the golden subcircuit gave in sar_cmp_psp_real.cir (NG_RL_FIRST_PS of sar_comparator_psp.t27): to a femtosecond, so the netlist whose 27 C run the model is judged by is that circuit. + var i : usize = 0; + while (i < 4) { + assert fabs64(NG_U50_FIRST_PS[i] - NG_RL_FIRST_PS[i]) < 0.0011; + i = i + 1; + } + // THE START: the golden deck starts from its initial conditions with uic and a clock at 200 ps, and the nodes inside the devices, which start at 0 V, disturb the others for longer than the 200 ps (n_clkc is at + // 1.30 V at the clock, dp and dn 0.62 V): its 27 C decision time is 508.1 ps. The settled start, the operating point with the eight nodes forced to the spec's rest, gives 602.5 ps at 27 C and 632.6 ps at 50 C; + // the free start (net1 where it settles, 0.7073 V) 607.145 ps, which is the external team's report to 0.02 percent. The no-charge model's 5.35 ps was judged against the 523.0 ps of the disturbed start + // (98 times too fast); against the settled start it is 118 times. + assert NG_U_FIRST_PS[0] > 508.1; + assert NG_U_FIRST_PS[0] < 508.2; + assert NG_S50_FIRST_PS[0] > 632.5; + assert NG_S50_FIRST_PS[0] < 632.6; + assert NG_S50_FIRST_PS[0] / 5.35 > 118.0; + assert NG_S50_FIRST_PS[0] / 5.35 < 118.4; + assert sp_rel(NG_F_FIRST_PS[0], 607.0) > 0.0002; + assert sp_rel(NG_F_FIRST_PS[0], 607.0) < 0.0003; + assert sp_rel(NG_S_FIRST_PS[0], 607.0) < 0.0 - 0.0073; + assert sp_rel(NG_S_FIRST_PS[0], 607.0) > 0.0 - 0.0074; + assert NG_S_FIRST_PS[0] > 10.0; + // THE SCHEME: ngspice's backward Euler on a fixed grid of 0.2, 0.4, 0.8 and 1.6 ps, against its converged trapezoidal run: the decision time is within 0.01, 0.02, 0.10 and 0.13 percent (+2 mV), within + // 0.01, 0.02, 0.12 and 0.16 percent (+0.5 mV), so the model's one step of 1.6 ps is not an error of the scheme at the resolution of this comparison; the resolution time, 0.04 percent to 0.3 percent. + var worst : f64 = 0.0; + var w2 : f64 = 0.0; + var k : usize = 0; + while (k < 4) { + const a : f64 = fabs64(sp_rel(NG_BE_FIRST_PS[2 * k], NG_S_FIRST_PS[0])); + const b : f64 = fabs64(sp_rel(NG_BE_FIRST_PS[2 * k + 1], NG_S_FIRST_PS[1])); + if (a > worst) { worst = a; } + if (b > worst) { worst = b; } + const c : f64 = fabs64(sp_rel(NG_BE_RES_PS[2 * k], NG_S_RES_PS[0])); + if (c > w2) { w2 = c; } + k = k + 1; + } + assert worst < 0.0017; + assert worst > 0.0016; + assert w2 < 0.0034; + // the reference rows are ngspice's own numbers: on the settled start the 13 node rows of the trapezoidal run and of the 1.6 ps grid agree to 5 mV from 25 ps on (the edge itself, the first two rows, differ + // by the time of the clock's 50 % point, 1.0 ps against 0.77) + var worst_rows : f64 = 0.0; + var r : usize = 2; + while (r < 13) { + var n : usize = 0; + while (n < 8) { + const d : f64 = fabs64(NG_NODES_TRAP[r * 8 + n] - NG_NODES_BE[r * 8 + n]); + if (d > worst_rows) { worst_rows = d; } + n = n + 1; + } + r = r + 1; + } + assert worst_rows < 0.005; + assert worst_rows > 0.0045; +}