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
This commit is contained in:
62
.trellis/spec/rtl/verilator-conventions.md
Normal file
62
.trellis/spec/rtl/verilator-conventions.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 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`:
|
||||
```verilog
|
||||
`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:
|
||||
```cpp
|
||||
// 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:
|
||||
```cpp
|
||||
dut->valid_i = 1;
|
||||
do {
|
||||
posedge(dut);
|
||||
} while (!dut->ready_o);
|
||||
```
|
||||
Reference in New Issue
Block a user