diff --git a/.trinity/seals/analog_AnalogAcceptSar.json b/.trinity/seals/analog_AnalogAcceptSar.json index 4f6b29ca1b..073527250b 100644 --- a/.trinity/seals/analog_AnalogAcceptSar.json +++ b/.trinity/seals/analog_AnalogAcceptSar.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+42a4860c4", + "built_by": "t27c-bootstrap@0.5.2+2d3203e90", "closure": [ { "spec": "specs/analog/linear.t27", @@ -7,7 +7,7 @@ }, { "spec": "specs/analog/mos_psp103.t27", - "spec_hash": "sha256:3edf0828e56e30977b96c2e943d203aac805fd197800d37a57fb4fc61c7152d8" + "spec_hash": "sha256:e04b69e5c43188a3d46a4c0949cfc727f239721de73fb46e92c8a845bbbbc98e" }, { "spec": "specs/analog/mos_psp103_50c_data.t27", @@ -15,7 +15,11 @@ }, { "spec": "specs/analog/mos_psp103_data.t27", - "spec_hash": "sha256:d233143ed2fa30d3bd0ff9546a2df008d568e4a4d9c7e0ef69ffedee2e92a5d6" + "spec_hash": "sha256:86e7c154c88a325e41ff4a154a3b766cb8d65e89e2ea82176b46ee4fc523dd74" + }, + { + "spec": "specs/analog/mos_psp103_gates.t27", + "spec_hash": "sha256:63ec92b62957ef68e8522c4dc392727ef8e5dc3b97195d519d68da1193b83e66" }, { "spec": "specs/analog/sar_adc.t27", @@ -51,23 +55,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:7c9dd3964fa325d00333c76dc95779c578609de9eca69b4e73d0b3a4feb4c225", - "gen_hash_rust": "sha256:da5d89f055e1e0b42d261c493bc87dea4a4f0a089f1e3ab75b125675c43300cf", - "gen_hash_verilog": "sha256:a4a93c52656bc335637904b014d2b36d5e2a6f1ab7acfafeb29a4fc30e328080", - "gen_hash_zig": "sha256:f96ed14294d3b6b5456f01db4128d08c40f7dc921c9cb1760f53ace64b03a41d", + "gen_hash_c": "sha256:34ccbab9cc4e3487395ef4e9b572a107c27a6dcfc9f1fce65e25448c65d2460d", + "gen_hash_rust": "sha256:fb56af614562c17edf7ddf801463a2b0cab6650b4833e140e3d753d7b72fe2b8", + "gen_hash_verilog": "sha256:c2311f1cb2c8fd5da7cda29c7c498104e62920a3d1cf3e17bfa0fd85eefb514c", + "gen_hash_zig": "sha256:23badcb27fb8ab78bc62a2956cdc25171e5d9e3274c50afe622e16c92c931e43", "module": "AnalogAcceptSar", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T03:11:58Z", + "sealed_at": "2026-10-20T08:13:43Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:47df48dd24939e164d669ed536f0fc5c7feb03edba165e7c7238212ae1354ab1", + "spec_hash": "sha256:815322645790478d663c1b0a8ce688e419fa7f78e2d9718042e7695d110f6087", "spec_path": "specs/analog/accept_sar.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 13, - "total": 13, + "passed": 14, + "total": 14, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/analog_AnalogMosPsp103Gates.json b/.trinity/seals/analog_AnalogMosPsp103Gates.json new file mode 100644 index 0000000000..42ed09a160 --- /dev/null +++ b/.trinity/seals/analog_AnalogMosPsp103Gates.json @@ -0,0 +1,41 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+2d3203e90", + "closure": [ + { + "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_table.t27", + "spec_hash": "sha256:54e248069ae620d18d260231e238b6bc42dc3c65792bd1673b162c2dc97b2edb" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:a27f3f394d8594134c3259e724858912d29f0870219ff3087c2beefaabb53a7f", + "gen_hash_rust": "sha256:773c19b0e5918f14274d5677437cec1ddca6bbba9a290593c7c74849913f0594", + "gen_hash_verilog": "sha256:e259d778dda4a1a118f8c81187e7dc22a319b098f3af0d00d7b46bb3b81b45a4", + "gen_hash_zig": "sha256:2ca337a6c0d2ccd3f92b1ad4e07d69298fc4d79b81075225c72f96f1829e1a0c", + "module": "AnalogMosPsp103Gates", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T08:13:50Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:63ec92b62957ef68e8522c4dc392727ef8e5dc3b97195d519d68da1193b83e66", + "spec_path": "specs/analog/mos_psp103_gates.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 6, + "total": 6, + "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 b13ab599c4..59938dd043 100644 --- a/docs/analog/accept-sar-2026-10-20.md +++ b/docs/analog/accept-sar-2026-10-20.md @@ -1,10 +1,10 @@ # 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. 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. 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). -What this document does not say: nothing about a transistor-level result for any gate-level block, nothing about the voltage level of any digital node, nothing about power, noise, mismatch, another corner or temperature, or speed. No model was tuned to turn a FAIL into a PASS, and none was changed to make a number come out. +What this document does not say: nothing about a transistor-level result for the gate-level blocks (dff, clock_gen, SAR_logic), nothing about the voltage level of any digital node but the nand's and the nor's, nothing about the delay of a gate into a load, nothing about the tgate's resistance, nothing about power, noise, mismatch, another corner or temperature, or speed. No model was tuned to turn a FAIL into a PASS, and none was changed to make a number come out. ## 1. The verdict rule @@ -16,17 +16,19 @@ A row is one quantity of the report's check. 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. +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. + ## 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 | 3 | clock_gen, SAR_logic, integration | +| PASS | 5 | clock_gen, SAR_logic, integration, nand, nor | | FAIL | 1 | dynamic_comparator (decision time) | -| NOT MODELLED | 9 | inverter, nand, nor, tgate, dff, tracking_switch, DAC_control, DAC_control_top, neg_bottom_plate_logic | +| NOT MODELLED | 7 | inverter, tgate, dff, tracking_switch, DAC_control, DAC_control_top, neg_bottom_plate_logic | -Over the 30 rows: 16 PASS, 1 FAIL, 13 NOT MODELLED. Five of the nine NOT MODELLED blocks have at least one measured, passing row (inverter 1, nand 1, nor 1, dff 2, tracking_switch 1); four have none (tgate, DAC_control, DAC_control_top, neg_bottom_plate_logic). The PASS of the three passing blocks is the PASS of the model named in section 4: gate level for clock_gen and SAR_logic, a behavioural converter fitted to the golden deck for integration. +Over the 30 rows: 19 PASS, 1 FAIL, 10 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. The new PASS rows are the nand's and the nor's levels (2) and the tgate's off leakage (1); the tgate's two resistances are measured now but stay NOT MODELLED, with their numbers and the reason (section 3b). Four of the seven NOT MODELLED blocks have at least one measured, passing row (inverter 1, tgate 1, dff 2, tracking_switch 1); three have none (DAC_control, DAC_control_top, neg_bottom_plate_logic). The PASS of clock_gen and SAR_logic is the PASS of the gate-level model named in section 4, that of integration the PASS of a behavioural converter fitted to the golden deck, and that of the nand and the nor the PASS of a DC solve on the PSP103 tables, each level within 3 uV of ngspice's. 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). @@ -39,12 +41,12 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | 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 | | | 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 | not produced | 1.5 V for a 1, microvolts for a 0 | 1.45 to 1.55 V; +-50 mV | NOT MODELLED | | +| 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` | -| nor | levels into 10 fF | not produced | 1.5 V for a 1, microvolts for a 0 | 1.45 to 1.55 V; +-50 mV | NOT MODELLED | | -| tgate | Ron at 0.75 V | not produced | 2.44 kohm | <= 30 kohm | NOT MODELLED | | -| tgate | worst Ron over 0.1 to 1.4 V | not produced | 2.78 kohm | <= 50 kohm | NOT MODELLED | | -| tgate | off leakage | not produced | 0.29 nA | <= 1 nA | NOT MODELLED | | +| nor | levels into 10 fF (DC solve, worst level of the 4 inputs) | 1 level 1.5 V - 1.85 uV; 0 levels 0.06 uV | 1.5 V for a 1, microvolts for a 0 | 1.45 to 1.55 V; +-50 mV | PASS (within 1.1 uV of ngspice) | `gt_levels` | +| tgate | Ron at 0.75 V (10 mV across it) | 1718 ohm, no body effect; ngspice's real cell 2460 ohm, 30.2 % above | 2.44 kohm | <= 30 kohm | NOT MODELLED (cross-check bar 10 %) | `gt_tg_ron` | +| tgate | worst Ron over 0.1 to 1.4 V (27 points) | 2109 ohm at 1.15 V, no body effect; ngspice's real cell 2776 ohm at 1.0 V, 24.0 % above | 2.78 kohm | <= 50 kohm | NOT MODELLED (cross-check bar 10 %) | `gt_tg_worst` | +| tgate | off leakage (full swing) | 291.0 pA; ngspice 285.6 pA at the output source, 271.4 pA at the input source | 0.29 nA | <= 1 nA | PASS (within 7.2 % of ngspice) | `gt_tg_off` | | dff | edge capture, hold, set, reset (logic level, 11 stages) | all right | all right | exact | PASS | `as_sar_run`, `as_dff_set_reset` | | dff | levels of the 8 observation points | not produced | all right | +-50 mV | NOT MODELLED | | | dff | clock-to-Q | 546 ps | 524 ps | 1 to 3000 ps | PASS | `as_sar_run` | @@ -52,9 +54,9 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | dynamic_comparator | levels after the decision | winner 1.49996 V, loser 3.6 mV | right | +-50 mV | PASS | `as_cmp` | | dynamic_comparator | reset | not produced | right | +-50 mV | NOT MODELLED | | | dynamic_comparator | decision time at +-2 mV | 5.35 ps | 607 ps | 10 to 10000 ps | **FAIL** | `as_cmp` | -| tracking_switch | tracking error, DC (our levels 0.2 V and 1.3 V into 3.93 pF) | -1.97 uV at both | < 0.001 mV at 0.3 V and 1.2 V into 2 pF | +-5 mV | PASS | `as_sw` | -| tracking_switch | hold step | not produced | -1.6 / -1.7 mV | +-30 mV | NOT MODELLED | | -| tracking_switch | droop | not produced | none | +-5 mV | NOT MODELLED | | +| tracking_switch | tracking error, DC (our levels 0.2 V and 1.3 V into 3.93 pF) | -1.97 uV at both; ngspice PSP103 +0.29 / -0.17 uV | < 0.001 mV at 0.3 V and 1.2 V into 2 pF | +-5 mV | PASS (within 2.3 uV of ngspice) | `as_sw` | +| tracking_switch | hold step | not produced; ngspice PSP103 cell -1.60 / -1.73 mV at the report's conditions | -1.6 / -1.7 mV | +-30 mV | NOT MODELLED | | +| tracking_switch | droop | not produced; ngspice PSP103 cell under 10 uV over 413 ns | none | +-5 mV | NOT MODELLED | | | clock_gen | slow delay (all trims 0), clks fall to clkc rise | 16.145 ns | 15.0 ns | 0.05 to 50 ns | PASS | `as_clk_golden` | | clock_gen | fast delay (all trims 1) | 9.822 ns | 9.2 ns | 0.02 to 40 ns | PASS | `as_clk_delay_ps` | | clock_gen | ratio slow / fast | 1.644 | 1.64 | >= 1.2 | PASS | | @@ -68,12 +70,58 @@ Times in ps or ns as written. "Report" is the report's measured value; "limit" i | 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 | | +## 3b. The new rows of M5d, with ngspice's number beside ours + +Every number in this table is printed by a deck of `specs/analog/decks` run in ngspice-47 with the IHP SG13G2 PSP103 (IHP-Open-PDK 21133f21, mos_tt, 27 C) on the same cell, and pinned in `specs/analog/mos_psp103_gates.t27` (nand, nor, tgate) or `specs/analog/accept_sar.t27` (tracking switch). "Difference" is ours minus ngspice's. The bar is what the cross-check rule of section 1 allows; the limit is the report's. + +| row | our value | ngspice | difference | bar | report | limit | verdict | +|---|---|---|---|---|---|---|---| +| nand 00, 1 level (droop under 1.5 V) | 0.179 uV | 0.316 uV | -0.138 uV | 10 uV | 1.5 V | 1.45 to 1.55 V | PASS | +| nand 01, 1 level | 0.477 uV | 0.721 uV | -0.244 uV | 10 uV | 1.5 V | 1.45 to 1.55 V | PASS | +| nand 10, 1 level | 0.477 uV | 0.687 uV | -0.210 uV | 10 uV | 1.5 V | 1.45 to 1.55 V | PASS | +| nand 11, 0 level | 0.179 uV | 2.537 uV | -2.358 uV | 10 uV | microvolts | +-50 mV | PASS | +| nor 00, 1 level (droop under 1.5 V) | 1.848 uV | 2.883 uV | -1.036 uV | 10 uV | 1.5 V | 1.45 to 1.55 V | PASS | +| nor 01, 0 level | 0.060 uV | 0.634 uV | -0.575 uV | 10 uV | microvolts | +-50 mV | PASS | +| nor 10, 0 level | 0.060 uV | 0.617 uV | -0.557 uV | 10 uV | microvolts | +-50 mV | PASS | +| nor 11, 0 level | 0.060 uV | 0.611 uV | -0.552 uV | 10 uV | microvolts | +-50 mV | PASS | +| tgate Ron at 0.75 V | 1718.0 ohm | 2459.6 ohm (real cell) | -741.6 ohm, -30.2 % | 10 % | 2.44 kohm | <= 30 kohm | NOT MODELLED | +| tgate worst Ron, 27 points 0.10 to 1.40 V | 2109.3 ohm (1.15 V) | 2776.1 ohm (real cell, 1.0 V) | -666.8 ohm, -24.0 % | 10 % | 2.78 kohm | <= 50 kohm | NOT MODELLED | +| tgate off leakage, 1.5 V across | 291.0 pA | 285.6 pA (output source), 271.4 pA (input source) | +5.4 pA (+1.9 %), +19.7 pA (+7.2 %) | 10 % | 0.29 nA | <= 1 nA | PASS | +| tracking error, 0.2 V into 3.93 pF | -1.967 uV | +0.287 uV | -2.253 uV | 10 uV | < 1 uV at 0.3 V and 1.2 V into 2 pF | +-5 mV | PASS | +| tracking error, 1.3 V into 3.93 pF | -1.967 uV | -0.174 uV | -1.793 uV | 10 uV | as above | +-5 mV | PASS | +| hold step, 0.3 V into 2 pF | not produced | -1.596 mV | | | -1.6 mV | +-30 mV | NOT MODELLED | +| hold step, 1.2 V into 2 pF | not produced | -1.730 mV | | | -1.7 mV | +-30 mV | NOT MODELLED | +| droop over the hold, all four runs | not produced | under 10 uV (zero at the 6 digits printed) | | | none | +-5 mV | NOT MODELLED | + +What the numbers say, one by one. + +- nand and nor levels. All eight levels, ours and ngspice's, are microvolts from the rail (1.5 V or 0 V): the report's "microvolts for a 0" is ngspice's too. The largest difference is 2.358 uV (the nand's 11, where the model's current floor leaves 0.18 uV and ngspice 2.54 uV); the bar of 10 uV was set at four times the worst difference and the verdict does not depend on it, since the limit is 50 mV. The levels are the DC solution with no load, which is the level a gate settles to into 10 fF; no transient is run, so nothing is said about how fast a gate gets there. +- tgate off leakage. Ours is the sum of the two table words at Vgs = 0, Vds = 1.5 V (the NMOS 261 pA, the PMOS 30 pA). ngspice's two sources differ by 14 pA (285.6 pA at the output, 271.4 pA at the input), the size of a junction leakage that sits in different terminals of the two; the terms are not separated here, and our number is within the 10 percent bar of both, and the report's 0.29 nA is ngspice's output-source number rounded. +- tgate Ron. Both resistances are NOT MODELLED because the model is 30 and 24 percent under the real cell, and the cause is measured, not guessed: the tables have no source-to-bulk voltage, and the real tgate has its bulks on the rails, so at 0.75 V the NMOS source sits 0.75 V above its bulk and the PMOS source 0.75 V below its bulk, which raises both thresholds. The deck `accept_tgate.cir` runs the same two devices again with each bulk tied to its own source, the condition the tables were made under: there ngspice gives 1717.2 ohm at 0.75 V and 2108.4 ohm at the worst point, and the model is within 0.06 percent of it at all 27 points (0.0588 percent at the largest). The gap to the real cell is largest at 0.85 V (31.1 percent) and the model is under the real cell at every one of the 27 points. Both resistances are far inside their limits (30 and 50 kohm); the rows are not PASS because the number is not the cell's number. +- tracking error. The square-law model (`sar_switch.t27`) tracks to -1.967 uV, the arithmetic floor; the PSP103 cell tracks to +0.287 and -0.174 uV (a settling residue, either sign); the 2.3 uV between them is under the bar of 10 uV and 2000 times under the limit. At the report's own conditions (0.3 V and 1.2 V into 2 pF) the PSP103 cell tracks to +0.346 and +0.350 uV; our model is not run at those two conditions (its load and its levels are fixed in `sar_switch.t27`), and the DC error is the model's floor at every level it has been run at. +- hold step and droop. The model has no gate capacitance and no leakage, so it produces neither, and the rows stay NOT MODELLED as before. What is new is ngspice's number for the same cell with the foundry's model: -1.596 and -1.730 mV at the report's conditions (the report's -1.6 and -1.7 mV), and -0.812 and -0.890 mV into our 3.93 pF (the charge injection spreads over a larger capacitor). The droop is zero at the six digits ngspice prints (under 10 uV) over the 413 ns of hold. + +### The reference run reproduces the report + +The same ngspice runs that serve as the reference give the numbers the external team measured on the same cells, so the cells we cross-check against are the report's cells (a test, `the_reference_run_of_the_real_tgate_and_the_psp103_switch_reproduces_the_reports_own_numbers`, pins each): + +| quantity | the report | our ngspice-47 run | difference | +|---|---|---|---| +| tgate Ron at 0.75 V | 2.44 kohm | 2459.6 ohm | +0.8 % | +| tgate worst Ron over 0.1 to 1.4 V | 2.78 kohm | 2776.1 ohm | -0.14 % | +| tgate off leakage | 0.29 nA | 0.2856 nA | -1.5 % | +| switch hold step at 0.3 V into 2 pF | -1.6 mV | -1.596 mV | +0.004 mV | +| switch hold step at 1.2 V into 2 pF | -1.7 mV | -1.730 mV | -0.030 mV (the report gives two digits) | +| switch tracking error, both levels | < 0.001 mV | 0.35 uV | inside | + ## 4. What each model is, and what that means for the verdict - inverter. The PSP103 tables of the SG13G2 NMOS (W 1.0 um) and PMOS (W 3.0 um), tt, 27 C, current in the log domain (`mos_psp103.t27`, M3c-2). The threshold is the input at which the DC transfer falls through Vdd / 2: 0.689243 V on the log tables, 0.689252 V on the linear ones; ngspice's trip point is 0.689 V and the report's 0.69 V. The delays into 20 fF are NOT MODELLED: the model's transient is validated at 500 fF (tpLH 1.344 ns, tpHL 1.018 ns) and 10 pF only, and a gate edge kicks the output 5 times further at 20 fF than at the 100 fF that `mos_psp103.t27` already says leaves the tables' 0 to Vdd range. Those 500 fF delays are not compared with the 1 to 500 ps limit: they are a different load. -- nand, nor, tgate, dff. Gate level (`sar_logic.t27`): a gate is a delay and a truth table, a transmission gate a switch with a stored node, a value is 0, 1 or X and never a voltage. Truth tables, capture, hold, set, reset and the clock-to-Q delay are measured on it. The levels in volts are NOT MODELLED, and so are the tgate's Ron and leakage (no spec models them; the 2592 ohm of a golden tgate in `sar_kick.t27` is an ngspice measurement, not ours). 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). +- 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. The two resistances are NOT MODELLED: the tables are made with source and bulk on one rail and have no body effect, and the real cell's bulks are on the rails, so 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 model agree to 0.06 percent, which is how the cause was found. The 2592 ohm of a golden tgate in `sar_kick.t27` is a different measurement (50 C, the golden deck's own bias) and is not compared. +- dff. Gate level (`sar_logic.t27`): capture, hold, set, reset and the clock-to-Q delay are measured on it; the levels in volts are NOT MODELLED. The dff clock-to-Q is the first stage of the sequencer, Valid rise to CK11 rise with D = VDD, 546 ps (ngspice's own is 550 ps). - dynamic_comparator. The PSP103 current tables at 50 C, the golden deck's temperature, with M4b's solver (`sar_comparator_psp.t27`). It has no charge: the devices' own capacitance is not in it, and against ngspice on the golden comparator with its charge its decision comes 98 times too fast (5.35 ps against 523 ps at +2 mV; the model is within 6.5 percent of ngspice with the charge swamped, which is the comparison the tables were built for). So the decision time FAILS the 10 ps lower limit by 4.65 ps, and the FAIL is the model's: ngspice's 523 ps on the same comparator with its charge is inside 10 to 10000 ps. The same run on the 27 C tables, the report's temperature, gives 5.12 ps and fails too. The reset row is NOT MODELLED because the run is one evaluation phase of 14 ps and the clock never falls. -- tracking_switch. A SQUARE-LAW bootstrapped switch (`sar_switch.t27`, MOSFET level 1). The tracking error at DC is measured: 1.97 uV, which is the fixed-point arithmetic floor (ngspice's own is 0.03 to 0.05 uV). The hold step and the droop are NOT MODELLED because the square law has no gate capacitance, so no charge injection, and no leakage: the model returns a step of 0.18 uV and a droop of exactly 0, which sit far inside +-30 mV and +-5 mV by having no effect at all. That is not a measurement of the effect the report's -1.6 and -1.7 mV show, so the rows are not passes. A model with the PSP103 switch is what the two rows need. +- tracking_switch. A SQUARE-LAW bootstrapped switch (`sar_switch.t27`, MOSFET level 1). The tracking error at DC is measured: 1.97 uV, which is the fixed-point arithmetic floor (ngspice's own on the square-law deck is 0.03 to 0.05 uV), and it is a row because ngspice's PSP103 run of the same cell at the same conditions (`accept_switch.cir`) gives +0.29 and -0.17 uV, 2.3 uV from it. The hold step and the droop are NOT MODELLED because the square law has no gate capacitance, so no charge injection, and no leakage: the model returns a step of 0.18 uV and a droop of exactly 0, which sit far inside +-30 mV and +-5 mV by having no effect at all. That is not a measurement of the effect the report's -1.6 and -1.7 mV show, so the rows are not passes; the same cell in ngspice with the foundry's model has the effect (-1.60 and -1.73 mV) and the doc shows it beside the rows. A model with the PSP103 switch is what the two rows need. - clock_gen, SAR_logic. Gate level with delay tables measured on the golden gates in ngspice (`sar_logic.t27`, 113 gates, the delay cell with its history); the delays are the model's own run, within 8 percent of ngspice's for every clkc edge. The slow delay is 16.145 ns (ngspice 16.147 ns), the fast one 9.822 ns (ngspice 9.669 ns, 1.6 percent), the ratio 1.644 (the report's 1.64). The stop time is taken from the eleventh Valid rise to the last clkc fall at the golden pace (40 ns): 5.704 ns (ngspice 5.807 ns); the report's 10.7 ns is not reproduced by this definition and the report's definition is not in the material this was made from, so the two are not compared. SAR_logic is driven by eleven Valid pulses of 10 ns every 20 ns, so CK11 to CK1 spans ten periods plus the 126 ps by which CK1's load makes it later than CK11. - DAC_control, DAC_control_top, neg_bottom_plate_logic. NOT MODELLED. The DAC (`sar_cdac.t27`) has ideal capacitors driven by ideal plate sources: the level a plate reaches is its input, not an output. The eleven latches with their plate lines and the N half's follow-then-complement logic are not circuits in any spec. The polarity (the plate goes to the complement of the decision) is exercised end to end by `sar_adc.t27`, whose behavioural converter matches the golden deck's codes 30 of 31 times; that is not these blocks' check. - integration. The behavioural converter of `sar_adc.t27`: ideal DAC capacitors, one fitted top-plate parasitic (0.63 of a 9 um x 9 um unit) and the comparator offset measured on the netlist (6.776 mV, `sar_kick.t27`), on an ideal sampling input. The 8 conversions are -1 V, -0.714 V, ... +1 V. The worst error is |code + 0.5 - ideal| = 18.5 LSB (at +1 V: the model's gain is 1.9 percent above ideal, which is what the fit found in the golden deck) and the smallest step 198 LSB (ideal 195). This is a PASS of the model that was fitted to the golden deck's 30 of 31 codes; it is not a circuit-level result. @@ -93,11 +141,13 @@ The four numbers agree within a few LSB. Without the measured offset the end cod ## 5. Conditions that differ from the report -- Tracking switch: the report drives 2 pF at 0.3 V and 1.2 V; ours drives 3.93 pF (the DAC's top plate) at 0.2 V and 1.3 V. The tracking error scales with R C dVin/dt and is zero at DC in both; the number is ours at our conditions. +- Tracking switch: the report drives 2 pF at 0.3 V and 1.2 V; ours drives 3.93 pF (the DAC's top plate) at 0.2 V and 1.3 V. The tracking error scales with R C dVin/dt and is zero at DC in both; the number is ours at our conditions. ngspice's PSP103 cell is run at both sets (`accept_switch.cir`): the tracking error is under 0.4 uV at all four, and the hold step at the report's conditions is the report's. Our model was not run at 0.3 V, 1.2 V and 2 pF: the load and the levels are constants of `sar_switch.t27`, which M5d does not touch. - Comparator: the report is at 27 C, the validated model is at 50 C (the golden deck's temperature). Both temperatures fail. - Inverter threshold: taken as the input at which the output crosses Vdd / 2 (the quantity ngspice's trip points in `mos_psp103.t27` are). The other usual definition, the input at which the output equals the input, is not computed here. - Integration: see above. The report's sample points are not in the material this was made from. -- The gate-level rows run on delays measured in ngspice at tt, 50 C (the golden deck's temperature), not the report's 27 C; the square-law switch has no temperature. +- The 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. +- tgate: Ron is 10 mV over the current with 10 mV across the gate (the measurement of `sar_off_ron.cir`, at 27 C here), on 27 outputs 50 mV apart from 0.10 V to 1.40 V; the report's sweep grid and its definition of Ron are not in the material this was made from, and the reference run (ngspice, same definition, same grid) gives the report's 2.44 and 2.78 kohm to 0.8 and 0.14 percent. The leakage is at 1.5 V across the off gate, the bias at which ngspice gives the report's 0.29 nA. ## 6. Negative controls @@ -108,19 +158,27 @@ Every check function accepts the report's own value and refuses a broken one, so | inverter threshold | PMOS table zeroed | the output never falls through Vdd / 2: the reader's no-crossing value, -0.031 V, refused by 0.5 V | | comparator sign | the inputs exchanged | the sign flips, refused | | comparator time | a missed decision, 20 ns | refused on both sides | -| tracking error | boost capacitor removed | -25.4 mV, refused by 5 mV | +| tracking error | boost capacitor removed | -25.4 mV, refused by 5 mV and, against ngspice's PSP103 cell, by the 10 uV bar | | clock ratio | the trim code ignored | 1.0, refused by 1.2 | | SAR_logic counts and order | the fifth dff with Set and Reset exchanged | 2, 5, 11 high instead of 1, 4, 11, wrong order, refused | | dff set and reset | the same dff | the set row loses CK7, the clks reset sets it, refused | | integration error | the MSB capacitor halved | 237 LSB, refused by 64 | -| nand, nor | the other gate's table, one row flipped | refused | +| nand, nor truth table | the other gate's table, one row flipped | refused | +| nand levels | the PMOS table zeroed / the NMOS table zeroed | the 1 levels fall to under 0.4 mV, 1.5 V off / the 0 level rises to 1.5 V; refused by the report's check and by the cross-check | +| nand, nor levels | the wrong gate (the nand's 1 read where the nor has a 0) | 1.5 V apart | +| tgate leakage | the NMOS table in both slots | 521.5 pA: inside the report's 1 nA, 82.6 percent over ngspice's, refused by the 10 percent bar | +| tgate Ron | the PMOS table zeroed / the NMOS table zeroed, against the no-body-effect cell | 2895 / 4225 ohm at 0.75 V, 69 and 146 percent over, refused by the 10 percent bar | | DAC_control_top | a wrong code, a wrong plate code, two bits swapped, code and plate exchanged | refused (1430 + 617 = 2047, the complement pair) | -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. +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. ## 7. Not done, and why -- The 20 fF inverter delays, the tgate's Ron and leakage, the comparator's reset, the tracking switch's hold step and droop, and the three DAC blocks are NOT MODELLED. Each needs a model that does not exist: a charge-aware inverter at 20 fF, a transmission gate from the PSP103 tables, a second clock phase for the comparator, a switch with gate capacitance and leakage, circuit-level plate drivers and latches. They are not approximated here. +- The 20 fF inverter delays, the tgate's two resistances, the comparator's reset, the tracking switch's hold step and droop, and the three DAC blocks are NOT MODELLED. Each needs a model that does not exist: a charge-aware inverter at 20 fF, a transmission gate with the body effect (the tables have none: a source-to-bulk dimension or a measured threshold shift per device, which the two tgate decks of M5d can validate on the real cell and on the cell with the bulks tied), a second clock phase for the comparator, a switch with gate capacitance and leakage (ngspice's PSP103 numbers for that cell, -1.60 and -1.73 mV and no droop, are in section 3b as the target), circuit-level plate drivers and latches. They are not approximated here. +- The nand's, the nor's and the tgate's delays into a load, and their levels in a transient: M5d does the DC levels only. +- The tracking error of our model at the report's own conditions (0.3 V and 1.2 V into 2 pF), see section 5. - The comparator's FAIL stays a FAIL. The cause is known and measured (no charge: 98 times too fast); the charge tables for the comparator are not made. - The 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. @@ -130,6 +188,9 @@ A limit of the control: some of the report's limits are wide, and a model error ``` ./target/release/t27c test-report specs/analog/accept_sar.t27 ./target/release/t27b test specs/analog/accept_sar.t27 --check +./target/release/t27b test specs/analog/mos_psp103_gates.t27 --check ``` -The spec has 13 tests (the verdict rule, one for each block or group of blocks, the corners line and the counts); every number in section 3 is pinned in them. `t27b test --check` exits 0 with no jit/interp mismatch. +`accept_sar.t27` has 14 tests (the verdict rule, one for each block or group of blocks, the reference run against the report, the corners line and the counts of blocks and of rows); `mos_psp103_gates.t27` has 6 (the nand, the nor, the gates' negative controls, the tgate without and with the body effect, the leakage). Every number in sections 3 and 3b 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. 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 58541c5448..1d85ea3838 100644 --- a/specs/analog/accept_sar.t27 +++ b/specs/analog/accept_sar.t27 @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 ; specs/analog/accept_sar.t27 -- the per-block acceptance checks of the external team's report of 2026-10-16, as an evaluator, applied to our SAR ADC blocks -; M5c of the analog emulation epic (#10486, issue #11212, night loop #11063). The external team's report of 2026-10-16 lists, for the 13 blocks of the golden SAR ADC, a +; 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. 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 ; pure check function per quantity, and one function per block that MEASURES the quantity on OUR model, by `use` of the spec that holds the model (no copy), and @@ -10,21 +11,28 @@ ; FAIL (our model produced the value and it is outside) or 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 x). A block is FAIL when any of its rows FAILs, PASS 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 not a pass. A FAIL is a measurement and is -; reported as measured; no model was tuned in this file to turn one into a PASS. +; reported as measured; no model was tuned in this file to turn one into a PASS. THE CROSS-CHECK RULE (M5d): a row from a transistor-level model counts as produced only when the +; same cell run in ngspice-47 with the PSP103 (specs/analog/decks/accept_*.cir) agrees with our number within a bar stated before the verdict is read: 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 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. -; nand, nor, tgate GATE LEVEL (sar_logic.t27): a gate is a delay and a truth table, a transmission gate a switch with a stored node. A logic value is 0, 1 or X, never a -; voltage. The truth table is measured and is a row of its own; the levels in volts and the tgate's Ron and leakage are NOT MODELLED. -; dff the same gate level (inside SAR_logic): capture, hold, set, reset and the clock-to-Q delay are measured on it; the levels in volts are NOT MODELLED. +; 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. +; tgate three rows on the same tables (mos_psp103_gates.t27): the off leakage (291.0 pA, 7.2 percent from ngspice's real cell) is a row; Ron at 0.75 V and the worst Ron +; over 0.1 to 1.4 V (1718 and 2109 ohm, inside 30 and 50 kohm) are NOT MODELLED: the real cell, run in ngspice with its bulks on the rails, is 30 and 24 percent +; above them, past the 10 percent bar, because the tables have no body effect. With each bulk tied to its source ngspice is within 0.06 percent of the tables. +; dff the gate level (inside SAR_logic): capture, hold, set, reset and the clock-to-Q delay are measured on it; the levels in volts are NOT MODELLED. ; dynamic_comparator PSP103 current tables at 50 C, the golden deck's temperature (sar_comparator_psp.t27), the NO-CHARGE model: the devices' own capacitance is not in it, so ; its decision comes 98 times too fast against ngspice (5.35 ps against 523 ps at +2 mV). The sign and the end levels are measured; the reset (a second clock ; phase) is not run; the decision time is measured and FAILS the 10 ps lower limit. -; tracking_switch SQUARE LAW (MOSFET level 1) bootstrapped switch (sar_switch.t27): the tracking error at DC is measured (1.97 uV, the arithmetic floor); the hold step and the -; droop are NOT MODELLED, because the square law has no gate capacitance (no charge injection) and no leakage: the model's 0.18 uV and 0 V would "pass" a -; 30 mV and a 5 mV limit by having no effect at all, which is not a measurement of the effect. +; tracking_switch SQUARE LAW (MOSFET level 1) bootstrapped switch (sar_switch.t27): the tracking error at DC is measured (1.97 uV, the arithmetic floor) and agrees with ngspice's +; PSP103 run of the same cell to 2.3 uV; the hold step and the droop are NOT MODELLED, because the square law has no gate capacitance (no charge injection) and no +; leakage: the model's 0.18 uV and 0 V would "pass" a 30 mV and a 5 mV limit by having no effect at all, which is not a measurement of the effect. ngspice's PSP103 +; cell has the effect (hold step -1.60 and -1.73 mV, the report's -1.6 and -1.7; droop under 10 uV) and the doc shows it beside the rows. ; clock_gen, SAR_logic gate level with delay tables MEASURED on the golden gates in ngspice (sar_logic.t27, 113 gates); the delays are the model's own run, within 8 % of ngspice. ; DAC_control, DAC_control_top, neg_bottom_plate_logic NOT MODELLED. The DAC (sar_cdac.t27) has ideal capacitors driven by ideal plate sources: the level a plate reaches ; is its input, not an output; the latches and the N half's follow-then-complement logic are not circuits in any spec. @@ -33,21 +41,30 @@ ; circuit-level result. ; - ; WHAT THE ROWS ARE NOT. The conditions differ in places and each is stated in the doc: the report's tracking switch drives 2 pF at 0.3 and 1.2 V, ours drives 3.93 pF at -; 0.2 and 1.3 V; the report's comparator is at 27 C, ours at 50 C (the 27 C tables give 5.12 ps and fail too); the report's ramp for the converter has 8 conversions +; 0.2 and 1.3 V (ngspice's PSP103 cell is run at both: the tracking error is under 0.4 uV at all four, and the hold step at the report's conditions is the report's); the levels of the +; nand and nor are the DC solution, which is what a gate settles to into 10 fF, not a transient; the report's comparator is at 27 C, ours at 50 C (the 27 C tables give 5.12 ps and +; fail too); the report's ramp for the converter has 8 conversions ; whose ideal end codes are 517 and 1579, ours is the literal -1 to +1 V ramp, and the report's own ramp is rebuilt as context only. The stop time of the clock is taken ; from the eleventh Valid rise to the last clkc fall at the golden pace of 40 ns; the report's 10.7 ns is not reproduced by that definition (ours is 5.70 ns) and its ; definition is not in the material this file was made from. ; - ; THE NEGATIVE CONTROLS (FR-004), each a test that passes by seeing the failure. Every check function refuses a broken value (a delay out of range, a swapped bit, a wrong ; code) and accepts the report's own measured value, so no check is always-FAIL or always-PASS. Where a model can be broken to leave the limit, that is done too: the -; PMOS table zeroed (the output never falls through Vdd / 2: the reader returns -0.031 V, no crossing), the inputs of the comparator exchanged (the sign flips), the boost capacitor removed (tracking error 25.4 mV), the trim code -; ignored (ratio 1.0), a dff with Set and Reset exchanged (the order, the counts and the set and reset rows fail), a converter whose MSB capacitor is halved (237 LSB). +; PMOS table zeroed (the output never falls through Vdd / 2: the reader returns -0.031 V, no crossing), the inputs of the comparator exchanged (the sign flips), the boost capacitor removed (tracking error 25.4 mV, refused by the limit and by the cross-check), the trim code +; ignored (ratio 1.0), a dff with Set and Reset exchanged (the order, the counts and the set and reset rows fail), a converter whose MSB capacitor is halved (237 LSB), a gate with +; its PMOS or NMOS table zeroed (the 1 levels at 0 V, the 0 level at 1.5 V), the tgate with the NMOS table in both slots (521 pA, inside the report's 1 nA and 82 percent over ngspice's, so the cross-check refuses it). ; Honest limit of the control: a few of the report's limits are wide (the inverter threshold 0.5 to 1.0 V, a bridge of 2 units gives 45.5 LSB against 64), and a model -; error inside the limit is not seen by that check; the sar_adc.t27 tests see it by the codes. +; error inside the limit is not seen by that check; the sar_adc.t27 tests see it by the codes. The level cross-check of a gate does not see a device's width (the two tables in each +; other's slots stay within 3 uV of ngspice's levels, mos_psp103_gates.t27 pins it); the tgate's leakage and Ron do. ; - -; WHAT A PASS SAYS. For the clock_gen, SAR_logic and integration blocks: every quantity the report's check enforces is produced by our model and is inside the report's limit. -; For a row marked PASS inside a NOT MODELLED block: that quantity, at the model's level above, is inside the limit. WHAT IT DOES NOT SAY. Nothing about a transistor-level -; result for any gate-level block, nothing about voltage levels of any digital node, nothing about power, noise, mismatch or another corner or temperature (the report's smoke +; THE REFERENCE RUN IS CHECKED AGAINST THE REPORT. The ngspice runs the cross-checks use give what the external team measured on the same cells: the real tgate 2459.6 ohm at 0.75 V +; (the report 2.44 kohm) and 2776.1 ohm worst (2.78 kohm), 0.2856 nA off (0.29 nA), and the PSP103 tracking switch -1.596 and -1.730 mV of hold step (-1.6 and -1.7 mV), so the +; numbers a model is judged by are the report's cells; a test pins each. +; - +; WHAT A PASS SAYS. For the clock_gen, SAR_logic, integration, nand and nor blocks: every quantity the report's check enforces is produced by our model and is inside the report's +; limit. For a row marked PASS inside a NOT MODELLED block: that quantity, at the model's level above, is inside the limit. WHAT IT DOES NOT SAY. Nothing about a transistor-level +; result for the gate-level blocks (dff, clock_gen, SAR_logic), nothing about voltage levels of any digital node but the nand's and nor's, nothing about the delay of any gate into a load, +; nothing about the tgate's resistance (the body effect is not in the tables), nothing about power, noise, mismatch or another corner or temperature (the report's smoke ; suite runs five corners at -40, 27 and 125 C; our tables have no -40 or 125 C and the line is NOT MODELLED), nothing about speed. The report's numbers are the report's; ours ; are computed here by the named functions. ASCII only, English only. ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -55,8 +72,9 @@ module AnalogAcceptSar; use analog::linear::{fabs64, q_sm}; -use analog::mos_psp103::{dc_out, cross_idx, DC_POINTS, DC_STEP}; +use analog::mos_psp103::{dc_out, cross_idx, rel_gap, DC_POINTS, DC_STEP}; use analog::mos_psp103_data::{TT_LG_N, TT_LG_P, TT_N, TT_P}; +use analog::mos_psp103_gates::{GT_B_LEVEL_V, GT_B_REL, GT_NG_RTG, GT_NG_LEAK_OUT_A, GT_NG_LEAK_IN_A, Lv, gt_levels, gt_tg_ron, gt_tg_v, gt_tg_worst, gt_tg_off, gt_ref_max}; use analog::mos_psp103_50c_data::{TT50_N, TT50_P}; use analog::sar_comparator::{SC_DV_M, SC_K, SC_N, SC_NN, SC_DP, SC_DN, SC_UPV, SC_VCM, SC_VTH_V, SC_H2, sc_sign, sc_build_table}; use analog::sar_comparator_psp::{sp_run, sp_first}; @@ -81,11 +99,13 @@ pub const AS_LABEL_NM : str = "NOT MODELLED"; /// The blocks of the report that are counted: inverter, nand, nor, tgate, dff, dynamic_comparator, tracking_switch, clock_gen, SAR_logic, DAC_control, DAC_control_top, /// neg_bottom_plate_logic, and the 11-bit integration run. The corners smoke suite is a test run over five corners and three temperatures, not a block: it is a line of its own. pub const AS_N_BLOCKS : u32 = 13; +/// The rows of those blocks: 30 (inverter 2, nand 2, nor 2, tgate 3, dff 3, dynamic_comparator 4, tracking_switch 3, clock_gen 4, SAR_logic 2, DAC_control 1, DAC_control_top 1, neg_bottom_plate_logic 1, integration 2). +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_TGATE : str = "no spec models a transmission gate's resistance or leakage; the 2592 ohm in sar_kick.t27 is an ngspice measurement, not ours"; +pub const AS_WHY_TG_RON : str = "the tables have no body effect: ngspice's real cell is 30 percent above ours at 0.75 V and 24 percent above at the worst point, past the 10 percent bar; with each bulk tied to its source ngspice agrees with ours to 0.06 percent"; pub const AS_WHY_CMP_RESET : str = "the comparator run is one evaluation phase of 14 ps; the clock never falls, so there is no reset phase to measure"; pub const AS_WHY_SW_HOLD : str = "square law: no gate capacitance, so no charge injection; the hold step the model returns is a following artifact of 0.18 uV"; pub const AS_WHY_SW_DROOP : str = "square law: no leakage; the droop the model returns is exactly zero by construction"; @@ -148,6 +168,21 @@ pub const R_DFF_C2Q_PS : f64 = 524.0; pub const R_CMP_DT_PS : f64 = 607.0; pub const R_SW_HOLD_A_V : f64 = 0.0 - 0.0016; pub const R_SW_HOLD_B_V : f64 = 0.0 - 0.0017; +; --- ngspice-47 + PSP103 mos_tt 27 C on the tracking_switch cell, specs/analog/decks/accept_switch.cir (6 digits as printed; a step is a difference of two) ---------------- +/// Tracking error V(out) - V(in) at the end of the track phase, at this file's conditions: 0.2 V and 1.3 V into the DAC's top plate (3930.2 fF and the cell's 4 fF). +pub const NG_SW_TE_A_LO_V : f64 = 0.000000286502; +pub const NG_SW_TE_A_HI_V : f64 = 0.0 - 0.000000173859; +/// The same at the report's conditions: 0.3 V and 1.2 V into 2 pF. +pub const NG_SW_TE_B_LO_V : f64 = 0.000000346209; +pub const NG_SW_TE_B_HI_V : f64 = 0.000000349712; +/// The hold step (V(out) 2 ns after the clock has fallen minus V(out) just before it falls) at the report's conditions, and at this file's. +pub const NG_SW_HOLD_B_LO_V : f64 = 0.0 - 0.001596; +pub const NG_SW_HOLD_B_HI_V : f64 = 0.0 - 0.00173; +pub const NG_SW_HOLD_A_LO_V : f64 = 0.0 - 0.000812; +pub const NG_SW_HOLD_A_HI_V : f64 = 0.0 - 0.00089; +/// The droop over the hold (V(out) at 499 ns minus at 86 ns) is 0 at the 6 digits printed in all four runs: its size is under 10 uV. +pub const NG_SW_DROOP_BOUND_V : f64 = 0.00001; + pub const R_CLK_SLOW_NS : f64 = 15.0; pub const R_CLK_FAST_NS : f64 = 9.2; pub const R_CLK_RATIO : f64 = 1.64; @@ -232,9 +267,24 @@ pub fn as_chk_gate_levels(one_v: f64, zero_v: f64) -> bool { return as_in(one_v, L_GATE_ONE_LO_V, L_GATE_ONE_HI_V) and as_within(zero_v, L_GATE_ZERO_V); } -/// tgate: Ron at 0.75 V at most 30 kohm, the worst Ron over 0.1 to 1.4 V at most 50 kohm, the off leakage at most 1 nA. +/// tgate: Ron at 0.75 V at most 30 kohm. +pub fn as_chk_tg_ron(ron_ohm: f64) -> bool { + return ron_ohm <= L_TG_RON_OHM; +} + +/// tgate: the worst Ron over 0.1 to 1.4 V at most 50 kohm. +pub fn as_chk_tg_worst(worst_ohm: f64) -> bool { + return worst_ohm <= L_TG_RON_WORST_OHM; +} + +/// tgate: the off leakage at most 1 nA. +pub fn as_chk_tg_leak(leak_na: f64) -> bool { + return leak_na <= L_TG_LEAK_NA; +} + +/// tgate: the three together. pub fn as_chk_tgate(ron_ohm: f64, worst_ohm: f64, leak_na: f64) -> bool { - return ron_ohm <= L_TG_RON_OHM and worst_ohm <= L_TG_RON_WORST_OHM and leak_na <= L_TG_LEAK_NA; + return as_chk_tg_ron(ron_ohm) and as_chk_tg_worst(worst_ohm) and as_chk_tg_leak(leak_na); } /// dff: edge capture, hold, set and reset all right. @@ -624,6 +674,22 @@ pub fn as_ramp_report_like() -> Ramp { return as_ramp(&kf, q_sm(off_late_v()), (517.5 - 1024.0) / 682.0, 1062.0 / 7.0 / 682.0); } +; --- the tgate on the tt log tables, against ngspice's real cell ------------------------------------------------------------------ +/// The tgate on the tables tn, tp (mos_psp103_gates.t27): Ron at 0.75 V (sweep point 13), the worst Ron of the 27 points, the off leakage in nA, and each one's relative gap to +/// ngspice's REAL cell (the bulks on the rails: accept_tgate.cir and accept_gates.cir): Ron to its 0.75 V point, the worst to the largest of ngspice's 27, the leakage to the larger of +/// ngspice's two source currents and to the smaller (the larger of the two gaps). +pub struct Tg { ron: f64, worst: f64, leak_na: f64, g_ron: f64, g_worst: f64, g_leak: f64 } + +pub fn as_tgate(tn: []const u32, tp: []const u32) -> Tg { + const ron : f64 = gt_tg_ron(tn, tp, gt_tg_v(13)); + const worst : f64 = gt_tg_worst(tn, tp); + const leak : f64 = gt_tg_off(tn, tp); + var g_leak : f64 = rel_gap(leak, GT_NG_LEAK_OUT_A); + const g_in : f64 = rel_gap(leak, GT_NG_LEAK_IN_A); + if (g_in > g_leak) { g_leak = g_in; } + return Tg { ron: ron, worst: worst, leak_na: leak * 1000000000.0, g_ron: rel_gap(ron, GT_NG_RTG[13]), g_worst: rel_gap(worst, gt_ref_max(>_NG_RTG)), g_leak: g_leak }; +} + ; --- 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 { @@ -633,28 +699,33 @@ pub fn as_blk_inverter() -> Blk { return b; } -/// nand: the truth table of the gate-level model; the levels in volts are not modelled. +/// 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 { var b : Blk = as_blk0(); b = as_add(b, as_verdict(true, as_chk_nand(sl_nand_code(0, 0), sl_nand_code(0, 1), sl_nand_code(1, 0), sl_nand_code(1, 1)))); - b = as_add(b, as_nm()); + const lv : Lv = gt_levels(&TT_LG_N, &TT_LG_P, false); + b = as_add(b, as_verdict(lv.gap_v <= GT_B_LEVEL_V, as_chk_gate_levels(lv.one_v, lv.zero_v))); return b; } -/// nor: the truth table of the gate-level model (the third input of sar_logic's nor code passed as 0); the levels in volts are not modelled. +/// nor: the truth table of the gate-level model (the third input of sar_logic's nor code passed as 0), and the levels of the DC solve, on the same terms as the nand. pub fn as_blk_nor() -> Blk { var b : Blk = as_blk0(); b = as_add(b, as_verdict(true, as_chk_nor(sl_nor_code(0, 0, 0), sl_nor_code(0, 1, 0), sl_nor_code(1, 0, 0), sl_nor_code(1, 1, 0)))); - b = as_add(b, as_nm()); + const lv : Lv = gt_levels(&TT_LG_N, &TT_LG_P, true); + b = as_add(b, as_verdict(lv.gap_v <= GT_B_LEVEL_V, as_chk_gate_levels(lv.one_v, lv.zero_v))); return b; } -/// tgate: Ron at 0.75 V, the worst Ron and the off leakage are not modelled. +/// tgate: Ron at 0.75 V, the worst Ron over 0.1 to 1.4 V and the off leakage on the PSP103 log tables; a row only when its value is within 10 percent of ngspice's run of the REAL +/// cell. The tables have no body effect, so the two resistances are 30 and 24 percent under and are NOT MODELLED; the leakage, at Vgs = 0, has none and is within 7.2 percent. pub fn as_blk_tgate() -> Blk { var b : Blk = as_blk0(); - b = as_add(b, as_nm()); - b = as_add(b, as_nm()); - b = as_add(b, as_nm()); + const t : Tg = as_tgate(&TT_LG_N, &TT_LG_P); + b = as_add(b, as_verdict(t.g_ron <= GT_B_REL, as_chk_tg_ron(t.ron))); + b = as_add(b, as_verdict(t.g_worst <= GT_B_REL, as_chk_tg_worst(t.worst))); + b = as_add(b, as_verdict(t.g_leak <= GT_B_REL, as_chk_tg_leak(t.leak_na))); return b; } @@ -681,12 +752,14 @@ pub fn as_blk_comparator() -> Blk { return b; } -/// tracking_switch: the tracking error at 0.2 V and 1.3 V (3.93 pF); the hold step and the droop are not modelled. +/// tracking_switch: the tracking error at 0.2 V and 1.3 V (3.93 pF), a row because both are within 10 uV of ngspice's PSP103 run of the same cell at the same conditions +/// (accept_switch.cir); the hold step and the droop are not modelled. pub fn as_blk_switch() -> Blk { var b : Blk = as_blk0(); const lo : Sw = as_sw(0, SW_CB); const hi : Sw = as_sw(2, SW_CB); - b = as_add(b, as_verdict(true, as_chk_sw_track(lo.te, hi.te))); + const agree : bool = as_within(lo.te - NG_SW_TE_A_LO_V, GT_B_LEVEL_V) and as_within(hi.te - NG_SW_TE_A_HI_V, GT_B_LEVEL_V); + b = as_add(b, as_verdict(agree, as_chk_sw_track(lo.te, hi.te))); b = as_add(b, as_nm()); b = as_add(b, as_nm()); return b; @@ -738,25 +811,41 @@ pub fn as_blk_integration() -> Blk { return b; } -/// The 13 blocks counted by their verdict: .pass blocks PASS, .fail blocks FAIL, .nm blocks NOT MODELLED. -pub fn as_all_blocks() -> Blk { +/// A block added to a tally: as one verdict (rows false: .pass counts the blocks that PASS) or as its rows (rows true: .pass counts the rows that PASS). +pub fn as_tally(t: Blk, b: Blk, rows: bool) -> Blk { + if (rows) { return Blk { pass: t.pass + b.pass, fail: t.fail + b.fail, nm: t.nm + b.nm }; } + return as_add(t, as_block_verdict(b)); +} + +/// The 13 blocks counted by their verdict (rows false) or the 30 rows of them counted by theirs (rows true). +pub fn as_count(rows: bool) -> Blk { var t : Blk = as_blk0(); - t = as_add(t, as_block_verdict(as_blk_inverter())); - t = as_add(t, as_block_verdict(as_blk_nand())); - t = as_add(t, as_block_verdict(as_blk_nor())); - t = as_add(t, as_block_verdict(as_blk_tgate())); - t = as_add(t, as_block_verdict(as_blk_dff())); - t = as_add(t, as_block_verdict(as_blk_comparator())); - t = as_add(t, as_block_verdict(as_blk_switch())); - t = as_add(t, as_block_verdict(as_blk_clock())); - t = as_add(t, as_block_verdict(as_blk_sar_logic())); - t = as_add(t, as_block_verdict(as_blk_dac_control())); - t = as_add(t, as_block_verdict(as_blk_dac_top())); - t = as_add(t, as_block_verdict(as_blk_neg_plate())); - t = as_add(t, as_block_verdict(as_blk_integration())); + t = as_tally(t, as_blk_inverter(), rows); + t = as_tally(t, as_blk_nand(), rows); + t = as_tally(t, as_blk_nor(), rows); + t = as_tally(t, as_blk_tgate(), rows); + t = as_tally(t, as_blk_dff(), rows); + t = as_tally(t, as_blk_comparator(), rows); + t = as_tally(t, as_blk_switch(), rows); + t = as_tally(t, as_blk_clock(), rows); + t = as_tally(t, as_blk_sar_logic(), rows); + t = as_tally(t, as_blk_dac_control(), rows); + t = as_tally(t, as_blk_dac_top(), rows); + t = as_tally(t, as_blk_neg_plate(), rows); + t = as_tally(t, as_blk_integration(), rows); return t; } +/// The 13 blocks counted by their verdict: .pass blocks PASS, .fail blocks FAIL, .nm blocks NOT MODELLED. +pub fn as_all_blocks() -> Blk { + return as_count(false); +} + +/// The rows of the 13 blocks counted by their verdict. +pub fn as_all_rows() -> Blk { + return as_count(true); +} + ; --- 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. @@ -826,7 +915,7 @@ test the_inverter_threshold_is_measured_on_the_psp103_log_tables_and_the_delays_ assert_eq(as_block_verdict(b), AS_NM); } -test the_nand_and_nor_truth_tables_are_measured_at_logic_level_and_the_levels_in_volts_are_not_modelled { +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 { assert(as_chk_nand(sl_nand_code(0, 0), sl_nand_code(0, 1), sl_nand_code(1, 0), sl_nand_code(1, 1))); assert(as_chk_nor(sl_nor_code(0, 0, 0), sl_nor_code(0, 1, 0), sl_nor_code(1, 0, 0), sl_nor_code(1, 1, 0))); // NEGATIVE CONTROL: each table is refused when it is the other gate's, and a single flipped row is refused @@ -844,17 +933,46 @@ test the_nand_and_nor_truth_tables_are_measured_at_logic_level_and_the_levels_in var leak : f64 = 0.2; assert(!as_chk_gate_levels(sag, low)); assert(!as_chk_gate_levels(one, leak)); + // THE LEVELS, a DC solve of the two-input gate on the tt log tables (mos_psp103_gates.t27), the level the gate settles to into 10 fF: the lowest 1 of the nand is 1.4999995 V and of the + // nor 1.4999981 V, the highest 0 is 0.178 uV and 0.06 uV; all inside the report's 1.45 to 1.55 V and 50 mV by four orders of magnitude + const nd : Lv = gt_levels(&TT_LG_N, &TT_LG_P, false); + const nr : Lv = gt_levels(&TT_LG_N, &TT_LG_P, true); + assert(as_chk_gate_levels(nd.one_v, nd.zero_v)); + assert(as_chk_gate_levels(nr.one_v, nr.zero_v)); + assert(nd.one_v > 1.4999995); + assert(nr.one_v > 1.4999981); + assert(nd.zero_v < 0.0000002); + assert(nr.zero_v < 0.0000001); + // the cross-check against ngspice's own run of the same cell (accept_gates.cir): the worst level is 2.36 uV (nand) and 1.04 uV (nor) from it, the bar is 10 uV, and a row is a + // verdict only when the cross-check holds, so a gap over the bar makes the row NOT MODELLED whatever the levels say + assert(nd.gap_v < GT_B_LEVEL_V); + assert(nr.gap_v < GT_B_LEVEL_V); + var yes : bool = true; + var no : bool = false; + assert_eq(as_verdict(no, as_chk_gate_levels(nd.one_v, nd.zero_v)), AS_NM); + assert_eq(as_verdict(yes, as_chk_gate_levels(nd.one_v, nd.zero_v)), AS_PASS); + // NEGATIVE CONTROLS on the model: a PMOS that never conducts (a zero table) leaves the 1 levels at 0 V, an NMOS that never conducts leaves the 0 level at 1.5 V; the report's check + // and the cross-check each refuse both + var dead : [4851]u32 = [0] ** 4851; + const np : Lv = gt_levels(&TT_LG_N, &dead, false); + const nn : Lv = gt_levels(&dead, &TT_LG_P, false); + assert(!as_chk_gate_levels(np.one_v, np.zero_v)); + assert(!as_chk_gate_levels(nn.one_v, nn.zero_v)); + assert(np.gap_v > GT_B_LEVEL_V); + assert(nn.gap_v > GT_B_LEVEL_V); const bn : Blk = as_blk_nand(); const br : Blk = as_blk_nor(); - assert_eq(bn.pass, 1); - assert_eq(bn.nm, 1); - assert_eq(br.pass, 1); - assert_eq(br.nm, 1); - assert_eq(as_block_verdict(bn), AS_NM); - assert_eq(as_block_verdict(br), AS_NM); + assert_eq(bn.pass, 2); + assert_eq(bn.fail, 0); + assert_eq(bn.nm, 0); + assert_eq(br.pass, 2); + assert_eq(br.fail, 0); + assert_eq(br.nm, 0); + assert_eq(as_block_verdict(bn), AS_PASS); + assert_eq(as_block_verdict(br), AS_PASS); } -test the_tgate_is_not_modelled_and_its_check_refuses_a_resistor_or_a_leak_out_of_range { +test the_tgate_leakage_is_a_row_and_its_two_resistances_are_not_modelled_because_the_tables_have_no_body_effect { assert(as_chk_tgate(R_TG_RON_OHM, R_TG_RON_WORST_OHM, R_TG_LEAK_NA)); var high_r : f64 = 60000.0; var high_w : f64 = 90000.0; @@ -862,13 +980,72 @@ test the_tgate_is_not_modelled_and_its_check_refuses_a_resistor_or_a_leak_out_of assert(!as_chk_tgate(high_r, R_TG_RON_WORST_OHM, R_TG_LEAK_NA)); assert(!as_chk_tgate(R_TG_RON_OHM, high_w, R_TG_LEAK_NA)); assert(!as_chk_tgate(R_TG_RON_OHM, R_TG_RON_WORST_OHM, leak)); + assert(!as_chk_tg_ron(high_r)); + assert(!as_chk_tg_worst(high_w)); + assert(!as_chk_tg_leak(leak)); + // THE TGATE on the tt log tables (mos_psp103_gates.t27, 10 mV across it, the output at 0.10 V to 1.40 V): Ron 1718.0 ohm at 0.75 V, 2109.3 ohm at the worst of the 27 points, 291.0 pA + // off at full swing; all three far inside the limits of the report (30 kohm, 50 kohm, 1 nA) + const t : Tg = as_tgate(&TT_LG_N, &TT_LG_P); + assert(t.ron > 1717.9); + assert(t.ron < 1718.1); + assert(t.worst > 2109.2); + assert(t.worst < 2109.4); + assert(t.leak_na > 0.2910); + assert(t.leak_na < 0.2911); + assert(as_chk_tg_ron(t.ron)); + assert(as_chk_tg_worst(t.worst)); + assert(as_chk_tg_leak(t.leak_na)); + // THE CROSS-CHECK against ngspice's run of the REAL cell (accept_tgate.cir, accept_gates.cir), the bar 10 percent: the leakage is 7.2 percent from it (1.9 percent from the larger of + // ngspice's two source currents), inside; the two resistances are 30.2 and 24.0 percent under it, outside by 3.0 and 2.4 times, and the rows are NOT MODELLED although both numbers are + // inside their limits: the tables have no body effect (mos_psp103_gates.t27 shows it, ngspice with each bulk tied to its source is 0.06 percent from ours) + assert(t.g_leak > 0.0724); + assert(t.g_leak < 0.0725); + assert(t.g_leak < GT_B_REL); + assert(t.g_ron > 0.3015); + assert(t.g_ron < 0.3016); + assert(t.g_worst > 0.2401); + assert(t.g_worst < 0.2402); + assert(t.g_ron > 3.0 * GT_B_REL); + assert(t.g_worst > 2.4 * GT_B_REL); + // NEGATIVE CONTROL on the leakage cross-check: the NMOS table in both slots leaks 521.5 pA, inside the report's 1 nA but 82.6 percent over ngspice's, so it is not a row + const two : Tg = as_tgate(&TT_LG_N, &TT_LG_N); + assert(as_chk_tg_leak(two.leak_na)); + assert(two.g_leak > 0.8); + assert(two.g_leak > 8.0 * GT_B_REL); const b : Blk = as_blk_tgate(); - assert_eq(b.pass, 0); + assert_eq(b.pass, 1); assert_eq(b.fail, 0); - assert_eq(b.nm, 3); + assert_eq(b.nm, 2); assert_eq(as_block_verdict(b), AS_NM); } +test the_reference_run_of_the_real_tgate_and_the_psp103_switch_reproduces_the_reports_own_numbers { + // THE POSITIVE CONTROL OF THE REFERENCE: the ngspice-47 runs of the cells (accept_tgate.cir, accept_gates.cir, accept_switch.cir) give what the external team's report measured, so + // the numbers our model is cross-checked against are the report's cells and not another circuit. Tgate: Ron at 0.75 V 2459.6 ohm (the report 2.44 kohm, 0.8 percent), the worst over + // 0.1 to 1.4 V 2776.1 ohm (2.78 kohm, 0.14 percent), the off leakage 0.2856 nA at the output source (0.29 nA, 1.5 percent) + assert(rel_gap(GT_NG_RTG[13], R_TG_RON_OHM) < 0.01); + assert(rel_gap(GT_NG_RTG[13], R_TG_RON_OHM) > 0.007); + assert(rel_gap(gt_ref_max(>_NG_RTG), R_TG_RON_WORST_OHM) < 0.002); + assert(rel_gap(GT_NG_LEAK_OUT_A * 1000000000.0, R_TG_LEAK_NA) < 0.02); + assert(as_chk_tgate(GT_NG_RTG[13], gt_ref_max(>_NG_RTG), GT_NG_LEAK_OUT_A * 1000000000.0)); + // The switch at the report's conditions (0.3 V and 1.2 V into 2 pF): the tracking error 0.35 uV (the report: under 1 uV), the hold step -1.596 and -1.730 mV (the report -1.6 and + // -1.7 mV, which it gives to two digits), the droop under 10 uV (the report: none); all inside the report's limits of 5 mV, 30 mV and 5 mV + assert(as_chk_sw_track(NG_SW_TE_B_LO_V, NG_SW_TE_B_HI_V)); + assert(NG_SW_TE_B_LO_V < 0.000001); + assert(NG_SW_TE_B_HI_V < 0.000001); + assert(fabs64(NG_SW_HOLD_B_LO_V - R_SW_HOLD_A_V) < 0.00005); + assert(fabs64(NG_SW_HOLD_B_HI_V - R_SW_HOLD_B_V) < 0.00005); + assert(as_chk_sw_hold(NG_SW_HOLD_B_LO_V, NG_SW_HOLD_B_HI_V)); + assert(as_chk_sw_droop(NG_SW_DROOP_BOUND_V)); + // at this file's conditions (0.2 V and 1.3 V into the DAC's 3.93 pF) the step is smaller, -0.81 and -0.89 mV: the charge injection splits over a larger capacitor + assert(NG_SW_HOLD_A_LO_V > NG_SW_HOLD_B_LO_V); + assert(NG_SW_HOLD_A_HI_V > NG_SW_HOLD_B_HI_V); + // and the model of this file has none of it: the square law returns 0.18 uV of step where ngspice's PSP103 cell returns -1.6 mV, which is why the two rows are NOT MODELLED + const mid : Sw = as_sw(1, SW_CB); + assert(fabs64(NG_SW_HOLD_B_LO_V) > 8000.0 * fabs64(mid.inj)); +} + + test the_dff_behaviour_and_clock_to_q_are_measured_at_gate_level_and_a_dff_with_set_and_reset_exchanged_fails_them { const r : SarRun = as_sar_run(false); const s : DffSr = as_dff_set_reset(false); @@ -981,11 +1158,27 @@ test the_tracking_error_is_measured_on_the_square_law_switch_and_the_hold_step_a assert(hi.te < 0.0 - 0.0000019); assert(hi.te > 0.0 - 0.0000021); assert(as_chk_sw_track(lo.te, hi.te)); - // NEGATIVE CONTROL on the model: no boost capacitor, the switch is barely on, the tracking error is 25.4 mV + // THE CROSS-CHECK against ngspice's PSP103 run of the same cell at the same conditions (accept_switch.cir: 0.2 V and 1.3 V into 3930.2 fF and the cell's 4 fF): the PSP103 cell + // tracks to +0.287 uV and -0.174 uV (a settling residue, either sign), the square-law model to -1.967 uV (the fixed-point floor), so the two are 2.25 and 1.79 uV apart; the bar + // is 10 uV, the limit 5 mV, and the row is a verdict because the two models agree on the DC tracking error to the microvolts + assert(NG_SW_TE_A_LO_V > 0.00000028); + assert(NG_SW_TE_A_LO_V < 0.00000029); + assert(NG_SW_TE_A_HI_V < 0.0 - 0.00000017); + assert(NG_SW_TE_A_HI_V > 0.0 - 0.00000018); + const d_lo : f64 = lo.te - NG_SW_TE_A_LO_V; + const d_hi : f64 = hi.te - NG_SW_TE_A_HI_V; + assert(d_lo < 0.0 - 0.00000225); + assert(d_lo > 0.0 - 0.00000226); + assert(d_hi < 0.0 - 0.00000179); + assert(d_hi > 0.0 - 0.00000180); + assert(as_within(d_lo, GT_B_LEVEL_V)); + assert(as_within(d_hi, GT_B_LEVEL_V)); + // NEGATIVE CONTROL on the model: no boost capacitor, the switch is barely on, the tracking error is 25.4 mV, which the limit and the cross-check each refuse const nocb : Sw = as_sw(1, 0); assert(nocb.te < 0.0 - 0.025); assert(nocb.te > 0.0 - 0.026); assert(!as_chk_sw_track(nocb.te, nocb.te)); + assert(!as_within(nocb.te - NG_SW_TE_A_LO_V, GT_B_LEVEL_V)); // the check accepts a tracking error of 1 uV (the report's "< 0.001 mV") and refuses 6 mV on either side var one_uv : f64 = 0.000001; var six : f64 = 0.006; @@ -1215,11 +1408,11 @@ test the_corners_smoke_suite_is_not_modelled_and_its_checks_refuse_a_delay_out_o assert_eq(as_nm(), AS_NM); } -test the_thirteen_blocks_count_3_pass_1_fail_and_9_not_modelled { +test the_thirteen_blocks_count_5_pass_1_fail_and_7_not_modelled_and_their_30_rows_19_pass_1_fail_and_10_not_modelled { // the blocks, one by one, each by the strict rule assert_eq(as_block_verdict(as_blk_inverter()), AS_NM); - assert_eq(as_block_verdict(as_blk_nand()), AS_NM); - assert_eq(as_block_verdict(as_blk_nor()), AS_NM); + assert_eq(as_block_verdict(as_blk_nand()), AS_PASS); + assert_eq(as_block_verdict(as_blk_nor()), AS_PASS); assert_eq(as_block_verdict(as_blk_tgate()), AS_NM); assert_eq(as_block_verdict(as_blk_dff()), AS_NM); assert_eq(as_block_verdict(as_blk_comparator()), AS_FAIL); @@ -1232,8 +1425,14 @@ test the_thirteen_blocks_count_3_pass_1_fail_and_9_not_modelled { 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, 3); + assert_eq(t.pass, 5); assert_eq(t.fail, 1); - assert_eq(t.nm, 9); + assert_eq(t.nm, 7); 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 19 PASS, 1 FAIL and 10 NOT MODELLED + const r : Blk = as_all_rows(); + assert_eq(r.pass, 19); + assert_eq(r.fail, 1); + assert_eq(r.nm, 10); + assert_eq(r.pass + r.fail + r.nm, AS_N_ROWS); } diff --git a/specs/analog/decks/accept_gates.cir b/specs/analog/decks/accept_gates.cir new file mode 100644 index 0000000000..37130cd1b0 --- /dev/null +++ b/specs/analog/decks/accept_gates.cir @@ -0,0 +1,78 @@ +IHP SG13G2 two-input gates and the transmission gate off, DC: output levels and off leakage, corner mos_tt, 27 C +* specs/analog/decks/accept_gates.cir -- reference data for specs/analog/mos_psp103_gates.t27 and its use in specs/analog/accept_sar.t27 (M5d, #11263, epic #10486). +* A netlist plus a .control block that prints the numbers. The cells are the nand, the nor and the tgate of the golden SAR ADC netlist, with the W, L and m of their devices +* (nmos 1.0 um / 0.45 um, pmos 3.0 um / 0.45 um, ng 1, m 1), which are facts of the circuit; none of its text is copied. The model is the IHP SG13G2 PSP103 of +* IHP-Open-PDK 21133f21, section mos_tt, 27 C, the one of the tables in mos_psp103_data.t27. Paths are NOT in this file: ngspice reads IHP_PDK and IHP_OSDI from the environment. +* Run: IHP_PDK= IHP_OSDI= ngspice -b accept_gates.cir (ngspice-47) +* What it prints (volts and amperes, 12 digits): +* dnd00 dnd01 dnd10 dnr00 the 1 levels as the droop under the supply, 1.5 V minus the output, of the nand for the inputs A B = 00, 01, 10 and of the nor for 00 +* v(nd11) v(nr01) v(nr10) v(nr11) the 0 levels, the output itself, of the nand for 11 and of the nor for 01, 10, 11 +* The levels are the DC solution with no load, which is the level a gate settles to into 10 fF. +* i(vli1) i(vlo1) the tgate off (pmos gate 1.5 V, nmos gate 0 V) with the input at 1.5 V and the output at 0 V: the current of the input source and of the output source (the +* i(vli2) i(vlo2) same with the input at 0 V and the output at 1.5 V: the two roles exchanged, so the same two numbers); the two differ by 14 pA, the size of +* a junction leakage, which sits in different terminals of the two sources; the terms are not separated here. +* Solver options: reltol 1e-9, vntol 1e-12, abstol 1e-15, gmin 1e-18 (no leak is added by the simulator), itl1 500: the levels are microvolts and the leakage picoamperes. At gmin 1e-18 +* the operating point is reached by source stepping (ngspice says so in its log), which is the solver working, not a failure of the run. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.subckt nand2 a b vdd out vss +XM1 n1 b vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM2 out a n1 vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM3 out a vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM4 out b vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +.ends +.subckt nor2 a b vdd out vss +XM1 out a vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM2 out b vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM3 out b n1 vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM4 n1 a vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +.ends +.subckt tgate clk nclk in out vdd vss +XM1 out clk in vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM2 in nclk out vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +.ends +VDD vdd 0 1.5 +V0 l0 0 0 +V1 l1 0 1.5 +Xnd00 l0 l0 vdd nd00 0 nand2 +Xnd01 l0 l1 vdd nd01 0 nand2 +Xnd10 l1 l0 vdd nd10 0 nand2 +Xnd11 l1 l1 vdd nd11 0 nand2 +Xnr00 l0 l0 vdd nr00 0 nor2 +Xnr01 l0 l1 vdd nr01 0 nor2 +Xnr10 l1 l0 vdd nr10 0 nor2 +Xnr11 l1 l1 vdd nr11 0 nor2 +Cnd00 nd00 0 10f +Cnd01 nd01 0 10f +Cnd10 nd10 0 10f +Cnd11 nd11 0 10f +Cnr00 nr00 0 10f +Cnr01 nr01 0 10f +Cnr10 nr10 0 10f +Cnr11 nr11 0 10f +* the tgate off, full swing both ways +VLI1 li1 0 1.5 +VLO1 lo1 0 0 +Xoff1 l1 l0 li1 lo1 vdd 0 tgate +VLI2 li2 0 0 +VLO2 lo2 0 1.5 +Xoff2 l1 l0 li2 lo2 vdd 0 tgate +.options temp=27 reltol=1e-9 vntol=1e-12 abstol=1e-15 gmin=1e-18 itl1=500 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set num_threads=2 +set numdgt=12 +op +let dnd00 = 1.5 - v(nd00) +let dnd01 = 1.5 - v(nd01) +let dnd10 = 1.5 - v(nd10) +let dnr00 = 1.5 - v(nr00) +echo BEGIN gates +print dnd00 dnd01 dnd10 +print v(nd11) +print dnr00 +print v(nr01) v(nr10) v(nr11) +print i(vli1) i(vlo1) i(vli2) i(vlo2) +echo END gates +.endc +.end diff --git a/specs/analog/decks/accept_switch.cir b/specs/analog/decks/accept_switch.cir new file mode 100644 index 0000000000..0ecfe3b443 --- /dev/null +++ b/specs/analog/decks/accept_switch.cir @@ -0,0 +1,89 @@ +IHP SG13G2 PSP103 bootstrapped tracking switch, DC input held through one clock pulse: tracking error, hold step, droop, corner mos_tt, 27 C +* specs/analog/decks/accept_switch.cir -- reference data for specs/analog/accept_sar.t27 (M5d, #11263, epic #10486). The tracking_switch cell of the golden SAR ADC netlist, ten +* transistors with the W, L and m of its devices (nmos 1.0 um / 0.45 um, pmos 3.0 um / 0.45 um, ng 1, m 1 -- facts of the circuit, none of its text), the inverter that makes +* the complement of the clock, the boost capacitor of 603.2 fF (cap_cmim 20 um x 20 um at the IHP PDK 21133f21: C = CJ L W + 2 CJSW (L + W), see sar_switch.t27) and the cell's seven +* 4 fF node capacitors, on the PSP103 of the IHP SG13G2 PDK, section mos_tt, 27 C. specs/analog/sar_switch.t27 is the SAME cell with the SQUARE LAW (MOSFET level 1); here it is +* the foundry's model, to see what the square law leaves out. Paths are NOT in this file: ngspice reads IHP_PDK and IHP_OSDI from the environment. +* Run: IHP_PDK= IHP_OSDI= ngspice -b accept_switch.cir (ngspice-47) +* The run: the clock rises over 2 ns, stays at 1.5 V for 80 ns (the track phase) and falls over 2 ns; the period of the golden deck is 500 ns, so the hold lasts to 500 ns. Four copies of the +* cell share the clock, each with its own input held at a constant: 0.3 V and 1.2 V into 2 pF (the conditions of the external team's report of 2026-10-16: te_b1, te_b2) and 0.2 V and +* 1.3 V into 3930.2 fF (the conditions of sar_switch.t27: the DAC's top plate; te_a1, te_a2). The output load is the 4 fF of the cell plus the load named here. Each output starts 0.1 V under its input. +* What it prints (.meas, 6 digits; the tracking error is read as a difference so that it keeps its digits): +* te_xx V(out) - V(in) at 81.9 ns, the end of the track phase (the clock falls from 82 ns) +* vo_f_xx V(out) at 81.9 ns; vo_h_xx at 86 ns, two ns after the fall ends: the hold step is vo_h - vo_f; vo_e_xx at 104 ns: the 20 ns droop of sar_switch.t27 is vo_e - vo_h; +* vo_z_xx at 499 ns: the droop over the whole hold is vo_z - vo_h. +* Solver options: reltol 1e-6, vntol 1e-9, abstol 1e-13, gmin 1e-18 (no leak is added by the simulator), a step of at most 100 ps. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.subckt inv vin vdd vss vout +XMP vout vin vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XMN vout vin vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +.ends +.subckt trk vdd vss vin vout clks +XM1 net1 clks vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM2 net1 clks net2 vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM3 net1 net3 net2 vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM4 net2 n_clks vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM5 vin net3 net2 vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM6 vout net3 vin vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM7 vdd net3 net4 net4 sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM8 net3 vdd net5 vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM9 net5 n_clks vss vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +XM10 net3 net1 net4 net4 sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +x1 clks vdd vss n_clks inv +C3 net2 net4 603.2f +C1 net4 0 4f +C2 vout 0 4f +C4 net1 0 4f +C6 net3 0 4f +C7 net5 0 4f +C8 net3 0 4f +C5 net2 0 4f +.ends +VDD vdd 0 1.5 +VCLK clks 0 PULSE(0 1.5 0 2n 2n 80n 500n) +Via1 ina1 0 0.2 +Via2 ina2 0 1.3 +Vib1 inb1 0 0.3 +Vib2 inb2 0 1.2 +Xa1 vdd 0 ina1 outa1 clks trk +Xa2 vdd 0 ina2 outa2 clks trk +Xb1 vdd 0 inb1 outb1 clks trk +Xb2 vdd 0 inb2 outb2 clks trk +CLa1 outa1 0 3930.2f +CLa2 outa2 0 3930.2f +CLb1 outb1 0 2p +CLb2 outb2 0 2p +Bta1 tea1 0 V = v(outa1) - v(ina1) +Bta2 tea2 0 V = v(outa2) - v(ina2) +Btb1 teb1 0 V = v(outb1) - v(inb1) +Btb2 teb2 0 V = v(outb2) - v(inb2) +.ic v(outa1)=0.1 v(outa2)=1.2 v(outb1)=0.2 v(outb2)=1.1 +.options temp=27 reltol=1e-6 vntol=1e-9 abstol=1e-13 gmin=1e-18 itl4=500 +.tran 100p 500n 0 100p +.meas tran te_a1 find v(tea1) at=81.9n +.meas tran te_a2 find v(tea2) at=81.9n +.meas tran te_b1 find v(teb1) at=81.9n +.meas tran te_b2 find v(teb2) at=81.9n +.meas tran vo_f_a1 find v(outa1) at=81.9n +.meas tran vo_h_a1 find v(outa1) at=86n +.meas tran vo_e_a1 find v(outa1) at=104n +.meas tran vo_z_a1 find v(outa1) at=499n +.meas tran vo_f_a2 find v(outa2) at=81.9n +.meas tran vo_h_a2 find v(outa2) at=86n +.meas tran vo_e_a2 find v(outa2) at=104n +.meas tran vo_z_a2 find v(outa2) at=499n +.meas tran vo_f_b1 find v(outb1) at=81.9n +.meas tran vo_h_b1 find v(outb1) at=86n +.meas tran vo_e_b1 find v(outb1) at=104n +.meas tran vo_z_b1 find v(outb1) at=499n +.meas tran vo_f_b2 find v(outb2) at=81.9n +.meas tran vo_h_b2 find v(outb2) at=86n +.meas tran vo_e_b2 find v(outb2) at=104n +.meas tran vo_z_b2 find v(outb2) at=499n +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set num_threads=2 +run +.endc +.end diff --git a/specs/analog/decks/accept_tgate.cir b/specs/analog/decks/accept_tgate.cir new file mode 100644 index 0000000000..9e78729100 --- /dev/null +++ b/specs/analog/decks/accept_tgate.cir @@ -0,0 +1,44 @@ +IHP SG13G2 transmission gate conducting, DC: resistance over the output level, corner mos_tt, 27 C +* specs/analog/decks/accept_tgate.cir -- reference data for specs/analog/mos_psp103_gates.t27 and its use in specs/analog/accept_sar.t27 (M5d, #11263, epic #10486). +* The tgate 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, +* none of its text) conducting: pmos gate 0 V, nmos gate 1.5 V. The input is held 10 mV above the output through a behavioural source whose branch current is the tgate's +* current, the output is swept from 0.10 V to 1.40 V in steps of 50 mV (27 points), and R = 10 mV / I, the measurement of sar_off_ron.cir at 27 C. Model: IHP SG13G2 PSP103 of +* IHP-Open-PDK 21133f21, section mos_tt. Paths are NOT in this file: ngspice reads IHP_PDK and IHP_OSDI from the environment. +* Run: IHP_PDK= IHP_OSDI= ngspice -b accept_tgate.cir (ngspice-47) +* What it prints (ohm, 12 digits), between BEGIN and END: +* rtg the real cell: the nmos source is the output, its bulk is on ground, so its source-to-bulk voltage is the output level; the pmos source is the input, its bulk on the supply, +* so its source-to-bulk voltage is 1.5 V minus the input. The BODY EFFECT is in it. +* rnb the same two devices with each bulk tied to its own source (the nmos bulk to the output, the pmos bulk to the input): the condition the current tables of +* mos_psp103_data.t27 were made under (source and bulk on the same rail), so what the table model should reproduce if the body effect is what separates it from rtg. +* Solver options: reltol 1e-9, vntol 1e-12, abstol 1e-15; the simulator's default gmin (1e-12 S: 1e-7 of a 7 uA current), itl1 500. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.subckt tgate clk nclk in out vdd vss +XM1 out clk in vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM2 in nclk out vss sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +.ends +.subckt tgate_nb clk nclk in out +XM1 out clk in in sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XM2 in nclk out out sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +.ends +VDD vdd 0 1.5 +V0 l0 0 0 +V1 l1 0 1.5 +VOUT tout 0 0.1 +BIN tin 0 V = v(tout) + 0.01 +Xon l0 l1 tin tout vdd 0 tgate +BIN2 tin2 0 V = v(tout) + 0.01 +Xnb l0 l1 tin2 tout tgate_nb +.options temp=27 reltol=1e-9 vntol=1e-12 abstol=1e-15 itl1=500 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set num_threads=2 +set numdgt=12 +dc vout 0.1 1.4 0.05 +let rtg = 0.01 / abs(i(bin)) +let rnb = 0.01 / abs(i(bin2)) +echo BEGIN ron +print rtg rnb +echo END ron +.endc +.end diff --git a/specs/analog/mos_psp103_gates.t27 b/specs/analog/mos_psp103_gates.t27 new file mode 100644 index 0000000000..92a1e8c6c5 --- /dev/null +++ b/specs/analog/mos_psp103_gates.t27 @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/mos_psp103_gates.t27 -- the two-input gates and the transmission gate of the SAR ADC on the PSP103 log tables, judged by ngspice +; M5d of the analog emulation epic (#10486, issue #11263). specs/analog/mos_psp103.t27 solves ONE CMOS inverter on the tt tables of the NMOS (W 1.0 um) and the PMOS (W 3.0 um), L 0.45 um. The +; nand, the nor and the tgate of the integrated converter's golden netlist are made of the same two devices, with the same W, L and m (nand: two NMOS in series and two PMOS in parallel; +; nor: two NMOS in parallel and two PMOS in series; tgate: one PMOS and one NMOS in parallel between the input and the output; the sizes are facts of the circuit, none of its text is +; copied), so no new table is needed: this file reads the tt LOG tables TT_LG_N and TT_LG_P (M3c-2, weak inversion included) with the integer reader `lg_fine` of mos_table.t27, which +; returns a current in IUNIT = 1.164 pA, and builds three things on it: the DC output of a two-input gate, the tgate's resistance and the tgate's off leakage. Nothing is copied from the +; inverter: its bisection (`dc_out`) is the one-device case of `gt_nand_y` below, where the stack's middle node is solved inside it. +; - +; THE GATE. `gt_nand_y(ts, tq, a, b)` is the DC output of a series stack of two devices of table ts (gates a and b, bottom source on the rail the output is measured from) against a +; parallel pair of table tq (gates VDD - a and VDD - b from the other rail): the largest y at which the stack's current does not exceed the pair's, by bisection on integer reads (the +; stack's current rises with y, the pair's falls), and for each y the middle node of the stack, where the bottom device's current equals the top device's, by a second bisection +; (`gt_mid`: the bottom current rises with the node and the top current falls, so there is one root). The nand is that call with the NMOS as the stack. The nor is the same call seen +; from the supply: the PMOS as the stack, the inputs mirrored (VDD - a), the output taken from the rail. A gate whose gate sits below its source reads the table at 0 (the tables +; stop at 0 V). The voltages are 2^-24 V words, the output resolution is 1 word = 59.6 nV, the currents are IUNIT words. +; THE TGATE. `gt_tg_i`: conducting (PMOS gate 0 V, NMOS gate Vdd), the input 10 mV above the output vout: the NMOS reads (Vdd - vout, 10 mV), its source being the output, the PMOS +; reads (vin, 10 mV), its source being the input, and the current is the sum. The resistance is 10 mV over it, at the 27 outputs 0.10 V, 0.15 V, .. 1.40 V (`gt_tg_ron`, `gt_tg_worst`; +; sar_off_ron.cir measures the golden tgate the same way). Off (PMOS gate Vdd, NMOS gate 0 V), the input at Vdd and the output at 0 V: both devices at Vgs = 0, Vds = Vdd, and the +; leakage is the sum of their two table words (`gt_tg_off`). +; - +; THE REFERENCE. ngspice-47 with the IHP SG13G2 PSP103 of IHP-Open-PDK 21133f21, mos_tt, 27 C, on the reference host (a container, 2 threads), 2026-10-20, three decks of +; specs/analog/decks: accept_gates.cir (the eight gate outputs, the off tgate), accept_tgate.cir (the conducting tgate, two cells), accept_switch.cir (used by accept_sar.t27). +; The levels are the DC solution with no load, which is what a gate settles to into 10 fF. The numbers are the GT_NG_* constants below, none changed after printing. +; - +; THE RESULT, our model against that run. +; levels the distance of each output from its ideal level (1.5 V minus a 1, a 0 itself), nand A B = 00, 01, 10, 11 / nor 00, 01, 10, 11, in uV: +; nand ours 0.179 0.477 0.477 0.179 ngspice 0.316 0.721 0.687 2.537 worst gap 2.358 uV (nand 11) +; nor ours 1.848 0.060 0.060 0.060 ngspice 2.883 0.634 0.617 0.611 worst gap 1.036 uV (nor 00) +; Every level of both gates is within 3 uV of 1.5 V or 0 V: the limit of the external team's report is 50 mV. The bar of the cross-check is 10 uV (GT_B_LEVEL_V), set at +; four times the worst gap: it says the model and ngspice put the output at the same microvolts, and the 50 mV verdicts do not depend on it. +; leakage the tgate off: ours 291.0 pA (NMOS 261 pA + PMOS 30 pA of the table words at Vgs = 0, Vds = 1.5 V); ngspice 285.6 pA at the output source and 271.4 pA at the input source +; (the two differ by 14 pA, the size of a junction leakage that sits in different terminals of the two sources; the terms are not separated here): +1.9 % and +7.2 %. The bar is 10 % (GT_B_REL). +; Ron ours 1718.0 ohm at 0.75 V and 2109.3 ohm at the worst of the 27 points; ngspice's REAL cell 2459.6 ohm and 2776.1 ohm: ours is 30.2 % and 24.0 % below. THE TABLES HAVE NO BODY +; EFFECT, and the real cell has bulks on the rails: while the tgate conducts at 0.75 V the NMOS source is 0.75 V above its bulk and the PMOS source 0.75 V below its bulk, which +; raises both thresholds. That is not an assumption: accept_tgate.cir also runs the same two devices with each bulk tied to its own source (the condition the tables were made +; under), and there our model is within 0.06 % of ngspice at all 27 points (the largest gap 0.0588 %) (ngspice 1717.2 ohm at 0.75 V, 2108.4 ohm at the worst). The bar of 10 % refuses the real cell by +; 3 times at 0.75 V and 2.4 times at the worst point, so the tgate's Ron rows of accept_sar.t27 are NOT MODELLED, with our numbers and ngspice's shown. +; the external team's report has 2.44 kohm at 0.75 V, 2.78 kohm worst and 0.29 nA: ngspice's real cell gives 2.46 kohm (0.8 % over), 2.78 kohm (0.1 % under) and 0.286 nA, so the +; reference run reproduces the report; the report's numbers are the report's, ours are the functions here. +; - +; THE NEGATIVE CONTROLS (FR-004), each a test that passes by seeing the failure. The nand with the PMOS table zeroed never rises (its 1 levels are under 0.4 mV, 1.5 V off); with the NMOS +; zeroed its 11 output stays at 1.5 V; the nand's output read where the nor has a 0 is 1.5 V off. The tgate's leakage with the NMOS table in both slots is 521.5 pA, 82.6 % over ngspice's, and +; with the NMOS zeroed 30.3 pA, 89 % under; its Ron at 0.75 V with the PMOS zeroed is 2895 ohm and with the NMOS zeroed 4225 ohm, 69 % and 146 % over the no-body-effect cell: the bar of 10 % +; refuses each. THE LIMIT OF THE LEVEL CONTROL: the two tables in each other's slots (a 3 um stack and a 1 um pair) leave every level within 0.69 uV (nand) and 2.7 uV (nor) of ngspice's, +; inside the bar, because a level is the leakage of the off side times the resistance of the on side and both are microvolts for either width: a level check does not see a device's +; width, and a test pins that (the leakage and the resistance see it). +; - +; WHAT A PASS SAYS. The tt log tables of the two devices, read by the integer reader and solved for the nand, nor and tgate cells of the golden netlist, give the gates' output levels +; within 2.4 uV of ngspice's, and the tgate's off leakage within 7.2 % of it, at 27 C with no load. WHAT IT DOES NOT SAY. The Ron of the real cell: it is 30 % off, for the body effect +; (the tables have no source-to-bulk voltage, and no test here pretends otherwise: the no-body-effect run agrees to 0.06 % and the real cell does not). Nothing about delay or a transient +; into 10 fF (the DC level is what the gate settles to), another corner or temperature (tt, 27 C only), mismatch, noise or power; the leakage is one bias (full swing) and its two junction +; terms are not separated. The model is a TABLE MODEL of PSP103, not PSP103. ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogMosPsp103Gates; + +use analog::mos_table::{VDD, lg_fine, dpos}; +use analog::mos_psp103::{UPV, IUNIT_A}; +use analog::mos_psp103_data::{TT_LG_N, TT_LG_P}; + +pub const KIND : str = "analog-cell-check"; +pub const ID : str = "analog/mos_psp103_gates"; +pub const REFERENCE : str = "IHP-Open-PDK 21133f21 sg13_lv_nmos/pmos (PSP103) through ngspice-47; decks specs/analog/decks/accept_gates.cir, accept_tgate.cir"; + +/// 10 mV across the tgate in units of 2^-24 V (167772.16, rounded down): the small signal of the Ron measurement. +pub const GT_DV : u32 = 167772; +/// The Ron sweep: the output at 0.10 V + 0.05 V k, k = 0 .. 26, so 0.10 V to 1.40 V; the input is 10 mV above it. k = 13 is 0.75 V. +pub const GT_NV : u32 = 27; +/// The cross-check bar for a level: ours and ngspice's output within 10 uV of each other. +pub const GT_B_LEVEL_V : f64 = 0.00001; +/// The cross-check bar for a resistance or a current: 10 percent of ngspice's. +pub const GT_B_REL : f64 = 0.10; + +; --- ngspice-47 + PSP103 mos_tt 27 C, the decks of specs/analog/decks (printed to 12 digits, none changed) ---------------------------------- +/// accept_gates.cir: the nand's output distance from its ideal level in V, inputs A B = 00, 01, 10, 11 (the 1 levels as 1.5 V minus the output, the 0 level as the output). +pub const GT_NG_NAND : [4]f64 = [3.164692066182e-07, 7.208471957121e-07, 6.868970867657e-07, 2.537007301171e-06]; +/// The nor's, inputs 00, 01, 10, 11 (the 1 level for 00 as 1.5 V minus the output). +pub const GT_NG_NOR : [4]f64 = [2.883448431801e-06, 6.342530482189e-07, 6.169936628426e-07, 6.113602654695e-07]; +/// The tgate off, input 1.5 V and output 0 V: the current of the output source and of the input source in A. +pub const GT_NG_LEAK_OUT_A : f64 = 2.855936198881e-10; +pub const GT_NG_LEAK_IN_A : f64 = 2.71379799196e-10; +/// accept_tgate.cir: R = 10 mV / I of the REAL tgate (bulks on the rails), output 0.10 V + 0.05 V k, in ohm. +pub const GT_NG_RTG : [27]f64 = [ + 1.385850169689e+03, 1.436940183067e+03, 1.495475607876e+03, 1.562989802067e+03, 1.641428551801e+03, 1.733192207340e+03, 1.840556415694e+03, + 1.961389669307e+03, 2.078911578725e+03, 2.175210690376e+03, 2.253511737344e+03, 2.323073375943e+03, 2.390489741297e+03, 2.459620200341e+03, + 2.531972294128e+03, 2.606525281963e+03, 2.679011255823e+03, 2.740623935101e+03, 2.776143506080e+03, 2.762403492626e+03, 2.676178974948e+03, + 2.531594339737e+03, 2.378836180061e+03, 2.242112927391e+03, 2.123359914762e+03, 2.019956253274e+03, 1.929212699950e+03 +]; +/// The same two devices with each bulk tied to its own source (the condition of the current tables). +pub const GT_NG_RNB : [27]f64 = [ + 1.365215261225e+03, 1.403458829789e+03, 1.446658843102e+03, 1.494710454293e+03, 1.544561854305e+03, 1.587480372693e+03, 1.616151649472e+03, + 1.632407253458e+03, 1.641784068477e+03, 1.649200796891e+03, 1.658285597710e+03, 1.671581138101e+03, 1.690831976540e+03, 1.717208511895e+03, + 1.751432178725e+03, 1.793786279514e+03, 1.843992527380e+03, 1.900918901949e+03, 1.962070717373e+03, 2.022824967882e+03, 2.075454403590e+03, + 2.108373119070e+03, 2.107837939555e+03, 2.068816154170e+03, 2.007537559864e+03, 1.943503954402e+03, 1.884071056350e+03 +]; + +; --- the gate ---------------------------------------------------------------------------------------------------- +/// a - m, or 0 when m is larger: a device whose gate sits below its source reads the table at Vgs = 0 (the tables stop at 0). +pub fn gt_sub(a: u32, m: u32) -> u32 { + return dpos(a as u64, m as u64) as u32; +} + +/// The node between the two devices of a series stack of the table ts, the top one with gate a and its drain at y, the bottom one with gate b and its source on the rail: the +/// largest m at which the bottom device's current does not exceed the top's. The bottom current rises with m and the top current falls, so the bisection has one root. +pub fn gt_mid(ts: []const u32, a: u32, b: u32, y: u32) -> u32 { + var lo : u32 = 0; + var hi : u32 = y; + while (hi - lo > 1) { + const m : u32 = lo + ((hi - lo) >> 1); + if (lg_fine(ts, b, m) > lg_fine(ts, gt_sub(a, m), y - m)) { hi = m; } else { lo = m; } + } + return lo; +} + +/// The DC output of a two-input gate whose series stack is made of the table ts (gates a and b, source on the rail the output is measured from) and whose parallel pair is made +/// of the table tq (gates VDD - a and VDD - b from the other rail): the largest y at which the stack's current does not exceed the pair's (the inverter's bisection in +/// `dc_out`, with the stack's middle node solved inside it). Currents in IUNIT = 1.164 pA, voltages in units of 2^-24 V. +pub fn gt_nand_y(ts: []const u32, tq: []const u32, a: u32, b: u32) -> u32 { + var lo : u32 = 0; + var hi : u32 = VDD; + while (hi - lo > 1) { + const y : u32 = lo + ((hi - lo) >> 1); + const m : u32 = gt_mid(ts, a, b, y); + const i_s : u64 = lg_fine(ts, b, m); + const i_q : u64 = lg_fine(tq, VDD - a, VDD - y) + lg_fine(tq, VDD - b, VDD - y); + if (i_s > i_q) { hi = y; } else { lo = y; } + } + return lo; +} + +/// The nand: two NMOS in series to ground (the stack is the NMOS table), two PMOS in parallel to the supply. Output in units of 2^-24 V. +pub fn gt_nand(tn: []const u32, tp: []const u32, a: u32, b: u32) -> u32 { + return gt_nand_y(tn, tp, a, b); +} + +/// The nor, the nand seen from the supply: two PMOS in series to the supply (the stack is the PMOS table, its inputs VDD - a and VDD - b), two NMOS in parallel to ground. The +/// output is the supply minus the distance the nand-shaped call finds below it. +pub fn gt_nor(tn: []const u32, tp: []const u32, a: u32, b: u32) -> u32 { + return VDD - gt_nand_y(tp, tn, VDD - a, VDD - b); +} + +/// The eight levels, the nand or the nor for the inputs A B = 00, 01, 10, 11 (code k = 2 A + B, a high input is VDD): one_v the lowest output of a logic 1 (1.5 V is ideal), zero_v +/// the highest output of a logic 0 (0 V is ideal), and gap_v the largest distance of a level from ngspice's (GT_NG_NAND or GT_NG_NOR), all in V. +pub struct Lv { one_v: f64, zero_v: f64, gap_v: f64 } + +pub fn gt_levels(tn: []const u32, tp: []const u32, nor: bool) -> Lv { + var one : f64 = 1.5; + var zero : f64 = 0.0; + var gap : f64 = 0.0; + var k : u32 = 0; + while (k < 4) { + const a : u32 = (k / 2) * VDD; + const b : u32 = (k % 2) * VDD; + var out : u32 = 0; + var is_one : bool = false; + var want : f64 = 0.0; + if (nor) { + out = gt_nor(tn, tp, a, b); + if (k == 0) { is_one = true; } + want = GT_NG_NOR[k as usize]; + } else { + out = gt_nand(tn, tp, a, b); + if (k != 3) { is_one = true; } + want = GT_NG_NAND[k as usize]; + } + const v : f64 = (out as f64) / UPV; + var dev : f64 = v; + if (is_one) { + dev = 1.5 - v; + if (v < one) { one = v; } + } else { + if (v > zero) { zero = v; } + } + const g : f64 = dev - want; + if (g > gap) { gap = g; } + if (0.0 - g > gap) { gap = 0.0 - g; } + k = k + 1; + } + return Lv { one_v: one, zero_v: zero, gap_v: gap }; +} + +; --- the tgate ----------------------------------------------------------------------------------------------------- +/// The tgate conducting (PMOS gate 0 V, NMOS gate Vdd): the current from an input dv above the output vout to the output, in IUNIT. The NMOS source is the output, the PMOS +/// source the input: the NMOS reads (Vdd - vout, dv), the PMOS (vin, dv). +pub fn gt_tg_i(tn: []const u32, tp: []const u32, vout: u32, dv: u32) -> u64 { + return lg_fine(tn, VDD - vout, dv) + lg_fine(tp, vout + dv, dv); +} + +/// The output of the sweep point k of the Ron sweep, in units of 2^-24 V. +pub fn gt_tg_v(k: u32) -> u32 { + return ((0.1 + 0.05 * (k as f64)) * UPV + 0.5) as u32; +} + +/// The tgate's resistance in ohm at output vout: the voltage across it over the current. +pub fn gt_tg_ron(tn: []const u32, tp: []const u32, vout: u32) -> f64 { + const i : f64 = (gt_tg_i(tn, tp, vout, GT_DV) as f64) * IUNIT_A; + return ((GT_DV as f64) / UPV) / i; +} + +/// The largest resistance over the 27 points, 0.10 V to 1.40 V. +pub fn gt_tg_worst(tn: []const u32, tp: []const u32) -> f64 { + var w : f64 = 0.0; + var k : u32 = 0; + while (k < GT_NV) { + const r : f64 = gt_tg_ron(tn, tp, gt_tg_v(k)); + if (r > w) { w = r; } + k = k + 1; + } + return w; +} + +/// The largest relative distance |ours - ngspice| / ngspice of the resistance from a 27-point reference (GT_NG_RTG or GT_NG_RNB). +pub fn gt_ron_gap(tn: []const u32, tp: []const u32, refs: []const f64) -> f64 { + var w : f64 = 0.0; + var k : u32 = 0; + while (k < GT_NV) { + const want : f64 = refs[k as usize]; + var g : f64 = (gt_tg_ron(tn, tp, gt_tg_v(k)) - want) / want; + if (g < 0.0) { g = 0.0 - g; } + if (g > w) { w = g; } + k = k + 1; + } + return w; +} + +/// The largest of a 27-point reference. +pub fn gt_ref_max(refs: []const f64) -> f64 { + var w : f64 = 0.0; + var k : u32 = 0; + while (k < GT_NV) { + if (refs[k as usize] > w) { w = refs[k as usize]; } + k = k + 1; + } + return w; +} + +/// The tgate off (PMOS gate Vdd, NMOS gate 0 V), the input at Vdd and the output at 0 V: both devices at Vgs = 0 and Vds = Vdd, and the current is the sum of their table words, in A. +pub fn gt_tg_off(tn: []const u32, tp: []const u32) -> f64 { + return ((lg_fine(tn, 0, VDD) + lg_fine(tp, 0, VDD)) as f64) * IUNIT_A; +} + +; --- 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_nand_levels_are_microvolts_from_the_rails_the_solver_brackets_its_roots_and_every_level_is_within_3_uV_of_ngspice { + var a0 : u32 = 0; + var a1 : u32 = VDD; + // the words (59.6 nV): the 1 levels (inputs 00, 01, 10) are 3, 8 and 8 words under the supply, the 0 level (11) is 3 words over ground + assert_eq(VDD - gt_nand(&TT_LG_N, &TT_LG_P, a0, a0), 3); + assert_eq(VDD - gt_nand(&TT_LG_N, &TT_LG_P, a0, a1), 8); + assert_eq(VDD - gt_nand(&TT_LG_N, &TT_LG_P, a1, a0), 8); + assert_eq(gt_nand(&TT_LG_N, &TT_LG_P, a1, a1), 3); + // the root is bracketed: at the output y the stack's current does not exceed the pair's, one word higher it does (inputs 01: the top device off, the bottom on) + const y : u32 = gt_nand_y(&TT_LG_N, &TT_LG_P, a0, a1); + const m0 : u32 = gt_mid(&TT_LG_N, a0, a1, y); + const m1 : u32 = gt_mid(&TT_LG_N, a0, a1, y + 1); + const pair0 : u64 = lg_fine(&TT_LG_P, VDD - a0, VDD - y) + lg_fine(&TT_LG_P, VDD - a1, VDD - y); + const pair1 : u64 = lg_fine(&TT_LG_P, VDD - a0, VDD - y - 1) + lg_fine(&TT_LG_P, VDD - a1, VDD - y - 1); + assert(lg_fine(&TT_LG_N, a1, m0) <= pair0); + assert(lg_fine(&TT_LG_N, a1, m1) > pair1); + // and the middle node of the stack with the top device on and the bottom off (inputs 10): it rises to where the bottom's leakage meets the top's current, and brackets the same way + const y10 : u32 = gt_nand_y(&TT_LG_N, &TT_LG_P, a1, a0); + const mm : u32 = gt_mid(&TT_LG_N, a1, a0, y10); + assert(mm > 8388608); + assert(lg_fine(&TT_LG_N, a0, mm) <= lg_fine(&TT_LG_N, gt_sub(a1, mm), y10 - mm)); + assert(lg_fine(&TT_LG_N, a0, mm + 1) > lg_fine(&TT_LG_N, gt_sub(a1, mm + 1), y10 - mm - 1)); + // the levels: the lowest 1 is 1.4999995 V, the highest 0 is 0.178 uV, and the worst gap to ngspice is 2.358 uV (11: 0.178 uV against 2.537 uV); the bar is 10 uV + const nd : Lv = gt_levels(&TT_LG_N, &TT_LG_P, false); + assert(nd.one_v > 1.4999995); + assert(nd.one_v < 1.4999996); + assert(nd.zero_v > 0.00000017); + assert(nd.zero_v < 0.00000018); + assert(nd.gap_v > 0.0000023); + assert(nd.gap_v < 0.0000024); + assert(nd.gap_v < GT_B_LEVEL_V); + // ngspice's own levels are microvolts too (the report: 1.5 V for a 1, microvolts for a 0) + assert(GT_NG_NAND[3] > 0.0000025); + assert(GT_NG_NAND[3] < 0.0000026); + assert(GT_NG_NAND[1] > 0.0000007); + assert(GT_NG_NAND[1] < 0.0000008); +} + +test the_nor_levels_are_the_nand_seen_from_the_supply_and_every_level_is_within_1_1_uV_of_ngspice { + var a0 : u32 = 0; + var a1 : u32 = VDD; + // the 1 level (00) is 31 words under the supply (1.85 uV: two PMOS in series against two NMOS leaking in parallel), the 0 levels (01, 10, 11) are 1 word over ground + assert_eq(VDD - gt_nor(&TT_LG_N, &TT_LG_P, a0, a0), 31); + assert_eq(gt_nor(&TT_LG_N, &TT_LG_P, a0, a1), 1); + assert_eq(gt_nor(&TT_LG_N, &TT_LG_P, a1, a0), 1); + assert_eq(gt_nor(&TT_LG_N, &TT_LG_P, a1, a1), 1); + const nr : Lv = gt_levels(&TT_LG_N, &TT_LG_P, true); + assert(nr.one_v > 1.4999981); + assert(nr.one_v < 1.4999982); + assert(nr.zero_v > 0.00000005); + assert(nr.zero_v < 0.00000007); + assert(nr.gap_v > 0.0000010); + assert(nr.gap_v < 0.0000011); + assert(nr.gap_v < GT_B_LEVEL_V); + assert(GT_NG_NOR[0] > 0.0000028); + assert(GT_NG_NOR[0] < 0.0000029); +} + +test a_gate_with_a_dead_device_table_or_the_wrong_gate_is_refused_and_the_tables_in_each_others_slots_are_not_and_that_is_the_limit_of_the_level_check { + var dead : [4851]u32 = [0] ** 4851; + var a0 : u32 = 0; + var a1 : u32 = VDD; + // NEGATIVE CONTROL, the PMOS never conducts (a zero table, F = 0): the output never rises (under 0.4 mV, the rounding of the off current to words), the 1 levels are 1.5 V away + // and the gap to ngspice is 1.5 V + const np : Lv = gt_levels(&TT_LG_N, &dead, false); + assert(gt_nand(&TT_LG_N, &dead, a0, a0) < 20000); + assert(np.one_v < 0.001); + assert(np.gap_v > 1.499); + assert(np.gap_v > 100000.0 * GT_B_LEVEL_V); + // the NMOS never conducts: the nand's 11 stays at the supply, its 0 level is 1.5 V (the report's check, 50 mV, refuses it), the gap is 1.5 V + const nn : Lv = gt_levels(&dead, &TT_LG_P, false); + assert(nn.zero_v > 1.4999); + assert(nn.gap_v > 1.4999); + // NEGATIVE CONTROL, the wrong gate: the nand's output for 01 (a 1, 1.5 V) read where the nor has a 0 (0.63 uV): 1.5 V apart + const wrong : f64 = (gt_nand(&TT_LG_N, &TT_LG_P, a0, a1) as f64) / UPV; + assert(wrong > 1.4999); + assert(wrong - GT_NG_NOR[1] > 1.4999); + // THE LIMIT OF THE CONTROL: the two tables in each other's slots (a 3 um stack and a 1 um pair) are still rail to rail within 10 uV of ngspice's levels (0.69 uV for the nand, + // 2.7 uV for the nor), because a level is the leakage of the off side times the on resistance of the other and both are microvolts either way. The cross-check on levels does not see + // a device's width; the Ron and the leakage below do + const ex : Lv = gt_levels(&TT_LG_P, &TT_LG_N, false); + const ey : Lv = gt_levels(&TT_LG_P, &TT_LG_N, true); + assert(ex.gap_v > 0.00000068); + assert(ex.gap_v < 0.00000070); + assert(ey.gap_v > 0.0000027); + assert(ey.gap_v < 0.0000028); + assert(ex.gap_v < GT_B_LEVEL_V); + assert(ey.gap_v < GT_B_LEVEL_V); +} + +test the_tgate_without_the_body_effect_is_within_0_07_percent_of_ngspice_at_all_27_points_where_the_two_cells_have_the_same_bulk_condition_as_the_tables { + // accept_tgate.cir, second cell: each bulk tied to its own source, the condition the tables were made under. 1718.0 ohm at 0.75 V (ngspice 1717.2), 2109.3 ohm at the worst point + // (ngspice 2108.4); the largest of the 27 relative gaps is 0.0588 % + const r075 : f64 = gt_tg_ron(&TT_LG_N, &TT_LG_P, gt_tg_v(13)); + assert(r075 > 1717.9); + assert(r075 < 1718.1); + const worst : f64 = gt_tg_worst(&TT_LG_N, &TT_LG_P); + assert(worst > 2109.2); + assert(worst < 2109.4); + assert(GT_NG_RNB[13] > 1717.2); + assert(GT_NG_RNB[13] < 1717.3); + assert(gt_ref_max(>_NG_RNB) > 2108.3); + assert(gt_ref_max(>_NG_RNB) < 2108.4); + const gap : f64 = gt_ron_gap(&TT_LG_N, &TT_LG_P, >_NG_RNB); + assert(gap > 0.0005); + assert(gap < 0.0007); + assert(gap < GT_B_REL); + // the sweep points: 0.10 V is output 1677722 words, 0.75 V is 12582912, 1.40 V is 23488102 + assert_eq(gt_tg_v(0), 1677722); + assert_eq(gt_tg_v(13), 12582912); + assert_eq(gt_tg_v(26), 23488102); + // NEGATIVE CONTROL, a device missing from the cell: the PMOS never conducts, the NMOS alone is 2895 ohm at 0.75 V, 69 % over the no-body-effect cell; the NMOS never conducts, the + // PMOS alone is 4225 ohm, 146 % over. The bar of 10 % refuses both, by 6.9 and 14.6 times + var dead : [4851]u32 = [0] ** 4851; + const rn : f64 = gt_tg_ron(&TT_LG_N, &dead, gt_tg_v(13)); + const rp : f64 = gt_tg_ron(&dead, &TT_LG_P, gt_tg_v(13)); + assert(rn > 2895.0); + assert(rn < 2896.0); + assert(rp > 4225.0); + assert(rp < 4226.0); + assert((rn - GT_NG_RNB[13]) / GT_NG_RNB[13] > 6.0 * GT_B_REL); + assert((rp - GT_NG_RNB[13]) / GT_NG_RNB[13] > 14.0 * GT_B_REL); + assert(gt_ron_gap(&TT_LG_N, &dead, >_NG_RNB) > 0.6); +} + +test the_real_tgate_is_24_to_31_percent_above_the_tables_at_every_point_because_the_tables_have_no_body_effect_and_the_10_percent_bar_refuses_it { + // accept_tgate.cir, first cell (bulks on the rails): 2459.6 ohm at 0.75 V (the external team's report 2.44 kohm) and 2776.1 ohm at the worst point, 1.0 V (the report 2.78 kohm). + // Ours are 1718.0 and 2109.3 ohm: 30.2 % and 24.0 % below; the largest of the 27 gaps is 31.1 % + assert(GT_NG_RTG[13] > 2459.6); + assert(GT_NG_RTG[13] < 2459.7); + assert(gt_ref_max(>_NG_RTG) > 2776.1); + assert(gt_ref_max(>_NG_RTG) < 2776.2); + const r075 : f64 = gt_tg_ron(&TT_LG_N, &TT_LG_P, gt_tg_v(13)); + const g075 : f64 = (GT_NG_RTG[13] - r075) / GT_NG_RTG[13]; + assert(g075 > 0.301); + assert(g075 < 0.302); + const worst : f64 = gt_tg_worst(&TT_LG_N, &TT_LG_P); + const gw : f64 = (gt_ref_max(>_NG_RTG) - worst) / gt_ref_max(>_NG_RTG); + assert(gw > 0.240); + assert(gw < 0.241); + const gap : f64 = gt_ron_gap(&TT_LG_N, &TT_LG_P, >_NG_RTG); + assert(gap > 0.311); + assert(gap < 0.312); + // the bar of 10 % refuses the real cell by 3 times at 0.75 V and 2.4 times at the worst point + assert(g075 > 3.0 * GT_B_REL); + assert(gw > 2.4 * GT_B_REL); + // the body effect only raises a resistance: at every one of the 27 points ngspice's real cell is above the table model, and above the no-body-effect cell + var k : u32 = 0; + var above : bool = true; + var above_nb : bool = true; + while (k < GT_NV) { + if (gt_tg_ron(&TT_LG_N, &TT_LG_P, gt_tg_v(k)) >= GT_NG_RTG[k as usize]) { above = false; } + if (GT_NG_RNB[k as usize] >= GT_NG_RTG[k as usize]) { above_nb = false; } + k = k + 1; + } + assert(above); + assert(above_nb); +} + +test the_tgate_off_leakage_is_291_pA_against_ngspices_286_pA_at_the_output_and_271_pA_at_the_input_and_two_nmos_tables_are_refused { + // the sum of the two table words at Vgs = 0, Vds = 1.5 V: the NMOS 224 words (261 pA), the PMOS 26 words (30 pA); 291.0 pA + const off : f64 = gt_tg_off(&TT_LG_N, &TT_LG_P); + assert(off > 0.000000000291); + assert(off < 0.0000000002911); + assert(GT_NG_LEAK_OUT_A > 0.000000000285); + assert(GT_NG_LEAK_OUT_A < 0.000000000286); + assert(GT_NG_LEAK_IN_A > 0.000000000271); + assert(GT_NG_LEAK_IN_A < 0.000000000272); + const g_out : f64 = (off - GT_NG_LEAK_OUT_A) / GT_NG_LEAK_OUT_A; + const g_in : f64 = (off - GT_NG_LEAK_IN_A) / GT_NG_LEAK_IN_A; + // +1.9 % and +7.2 %: inside the bar of 10 % at both sources + assert(g_out > 0.019); + assert(g_out < 0.0191); + assert(g_in > 0.0723); + assert(g_in < 0.0725); + assert(g_out < GT_B_REL); + assert(g_in < GT_B_REL); + // NEGATIVE CONTROL: the NMOS table in both slots leaks 521.5 pA, 82.6 % over the output source's, 8 times the bar; the PMOS alone (the NMOS dead) 30.3 pA, 89 % under + var dead : [4851]u32 = [0] ** 4851; + const two : f64 = gt_tg_off(&TT_LG_N, &TT_LG_N); + const only_p : f64 = gt_tg_off(&dead, &TT_LG_P); + assert(two > 0.000000000521); + assert(two < 0.000000000522); + assert((two - GT_NG_LEAK_OUT_A) / GT_NG_LEAK_OUT_A > 8.0 * GT_B_REL); + assert(only_p > 0.0000000000302); + assert(only_p < 0.0000000000303); + assert((GT_NG_LEAK_OUT_A - only_p) / GT_NG_LEAK_OUT_A > 8.0 * GT_B_REL); +}