// SPDX-License-Identifier: Apache-2.0 ; specs/widgets/synth-smells.t27 -- a lint card for a yosys synthesis log ; Source of truth for public/widgets/synth-smells/ (gHashTag/trinity, apps/website). The page is ; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its ; own and reads every word below from window.T27_WIDGET. ; ASCII only (L3), English only (LANG-EN). ; WHAT IT DOES: the reader pastes or drops the text a yosys run printed (a `yosys -l` log, or the ; terminal output of `yosys -p "... synth_xilinx ..."`). The page reads it in the browser with ; public/widgets/synth-smells/smellparse.js and shows a lint card: the error yosys stopped at, ; inferred latches, wires used with no driver, nets with conflicting drivers, memories rebuilt ; from flip-flops, the cells and wires opt_clean removed, the warning total, and the cell counts ; of the last `stat` block (LUT1..LUT6, flip-flops, CARRY4, MUXF7/8, DSP48E1, RAMB, latches, IO). ; Each smell carries one line saying what it means. The result can be saved as a PNG card drawn in ; the page. The log never leaves the browser. ; THE SAMPLES: four real yosys 0.67 runs, `synth_xilinx -family xc7 -flatten`, made by ; scripts/widget-data/synth-smells.mjs. Three are t27 specs compiled to Verilog by ; public/t27/t27_compiler.wasm in node: stream_ternary_mac and comb_ternary_dot synthesise clean, ; and base/ternary_add is refused because the generated Verilog calls @rem (gHashTag/t27#6224). ; The fourth is a hand-written teaching sample, scripts/widget-data/synth-smells-teaching.v, NOT ; generated from any spec: it holds a latch, an undriven wire, two drivers on one wire and a ; reset-cleared register file on purpose. The logs are trimmed by that script, never by hand: it ; keeps every line the parser reads, the pass header above each and the final statistics, and ; fails unless the full and trimmed log parse to the same counts. Every K_ fact below is re-read ; from the shipped logs by `node scripts/widget-data/synth-smells.mjs --check`. ; WHAT IT DOES NOT CLAIM: that a log with no smell is a correct design, or that a smell is always a ; bug (a latch can be meant). It reads only what yosys printed; it does not re-run synthesis, and a ; log from another tool or a much older yosys may read as fewer smells than it holds. Cell counts ; are post-synthesis, before place and route. ; phi^2 + 1/phi^2 = 3 | TRINITY module widget_synth_smells; pub const KIND : str = "widget-tool"; pub const ID : str = "synth-smells"; pub const TITLE : str = "Synth smells: a lint card for your yosys log"; pub const DESCRIPTION : str = "Paste or drop a yosys synth_xilinx log: latches, undriven wires, driver conflicts, memories turned into flip-flops, warnings and final cell counts, read in your browser."; pub const IMAGE_ALT : str = "A lint card on black for four real yosys 0.67 synth_xilinx logs: two t27 specs synthesise clean (346 and 317 LUTs), one is refused at a syntax error, and a hand-written teaching sample shows 1 latch, 1 undriven wire, 3 conflicting-driver nets, 1 memory rebuilt from flip-flops and 12 warnings."; pub const HOOK : str = "Paste your yosys log and see the latch you did not mean, the wire nothing drives, and the RAM that became 16 flip-flops."; pub const CATEGORY : str = "fpga"; pub const DATA_SOURCES : [6]str = ["Yosys 0.67+post (git sha1 b8e7da6f), one run per sample: yosys -q -l .full.log -p \"read_verilog -sv .v; synth_xilinx -family xc7 -flatten -top \"; every command, exit code and time in samples.json", "specs/ternary/stream_ternary_mac.t27, specs/ternary/comb_ternary_dot.t27 and specs/base/ternary_add.t27, compiled to Verilog by public/t27/t27_compiler.wasm in node (sha256 of spec, compiler and Verilog in samples.json)", "scripts/widget-data/synth-smells-teaching.v: a hand-written teaching sample, not from a spec, with a latch, an undriven wire, two drivers on one wire and a reset-cleared register file", "node scripts/widget-data/synth-smells.mjs: compiles, runs yosys, trims each log by rule (never by hand) and fails unless the full and trimmed log parse to the same counts", "public/widgets/synth-smells/smellparse.js: the one parser, run in node for the samples and in the browser for your log", "node scripts/widget-data/synth-smells.mjs --check: re-parses the shipped logs and fails if any K_ fact in this spec disagrees"]; pub const READS_LOCAL_FILES : bool = true; pub const SENDS_NOTHING : bool = true; ; --- The samples (the data script reads these) ------------------------------------------------- pub const K_SAMPLES_JSON : str = "samples.json"; pub const K_SAMPLE_IDS : [4]str = ["stream-mac", "comb-dot", "ternary-add", "teaching"]; ; Where each sample's Verilog comes from: a spec under public/t27/files/specs/ (compiled by the wasm ; compiler), or a hand-written file under apps/website. pub const K_SAMPLE_KINDS : [4]str = ["t27", "t27", "t27", "hand"]; pub const K_SAMPLE_SOURCES : [4]str = ["ternary/stream_ternary_mac.t27", "ternary/comb_ternary_dot.t27", "base/ternary_add.t27", "scripts/widget-data/synth-smells-teaching.v"]; ; A log over this many bytes is not shipped as a sample. pub const K_MAX_LOG_BYTES : u16 = 16384; ; A pasted log over this many characters is read only up to here. pub const K_MAX_PASTE_CHARS : u32 = 8000000; ; --- Facts from the run (scripts/widget-data/synth-smells.mjs re-reads every one from the logs) --- ; One entry per K_SAMPLE_IDS, in order. pub const K_ERRORS : [4]u16 = [0, 0, 1, 0]; pub const K_LATCHES : [4]u16 = [0, 0, 0, 1]; pub const K_UNDRIVEN : [4]u16 = [0, 0, 0, 1]; pub const K_UNDRIVEN_BITS : [4]u16 = [0, 0, 0, 4]; pub const K_CONFLICTS : [4]u16 = [0, 0, 0, 3]; pub const K_MEMORIES : [4]u16 = [0, 0, 0, 1]; pub const K_REMOVED_CELLS : [4]u16 = [1081, 250, 0, 15]; pub const K_REMOVED_WIRES : [4]u16 = [5427, 3183, 0, 363]; pub const K_WARNINGS : [4]u16 = [0, 0, 0, 12]; pub const K_CELLS : [4]u16 = [543, 457, 0, 53]; ; yosys 0.67 lists FDRE/FDCE, CARRY4 and MUXF7/8 under "submodules", outside its "cells" total. pub const K_SUBMODULES : [4]u16 = [185, 144, 0, 16]; pub const K_LUTS : [4]u16 = [346, 317, 0, 16]; pub const K_FFS : [4]u16 = [32, 0, 0, 16]; pub const K_LATCH_CELLS : [4]u16 = [0, 0, 0, 4]; pub const K_CARRY : [4]u16 = [10, 2, 0, 0]; ; The Verilog line yosys stopped at in the refused sample. pub const K_REFUSED_LINE : u16 = 213; ; --- How it draws ------------------------------------------------------------------------------- pub const K_MIN_TARGET_PX : u8 = 32; pub const K_CARD_W : u16 = 1200; pub const K_CARD_H : u16 = 630; ; Evidence lines listed under a smell before the rest are folded. pub const K_LIST_MAX : u8 = 8; ; --- Every word on the screen ------------------------------------------------------------------- ; Templates take {0}, {1}, ... in order. pub const SAY_SAMPLES_LABEL : str = "Real logs"; pub const SAY_SAMPLE_NAMES : [4]str = ["t27 stream MAC", "t27 comb dot", "t27 ternary_add (refused)", "Teaching sample"]; pub const SAY_SAMPLE_LINES : [4]str = ["specs/ternary/stream_ternary_mac.t27, compiled to Verilog by this site's wasm compiler and synthesised by yosys 0.67 synth_xilinx.", "specs/ternary/comb_ternary_dot.t27, compiled to Verilog by this site's wasm compiler and synthesised by yosys 0.67 synth_xilinx.", "specs/base/ternary_add.t27, compiled the same way: yosys refuses it, because the generated Verilog calls @rem, which is not Verilog (gHashTag/t27#6224).", "Hand-written teaching sample, NOT from a t27 spec: an incomplete case, an undriven wire, two drivers on one wire and a reset-cleared register file, each put there on purpose."]; pub const SAY_SAMPLE_NOTE : str = "Trimmed by script to the lines the parser reads, with their pass headers and the final statistics."; pub const SAY_OPEN_LOG : str = "Open this log"; pub const SAY_OWN_LABEL : str = "Your log"; pub const SAY_DROP : str = "Drop a yosys log here, or pick a file"; pub const SAY_PASTE_HINT : str = "...or paste the text yosys printed here"; pub const SAY_PASTE_LABEL : str = "yosys log text"; pub const SAY_READ : str = "Read the log"; pub const SAY_CLEAR : str = "Clear"; pub const SAY_PICKED : str = "{0}: {1} bytes"; pub const SAY_PASTED : str = "pasted text"; pub const SAY_CUT : str = "Read the first {0} characters only."; pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is uploaded."; pub const SAY_ERR_READ : str = "Could not read {0}."; pub const SAY_ERR_LOAD : str = "The sample logs did not load."; pub const SAY_VERDICT_CLEAN : str = "No smells"; pub const SAY_VERDICT_SMELLS : str = "{0} smells"; pub const SAY_VERDICT_ONE : str = "1 smell"; pub const SAY_VERDICT_REFUSED : str = "Refused"; pub const SAY_VERDICT_UNKNOWN : str = "Not a yosys log?"; pub const SAY_VERDICT_LINE : str = "{0}, {1} lines read."; pub const SAY_UNKNOWN_LINE : str = "No yosys banner, no known message and no statistics block in this text."; pub const SAY_NO_VERSION : str = "yosys version not in the log"; pub const SAY_VERSION : str = "yosys {0}"; pub const SAY_COMMAND : str = "Command: {0}"; pub const SAY_REFUSED_LINE : str = "{0} line {1}: {2}"; pub const SAY_SOURCE_LINE : str = "The source line: {0}"; ; The smells, in the order the card lists them, with what each means and the yosys pass that says it. pub const SAY_SMELL_IDS : [7]str = ["error", "latch", "undriven", "conflict", "memory", "removed", "warnings"]; ; 2 = red (fix it), 1 = gold (look at it), 0 = information. pub const K_SMELL_LEVELS : [7]u8 = [2, 2, 2, 2, 1, 0, 1]; pub const SAY_SMELL_NAMES : [7]str = ["Errors", "Latches inferred", "Wires with no driver", "Conflicting drivers", "Memories rebuilt from flip-flops", "Removed as unused", "Warnings"]; pub const SAY_SMELL_MEANINGS : [7]str = ["yosys stopped here: the source did not parse or elaborate, so nothing after it was synthesised.", "A combinational block leaves a signal unassigned on some path, so the circuit must hold its old value: a level-sensitive latch, usually an incomplete case or an if without else.", "A wire is read but nothing writes it; synthesis treats it as undefined, so the logic it feeds is not what the source seems to say.", "Two drivers write one net, which on a chip is a short. As optimisation renames nets, one conflict can be reported under several names.", "An array could not become block or distributed RAM and was rebuilt from flip-flops and multiplexers: often a reset loop or an access no RAM primitive has.", "Cells and wires opt_clean deleted because no output depends on them. Normal tidy-up; a large count means much of what the frontend built reaches no output.", "Every Warning: line yosys printed, taken from its own summary line when it prints one."]; pub const SAY_SMELL_PASSES : [7]str = ["frontend", "proc_dlatch", "check", "check", "memory_map", "opt_clean", "all passes"]; pub const SAY_SMELL_COUNT : [7]str = ["{0}", "{0}", "{0}, bits: {1}", "{0}", "{0}", "cells: {0}, wires: {1}", "{0}, unique: {1}"]; pub const SAY_PASS_LABEL : str = "reported by {0}"; pub const SAY_MORE : str = "and {0} more"; pub const SAY_NONE : str = "none"; pub const SAY_CELLS_TITLE : str = "Cells in the last stat block"; pub const SAY_CELLS_TOTAL : str = "{0} cells"; pub const SAY_CELLS_SUB : str = "{0} cells, and {1} more that yosys lists as submodules (flip-flops, CARRY4, MUXF7/8 in yosys 0.67)"; pub const SAY_NO_STAT : str = "No statistics block in this log: yosys stopped before stat, or the log was cut."; pub const SAY_GROUP_IDS : [10]str = ["lut", "ff", "latch", "carry", "mux", "dsp", "bram", "lutram", "io", "other"]; pub const SAY_GROUP_NAMES : [10]str = ["LUTs", "Flip-flops", "Latches", "Carry", "MUXF7/8", "DSP48E1", "Block RAM", "LUT RAM, SRL", "IO, clock buffers", "Other"]; pub const SAY_COLS : [3]str = ["Cell", "Group", "Count"]; pub const SAY_SHOW_CELLS : str = "Show every cell type"; pub const SAY_HIDE_CELLS : str = "Hide cell types"; pub const SAY_PNG_BUTTON : str = "Save as PNG card"; pub const SAY_PNG_FILE : str = "synth-smells-{0}.png"; pub const SAY_CARD_BRAND : str = "TRINITY S3AI / SYNTH SMELLS"; pub const SAY_CARD_URL : str = "t27.ai/widgets/synth-smells/ -- read in the browser, nothing uploaded"; pub const SAY_CARD_CELLS : str = "{0} cells: {1}"; pub const SAY_HONEST : str = "A log with no smell is not a proof the design is right, and a latch can be meant. This reads only what yosys printed; it does not run synthesis."; ; --- Tests: each holds of this file, and scripts/widget-data/synth-smells.mjs runs them again with ; every K_ fact replaced by the value parsed from the shipped logs. test the_teaching_sample_shows_every_smell_it_was_written_with { assert K_SAMPLE_KINDS[3] == "hand"; assert K_LATCHES[3] > 0; assert K_UNDRIVEN[3] > 0; assert K_CONFLICTS[3] > 0; assert K_MEMORIES[3] > 0; assert K_LATCH_CELLS[3] >= K_LATCHES[3]; assert K_UNDRIVEN_BITS[3] >= K_UNDRIVEN[3]; assert K_WARNINGS[3] >= K_LATCHES[3] + K_UNDRIVEN_BITS[3] + K_CONFLICTS[3]; assert K_ERRORS[3] == 0; } test the_two_clean_t27_specs_have_no_smell_and_real_cells { assert K_SAMPLE_KINDS[0] == "t27"; assert K_SAMPLE_KINDS[1] == "t27"; assert K_ERRORS[0] + K_LATCHES[0] + K_UNDRIVEN[0] + K_CONFLICTS[0] + K_MEMORIES[0] + K_WARNINGS[0] == 0; assert K_ERRORS[1] + K_LATCHES[1] + K_UNDRIVEN[1] + K_CONFLICTS[1] + K_MEMORIES[1] + K_WARNINGS[1] == 0; assert K_LUTS[0] > 0; assert K_LUTS[1] > 0; assert K_LUTS[0] <= K_CELLS[0]; assert K_LUTS[1] <= K_CELLS[1]; assert K_LUTS[3] + K_LATCH_CELLS[3] <= K_CELLS[3]; assert K_FFS[0] + K_CARRY[0] <= K_SUBMODULES[0]; assert K_FFS[3] <= K_SUBMODULES[3]; } test the_refused_spec_stops_before_any_cell { assert K_SAMPLE_SOURCES[2] == "base/ternary_add.t27"; assert K_ERRORS[2] == 1; assert K_CELLS[2] == 0; assert K_REMOVED_CELLS[2] == 0; assert K_REFUSED_LINE > 0; } test the_smell_lists_line_up { assert SAY_SMELL_IDS[0] == "error"; assert SAY_SMELL_IDS[1] == "latch"; assert SAY_SMELL_IDS[6] == "warnings"; assert K_SMELL_LEVELS[0] == 2; assert K_SMELL_LEVELS[5] == 0; assert SAY_GROUP_IDS[0] == "lut"; assert SAY_GROUP_IDS[9] == "other"; } test the_page_reads_files_and_sends_nothing { assert READS_LOCAL_FILES == true; assert SENDS_NOTHING == true; assert CATEGORY == "fpga"; assert K_CARD_W == 1200; assert K_CARD_H == 630; }