blob: 4724da80bce2c4b5290c4e6ed47c1aa8f8e53af5 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Main Life Cycle Controller FSM.
module lc_ctrl_fsm
import lc_ctrl_pkg::*;
import lc_ctrl_state_pkg::*;
#(// Random netlist constants
parameter lc_keymgr_div_t RndCnstLcKeymgrDivInvalid = LcKeymgrDivWidth'(0),
parameter lc_keymgr_div_t RndCnstLcKeymgrDivTestDevRma = LcKeymgrDivWidth'(1),
parameter lc_keymgr_div_t RndCnstLcKeymgrDivProduction = LcKeymgrDivWidth'(2)
) (
// This module is combinational, but we
// need the clock and reset for the assertions.
input clk_i,
input rst_ni,
// Initialization request from power manager.
input init_req_i,
output logic init_done_o,
output logic idle_o,
// Escalatio input
input esc_scrap_state_i,
input esc_wipe_secrets_i,
// Life cycle state vector from OTP.
input lc_state_valid_i,
input lc_state_e lc_state_i,
input lc_id_state_e lc_id_state_i,
input lc_cnt_e lc_cnt_i,
// Token input from OTP (these are all hash post-images).
input lc_token_t test_unlock_token_i,
input lc_token_t test_exit_token_i,
input lc_token_t rma_token_i,
// Transition trigger interface.
input trans_cmd_i,
input dec_lc_state_e trans_target_i,
// Decoded life cycle state for CSRs.
output dec_lc_state_e dec_lc_state_o,
output dec_lc_cnt_t dec_lc_cnt_o,
output dec_lc_id_state_e dec_lc_id_state_o,
// Token hashing interface
output logic token_hash_req_o,
input token_hash_ack_i,
input lc_token_t hashed_token_i,
// OTP programming interface
output logic otp_prog_req_o,
output lc_state_e otp_prog_lc_state_o,
output lc_cnt_e otp_prog_lc_cnt_o,
input otp_prog_ack_i,
input otp_prog_err_i,
// Error outputs going to CSRs
output logic trans_success_o,
output logic trans_cnt_oflw_error_o,
output logic trans_invalid_error_o,
output logic token_invalid_error_o,
output logic flash_rma_error_o,
output logic otp_prog_error_o,
output logic state_invalid_error_o,
// Life cycle broadcast outputs.
output lc_tx_t lc_dft_en_o,
output lc_tx_t lc_nvm_debug_en_o,
output lc_tx_t lc_hw_debug_en_o,
output lc_tx_t lc_cpu_en_o,
output lc_tx_t lc_creator_seed_sw_rw_en_o,
output lc_tx_t lc_owner_seed_sw_rw_en_o,
output lc_tx_t lc_iso_part_sw_rd_en_o,
output lc_tx_t lc_iso_part_sw_wr_en_o,
output lc_tx_t lc_seed_hw_rd_en_o,
output lc_tx_t lc_keymgr_en_o,
output lc_tx_t lc_escalate_en_o,
output lc_tx_t lc_check_byp_en_o,
// Request and feedback to/from clock manager and AST.
output lc_tx_t lc_clk_byp_req_o,
input lc_tx_t lc_clk_byp_ack_i,
// Request and feedback to/from flash controller
output lc_tx_t lc_flash_rma_req_o,
input lc_tx_t lc_flash_rma_ack_i,
// State group diversification value for keymgr
output lc_keymgr_div_t lc_keymgr_div_o
);
/////////////////////////////
// Synchronizers / Buffers //
/////////////////////////////
// We use multiple copies of these signals in the
// FSM checks below.
lc_tx_t [2:0] lc_clk_byp_ack;
lc_tx_t [1:0] lc_flash_rma_ack;
prim_lc_sync #(
.NumCopies(3)
) u_prim_lc_sync_clk_byp_ack (
.clk_i,
.rst_ni,
.lc_en_i(lc_clk_byp_ack_i),
.lc_en_o(lc_clk_byp_ack)
);
prim_lc_sync #(
.NumCopies(2)
) u_prim_lc_sync_flash_rma_ack (
.clk_i,
.rst_ni,
.lc_en_i(lc_flash_rma_ack_i),
.lc_en_o(lc_flash_rma_ack)
);
///////////////
// FSM Logic //
///////////////
fsm_state_e fsm_state_d, fsm_state_q;
// Continously feed in valid signal for LC state.
logic lc_state_valid_d, lc_state_valid_q;
assign lc_state_valid_d = lc_state_valid_i;
// Encoded state vector.
lc_state_e lc_state_d, lc_state_q, next_lc_state;
lc_cnt_e lc_cnt_d, lc_cnt_q, next_lc_cnt;
lc_id_state_e lc_id_state_d, lc_id_state_q;
// Working register for hashed token.
lc_token_t hashed_token_d, hashed_token_q;
// Feed the next lc state reg back to the programming interface of OTP.
assign otp_prog_lc_state_o = next_lc_state;
assign otp_prog_lc_cnt_o = next_lc_cnt;
// Conditional LC signal outputs
lc_tx_t lc_clk_byp_req, lc_flash_rma_req, lc_check_byp_en;
`ASSERT_KNOWN(LcStateKnown_A, lc_state_q )
`ASSERT_KNOWN(LcCntKnown_A, lc_cnt_q )
`ASSERT_KNOWN(LcIdStateKnown_A, lc_id_state_q)
`ASSERT_KNOWN(FsmStateKnown_A, fsm_state_q )
// Hashed token to compare against.
lc_token_t hashed_token_mux;
always_comb begin : p_fsm
// FSM default state assignments.
fsm_state_d = fsm_state_q;
lc_state_d = lc_state_q;
lc_cnt_d = lc_cnt_q;
lc_id_state_d = lc_id_state_q;
// Token hashing.
token_hash_req_o = 1'b0;
hashed_token_d = hashed_token_q;
// OTP Interface
otp_prog_req_o = 1'b0;
// Defaults for status/error signals.
token_invalid_error_o = 1'b0;
otp_prog_error_o = 1'b0;
flash_rma_error_o = 1'b0;
trans_success_o = 1'b0;
// Status indication going to power manager.
init_done_o = 1'b1;
idle_o = 1'b1;
// These signals remain asserted once set to On.
// Note that the remaining life cycle signals are decoded in
// the lc_ctrl_signal_decode submodule.
lc_clk_byp_req = lc_clk_byp_req_o;
lc_flash_rma_req = lc_flash_rma_req_o;
lc_check_byp_en = lc_check_byp_en_o;
unique case (fsm_state_q)
///////////////////////////////////////////////////////////////////
// Wait here until OTP has initialized and the
// power manager sends an initialization request.
ResetSt: begin
init_done_o = 1'b0;
lc_clk_byp_req = Off;
lc_flash_rma_req = Off;
lc_check_byp_en = Off;
if (init_req_i && lc_state_valid_q) begin
fsm_state_d = IdleSt;
// Fetch LC state vector from OTP.
lc_state_d = lc_state_i;
lc_cnt_d = lc_cnt_i;
lc_id_state_d = lc_id_state_i;
end
end
///////////////////////////////////////////////////////////////////
// Idle state where life cycle control signals are broadcast.
// Note that the life cycle signals are decoded and broadcast
// in the lc_ctrl_signal_decode submodule.
IdleSt: begin
idle_o = 1'b1;
// Continuously fetch LC state vector from OTP.
// The state is locked in once a transition is started.
lc_state_d = lc_state_i;
lc_cnt_d = lc_cnt_i;
lc_id_state_d = lc_id_state_i;
// Initiate a transition. This will first increment the
// life cycle counter before hashing and checking the token.
if (trans_cmd_i) begin
fsm_state_d = ClkMuxSt;
end
end
///////////////////////////////////////////////////////////////////
// Clock mux state. If in RAW or TEST_LOCKED the bypass request is
// asserted and we have to wait until the clock mux and clock manager
// have switched the mux and the clock divider. Also, we disable the
// life cycle partition checks at this point since we are going to
// alter the contents in the OTP memory array, which could lead to
// spurious escalations.
ClkMuxSt: begin
lc_check_byp_en = On;
if (lc_state_q inside {LcStRaw,
LcStTestLocked0,
LcStTestLocked1,
LcStTestLocked2}) begin
lc_clk_byp_req = On;
if (lc_clk_byp_ack[0] == On) begin
fsm_state_d = CntIncrSt;
end
end else begin
fsm_state_d = CntIncrSt;
end
end
///////////////////////////////////////////////////////////////////
// This increments the life cycle counter state.
CntIncrSt: begin
// If the counter has reached the maximum, bail out.
if (trans_cnt_oflw_error_o) begin
fsm_state_d = PostTransSt;
end else begin
fsm_state_d = CntProgSt;
end
end
///////////////////////////////////////////////////////////////////
// This programs the life cycle counter state.
CntProgSt: begin
otp_prog_req_o = 1'b1;
// If the clock mux has been steered, double check that this is still the case.
// Otherwise abort the transition operation.
if (lc_clk_byp_req != lc_clk_byp_ack[1]) begin
fsm_state_d = PostTransSt;
otp_prog_error_o = 1'b1;
end
// Check return value and abort if there
// was an error.
if (otp_prog_ack_i) begin
if (otp_prog_err_i) begin
fsm_state_d = PostTransSt;
otp_prog_error_o = 1'b1;
end else begin
fsm_state_d = TransCheckSt;
end
end
end
///////////////////////////////////////////////////////////////////
// First transition valid check. This will be repeated several
// times below.
TransCheckSt: begin
if (trans_invalid_error_o) begin
fsm_state_d = PostTransSt;
end else begin
fsm_state_d = TokenHashSt;
end
end
///////////////////////////////////////////////////////////////////
// Hash and compare the token, no matter whether this transition
// is conditional or not. Unconditional transitions just use a known
// all-zero token value. That way, we always compare a hashed token
// and guarantee that no other control flow path exists that could
// bypass the token check.
TokenHashSt: begin
token_hash_req_o = 1'b1;
if (token_hash_ack_i) begin
// This is the first comparison.
// The token is registered and then
// compared two more times further below.
hashed_token_d = hashed_token_i;
if (hashed_token_i == hashed_token_mux) begin
fsm_state_d = FlashRmaSt;
end else begin
fsm_state_d = PostTransSt;
token_invalid_error_o = 1'b1;
end
end
end
///////////////////////////////////////////////////////////////////
// Flash RMA state. Note that we check the flash response again
// two times later below.
FlashRmaSt: begin
if (trans_target_i == DecLcStRma) begin
lc_flash_rma_req = On;
if (lc_flash_rma_ack[0] == On) begin
fsm_state_d = TokenCheck0St;
end
end else begin
fsm_state_d = TokenCheck0St;
end
end
///////////////////////////////////////////////////////////////////
// Check again two times whether this transition and the hashed
// token are valid. Also check again whether the flash RMA
// response is valid.
TokenCheck0St,
TokenCheck1St: begin
if (trans_invalid_error_o) begin
fsm_state_d = PostTransSt;
end else begin
// If any of these RMA are conditions are true,
// all of them must be true at the same time.
if ((trans_target_i != DecLcStRma &&
lc_flash_rma_req_o == Off &&
lc_flash_rma_ack[1] == Off) ||
(trans_target_i == DecLcStRma &&
lc_flash_rma_req_o == On &&
lc_flash_rma_ack[1] == On)) begin
if (hashed_token_i == hashed_token_mux) begin
if (fsm_state_q == TokenCheck1St) begin
// This is the only way we can get into the
// programming state.
fsm_state_d = TransProgSt;
end else begin
fsm_state_d = TokenCheck1St;
end
end else begin
fsm_state_d = PostTransSt;
token_invalid_error_o = 1'b1;
end
// The flash RMA process failed.
end else begin
fsm_state_d = PostTransSt;
flash_rma_error_o = 1'b1;
end
end
end
///////////////////////////////////////////////////////////////////
// Initiate OTP transaction. Note that the concurrent
// LC state check is continuously checking whether the
// new LC state remains valid. Once the ack returns we are
// done with the transition and can go into the terminal PosTransSt.
TransProgSt: begin
otp_prog_req_o = 1'b1;
// If the clock mux has been steered, double check that this is still the case.
// Otherwise abort the transition operation.
if (lc_clk_byp_req != lc_clk_byp_ack[2]) begin
fsm_state_d = PostTransSt;
otp_prog_error_o = 1'b1;
end
if (otp_prog_ack_i) begin
fsm_state_d = PostTransSt;
otp_prog_error_o = otp_prog_err_i;
trans_success_o = ~otp_prog_err_i;
end
end
///////////////////////////////////////////////////////////////////
// Terminal error states.
PostTransSt,
EscalateSt,
InvalidSt: ;
///////////////////////////////////////////////////////////////////
// Go to terminal error state if we get here.
default: fsm_state_d = InvalidSt;
///////////////////////////////////////////////////////////////////
endcase
// If at any time the life cycle state encoding is not valid,
// we jump into the terminal error state right away.
if (state_invalid_error_o) begin
fsm_state_d = InvalidSt;
end else if (esc_scrap_state_i) begin
fsm_state_d = EscalateSt;
end
end
/////////////////
// State Flops //
/////////////////
// This primitive is used to place a size-only constraint on the
// flops in order to prevent FSM state encoding optimizations.
logic [FsmStateWidth-1:0] fsm_state_raw_q;
assign fsm_state_q = fsm_state_e'(fsm_state_raw_q);
prim_flop #(
.Width(FsmStateWidth),
.ResetValue(FsmStateWidth'(ResetSt))
) u_state_regs (
.clk_i,
.rst_ni,
.d_i ( fsm_state_d ),
.q_o ( fsm_state_raw_q )
);
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
if (!rst_ni) begin
lc_state_q <= LcStScrap;
lc_cnt_q <= LcCnt16;
lc_id_state_q <= LcIdPersonalized;
lc_state_valid_q <= 1'b0;
hashed_token_q <= {LcTokenWidth{1'b1}};
end else begin
lc_state_q <= lc_state_d;
lc_cnt_q <= lc_cnt_d;
lc_id_state_q <= lc_id_state_d;
lc_state_valid_q <= lc_state_valid_d;
hashed_token_q <= hashed_token_d;
end
end
///////////////
// Token mux //
///////////////
// This indexes the correct token, based on the transition arc.
// Note that we always perform a token comparison, even in case of
// unconditional transitions. In the case of unconditional tokens
// we just pass an all-zero constant through the hashing function.
lc_token_t [2**TokenIdxWidth-1:0] hashed_tokens;
logic [TokenIdxWidth-1:0] token_idx;
always_comb begin : p_token_assign
hashed_tokens = '0;
hashed_tokens[ZeroTokenIdx] = AllZeroTokenHashed;
hashed_tokens[RawUnlockTokenIdx] = RndCnstRawUnlockTokenHashed;
hashed_tokens[TestUnlockTokenIdx] = test_unlock_token_i;
hashed_tokens[TestExitTokenIdx] = test_exit_token_i;
hashed_tokens[RmaTokenIdx] = rma_token_i;
hashed_tokens[InvalidTokenIdx] = '0;
end
assign token_idx = TransTokenIdxMatrix[dec_lc_state_o][trans_target_i];
assign hashed_token_mux = hashed_tokens[token_idx];
////////////////////////////////////////////////////////////////////
// Decoding and transition logic for redundantly encoded LC state //
////////////////////////////////////////////////////////////////////
// This decodes the state into a format that can be exposed in the CSRs,
// and flags any errors in the state encoding. Errors will move the
// main FSM into INVALID right away.
lc_ctrl_state_decode u_lc_ctrl_state_decode (
.lc_state_valid_i ( lc_state_valid_q ),
.lc_state_i ( lc_state_q ),
.lc_id_state_i ( lc_id_state_q ),
.lc_cnt_i ( lc_cnt_q ),
.fsm_state_i ( fsm_state_q ),
.dec_lc_state_o,
.dec_lc_id_state_o,
.dec_lc_cnt_o,
.state_invalid_error_o
);
// LC transition checker logic and next state generation.
lc_ctrl_state_transition u_lc_ctrl_state_transition (
.lc_state_i ( lc_state_q ),
.lc_cnt_i ( lc_cnt_q ),
.dec_lc_state_i ( dec_lc_state_o ),
.fsm_state_i ( fsm_state_q ),
.trans_target_i,
.next_lc_state_o ( next_lc_state ),
.next_lc_cnt_o ( next_lc_cnt ),
.trans_cnt_oflw_error_o,
.trans_invalid_error_o
);
// LC signal decoder and broadcasting logic.
lc_ctrl_signal_decode #(
.RndCnstLcKeymgrDivInvalid ( RndCnstLcKeymgrDivInvalid ),
.RndCnstLcKeymgrDivTestDevRma ( RndCnstLcKeymgrDivTestDevRma ),
.RndCnstLcKeymgrDivProduction ( RndCnstLcKeymgrDivProduction )
) u_lc_ctrl_signal_decode (
.clk_i,
.rst_ni,
.lc_state_valid_i ( lc_state_valid_q ),
.lc_state_i ( lc_state_q ),
.lc_id_state_i ( lc_id_state_q ),
.fsm_state_i ( fsm_state_q ),
.esc_wipe_secrets_i,
.lc_dft_en_o,
.lc_nvm_debug_en_o,
.lc_hw_debug_en_o,
.lc_cpu_en_o,
.lc_creator_seed_sw_rw_en_o,
.lc_owner_seed_sw_rw_en_o,
.lc_iso_part_sw_rd_en_o,
.lc_iso_part_sw_wr_en_o,
.lc_seed_hw_rd_en_o,
.lc_keymgr_en_o,
.lc_escalate_en_o,
.lc_keymgr_div_o
);
// Conditional signals set by main FSM.
prim_lc_sender u_prim_lc_sender_clk_byp_req (
.clk_i,
.rst_ni,
.lc_en_i(lc_clk_byp_req),
.lc_en_o(lc_clk_byp_req_o)
);
prim_lc_sender u_prim_lc_sender_flash_rma_req (
.clk_i,
.rst_ni,
.lc_en_i(lc_flash_rma_req),
.lc_en_o(lc_flash_rma_req_o)
);
prim_lc_sender u_prim_lc_sender_check_byp_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_check_byp_en),
.lc_en_o(lc_check_byp_en_o)
);
////////////////
// Assertions //
////////////////
`ASSERT(ClkBypStaysOnOnceAsserted_A,
lc_escalate_en_o == On
|=>
lc_escalate_en_o == On)
`ASSERT(FlashRmaStaysOnOnceAsserted_A,
lc_flash_rma_req_o == On
|=>
lc_flash_rma_req_o == On)
endmodule : lc_ctrl_fsm