blob: 461b86d09f635534bc42fdbadd3dbfd897a1f080 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// KMAC Entropy Generation module
`include "prim_assert.sv"
module kmac_entropy
import kmac_pkg::*;
(
input clk_i,
input rst_ni,
// EDN interface
output logic entropy_req_o,
input entropy_ack_i,
input [MsgWidth-1:0] entropy_data_i,
// Entropy to internal
output logic rand_valid_o,
output [sha3_pkg::StateW-1:0] rand_data_o,
input rand_consumed_i,
// Status
input in_keyblock_i,
// Configurations
input entropy_mode_e mode_i,
//// SW sets ready bit when EDN is ready to accept requests through its app.
//// interface.
input entropy_ready_i,
//// Garbage random value when not processing Keyblock, if this config is
//// turned on, the logic sending garbage value and never de-assert
//// rand_valid_o unless it is not processing KeyBlock.
input fast_process_i,
//// SW update of seed
input seed_update_i,
input [63:0] seed_data_i,
//// SW may initiate manual EDN seed refresh
input entropy_refresh_req_i,
//// Timer limit value
//// If value is 0, timer is disabled
input [TimerPrescalerW-1:0] wait_timer_prescaler_i,
input [EdnWaitTimerW-1:0] wait_timer_limit_i,
// Status out
//// Hash Ops counter. Count how many hashing ops (KMAC) have run
//// after the clear request from SW
output logic [kmac_reg_pkg::HashCntW-1:0] hash_cnt_o,
input hash_cnt_clr_i,
input [kmac_reg_pkg::HashCntW-1:0] hash_threshold_i,
// Error output
output err_t err_o,
input err_processed_i
);
/////////////////
// Definitions //
/////////////////
// Timer Widths are defined in kmac_pkg
// storage width
localparam int unsigned EntropyLfsrW = 64;
localparam int unsigned EntropyStorageW = 320;
localparam int unsigned EntropyMultiply = sha3_pkg::StateW / EntropyStorageW;
`ASSERT_INIT(StorageNoRemainder_A, (sha3_pkg::StateW%EntropyStorageW) == 0)
`ASSERT_INIT(LfsrNoRemainder_A, (EntropyStorageW%EntropyLfsrW) == 0)
localparam int unsigned StorageEntries = EntropyStorageW / EntropyLfsrW ;
localparam int unsigned StorageIndexW = $clog2(StorageEntries);
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 3 -m 8 -n 10 \
// -s 507672272 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: |||||||||| (14.29%)
// 4: |||||||||| (14.29%)
// 5: |||||||||||||||||||| (28.57%)
// 6: |||||||||||| (17.86%)
// 7: ||||||| (10.71%)
// 8: ||||||| (10.71%)
// 9: || (3.57%)
// 10: --
//
// Minimum Hamming distance: 3
// Maximum Hamming distance: 9
// Minimum Hamming weight: 2
// Maximum Hamming weight: 7
//
localparam int StateWidth = 10;
// States
typedef enum logic [StateWidth-1:0] {
// Reset: Reset state. The entropy is not ready. The state machine should
// get new entropy from EDN or the seed should be feeded by the software.
StRandReset = 10'b 1101110011,
// The seed is fed into LFSR and the entropy is ready. It means the
// rand_valid is asserted with valid data. It takes a few steps to reach
// this state from StRandIdle.
StRandReady = 10'b 1001111000,
// EDN interface: Send request and receive
// RandEdnReq state can be transit from StRandReset or from StRandReady
//
// Reset --> EdnReq:
// If entropy source module is ready, the software sets a bit in CFG
// also sets the entropy mode to EdnMode. Then this FSM moves to EdnReq
// to initialize LFSR seed.
//
// Ready --> EdnReq:
// 1. If a mode is configured as to update entropy everytime it is
// consumed, then the FSM moves from Ready to EdnReq to refresh seed
// 2. If the software enabled EDN timer and the timer is expired and
// also the KMAC is processing the key block, the FSM moves to
// EdnReq to refresh seed
// 3. If a KMAC operation is completed, the FSM also refreshes the LFSR
// seed to prepare next KMAC op or wipe out operation.
StRandEdn = 10'b 0110000100,
// Sw Seed: If mode is set to manual mode, This entropy module needs initial
// seed from the software. It waits the seed update signal to expand initial
// entropy
StSwSeedWait = 10'b 1100100111,
// Expand: The SW or EDN provides 64-bit entropy (seed). In this state, this
// entropy generator expands the 64-bit entropy into 320-bit entropy using
// LFSR. Then it expands 320-bit pseudo random entropy into 1600-bit by
// replicating the PR entropy five times w/ compile-time shuffling scheme.
StRandExpand = 10'b 1011110110,
// ErrWaitExpired: If Edn timer expires, FSM moves to this state and wait
// the software response. Software should switch to manual mode then disable
// the timer (to 0) and update the seed via register interface.
StRandErrWaitExpired = 10'b 0000001100,
// ErrNoValidMode: If SW sets entropy ready but the mode is not either
// Manual Mode nor EdnMode, this logic reports to SW with
// NoValidEntropyMode.
StRandErrIncorrectMode = 10'b 0001100011,
// Err: After the error is reported, FSM sits in Err state ignoring all the
// requests. It does not generate new entropy and drops the entropy valid
// signal.
//
// SW sets err_processed signal to clear the error. The software should
// clear the entropy ready signal before clear the error interrupt so that
// the FSM sits in StRandReset state not moving forward with incorrect
// configurations.
StRandErr = 10'b 1110010000
} rand_st_e;
/////////////
// Signals //
/////////////
// Timers
// "Wait Timer": This timer is in active when FSM sends entropy request to EDN
// If EDN does not return the entropy data until the timer expired, FSM
// moves to error state and report the error to the system.
localparam int unsigned TimerW = EdnWaitTimerW;
logic timer_enable, timer_update, timer_expired, timer_pulse;
logic [TimerW-1:0] timer_limit;
logic [TimerW-1:0] timer_value;
localparam int unsigned PrescalerW = TimerPrescalerW;
logic [PrescalerW-1:0] prescaler_cnt;
// LFSR
//// SW configures to use EDN or SEED register as a LFSR seed
logic lfsr_seed_en;
logic [EntropyLfsrW-1:0] lfsr_seed;
logic lfsr_en;
logic [EntropyLfsrW-1:0] lfsr_data;
// storage
logic storage_update;
logic storage_idx_clear;
logic storage_filled;
// Entropy valid signal
// FSM set and clear the valid signal, rand_consume signal clear the valid
// signal. Split the set, clear to make entropy valid while FSM is processing
// other tasks.
logic rand_valid_set, rand_valid_clear;
//////////////
// Datapath //
//////////////
// Timers ===================================================================
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
timer_value <= '0;
end else if (timer_update) begin
timer_value <= timer_limit;
end else if (timer_expired) begin
timer_value <= '0; // keep the value
end else if (timer_enable && timer_pulse && |timer_value) begin // if non-zero timer v
timer_value <= timer_value - 1'b 1;
end
end
assign timer_limit = TimerW'(wait_timer_limit_i);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
timer_expired <= 1'b 0;
end else if (timer_update) begin
timer_expired <= 1'b 0;
end else if (timer_enable && (timer_value == '0)) begin
timer_expired <= 1'b 1;
end
end
// Prescaler
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
prescaler_cnt <= '0;
end else if (timer_update) begin
prescaler_cnt <= wait_timer_prescaler_i;
end else if (timer_enable && prescaler_cnt == '0) begin
prescaler_cnt <= wait_timer_prescaler_i;
end else if (timer_enable) begin
prescaler_cnt <= prescaler_cnt - 1'b 1;
end
end
assign timer_pulse = (timer_enable && prescaler_cnt == '0);
// Timers -------------------------------------------------------------------
// Hash Counter
logic threshold_hit;
logic threshold_hit_q, threshold_hit_clr; // latched hit
logic hash_progress_d, hash_progress_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) hash_progress_q <= 1'b 0;
else hash_progress_q <= hash_progress_d;
end
assign hash_progress_d = in_keyblock_i;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
hash_cnt_o <= '0;
end else if (hash_cnt_clr_i || threshold_hit || entropy_refresh_req_i) begin
hash_cnt_o <= '0;
end else if (hash_progress_q && !hash_progress_d) begin
hash_cnt_o <= hash_cnt_o + 1'b 1;
end
end
assign threshold_hit = |hash_threshold_i && (hash_threshold_i <= hash_cnt_o);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) threshold_hit_q <= 1'b 0;
else if (threshold_hit_clr) threshold_hit_q <= 1'b 0;
else if (threshold_hit) threshold_hit_q <= 1'b 1;
end
// LFSR =====================================================================
//// FSM controls the seed enable signal `lfsr_seed_en`.
//// Seed selection
always_comb begin
unique case (mode_i)
EntropyModeNone: lfsr_seed = '0;
// TODO: Check EDN Bus width
EntropyModeEdn: lfsr_seed = entropy_data_i;
EntropyModeSw: lfsr_seed = seed_data_i;
default: lfsr_seed = '0;
endcase
end
`ASSERT_KNOWN(ModeKnown_A, mode_i)
prim_lfsr #(
.LfsrDw(EntropyLfsrW),
.EntropyDw(EntropyLfsrW),
.StateOutDw(EntropyLfsrW)
) u_lfsr (
.clk_i,
.rst_ni,
.seed_en_i(lfsr_seed_en),
.seed_i (lfsr_seed),
.lfsr_en_i(lfsr_en),
.entropy_i('0), // Does not use additional entropy while operating
.state_o (lfsr_data) // (partial) LFSR state output StateOutDw
);
// LFSR ---------------------------------------------------------------------
// 320-bit storage ==========================================================
logic [EntropyStorageW-1:0] entropy_storage;
logic [StorageIndexW-1:0] storage_idx;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
entropy_storage <= '0;
end else if (storage_update) begin
for (int unsigned i = 0 ; i < StorageEntries ; i++) begin
if (StorageIndexW'(i) == storage_idx) begin
entropy_storage[i*EntropyLfsrW+:EntropyLfsrW] <= lfsr_data;
end
end
end
// TODO: Should the consumed entropy be discarded?
end
//// Index
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
storage_idx <= '0;
end else if (storage_idx_clear) begin
storage_idx <= '0;
end else if (storage_filled) begin
storage_idx <= storage_idx;
end else if (storage_update) begin
storage_idx <= storage_idx + 1'b 1;
end
end
assign storage_filled = (storage_idx == StorageIndexW'(StorageEntries));
// 320-bit storage ----------------------------------------------------------
// Storage expands to StateW ================================================
// May adopt fancy shuffling scheme to obsfucate
// Or, convert the 320bit to sheet then multiply then unroll into 1600bit
assign rand_data_o = {EntropyMultiply{entropy_storage}};
// entropy valid
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rand_valid_o <= 1'b 0;
end else if (rand_valid_set) begin
rand_valid_o <= 1'b 1;
end else if (rand_valid_clear) begin
rand_valid_o <= 1'b 0;
end
end
`ASSUME(ConsumeNotAseertWhenNotReady_M, rand_consumed_i |-> rand_valid_o)
// Storage expands to StateW ------------------------------------------------
///////////////////
// State Machine //
///////////////////
rand_st_e st, st_d;
logic [StateWidth-1:0] st_raw_q;
assign st = rand_st_e'(st_raw_q);
// State FF
prim_flop #(
.Width(StateWidth),
.ResetValue(StateWidth'(StRandReset))
) u_state_regs (
.clk_i,
.rst_ni,
.d_i ( st_d ),
.q_o ( st_raw_q )
);
// State: Next State and Output Logic
always_comb begin
st_d = StRandReset;
// Default Timer values
timer_enable = 1'b 0;
timer_update = 1'b 0;
threshold_hit_clr = 1'b 0;
// EDN request
entropy_req_o = 1'b 0;
// rand is valid when this logic expands the entropy.
// FSM sets the valid signal, the signal is cleared by `consume` signal
// or FSM clear signal.
// Why split the signal to set and clear?
// FSM only set the signal to make entropy valid while processing other
// tasks such as EDN request.
rand_valid_set = 1'b 0;
rand_valid_clear = 1'b 0;
// lfsr_en: Let LFSR run
// To save power, this logic enables LFSR when it needs entropy expansion.
// TODO: Check if random LFSR run while staying in ready state to obsfucate
// LFSR value?
lfsr_en = 1'b 0;
// lfsr_seed_en: Signal to update LFSR seed
// LFSR seed can be updated by EDN or SW.
lfsr_seed_en = 1'b 0;
// Entropy Storage control signals
storage_idx_clear = 1'b 0;
storage_update = 1'b 0;
// Error
err_o = '{valid: 1'b 0, code: ErrNone, info: '0};
unique case (st)
StRandReset: begin
if (entropy_ready_i) begin
// As SW ready, discard current dummy entropy and refresh.
rand_valid_clear = 1'b 1;
// SW has configured KMAC
unique case (mode_i)
EntropyModeSw: begin
st_d = StSwSeedWait;
end
EntropyModeEdn: begin
st_d = StRandEdn;
// Timer reset
timer_update = 1'b 1;
end
default: begin
// EntropyModeNone or other values
// Error. No valid mode given, report to SW
st_d = StRandErrIncorrectMode;
end
endcase
end else begin
st_d = StRandReset;
// Setting the dummy rand gate until SW prepares.
// This lets the Application Interface move forward out of reset
// without SW intervention.
rand_valid_set = 1'b 1;
end
end
StRandReady: begin
timer_enable = 1'b 1; // If limit is zero, timer won't work
if ( (fast_process_i && in_keyblock_i && rand_consumed_i)
|| (!fast_process_i && rand_consumed_i)) begin
// If fast_process is set, don't clear the rand valid, even
// consumed. So, the logic does not expand the entropy again.
// If fast_process is not set, then every rand_consume signal
// triggers rand expansion.
st_d = StRandExpand;
lfsr_en = 1'b 1;
storage_idx_clear = 1'b 1;
rand_valid_clear = 1'b 1;
end else if (entropy_refresh_req_i || threshold_hit_q) begin
st_d = StRandEdn;
// Timer reset
timer_update = 1'b 1;
// Clear the threshold as it refreshes the hash
threshold_hit_clr = 1'b 1;
end else begin
st_d = StRandReady;
end
end
StRandEdn: begin
// Send request
entropy_req_o = 1'b 1;
// Wait timer
timer_enable = 1'b 1;
if (timer_expired && |wait_timer_limit_i) begin
// If timer count is non-zero and expired;
st_d = StRandErrWaitExpired;
end else if (entropy_ack_i) begin
st_d = StRandExpand;
lfsr_en = 1'b 1;
lfsr_seed_en = 1'b 1;
rand_valid_clear = 1'b 1;
storage_idx_clear = 1'b 1;
// TODO: check fips?
end else if (rand_consumed_i) begin
// Somehow, while waiting the EDN entropy, the KMAC or SHA3 logic
// consumed the remained entropy. This can happen when the previous
// SHA3/ KMAC op completed and this Entropy FSM has moved to this
// state to refresh the entropy and the SW initiates another hash
// operation while waiting for the EDN response.
st_d = StRandEdn;
rand_valid_clear = 1'b 1;
end else begin
st_d = StRandEdn;
end
end
StSwSeedWait: begin
if (seed_update_i) begin
st_d = StRandExpand;
lfsr_en = 1'b 1;
lfsr_seed_en = 1'b 1;
rand_valid_clear = 1'b 1;
storage_idx_clear = 1'b 1;
end else begin
st_d = StSwSeedWait;
end
end
StRandExpand: begin
lfsr_en = 1'b 1;
if (storage_filled) begin
st_d = StRandReady;
storage_update = 1'b 0;
rand_valid_set = 1'b 1;
end else begin
st_d = StRandExpand;
storage_update = 1'b 1;
end
end
StRandErrWaitExpired: begin
st_d = StRandErr;
err_o = '{ valid: 1'b 1,
code: ErrWaitTimerExpired,
info: 24'(timer_value)
};
end
StRandErrIncorrectMode: begin
st_d = StRandErr;
err_o = '{ valid: 1'b 1,
code: ErrIncorrectEntropyMode,
info: 24'(mode_i)
};
end
StRandErr: begin
// Keep entropy signal valid to complete current hashing even with error
rand_valid_set = 1'b 1;
if (err_processed_i) begin
st_d = StRandReset;
// TODO: Reset as much as
end else begin
st_d = StRandErr;
end
end
default: begin
st_d = StRandReset;
end
endcase
end
`ASSERT_KNOWN(RandStKnown_A, st)
////////////////
// Assertions //
////////////////
// entropy storage cannot be exceed the Entry number.
// filled is asserted when the storage index meets the Entry number.
// So, if filled, no update signal shall be asserted
`ASSERT(StorageIdxInBound_A, storage_filled |-> !storage_update)
endmodule : kmac_entropy