diff --git a/.trellis/spec/rtl/index.md b/.trellis/spec/rtl/index.md new file mode 100644 index 0000000..31a90ba --- /dev/null +++ b/.trellis/spec/rtl/index.md @@ -0,0 +1,14 @@ +# RTL Specifications + +## Pre-Development Checklist + +Before writing RTL code or testbenches, read: +1. [Verilator Conventions](./verilator-conventions.md) — for C++ Verilator testbenches +2. [XSIM Testbench Conventions](./xsim-tb-conventions.md) — for Vivado XSIM Verilog testbenches + +## Files + +| File | Purpose | +|------|---------| +| `verilator-conventions.md` | Verilator 5.046 C++ testbench conventions (clock, timing, valid/ready protocol) | +| `xsim-tb-conventions.md` | Vivado XSIM Verilog testbench conventions (template, vector format, TCL scripts) | diff --git a/.trellis/spec/rtl/xsim-tb-conventions.md b/.trellis/spec/rtl/xsim-tb-conventions.md new file mode 100644 index 0000000..9fdca8d --- /dev/null +++ b/.trellis/spec/rtl/xsim-tb-conventions.md @@ -0,0 +1,181 @@ +# Vivado XSIM Verilog Testbench Conventions + +## Purpose + +Conventions for writing Verilog testbenches targeting Vivado XSIM simulator. +These testbenches co-exist with Verilator C++ testbenches (`.cpp`) — both go in the same `TB/` directory. + +## Directory Structure + +``` +sync_rtl// +├── .v # RTL source +├── TB/ +│ ├── tb_.cpp # Verilator C++ testbench (optional) +│ ├── tb__xsim.v # XSIM Verilog testbench +│ ├── gen_vectors.py # Python vector generator (stdlib only) +│ ├── xsim_run.tcl # Vivado compile+elaborate+simulate script +│ └── vectors/ +│ └── _input.hex # Test input vectors for $readmemh +``` + +## TB File Template + +```verilog +// tb__xsim.v - Vivado XSIM testbench for +// Usage: +// xvlog -sv .v tb__xsim.v +// xelab tb__xsim -s +// xsim -R + +`timescale 1ns / 1ps + +module tb__xsim; + + parameter VECTOR_FILE = "sync_rtl//TB/vectors/_input.hex"; + parameter TIMEOUT_CYCLES = 10000; + + // DUT signals (reg for inputs, wire for outputs) + reg clk, rst_n; + // ... module-specific ports ... + + // DUT instantiation + u_dut ( + .clk(clk), .rst_n(rst_n), + // ... + ); + + // Clock: 100 MHz (10 ns period) + initial clk = 1'b0; + always #5 clk = ~clk; + + // Vector memory (width depends on module) + reg [W-1:0] vector_mem [0:MAX_VECTORS-1]; + + integer pass_count, fail_count; + + initial begin + // Load vectors + $readmemh(VECTOR_FILE, vector_mem); + + // Reset: rst_n low 3 cycles + rst_n <= 1'b0; + repeat (3) @(posedge clk); + rst_n <= 1'b1; + @(posedge clk); + + // Process each vector: drive → wait valid_o → capture → verify + for (idx = 0; idx < vec_count; idx = idx + 1) begin + // Drive DUT inputs (use <= for reg drives) + // ... + + // Wait for valid_o with timeout + cycle_count = 0; + while (!valid_o && cycle_count < TIMEOUT_CYCLES) begin + @(posedge clk); + cycle_count = cycle_count + 1; + end + + if (cycle_count >= TIMEOUT_CYCLES) begin + $display("ERROR: Timeout on vector %0d", idx); + fail_count = fail_count + 1; + end else begin + // Capture output, verify, write result + pass_count = pass_count + 1; + end + end + + $display("PASS: %0d FAIL: %0d", pass_count, fail_count); + $finish; + end + + // Timeout watchdog + initial begin + #(TIMEOUT_CYCLES * 10 * 100); + $display("FATAL: Global timeout"); + $finish; + end + +endmodule +``` + +## Key Rules + +### Clock +- Always: `initial clk = 1'b0; always #5 clk = ~clk;` (100 MHz, 10 ns period) + +### Reset +- Active-low (`rst_n`), held low for 3 cycles minimum +- Pattern: `rst_n <= 1'b0; repeat (3) @(posedge clk); rst_n <= 1'b1; @(posedge clk);` + +### DUT Drive Protocol +- Use **non-blocking assignment** (`<=`) for all DUT input drives in `initial` blocks +- Use **blocking assignment** (`=`) for local variable initialization only +- Follow valid/ready handshake: assert `valid_i`, wait `ready_o`, then de-assert + +### Timeout Watchdog +- Every TB MUST have a global timeout watchdog initial block +- Pattern: `#(TIMEOUT_CYCLES * 10 * 100);` (gives TIMEOUT_CYCLES × 1000ns margin) +- Module-specific per-vector timeouts also required (using `cycle_count < TIMEOUT_CYCLES` loop) + +### Pass/Fail Tracking +- Use `integer pass_count, fail_count;` +- Increment on each vector completion or timeout +- Print summary with `$display("PASS: %0d FAIL: %0d", pass_count, fail_count)` before `$finish` + +## Vector Format (`$readmemh`) + +Vectors are packed as single hex numbers per line: + +``` +// Simple module (e.g., mod_add, 24-bit vectors) +// Hex chars = ceil(W/4) +000000 +0640c8 +d00d00 +``` + +``` +// Complex module (e.g., ntt_core, 3076-bit vectors) +// Hex chars = 769 +00000000...0000 +10000000...0001 +``` + +Width `W` must evenly contain all packed fields. Use padding bits to align to hex-char boundaries (multiples of 4 bits). + +## xsim_run.tcl + +```tcl +# Compile RTL dependencies (order matters for submodule hierarchy) +xvlog -sv -include_dirs . /.v +xvlog -sv -include_dirs . /.v + +# Compile testbench +xvlog -sv /tb__xsim.v + +# Elaborate +xelab tb__xsim -s + +# Run +xsim -R +``` + +### CRITICAL: Include Directories +- If ANY compiled file uses `` `include "sync_rtl/common/defines.vh" `` (or any relative include), add `-include_dirs .` to ALL `xvlog` invocations in that TCL +- This ensures Vivado resolves paths relative to the project root + +## gen_vectors.py + +- **Stdlib only**: Use only Python standard library modules (`hashlib`, `os`, `sys`, `random`, `math`) +- **Output**: Write hex vectors to `vectors/_input.hex`, one packed hex number per line +- **Self-contained**: Should be runnable standalone (`python3 gen_vectors.py`) +- **Bit ordering**: Match RTL FIPS 202 bit ordering — seed[0] = first bit into sponge. For Python hashlib, this means `bytes.fromhex(seed_hex)[::-1]` (reverse byte order) + +## Common Mistakes + +1. **Forgetting `-include_dirs .`**: DUTs using `` `include "sync_rtl/common/defines.vh" `` will fail compilation if the project root is not in the include path +2. **Using `=` for DUT drives**: All DUT input assignments in `initial` blocks must use `<=`, not `=`, to avoid race conditions +3. **Missing timeout watchdog**: If the DUT deadlocks, the simulation will hang forever without a watchdog +4. **Bit order mismatch**: Python hashlib and Verilog RTL may have different bit ordering for SHA3/SHAKE operations — verify with known test vectors +5. **rush_n polarity**: The reset is active-low (`rst_n`), not active-high — held to 0 for reset, 1 for normal operation