| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Keccak full round logic based on given input `Width` |
| // e.g. Width 800 requires 22 rounds |
| |
| `include "prim_assert.sv" |
| |
| module keccak_round #( |
| parameter int Width = 1600, // b= {25, 50, 100, 200, 400, 800, 1600} |
| |
| // Derived |
| localparam int W = Width/25, |
| localparam int L = $clog2(W), |
| localparam int MaxRound = 12 + 2*L, // Keccak-f only |
| localparam int RndW = $clog2(MaxRound+1), // Representing up to MaxRound-1 |
| |
| // Feed parameters |
| parameter int DInWidth = 64, // currently only 64bit supported |
| localparam int DInEntry = Width / DInWidth, |
| localparam int DInAddr = $clog2(DInEntry), |
| |
| // Control parameters |
| parameter bit EnMasking = 1'b0, // Enable secure hardening |
| localparam int Share = EnMasking ? 2 : 1, |
| |
| // If ReuseShare is not 0, the logic will use unused sheet as an entropy |
| // at Chi stage. It still needs small portion of the fresh entropy from |
| // rand_data_i but the amount required are significantly small. |
| // TODO: Implement the feature inside keccak_2share |
| parameter bit ReuseShare = 1'b0 // Re-use adjacent share for entropy |
| ) ( |
| input clk_i, |
| input rst_ni, |
| |
| // Message Feed |
| input valid_i, |
| input [DInAddr-1:0] addr_i, |
| input [DInWidth-1:0] data_i [Share], |
| output ready_o, |
| |
| // In-process control |
| input run_i, // Pulse signal to initiates Keccak full round |
| input rand_valid_i, |
| input [Width-1:0] rand_data_i, |
| output logic rand_consumed_o, |
| |
| output logic complete_o, // Indicates full round is done |
| |
| // State out. This can be used as Digest |
| output logic [Width-1:0] state_o [Share], |
| |
| input clear_i // Clear internal state to '0 |
| ); |
| |
| ///////////////////// |
| // Control signals // |
| ///////////////////// |
| |
| // Update storage register |
| logic update_storage; |
| |
| // Reset the storage to 0 to initiate new Hash operation |
| logic rst_storage; |
| |
| // XOR message into storage register |
| // It only does based on the given DInWidth. |
| // If DInWidth < Width, it takes multiple cycles to XOR all message |
| logic xor_message; |
| |
| // Select Keccak_p datapath |
| // 0: Select Phase1 (Theta -> Rho -> Pi) |
| // 1: Select Phase2 (Chi -> Iota) |
| // `sel_mux` need to be asserted until the Chi stage is consumed, |
| // It means sel_mux should be 1 until one cycle after `rand_valid_i` is asserted. |
| logic sel_mux; |
| |
| |
| // Increase/ Reset Round number |
| logic inc_rnd_num; |
| logic rst_rnd_num; |
| |
| // Round reaches end |
| // This signal indicates the round reaches desired number, which is MaxRound -1. |
| // MaxRound is dependant on the Width. In case of SHA3/SHAKE, MaxRound is 24. |
| logic rnd_eq_end; |
| |
| // Complete of Keccak_f |
| // State machine asserts `complete_d` when it reaches at the end of round and |
| // operation (Phase3 if Masked). The the stage, the storage still doesn't have |
| // the valid states. So precisely it is not completed yet. |
| // State generated `complete_d` is latched with the clock and creates a pulse |
| // signal one cycle later. The signal is the indication of completion. |
| // |
| // Intentionally removed any intermediate step (so called StComplete) in order |
| // to save a clock to proceeds next round. |
| logic complete_d; |
| |
| ////////////////////// |
| // Datapath Signals // |
| ////////////////////// |
| |
| // Single round keccak output data |
| logic [Width-1:0] keccak_out [Share]; |
| |
| // Keccak Round indicator: range from 0 .. MaxRound |
| logic [RndW-1:0] round; |
| |
| // Random value and valid signal used in Keccak_p |
| // There's plan to make random value generation configurable. |
| // 1. Tied to 0 in case of random value is not needed. It means the Keccak |
| // doesn't need to be masked and throughput is the most important thing. |
| // 2. Receive random value from entropy source. This requires to fill 1600b |
| // of entropy. It takes long time so generally it will have smaller bits |
| // from tru entropy source and expands to 1600b (Width). |
| // 3. Reuse Share. This option is to reuse the other part of share. Chi stage |
| // uses 3 sheets to create a sheet. (newX = X ^ (~(X+1) & (X+2))). So the |
| // other two shares (X-1, X-2) can be assumed as random values, and may be |
| // used as entropy source. It is weaker than the use of true entropy, but |
| // much faster. |
| logic keccak_rand_valid, keccak_rand_consumed; |
| logic [Width-1:0] keccak_rand_data; |
| |
| ////////////////////// |
| // Keccak Round FSM // |
| ////////////////////// |
| |
| // state inputs |
| assign rnd_eq_end = (int'(round) == MaxRound - 1); |
| |
| typedef enum logic [2:0] { |
| StIdle, |
| |
| // Active state is used in Unmasked version only. |
| // It handles keccak round in a cycle |
| StActive, |
| |
| // Phase1 --> Phase2 --> Phase3 |
| // Activated only in Masked version. |
| // Phase1 processes Theta, Rho, Pi steps in a cycle and stores the states |
| // into storage. It unconditionally moves to Phase2. |
| StPhase1, |
| |
| // First half part of Chi step. It waits random value is ready |
| // then move to Phase 3. |
| StPhase2, |
| |
| // Second half of Chi step and Iota step. |
| // This state doesn't require random value as it is XORed into the states |
| // in Phase2. If round is reached to the end (MaxRound -1) then it |
| // completes the process and goes back to Idle. If not, repeats the Phase |
| // again. |
| StPhase3, |
| |
| // Error state. Not clearly defined yet. |
| // Intention is if any unexpected input in the process, state moves to |
| // here and report through the error fifo with debugging information. |
| StError |
| } keccak_st_e; |
| keccak_st_e keccak_st, keccak_st_d; |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| keccak_st <= StIdle; |
| end else begin |
| keccak_st <= keccak_st_d; |
| end |
| end |
| |
| // Next state logic and output logic |
| always_comb begin |
| // Default values |
| keccak_st_d = StIdle; |
| |
| xor_message = 1'b 0; |
| update_storage = 1'b 0; |
| rst_storage = 1'b 0; |
| |
| inc_rnd_num = 1'b 0; |
| rst_rnd_num = 1'b 0; |
| |
| keccak_rand_consumed = 1'b 0; |
| |
| sel_mux = 1'b 0; |
| |
| complete_d = 1'b 0; |
| |
| unique case (keccak_st) |
| StIdle: begin |
| if (valid_i) begin |
| // State machine allows Sponge Absorbing only in Idle state. |
| keccak_st_d = StIdle; |
| |
| xor_message = 1'b 1; |
| update_storage = 1'b 1; |
| end else if (clear_i) begin |
| // Opt1. State machine allows resetting the storage only in Idle |
| // Opt2. storage resets regardless of states but clear_i |
| // Both are added in the design at this time. Will choose the |
| // direction later. |
| keccak_st_d = StIdle; |
| |
| rst_storage = 1'b 1; |
| end else if (EnMasking && run_i) begin |
| // Masked version of Keccak handling |
| keccak_st_d = StPhase1; |
| end else if (!EnMasking && run_i) begin |
| // Unmasked version of Keccak handling |
| keccak_st_d = StActive; |
| end else begin |
| keccak_st_d = StIdle; |
| end |
| end |
| |
| StActive: begin |
| // Run Keccak single round logic until it reaches MaxRound - 1 |
| update_storage = 1'b 1; |
| |
| if (rnd_eq_end) begin |
| keccak_st_d = StIdle; |
| |
| rst_rnd_num = 1'b 1; |
| complete_d = 1'b 1; |
| end else begin |
| keccak_st_d = StActive; |
| |
| inc_rnd_num = 1'b 1; |
| end |
| end |
| |
| StPhase1: begin |
| // Unconditionally move to next phase. |
| keccak_st_d = StPhase2; |
| |
| update_storage = 1'b 1; |
| sel_mux = 1'b 0; |
| end |
| |
| StPhase2: begin |
| // Second phase (Chi 1/2) |
| sel_mux = 1'b 1; |
| |
| if (keccak_rand_valid) begin |
| keccak_st_d = StPhase3; |
| |
| keccak_rand_consumed = 1'b 1; |
| end else begin |
| keccak_st_d = StPhase2; |
| end |
| end |
| |
| StPhase3: begin |
| sel_mux = 1'b 1; |
| update_storage = 1'b 1; |
| |
| if (rnd_eq_end) begin |
| keccak_st_d = StIdle; |
| |
| rst_rnd_num = 1'b 1; |
| complete_d = 1'b 1; |
| end else begin |
| keccak_st_d = StPhase1; |
| |
| inc_rnd_num = 1'b 1; |
| end |
| end |
| |
| StError: begin |
| keccak_st_d = StIdle; |
| end |
| |
| default: begin |
| keccak_st_d = StError; |
| end |
| endcase |
| end |
| |
| // Ready indicates the keccak_round is able to receive new message. |
| // While keccak_round is processing the data, it blocks the new message to be |
| // XORed into the current state. |
| assign ready_o = (keccak_st == StIdle) ? 1'b 1 : 1'b 0; |
| |
| //////////////////////////// |
| // Keccak state registers // |
| //////////////////////////// |
| logic [Width-1:0] storage [Share]; |
| logic [Width-1:0] storage_d [Share]; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| storage <= '{default:'0}; |
| end else if (rst_storage) begin |
| storage <= '{default:'0}; |
| end else if (update_storage) begin |
| storage <= storage_d; |
| end |
| end |
| |
| assign state_o = storage; |
| |
| // Storage register input |
| // The incoming message is XORed with the existing storage registers. |
| // The logic can accept not a block size incoming message chunk but |
| // the size defined in `DInWidth` parameter with its position. |
| |
| always_comb begin |
| storage_d = keccak_out; |
| if (xor_message) begin |
| for (int j = 0 ; j < Share ; j++) begin |
| for (int unsigned i = 0 ; i < DInEntry ; i++) begin |
| // TODO: handle If Width is not integer divisable by DInWidth |
| // Currently it is not allowed to have partial write |
| // Please see the Assertion `WidthDivisableByDInWidth_A` |
| if (addr_i == i[DInAddr-1:0]) begin |
| storage_d[j][i*DInWidth+:DInWidth] = |
| storage[j][i*DInWidth+:DInWidth] ^ data_i[j]; |
| end else begin |
| storage_d[j][i*DInWidth+:DInWidth] = storage[j][i*DInWidth+:DInWidth]; |
| end |
| end // for i |
| end // for j |
| end // if xor_message |
| end |
| |
| ////////////// |
| // Datapath // |
| ////////////// |
| keccak_2share #( |
| .Width (Width), |
| .EnMasking (EnMasking) |
| ) u_keccak_p ( |
| .clk_i, |
| .rst_ni, |
| |
| .rnd_i (round), |
| .rand_valid_i (keccak_rand_valid), |
| .rand_i (keccak_rand_data), |
| .sel_i (sel_mux), |
| .s_i (storage), |
| .s_o (keccak_out) |
| ); |
| |
| // keccak entropy handling |
| // TODO: Consider reuse of internal share |
| assign keccak_rand_valid = rand_valid_i; |
| assign rand_consumed_o = keccak_rand_consumed; |
| |
| assign keccak_rand_data = rand_data_i; |
| `ASSERT_INIT(NoReuseShare_A, ReuseShare == 0) |
| |
| // Round number |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| round <= '0; |
| end else if (rst_rnd_num) begin |
| round <= '0; |
| end else if (inc_rnd_num) begin |
| round <= round + 1'b 1; |
| end |
| end |
| |
| // completion signal |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| complete_o <= 1'b 0; |
| end else begin |
| complete_o <= complete_d; |
| end |
| end |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| // Only allow `DInWidth` that `Width` is integer divisable by `DInWidth` |
| `ASSERT_INIT(WidthDivisableByDInWidth_A, (Width % DInWidth) == 0) |
| |
| // If `run_i` triggerred, it shall complete |
| //`ASSERT(RunResultComplete_A, run_i ##[MaxRound:] complete_o, clk_i, !rst_ni) |
| |
| // valid_i and run_i cannot be asserted at the same time |
| `ASSUME(OneHot0ValidAndRun_A, $onehot0({valid_i, run_i}), clk_i, !rst_ni) |
| |
| // valid_i, run_i only asserted in Idle state |
| `ASSUME(ValidRunAssertStIdle_A, valid_i || run_i |-> keccak_st == StIdle, clk_i, !rst_ni) |
| |
| // clear_i is assumed to be asserted in Idle state |
| `ASSUME(ClearAssertStIdle_A, clear_i |-> keccak_st == StIdle, clk_i, !rst_ni) |
| |
| // EnMasking controls the valid states |
| if (EnMasking) begin : gen_mask_st_chk |
| `ASSERT(EnMaskingValidStates_A, keccak_st != StActive, clk_i, !rst_ni) |
| end else begin : gen_unmask_st_chk |
| `ASSERT(UnmaskValidStates_A, !(keccak_st inside {StPhase1, StPhase2, StPhase3}), |
| clk_i, !rst_ni) |
| end |
| |
| // If message is fed, it shall start from 0 |
| // TODO: Handle the case `addr_i` changes prior to `valid_i` |
| //`ASSUME(MsgStartFrom0_A, valid_i |-> |
| // (addr_i == 0) || (addr_i == $past(addr_i) + 1), |
| // clk_i,!rst_ni) |
| |
| |
| endmodule |
| |