[dv/sram] SRAM smoke test, bring to V1 status

This PR implements the smoke test for `sram_ctrl` as laid out in the
testplan.

Note that this PR requires #4794 to be merged first, CI will fail until
then.

This PR also incorporates the changes made by @weicaiyang in #5122 to
fix the RAL HDL hierarchy, and adds the SRAM configs to the nightly
regression.

Finally, this PR also updates the `sram_ctrl` to V1 status as all items
have been completed.

Signed-off-by: Udi Jonnalagadda <udij@google.com>
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl.hjson b/hw/ip/sram_ctrl/data/sram_ctrl.hjson
index f5ebb40..4127c2c 100644
--- a/hw/ip/sram_ctrl/data/sram_ctrl.hjson
+++ b/hw/ip/sram_ctrl/data/sram_ctrl.hjson
@@ -199,6 +199,8 @@
       hwqe:     "true",
       hwext:    "true",
       regwen:   "CTRL_REGWEN"
+      tags: [// avoid writing to CTRL, as this will cause STATUS to be modified
+             "excl:CsrNonInitTests:CsrExclWrite"]
       fields: [
         { bits: "0",
           name: "RENEW_SCR_KEY",
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl.prj.hjson b/hw/ip/sram_ctrl/data/sram_ctrl.prj.hjson
index 1087e5b..70c3625 100644
--- a/hw/ip/sram_ctrl/data/sram_ctrl.prj.hjson
+++ b/hw/ip/sram_ctrl/data/sram_ctrl.prj.hjson
@@ -5,12 +5,12 @@
 {
     name:               "sram_ctrl",
     design_spec:        "hw/ip/sram_ctrl/doc",
-    dv_doc:            "",
+    dv_doc:             "hw/ip/sram_ctrl/doc/dv",
     hw_checklist:       "hw/ip/sram_ctrl/doc/checklist",
-    sw_checklist:       ""
+    sw_checklist:       "",
     version:            "0.1",
     life_stage:         "L1",
     design_stage:       "D2",
-    verification_stage: "V0",
+    verification_stage: "V1",
     notes:              "",
 }
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson b/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson
index 33f029f..2f7df21 100644
--- a/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson
+++ b/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson
@@ -39,14 +39,14 @@
                 - Perform a number of random memory accesses to the SRAM
               - Verify that all memory access succeed even if the scrambling key changes at arbitrary
                 intervals
-            ''
+            '''
       milestone: V2
       tests: ["{name}_multiple_keys"]
     }
     {
       name: stress_pipeline
       desc: '''
-            This test is the same as the multiple_keys_test, but we now do a series of back-to-back
+            This test is the same as the multiple_keys_test but we now do a series of back-to-back
             memory accesses at each random address in order to create read/write conflicts and
             stress the encryption pipeline.
             '''
diff --git a/hw/ip/sram_ctrl/doc/checklist.md b/hw/ip/sram_ctrl/doc/checklist.md
index d8fcf51..4359054 100644
--- a/hw/ip/sram_ctrl/doc/checklist.md
+++ b/hw/ip/sram_ctrl/doc/checklist.md
@@ -110,28 +110,28 @@
 
  Type         | Item                                  | Resolution  | Note/Collaterals
 --------------|---------------------------------------|-------------|------------------
-Documentation | [DV_DOC_DRAFT_COMPLETED][]            | Not Started |
-Documentation | [DV_PLAN_COMPLETED][]                 | Not Started |
-Testbench     | [TB_TOP_CREATED][]                    | Not Started |
-Testbench     | [PRELIMINARY_ASSERTION_CHECKS_ADDED][]| Not Started |
-Testbench     | [SIM_TB_ENV_CREATED][]                | Not Started |
-Testbench     | [SIM_RAL_MODEL_GEN_AUTOMATED][]       | Not Started |
-Testbench     | [CSR_CHECK_GEN_AUTOMATED][]           | Not Started |
-Testbench     | [TB_GEN_AUTOMATED][]                  | Not Started |
-Tests         | [SIM_SMOKE_TEST_PASSING][]            | Not Started |
-Tests         | [SIM_CSR_MEM_TEST_SUITE_PASSING][]    | Not Started |
-Tests         | [FPV_MAIN_ASSERTIONS_PROVEN][]        | Not Started |
-Tool Setup    | [SIM_ALT_TOOL_SETUP][]                | Not Started |
-Regression    | [SIM_SMOKE_REGRESSION_SETUP][]        | Not Started |
-Regression    | [SIM_NIGHTLY_REGRESSION_SETUP][]      | Not Started |
-Regression    | [FPV_REGRESSION_SETUP][]              | Not Started |
-Coverage      | [SIM_COVERAGE_MODEL_ADDED][]          | Not Started |
-Code Quality  | [TB_LINT_SETUP][]                     | Not Started |
-Integration   | [PRE_VERIFIED_SUB_MODULES_V1][]       | Not Started |
-Review        | [DESIGN_SPEC_REVIEWED][]              | Not Started |
-Review        | [DV_PLAN_REVIEWED][]                  | Not Started |
-Review        | [STD_TEST_CATEGORIES_PLANNED][]       | Not Started | Exception (?)
-Review        | [V2_CHECKLIST_SCOPED][]               | Not Started |
+Documentation | [DV_DOC_DRAFT_COMPLETED][]            | Done        |
+Documentation | [DV_PLAN_COMPLETED][]                 | Done        |
+Testbench     | [TB_TOP_CREATED][]                    | Done        |
+Testbench     | [PRELIMINARY_ASSERTION_CHECKS_ADDED][]| Done        |
+Testbench     | [SIM_TB_ENV_CREATED][]                | Done        |
+Testbench     | [SIM_RAL_MODEL_GEN_AUTOMATED][]       | Done        |
+Testbench     | [CSR_CHECK_GEN_AUTOMATED][]           | Done        |
+Testbench     | [TB_GEN_AUTOMATED][]                  | N/A         |
+Tests         | [SIM_SMOKE_TEST_PASSING][]            | Done        |
+Tests         | [SIM_CSR_MEM_TEST_SUITE_PASSING][]    | Done        |
+Tests         | [FPV_MAIN_ASSERTIONS_PROVEN][]        | N/A         |
+Tool Setup    | [SIM_ALT_TOOL_SETUP][]                | Done        |
+Regression    | [SIM_SMOKE_REGRESSION_SETUP][]        | Done        |
+Regression    | [SIM_NIGHTLY_REGRESSION_SETUP][]      | Done        |
+Regression    | [FPV_REGRESSION_SETUP][]              | Done        |
+Coverage      | [SIM_COVERAGE_MODEL_ADDED][]          | Done        |
+Code Quality  | [TB_LINT_SETUP][]                     | Done        |
+Integration   | [PRE_VERIFIED_SUB_MODULES_V1][]       | Done        |
+Review        | [DESIGN_SPEC_REVIEWED][]              | Done        |
+Review        | [DV_PLAN_REVIEWED][]                  | Done        |
+Review        | [STD_TEST_CATEGORIES_PLANNED][]       | Done        | Exception (Security/Power/Debug)
+Review        | [V2_CHECKLIST_SCOPED][]               | Done        |
 
 [DV_DOC_DRAFT_COMPLETED]:             {{<relref "/doc/project/checklist.md#dv_doc_draft_completed" >}}
 [DV_PLAN_COMPLETED]:                  {{<relref "/doc/project/checklist.md#dv_plan_completed" >}}
diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv
index 022e0e4..e086082 100644
--- a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv
@@ -9,17 +9,24 @@
     .VIRTUAL_SEQUENCER_T (sram_ctrl_virtual_sequencer)
   );
   `uvm_object_utils(sram_ctrl_base_vseq)
+  `uvm_object_new
 
   // various knobs to enable certain routines
   bit do_sram_ctrl_init = 1'b1;
 
-  `uvm_object_new
-
   virtual task dut_init(string reset_kind = "HARD");
     super.dut_init();
     if (do_sram_ctrl_init) sram_ctrl_init();
   endtask
 
+  virtual task apply_reset(string kind = "HARD");
+    super.apply_reset();
+    cfg.lc_vif.init();
+    if (kind == "HARD") begin
+      cfg.otp_clk_rst_vif.apply_reset();
+    end
+  endtask
+
   virtual task dut_shutdown();
     // check for pending sram_ctrl operations and wait for them to complete
     // TODO
@@ -27,7 +34,58 @@
 
   // setup basic sram_ctrl features
   virtual task sram_ctrl_init();
-    `uvm_error(`gfn, "FIXME")
+    cfg.mem_bkdr_vif.clear_mem();
+  endtask
+
+  // Request a new scrambling key from the OTP interface.
+  //
+  // Will trigger a request to the KDI push_pull agent.
+  virtual task req_scr_key();
+    csr_wr(.csr(ral.ctrl), .value(1'b1));
+  endtask
+
+  // Task to perform a single SRAM read at the specified location
+  virtual task do_single_read(bit [TL_AW-1:0] addr,
+                              bit             blocking = $urandom_range(0, 1));
+    logic [TL_DW-1:0] rdata;
+    tl_access(.addr(addr),
+              .data(rdata),
+              .mask(get_rand_contiguous_mask()),
+              .write(1'b0),
+              .blocking(blocking),
+              .tl_sequencer_h(p_sequencer.sram_tl_sequencer_h));
+  endtask
+
+  // Task to perform a single SRAM write at the specified location
+  virtual task do_single_write(bit [TL_AW-1:0] addr,
+                               bit [TL_DW-1:0] data,
+                               bit             blocking = $urandom_range(0, 1));
+    tl_access(.addr(addr),
+              .data(data),
+              .mask(get_rand_contiguous_mask()),
+              .write(1'b1),
+              .blocking(blocking),
+              .tl_sequencer_h(p_sequencer.sram_tl_sequencer_h));
+  endtask
+
+  // Task to perform `num_ops` fully randomized memory transactions.
+  virtual task do_rand_ops(int num_ops,
+                           bit blocking = $urandom_range(0, 1));
+    bit [TL_DW-1:0] data;
+    bit [TL_AW-1:0] addr;
+    bit we;
+    repeat (num_ops) begin
+      `DV_CHECK_STD_RANDOMIZE_FATAL(data)
+      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(we, we inside {0, 1};)
+      `DV_CHECK_STD_RANDOMIZE_FATAL(addr)
+
+      tl_access(.addr(addr),
+                .data(data),
+                .mask(get_rand_contiguous_mask()),
+                .write(we),
+                .blocking(blocking),
+                .tl_sequencer_h(p_sequencer.sram_tl_sequencer_h));
+    end
   endtask
 
 endclass : sram_ctrl_base_vseq
diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_smoke_vseq.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_smoke_vseq.sv
index 54a3667..0b60326 100644
--- a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_smoke_vseq.sv
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_smoke_vseq.sv
@@ -8,8 +8,66 @@
 
   `uvm_object_new
 
+  // Indicates the number of memory accesses to be performed
+  // before requesting a new scrambling key
+  rand int num_ops;
+
+  // Indicates the number of memory accesses to be performed
+  // when SRAM comes out of reset
+  rand int num_ops_after_reset;
+
+  // An SRAM "transaction" is a full round of:
+  //  - Provisioning a new scrambling key from OTP
+  //  - Executing a random number of memory accesses to SRAM
+  constraint num_trans_c {
+    num_trans == 1;
+  }
+
+  // TODO: 10_000 iterations takes roughly 150s CPU time during simulation.
+  //       If this is too much, modify the constraint.
+  constraint num_ops_c {
+    num_ops dist {
+      [1 : 999]     :/ 1,
+      [1000 : 4999] :/ 3,
+      [5000 : 9999] :/ 5,
+      10_000        :/ 1
+    };
+  }
+
+  // This can be much smaller than `num_ops`, as we only perform some memory accesses
+  // after reset to make sure that things are working normally.
+  constraint num_ops_after_reset_c {
+    num_ops_after_reset inside {[20 : 50]};
+  }
+
   task body();
-    `uvm_error(`gfn, "FIXME")
+
+    // do some memory transactions right after reset (zeroed key and nonce)
+    `uvm_info(`gfn,
+              $sformatf("Performing %0d random memory accesses after reset!", num_ops_after_reset),
+              UVM_LOW)
+    do_rand_ops(num_ops_after_reset, 1);
+
+    `uvm_info(`gfn, $sformatf("Starting %0d SRAM transactions", num_trans), UVM_LOW)
+    for (int i = 0; i < num_trans; i++) begin
+      `uvm_info(`gfn, $sformatf("iteration: %0d", i), UVM_LOW)
+
+      `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_ops)
+
+      // Request a new scrambling key
+      req_scr_key();
+
+      // wait for a valid KDI transaction to be completed
+      //
+      // STATUS.scr_key_seed_valid return value will be checked by scoreboard
+      csr_spinwait(.ptr(ral.status.scr_key_valid), .exp_data(1'b1));
+
+      // Do some random memory accesses
+      `uvm_info(`gfn,
+                $sformatf("Performing %0d random memory accesses!", num_ops),
+                UVM_LOW)
+      do_rand_ops(num_ops);
+    end
   endtask : body
 
 endclass : sram_ctrl_smoke_vseq
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv
index fab5b7b..f84ece9 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv
@@ -25,11 +25,10 @@
     if (!uvm_config_db#(virtual clk_rst_if)::get(this, "", "otp_clk_rst_vif", cfg.otp_clk_rst_vif)) begin
       `uvm_fatal(`gfn, "failed to get otp_clk_rst_if from uvm_config_db")
     end
-    // TODO: eventually set the OTP clock to a different frequency
-    cfg.otp_clk_rst_vif.set_freq_mhz(cfg.clk_freq_mhz);
+    cfg.otp_clk_rst_vif.set_freq_mhz(cfg.otp_freq_mhz);
 
     // Get the LC interface
-    if (!uvm_config_db#(lc_vif)::get(this, "", "lc_vif", cfg.lc_vif)) begin
+    if (!uvm_config_db#(virtual sram_ctrl_lc_if)::get(this, "", "lc_vif", cfg.lc_vif)) begin
       `uvm_fatal(`gfn, "failed to get lc_vif from uvm_config_db")
     end
 
@@ -54,6 +53,17 @@
 
   function void connect_phase(uvm_phase phase);
     super.connect_phase(phase);
+
+    virtual_sequencer.sram_tl_sequencer_h = m_sram_tl_agent.sequencer;
+
+    if (cfg.en_scb) begin
+      // connect SRAM TLUL ports
+      m_sram_tl_agent.monitor.a_chan_port.connect(scoreboard.sram_tl_a_chan_fifo.analysis_export);
+      m_sram_tl_agent.monitor.d_chan_port.connect(scoreboard.sram_tl_d_chan_fifo.analysis_export);
+
+      // connect KDI port
+      m_kdi_agent.monitor.analysis_port.connect(scoreboard.kdi_fifo.analysis_export);
+    end
   endfunction
 
 endclass
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv
index c50fe16..8434aa7 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv
@@ -15,18 +15,38 @@
 
   // ext interfaces
   virtual clk_rst_if otp_clk_rst_vif;
-  lc_vif lc_vif;
+  virtual sram_ctrl_lc_if lc_vif;
   mem_bkdr_vif mem_bkdr_vif;
 
+  // otp clk freq
+  rand dv_utils_pkg::clk_freq_mhz_e otp_freq_mhz;
+
   virtual function void initialize(bit [31:0] csr_base_addr = '1);
     list_of_alerts = sram_ctrl_env_pkg::LIST_OF_ALERTS;
     super.initialize(csr_base_addr);
 
-    // Build KDI cfg object
+    ral.set_hdl_path_root("tb.dut.u_sram_ctrl", "BkdrRegPathRtl");
+    ral.set_hdl_path_root("tb.dut.u_sram_ctrl", "BkdrRegPathRtlCommitted");
+    ral.set_hdl_path_root("tb.dut.u_sram_ctrl", "BkdrRegPathRtlShadow");
+
+    // Build KDI cfg object and configure
     m_kdi_cfg = push_pull_agent_cfg#(.DeviceDataWidth(KDI_DATA_SIZE))::type_id::create("m_kdi_cfg");
+    m_kdi_cfg.agent_type = push_pull_agent_pkg::PullAgent;
+    m_kdi_cfg.if_mode = dv_utils_pkg::Device;
+
+    // CDC synchronization between OTP and SRAM clock domains requires that the scrambling seed data
+    // should be held for at least a few cycles before it can be safely latched by the SRAM domain.
+    // Easy way to do this is just to force the push_pull_agent to hold the data until the next key
+    // request is sent out.
+    m_kdi_cfg.hold_d_data_until_next_req = 1'b1;
+
+    // KDI interface will never need zero delay mode.
+    // As per SRAM spec, KDI process will generally take around 800 CPU cyclesj
+    m_kdi_cfg.zero_delays.rand_mode(0);
 
     // Build SRAM TL cfg object
     m_sram_cfg = tl_agent_cfg::type_id::create("m_sram_cfg");
+    m_sram_cfg.if_mode = dv_utils_pkg::Host;
   endfunction
 
 endclass
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
index bc8e78c..3f89812 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
@@ -9,6 +9,7 @@
  */
 
 class sram_ctrl_env_cov extends cip_base_env_cov #(.CFG_T(sram_ctrl_env_cfg));
+
   `uvm_component_utils(sram_ctrl_env_cov)
 
   // the base class provides the following handles for use:
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_pkg.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_pkg.sv
index 26d1c01..ef837d8 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_pkg.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_pkg.sv
@@ -31,8 +31,7 @@
   parameter int KDI_DATA_SIZE = 1 + otp_ctrl_pkg::SramKeyWidth + otp_ctrl_pkg::SramNonceWidth;
 
   // types
-  typedef virtual mem_bkdr_if #(.MEM_ADDR_WIDTH(`SRAM_ADDR_WIDTH),
-                                .MEM_BYTES_PER_WORD(`SRAM_DATA_WIDTH >> 3)) mem_bkdr_vif;
+  typedef virtual mem_bkdr_if #(.MEM_PARITY(1)) mem_bkdr_vif;
   typedef virtual sram_ctrl_lc_if lc_vif;
 
   // package sources
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_lc_if.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_lc_if.sv
index 1d3d584..d488e12 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_lc_if.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_lc_if.sv
@@ -4,4 +4,14 @@
 
 interface sram_ctrl_lc_if;
   lc_ctrl_pkg::lc_tx_t lc_esc_en;
+
+  // LC escalation signal must be stable before reset ends
+  task automatic init();
+    lc_esc_en = lc_ctrl_pkg::Off;
+  endtask
+
+  task automatic drive_lc_esc_en(lc_ctrl_pkg::lc_tx_t esc_en);
+    lc_esc_en = esc_en;
+  endtask
+
 endinterface
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
index f70e083..b6f012b 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
@@ -8,17 +8,58 @@
     .COV_T(sram_ctrl_env_cov)
   );
   `uvm_component_utils(sram_ctrl_scoreboard)
+  `uvm_component_new
 
   // local variables
 
-  // TLM agent fifos
+  typedef struct {
+    // 1 for writes, 0 for reads
+    bit we;
+
+    // TLUL address
+    bit [TL_AW-1:0] addr;
+
+    // Contains either the requested write data
+    // or the read response data
+    bit [TL_DW-1:0] data;
+
+    // only writes are masked, all reads are full-word
+    bit [TL_DBW-1:0] mask;
+
+    // Tag the memory transaction with the appropriate key and nonce,
+    // so that we can keep track even if a new key is requested
+    otp_ctrl_pkg::sram_key_t key;
+    otp_ctrl_pkg::sram_nonce_t nonce;
+
+  } sram_trans_t;
+
+  // TLM agent fifos for the tl_agent connected to the SRAM memory itself
+  uvm_tlm_analysis_fifo #(tl_seq_item) sram_tl_a_chan_fifo;
+  uvm_tlm_analysis_fifo #(tl_seq_item) sram_tl_d_chan_fifo;
+
+  uvm_tlm_analysis_fifo #(push_pull_item#(.DeviceDataWidth(KDI_DATA_SIZE))) kdi_fifo;
 
   // local queues to hold incoming packets pending comparison
 
-  `uvm_component_new
+  // mailbox to send a sram_trans_t struct to the data phase collection,
+  // where read addresses can be collected.
+  mailbox #(sram_trans_t) data_phase_mbox;
+
+  // mailbox that all completed sram_trans_t structs will be pushed into for processing.
+  mailbox #(sram_trans_t) completed_trans_mbox;
+
+  otp_ctrl_pkg::sram_key_t key     = sram_ctrl_pkg::RndCnstSramKeyDefault;
+  otp_ctrl_pkg::sram_nonce_t nonce = sram_ctrl_pkg::RndCnstSramNonceDefault;
 
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
+    sram_tl_a_chan_fifo = new("sram_tl_a_chan_fifo", this);
+    sram_tl_d_chan_fifo = new("sram_tl_d_chan_fifo", this);
+
+    kdi_fifo = new("kdi_fifo", this);
+
+    data_phase_mbox = new();
+    completed_trans_mbox = new();
   endfunction
 
   function void connect_phase(uvm_phase phase);
@@ -28,9 +69,159 @@
   task run_phase(uvm_phase phase);
     super.run_phase(phase);
     fork
+      process_sram_tl_a_chan_fifo();
+      process_sram_tl_d_chan_fifo();
+      process_kdi_fifo();
+      process_sram_trans();
     join_none
   endtask
 
+  virtual task process_sram_tl_a_chan_fifo();
+    tl_seq_item item;
+    sram_trans_t addr_trans;
+    forever begin
+      sram_tl_a_chan_fifo.get(item);
+      if (!cfg.en_scb) continue;
+      `uvm_info(`gfn, $sformatf("Received sram_tl_a_chan item:\n%0s", item.sprint()), UVM_HIGH)
+
+      // TODO: need to ensure that we don't process TLUL errors
+
+      addr_trans.we = item.is_write();
+      addr_trans.addr = item.a_addr;
+      addr_trans.mask = item.a_mask;
+
+      addr_trans.key = key;
+      addr_trans.nonce = nonce;
+
+      // Only set data in address phase if a write is detected
+      if (item.is_write()) begin
+        addr_trans.data = item.a_data;
+        // write the completed transaction to be processed
+        completed_trans_mbox.put(addr_trans);
+        `uvm_info({`gfn, "::process_sram_tl_a_chan_fifo()"},
+                  $sformatf("Put COMPLETED_WRITE transaction into completed_trans_mbox: %0p", addr_trans),
+                  UVM_HIGH)
+      end else begin
+        // on a read transaction, set the address and then send the incomplete transaction item
+        // to be completed in the data phase
+        data_phase_mbox.put(addr_trans);
+        `uvm_info({`gfn, "::process_sram_tl_a_chan_fifo()"},
+                  $sformatf("Put INCOMPLETE_READ transaction into data_phase_mbox: %0p", addr_trans),
+                  UVM_HIGH)
+      end
+    end
+  endtask
+
+  virtual task process_sram_tl_d_chan_fifo();
+    tl_seq_item item;
+    sram_trans_t data_trans;
+    forever begin
+      sram_tl_d_chan_fifo.get(item);
+      if (!cfg.en_scb) continue;
+      `uvm_info(`gfn, $sformatf("Received sram_tl_d_chan item:\n%0s", item.sprint()), UVM_HIGH)
+
+      // TODO: need to ensure that we don't process TLUL errors
+
+      // check packet integrity
+      void'(item.is_ok());
+
+      // here we only want to process read responses
+      //
+      // TODO: for now...
+      if (item.d_opcode == tlul_pkg::AccessAckData) begin
+        // if data channel has a read response, then there is an incomplete transaction
+        // waiting in the data_phase_mbox
+        data_phase_mbox.get(data_trans);
+        `uvm_info({`gfn, "::process_sram_tl_d_chan_fifo()"},
+                  $sformatf("Got INCOMPLETE_READ transaction from data_phase_mbox: %0p", data_trans),
+                  UVM_HIGH)
+        // set the read response data
+        data_trans.data = item.d_data;
+        completed_trans_mbox.put(data_trans);
+        `uvm_info({`gfn, "::process_sram_tl_d_chan_fifo()"},
+                  $sformatf("Put COMPLETED_READ transaction into completed_trans_mbox: %0p", data_trans),
+                  UVM_HIGH)
+      end
+    end
+  endtask
+
+  // This task polls the kdi_fifo for completed key request transactions
+  virtual task process_kdi_fifo();
+    bit seed_valid;
+    push_pull_item #(.DeviceDataWidth(KDI_DATA_SIZE)) item;
+    forever begin
+      kdi_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("Received transaction from kdi_fifo:\n%0s", item.convert2string()), UVM_HIGH)
+
+      // When KDI item is seen, update key, nonce
+      //
+      // TODO: update STATUS.scr_key_seed_valid
+      {key, nonce, seed_valid} = item.d_data;
+
+      `uvm_info(`gfn, $sformatf("Updated key: 0x%0x", key), UVM_HIGH)
+      `uvm_info(`gfn, $sformatf("Updated nonce: 0x%0x", nonce), UVM_HIGH)
+    end
+  endtask
+
+  // This task continuously pulls items from the completed_trans_mbox
+  // and checks them for correctness by using the mem_bkdr_if.
+  virtual task process_sram_trans();
+    sram_trans_t trans;
+    forever begin
+      completed_trans_mbox.get(trans);
+
+      // SRAM writes take 2 cycles to execute, while reads return data in the TLUL response.
+      if (trans.we) begin
+        cfg.clk_rst_vif.wait_clks(2);
+      end
+
+      `uvm_info({`gfn, "::process_sram_trans()"},
+                $sformatf("Received COMPLETED transaction from completed_trans_mbox: %0p", trans),
+                UVM_HIGH)
+      check_mem_trans(trans);
+    end
+  endtask
+
+  // Given a complete memory transaction item as input,
+  // this function compares against the SRAM for correctness
+  // using the mem_bkdr_if.
+  //
+  // TLUL allows partial reads and writes, so we first need to construct a bit-mask
+  // based off of the TLUL mask field.
+  // We then read from the memory using the backdoor interface, and can then directly compare
+  // the TLUL response data to the backdoor-read data using the bit-mask.
+  virtual function void check_mem_trans(sram_trans_t t);
+    bit [TL_AW-1:0] word_addr;
+    bit [TL_DW-1:0] bit_mask;
+
+    // data read from SRAM through backdoor
+    bit [TL_DW-1:0] exp_data;
+    bit [TL_DW-1:0] exp_masked_data;
+    bit [TL_DW-1:0] act_masked_data;
+
+    `uvm_info(`gfn, $sformatf("Checking SRAM memory transaction: %0p", t), UVM_HIGH)
+
+    // Word align the request address
+    word_addr = {t.addr[TL_AW-1:2], 2'b00};
+    `uvm_info(`gfn, $sformatf("word_addr: 0x%0x", word_addr), UVM_HIGH)
+
+    // Expand the byte-oriented mask into a full bit mask
+    for (int i = 0; i < TL_DBW; i++) begin
+      bit_mask[i*8 +: 8] = {8{t.mask[i]}};
+    end
+
+    // backdoor read the mem
+    exp_data = cfg.mem_bkdr_vif.sram_encrypt_read32(word_addr, t.key, t.nonce);
+
+    exp_masked_data = exp_data & bit_mask;
+    act_masked_data = t.data & bit_mask;
+
+    `uvm_info(`gfn, $sformatf("exp_masked_data: 0x%0x", exp_masked_data), UVM_HIGH)
+    `uvm_info(`gfn, $sformatf("act_masked_data: 0x%0x", act_masked_data), UVM_HIGH)
+
+    `DV_CHECK_EQ_FATAL(exp_masked_data, act_masked_data)
+  endfunction
+
   virtual task process_tl_access(tl_seq_item item, tl_channels_e channel = DataChannel);
     uvm_reg csr;
     bit     do_read_check   = 1'b1;
@@ -71,6 +262,27 @@
       "intr_test": begin
         // FIXME
       end
+      "exec_regwen": begin
+        // do nothing
+      end
+      "exec": begin
+      end
+      "status": begin
+        // TODO
+        do_read_check = 1'b0;
+      end
+      "ctrl_regwen": begin
+        // do nothing
+      end
+      "ctrl": begin
+        // do nothing if 0 is written
+        if (addr_phase_write && item.a_data) begin
+        end
+      end
+      "error_address": begin
+        // TODO
+        do_read_check = 1'b0;
+      end
       default: begin
         `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name()))
       end
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_virtual_sequencer.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_virtual_sequencer.sv
index 710d753..e51f543 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_virtual_sequencer.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_virtual_sequencer.sv
@@ -8,6 +8,7 @@
   );
   `uvm_component_utils(sram_ctrl_virtual_sequencer)
 
+  tl_sequencer sram_tl_sequencer_h;
 
   `uvm_component_new
 
diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson b/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson
index 7829b75..0da9022 100644
--- a/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson
@@ -51,11 +51,11 @@
   build_modes: [
     {
       name: sram_ctrl_main
-      build_opts: ["+define+SRAM_ADDR_WIDTH=14", "+define+SRAM_DATA_WIDTH=32"]
+      build_opts: ["+define+SRAM_ADDR_WIDTH=14"]
     }
     {
       name: sram_ctrl_ret
-      build_opts: ["+define+SRAM_ADDR_WIDTH=10", "+define+SRAM_DATA_WIDTH=32"]
+      build_opts: ["+define+SRAM_ADDR_WIDTH=10"]
     }
   ]
 
diff --git a/hw/ip/sram_ctrl/dv/tb.sv b/hw/ip/sram_ctrl/dv/tb.sv
index 646433a..32a837b 100644
--- a/hw/ip/sram_ctrl/dv/tb.sv
+++ b/hw/ip/sram_ctrl/dv/tb.sv
@@ -105,9 +105,8 @@
   // bind mem_bkdr_if
   `define SRAM_CTRL_MEM_HIER \
     dut.u_ram1p_sram.u_prim_ram_1p_adv.u_mem
-  bind `SRAM_CTRL_MEM_HIER mem_bkdr_if #(.MEM_ADDR_WIDTH(`SRAM_ADDR_WIDTH),
-                                         .MEM_BYTES_PER_WORD(`SRAM_DATA_WIDTH >> 3),
-                                         .MEM_PARITY(1)) mem_bkdr_if ();
+
+  bind `SRAM_CTRL_MEM_HIER mem_bkdr_if #(.MEM_PARITY(1)) mem_bkdr_if ();
 
   initial begin
     // drive clk and rst_n from clk_if
diff --git a/hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson b/hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson
index fd31d1d..b7aa412 100644
--- a/hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson
+++ b/hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson
@@ -33,6 +33,8 @@
              "{proj_root}/hw/ip/prim/dv/prim_prince/prim_prince_sim_cfg.hjson",
              "{proj_root}/hw/ip/rv_timer/dv/rv_timer_sim_cfg.hjson",
              "{proj_root}/hw/ip/spi_device/dv/spi_device_sim_cfg.hjson",
+             "{proj_root}/hw/ip/sram_ctrl/dv/sram_ctrl_main_sim_cfg.hjson",
+             "{proj_root}/hw/ip/sram_ctrl/dv/sram_ctrl_ret_sim_cfg.hjson",
              "{proj_root}/hw/ip/uart/dv/uart_sim_cfg.hjson",
              "{proj_root}/hw/ip/usbdev/dv/usbdev_sim_cfg.hjson",
              // Top level IPs.