blob: 0d5c5ed411699c0b344dc02cf79c91d6303391a8 [file]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Unbuffered partition for OTP controller.
//
`include "prim_flop_macros.sv"
module otp_ctrl_part_unbuf
import otp_ctrl_pkg::*;
import otp_ctrl_reg_pkg::*;
import otp_ctrl_part_pkg::*;
#(
// Partition information.
parameter part_info_t Info = PartInfoDefault
) (
input clk_i,
input rst_ni,
// Pulse to start partition initialisation (required once per power cycle).
input init_req_i,
output logic init_done_o,
// Escalation input. This moves the FSM into a terminal state and locks down
// the partition.
input lc_ctrl_pkg::lc_tx_t escalate_en_i,
// Output error state of partition, to be consumed by OTP error/alert logic.
// Note that most errors are not recoverable and move the partition 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
// SEC_CM: ACCESS.CTRL.MUBI
input part_access_t access_i, // runtime lock from CSRs
output part_access_t access_o,
// Buffered 64bit digest output.
output logic [ScrmblBlockWidth-1:0] digest_o,
// Interface to TL-UL adapter
input logic tlul_req_i,
output logic tlul_gnt_o,
input [SwWindowAddrWidth-1:0] tlul_addr_i,
output logic [1:0] tlul_rerror_o,
output logic tlul_rvalid_o,
output logic [31:0] tlul_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
);
////////////////////////
// Integration Checks //
////////////////////////
import prim_mubi_pkg::*;
import prim_util_pkg::vbits;
localparam logic [OtpByteAddrWidth:0] PartEnd = (OtpByteAddrWidth+1)'(Info.offset) +
(OtpByteAddrWidth+1)'(Info.size);
localparam int unsigned DigestOffsetInt = int'(PartEnd) - ScrmblBlockWidth/8;
localparam bit [OtpByteAddrWidth-1:0] DigestOffset = DigestOffsetInt[OtpByteAddrWidth-1:0];
// Integration checks for parameters.
`ASSERT_INIT(OffsetMustBeBlockAligned_A, (Info.offset % (ScrmblBlockWidth/8)) == 0)
`ASSERT_INIT(SizeMustBeBlockAligned_A, (Info.size % (ScrmblBlockWidth/8)) == 0)
`ASSERT_INIT(DigestOffsetMustBeRepresentable_A, DigestOffsetInt == int'(DigestOffset))
///////////////////////
// OTP Partition FSM //
///////////////////////
// SEC_CM: PART.FSM.SPARSE
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 7 -n 10 \
// -s 4247417884 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (52.38%)
// 6: |||||||||||| (33.33%)
// 7: | (4.76%)
// 8: ||| (9.52%)
// 9: --
// 10: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 8
// Minimum Hamming weight: 3
// Maximum Hamming weight: 9
//
localparam int StateWidth = 10;
typedef enum logic [StateWidth-1:0] {
ResetSt = 10'b1010110110,
InitSt = 10'b0100010011,
InitWaitSt = 10'b0001011000,
IdleSt = 10'b1011101001,
ReadSt = 10'b0101101110,
ReadWaitSt = 10'b0110100101,
ErrorSt = 10'b1111011111
} state_e;
typedef enum logic {
DigestAddrSel = 1'b0,
DataAddrSel = 1'b1
} addr_sel_e;
state_e state_d, state_q;
addr_sel_e otp_addr_sel;
otp_err_e error_d, error_q;
logic digest_reg_en;
logic ecc_err;
logic [SwWindowAddrWidth-1:0] tlul_addr_d, tlul_addr_q;
// This is only used to return bus errors when the FSM is in ErrorSt.
logic pending_tlul_error_d, pending_tlul_error_q;
// Output partition error state.
assign error_o = error_q;
// This partition cannot do any write accesses, hence we tie this
// constantly off.
assign otp_wdata_o = '0;
assign otp_cmd_o = prim_otp_pkg::Read;
`ASSERT_KNOWN(FsmStateKnown_A, state_q)
always_comb begin : p_fsm
// Default assignments
state_d = state_q;
// Response to init request
init_done_o = 1'b0;
// OTP signals
otp_req_o = 1'b0;
otp_addr_sel = DigestAddrSel;
// TL-UL signals
tlul_gnt_o = 1'b0;
tlul_rvalid_o = 1'b0;
tlul_rerror_o = '0;
// Enable for buffered digest register
digest_reg_en = 1'b0;
// Error Register
error_d = error_q;
pending_tlul_error_d = 1'b0;
fsm_err_o = 1'b0;
unique case (state_q)
///////////////////////////////////////////////////////////////////
// State right after reset. Wait here until we get a an
// initialization request.
ResetSt: begin
if (init_req_i) begin
state_d = InitSt;
end
end
///////////////////////////////////////////////////////////////////
// Initialization reads out the digest only in unbuffered
// partitions. Wait here until the OTP request has been granted.
// And then wait until the OTP word comes back.
InitSt: begin
otp_req_o = 1'b1;
if (otp_gnt_i) begin
state_d = InitWaitSt;
end
end
///////////////////////////////////////////////////////////////////
// Wait for OTP response and write to digest buffer register. In
// case an OTP transaction fails, latch the OTP error code and
// jump to a terminal error state.
InitWaitSt: begin
if (otp_rvalid_i) begin
digest_reg_en = 1'b1;
// Depending on the partition configuration, we do not treat uncorrectable ECC errors
// as fatal.
if (!Info.ecc_fatal && otp_err_e'(otp_err_i) == MacroEccUncorrError ||
otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError}) begin
state_d = IdleSt;
// 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
end
///////////////////////////////////////////////////////////////////
// Wait for TL-UL requests coming in.
// Then latch address and go to readout state.
IdleSt: begin
init_done_o = 1'b1;
if (tlul_req_i) begin
error_d = NoError; // clear recoverable soft errors.
state_d = ReadSt;
tlul_gnt_o = 1'b1;
end
end
///////////////////////////////////////////////////////////////////
// If the address is out of bounds, or if the partition is
// locked, signal back a bus error. Note that such an error does
// not cause the partition to go into error state. Otherwise if
// these checks pass, an OTP word is requested.
ReadSt: begin
init_done_o = 1'b1;
// Double check the address range.
if ({tlul_addr_q, 2'b00} >= Info.offset &&
{1'b0, tlul_addr_q, 2'b00} < PartEnd &&
mubi8_test_false_strict(access_o.read_lock)) begin
otp_req_o = 1'b1;
otp_addr_sel = DataAddrSel;
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.
tlul_rvalid_o = 1'b1;
tlul_rerror_o = 2'b11; // This causes the TL-UL adapter to return a bus error.
end
end
///////////////////////////////////////////////////////////////////
// Wait for OTP response and and release the TL-UL response. In
// case an OTP transaction fails, latch the OTP error code,
// signal a TL-Ul bus error and jump to a terminal error state.
ReadWaitSt: begin
init_done_o = 1'b1;
if (otp_rvalid_i) begin
tlul_rvalid_o = 1'b1;
// Depending on the partition configuration, we do not treat uncorrectable ECC errors
// as fatal.
if (!Info.ecc_fatal && otp_err_e'(otp_err_i) == MacroEccUncorrError ||
otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError}) begin
state_d = IdleSt;
// 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);
// This causes the TL-UL adapter to return a bus error.
tlul_rerror_o = 2'b11;
end
end
end
///////////////////////////////////////////////////////////////////
// Terminal Error State. This locks access to the partition.
// Make sure the partition signals an error state if no error
// code has been latched so far.
ErrorSt: begin
if (error_q == NoError) begin
error_d = FsmStateError;
end
// Return bus errors if there are pending TL-UL requests.
if (pending_tlul_error_q) begin
tlul_rerror_o = 2'b11;
tlul_rvalid_o = 1'b1;
end else if (tlul_req_i) begin
tlul_gnt_o = 1'b1;
pending_tlul_error_d = 1'b1;
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
// an ECC error or escalation, and lock access to the partition down.
// SEC_CM: PART.FSM.LOCAL_ESC
if (ecc_err) begin
state_d = ErrorSt;
if (state_q != ErrorSt) begin
error_d = CheckFailError;
end
end
// SEC_CM: PART.FSM.GLOBAL_ESC
if (escalate_en_i != lc_ctrl_pkg::Off) begin
state_d = ErrorSt;
fsm_err_o = 1'b1;
if (state_q != ErrorSt) begin
error_d = FsmStateError;
end
end
end
///////////////////////////////////
// Signals to/from TL-UL Adapter //
///////////////////////////////////
assign tlul_addr_d = tlul_addr_i;
// Do not forward data in case of an error.
assign tlul_rdata_o = (tlul_rvalid_o && tlul_rerror_o == '0) ? otp_rdata_i[31:0] : '0;
// Note that OTP works on halfword (16bit) addresses, hence need to
// shift the addresses appropriately.
logic [OtpByteAddrWidth-1:0] addr_calc;
assign addr_calc = (otp_addr_sel == DigestAddrSel) ? DigestOffset : {tlul_addr_q, 2'b00};
assign otp_addr_o = addr_calc[OtpByteAddrWidth-1:OtpAddrShift];
if (OtpAddrShift > 0) begin : gen_unused
logic unused_bits;
assign unused_bits = ^addr_calc[OtpAddrShift-1:0];
end
// Request 32bit except in case of the digest.
assign otp_size_o = (otp_addr_sel == DigestAddrSel) ?
OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1)) :
OtpSizeWidth'(unsigned'(32 / OtpWidth - 1));
////////////////
// Digest Reg //
////////////////
// SEC_CM: PART.DATA_REG.INTEGRITY
otp_ctrl_ecc_reg #(
.Width ( ScrmblBlockWidth ),
.Depth ( 1 )
) u_otp_ctrl_ecc_reg (
.clk_i,
.rst_ni,
.wren_i ( digest_reg_en ),
.addr_i ( '0 ),
.wdata_i ( otp_rdata_i ),
.data_o ( digest_o ),
.ecc_err_o ( ecc_err )
);
////////////////////////
// DAI Access Control //
////////////////////////
mubi8_t init_locked;
assign init_locked = (~init_done_o) ? MuBi8True : MuBi8False;
// Aggregate all possible DAI write locks. The partition is also locked when uninitialized.
// Note that the locks are redundantly encoded values.
part_access_t access_pre;
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_write_lock_pre (
.clk_i,
.rst_ni,
.mubi_i(mubi8_and_lo(init_locked, access_i.write_lock)),
.mubi_o(access_pre.write_lock)
);
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_read_lock_pre (
.clk_i,
.rst_ni,
.mubi_i(mubi8_and_lo(init_locked, access_i.read_lock)),
.mubi_o(access_pre.read_lock)
);
// SEC_CM: PART.MEM.SW_UNWRITABLE
if (Info.write_lock) begin : gen_digest_write_lock
mubi8_t digest_locked;
assign digest_locked = (digest_o != '0) ? MuBi8True : MuBi8False;
// This prevents the synthesis tool from optimizing the multibit signal.
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_write_lock (
.clk_i,
.rst_ni,
.mubi_i(mubi8_and_lo(access_pre.write_lock, digest_locked)),
.mubi_o(access_o.write_lock)
);
`ASSERT(DigestWriteLocksPartition_A, digest_o |-> mubi8_test_true_loose(access_o.write_lock))
end else begin : gen_no_digest_write_lock
assign access_o.write_lock = access_pre.write_lock;
end
// SEC_CM: PART.MEM.SW_UNREADABLE
if (Info.read_lock) begin : gen_digest_read_lock
mubi8_t digest_locked;
assign digest_locked = (digest_o != '0) ? MuBi8True : MuBi8False;
// This prevents the synthesis tool from optimizing the multibit signal.
prim_mubi8_sender #(
.AsyncOn(0)
) u_prim_mubi8_sender_read_lock (
.clk_i,
.rst_ni,
.mubi_i(mubi8_and_lo(access_pre.read_lock, digest_locked)),
.mubi_o(access_o.read_lock)
);
`ASSERT(DigestReadLocksPartition_A, digest_o |-> mubi8_test_true_loose(access_o.read_lock))
end else begin : gen_no_digest_read_lock
assign access_o.read_lock = access_pre.read_lock;
end
///////////////
// 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;
tlul_addr_q <= '0;
pending_tlul_error_q <= 1'b0;
end else begin
error_q <= error_d;
pending_tlul_error_q <= pending_tlul_error_d;
if (tlul_gnt_o) begin
tlul_addr_q <= tlul_addr_d;
end
end
end
////////////////
// Assertions //
////////////////
// Known assertions
`ASSERT_KNOWN(InitDoneKnown_A, init_done_o)
`ASSERT_KNOWN(ErrorKnown_A, error_o)
`ASSERT_KNOWN(AccessKnown_A, access_o)
`ASSERT_KNOWN(DigestKnown_A, digest_o)
`ASSERT_KNOWN(TlulGntKnown_A, tlul_gnt_o)
`ASSERT_KNOWN(TlulRerrorKnown_A, tlul_rerror_o)
`ASSERT_KNOWN(TlulRvalidKnown_A, tlul_rvalid_o)
`ASSERT_KNOWN(TlulRdataKnown_A, tlul_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)
// Uninitialized partitions should always be locked, no matter what.
`ASSERT(InitWriteLocksPartition_A,
~init_done_o
|->
mubi8_test_true_loose(access_o.write_lock))
`ASSERT(InitReadLocksPartition_A,
~init_done_o
|->
mubi8_test_true_loose(access_o.read_lock))
// Incoming Lock propagation
`ASSERT(WriteLockPropagation_A,
mubi8_test_true_loose(access_i.write_lock)
|->
mubi8_test_true_loose(access_o.write_lock))
`ASSERT(ReadLockPropagation_A,
mubi8_test_true_loose(access_i.read_lock)
|->
mubi8_test_true_loose(access_o.read_lock))
// If the partition is read locked, the TL-UL access must error out
`ASSERT(TlulReadOnReadLock_A,
tlul_req_i && tlul_gnt_o ##1 mubi8_test_true_loose(access_o.read_lock)
|->
tlul_rerror_o > '0 && tlul_rvalid_o)
// ECC error in buffer regs.
`ASSERT(EccErrorState_A,
ecc_err
|=>
state_q == ErrorSt)
// OTP error response
`ASSERT(OtpErrorState_A,
state_q inside {InitWaitSt, ReadWaitSt} && otp_rvalid_i &&
!(otp_err_e'(otp_err_i) inside {NoError, MacroEccCorrError} ||
otp_err_e'(otp_err_i) == MacroEccUncorrError && !Info.ecc_fatal) && !ecc_err
|=>
state_q == ErrorSt && error_o == $past(otp_err_e'(otp_err_i)))
endmodule : otp_ctrl_part_unbuf