Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .trinity/seals/analog_AnalogBench.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
192 changes: 190 additions & 2 deletions specs/analog/bench.t27
Original file line number Diff line number Diff line change
Expand Up @@ -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 <deck>, 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

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
41 changes: 41 additions & 0 deletions specs/analog/decks/psp103_inv_be_500f_4us_wall.cir
Original file line number Diff line number Diff line change
@@ -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=<checkout> IHP_OSDI=<osdi dir> 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
Loading