Files
mlkem-sync/sync_rtl/sha3/sha3_top_shared.v
FallenSigh 0a8b3dae69 feat(enc): Encaps E0 - op_i/msg_i/ek-load scaffold + H(ek)+G(m||H(ek))
Extend mlkem_top with a runtime op_i select (0=KeyGen, 1=Encaps) and the
first Encaps stages, reusing the shared keccak_core and the ST_H multi-block
SHA3-256 machinery:
  ST_ENC_H: H(ek) over preloaded ek_bram (same FSM as KeyGen ST_H)
  ST_ENC_G: (K,r) = G(m||H(ek)) via new 64-byte single-block SHA3-512

- sha3_top_shared: add mode=2'b11 = SHA3-512 over a full 512-bit message
  (g512_pad). Standalone tb_sha3_g512 confirms it byte-exact.
- mlkem_top: new ports op_i, msg_i, ek_in_{we,addr,byte} (ek preload), ss_o,
  dbg_ct_*, dbg_r_o/dbg_hek_o. st widened 4->5 bits; ST_ENC_* states added.
  Renamed message port to msg_i to avoid collision with ST_M counter m_i.
- TB tb_mlkem_enc_katK + gen_encaps_vectors.py (per-byte ek/m/ct/ss vectors).

Verified ss==KAT.ss for K=2/3/4, cases 0-2 (all PASS). KeyGen unaffected
(K=2 c0 still ek==pk, dk==sk byte-exact).
2026-06-29 01:00:47 +08:00

188 lines
8.1 KiB
Verilog

// 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