blob: 14e4aaa422017b116efee76d4d085f90449bc8d0 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Scrambling key derivation module for OTP.
//
`include "prim_flop_macros.sv"
module otp_ctrl_kdi
import otp_ctrl_pkg::*;
import otp_ctrl_reg_pkg::*;
import otp_ctrl_part_pkg::*;
#(
parameter scrmbl_key_init_t RndCnstScrmblKeyInit = RndCnstScrmblKeyInitDefault
) (
input clk_i,
input rst_ni,
// Pulse to enable this module after OTP partitions have
// been initialized.
input kdi_en_i,
// Escalation input. This moves the FSM into a terminal state.
input lc_ctrl_pkg::lc_tx_t escalate_en_i,
// FSM is in error state
output logic fsm_err_o,
// Key seed inputs from OTP
input logic scrmbl_key_seed_valid_i,
input logic [FlashKeySeedWidth-1:0] flash_data_key_seed_i,
input logic [FlashKeySeedWidth-1:0] flash_addr_key_seed_i,
input logic [SramKeySeedWidth-1:0] sram_data_key_seed_i,
// EDN interface for requesting entropy
output logic edn_req_o,
input edn_ack_i,
input [EdnDataWidth-1:0] edn_data_i,
// Scrambling key requests
input flash_otp_key_req_t flash_otp_key_i,
output flash_otp_key_rsp_t flash_otp_key_o,
input sram_otp_key_req_t [NumSramKeyReqSlots-1:0] sram_otp_key_i,
output sram_otp_key_rsp_t [NumSramKeyReqSlots-1:0] sram_otp_key_o,
input otbn_otp_key_req_t otbn_otp_key_i,
output otbn_otp_key_rsp_t otbn_otp_key_o,
// Scrambling mutex request
output logic scrmbl_mtx_req_o,
input scrmbl_mtx_gnt_i,
// Scrambling datapath interface
output otp_scrmbl_cmd_e scrmbl_cmd_o,
output digest_mode_e scrmbl_mode_o,
output logic [ConstSelWidth-1:0] scrmbl_sel_o,
output logic [ScrmblBlockWidth-1:0] scrmbl_data_o,
output logic scrmbl_valid_o,
input logic scrmbl_ready_i,
input logic scrmbl_valid_i,
input logic [ScrmblBlockWidth-1:0] scrmbl_data_i
);
import prim_util_pkg::vbits;
////////////////////////
// Integration Checks //
////////////////////////
// 2xFlash, OTBN + SRAM slots
localparam int NumReq = 3 + NumSramKeyReqSlots;
// Make sure key sizes in the system are multiples of 64bit and not larger than 256bit.
`ASSERT_INIT(KeyNonceSize0_A, (FlashKeySeedWidth <= 256) && ((FlashKeySeedWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize1_A, (SramKeySeedWidth <= 256) && ((SramKeySeedWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize2_A, (FlashKeyWidth <= 256) && ((FlashKeyWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize3_A, (SramKeyWidth <= 256) && ((SramKeyWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize4_A, (SramNonceWidth <= 256) && ((SramNonceWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize5_A, (OtbnKeyWidth <= 256) && ((OtbnKeyWidth % 64) == 0))
`ASSERT_INIT(KeyNonceSize6_A, (OtbnNonceWidth <= 256) && ((OtbnNonceWidth % 64) == 0))
// Make sure EDN interface has compatible width.
`ASSERT_INIT(EntropyWidthDividesDigestBlockWidth_A, (ScrmblKeyWidth % EdnDataWidth) == 0)
// Currently the assumption is that the SRAM nonce is the widest.
`ASSERT_INIT(NonceWidth_A, NumNonceChunks * ScrmblBlockWidth == SramNonceWidth)
///////////////////////////////////
// Input Mapping and Arbitration //
///////////////////////////////////
// The key derivation and token hashing functions are aligned such that 2 x 128bit key
// seeds / token blocks are processed in two subsequent steps using the digest primitive.
// This effectively compresses these blocks down into 2 x 64bit blocks, thereby creating
// one 128bit key or token output.
//
// The same FSM is shared among the different flavors of key derivation and token
// hashing functions, and the following configuration options are available:
//
// 1) ingest an additional 128bit entropy block after ingesting a 128bit key seed.
// 2) keep digest state after producing the first 64bit block instead of reverting to the IV.
// 3) netlist constant index.
// 4) fetch additional entropy for the nonce output.
// 5) whether or not the key seed is valid. if not, it will be defaulted to '0.
// 6) 256bit key seed / token input.
//
// The configuration options are set further below, depending on the request type.
typedef struct packed {
logic ingest_entropy; // 1)
logic chained_digest; // 2)
digest_sel_e digest_sel; // 3)
logic fetch_nonce; // 4)
logic [1:0] nonce_size; // 4)
logic seed_valid; // 5)
logic [3:0][ScrmblBlockWidth-1:0] seed; // 6)
} req_bundle_t;
logic [NumReq-1:0] req, gnt;
req_bundle_t req_bundles [NumReq];
assign req[0] = flash_otp_key_i.data_req;
assign req[1] = flash_otp_key_i.addr_req;
assign req[2] = otbn_otp_key_i.req;
assign flash_otp_key_o.data_ack = gnt[0];
assign flash_otp_key_o.addr_ack = gnt[1];
assign otbn_otp_key_o.ack = gnt[2];
// anchored seeds
logic [FlashKeySeedWidth-1:0] flash_data_key_seed;
logic [FlashKeySeedWidth-1:0] flash_addr_key_seed;
logic [SramKeySeedWidth-1:0] sram_data_key_seed;
prim_sec_anchor_buf #(
.Width(FlashKeySeedWidth)
) u_flash_data_key_anchor (
.in_i(flash_data_key_seed_i),
.out_o(flash_data_key_seed)
);
prim_sec_anchor_buf #(
.Width(FlashKeySeedWidth)
) u_flash_addr_key_anchor (
.in_i(flash_addr_key_seed_i),
.out_o(flash_addr_key_seed)
);
prim_sec_anchor_buf #(
.Width(SramKeySeedWidth)
) u_sram_data_key_anchor (
.in_i(sram_data_key_seed_i),
.out_o(sram_data_key_seed)
);
// Flash data key
assign req_bundles[0] = '{ingest_entropy: 1'b0, // no random entropy added
chained_digest: 1'b0, // revert to netlist IV between blocks
digest_sel: FlashDataKey,
fetch_nonce: 1'b1,
nonce_size: 2'(FlashKeyWidth/EdnDataWidth-1),
seed_valid: scrmbl_key_seed_valid_i,
seed: flash_data_key_seed}; // 2x128bit
// Flash addr key
assign req_bundles[1] = '{ingest_entropy: 1'b0, // no random entropy added
chained_digest: 1'b0, // revert to netlist IV between blocks
digest_sel: FlashAddrKey,
fetch_nonce: 1'b1,
nonce_size: '0,
seed_valid: scrmbl_key_seed_valid_i,
seed: flash_addr_key_seed}; // 2x128bit
// OTBN key
assign req_bundles[2] = '{ingest_entropy: 1'b1, // ingest random data
chained_digest: 1'b0, // revert to netlist IV between blocks
digest_sel: SramDataKey,
fetch_nonce: 1'b1, // fetch nonce
nonce_size: 2'(OtbnNonceWidth/EdnDataWidth-1),
seed_valid: scrmbl_key_seed_valid_i,
seed: {sram_data_key_seed, // reuse same seed
sram_data_key_seed}};
// SRAM keys
for (genvar k = 3; k < NumReq; k++) begin : gen_req_assign
assign req[k] = sram_otp_key_i[k-3].req;
assign sram_otp_key_o[k-3].ack = gnt[k];
assign req_bundles[k] = '{ingest_entropy: 1'b1, // ingest random data
chained_digest: 1'b0, // revert to netlist IV between blocks
digest_sel: SramDataKey,
fetch_nonce: 1'b1, // fetch nonce
nonce_size: 2'(SramNonceWidth/EdnDataWidth-1),
seed_valid: scrmbl_key_seed_valid_i,
seed: {sram_data_key_seed, // reuse same seed
sram_data_key_seed}};
end
// This arbitrates among incoming key derivation requests on a
// round robin basis to prevent deadlock.
logic req_valid, req_ready;
req_bundle_t req_bundle;
prim_arbiter_tree #(
.N(NumReq),
.DW($bits(req_bundle_t)))
u_req_arb (
.clk_i,
.rst_ni,
.req_chk_i ( 1'b1 ),
.req_i ( req ),
.data_i ( req_bundles ),
.gnt_o ( gnt ),
.idx_o ( ),
.valid_o ( req_valid ),
.data_o ( req_bundle ),
.ready_i ( req_ready )
);
//////////////////////////////
// Temporary Regs and Muxes //
//////////////////////////////
localparam int CntWidth = 2;
logic seed_cnt_clr, seed_cnt_en, entropy_cnt_clr, entropy_cnt_en, seed_cnt_err, entropy_cnt_err;
logic [CntWidth-1:0] seed_cnt, entropy_cnt;
// SEC_CM: KDI_SEED.CTR.REDUN
prim_count #(
.Width(CntWidth)
) u_prim_count_seed (
.clk_i,
.rst_ni,
.clr_i(seed_cnt_clr),
.set_i(1'b0),
.set_cnt_i('0),
.incr_en_i(seed_cnt_en),
.decr_en_i(1'b0),
.step_i(CntWidth'(1)),
.cnt_o(seed_cnt),
.cnt_next_o(),
.err_o(seed_cnt_err)
);
// SEC_CM: KDI_ENTROPY.CTR.REDUN
prim_count #(
.Width(CntWidth)
) u_prim_count_entropy (
.clk_i,
.rst_ni,
.clr_i(entropy_cnt_clr),
.set_i(1'b0),
.set_cnt_i('0),
.incr_en_i(entropy_cnt_en),
.decr_en_i(1'b0),
.step_i(CntWidth'(1)),
.cnt_o(entropy_cnt),
.cnt_next_o(),
.err_o(entropy_cnt_err)
);
logic seed_valid_reg_en;
logic key_reg_en, nonce_reg_en;
logic seed_valid_d, seed_valid_q;
logic [ScrmblKeyWidth/ScrmblBlockWidth-1:0][ScrmblBlockWidth-1:0] key_out_d, key_out_q;
logic [NumNonceChunks-1:0][ScrmblBlockWidth-1:0] nonce_out_d, nonce_out_q;
always_comb begin : p_outregs
key_out_d = key_out_q;
nonce_out_d = nonce_out_q;
seed_valid_d = seed_valid_q;
if (key_reg_en) begin
key_out_d[seed_cnt[1]] = scrmbl_data_i;
end
if (nonce_reg_en) begin
nonce_out_d[entropy_cnt[$clog2(NumNonceChunks)-1:0]] = edn_data_i;
end
if (seed_valid_reg_en) begin
seed_valid_d = req_bundle.seed_valid;
end
end
// Connect keys/nonce outputs to output regs.
prim_sec_anchor_flop #(
.Width(ScrmblKeyWidth),
.ResetValue(RndCnstScrmblKeyInit.key)
) u_key_out_anchor (
.clk_i,
.rst_ni,
.d_i(key_out_d),
.q_o(key_out_q)
);
assign otbn_otp_key_o.key = key_out_q;
assign otbn_otp_key_o.nonce = nonce_out_q[OtbnNonceSel-1:0];
assign otbn_otp_key_o.seed_valid = seed_valid_q;
assign flash_otp_key_o.key = key_out_q;
assign flash_otp_key_o.rand_key = nonce_out_q[FlashNonceSel-1:0];
assign flash_otp_key_o.seed_valid = seed_valid_q;
for (genvar k = 0; k < NumSramKeyReqSlots; k++) begin : gen_out_assign
assign sram_otp_key_o[k].key = key_out_q;
assign sram_otp_key_o[k].nonce = nonce_out_q[SramNonceSel-1:0];
assign sram_otp_key_o[k].seed_valid = seed_valid_q;
end
typedef enum logic {
SeedData,
EntropyData
} data_sel_e;
// Select correct 64bit block.
data_sel_e data_sel;
assign scrmbl_data_o = (data_sel == EntropyData) ? nonce_out_q[entropy_cnt[0]] :
// Gate seed value to '0 if invalid.
(req_bundle.seed_valid) ? req_bundle.seed[seed_cnt] : '0;
/////////////////
// Control FSM //
/////////////////
// SEC_CM: KDI.FSM.SPARSE
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 11 -n 10 \
// -s 2544133835 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (54.55%)
// 6: |||||||||||||||| (45.45%)
// 7: --
// 8: --
// 9: --
// 10: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 6
// Minimum Hamming weight: 3
// Maximum Hamming weight: 9
//
localparam int StateWidth = 10;
typedef enum logic [StateWidth-1:0] {
ResetSt = 10'b0101100001,
IdleSt = 10'b0001011011,
DigClrSt = 10'b1101010110,
DigLoadSt = 10'b0010110111,
FetchEntropySt = 10'b1000001101,
DigEntropySt = 10'b0100111100,
DigFinSt = 10'b1000100010,
DigWaitSt = 10'b1110010001,
FetchNonceSt = 10'b0011000100,
FinishSt = 10'b1011111000,
ErrorSt = 10'b1111101111
} state_e;
state_e state_d, state_q;
logic edn_req_d, edn_req_q;
assign edn_req_o = edn_req_q;
always_comb begin : p_fsm
state_d = state_q;
// FSM Error output
fsm_err_o = 1'b0;
// Counters
seed_cnt_en = 1'b0;
seed_cnt_clr = 1'b0;
entropy_cnt_en = 1'b0;
entropy_cnt_clr = 1'b0;
// EDN 128bit block fetch request.
// This keeps the request alive until it has
// been acked to adhere to the req/ack protocol
// even in cases where the FSM jumps into
// an error state while waiting for a request.
edn_req_d = edn_req_q & ~edn_ack_i;
// Data selection and temp registers
data_sel = SeedData;
key_reg_en = 1'b0;
nonce_reg_en = 1'b0;
seed_valid_reg_en = 1'b0;
// Scrambling datapath
scrmbl_mtx_req_o = 1'b0;
scrmbl_sel_o = req_bundle.digest_sel;
scrmbl_cmd_o = LoadShadow;
scrmbl_mode_o = StandardMode;
scrmbl_valid_o = 1'b0;
// Request acknowledgement
req_ready = 1'b0;
unique case (state_q)
///////////////////////////////////////////////////////////////////
// State right after reset. Wait here until KDI gets enabled.
ResetSt: begin
if (kdi_en_i) begin
state_d = IdleSt;
end
end
///////////////////////////////////////////////////////////////////
// Wait for a request, then go and acquire the mutex.
IdleSt: begin
if (req_valid) begin
state_d = DigClrSt;
seed_cnt_clr = 1'b1;
entropy_cnt_clr = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// First, acquire the mutex for the digest and clear the digest state.
DigClrSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
// Need to reset the digest state and set digest mode to "standard".
scrmbl_cmd_o = DigestInit;
if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin
state_d = DigLoadSt;
end
end
///////////////////////////////////////////////////////////////////
// Load two 64bit blocks of the seed, and trigger digest calculation.
DigLoadSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
// Trigger digest round in case this is the second block in a row.
if (seed_cnt[0]) begin
scrmbl_cmd_o = Digest;
if (scrmbl_ready_i) begin
// Go and ingest a block of entropy if required.
if (req_bundle.ingest_entropy) begin
state_d = FetchEntropySt;
// Otherwise go to digest finalization state.
end else begin
state_d = DigFinSt;
end
end
// Just load first 64bit block and stay here.
end else if (scrmbl_ready_i) begin
seed_cnt_en = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Fetch random data to ingest for key derivation.
FetchEntropySt: begin
scrmbl_mtx_req_o = 1'b1;
edn_req_d = 1'b1;
if (edn_ack_i) begin
nonce_reg_en = 1'b1;
// Finished, go and acknowledge this request.
if (entropy_cnt == 2'h1) begin
state_d = DigEntropySt;
entropy_cnt_clr = 1'b1;
// Keep on requesting entropy.
end else begin
entropy_cnt_en = 1'b1;
end
end
end
///////////////////////////////////////////////////////////////////
// Load two 64bit blocks of entropy data.
DigEntropySt: begin
scrmbl_mtx_req_o = 1'b1;
data_sel = EntropyData;
scrmbl_valid_o = 1'b1;
// Trigger digest round in case this is the second block in a row,
// and go to digest finalization.
if (entropy_cnt[0]) begin
scrmbl_cmd_o = Digest;
if (scrmbl_ready_i) begin
state_d = DigFinSt;
entropy_cnt_clr = 1'b1;
end
// Just load first 64bit block and stay here.
end else if (scrmbl_ready_i) begin
entropy_cnt_en = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Trigger digest finalization and go wait for the result.
DigFinSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
scrmbl_cmd_o = DigestFinalize;
if (scrmbl_ready_i) begin
state_d = DigWaitSt;
end
end
///////////////////////////////////////////////////////////////////
// Wait for the digest to return, and write the result to the key
// output register. Go back and process the second part of the
// input seed if needed.
DigWaitSt: begin
scrmbl_mtx_req_o = 1'b1;
if (scrmbl_valid_i) begin
key_reg_en = 1'b1;
// Not finished yet, need to go back and produce second 64bit block.
if (seed_cnt == 2'h1) begin
seed_cnt_en = 1'b1;
// In this case the previous digest state is kept,
// which leads to a chained digest.
if (req_bundle.chained_digest) begin
state_d = DigLoadSt;
// In this case we revert the digest state to the netlist IV.
end else begin
state_d = DigClrSt;
end
// This was the second 64bit output block.
end else begin
seed_cnt_clr = 1'b1;
// Make sure we output the status of the key seed in OTP.
seed_valid_reg_en = 1'b1;
// Check whether we need to fetch additional nonce data.
if (req_bundle.fetch_nonce) begin
state_d = FetchNonceSt;
end else begin
// Finished, go and acknowledge this request.
state_d = FinishSt;
end
end
end
end
///////////////////////////////////////////////////////////////////
// Fetch additional nonce data. Note that the mutex is released in
// this state.
FetchNonceSt: begin
edn_req_d = 1'b1;
if (edn_ack_i) begin
nonce_reg_en = 1'b1;
// Finished, go and acknowledge this request.
if (entropy_cnt == req_bundle.nonce_size) begin
state_d = FinishSt;
entropy_cnt_clr = 1'b1;
// Keep on requesting entropy.
end else begin
entropy_cnt_en = 1'b1;
end
end
end
///////////////////////////////////////////////////////////////////
// Acknowledge request and go back to IdleSt.
FinishSt: begin
state_d = IdleSt;
req_ready = 1'b1;
end
///////////////////////////////////////////////////////////////////
// Terminal error state. This raises an alert.
ErrorSt: begin
fsm_err_o = 1'b1;
end
///////////////////////////////////////////////////////////////////
// This should never happen, hence we directly jump into the
// error state, where an alert will be triggered.
default: begin
state_d = ErrorSt;
fsm_err_o = 1'b1;
end
///////////////////////////////////////////////////////////////////
endcase // state_q
// Unconditionally jump into the terminal error state in case of escalation.
// SEC_CM: KDI.FSM.LOCAL_ESC, KDI.FSM.GLOBAL_ESC
if (escalate_en_i != lc_ctrl_pkg::Off || seed_cnt_err || entropy_cnt_err) begin
state_d = ErrorSt;
fsm_err_o = 1'b1;
end
end
///////////////
// Registers //
///////////////
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, ResetSt)
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
if (!rst_ni) begin
nonce_out_q <= RndCnstScrmblKeyInit.nonce;
seed_valid_q <= 1'b0;
edn_req_q <= 1'b0;
end else begin
nonce_out_q <= nonce_out_d;
seed_valid_q <= seed_valid_d;
edn_req_q <= edn_req_d;
end
end
////////////////
// Assertions //
////////////////
`ASSERT_KNOWN(FsmErrKnown_A, fsm_err_o)
`ASSERT_KNOWN(EdnReqKnown_A, edn_req_o)
`ASSERT_KNOWN(FlashOtpKeyRspKnown_A, flash_otp_key_o)
`ASSERT_KNOWN(SramOtpKeyRspKnown_A, sram_otp_key_o)
`ASSERT_KNOWN(OtbnOtpKeyRspKnown_A, otbn_otp_key_o)
`ASSERT_KNOWN(ScrmblMtxReqKnown_A, scrmbl_mtx_req_o)
`ASSERT_KNOWN(ScrmblCmdKnown_A, scrmbl_cmd_o)
`ASSERT_KNOWN(ScrmblModeKnown_A, scrmbl_mode_o)
`ASSERT_KNOWN(ScrmblSelKnown_A, scrmbl_sel_o)
`ASSERT_KNOWN(ScrmblDataKnown_A, scrmbl_data_o)
`ASSERT_KNOWN(ScrmblValidKnown_A, scrmbl_valid_o)
endmodule : otp_ctrl_kdi