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
5.9 KiB
5.9 KiB
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
// 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 ininitialblocks - Use blocking assignment (
=) for local variable initialization only - Follow valid/ready handshake: assert
valid_i, waitready_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_CYCLESloop)
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
# 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 ALLxvloginvocations 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
- Forgetting
-include_dirs .: DUTs using`include "sync_rtl/common/defines.vh"will fail compilation if the project root is not in the include path - Using
=for DUT drives: All DUT input assignments ininitialblocks must use<=, not=, to avoid race conditions - Missing timeout watchdog: If the DUT deadlocks, the simulation will hang forever without a watchdog
- Bit order mismatch: Python hashlib and Verilog RTL may have different bit ordering for SHA3/SHAKE operations — verify with known test vectors
- rush_n polarity: The reset is active-low (
rst_n), not active-high — held to 0 for reset, 1 for normal operation