diff --git a/specs/widgets/bit_autopsy_logic.t27 b/specs/widgets/bit_autopsy_logic.t27 new file mode 100644 index 0000000000..89a2ec773b --- /dev/null +++ b/specs/widgets/bit_autopsy_logic.t27 @@ -0,0 +1,1150 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/widgets/bit_autopsy_logic.t27 -- one Xilinx 7-series bitstream taken apart, as pure rules over +; bytes: the JavaScript file apps/website/public/widgets/bit-autopsy/autopsy.js (289 lines, gHashTag/trinity) +; ported to .t27. Epic gHashTag/t27#10937, issue #10938. Owner rule (Only t27): the logic is written in +; .t27, never by hand in JavaScript. The page reads a dropped or picked file in the reader's browser and +; sends nothing anywhere; these rules keep that: they see only the bytes the glue hands over, fill buffers +; the glue hands over, read no file and hold no state between calls. +; -- +; Table: every export of autopsy.js and the function(s) that replace it. +; SYNC_WORD FRAME_WORDS ECC_WORD ECC_MASK IDCODE_MASK PAD_FRAMES_PER_ROW the constants of the same names +; REG_NAMES, CMD_NAMES the tables of the same names (UG470 tables 5-23 and 5-25); "" where the JavaScript +; has no entry; cmd_name_known(v) says whether CMD_NAMES[v] exists +; hex8(v) hex8: "0x" and eight lower-case hex digits into out, 10 bytes +; parseHeader(u8) parse_header: fills hd, see "header layout" +; findSync(u8,from,limit) find_sync: the offset or -1 +; dieOf(s) die_of: the lower-cased "xc7" letters digits prefix into out; its length, -1 for null +; lookupIdcode(code,parts) lookup_idcode: the index of the entry, -1 for null; parts are the low 32 bits of +; each entry's Number(p[0]), which the glue computes +; frameSlots(geom) frame_slots: the FARs, NONE (4294967295) for a pad slot +; walkPackets(u8,sync) walk_packets: fills wp, fdri, cmds, see "packet layout" +; autopsy(u8,{geoms,parts,buckets}) autopsy: fills res, lists, cells, strs, see "autopsy layout" +; Not ported: nothing of autopsy.js is left out. The private helpers be32, popcount, encodeFar and shade are +; be32, popcount, encode_far and shade_char here. +; -- +; Domain. A bitstream is bytes. Geometry values stay in the ranges the FAR holds (block < 8, bottom < 2, +; row < 32, columns < 1024, minors < 128, 4 or 5 blocks at most are drawn); buckets is at least 1 (status 5 +; otherwise); a file shorter than 4 GiB. JavaScript's Number(p[0]) of the parts table is the glue's. +; -- +; header layout (hd u64 x 12): [0] 1 when the header parsed, [1 .. 8] design offset, length, part offset, +; length, date offset, length, time offset, length (offsets in u8; length is the text up to the first NUL), +; [9] the length field, [10] dataStart. +; packet layout (walk_packets(u, sync, wp, fdri, cmds) -> status 0 ok, 1 fdri or cmds full): +; wp u64 x 24: [0] idcode present (0 when the last IDCODE write had no data), [1] idcode, [2] end, [3] desync, +; [4 .. 14] the counts type1 type2 nop pad writes reads cmd crc far fdri other, [15] refuse count, [16 17] +; refuse codes (1 encrypted, 2 compressed; in order of first occurrence, each once), [18] warning count, +; [19 20] warning codes (1 truncated, 2 badheader), [21] fdri entries, [22] command values. +; fdri u32 x 4 per entry: offset of the first data byte, words, far present, far. +; cmds u32: the CMD value of every CMD write that had data, in order. +; autopsy layout (autopsy(u, geoms, parts, models, buckets, res, lists, cells, strs, fdri, cmds, nz, fars, +; slots, hitall) -> status 0 | 1 | 3 | 5): +; geoms u32: [count], then per geometry: idcode, rows, then per row: block, bottom, row, columns, and +; the column frame counts. +; parts u32 idcodes, models the model texts one per line ("xc7a100\n..."), same order. +; res u64 x 64: [0] bytes, [1] kind (1 bit, 0 bin), [2] header present, [3 .. 10] design, part, date, time +; (offset, length in u), [11] header length field, [12] headerLengthOk (0 false, 1 true, 2 null), +; [13] sync (u64 max for -1), [14] idcode present, [15] idcode, [16] idcodeVersion, [17] device index +; (u64 max for null), [18 19] headerDie (offset, length in strs; length u64 max for null), [20 21] +; idDie, [22] agree (0 false, 1 true, 2 null), [23] zeroBytes, [24] refuse count, [25 26] refuse +; codes (1 encrypted, 2 compressed, 3 nosync), [27] warning count, [28 .. 30] warning codes (1 truncated, +; 2 badheader, 3 partialframe), [31 .. 41] packet counts (as in wp [4 .. 14]), [42] packets present, +; [43] commands, [44] frameWords, [45] frameSlots, [46] padFrames, [47] configFrames, [48] nonzeroFrames, +; [49] ones, [50] mapped, [51] geometry index (u64 max for null), [52 .. 57] blocks 0 1 2 frames and +; nonzero, [58] strip entries. +; lists u32: the commands (res[43] values), then per strip entry block, bottom, row (4294967295 for null). +; cells u8: per strip entry `buckets` characters of the strip. +; strs u8: the two die texts. fdri u32 x 4 per FDRI write; cmds u32; nz u8, fars u32: per frame slot; slots u32: +; per slot of the geometry; hitall u32: 2 * buckets per geometry row (a one-row strip when unmapped). +; status 0 ok, 1 a buffer is too small, 3 nz/fars too small, 5 buckets is 0. +; ASCII only (L3), English only (LANG-EN). +; phi^2 + 1/phi^2 = 3 | TRINITY + +module widget_bit_autopsy_logic; + +pub const SYNC_WORD : u32 = 2862274918; +pub const FRAME_WORDS : u32 = 101; +pub const ECC_WORD : u32 = 50; +pub const ECC_MASK : u32 = 8191; +pub const IDCODE_MASK : u32 = 268435455; +pub const PAD_FRAMES_PER_ROW : u32 = 2; + +pub const NONE : u32 = 4294967295; +pub const NONE64 : u64 = 18446744073709551615; + +pub const REG_CRC : u32 = 0; +pub const REG_FAR : u32 = 1; +pub const REG_FDRI : u32 = 2; +pub const REG_CMD : u32 = 4; +pub const REG_CTL0 : u32 = 5; +pub const REG_MFWR : u32 = 10; +pub const REG_CBC : u32 = 11; +pub const REG_IDCODE : u32 = 12; +pub const CMD_DESYNC : u32 = 13; +pub const CTL0_DEC : u32 = 64; + +pub const REG_NAMES : [32]str = ["CRC", "FAR", "FDRI", "FDRO", "CMD", "CTL0", "MASK", "STAT", "LOUT", "COR0", "MFWR", "CBC", "IDCODE", "AXSS", "COR1", "", "WBSTAR", "TIMER", "", "RBCRC_SW", "", "", "BOOTSTS", "", "CTL1", "", "", "", "", "", "", "BSPI"]; +pub const CMD_NAMES : [20]str = ["NULL", "WCFG", "MFW", "LFRM", "RCFG", "START", "RCAP", "RCRC", "AGHIGH", "SWITCH", "GRESTORE", "SHUTDOWN", "GCAPTURE", "DESYNC", "", "IPROG", "CRCC", "LTIMER", "BSPI_READ", "FALL_EDGE"]; + +/// Does CMD_NAMES have an entry for this CMD value? +pub fn cmd_name_known(v: u32) -> bool { + return v < 20 and v != 14; +} + +// ------------------------------------------------------------------------------------------------ +// small pure functions +// ------------------------------------------------------------------------------------------------ + +pub fn be32(u: []const u8, o: usize) -> u32 { + return ((u[o] as u32) << 24) | ((u[o + 1] as u32) << 16) | ((u[o + 2] as u32) << 8) | (u[o + 3] as u32); +} + +pub fn popcount(x: u32) -> u32 { + var v : u32 = x - ((x >> 1) & 1431655765); + v = (v & 858993459) + ((v >> 2) & 858993459); + const w : u32 = (v + (v >> 4)) & 252645135; + return ((((w as u64) * 16843009) >> 24) & 255) as u32; +} + +pub fn encode_far(block: u32, bottom: u32, row: u32, column: u32, minor: u32) -> u32 { + return ((block & 7) << 23) | ((bottom & 1) << 22) | ((row & 31) << 17) | ((column & 1023) << 7) | (minor & 127); +} + +/// "0x" and eight lower-case hex digits of v into out[0 .. 10]; returns 10. +pub fn hex8(v: u32, out: []u8) -> usize { + out[0] = 48; + out[1] = 120; + var i : usize = 0; + while (i < 8) { + const d : u32 = (v >> ((28 - 4 * (i as u32)) as u32)) & 15; + if (d < 10) { out[2 + i] = (48 + d) as u8; } else { out[2 + i] = (87 + d) as u8; } + i = i + 1; + } + return 10; +} + +/// One character of the strip: '-' no frame there, '0' none non-zero, '1' to '9' the share of non-zero frames. +pub fn shade_char(hit: u32, all: u32) -> u8 { + if (all == 0) { return 45; } + if (hit == 0) { return 48; } + var d : f64 = @ceil(((hit as f64) / (all as f64)) * 9.0); + if (d < 1.0) { d = 1.0; } + return (48 + (d as u32)) as u8; +} + +/// The offset of the sync word at or after `from`, searching `limit` bytes, or -1. +pub fn find_sync(u: []const u8, from: usize, limit: usize) -> i64 { + var end : usize = 0; + if (u.len >= 3) { end = u.len - 3; } + if (from + limit < end) { end = from + limit; } + var i : usize = from; + while (i < end) { + if (u[i] == 170 and u[i + 1] == 153 and u[i + 2] == 85 and u[i + 3] == 102) { return i as i64; } + i = i + 1; + } + return -1; +} + +/// The text of a header field: from start, at most len bytes, stopping at a NUL or the end. +pub fn field_len(u: []const u8, start: usize, len: usize) -> usize { + var i : usize = start; + while (i < start + len and i < u.len and u[i] != 0) { i = i + 1; } + if (i < start) { return 0; } + return i - start; +} + +/// parseHeader: the .bit header; see "header layout". Returns whether it parsed. +pub fn parse_header(u: []const u8, hd: []u64) -> bool { + var k : usize = 0; + while (k < 12) { + hd[k] = 0; + k = k + 1; + } + if (u.len < 13) { return false; } + if (u[0] != 0 or u[1] != 9 or u[2] != 15 or u[3] != 240 or u[4] != 15 or u[5] != 240 or u[6] != 15 or u[7] != 240 or u[8] != 15 or u[9] != 240 or u[10] != 0 or u[11] != 0 or u[12] != 1) { return false; } + var o : usize = 13; + // design, part, date, time: offset and length of the last field of each kind, empty by default + var fo : [4]usize = [0; 4]; + var fl : [4]usize = [0; 4]; + while (o + 3 <= u.len and o < 4096) { + const key : u8 = u[o]; + if (key == 101) { + if (o + 5 > u.len) { return false; } + hd[0] = 1; + var q : usize = 0; + while (q < 4) { + hd[1 + 2 * q] = fo[q] as u64; + hd[2 + 2 * q] = fl[q] as u64; + q = q + 1; + } + hd[9] = be32(u, o + 1) as u64; + hd[10] = (o + 5) as u64; + return true; + } + if (key < 97 or key > 100) { return false; } + const len : usize = ((u[o + 1] as usize) << 8) | (u[o + 2] as usize); + const slot : usize = (key - 97) as usize; + fo[slot] = o + 3; + fl[slot] = field_len(u, o + 3, len); + o = o + 3 + len; + } + return false; +} + +/// dieOf: the lower-cased "xc7", letters, digits prefix of s into out; its length, or -1. +pub fn die_of(s: []const u8, out: []u8) -> i64 { + if (s.len < 5) { return -1; } + if ((s[0] | 32) != 120 or (s[1] | 32) != 99 or s[2] != 55) { return -1; } + var i : usize = 3; + while (i < s.len and ((s[i] >= 97 and s[i] <= 122) or (s[i] >= 65 and s[i] <= 90))) { i = i + 1; } + if (i == 3) { return -1; } + const d : usize = i; + while (i < s.len and s[i] >= 48 and s[i] <= 57) { i = i + 1; } + if (i == d) { return -1; } + var k : usize = 0; + while (k < i) { + var c : u8 = s[k]; + if (c >= 65 and c <= 90) { c = c + 32; } + if (k < out.len) { out[k] = c; } + k = k + 1; + } + return i as i64; +} + +/// lookupIdcode: the index of the first entry whose idcode equals `code` once the version nibble is masked +/// on both sides, or -1. +pub fn lookup_idcode(code: u32, parts: []const u32) -> i64 { + const key : u32 = code & IDCODE_MASK; + var i : usize = 0; + while (i < parts.len) { + if ((parts[i] & IDCODE_MASK) == key) { return i as i64; } + i = i + 1; + } + return -1; +} + +/// Slots of geometry g (its offset in `geoms` after the count): rows * (columns' frames + PAD) entries. +pub fn geom_slot_count(geoms: []const u32, at: usize) -> usize { + var p : usize = at + 2; + const nrows : usize = geoms[at + 1] as usize; + var total : usize = 0; + var r : usize = 0; + while (r < nrows) { + const ncols : usize = geoms[p + 3] as usize; + var c : usize = 0; + while (c < ncols) { + total = total + (geoms[p + 4 + c] as usize); + c = c + 1; + } + total = total + (PAD_FRAMES_PER_ROW as usize); + p = p + 4 + ncols; + r = r + 1; + } + return total; +} + +/// frameSlots of the geometry at `at`: the FAR of every frame slot in FDRI order, NONE for the pad slots. +/// Returns how many; out must hold geom_slot_count entries. +pub fn frame_slots(geoms: []const u32, at: usize, out: []u32) -> usize { + var p : usize = at + 2; + const nrows : usize = geoms[at + 1] as usize; + var n : usize = 0; + var r : usize = 0; + while (r < nrows) { + const block : u32 = geoms[p]; + const bottom : u32 = geoms[p + 1]; + const row : u32 = geoms[p + 2]; + const ncols : usize = geoms[p + 3] as usize; + var c : usize = 0; + while (c < ncols) { + const frames : u32 = geoms[p + 4 + c]; + var mi : u32 = 0; + while (mi < frames) { + if (n < out.len) { out[n] = encode_far(block, bottom, row, c as u32, mi); } + n = n + 1; + mi = mi + 1; + } + c = c + 1; + } + var k : u32 = 0; + while (k < PAD_FRAMES_PER_ROW) { + if (n < out.len) { out[n] = NONE; } + n = n + 1; + k = k + 1; + } + p = p + 4 + ncols; + r = r + 1; + } + return n; +} + +// ------------------------------------------------------------------------------------------------ +// walkPackets +// ------------------------------------------------------------------------------------------------ + +pub fn add_refuse(wp: []u64, code: u64) { + var i : usize = 0; + while (i < (wp[15] as usize)) { + if (wp[16 + i] == code) { return; } + i = i + 1; + } + if (wp[15] < 2) { + wp[16 + (wp[15] as usize)] = code; + wp[15] = wp[15] + 1; + } +} + +pub fn add_warning(wp: []u64, code: u64) { + var i : usize = 0; + while (i < (wp[18] as usize)) { + if (wp[19 + i] == code) { return; } + i = i + 1; + } + if (wp[18] < 2) { + wp[19 + (wp[18] as usize)] = code; + wp[18] = wp[18] + 1; + } +} + +pub fn push_fdri(fdri: []u32, wp: []u64, off: usize, words: usize, far_present: bool, far: u32) { + const k : usize = wp[21] as usize; + if ((k + 1) * 4 <= fdri.len) { + fdri[k * 4] = off as u32; + fdri[k * 4 + 1] = words as u32; + if (far_present) { fdri[k * 4 + 2] = 1; } else { fdri[k * 4 + 2] = 0; } + fdri[k * 4 + 3] = far; + } else { + wp[23] = 1; + } + wp[21] = (k + 1) as u64; +} + +/// walkPackets: see "packet layout". Returns 0, or 1 when fdri or cmds was too small. +pub fn walk_packets(u: []const u8, sync: usize, wp: []u64, fdri: []u32, cmds: []u32) -> u32 { + var z : usize = 0; + while (z < 24) { + wp[z] = 0; + z = z + 1; + } + var o : usize = sync + 4; + var far_present : bool = false; + var far : u32 = 0; + var last_reg : u32 = NONE; + const n : usize = u.len; + var go : bool = true; + while (o + 4 <= n and go) { + const w : u32 = be32(u, o); + const typ : u32 = w >> 29; + if (typ == 1) { + const op : u32 = (w >> 27) & 3; + const reg : u32 = (w >> 13) & 16383; + const count : usize = (w & 2047) as usize; + if (op == 0) { + wp[6] = wp[6] + 1; + o = o + 4; + continue; + } + wp[4] = wp[4] + 1; + if ((o as u64) + 4 + (count as u64) * 4 > (n as u64)) { + add_warning(wp, 1); + o = n; + go = false; + continue; + } + if (op == 1) { wp[9] = wp[9] + 1; } + var advanced : bool = false; + if (op == 2) { + wp[8] = wp[8] + 1; + var has_v : bool = count > 0; + var v : u32 = 0; + if (has_v) { v = be32(u, o + 4); } + if (reg == REG_CMD) { + wp[10] = wp[10] + 1; + if (has_v) { + const k : usize = wp[22] as usize; + if (k < cmds.len) { cmds[k] = v; } else { wp[23] = 1; } + wp[22] = (k + 1) as u64; + } + } else { + if (reg == REG_CRC) { + wp[11] = wp[11] + 1; + } else { + if (reg == REG_FAR) { + wp[12] = wp[12] + 1; + far_present = has_v; + far = v; + } else { + if (reg == REG_IDCODE) { + if (has_v) { wp[0] = 1; wp[1] = v as u64; } else { wp[0] = 0; wp[1] = 0; } + } else { + if (reg == REG_CBC) { + add_refuse(wp, 1); + } else { + if (reg == REG_CTL0 and has_v and (v & CTL0_DEC) != 0) { + add_refuse(wp, 1); + } else { + if (reg == REG_MFWR) { add_refuse(wp, 2); } + } + } + } + } + } + } + if (reg == REG_FDRI) { + wp[13] = wp[13] + 1; + if (count > 0) { push_fdri(fdri, wp, o + 4, count, far_present, far); } + } + if (reg == REG_CMD and has_v and v == CMD_DESYNC) { + o = o + 4 + count * 4; + wp[3] = 1; + advanced = true; + go = false; + } + } + if (!advanced) { + last_reg = reg; + o = o + 4 + count * 4; + } + } else { + if (typ == 2) { + const op : u32 = (w >> 27) & 3; + const count : usize = (w & 134217727) as usize; + wp[5] = wp[5] + 1; + if ((o as u64) + 4 + (count as u64) * 4 > (n as u64)) { + add_warning(wp, 1); + o = n; + go = false; + continue; + } + if (op == 2) { + wp[8] = wp[8] + 1; + if (last_reg == REG_FDRI) { + wp[13] = wp[13] + 1; + push_fdri(fdri, wp, o + 4, count, far_present, far); + } + } + o = o + 4 + count * 4; + } else { + if (w == 4294967295 or w == 0) { + wp[7] = wp[7] + 1; + o = o + 4; + } else { + wp[14] = wp[14] + 1; + add_warning(wp, 2); + o = o + 4; + } + } + } + } + wp[2] = o as u64; + if (wp[23] != 0) { return 1; } + return 0; +} + +// ------------------------------------------------------------------------------------------------ +// the autopsy +// ------------------------------------------------------------------------------------------------ + +/// The offset in `geoms` of the geometry whose idcode matches `code` (version nibble masked), or -1. +pub fn find_geom(geoms: []const u32, code: u32) -> i64 { + const count : usize = geoms[0] as usize; + var at : usize = 1; + var g : usize = 0; + while (g < count) { + if ((geoms[at] & IDCODE_MASK) == (code & IDCODE_MASK)) { return at as i64; } + const nrows : usize = geoms[at + 1] as usize; + var p : usize = at + 2; + var r : usize = 0; + while (r < nrows) { + p = p + 4 + (geoms[p + 3] as usize); + r = r + 1; + } + at = p; + g = g + 1; + } + return -1; +} + +pub fn geom_index_of(geoms: []const u32, at: usize) -> usize { + const count : usize = geoms[0] as usize; + var a : usize = 1; + var g : usize = 0; + while (g < count) { + if (a == at) { return g; } + const nrows : usize = geoms[a + 1] as usize; + var p : usize = a + 2; + var r : usize = 0; + while (r < nrows) { + p = p + 4 + (geoms[p + 3] as usize); + r = r + 1; + } + a = p; + g = g + 1; + } + return 0; +} + +/// The k-th line of a newline-separated text, as (start, end). +pub fn nth_line(t: []const u8, k: usize) -> i64 { + var line : usize = 0; + var start : usize = 0; + var i : usize = 0; + while (i <= t.len) { + if (i == t.len or t[i] == 10) { + if (line == k) { return ((start as i64) * 4294967296) + (i as i64); } + line = line + 1; + start = i + 1; + } + i = i + 1; + } + return ((t.len as i64) * 4294967296) + (t.len as i64); +} + +/// autopsy: see "autopsy layout" in the header. +pub fn autopsy(u: []const u8, geoms: []const u32, parts: []const u32, models: []const u8, buckets: usize, res: []u64, lists: []u32, cells: []u8, strs: []u8, fdri: []u32, cmds: []u32, nz: []u8, fars: []u32, slots: []u32, hitall: []u32) -> u32 { + var z : usize = 0; + while (z < 64) { + res[z] = 0; + z = z + 1; + } + res[13] = NONE64; + res[17] = NONE64; + res[19] = NONE64; + res[21] = NONE64; + res[22] = 2; + res[12] = 2; + res[51] = NONE64; + if (buckets == 0) { return 5; } + res[0] = u.len as u64; + var zeros : u64 = 0; + var i : usize = 0; + while (i < u.len) { + if (u[i] == 0) { zeros = zeros + 1; } + i = i + 1; + } + res[23] = zeros; + var hd : [12]u64 = [0; 12]; + var from : usize = 0; + var header_die_len : i64 = -1; + var strs_used : usize = 0; + if (parse_header(u, hd[0 .. 12])) { + res[1] = 1; + res[2] = 1; + z = 0; + while (z < 8) { + res[3 + z] = hd[1 + z]; + z = z + 1; + } + res[11] = hd[9]; + if (hd[9] == ((u.len as u64) - hd[10])) { res[12] = 1; } else { res[12] = 0; } + const po : usize = hd[3] as usize; + const pl : usize = hd[4] as usize; + header_die_len = die_of(u[po .. po + pl], strs[0 .. strs.len]); + if (header_die_len > (strs.len as i64)) { return 1; } + if (header_die_len >= 0) { + res[18] = 0; + res[19] = header_die_len as u64; + strs_used = header_die_len as usize; + } + from = hd[10] as usize; + } + const sy : i64 = find_sync(u, from, 1048576); + if (sy < 0) { + res[24] = 1; + res[25] = 3; + return 0; + } + res[13] = sy as u64; + var wp : [24]u64 = [0; 24]; + const wst : u32 = walk_packets(u, sy as usize, wp[0 .. 24], fdri, cmds); + if (wst != 0) { return 1; } + res[42] = 1; + z = 0; + while (z < 11) { + res[31 + z] = wp[4 + z]; + z = z + 1; + } + res[43] = wp[22]; + z = 0; + while (z < (wp[22] as usize)) { + if (z < lists.len) { lists[z] = cmds[z]; } else { return 1; } + z = z + 1; + } + res[27] = wp[18]; + res[28] = wp[19]; + res[29] = wp[20]; + res[24] = wp[15]; + res[25] = wp[16]; + res[26] = wp[17]; + var have_id : bool = wp[0] == 1; + var idc : u32 = 0; + if (have_id) { + idc = wp[1] as u32; + res[14] = 1; + res[15] = wp[1]; + res[16] = (idc >> 28) as u64; + const di : i64 = lookup_idcode(idc, parts); + if (di >= 0) { + res[17] = di as u64; + const ln : i64 = nth_line(models, di as usize); + const ms : usize = (ln / 4294967296) as usize; + const me : usize = (ln % 4294967296) as usize; + const idl : i64 = die_of(models[ms .. me], strs[strs_used .. strs.len]); + if (idl > ((strs.len - strs_used) as i64)) { return 1; } + if (idl >= 0) { + res[20] = strs_used as u64; + res[21] = idl as u64; + strs_used = strs_used + (idl as usize); + } + } + if (res[21] != NONE64 and res[19] != NONE64) { + // agree: the two lower-cased dies are the same text + res[22] = 0; + if (res[21] == res[19]) { + var same : bool = true; + var q : usize = 0; + while (q < (res[19] as usize)) { + if (strs[(res[18] as usize) + q] != strs[(res[20] as usize) + q]) { same = false; } + q = q + 1; + } + if (same) { res[22] = 1; } + } + } + } + if (wp[15] > 0) { return 0; } + + // the frames + var frame_words : u64 = 0; + z = 0; + while (z < (wp[21] as usize)) { + frame_words = frame_words + (fdri[z * 4 + 1] as u64); + z = z + 1; + } + res[44] = frame_words; + const frame_slots_n : usize = (frame_words / (FRAME_WORDS as u64)) as usize; + res[45] = frame_slots_n as u64; + if (frame_words % (FRAME_WORDS as u64) != 0) { + // partialframe is appended after the packet warnings + const wc : usize = res[27] as usize; + if (wc < 3) { + res[28 + wc] = 3; + res[27] = (wc + 1) as u64; + } + } + if (nz.len < frame_slots_n or fars.len < frame_slots_n) { return 3; } + z = 0; + while (z < frame_slots_n) { + nz[z] = 0; + z = z + 1; + } + var k : usize = 0; + var nonzero_frames : u64 = 0; + var ones : u64 = 0; + var e : usize = 0; + while (e < (wp[21] as usize)) { + const woff : usize = fdri[e * 4] as usize; + const nf : usize = ((fdri[e * 4 + 1] as u64) / (FRAME_WORDS as u64)) as usize; + var f : usize = 0; + while (f < nf and k < frame_slots_n) { + const base : usize = woff + f * (FRAME_WORDS as usize) * 4; + var fones : u64 = 0; + var wi : usize = 0; + while (wi < (FRAME_WORDS as usize)) { + var v : u32 = be32(u, base + wi * 4); + if (wi == (ECC_WORD as usize)) { v = v & (4294967295 - ECC_MASK); } + if (v != 0) { fones = fones + (popcount(v) as u64); } + wi = wi + 1; + } + nz[k] = 0; + if (fones != 0) { + nz[k] = 1; + nonzero_frames = nonzero_frames + 1; + ones = ones + fones; + } + f = f + 1; + k = k + 1; + } + e = e + 1; + } + res[48] = nonzero_frames; + res[49] = ones; + + // frame addresses from the die's layout + var geom_at : i64 = -1; + if (have_id) { geom_at = find_geom(geoms, idc); } + var mapped : bool = false; + var nslots : usize = 0; + if (geom_at >= 0) { + const ga : usize = geom_at as usize; + const need : usize = geom_slot_count(geoms, ga); + if (slots.len < need) { return 1; } + nslots = frame_slots(geoms, ga, slots[0 .. need]); + // fars[j]: NONE for a pad slot, NONE - 1 for undefined, else the FAR + var ok : bool = true; + var j : usize = 0; + e = 0; + while (e < (wp[21] as usize)) { + const nf : usize = ((fdri[e * 4 + 1] as u64) / (FRAME_WORDS as u64)) as usize; + var at : i64 = -1; + if (fdri[e * 4 + 2] == 1) { + var s : usize = 0; + while (s < nslots and at < 0) { + if (slots[s] != NONE and slots[s] == fdri[e * 4 + 3]) { at = s as i64; } + s = s + 1; + } + } + if (at < 0) { ok = false; } + var f : usize = 0; + while (f < nf and j < frame_slots_n) { + fars[j] = NONE - 1; + if (at >= 0 and (at as usize) + f < nslots) { fars[j] = slots[(at as usize) + f]; } + f = f + 1; + j = j + 1; + } + e = e + 1; + } + // frames never reached keep the initial `undefined` of fill(undefined) + while (j < frame_slots_n) { + fars[j] = NONE - 1; + j = j + 1; + } + if (ok) { + mapped = true; + res[50] = 1; + res[51] = geom_index_of(geoms, ga) as u64; + } + } + if (mapped) { + const ga : usize = geom_at as usize; + const nrows : usize = geoms[ga + 1] as usize; + var pads : u64 = 0; + var q : usize = 0; + while (q < frame_slots_n) { + if (fars[q] == NONE) { pads = pads + 1; } + q = q + 1; + } + res[46] = pads; + res[47] = (frame_slots_n as u64) - pads; + if (hitall.len < 2 * buckets * nrows) { return 1; } + var h : usize = 0; + while (h < 2 * buckets * nrows) { + hitall[h] = 0; + h = h + 1; + } + // geometry rows: p offsets + var fi : usize = 0; + while (fi < frame_slots_n) { + const far : u32 = fars[fi]; + if (far != NONE and far != NONE - 1) { + const blk : u32 = (far >> 23) & 7; + if (blk < 3) { + res[52 + 2 * (blk as usize)] = res[52 + 2 * (blk as usize)] + 1; + if (nz[fi] == 1) { res[53 + 2 * (blk as usize)] = res[53 + 2 * (blk as usize)] + 1; } + } + // the row: the last geometry row with the same block, bottom and row + var ri : i64 = -1; + var rp : usize = ga + 2; + var r : usize = 0; + var rpos : usize = 0; + while (r < nrows) { + const gb : u32 = geoms[rp]; + const gbo : u32 = geoms[rp + 1]; + const gr : u32 = geoms[rp + 2]; + if (((gb << 6) | (gbo << 5) | gr) == (far >> 17)) { + ri = r as i64; + rpos = rp; + } + rp = rp + 4 + (geoms[rp + 3] as usize); + r = r + 1; + } + if (ri >= 0) { + // total frames of the row, and the start of the column named by the FAR + const ncols : usize = geoms[rpos + 3] as usize; + var total : u64 = 0; + var c : usize = 0; + while (c < ncols) { + total = total + (geoms[rpos + 4 + c] as u64); + c = c + 1; + } + // colStart: the last row with this block/bottom/row key and a column of that number + var cstart : u64 = 0; + var rp2 : usize = ga + 2; + var r2 : usize = 0; + const want : u32 = far & (4294967295 - 127); + while (r2 < nrows) { + const nc2 : usize = geoms[rp2 + 3] as usize; + var acc : u64 = 0; + var c2 : usize = 0; + while (c2 < nc2) { + if (encode_far(geoms[rp2], geoms[rp2 + 1], geoms[rp2 + 2], c2 as u32, 0) == want) { cstart = acc; } + acc = acc + (geoms[rp2 + 4 + c2] as u64); + c2 = c2 + 1; + } + rp2 = rp2 + 4 + nc2; + r2 = r2 + 1; + } + const pos : u64 = cstart + ((far & 127) as u64); + const ratio : f64 = ((pos as f64) / (total as f64)) * (buckets as f64); + var bi : f64 = @floor(ratio); + if (ratio == ratio) { + if (bi > ((buckets - 1) as f64)) { bi = (buckets - 1) as f64; } + const b : usize = bi as usize; + const base : usize = (ri as usize) * 2 * buckets; + hitall[base + buckets + b] = hitall[base + buckets + b] + 1; + if (nz[fi] == 1) { hitall[base + b] = hitall[base + b] + 1; } + } + } + } + fi = fi + 1; + } + // the strip: one entry per geometry row + res[58] = nrows as u64; + const lbase : usize = res[43] as usize; + if (lists.len < lbase + 3 * nrows or cells.len < buckets * nrows) { return 1; } + var r : usize = 0; + var rp : usize = ga + 2; + while (r < nrows) { + lists[lbase + r * 3] = geoms[rp]; + lists[lbase + r * 3 + 1] = geoms[rp + 1]; + lists[lbase + r * 3 + 2] = geoms[rp + 2]; + var b : usize = 0; + while (b < buckets) { + cells[r * buckets + b] = shade_char(hitall[r * 2 * buckets + b], hitall[r * 2 * buckets + buckets + b]); + b = b + 1; + } + rp = rp + 4 + (geoms[rp + 3] as usize); + r = r + 1; + } + } else { + res[47] = frame_slots_n as u64; + if (frame_slots_n > 0) { + if (hitall.len < 2 * buckets) { return 1; } + var h : usize = 0; + while (h < 2 * buckets) { + hitall[h] = 0; + h = h + 1; + } + var per : f64 = (frame_slots_n as f64) / (buckets as f64); + if (per == 0.0 or per != per) { per = 1.0; } + var fi : usize = 0; + while (fi < frame_slots_n) { + var cf : f64 = @floor((fi as f64) / per); + if (cf > ((buckets - 1) as f64)) { cf = (buckets - 1) as f64; } + const c : usize = cf as usize; + hitall[buckets + c] = hitall[buckets + c] + 1; + if (nz[fi] == 1) { hitall[c] = hitall[c] + 1; } + fi = fi + 1; + } + res[58] = 1; + const lbase : usize = res[43] as usize; + if (lists.len < lbase + 3 or cells.len < buckets) { return 1; } + lists[lbase] = NONE; + lists[lbase + 1] = NONE; + lists[lbase + 2] = NONE; + var b : usize = 0; + while (b < buckets) { + cells[b] = shade_char(hitall[b], hitall[buckets + b]); + b = b + 1; + } + } + } + return 0; +} + + + +// ------------------------------------------------------------------------------------------------ +// tests: the first 369 bytes of blink_j26.bit (the header, the sync word at 181 and the packets up to the +// first FDRI write, from gHashTag/t27 fpga/openxc7-synth) and small streams built word by word; the +// answers are samples.json's for the first, and autopsy.js's for the others. +// ------------------------------------------------------------------------------------------------ + +pub const REAL_HEAD : [369]u8 = [0, 9, 15, 240, 15, 240, 15, 240, 15, 240, 0, 0, 1, 97, 0, 66, 47, 119, 111, 114, 107, 47, 102, 112, 103, 97, 47, 111, 112, 101, 110, 120, 99, 55, 45, 115, 121, 110, 116, 104, 47, 98, 108, 105, 110, 107, 95, 106, 50, 54, 46, 102, 114, 97, 109, 101, 115, 59, 71, 101, 110, 101, 114, 97, 116, 111, 114, 61, 120, 99, 55, 102, 114, 97, 109, 101, 115, 50, 98, 105, 116, 0, 98, 0, 17, 120, 99, 55, 97, 49, 48, 48, 116, 102, 103, 103, 54, 55, 54, 45, 49, 0, 99, 0, 11, 50, 48, 50, 54, 47, 48, 53, 47, 48, 56, 0, 100, 0, 9, 49, 57, 58, 51, 51, 58, 50, 50, 0, 101, 0, 58, 96, 124, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 187, 17, 34, 0, 68, 255, 255, 255, 255, 255, 255, 255, 255, 170, 153, 85, 102, 32, 0, 0, 0, 48, 2, 32, 1, 0, 0, 0, 0, 48, 2, 0, 1, 0, 0, 0, 0, 48, 0, 128, 1, 0, 0, 0, 0, 32, 0, 0, 0, 48, 0, 128, 1, 0, 0, 0, 7, 32, 0, 0, 0, 32, 0, 0, 0, 48, 2, 96, 1, 0, 0, 0, 0, 48, 1, 32, 1, 2, 0, 63, 229, 48, 1, 192, 1, 0, 0, 0, 0, 48, 1, 128, 1, 3, 99, 16, 147, 48, 0, 128, 1, 0, 0, 0, 9, 32, 0, 0, 0, 48, 0, 192, 1, 0, 0, 4, 1, 48, 0, 160, 1, 0, 0, 5, 1, 48, 0, 192, 1, 0, 0, 0, 0, 48, 3, 0, 1, 0, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 48, 0, 32, 1, 0, 0, 0, 0, 48, 0, 128, 1, 0, 0, 0, 1, 32, 0, 0, 0, 48, 0, 64, 0, 80, 14, 149, 216]; + +pub var T_BIN : [4096]u8 = [0; 4096]; +pub var T_RES : [64]u64 = [0; 64]; +pub var T_LISTS : [256]u32 = [0; 256]; +pub var T_CELLS : [256]u8 = [0; 256]; +pub var T_STRS : [64]u8 = [0; 64]; +pub var T_FDRI : [64]u32 = [0; 64]; +pub var T_CMDS : [64]u32 = [0; 64]; +pub var T_NZ : [512]u8 = [0; 512]; +pub var T_FARS : [512]u32 = [0; 512]; +pub var T_SLOTS : [4096]u32 = [0; 4096]; +pub var T_HIT : [1024]u32 = [0; 1024]; +pub var T_HD : [12]u64 = [0; 12]; +pub var T_WP : [24]u64 = [0; 24]; +pub var T_OUT : [32]u8 = [0; 32]; + +/// The test geometry (one device, two rows) and the real a100t layout, flat as autopsy wants them. +pub var T_GEOM : [64]u32 = [0; 64]; +pub var T_PARTS : [2]u32 = [0; 2]; + +pub fn t_setup() { + T_GEOM[0] = 1; + T_GEOM[1] = 56807571; + T_GEOM[2] = 2; + T_GEOM[3] = 0; + T_GEOM[4] = 0; + T_GEOM[5] = 0; + T_GEOM[6] = 2; + T_GEOM[7] = 2; + T_GEOM[8] = 1; + T_GEOM[9] = 1; + T_GEOM[10] = 0; + T_GEOM[11] = 0; + T_GEOM[12] = 1; + T_GEOM[13] = 1; + T_PARTS[0] = 56807571; + T_PARTS[1] = 56823955; +} + +pub fn t_models() -> []const u8 { + return "xc7a35\nxc7a100\n"; +} + +/// Zero T_BIN and write one big-endian word. +pub fn t_clear() { + var i : usize = 0; + while (i < 4096) { + T_BIN[i] = 0; + i = i + 1; + } +} + +pub fn t_word(at: usize, v: u32) { + T_BIN[at] = ((v >> 24) & 255) as u8; + T_BIN[at + 1] = ((v >> 16) & 255) as u8; + T_BIN[at + 2] = ((v >> 8) & 255) as u8; + T_BIN[at + 3] = (v & 255) as u8; +} + +pub fn t_run(len: usize, buckets: usize) -> u32 { + t_setup(); + return autopsy(T_BIN[0 .. len], T_GEOM[0 .. 64], T_PARTS[0 .. 2], t_models(), buckets, T_RES[0 .. 64], T_LISTS[0 .. 256], T_CELLS[0 .. 256], T_STRS[0 .. 64], T_FDRI[0 .. 64], T_CMDS[0 .. 64], T_NZ[0 .. 512], T_FARS[0 .. 512], T_SLOTS[0 .. 4096], T_HIT[0 .. 1024]); +} + + +test the_real_head_says_what_samples_json_says_about_blink_j26 { + t_clear(); + var i : usize = 0; + while (i < 369) { + T_BIN[i] = REAL_HEAD[i]; + i = i + 1; + } + t_setup(); + assert autopsy(T_BIN[0 .. 369], T_GEOM[0 .. 64], T_PARTS[0 .. 2], t_models(), 160, T_RES[0 .. 64], T_LISTS[0 .. 256], T_CELLS[0 .. 256], T_STRS[0 .. 64], T_FDRI[0 .. 64], T_CMDS[0 .. 64], T_NZ[0 .. 512], T_FARS[0 .. 512], T_SLOTS[0 .. 4096], T_HIT[0 .. 1024]) == 0; + assert T_RES[0] == 369; + assert T_RES[1] == 1; + assert T_RES[2] == 1; + assert T_RES[13] == 181; + assert T_RES[11] == 3825788; + assert T_RES[12] == 0; + assert T_RES[14] == 1; + assert T_RES[15] == 56823955; + assert T_RES[16] == 0; + assert T_RES[17] == 1; + assert T_RES[22] == 1; + assert T_RES[24] == 0; + assert T_RES[27] == 1; + assert T_RES[28] == 1; + assert T_RES[31] == 16; + assert T_RES[32] == 1; + assert T_RES[33] == 14; + assert T_RES[37] == 4; + assert T_RES[43] == 4; + assert T_LISTS[0] == 0; + assert T_LISTS[1] == 7; + assert T_LISTS[3] == 1; + assert T_RES[42] == 1; + assert T_RES[50] == 0; + assert T_RES[45] == 0; + assert hd_part_is("xc7a100tfgg676-1"); +} + +pub fn hd_part_is(w: []const u8) -> bool { + return parse_header(T_BIN[0 .. 369], T_HD[0 .. 12]) and (T_HD[4] as usize) == w.len and T_BIN[T_HD[3] as usize] == w[0] and T_BIN[(T_HD[3] as usize) + w.len - 1] == w[w.len - 1] and T_HD[0] == 1 and T_HD[10] == 133; +} + +test a_mapped_stream_counts_frames_and_ones_and_draws_the_strip { + t_clear(); + t_word(0, 2862175590); + t_word(4, 536870912); + t_word(8, 805404673); + t_word(12, 56807571); + t_word(16, 805339137); + t_word(20, 7); + t_word(24, 805314561); + t_word(32, 805339137); + t_word(36, 1); + t_word(40, 805323055); + t_word(56, 2147483649); + t_word(244, 8191); + t_word(1052, 65535); + t_word(1252, 1); + t_word(1256, 805339137); + t_word(1260, 13); + t_word(1264, 4294967295); + assert t_run(1268, 8) == 0; + assert T_RES[1] == 0; + assert T_RES[13] == 0; + assert T_RES[14] == 1; + assert T_RES[17] == 0; + assert T_RES[44] == 303; + assert T_RES[45] == 3; + assert T_RES[48] == 2; + assert T_RES[49] == 6; + assert T_RES[50] == 1; + assert T_RES[46] == 0; + assert T_RES[47] == 3; + assert T_RES[52] == 3; + assert T_RES[53] == 2; + assert T_RES[58] == 2; + assert T_RES[43] == 3; + assert T_LISTS[3] == 0; + assert T_CELLS[0] == 57; + assert T_CELLS[1] == 45; + assert T_CELLS[2] == 48; + assert T_CELLS[3] == 45; + assert T_CELLS[4] == 45; + assert T_CELLS[5] == 57; + assert T_CELLS[6] == 45; + assert T_CELLS[7] == 45; + assert T_LISTS[6] == 1; + assert T_CELLS[8] == 45; + assert T_CELLS[9] == 45; + assert T_CELLS[10] == 45; + assert T_CELLS[11] == 45; + assert T_CELLS[12] == 45; + assert T_CELLS[13] == 45; + assert T_CELLS[14] == 45; + assert T_CELLS[15] == 45; + assert T_RES[37] == 3; + assert T_RES[27] == 0; +} + +test an_unknown_device_is_drawn_in_stream_order { + t_clear(); + t_word(0, 2862175590); + t_word(4, 805404673); + t_word(8, 326307987); + t_word(12, 805322954); + t_word(20, 3); + t_word(448, 240); + assert t_run(824, 4) == 0; + assert T_RES[14] == 1; + assert T_RES[17] == 18446744073709551615; + assert T_RES[50] == 0; + assert T_RES[45] == 2; + assert T_RES[48] == 2; + assert T_RES[49] == 6; + assert T_RES[58] == 1; + assert T_LISTS[0] == 4294967295; + assert T_CELLS[0] == 57; + assert T_CELLS[1] == 45; + assert T_CELLS[2] == 57; + assert T_CELLS[3] == 45; +} + +test an_encrypted_stream_is_refused_before_any_frame_is_read { + t_clear(); + t_word(0, 2862175590); + t_word(4, 805404673); + t_word(8, 56823955); + t_word(12, 805396481); + t_word(16, 5); + t_word(20, 805322853); + t_word(28, 1); + assert t_run(428, 4) == 0; + assert T_RES[24] == 1; + assert T_RES[25] == 1; + assert T_RES[44] == 0; + assert T_RES[14] == 1; + assert T_RES[17] == 1; +} + +test compression_and_the_decrypt_bit_are_refusals_too { + t_clear(); + t_word(0, 2862175590); + t_word(4, 805388290); + t_word(8, 1); + t_word(12, 2); + assert t_run(16, 4) == 0; + assert T_RES[24] == 1; + assert T_RES[25] == 2; + assert T_RES[14] == 0; + t_word(4, 805347329); + t_word(8, 64); + t_word(12, 0); + assert t_run(16, 4) == 0; + assert T_RES[24] == 1; + assert T_RES[25] == 1; +} + +test a_bad_word_a_partial_frame_and_a_cut_write_are_warnings { + t_clear(); + t_word(0, 2862175590); + t_word(4, 1073741824); + t_word(8, 805404673); + t_word(12, 56823955); + t_word(20, 4294967295); + t_word(24, 805322902); + t_word(36, 9); + assert t_run(628, 4) == 0; + assert T_RES[27] == 1; + assert T_RES[28] == 3; + assert T_RES[44] == 150; + assert T_RES[45] == 1; + assert T_RES[41] == 0; + assert T_RES[34] == 2; +} + +test no_sync_word_means_nosync_and_the_small_functions_agree { + t_clear(); + T_BIN[0] = 1; + T_BIN[1] = 2; + T_BIN[2] = 3; + T_BIN[3] = 4; + T_BIN[4] = 5; + assert t_run(5, 4) == 0; + assert T_RES[24] == 1; + assert T_RES[25] == 3; + assert T_RES[13] == 18446744073709551615; + assert T_RES[42] == 0; + assert hex8(56823955, T_OUT[0 .. 32]) == 10; + assert T_OUT[0] == 48; + assert T_OUT[1] == 120; + assert T_OUT[2] == 48; + assert T_OUT[3] == 51; + assert T_OUT[4] == 54; + assert T_OUT[9] == 51; + assert popcount(4294967295) == 32; + assert popcount(2863311530) == 16; + assert popcount(0) == 0; + assert encode_far(1, 1, 3, 5, 9) == 12976777; + assert shade_char(0, 0) == 45; + assert shade_char(0, 4) == 48; + assert shade_char(1, 100) == 49; + assert shade_char(9, 9) == 57; + assert shade_char(1, 3) == 51; + assert cmd_name_known(13); + assert cmd_name_known(14) == false; + assert cmd_name_known(20) == false; + assert find_sync(REAL_HEAD[0 .. 369], 0, 1048576) == 181; + assert find_sync(REAL_HEAD[0 .. 369], 182, 1048576) == -1; + assert lookup_idcode(2147483648 + 56823955, T_PARTS[0 .. 2]) == 1; + assert die_of("XC7A100TFGG676-1", T_OUT[0 .. 32]) == 7; + assert T_OUT[0] == 120; + assert T_OUT[6] == 48; + assert die_of("xc7a", T_OUT[0 .. 32]) == -1; + assert die_of("foo", T_OUT[0 .. 32]) == -1; + t_setup(); + assert geom_slot_count(T_GEOM[0 .. 64], 1) == 8; + assert frame_slots(T_GEOM[0 .. 64], 1, T_SLOTS[0 .. 4096]) == 8; + assert T_SLOTS[0] == 0; + assert T_SLOTS[1] == 1; + assert T_SLOTS[2] == 128; + assert T_SLOTS[3] == 4294967295; + assert T_SLOTS[4] == 4294967295; + assert T_SLOTS[5] == 8388608; +} + diff --git a/specs/widgets/bit_diff_logic.t27 b/specs/widgets/bit_diff_logic.t27 new file mode 100644 index 0000000000..733ce589c7 --- /dev/null +++ b/specs/widgets/bit_diff_logic.t27 @@ -0,0 +1,1761 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/widgets/bit_diff_logic.t27 -- the bit-diff widget's bitstream reader and frame comparison: a Xilinx +; 7-series .bit or .bin read into packets and frame writes, mapped onto a device's frame addresses, +; compared frame by frame. Ported from trinity apps/website/public/widgets/bit-diff/bitparse.js (371 +; lines). Epic gHashTag/t27#10937, issue #10938. UG470 names for registers and commands are kept. +; -- +; JS export -> t27 function(s) note +; SYNC_WORD, FRAME_WORDS, ECC_WORD, ECC_MASK -> consts of this module +; REG_NAMES, CMD_NAMES, BLOCK_NAMES -> name_of(kind, code) fills the name buffer, name_byte(i) reads it +; hex8 -> lb_hex8 (inside the line builder), hex8_into(v) + name_byte +; decodeFar -> decode_far(far, f) encodeFar -> encode_far(block, bottom, row, column, minor) +; findSync -> find_sync(u8, from, limit) +; parseBitstream -> bs_parse(inst, u8) + bs_* getters (inst 0 is stream A, 1 is stream B) +; packetLine -> packet_line(inst, k) + name_byte(i) packetSequence -> bs_seq(inst) + seq_byte(inst, i) +; lineDiff -> line_diff(a_list, b_list) + ld_* getters +; frameSlots -> slots_build(g) + slot_get(i) +; mapFrames -> bs_map(inst) + bs_off / bs_far getters (geometries from geom_begin / geom_add) +; frameDelta -> frame_delta(a, ao, b, bo) + fd_* getters +; compareFrames -> compare_frames(ua, ub) + cmp_* getters (the async onProgress callback is the glue's) +; bytesIdentical -> bytes_identical(a, b) +; stripOf -> strip_of(buckets) + strip_* getters +; summarize -> the bs_* getters (the file name and sha256 come from the glue, as in JS) +; compareBitstreams-> compare_all(ua, ub, buckets): parse, map, compare, strip, packetsSame, identical +; -- +; Calls, in order, for a page: geom_begin(); geom_add(idcode, name, rows) per geometry; compare_all(ua, ub, +; buckets) (or bs_parse + bs_map per stream and the parts by hand). rows of geom_add is i32: per row +; block, bottom, row, ncols, then ncols frame counts. +; Result layout (all offsets are BYTE offsets into the stream passed to bs_parse, or into the name buffer): +; bs_info(inst, f): 0 size 1 kind (1 = .bit) 2 headerEnd 3 sync (-1 none) 4 idcode state (0 null, 1 a number, +; 2 undefined: an IDCODE write that was not one word) 5 idcode 6 packet count 7 FDRI write count 8 words +; 9 end (-1 undefined) 10 desync 11 frameWords 12 frames (-1 undefined: no sync word) 13 warning count +; 14..19 warning codes in order (1 nosync 2 encrypted 3 compressed 4 truncated 5 badheader 6 partialframe) +; 20..31 header fields a,b,c,d as (start, bytes, nul padding) ; 32 header length field e +; 33 mapped 34 geometry index (-1 none) +; bs_pk(inst, k, f): 0 off 1 type 2 op (-1 for a bad word) 3 reg (-1 null) 4 count 5 data 6 has value 7 value 8 pad or raw +; bs_fdri(inst, k, f): 0 off 1 words 2 far null 3 far. bs_off(inst, k), bs_far(inst, k, f): 0 null flag 1 far. +; compare_frames -> cmp_info(f): 0 frames 1 framesA 2 framesB 3 differing 4 onlyOne 5 first 6 byAddress +; 7 detail kind (0 none, 1 differing frame, 2 present in one only) 8 detail index 9 far null 10 far +; 11 words 12 bits 13 ecc 14 listed words; cmp_list(i, f): 0 word 1 a 2 b; cmp_diff(i) the per-frame byte. +; strip_of -> strip_info(f): 0 row count 1 buckets; strip_row(r, f): 0 has meta 1 block 2 bottom 3 row; +; strip_cell(r, c). +; Quirks of the JavaScript kept: out-of-range bytes read as 0 (be32 of a short tail); a header text +; field runs past the end of the file as NULs; an IDCODE write of other than one word leaves idcode +; "undefined" (it prints as 0x00000000); a type 2 packet takes the register of the last type 1 packet, +; even null ("REGnull"); the Map from FAR to slot or frame keeps the LAST entry; a stream with no sync +; word has frames undefined, which JS turns into NaN totals in a comparison. +; Not carried: compare_all on a stream with no sync word (status 9); numbers beyond 2^31 bytes of +; stream; geometry with fractional numbers. Table limits (the JS has none): 65536 packets, 16384 FDRI +; writes, 262144 frames and slots per stream, 8 geometries, 1024 geometry rows, 65536 geometry columns, +; 1 MB of packet text per stream, 1024 strip buckets, line diff inputs of 65536 lines and 4.2 M cells. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module widget_bit_diff_logic; + +pub const SYNC_WORD : u32 = 2862175590; +pub const FRAME_WORDS : u32 = 101; +pub const ECC_WORD : u32 = 50; +pub const ECC_MASK : u32 = 8191; + +pub const REG_FAR : u32 = 1; +pub const REG_FDRI : u32 = 2; +pub const REG_CMD : u32 = 4; +pub const REG_MFWR : u32 = 10; +pub const REG_CBC : u32 = 11; +pub const REG_IDCODE : u32 = 12; + +pub const MP : usize = 65536; +pub const MW : usize = 16384; +pub const MF : usize = 262144; + +pub const I_SIZE : usize = 0; +pub const I_KIND : usize = 1; +pub const I_HEND : usize = 2; +pub const I_SYNC : usize = 3; +pub const I_IDS : usize = 4; +pub const I_ID : usize = 5; +pub const I_NPK : usize = 6; +pub const I_NFD : usize = 7; +pub const I_WORDS : usize = 8; +pub const I_END : usize = 9; +pub const I_DESYNC : usize = 10; +pub const I_FW : usize = 11; +pub const I_FRAMES : usize = 12; +pub const I_NW : usize = 13; +pub const I_W0 : usize = 14; +pub const I_HDR : usize = 20; +pub const I_E : usize = 32; +pub const I_MAPPED : usize = 33; +pub const I_GEOM : usize = 34; + +pub var INS : [128]i64 = [0; 128]; +pub var PKO : [131072]u32 = [0; 131072]; +pub var PKT : [131072]u8 = [0; 131072]; +pub var PKP : [131072]i32 = [0; 131072]; +pub var PKR : [131072]i32 = [0; 131072]; +pub var PKC : [131072]u32 = [0; 131072]; +pub var PKD : [131072]u32 = [0; 131072]; +pub var PKH : [131072]u8 = [0; 131072]; +pub var PKV : [131072]u32 = [0; 131072]; +pub var PKX : [131072]u32 = [0; 131072]; +pub var FDO : [32768]u32 = [0; 32768]; +pub var FDW : [32768]u32 = [0; 32768]; +pub var FDN : [32768]u8 = [0; 32768]; +pub var FDF : [32768]u32 = [0; 32768]; +pub var OFFS : [524288]u32 = [0; 524288]; +pub var FARS : [524288]u32 = [0; 524288]; +pub var FARN : [524288]u8 = [0; 524288]; +pub var DIFF : [262144]u8 = [0; 262144]; +pub var CMP : [32]i64 = [0; 32]; +pub var CLW : [8]u32 = [0; 8]; +pub var CLA : [8]u32 = [0; 8]; +pub var CLB : [8]u32 = [0; 8]; +pub var FDL : [8]i64 = [0; 8]; + +pub var GID : [8]u32 = [0; 8]; +pub var GNO : [8]u32 = [0; 8]; +pub var GNL : [8]u32 = [0; 8]; +pub var GRO : [8]u32 = [0; 8]; +pub var GRN : [8]u32 = [0; 8]; +pub var RWB : [1024]u32 = [0; 1024]; +pub var RWK : [1024]u32 = [0; 1024]; +pub var RWR : [1024]u32 = [0; 1024]; +pub var RWC : [1024]u32 = [0; 1024]; +pub var RWO : [1024]u32 = [0; 1024]; +pub var CNT : [65536]u32 = [0; 65536]; +pub var GPL : [4096]u8 = [0; 4096]; +pub var GV : [8]u64 = [0; 8]; +pub var SLOT : [262144]u32 = [0; 262144]; +pub var SLN : [262144]u8 = [0; 262144]; +pub var HTS : [524288]u32 = [0; 524288]; +pub var HTB : [524288]u32 = [0; 524288]; +pub var HTM : [524288]u32 = [0; 524288]; +pub var MIK : [262144]u32 = [0; 262144]; +pub var MIV : [262144]u32 = [0; 262144]; +pub var STC : [1048576]u8 = [0; 1048576]; +pub var STRI : [8]i64 = [0; 8]; +pub var SQB : [2097152]u8 = [0; 2097152]; +pub var SQN : [2]u64 = [0; 2]; +pub var LB : [512]u8 = [0; 512]; +pub var PREV : [512]u8 = [0; 512]; +pub var LBN : [2]u64 = [0; 2]; +pub var MAGIC : [13]u8 = [0, 9, 15, 240, 15, 240, 15, 240, 15, 240, 0, 0, 1]; +pub var DP : [4300000]u16 = [0; 4300000]; +pub var DAO : [65536]u32 = [0; 65536]; +pub var DAL : [65536]u32 = [0; 65536]; +pub var DBO : [65536]u32 = [0; 65536]; +pub var DBL : [65536]u32 = [0; 65536]; +pub var DRO : [140000]u8 = [0; 140000]; +pub var DRA : [140000]u32 = [0; 140000]; +pub var DRB : [140000]u32 = [0; 140000]; +pub var LDN : [4]u64 = [0; 4]; + +// ---------------------------------------------------------------- bytes +/// A byte, or 0 past the end (what JavaScript's `undefined << n` gives). +pub fn bat(u: []const u8, i: usize) -> u64 { + if (i < u.len) { return u[i] as u64; } + return 0; +} + +pub fn be32(u: []const u8, o: usize) -> u32 { + return (bat(u, o) * 16777216 + bat(u, o + 1) * 65536 + bat(u, o + 2) * 256 + bat(u, o + 3)) as u32; +} + +/// The position of the sync word's first byte at or after `from` within `limit`, or -1. +pub fn find_sync(u: []const u8, from: usize, limit: usize) -> i64 { + var end : i64 = (u.len as i64) - 3; + const lim : i64 = (from as i64) + (limit as i64); + if (lim < end) { end = lim; } + var i : i64 = from as i64; + while (i < end) { + const p : usize = i as usize; + if (u[p] == 170 and u[p + 1] == 153 and u[p + 2] == 85 and u[p + 3] == 102) { return i; } + i = i + 1; + } + return -1; +} + +pub fn bytes_identical(a: []const u8, b: []const u8) -> bool { + if (a.len != b.len) { return false; } + var i : usize = 0; + while (i < a.len) { + if (a[i] != b[i]) { return false; } + i = i + 1; + } + return true; +} + +/// FAR fields, UG470 table 5-24: f 0 block 1 bottom 2 row 3 column 4 minor. +pub fn decode_far(far: u32, f: u32) -> u32 { + if (f == 0) { return (far / 8388608) % 8; } + if (f == 1) { return (far / 4194304) % 2; } + if (f == 2) { return (far / 131072) % 32; } + if (f == 3) { return (far / 128) % 1024; } + return far % 128; +} + +pub fn encode_far(block: u32, bottom: u32, row: u32, column: u32, minor: u32) -> u32 { + return ((block % 8) * 8388608 + (bottom % 2) * 4194304 + (row % 32) * 131072 + (column % 1024) * 128 + (minor % 128)) as u32; +} + +// ---------------------------------------------------------------- names and the line builder +pub fn lb_reset() -> bool { + LBN[0] = 0; + return true; +} + +pub fn lb_byte(b: u8) -> bool { + if (LBN[0] < 512) { LB[LBN[0] as usize] = b; } + LBN[0] = LBN[0] + 1; + return true; +} + +pub fn lb_put(w: []const u8) -> bool { + var k : usize = 0; + while (k < w.len) { + lb_byte(w[k]); + k = k + 1; + } + return true; +} + +pub fn lb_dec(v: u64) -> bool { + var digits : u64 = 1; + var t : u64 = v; + while (t >= 10) { + t = t / 10; + digits = digits * 10; + } + var d : u64 = digits; + var r : u64 = v; + while (d > 0) { + lb_byte((48 + r / d) as u8); + r = r % d; + d = d / 10; + } + return true; +} + +pub fn hex_digit(v: u64) -> u8 { + if (v < 10) { return (48 + v) as u8; } + return (87 + v) as u8; +} + +pub fn lb_hex8(v: u32) -> bool { + lb_put("0x"); + var sh : u64 = 268435456; + var r : u64 = v as u64; + var k : u32 = 0; + while (k < 8) { + lb_byte(hex_digit(r / sh)); + r = r % sh; + sh = sh / 16; + k = k + 1; + } + return true; +} + +/// REG_NAMES[reg] or "REG" ("REGnull" for no register). +pub fn lb_reg(reg: i32) -> bool { + if (reg < 0) { return lb_put("REGnull"); } + if (reg == 0) { return lb_put("CRC"); } + if (reg == 1) { return lb_put("FAR"); } + if (reg == 2) { return lb_put("FDRI"); } + if (reg == 3) { return lb_put("FDRO"); } + if (reg == 4) { return lb_put("CMD"); } + if (reg == 5) { return lb_put("CTL0"); } + if (reg == 6) { return lb_put("MASK"); } + if (reg == 7) { return lb_put("STAT"); } + if (reg == 8) { return lb_put("LOUT"); } + if (reg == 9) { return lb_put("COR0"); } + if (reg == 10) { return lb_put("MFWR"); } + if (reg == 11) { return lb_put("CBC"); } + if (reg == 12) { return lb_put("IDCODE"); } + if (reg == 13) { return lb_put("AXSS"); } + if (reg == 14) { return lb_put("COR1"); } + if (reg == 16) { return lb_put("WBSTAR"); } + if (reg == 17) { return lb_put("TIMER"); } + if (reg == 19) { return lb_put("RBCRC_SW"); } + if (reg == 22) { return lb_put("BOOTSTS"); } + if (reg == 24) { return lb_put("CTL1"); } + if (reg == 31) { return lb_put("BSPI"); } + lb_put("REG"); + return lb_dec(reg as u64); +} + +/// CMD_NAMES[v] ?? v +pub fn lb_cmd(v: u32) -> bool { + if (v == 0) { return lb_put("NULL"); } + if (v == 1) { return lb_put("WCFG"); } + if (v == 2) { return lb_put("MFW"); } + if (v == 3) { return lb_put("LFRM"); } + if (v == 4) { return lb_put("RCFG"); } + if (v == 5) { return lb_put("START"); } + if (v == 6) { return lb_put("RCAP"); } + if (v == 7) { return lb_put("RCRC"); } + if (v == 8) { return lb_put("AGHIGH"); } + if (v == 9) { return lb_put("SWITCH"); } + if (v == 10) { return lb_put("GRESTORE"); } + if (v == 11) { return lb_put("SHUTDOWN"); } + if (v == 12) { return lb_put("GCAPTURE"); } + if (v == 13) { return lb_put("DESYNC"); } + if (v == 15) { return lb_put("IPROG"); } + if (v == 16) { return lb_put("CRCC"); } + if (v == 17) { return lb_put("LTIMER"); } + if (v == 18) { return lb_put("BSPI_READ"); } + if (v == 19) { return lb_put("FALL_EDGE"); } + return lb_dec(v as u64); +} + +/// Fill the name buffer: kind 0 REG_NAMES[code], 1 CMD_NAMES[code], 2 BLOCK_NAMES[code]; the length, -1 when there is no such name. +pub fn name_of(kind: u32, code: u32) -> i64 { + lb_reset(); + if (kind == 0) { + if (code > 31) { return -1; } + lb_reg(code as i32); + if (LB[0] == 82 and LB[1] == 69 and LB[2] == 71 and LBN[0] > 3 and LB[3] >= 48 and LB[3] <= 57) { return -1; } + } + if (kind == 1) { + lb_cmd(code); + if (LB[0] >= 48 and LB[0] <= 57) { return -1; } + } + if (kind == 2) { + if (code == 0) { lb_put("CLB_IO_CLK"); } + if (code == 1) { lb_put("BLOCK_RAM"); } + if (code == 2) { lb_put("CFG_CLB"); } + if (code > 2) { return -1; } + } + return LBN[0] as i64; +} + +pub fn hex8_into(v: u32) -> i64 { + lb_reset(); + lb_hex8(v); + return LBN[0] as i64; +} + +pub fn name_byte(i: u32) -> u8 { + return LB[i as usize]; +} + +// ---------------------------------------------------------------- parseBitstream +pub fn warn(inst: usize, code: i64) -> bool { + const b : usize = inst * 64; + var k : usize = 0; + while (k < (INS[b + I_NW] as usize)) { + if (INS[b + I_W0 + k] == code) { return true; } + k = k + 1; + } + if (INS[b + I_NW] >= 6) { return false; } + INS[b + I_W0 + (INS[b + I_NW] as usize)] = code; + INS[b + I_NW] = INS[b + I_NW] + 1; + return true; +} + +/// The text(start, len) of the header: bytes up to a NUL, a position past the end counts as a NUL character appended. +pub fn header_text(u: []const u8, b: usize, slot: usize, start: usize, len: usize) -> bool { + var i : usize = start; + var inr : i64 = 0; + var pad : i64 = 0; + var stop : bool = false; + while (i < start + len and !stop) { + if (i < u.len) { + if (u[i] == 0) { + stop = true; + } else { + inr = inr + 1; + } + } else { + pad = pad + 1; + } + if (!stop) { i = i + 1; } + } + INS[b + I_HDR + slot * 3] = start as i64; + INS[b + I_HDR + slot * 3 + 1] = inr; + INS[b + I_HDR + slot * 3 + 2] = pad; + return true; +} + +pub fn parse_header(u: []const u8, b: usize) -> bool { + var i : usize = 0; + while (i < 13) { + if (i >= u.len or u[i] != MAGIC[i]) { return false; } + i = i + 1; + } + var k : usize = 0; + while (k < 12) { + INS[b + I_HDR + k] = 0; + k = k + 1; + } + var o : usize = 13; + while (o < u.len and o < 4096) { + const key : u8 = u[o]; + if (key == 101) { + INS[b + I_E] = be32(u, o + 1) as i64; + INS[b + I_HEND] = (o + 5) as i64; + return true; + } + if (key < 97 or key > 100) { return false; } + const len : usize = ((bat(u, o + 1) * 256) + bat(u, o + 2)) as usize; + header_text(u, b, (key - 97) as usize, o + 3, len); + o = o + 3 + len; + } + return false; +} + +pub fn pk_push(inst: usize, off: usize, ty: u8, op: i32, reg: i32, count: u32, data: usize, hasv: u8, value: u32, x: u32) -> bool { + const b : usize = inst * 64; + const k : usize = INS[b + I_NPK] as usize; + if (k >= MP) { return false; } + const s : usize = inst * MP + k; + PKO[s] = off as u32; + PKT[s] = ty; + PKP[s] = op; + PKR[s] = reg; + PKC[s] = count; + PKD[s] = data as u32; + PKH[s] = hasv; + PKV[s] = value; + PKX[s] = x; + INS[b + I_NPK] = INS[b + I_NPK] + 1; + return true; +} + +pub fn fd_push(inst: usize, off: usize, words: u32, fnull: u8, far: u32) -> bool { + const b : usize = inst * 64; + const k : usize = INS[b + I_NFD] as usize; + if (k >= MW) { return false; } + const s : usize = inst * MW + k; + FDO[s] = off as u32; + FDW[s] = words; + FDN[s] = fnull; + FDF[s] = far; + INS[b + I_NFD] = INS[b + I_NFD] + 1; + INS[b + I_FW] = INS[b + I_FW] + (words as i64); + return true; +} + +/// parseBitstream(u8). 0 ok, -1 over a table limit. +pub fn bs_parse(inst: u32, u: []const u8) -> i32 { + const ii : usize = inst as usize; + const b : usize = ii * 64; + var f : usize = 0; + while (f < 64) { + INS[b + f] = 0; + f = f + 1; + } + INS[b + I_SIZE] = u.len as i64; + INS[b + I_SYNC] = -1; + INS[b + I_END] = -1; + INS[b + I_FRAMES] = -1; + INS[b + I_GEOM] = -1; + if (parse_header(u, b)) { + INS[b + I_KIND] = 1; + } else { + INS[b + I_HEND] = 0; + } + const sync : i64 = find_sync(u, INS[b + I_HEND] as usize, 1048576); + INS[b + I_SYNC] = sync; + if (sync < 0) { + warn(ii, 1); + return 0; + } + var o : usize = (sync as usize) + 4; + var far_null : bool = true; + var far : u32 = 0; + var last_reg : i32 = -1; + const n : usize = u.len; + var brk : bool = false; + while (o + 4 <= n and !brk) { + const w : u32 = be32(u, o); + const ty : u32 = w / 536870912; + if (ty == 1) { + const op : u32 = (w / 134217728) % 4; + const reg : u32 = (w / 8192) % 16384; + const count : u32 = w % 2048; + var hasv : u8 = 0; + var value : u32 = 0; + var finish : bool = false; + if (op == 2 and count > 0 and o + 4 + (count as usize) * 4 <= n) { + if (reg == REG_CMD) { + value = be32(u, o + 4); + hasv = 1; + } else { + if (reg == REG_FAR) { + far = be32(u, o + 4); + far_null = false; + value = far; + hasv = 1; + } else { + if (count == 1) { + value = be32(u, o + 4); + hasv = 1; + } + } + } + if (reg == REG_IDCODE) { + if (hasv == 1) { + INS[b + I_IDS] = 1; + INS[b + I_ID] = value as i64; + } else { + INS[b + I_IDS] = 2; + INS[b + I_ID] = 0; + } + } + if (reg == REG_CBC) { warn(ii, 2); } + if (reg == REG_MFWR) { warn(ii, 3); } + if (reg == REG_FDRI) { + var fnl : u8 = 0; + if (far_null) { fnl = 1; } + if (!fd_push(ii, o + 4, count, fnl, far)) { return -1; } + } + if (reg == REG_CMD and value == 13) { + if (!pk_push(ii, o, 1, op as i32, reg as i32, count, o + 4, hasv, value, 0)) { return -1; } + o = o + 4 + (count as usize) * 4; + INS[b + I_DESYNC] = 1; + finish = true; + brk = true; + } + } + if (!finish) { + last_reg = reg as i32; + if (!pk_push(ii, o, 1, op as i32, reg as i32, count, o + 4, hasv, value, 0)) { return -1; } + if (op == 0) { o = o + 4; } else { o = o + 4 + (count as usize) * 4; } + } + } else { + if (ty == 2) { + const op : u32 = (w / 134217728) % 4; + const count : u32 = w % 134217728; + const reg : i32 = last_reg; + if (o + 4 + (count as usize) * 4 > n) { + warn(ii, 4); + if (!pk_push(ii, o, 2, op as i32, reg, count, o + 4, 0, 0, 0)) { return -1; } + brk = true; + } else { + if (op == 2 and reg == 2) { + var fnl : u8 = 0; + if (far_null) { fnl = 1; } + if (!fd_push(ii, o + 4, count, fnl, far)) { return -1; } + } + if (!pk_push(ii, o, 2, op as i32, reg, count, o + 4, 0, 0, 0)) { return -1; } + o = o + 4 + (count as usize) * 4; + } + } else { + if (w == 4294967295 or w == 0) { + if (!pk_push(ii, o, 0, 0, -1, 0, 0, 0, 0, w)) { return -1; } + o = o + 4; + } else { + warn(ii, 5); + if (!pk_push(ii, o, ty as u8, -1, -1, 0, 0, 0, 0, w)) { return -1; } + o = o + 4; + } + } + } + } + INS[b + I_END] = o as i64; + INS[b + I_WORDS] = (((o as i64) - sync) / 4); + const fw : i64 = INS[b + I_FW]; + INS[b + I_FRAMES] = fw / 101; + if (fw % 101 != 0) { warn(ii, 6); } + return 0; +} + +pub fn bs_info(inst: u32, f: u32) -> i64 { + return INS[(inst as usize) * 64 + (f as usize)]; +} + +pub fn bs_pk(inst: u32, k: u32, f: u32) -> i64 { + const s : usize = (inst as usize) * MP + (k as usize); + if (f == 0) { return PKO[s] as i64; } + if (f == 1) { return PKT[s] as i64; } + if (f == 2) { return PKP[s] as i64; } + if (f == 3) { return PKR[s] as i64; } + if (f == 4) { return PKC[s] as i64; } + if (f == 5) { return PKD[s] as i64; } + if (f == 6) { return PKH[s] as i64; } + if (f == 7) { return PKV[s] as i64; } + return PKX[s] as i64; +} + +pub fn bs_fdri(inst: u32, k: u32, f: u32) -> i64 { + const s : usize = (inst as usize) * MW + (k as usize); + if (f == 0) { return FDO[s] as i64; } + if (f == 1) { return FDW[s] as i64; } + if (f == 2) { return FDN[s] as i64; } + return FDF[s] as i64; +} + +// ---------------------------------------------------------------- packetLine and packetSequence +/// packetLine(pk) into the line buffer; the length. +pub fn packet_line(inst: u32, k: u32) -> i64 { + const s : usize = (inst as usize) * MP + (k as usize); + lb_reset(); + const ty : u8 = PKT[s]; + if (ty == 0) { + if (PKX[s] == 0) { lb_put("ZERO"); } else { lb_put("DUMMY"); } + return LBN[0] as i64; + } + const op : i32 = PKP[s]; + if (op == -1) { + lb_put("WORD "); + lb_hex8(PKX[s]); + return LBN[0] as i64; + } + if (op == 0) { + lb_put("NOP"); + return LBN[0] as i64; + } + const reg : i32 = PKR[s]; + if (reg == 4 and PKH[s] == 1) { + lb_put("CMD "); + lb_cmd(PKV[s]); + return LBN[0] as i64; + } + if (op == 1) { lb_put("READ"); } + if (op == 2) { lb_put("WRITE"); } + if (op == 3) { lb_put("RSVD"); } + if (reg == 2) { + lb_put(" FDRI "); + lb_dec(PKC[s] as u64); + if (ty == 2) { lb_put(" (type 2)"); } + return LBN[0] as i64; + } + lb_put(" "); + lb_reg(reg); + if (PKH[s] == 1 and PKC[s] == 1) { + lb_put(" "); + lb_hex8(PKV[s]); + return LBN[0] as i64; + } + lb_put(" x"); + lb_dec(PKC[s] as u64); + return LBN[0] as i64; +} + +pub fn sq_put(inst: usize, b: u8) -> bool { + if (SQN[inst] >= 1048576) { return false; } + SQB[inst * 1048576 + (SQN[inst] as usize)] = b; + SQN[inst] = SQN[inst] + 1; + return true; +} + +pub fn sq_flush(inst: usize, plen: usize, cnt: u64) -> bool { + var k : usize = 0; + while (k < plen) { + if (!sq_put(inst, PREV[k])) { return false; } + k = k + 1; + } + if (cnt > 1) { + sq_put(inst, 32); + sq_put(inst, 42); + sq_put(inst, 32); + var digits : u64 = 1; + var t : u64 = cnt; + while (t >= 10) { + t = t / 10; + digits = digits * 10; + } + var d : u64 = digits; + var r : u64 = cnt; + while (d > 0) { + sq_put(inst, (48 + r / d) as u8); + r = r % d; + d = d / 10; + } + } + return sq_put(inst, 10); +} + +/// packetSequence(parsed): the packet lines with runs folded, each ended by byte 10, in SQB. The byte count, -1 over the limit. +pub fn bs_seq(inst: u32) -> i64 { + const ii : usize = inst as usize; + SQN[ii] = 0; + const npk : usize = INS[ii * 64 + I_NPK] as usize; + var have : bool = false; + var plen : usize = 0; + var cnt : u64 = 0; + var k : usize = 0; + while (k < npk) { + const l : i64 = packet_line(inst, k as u32); + if (l > 512) { return -1; } + const ln : usize = l as usize; + var same : bool = false; + if (have and ln == plen) { + same = true; + var c : usize = 0; + while (c < ln) { + if (LB[c] != PREV[c]) { same = false; } + c = c + 1; + } + } + if (same) { + cnt = cnt + 1; + } else { + if (have) { + if (!sq_flush(ii, plen, cnt)) { return -1; } + } + var c2 : usize = 0; + while (c2 < ln) { + PREV[c2] = LB[c2]; + c2 = c2 + 1; + } + plen = ln; + cnt = 1; + have = true; + } + k = k + 1; + } + if (have) { + if (!sq_flush(ii, plen, cnt)) { return -1; } + } + return SQN[ii] as i64; +} + +pub fn seq_byte(inst: u32, i: u32) -> u8 { + return SQB[(inst as usize) * 1048576 + (i as usize)]; +} + +// ---------------------------------------------------------------- lineDiff +pub fn ld_items(list: []const u8, o: bool) -> i64 { + var a : usize = 0; + var n : usize = 0; + while (a < list.len) { + var e : usize = a; + while (e < list.len and list[e] != 10) { e = e + 1; } + if (n >= 65536) { return -1; } + if (o) { + DAO[n] = a as u32; + DAL[n] = (e - a) as u32; + } else { + DBO[n] = a as u32; + DBL[n] = (e - a) as u32; + } + n = n + 1; + a = e + 1; + } + return n as i64; +} + +pub fn ld_eq(a: []const u8, i: usize, b: []const u8, j: usize) -> bool { + if (DAL[i] != DBL[j]) { return false; } + var k : usize = 0; + while (k < (DAL[i] as usize)) { + if (a[(DAO[i] as usize) + k] != b[(DBO[j] as usize) + k]) { return false; } + k = k + 1; + } + return true; +} + +/// lineDiff(a, b): rows in ld_row. 0 ok, 1 null (too big: n*m > 4e6), -1 over a table limit. +pub fn line_diff(a: []const u8, b: []const u8) -> i32 { + const na : i64 = ld_items(a, true); + const nb : i64 = ld_items(b, false); + if (na < 0 or nb < 0) { return -1; } + const n : usize = na as usize; + const m : usize = nb as usize; + if ((n as u64) * (m as u64) > 4000000) { return 1; } + const w : usize = m + 1; + if ((n + 1) * w > 4300000) { return -1; } + var c : usize = 0; + while (c < w) { + DP[n * w + c] = 0; + c = c + 1; + } + var r : usize = 0; + while (r <= n) { + DP[r * w + m] = 0; + r = r + 1; + } + var i : usize = n; + while (i > 0) { + i = i - 1; + var j : usize = m; + while (j > 0) { + j = j - 1; + if (ld_eq(a, i, b, j)) { + DP[i * w + j] = DP[(i + 1) * w + j + 1] + 1; + } else { + const x : u16 = DP[(i + 1) * w + j]; + const y : u16 = DP[i * w + j + 1]; + if (x > y) { DP[i * w + j] = x; } else { DP[i * w + j] = y; } + } + } + } + var p : usize = 0; + var q : usize = 0; + var rows : usize = 0; + while (p < n or q < m) { + if (rows >= 140000) { return -1; } + if (p < n and q < m and ld_eq(a, p, b, q)) { + DRO[rows] = 61; + DRA[rows] = p as u32; + DRB[rows] = q as u32; + p = p + 1; + q = q + 1; + } else { + if (q < m and (p >= n or DP[p * w + q + 1] >= DP[(p + 1) * w + q])) { + DRO[rows] = 43; + DRA[rows] = 0; + DRB[rows] = q as u32; + q = q + 1; + } else { + DRO[rows] = 45; + DRA[rows] = p as u32; + DRB[rows] = 0; + p = p + 1; + } + } + rows = rows + 1; + } + LDN[0] = rows as u64; + return 0; +} + +pub fn ld_count() -> u64 { + return LDN[0]; +} + +/// A row of the diff: f 0 the op byte ('=', '+', '-'), 1 the index in a, 2 the index in b. +pub fn ld_row(i: u32, f: u32) -> u32 { + if (f == 0) { return DRO[i as usize] as u32; } + if (f == 1) { return DRA[i as usize]; } + return DRB[i as usize]; +} + +// ---------------------------------------------------------------- geometry and mapFrames +pub fn geom_begin() -> i32 { + var f : usize = 0; + while (f < 8) { + GV[f] = 0; + f = f + 1; + } + return 0; +} + +/// One geometry: idcode, name, rows (i32: block, bottom, row, ncols, then the ncols frame counts, per row). +pub fn geom_add(idcode: u32, name: []const u8, rows: []const i32) -> i32 { + const g : usize = GV[0] as usize; + if (g >= 8) { return -1; } + if (GV[3] + (name.len as u64) > 4096) { return -1; } + GID[g] = idcode; + GNO[g] = GV[3] as u32; + GNL[g] = name.len as u32; + var k : usize = 0; + while (k < name.len) { + GPL[(GV[3] as usize) + k] = name[k]; + k = k + 1; + } + GV[3] = GV[3] + (name.len as u64); + GRO[g] = GV[1] as u32; + var p : usize = 0; + var nr : u32 = 0; + while (p + 4 <= rows.len) { + const r : usize = GV[1] as usize; + if (r >= 1024) { return -1; } + const nc : usize = rows[p + 3] as usize; + if (p + 4 + nc > rows.len or GV[2] + (nc as u64) > 65536) { return -1; } + RWB[r] = rows[p] as u32; + RWK[r] = rows[p + 1] as u32; + RWR[r] = rows[p + 2] as u32; + RWC[r] = nc as u32; + RWO[r] = GV[2] as u32; + var c : usize = 0; + while (c < nc) { + CNT[(GV[2] as usize) + c] = rows[p + 4 + c] as u32; + c = c + 1; + } + GV[2] = GV[2] + (nc as u64); + GV[1] = GV[1] + 1; + nr = nr + 1; + p = p + 4 + nc; + } + GRN[g] = nr; + GV[0] = GV[0] + 1; + return 0; +} + +/// frameSlots(geom): the address of every frame slot, pad frames null (SLN = 1). The count, -1 over the limit. +pub fn slots_build(g: u32) -> i64 { + var n : usize = 0; + const gi : usize = g as usize; + var r : usize = 0; + while (r < (GRN[gi] as usize)) { + const ri : usize = (GRO[gi] as usize) + r; + var c : usize = 0; + while (c < (RWC[ri] as usize)) { + const cnt : u32 = CNT[(RWO[ri] as usize) + c]; + var mi : u32 = 0; + while (mi < cnt) { + if (n >= 262144) { return -1; } + SLOT[n] = encode_far(RWB[ri], RWK[ri], RWR[ri], c as u32, mi); + SLN[n] = 0; + n = n + 1; + mi = mi + 1; + } + c = c + 1; + } + if (n + 2 > 262144) { return -1; } + SLN[n] = 1; + SLN[n + 1] = 1; + SLOT[n] = 0; + SLOT[n + 1] = 0; + n = n + 2; + r = r + 1; + } + GV[4] = n as u64; + return n as i64; +} + +pub fn slot_get(i: u32) -> i64 { + if (SLN[i as usize] == 1) { return -1; } + return SLOT[i as usize] as i64; +} + +pub fn hslot(key: u32) -> usize { + return (((key as u64) * 2654435761) % 4294967296 % 524288) as usize; +} + +/// mapFrames(parsed, geometries) for stream inst: offsets, addresses, mapped, geometry index. +pub fn bs_map(inst: u32) -> i32 { + const ii : usize = inst as usize; + const b : usize = ii * 64; + var gi : i64 = -1; + if (INS[b + I_IDS] == 1) { + var g : usize = 0; + while (g < (GV[0] as usize) and gi < 0) { + if ((GID[g] as i64) == INS[b + I_ID]) { gi = g as i64; } + g = g + 1; + } + } + var nsl : i64 = 0; + if (gi >= 0) { + nsl = slots_build(gi as u32); + if (nsl < 0) { return -1; } + var h : usize = 0; + while (h < 524288) { + HTS[h] = 0; + h = h + 1; + } + var s : usize = 0; + while (s < (nsl as usize)) { + if (SLN[s] == 0) { + var p : usize = hslot(SLOT[s]); + var done : bool = false; + while (!done) { + if (HTS[p] == 0) { + HTS[p] = (s + 1) as u32; + done = true; + } else { + if (SLOT[(HTS[p] - 1) as usize] == SLOT[s]) { + HTS[p] = (s + 1) as u32; + done = true; + } else { + p = (p + 1) % 524288; + } + } + } + } + s = s + 1; + } + } + var frames : i64 = INS[b + I_FRAMES]; + if (frames < 0) { frames = 0; } + if (frames > (MF as i64)) { return -1; } + var z : usize = 0; + while (z < (frames as usize)) { + FARN[ii * MF + z] = 1; + FARS[ii * MF + z] = 0; + OFFS[ii * MF + z] = 0; + z = z + 1; + } + var mapped : bool = gi >= 0; + var k : usize = 0; + var wi : usize = 0; + while (wi < (INS[b + I_NFD] as usize)) { + const s2 : usize = ii * MW + wi; + const nf : usize = ((FDW[s2] / 101) as usize); + var at : i64 = -1; + if (gi >= 0 and FDN[s2] == 0) { + var p2 : usize = hslot(FDF[s2]); + var found : bool = false; + while (HTS[p2] != 0 and !found) { + if (SLOT[(HTS[p2] - 1) as usize] == FDF[s2]) { + found = true; + } else { + p2 = (p2 + 1) % 524288; + } + } + if (found) { at = (HTS[p2] - 1) as i64; } + } + if (at < 0 and gi >= 0 and FDN[s2] == 0 and FDF[s2] == 0) { at = 0; } + if (at < 0) { mapped = false; } + var f : usize = 0; + while (f < nf and k < (frames as usize)) { + const q : usize = ii * MF + k; + OFFS[q] = FDO[s2] + (f as u32) * 404; + if (at >= 0 and (at as usize) + f < (nsl as usize)) { + const sx : usize = (at as usize) + f; + if (SLN[sx] == 0) { + FARN[q] = 0; + FARS[q] = SLOT[sx]; + } + } else { + if (f == 0 and FDN[s2] == 0) { + FARN[q] = 0; + FARS[q] = FDF[s2]; + } + } + f = f + 1; + k = k + 1; + } + wi = wi + 1; + } + if (mapped) { INS[b + I_MAPPED] = 1; } else { INS[b + I_MAPPED] = 0; } + INS[b + I_GEOM] = gi; + return 0; +} + +pub fn bs_off(inst: u32, k: u32) -> u32 { + return OFFS[(inst as usize) * MF + (k as usize)]; +} + +pub fn bs_far(inst: u32, k: u32, f: u32) -> u32 { + if (f == 0) { return FARN[(inst as usize) * MF + (k as usize)] as u32; } + return FARS[(inst as usize) * MF + (k as usize)]; +} + +// ---------------------------------------------------------------- frames compared +/// frameDelta(a, ao, b, bo): words, bits, ecc, and the first 8 differing words. +pub fn frame_delta(a: []const u8, ao: usize, b: []const u8, bo: usize) -> bool { + var words : i64 = 0; + var bits : i64 = 0; + var ecc : i64 = 0; + var listed : usize = 0; + var w : usize = 0; + while (w < 101) { + const x : u32 = be32(a, ao + w * 4); + const y : u32 = be32(b, bo + w * 4); + if (x != y) { + words = words + 1; + const v : u64 = xor32(x, y) as u64; + if (w == 50) { + var e : u64 = v % 8192; + while (e > 0) { + ecc = ecc + ((e % 2) as i64); + e = e / 2; + } + } + var t : u64 = v; + while (t > 0) { + bits = bits + ((t % 2) as i64); + t = t / 2; + } + if (listed < 8) { + CLW[listed] = w as u32; + CLA[listed] = x; + CLB[listed] = y; + listed = listed + 1; + } + } + w = w + 1; + } + FDL[0] = words; + FDL[1] = bits; + FDL[2] = ecc; + FDL[3] = listed as i64; + return true; +} + +pub fn xor32(x: u32, y: u32) -> u32 { + var r : u64 = 0; + var m : u64 = 1; + var a : u64 = x as u64; + var b : u64 = y as u64; + var k : u32 = 0; + while (k < 32) { + if ((a % 2) != (b % 2)) { r = r + m; } + a = a / 2; + b = b / 2; + m = m * 2; + k = k + 1; + } + return r as u32; +} + +pub fn same_frame(a: []const u8, ao: usize, b: []const u8, bo: usize) -> bool { + var i : usize = 0; + while (i < 404) { + if (a[ao + i] != b[bo + i]) { return false; } + i = i + 1; + } + return true; +} + +/// compareFrames(ua, pa, ma, ub, pb, mb): needs bs_parse and bs_map for both streams. 0 ok, -1 over a limit. +pub fn compare_frames(ua: []const u8, ub: []const u8) -> i32 { + const fa : usize = INS[I_FRAMES] as usize; + const fb : usize = INS[64 + I_FRAMES] as usize; + const by_address : bool = INS[I_MAPPED] == 1 and INS[64 + I_MAPPED] == 1 and INS[I_GEOM] >= 0 and INS[64 + I_GEOM] >= 0 and same_geom_name(INS[I_GEOM] as usize, INS[64 + I_GEOM] as usize); + var n : usize = fa; + if (fb > n) { n = fb; } + if (n > MF) { return -1; } + if (by_address) { + var h : usize = 0; + while (h < 524288) { + HTB[h] = 0; + h = h + 1; + } + var i : usize = 0; + while (i < fb) { + if (FARN[MF + i] == 0) { + var p : usize = hslot(FARS[MF + i]); + var done : bool = false; + while (!done) { + if (HTB[p] == 0) { + HTB[p] = (i + 1) as u32; + done = true; + } else { + if (FARS[MF + ((HTB[p] - 1) as usize)] == FARS[MF + i]) { + HTB[p] = (i + 1) as u32; + done = true; + } else { + p = (p + 1) % 524288; + } + } + } + } + i = i + 1; + } + } + var differing : i64 = 0; + var only_one : i64 = 0; + var first : i64 = -1; + var i2 : usize = 0; + while (i2 < n) { + var j : i64 = i2 as i64; + if (by_address and i2 < fa and FARN[i2] == 0) { + j = pair_of(FARS[i2]); + } + if (i2 >= fa or j < 0 or j >= (fb as i64)) { + DIFF[i2] = 2; + only_one = only_one + 1; + if (first < 0) { first = i2 as i64; } + } else { + if (!same_frame(ua, OFFS[i2] as usize, ub, OFFS[MF + (j as usize)] as usize)) { + DIFF[i2] = 1; + differing = differing + 1; + if (first < 0) { first = i2 as i64; } + } else { + DIFF[i2] = 0; + } + } + i2 = i2 + 1; + } + CMP[0] = n as i64; + CMP[1] = fa as i64; + CMP[2] = fb as i64; + CMP[3] = differing; + CMP[4] = only_one; + CMP[5] = first; + CMP[6] = 0; + if (by_address) { CMP[6] = 1; } + CMP[7] = 0; + if (first >= 0) { + const fi : usize = first as usize; + CMP[8] = first; + CMP[9] = 1; + CMP[10] = 0; + if (fi < fa and FARN[fi] == 0) { + CMP[9] = 0; + CMP[10] = FARS[fi] as i64; + } + CMP[11] = -1; + CMP[12] = -1; + CMP[13] = -1; + CMP[14] = 0; + if (DIFF[fi] == 1) { + CMP[7] = 1; + var j2 : usize = fi; + if (by_address and FARN[fi] == 0) { + j2 = pair_of(FARS[fi]) as usize; + } + frame_delta(ua, OFFS[fi] as usize, ub, OFFS[MF + j2] as usize); + CMP[11] = FDL[0]; + CMP[12] = FDL[1]; + CMP[13] = FDL[2]; + CMP[14] = FDL[3]; + } else { + CMP[7] = 2; + } + } + return 0; +} + +pub fn same_geom_name(a: usize, b: usize) -> bool { + if (GNL[a] != GNL[b]) { return false; } + var k : usize = 0; + while (k < (GNL[a] as usize)) { + if (GPL[(GNO[a] as usize) + k] != GPL[(GNO[b] as usize) + k]) { return false; } + k = k + 1; + } + return true; +} + +/// pairB.get(far): the last frame of stream B with that address, -1 when none. +pub fn pair_of(far: u32) -> i64 { + var p : usize = hslot(far); + while (HTB[p] != 0) { + if (FARS[MF + ((HTB[p] - 1) as usize)] == far) { return (HTB[p] - 1) as i64; } + p = (p + 1) % 524288; + } + return -1; +} + +pub fn cmp_info(f: u32) -> i64 { + return CMP[f as usize]; +} + +pub fn cmp_list(i: u32, f: u32) -> u32 { + if (f == 0) { return CLW[i as usize]; } + if (f == 1) { return CLA[i as usize]; } + return CLB[i as usize]; +} + +pub fn cmp_diff(i: u32) -> u8 { + return DIFF[i as usize]; +} + +// ---------------------------------------------------------------- stripOf +/// stripOf(cmp, map, geom, buckets) for stream A's map. geom is the geometry index (-1 none). +/// 0 ok, -1 over a limit. +pub fn strip_of(buckets: u32, geom: i64) -> i32 { + if (buckets == 0 or buckets > 1024) { return -1; } + const nb : usize = buckets as usize; + const frames : usize = CMP[0] as usize; + if (geom < 0 or CMP[6] == 0) { + var c : usize = 0; + while (c < nb) { + STC[c] = 0; + c = c + 1; + } + const per : f64 = (frames as f64) / (buckets as f64); + var i : usize = 0; + while (i < frames) { + var cc : usize = ((i as f64) / per) as usize; + if (cc > nb - 1) { cc = nb - 1; } + if (DIFF[i] > STC[cc]) { STC[cc] = DIFF[i]; } + i = i + 1; + } + STRI[0] = 1; + STRI[1] = buckets as i64; + STRI[2] = 0; + return 0; + } + const gi : usize = geom as usize; + const nr : usize = GRN[gi] as usize; + if (nr * nb > 1048576) { return -1; } + var z : usize = 0; + while (z < nr * nb) { + STC[z] = 0; + z = z + 1; + } + // minorIndex: encodeFar(block, bottom, row, c, 0) -> running frame count, last entry wins + var h : usize = 0; + while (h < 524288) { + HTM[h] = 0; + h = h + 1; + } + var nm : usize = 0; + var r : usize = 0; + while (r < nr) { + const ri : usize = (GRO[gi] as usize) + r; + var s : u32 = 0; + var c2 : usize = 0; + while (c2 < (RWC[ri] as usize)) { + const key : u32 = encode_far(RWB[ri], RWK[ri], RWR[ri], c2 as u32, 0); + var p : usize = hslot(key); + var done : bool = false; + while (!done) { + if (HTM[p] == 0) { + if (nm >= 262144) { return -1; } + MIK[nm] = key; + MIV[nm] = s; + nm = nm + 1; + HTM[p] = nm as u32; + done = true; + } else { + if (MIK[(HTM[p] - 1) as usize] == key) { + MIV[(HTM[p] - 1) as usize] = s; + done = true; + } else { + p = (p + 1) % 524288; + } + } + } + s = s + CNT[(RWO[ri] as usize) + c2]; + c2 = c2 + 1; + } + r = r + 1; + } + var i2 : usize = 0; + while (i2 < frames) { + if (i2 < (INS[I_FRAMES] as usize) and FARN[i2] == 0) { + const far : u32 = FARS[i2]; + const rk : u32 = far / 131072; + // the last row whose key matches + var ri2 : i64 = -1; + var q : usize = 0; + while (q < nr) { + const rr : usize = (GRO[gi] as usize) + q; + const rkey : u64 = ((RWB[rr] as u64) * 64 + (RWK[rr] as u64) * 32 + (RWR[rr] as u64)) % 4294967296; + if (rkey == (rk as u64)) { ri2 = q as i64; } + q = q + 1; + } + if (ri2 >= 0) { + const rr2 : usize = (GRO[gi] as usize) + (ri2 as usize); + var total : u64 = 0; + var cc2 : usize = 0; + while (cc2 < (RWC[rr2] as usize)) { + total = total + (CNT[(RWO[rr2] as usize) + cc2] as u64); + cc2 = cc2 + 1; + } + var base : u64 = 0; + var p2 : usize = hslot(far - (far % 128)); + var found : bool = false; + while (HTM[p2] != 0 and !found) { + if (MIK[(HTM[p2] - 1) as usize] == far - (far % 128)) { + found = true; + base = MIV[(HTM[p2] - 1) as usize] as u64; + } else { + p2 = (p2 + 1) % 524288; + } + } + const pos : u64 = base + ((far % 128) as u64); + var skip : bool = false; + var cidx : usize = nb - 1; + if (total == 0) { + if (pos == 0) { skip = true; } + } else { + const cf : f64 = ((pos as f64) / (total as f64)) * (buckets as f64); + const ci : u64 = cf as u64; + if (ci < (nb as u64) - 1) { cidx = ci as usize; } + } + if (!skip) { + const cell : usize = (ri2 as usize) * nb + cidx; + if (DIFF[i2] > STC[cell]) { STC[cell] = DIFF[i2]; } + } + } + } + i2 = i2 + 1; + } + STRI[0] = nr as i64; + STRI[1] = buckets as i64; + STRI[2] = 1; + STRI[3] = geom; + return 0; +} + +pub fn strip_info(f: u32) -> i64 { + return STRI[f as usize]; +} + +/// Row metadata of the strip: f 0 has meta (1 on the geometry path), 1 block, 2 bottom, 3 row. +pub fn strip_row(r: u32, f: u32) -> i64 { + if (STRI[2] == 0) { return 0; } + const ri : usize = (GRO[STRI[3] as usize] as usize) + (r as usize); + if (f == 0) { return 1; } + if (f == 1) { return RWB[ri] as i64; } + if (f == 2) { return RWK[ri] as i64; } + return RWR[ri] as i64; +} + +pub fn strip_cell(r: u32, c: u32) -> u8 { + return STC[(r as usize) * (STRI[1] as usize) + (c as usize)]; +} + +/// compareBitstreams: parse, map and compare two streams (the geometries set), strip, and the identity and packet checks. +/// 0 ok, 9 a stream has no sync word (not carried), -1 over a limit. cmp_info(15) = identical, (16) = packetsSame. +pub fn compare_all(ua: []const u8, ub: []const u8, buckets: u32) -> i32 { + if (bs_parse(0, ua) < 0) { return -1; } + if (bs_parse(1, ub) < 0) { return -1; } + if (INS[I_FRAMES] < 0 or INS[64 + I_FRAMES] < 0) { return 9; } + if (bs_map(0) < 0) { return -1; } + if (bs_map(1) < 0) { return -1; } + if (compare_frames(ua, ub) < 0) { return -1; } + var gsel : i64 = -1; + if (INS[I_GEOM] >= 0) { + var g : usize = 0; + while (g < (GV[0] as usize) and gsel < 0) { + if (same_geom_name(g, INS[I_GEOM] as usize)) { gsel = g as i64; } + g = g + 1; + } + } + if (strip_of(buckets, gsel) < 0) { return -1; } + const la : i64 = bs_seq(0); + const lb : i64 = bs_seq(1); + if (la < 0 or lb < 0) { return -1; } + var same : bool = la == lb; + var k : usize = 0; + while (same and k < (la as usize)) { + if (SQB[k] != SQB[1048576 + k]) { same = false; } + k = k + 1; + } + CMP[16] = 0; + if (same) { CMP[16] = 1; } + CMP[15] = 0; + if (bytes_identical(ua, ub)) { CMP[15] = 1; } + return 0; +} + +// ---------------------------------------------------------------- tests +pub var BUF : [16384]u8 = [0; 16384]; +pub var BN : usize = 0; +pub var BUF2 : [16384]u8 = [0; 16384]; +pub var BN2 : usize = 0; +pub var GR : [8]i32 = [0; 8]; +pub var LA : [256]u8 = [0; 256]; +pub var LBB : [256]u8 = [0; 256]; + +pub fn b_reset() -> usize { + BN = 0; + return 0; +} + +pub fn b_byte(v: u32) -> usize { + BUF[BN] = v as u8; + BN = BN + 1; + return BN; +} + +pub fn w32(v: u32) -> usize { + b_byte(v / 16777216); + b_byte((v / 65536) % 256); + b_byte((v / 256) % 256); + b_byte(v % 256); + return BN; +} + +pub fn b_text(s: []const u8) -> usize { + var k : usize = 0; + while (k < s.len) { + b_byte(s[k] as u32); + k = k + 1; + } + return BN; +} + +/// A .bit header with the four text fields, as xc7frames2bit writes it, then field e. +pub fn b_header(total: u32) -> usize { + b_reset(); + b_byte(0); b_byte(9); b_byte(15); b_byte(240); b_byte(15); b_byte(240); b_byte(15); b_byte(240); b_byte(15); b_byte(240); b_byte(0); b_byte(0); b_byte(1); + b_byte(97); b_byte(0); b_byte(3); b_byte(100); b_byte(49); b_byte(0); + b_byte(98); b_byte(0); b_byte(8); b_text("xc7a35t"); b_byte(0); + b_byte(99); b_byte(0); b_byte(11); b_text("2026/10/02"); b_byte(0); + b_byte(100); b_byte(0); b_byte(9); b_text("19:50:34"); b_byte(0); + b_byte(101); + w32(total); + return BN; +} + +/// The packets of a small configuration: NOP, TIMER, RCRC, IDCODE, FAR, WCFG, an FDRI write of two frames +/// (a type 1 header of count 0 then a type 2 packet), three NOPs, DESYNC. The second frame's word 7 is `tweak`. +pub fn b_body(tweak: u32) -> usize { + w32(4294967295); + w32(4294967295); + w32(2862175590); + w32(536870912); + w32(805445633); + w32(0); + w32(805339137); + w32(7); + w32(805404673); + w32(56823955); + w32(805314561); + w32(0); + w32(805339137); + w32(1); + w32(805322752); + w32(1342177482); + var k : u32 = 0; + while (k < 202) { + if (k == 101 + 7) { w32(tweak); } else { w32(k * 3 + 1); } + k = k + 1; + } + w32(536870912); + w32(536870912); + w32(536870912); + w32(805339137); + w32(13); + w32(536870912); + w32(536870912); + return BN; +} + +/// A geometry with idcode 56823955 (0x03631093): one row, block 0, bottom 0, row 0, one column of 2 frames. +pub fn tiny_geom() -> i32 { + geom_begin(); + GR[0] = 0; + GR[1] = 0; + GR[2] = 0; + GR[3] = 1; + GR[4] = 2; + return geom_add(56823955, "tiny", GR[0 .. 5]); +} + +pub fn same_name(w: []const u8, n: i64) -> bool { + if ((w.len as i64) != n) { return false; } + var k : usize = 0; + while (k < w.len) { + if (LB[k] != w[k]) { return false; } + k = k + 1; + } + return true; +} + +test a_bit_file_is_read_into_header_packets_and_frame_writes { + b_header(850); + b_body(0); + const u : []const u8 = BUF[0 .. BN]; + assert bs_parse(0, u) == 0; + assert bs_info(0, 1) == 1; + assert bs_info(0, 0) == (BN as i64); + assert bs_info(0, 2) == 61; + assert bs_info(0, 3) == 69; + assert bs_info(0, 4) == 1; + assert bs_info(0, 5) == 56823955; + assert bs_info(0, 12) == 2; + assert bs_info(0, 11) == 202; + assert bs_info(0, 7) == 1; + assert bs_info(0, 10) == 1; + assert bs_info(0, 13) == 0; + assert bs_info(0, 32) == 850; + assert bs_info(0, 21) == 2; + assert u[bs_info(0, 20) as usize] == 100; + assert bs_fdri(0, 0, 1) == 202; + assert bs_fdri(0, 0, 2) == 0; + assert bs_fdri(0, 0, 3) == 0; + assert bs_info(0, 9) == bs_info(0, 0) - 8; + assert bs_pk(0, 0, 1) == 1; + assert bs_pk(0, 2, 7) == 7; + assert bs_pk(0, 2, 6) == 1; + assert bs_pk(0, 0, 6) == 0; +} + +test packets_read_as_mnemonic_lines { + b_header(850); + b_body(0); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert same_name("NOP", packet_line(0, 0)); + assert same_name("WRITE TIMER 0x00000000", packet_line(0, 1)); + assert same_name("CMD RCRC", packet_line(0, 2)); + assert same_name("WRITE IDCODE 0x03631093", packet_line(0, 3)); + assert same_name("WRITE FAR 0x00000000", packet_line(0, 4)); + assert same_name("CMD WCFG", packet_line(0, 5)); + assert same_name("WRITE FDRI 0", packet_line(0, 6)); + assert same_name("WRITE FDRI 202 (type 2)", packet_line(0, 7)); + assert same_name("CMD DESYNC", packet_line(0, 11)); +} + +test runs_of_equal_lines_are_folded { + b_header(850); + b_body(0); + assert bs_parse(0, BUF[0 .. BN]) == 0; + const n : i64 = bs_seq(0); + assert n > 60; + var lines : u32 = 0; + var k : u32 = 0; + var star : bool = false; + while (k < (n as u32)) { + if (seq_byte(0, k) == 10) { lines = lines + 1; } + if (seq_byte(0, k) == 42 and seq_byte(0, k + 1) == 32 and seq_byte(0, k + 2) == 51) { star = true; } + k = k + 1; + } + assert star; + assert lines == 10; + assert seq_byte(0, 0) == 78; + assert seq_byte(0, 3) == 10; +} + +test two_streams_are_compared_frame_by_frame_with_the_geometry { + assert tiny_geom() == 0; + b_header(850); + b_body(0); + for_copy_a(); + b_header(850); + b_body(4); + const ub : []const u8 = BUF[0 .. BN]; + const ua : []const u8 = BUF2[0 .. BN2]; + assert compare_all(ua, ub, 8) == 0; + assert bs_info(0, 33) == 1; + assert bs_info(0, 34) == 0; + assert bs_far(0, 0, 0) == 0; + assert bs_far(0, 0, 1) == 0; + assert bs_far(0, 1, 1) == 1; + assert cmp_info(0) == 2; + assert cmp_info(3) == 1; + assert cmp_info(4) == 0; + assert cmp_info(5) == 1; + assert cmp_info(6) == 1; + assert cmp_info(7) == 1; + assert cmp_info(11) == 1; + assert cmp_info(12) == 1; + assert cmp_info(13) == 0; + assert cmp_info(14) == 1; + assert cmp_list(0, 0) == 7; + assert cmp_list(0, 1) == 0; + assert cmp_list(0, 2) == 4; + assert cmp_diff(0) == 0; + assert cmp_diff(1) == 1; + assert cmp_info(15) == 0; + assert cmp_info(16) == 1; + assert strip_info(0) == 1; + assert strip_info(2) == 1; + assert strip_row(0, 0) == 1; + assert strip_cell(0, 0) == 0; + assert strip_cell(0, 4) == 1; + assert compare_all(ua, ua, 8) == 0; + assert cmp_info(15) == 1; + assert cmp_info(3) == 0; +} + +pub fn for_copy_a() -> usize { + var k : usize = 0; + while (k < BN) { + BUF2[k] = BUF[k]; + k = k + 1; + } + BN2 = BN; + return BN2; +} + +test without_a_geometry_frames_are_paired_by_position { + geom_begin(); + b_header(850); + b_body(0); + for_copy_a(); + b_header(850); + b_body(4); + assert compare_all(BUF2[0 .. BN2], BUF[0 .. BN], 4) == 0; + assert bs_info(0, 33) == 0; + assert bs_info(0, 34) == -1; + assert bs_far(0, 0, 0) == 0; + assert bs_far(0, 0, 1) == 0; + assert bs_far(0, 1, 0) == 1; + assert cmp_info(6) == 0; + assert cmp_info(3) == 1; + assert strip_info(2) == 0; + assert strip_cell(0, 0) == 0; + assert strip_cell(0, 2) == 1; + assert strip_row(0, 0) == 0; +} + +test odd_streams_raise_the_warnings_the_javascript_raises { + b_reset(); + w32(1); + w32(2); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert bs_info(0, 3) == -1; + assert bs_info(0, 13) == 1; + assert bs_info(0, 14) == 1; + assert bs_info(0, 12) == -1; + b_reset(); + w32(2862175590); + w32(805322752); + w32(1342177482 + 1000); + w32(5); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert bs_info(0, 13) == 1; + assert bs_info(0, 14) == 4; + assert bs_info(0, 9) == 8; + assert bs_info(0, 12) == 0; + b_reset(); + w32(2862175590); + w32(805396481); + w32(1); + w32(805388289); + w32(2); + w32(805322757); + w32(1); + w32(2); + w32(3); + w32(4); + w32(5); + w32(100); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert bs_info(0, 13) == 4; + assert bs_info(0, 14) == 2; + assert bs_info(0, 15) == 3; + assert bs_info(0, 16) == 5; + assert bs_info(0, 17) == 6; + assert bs_info(0, 4) == 0; + assert bs_info(0, 11) == 5; + assert bs_info(0, 12) == 0; + b_reset(); + w32(2862175590); + w32(805371905); + w32(1); + w32(805371905); + w32(2); + w32(805371905); + w32(3); + w32(805371905); + w32(0); + w32(805371905); + w32(0); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert bs_info(0, 13) == 0; + assert bs_pk(0, 0, 7) == 1; + b_reset(); + w32(2862175590); + w32(805404674); + w32(5); + w32(6); + assert bs_parse(0, BUF[0 .. BN]) == 0; + assert bs_info(0, 4) == 2; + assert same_name("WRITE IDCODE x2", packet_line(0, 0)); +} + +test names_far_fields_and_hex_follow_ug470 { + assert name_of(0, 2) == 4; + assert LB[0] == 70; + assert name_of(0, 31) == 4; + assert name_of(0, 15) == -1; + assert name_of(1, 13) == 6; + assert name_of(1, 14) == -1; + assert name_of(2, 1) == 9; + assert name_of(2, 3) == -1; + assert encode_far(1, 1, 3, 17, 5) == 12978309; + assert decode_far(12978309, 0) == 1; + assert decode_far(12978309, 1) == 1; + assert decode_far(12978309, 2) == 3; + assert decode_far(12978309, 3) == 17; + assert decode_far(12978309, 4) == 5; + assert hex8_into(255) == 10; + assert LB[0] == 48; + assert LB[1] == 120; + assert LB[9] == 102; + assert find_sync(BUF[0 .. 0], 0, 100) == -1; +} + +pub fn ls_two() -> usize { + LA[0] = 97; LA[1] = 10; LA[2] = 98; LA[3] = 10; LA[4] = 99; LA[5] = 10; LA[6] = 100; LA[7] = 10; + LBB[0] = 97; LBB[1] = 10; LBB[2] = 120; LBB[3] = 10; LBB[4] = 99; LBB[5] = 10; LBB[6] = 100; LBB[7] = 10; LBB[8] = 101; LBB[9] = 10; + return 8; +} + +test lines_are_diffed_by_longest_common_subsequence { + ls_two(); + assert line_diff(LA[0 .. 8], LBB[0 .. 10]) == 0; + assert ld_count() == 6; + assert ld_row(0, 0) == 61; + assert ld_row(1, 0) == 43; + assert ld_row(1, 2) == 1; + assert ld_row(2, 0) == 45; + assert ld_row(2, 1) == 1; + assert ld_row(3, 0) == 61; + assert ld_row(4, 0) == 61; + assert ld_row(5, 0) == 43; + assert ld_row(5, 2) == 4; + assert line_diff(LA[0 .. 0], LBB[0 .. 0]) == 0; + assert ld_count() == 0; +} diff --git a/specs/widgets/build_receipt.t27 b/specs/widgets/build_receipt.t27 new file mode 100644 index 0000000000..6da77f9198 --- /dev/null +++ b/specs/widgets/build_receipt.t27 @@ -0,0 +1,1388 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/widgets/build-receipt.t27 -- one open-flow build, t27 spec to XC7A200T bitstream, as a receipt +; Source of truth for public/widgets/build-receipt/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs +; compiles this file, checks its constants, evaluates the tests below and writes the page; the page +; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every +; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant or a field +; of receipt.json written by the same script run. +; ASCII only (L3), English only (LANG-EN). + +; WHAT IT SHOWS: a shop-style receipt for one real build on one machine. Each stage of the open flow +; is a line item with its wall time, its tool and version, the size of the file it wrote and that +; file's sha256: the t27 compiler (WebAssembly, in node) turns specs/ternary/stream_ternary_mac.t27 +; into Verilog, yosys synthesises it, nextpnr-xilinx places and routes it on the xc7a200tfbg676-1 +; chipdb, prjxray's fasm2frames and xc7frames2bit write the bitstream, and the last line hashes it. +; Under the items: the TOTAL, a line that no vendor tool and no licence was used, the exact commands, +; and how to reproduce it. A barcode is drawn from the bitstream's sha256. + +; DATA PROVENANCE: scripts/widget-data/build-receipt.mjs ran every stage in /tmp/widget-receipt and +; wrote receipt.json and the generated block below; it then re-checked that the stage times sum to +; the total within rounding and that the stage count matches the data. The datapath is generated +; from the t27 spec; the board wrapper (clock from STARTUPE2's CFGMCLK, two LFSRs as stimulus, three +; LEDs) is hand-written Verilog, scripts/widget-data/build-receipt-top.v, and the receipt says so. +; Outside the total, bitwalk (gHashTag/t27 specs/xilinx7, generated by the same compiler's rust +; target) writes a second .bit from the same frames to check xc7frames2bit's payload. + +; WHAT IT DOES NOT CLAIM: that the bitstream was loaded into a board (it was not: no JTAG was run), +; that the design does anything useful on one, or that the times hold on another machine. The run +; shared the machine with other work; its load average at start is printed on the receipt. No Vivado +; was run, so there is no Vivado time to compare against. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module widget_build_receipt; + +use widgets::html_out::ok; +use widgets::html_out::OVER; +use widgets::html_out::put; +use widgets::html_out::raw; +use widgets::html_out::put_escaped; +use widgets::html_out::text; +use widgets::html_out::num; +use widgets::html_out::attr; +use widgets::html_out::fill; +use widgets::html_out::fill_named; +use widgets::html_out::dec_to; +use widgets::html_out::name_is; +use widgets::html_out::is_word_byte; +use widgets::html_out::H_LT; +use widgets::html_out::H_GT; +use widgets::html_out::H_AMP; +use widgets::html_out::H_QUOTE; +use widgets::html_out::H_APOS; +use widgets::html_out::H_SPACE; +use widgets::html_out::H_SLASH; +use widgets::html_out::H_EQ; +use widgets::json_scan::NO_SPAN; +use widgets::json_scan::SPAN; +use widgets::json_scan::J_NONE; +use widgets::json_scan::J_OBJECT; +use widgets::json_scan::J_ARRAY; +use widgets::json_scan::J_STRING; +use widgets::json_scan::J_NUMBER; +use widgets::json_scan::J_TRUE; +use widgets::json_scan::J_FALSE; +use widgets::json_scan::J_NULL; +use widgets::json_scan::jspan; +use widgets::json_scan::span_lo; +use widgets::json_scan::span_hi; +use widgets::json_scan::skip_ws; +use widgets::json_scan::string_end; +use widgets::json_scan::value_end; +use widgets::json_scan::root; +use widgets::json_scan::kind; +use widgets::json_scan::key_is; +use widgets::json_scan::obj_get; +use widgets::json_scan::arr_len; +use widgets::json_scan::arr_at; +use widgets::json_scan::hex4; +use widgets::json_scan::escape_at; +use widgets::json_scan::cp_utf8_len; +use widgets::json_scan::cp_utf8_byte; +use widgets::json_scan::str_len; +use widgets::json_scan::str_byte; +use widgets::js_num::JN_OVER; +use widgets::js_num::JN_CAP; +use widgets::js_num::JN_TWO52; +use widgets::js_num::JN_TWO53; +use widgets::js_num::jn_put; +use widgets::js_num::jn_raw; +use widgets::js_num::jn_digits; +use widgets::js_num::jn_mul; +use widgets::js_num::jn_mul2; +use widgets::js_num::jn_mul5; +use widgets::js_num::jn_shift; +use widgets::js_num::jn_cmp; +use widgets::js_num::jn_expo; +use widgets::js_num::jn_mant; +use widgets::js_num::jn_make; +use widgets::js_num::jn_exact; +use widgets::js_num::jn_fscale; +use widgets::js_num::js_is_nan; +use widgets::js_num::js_nan; +use widgets::js_num::js_inf; +use widgets::js_num::js_floor; +use widgets::js_num::js_ceil; +use widgets::js_num::js_max; +use widgets::js_num::js_min; +use widgets::js_num::js_round; +use widgets::js_num::jn_shortest; +use widgets::js_num::jn_copy; +use widgets::js_num::jn_put_run; +use widgets::js_num::jn_layout; +use widgets::js_num::js_str_num; +use widgets::js_num::js_to_fixed; +use widgets::js_num::js_locale_num; +use widgets::js_num::jn_num_ok; +use widgets::js_num::jn_cmp_text; +use widgets::js_num::js_parse_num; +use widgets::js_num::jn_dec_value; +use widgets::js_num::jn_abs; +use widgets::js_num::js_str_u64; +use widgets::js_val::jv_str_to; +use widgets::js_val::jv_text_str; +use widgets::js_val::jv_numval; +use widgets::js_val::jv_str; +use widgets::js_val::jv_text; +use widgets::js_val::jv_is; +use widgets::js_val::jv_truthy; +use widgets::js_val::jv_is_ws; +use widgets::js_val::jv_number_of_text; +use widgets::js_val::jv_num; +use widgets::js_val::jv_vput_str; +use widgets::js_val::jv_vput_f64; +use widgets::js_val::jv_vput_val; +use widgets::js_val::jv_vput_defined; +use widgets::js_val::jv_vput_fixed; +use widgets::js_val::jv_kid; +use widgets::js_val::jv_find; +use widgets::js_val::jv_fill; +use widgets::js_val::jv_string_ok; +use widgets::js_val::jv_number_ok; +use widgets::js_val::jv_lit_ok; +use widgets::js_val::jv_valid; +use widgets::js_val::jv_ws_at; +use widgets::js_val::jv_ws_before; +use widgets::js_val::jv_digit_value; + +pub const KIND : str = "widget-tool"; +pub const ID : str = "build-receipt"; +pub const TITLE : str = "Build receipt: t27 spec to XC7A200T bitstream, no Vivado"; +pub const DESCRIPTION : str = "One real open-flow build itemised like a shop receipt: each stage from a t27 spec to an Artix-7 200T bitstream with its time, tool version, output size and sha256."; +pub const IMAGE_ALT : str = "A white thermal-paper receipt on black. Header: Trinity S3AI open flow, build receipt. Six line items with times: t27 to Verilog 0.288 s, synthesis 29.608 s, place and route 57.238 s, FASM to frames 15.979 s, frames to .bit 0.635 s, sha256 0.016 s. TOTAL 103.764 s. Vivado licence 0. Below: a barcode drawn from the bitstream sha256 and the first hex digits of that sha."; +pub const HOOK : str = "From a ternary spec to an Artix-7 200T bitstream with no Vivado: every stage timed, versioned and hashed."; +pub const CATEGORY : str = "fpga"; +pub const DATA_SOURCES : [7]str = ["scripts/widget-data/build-receipt.mjs run, 2026-10-04 on an Apple M1 Pro: every stage time (process.hrtime), cpu and peak memory (/usr/bin/time -l), output size and sha256 (node crypto), written to receipt.json", "public/t27/t27_compiler.wasm in node, targets.verilog of specs/ternary/stream_ternary_mac.t27", "Yosys 0.67+post b8e7da6f40: yosys -V; synth_xilinx -flatten -abc9 -arch xc7", "nextpnr-xilinx 0.9.2-107-g7037c948 with ~/openxc7-src/chipdb/xc7a200tfbg676-1.bin: fmax and utilisation from its log, --freq 80 --seed 1", "prjxray c9f02d85 fasm2frames.py and xc7frames2bit with prjxray-db ab1fc60 artix7 xc7a200tfbg676-1 part.yaml", "bitwalk cross-check: gHashTag/t27 29b78e7fe specs/xilinx7, rust target of the same compiler, rustc 1.98.1", "LED pins R23, T23, J26 LVCMOS33: gHashTag/t27 fpga/vsa/gf16_heartbeat_top.xdc (QMTech Wukong V1)"]; +pub const READS_LOCAL_FILES : bool = false; +pub const SENDS_NOTHING : bool = true; + +; --- Numbers the tool relies on ------------------------------------------------------------ +pub const K_DATA_FILE : str = "./receipt.json"; +; The open flow has six stages: gen, synth, pnr, frames, bit, sha256. +pub const K_STAGE_PLAN : u8 = 6; +; Each stage time is rounded to the millisecond on its own and the total once, from the exact sum. +; So the items may miss the total by at most half a millisecond each: K_STAGE_PLAN / 2, rounded up. +pub const K_ROUND_TOL_MS : u8 = 3; + +; --- The run: written by the script, re-checked by it and by the tests below --------------- +; --- BEGIN generated by scripts/widget-data/build-receipt.mjs run: do not edit by hand --- +; Run of 2026-10-04T17:41:38Z on Apple M1 Pro, 8 cores, load 151.2 at start. +pub const K_STAGE_COUNT : u8 = 6; +pub const K_STAGES_OK : u8 = 6; +pub const K_STAGE_MS : [6]u32 = [288, 29608, 57238, 15979, 635, 16]; +pub const K_TOTAL_MS : u32 = 103764; +pub const K_BIT_BYTES : u32 = 9730757; +pub const K_VENDOR_TOOLS_RUN : u8 = 0; +; --- END generated --- + +pub const SAY_STAGE_NAMES : [6]str = ["t27 to Verilog", "synthesis", "place and route", "FASM to frames", "frames to .bit", "sha256 of the .bit"]; + +; --- Words on the receipt ----------------------------------------------------------------- +; Templates fill {name} slots from receipt.json; tool.js adds no words of its own. +pub const SAY_SHOP : str = "TRINITY S3AI OPEN FLOW"; +pub const SAY_RECEIPT : str = "build receipt no. {no}"; +pub const SAY_WHEN : str = "{when} UTC"; +pub const SAY_MACHINE : str = "{cpu}, {cores} cores, macOS {macos}"; +pub const SAY_LOAD : str = "load average {load} at start: the machine was shared"; +pub const SAY_LABEL_SPEC : str = "spec"; +pub const SAY_LABEL_PART : str = "part"; +pub const SAY_LABEL_BOARD : str = "board"; +pub const SAY_WRAPPER : str = "datapath {name} is generated from the spec; the board wrapper is {lines} lines of hand-written Verilog"; +pub const SAY_COL_ITEM : str = "ITEM"; +pub const SAY_COL_TIME : str = "TIME"; +pub const SAY_OUT : str = "wrote {file}, {size}"; +pub const SAY_SHA : str = "sha256 {sha}"; +pub const SAY_CPU : str = "cpu {cpu} s, peak memory {rss} MB"; +pub const SAY_FAILED : str = "FAILED, exit {exit}"; +pub const SAY_NOT_RUN : str = "not run"; +pub const SAY_ITEMS : str = "ITEMS {ok} of {count}"; +pub const SAY_TOTAL : str = "TOTAL"; +pub const SAY_NO_TOTAL : str = "NO TOTAL: a stage failed after {s} in all"; +pub const SAY_VIVADO : str = "Vivado licence"; +pub const SAY_VENDOR : str = "vendor tools run"; +pub const SAY_END_TO_END : str = "end to end, with the script's own work: {s}"; +pub const SAY_FMAX : str = "fmax {clock} {mhz} MHz, target {target} MHz"; +pub const SAY_PASS : str = "PASS"; +pub const SAY_FAIL : str = "FAIL"; +pub const SAY_UTIL_TITLE : str = "used on the 200T"; +pub const SAY_FILE_SHA : str = "sha256 of the .bit file"; +pub const SAY_FILE_SHA_NOTE : str = "changes every run: the file header holds a path, a date and a time"; +pub const SAY_PAYLOAD_SHA : str = "sha256 from the sync word on, {size}"; +pub const SAY_SAME_PAYLOAD : str = "same payload as the {n} earlier run(s) of the same inputs"; +pub const SAY_OTHER_PAYLOAD : str = "payload differs from an earlier run of the same inputs"; +pub const SAY_EARLIER : str = "earlier run {when}: total {s}, load {load}"; +pub const SAY_CROSS_OK : str = "cross-check: bitwalk, t27's own writer, wrote the same payload from the same frames in {ms} ms (not in the total)"; +pub const SAY_CROSS_BAD : str = "cross-check: bitwalk wrote a different payload from the same frames"; +pub const SAY_CROSS_SKIP : str = "cross-check: bitwalk was not run"; +pub const SAY_NOT_PROGRAMMED : str = "Not loaded into any board: no JTAG was run."; +pub const SAY_BARCODE : str = "Barcode drawn from the sha256 of the .bit file"; +pub const SAY_FOOTER : str = "phi^2 + 1/phi^2 = 3 | keep the sha, not the receipt"; +pub const SAY_SHOW_CMDS : str = "show commands"; +pub const SAY_HIDE_CMDS : str = "hide commands"; +pub const SAY_VERIFY_TITLE : str = "Verify it yourself"; +pub const SAY_VERIFY_STEPS : [5]str = ["Install the openXC7 tools: yosys, nextpnr-xilinx with the xc7a200tfbg676-1 chipdb, and prjxray with its database.", "Clone gHashTag/trinity, then run: cd apps/website && node scripts/widget-data/build-receipt.mjs run", "Compare the payload sha256 (from the sync word on), not the file sha256: the file header holds a path and a time.", "Run node scripts/widget-data/build-receipt.mjs check: it re-adds the stage times and recounts the stages.", "Expect other times: these were taken once, on one shared laptop."]; +pub const SAY_SPEC_DIFF : str = "the spec and receipt.json disagree on {what}"; +pub const SAY_LOADING : str = "printing the receipt"; +pub const SAY_ERROR : str = "could not read the receipt data"; +pub const SAY_UNITS : [4]str = ["B", "kB", "MB", "GB"]; +pub const SAY_SECONDS : str = "{s} s"; + +; --- Tests: the generator evaluates these before it writes the page ---------------------- +test the_items_add_up_to_the_total { + assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] <= K_TOTAL_MS + K_ROUND_TOL_MS; + assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] + K_ROUND_TOL_MS >= K_TOTAL_MS; + assert K_ROUND_TOL_MS == (K_STAGE_PLAN + 1) / 2; +} + +test the_stage_count_matches_the_data { + assert K_STAGE_COUNT == K_STAGE_PLAN; + assert K_STAGES_OK <= K_STAGE_COUNT; + assert SAY_STAGE_NAMES[5] == "sha256 of the .bit"; +} + +test no_vendor_tool_was_run { + assert K_VENDOR_TOOLS_RUN == 0; + assert K_BIT_BYTES > 0; +} + +test the_page_sends_nothing { + assert READS_LOCAL_FILES == false; + assert SENDS_NOTHING == true; + assert CATEGORY == "fpga"; + assert K_DATA_FILE == "./receipt.json"; +} + + +; ================================================================================================= +; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27) +; Everything below replaces public/widgets/build-receipt/tool.js (221 lines of hand-written JavaScript). +; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's: +; w_init(hash) the page opens: "printing the receipt" +; w_data(i, bytes) file i of K_DATA_FILE has been fetched; w_data_failed(i) when it could not be +; w_click(i) the "show commands" button (the only one) was pressed +; w_render(dst) the page's HTML, length (0 on overflow) +; The receipt is drawn from the bytes of receipt.json (json_scan.t27) and the values are written the way +; tool.js's String() / toFixed(3) / Math.round / template literals write them (js_val.t27, js_num.t27). +; Where tool.js throws while it draws (receipt.stages no array, a null stage, a machine that is a string +; or a number, a payload digest that is no string, a utilisation row that is no array, earlier runs with a +; null entry, commands that are no array) its catch shows SAY_ERROR; so does this page (data_ok). +; The spec-versus-data check (specDiff) reads the K_ constants of this very file. +; NOT carried over: String.prototype.toLowerCase / toUpperCase beyond ASCII and Latin-1 (the tool-name +; test and the receipt number); Object.entries order for integer-like keys of `utilisation`, and duplicate keys; a +; non-empty string as `utilisation` or `earlier_runs`, and a string as a row of `utilisation`; +; the data file is cut at DATA_CAP bytes (the page then says it could not be read); a digest longer than +; 293 hex digits is cut in the barcode and one longer than 511 bytes everywhere. +; MEASURED 2026-10-19: t27c 0.5.2 gen + a 5-line Zig export wrapper + zig build-exe wasm32-freestanding +; ReleaseSmall give a 32541-byte wasm. The original page (tool.js) and this one ran in two happy-dom +; windows against the real 6673-byte receipt.json and 188 variants of it, 951 comparisons of tag, +; attributes (the barcode svg included) and text, the button pressed up to 12 times in a row (569 presses) +; and the page before the data arrives and after: no fmax / a failing one / an odd one, utilisation empty / +; absent / an array / short rows / odd keys, stages not run / failed (with and without exit and error) / +; with odd status words, outputs missing / partial / byte sizes at every unit boundary (0, 999, 1000, +; 999999, 1e9, 1.5e12, negative, a string, 7e21), cpu and rss forms, ms of every form (a string makes the +; sum a string, as in JavaScript), an extra / fewer / duplicate / one stage, every kind of spec-versus-data +; disagreement (the warning line), a missing / null bit, digests of every shape (upper case, short, empty, +; non-hex, 200 digits, a number, Latin-1 letters), cross-check ok / bad / skipped / missing, earlier runs +; none / many / odd stamps, an incomplete receipt, vendor tools of every form, machine and design missing / +; partial / numbers, commands empty / markup / nested, version and tool names with and without a match, +; markup and unicode in every string, 15 data files the page cannot draw (a root that is an array or null, +; stages missing / an object / with a null, a number and a string in it, a machine that is a string or a +; number, a payload that is a number or an array, a utilisation row that is a number or null, an earlier +; run that is null, commands that are a string or an object): all end on the error line in both; 70 files +; with random numbers and strings in every field, 8 long sessions, a 404 and a body that is not JSON: +; 0 differences. A negative control (one node of the native DOM changed) is caught. +; Not compared: the stylesheet (happy-dom); the cases under "NOT carried over". + +pub const DATA_CAP : u32 = 32768; +pub var DATA : [32768]u8 = [0; 32768]; +pub var DATA_LEN : u32 = 0; +pub var DATA_STATE : u8 = 0; +pub var CMDS_ON : bool = false; + +pub fn w_init(h: []const u8) -> u32 { + DATA_STATE = 0; + DATA_LEN = 0; + CMDS_ON = false; + return 0; +} + +fn nullish(d: []const u8, sp: i64) -> bool { + const k : u8 = kind(d, sp); + return k == J_NONE or k == J_NULL; +} + +/// receipt.stages is an array, every stage is non-null, and the rest of render() would not throw. +fn data_ok(d: []const u8, r: i64) -> bool { + if (kind(d, r) != J_OBJECT) { return false; } + const st : i64 = obj_get(d, r, "stages"); + if (kind(d, st) != J_ARRAY) { return false; } + const cnt : u32 = arr_len(d, st); + var i : u32 = 0; + while (i < cnt) { + if (nullish(d, arr_at(d, st, i))) { return false; } + i = i + 1; + } + const m : i64 = obj_get(d, r, "machine"); + if (jv_truthy(d, m) and kind(d, m) != J_OBJECT and kind(d, m) != J_ARRAY) { return false; } + const bit : i64 = obj_get(d, r, "bit"); + var pay : i64 = obj_get(d, bit, "payload_sha256"); + if (nullish(d, pay)) { pay = obj_get(d, bit, "sha256"); } + if (!nullish(d, pay) and kind(d, pay) != J_STRING) { return false; } + const ut : i64 = obj_get(d, r, "utilisation"); + if (jv_truthy(d, ut)) { + const k : u8 = kind(d, ut); + if (k == J_STRING) { return false; } + if (k == J_OBJECT or k == J_ARRAY) { + if (!entries_ok(d, ut)) { return false; } + } + } + if (jv_truthy(d, bit)) { + const er : i64 = obj_get(d, r, "earlier_runs"); + if (jv_truthy(d, er) and kind(d, er) == J_STRING) { return false; } + if (kind(d, er) == J_OBJECT and jv_truthy(d, obj_get(d, er, "length"))) { return false; } + if (kind(d, er) == J_ARRAY) { + const ec : u32 = arr_len(d, er); + var j : u32 = 0; + while (j < ec) { + if (nullish(d, arr_at(d, er, j))) { return false; } + j = j + 1; + } + } + } + const cm : i64 = obj_get(d, r, "commands"); + if (jv_truthy(d, cm) and kind(d, cm) != J_ARRAY) { return false; } + return true; +} + +/// Every entry of the object / array `ut` is an array (the `[used, total]` destructuring does not throw). +fn entries_ok(d: []const u8, ut: i64) -> bool { + if (kind(d, ut) == J_ARRAY) { + const c : u32 = arr_len(d, ut); + var i : u32 = 0; + while (i < c) { + const v : i64 = arr_at(d, ut, i); + if (kind(d, v) != J_ARRAY) { return false; } + i = i + 1; + } + return true; + } + // an object: walk its members in file order + const hi : usize = span_hi(ut); + var p : usize = skip_ws(d, span_lo(ut) + 1); + while (p < hi and d[p] == 34) { + const ke : usize = string_end(d, p); + const q : usize = skip_ws(d, ke); + const v : usize = skip_ws(d, q + 1); + const ve : usize = value_end(d, v); + if (kind(d, jspan(v, ve)) != J_ARRAY) { return false; } + p = skip_ws(d, ve); + if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); } + } + return true; +} + +pub fn w_data(i: u32, s: []const u8) -> u32 { + if (s.len > (DATA_CAP as usize)) { + DATA_STATE = 2; + return 0; + } + var k : usize = 0; + while (k < s.len) { + DATA[k] = s[k]; + k = k + 1; + } + DATA_LEN = s.len as u32; + DATA_STATE = 1; + const d : []const u8 = DATA[0..(DATA_LEN as usize)]; + if (!jv_valid(d)) { + DATA_STATE = 2; + return 0; + } + if (!data_ok(d, root(d))) { DATA_STATE = 2; } + return 0; +} + +pub fn w_data_failed(i: u32) -> u32 { + DATA_STATE = 2; + return 0; +} + +pub fn w_click(i: u32) -> u32 { + if (i == 0 and DATA_STATE == 1) { CMDS_ON = !CMDS_ON; } + return 0; +} + +// --- small writers ------------------------------------------------------------------------------ + +///
+fn div_open(dst: []u8, n0: u32, prefix: []const u8, cls: []const u8) -> u32 { + var n : u32 = raw(dst, n0, "
0) { + n = put(dst, n, 32); + n = raw(dst, n, cls); + } + return raw(dst, n, "\">"); +} + +fn rule_html(dst: []u8, n0: u32) -> u32 { + return raw(dst, n0, "
"); +} + +/// line(left, right, cls) up to the left text. +fn line_open(dst: []u8, n0: u32, cls: []const u8) -> u32 { + var n : u32 = div_open(dst, n0, "br-line", cls); + return raw(dst, n, ""); +} + +fn line_mid(dst: []u8, n0: u32) -> u32 { + return raw(dst, n0, ""); +} + +fn line_close(dst: []u8, n0: u32) -> u32 { + return raw(dst, n0, "
"); +} + +fn put_all(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 { + var n : u32 = n0; + var i : u32 = 0; + while (i < l) { + n = put_escaped(dst, n, s[i as usize]); + i = i + 1; + } + return n; +} + +/// The text of a textContent / child assignment: null and undefined add nothing. +fn tc_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 { + if (nullish(d, sp)) { return n0; } + return jv_text(dst, n0, d, sp); +} + +/// (ms / 1000).toFixed(3) in SAY_SECONDS. +fn secs_text(dst: []u8, n0: u32, ms: f64) -> u32 { + var vb : [128]u8 = [0; 128]; + const vn : u32 = jv_vput_fixed(vb[0..], 0, "s", ms / 1000.0, 3); + return jv_fill(dst, n0, SAY_SECONDS, vb[0..], vn, false); +} + +/// `x ?? 0` then Number(): the coerced number of a field with a default of 0. +fn num_or0(d: []const u8, sp: i64) -> f64 { + if (nullish(d, sp)) { return 0.0; } + return jv_num(d, sp); +} + +/// Trim JavaScript white space off buf[0..l); the new start in lo and end in hi (written back through the arrays). +fn trim_range(buf: []const u8, l: u32, out: []u32) -> u32 { + var lo : u32 = 0; + var hi : u32 = l; + var w : u32 = 1; + while (lo < hi and w > 0) { + w = jv_ws_at(buf, lo, hi); + lo = lo + w; + } + w = 1; + while (hi > lo and w > 0) { + w = jv_ws_before(buf, lo, hi); + hi = hi - w; + } + out[0] = lo; + out[1] = hi; + return 0; +} + +/// size(bytes) as raw bytes into out, trimmed: `12.34 MB (12345678 B)` / `4492 B`; the length. +fn size_raw(out: []u8, d: []const u8, sp: i64) -> u32 { + var tmp : [512]u8 = [0; 512]; + var t : u32 = 0; + var v : f64 = jv_num(d, sp); + var i : u32 = 0; + const last : u32 = (SAY_UNITS.len as u32) - 1; + while (v >= 1000.0 and i < last) { + v = v / 1000.0; + i = i + 1; + } + if (i == 0) { + t = jv_str(tmp[0..], t, d, sp); + } else { + t = js_to_fixed(tmp[0..], t, v, 2); + } + t = put(tmp[0..], t, 32); + t = jn_raw(tmp[0..], t, SAY_UNITS[i as usize]); + if (i != 0) { + t = jn_raw(tmp[0..], t, " ("); + t = jv_str(tmp[0..], t, d, sp); + t = put(tmp[0..], t, 32); + t = jn_raw(tmp[0..], t, SAY_UNITS[0]); + t = put(tmp[0..], t, 41); + } + var rg : [2]u32 = [0; 2]; + trim_range(tmp[0..], t, rg[0..]); + var n : u32 = 0; + var q : u32 = rg[0]; + while (q < rg[1]) { + n = jn_put(out, n, tmp[q as usize]); + q = q + 1; + } + return n; +} + +fn size_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 { + var tmp : [600]u8 = [0; 600]; + const l : u32 = size_raw(tmp[0..], d, sp); + return put_all(dst, n0, tmp[0..], l); +} + +/// toLowerCase for ASCII and Latin-1 (UTF-8 C3 80..9E except 97), in place; the length does not change. +fn lower_in_place(buf: []u8, l: u32) -> u32 { + var i : u32 = 0; + while (i < l) { + const c : u8 = buf[i as usize]; + if (c >= 65 and c <= 90) { buf[i as usize] = c + 32; } + if (c == 195 and i + 1 < l) { + const c2 : u8 = buf[(i + 1) as usize]; + if (c2 >= 128 and c2 <= 158 and c2 != 151) { buf[(i + 1) as usize] = c2 + 32; } + i = i + 1; + } + i = i + 1; + } + return l; +} + +/// toolLine(s): the version alone when it already names the tool, else `tool | version`. +fn tool_line(dst: []u8, n0: u32, d: []const u8, s: i64) -> u32 { + var ver : [512]u8 = [0; 512]; + var vl : u32 = 0; + const vv : i64 = obj_get(d, s, "version"); + if (!nullish(d, vv)) { vl = jv_str(ver[0..], 0, d, vv); } + var tool : [512]u8 = [0; 512]; + const tl : u32 = jv_str(tool[0..], 0, d, obj_get(d, s, "tool")); + var fl : u32 = 0; + while (fl < tl and tool[fl as usize] != 32) { fl = fl + 1; } + var lver : [512]u8 = [0; 512]; + var ltool : [512]u8 = [0; 512]; + var c : u32 = 0; + while (c < vl) { + lver[c as usize] = ver[c as usize]; + c = c + 1; + } + c = 0; + while (c < fl) { + ltool[c as usize] = tool[c as usize]; + c = c + 1; + } + lower_in_place(lver[0..], vl); + lower_in_place(ltool[0..], fl); + // does lower(ver) include lower(first)? + var found : bool = fl == 0; + var a : u32 = 0; + while (!found and a + fl <= vl) { + var m : bool = true; + var b : u32 = 0; + while (m and b < fl) { + if (lver[(a + b) as usize] != ltool[b as usize]) { m = false; } + b = b + 1; + } + if (m) { found = true; } + a = a + 1; + } + if (found) { return put_all(dst, n0, ver[0..], vl); } + var n : u32 = put_all(dst, n0, tool[0..], tl); + n = jn_raw(dst, n, " | "); + return put_all(dst, n, ver[0..], vl); +} + +fn hexval(b: u8) -> u32 { + if (b >= 48 and b <= 57) { return (b - 48) as u32; } + if (b >= 97 and b <= 102) { return (b - 97) as u32 + 10; } + if (b >= 65 and b <= 70) { return (b - 65) as u32 + 10; } + return 99; +} + +/// barcode(hex): one bar and one gap per hex digit, widths 1 to 4, with a guard at each end. +fn barcode_html(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 { + var tmp : [512]u8 = [0; 512]; + var l : u32 = 0; + if (jv_truthy(d, sp)) { l = jv_str(tmp[0..], 0, d, sp); } + var parts : [600]u32 = [0; 600]; + var pc : u32 = 0; + parts[0] = 1; parts[1] = 1; parts[2] = 1; parts[3] = 1; + pc = 4; + var i : u32 = 0; + while (i < l and pc < 590) { + const hv : u32 = hexval(tmp[i as usize]); + if (hv < 16) { + parts[pc as usize] = hv / 4 + 1; + parts[(pc + 1) as usize] = hv % 4 + 1; + pc = pc + 2; + } + i = i + 1; + } + parts[pc as usize] = 1; parts[(pc + 1) as usize] = 1; parts[(pc + 2) as usize] = 1; parts[(pc + 3) as usize] = 1; + pc = pc + 4; + var width : u32 = 12; + i = 0; + while (i < pc) { + width = width + parts[i as usize]; + i = i + 1; + } + var n : u32 = raw(dst, n0, ""); + var x : u32 = 6; + i = 0; + while (i < pc) { + if (i % 2 == 0) { + n = raw(dst, n, ""); + } + x = x + parts[i as usize]; + i = i + 1; + } + return raw(dst, n, ""); +} + +// --- the stages ----------------------------------------------------------------------------------- + +fn stage_item(dst: []u8, n0: u32, d: []const u8, s: i64, ix: u32) -> u32 { + const status : i64 = obj_get(d, s, "status"); + var n : u32 = raw(dst, n0, "
"); + // line(name, ...) + n = line_open(dst, n, ""); + n = js_str_u64(dst, n, (ix + 1) as u64); + n = put(dst, n, 32); + if (ix < (SAY_STAGE_NAMES.len as u32)) { n = text(dst, n, SAY_STAGE_NAMES[ix as usize]); } else { n = jv_text(dst, n, d, obj_get(d, s, "id")); } + n = line_mid(dst, n); + if (jv_is(d, status, "not run")) { + n = text(dst, n, SAY_NOT_RUN); + n = line_close(dst, n); + n = div_open(dst, n, "br-sub", ""); + n = tc_text(dst, n, d, obj_get(d, s, "tool")); + n = raw(dst, n, "
"); + return n; + } + n = secs_text(dst, n, num_or0(d, obj_get(d, s, "ms"))); + n = line_close(dst, n); + n = div_open(dst, n, "br-sub", ""); + n = tool_line(dst, n, d, s); + n = raw(dst, n, ""); + if (failed) { + n = div_open(dst, n, "br-sub", "br-bad"); + var vb : [256]u8 = [0; 256]; + var vn : u32 = 0; + const ex : i64 = obj_get(d, s, "exit"); + if (nullish(d, ex)) { vn = jv_vput_str(vb[0..], 0, "exit", ""); } else { vn = jv_vput_val(vb[0..], 0, "exit", d, ex); } + n = jv_fill(dst, n, SAY_FAILED, vb[0..], vn, false); + n = raw(dst, n, ""); + if (jv_truthy(d, obj_get(d, s, "error"))) { + n = raw(dst, n, "
");
+            n = jv_text(dst, n, d, obj_get(d, s, "error"));
+            n = raw(dst, n, "
"); + } + return raw(dst, n, ""); + } + const out : i64 = obj_get(d, s, "output"); + if (jv_truthy(d, out)) { + n = div_open(dst, n, "br-sub", "br-detail"); + var ob : [1024]u8 = [0; 1024]; + var on : u32 = jv_vput_val(ob[0..], 0, "file", d, obj_get(d, out, "file")); + var szn : [600]u8 = [0; 600]; + const tmpn : u32 = size_raw(szn[0..], d, obj_get(d, out, "bytes")); + on = jv_vput_str(ob[0..], on, "size", szn[0..(tmpn as usize)]); + n = jv_fill(dst, n, SAY_OUT, ob[0..], on, false); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-hex br-detail"); + var hb : [512]u8 = [0; 512]; + var hn : u32 = jv_vput_val(hb[0..], 0, "sha", d, obj_get(d, out, "sha256")); + n = jv_fill(dst, n, SAY_SHA, hb[0..], hn, false); + n = raw(dst, n, ""); + } + const cpu : i64 = obj_get(d, s, "cpu_s"); + if (!nullish(d, cpu)) { + n = div_open(dst, n, "br-sub", "br-faint br-detail"); + var cb : [256]u8 = [0; 256]; + var cn : u32 = jv_vput_val(cb[0..], 0, "cpu", d, cpu); + cn = jv_vput_f64(cb[0..], cn, "rss", js_round(num_or0(d, obj_get(d, s, "rss_mb")))); + n = jv_fill(dst, n, SAY_CPU, cb[0..], cn, false); + n = raw(dst, n, ""); + } + return raw(dst, n, ""); +} + +/// The names specDiff() lists: where the generated constants of this spec and receipt.json disagree. +fn spec_diff(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 { + const st : i64 = obj_get(d, r, "stages"); + const cnt : u32 = arr_len(d, st); + var n : u32 = n0; + var any : bool = false; + const sc : i64 = obj_get(d, r, "stage_count"); + if (!(kind(d, sc) == J_NUMBER and jv_num(d, sc) == (K_STAGE_COUNT as f64) and jv_num(d, sc) == (cnt as f64))) { + n = raw(dst, n, "K_STAGE_COUNT"); + any = true; + } + const so : i64 = obj_get(d, r, "stages_ok"); + if (!(kind(d, so) == J_NUMBER and jv_num(d, so) == (K_STAGES_OK as f64))) { + if (any) { n = raw(dst, n, ", "); } + n = raw(dst, n, "K_STAGES_OK"); + any = true; + } + var msbad : bool = false; + if ((K_STAGE_MS.len as u32) != cnt) { msbad = true; } + var sum : f64 = 0.0; + // reduce((a, b) => a + b, 0): numbers add; once a string (or object / array) is met the rest is concatenated + var sbuf : [1100]u8 = [0; 1100]; + var sl : u32 = 0; + var strmode : bool = false; + var i : u32 = 0; + while (i < cnt) { + const ms : i64 = obj_get(d, arr_at(d, st, i), "ms"); + const mk : u8 = kind(d, ms); + var mv : f64 = 0.0; + var is_num : bool = true; + if (!nullish(d, ms)) { + mv = jv_num(d, ms); + is_num = mk == J_NUMBER; + } + if (i < (K_STAGE_MS.len as u32)) { + if (!is_num or mv != (K_STAGE_MS[i as usize] as f64)) { msbad = true; } + } + if (!strmode and (mk == J_STRING or mk == J_OBJECT or mk == J_ARRAY)) { + strmode = true; + sl = js_str_num(sbuf[0..], 0, sum); + } + if (strmode) { + if (nullish(d, ms)) { sl = jn_raw(sbuf[0..], sl, "0"); } else { sl = jv_str(sbuf[0..], sl, d, ms); } + } else { + sum = sum + mv; + } + i = i + 1; + } + if (strmode) { sum = jv_number_of_text(sbuf[0..], sl); } + if (msbad) { + if (any) { n = raw(dst, n, ", "); } + n = raw(dst, n, "K_STAGE_MS"); + any = true; + } + const tm : i64 = obj_get(d, r, "total_ms"); + if (!(kind(d, tm) == J_NUMBER and jv_num(d, tm) == (K_TOTAL_MS as f64))) { + if (any) { n = raw(dst, n, ", "); } + n = raw(dst, n, "K_TOTAL_MS"); + any = true; + } + const bit : i64 = obj_get(d, r, "bit"); + if (jv_truthy(d, bit)) { + const bb : i64 = obj_get(d, bit, "bytes"); + if (!(kind(d, bb) == J_NUMBER and jv_num(d, bb) == (K_BIT_BYTES as f64))) { + if (any) { n = raw(dst, n, ", "); } + n = raw(dst, n, "K_BIT_BYTES"); + any = true; + } + } + var gap : f64 = sum - jv_num(d, tm); + if (gap < 0.0) { gap = 0.0 - gap; } + if (gap > (K_ROUND_TOL_MS as f64)) { + if (any) { n = raw(dst, n, ", "); } + n = raw(dst, n, "total_ms"); + } + return n; +} + +fn member_value(d: []const u8, ut: i64, ix: u32, key_out: []u8, key_len: []u32) -> i64 { + const hi : usize = span_hi(ut); + var p : usize = skip_ws(d, span_lo(ut) + 1); + var at : u32 = 0; + while (p < hi and d[p] == 34) { + const ke : usize = string_end(d, p); + const q : usize = skip_ws(d, ke); + const v : usize = skip_ws(d, q + 1); + const ve : usize = value_end(d, v); + if (at == ix) { + key_len[0] = jv_str_to(key_out, 0, d, jspan(p, ke)); + return jspan(v, ve); + } + at = at + 1; + p = skip_ws(d, ve); + if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); } + } + return NO_SPAN; +} + +fn member_count(d: []const u8, ut: i64) -> u32 { + const hi : usize = span_hi(ut); + var p : usize = skip_ws(d, span_lo(ut) + 1); + var at : u32 = 0; + while (p < hi and d[p] == 34) { + const ke : usize = string_end(d, p); + const q : usize = skip_ws(d, ke); + const v : usize = skip_ws(d, q + 1); + const ve : usize = value_end(d, v); + at = at + 1; + p = skip_ws(d, ve); + if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); } + } + return at; +} + +fn paper_html(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 { + const bit : i64 = obj_get(d, r, "bit"); + const mach : i64 = obj_get(d, r, "machine"); + const des : i64 = obj_get(d, r, "design"); + const sha : i64 = obj_get(d, bit, "sha256"); + var n : u32 = raw(dst, n0, "
"); + // the receipt number: the payload digest (else the file digest), first 8 characters, upper case + var pay : i64 = obj_get(d, bit, "payload_sha256"); + if (nullish(d, pay)) { pay = sha; } + var nb : [64]u8 = [0; 64]; + var nl : u32 = 0; + if (kind(d, pay) == J_STRING) { + var raw8 : [512]u8 = [0; 512]; + const rl : u32 = jv_str(raw8[0..], 0, d, pay); + var units : u32 = 0; + var q : u32 = 0; + while (q < rl and units < 8 and nl < 56) { + const c : u8 = raw8[q as usize]; + var w : u32 = 1; + var u : u32 = 1; + if (c >= 192) { w = 2; } + if (c >= 224) { w = 3; } + if (c >= 240) { w = 4; u = 2; } + if (units + u > 8) { break; } + if (c >= 97 and c <= 122) { + nb[nl as usize] = c - 32; + nl = nl + 1; + } else { + if (c == 195 and q + 1 < rl) { + // Latin-1: a..o / o..y with a diaeresis etc. go up by 32 (not the division sign); y with diaeresis and sharp s are special + const c2 : u8 = raw8[(q + 1) as usize]; + if (c2 == 159) { + nb[nl as usize] = 83; + nb[(nl + 1) as usize] = 83; + nl = nl + 2; + } else { + if (c2 == 191) { + nb[nl as usize] = 197; + nb[(nl + 1) as usize] = 184; + nl = nl + 2; + } else { + nb[nl as usize] = c; + if (c2 >= 160 and c2 <= 190 and c2 != 183) { nb[(nl + 1) as usize] = c2 - 32; } else { nb[(nl + 1) as usize] = c2; } + nl = nl + 2; + } + } + } else { + if (c == 194 and q + 1 < rl and raw8[(q + 1) as usize] == 181) { + nb[nl as usize] = 206; + nb[(nl + 1) as usize] = 156; + nl = nl + 2; + } else { + var z : u32 = 0; + while (z < w and q + z < rl) { + nb[(nl + z) as usize] = raw8[(q + z) as usize]; + z = z + 1; + } + nl = nl + z; + } + } + } + q = q + w; + units = units + u; + } + } + var vb : [512]u8 = [0; 512]; + var vn : u32 = jv_vput_str(vb[0..], 0, "no", nb[0..(nl as usize)]); + n = div_open(dst, n, "br-center", "br-shop"); + n = text(dst, n, SAY_SHOP); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-center", ""); + n = jv_fill(dst, n, SAY_RECEIPT, vb[0..], vn, false); + n = raw(dst, n, ""); + // when + n = div_open(dst, n, "br-center", ""); + var wb : [1200]u8 = [0; 1200]; + var wtmp : [1100]u8 = [0; 1100]; + const wl : u32 = utc_raw(wtmp[0..], d, obj_get(d, r, "when_utc")); + var wn : u32 = jv_vput_str(wb[0..], 0, "when", wtmp[0..(wl as usize)]); + n = jv_fill(dst, n, SAY_WHEN, wb[0..], wn, false); + n = raw(dst, n, ""); + // machine + n = div_open(dst, n, "br-center", "br-faint"); + var mb : [1024]u8 = [0; 1024]; + var mn : u32 = 0; + if (kind(d, mach) == J_OBJECT) { + mn = jv_vput_defined(mb[0..], mn, "cpu", d, obj_get(d, mach, "cpu")); + mn = jv_vput_defined(mb[0..], mn, "cores", d, obj_get(d, mach, "cores")); + mn = jv_vput_defined(mb[0..], mn, "macos", d, obj_get(d, mach, "macos")); + } + n = jv_fill(dst, n, SAY_MACHINE, mb[0..], mn, false); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-center", "br-faint"); + var lb : [256]u8 = [0; 256]; + var ln : u32 = 0; + const la : i64 = obj_get(d, mach, "loadavg_1m_at_start"); + if (nullish(d, la)) { ln = jv_vput_str(lb[0..], 0, "load", ""); } else { ln = jv_vput_val(lb[0..], 0, "load", d, la); } + n = jv_fill(dst, n, SAY_LOAD, lb[0..], ln, false); + n = raw(dst, n, ""); + n = rule_html(dst, n); + // design + n = div_open(dst, n, "br-sub", ""); + n = text(dst, n, SAY_LABEL_SPEC); + n = raw(dst, n, ": "); + n = nn_text(dst, n, d, obj_get(d, des, "spec")); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", ""); + n = text(dst, n, SAY_LABEL_PART); + n = raw(dst, n, ": "); + n = nn_text(dst, n, d, obj_get(d, des, "part")); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-detail"); + n = text(dst, n, SAY_LABEL_BOARD); + n = raw(dst, n, ": "); + n = nn_text(dst, n, d, obj_get(d, des, "board")); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-faint br-detail"); + var db : [1024]u8 = [0; 1024]; + var dn : u32 = 0; + dn = jv_vput_nn(db[0..], dn, "name", d, obj_get(d, des, "datapath")); + dn = jv_vput_nn(db[0..], dn, "lines", d, obj_get(d, des, "wrapper_lines")); + n = jv_fill(dst, n, SAY_WRAPPER, db[0..], dn, false); + n = raw(dst, n, ""); + n = rule_html(dst, n); + n = line_open(dst, n, "br-head"); + n = text(dst, n, SAY_COL_ITEM); + n = line_mid(dst, n); + n = text(dst, n, SAY_COL_TIME); + n = line_close(dst, n); + // items + const st : i64 = obj_get(d, r, "stages"); + const cnt : u32 = arr_len(d, st); + var i : u32 = 0; + while (i < cnt) { + n = stage_item(dst, n, d, arr_at(d, st, i), i); + i = i + 1; + } + n = rule_html(dst, n); + // totals + n = div_open(dst, n, "br-line", ""); + var tb : [256]u8 = [0; 256]; + var tn : u32 = jv_vput_val(tb[0..], 0, "ok", d, obj_get(d, r, "stages_ok")); + tn = jv_vput_val(tb[0..], tn, "count", d, obj_get(d, r, "stage_count")); + n = jv_fill(dst, n, SAY_ITEMS, tb[0..], tn, false); + n = raw(dst, n, ""); + const total_ms : f64 = jv_num(d, obj_get(d, r, "total_ms")); + if (jv_truthy(d, obj_get(d, r, "complete"))) { + n = line_open(dst, n, "br-total"); + n = text(dst, n, SAY_TOTAL); + n = line_mid(dst, n); + n = secs_text(dst, n, total_ms); + n = line_close(dst, n); + } else { + n = div_open(dst, n, "br-sub", "br-bad br-total-miss"); + var sb : [256]u8 = [0; 256]; + var stmp : [200]u8 = [0; 200]; + const sl : u32 = secs_text_raw(stmp[0..], total_ms); + const sn : u32 = jv_vput_str(sb[0..], 0, "s", stmp[0..(sl as usize)]); + n = jv_fill(dst, n, SAY_NO_TOTAL, sb[0..], sn, false); + n = raw(dst, n, ""); + } + var vt : [64]u8 = [0; 64]; + var vtl : u32 = 0; + const vend : i64 = obj_get(d, r, "vendor_tools_run"); + if (nullish(d, vend)) { vtl = jn_raw(vt[0..], 0, "0"); } else { vtl = jv_str(vt[0..], 0, d, vend); } + n = line_open(dst, n, "br-big"); + n = text(dst, n, SAY_VIVADO); + n = line_mid(dst, n); + n = put_all(dst, n, vt[0..], vtl); + n = line_close(dst, n); + n = line_open(dst, n, ""); + n = text(dst, n, SAY_VENDOR); + n = line_mid(dst, n); + n = put_all(dst, n, vt[0..], vtl); + n = line_close(dst, n); + n = div_open(dst, n, "br-sub", "br-faint"); + var eb : [256]u8 = [0; 256]; + var etmp : [200]u8 = [0; 200]; + const el : u32 = secs_text_raw(etmp[0..], num_or0(d, obj_get(d, r, "wall_ms_end_to_end"))); + const en : u32 = jv_vput_str(eb[0..], 0, "s", etmp[0..(el as usize)]); + n = jv_fill(dst, n, SAY_END_TO_END, eb[0..], en, false); + n = raw(dst, n, ""); + n = rule_html(dst, n); + // the design that came out + const fm : i64 = obj_get(d, r, "fmax"); + if (jv_truthy(d, fm)) { + const pass : bool = jv_truthy(d, obj_get(d, fm, "pass")); + if (pass) { n = line_open(dst, n, ""); } else { n = line_open(dst, n, "br-bad"); } + var fb : [512]u8 = [0; 512]; + var fn_ : u32 = jv_vput_val(fb[0..], 0, "clock", d, obj_get(d, fm, "clock")); + fn_ = jv_vput_val(fb[0..], fn_, "mhz", d, obj_get(d, fm, "mhz")); + fn_ = jv_vput_val(fb[0..], fn_, "target", d, obj_get(d, fm, "target_mhz")); + n = jv_fill(dst, n, SAY_FMAX, fb[0..], fn_, false); + n = line_mid(dst, n); + if (pass) { n = text(dst, n, SAY_PASS); } else { n = text(dst, n, SAY_FAIL); } + n = line_close(dst, n); + } + const ut : i64 = obj_get(d, r, "utilisation"); + var has_entries : bool = false; + if (jv_truthy(d, ut)) { + if (kind(d, ut) == J_ARRAY) { has_entries = arr_len(d, ut) > 0; } + if (kind(d, ut) == J_OBJECT) { has_entries = member_count(d, ut) > 0; } + } + if (has_entries) { + n = div_open(dst, n, "br-sub", ""); + n = text(dst, n, SAY_UTIL_TITLE); + n = raw(dst, n, ""); + var ecount : u32 = 0; + if (kind(d, ut) == J_ARRAY) { ecount = arr_len(d, ut); } else { ecount = member_count(d, ut); } + var e : u32 = 0; + while (e < ecount) { + var kb : [512]u8 = [0; 512]; + var kl : [1]u32 = [0; 1]; + var val : i64 = NO_SPAN; + if (kind(d, ut) == J_ARRAY) { + val = arr_at(d, ut, e); + kl[0] = js_str_u64(kb[0..], 0, e as u64); + } else { + val = member_value(d, ut, e, kb[0..], kl[0..]); + } + n = line_open(dst, n, "br-small"); + n = raw(dst, n, " "); + n = put_all(dst, n, kb[0..], kl[0]); + n = line_mid(dst, n); + n = jv_text(dst, n, d, arr_at(d, val, 0)); + n = raw(dst, n, " / "); + n = jv_text(dst, n, d, arr_at(d, val, 1)); + n = line_close(dst, n); + e = e + 1; + } + } + if (jv_truthy(d, fm) or jv_truthy(d, ut)) { n = rule_html(dst, n); } + // the bitstream + if (jv_truthy(d, bit)) { + n = div_open(dst, n, "br-sub", ""); + n = text(dst, n, SAY_FILE_SHA); + n = raw(dst, n, ": "); + n = size_text(dst, n, d, obj_get(d, bit, "bytes")); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-hex"); + n = tc_text(dst, n, d, obj_get(d, bit, "sha256")); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-faint"); + n = text(dst, n, SAY_FILE_SHA_NOTE); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", ""); + var pb : [600]u8 = [0; 600]; + var pstr : [600]u8 = [0; 600]; + const pl : u32 = size_raw(pstr[0..], d, obj_get(d, bit, "payload_bytes")); + const pn : u32 = jv_vput_str(pb[0..], 0, "size", pstr[0..(pl as usize)]); + n = jv_fill(dst, n, SAY_PAYLOAD_SHA, pb[0..], pn, false); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-sub", "br-hex"); + n = tc_text(dst, n, d, obj_get(d, bit, "payload_sha256")); + n = raw(dst, n, ""); + const er : i64 = obj_get(d, r, "earlier_runs"); + var ec : u32 = 0; + if (kind(d, er) == J_ARRAY) { ec = arr_len(d, er); } + if (ec > 0) { + const same : bool = jv_truthy(d, obj_get(d, r, "payload_same_as_earlier")); + if (same) { n = div_open(dst, n, "br-sub", ""); } else { n = div_open(dst, n, "br-sub", "br-bad"); } + if (same) { + var sb2 : [64]u8 = [0; 64]; + const sn2 : u32 = jv_vput_f64(sb2[0..], 0, "n", ec as f64); + n = jv_fill(dst, n, SAY_SAME_PAYLOAD, sb2[0..], sn2, false); + } else { + n = text(dst, n, SAY_OTHER_PAYLOAD); + } + n = raw(dst, n, ""); + var x : u32 = 0; + while (x < ec) { + const e2 : i64 = arr_at(d, er, x); + n = div_open(dst, n, "br-sub", "br-faint"); + var xb : [1600]u8 = [0; 1600]; + var xt : [1100]u8 = [0; 1100]; + var xtl : u32 = utc_raw(xt[0..], d, obj_get(d, e2, "when_utc")); + var xn : u32 = jv_vput_str(xb[0..], 0, "when", xt[0..(xtl as usize)]); + var xs : [200]u8 = [0; 200]; + const xsl : u32 = secs_text_raw(xs[0..], jv_num(d, obj_get(d, e2, "total_ms"))); + xn = jv_vput_str(xb[0..], xn, "s", xs[0..(xsl as usize)]); + xn = jv_vput_val(xb[0..], xn, "load", d, obj_get(d, e2, "loadavg_1m_at_start")); + n = jv_fill(dst, n, SAY_EARLIER, xb[0..], xn, false); + n = raw(dst, n, ""); + x = x + 1; + } + } + const cc : i64 = obj_get(d, r, "cross_check"); + const cs : i64 = obj_get(d, cc, "status"); + const cok : bool = jv_is(d, cs, "ok"); + const ceq : bool = jv_truthy(d, obj_get(d, cc, "payload_equal")); + if (cok and !ceq) { n = div_open(dst, n, "br-sub", "br-bad"); } else { n = div_open(dst, n, "br-sub", ""); } + if (cok and ceq) { + var cb : [256]u8 = [0; 256]; + const cn : u32 = jv_vput_val(cb[0..], 0, "ms", d, obj_get(d, cc, "ms")); + n = jv_fill(dst, n, SAY_CROSS_OK, cb[0..], cn, false); + } else { + if (cok) { n = text(dst, n, SAY_CROSS_BAD); } else { n = text(dst, n, SAY_CROSS_SKIP); } + } + n = raw(dst, n, ""); + n = rule_html(dst, n); + n = barcode_html(dst, n, d, sha); + n = div_open(dst, n, "br-center", "br-hex br-tiny"); + n = tc_text(dst, n, d, sha); + n = raw(dst, n, ""); + } + n = div_open(dst, n, "br-center", "br-small"); + n = text(dst, n, SAY_NOT_PROGRAMMED); + n = raw(dst, n, ""); + n = div_open(dst, n, "br-center", "br-faint br-small"); + n = text(dst, n, SAY_FOOTER); + n = raw(dst, n, ""); + return raw(dst, n, "
"); +} + +/// `x ?? ''` as a child / template-literal piece: the value's String(), nothing for null / missing. +fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 { + if (nullish(d, sp)) { return n0; } + return jv_text(dst, n0, d, sp); +} + +/// A pool entry for `{key: x ?? ''}`. +fn jv_vput_nn(vb: []u8, n0: u32, key: []const u8, d: []const u8, sp: i64) -> u32 { + if (nullish(d, sp)) { return jv_vput_str(vb, n0, key, ""); } + return jv_vput_val(vb, n0, key, d, sp); +} + +/// utc(iso) as raw bytes (not escaped) into out; the length. +fn utc_raw(out: []u8, d: []const u8, sp: i64) -> u32 { + var l : u32 = 0; + if (!nullish(d, sp)) { l = jv_str(out, 0, d, sp); } + var k : u32 = 0; + var done : bool = false; + while (k < l and !done) { + if (out[k as usize] == 84) { + out[k as usize] = 32; + done = true; + } + k = k + 1; + } + if (l > 0 and out[(l - 1) as usize] == 90) { + var e : u32 = l - 1; + var j : u32 = e; + while (j > 0 and out[(j - 1) as usize] >= 48 and out[(j - 1) as usize] <= 57) { j = j - 1; } + if (j < e and j > 0 and out[(j - 1) as usize] == 46) { e = j - 1; } + l = e; + } + return l; +} + +/// secs(ms) as raw bytes into out: SAY_SECONDS with (ms / 1000).toFixed(3). +fn secs_text_raw(out: []u8, ms: f64) -> u32 { + var tmp : [128]u8 = [0; 128]; + const l : u32 = js_to_fixed(tmp[0..], 0, ms / 1000.0, 3); + return fill_raw(out, SAY_SECONDS, tmp[0..(l as usize)]); +} + +/// The template with {s} replaced by v, as raw (unescaped) bytes. +fn fill_raw(out: []u8, t: []const u8, v: []const u8) -> u32 { + var n : u32 = 0; + var i : usize = 0; + while (i < t.len) { + if (t[i] == 123 and i + 2 < t.len and t[i + 1] == 115 and t[i + 2] == 125) { + n = jn_raw(out, n, v); + i = i + 3; + } else { + n = jn_put(out, n, t[i]); + i = i + 1; + } + } + return n; +} + +pub fn w_render(dst: []u8) -> u32 { + var n : u32 = 0; + if (DATA_STATE == 0) { + n = raw(dst, n, "

"); + n = text(dst, n, SAY_LOADING); + n = raw(dst, n, "

"); + if (!ok(n)) { return 0; } + return n; + } + if (DATA_STATE == 2) { + n = raw(dst, n, "

"); + n = text(dst, n, SAY_ERROR); + n = raw(dst, n, "

"); + if (!ok(n)) { return 0; } + return n; + } + const d : []const u8 = DATA[0..(DATA_LEN as usize)]; + const r : i64 = root(d); + n = raw(dst, n, "
"); + var diff : [512]u8 = [0; 512]; + const dl : u32 = spec_diff(diff[0..], 0, d, r); + if (dl > 0) { + n = raw(dst, n, "

"); + var wb : [600]u8 = [0; 600]; + const wn : u32 = jv_vput_str(wb[0..], 0, "what", diff[0..(dl as usize)]); + n = jv_fill(dst, n, SAY_SPEC_DIFF, wb[0..], wn, false); + n = raw(dst, n, "

"); + } + n = paper_html(dst, n, d, r); + n = raw(dst, n, "

"); + n = text(dst, n, SAY_VERIFY_TITLE); + n = raw(dst, n, "

    "); + var v : usize = 0; + while (v < SAY_VERIFY_STEPS.len) { + n = raw(dst, n, "
  1. "); + n = text(dst, n, SAY_VERIFY_STEPS[v]); + n = raw(dst, n, "
  2. "); + v = v + 1; + } + n = raw(dst, n, "
"); + if (!ok(n)) { return 0; } + return n; +} + +/// The splicer follows only the names a module itself mentions, not the names the imported functions +/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call. +/// Never called. +pub fn imports_anchor(o: []u8, s: []const u8) -> u32 { + var n : u32 = 0; + n = n + (ok(0) as u32); + n = n + OVER; + n = n + put(o, 0, 0); + n = n + raw(o, 0, s); + n = n + put_escaped(o, 0, 0); + n = n + text(o, 0, s); + n = n + num(o, 0, 0); + n = n + attr(o, 0, s, s); + n = n + fill(o, 0, s, s, s, s, 0); + n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s); + n = n + dec_to(o, 0); + n = n + (name_is(s, 0, 0, s) as u32); + n = n + (is_word_byte(0) as u32); + n = n + (H_LT as u32); + n = n + (H_GT as u32); + n = n + (H_AMP as u32); + n = n + (H_QUOTE as u32); + n = n + (H_APOS as u32); + n = n + (H_SPACE as u32); + n = n + (H_SLASH as u32); + n = n + (H_EQ as u32); + n = n + ((NO_SPAN < 0) as u32); + n = n + ((SPAN > 0) as u32); + n = n + (J_NONE as u32); + n = n + (J_OBJECT as u32); + n = n + (J_ARRAY as u32); + n = n + (J_STRING as u32); + n = n + (J_NUMBER as u32); + n = n + (J_TRUE as u32); + n = n + (J_FALSE as u32); + n = n + (J_NULL as u32); + n = n + (jspan(0, 0) as u32); + n = n + (span_lo(0) as u32); + n = n + (span_hi(0) as u32); + n = n + (skip_ws(s, 0) as u32); + n = n + (string_end(s, 0) as u32); + n = n + (value_end(s, 0) as u32); + n = n + (root(s) as u32); + n = n + (kind(s, 0) as u32); + n = n + (key_is(s, 0, 0, s) as u32); + n = n + (obj_get(s, 0, s) as u32); + n = n + arr_len(s, 0); + n = n + (arr_at(s, 0, 0) as u32); + n = n + hex4(s, 0); + n = n + escape_at(s, 0, 0); + n = n + cp_utf8_len(0); + n = n + (cp_utf8_byte(0, 0) as u32); + n = n + str_len(s, 0); + n = n + (str_byte(s, 0, 0) as u32); + n = n + JN_OVER; + n = n + JN_CAP; + n = n + ((JN_TWO52 > 0.0) as u32); + n = n + ((JN_TWO53 > 0.0) as u32); + n = n + jn_put(o, 0, 0); + n = n + jn_raw(o, 0, s); + n = n + jn_digits(o, 0); + n = n + jn_mul(o, 0, 0); + n = n + jn_mul2(o, 0, 0); + n = n + jn_mul5(o, 0, 0); + n = n + jn_shift(o, 0, 0); + n = n + (jn_cmp(s, s, 0) as u32); + n = n + (jn_expo(0.0) as u32); + n = n + (jn_mant(0.0) as u32); + n = n + ((jn_make(0, 0) > 0.0) as u32); + n = n + jn_exact(o, 0, 0); + n = n + jn_fscale(0); + n = n + (js_is_nan(0.0) as u32); + n = n + ((js_nan(0.0) > 0.0) as u32); + n = n + ((js_inf(1.0) > 0.0) as u32); + n = n + ((js_floor(0.0) > 0.0) as u32); + n = n + ((js_ceil(0.0) > 0.0) as u32); + n = n + ((js_max(0.0, 0.0) > 0.0) as u32); + n = n + ((js_min(0.0, 0.0) > 0.0) as u32); + n = n + ((js_round(0.0) > 0.0) as u32); + n = n + (jn_shortest(o, 1.0) as u32); + n = n + jn_copy(o, s, 0); + n = n + jn_put_run(o, 0, s, 0, 0); + n = n + jn_layout(o, 0, s, 0, 0); + n = n + js_str_num(o, 0, 0.0); + n = n + js_to_fixed(o, 0, 0.0, 0); + n = n + js_locale_num(o, 0, 0.0); + n = n + (jn_num_ok(s, 0, 0) as u32); + n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32); + n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32); + n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32); + n = n + (jn_abs(0) as u32); + n = n + js_str_u64(o, 0, 0); + n = n + jv_str_to(o, 0, s, 0); + n = n + jv_text_str(o, 0, s, 0); + n = n + ((jv_numval(s, 0) > 0.0) as u32); + n = n + jv_str(o, 0, s, 0); + n = n + jv_text(o, 0, s, 0); + n = n + (jv_is(s, 0, s) as u32); + n = n + (jv_truthy(s, 0) as u32); + n = n + (jv_is_ws(0) as u32); + n = n + ((jv_number_of_text(s, 0) > 0.0) as u32); + n = n + ((jv_num(s, 0) > 0.0) as u32); + n = n + jv_vput_str(o, 0, s, s); + n = n + jv_vput_f64(o, 0, s, 0.0); + n = n + jv_vput_val(o, 0, s, s, 0); + n = n + jv_vput_defined(o, 0, s, s, 0); + n = n + jv_vput_fixed(o, 0, s, 0.0, 0); + n = n + jv_kid(o, 0, s, 0); + n = n + jv_find(s, 0, s); + n = n + jv_fill(o, 0, s, s, 0, true); + n = n + (jv_string_ok(s, 0) as u32); + n = n + (jv_number_ok(s, 0) as u32); + n = n + (jv_lit_ok(s, 0, s) as u32); + n = n + (jv_valid(s) as u32); + n = n + jv_ws_at(s, 0, 0); + n = n + jv_ws_before(s, 0, 0); + n = n + jv_digit_value(0); + return n; +} diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27 index 0791601bb9..1803efd18f 100644 --- a/specs/widgets/catalog.t27 +++ b/specs/widgets/catalog.t27 @@ -34,9 +34,10 @@ ; Stages reached, each with a differential against the hand-written version on the trinity side. ; PORT_LOGIC (the pure logic file is .t27, built to wasm, 0 mismatches against the JavaScript): ; gatle 866178 comparisons, lut-depth 12125, stat-card 30827, fasm-skyline 384784, lut-treemap -; 14667449, lut-explain 1395124. +; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007. +; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27. ; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches) -; and jtag-verdict and race-the-bee at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM +; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket and test-touch at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM ; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing ; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps ; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when @@ -90,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card pub const PORT_STAGES : u8 = 5; -pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 3, 2, 2, 2, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1]; +pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 1, 2, 2, 1, 1, 3, 2, 2, 2, 2, 1, 3, 1, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2]; ; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card). ; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues. @@ -226,17 +227,18 @@ test the_waves_cover_every_tool_once { assert hand_lines(12) == 1432; } -test eight_tools_have_left_the_words_stage_with_a_differential { - assert count_port(PORT_WORDS) == TOOL_COUNT - 8; - assert count_port(PORT_LOGIC) == 6; - assert count_port(PORT_DRAW) == 2; +test seven_pages_are_native_and_the_twelve_logic_files_are_ported { + assert count_port(PORT_DRAW) == 7; + assert count_port(PORT_WORDS) == 9; + assert count_port(PORT_LOGIC) == 12; assert count_port(PORT_SEALED) == 0; - assert TOOL_PORT[4] == PORT_LOGIC; - assert TOOL_PORT[7] == PORT_LOGIC; - assert TOOL_PORT[11] == PORT_LOGIC; - assert TOOL_PORT[12] == PORT_LOGIC; - assert TOOL_PORT[13] == PORT_LOGIC; - assert TOOL_PORT[19] == PORT_LOGIC; + assert count_port(PORT_LOGIC) + count_port(PORT_DRAW) == 19; assert TOOL_PORT[10] == PORT_DRAW; assert TOOL_PORT[16] == PORT_DRAW; + assert TOOL_PORT[2] == PORT_DRAW; + assert TOOL_PORT[21] == PORT_DRAW; + assert TOOL_PORT[22] == PORT_DRAW; + assert TOOL_PORT[24] == PORT_DRAW; + assert TOOL_PORT[26] == PORT_DRAW; + assert TOOL_PORT[7] == PORT_LOGIC; } diff --git a/specs/widgets/fsm_sketch_logic.t27 b/specs/widgets/fsm_sketch_logic.t27 new file mode 100644 index 0000000000..6016c62fb2 --- /dev/null +++ b/specs/widgets/fsm_sketch_logic.t27 @@ -0,0 +1,2837 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/widgets/fsm_sketch_logic.t27 -- the FSM reader and layout of the fsm-sketch widget, as pure rules +; over bytes and numbers: the JavaScript file apps/website/public/widgets/fsm-sketch/fsmparse.js (379 lines, +; gHashTag/trinity) ported to .t27. Epic gHashTag/t27#10937, issue #10938. Owner rule (Only t27): the logic is +; written in .t27, never by hand in JavaScript. The page reads a pasted or dropped yosys log or KISS2 file +; in the reader's browser and sends nothing anywhere; these rules keep that: they see only the bytes and +; numbers the glue hands over, fill buffers the glue hands over, and hold no state between calls. +; -- +; Table: every export of fsmparse.js and the function(s) that replace it. +; sniff(text) sniff: 0 none, 1 log, 2 kiss2. +; parseAny(text) fsm_parse with mode 0: sniff, then parse_log or parse_kiss2; one result stream, see +; "the result stream" before fsm_parse's helpers. +; parseLog, parseKiss2 fsm_parse with mode 1 and mode 2 (parse_log, parse_kiss2). +; countsOf(f) counts_of. +; codeValue(code) code_value: -1 for null. +; conditionOf(pat,names) condition_of: the (index, bit) pairs; the glue joins "name=bit" (names[idx] ?? in[idx]). +; walkOrder(f) walk_order. +; layoutFsm(f, opts) layout_fsm. opts are passed explicitly: font, charW, edgeFont, maxLabelRows; the +; JavaScript defaults (14, 0.6 * font, round(0.86 * font), 3) are the glue's. The +; label lines of a state are given as two numbers each: how many lines and the +; longest line's length in UTF-16 units (a label's text is drawn by the glue). +; js_number is Number() of a KISS2 header token, which parseKiss2 uses. +; Not ported: nothing of fsmparse.js is left out, but the three libm calls of layoutFsm stay with the host +; (see "Floating point"). The private helpers stateNum, unslash, unit, rot, boxExit, labelBox, overlap, +; placeLabels, bounds and place are the functions of the same names here (stateNum is state_num). +; -- +; Domain. ASCII or UTF-8 bytes; bytes above 127 are ordinary non-space characters (JavaScript's \s and trim() +; also take NBSP, U+2028 and friends as space: not ported, not claimed). A number in a log or KISS2 file is read +; as an integer of up to 15 digits; an index above 2147483647 or a state number above 15 digits sets status 5 +; ("out of the claimed domain"). A KISS2 header value goes through Number() in the JavaScript: decimal +; integers and decimals with at most 15 significant digits and an exponent within 22, hex, octal and +; binary literals and Infinity are read the same here; any other form is NaN, as there. A longer decimal sets +; status 5. +; One JavaScript throw is reproduced as status 6: a "FSM `...' from module `...':" line above an +; "Information on FSM" line whose module name has no non-quote text right after "from module `". +; -- +; Floating point. layout_fsm repeats fsmparse.js's arithmetic operation for operation in f64. Math.hypot is +; V8's (the largest absolute value, then Kahan-summed squares of the ratios); Math.max and Math.min +; propagate NaN and order -0 below +0; Math.round is floor(x + 0.5) without the 0.49999999999999994 trap. +; The three libm calls of the JavaScript stay with the host, because Math.sin, Math.cos and Math.atan2 are +; the engine's own (measured: V8 12.9 on arm64 differs in the last bit from fdlibm, musl and the system +; libm in about 1 call in 100, so no port can promise their bits): (1) the cos and sin of the node angles +; -Math.PI / 2 + (2 * Math.PI * k) / n, k below n, and of -0.45, 0.45, -0.55, 0.55 come in as the table +; trig (cos, sin pairs: 2n for the nodes, then 8 for the four fixed angles, in that order); node_angle(k, n) +; gives the angle to ask the host for. (2) arrow.angle, which no other result depends on, is +; Math.atan2(arrow.y - c.y, arrow.x - c.x) with c the second control point of a self loop (ef 4, 5) and +; the control point of a curve (ef 2, 3): the glue computes it from the returned points; ef 10 is 0. +; The `**` of the JavaScript is exact for every base and exponent it is applied to with a constant t of +; 0.25 0.5 0.75, and x ** 2 is x * x. +; ASCII only (L3), English only (LANG-EN). +; phi^2 + 1/phi^2 = 3 | TRINITY + +module widget_fsm_sketch_logic; + +pub const NONE : u32 = 4294967295; +pub const NO_TEXT : i64 = -1; +pub const PACK : i64 = 4294967296; +pub const PI : f64 = 3.141592653589793; + +pub var V : [8]f64 = [0.0; 8]; + +// ------------------------------------------------------------------------------------------------ +// numbers and the small pure functions +// ------------------------------------------------------------------------------------------------ + +pub fn pack(lo: usize, hi: usize) -> i64 { + return (lo as i64) * PACK + (hi as i64); +} + +pub fn lo_of(p: i64) -> usize { + return (p / PACK) as usize; +} + +pub fn hi_of(p: i64) -> usize { + return (p % PACK) as usize; +} + +pub fn inf() -> f64 { + var z : f64 = 0.0; + return 1.0 / z; +} + +pub fn nan() -> f64 { + var z : f64 = 0.0; + return z / z; +} + +pub fn neg_zero(x: f64) -> bool { + if (x != 0.0) { return false; } + var y : f64 = x; + return 1.0 / y < 0.0; +} + +/// JavaScript's truthiness of a number: not 0, not NaN. +pub fn truthy(x: f64) -> bool { + return x != 0.0 and x == x; +} + +/// Math.max of two numbers: NaN wins, +0 is above -0. +pub fn jsmax(a: f64, b: f64) -> f64 { + if (a != a) { return a; } + if (b != b) { return b; } + if (a > b) { return a; } + if (b > a) { return b; } + if (a == 0.0 and neg_zero(a)) { return b; } + return a; +} + +/// Math.min of two numbers: NaN wins, -0 is below +0. +pub fn jsmin(a: f64, b: f64) -> f64 { + if (a != a) { return a; } + if (b != b) { return b; } + if (a < b) { return a; } + if (b < a) { return b; } + if (a == 0.0 and !neg_zero(a)) { return b; } + return a; +} + +/// Math.round: the nearest integer, halves toward +infinity. +pub fn jsround(x: f64) -> f64 { + const r : f64 = @floor(x); + if (x - r >= 0.5) { + if (x < 0.0 and r + 1.0 == 0.0) { + var m1 : f64 = 0.0 - 1.0; + return 0.0 * m1; + } + return r + 1.0; + } + return r; +} + +/// V8's Math.hypot(x, y). +pub fn hypot2(x: f64, y: f64) -> f64 { + const ax : f64 = @abs(x); + const ay : f64 = @abs(y); + var max : f64 = 0.0; + if (ax == ax and ax > max) { max = ax; } + if (ay == ay and ay > max) { max = ay; } + if (max == inf()) { return inf(); } + if (x != x or y != y) { return nan(); } + if (max == 0.0) { return 0.0; } + var sum : f64 = 0.0; + var comp : f64 = 0.0; + var k : u32 = 0; + while (k < 2) { + var v : f64 = ax; + if (k == 1) { v = ay; } + const q : f64 = v / max; + const summand : f64 = q * q - comp; + const prelim : f64 = sum + summand; + comp = (prelim - sum) - summand; + sum = prelim; + k = k + 1; + } + return @sqrt(sum) * max; +} + +/// The unit vector of (x, y) into V[0], V[1]: unit = (x, y) => { const l = Math.hypot(x, y) || 1; ... }. +pub fn unit_to(x: f64, y: f64) { + var l : f64 = hypot2(x, y); + if (!truthy(l)) { l = 1.0; } + V[0] = x / l; + V[1] = y / l; +} + +/// rot([x, y], a) into V[0], V[1], given cos(a) and sin(a) (the glue's Math.cos and Math.sin). +pub fn rot_to(x: f64, y: f64, cs: f64, sn: f64) { + V[0] = x * cs - y * sn; + V[1] = x * sn + y * cs; +} + +/// The angle of the k-th state on the ellipse of n states: -Math.PI / 2 + (2 * Math.PI * k) / n. +pub fn node_angle(k: usize, n: usize) -> f64 { + return (0.0 - PI) / 2.0 + (2.0 * PI * (k as f64)) / (n as f64); +} + +/// Where a ray from a box's centre in direction (dx, dy) leaves the box, into V[0], V[1]. +pub fn box_exit(nx: f64, ny: f64, nw: f64, nh: f64, dx: f64, dy: f64, pad: f64) { + const hw : f64 = nw / 2.0 + pad; + const hh : f64 = nh / 2.0 + pad; + var a : f64 = inf(); + if (truthy(dx)) { a = hw / @abs(dx); } + var b : f64 = inf(); + if (truthy(dy)) { b = hh / @abs(dy); } + const t : f64 = jsmin(a, b); + V[0] = nx + dx * t; + V[1] = ny + dy * t; +} + +/// parseInt(code, 2) of a code of 0 and 1 of at most 52 digits; -1 when it holds anything else. +pub fn code_value(code: []const u8) -> i64 { + if (code.len == 0 or code.len > 52) { return -1; } + var v : i64 = 0; + var i : usize = 0; + while (i < code.len) { + const c : u8 = code[i]; + if (c != 48 and c != 49) { return -1; } + v = v * 2 + ((c - 48) as i64); + i = i + 1; + } + return v; +} + +/// conditionOf: for every character of the pattern that is not "-", its input index (highest first as +/// yosys prints) in out_idx and the character in out_bit; returns how many. +pub fn condition_of(pattern: []const u8, out_idx: []u32, out_bit: []u8) -> usize { + const n : usize = pattern.len; + var cnt : usize = 0; + var j : usize = 0; + while (j < n) { + const c : u8 = pattern[j]; + if (c != 45) { + if (cnt < out_idx.len and cnt < out_bit.len) { + out_idx[cnt] = (n - 1 - j) as u32; + out_bit[cnt] = c; + } + cnt = cnt + 1; + } + j = j + 1; + } + return cnt; +} + +/// countsOf: out = [states, transitions, inputs, outputs, bits]. +pub fn counts_of(n_states: u32, n_rows: u32, n_in: f64, n_out: f64, n_bits: f64, out: []f64) { + out[0] = n_states as f64; + out[1] = n_rows as f64; + out[2] = n_in; + out[3] = n_out; + out[4] = n_bits; +} + +// ------------------------------------------------------------------------------------------------ +// walkOrder +// ------------------------------------------------------------------------------------------------ + +/// States in the order a reader follows them: breadth-first from reset (or 0), then any left over. +/// order gets n states; work needs 2 * n + nrows + 2 words. Rows whose ends are not below n are ignored. +/// Returns n. +pub fn walk_order(n: usize, reset: i64, nrows: usize, rows_from: []const u32, rows_to: []const u32, order: []u32, work: []u32) -> usize { + // work[0 .. n] offsets, work[n + 1 .. n + 1 + nrows] neighbours, work[n + 1 + nrows .. 2n + 1 + nrows] seen + const adj : usize = n + 1; + const seen : usize = n + 1 + nrows; + var i : usize = 0; + while (i <= n) { + work[i] = 0; + i = i + 1; + } + i = 0; + while (i < n) { + work[seen + i] = 0; + i = i + 1; + } + var r : usize = 0; + while (r < nrows) { + const f : usize = rows_from[r] as usize; + const t : usize = rows_to[r] as usize; + if (f < n and t < n and f != t) { work[f + 1] = work[f + 1] + 1; } + r = r + 1; + } + i = 0; + while (i < n) { + work[i + 1] = work[i + 1] + work[i]; + i = i + 1; + } + // fill with a cursor kept in order[] (free until the walk starts) + i = 0; + while (i < n) { + order[i] = work[i]; + i = i + 1; + } + r = 0; + while (r < nrows) { + const f : usize = rows_from[r] as usize; + const t : usize = rows_to[r] as usize; + if (f < n and t < n and f != t) { + work[adj + (order[f] as usize)] = t as u32; + order[f] = order[f] + 1; + } + r = r + 1; + } + // sort each neighbour list ascending + i = 0; + while (i < n) { + const lo : usize = work[i] as usize; + const hi : usize = work[i + 1] as usize; + var a : usize = lo + 1; + while (a < hi) { + const v : u32 = work[adj + a]; + var b : usize = a; + while (b > lo and work[adj + b - 1] > v) { + work[adj + b] = work[adj + b - 1]; + b = b - 1; + } + work[adj + b] = v; + a = a + 1; + } + i = i + 1; + } + var start : usize = 0; + if (reset >= 0) { start = reset as usize; } + var tail : usize = 0; + var head : usize = 0; + var s : usize = 0; + var pass : usize = 0; + // the roots are start, then 0 .. n - 1 + while (pass <= n) { + var root : usize = start; + if (pass > 0) { root = pass - 1; } + pass = pass + 1; + if (root < n and work[seen + root] == 0) { + work[seen + root] = 1; + order[tail] = root as u32; + tail = tail + 1; + while (head < tail) { + const x : usize = order[head] as usize; + head = head + 1; + var q : usize = work[x] as usize; + const qe : usize = work[x + 1] as usize; + while (q < qe) { + const y : usize = work[adj + q] as usize; + if (work[seen + y] == 0) { + work[seen + y] = 1; + order[tail] = y as u32; + tail = tail + 1; + } + q = q + 1; + } + } + } + } + s = tail; + return n; +} + +// ------------------------------------------------------------------------------------------------ +// layoutFsm +// ------------------------------------------------------------------------------------------------ + +/// The box a label covers, into V[0 .. 3] = x0, y0, x1, y1. anchor 0 middle 1 start 2 end; valign 0 middle +/// 1 bottom 2 top; labmax is the longest line length (at least 1, or the "more" text length + 6); shown and +/// more as in the label. +pub fn label_box(lx: f64, ly: f64, anchor: u32, valign: u32, labmax: u32, shown: u32, more: u32, edge_font: f64, edge_char_w: f64) { + const lh : f64 = edge_font * 1.15; + const bw : f64 = (labmax as f64) * edge_char_w; + var extra : f64 = 0.0; + if (more != 0) { extra = 1.0; } + const bh : f64 = ((shown as f64) + extra) * lh; + var x0 : f64 = lx - bw / 2.0; + if (anchor == 1) { x0 = lx; } + if (anchor == 2) { x0 = lx - bw; } + var y0 : f64 = ly - bh / 2.0; + if (valign == 1) { y0 = ly - bh; } + if (valign == 2) { y0 = ly; } + V[0] = x0; + V[1] = y0; + V[2] = x0 + bw; + V[3] = y0 + bh; +} + +/// overlap of two boxes (x0, y0, x1, y1). +pub fn overlap(px0: f64, py0: f64, px1: f64, py1: f64, qx0: f64, qy0: f64, qx1: f64, qy1: f64) -> f64 { + const w : f64 = jsmax(0.0, jsmin(px1, qx1) - jsmax(px0, qx0)); + const h : f64 = jsmax(0.0, jsmin(py1, qy1) - jsmax(py0, qy0)); + return w * h; +} + +pub fn digits_of(v: u32) -> u32 { + var n : u32 = 1; + var x : u32 = v; + while (x >= 10) { + x = x / 10; + n = n + 1; + } + return n; +} + +pub fn anchor_of(nx: f64) -> u32 { + if (@abs(nx) < 0.35) { return 0; } + if (nx > 0.0) { return 1; } + return 2; +} + +pub fn valign_of(ny: f64) -> u32 { + if (ny < -0.35) { return 1; } + if (ny > 0.35) { return 2; } + return 0; +} + +/// layoutFsm: node boxes and edge curves for one FSM in a w x h box. See the header of the file. +/// rows_from, rows_to, rows_inlen: nrows entries (every from and to below n). +/// label_n, label_maxlen: n entries. +/// nodes (f64, 4 per state): x, y, w, h. +/// ef (f64, 13 per edge): p0 x y, c1 (the control point of a quadratic) x y, c2 x y (self loops only; 0 +/// otherwise), p1 x y, arrow x y angle, label x y. For a quadratic the points are p0, c, p1 and the +/// entries 4 to 7 hold 0, 0, then p1: [p0, c, 0, 0, p1]. Self loops: [p0, c1, c2, p1]. +/// eu (u32, 10 per edge): from, to, self, rows, lines shown, more, anchor, valign, longest label line, +/// unused. The shown lines are the first rows of the pair, in row order. +/// order: n words (the walk order), work: u32 of 2n + nrows + 2 + 2nrows + 4, fw: f64 of 4 * (n + 2 * nrows) + 8. +/// Returns the number of edges. +pub fn layout_fsm(n: usize, reset: i64, nrows: usize, rows_from: []const u32, rows_to: []const u32, rows_inlen: []const u32, label_n: []const u32, label_maxlen: []const u32, w: f64, h: f64, font: f64, charw: f64, edgefont: f64, maxrows: i64, nodes: []f64, ef: []f64, eu: []u32, order: []u32, work: []u32, fw: []f64, trig: []const f64) -> usize { + const edge_char_w : f64 = edgefont * (charw / font); + const line_h : f64 = jsround(font * 1.25); + // boxes + var maxw : f64 = 0.0; + var maxh : f64 = 0.0; + var i : usize = 0; + while (i < n) { + var tw : f64 = (0.0 - inf()) * charw; + if (label_n[i] > 0) { tw = (label_maxlen[i] as f64) * charw; } + const bw : f64 = jsmax(tw + font * 1.4, font * 3.2); + const bh : f64 = (label_n[i] as f64) * line_h + font * 0.9; + nodes[i * 4 + 2] = bw; + nodes[i * 4 + 3] = bh; + maxw = jsmax(maxw, bw); + maxh = jsmax(maxh, bh); + i = i + 1; + } + const loop_r : f64 = jsmax(maxh * 0.8, font * 2.2); + walk_order(n, reset, nrows, rows_from, rows_to, order, work); + // one edge per (from, to) pair, in order of first row + var ne : usize = 0; + var r : usize = 0; + while (r < nrows) { + var e : usize = 0; + var hit : bool = false; + while (e < ne and !hit) { + if (eu[e * 10] == rows_from[r] and eu[e * 10 + 1] == rows_to[r]) { hit = true; } else { e = e + 1; } + } + if (!hit) { + eu[ne * 10] = rows_from[r]; + eu[ne * 10 + 1] = rows_to[r]; + eu[ne * 10 + 3] = 0; + e = ne; + ne = ne + 1; + } + eu[e * 10 + 3] = eu[e * 10 + 3] + 1; + r = r + 1; + } + var e : usize = 0; + while (e < ne) { + const cntr : i64 = eu[e * 10 + 3] as i64; + var shown : i64 = cntr; + var more : i64 = 0; + if (cntr > maxrows) { + var keep : i64 = maxrows - 1; + if (keep < 1) { keep = 1; } + shown = keep; + more = cntr - keep; + } + eu[e * 10 + 2] = 0; + if (eu[e * 10] == eu[e * 10 + 1]) { eu[e * 10 + 2] = 1; } + eu[e * 10 + 4] = shown as u32; + eu[e * 10 + 5] = more as u32; + var lm : u32 = 1; + var seen : i64 = 0; + r = 0; + while (r < nrows and seen < shown) { + if (rows_from[r] == eu[e * 10] and rows_to[r] == eu[e * 10 + 1]) { + if (rows_inlen[r] > lm) { lm = rows_inlen[r]; } + seen = seen + 1; + } + r = r + 1; + } + if (more != 0) { + const mt : u32 = digits_of(more as u32) + 6; + if (mt > lm) { lm = mt; } + } + eu[e * 10 + 8] = lm; + eu[e * 10 + 9] = 0; + e = e + 1; + } + // the ellipse, shrunk until everything fits + const fx0 : f64 = 4.0; + const fy0 : f64 = 4.0; + const fx1 : f64 = w - 4.0; + const fy1 : f64 = h - 4.0; + var rx : f64 = jsmax(0.0, w / 2.0 - maxw / 2.0 - 6.0); + var ry : f64 = jsmax(0.0, h / 2.0 - maxh / 2.0 - 6.0); + var pass : u32 = 0; + var done : bool = false; + while (pass < 12 and !done) { + place(n, ne, rx, ry, w, h, edgefont, edge_char_w, loop_r, nodes, ef, eu, order, fw, trig); + // bounds + var bx0 : f64 = inf(); + var by0 : f64 = inf(); + var bx1 : f64 = 0.0 - inf(); + var by1 : f64 = 0.0 - inf(); + i = 0; + while (i < n) { + const x : f64 = nodes[i * 4]; + const y : f64 = nodes[i * 4 + 1]; + const nw : f64 = nodes[i * 4 + 2]; + const nh : f64 = nodes[i * 4 + 3]; + bx0 = jsmin(bx0, x - nw / 2.0 - 6.0); + by0 = jsmin(by0, y - nh / 2.0 - 6.0); + bx1 = jsmax(bx1, x + nw / 2.0 + 6.0); + by1 = jsmax(by1, y + nh / 2.0 + 6.0); + i = i + 1; + } + e = 0; + while (e < ne) { + label_box(ef[e * 13 + 11], ef[e * 13 + 12], eu[e * 10 + 6], eu[e * 10 + 7], eu[e * 10 + 8], eu[e * 10 + 4], eu[e * 10 + 5], edgefont, edge_char_w); + bx0 = jsmin(bx0, V[0]); + by0 = jsmin(by0, V[1]); + bx1 = jsmax(bx1, V[2]); + by1 = jsmax(by1, V[3]); + if (eu[e * 10 + 2] == 1) { + var s : u32 = 0; + while (s < 3) { + var t : f64 = 0.25; + if (s == 1) { t = 0.5; } + if (s == 2) { t = 0.75; } + const u1 : f64 = 1.0 - t; + const k0 : f64 = u1 * u1 * u1; + const k1 : f64 = 3.0 * (u1 * u1) * t; + const k2 : f64 = 3.0 * u1 * t * t; + const k3 : f64 = t * t * t; + const x : f64 = k0 * ef[e * 13] + k1 * ef[e * 13 + 2] + k2 * ef[e * 13 + 4] + k3 * ef[e * 13 + 6]; + const y : f64 = k0 * ef[e * 13 + 1] + k1 * ef[e * 13 + 3] + k2 * ef[e * 13 + 5] + k3 * ef[e * 13 + 7]; + bx0 = jsmin(bx0, x - 2.0); + by0 = jsmin(by0, y - 2.0); + bx1 = jsmax(bx1, x + 2.0); + by1 = jsmax(by1, y + 2.0); + s = s + 1; + } + } + e = e + 1; + } + const ox : f64 = jsmax(jsmax(0.0, fx0 - bx0), bx1 - fx1); + const oy : f64 = jsmax(jsmax(0.0, fy0 - by0), by1 - fy1); + if (ox < 1.0 and oy < 1.0) { + done = true; + } else { + rx = jsmax(0.0, rx - ox - 1.0); + ry = jsmax(0.0, ry - oy - 1.0); + } + pass = pass + 1; + } + return ne; +} + +/// place(rx, ry): the node centres on the ellipse, the edge curves, then the edge labels. +pub fn place(n: usize, ne: usize, rx: f64, ry: f64, w: f64, h: f64, edgefont: f64, edge_char_w: f64, loop_r: f64, nodes: []f64, ef: []f64, eu: []u32, order: []u32, fw: []f64, trig: []const f64) { + const cx : f64 = w / 2.0; + const cy : f64 = h / 2.0; + var i : usize = 0; + while (i < n) { + nodes[i * 4] = cx; + nodes[i * 4 + 1] = cy; + i = i + 1; + } + if (n != 1) { + var k : usize = 0; + while (k < n) { + const st : usize = order[k] as usize; + nodes[st * 4] = cx + rx * trig[k * 2]; + nodes[st * 4 + 1] = cy + ry * trig[k * 2 + 1]; + k = k + 1; + } + } + var e : usize = 0; + while (e < ne) { + const af : usize = eu[e * 10] as usize; + const bt : usize = eu[e * 10 + 1] as usize; + const ax : f64 = nodes[af * 4]; + const ay : f64 = nodes[af * 4 + 1]; + const aw : f64 = nodes[af * 4 + 2]; + const ah : f64 = nodes[af * 4 + 3]; + const o : usize = e * 13; + if (eu[e * 10 + 2] == 1) { + var ux : f64 = 0.0; + var uy : f64 = 0.0 - 1.0; + if (n != 1) { + unit_to(ax - cx, ay - cy); + ux = V[0]; + uy = V[1]; + } + rot_to(ux, uy, trig[2 * n], trig[2 * n + 1]); + box_exit(ax, ay, aw, ah, V[0], V[1], 0.0); + const p0x : f64 = V[0]; + const p0y : f64 = V[1]; + rot_to(ux, uy, trig[2 * n + 2], trig[2 * n + 3]); + box_exit(ax, ay, aw, ah, V[0], V[1], 0.0); + const p1x : f64 = V[0]; + const p1y : f64 = V[1]; + rot_to(ux, uy, trig[2 * n + 4], trig[2 * n + 5]); + const c1x : f64 = ax + V[0] * (loop_r * 1.6 + aw / 5.0); + const c1y : f64 = ay + V[1] * (loop_r * 1.6 + ah / 3.0); + rot_to(ux, uy, trig[2 * n + 6], trig[2 * n + 7]); + const c2x : f64 = ax + V[0] * (loop_r * 1.6 + aw / 5.0); + const c2y : f64 = ay + V[1] * (loop_r * 1.6 + ah / 3.0); + const apx : f64 = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x; + const apy : f64 = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y; + ef[o] = p0x; + ef[o + 1] = p0y; + ef[o + 2] = c1x; + ef[o + 3] = c1y; + ef[o + 4] = c2x; + ef[o + 5] = c2y; + ef[o + 6] = p1x; + ef[o + 7] = p1y; + ef[o + 8] = p1x; + ef[o + 9] = p1y; + ef[o + 10] = 0.0; + ef[o + 11] = apx + ux * edgefont * 0.5; + ef[o + 12] = apy + uy * edgefont * 0.5; + eu[e * 10 + 6] = anchor_of(ux); + eu[e * 10 + 7] = valign_of(uy); + } else { + const bx : f64 = nodes[bt * 4]; + const by : f64 = nodes[bt * 4 + 1]; + const bw : f64 = nodes[bt * 4 + 2]; + const bh : f64 = nodes[bt * 4 + 3]; + const dx : f64 = bx - ax; + const dy : f64 = by - ay; + var len : f64 = hypot2(dx, dy); + if (!truthy(len)) { len = 1.0; } + const nx : f64 = -dy / len; + const ny : f64 = dx / len; + var back : bool = false; + var q : usize = 0; + while (q < ne and !back) { + if (eu[q * 10] == eu[e * 10 + 1] and eu[q * 10 + 1] == eu[e * 10]) { back = true; } + q = q + 1; + } + var bend : f64 = len * 0.1; + if (back) { bend = len * 0.2; } + const cxx : f64 = (ax + bx) / 2.0 + nx * bend; + const cyy : f64 = (ay + by) / 2.0 + ny * bend; + unit_to(cxx - ax, cyy - ay); + box_exit(ax, ay, aw, ah, V[0], V[1], 0.0); + const p0x : f64 = V[0]; + const p0y : f64 = V[1]; + unit_to(cxx - bx, cyy - by); + box_exit(bx, by, bw, bh, V[0], V[1], 2.0); + const p1x : f64 = V[0]; + const p1y : f64 = V[1]; + ef[o] = p0x; + ef[o + 1] = p0y; + ef[o + 2] = cxx; + ef[o + 3] = cyy; + ef[o + 4] = 0.0; + ef[o + 5] = 0.0; + ef[o + 6] = p1x; + ef[o + 7] = p1y; + ef[o + 8] = p1x; + ef[o + 9] = p1y; + ef[o + 10] = 0.0; + } + e = e + 1; + } + place_labels(n, ne, w, h, edgefont, edge_char_w, nodes, ef, eu, fw); +} + +/// placeLabels: self-loop labels stay at the loop's apex; every other label tries points along its curve +/// on either side and takes the cheapest. fw holds the boxes already taken, 4 f64 each. +pub fn place_labels(n: usize, ne: usize, w: f64, h: f64, edgefont: f64, edge_char_w: f64, nodes: []f64, ef: []f64, eu: []u32, fw: []f64) { + var np : usize = 0; + var i : usize = 0; + while (i < n) { + const x : f64 = nodes[i * 4]; + const y : f64 = nodes[i * 4 + 1]; + const nw : f64 = nodes[i * 4 + 2]; + const nh : f64 = nodes[i * 4 + 3]; + fw[np * 4] = x - nw / 2.0 - 3.0; + fw[np * 4 + 1] = y - nh / 2.0 - 3.0; + fw[np * 4 + 2] = x + nw / 2.0 + 3.0; + fw[np * 4 + 3] = y + nh / 2.0 + 3.0; + np = np + 1; + i = i + 1; + } + var e : usize = 0; + while (e < ne) { + if (eu[e * 10 + 2] == 1) { + label_box(ef[e * 13 + 11], ef[e * 13 + 12], eu[e * 10 + 6], eu[e * 10 + 7], eu[e * 10 + 8], eu[e * 10 + 4], eu[e * 10 + 5], edgefont, edge_char_w); + fw[np * 4] = V[0]; + fw[np * 4 + 1] = V[1]; + fw[np * 4 + 2] = V[2]; + fw[np * 4 + 3] = V[3]; + np = np + 1; + } + e = e + 1; + } + const gx0 : f64 = 2.0; + const gy0 : f64 = 2.0; + const gx1 : f64 = w - 2.0; + const gy1 : f64 = h - 2.0; + e = 0; + while (e < ne) { + if (eu[e * 10 + 2] == 0) { + const o : usize = e * 13; + const p0x : f64 = ef[o]; + const p0y : f64 = ef[o + 1]; + const cx : f64 = ef[o + 2]; + const cy : f64 = ef[o + 3]; + const p1x : f64 = ef[o + 6]; + const p1y : f64 = ef[o + 7]; + var have : bool = false; + var best_cost : f64 = 0.0; + var best_x : f64 = 0.0; + var best_y : f64 = 0.0; + var best_anchor : u32 = 0; + var best_valign : u32 = 0; + var bb0 : f64 = 0.0; + var bb1 : f64 = 0.0; + var bb2 : f64 = 0.0; + var bb3 : f64 = 0.0; + var ti : u32 = 0; + var stop : bool = false; + while (ti < 7 and !stop) { + var t : f64 = 0.5; + if (ti == 1) { t = 0.4; } + if (ti == 2) { t = 0.6; } + if (ti == 3) { t = 0.3; } + if (ti == 4) { t = 0.7; } + if (ti == 5) { t = 0.22; } + if (ti == 6) { t = 0.78; } + const om : f64 = 1.0 - t; + const ptx : f64 = om * om * p0x + 2.0 * om * t * cx + t * t * p1x; + const pty : f64 = om * om * p0y + 2.0 * om * t * cy + t * t * p1y; + unit_to(2.0 * om * (cx - p0x) + 2.0 * t * (p1x - cx), 2.0 * om * (cy - p0y) + 2.0 * t * (p1y - cy)); + const tanx : f64 = V[0]; + const tany : f64 = V[1]; + var si : u32 = 0; + while (si < 2) { + var side : f64 = 1.0; + if (si == 1) { side = 0.0 - 1.0; } + const nrx : f64 = -tany * side; + const nry : f64 = tanx * side; + const lx : f64 = ptx + nrx * edgefont * 0.5; + const ly : f64 = pty + nry * edgefont * 0.5; + const anc : u32 = anchor_of(nrx); + const val : u32 = valign_of(nry); + label_box(lx, ly, anc, val, eu[e * 10 + 8], eu[e * 10 + 4], eu[e * 10 + 5], edgefont, edge_char_w); + const x0 : f64 = V[0]; + const y0 : f64 = V[1]; + const x1 : f64 = V[2]; + const y1 : f64 = V[3]; + const out : f64 = (x1 - x0) * (y1 - y0) - overlap(x0, y0, x1, y1, gx0, gy0, gx1, gy1); + var sum : f64 = 0.0; + var q : usize = 0; + while (q < np) { + sum = sum + overlap(x0, y0, x1, y1, fw[q * 4], fw[q * 4 + 1], fw[q * 4 + 2], fw[q * 4 + 3]); + q = q + 1; + } + const cost : f64 = sum + out * 4.0 + @abs(t - 0.5) * 4.0; + if (!have or cost < best_cost) { + have = true; + best_cost = cost; + best_x = lx; + best_y = ly; + best_anchor = anc; + best_valign = val; + bb0 = x0; + bb1 = y0; + bb2 = x1; + bb3 = y1; + } + si = si + 1; + } + if (have and best_cost < 1.0) { stop = true; } + ti = ti + 1; + } + ef[o + 11] = best_x; + ef[o + 12] = best_y; + eu[e * 10 + 6] = best_anchor; + eu[e * 10 + 7] = best_valign; + fw[np * 4] = bb0; + fw[np * 4 + 1] = bb1; + fw[np * 4 + 2] = bb2; + fw[np * 4 + 3] = bb3; + np = np + 1; + } + e = e + 1; + } +} + +// ------------------------------------------------------------------------------------------------ +// text helpers +// ------------------------------------------------------------------------------------------------ + +pub const EV : usize = 10; + +pub const EV_FOUND : u32 = 1; +pub const EV_REJ : u32 = 2; +pub const EV_REASON : u32 = 3; +pub const EV_RECODE : u32 = 4; +pub const EV_FSM : u32 = 5; +pub const EV_NUM : u32 = 6; +pub const EV_IN : u32 = 7; +pub const EV_OUT : u32 = 8; +pub const EV_STATE : u32 = 9; +pub const EV_ROW : u32 = 10; +pub const EV_RESET : u32 = 11; +pub const EV_NAME : u32 = 20; +pub const EV_ROW2 : u32 = 21; +pub const EV_ILB : u32 = 22; +pub const EV_OB : u32 = 23; + +pub const N_KISS2_S : u32 = 0; +pub const N_KISS2_P : u32 = 1; +pub const N_NO_RESET_NAMED : u32 = 2; +pub const N_NO_RESET : u32 = 3; +pub const N_WIDTH_IN : u32 = 4; +pub const N_RECODED : u32 = 5; +pub const N_NO_INFO : u32 = 6; + +pub const E_UNKNOWN : u32 = 1; +pub const E_KISS2ROW : u32 = 2; +pub const E_NOFSMPASS : u32 = 3; + +pub var M : [8]usize = [0; 8]; +pub var NUMFLAG : u32 = 0; +pub var PEND : [2]usize = [0; 2]; + +pub fn is_ws(b: u8) -> bool { + return b == 32 or (b >= 9 and b <= 13); +} + +pub fn is_digit(b: u8) -> bool { + return b >= 48 and b <= 57; +} + +pub fn match_in(s: []const u8, p: usize, hi: usize, w: []const u8) -> bool { + if (p + w.len > hi) { return false; } + var k : usize = 0; + while (k < w.len) { + if (s[p + k] != w[k]) { return false; } + k = k + 1; + } + return true; +} + +/// p + the length of lit when lit sits at s[p ..] inside s[.. hi], else -1. +pub fn eat(s: []const u8, p: usize, hi: usize, lit: []const u8) -> i64 { + if (match_in(s, p, hi, lit)) { return (p + lit.len) as i64; } + return -1; +} + +pub fn span_eq(a: []const u8, alo: usize, ahi: usize, blo: usize, bhi: usize) -> bool { + if (ahi - alo != bhi - blo) { return false; } + var k : usize = 0; + const n : usize = ahi - alo; + while (k < n) { + if (a[alo + k] != a[blo + k]) { return false; } + k = k + 1; + } + return true; +} + +pub fn skip_ws(s: []const u8, p: usize, hi: usize) -> usize { + var q : usize = p; + while (q < hi and is_ws(s[q])) { q = q + 1; } + return q; +} + +pub fn skip_digits(s: []const u8, p: usize, hi: usize) -> usize { + var q : usize = p; + while (q < hi and is_digit(s[q])) { q = q + 1; } + return q; +} + +/// The end of the non-space run that starts at p. +pub fn tok_end(s: []const u8, p: usize, hi: usize) -> usize { + var q : usize = p; + while (q < hi and !is_ws(s[q])) { q = q + 1; } + return q; +} + +/// The end of the run of bytes other than ' that starts at p. +pub fn run_noquote(s: []const u8, p: usize, hi: usize) -> usize { + var q : usize = p; + while (q < hi and s[q] != 39) { q = q + 1; } + return q; +} + +/// The end of the run of [01\-xz] that starts at p. +pub fn run_code(s: []const u8, p: usize, hi: usize) -> usize { + var q : usize = p; + while (q < hi and (s[q] == 48 or s[q] == 49 or s[q] == 45 or s[q] == 120 or s[q] == 122)) { q = q + 1; } + return q; +} + +pub fn rstrip(s: []const u8, lo: usize, hi: usize) -> usize { + var e : usize = hi; + while (e > lo and is_ws(s[e - 1])) { e = e - 1; } + return e; +} + +/// Does s[lo .. hi] hold no carriage return? (JavaScript's . does not match one.) +pub fn no_cr(s: []const u8, lo: usize, hi: usize) -> bool { + var i : usize = lo; + while (i < hi) { + if (s[i] == 13 or s[i] == 10) { return false; } + i = i + 1; + } + return true; +} + +pub fn has_sub(s: []const u8, w: []const u8) -> bool { + var p : usize = 0; + while (p + w.len <= s.len) { + if (match_in(s, p, s.len, w)) { return true; } + p = p + 1; + } + return false; +} + +/// Number(digits) for a run of ASCII digits, saturating at 2147483647 and flagging NUMFLAG = 5 above it. +pub fn dig_val(s: []const u8, lo: usize, hi: usize) -> u32 { + var v : u64 = 0; + var i : usize = lo; + while (i < hi) { + v = v * 10 + ((s[i] - 48) as u64); + if (v > 2147483647) { + NUMFLAG = 5; + return 2147483647; + } + i = i + 1; + } + return v as u32; +} + +/// text.split(/\r?\n/): ls gets (start, end) for every line; returns how many (0 when ls is too short). +pub fn split_nl(text: []const u8, ls: []u32) -> usize { + if (ls.len < 2 * (text.len + 1)) { return 0; } + var n : usize = 0; + var start : usize = 0; + var i : usize = 0; + while (i < text.len) { + if (text[i] == 10) { + var e : usize = i; + if (e > start and text[e - 1] == 13) { e = e - 1; } + ls[n * 2] = start as u32; + ls[n * 2 + 1] = e as u32; + n = n + 1; + start = i + 1; + } + i = i + 1; + } + ls[n * 2] = start as u32; + ls[n * 2 + 1] = text.len as u32; + return n + 1; +} + +// ------------------------------------------------------------------------------------------------ +// Number() of a header token, and stateNum +// ------------------------------------------------------------------------------------------------ + +pub fn hex_val(b: u8) -> i64 { + if (b >= 48 and b <= 57) { return (b - 48) as i64; } + if (b >= 97 and b <= 102) { return (b - 87) as i64; } + if (b >= 65 and b <= 70) { return (b - 55) as i64; } + return -1; +} + +/// JavaScript's Number(token) for a token with no white space. A form outside the claimed domain sets +/// NUMFLAG = 5 (see the header). +pub fn js_number(s: []const u8, lo: usize, hi: usize) -> f64 { + var i : usize = lo; + var neg : bool = false; + if (i >= hi) { return 0.0; } + if (hi - lo >= 3 and s[lo] == 48 and (s[lo + 1] == 120 or s[lo + 1] == 88 or s[lo + 1] == 111 or s[lo + 1] == 79 or s[lo + 1] == 98 or s[lo + 1] == 66)) { + var radix : f64 = 16.0; + if (s[lo + 1] == 111 or s[lo + 1] == 79) { radix = 8.0; } + if (s[lo + 1] == 98 or s[lo + 1] == 66) { radix = 2.0; } + var v : f64 = 0.0; + var k : usize = lo + 2; + while (k < hi) { + const d : i64 = hex_val(s[k]); + if (d < 0 or (d as f64) >= radix) { return nan(); } + v = v * radix + (d as f64); + k = k + 1; + } + if (hi - lo - 2 > 13 and radix == 16.0) { NUMFLAG = 5; } + if (hi - lo - 2 > 17 and radix == 8.0) { NUMFLAG = 5; } + if (hi - lo - 2 > 52 and radix == 2.0) { NUMFLAG = 5; } + return v; + } + if (s[i] == 43 or s[i] == 45) { + neg = s[i] == 45; + i = i + 1; + } + if (match_in(s, i, hi, "Infinity") and hi - i == 8) { + if (neg) { return 0.0 - inf(); } + return inf(); + } + var mant : f64 = 0.0; + var sig : u32 = 0; + var any : bool = false; + var lead : bool = true; + var frac : i64 = 0; + while (i < hi and is_digit(s[i])) { + any = true; + if (s[i] != 48 or !lead) { + lead = false; + sig = sig + 1; + } + mant = mant * 10.0 + ((s[i] - 48) as f64); + i = i + 1; + } + if (i < hi and s[i] == 46) { + i = i + 1; + while (i < hi and is_digit(s[i])) { + any = true; + if (s[i] != 48 or !lead) { + lead = false; + sig = sig + 1; + } + mant = mant * 10.0 + ((s[i] - 48) as f64); + frac = frac + 1; + i = i + 1; + } + } + if (!any) { return nan(); } + var e10 : i64 = 0; + if (i < hi and (s[i] == 101 or s[i] == 69)) { + i = i + 1; + var eneg : bool = false; + if (i < hi and (s[i] == 43 or s[i] == 45)) { + eneg = s[i] == 45; + i = i + 1; + } + const d0 : usize = i; + var ev : i64 = 0; + while (i < hi and is_digit(s[i])) { + if (ev < 100000) { ev = ev * 10 + ((s[i] - 48) as i64); } + i = i + 1; + } + if (i == d0) { return nan(); } + e10 = ev; + if (eneg) { e10 = 0 - ev; } + } + if (i != hi) { return nan(); } + var result : f64 = 0.0; + if (mant != 0.0) { + if (sig > 15) { NUMFLAG = 5; } + const k10 : i64 = e10 - frac; + var p10 : f64 = 1.0; + var q : i64 = 0; + var ak : i64 = k10; + if (k10 < 0) { ak = 0 - k10; } + if (ak > 22) { NUMFLAG = 5; } + while (q < ak and q < 22) { + p10 = p10 * 10.0; + q = q + 1; + } + if (k10 >= 0) { result = mant * p10; } else { result = mant / p10; } + } + if (neg) { return -result; } + return result; +} + +/// stateNum: the number of a name of the form s, or -1 (JavaScript's NaN) when it has another shape. +pub fn state_num(s: []const u8, lo: usize, hi: usize) -> i64 { + if (hi - lo < 2 or s[lo] != 115) { return -1; } + var v : i64 = 0; + var i : usize = lo + 1; + while (i < hi) { + if (!is_digit(s[i])) { return -1; } + v = v * 10 + ((s[i] - 48) as i64); + if (v > 1000000000000000) { + NUMFLAG = 5; + return 1000000000000000; + } + i = i + 1; + } + return v; +} + +// ------------------------------------------------------------------------------------------------ +// sniff +// ------------------------------------------------------------------------------------------------ + +pub fn is_nl(b: u8) -> bool { + return b == 10 or b == 13; +} + +/// After the end of the second token of a row: (\s+[01\-]*)?\s*$ with $ at a line end. +pub fn tail_ok(s: []const u8, e2: usize) -> bool { + var j : usize = e2; + var go : bool = true; + while (go) { + if (j >= s.len) { return true; } + if (is_nl(s[j])) { return true; } + if (is_ws(s[j])) { j = j + 1; } else { go = false; } + } + if (j == e2) { return false; } + var k : usize = j; + while (k < s.len and (s[k] == 48 or s[k] == 49 or s[k] == 45)) { k = k + 1; } + if (k < s.len and !is_ws(s[k])) { return false; } + var m : usize = k; + go = true; + while (go) { + if (m >= s.len) { return true; } + if (is_nl(s[m])) { return true; } + if (is_ws(s[m])) { m = m + 1; } else { go = false; } + } + return false; +} + +/// /^\s*[01\-]*\s+\S+\s+\S+(\s+[01\-]*)?\s*$/m anywhere in the text. +pub fn kiss_row_anywhere(s: []const u8) -> bool { + var p : usize = 0; + while (p <= s.len) { + if (p == 0 or is_nl(s[p - 1])) { + const q0 : usize = skip_ws(s, p, s.len); + if (q0 < s.len) { + const e0 : usize = tok_end(s, q0, s.len); + var all01 : bool = true; + var c : usize = q0; + while (c < e0) { + if (s[c] != 48 and s[c] != 49 and s[c] != 45) { all01 = false; } + c = c + 1; + } + if (all01) { + const p1 : usize = skip_ws(s, e0, s.len); + if (p1 > e0 and p1 < s.len) { + const e1 : usize = tok_end(s, p1, s.len); + const p2 : usize = skip_ws(s, e1, s.len); + if (p2 > e1 and p2 < s.len) { + const e2 : usize = tok_end(s, p2, s.len); + if (tail_ok(s, e2)) { return true; } + } + } + } + if (q0 > p) { + const p2 : usize = skip_ws(s, e0, s.len); + if (p2 > e0 and p2 < s.len) { + const e2 : usize = tok_end(s, p2, s.len); + if (tail_ok(s, e2)) { return true; } + } + } + } + } + p = p + 1; + } + return false; +} + +/// /^\s*\.(i|s|p|r)\s+\S+/m anywhere in the text. +pub fn kiss_head_anywhere(s: []const u8) -> bool { + var q : usize = 0; + while (q + 2 < s.len) { + if (s[q] == 46 and (s[q + 1] == 105 or s[q + 1] == 115 or s[q + 1] == 112 or s[q + 1] == 114) and is_ws(s[q + 2])) { + const after : usize = skip_ws(s, q + 2, s.len); + if (after < s.len) { + var b : usize = q; + while (b > 0 and is_ws(s[b - 1])) { b = b - 1; } + var ok : bool = b == 0; + var j : usize = b; + while (j < q and !ok) { + if (is_nl(s[j])) { ok = true; } + j = j + 1; + } + if (ok) { return true; } + } + } + q = q + 1; + } + return false; +} + +/// sniff: 1 a yosys log, 2 a KISS2 file, 0 neither. +pub fn sniff(text: []const u8) -> u8 { + if (has_sub(text, "Executing FSM_DETECT pass") or has_sub(text, "Information on FSM ") or has_sub(text, "FSM state register")) { return 1; } + if (kiss_head_anywhere(text) and kiss_row_anywhere(text)) { return 2; } + return 0; +} + +// ------------------------------------------------------------------------------------------------ +// the result stream +// ------------------------------------------------------------------------------------------------ +// res (u32 x 32): [0] status, [1] ok, [2] kind (0 none, 1 log, 2 kiss2), [3] error id, [4] error line +// offset, [5] its length, [6] version offset (4294967295 none), [7] its length, [8] detectRan (0, 1; +// 2 for a KISS2 file), [9] fsms, [10] found, [11] rejected, [12] notes, [13] words written to out, +// [16] events used (internal), [17] nslots (internal). +// out (u32), in this order: +// found 2 words each: offset, length +// rejected offset, length, number of reasons, then 2 words per reason +// notes 5 words each: id, a0, a1, a2, 0 -- kiss2_s and kiss2_p: the header value (offset, length) +// and the count; no_reset_named: the name (offset, length); no_info: found, fsms; width_in: +// nums[0] holds the number +// fsms a 16-word header per FSM, then its states (4 words each), rows (6 words each), input +// entries (3 words each), output entries (3 words each): +// header [0] id offset, [1] id length (id offset 4294967295: the id is "kiss2"), [2] module offset +// (4294967295: null), [3] its length, [4] reg offset (4294967295: null), [5] its length, +// [6] reset (4294967295: -1), [7] encoding offset (4294967295: null), [8] its length, +// [9] states, [10] rows, [11] inputs array length (4294967295: null), [12] input entries, +// [13] outputs array length (4294967295: null), [14] output entries, [15] 1 when nBits is null +// state name offset (4294967295: the name is "s" and the index), name length, code offset +// (4294967295: null), code length +// row from, to, in offset, in length, out offset, out length +// entry index, offset, length (an index below the array length that has no entry is a hole) +// nums (f64): [0] the nIn of a width_in note; then 3 per FSM: nIn, nOut, nBits. +// Offsets are in text. + +pub fn ow(out: []u32, res: []u32, v: u32) { + const at : usize = res[13] as usize; + if (at < out.len) { + out[at] = v; + } else { + if (res[0] == 0) { res[0] = 1; } + } + res[13] = (at + 1) as u32; +} + +/// Write an event of 10 words; returns its index. +pub fn ev(work: []u32, res: []u32, tag: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32, a6: u32, a7: u32, a8: u32, a9: u32) -> usize { + const at : usize = res[16] as usize; + if ((at + 1) * EV > work.len) { + if (res[0] == 0) { res[0] = 2; } + return at; + } + work[at * EV] = tag; + work[at * EV + 1] = a1; + work[at * EV + 2] = a2; + work[at * EV + 3] = a3; + work[at * EV + 4] = a4; + work[at * EV + 5] = a5; + work[at * EV + 6] = a6; + work[at * EV + 7] = a7; + work[at * EV + 8] = a8; + work[at * EV + 9] = a9; + res[16] = (at + 1) as u32; + return at; +} + +pub fn clear_res(res: []u32, nums: []f64) { + var k : usize = 0; + while (k < 32) { + res[k] = 0; + k = k + 1; + } + res[6] = NONE; + res[8] = 2; + nums[0] = 0.0; + NUMFLAG = 0; +} + +// ------------------------------------------------------------------------------------------------ +// KISS2 +// ------------------------------------------------------------------------------------------------ + +/// Is the byte allowed in a KISS2 input or output pattern? +pub fn pat_ok(s: []const u8, lo: usize, hi: usize) -> bool { + var i : usize = lo; + while (i < hi) { + if (s[i] != 48 and s[i] != 49 and s[i] != 45) { return false; } + i = i + 1; + } + return true; +} + +/// Index of the name s[lo .. hi] in the NAME events (or a new one). +pub fn name_index(text: []const u8, work: []u32, res: []u32, lo: usize, hi: usize) -> u32 { + var idx : u32 = 0; + var k : usize = 0; + const n : usize = res[16] as usize; + while (k < n) { + if (work[k * EV] == EV_NAME) { + const o : usize = work[k * EV + 1] as usize; + if (span_eq(text, o, o + (work[k * EV + 2] as usize), lo, hi)) { return work[k * EV + 3]; } + idx = idx + 1; + } + k = k + 1; + } + ev(work, res, EV_NAME, lo as u32, (hi - lo) as u32, idx, 0, 0, 0, 0, 0, 0); + return idx; +} + +/// parseKiss2: see "the result stream". tmp needs 4 words per state name. +pub fn parse_kiss2(text: []const u8, ls: []u32, nlines: usize, work: []u32, tmp: []u32, out: []u32, nums: []f64, res: []u32) { + // head values: i o s p r, as (offset, length), offset NONE when undefined + var hoff : [5]u32 = [4294967295; 5]; + var hlen : [5]u32 = [0; 5]; + var ilb_gen : u32 = 0; + var ob_gen : u32 = 0; + var nrows : u32 = 0; + var stopped : bool = false; + var li : usize = 0; + while (li < nlines and !stopped and res[0] == 0) { + const rlo : usize = ls[li * 2] as usize; + const rhi : usize = ls[li * 2 + 1] as usize; + li = li + 1; + // strip the comment: the first # with no CR after it in the line + var last_cr : i64 = -1; + var c : usize = rlo; + while (c < rhi) { + if (text[c] == 13) { last_cr = c as i64; } + c = c + 1; + } + var cut : usize = rhi; + var found_hash : bool = false; + c = rlo; + while (c < rhi and !found_hash) { + if (text[c] == 35 and (c as i64) > last_cr) { + cut = c; + found_hash = true; + } + c = c + 1; + } + var a : usize = rlo; + var b : usize = cut; + while (a < b and is_ws(text[a])) { a = a + 1; } + while (b > a and is_ws(text[b - 1])) { b = b - 1; } + if (b == a) { continue; } + if (text[a] == 46) { + const kend : usize = tok_end(text, a, b); + if (match_in(text, a, kend, ".e") and kend - a == 2) { stopped = true; continue; } + if (match_in(text, a, kend, ".end") and kend - a == 4) { stopped = true; continue; } + if (match_in(text, a, kend, ".ilb") and kend - a == 4) { + ilb_gen = ilb_gen + 1; + var p : usize = skip_ws(text, kend, b); + while (p < b) { + const e : usize = tok_end(text, p, b); + ev(work, res, EV_ILB, ilb_gen, p as u32, (e - p) as u32, 0, 0, 0, 0, 0, 0); + p = skip_ws(text, e, b); + } + continue; + } + if (match_in(text, a, kend, ".ob") and kend - a == 3) { + ob_gen = ob_gen + 1; + var p : usize = skip_ws(text, kend, b); + while (p < b) { + const e : usize = tok_end(text, p, b); + ev(work, res, EV_OB, ob_gen, p as u32, (e - p) as u32, 0, 0, 0, 0, 0, 0); + p = skip_ws(text, e, b); + } + continue; + } + var key : i64 = -1; + if (kend - a == 2) { + if (text[a + 1] == 105) { key = 0; } + if (text[a + 1] == 111) { key = 1; } + if (text[a + 1] == 115) { key = 2; } + if (text[a + 1] == 112) { key = 3; } + if (text[a + 1] == 114) { key = 4; } + } + if (key >= 0) { + const p : usize = skip_ws(text, kend, b); + if (p < b) { + const e : usize = tok_end(text, p, b); + hoff[key as usize] = p as u32; + hlen[key as usize] = (e - p) as u32; + } else { + hoff[key as usize] = NONE; + hlen[key as usize] = 0; + } + } + continue; + } + // a transition row + var ts : [5]usize = [0; 5]; + var te : [5]usize = [0; 5]; + var ntok : usize = 0; + var p : usize = a; + while (p < b) { + const e : usize = tok_end(text, p, b); + if (ntok < 5) { + ts[ntok] = p; + te[ntok] = e; + } + ntok = ntok + 1; + p = skip_ws(text, e, b); + } + var nin_zero : bool = false; + var nout_zero : bool = false; + if (hoff[0] != NONE) { nin_zero = js_number(text, hoff[0] as usize, (hoff[0] + hlen[0]) as usize) == 0.0; } + if (hoff[1] != NONE) { nout_zero = js_number(text, hoff[1] as usize, (hoff[1] + hlen[1]) as usize) == 0.0; } + var io : usize = 0; + var il : usize = 0; + var fo : usize = 0; + var fl : usize = 0; + var to : usize = 0; + var tl : usize = 0; + var oo : usize = 0; + var ol : usize = 0; + var good : bool = true; + if (ntok == 4) { + io = ts[0]; il = te[0] - ts[0]; fo = ts[1]; fl = te[1] - ts[1]; to = ts[2]; tl = te[2] - ts[2]; oo = ts[3]; ol = te[3] - ts[3]; + } else { + if (ntok == 3 and nin_zero) { + fo = ts[0]; fl = te[0] - ts[0]; to = ts[1]; tl = te[1] - ts[1]; oo = ts[2]; ol = te[2] - ts[2]; + } else { + if (ntok == 3 and nout_zero) { + io = ts[0]; il = te[0] - ts[0]; fo = ts[1]; fl = te[1] - ts[1]; to = ts[2]; tl = te[2] - ts[2]; + } else { + good = false; + } + } + } + if (good) { + if (!pat_ok(text, io, io + il) or !pat_ok(text, oo, oo + ol)) { good = false; } + } + if (!good) { + res[1] = 0; + res[3] = E_KISS2ROW; + res[4] = rlo as u32; + // raw.trim() + var ra : usize = rlo; + var rb : usize = rhi; + while (ra < rb and is_ws(text[ra])) { ra = ra + 1; } + while (rb > ra and is_ws(text[rb - 1])) { rb = rb - 1; } + res[4] = ra as u32; + res[5] = (rb - ra) as u32; + return; + } + const fi : u32 = name_index(text, work, res, fo, fo + fl); + const ti : u32 = name_index(text, work, res, to, to + tl); + ev(work, res, EV_ROW2, fi, ti, io as u32, il as u32, oo as u32, ol as u32, 0, 0, 0); + nrows = nrows + 1; + } + if (res[0] != 0) { return; } + // names + var nnames : usize = 0; + var k : usize = 0; + const nev : usize = res[16] as usize; + while (k < nev) { + if (work[k * EV] == EV_NAME) { nnames = nnames + 1; } + k = k + 1; + } + if (hoff[0] == NONE and hoff[2] == NONE and nrows == 0) { + res[1] = 0; + res[3] = E_UNKNOWN; + return; + } + if (tmp.len < 4 * (nnames + 1)) { + res[0] = 2; + return; + } + // tmp[0 .. n]: order (new position -> old index); tmp[n .. 2n]: remap (old -> new); numbers in tmp[2n ..] + var all_num : bool = nnames > 0; + k = 0; + var ni : usize = 0; + while (k < nev) { + if (work[k * EV] == EV_NAME) { + const o : usize = work[k * EV + 1] as usize; + const sn : i64 = state_num(text, o, o + (work[k * EV + 2] as usize)); + if (sn < 0) { all_num = false; } + tmp[ni] = ni as u32; + ni = ni + 1; + } + k = k + 1; + } + if (all_num) { + // stable insertion sort of the order by state number (the numbers are kept in two words each below) + var i : usize = 1; + while (i < nnames) { + const cur : u32 = tmp[i]; + const cv : i64 = state_num_of(text, work, nev, cur as usize); + var j : usize = i; + while (j > 0 and state_num_of(text, work, nev, tmp[j - 1] as usize) > cv) { + tmp[j] = tmp[j - 1]; + j = j - 1; + } + tmp[j] = cur; + i = i + 1; + } + } + var q : usize = 0; + while (q < nnames) { + tmp[nnames + (tmp[q] as usize)] = q as u32; + q = q + 1; + } + // results + res[1] = 1; + res[2] = 2; + res[9] = 1; + // nIn, nOut + var nin : f64 = 0.0; + var nout : f64 = 0.0; + var first_in : u32 = NONE; + var first_out : u32 = NONE; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_ROW2) { + first_in = work[k * EV + 4]; + first_out = work[k * EV + 6]; + k = nev; + } else { + k = k + 1; + } + } + if (first_in == NONE) { first_in = 0; first_out = 0; } + if (hoff[0] != NONE) { nin = js_number(text, hoff[0] as usize, (hoff[0] + hlen[0]) as usize); } else { nin = first_in as f64; } + if (hoff[1] != NONE) { nout = js_number(text, hoff[1] as usize, (hoff[1] + hlen[1]) as usize); } else { nout = first_out as f64; } + // reset + var reset : u32 = NONE; + var has_reset_name : bool = hoff[4] != NONE; + if (has_reset_name) { + q = 0; + while (q < nnames) { + const oi : usize = tmp[q] as usize; + const nm : usize = name_event(work, nev, oi); + const no : usize = work[nm * EV + 1] as usize; + if (span_eq(text, no, no + (work[nm * EV + 2] as usize), hoff[4] as usize, (hoff[4] + hlen[4]) as usize) and reset == NONE) { reset = q as u32; } + q = q + 1; + } + } + // notes + var notes : u32 = 0; + var out_notes_at : usize = 0; + // found, rejected: none + res[10] = 0; + res[11] = 0; + // count the notes first to write the count, then write them + // kiss2_s + if (hoff[2] != NONE and js_number(text, hoff[2] as usize, (hoff[2] + hlen[2]) as usize) != (nnames as f64)) { + ow(out, res, N_KISS2_S); + ow(out, res, hoff[2]); + ow(out, res, hlen[2]); + ow(out, res, nnames as u32); + ow(out, res, 0); + notes = notes + 1; + } + if (hoff[3] != NONE and js_number(text, hoff[3] as usize, (hoff[3] + hlen[3]) as usize) != (nrows as f64)) { + ow(out, res, N_KISS2_P); + ow(out, res, hoff[3]); + ow(out, res, hlen[3]); + ow(out, res, nrows); + ow(out, res, 0); + notes = notes + 1; + } + if (has_reset_name and reset == NONE) { + ow(out, res, N_NO_RESET_NAMED); + ow(out, res, hoff[4]); + ow(out, res, hlen[4]); + ow(out, res, 0); + ow(out, res, 0); + notes = notes + 1; + } + if (!has_reset_name) { + ow(out, res, N_NO_RESET); + ow(out, res, 0); + ow(out, res, 0); + ow(out, res, 0); + ow(out, res, 0); + notes = notes + 1; + } + var width_bad : bool = false; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_ROW2) { + if ((work[k * EV + 4] as f64) != nin) { width_bad = true; } + } + k = k + 1; + } + if (width_bad) { + ow(out, res, N_WIDTH_IN); + ow(out, res, 0); + ow(out, res, 0); + ow(out, res, 0); + ow(out, res, 0); + nums[0] = nin; + notes = notes + 1; + } + res[12] = notes; + // the FSM block + var in_len : u32 = NONE; + var n_in_ent : u32 = 0; + var out_len : u32 = NONE; + var n_out_ent : u32 = 0; + if (ilb_gen > 0) { + var cnt : u32 = 0; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_ILB and work[k * EV + 1] == ilb_gen) { cnt = cnt + 1; } + k = k + 1; + } + if ((cnt as f64) == nin) { + in_len = cnt; + n_in_ent = cnt; + } + } + if (ob_gen > 0) { + var cnt : u32 = 0; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_OB and work[k * EV + 1] == ob_gen) { cnt = cnt + 1; } + k = k + 1; + } + if ((cnt as f64) == nout) { + out_len = cnt; + n_out_ent = cnt; + } + } + ow(out, res, NONE); + ow(out, res, 0); + ow(out, res, NONE); + ow(out, res, 0); + ow(out, res, NONE); + ow(out, res, 0); + ow(out, res, reset); + ow(out, res, NONE); + ow(out, res, 0); + ow(out, res, nnames as u32); + ow(out, res, nrows); + ow(out, res, in_len); + ow(out, res, n_in_ent); + ow(out, res, out_len); + ow(out, res, n_out_ent); + ow(out, res, 1); + nums[1] = nin; + nums[2] = nout; + nums[3] = 0.0; + q = 0; + while (q < nnames) { + const oi : usize = tmp[q] as usize; + const nm : usize = name_event(work, nev, oi); + ow(out, res, work[nm * EV + 1]); + ow(out, res, work[nm * EV + 2]); + ow(out, res, NONE); + ow(out, res, 0); + q = q + 1; + } + k = 0; + while (k < nev) { + if (work[k * EV] == EV_ROW2) { + ow(out, res, tmp[nnames + (work[k * EV + 1] as usize)]); + ow(out, res, tmp[nnames + (work[k * EV + 2] as usize)]); + ow(out, res, work[k * EV + 3]); + ow(out, res, work[k * EV + 4]); + ow(out, res, work[k * EV + 5]); + ow(out, res, work[k * EV + 6]); + } + k = k + 1; + } + if (n_in_ent > 0) { + // inputs = ilb.slice().reverse(): entry i is the (len - 1 - i)-th token + var seen : u32 = 0; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_ILB and work[k * EV + 1] == ilb_gen) { + ow(out, res, in_len - 1 - seen); + ow(out, res, work[k * EV + 2]); + ow(out, res, work[k * EV + 3]); + seen = seen + 1; + } + k = k + 1; + } + } + if (n_out_ent > 0) { + var seen : u32 = 0; + k = 0; + while (k < nev) { + if (work[k * EV] == EV_OB and work[k * EV + 1] == ob_gen) { + ow(out, res, out_len - 1 - seen); + ow(out, res, work[k * EV + 2]); + ow(out, res, work[k * EV + 3]); + seen = seen + 1; + } + k = k + 1; + } + } +} + +/// The event index of the idx-th NAME event. +pub fn name_event(work: []const u32, nev: usize, idx: usize) -> usize { + var seen : usize = 0; + var k : usize = 0; + while (k < nev) { + if (work[k * EV] == EV_NAME) { + if (seen == idx) { return k; } + seen = seen + 1; + } + k = k + 1; + } + return 0; +} + +pub fn state_num_of(text: []const u8, work: []const u32, nev: usize, idx: usize) -> i64 { + const e : usize = name_event(work, nev, idx); + const o : usize = work[e * EV + 1] as usize; + return state_num(text, o, o + (work[e * EV + 2] as usize)); +} + +// ------------------------------------------------------------------------------------------------ +// the yosys log +// ------------------------------------------------------------------------------------------------ + +/// ^Found FSM state register (\S+?)\.?\s*$ -> M[0..1] the name. +pub fn m_found(s: []const u8, lo: usize, hi: usize) -> bool { + const p : i64 = eat(s, lo, hi, "Found FSM state register "); + if (p < 0) { return false; } + const a : usize = p as usize; + if (a >= hi or is_ws(s[a])) { return false; } + const e : usize = tok_end(s, a, hi); + if (skip_ws(s, e, hi) != hi) { return false; } + var x : usize = e; + if (e - a >= 2 and s[e - 1] == 46) { x = e - 1; } + M[0] = a; + M[1] = x; + return true; +} + +/// ^Not marking (\S+) as FSM state register:\s*$ -> M[0..1] the register. +pub fn m_not_marking(s: []const u8, lo: usize, hi: usize) -> bool { + const p : i64 = eat(s, lo, hi, "Not marking "); + if (p < 0) { return false; } + const a : usize = p as usize; + const e : usize = tok_end(s, a, hi); + if (e == a) { return false; } + const q : i64 = eat(s, e, hi, " as FSM state register:"); + if (q < 0) { return false; } + if (skip_ws(s, q as usize, hi) != hi) { return false; } + M[0] = a; + M[1] = e; + return true; +} + +/// ^Recoding FSM `([^']+)' from module `([^']+)' using `([^']+)' encoding: -> M[0..1] the FSM, M[2..3] the encoding. +pub fn m_recoding(s: []const u8, lo: usize, hi: usize) -> bool { + const p : i64 = eat(s, lo, hi, "Recoding FSM `"); + if (p < 0) { return false; } + const a0 : usize = p as usize; + const a1 : usize = run_noquote(s, a0, hi); + if (a1 == a0) { return false; } + const q : i64 = eat(s, a1, hi, "' from module `"); + if (q < 0) { return false; } + const b0 : usize = q as usize; + const b1 : usize = run_noquote(s, b0, hi); + if (b1 == b0) { return false; } + const r : i64 = eat(s, b1, hi, "' using `"); + if (r < 0) { return false; } + const c0 : usize = r as usize; + const c1 : usize = run_noquote(s, c0, hi); + if (c1 == c0) { return false; } + if (eat(s, c1, hi, "' encoding:") < 0) { return false; } + M[0] = a0; + M[1] = a1; + M[2] = c0; + M[3] = c1; + return true; +} + +/// mapping auto encoding to `([^`']+)'?`? anywhere -> M[0..1]. +pub fn m_mapping(s: []const u8, lo: usize, hi: usize) -> bool { + var p : usize = lo; + while (p < hi) { + const q : i64 = eat(s, p, hi, "mapping auto encoding to `"); + if (q >= 0) { + const a : usize = q as usize; + var r : usize = a; + while (r < hi and s[r] != 96 and s[r] != 39) { r = r + 1; } + if (r > a) { + M[0] = a; + M[1] = r; + return true; + } + } + p = p + 1; + } + return false; +} + +/// ^\s*Information on FSM (\S+) \((.+)\):\s*$ -> M[0..1] the id, M[2..3] the register. +pub fn m_info(s: []const u8, lo: usize, hi: usize) -> bool { + const p0 : usize = skip_ws(s, lo, hi); + const p : i64 = eat(s, p0, hi, "Information on FSM "); + if (p < 0) { return false; } + const a : usize = p as usize; + const e : usize = tok_end(s, a, hi); + if (e == a) { return false; } + const q : i64 = eat(s, e, hi, " ("); + if (q < 0) { return false; } + const c : usize = q as usize; + const re : usize = rstrip(s, lo, hi); + if (re < c + 3) { return false; } + if (s[re - 2] != 41 or s[re - 1] != 58) { return false; } + if (!no_cr(s, c, re - 2)) { return false; } + M[0] = a; + M[1] = e; + M[2] = c; + M[3] = re - 2; + return true; +} + +/// ^\s*