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:
@@ -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) |
|
||||
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user