Files
mlkem-sync/.trellis/spec/rtl/verilator-conventions.md
FallenSigh 8fdf944555 feat: init mlkem project with Verilator test framework
- sync_rtl/common/: skid_buffer, pipeline_reg, defines (valid/ready)
- sync_rtl/mod_add/: modular adder example with Verilator C++ TB
- test_framework/: Python-driven Verilator compile/sim/compare pipeline
- test_framework/modules/mod_add/: 50-vector test plan, full鏈路 PASS
- .trellis/spec/: RTL and test_framework conventions documented
2026-06-24 19:43:29 +08:00

1.8 KiB

Verilator RTL Conventions

Verilator Version

5.046 (Fedora package). Requires C++14 or later.

Compile Arguments

Do NOT add -CFLAGS -std=c++11. Verilator 5.046 uses C++14 features (""s string literal). Let Verilator use its default C++ standard.

Correct base command:

verilator -Wall --cc --build --timing --exe --top-module <top> <rtl> <tb.cpp>

Include Paths

All Verilog files use paths relative to project root (~/Dev/mlkem). Pass +incdir+<project_root> to Verilator so \include "sync_rtl/common/defines.vh"` resolves.

Clock Period

100MHz = 10ns. Defined centrally in sync_rtl/common/defines.vh:

`define CLK_PERIOD 10.0

Testbench Timing Protocol (Verilator C++)

Critical Rule: Always advance at least one posedge before checking signals

Setting a DUT input (e.g., dut->valid_i = 1) does NOT take effect until dut->eval() is called. A posedge requires: dut->clk = 1; dut->eval();.

pipeline_reg valid/ready timing

The pipeline_reg module's valid_o is HIGH for exactly ONE cycle between the posedge that captures data and the next posedge that consumes it (when ready_i=1).

Correct read pattern:

// 1. Drive input + posedge → data captured, valid_o → 1
dut->valid_i = 1;
posedge(dut);  // dut->clk = 1; eval(); dut->clk = 0; eval();
dut->valid_i = 0;

// 2. Read result NOW (valid_o is high, sum is valid)
printf("RESULT: %03X\n", dut->sum & 0xFFF);

// 3. Consume with next posedge → valid_o → 0
posedge(dut);

Anti-pattern: while(!dut->ready_o) without eval

Setting valid_i=1 and immediately checking ready_o in a while loop that starts with NO eval() will skip the first clock edge entirely if ready_o is already 1. Use do-while to guarantee at least one edge:

dut->valid_i = 1;
do {
    posedge(dut);
} while (!dut->ready_o);