blob: 9059617c49f8937422d8e7e9cedca8753261e61d [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 Application interface
`include "prim_assert.sv"
module kmac_app
import kmac_pkg::*;
#(
// App specific configs are defined in kmac_pkg
parameter bit EnMasking = 1'b0,
localparam int Share = (EnMasking) ? 2 : 1, // derived parameter
parameter bit SecIdleAcceptSwMsg = 1'b0
) (
input clk_i,
input rst_ni,
// Secret Key from register
input [MaxKeyLen-1:0] reg_key_data_i [Share],
input key_len_e reg_key_len_i,
// Prefix from register
input [sha3_pkg::NSRegisterSize*8-1:0] reg_prefix_i,
// mode, strength, kmac_en from register
input reg_kmac_en_i,
input sha3_pkg::sha3_mode_e reg_sha3_mode_i,
input sha3_pkg::keccak_strength_e reg_keccak_strength_i,
// Data from Software
input sw_valid_i,
input [MsgWidth-1:0] sw_data_i,
input [MsgWidth-1:0] sw_mask_i,
output logic sw_ready_o,
// KeyMgr Sideload Key interface
input keymgr_pkg::hw_key_req_t keymgr_key_i,
// Application Message in/ Digest out interface + control signals
input app_req_t [NumAppIntf-1:0] app_i,
output app_rsp_t [NumAppIntf-1:0] app_o,
// to KMAC Core: Secret key
output [MaxKeyLen-1:0] key_data_o [Share],
output key_len_e key_len_o,
// to MSG_FIFO
output logic kmac_valid_o,
output logic [MsgWidth-1:0] kmac_data_o,
output logic [MsgWidth-1:0] kmac_mask_o,
input kmac_ready_i,
// KMAC Core
output logic kmac_en_o,
// To Sha3 Core
output logic [sha3_pkg::NSRegisterSize*8-1:0] sha3_prefix_o,
output sha3_pkg::sha3_mode_e sha3_mode_o,
output sha3_pkg::keccak_strength_e keccak_strength_o,
// STATE from SHA3 Core
input keccak_state_valid_i,
input [sha3_pkg::StateW-1:0] keccak_state_i [Share],
// to STATE TL-window if Application is not active, the incoming state goes to
// register if kdf_en is set, the state value goes to application and the
// output to the register is all zero.
output logic reg_state_valid_o,
output logic [sha3_pkg::StateW-1:0] reg_state_o [Share],
// Configurations If key_en is set, the logic uses KeyMgr's sideloaded key as
// a secret key rather than register values. This only affects when software
// initiates. If App initiates the hash operation and uses KMAC algorithm, it
// always uses sideloaded key.
input keymgr_key_en_i,
// Commands
// Command from software
input kmac_cmd_e sw_cmd_i,
// from SHA3
input absorbed_i,
// to KMAC
output kmac_cmd_e cmd_o,
// to SW
output logic absorbed_o,
// To status
output logic app_active_o,
// Error input
// This error comes from KMAC/SHA3 engine.
// KeyMgr interface delivers the error signal to KeyMgr to drop the current op
// and re-initiate.
// If error happens, regardless of SW-initiated or KeyMgr-initiated, the error
// is reported to the ERR_CODE so that SW can look into.
input error_i,
// SW sets err_processed bit in CTRL then the logic goes to Idle
input err_processed_i,
// error_o value is pushed to Error FIFO at KMAC/SHA3 top and reported to SW
output kmac_pkg::err_t error_o
);
/////////////////
// Definitions //
/////////////////
// Digest width is same to the key width `keymgr_pkg::KeyWidth`.
localparam int KeyMgrKeyW = $bits(keymgr_key_i.key[0]);
localparam key_len_e KeyLen [5] = '{Key128, Key192, Key256, Key384, Key512};
localparam int SelKeySize = (AppKeyW == 128) ? 0 :
(AppKeyW == 192) ? 1 :
(AppKeyW == 256) ? 2 :
(AppKeyW == 384) ? 3 :
(AppKeyW == 512) ? 4 : 0 ;
localparam int SelDigSize = (AppDigestW == 128) ? 0 :
(AppDigestW == 192) ? 1 :
(AppDigestW == 256) ? 2 :
(AppDigestW == 384) ? 3 :
(AppDigestW == 512) ? 4 : 0 ;
localparam key_len_e SideloadedKey = KeyLen[SelKeySize];
// Define right_encode(outlen) value here
// Look at kmac_pkg::key_len_e for the kinds of key size
//
// These values should be exactly the same as the key length encodings
// in kmac_core.sv, with the only difference being that the byte representing
// the byte-length of the encoded value is in the MSB position due to right encoding
// instead of in the LSB position (left encoding).
localparam int OutLenW = 24;
localparam logic [OutLenW-1:0] EncodedOutLen [5]= '{
24'h 0001_80, // Key128
24'h 0001_C0, // Key192
24'h 02_0001, // Key256
24'h 02_8001, // Key384
24'h 02_0002 // Key512
};
localparam logic [OutLenW-1:0] EncodedOutLenMask [5] = '{
24'h 00FFFF, // Key128,
24'h 00FFFF, // Key192
24'h FFFFFF, // Key256
24'h FFFFFF, // Key384
24'h FFFFFF // Key512
};
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 3 -m 9 -n 10 \
// -s 155490773 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: |||||||||| (16.67%)
// 4: |||||||||||||||||||| (30.56%)
// 5: |||||||||||||||| (25.00%)
// 6: ||||||||| (13.89%)
// 7: ||||||||| (13.89%)
// 8: --
// 9: --
// 10: --
//
// Minimum Hamming distance: 3
// Maximum Hamming distance: 7
// Minimum Hamming weight: 2
// Maximum Hamming weight: 9
//
localparam int StateWidth = 10;
// States
typedef enum logic [StateWidth-1:0] {
StIdle = 10'b1011011010,
// Application operation.
//
// if start request comes from an App first, until the operation ends by the
// requested App, all operations are granted to the specific App. SW
// requests and other Apps requests will be ignored.
//
// App interface does not have control signals. When first data valid occurs
// from an App, this logic asserts the start command to the downstream. When
// last beat pulse comes, this logic asserts the process to downstream
// (after the transaction is accepted regardless of partial writes or not)
// When absorbed by SHA3 core, the logic sends digest to the requested App
// and right next cycle, it triggers done command to downstream.
// In StAppCfg state, it latches the cfg from AppCfg parameter to determine
// the kmac_mode, sha3_mode, keccak strength.
StAppCfg = 10'b0001010000,
StAppMsg = 10'b0001011111,
// In StKeyOutLen, this module pushes encoded outlen to the MSG_FIFO.
// Assume the length is 256 bit, the data will be 48'h 02_0100
StAppOutLen = 10'b1011001111,
StAppProcess = 10'b1000100110,
StAppWait = 10'b0010010110,
// SW Controlled
// If start request comes from SW first, until the operation ends, all
// requests from KeyMgr will be discarded.
StSw = 10'b0111111111,
// Error KeyNotValid
// When KeyMgr operates, the secret key is not ready yet.
StKeyMgrErrKeyNotValid = 10'b1001110100,
StError = 10'b1101011101
} st_e;
/////////////
// Signals //
/////////////
st_e st, st_d;
// app_rsp_t signals
// The state machine controls mux selection, which controls the ready signal
// the other responses are controled in separate logic. So define the signals
// here and merge them to the response.
logic app_data_ready, fsm_data_ready;
logic app_digest_done, fsm_digest_done_q, fsm_digest_done_d;
logic [AppDigestW-1:0] app_digest [2];
// One more slot for value NumAppIntf. It is the value when no app intf is
// chosen.
localparam int unsigned AppIdxW = $clog2(NumAppIntf);
// app_id indicates, which app interface was chosen. various logic use this
// value to get the config or return the data.
logic [AppIdxW-1:0] app_id, app_id_d;
logic clr_appid, set_appid;
// Output length
logic [OutLenW-1:0] encoded_outlen, encoded_outlen_mask;
// state output
// Mux selection signal
app_mux_sel_e mux_sel;
// Error checking logic
kmac_pkg::err_t fsm_err, mux_err;
////////////////////////////
// Application Mux/ Demux //
////////////////////////////
// Processing return data.
// sends to only selected app intf.
// clear digest right after done to not leak info to other interface
always_comb begin
for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin
if (i == app_id) begin
app_o[i] = '{
ready: app_data_ready | fsm_data_ready,
done: app_digest_done | fsm_digest_done_q,
digest_share0: app_digest[0],
digest_share1: app_digest[1],
// if fsm asserts done, should be an error case.
error: error_i | fsm_digest_done_q
};
end else begin
app_o[i] = '{
ready: 1'b 0,
done: 1'b 0,
digest_share0: '0,
digest_share1: '0,
error: 1'b 0
};
end
end // for {i, NumAppIntf, i++}
end // aiways_comb
// app_id latch
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) app_id <= AppIdxW'(0) ; // Do not select any
else if (clr_appid) app_id <= AppIdxW'(0);
else if (set_appid) app_id <= app_id_d;
end
// app_id selection as of now, app_id uses Priority. The assumption is that
// the request normally does not collide. (ROM_CTRL activates very early
// stage at the boot sequence)
//
// If this assumption is not true, consider RR arbiter.
// Prep for arbiter
logic [NumAppIntf-1:0] app_reqs;
logic [NumAppIntf-1:0] unused_app_gnts;
logic [$clog2(NumAppIntf)-1:0] arb_idx;
logic arb_valid;
logic arb_ready;
always_comb begin
app_reqs = '0;
for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin
app_reqs[i] = app_i[i].valid;
end
end
prim_arbiter_fixed #(
.N (NumAppIntf),
.DW(1),
.EnDataPort(1'b 0)
) u_appid_arb (
.clk_i,
.rst_ni,
.req_i (app_reqs),
.data_i ('{default:'0}),
.gnt_o (unused_app_gnts),
.idx_o (arb_idx),
.valid_o (arb_valid),
.data_o (), // not used
.ready_i (arb_ready)
);
assign app_id_d = AppIdxW'(arb_idx);
assign arb_ready = set_appid;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) fsm_digest_done_q <= 1'b 0;
else fsm_digest_done_q <= fsm_digest_done_d;
end
/////////
// FSM //
/////////
// State register
logic [StateWidth-1:0] st_raw;
assign st = st_e'(st_raw);
prim_flop #(
.Width (StateWidth),
.ResetValue (StateWidth'(StIdle))
) u_state_regs (
.clk_i,
.rst_ni,
.d_i ( st_d ),
.q_o ( st_raw )
);
// Next State & output logic
always_comb begin
st_d = StIdle;
mux_sel = SecIdleAcceptSwMsg ? SelSw : SelNone;
// app_id control
set_appid = 1'b 0;
clr_appid = 1'b 0;
// Commands
cmd_o = CmdNone;
// Software output
absorbed_o = 1'b 0;
// Error
fsm_err = '{valid: 1'b 0, code: ErrNone, info: '0};
// If error happens, FSM asserts data ready but discard incoming msg
fsm_data_ready = 1'b 0;
fsm_digest_done_d = 1'b 0;
unique case (st)
StIdle: begin
if (arb_valid) begin
st_d = StAppCfg;
// choose app_id
set_appid = 1'b 1;
end else if (sw_cmd_i == CmdStart) begin
st_d = StSw;
// Software initiates the sequence
cmd_o = CmdStart;
end else begin
st_d = StIdle;
end
end
StAppCfg: begin
if ((AppCfg[app_id].Mode == AppKMAC) && !keymgr_key_i.valid) begin
st_d = StKeyMgrErrKeyNotValid;
// As mux_sel is not set to SelApp, app_data_ready is still 0.
// This logic won't accept the requests from the selected App.
end else begin
// As Cfg is stable now, it sends cmd
st_d = StAppMsg;
// App initiates the data
cmd_o = CmdStart;
end
end
StAppMsg: begin
mux_sel = SelApp;
// Wait until the completion (done) from KeyMgr?
// Or absorb completion?
if (app_i[app_id].valid && app_o[app_id].ready && app_i[app_id].last) begin
if (AppCfg[app_id].Mode == AppKMAC) begin
st_d = StAppOutLen;
end else begin
st_d = StAppProcess;
end
end else begin
st_d = StAppMsg;
end
end
StAppOutLen: begin
mux_sel = SelOutLen;
if (kmac_valid_o && kmac_ready_i) begin
st_d = StAppProcess;
end else begin
st_d = StAppOutLen;
end
end
StAppProcess: begin
cmd_o = CmdProcess;
st_d = StAppWait;
end
StAppWait: begin
if (absorbed_i) begin
// Send digest to KeyMgr and complete the op
st_d = StIdle;
cmd_o = CmdDone;
clr_appid = 1'b 1;
end else begin
st_d = StAppWait;
end
end
StSw: begin
mux_sel = SelSw;
cmd_o = sw_cmd_i;
absorbed_o = absorbed_i;
if (sw_cmd_i == CmdDone) begin
st_d = StIdle;
end else begin
st_d = StSw;
end
end
StKeyMgrErrKeyNotValid: begin
st_d = StError;
// As mux_sel is not set to SelApp, app_data_ready is still 0.
// This logic won't accept the requests from the selected App.
fsm_err.valid = 1'b 1;
fsm_err.code = ErrKeyNotValid;
fsm_err.info = 24'(app_id);
end
StError: begin
// In this state, the state machine flush out the request
st_d = StError;
fsm_data_ready = 1'b 1;
if (err_processed_i) begin
st_d = StIdle;
// clear internal variables
clr_appid = 1'b 1;
end
if (app_i[app_id].valid && app_i[app_id].last) begin
// Send garbage digest to the app interface to complete transaction
fsm_digest_done_d = 1'b 1;
end
end
default: begin
st_d = StIdle;
end
endcase
end
if (SecIdleAcceptSwMsg != 1'b0) begin : gen_lint_err
// Create a lint error to reduce the risk of accidentally enabling this feature.
logic sec_idle_accept_sw_msg_dummy;
assign sec_idle_accept_sw_msg_dummy = (st == StIdle);
end
//////////////
// Datapath //
//////////////
// Encoded output length
assign encoded_outlen = EncodedOutLen[SelDigSize];
assign encoded_outlen_mask = EncodedOutLenMask[SelKeySize];
// Data mux
// This is the main part of the KeyMgr interface logic.
// The FSM selects KeyMgr interface in a cycle after it receives the first
// valid data from KeyMgr. The ready signal to the KeyMgr data interface
// represents the MSG_FIFO ready, only when it is in StKeyMgrMsg state.
// After KeyMgr sends last beat, the kmac interface (to MSG_FIFO) is switched
// to OutLen. OutLen is pre-defined values. See `EncodeOutLen` parameter above.
always_comb begin
app_data_ready = 1'b 0;
sw_ready_o = 1'b 1;
kmac_valid_o = 1'b 0;
kmac_data_o = '0;
kmac_mask_o = '0;
unique case (mux_sel)
SelApp: begin
// app_id is valid at this time
kmac_valid_o = app_i[app_id].valid;
kmac_data_o = app_i[app_id].data;
// Expand strb to bits. prim_packer inside MSG_FIFO accepts the bit masks
for (int i = 0 ; i < $bits(app_i[app_id].strb) ; i++) begin
kmac_mask_o[8*i+:8] = {8{app_i[app_id].strb[i]}};
end
app_data_ready = kmac_ready_i;
end
SelOutLen: begin
// Write encoded output length value
kmac_valid_o = 1'b 1; // always write
kmac_data_o = MsgWidth'(encoded_outlen);
kmac_mask_o = MsgWidth'(encoded_outlen_mask);
end
SelSw: begin
kmac_valid_o = sw_valid_i;
kmac_data_o = sw_data_i ;
kmac_mask_o = sw_mask_i ;
sw_ready_o = kmac_ready_i ;
end
default: begin // Incl. SelNone
kmac_valid_o = 1'b 0;
kmac_data_o = '0;
kmac_mask_o = '0;
end
endcase
end
// Error checking for Mux
always_comb begin
mux_err = '{valid: 1'b 0, code: ErrNone, info: '0};
if (mux_sel != SelSw && sw_valid_i) begin
// If SW writes message into FIFO
mux_err = '{
valid: 1'b 1,
code: ErrSwPushedMsgFifo,
info: 24'({8'h 00, 8'(st), 8'(mux_sel)})
};
end else if (app_active_o && sw_cmd_i != CmdNone) begin
// If SW issues command except start
mux_err = '{
valid: 1'b 1,
code: ErrSwIssuedCmdInAppActive,
info: 24'(sw_cmd_i)
};
end
end
// Keccak state Demux
// Keccak state --> Register output is enabled when state is in StSw
always_comb begin
if (mux_sel == SelSw) begin
reg_state_valid_o = keccak_state_valid_i;
reg_state_o = keccak_state_i;
end else begin
reg_state_valid_o = 1'b 0;
reg_state_o = '{default:'0};
end
end
// Keccak state --> KeyMgr
always_comb begin
app_digest_done = 1'b 0;
app_digest = '{default:'0};
if (st == StAppWait && absorbed_i) begin
// SHA3 engine has calculated the hash. Return the data to KeyMgr
app_digest_done = 1'b 1;
// digest has always 2 entries. If !EnMasking, second is tied to 0.
for (int i = 0 ; i < Share ; i++) begin
// Return the portion of state.
app_digest[i] = keccak_state_i[i][AppDigestW-1:0];
end
end
end
// Secret Key Mux
// Prepare merged key if EnMasking is not set.
// Combine share keys into unpacked array for logic below to assign easily.
logic [MaxKeyLen-1:0] keymgr_key [Share];
if (EnMasking == 1) begin : g_masked_key
for (genvar i = 0; i < Share; i++) begin : gen_key_pad
assign keymgr_key[i] = {(MaxKeyLen-KeyMgrKeyW)'(0), keymgr_key_i.key[i]};
end
end else begin : g_unmasked_key
always_comb begin
keymgr_key[0] = '0;
for (int i = 0; i < Share; i++) begin
keymgr_key[0][KeyMgrKeyW-1:0] ^= keymgr_key_i.key[i];
end
end
end
// Sideloaded key is used when KeyMgr KDF is active or !!CFG.sideload is set
always_comb begin
if (keymgr_key_en_i || (mux_sel == SelApp)) begin
// KeyLen is fixed to the $bits(sideloaded_key)
key_len_o = SideloadedKey;
end else begin
key_len_o = reg_key_len_i;
end
end
for (genvar i = 0 ; i < Share ; i++) begin : g_key_assign
assign key_data_o[i] = (keymgr_key_en_i || (mux_sel == SelApp))
? keymgr_key[i]
: reg_key_data_i[i] ;
end
// Prefix Demux
// For SW, always prefix register.
// For App intf, check PrefixMode cfg and if 1, use Prefix cfg.
always_comb begin
sha3_prefix_o = '0;
unique case (st)
StAppCfg, StAppMsg, StAppOutLen, StAppProcess, StAppWait: begin
// Check app intf cfg
for (int unsigned i = 0 ; i < NumAppIntf ; i++) begin
if (app_id == i) begin
if (AppCfg[i].PrefixMode == 1'b 0) begin
sha3_prefix_o = reg_prefix_i;
end else begin
sha3_prefix_o = AppCfg[i].Prefix;
end
end
end
end
StSw: begin
sha3_prefix_o = reg_prefix_i;
end
default: begin
sha3_prefix_o = reg_prefix_i;
end
endcase
end
// KMAC en / SHA3 mode / Strength
// by default, it uses reg cfg. When app intf reqs come, it uses AppCfg.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
kmac_en_o <= 1'b 0;
sha3_mode_o <= sha3_pkg::Sha3;
keccak_strength_o <= sha3_pkg::L256;
end else if (clr_appid) begin
// As App completed, latch reg value
kmac_en_o <= reg_kmac_en_i;
sha3_mode_o <= reg_sha3_mode_i;
keccak_strength_o <= reg_keccak_strength_i;
end else if (set_appid) begin
kmac_en_o <= AppCfg[arb_idx].Mode == AppKMAC ? 1'b 1 : 1'b 0;
sha3_mode_o <= AppCfg[arb_idx].Mode == AppSHA3
? sha3_pkg::Sha3 : sha3_pkg::CShake;
keccak_strength_o <= AppCfg[arb_idx].Strength ;
end else if (st == StIdle) begin
kmac_en_o <= reg_kmac_en_i;
sha3_mode_o <= reg_sha3_mode_i;
keccak_strength_o <= reg_keccak_strength_i;
end
end
// Status
assign app_active_o = (st inside {StAppCfg, StAppMsg, StAppOutLen,
StAppProcess, StAppWait});
// Error Reporting ==========================================================
always_comb begin
priority casez ({fsm_err.valid, mux_err.valid})
2'b ?1: error_o = mux_err;
2'b 10: error_o = fsm_err;
default: error_o = '{valid: 1'b0, code: ErrNone, info: '0};
endcase
end
////////////////
// Assertions //
////////////////
// KeyMgr sideload key and the digest should be in the Key Length value
`ASSERT_INIT(SideloadKeySameToDigest_A, KeyMgrKeyW <= AppDigestW)
`ASSERT_INIT(AppIntfInRange_A, AppDigestW inside {128, 192, 256, 384, 512})
endmodule