chore(tb): remove Verilator TBs + framework; parallelize XSIM runs

Verilator is no longer used (all verification is via Vivado XSIM). Remove:
- 10 per-module tb_*.cpp Verilator testbenches
- the entire test_framework/ Verilator harness (lib/, run_all.py, config.json,
  per-module test_plan.json/gen_vectors.py, golden vectors, reports)
- stale specs: verilator-conventions.md, test_framework/structure.md
  (index.md updated to drop the Verilator entry)

Parallelize run_tb.sh K x case execution (modules stay serial):
- new run_xsim_jobs helper: compile+elaborate once (serial, populates the
  shared xsim.dir), then run each (K,case) xsim in its own private workdir
  with a COPY of xsim.dir (~1MB) so concurrent same-snapshot runs don't clobber
  each other's runtime logs. Each workdir symlinks the repo sync_rtl tree so
  the TB's repo-relative $readmemh vector paths resolve.
- top/enc/dec runners refactored to build a (snapshot:K:case) spec list and
  hand it to run_xsim_jobs; ordered PASS/FAIL summary + per-job /tmp logs
  preserved. Bare './run_tb.sh top' now also takes the parallel path.

Speedup (20 cores): top full sweep 2:11 -> 0:51 (~2.6x), ~320% CPU.
Verified: top (11) / enc (9) / dec (9) all PASS; missing-vector runs still
fail (file-not-found guard -> exit 1).
This commit is contained in:
2026-06-29 16:05:06 +08:00
parent 030931d4e5
commit e46d2258d9
135 changed files with 347 additions and 22072 deletions

View File

@@ -3,12 +3,10 @@
## 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
1. [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) |

View File

@@ -1,62 +0,0 @@
# 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);
```

View File

@@ -1,66 +0,0 @@
# Test Framework Structure
## Directory Layout
```
test_framework/
├── run_all.py # CLI entry: --module, --case, --list, --quick
├── config.json # Verilator path, clock period, timeouts
├── lib/
│ ├── test_runner.py # Discovery, compile, run, compare pipeline
│ ├── sim_controller.py # Verilator compiles/run wrapper
│ ├── vector_gen.py # Base class for vector generators
│ ├── result_checker.py # Hex-file comparison
│ └── reporter.py # Terminal + HTML output
├── modules/
│ └── <module>/
│ ├── test_plan.json # Module test definition
│ ├── gen_vectors.py # Vector generator (subclass of VectorGenerator)
│ └── vectors/ # Generated hex files (auto-cleaned)
└── reports/
├── latest/
└── history/
```
## test_plan.json Schema
```json
{
"module": "<name>",
"rtl_top": "sync_rtl/<path>/<top>.v",
"rtl_deps": ["sync_rtl/common/<dep>.v"],
"tb_cpp": "sync_rtl/<path>/TB/tb_<top>.cpp",
"simulator": "verilator",
"timeout_s": 30,
"cases": [{
"id": "<case_id>",
"description": "<what this tests>",
"params": {},
"num_vectors": 50,
"tolerance": "bit_exact"
}]
}
```
- `rtl_top`: top module basename (without .v) is used as Verilator `--top-module`
- `rtl_deps`: listed before `rtl_top` in Verilator command line
- `tolerance`: "bit_exact" uses result_checker.py; otherwise delegates to gen_vectors.compare_results()
## Vector Generator Contract
Each module's `gen_vectors.py` must:
1. Subclass `VectorGenerator` from `test_framework.lib.vector_gen`
2. Implement `generate_one(params) -> dict` returning `{"input": {...}, "expected": {...}}`
3. Override `write_hex_file(vectors, filepath)` for input format
4. Override `write_expected_file(vectors, filepath)` for expected format
The framework auto-discovers the generator class by scanning for subclasses of VectorGenerator.
## Adding a New Module
1. Create `test_framework/modules/<name>/test_plan.json`
2. Create `test_framework/modules/<name>/gen_vectors.py`
3. Create `sync_rtl/<name>/<name>_sync.v` (RTL)
4. Create `sync_rtl/<name>/TB/tb_<name>.cpp` (Verilator C++ TB)
5. Run `python3 test_framework/run_all.py --list` to verify discovery
6. Run `python3 test_framework/run_all.py --module <name>` to test