blob: dcc4a7eabfc9b240074bc199f52a9e6d68456d41 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// OTP Controller top.
//
`include "prim_assert.sv"
module otp_ctrl
import otp_ctrl_pkg::*;
import otp_ctrl_reg_pkg::*;
import otp_ctrl_part_pkg::*;
#(
// Enable asynchronous transitions on alerts.
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
// Compile time random constants, to be overriden by topgen.
parameter lfsr_seed_t RndCnstLfsrSeed = RndCnstLfsrSeedDefault,
parameter lfsr_perm_t RndCnstLfsrPerm = RndCnstLfsrPermDefault,
parameter scrmbl_key_init_t RndCnstScrmblKeyInit = RndCnstScrmblKeyInitDefault,
// Hexfile file to initialize the OTP macro.
// Note that the hexdump needs to account for ECC.
parameter MemInitFile = ""
) (
// OTP clock
input clk_i,
input rst_ni,
// EDN clock and interface
logic clk_edn_i,
logic rst_edn_ni,
output edn_pkg::edn_req_t edn_o,
input edn_pkg::edn_rsp_t edn_i,
// Bus Interface
input tlul_pkg::tl_h2d_t core_tl_i,
output tlul_pkg::tl_d2h_t core_tl_o,
input tlul_pkg::tl_h2d_t prim_tl_i,
output tlul_pkg::tl_d2h_t prim_tl_o,
// Interrupt Requests
output logic intr_otp_operation_done_o,
output logic intr_otp_error_o,
// Alerts
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,
// Observability to AST
input ast_pkg::ast_obs_ctrl_t obs_ctrl_i,
output logic [7:0] otp_obs_o,
// Macro-specific power sequencing signals to/from AST.
output otp_ast_req_t otp_ast_pwr_seq_o,
input otp_ast_rsp_t otp_ast_pwr_seq_h_i,
// Power manager interface (inputs are synced to OTP clock domain)
input pwrmgr_pkg::pwr_otp_req_t pwr_otp_i,
output pwrmgr_pkg::pwr_otp_rsp_t pwr_otp_o,
// Macro-specific test registers going to lifecycle TAP
input lc_otp_vendor_test_req_t lc_otp_vendor_test_i,
output lc_otp_vendor_test_rsp_t lc_otp_vendor_test_o,
// Lifecycle transition command interface
input lc_otp_program_req_t lc_otp_program_i,
output lc_otp_program_rsp_t lc_otp_program_o,
// Lifecycle broadcast inputs
// SEC_CM: LC_CTRL.INTERSIG.MUBI
input lc_ctrl_pkg::lc_tx_t lc_creator_seed_sw_rw_en_i,
input lc_ctrl_pkg::lc_tx_t lc_seed_hw_rd_en_i,
input lc_ctrl_pkg::lc_tx_t lc_dft_en_i,
input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i,
input lc_ctrl_pkg::lc_tx_t lc_check_byp_en_i,
// OTP broadcast outputs
// SEC_CM: TOKEN_VALID.CTRL.MUBI
output otp_lc_data_t otp_lc_data_o,
output otp_keymgr_key_t otp_keymgr_key_o,
// Scrambling key requests
input flash_otp_key_req_t flash_otp_key_i,
output flash_otp_key_rsp_t flash_otp_key_o,
input sram_otp_key_req_t [NumSramKeyReqSlots-1:0] sram_otp_key_i,
output sram_otp_key_rsp_t [NumSramKeyReqSlots-1:0] sram_otp_key_o,
input otbn_otp_key_req_t otbn_otp_key_i,
output otbn_otp_key_rsp_t otbn_otp_key_o,
// Hardware config bits
output otp_hw_cfg_t otp_hw_cfg_o,
// External voltage for OTP
inout wire otp_ext_voltage_h_io,
// Scan
input scan_en_i,
input scan_rst_ni,
input prim_mubi_pkg::mubi4_t scanmode_i,
// Test-related GPIO output
output logic [OtpTestVectWidth-1:0] cio_test_o,
output logic [OtpTestVectWidth-1:0] cio_test_en_o
);
import prim_mubi_pkg::*;
import prim_util_pkg::vbits;
////////////////////////
// Integration Checks //
////////////////////////
// This ensures that we can transfer scrambler data blocks in and out of OTP atomically.
`ASSERT_INIT(OtpIfWidth_A, OtpIfWidth == ScrmblBlockWidth)
// These error codes need to be identical.
`ASSERT_INIT(ErrorCodeWidth_A, OtpErrWidth == prim_otp_pkg::ErrWidth)
`ASSERT_INIT(OtpErrorCode0_A, int'(NoError) == int'(prim_otp_pkg::NoError))
`ASSERT_INIT(OtpErrorCode1_A, int'(MacroError) == int'(prim_otp_pkg::MacroError))
`ASSERT_INIT(OtpErrorCode2_A, int'(MacroEccCorrError) == int'(prim_otp_pkg::MacroEccCorrError))
`ASSERT_INIT(OtpErrorCode3_A,
int'(MacroEccUncorrError) == int'(prim_otp_pkg::MacroEccUncorrError))
`ASSERT_INIT(OtpErrorCode4_A,
int'(MacroWriteBlankError) == int'(prim_otp_pkg::MacroWriteBlankError))
/////////////
// Regfile //
/////////////
// We have one CSR node, one functional TL-UL window and a gate module for that window
logic [2:0] intg_error;
tlul_pkg::tl_h2d_t tl_win_h2d;
tlul_pkg::tl_d2h_t tl_win_d2h;
otp_ctrl_reg_pkg::otp_ctrl_core_reg2hw_t reg2hw;
otp_ctrl_reg_pkg::otp_ctrl_core_hw2reg_t hw2reg;
// SEC_CM: DIRECT_ACCESS.CONFIG.REGWEN, CHECK_TRIGGER.CONFIG.REGWEN, CHECK.CONFIG.REGWEN
otp_ctrl_core_reg_top u_reg_core (
.clk_i,
.rst_ni,
.tl_i ( core_tl_i ),
.tl_o ( core_tl_o ),
.tl_win_o ( tl_win_h2d ),
.tl_win_i ( tl_win_d2h ),
.reg2hw ( reg2hw ),
.hw2reg ( hw2reg ),
// SEC_CM: BUS.INTEGRITY
.intg_err_o( intg_error[0] ),
.devmode_i ( 1'b1 )
);
///////////////////////////////////////
// Life Cycle Signal Synchronization //
///////////////////////////////////////
lc_ctrl_pkg::lc_tx_t lc_creator_seed_sw_rw_en, lc_seed_hw_rd_en, lc_check_byp_en;
lc_ctrl_pkg::lc_tx_t [2:0] lc_dft_en;
// NumAgents + lfsr timer and scrambling datapath.
lc_ctrl_pkg::lc_tx_t [NumAgentsIdx+1:0] lc_escalate_en, lc_escalate_en_synced;
// Single wire for gating assertions in arbitration and CDC primitives.
logic lc_escalate_en_any;
prim_lc_sync #(
.NumCopies(NumAgentsIdx+2)
) u_prim_lc_sync_escalate_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_escalate_en_i),
.lc_en_o(lc_escalate_en_synced)
);
prim_lc_sync #(
.NumCopies(1)
) u_prim_lc_sync_creator_seed_sw_rw_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_creator_seed_sw_rw_en_i),
.lc_en_o({lc_creator_seed_sw_rw_en})
);
prim_lc_sync #(
.NumCopies(1)
) u_prim_lc_sync_seed_hw_rd_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_seed_hw_rd_en_i),
.lc_en_o({lc_seed_hw_rd_en})
);
prim_lc_sync #(
.NumCopies(3)
) u_prim_lc_sync_dft_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_dft_en_i),
.lc_en_o(lc_dft_en)
);
prim_lc_sync #(
.NumCopies(1)
) u_prim_lc_sync_check_byp_en (
.clk_i,
.rst_ni,
.lc_en_i(lc_check_byp_en_i),
.lc_en_o({lc_check_byp_en})
);
/////////////////////////////////////
// TL-UL SW partition select logic //
/////////////////////////////////////
// The SW partitions share the same TL-UL adapter.
logic tlul_req, tlul_gnt, tlul_rvalid;
logic [SwWindowAddrWidth-1:0] tlul_addr;
logic [1:0] tlul_rerror;
logic [31:0] tlul_rdata;
import prim_mubi_pkg::MuBi4False;
tlul_adapter_sram #(
.SramAw ( SwWindowAddrWidth ),
.SramDw ( 32 ),
.Outstanding ( 1 ),
.ByteAccess ( 0 ),
.ErrOnWrite ( 1 ) // No write accesses allowed here.
) u_tlul_adapter_sram (
.clk_i,
.rst_ni,
.en_ifetch_i ( MuBi4False ),
.tl_i ( tl_win_h2d ),
.tl_o ( tl_win_d2h ),
.req_o ( tlul_req ),
.gnt_i ( tlul_gnt ),
.we_o ( ), // unused
.addr_o ( tlul_addr ),
.wdata_o ( ), // unused
.wmask_o ( ), // unused
// SEC_CM: BUS.INTEGRITY
.intg_error_o( intg_error[1] ),
.rdata_i ( tlul_rdata ),
.rvalid_i ( tlul_rvalid ),
.rerror_i ( tlul_rerror ),
.req_type_o ( )
);
logic [NumPart-1:0] tlul_part_sel_oh;
for (genvar k = 0; k < NumPart; k++) begin : gen_part_sel
localparam logic [OtpByteAddrWidth:0] PartEnd = (OtpByteAddrWidth+1)'(PartInfo[k].offset) +
(OtpByteAddrWidth+1)'(PartInfo[k].size);
assign tlul_part_sel_oh[k] = ({tlul_addr, 2'b00} >= PartInfo[k].offset) &
({1'b0, {tlul_addr, 2'b00}} < PartEnd);
end
`ASSERT(PartSelMustBeOnehot_A, $onehot0(tlul_part_sel_oh))
logic [NumPartWidth-1:0] tlul_part_idx;
prim_arbiter_fixed #(
.N(NumPart),
.EnDataPort(0)
) u_part_sel_idx (
.clk_i,
.rst_ni,
.req_i ( tlul_part_sel_oh ),
.data_i ( '{default: '0} ),
.gnt_o ( ), // unused
.idx_o ( tlul_part_idx ),
.valid_o ( ), // unused
.data_o ( ), // unused
.ready_i ( 1'b0 )
);
logic tlul_oob_err_d, tlul_oob_err_q;
logic [NumPart-1:0] part_tlul_req, part_tlul_gnt, part_tlul_rvalid;
logic [SwWindowAddrWidth-1:0] part_tlul_addr;
logic [NumPart-1:0][1:0] part_tlul_rerror;
logic [NumPart-1:0][31:0] part_tlul_rdata;
always_comb begin : p_tlul_assign
// Send request to the correct partition.
part_tlul_addr = tlul_addr;
part_tlul_req = '0;
tlul_oob_err_d = 1'b0;
if (tlul_req) begin
if (tlul_part_sel_oh != '0) begin
part_tlul_req[tlul_part_idx] = 1'b1;
end else begin
// Error out in the next cycle if address was out of bounds.
tlul_oob_err_d = 1'b1;
end
end
// aggregate TL-UL responses
tlul_gnt = |part_tlul_gnt | tlul_oob_err_q;
tlul_rvalid = |part_tlul_rvalid | tlul_oob_err_q;
tlul_rerror = '0;
tlul_rdata = '0;
for (int k = 0; k < NumPart; k++) begin
tlul_rerror |= part_tlul_rerror[k];
tlul_rdata |= part_tlul_rdata[k];
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin : p_tlul_reg
if (!rst_ni) begin
tlul_oob_err_q <= 1'b0;
end else begin
tlul_oob_err_q <= tlul_oob_err_d;
end
end
///////////////////////////////
// Digests and LC State CSRs //
///////////////////////////////
logic [ScrmblBlockWidth-1:0] unused_digest;
logic [NumPart-1:0][ScrmblBlockWidth-1:0] part_digest;
assign hw2reg.vendor_test_digest = part_digest[VendorTestIdx];
assign hw2reg.creator_sw_cfg_digest = part_digest[CreatorSwCfgIdx];
assign hw2reg.owner_sw_cfg_digest = part_digest[OwnerSwCfgIdx];
assign hw2reg.hw_cfg_digest = part_digest[HwCfgIdx];
assign hw2reg.secret0_digest = part_digest[Secret0Idx];
assign hw2reg.secret1_digest = part_digest[Secret1Idx];
assign hw2reg.secret2_digest = part_digest[Secret2Idx];
// LC partition has no digest
assign unused_digest = part_digest[LifeCycleIdx];
//////////////////////////////
// Access Defaults and CSRs //
//////////////////////////////
// SEC_CM: ACCESS.CTRL.MUBI
part_access_t [NumPart-1:0] part_access_pre, part_access;
always_comb begin : p_access_control
// Default (this will be overridden by partition-internal settings).
part_access_pre = {{32'(2*NumPart)}{MuBi8False}};
// Permanently lock DAI write and read access to the life cycle partition.
// The LC partition can only be read from and written to via the LC controller.
// SEC_CM: LC_PART.MEM.SW_NOACCESS
part_access_pre[LifeCycleIdx].write_lock = MuBi8True;
part_access_pre[LifeCycleIdx].read_lock = MuBi8True;
// Propagate CSR read enables down to the SW_CFG partitions.
// SEC_CM: PART.MEM.REGREN
if (!reg2hw.vendor_test_read_lock) begin
part_access_pre[VendorTestIdx].read_lock = MuBi8True;
end
if (!reg2hw.creator_sw_cfg_read_lock) begin
part_access_pre[CreatorSwCfgIdx].read_lock = MuBi8True;
end
if (!reg2hw.owner_sw_cfg_read_lock) begin
part_access_pre[OwnerSwCfgIdx].read_lock = MuBi8True;
end
// The SECRET2 partition can only be accessed (write&read) when provisioning is enabled.
if (lc_creator_seed_sw_rw_en != lc_ctrl_pkg::On) begin
part_access_pre[Secret2Idx] = {2{MuBi8True}};
end
end
// This prevents the synthesis tool from optimizing the multibit signals.
for (genvar k = 0; k < NumPart; k++) begin : gen_bufs
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_write_lock (
.clk_i,
.rst_ni,
.mubi_i(part_access_pre[k].write_lock),
.mubi_o(part_access[k].write_lock)
);
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_read_lock (
.clk_i,
.rst_ni,
.mubi_i(part_access_pre[k].read_lock),
.mubi_o(part_access[k].read_lock)
);
end
//////////////////////
// DAI-related CSRs //
//////////////////////
logic dai_idle;
logic dai_req;
dai_cmd_e dai_cmd;
logic [OtpByteAddrWidth-1:0] dai_addr;
logic [NumDaiWords-1:0][31:0] dai_wdata, dai_rdata;
// Any write to this register triggers a DAI command.
assign dai_req = reg2hw.direct_access_cmd.digest.qe |
reg2hw.direct_access_cmd.wr.qe |
reg2hw.direct_access_cmd.rd.qe;
assign dai_cmd = dai_cmd_e'({reg2hw.direct_access_cmd.digest.q,
reg2hw.direct_access_cmd.wr.q,
reg2hw.direct_access_cmd.rd.q});
assign dai_addr = reg2hw.direct_access_address.q;
assign dai_wdata = reg2hw.direct_access_wdata;
assign hw2reg.direct_access_rdata = dai_rdata;
// This write-protects all DAI regs during pending operations.
assign hw2reg.direct_access_regwen.d = dai_idle;
// The DAI and the LCI can initiate write transactions, which
// are critical and we must not power down if such transactions
// are pending. Hence, we signal the LCI/DAI idle state to the
// power manager. This signal is flopped here as it has to
// cross a clock boundary to the power manager.
logic dai_prog_idle, lci_prog_idle, otp_idle_d, otp_idle_q;
assign otp_idle_d = lci_prog_idle & dai_prog_idle;
assign pwr_otp_o.otp_idle = otp_idle_q;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_idle_reg
if (!rst_ni) begin
otp_idle_q <= 1'b0;
end else begin
otp_idle_q <= otp_idle_d;
end
end
//////////////////////////////////////
// Ctrl/Status CSRs, Errors, Alerts //
//////////////////////////////////////
// Status and error reporting CSRs, error interrupt generation and alerts.
otp_err_e [NumPart+1:0] part_error;
logic [NumAgents-1:0] part_fsm_err;
logic [NumPart+1:0] part_errors_reduced;
logic otp_operation_done, otp_error;
logic fatal_macro_error_d, fatal_macro_error_q;
logic fatal_check_error_d, fatal_check_error_q;
logic fatal_bus_integ_error_d, fatal_bus_integ_error_q;
logic chk_pending, chk_timeout;
logic lfsr_fsm_err, scrmbl_fsm_err;
always_comb begin : p_errors_alerts
hw2reg.err_code = part_error;
// Note: since these are all fatal alert events, we latch them and keep on sending
// alert events via the alert senders. These regs can only be cleared via a system reset.
fatal_macro_error_d = fatal_macro_error_q;
fatal_check_error_d = fatal_check_error_q;
fatal_bus_integ_error_d = fatal_bus_integ_error_q | (|intg_error);
// These are the per-partition buffered escalation inputs
lc_escalate_en = lc_escalate_en_synced;
// Need a single wire for gating assertions in arbitration and CDC primitives.
lc_escalate_en_any = 1'b0;
// Aggregate all the macro alerts from the partitions
for (int k = 0; k < NumPart; k++) begin
// Filter for critical error codes that should not occur in the field.
fatal_macro_error_d |= part_error[k] == MacroError;
// While uncorrectable ECC errors are always reported, they do not trigger a fatal alert
// event in some partitions like the VENDOR_TEST partition.
if (PartInfo[k].ecc_fatal) begin
fatal_macro_error_d |= part_error[k] == MacroEccUncorrError;
end
end
// Aggregate all the macro alerts from the DAI/LCI
for (int k = NumPart; k < NumPart+2; k++) begin
// Filter for critical error codes that should not occur in the field.
fatal_macro_error_d |= part_error[k] inside {MacroError, MacroEccUncorrError};
end
// Aggregate all the remaining errors / alerts from the partitions and the DAI/LCI
for (int k = 0; k < NumPart+2; k++) begin
// Set the error bit if the error status of the corresponding partition is nonzero.
// Need to reverse the order here since the field enumeration in hw2reg.status is reversed.
part_errors_reduced[NumPart+1-k] = |part_error[k];
// Filter for integrity and consistency check failures.
fatal_check_error_d |= part_error[k] inside {CheckFailError, FsmStateError};
// If a fatal alert has been observed in any of the partitions/FSMs,
// we locally trigger escalation within OTP, which moves all FSMs
// to a terminal error state.
if (fatal_macro_error_q || fatal_check_error_q) begin
lc_escalate_en[k] = lc_ctrl_pkg::On;
end
if (lc_escalate_en[k] == lc_ctrl_pkg::On) begin
lc_escalate_en_any = 1'b1;
end
end
// Errors from other non-partition FSMs.
fatal_check_error_d |= chk_timeout |
lfsr_fsm_err |
scrmbl_fsm_err |
(|part_fsm_err);
end
// Assign these to the status register.
assign hw2reg.status = {part_errors_reduced,
chk_timeout,
lfsr_fsm_err,
scrmbl_fsm_err,
part_fsm_err[KdiIdx],
fatal_bus_integ_error_q,
dai_idle,
chk_pending};
// If we got an error, we trigger an interrupt.
logic [$bits(part_errors_reduced)+4-1:0] interrupt_triggers_d, interrupt_triggers_q;
// This makes sure that interrupts are not sticky.
assign interrupt_triggers_d = {
part_errors_reduced,
chk_timeout,
lfsr_fsm_err,
scrmbl_fsm_err,
|part_fsm_err
};
assign otp_error = |(interrupt_triggers_d & ~interrupt_triggers_q);
always_ff @(posedge clk_i or negedge rst_ni) begin : p_alert_regs
if (!rst_ni) begin
fatal_macro_error_q <= '0;
fatal_check_error_q <= '0;
fatal_bus_integ_error_q <= '0;
interrupt_triggers_q <= '0;
end else begin
fatal_macro_error_q <= fatal_macro_error_d;
fatal_check_error_q <= fatal_check_error_d;
fatal_bus_integ_error_q <= fatal_bus_integ_error_d;
interrupt_triggers_q <= interrupt_triggers_d;
end
end
//////////////////////////////////
// Interrupts and Alert Senders //
//////////////////////////////////
prim_intr_hw #(
.Width(1)
) u_intr_operation_done (
.clk_i,
.rst_ni,
.event_intr_i ( otp_operation_done ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_operation_done.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_operation_done.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_operation_done.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_operation_done.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_operation_done.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_operation_done.d ),
.intr_o ( intr_otp_operation_done_o )
);
prim_intr_hw #(
.Width(1)
) u_intr_error (
.clk_i,
.rst_ni,
.event_intr_i ( otp_error ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.otp_error.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.otp_error.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.otp_error.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.otp_error.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.otp_error.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.otp_error.d ),
.intr_o ( intr_otp_error_o )
);
logic [NumAlerts-1:0] alerts;
logic [NumAlerts-1:0] alert_test;
logic fatal_prim_otp_alert, recov_prim_otp_alert;
assign alerts = {
recov_prim_otp_alert,
fatal_prim_otp_alert,
fatal_bus_integ_error_q,
fatal_check_error_q,
fatal_macro_error_q
};
assign alert_test = {
reg2hw.alert_test.recov_prim_otp_alert.q &
reg2hw.alert_test.recov_prim_otp_alert.qe,
reg2hw.alert_test.fatal_prim_otp_alert.q &
reg2hw.alert_test.fatal_prim_otp_alert.qe,
reg2hw.alert_test.fatal_bus_integ_error.q &
reg2hw.alert_test.fatal_bus_integ_error.qe,
reg2hw.alert_test.fatal_check_error.q &
reg2hw.alert_test.fatal_check_error.qe,
reg2hw.alert_test.fatal_macro_error.q &
reg2hw.alert_test.fatal_macro_error.qe
};
localparam logic [NumAlerts-1:0] AlertIsFatal = {
1'b0, // recov_prim_otp_alert
1'b1, // fatal_prim_otp_alert
1'b1, // fatal_bus_integ_error_q
1'b1, // fatal_check_error_q
1'b1 // fatal_macro_error_q
};
for (genvar k = 0; k < NumAlerts; k++) begin : gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[k]),
.IsFatal(AlertIsFatal[k])
) u_prim_alert_sender (
.clk_i,
.rst_ni,
.alert_test_i ( alert_test[k] ),
.alert_req_i ( alerts[k] ),
.alert_ack_o ( ),
.alert_state_o ( ),
.alert_rx_i ( alert_rx_i[k] ),
.alert_tx_o ( alert_tx_o[k] )
);
end
////////////////////////////////
// LFSR Timer and CSR mapping //
////////////////////////////////
logic integ_chk_trig, cnsty_chk_trig;
logic [NumPart-1:0] integ_chk_req, integ_chk_ack;
logic [NumPart-1:0] cnsty_chk_req, cnsty_chk_ack;
logic lfsr_edn_req, lfsr_edn_ack;
logic [EdnDataWidth-1:0] edn_data;
assign integ_chk_trig = reg2hw.check_trigger.integrity.q &
reg2hw.check_trigger.integrity.qe;
assign cnsty_chk_trig = reg2hw.check_trigger.consistency.q &
reg2hw.check_trigger.consistency.qe;
// SEC_CM: PART.DATA_REG.BKGN_CHK
otp_ctrl_lfsr_timer #(
.RndCnstLfsrSeed(RndCnstLfsrSeed),
.RndCnstLfsrPerm(RndCnstLfsrPerm)
) u_otp_ctrl_lfsr_timer (
.clk_i,
.rst_ni,
.edn_req_o ( lfsr_edn_req ),
.edn_ack_i ( lfsr_edn_ack ),
.edn_data_i ( edn_data ),
// We can enable the timer once OTP has initialized.
// Note that this is only the initial release that gets
// the timer FSM into an operational state.
// Whether or not the timers / background checks are
// activated depends on the CSR configuration (by default
// they are switched off).
.timer_en_i ( pwr_otp_o.otp_done ),
// This idle signal is the same that is output to the power
// manager, and indicates whether there is an ongoing OTP programming
// operation. It is used to pause the consistency check timeout
// counter in order to prevent spurious timeouts (OTP programming
// operations are very slow compared to readout operations and can
// hence interfere with the timeout mechanism).
.otp_prog_busy_i ( ~otp_idle_d ),
.integ_chk_trig_i ( integ_chk_trig ),
.cnsty_chk_trig_i ( cnsty_chk_trig ),
.chk_pending_o ( chk_pending ),
.timeout_i ( reg2hw.check_timeout.q ),
.integ_period_msk_i ( reg2hw.integrity_check_period.q ),
.cnsty_period_msk_i ( reg2hw.consistency_check_period.q ),
.integ_chk_req_o ( integ_chk_req ),
.cnsty_chk_req_o ( cnsty_chk_req ),
.integ_chk_ack_i ( integ_chk_ack ),
.cnsty_chk_ack_i ( cnsty_chk_ack ),
.escalate_en_i ( lc_escalate_en[NumAgents] ),
.chk_timeout_o ( chk_timeout ),
.fsm_err_o ( lfsr_fsm_err )
);
///////////////////////////////////////
// EDN Arbitration, Request and Sync //
///////////////////////////////////////
// Both the key derivation and LFSR reseeding are low bandwidth,
// hence they can share the same EDN interface.
logic edn_req, edn_ack;
logic key_edn_req, key_edn_ack;
prim_arbiter_tree #(
.N(2),
.EnDataPort(0)
) u_edn_arb (
.clk_i,
.rst_ni,
.req_chk_i ( ~lc_escalate_en_any ),
.req_i ( {lfsr_edn_req, key_edn_req} ),
.data_i ( '{default: '0} ),
.gnt_o ( {lfsr_edn_ack, key_edn_ack} ),
.idx_o ( ), // unused
.valid_o ( edn_req ),
.data_o ( ), // unused
.ready_i ( edn_ack )
);
// This synchronizes the data coming from EDN and stacks the
// 32bit EDN words to achieve an internal entropy width of 64bit.
prim_edn_req #(
.OutWidth(EdnDataWidth)
) u_prim_edn_req (
.clk_i,
.rst_ni,
.req_chk_i ( ~lc_escalate_en_any ),
.req_i ( edn_req ),
.ack_o ( edn_ack ),
.data_o ( edn_data ),
.fips_o ( ), // unused
.err_o ( ), // unused
.clk_edn_i,
.rst_edn_ni,
.edn_o,
.edn_i
);
///////////////////////////////
// OTP Macro and Arbitration //
///////////////////////////////
typedef struct packed {
prim_otp_pkg::cmd_e cmd;
logic [OtpSizeWidth-1:0] size; // Number of native words to write.
logic [OtpIfWidth-1:0] wdata;
logic [OtpAddrWidth-1:0] addr; // Halfword address.
} otp_bundle_t;
logic [NumAgents-1:0] part_otp_arb_req, part_otp_arb_gnt;
otp_bundle_t part_otp_arb_bundle [NumAgents];
logic otp_arb_valid, otp_arb_ready;
logic otp_prim_valid, otp_prim_ready;
logic otp_rsp_fifo_valid, otp_rsp_fifo_ready;
logic [vbits(NumAgents)-1:0] otp_arb_idx;
otp_bundle_t otp_arb_bundle;
// The OTP interface is arbitrated on a per-cycle basis, meaning that back-to-back
// transactions can be completely independent.
prim_arbiter_tree #(
.N(NumAgents),
.DW($bits(otp_bundle_t))
) u_otp_arb (
.clk_i,
.rst_ni,
.req_chk_i ( ~lc_escalate_en_any ),
.req_i ( part_otp_arb_req ),
.data_i ( part_otp_arb_bundle ),
.gnt_o ( part_otp_arb_gnt ),
.idx_o ( otp_arb_idx ),
.valid_o ( otp_arb_valid ),
.data_o ( otp_arb_bundle ),
.ready_i ( otp_arb_ready )
);
// Don't issue more transactions than what the rsp_fifo can keep track of.
assign otp_arb_ready = otp_prim_ready & otp_rsp_fifo_ready;
assign otp_prim_valid = otp_arb_valid & otp_rsp_fifo_ready;
assign otp_rsp_fifo_valid = otp_prim_ready & otp_prim_valid;
prim_otp_pkg::err_e part_otp_err;
logic [OtpIfWidth-1:0] part_otp_rdata;
logic otp_rvalid;
tlul_pkg::tl_h2d_t prim_tl_h2d_gated;
tlul_pkg::tl_d2h_t prim_tl_d2h_gated;
// Life cycle qualification of TL-UL test interface.
// SEC_CM: TEST.BUS.LC_GATED
// SEC_CM: TEST_TL_LC_GATE.FSM.SPARSE
tlul_lc_gate #(
.NumGatesPerDirection(2)
) u_tlul_lc_gate (
.clk_i,
.rst_ni,
.tl_h2d_i(prim_tl_i),
.tl_d2h_o(prim_tl_o),
.tl_h2d_o(prim_tl_h2d_gated),
.tl_d2h_i(prim_tl_d2h_gated),
.lc_en_i (lc_dft_en[0]),
.flush_req_i('0),
.flush_ack_o(),
.resp_pending_o(),
.err_o (intg_error[2])
);
// Test-related GPIOs.
// SEC_CM: TEST.BUS.LC_GATED
logic [OtpTestVectWidth-1:0] otp_test_vect;
assign cio_test_o = (lc_ctrl_pkg::lc_tx_test_true_strict(lc_dft_en[1])) ?
otp_test_vect : '0;
assign cio_test_en_o = (lc_ctrl_pkg::lc_tx_test_true_strict(lc_dft_en[2])) ?
{OtpTestVectWidth{1'b1}} : '0;
// SEC_CM: MACRO.MEM.CM, MACRO.MEM.INTEGRITY
prim_otp #(
.Width ( OtpWidth ),
.Depth ( OtpDepth ),
.SizeWidth ( OtpSizeWidth ),
.PwrSeqWidth ( OtpPwrSeqWidth ),
.TestCtrlWidth ( OtpTestCtrlWidth ),
.TestStatusWidth ( OtpTestStatusWidth ),
.TestVectWidth ( OtpTestVectWidth ),
.MemInitFile ( MemInitFile ),
.VendorTestOffset ( VendorTestOffset ),
.VendorTestSize ( VendorTestSize )
) u_otp (
.clk_i,
.rst_ni,
// Observability controls to/from AST
.obs_ctrl_i,
.otp_obs_o,
// Power sequencing signals to/from AST
.pwr_seq_o ( otp_ast_pwr_seq_o.pwr_seq ),
.pwr_seq_h_i ( otp_ast_pwr_seq_h_i.pwr_seq_h ),
.ext_voltage_io ( otp_ext_voltage_h_io ),
// Test interface
.test_ctrl_i ( lc_otp_vendor_test_i.ctrl ),
.test_status_o ( lc_otp_vendor_test_o.status ),
.test_vect_o ( otp_test_vect ),
.test_tl_i ( prim_tl_h2d_gated ),
.test_tl_o ( prim_tl_d2h_gated ),
// Other DFT signals
.scan_en_i,
.scan_rst_ni,
.scanmode_i,
// Alerts
.fatal_alert_o ( fatal_prim_otp_alert ),
.recov_alert_o ( recov_prim_otp_alert ),
// Read / Write command interface
.ready_o ( otp_prim_ready ),
.valid_i ( otp_prim_valid ),
.cmd_i ( otp_arb_bundle.cmd ),
.size_i ( otp_arb_bundle.size ),
.addr_i ( otp_arb_bundle.addr ),
.wdata_i ( otp_arb_bundle.wdata ),
// Read data out
.valid_o ( otp_rvalid ),
.rdata_o ( part_otp_rdata ),
.err_o ( part_otp_err )
);
logic otp_fifo_valid;
logic [vbits(NumAgents)-1:0] otp_part_idx;
logic [NumAgents-1:0] part_otp_rvalid;
// We can have up to two OTP commands in flight, hence we size this to be 2 deep.
// The partitions can unconditionally sink requested data.
prim_fifo_sync #(
.Width(vbits(NumAgents)),
.Depth(2)
) u_otp_rsp_fifo (
.clk_i,
.rst_ni,
.clr_i ( 1'b0 ),
.wvalid_i ( otp_rsp_fifo_valid ),
.wready_o ( otp_rsp_fifo_ready ),
.wdata_i ( otp_arb_idx ),
.rvalid_o ( otp_fifo_valid ),
.rready_i ( otp_rvalid ),
.rdata_o ( otp_part_idx ),
.depth_o ( ),
.full_o ( ),
.err_o ( )
);
// Steer response back to the partition where this request originated.
always_comb begin : p_rvalid
part_otp_rvalid = '0;
part_otp_rvalid[otp_part_idx] = otp_rvalid & otp_fifo_valid;
end
// Note that this must be true by construction.
`ASSERT(OtpRespFifoUnderflow_A, otp_rvalid |-> otp_fifo_valid)
/////////////////////////////////////////
// Scrambling Datapath and Arbitration //
/////////////////////////////////////////
// Note: as opposed to the OTP arbitration above, we do not perform cycle-wise arbitration, but
// transaction-wise arbitration. This is implemented using a RR arbiter that acts as a mutex.
// I.e., each agent (e.g. the DAI or a partition) can request a lock on the mutex. Once granted,
// the partition can keep the the lock as long as needed for the transaction to complete. The
// partition must yield its lock by deasserting the request signal for the arbiter to proceed.
// Since this scheme does not have built-in preemtion, it must be ensured that the agents
// eventually release their locks for this to be fair.
//
// See also https://docs.opentitan.org/hw/ip/otp_ctrl/doc/index.html#block-diagram for details.
typedef struct packed {
otp_scrmbl_cmd_e cmd;
digest_mode_e mode;
logic [ConstSelWidth-1:0] sel;
logic [ScrmblBlockWidth-1:0] data;
logic valid;
} scrmbl_bundle_t;
logic [NumAgents-1:0] part_scrmbl_mtx_req, part_scrmbl_mtx_gnt;
scrmbl_bundle_t part_scrmbl_req_bundle [NumAgents];
scrmbl_bundle_t scrmbl_req_bundle;
logic [vbits(NumAgents)-1:0] scrmbl_mtx_idx;
logic scrmbl_mtx_valid;
// Note that arbiter decisions do not change when backpressured.
// Hence, the idx_o signal is guaranteed to remain stable until ack'ed.
prim_arbiter_tree #(
.N(NumAgents),
.DW($bits(scrmbl_bundle_t))
) u_scrmbl_mtx (
.clk_i,
.rst_ni,
.req_chk_i ( 1'b0 ), // REQ is allowed to go low again without ACK even
// during normal operation.
.req_i ( part_scrmbl_mtx_req ),
.data_i ( part_scrmbl_req_bundle ),
.gnt_o ( ),
.idx_o ( scrmbl_mtx_idx ),
.valid_o ( scrmbl_mtx_valid ),
.data_o ( scrmbl_req_bundle ),
.ready_i ( 1'b0 )
);
// Since the ready_i signal of the arbiter is statically set to 1'b0 above, we are always in a
// "backpressure" situation, where the RR arbiter will automatically advance the internal RR state
// to give the current winner max priority in subsequent cycles in order to keep the decision
// stable. Rearbitration occurs once the winning agent deasserts its request.
always_comb begin : p_mutex
part_scrmbl_mtx_gnt = '0;
part_scrmbl_mtx_gnt[scrmbl_mtx_idx] = scrmbl_mtx_valid;
end
logic [ScrmblBlockWidth-1:0] part_scrmbl_rsp_data;
logic scrmbl_arb_req_ready, scrmbl_arb_rsp_valid;
logic [NumAgents-1:0] part_scrmbl_req_ready, part_scrmbl_rsp_valid;
// SEC_CM: SECRET.MEM.SCRAMBLE
// SEC_CM: PART.MEM.DIGEST
otp_ctrl_scrmbl u_otp_ctrl_scrmbl (
.clk_i,
.rst_ni,
.cmd_i ( scrmbl_req_bundle.cmd ),
.mode_i ( scrmbl_req_bundle.mode ),
.sel_i ( scrmbl_req_bundle.sel ),
.data_i ( scrmbl_req_bundle.data ),
.valid_i ( scrmbl_req_bundle.valid ),
.ready_o ( scrmbl_arb_req_ready ),
.data_o ( part_scrmbl_rsp_data ),
.valid_o ( scrmbl_arb_rsp_valid ),
.escalate_en_i ( lc_escalate_en[NumAgents+1] ),
.fsm_err_o ( scrmbl_fsm_err )
);
// steer back responses
always_comb begin : p_scmrbl_resp
part_scrmbl_req_ready = '0;
part_scrmbl_rsp_valid = '0;
part_scrmbl_req_ready[scrmbl_mtx_idx] = scrmbl_arb_req_ready;
part_scrmbl_rsp_valid[scrmbl_mtx_idx] = scrmbl_arb_rsp_valid;
end
/////////////////////////////
// Direct Access Interface //
/////////////////////////////
logic part_init_req;
logic [NumPart-1:0] part_init_done;
part_access_t [NumPart-1:0] part_access_dai;
// The init request comes from the power manager, which lives in the AON clock domain.
logic pwr_otp_req_synced;
prim_flop_2sync #(
.Width(1)
) u_otp_init_sync (
.clk_i,
.rst_ni,
.d_i ( pwr_otp_i.otp_init ),
.q_o ( pwr_otp_req_synced )
);
// Register this signal as it has to cross a clock boundary.
logic pwr_otp_rsp_d, pwr_otp_rsp_q;
assign pwr_otp_o.otp_done = pwr_otp_rsp_q;
always_ff @(posedge clk_i or negedge rst_ni) begin : p_init_reg
if (!rst_ni) begin
pwr_otp_rsp_q <= 1'b0;
end else begin
pwr_otp_rsp_q <= pwr_otp_rsp_d;
end
end
otp_ctrl_dai u_otp_ctrl_dai (
.clk_i,
.rst_ni,
.init_req_i ( pwr_otp_req_synced ),
.init_done_o ( pwr_otp_rsp_d ),
.part_init_req_o ( part_init_req ),
.part_init_done_i ( part_init_done ),
.escalate_en_i ( lc_escalate_en[DaiIdx] ),
.error_o ( part_error[DaiIdx] ),
.fsm_err_o ( part_fsm_err[DaiIdx] ),
.part_access_i ( part_access_dai ),
.dai_addr_i ( dai_addr ),
.dai_cmd_i ( dai_cmd ),
.dai_req_i ( dai_req ),
.dai_wdata_i ( dai_wdata ),
.dai_idle_o ( dai_idle ),
.dai_prog_idle_o ( dai_prog_idle ),
.dai_cmd_done_o ( otp_operation_done ),
.dai_rdata_o ( dai_rdata ),
.otp_req_o ( part_otp_arb_req[DaiIdx] ),
.otp_cmd_o ( part_otp_arb_bundle[DaiIdx].cmd ),
.otp_size_o ( part_otp_arb_bundle[DaiIdx].size ),
.otp_wdata_o ( part_otp_arb_bundle[DaiIdx].wdata ),
.otp_addr_o ( part_otp_arb_bundle[DaiIdx].addr ),
.otp_gnt_i ( part_otp_arb_gnt[DaiIdx] ),
.otp_rvalid_i ( part_otp_rvalid[DaiIdx] ),
.otp_rdata_i ( part_otp_rdata ),
.otp_err_i ( part_otp_err ),
.scrmbl_mtx_req_o ( part_scrmbl_mtx_req[DaiIdx] ),
.scrmbl_mtx_gnt_i ( part_scrmbl_mtx_gnt[DaiIdx] ),
.scrmbl_cmd_o ( part_scrmbl_req_bundle[DaiIdx].cmd ),
.scrmbl_mode_o ( part_scrmbl_req_bundle[DaiIdx].mode ),
.scrmbl_sel_o ( part_scrmbl_req_bundle[DaiIdx].sel ),
.scrmbl_data_o ( part_scrmbl_req_bundle[DaiIdx].data ),
.scrmbl_valid_o ( part_scrmbl_req_bundle[DaiIdx].valid ),
.scrmbl_ready_i ( part_scrmbl_req_ready[DaiIdx] ),
.scrmbl_valid_i ( part_scrmbl_rsp_valid[DaiIdx] ),
.scrmbl_data_i ( part_scrmbl_rsp_data )
);
////////////////////////////////////
// Lifecycle Transition Interface //
////////////////////////////////////
logic [PartInfo[LifeCycleIdx].size-1:0][7:0] lc_otp_program_data;
assign lc_otp_program_data[LcStateOffset-LifeCycleOffset +: LcStateSize] =
lc_otp_program_i.state;
assign lc_otp_program_data[LcTransitionCntOffset-LifeCycleOffset +: LcTransitionCntSize] =
lc_otp_program_i.count;
otp_ctrl_lci #(
.Info(PartInfo[LifeCycleIdx])
) u_otp_ctrl_lci (
.clk_i,
.rst_ni,
.lci_en_i ( pwr_otp_o.otp_done ),
.escalate_en_i ( lc_escalate_en[LciIdx] ),
.error_o ( part_error[LciIdx] ),
.fsm_err_o ( part_fsm_err[LciIdx] ),
.lci_prog_idle_o ( lci_prog_idle ),
.lc_req_i ( lc_otp_program_i.req ),
.lc_data_i ( lc_otp_program_data ),
.lc_ack_o ( lc_otp_program_o.ack ),
.lc_err_o ( lc_otp_program_o.err ),
.otp_req_o ( part_otp_arb_req[LciIdx] ),
.otp_cmd_o ( part_otp_arb_bundle[LciIdx].cmd ),
.otp_size_o ( part_otp_arb_bundle[LciIdx].size ),
.otp_wdata_o ( part_otp_arb_bundle[LciIdx].wdata ),
.otp_addr_o ( part_otp_arb_bundle[LciIdx].addr ),
.otp_gnt_i ( part_otp_arb_gnt[LciIdx] ),
.otp_rvalid_i ( part_otp_rvalid[LciIdx] ),
.otp_rdata_i ( part_otp_rdata ),
.otp_err_i ( part_otp_err )
);
// Tie off unused connections.
assign part_scrmbl_mtx_req[LciIdx] = '0;
assign part_scrmbl_req_bundle[LciIdx] = '0;
// This stops lint from complaining about unused signals.
logic unused_lci_scrmbl_sigs;
assign unused_lci_scrmbl_sigs = ^{part_scrmbl_mtx_gnt[LciIdx],
part_scrmbl_req_ready[LciIdx],
part_scrmbl_rsp_valid[LciIdx]};
////////////////////////////////////
// Key Derivation Interface (KDI) //
////////////////////////////////////
logic scrmbl_key_seed_valid;
logic [SramKeySeedWidth-1:0] sram_data_key_seed;
logic [FlashKeySeedWidth-1:0] flash_data_key_seed, flash_addr_key_seed;
otp_ctrl_kdi #(
.RndCnstScrmblKeyInit(RndCnstScrmblKeyInit)
) u_otp_ctrl_kdi (
.clk_i,
.rst_ni,
.kdi_en_i ( pwr_otp_o.otp_done ),
.escalate_en_i ( lc_escalate_en[KdiIdx] ),
.fsm_err_o ( part_fsm_err[KdiIdx] ),
.scrmbl_key_seed_valid_i ( scrmbl_key_seed_valid ),
.flash_data_key_seed_i ( flash_data_key_seed ),
.flash_addr_key_seed_i ( flash_addr_key_seed ),
.sram_data_key_seed_i ( sram_data_key_seed ),
.edn_req_o ( key_edn_req ),
.edn_ack_i ( key_edn_ack ),
.edn_data_i ( edn_data ),
.flash_otp_key_i,
.flash_otp_key_o,
.sram_otp_key_i,
.sram_otp_key_o,
.otbn_otp_key_i,
.otbn_otp_key_o,
.scrmbl_mtx_req_o ( part_scrmbl_mtx_req[KdiIdx] ),
.scrmbl_mtx_gnt_i ( part_scrmbl_mtx_gnt[KdiIdx] ),
.scrmbl_cmd_o ( part_scrmbl_req_bundle[KdiIdx].cmd ),
.scrmbl_mode_o ( part_scrmbl_req_bundle[KdiIdx].mode ),
.scrmbl_sel_o ( part_scrmbl_req_bundle[KdiIdx].sel ),
.scrmbl_data_o ( part_scrmbl_req_bundle[KdiIdx].data ),
.scrmbl_valid_o ( part_scrmbl_req_bundle[KdiIdx].valid ),
.scrmbl_ready_i ( part_scrmbl_req_ready[KdiIdx] ),
.scrmbl_valid_i ( part_scrmbl_rsp_valid[KdiIdx] ),
.scrmbl_data_i ( part_scrmbl_rsp_data )
);
// Tie off OTP bus access, since this is not needed.
assign part_otp_arb_req[KdiIdx] = 1'b0;
assign part_otp_arb_bundle[KdiIdx] = '0;
// This stops lint from complaining about unused signals.
logic unused_kdi_otp_sigs;
assign unused_kdi_otp_sigs = ^{part_otp_arb_gnt[KdiIdx],
part_otp_rvalid[KdiIdx]};
/////////////////////////
// Partition Instances //
/////////////////////////
logic [$bits(PartInvDefault)/8-1:0][7:0] part_buf_data;
for (genvar k = 0; k < NumPart; k ++) begin : gen_partitions
////////////////////////////////////////////////////////////////////////////////////////////////
if (PartInfo[k].variant == Unbuffered) begin : gen_unbuffered
otp_ctrl_part_unbuf #(
.Info(PartInfo[k])
) u_part_unbuf (
.clk_i,
.rst_ni,
.init_req_i ( part_init_req ),
.init_done_o ( part_init_done[k] ),
.escalate_en_i ( lc_escalate_en[k] ),
.error_o ( part_error[k] ),
.fsm_err_o ( part_fsm_err[k] ),
.access_i ( part_access[k] ),
.access_o ( part_access_dai[k] ),
.digest_o ( part_digest[k] ),
.tlul_req_i ( part_tlul_req[k] ),
.tlul_gnt_o ( part_tlul_gnt[k] ),
.tlul_addr_i ( part_tlul_addr ),
.tlul_rerror_o ( part_tlul_rerror[k] ),
.tlul_rvalid_o ( part_tlul_rvalid[k] ),
.tlul_rdata_o ( part_tlul_rdata[k] ),
.otp_req_o ( part_otp_arb_req[k] ),
.otp_cmd_o ( part_otp_arb_bundle[k].cmd ),
.otp_size_o ( part_otp_arb_bundle[k].size ),
.otp_wdata_o ( part_otp_arb_bundle[k].wdata ),
.otp_addr_o ( part_otp_arb_bundle[k].addr ),
.otp_gnt_i ( part_otp_arb_gnt[k] ),
.otp_rvalid_i ( part_otp_rvalid[k] ),
.otp_rdata_i ( part_otp_rdata ),
.otp_err_i ( part_otp_err )
);
// Tie off unused connections.
assign part_scrmbl_mtx_req[k] = '0;
assign part_scrmbl_req_bundle[k] = '0;
// These checks do not exist in this partition type,
// so we always acknowledge the request.
assign integ_chk_ack[k] = 1'b1;
assign cnsty_chk_ack[k] = 1'b1;
// No buffered data to expose.
assign part_buf_data[PartInfo[k].offset +: PartInfo[k].size] = '0;
// This stops lint from complaining about unused signals.
logic unused_part_scrmbl_sigs;
assign unused_part_scrmbl_sigs = ^{part_scrmbl_mtx_gnt[k],
part_scrmbl_req_ready[k],
part_scrmbl_rsp_valid[k],
integ_chk_req[k],
cnsty_chk_req[k]};
// Alert assertion for sparse FSM.
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartUnbufFsmCheck_A,
u_part_unbuf.u_state_regs, alert_tx_o[1])
////////////////////////////////////////////////////////////////////////////////////////////////
end else if (PartInfo[k].variant == Buffered) begin : gen_buffered
otp_ctrl_part_buf #(
.Info(PartInfo[k]),
.DataDefault(PartInvDefault[PartInfo[k].offset*8 +: PartInfo[k].size*8])
) u_part_buf (
.clk_i,
.rst_ni,
.init_req_i ( part_init_req ),
.init_done_o ( part_init_done[k] ),
.integ_chk_req_i ( integ_chk_req[k] ),
.integ_chk_ack_o ( integ_chk_ack[k] ),
.cnsty_chk_req_i ( cnsty_chk_req[k] ),
.cnsty_chk_ack_o ( cnsty_chk_ack[k] ),
.escalate_en_i ( lc_escalate_en[k] ),
// Only supported by life cycle partition (see further below).
.check_byp_en_i ( lc_ctrl_pkg::Off ),
.error_o ( part_error[k] ),
.fsm_err_o ( part_fsm_err[k] ),
.access_i ( part_access[k] ),
.access_o ( part_access_dai[k] ),
.digest_o ( part_digest[k] ),
.data_o ( part_buf_data[PartInfo[k].offset +: PartInfo[k].size] ),
.otp_req_o ( part_otp_arb_req[k] ),
.otp_cmd_o ( part_otp_arb_bundle[k].cmd ),
.otp_size_o ( part_otp_arb_bundle[k].size ),
.otp_wdata_o ( part_otp_arb_bundle[k].wdata ),
.otp_addr_o ( part_otp_arb_bundle[k].addr ),
.otp_gnt_i ( part_otp_arb_gnt[k] ),
.otp_rvalid_i ( part_otp_rvalid[k] ),
.otp_rdata_i ( part_otp_rdata ),
.otp_err_i ( part_otp_err ),
.scrmbl_mtx_req_o ( part_scrmbl_mtx_req[k] ),
.scrmbl_mtx_gnt_i ( part_scrmbl_mtx_gnt[k] ),
.scrmbl_cmd_o ( part_scrmbl_req_bundle[k].cmd ),
.scrmbl_mode_o ( part_scrmbl_req_bundle[k].mode ),
.scrmbl_sel_o ( part_scrmbl_req_bundle[k].sel ),
.scrmbl_data_o ( part_scrmbl_req_bundle[k].data ),
.scrmbl_valid_o ( part_scrmbl_req_bundle[k].valid ),
.scrmbl_ready_i ( part_scrmbl_req_ready[k] ),
.scrmbl_valid_i ( part_scrmbl_rsp_valid[k] ),
.scrmbl_data_i ( part_scrmbl_rsp_data )
);
// Buffered partitions are not accessible via the TL-UL window.
logic unused_part_tlul_sigs;
assign unused_part_tlul_sigs = ^part_tlul_req[k];
assign part_tlul_gnt[k] = 1'b0;
assign part_tlul_rerror[k] = '0;
assign part_tlul_rvalid[k] = 1'b0;
assign part_tlul_rdata[k] = '0;
// Alert assertion for sparse FSM.
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartBufFsmCheck_A,
u_part_buf.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntPartBufCheck_A,
u_part_buf.u_prim_count, alert_tx_o[1])
////////////////////////////////////////////////////////////////////////////////////////////////
end else if (PartInfo[k].variant == LifeCycle) begin : gen_lifecycle
otp_ctrl_part_buf #(
.Info(PartInfo[k]),
.DataDefault(PartInvDefault[PartInfo[k].offset*8 +: PartInfo[k].size*8])
) u_part_buf (
.clk_i,
.rst_ni,
.init_req_i ( part_init_req ),
.init_done_o ( part_init_done[k] ),
.integ_chk_req_i ( integ_chk_req[k] ),
.integ_chk_ack_o ( integ_chk_ack[k] ),
.cnsty_chk_req_i ( cnsty_chk_req[k] ),
.cnsty_chk_ack_o ( cnsty_chk_ack[k] ),
.escalate_en_i ( lc_escalate_en[k] ),
// This is only supported by the life cycle partition. We need to prevent this partition
// from escalating once the life cycle state in memory is being updated (and hence not
// consistent with the values in the buffer regs anymore).
.check_byp_en_i ( lc_check_byp_en ),
.error_o ( part_error[k] ),
.fsm_err_o ( part_fsm_err[k] ),
.access_i ( part_access[k] ),
.access_o ( part_access_dai[k] ),
.digest_o ( part_digest[k] ),
.data_o ( part_buf_data[PartInfo[k].offset +: PartInfo[k].size] ),
.otp_req_o ( part_otp_arb_req[k] ),
.otp_cmd_o ( part_otp_arb_bundle[k].cmd ),
.otp_size_o ( part_otp_arb_bundle[k].size ),
.otp_wdata_o ( part_otp_arb_bundle[k].wdata ),
.otp_addr_o ( part_otp_arb_bundle[k].addr ),
.otp_gnt_i ( part_otp_arb_gnt[k] ),
.otp_rvalid_i ( part_otp_rvalid[k] ),
.otp_rdata_i ( part_otp_rdata ),
.otp_err_i ( part_otp_err ),
// The LC partition does not need any scrambling features.
.scrmbl_mtx_req_o ( ),
.scrmbl_mtx_gnt_i ( 1'b0 ),
.scrmbl_cmd_o ( ),
.scrmbl_mode_o ( ),
.scrmbl_sel_o ( ),
.scrmbl_data_o ( ),
.scrmbl_valid_o ( ),
.scrmbl_ready_i ( 1'b0 ),
.scrmbl_valid_i ( 1'b0 ),
.scrmbl_data_i ( '0 )
);
// Buffered partitions are not accessible via the TL-UL window.
logic unused_part_tlul_sigs;
assign unused_part_tlul_sigs = ^part_tlul_req[k];
assign part_tlul_gnt[k] = 1'b0;
assign part_tlul_rerror[k] = '0;
assign part_tlul_rvalid[k] = 1'b0;
assign part_tlul_rdata[k] = '0;
// Tie off unused connections.
assign part_scrmbl_mtx_req[k] = '0;
assign part_scrmbl_req_bundle[k] = '0;
// This stops lint from complaining about unused signals.
logic unused_part_scrmbl_sigs;
assign unused_part_scrmbl_sigs = ^{part_scrmbl_mtx_gnt[k],
part_scrmbl_req_ready[k],
part_scrmbl_rsp_valid[k]};
// Alert assertion for sparse FSM.
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlPartLcFsmCheck_A,
u_part_buf.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntPartLcCheck_A,
u_part_buf.u_prim_count, alert_tx_o[1])
////////////////////////////////////////////////////////////////////////////////////////////////
end else begin : gen_invalid
// This is invalid and should break elaboration
assert_static_in_generate_invalid assert_static_in_generate_invalid();
end
////////////////////////////////////////////////////////////////////////////////////////////////
end
//////////////////////////////////
// Buffered Data Output Mapping //
//////////////////////////////////
// Output complete hardware config partition.
// Actual mapping to other IPs is done via the intersignal topgen feature,
// selection of fields can be done using the otp_hw_cfg_t struct fields.
assign otp_hw_cfg_o.valid = (part_init_done[HwCfgIdx]) ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off;
assign otp_hw_cfg_o.data = otp_hw_cfg_data_t'(part_buf_data[HwCfgOffset +: HwCfgSize]);
// Root keys
logic otp_keymgr_key_valid_d, otp_keymgr_key_valid_q; // need to latch valid
assign otp_keymgr_key_valid_d = part_digest[Secret2Idx] != '0;
assign otp_keymgr_key_o.valid = otp_keymgr_key_valid_q;
assign otp_keymgr_key_o.key_share0 = (lc_seed_hw_rd_en == lc_ctrl_pkg::On) ?
part_buf_data[CreatorRootKeyShare0Offset +:
CreatorRootKeyShare0Size] :
PartInvDefault[CreatorRootKeyShare0Offset*8 +:
CreatorRootKeyShare0Size*8];
assign otp_keymgr_key_o.key_share1 = (lc_seed_hw_rd_en == lc_ctrl_pkg::On) ?
part_buf_data[CreatorRootKeyShare1Offset +:
CreatorRootKeyShare1Size] :
PartInvDefault[CreatorRootKeyShare1Offset*8 +:
CreatorRootKeyShare1Size*8];
prim_flop #(
.Width(1)
) u_keygmr_key_valid (
.clk_i,
.rst_ni,
.d_i (otp_keymgr_key_valid_d),
.q_o (otp_keymgr_key_valid_q)
);
// as key_share0, key_share1 are directly connected from digest and go
// through mux, Assertion is added to check the relationship between
// lc_seed_hw_rd_en and key_valid
`ASSERT(LcSeedHwRdEnStable_A,
$rose(otp_keymgr_key_valid_q) |=> $stable(lc_seed_hw_rd_en) [*1:$],
clk_i,
!rst_ni || (lc_escalate_en_i == lc_ctrl_pkg::On) // Disable assertion if esc is high
)
// Scrambling Keys
assign scrmbl_key_seed_valid = part_digest[Secret1Idx] != '0;
assign sram_data_key_seed = part_buf_data[SramDataKeySeedOffset +:
SramDataKeySeedSize];
assign flash_data_key_seed = part_buf_data[FlashDataKeySeedOffset +:
FlashDataKeySeedSize];
assign flash_addr_key_seed = part_buf_data[FlashAddrKeySeedOffset +:
FlashAddrKeySeedSize];
// Test unlock and exit tokens and RMA token
assign otp_lc_data_o.test_exit_token = part_buf_data[TestExitTokenOffset +:
TestExitTokenSize];
assign otp_lc_data_o.test_unlock_token = part_buf_data[TestUnlockTokenOffset +:
TestUnlockTokenSize];
assign otp_lc_data_o.rma_token = part_buf_data[RmaTokenOffset +:
RmaTokenSize];
lc_ctrl_pkg::lc_tx_t test_tokens_valid, rma_token_valid, secrets_valid;
// The test tokens have been provisioned.
assign test_tokens_valid = (part_digest[Secret0Idx] != '0) ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off;
// The rma token has been provisioned.
assign rma_token_valid = (part_digest[Secret2Idx] != '0) ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off;
// The device is personalized if the root key has been provisioned and locked.
assign secrets_valid = (part_digest[Secret2Idx] != '0) ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off;
// Buffer these constants in order to ensure that synthesis does not try to optimize the encoding.
// SEC_CM: TOKEN_VALID.CTRL.MUBI
prim_lc_sender #(
.AsyncOn(0)
) u_prim_lc_sender_test_tokens_valid (
.clk_i,
.rst_ni,
.lc_en_i(test_tokens_valid),
.lc_en_o(otp_lc_data_o.test_tokens_valid)
);
prim_lc_sender #(
.AsyncOn(0)
) u_prim_lc_sender_rma_token_valid (
.clk_i,
.rst_ni,
.lc_en_i(rma_token_valid),
.lc_en_o(otp_lc_data_o.rma_token_valid)
);
prim_lc_sender #(
.AsyncOn(0)
) u_prim_lc_sender_secrets_valid (
.clk_i,
.rst_ni,
.lc_en_i(secrets_valid),
.lc_en_o(otp_lc_data_o.secrets_valid)
);
// Lifecycle state
assign otp_lc_data_o.state = lc_ctrl_state_pkg::lc_state_e'(part_buf_data[LcStateOffset +:
LcStateSize]);
assign otp_lc_data_o.count = lc_ctrl_state_pkg::lc_cnt_e'(part_buf_data[LcTransitionCntOffset +:
LcTransitionCntSize]);
// Assert life cycle state valid signal only when all partitions have initialized.
assign otp_lc_data_o.valid = &part_init_done;
// Signal whether there are any errors in the life cycle partition (both correctable and
// uncorrectable ones). This bit is made available via the JTAG TAP, which is useful for
// production testing in RAW life cycle state where the OTP regs are not accessible.
assign otp_lc_data_o.error = |part_error[LifeCycleIdx];
// Not all bits of part_buf_data are used here.
logic unused_buf_data;
assign unused_buf_data = ^part_buf_data;
////////////////
// Assertions //
////////////////
`ASSERT_INIT(CreatorRootKeyShare0Size_A, KeyMgrKeyWidth == CreatorRootKeyShare0Size * 8)
`ASSERT_INIT(CreatorRootKeyShare1Size_A, KeyMgrKeyWidth == CreatorRootKeyShare1Size * 8)
`ASSERT_INIT(FlashDataKeySeedSize_A, FlashKeySeedWidth == FlashDataKeySeedSize * 8)
`ASSERT_INIT(FlashAddrKeySeedSize_A, FlashKeySeedWidth == FlashAddrKeySeedSize * 8)
`ASSERT_INIT(SramDataKeySeedSize_A, SramKeySeedWidth == SramDataKeySeedSize * 8)
`ASSERT_INIT(RmaTokenSize_A, lc_ctrl_state_pkg::LcTokenWidth == RmaTokenSize * 8)
`ASSERT_INIT(TestUnlockTokenSize_A, lc_ctrl_state_pkg::LcTokenWidth == TestUnlockTokenSize * 8)
`ASSERT_INIT(TestExitTokenSize_A, lc_ctrl_state_pkg::LcTokenWidth == TestExitTokenSize * 8)
`ASSERT_INIT(LcStateSize_A, lc_ctrl_state_pkg::LcStateWidth == LcStateSize * 8)
`ASSERT_INIT(LcTransitionCntSize_A, lc_ctrl_state_pkg::LcCountWidth == LcTransitionCntSize * 8)
`ASSERT_KNOWN(OtpAstPwrSeqKnown_A, otp_ast_pwr_seq_o)
`ASSERT_KNOWN(CoreTlOutKnown_A, core_tl_o)
`ASSERT_KNOWN(PrimTlOutKnown_A, prim_tl_o)
`ASSERT_KNOWN(IntrOtpOperationDoneKnown_A, intr_otp_operation_done_o)
`ASSERT_KNOWN(IntrOtpErrorKnown_A, intr_otp_error_o)
`ASSERT_KNOWN(AlertTxKnown_A, alert_tx_o)
`ASSERT_KNOWN(PwrOtpInitRspKnown_A, pwr_otp_o)
`ASSERT_KNOWN(LcOtpProgramRspKnown_A, lc_otp_program_o)
`ASSERT_KNOWN(OtpLcDataKnown_A, otp_lc_data_o)
`ASSERT_KNOWN(OtpKeymgrKeyKnown_A, otp_keymgr_key_o)
`ASSERT_KNOWN(FlashOtpKeyRspKnown_A, flash_otp_key_o)
`ASSERT_KNOWN(OtpSramKeyKnown_A, sram_otp_key_o)
`ASSERT_KNOWN(OtpOtgnKeyKnown_A, otbn_otp_key_o)
`ASSERT_KNOWN(OtpHwCfgKnown_A, otp_hw_cfg_o)
// Alert assertions for sparse FSMs.
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlDaiFsmCheck_A,
u_otp_ctrl_dai.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlKdiFsmCheck_A,
u_otp_ctrl_kdi.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlLciFsmCheck_A,
u_otp_ctrl_lci.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlLfsrTimerFsmCheck_A,
u_otp_ctrl_lfsr_timer.u_state_regs, alert_tx_o[1])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlScrambleFsmCheck_A,
u_otp_ctrl_scrmbl.u_state_regs, alert_tx_o[1])
// Alert assertions for redundant counters.
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntIntegCheck_A,
u_otp_ctrl_lfsr_timer.u_prim_count_integ, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntCnstyCheck_A,
u_otp_ctrl_lfsr_timer.u_prim_count_cnsty, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntDaiCheck_A,
u_otp_ctrl_dai.u_prim_count, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntKdiSeedCheck_A,
u_otp_ctrl_kdi.u_prim_count_seed, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntKdiEntropyCheck_A,
u_otp_ctrl_kdi.u_prim_count_entropy, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntLciCheck_A,
u_otp_ctrl_lci.u_prim_count, alert_tx_o[1])
`ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CntScrmblCheck_A,
u_otp_ctrl_scrmbl.u_prim_count, alert_tx_o[1])
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(TlLcGateFsm_A,
u_tlul_lc_gate.u_state_regs, alert_tx_o[2])
// Alert assertions for double LFSR.
`ASSERT_PRIM_DOUBLE_LFSR_ERROR_TRIGGER_ALERT(DoubleLfsrCheck_A,
u_otp_ctrl_lfsr_timer.u_prim_double_lfsr, alert_tx_o[1])
// Alert assertions for reg_we onehot check
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A, u_reg_core, alert_tx_o[2])
// Assertions for countermeasures inside prim_otp
`ifndef PRIM_DEFAULT_IMPL
`define PRIM_DEFAULT_IMPL prim_pkg::ImplGeneric
`endif
if (`PRIM_DEFAULT_IMPL == prim_pkg::ImplGeneric) begin : gen_reg_we_assert_generic
`ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(PrimFsmCheck_A,
u_otp.gen_generic.u_impl_generic.u_state_regs, alert_tx_o[3])
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(PrimRegWeOnehotCheck_A,
u_otp.gen_generic.u_impl_generic.u_reg_top, alert_tx_o[3])
end
endmodule : otp_ctrl