diff --git a/sync_rtl/sha3/TB/tb_sha3.cpp b/sync_rtl/sha3/TB/tb_sha3.cpp new file mode 100644 index 0000000..7effb9a --- /dev/null +++ b/sync_rtl/sha3/TB/tb_sha3.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/sync_rtl/sha3/keccak_core.v b/sync_rtl/sha3/keccak_core.v new file mode 100644 index 0000000..6cf5c7d --- /dev/null +++ b/sync_rtl/sha3/keccak_core.v @@ -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 diff --git a/sync_rtl/sha3/keccak_round.v b/sync_rtl/sha3/keccak_round.v new file mode 100644 index 0000000..4ba1a25 --- /dev/null +++ b/sync_rtl/sha3/keccak_round.v @@ -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 diff --git a/sync_rtl/sha3/sha3_top.v b/sync_rtl/sha3/sha3_top.v new file mode 100644 index 0000000..a9b2c8c --- /dev/null +++ b/sync_rtl/sha3/sha3_top.v @@ -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_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 diff --git a/test_framework/modules/sha3/gen_vectors.py b/test_framework/modules/sha3/gen_vectors.py new file mode 100644 index 0000000..51db608 --- /dev/null +++ b/test_framework/modules/sha3/gen_vectors.py @@ -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') diff --git a/test_framework/modules/sha3/test_plan.json b/test_framework/modules/sha3/test_plan.json new file mode 100644 index 0000000..be79c19 --- /dev/null +++ b/test_framework/modules/sha3/test_plan.json @@ -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" + } + ] +}