From 861c2ea3ec545b1f943df697cef2aeb639c7af62 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 23:06:56 +0700 Subject: [PATCH 1/2] analog: bench.t27 -- ngspice wall time and a fixed-step backward-Euler reference for the M0/M1 circuits (Closes #10505) specs/analog/bench.t27 records, for five circuits (the M0/M1 RC, the series RLC, 3-, 5- and 10-node RC ladders), ngspice-47 wall time (median of 10 runs, process start included), ngspice's own .tran analysis time, internal time points, and two deviations. It also encodes the claim rule: may_claim_speedup is true only with a measured board time and wall(ngspice) / wall(board incl. readout) >= 1. Fixed-step finding: ngspice reproduces backward Euler on the exact grid to float64 rounding (largest deviation 1e-15 to 9.4e-15 V against an f64 LU recurrence) with method=gear maxord=1 trtol=1e7, a B source (no breakpoints) and a pre-roll in which the circuit rests at 0 V. A PWL source cannot: its corners are breakpoints and ngspice restarts its step ramp after each, leaving 4 of 217 points on the grid and v(out) at 5 us off by 1.7e-5 V. specs/analog/decks/ holds a fixed and an adaptive deck per circuit and rc_pwl_naive.cir as the negative control. Measured on Apple M1, macOS 26.3.1, sandboxed session: ngspice wall is 14 to 15 ms for every row, of which .tran is 0.8 to 1.0 ms; it is flat from 1 to 10 unknowns. M0's board time is 12 us compute + 9375 us UART line = 9387 us, so the wall ratio is 1.52; that compares two fixed overheads. Only M0 has a board number; the RLC and the 5-node ladder wait for M1b (#10496); the bitstream build time is not in any receipt, so it is recorded as not measured and no break-even count is stated. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/analog_AnalogBench.json | 28 ++ specs/analog/bench.t27 | 338 +++++++++++++++++++++++++ specs/analog/decks/lad10_adaptive.cir | 31 +++ specs/analog/decks/lad10_fixed.cir | 35 +++ specs/analog/decks/lad3_adaptive.cir | 16 ++ specs/analog/decks/lad3_fixed.cir | 20 ++ specs/analog/decks/lad5_adaptive.cir | 20 ++ specs/analog/decks/lad5_fixed.cir | 24 ++ specs/analog/decks/rc_adaptive.cir | 12 + specs/analog/decks/rc_fixed.cir | 19 ++ specs/analog/decks/rc_pwl_naive.cir | 14 + specs/analog/decks/rlc_adaptive.cir | 14 + specs/analog/decks/rlc_fixed.cir | 17 ++ 13 files changed, 588 insertions(+) create mode 100644 .trinity/seals/analog_AnalogBench.json create mode 100644 specs/analog/bench.t27 create mode 100644 specs/analog/decks/lad10_adaptive.cir create mode 100644 specs/analog/decks/lad10_fixed.cir create mode 100644 specs/analog/decks/lad3_adaptive.cir create mode 100644 specs/analog/decks/lad3_fixed.cir create mode 100644 specs/analog/decks/lad5_adaptive.cir create mode 100644 specs/analog/decks/lad5_fixed.cir create mode 100644 specs/analog/decks/rc_adaptive.cir create mode 100644 specs/analog/decks/rc_fixed.cir create mode 100644 specs/analog/decks/rc_pwl_naive.cir create mode 100644 specs/analog/decks/rlc_adaptive.cir create mode 100644 specs/analog/decks/rlc_fixed.cir diff --git a/.trinity/seals/analog_AnalogBench.json b/.trinity/seals/analog_AnalogBench.json new file mode 100644 index 0000000000..112e7a744b --- /dev/null +++ b/.trinity/seals/analog_AnalogBench.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:61e2adc5415b1e02e6b1c29f968efc643e388b6ee55b3aa9c881377944a89f22", + "gen_hash_rust": "sha256:62819f48999916056083c4eaeb21c5703840ed3741192c540806268261df79f8", + "gen_hash_verilog": "sha256:a2b21cb104393b95a80c4917a40e8630deb794831936bddd9399c5754e0444c8", + "gen_hash_zig": "sha256:4d113dbbbec31ffe40a866dede790feae7d73a72a9510a0b7760f04192f6323b", + "module": "AnalogBench", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T16:06:41Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:7c993538fc085e78381c9b70dfcf522aa4497274cfc81b9295cf2c0bcb1e27b7", + "spec_path": "specs/analog/bench.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 11, + "total": 11, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/specs/analog/bench.t27 b/specs/analog/bench.t27 new file mode 100644 index 0000000000..98f5f976dd --- /dev/null +++ b/specs/analog/bench.t27 @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/bench.t27 -- ngspice wall time against the board for the analog circuits, and ngspice as a fixed-step reference +; Issue #10505, weakness W7 and W10 of docs/analog/competitors.md, epic #10486. Everything hand-written is this file and the +; netlists in specs/analog/decks/ (plain SPICE decks, data, no .control block). +; - +; THE QUESTION. M0 runs 10 us of circuit time in 12 us of board time, and nothing had been measured against ngspice. A speedup +; over ngspice must not be claimed from a guess. This file holds what was measured, the rule that decides whether a speedup may +; be claimed, and the tests that keep both honest. Every later document of #10486 that mentions speed cites a row of this file +; or makes no speed claim. +; - +; HOW IT WAS MEASURED. One machine, one day: 2026-10-18, Apple M1, 8 GB, macOS 26.3.1 (Darwin 25.3.0, arm64), ngspice-47 from +; Homebrew (/opt/homebrew/bin/ngspice, SPARSE 1.3 direct solver, which is the default; KLU is compiled in but not selected). The +; session was sandboxed (macOS Seatbelt), so process start-up includes the sandbox's cost; a bare shell would be somewhat lower. +; Command, per deck: ngspice -b -r .raw specs/analog/decks/.cir +; Wall time: the clock around the whole process (start-up, parse, run, write the raw file, exit) read with Python 3.14.6 +; time.perf_counter; one untimed warm-up run, then 10 timed runs, the median is stored (the smallest of the 10 too, for the +; claim check). Two more times are ngspice's own, read from its batch output: "Total analysis time" (the .tran work, 1 us +; resolution) and "Total elapsed time" (1 ms resolution). The deviations come from a float64 reference that is NOT in the repo: +; backward Euler by an LU solve of (G + C/dt) x' = (C/dt) x + B u from each circuit's element table, 200 steps (numpy 2.4.4), and +; for the adaptive runs the exact response of the continuous circuit (matrix exponential, piecewise-linear source, an algebraic +; node eliminated by a Schur complement). That helper was a throwaway; when the SPICE reader of M2 exists, a t27 tool reads the +; raw file and replaces it. The numbers below are therefore measurements, and only the RC rows can be re-derived inside this file. +; - +; THE FIXED-STEP FINDING (W10). Question: can ngspice be an independent fixed-step reference, backward Euler on the exact grid +; of the board? Answer: yes, to float64 rounding, with a recipe; a plain PWL deck cannot. +; 1. method=gear with maxord=1 is backward Euler (the C and L companion models use 1/h). trtol=1e7 makes the truncation test +; accept any step, so the step is limited only by tmax (= tstep) and by breakpoints. Without trtol the step collapses at +; the source edge to 2.1e-13 s. Without maxord=1 the steps stay at 50 ns but the method is trapezoidal: 0.024 V from +; backward Euler on the RC. +; 2. ngspice opens every .tran with a ramp of steps (0.5, 1, 2, 4, 8, 16 ns ... for tstep = 50 ns, doubling), reaches a first +; breakpoint at tstep, and restarts a ramp after every breakpoint (53.2, 59.6, 72.4, 98.0, 148.0 ns ...). A PWL corner or a +; PULSE edge is a breakpoint, so a PWL source puts the internal points off the grid, and backward Euler on that nonuniform +; grid is a different recurrence. Measured on rc_pwl_naive.cir: 217 internal points, 4 of them on the 50 ns grid, v(1) at +; 5 us off the recurrence by 1.7e-5 V (0.992412 against 0.992396), at 10 us by 1.2e-4 V, and 0.016 V at the first grid +; point, whose interval holds 8 substeps. That is the smallest deviation a PWL source gives; it is a property of +; ngspice's step control, not of the options. +; 3. The recipe that works (rc_fixed.cir and the other *_fixed.cir): a B source, which declares no breakpoint, and a pre-roll. +; While the source is 0 V the circuit rests at 0, and any step from rest with zero input leaves it at exactly 0, so the +; ramp cannot touch the state. The source turns on at 8.5 steps, half a step from the grid the ramp ends on (that grid sits +; 2 ns before a multiple of 50 ns), and from the first internal point after it every step equals tmax. Step k of the run +; ends at the k-th internal point after turn-on. The raw file (-r) holds float64 values, so no print rounding enters. +; 4. Result on five circuits: the largest deviation of any node, any of the 200 points, from the float64 backward Euler +; recurrence is 1e-15 to 9.4e-15 V (a few ulps of values near 1 V); every step of the 200 is dt to 2.2e-14 relative. The +; target was 1e-9 V. The price is 20 extra internal points (220, not 201) and a source that is a function of time. +; The same sources and the default options, written as a user writes them (*_adaptive.cir), give the adaptive column: 217 or +; 218 internal points and a deviation from the continuous circuit of 2.4e-4 to 3.4e-4 V, which is ngspice's own tolerance +; (reltol 1e-3); that number says nothing about the board, whose backward Euler at 50 ns differs from the continuous circuit +; by its own discretization error. +; - +; THE TABLE. Four distinct netlists, five rows. M1's circuit 1 (linear.t27) is M0's RC, one netlist, so it is one row with +; both names. The fifth row is a 10-node RC ladder that no cell in this repository implements (NMAX is 5): it is here so the +; ngspice time can be read against circuit size, and it has no board number and none planned. Rows: 0 the RC low-pass (M0 and +; M1 circuit 1), 1 the series RLC, 2 the 3-node RC ladder, 3 the 5-node RC ladder, 4 the 10-node RC ladder. +; - +; THE BOARD SIDE. Only M0 has a board number today. Its time is what the receipt says and nothing more: 1200 clocks at 100 MHz +; is 12 us of compute, and the result leaves on a 115200 baud UART as one line of 108 characters at 10 bits each, which is +; 9375 us, so a host that is listening from reset has the first full line at 12 + 9375 = 9387 us. That is a computed figure, not +; a stopwatch reading: it leaves out the host's own serial read and any program start-up, which ngspice's wall time includes. +; The RLC and the 5-node ladder wait for their board tops (M1b, #10496); the 3-node and 10-node ladders have no board top. +; Their rows say so (BOARD_PENDING, BOARD_NONE) and no number is invented for them. The bitstream build time is not in any +; receipt in this repository and was not measured here: BUILD_US is 0 meaning "not measured", and the break-even count of +; runs is therefore not stated for any row (break_even_runs returns 0 for 0 and for a board slower than ngspice). +; - +; WHAT THE NUMBERS SAY. For every row ngspice's process wall is 14 to 15 ms, of which the .tran itself is 0.8 to 1.0 ms: more +; than 90 percent is start-up and file work, and the wall time is flat from 1 to 10 unknowns (10-node over RC is 1.06). The +; board's 9387 us is 99.9 percent the serial line. So on M0 the wall ratio is 1.52 (ngspice 14228 us over the board's 9387 us), +; and it is a ratio of two fixed overheads, not of two simulators. The compute-only ratio, ngspice's 796 us .tran over the +; board's 12 us, is 66, a different quantity; the user of a run pays the readout, so it is not a speed claim about a run. +; - +; THE CLAIM RULE (FR-002). may_claim_speedup(state, ng_wall_us, board_wall_us) is true only when the board number is measured +; (BOARD_MEASURED) and wall(ngspice) / wall(board including readout) is at least 1. A circuit with a pending or absent board +; number, a board slower than ngspice, or a zero board time never gets a claim. The tests include those negative controls and +; check that no row but M0 can be claimed. Where the rule says yes, the sentence that goes into a document is the one above +; with its two definitions, not "N times faster than SPICE". +; - +; 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. +; 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). +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogBench; + +pub const KIND : str = "analog-bench"; +pub const ID : str = "analog/bench"; +pub const REFERENCE : str = "docs/analog/competitors.md W7 and W10; specs/analog/linear.t27; specs/fpga/rc_filter_ax7203.t27"; +pub const NGSPICE : str = "ngspice-47 (Homebrew), SPARSE 1.3"; +pub const MACHINE : str = "Apple M1, 8 GB, macOS 26.3.1 arm64, sandboxed session"; +pub const DATE : str = "2026-10-18"; + +; --- the table: row indices ------------------------------------------------------------------------ +pub const ROWS : usize = 5; +pub const ROW_RC : usize = 0; +pub const ROW_RLC : usize = 1; +pub const ROW_LAD3 : usize = 2; +pub const ROW_LAD5 : usize = 3; +pub const ROW_LAD10 : usize = 4; +/// Unknowns of each circuit (free nodes plus inductors). +pub const UNKNOWNS : [5]u32 = [1, 3, 3, 5, 10]; +/// Step of each circuit in ns. +pub const DT_NS : [5]u32 = [50, 5, 50, 50, 50]; +/// Steps of the run: u = 1 V for the first 100, 0 V for the next 100. +pub const STEPS : u32 = 200; + +; --- ngspice, fixed-step backward Euler decks (*_fixed.cir), times in microseconds ------------------------- +/// Median of 10 process wall times. +pub const FIX_WALL_US : [5]u32 = [14228, 14813, 14607, 14659, 15021]; +/// The fastest of those 10. +pub const FIX_WALL_MIN_US : [5]u32 = [13855, 14164, 14052, 13942, 14383]; +/// Median of ngspice's "Total analysis time". +pub const FIX_TRAN_US : [5]u32 = [796, 914, 836, 866, 983]; +/// Median of ngspice's "Total elapsed time" (1 ms resolution). +pub const FIX_ELAPSED_US : [5]u32 = [4500, 5000, 5000, 5000, 5000]; +/// Internal time points written (the 200 steps of the run plus the pre-roll and the tail). +pub const FIX_POINTS : [5]u32 = [220, 220, 220, 220, 220]; +/// Largest |ngspice - float64 backward Euler| over every node and every step, in volts. +pub const FIX_BE_DEV : [5]f64 = [9.992007221626409e-16, 2.6645352591003757e-15, 9.381384558082573e-15, 7.771561172376096e-15, 7.66053886991358e-15]; +/// Largest relative difference between two of the 200 consecutive steps and dt, over all five decks. +pub const FIX_STEP_SPREAD : f64 = 2.2e-14; +/// The RC deck's v(1) after step 99 (5 us) and step 199 (10 us), as ngspice wrote them. +pub const FIX_RC_A : f64 = 0.9923955100021264; +pub const FIX_RC_B : f64 = 0.007546661729745712; + +; --- ngspice, default adaptive decks (*_adaptive.cir), times in microseconds ----------------------------- +pub const ADA_WALL_US : [5]u32 = [14373, 14691, 14106, 14281, 15336]; +pub const ADA_TRAN_US : [5]u32 = [786, 885, 790, 846, 1013]; +pub const ADA_POINTS : [5]u32 = [217, 218, 218, 218, 218]; +/// Largest |ngspice - exact continuous solution| over every node (the inductor current times 100 Ohm too) and every time point, in volts. +pub const ADA_EXACT_DEV : [5]f64 = [2.4123845185308745e-4, 3.3590388159099316e-4, 2.4387111651524850e-4, 2.4248478092381287e-4, 2.4252851884498660e-4]; + +; --- the negative control: the same backward Euler, a PWL source (rc_pwl_naive.cir) -------------------- +pub const NAIVE_POINTS : u32 = 217; +/// Internal points that sit on the 50 ns grid. +pub const NAIVE_ON_GRID : u32 = 4; +/// v(1) at 5 us as ngspice wrote it, against FIX_RC_A. +pub const NAIVE_RC_A : f64 = 0.9924122730094892; +/// |v(1) at 10 us - the recurrence's value|. +pub const NAIVE_B_DEV : f64 = 1.223932749906103e-4; +/// Largest deviation at the points that are on the grid. +pub const NAIVE_GRID_DEV : f64 = 0.016110220104977313; + +; --- the board ----------------------------------------------------------------------------------- +pub const BOARD_NONE : u8 = 0; +/// A board top is planned (M1b, #10496) and has no receipt yet. +pub const BOARD_PENDING : u8 = 1; +/// The receipt gives the board time. +pub const BOARD_MEASURED : u8 = 2; +pub const BOARD_STATE : [5]u8 = [2, 1, 0, 1, 0]; +/// Board wall time including readout, microseconds; 0 where there is no number. +pub const BOARD_WALL_US : [5]u32 = [9387, 0, 0, 0, 0]; +/// M0: clocks of one run and the system clock in MHz (rc_filter_ax7203.t27 RUN_CLOCKS, SYS_MHZ). +pub const M0_RUN_CLOCKS : u32 = 1200; +pub const M0_SYS_MHZ : u32 = 100; +/// M0: the receipt line is 108 characters at 115200 baud, 10 bits each (LINE_CHARS, BAUD_DIV = 868). +pub const M0_LINE_CHARS : u32 = 108; +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; + +; --- the rule ------------------------------------------------------------------------------------ + +/// Microseconds the UART needs for n characters at the given baud (8N1: 10 bits each). +pub fn uart_us(chars: u32, baud: u32) -> u32 { + return (((chars as u64) * 10 * 1000000) / (baud as u64)) as u32; +} + +/// Microseconds of a run of clocks at mhz megahertz. +pub fn clocks_us(clocks: u32, mhz: u32) -> u32 { + return clocks / mhz; +} + +/// a / b in thousandths, rounded down; 0 when b is 0. +pub fn ratio_permille(a: u32, b: u32) -> u32 { + if (b == 0) { return 0; } + return (((a as u64) * 1000) / (b as u64)) as u32; +} + +/// The share of the wall time that is not the .tran work, in thousandths. +pub fn startup_permille(wall: u32, tran: u32) -> u32 { + if (wall == 0 or tran > wall) { return 0; } + return ratio_permille(wall - tran, wall); +} + +/// A speedup over ngspice may be claimed only with a measured board time (including readout) and wall(ngspice) / wall(board) >= 1. +pub fn may_claim_speedup(state: u8, ng_wall_us: u32, board_wall_us: u32) -> bool { + if (state != BOARD_MEASURED) { return false; } + if (board_wall_us == 0) { return false; } + return ng_wall_us >= board_wall_us; +} + +/// Runs after which one bitstream build pays for itself: ceil(build / (ngspice - board)); 0 when the build time is not measured +/// (0), or when the board is not faster than ngspice (it never pays back). +pub fn break_even_runs(build_us: u64, ng_wall_us: u32, board_wall_us: u32) -> u64 { + if (build_us == 0 or board_wall_us >= ng_wall_us) { return 0; } + const gain : u64 = (ng_wall_us - board_wall_us) as u64; + return (build_us + gain - 1) / gain; +} + +/// |x| for the f64 checks. +pub fn fabs64(x: f64) -> f64 { + if (x < 0.0) { return 0.0 - x; } + return x; +} + +; --- tests --------------------------------------------------------------------------------------- + +test the_fixed_decks_reproduce_backward_euler_to_f64_rounding { + var i : usize = 0; + while (i < ROWS) { + assert FIX_BE_DEV[i] >= 0.0; + assert FIX_BE_DEV[i] < 1.0e-14; + assert FIX_POINTS[i] >= STEPS + 1; + assert FIX_POINTS[i] <= STEPS + 25; + i = i + 1; + } + assert FIX_STEP_SPREAD > 0.0; + assert FIX_STEP_SPREAD < 1.0e-12; +} + +test the_recorded_rc_values_are_the_closed_backward_euler_recurrence { + var x : f64 = 0.0; + var xa : f64 = 0.0; + var k : u32 = 0; + while (k < STEPS) { + if (k < 100) { x = (x + 0.05) / 1.05; } else { x = x / 1.05; } + if (k == 99) { xa = x; } + k = k + 1; + } + assert fabs64(FIX_RC_A - xa) < 1.0e-14; + assert fabs64(FIX_RC_B - x) < 1.0e-14; + assert fabs64(xa - 0.992396) < 5.0e-7; + assert fabs64(FIX_RC_A - 0.9932620530009145) > 8.0e-4; +} + +test a_pwl_source_leaves_the_grid_and_the_b_source_does_not { + assert NAIVE_POINTS == 217; + assert NAIVE_ON_GRID < 10; + const dev_a : f64 = fabs64(NAIVE_RC_A - FIX_RC_A); + assert dev_a > 1.0e-5; + assert dev_a < 2.0e-5; + assert NAIVE_B_DEV > 1.0e-4; + assert NAIVE_GRID_DEV > 1.0e-2; + assert dev_a > 1.0e8 * FIX_BE_DEV[ROW_RC]; + assert NAIVE_GRID_DEV > 1.0e12 * FIX_BE_DEV[ROW_RC]; +} + +test the_adaptive_runs_are_inside_ngspices_own_tolerance_of_the_continuous_circuit { + var i : usize = 0; + while (i < ROWS) { + assert ADA_EXACT_DEV[i] > 1.0e-5; + assert ADA_EXACT_DEV[i] < 1.0e-3; + assert ADA_POINTS[i] >= STEPS; + assert ADA_POINTS[i] <= STEPS + 25; + assert ADA_EXACT_DEV[i] > 1.0e9 * FIX_BE_DEV[i]; + i = i + 1; + } +} + +test process_start_up_is_most_of_every_wall_time { + var i : usize = 0; + while (i < ROWS) { + assert FIX_TRAN_US[i] < FIX_ELAPSED_US[i]; + assert FIX_ELAPSED_US[i] < FIX_WALL_US[i]; + assert FIX_WALL_MIN_US[i] <= FIX_WALL_US[i]; + assert startup_permille(FIX_WALL_US[i], FIX_TRAN_US[i]) > 900; + assert startup_permille(ADA_WALL_US[i], ADA_TRAN_US[i]) > 900; + i = i + 1; + } + assert startup_permille(FIX_WALL_US[ROW_RC], FIX_TRAN_US[ROW_RC]) == 944; + assert startup_permille(10, 11) == 0; + assert startup_permille(0, 0) == 0; +} + +test the_wall_time_is_flat_from_one_to_ten_unknowns { + assert UNKNOWNS[ROW_LAD10] == 10 * UNKNOWNS[ROW_RC]; + assert ratio_permille(FIX_WALL_US[ROW_LAD10], FIX_WALL_US[ROW_RC]) == 1055; + assert ratio_permille(FIX_WALL_US[ROW_LAD10], FIX_WALL_US[ROW_RC]) < 1100; + assert ratio_permille(FIX_TRAN_US[ROW_LAD10], FIX_TRAN_US[ROW_RC]) == 1234; +} + +test the_m0_board_wall_is_the_serial_line_plus_twelve_microseconds { + assert clocks_us(M0_RUN_CLOCKS, M0_SYS_MHZ) == 12; + assert uart_us(M0_LINE_CHARS, M0_BAUD) == 9375; + assert clocks_us(M0_RUN_CLOCKS, M0_SYS_MHZ) + uart_us(M0_LINE_CHARS, M0_BAUD) == BOARD_WALL_US[ROW_RC]; + assert ratio_permille(uart_us(M0_LINE_CHARS, M0_BAUD), BOARD_WALL_US[ROW_RC]) == 998; + assert uart_us(1, 115200) == 86; +} + +test the_m0_ratios_are_what_the_text_says { + assert ratio_permille(FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]) == 1515; + assert ratio_permille(ADA_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]) == 1531; + assert ratio_permille(FIX_TRAN_US[ROW_RC], clocks_us(M0_RUN_CLOCKS, M0_SYS_MHZ)) == 66333; + assert ratio_permille(FIX_WALL_US[ROW_RC], 0) == 0; +} + +test the_claim_rule_and_its_negative_controls { + assert may_claim_speedup(BOARD_MEASURED, 14228, 9387); + assert may_claim_speedup(BOARD_MEASURED, 9387, 9387); + assert !may_claim_speedup(BOARD_MEASURED, 9386, 9387); + assert !may_claim_speedup(BOARD_MEASURED, 9000, 9387); + assert !may_claim_speedup(BOARD_MEASURED, 14228, 0); + assert !may_claim_speedup(BOARD_PENDING, 14228, 9387); + assert !may_claim_speedup(BOARD_NONE, 1000000, 1); + assert !may_claim_speedup(BOARD_PENDING, 1000000, 0); +} + +test the_table_claims_a_speedup_only_where_a_board_number_exists { + var claims : u32 = 0; + var i : usize = 0; + while (i < ROWS) { + if (may_claim_speedup(BOARD_STATE[i], FIX_WALL_US[i], BOARD_WALL_US[i])) { claims = claims + 1; } + if (BOARD_STATE[i] != BOARD_MEASURED) { assert BOARD_WALL_US[i] == 0; } + if (BOARD_STATE[i] == BOARD_MEASURED) { assert BOARD_WALL_US[i] > 0; } + i = i + 1; + } + assert claims == 1; + assert may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]); + assert may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_MIN_US[ROW_RC], BOARD_WALL_US[ROW_RC]); + assert may_claim_speedup(BOARD_STATE[ROW_RC], ADA_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]); + assert BOARD_STATE[ROW_RLC] == BOARD_PENDING; + assert BOARD_STATE[ROW_LAD5] == BOARD_PENDING; + assert BOARD_STATE[ROW_LAD3] == BOARD_NONE; + assert BOARD_STATE[ROW_LAD10] == BOARD_NONE; +} + +test the_break_even_count_is_stated_only_with_a_measured_build { + assert BUILD_US == 0; + assert break_even_runs(BUILD_US, FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]) == 0; + assert break_even_runs(100000000, 14228, 9387) == 20657; + assert break_even_runs(4841, 14228, 9387) == 1; + assert break_even_runs(4842, 14228, 9387) == 2; + assert break_even_runs(100000000, 9000, 9387) == 0; + assert break_even_runs(100000000, 9387, 9387) == 0; +} diff --git a/specs/analog/decks/lad10_adaptive.cir b/specs/analog/decks/lad10_adaptive.cir new file mode 100644 index 0000000000..406139319c --- /dev/null +++ b/specs/analog/decks/lad10_adaptive.cir @@ -0,0 +1,31 @@ +10-node RC ladder (beyond the M1 cell), ngspice default adaptive transient +* specs/analog/decks/lad10_adaptive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit and run as lad10_fixed.cir (not a circuit of specs/analog/linear.t27), written as a user would write +* it: a PWL source and the default options (trapezoidal integration, reltol 1e-3, ngspice's own step control). tmax is +* its default, min(tstep, tstop/50) = 50 ns. +* IN = 1 V until 5000 ns and 0 V from 5050 ns: step 99 of the run ends at 5000 ns, step 100 at 5050 ns, and the source +* carries the value of the step's END point, as the backward Euler recurrence does. uic with IC=0 starts all nodes at 0 V. +* Run: ngspice -b -r lad10_adaptive.raw lad10_adaptive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 5000n 1 5050n 0) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +R4 3 4 1k +R5 4 5 1k +R6 5 6 1k +R7 6 7 1k +R8 7 8 1k +R9 8 9 1k +R10 9 10 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +C4 4 0 1n IC=0 +C5 5 0 1n IC=0 +C6 6 0 1n IC=0 +C7 7 0 1n IC=0 +C8 8 0 1n IC=0 +C9 9 0 1n IC=0 +C10 10 0 1n IC=0 +.tran 50n 10000n uic +.end diff --git a/specs/analog/decks/lad10_fixed.cir b/specs/analog/decks/lad10_fixed.cir new file mode 100644 index 0000000000..f7c5fe7211 --- /dev/null +++ b/specs/analog/decks/lad10_fixed.cir @@ -0,0 +1,35 @@ +10-node RC ladder (beyond the M1 cell), backward Euler on a fixed 50 ns grid +* specs/analog/decks/lad10_fixed.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* Circuit: R1..R10 = 1 kOhm in series from IN through nodes 1..10; C1..C10 = 1 nF from nodes 1..10 to ground; all +* capacitors start at 0 V. This is NOT a circuit of specs/analog/linear.t27 (its NMAX is 5): it is here so the ngspice +* wall time can be read against circuit size, and it has no board number. +* Run: 200 steps of 50 ns, IN = 1 V for the first 100 steps and 0 V for the next 100. +* The grid is made exact as in rc_fixed.cir: a B source declares no breakpoint, the circuit rests at 0 V while the source +* is 0, the source turns on at 425 ns (half a step from the nearest grid point) and turns off at 5425 ns, and from the +* first internal point after 425 ns every step is 50 ns. Step k (k = 0..199) ends at the k-th internal point after 425 ns. +* tstop = 425 ns + 201 steps. gear with maxord 1 is backward Euler; trtol = 1e7 stops the truncation test. +* Run: ngspice -b -r lad10_fixed.raw lad10_fixed.cir (the raw file holds float64 values) +B1 in 0 V = u(time-425n) - u(time-5425n) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +R4 3 4 1k +R5 4 5 1k +R6 5 6 1k +R7 6 7 1k +R8 7 8 1k +R9 8 9 1k +R10 9 10 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +C4 4 0 1n IC=0 +C5 5 0 1n IC=0 +C6 6 0 1n IC=0 +C7 7 0 1n IC=0 +C8 8 0 1n IC=0 +C9 9 0 1n IC=0 +C10 10 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 50n 10475n 0 50n uic +.end diff --git a/specs/analog/decks/lad3_adaptive.cir b/specs/analog/decks/lad3_adaptive.cir new file mode 100644 index 0000000000..65afb6e7d9 --- /dev/null +++ b/specs/analog/decks/lad3_adaptive.cir @@ -0,0 +1,16 @@ +M1 3-node RC ladder, ngspice default adaptive transient +* specs/analog/decks/lad3_adaptive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit and run as lad3_fixed.cir, written as a user would write it: a PWL source and the default options +* (trapezoidal integration, reltol 1e-3, ngspice's own step control). tmax is its default, min(tstep, tstop/50) = 50 ns. +* IN = 1 V until 5000 ns and 0 V from 5050 ns: step 99 of the run ends at 5000 ns, step 100 at 5050 ns, and the source +* carries the value of the step's END point, as the backward Euler recurrence does. uic with IC=0 starts all nodes at 0 V. +* Run: ngspice -b -r lad3_adaptive.raw lad3_adaptive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 5000n 1 5050n 0) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +.tran 50n 10000n uic +.end diff --git a/specs/analog/decks/lad3_fixed.cir b/specs/analog/decks/lad3_fixed.cir new file mode 100644 index 0000000000..17891ecdf5 --- /dev/null +++ b/specs/analog/decks/lad3_fixed.cir @@ -0,0 +1,20 @@ +M1 3-node RC ladder, backward Euler on a fixed 50 ns grid +* specs/analog/decks/lad3_fixed.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* Circuit: R1, R2, R3 = 1 kOhm in series from IN through nodes 1, 2 and 3; C1, C2, C3 = 1 nF from nodes 1, 2, 3 to +* ground; all capacitors start at 0 V (specs/analog/linear.t27, circuit 3). +* Run: 200 steps of 50 ns, IN = 1 V for the first 100 steps and 0 V for the next 100. +* The grid is made exact as in rc_fixed.cir: a B source declares no breakpoint, the circuit rests at 0 V while the source +* is 0, the source turns on at 425 ns (half a step from the nearest grid point) and turns off at 5425 ns, and from the +* first internal point after 425 ns every step is 50 ns. Step k (k = 0..199) ends at the k-th internal point after 425 ns. +* tstop = 425 ns + 201 steps. gear with maxord 1 is backward Euler; trtol = 1e7 stops the truncation test. +* Run: ngspice -b -r lad3_fixed.raw lad3_fixed.cir (the raw file holds float64 values) +B1 in 0 V = u(time-425n) - u(time-5425n) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 50n 10475n 0 50n uic +.end diff --git a/specs/analog/decks/lad5_adaptive.cir b/specs/analog/decks/lad5_adaptive.cir new file mode 100644 index 0000000000..d0503b2c0a --- /dev/null +++ b/specs/analog/decks/lad5_adaptive.cir @@ -0,0 +1,20 @@ +M1 5-node RC ladder, ngspice default adaptive transient +* specs/analog/decks/lad5_adaptive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit and run as lad5_fixed.cir, written as a user would write it: a PWL source and the default options +* (trapezoidal integration, reltol 1e-3, ngspice's own step control). tmax is its default, min(tstep, tstop/50) = 50 ns. +* IN = 1 V until 5000 ns and 0 V from 5050 ns: step 99 of the run ends at 5000 ns, step 100 at 5050 ns, and the source +* carries the value of the step's END point, as the backward Euler recurrence does. uic with IC=0 starts all nodes at 0 V. +* Run: ngspice -b -r lad5_adaptive.raw lad5_adaptive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 5000n 1 5050n 0) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +R4 3 4 1k +R5 4 5 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +C4 4 0 1n IC=0 +C5 5 0 1n IC=0 +.tran 50n 10000n uic +.end diff --git a/specs/analog/decks/lad5_fixed.cir b/specs/analog/decks/lad5_fixed.cir new file mode 100644 index 0000000000..14b16329d9 --- /dev/null +++ b/specs/analog/decks/lad5_fixed.cir @@ -0,0 +1,24 @@ +M1 5-node RC ladder, backward Euler on a fixed 50 ns grid +* specs/analog/decks/lad5_fixed.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* Circuit: R1..R5 = 1 kOhm in series from IN through nodes 1..5; C1..C5 = 1 nF from nodes 1..5 to ground; all +* capacitors start at 0 V (specs/analog/linear.t27, circuit 4). +* Run: 200 steps of 50 ns, IN = 1 V for the first 100 steps and 0 V for the next 100. +* The grid is made exact as in rc_fixed.cir: a B source declares no breakpoint, the circuit rests at 0 V while the source +* is 0, the source turns on at 425 ns (half a step from the nearest grid point) and turns off at 5425 ns, and from the +* first internal point after 425 ns every step is 50 ns. Step k (k = 0..199) ends at the k-th internal point after 425 ns. +* tstop = 425 ns + 201 steps. gear with maxord 1 is backward Euler; trtol = 1e7 stops the truncation test. +* Run: ngspice -b -r lad5_fixed.raw lad5_fixed.cir (the raw file holds float64 values) +B1 in 0 V = u(time-425n) - u(time-5425n) +R1 in 1 1k +R2 1 2 1k +R3 2 3 1k +R4 3 4 1k +R5 4 5 1k +C1 1 0 1n IC=0 +C2 2 0 1n IC=0 +C3 3 0 1n IC=0 +C4 4 0 1n IC=0 +C5 5 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 50n 10475n 0 50n uic +.end diff --git a/specs/analog/decks/rc_adaptive.cir b/specs/analog/decks/rc_adaptive.cir new file mode 100644 index 0000000000..2c9665949d --- /dev/null +++ b/specs/analog/decks/rc_adaptive.cir @@ -0,0 +1,12 @@ +M0 RC low-pass, ngspice default adaptive transient +* specs/analog/decks/rc_adaptive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit and run as rc_fixed.cir, written as a user would write it: a PWL source and the default options +* (trapezoidal integration, reltol 1e-3, ngspice's own step control). tmax is its default, min(tstep, tstop/50) = 50 ns. +* IN = 1 V until 5000 ns and 0 V from 5050 ns: step 99 of the run ends at 5000 ns, step 100 at 5050 ns, and the source +* carries the value of the step's END point, as the backward Euler recurrence does. uic with IC=0 starts OUT at 0 V. +* Run: ngspice -b -r rc_adaptive.raw rc_adaptive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 5000n 1 5050n 0) +R1 in 1 1k +C1 1 0 1n IC=0 +.tran 50n 10000n uic +.end diff --git a/specs/analog/decks/rc_fixed.cir b/specs/analog/decks/rc_fixed.cir new file mode 100644 index 0000000000..adf3b8a9a6 --- /dev/null +++ b/specs/analog/decks/rc_fixed.cir @@ -0,0 +1,19 @@ +M0 RC low-pass, backward Euler on a fixed 50 ns grid +* specs/analog/decks/rc_fixed.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* Circuit: R1 = 1 kOhm from IN to node 1 (OUT), C1 = 1 nF from node 1 to ground, OUT starts at 0 V +* (specs/fpga/rc_filter_ax7203.t27; node numbers as in specs/analog/linear.t27). +* Run: 200 steps of 50 ns, IN = 1 V for the first 100 steps and 0 V for the next 100 (vin_at of linear.t27). +* How the grid is made exact. ngspice opens every .tran with steps of tstep/100 that double up to tmax, and opens that +* ramp again after every breakpoint, so a PWL or PULSE source puts internal points off the grid. A B source declares no +* breakpoint. While the source is 0 V the circuit rests at 0 V and every step of the ramp leaves it at exactly 0 V, so the +* ramp cannot touch the state. The source turns on at 425 ns, half a step from the nearest grid point; from the first +* internal point after 425 ns every step is 50 ns. Step k of the run (k = 0..199) ends at the k-th internal point after +* 425 ns; the source is 1 V until 5425 ns and 0 V after. tstop = 425 ns + 201 steps. +* Integration: gear with maxord 1 is backward Euler. trtol = 1e7 stops the truncation test from shrinking a step. +* Run: ngspice -b -r rc_fixed.raw rc_fixed.cir (the raw file holds float64 values) +B1 in 0 V = u(time-425n) - u(time-5425n) +R1 in 1 1k +C1 1 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 50n 10475n 0 50n uic +.end diff --git a/specs/analog/decks/rc_pwl_naive.cir b/specs/analog/decks/rc_pwl_naive.cir new file mode 100644 index 0000000000..7da1c3d49a --- /dev/null +++ b/specs/analog/decks/rc_pwl_naive.cir @@ -0,0 +1,14 @@ +M0 RC low-pass, backward Euler with a PWL source: the negative control of rc_fixed.cir +* specs/analog/decks/rc_pwl_naive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit, integration (gear, maxord 1 = backward Euler) and trtol as rc_fixed.cir, but the source is the +* natural PWL. The corners at 5000 ns and 5050 ns are breakpoints: ngspice reaches them exactly and restarts its step +* ramp after each (steps of a fraction of the interval that double up to tmax), and it also opens the run with such a +* ramp. So the internal points are NOT the 50 ns grid and the result is backward Euler on a different, nonuniform +* grid: v(1) at 5000 ns differs from the backward Euler recurrence at the 1e-5 level, not at the 1e-15 level. +* Run: ngspice -b -r rc_pwl_naive.raw rc_pwl_naive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 5000n 1 5050n 0) +R1 in 1 1k +C1 1 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 50n 10000n 0 50n uic +.end diff --git a/specs/analog/decks/rlc_adaptive.cir b/specs/analog/decks/rlc_adaptive.cir new file mode 100644 index 0000000000..27ec9e63ec --- /dev/null +++ b/specs/analog/decks/rlc_adaptive.cir @@ -0,0 +1,14 @@ +M1 series RLC, ngspice default adaptive transient +* specs/analog/decks/rlc_adaptive.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* The same circuit and run as rlc_fixed.cir, written as a user would write it: a PWL source and the default options +* (trapezoidal integration, reltol 1e-3, ngspice's own step control). tmax is its default, min(tstep, tstop/50) = 5 ns. +* IN = 1 V until 500 ns and 0 V from 505 ns: step 99 of the run ends at 500 ns, step 100 at 505 ns, and the source +* carries the value of the step's END point, as the backward Euler recurrence does. uic with IC=0 starts the capacitor +* voltage and the inductor current at 0. +* Run: ngspice -b -r rlc_adaptive.raw rlc_adaptive.cir (the raw file holds float64 values) +V1 in 0 PWL(0 1 500n 1 505n 0) +R1 in 1 100 +L1 1 2 10u IC=0 +C1 2 0 1n IC=0 +.tran 5n 1000n uic +.end diff --git a/specs/analog/decks/rlc_fixed.cir b/specs/analog/decks/rlc_fixed.cir new file mode 100644 index 0000000000..eeda2b922d --- /dev/null +++ b/specs/analog/decks/rlc_fixed.cir @@ -0,0 +1,17 @@ +M1 series RLC, backward Euler on a fixed 5 ns grid +* specs/analog/decks/rlc_fixed.cir -- test data for specs/analog/bench.t27 (#10505). A plain netlist, no .control block. +* Circuit: R1 = 100 Ohm from IN to node 1, L1 = 10 uH from node 1 to node 2 (the capacitor voltage), C1 = 1 nF from +* node 2 to ground; capacitor voltage and inductor current start at 0 (specs/analog/linear.t27, circuit 2). +* Run: 200 steps of 5 ns, IN = 1 V for the first 100 steps and 0 V for the next 100. +* The grid is made exact as in rc_fixed.cir: a B source declares no breakpoint, the circuit rests at 0 while the source +* is 0, the source turns on at 42.5 ns (half a step from the nearest grid point) and turns off at 542.5 ns, and from the +* first internal point after 42.5 ns every step is 5 ns. Step k (k = 0..199) ends at the k-th internal point after 42.5 ns. +* tstop = 42.5 ns + 201 steps. gear with maxord 1 is backward Euler; trtol = 1e7 stops the truncation test. +* Run: ngspice -b -r rlc_fixed.raw rlc_fixed.cir (the raw file holds float64 values) +B1 in 0 V = u(time-42.5n) - u(time-542.5n) +R1 in 1 100 +L1 1 2 10u IC=0 +C1 2 0 1n IC=0 +.options method=gear maxord=1 trtol=1e7 +.tran 5n 1047.5n 0 5n uic +.end From b7605a799a5cdabcf191254758e3ee6268c164a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 23:09:21 +0700 Subject: [PATCH 2/2] analog: bench claim rule needs a measured build time -- no speedup claim for any row yet (Closes #10505) Review on PR #10517: may_claim_speedup allowed M0 (wall ratio 1.52), but that ratio compares ngspice process start-up with the UART line, measured inside a sandboxed session, and the bitstream build time is not measured, so the break-even run count FR-001 asks for does not exist. The rule now also requires build_us > 0; with BUILD_US = 0 no row is claimable, and a test shows the same M0 numbers would pass once a build is measured. 11/11 tests, 0 vacuous; seal re-saved and verified. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/analog_AnalogBench.json | 12 +++---- specs/analog/bench.t27 | 50 +++++++++++++++----------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/.trinity/seals/analog_AnalogBench.json b/.trinity/seals/analog_AnalogBench.json index 112e7a744b..226220d291 100644 --- a/.trinity/seals/analog_AnalogBench.json +++ b/.trinity/seals/analog_AnalogBench.json @@ -2,16 +2,16 @@ "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:61e2adc5415b1e02e6b1c29f968efc643e388b6ee55b3aa9c881377944a89f22", - "gen_hash_rust": "sha256:62819f48999916056083c4eaeb21c5703840ed3741192c540806268261df79f8", - "gen_hash_verilog": "sha256:a2b21cb104393b95a80c4917a40e8630deb794831936bddd9399c5754e0444c8", - "gen_hash_zig": "sha256:4d113dbbbec31ffe40a866dede790feae7d73a72a9510a0b7760f04192f6323b", + "gen_hash_c": "sha256:89195447f8fee90c8db58f4eee50988ada367390d06d6c41e171c817e16f2680", + "gen_hash_rust": "sha256:c26da9d6dd68f89c6d9e3d5b8b1ee978836bb638dd9130c6ad1d02d4f14b2b06", + "gen_hash_verilog": "sha256:1fb746ee68114b0e7506ec46cc0277e02a9ac9a7f2236a4f894032ede144f252", + "gen_hash_zig": "sha256:3cc22bec9ee00c4031f82679cb6acabba759bee6dd4538ffb35dbd5a025f9d14", "module": "AnalogBench", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T16:06:41Z", + "sealed_at": "2026-10-18T16:09:11Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:7c993538fc085e78381c9b70dfcf522aa4497274cfc81b9295cf2c0bcb1e27b7", + "spec_hash": "sha256:daa6ede3a542d8bfe2ce93995f3808f0549037a0b63538f2179ec107dc2b2f60", "spec_path": "specs/analog/bench.t27", "tests": { "failed": 0, diff --git a/specs/analog/bench.t27 b/specs/analog/bench.t27 index 98f5f976dd..ceeab4918d 100644 --- a/specs/analog/bench.t27 +++ b/specs/analog/bench.t27 @@ -67,11 +67,14 @@ ; and it is a ratio of two fixed overheads, not of two simulators. The compute-only ratio, ngspice's 796 us .tran over the ; board's 12 us, is 66, a different quantity; the user of a run pays the readout, so it is not a speed claim about a run. ; - -; THE CLAIM RULE (FR-002). may_claim_speedup(state, ng_wall_us, board_wall_us) is true only when the board number is measured -; (BOARD_MEASURED) and wall(ngspice) / wall(board including readout) is at least 1. A circuit with a pending or absent board -; number, a board slower than ngspice, or a zero board time never gets a claim. The tests include those negative controls and -; check that no row but M0 can be claimed. Where the rule says yes, the sentence that goes into a document is the one above -; with its two definitions, not "N times faster than SPICE". +; THE CLAIM RULE (FR-001, FR-002). may_claim_speedup(state, ng_wall_us, board_wall_us, build_us) is true only when the board +; number is measured (BOARD_MEASURED), the bitstream build time is measured (build_us > 0, so the break-even run count can be +; stated: FR-001 says the build MUST be reported, not hidden), and wall(ngspice) / wall(board including readout) is at least 1. +; A circuit with a pending or absent board number, an unmeasured build, a board slower than ngspice, or a zero board time never +; gets a claim. Today BUILD_US is 0, so NO row may be claimed, M0 included: its 1.52 is a ratio of two fixed overheads +; (ngspice process start-up against the UART line, measured inside a sandboxed session), not of two simulators. The tests +; 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". ; - ; 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 @@ -185,10 +188,12 @@ pub fn startup_permille(wall: u32, tran: u32) -> u32 { return ratio_permille(wall - tran, wall); } -/// A speedup over ngspice may be claimed only with a measured board time (including readout) and wall(ngspice) / wall(board) >= 1. -pub fn may_claim_speedup(state: u8, ng_wall_us: u32, board_wall_us: u32) -> bool { +/// A speedup over ngspice may be claimed only with a measured board time (including readout), a measured bitstream build +/// time (so the break-even run count exists), and wall(ngspice) / wall(board) >= 1. +pub fn may_claim_speedup(state: u8, ng_wall_us: u32, board_wall_us: u32, build_us: u64) -> bool { if (state != BOARD_MEASURED) { return false; } if (board_wall_us == 0) { return false; } + if (build_us == 0) { return false; } return ng_wall_us >= board_wall_us; } @@ -298,29 +303,34 @@ test the_m0_ratios_are_what_the_text_says { } test the_claim_rule_and_its_negative_controls { - assert may_claim_speedup(BOARD_MEASURED, 14228, 9387); - assert may_claim_speedup(BOARD_MEASURED, 9387, 9387); - assert !may_claim_speedup(BOARD_MEASURED, 9386, 9387); - assert !may_claim_speedup(BOARD_MEASURED, 9000, 9387); - assert !may_claim_speedup(BOARD_MEASURED, 14228, 0); - assert !may_claim_speedup(BOARD_PENDING, 14228, 9387); - assert !may_claim_speedup(BOARD_NONE, 1000000, 1); - assert !may_claim_speedup(BOARD_PENDING, 1000000, 0); + assert may_claim_speedup(BOARD_MEASURED, 14228, 9387, 60000000); + assert may_claim_speedup(BOARD_MEASURED, 9387, 9387, 60000000); + assert !may_claim_speedup(BOARD_MEASURED, 9386, 9387, 60000000); + assert !may_claim_speedup(BOARD_MEASURED, 9000, 9387, 60000000); + assert !may_claim_speedup(BOARD_MEASURED, 14228, 0, 60000000); + assert !may_claim_speedup(BOARD_PENDING, 14228, 9387, 60000000); + assert !may_claim_speedup(BOARD_NONE, 1000000, 1, 60000000); + assert !may_claim_speedup(BOARD_PENDING, 1000000, 0, 60000000); + // An unmeasured build blocks the claim even where the wall ratio is above 1. + assert !may_claim_speedup(BOARD_MEASURED, 14228, 9387, 0); } test the_table_claims_a_speedup_only_where_a_board_number_exists { var claims : u32 = 0; var i : usize = 0; while (i < ROWS) { - if (may_claim_speedup(BOARD_STATE[i], FIX_WALL_US[i], BOARD_WALL_US[i])) { claims = claims + 1; } + if (may_claim_speedup(BOARD_STATE[i], FIX_WALL_US[i], BOARD_WALL_US[i], BUILD_US)) { claims = claims + 1; } if (BOARD_STATE[i] != BOARD_MEASURED) { assert BOARD_WALL_US[i] == 0; } if (BOARD_STATE[i] == BOARD_MEASURED) { assert BOARD_WALL_US[i] > 0; } i = i + 1; } - assert claims == 1; - assert may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]); - assert may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_MIN_US[ROW_RC], BOARD_WALL_US[ROW_RC]); - assert may_claim_speedup(BOARD_STATE[ROW_RC], ADA_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC]); + // No row may be claimed today: the build time is not measured, M0 included. + assert claims == 0; + assert !may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC], BUILD_US); + assert !may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_MIN_US[ROW_RC], BOARD_WALL_US[ROW_RC], BUILD_US); + assert !may_claim_speedup(BOARD_STATE[ROW_RC], ADA_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC], BUILD_US); + // With a measured build the same M0 numbers would pass the wall test; the build is what is missing. + assert may_claim_speedup(BOARD_STATE[ROW_RC], FIX_WALL_US[ROW_RC], BOARD_WALL_US[ROW_RC], 60000000); assert BOARD_STATE[ROW_RLC] == BOARD_PENDING; assert BOARD_STATE[ROW_LAD5] == BOARD_PENDING; assert BOARD_STATE[ROW_LAD3] == BOARD_NONE;