[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));