[dv/otp] Probe internal mubi interface

Force internal mubi types to values that are not true or false.
Make sure design locked the part accesses.

Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/ip/otp_ctrl/dv/cov/otp_ctrl_cov_bind.sv b/hw/ip/otp_ctrl/dv/cov/otp_ctrl_cov_bind.sv
index d33f727..915e892 100644
--- a/hw/ip/otp_ctrl/dv/cov/otp_ctrl_cov_bind.sv
+++ b/hw/ip/otp_ctrl/dv/cov/otp_ctrl_cov_bind.sv
@@ -3,7 +3,28 @@
 // SPDX-License-Identifier: Apache-2.0
 //
 // Binds OTP_CTRL functional coverage interaface to the top level OTP_CTRL module.
+`define PART_MUBI_COV(__part_name, __index)                                           \
+  bind otp_ctrl cip_mubi_cov_if #(.Width(8)) ``__part_name``_read_lock_mubi_cov_if (  \
+    .rst_ni (rst_ni),                                                                 \
+    .mubi   (part_access[``__index``].read_lock)                                      \
+  );                                                                                  \
+  bind otp_ctrl cip_mubi_cov_if #(.Width(8)) ``__part_name``_write_lock_mubi_cov_if ( \
+    .rst_ni (rst_ni),                                                                 \
+    .mubi   (part_access[``__index``].write_lock)                                     \
+  );
+
+`define DAI_MUBI_COV(__part_name, __index)                                                \
+  bind otp_ctrl cip_mubi_cov_if #(.Width(8)) dai_``__part_name``_read_lock_mubi_cov_if (  \
+    .rst_ni (rst_ni),                                                                     \
+    .mubi   (part_access_dai[``__index``].read_lock)                                      \
+  );                                                                                      \
+  bind otp_ctrl cip_mubi_cov_if #(.Width(8)) dai_``__part_name``_write_lock_mubi_cov_if ( \
+    .rst_ni (rst_ni),                                                                     \
+    .mubi   (part_access_dai[``__index``].write_lock)                                     \
+  );
+
 module otp_ctrl_cov_bind;
+  import otp_ctrl_part_pkg::*;
 
   bind otp_ctrl otp_ctrl_cov_if u_otp_ctrl_cov_if (
     .pwr_otp_o        (pwr_otp_o),
@@ -38,4 +59,25 @@
     .rst_ni (rst_ni),
     .mubi   (lc_check_byp_en_i)
   );
+
+  // Mubi internal coverage for buffered and unbuffered partitions.
+  `PART_MUBI_COV(vendor_test, VendorTestIdx)
+  `PART_MUBI_COV(creator_sw, CreatorSwCfgIdx)
+  `PART_MUBI_COV(owner_sw, OwnerSwCfgIdx)
+  `PART_MUBI_COV(hw_cfg, HwCfgIdx)
+  `PART_MUBI_COV(secret0, Secret0Idx)
+  `PART_MUBI_COV(secret1, Secret1Idx)
+  `PART_MUBI_COV(secret2, Secret2Idx)
+
+  // Mubi internal coverage for DAI interface access
+  `DAI_MUBI_COV(vendor_test, VendorTestIdx)
+  `DAI_MUBI_COV(creator_sw, CreatorSwCfgIdx)
+  `DAI_MUBI_COV(owner_sw, OwnerSwCfgIdx)
+  `DAI_MUBI_COV(hw_cfg, HwCfgIdx)
+  `DAI_MUBI_COV(secret0, Secret0Idx)
+  `DAI_MUBI_COV(secret1, Secret1Idx)
+  `DAI_MUBI_COV(secret2, Secret2Idx)
+
+`undef PART_MUBI_COV
+`undef DAI_MUBI_COV
 endmodule
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
index 2275ffe..95c895d 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env.sv
@@ -79,6 +79,9 @@
       `uvm_fatal(`gfn, "failed to get otp_ctrl_vif from uvm_config_db")
     end
 
+    // Check if `NumPart` constant is assigned to the correct value.
+    `DV_CHECK(NumPart == (LifeCycleIdx + 1))
+
   endfunction
 
   function void connect_phase(uvm_phase phase);
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
index 5c91bba..2c2e5b4 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_cfg.sv
@@ -40,8 +40,9 @@
 
   // TODO: reggen tool optimization. Temp mannual setup for prim_tl_agent.
   rand tl_agent_cfg prim_tl_agent_cfg;
+
   // Introduce this flag to avoid close source conflict.
-  bit               create_prim_tl_agent = 1;
+  bit create_prim_tl_agent = 1;
 
   `uvm_object_utils_begin(otp_ctrl_env_cfg)
   `uvm_object_utils_end
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
index 457414a..c493b0c 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_env_pkg.sv
@@ -177,6 +177,11 @@
     OtpMacroAlert
   } otp_alert_e;
 
+  typedef struct packed {
+    bit read_lock;
+    bit write_lock;
+  } otp_part_access_lock_t;
+
   typedef virtual otp_ctrl_if otp_ctrl_vif;
 
   parameter otp_err_code_e OTP_TERMINAL_ERRS[4] = {OtpMacroEccUncorrError,
diff --git a/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv b/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
index 620c59c..90aa84b 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_if.sv
@@ -217,6 +217,36 @@
     endcase
   endtask
 
+  // This task forces otp_ctrl's internal mubi signals to values that are not mubi::true or mubi::
+  // false. Then scb will check if design treats these values as locking the partition access.
+  task automatic force_part_access_mubi(otp_part_access_lock_t forced_part_access_sel[NumPart-1]);
+    static part_access_t [NumPart-1:0] part_access_val, part_access_dai_val;
+    @(posedge clk_i);
+
+    part_access_val = tb.dut.part_access;
+    part_access_dai_val = tb.dut.part_access_dai;
+
+    foreach (forced_part_access_sel[i]) begin
+      if (forced_part_access_sel[i].read_lock) begin
+        part_access_val[i].read_lock = get_rand_mubi8_val(.t_weight(0), .f_weight(0));
+        part_access_dai_val[i].read_lock = get_rand_mubi8_val(.t_weight(0), .f_weight(0));
+      end
+      if (forced_part_access_sel[i].write_lock) begin
+        part_access_val[i].write_lock = get_rand_mubi8_val(.t_weight(0), .f_weight(0));
+        part_access_dai_val[i].write_lock = get_rand_mubi8_val(.t_weight(0), .f_weight(0));
+      end
+   end
+
+   force tb.dut.part_access = part_access_val;
+   force tb.dut.part_access_dai = part_access_dai_val;
+  endtask
+
+  task automatic release_part_access_mubi();
+    @(posedge clk_i);
+    release tb.dut.part_access;
+    release tb.dut.part_access_dai;
+  endtask
+
   // In open source environment, `otp_alert_o` to is tied to 2'b01 (alert_p is 0 and alert_n is 1).
   if (`PRIM_DEFAULT_IMPL == prim_pkg::ImplGeneric) `ASSERT(OtpAstAlertO_A, otp_alert_o == 2'b01)
 
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 3822b2a..2ccfa76 100644
--- a/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
+++ b/hw/ip/otp_ctrl/dv/env/otp_ctrl_scoreboard.sv
@@ -120,9 +120,9 @@
         @(posedge cfg.otp_ctrl_vif.pwr_otp_done_o || cfg.under_reset ||
                   cfg.otp_ctrl_vif.alert_reqs) begin
           if (!cfg.under_reset && !cfg.otp_ctrl_vif.alert_reqs) begin
-            otp_ctrl_part_pkg::otp_hw_cfg_data_t exp_hwcfg_data;
-            otp_ctrl_pkg::otp_keymgr_key_t       exp_keymgr_data;
-            otp_ctrl_pkg::otp_lc_data_t          exp_lc_data;
+            otp_ctrl_part_pkg::otp_hw_cfg_data_t   exp_hwcfg_data;
+            otp_ctrl_pkg::otp_keymgr_key_t         exp_keymgr_data;
+            otp_ctrl_pkg::otp_lc_data_t            exp_lc_data;
             bit [otp_ctrl_pkg::KeyMgrKeyWidth-1:0] exp_keymgr_key0, exp_keymgr_key1;
 
             if (dai_digest_ip != LifeCycleIdx) begin
diff --git a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
index 0ec4950..7229012 100644
--- a/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
+++ b/hw/ip/otp_ctrl/dv/env/seq_lib/otp_ctrl_base_vseq.sv
@@ -64,6 +64,7 @@
   // Cfg errors are cleared after reset
   virtual task apply_reset(string kind = "HARD");
     super.apply_reset(kind);
+    cfg.otp_ctrl_vif.release_part_access_mubi();
   endtask
 
   virtual task dut_shutdown();
@@ -257,6 +258,61 @@
     csr_rd(.ptr(ral.secret2_digest[1]),        .value(val));
   endtask
 
+  // If the partition is read/write locked, there is 20% chance we will force the internal mubi
+  // access signal to the values other than mubi::true or mubi::false.
+  virtual task force_mubi_part_access();
+    otp_part_access_lock_t forced_mubi_part_access[NumPart-1];
+
+    if (`gmv(ral.vendor_test_digest[0]) || `gmv(ral.vendor_test_digest[1])) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[VendorTestIdx].write_lock = 1;
+    end
+    if (`gmv(ral.vendor_test_read_lock) == 0) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[VendorTestIdx].read_lock = 1;
+    end
+
+    if (`gmv(ral.creator_sw_cfg_digest[0]) || `gmv(ral.creator_sw_cfg_digest[1])) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[CreatorSwCfgIdx].write_lock = 1;
+    end
+    if (`gmv(ral.creator_sw_cfg_read_lock) == 0) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[CreatorSwCfgIdx].read_lock = 1;
+    end
+
+    if (`gmv(ral.owner_sw_cfg_digest[0]) || `gmv(ral.owner_sw_cfg_digest[1])) begin
+      if ($urandom_range(0, 4) == 0) forced_mubi_part_access[OwnerSwCfgIdx].write_lock = 1;
+    end
+    if (`gmv(ral.owner_sw_cfg_read_lock) == 0) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[OwnerSwCfgIdx].read_lock = 1;
+    end
+
+    if (`gmv(ral.hw_cfg_digest[0]) || `gmv(ral.hw_cfg_digest[1])) begin
+      // TODO: hw_cfg part cannot be read locked.
+      // if (!$urandom_range(0, 4)) cfg.forced_mubi_part_access[HwCfgIdx].read_lock = 1;
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[HwCfgIdx].write_lock = 1;
+    end
+
+    if (`gmv(ral.secret0_digest[0]) || `gmv(ral.secret0_digest[1])) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret0Idx].read_lock = 1;
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret0Idx].write_lock = 1;
+    end
+
+    if (`gmv(ral.secret1_digest[0]) || `gmv(ral.secret1_digest[1])) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret1Idx].read_lock = 1;
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret1Idx].write_lock = 1;
+    end
+
+    if (`gmv(ral.secret2_digest[0]) || `gmv(ral.secret2_digest[1])) begin
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret2Idx].read_lock = 1;
+      if (!$urandom_range(0, 4)) forced_mubi_part_access[Secret2Idx].write_lock = 1;
+    end
+
+    foreach (forced_mubi_part_access[i]) begin
+      `uvm_info(`gfn, $sformatf("partition %0d inject mubi value: read=%0b, write=%0b", i,
+          forced_mubi_part_access[i].read_lock, forced_mubi_part_access[i].write_lock), UVM_HIGH)
+    end
+
+    cfg.otp_ctrl_vif.force_part_access_mubi(forced_mubi_part_access);
+  endtask
+
   // This function backdoor inject error according to ecc_err.
   // For example, if err_mask is set to 'b01, bit 1 in OTP macro will be flipped.
   // This function will output original backdoor read data for the given address.
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 b304d08..5a4f201 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
@@ -118,6 +118,11 @@
       end
       trigger_checks(.val(check_trigger_val), .wait_done(1), .ecc_err(ecc_chk_err));
 
+      if ($urandom_range(0, 1) && access_locked_parts) write_sw_rd_locks();
+
+      // Backdoor write mubi to values that are not true or false.
+      force_mubi_part_access();
+
       if (do_req_keys && !cfg.otp_ctrl_vif.alert_reqs) begin
         req_otbn_key();
         req_flash_addr_key();
@@ -130,8 +135,6 @@
         if (cfg.otp_ctrl_vif.lc_prog_req == 0) csr_rd(.ptr(ral.err_code[0]), .value(tlul_val));
       end
 
-      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, backdoor_rd_val;