blob: 18604a245a651ccbe39f5383415f91df74dcf36c [file] [log] [blame]
// 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