// SPDX-License-Identifier: Apache-2.0 ; specs/widgets/fasm-skyline.t27 -- a 7-series FASM file drawn as a skyline on the XC7A200T die ; Source of truth for public/widgets/fasm-skyline/ (gHashTag/trinity, apps/website): ; 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 the two data files named below. ; ASCII only (L3), English only (LANG-EN). ; WHAT IT DOES: the reader drops a 7-series .fasm (the text nextpnr-xilinx writes with --fasm). The ; page counts the features each tile sets, one FASM line being one feature, the tile being the ; text before the first '.', and draws the die isometrically: the floor is every tile of the ; XC7A200T's 265 x 261 grid coloured by family, and each touched tile is a column whose height is ; its feature count. It shows the tiles touched, the features set, the tallest tile and the share ; of the die's configurable tiles touched, a table by family, and the tile under the pointer. The ; reader can rotate, pan and zoom, and download the skyline as a PNG and as an STL. The file is ; read with FileReader and sent nowhere. ; HOW IT COUNTS (public/widgets/fasm-skyline/skyline.js, run by the page and by the data script): ; '#' comments and '{...}' annotations are dropped; a line whose value is zero (a BRAM INIT word of ; all zeros, for example) sets no bit and is counted apart, not as a feature; a 64-bit LUT INIT is ; one feature, not 64. A tile name places itself only if prjxray-db has a tile of that type at the ; grid cell the name maps to; every other name is listed as not drawn, never guessed at. ; DATA PROVENANCE: grid.json and samples.json are written by scripts/widget-data/fasm-skyline.mjs ; build, and its check job re-counts both FASM files with skyline.js and compares every K_ number ; below. grid.json is derived from prjxray-db ab1fc60 artix7/xc7a200t/tilegrid.json (sha256 in ; K_TILEGRID_SHA256); before writing it the script looks up all 69165 tiles through skyline.js and ; each must land on its own grid_x and grid_y. Sample one, node.fasm, is the TRI-NET node v2 ; (hand-written Verilog) placed and routed for the ALINX AX7203 board, part xc7a200tfbg484-2: the ; same XC7A200T die as the bench's QMTech Wukong V1, a different package and board. Sample two, ; routed.fasm, is StreamTernaryMac generated from the t27 spec specs/ternary/stream_ternary_mac.t27 ; and placed and routed for xc7a200tfbg676-1 by the build-receipt widget's run of 2026-10-04 (its ; receipt.json records the commands and this file's sha256). No new build was run for this widget. ; WHAT IT DOES NOT CLAIM: that either design was programmed into a board here or met timing. A ; feature count is not a bit count and not a resource count: an INT tile with 100 pips and a CLB ; tile with 100 features are not the same amount of logic. The drawing is the XC7A200T's: a file ; for another part is drawn only where its tile names are tiles of the 200T. The link-preview card ; shows sample one; a static page cannot put the reader's own skyline into a link preview, so the ; page offers the PNG to attach instead. ; phi^2 + 1/phi^2 = 3 | TRINITY module widget_fasm_skyline; pub const KIND : str = "widget-tool"; pub const ID : str = "fasm-skyline"; pub const TITLE : str = "FASM skyline: a routed 7-series design as a city on the XC7A200T die"; pub const DESCRIPTION : str = "Drop a .fasm: every tile it configures becomes a column as tall as the features it sets, on the full XC7A200T grid. Counted in your browser; PNG to share."; pub const IMAGE_ALT : str = "FASM skyline card: the XC7A200T die, 265 x 261 tiles, drawn isometrically with node.fasm for the ALINX AX7203 standing on it: 114803 features in 2148 tiles, 5.23% of the 41106 tiles that hold configuration bits. Tallest column BRAM_L_X6Y230, 402 features. Logic columns green, routing blue."; pub const HOOK : str = "114803 FASM features in 2148 tiles: 5.23% of an XC7A200T. Drop your own .fasm and watch the design stand up as a city."; pub const CATEGORY : str = "fpga"; pub const DATA_SOURCES : [6]str = ["prjxray-db ab1fc60, artix7/xc7a200t/tilegrid.json (sha256 9908f8b3411195a2...): 69165 tiles with type, grid_x and grid_y, reduced to grid.json by scripts/widget-data/fasm-skyline.mjs build", "node.fasm (sha256 e413fb51f92e88cb..., 4330633 bytes): TRI-NET node v2, hand-written Verilog, top trinet_node_v2_ax7203, for the ALINX AX7203 board, part xc7a200tfbg484-2; built in /tmp/x7board/node0 and recorded in its manifest.json", "Yosys 0.67+post (git b8e7da6f): synth_xilinx -flatten -abc9 -nocarry -nodsp -arch xc7. nextpnr-xilinx 0.9.2-107-g7037c948: --chipdb xc7a200tfbg484-2.bin --freq 50 --seed 1 --placer heap --router router1 --timing-allow-fail --fasm node.fasm", "routed.fasm (sha256 daf92c6604a35eb3..., 206571 bytes): StreamTernaryMac, Verilog generated by t27_compiler.wasm (sha256 bb39b9a5ca412a26...) from specs/ternary/stream_ternary_mac.t27 (sha256 f46c60b0ba669110...); the build-receipt widget's run of 2026-10-04, recorded in public/widgets/build-receipt/receipt.json", "Yosys 0.67+post (git b8e7da6f): synth_xilinx -flatten -abc9 -arch xc7 -top build_receipt_top. nextpnr-xilinx 0.9.2-107-g7037c948: --chipdb xc7a200tfbg676-1.bin --xdc build-receipt.xdc --freq 80 --seed 1 --fasm routed.fasm", "scripts/widget-data/fasm-skyline.mjs with public/widgets/fasm-skyline/skyline.js: build writes grid.json and samples.json; check re-counts both FASM files and compares every K_ number of this spec. A file you drop is counted by the same skyline.js in your browser"]; pub const READS_LOCAL_FILES : bool = true; pub const SENDS_NOTHING : bool = true; ; --- Numbers the tool relies on -------------------------------------------------------------- pub const K_DATA_GRID : str = "./grid.json"; pub const K_DATA_SAMPLES : str = "./samples.json"; ; Tile families, the colour of a column and the colour of the floor under it. pub const K_FAMILIES : [7]str = ["CLB", "INT", "BRAM", "DSP", "IO", "CLK", "OTHER"]; pub const K_FAMILY_COLORS : [7]str = ["#00ff88", "#00b4ff", "#be78ff", "#ff8c3c", "#ff4d6d", "#ffd700", "#8a8a8a"]; pub const K_FLOOR_COLORS : [7]str = ["#0d2a1d", "#0a1a26", "#24163a", "#36200e", "#36121a", "#2e2a0c", "#141414"]; ; A tile type is of family K_TYPE_FAMILY[i] when it starts with K_TYPE_PREFIX[i]; first match wins, ; no match is OTHER. The interface tiles beside BRAM, IO and the fabric are routing, so INT. pub const K_TYPE_PREFIX : [17]str = ["CLBLL", "CLBLM", "BRAM_INT_INTERFACE", "INT_INTERFACE", "IO_INT_INTERFACE", "INT_L", "INT_R", "BRAM_L", "BRAM_R", "DSP", "LIOB", "RIOB", "LIOI", "RIOI", "CLK", "HCLK", "CMT"]; pub const K_TYPE_FAMILY : [17]str = ["CLB", "CLB", "INT", "INT", "INT", "INT", "INT", "BRAM", "BRAM", "DSP", "IO", "IO", "IO", "IO", "CLK", "CLK", "CLK"]; ; The die: prjxray-db ab1fc60 artix7/xc7a200t/tilegrid.json. A tile "holds configuration bits" when ; its tilegrid entry has a non-empty bits object; the share of the die is measured against those. pub const K_TILEGRID_SHA256 : str = "9908f8b3411195a24249a79e4e060f5d34f45f22111cf2139a3ec06d683511a0"; pub const K_GRID_W : u16 = 265; pub const K_GRID_H : u16 = 261; pub const K_DIE_TILES : u32 = 69165; pub const K_CONFIG_TILES : u32 = 41106; pub const K_CONFIG_FAMILY_TILES : [7]u32 = [16825, 22250, 365, 370, 520, 503, 273]; ; Sample one: node.fasm, TRI-NET node v2 for the ALINX AX7203 (xc7a200tfbg484-2). pub const K_NODE_SHA256 : str = "e413fb51f92e88cbee0cdd82d588aaaed8313d5ddbc3b5c25177c465329995f4"; pub const K_NODE_FEATURES : u32 = 114803; pub const K_NODE_TILES : u32 = 2148; pub const K_NODE_CLEARED : u32 = 144; pub const K_NODE_TALLEST_TILE : str = "BRAM_L_X6Y230"; pub const K_NODE_TALLEST : u32 = 402; pub const K_NODE_SHARE_BP : u32 = 523; pub const K_NODE_FAMILY_TILES : [7]u32 = [757, 1339, 1, 0, 10, 40, 1]; pub const K_NODE_FAMILY_FEATURES : [7]u32 = [46524, 67650, 402, 0, 73, 143, 11]; ; Sample two: routed.fasm, StreamTernaryMac from a t27 spec (xc7a200tfbg676-1). pub const K_MAC_SHA256 : str = "daf92c6604a35eb356a0498c29b1558db5961f20037858a334109ad12e6a5215"; pub const K_MAC_FEATURES : u32 = 5201; pub const K_MAC_TILES : u32 = 167; pub const K_MAC_CLEARED : u32 = 0; pub const K_MAC_TALLEST_TILE : str = "CLBLL_L_X2Y133"; pub const K_MAC_TALLEST : u32 = 110; pub const K_MAC_SHARE_BP : u32 = 41; pub const K_MAC_FAMILY_TILES : [7]u32 = [47, 95, 0, 0, 4, 20, 1]; pub const K_MAC_FAMILY_FEATURES : [7]u32 = [2682, 2401, 0, 0, 24, 84, 10]; ; Drawing, interaction and export. Heights are relative: the tallest column of a file is drawn ; K_HEIGHT_PCT percent of the mean grid side tall, whatever its count. pub const K_HEIGHT_PCT : u8 = 45; pub const K_MIN_TARGET_PX : u8 = 32; pub const K_MAX_ZOOM : u8 = 60; pub const K_MAX_FILE_MB : u16 = 256; pub const K_PNG_W : u16 = 1200; pub const K_PNG_H : u16 = 630; ; --- Words on the page ------------------------------------------------------------------------ pub const SAY_LOADING : str = "Loading the XC7A200T tile grid..."; pub const SAY_ERR_DATA : str = "The die map (grid.json) or the samples (samples.json) did not load, so nothing can be drawn."; pub const SAY_SAMPLES : [2]str = ["AX7203 node (sample)", "t27 MAC (sample)"]; pub const SAY_SAMPLE_LINES : [2]str = ["TRI-NET node v2, hand-written Verilog, placed and routed for the ALINX AX7203 board, part xc7a200tfbg484-2: the same XC7A200T die as the QMTech Wukong V1 on our bench, a different package and board. Routed with --timing-allow-fail; no timing claim is made here.", "StreamTernaryMac, Verilog generated from the t27 spec specs/ternary/stream_ternary_mac.t27, placed and routed for xc7a200tfbg676-1 by the build-receipt widget's run of 2026-10-04. Not programmed into a board. No new build was run for this page; this is that run's file, same sha256."]; pub const SAY_WHOLE_DIE : str = "Whole die"; pub const SAY_FIT_DESIGN : str = "Fit design"; pub const SAY_ROTATE : str = "Rotate"; pub const SAY_DOWNLOAD_PNG : str = "Download PNG"; pub const SAY_DOWNLOAD_STL : str = "Download STL"; pub const SAY_CANVAS_LABEL : str = "Isometric drawing of the XC7A200T die: each configured tile is a column as tall as the FASM features it sets."; pub const SAY_HINT : str = "Drag to pan, wheel or pinch to zoom, tap a column for its tile. Keys: arrows, + and -, R to rotate."; pub const SAY_STAT_LABELS : [4]str = ["Tiles touched", "Features set", "Tallest tile", "Share of the die"]; pub const SAY_STAT_TALLEST : str = "{0} features"; pub const SAY_STAT_SHARE : str = "{0} of {1} tiles that hold configuration bits"; pub const SAY_FAMILY_HEAD : [3]str = ["Family", "Tiles touched / on die", "Features"]; pub const SAY_FAMILY_NAMES : [7]str = ["Logic (CLBLL, CLBLM)", "Routing (INT and interfaces)", "Block RAM", "DSP", "IO (IOB, IOI)", "Clocking (CLK, HCLK, CMT)", "Other"]; pub const SAY_OF : str = "{0} / {1}"; pub const SAY_PICK_NONE : str = "Point at or tap a column to see its tile."; pub const SAY_PICK_FIELDS : [5]str = ["Tile", "Type", "Family", "Features", "grid_x, grid_y"]; pub const SAY_SUMMARY : str = "{0}: {1} features in {2} tiles"; pub const SAY_SOURCE_SAMPLE : str = "Sample {0}, part {1}, sha256 {2}..."; pub const SAY_SOURCE_FILE : str = "Your file {0}, {1} bytes, counted in this browser and sent nowhere. {2}"; pub const SAY_CLEARED : str = "{0} lines set a value of zero (an empty BRAM INIT word, for example): they set no bit and are not counted."; pub const SAY_SCALE : str = "Heights are relative: the tallest column ({0} features) is drawn the same height in every file. Floor: the {1} x {2} tile grid."; pub const SAY_OFF_MAP : str = "{0} tiles ({1} features) are not tiles of the XC7A200T grid and are not drawn"; pub const SAY_BAD_LINES : str = "{0} lines are not FASM features (no tile name before the first '.')"; pub const SAY_HOW : str = "How this sample was made"; pub const SAY_DROP : str = "Drop a .fasm here, or tap to pick one. It is read in this browser and never uploaded."; pub const SAY_READING : str = "Reading {0} in this browser..."; pub const SAY_ERR_SIZE : str = "{0} is larger than {1} MB, the most this page reads."; pub const SAY_ERR_READ : str = "{0} could not be read."; pub const SAY_ERR_EMPTY : str = "{0} sets no FASM features: no line has a tile name and a feature."; pub const SAY_SHARE_NOTE : str = "A static page cannot put your skyline into a link preview: the preview card always shows the AX7203 sample. Download the PNG and attach it to your post instead."; pub const SAY_PNG_TITLE : str = "FASM skyline"; pub const SAY_PNG_TILES : str = "{0} tiles touched ({1}% of the die)"; pub const SAY_PNG_FEATURES : str = "{0} features set"; pub const SAY_PNG_TALLEST : str = "tallest {0}: {1}"; pub const SAY_PNG_BRAND : str = "Trinity S3AI"; pub const SAY_PNG_FOOT : str = "t27.ai/widgets/fasm-skyline/"; ; --- Tests --------------------------------------------------------------------------------------- test the_die_counts_add_up { assert K_GRID_W * K_GRID_H == K_DIE_TILES; assert K_DIE_TILES == 69165; assert K_CONFIG_FAMILY_TILES[0] + K_CONFIG_FAMILY_TILES[1] + K_CONFIG_FAMILY_TILES[2] + K_CONFIG_FAMILY_TILES[3] + K_CONFIG_FAMILY_TILES[4] + K_CONFIG_FAMILY_TILES[5] + K_CONFIG_FAMILY_TILES[6] == K_CONFIG_TILES; assert K_CONFIG_TILES == 41106; assert K_CONFIG_TILES < K_DIE_TILES; } test the_ax7203_node_sample_adds_up { assert K_NODE_TILES == 2148; assert K_NODE_FEATURES == 114803; assert K_NODE_FAMILY_TILES[0] + K_NODE_FAMILY_TILES[1] + K_NODE_FAMILY_TILES[2] + K_NODE_FAMILY_TILES[3] + K_NODE_FAMILY_TILES[4] + K_NODE_FAMILY_TILES[5] + K_NODE_FAMILY_TILES[6] == K_NODE_TILES; assert K_NODE_FAMILY_FEATURES[0] + K_NODE_FAMILY_FEATURES[1] + K_NODE_FAMILY_FEATURES[2] + K_NODE_FAMILY_FEATURES[3] + K_NODE_FAMILY_FEATURES[4] + K_NODE_FAMILY_FEATURES[5] + K_NODE_FAMILY_FEATURES[6] == K_NODE_FEATURES; assert K_NODE_SHARE_BP == (K_NODE_TILES * 10000 + K_CONFIG_TILES / 2) / K_CONFIG_TILES; assert K_NODE_TALLEST_TILE == "BRAM_L_X6Y230"; assert K_NODE_TALLEST <= K_NODE_FAMILY_FEATURES[2]; assert K_NODE_FAMILY_TILES[0] <= K_CONFIG_FAMILY_TILES[0]; assert K_NODE_FAMILY_TILES[1] <= K_CONFIG_FAMILY_TILES[1]; } test the_t27_mac_sample_adds_up { assert K_MAC_TILES == 167; assert K_MAC_FEATURES == 5201; assert K_MAC_FAMILY_TILES[0] + K_MAC_FAMILY_TILES[1] + K_MAC_FAMILY_TILES[2] + K_MAC_FAMILY_TILES[3] + K_MAC_FAMILY_TILES[4] + K_MAC_FAMILY_TILES[5] + K_MAC_FAMILY_TILES[6] == K_MAC_TILES; assert K_MAC_FAMILY_FEATURES[0] + K_MAC_FAMILY_FEATURES[1] + K_MAC_FAMILY_FEATURES[2] + K_MAC_FAMILY_FEATURES[3] + K_MAC_FAMILY_FEATURES[4] + K_MAC_FAMILY_FEATURES[5] + K_MAC_FAMILY_FEATURES[6] == K_MAC_FEATURES; assert K_MAC_SHARE_BP == (K_MAC_TILES * 10000 + K_CONFIG_TILES / 2) / K_CONFIG_TILES; assert K_MAC_TALLEST <= K_MAC_FAMILY_FEATURES[0]; assert K_MAC_CLEARED == 0; assert K_MAC_TILES < K_NODE_TILES; } test every_type_prefix_names_a_family { assert K_TYPE_FAMILY[0] == K_FAMILIES[0]; assert K_TYPE_FAMILY[1] == K_FAMILIES[0]; assert K_TYPE_FAMILY[2] == K_FAMILIES[1]; assert K_TYPE_FAMILY[6] == K_FAMILIES[1]; assert K_TYPE_FAMILY[7] == K_FAMILIES[2]; assert K_TYPE_FAMILY[9] == K_FAMILIES[3]; assert K_TYPE_FAMILY[10] == K_FAMILIES[4]; assert K_TYPE_FAMILY[16] == K_FAMILIES[5]; } test the_page_reads_files_and_sends_nothing { assert READS_LOCAL_FILES == true; assert SENDS_NOTHING == true; assert CATEGORY == "fpga"; assert K_MIN_TARGET_PX >= 32; assert K_DATA_GRID == "./grid.json"; assert K_PNG_W * 630 == K_PNG_H * 1200; }