| // 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 |
| import prim_mubi_pkg::*; |
| #( |
| 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 SCA hardening, requires Width >= 50 |
| localparam int Share = EnMasking ? 2 : 1 |
| ) ( |
| 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 rand_early_i, |
| input [Width/2-1:0] rand_data_i, |
| input rand_aux_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], |
| |
| // Life cycle |
| input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i, |
| |
| // Errors: |
| // sparse_fsm_error: Checking if FSM state falls into unknown value |
| output logic sparse_fsm_error_o, |
| // round_count_error: prim_count checks round value consistency |
| output logic round_count_error_o, |
| // rst_storage_error: check if reset signal asserted out of the |
| // permitted window |
| output logic rst_storage_error_o, |
| |
| input prim_mubi_pkg::mubi4_t 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) |
| // `phase_sel` needs to be asserted until the Chi stage is consumed, |
| mubi4_t phase_sel; |
| |
| // Cycle index used for controlling input/output muxes and write enables inside |
| // keccak_2share. |
| logic [1:0] cycle; |
| |
| // 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 |
| logic keccak_rand_consumed; |
| logic [Width/2-1:0] keccak_rand_data; |
| logic keccak_rand_aux; |
| |
| ////////////////////// |
| // Keccak Round FSM // |
| ////////////////////// |
| |
| // state inputs |
| assign rnd_eq_end = (int'(round) == MaxRound - 1); |
| |
| // Encoding generated with: |
| // $ ./util/design/sparse-fsm-encode.py -d 3 -m 8 -n 6 \ |
| // -s 1363425333 --language=sv |
| // |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: |||||||||||||||||||| (57.14%) |
| // 4: ||||||||||||||| (42.86%) |
| // 5: -- |
| // 6: -- |
| // |
| // Minimum Hamming distance: 3 |
| // Maximum Hamming distance: 4 |
| // Minimum Hamming weight: 1 |
| // Maximum Hamming weight: 5 |
| // |
| localparam int StateWidth = 6; |
| typedef enum logic [StateWidth-1:0] { |
| StIdle = 6'b011111, |
| |
| // Active state is used in Unmasked version only. |
| // It handles keccak round in a cycle |
| StActive = 6'b000100, |
| |
| // Phase1 --> Phase2Cycle1 --> Phase2Cycle2 --> Phase2Cycle3 |
| // Activated only in Masked version. |
| // Phase1 processes Theta, Rho, Pi steps in a cycle and stores the states |
| // into storage. It only moves to Phase2 once the randomness required for |
| // Phase2 is available. |
| StPhase1 = 6'b101101, |
| |
| // Chi Stage 1 for first lane halves. Unconditionally move to Phase2Cycle2. |
| StPhase2Cycle1 = 6'b000011, |
| |
| // Chi Stage 2 and Iota for first lane halves. Chi Stage 1 for second |
| // lane halves. We only move forward if the fresh randomness required for |
| // remasking is available. Otherwise, keep computing Phase2Cycle1. |
| StPhase2Cycle2 = 6'b011000, |
| |
| // Chi Stage 2 and Iota for second lane halves. |
| // This state doesn't require random value as it is XORed into the states |
| // in Phase1 and Phase2Cycle2. When doing the last round (MaxRound -1) |
| // it completes the process and goes back to Idle. If not, it repeats |
| // the phases again. |
| StPhase2Cycle3 = 6'b101010, |
| |
| // 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 = 6'b110001, |
| |
| StTerminalError = 6'b110110 |
| } keccak_st_e; |
| keccak_st_e keccak_st, keccak_st_d; |
| `PRIM_FLOP_SPARSE_FSM(u_state_regs, keccak_st_d, keccak_st, keccak_st_e, StIdle) |
| |
| // Next state logic and output logic |
| // SEC_CM: FSM.SPARSE |
| always_comb begin |
| // Default values |
| keccak_st_d = keccak_st; |
| |
| 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; |
| |
| phase_sel = MuBi4False; |
| cycle = 2'h 0; |
| |
| complete_d = 1'b 0; |
| |
| sparse_fsm_error_o = 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 (prim_mubi_pkg::mubi4_test_true_strict(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 |
| // Theta, Rho and Pi |
| phase_sel = MuBi4False; |
| cycle = 2'h 0; |
| |
| // Only update state and move on once we know the randomness required |
| // for Phase2 will be available in the next clock cycle. This way the |
| // DOM multipliers inside keccak_2share will be presented the new |
| // state (updated with update_storage) at the same time as the new |
| // randomness (updated with rand_early_i). Otherwise, stale entropy is |
| // paired with fresh data or vice versa. This could lead to undesired |
| // SCA leakage. |
| if (rand_early_i || rand_valid_i) begin |
| keccak_st_d = StPhase2Cycle1; |
| update_storage = 1'b 1; |
| end else begin |
| keccak_st_d = StPhase1; |
| end |
| end |
| |
| StPhase2Cycle1: begin |
| // Chi Stage 1 for first lane halves. |
| phase_sel = MuBi4True; |
| cycle = 2'h 1; |
| |
| // Trigger randomness update for next cycle. |
| keccak_rand_consumed = 1'b 1; |
| |
| // Unconditionally move to next phase/cycle. |
| keccak_st_d = StPhase2Cycle2; |
| end |
| |
| StPhase2Cycle2: begin |
| // Chi Stage 1 for second lane halves. |
| // Chi Stage 2 and Iota for first lane halves. |
| phase_sel = MuBi4True; |
| |
| // Only update state and move on if the required randomness is |
| // available. This way the DOM multipliers inside keccak_2share will be |
| // presented the second lane halves at the same time as the new |
| // randomness. Otherwise, stale entropy is paired with fresh data or |
| // vice versa. This could lead to undesired SCA leakage. |
| if (rand_valid_i) begin |
| cycle = 2'h 2; |
| |
| // Trigger randomness update for next round. |
| keccak_rand_consumed = 1'b 1; |
| |
| // Update first lane halves. |
| update_storage = 1'b 1; |
| |
| keccak_st_d = StPhase2Cycle3; |
| end else begin |
| cycle = 2'h 1; |
| keccak_st_d = StPhase2Cycle2; |
| end |
| end |
| |
| StPhase2Cycle3: begin |
| // Chi Stage 2 and Iota for second lane halves. |
| phase_sel = MuBi4True; |
| cycle = 2'h 3; |
| |
| // Update second lane halves. |
| 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 = StError; |
| end |
| |
| StTerminalError: begin |
| //this state is terminal |
| keccak_st_d = keccak_st; |
| sparse_fsm_error_o = 1'b 1; |
| end |
| |
| default: begin |
| keccak_st_d = StTerminalError; |
| sparse_fsm_error_o = 1'b 1; |
| end |
| endcase |
| |
| // SEC_CM: FSM.GLOBAL_ESC, FSM.LOCAL_ESC |
| // Unconditionally jump into the terminal error state |
| // if the life cycle controller triggers an escalation. |
| if (lc_escalate_en_i != lc_ctrl_pkg::Off) begin |
| keccak_st_d = StTerminalError; |
| end |
| 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 // |
| //////////////////////////// |
| |
| // SEC_CM: LOGIC.INTEGRITY |
| logic rst_n; |
| prim_sec_anchor_buf #( |
| .Width(1) |
| ) u_prim_sec_anchor_buf ( |
| .in_i(rst_ni), |
| .out_o(rst_n) |
| ); |
| |
| logic [Width-1:0] storage [Share]; |
| logic [Width-1:0] storage_d [Share]; |
| always_ff @(posedge clk_i or negedge rst_n) begin |
| if (!rst_n) 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 |
| |
| // Check the rst_storage integrity |
| logic rst_storage_error; |
| |
| always_comb begin : chk_rst_storage |
| rst_storage_error = 1'b 0; |
| |
| if (rst_storage) begin |
| // FSM should be in StIdle and clear_i should be high |
| if ((keccak_st != StIdle) || |
| prim_mubi_pkg::mubi4_test_false_loose(clear_i)) begin |
| rst_storage_error = 1'b 1; |
| end |
| end |
| end : chk_rst_storage |
| |
| assign rst_storage_error_o = rst_storage_error ; |
| |
| ////////////// |
| // Datapath // |
| ////////////// |
| keccak_2share #( |
| .Width (Width), |
| .EnMasking (EnMasking) |
| ) u_keccak_p ( |
| .clk_i, |
| .rst_ni, |
| |
| .lc_escalate_en_i, |
| |
| .rnd_i (round), |
| .phase_sel_i (phase_sel), |
| .cycle_i (cycle), |
| .rand_aux_i (keccak_rand_aux), |
| .rand_i (keccak_rand_data), |
| .s_i (storage), |
| .s_o (keccak_out) |
| ); |
| |
| // keccak entropy handling |
| assign rand_consumed_o = keccak_rand_consumed; |
| |
| assign keccak_rand_data = rand_data_i; |
| assign keccak_rand_aux = rand_aux_i; |
| |
| // Round number |
| // This primitive is used to place a hardened counter |
| // SEC_CM: CTR.REDUN |
| prim_count #( |
| .Width(RndW) |
| ) u_round_count ( |
| .clk_i, |
| .rst_ni, |
| .clr_i(rst_rnd_num), |
| .set_i(1'b0), |
| .set_cnt_i('0), |
| .incr_en_i(inc_rnd_num), |
| .decr_en_i(1'b0), |
| .step_i(RndW'(1)), |
| .cnt_o(round), |
| .cnt_next_o(), |
| .err_o(round_count_error_o) |
| ); |
| |
| // 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, |
| prim_mubi_pkg::mubi4_test_true_strict(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, StPhase2Cycle1, StPhase2Cycle2, StPhase2Cycle3}), |
| 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 |