Files
mlkem-sync/run_tb.sh
FallenSigh 030931d4e5 feat(dec): Decaps D0 - op_i widen + dk/c load + parse
Scaffolding for ML-KEM Decaps (FIPS 203 Alg 18):
- op_i widened to 2-bit: 00=KeyGen, 01=Encaps, 10=Decaps (op_r too).
- New ST_DEC_LOAD state (D0: settles to DONE so load/parse is dbg-checkable).
- dk (=sk) streamed via dk_in_*; load logic routes each byte by region:
  [0,384K)->dk_pke (dkp_bram), [384K,768K+32)->ek_pke (ek_bram),
  [768K+32,+32)->H(ek) (hek_r), [768K+64,+32)->z (z_r). Routing uses the
  LIVE k_i input, not start-captured k_r (dk is streamed before start_i).
- c (=ct) streamed via c_in_* into a SEPARATE c_in_bram, so the computed c'
  (ct_bram) can later be compared against original c and J(z||c) can read c.
- New dbg taps: dbg_mprime_o/dbg_kbar_o/dbg_decz_o/dbg_dech_o.

TB: tb_mlkem_dec_katK_xsim verifies dk parse (H(ek), z, ek_pke/dk_pke BRAM
round-trip). gen_decaps_vectors.py emits dec_k{K}_c{N}_{dk,ct,ss,ctn,ssn}.hex
from the NIST KAT. run_tb.sh gains a 'dec' module (mirrors 'enc').

Regression fix: old KeyGen/Encaps TBs didn't connect the new input ports,
floating them to X and corrupting the ek/dkp write muxes -> tied off
dk_in_*/c_in_*/new dbg taps in both.

Verified: dec D0 K=2/3/4 PASS; KeyGen K=2 + Encaps K=2 unregressed.
2026-06-29 15:22:34 +08:00

253 lines
9.7 KiB
Bash
Executable File

#!/bin/bash
# run_tb.sh - Run Vivado XSIM testbench for a module
#
# Usage: ./run_tb.sh <module> [K] [CASE]
# ./run_tb.sh mod_add # run the module's full xsim_run.tcl
# ./run_tb.sh top # ML-KEM KeyGen: all K, all cases
# ./run_tb.sh top 2 # only K=2 (ML-KEM-512), all its cases
# ./run_tb.sh top 2 3 # only K=2, only CASE=3
# ./run_tb.sh top 4 0 # only K=4 (ML-KEM-1024), CASE=0
# ./run_tb.sh enc # ML-KEM Encaps: all K, all cases (0..2)
# ./run_tb.sh enc 2 # Encaps K=2, all its cases
# ./run_tb.sh enc 4 1 # Encaps K=4, only CASE=1
# ./run_tb.sh dec # ML-KEM Decaps: all K, all cases (0..2)
# ./run_tb.sh dec 2 0 # Decaps K=2, only CASE=0
# ./run_tb.sh --list
#
# 'top' (KeyGen), 'enc' (Encaps) and 'dec' (Decaps) share the same RTL datapath;
# all compile the xvlog lines from sync_rtl/top/TB/xsim_run.tcl. They differ only
# in the testbench: top -> tb_mlkem_kg_katK_xsim, enc -> tb_mlkem_enc_katK_xsim,
# dec -> tb_mlkem_dec_katK_xsim.
# For these, K (2/3/4) and CASE select a single KAT run so you can iterate
# quickly. Other modules ignore the extra args and run their xsim_run.tcl verbatim.
#
# Prerequisites:
# Vivado 2019.2 at /opt/Xilinx/Vivado/2019.2/
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VIVADO_SETTINGS="/opt/Xilinx/Vivado/2019.2/settings64.sh"
if [ "$1" = "--list" ]; then
echo "Available modules:"
for d in "$SCRIPT_DIR"/sync_rtl/*/TB; do
if [ -f "$d/xsim_run.tcl" ]; then
name=$(basename "$(dirname "$d")")
echo " $name"
fi
done
echo " enc (ML-KEM Encaps; shares the 'top' RTL/tcl, enc testbench)"
echo " dec (ML-KEM Decaps; shares the 'top' RTL/tcl, dec testbench)"
exit 0
fi
if [ -z "$1" ]; then
echo "Usage: $0 <module_name>"
echo " $0 --list"
exit 1
fi
MODULE="$1"
SEL_K="$2" # optional: 2/3/4 (top/enc modules only)
SEL_CASE="$3" # optional: KAT case index (top/enc modules only)
# 'enc' is not its own RTL dir; it reuses the 'top' KeyGen tcl compile list
# (same datapath) and swaps in the encaps testbench. 'dec' likewise (Decaps).
if [ "$MODULE" = "enc" ] || [ "$MODULE" = "dec" ]; then
TB_DIR="$SCRIPT_DIR/sync_rtl/top/TB"
else
TB_DIR="$SCRIPT_DIR/sync_rtl/$MODULE/TB"
fi
TCL_FILE="$TB_DIR/xsim_run.tcl"
if [ ! -f "$TCL_FILE" ]; then
echo "ERROR: No xsim_run.tcl found for module '$MODULE'"
echo " Expected: $TCL_FILE"
echo " Run '$0 --list' to see available modules"
exit 1
fi
# Source Vivado environment
if [ -f "$VIVADO_SETTINGS" ]; then
source "$VIVADO_SETTINGS"
else
echo "ERROR: Vivado settings not found at $VIVADO_SETTINGS"
exit 1
fi
# Fix ncurses compatibility for Vivado 2019.2 on modern Linux
export LD_PRELOAD="${LD_PRELOAD}${LD_PRELOAD:+:}/usr/lib64/libtinfo.so.5"
cd "$SCRIPT_DIR"
echo "========================================"
echo " Running testbench for: $MODULE"
echo "========================================"
# Save TCL variables as bash variables, run commands with substitution
execute_tcl() {
local tcl_file="$1"
local tmp_script="/tmp/run_tb_$$.sh"
# Convert TCL 'set VAR value' to bash 'VAR=value'
{
grep -E '^set [A-Z0-9_]+ ' "$tcl_file" | while read -r _ var value; do
echo "${var}=${value}"
done
# Extract xvlog, xelab, xsim commands with bash variable expansion
grep -E '^(xvlog|xelab|xsim) ' "$tcl_file" | while read -r cmd; do
printf 'echo " %s"\n' "$cmd"
printf '%s 2>&1\n' "$cmd"
done
} > "$tmp_script"
chmod +x "$tmp_script"
. "$tmp_script"
rm -f "$tmp_script"
}
# 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.
run_top_selected() {
local tcl_file="$1" k="$2" csel="$3"
set +e # we do our own error handling / result parsing here
rm -rf xsim.dir .Xil
# Compile every xvlog line from the tcl, verbatim.
while read -r cmd; do
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
done
return $fail
}
# ML-KEM Encaps runner. Compiles the 'top' tcl xvlog lines (KeyGen datapath =
# Encaps datapath) MINUS the KeyGen TB, plus the encaps TB, then elaborates the
# requested K and runs the requested CASE(s). Mirrors run_top_selected.
run_enc_selected() {
local tcl_file="$1" ksel="$2" csel="$3"
set +e
rm -rf xsim.dir .Xil
# Compile every xvlog line from the tcl EXCEPT the KeyGen TB (the encaps TB
# replaces it). The tcl already includes pipeline_reg + comp_decomp_sync.
while read -r cmd; do
[[ "$cmd" == *tb_mlkem_kg_katK* ]] && continue
eval "$cmd" || { echo "COMPILE FAILED: $cmd"; return 1; }
done < <(grep -E '^xvlog ' "$tcl_file")
echo " xvlog -sv --relax sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v"
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.
local ks; if [ -n "$ksel" ]; then ks="$ksel"; else ks="2 3 4"; fi
local fail=0
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}_*).
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
done
return $fail
}
# ML-KEM Decaps runner. Same compile basis as enc (top tcl minus KeyGen TB) plus
# the decaps TB. Result line matches the final-stage PASS tag (D0..D7).
run_dec_selected() {
local tcl_file="$1" ksel="$2" csel="$3"
set +e
rm -rf xsim.dir .Xil
while read -r cmd; do
[[ "$cmd" == *tb_mlkem_kg_katK* ]] && continue
eval "$cmd" || { echo "COMPILE FAILED: $cmd"; return 1; }
done < <(grep -E '^xvlog ' "$tcl_file")
echo " xvlog -sv --relax sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v"
xvlog -sv --relax sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v \
|| { echo "DEC TB COMPILE FAILED"; return 1; }
local ks; if [ -n "$ksel" ]; then ks="$ksel"; else ks="2 3 4"; fi
local fail=0
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; }
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
done
return $fail
}
if [ "$MODULE" = "enc" ]; then
run_enc_selected "$TCL_FILE" "$SEL_K" "$SEL_CASE"
exit $?
fi
if [ "$MODULE" = "dec" ]; then
run_dec_selected "$TCL_FILE" "$SEL_K" "$SEL_CASE"
exit $?
fi
if [ "$MODULE" = "top" ] && [ -n "$SEL_K" ]; then
run_top_selected "$TCL_FILE" "$SEL_K" "$SEL_CASE"
exit $?
fi
execute_tcl "$TCL_FILE"