feat: add ML-KEM accelerator with AXI slave wrapper
Wrap mlkem_top in an AXI4 burst slave (mlkem_axi_slave) mapped to the freed FFT slot 6 at 0x1f400000 (virtual 0xbf400000). The core runs on sys_clk/2 (25 MHz); the wrapper handles the clock-domain crossing: start toggle->pulse, wait-stated byte-preload handshake, busy/done sync, and direct readback taps (ek/dk/ct/ss). CPU drives it via a register map (CTRL/STATUS/CONFIG, d/z/msg, preload + readback windows). Wire into soc_top on the existing axiOut_6 nets (no crossbar change), add mlkem_intr on intrpt[3]. Add sdk mlkem_test example running a KeyGen->Encaps->Decaps self-consistency round trip.
This commit is contained in:
99
rtl/ip/mlkem/sha3/keccak_core.v
Normal file
99
rtl/ip/mlkem/sha3/keccak_core.v
Normal file
@@ -0,0 +1,99 @@
|
||||
// keccak_core.v - Synchronous Keccak-f[1600] core with valid/ready
|
||||
//
|
||||
// Runs 24 rounds of keccak_round on the input state.
|
||||
// valid_i high with ready_o high → load state_i, start 24-round sequence.
|
||||
// Each round takes 1 cycle. After round 23, valid_o asserted with state_o.
|
||||
//
|
||||
// Parameter:
|
||||
// ROUNDS = 24 (default)
|
||||
//
|
||||
// Interface:
|
||||
// clk, rst_n - clock, active-low reset
|
||||
// state_i - 1600-bit input state
|
||||
// valid_i - start permutation
|
||||
// ready_o - core can accept new input
|
||||
// state_o - 1600-bit output state (after permutation)
|
||||
// valid_o - output is valid
|
||||
// ready_i - consumer accepts output
|
||||
|
||||
module keccak_core #(parameter ROUNDS = 24) (
|
||||
input clk,
|
||||
input rst_n,
|
||||
input [1599:0] state_i,
|
||||
input valid_i,
|
||||
output ready_o,
|
||||
output [1599:0] state_o,
|
||||
output valid_o,
|
||||
input ready_i
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// Internal registers
|
||||
// ================================================================
|
||||
reg busy_r; // 1 while running permutation
|
||||
reg [4:0] cnt_r; // round counter (0 to ROUNDS-1)
|
||||
reg [1599:0] state_r; // current state
|
||||
|
||||
// ================================================================
|
||||
// Next-state logic
|
||||
// ================================================================
|
||||
wire cnt_is_last; // cnt_r == ROUNDS-1 ?
|
||||
wire [1599:0] state_next; // combinational round output
|
||||
|
||||
assign cnt_is_last = (cnt_r == (ROUNDS - 1));
|
||||
|
||||
// keccak_round: apply one round to current state
|
||||
keccak_round u_round (
|
||||
.state_i(state_r),
|
||||
.round_i(cnt_r),
|
||||
.state_o(state_next)
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// Valid/ready handshake
|
||||
// ================================================================
|
||||
// ready_o: accept new input when not busy (or just finishing)
|
||||
assign ready_o = !busy_r;
|
||||
|
||||
// valid_o: output valid when we just finished the last round
|
||||
// (the final state is already in state_r at the output cycle)
|
||||
assign valid_o = busy_r && cnt_is_last;
|
||||
|
||||
// state_o: output the final state
|
||||
assign state_o = state_next; // final state from the last round
|
||||
|
||||
// ================================================================
|
||||
// Sequential logic
|
||||
// ================================================================
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
busy_r <= 1'b0;
|
||||
cnt_r <= 5'd0;
|
||||
state_r <= 1600'd0;
|
||||
end else begin
|
||||
if (!busy_r) begin
|
||||
// IDLE: wait for valid_i
|
||||
if (valid_i) begin
|
||||
state_r <= state_i;
|
||||
cnt_r <= 5'd0;
|
||||
busy_r <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
// BUSY: running permutations
|
||||
if (cnt_is_last) begin
|
||||
// Final round: check if output can be accepted
|
||||
if (ready_i) begin
|
||||
busy_r <= 1'b0;
|
||||
cnt_r <= 5'd0;
|
||||
end
|
||||
// else: hold until ready_i
|
||||
end else begin
|
||||
// Intermediate round: advance state
|
||||
state_r <= state_next;
|
||||
cnt_r <= cnt_r + 5'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
248
rtl/ip/mlkem/sha3/keccak_round.v
Normal file
248
rtl/ip/mlkem/sha3/keccak_round.v
Normal file
@@ -0,0 +1,248 @@
|
||||
// keccak_round.v - Single Keccak-f round (theta/rho/pi/chi/iota)
|
||||
//
|
||||
// Pure combinational: no clock, no valid/ready.
|
||||
// Input: 1600-bit state, 5-bit round index (0-23).
|
||||
// Output: transformed 1600-bit state.
|
||||
//
|
||||
// State layout: s[64*(5*y + x) + z] = A[x][y][z]
|
||||
// Lane(x,y) = s[64*(5*y + x) +: 64]
|
||||
|
||||
module keccak_round (
|
||||
input [1599:0] state_i,
|
||||
input [4:0] round_i,
|
||||
output [1599:0] state_o
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// Round constants (24 entries, FIPS 202)
|
||||
// ================================================================
|
||||
wire [63:0] RC [0:23];
|
||||
assign RC[ 0] = 64'h0000000000000001;
|
||||
assign RC[ 1] = 64'h0000000000008082;
|
||||
assign RC[ 2] = 64'h800000000000808A;
|
||||
assign RC[ 3] = 64'h8000000080008000;
|
||||
assign RC[ 4] = 64'h000000000000808B;
|
||||
assign RC[ 5] = 64'h0000000080000001;
|
||||
assign RC[ 6] = 64'h8000000080008081;
|
||||
assign RC[ 7] = 64'h8000000000008009;
|
||||
assign RC[ 8] = 64'h000000000000008A;
|
||||
assign RC[ 9] = 64'h0000000000000088;
|
||||
assign RC[10] = 64'h0000000080008009;
|
||||
assign RC[11] = 64'h000000008000000A;
|
||||
assign RC[12] = 64'h000000008000808B;
|
||||
assign RC[13] = 64'h800000000000008B;
|
||||
assign RC[14] = 64'h8000000000008089;
|
||||
assign RC[15] = 64'h8000000000008003;
|
||||
assign RC[16] = 64'h8000000000008002;
|
||||
assign RC[17] = 64'h8000000000000080;
|
||||
assign RC[18] = 64'h000000000000800A;
|
||||
assign RC[19] = 64'h800000008000000A;
|
||||
assign RC[20] = 64'h8000000080008081;
|
||||
assign RC[21] = 64'h8000000000008080;
|
||||
assign RC[22] = 64'h0000000080000001;
|
||||
assign RC[23] = 64'h8000000080008008;
|
||||
|
||||
// ================================================================
|
||||
// Rho rotation offsets (FIPS 202, Table 2)
|
||||
// Inlined as generate-if chains for tool compatibility
|
||||
// ================================================================
|
||||
|
||||
// ================================================================
|
||||
// Unpack flat 1600-bit input into 25 lanes (each 64-bit)
|
||||
// Lane index: 5*y + x
|
||||
// ================================================================
|
||||
wire [63:0] A [0:4][0:4];
|
||||
genvar gx, gy;
|
||||
generate
|
||||
for (gx = 0; gx < 5; gx = gx + 1) begin : up_x
|
||||
for (gy = 0; gy < 5; gy = gy + 1) begin : up_y
|
||||
assign A[gx][gy] = state_i[((5 * gy + gx) * 64) +: 64];
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ================================================================
|
||||
// Theta step
|
||||
// C[x] = XOR over y of A[x][y]
|
||||
// D[x] = C[x-1] ^ ROTL(C[x+1], 1)
|
||||
// A_theta[x][y] = A[x][y] ^ D[x]
|
||||
// ================================================================
|
||||
wire [63:0] C [0:4];
|
||||
wire [63:0] D [0:4];
|
||||
wire [63:0] A_t [0:4][0:4];
|
||||
|
||||
generate
|
||||
for (gx = 0; gx < 5; gx = gx + 1) begin : th_x
|
||||
// C[x] = XOR of the 5 lanes in column x
|
||||
assign C[gx] = A[gx][0] ^ A[gx][1] ^ A[gx][2] ^ A[gx][3] ^ A[gx][4];
|
||||
|
||||
// D[x] = C[(x-1) mod 5] ^ ROTL(C[(x+1) mod 5], 1)
|
||||
localparam [2:0] xp1 = (gx == 4) ? 3'd0 : (gx + 3'd1);
|
||||
localparam [2:0] xm1 = (gx == 0) ? 3'd4 : (gx - 3'd1);
|
||||
assign D[gx] = C[xm1] ^ {C[xp1][62:0], C[xp1][63]};
|
||||
|
||||
for (gy = 0; gy < 5; gy = gy + 1) begin : th_y
|
||||
assign A_t[gx][gy] = A[gx][gy] ^ D[gx];
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ================================================================
|
||||
// Rho step (rotation of each lane by precomputed offset)
|
||||
// ================================================================
|
||||
wire [63:0] A_r [0:4][0:4];
|
||||
generate
|
||||
for (gx = 0; gx < 5; gx = gx + 1) begin : rh_x
|
||||
for (gy = 0; gy < 5; gy = gy + 1) begin : rh_y
|
||||
if (gx == 0 && gy == 0) begin : rho_00
|
||||
assign A_r[gx][gy] = A_t[gx][gy];
|
||||
end else
|
||||
if (gy == 0 && gx == 1) begin : rho_10
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][62:0], A_t[gx][gy][63]};
|
||||
end else
|
||||
if (gy == 0 && gx == 2) begin : rho_20
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][1:0], A_t[gx][gy][63:2]};
|
||||
end else
|
||||
if (gy == 0 && gx == 3) begin : rho_30
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][35:0], A_t[gx][gy][63:36]};
|
||||
end else
|
||||
if (gy == 0 && gx == 4) begin : rho_40
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][36:0], A_t[gx][gy][63:37]};
|
||||
end else
|
||||
if (gy == 1 && gx == 0) begin : rho_01
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][27:0], A_t[gx][gy][63:28]};
|
||||
end else
|
||||
if (gy == 1 && gx == 1) begin : rho_11
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][19:0], A_t[gx][gy][63:20]};
|
||||
end else
|
||||
if (gy == 1 && gx == 2) begin : rho_21
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][57:0], A_t[gx][gy][63:58]};
|
||||
end else
|
||||
if (gy == 1 && gx == 3) begin : rho_31
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][8:0], A_t[gx][gy][63:9]};
|
||||
end else
|
||||
if (gy == 1 && gx == 4) begin : rho_41
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][43:0], A_t[gx][gy][63:44]};
|
||||
end else
|
||||
if (gy == 2 && gx == 0) begin : rho_02
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][60:0], A_t[gx][gy][63:61]};
|
||||
end else
|
||||
if (gy == 2 && gx == 1) begin : rho_12
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][53:0], A_t[gx][gy][63:54]};
|
||||
end else
|
||||
if (gy == 2 && gx == 2) begin : rho_22
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][20:0], A_t[gx][gy][63:21]};
|
||||
end else
|
||||
if (gy == 2 && gx == 3) begin : rho_32
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][38:0], A_t[gx][gy][63:39]};
|
||||
end else
|
||||
if (gy == 2 && gx == 4) begin : rho_42
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][24:0], A_t[gx][gy][63:25]};
|
||||
end else
|
||||
if (gy == 3 && gx == 0) begin : rho_03
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][22:0], A_t[gx][gy][63:23]};
|
||||
end else
|
||||
if (gy == 3 && gx == 1) begin : rho_13
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][18:0], A_t[gx][gy][63:19]};
|
||||
end else
|
||||
if (gy == 3 && gx == 2) begin : rho_23
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][48:0], A_t[gx][gy][63:49]};
|
||||
end else
|
||||
if (gy == 3 && gx == 3) begin : rho_33
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][42:0], A_t[gx][gy][63:43]};
|
||||
end else
|
||||
if (gy == 3 && gx == 4) begin : rho_43
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][55:0], A_t[gx][gy][63:56]};
|
||||
end else
|
||||
if (gy == 4 && gx == 0) begin : rho_04
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][45:0], A_t[gx][gy][63:46]};
|
||||
end else
|
||||
if (gy == 4 && gx == 1) begin : rho_14
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][61:0], A_t[gx][gy][63:62]};
|
||||
end else
|
||||
if (gy == 4 && gx == 2) begin : rho_24
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][2:0], A_t[gx][gy][63:3]};
|
||||
end else
|
||||
if (gy == 4 && gx == 3) begin : rho_34
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][7:0], A_t[gx][gy][63:8]};
|
||||
end else
|
||||
if (gy == 4 && gx == 4) begin : rho_44
|
||||
assign A_r[gx][gy] = {A_t[gx][gy][49:0], A_t[gx][gy][63:50]};
|
||||
end
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ================================================================
|
||||
// Pi step (permute lanes)
|
||||
// A_pi[x][y] = A_rho[(x + 3*y) % 5][x]
|
||||
// ================================================================
|
||||
wire [63:0] A_p [0:4][0:4];
|
||||
generate
|
||||
for (gx = 0; gx < 5; gx = gx + 1) begin : pi_x
|
||||
for (gy = 0; gy < 5; gy = gy + 1) begin : pi_y
|
||||
localparam [2:0] src_x = (gx + 3 * gy) % 5;
|
||||
assign A_p[gx][gy] = A_r[src_x][gx];
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ================================================================
|
||||
// Chi step
|
||||
// A_chi[x][y] = A_pi[x][y] ^ (~A_pi[x+1][y] & A_pi[x+2][y])
|
||||
// ================================================================
|
||||
wire [63:0] A_c [0:4][0:4];
|
||||
generate
|
||||
for (gx = 0; gx < 5; gx = gx + 1) begin : ch_x
|
||||
localparam [2:0] xp1 = (gx == 4) ? 3'd0 : (gx + 3'd1);
|
||||
localparam [2:0] xp2 = (gx == 3) ? 3'd0 :
|
||||
(gx == 4) ? 3'd1 : (gx + 3'd2);
|
||||
for (gy = 0; gy < 5; gy = gy + 1) begin : ch_y
|
||||
assign A_c[gx][gy] = A_p[gx][gy] ^
|
||||
((~A_p[xp1][gy]) & A_p[xp2][gy]);
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// ================================================================
|
||||
// Iota step
|
||||
// A_iota[0][0] = A_chi[0][0] ^ RC[round_i]
|
||||
// Other lanes unchanged
|
||||
// ================================================================
|
||||
wire [63:0] A_io [0:4][0:4];
|
||||
assign A_io[0][0] = A_c[0][0] ^ RC[round_i];
|
||||
assign A_io[0][1] = A_c[0][1];
|
||||
assign A_io[0][2] = A_c[0][2];
|
||||
assign A_io[0][3] = A_c[0][3];
|
||||
assign A_io[0][4] = A_c[0][4];
|
||||
assign A_io[1][0] = A_c[1][0];
|
||||
assign A_io[1][1] = A_c[1][1];
|
||||
assign A_io[1][2] = A_c[1][2];
|
||||
assign A_io[1][3] = A_c[1][3];
|
||||
assign A_io[1][4] = A_c[1][4];
|
||||
assign A_io[2][0] = A_c[2][0];
|
||||
assign A_io[2][1] = A_c[2][1];
|
||||
assign A_io[2][2] = A_c[2][2];
|
||||
assign A_io[2][3] = A_c[2][3];
|
||||
assign A_io[2][4] = A_c[2][4];
|
||||
assign A_io[3][0] = A_c[3][0];
|
||||
assign A_io[3][1] = A_c[3][1];
|
||||
assign A_io[3][2] = A_c[3][2];
|
||||
assign A_io[3][3] = A_c[3][3];
|
||||
assign A_io[3][4] = A_c[3][4];
|
||||
assign A_io[4][0] = A_c[4][0];
|
||||
assign A_io[4][1] = A_c[4][1];
|
||||
assign A_io[4][2] = A_c[4][2];
|
||||
assign A_io[4][3] = A_c[4][3];
|
||||
assign A_io[4][4] = A_c[4][4];
|
||||
|
||||
// ================================================================
|
||||
// Pack 25 lanes back into 1600-bit flat output
|
||||
// ================================================================
|
||||
assign state_o = {A_io[4][4], A_io[3][4], A_io[2][4], A_io[1][4], A_io[0][4],
|
||||
A_io[4][3], A_io[3][3], A_io[2][3], A_io[1][3], A_io[0][3],
|
||||
A_io[4][2], A_io[3][2], A_io[2][2], A_io[1][2], A_io[0][2],
|
||||
A_io[4][1], A_io[3][1], A_io[2][1], A_io[1][1], A_io[0][1],
|
||||
A_io[4][0], A_io[3][0], A_io[2][0], A_io[1][0], A_io[0][0]};
|
||||
|
||||
endmodule
|
||||
215
rtl/ip/mlkem/sha3/sha3_top.v
Normal file
215
rtl/ip/mlkem/sha3/sha3_top.v
Normal file
@@ -0,0 +1,215 @@
|
||||
// sha3_top.v - SHA3/SHAKE top wrapper with valid/ready interface
|
||||
//
|
||||
// Implements SHA3-512 (G), SHA3-256 (H), SHAKE-256 (J) over a single
|
||||
// Keccak-f[1600] core. Supports single-block absorption, plus a separate
|
||||
// multi-block SHA3-256 streaming absorb path for long inputs (e.g. H(ek)).
|
||||
//
|
||||
// Single-block modes (mb_en=0):
|
||||
// 00 = G (SHA3-512): rate=576, suffix=01, msg_len=264, out=512
|
||||
// 01 = H (SHA3-256): rate=1088, suffix=01, msg_len=256, out=256
|
||||
// 10 = J (SHAKE-256): rate=1088, suffix=1111,msg_len=512,out=256
|
||||
//
|
||||
// Multi-block SHA3-256 (mb_en=1):
|
||||
// Streams pre-padded 1088-bit rate blocks. The CALLER applies SHA3-256
|
||||
// padding (suffix 0x06 ... 0x80) to the final block so this module only
|
||||
// does the absorb loop: state ^= block; state = Keccak-p(state); repeat.
|
||||
// The single-block paths above are bit-identical when mb_en=0.
|
||||
//
|
||||
// Interface (single-block):
|
||||
// mode[1:0] - 00=G, 01=H, 10=J
|
||||
// data_i - 512-bit message input
|
||||
// valid_i / ready_o / hash_o[511:0] / valid_o / ready_i
|
||||
//
|
||||
// Interface (multi-block SHA3-256, active when mb_en=1):
|
||||
// mb_en - 1 selects the multi-block absorb path
|
||||
// mb_block_i - one pre-padded 1088-bit (136-byte) rate block, byte 0 in [7:0]
|
||||
// mb_valid_i - block valid
|
||||
// mb_ready_o - module can accept a block
|
||||
// mb_last_i - asserted with the final (already-padded) block
|
||||
// result reuses hash_o[255:0] / valid_o / ready_i
|
||||
|
||||
module sha3_top (
|
||||
input clk,
|
||||
input rst_n,
|
||||
input [1:0] mode,
|
||||
input [511:0] data_i,
|
||||
input valid_i,
|
||||
output ready_o,
|
||||
output [511:0] hash_o,
|
||||
output valid_o,
|
||||
input ready_i,
|
||||
// --- multi-block SHA3-256 absorb (tie mb_en=0 to disable) ---
|
||||
input mb_en,
|
||||
input [1087:0] mb_block_i,
|
||||
input mb_valid_i,
|
||||
input mb_last_i,
|
||||
output mb_ready_o
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// FSM state encoding
|
||||
// ================================================================
|
||||
localparam ST_IDLE = 2'd0;
|
||||
localparam ST_PERMUTE = 2'd1;
|
||||
localparam ST_SQUEEZE = 2'd2;
|
||||
|
||||
reg [1:0] state_r, state_next;
|
||||
|
||||
// ================================================================
|
||||
// Absorb state: message || suffix || pad10*1 into rate bits
|
||||
// Built combinationally from data_i and mode.
|
||||
//
|
||||
// G: padded_block = {1, 308'b0, 1, 2'b01, data_i[263:0]}
|
||||
// absorb_state = {1024'b0, padded_block_576}
|
||||
//
|
||||
// H: padded_block = {1, 828'b0, 1, 2'b01, data_i[255:0]}
|
||||
// absorb_state = {512'b0, padded_block_1088}
|
||||
//
|
||||
// J: padded_block = {1, 570'b0, 1, 4'b1111, data_i[511:0]}
|
||||
// absorb_state = {512'b0, padded_block_1088}
|
||||
// ================================================================
|
||||
wire [575:0] g_pad;
|
||||
wire [1087:0] h_pad;
|
||||
wire [1087:0] j_pad;
|
||||
|
||||
assign g_pad = {1'b1, {308{1'b0}}, 1'b1, 2'b10, data_i[263:0]};
|
||||
assign h_pad = {1'b1, {828{1'b0}}, 1'b1, 2'b10, data_i[255:0]};
|
||||
// J: SHAKE suffix is "1111" — all ones, order irrelevant
|
||||
assign j_pad = {1'b1, {570{1'b0}}, 1'b1, 4'b1111, data_i[511:0]};
|
||||
|
||||
wire [1599:0] absorb_state;
|
||||
assign absorb_state = (mode == 2'b00) ? {{(1600-576){1'b0}}, g_pad} :
|
||||
(mode == 2'b01) ? {{(1600-1088){1'b0}}, h_pad} :
|
||||
(mode == 2'b10) ? {{(1600-1088){1'b0}}, j_pad} :
|
||||
1600'd0;
|
||||
|
||||
// ================================================================
|
||||
// Multi-block SHA3-256 absorb FSM (active only when mb_en=1)
|
||||
//
|
||||
// Running sponge state mb_state_r (init 0). For each pre-padded
|
||||
// 1088-bit rate block: mb_state_r ^= block; mb_state_r = Keccak-p(...).
|
||||
// After the last block, squeeze 256 bits. The caller pads the final
|
||||
// block (SHA3-256 suffix 0x06 ... 0x80), so this FSM is pure absorb.
|
||||
// ================================================================
|
||||
localparam MB_IDLE = 2'd0; // ready for a block (or first block)
|
||||
localparam MB_PERMUTE = 2'd1; // keccak running on xored state
|
||||
localparam MB_DONE = 2'd2; // squeeze: present 256-bit digest
|
||||
|
||||
reg [1:0] mb_state, mb_state_next;
|
||||
reg [1599:0] mb_state_r; // running sponge state
|
||||
reg mb_last_r; // captured last-block flag
|
||||
reg [255:0] mb_digest_r; // latched 256-bit digest (sticky in MB_DONE)
|
||||
|
||||
// XOR the incoming block into the low 1088 bits (rate) of the state.
|
||||
wire [1599:0] mb_xored;
|
||||
assign mb_xored = mb_state_r ^ {{(1600-1088){1'b0}}, mb_block_i};
|
||||
|
||||
// Accept a block only in MB_IDLE while enabled.
|
||||
assign mb_ready_o = mb_en && (mb_state == MB_IDLE);
|
||||
wire mb_accept = mb_en && (mb_state == MB_IDLE) && mb_valid_i;
|
||||
wire mb_kc_valid = mb_accept; // start keccak on the accept cycle
|
||||
|
||||
always @(*) begin
|
||||
mb_state_next = mb_state;
|
||||
case (mb_state)
|
||||
MB_IDLE: if (mb_accept) mb_state_next = MB_PERMUTE;
|
||||
MB_PERMUTE: if (kc_valid_o) mb_state_next = mb_last_r ? MB_DONE : MB_IDLE;
|
||||
MB_DONE: if (ready_i) mb_state_next = MB_IDLE;
|
||||
default: mb_state_next = MB_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// Keccak core
|
||||
// ================================================================
|
||||
wire kc_valid_i;
|
||||
/* verilator lint_off UNUSEDSIGNAL */
|
||||
wire [1599:0] kc_state_o;
|
||||
wire kc_ready_o;
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
wire kc_valid_o;
|
||||
|
||||
// Keccak input: multi-block xored state when mb_en, else single-block absorb.
|
||||
wire [1599:0] kc_state_i_mux;
|
||||
assign kc_state_i_mux = mb_en ? mb_xored : absorb_state;
|
||||
|
||||
keccak_core #(.ROUNDS(24)) u_keccak (
|
||||
.clk (clk),
|
||||
.rst_n (rst_n),
|
||||
.state_i (kc_state_i_mux),
|
||||
.valid_i (kc_valid_i),
|
||||
.ready_o (kc_ready_o), // unused but must connect
|
||||
.state_o (kc_state_o),
|
||||
.valid_o (kc_valid_o),
|
||||
.ready_i (1'b1) // always accept output
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// FSM combinational logic
|
||||
// ================================================================
|
||||
assign ready_o = !mb_en && (state_r == ST_IDLE);
|
||||
|
||||
// kc_valid_i: single-block start (state_next==PERMUTE) OR multi-block accept.
|
||||
assign kc_valid_i = mb_en ? mb_kc_valid : (state_next == ST_PERMUTE);
|
||||
|
||||
always @(*) begin
|
||||
state_next = state_r;
|
||||
case (state_r)
|
||||
ST_IDLE: if (valid_i && ready_o) state_next = ST_PERMUTE;
|
||||
ST_PERMUTE: if (kc_valid_o) state_next = ST_SQUEEZE;
|
||||
ST_SQUEEZE: if (valid_o && ready_i) state_next = ST_IDLE;
|
||||
default: state_next = ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// Output
|
||||
// ================================================================
|
||||
// Register for squeezed output (only 512 bits needed)
|
||||
reg [511:0] squeezed_state_r;
|
||||
|
||||
// valid_o / hash_o serve both paths, selected by mb_en.
|
||||
assign valid_o = mb_en ? (mb_state == MB_DONE) : (state_r == ST_SQUEEZE);
|
||||
assign hash_o = mb_en ? {256'b0, mb_digest_r} : squeezed_state_r;
|
||||
|
||||
// ================================================================
|
||||
// Sequential logic
|
||||
// ================================================================
|
||||
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
state_r <= ST_IDLE;
|
||||
squeezed_state_r <= 512'd0;
|
||||
mb_state <= MB_IDLE;
|
||||
mb_state_r <= 1600'd0;
|
||||
mb_last_r <= 1'b0;
|
||||
mb_digest_r <= 256'd0;
|
||||
end else begin
|
||||
state_r <= state_next;
|
||||
mb_state <= mb_state_next;
|
||||
|
||||
// --- single-block: latch squeezed output ---
|
||||
if (state_r == ST_PERMUTE && kc_valid_o) begin
|
||||
squeezed_state_r <= kc_state_o[511:0];
|
||||
end
|
||||
|
||||
// --- multi-block: capture last flag on accept ---
|
||||
if (mb_accept) begin
|
||||
mb_last_r <= mb_last_i;
|
||||
end
|
||||
|
||||
// --- multi-block: latch permuted state when keccak finishes ---
|
||||
if (mb_state == MB_PERMUTE && kc_valid_o) begin
|
||||
mb_state_r <= kc_state_o;
|
||||
// On the final block, latch the 256-bit digest (sticky for MB_DONE).
|
||||
if (mb_last_r) mb_digest_r <= kc_state_o[255:0];
|
||||
end
|
||||
|
||||
// --- multi-block: clear running state after digest consumed ---
|
||||
if (mb_state == MB_DONE && ready_i) begin
|
||||
mb_state_r <= 1600'd0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
187
rtl/ip/mlkem/sha3/sha3_top_shared.v
Normal file
187
rtl/ip/mlkem/sha3/sha3_top_shared.v
Normal file
@@ -0,0 +1,187 @@
|
||||
// sha3_top_shared.v - SHA3/SHAKE wrapper with EXTERNAL (shared) keccak_core.
|
||||
//
|
||||
// Identical to sha3_top.v except the internal keccak_core instance is
|
||||
// replaced by explicit kc_* ports, so several Keccak consumers can share a
|
||||
// single keccak_core via a phase mux (see mlkem_top). The sponge state
|
||||
// (mb_state_r) and all FSM/squeeze logic stay here; only the 24-round
|
||||
// permutation is external. Bit-identical to sha3_top when wired to a
|
||||
// dedicated keccak_core.
|
||||
//
|
||||
// Single-block modes (mb_en=0):
|
||||
// 00 = G (SHA3-512): rate=576, suffix=01, msg_len=264, out=512
|
||||
// 01 = H (SHA3-256): rate=1088, suffix=01, msg_len=256, out=256
|
||||
// 10 = J (SHAKE-256): rate=1088, suffix=1111,msg_len=512,out=256
|
||||
//
|
||||
// Multi-block SHA3-256 (mb_en=1): streams pre-padded 1088-bit rate blocks;
|
||||
// the CALLER applies SHA3-256 padding to the final block.
|
||||
//
|
||||
// Keccak interface (port names mirror the keccak_core port each connects to):
|
||||
// kc_state_o[1599:0] - keccak result (input, from keccak_core.state_o)
|
||||
// kc_valid_o - keccak output valid (input, from keccak_core.valid_o)
|
||||
// kc_ready_o - keccak ready for input (input, from keccak_core.ready_o)
|
||||
// kc_state_i[1599:0] - keccak input state (output, to keccak_core.state_i)
|
||||
// kc_valid_i - request permutation (output, to keccak_core.valid_i)
|
||||
// kc_ready_i - accept keccak output (output, to keccak_core.ready_i, always 1)
|
||||
|
||||
module sha3_top_shared (
|
||||
input clk,
|
||||
input rst_n,
|
||||
input [1:0] mode,
|
||||
input [511:0] data_i,
|
||||
input valid_i,
|
||||
output ready_o,
|
||||
output [511:0] hash_o,
|
||||
output valid_o,
|
||||
input ready_i,
|
||||
// --- multi-block SHA3-256 absorb (tie mb_en=0 to disable) ---
|
||||
input mb_en,
|
||||
input [1087:0] mb_block_i,
|
||||
input mb_valid_i,
|
||||
input mb_last_i,
|
||||
output mb_ready_o,
|
||||
// --- external (shared) keccak_core interface ---
|
||||
input [1599:0] kc_state_o,
|
||||
input kc_valid_o,
|
||||
/* verilator lint_off UNUSEDSIGNAL */
|
||||
input kc_ready_o,
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
output [1599:0] kc_state_i,
|
||||
output kc_valid_i,
|
||||
output kc_ready_i
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// FSM state encoding
|
||||
// ================================================================
|
||||
localparam ST_IDLE = 2'd0;
|
||||
localparam ST_PERMUTE = 2'd1;
|
||||
localparam ST_SQUEEZE = 2'd2;
|
||||
|
||||
reg [1:0] state_r, state_next;
|
||||
|
||||
// ================================================================
|
||||
// Absorb state: message || suffix || pad10*1 into rate bits
|
||||
// ================================================================
|
||||
wire [575:0] g_pad;
|
||||
wire [575:0] g512_pad;
|
||||
wire [1087:0] h_pad;
|
||||
wire [1087:0] j_pad;
|
||||
|
||||
assign g_pad = {1'b1, {308{1'b0}}, 1'b1, 2'b10, data_i[263:0]};
|
||||
assign h_pad = {1'b1, {828{1'b0}}, 1'b1, 2'b10, data_i[255:0]};
|
||||
// J: SHAKE suffix is "1111" — all ones, order irrelevant
|
||||
assign j_pad = {1'b1, {570{1'b0}}, 1'b1, 4'b1111, data_i[511:0]};
|
||||
// mode 11 = G over a full 64-byte (512-bit) message (Encaps: G(m||H(ek))).
|
||||
// SHA3-512 rate=576; suffix 01 + pad10*1: 1 + 60 zeros + 1 + 2'b10 + 512 = 576.
|
||||
assign g512_pad = {1'b1, {60{1'b0}}, 1'b1, 2'b10, data_i[511:0]};
|
||||
|
||||
wire [1599:0] absorb_state;
|
||||
assign absorb_state = (mode == 2'b00) ? {{(1600-576){1'b0}}, g_pad} :
|
||||
(mode == 2'b01) ? {{(1600-1088){1'b0}}, h_pad} :
|
||||
(mode == 2'b10) ? {{(1600-1088){1'b0}}, j_pad} :
|
||||
{{(1600-576){1'b0}}, g512_pad};
|
||||
|
||||
// ================================================================
|
||||
// Multi-block SHA3-256 absorb FSM (active only when mb_en=1)
|
||||
// ================================================================
|
||||
localparam MB_IDLE = 2'd0; // ready for a block (or first block)
|
||||
localparam MB_PERMUTE = 2'd1; // keccak running on xored state
|
||||
localparam MB_DONE = 2'd2; // squeeze: present 256-bit digest
|
||||
|
||||
reg [1:0] mb_state, mb_state_next;
|
||||
reg [1599:0] mb_state_r; // running sponge state
|
||||
reg mb_last_r; // captured last-block flag
|
||||
reg [255:0] mb_digest_r; // latched 256-bit digest (sticky in MB_DONE)
|
||||
|
||||
// XOR the incoming block into the low 1088 bits (rate) of the state.
|
||||
wire [1599:0] mb_xored;
|
||||
assign mb_xored = mb_state_r ^ {{(1600-1088){1'b0}}, mb_block_i};
|
||||
|
||||
// Accept a block only in MB_IDLE while enabled.
|
||||
assign mb_ready_o = mb_en && (mb_state == MB_IDLE);
|
||||
wire mb_accept = mb_en && (mb_state == MB_IDLE) && mb_valid_i;
|
||||
wire mb_kc_valid = mb_accept; // start keccak on the accept cycle
|
||||
|
||||
always @(*) begin
|
||||
mb_state_next = mb_state;
|
||||
case (mb_state)
|
||||
MB_IDLE: if (mb_accept) mb_state_next = MB_PERMUTE;
|
||||
MB_PERMUTE: if (kc_valid_o) mb_state_next = mb_last_r ? MB_DONE : MB_IDLE;
|
||||
MB_DONE: if (ready_i) mb_state_next = MB_IDLE;
|
||||
default: mb_state_next = MB_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// External keccak_core interface (was internal keccak_core)
|
||||
// ================================================================
|
||||
// Keccak input: multi-block xored state when mb_en, else single-block absorb.
|
||||
assign kc_state_i = mb_en ? mb_xored : absorb_state;
|
||||
// Always accept keccak output (matches the dedicated-core ready_i=1'b1).
|
||||
assign kc_ready_i = 1'b1;
|
||||
// kc_valid_i: single-block start (state_next==PERMUTE) OR multi-block accept.
|
||||
assign kc_valid_i = mb_en ? mb_kc_valid : (state_next == ST_PERMUTE);
|
||||
|
||||
// ================================================================
|
||||
// FSM combinational logic
|
||||
// ================================================================
|
||||
assign ready_o = !mb_en && (state_r == ST_IDLE);
|
||||
|
||||
always @(*) begin
|
||||
state_next = state_r;
|
||||
case (state_r)
|
||||
ST_IDLE: if (valid_i && ready_o) state_next = ST_PERMUTE;
|
||||
ST_PERMUTE: if (kc_valid_o) state_next = ST_SQUEEZE;
|
||||
ST_SQUEEZE: if (valid_o && ready_i) state_next = ST_IDLE;
|
||||
default: state_next = ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// Output
|
||||
// ================================================================
|
||||
reg [511:0] squeezed_state_r;
|
||||
|
||||
// valid_o / hash_o serve both paths, selected by mb_en.
|
||||
assign valid_o = mb_en ? (mb_state == MB_DONE) : (state_r == ST_SQUEEZE);
|
||||
assign hash_o = mb_en ? {256'b0, mb_digest_r} : squeezed_state_r;
|
||||
|
||||
// ================================================================
|
||||
// Sequential logic
|
||||
// ================================================================
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
state_r <= ST_IDLE;
|
||||
squeezed_state_r <= 512'd0;
|
||||
mb_state <= MB_IDLE;
|
||||
mb_state_r <= 1600'd0;
|
||||
mb_last_r <= 1'b0;
|
||||
mb_digest_r <= 256'd0;
|
||||
end else begin
|
||||
state_r <= state_next;
|
||||
mb_state <= mb_state_next;
|
||||
|
||||
// --- single-block: latch squeezed output ---
|
||||
if (state_r == ST_PERMUTE && kc_valid_o) begin
|
||||
squeezed_state_r <= kc_state_o[511:0];
|
||||
end
|
||||
|
||||
// --- multi-block: capture last flag on accept ---
|
||||
if (mb_accept) begin
|
||||
mb_last_r <= mb_last_i;
|
||||
end
|
||||
|
||||
// --- multi-block: latch permuted state when keccak finishes ---
|
||||
if (mb_state == MB_PERMUTE && kc_valid_o) begin
|
||||
mb_state_r <= kc_state_o;
|
||||
if (mb_last_r) mb_digest_r <= kc_state_o[255:0];
|
||||
end
|
||||
|
||||
// --- multi-block: clear running state after digest consumed ---
|
||||
if (mb_state == MB_DONE && ready_i) begin
|
||||
mb_state_r <= 1600'd0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user