[lc_ctrl] Add first cut implementation
Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/lc_ctrl/lc_ctrl.core b/hw/ip/lc_ctrl/lc_ctrl.core
new file mode 100644
index 0000000..47193c8
--- /dev/null
+++ b/hw/ip/lc_ctrl/lc_ctrl.core
@@ -0,0 +1,71 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:ip:lc_ctrl:0.1"
+description: "LC Controller"
+
+filesets:
+ files_rtl:
+ depend:
+ - lowrisc:prim:all
+ - lowrisc:prim:lc_sync
+ - lowrisc:ip:lc_ctrl_pkg
+ - lowrisc:ip:tlul
+ - lowrisc:ip:pwrmgr_pkg
+ - lowrisc:ip:otp_ctrl_pkg
+ files:
+ - rtl/lc_ctrl_reg_top.sv
+ - rtl/lc_ctrl_state_decode.sv
+ - rtl/lc_ctrl_state_transition.sv
+ - rtl/lc_ctrl_signal_decode.sv
+ - rtl/lc_ctrl_fsm.sv
+ - rtl/lc_ctrl.sv
+ file_type: systemVerilogSource
+
+ files_verilator_waiver:
+ depend:
+ # common waivers
+ - lowrisc:lint:common
+ - lowrisc:lint:comportable
+ files:
+ - lint/lc_ctrl.vlt
+ file_type: vlt
+
+ files_ascentlint_waiver:
+ depend:
+ # common waivers
+ - lowrisc:lint:common
+ - lowrisc:lint:comportable
+ files:
+ - lint/lc_ctrl.waiver
+ file_type: waiver
+
+parameters:
+ SYNTHESIS:
+ datatype: bool
+ paramtype: vlogdefine
+
+
+targets:
+ default: &default_target
+ filesets:
+ - tool_verilator ? (files_verilator_waiver)
+ - tool_ascentlint ? (files_ascentlint_waiver)
+ - files_rtl
+ toplevel: lc_ctrl
+
+ lint:
+ <<: *default_target
+ default_tool: verilator
+ parameters:
+ - SYNTHESIS=true
+ tools:
+ ascentlint:
+ ascentlint_options:
+ - "-wait_license"
+ - "-stop_on_error"
+ verilator:
+ mode: lint-only
+ verilator_options:
+ - "-Wall"
diff --git a/hw/ip/lc_ctrl/lc_ctrl_pkg.core b/hw/ip/lc_ctrl/lc_ctrl_pkg.core
index 09d879f..281aa3e 100644
--- a/hw/ip/lc_ctrl/lc_ctrl_pkg.core
+++ b/hw/ip/lc_ctrl/lc_ctrl_pkg.core
@@ -8,7 +8,6 @@
files_rtl:
depend:
- lowrisc:tlul:headers
-
files:
- rtl/lc_ctrl_reg_pkg.sv
- rtl/lc_ctrl_pkg.sv
diff --git a/hw/ip/lc_ctrl/lint/lc_ctrl.vlt b/hw/ip/lc_ctrl/lint/lc_ctrl.vlt
new file mode 100644
index 0000000..ddeca7e
--- /dev/null
+++ b/hw/ip/lc_ctrl/lint/lc_ctrl.vlt
@@ -0,0 +1,6 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// waiver file for LC controller
+
diff --git a/hw/ip/lc_ctrl/lint/lc_ctrl.waiver b/hw/ip/lc_ctrl/lint/lc_ctrl.waiver
new file mode 100644
index 0000000..cbd0a96
--- /dev/null
+++ b/hw/ip/lc_ctrl/lint/lc_ctrl.waiver
@@ -0,0 +1,6 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# waiver file for LC controller
+
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl.sv
new file mode 100644
index 0000000..3383cba
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl.sv
@@ -0,0 +1,446 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Life cycle controller top.
+//
+
+`include "prim_assert.sv"
+
+module lc_ctrl
+ import lc_ctrl_pkg::*;
+ import lc_ctrl_reg_pkg::*;
+#(
+ // Enable asynchronous transitions on alerts.
+ parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
+) (
+ input clk_i,
+ input rst_ni,
+ // Bus Interface (device)
+ input tlul_pkg::tl_h2d_t tl_i,
+ output tlul_pkg::tl_d2h_t tl_o,
+ // Alert outputs.
+ input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
+ output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,
+ // Escalation inputs (severity 1 and 2).
+ // These need not be synchronized since the alert handler is
+ // in the same clock domain as the LC controller.
+ input prim_esc_pkg::esc_rx_t esc_wipe_secrets_tx_i,
+ output prim_esc_pkg::esc_tx_t esc_wipe_secrets_rx_o,
+ input prim_esc_pkg::esc_rx_t esc_scrap_state_tx_i,
+ output prim_esc_pkg::esc_tx_t esc_scrap_state_rx_o,
+ // Power manager interface (inputs are synced to lifecycle clock domain).
+ input pwrmgr_pkg::pwr_lc_req_t pwr_lc_i,
+ output pwrmgr_pkg::pwr_lc_rsp_t pwr_lc_o,
+ // Life cycle transition command interface.
+ // No sync required since LC and OTP are in the same clock domain.
+ output otp_ctrl_pkg::lc_otp_program_req_t lc_otp_program_o,
+ input otp_ctrl_pkg::lc_otp_program_rsp_t lc_otp_program_i,
+ // Life cycle hashing interface for raw unlock
+ // No sync required since LC and OTP are in the same clock domain.
+ output otp_ctrl_pkg::lc_otp_token_req_t lc_otp_token_o,
+ input otp_ctrl_pkg::lc_otp_token_rsp_t lc_otp_token_i,
+ // OTP broadcast outputs
+ // No sync required since LC and OTP are in the same clock domain.
+ input otp_ctrl_pkg::otp_lc_data_t otp_lc_data_i,
+ // Life cycle broadcast outputs (all of them are registered).
+ 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_provision_wr_en_o,
+ output lc_tx_t lc_provision_rd_en_o,
+ output lc_tx_t lc_keymgr_en_o,
+ output lc_tx_t lc_escalate_en_o,
+ // Request and feedback to/from clock manager and AST.
+ // The ack is synced to the lc clock domain using prim_lc_sync.
+ 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.
+ // The ack is synced to the lc clock domain using prim_lc_sync.
+ output lc_flash_rma_seed_t lc_flash_rma_seed_o,
+ output lc_tx_t lc_flash_rma_req_o,
+ input lc_tx_t lc_flash_rma_ack_i
+);
+
+ ////////////////////////
+ // Integration Checks //
+ ////////////////////////
+
+ // Check that the CSR parameters correspond with the ones used in the design.
+ `ASSERT_INIT(DecLcStateWidthCheck_A, CsrLcStateWidth == DecLcStateWidth)
+ `ASSERT_INIT(DecLcCountWidthCheck_A, CsrLcCountWidth == DecLcCountWidth)
+ `ASSERT_INIT(DecLcIdStateWidthCheck_A, CsrLcIdStateWidth == DecLcIdStateWidth)
+ `ASSERT_INIT(NumTokenWordsCheck_A, NumTokenWords == LcTokenWidth/32)
+
+ /////////////
+ // Regfile //
+ /////////////
+
+ lc_ctrl_reg_pkg::lc_ctrl_reg2hw_t reg2hw;
+ lc_ctrl_reg_pkg::lc_ctrl_hw2reg_t hw2reg;
+
+ lc_ctrl_reg_top u_reg (
+ .clk_i,
+ .rst_ni,
+ .tl_i,
+ .tl_o,
+ .reg2hw ( reg2hw ),
+ .hw2reg ( hw2reg ),
+ .devmode_i ( 1'b1 )
+ );
+
+ ////////////////////
+ // Life Cycle TAP //
+ ////////////////////
+
+ tlul_pkg::tl_h2d_t tap_tl_h2d;
+ tlul_pkg::tl_d2h_t tap_tl_d2h, unused_tap_tl_d2h;
+ lc_ctrl_reg_pkg::lc_ctrl_reg2hw_t tap_reg2hw;
+ lc_ctrl_reg_pkg::lc_ctrl_hw2reg_t tap_hw2reg;
+
+ lc_ctrl_reg_top u_reg_tap (
+ .clk_i,
+ .rst_ni,
+ .tl_i ( tap_tl_h2d ),
+ .tl_o ( tap_tl_d2h ),
+ .reg2hw ( tap_reg2hw ),
+ .hw2reg ( tap_hw2reg ),
+ .devmode_i ( 1'b1 )
+ );
+
+ // TODO: implement TAP
+ assign tap_tl_h2d = '0;
+ assign unused_tap_tl_d2h = tap_tl_d2h;
+
+ ///////////////////////////////////////
+ // Transition Interface and HW Mutex //
+ ///////////////////////////////////////
+
+ // TODO: expose device ID
+ // TODO: expose other info to expose via CSRs / TAP?
+
+ // All registers are HWext
+ logic trans_success_d, trans_success_q;
+ logic trans_cnt_oflw_error_d, trans_cnt_oflw_error_q;
+ logic trans_invalid_error_d, trans_invalid_error_q;
+ logic token_invalid_error_d, token_invalid_error_q;
+ logic flash_rma_error_d, flash_rma_error_q;
+ logic otp_prog_error_d, otp_prog_error_q;
+ logic state_invalid_error_d, state_invalid_error_q;
+ logic sw_claim_transition_if_d, sw_claim_transition_if_q;
+ logic tap_claim_transition_if_d, tap_claim_transition_if_q;
+ logic transition_cmd;
+ lc_token_t transition_token_d, transition_token_q;
+ dec_lc_state_e transition_target_d, transition_target_q;
+ // No need to register these.
+ dec_lc_state_e dec_lc_state;
+ dec_lc_cnt_t dec_lc_cnt;
+ dec_lc_id_state_e dec_lc_id_state;
+
+ logic lc_idle_d;
+
+ always_comb begin : p_csr_assign_outputs
+ hw2reg = '0;
+ hw2reg.status.ready = lc_idle_d;
+ hw2reg.status.transition_successful = trans_success_q;
+ hw2reg.status.transition_count_error = trans_cnt_oflw_error_q;
+ hw2reg.status.transition_error = trans_invalid_error_q;
+ hw2reg.status.token_error = token_invalid_error_q;
+ hw2reg.status.flash_rma_error = flash_rma_error_q;
+ hw2reg.status.otp_error = otp_prog_error_q;
+ hw2reg.status.state_error = state_invalid_error_q;
+ hw2reg.transition_regwen = lc_idle_d;
+ hw2reg.lc_state = dec_lc_state;
+ hw2reg.lc_transition_cnt = dec_lc_cnt;
+ hw2reg.lc_id_state = dec_lc_id_state;
+ // The assignments above are identical for the TAP.
+ tap_hw2reg = hw2reg;
+
+ // Assignments gated by mutex.
+ hw2reg.claim_transition_if = sw_claim_transition_if_q;
+ if (sw_claim_transition_if_q) begin
+ hw2reg.transition_token = transition_token_q;
+ hw2reg.transition_target = transition_target_q;
+ end
+
+ tap_hw2reg.claim_transition_if = tap_claim_transition_if_q;
+ if (tap_claim_transition_if_q) begin
+ tap_hw2reg.transition_token = transition_token_q;
+ tap_hw2reg.transition_target = transition_target_q;
+ end
+ end
+
+ always_comb begin : p_csr_assign_inputs
+ sw_claim_transition_if_d = sw_claim_transition_if_q;
+ tap_claim_transition_if_d = tap_claim_transition_if_q;
+ transition_token_d = transition_token_q;
+ transition_target_d = transition_target_q;
+ transition_cmd = 1'b0;
+
+ // SW mutex claim.
+ if (!tap_claim_transition_if_q &&
+ reg2hw.claim_transition_if.qe) begin
+ sw_claim_transition_if_d = reg2hw.claim_transition_if.q;
+ end
+ // TAP mutex claim. This has prio over SW above.
+ if (!sw_claim_transition_if_q &&
+ tap_reg2hw.claim_transition_if.qe) begin
+ tap_claim_transition_if_d = tap_reg2hw.claim_transition_if.q;
+ end
+
+ // The idle signal serves as the REGWEN in this case.
+ if (lc_idle_d) begin
+ if (tap_claim_transition_if_q) begin
+ transition_cmd = tap_reg2hw.transition_cmd.q &
+ tap_reg2hw.transition_cmd.qe;
+
+ for (int k = 0; k < LcTokenWidth/32; k++) begin
+ if (tap_reg2hw.transition_token[k].qe) begin
+ transition_token_d[k*32 +: 32] = tap_reg2hw.transition_token[k].q;
+ end
+ end
+
+ if (tap_reg2hw.transition_target.qe) begin
+ transition_target_d = tap_reg2hw.transition_target.q;
+ end
+ end else if (sw_claim_transition_if_q) begin
+ transition_cmd = reg2hw.transition_cmd.q &
+ reg2hw.transition_cmd.qe;
+
+ for (int k = 0; k < LcTokenWidth/32; k++) begin
+ if (reg2hw.transition_token[k].qe) begin
+ transition_token_d[k*32 +: 32] = reg2hw.transition_token[k].q;
+ end
+ end
+
+ if (reg2hw.transition_target.qe) begin
+ transition_target_d = reg2hw.transition_target.q;
+ end
+ end
+ end
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_csrs
+ if (!rst_ni) begin
+ trans_success_q <= 1'b0;
+ trans_invalid_error_q <= 1'b0;
+ token_invalid_error_q <= 1'b0;
+ flash_rma_error_q <= 1'b0;
+ otp_prog_error_q <= 1'b0;
+ state_invalid_error_q <= 1'b0;
+ sw_claim_transition_if_q <= '0;
+ tap_claim_transition_if_q <= '0;
+ transition_token_q <= '0;
+ transition_target_q <= '0;
+ end else begin
+ // All status and error bits are terminal and require a reset cycle.
+ trans_success_q <= trans_success_d | trans_success_q;
+ trans_cnt_oflw_error_q <= trans_cnt_oflw_error_d | trans_cnt_oflw_error_q;
+ trans_invalid_error_q <= trans_invalid_error_d | trans_invalid_error_q;
+ token_invalid_error_q <= token_invalid_error_d | token_invalid_error_q;
+ flash_rma_error_q <= flash_rma_error_d | flash_rma_error_q;
+ otp_prog_error_q <= otp_prog_error_d | otp_prog_error_q;
+ state_invalid_error_q <= state_invalid_error_d | state_invalid_error_q;
+ // Other regs, gated by mutex further below.
+ sw_claim_transition_if_q <= sw_claim_transition_if_d;
+ tap_claim_transition_if_q <= tap_claim_transition_if_d;
+ transition_token_q <= transition_token_d;
+ transition_target_q <= transition_target_d;
+ end
+ end
+
+ //////////////////
+ // Alert Sender //
+ //////////////////
+
+ logic [NumAlerts-1:0] alerts;
+ logic [NumAlerts-1:0] alert_test;
+ logic [NumAlerts-1:0] tap_alert_test;
+
+ assign alerts = {
+ otp_prog_error_q,
+ state_invalid_error_q
+ };
+
+ assign alert_test = {
+ reg2hw.alert_test.lc_programming_failure.q &
+ reg2hw.alert_test.lc_programming_failure.qe,
+ reg2hw.alert_test.lc_state_failure.q &
+ reg2hw.alert_test.lc_state_failure.qe
+ };
+
+ assign tap_alert_test = {
+ tap_reg2hw.alert_test.lc_programming_failure.q &
+ tap_reg2hw.alert_test.lc_programming_failure.qe,
+ tap_reg2hw.alert_test.lc_state_failure.q &
+ tap_reg2hw.alert_test.lc_state_failure.qe
+ };
+
+ for (genvar k = 0; k < NumAlerts; k++) begin : gen_alert_tx
+ prim_alert_sender #(
+ .AsyncOn(AlertAsyncOn[k])
+ ) u_prim_alert_sender (
+ .clk_i,
+ .rst_ni,
+ .alert_req_i ( alerts[k] |
+ alert_test[k] |
+ tap_alert_test[k] ),
+ .alert_ack_o ( ),
+ .alert_rx_i ( alert_rx_i[k] ),
+ .alert_tx_o ( alert_tx_o[k] )
+ );
+ end
+
+ //////////////////////////
+ // Escalation Receivers //
+ //////////////////////////
+
+ // This escalation action triggers the
+ // lc_escalate_en life cycle control signal.
+ logic esc_wipe_secrets;
+ prim_esc_receiver u_prim_esc_receiver1 (
+ .clk_i,
+ .rst_ni,
+ .esc_en_o (esc_wipe_secrets),
+ .esc_rx_o (esc_wipe_secrets_rx_o),
+ .esc_tx_i (esc_wipe_secrets_tx_i)
+ );
+
+ // This escalation action moves the life cycle
+ // state into a temporary "SCRAP" state named "ESCALATE".
+ logic esc_scrap_state;
+ prim_esc_receiver u_prim_esc_receiver2 (
+ .clk_i,
+ .rst_ni,
+ .esc_en_o (esc_scrap_state),
+ .esc_rx_o (esc_scrap_state_rx_o),
+ .esc_tx_i (esc_scrap_state_tx_i)
+ );
+
+ ////////////////////////////
+ // Synchronization of IOs //
+ ////////////////////////////
+
+ // Signals going to and coming from power manager.
+ logic lc_init;
+ prim_flop_2sync #(
+ .Width(1)
+ ) u_prim_flop_2sync_init (
+ .clk_i,
+ .rst_ni,
+ .d_i(pwr_lc_i.lc_init),
+ .q_o(lc_init)
+ );
+
+ logic lc_done_d, lc_done_q;
+ logic lc_idle_q;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_sync_regs
+ if (!rst_ni) begin
+ lc_done_q <= 1'b0;
+ lc_idle_q <= 1'b0;
+ end else begin
+ lc_done_q <= lc_done_d;
+ lc_idle_q <= lc_idle_d;
+ end
+ end
+
+ assign pwr_lc_o.lc_done = lc_done_q;
+ assign pwr_lc_o.lc_idle = lc_idle_q;
+
+ // Life cycle ACK signals.
+ lc_tx_t lc_clk_byp_ack;
+ prim_lc_sync 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)
+ );
+
+ lc_tx_t lc_flash_rma_ack;
+ prim_lc_sync 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)
+ );
+
+ ////////////
+ // LC FSM //
+ ////////////
+
+ assign lc_otp_token_o.token_input = transition_token_q;
+ assign lc_flash_rma_seed_o = transition_token_q[RmaSeedWidth-1:0];
+
+ lc_ctrl_fsm u_lc_ctrl_fsm (
+ .clk_i,
+ .rst_ni,
+ .init_req_i ( lc_init ),
+ .init_done_o ( lc_done_d ),
+ .idle_o ( lc_idle_d ),
+ .esc_scrap_state_i ( esc_scrap_state ),
+ .esc_wipe_secrets_i ( esc_wipe_secrets ),
+ .lc_state_valid_i ( otp_lc_data_i.valid ),
+ .lc_state_i ( otp_lc_data_i.state ),
+ .lc_id_state_i ( otp_lc_data_i.id_state ),
+ .lc_cnt_i ( otp_lc_data_i.count ),
+ .test_unlock_token_i ( otp_lc_data_i.test_unlock_token ),
+ .test_exit_token_i ( otp_lc_data_i.test_exit_token ),
+ .rma_token_i ( otp_lc_data_i.rma_token ),
+ .trans_cmd_i ( transition_cmd ),
+ .trans_target_i ( transition_target_q ),
+ .dec_lc_state_o ( dec_lc_state ),
+ .dec_lc_cnt_o ( dec_lc_cnt ),
+ .dec_lc_id_state_o ( dec_lc_id_state ),
+ .token_hash_req_o ( lc_otp_token_o.req ),
+ .token_hash_ack_i ( lc_otp_token_i.ack ),
+ .hashed_token_i ( lc_otp_token_i.hashed_token ),
+ .otp_prog_req_o ( lc_otp_program_o.req ),
+ .otp_prog_lc_state_o ( lc_otp_program_o.state ),
+ .otp_prog_lc_cnt_o ( lc_otp_program_o.count ),
+ .otp_prog_ack_i ( lc_otp_program_i.ack ),
+ .otp_prog_err_i ( lc_otp_program_i.err ),
+ .trans_success_o ( trans_success_d ),
+ .trans_cnt_oflw_error_o ( trans_cnt_oflw_error_d ),
+ .trans_invalid_error_o ( trans_invalid_error_d ),
+ .token_invalid_error_o ( token_invalid_error_d ),
+ .flash_rma_error_o ( flash_rma_error_d ),
+ .otp_prog_error_o ( otp_prog_error_d ),
+ .state_invalid_error_o ( state_invalid_error_d ),
+ .lc_dft_en_o,
+ .lc_nvm_debug_en_o,
+ .lc_hw_debug_en_o,
+ .lc_cpu_en_o,
+ .lc_provision_wr_en_o,
+ .lc_provision_rd_en_o,
+ .lc_keymgr_en_o,
+ .lc_escalate_en_o,
+ .lc_clk_byp_req_o,
+ .lc_clk_byp_ack_i ( lc_clk_byp_ack ),
+ .lc_flash_rma_req_o,
+ .lc_flash_rma_ack_i ( lc_flash_rma_ack )
+ );
+
+ ////////////////
+ // Assertions //
+ ////////////////
+
+ `ASSERT_KNOWN(TlOKnown, tl_o )
+ `ASSERT_KNOWN(AlertTxKnown_A, alert_tx_o )
+ `ASSERT_KNOWN(PwrLcKnown_A, pwr_lc_o )
+ `ASSERT_KNOWN(LcOtpProgramKnwon_A, lc_otp_program_o )
+ `ASSERT_KNOWN(LcOtpTokenKnown_A, lc_otp_token_o )
+ `ASSERT_KNOWN(LcDftEnKnown_A, lc_dft_en_o )
+ `ASSERT_KNOWN(LcNvmDebugEnKnown_A, lc_nvm_debug_en_o )
+ `ASSERT_KNOWN(LcHwDebugEnKnown_A, lc_hw_debug_en_o )
+ `ASSERT_KNOWN(LcCpuEnKnown_A, lc_cpu_en_o )
+ `ASSERT_KNOWN(LcProvisionWrEnKnown_A, lc_provision_wr_en_o )
+ `ASSERT_KNOWN(LcProvisionRdEnKnown_A, lc_provision_rd_en_o )
+ `ASSERT_KNOWN(LcKeymgrEnKnown_A, lc_keymgr_en_o )
+ `ASSERT_KNOWN(LcEscalateEnKnown_A, lc_escalate_en_o )
+ `ASSERT_KNOWN(LcClkBypReqKnown_A, lc_clk_byp_req_o )
+ `ASSERT_KNOWN(LcFlashRmaSeedKnown_A, lc_flash_rma_seed_o )
+ `ASSERT_KNOWN(LcFlashRmaReqKnown_A, lc_flash_rma_req_o )
+
+endmodule : lc_ctrl
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_fsm.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_fsm.sv
new file mode 100644
index 0000000..2fe44c7
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_fsm.sv
@@ -0,0 +1,447 @@
+// 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::*;
+(
+ // 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.
+ 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_provision_wr_en_o,
+ output lc_tx_t lc_provision_rd_en_o,
+ output lc_tx_t lc_keymgr_en_o,
+ output lc_tx_t lc_escalate_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
+);
+
+ ///////////////
+ // 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 lc state reg back to the programming interface of OTP.
+ assign otp_prog_lc_state_o = lc_state_q;
+ assign otp_prog_lc_cnt_o = lc_cnt_q;
+
+ // Conditional LC signal outputs
+ lc_tx_t lc_clk_byp_req_d, lc_clk_byp_req_q;
+ lc_tx_t lc_flash_rma_req_d, lc_flash_rma_req_q;
+
+ `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.
+ logic [LcTokenWidth-1:0] 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;
+
+ // The clock bypass and RMA 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_d = lc_clk_byp_req_q;
+ lc_flash_rma_req_d = lc_flash_rma_req_q;
+
+ 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;
+ if (init_req_i && lc_state_valid_q) begin
+ fsm_state_d = IdleSt;
+ 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 from OTP.
+ 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.
+ ClkMuxSt: begin
+ if (lc_state_q inside {LcStRaw,
+ LcStTestLocked0,
+ LcStTestLocked1,
+ LcStTestLocked2}) begin
+ lc_clk_byp_req_d = On;
+ if (lc_clk_byp_ack_i == 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;
+ lc_cnt_d = next_lc_cnt;
+ end
+ end
+ ///////////////////////////////////////////////////////////////////
+ // This programs the life cycle counter state.
+ CntProgSt: begin
+ otp_prog_req_o = 1'b1;
+ // Check return value and
+ 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_d = On;
+ if (lc_flash_rma_ack_i == 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_q == Off &&
+ lc_flash_rma_ack_i == Off) ||
+ (trans_target_i == DecLcStRma &&
+ lc_flash_rma_req_q == On &&
+ lc_flash_rma_ack_i == 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;
+ lc_state_d = next_lc_state;
+ 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 (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}};
+ lc_clk_byp_req_q <= Off;
+ lc_flash_rma_req_q <= Off;
+ 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;
+ lc_clk_byp_req_q <= lc_clk_byp_req_d;
+ lc_flash_rma_req_q <= lc_flash_rma_req_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.
+ logic [2**TokenIdxWidth-1:0][LcTokenWidth-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] = RawUnlockTokenHashed;
+ 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 ),
+ .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 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_provision_wr_en_o,
+ .lc_provision_rd_en_o,
+ .lc_keymgr_en_o,
+ .lc_escalate_en_o
+ );
+
+ // Conditional signals set by main FSM.
+ assign lc_clk_byp_req_o = lc_clk_byp_req_q;
+ assign lc_flash_rma_req_o = lc_flash_rma_req_q;
+
+ ////////////////
+ // Assertions //
+ ////////////////
+
+ `ASSERT(ClkBypStaysOnOnceAsserted_A,
+ lc_escalate_en_q == On
+ |=>
+ lc_escalate_en_q == On)
+
+ `ASSERT(FlashRmaStaysOnOnceAsserted_A,
+ lc_flash_rma_req_o == On
+ |=>
+ lc_flash_rma_req_o == On)
+
+endmodule : lc_ctrl_fsm
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
index 1f024c0..85ca004 100644
--- a/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
@@ -5,6 +5,76 @@
package lc_ctrl_pkg;
+ import prim_util_pkg::vbits;
+
+ // TODO: need to generate these randomly, based on ECC
+ // polynomial used inside the OTP macro.
+ // The A/B values are used for the encoded LC state.
+ parameter logic [15:0] A0 = 16'h0000;
+ parameter logic [15:0] A1 = 16'h0000;
+ parameter logic [15:0] A2 = 16'h0000;
+ parameter logic [15:0] A3 = 16'h0000;
+ parameter logic [15:0] A4 = 16'h0000;
+ parameter logic [15:0] A5 = 16'h0000;
+ parameter logic [15:0] A6 = 16'h0000;
+ parameter logic [15:0] A7 = 16'h0000;
+ parameter logic [15:0] A8 = 16'h0000;
+ parameter logic [15:0] A9 = 16'h0000;
+ parameter logic [15:0] A10 = 16'h0000;
+ parameter logic [15:0] A11 = 16'h0000;
+
+ parameter logic [15:0] B0 = 16'hFFFF;
+ parameter logic [15:0] B1 = 16'hFFFF;
+ parameter logic [15:0] B2 = 16'hFFFF;
+ parameter logic [15:0] B3 = 16'hFFFF;
+ parameter logic [15:0] B4 = 16'hFFFF;
+ parameter logic [15:0] B5 = 16'hFFFF;
+ parameter logic [15:0] B6 = 16'hFFFF;
+ parameter logic [15:0] B7 = 16'hFFFF;
+ parameter logic [15:0] B8 = 16'hFFFF;
+ parameter logic [15:0] B9 = 16'hFFFF;
+ parameter logic [15:0] B10 = 16'hFFFF;
+ parameter logic [15:0] B11 = 16'hFFFF;
+
+ // The C/D values are used for the encoded LC transition counter.
+ parameter logic [15:0] C0 = 16'h0000;
+ parameter logic [15:0] C1 = 16'h0000;
+ parameter logic [15:0] C2 = 16'h0000;
+ parameter logic [15:0] C3 = 16'h0000;
+ parameter logic [15:0] C4 = 16'h0000;
+ parameter logic [15:0] C5 = 16'h0000;
+ parameter logic [15:0] C6 = 16'h0000;
+ parameter logic [15:0] C7 = 16'h0000;
+ parameter logic [15:0] C8 = 16'h0000;
+ parameter logic [15:0] C9 = 16'h0000;
+ parameter logic [15:0] C10 = 16'h0000;
+ parameter logic [15:0] C11 = 16'h0000;
+ parameter logic [15:0] C12 = 16'h0000;
+ parameter logic [15:0] C13 = 16'h0000;
+ parameter logic [15:0] C14 = 16'h0000;
+ parameter logic [15:0] C15 = 16'h0000;
+
+ parameter logic [15:0] D0 = 16'hFFFF;
+ parameter logic [15:0] D1 = 16'hFFFF;
+ parameter logic [15:0] D2 = 16'hFFFF;
+ parameter logic [15:0] D3 = 16'hFFFF;
+ parameter logic [15:0] D4 = 16'hFFFF;
+ parameter logic [15:0] D5 = 16'hFFFF;
+ parameter logic [15:0] D6 = 16'hFFFF;
+ parameter logic [15:0] D7 = 16'hFFFF;
+ parameter logic [15:0] D8 = 16'hFFFF;
+ parameter logic [15:0] D9 = 16'hFFFF;
+ parameter logic [15:0] D10 = 16'hFFFF;
+ parameter logic [15:0] D11 = 16'hFFFF;
+ parameter logic [15:0] D12 = 16'hFFFF;
+ parameter logic [15:0] D13 = 16'hFFFF;
+ parameter logic [15:0] D14 = 16'hFFFF;
+ parameter logic [15:0] D15 = 16'hFFFF;
+
+ // The E/F values are used for the encoded ID state.
+ parameter logic [15:0] E0 = 16'h0000;
+ parameter logic [15:0] F0 = 16'hFFFF;
+
/////////////////////////////////
// General Typedefs and Params //
/////////////////////////////////
@@ -15,30 +85,108 @@
parameter int LcStateWidth = NumLcStateValues * LcValueWidth;
parameter int NumLcCountValues = 16;
parameter int LcCountWidth = NumLcCountValues * LcValueWidth;
+ parameter int NumLcStates = 13;
+ parameter int DecLcStateWidth = vbits(NumLcStates);
+ parameter int DecLcCountWidth = vbits(NumLcCountValues+1);
+ parameter int LcIdStateWidth = LcValueWidth;
+ parameter int DecLcIdStateWidth = 2;
- typedef enum logic [LcValueWidth-1:0] {
- Blk = 16'h0000, // blank
- Set = 16'hF5FA // programmed
- } lc_value_e;
+ typedef logic [LcTokenWidth-1:0] lc_token_t;
+ // TODO: make this secret and generate randomly, given a specific ECC polynomial.
typedef enum logic [LcStateWidth-1:0] {
// Halfword idx : 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
- LcStRaw = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk},
- LcStTestUnlocked0 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set},
- LcStTestLocked0 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set},
- LcStTestUnlocked1 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set},
- LcStTestLocked1 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set},
- LcStTestUnlocked2 = {Blk, Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set},
- LcStTestLocked2 = {Blk, Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set},
- LcStTestUnlocked3 = {Blk, Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
- LcStDev = {Blk, Blk, Blk, Blk, Set, Set, Set, Set, Set, Set, Set, Set},
- LcStProd = {Blk, Blk, Blk, Set, Blk, Set, Set, Set, Set, Set, Set, Set},
- LcStProdEnd = {Blk, Blk, Set, Blk, Blk, Set, Set, Set, Set, Set, Set, Set},
- LcStRma = {Set, Set, Blk, Set, Set, Set, Set, Set, Set, Set, Set, Set},
- LcStScrap = {Set, Set, Set, Set, Set, Set, Set, Set, Set, Set, Set, Set}
+ LcStRaw = '0,
+ LcStTestUnlocked0 = {A11, A10, A9, A8, A7, A6, A5, A4, A3, A2, A1, B0},
+ LcStTestLocked0 = {A11, A10, A9, A8, A7, A6, A5, A4, A3, A2, B1, B0},
+ LcStTestUnlocked1 = {A11, A10, A9, A8, A7, A6, A5, A4, A3, B2, B1, B0},
+ LcStTestLocked1 = {A11, A10, A9, A8, A7, A6, A5, A4, B3, B2, B1, B0},
+ LcStTestUnlocked2 = {A11, A10, A9, A8, A7, A6, A5, B4, B3, B2, B1, B0},
+ LcStTestLocked2 = {A11, A10, A9, A8, A7, A6, B5, B4, B3, B2, B1, B0},
+ LcStTestUnlocked3 = {A11, A10, A9, A8, A7, B6, B5, B4, B3, B2, B1, B0},
+ LcStDev = {A11, A10, A9, A8, B7, B6, B5, B4, B3, B2, B1, B0},
+ LcStProd = {A11, A10, A9, B8, A7, B6, B5, B4, B3, B2, B1, B0},
+ LcStProdEnd = {A11, A10, B9, A8, A7, B6, B5, B4, B3, B2, B1, B0},
+ LcStRma = {B11, B10, A9, B8, B7, B6, B5, B4, B3, B2, B1, B0},
+ LcStScrap = {B11, B10, B9, B8, B7, B6, B5, B4, B3, B2, B1, B0}
} lc_state_e;
- typedef lc_value_e [NumLcCountValues-1:0] lc_cnt_t;
+ // Decoded life cycle state, used to interface with CSRs and TAP.
+ typedef enum logic [DecLcStateWidth-1:0] {
+ DecLcStRaw = 4'h0,
+ DecLcStTestUnlocked0 = 4'h1,
+ DecLcStTestLocked0 = 4'h2,
+ DecLcStTestUnlocked1 = 4'h3,
+ DecLcStTestLocked1 = 4'h4,
+ DecLcStTestUnlocked2 = 4'h5,
+ DecLcStTestLocked2 = 4'h6,
+ DecLcStTestUnlocked3 = 4'h7,
+ DecLcStDev = 4'h8,
+ DecLcStProd = 4'h9,
+ DecLcStProdEnd = 4'hA,
+ DecLcStRma = 4'hB,
+ DecLcStScrap = 4'hC,
+ DecLcStPostTrans = 4'hD,
+ DecLcStEscalate = 4'hE,
+ DecLcStInvalid = 4'hF
+ } dec_lc_state_e;
+
+ typedef enum logic [LcIdStateWidth-1:0] {
+ LcIdBlank = E0,
+ LcIdPersonalized = F0
+ } lc_id_state_e;
+
+ typedef enum logic [DecLcIdStateWidth-1:0] {
+ DecLcIdBlank = 2'd0,
+ DecLcIdPersonalized = 2'd1,
+ DecLcIdInvalid = 2'd2
+ } dec_lc_id_state_e;
+
+ typedef enum logic [LcCountWidth-1:0] {
+ LcCntRaw = '0,
+ LcCnt1 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, C5, C4, C3, C2, C1, D0},
+ LcCnt2 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, C5, C4, C3, C2, D1, D0},
+ LcCnt3 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, C5, C4, C3, D2, D1, D0},
+ LcCnt4 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, C5, C4, D3, D2, D1, D0},
+ LcCnt5 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, C5, D4, D3, D2, D1, D0},
+ LcCnt6 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, C6, D5, D4, D3, D2, D1, D0},
+ LcCnt7 = {C15, C14, C13, C12, C11, C10, C9, C8, C7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt8 = {C15, C14, C13, C12, C11, C10, C9, C8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt9 = {C15, C14, C13, C12, C11, C10, C9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt10 = {C15, C14, C13, C12, C11, C10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt11 = {C15, C14, C13, C12, C11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt12 = {C15, C14, C13, C12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt13 = {C15, C14, C13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt14 = {C15, C14, D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt15 = {C15, D14, D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0},
+ LcCnt16 = {D15, D14, D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0}
+ } lc_cnt_e;
+
+ typedef logic [DecLcCountWidth-1:0] dec_lc_cnt_t;
+
+
+ ///////////////////////////////////////
+ // Netlist Constants (Hashed Tokens) //
+ ///////////////////////////////////////
+
+ parameter int NumTokens = 6;
+ parameter int TokenIdxWidth = vbits(NumTokens);
+ typedef enum logic [TokenIdxWidth-1:0] {
+ // This is the index for the hashed all-zero constant.
+ // All unconditional transitions use this token.
+ ZeroTokenIdx = 3'h0,
+ RawUnlockTokenIdx = 3'h1,
+ TestUnlockTokenIdx = 3'h2,
+ TestExitTokenIdx = 3'h3,
+ RmaTokenIdx = 3'h4,
+ // This is the index for an all-zero value (i.e., hashed value = '0).
+ // This is used as an additional blocker for some invalid state transition edges.
+ InvalidTokenIdx = 3'h5
+ } token_idx_e;
+
+ // TODO: precompute these values (probably have to do that in OTP at elab time).
+ parameter logic [TokenIdxWidth-1:0] RawUnlockTokenHashed = '0;
+ parameter logic [TokenIdxWidth-1:0] AllZeroTokenHashed = '0;
////////////////////////////////
// Typedefs for LC Interfaces //
@@ -50,10 +198,149 @@
Off = 4'b0101
} lc_tx_e;
- typedef struct packed {
- lc_tx_e state;
- } lc_tx_t;
+ typedef lc_tx_e lc_tx_t;
parameter lc_tx_t LC_TX_DEFAULT = Off;
+ parameter int RmaSeedWidth = 32;
+ typedef logic [RmaSeedWidth-1:0] lc_flash_rma_seed_t;
+
+ ////////////////////
+ // Main FSM State //
+ ////////////////////
+
+ // Encoding generated with:
+ // $ ./sparse-fsm-encode.py -d 5 -m 14 -n 16 \
+ // -s 2934212379 --language=sv
+ //
+ // Hamming distance histogram:
+ //
+ // 0: --
+ // 1: --
+ // 2: --
+ // 3: --
+ // 4: --
+ // 5: |||||| (6.59%)
+ // 6: |||||||||| (10.99%)
+ // 7: |||||||||||||||| (17.58%)
+ // 8: |||||||||||||||||||| (20.88%)
+ // 9: |||||||||||||||| (17.58%)
+ // 10: |||||||||||||| (15.38%)
+ // 11: |||||| (6.59%)
+ // 12: ||| (3.30%)
+ // 13: | (1.10%)
+ // 14: --
+ // 15: --
+ // 16: --
+ //
+ // Minimum Hamming distance: 5
+ // Maximum Hamming distance: 13
+ //
+ localparam int FsmStateWidth = 16;
+ typedef enum logic [FsmStateWidth-1:0] {
+ ResetSt = 16'b1100000001111011,
+ IdleSt = 16'b1111011010111100,
+ ClkMuxSt = 16'b0000011110101101,
+ CntIncrSt = 16'b1100111011001001,
+ CntProgSt = 16'b0011001111000111,
+ TransCheckSt = 16'b0000110001010100,
+ TokenHashSt = 16'b1110100010001111,
+ FlashRmaSt = 16'b0110111010110000,
+ TokenCheck0St = 16'b0010000011000000,
+ TokenCheck1St = 16'b1101010101101111,
+ TransProgSt = 16'b1000000110101011,
+ PostTransSt = 16'b0110110100101100,
+ EscalateSt = 16'b1010100001010001,
+ InvalidSt = 16'b1011110110011011
+ } fsm_state_e;
+
+ ///////////////////////////////////////////
+ // Manufacturing State Transition Matrix //
+ ///////////////////////////////////////////
+
+ // The token index matrix below encodes 1) which transition edges are valid and 2) which token
+ // to use for a given transition edge. Note that unconditional but otherwise valid transitions
+ // are assigned the ZeroTokenIdx, whereas invalid transitions are assigned an InvalidTokenIdx.
+ parameter token_idx_e [NumLcStates-1:0][NumLcStates-1:0] TransTokenIdxMatrix = {
+ // SCRAP
+ {13{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, DEV, PROD, PROD_END, RMA, SCRAP
+ // RMA
+ ZeroTokenIdx, // -> SCRAP
+ {12{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, DEV, PROD, PROD_END, RMA
+ // PROD_END
+ ZeroTokenIdx, // -> SCRAP
+ {12{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, DEV, PROD, PROD_END, RMA
+ // PROD
+ ZeroTokenIdx, // -> SCRAP
+ RmaTokenIdx, // -> RMA
+ {11{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, DEV, PROD, PROD_END
+ // DEV
+ ZeroTokenIdx, // -> SCRAP
+ RmaTokenIdx, // -> RMA
+ {11{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, DEV, PROD, PROD_END
+ // TEST_UNLOCKED2
+ {2{ZeroTokenIdx}}, // -> SCRAP, RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ {8{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-3, RAW
+ // TEST_LOCKED2
+ ZeroTokenIdx, // -> SCRAP
+ InvalidTokenIdx, // -> RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED3
+ {7{InvalidTokenIdx}}, // -> TEST_LOCKED0-2, TEST_UNLOCKED0-2, RAW
+ // TEST_UNLOCKED2
+ {2{ZeroTokenIdx}}, // -> SCRAP, RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ InvalidTokenIdx, // -> TEST_UNLOCKED3
+ ZeroTokenIdx, // -> TEST_LOCKED2
+ {6{InvalidTokenIdx}}, // -> TEST_LOCKED0-1, TEST_UNLOCKED0-2, RAW
+ // TEST_LOCKED1
+ ZeroTokenIdx, // -> SCRAP
+ InvalidTokenIdx, // -> RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED3
+ InvalidTokenIdx , // -> TEST_LOCKED2
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED2
+ {5{InvalidTokenIdx}}, // -> TEST_LOCKED0-1, TEST_UNLOCKED0-1, RAW
+ // TEST_UNLOCKED1
+ {2{ZeroTokenIdx}}, // -> SCRAP, RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ InvalidTokenIdx, // -> TEST_UNLOCKED3
+ ZeroTokenIdx, // -> TEST_LOCKED2
+ InvalidTokenIdx, // -> TEST_UNLOCKED2
+ ZeroTokenIdx, // -> TEST_LOCKED1
+ {4{InvalidTokenIdx}}, // -> TEST_LOCKED0, TEST_UNLOCKED0-1, RAW
+ // TEST_LOCKED0
+ ZeroTokenIdx, // -> SCRAP
+ InvalidTokenIdx, // -> RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED3
+ InvalidTokenIdx, // -> TEST_LOCKED2
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED2
+ InvalidTokenIdx, // -> TEST_LOCKED1
+ TestUnlockTokenIdx, // -> TEST_UNLOCKED1
+ {3{InvalidTokenIdx}}, // -> TEST_LOCKED0, TEST_UNLOCKED0, RAW
+ // TEST_UNLOCKED0
+ {2{ZeroTokenIdx}}, // -> SCRAP, RMA
+ {3{TestExitTokenIdx}}, // -> PROD, PROD_END, DEV
+ InvalidTokenIdx, // -> TEST_UNLOCKED3
+ ZeroTokenIdx, // -> TEST_LOCKED2
+ InvalidTokenIdx, // -> TEST_UNLOCKED2
+ ZeroTokenIdx, // -> TEST_LOCKED1
+ InvalidTokenIdx, // -> TEST_UNLOCKED1
+ ZeroTokenIdx, // -> TEST_LOCKED0
+ {2{InvalidTokenIdx}}, // -> TEST_UNLOCKED0, RAW
+ // RAW
+ ZeroTokenIdx, // -> SCRAP
+ {4{InvalidTokenIdx}}, // -> RMA, PROD, PROD_END, DEV
+ RawUnlockTokenIdx, // -> TEST_UNLOCKED3
+ InvalidTokenIdx, // -> TEST_LOCKED2
+ RawUnlockTokenIdx, // -> TEST_UNLOCKED2
+ InvalidTokenIdx, // -> TEST_LOCKED1
+ RawUnlockTokenIdx, // -> TEST_UNLOCKED1
+ InvalidTokenIdx, // -> TEST_LOCKED0
+ RawUnlockTokenIdx, // -> TEST_UNLOCKED0
+ InvalidTokenIdx // -> RAW
+ };
+
endpackage : lc_ctrl_pkg
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_signal_decode.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_signal_decode.sv
new file mode 100644
index 0000000..75507d3
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_signal_decode.sv
@@ -0,0 +1,183 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Life cycle signal decoder and sender module.
+
+module lc_ctrl_signal_decode
+ import lc_ctrl_pkg::*;
+(
+ input clk_i,
+ input rst_ni,
+ // Life cycle state vector.
+ input logic lc_state_valid_i,
+ input lc_state_e lc_state_i,
+ input lc_id_state_e lc_id_state_i,
+ input fsm_state_e fsm_state_i,
+ // Escalation enable from escalation receiver.
+ input esc_wipe_secrets_i,
+ // 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_provision_wr_en_o,
+ output lc_tx_t lc_provision_rd_en_o,
+ output lc_tx_t lc_keymgr_en_o,
+ output lc_tx_t lc_escalate_en_o
+);
+
+ //////////////////////////
+ // Signal Decoder Logic //
+ //////////////////////////
+
+ lc_tx_t lc_dft_en_d, lc_dft_en_q;
+ lc_tx_t lc_nvm_debug_en_d, lc_nvm_debug_en_q;
+ lc_tx_t lc_hw_debug_en_d, lc_hw_debug_en_q;
+ lc_tx_t lc_cpu_en_d, lc_cpu_en_q;
+ lc_tx_t lc_provision_wr_en_d, lc_provision_wr_en_q;
+ lc_tx_t lc_provision_rd_en_d, lc_provision_rd_en_q;
+ lc_tx_t lc_keymgr_en_d, lc_keymgr_en_q;
+ lc_tx_t lc_escalate_en_d, lc_escalate_en_q;
+
+ always_comb begin : p_lc_signal_decode
+ // Life cycle control signal defaults
+ lc_dft_en_d = Off;
+ lc_nvm_debug_en_d = Off;
+ lc_hw_debug_en_d = Off;
+ lc_cpu_en_d = Off;
+ lc_provision_wr_en_d = Off;
+ lc_provision_rd_en_d = Off;
+ lc_keymgr_en_d = Off;
+ lc_escalate_en_d = Off;
+
+ // The escalation life cycle signal is always decoded, no matter
+ // which state we currently are in.
+ if (esc_wipe_secrets_i) begin
+ lc_escalate_en_d = On;
+ end
+
+ // Only broadcast during the following main FSM states
+ if (lc_state_valid_i && fsm_state_i inside {IdleSt,
+ ClkMuxSt,
+ CntIncrSt,
+ CntProgSt,
+ TransCheckSt,
+ FlashRmaSt,
+ TokenHashSt,
+ TokenCheck0St,
+ TokenCheck1St,
+ TransProgSt}) begin
+ unique case (lc_state_i)
+ ///////////////////////////////////////////////////////////////////
+ // Enable DFT and debug functionality, including the CPU in the
+ // test unlocked states.
+ LcStTestUnlocked0,
+ LcStTestUnlocked1,
+ LcStTestUnlocked2,
+ LcStTestUnlocked3: begin
+ lc_dft_en_d = On;
+ lc_nvm_debug_en_d = On;
+ lc_hw_debug_en_d = On;
+ lc_cpu_en_d = On;
+ end
+ ///////////////////////////////////////////////////////////////////
+ // Enable production functions
+ LcStProd, LcStProdEnd: begin
+ lc_cpu_en_d = On;
+ lc_keymgr_en_d = On;
+ lc_provision_rd_en_d = On;
+ // Only allow provisioning if the defice has not yet been personalized.
+ if (lc_id_state_i == LcIdBlank) begin
+ lc_provision_wr_en_d = On;
+ end
+ end
+ ///////////////////////////////////////////////////////////////////
+ // Same functions as PROD, but with additional debug functionality.
+ LcStDev: begin
+ lc_hw_debug_en_d = On;
+ lc_cpu_en_d = On;
+ lc_keymgr_en_d = On;
+ lc_provision_rd_en_d = On;
+ // Only allow provisioning if the defice has not yet been personalized.
+ if (lc_id_state_i == LcIdBlank) begin
+ lc_provision_wr_en_d = On;
+ end
+ end
+ ///////////////////////////////////////////////////////////////////
+ // Enable all test and production functions.
+ LcStRma: begin
+ lc_dft_en_d = On;
+ lc_nvm_debug_en_d = On;
+ lc_hw_debug_en_d = On;
+ lc_cpu_en_d = On;
+ lc_keymgr_en_d = On;
+ lc_provision_rd_en_d = On;
+ // Only allow provisioning if the defice has not yet been personalized.
+ if (lc_id_state_i == LcIdBlank) begin
+ lc_provision_wr_en_d = On;
+ end
+ end
+ ///////////////////////////////////////////////////////////////////
+ // Invalid or scrapped life cycle state, do not assert
+ // any signals other than escalate_en and clk_byp_en.
+ default: ;
+ endcase // lc_state_i
+ end
+ end
+
+ /////////////////////////////////
+ // Control signal output flops //
+ /////////////////////////////////
+
+ assign lc_dft_en_o = lc_dft_en_q;
+ assign lc_nvm_debug_en_o = lc_nvm_debug_en_q;
+ assign lc_hw_debug_en_o = lc_hw_debug_en_q;
+ assign lc_cpu_en_o = lc_cpu_en_q;
+ assign lc_provision_wr_en_o = lc_provision_wr_en_q;
+ assign lc_provision_rd_en_o = lc_provision_rd_en_q;
+ assign lc_keymgr_en_o = lc_keymgr_en_q;
+ assign lc_escalate_en_o = lc_escalate_en_q;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ lc_dft_en_q <= Off;
+ lc_nvm_debug_en_q <= Off;
+ lc_hw_debug_en_q <= Off;
+ lc_cpu_en_q <= Off;
+ lc_provision_wr_en_q <= Off;
+ lc_provision_rd_en_q <= Off;
+ lc_keymgr_en_q <= Off;
+ lc_escalate_en_q <= Off;
+ end else begin
+ lc_dft_en_q <= lc_dft_en_d;
+ lc_nvm_debug_en_q <= lc_nvm_debug_en_d;
+ lc_hw_debug_en_q <= lc_hw_debug_en_d;
+ lc_cpu_en_q <= lc_cpu_en_d;
+ lc_provision_wr_en_q <= lc_provision_wr_en_d;
+ lc_provision_rd_en_q <= lc_provision_rd_en_d;
+ lc_keymgr_en_q <= lc_keymgr_en_d;
+ lc_escalate_en_q <= lc_escalate_en_d;
+ end
+ end
+
+ ////////////////
+ // Assertions //
+ ////////////////
+
+ `ASSERT(SignalsAreOffWhenNotEnabled_A,
+ !lc_state_valid_i
+ |=>
+ lc_dft_en_o == Off &&
+ lc_nvm_debug_en_o == Off &&
+ lc_hw_debug_en_o == Off &&
+ lc_cpu_en_o == Off &&
+ lc_provision_wr_en_o == Off &&
+ lc_provision_rd_en_o == Off &&
+ lc_keymgr_en_o == Off &&
+ lc_dft_en_o == Off)
+
+ `ASSERT(EscalationAlwaysDecoded_A,
+ (lc_escalate_en_o == On) == $past(esc_wipe_secrets_i))
+
+endmodule : lc_ctrl_signal_decode
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_state_decode.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_state_decode.sv
new file mode 100644
index 0000000..d684c29
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_state_decode.sv
@@ -0,0 +1,125 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Life cycle state decoder. This is a purely combinational module.
+
+module lc_ctrl_state_decode
+ import lc_ctrl_pkg::*;
+(
+ // Life cycle state vector.
+ input logic 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,
+ // Main FSM state.
+ input fsm_state_e fsm_state_i,
+ // Decoded state vector.
+ output dec_lc_state_e dec_lc_state_o,
+ output dec_lc_id_state_e dec_lc_id_state_o,
+ output dec_lc_cnt_t dec_lc_cnt_o,
+ output logic state_invalid_error_o
+);
+
+ //////////////////////////
+ // Signal Decoder Logic //
+ //////////////////////////
+
+ // The decoder logic below decodes the life cycle state vector and counter
+ // into a format that can be exposed in the CSRs. If the state is invalid,
+ // this will be flagged as well.
+
+ always_comb begin : p_lc_state_decode
+ // Decoded state defaults
+ dec_lc_state_o = DecLcStInvalid;
+ dec_lc_cnt_o = {DecLcCountWidth{1'b1}};
+ dec_lc_id_state_o = DecLcIdInvalid;
+ state_invalid_error_o = 1'b0;
+
+ unique case (fsm_state_i)
+ // Don't decode anything in ResetSt
+ ResetSt: ;
+ // These are temporary, terminal states that are not encoded
+ // in the persistenc LC state vector from OTP, hence we decode them first.
+ EscalateSt: dec_lc_state_o = DecLcStEscalate;
+ PostTransSt: dec_lc_state_o = DecLcStPostTrans;
+ InvalidSt: dec_lc_state_o = DecLcStInvalid;
+ // Otherwise check and decode the life cycle state continously.
+ default: begin
+ // Note that we require that the valid signal from OTP is
+ // asserted at all times except when the LC controller is in ResetSt.
+ // This will trigger an invalid_state_error when the OTP partition
+ // is corrupt and moved into an error state, where the valid bit is
+ // deasserted.
+ state_invalid_error_o = ~lc_state_valid_i;
+
+ unique case (lc_state_i)
+ LcStRaw: dec_lc_state_o = DecLcStRaw;
+ LcStTestUnlocked0: dec_lc_state_o = DecLcStTestUnlocked0;
+ LcStTestLocked0: dec_lc_state_o = DecLcStTestLocked0;
+ LcStTestUnlocked1: dec_lc_state_o = DecLcStTestUnlocked1;
+ LcStTestLocked1: dec_lc_state_o = DecLcStTestLocked1;
+ LcStTestUnlocked2: dec_lc_state_o = DecLcStTestUnlocked2;
+ LcStTestLocked2: dec_lc_state_o = DecLcStTestLocked2;
+ LcStTestUnlocked3: dec_lc_state_o = DecLcStTestUnlocked3;
+ LcStDev: dec_lc_state_o = DecLcStDev;
+ LcStProd: dec_lc_state_o = DecLcStProd;
+ LcStProdEnd: dec_lc_state_o = DecLcStProdEnd;
+ LcStRma: dec_lc_state_o = DecLcStRma;
+ LcStScrap: dec_lc_state_o = DecLcStScrap;
+ default: state_invalid_error_o = 1'b1;
+ endcase // lc_state_i
+
+ unique case (lc_cnt_i)
+ LcCntRaw: dec_lc_cnt_o = 5'd0;
+ LcCnt1: dec_lc_cnt_o = 5'd1;
+ LcCnt2: dec_lc_cnt_o = 5'd2;
+ LcCnt3: dec_lc_cnt_o = 5'd3;
+ LcCnt4: dec_lc_cnt_o = 5'd4;
+ LcCnt5: dec_lc_cnt_o = 5'd5;
+ LcCnt6: dec_lc_cnt_o = 5'd6;
+ LcCnt7: dec_lc_cnt_o = 5'd7;
+ LcCnt8: dec_lc_cnt_o = 5'd8;
+ LcCnt9: dec_lc_cnt_o = 5'd9;
+ LcCnt10: dec_lc_cnt_o = 5'd10;
+ LcCnt11: dec_lc_cnt_o = 5'd11;
+ LcCnt12: dec_lc_cnt_o = 5'd12;
+ LcCnt13: dec_lc_cnt_o = 5'd13;
+ LcCnt14: dec_lc_cnt_o = 5'd14;
+ LcCnt15: dec_lc_cnt_o = 5'd15;
+ LcCnt16: dec_lc_cnt_o = 5'd16;
+ default: state_invalid_error_o = 1'b1;
+ endcase // lc_cnt_i
+
+ unique case (lc_id_state_i)
+ LcIdBlank: dec_lc_id_state_o = DecLcIdBlank;
+ LcIdPersonalized: dec_lc_id_state_o = DecLcIdPersonalized;
+ default: state_invalid_error_o = 1'b1;
+ endcase // lc_id_state_i
+
+ // Require that any non-raw state has a valid, nonzero
+ // transition count.
+ if (lc_state_i != LcStRaw && lc_cnt_i != LcCntRaw) begin
+ state_invalid_error_o = 1'b1;
+ end
+
+ // We can't have a personalized device that is
+ // still in RAW or any of the test states.
+ if ((lc_id_state_i == LcIdPersonalized) &&
+ !(lc_state_i inside {LcStDev,
+ LcStProd,
+ LcStProdEnd,
+ LcStRma,
+ LcStScrap})) begin
+ state_invalid_error_o = 1'b1;
+ end
+ end
+ endcase // lc_id_state_i
+ end
+
+ ////////////////
+ // Assertions //
+ ////////////////
+
+
+endmodule : lc_ctrl_state_decode
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_state_transition.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_state_transition.sv
new file mode 100644
index 0000000..036f70b
--- /dev/null
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_state_transition.sv
@@ -0,0 +1,95 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Life cycle state transition function. Checks whether a transition is valid
+// and computes the target state. This module is purely combinational.
+
+module lc_ctrl_state_transition
+ import lc_ctrl_pkg::*;
+(
+ // Life cycle state vector.
+ input lc_state_e lc_state_i,
+ input lc_cnt_e lc_cnt_i,
+ // Decoded lc state input
+ input dec_lc_state_e dec_lc_state_i,
+ // Transition target.
+ input dec_lc_state_e trans_target_i,
+ // Updated state vector.
+ output lc_state_e next_lc_state_o,
+ output lc_cnt_e next_lc_cnt_o,
+ // If the transition counter is maxed out
+ output logic trans_cnt_oflw_error_o,
+ output logic trans_invalid_error_o
+);
+
+ //////////////////////////
+ // Signal Decoder Logic //
+ //////////////////////////
+
+ // The decoder logic below checks whether a given transition edge
+ // is valid and computes the next lc counter ans state vectors.
+ always_comb begin : p_lc_state_transition
+ // Decoded state defaults
+ next_lc_cnt_o = lc_cnt_i;
+ next_lc_state_o = lc_state_i;
+ trans_cnt_oflw_error_o = 1'b0;
+ trans_invalid_error_o = 1'b0;
+
+ // In this state, the life cycle counter is incremented.
+ // Throw an error if the counter is already maxed out.
+ unique case (lc_cnt_i)
+ LcCntRaw: next_lc_cnt_o = LcCnt1;
+ LcCnt1: next_lc_cnt_o = LcCnt2;
+ LcCnt2: next_lc_cnt_o = LcCnt3;
+ LcCnt3: next_lc_cnt_o = LcCnt4;
+ LcCnt4: next_lc_cnt_o = LcCnt5;
+ LcCnt5: next_lc_cnt_o = LcCnt6;
+ LcCnt6: next_lc_cnt_o = LcCnt7;
+ LcCnt7: next_lc_cnt_o = LcCnt8;
+ LcCnt8: next_lc_cnt_o = LcCnt9;
+ LcCnt9: next_lc_cnt_o = LcCnt10;
+ LcCnt10: next_lc_cnt_o = LcCnt11;
+ LcCnt11: next_lc_cnt_o = LcCnt12;
+ LcCnt12: next_lc_cnt_o = LcCnt13;
+ LcCnt13: next_lc_cnt_o = LcCnt14;
+ LcCnt14: next_lc_cnt_o = LcCnt15;
+ LcCnt15: next_lc_cnt_o = LcCnt16;
+ LcCnt16: trans_cnt_oflw_error_o = 1'b1;
+ default: trans_cnt_oflw_error_o = 1'b1;
+ endcase // lc_cnt_i
+
+ // Check that the decoded transition indexes are valid
+ // before indexing the state transition matrix.
+ if (dec_lc_state_i <= DecLcStScrap ||
+ trans_target_i <= DecLcStScrap) begin
+ // Check the state transition token matrix in order to see whether this
+ // transition is valid. All transitions have a token index value different
+ // from InvalidTokenIdx.
+ if (TransTokenIdxMatrix[dec_lc_state_i][trans_target_i] != InvalidTokenIdx) begin
+ // Encode the target state.
+ unique case (trans_target_i)
+ DecLcStRaw: next_lc_state_o = LcStRaw;
+ DecLcStTestUnlocked0: next_lc_state_o = LcStTestUnlocked0;
+ DecLcStTestLocked0: next_lc_state_o = LcStTestLocked0;
+ DecLcStTestUnlocked1: next_lc_state_o = LcStTestUnlocked1;
+ DecLcStTestLocked1: next_lc_state_o = LcStTestLocked1;
+ DecLcStTestUnlocked2: next_lc_state_o = LcStTestUnlocked2;
+ DecLcStTestLocked2: next_lc_state_o = LcStTestLocked2;
+ DecLcStTestUnlocked3: next_lc_state_o = LcStTestUnlocked3;
+ DecLcStDev: next_lc_state_o = LcStDev;
+ DecLcStProd: next_lc_state_o = LcStProd;
+ DecLcStProdEnd: next_lc_state_o = LcStProdEnd;
+ DecLcStRma: next_lc_state_o = LcStRma;
+ DecLcStScrap: next_lc_state_o = LcStScrap;
+ default: ;
+ endcase // trans_target_i
+ end else begin
+ trans_invalid_error_o = 1'b1;
+ end
+ end else begin
+ trans_invalid_error_o = 1'b1;
+ end
+ end
+
+endmodule : lc_ctrl_state_transition
diff --git a/hw/ip/prim/rtl/prim_lc_sync.sv b/hw/ip/prim/rtl/prim_lc_sync.sv
index 1de7450..516e2cb 100644
--- a/hw/ip/prim/rtl/prim_lc_sync.sv
+++ b/hw/ip/prim/rtl/prim_lc_sync.sv
@@ -25,10 +25,10 @@
`ASSERT_INIT(NumCopiesMustBeGreaterZero_A, NumCopies > 0)
- lc_ctrl_pkg::lc_tx_t lc_en;
+ logic [lc_ctrl_pkg::TxWidth-1:0] lc_en;
prim_flop_2sync #(
.Width(lc_ctrl_pkg::TxWidth),
- .ResetValue(int'(lc_ctrl_pkg::Off))
+ .ResetValue(lc_ctrl_pkg::TxWidth'(lc_ctrl_pkg::Off))
) u_prim_flop_2sync (
.clk_i,
.rst_ni,
@@ -36,16 +36,19 @@
.q_o(lc_en)
);
+ logic [NumCopies-1:0][lc_ctrl_pkg::TxWidth-1:0] lc_en_copies;
for (genvar j = 0; j < NumCopies; j++) begin : gen_buffs
for (genvar k = 0; k < lc_ctrl_pkg::TxWidth; k++) begin : gen_bits
// TODO: replace this with a normal buffer primitive, once available.
prim_clock_buf u_prim_clock_buf (
.clk_i(lc_en[k]),
- .clk_o(lc_en_o[j][k])
+ .clk_o(lc_en_copies[j][k])
);
end
end
+ assign lc_en_o = lc_en_copies;
+
////////////////
// Assertions //
////////////////
diff --git a/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson b/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
index 1ca9e4b..1d2caa6 100644
--- a/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
+++ b/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
@@ -68,6 +68,11 @@
import_cfgs: ["{proj_root}/hw/lint/tools/dvsim/common_lint_cfg.hjson"]
rel_path: "hw/ip/i2c/lint/{tool}"
},
+ { name: lc_ctrl
+ fusesoc_core: lowrisc:ip:lc_ctrl
+ import_cfgs: ["{proj_root}/hw/lint/tools/dvsim/common_lint_cfg.hjson"]
+ rel_path: "hw/ip/lc_ctrl/lint/{tool}"
+ },
{ name: pattgen
fusesoc_core: lowrisc:ip:pattgen
import_cfgs: ["{proj_root}/hw/lint/tools/dvsim/common_lint_cfg.hjson"]