diff --git a/.trinity/seals/analog_AnalogAcceptSar.json b/.trinity/seals/analog_AnalogAcceptSar.json index cf20340e91..b41152c485 100644 --- a/.trinity/seals/analog_AnalogAcceptSar.json +++ b/.trinity/seals/analog_AnalogAcceptSar.json @@ -17,6 +17,10 @@ "spec": "specs/analog/mos_psp103_gates.t27", "spec_hash": "sha256:1b86ebd4bba806b972b39625d135646d7880255ab934c521a51467c92e42ee35" }, + { + "spec": "specs/analog/mos_psp103_inv20.t27", + "spec_hash": "sha256:be818b3a48e2dbe02327bd9257d1273c07d6473637f84aba376da11c3fb3f26a" + }, { "spec": "specs/analog/sar_adc.t27", "spec_hash": "sha256:208a64705a1d07ff14d3131d999135a3d5da6c7cb9bc2e919430d7e60b34db68" @@ -55,16 +59,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b66af5af3cc53c27fc14dfb1317d2134c9adf18900e43bbe57dbf9a794aa9a5f", - "gen_hash_rust": "sha256:b942a24880e75b0d0b69639af85d1bb2868aa138ceecee19933630474018237a", - "gen_hash_verilog": "sha256:88b09e15ce920440b641b1419d00c13f0d917872b484db9c14cd5e82925a05e8", - "gen_hash_zig": "sha256:927eb1ca460416a20320b84189450c287f5e8eeb884216b4e14fc6062c8244b2", + "gen_hash_c": "sha256:b767ded3c58645c55d37932df32bebdaefdb478d576270261b16e66a55116380", + "gen_hash_rust": "sha256:16a9791d37fb11fa6371732d33a7d8962556bbc0945b0ca2b9c02439b6f6358d", + "gen_hash_verilog": "sha256:02dca5f81784af3de632a5455449f75b31638cded6afe634df5b64e696813fa7", + "gen_hash_zig": "sha256:7e542593ba19acb370f3805db2c4b1b512833c176aa70f9fae938879fb04af24", "module": "AnalogAcceptSar", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T10:59:07Z", + "sealed_at": "2026-10-20T12:38:53Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:b70fef8d2eedf99f3423a2fa8ad06844da5891959a3c7500db7e29c7a2130f18", + "spec_hash": "sha256:521b7f9ef00ae1ed63e70a162ca3918ef412477a49c30df6790a182a824b8de9", "spec_path": "specs/analog/accept_sar.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/analog_AnalogMosPsp103Inv20.json b/.trinity/seals/analog_AnalogMosPsp103Inv20.json new file mode 100644 index 0000000000..319982a8a3 --- /dev/null +++ b/.trinity/seals/analog_AnalogMosPsp103Inv20.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.t27", + "spec_hash": "sha256:e04b69e5c43188a3d46a4c0949cfc727f239721de73fb46e92c8a845bbbbc98e" + }, + { + "spec": "specs/analog/mos_psp103_data.t27", + "spec_hash": "sha256:86e7c154c88a325e41ff4a154a3b766cb8d65e89e2ea82176b46ee4fc523dd74" + }, + { + "spec": "specs/analog/mos_psp103_gates.t27", + "spec_hash": "sha256:1b86ebd4bba806b972b39625d135646d7880255ab934c521a51467c92e42ee35" + }, + { + "spec": "specs/analog/mos_table.t27", + "spec_hash": "sha256:cbebd820d2284174fd88fd6e5c36fb4f0a7778c9977c9742fe215b177d623bd8" + }, + { + "spec": "specs/analog/sar_comparator_q.t27", + "spec_hash": "sha256:6fad103672e13c04173fe1c21f502304c5a60012549f2631330a38f78c82cf27" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:3377346f66fa7e7fb4f224341c0b22984e7208a69a0d2497343c3c583712db41", + "gen_hash_rust": "sha256:7b840ab30f43f5508ddf646ee1514a33be78db0291eef69dc044c78c8b156443", + "gen_hash_verilog": "sha256:b3ef1b45cbc696008398014149b3f47603315c7190c0228ab8df08b41f93ed2d", + "gen_hash_zig": "sha256:2ee3de4d11cd62e2e69722a9df95381c05b8938a3d4260a5d5f05aaca9851e2f", + "module": "AnalogMosPsp103Inv20", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T12:38:42Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:be818b3a48e2dbe02327bd9257d1273c07d6473637f84aba376da11c3fb3f26a", + "spec_path": "specs/analog/mos_psp103_inv20.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 10, + "total": 10, + "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 1cd679a7f7..f4dc967080 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); 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. +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); M5e (issue #11350) gives the inverter's delays into 20 fF the devices' own charge and an output node free to leave the tables' 0 to Vdd range, which turns the inverter's last row from NOT MODELLED to PASS, so that block is a PASS too (section 3e). 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). @@ -14,23 +14,25 @@ A row is one quantity of the report's check. - FAIL: our model produced the value and it is outside the limit. It is reported as measured. - NOT MODELLED: our model does not produce the quantity the check enforces. Nothing is compared, and the row is never a pass, whatever a constant might have said (`as_verdict(false, x)` is NOT MODELLED for both values of `x`; a test pins it). -A block is FAIL when any row fails, PASS only when it has rows and every row is PASS, and NOT MODELLED otherwise. A block whose threshold passes and whose delay is not modelled is therefore not a pass: it is NOT MODELLED, and its passing row is still shown. +A block is FAIL when any row fails, PASS only when it has rows and every row is PASS, and NOT MODELLED otherwise. A block with a passing row beside a row that is not modelled (the dff's behaviour and clock-to-Q beside its levels) is therefore not a pass: it is NOT MODELLED, and its passing rows are still shown. The inverter was such a block until M5e: its threshold passed and its delays were not modelled. 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. +The inverter's delay row (M5e, issue #11350) is worded the same way: PASS only inside the report's limit (1 to 500 ps, both delays) AND within 10 percent of ngspice-47's converged run of the same cell into 20 fF (`psp103_inv20_fine.cir`, each delay); a produced value that disagrees is the model's FAIL. The report's own limit accepts the control of section 3e (the charge removed gives 39 and 52 ps), so the cross-check is what refuses it. + ## 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 | 7 | clock_gen, SAR_logic, integration, nand, nor, tgate, dynamic_comparator | +| PASS | 8 | clock_gen, SAR_logic, integration, nand, nor, tgate, dynamic_comparator, inverter | | FAIL | 0 | | -| NOT MODELLED | 6 | inverter, dff, tracking_switch, DAC_control, DAC_control_top, neg_bottom_plate_logic | +| NOT MODELLED | 5 | dff, tracking_switch, DAC_control, DAC_control_top, neg_bottom_plate_logic | -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). +Over the 30 rows: 24 PASS, 0 FAIL, 6 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; after M4b-q and before M5e 7 / 0 / 6 blocks and 23 / 0 / 7 rows. The M5e row is the inverter's delays into 20 fF (PASS, NOT MODELLED before), so the inverter block, whose threshold row already passed, is a PASS (section 3e). 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. Two of the five NOT MODELLED blocks have at least one measured, passing row (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), and that of the inverter the PASS of a transient of the cell into 20 fF on the PSP103 tables with the devices' charge, each delay 0.5 percent from ngspice's (section 3e). 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). @@ -41,7 +43,7 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | block | check | our value | report | limit | verdict | function | |---|---|---|---|---|---|---| | inverter | switching threshold | 0.6892 V | 0.69 V | 0.5 to 1.0 V | PASS | `as_inv_vth_of` | -| inverter | delays fall / rise into 20 fF | not produced | 81 / 100 ps | 1 to 500 ps | NOT MODELLED | | +| inverter | delays fall / rise into 20 fF (ideal gate step) | fall 75.755 ps, rise 93.246 ps; ngspice 75.368 and 92.778 ps (+0.51 % and +0.50 %) | 81 / 100 ps | 1 to 500 ps | PASS (within 10 % of ngspice) | `as_inv_delay` | | nand | truth table 00, 01, 10, 11 (logic level) | 1, 1, 1, 0 | rows right | exact | PASS | `sl_nand_code` | | nand | levels into 10 fF (DC solve, worst level of the 4 inputs) | 1 levels 1.5 V - 0.18 / 0.48 / 0.48 uV; 0 level 0.18 uV | 1.5 V for a 1, microvolts for a 0 | 1.45 to 1.55 V; +-50 mV | PASS (within 2.4 uV of ngspice) | `gt_levels` | | nor | truth table 00, 01, 10, 11 (logic level) | 1, 0, 0, 0 | rows right | exact | PASS | `sl_nor_code` | @@ -70,7 +72,7 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | neg_bottom_plate_logic | Bit_n follows Bit while CK11 is low, complement after; VCM | not produced | 0.75 V | 0.7 to 0.8 V; +-50 mV | NOT MODELLED | | | integration | worst code error, -1 V to +1 V ramp of 8 conversions | 18.5 LSB | 13.8 LSB | +-64 LSB | PASS | `as_ramp_literal` | | integration | smallest step between conversions | 198 LSB | 154 LSB (ideal 152) | 60 to 250 LSB | PASS | `as_ramp_literal` | -| corners (smoke suite, not a block) | inverter delay 62 to 124 ps, comparator decision 467 to 829 ps, 5 corners x 3 temperatures | not produced | within | as stated | NOT MODELLED | | +| corners (smoke suite, not a block) | inverter delay 62 to 124 ps, comparator decision 467 to 829 ps, 5 corners x 3 temperatures | not produced (the tt 27 C delays, 75.8 / 93.2 ps and 613 ps, are one point of the 15, and inside the range) | within | as stated | NOT MODELLED | | ## 3b. The new rows of M5d, with ngspice's number beside ours @@ -175,9 +177,57 @@ THE ONE STEP BEYOND THE ISSUE. The issue asks for the drain and gate charge from 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. +## 3e. The inverter into 20 fF with the devices' own charge (M5e, issue #11350) + +The last row of the inverter block, "delays fall / rise into 20 fF" (the report's 81 and 100 ps, limit 1 to 500 ps), was NOT MODELLED: the transient of `mos_psp103.t27` is validated at 500 fF and 10 pF, and said of 20 fF that the gate edge kicks the output node 5 times further than at 100 fF, past the 0 to 1.5 V range of the tables. `specs/analog/mos_psp103_inv20.t27` is that run. + +THE CELL. The inverter of the golden SAR ADC netlist: a PMOS of W 3.0 um, L 0.45 um, ng 1, m 1 with its bulk on the supply and an NMOS of W 1.0 um, L 0.45 um, ng 1, m 1 with its bulk on ground, 1.5 V, 20 fF on the output and nothing else. The repository states those sizes in the header of `mos_psp103.t27` (W, L and Vdd, "facts of the circuit") and in the deck `psp103_inv_be_500f.cir` (W, L, ng and m of both devices); the tables of `mos_psp103_data.t27` are of exactly these two devices, so no table is new. Each source is on its own bulk, so the source-to-bulk axis of M3e is read at Vsb = 0, which is the 2-D log read bit for bit (a test); a channel end kicked past a rail is a forward junction and is read at 0, as `sar_comparator_q.t27` reads it. + +THE REFERENCE. `specs/analog/decks/psp103_inv20_fine.cir`, run in ngspice-47 with the PSP103 (IHP-Open-PDK 21133f21, mos_tt, 27 C, OSDI) on the reference host: the same two devices and 20 fF, the gate an IDEAL step (a B source of `u(time - t0)`, no slew, so that the model's gate edge, one jump of charge, is the same edge), at the supply until 1 ns, 0 V until 2 ns, the supply from 2 ns, each level held 20 delays; trapezoidal rule with the step held to 0.05 ps. The reference's own sensitivity to its step is a number of the deck: runs at 0.2 ps and 0.01 ps give the same 50 percent crossings to 1 fs. The deck also prints the kick (the output goes to -0.3848 V 0.56 ps after the first edge and to 1.9634 V 0.49 ps after the second) and 20 samples after each edge. + +THE MODEL. One node, the output, with the equation of the M3c charge step (`newton_iter_q` of `mos_table.t27`): the node holds C y + Qd_n - Qd_p, so a gate edge is a jump of charge, the Miller kick. The tables are M3c's and are not rescaled (a charge word is Q / 500 fF, a current word the current that moves a 500 fF node in 100 ps; C = 20 fF and the step enter as the factors 25 and 25 dt / 100 ps). Reused, not copied: the current is `sq_i` and the charge `sq_qt` and `sq_qsrc` of `sar_comparator_q.t27` (the tables extended linearly past their edges, the source terminal closed by its two edges). The new part is that the output is free to leave 0 to Vdd: an ideal gate edge kicks it 0.40 V below ground when the gate falls and 0.50 V over the supply when it rises (ngspice's 0.38 V and 0.46 V), and while it is out of range the two ends of the channel of the NMOS (below ground) or the PMOS (over the supply) exchange their roles: the output is then the source, and the terminal charge on it is the source terminal's. The charge and the current are continuous across the exchange (a test). Each step is solved by bisection (64 halvings, the residual rises with the output: a test), so the answer is the converged step and not K Newton iterations of the board's arithmetic. The M3c integer step could not be reused as it is: it holds the output to 0 to Vdd, and a charge table scaled by 25 for 20 fF overflows the u64 bound of its bilinear read when scaled plainly and goes below 0 when scaled around its 2^21 origin. + +THE NUMBERS (delay = the 0.75 V crossing after the edge, ps; the gap is the model's over ngspice's converged run): + +| | fall (output falls) | rise (output rises) | +|---|---|---| +| model, 1 ps step | 75.755 | 93.246 | +| ngspice-47, converged | 75.368 | 92.778 | +| gap | +0.513 % | +0.504 % | +| the report (its own stimulus) | 81 | 100 | +| ours against the report | 6.5 % under | 6.8 % under | + +The time step. The delay falls with the step (backward Euler is slow by its first-order error) and each halving takes half of the last: + +| step | fall | rise | gap to ngspice (fall, rise) | +|---|---|---|---| +| 4 ps | 75.971 | 93.485 | +0.80 %, +0.76 % | +| 2 ps | 75.826 | 93.324 | +0.61 %, +0.59 % | +| 1 ps (the verdict) | 75.755 | 93.246 | +0.51 %, +0.50 % | +| 0.5 ps | 75.720 | 93.210 | +0.47 %, +0.47 % | +| 0.25 ps | 75.703 | 93.191 | +0.44 %, +0.45 % | + +Halving the step moves each delay by 0.19 / 0.17 percent (4 to 2 ps), 0.094 / 0.084 (2 to 1), 0.046 / 0.039 (1 to 0.5, the issue's under-1-percent condition) and 0.022 / 0.020 percent (fall / rise); the limit in time is about 75.686 and 93.172 ps, so 1 ps is 0.09 and 0.08 percent over it, a sixth of the gap to ngspice, which is the tables' and the charge's. A test pins each of these. + +The waveform, the model at 0.5 ps against ngspice's 20 samples per edge (0.5 ps to 400 ps): the rise is within 11.3 mV (0.75 percent of the supply) at the worst sample, the first, where the model's kick is 0.3958 V against ngspice's 0.3848 V (2.9 percent larger), and within 5.4 mV from 30 ps on; the fall is within 35.0 mV (2.3 percent) at the first sample, where the model's kick is 0.4984 V over the supply against 0.4634 V (7.5 percent larger), and within 5.8 mV from 30 ps on. Through the 50 percent crossing the model is 4 to 5 mV under the rising output and 4.5 to 6 mV over the falling one, which at 12 to 16 V/ns is the half percent of the delays. The cause of the larger kick is not isolated. + +The same code at 500 fF and 100 ps (factors 1 and 1, no table scaled) follows ngspice's backward Euler on the same 100 ps grid (`psp103_inv_be_500f.cir`) to -0.0022 percent (rise) and -0.026 percent (fall), where the kick is 6 mV: where the old step held, the new one is the old one. + +THE NEGATIVE CONTROLS (each a test; the bar is 10 percent of ngspice's delay): + +| the break | fall | rise | gap to ngspice | verdict | +|---|---|---|---|---| +| the devices' charge removed (FR-004) | 39.043 ps | 51.868 ps | -48.2 %, -44.1 % | inside the report's 1 to 500 ps, refused by the bar: the delay row and the block FAIL | +| the output held to 0 to 1.5 V, as the M3c step holds it (the kick is lost) | 46.793 ps | 63.122 ps | -37.9 %, -32.0 % | refused by the bar: FAIL | +| a load of 25 fF instead of 20 | 85.544 ps | 106.242 ps | +13.5 %, +14.5 % | refused by the bar (22 fF gives +5.7 and +6.1 percent, inside it) | + +The report's own limit (1 to 500 ps) does not see any of the three; the cross-check does. The report's separate corners suite (62 to 124 ps) does see the charge removed (39 and 52 ps). + +What this does not say: another corner or temperature (tt, 27 C), another load, or another input slew (the gate edge is ideal; the report's own stimulus is not in the material this was made from), the waveform inside the first 30 ps (11 and 35 mV off at the first sample), a device's current past the tables' edge (held at the edge: the PMOS at Vsd up to 1.9 V and the NMOS at Vds up to 2.0 V are read at 1.5 V), the junction conduction of the kicked node (read at 0), PSP103's NQS and gate-resistance effects, mismatch, noise, power, hardware. The model is f64 with integer table reads and a bisection: the test-side model of the cell, not the board's arithmetic. + ## 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. +- 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 (M5e, `mos_psp103_inv20.t27`, section 3e) are the same two devices (W 1.0 um and 3.0 um, L 0.45 um, m 1) with the devices' own drain and source charge, an ideal gate step, the output node free to leave the tables' 0 to Vdd range, backward Euler of 1 ps: fall 75.755 ps and rise 93.246 ps, +0.51 and +0.50 percent from ngspice's converged run of the same cell (75.368 and 92.778 ps), inside the report's 1 to 500 ps: the row PASSES, so the block does. Before M5e they were NOT MODELLED: the transient was validated at 500 fF (tpLH 1.344 ns, tpHL 1.018 ns) and 10 pF only, and 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). @@ -205,6 +255,7 @@ The four numbers agree within a few LSB. Without the measured offset the end cod - 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 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. +- Inverter delays: the gate is an ideal step, the load 20 fF and nothing else; the report's own stimulus (its input slew, the cell that drives it) is not in the material this was made from, and our delays are 6.5 and 6.8 percent under the report's 81 and 100 ps. The row is judged against ngspice's run of the same cell with the same ideal step, not against the report's number; the report's limit (1 to 500 ps) is the check. - 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. - nand and nor: the levels are the DC solution with no load, which is the level a gate settles to into 10 fF; the report's measurement is a settled level into 10 fF, and no transient is run here. @@ -217,6 +268,10 @@ Every check function accepts the report's own value and refuses a broken one, so | check | the break | its value | |---|---|---| | 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 | +| inverter delays (M5e) | the devices' charge removed | 39.043 ps fall, 51.868 ps rise, 48.2 and 44.1 percent under ngspice's: inside the report's 1 to 500 ps, refused by the 10 percent bar, row and block FAIL; refused by the corners suite's 62 to 124 ps too | +| inverter delays (M5e) | the output held to 0 to 1.5 V (the M3c step's range) | 46.793 ps fall, 63.122 ps rise, 37.9 and 32.0 percent under: refused by the bar, row and block FAIL | +| inverter delays (M5e) | a load of 25 fF instead of 20 | 85.544 ps fall, 106.242 ps rise, +13.5 and +14.5 percent over: refused by the bar (22 fF, +5.7 and +6.1 percent, is inside it) | +| inverter delays (M5e) | a delay outside the limit that agrees with ngspice | FAIL: the report's limit is checked as well | | 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 | @@ -241,27 +296,29 @@ Every check function accepts the report's own value and refuses a broken one, so Four mutants of the new code were also run and each fails tests (not committed): the nor's output not mirrored (2 tests fail), the 10 percent bar loosened to 50 percent (3), the tgate's NMOS gate drive wrong (2), and the tgate's Ron verdict made to ignore the cross-check (2, the tgate's and the counts'). -A limit of the control: some of the report's limits are wide, and a model error inside the limit is not seen by that check. A bridge capacitor of 2 units gives a worst error of 45.5 LSB, which the +-64 LSB limit accepts; the inverter threshold limit (0.5 to 1.0 V) accepts the two tables in each other's slots (0.811 V) and every corner's trip point (ngspice's 0.663 to 0.718 V). `sar_adc.t27` sees the bridge by the codes (0 of 31 match), `mos_psp103.t27` sees the corners by the delays. Two more, from M5d. The level cross-check of a gate does not see a device's width: the two tables in each other's slots (a 3 um stack against a 1 um pair) leave every level within 0.69 uV (nand) and 2.7 uV (nor) of ngspice's, because a level is the leakage of the off side times the resistance of the on side and both are microvolts for either width; the leakage and the resistance do see it. And the choice of the reference cell matters: against the no-body-effect cell (each bulk on its source) the model's Ron is 0.06 percent off and would have PASSED, with a number that is 30 percent from the real cell; the reference is the real cell (bulks on the rails), on purpose. +A limit of the control: some of the report's limits are wide, and a model error inside the limit is not seen by that check. A bridge capacitor of 2 units gives a worst error of 45.5 LSB, which the +-64 LSB limit accepts; the inverter threshold limit (0.5 to 1.0 V) accepts the two tables in each other's slots (0.811 V) and every corner's trip point (ngspice's 0.663 to 0.718 V). `sar_adc.t27` sees the bridge by the codes (0 of 31 match), `mos_psp103.t27` sees the corners by the delays. The inverter's delay limit (1 to 500 ps) is as wide: it accepts all three breaks of section 3e (39 to 106 ps), and it is the cross-check against ngspice's converged run that refuses them. Two more, from M5d. The level cross-check of a gate does not see a device's width: the two tables in each other's slots (a 3 um stack against a 1 um pair) leave every level within 0.69 uV (nand) and 2.7 uV (nor) of ngspice's, because a level is the leakage of the off side times the resistance of the on side and both are microvolts for either width; the leakage and the resistance do see it. And the choice of the reference cell matters: against the no-body-effect cell (each bulk on its source) the model's Ron is 0.06 percent off and would have PASSED, with a number that is 30 percent from the real cell; the reference is the real cell (bulks on the rails), on purpose. ## 7. Not done, and why -- 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 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, the comparator's reset until M4b-q ran a second clock phase, and the inverter's 20 fF delays until M5e freed the output node from the tables' range). Each needs a model that does not exist: 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 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. +- 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. M5e met the same defect twice more (a struct field `i: f64` made the `i` of `cross_idx` in `mos_psp103.t27` a float, a field `cur: f64` the `cur` of `sar_comparator.t27`'s `sc_apply_lim`, each a Zig compile error) and the fields of `IvDev` in `mos_psp103_inv20.t27` are named `chg_w` and `cur_iu` so that no local of an imported spec shares a name with them. ## 8. How to run it ``` ./target/release/t27c test-report specs/analog/accept_sar.t27 +./target/release/t27c test-report specs/analog/mos_psp103_inv20.t27 ./target/release/t27b test specs/analog/accept_sar.t27 --check +./target/release/t27b test specs/analog/mos_psp103_inv20.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 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. +`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_inv20.t27` has 10 (the two delays against ngspice, the halving of the step, the waveform and the kick, the three negative controls, the source-to-bulk read, the premise of the bisection, the continuity across the exchange of the channel's ends, the 500 fF anchor, the rest levels); `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, 3d and 3e 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. 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. +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. M5e adds `psp103_inv20_fine.cir` (the inverter into 20 fF with an ideal gate step: the two 50 percent crossings at three time steps, the rest levels, the kick and 20 samples after each edge), run on the reference host in 47 seconds (an earlier revision of the deck, without the 10-digit print and the two further steps, gave the same numbers; the container and its files were removed after each run). 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 dcc477822c..5ad858dbb7 100644 --- a/specs/analog/accept_sar.t27 +++ b/specs/analog/accept_sar.t27 @@ -2,7 +2,8 @@ ; 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; 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 +; its reset from NOT MODELLED to PASS; M5e (issue #11350) gives the inverter's delays into 20 fF the devices' own charge and an output node free to leave the tables' 0 to Vdd range +; (mos_psp103_inv20.t27), which turns the last row of the inverter 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 @@ -18,9 +19,16 @@ ; a resistance or a current. A value without an ngspice run, or that does not agree with it, is shown and is NOT MODELLED. ; - ; THE LEVEL OF EACH MODEL, and what it means for the verdict (the doc docs/analog/accept-sar-2026-10-20.md has the table with the numbers). -; inverter PSP103 tables of the SG13G2 NMOS (W 1.0 um) and PMOS (W 3.0 um), tt, 27 C, current in the log domain (M3c-2, mos_psp103.t27): its DC transfer gives the -; switching threshold. The delays into 20 fF are NOT MODELLED: the model's transient is validated at 500 fF and 10 pF only, and at 20 fF the gate edge -; kicks the output 5 times further outside the tables' range of 0 to Vdd than the 100 fF case that mos_psp103.t27 already says it cannot hold. +; inverter PSP103 tables of the SG13G2 NMOS (W 1.0 um) and PMOS (W 3.0 um), L 0.45 um, m 1 (the cell of psp103_inv_be_500f.cir and of the header of mos_psp103.t27), tt, 27 C, current in the log +; domain (M3c-2, mos_psp103.t27): its DC transfer gives the switching threshold, and (M5e, mos_psp103_inv20.t27) the same two devices into 20 fF, with the devices' own drain +; and source charge, the output node free to leave 0 to Vdd (an ideal gate edge kicks it 0.40 V below ground and 0.50 V over the supply), backward Euler of 1 ps, give the +; two delays. Before M5e the delays were NOT MODELLED: the transient was validated at 500 fF and 10 pF only, and at 20 fF the kick is 5 times further outside the tables' range than +; the 100 fF case that mos_psp103.t27 already says it cannot hold. Each source is on its own bulk, so the Vsb axis of M3e is read at Vsb = 0 (the 2-D read, bit for bit). A row is +; PASS only inside the report's limit (1 to 500 ps) AND within the bar of ngspice-47's converged run of the same cell into 20 fF (psp103_inv20_fine.cir, an ideal gate step): +; 10 percent. The model's fall is 75.755 ps against ngspice's 75.368 ps (+0.51 percent) and its rise 93.246 ps against 92.778 ps (+0.50 percent); halving the step to 0.5 ps +; moves them by 0.046 and 0.039 percent. Both rows PASS, so the block PASSes. NEGATIVE CONTROLS: the same model with the charge removed (39.0 and 51.9 ps, 48 and 44 percent +; under ngspice's, inside the report's limit and refused by the bar) and with the output held to the tables' range (46.8 and 63.1 ps, 38 and 32 percent under) are each the +; model's FAIL. ; nand, nor two rows each. The truth table at GATE LEVEL (sar_logic.t27: a gate is a delay and a truth table, a value is 0, 1 or X) is one; the LEVELS in volts are the ; other, a DC solve of the two-input gate on the same PSP103 log tables as the inverter (mos_psp103_gates.t27, M5d), each level within 3 uV of ngspice's run of the ; same cell. Both rows PASS, so the two blocks PASS. @@ -85,6 +93,7 @@ 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_inv20::{IV_DT_FS, IV_M_NOCHARGE, IV_M_RANGE, NG20_TPLH_PS, NG20_TPHL_PS, IvDelays, iv_delays}; 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}; @@ -113,7 +122,6 @@ pub const AS_N_BLOCKS : u32 = 13; pub const AS_N_ROWS : u32 = 30; ; --- why a row is NOT MODELLED (one reason per kind, quoted by the doc) --------------------------------------------- -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_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"; @@ -424,6 +432,17 @@ 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 inverter's delays into 20 fF on the model of mos_psp103_inv20.t27 (M5e: the tt 27 C PSP103 log tables read at Vsb = 0, the devices' own drain and source charge, an ideal gate step, the output +/// node free to leave 0 to Vdd, backward Euler of 1 ps), mode 0, or one of its broken forms (IV_M_NOCHARGE, IV_M_RANGE: the negative controls): fall_ps, the output falling after the gate rises (the report's +/// "fall"), rise_ps, the output rising after the gate falls (its "rise"), and the CROSS-CHECK against ngspice-47's converged run of the same cell into 20 fF (specs/analog/decks/psp103_inv20_fine.cir): +/// g_fall and g_rise, each delay's relative gap to ngspice's own. +pub struct InvDly { fall_ps: f64, rise_ps: f64, g_fall: f64, g_rise: f64 } + +pub fn as_inv_delay(mode: u32) -> InvDly { + const d : IvDelays = iv_delays(IV_DT_FS, mode); + return InvDly { fall_ps: d.fall_ps, rise_ps: d.rise_ps, g_fall: rel_gap(d.fall_ps, NG20_TPHL_PS), g_rise: rel_gap(d.rise_ps, NG20_TPLH_PS) }; +} + /// 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 @@ -717,14 +736,21 @@ pub fn as_tgate(tn: []const u32, tp: []const u32, body: bool) -> Tg { } ; --- the blocks: every row measured on our model, or NOT MODELLED -------------------------------------------------------------- -/// inverter: the switching threshold from the PSP103 log tables (PASS or FAIL by the number); the delays into 20 fF are not modelled. -pub fn as_blk_inverter() -> Blk { +/// inverter: the two rows of a measured threshold vth (as_inv_vth_of) and measured delays d (as_inv_delay). The threshold is a PASS or FAIL by the number. The delays are produced (M5e), so the +/// row is not NOT MODELLED, and it is a PASS only inside the report's limit (1 to 500 ps, both) AND within the bar of ngspice's converged run of the same cell (10 percent, each delay): a value that +/// disagrees with ngspice is the model's FAIL, on the terms of the comparator's rows (M4b-q). +pub fn as_blk_inv(vth: f64, d: InvDly) -> Blk { var b : Blk = as_blk0(); - b = as_add(b, as_verdict(true, as_chk_inv_vth(as_inv_vth_of(&TT_LG_N, &TT_LG_P)))); - b = as_add(b, as_nm()); + b = as_add(b, as_verdict(true, as_chk_inv_vth(vth))); + b = as_add(b, as_verdict(true, as_chk_inv_delay(d.fall_ps, d.rise_ps) and d.g_fall < GT_B_REL and d.g_rise < GT_B_REL)); return b; } +/// The inverter block of the report: the cell of the golden netlist on the tt 27 C PSP103 log tables, its threshold and its delays into 20 fF. +pub fn as_blk_inverter() -> Blk { + return as_blk_inv(as_inv_vth_of(&TT_LG_N, &TT_LG_P), as_inv_delay(0)); +} + /// nand: the truth table of the gate-level model, and the levels of the DC solve on the PSP103 log tables (mos_psp103_gates.t27), a row only when every level is within 10 uV of /// ngspice's own run of the same cell. pub fn as_blk_nand() -> Blk { @@ -910,7 +936,7 @@ test an_unmeasured_row_is_never_a_pass_and_a_block_is_a_pass_only_with_every_row assert(AS_PASS != AS_NM); } -test the_inverter_threshold_is_measured_on_the_psp103_log_tables_and_the_delays_into_20_ff_are_not_modelled { +test the_inverter_threshold_and_its_delays_into_20_ff_are_measured_on_the_psp103_log_tables_and_the_delays_are_within_1_percent_of_ngspice { const lg : f64 = as_inv_vth_of(&TT_LG_N, &TT_LG_P); const li : f64 = as_inv_vth_of(&TT_N, &TT_P); // 0.68924 V on the log tables and 0.68925 V on the linear ones (ngspice's trip point is 0.689 V, mos_psp103.t27); the report's 0.69 V @@ -944,12 +970,49 @@ test the_inverter_threshold_is_measured_on_the_psp103_log_tables_and_the_delays_ var slow : f64 = 1340.0; assert(!as_chk_inv_delay(fast, R_INV_RISE_PS)); assert(!as_chk_inv_delay(R_INV_FALL_PS, slow)); - // the block: one row PASS, one NOT MODELLED, so the block is NOT MODELLED + // THE DELAYS INTO 20 fF (M5e), measured on the model of mos_psp103_inv20.t27: fall 75.755 ps and rise 93.246 ps, inside the report's 1 to 500 ps, and within +0.51 and +0.50 percent of + // ngspice-47's converged run of the same cell (75.368 and 92.778 ps), a twentieth of the 10 percent bar; ours are 6.5 and 6.8 percent under the report's own 81 and 100 ps (its stimulus is + // its own, the row is judged against the cell run here) + const d : InvDly = as_inv_delay(0); + assert fabs64(d.fall_ps - 75.755) < 0.002; + assert fabs64(d.rise_ps - 93.246) < 0.002; + assert d.g_fall > 0.0051; + assert d.g_fall < 0.0052; + assert d.g_rise > 0.0050; + assert d.g_rise < 0.0051; + assert(as_chk_inv_delay(d.fall_ps, d.rise_ps)); + var bar : f64 = GT_B_REL; + assert d.g_fall < bar; + assert d.g_rise < bar; + // the block: both rows measured and PASS, so the block is a PASS const b : Blk = as_blk_inverter(); - assert_eq(b.pass, 1); + assert_eq(b.pass, 2); assert_eq(b.fail, 0); - assert_eq(b.nm, 1); - assert_eq(as_block_verdict(b), AS_NM); + assert_eq(b.nm, 0); + assert_eq(as_block_verdict(b), AS_PASS); + // NEGATIVE CONTROL (FR-004), the charge removed from the same model: 39.0 and 51.9 ps, INSIDE the report's 1 to 500 ps (its check alone accepts them) and 48 and 44 percent under ngspice's, so + // the bar refuses them; the delay row is the model's FAIL, the threshold row is untouched, and the block is a FAIL, not a PASS and not an unmeasured row + const nc : InvDly = as_inv_delay(IV_M_NOCHARGE); + assert(as_chk_inv_delay(nc.fall_ps, nc.rise_ps)); + assert nc.g_fall > 0.48; + assert nc.g_rise > 0.44; + assert nc.g_fall > 4.0 * bar; + assert nc.g_rise > 4.0 * bar; + const bn : Blk = as_blk_inv(as_inv_vth_of(&TT_LG_N, &TT_LG_P), nc); + assert_eq(bn.pass, 1); + assert_eq(bn.fail, 1); + assert_eq(bn.nm, 0); + assert_eq(as_block_verdict(bn), AS_FAIL); + // the output held to the tables' 0 to 1.5 V range, as the M3c charge step holds it (the kick is lost): 46.8 and 63.1 ps, 38 and 32 percent under ngspice's: the same FAIL + const rg : InvDly = as_inv_delay(IV_M_RANGE); + assert(as_chk_inv_delay(rg.fall_ps, rg.rise_ps)); + assert rg.g_fall > 0.37; + assert rg.g_rise > 0.31; + const br : Blk = as_blk_inv(as_inv_vth_of(&TT_LG_N, &TT_LG_P), rg); + assert_eq(as_block_verdict(br), AS_FAIL); + // and the report's check does not see either break: it is the cross-check that does. A delay that passes the limit and disagrees with ngspice is a FAIL, and an agreeing value outside the limit is too + const outside : InvDly = InvDly { fall_ps: 0.5, rise_ps: d.rise_ps, g_fall: 0.0, g_rise: d.g_rise }; + assert_eq(as_block_verdict(as_blk_inv(as_inv_vth_of(&TT_LG_N, &TT_LG_P), outside)), AS_FAIL); } test the_nand_and_nor_truth_tables_are_measured_at_logic_level_and_their_levels_on_the_psp103_log_tables_are_within_3_uV_of_ngspice { @@ -1522,12 +1585,20 @@ test the_corners_smoke_suite_is_not_modelled_and_its_checks_refuse_a_delay_out_o assert(as_chk_corner_cmp(c.dt_ps)); const z : Cmp = as_cmp(SQ_M_NOCHARGE); assert(!as_chk_corner_cmp(z.dt_ps)); + // the same for the inverter (M5e): the tt 27 C delays, 75.8 and 93.2 ps, are inside the corners' 62 to 124 ps, which says nothing about the other 14 points; the charge-removed control's 39.0 and 51.9 + // ps are under it, so this check, unlike the block's 1 to 500 ps, sees that break by itself + const di : InvDly = as_inv_delay(0); + assert(as_chk_corner_inv(di.fall_ps)); + assert(as_chk_corner_inv(di.rise_ps)); + const dz : InvDly = as_inv_delay(IV_M_NOCHARGE); + assert(!as_chk_corner_inv(dz.fall_ps)); + assert(!as_chk_corner_inv(dz.rise_ps)); assert_eq(as_nm(), AS_NM); } -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 { +test the_thirteen_blocks_count_8_pass_0_fail_and_5_not_modelled_and_their_30_rows_24_pass_0_fail_and_6_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_inverter()), AS_PASS); 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); @@ -1542,16 +1613,16 @@ test the_thirteen_blocks_count_7_pass_0_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, 7); + assert_eq(t.pass, 8); assert_eq(t.fail, 0); - assert_eq(t.nm, 6); + assert_eq(t.nm, 5); 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 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) + // and over the rows of those blocks (the corners line is not a block and has no row here): 30 rows, of which 24 PASS, 0 FAIL and 6 NOT MODELLED (before M5e's inverter delays, which were NOT + // MODELLED, 23, 0 and 7 rows, 7, 0 and 6 blocks; 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, 23); + assert_eq(r.pass, 24); assert_eq(r.fail, 0); - assert_eq(r.nm, 7); + assert_eq(r.nm, 6); assert_eq(r.pass + r.fail + r.nm, AS_N_ROWS); } diff --git a/specs/analog/decks/psp103_inv20_fine.cir b/specs/analog/decks/psp103_inv20_fine.cir new file mode 100644 index 0000000000..0252107f06 --- /dev/null +++ b/specs/analog/decks/psp103_inv20_fine.cir @@ -0,0 +1,66 @@ +IHP SG13G2 inverter into 20 fF, converged reference (trapezoidal rule, steps of at most 0.05 ps), corner mos_tt, 27 C +* specs/analog/decks/psp103_inv20_fine.cir -- reference data for specs/analog/mos_psp103_inv20.t27 and its use in specs/analog/accept_sar.t27 (analog M5e, #11350, epic #10486). +* The inverter of the golden SAR ADC netlist: a pmos 3.0 um / 0.45 um with its bulk on the supply, an nmos 1.0 um / 0.45 um with its bulk on ground, ng 1, m 1 (facts of the circuit, +* the W, L and m of psp103_inv_be_500f.cir; none of the golden deck's text), loaded by 20 fF on the output and nothing else: the load of the report's row "delays fall / rise into 20 fF". +* Model: IHP SG13G2 PSP103 of IHP-Open-PDK 21133f21, section mos_tt, through ngspice-47 and OSDI. Paths are NOT in this file: ngspice reads IHP_PDK and IHP_OSDI from the environment. +* Run: IHP_PDK= IHP_OSDI= ngspice -b psp103_inv20_fine.cir (ngspice-47) +* The input is an IDEAL step (a B source of u(time - t0), no slew, so that the table model's gate edge, one jump of the node's charge, is the same edge): the gate is at the supply until 1 ns, +* falls to 0 V at 1 ns (the output rises) and returns to the supply at 2 ns (the output falls). Each level is held for 1 ns, 20 delays, so the output has settled to well under a microvolt. The time +* step is held to 0.05 ps (1/80 of the 4 ps grid of the table model's coarsest run), the settings of psp103_inv_fine_500f.cir otherwise. +* What it prints (ngspice .meas, 10 digits): tplh and tphl, the times at which the output crosses 0.75 V rising after the edge at 1 ns and falling after the edge at 2 ns (the input crosses 0.75 V AT the +* edge, so the delays are these minus 1 ns and minus 2 ns); the rest levels at 0.9, 1.9 and 2.9 ns; the lowest the output goes after the first edge (the kick below ground) and the highest after the +* second (the kick above the supply) with the times they happen; and the output at fixed times after each edge, between "SAMPLES " lines, as the table model's waveform is compared. +* The two blocks "COARSE 0.2p" and "COARSE 0.01p" run the same circuit again with the step held to 0.2 ps and to 0.01 ps and print the two crossings, so that the reference's own sensitivity to +* its time step is a number of this deck (the delays of the three runs are tplh, tphl; tplh2, tphl2; tplh3, tphl3). +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +VDD vdd 0 1.5 +Bin in 0 V = 1.5*(1-u(time-1n)+u(time-2n)) +XIP out in vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN out in 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +CL out 0 20f +.temp 27 +.options reltol=1e-6 vntol=1e-9 abstol=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 +tran 0.1p 3n 0 0.05p +echo EDGES +meas tran tplh when v(out)=0.75 rise=1 +meas tran tphl when v(out)=0.75 fall=1 +print tplh tphl +echo RESTS +meas tran rest0 find v(out) at=0.9n +meas tran rest1 find v(out) at=1.9n +meas tran rest2 find v(out) at=2.9n +echo KICKS +meas tran vlow min v(out) from=1n to=1.2n +meas tran tlow min_at v(out) from=1n to=1.2n +meas tran vhigh max v(out) from=2n to=2.2n +meas tran thigh max_at v(out) from=2n to=2.2n +echo SAMPLES 1 +foreach dtn 0.5p 1p 2p 3p 4p 5p 7.5p 10p 15p 20p 25p 30p 40p 50p 60p 80p 100p 150p 200p 400p + let tt = 1n + $dtn + echo T $dtn + meas tran vs find v(out) at=$&tt +end +echo SAMPLES 2 +foreach dtn 0.5p 1p 2p 3p 4p 5p 7.5p 10p 15p 20p 25p 30p 40p 50p 60p 80p 100p 150p 200p 400p + let tt = 2n + $dtn + echo T $dtn + meas tran vs find v(out) at=$&tt +end +echo COARSE 0.2p +tran 0.1p 3n 0 0.2p +meas tran tplh2 when v(out)=0.75 rise=1 +meas tran tphl2 when v(out)=0.75 fall=1 +print tplh2 tphl2 +echo COARSE 0.01p +tran 0.1p 3n 0 0.01p +meas tran tplh3 when v(out)=0.75 rise=1 +meas tran tphl3 when v(out)=0.75 fall=1 +print tplh3 tphl3 +.endc +.end diff --git a/specs/analog/mos_psp103_inv20.t27 b/specs/analog/mos_psp103_inv20.t27 new file mode 100644 index 0000000000..4590afc9cd --- /dev/null +++ b/specs/analog/mos_psp103_inv20.t27 @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/mos_psp103_inv20.t27 -- the SAR ADC's inverter into 20 fF on the IHP SG13G2 PSP103 tables with the devices' own charge, judged by ngspice +; M5e of the analog emulation epic (#10486, issue #11350). specs/analog/accept_sar.t27 applies the external team's per-block checks to our models; its inverter block had one row it could not +; produce, "delays fall / rise into 20 fF" (the report's 81 and 100 ps, limit 1 to 500 ps). The 500 fF and 10 pF inverters of mos_psp103.t27 (M3b, M3c) are validated to 0.05 percent against +; ngspice, and said in their header that 20 fF is out of reach: the gate edge kicks the output node 5 times further than at 100 fF, past the 0 to 1.5 V range of the tables. This file is that +; run. The cell, the tables, the charge and the equation are the ones that are already there; what is new is the output node's freedom to leave 0 .. Vdd and the delay measure. +; - +; WHAT WAS ASKED AND WHAT CAME OUT. The issue asks for the two 50 percent delays of the SAR netlist's inverter into 20 fF on the tt tables with the devices' charge, the time step fine enough that +; halving it moves each delay by under 1 percent, a comparison with ngspice-47's own run of the same cell (a row PASS only inside the report's limit AND within 10 percent of ngspice), and a +; negative control with the charge removed. All four are here (ideal gate step; times in ps after the edge): +; fall (output falls) rise (output rises) +; this model, 1 ps 75.755 93.246 +; ngspice-47, converged 75.368 92.778 psp103_inv20_fine.cir (step 0.05 ps; 0.2 ps and 0.01 ps give the same two crossings to 1 fs) +; gap +0.513 % +0.504 % the bar is 10 % +; the report 81 100 its own stimulus; ours is 6.5 and 6.8 % under it: context, not the reference +; and halving the step: 4, 2, 1, 0.5, 0.25 ps give fall 75.971, 75.826, 75.755, 75.720, 75.703 and rise 93.485, 93.324, 93.246, 93.210, 93.191: each halving from 4 ps moves the delays (fall / rise) by +; 0.19 / 0.17 %, 0.094 / 0.084 %, 0.046 / 0.039 % (1 ps against 0.5 ps, the issue's 1 percent), 0.022 / 0.020 %; backward Euler is slow by its first-order error and the delay falls with the step. +; THE CELL. The inverter of the golden SAR ADC netlist: a PMOS of W 3.0 um, L 0.45 um, ng 1, m 1 with its bulk on the supply and an NMOS of W 1.0 um, L 0.45 um, ng 1, m 1 with its bulk on +; ground, supply 1.5 V, 20 fF on the output and nothing else. Where the repository states those sizes: the header of mos_psp103.t27 (THE CIRCUIT: W, L and Vdd, "facts of the circuit") and the deck +; psp103_inv_be_500f.cir (W, L, ng and m of both devices); the tables of mos_psp103_data.t27 are of exactly these two devices, so no table is new. 20 fF is the load of the report's row and of +; accept_sar.t27. The tables hold each device with its bulk on its source: here both sources are on their own bulks (the NMOS source on ground and bulk on ground, the PMOS source on the supply and +; bulk on the supply), so the source-to-bulk axis of M3e is read at Vsb = 0, which is the 2-D log read bit for bit (a test), and an end of a channel that goes past a rail is a forward junction, read +; at 0 as sar_comparator_q.t27 reads it. +; - +; THE MODEL. One node, the output, in words of 2^-24 V (voltages may be negative: f64). The equation is the backward Euler of M3c (newton_iter_q of mos_table.t27): the node holds C y + Qd_n - Qd_p, +; so a step from y0 with the gate going from a0 to a is +; (y - y0) + KC (Q(y, a) - Q(y0, a0)) + KI I(y, a) / 256 = 0, KC = 500 fF / C = 25, KI = KC dt / 100 ps, +; a gate edge being a jump of charge (the Miller kick), not a current. The tables are M3c's: a word of a charge table is Q / 500 fF and a word of a current table is the current that moves a 500 +; fF node in 100 ps, so no table is rescaled; C and dt enter as KC and KI (the integer step could not do it: a charge table scaled by 25 overflows the u64 bound of the bilinear read when scaled +; plainly and goes below 0 when scaled around its 2^21 origin, and `newton_iter_q` holds y to 0 .. Vdd in any case). What is reused, not copied: the current is `sq_i` of sar_comparator_q.t27 (the log tables on the source-to-bulk axis, the integer +; reader), the charge is its `sq_qt` (the tables read by M3a's bilinear read and extended linearly past their edges with the slope of the edge cell, so the charge and its slope are one function), the +; source terminal's charge its `sq_qsrc` (the two-edge closure of the tables' source terminal, Qs = Qd at Vds = 0 and Qs = 0 at a gate drive of 0, exactly), the tables `sq_qd_tab` and `sq_qg_tab` +; of its devices; the f64 here is the assembly of one node from them and the bisection that solves it. +; the new part the output leaves 0 .. Vdd: an ideal gate edge kicks it 0.40 V below ground when the gate falls and 0.50 V over the supply when it rises (ngspice 0.38 V and 0.46 V). Below ground the two +; ends of the NMOS channel exchange their roles (the drain is on ground and the output is the source: the gate drive is the gate over the output, Vds is minus the output, and the +; terminal on the output is the source terminal, closed by `sq_qsrc`); over the supply the PMOS's do. The charge and the current are continuous across the exchange (a test: 4 words and +; 2 IUNIT over 2 words of output). The current beyond the tables' edge is held at the edge (`lim`, the reader's), the charge goes on linearly. +; the solve the root of the residual by bisection over -1.25 V to 3 V, 64 halvings (the residual rises with the output: a test), so the answer is the converged step, not K Newton iterations +; of the board's arithmetic; the DC rest level of each edge is the root of the current leaving the node. The gate is an ideal step at the start of step 0, the delay the crossing of +; 0.75 V by linear interpolation of the samples from the edge at index 0. +; - +; THE REFERENCE. specs/analog/decks/psp103_inv20_fine.cir: the same two devices (mos_tt of IHP-Open-PDK 21133f21 through ngspice-47 and OSDI, 27 C) and 20 fF, a B source stepping the gate as +; u(time - t0) (no slew), the gate at the supply until 1 ns, 0 V until 2 ns, the supply from 2 ns, each level held 20 delays; trapezoidal rule, the step held to 0.05 ps. The reference's own +; sensitivity to its step is a number of the deck: 0.2 ps and 0.01 ps give the same 50 percent crossings to 1 fs (1.092778 ns and 2.075368 or 2.075369 ns). It prints the crossings, the rest levels, +; the kick (-0.384768 V at 0.56 ps and 1.96343 V at 0.49 ps after the edges) and 20 samples after each edge. +; THE WAVEFORM, the model at 0.5 ps against ngspice's 20 samples per edge: the rise is within 11.3 mV of ngspice's (0.75 percent of the supply) at the worst sample, the first, 0.5 ps after the edge, +; where the model's kick is 0.3958 V against ngspice's 0.3848 V (2.9 percent larger), and within 5.4 mV from 30 ps on; the fall is within 35.0 mV (2.3 percent) at the first sample, where the model's +; kick over the supply is 0.4984 V against 0.4634 V (7.5 percent larger), and within 5.8 mV from 30 ps on. The model runs 4 to 5 mV under ngspice's rising output and 4.5 to 6 mV over its falling +; one through the 50 percent crossing (5 mV at a slew of 12 to 16 V/ns is 0.4 ps, the half percent of the delays). The model's kick is larger by 2.9 and 7.5 percent; the cause is not isolated (the +; tables' gate-to-drain capacitance at a gate step of 1.5 V, their extension past the edge and the closure of the source terminal are the candidates). +; - +; THE ERRORS, kept apart. The scheme: backward Euler at 1 ps is 0.09 (fall) and 0.08 (rise) percent over its own limit in time (each halving in the table above takes half of the last: the limit is about +; 75.686 and 93.172 ps), which is a sixth of the +0.51 and +0.50 percent against ngspice: that gap is the tables' and the charge's. The extension: the same code at 500 fF and 100 ps (KC = 1, KI = 1, no +; table scaled) follows ngspice's backward Euler on the same grid to -0.0022 (rise) and -0.026 (fall) percent (psp103_inv_be_500f.cir; mos_psp103.t27's own log-table run is within 0.0018 and 0.025 +; percent), where the kick is 6 mV and the range binds little: the new step is the validated one where the old one holds. +; THE NEGATIVE CONTROLS, tests that pass by seeing the failure (the bar is 10 percent of ngspice's delay): +; charge removed the devices store nothing, the node is the 20 fF alone: fall 39.043 ps, rise 51.868 ps, 48.2 and 44.1 percent under ngspice's. No kick, no capacitance but the load. +; Both are INSIDE the report's 1 to 500 ps, so the report's check alone accepts them and the cross-check is what refuses them (FR-004). +; output held to 0 .. Vdd the model with the bisection's bracket cut to the tables' range, as `newton_iter_q` holds the output: the kick is lost, fall 46.793 ps and rise 63.122 ps, 37.9 and 32.0 +; percent under. The range is the first thing the issue says to extend, and it is why the M3c step cannot be reused as it is. +; a load of 25 fF the same model with 5 fF more: fall 85.544 ps and rise 106.242 ps, +13.5 and +14.5 percent over ngspice's: the bar sees a load error of a quarter; 22 fF gives +5.7 and +6.1 +; percent, inside it. +; - +; WHAT A PASS SAYS. The inverter of the golden netlist, on the tt 27 C PSP103 tables of the repository with the devices' own drain and source charge, an ideal gate step and an output free to go +; 0.4 to 0.5 V outside the rails, has a 50 percent delay of 75.755 ps falling and 93.246 ps rising into 20 fF at a step of 1 ps (within 0.09 percent of its limit in time), within 0.52 percent of +; ngspice-47's converged run of the same cell, inside the report's 1 to 500 ps; the charge removed, the range held to the tables' and a load of 25 fF are each refused by the 10 percent bar. +; WHAT IT DOES NOT SAY. Another corner or temperature (tt, 27 C: the tables have no -40 or 125 C and the report's smoke suite is five corners at three temperatures; accept_sar.t27 keeps that line NOT +; MODELLED), another load or another input slew (the gate edge is ideal; the report's own stimulus is not in the material this file was made from, and our 81-and-100-ps counterparts are 6.5 and 6.8 +; percent under the report's), the waveform inside the first 30 ps (11 and 35 mV off at the first sample), the current of a device past the tables' edge (held at the edge: the PMOS at Vsd up to +; 1.9 V and the NMOS at Vds up to 2.0 V are read at 1.5 V), the junction conduction of the kicked node (read at 0), the NQS and gate-resistance effects of PSP103, mismatch, noise, power, the output +; level at rest (30 nV here against ngspice's 0.64 uV, its gmin), hardware. This file is f64 with integer table reads and a bisection: it is the test-side model of the cell, not the board's arithmetic, +; and no board top, clock, LUT, DSP, BRAM, Fmax or UART line is claimed. The golden deck is not in this repository (its inverter is written out in the decks). ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogMosPsp103Inv20; + +use analog::linear::{fabs64}; +use analog::mos_table::{lg_fine}; +use analog::mos_psp103::{HALF, cross_idx, rel_gap, steps_of_fs, INV500_BE_TPLH_FS, INV500_BE_TPHL_FS, EDGE1_500F_FS, EDGE2_500F_FS, DT_500F_FS}; +use analog::mos_psp103_data::{TT_LG_N, TT_LG_P}; +use analog::mos_psp103_gates::{GT_B_REL}; +use analog::sar_comparator_q::{sq_i, sq_qt, sq_qsrc, sq_qd_tab, sq_qg_tab, sq_wf}; + +pub const KIND : str = "analog-cell"; +pub const ID : str = "analog/mos_psp103_inv20"; +pub const REFERENCE : str = "IHP-Open-PDK 21133f21 sg13_lv_nmos/pmos (PSP103) through ngspice-47, the inverter of the golden SAR ADC netlist into 20 fF (specs/analog/decks/psp103_inv20_fine.cir)"; + +; --- the circuit and the run ------------------------------------------------------------------------------------------ +/// The load: 20 fF, in attofarad. The tables hold Q / 500 fF and a current that moves a 500 fF node in 100 ps, so the node equation below is scaled by KC = 500 fF / C and KI = KC dt / 100 ps. +pub const IV_C_AF : u32 = 20000; +/// The step of the verdict: 1 ps (a delay is 75 to 93 steps); the window of a run, 200 ps (past the 50 percent crossing of every mode of the controls). +pub const IV_DT_FS : u32 = 1000; +pub const IV_WINDOW_FS : u32 = 200000; +/// The supply in words of 2^-24 V, and the span the bisection searches: 1.25 V below ground to 3 V (the output goes 0.38 V below ground and 0.46 V above the supply in ngspice, 0.40 V and 0.50 V in the model). +pub const IV_VDD : f64 = 25165824.0; +pub const IV_LO : f64 = 0.0 - 20971520.0; +pub const IV_HI : f64 = 50331648.0; +pub const IV_ITERS : u32 = 64; +/// The modes of a run: the model, and the two ways of breaking it that the negative controls use. +pub const IV_M_NOCHARGE : u32 = 1; +pub const IV_M_RANGE : u32 = 2; + +; A voltage in words (2^-24 V) is read into an unsigned word, rounded half up, inline: `(v + 0.5) as u32`. Every argument below is non-negative by the branch it is in (a gate is 0 or the +; supply; the output is on the side of a rail that the branch tests; a difference is taken in the direction that the branch tests), and a one-line function for it would be the body of +; `round_to_u32` of specs/igla/coder/arch.t27, which `t27c dupes --bodies` counts as a copy. + +/// The source terminal's charge of a device of kind (0 NMOS, 1 PMOS) at the gate drive x (xn below the source) and Vds y, in table words: the two-edge closure of sar_comparator_q.t27 (its +/// `sq_qsrc`), read from the same charge tables, extended past the tables' edges as `sq_qt` extends them. +pub fn iv_qs(kind: u32, x: u32, xn: u32, y: u32) -> f64 { + const pm : bool = kind == 1; + const td : []const u32 = sq_qd_tab(kind); + const tg : []const u32 = sq_qg_tab(kind); + const qd : u64 = sq_qt(td, x, xn, y, pm); + const qg : u64 = sq_qt(tg, x, xn, y, pm); + return sq_wf(sq_qsrc(qd, qg, sq_qt(td, x, xn, 0, pm), sq_qt(tg, x, xn, 0, pm), sq_qt(td, 0, 0, y, pm), sq_qt(tg, 0, 0, y, pm))); +} + +/// What one device puts on the output node: chg_w, its terminal charge there (table words, Q / 500 fF in 2^-24 V), and cur_iu, the current it takes out of the node (IUNIT = 1.164 pA; negative when +/// it puts current in). The field names are long and unlike any local of an imported spec on purpose: t27c's Zig backend gives an imported function's local the type of a struct field of the same +/// name (a field `i` of type f64 made the `i` of `cross_idx` a float, a field `cur` the `cur` of `sc_apply_lim`: the defect of section 7 of docs/analog/accept-sar-2026-10-20.md), and the spec did not compile. +pub struct IvDev { chg_w: f64, cur_iu: f64 } + +/// The NMOS (drain on the output y, source and bulk on ground, gate at a; all in words). Below ground the two ends of the channel exchange their roles: the output is then the source, the gate +/// drive is the gate over the output, the source-to-bulk voltage is 0 (the junction is forward, as sq_dev reads it) and the terminal on the output is the source terminal. +pub fn iv_nmos(y: f64, a: f64, mode: u32) -> IvDev { + if (y >= 0.0) { + const x : u32 = (a + 0.5) as u32; + const e : u32 = (y + 0.5) as u32; + var q : f64 = 0.0; + if ((mode & IV_M_NOCHARGE) == 0) { q = sq_wf(sq_qt(sq_qd_tab(0), x, 0, e, false)); } + return IvDev { chg_w: q, cur_iu: (sq_i(0, x, e, 0)) as f64 }; + } + const d : f64 = 0.0 - y; + const x2 : u32 = (a + d + 0.5) as u32; + const e2 : u32 = (d + 0.5) as u32; + var q2 : f64 = 0.0; + if ((mode & IV_M_NOCHARGE) == 0) { q2 = iv_qs(0, x2, 0, e2); } + return IvDev { chg_w: q2, cur_iu: 0.0 - (sq_i(0, x2, e2, 0)) as f64 }; +} + +/// The PMOS (drain on the output y, source and bulk on the supply, gate at a). Above the supply the ends exchange their roles and the terminal on the output is the source terminal. +pub fn iv_pmos(y: f64, a: f64, mode: u32) -> IvDev { + if (y < IV_VDD) { + var x : u32 = 0; + var xn : u32 = 0; + if (IV_VDD > a) { x = (IV_VDD - a + 0.5) as u32; } else { xn = (a - IV_VDD + 0.5) as u32; } + const e : u32 = (IV_VDD - y + 0.5) as u32; + var q : f64 = 0.0; + if ((mode & IV_M_NOCHARGE) == 0) { q = sq_wf(sq_qt(sq_qd_tab(1), x, xn, e, true)); } + return IvDev { chg_w: q, cur_iu: 0.0 - (sq_i(1, x, e, 0)) as f64 }; + } + var x2 : u32 = 0; + var xn2 : u32 = 0; + if (y > a) { x2 = (y - a + 0.5) as u32; } else { xn2 = (a - y + 0.5) as u32; } + const e2 : u32 = (y - IV_VDD + 0.5) as u32; + var q2 : f64 = 0.0; + if ((mode & IV_M_NOCHARGE) == 0) { q2 = iv_qs(1, x2, xn2, e2); } + return IvDev { chg_w: q2, cur_iu: (sq_i(1, x2, e2, 0)) as f64 }; +} + +/// Both devices on the output node: the charge (table words) and the current leaving the node (IUNIT). +pub fn iv_node(y: f64, a: f64, mode: u32) -> IvDev { + const n : IvDev = iv_nmos(y, a, mode); + const p : IvDev = iv_pmos(y, a, mode); + return IvDev { chg_w: n.chg_w + p.chg_w, cur_iu: n.cur_iu + p.cur_iu }; +} + +/// The DC level of the output with the gate at a: the root of the current leaving the node, by bisection (the current leaving rises with y). +pub fn iv_rest(a: f64) -> f64 { + var lo : f64 = IV_LO; + var hi : f64 = IV_HI; + var k : u32 = 0; + while (k < IV_ITERS) { + const mid : f64 = 0.5 * (lo + hi); + const n : IvDev = iv_node(mid, a, 0); + if (n.cur_iu > 0.0) { hi = mid; } else { lo = mid; } + k = k + 1; + } + return 0.5 * (lo + hi); +} + +/// The residual of one backward Euler step from y0 (whose node charge, with the gate it had, is q0) to y with the gate at a, in words of the node's own voltage: +/// g(y) = (y - y0) + KC (Q(y, a) - q0) + KI I(y, a) / 256, KC = 500 fF / C, KI = KC dt / 100 ps. +/// It is the equation of `newton_iter_q` in mos_table.t27 (the node holds C y + Qd_n - Qd_p; a gate edge is a jump of charge, the Miller kick, not a current), with the node free to leave 0 .. Vdd. +pub fn iv_g(y: f64, y0: f64, a: f64, q0: f64, kc: f64, ki: f64, mode: u32) -> f64 { + const n : IvDev = iv_node(y, a, mode); + return (y - y0) + kc * (n.chg_w - q0) + ki * n.cur_iu / 256.0; +} + +/// One backward Euler step: the root of g, by bisection (g rises with y: a test). With IV_M_RANGE the root is searched in 0 .. Vdd only, the range of the tables and of `newton_iter_q`. +pub fn iv_step(y0: f64, a: f64, q0: f64, kc: f64, ki: f64, mode: u32) -> f64 { + var lo : f64 = IV_LO; + var hi : f64 = IV_HI; + if ((mode & IV_M_RANGE) != 0) { + lo = 0.0; + hi = IV_VDD; + } + var k : u32 = 0; + while (k < IV_ITERS) { + const mid : f64 = 0.5 * (lo + hi); + if (iv_g(mid, y0, a, q0, kc, ki, mode) > 0.0) { hi = mid; } else { lo = mid; } + k = k + 1; + } + return 0.5 * (lo + hi); +} + +/// The run of one edge into c_af attofarad with a step of dt_fs femtoseconds. edge 0: the gate falls from the supply to 0 (the output rises); edge 1: the gate rises (the output falls). The +/// gate is an ideal step at the start of step 0. out[0] is the DC rest level before the edge, out[k + 1] the output at the end of step k, for n steps. +pub fn iv_run(c_af: u32, dt_fs: u32, edge: u32, n: u32, mode: u32, out: []f64) { + var a0 : f64 = IV_VDD; + var a : f64 = 0.0; + if (edge == 1) { + a0 = 0.0; + a = IV_VDD; + } + const kc : f64 = 500000.0 / (c_af as f64); + const ki : f64 = kc * (dt_fs as f64) / 100000.0; + var y : f64 = iv_rest(a0); + out[0] = y; + const r : IvDev = iv_node(y, a0, mode); + var qprev : f64 = r.chg_w; + var k : u32 = 0; + while (k < n) { + const y1 : f64 = iv_step(y, a, qprev, kc, ki, mode); + out[(k + 1) as usize] = y1; + const qn : IvDev = iv_node(y1, a, mode); + qprev = qn.chg_w; + y = y1; + k = k + 1; + } +} + +/// The 50 percent delay of a run of one edge in picoseconds: the crossing of 0.75 V from the edge at index 0, linear between samples; -1 when the output does not cross. +pub fn iv_delay_ps(out: []const f64, edge: u32, n: u32, dt_fs: u32) -> f64 { + const c : f64 = cross_idx(out, HALF, edge == 0, 0, n); + if (c < 0.0) { return 0.0 - 1.0; } + return c * (dt_fs as f64) / 1000.0; +} + +/// The two delays of the inverter into 20 fF: rise_ps, the output rising after the gate falls (the report's "rise"), and fall_ps, the output falling after the gate rises ("fall"), at step dt_fs in mode. +pub struct IvDelays { rise_ps: f64, fall_ps: f64 } + +pub fn iv_delays(dt_fs: u32, mode: u32) -> IvDelays { + const n : u32 = IV_WINDOW_FS / dt_fs; + var out : [2048]f64 = [0.0] ** 2048; + iv_run(IV_C_AF, dt_fs, 0, n, mode, &out); + const up : f64 = iv_delay_ps(&out, 0, n, dt_fs); + iv_run(IV_C_AF, dt_fs, 1, n, mode, &out); + const dn : f64 = iv_delay_ps(&out, 1, n, dt_fs); + return IvDelays { rise_ps: up, fall_ps: dn }; +} + +; --- ngspice-47, PSP103 mos_tt 27 C, the inverter into 20 fF (specs/analog/decks/psp103_inv20_fine.cir) -------------------------------------------------------- +/// The 50 percent delays after the ideal gate steps at 1 ns (the output rises) and at 2 ns (the output falls), ps, from the run with the step held to 0.05 ps (the .meas crossings 1.092778 ns and +/// 2.075368 ns; the runs at 0.2 ps and 0.01 ps give the same two numbers to 1 fs: 1.092778 ns, 2.075369 ns). +pub const NG20_TPLH_PS : f64 = 92.778; +pub const NG20_TPHL_PS : f64 = 75.368; +/// The extremes of the kick (V) and the time after the edge it happens (ps): the output goes to 0.3848 V below ground 0.56 ps after the first edge and to 1.9634 V (0.4634 V over the supply) 0.49 ps after the second. +pub const NG20_KICK_LOW_V : f64 = 0.0 - 0.384768; +pub const NG20_KICK_HIGH_V : f64 = 1.96343; +/// The output (V) at fixed times after each edge, and the same times as indices of the 0.5 ps run: 0.5 1 2 3 4 5 7.5 10 15 20 25 30 40 50 60 80 100 150 200 400 ps. +pub const NG20_IDX : [20]u32 = [1, 2, 4, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 120, 160, 200, 300, 400, 800]; +pub const NG20_RISE_V : [20]f64 = [ + 0.0 - 0.384547, 0.0 - 0.380452, 0.0 - 0.367915, 0.0 - 0.355383, 0.0 - 0.342865, 0.0 - 0.330353, 0.0 - 0.299032, 0.0 - 0.267523, 0.0 - 0.203519, 0.0 - 0.138696, + 0.0 - 0.0738717, 0.0 - 0.00926922, 0.119042, 0.245808, 0.370431, 0.609224, 0.823863, 1.19920, 1.37848, 1.49742 +]; +pub const NG20_FALL_V : [20]f64 = [ + 1.96343, 1.95850, 1.94441, 1.93024, 1.91603, 1.90178, 1.86579, 1.82891, 1.74982, 1.66483, + 1.57904, 1.49347, 1.32345, 1.15560, 0.991122, 0.681769, 0.425803, 0.102081, 0.0219727, 0.0000437790 +]; + +/// The largest distance, in V, of a run of one edge at 0.5 ps (800 steps) from ngspice's samples ng (the sample j at index NG20_IDX[j]), from sample `first` on (0 is all 20; 11 is from 30 ps). +pub fn iv_wave_gap(edge: u32, mode: u32, ng: []const f64, first: usize) -> f64 { + var out : [2048]f64 = [0.0] ** 2048; + iv_run(IV_C_AF, 500, edge, 800, mode, &out); + var worst : f64 = 0.0; + var j : usize = first; + while (j < 20) { + const d : f64 = fabs64(out[NG20_IDX[j] as usize] / 16777216.0 - ng[j]); + if (d > worst) { worst = d; } + j = j + 1; + } + return worst; +} + +/// The extreme of the kick of a run of one edge, in V (the lowest output of the rise, the highest of the fall), at step dt_fs, over the first 20 ps. +pub fn iv_kick(edge: u32, dt_fs: u32, mode: u32) -> f64 { + const n : u32 = 20000 / dt_fs; + var out : [2048]f64 = [0.0] ** 2048; + iv_run(IV_C_AF, dt_fs, edge, n, mode, &out); + var m : f64 = out[0]; + var k : u32 = 1; + while (k <= n) { + const v : f64 = out[k as usize]; + if (edge == 0 and v < m) { m = v; } + if (edge == 1 and v > m) { m = v; } + k = k + 1; + } + return m / 16777216.0; +} + +; --- 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_inverter_into_20_fF_has_its_50_percent_delays_within_1_percent_of_ngspice_converged { + const d : IvDelays = iv_delays(IV_DT_FS, 0); + var ng_up : f64 = NG20_TPLH_PS; + var ng_dn : f64 = NG20_TPHL_PS; + const g_up : f64 = rel_gap(d.rise_ps, ng_up); + const g_dn : f64 = rel_gap(d.fall_ps, ng_dn); + // rise 93.246 ps against ngspice's 92.778 ps (+0.504 %), fall 75.755 ps against 75.368 ps (+0.513 %): the same sign, a twentieth of the 10 percent bar + assert fabs64(d.rise_ps - 93.246) < 0.002; + assert fabs64(d.fall_ps - 75.755) < 0.002; + assert d.rise_ps > ng_up; + assert d.fall_ps > ng_dn; + assert g_up > 0.0050; + assert g_up < 0.0051; + assert g_dn > 0.0051; + assert g_dn < 0.0052; + var bar : f64 = GT_B_REL; + assert g_up < bar / 10.0; + assert g_dn < bar / 10.0; + // against the external team's report (81 ps fall, 100 ps rise, its own stimulus, context only): 6.5 and 6.8 percent under + var rep_fall : f64 = 81.0; + var rep_rise : f64 = 100.0; + assert d.fall_ps < rep_fall; + assert d.rise_ps < rep_rise; + assert rel_gap(d.fall_ps, rep_fall) < 0.066; + assert rel_gap(d.rise_ps, rep_rise) < 0.069; +} + +test halving_the_step_moves_each_delay_by_under_0_1_percent_and_the_run_is_converged_in_time { + const d4 : IvDelays = iv_delays(4000, 0); + const d2 : IvDelays = iv_delays(2000, 0); + const d1 : IvDelays = iv_delays(1000, 0); + const d05 : IvDelays = iv_delays(500, 0); + const d025 : IvDelays = iv_delays(250, 0); + var one_percent : f64 = 0.01; + // the step of the verdict, 1 ps, against half of it: 93.246 -> 93.210 ps and 75.755 -> 75.720 ps, 0.039 and 0.046 percent + assert rel_gap(d1.rise_ps, d05.rise_ps) < one_percent; + assert rel_gap(d1.fall_ps, d05.fall_ps) < one_percent; + assert rel_gap(d1.rise_ps, d05.rise_ps) < 0.0005; + assert rel_gap(d1.fall_ps, d05.fall_ps) < 0.0005; + // every halving from 4 ps down moves each delay by under 0.2 percent, and the delay falls with the step (backward Euler is slow by its first-order error) + assert rel_gap(d4.rise_ps, d2.rise_ps) < 0.002; + assert rel_gap(d4.fall_ps, d2.fall_ps) < 0.002; + assert rel_gap(d2.rise_ps, d1.rise_ps) < 0.001; + assert rel_gap(d2.fall_ps, d1.fall_ps) < 0.001; + assert rel_gap(d05.rise_ps, d025.rise_ps) < 0.0003; + assert rel_gap(d05.fall_ps, d025.fall_ps) < 0.0003; + assert d4.rise_ps > d2.rise_ps; + assert d2.rise_ps > d1.rise_ps; + assert d1.rise_ps > d05.rise_ps; + assert d05.rise_ps > d025.rise_ps; + assert d4.fall_ps > d2.fall_ps; + assert d2.fall_ps > d1.fall_ps; + assert d1.fall_ps > d05.fall_ps; + assert d05.fall_ps > d025.fall_ps; +} + +test the_waveform_and_the_kick_follow_ngspice_at_20_samples_after_each_edge { + // the run at 0.5 ps against ngspice's 20 samples per edge: the rise is within 11.3 mV (0.75 percent of the supply, at the first sample), the fall within 35.0 mV (2.3 percent, at the first + // sample too: the model's kick over the supply is 35 mV higher, 7.5 percent of the kick); from 30 ps on both are within 6 mV + const wr : f64 = iv_wave_gap(0, 0, &NG20_RISE_V, 0); + const wf : f64 = iv_wave_gap(1, 0, &NG20_FALL_V, 0); + assert wr > 0.0112; + assert wr < 0.0114; + assert wf > 0.0349; + assert wf < 0.0351; + const wr30 : f64 = iv_wave_gap(0, 0, &NG20_RISE_V, 11); + const wf30 : f64 = iv_wave_gap(1, 0, &NG20_FALL_V, 11); + assert wr30 > 0.0053; + assert wr30 < 0.0055; + assert wf30 > 0.0057; + assert wf30 < 0.0059; + // the kick: the output goes 0.3958 V below ground (ngspice 0.3848 V) and 1.9984 V, 0.4984 V over the supply (ngspice 1.9634 V): the model's is 2.9 and 7.5 percent larger + const kl : f64 = iv_kick(0, 500, 0); + const kh : f64 = iv_kick(1, 500, 0); + var ng_low : f64 = NG20_KICK_LOW_V; + var ng_high : f64 = NG20_KICK_HIGH_V; + assert kl < 0.0 - 0.395; + assert kl > 0.0 - 0.397; + assert kh > 1.998; + assert kh < 1.999; + assert kl < ng_low; + assert kh > ng_high; + assert fabs64(kl - ng_low) / fabs64(ng_low) < 0.03; + assert (kh - ng_high) / (ng_high - 1.5) < 0.08; + // the kick is real: both are over 0.38 V outside the 0 to 1.5 V range of the tables + assert kl < 0.0 - 0.38; + assert kh > 1.88; +} + +test the_charge_removed_and_the_range_of_the_tables_are_refused_by_the_10_percent_bar { + var bar : f64 = GT_B_REL; + var ng_up : f64 = NG20_TPLH_PS; + var ng_dn : f64 = NG20_TPHL_PS; + // NEGATIVE CONTROL 1, FR-004: the devices' charge removed (no Miller kick, no capacitance on the node but the 20 fF): 51.868 and 39.043 ps, 44.1 and 48.2 percent fast + const nc : IvDelays = iv_delays(IV_DT_FS, IV_M_NOCHARGE); + assert nc.rise_ps > 51.8; + assert nc.rise_ps < 51.9; + assert nc.fall_ps > 39.0; + assert nc.fall_ps < 39.1; + assert rel_gap(nc.rise_ps, ng_up) > 0.44; + assert rel_gap(nc.fall_ps, ng_dn) > 0.48; + assert rel_gap(nc.rise_ps, ng_up) > 4.0 * bar; + assert rel_gap(nc.fall_ps, ng_dn) > 4.0 * bar; + // NEGATIVE CONTROL 2: the output held to the tables' 0 to 1.5 V range, as `newton_iter_q` holds it (the kick is lost): 63.122 and 46.793 ps, 32.0 and 37.9 percent fast + const rg : IvDelays = iv_delays(IV_DT_FS, IV_M_RANGE); + assert rg.rise_ps > 63.1; + assert rg.rise_ps < 63.2; + assert rg.fall_ps > 46.7; + assert rg.fall_ps < 46.9; + assert rel_gap(rg.rise_ps, ng_up) > 0.31; + assert rel_gap(rg.fall_ps, ng_dn) > 0.37; + assert rel_gap(rg.rise_ps, ng_up) > 3.0 * bar; + assert rel_gap(rg.fall_ps, ng_dn) > 3.0 * bar; + // and the model itself, on the same two measures, is inside the bar + const d : IvDelays = iv_delays(IV_DT_FS, 0); + assert rel_gap(d.rise_ps, ng_up) < bar; + assert rel_gap(d.fall_ps, ng_dn) < bar; +} + +test a_load_of_25_fF_instead_of_20_is_refused_too_the_bar_sees_a_quarter_of_the_load { + // NEGATIVE CONTROL 3: the same model with the wrong load. The delay is not proportional to the load (the devices' own charge is 12 fF or so of it), but 25 fF in place of 20 moves + // both delays by over 10 percent (fall 85.544 ps and rise 106.242 ps, +13.5 and +14.5 percent over ngspice's), so the bar of the verdict sees a load error of that size + var out : [2048]f64 = [0.0] ** 2048; + iv_run(25000, IV_DT_FS, 0, 200, 0, &out); + const up : f64 = iv_delay_ps(&out, 0, 200, IV_DT_FS); + iv_run(25000, IV_DT_FS, 1, 200, 0, &out); + const dn : f64 = iv_delay_ps(&out, 1, 200, IV_DT_FS); + var ng_up : f64 = NG20_TPLH_PS; + var ng_dn : f64 = NG20_TPHL_PS; + var bar : f64 = GT_B_REL; + assert rel_gap(up, ng_up) > bar; + assert rel_gap(dn, ng_dn) > bar; + assert up > ng_up; + assert dn > ng_dn; + assert up > 106.2; + assert up < 106.3; + assert dn > 85.5; + assert dn < 85.6; +} + +test the_devices_are_read_at_a_source_to_bulk_voltage_of_0_because_each_source_is_on_its_own_bulk { + // The inverter has no body effect: the NMOS source and bulk are on ground and the PMOS source and bulk on the supply, and an end of the channel that goes past a rail (the output kicked) is a + // forward junction, read at 0 as `sq_dev` reads it. The read on the source-to-bulk axis (sq_i, the PR of M3e) at Vsb = 0 is the 2-D log read bit for bit, on both devices, at points over + // the whole range of the gate drive and Vds, including the corners + var k : u32 = 0; + var same : u32 = 0; + var total : u32 = 0; + while (k < 25) { + const x : u32 = (k * 1048576 + (k * 7919) % 1048576) % 25165825; + const y : u32 = ((24 - k) * 1048576 + (k * 104729) % 1048576) % 25165825; + if (sq_i(0, x, y, 0) == lg_fine(&TT_LG_N, x, y)) { same = same + 1; } + if (sq_i(1, x, y, 0) == lg_fine(&TT_LG_P, x, y)) { same = same + 1; } + total = total + 2; + k = k + 1; + } + assert_eq(same, total); + // and the axis is not nothing: at Vsb = 0.5 V the NMOS carries less current at the same drive (the threshold moves), so the equality above is a statement about Vsb = 0 + var x0 : u32 = 12582912; + var y0 : u32 = 12582912; + var vb : u32 = 8388608; + assert sq_i(0, x0, y0, vb) < sq_i(0, x0, y0, 0); +} + +test the_node_equation_rises_with_the_output_so_the_bisection_has_one_root { + // g(y) of one step is strictly increasing in y over 0.5 V below ground to 2.2 V, for the two edges and for a step from the middle: the premise of `iv_step` + var kc : f64 = 25.0; + var ki : f64 = 0.25; + var vdd : f64 = IV_VDD; + const rest_hi : f64 = iv_rest(vdd); + const rest_lo : f64 = iv_rest(0.0); + const q_hi : IvDev = iv_node(rest_hi, vdd, 0); + const q_lo : IvDev = iv_node(rest_lo, 0.0, 0); + const q_mid : IvDev = iv_node(12582912.0, vdd, 0); + var rising : u32 = 0; + var total : u32 = 0; + var j : u32 = 0; + while (j < 168) { + const y : f64 = (j as f64) * 262144.0 - 8388608.0; + const y2 : f64 = y + 262144.0; + if (iv_g(y2, rest_hi, 0.0, q_hi.chg_w, kc, ki, 0) > iv_g(y, rest_hi, 0.0, q_hi.chg_w, kc, ki, 0)) { rising = rising + 1; } + if (iv_g(y2, rest_lo, vdd, q_lo.chg_w, kc, ki, 0) > iv_g(y, rest_lo, vdd, q_lo.chg_w, kc, ki, 0)) { rising = rising + 1; } + if (iv_g(y2, 12582912.0, 0.0, q_mid.chg_w, kc, ki, 0) > iv_g(y, 12582912.0, 0.0, q_mid.chg_w, kc, ki, 0)) { rising = rising + 1; } + total = total + 3; + j = j + 1; + } + assert_eq(rising, total); + // and the root the step finds is a root: g changes sign across it within one word, for the first step of each edge + const y1 : f64 = iv_step(rest_hi, 0.0, q_hi.chg_w, kc, ki, 0); + assert iv_g(y1 + 1.0, rest_hi, 0.0, q_hi.chg_w, kc, ki, 0) >= 0.0; + assert iv_g(y1 - 1.0, rest_hi, 0.0, q_hi.chg_w, kc, ki, 0) <= 0.0; + const y2 : f64 = iv_step(rest_lo, vdd, q_lo.chg_w, kc, ki, 0); + assert iv_g(y2 + 1.0, rest_lo, vdd, q_lo.chg_w, kc, ki, 0) >= 0.0; + assert iv_g(y2 - 1.0, rest_lo, vdd, q_lo.chg_w, kc, ki, 0) <= 0.0; + // the first step of the rise ends below ground and the first step of the fall above the supply: the kick + assert y1 < 0.0; + assert y2 > vdd; +} + +test the_charge_and_the_current_do_not_jump_when_the_ends_of_a_channel_exchange_their_roles { + // At 0 V the NMOS channel's drain and source exchange and at the supply the PMOS's: the terminal on the output changes from the drain to the source. The charge is closed by the two + // edges of sar_comparator_q.t27 (Qs = Qd at Vds = 0) so it does not jump: across 2 words of the output (119 nV) the charge of each device moves by at most 4 words (a word is Q / 500 fF in + // 2^-24 V), for the gate at either rail; and the current is a clean reflection: the same magnitude on the two sides (to 2 IUNIT = 2.3 pA), the opposite sign + var gate_hi : f64 = IV_VDD; + var gate_lo : f64 = 0.0; + var below : f64 = 0.0 - 1.0; + var above : f64 = 1.0; + var top_below : f64 = IV_VDD - 1.0; + var top_above : f64 = IV_VDD + 1.0; + const n1 : IvDev = iv_nmos(below, gate_lo, 0); + const n2 : IvDev = iv_nmos(above, gate_lo, 0); + const n3 : IvDev = iv_nmos(below, gate_hi, 0); + const n4 : IvDev = iv_nmos(above, gate_hi, 0); + const p1 : IvDev = iv_pmos(top_below, gate_lo, 0); + const p2 : IvDev = iv_pmos(top_above, gate_lo, 0); + const p3 : IvDev = iv_pmos(top_below, gate_hi, 0); + const p4 : IvDev = iv_pmos(top_above, gate_hi, 0); + assert fabs64(n1.chg_w - n2.chg_w) < 4.0; + assert fabs64(n3.chg_w - n4.chg_w) < 4.0; + assert fabs64(p1.chg_w - p2.chg_w) < 4.0; + assert fabs64(p3.chg_w - p4.chg_w) < 4.0; + assert fabs64(n1.cur_iu + n2.cur_iu) < 2.0; + assert fabs64(n3.cur_iu + n4.cur_iu) < 2.0; + assert fabs64(p1.cur_iu + p2.cur_iu) < 2.0; + assert fabs64(p3.cur_iu + p4.cur_iu) < 2.0; + // the reflection is not zero: the NMOS on (gate high) carries 33 IUNIT at 1 word of Vds + assert n4.cur_iu > 30.0; + assert n3.cur_iu < 0.0 - 30.0; +} + +test the_extended_step_reproduces_the_500_fF_inverter_of_m3c_where_the_range_does_not_bind { + // The same code at 500 fF and dt = 100 ps (KC = 1, KI = 1: the tables' own scale, so no scaling of any table) against ngspice's backward Euler on the same 100 ps grid (psp103_inv_be_500f.cir, + // the delays that mos_psp103.t27's `q500_run` and `inv500_log` are held to 0.05 percent of): rise 1.34349 ns against 1.34352 (-0.0022 percent), fall 1.01804 ns against 1.01830 (-0.026 percent). + // The kick is 6 mV there, so the extension past 0 .. 1.5 V matters little and the step is the validated one. + var out : [2048]f64 = [0.0] ** 2048; + iv_run(500000, 100000, 0, 60, 0, &out); + const up : f64 = cross_idx(&out, HALF, true, 0, 60); + iv_run(500000, 100000, 1, 60, 0, &out); + const dn : f64 = cross_idx(&out, HALF, false, 0, 60); + const ng_up : f64 = steps_of_fs(INV500_BE_TPLH_FS, EDGE1_500F_FS, DT_500F_FS); + const ng_dn : f64 = steps_of_fs(INV500_BE_TPHL_FS, EDGE2_500F_FS, DT_500F_FS); + assert rel_gap(up, ng_up) < 0.0005; + assert rel_gap(dn, ng_dn) < 0.0005; + assert rel_gap(up, ng_up) > 0.00001; + assert rel_gap(dn, ng_dn) > 0.0002; + assert up < ng_up; + assert dn < ng_dn; +} + +test the_rest_level_is_microvolts_from_the_rail_and_the_gate_low_rest_is_the_supply { + // The DC solve with the gate at the supply (the NMOS on, the PMOS off) puts the output within a microvolt of ground (30 nV here, ngspice's 0.636 uV: its gmin of 1e-12 S is a leak of the + // same order), and with the gate at 0 within a microvolt of the supply (0.45 uV under it, ngspice's 1.5 V at the 6 digits it prints) + const hi : f64 = iv_rest(IV_VDD); + const lo : f64 = iv_rest(0.0); + var vdd : f64 = IV_VDD; + assert hi >= 0.0; + assert hi < 17.0; + assert lo < vdd; + assert vdd - lo < 17.0; +}