[dv/otp_ctrl] Modify ECC error injection in sequence and scb
This PR continues to deattach ecc error injection from dai_rd.
Now we can freely inject ecc error for any read (both dai_rd and tlul_rd).
Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
index d3639dd..db0b888 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -507,15 +507,34 @@
// SW CFG window
end else if ((csr_addr & addr_mask) inside
{[SW_WINDOW_BASE_ADDR : SW_WINDOW_BASE_ADDR + SW_WINDOW_SIZE]}) begin
- if (data_phase_read && check_dai_rd_data) begin
- bit [TL_AW-1:0] otp_addr = (csr_addr & addr_mask - SW_WINDOW_BASE_ADDR) >> 2;
- // TODO: macro ecc uncorrectable error once mem_bkdr_util supports
- //`DV_CHECK_EQ(item.d_data, 0,
- // $sformatf("mem read mismatch at TLUL addr %0h, csr_addr %0h",
- // csr_addr, otp_addr << 2))
- `DV_CHECK_EQ(item.d_data, otp_a[otp_addr],
- $sformatf("mem read mismatch at TLUL addr %0h, csr_addr %0h",
- csr_addr, otp_addr << 2))
+ if (data_phase_read) begin
+ bit [TL_AW-1:0] dai_addr = (csr_addr & addr_mask - SW_WINDOW_BASE_ADDR);
+ bit [TL_AW-1:0] otp_addr = dai_addr >> 2;
+ int part_idx = get_part_index(dai_addr);
+ bit [TL_DW-1:0] read_out;
+ int ecc_err = read_a_word_with_ecc(dai_addr, read_out);
+
+ // ECC uncorrectable errors are gated by `is_tl_mem_access_allowed` function.
+ if (ecc_err != OtpNoEccErr) begin
+ predict_err(part_idx, OtpMacroEccCorrError);
+ if (ecc_err == OtpEccCorrErr) begin
+ `DV_CHECK_EQ(item.d_data, otp_a[otp_addr],
+ $sformatf("mem read mismatch at TLUL addr %0h, csr_addr %0h",
+ csr_addr, dai_addr))
+ end else begin
+ // This is an exception for vendor test partition, which reports uncorrectable errors
+ // as correctable ECC errors.
+ // Only check the first 16 bits because if ECC readout detects uncorrectable error, it
+ // won't continue read the remaining 16 bits.
+ `DV_CHECK_EQ(item.d_data & 16'hffff, read_out & 16'hffff,
+ $sformatf("mem read mismatch at TLUL addr %0h, csr_addr %0h",
+ csr_addr, dai_addr))
+ end
+ end else if (ecc_err == OtpNoEccErr) begin
+ `DV_CHECK_EQ(item.d_data, otp_a[otp_addr],
+ $sformatf("mem read mismatch at TLUL addr %0h, csr_addr %0h",
+ csr_addr, dai_addr))
+ end
end
return;
end else begin
@@ -1179,8 +1198,12 @@
output bit mem_byte_access_err,
output bit mem_wo_err,
output bit mem_ro_err);
- // If sw partition is read locked, then access policy changes from RO to no access
uvm_reg_addr_t addr = cfg.ral_models[ral_name].get_word_aligned_addr(item.a_addr);
+ uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].get_word_aligned_addr(item.a_addr);
+ bit [TL_AW-1:0] addr_mask = ral.get_addr_mask();
+ bit [TL_AW-1:0] dai_addr = (csr_addr & addr_mask - SW_WINDOW_BASE_ADDR);
+
+ // If sw partition is read locked, then access policy changes from RO to no access
if (`gmv(ral.vendor_test_read_lock) == 0 || cfg.otp_ctrl_vif.under_error_states()) begin
if (addr inside {[cfg.ral_models[ral_name].mem_ranges[0].start_addr + VendorTestOffset :
cfg.ral_models[ral_name].mem_ranges[0].start_addr + VendorTestOffset +
@@ -1211,6 +1234,21 @@
return 0;
end
end
+
+ // Check ECC uncorrectable fatal error.
+ if (dai_addr < LifeCycleOffset) begin
+ int part_idx = get_part_index(dai_addr);
+ bit [TL_DW-1:0] read_out;
+ int ecc_err = read_a_word_with_ecc(dai_addr, read_out);
+ if (ecc_err == OtpEccUncorrErr && !ecc_corr_err_only_part(part_idx)) begin
+ predict_err(part_idx, OtpMacroEccUncorrError);
+ set_exp_alert("fatal_macro_error", 1, 20);
+ `DV_CHECK_EQ(item.d_data, 0,
+ $sformatf("ECC uncorrectable error exp to readout all 0s at TLUL addr %0h",
+ addr))
+ return 0;
+ end
+ end
return super.is_tl_mem_access_allowed(item, ral_name, mem_byte_access_err, mem_wo_err,
mem_ro_err);
endfunction
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_smoke_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_smoke_vseq.sv
index 751af24..940f2a6 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_smoke_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_smoke_vseq.sv
@@ -128,7 +128,7 @@
if ($urandom_range(0, 1) && access_locked_parts) write_sw_rd_locks();
for (int i = 0; i < num_dai_op; i++) begin
- bit [TL_DW-1:0] rdata0, rdata1;
+ bit [TL_DW-1:0] rdata0, rdata1, backdoor_rd_val;
`DV_CHECK_RANDOMIZE_FATAL(this)
// recalculate part_idx in case some test turn off constraint dai_wr_legal_addr_c
@@ -142,6 +142,11 @@
if (cfg.otp_ctrl_vif.lc_prog_req == 0) csr_rd(.ptr(ral.err_code[0]), .value(tlul_val));
end
+ // Inject ECC error.
+ if (ecc_otp_err != OtpNoEccErr && dai_addr < LifeCycleOffset) begin
+ backdoor_rd_val = backdoor_inject_ecc_err(dai_addr, ecc_otp_err);
+ end
+
if (rand_rd) begin
// OTP read via DAI, check data in scb
dai_rd(dai_addr, rdata0, rdata1);
@@ -154,6 +159,11 @@
tl_access(.addr(tlul_addr), .write(0), .data(tlul_val), .blocking(1), .check_rsp(0));
end
+ // Backdoor restore injected ECC error, but should not affect fatal alerts.
+ if (ecc_otp_err != OtpNoEccErr && dai_addr < LifeCycleOffset) begin
+ cfg.mem_bkdr_util_h.write32({dai_addr[TL_DW-3:2], 2'b00}, backdoor_rd_val);
+ end
+
if ($urandom_range(0, 1)) csr_rd(.ptr(ral.direct_access_regwen), .value(tlul_val));
if ($urandom_range(0, 1)) csr_rd(.ptr(ral.status), .value(tlul_val));
if (cfg.otp_ctrl_vif.lc_prog_req == 0) csr_rd(.ptr(ral.err_code[0]), .value(tlul_val));