Phase 1.1 of ML-KEM sync rewrite. - keccak_round.v: combinational theta/rho/pi/chi/iota - keccak_core.v: 24-round pipeline, valid/ready - sha3_top.v: sponge FSM, modes G(SHA3-512)/H(SHA3-256)/J(SHAKE-256) - Verilator C++ TB + Python vector gen against reference - Verified: 25/25 vectors bit-exact vs Python G()/H()/J()
140 lines
5.2 KiB
Verilog
140 lines
5.2 KiB
Verilog
// 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 (Phase 1.1).
|
|
//
|
|
// Modes:
|
|
// 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
|
|
//
|
|
// Interface:
|
|
// clk, rst_n - clock, active-low reset
|
|
// mode[1:0] - 00=G, 01=H, 10=J
|
|
// data_i - 512-bit message input
|
|
// valid_i - input valid
|
|
// ready_o - can accept new input
|
|
// hash_o - 512-bit hash output (lower 256 for H/J)
|
|
// valid_o - output valid
|
|
// ready_i - consumer accepts output
|
|
|
|
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
|
|
);
|
|
|
|
// ================================================================
|
|
// 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;
|
|
|
|
// ================================================================
|
|
// 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_core #(.ROUNDS(24)) u_keccak (
|
|
.clk (clk),
|
|
.rst_n (rst_n),
|
|
.state_i (absorb_state),
|
|
.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 = (state_r == ST_IDLE);
|
|
|
|
// kc_valid_i: start keccak_core during IDLE when input accepted.
|
|
// Driven from state_next to avoid NBA latency: when state_r==IDLE
|
|
// and valid_i→1, state_next=PERMUTE immediately (combinational).
|
|
// keccak_core sees valid_i=1 on the stable cycle before the posedge.
|
|
// On subsequent PERMUTE cycles, busy_r blocks re-start.
|
|
assign kc_valid_i = (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
|
|
// ================================================================
|
|
assign valid_o = (state_r == ST_SQUEEZE);
|
|
assign hash_o = squeezed_state_r;
|
|
|
|
// ================================================================
|
|
// Sequential logic
|
|
// ================================================================
|
|
// Register for squeezed output (only 512 bits needed)
|
|
reg [511:0] squeezed_state_r;
|
|
|
|
always @(posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
state_r <= ST_IDLE;
|
|
squeezed_state_r <= 512'd0;
|
|
end else begin
|
|
state_r <= state_next;
|
|
|
|
// Latch squeezed output when keccak_core finishes
|
|
if (state_r == ST_PERMUTE && kc_valid_o) begin
|
|
squeezed_state_r <= kc_state_o[511:0];
|
|
end
|
|
end
|
|
end
|
|
|
|
endmodule
|