|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | // Key manager top level | 
|  | // | 
|  |  | 
|  | `include "prim_assert.sv" | 
|  |  | 
|  | module keymgr_ctrl | 
|  | import keymgr_pkg::*; | 
|  | import keymgr_reg_pkg::*; | 
|  | #( | 
|  | parameter bit KmacEnMasking = 1'b1 | 
|  | ) ( | 
|  | input clk_i, | 
|  | input rst_ni, | 
|  |  | 
|  | // lifecycle enforcement | 
|  | // SEC_CM: CTRL.FSM.GLOBAL_ESC | 
|  | input en_i, | 
|  |  | 
|  | // faults that can occur outside of operations | 
|  | input regfile_intg_err_i, | 
|  | input shadowed_update_err_i, | 
|  | input shadowed_storage_err_i, | 
|  | input reseed_cnt_err_i, | 
|  | input sideload_sel_err_i, | 
|  | input sideload_fsm_err_i, | 
|  |  | 
|  | // Software interface | 
|  | input op_start_i, | 
|  | input keymgr_ops_e op_i, | 
|  | input [CdiWidth-1:0] op_cdi_sel_i, | 
|  | output logic op_done_o, | 
|  | output keymgr_op_status_e status_o, | 
|  | output logic [ErrLastPos-1:0] error_o, | 
|  | output logic [FaultLastPos-1:0] fault_o, | 
|  | output logic data_hw_en_o, | 
|  | output logic data_sw_en_o, | 
|  | output logic data_valid_o, | 
|  | output logic wipe_key_o, | 
|  | output keymgr_working_state_e working_state_o, | 
|  | output logic sw_binding_unlock_o, | 
|  | output logic init_o, | 
|  |  | 
|  | // Data input | 
|  | input  otp_ctrl_pkg::otp_keymgr_key_t root_key_i, | 
|  | output prim_mubi_pkg::mubi4_t hw_sel_o, | 
|  | output keymgr_stage_e stage_sel_o, | 
|  | output logic invalid_stage_sel_o, | 
|  | output logic [CdiWidth-1:0] cdi_sel_o, | 
|  |  | 
|  | // KMAC ctrl interface | 
|  | output logic adv_en_o, | 
|  | output logic id_en_o, | 
|  | output logic gen_en_o, | 
|  | output hw_key_req_t key_o, | 
|  | input kmac_done_i, | 
|  | input kmac_input_invalid_i, // asserted when selected data fails criteria check | 
|  | input kmac_fsm_err_i, // asserted when kmac fsm reaches unexpected state | 
|  | input kmac_op_err_i,  // asserted when kmac itself reports an error | 
|  | input kmac_done_err_i,// asserted when kmac unexpectedly toggles done | 
|  | input kmac_cmd_err_i, // asserted when more than one command given to kmac | 
|  | input [Shares-1:0][KeyWidth-1:0] kmac_data_i, | 
|  |  | 
|  | // prng control interface | 
|  | input [Shares-1:0][RandWidth-1:0] entropy_i, | 
|  | input prng_reseed_ack_i, | 
|  | output logic prng_reseed_req_o, | 
|  | output logic prng_en_o | 
|  | ); | 
|  |  | 
|  | localparam int EntropyWidth = LfsrWidth / 2; | 
|  | localparam int EntropyRounds = KeyWidth / EntropyWidth; | 
|  | localparam int EntropyRndWidth = prim_util_pkg::vbits(EntropyRounds); | 
|  | localparam int CntWidth = EntropyRounds > CDIs ? EntropyRndWidth : CdiWidth; | 
|  | localparam int EccDataWidth = 64; | 
|  | localparam int EccWidth = 8; | 
|  | localparam int EccWords = KeyWidth / EccDataWidth; | 
|  | localparam int TotalEccWords = EccWords * Shares * CDIs; | 
|  |  | 
|  |  | 
|  | // Enumeration for working state | 
|  | // Encoding generated with: | 
|  | // $ ./util/design/sparse-fsm-encode.py -d 5 -m 11 -n 10 \ | 
|  | //      -s 4101887575 --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: 2 | 
|  | // Maximum Hamming weight: 8 | 
|  | // | 
|  | localparam int StateWidth = 10; | 
|  | typedef enum logic [StateWidth-1:0] { | 
|  | StCtrlReset          = 10'b1101100001, | 
|  | StCtrlEntropyReseed  = 10'b1110010010, | 
|  | StCtrlRandom         = 10'b0011110100, | 
|  | StCtrlRootKey        = 10'b0110101111, | 
|  | StCtrlInit           = 10'b0100000100, | 
|  | StCtrlCreatorRootKey = 10'b1000011101, | 
|  | StCtrlOwnerIntKey    = 10'b0001001010, | 
|  | StCtrlOwnerKey       = 10'b1101111110, | 
|  | StCtrlDisabled       = 10'b1010101000, | 
|  | StCtrlWipe           = 10'b0000110011, | 
|  | StCtrlInvalid        = 10'b1011000111 | 
|  | } state_e; | 
|  | state_e state_q, state_d; | 
|  |  | 
|  | // A variable that represents differentiates states before root key and after root key. | 
|  | logic initialized; | 
|  |  | 
|  | // There are two versions of the key state, one for sealing one for attestation | 
|  | // Among each version, there are multiple shares | 
|  | // Each share is a fixed multiple of the entropy width | 
|  | logic [CDIs-1:0][Shares-1:0][EntropyRounds-1:0][EntropyWidth-1:0] key_state_d; | 
|  | logic [CDIs-1:0][Shares-1:0][EccWords-1:0][EccDataWidth-1:0] key_state_ecc_words_d; | 
|  | logic [CDIs-1:0][Shares-1:0][EccWords-1:0][EccDataWidth-1:0] key_state_q; | 
|  | logic [CDIs-1:0][Shares-1:0][EccWords-1:0][EccWidth-1:0] key_state_ecc_q; | 
|  | logic [CntWidth-1:0] cnt; | 
|  | logic [CdiWidth-1:0] cdi_cnt; | 
|  |  | 
|  | // error conditions | 
|  | logic invalid_kmac_out; | 
|  | logic invalid_op; | 
|  | logic cnt_err; | 
|  | // states fall out of sparsely encoded range | 
|  | logic state_intg_err_q, state_intg_err_d; | 
|  |  | 
|  | /////////////////////////// | 
|  | //  General operation decode | 
|  | /////////////////////////// | 
|  |  | 
|  | logic adv_op, dis_op, gen_id_op, gen_sw_op, gen_hw_op, gen_op; | 
|  | assign adv_op    = (op_i == OpAdvance); | 
|  | assign gen_id_op = (op_i == OpGenId); | 
|  | assign gen_sw_op = (op_i == OpGenSwOut); | 
|  | assign gen_hw_op = (op_i == OpGenHwOut); | 
|  | assign dis_op    = ~(op_i inside {OpAdvance, OpGenId, OpGenSwOut, OpGenHwOut}); | 
|  | assign gen_op    = (gen_id_op | gen_sw_op | gen_hw_op); | 
|  |  | 
|  | /////////////////////////// | 
|  | //  interaction between software and main fsm | 
|  | /////////////////////////// | 
|  | // disable is treated like an advanced call | 
|  | logic advance_sel; | 
|  | logic disable_sel; | 
|  | logic gen_out_hw_sel; | 
|  |  | 
|  | assign advance_sel    = op_start_i & adv_op    & en_i; | 
|  | assign gen_out_hw_sel = op_start_i & gen_hw_op & en_i; | 
|  |  | 
|  | // disable is selected whenever a normal operation is not set | 
|  | assign disable_sel    = (op_start_i & dis_op) | !en_i; | 
|  |  | 
|  |  | 
|  | /////////////////////////// | 
|  | //  interaction between main control fsm and operation fsm | 
|  | /////////////////////////// | 
|  |  | 
|  | // req/ack interface with op handling fsm | 
|  | logic op_req; | 
|  | logic op_ack; | 
|  | logic op_update; | 
|  | logic op_busy; | 
|  | logic disabled; | 
|  | logic invalid; | 
|  |  | 
|  | logic adv_req, dis_req, id_req, gen_req; | 
|  | assign adv_req = op_req & adv_op; | 
|  | assign dis_req = op_req & dis_op; | 
|  | assign id_req  = op_req & gen_id_op; | 
|  | assign gen_req = op_req & (gen_sw_op | gen_hw_op); | 
|  |  | 
|  | /////////////////////////// | 
|  | //  interaction between operation fsm and software | 
|  | /////////////////////////// | 
|  | // categories of keymgr errors | 
|  | logic [SyncErrLastIdx-1:0] sync_err; | 
|  | logic [SyncFaultLastIdx-1:0] sync_fault; | 
|  | logic [AsyncFaultLastIdx-1:0] async_fault; | 
|  |  | 
|  | logic op_err; | 
|  | logic op_fault_err; | 
|  |  | 
|  | // unlock sw binding configuration whenever an advance call is made without errors | 
|  | assign sw_binding_unlock_o = adv_req & op_ack & ~(op_err | op_fault_err); | 
|  |  | 
|  | // error definition | 
|  | // check incoming kmac data validity | 
|  | // Only check during the periods when there is actual kmac output | 
|  | assign invalid_kmac_out = (op_update | op_ack) & | 
|  | (~valid_data_chk(kmac_data_i[0]) | | 
|  | (~valid_data_chk(kmac_data_i[1]) & KmacEnMasking)); | 
|  |  | 
|  | // async errors have nothing to do with the operation and thus should not | 
|  | // impact operation results. | 
|  | assign op_err = |sync_err; | 
|  |  | 
|  | assign op_fault_err = |{sync_fault, async_fault}; | 
|  |  | 
|  | /////////////////////////// | 
|  | //  key update controls | 
|  | /////////////////////////// | 
|  |  | 
|  | // update select can come from both main and operation fsm's | 
|  | keymgr_key_update_e update_sel, op_update_sel; | 
|  |  | 
|  | // req from main control fsm to key update controls | 
|  | logic wipe_req; | 
|  | logic random_req; | 
|  | logic random_ack; | 
|  | logic ld_root_key; | 
|  |  | 
|  | // wipe and initialize take precedence | 
|  | assign update_sel = wipe_req             ? KeyUpdateWipe   : | 
|  | random_req           ? KeyUpdateRandom : | 
|  | init_o | ld_root_key ? KeyUpdateRoot   : op_update_sel; | 
|  |  | 
|  | /////////////////////////// | 
|  | //  interaction between main fsm and prng | 
|  | /////////////////////////// | 
|  |  | 
|  | assign prng_en_o = random_req | disabled | invalid | wipe_req; | 
|  |  | 
|  | ////////////////////////// | 
|  | // Main Control FSM | 
|  | ////////////////////////// | 
|  | // SEC_CM: CTRL.FSM.SPARSE | 
|  | `PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, StCtrlReset) | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | state_intg_err_q <= '0; | 
|  | end else begin | 
|  | state_intg_err_q <= state_intg_err_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | // prevents unknowns from reaching the outside world. | 
|  | // - whatever operation causes the input data select to be disabled should not expose the key | 
|  | //   state. | 
|  | // - when there are no operations, the key state also should be exposed. | 
|  | assign key_o.valid = op_req; | 
|  |  | 
|  | assign cdi_sel_o = advance_sel ? cdi_cnt : op_cdi_sel_i; | 
|  |  | 
|  | assign invalid_stage_sel_o = ~(stage_sel_o inside {Creator, OwnerInt, Owner}); | 
|  | for (genvar i = 0; i < Shares; i++) begin : gen_key_out_assign | 
|  | assign key_o.key[i] = invalid_stage_sel_o ? | 
|  | {EntropyRounds{entropy_i[i]}} : | 
|  | key_state_q[cdi_sel_o][i]; | 
|  | end | 
|  |  | 
|  |  | 
|  | //SEC_CM: CTRL.KEY.INTEGRITY | 
|  | assign key_state_ecc_words_d = key_state_d; | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | key_state_q <= '0; | 
|  | key_state_ecc_q <= {TotalEccWords{prim_secded_pkg::SecdedInv7264ZeroEcc}}; | 
|  | end else begin | 
|  | for (int i = 0; i < CDIs; i++) begin | 
|  | for (int j = 0; j < Shares; j++) begin | 
|  | for (int k = 0; k < EccWords; k++) begin | 
|  | {key_state_ecc_q[i][j][k], key_state_q[i][j][k]} <= | 
|  | prim_secded_pkg::prim_secded_inv_72_64_enc(key_state_ecc_words_d[i][j][k]); | 
|  | end | 
|  | end | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | logic [CDIs-1:0][Shares-1:0][EccWords-1:0] ecc_errs; | 
|  | for (genvar i = 0; i < CDIs; i++) begin : gen_ecc_loop_cdi | 
|  | for (genvar j = 0; j < Shares; j++) begin : gen_ecc_loop_shares | 
|  | for (genvar k = 0; k < EccWords; k++) begin : gen_ecc_loop_words | 
|  | logic [1:0] errs; | 
|  | prim_secded_inv_72_64_dec u_dec ( | 
|  | .data_i({key_state_ecc_q[i][j][k], key_state_q[i][j][k]}), | 
|  | .data_o(), | 
|  | .syndrome_o(), | 
|  | .err_o(errs) | 
|  | ); | 
|  | assign ecc_errs[i][j][k] = |errs; | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | // root key valid sync | 
|  | logic root_key_valid_q; | 
|  |  | 
|  | prim_flop_2sync # ( | 
|  | .Width(1) | 
|  | ) u_key_valid_sync ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .d_i(root_key_i.valid), | 
|  | .q_o(root_key_valid_q) | 
|  | ); | 
|  |  | 
|  | // Do not let the count toggle unless an advance operation is | 
|  | // selected | 
|  | assign cdi_cnt = op_req ? cnt[CdiWidth-1:0] : '0; | 
|  |  | 
|  | always_comb begin | 
|  | key_state_d = key_state_q; | 
|  | data_valid_o = 1'b0; | 
|  | wipe_key_o = 1'b0; | 
|  |  | 
|  | // if a wipe request arrives, immediately destroy the | 
|  | // keys regardless of current state | 
|  | unique case (update_sel) | 
|  | KeyUpdateRandom: begin | 
|  | for (int i = 0; i < CDIs; i++) begin | 
|  | for (int j = 0; j < Shares; j++) begin | 
|  | key_state_d[i][j][cnt[EntropyRndWidth-1:0]] = entropy_i[j]; | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | KeyUpdateRoot: begin | 
|  | if (root_key_valid_q) begin | 
|  | for (int i = 0; i < CDIs; i++) begin | 
|  | if (KmacEnMasking) begin : gen_two_share_key | 
|  | key_state_d[i][0] = root_key_i.key_share0; | 
|  | key_state_d[i][1] = root_key_i.key_share1; | 
|  | end else begin : gen_one_share_key | 
|  | key_state_d[i][0] = root_key_i.key_share0 ^ root_key_i.key_share1; | 
|  | key_state_d[i][1] = '0; | 
|  | end | 
|  | end | 
|  | end else begin | 
|  | // if root key is not valid, load and invalid value | 
|  | for (int i = 0; i < CDIs; i++) begin | 
|  | key_state_d[i][0] = '0; | 
|  | key_state_d[i][1] = '{default: '1}; | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | KeyUpdateKmac: begin | 
|  | data_valid_o = gen_op; | 
|  | key_state_d[cdi_sel_o] = (adv_op || dis_op) ? kmac_data_i : key_state_q[cdi_sel_o]; | 
|  | end | 
|  |  | 
|  | KeyUpdateWipe: begin | 
|  | wipe_key_o = 1'b1; | 
|  | for (int i = 0; i < CDIs; i++) begin | 
|  | for (int j = 0; j < Shares; j++) begin | 
|  | key_state_d[i][j] = {EntropyRounds{entropy_i[j]}}; | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | default:; | 
|  | endcase // unique case (update_sel) | 
|  | end | 
|  |  | 
|  | // SEC_CM: CTRL.CTR.REDUN | 
|  | prim_count #( | 
|  | .Width(CntWidth) | 
|  | ) u_cnt ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .clr_i(op_ack | random_ack), | 
|  | .set_i('0), | 
|  | .set_cnt_i('0), | 
|  | .incr_en_i(op_update | random_req), | 
|  | .decr_en_i(1'b0), | 
|  | .step_i(CntWidth'(1'b1)), | 
|  | .cnt_o(cnt), | 
|  | .cnt_next_o(), | 
|  | .err_o(cnt_err) | 
|  | ); | 
|  |  | 
|  |  | 
|  | prim_mubi4_sender u_hw_sel ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .mubi_i (prim_mubi_pkg::mubi4_bool_to_mubi(gen_out_hw_sel)), | 
|  | .mubi_o (hw_sel_o) | 
|  | ); | 
|  |  | 
|  | // when in a state that accepts commands, look at op_ack for completion | 
|  | // when in a state that does not accept commands, wait for other triggers. | 
|  | assign op_done_o = op_req ? op_ack : | 
|  | (init_o | invalid_op); | 
|  |  | 
|  |  | 
|  | // There are 3 possibilities | 
|  | // advance to next state (software command) | 
|  | // advance to disabled state (software command) | 
|  | // advance to invalid state (detected fault) | 
|  | logic adv_state; | 
|  | logic dis_state; | 
|  | logic inv_state; | 
|  | assign adv_state = op_ack & adv_req & ~op_err; | 
|  | assign dis_state = op_ack & dis_req; | 
|  |  | 
|  | // SEC_CM: CTRL.FSM.LOCAL_ESC | 
|  | // begin invalidation when faults are observed. | 
|  | // sync faults only invalidate on transaction boudaries | 
|  | // async faults begin invalidating immediately | 
|  | assign inv_state = |fault_o; | 
|  |  | 
|  | always_comb begin | 
|  | // persistent data | 
|  | state_d = state_q; | 
|  |  | 
|  | // request to op handling | 
|  | op_req = 1'b0; | 
|  | random_req = 1'b0; | 
|  | random_ack = 1'b0; | 
|  |  | 
|  | // request to key updates | 
|  | wipe_req = 1'b0; | 
|  |  | 
|  | // invalid operation issued | 
|  | invalid_op = '0; | 
|  |  | 
|  | // data update and select signals | 
|  | stage_sel_o = Disable; | 
|  |  | 
|  | // indication that state is disabled | 
|  | disabled = 1'b0; | 
|  |  | 
|  | // indication that state is invalid | 
|  | invalid = 1'b0; | 
|  |  | 
|  | // enable prng toggling | 
|  | prng_reseed_req_o = 1'b0; | 
|  |  | 
|  | // initialization complete | 
|  | init_o = 1'b0; | 
|  |  | 
|  | // Most states are initialized, mark the exceptions | 
|  | initialized = 1'b1; | 
|  |  | 
|  | // during certain states, the otp root key is continuosly loaded | 
|  | ld_root_key = 1'b0; | 
|  |  | 
|  | // if state is ever faulted, hold on to this indication | 
|  | // until reset. | 
|  | state_intg_err_d = state_intg_err_q; | 
|  |  | 
|  | unique case (state_q) | 
|  | // Only advance can be called from reset state | 
|  | StCtrlReset: begin | 
|  | initialized = 1'b0; | 
|  |  | 
|  | // always use random data for advance, since out of reset state | 
|  | // the key state will be randomized. | 
|  | stage_sel_o = Disable; | 
|  |  | 
|  | // key state is updated when it is an advance call | 
|  | // all other operations are invalid, including disable | 
|  | invalid_op = op_start_i & ~advance_sel; | 
|  |  | 
|  | // if there was a structural fault before anything began, wipe immediately | 
|  | if (inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | end else if (advance_sel) begin | 
|  | state_d = StCtrlEntropyReseed; | 
|  | end | 
|  | end | 
|  |  | 
|  | // reseed entropy | 
|  | StCtrlEntropyReseed: begin | 
|  | initialized = 1'b0; | 
|  | prng_reseed_req_o = 1'b1; | 
|  |  | 
|  | if (prng_reseed_ack_i) begin | 
|  | state_d = StCtrlRandom; | 
|  | end | 
|  | end | 
|  |  | 
|  | // This state does not accept any command. | 
|  | StCtrlRandom: begin | 
|  | initialized = 1'b0; | 
|  | random_req = 1'b1; | 
|  |  | 
|  | // when mask population is complete, xor the root_key into the zero share | 
|  | // if in the future the root key is updated to 2 shares, it will direclty overwrite | 
|  | // the values here | 
|  | if (int'(cnt) == EntropyRounds-1) begin | 
|  | random_ack = 1'b1; | 
|  | state_d = StCtrlRootKey; | 
|  | end | 
|  | end | 
|  |  | 
|  | // load the root key. | 
|  | StCtrlRootKey: begin | 
|  | init_o = 1'b1; | 
|  | initialized = 1'b1; | 
|  | state_d = en_i ? StCtrlInit : StCtrlWipe; | 
|  | end | 
|  |  | 
|  | // Beginning from the Init state, operations are accepted. | 
|  | // Only valid operation is advance state. If invalid command received, | 
|  | // random data is selected for operation and no persistent state is changed. | 
|  | StCtrlInit: begin | 
|  | op_req = op_start_i; | 
|  |  | 
|  | // when advancing select creator data, otherwise use random input | 
|  | stage_sel_o = advance_sel ? Creator : Disable; | 
|  | invalid_op = op_start_i & ~(advance_sel | disable_sel); | 
|  |  | 
|  | // as long as an operation is not requested, continously load root key | 
|  | // if it is valid. | 
|  | // If an invalidate condition hits, also stop loading key | 
|  | ld_root_key = ~op_start_i; | 
|  | if (!en_i || inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | ld_root_key = '0; | 
|  | end else if (dis_state) begin | 
|  | state_d = StCtrlDisabled; | 
|  | end else if (adv_state) begin | 
|  | state_d = StCtrlCreatorRootKey; | 
|  | end | 
|  | end | 
|  |  | 
|  | // all commands  are valid during this stage | 
|  | StCtrlCreatorRootKey: begin | 
|  | op_req = op_start_i; | 
|  |  | 
|  | // when generating, select creator data input | 
|  | // when advancing, select owner intermediate key as target | 
|  | // when disabling, select random data input | 
|  | stage_sel_o = disable_sel ? Disable  : | 
|  | advance_sel ? OwnerInt : Creator; | 
|  |  | 
|  | if (!en_i || inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | end else if (dis_state) begin | 
|  | state_d = StCtrlDisabled; | 
|  | end else if (adv_state) begin | 
|  | state_d = StCtrlOwnerIntKey; | 
|  | end | 
|  | end | 
|  |  | 
|  | // all commands are valid during this stage | 
|  | StCtrlOwnerIntKey: begin | 
|  | op_req = op_start_i; | 
|  |  | 
|  | // when generating, select owner intermediate data input | 
|  | // when advancing, select owner as target | 
|  | // when disabling, select random data input | 
|  | stage_sel_o = disable_sel ? Disable  : | 
|  | advance_sel ? Owner : OwnerInt; | 
|  |  | 
|  | if (!en_i || inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | end else if (dis_state) begin | 
|  | state_d = StCtrlDisabled; | 
|  | end else if (adv_state) begin | 
|  | state_d = StCtrlOwnerKey; | 
|  | end | 
|  | end | 
|  |  | 
|  | // all commands are valid during this stage | 
|  | // however advance goes directly to disabled state | 
|  | StCtrlOwnerKey: begin | 
|  | op_req = op_start_i; | 
|  |  | 
|  | // when generating, select owner data input | 
|  | // when advancing, select disable as target | 
|  | // when disabling, select random data input | 
|  | stage_sel_o = disable_sel | advance_sel ? Disable : Owner; | 
|  |  | 
|  | if (!en_i || inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | end else if (adv_state || dis_state) begin | 
|  | state_d = StCtrlDisabled; | 
|  | end | 
|  | end | 
|  |  | 
|  | // The wipe state immediately clears out the key state, but waits for any ongoing | 
|  | // transaction to finish before going to disabled state. | 
|  | // Unlike the random state, this is an immedaite shutdown request, so all parts of the | 
|  | // key are wiped. | 
|  | StCtrlWipe: begin | 
|  | wipe_req = 1'b1; | 
|  | // if there was already an operation ongoing, maintain the request until completion | 
|  | op_req = op_busy; | 
|  | invalid_op = op_start_i; | 
|  |  | 
|  | // If the enable is dropped during the middle of a transaction, we clear and wait for that | 
|  | // transaction to gracefully complete (if it can). | 
|  | // There are two scenarios: | 
|  | // 1. the operation completed right when we started wiping, in which case the done would | 
|  | //    clear the start. | 
|  | // 2. the operation completed before we started wiping, or there was never an operation to | 
|  | //    begin with (op_start_i == 0), in this case, don't wait and immediately transition | 
|  | if (!op_start_i) begin | 
|  | state_d = StCtrlInvalid; | 
|  | end | 
|  | end | 
|  |  | 
|  | // StCtrlDisabled and StCtrlInvalid are almost functionally equivalent | 
|  | // The only difference is that Disabled is entered through software invocation, | 
|  | // while Invalid is entered through life cycle disable or operational fault. | 
|  | // | 
|  | // Both states continue to kick off random transactions | 
|  | // All transactions are treated as invalid despite completing | 
|  | StCtrlDisabled: begin | 
|  | op_req = op_start_i; | 
|  | disabled = 1'b1; | 
|  |  | 
|  | if (!en_i || inv_state) begin | 
|  | state_d = StCtrlWipe; | 
|  | end | 
|  | end | 
|  |  | 
|  | StCtrlInvalid: begin | 
|  | op_req = op_start_i; | 
|  | invalid = 1'b1; | 
|  | end | 
|  |  | 
|  | // latch the fault indication and start to wipe the key manager | 
|  | default: begin | 
|  | state_intg_err_d = 1'b1; | 
|  | state_d = StCtrlWipe; | 
|  | end | 
|  |  | 
|  | endcase // unique case (state_q) | 
|  | end // always_comb | 
|  |  | 
|  | // Current working state provided for software read | 
|  | // Certain states are collapsed for simplicity | 
|  | keymgr_working_state_e last_working_st; | 
|  | logic update_en; | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | last_working_st <= StReset; | 
|  | end else if (update_en) begin | 
|  | last_working_st <= working_state_o; | 
|  | end | 
|  | end | 
|  |  | 
|  | always_comb begin | 
|  | update_en = 1'b1; | 
|  | working_state_o = StInvalid; | 
|  |  | 
|  | unique case (state_q) | 
|  | StCtrlReset, StCtrlEntropyReseed, StCtrlRandom: | 
|  | working_state_o = StReset; | 
|  |  | 
|  | StCtrlRootKey, StCtrlInit: | 
|  | working_state_o = StInit; | 
|  |  | 
|  | StCtrlCreatorRootKey: | 
|  | working_state_o = StCreatorRootKey; | 
|  |  | 
|  | StCtrlOwnerIntKey: | 
|  | working_state_o = StOwnerIntKey; | 
|  |  | 
|  | StCtrlOwnerKey: | 
|  | working_state_o = StOwnerKey; | 
|  |  | 
|  | StCtrlDisabled: | 
|  | working_state_o = StDisabled; | 
|  |  | 
|  | StCtrlWipe: begin | 
|  | update_en = 1'b0; | 
|  | working_state_o = last_working_st; | 
|  | end | 
|  |  | 
|  | StCtrlInvalid: | 
|  | working_state_o = StInvalid; | 
|  |  | 
|  | default: | 
|  | working_state_o = StInvalid; | 
|  | endcase // unique case (state_q) | 
|  | end | 
|  |  | 
|  | always_comb begin | 
|  | status_o = OpIdle; | 
|  | if (op_done_o) begin | 
|  | // It is possible for an operation to finish the same cycle en_i goes low. | 
|  | // The main fsm handling is one cycle behind, but still report operation | 
|  | // fail. | 
|  | status_o = |{error_o, fault_o} ? OpDoneFail : OpDoneSuccess; | 
|  | end else if (op_start_i) begin | 
|  | status_o = OpWip; | 
|  | end | 
|  | end | 
|  |  | 
|  |  | 
|  | ///////////////////////// | 
|  | // Operateion state, handle advance and generate | 
|  | ///////////////////////// | 
|  |  | 
|  | logic op_fsm_err; | 
|  | keymgr_op_state_ctrl u_op_state ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .adv_req_i(adv_req), | 
|  | .dis_req_i(dis_req), | 
|  | .id_req_i(id_req), | 
|  | .gen_req_i(gen_req), | 
|  | .cnt_i(cdi_cnt), | 
|  | .op_ack_o(op_ack), | 
|  | .op_busy_o(op_busy), | 
|  | .op_update_o(op_update), | 
|  | .kmac_done_i, | 
|  | .adv_en_o, | 
|  | .id_en_o, | 
|  | .gen_en_o, | 
|  | .op_fsm_err_o(op_fsm_err) | 
|  | ); | 
|  |  | 
|  | // operational state cross check.  The state value must be consistent with | 
|  | // the input operations. | 
|  | logic op_state_cmd_err; | 
|  | assign op_state_cmd_err = (adv_en_o & ~(advance_sel | disable_sel)) | | 
|  | (gen_en_o & ~gen_op); | 
|  |  | 
|  | // operations fsm update precedence | 
|  | // when in invalid state, always update. | 
|  | // when in disabled state, always update unless a fault is encountered. | 
|  | assign op_update_sel = (op_ack | op_update) & invalid      ? KeyUpdateKmac : | 
|  | (op_ack | op_update) & op_fault_err ? KeyUpdateWipe : | 
|  | (op_ack | op_update) & disabled     ? KeyUpdateKmac : | 
|  | (op_ack | op_update) & op_err       ? KeyUpdateIdle : | 
|  | (op_ack | op_update)                ? KeyUpdateKmac : KeyUpdateIdle; | 
|  |  | 
|  |  | 
|  | /////////////////////////////// | 
|  | // Suppress kmac return data | 
|  | /////////////////////////////// | 
|  |  | 
|  | logic data_fsm_err; | 
|  | keymgr_data_en_state u_data_en ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .hw_sel_i(hw_sel_o), | 
|  | .adv_en_i(adv_en_o), | 
|  | .id_en_i(id_en_o), | 
|  | .gen_en_i(gen_en_o), | 
|  | .op_done_i(op_done_o), | 
|  | .op_start_i, | 
|  | .data_hw_en_o, | 
|  | .data_sw_en_o, | 
|  | .fsm_err_o(data_fsm_err) | 
|  | ); | 
|  |  | 
|  | ///////////////////////// | 
|  | // Cross-checks, errors and faults | 
|  | ///////////////////////// | 
|  |  | 
|  | logic vld_state_change_d, vld_state_change_q; | 
|  | assign vld_state_change_d = (state_d != state_q) & | 
|  | (state_d inside {StCtrlRootKey, | 
|  | StCtrlCreatorRootKey, | 
|  | StCtrlOwnerIntKey, | 
|  | StCtrlOwnerKey}); | 
|  |  | 
|  | // capture for cross check in following cycle | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) begin | 
|  | vld_state_change_q <= '0; | 
|  | end else begin | 
|  | vld_state_change_q <= vld_state_change_d; | 
|  | end | 
|  | end | 
|  |  | 
|  | // state cross check | 
|  | // if the state advanced, ensure that it was due to an advanced operation | 
|  | logic state_change_err; | 
|  | assign state_change_err = vld_state_change_q & !adv_op; | 
|  |  | 
|  | keymgr_err u_err ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .invalid_op_i(invalid_op), | 
|  | .disabled_i(disabled | (initialized & ~en_i)), | 
|  | .invalid_i(invalid), | 
|  | .kmac_input_invalid_i, | 
|  | .shadowed_update_err_i, | 
|  | .kmac_op_err_i, | 
|  | .invalid_kmac_out_i(invalid_kmac_out), | 
|  | .sideload_sel_err_i, | 
|  | .kmac_cmd_err_i, | 
|  | .kmac_fsm_err_i, | 
|  | .kmac_done_err_i, | 
|  | .regfile_intg_err_i, | 
|  | .shadowed_storage_err_i, | 
|  | .ctrl_fsm_err_i(state_intg_err_q | state_intg_err_d), | 
|  | .data_fsm_err_i(data_fsm_err), | 
|  | .op_fsm_err_i(op_fsm_err), | 
|  | .ecc_err_i(|ecc_errs), | 
|  | .state_change_err_i(state_change_err), | 
|  | .op_state_cmd_err_i(op_state_cmd_err), | 
|  | .cnt_err_i(cnt_err), | 
|  | .reseed_cnt_err_i, | 
|  | .sideload_fsm_err_i, | 
|  |  | 
|  | .op_update_i(op_update), | 
|  | .op_done_i(op_done_o), | 
|  |  | 
|  | .sync_err_o(sync_err), | 
|  | .async_err_o(), | 
|  | .sync_fault_o(sync_fault), | 
|  | .async_fault_o(async_fault), | 
|  | .error_o, | 
|  | .fault_o | 
|  | ); | 
|  |  | 
|  | /////////////////////////////// | 
|  | // Functions | 
|  | /////////////////////////////// | 
|  |  | 
|  | // unclear what this is supposed to be yet | 
|  | // right now just check to see if it not all 0's and not all 1's | 
|  | function automatic logic valid_data_chk (logic [KeyWidth-1:0] value); | 
|  |  | 
|  | return |value & ~&value; | 
|  |  | 
|  | endfunction // byte_mask | 
|  |  | 
|  | ///////////////////////////////// | 
|  | // Assertions | 
|  | ///////////////////////////////// | 
|  |  | 
|  | // This assertion will not work if fault_status ever takes on metafields such as | 
|  | // qe / re etc. | 
|  | `ASSERT_INIT(SameErrCnt_A, $bits(keymgr_reg2hw_fault_status_reg_t) == | 
|  | (SyncFaultLastIdx + AsyncFaultLastIdx)) | 
|  |  | 
|  | // stage select should always be Disable whenever it is not enabled | 
|  | `ASSERT(StageDisableSel_A, !en_i |-> stage_sel_o == Disable) | 
|  |  | 
|  | // Unless it is a legal command, only select disable | 
|  | `ASSERT(InitLegalCommands_A, op_start_i & en_i & state_q inside {StCtrlInit} & | 
|  | !(op_i inside {OpAdvance}) |-> stage_sel_o == Disable) | 
|  |  | 
|  | // All commands are legal, so select disable only if operation is disable | 
|  | `ASSERT(GeneralLegalCommands_A, op_start_i & en_i & | 
|  | state_q inside {StCtrlCreatorRootKey, StCtrlOwnerIntKey} & | 
|  | (op_i inside {OpDisable}) |-> stage_sel_o == Disable) | 
|  |  | 
|  | `ASSERT(OwnerLegalCommands_A, op_start_i & en_i & state_q inside {StCtrlOwnerKey} & | 
|  | (op_i inside {OpAdvance, OpDisable}) |-> stage_sel_o == Disable) | 
|  |  | 
|  | // load_key should not be high if there is no ongoing operation | 
|  | `ASSERT(LoadKey_A, key_o.valid |-> op_start_i) | 
|  |  | 
|  | // The count value should always be 0 when a transaction start | 
|  | `ASSERT(CntZero_A, $rose(op_start_i) |-> cnt == '0) | 
|  |  | 
|  | // Whenever a transaction completes, data_en must return to 0 on the next cycle | 
|  | `ASSERT(DataEnDis_A, op_start_i & op_done_o |=> ~data_hw_en_o && ~data_sw_en_o) | 
|  |  | 
|  | // Whenever data enable asserts, it must be the case that there was a generate or | 
|  | // id operation | 
|  | `ASSERT(DataEn_A, data_hw_en_o | data_sw_en_o |-> (id_en_o | gen_en_o) & ~adv_en_o) | 
|  |  | 
|  | // Check that the FSM is linear and does not contain any loops | 
|  | `ASSERT_FPV_LINEAR_FSM(SecCmCFILinear_A, state_q, state_e) | 
|  |  | 
|  | endmodule |