[dv] Add random backdoor for csr_hw_reset

1. Add random backdoor for csr_hw_reset, so that we can test RO register
which is updated by hw
2. Add hier_path for IP that has extra hierarchy for reg block
3. backdoor support to top-level

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/dv/sv/csr_utils/csr_seq_lib.sv b/hw/dv/sv/csr_utils/csr_seq_lib.sv
index 1b5322b..71d076c 100644
--- a/hw/dv/sv/csr_utils/csr_seq_lib.sv
+++ b/hw/dv/sv/csr_utils/csr_seq_lib.sv
@@ -158,6 +158,8 @@
 // checks. It is run as the first step of the CSR HW reset test.
 //--------------------------------------------------------------------------------------------------
 class csr_write_seq extends csr_base_seq;
+  static bit test_backdoor_path_done; // only run once
+  bit en_rand_backdoor_write;
   `uvm_object_utils(csr_write_seq)
 
   `uvm_object_new
@@ -165,7 +167,20 @@
   virtual task body();
     uvm_reg_data_t wdata;
 
+    // check all hdl paths are valid
+    if (!test_backdoor_path_done) begin
+      uvm_reg_mem_hdl_paths_seq hdl_check_seq;
+      hdl_check_seq = uvm_reg_mem_hdl_paths_seq::type_id::create("hdl_check_seq");
+      foreach (models[i]) begin
+        hdl_check_seq.model = models[i];
+        hdl_check_seq.start(null);
+      end
+      test_backdoor_path_done = 1;
+    end
+
     foreach (test_csrs[i]) begin
+      dv_base_reg dv_csr;
+      bit         backdoor;
       // check if parent block or register is excluded from write
       if (m_csr_excl_item.is_excl(test_csrs[i], CsrExclWrite, CsrHwResetTest)) begin
         `uvm_info(`gtn, $sformatf("Skipping register %0s due to CsrExclWrite exclusion",
@@ -178,7 +193,14 @@
 
       `DV_CHECK_STD_RANDOMIZE_FATAL(wdata)
       wdata &= get_mask_excl_fields(test_csrs[i], CsrExclWrite, CsrHwResetTest, m_csr_excl_item);
-      csr_wr(.csr(test_csrs[i]), .value(wdata), .blocking(0));
+
+      `downcast(dv_csr, test_csrs[i])
+      if (en_rand_backdoor_write && !dv_csr.get_is_ext_reg()) begin
+        `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(backdoor,
+                                           backdoor dist {0 :/ 7, 1 :/ 3};)
+      end
+
+      csr_wr(.csr(test_csrs[i]), .value(wdata), .blocking(0), .backdoor(backdoor));
     end
   endtask
 
diff --git a/hw/dv/sv/csr_utils/csr_utils_pkg.sv b/hw/dv/sv/csr_utils/csr_utils_pkg.sv
index bb0252f..0e08a19 100644
--- a/hw/dv/sv/csr_utils/csr_utils_pkg.sv
+++ b/hw/dv/sv/csr_utils/csr_utils_pkg.sv
@@ -232,8 +232,7 @@
                         input bit            predict = 0,
                         input uvm_reg_map    map = null);
     if (backdoor) begin
-        csr_poke(csr, value, check);
-        if (predict) void'(csr.predict(.value(value), .kind(UVM_PREDICT_DIRECT)));
+      csr_poke(csr, value, check, predict);
     end else if (blocking) begin
       csr_wr_sub(csr, value, check, path, timeout_ns, map);
       if (predict) void'(csr.predict(.value(value), .kind(UVM_PREDICT_WRITE)));
@@ -286,13 +285,24 @@
   // backdoor write csr
   task automatic csr_poke(input uvm_reg        csr,
                           input uvm_reg_data_t value,
-                          input uvm_check_e    check = UVM_CHECK);
+                          input uvm_check_e    check = UVM_CHECK,
+                          input bit            predict = 0);
     uvm_status_e  status;
     string        msg_id = {csr_utils_pkg::msg_id, "::csr_poke"};
+    uvm_reg_data_t old_mirrored_val = csr.get_mirrored_value();
 
     csr.poke(.status(status), .value(value));
-    if (check == UVM_CHECK) begin
-      `DV_CHECK_EQ(status, UVM_IS_OK, "", error, msg_id)
+    if (check == UVM_CHECK && status != UVM_IS_OK) begin
+      string str;
+      uvm_hdl_path_concat paths[$];
+      csr.get_full_hdl_path(paths);
+      foreach (paths[0].slices[i]) str = $sformatf("%0s\n%0s", str, paths[0].slices[i].path);
+      `uvm_fatal(msg_id, $sformatf("poke failed for %0s, check below paths %0s",
+                                   csr.get_full_name(), str))
+    end
+    // poke always updates predict value, if predict == 0, revert back to old mirrored value
+    if (!predict) begin
+      void'(csr.predict(.value(old_mirrored_val), .kind(UVM_PREDICT_DIRECT)));
     end
   endtask
 
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv b/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
index 6bcb400..d1c1a3e 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
@@ -11,10 +11,6 @@
 
   // when use UVM_PREDICT_WRITE and the CSR access is WO, this function will return the default
   // val of the register, rather than the written value
-  // TODO, need to handle predict value when backdoor write happens WO reg
-  //   1. for read, design ties the read data to default value
-  //   2. when backdoor write updates internal reg, backdoor read can get the written value, but
-  //   frontdoor read always returns the default value.
   virtual function uvm_reg_data_t XpredictX(uvm_reg_data_t cur_val,
                                             uvm_reg_data_t wr_val,
                                             uvm_reg_map map);
diff --git a/hw/dv/sv/dv_lib/dv_base_vseq.sv b/hw/dv/sv/dv_lib/dv_base_vseq.sv
index 7cade3b..9bbf7b9 100644
--- a/hw/dv/sv/dv_lib/dv_base_vseq.sv
+++ b/hw/dv/sv/dv_lib/dv_base_vseq.sv
@@ -172,6 +172,7 @@
       m_csr_write_seq.models = cfg.ral_models;
       m_csr_write_seq.set_csr_excl_item(csr_excl);
       m_csr_write_seq.external_checker = cfg.en_scb;
+      m_csr_write_seq.en_rand_backdoor_write = 1;
       if (!enable_asserts_in_hw_reset_rand_wr) $assertoff;
       m_csr_write_seq.start(null);
 
diff --git a/hw/ip/alert_handler/data/alert_handler.hjson b/hw/ip/alert_handler/data/alert_handler.hjson
index 1f264af..8b15774 100644
--- a/hw/ip/alert_handler/data/alert_handler.hjson
+++ b/hw/ip/alert_handler/data/alert_handler.hjson
@@ -17,6 +17,7 @@
   clock_primary: "clk_i",
   bus_device: "tlul",
   regwidth: "32",
+  hier_path: "i_reg_wrap"
   param_list: [
     { name: "NAlerts",
       desc: "Number of peripheral inputs",
diff --git a/hw/ip/alert_handler/data/alert_handler.hjson.tpl b/hw/ip/alert_handler/data/alert_handler.hjson.tpl
index 09adb7c..50aacb6 100644
--- a/hw/ip/alert_handler/data/alert_handler.hjson.tpl
+++ b/hw/ip/alert_handler/data/alert_handler.hjson.tpl
@@ -20,6 +20,7 @@
   clock_primary: "clk_i",
   bus_device: "tlul",
   regwidth: "32",
+  hier_path: "i_reg_wrap"
 ##############################################################################
   param_list: [
     { name: "NAlerts",
diff --git a/hw/top_earlgrey/ip/alert_handler/data/autogen/alert_handler.hjson b/hw/top_earlgrey/ip/alert_handler/data/autogen/alert_handler.hjson
index ccb9597..4bf94f9 100644
--- a/hw/top_earlgrey/ip/alert_handler/data/autogen/alert_handler.hjson
+++ b/hw/top_earlgrey/ip/alert_handler/data/autogen/alert_handler.hjson
@@ -25,6 +25,7 @@
   clock_primary: "clk_i",
   bus_device: "tlul",
   regwidth: "32",
+  hier_path: "i_reg_wrap"
   param_list: [
     { name: "NAlerts",
       desc: "Number of peripheral inputs",
diff --git a/util/reggen/data.py b/util/reggen/data.py
index f933356..dd0fefe 100644
--- a/util/reggen/data.py
+++ b/util/reggen/data.py
@@ -211,6 +211,7 @@
     addr_width = 12
     base_addr = 0
     name = ""
+    hier_path = ""
     regs = []
     wins = []
     blocks = []
@@ -222,6 +223,7 @@
         self.addr_width = 12
         self.base_addr = 0
         self.name = ""
+        self.hier_path = ""
         self.regs = []
         self.wins = []
         self.blocks = []
diff --git a/util/reggen/gen_rtl.py b/util/reggen/gen_rtl.py
index c4a5991..a788c48 100644
--- a/util/reggen/gen_rtl.py
+++ b/util/reggen/gen_rtl.py
@@ -145,6 +145,8 @@
 
     block.params = obj["param_list"] if "param_list" in obj else []
 
+    block.hier_path = obj["hier_path"] if "hier_path" in obj else ""
+
     for r in obj["registers"]:
         # Check if any exception condition hit
         if 'reserved' in r:
diff --git a/util/reggen/uvm_reg.sv.tpl b/util/reggen/uvm_reg.sv.tpl
index d18a2a8..e2229ca 100644
--- a/util/reggen/uvm_reg.sv.tpl
+++ b/util/reggen/uvm_reg.sv.tpl
@@ -12,6 +12,9 @@
 % endfor
 <%
 regs_flat = block.get_regs_flat()
+hier_path = ""
+if (block.hier_path):
+  hier_path = block.hier_path + "."
 %>\
 
 // Block: ${block.name}
@@ -38,7 +41,8 @@
 % for r in regs_flat:
 <%
   reg_width = block.width
-  reg_name= r.name
+  reg_name = r.name
+  is_ext = 0
 %>\
   // Class: ${gen_dv.rcname(block, r)}
   class ${gen_dv.rcname(block, r)} extends dv_base_reg;
@@ -70,6 +74,15 @@
   else:
     field_volatile = 1
   field_tags = f.tags
+
+  if r.hwext or (f.hwaccess == HwAccess.NONE and f.swrdaccess == SwRdAccess.RD and
+                 f.swwraccess == SwWrAccess.NONE):
+    is_ext = 1
+
+  if len(r.fields) == 1:
+    reg_field_name = reg_name
+  else:
+    reg_field_name = reg_name + "_" + f.name
 %>\
       ${f.name} = dv_base_reg_field::type_id::create("${f.name}");
       ${f.name}.configure(
@@ -83,26 +96,28 @@
         .is_rand(1),
         .individually_accessible(1));
       ${f.name}.set_original_access("${field_access}");
-  % if r.hwext:
-      set_is_ext_reg(1);
-  % endif
-  % if len(r.fields) == 1:
-      add_hdl_path_slice("u_${reg_name}.q", ${f.lsb}, ${field_size});
+  % if f.hwaccess == HwAccess.NONE and f.swrdaccess == SwRdAccess.RD and f.swwraccess == SwWrAccess.NONE:
+      // constant reg
+      add_hdl_path_slice("${hier_path}u_reg.${reg_field_name}_qs", ${f.lsb}, ${field_size});
   % else:
-      add_hdl_path_slice("u_${reg_name}_${f.name}.q", ${f.lsb}, ${field_size});
+      add_hdl_path_slice("${hier_path}u_reg.u_${reg_field_name}.q${"s" if r.hwext else ""}", ${f.lsb}, ${field_size});
   % endif
-% if field_tags:
+  % if field_tags:
       // create field tags
-% for field_tag in field_tags:
+    % for field_tag in field_tags:
 <%
   tag = field_tag.split(":")
 %>\
-% if tag[0] == "excl":
+    % if tag[0] == "excl":
       csr_excl.add_excl(${f.name}.get_full_name(), ${tag[2]}, ${tag[1]});
-% endif
+      % endif
+    % endfor
+  % endif
 % endfor
+
+% if is_ext:
+      set_is_ext_reg(1);
 % endif
-% endfor
     endfunction : build
 
   endclass : ${gen_dv.rcname(block, r)}
@@ -178,12 +193,12 @@
       ${b.name} = ${gen_dv.bcname(b)}::type_id::create("${b.name}");
       ${b.name}.configure(.parent(this));
       ${b.name}.build(.base_addr(base_addr + ${gen_dv.sv_base_addr(b)}), .csr_excl(csr_excl));
-      ${b.name}.set_hdl_path_root("tb.dut.top_earlgrey.u_${b.name}.u_reg");
+      ${b.name}.set_hdl_path_root("tb.dut.top_earlgrey.u_${b.name}");
       default_map.add_submap(.child_map(${b.name}.default_map),
                              .offset(base_addr + ${gen_dv.sv_base_addr(b)}));
 % endfor
 % if regs_flat:
-      set_hdl_path_root("tb.dut.u_reg");
+      set_hdl_path_root("tb.dut");
 
       // create registers
 % endif