From 2d4d4615f9266f16e0a3b7d47fdd869cf7268e48 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 03:08:48 +0700 Subject: [PATCH] analog M5b: the M4e converter's 8-conversion trace as an ngspice RAW file that ngspice reads back bit for bit -- sar_trace_raw.t27 (Closes #11148) specs/analog/sar_trace_raw.t27 runs the converter of sar_adc.t27 (its functions come in with use, none is copied) for 8 conversions of the fitted model, whose codes equal ngspice's NG_CODE on all 8, builds the trace (time, v(vdp), v(vdn), code) and has raw_writer.t27 write it, binary and ASCII. Both files are pinned in the spec (hex and text) and the tests assert that the writer's bytes equal them; the read-back decodes the pinned hex out of the spec text, so a byte the test pins is a byte ngspice reads. trace_readback reads a file through raw_check and compares every double with the trace by bit pattern, reporting the count, the first point and variable, and the largest difference. specs/analog/decks/sar_trace_raw.cir is the deck of the read-back (load, print, write back). No hand-written program is added: the files ngspice loads are decoded from the spec's constants. Read-back, ngspice-47 in a container on the remote host: points variables file largest difference to the spec's doubles 8 time v(vdp) v(vdn) code binary 0 (0 of 32 differ; print with 17 digits and the rewritten file agree) 8 time v(vdp) v(vdn) code ASCII 4 of 32 differ (the time axis), 1 ulp, 1.9e-16 relative: %.15e holds 16 digits Negative controls, each in the spec: every one of the 256 data bytes flipped (lowest and highest bit) is a file the header checks accept and the reader reports at its point and variable; a header with 9, 7 points or 5 variables is refused (short, trailing, variables); a trace of the broken converter (no parasitic, no offset) differs in all 8 codes. ngspice loads the changed byte and prints the changed value, and refuses the 9-point header ("bad rawfile", "no data read"). Four mutants of the spec (sample instant, a blind reader, a wrong type, a Command line) each fail. Tests: 7, 0 fail, 0 vacuous (t27c test-report); t27b test --check exit 0. Closes #11148 Co-Authored-By: Claude Sonnet 5.5 --- specs/analog/decks/sar_trace_raw.cir | 44 +++ specs/analog/sar_trace_raw.t27 | 411 +++++++++++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 specs/analog/decks/sar_trace_raw.cir create mode 100644 specs/analog/sar_trace_raw.t27 diff --git a/specs/analog/decks/sar_trace_raw.cir b/specs/analog/decks/sar_trace_raw.cir new file mode 100644 index 0000000000..62ab7997da --- /dev/null +++ b/specs/analog/decks/sar_trace_raw.cir @@ -0,0 +1,44 @@ +m5b read back of a sar trace raw file +* specs/analog/decks/sar_trace_raw.cir -- the read-back of specs/analog/sar_trace_raw.t27 (#11148). The deck simulates nothing: its one resistor only gives +* ngspice a circuit, and the control block loads RAW files, prints them and writes them back, so that the doubles ngspice read can be compared with the doubles +* the spec pins. Run: ngspice -b sar_trace_raw.cir (from a directory that holds the four files below; ngspice-47 exits 1, "incomplete or empty netlist", after the +* control block has run, because the deck has no analysis). +* The files are decoded from the text of the spec, not written by another program: +* sar_trace_bin.raw the constants TR_BIN_H0, TR_BIN_H1, TR_BIN_H2 joined and read as hex (500 bytes), for example +* grep '^pub const TR_BIN_H' specs/analog/sar_trace_raw.t27 | sed 's/.*= "//; s/";$//' | tr -d '\n' | xxd -r -p > sar_trace_bin.raw +* sar_trace_asc.raw the constants TR_ASC0, TR_ASC1, TR_ASC2 joined, with the escapes \t and \n read as a tab and a line feed (1004 bytes) +* flip_bin.raw sar_trace_bin.raw with the lowest bit of byte 356 (the first byte of v(vdn) at point 3) flipped: the spec's changed-byte control +* short_bin.raw sar_trace_bin.raw with the line "No. Points: 8" changed to "No. Points: 9": the spec's wrong-point-count control +* ngspice names the plots tran1 .. tran3 as it loads them and writes the variables of a loaded plot in its own order (time, code, v(vdn), v(vdp)); rb_bin.raw and +* rb_asc.raw are the binary files it writes back. +R1 1 0 1k +.control +version +set numdgt=16 +echo ==== BINARY: load +load sar_trace_bin.raw +echo ==== BINARY: display +display +echo ==== BINARY: print all +print all +set filetype=binary +write rb_bin.raw all +echo ==== ASCII: load +load sar_trace_asc.raw +echo ==== ASCII: display +display +echo ==== ASCII: print all +print all +set filetype=binary +write rb_asc.raw all +echo ==== FLIPPED BYTE: load +load flip_bin.raw +echo ==== FLIPPED BYTE: print all +print all +echo ==== WRONG POINT COUNT: load +load short_bin.raw +echo ==== WRONG POINT COUNT: print all +print all +echo ==== DONE +.endc +.end diff --git a/specs/analog/sar_trace_raw.t27 b/specs/analog/sar_trace_raw.t27 new file mode 100644 index 0000000000..d52d9f45fa --- /dev/null +++ b/specs/analog/sar_trace_raw.t27 @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/analog/sar_trace_raw.t27 -- the SAR converter's trace as an ngspice RAW file, and the reader that judges it against the trace +; M5b of the analog emulation epic (#10486, issue #11148). M4e's behavioural converter (specs/analog/sar_adc.t27) converts the held top +; plates of the golden deck; this file runs it for 8 conversions, builds the trace (time, the two plate voltages, the output code) and has the +; M5 writer (specs/analog/raw_writer.t27, #10909) write it as the file ngspice's own `write` writes. Everything the trace needs is called by +; name: the converter's functions and the writer's come in with `use`, no byte layout and no converter arithmetic is copied here. +; - +; THE TRACE. Conversions 0 .. 7 of the 31 the converter pins, with the FITTED model of sar_adc.t27 (Cp = CP_FIT, offset = OFF_FIT_V; the row that +; matches 30 of 31). Four variables, one point per conversion: time (the instant the held plates were read in the golden deck, 98 ns into the +; 500 ns conversion period: 98 ns, 598 ns, ...), v(vdp) and v(vdn) (the held top plates S_VP and S_VN, volts: the converter's inputs, as the +; deck's own run holds them) and code (the 11-bit output code convert_sar gives for those plates, as a float64: 1006, 841, 686, 544, 421, 321, +; 248, 205). On these eight the model's code equals ngspice's NG_CODE on every conversion (a test); the trace is the converter's own run, not a +; copy of the pinned codes. The plate type is `voltage` and the time `time`, as ngspice names them; the code is a count, type `notype`. +; THE FILE. raw_write writes it: the header (Title, Date, Plotname Transient Analysis, Flags real, the counts, the Variables block), then the +; points, ASCII or binary. The file has NO Command line: ngspice executes the text of that line when it loads a file. The Date line is the fixed +; string TR_DATE (the writer takes it as a parameter so that the bytes are the same every run). +; - +; THE PINS. TR_BITS holds the 32 doubles of the trace by bit pattern (time-major: point p, variable v is TR_BITS[4 p + v]); TR_BIN_H0..2 hold the +; whole binary file (500 bytes: the 244-byte header and the 32 little-endian doubles) as hex, TR_ASC0..2 the whole ASCII file (1004 bytes) as text. +; Tests assert that the trace computed from the converter equals TR_BITS and that the writer's bytes equal both files, byte for byte. These constants +; are the file ngspice reads: the read-back job (a scratch script outside the repository; no hand-written program is added for it) takes TR_BIN_H* +; out of this file's text, decodes the hex into a file, and ngspice loads that file. A byte the test pins is a byte ngspice reads. +; THE READER. trace_readback reads a binary file through raw_writer's raw_check (the header lines in order, the counts against the body) and then +; compares every double with the trace by bit pattern, reporting how many differ, the first point and variable, and the largest absolute difference. +; A header that lies about its points or variables is refused by raw_check; a data byte that is changed leaves the shape intact, so the header checks +; pass and the comparison reports the point and the variable. Both are negative controls (tests below), each over every byte of the data. +; - +; THE READ-BACK (the receipt of issue #11148; ngspice-47, KLU build of Mon Oct 19 11:40:05 UTC 2026, in a container on the remote host, one container and +; two threads: `ngspice -b specs/analog/decks/sar_trace_raw.cir` from a directory that holds the four files that deck names, which are decoded from the +; constants below. The deck is one resistor and a control block: `load` of a file, `print all` with `set numdgt=16` (17 significant digits, enough to +; tell any two doubles apart) and `write` as binary, whose doubles are then compared with the pinned ones): +; binary file loads as plot tran1 (Transient Analysis): code notype, time time, v(vdn) voltage, v(vdp) voltage, 8 long each. `print all` shows 32 values that equal +; the pinned doubles exactly (read back as float64), and the binary file ngspice writes from the loaded plot holds the 32 doubles bit for bit: 0 of 32 +; differ, the largest difference is 0. +; ASCII file loads the same way (tran2). Its rewritten binary file differs from the pinned doubles in 4 of 32 values, all on the time axis (points 2, 3, 5, 6), +; by one unit in the last place: at most 1.9e-16 relative, 4.2e-22 s. The file holds 16 significant digits; the other 28 values are exact. +; changed byte the lowest bit of v(vdn) at point 3 flipped: loads, and prints 1.0969568675999999 where the pinned file prints 1.0969568676000001. +; wrong count a header of 9 points over 8 points of data: "bad rawfile, point 8, var time, load aborted", then "no data read": ngspice refuses it as the reader does. +; (the batch run exits 1 only because the deck has no analysis: "incomplete or empty netlist", printed after the control block has run.) +; - +; WHAT A PASS SAYS. The trace is the fitted converter's run on the first 8 held samples of the golden run, with codes that equal ngspice's. The writer's +; binary and ASCII files are the pinned bytes, and the reader takes them (8 points, 4 variables). Every double of the binary file reads back through the +; reader bit for bit, and a changed data byte, a wrong point count, a wrong variable count and a trace from a broken converter (no parasitic, no offset) are +; each refused or reported with the point. The same bytes load in ngspice-47 and print the same doubles (the receipt above). +; WHAT IT DOES NOT SAY. That the test itself runs ngspice: no test here does; the read-back above is a run of the pinned bytes, repeatable with the commands +; named in it. Nothing about the converter beyond the 8 conversions: the other 23 are not in the file, and conversion 0 starts from the deck's .ic state. Nothing +; about the waveform between the held samples: the file holds one point per conversion, not the 500 ns of the run. The ASCII file holds 16 significant digits, +; so a reader gets each value to 2e-16 relative at best (the time axis, a product of an integer and 1 ns, is the one that shows it: ngspice's rewritten +; ASCII file differs from the binary one in the last digit of 4 of the 8 times); only the binary file is exact, and only the binary file is read back +; for equality. The reader checks the syntax of an ASCII value and never its digits, so a changed ASCII digit is caught by the pinned text alone. The +; code variable is typed `notype`: a reader that wants a voltage for it (as the golden deck's V(out) is) rescales it by 1.5 V / 2048. Nothing about any +; reader of the format other than ngspice-47 and this file's own. +; ASCII only, English only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogSarTraceRaw; + +use analog::linear::{q_sm, fabs64}; +use analog::sar_cdac::{UNITS}; +use analog::sar_adc::{S_VP, S_VN, NG_CODE, CP_FIT, OFF_FIT_V, model_k, convert_sar}; +use analog::raw_writer::{RawInfo, RAW_OK, RAW_E_SHORT, RAW_E_TRAIL, RAW_E_VARS, RAW_NONE, raw_write, raw_check, raw_put_header, raw_put_binary, raw_binary_value, raw_get_le64, raw_to_bits, raw_at, raw_at_hex, raw_find}; + +pub const KIND : str = "analog-raw-trace"; +pub const ID : str = "analog/sar_trace_raw"; +pub const REFERENCE : str = "ngspice-47 load and print of the file this spec writes; specs/analog/raw_writer.t27; specs/analog/sar_adc.t27"; + +; --- the trace ------------------------------------------------------------------------------------------------------------- +/// Conversions in the trace: 0 .. 7 of the 31 of sar_adc.t27. +pub const TR_CONV : usize = 8; +/// time, v(vdp), v(vdn), code. +pub const TR_NVARS : usize = 4; +/// TR_CONV * TR_NVARS: the f64 of a trace. +pub const TR_VALS : usize = 32; +/// The variable table of raw_write: "nametype" lines. +pub const TR_VARS : str = "time\ttime\nv(vdp)\tvoltage\nv(vdn)\tvoltage\ncode\tnotype\n"; +pub const TR_TITLE : str = "m5b sar adc, behavioural converter, 8 conversions"; +pub const TR_DATE : str = "Tue Oct 20 00:00:00 2026"; +pub const TR_PLOT : str = "Transient Analysis"; +/// One conversion period of the golden deck, ns (sar_adc.t27, THE CODE and THE SAMPLES). +pub const TR_PERIOD_NS : u32 = 500; +/// The held plates are read 98 ns into the period. +pub const TR_SAMPLE_NS : u32 = 98; +pub const TR_NS : f64 = 1.0e-9; + +/// The trace of the converter with a top-plate parasitic of cp units and a comparator offset of off_v volts into vals (TR_VALS f64, point-major); +/// how many of the TR_CONV codes equal ngspice's. +pub fn sar_trace_with(vals: []f64, cp: f64, off_v: f64) -> u32 { + const kf : [20]u32 = model_k(&UNITS, 1.0, cp); + const off : u32 = q_sm(off_v); + var hit : u32 = 0; + var k : usize = 0; + while (k < TR_CONV) { + const row : usize = k * TR_NVARS; + const c : u32 = convert_sar(&kf, q_sm(S_VP[k]), q_sm(S_VN[k]), off); + vals[row] = ((TR_PERIOD_NS * (k as u32) + TR_SAMPLE_NS) as f64) * TR_NS; + vals[row + 1] = S_VP[k]; + vals[row + 2] = S_VN[k]; + vals[row + 3] = c as f64; + if (c == NG_CODE[k]) { hit = hit + 1; } + k = k + 1; + } + return hit; +} + +/// The trace of the fitted converter (sar_adc.t27 row 2: Cp = CP_FIT, offset = OFF_FIT_V). +pub fn sar_trace(vals: []f64) -> u32 { + return sar_trace_with(vals, CP_FIT, OFF_FIT_V); +} + +/// The trace of the converter with cp and off_v as a RAW file in out, binary or ASCII, written by raw_write; the length it needs. 0 if the converter's +/// codes are not ngspice's on all TR_CONV conversions (a converter that is not the fitted one does not get its run written as this trace). vals: +/// TR_VALS f64 of scratch; a and d: the scratch of raw_put_f64. +pub fn sar_trace_file(out: []u8, binary: bool, cp: f64, off_v: f64, vals: []f64, a: []u64, d: []u8) -> usize { + const hit : u32 = sar_trace_with(vals, cp, off_v); + if (hit != (TR_CONV as u32)) { return 0; } + return raw_write(out, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, vals, binary, a, d); +} + +; --- the reader that judges a file against the trace ---------------------------------------------------------------------- +/// The file is binary and raw_check took it, but it does not hold TR_CONV points of TR_NVARS variables. +pub const TR_E_COUNTS : u32 = 21; +/// The file is ASCII: its digits are not compared. +pub const TR_E_MODE : u32 = 20; + +/// What a binary file reads back as: err is RAW_OK, a raw_check reason or TR_E_*; differ doubles whose bit pattern is not the trace's; the first of them +/// (RAW_NONE if none); the largest absolute difference of the ones that differ. +pub struct TraceRead { err: u32, nv: usize, np: usize, differ: u32, first_point: usize, first_var: usize, worst: f64 } + +/// Read buf[0..n) with raw_check, then compare every double with `want` (TR_VALS f64, point-major) by bit pattern. +pub fn trace_readback(buf: []const u8, n: usize, want: []const f64) -> TraceRead { + var r : TraceRead = TraceRead { err: 0, nv: 0, np: 0, differ: 0, first_point: RAW_NONE, first_var: RAW_NONE, worst: 0.0 }; + const inf : RawInfo = raw_check(buf, n); + r.err = inf.err; + r.nv = inf.nv; + r.np = inf.np; + if (inf.err != RAW_OK) { return r; } + if (!inf.binary) { + r.err = TR_E_MODE; + return r; + } + if (inf.nv != TR_NVARS or inf.np != TR_CONV) { + r.err = TR_E_COUNTS; + return r; + } + var p : usize = 0; + while (p < inf.np) { + var v : usize = 0; + while (v < inf.nv) { + const x : f64 = raw_binary_value(buf, inf, p, v); + const y : f64 = want[p * inf.nv + v]; + if (raw_to_bits(x) != raw_to_bits(y)) { + r.differ = r.differ + 1; + if (r.first_point == RAW_NONE) { + r.first_point = p; + r.first_var = v; + } + const dd : f64 = fabs64(x - y); + if (dd > r.worst) { r.worst = dd; } + } + v = v + 1; + } + p = p + 1; + } + return r; +} + +/// Does the file read back as the trace: accepted by the reader and not one double different? +pub fn trace_equal(r: TraceRead) -> bool { + return r.err == RAW_OK and r.differ == 0; +} + +; --- the pinned file ---------------------------------------------------------------------------------------------------------- +; The 32 doubles of the trace, time-major, by bit pattern; the binary file (header 244 bytes + 256 bytes of data) as hex; the ASCII file as text. +pub const TR_BITS : [32]u64 = [ + 0x3E7A4E823B7B7894, 0x3FE74353DADEE36F, 0x3FE84B83C5309C0B, 0x408F700000000000, + 0x3EA410C7E8255CA0, 0x3FE3AC630B6B82AB, 0x3FEC464C44D3CEDD, 0x408A480000000000, + 0x3EB26BDFC46DA517, 0x3FE01AC8EAA2C82D, 0x3FEFD72A09DA4283, 0x4085700000000000, + 0x3EBACF5B94C89BDE, 0x3FD9ACDDFA863E54, 0x3FF18D22A4F76F03, 0x4081000000000000, + 0x3EC1996BB291C952, 0x3FD3FFB41ABFC2FB, 0x3FF2F8103391C35B, 0x407A500000000000, + 0x3EC5CB299ABF44B6, 0x3FCECE9BDA53AD83, 0x3FF41F17A20148E0, 0x4074100000000000, + 0x3EC9FCE782ECC019, 0x3FC81CC2259A6777, 0x3FF4F5B63C324D6A, 0x406F000000000000, + 0x3ECE2EA56B1A3B7C, 0x3FC42E39069B7573, 0x3FF574488DDAC0F9, 0x4069A00000000000, +]; +pub const TR_BIN_H0 : str = "5469746c653a206d356220736172206164632c206265686176696f7572616c20636f6e7665727465722c203820636f6e76657273696f6e730a446174653a20547565204f63742032302030303a30303a30302020323032360a506c6f746e616d653a205472616e7369656e7420416e616c797369730a466c6167733a207265616c0a4e6f2e205661726961626c65733a20340a4e6f2e20506f696e74733a20380a5661726961626c65733a0a09300974696d650974696d650a09310976287664702909766f6c7461"; +pub const TR_BIN_H1 : str = "67650a093209762876646e2909766f6c746167650a093309636f6465096e6f747970650a42696e6172793a0a94787b3b824e7a3e6fe3deda5343e73f0b9c30c5834be83f0000000000708f40a05c25e8c710a43eab826b0b63ace33fddced3444c46ec3f0000000000488a4017a56dc4df6bb23e2dc8a2eac81ae03f8342da092ad7ef3f0000000000708540de9bc8945bcfba3e543e86faddacd93f036ff7a4228df13f000000000000814052c991b26b99c13efbc2bf1ab4ffd33f5bc3913310f8f23f00000000"; +pub const TR_BIN_H2 : str = "00507a40b644bf9a29cbc53e83ad53da9bcece3fe04801a2171ff43f000000000010744019c0ec82e7fcc93e77679a25c21cc83f6a4d323cb6f5f43f0000000000006f407c3b1a6ba52ece3e73759b06392ec43ff9c0da8d4874f53f0000000000a06940"; +pub const TR_BIN_LEN : usize = 500; +pub const TR_ASC0 : str = "Title: m5b sar adc, behavioural converter, 8 conversions\nDate: Tue Oct 20 00:00:00 2026\nPlotname: Transient Analysis\nFlags: real\nNo. Variables: 4\nNo. Points: 8\nVariables:\n\t0\ttime\ttime\n\t1\tv(vdp)\tvoltage\n\t2\tv(vdn)\tvoltage\n\t3\tcode\tnotype\nValues:\n"; +pub const TR_ASC1 : str = " 0\t9.800000000000000e-08\n\t7.269686961000000e-01\n\t7.592181064000000e-01\n\t1.006000000000000e+03\n\n 1\t5.980000000000000e-07\n\t6.147933219000000e-01\n\t8.835812897000000e-01\n\t8.410000000000000e+02\n\n 2\t1.098000000000000e-06\n\t5.032696326000000e-01\n\t9.950151627000000e-01\n\t6.860000000000000e+02\n\n 3\t1.598000000000000e-06\n\t4.011759707000000e-01\n\t1.096956867600000e+00\n\t5.440000000000000e+02\n\n"; +pub const TR_ASC2 : str = " 4\t2.098000000000000e-06\n\t3.124819051000000e-01\n\t1.185562325900000e+00\n\t4.210000000000000e+02\n\n 5\t2.598000000000000e-06\n\t2.406802002000000e-01\n\t1.257590897400000e+00\n\t3.210000000000000e+02\n\n 6\t3.098000000000000e-06\n\t1.883776363000000e-01\n\t1.309988246100000e+00\n\t2.480000000000000e+02\n\n 7\t3.598000000000000e-06\n\t1.576606066000000e-01\n\t1.340889505500000e+00\n\t2.050000000000000e+02\n\n"; +pub const TR_ASC_LEN : usize = 1004; + +; --- tests --------------------------------------------------------------------------------------------------------------------- + +test the_trace_is_the_converters_own_run_and_its_codes_are_ngspices { + var vals : [32]f64 = [0.0] ** 32; + assert_eq(sar_trace(&vals), 8); + var k : usize = 0; + while (k < TR_CONV) { + const row : usize = k * TR_NVARS; + // the plates are the held samples of the golden run, the code is the converter's and equals ngspice's + assert(vals[row + 1] == S_VP[k]); + assert(vals[row + 2] == S_VN[k]); + assert(vals[row + 3] == (NG_CODE[k] as f64)); + // the time axis: 98 ns, then one 500 ns period per conversion + assert fabs64(vals[row] - (98.0 + 500.0 * (k as f64)) * 0.000000001) < 0.000000000000001; + k = k + 1; + } + // the values are the pinned bit patterns + var i : usize = 0; + while (i < TR_VALS) { + assert(raw_to_bits(vals[i]) == TR_BITS[i]); + i = i + 1; + } + // the codes are 11-bit counts, falling here as the sine's held differential falls + assert(vals[3] == 1006.0); + assert(vals[31] == 205.0); + assert(vals[3] > vals[7]); + assert(vals[27] > vals[31]); +} + +test the_binary_file_is_the_pinned_bytes_and_has_no_command_line { + var vals : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [2048]u8 = [0] ** 2048; + const n : usize = sar_trace_file(&out, true, CP_FIT, OFF_FIT_V, &vals, &a, &d); + assert_eq(n, TR_BIN_LEN); + const want : [3]str = [TR_BIN_H0, TR_BIN_H1, TR_BIN_H2]; + var at : usize = 0; + var k : usize = 0; + while (k < 3) { + assert(raw_at_hex(&out, at, want[k])); + at = at + want[k].len / 2; + k = k + 1; + } + assert_eq(at, n); + // ngspice executes the text of a Command: line when it loads the file, so there is none + assert_eq(raw_find(&out, n, "Command:", 0), RAW_NONE); + // the data are the 32 pinned doubles, little-endian, after the 244-byte header + const inf : RawInfo = raw_check(&out, n); + assert_eq(inf.data, 244); + var i : usize = 0; + while (i < TR_VALS) { + assert(raw_get_le64(&out, inf.data + 8 * i) == TR_BITS[i]); + i = i + 1; + } + // the comparison is not vacuous: one changed byte is not the pinned file + out[300] = out[300] ^ 1; + assert(!raw_at_hex(&out, 200, TR_BIN_H1)); +} + +test the_ascii_file_is_the_pinned_text_and_the_reader_takes_it { + var vals : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [4096]u8 = [0] ** 4096; + const n : usize = sar_trace_file(&out, false, CP_FIT, OFF_FIT_V, &vals, &a, &d); + assert_eq(n, TR_ASC_LEN); + const want : [3]str = [TR_ASC0, TR_ASC1, TR_ASC2]; + var at : usize = 0; + var k : usize = 0; + while (k < 3) { + assert(raw_at(&out, at, want[k])); + at = at + want[k].len; + k = k + 1; + } + assert_eq(at, n); + assert_eq(raw_find(&out, n, "Command:", 0), RAW_NONE); + const inf : RawInfo = raw_check(&out, n); + assert_eq(inf.err, RAW_OK); + assert(!inf.binary); + assert_eq(inf.nv, 4); + assert_eq(inf.np, 8); + // the ASCII file is not read back for equality: the comparison refuses it for that reason + const rm : TraceRead = trace_readback(&out, n, &vals); + assert_eq(rm.err, TR_E_MODE); + // one changed digit is still a number to the reader and is not the pinned text + out[n - 4] = out[n - 4] + 1; + assert_eq(raw_check(&out, n).err, RAW_OK); + assert(!raw_at(&out, n - want[2].len, want[2])); +} + +test every_double_of_the_binary_file_reads_back_bit_for_bit { + var vals : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [2048]u8 = [0] ** 2048; + const n : usize = sar_trace_file(&out, true, CP_FIT, OFF_FIT_V, &vals, &a, &d); + const r : TraceRead = trace_readback(&out, n, &vals); + assert_eq(r.err, RAW_OK); + assert_eq(r.nv, 4); + assert_eq(r.np, 8); + assert_eq(r.differ, 0); + assert_eq(r.first_point, RAW_NONE); + assert_eq(r.first_var, RAW_NONE); + assert(r.worst == 0.0); + assert(trace_equal(r)); + // the same file read against the pinned bit patterns instead of the computed trace + const inf : RawInfo = raw_check(&out, n); + var i : usize = 0; + var worst : f64 = 0.0; + while (i < TR_VALS) { + const x : f64 = raw_binary_value(&out, inf, i / 4, i % 4); + assert(raw_to_bits(x) == TR_BITS[i]); + const dd : f64 = fabs64(x - vals[i]); + if (dd > worst) { worst = dd; } + i = i + 1; + } + assert(worst == 0.0); +} + +test a_data_byte_that_is_changed_does_not_read_back_equal_and_the_reader_reports_the_point { + var vals : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [2048]u8 = [0] ** 2048; + const n : usize = sar_trace_file(&out, true, CP_FIT, OFF_FIT_V, &vals, &a, &d); + const inf : RawInfo = raw_check(&out, n); + assert_eq(inf.err, RAW_OK); + // every one of the 256 data bytes, twice: its lowest bit and its highest bit flipped + var pass : u32 = 0; + while (pass < 2) { + var flip : u8 = 1; + if (pass == 1) { flip = 128; } + var i : usize = 0; + while (i < TR_VALS * 8) { + const at : usize = inf.data + i; + out[at] = out[at] ^ flip; + const r : TraceRead = trace_readback(&out, n, &vals); + // the shape of the file is intact, so the header checks pass; one double differs, and the reader names it + assert_eq(r.err, RAW_OK); + assert_eq(r.differ, 1); + assert_eq(r.first_point, i / 32); + assert_eq(r.first_var, (i % 32) / 8); + assert(r.worst > 0.0); + assert(!trace_equal(r)); + out[at] = out[at] ^ flip; + i = i + 1; + } + pass = pass + 1; + } + // put back, the file reads equal again + assert(trace_equal(trace_readback(&out, n, &vals))); +} + +test a_header_with_the_wrong_point_count_or_variable_count_is_refused { + var vals : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [2048]u8 = [0] ** 2048; + assert_eq(sar_trace(&vals), 8); + // 9 points stated, 8 written: the body is short + var w : usize = raw_put_header(&out, 0, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, TR_NVARS, 9, true); + w = raw_put_binary(&out, w, TR_NVARS, TR_CONV, &vals); + var r : TraceRead = trace_readback(&out, w, &vals); + assert_eq(r.err, RAW_E_SHORT); + assert(!trace_equal(r)); + // 7 points stated, 8 written: bytes follow the last point + w = raw_put_header(&out, 0, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, TR_NVARS, 7, true); + w = raw_put_binary(&out, w, TR_NVARS, TR_CONV, &vals); + r = trace_readback(&out, w, &vals); + assert_eq(r.err, RAW_E_TRAIL); + assert(!trace_equal(r)); + // 5 variables stated for a table of 4 + w = raw_put_header(&out, 0, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, 5, TR_CONV, true); + w = raw_put_binary(&out, w, 5, TR_CONV, &vals); + r = trace_readback(&out, w, &vals); + assert_eq(r.err, RAW_E_VARS); + assert(!trace_equal(r)); + // a file that is consistent but holds 9 points (the ninth is zeros) is accepted by the header checks and is not the 8-point trace + w = raw_put_header(&out, 0, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, TR_NVARS, 9, true); + w = raw_put_binary(&out, w, TR_NVARS, 9, &vals); + assert_eq(raw_check(&out, w).err, RAW_OK); + r = trace_readback(&out, w, &vals); + assert_eq(r.err, TR_E_COUNTS); + assert_eq(r.np, 9); + assert(!trace_equal(r)); + // the right counts read back equal + w = raw_put_header(&out, 0, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, TR_NVARS, TR_CONV, true); + w = raw_put_binary(&out, w, TR_NVARS, TR_CONV, &vals); + assert(trace_equal(trace_readback(&out, w, &vals))); + assert_eq(w, TR_BIN_LEN); +} + +test a_broken_converter_writes_a_file_that_is_a_valid_file_and_is_not_the_trace { + var good : [32]f64 = [0.0] ** 32; + var bad : [32]f64 = [0.0] ** 32; + var a : [90]u64 = [0] ** 90; + var d : [810]u8 = [0] ** 810; + var out : [2048]u8 = [0] ** 2048; + assert_eq(sar_trace(&good), 8); + // no parasitic and no offset: the merged blocks as they are (sar_adc.t27 row 1) match none of ngspice's codes + assert_eq(sar_trace_with(&bad, 0.0, 0.0), 0); + const n : usize = raw_write(&out, TR_TITLE, TR_DATE, "", TR_PLOT, TR_VARS, &bad, true, &a, &d); + assert_eq(n, TR_BIN_LEN); + assert_eq(raw_check(&out, n).err, RAW_OK); + const r : TraceRead = trace_readback(&out, n, &good); + assert_eq(r.err, RAW_OK); + // the plates and the time agree, every code differs: the first difference is the code of point 0 + assert_eq(r.differ, 8); + assert_eq(r.first_point, 0); + assert_eq(r.first_var, 3); + // the codes are 4, 1, 2, 5, 7, 9, 11 and 12 away from ngspice's + assert(r.worst == 12.0); + assert(!trace_equal(r)); + // and the trace writer refuses a converter whose codes are not ngspice's: no parasitic and no offset, the fitted parasitic with no offset + assert_eq(sar_trace_file(&out, true, 0.0, 0.0, &bad, &a, &d), 0); + assert_eq(sar_trace_file(&out, true, CP_FIT, 0.0, &bad, &a, &d), 0); + assert_eq(sar_trace_file(&out, true, CP_FIT, OFF_FIT_V, &bad, &a, &d), TR_BIN_LEN); +}