mlkem test diagnostics and allocator updates

This commit is contained in:
2026-07-08 02:35:56 +08:00
parent 65607badd2
commit 0c85a20b4e
40 changed files with 37168 additions and 2093 deletions

View File

@@ -1,16 +1,13 @@
// mlkem_axi_slave.v — AXI4 (burst) slave wrapper for the ML-KEM core.
// Makes the ML-KEM accelerator (KeyGen / Encaps / Decaps) CPU-controllable.
//
// The bus runs at sys_clk (50 MHz). The ML-KEM core cannot close timing at
// 50 MHz, so it is clocked by mlkem_clk = sys_clk / 2 (25 MHz), produced by a
// toggle flip-flop in this wrapper. mlkem_clk is a phase-aligned synchronous
// divide of sys_clk; every signal crossing between the two domains still goes
// through an explicit synchronizer or toggle handshake.
// NOTE (FPGA): for real silicon route mlkem_clk through a BUFG/BUFGCE rather
// than a logic-generated net; kept as a plain toggle here for portability.
// This slave is placed behind an AXI clock-domain converter. Its AXI side and
// mlkem_top both run in mlkem_clk; sys_clk masters reach it through Axi_CDC in
// fft_mlkem_axi_slave.
//
// Register map (byte offsets from base, e.g. virtual 0xbf400000):
// 0x000 CTRL W bit0 = start (launches op selected in CONFIG)
// bit1 = soft_reset (resets ML-KEM core state)
// 0x004 STATUS R bit0 busy, bit1 done, bit2 preload_busy
// W1C write bit1=1 to clear the latched done flag
// 0x008 CONFIG RW [2:0] k (2/3/4), [5:4] op (0=KeyGen 1=Encaps 2=Decaps)
@@ -27,9 +24,11 @@
// 0x1000..0x1FFF EK W: preload ek_in, R: read generated ek
// 0x2000..0x2FFF DK W: preload dk_in, R: read generated full dk
// 0x3000..0x3FFF CT W: preload c_in, R: read generated ciphertext
(* use_dsp = "no" *)
module mlkem_axi_slave (
input clk, // sys_clk (AXI bus clock, 50 MHz)
input resetn, // sys_resetn (active low, already synchronized)
input clk, // mlkem_clk AXI-side clock after Axi_CDC
input resetn, // mlkem_resetn, active low
// AXI Write Address Channel
input [4:0] s_axi_awid,
@@ -153,20 +152,7 @@ module mlkem_axi_slave (
endfunction
// ==========================================
// mlkem_clk = sys_clk / 2 (25 MHz) via toggle FF.
// ==========================================
reg mlkem_clk;
always @(posedge clk or negedge resetn) begin
if (!resetn) mlkem_clk <= 1'b0;
else mlkem_clk <= ~mlkem_clk;
end
// Reset for the mlkem domain: reuse sys_resetn (async-assert, and the core's
// own rst_n is sampled on mlkem_clk edges).
wire mlkem_rstn = resetn;
// ==========================================
// sys_clk-domain config / data registers (quasi-static; fed to the core
// combinationally — written long before start, stable across the op).
// AXI/core clock-domain config / data registers.
// ==========================================
reg [2:0] cfg_k; // 2/3/4
reg [1:0] cfg_op; // 0=KeyGen 1=Encaps 2=Decaps
@@ -188,55 +174,79 @@ module mlkem_axi_slave (
reg [11:0] rdb_addr; // output byte index
// ==========================================
// CDC: start pulse (sys toggle -> mlkem edge-detect -> 1-cyc start_i)
// Start pulse: CTRL.start flips a toggle, edge-detected into start_i.
// ==========================================
reg start_tg; // sys-domain toggle, flips on CTRL.start write
reg [2:0] start_sync; // mlkem-domain 2FF sync (+1 for edge)
wire mlkem_start = start_sync[2] ^ start_sync[1]; // 1 mlkem_clk pulse
wire mlkem_start = start_sync[2] ^ start_sync[1]; // 1 clk pulse
// ==========================================
// CDC: busy / done back to sys
// Software reset pulse: CTRL.soft_reset flips a toggle and stretches reset.
// ==========================================
wire core_busy; // busy_o (mlkem domain)
wire core_done; // done_o pulse (mlkem domain)
reg done_latch; // sticky done in mlkem domain
reg [1:0] busy_sync; // -> sys
reg [1:0] done_sync; // -> sys
// done-clear: sys toggle -> mlkem edge -> clears done_latch
reg dclr_tg; // sys-domain toggle (STATUS W1C of done)
reg [2:0] dclr_sync; // mlkem-domain 2FF sync (+1 for edge)
reg srst_tg; // flips on CTRL.soft_reset
reg [2:0] srst_sync; // edge detector shift register
reg [3:0] srst_cnt; // reset stretcher in clk domain
wire mlkem_srst_edge = srst_sync[2] ^ srst_sync[1];
wire mlkem_srst_active = (srst_cnt != 4'd0);
wire core_resetn = resetn && !mlkem_srst_active;
// ==========================================
// Busy / done status.
// ==========================================
wire core_busy;
wire core_done;
reg done_latch;
reg [1:0] busy_sync;
reg [1:0] done_sync;
// done-clear: STATUS W1C flips a toggle, edge-detected to clear done_latch.
reg dclr_tg;
reg [2:0] dclr_sync;
wire mlkem_dclr = dclr_sync[2] ^ dclr_sync[1];
// ==========================================
// CDC: preload byte write handshake.
// sys: latch {sel,addr,byte}, flip pre_req_tg, hold wready low.
// mlkem: 2FF sync pre_req_tg, on edge pulse the selected *_in_we 1 cycle,
// flip pre_ack_tg.
// sys: 2FF sync pre_ack_tg, on edge release the bus (addr++).
// Preload byte write handshake.
// Latch {sel,addr,byte}, flip pre_req_tg, pulse the selected *_in_we for
// one cycle, then flip pre_ack_tg to release the AXI write beat.
// ==========================================
reg pre_req_tg; // sys toggle: a byte is pending
reg [2:0] pre_req_sync; // mlkem 2FF (+1 for edge)
reg pre_req_tg; // a byte is pending
reg [2:0] pre_req_sync; // edge detector shift register
wire mlkem_pre_we = pre_req_sync[2] ^ pre_req_sync[1];
reg pre_ack_tg; // mlkem toggle: byte consumed
reg [2:0] pre_ack_sync; // sys 2FF (+1 for edge)
reg pre_ack_tg; // byte consumed
reg [2:0] pre_ack_sync; // edge detector shift register
wire pre_ack_edge = pre_ack_sync[2] ^ pre_ack_sync[1];
reg pre_busy; // sys: a preload handshake is in flight
// latched byte/port for the in-flight preload (sys, stable while pre_busy)
reg pre_busy; // a preload handshake is in flight
// latched byte/port for the in-flight preload, stable while pre_busy
reg [1:0] pre_sel_l;
reg [11:0] pre_addr_l;
reg [7:0] pre_byte_l;
reg pre_auto_inc_l;
// ==========================================
// Bulk readback byte handshake.
// Hold {sel,addr}, flip rdb_req_tg, let the BRAM read settle, latch one
// byte, then flip rdb_ack_tg.
// ==========================================
reg rdb_req_tg; // one readback byte request
reg [2:0] rdb_req_sync; // edge detector shift register
wire mlkem_rdb_req = rdb_req_sync[2] ^ rdb_req_sync[1];
reg rdb_ack_tg; // readback byte ready
reg [2:0] rdb_ack_sync; // edge detector shift register
wire rdb_ack_edge = rdb_ack_sync[2] ^ rdb_ack_sync[1];
reg [1:0] rdb_req_sel_l; // stable while request in flight
reg [11:0] rdb_req_addr_l; // stable while request in flight
reg [1:0] rdb_sel_m; // captured request
reg [11:0] rdb_addr_m; // captured request
reg [1:0] rdb_wait_m; // waits for BRAM registered read
reg rdb_busy_m;
reg [7:0] rdb_data_m; // held stable until next ack
// ==========================================
// mlkem_top instance
// ==========================================
wire [7:0] dbg_byte_o, dbg_dk_o, dbg_ct_o;
wire [255:0] ss_o;
wire bulk_read_active;
wire [1:0] bulk_rdb_sel;
wire [11:0] bulk_rdb_addr;
wire [1:0] eff_rdb_sel = bulk_read_active ? bulk_rdb_sel : rdb_sel;
wire [11:0] eff_rdb_addr = bulk_read_active ? bulk_rdb_addr : rdb_addr;
wire [1:0] eff_rdb_sel = rdb_busy_m ? rdb_sel_m : rdb_sel;
wire [11:0] eff_rdb_addr = rdb_busy_m ? rdb_addr_m : rdb_addr;
wire dbg_byte_sel = (eff_rdb_sel == 2'd1); // 0=ek, 1=dk_pke
// The core's ek/dkp read-address muxes give dbg_dk_idx_i priority over
// dbg_byte_idx_i whenever it lands in the ek (768..1567) or dk_pke (<768)
@@ -245,21 +255,20 @@ module mlkem_axi_slave (
// it out of range (0xFFF) so an ek/dk_pke readback isn't corrupted.
wire [11:0] dbg_dk_idx = (eff_rdb_sel == 2'd2) ? eff_rdb_addr : 12'hFFF;
// one-cycle write-enable pulses into the core (combinational off the synced
// edge; pre_sel_l/pre_addr_l/pre_byte_l are stable in sys, sampled by core).
wire ek_in_we = mlkem_pre_we && (pre_sel_l == 2'd0);
wire dk_in_we = mlkem_pre_we && (pre_sel_l == 2'd1);
wire c_in_we = mlkem_pre_we && (pre_sel_l == 2'd2);
// one-cycle write-enable pulses into the core.
wire ek_in_we = core_resetn && mlkem_pre_we && (pre_sel_l == 2'd0);
wire dk_in_we = core_resetn && mlkem_pre_we && (pre_sel_l == 2'd1);
wire c_in_we = core_resetn && mlkem_pre_we && (pre_sel_l == 2'd2);
mlkem_top #(.KMAX(4)) u_mlkem (
.clk (mlkem_clk),
.rst_n (mlkem_rstn),
.clk (clk),
.rst_n (core_resetn),
.k_i (cfg_k),
.op_i (cfg_op),
.d_i (d_bus),
.z_i (z_bus),
.msg_i (msg_bus),
.start_i (mlkem_start),
.start_i (core_resetn ? mlkem_start : 1'b0),
.busy_o (core_busy),
.done_o (core_done),
// ek preload
@@ -302,44 +311,82 @@ module mlkem_axi_slave (
);
// ==========================================
// mlkem_clk domain: synchronizers, done latch, preload-ack toggle.
// Core/AXI clock domain: edge detectors, done latch, preload/readback acks.
// ==========================================
always @(posedge mlkem_clk or negedge mlkem_rstn) begin
if (!mlkem_rstn) begin
always @(posedge clk or negedge resetn) begin
if (!resetn) begin
start_sync <= 3'd0;
srst_sync <= 3'd0;
srst_cnt <= 4'd0;
dclr_sync <= 3'd0;
pre_req_sync <= 3'd0;
pre_ack_tg <= 1'b0;
rdb_req_sync <= 3'd0;
rdb_ack_tg <= 1'b0;
rdb_sel_m <= 2'd0;
rdb_addr_m <= 12'd0;
rdb_wait_m <= 2'd0;
rdb_busy_m <= 1'b0;
rdb_data_m <= 8'd0;
done_latch <= 1'b0;
end else begin
// 2FF (+edge) synchronizers of sys-domain toggles
// Edge-detect request toggles.
start_sync <= {start_sync[1:0], start_tg};
srst_sync <= {srst_sync[1:0], srst_tg};
dclr_sync <= {dclr_sync[1:0], dclr_tg};
pre_req_sync <= {pre_req_sync[1:0], pre_req_tg};
rdb_req_sync <= {rdb_req_sync[1:0], rdb_req_tg};
if (mlkem_srst_edge)
srst_cnt <= 4'd8;
else if (srst_cnt != 4'd0)
srst_cnt <= srst_cnt - 4'd1;
// acknowledge a consumed preload byte (write-enable pulsed this cycle)
if (mlkem_pre_we) pre_ack_tg <= ~pre_ack_tg;
if (core_resetn && mlkem_pre_we) pre_ack_tg <= ~pre_ack_tg;
if (!core_resetn) begin
rdb_wait_m <= 2'd0;
rdb_busy_m <= 1'b0;
end else if (mlkem_rdb_req) begin
rdb_sel_m <= rdb_req_sel_l;
rdb_addr_m <= rdb_req_addr_l;
rdb_wait_m <= 2'd2;
rdb_busy_m <= 1'b1;
end else if (rdb_wait_m != 2'd0) begin
if (rdb_wait_m == 2'd1) begin
rdb_data_m <= rdb_byte;
rdb_ack_tg <= ~rdb_ack_tg;
rdb_wait_m <= 2'd0;
rdb_busy_m <= 1'b0;
end else begin
rdb_wait_m <= rdb_wait_m - 2'd1;
end
end
// sticky done: set on the core's done_o pulse, cleared by synced W1C.
// (mlkem_start also clears it so a new run starts with done low.)
if (core_done) done_latch <= 1'b1;
if (mlkem_srst_active) done_latch <= 1'b0;
else if (core_done) done_latch <= 1'b1;
else if (mlkem_dclr) done_latch <= 1'b0;
else if (mlkem_start) done_latch <= 1'b0;
end
end
// ==========================================
// sys_clk domain: sync busy/done/preload-ack back from mlkem.
// AXI clock domain: register status and acknowledge toggles.
// ==========================================
always @(posedge clk or negedge resetn) begin
if (!resetn) begin
busy_sync <= 2'd0;
done_sync <= 2'd0;
pre_ack_sync <= 3'd0;
rdb_ack_sync <= 3'd0;
end else begin
busy_sync <= {busy_sync[0], core_busy};
busy_sync <= {busy_sync[0], core_busy | mlkem_srst_active};
done_sync <= {done_sync[0], done_latch};
pre_ack_sync <= {pre_ack_sync[1:0], pre_ack_tg};
rdb_ack_sync <= {rdb_ack_sync[1:0], rdb_ack_tg};
end
end
wire status_busy = busy_sync[1];
@@ -389,6 +436,7 @@ module mlkem_axi_slave (
rdb_sel <= 2'd0;
rdb_addr <= 12'd0;
start_tg <= 1'b0;
srst_tg <= 1'b0;
dclr_tg <= 1'b0;
pre_req_tg <= 1'b0;
pre_busy <= 1'b0;
@@ -453,8 +501,10 @@ module mlkem_axi_slave (
end else begin
// normal single-cycle register write
case (1'b1)
(w_offset == ADDR_CTRL):
(w_offset == ADDR_CTRL): begin
if (s_axi_wdata[0]) start_tg <= ~start_tg;
if (s_axi_wdata[1]) srst_tg <= ~srst_tg;
end
(w_offset == ADDR_STATUS):
if (s_axi_wdata[1]) dclr_tg <= ~dclr_tg; // clear done
(w_offset == ADDR_CONFIG): begin
@@ -541,7 +591,6 @@ module mlkem_axi_slave (
localparam R_IDLE = 3'd0;
localparam R_DATA = 3'd1;
localparam R_BULK_WAIT = 3'd2;
localparam RDB_WAIT_CYCLES = 3'd4;
reg [2:0] r_state;
reg [31:0] r_addr;
@@ -557,13 +606,8 @@ module mlkem_axi_slave (
reg [1:0] bulk_sel_r;
reg [11:0] bulk_addr_r;
reg [1:0] bulk_lane;
reg [2:0] bulk_wait;
reg [31:0] bulk_word;
assign bulk_read_active = r_bulk && (r_state == R_BULK_WAIT);
assign bulk_rdb_sel = bulk_sel_r;
assign bulk_rdb_addr = bulk_addr_r;
always @(posedge clk or negedge resetn) begin
if (!resetn) begin
r_state <= R_IDLE;
@@ -579,8 +623,10 @@ module mlkem_axi_slave (
bulk_sel_r <= 2'd0;
bulk_addr_r <= 12'd0;
bulk_lane <= 2'd0;
bulk_wait <= 3'd0;
bulk_word <= 32'd0;
rdb_req_tg <= 1'b0;
rdb_req_sel_l <= 2'd0;
rdb_req_addr_l <= 12'd0;
end else begin
case (r_state)
R_IDLE: begin
@@ -597,8 +643,10 @@ module mlkem_axi_slave (
bulk_sel_r <= window_readback_sel(s_axi_araddr[15:12]);
bulk_addr_r <= s_axi_araddr[11:0];
bulk_lane <= 2'd0;
bulk_wait <= RDB_WAIT_CYCLES;
bulk_word <= 32'd0;
rdb_req_sel_l <= window_readback_sel(s_axi_araddr[15:12]);
rdb_req_addr_l <= s_axi_araddr[11:0];
rdb_req_tg <= ~rdb_req_tg;
s_axi_rvalid <= 1'b0;
s_axi_rlast <= 1'b0;
r_state <= R_BULK_WAIT;
@@ -610,14 +658,12 @@ module mlkem_axi_slave (
end
end
R_BULK_WAIT: begin
if (bulk_wait != 3'd0) begin
bulk_wait <= bulk_wait - 3'd1;
end else begin
if (rdb_ack_edge) begin
case (bulk_lane)
2'd0: bulk_word[7:0] <= rdb_byte;
2'd1: bulk_word[15:8] <= rdb_byte;
2'd2: bulk_word[23:16] <= rdb_byte;
default: bulk_word[31:24] <= rdb_byte;
2'd0: bulk_word[7:0] <= rdb_data_m;
2'd1: bulk_word[15:8] <= rdb_data_m;
2'd2: bulk_word[23:16] <= rdb_data_m;
default: bulk_word[31:24] <= rdb_data_m;
endcase
if (bulk_lane == 2'd3) begin
@@ -627,7 +673,8 @@ module mlkem_axi_slave (
end else begin
bulk_lane <= bulk_lane + 2'd1;
bulk_addr_r <= bulk_addr_r + 12'd1;
bulk_wait <= RDB_WAIT_CYCLES;
rdb_req_addr_l <= bulk_addr_r + 12'd1;
rdb_req_tg <= ~rdb_req_tg;
end
end
end
@@ -645,8 +692,10 @@ module mlkem_axi_slave (
if (r_bulk) begin
bulk_addr_r <= (r_burst == 2'b01) ? (r_offset + 12'd4) : r_offset;
bulk_lane <= 2'd0;
bulk_wait <= RDB_WAIT_CYCLES;
bulk_word <= 32'd0;
rdb_req_sel_l <= bulk_sel_r;
rdb_req_addr_l <= (r_burst == 2'b01) ? (r_offset + 12'd4) : r_offset;
rdb_req_tg <= ~rdb_req_tg;
s_axi_rvalid <= 1'b0;
s_axi_rlast <= 1'b0;
r_state <= R_BULK_WAIT;