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

151
run_tb.sh
View File

@@ -106,11 +106,68 @@ execute_tcl() {
rm -f "$tmp_script"
}
# ---- parallel xsim launcher --------------------------------------------------
# xsim writes runtime logs into xsim.dir/<snapshot>/, so two runs of the SAME
# snapshot clobber each other. To run (K,case) in parallel we give each job its
# own COPY of xsim.dir (~1 MB, cheap) under a private workdir, run xsim there,
# and tee output to a per-job log. Compile+elaborate stay serial (they populate
# the shared xsim.dir BEFORE any parallel run starts).
#
# run_xsim_jobs <module-tag> <pass-grep-regex> <pass-match-string> <spec>...
# each <spec> = "snapshot:K:CASE". Launches all specs in parallel, waits,
# then prints an ordered summary and returns nonzero if any job failed.
PAR_BASE="${TMPDIR:-/tmp}/run_tb_par_$$"
run_xsim_jobs() {
local mtag="$1" pgrep="$2" pmatch="$3"; shift 3
local specs=("$@")
local repo; repo="$(pwd)"
rm -rf "$PAR_BASE"; mkdir -p "$PAR_BASE"
# launch all jobs in parallel
local s snap k c wd
for s in "${specs[@]}"; do
IFS=: read -r snap k c <<< "$s"
wd="$PAR_BASE/${mtag}_k${k}_c${c}"
mkdir -p "$wd"
cp -r "$repo/xsim.dir" "$wd/xsim.dir"
# The TB loads vectors via repo-relative $readmemh paths
# (sync_rtl/top/TB/vectors/...). xsim resolves these against the run
# cwd, so symlink the repo's sync_rtl tree into each private workdir.
ln -sf "$repo/sync_rtl" "$wd/sync_rtl"
(
cd "$wd"
echo " xsim $snap -R -testplusarg CASE=$c"
xsim "$snap" -R -testplusarg "CASE=$c" --nolog > "$wd/run.out" 2>&1
) &
done
wait
# ordered summary + per-job log copy to /tmp/run_tb_<mtag>_k*_c*.log
local fail=0 pf nf log
for s in "${specs[@]}"; do
IFS=: read -r snap k c <<< "$s"
wd="$PAR_BASE/${mtag}_k${k}_c${c}"
log="/tmp/run_tb_${mtag}_k${k}_c${c}.log"
cp -f "$wd/run.out" "$log" 2>/dev/null
pf=$(grep -oE "$pgrep" "$log" | tail -1)
nf=$(grep -c 'cannot be opened' "$log")
echo " K=$k CASE=$c -> ${pf:-NORESULT} (file-not-found=$nf, log: $log)"
# pmatch == "PASS*" means "any PASS (Dn) stage tag"; else exact match.
if [ "$pmatch" = "PASS*" ]; then
{ [[ "$pf" == PASS* ]] && [ "$nf" -eq 0 ]; } || fail=1
else
{ [ "$pf" = "$pmatch" ] && [ "$nf" -eq 0 ]; } || fail=1
fi
done
rm -rf "$PAR_BASE"
return $fail
}
# Fast single-K / single-case path for the 'top' (ML-KEM KeyGen) module.
# Compiles the same RTL as the tcl, but elaborates ONLY the requested K and
# runs ONLY the requested CASE(s). Avoids the 3-snapshot + 11-run full sweep.
# runs the CASE(s) in parallel. Avoids the 3-snapshot + 11-run serial sweep.
run_top_selected() {
local tcl_file="$1" k="$2" csel="$3"
local tcl_file="$1" ksel="$2" csel="$3"
set +e # we do our own error handling / result parsing here
rm -rf xsim.dir .Xil
@@ -120,29 +177,22 @@ run_top_selected() {
eval "$cmd" || { echo "COMPILE FAILED: $cmd"; return 1; }
done < <(grep -E '^xvlog ' "$tcl_file")
# Cases per K: K=2 has 0..4, K=3/4 have 0..2.
local cases
if [ "$k" = "2" ]; then cases="0 1 2 3 4"; else cases="0 1 2"; fi
if [ -n "$csel" ]; then cases="$csel"; fi
echo " xelab tb_mlkem_kg_katK_xsim -generic_top KP=$k -s mlkem_kg_k$k --timescale 1ns/1ps"
xelab tb_mlkem_kg_katK_xsim -generic_top KP=$k -s mlkem_kg_k$k --timescale 1ns/1ps \
|| { echo "ELAB FAILED for K=$k"; return 1; }
local fail=0
for c in $cases; do
local log="/tmp/run_tb_top_k${k}_c${c}.log"
echo " xsim mlkem_kg_k$k -R -testplusarg CASE=$c"
echo "========================================" | tee "$log"
xsim "mlkem_kg_k$k" -R -testplusarg "CASE=$c" 2>&1 | tee -a "$log"
echo "========================================" | tee -a "$log"
local pf nf
pf=$(grep -oE 'PASS|FAIL' "$log" | tail -1)
nf=$(grep -c 'cannot be opened' "$log")
echo " K=$k CASE=$c -> ${pf:-NORESULT} (file-not-found=$nf, log: $log)"
{ [ "$pf" = "PASS" ] && [ "$nf" -eq 0 ]; } || fail=1
# K to run: requested one, or all of 2/3/4. Elaborate snapshots (serial).
local ks; if [ -n "$ksel" ]; then ks="$ksel"; else ks="2 3 4"; fi
local k specs=()
for k in $ks; do
echo " xelab tb_mlkem_kg_katK_xsim -generic_top KP=$k -s mlkem_kg_k$k --timescale 1ns/1ps"
xelab tb_mlkem_kg_katK_xsim -generic_top KP=$k -s mlkem_kg_k$k --timescale 1ns/1ps \
|| { echo "ELAB FAILED for K=$k"; return 1; }
# Cases per K: K=2 has 0..4, K=3/4 have 0..2.
local cases
if [ "$k" = "2" ]; then cases="0 1 2 3 4"; else cases="0 1 2"; fi
if [ -n "$csel" ]; then cases="$csel"; fi
local c
for c in $cases; do specs+=("mlkem_kg_k$k:$k:$c"); done
done
return $fail
run_xsim_jobs top 'PASS|FAIL' PASS "${specs[@]}"
}
# ML-KEM Encaps runner. Compiles the 'top' tcl xvlog lines (KeyGen datapath =
@@ -165,31 +215,21 @@ run_enc_selected() {
xvlog -sv --relax sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v \
|| { echo "ENC TB COMPILE FAILED"; return 1; }
# K to run: requested one, or all of 2/3/4.
# K to run: requested one, or all of 2/3/4. Elaborate snapshots (serial),
# then run all (K,case) in parallel.
local ks; if [ -n "$ksel" ]; then ks="$ksel"; else ks="2 3 4"; fi
local fail=0
local k specs=()
for k in $ks; do
echo " xelab tb_mlkem_enc_katK_xsim -generic_top KP=$k -s mlkem_enc_k$k --timescale 1ns/1ps"
xelab tb_mlkem_enc_katK_xsim -generic_top KP=$k -s mlkem_enc_k$k --timescale 1ns/1ps \
|| { echo "ELAB FAILED for K=$k"; fail=1; continue; }
# Encaps KAT cases per K: 0..2 (vectors enc_k{K}_c{0,1,2}_*).
|| { echo "ELAB FAILED for K=$k"; return 1; }
local cases="0 1 2"
if [ -n "$csel" ]; then cases="$csel"; fi
for c in $cases; do
local log="/tmp/run_tb_enc_k${k}_c${c}.log"
echo " xsim mlkem_enc_k$k -R -testplusarg CASE=$c"
echo "========================================" | tee "$log"
xsim "mlkem_enc_k$k" -R -testplusarg "CASE=$c" 2>&1 | tee -a "$log"
echo "========================================" | tee -a "$log"
local pf nf
pf=$(grep -oE 'PASS \(E7\)|FAIL \(E7\)' "$log" | tail -1)
nf=$(grep -c 'cannot be opened' "$log")
echo " K=$k CASE=$c -> ${pf:-NORESULT} (file-not-found=$nf, log: $log)"
{ [ "$pf" = "PASS (E7)" ] && [ "$nf" -eq 0 ]; } || fail=1
done
local c
for c in $cases; do specs+=("mlkem_enc_k$k:$k:$c"); done
done
return $fail
run_xsim_jobs enc 'PASS \(E7\)|FAIL \(E7\)' 'PASS (E7)' "${specs[@]}"
}
# ML-KEM Decaps runner. Same compile basis as enc (top tcl minus KeyGen TB) plus
@@ -210,29 +250,19 @@ run_dec_selected() {
|| { echo "DEC TB COMPILE FAILED"; return 1; }
local ks; if [ -n "$ksel" ]; then ks="$ksel"; else ks="2 3 4"; fi
local fail=0
local k specs=()
for k in $ks; do
echo " xelab tb_mlkem_dec_katK_xsim -generic_top KP=$k -s mlkem_dec_k$k --timescale 1ns/1ps"
xelab tb_mlkem_dec_katK_xsim -generic_top KP=$k -s mlkem_dec_k$k --timescale 1ns/1ps \
|| { echo "ELAB FAILED for K=$k"; fail=1; continue; }
|| { echo "ELAB FAILED for K=$k"; return 1; }
local cases="0 1 2"
if [ -n "$csel" ]; then cases="$csel"; fi
for c in $cases; do
local log="/tmp/run_tb_dec_k${k}_c${c}.log"
echo " xsim mlkem_dec_k$k -R -testplusarg CASE=$c"
echo "========================================" | tee "$log"
xsim "mlkem_dec_k$k" -R -testplusarg "CASE=$c" 2>&1 | tee -a "$log"
echo "========================================" | tee -a "$log"
local pf nf
# match the highest-stage result line: 'K=.. CASE .. PASS (Dn): ...'
pf=$(grep -oE 'PASS \(D[0-7]\)|FAIL \(D[0-7]\)' "$log" | tail -1)
nf=$(grep -c 'cannot be opened' "$log")
echo " K=$k CASE=$c -> ${pf:-NORESULT} (file-not-found=$nf, log: $log)"
{ [[ "$pf" == PASS* ]] && [ "$nf" -eq 0 ]; } || fail=1
done
local c
for c in $cases; do specs+=("mlkem_dec_k$k:$k:$c"); done
done
return $fail
# match the highest-stage result line: 'K=.. CASE .. PASS (Dn): ...'
run_xsim_jobs dec 'PASS \(D[0-7]\)|FAIL \(D[0-7]\)' 'PASS*' "${specs[@]}"
}
if [ "$MODULE" = "enc" ]; then
@@ -245,7 +275,8 @@ if [ "$MODULE" = "dec" ]; then
exit $?
fi
if [ "$MODULE" = "top" ] && [ -n "$SEL_K" ]; then
if [ "$MODULE" = "top" ]; then
# run_top_selected handles all-K (no SEL_K) or a single K, cases in parallel.
run_top_selected "$TCL_FILE" "$SEL_K" "$SEL_CASE"
exit $?
fi