diff --git a/.trinity/seals/analog_AnalogBench.json b/.trinity/seals/analog_AnalogBench.json index 226220d291..e701e36761 100644 --- a/.trinity/seals/analog_AnalogBench.json +++ b/.trinity/seals/analog_AnalogBench.json @@ -1,28 +1,28 @@ { - "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", + "built_by": "t27c-bootstrap@0.5.2+2d3203e90", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:89195447f8fee90c8db58f4eee50988ada367390d06d6c41e171c817e16f2680", - "gen_hash_rust": "sha256:c26da9d6dd68f89c6d9e3d5b8b1ee978836bb638dd9130c6ad1d02d4f14b2b06", - "gen_hash_verilog": "sha256:1fb746ee68114b0e7506ec46cc0277e02a9ac9a7f2236a4f894032ede144f252", - "gen_hash_zig": "sha256:3cc22bec9ee00c4031f82679cb6acabba759bee6dd4538ffb35dbd5a025f9d14", + "gen_hash_c": "sha256:380564db8a28fc9186d2e01e2e40c1d526677058f349c2f7f4ab680e70f5753d", + "gen_hash_rust": "sha256:038516f9da32e85c26a93b93b0789a0822c0078d58d74327334d139c80540d87", + "gen_hash_verilog": "sha256:ef2bafcffe8b52ebb17fac57e627903a47507cf1a97b31ab5615c46384ebeec4", + "gen_hash_zig": "sha256:6d4db20d1b7a8e8a9a59ae182a2a192168175055c17576789f93bd00748acbb2", "module": "AnalogBench", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T16:09:11Z", + "sealed_at": "2026-10-20T09:40:52Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:daa6ede3a542d8bfe2ce93995f3808f0549037a0b63538f2179ec107dc2b2f60", + "spec_hash": "sha256:9a7af995e2511e8d1aeb06e5812f7c320dfe6e3c98bbfcea6bf5d9821c1851db", "spec_path": "specs/analog/bench.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 11, - "total": 11, + "passed": 14, + "total": 14, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/specs/analog/bench.t27 b/specs/analog/bench.t27 index ceeab4918d..abde1bd363 100644 --- a/specs/analog/bench.t27 +++ b/specs/analog/bench.t27 @@ -76,13 +76,47 @@ ; include those negative controls. When the rule does say yes, the sentence that goes into a document is the one above with ; its definitions and the break-even count, not "N times faster than SPICE". ; - +; THE PSP103 INVERTER, WALL TIME PER STEP (#11291, epic #10486). The 500 fF PSP103 inverter of specs/analog/mos_psp103.t27 on the fixed +; 100 ps backward Euler grid was timed in ngspice at two lengths: INV_ROW_40N, the 40.4 ns run the board does (412 time points), and +; INV_ROW_4US, 4.0004 us (40013 points). The second exists because the first is 88 percent process start-up: its "Total elapsed time" +; is 80 ms for a .tran of 9.4 ms, so the cost of a step cannot be read from it. The deck of the second is +; specs/analog/decks/psp103_inv_be_500f_4us_wall.cir: the committed psp103_inv_be_500f.cir with the .tran stop 4.0004u, `set +; num_threads=1` before `run`, `rusage all` after it, and the print block removed (all four are named in its header). The 40 ns row is +; that deck with the stop 40.4n. +; Measured 2026-10-20 on the remote x86 reference host (x86, Intel i7-8700 3.2 GHz, 12 CPUs, load average 0.05 before the job), as a +; container job, ngspice-47 with psp103.osdi (SPARSE 1.3), ONE thread. Command: ngspice -b , IHP_PDK and IHP_OSDI set. The numbers +; are ngspice's own, from the `rusage all` lines: "Transient analysis time" (6 digits as printed), "Transient timepoints" and "Total +; elapsed time" (1 ms resolution; it holds the netlist and OSDI loading and the run). Each row is ONE run, not a median: the batch log +; holds two runs of the .tran and its summary at exit is cumulative (1.620 s elapsed for the 4 us deck); the row is the first run. The +; second run's total analysis time (the cumulative figure less the first's) is below the first's by 3.2 percent (4 us) and 1.1 percent +; (40 ns), the only repeat measured. +; What the tests derive: the real-time ratio, ngspice's transient analysis time over the circuit time it simulated, is 195623 for the 4 us +; run and 233201 for the 40 ns run (seconds of ngspice per second of circuit), and the time of one time point is 19557 ns and 22867 ns. +; Both runs print the same tplh and tphl (1.44352 ns and 21.1183 ns from time 0; delays 1.34352 and 1.01830 ns after the input edges at +; 0.1 ns and 20.1 ns), which are the ngspice delays recorded in mos_psp103.t27 for the tt corner: the same circuit, the same answer. +; THE BOARD'S STEP, NEXT TO IT, NOT A SPEED CLAIM. specs/fpga/psp103_inv_ax7203.t27 runs the same circuit as 400 steps of 100 ps, 1700 to +; 1960 clocks a step at 100 MHz: 17.0 to 19.6 us a step, a real-time ratio of 170000 to 196000. Next to ngspice's 19.56 us a point (4 us +; run) and 22.87 us (40 ns run) the numbers are of the same size, and this file draws no conclusion from that, because they are not the +; cost of the same work. (1) ngspice evaluates the full PSP103 model (the OSDI code) in every Newton iteration of every point; the board +; reads tables of it with exact-integer arithmetic, a table model that is within 0.02 percent of ngspice's two delays at tt, not the model. +; (2) ngspice ran on one thread of a general-purpose CPU; the board is a bit-serial, unpipelined machine at 100 MHz (a shift-and-add +; multiplier and a restoring divider at one bit a clock), idle 99.7 percent of the time while its UART line goes out. (3) The board's clocks +; are the spec's machine run by the tests, cycle exact, not a stopwatch reading; no board wall time with readout is stored (one receipt +; line takes 5.7 ms on the UART, about 300 times the step) and the bitstream build time is not measured (BUILD_US is 0). So +; may_claim_speedup is false for both inverter rows, the tests say so, and no document of #10486 may call the board faster or slower than +; ngspice on the strength of these rows. +; - ; WHAT A PASS SAYS. The stored numbers are consistent with each other and with the closed backward Euler recurrence of the RC ; (the recorded ngspice values at steps 99 and 199 are that recurrence to 1e-14), the derived ratios are what the text says, the -; claim rule behaves on its negative controls, and the PWL deck is measurably off the grid while the B-source decks are not. +; claim rule behaves on its negative controls, and the PWL deck is measurably off the grid while the B-source decks are not. For the +; inverter rows: the real-time ratios and the per-point times derive from the stored numbers, the two runs are one circuit with one +; answer, the board's step time is derived next to them, and the claim rule is false for both rows. ; WHAT IT DOES NOT SAY. It does not re-run ngspice or the board; it holds one day's measurement. Nothing about another machine, ; ngspice version, solver (KLU) or option set, nothing about a larger circuit than 10 unknowns, nothing about power, nothing ; about the bitstream build time (not measured), nothing about the board for any circuit but M0, and nothing about accuracy -; against ngspice with the PSP103 or any PDK model (M3 on). +; against ngspice with the PSP103 or any PDK model (M3 on). The inverter rows are one run each, on one host, on one thread, tt corner +; only; they say nothing about a multi-threaded ngspice, another host, or the cost of a step at another node count, and nothing about +; the board's wall time or whether the board is faster than ngspice: they put two step times side by side and stop there. ; ASCII only, English only. ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -164,6 +198,43 @@ pub const M0_BAUD : u32 = 115200; /// The bitstream build time in microseconds; 0 means not measured. No receipt in this repository states one. pub const BUILD_US : u64 = 0; +; --- ngspice, the PSP103 inverter at 500 fF, 40.4 ns and 4.0004 us (#11291) -------------------------------------- +pub const INV_NGSPICE : str = "ngspice-47, psp103.osdi, SPARSE 1.3, one thread (set num_threads=1)"; +pub const INV_HOST : str = "x86, Intel i7-8700 3.2 GHz"; +pub const INV_DATE : str = "2026-10-20"; +/// Host threads ngspice used. +pub const INV_THREADS : u32 = 1; +pub const INV_ROWS : usize = 2; +pub const INV_ROW_40N : usize = 0; +pub const INV_ROW_4US : usize = 1; +/// The fixed grid of both decks in picoseconds (tmax = dt = 100 ps). +pub const INV_DT_PS : u32 = 100; +/// The .tran stop, which is the circuit time simulated, in picoseconds: 40.4 ns and 4.0004 us. +pub const INV_CIRCUIT_PS : [2]u64 = [40400, 4000400]; +/// ngspice "Transient timepoints" (all accepted, none rejected). +pub const INV_POINTS : [2]u32 = [412, 40013]; +/// ngspice "Transient analysis time" in nanoseconds, as printed to 6 digits: 0.00942136 s and 0.782573 s. +pub const INV_TRAN_NS : [2]u64 = [9421360, 782573000]; +/// ngspice "Total elapsed time" at the rusage line in microseconds (1 ms resolution): 0.080 s and 0.862 s. +pub const INV_ELAPSED_US : [2]u32 = [80000, 862000]; +/// The .meas tplh and tphl of both runs (the same two numbers in both logs) in femtoseconds from time 0: 1.44352 ns and 21.1183 ns. +pub const INV_TPLH_FS : u64 = 1443520; +pub const INV_TPHL_FS : u64 = 21118300; +/// The input edges the two delays start from, in femtoseconds: 0.1 ns and 20.1 ns. +pub const INV_EDGE_UP_FS : u64 = 100000; +pub const INV_EDGE_DOWN_FS : u64 = 20100000; + +; --- the board's step for the same circuit, next to the rows above and not a speed claim (#11291) ------------------- +/// specs/fpga/psp103_inv_ax7203.t27 (header and the test on the longest step): 400 steps of INV_DT_PS, 1700 to 1960 clocks a step at 100 MHz. +pub const BOARD_INV_STEPS : u32 = 400; +pub const BOARD_INV_CLOCKS_MIN : u32 = 1700; +pub const BOARD_INV_CLOCKS_MAX : u32 = 1960; +pub const BOARD_INV_MHZ : u32 = 100; +/// The board top exists, but no board wall time with readout is stored here, so the claim rule sees BOARD_PENDING, never BOARD_MEASURED. +pub const BOARD_INV_STATE : u8 = BOARD_PENDING; +/// Board wall time including readout in microseconds; 0: none is stored. +pub const BOARD_INV_WALL_US : u32 = 0; + ; --- the rule ------------------------------------------------------------------------------------ /// Microseconds the UART needs for n characters at the given baud (8N1: 10 bits each). @@ -211,6 +282,35 @@ pub fn fabs64(x: f64) -> f64 { return x; } +/// |a - b| for unsigned integers. +pub fn absdiff64(a: u64, b: u64) -> u64 { + if (a >= b) { return a - b; } + return b - a; +} + +/// Nanoseconds of a run of clocks at mhz megahertz (clocks_us rounds down to whole microseconds). +pub fn clocks_ns(clocks: u32, mhz: u32) -> u32 { + return clocks * 1000 / mhz; +} + +/// Nanoseconds to whole microseconds, rounded down. +pub fn ns_to_us(ns: u64) -> u32 { + return (ns / 1000) as u32; +} + +/// The real-time ratio: nanoseconds of computing per nanosecond of circuit, from tran_ns of computing for circuit_ps picoseconds of +/// circuit, rounded down; 0 when the circuit time is 0. +pub fn realtime_ratio(tran_ns: u64, circuit_ps: u64) -> u64 { + if (circuit_ps == 0) { return 0; } + return (tran_ns * 1000) / circuit_ps; +} + +/// The time of one time point in nanoseconds, rounded down; 0 when there are no points. +pub fn per_point_ns(tran_ns: u64, points: u32) -> u64 { + if (points == 0) { return 0; } + return tran_ns / (points as u64); +} + ; --- tests --------------------------------------------------------------------------------------- test the_fixed_decks_reproduce_backward_euler_to_f64_rounding { @@ -346,3 +446,91 @@ test the_break_even_count_is_stated_only_with_a_measured_build { assert break_even_runs(100000000, 9000, 9387) == 0; assert break_even_runs(100000000, 9387, 9387) == 0; } + +test the_inverter_wall_rows_derive_the_real_time_ratio_and_the_step_time { + // 0.782573 s of .tran for 4.0004 us of circuit, 0.00942136 s for 40.4 ns. + const r4 : u64 = realtime_ratio(INV_TRAN_NS[INV_ROW_4US], INV_CIRCUIT_PS[INV_ROW_4US]); + const r40 : u64 = realtime_ratio(INV_TRAN_NS[INV_ROW_40N], INV_CIRCUIT_PS[INV_ROW_40N]); + assert r4 == 195623; + assert r40 == 233201; + assert absdiff64(r4, 195600) <= 100; + assert absdiff64(r40, 233200) <= 200; + // The time of one time point: about 19.56 us and 22.87 us. + const p4 : u64 = per_point_ns(INV_TRAN_NS[INV_ROW_4US], INV_POINTS[INV_ROW_4US]); + const p40 : u64 = per_point_ns(INV_TRAN_NS[INV_ROW_40N], INV_POINTS[INV_ROW_40N]); + assert p4 == 19557; + assert p40 == 22867; + assert absdiff64(p4, 19560) <= 10; + assert absdiff64(p40, 22870) <= 10; + // The shorter run costs 17 percent more a point; the 4 us row is the one for the cost of a step. + assert ratio_permille((p40 as u32), (p4 as u32)) == 1169; + // Guards: no circuit time and no points give 0, not a division by zero. + assert realtime_ratio(1, 0) == 0; + assert per_point_ns(1, 0) == 0; + assert absdiff64(3, 5) == 2; + assert absdiff64(5, 3) == 2; +} + +test the_two_inverter_runs_are_one_circuit_and_the_long_one_is_mostly_simulation { + assert INV_THREADS == 1; + assert INV_ROWS == 2; + var i : usize = 0; + while (i < INV_ROWS) { + // On the 100 ps grid the run has circuit time / dt points, plus the opening ramp's (8 and 9 here), never fewer. + const steps : u64 = INV_CIRCUIT_PS[i] / (INV_DT_PS as u64); + assert (INV_POINTS[i] as u64) >= steps; + assert (INV_POINTS[i] as u64) <= steps + 25; + // The process holds the .tran, so its elapsed time is longer than the .tran's. + assert INV_ELAPSED_US[i] > ns_to_us(INV_TRAN_NS[i]); + i = i + 1; + } + assert (INV_POINTS[INV_ROW_4US] as u64) - (INV_CIRCUIT_PS[INV_ROW_4US] / (INV_DT_PS as u64)) == 9; + assert (INV_POINTS[INV_ROW_40N] as u64) - (INV_CIRCUIT_PS[INV_ROW_40N] / (INV_DT_PS as u64)) == 8; + // Start-up, loading and everything outside the .tran: 88 percent of the 40 ns run, 9 percent of the 4 us run. + assert startup_permille(INV_ELAPSED_US[INV_ROW_40N], ns_to_us(INV_TRAN_NS[INV_ROW_40N])) == 882; + assert startup_permille(INV_ELAPSED_US[INV_ROW_4US], ns_to_us(INV_TRAN_NS[INV_ROW_4US])) == 92; + // The same circuit and the same answer: the two delays are ngspice's tt delays recorded in mos_psp103.t27 (1.34352 ns and 1.01830 ns). + assert INV_TPLH_FS - INV_EDGE_UP_FS == 1343520; + assert INV_TPHL_FS - INV_EDGE_DOWN_FS == 1018300; + // The 4 us run is 99 times the circuit time of the 40 ns run. + assert INV_CIRCUIT_PS[INV_ROW_4US] / INV_CIRCUIT_PS[INV_ROW_40N] == 99; +} + +test the_board_step_is_next_to_ngspices_and_is_not_a_speed_claim { + // 1700 and 1960 clocks at 100 MHz: 17.0 and 19.6 us a step of 100 ps, a real-time ratio of 170000 and 196000. + const board_min : u32 = clocks_ns(BOARD_INV_CLOCKS_MIN, BOARD_INV_MHZ); + const board_max : u32 = clocks_ns(BOARD_INV_CLOCKS_MAX, BOARD_INV_MHZ); + assert board_min == 17000; + assert board_max == 19600; + assert realtime_ratio((board_min as u64), (INV_DT_PS as u64)) == 170000; + assert realtime_ratio((board_max as u64), (INV_DT_PS as u64)) == 196000; + // The four step times are of the same size (10 to 30 us); the file draws no conclusion from that. + const p4 : u64 = per_point_ns(INV_TRAN_NS[INV_ROW_4US], INV_POINTS[INV_ROW_4US]); + const p40 : u64 = per_point_ns(INV_TRAN_NS[INV_ROW_40N], INV_POINTS[INV_ROW_40N]); + assert p4 > 10000; + assert p4 < 30000; + assert p40 > 10000; + assert p40 < 30000; + assert board_min > 10000; + assert board_max < 30000; + // No claim: the board has no wall time with readout and the bitstream build is not measured. + assert BOARD_INV_STATE != BOARD_MEASURED; + assert BOARD_INV_STATE == BOARD_PENDING; + assert BOARD_INV_WALL_US == 0; + assert BUILD_US == 0; + var i : usize = 0; + while (i < INV_ROWS) { + assert !may_claim_speedup(BOARD_INV_STATE, INV_ELAPSED_US[i], BOARD_INV_WALL_US, BUILD_US); + // Forcing the state to measured does not help: a board wall time of 0 and an unmeasured build each block it. + assert !may_claim_speedup(BOARD_MEASURED, INV_ELAPSED_US[i], BOARD_INV_WALL_US, BUILD_US); + i = i + 1; + } + // The compute-only time of the board's whole 400-step run (7840 us at the slowest step, no readout) is not a wall time with readout: + // handed to the rule as one against the 40 ns row's 80000 us it would pass the ratio test, and the missing build still blocks it. + const compute_us : u32 = clocks_us(BOARD_INV_STEPS * BOARD_INV_CLOCKS_MAX, BOARD_INV_MHZ); + assert compute_us == 7840; + assert INV_ELAPSED_US[INV_ROW_40N] >= compute_us; + assert !may_claim_speedup(BOARD_MEASURED, INV_ELAPSED_US[INV_ROW_40N], compute_us, BUILD_US); + assert !may_claim_speedup(BOARD_PENDING, INV_ELAPSED_US[INV_ROW_40N], compute_us, 60000000); + assert break_even_runs(BUILD_US, INV_ELAPSED_US[INV_ROW_40N], compute_us) == 0; +} diff --git a/specs/analog/decks/psp103_inv_be_500f_4us_wall.cir b/specs/analog/decks/psp103_inv_be_500f_4us_wall.cir new file mode 100644 index 0000000000..1312901444 --- /dev/null +++ b/specs/analog/decks/psp103_inv_be_500f_4us_wall.cir @@ -0,0 +1,41 @@ +IHP SG13G2 inverter, 500 fF, backward Euler on a fixed 100 ps grid, corner mos_tt +* specs/analog/decks/psp103_inv_be_500f_4us_wall.cir -- the wall-time deck of specs/analog/bench.t27 (#11291, epic #10486). It is +* specs/analog/decks/psp103_inv_be_500f.cir with three changes to the netlist and the control block, and one removal; the circuit, the +* models, the options and the source are the same lines: +* 1. .tran stop 4.0004u instead of 40.4n: 40004 steps of 100 ps instead of 404, so that ngspice's "Transient analysis time" is most +* of its process time (at 40.4 ns the process is mostly start-up and the .tran is about a tenth of it); +* 2. "set num_threads=1" before "run": one host thread, so the time is one core's; +* 3. "rusage all" after "run": ngspice prints its own time and memory use, among them "Transient analysis time", "Transient +* timepoints" and "Total elapsed time", which are the numbers bench.t27 stores. +* Removed: the print block of M3c (set numdgt .. print ts ys .. echo END tran_be). It is the waveform of the 40.4 ns run; the run that +* was timed did not print it, and this deck is the run that was timed. +* The 40 ns row of bench.t27 is this deck with the stop 40.4n (the committed deck with changes 2 and 3 and without its print block). +* Paths are NOT in this file: ngspice reads IHP_PDK and IHP_OSDI from the environment (see psp103_tab_tt.cir). +* Run: IHP_PDK= IHP_OSDI= ngspice -b psp103_inv_be_500f_4us_wall.cir (ngspice-47) +* The batch log holds two runs of the .tran. The rusage lines are printed right after the first, the control block's "run", and are the +* ones bench.t27 stores; the summary at exit is cumulative over both runs. +* Grid: tstep = 0.78125 dt makes the opening ramp end exactly at dt = 100 ps, so every internal point from then on is a multiple of dt. The +* input is a B source of nint(time / dt): Vdd up to index 1, 0 V from index 2 (150 ps) to index 201, Vdd from index 202. Step k of the spec ends +* at (k + 2) dt; step 0 starts at t = dt = 100 ps and step 200 at 201 dt = 20.1 ns. +* What it prints (ngspice .meas, 6 digits, in s): tplh and tphl, the times at which the output crosses 0.75 V rising (after step 0's edge) and +* falling (after step 200's edge); the delays are these minus 100 ps and minus 20.1 ns. The logs of the 4 us run and the 40.4 ns run print +* the same two numbers: both see the same two edges, and the input has no later one. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +VDD vdd 0 1.5 +Bin in 0 V = 1.5*(1-min(max(nint(time/1e-10)-1,0),1)+min(max(nint(time/1e-10)-201,0),1)) +XIP out in vdd vdd sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1 +XIN out in 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1 +CL out 0 500f +.temp 27 +.options method=gear maxord=1 trtol=1e7 reltol=1e-6 vntol=1e-9 abstol=1e-12 itl4=200 +.tran 78.125p 4.0004u 0 100p +.meas tran tplh when v(out)=0.75 rise=1 +.meas tran tphl when v(out)=0.75 fall=1 +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set num_threads=1 +run +rusage all +.endc +.end