[dv/alert_handler] Support shadow register sequence

This PR supports alert_handler shadow reg sequence. The alert_handler
does not have any outgoing alerts, so the checking is slightly different
from common sequence.

Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
index 6b8c8e1..25b95b6 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
@@ -32,12 +32,15 @@
     end
     begin
       string alert_name = csr.get_update_err_alert_name();
-      `DV_SPINWAIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.get_alert()) begin
-                     cfg.clk_rst_vif.wait_clks(1);
-                   end,
-                   $sformatf("%0s update_err alert not detected", csr.get_name()))
-      `DV_SPINWAIT(cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();,
-                   $sformatf("timeout for alert:%0s", alert_name))
+      // This logic gates alert_handler testbench because it does not have outgoing alert.
+      if (cfg.m_alert_agent_cfg.exists(alert_name)) begin
+        `DV_SPINWAIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.get_alert()) begin
+                       cfg.clk_rst_vif.wait_clks(1);
+                     end,
+                     $sformatf("%0s update_err alert not detected", csr.get_name()))
+        `DV_SPINWAIT(cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();,
+                     $sformatf("timeout for alert:%0s", alert_name))
+      end
     end
   join
 endtask
@@ -81,7 +84,9 @@
   csr_wr(.ptr(shadowed_csr), .value(wdata), .en_shadow_wr(0), .predict(1));
 
   shadow_reg_wr(shadowed_csr, err_wdata);
-  predict_shadow_reg_status(.predict_update_err(1));
+  // If the shadow register is external register, writing two different value might not actually
+  // trigger update error. So we trigger dv_base_reg function to double check.
+  predict_shadow_reg_status(.predict_update_err(shadowed_csr.get_shadow_update_err()));
 
   read_check_shadow_reg_status("Write_and_check_update_error task");
   csr_rd_check(.ptr(shadowed_csr), .compare_vs_ral(1));
@@ -104,8 +109,10 @@
   wdata = get_shadow_reg_diff_val(shadowed_csr, wdata);
   csr_wr(.ptr(shadowed_csr), .value(wdata), .en_shadow_wr(1), .predict(1));
 
-  `DV_CHECK_EQ(cfg.m_alert_agent_cfg[alert_name].vif.get_alert(), 0,
-               $sformatf("Unexpected alert: %s fired", alert_name))
+  if (cfg.m_alert_agent_cfg.exists(alert_name)) begin
+    `DV_CHECK_EQ(cfg.m_alert_agent_cfg[alert_name].vif.get_alert(), 0,
+                 $sformatf("Unexpected alert: %s fired", alert_name))
+  end
 
   read_check_shadow_reg_status("Check_csr_read_clear_staged_val task");
   csr_rd_check(.ptr(shadowed_csr), .compare_vs_ral(1));
@@ -127,12 +134,12 @@
   err_val = get_shadow_reg_diff_val(shadowed_csr, origin_val);
 
   csr_poke(.ptr(shadowed_csr), .value(err_val), .kind(kind), .predict(1));
-  predict_shadow_reg_status(.predict_storage_err(1));
+  predict_shadow_reg_status(.predict_storage_err(shadowed_csr.get_shadow_storage_err()));
   `uvm_info(`gfn, $sformatf("backdoor write %s through %s with value 0x%0h",
             shadowed_csr.`gfn, kind.name, err_val), UVM_HIGH);
 
   // This non-blocking task checks if the alert is continuously firing until reset is issued.
-  check_fatal_alert_nonblocking(alert_name);
+  if (cfg.m_alert_agent_cfg.exists(alert_name)) check_fatal_alert_nonblocking(alert_name);
 
   // Wait random clock cycles and ensure the fatal alert is continuously firing.
   cfg.clk_rst_vif.wait_clks($urandom_range(10, 100));
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg.sv b/hw/dv/sv/dv_base_reg/dv_base_reg.sv
index a4aca6e..a3fda0f 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg.sv
@@ -26,6 +26,7 @@
                int          has_coverage);
     super.new(name, n_bits, has_coverage);
     atomic_en_shadow_wr = new(1);
+    shadowed_val = ~committed_val;
   endfunction : new
 
   function void get_dv_base_reg_fields(ref dv_base_reg_field dv_fields[$]);
@@ -159,11 +160,11 @@
   endfunction
 
   function bit get_shadow_storage_err();
-    uvm_reg_data_t mask = (1'b1 << (get_msb_pos() + 1)) - 1;
+    uvm_reg_data_t mask = get_reg_mask();
     uvm_reg_data_t shadowed_val_temp = (~shadowed_val) & mask;
     uvm_reg_data_t committed_val_temp = committed_val & mask;
-    `uvm_info(`gfn, $sformatf("shadow_val %0h, commmit_val %0h", shadowed_val_temp,
-                              committed_val_temp), UVM_DEBUG)
+    `uvm_info(`gfn, $sformatf("shadow_val %0h, commmit_val %0h", shadowed_val & mask,
+                              committed_val_temp), UVM_HIGH)
     return shadowed_val_temp != committed_val_temp;
   endfunction
 
@@ -182,19 +183,20 @@
 
     // no need to update shadow value or access type if access is not OK, as access is aborted
     // no need to update if not write
-    if (rw.status != UVM_IS_OK || kind != UVM_PREDICT_WRITE) return;
+    // no need to update staged value if it triggers a storage error
+    if (rw.status != UVM_IS_OK || kind != UVM_PREDICT_WRITE || get_shadow_storage_err()) return;
 
     if (is_shadowed && !shadow_fatal_lock) begin
       // first write
       if (!shadow_wr_staged) begin
         shadow_wr_staged = 1;
         // rw.value is a dynamic array
-        staged_shadow_val = rw.value[0];
+        staged_shadow_val = rw.value[0] & get_reg_mask();
         return;
       end begin
         // second write
         shadow_wr_staged = 0;
-        if (staged_shadow_val != rw.value[0]) begin
+        if (staged_shadow_val != (rw.value[0] & get_reg_mask())) begin
           dv_base_reg_field dv_fields[$];
           get_dv_base_reg_fields(dv_fields);
 
@@ -212,7 +214,7 @@
           // If there is no shadow_update error, update the entire committed value.
           committed_val = staged_shadow_val;
         end
-        shadowed_val  = ~committed_val;
+        shadowed_val = ~committed_val;
       end
       lock_lockable_flds(committed_val);
     end else begin
diff --git a/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson b/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
index b25cc23..d967bce 100644
--- a/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
+++ b/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
@@ -27,6 +27,7 @@
                 "{proj_root}/hw/dv/tools/dvsim/tests/csr_tests.hjson",
                 "{proj_root}/hw/dv/tools/dvsim/tests/intr_test.hjson",
                 "{proj_root}/hw/dv/tools/dvsim/tests/tl_access_tests.hjson",
+                "{proj_root}/hw/dv/tools/dvsim/tests/shadow_reg_errors_tests.hjson",
                 "{proj_root}/hw/dv/tools/dvsim/tests/stress_tests.hjson"]
 
   // Add additional tops for simulation.
diff --git a/hw/ip/alert_handler/dv/env/alert_handler_env_cfg.sv b/hw/ip/alert_handler/dv/env/alert_handler_env_cfg.sv
index dc6d390..c63b991 100644
--- a/hw/ip/alert_handler/dv/env/alert_handler_env_cfg.sv
+++ b/hw/ip/alert_handler/dv/env/alert_handler_env_cfg.sv
@@ -20,6 +20,8 @@
   virtual function void initialize(bit [TL_AW-1:0] csr_base_addr = '1);
     has_edn = 1;
     super.initialize(csr_base_addr);
+    shadow_update_err_status_fields[ral.loc_alert_cause[LocalShadowRegUpdateErr].la] = 1;
+    shadow_storage_err_status_fields[ral.loc_alert_cause[LocalShadowRegStorageErr].la] = 1;
 
     // set num_interrupts & num_alerts
     begin
diff --git a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_common_vseq.sv b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_common_vseq.sv
index d023523..3095e6d 100644
--- a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_common_vseq.sv
+++ b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_common_vseq.sv
@@ -29,4 +29,22 @@
     csr_rd_check(.ptr(ral.loc_alert_cause[LocalBusIntgFail]), .compare_value(exp_val));
   endtask
 
+  virtual function void predict_shadow_reg_status(bit predict_update_err  = 0,
+                                                  bit predict_storage_err = 0);
+    if (predict_update_err) begin
+      foreach (cfg.shadow_update_err_status_fields[status_field]) begin
+        if (`gmv(ral.loc_alert_en_shadowed[LocalShadowRegUpdateErr])) begin
+          void'(status_field.predict(cfg.shadow_update_err_status_fields[status_field]));
+        end
+      end
+    end
+    if (predict_storage_err) begin
+      foreach (cfg.shadow_storage_err_status_fields[status_field]) begin
+        if (`gmv(ral.loc_alert_en_shadowed[LocalShadowRegStorageErr])) begin
+          void'(status_field.predict(cfg.shadow_storage_err_status_fields[status_field]));
+        end
+      end
+    end
+  endfunction
+
 endclass