feat(sha3): implement synchronous Keccak-f[1600] core with G/H/J modes

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()
This commit is contained in:
2026-06-24 20:33:44 +08:00
parent 94783441b4
commit 453bc899fc
6 changed files with 904 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
// tb_sha3.cpp - Verilator C++ testbench for sha3_top
//
// Reads test vectors from +VECTOR_FILE=plusarg.
// Format: "MM DDDD..." (mode hex, then 512-bit message hex)
// MM: "00"=G, "01"=H, "10"=J
// DDDD...: 128 hex chars representing 512-bit data_i
//
// Drives DUT with mode and input, waits for ready_o/valid_o,
// prints "RESULT: OUTPUT_HEX\n" to stdout.
//
// Clock: 10ns period. Reset: 2 cycles.
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include "Vsha3_top.h"
#include "verilated.h"
#define CLK_PERIOD_NS 10.0
#define TIMEOUT_CYCLES 500000
static vluint64_t main_time = 0;
double sc_time_stamp() {
return main_time;
}
// Toggle clock: both edges + eval (one full cycle)
static void posedge(Vsha3_top* dut) {
dut->clk = !dut->clk;
main_time += (vluint64_t)(CLK_PERIOD_NS / 2.0);
dut->eval();
dut->clk = !dut->clk;
main_time += (vluint64_t)(CLK_PERIOD_NS / 2.0);
dut->eval();
}
static int hex_char_to_nibble(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return 0;
}
// Parse a hex string into Verilator WData words (16 x 32-bit = 512 bits).
// Hex string is MSB-first (leftmost hex char = bits 511:508).
// data[0] = bits[31:0], data[15] = bits[511:480].
static void hex_to_512(const std::string& hex, uint32_t data_words[16]) {
for (int w = 0; w < 16; w++) data_words[w] = 0;
int len = (int)hex.length();
// nibble_idx increments for each hex char from RIGHT to LEFT
int nibble_idx = 0;
for (int i = len - 1; i >= 0; i--) {
char c = hex[i];
if (c == ' ' || c == '\t') continue;
int nib = hex_char_to_nibble(c);
int word_idx = nibble_idx / 8; // 8 nibbles per 32-bit word
int shift = (nibble_idx % 8) * 4; // shift within the word
if (word_idx < 16) {
data_words[word_idx] |= ((uint32_t)nib << shift);
}
nibble_idx++;
}
}
// Print hash_o as hex (MSB-first).
// data[0] = bits[31:0], data[15] = bits[511:480].
static void print_hex(uint32_t data_words[16], int bits) {
int full_words = bits / 32;
// Build hex from MSB nibble to LSB nibble
for (int w = full_words - 1; w >= 0; w--) {
uint32_t val = data_words[w];
for (int j = 28; j >= 0; j -= 4) {
int nib = (int)((val >> j) & 0xF);
printf("%01X", nib);
}
}
printf("\n");
}
int main(int argc, char** argv) {
Verilated::commandArgs(argc, argv);
// Parse +VECTOR_FILE= plusarg
const char* vector_file = NULL;
for (int i = 1; i < argc; i++) {
std::string arg(argv[i]);
if (arg.rfind("+VECTOR_FILE=", 0) == 0) {
vector_file = argv[i] + 13;
}
}
if (!vector_file) {
std::cerr << "ERROR: +VECTOR_FILE= not specified" << std::endl;
return 1;
}
std::ifstream infile(vector_file);
if (!infile.is_open()) {
std::cerr << "ERROR: Cannot open vector file: " << vector_file << std::endl;
return 1;
}
// Instantiate DUT
Vsha3_top* dut = new Vsha3_top;
// Initialize
dut->clk = 0;
dut->rst_n = 0;
dut->mode = 0;
for (int w = 0; w < 16; w++) dut->data_i[w] = 0;
dut->valid_i = 0;
dut->ready_i = 0;
// Reset: 2 full cycles
for (int i = 0; i < 2; i++) posedge(dut);
dut->rst_n = 1;
// Consumer always ready
dut->ready_i = 1;
std::string line;
vluint64_t cycle = 0;
int vec_count = 0;
while (std::getline(infile, line)) {
if (line.empty() || line[0] == '#') continue;
// Parse: MODE_HEX MESSAGE_HEX
std::istringstream iss(line);
std::string mode_str, data_hex;
if (!(iss >> mode_str >> data_hex)) continue;
if (mode_str.length() < 2) continue;
int mode_val = hex_char_to_nibble(mode_str[1]);
// Set mode
dut->mode = mode_val & 0x3;
// Set data_i from hex string
uint32_t data_words[16];
hex_to_512(data_hex, data_words);
for (int w = 0; w < 16; w++) dut->data_i[w] = data_words[w];
// Assert valid_i for one cycle
dut->valid_i = 1;
// posedge: DUT samples valid_i, starts keccak_core
posedge(dut);
cycle++;
dut->valid_i = 0;
// Wait for valid_o (keccak_core takes ~25 cycles)
do {
posedge(dut);
cycle++;
if (cycle > TIMEOUT_CYCLES) {
std::cerr << "ERROR: Timeout waiting for valid_o (vec " << vec_count << ")" << std::endl;
goto done;
}
} while (!dut->valid_o);
// Read hash_o and print
uint32_t hash_words[16];
for (int w = 0; w < 16; w++) hash_words[w] = dut->hash_o[w];
int out_bits = (mode_val == 0) ? 512 : 256;
printf("RESULT: ");
print_hex(hash_words, out_bits);
// One more cycle for valid_o handshake
posedge(dut);
cycle++;
vec_count++;
}
done:
infile.close();
delete dut;
if (vec_count == 0) {
std::cerr << "ERROR: No vectors processed" << std::endl;
return 1;
}
return 0;
}

View 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

View 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

139
sync_rtl/sha3/sha3_top.v Normal file
View File

@@ -0,0 +1,139 @@
// 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_i1, 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

View File

@@ -0,0 +1,195 @@
"""gen_vectors.py - Test vector generator for sha3 module.
Generates vectors for G (SHA3-512), H (SHA3-256), J (SHAKE-256) modes
and computes expected outputs using the Python reference implementation.
"""
import os
import random
import sys
# Add test_framework/lib to path for VectorGenerator base class
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
# Add Python reference path
_REF_PATH = os.path.expanduser(
"~/Dev/server_code/python_project/PQC_2025/A_ML_KEM_v0")
_REF_PATH = os.path.expanduser(_REF_PATH)
sys.path.insert(0, _REF_PATH)
import SHA_3
from vector_gen import VectorGenerator
def _bits_to_hex(bits_str, num_bits):
"""Convert binary string (LSB-first: bits_str[0] = bit 0) to hex.
Returns hex with MSB-nibble first (matching Verilog %%X format).
Each group of 4 bits forms a nibble: bit[i+3:i] → two hex chars.
"""
result = []
for i in range(0, num_bits, 4):
nib = 0
for j in range(4):
pos = i + j
if pos < len(bits_str) and bits_str[pos] == '1':
nib |= (1 << j)
result.insert(0, '0123456789ABCDEF'[nib])
return ''.join(result)
def _hex_to_bits(hex_str, num_bits):
"""Convert hex string (MSB-first) to binary string (LSB-first).
Returns a binary string where result[0] = bit 0 (LSB).
"""
hex_val = int(hex_str, 16)
bits = ''
for i in range(num_bits):
bits += '1' if (hex_val & (1 << i)) else '0'
return bits
def _random_bits(length):
"""Generate a random binary string of given length."""
val = random.getrandbits(length)
bits = ''
for i in range(length):
bits += '1' if (val & (1 << i)) else '0'
return bits
class Sha3VectorGenerator(VectorGenerator):
"""Generates test vectors for the sha3_top module."""
def generate_one(self, params: dict) -> dict:
"""Generate a single test vector.
For G mode:
- d: 256-bit random binary string
- k: 8-bit random binary string
- Input to RTL: d||k packed into 512-bit value (data_i[263:0])
- Expected: G(d, k) = 512-bit binary string
For H mode:
- ek: 256-bit random binary string (single-block)
- Input to RTL: ek in data_i[255:0]
- Expected: H(ek) = 256-bit binary string
For J mode:
- z: 256-bit random binary string
- c: 256-bit random binary string
- Input to RTL: z||c in data_i[511:0]
- Expected: J(z, c) = 256-bit binary string
Args:
params: dict with 'mode' key ('G', 'H', 'J').
Returns:
dict with 'input' and 'expected' keys.
"""
mode = params.get('mode', 'G')
if mode == 'G':
# d: 256 bits, k: 8 bits
d = _random_bits(256)
k = _random_bits(8)
msg = d + k # 264 bits, LSB first
# Pack into 512-bit hex value: data_i[511:0]
# pad msg to 512 bits with zeros (upper bits = 0)
msg_512 = msg + '0' * (512 - len(msg))
input_hex = _bits_to_hex(msg_512, 512)
# Expected: G(d, k) returns binary string
expected_bits = SHA_3.G(d, k)
expected_hex = _bits_to_hex(expected_bits, 512)
return {
'input': {
'mode': 0,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
elif mode == 'H':
# ek: 256-bit random message (single-block test)
ek = _random_bits(256)
# Pack into 512-bit hex: data_i[511:0]
msg_512 = ek + '0' * (512 - len(ek))
input_hex = _bits_to_hex(msg_512, 512)
# Expected: H(ek) returns 256-bit binary string
expected_bits = SHA_3.H(ek)
expected_hex = _bits_to_hex(expected_bits, 256)
return {
'input': {
'mode': 1,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
elif mode == 'J':
# z: 256 bits, c: 256 bits
z = _random_bits(256)
c = _random_bits(256)
msg = z + c # 512 bits
# Pack into 512-bit hex
input_hex = _bits_to_hex(msg, 512)
# Expected: J(z, c) returns 256-bit binary string
expected_bits = SHA_3.J(z, c)
expected_hex = _bits_to_hex(expected_bits, 256)
return {
'input': {
'mode': 2,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
else:
raise ValueError(f"Unknown mode: {mode}")
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
"""Write input vectors as "MM DDDD..." hex format (mode, data).
Each line: "MM DDDD..." where MM is hex mode (00/01/10) and DDDD...
is the hex-encoded 512-bit message.
Args:
vectors: List of vector dicts from generate_one().
filepath: Path to write the hex file.
"""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w') as f:
for v in vectors:
mode_val = v['input']['mode']
data_hex = v['input']['data']
f.write(f'{mode_val:02X} {data_hex}\n')
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
"""Write expected output as hex strings, one per line.
Args:
vectors: List of vector dicts from generate_one().
filepath: Path to write the expected hex file.
"""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w') as f:
for v in vectors:
expected_hex = v['expected']['hash']
f.write(f'{expected_hex}\n')

View File

@@ -0,0 +1,31 @@
{
"module": "sha3",
"rtl_top": "sync_rtl/sha3/sha3_top.v",
"rtl_deps": ["sync_rtl/sha3/keccak_core.v", "sync_rtl/sha3/keccak_round.v"],
"tb_cpp": "sync_rtl/sha3/TB/tb_sha3.cpp",
"simulator": "verilator",
"timeout_s": 120,
"cases": [
{
"id": "g_basic",
"description": "SHA3-512 G(d||k): 64B input → 64B output, verified against Python reference",
"params": {"mode": "G", "k": 2},
"num_vectors": 10,
"tolerance": "bit_exact"
},
{
"id": "h_basic",
"description": "SHA3-256 H(ek): 32B+ output, single-block",
"params": {"mode": "H", "k": 2},
"num_vectors": 10,
"tolerance": "bit_exact"
},
{
"id": "j_basic",
"description": "SHAKE-256 J(z||c): variable input → 32B output",
"params": {"mode": "J"},
"num_vectors": 5,
"tolerance": "bit_exact"
}
]
}