[dv] Update tl_intg for chip-level

1. address Sri's comments at #6852
2. Move cpu_stub point prior to the place  where design generates ecc
values, so that the ecc gen and ecc check are in the design data path
3. add knob `en_tl_intg_gen` to decide if TB need to generate ecc or
not. For block level, set to 1, for chip, set to 0

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/dv/sv/cip_lib/cip_base_env.sv b/hw/dv/sv/cip_lib/cip_base_env.sv
index bb5724f..93ff2c2 100644
--- a/hw/dv/sv/cip_lib/cip_base_env.sv
+++ b/hw/dv/sv/cip_lib/cip_base_env.sv
@@ -11,7 +11,7 @@
   `uvm_component_param_utils(cip_base_env #(CFG_T, VIRTUAL_SEQUENCER_T, SCOREBOARD_T, COV_T))
 
   tl_agent                                           m_tl_agents[string];
-  tl_reg_adapter#(cip_tl_seq_item)                   m_tl_reg_adapters[string];
+  tl_reg_adapter #(tl_seq_item)                      m_tl_reg_adapters[string];
   alert_esc_agent                                    m_alert_agent[string];
   push_pull_agent#(.DeviceDataWidth(EDN_DATA_WIDTH)) m_edn_pull_agent;
 
@@ -20,6 +20,10 @@
   virtual function void build_phase(uvm_phase phase);
     super.build_phase(phase);
 
+    // use cip_tl_seq_item to create tl_seq_item with correct integrity values and obtain integrity
+    // related functions
+    if (cfg.en_tl_intg_gen) tl_seq_item::type_id::set_type_override(cip_tl_seq_item::get_type());
+
     // Retrieve the virtual interfaces from uvm_config_db.
     if (!uvm_config_db#(intr_vif)::get(this, "", "intr_vif", cfg.intr_vif) &&
         cfg.num_interrupts > 0) begin
@@ -33,7 +37,7 @@
     // Create & configure the TL agent.
     foreach (cfg.m_tl_agent_cfgs[i]) begin
       m_tl_agents[i] = tl_agent::type_id::create({"m_tl_agent_", i}, this);
-      m_tl_reg_adapters[i] = tl_reg_adapter#(cip_tl_seq_item)::type_id::create(
+      m_tl_reg_adapters[i] = tl_reg_adapter#(tl_seq_item)::type_id::create(
                              {"m_tl_reg_adapter_", i});
       m_tl_reg_adapters[i].cfg = cfg.m_tl_agent_cfgs[i];
       uvm_config_db#(tl_agent_cfg)::set(this, $sformatf("m_tl_agent_%s*", i), "cfg",
diff --git a/hw/dv/sv/cip_lib/cip_base_env_cfg.sv b/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
index 5a41fbc..9908873 100644
--- a/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
+++ b/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
@@ -13,6 +13,9 @@
 
   // Override this alert name at `initialize` if it's not as below
   string              tl_intg_alert_name = "fatal_fault";
+  // Enables TL integrity generation & checking with *_user bits.
+  // Assume ALL TL agents have integrity check enabled or disabled altogether.
+  bit                 en_tl_intg_gen = 1;
 
   alert_esc_agent_cfg m_alert_agent_cfg[string];
   push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH)) m_edn_pull_agent_cfg;
diff --git a/hw/dv/sv/cip_lib/cip_base_pkg.sv b/hw/dv/sv/cip_lib/cip_base_pkg.sv
index c6a0a4e..7e7e165 100644
--- a/hw/dv/sv/cip_lib/cip_base_pkg.sv
+++ b/hw/dv/sv/cip_lib/cip_base_pkg.sv
@@ -25,6 +25,7 @@
   string msg_id = "cip_base_pkg";
   parameter uint EDN_BUS_WIDTH = 32;
   parameter uint EDN_DATA_WIDTH = EDN_BUS_WIDTH + 1; // 32 bits bus data, 1 bit fips
+  parameter uint MAX_TL_ECC_ERRORS = 3;
 
   typedef enum {
     err_update,
@@ -35,7 +36,7 @@
     TlIntgErrNone,
     TlIntgErrCmd,
     TlIntgErrData,
-    TlIntgErrBoth // have both payload and data intg errors
+    TlIntgErrBoth  // Inject errors in both command and data.
   } tl_intg_err_e;
 
   typedef class cip_tl_seq_item;
diff --git a/hw/dv/sv/cip_lib/cip_base_scoreboard.sv b/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
index 7bac7de..cc75e4e 100644
--- a/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
+++ b/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
@@ -255,7 +255,6 @@
     bit is_tl_unmapped_addr, is_tl_err, mem_access_err;
     bit csr_aligned_err, csr_size_err, tl_item_err;
     bit has_intg_err;
-    cip_tl_seq_item cip_tl_item;
 
     if (!is_tl_access_mapped_addr(item, ral_name)) begin
       is_tl_unmapped_addr = 1;
@@ -269,8 +268,8 @@
     csr_aligned_err = !is_tl_csr_write_addr_word_aligned(item, ral_name);
     csr_size_err    = !is_tl_csr_write_size_gte_csr_width(item, ral_name);
     tl_item_err     = item.get_exp_d_error();
-    `downcast(cip_tl_item, item)
-    has_intg_err = !cip_tl_item.is_a_user_ok(.throw_error(0));
+
+    if (cfg.en_tl_intg_gen) has_intg_err = !item.is_a_chan_intg_ok(.throw_error(0));
 
     if (!is_tl_err && (mem_access_err || csr_aligned_err || csr_size_err || tl_item_err ||
                        has_intg_err)) begin
diff --git a/hw/dv/sv/cip_lib/cip_base_test.sv b/hw/dv/sv/cip_lib/cip_base_test.sv
index 6599af2..2006952 100644
--- a/hw/dv/sv/cip_lib/cip_base_test.sv
+++ b/hw/dv/sv/cip_lib/cip_base_test.sv
@@ -8,12 +8,5 @@
 
   `uvm_component_new
 
-  virtual function void build_phase(uvm_phase phase);
-    super.build_phase(phase);
-
-    // use cip_tl_seq_item to create tl_seq_item with correct integrity values and obtain integrity
-    // related functions
-    tl_seq_item::type_id::set_type_override(cip_tl_seq_item::get_type());
-  endfunction
 endclass : cip_base_test
 
diff --git a/hw/dv/sv/cip_lib/doc/index.md b/hw/dv/sv/cip_lib/doc/index.md
index 41c6ced..6a272d7 100644
--- a/hw/dv/sv/cip_lib/doc/index.md
+++ b/hw/dv/sv/cip_lib/doc/index.md
@@ -196,6 +196,10 @@
   cases, including unmapped address error, protocol error, memory access error
   etc. All the items sent in this task will trigger d_error and won't change the
   CSR/memory value.
+* **task run_tl_intg_err_vseq**: This task will test TLUL integrity error. It contains
+  2 parallel threads. One thread calls `csr_rw` seq to do random CSR accesses. The
+  other issues a TLUL item with integrity error on `a_user` and then check the fatal
+  alert is triggered.
 * **task run_stress_all_with_rand_reset_vseq**: This task runs 3 parallel threads,
   which are ip_stress_all_vseq, run_tl_errors_vseq and reset sequence. After
   reset occurs, the other threads will be killed and then all the CSRs will be read
@@ -250,6 +254,13 @@
                       type ENV_T = cip_base_env) extends uvm_test;
 ```
 
+### cip_tl_seq_item
+This is extended class of tl_seq_item to generate correct integrity values in
+a_user and d_user. If DUT relies on the agent to generate integrity for TLUL, set
+`cfg.en_tl_intg_gen = 1`, cip_tl_seq_item will override tl_seq_item, and integrity
+values will be generated. If TLUL integrity is handled in the DUT, set
+`cfg.en_tl_intg_gen = 0`, then `a_user` and `d_user` will be fully randomized.
+
 ## Extending from CIP library classes
 Let's say we are verifying an actual comportable IP `uart` which has `uart_tx`
 and `uart_rx` interface. User then develops the `uart_agent` to be able to talk
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
index 8742ce8..83a3b64 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
@@ -243,6 +243,8 @@
 endtask
 
 virtual task run_tl_intg_err_vseq_sub(int num_times = 1, string ral_name);
+  `DV_CHECK_EQ(cfg.en_tl_intg_gen, 1)
+
   for (int trans = 1; trans <= num_times; trans++) begin
     `uvm_info(`gfn, $sformatf("Running run_tl_intg_err_vseq %0d/%0d", trans, num_times),
               UVM_LOW)
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_tl_seq_item.sv b/hw/dv/sv/cip_lib/seq_lib/cip_tl_seq_item.sv
index 0cdbf10..06993e3 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_tl_seq_item.sv
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_tl_seq_item.sv
@@ -5,22 +5,27 @@
 // extend tl_seq_item to return a|d_user data with ECC value
 class cip_tl_seq_item extends tl_seq_item;
 
-  `uvm_object_utils_begin(cip_tl_seq_item)
-  `uvm_object_utils_end
-
   `uvm_object_new
 
   tlul_pkg::tl_type_e tl_type = DataType;
   tl_intg_err_e       tl_intg_err_type = TlIntgErrNone;
   // the max errors that we can detect
-  int                 max_ecc_errors = 3;
+  int                 max_ecc_errors = MAX_TL_ECC_ERRORS;
+
+  `uvm_object_utils_begin(cip_tl_seq_item)
+    `uvm_field_enum(tlul_pkg::tl_type_e, tl_type,          UVM_DEFAULT)
+    `uvm_field_enum(tl_intg_err_e,       tl_intg_err_type, UVM_DEFAULT)
+    `uvm_field_int(max_ecc_errors,                         UVM_DEFAULT)
+  `uvm_object_utils_end
 
   function void post_randomize();
-    update_a_chan_intg_val(tl_intg_err_type);
+    a_user = compute_a_user();
+    inject_a_chan_intg_err();
   endfunction
 
-  // calculate ecc value for a_user
-  virtual function tl_a_user_t get_a_user_val();
+  // calculate ecc value for a_user and return a_user
+  // class member a_user isn't updated in this function
+  virtual function tl_a_user_t compute_a_user();
     tl_a_user_t user;
     tl_h2d_cmd_intg_t cmd_intg_payload;
     logic [H2DCmdFullWidth - 1 : 0] cmd_with_intg;
@@ -39,12 +44,12 @@
     user.rsvd = '0;
     user.tl_type = tl_type;
     user.cmd_intg = cmd_with_intg[H2DCmdFullWidth -1 -: H2DCmdIntgWidth];
-    user.data_intg = data_with_intg[DataFullWidth -1 -: DataIntgWidth];;
+    user.data_intg = data_with_intg[DataFullWidth -1 -: DataIntgWidth];
     return user;
-  endfunction // get_a_user_val
+  endfunction : compute_a_user
 
   // device facing version of the function above
-  virtual function tl_d_user_t get_d_user_val();
+  virtual function tl_d_user_t compute_d_user();
     tl_d_user_t user;
     tl_d2h_rsp_intg_t rsp_intg_payload;
     logic [D2HRspFullWidth - 1:0] rsp_with_intg;
@@ -62,132 +67,111 @@
     user.rsp_intg = rsp_with_intg[D2HRspFullWidth -1 -: D2HRspIntgWidth];
     user.data_intg = data_with_intg[DataFullWidth -1 -: DataIntgWidth];
     return user;
-  endfunction // get_d_user_val
+  endfunction : compute_d_user
 
   // update data and ecc value for a channel
-  virtual function void update_a_chan_intg_val(tl_intg_err_e tl_intg_err_type = TlIntgErrNone);
-    tl_a_user_t cur_user = get_a_user_val();
-    tl_a_user_t final_user;
-    cip_tl_seq_item cur_item;
-    bit [DataIntgWidth + $bits(tl_h2d_cmd_intg_t) - 1 : 0] cmd_and_intg_err_mask;
-    bit [DataIntgWidth + BUS_DW - 1 : 0]                   data_and_intg_err_mask;
+  virtual function void inject_a_chan_intg_err();
+    // define a struct type local a_user to access ECC or other field easily
+    tl_a_user_t l_a_user = tl_a_user_t'(a_user);
 
-    `downcast(cur_item, this.clone())
+    if (tl_intg_err_type == TlIntgErrNone) return;
 
-    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(cmd_and_intg_err_mask,
-        if (tl_intg_err_type inside {TlIntgErrCmd, TlIntgErrBoth}) {
-          $countones(cmd_and_intg_err_mask) inside {[1 : max_ecc_errors]};
-        } else {
-          cmd_and_intg_err_mask == '0;
-        })
-    {a_addr, a_opcode, a_mask, final_user.tl_type, final_user.cmd_intg} = {
-        cur_item.a_addr,
-        cur_item.a_opcode,
-        cur_item.a_mask,
-        cur_user.tl_type,
-        cur_user.cmd_intg} ^ cmd_and_intg_err_mask;
+    if (tl_intg_err_type inside {TlIntgErrCmd, TlIntgErrBoth}) begin
+      bit [DataIntgWidth + $bits(tl_h2d_cmd_intg_t) - 1 : 0] cmd_and_intg_err_mask;
+      // Pre-populate str with format specifiers for the updated values that will be set later.
+      string str = {"TL data or integrity bits have been flipped, see the changes as below:\n",
+                   $sformatf("\t a_addr: 0x%0x\n", a_addr), " -> 0x%0x",
+                   $sformatf("\t a_opcode: 0x%0x\n", a_opcode), " -> 0x%0x",
+                   $sformatf("\t a_mask: 0x%0x\n", a_mask), " -> 0x%0x",
+                   $sformatf("\t tl_type: 0x%0x\n", l_a_user.tl_type), " -> 0x%0x",
+                   $sformatf("\t cmd_intg: 0x%0x\n", l_a_user.cmd_intg), " -> 0x%0x"};
 
-    if (cmd_and_intg_err_mask != '0) begin
-      string str = "TL cmd or integrity bits have been flipped, see the changes as below:\n";
-      str = $sformatf("%s\t a_addr: 0x%0x -> 0x%0x\n", str, cur_item.a_addr, a_addr);
-      str = $sformatf("%s\t a_opcode: 0x%0x -> 0x%0x\n", str, cur_item.a_opcode, a_opcode);
-      str = $sformatf("%s\t a_mask: 0x%0x -> 0x%0x\n", str, cur_item.a_mask, a_mask);
-      str = $sformatf("%s\t tl_type: 0x%0x -> 0x%0x\n", str, cur_user.tl_type, final_user.tl_type);
-      str = $sformatf("%s\t cmd_intg: 0x%0x -> 0x%0x\n", str, cur_user.cmd_intg,
-                      final_user.cmd_intg);
+      // Flip cmd or intg ecc
+      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(cmd_and_intg_err_mask,
+          $countones(cmd_and_intg_err_mask) inside {[1 : max_ecc_errors]};)
+      {a_addr, a_opcode, a_mask, l_a_user.tl_type, l_a_user.cmd_intg} ^= cmd_and_intg_err_mask;
+
+      str = $sformatf(str, a_addr, a_opcode, a_mask, l_a_user.tl_type, l_a_user.cmd_intg);
       `uvm_info(`gfn, str, UVM_LOW)
     end
 
-    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(data_and_intg_err_mask,
-        if (tl_intg_err_type inside {TlIntgErrData, TlIntgErrBoth}) {
-          $countones(data_and_intg_err_mask) inside {[1 : max_ecc_errors]};
-        } else {
-          data_and_intg_err_mask == '0;
-        })
-    {a_data, final_user.data_intg} = {cur_item.a_data, final_user.data_intg} ^
-                                     data_and_intg_err_mask;
+    if (tl_intg_err_type inside {TlIntgErrData, TlIntgErrBoth}) begin
+      bit [DataIntgWidth + BUS_DW - 1 : 0] data_and_intg_err_mask;
+      // Pre-populate str with format specifiers for the updated values that will be set later.
+      string str = {"TL data or integrity bits have been flipped, see the changes as below:\n",
+                   $sformatf("\t a_data: 0x%0x\n", a_data), " -> 0x%0x"};
 
-    if (cmd_and_intg_err_mask != '0) begin
-      string str = "TL data or integrity bits have been flipped, see the changes as below: \n";
-      str = $sformatf("%s\t a_data: 0x%0x -> 0x%0x\n", str, cur_item.a_data, a_data);
-      str = $sformatf("%s\t data_intg: 0x%0x -> 0x%0x\n", str, cur_user.data_intg,
-                      final_user.data_intg);
+      // Flip data or intg ecc
+      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(data_and_intg_err_mask,
+          $countones(data_and_intg_err_mask) inside {[1 : max_ecc_errors]};)
+      {a_data, l_a_user.data_intg} ^= data_and_intg_err_mask;
+
+      str = $sformatf(str, a_data);
       `uvm_info(`gfn, str, UVM_LOW)
     end
 
-    a_user = final_user;
-  endfunction // update_a_chan_intg_val
+    a_user = l_a_user;
+  endfunction : inject_a_chan_intg_err
 
   // update data and ecc value for d channel
-  virtual function void update_d_chan_intg_val(tl_intg_err_e tl_intg_err_type = TlIntgErrNone);
-    tl_d_user_t cur_user = get_d_user_val();
-    tl_d_user_t final_user;
-    cip_tl_seq_item cur_item;
-    bit [DataIntgWidth + $bits(tl_d2h_rsp_intg_t) - 1 : 0] rsp_and_intg_err_mask;
-    bit [DataIntgWidth + BUS_DW - 1 : 0]                   data_and_intg_err_mask;
+  virtual function void inject_d_chan_intg_err();
+    // define a struct type local d_user to access ECC or other field easily
+    tl_d_user_t l_d_user = tl_d_user_t'(d_user);
 
-    `downcast(cur_item, this.clone())
+    if (tl_intg_err_type == TlIntgErrNone) return;
 
+    if (tl_intg_err_type inside {TlIntgErrCmd, TlIntgErrBoth}) begin
+      bit [DataIntgWidth + $bits(tl_d2h_rsp_intg_t) - 1 : 0] rsp_and_intg_err_mask;
+      // Pre-populate str with format specifiers for the updated values that will be set later.
+      string str = {"TL data or integrity bits have been flipped, see the changes as below:\n",
+                   $sformatf("\t d_opcode: 0x%0x\n", d_opcode), " -> 0x%0x",
+                   $sformatf("\t d_size: 0x%0x\n", d_size), " -> 0x%0x",
+                   $sformatf("\t d_error: 0x%0x\n", d_error), " -> 0x%0x",
+                   $sformatf("\t rsp_intg: 0x%0x\n", l_d_user.rsp_intg), " -> 0x%0x"};
+
+    // Flip cmd or intg ecc
     `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(rsp_and_intg_err_mask,
-        if (tl_intg_err_type inside {TlIntgErrCmd, TlIntgErrBoth}) {
-          $countones(rsp_and_intg_err_mask) inside {[1 : max_ecc_errors]};
-        } else {
-          rsp_and_intg_err_mask == '0;
-        })
-    {d_opcode, d_size, d_error, final_user.rsp_intg} = {
-        cur_item.d_opcode,
-        cur_item.d_size,
-        cur_item.d_error,
-        cur_user.rsp_intg} ^ rsp_and_intg_err_mask;
+        $countones(rsp_and_intg_err_mask) inside {[1 : max_ecc_errors]};)
+    {d_opcode, d_size, d_error, l_d_user.rsp_intg} ^= rsp_and_intg_err_mask;
 
-    if (rsp_and_intg_err_mask != '0) begin
-      string str = "TL cmd or integrity bits have been flipped, see the changes as below:\n";
-      str = $sformatf("%s\t d_opcode: 0x%0x -> 0x%0x\n", str, cur_item.d_opcode, d_opcode);
-      str = $sformatf("%s\t d_size: 0x%0x -> 0x%0x\n", str, cur_item.d_size, d_size);
-      str = $sformatf("%s\t d_error: 0x%0x -> 0x%0x\n", str, cur_item.d_error, d_error);
-      str = $sformatf("%s\t rsp_intg: 0x%0x -> 0x%0x\n", str, cur_user.rsp_intg,
-                      final_user.rsp_intg);
+      str = $sformatf(str, d_opcode, d_size, d_error, l_d_user.rsp_intg);
       `uvm_info(`gfn, str, UVM_LOW)
     end
 
-    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(data_and_intg_err_mask,
-        if (tl_intg_err_type inside {TlIntgErrData, TlIntgErrBoth}) {
-          $countones(data_and_intg_err_mask) inside {[1 : max_ecc_errors]};
-        } else {
-          data_and_intg_err_mask == '0;
-        })
-    {a_data, final_user.data_intg} = {cur_item.a_data, final_user.data_intg} ^
-                                     data_and_intg_err_mask;
+    if (tl_intg_err_type inside {TlIntgErrData, TlIntgErrBoth}) begin
+      bit [DataIntgWidth + BUS_DW - 1 : 0] data_and_intg_err_mask;
+      // Pre-populate str with format specifiers for the updated values that will be set later.
+      string str = {"TL data or integrity bits have been flipped, see the changes as below:\n",
+                   $sformatf("\t a_data: 0x%0x\n", d_data), " -> 0x%0x"};
 
-    if (rsp_and_intg_err_mask != '0) begin
-      string str = "TL data or integrity bits have been flipped, see the changes as below: \n";
-      str = $sformatf("%s\t a_data: 0x%0x -> 0x%0x\n", str, cur_item.a_data, a_data);
-      str = $sformatf("%s\t data_intg: 0x%0x -> 0x%0x\n", str, cur_user.data_intg,
-                      final_user.data_intg);
+      // Flip data or intg ecc
+      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(data_and_intg_err_mask,
+          $countones(data_and_intg_err_mask) inside {[1 : max_ecc_errors]};)
+      {d_data, l_d_user.data_intg} ^= data_and_intg_err_mask;
+
+      str = $sformatf(str, d_data);
       `uvm_info(`gfn, str, UVM_LOW)
     end
 
-    d_user = final_user;
-  endfunction // update_d_chan_intg_val
+    d_user = l_d_user;
+  endfunction : inject_d_chan_intg_err
 
-  virtual function bit is_a_user_ok(bit throw_error = 1'b1);
-    tl_a_user_t exp_user = get_a_user_val();
-    tl_a_user_t act_user = tl_a_user_t'(a_user);
+  virtual function bit is_a_chan_intg_ok(bit throw_error = 1'b1);
+    tl_a_user_t exp_a_user = compute_a_user();
+    tl_a_user_t act_a_user = tl_a_user_t'(a_user);
 
     // TODO, #6887, dat_intg isn't implemented in design
-    // is_a_user_ok = (act_user.cmd_intg == exp_user.cmd_intg) &&
-    //                (act_user.data_intg == exp_user.data_intg);
-    is_a_user_ok = (act_user.cmd_intg == exp_user.cmd_intg);
+    // is_a_chan_intg_ok = act_a_user == exp_a_user;
+    is_a_chan_intg_ok = (act_a_user.cmd_intg == exp_a_user.cmd_intg);
 
-    if (!is_a_user_ok) begin
-      `uvm_info(`gfn, $sformatf(
-                "cmd_intg act (0x%0x) != exp (0x%0x), data_intg act (0x%0x) != exp (0x%0x)",
-                act_user.cmd_intg, exp_user.cmd_intg, act_user.data_intg, exp_user.data_intg),
-                UVM_LOW)
+    if (!is_a_chan_intg_ok) begin
+      string str = $sformatf("cmd_intg act (%p) != exp (%p)", act_a_user, exp_a_user);
+      if (throw_error) begin
+        `uvm_error(`gfn, str)
+      end else begin
+        `uvm_info(`gfn, str, UVM_MEDIUM)
+      end
     end
-    if (!is_a_user_ok && throw_error) begin
-      `uvm_error(`gfn, "a_user integrity check fails")
-    end
-  endfunction // is_a_user_ok
+  endfunction : is_a_chan_intg_ok
 
 endclass
diff --git a/hw/dv/sv/tl_agent/tl_seq_item.sv b/hw/dv/sv/tl_agent/tl_seq_item.sv
index 9eac0aa..9881bd3 100644
--- a/hw/dv/sv/tl_agent/tl_seq_item.sv
+++ b/hw/dv/sv/tl_agent/tl_seq_item.sv
@@ -309,6 +309,23 @@
       `uvm_error(`gfn, $sformatf("a_source: 0x%0h & d_source: 0x%0h mismatch", a_source, d_source))
   endfunction
 
+  // Compute and check the integrity of the a_channel payload.
+  //
+  // The TL agent is generic and adheres to the TLUL spec, which does not define
+  // how the integrity of the payload on each channel is computed / checked. That
+  // is up to the chip implementation. Typically, parity / ECC scheme is used, with
+  // the redundant bits transmitted through *_user.
+  //
+  // Returns 1 if the integrity of a_channel is maintained, 0 otherwise. This base
+  // class implementation vacuously returns 1.
+  virtual function bit is_a_chan_intg_ok(bit throw_error = 1'b1);
+    return 1;
+  endfunction
+
+  // d_channel version of the function above
+  virtual function bit is_d_chan_intg_ok(bit throw_error = 1'b1);
+    return 1;
+  endfunction
 endclass
 
 `undef chk_prot_a_opcode
diff --git a/hw/top_earlgrey/dv/tb/chip_hier_macros.svh b/hw/top_earlgrey/dv/tb/chip_hier_macros.svh
index c82d2b4..4c14773 100644
--- a/hw/top_earlgrey/dv/tb/chip_hier_macros.svh
+++ b/hw/top_earlgrey/dv/tb/chip_hier_macros.svh
@@ -8,6 +8,7 @@
 `define ALERT_HANDLER_HIER    `CHIP_HIER.u_alert_handler
 `define CLKMGR_HIER           `CHIP_HIER.u_clkmgr_aon
 `define CPU_HIER              `CHIP_HIER.u_rv_core_ibex
+`define CPU_TL_ADAPT_D_HIER   `CPU_HIER.tl_adapter_host_d_ibex
 `define EFLASH_HIER           `CHIP_HIER.u_flash_eflash.u_flash
 `define GPIO_HIER             `CHIP_HIER.u_gpio
 `define OTP_CTRL_HIER         `CHIP_HIER.u_otp_ctrl
diff --git a/hw/top_earlgrey/dv/tb/tb.sv b/hw/top_earlgrey/dv/tb/tb.sv
index 3122139..f004968 100644
--- a/hw/top_earlgrey/dv/tb/tb.sv
+++ b/hw/top_earlgrey/dv/tb/tb.sv
@@ -360,12 +360,18 @@
     void'($value$plusargs("stub_cpu=%0b", stub_cpu));
     if (stub_cpu) begin
       force `CPU_HIER.clk_i = 1'b0;
-      force `CPU_HIER.tl_d_o = cpu_d_tl_if.h2d;
+      // tl type is used to calculate ECC and we use DataType for cpu data interface
+      force cpu_d_tl_if.h2d.a_user.tl_type = tlul_pkg::DataType;
+      force `CPU_TL_ADAPT_D_HIER.tl_out = cpu_d_tl_if.h2d;
+      force cpu_d_tl_if.d2h = `CPU_TL_ADAPT_D_HIER.tl_i;
     end else begin
+      // when en_sim_sram == 1, need to make sure the access to sim_sram doesn't appear on
+      // cpu_d_tl_if, otherwise, we may have unmapped access as scb doesn't regnize addresses of
+      // sim_sram. `CPU_HIER.tl_d_* is the right place to avoid seeing sim_sram accesses
       force cpu_d_tl_if.h2d = `CPU_HIER.tl_d_o;
+      force cpu_d_tl_if.d2h = `CPU_HIER.tl_d_i;
     end
   end
-  assign cpu_d_tl_if.d2h = `CPU_HIER.tl_d_i;
 
   // otp test_access memory is only accessible after otp_init and lc_dft_en = 1.
   // TODO: remove them once the otp/pwr otp/lc connections are completed.
diff --git a/hw/top_earlgrey/dv/tests/chip_base_test.sv b/hw/top_earlgrey/dv/tests/chip_base_test.sv
index f9fa64a..1da340b 100644
--- a/hw/top_earlgrey/dv/tests/chip_base_test.sv
+++ b/hw/top_earlgrey/dv/tests/chip_base_test.sv
@@ -20,6 +20,9 @@
     string sw_images_plusarg;
     super.build_phase(phase);
 
+    // TL integrity gen is in the design data path, no need to generate it in the agent
+    cfg.en_tl_intg_gen = 0;
+
     // Knob to en/dis stubbing cpu (disabled by default).
     void'($value$plusargs("stub_cpu=%0b", cfg.stub_cpu));
     // Set tl_agent's is_active bit based on the retrieved stub_cpu value.