// SPDX-License-Identifier: Apache-2.0 ; specs/widgets/cost-curves.t27 -- cost curves: what an adder, multiplier, MAC or comparator costs on a 7-series ; Source of truth for public/widgets/cost-curves/ (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 DRAWS: cost against width for four binary operations (add, multiply, multiply- ; accumulate, compare) at the widths in K_WIDTHS, each synthesised by yosys ; `synth_xilinx -family xc7 -flatten` twice: with DSP48E1 blocks allowed and with -nodsp. The ; metric is chosen by the reader: LUTs (LUT1..LUT6 summed), flip-flops (FDRE/FDCE/FDSE/FDPE), ; CARRY4 or DSP48E1. A dashed line is the whole XC7A200T, read from prjxray-db's tilegrid.json ; and cross-checked against nextpnr-xilinx's bel counts on the xc7a200tfbg676-1 chipdb. ; THE TERNARY SIDE, SAID PLAINLY: n trits carry n*log2(3) bits of information, so a 15-trit ; adder is drawn at x = 23.8 bits beside the 24-bit binary adder. Every ternary design here is ; binary-encoded, two wires per trit (N=00, Z=01, P=10), and synthesised onto binary LUTs. That ; is NOT ternary hardware and says nothing about what a native ternary circuit would cost; it ; says what carrying trits through a binary FPGA costs. The n-trit adder sweep is a reference ; ripple adder this widget's data script writes (checked exhaustively at 3 trits under Icarus ; Verilog), because no spec in the corpus is an n-trit adder. The t27 points are real specs ; from the corpus, compiled to Verilog by public/t27/t27_compiler.wasm. No spec in the corpus ; is an n-trit multiplier, so there is no ternary multiplier curve; comb_ternary_dot and ; stream_ternary_mac are 27 one-trit products summed, drawn at their operand information ; (27 trits = 42.8 bits) beside one 43-bit-wide binary product -- the same information, a ; different operation, and the page says so beside the point. ; PROVENANCE: scripts/widget-data/cost-curves.mjs writes every module, runs every command and ; writes data.json: the yosys, iverilog and nextpnr-xilinx versions, every command, the seconds ; each run took, the full cell map of each run, the spec and compiler sha256, and the findings. ; A module yosys refuses, or one that synthesises to nothing, is a finding on the page, never a ; silent gap. The K_ facts below are re-checked against data.json by the same script ; (`--check`); a spec that disagrees with the data fails it. ; WHAT IT DOES NOT CLAIM: timing, power or routability. These are post-synthesis cell counts, ; before place and route; the capacity percentage assumes one LUT per LUT6 site, and the packer ; can put two small LUTs in one site. A different yosys, or Vivado, gives different numbers. ; phi^2 + 1/phi^2 = 3 | TRINITY module widget_cost_curves; pub const KIND : str = "widget-tool"; pub const ID : str = "cost-curves"; pub const TITLE : str = "Cost curves: adders, multipliers and MACs on a Xilinx 7-series"; pub const DESCRIPTION : str = "Real yosys synth_xilinx counts for add, multiply, MAC and compare from 4 to 64 bits, with and without DSPs, against a whole XC7A200T, and t27 ternary specs beside them."; pub const IMAGE_ALT : str = "A log-scale chart on black under the headline 9,772 LUTs = 7.26% of an XC7A200T: LUTs against operand width from 4 to 64 bits, with -nodsp. The binary multiplier climbs to 9,772 LUTs, the binary-encoded ternary adder reaches 480 at 40 trits, the binary adder 64, all under a dashed line at the 134,600 LUT6 sites of an XC7A200T."; pub const HOOK : str = "A 64x64 multiplier with -nodsp costs 9,772 LUTs, 7.26% of an XC7A200T. Let it use DSPs and it is 16 DSP48E1 and 243 LUTs."; pub const CATEGORY : str = "fpga"; pub const DATA_SOURCES : [5]str = ["yosys synth_xilinx -family xc7 -flatten, with and without -nodsp, on Verilog written by scripts/widget-data/cost-curves.mjs; versions and every command in data.json", "specs/ternary/ternary_full_adder.t27, ternary_ripple_adder.t27, comb_ternary_dot.t27, stream_ternary_mac.t27 and specs/base/ternary_add.t27, compiled by public/t27/t27_compiler.wasm", "prjxray-db artix7/xc7a200t/tilegrid.json: SLICEL, SLICEM and DSP48E1 site counts", "nextpnr-xilinx --pack-only on the xc7a200tfbg676-1 chipdb: SLICE_LUTX, SLICE_FFX, CARRY4 and DSP48E1 bel counts", "Icarus Verilog: the reference n-trit adder checked on all 729 input pairs at 3 trits"]; pub const READS_LOCAL_FILES : bool = false; pub const SENDS_NOTHING : bool = true; ; --- What is measured (the data script reads these) --------------------------------------------- pub const K_DATA_JSON : str = "data.json"; pub const K_OPS : [4]str = ["add", "mul", "mac", "cmp"]; pub const K_WIDTHS : [8]u16 = [4, 8, 12, 16, 24, 32, 48, 64]; ; Trit counts chosen so n*log2(3) lands near each binary width: 3 -> 4.8, ..., 40 -> 63.4 bits. pub const K_TRIT_COUNTS : [8]u16 = [3, 5, 8, 10, 15, 20, 30, 40]; ; log2(3) in thousandths, rounded: 1.58496... pub const K_LOG2_3_MILLI : u16 = 1585; ; The t27 specs synthesised, under public/t27/files/specs/, with the chart each lands on, its ; operand information in thousandths of a bit, and the binary twin written beside it ("none" when ; the twin is the binary curve itself). pub const K_T27_SPECS : [5]str = ["ternary/ternary_full_adder.t27", "ternary/ternary_ripple_adder.t27", "base/ternary_add.t27", "ternary/comb_ternary_dot.t27", "ternary/stream_ternary_mac.t27"]; pub const K_T27_OPS : [5]str = ["add", "add", "add", "mul", "mac"]; pub const K_T27_INFO_MILLIBITS : [5]u32 = [1000, 2000, 1585, 42794, 42794]; ; Trits per operand where the spec's operands are trits (0 where they are bits carried as trits). pub const K_T27_TRITS : [5]u16 = [0, 0, 1, 27, 27]; pub const K_T27_TWINS : [5]str = ["fa1", "add2", "none", "none", "none"]; ; The synthesis budget: wall-clock seconds for every yosys run together (about 15 minutes). pub const K_SYNTH_BUDGET_S : u16 = 900; ; --- How it draws ------------------------------------------------------------------------------- pub const K_MIN_TARGET_PX : u8 = 32; ; The page opens on this entry of K_OPS (multiply), with -nodsp and LUTs: the chart the card shows. pub const K_START_OP : u8 = 1; pub const K_CHART_H_PX : u16 = 340; ; --- Facts from the run (scripts/widget-data/cost-curves.mjs re-checks every one against data.json) --- ; LUTs (LUT1..LUT6 summed) and CARRY4 of the binary adder at each K_WIDTHS, with -nodsp. pub const K_ADD_LUTS : [8]u16 = [4, 8, 12, 16, 24, 32, 48, 64]; pub const K_ADD_CARRY4 : [8]u16 = [2, 3, 4, 5, 7, 9, 13, 17]; ; LUTs of the reference balanced-ternary adder at each K_TRIT_COUNTS, with -nodsp. pub const K_TADD_LUTS : [8]u16 = [36, 60, 96, 120, 180, 240, 360, 480]; ; LUTs of the binary multiplier with -nodsp, and DSP48E1 blocks of the multiplier and the MAC ; with DSPs allowed, at each K_WIDTHS. pub const K_MUL_NODSP_LUTS : [8]u16 = [24, 130, 278, 528, 1312, 2322, 5642, 9772]; pub const K_MUL_DSP48 : [8]u16 = [0, 1, 1, 1, 2, 4, 9, 16]; pub const K_MAC_DSP48 : [8]u16 = [0, 1, 1, 1, 2, 4, 9, 16]; ; The hook: the 64x64 multiplier with -nodsp, and its share of the device's LUT6 sites in ; hundredths of a percent, truncated. pub const K_HOOK_LUTS : u16 = 9772; pub const K_HOOK_BASIS_POINTS : u16 = 726; ; The t27 specs, with -nodsp, and the binary twin of each adder. pub const K_T27_FA_LUTS : u16 = 52; pub const K_TWIN_FA1_LUTS : u16 = 2; pub const K_T27_RIPPLE_LUTS : u16 = 101; pub const K_TWIN_ADD2_LUTS : u16 = 2; pub const K_COMB_DOT_LUTS : u16 = 317; pub const K_STREAM_MAC_LUTS : u16 = 346; pub const K_STREAM_MAC_FFS : u16 = 32; pub const K_STREAM_MAC_DSP48 : u16 = 0; ; The XC7A200T: slices from prjxray-db (SLICEL + SLICEM sites), the rest derived per slice and ; matched against nextpnr-xilinx's bel counts (two SLICE_LUTX bels per LUT6 site). pub const K_CAP_SLICES : u16 = 33650; pub const K_CAP_LUT6 : u32 = 134600; pub const K_CAP_FF : u32 = 269200; pub const K_CAP_CARRY4 : u16 = 33650; pub const K_CAP_DSP : u16 = 740; pub const K_CAP_LUT_BELS : u32 = 269200; ; Runs yosys refused, and runs that synthesised to no cells. Both are findings on the page. pub const K_REJECTED : u16 = 2; pub const K_EMPTY : u16 = 0; ; --- Every word on the screen ------------------------------------------------------------------- ; {placeholders} are filled by tool.js from data.json; nothing else is added. pub const SAY_OP_LABEL : str = "Operation"; pub const SAY_OPS : [4]str = ["Add", "Multiply", "MAC", "Compare"]; pub const SAY_OP_TITLES : [4]str = ["a + b: W-bit operands, W+1 result bits, no clock", "a * b: W-bit unsigned operands, 2W result bits, no clock", "acc <= acc + a * b: 2W-bit accumulator, clock enable, synchronous reset", "a < b: W-bit unsigned operands, one result bit"]; pub const SAY_METRIC_LABEL : str = "Count"; pub const SAY_METRICS : [4]str = ["LUTs", "FFs", "CARRY4", "DSP"]; pub const SAY_METRIC_LONG : [4]str = ["LUTs (LUT1..LUT6)", "flip-flops (FDRE, FDCE, FDSE, FDPE)", "CARRY4 carry blocks", "DSP48E1 blocks"]; pub const SAY_DSP_LABEL : str = "DSP48E1"; pub const SAY_DSP_ON : str = "allowed"; pub const SAY_DSP_OFF : str = "-nodsp"; pub const SAY_DSP_ON_TITLE : str = "synth_xilinx may map multiplies into DSP48E1 blocks"; pub const SAY_DSP_OFF_TITLE : str = "synth_xilinx -nodsp: everything in LUTs, carry chains and flip-flops"; pub const SAY_SCALE_LABEL : str = "Scale"; pub const SAY_SCALE_LOG : str = "log"; pub const SAY_SCALE_LIN : str = "linear"; pub const SAY_VIEW_LABEL : str = "View"; pub const SAY_VIEW_CHART : str = "Chart"; pub const SAY_VIEW_TABLE : str = "Table"; pub const SAY_X_AXIS : str = "operand width, bits (n trits drawn at n x log2(3) bits)"; pub const SAY_Y_AXIS : str = "{metric}, {scale} scale"; pub const SAY_SERIES : [4]str = ["binary {op}", "ternary adder, 2 wires per trit (reference)", "t27 spec, compiled to Verilog", "binary twin of a t27 adder"]; pub const SAY_CAP : str = "XC7A200T: {n} {unit}"; pub const SAY_CAP_ABOVE : str = "XC7A200T: {n} {unit}, far above this chart (switch to log)"; pub const SAY_CAP_UNITS : [4]str = ["LUT6 sites", "flip-flops", "CARRY4", "DSP48E1"]; pub const SAY_CHART_LABEL : str = "Cost against operand width. Hover or tap near a point for its exact cell counts."; pub const SAY_HINT : str = "hover or tap a point: exact counts"; pub const SAY_TIP_BITS : str = "{bits}-bit operands"; pub const SAY_TIP_TRITS : str = "{trits} trits = {bits} bits of information"; pub const SAY_TIP_SHARE : str = "{pct}% of the XC7A200T's {unit}"; pub const SAY_TIP_SECONDS : str = "yosys {s} s"; pub const SAY_TIP_SPEC : str = "{spec} (module {top})"; pub const SAY_COLS : [11]str = ["Design", "Bits", "Trits", "LUT", "FF", "CARRY4", "DSP48E1", "MUXF7", "MUXF8", "Share", "yosys s"]; pub const SAY_SHARE_TITLE : str = "Share of the XC7A200T for the chosen count"; pub const SAY_REFUSED : str = "refused"; pub const SAY_NONE : str = "-"; pub const SAY_NOTE_TITLE : str = "What is compared"; pub const SAY_NOTES : [4]str = ["Binary: one LUT per bit plus a CARRY4 per four bits. Ternary reference: n trits at n x log2(3) bits of information, each trit two wires, decoded, added and re-encoded in binary LUTs: {tadd} LUTs for 15 trits against {add} for 24 bits. The t27 adder specs carry each trit in a u8 port and compute binary sums through sign neurons: {fa} LUTs for a one-bit full adder whose binary twin is {fa_twin}.", "Binary: one W x W product. The t27 point is comb_ternary_dot.t27: 27 one-trit products summed, drawn at its operand information (27 trits = 42.8 bits). Same information, a different operation: it multiplies nothing wider than a trit. Its own header says about 160 LUT6; this run counts {dot} LUTs.", "Binary: acc + a x b into a 2W-bit register. The t27 point is stream_ternary_mac.t27: one 27-trit dot product per clock into a 32-bit accumulator, drawn at 42.8 bits. Same operand information, a different operation.", "Binary only: a < b, one carry chain. No t27 spec in the corpus is a comparator, so there is no ternary point."]; pub const SAY_HONEST : str = "Every ternary design here is binary-encoded and synthesised onto binary LUTs. That is not ternary hardware, and it says nothing about a native ternary circuit; it says what carrying trits through a binary FPGA costs. No t27 spec is an n-trit multiplier, so there is no ternary multiplier curve."; pub const SAY_PACK_NOTE : str = "Shares assume one LUT per LUT6 site; the packer can put two small LUTs in one. These are post-synthesis cell counts, before place and route: no timing, no power."; pub const SAY_FINDINGS_TITLE : str = "Findings"; pub const SAY_FINDING_REJECTED : str = "{spec}: yosys refused the Verilog the compiler generated ({mode}). {detail}. The line: {line}"; pub const SAY_FINDING_NO_PORTS : str = "{spec}: the generated module {top} has only clk, rst_n, en and ready, so no value can cross its boundary; even parsed, it would synthesise to nothing."; pub const SAY_FINDING_EMPTY : str = "{id}: synthesised to no logic cells."; pub const SAY_FINDING_OTHER : str = "{spec}: {detail}"; pub const SAY_PROV_TITLE : str = "Where the numbers come from"; pub const SAY_PROV_TOOLS : str = "{yosys}. {iverilog}. nextpnr-xilinx {nextpnr}."; pub const SAY_PROV_SYNTH : str = "Every module: {synth}, once with DSPs allowed and once with -nodsp. {runs} runs, {sum} s of yosys, {wall} s wall clock on {jobs} jobs."; pub const SAY_PROV_CAP : str = "Capacity: prjxray-db {file} lists {slicel} SLICEL + {slicem} SLICEM = {slices} slices (4 LUT6, 8 flip-flops, 1 CARRY4 each) and {dsp} DSP48E1. nextpnr-xilinx --pack-only on {chipdb} agrees: {lutx} SLICE_LUTX bels (2 per LUT6), {ffx} SLICE_FFX, {carry} CARRY4, {dspn} DSP48E1."; pub const SAY_PROV_CHECK : str = "The reference ternary adder was checked on all {cases} input pairs at {trits} trits under Icarus Verilog before synthesis: {verdict}."; pub const SAY_PASS : str = "all correct"; pub const SAY_PROV_SPEC : str = "Spec {spec}, sha256 {sha}. Compiler {compiler}, sha256 {csha}. Run at {at}."; pub const SAY_COMMANDS : str = "Every command ({n})"; pub const SAY_LOADING : str = "Reading the synthesis results..."; pub const SAY_ERR_LOAD : str = "The synthesis results did not load: {why}"; ; --- Tests: each holds of this file, and scripts/widget-data/cost-curves.mjs runs them again with ; every K_ fact replaced by the value computed from data.json. test add_luts_grow_with_width { assert K_ADD_LUTS[1] > K_ADD_LUTS[0]; assert K_ADD_LUTS[2] > K_ADD_LUTS[1]; assert K_ADD_LUTS[3] > K_ADD_LUTS[2]; assert K_ADD_LUTS[4] > K_ADD_LUTS[3]; assert K_ADD_LUTS[5] > K_ADD_LUTS[4]; assert K_ADD_LUTS[6] > K_ADD_LUTS[5]; assert K_ADD_LUTS[7] > K_ADD_LUTS[6]; } test nodsp_multiplier_luts_grow_with_width { assert K_MUL_NODSP_LUTS[1] > K_MUL_NODSP_LUTS[0]; assert K_MUL_NODSP_LUTS[2] > K_MUL_NODSP_LUTS[1]; assert K_MUL_NODSP_LUTS[3] > K_MUL_NODSP_LUTS[2]; assert K_MUL_NODSP_LUTS[4] > K_MUL_NODSP_LUTS[3]; assert K_MUL_NODSP_LUTS[5] > K_MUL_NODSP_LUTS[4]; assert K_MUL_NODSP_LUTS[6] > K_MUL_NODSP_LUTS[5]; assert K_MUL_NODSP_LUTS[7] > K_MUL_NODSP_LUTS[6]; } test capacity_is_counted_per_slice { assert K_CAP_LUT6 == 4 * K_CAP_SLICES; assert K_CAP_FF == 8 * K_CAP_SLICES; assert K_CAP_CARRY4 == K_CAP_SLICES; assert K_CAP_LUT_BELS == 2 * K_CAP_LUT6; } test hook_is_the_widest_nodsp_multiplier { assert K_HOOK_LUTS == K_MUL_NODSP_LUTS[7]; assert K_HOOK_BASIS_POINTS == K_HOOK_LUTS * 10000 / K_CAP_LUT6; assert K_MUL_DSP48[7] == 16; assert K_WIDTHS[7] == 64; } test trit_counts_carry_the_bit_widths { assert K_TRIT_COUNTS[0] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[0]; assert K_TRIT_COUNTS[0] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[0]; assert K_TRIT_COUNTS[1] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[1]; assert K_TRIT_COUNTS[1] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[1]; assert K_TRIT_COUNTS[2] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[2]; assert K_TRIT_COUNTS[2] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[2]; assert K_TRIT_COUNTS[3] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[3]; assert K_TRIT_COUNTS[3] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[3]; assert K_TRIT_COUNTS[4] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[4]; assert K_TRIT_COUNTS[4] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[4]; assert K_TRIT_COUNTS[5] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[5]; assert K_TRIT_COUNTS[5] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[5]; assert K_TRIT_COUNTS[6] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[6]; assert K_TRIT_COUNTS[6] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[6]; assert K_TRIT_COUNTS[7] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[7]; assert K_TRIT_COUNTS[7] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[7]; assert K_T27_INFO_MILLIBITS[3] <= 27 * K_LOG2_3_MILLI; assert K_T27_INFO_MILLIBITS[3] + 1 >= 27 * K_LOG2_3_MILLI; assert K_T27_INFO_MILLIBITS[4] == K_T27_INFO_MILLIBITS[3]; } test binary_encoded_trits_cost_more_luts_at_equal_information { assert K_TADD_LUTS[0] > K_ADD_LUTS[0]; assert K_TADD_LUTS[1] > K_ADD_LUTS[1]; assert K_TADD_LUTS[2] > K_ADD_LUTS[2]; assert K_TADD_LUTS[3] > K_ADD_LUTS[3]; assert K_TADD_LUTS[4] > K_ADD_LUTS[4]; assert K_TADD_LUTS[5] > K_ADD_LUTS[5]; assert K_TADD_LUTS[6] > K_ADD_LUTS[6]; assert K_TADD_LUTS[7] > K_ADD_LUTS[7]; assert K_T27_FA_LUTS > K_TWIN_FA1_LUTS; assert K_T27_RIPPLE_LUTS > K_TWIN_ADD2_LUTS; } test the_stream_mac_uses_no_dsp { assert K_STREAM_MAC_DSP48 == 0; assert K_STREAM_MAC_FFS == 32; assert K_REJECTED + K_EMPTY > 0; }