diff --git a/.trinity/seals/analog_AnalogAcceptSar.json b/.trinity/seals/analog_AnalogAcceptSar.json new file mode 100644 index 0000000000..4f6b29ca1b --- /dev/null +++ b/.trinity/seals/analog_AnalogAcceptSar.json @@ -0,0 +1,77 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+42a4860c4", + "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + }, + { + "spec": "specs/analog/mos_psp103.t27", + "spec_hash": "sha256:3edf0828e56e30977b96c2e943d203aac805fd197800d37a57fb4fc61c7152d8" + }, + { + "spec": "specs/analog/mos_psp103_50c_data.t27", + "spec_hash": "sha256:9e68629797a29516836ca3e0b5e4881b0e751d847c58fd52b63e8f69746dbbe1" + }, + { + "spec": "specs/analog/mos_psp103_data.t27", + "spec_hash": "sha256:d233143ed2fa30d3bd0ff9546a2df008d568e4a4d9c7e0ef69ffedee2e92a5d6" + }, + { + "spec": "specs/analog/sar_adc.t27", + "spec_hash": "sha256:208a64705a1d07ff14d3131d999135a3d5da6c7cb9bc2e919430d7e60b34db68" + }, + { + "spec": "specs/analog/sar_cdac.t27", + "spec_hash": "sha256:a7c45039214a8b2e92333a88befc95af33ff482ce08af3be97e2c5b8be3eacd3" + }, + { + "spec": "specs/analog/sar_comparator.t27", + "spec_hash": "sha256:db347ab1845e5323822b3ab77ba3d43b9923d105cc717e7224ad54c7328d23a7" + }, + { + "spec": "specs/analog/sar_comparator_psp.t27", + "spec_hash": "sha256:03fa3e42e50de0a06436cc2e57636a6b59c1be04243a9b836920832e8dae0c40" + }, + { + "spec": "specs/analog/sar_kick.t27", + "spec_hash": "sha256:b38a9a0bd22837f7ca1d5609756c522cd791f7b0f11115b7863e04cd4e15bf10" + }, + { + "spec": "specs/analog/sar_logic.t27", + "spec_hash": "sha256:80880ba02bc8f161cc968121104211a58f6f6c4d092c7d26a0a5e129f18ddb7f" + }, + { + "spec": "specs/analog/sar_switch.t27", + "spec_hash": "sha256:122726502a51cb4c8980674b819ee3251bf8ee69a5586d6cd44a5eafcf284cb4" + }, + { + "spec": "specs/tools/t27o/spice_convert.t27", + "spec_hash": "sha256:1a11784a07eb726b483971ecf12046b3f97cc51b365d7b694e2557bd806a90f9" + } + ], + "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", + "module": "AnalogAcceptSar", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T03:11:58Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:47df48dd24939e164d669ed536f0fc5c7feb03edba165e7c7238212ae1354ab1", + "spec_path": "specs/analog/accept_sar.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 13, + "total": 13, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/analog_AnalogSarKick.json b/.trinity/seals/analog_AnalogSarKick.json index 4426af77c2..2f3fb1da2b 100644 --- a/.trinity/seals/analog_AnalogSarKick.json +++ b/.trinity/seals/analog_AnalogSarKick.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+0fba674ef", + "built_by": "t27c-bootstrap@0.5.2+42a4860c4", "closure": [ { "spec": "specs/analog/linear.t27", @@ -7,16 +7,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:516df5bf9f81aa5a2115b870c10eb58b21e5edb76d59da54c6c1fa621a5034d7", - "gen_hash_rust": "sha256:5516883b9db0b8b604aafaa502a79c08d8680e7be9b7a4aa05fcf61666eeea4f", - "gen_hash_verilog": "sha256:524c966626538ac391a13e03108559b227118c2251dc9c9e12b2b6d0324447e0", - "gen_hash_zig": "sha256:01d83589ceef71018375c7f4630ef68b02bb30212b7d06f4f95455360c256089", + "gen_hash_c": "sha256:77daac412fa8244abc763daf5b7b7fa39d95b2f71547dd4670078b0b83a0bfec", + "gen_hash_rust": "sha256:a986bdfd81d3c5ed719ec1d54de5db8b60b82b165761b5ee4781b989792e8fca", + "gen_hash_verilog": "sha256:d10e1ae69bac9c40b293e22a482359faa8f908efacc8a685ef3e9fcdb09c74c2", + "gen_hash_zig": "sha256:e63b5a59b13bc07961afb614f789de6f28afd716149333decbf35b8129d9de3a", "module": "AnalogSarKick", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T16:29:46Z", + "sealed_at": "2026-10-20T03:12:04Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8c7c3968475c3f62c78f7b0a7739c2f049680eb9911d23be7f4d0ce0a4922dff", + "spec_hash": "sha256:b38a9a0bd22837f7ca1d5609756c522cd791f7b0f11115b7863e04cd4e15bf10", "spec_path": "specs/analog/sar_kick.t27", "tests": { "failed": 0, diff --git a/docs/analog/accept-sar-2026-10-20.md b/docs/analog/accept-sar-2026-10-20.md new file mode 100644 index 0000000000..b13ab599c4 --- /dev/null +++ b/docs/analog/accept-sar-2026-10-20.md @@ -0,0 +1,135 @@ +# 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. + +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. + +## 1. The verdict rule + +A row is one quantity of the report's check. + +- PASS: our model produced the value and it is inside the report's limit. +- FAIL: our model produced the value and it is outside the limit. It is reported as measured. +- NOT MODELLED: our model does not produce the quantity the check enforces. Nothing is compared, and the row is never a pass, whatever a constant might have said (`as_verdict(false, x)` is NOT MODELLED for both values of `x`; a test pins it). + +A block is FAIL when any row fails, PASS only when it has rows and every row is PASS, and NOT MODELLED otherwise. A block whose threshold passes and whose delay is not modelled is therefore not a pass: it is NOT MODELLED, and its passing row is still shown. + +## 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 | +| 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 | + +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. + +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). + +## 3. The table + +Times in ps or ns as written. "Report" is the report's measured value; "limit" is what its check enforces. Our value is from the function named in the last column. + +| block | check | our value | report | limit | verdict | function | +|---|---|---|---|---|---|---| +| inverter | switching threshold | 0.6892 V | 0.69 V | 0.5 to 1.0 V | PASS | `as_inv_vth_of` | +| inverter | delays fall / rise into 20 fF | not produced | 81 / 100 ps | 1 to 500 ps | NOT MODELLED | | +| 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 | | +| 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 | | +| 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` | +| dynamic_comparator | sign at +-2 mV, 0.75 V common mode | +2 mV: dp wins; -2 mV: dn wins | right | right | PASS | `as_cmp` | +| dynamic_comparator | levels after the decision | winner 1.49996 V, loser 3.6 mV | right | +-50 mV | PASS | `as_cmp` | +| dynamic_comparator | reset | not produced | right | +-50 mV | NOT MODELLED | | +| dynamic_comparator | decision time at +-2 mV | 5.35 ps | 607 ps | 10 to 10000 ps | **FAIL** | `as_cmp` | +| 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 | | +| 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 | | +| clock_gen | stops after Valid | 5.704 ns | 10.7 ns | <= 30 ns | PASS | `as_clk_golden` | +| SAR_logic | one more CK per Valid edge, CK11 first; 1, 4, 11 high after 1, 4, 11 edges | 1, 4, 11; thermometer in order at all 11 edges, held between edges | exact counts | exact | PASS | `as_sar_run` | +| SAR_logic | CK11 to CK1 | 200.126 ns | 200 ns | 199 to 201 ns | PASS | `as_sar_run` | +| DAC_control | idle 0.75 V; 0 V / 1.5 V after decisions 1 / 0 | not produced | right | 0.7 to 0.8 V; +-50 mV | NOT MODELLED | | +| DAC_control_top | 11 bits latch in CK order; code, plate code | not produced | 1430, 617 | exact | NOT MODELLED | | +| neg_bottom_plate_logic | Bit_n follows Bit while CK11 is low, complement after; VCM | not produced | 0.75 V | 0.7 to 0.8 V; +-50 mV | NOT MODELLED | | +| integration | worst code error, -1 V to +1 V ramp of 8 conversions | 18.5 LSB | 13.8 LSB | +-64 LSB | PASS | `as_ramp_literal` | +| integration | smallest step between conversions | 198 LSB | 154 LSB (ideal 152) | 60 to 250 LSB | PASS | `as_ramp_literal` | +| corners (smoke suite, not a block) | inverter delay 62 to 124 ps, comparator decision 467 to 829 ps, 5 corners x 3 temperatures | not produced | within | as stated | NOT MODELLED | | + +## 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). +- 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. +- 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. + +### Context, not a verdict: the report's own ramp + +The report's ideal end codes are 517 and 1579 and its ideal step is 152 LSB, which is 7 equal steps of 151.7 LSB, so its 8 conversions are equally spaced and do not start at -1 V. Rebuilt from those two numbers (the inputs are inferred, not given), our converter on the same ramp gives: + +| quantity | our value | report | +|---|---|---| +| first code (ideal 517) | 512 | 510 | +| last code (ideal 1579) | 1595 | 1593 | +| worst error | 16.0 LSB | 13.8 LSB | +| smallest step | 154 LSB | 154 LSB (ideal 152) | + +The four numbers agree within a few LSB. Without the measured offset the end codes are 507 and 1590. The literal -1 V to +1 V ramp is the one the verdict uses, because it is the check as stated. + +## 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. +- 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. + +## 6. Negative controls + +Every check function accepts the report's own value and refuses a broken one, so none is always-FAIL or always-PASS. Where a model can be broken to leave the limit, that is done too, and the same check refuses it: + +| check | the break | its value | +|---|---|---| +| inverter threshold | PMOS table zeroed | the output never falls through Vdd / 2: the reader's no-crossing value, -0.031 V, refused by 0.5 V | +| 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 | +| 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 | +| 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. + +## 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 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. + +## 8. How to run it + +``` +./target/release/t27c test-report specs/analog/accept_sar.t27 +./target/release/t27b test specs/analog/accept_sar.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. diff --git a/specs/analog/accept_sar.t27 b/specs/analog/accept_sar.t27 new file mode 100644 index 0000000000..58541c5448 --- /dev/null +++ b/specs/analog/accept_sar.t27 @@ -0,0 +1,1239 @@ +// 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 +; 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 +; applies the check to it. The report is cited as "the external team's report of 2026-10-16" and nowhere else; no person or company is named. +; - +; THE VERDICT RULE, and why it is strict. A row is one quantity of the report's check. Its verdict is PASS (our model produced the value and it is inside the limit), +; 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. +; - +; 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. +; 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. +; 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. +; integration the behavioural converter (sar_adc.t27): ideal DAC capacitors plus ONE fitted top-plate parasitic and the comparator offset measured on the netlist +; (sar_kick.t27), on an ideal sampling input. A PASS here is a PASS of that model, which was fitted to the golden deck's 30 of 31 codes; it is not a +; 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 +; 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). +; 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. +; - +; 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 +; 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 + +module AnalogAcceptSar; + +use analog::linear::{fabs64, q_sm}; +use analog::mos_psp103::{dc_out, cross_idx, DC_POINTS, DC_STEP}; +use analog::mos_psp103_data::{TT_LG_N, TT_LG_P, TT_N, TT_P}; +use analog::mos_psp103_50c_data::{TT50_N, TT50_P}; +use analog::sar_comparator::{SC_DV_M, SC_K, SC_N, SC_NN, SC_DP, SC_DN, SC_UPV, SC_VCM, SC_VTH_V, SC_H2, sc_sign, sc_build_table}; +use analog::sar_comparator_psp::{sp_run, sp_first}; +use analog::sar_switch::{SW_CB, SW_K, SW_DV_M, sw_make_vin, sw_run, sw_widen, sw_te, sw_inj, sw_droop}; +use analog::sar_logic::{SL_VAL, SL_S0, SL_S1, SL_TMIN, SL_NG_CLKS_F, sl_build, sl_put, sl_run, sl_edge, sl_nand_code, sl_nor_code}; +use analog::sar_adc::{convert_sar, model_k, CP_FIT}; +use analog::sar_cdac::{UNITS}; +use analog::sar_kick::{off_late_v}; +use tools::t27o::spice_convert::{NONE}; + +pub const KIND : str = "analog-cell-check"; +pub const ID : str = "analog/accept_sar"; +pub const REFERENCE : str = "the external team's report of 2026-10-16: per-block checks and limits of the 13 SAR ADC blocks, tt, 27 C, VDD 1.5 V"; + +; --- the verdicts --------------------------------------------------------------------------------------------- +pub const AS_PASS : u32 = 0; +pub const AS_FAIL : u32 = 1; +pub const AS_NM : u32 = 2; +pub const AS_LABEL_PASS : str = "PASS"; +pub const AS_LABEL_FAIL : str = "FAIL"; +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; + +; --- 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_CMP_RESET : str = "the comparator run is one evaluation phase of 14 ps; the clock never falls, so there is no reset phase to measure"; +pub const AS_WHY_SW_HOLD : str = "square law: no gate capacitance, so no charge injection; the hold step the model returns is a following artifact of 0.18 uV"; +pub const AS_WHY_SW_DROOP : str = "square law: no leakage; the droop the model returns is exactly zero by construction"; +pub const AS_WHY_DAC_CONTROL : str = "the DAC takes its plate levels as ideal sources: the level is the input, not a result"; +pub const AS_WHY_DAC_TOP : str = "the eleven latches and the plate lines are not a circuit in any spec; the converter's decision order is behavioural"; +pub const AS_WHY_NEG_PLATE : str = "the N half's follow-then-complement logic is not a circuit in any spec; the DAC model has the N plate move reversed by construction"; +pub const AS_WHY_CORNERS : str = "the tables have five corners at 27 C (tt also at 50 C) and none at -40 or 125 C; the report's suite is five corners at three temperatures"; + +; --- the limits of the report (tt, 27 C, VDD 1.5 V) ------------------------------------------------------------- +pub const L_INV_VTH_LO_V : f64 = 0.5; +pub const L_INV_VTH_HI_V : f64 = 1.0; +pub const L_INV_DELAY_LO_PS : f64 = 1.0; +pub const L_INV_DELAY_HI_PS : f64 = 500.0; +pub const L_GATE_ONE_LO_V : f64 = 1.45; +pub const L_GATE_ONE_HI_V : f64 = 1.55; +pub const L_GATE_ZERO_V : f64 = 0.05; +pub const L_TG_RON_OHM : f64 = 30000.0; +pub const L_TG_RON_WORST_OHM : f64 = 50000.0; +pub const L_TG_LEAK_NA : f64 = 1.0; +pub const L_DFF_LEVEL_V : f64 = 0.05; +pub const L_DFF_C2Q_LO_PS : f64 = 1.0; +pub const L_DFF_C2Q_HI_PS : f64 = 3000.0; +pub const L_CMP_LEVEL_V : f64 = 0.05; +pub const L_CMP_DT_LO_PS : f64 = 10.0; +pub const L_CMP_DT_HI_PS : f64 = 10000.0; +pub const L_SW_TRACK_V : f64 = 0.005; +pub const L_SW_HOLD_V : f64 = 0.03; +pub const L_SW_DROOP_V : f64 = 0.005; +pub const L_CLK_SLOW_LO_NS : f64 = 0.05; +pub const L_CLK_SLOW_HI_NS : f64 = 50.0; +pub const L_CLK_FAST_LO_NS : f64 = 0.02; +pub const L_CLK_FAST_HI_NS : f64 = 40.0; +pub const L_CLK_RATIO_MIN : f64 = 1.2; +pub const L_CLK_STOP_MAX_NS : f64 = 30.0; +pub const L_SAR_SPAN_LO_NS : f64 = 199.0; +pub const L_SAR_SPAN_HI_NS : f64 = 201.0; +pub const L_DAC_IDLE_LO_V : f64 = 0.7; +pub const L_DAC_IDLE_HI_V : f64 = 0.8; +pub const L_DAC_LEVEL_V : f64 = 0.05; +pub const L_TOP_CODE : u32 = 1430; +pub const L_TOP_PLATE : u32 = 617; +pub const L_NEG_VCM_LO_V : f64 = 0.7; +pub const L_NEG_VCM_HI_V : f64 = 0.8; +pub const L_INT_ERR_LSB : f64 = 64.0; +pub const L_INT_STEP_LO_LSB : f64 = 60.0; +pub const L_INT_STEP_HI_LSB : f64 = 250.0; +pub const L_CORNER_INV_LO_PS : f64 = 62.0; +pub const L_CORNER_INV_HI_PS : f64 = 124.0; +pub const L_CORNER_CMP_LO_PS : f64 = 467.0; +pub const L_CORNER_CMP_HI_PS : f64 = 829.0; + +; --- what the report measured (the positive controls: its own values must pass its own checks) ---------------------------- +pub const R_INV_VTH_V : f64 = 0.69; +pub const R_INV_FALL_PS : f64 = 81.0; +pub const R_INV_RISE_PS : f64 = 100.0; +pub const R_TG_RON_OHM : f64 = 2440.0; +pub const R_TG_RON_WORST_OHM : f64 = 2780.0; +pub const R_TG_LEAK_NA : f64 = 0.29; +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; +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; +pub const R_CLK_STOP_NS : f64 = 10.7; +pub const R_INT_FIRST_CODE : u32 = 510; +pub const R_INT_LAST_CODE : u32 = 1593; +pub const R_INT_IDEAL_FIRST : u32 = 517; +pub const R_INT_IDEAL_LAST : u32 = 1579; +pub const R_INT_ERR_LSB : f64 = 13.8; +pub const R_INT_STEP_LSB : f64 = 154.0; +pub const R_INT_IDEAL_STEP_LSB : f64 = 152.0; + +; --- the verdict machinery --------------------------------------------------------------------------------------- +/// One row. `measured` is false when our model does not produce the quantity: the row is NOT MODELLED whatever `inside` says, so an unmeasured row is never a PASS. +pub fn as_verdict(measured: bool, inside: bool) -> u32 { + if (!measured) { return AS_NM; } + if (inside) { return AS_PASS; } + return AS_FAIL; +} + +/// A row that our model does not produce. +pub fn as_nm() -> u32 { + return as_verdict(false, true); +} + +/// The rows of one block, counted by verdict. +pub struct Blk { pass: u32, fail: u32, nm: u32 } + +pub fn as_blk0() -> Blk { + return Blk { pass: 0, fail: 0, nm: 0 }; +} + +/// The block with one more row of verdict v. +pub fn as_add(b: Blk, v: u32) -> Blk { + var p : u32 = b.pass; + var f : u32 = b.fail; + var n : u32 = b.nm; + if (v == AS_PASS) { p = p + 1; } + if (v == AS_FAIL) { f = f + 1; } + if (v == AS_NM) { n = n + 1; } + return Blk { pass: p, fail: f, nm: n }; +} + +/// A block: FAIL when a row fails, PASS only when it has rows and every row passes, NOT MODELLED otherwise. +pub fn as_block_verdict(b: Blk) -> u32 { + if (b.fail > 0) { return AS_FAIL; } + if (b.nm > 0 or b.pass == 0) { return AS_NM; } + return AS_PASS; +} + +; --- the checks: one pure function per quantity of the report, the limit inside --------------------------------------------- +pub fn as_in(v: f64, lo: f64, hi: f64) -> bool { + return v >= lo and v <= hi; +} + +pub fn as_within(v: f64, tol: f64) -> bool { + return fabs64(v) <= tol; +} + +/// inverter: switching threshold 0.5 to 1.0 V. +pub fn as_chk_inv_vth(v: f64) -> bool { + return as_in(v, L_INV_VTH_LO_V, L_INV_VTH_HI_V); +} + +/// inverter: both delays into 20 fF, 1 to 500 ps. +pub fn as_chk_inv_delay(fall_ps: f64, rise_ps: f64) -> bool { + return as_in(fall_ps, L_INV_DELAY_LO_PS, L_INV_DELAY_HI_PS) and as_in(rise_ps, L_INV_DELAY_LO_PS, L_INV_DELAY_HI_PS); +} + +/// nand truth table (codes of sar_logic: SL_S1 for a 1, SL_S0 for a 0) for inputs 00, 01, 10, 11. +pub fn as_chk_nand(c00: usize, c01: usize, c10: usize, c11: usize) -> bool { + return c00 == SL_S1 and c01 == SL_S1 and c10 == SL_S1 and c11 == SL_S0; +} + +/// nor truth table for inputs 00, 01, 10, 11. +pub fn as_chk_nor(c00: usize, c01: usize, c10: usize, c11: usize) -> bool { + return c00 == SL_S1 and c01 == SL_S0 and c10 == SL_S0 and c11 == SL_S0; +} + +/// nand, nor: a 1 is 1.45 to 1.55 V, a 0 is within 50 mV of 0 V. +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. +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; +} + +/// dff: edge capture, hold, set and reset all right. +pub fn as_chk_dff_behaviour(capture: bool, hold: bool, set: bool, reset: bool) -> bool { + return capture and hold and set and reset; +} + +/// dff: the levels of the eight observation points within 50 mV of their rail (the worst deviation). +pub fn as_chk_dff_levels(worst_dev_v: f64) -> bool { + return as_within(worst_dev_v, L_DFF_LEVEL_V); +} + +/// dff: clock-to-Q 1 to 3000 ps. +pub fn as_chk_dff_c2q(ps: f64) -> bool { + return as_in(ps, L_DFF_C2Q_LO_PS, L_DFF_C2Q_HI_PS); +} + +/// comparator: +2 mV makes dp win and -2 mV makes dn win (win_p, win_m are 1 where dp wins). +pub fn as_chk_cmp_sign(win_p: u32, win_m: u32) -> bool { + return win_p == 1 and win_m == 0; +} + +/// comparator: after the decision the winner is within 50 mV of 1.5 V and the loser within 50 mV of 0 V. +pub fn as_chk_cmp_levels(hi_v: f64, lo_v: f64) -> bool { + return as_within(hi_v - 1.5, L_CMP_LEVEL_V) and as_within(lo_v, L_CMP_LEVEL_V); +} + +/// comparator: in reset both outputs are within 50 mV of 0 V. +pub fn as_chk_cmp_reset(dp_v: f64, dn_v: f64) -> bool { + return as_within(dp_v, L_CMP_LEVEL_V) and as_within(dn_v, L_CMP_LEVEL_V); +} + +/// comparator: the decision time, 10 to 10000 ps. +pub fn as_chk_cmp_time(ps: f64) -> bool { + return as_in(ps, L_CMP_DT_LO_PS, L_CMP_DT_HI_PS); +} + +/// tracking switch: the tracking error at two input levels within 5 mV. +pub fn as_chk_sw_track(te_a_v: f64, te_b_v: f64) -> bool { + return as_within(te_a_v, L_SW_TRACK_V) and as_within(te_b_v, L_SW_TRACK_V); +} + +/// tracking switch: the hold step at two input levels within 30 mV. +pub fn as_chk_sw_hold(step_a_v: f64, step_b_v: f64) -> bool { + return as_within(step_a_v, L_SW_HOLD_V) and as_within(step_b_v, L_SW_HOLD_V); +} + +/// tracking switch: the droop during the hold within 5 mV. +pub fn as_chk_sw_droop(v: f64) -> bool { + return as_within(v, L_SW_DROOP_V); +} + +/// clock_gen: the slow delay (all trims 0), 0.05 to 50 ns. +pub fn as_chk_clk_slow(ns: f64) -> bool { + return as_in(ns, L_CLK_SLOW_LO_NS, L_CLK_SLOW_HI_NS); +} + +/// clock_gen: the fast delay (all trims 1), 0.02 to 40 ns. +pub fn as_chk_clk_fast(ns: f64) -> bool { + return as_in(ns, L_CLK_FAST_LO_NS, L_CLK_FAST_HI_NS); +} + +/// clock_gen: slow over fast at least 1.2. +pub fn as_chk_clk_ratio(r: f64) -> bool { + return r >= L_CLK_RATIO_MIN; +} + +/// clock_gen: the clock stops at most 30 ns after Valid. +pub fn as_chk_clk_stop(ns: f64) -> bool { + return ns >= 0.0 and ns <= L_CLK_STOP_MAX_NS; +} + +/// SAR_logic: one more CK output high per Valid edge, CK11 first, in order and held; exactly 1, 4 and 11 high after 1, 4 and 11 edges. +pub fn as_chk_sar_count(edge_ok: bool, hold_ok: bool, c1: u32, c4: u32, c11: u32) -> bool { + return edge_ok and hold_ok and c1 == 1 and c4 == 4 and c11 == 11; +} + +/// SAR_logic: CK11 to CK1 in 199 to 201 ns. +pub fn as_chk_sar_span(ns: f64) -> bool { + return as_in(ns, L_SAR_SPAN_LO_NS, L_SAR_SPAN_HI_NS); +} + +/// DAC_control: the bottom plate idles at VCM (0.7 to 0.8 V) and goes to 0 V after decision 1 and 1.5 V after decision 0, within 50 mV. +pub fn as_chk_dac_level(idle_v: f64, after1_v: f64, after0_v: f64) -> bool { + return as_in(idle_v, L_DAC_IDLE_LO_V, L_DAC_IDLE_HI_V) and as_within(after1_v, L_DAC_LEVEL_V) and as_within(after0_v - 1.5, L_DAC_LEVEL_V); +} + +/// DAC_control_top: the eleven bits latch in CK order to exactly code 1430, and the plate code is exactly 617. +pub fn as_chk_dac_top(code: u32, plate: u32) -> bool { + return code == L_TOP_CODE and plate == L_TOP_PLATE; +} + +/// neg_bottom_plate_logic: the idle level VCM within 0.7 to 0.8 V. +pub fn as_chk_neg_plate(vcm_v: f64) -> bool { + return as_in(vcm_v, L_NEG_VCM_LO_V, L_NEG_VCM_HI_V); +} + +/// integration: the worst code error within 64 LSB. +pub fn as_chk_int_err(lsb: f64) -> bool { + return lsb <= L_INT_ERR_LSB; +} + +/// integration: the smallest step between consecutive conversions 60 to 250 LSB. +pub fn as_chk_int_step(lsb: f64) -> bool { + return as_in(lsb, L_INT_STEP_LO_LSB, L_INT_STEP_HI_LSB); +} + +/// corners smoke suite: the SAR inverter delay 62 to 124 ps. +pub fn as_chk_corner_inv(ps: f64) -> bool { + return as_in(ps, L_CORNER_INV_LO_PS, L_CORNER_INV_HI_PS); +} + +/// corners smoke suite: the comparator decision 467 to 829 ps. +pub fn as_chk_corner_cmp(ps: f64) -> bool { + return as_in(ps, L_CORNER_CMP_LO_PS, L_CORNER_CMP_HI_PS); +} + +; --- measurements on our models ------------------------------------------------------------------------------------ +/// The inverter's switching threshold in volts from the DC transfer of the tables tn, tp (49 points of 31.25 mV, the bisection of mos_psp103.t27's dc_out): the input at +/// which the output falls through Vdd / 2 (SC_VCM is 0.75 V in units of 2^-24 V). -0.03125 V, one step below zero, means the output never fell through it. +pub fn as_inv_vth_of(tn: []const u32, tp: []const u32) -> f64 { + var model : [49]f64 = [0.0] ** 49; + var k : u32 = 0; + while (k < DC_POINTS) { + model[k as usize] = (dc_out(tn, tp, k * DC_STEP)) as f64; + k = k + 1; + } + return cross_idx(&model, (SC_VCM as f64), false, 0, 48) * 0.03125; +} + +/// The comparator at +2 mV (win_p, the end levels, the decision time) and at -2 mV (win_m), 0.75 V common mode, current tables tn, tp. The decision time is in ps. +pub struct Cmp { win_p: u32, win_m: u32, hi_v: f64, lo_v: f64, dt_ps: f64 } + +pub fn as_cmp(tn: []const u32, tp: []const u32) -> Cmp { + var out : [1128]u32 = [0] ** 1128; + var res : [140]u32 = [0] ** 140; + const sa : u64 = sp_run(tn, tp, SC_H2, false, SC_K, &SC_DV_M, &out, &res); + var wp : u32 = 0; + if (sc_sign(&out)) { wp = 1; } + const last : u32 = SC_N * SC_NN; + const dp : f64 = ((out[(last + SC_DP) as usize]) as f64) / SC_UPV; + const dn : f64 = ((out[(last + SC_DN) as usize]) as f64) / SC_UPV; + const dt : f64 = sp_first(&out) * 0.1; + const sb : u64 = sp_run(tn, tp, SC_H2, true, SC_K, &SC_DV_M, &out, &res); + var wm : u32 = 0; + if (sc_sign(&out)) { wm = 1; } + return Cmp { win_p: wp, win_m: wm, hi_v: dp, lo_v: dn, dt_ps: dt }; +} + +/// The tracking switch's run for stimulus sel (0 to 2: DC at 0.2, 0.75, 1.3 V) with boost capacitor cb (SW_CB, or 0 for the negative control): the tracking error at the +/// end of the track phase, the step the falling clock makes and the droop of the hold, in volts. +pub struct Sw { te: f64, inj: f64, droop: f64 } + +pub fn as_sw(sel: u32, cb: u32) -> Sw { + var t : [625]u32 = [0] ** 625; + sc_build_table(&t, SC_VTH_V); + var vin : [417]u32 = [0] ** 417; + var out : [3336]u32 = [0] ** 3336; + var res : [416]u32 = [0] ** 416; + var nit : [416]u32 = [0] ** 416; + var w : [3336]f64 = [0.0] ** 3336; + sw_make_vin(sel, &vin); + const s : u64 = sw_run(&t, &vin, cb, SW_K, &SW_DV_M, &out, &res, &nit); + sw_widen(&out, &w); + return Sw { te: sw_te(&w, &vin), inj: sw_inj(&w), droop: sw_droop(&w) }; +} + +/// The stimulus of the sequencer's netlist: the inputs at t = 0 with clks high (the reset) and the trim code (a, b, c), clks falling at SL_NG_CLKS_F, then npulse Valid +/// pulses of `high` ps every `period` ps from t0. The edges are sorted by time. ids is sar_clk's nets (vdd, vss, d, set, clks, valid, a, b, c, CK11 .. CK1, clkc). +pub fn as_stim(s_t: []usize, s_n: []usize, s_v: []usize, ids: []const usize, code: usize, t0: usize, period: usize, high: usize, npulse: usize) -> usize { + var k : usize = 0; + k = sl_put(s_t, s_n, s_v, k, 0, ids[0], 1); + k = sl_put(s_t, s_n, s_v, k, 0, ids[1], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[2], 1); + k = sl_put(s_t, s_n, s_v, k, 0, ids[3], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[6], (code / 4) % 2); + k = sl_put(s_t, s_n, s_v, k, 0, ids[7], (code / 2) % 2); + k = sl_put(s_t, s_n, s_v, k, 0, ids[8], code % 2); + k = sl_put(s_t, s_n, s_v, k, 0, ids[5], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[4], 1); + k = sl_put(s_t, s_n, s_v, k, SL_NG_CLKS_F, ids[4], 0); + var i : usize = 0; + while (i < npulse) { + k = sl_put(s_t, s_n, s_v, k, t0 + i * period, ids[5], 1); + k = sl_put(s_t, s_n, s_v, k, t0 + i * period + high, ids[5], 0); + i = i + 1; + } + return k; +} + +/// Is CK11 .. CK(12 - n) high and the others low (the sequencer fills as a thermometer, it does not walk)? +pub fn as_thermo(ms: []const usize, ids: []const usize, n: usize) -> bool { + var j : usize = 0; + var ok : bool = true; + while (j < 11) { + const v : usize = ms[SL_VAL + ids[9 + j]]; + var want : usize = 0; + if (j < n) { want = 1; } + if (v != want) { ok = false; } + j = j + 1; + } + return ok; +} + +/// How many of CK11 .. CK1 are high. +pub fn as_high(ms: []const usize, ids: []const usize) -> u32 { + var j : usize = 0; + var c : u32 = 0; + while (j < 11) { + if (ms[SL_VAL + ids[9 + j]] == 1) { c = c + 1; } + j = j + 1; + } + return c; +} + +/// The sequencer driven by eleven Valid pulses of 10 ns every 20 ns (so CK11 to CK1 spans 200 ns): edge_ok, every CK output right 8 ns after each Valid rise (and all low +/// before the first); hold_ok, right 8 ns after each Valid fall; c1, c4, c11, how many are high after 1, 4 and 11 edges; span_ps, CK1's rise minus CK11's; c2q_ps, CK11's +/// rise after the first Valid rise (the first dff's clock-to-Q with D = VDD). With `swapped` the fifth dff has Set and Reset exchanged (the negative control). +pub struct SarRun { ok: bool, edge_ok: bool, hold_ok: bool, c1: u32, c4: u32, c11: u32, span_ps: usize, c2q_ps: usize } + +pub fn as_sar_run(swapped: bool) -> SarRun { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5500]usize = [0] ** 5500; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + if (!sl_build(&src, &w, &out, &st, >, &ms, &ids, swapped)) { + return SarRun { ok: false, edge_ok: false, hold_ok: false, c1: 0, c4: 0, c11: 0, span_ps: 0, c2q_ps: 0 }; + } + const k : usize = as_stim(&s_t, &s_n, &s_v, &ids, 0, 80100, 20000, 10000, 11); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 80000); + var edge_ok : bool = as_thermo(&ms, &ids, 0); + var hold_ok : bool = true; + var c1 : u32 = 0; + var c4 : u32 = 0; + var c11 : u32 = 0; + var n : usize = 0; + while (n < 11) { + const tr : usize = 80100 + n * 20000; + sl_run(&ms, >, &s_t, &s_n, &s_v, k, tr + 8000); + if (!as_thermo(&ms, &ids, n + 1)) { edge_ok = false; } + const hi : u32 = as_high(&ms, &ids); + if (n == 0) { c1 = hi; } + if (n == 3) { c4 = hi; } + if (n == 10) { c11 = hi; } + sl_run(&ms, >, &s_t, &s_n, &s_v, k, tr + 18000); + if (!as_thermo(&ms, &ids, n + 1)) { hold_ok = false; } + n = n + 1; + } + const t11 : usize = sl_edge(&ms, ids[9], 1, 1, SL_TMIN); + const t1 : usize = sl_edge(&ms, ids[19], 1, 1, SL_TMIN); + var span : usize = 0; + var c2q : usize = 0; + if (t11 != NONE and t1 != NONE) { + span = t1 - t11; + c2q = t11 - 80100; + } + return SarRun { ok: true, edge_ok: edge_ok, hold_ok: hold_ok, c1: c1, c4: c4, c11: c11, span_ps: span, c2q_ps: c2q }; +} + +/// The clock at the golden pace (Valid high 20.2 ns every 40 ns, trim code 000): slow_ps, the first clkc rise after clks falls; stop_ps, the last clkc fall after the +/// eleventh Valid rise (CK1 is high by then and the clock does not restart). +pub struct Clk { slow_ps: usize, stop_ps: usize } + +pub fn as_clk_golden() -> Clk { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5500]usize = [0] ** 5500; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + if (!sl_build(&src, &w, &out, &st, >, &ms, &ids, false)) { return Clk { slow_ps: 0, stop_ps: 0 }; } + const k : usize = as_stim(&s_t, &s_n, &s_v, &ids, 0, 80100, 40000, 20200, 11); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 540000); + const r1 : usize = sl_edge(&ms, ids[20], 1, 1, SL_TMIN); + const f11 : usize = sl_edge(&ms, ids[20], 0, 11, SL_TMIN); + var slow : usize = 0; + var stop : usize = 0; + if (r1 != NONE) { slow = r1 - SL_NG_CLKS_F; } + if (f11 != NONE) { stop = f11 - (80100 + 10 * 40000); } + return Clk { slow_ps: slow, stop_ps: stop }; +} + +/// The delay from clks falling to clkc rising, ps, for the trim code (4 A + 2 B + C): 0 is the slow setting, 7 the fast one. 0 when the model could not be built. +pub fn as_clk_delay_ps(code: usize) -> usize { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5500]usize = [0] ** 5500; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + if (!sl_build(&src, &w, &out, &st, >, &ms, &ids, false)) { return 0; } + const k : usize = as_stim(&s_t, &s_n, &s_v, &ids, code, 80100, 40000, 20200, 0); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 70000); + const r1 : usize = sl_edge(&ms, ids[20], 1, 1, SL_TMIN); + if (r1 == NONE) { return 0; } + return r1 - SL_NG_CLKS_F; +} + +/// Set and reset on the eleven dffs of the sequencer: all CK low after the reset and the clks fall; Set high while clks is low puts all eleven high (set_ok); they keep it +/// when Set is released (hold_ok); clks high resets them all (reset_ok). With `swapped` the fifth dff has Set and Reset exchanged (the negative control). +pub struct DffSr { ok: bool, set_ok: bool, hold_ok: bool, reset_ok: bool } + +pub fn as_dff_set_reset(swapped: bool) -> DffSr { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5500]usize = [0] ** 5500; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + if (!sl_build(&src, &w, &out, &st, >, &ms, &ids, swapped)) { + return DffSr { ok: false, set_ok: false, hold_ok: false, reset_ok: false }; + } + var k : usize = as_stim(&s_t, &s_n, &s_v, &ids, 0, 80100, 40000, 20200, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 40000, ids[3], 1); + k = sl_put(&s_t, &s_n, &s_v, k, 50000, ids[3], 0); + k = sl_put(&s_t, &s_n, &s_v, k, 60000, ids[4], 1); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 35000); + const before : u32 = as_high(&ms, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 45000); + const during : u32 = as_high(&ms, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 55000); + const after : u32 = as_high(&ms, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 70000); + const reset : u32 = as_high(&ms, &ids); + return DffSr { ok: true, set_ok: during == 11, hold_ok: after == 11, reset_ok: before == 0 and reset == 0 }; +} + +/// The converter on an 8-conversion ramp of the differential input from dv0 volts in steps of ddv, ideal sampling about 0.75 V, with DAC ratios k and comparator offset +/// word off: worst, the largest |code + 0.5 - ideal| in LSB (ideal = dv 1023 / 1.5 V + 1024, the bin centre of an ideal converter); step, the smallest difference between +/// consecutive codes; first and last, the codes of the first and last conversion. +pub struct Ramp { worst: f64, step: f64, first: u32, last: u32 } + +pub fn as_ramp(k: []const u32, off: u32, dv0: f64, ddv: f64) -> Ramp { + var worst : f64 = 0.0; + var smallest : f64 = 100000.0; + var first : u32 = 0; + var last : u32 = 0; + var prev : u32 = 0; + var i : usize = 0; + while (i < 8) { + const dv : f64 = dv0 + ddv * (i as f64); + const c : u32 = convert_sar(k, q_sm(0.75 + dv / 2.0), q_sm(0.75 - dv / 2.0), off); + const x : f64 = dv * 1023.0 / 1.5 + 1024.0; + const e : f64 = fabs64((c as f64) + 0.5 - x); + if (e > worst) { worst = e; } + if (i == 0) { first = c; } + if (i == 7) { last = c; } + if (i > 0) { + var d : f64 = (c as f64) - (prev as f64); + if (d < 0.0) { d = 0.0 - d; } + if (d < smallest) { smallest = d; } + } + prev = c; + i = i + 1; + } + return Ramp { worst: worst, step: smallest, first: first, last: last }; +} + +/// The ramp of the check as stated: -1 V to +1 V in 8 equal points, the model of sar_adc.t27 with its fitted top-plate parasitic and the measured offset. +pub fn as_ramp_literal() -> Ramp { + const kf : [20]u32 = model_k(&UNITS, 1.0, CP_FIT); + return as_ramp(&kf, q_sm(off_late_v()), 0.0 - 1.0, 2.0 / 7.0); +} + +/// The ramp rebuilt from the report's own numbers, context only: 8 equal points whose ideal codes run from 517 to 1579 (the report's ideal end codes; their spacing, 151.7 +/// LSB, is the report's ideal step of 152). The input is inferred from those codes, not given by the report. +pub fn as_ramp_report_like() -> Ramp { + const kf : [20]u32 = model_k(&UNITS, 1.0, CP_FIT); + return as_ramp(&kf, q_sm(off_late_v()), (517.5 - 1024.0) / 682.0, 1062.0 / 7.0 / 682.0); +} + +; --- 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 { + var b : Blk = as_blk0(); + b = as_add(b, as_verdict(true, as_chk_inv_vth(as_inv_vth_of(&TT_LG_N, &TT_LG_P)))); + b = as_add(b, as_nm()); + return b; +} + +/// nand: the truth table of the gate-level model; the levels in volts are not modelled. +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()); + 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. +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()); + return b; +} + +/// tgate: Ron at 0.75 V, the worst Ron and the off leakage are not modelled. +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()); + return b; +} + +/// dff: capture and hold (the sequencer's thermometer at each Valid edge and between edges), set and reset (the Set pulse and the clks reset), the clock-to-Q of the first +/// stage; the levels in volts are not modelled. +pub fn as_blk_dff() -> Blk { + var b : Blk = as_blk0(); + const r : SarRun = as_sar_run(false); + const s : DffSr = as_dff_set_reset(false); + b = as_add(b, as_verdict(r.ok and s.ok, as_chk_dff_behaviour(r.edge_ok, r.hold_ok and s.hold_ok, s.set_ok, s.reset_ok))); + b = as_add(b, as_nm()); + b = as_add(b, as_verdict(r.ok, as_chk_dff_c2q((r.c2q_ps as f64)))); + return b; +} + +/// dynamic_comparator: the sign at +-2 mV, the end levels, the decision time, all on the PSP103 50 C tables; the reset is not run. +pub fn as_blk_comparator() -> Blk { + var b : Blk = as_blk0(); + const c : Cmp = as_cmp(&TT50_N, &TT50_P); + b = as_add(b, as_verdict(true, as_chk_cmp_sign(c.win_p, c.win_m))); + b = as_add(b, as_verdict(true, as_chk_cmp_levels(c.hi_v, c.lo_v))); + b = as_add(b, as_nm()); + b = as_add(b, as_verdict(true, as_chk_cmp_time(c.dt_ps))); + 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. +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))); + b = as_add(b, as_nm()); + b = as_add(b, as_nm()); + return b; +} + +/// clock_gen: the slow and fast delays, their ratio, the stop time. +pub fn as_blk_clock() -> Blk { + var b : Blk = as_blk0(); + const g : Clk = as_clk_golden(); + const slow : f64 = (g.slow_ps as f64) / 1000.0; + const fast : f64 = (as_clk_delay_ps(7) as f64) / 1000.0; + b = as_add(b, as_verdict(g.slow_ps > 0, as_chk_clk_slow(slow))); + b = as_add(b, as_verdict(fast > 0.0, as_chk_clk_fast(fast))); + b = as_add(b, as_verdict(g.slow_ps > 0 and fast > 0.0, as_chk_clk_ratio(slow / fast))); + b = as_add(b, as_verdict(g.stop_ps > 0, as_chk_clk_stop((g.stop_ps as f64) / 1000.0))); + return b; +} + +/// SAR_logic: the counts and the order, the CK11 to CK1 span. +pub fn as_blk_sar_logic() -> Blk { + var b : Blk = as_blk0(); + const r : SarRun = as_sar_run(false); + b = as_add(b, as_verdict(r.ok, as_chk_sar_count(r.edge_ok, r.hold_ok, r.c1, r.c4, r.c11))); + b = as_add(b, as_verdict(r.ok and r.span_ps > 0, as_chk_sar_span((r.span_ps as f64) / 1000.0))); + return b; +} + +/// DAC_control: the plate levels are the DAC's input, not a result. +pub fn as_blk_dac_control() -> Blk { + return as_add(as_blk0(), as_nm()); +} + +/// DAC_control_top: not a circuit in any spec. +pub fn as_blk_dac_top() -> Blk { + return as_add(as_blk0(), as_nm()); +} + +/// neg_bottom_plate_logic: not a circuit in any spec. +pub fn as_blk_neg_plate() -> Blk { + return as_add(as_blk0(), as_nm()); +} + +/// integration: the literal -1 V to +1 V ramp of 8 conversions on the behavioural converter. +pub fn as_blk_integration() -> Blk { + var b : Blk = as_blk0(); + const r : Ramp = as_ramp_literal(); + b = as_add(b, as_verdict(true, as_chk_int_err(r.worst))); + b = as_add(b, as_verdict(true, as_chk_int_step(r.step))); + 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 { + 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())); + return t; +} + +; --- 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 an_unmeasured_row_is_never_a_pass_and_a_block_is_a_pass_only_with_every_row_measured_and_inside { + var yes : bool = true; + var no : bool = false; + assert_eq(as_verdict(no, yes), AS_NM); + assert_eq(as_verdict(no, no), AS_NM); + assert_eq(as_verdict(yes, yes), AS_PASS); + assert_eq(as_verdict(yes, no), AS_FAIL); + assert_eq(as_nm(), AS_NM); + // a block: a failing row fails it, a row not modelled keeps it from passing, only measured passes pass it, and no rows is not a pass + var pass_nm : Blk = as_add(as_add(as_blk0(), AS_PASS), AS_NM); + assert_eq(pass_nm.pass, 1); + assert_eq(pass_nm.nm, 1); + assert_eq(as_block_verdict(pass_nm), AS_NM); + assert_eq(as_block_verdict(as_add(pass_nm, AS_FAIL)), AS_FAIL); + assert_eq(as_block_verdict(as_add(as_add(as_blk0(), AS_PASS), AS_PASS)), AS_PASS); + assert_eq(as_block_verdict(as_blk0()), AS_NM); + assert_eq(as_block_verdict(as_add(as_blk0(), AS_NM)), AS_NM); + assert_eq(as_block_verdict(as_add(as_blk0(), AS_FAIL)), AS_FAIL); + // the three verdicts are three different numbers + assert(AS_PASS != AS_FAIL); + assert(AS_FAIL != AS_NM); + assert(AS_PASS != AS_NM); +} + +test the_inverter_threshold_is_measured_on_the_psp103_log_tables_and_the_delays_into_20_ff_are_not_modelled { + const lg : f64 = as_inv_vth_of(&TT_LG_N, &TT_LG_P); + const li : f64 = as_inv_vth_of(&TT_N, &TT_P); + // 0.68924 V on the log tables and 0.68925 V on the linear ones (ngspice's trip point is 0.689 V, mos_psp103.t27); the report's 0.69 V + assert lg > 0.68920; + assert lg < 0.68930; + assert fabs64(lg - li) < 0.00001; + assert fabs64(lg - R_INV_VTH_V) < 0.001; + assert(as_chk_inv_vth(lg)); + // the check accepts the report's value and refuses a broken one on either side + assert(as_chk_inv_vth(R_INV_VTH_V)); + var bad_lo : f64 = 0.4; + var bad_hi : f64 = 1.2; + assert(!as_chk_inv_vth(bad_lo)); + assert(!as_chk_inv_vth(bad_hi)); + // NEGATIVE CONTROL on the model: a PMOS that never conducts (a zero table in its slot) leaves the output low at every input but 0, so it never falls through Vdd / 2 and + // the reader returns its no-crossing value, -0.03125 V (one sweep step below zero) + var zero : [625]u32 = [0] ** 625; + const dead : f64 = as_inv_vth_of(&TT_N, &zero); + assert dead < 0.0; + assert dead > 0.0 - 0.0313; + assert(!as_chk_inv_vth(dead)); + // THE LIMIT OF THE CONTROL: the two tables in each other's slots move the trip point to 0.811 V, and the wide limit of 0.5 to 1.0 V accepts it (mos_psp103.t27 refuses it by + // the DC transfer and the delays); the same holds for every corner's trip point + const swapped : f64 = as_inv_vth_of(&TT_P, &TT_N); + assert(swapped > 0.8105); + assert(swapped < 0.8110); + assert(as_chk_inv_vth(swapped)); + // the delay check: the report's 81 and 100 ps pass, a delay out of range on either side is refused, and either one alone is enough + assert(as_chk_inv_delay(R_INV_FALL_PS, R_INV_RISE_PS)); + var fast : f64 = 0.5; + var slow : f64 = 1340.0; + assert(!as_chk_inv_delay(fast, R_INV_RISE_PS)); + assert(!as_chk_inv_delay(R_INV_FALL_PS, slow)); + // the block: one row PASS, one NOT MODELLED, so the block is NOT MODELLED + const b : Blk = as_blk_inverter(); + assert_eq(b.pass, 1); + assert_eq(b.fail, 0); + assert_eq(b.nm, 1); + 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 { + 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 + assert(!as_chk_nand(sl_nor_code(0, 0, 0), sl_nor_code(0, 1, 0), sl_nor_code(1, 0, 0), sl_nor_code(1, 1, 0))); + assert(!as_chk_nor(sl_nand_code(0, 0), sl_nand_code(0, 1), sl_nand_code(1, 0), sl_nand_code(1, 1))); + var s0 : usize = SL_S0; + var s1 : usize = SL_S1; + assert(!as_chk_nand(s1, s1, s0, s0)); + assert(!as_chk_nor(s1, s0, s0, s1)); + // the levels check (the report: 1.5 V for a 1 and microvolts for a 0): the report's own values pass, a droop of the 1 or a leak of the 0 is refused + var one : f64 = 1.5; + var low : f64 = 0.000002; + assert(as_chk_gate_levels(one, low)); + var sag : f64 = 1.2; + var leak : f64 = 0.2; + assert(!as_chk_gate_levels(sag, low)); + assert(!as_chk_gate_levels(one, leak)); + 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); +} + +test the_tgate_is_not_modelled_and_its_check_refuses_a_resistor_or_a_leak_out_of_range { + 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; + var leak : f64 = 5.0; + 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)); + const b : Blk = as_blk_tgate(); + assert_eq(b.pass, 0); + assert_eq(b.fail, 0); + assert_eq(b.nm, 3); + assert_eq(as_block_verdict(b), AS_NM); +} + +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); + assert(r.ok); + assert(s.ok); + // capture and hold: after each of the eleven Valid edges the CK outputs are a thermometer of that many, before the first all are low, and nothing moves between edges + assert(r.edge_ok); + assert(r.hold_ok); + // set and reset: Set puts all eleven high, releasing it holds them, clks high clears them + assert(s.set_ok); + assert(s.hold_ok); + assert(s.reset_ok); + assert(as_chk_dff_behaviour(r.edge_ok, r.hold_ok and s.hold_ok, s.set_ok, s.reset_ok)); + // clock-to-Q of the first stage: 546 ps (ngspice's own Valid to CK11 is 550 ps, sar_logic.t27); the report's 524 ps; 1 to 3000 ps + assert(r.c2q_ps > 540); + assert(r.c2q_ps < 552); + const c2q : f64 = (r.c2q_ps as f64); + assert(as_chk_dff_c2q(c2q)); + assert(as_chk_dff_c2q(R_DFF_C2Q_PS)); + var early : f64 = 0.4; + var late : f64 = 4000.0; + assert(!as_chk_dff_c2q(early)); + assert(!as_chk_dff_c2q(late)); + // NEGATIVE CONTROL on the model: the fifth dff with Set and Reset exchanged. The set row loses CK7, the clks reset sets it instead of clearing it, the order is wrong + const sw : SarRun = as_sar_run(true); + const ss : DffSr = as_dff_set_reset(true); + assert(sw.ok); + assert(ss.ok); + assert(!sw.edge_ok); + assert(!ss.set_ok); + assert(!ss.reset_ok); + assert(!as_chk_dff_behaviour(sw.edge_ok, sw.hold_ok and ss.hold_ok, ss.set_ok, ss.reset_ok)); + // and a wrong row refuses the check one at a time + var t : bool = true; + var f : bool = false; + assert(!as_chk_dff_behaviour(f, t, t, t)); + assert(!as_chk_dff_behaviour(t, f, t, t)); + assert(!as_chk_dff_behaviour(t, t, f, t)); + assert(!as_chk_dff_behaviour(t, t, t, f)); + // the levels: the gate-level model has none; a dff output 0.2 V off its rail is refused + var rail : f64 = 0.001; + var off : f64 = 0.2; + assert(as_chk_dff_levels(rail)); + assert(!as_chk_dff_levels(off)); + const b : Blk = as_blk_dff(); + assert_eq(b.pass, 2); + assert_eq(b.fail, 0); + assert_eq(b.nm, 1); + assert_eq(as_block_verdict(b), AS_NM); +} + +test the_comparator_decides_the_right_way_to_the_rails_and_its_decision_time_is_refused_by_the_10_ps_lower_limit { + const c : Cmp = as_cmp(&TT50_N, &TT50_P); + // the sign at +-2 mV: dp wins at +2 mV and dn at -2 mV + assert_eq(c.win_p, 1); + assert_eq(c.win_m, 0); + assert(as_chk_cmp_sign(c.win_p, c.win_m)); + // the end levels after 14 ps: the winner at 1.49996 V, the loser at 3.6 mV + assert(c.hi_v > 1.4999); + assert(c.hi_v < 1.5); + assert(c.lo_v > 0.003); + assert(c.lo_v < 0.004); + assert(as_chk_cmp_levels(c.hi_v, c.lo_v)); + // THE DECISION TIME: 5.35 ps (53.50 steps of 0.1 ps after the clock's 50 % point, sar_comparator_psp.t27), below the 10 ps limit: FAIL, by 4.65 ps + assert(c.dt_ps > 5.349); + assert(c.dt_ps < 5.351); + assert(!as_chk_cmp_time(c.dt_ps)); + assert(c.dt_ps < L_CMP_DT_LO_PS); + // the same on the 27 C tables (the report's temperature): 5.12 ps, also refused + const c27 : Cmp = as_cmp(&TT_N, &TT_P); + assert(c27.dt_ps > 5.11); + assert(c27.dt_ps < 5.13); + assert(!as_chk_cmp_time(c27.dt_ps)); + assert_eq(c27.win_p, 1); + assert_eq(c27.win_m, 0); + // the check is not always-FAIL: the report's 607 ps passes, and so would ngspice's own 523 ps on the golden comparator with its charge; 20 ns and a missed decision (-0.1) are refused + assert(as_chk_cmp_time(R_CMP_DT_PS)); + var ng : f64 = 523.032; + var long : f64 = 20000.0; + var none : f64 = 0.0 - 0.1; + assert(as_chk_cmp_time(ng)); + assert(!as_chk_cmp_time(long)); + assert(!as_chk_cmp_time(none)); + // NEGATIVE CONTROL on the model, the sign: the inputs wired the wrong way round is the same run read with the roles exchanged, and the sign check refuses it + assert(!as_chk_cmp_sign(c.win_m, c.win_p)); + // the levels check refuses a latch that stops half way, and a loser that does not fall + var half_hi : f64 = 1.2; + var half_lo : f64 = 0.3; + assert(!as_chk_cmp_levels(half_hi, c.lo_v)); + assert(!as_chk_cmp_levels(c.hi_v, half_lo)); + // the reset row is not run: the report's reset level (0 V, within 50 mV) is checkable but nothing of ours is fed to it + var z : f64 = 0.0; + var up : f64 = 1.5; + assert(as_chk_cmp_reset(z, z)); + assert(!as_chk_cmp_reset(up, z)); + const b : Blk = as_blk_comparator(); + assert_eq(b.pass, 2); + assert_eq(b.fail, 1); + assert_eq(b.nm, 1); + assert_eq(as_block_verdict(b), AS_FAIL); +} + +test the_tracking_error_is_measured_on_the_square_law_switch_and_the_hold_step_and_droop_are_not_modelled_because_it_has_no_charge_and_no_leak { + const lo : Sw = as_sw(0, SW_CB); + const hi : Sw = as_sw(2, SW_CB); + const mid : Sw = as_sw(1, SW_CB); + // the tracking error at the end of the track phase: 1.97 microvolts at 0.2 V and at 1.3 V (the fixed-point floor, ngspice has 0.03 and 0.05 uV), 5 mV allowed + assert(lo.te < 0.0 - 0.0000019); + assert(lo.te > 0.0 - 0.0000021); + 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 + 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)); + // 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; + assert(as_chk_sw_track(one_uv, one_uv)); + assert(!as_chk_sw_track(six, one_uv)); + assert(!as_chk_sw_track(one_uv, 0.0 - six)); + // WHY THE OTHER TWO ROWS ARE NOT MODELLED. The model returns a hold step of 0.18 uV and a droop of exactly 0: the square law has no gate capacitance and no leak, so there is + // nothing in it that could step or droop. Those numbers sit far inside 30 mV and 5 mV and would "pass" by having no effect; the report's own -1.6 and -1.7 mV are what a + // real switch's charge injection does, and the check accepts them and refuses 50 mV + assert(fabs64(mid.inj) < 0.000001); + assert(fabs64(mid.inj) > 0.0000001); + assert(fabs64(mid.droop) < 0.000000001); + assert(as_chk_sw_hold(R_SW_HOLD_A_V, R_SW_HOLD_B_V)); + var big : f64 = 0.05; + assert(!as_chk_sw_hold(big, R_SW_HOLD_B_V)); + assert(!as_chk_sw_hold(R_SW_HOLD_A_V, 0.0 - big)); + assert(as_chk_sw_droop(one_uv)); + assert(!as_chk_sw_droop(six)); + const b : Blk = as_blk_switch(); + assert_eq(b.pass, 1); + assert_eq(b.fail, 0); + assert_eq(b.nm, 2); + assert_eq(as_block_verdict(b), AS_NM); +} + +test the_clock_generators_delays_ratio_and_stop_are_measured_on_the_gate_level_model_and_a_clock_that_ignores_its_trim_is_refused { + const g : Clk = as_clk_golden(); + const slow : f64 = (g.slow_ps as f64) / 1000.0; + const fast : f64 = (as_clk_delay_ps(7) as f64) / 1000.0; + // slow (trim 000) 16.145 ns, ngspice 16.147 ns; fast (trim 111) 9.822 ns, ngspice 9.669 ns (sar_logic.t27's bound is 6 %); ratio 1.644, the report's 1.64 + assert(slow > 16.1); + assert(slow < 16.2); + assert(fast > 9.7); + assert(fast < 9.9); + assert(as_chk_clk_slow(slow)); + assert(as_chk_clk_fast(fast)); + const ratio : f64 = slow / fast; + assert(ratio > 1.63); + assert(ratio < 1.66); + assert(as_chk_clk_ratio(ratio)); + // the stop: the clock's last fall comes 5.70 ns after the eleventh Valid rise (ngspice 5.81 ns); the report's 10.7 ns is a different definition, both are inside 30 ns + const stop : f64 = (g.stop_ps as f64) / 1000.0; + assert(stop > 5.6); + assert(stop < 5.8); + assert(as_chk_clk_stop(stop)); + assert(as_chk_clk_stop(R_CLK_STOP_NS)); + // the checks accept the report's values and refuse a delay out of range on either side + assert(as_chk_clk_slow(R_CLK_SLOW_NS)); + assert(as_chk_clk_fast(R_CLK_FAST_NS)); + assert(as_chk_clk_ratio(R_CLK_RATIO)); + var tiny : f64 = 0.01; + var huge : f64 = 60.0; + assert(!as_chk_clk_slow(tiny)); + assert(!as_chk_clk_slow(huge)); + assert(!as_chk_clk_fast(tiny)); + assert(!as_chk_clk_fast(huge)); + var flat : f64 = 1.1; + assert(!as_chk_clk_ratio(flat)); + var runaway : f64 = 31.0; + var never : f64 = 0.0 - 1.0; + assert(!as_chk_clk_stop(runaway)); + assert(!as_chk_clk_stop(never)); + // NEGATIVE CONTROL on the model: a clock that ignores its trim (code 0 read for the fast setting) has a ratio of exactly 1 and is refused + const same : f64 = (as_clk_delay_ps(0) as f64) / 1000.0; + assert(same > 16.1); + assert(!as_chk_clk_ratio(slow / same)); + const b : Blk = as_blk_clock(); + assert_eq(b.pass, 4); + assert_eq(b.fail, 0); + assert_eq(b.nm, 0); + assert_eq(as_block_verdict(b), AS_PASS); +} + +test the_sar_logic_adds_one_output_per_valid_edge_ck11_first_and_spans_200_ns_and_a_swapped_dff_is_refused { + const r : SarRun = as_sar_run(false); + assert(r.ok); + // one more CK high per Valid edge, CK11 first, the thermometer in order, held between edges; 1, 4 and 11 high after 1, 4 and 11 edges + assert(r.edge_ok); + assert(r.hold_ok); + assert_eq(r.c1, 1); + assert_eq(r.c4, 4); + assert_eq(r.c11, 11); + assert(as_chk_sar_count(r.edge_ok, r.hold_ok, r.c1, r.c4, r.c11)); + // CK11 to CK1 in 200.126 ns: ten Valid periods of 20 ns plus the 126 ps by which CK1's load makes it later than CK11 + assert(r.span_ps > 200100); + assert(r.span_ps < 200150); + const span : f64 = (r.span_ps as f64) / 1000.0; + assert(as_chk_sar_span(span)); + // the check refuses a span out of range on either side, and a wrong count, one at a time + var short : f64 = 198.0; + var long : f64 = 202.0; + assert(!as_chk_sar_span(short)); + assert(!as_chk_sar_span(long)); + var t : bool = true; + assert(!as_chk_sar_count(t, t, 2, 4, 11)); + assert(!as_chk_sar_count(t, t, 1, 5, 11)); + assert(!as_chk_sar_count(t, t, 1, 4, 10)); + assert(!as_chk_sar_count(false, t, 1, 4, 11)); + assert(!as_chk_sar_count(t, false, 1, 4, 11)); + // NEGATIVE CONTROL on the model: the fifth dff with Set and Reset exchanged starts CK7 high, so two outputs are high after the first edge, five after the fourth, the + // order is wrong, and the same check refuses the run + const sw : SarRun = as_sar_run(true); + assert(sw.ok); + assert_eq(sw.c1, 2); + assert_eq(sw.c4, 5); + assert_eq(sw.c11, 11); + assert(!sw.edge_ok); + assert(!as_chk_sar_count(sw.edge_ok, sw.hold_ok, sw.c1, sw.c4, sw.c11)); + const b : Blk = as_blk_sar_logic(); + assert_eq(b.pass, 2); + assert_eq(b.fail, 0); + assert_eq(b.nm, 0); + assert_eq(as_block_verdict(b), AS_PASS); +} + +test the_dac_control_dac_control_top_and_neg_bottom_plate_logic_are_not_modelled_and_their_checks_refuse_a_swapped_plate_and_a_wrong_code { + // DAC_control: the report's levels pass (0.75 V idle, 0 V after decision 1, 1.5 V after decision 0); the two decisions swapped, a plate stuck at the rail while idle, and a + // half-way level are refused + var vcm : f64 = 0.75; + var rail_hi : f64 = 1.5; + var rail_lo : f64 = 0.0; + assert(as_chk_dac_level(vcm, rail_lo, rail_hi)); + assert(!as_chk_dac_level(vcm, rail_hi, rail_lo)); + assert(!as_chk_dac_level(rail_hi, rail_lo, rail_hi)); + var mid_lvl : f64 = 1.1; + assert(!as_chk_dac_level(vcm, rail_lo, mid_lvl)); + // DAC_control_top: the report's code 1430 and plate code 617 pass; they are complements in 11 bits (1430 + 617 = 2047), so a wrong code, a wrong plate code and a code with two + // bits swapped are refused + var code : u32 = L_TOP_CODE; + var plate : u32 = L_TOP_PLATE; + assert_eq(code + plate, 2047); + assert(as_chk_dac_top(code, plate)); + assert(!as_chk_dac_top(code, plate + 1)); + assert(!as_chk_dac_top(code + 1, plate)); + var swapped_bits : u32 = code ^ 6; + assert(swapped_bits != code); + assert(!as_chk_dac_top(swapped_bits, plate)); + assert(!as_chk_dac_top(plate, code)); + // neg_bottom_plate_logic: VCM within 0.7 to 0.8 V + assert(as_chk_neg_plate(vcm)); + assert(!as_chk_neg_plate(rail_hi)); + assert(!as_chk_neg_plate(rail_lo)); + // none of the three blocks is fed a value of ours: each is one NOT MODELLED row, never a pass + const b1 : Blk = as_blk_dac_control(); + const b2 : Blk = as_blk_dac_top(); + const b3 : Blk = as_blk_neg_plate(); + assert_eq(b1.pass + b2.pass + b3.pass, 0); + assert_eq(b1.fail + b2.fail + b3.fail, 0); + assert_eq(b1.nm + b2.nm + b3.nm, 3); + assert_eq(as_block_verdict(b1), AS_NM); + assert_eq(as_block_verdict(b2), AS_NM); + assert_eq(as_block_verdict(b3), AS_NM); +} + +test the_integration_ramp_is_measured_on_the_behavioural_converter_and_a_halved_msb_capacitor_is_refused { + const r : Ramp = as_ramp_literal(); + // the literal ramp, -1 V to +1 V in 8 points of 285.7 mV: the worst error is 18.5 LSB (at +1 V: the converter's gain is 1.9 % high), the smallest step 198 LSB (ideal 195) + assert(r.worst > 18.4); + assert(r.worst < 18.6); + assert(r.step > 197.5); + assert(r.step < 198.5); + assert_eq(r.first, 333); + assert_eq(r.last, 1724); + assert(as_chk_int_err(r.worst)); + assert(as_chk_int_step(r.step)); + // the checks accept the report's own numbers and refuse numbers out of range on either side + assert(as_chk_int_err(R_INT_ERR_LSB)); + assert(as_chk_int_step(R_INT_STEP_LSB)); + var big : f64 = 100.0; + var tiny : f64 = 40.0; + var wide : f64 = 300.0; + assert(!as_chk_int_err(big)); + assert(!as_chk_int_step(tiny)); + assert(!as_chk_int_step(wide)); + // NEGATIVE CONTROL on the model: the MSB capacitor halved (8 units instead of 16) gives a worst error of 237 LSB + var bad : [10]u32 = UNITS; + bad[9] = 8; + const kb : [20]u32 = model_k(&bad, 1.0, CP_FIT); + const rb : Ramp = as_ramp(&kb, q_sm(off_late_v()), 0.0 - 1.0, 2.0 / 7.0); + assert(rb.worst > 236.0); + assert(rb.worst < 238.0); + assert(!as_chk_int_err(rb.worst)); + // a converter that is the ideal capacitors with no parasitic and no offset is the ideal quantizer: the worst error is the half LSB of the bin centre + const k0 : [20]u32 = model_k(&UNITS, 1.0, 0.0); + const ri : Ramp = as_ramp(&k0, 0, 0.0 - 1.0, 2.0 / 7.0); + assert(ri.worst < 0.51); + // CONTEXT, not a verdict: the ramp rebuilt from the report's ideal end codes (517 and 1579, spacing 151.7 LSB). Our model gives codes 512 and 1595 where the report has 510 + // and 1593, a worst error of 16.0 LSB where it has 13.8, and a smallest step of 154 LSB where it has 154 (ideal 152). The report's inputs are inferred, so these are four numbers + // that agree to within a few LSB, not a check of the model against the report + const rr : Ramp = as_ramp_report_like(); + assert_eq(rr.first, 512); + assert_eq(rr.last, 1595); + assert(rr.worst > 15.9); + assert(rr.worst < 16.1); + assert(rr.step > 153.5); + assert(rr.step < 154.5); + var d_first : u32 = rr.first - R_INT_FIRST_CODE; + var d_last : u32 = rr.last - R_INT_LAST_CODE; + assert(d_first <= 3); + assert(d_last <= 3); + assert(as_chk_int_err(rr.worst)); + assert(as_chk_int_step(rr.step)); + const b : Blk = as_blk_integration(); + assert_eq(b.pass, 2); + assert_eq(b.fail, 0); + assert_eq(b.nm, 0); + assert_eq(as_block_verdict(b), AS_PASS); +} + +test the_corners_smoke_suite_is_not_modelled_and_its_checks_refuse_a_delay_out_of_range { + // the model has the inverter in five corners at 27 C and the comparator in tt at 27 C and 50 C, none at -40 or 125 C: the grid of the report cannot be run + var inv_lo : f64 = 62.0; + var inv_hi : f64 = 124.0; + var cmp_lo : f64 = 467.0; + var cmp_hi : f64 = 829.0; + assert(as_chk_corner_inv(inv_lo)); + assert(as_chk_corner_inv(inv_hi)); + assert(as_chk_corner_cmp(cmp_lo)); + assert(as_chk_corner_cmp(cmp_hi)); + var fast : f64 = 40.0; + var slow : f64 = 200.0; + assert(!as_chk_corner_inv(fast)); + assert(!as_chk_corner_inv(slow)); + // the no-charge comparator's 5.35 ps is two orders of magnitude under the lower limit + const c : Cmp = as_cmp(&TT50_N, &TT50_P); + assert(!as_chk_corner_cmp(c.dt_ps)); + assert_eq(as_nm(), AS_NM); +} + +test the_thirteen_blocks_count_3_pass_1_fail_and_9_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_tgate()), AS_NM); + assert_eq(as_block_verdict(as_blk_dff()), AS_NM); + assert_eq(as_block_verdict(as_blk_comparator()), AS_FAIL); + assert_eq(as_block_verdict(as_blk_switch()), AS_NM); + assert_eq(as_block_verdict(as_blk_clock()), AS_PASS); + assert_eq(as_block_verdict(as_blk_sar_logic()), AS_PASS); + assert_eq(as_block_verdict(as_blk_dac_control()), AS_NM); + assert_eq(as_block_verdict(as_blk_dac_top()), AS_NM); + assert_eq(as_block_verdict(as_blk_neg_plate()), AS_NM); + 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.fail, 1); + assert_eq(t.nm, 9); + assert_eq(t.pass + t.fail + t.nm, AS_N_BLOCKS); +} diff --git a/specs/analog/sar_kick.t27 b/specs/analog/sar_kick.t27 index 0aef5c7fa5..834c41d921 100644 --- a/specs/analog/sar_kick.t27 +++ b/specs/analog/sar_kick.t27 @@ -266,7 +266,7 @@ pub const SCAN_STEP : f64 = 0.000005; pub const SCAN_N : usize = 1000; /// A threshold: the middle and the half width of the first run of grid points with the fewest errors, and that fewest number of errors. -pub struct KickThr { mid: f64, half: f64, errors: u32 } +pub struct KickThr { mid: f64, hw: f64, errors: u32 } /// The threshold (VDP - VDN at which the latch flips, V) at decision `idx`: the grid points with the fewest disagreeing decisions, the first contiguous run of them. pub fn late_thr(idx: u32) -> KickThr { @@ -291,8 +291,8 @@ pub fn late_thr(idx: u32) -> KickThr { j = j + 1; } const mid : f64 = SCAN_LO + SCAN_STEP * (((first + last) as f64) / 2.0); - const half : f64 = SCAN_STEP * (((last - first) as f64) / 2.0); - return KickThr { mid: mid, half: half, errors: best }; + const hw : f64 = SCAN_STEP * (((last - first) as f64) / 2.0); + return KickThr { mid: mid, hw: hw, errors: best }; } /// The fewest disagreeing decisions one threshold can make for decisions 9 and 10 together. @@ -427,11 +427,11 @@ test the_last_two_decisions_are_bracketed_by_dc_conversions_and_their_offset_is_ // index 9: no decision disagrees with the threshold; it is -6.6575 mV, bracketed to 0.05 mV assert_eq(t9.errors, 0); assert fabs64(t9.mid + 0.0066575) < 0.000001; - assert(t9.half < 0.00005); + assert(t9.hw < 0.00005); // index 10: one decision (a different bit path, -7.061 mV) disagrees; the rest bracket -6.895 mV to 5 uV assert_eq(t10.errors, 1); assert fabs64(t10.mid + 0.006895) < 0.000001; - assert(t10.half < 0.00001); + assert(t10.hw < 0.00001); assert(t10.mid < t9.mid); // the model's late offset is their mean assert fabs64(off_late_v() - 0.0067762) < 0.0000001; @@ -447,10 +447,10 @@ test the_last_two_decisions_are_bracketed_by_dc_conversions_and_their_offset_is_ const t7 : KickThr = late_thr(7); assert_eq(t8.errors, 0); assert_eq(t7.errors, 0); - assert(t8.half < 0.0006); - assert(t7.half < 0.0006); - assert(fabs64(t8.mid - (0.0 - off_late_v())) < t8.half + 0.0004); - assert(fabs64(t7.mid - (0.0 - off_late_v())) < t7.half + 0.0004); + assert(t8.hw < 0.0006); + assert(t7.hw < 0.0006); + assert(fabs64(t8.mid - (0.0 - off_late_v())) < t8.hw + 0.0004); + assert(fabs64(t7.mid - (0.0 - off_late_v())) < t7.hw + 0.0004); } test negative_control_one_threshold_for_decisions_9_and_10_together_disagrees_with_the_decisions {