diff --git a/.claude/plans/encaps_plan.md b/.claude/plans/encaps_plan.md index 7f950ae..d5da15d 100644 --- a/.claude/plans/encaps_plan.md +++ b/.claude/plans/encaps_plan.md @@ -71,7 +71,7 @@ ct_bytes_rt = c1_bytes_rt + c2_bytes_rt; // 768 / 1088 / 1568 - **E2 — y/e1/e2 采样 (η1/η2)**:ST_ENC_C,nonce 0..2K,eta 在 e1/e2 切 2。dbg 验证 y/e1/e2(对 ml-kem-r)。 - **E3 — ŷ = NTT(y)**:ST_ENC_N,mode=0,就地。dbg 对 y_hat。 - **E4 — u = INTT(Σ Â[j][i]∘ŷ[j]) + e1**:ST_ENC_U,poly_mul + 累加 + INTT(mode=1) + 加 e1。**转置寻址 slot=j·K+i**。dbg 对 u。 -- **E5 — Compress_du + byteEncode_du → c1** ✅:comp_decomp(mode0,d=du) + 通用 LSB-first 打包器(ST_ENC_C1)写 ct_bram c1 区。dbg_ct tap 比 ct[0..c1_bytes]==KAT.ct 前缀,K=2/3/4 全过(含 K=4 du=11 跨字节)。runner = `./run_enc.sh K CASE`。 +- **E5 — Compress_du + byteEncode_du → c1** ✅:comp_decomp(mode0,d=du) + 通用 LSB-first 打包器(ST_ENC_C1)写 ct_bram c1 区。dbg_ct tap 比 ct[0..c1_bytes]==KAT.ct 前缀,K=2/3/4 全过(含 K=4 du=11 跨字节)。runner = `./run_tb.sh enc [K] [CASE]`(已并入 run_tb;旧 run_enc.sh 已删)。 - **E6 — v = INTT(Σ t̂[j]∘ŷ[j]) + e2 + mu** ✅:ST_ENC_C1→ST_ENC_TDEC(t̂ 解码进 **bank_a** slot j·K)→ST_ENC_E2MV(e2 从 bank_t[0] 搬到 **bank_a[1]**)→ST_ENC_V(复用 u_* MAC/INTT/ADD,u_row≡0)。mu[w]=m_r[w]?1665:0。v 落 bank_t[UPSUM],dbg slot 9(K=2)。dbg 对 v==ml-kem-r golden 全过。**踩坑:e2 搬迁曾 off-by-one(写 e2[i+1] 到 slot i),已修(em_widx==em_ridx,写延后 1 拍对齐 bram 读)。** verify_e1 已废弃(TDEC 覆盖 bank_a 的 A_hat;A_hat 由 E4 转置 MAC 间接验证)。 - **E7 — Compress_dv + byteEncode_dv → c2 + 端到端 KAT** ✅:ST_ENC_V→ST_ENC_C2,复用 E5 打包器(cp_d=dv,coeff 源 bank_t[UPSUM]=v,poly 数=1),cp_wa 接着 c1_bytes 续写(不重置)。FSM:V→C2→DONE。verify_e7 比全长 ct==KAT.ct。**全 K(2/3/4)、多 case 端到端通过:ct==KAT.ct && ss==KAT.ss。Encaps 完成。** diff --git a/run_enc.sh b/run_enc.sh deleted file mode 100755 index 0f5c40a..0000000 --- a/run_enc.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# run_enc.sh - Run Vivado XSIM for the ML-KEM Encaps testbench (tb_mlkem_enc_katK). -# -# Usage: ./run_enc.sh [K] [CASE] -# ./run_enc.sh # K=2 CASE=0 -# ./run_enc.sh 3 1 # K=3 CASE=1 -# -# Compiles the same RTL as the KeyGen tcl (mlkem_top + leaves), elaborates the -# encaps TB for the requested K, and runs the requested CASE. - -set -e -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -VIVADO_SETTINGS="/opt/Xilinx/Vivado/2019.2/settings64.sh" - -SEL_K="${1:-2}" -SEL_CASE="${2:-0}" - -source "$VIVADO_SETTINGS" -export LD_PRELOAD="${LD_PRELOAD}${LD_PRELOAD:+:}/usr/lib64/libtinfo.so.5" - -cd "$SCRIPT_DIR" -rm -rf xsim.dir .Xil - -# Compile every xvlog RTL line from the KeyGen tcl (same datapath, now incl. -# pipeline_reg + comp_decomp_sync + mlkem_top), skipping only the KeyGen TB. -TCL="sync_rtl/top/TB/xsim_run.tcl" -while read -r cmd; do - [[ "$cmd" == *tb_mlkem_kg_katK* ]] && continue # skip the KeyGen TB - echo " $cmd" - eval "$cmd" || { echo "COMPILE FAILED: $cmd"; exit 1; } -done < <(grep -E '^xvlog ' "$TCL") - -# Compile the encaps TB -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 "TB COMPILE FAILED"; exit 1; } - -echo " xelab tb_mlkem_enc_katK_xsim -generic_top KP=$SEL_K -s mlkem_enc_k$SEL_K --timescale 1ns/1ps" -xelab tb_mlkem_enc_katK_xsim -generic_top KP=$SEL_K -s mlkem_enc_k$SEL_K --timescale 1ns/1ps \ - || { echo "ELAB FAILED for K=$SEL_K"; exit 1; } - -LOG="/tmp/run_enc_k${SEL_K}_c${SEL_CASE}.log" -echo " xsim mlkem_enc_k$SEL_K -R -testplusarg CASE=$SEL_CASE" -xsim "mlkem_enc_k$SEL_K" -R -testplusarg "CASE=$SEL_CASE" 2>&1 | tee "$LOG" diff --git a/run_tb.sh b/run_tb.sh index 223744e..bda2aca 100755 --- a/run_tb.sh +++ b/run_tb.sh @@ -7,11 +7,16 @@ # ./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 --list # -# For the 'top' module, K (2/3/4) and CASE select a single KAT run so you can -# iterate quickly instead of elaborating 3 snapshots and running all 11 cases. -# Other modules ignore the extra args and run their xsim_run.tcl verbatim. +# 'top' (KeyGen) and 'enc' (Encaps) share the same RTL datapath; both 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. +# For these two, 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/ @@ -29,6 +34,7 @@ if [ "$1" = "--list" ]; then echo " $name" fi done + echo " enc (ML-KEM Encaps; shares the 'top' RTL/tcl, enc testbench)" exit 0 fi @@ -39,9 +45,15 @@ if [ -z "$1" ]; then fi MODULE="$1" -SEL_K="$2" # optional: 2/3/4 (top module only) -SEL_CASE="$3" # optional: KAT case index (top module only) -TB_DIR="$SCRIPT_DIR/sync_rtl/$MODULE/TB" +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. +if [ "$MODULE" = "enc" ]; 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 @@ -129,9 +141,61 @@ run_top_selected() { 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 +} + +if [ "$MODULE" = "enc" ]; then + run_enc_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" +execute_tcl "$TCL_FILE" \ No newline at end of file diff --git a/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v b/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v index 09cbd88..6622966 100644 --- a/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v +++ b/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v @@ -254,16 +254,34 @@ module tb_mlkem_enc_katK_xsim; integer be; begin be = 0; + // Dump the hardware-produced ct (read from ct_bram via dbg_ct tap), + // then compare to KAT.ct byte-exact. The $write loop prints the whole + // ciphertext on one line (byte 0 first), same format as ml-kem-r's + // encaps_io example, so the two can be eyeballed / diffed. + $write(" ct = "); for (i = 0; i < CTB; i = i + 1) begin dbg_ct_idx_i = i[10:0]; @(posedge clk); @(posedge clk); @(posedge clk); + $write("%02x", dbg_ct_o); if (dbg_ct_o !== ct_b[i]) begin - if (be < 8) $display(" CT[%0d] got=%02x exp=%02x", i, dbg_ct_o, ct_b[i]); be = be + 1; end end + $write("\n"); if (be == 0) $display("K=%0d CASE %0d PASS (E7): ct (%0d B) == KAT.ct [ct==KAT.ct && ss==KAT.ss]", KP, casenum, CTB); - else $display("K=%0d CASE %0d FAIL (E7): %0d ct byte mismatches", KP, casenum, be); + else begin + $display("K=%0d CASE %0d FAIL (E7): %0d ct byte mismatches", KP, casenum, be); + // re-scan to print the first few mismatching byte positions + be = 0; + for (i = 0; i < CTB; i = i + 1) begin + dbg_ct_idx_i = i[10:0]; + @(posedge clk); @(posedge clk); @(posedge clk); + if (dbg_ct_o !== ct_b[i]) begin + if (be < 8) $display(" CT[%0d] got=%02x exp=%02x", i, dbg_ct_o, ct_b[i]); + be = be + 1; + end + end + end end endtask endmodule