blob: c9f6e60d22a65e8203a3044191da74e458cc86b7 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Flash Controller for life cycle / key management handling
//
module flash_ctrl_lcmgr
import flash_ctrl_pkg::*;
import lc_ctrl_pkg::lc_tx_t;
#(
parameter flash_key_t RndCnstAddrKey = RndCnstAddrKeyDefault,
parameter flash_key_t RndCnstDataKey = RndCnstDataKeyDefault,
parameter all_seeds_t RndCnstAllSeeds = RndCnstAllSeedsDefault
) (
input clk_i,
input rst_ni,
input clk_otp_i,
input rst_otp_ni,
// initialization command
input init_i,
// only access seeds when provisioned
input provision_en_i,
// combined escalation disable
input prim_mubi_pkg::mubi4_t disable_i,
// interface to ctrl arb control ports
output flash_ctrl_reg_pkg::flash_ctrl_reg2hw_control_reg_t ctrl_o,
output logic req_o,
output logic [BusAddrByteW-1:0] addr_o,
input done_i,
input flash_ctrl_err_t err_i,
// interface to ctrl_arb data ports
output logic rready_o,
input rvalid_i,
output logic wvalid_o,
input wready_i,
// direct form rd_fifo
// rdata_i is {data_integrity, data}, the upper bits store integrity
input [BusFullWidth-1:0] rdata_i,
// direct to wr_fifo
output logic [BusFullWidth-1:0] wdata_o,
// external rma request
// This should be simplified to just multi-bit request and multi-bit response
input lc_tx_t rma_req_i,
output lc_tx_t rma_ack_o,
// seeds to the outside world,
output logic [NumSeeds-1:0][SeedWidth-1:0] seeds_o,
// indicate to memory protection what phase the hw interface is in
output flash_lcmgr_phase_e phase_o,
// fatal errors
output logic fatal_err_o,
output logic intg_err_o,
// error status to registers
output logic seed_err_o,
// enable read buffer in flash_phy
output logic rd_buf_en_o,
// request otp keys
output otp_ctrl_pkg::flash_otp_key_req_t otp_key_req_o,
input otp_ctrl_pkg::flash_otp_key_rsp_t otp_key_rsp_i,
output flash_key_t addr_key_o,
output flash_key_t data_key_o,
output flash_key_t rand_addr_key_o,
output flash_key_t rand_data_key_o,
// entropy interface
output logic edn_req_o,
input edn_ack_i,
output logic lfsr_en_o,
input [BusWidth-1:0] rand_i,
// disable access to flash
output lc_tx_t dis_access_o,
// init ongoing
output logic init_busy_o,
output logic initialized_o,
// debug state output
output logic [10:0] debug_state_o
);
import lc_ctrl_pkg::lc_tx_test_true_strict;
// total number of pages to be wiped during RMA entry
localparam int unsigned WipeIdxWidth = prim_util_pkg::vbits(WipeEntries);
localparam int unsigned MaxWipeEntry = WipeEntries - 1;
// seed related local params
localparam int unsigned SeedReads = SeedWidth / BusWidth;
localparam int unsigned SeedRdsWidth = $clog2(SeedReads);
localparam int unsigned SeedCntWidth = $clog2(NumSeeds+1);
localparam int unsigned NumSeedWidth = $clog2(NumSeeds);
// the various seed outputs
logic [NumSeeds-1:0][SeedReads-1:0][BusWidth-1:0] seeds_q;
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (40.00%)
// 6: |||||||||||||||| (33.33%)
// 7: ||||| (11.11%)
// 8: |||||| (13.33%)
// 9: | (2.22%)
// 10: --
// 11: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 9
// Minimum Hamming weight: 4
// Maximum Hamming weight: 7
//
localparam int StateWidth = 11;
typedef enum logic [StateWidth-1:0] {
StIdle = 11'b10001000001,
StReqAddrKey = 11'b01110101100,
StReqDataKey = 11'b01110010001,
StReadSeeds = 11'b11011111110,
StReadEval = 11'b01000100111,
StWait = 11'b00100111011,
StEntropyReseed = 11'b00011000110,
StRmaWipe = 11'b10010110101,
StRmaRsp = 11'b10110001010,
StDisabled = 11'b11111100011,
StInvalid = 11'b11101011000
} lcmgr_state_e;
lcmgr_state_e state_q, state_d;
logic state_err;
assign debug_state_o = state_q;
//SEC_CM: CTRL.FSM.SPARSE
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, lcmgr_state_e, StIdle)
lc_tx_t err_sts_d, err_sts_q;
logic err_sts_set;
lc_tx_t rma_ack_d, rma_ack_q;
logic validate_q, validate_d;
logic [SeedCntWidth-1:0] seed_cnt_q;
logic [SeedRdsWidth-1:0] addr_cnt_q;
logic seed_cnt_en, seed_cnt_clr;
logic addr_cnt_en, addr_cnt_clr;
logic rma_wipe_req, rma_wipe_done, rma_wipe_req_int;
logic [WipeIdxWidth-1:0] rma_wipe_idx;
logic rma_wipe_idx_incr;
flash_lcmgr_phase_e phase;
logic seed_phase;
logic rma_phase;
logic seed_err_q, seed_err_d;
assign seed_phase = phase == PhaseSeed;
assign rma_phase = phase == PhaseRma;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rma_ack_q <= lc_ctrl_pkg::Off;
validate_q <= 1'b0;
seed_err_q <= '0;
end else begin
rma_ack_q <= rma_ack_d;
validate_q <= validate_d;
seed_err_q <= seed_err_d;
end
end
assign seed_err_o = seed_err_q | seed_err_d;
// seed cnt tracks which seed round we are handling at the moment
// SEC_CM: CTR.REDUN
logic seed_cnt_err_d, seed_cnt_err_q;
prim_count #(
.Width(SeedCntWidth)
) u_seed_cnt (
.clk_i,
.rst_ni,
.clr_i(seed_cnt_clr),
.set_i('0),
.set_cnt_i('0),
.incr_en_i(seed_cnt_en),
.decr_en_i(1'b0),
.step_i(SeedCntWidth'(1'b1)),
.cnt_o(seed_cnt_q),
.cnt_next_o(),
.err_o(seed_cnt_err_d)
);
// SEC_CM: CTR.REDUN
logic addr_cnt_err_d, addr_cnt_err_q;
prim_count #(
.Width(SeedRdsWidth)
) u_addr_cnt (
.clk_i,
.rst_ni,
.clr_i(addr_cnt_clr),
.set_i('0),
.set_cnt_i('0),
.incr_en_i(addr_cnt_en),
.decr_en_i(1'b0),
.step_i(SeedRdsWidth'(1'b1)),
.cnt_o(addr_cnt_q),
.cnt_next_o(),
.err_o(addr_cnt_err_d)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
addr_cnt_err_q <= '0;
seed_cnt_err_q <= '0;
end else begin
addr_cnt_err_q <= addr_cnt_err_q | addr_cnt_err_d;
seed_cnt_err_q <= seed_cnt_err_q | seed_cnt_err_d;
end
end
// read data integrity check
logic data_err;
logic data_intg_ok;
tlul_data_integ_dec u_data_intg_chk (
.data_intg_i(rdata_i),
.data_err_o(data_err)
);
assign data_intg_ok = ~data_err;
// hold on to failed integrity until reset
logic data_invalid_d, data_invalid_q;
assign data_invalid_d = data_invalid_q |
(rvalid_i & ~data_intg_ok);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
data_invalid_q <= '0;
end else begin
data_invalid_q <= data_invalid_d;
end
end
// capture the seed values
logic [SeedRdsWidth-1:0] rd_idx;
logic [NumSeedWidth-1:0] seed_idx;
assign rd_idx = addr_cnt_q[SeedRdsWidth-1:0];
assign seed_idx = seed_cnt_q[NumSeedWidth-1:0];
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
seeds_q <= RndCnstAllSeeds;
end else if (seed_phase && validate_q && rvalid_i) begin
// validate current value
seeds_q[seed_idx][rd_idx] <= seeds_q[seed_idx][rd_idx] &
rdata_i[BusWidth-1:0];
end else if (seed_phase && rvalid_i) begin
seeds_q[seed_idx][rd_idx] <= rdata_i[BusWidth-1:0];
end
end
page_addr_t seed_page;
logic [InfoTypesWidth-1:0] seed_info_sel;
logic [BusAddrW-1:0] seed_page_addr;
assign seed_page = SeedInfoPageSel[seed_idx];
assign seed_info_sel = seed_page.sel;
assign seed_page_addr = BusAddrW'({seed_page.addr, BusWordW'(0)});
logic start;
flash_op_e op;
flash_prog_e prog_type;
flash_erase_e erase_type;
flash_part_e part_sel;
logic [InfoTypesWidth-1:0] info_sel;
logic [11:0] num_words;
logic [BusAddrW-1:0] addr;
assign prog_type = FlashProgNormal;
assign erase_type = FlashErasePage;
// seed phase is always read
// rma phase is erase unless we are validating
assign op = FlashOpRead;
// synchronize inputs
logic init_q;
prim_flop_2sync #(
.Width(1),
.ResetValue(0)
) u_sync_flash_init (
.clk_i,
.rst_ni,
.d_i(init_i),
.q_o(init_q)
);
typedef enum logic [1:0] {
RmaReqInit,
RmaReqKey,
RmaReqWait,
RmaReqLast
} rma_req_idx_e;
lc_tx_t [RmaReqLast-1:0] rma_req;
prim_lc_sync #(
.NumCopies(int'(RmaReqLast))
) u_sync_rma_req (
.clk_i,
.rst_ni,
.lc_en_i(rma_req_i),
.lc_en_o(rma_req)
);
logic addr_key_req_d;
logic addr_key_ack_q;
logic data_key_req_d;
logic data_key_ack_q;
// req/ack to otp
prim_sync_reqack u_addr_sync_reqack (
.clk_src_i(clk_i),
.rst_src_ni(rst_ni),
.clk_dst_i(clk_otp_i),
.rst_dst_ni(rst_otp_ni),
.req_chk_i(1'b1),
.src_req_i(addr_key_req_d),
.src_ack_o(addr_key_ack_q),
.dst_req_o(otp_key_req_o.addr_req),
.dst_ack_i(otp_key_rsp_i.addr_ack)
);
// req/ack to otp
prim_sync_reqack u_data_sync_reqack (
.clk_src_i(clk_i),
.rst_src_ni(rst_ni),
.clk_dst_i(clk_otp_i),
.rst_dst_ni(rst_otp_ni),
.req_chk_i(1'b1),
.src_req_i(data_key_req_d),
.src_ack_o(data_key_ack_q),
.dst_req_o(otp_key_req_o.data_req),
.dst_ack_i(otp_key_rsp_i.data_ack)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
addr_key_o <= RndCnstAddrKey;
data_key_o <= RndCnstDataKey;
end else begin
if (addr_key_req_d && addr_key_ack_q) begin
addr_key_o <= flash_key_t'(otp_key_rsp_i.key);
rand_addr_key_o <= flash_key_t'(otp_key_rsp_i.rand_key);
end
if (data_key_req_d && data_key_ack_q) begin
data_key_o <= flash_key_t'(otp_key_rsp_i.key);
rand_data_key_o <= flash_key_t'(otp_key_rsp_i.rand_key);
end
end
end
///////////////////////////////
// Hardware Interface FSM
///////////////////////////////
logic rma_done;
assign rma_done = lc_tx_test_true_strict(
lc_ctrl_pkg::lc_tx_and_hi(rma_req_i,rma_ack_d));
always_comb begin
// phases of the hardware interface
phase = PhaseNone;
// timer controls
seed_cnt_en = 1'b0;
seed_cnt_clr = 1'b0;
addr_cnt_en = 1'b0;
addr_cnt_clr = 1'b0;
// flash ctrrl arb controls
start = 1'b0;
addr = '0;
part_sel = FlashPartInfo;
info_sel = 0;
num_words = SeedReads[11:0] - 12'd1;
// seed status
seed_err_d = seed_err_q;
state_d = state_q;
rma_ack_d = lc_ctrl_pkg::Off;
validate_d = validate_q;
// read buffer enable
rd_buf_en_o = 1'b0;
addr_key_req_d = 1'b0;
data_key_req_d = 1'b0;
// entropy handling
edn_req_o = 1'b0;
lfsr_en_o = 1'b0;
// rma related
rma_wipe_req = 1'b0;
rma_wipe_idx_incr = 1'b0;
// disable flash access entirely
dis_access_o = lc_ctrl_pkg::Off;
state_err = 1'b0;
unique case (state_q)
// If rma request is seen, directly transition to wipe.
// Since init has not been called, there are no guarantees
// to entropy behavior, thus do not reseed
StIdle: begin
if (lc_tx_test_true_strict(rma_req[RmaReqInit])) begin
state_d = StRmaWipe;
end else if (init_q) begin
state_d = StReqAddrKey;
end
end
StReqAddrKey: begin
phase = PhaseSeed;
addr_key_req_d = 1'b1;
if (lc_tx_test_true_strict(rma_req[RmaReqKey])) begin
state_d = StRmaWipe;
end else if (addr_key_ack_q) begin
state_d = StReqDataKey;
end
end
StReqDataKey: begin
phase = PhaseSeed;
data_key_req_d = 1'b1;
if (lc_tx_test_true_strict(rma_req[RmaReqKey])) begin
state_d = StRmaWipe;
end else if (data_key_ack_q) begin
// provision_en is only a "good" value after otp/lc initialization
state_d = provision_en_i ? StReadSeeds : StWait;
end
end
// read seeds
StReadSeeds: begin
// seeds can be updated in this state
phase = PhaseSeed;
// kick off flash transaction
start = 1'b1;
addr = BusAddrW'(seed_page_addr);
info_sel = seed_info_sel;
// we have checked all seeds, proceed
addr_cnt_en = rvalid_i;
if (seed_cnt_q == NumSeeds) begin
start = 1'b0;
state_d = StWait;
end else if (done_i) begin
seed_err_d = |err_i;
state_d = StReadEval;
end
end // case: StReadSeeds
StReadEval: begin
phase = PhaseSeed;
addr_cnt_clr = 1'b1;
state_d = StReadSeeds;
if (validate_q) begin
seed_cnt_en = 1'b1;
validate_d = 1'b0;
end else begin
validate_d = 1'b1;
end
end
// Waiting for an rma entry command
StWait: begin
rd_buf_en_o = 1'b1;
if (lc_tx_test_true_strict(rma_req[RmaReqWait])) begin
state_d = StEntropyReseed;
end
end
// Reseed entropy
StEntropyReseed: begin
edn_req_o = 1'b1;
if(edn_ack_i) begin
state_d = StRmaWipe;
end
end
StRmaWipe: begin
phase = PhaseRma;
lfsr_en_o = 1'b1;
rma_wipe_req = 1'b1;
if (rma_wipe_idx == MaxWipeEntry[WipeIdxWidth-1:0] && rma_wipe_done) begin
// first check for error status
// If error status is set, go directly to invalid terminal state
// If error status is good, go to second check
state_d = (err_sts_q != lc_ctrl_pkg::On) ? StInvalid : StRmaRsp;
end else if (rma_wipe_done) begin
rma_wipe_idx_incr = 1;
end
end
// response to rma request
// Second check for error status:
// If error status indicates error, jump to invalid terminal state
// Otherwise assign output to error status;
StRmaRsp: begin
phase = PhaseRma;
dis_access_o = lc_ctrl_pkg::On;
if (err_sts_q != lc_ctrl_pkg::On) begin
state_d = StInvalid;
end else begin
rma_ack_d = err_sts_q;
end
end
// Disabled state is functionally equivalent to invalid, just without the
// the explicit error-ing
StDisabled: begin
dis_access_o = lc_ctrl_pkg::On;
rma_ack_d = lc_ctrl_pkg::Off;
state_d = StDisabled;
end
StInvalid: begin
dis_access_o = lc_ctrl_pkg::On;
state_err = 1'b1;
rma_ack_d = lc_ctrl_pkg::Off;
state_d = StInvalid;
end
// Invalid catch-all state
default: begin
dis_access_o = lc_ctrl_pkg::On;
state_err = 1'b1;
state_d = StInvalid;
end
endcase // unique case (state_q)
// This fsm does not directly interface with flash so can be
// be transitioned to invalid immediately.
// If rma transition is successful however, do not transition
// and continue acking the life cycle controller, as disable is
// expected behavior under this situation.
if (prim_mubi_pkg::mubi4_test_true_loose(disable_i) &&
state_d != StInvalid &&
!rma_done) begin
state_d = StDisabled;
end
end // always_comb
// If state is already invalid, disable has no impact.
// If state is currently in StRmaRsp with a successful RMA transition, also do not
// transition to disabled state as we need to continue acknowledging lc_ctrl.
`ASSERT(DisableChk_A, prim_mubi_pkg::mubi4_test_true_loose(disable_i) & state_q != StRmaRsp
|=> state_q == StDisabled)
///////////////////////////////
// RMA wiping Mechanism
///////////////////////////////
localparam int unsigned PageCntWidth = prim_util_pkg::vbits(PagesPerBank + 1);
localparam int unsigned WordCntWidth = prim_util_pkg::vbits(BusWordsPerPage + 1);
localparam int unsigned MaxRmaProgBurst = WidthMultiple * 2;
localparam int unsigned BeatCntWidth = prim_util_pkg::vbits(MaxRmaProgBurst);
localparam int unsigned MaxBeatCnt = MaxRmaProgBurst - 1;
logic page_cnt_ld;
logic page_cnt_incr;
logic page_cnt_clr;
logic word_cnt_incr;
logic word_cnt_ld;
logic word_cnt_clr;
logic prog_cnt_en;
logic rd_cnt_en;
logic beat_cnt_clr;
logic [PageCntWidth-1:0] page_cnt, end_page;
logic [WordCntWidth-1:0] word_cnt;
logic [BeatCntWidth-1:0] beat_cnt;
logic [MaxBeatCnt:0][BusWidth-1:0] prog_data;
assign end_page = RmaWipeEntries[rma_wipe_idx].start_page +
RmaWipeEntries[rma_wipe_idx].num_pages;
rma_state_e rma_state_d, rma_state_q;
// SEC_CM: CTRL.FSM.SPARSE
`PRIM_FLOP_SPARSE_FSM(u_rma_state_regs, rma_state_d, rma_state_q, rma_state_e, StRmaIdle)
// SEC_CM: CTR.REDUN
logic page_err_q, page_err_d;
prim_count #(
.Width(PageCntWidth)
) u_page_cnt (
.clk_i,
.rst_ni,
.clr_i(page_cnt_clr),
.set_i(page_cnt_ld),
.set_cnt_i(RmaWipeEntries[rma_wipe_idx].start_page),
.incr_en_i(page_cnt_incr),
.decr_en_i(1'b0),
.step_i(PageCntWidth'(1)),
.cnt_o(page_cnt),
.cnt_next_o(),
.err_o(page_err_d)
);
logic word_err_q, word_err_d;
//SEC_CM: CTR.REDUN
prim_count #(
.Width(WordCntWidth)
) u_word_cnt (
.clk_i,
.rst_ni,
.clr_i(word_cnt_clr),
.set_i(word_cnt_ld),
.set_cnt_i('0),
.incr_en_i(word_cnt_incr),
.decr_en_i(1'b0),
.step_i(WordCntWidth'(MaxRmaProgBurst)),
.cnt_o(word_cnt),
.cnt_next_o(),
.err_o(word_err_d)
);
logic rma_idx_err_q, rma_idx_err_d;
//SEC_CM: CTR.REDUN
prim_count #(
.Width(WipeIdxWidth)
) u_wipe_idx_cnt (
.clk_i,
.rst_ni,
.clr_i('0),
.set_i('0),
.set_cnt_i('0),
.incr_en_i(rma_wipe_idx_incr),
.decr_en_i(1'b0),
.step_i(WipeIdxWidth'(1'b1)),
.cnt_o(rma_wipe_idx),
.cnt_next_o(),
.err_o(rma_idx_err_d)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
page_err_q <= '0;
word_err_q <= '0;
rma_idx_err_q <= '0;
end else begin
page_err_q <= page_err_q | page_err_d;
word_err_q <= word_err_q | word_err_d;
rma_idx_err_q <= rma_idx_err_q | rma_idx_err_d;
end
end
// beat cnt is not made a prim_count because if beat_cnt
// if tampered, the read verification stage will automatically
// fail.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
beat_cnt <= '0;
end else if (beat_cnt_clr) begin
beat_cnt <= '0;
end else if (prog_cnt_en) begin
if (wvalid_o && wready_i) begin
beat_cnt <= beat_cnt + 1'b1;
end
end else if (rd_cnt_en) begin
if (rvalid_i && rready_o) begin
beat_cnt <= beat_cnt + 1'b1;
end
end
end
// latch data programmed
always_ff @(posedge clk_i) begin
if (prog_cnt_en && wvalid_o && wready_i) begin
prog_data[beat_cnt] <= rand_i;
end
end
// once error is set to off, it cannot be unset without a reboot
// On - no errors
// Off - errors were observed
logic [lc_ctrl_pkg::TxWidth-1:0] err_sts_raw_q;
assign err_sts_q = lc_tx_t'(err_sts_raw_q);
assign err_sts_d = err_sts_set && (err_sts_q != lc_ctrl_pkg::Off) ? lc_ctrl_pkg::Off : err_sts_q;
// This primitive is used to place a size-only constraint on the flops in order to prevent
// optimizations. Without this Vivado may infer combo loops. For details, see
// https://github.com/lowRISC/opentitan/issues/10204
prim_flop #(
.Width(lc_ctrl_pkg::TxWidth),
.ResetValue(lc_ctrl_pkg::TxWidth'(lc_ctrl_pkg::On))
) u_prim_flop_err_sts (
.clk_i,
.rst_ni,
.d_i(err_sts_d),
.q_o(err_sts_raw_q)
);
logic rma_start;
logic [BusAddrW-1:0] rma_addr;
flash_op_e rma_op;
flash_part_e rma_part_sel;
logic [InfoTypesWidth-1:0] rma_info_sel;
logic [11:0] rma_num_words;
assign rma_addr = {RmaWipeEntries[rma_wipe_idx].bank,
page_cnt[PageW-1:0],
word_cnt[BusWordW-1:0]};
assign rma_part_sel = RmaWipeEntries[rma_wipe_idx].part;
assign rma_info_sel = RmaWipeEntries[rma_wipe_idx].info_sel;
assign rma_num_words = MaxBeatCnt;
// this variable is specifically here to work around some tooling issues identified in #9661.
// Certain tools identify rma_wipe_req as part of a combinational loop.
// It is not a combinational loop in the synthesized sense, but rather a combinational loop from
// the perspective of simulation scheduler.
// This is not a synthesized combo loop for the following reasons
// 1. rma_wipe_req is changed only based on the value of state_q, so it cannot be
// affected by non-flop signals
// 2. other lint tools do not identify (including sign-off tool) an issue.
// The direct feedthrough assignment below helps address some of the tooling issues.
assign rma_wipe_req_int = rma_wipe_req;
//fsm for handling the actual wipe
logic fsm_err;
// SEC_CM: RMA_ENTRY.MEM.SEC_WIPE
always_comb begin
rma_state_d = rma_state_q;
rma_wipe_done = 1'b0;
rma_start = 1'b0;
rma_op = FlashOpInvalid;
err_sts_set = 1'b0;
page_cnt_ld = 1'b0;
page_cnt_incr = 1'b0;
page_cnt_clr = 1'b0;
word_cnt_ld = 1'b0;
word_cnt_incr = 1'b0;
word_cnt_clr = 1'b0;
prog_cnt_en = 1'b0;
rd_cnt_en = 1'b0;
beat_cnt_clr = 1'b0;
fsm_err = 1'b0;
unique case (rma_state_q)
// Transition to invalid state via disable only when any ongoing stateful
// operations are complete. This ensures we do not electically disturb
// any ongoing operation.
// This of course cannot be guaranteed if the FSM state is directly disturbed,
// and that is considered an extremely invasive attack.
StRmaIdle: begin
if (prim_mubi_pkg::mubi4_test_true_loose(disable_i)) begin
rma_state_d = StRmaDisabled;
end else if (rma_wipe_req_int) begin
rma_state_d = StRmaPageSel;
page_cnt_ld = 1'b1;
end
end
StRmaPageSel: begin
if (prim_mubi_pkg::mubi4_test_true_loose(disable_i)) begin
rma_state_d = StRmaDisabled;
end else if (page_cnt < end_page) begin
rma_state_d = StRmaErase;
end else begin
rma_wipe_done = 1'b1;
page_cnt_clr = 1'b1;
rma_state_d = StRmaIdle;
end
end
StRmaErase: begin
rma_start = 1'b1;
rma_op = FlashOpErase;
if (done_i) begin
err_sts_set = |err_i;
rma_state_d = StRmaEraseWait;
end
end
StRmaEraseWait: begin
word_cnt_ld = 1'b1;
rma_state_d = StRmaWordSel;
end
StRmaWordSel: begin
if (prim_mubi_pkg::mubi4_test_true_loose(disable_i)) begin
rma_state_d = StRmaDisabled;
end else if (word_cnt < BusWordsPerPage) begin
rma_state_d = StRmaProgram;
end else begin
word_cnt_clr = 1'b1;
page_cnt_incr = 1'b1;
rma_state_d = StRmaPageSel;
end
end
StRmaProgram: begin
rma_start = 1'b1;
rma_op = FlashOpProgram;
prog_cnt_en = 1'b1;
if ((beat_cnt == MaxBeatCnt[BeatCntWidth-1:0]) && wready_i) begin
rma_state_d = StRmaProgramWait;
end
end
StRmaProgramWait: begin
rma_start = 1'b1;
rma_op = FlashOpProgram;
if (done_i) begin
beat_cnt_clr = 1'b1;
err_sts_set = |err_i;
rma_state_d = StRmaRdVerify;
end
end
StRmaRdVerify: begin
rma_start = 1'b1;
rma_op = FlashOpRead;
rd_cnt_en = 1'b1;
if ((beat_cnt == MaxBeatCnt[BeatCntWidth-1:0]) && done_i) begin
beat_cnt_clr = 1'b1;
word_cnt_incr = 1'b1;
rma_state_d = StRmaWordSel;
end
if (rvalid_i && rready_o) begin
err_sts_set = prog_data[beat_cnt] != rdata_i[BusWidth-1:0];
end
end
StRmaDisabled: begin
rma_state_d = StRmaDisabled;
end
StRmaInvalid: begin
rma_state_d = StRmaInvalid;
err_sts_set = 1'b1;
fsm_err = 1'b1;
end
default: begin
rma_state_d = StRmaInvalid;
fsm_err = 1'b1;
end
endcase // unique case (rma_state_q)
end // always_comb
// TODO: Replace with a wrapper from tlul, that way the module does not need to know what this is
prim_secded_inv_39_32_enc u_bus_intg (
.data_i(rand_i),
.data_o(wdata_o)
);
assign wvalid_o = prog_cnt_en;
assign ctrl_o.start.q = seed_phase ? start : rma_start;
assign ctrl_o.op.q = seed_phase ? op : rma_op;
assign ctrl_o.prog_sel.q = prog_type;
assign ctrl_o.erase_sel.q = erase_type;
assign ctrl_o.partition_sel.q = seed_phase ? part_sel : rma_part_sel;
assign ctrl_o.info_sel.q = seed_phase ? info_sel : rma_info_sel;
assign ctrl_o.num = seed_phase ? num_words : rma_num_words;
// address is consistent with software width format (full bus)
assign addr_o = seed_phase ? {addr, {BusByteWidth{1'b0}}} :
{rma_addr, {BusByteWidth{1'b0}}};
assign init_busy_o = seed_phase;
// Initialization is considered done when read buffer is enabled.
assign initialized_o = rd_buf_en_o;
assign req_o = seed_phase | rma_phase;
assign rready_o = 1'b1;
assign seeds_o = seeds_q;
assign phase_o = phase;
assign rma_ack_o = rma_ack_q;
// all of these are considered fatal errors
assign fatal_err_o = page_err_q | word_err_q | fsm_err | state_err | rma_idx_err_q |
addr_cnt_err_q | seed_cnt_err_q;
// integrity error is its own category
assign intg_err_o = data_invalid_q;
logic unused_seed_valid;
assign unused_seed_valid = otp_key_rsp_i.seed_valid;
// assertion
`ifdef INC_ASSERT
//VCS coverage off
// pragma coverage off
localparam int MaxRmaDataWidth = MaxRmaProgBurst * BusWidth;
localparam int ShiftWidth = (MaxRmaProgBurst - 1) * BusWidth;
logic [MaxRmaDataWidth-1:0] rma_data_q, rma_data;
always_ff @(posedge clk_i) begin
if (rma_start && rvalid_i && rready_o) begin
rma_data_q <= rma_data;
end
end
//VCS coverage on
// pragma coverage on
assign rma_data = {rdata_i, rma_data_q[MaxRmaDataWidth-1 -: ShiftWidth]};
// check the rma programmed value actually matches what was read back
`ASSERT(ProgRdVerify_A, rma_start & rd_cnt_en & done_i |-> prog_data == rma_data)
`endif
endmodule // flash_ctrl_lcmgr