[sram/dv] initial SRAM testbench

This PR adds the auto-generated testbench files produced by uvmdvgen,
and instantiates all of the necessary interfaces.

Note that `tb.sv` hooks up 3 separate modules as the DUT, this is due to
how the SRAM controller interfaces to the SRAM memory scrambling module
itself.

Also note that, similar to KMAC, two different variants of the SRAM are
being tested: one with a depth of `2**10` and one with a depth of
`2**14`, to mimic the Main and Retention SRAMs instantiated at the top
level.

This PR gets the SRAM testbench into a compile-able state, with no tests
or additional runtime functionality implemented.

Signed-off-by: Udi Jonnalagadda <udij@google.com>
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson b/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson
new file mode 100644
index 0000000..977d28d
--- /dev/null
+++ b/hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson
@@ -0,0 +1,34 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{
+  name: "sram_ctrl_base"
+  // TODO: remove the common testplans if not applicable
+  import_testplans: ["hw/dv/tools/dvsim/testplans/csr_testplan.hjson",
+                     "hw/dv/tools/dvsim/testplans/mem_testplan.hjson",
+                     "hw/dv/tools/dvsim/testplans/intr_test_testplan.hjson",
+                     "hw/dv/tools/dvsim/testplans/stress_all_with_reset_testplan.hjson",
+                     "hw/dv/tools/dvsim/testplans/tl_device_access_types_testplan.hjson"]
+  entries: [
+    {
+      name: smoke
+      desc: '''
+            Smoke test accessing a major datapath within the sram_ctrl.
+
+            **Stimulus**:
+            - TBD
+
+            **Checks**:
+            - TBD
+            '''
+      milestone: V1
+      tests: ["{name}_smoke"]
+    }
+    {
+      name: feature1
+      desc: '''Add more test entries here like above.'''
+      milestone: V1
+      tests: []
+    }
+  ]
+}
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl_main_testplan.hjson b/hw/ip/sram_ctrl/data/sram_ctrl_main_testplan.hjson
new file mode 100644
index 0000000..86ef960
--- /dev/null
+++ b/hw/ip/sram_ctrl/data/sram_ctrl_main_testplan.hjson
@@ -0,0 +1,9 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// testplan for the main SRAM variant
+{
+  name: "sram_ctrl_main"
+  import_testplans: ["hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson"]
+}
diff --git a/hw/ip/sram_ctrl/data/sram_ctrl_ret_testplan.hjson b/hw/ip/sram_ctrl/data/sram_ctrl_ret_testplan.hjson
new file mode 100644
index 0000000..56cd50c
--- /dev/null
+++ b/hw/ip/sram_ctrl/data/sram_ctrl_ret_testplan.hjson
@@ -0,0 +1,9 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// testplan for the retention SRAM variant
+{
+  name: "sram_ctrl_ret"
+  import_testplans: ["hw/ip/sram_ctrl/data/sram_ctrl_base_testplan.hjson"]
+}
diff --git a/hw/ip/sram_ctrl/doc/dv_plan/index.md b/hw/ip/sram_ctrl/doc/dv_plan/index.md
new file mode 100644
index 0000000..c588daf
--- /dev/null
+++ b/hw/ip/sram_ctrl/doc/dv_plan/index.md
@@ -0,0 +1,118 @@
+---
+title: "SRAM_CTRL DV Plan"
+---
+
+<!-- Copy this file to hw/ip/sram_ctrl/doc/sram_ctrl_dv_plan.md and make changes as needed.
+For convenience 'sram_ctrl' in the document can be searched and replaced easily with the
+desired IP (with case sensitivity!). Also, use the testbench block diagram
+located at OpenTitan team drive / 'design verification'
+as a starting point and modify it to reflect your sram_ctrl testbench and save it
+to hw/ip/sram_ctrl/doc/tb.svg. It should get linked and rendered under the block
+diagram section below. Please update / modify / remove sections below as
+applicable. Once done, remove this comment before making a PR. -->
+
+## Goals
+* **DV**
+  * Verify all SRAM_CTRL IP features by running dynamic simulations with a SV/UVM based testbench
+  * Develop and run all tests based on the [testplan](#testplan) below towards closing code and functional coverage on the IP and all of its sub-modules
+* **FPV**
+  * Verify TileLink device protocol compliance with an SVA based testbench
+
+## Current status
+* [Design & verification stage]({{< relref "hw" >}})
+  * [HW development stages]({{< relref "doc/project/development_stages" >}})
+* [Simulation results](https://reports.opentitan.org/hw/ip/sram_ctrl/dv/latest/results.html)
+
+## Design features
+For detailed information on SRAM_CTRL design features, please see the [SRAM_CTRL HWIP technical specification]({{< relref "hw/ip/sram_ctrl/doc" >}}).
+
+## Testbench architecture
+SRAM_CTRL testbench has been constructed based on the [CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
+
+### Block diagram
+![Block diagram](tb.svg)
+
+### Top level testbench
+Top level testbench is located at `hw/ip/sram_ctrl/dv/tb/tb.sv`. It instantiates the SRAM_CTRL DUT module `hw/ip/sram_ctrl/rtl/sram_ctrl.sv`.
+In addition, it instantiates the following interfaces, connects them to the DUT and sets their handle into `uvm_config_db`:
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
+* SRAM_CTRL IOs
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}})
+* Alerts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}})
+* Devmode ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}})
+
+### Common DV utility components
+The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
+
+### Compile-time configurations
+[list compile time configurations, if any and what are they used for]
+
+### Global types & methods
+All common types and methods defined at the package level can be found in
+`sram_ctrl_env_pkg`. Some of them in use are:
+```systemverilog
+[list a few parameters, types & methods; no need to mention all]
+```
+### TL_agent
+SRAM_CTRL testbench instantiates (already handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
+which provides the ability to drive and independently monitor random traffic via
+TL host interface into SRAM_CTRL device.
+
+### UVC/agent 1
+[Describe here or add link to its README]
+
+### UVC/agent 2
+[Describe here or add link to its README]
+
+### UVM RAL Model
+The SRAM_CTRL RAL model is created with the [`ralgen`]({{< relref "hw/dv/tools/ralgen/README.md" >}}) FuseSoC generator script automatically when the simulation is at the build stage.
+
+It can be created manually by invoking [`regtool`]({{< relref "util/reggen/README.md" >}}):
+
+### Reference models
+[Describe reference models in use if applicable, example: SHA256/HMAC]
+
+### Stimulus strategy
+#### Test sequences
+All test sequences reside in `hw/ip/sram_ctrl/dv/env/seq_lib`.
+The `sram_ctrl_base_vseq` virtual sequence is extended from `cip_base_vseq` and serves as a starting point.
+All test sequences are extended from `sram_ctrl_base_vseq`.
+It provides commonly used handles, variables, functions and tasks that the test sequences can simple use / call.
+Some of the most commonly used tasks / functions are as follows:
+* task 1:
+* task 2:
+
+#### Functional coverage
+To ensure high quality constrained random stimulus, it is necessary to develop a functional coverage model.
+The following covergroups have been developed to prove that the test intent has been adequately met:
+* cg1:
+* cg2:
+
+### Self-checking strategy
+#### Scoreboard
+The `sram_ctrl_scoreboard` is primarily used for end to end checking.
+It creates the following analysis ports to retrieve the data monitored by corresponding interface agents:
+* analysis port1:
+* analysis port2:
+<!-- explain inputs monitored, flow of data and outputs checked -->
+
+#### Assertions
+* TLUL assertions: The `tb/sram_ctrl_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance.
+* Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset.
+* assert prop 1:
+* assert prop 2:
+
+## Building and running tests
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
+Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
+Here's how to run a smoke test:
+```console
+$ $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/ip/sram_ctrl/dv/sram_ctrl_sim_cfg.hjson -i sram_ctrl_smoke
+```
+
+## Testplan
+<!-- TODO: uncomment the line below after adding the testplan -->
+{{</* testplan "hw/ip/sram_ctrl/data/sram_ctrl_testplan.hjson" */>}}
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
new file mode 100644
index 0000000..022e0e4
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv
@@ -0,0 +1,33 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_base_vseq extends cip_base_vseq #(
+    .RAL_T               (sram_ctrl_reg_block),
+    .CFG_T               (sram_ctrl_env_cfg),
+    .COV_T               (sram_ctrl_env_cov),
+    .VIRTUAL_SEQUENCER_T (sram_ctrl_virtual_sequencer)
+  );
+  `uvm_object_utils(sram_ctrl_base_vseq)
+
+  // 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 dut_shutdown();
+    // check for pending sram_ctrl operations and wait for them to complete
+    // TODO
+  endtask
+
+  // setup basic sram_ctrl features
+  virtual task sram_ctrl_init();
+    `uvm_error(`gfn, "FIXME")
+  endtask
+
+endclass : sram_ctrl_base_vseq
diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_common_vseq.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_common_vseq.sv
new file mode 100644
index 0000000..dc17dc0
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_common_vseq.sv
@@ -0,0 +1,17 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_common_vseq extends sram_ctrl_base_vseq;
+  `uvm_object_utils(sram_ctrl_common_vseq)
+
+  constraint num_trans_c {
+    num_trans inside {[1:2]};
+  }
+  `uvm_object_new
+
+  virtual task body();
+    run_common_vseq_wrapper(num_trans);
+  endtask : body
+
+endclass
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
new file mode 100644
index 0000000..54a3667
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_smoke_vseq.sv
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// smoke test vseq
+class sram_ctrl_smoke_vseq extends sram_ctrl_base_vseq;
+  `uvm_object_utils(sram_ctrl_smoke_vseq)
+
+  `uvm_object_new
+
+  task body();
+    `uvm_error(`gfn, "FIXME")
+  endtask : body
+
+endclass : sram_ctrl_smoke_vseq
diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_vseq_list.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_vseq_list.sv
new file mode 100644
index 0000000..266402c
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_vseq_list.sv
@@ -0,0 +1,7 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "sram_ctrl_base_vseq.sv"
+`include "sram_ctrl_smoke_vseq.sv"
+`include "sram_ctrl_common_vseq.sv"
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core
new file mode 100644
index 0000000..da2022f
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core
@@ -0,0 +1,39 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:sram_ctrl_env:0.1"
+description: "SRAM_CTRL DV UVM environment"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:ralgen
+      - lowrisc:dv:cip_lib
+      - lowrisc:dv:mem_bkdr_if
+    files:
+      - sram_ctrl_env_pkg.sv
+      - sram_ctrl_lc_if.sv
+      - sram_ctrl_env_cfg.sv: {is_include_file: true}
+      - sram_ctrl_env_cov.sv: {is_include_file: true}
+      - sram_ctrl_virtual_sequencer.sv: {is_include_file: true}
+      - sram_ctrl_scoreboard.sv: {is_include_file: true}
+      - sram_ctrl_env.sv: {is_include_file: true}
+      - seq_lib/sram_ctrl_vseq_list.sv: {is_include_file: true}
+      - seq_lib/sram_ctrl_base_vseq.sv: {is_include_file: true}
+      - seq_lib/sram_ctrl_common_vseq.sv: {is_include_file: true}
+      - seq_lib/sram_ctrl_smoke_vseq.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+generate:
+  ral:
+    generator: ralgen
+    parameters:
+      name: sram_ctrl
+      ip_hjson: ../../data/sram_ctrl.hjson
+
+targets:
+  default:
+    filesets:
+      - files_dv
+    generate:
+      - ral
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv
new file mode 100644
index 0000000..f3198bd
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.sv
@@ -0,0 +1,58 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_env extends cip_base_env #(
+    .CFG_T              (sram_ctrl_env_cfg),
+    .COV_T              (sram_ctrl_env_cov),
+    .VIRTUAL_SEQUENCER_T(sram_ctrl_virtual_sequencer),
+    .SCOREBOARD_T       (sram_ctrl_scoreboard)
+  );
+  `uvm_component_utils(sram_ctrl_env)
+
+  `uvm_component_new
+
+  // TL agent for the SRAM memory interface
+  tl_agent m_sram_tl_agent;
+
+  // KDI agent
+  push_pull_agent#(.DeviceDataWidth(KDI_DATA_SIZE)) m_kdi_agent;
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+
+    // Get the OTP clk/rst interface
+    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);
+
+    // Get the LC interface
+    if (!uvm_config_db#(lc_vif)::get(this, "", "lc_vif", cfg.lc_vif)) begin
+      `uvm_fatal(`gfn, "failed to get lc_vif from uvm_config_db")
+    end
+
+    // Get the mem_bkdr interface
+    if (!uvm_config_db#(mem_bkdr_vif)::get(this, "", "mem_bkdr_vif", cfg.mem_bkdr_vif)) begin
+      `uvm_fatal(`gfn, "failed to get mem_bkdr_vif from uvm_config_db")
+    end
+
+    // Build the TLUL SRAM agent
+    m_sram_tl_agent = tl_agent::type_id::create("m_sram_tl_agent", this);
+    uvm_config_db#(tl_agent_cfg)::set(this,
+      "m_sram_tl_agent", "cfg", cfg.m_sram_cfg);
+
+    // Build the KDI agent
+    m_kdi_agent = push_pull_agent#(.DeviceDataWidth(KDI_DATA_SIZE))::type_id
+      ::create("m_kdi_agent", this);
+    uvm_config_db#(push_pull_agent_cfg#(.DeviceDataWidth(KDI_DATA_SIZE)))::set(
+      this, "m_kdi_agent", "cfg", cfg.m_kdi_cfg);
+
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+  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
new file mode 100644
index 0000000..c50fe16
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv
@@ -0,0 +1,32 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_env_cfg extends cip_base_env_cfg #(.RAL_T(sram_ctrl_reg_block));
+
+  `uvm_object_utils_begin(sram_ctrl_env_cfg)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+  // ext component cfgs
+  rand tl_agent_cfg m_sram_cfg;
+  rand push_pull_agent_cfg#(.DeviceDataWidth(KDI_DATA_SIZE)) m_kdi_cfg;
+
+  // ext interfaces
+  virtual clk_rst_if otp_clk_rst_vif;
+  lc_vif lc_vif;
+  mem_bkdr_vif mem_bkdr_vif;
+
+  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
+    m_kdi_cfg = push_pull_agent_cfg#(.DeviceDataWidth(KDI_DATA_SIZE))::type_id::create("m_kdi_cfg");
+
+    // Build SRAM TL cfg object
+    m_sram_cfg = tl_agent_cfg::type_id::create("m_sram_cfg");
+  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
new file mode 100644
index 0000000..bc8e78c
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
@@ -0,0 +1,32 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Covergoups that are dependent on run-time parameters that may be available
+ * only in build_phase can be defined here
+ * Covergroups may also be wrapped inside helper classes if needed.
+ */
+
+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:
+  // sram_ctrl_env_cfg: cfg
+
+  // covergroups
+  // [add covergroups here]
+
+  function new(string name, uvm_component parent);
+    super.new(name, parent);
+    // [instantiate covergroups here]
+  endfunction : new
+
+  virtual function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    // [or instantiate covergroups here]
+    // Please instantiate sticky_intr_cov array of objects for all interrupts that are sticky
+    // See cip_base_env_cov for details
+  endfunction
+
+endclass
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
new file mode 100644
index 0000000..369001a
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_pkg.sv
@@ -0,0 +1,46 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package sram_ctrl_env_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import top_pkg::*;
+  import dv_utils_pkg::*;
+  import dv_lib_pkg::*;
+  import tl_agent_pkg::*;
+  import cip_base_pkg::*;
+  import csr_utils_pkg::*;
+  import push_pull_agent_pkg::*;
+  import sram_ctrl_ral_pkg::*;
+  import sram_ctrl_pkg::*;
+  import otp_ctrl_pkg::*;
+  import lc_ctrl_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // parameters
+  parameter string LIST_OF_ALERTS[] = { "sram_integ_alert" };
+  parameter uint   NUM_ALERTS = 1;
+
+  // Number of bits in the otp_ctrl_pkg::sram_otp_key_rsp_t struct:
+  // 1 bit for valid, SramKeyWidth bits for the key, SramNonceWidth bits for the nonce.
+  parameter int KDI_DATA_SIZE = 1 + otp_ctrl_pkg::SramKeyWidth + otp_ctrl_pkg::SramNonceWidth;
+
+  // types
+  typedef virtual mem_bkdr_if mem_bkdr_vif;
+  typedef virtual sram_ctrl_lc_if lc_vif;
+
+  // functions
+
+  // package sources
+  `include "sram_ctrl_env_cfg.sv"
+  `include "sram_ctrl_env_cov.sv"
+  `include "sram_ctrl_virtual_sequencer.sv"
+  `include "sram_ctrl_scoreboard.sv"
+  `include "sram_ctrl_env.sv"
+  `include "sram_ctrl_vseq_list.sv"
+
+endpackage
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
new file mode 100644
index 0000000..1d3d584
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_lc_if.sv
@@ -0,0 +1,7 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+interface sram_ctrl_lc_if;
+  lc_ctrl_pkg::lc_tx_t lc_esc_en;
+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
new file mode 100644
index 0000000..f70e083
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
@@ -0,0 +1,99 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_scoreboard extends cip_base_scoreboard #(
+    .CFG_T(sram_ctrl_env_cfg),
+    .RAL_T(sram_ctrl_reg_block),
+    .COV_T(sram_ctrl_env_cov)
+  );
+  `uvm_component_utils(sram_ctrl_scoreboard)
+
+  // local variables
+
+  // TLM agent fifos
+
+  // local queues to hold incoming packets pending comparison
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+  endfunction
+
+  task run_phase(uvm_phase phase);
+    super.run_phase(phase);
+    fork
+    join_none
+  endtask
+
+  virtual task process_tl_access(tl_seq_item item, tl_channels_e channel = DataChannel);
+    uvm_reg csr;
+    bit     do_read_check   = 1'b1;
+    bit     write           = item.is_write();
+    uvm_reg_addr_t csr_addr = ral.get_word_aligned_addr(item.a_addr);
+
+    bit addr_phase_read   = (!write && channel == AddrChannel);
+    bit addr_phase_write  = (write && channel == AddrChannel);
+    bit data_phase_read   = (!write && channel == DataChannel);
+    bit data_phase_write  = (write && channel == DataChannel);
+
+    // if access was to a valid csr, get the csr handle
+    if (csr_addr inside {cfg.csr_addrs}) begin
+      csr = ral.default_map.get_reg_by_offset(csr_addr);
+      `DV_CHECK_NE_FATAL(csr, null)
+    end
+    else begin
+      `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr))
+    end
+
+    // if incoming access is a write to a valid csr, then make updates right away
+    if (addr_phase_write) begin
+      void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask)));
+    end
+
+    // process the csr req
+    // for write, update local variable and fifo at address phase
+    // for read, update predication at address phase and compare at data phase
+    case (csr.get_name())
+      // add individual case item for each csr
+      "intr_state": begin
+        // FIXME
+        do_read_check = 1'b0;
+      end
+      "intr_enable": begin
+        // FIXME
+      end
+      "intr_test": begin
+        // FIXME
+      end
+      default: begin
+        `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name()))
+      end
+    endcase
+
+    // On reads, if do_read_check, is set, then check mirrored_value against item.d_data
+    if (data_phase_read) begin
+      if (do_read_check) begin
+        `DV_CHECK_EQ(csr.get_mirrored_value(), item.d_data,
+                     $sformatf("reg name: %0s", csr.get_full_name()))
+      end
+      void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ)));
+    end
+  endtask
+
+  virtual function void reset(string kind = "HARD");
+    super.reset(kind);
+    // reset local fifos queues and variables
+  endfunction
+
+  function void check_phase(uvm_phase phase);
+    super.check_phase(phase);
+    // post test checks - ensure that all local fifos and queues are empty
+  endfunction
+
+endclass
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
new file mode 100644
index 0000000..710d753
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_virtual_sequencer.sv
@@ -0,0 +1,14 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_virtual_sequencer extends cip_base_virtual_sequencer #(
+    .CFG_T(sram_ctrl_env_cfg),
+    .COV_T(sram_ctrl_env_cov)
+  );
+  `uvm_component_utils(sram_ctrl_virtual_sequencer)
+
+
+  `uvm_component_new
+
+endclass
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
new file mode 100644
index 0000000..ca87011
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson
@@ -0,0 +1,83 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{
+  // Name of the sim cfg - typically same as the name of the DUT.
+  name: sram_ctrl
+
+  // Top level dut name (sv module).
+  dut: sram_ctrl_wrapper
+
+  // Top level testbench name (sv module).
+  tb: tb
+
+  // Simulator used to sign off this block
+  tool: vcs
+
+  // Fusesoc core file used for building the file list.
+  fusesoc_core: lowrisc:dv:sram_ctrl_sim:0.1
+
+  // Testplan hjson file.
+  testplan: "{proj_root}/hw/ip/sram_ctrl/data/{variant}_testplan.hjson"
+
+  // RAL spec - used to generate the RAL model.
+  ral_spec: "{proj_root}/hw/ip/sram_ctrl/data/sram_ctrl.hjson"
+
+  // Import additional common sim cfg files.
+  // TODO: remove imported cfgs that do not apply.
+  import_cfgs: [// Project wide common sim cfg file
+                "{proj_root}/hw/dv/tools/dvsim/common_sim_cfg.hjson",
+                // Common CIP test lists
+                "{proj_root}/hw/dv/tools/dvsim/tests/csr_tests.hjson",
+                "{proj_root}/hw/dv/tools/dvsim/tests/mem_tests.hjson",
+                "{proj_root}/hw/dv/tools/dvsim/tests/alert_test.hjson",
+                "{proj_root}/hw/dv/tools/dvsim/tests/tl_access_tests.hjson",
+                "{proj_root}/hw/dv/tools/dvsim/tests/stress_tests.hjson"]
+
+  // Add additional tops for simulation.
+  sim_tops: ["sram_ctrl_bind"]
+
+  // Default iterations for all tests - each test entry can override this.
+  reseed: 50
+
+  // Need to override the default output directory
+  overrides: [
+    {
+      name: scratch_path
+      value: "{scratch_base_path}/{variant}-{flow}-{tool}"
+    }
+  ]
+
+  build_modes: [
+    {
+      name: sram_ctrl_main
+      build_opts: ["+define+ADDR_BITS=14", "+define+WIDTH=32"]
+    }
+    {
+      name: sram_ctrl_ret
+      build_opts: ["+define+ADDR_BITS=10", "+define+WIDTH=32"]
+    }
+  ]
+
+  // Default UVM test and seq class name.
+  uvm_test: sram_ctrl_base_test
+  uvm_test_seq: sram_ctrl_base_vseq
+
+  // List of test specifications.
+  tests: [
+    {
+      name: "{variant}_smoke"
+      uvm_test_seq: sram_ctrl_smoke_vseq
+    }
+
+    // TODO: add more tests here
+  ]
+
+  // List of regressions.
+  regressions: [
+    {
+      name: smoke
+      tests: ["{variant}_smoke"]
+    }
+  ]
+}
diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_main_sim_cfg.hjson b/hw/ip/sram_ctrl/dv/sram_ctrl_main_sim_cfg.hjson
new file mode 100644
index 0000000..8973af4
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_main_sim_cfg.hjson
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// sim cfg file for the main SRAM variant
+{
+  // Name of the sim cfg variant
+  variant: sram_ctrl_main
+
+  // Import the base sram_ctrl sim_cfg file
+  import_cfgs: ["{proj_root}/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson"]
+
+  // Enable the appropriate build mode for all tests
+  en_build_modes: ["sram_ctrl_main"]
+}
diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_ret_sim_cfg.hjson b/hw/ip/sram_ctrl/dv/sram_ctrl_ret_sim_cfg.hjson
new file mode 100644
index 0000000..4dbb0fb
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_ret_sim_cfg.hjson
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// sim cfg file for the retention SRAM variant
+{
+  // Name of the sim cfg variant
+  variant: sram_ctrl_ret
+
+  // Import the base sram_ctrl sim_cfg file
+  import_cfgs: ["{proj_root}/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson"]
+
+  // Enable the appropriate build mode for all tests
+  en_build_modes: ["sram_ctrl_ret"]
+}
diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_sim.core b/hw/ip/sram_ctrl/dv/sram_ctrl_sim.core
new file mode 100644
index 0000000..a98aa33
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_sim.core
@@ -0,0 +1,34 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:sram_ctrl_sim:0.1"
+description: "SRAM_CTRL DV sim target"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:ip:sram_ctrl
+      - lowrisc:prim:ram_1p_scr
+    files:
+      - sram_ctrl_wrapper.sv
+    file_type: systemVerilogSource
+
+  files_dv:
+    depend:
+      - lowrisc:dv:sram_ctrl_test
+      - lowrisc:dv:sram_ctrl_sva
+    files:
+      - tb.sv
+    file_type: systemVerilogSource
+
+targets:
+  sim: &sim_target
+    toplevel: tb
+    filesets:
+      - files_rtl
+      - files_dv
+    default_tool: vcs
+
+  # TODO: add a lint check cfg in `hw/top_earlgrey/lint/top_earlgrey_dv_lint_cfgs.hjson`
+  lint:
+    <<: *sim_target
diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_wrapper.sv b/hw/ip/sram_ctrl/dv/sram_ctrl_wrapper.sv
new file mode 100644
index 0000000..a6aa441
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sram_ctrl_wrapper.sv
@@ -0,0 +1,125 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+
+// Wrapper module for DV that instantiates all 3 requisite module to form the full SRAM:
+// - sram_ctrl
+// - tlul_adapter_sram
+// - prim_ram_1p_scr
+module sram_ctrl_wrapper
+  // dep packages
+  import sram_ctrl_pkg::*;
+  import sram_ctrl_reg_pkg::*;
+#(
+  parameter int unsigned AddrWidth = 14,
+  parameter int unsigned DataWidth = 32
+) (
+  // clock/reset for sram_ctrl
+  input                                             clk_i,
+  input                                             rst_ni,
+  // clock/reset for otp_ctrl
+  input                                             clk_otp_i,
+  input                                             rst_otp_ni,
+  // TLUL interface for CSR regfile
+  input tlul_pkg::tl_h2d_t                          csr_tl_i,
+  output tlul_pkg::tl_d2h_t                         csr_tl_o,
+  // TLUL interface for the SRAM memory
+  input tlul_pkg::tl_h2d_t                          sram_tl_i,
+  output tlul_pkg::tl_d2h_t                         sram_tl_o,
+  // Alert I/O
+  input prim_alert_pkg::alert_rx_t [NumAlerts-1:0]  alert_rx_i,
+  output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,
+  // Life-cycle escalation input
+  input lc_ctrl_pkg::lc_tx_t                        lc_escalate_en_i,
+  // Key request to OTP
+  output otp_ctrl_pkg::sram_otp_key_req_t           sram_otp_key_o,
+  input otp_ctrl_pkg::sram_otp_key_rsp_t            sram_otp_key_i
+);
+
+  // Scrambling key interface between sram_ctrl and scrambling RAM
+  sram_scr_req_t scr_req;
+  sram_scr_rsp_t scr_rsp;
+
+  // SRAM interface between TLUL adapter and scrambling RAM
+  wire                  req;
+  wire                  gnt;
+  wire                  we;
+  wire [AddrWidth-1:0]  addr;
+  wire [DataWidth-1:0]  wdata;
+  wire [DataWidth-1:0]  wmask;
+  wire [DataWidth-1:0]  rdata;
+  wire                  rvalid;
+
+  // SRAM Controller
+  sram_ctrl u_sram_ctrl (
+    // main clock
+    .clk_i            (clk_i            ),
+    .rst_ni           (rst_ni           ),
+    // OTP clock
+    .clk_otp_i        (clk_otp_i        ),
+    .rst_otp_ni       (rst_otp_ni       ),
+    // TLUL interface for CSRs
+    .tl_i             (csr_tl_i         ),
+    .tl_o             (csr_tl_o         ),
+    // Alert I/O
+    .alert_rx_i       (alert_rx_i       ),
+    .alert_tx_o       (alert_tx_o       ),
+    // Life cycle escalation
+    .lc_escalate_en_i (lc_escalate_en_i ),
+    // OTP key derivation interface
+    .sram_otp_key_o   (sram_otp_key_o   ),
+    .sram_otp_key_i   (sram_otp_key_i   ),
+    // Interface with SRAM memory scrambling
+    .sram_scr_o       (scr_req          ),
+    .sram_scr_i       (scr_rsp          )
+  );
+
+  // TLUL Adapter SRAM
+  tlul_adapter_sram #(
+    .SramAw(AddrWidth),
+    .SramDw(DataWidth)
+  ) u_tl_adapter_sram (
+    .clk_i    (clk_i          ),
+    .rst_ni   (rst_ni         ),
+    // TLUL interface to SRAM memory
+    .tl_i     (sram_tl_i      ),
+    .tl_o     (sram_tl_o      ),
+    // Corresponding SRAM request interface
+    .req_o    (req            ),
+    .gnt_i    (gnt            ),
+    .we_o     (we             ),
+    .addr_o   (addr           ),
+    .wdata_o  (wdata          ),
+    .wmask_o  (wmask          ),
+    .rdata_i  (rdata          ),
+    .rvalid_i (rvalid         ),
+    .rerror_i (scr_rsp.rerror )
+  );
+
+  // Scrambling memory
+  prim_ram_1p_scr #(
+    .Width(DataWidth),
+    .Depth(2 ** AddrWidth)
+  ) u_ram1p_sram (
+    .clk_i      (clk_i          ),
+    .rst_ni     (rst_ni         ),
+    // Key interface
+    .key_valid_i(scr_req.valid  ),
+    .key_i      (scr_req.key    ),
+    .nonce_i    (scr_req.nonce  ),
+    // SRAM response interface to TLUL adapter
+    .req_i      (req            ),
+    .gnt_o      (gnt            ),
+    .write_i    (we             ),
+    .addr_i     (addr           ),
+    .wdata_i    (wdata          ),
+    .wmask_i    (wmask          ),
+    .rdata_o    (rdata          ),
+    .rvalid_o   (rvalid         ),
+    .rerror_o   (scr_rsp.rerror ),
+    .raddr_o    (scr_rsp.raddr  ),
+    .cfg_i      ('0             )
+  );
+
+endmodule
diff --git a/hw/ip/sram_ctrl/dv/sva/sram_ctrl_bind.sv b/hw/ip/sram_ctrl/dv/sva/sram_ctrl_bind.sv
new file mode 100644
index 0000000..ad2e6a8
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sva/sram_ctrl_bind.sv
@@ -0,0 +1,26 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+module sram_ctrl_bind;
+
+  bind sram_ctrl tlul_assert #(
+    .EndpointType("Device")
+  ) tlul_assert_device (
+    .clk_i,
+    .rst_ni,
+    .h2d  (tl_i),
+    .d2h  (tl_o)
+  );
+
+  import sram_ctrl_reg_pkg::*;
+  bind sram_ctrl sram_ctrl_csr_assert_fpv sram_ctrl_csr_assert (
+    .clk_i,
+    .rst_ni,
+    .h2d    (tl_i),
+    .d2h    (tl_o),
+    .reg2hw (reg2hw),
+    .hw2reg (hw2reg)
+  );
+
+endmodule
diff --git a/hw/ip/sram_ctrl/dv/sva/sram_ctrl_sva.core b/hw/ip/sram_ctrl/dv/sva/sram_ctrl_sva.core
new file mode 100644
index 0000000..1c73081
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/sva/sram_ctrl_sva.core
@@ -0,0 +1,38 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:sram_ctrl_sva:0.1"
+description: "SRAM_CTRL assertion modules and bind file."
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:tlul:headers
+      - lowrisc:fpv:csr_assert_gen
+    files:
+      - sram_ctrl_bind.sv
+    file_type: systemVerilogSource
+
+  files_formal:
+    depend:
+      - lowrisc:ip:sram_ctrl
+
+generate:
+  csr_assert_gen:
+    generator: csr_assert_gen
+    parameters:
+      spec: ../../data/sram_ctrl.hjson
+      depend: lowrisc:ip:sram_ctrl
+
+targets:
+  default: &default_target
+    filesets:
+      - files_dv
+    generate:
+      - csr_assert_gen
+  formal:
+    <<: *default_target
+    filesets:
+      - files_formal
+      - files_dv
+    toplevel: sram_ctrl
diff --git a/hw/ip/sram_ctrl/dv/tb.sv b/hw/ip/sram_ctrl/dv/tb.sv
new file mode 100644
index 0000000..dc58c1d
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/tb.sv
@@ -0,0 +1,130 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+module tb;
+  // dep packages
+  import uvm_pkg::*;
+  import dv_utils_pkg::*;
+  import sram_ctrl_pkg::*;
+  import sram_ctrl_env_pkg::*;
+  import sram_ctrl_test_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  wire clk, rst_n;
+  wire clk_otp, rst_otp_n;
+  wire devmode;
+  wire [NUM_MAX_INTERRUPTS-1:0] interrupts;
+
+  // OTP key derivation interface
+  otp_ctrl_pkg::sram_otp_key_req_t key_req;
+  otp_ctrl_pkg::sram_otp_key_rsp_t key_rsp;
+
+  otp_ctrl_pkg::sram_key_t   key;
+  otp_ctrl_pkg::sram_nonce_t nonce;
+
+  wire seed_valid;
+
+  lc_ctrl_pkg::lc_tx_t lc_esc_en;
+
+  // interfaces
+  clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
+  pins_if #(NUM_MAX_INTERRUPTS) intr_if(interrupts);
+  clk_rst_if otp_clk_rst_if(.clk(clk_otp), .rst_n(rst_otp_n));
+  pins_if #(1) devmode_if(devmode);
+
+  // TLUL interface to the CSR regfile
+  tl_if tl_if(.clk(clk), .rst_n(rst_n));
+
+  // TLUL interface to the SRAM memory itself
+  tl_if sram_tl_if(.clk(clk), .rst_n(rst_n));
+
+  // KDI interface for the OTP<->SRAM connections
+  push_pull_if #(.DeviceDataWidth(KDI_DATA_SIZE)) kdi_if(.clk(clk_otp), .rst_n(rst_otp_n));
+
+  // Interface for lifecycle escalation
+  sram_ctrl_lc_if lc_if();
+
+  `DV_ALERT_IF_CONNECT
+
+  // DUT
+  //
+  // Top level parameters for SRAM are:
+  // - ADDR_BITS: The number of address bits.
+  //              This will be set to 10 for the retention SRAM,
+  //              and 14 for the Main SRAM.
+  //
+  // - WIDTH: width of the SRAM data.
+  //          This is set to 32 for both the Main and Retention SRAMS,
+  //          but is parameterizable in case we want to test the SRAM variant
+  //          used in OTBN.
+
+  sram_ctrl_wrapper #(
+    .AddrWidth(`ADDR_BITS),
+    .DataWidth(`WIDTH)
+  ) dut (
+    // main clock
+    .clk_i(clk),
+    .rst_ni(rst_n),
+    // OTP clock
+    .clk_otp_i(clk_otp),
+    .rst_otp_ni(rst_otp_n),
+    // TLUL interface for CSR regfile
+    .csr_tl_i(tl_if.h2d),
+    .csr_tl_o(tl_if.d2h),
+    // TLUL interface for SRAM memory
+    .sram_tl_i(sram_tl_if.h2d),
+    .sram_tl_o(sram_tl_if.d2h),
+    // Alert I/O
+    .alert_rx_i(alert_rx),
+    .alert_tx_o(alert_tx),
+    // Life cycle escalation
+    .lc_escalate_en_i(lc_esc_en),
+    // OTP key derivation interface
+    .sram_otp_key_o(key_req),
+    .sram_otp_key_i(key_rsp)
+  );
+
+  // KDI interface assignments
+  assign kdi_if.req         = key_req.req;
+  assign key_rsp.ack        = kdi_if.ack;
+  assign key_rsp.key        = key;
+  assign key_rsp.nonce      = nonce;
+  assign key_rsp.seed_valid = seed_valid;
+  // key, nonce, seed_valid all driven by push_pull Device interface
+  assign {key, nonce, seed_valid} = kdi_if.d_data;
+
+  // LC interface assignment
+  assign lc_esc_en = lc_if.lc_esc_en;
+
+  // bind mem_bkdr_if
+  `define SRAM_CTRL_MEM_HIER \
+    dut.u_ram1p_sram.u_prim_ram_1p_adv.u_mem.gen_generic.u_impl_generic
+  bind `SRAM_CTRL_MEM_HIER mem_bkdr_if mem_bkdr_if();
+
+  initial begin
+    // drive clk and rst_n from clk_if
+    clk_rst_if.set_active();
+    otp_clk_rst_if.set_active();
+
+    // set interfaces into uvm_config_db
+    uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "clk_rst_vif", clk_rst_if);
+    uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "otp_clk_rst_vif", otp_clk_rst_if);
+    uvm_config_db#(intr_vif)::set(null, "*.env", "intr_vif", intr_if);
+    uvm_config_db#(devmode_vif)::set(null, "*.env", "devmode_vif", devmode_if);
+    uvm_config_db#(virtual push_pull_if#(.DeviceDataWidth(KDI_DATA_SIZE)))::set(null,
+      "*.env.m_kdi_agent*", "vif", kdi_if);
+    uvm_config_db#(virtual sram_ctrl_lc_if)::set(null, "*.env", "lc_vif", lc_if);
+    uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if);
+    uvm_config_db#(virtual tl_if)::set(null, "*.env.m_sram_tl_agent*", "vif", sram_tl_if);
+    uvm_config_db#(mem_bkdr_vif)::set(null, "*.env", "mem_bkdr_vif",
+      `SRAM_CTRL_MEM_HIER.mem_bkdr_if);
+
+    $timeformat(-12, 0, " ps", 12);
+    run_test();
+  end
+
+endmodule
diff --git a/hw/ip/sram_ctrl/dv/tests/sram_ctrl_base_test.sv b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_base_test.sv
new file mode 100644
index 0000000..d6b4d9c
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_base_test.sv
@@ -0,0 +1,20 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class sram_ctrl_base_test extends cip_base_test #(
+    .CFG_T(sram_ctrl_env_cfg),
+    .ENV_T(sram_ctrl_env)
+  );
+
+  `uvm_component_utils(sram_ctrl_base_test)
+  `uvm_component_new
+
+  // the base class dv_base_test creates the following instances:
+  // sram_ctrl_env_cfg: cfg
+  // sram_ctrl_env:     env
+
+  // the base class also looks up UVM_TEST_SEQ plusarg to create and run that seq in
+  // the run_phase; as such, nothing more needs to be done
+
+endclass : sram_ctrl_base_test
diff --git a/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test.core b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test.core
new file mode 100644
index 0000000..fec1d7a
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test.core
@@ -0,0 +1,19 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:sram_ctrl_test:0.1"
+description: "SRAM_CTRL DV UVM test"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:sram_ctrl_env
+    files:
+      - sram_ctrl_test_pkg.sv
+      - sram_ctrl_base_test.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test_pkg.sv b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test_pkg.sv
new file mode 100644
index 0000000..1209864
--- /dev/null
+++ b/hw/ip/sram_ctrl/dv/tests/sram_ctrl_test_pkg.sv
@@ -0,0 +1,22 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package sram_ctrl_test_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import cip_base_pkg::*;
+  import sram_ctrl_env_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // local types
+
+  // functions
+
+  // package sources
+  `include "sram_ctrl_base_test.sv"
+
+endpackage
diff --git a/util/reggen/fpv_csr.sv.tpl b/util/reggen/fpv_csr.sv.tpl
index 99f93bb..ec8aad5 100644
--- a/util/reggen/fpv_csr.sv.tpl
+++ b/util/reggen/fpv_csr.sv.tpl
@@ -41,6 +41,8 @@
   `define REGWEN_PATH dut.${block.hier_path}.u_reg
   % elif block.name == "flash_ctrl":
   `define REGWEN_PATH dut.u_flash_ctrl.u_reg
+  % elif block.name == "sram_ctrl":
+  `define REGWEN_PATH dut.u_sram_ctrl.u_reg
   % else:
   `define REGWEN_PATH dut.u_reg
   % endif