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:
@@ -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