// SPDX-License-Identifier: Apache-2.0 ; specs/widgets/bit-diff.t27 -- two Xilinx 7-series bitstreams compared frame by frame ; Source of truth for public/widgets/bit-diff/ (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. ; ASCII only (L3), English only (LANG-EN). ; WHAT IT DOES: reads two bitstreams the reader drops or picks (.bit with its header, or a raw ; .bin), in the reader's browser, with FileReader. It reads the .bit header (design, part, date, ; time), finds the sync word, walks the type 1 and type 2 configuration packets, collects the FDRI ; frame data, gives each 101-word frame its frame address (FAR) from the die's row and column ; layout, and reports: byte-identical or not, the header side by side, the packet and command ; sequence side by side, the first frame that differs with its FAR decoded, and a map of which ; frames differ. The file never leaves the page; nothing is uploaded. ; DATA PROVENANCE: the two demo pairs are summaries computed by the same parser ; (public/widgets/bit-diff/bitparse.js) run in node by scripts/widget-data/bit-diff.mjs on the ; real files; the bitstreams themselves are not shipped (about 9.7 MB each). Pair one is the ; x7-board recording (t27.ai/term/x7-board/): ours.bit, written by bitwalk from frames t27 placed, ; against trinet_node0.bit, written by openXC7's xc7frames2bit, both for xc7a200tfbg484-2. Pair two ; is two real Ethernet builds of the AX7203 node from trinity-fpga artifacts/bitstreams (E2 PHY ; status, E3 ARP and ICMP), so the demo also shows what a difference looks like. The row and column ; layout (geometry.json) is read from prjxray-db ab1fc60 part.json for the four Artix-7 dies; the ; frame mapping was checked against bitwalk's .frames file: all 20230 listed frames sit at their ; addresses in the .bit, word for word apart from the ECC word the .bit writer fills in. ; WHAT IT DOES NOT CLAIM: that two identical bitstreams were built from the same sources, or that ; two different ones behave differently on a board. It does not read compressed (MFWR) or encrypted ; (CBC) bitstreams, readback files, or devices other than 7-series; a part with no geometry gets ; frames in stream order with only the first address known. Frame addresses come from prjxray-db, ; not from AMD documentation. ; phi^2 + 1/phi^2 = 3 | TRINITY module widget_bit_diff; pub const KIND : str = "widget-tool"; pub const ID : str = "bit-diff"; pub const TITLE : str = "Bitstream diff: two 7-series .bit files, frame by frame"; pub const DESCRIPTION : str = "Drop two Xilinx 7-series bitstreams: header, packets and every 101-word frame compared in your browser, the first differing frame address decoded. Nothing is uploaded."; pub const IMAGE_ALT : str = "Bitstream diff card: t27's ours.bit against openXC7's trinet_node0.bit for the XC7A200T, byte-identical across all 24,080 configuration frames, with the same sha256 68249475b57e623f; beside it a frame map of two Ethernet builds where 590 frames differ."; pub const HOOK : str = "Check the byte-identical claim yourself: drop t27's .bit and openXC7's and see every frame compared, or the first one that is not."; pub const CATEGORY : str = "fpga"; pub const DATA_SOURCES : [5]str = ["t27.ai/term/x7-board/ recording: /tmp/x7board/node0/ours.bit (bitwalk) and trinet_node0.bit (openXC7 xc7frames2bit), sha256 68249475b57e623f05564e3d839cfb0c7e7b3e21fe89a014953f12fdd53f95b3", "trinity-fpga artifacts/bitstreams: e2_eth_phy_status_zinv_t1.bit (sha256 501ab904...) and e3_eth_arp_icmp_zinv_t1.bit (sha256 e75a6ff7...)", "prjxray-db ab1fc60 artix7 part.json: rows, columns and frames per column of xc7a35t, xc7a50t, xc7a100t, xc7a200t", "AMD UG470 7 Series FPGAs Configuration: packet header, register and command codes, frame address fields", "scripts/widget-data/bit-diff.mjs: the summaries, computed by public/widgets/bit-diff/bitparse.js in node"]; pub const READS_LOCAL_FILES : bool = true; pub const SENDS_NOTHING : bool = true; ; --- Numbers the tool relies on ------------------------------------------------------------ ; 0xAA995566, the word after which the configuration logic reads packets. pub const K_SYNC_WORD : u32 = 2862175590; pub const K_FRAME_WORDS : u8 = 101; ; Word 50 of a frame carries the frame ECC in bits 0..12; the rest of it is configuration. pub const K_ECC_WORD : u8 = 50; pub const K_ECC_MASK : u16 = 8191; ; Every configuration row in the FDRI stream ends with two pad frames. pub const K_PAD_FRAMES_PER_ROW : u8 = 2; ; XC7A200T: IDCODE 0x03636093, five clock-region rows (two top, three bottom), each with 3660 ; CLB/IO/CLK frames and 1152 block RAM frames (prjxray-db part.json). pub const K_XC7A200T_IDCODE : u32 = 56844435; pub const K_XC7A200T_ROWS : u8 = 5; pub const K_XC7A200T_CLB_ROW_FRAMES : u16 = 3660; pub const K_XC7A200T_BRAM_ROW_FRAMES : u16 = 1152; pub const K_XC7A200T_FRAMES : u32 = 24080; ; Cells per band in the frame map. pub const K_STRIP_BUCKETS : u16 = 160; ; Frames compared between two pauses that let the page draw. pub const K_CHUNK_FRAMES : u16 = 2000; ; --- Words on screen ------------------------------------------------------------------------ ; Templates take {0}, {1}, ... in order. pub const SAY_DEMO_LABEL : str = "Real pairs, precomputed"; pub const SAY_DEMO_IDS : [2]str = ["x7-board", "e2-e3"]; pub const SAY_DEMO_NAMES : [2]str = ["t27 vs openXC7", "E2 vs E3 Ethernet"]; pub const SAY_DEMO_LINES : [2]str = ["ours.bit, written by bitwalk from t27's frames, against trinet_node0.bit from openXC7's xc7frames2bit: the x7-board recording.", "Two real builds of the AX7203 node: E2 (PHY status) and E3 (ARP and ICMP), from trinity-fpga artifacts."]; pub const SAY_DEMO_NOTE : str = "Computed in node by this page's own parser on the real files; the 9.7 MB bitstreams are not shipped. Drop them below to compute it again here."; pub const SAY_OWN_LABEL : str = "Your two files"; pub const SAY_DROP_A : str = "A: drop or pick a .bit or .bin"; pub const SAY_DROP_B : str = "B: drop or pick a .bit or .bin"; pub const SAY_PICKED : str = "{0}: {1} bytes"; pub const SAY_COMPARE : str = "Compare"; pub const SAY_CLEAR : str = "Clear"; pub const SAY_READING : str = "Reading {0}"; pub const SAY_COMPARING : str = "Comparing frames: {0} of {1}"; pub const SAY_LOCAL_NOTE : str = "Read in this tab with FileReader; nothing is uploaded."; pub const SAY_IDENTICAL : str = "Byte-identical"; pub const SAY_FRAMES_SAME : str = "Frames identical"; pub const SAY_DIFFERS : str = "Not identical"; pub const SAY_SAME_LINE : str = "{0} configuration frames, every byte the same; sha256 {1}"; pub const SAY_FRAMES_SAME_LINE : str = "All {0} configuration frames are the same; the files differ outside them."; pub const SAY_DIFF_LINE : str = "{0} of {1} frames differ"; pub const SAY_ONLY_ONE_LINE : str = "{0} frames are in one file only"; pub const SAY_BY_POSITION : str = "Frames paired by position: no shared frame-address layout for these files."; pub const SAY_HEADER_TITLE : str = "Header and stream"; pub const SAY_COL_FIELD : str = "field"; pub const SAY_COL_A : str = "A"; pub const SAY_COL_B : str = "B"; pub const SAY_FIELDS : [13]str = ["file", "bytes", "sha256", "design", "part", "date", "time", "IDCODE", "sync word at byte", "frames", "FDRI writes", "bytes after DESYNC", "notes"]; pub const SAY_NO_HEADER : str = "none (raw .bin)"; pub const SAY_FIRST_TITLE : str = "First differing frame"; pub const SAY_FAR_FIELDS : [6]str = ["frame address", "block type", "half", "row", "column", "minor"]; pub const SAY_BLOCKS : [3]str = ["CLB, IO, CLK", "block RAM content", "CFG CLB"]; pub const SAY_BLOCKS_SHORT : [3]str = ["CLB", "BRAM", "CFG"]; pub const SAY_HALVES : [2]str = ["top", "bottom"]; pub const SAY_FIRST_LINE : str = "Frame {0} of the stream: {1} words and {2} bits differ, {3} of those bits in the ECC field."; pub const SAY_ONLY_ONE_FIRST : str = "Frame {0} of the stream is in one file only."; pub const SAY_NO_FAR : str = "unknown"; pub const SAY_WORD_HEAD : [3]str = ["word", "A", "B"]; pub const SAY_MAP_TITLE : str = "Frame map"; pub const SAY_MAP_LINE : str = "One band per configuration row, in frame-address order from left to right; each cell is a run of frames."; pub const SAY_LEGEND : [3]str = ["same", "differs", "in one file only"]; pub const SAY_PACKETS_TITLE : str = "Packets and commands"; pub const SAY_PACKETS_SAME : str = "The same {0} packet lines in both files (repeats folded)."; pub const SAY_PACKETS_DIFF : str = "{0} packet lines differ."; pub const SAY_SHOW_PACKETS : str = "Show packets"; pub const SAY_HIDE_PACKETS : str = "Hide packets"; pub const SAY_WARN_IDS : [6]str = ["nosync", "encrypted", "compressed", "truncated", "badheader", "partialframe"]; pub const SAY_WARN_TEXT : [6]str = ["no sync word 0xAA995566 found", "encrypted (writes CBC): frames not readable", "compressed (writes MFWR): frames not all present", "file ends inside a packet", "words that are not packet headers", "FDRI data is not a whole number of frames"]; pub const SAY_ERR_READ : str = "Could not read {0}."; pub const SAY_ERR_DEMO : str = "The demo summary did not load."; ; --- Tests ---------------------------------------------------------------------------------- test the_sync_word_is_aa995566 { assert K_SYNC_WORD == 2862175590; assert K_SYNC_WORD == 170 * 16777216 + 153 * 65536 + 85 * 256 + 102; } test a_frame_is_101_words_and_ecc_sits_inside_it { assert K_FRAME_WORDS == 101; assert K_ECC_WORD < K_FRAME_WORDS; assert K_ECC_MASK == 8191; } test the_xc7a200t_stream_holds_24080_frame_slots { assert K_XC7A200T_FRAMES == K_XC7A200T_ROWS * (K_XC7A200T_CLB_ROW_FRAMES + K_PAD_FRAMES_PER_ROW) + K_XC7A200T_ROWS * (K_XC7A200T_BRAM_ROW_FRAMES + K_PAD_FRAMES_PER_ROW); assert K_XC7A200T_FRAMES != K_XC7A200T_ROWS * (K_XC7A200T_CLB_ROW_FRAMES + K_XC7A200T_BRAM_ROW_FRAMES); assert K_XC7A200T_IDCODE == 56844435; } test the_demo_opens_on_the_x7_board_pair { assert SAY_DEMO_IDS[0] == "x7-board"; assert SAY_DEMO_IDS[1] == "e2-e3"; } test the_page_reads_files_and_sends_nothing { assert READS_LOCAL_FILES == true; assert SENDS_NOTHING == true; assert CATEGORY == "fpga"; }