blob: 8fc01cf4501f1abd7c3da666174bda55de9c1abc [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Direct access interface for OTP controller.
//
`include "prim_flop_macros.sv"
module otp_ctrl_dai
import otp_ctrl_pkg::*;
import otp_ctrl_reg_pkg::*;
import otp_ctrl_part_pkg::*;
(
input clk_i,
input rst_ni,
// Init reqest from power manager
input init_req_i,
output logic init_done_o,
// Init request going to partitions
output logic part_init_req_o,
input [NumPart-1:0] part_init_done_i,
// Escalation input. This moves the FSM into a terminal state and locks down
// the DAI.
input lc_ctrl_pkg::lc_tx_t escalate_en_i,
// Output error state of DAI, to be consumed by OTP error/alert logic.
// Note that most errors are not recoverable and move the DAI FSM into
// a terminal error state.
output otp_err_e error_o,
// This error signal is pulsed high if the FSM has been glitched into an invalid state.
// Although it is somewhat redundant with the error code in error_o above, it is
// meant to cover cases where we already latched an error code while the FSM is
// glitched into an invalid state (since in that case, the error code will not be
// overridden with the FSM error code so that the original error code is still
// discoverable).
output logic fsm_err_o,
// Access/lock status from partitions
// SEC_CM: ACCESS.CTRL.MUBI
input part_access_t [NumPart-1:0] part_access_i,
// CSR interface
input [OtpByteAddrWidth-1:0] dai_addr_i,
input dai_cmd_e dai_cmd_i,
input logic dai_req_i,
input [NumDaiWords-1:0][31:0] dai_wdata_i,
output logic dai_idle_o, // wired to the status CSRs
output logic dai_prog_idle_o, // wired to lfsr timer and pwrmgr
output logic dai_cmd_done_o, // this is used to raise an IRQ
output logic [NumDaiWords-1:0][31:0] dai_rdata_o,
// OTP interface
output logic otp_req_o,
output prim_otp_pkg::cmd_e otp_cmd_o,
output logic [OtpSizeWidth-1:0] otp_size_o,
output logic [OtpIfWidth-1:0] otp_wdata_o,
output logic [OtpAddrWidth-1:0] otp_addr_o,
input otp_gnt_i,
input otp_rvalid_i,
input [ScrmblBlockWidth-1:0] otp_rdata_i,
input prim_otp_pkg::err_e otp_err_i,
// Scrambling mutex request
output logic scrmbl_mtx_req_o,
input scrmbl_mtx_gnt_i,
// Scrambling datapath interface
output otp_scrmbl_cmd_e scrmbl_cmd_o,
output digest_mode_e scrmbl_mode_o,
output logic [ConstSelWidth-1:0] scrmbl_sel_o,
output logic [ScrmblBlockWidth-1:0] scrmbl_data_o,
output logic scrmbl_valid_o,
input logic scrmbl_ready_i,
input logic scrmbl_valid_i,
input logic [ScrmblBlockWidth-1:0] scrmbl_data_i
);
////////////////////////
// Integration Checks //
////////////////////////
import prim_mubi_pkg::*;
import prim_util_pkg::vbits;
localparam int CntWidth = OtpByteAddrWidth - $clog2(ScrmblBlockWidth/8);
// Integration checks for parameters.
`ASSERT_INIT(CheckNativeOtpWidth0_A, (ScrmblBlockWidth % OtpWidth) == 0)
`ASSERT_INIT(CheckNativeOtpWidth1_A, (32 % OtpWidth) == 0)
/////////////////////
// DAI Control FSM //
/////////////////////
// SEC_CM: DAI.FSM.SPARSE
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 20 -n 12 \
// -s 3011551511 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||| (31.05%)
// 6: |||||||||||||||||||| (36.84%)
// 7: |||||||| (15.26%)
// 8: |||| (8.95%)
// 9: || (5.26%)
// 10: (1.58%)
// 11: (1.05%)
// 12: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 11
// Minimum Hamming weight: 2
// Maximum Hamming weight: 9
//
localparam int StateWidth = 12;
typedef enum logic [StateWidth-1:0] {
ResetSt = 12'b101111010100,
InitOtpSt = 12'b110000110010,
InitPartSt = 12'b000111111001,
IdleSt = 12'b111010000011,
ErrorSt = 12'b100010001110,
ReadSt = 12'b100101100110,
ReadWaitSt = 12'b001100000000,
DescrSt = 12'b011000101111,
DescrWaitSt = 12'b110101011111,
WriteSt = 12'b110111001000,
WriteWaitSt = 12'b111001111100,
ScrSt = 12'b000000010101,
ScrWaitSt = 12'b010110110100,
DigClrSt = 12'b001111001111,
DigReadSt = 12'b001001110011,
DigReadWaitSt = 12'b101110111010,
DigSt = 12'b011111100010,
DigPadSt = 12'b011010011000,
DigFinSt = 12'b110011100101,
DigWaitSt = 12'b100000101001
} state_e;
typedef enum logic [1:0] {
OtpData = 2'b00,
DaiData = 2'b01,
ScrmblData = 2'b10
} data_sel_e;
typedef enum logic {
PartOffset = 1'b0,
DaiOffset = 1'b1
} addr_sel_e;
state_e state_d, state_q;
logic [CntWidth-1:0] cnt;
logic cnt_en, cnt_clr, cnt_err;
otp_err_e error_d, error_q;
logic data_en, data_clr;
data_sel_e data_sel;
addr_sel_e base_sel_d, base_sel_q;
logic [ScrmblBlockWidth-1:0] data_q;
logic [NumPartWidth-1:0] part_idx;
logic [NumPart-1:0][OtpAddrWidth-1:0] digest_addr_lut;
// Output partition error state.
assign error_o = error_q;
// Working register is connected to data outputs.
assign otp_wdata_o = data_q;
assign scrmbl_data_o = data_q;
// Only expose this working register in IdleSt.
// The FSM below makes sure to clear this register
// after digest and write ops.
assign dai_rdata_o = (state_q == IdleSt) ? data_q : '0;
always_comb begin : p_fsm
state_d = state_q;
// Init signals
init_done_o = 1'b1;
part_init_req_o = 1'b0;
// DAI signals
dai_idle_o = 1'b0;
dai_prog_idle_o = 1'b1;
dai_cmd_done_o = 1'b0;
// OTP signals
otp_req_o = 1'b0;
otp_cmd_o = prim_otp_pkg::Init;
// Scrambling mutex
scrmbl_mtx_req_o = 1'b0;
// Scrambling datapath
scrmbl_cmd_o = LoadShadow;
scrmbl_sel_o = CnstyDigest;
scrmbl_mode_o = StandardMode;
scrmbl_valid_o = 1'b0;
// Counter
cnt_en = 1'b0;
cnt_clr = 1'b0;
base_sel_d = base_sel_q;
// Temporary data register
data_en = 1'b0;
data_clr = 1'b0;
data_sel = OtpData;
// Error Register
error_d = error_q;
fsm_err_o = 1'b0;
unique case (state_q)
///////////////////////////////////////////////////////////////////
// We get here after reset and wait until the power manager
// requests OTP initialization. If initialization is requested,
// an init command is written to the OTP macro, and we move on
// to the InitOtpSt waiting state.
ResetSt: begin
init_done_o = 1'b0;
dai_prog_idle_o = 1'b0;
data_clr = 1'b1;
if (init_req_i) begin
otp_req_o = 1'b1;
if (otp_gnt_i) begin
state_d = InitOtpSt;
end
end
end
///////////////////////////////////////////////////////////////////
// We wait here unitl the OTP macro has initialized without
// error. If an error occurred during this stage, we latch that
// error and move into a terminal error state.
InitOtpSt: begin
init_done_o = 1'b0;
dai_prog_idle_o = 1'b0;
if (otp_rvalid_i) begin
if ((!(otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError}))) begin
state_d = ErrorSt;
error_d = otp_err_e'(otp_err_i);
end else begin
state_d = InitPartSt;
end
end
end
///////////////////////////////////////////////////////////////////
// Since the OTP macro is now functional, we can send out an
// initialization request to all partitions and wait until they
// all have initialized.
InitPartSt: begin
init_done_o = 1'b0;
dai_prog_idle_o = 1'b0;
part_init_req_o = 1'b1;
if (part_init_done_i == {NumPart{1'b1}}) begin
state_d = IdleSt;
end
end
///////////////////////////////////////////////////////////////////
// Idle state where we wait for incoming commands.
// Invalid commands trigger a CmdInvErr, which is recoverable.
IdleSt: begin
dai_idle_o = 1'b1;
if (dai_req_i) begin
// This clears previous (recoverable) and reset the counter.
error_d = NoError;
cnt_clr = 1'b1;
unique case (dai_cmd_i)
DaiRead: begin
state_d = ReadSt;
// Clear the temporary data register.
data_clr = 1'b1;
base_sel_d = DaiOffset;
end
DaiWrite: begin
data_sel = DaiData;
// Fetch data block.
data_en = 1'b1;
base_sel_d = DaiOffset;
// If this partition is scrambled, directly go to write scrambling first.
if (PartInfo[part_idx].secret) begin
state_d = ScrSt;
end else begin
state_d = WriteSt;
end
end
DaiDigest: begin
state_d = DigClrSt;
scrmbl_mtx_req_o = 1'b1;
base_sel_d = PartOffset;
end
default: ; // Ignore invalid commands
endcase // dai_cmd_i
end // dai_req_i
end
///////////////////////////////////////////////////////////////////
// Each time we request a block of data from OTP, we re-check
// whether read access has been locked for this partition. If
// that is the case, we immediately bail out. Otherwise, we
// request a block of data from OTP.
ReadSt: begin
if (mubi8_test_false_strict(part_access_i[part_idx].read_lock) ||
// HW digests always remain readable.
PartInfo[part_idx].hw_digest && otp_addr_o == digest_addr_lut[part_idx]) begin
otp_req_o = 1'b1;
otp_cmd_o = prim_otp_pkg::Read;
if (otp_gnt_i) begin
state_d = ReadWaitSt;
end
end else begin
state_d = IdleSt;
error_d = AccessError; // Signal this error, but do not go into terminal error state.
dai_cmd_done_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Wait for OTP response and write to readout register. Check
// whether descrambling is required or not. In case an OTP
// transaction fails, latch the OTP error code, and jump to
// terminal error state.
ReadWaitSt: begin
// Continuously check read access and bail out if this is not consistent.
if (mubi8_test_false_strict(part_access_i[part_idx].read_lock) ||
// HW digests always remain readable.
PartInfo[part_idx].hw_digest && otp_addr_o == digest_addr_lut[part_idx]) begin
if (otp_rvalid_i) begin
// Check OTP return code. Depending on the partition configuration, we do not treat
// uncorrectable ECC errors as fatal.
if (!PartInfo[part_idx].ecc_fatal && otp_err_e'(otp_err_i) == MacroEccUncorrError ||
otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError}) begin
data_en = 1'b1;
// We do not need to descramble the digest values.
if (PartInfo[part_idx].secret && otp_addr_o != digest_addr_lut[part_idx]) begin
state_d = DescrSt;
end else begin
state_d = IdleSt;
dai_cmd_done_o = 1'b1;
end
// At this point the only error that we could have gotten are correctable ECC errors.
// There is one exception, though, which are partitions where the ecc_fatal
// bit is set to 0 (this is only used for test partitions). In that a case,
// correctable and uncorrectable ECC errors are both collapsed and signalled
// as MacroEccCorrError
if (otp_err_e'(otp_err_i) != NoError) begin
error_d = MacroEccCorrError;
end
end else begin
state_d = ErrorSt;
error_d = otp_err_e'(otp_err_i);
end
end
// At this point, this check MUST succeed - otherwise this means that
// there was a tampering attempt. Hence we go into a terminal error state
// when this check fails.
end else begin
state_d = ErrorSt;
error_d = FsmStateError;
end
end
///////////////////////////////////////////////////////////////////
// Descrambling state. This first acquires the scrambling
// datapath mutex. Note that once the mutex is acquired, we have
// exclusive access to the scrambling datapath until we release
// the mutex by deasserting scrmbl_mtx_req_o.
// SEC_CM: SECRET.MEM.SCRAMBLE
DescrSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
scrmbl_cmd_o = Decrypt;
scrmbl_sel_o = PartInfo[part_idx].key_sel;
if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin
state_d = DescrWaitSt;
end
end
///////////////////////////////////////////////////////////////////
// Wait for the descrambled data to return. Note that we release
// the mutex lock upon leaving this state.
// SEC_CM: SECRET.MEM.SCRAMBLE
DescrWaitSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_sel_o = PartInfo[part_idx].key_sel;
data_sel = ScrmblData;
if (scrmbl_valid_i) begin
state_d = IdleSt;
data_en = 1'b1;
dai_cmd_done_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// First, check whether write accesses are allowed to this
// partition, and error out otherwise. Note that for buffered
// partitions, we do not allow DAI writes to the digest offset.
// Unbuffered partitions have SW managed digests, hence that
// check is not needed in that case. The LC partition is
// permanently write locked and can hence not be written via the DAI.
WriteSt: begin
dai_prog_idle_o = 1'b0;
if (mubi8_test_false_strict(part_access_i[part_idx].write_lock) &&
// If this is a HW digest write to a buffered partition.
((PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest &&
base_sel_q == PartOffset && otp_addr_o == digest_addr_lut[part_idx]) ||
// If this is a non HW digest write to a buffered partition.
(PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest &&
base_sel_q == DaiOffset && otp_addr_o < digest_addr_lut[part_idx]) ||
// If this is a write to an unbuffered partition
(PartInfo[part_idx].variant != Buffered && base_sel_q == DaiOffset))) begin
otp_req_o = 1'b1;
otp_cmd_o = prim_otp_pkg::Write;
if (otp_gnt_i) begin
state_d = WriteWaitSt;
end
end else begin
// Clear working register state.
data_clr = 1'b1;
state_d = IdleSt;
error_d = AccessError; // Signal this error, but do not go into terminal error state.
dai_cmd_done_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Wait for OTP response, and then go back to idle. In case an
// OTP transaction fails, latch the OTP error code, and jump to
// terminal error state.
WriteWaitSt: begin
dai_prog_idle_o = 1'b0;
// Continuously check write access and bail out if this is not consistent.
if (mubi8_test_false_strict(part_access_i[part_idx].write_lock) &&
// If this is a HW digest write to a buffered partition.
((PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest &&
base_sel_q == PartOffset && otp_addr_o == digest_addr_lut[part_idx]) ||
// If this is a non HW digest write to a buffered partition.
(PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest &&
base_sel_q == DaiOffset && otp_addr_o < digest_addr_lut[part_idx]) ||
// If this is a write to an unbuffered partition
(PartInfo[part_idx].variant != Buffered && base_sel_q == DaiOffset))) begin
if (otp_rvalid_i) begin
// Check OTP return code. Note that non-blank errors are recoverable.
if ((!(otp_err_e'(otp_err_i) inside {NoError, MacroWriteBlankError}))) begin
state_d = ErrorSt;
error_d = otp_err_e'(otp_err_i);
end else begin
// Clear working register state.
data_clr = 1'b1;
state_d = IdleSt;
dai_cmd_done_o = 1'b1;
// Signal non-blank state, but do not go to terminal error state.
if (otp_err_e'(otp_err_i) == MacroWriteBlankError) begin
error_d = otp_err_e'(otp_err_i);
end
end
end
// At this point, this check MUST succeed - otherwise this means that
// there was a tampering attempt. Hence we go into a terminal error state
// when this check fails.
end else begin
state_d = ErrorSt;
error_d = FsmStateError;
end
end
///////////////////////////////////////////////////////////////////
// Scrambling state. This first acquires the scrambling
// datapath mutex. Note that once the mutex is acquired, we have
// exclusive access to the scrambling datapath until we release
// the mutex by deasserting scrmbl_mtx_req_o.
// SEC_CM: SECRET.MEM.SCRAMBLE
ScrSt: begin
scrmbl_mtx_req_o = 1'b1;
// Check write access and bail out if this is not consistent.
if (mubi8_test_false_strict(part_access_i[part_idx].write_lock) &&
// If this is a non HW digest write to a buffered partition.
(PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].secret &&
PartInfo[part_idx].hw_digest && base_sel_q == DaiOffset &&
otp_addr_o < digest_addr_lut[part_idx])) begin
scrmbl_valid_o = 1'b1;
scrmbl_cmd_o = Encrypt;
scrmbl_sel_o = PartInfo[part_idx].key_sel;
if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin
state_d = ScrWaitSt;
end
end else begin
state_d = IdleSt;
error_d = AccessError; // Signal this error, but do not go into terminal error state.
dai_cmd_done_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Wait for the scrambled data to return. Note that we release
// the mutex lock upon leaving this state.
// SEC_CM: SECRET.MEM.SCRAMBLE
ScrWaitSt: begin
scrmbl_mtx_req_o = 1'b1;
// Continously check write access and bail out if this is not consistent.
if (mubi8_test_false_strict(part_access_i[part_idx].write_lock) &&
// If this is a non HW digest write to a buffered partition.
(PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].secret &&
PartInfo[part_idx].hw_digest && base_sel_q == DaiOffset &&
otp_addr_o < digest_addr_lut[part_idx])) begin
data_sel = ScrmblData;
if (scrmbl_valid_i) begin
state_d = WriteSt;
data_en = 1'b1;
end
// At this point, this check MUST succeed - otherwise this means that
// there was a tampering attempt. Hence we go into a terminal error state
// when this check fails.
end else begin
state_d = ErrorSt;
error_d = FsmStateError;
end
end
///////////////////////////////////////////////////////////////////
// First, acquire the mutex for the digest and clear the digest state.
// SEC_CM: PART.MEM.DIGEST
DigClrSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
// Need to reset the digest state and set digest mode to "standard".
scrmbl_cmd_o = DigestInit;
if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin
state_d = DigReadSt;
end
end
///////////////////////////////////////////////////////////////////
// This requests a 64bit block to be pushed into the digest datapath.
// We also check here whether the partition has been write locked.
// SEC_CM: PART.MEM.DIGEST
DigReadSt: begin
scrmbl_mtx_req_o = 1'b1;
if (mubi8_test_false_strict(part_access_i[part_idx].read_lock) &&
mubi8_test_false_strict(part_access_i[part_idx].write_lock)) begin
otp_req_o = 1'b1;
otp_cmd_o = prim_otp_pkg::Read;
if (otp_gnt_i) begin
state_d = DigReadWaitSt;
end
end else begin
state_d = IdleSt;
error_d = AccessError; // Signal this error, but do not go into terminal error state.
dai_cmd_done_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Wait for OTP response and write to readout register. Check
// whether descrambling is required or not. In case an OTP
// transaction fails, latch the OTP error code, and jump to
// terminal error state.
// SEC_CM: PART.MEM.DIGEST
DigReadWaitSt: begin
scrmbl_mtx_req_o = 1'b1;
if (otp_rvalid_i) begin
cnt_en = 1'b1;
// Check OTP return code.
if ((!(otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError}))) begin
state_d = ErrorSt;
error_d = otp_err_e'(otp_err_i);
end else begin
data_en = 1'b1;
state_d = DigSt;
// Signal soft ECC errors, but do not go into terminal error state.
if (otp_err_e'(otp_err_i) == MacroEccCorrError) begin
error_d = otp_err_e'(otp_err_i);
end
end
end
end
///////////////////////////////////////////////////////////////////
// Push the word read into the scrambling datapath. The last
// block is repeated in case the number blocks in this partition
// is odd.
// SEC_CM: PART.MEM.DIGEST
DigSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
// No need to digest the digest value itself
if (otp_addr_o == digest_addr_lut[part_idx]) begin
// Trigger digest round in case this is the second block in a row.
if (!cnt[0]) begin
scrmbl_cmd_o = Digest;
if (scrmbl_ready_i) begin
state_d = DigFinSt;
end
// Otherwise, just load low word and go to padding state.
end else if (scrmbl_ready_i) begin
state_d = DigPadSt;
end
end else begin
// Trigger digest round in case this is the second block in a row.
if (!cnt[0]) begin
scrmbl_cmd_o = Digest;
end
// Go back and fetch more data blocks.
if (scrmbl_ready_i) begin
state_d = DigReadSt;
end
end
end
///////////////////////////////////////////////////////////////////
// Padding state, just repeat the last block and go to digest
// finalization.
// SEC_CM: PART.MEM.DIGEST
DigPadSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
scrmbl_cmd_o = Digest;
if (scrmbl_ready_i) begin
state_d = DigFinSt;
end
end
///////////////////////////////////////////////////////////////////
// Trigger digest finalization and go wait for the result.
// SEC_CM: PART.MEM.DIGEST
DigFinSt: begin
scrmbl_mtx_req_o = 1'b1;
scrmbl_valid_o = 1'b1;
scrmbl_cmd_o = DigestFinalize;
if (scrmbl_ready_i) begin
state_d = DigWaitSt;
end
end
///////////////////////////////////////////////////////////////////
// Wait for the digest to return, and write the result to OTP.
// Note that the write address will be correct in this state,
// since the counter has been stepped to the correct address as
// part of the readout sequence, and the correct size for this
// access has been loaded before.
// SEC_CM: PART.MEM.DIGEST
DigWaitSt: begin
scrmbl_mtx_req_o = 1'b1;
data_sel = ScrmblData;
if (scrmbl_valid_i) begin
state_d = WriteSt;
data_en = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// Terminal Error State. This locks access to the DAI. Make sure
// an FsmStateError error code is assigned here, in case no error code has
// been assigned yet.
ErrorSt: begin
if (error_q == NoError) begin
error_d = FsmStateError;
end
end
///////////////////////////////////////////////////////////////////
// We should never get here. If we do (e.g. via a malicious
// glitch), error out immediately.
default: begin
state_d = ErrorSt;
fsm_err_o = 1'b1;
end
///////////////////////////////////////////////////////////////////
endcase // state_q
// Unconditionally jump into the terminal error state in case of escalation.
// SEC_CM: DAI.FSM.LOCAL_ESC, DAI.FSM.GLOBAL_ESC
if (escalate_en_i != lc_ctrl_pkg::Off || cnt_err) begin
state_d = ErrorSt;
fsm_err_o = 1'b1;
if (state_q != ErrorSt) begin
error_d = FsmStateError;
end
end
end
////////////////////////////
// Partition Select Logic //
////////////////////////////
// This checks which partition the address belongs to by comparing
// the incoming address to the partition address ranges. The onehot
// bitvector generated by the parallel comparisons is fed into a
// binary tree that determines the partition index with O(log(N)) delay.
logic [NumPart-1:0] part_sel_oh;
for (genvar k = 0; k < NumPart; k++) begin : gen_part_sel
localparam int unsigned PartEndInt = 32'(PartInfo[k].offset) + 32'(PartInfo[k].size);
localparam int unsigned DigestOffsetInt = PartEndInt - ScrmblBlockWidth / 8;
localparam int unsigned DigestAddrLutInt = DigestOffsetInt >> OtpAddrShift;
// PartEnd has an extra bit to cope with the case where offset + size overflows. However, we
// arrange the address map to make sure that PartEndInt is at most 1 << OtpByteAddrWidth. Check
// that here.
`ASSERT_INIT(PartEndMax_A, PartEndInt <= (1 << OtpByteAddrWidth))
// The shift right by OtpAddrShift drops exactly the bottom bits that are needed to convert
// between OtpAddrWidth and OtpByteAddrWidth, so we know that we can slice safely here.
localparam bit [OtpAddrWidth-1:0] DigestAddrLut = DigestAddrLutInt[OtpAddrWidth-1:0];
assign part_sel_oh[k] = (dai_addr_i >= PartInfo[k].offset) &
({1'b0, dai_addr_i} < PartEndInt[OtpByteAddrWidth:0]);
assign digest_addr_lut[k] = DigestAddrLut;
end
`ASSERT(ScrmblBlockWidthGe8_A, ScrmblBlockWidth >= 8)
`ASSERT(PartSelMustBeOnehot_A, $onehot0(part_sel_oh))
prim_arbiter_fixed #(
.N(NumPart),
.EnDataPort(0)
) u_part_sel_idx (
.clk_i,
.rst_ni,
.req_i ( part_sel_oh ),
.data_i ( '{default: '0} ),
.gnt_o ( ), // unused
.idx_o ( part_idx ),
.valid_o ( ), // unused
.data_o ( ), // unused
.ready_i ( 1'b0 )
);
/////////////////////////////////////
// Address Calculations for Digest //
/////////////////////////////////////
// Depending on whether this is a 32bit or 64bit partition, we cut off the lower address bits.
// Access sizes are either 64bit or 32bit, depending on what region the access goes to.
logic [OtpByteAddrWidth-1:0] addr_base;
always_comb begin : p_size_sel
otp_size_o = OtpSizeWidth'(unsigned'(32 / OtpWidth - 1));
addr_base = {dai_addr_i[OtpByteAddrWidth-1:2], 2'h0};
// 64bit transaction for scrambled partitions.
if (PartInfo[part_idx].secret) begin
otp_size_o = OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1));
addr_base = {dai_addr_i[OtpByteAddrWidth-1:3], 3'h0};
// 64bit transaction if computing a digest.
end else if (PartInfo[part_idx].hw_digest && (base_sel_q == PartOffset)) begin
otp_size_o = OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1));
addr_base = PartInfo[part_idx].offset;
// 64bit transaction if the DAI address points to the partition's digest offset.
end else if ((base_sel_q == DaiOffset) &&
({dai_addr_i[OtpByteAddrWidth-1:3], 2'b0} == digest_addr_lut[part_idx])) begin
otp_size_o = OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1));
addr_base = {dai_addr_i[OtpByteAddrWidth-1:3], 3'h0};
end
end
// Address counter - this is only used for computing a digest, hence the increment is
// fixed to 8 byte.
// SEC_CM: DAI.CTR.REDUN
prim_count #(
.Width(CntWidth)
) u_prim_count (
.clk_i,
.rst_ni,
.clr_i(cnt_clr),
.set_i(1'b0),
.set_cnt_i('0),
.incr_en_i(cnt_en),
.decr_en_i(1'b0),
.step_i(CntWidth'(1)),
.cnt_o(cnt),
.cnt_next_o(),
.err_o(cnt_err)
);
// Note that OTP works on halfword (16bit) addresses, hence need to
// shift the addresses appropriately.
logic [OtpByteAddrWidth-1:0] addr_calc;
assign addr_calc = {cnt, {$clog2(ScrmblBlockWidth/8){1'b0}}} + addr_base;
assign otp_addr_o = OtpAddrWidth'(addr_calc >> OtpAddrShift);
///////////////
// Registers //
///////////////
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, ResetSt)
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
if (!rst_ni) begin
error_q <= NoError;
data_q <= '0;
base_sel_q <= DaiOffset;
end else begin
error_q <= error_d;
base_sel_q <= base_sel_d;
// Working register
if (data_clr) begin
data_q <= '0;
end else if (data_en) begin
if (data_sel == ScrmblData) begin
data_q <= scrmbl_data_i;
end else if (data_sel == DaiData) begin
data_q <= dai_wdata_i;
end else begin
data_q <= otp_rdata_i;
end
end
end
end
////////////////
// Assertions //
////////////////
// Known assertions
`ASSERT_KNOWN(InitDoneKnown_A, init_done_o)
`ASSERT_KNOWN(PartInitReqKnown_A, part_init_req_o)
`ASSERT_KNOWN(ErrorKnown_A, error_o)
`ASSERT_KNOWN(DaiIdleKnown_A, dai_idle_o)
`ASSERT_KNOWN(DaiRdataKnown_A, dai_rdata_o)
`ASSERT_KNOWN(OtpReqKnown_A, otp_req_o)
`ASSERT_KNOWN(OtpCmdKnown_A, otp_cmd_o)
`ASSERT_KNOWN(OtpSizeKnown_A, otp_size_o)
`ASSERT_KNOWN(OtpWdataKnown_A, otp_wdata_o)
`ASSERT_KNOWN(OtpAddrKnown_A, otp_addr_o)
`ASSERT_KNOWN(ScrmblMtxReqKnown_A, scrmbl_mtx_req_o)
`ASSERT_KNOWN(ScrmblCmdKnown_A, scrmbl_cmd_o)
`ASSERT_KNOWN(ScrmblModeKnown_A, scrmbl_mode_o)
`ASSERT_KNOWN(ScrmblSelKnown_A, scrmbl_sel_o)
`ASSERT_KNOWN(ScrmblDataKnown_A, scrmbl_data_o)
`ASSERT_KNOWN(ScrmblValidKnown_A, scrmbl_valid_o)
// OTP error response
`ASSERT(OtpErrorState_A,
state_q inside {InitOtpSt, ReadWaitSt, WriteWaitSt, DigReadWaitSt} && otp_rvalid_i &&
!(otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError, MacroWriteBlankError}) &&
!(!PartInfo[part_idx].ecc_fatal && otp_err_e'(otp_err_i) == MacroEccUncorrError)
|=>
state_q == ErrorSt && error_o == $past(otp_err_e'(otp_err_i)))
endmodule : otp_ctrl_dai