From fd984d3d1bc279f4c37da4dcbf2f0bf2ef44e5e8 Mon Sep 17 00:00:00 2001 From: drewbabel Date: Thu, 16 Jul 2026 22:28:25 -0700 Subject: [PATCH 1/2] [syn] Add the post-route Fmax script --- fmax.sh | 53 +++++++++++++++++++++++++++++++++++++++++++ fmax/tt_async_fifo.sv | 36 +++++++++++++++++++++++++++++ fmax/tt_sync_fifo.sv | 29 +++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100755 fmax.sh create mode 100644 fmax/tt_async_fifo.sv create mode 100644 fmax/tt_sync_fifo.sv diff --git a/fmax.sh b/fmax.sh new file mode 100755 index 0000000..403263b --- /dev/null +++ b/fmax.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Fmax and utilization +# Usage ./fmax.sh [clock] +set -e + +MOD="$1"; HARNESS="$2"; shift 2 +CLKS="$*" +CHIPDB="$HOME/Documents/code/nextpnr-xilinx/xilinx/xc7a35t.bin" +mkdir -p build + +# Module utilization +sv2v rtl/*.sv > build/util_$MOD.v +yosys -p "read_verilog build/util_$MOD.v; synth_xilinx -top $MOD -flatten; stat" 2>/dev/null \ + | awk ' + /^=== / { lut = 0; ff = 0; bram = 0; dram = 0 } + /^[[:space:]]+[0-9]+[[:space:]]+LUT/ { lut += $1 } + /^[[:space:]]+[0-9]+[[:space:]]+FD/ { ff += $1 } + /^[[:space:]]+[0-9]+[[:space:]]+RAMB/ { bram += $1 } + /^[[:space:]]+[0-9]+[[:space:]]+RAM[0-9]/ { dram += $1 } + END { printf "util %d %d %d %d\n", lut+0, ff+0, bram+0, dram+0 }' > build/util_$MOD.txt + +# Route the harness +sv2v rtl/*.sv fmax/$HARNESS.sv > build/fmax_$HARNESS.v +yosys -q -p "read_verilog build/fmax_$HARNESS.v; synth_xilinx -top $HARNESS -flatten; write_json build/fmax_$HARNESS.json" 2>/dev/null + +# Pin pool +CLKPINS=(W5 V17 V16) +IOPINS=(W16 W17 W15 V15 W14 W13 U16 E19 U19 V19) +XDC=build/fmax_$HARNESS.xdc +: > $XDC +ci=0; ii=0 +for p in $(python3 - "build/fmax_$HARNESS.json" "$HARNESS" <<'PY' +import json, sys +d = json.load(open(sys.argv[1])) +ports = d["modules"][sys.argv[2]]["ports"] +print(" ".join(ports.keys())) +PY +); do + if echo " $CLKS " | grep -q " $p "; then pin=${CLKPINS[$ci]}; ci=$((ci+1)); else pin=${IOPINS[$ii]}; ii=$((ii+1)); fi + echo "set_property PACKAGE_PIN $pin [get_ports $p]" >> $XDC + echo "set_property IOSTANDARD LVCMOS33 [get_ports $p]" >> $XDC +done + +nextpnr-xilinx --chipdb "$CHIPDB" --xdc $XDC --json build/fmax_$HARNESS.json \ + --fasm build/fmax_$HARNESS.fasm --router router2 2>build/fmax_$HARNESS.log || true + +read lut ff bram dram < <(awk '{print $2, $3, $4, $5}' build/util_$MOD.txt) +echo "=== $MOD ===" +echo " LUTs $lut Flip-flops $ff Block RAMs $bram Distributed RAM $dram" +# Fmax per clock +grep -iE "Max frequency for clock" build/fmax_$HARNESS.log \ + | sed -E "s/.*clock '([^']+)': ([0-9.]+) MHz.*/\1 \2/" \ + | awk '{ f[$1] = $2 } END { for (c in f) printf " Fmax %-14s %s MHz\n", c, f[c] }' diff --git a/fmax/tt_async_fifo.sv b/fmax/tt_async_fifo.sv new file mode 100644 index 0000000..2037645 --- /dev/null +++ b/fmax/tt_async_fifo.sv @@ -0,0 +1,36 @@ +// Async FIFO harness +module tt_async_fifo ( + input logic wr_clk, + input logic rd_clk, + input logic rst_n, + input logic si, + output logic so_w, + output logic so_r +); + localparam int NW = 9; + + logic [NW-1:0] win_r; + logic rin_r; + logic full_w; + logic [7:0] rd_data_w; + logic empty_w; + + always_ff @(posedge wr_clk) win_r <= {win_r[NW-2:0], si}; + always_ff @(posedge rd_clk) rin_r <= si; + + async_fifo dut ( + .wr_clk (wr_clk), + .wr_rst_n(rst_n), + .wr_en (win_r[0]), + .wr_data (win_r[8:1]), + .full (full_w), + .rd_clk (rd_clk), + .rd_rst_n(rst_n), + .rd_en (rin_r), + .rd_data (rd_data_w), + .empty (empty_w) + ); + + always_ff @(posedge wr_clk) so_w <= full_w; + always_ff @(posedge rd_clk) so_r <= ^{rd_data_w, empty_w}; +endmodule diff --git a/fmax/tt_sync_fifo.sv b/fmax/tt_sync_fifo.sv new file mode 100644 index 0000000..27aefa8 --- /dev/null +++ b/fmax/tt_sync_fifo.sv @@ -0,0 +1,29 @@ +// Sync FIFO harness +module tt_sync_fifo ( + input logic clk, + input logic rst_n, + input logic si, + output logic so +); + localparam int NIN = 10; + localparam int NOUT = 10; + + logic [NIN-1:0] in_r; + logic [NOUT-1:0] out_w, out_r; + + always_ff @(posedge clk) in_r <= {in_r[NIN-2:0], si}; + + sync_fifo dut ( + .clk (clk), + .rst_n (rst_n), + .wr_en (in_r[0]), + .rd_en (in_r[1]), + .wr_data(in_r[9:2]), + .rd_data(out_w[7:0]), + .full (out_w[8]), + .empty (out_w[9]) + ); + + always_ff @(posedge clk) out_r <= out_w; + always_ff @(posedge clk) so <= ^out_r; +endmodule From 4966a04447207bc4dd9b1bc7709d7ec3a856f196 Mon Sep 17 00:00:00 2001 From: drewbabel Date: Thu, 16 Jul 2026 22:28:39 -0700 Subject: [PATCH 2/2] [docs] Add a post-route timing table --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7ba579..3109da0 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ make MOD=sync_fifo # run a module's testbench make wave MOD=sync_fifo # run the testbench and open the waveform in Surfer make formal MOD=async_fifo # run the module's SymbiYosys proof ./synth_stats.sh sync_fifo # report a module's synthesis cost +./fmax.sh async_fifo tt_async_fifo wr_clk rd_clk # fmax and utilization ``` ## Synthesis @@ -99,6 +100,15 @@ Synthesized for the Digilent Basys 3 (Xilinx Artix-7). | `sync_fifo` | 8 | 18 | 4 | | `async_fifo` | 19 | 46 | 4 | +### Post-route timing + +`fmax.sh` places and routes each FIFO in a registered-boundary harness and reports the maximum clock frequency. The frequencies come from the open nextpnr-xilinx flow, which is experimental and not vendor signed timing analysis. The `async_fifo` carries one frequency per clock domain, since a single figure has no meaning across the two independent clocks. The memory maps to distributed RAM rather than block RAM at this depth. + +| Module | LUTs | Flip-flops | Block RAMs | Distributed RAM | Fmax | +|--------|------|------------|------------|-----------------|------| +| `sync_fifo` | 8 | 18 | 0 | 2 | 324 MHz | +| `async_fifo` | 19 | 46 | 0 | 2 | 296 MHz write, 343 MHz read | + ### Tool versions -Icarus Verilog 13.0, Yosys 0.66, SymbiYosys 0.66 with Z3, and Surfer. +Icarus Verilog 13.0, Yosys 0.66, SymbiYosys 0.66 with Z3, nextpnr-xilinx 0.8.2, and Surfer.