DMA DV framework

-Generated by uvmdvgen
-Implement smoke test only

Change-Id: I2a355cc75080e6340deb06c3667575014e7dca39
diff --git a/hw/top_matcha/ip/dma/data/dma_testplan.hjson b/hw/top_matcha/ip/dma/data/dma_testplan.hjson
index 7ba2ea0..d9610f9 100644
--- a/hw/top_matcha/ip/dma/data/dma_testplan.hjson
+++ b/hw/top_matcha/ip/dma/data/dma_testplan.hjson
@@ -27,7 +27,7 @@
             - Source and destination memory contain the same data.
 
             '''
-      stage: V0
+      stage: V1
       tests: ["dma_smoke"]
     }
     {
@@ -41,7 +41,7 @@
             - The data written by the writer matches the data written to the reader.
 
       '''
-      stage: V0
+      stage: V1
       tests: []
     }
     {
@@ -54,7 +54,7 @@
             Check
             - Read from the writer and confirm it's the same data fetched by the reader.
       '''
-      stage: V0
+      stage: V1
       tests: []
     }
     {
@@ -68,7 +68,7 @@
             Check
             - Confirm the address stays fixed and valid data is fetched to the reader.
       '''
-      stage: V0
+      stage: V1
       tests: []
     }
     {
@@ -82,7 +82,7 @@
             Check
             - Confirm the address stays fixed and valid data is written by the writer.
       '''
-      stage: V0
+      stage: V1
       tests: []
     }
   ]
diff --git a/hw/top_matcha/ip/dma/dv/README.md b/hw/top_matcha/ip/dma/dv/README.md
new file mode 100644
index 0000000..91b15e7
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/README.md
@@ -0,0 +1,22 @@
+**Copyright 2023 Google LLC.**
+
+1. Set up environment:
+
+In the root of the shodan repo:
+
+```
+export ROOTDIR=`pwd`
+export titan_root=$ROOTDIR/hw/opentitan-upstream
+export PYTHONPATH=$ROOTDIR/hw/matcha/util:$titan_root/util:$titan_root/util/dvsim:$PYTHONPATH
+```
+
+2. Sample command to run DMA tests
+
+```
+cd $ROOTDIR/hw/matcha
+./util/dvsim_matcha/dvsim.py ./hw/top_matcha/ip/dma/dv/dma_sim_cfg.hjson -i dma_smoke
+```
+
+3. Run in debug mode with `-v d` option
+
+4. Dump out waveform with `-w fsdb` option
\ No newline at end of file
diff --git a/hw/top_matcha/ip/dma/dv/dma_sim.core b/hw/top_matcha/ip/dma/dv/dma_sim.core
new file mode 100644
index 0000000..c091987
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/dma_sim.core
@@ -0,0 +1,31 @@
+CAPI=2:
+# Coyright 2023 Google LLC.
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:dma_sim:0.1"
+description: "DMA DV sim target"
+filesets:
+  files_rtl:
+    depend:
+      - google:ip:dma
+
+  files_dv:
+    depend:
+      - lowrisc:dv:dma_test
+      - lowrisc:dv:dma_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/top_matcha/ip/dma/dv/dma_sim_cfg.hjson b/hw/top_matcha/ip/dma/dv/dma_sim_cfg.hjson
new file mode 100644
index 0000000..0d3f389
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/dma_sim_cfg.hjson
@@ -0,0 +1,63 @@
+// Copyright 2023 Google LLC.
+// 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: dma
+
+  // Top level dut name (sv module).
+  dut: dma
+
+  // 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:dma_sim:0.1
+
+  // Testplan hjson file.
+  testplan: "{proj_root}/hw/top_matcha/ip/dma/data/dma_testplan.hjson"
+
+  // RAL spec - used to generate the RAL model.
+  ral_spec: "{proj_root}/hw/ip/dma/data/dma.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
+                "{titan_root}/hw/dv/tools/dvsim/tests/csr_tests.hjson",
+                "{titan_root}/hw/dv/tools/dvsim/tests/mem_tests.hjson",
+                "{titan_root}/hw/dv/tools/dvsim/tests/tl_access_tests.hjson"]
+
+  // Add additional tops for simulation.
+  sim_tops: ["dma_bind"]
+
+  // Default iterations for all tests - each test entry can override this.
+  reseed: 1
+
+  // Default UVM test and seq class name.
+  uvm_test: dma_base_test
+  uvm_test_seq: dma_base_vseq
+
+  // List of test specifications.
+  tests: [
+    {
+      name: dma_smoke
+      uvm_test_seq: dma_smoke_vseq
+    }
+
+    // TODO: add more tests here
+  ]
+
+  // List of regressions.
+  regressions: [
+    {
+      name: smoke
+      tests: ["dma_smoke"]
+    }
+  ]
+}
diff --git a/hw/top_matcha/ip/dma/dv/env/dma_env.core b/hw/top_matcha/ip/dma/dv/env/dma_env.core
new file mode 100644
index 0000000..e8b3f15
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_env.core
@@ -0,0 +1,38 @@
+CAPI=2:
+# Copyright 2023 Google LLC.
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:dma_env:0.1"
+description: "DMA DV UVM environment"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:ralgen_matcha
+      - lowrisc:dv:cip_lib
+    files:
+      - dma_env_pkg.sv
+      - dma_env_cfg.sv: {is_include_file: true}
+      - dma_env_cov.sv: {is_include_file: true}
+      - dma_virtual_sequencer.sv: {is_include_file: true}
+      - dma_scoreboard.sv: {is_include_file: true}
+      - dma_env.sv: {is_include_file: true}
+      - seq_lib/dma_vseq_list.sv: {is_include_file: true}
+      - seq_lib/dma_base_vseq.sv: {is_include_file: true}
+      - seq_lib/dma_common_vseq.sv: {is_include_file: true}
+      - seq_lib/dma_smoke_vseq.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+generate:
+  ral:
+    generator: ralgen
+    parameters:
+      name: dma
+      ip_hjson: ../../data/dma.hjson
+
+targets:
+  default:
+    filesets:
+      - files_dv
+    generate:
+      - ral
diff --git a/hw/top_matcha/ip/dma/dv/env/dma_env.sv b/hw/top_matcha/ip/dma/dv/env/dma_env.sv
new file mode 100644
index 0000000..146e5f9
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_env.sv
@@ -0,0 +1,55 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_env extends cip_base_env #(
+    .CFG_T              (dma_env_cfg),
+    .COV_T              (dma_env_cov),
+    .VIRTUAL_SEQUENCER_T(dma_virtual_sequencer),
+    .SCOREBOARD_T       (dma_scoreboard)
+  );
+  `uvm_component_utils(dma_env)
+
+  tl_agent m_reader_tl_agent;
+  tl_agent m_writer_tl_agent;
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    // create components
+    m_reader_tl_agent = tl_agent::type_id::create("m_reader_tl_agent", this);
+    uvm_config_db#(tl_agent_cfg)::set(this, "m_reader_tl_agent*", "cfg", cfg.m_reader_tl_agent_cfg);
+    cfg.m_reader_tl_agent_cfg.en_cov = cfg.en_cov;
+    // create components
+    m_writer_tl_agent = tl_agent::type_id::create("m_writer_tl_agent", this);
+    uvm_config_db#(tl_agent_cfg)::set(this, "m_writer_tl_agent*", "cfg", cfg.m_writer_tl_agent_cfg);
+    cfg.m_writer_tl_agent_cfg.en_cov = cfg.en_cov;
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+    if (cfg.en_scb) begin
+      m_reader_tl_agent.monitor.a_chan_port.connect(scoreboard.reader_tl_a_chan_fifo.analysis_export);
+      m_reader_tl_agent.monitor.d_chan_port.connect(scoreboard.reader_tl_d_chan_fifo.analysis_export);
+      m_writer_tl_agent.monitor.a_chan_port.connect(scoreboard.writer_tl_a_chan_fifo.analysis_export);
+      m_writer_tl_agent.monitor.d_chan_port.connect(scoreboard.writer_tl_d_chan_fifo.analysis_export);
+    end
+    if (cfg.is_active && cfg.m_reader_tl_agent_cfg.is_active) begin
+      virtual_sequencer.reader_tl_sequencer_h = m_reader_tl_agent.sequencer;
+    end
+    if (cfg.is_active && cfg.m_writer_tl_agent_cfg.is_active) begin
+      virtual_sequencer.writer_tl_sequencer_h = m_writer_tl_agent.sequencer;
+    end
+
+    // Connect the DUT's reader tl to the sequencer
+    m_reader_tl_agent.monitor.d_chan_port.connect(
+	    virtual_sequencer.reader_tl_rx_fifo.analysis_export);
+
+    // Connect the DUT's writer tl to the sequencer
+    m_writer_tl_agent.monitor.a_chan_port.connect(
+	    virtual_sequencer.writer_tl_rx_fifo.analysis_export);
+  endfunction
+
+endclass
diff --git a/hw/top_matcha/ip/dma/dv/env/dma_env_cfg.sv b/hw/top_matcha/ip/dma/dv/env/dma_env_cfg.sv
new file mode 100644
index 0000000..69ea0d6
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_env_cfg.sv
@@ -0,0 +1,53 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_env_cfg extends cip_base_env_cfg #(.RAL_T(dma_reg_block));
+
+  // ext component cfgs
+  rand tl_agent_cfg m_reader_tl_agent_cfg;
+  rand tl_agent_cfg m_writer_tl_agent_cfg;
+
+  `uvm_object_utils_begin(dma_env_cfg)
+    `uvm_field_object(m_reader_tl_agent_cfg, UVM_DEFAULT)
+    `uvm_field_object(m_writer_tl_agent_cfg, UVM_DEFAULT)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+  virtual function void initialize(bit [31:0] csr_base_addr = 32'h40200000);
+    super.initialize(csr_base_addr);
+    // create reader_tl agent config obj
+    m_reader_tl_agent_cfg = tl_agent_cfg::type_id::create("m_reader_tl_agent_cfg");
+    m_reader_tl_agent_cfg.if_mode = dv_utils_pkg::Device;
+    m_reader_tl_agent_cfg.is_active = 1;
+    m_reader_tl_agent_cfg.device_can_rsp_on_same_cycle = 1;
+    m_reader_tl_agent_cfg.a_valid_delay_min = 0;
+    m_reader_tl_agent_cfg.a_valid_delay_max = 0;
+    m_reader_tl_agent_cfg.d_valid_delay_min = 0;
+    m_reader_tl_agent_cfg.d_valid_delay_max = 0;
+    m_reader_tl_agent_cfg.a_ready_delay_min = 0;
+    m_reader_tl_agent_cfg.a_ready_delay_max = 0;
+    m_reader_tl_agent_cfg.d_ready_delay_min = 0;
+    m_reader_tl_agent_cfg.d_ready_delay_max = 0;
+
+    // create writer_tl agent config obj
+    m_writer_tl_agent_cfg = tl_agent_cfg::type_id::create("m_writer_tl_agent_cfg");
+    m_writer_tl_agent_cfg.if_mode = dv_utils_pkg::Device;
+    m_writer_tl_agent_cfg.is_active = 1;
+    m_writer_tl_agent_cfg.device_can_rsp_on_same_cycle = 1;
+    m_writer_tl_agent_cfg.a_valid_delay_min = 0;
+    m_writer_tl_agent_cfg.a_valid_delay_max = 0;
+    m_writer_tl_agent_cfg.d_valid_delay_min = 0;
+    m_writer_tl_agent_cfg.d_valid_delay_max = 0;
+    m_writer_tl_agent_cfg.a_ready_delay_min = 0;
+    m_writer_tl_agent_cfg.a_ready_delay_max = 0;
+    m_writer_tl_agent_cfg.d_ready_delay_min = 0;
+    m_writer_tl_agent_cfg.d_ready_delay_max = 0;
+
+    // only support 1 outstanding TL items in tlul_adapter
+    m_tl_agent_cfg.max_outstanding_req = 1;
+  endfunction
+
+endclass
diff --git a/hw/top_matcha/ip/dma/dv/env/dma_env_cov.sv b/hw/top_matcha/ip/dma/dv/env/dma_env_cov.sv
new file mode 100644
index 0000000..79064da
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_env_cov.sv
@@ -0,0 +1,33 @@
+// Copyright 2023 Google LLC.
+// 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 dma_env_cov extends cip_base_env_cov #(.CFG_T(dma_env_cfg));
+  `uvm_component_utils(dma_env_cov)
+
+  // the base class provides the following handles for use:
+  // dma_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/top_matcha/ip/dma/dv/env/dma_env_pkg.sv b/hw/top_matcha/ip/dma/dv/env/dma_env_pkg.sv
new file mode 100644
index 0000000..fecef3c
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_env_pkg.sv
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package dma_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 dv_base_reg_pkg::*;
+  import csr_utils_pkg::*;
+  import dma_ral_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // parameters
+
+  // types
+
+  // functions
+
+  // package sources
+  `include "dma_env_cfg.sv"
+  `include "dma_env_cov.sv"
+  `include "dma_virtual_sequencer.sv"
+  `include "dma_scoreboard.sv"
+  `include "dma_env.sv"
+  `include "dma_vseq_list.sv"
+
+endpackage
diff --git a/hw/top_matcha/ip/dma/dv/env/dma_scoreboard.sv b/hw/top_matcha/ip/dma/dv/env/dma_scoreboard.sv
new file mode 100644
index 0000000..cb52101
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_scoreboard.sv
@@ -0,0 +1,150 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_scoreboard extends cip_base_scoreboard #(
+    .CFG_T(dma_env_cfg),
+    .RAL_T(dma_reg_block),
+    .COV_T(dma_env_cov)
+  );
+  `uvm_component_utils(dma_scoreboard)
+
+  // local variables
+
+  // TLM agent fifos
+  uvm_tlm_analysis_fifo #(tl_seq_item) reader_tl_a_chan_fifo;
+  uvm_tlm_analysis_fifo #(tl_seq_item) reader_tl_d_chan_fifo;
+  uvm_tlm_analysis_fifo #(tl_seq_item) writer_tl_a_chan_fifo;
+  uvm_tlm_analysis_fifo #(tl_seq_item) writer_tl_d_chan_fifo;
+
+  // local queues to hold incoming packets pending comparison
+  tl_seq_item reader_tl_q[$];
+  tl_seq_item writer_tl_q[$];
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    reader_tl_a_chan_fifo = new("reader_tl_a_chan_fifo", this);
+    reader_tl_d_chan_fifo = new("reader_tl_d_chan_fifo", this);
+    writer_tl_a_chan_fifo = new("writer_tl_a_chan_fifo", this);
+    writer_tl_d_chan_fifo = new("writer_tl_d_chan_fifo", this);
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+  endfunction
+
+  task run_phase(uvm_phase phase);
+    super.run_phase(phase);
+    fork
+      process_reader_tl_a_chan_fifo();
+      process_reader_tl_d_chan_fifo();
+
+      process_writer_tl_a_chan_fifo();
+      process_writer_tl_d_chan_fifo();
+    join_none
+  endtask
+
+  virtual task process_reader_tl_a_chan_fifo();
+    tl_seq_item item;
+    forever begin
+      reader_tl_a_chan_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("received reader_tl a_chan item:\n%0s", item.sprint()), UVM_HIGH)
+    end
+  endtask
+
+  virtual task process_reader_tl_d_chan_fifo();
+    tl_seq_item item;
+    forever begin
+      reader_tl_d_chan_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("received reader_tl d_chan item:\n%0s", item.sprint()), UVM_HIGH)
+    end
+  endtask
+
+  virtual task process_writer_tl_a_chan_fifo();
+    tl_seq_item item;
+    forever begin
+      writer_tl_a_chan_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("received writer_tl a_chan_item:\n%0s", item.sprint()), UVM_HIGH)
+    end
+  endtask
+
+  virtual task process_writer_tl_d_chan_fifo();
+    tl_seq_item item;
+    forever begin
+      writer_tl_d_chan_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("received writer_tl d_chan_item:\n%0s", item.sprint()), UVM_HIGH)
+    end
+  endtask
+
+  virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
+    uvm_reg csr;
+    bit     do_read_check   = 1'b1;
+    bit     write           = item.is_write();
+    uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].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.ral_models[ral_name].csr_addrs}) begin
+      csr = cfg.ral_models[ral_name].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())
+      "ctrl",
+      "status",
+      "rdr_start_addr",
+      "rdr_length",
+      "rdr_line_cnt",
+      "rdr_stride",
+      "wtr_start_addr",
+      "wtr_length",
+      "wtr_line_cnt",
+      "wtr_stride",
+      "intr_state",
+      "intr_enable": 
+      begin
+      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/top_matcha/ip/dma/dv/env/dma_virtual_sequencer.sv b/hw/top_matcha/ip/dma/dv/env/dma_virtual_sequencer.sv
new file mode 100644
index 0000000..aedfad6
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/dma_virtual_sequencer.sv
@@ -0,0 +1,29 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_virtual_sequencer extends cip_base_virtual_sequencer #(
+    .CFG_T(dma_env_cfg),
+    .COV_T(dma_env_cov)
+  );
+  `uvm_component_utils(dma_virtual_sequencer)
+
+  tl_sequencer reader_tl_sequencer_h;
+  tl_sequencer writer_tl_sequencer_h;
+
+  // Grab packets from reader_tl_if
+  uvm_tlm_analysis_fifo #(tl_seq_item) reader_tl_rx_fifo;
+
+  // Grab packets from writer_tl_if
+  uvm_tlm_analysis_fifo #(tl_seq_item) writer_tl_rx_fifo;
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    reader_tl_rx_fifo = new("reader_tl_rx_fifo", this);
+    writer_tl_rx_fifo = new("writer_tl_rx_fifo", this);
+  endfunction
+
+endclass
diff --git a/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_base_vseq.sv b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_base_vseq.sv
new file mode 100644
index 0000000..54cd1ed
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_base_vseq.sv
@@ -0,0 +1,75 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_base_vseq extends cip_base_vseq #(
+    .RAL_T               (dma_reg_block),
+    .CFG_T               (dma_env_cfg),
+    .COV_T               (dma_env_cov),
+    .VIRTUAL_SEQUENCER_T (dma_virtual_sequencer)
+  );
+  `uvm_object_utils(dma_base_vseq)
+
+  // various knobs to enable certain routines
+  bit do_dma_init = 1'b1;
+
+  // Local queue for holding received writer data
+  byte writer_data_q[$];
+
+  `uvm_object_new
+
+  // Grab packets sent by the DUT over the writer tl
+  virtual task get_writer_data_items();
+    tl_seq_item item;
+
+    forever begin
+      p_sequencer.writer_tl_rx_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("Received data from DMA:\n%0h", item.a_data), UVM_HIGH)
+      writer_data_q.push_back(item.a_data & 32'h000000FF);
+    end
+  endtask
+
+  virtual task dut_init(string reset_kind = "HARD");
+    super.dut_init();
+    if (do_dma_init) dma_init();
+  endtask
+
+  virtual task dut_shutdown();
+    // check for pending dma operations and wait for them to complete
+    // TODO
+  endtask
+
+  // setup basic dma features
+  virtual task dma_init();
+  endtask
+
+  virtual task dma_setup(int read_addr, int write_addr, int line_width, int line_count, int line_stride);
+    ral.rdr_start_addr.set(read_addr);
+    csr_update(.csr(ral.rdr_start_addr));
+    ral.rdr_length.set(line_width);
+    csr_update(.csr(ral.rdr_length));
+    ral.rdr_line_cnt.set(line_count);
+    csr_update(.csr(ral.rdr_line_cnt));
+    ral.rdr_stride.set(line_stride);
+    csr_update(.csr(ral.rdr_stride));
+
+    ral.wtr_start_addr.set(write_addr);
+    csr_update(.csr(ral.wtr_start_addr));
+    ral.wtr_length.set(line_width);
+    csr_update(.csr(ral.wtr_length));
+    ral.wtr_line_cnt.set(line_count);
+    csr_update(.csr(ral.wtr_line_cnt));
+    ral.wtr_stride.set(line_stride);
+    csr_update(.csr(ral.wtr_stride));
+  endtask
+
+  virtual task dma_start();
+    ral.ctrl.rdr_start.set(1'b1);
+    ral.ctrl.wtr_start.set(1'b1);
+    ral.ctrl.rdr_sync_dis.set(1'b1);
+    ral.ctrl.wtr_sync_dis.set(1'b1);
+    csr_update(.csr(ral.ctrl));
+  endtask
+
+endclass : dma_base_vseq
diff --git a/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_common_vseq.sv b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_common_vseq.sv
new file mode 100644
index 0000000..294f5ec
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_common_vseq.sv
@@ -0,0 +1,18 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_common_vseq extends dma_base_vseq;
+  `uvm_object_utils(dma_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/top_matcha/ip/dma/dv/env/seq_lib/dma_smoke_vseq.sv b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_smoke_vseq.sv
new file mode 100644
index 0000000..fa43ae7
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_smoke_vseq.sv
@@ -0,0 +1,74 @@
+// Copyright 2023 Google LLC.
+// 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 dma_smoke_vseq extends dma_base_vseq;
+  `uvm_object_utils(dma_smoke_vseq)
+
+  `uvm_object_new
+
+  localparam uint DMA_READ_ADDR 	= 32'h10008000;
+  localparam uint DMA_WRITE_ADDR 	= 32'h50020000;
+  localparam uint DMA_LINE_WIDTH	= 1;
+  localparam uint DMA_DATA_SIZE 	= 1;
+  localparam uint DMA_LINE_STRIDE	= 0;
+
+  tl_device_seq reader_device_seq;
+  tl_device_seq writer_device_seq;
+
+  // A set of bytes expected to be received on writer tl
+  byte exp_writer_data_q[$];
+
+  task process_reader_request();
+    tl_seq_item item;
+
+    forever begin
+      p_sequencer.reader_tl_rx_fifo.get(item);
+
+      `uvm_info(`gfn, $sformatf("Sending data from reader_tl:\n%0h", item.d_data), UVM_HIGH)
+      exp_writer_data_q.push_back(item.d_data & 32'h000000FF);
+    end
+  endtask
+
+  task start_reader_device_seq();
+    // Create and start tl_device_seq on reader_tl
+    reader_device_seq = tl_device_seq#()::type_id::create("reader_device_seq");
+    reader_device_seq.start(p_sequencer.reader_tl_sequencer_h);
+
+  endtask
+
+  task start_writer_device_seq();
+    // Create and start tl_device_seq on writer_tl
+    writer_device_seq = tl_device_seq#()::type_id::create("writer_device_seq");
+    writer_device_seq.start(p_sequencer.writer_tl_sequencer_h);
+  endtask
+
+  virtual task body();
+    // super.body();
+
+    fork start_reader_device_seq(); join_none
+    fork start_writer_device_seq(); join_none
+
+    #1000
+    `uvm_info(`gfn, "Configuring DMA", UVM_HIGH)
+    dma_setup(DMA_READ_ADDR, DMA_WRITE_ADDR, DMA_LINE_WIDTH, DMA_DATA_SIZE, DMA_LINE_STRIDE);
+
+    #1000
+    `uvm_info(`gfn, "Starting DMA", UVM_HIGH)
+    dma_start();
+
+    // Spawn off a thread to handle reader tl
+    fork process_reader_request(); join_none
+
+    // Spawn off a thread to handle writer tl
+    fork get_writer_data_items(); join_none
+
+    #100000
+    foreach (exp_writer_data_q[i]) begin
+      `DV_CHECK_EQ(writer_data_q[i], exp_writer_data_q[i], $sformatf("index: %0d", i))
+    end
+  endtask : body
+
+endclass : dma_smoke_vseq
diff --git a/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_vseq_list.sv b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_vseq_list.sv
new file mode 100644
index 0000000..5f58bf0
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/env/seq_lib/dma_vseq_list.sv
@@ -0,0 +1,8 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "dma_base_vseq.sv"
+`include "dma_smoke_vseq.sv"
+`include "dma_common_vseq.sv"
diff --git a/hw/top_matcha/ip/dma/dv/sva/dma_bind.sv b/hw/top_matcha/ip/dma/dv/sva/dma_bind.sv
new file mode 100644
index 0000000..6365454
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/sva/dma_bind.sv
@@ -0,0 +1,24 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+module dma_bind;
+
+  bind dma tlul_assert #(
+    .EndpointType("Device")
+  ) tlul_assert_device (
+    .clk_i,
+    .rst_ni,
+    .h2d  (tl_d_i),
+    .d2h  (tl_d_o)
+  );
+
+  bind dma dma_csr_assert_fpv dma_csr_assert (
+    .clk_i,
+    .rst_ni,
+    .h2d    (tl_d_i),
+    .d2h    (tl_d_o)
+  );
+
+endmodule
diff --git a/hw/top_matcha/ip/dma/dv/sva/dma_sva.core b/hw/top_matcha/ip/dma/dv/sva/dma_sva.core
new file mode 100644
index 0000000..ae06a4f
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/sva/dma_sva.core
@@ -0,0 +1,38 @@
+CAPI=2:
+# Copyright 2023 Google LLC.
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:dma_sva:0.1"
+description: "DMA assertion modules and bind file."
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:tlul:headers
+      - lowrisc:fpv:csr_assert_gen
+    files:
+      - dma_bind.sv
+    file_type: systemVerilogSource
+
+  files_formal:
+    depend:
+      - lowrisc:ip:dma
+
+generate:
+  csr_assert_gen:
+    generator: csr_assert_gen
+    parameters:
+      spec: ../../data/dma.hjson
+
+targets:
+  default: &default_target
+    filesets:
+      - files_dv
+    generate:
+      - csr_assert_gen
+  formal:
+    <<: *default_target
+    filesets:
+      - files_formal
+      - files_dv
+    toplevel: dma
diff --git a/hw/top_matcha/ip/dma/dv/tb.sv b/hw/top_matcha/ip/dma/dv/tb.sv
new file mode 100644
index 0000000..5b870e4
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/tb.sv
@@ -0,0 +1,65 @@
+// Copyright 2023 Google LLC.
+// 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 dma_env_pkg::*;
+  import dma_test_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  wire clk, rst_n;
+  wire devmode;
+
+  // interfaces
+  clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
+  pins_if #(1) devmode_if(devmode);
+  tl_if tl_if(.clk(clk), .rst_n(rst_n));
+  tl_if reader_tl_if(.clk(clk), .rst_n(rst_n));
+  tl_if writer_tl_if(.clk(clk), .rst_n(rst_n));
+
+  logic intr_reader_done;
+  logic intr_writer_done;
+
+  // dut
+  dma dut (
+    .clk_i                (clk      ),
+    .rst_ni               (rst_n    ),
+
+    .reader_tl_h_o	  (reader_tl_if.h2d),
+    .reader_tl_h_i	  (reader_tl_if.d2h),
+
+    .writer_tl_h_o	  (writer_tl_if.h2d),
+    .writer_tl_h_i	  (writer_tl_if.d2h),
+
+    .tl_d_i               (tl_if.h2d),
+    .tl_d_o               (tl_if.d2h),
+
+    .intr_reader_done_o   (intr_reader_done),
+    .intr_writer_done_o   (intr_writer_done)
+    );
+
+  initial begin
+    // drive clk and rst_n from clk_if
+    clk_rst_if.set_active();
+    uvm_config_db#(virtual clk_rst_if)::set(null, "*.env", "clk_rst_vif", clk_rst_if);
+    uvm_config_db#(devmode_vif)::set(null, "*.env", "devmode_vif", devmode_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_reader_tl_agent*", "vif", reader_tl_if);
+    uvm_config_db#(virtual tl_if)::set(null, "*.env.m_writer_tl_agent*", "vif", writer_tl_if);
+    $timeformat(-12, 0, " ps", 12);
+    run_test();
+  end
+
+  initial
+  begin
+    $assertoff(0, tb.dut);
+  end
+
+endmodule
diff --git a/hw/top_matcha/ip/dma/dv/tests/dma_base_test.sv b/hw/top_matcha/ip/dma/dv/tests/dma_base_test.sv
new file mode 100644
index 0000000..90d1f6c
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/tests/dma_base_test.sv
@@ -0,0 +1,27 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class dma_base_test extends cip_base_test #(
+    .CFG_T(dma_env_cfg),
+    .ENV_T(dma_env)
+  );
+
+  `uvm_component_utils(dma_base_test)
+  `uvm_component_new
+
+  // the base class dv_base_test creates the following instances:
+  // dma_env_cfg: cfg
+  // dma_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
+
+  virtual function void build_phase(uvm_phase phase);
+    max_quit_count  = 50;
+    test_timeout_ns = 600_000_000; // 600ms
+    super.build_phase(phase);
+  endfunction : build_phase
+
+endclass : dma_base_test
diff --git a/hw/top_matcha/ip/dma/dv/tests/dma_test.core b/hw/top_matcha/ip/dma/dv/tests/dma_test.core
new file mode 100644
index 0000000..309bb5d
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/tests/dma_test.core
@@ -0,0 +1,20 @@
+CAPI=2:
+# Copyright 2023 Google LLC.
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:dma_test:0.1"
+description: "DMA DV UVM test"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:dma_env
+    files:
+      - dma_test_pkg.sv
+      - dma_base_test.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/top_matcha/ip/dma/dv/tests/dma_test_pkg.sv b/hw/top_matcha/ip/dma/dv/tests/dma_test_pkg.sv
new file mode 100644
index 0000000..f4e0f2d
--- /dev/null
+++ b/hw/top_matcha/ip/dma/dv/tests/dma_test_pkg.sv
@@ -0,0 +1,23 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package dma_test_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import cip_base_pkg::*;
+  import dma_env_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // local types
+
+  // functions
+
+  // package sources
+  `include "dma_base_test.sv"
+
+endpackage