feat(enc): Encaps E5 - c1 = byteEncode_du(Compress_du(u))
ST_ENC_C1: per-coeff Compress_du via comp_decomp_sync (mode 0) then LSB-first byte packing into ct_bram. 5-phase micro-seq reads u[cp_poly] from bank_se (rel K+poly), feeds the compressor (1-cyc pipe), appends du bits to cp_buf, and drains whole bytes. Each poly = 256*du bits (whole bytes) so the bit buffer empties at every poly boundary. ST_ENC_U now advances to ST_ENC_C1 (was ST_DONE). TB: verify_e5 compares ct_bram[0..c1_bytes-1] to the KAT.ct prefix via the dbg_ct tap. run_enc.sh: encaps TB runner (compiles comp_decomp_sync which the KeyGen tcl omits). Verified K=2/3/4 c1 == KAT.ct prefix (640/960/1408 B; K=4 du=11 cross-byte path), K=2 cases 0-2.
This commit is contained in:
@@ -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 + 通用打包器写 ct_bram c1 区。dbg 对 ct[0..c1_bytes]==KAT.ct 前缀。
|
||||
- **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`。
|
||||
- **E6 — v = INTT(Σ t̂[j]∘ŷ[j]) + e2 + mu**:ST_ENC_V,mu 流内由 m bit 生成。dbg 对 v。
|
||||
- **E7 — Compress_dv + byteEncode_dv → c2 + 端到端 KAT**:写 ct c2 区。干净 TB 喂 ek/m,比 ct(全长)==KAT.ct 且 ss==KAT.ss,K=2/3/4 各 count=0..N。
|
||||
|
||||
|
||||
56
run_enc.sh
Executable file
56
run_enc.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/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), verbatim.
|
||||
TCL="sync_rtl/top/TB/xsim_run.tcl"
|
||||
while read -r cmd; do
|
||||
[[ "$cmd" == *tb_mlkem_kg_katK* ]] && continue # skip the KeyGen TB
|
||||
[[ "$cmd" == *mlkem_top.v* ]] && continue # defer top until comp_decomp compiled
|
||||
echo " $cmd"
|
||||
eval "$cmd" || { echo "COMPILE FAILED: $cmd"; exit 1; }
|
||||
done < <(grep -E '^xvlog ' "$TCL")
|
||||
|
||||
# Encaps-only leaf: comp_decomp_sync (compress) + its pipeline_reg dep.
|
||||
for f in sync_rtl/common/pipeline_reg.v sync_rtl/comp_decomp/comp_decomp_sync.v; do
|
||||
[[ -f "$f" ]] || continue
|
||||
echo " xvlog -sv --relax -i . $f"
|
||||
xvlog -sv --relax -i . "$f" || { echo "COMPILE FAILED: $f"; exit 1; }
|
||||
done
|
||||
|
||||
# Now compile the top (depends on comp_decomp_sync).
|
||||
echo " xvlog -sv --relax -i . sync_rtl/top/mlkem_top.v"
|
||||
xvlog -sv --relax -i . sync_rtl/top/mlkem_top.v \
|
||||
|| { echo "TOP COMPILE FAILED"; exit 1; }
|
||||
|
||||
# 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"
|
||||
@@ -10,6 +10,8 @@ module tb_mlkem_enc_katK_xsim;
|
||||
parameter KP = 2;
|
||||
localparam EKB = 384*KP + 32; // ek (=pk) bytes
|
||||
localparam CTB = (KP==4) ? 1568 : (32*(10*KP+4)); // ct bytes: K2 768,K3 1088,K4 1568
|
||||
localparam DU = (KP==4) ? 11 : 10; // compression du
|
||||
localparam C1B = 32*DU*KP; // c1 byte count: K2 640,K3 960,K4 1408
|
||||
|
||||
reg clk=0, rst_n=0, start_i=0;
|
||||
reg [2:0] k_i;
|
||||
@@ -41,8 +43,9 @@ module tb_mlkem_enc_katK_xsim;
|
||||
reg [7:0] ek_b [0:EKB-1];
|
||||
reg [7:0] m_b [0:31];
|
||||
reg [7:0] ss_b [0:31];
|
||||
reg [7:0] ct_b [0:CTB-1];
|
||||
integer c, i, errors, casenum, j;
|
||||
reg [8*80-1:0] tag, ekfile, mfile, ssfile;
|
||||
reg [8*80-1:0] tag, ekfile, mfile, ssfile, ctfile;
|
||||
|
||||
initial begin
|
||||
if (!$value$plusargs("CASE=%d", casenum)) casenum = 0;
|
||||
@@ -50,9 +53,11 @@ module tb_mlkem_enc_katK_xsim;
|
||||
$sformat(ekfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ek.hex", tag, casenum);
|
||||
$sformat(mfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_m.hex", tag, casenum);
|
||||
$sformat(ssfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ss.hex", tag, casenum);
|
||||
$sformat(ctfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ct.hex", tag, casenum);
|
||||
$readmemh(ekfile, ek_b);
|
||||
$readmemh(mfile, m_b);
|
||||
$readmemh(ssfile, ss_b);
|
||||
$readmemh(ctfile, ct_b);
|
||||
|
||||
// build m_i: byte i in m_i[8*i +: 8]
|
||||
m_i = 256'd0;
|
||||
@@ -102,6 +107,9 @@ module tb_mlkem_enc_katK_xsim;
|
||||
verify_e3;
|
||||
verify_e4;
|
||||
end
|
||||
// E5: c1 = byteEncode_du(Compress_du(u)) must equal KAT.ct[0..C1B-1].
|
||||
// Runs for every K/case (ct_b is the full KAT ciphertext).
|
||||
verify_e5;
|
||||
$finish;
|
||||
end
|
||||
|
||||
@@ -193,4 +201,24 @@ module tb_mlkem_enc_katK_xsim;
|
||||
end
|
||||
endtask
|
||||
initial begin #120000000; $display("FAIL: global timeout"); $finish; end
|
||||
|
||||
// E5: read ct_bram bytes 0..C1B-1 via dbg_ct tap; compare to KAT.ct prefix.
|
||||
// dbg_ct_idx_i -> ct_rd_addr (1-cyc registered read) -> dbg_ct_o (comb tap):
|
||||
// wait 3 cycles per byte (same cadence as the coeff readback tasks).
|
||||
task verify_e5;
|
||||
integer be;
|
||||
begin
|
||||
be = 0;
|
||||
for (i = 0; i < C1B; 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(" C1[%0d] got=%02x exp=%02x", i, dbg_ct_o, ct_b[i]);
|
||||
be = be + 1;
|
||||
end
|
||||
end
|
||||
if (be == 0) $display("K=%0d CASE %0d PASS (E5): c1 (%0d B) == KAT.ct prefix", KP, casenum, C1B);
|
||||
else $display("K=%0d CASE %0d FAIL (E5): %0d c1 byte mismatches", KP, casenum, be);
|
||||
end
|
||||
endtask
|
||||
endmodule
|
||||
|
||||
@@ -128,11 +128,21 @@ module mlkem_top #(
|
||||
reg td_we;
|
||||
reg [9:0] td_wa; // PT_AW=10 (declared below; literal here)
|
||||
reg [11:0] td_wd;
|
||||
// ct readback tap (ct_bram added in E5/E7); tied off until then.
|
||||
/* verilator lint_off UNUSEDSIGNAL */
|
||||
wire [10:0] dbg_ct_idx_unused = dbg_ct_idx_i;
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
assign dbg_ct_o = 8'd0;
|
||||
|
||||
// ---- ct_bram: ciphertext byte buffer (<=1568 B). Written by E5/E7
|
||||
// (compress + byteEncode_du/dv), read back via dbg_ct tap. ----
|
||||
wire [10:0] ct_rd_addr;
|
||||
wire [7:0] ct_rd_data;
|
||||
reg ct_we;
|
||||
reg [10:0] ct_wa;
|
||||
reg [7:0] ct_wd;
|
||||
sd_bram #(.W(8), .D(2048), .A(11)) u_ct_bram (
|
||||
.clk(clk),
|
||||
.rd_addr(ct_rd_addr), .rd_data(ct_rd_data),
|
||||
.wr_en(ct_we), .wr_addr(ct_wa), .wr_data(ct_wd)
|
||||
);
|
||||
assign ct_rd_addr = dbg_ct_idx_i;
|
||||
assign dbg_ct_o = ct_rd_data;
|
||||
|
||||
// ================================================================
|
||||
// Polynomial storage, sized for KMAX (worst case). Runtime k uses a
|
||||
@@ -263,6 +273,7 @@ module mlkem_top #(
|
||||
(st == ST_ENC_U) ?
|
||||
((u_sub == 2'd0) ? u_pm_b_full[PSE_AW-1:0] : // MAC: y_hat[u_j]
|
||||
u_add_e1rd[PSE_AW-1:0]) : // ADD: e1[u_row] read
|
||||
(st == ST_ENC_C1) ? cp_se_full[PSE_AW-1:0] : // C1: u[cp_poly]
|
||||
dbg_se_addr[PSE_AW-1:0];
|
||||
// bank_se write: KeyGen ST_C CBD (s/e), Encaps ST_ENC_C CBD (y/e1, c_poly<2K;
|
||||
// e2 at c_poly==2K goes to bank_t instead), ST_N/ST_ENC_N NTT writeback,
|
||||
@@ -743,6 +754,45 @@ module mlkem_top #(
|
||||
wire [12:0] u_usum = {1'b0, bt_rd_data} + {1'b0, bse_rd_data}; // psum + e1
|
||||
wire [11:0] u_uq = (u_usum >= 13'(Q)) ? (u_usum - 13'(Q)) : u_usum[11:0];
|
||||
|
||||
// ================================================================
|
||||
// E5/E7: Compress_d + byteEncode_d -> ciphertext (Encaps ST_ENC_C1/C2).
|
||||
// Per coeff: read poly coeff -> comp_decomp (mode 0 compress, d=du/dv) ->
|
||||
// bit-packer (LSB-first) -> emit bytes to ct_bram. c1 = K polys of u
|
||||
// (d=du), then c2 = 1 poly of v (d=dv). Per poly = 256 coeffs -> 32*d
|
||||
// bytes (whole), so the bit buffer empties at each poly boundary.
|
||||
// micro-phase cp_ph: 0 present coeff addr; 1 feed comp_decomp (cd_valid);
|
||||
// 2 wait pipe; 3 capture compressed + accumulate bits; 4..n drain bytes.
|
||||
// ================================================================
|
||||
wire cd_active = (st == ST_ENC_C1) || (st == ST_ENC_C2);
|
||||
reg [11:0] cd_coeff; // coeff presented to comp_decomp
|
||||
reg cd_valid; // 1-cyc pulse to comp_decomp
|
||||
wire cd_ready;
|
||||
wire [11:0] cd_out; // compressed value (low d bits valid)
|
||||
wire cd_vo;
|
||||
wire [4:0] cp_d = (st == ST_ENC_C2) ? dv_rt : du_rt; // compress width
|
||||
comp_decomp_sync u_comp (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.coeff_in(cd_coeff),
|
||||
.d(cp_d),
|
||||
.mode(1'b0), // compress
|
||||
.valid_i(cd_valid),
|
||||
.ready_o(cd_ready),
|
||||
.coeff_out(cd_out),
|
||||
.valid_o(cd_vo),
|
||||
.ready_i(1'b1)
|
||||
);
|
||||
// bit-packer / ct walk bookkeeping
|
||||
reg [2:0] cp_poly; // c1: 0..K-1 (u rows); c2: single v
|
||||
reg [7:0] cp_idx; // coeff 0..255 within poly
|
||||
reg [2:0] cp_ph; // micro-phase
|
||||
reg [24:0] cp_buf; // bit accumulator (LSB-first)
|
||||
reg [5:0] cp_nbits; // valid bits in cp_buf
|
||||
reg [11:0] cp_wa; // ct_bram byte write address (runs c1 then c2)
|
||||
reg cp_done; // serialization complete (this region)
|
||||
// coeff source: c1 reads u[cp_poly] in bank_se rel (K+cp_poly); c2 reads v
|
||||
// (lands in bank_t rel slot V_SLOT -- defined in E6). For E5 only c1 path.
|
||||
wire [13:0] cp_se_full = ({2'b0,k_r}+{2'b0,cp_poly})*256 + cp_idx; // bank_se u[cp_poly]
|
||||
|
||||
reg pm_valid;
|
||||
wire pm_ready;
|
||||
wire [11:0] pm_coeff;
|
||||
@@ -804,7 +854,8 @@ module mlkem_top #(
|
||||
ST_ENC_A: if (a_pair >= kk_rt) st_next = ST_ENC_C;
|
||||
ST_ENC_C: if (c_poly >= {k_r, 1'b1}) st_next = ST_ENC_N; // 2K+1 polys done
|
||||
ST_ENC_N: if (n_slot >= {2'b0, k_r}) st_next = ST_ENC_U; // K slots (y_hat)
|
||||
ST_ENC_U: if (u_row >= k_r) st_next = ST_DONE; // E4: u[0..K-1] done
|
||||
ST_ENC_U: if (u_row >= k_r) st_next = ST_ENC_C1; // u[0..K-1] done
|
||||
ST_ENC_C1: if (cp_done) st_next = ST_DONE; // E5: c1 packed
|
||||
ST_ENC_TDEC: if (td_done) st_next = ST_DONE; // (TDEC deferred to V-prep later)
|
||||
ST_DONE: st_next = ST_IDLE;
|
||||
default: st_next = ST_IDLE;
|
||||
@@ -878,6 +929,18 @@ module mlkem_top #(
|
||||
u_aidx <= 9'd0;
|
||||
u_awidx <= 8'd0;
|
||||
u_avalid <= 1'b0;
|
||||
cd_coeff <= 12'd0;
|
||||
cd_valid <= 1'b0;
|
||||
cp_poly <= 3'd0;
|
||||
cp_idx <= 8'd0;
|
||||
cp_ph <= 3'd0;
|
||||
cp_buf <= 25'd0;
|
||||
cp_nbits <= 6'd0;
|
||||
cp_wa <= 12'd0;
|
||||
cp_done <= 1'b0;
|
||||
ct_we <= 1'b0;
|
||||
ct_wa <= 11'd0;
|
||||
ct_wd <= 8'd0;
|
||||
e_poly <= 3'd0;
|
||||
e_pair <= 8'd0;
|
||||
e_ph <= 2'd0;
|
||||
@@ -911,6 +974,7 @@ module mlkem_top #(
|
||||
ek_we <= 1'b0;
|
||||
dkp_we <= 1'b0;
|
||||
td_we <= 1'b0; // TDEC bank_t write default low
|
||||
ct_we <= 1'b0; // ct_bram byte write default low (E5/E7)
|
||||
|
||||
// Kick off when entering from IDLE: KeyGen starts G; Encaps captures
|
||||
// op/m and arms the H(ek) machinery (ST_ENC_H reuses the ST_H FSM).
|
||||
@@ -1241,11 +1305,75 @@ module mlkem_top #(
|
||||
u_pending <= 1'b0;
|
||||
pm_valid <= 1'b0;
|
||||
end else begin
|
||||
u_row <= u_row + 3'd1; // == K -> ST_DONE
|
||||
u_row <= u_row + 3'd1; // == K -> ST_ENC_C1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Arm E5 (ST_ENC_C1) when U finishes: c1 = byteEncode_du(Compress_du(u)).
|
||||
// Walk K polys * 256 coeffs; reset bit-packer + ct write pointer.
|
||||
if (st == ST_ENC_U && st_next == ST_ENC_C1) begin
|
||||
cp_poly <= 3'd0;
|
||||
cp_idx <= 8'd0;
|
||||
cp_ph <= 3'd0;
|
||||
cp_buf <= 25'd0;
|
||||
cp_nbits <= 6'd0;
|
||||
cp_wa <= 12'd0;
|
||||
cp_done <= 1'b0;
|
||||
cd_valid <= 1'b0;
|
||||
end
|
||||
|
||||
// ---- ST_ENC_C1: Compress_du(u[i]) -> byteEncode_du -> ct c1 region ----
|
||||
// Per coeff, 5-phase micro-sequence (read-ahead 1 cyc bram + 1 cyc
|
||||
// comp_decomp pipe), then a drain sub-phase emitting whole bytes:
|
||||
// ph0: present u[cp_poly][cp_idx] addr to bank_se (cp_se_full).
|
||||
// ph1: coeff arrives (bse_rd_data) -> latch into cd_coeff, pulse cd_valid.
|
||||
// ph2: drop cd_valid (1-cyc pulse); comp_decomp captures.
|
||||
// ph3: cd_vo high -> cd_out (low du bits) valid; append LSB-first to cp_buf.
|
||||
// ph4: drain: while >=8 bits buffered, emit one ct byte/cycle; then advance.
|
||||
// Each poly = 256 coeffs = 32*du bytes (whole), so cp_buf empties at
|
||||
// each poly boundary (no carry across polys).
|
||||
if (st == ST_ENC_C1 && !cp_done) begin
|
||||
case (cp_ph)
|
||||
3'd0: cp_ph <= 3'd1; // addr presented; wait read
|
||||
3'd1: begin
|
||||
cd_coeff <= bse_rd_data; // u coeff (registered read)
|
||||
cd_valid <= 1'b1; // feed comp_decomp (1-cyc pulse)
|
||||
cp_ph <= 3'd2;
|
||||
end
|
||||
3'd2: begin
|
||||
cd_valid <= 1'b0; // comp_decomp captured this cyc
|
||||
cp_ph <= 3'd3;
|
||||
end
|
||||
3'd3: begin
|
||||
// cd_out valid (cd_vo): append du bits LSB-first at bit cp_nbits
|
||||
cp_buf <= cp_buf | (({13'd0, cd_out} & ((25'd1 << du_rt) - 25'd1)) << cp_nbits);
|
||||
cp_nbits <= cp_nbits + {1'b0, du_rt};
|
||||
cp_ph <= 3'd4;
|
||||
end
|
||||
default: begin // 3'd4: drain whole bytes
|
||||
if (cp_nbits >= 6'd8) begin
|
||||
ct_we <= 1'b1;
|
||||
ct_wa <= cp_wa;
|
||||
ct_wd <= cp_buf[7:0];
|
||||
cp_wa <= cp_wa + 12'd1;
|
||||
cp_buf <= cp_buf >> 8;
|
||||
cp_nbits <= cp_nbits - 6'd8;
|
||||
end else begin
|
||||
// coeff fully packed; advance coeff / poly
|
||||
if (cp_idx == 8'd255) begin
|
||||
cp_idx <= 8'd0;
|
||||
if (cp_poly + 3'd1 < k_r) cp_poly <= cp_poly + 3'd1;
|
||||
else cp_done <= 1'b1; // c1 complete -> DONE
|
||||
end else begin
|
||||
cp_idx <= cp_idx + 8'd1;
|
||||
end
|
||||
cp_ph <= 3'd0;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
// Arm E stage when M finishes
|
||||
if (st == ST_M && st_next == ST_E) begin
|
||||
e_poly <= 3'd0;
|
||||
|
||||
Reference in New Issue
Block a user