docs(spec): add XSIM testbench conventions to RTL spec
Document Vivado XSIM Verilog testbench conventions: - File naming, directory structure, TB template - Clock/reset patterns, valid/ready protocol - Vector format for - xsim_run.tcl conventions with -include_dirs requirement - gen_vectors.py conventions (stdlib only, bit ordering) - Common mistakes checklist
This commit is contained in:
14
.trellis/spec/rtl/index.md
Normal file
14
.trellis/spec/rtl/index.md
Normal file
@@ -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) |
|
||||||
181
.trellis/spec/rtl/xsim-tb-conventions.md
Normal file
181
.trellis/spec/rtl/xsim-tb-conventions.md
Normal file
@@ -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/<module>/
|
||||||
|
├── <module>.v # RTL source
|
||||||
|
├── TB/
|
||||||
|
│ ├── tb_<module>.cpp # Verilator C++ testbench (optional)
|
||||||
|
│ ├── tb_<module>_xsim.v # XSIM Verilog testbench
|
||||||
|
│ ├── gen_vectors.py # Python vector generator (stdlib only)
|
||||||
|
│ ├── xsim_run.tcl # Vivado compile+elaborate+simulate script
|
||||||
|
│ └── vectors/
|
||||||
|
│ └── <module>_input.hex # Test input vectors for $readmemh
|
||||||
|
```
|
||||||
|
|
||||||
|
## TB File Template
|
||||||
|
|
||||||
|
```verilog
|
||||||
|
// tb_<module>_xsim.v - Vivado XSIM testbench for <module>
|
||||||
|
// Usage:
|
||||||
|
// xvlog -sv <deps> <module>.v tb_<module>_xsim.v
|
||||||
|
// xelab tb_<module>_xsim -s <snapshot>
|
||||||
|
// xsim <snapshot> -R
|
||||||
|
|
||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
module tb_<module>_xsim;
|
||||||
|
|
||||||
|
parameter VECTOR_FILE = "sync_rtl/<module>/TB/vectors/<module>_input.hex";
|
||||||
|
parameter TIMEOUT_CYCLES = 10000;
|
||||||
|
|
||||||
|
// DUT signals (reg for inputs, wire for outputs)
|
||||||
|
reg clk, rst_n;
|
||||||
|
// ... module-specific ports ...
|
||||||
|
|
||||||
|
// DUT instantiation
|
||||||
|
<module> 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 . <rtl_dir>/<submodule>.v
|
||||||
|
xvlog -sv -include_dirs . <rtl_dir>/<dut>.v
|
||||||
|
|
||||||
|
# Compile testbench
|
||||||
|
xvlog -sv <tb_dir>/tb_<module>_xsim.v
|
||||||
|
|
||||||
|
# Elaborate
|
||||||
|
xelab tb_<module>_xsim -s <snapshot>
|
||||||
|
|
||||||
|
# Run
|
||||||
|
xsim <snapshot> -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/<module>_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
|
||||||
Reference in New Issue
Block a user