Add baseline DV for I2C
diff --git a/hw/dv/sv/i2c_agent/README.md b/hw/dv/sv/i2c_agent/README.md
new file mode 100644
index 0000000..ca68b60
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/README.md
@@ -0,0 +1,3 @@
+{{% lowrisc-doc-hdr I2C DV UVM Agent }}
+
+I2C DV UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/i2c_agent/i2c_agent.core b/hw/dv/sv/i2c_agent/i2c_agent.core
new file mode 100644
index 0000000..48c6fa0
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_agent.core
@@ -0,0 +1,28 @@
+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:i2c_agent:0.1"
+description: "I2C DV UVM agent"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_utils
+      - lowrisc:dv:dv_lib
+    files:
+      - i2c_if.sv
+      - i2c_agent_pkg.sv
+      - i2c_agent_cfg.sv: {is_include_file: true}
+      - i2c_agent_cov.sv: {is_include_file: true}
+      - i2c_item.sv: {is_include_file: true}
+      - i2c_driver.sv: {is_include_file: true}
+      - i2c_monitor.sv: {is_include_file: true}
+      - i2c_agent.sv: {is_include_file: true}
+      - seq_lib/i2c_base_seq.sv: {is_include_file: true}
+      - seq_lib/i2c_seq_list.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/dv/sv/i2c_agent/i2c_agent.sv b/hw/dv/sv/i2c_agent/i2c_agent.sv
new file mode 100644
index 0000000..6148e9d
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_agent.sv
@@ -0,0 +1,24 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_agent extends dv_base_agent #(
+      .CFG_T          (i2c_agent_cfg),
+      .DRIVER_T       (i2c_driver),
+      .SEQUENCER_T    (i2c_sequencer),
+      .MONITOR_T      (i2c_monitor),
+      .COV_T          (i2c_agent_cov)
+  );
+
+  `uvm_component_utils(i2c_agent)
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    // get i2c_if handle
+    if (!uvm_config_db#(virtual i2c_if)::get(this, "", "vif", cfg.vif))
+      `uvm_fatal(`gfn, "failed to get i2c_if handle from uvm_config_db")
+  endfunction
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv b/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv
new file mode 100644
index 0000000..162bd2d
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_agent_cfg.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
+
+class i2c_agent_cfg extends dv_base_agent_cfg;
+
+// interface handle used by driver, monitor & the sequencer, via cfg handle
+  virtual i2c_if vif;
+
+  `uvm_object_utils_begin(i2c_agent_cfg)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_agent_cov.sv b/hw/dv/sv/i2c_agent/i2c_agent_cov.sv
new file mode 100644
index 0000000..36a58f2
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_agent_cov.sv
@@ -0,0 +1,18 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_agent_cov extends dv_base_agent_cov #(i2c_agent_cfg);
+  `uvm_component_utils(i2c_agent_cov)
+
+  // the base class provides the following handles for use:
+  // i2c_agent_cfg: cfg
+
+  // covergroups
+
+  function new(string name, uvm_component parent);
+    super.new(name, parent);
+    // instantiate all covergroups here
+  endfunction : new
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv b/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
new file mode 100644
index 0000000..52188d0
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package i2c_agent_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import dv_utils_pkg::*;
+  import dv_lib_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // parameters
+
+  // local types
+  // forward declare classes to allow typedefs below
+  typedef class i2c_item;
+  typedef class i2c_agent_cfg;
+
+  // reuse dv_base_seqeuencer as is with the right parameter set
+  typedef dv_base_sequencer #(.ITEM_T     (i2c_item),
+                              .CFG_T      (i2c_agent_cfg)) i2c_sequencer;
+
+  // functions
+
+  // package sources
+  `include "i2c_item.sv"
+  `include "i2c_agent_cfg.sv"
+  `include "i2c_agent_cov.sv"
+  `include "i2c_driver.sv"
+  `include "i2c_monitor.sv"
+  `include "i2c_agent.sv"
+  `include "i2c_seq_list.sv"
+
+  endpackage: i2c_agent_pkg
diff --git a/hw/dv/sv/i2c_agent/i2c_device_driver.sv b/hw/dv/sv/i2c_agent/i2c_device_driver.sv
new file mode 100644
index 0000000..b13d19e
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_device_driver.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
+
+class i2c_device_driver extends i2c_driver;
+  `uvm_component_utils(i2c_device_driver)
+
+  // the base class provides the following handles for use:
+  // i2c_agent_cfg: cfg
+
+  `uvm_component_new
+
+  virtual task run_phase(uvm_phase phase);
+    // base class forks off reset_signals() and get_and_drive() tasks
+    super.run_phase(phase);
+  endtask
+
+  // reset signals
+  virtual task reset_signals();
+  endtask
+
+  // drive trans received from sequencer
+  virtual task get_and_drive();
+  endtask
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_driver.sv b/hw/dv/sv/i2c_agent/i2c_driver.sv
new file mode 100644
index 0000000..a85b4d2
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_driver.sv
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_driver extends dv_base_driver #(i2c_item, i2c_agent_cfg);
+  `uvm_component_utils(i2c_driver)
+
+  // the base class provides the following handles for use:
+  // i2c_agent_cfg: cfg
+
+  `uvm_component_new
+
+  virtual task run_phase(uvm_phase phase);
+    // base class forks off reset_signals() and get_and_drive() tasks
+    super.run_phase(phase);
+  endtask
+
+  // reset signals
+  virtual task reset_signals();
+  endtask
+
+  // drive trans received from sequencer
+  virtual task get_and_drive();
+    forever begin
+      seq_item_port.get_next_item(req);
+      $cast(rsp, req.clone());
+      rsp.set_id_info(req);
+      `uvm_info(`gfn, $sformatf("rcvd item:\n%0s", req.sprint()), UVM_HIGH)
+      // TODO: do the driving part
+      //
+      // send rsp back to seq
+      `uvm_info(`gfn, "item sent", UVM_HIGH)
+      seq_item_port.item_done(rsp);
+    end
+  endtask
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_host_driver.sv b/hw/dv/sv/i2c_agent/i2c_host_driver.sv
new file mode 100644
index 0000000..9b82dd3
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_host_driver.sv
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_host_driver extends i2c_driver;
+  `uvm_component_utils(i2c_host_driver)
+
+  // the base class provides the following handles for use:
+  // i2c_agent_cfg: cfg
+
+  `uvm_component_new
+
+  virtual task run_phase(uvm_phase phase);
+    // base class forks off reset_signals() and get_and_drive() tasks
+    super.run_phase(phase);
+  endtask
+
+  // reset signals
+  virtual task reset_signals();
+  endtask
+
+  // drive trans received from sequencer
+  virtual task get_and_drive();
+    forever begin
+      seq_item_port.get_next_item(req);
+      $cast(rsp, req.clone());
+      rsp.set_id_info(req);
+      `uvm_info(`gfn, $sformatf("rcvd item:\n%0s", req.sprint()), UVM_HIGH)
+      // TODO: do the driving part
+
+      // send rsp back to seq
+      `uvm_info(`gfn, "item sent", UVM_HIGH)
+      seq_item_port.item_done(rsp);
+    end
+  endtask
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_if.sv b/hw/dv/sv/i2c_agent/i2c_if.sv
new file mode 100644
index 0000000..60c846a
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_if.sv
@@ -0,0 +1,87 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+interface i2c_if ();
+  // interface pins
+  logic scl_i;
+  logic scl_o;
+  logic scl_en_o;
+  logic sda_i;
+  logic sda_o;
+  logic sda_en_o;
+
+  bit BusIsFree=1;
+
+/*
+  // debug signals (TBD)
+  int            SclFrequency;
+  int            SclClockPeriod;
+  int            SclMinFreq;
+  int            SclMaxFreq;
+  bus_speed_e    BusSpeed;
+
+  //Timing0
+  int            SclHighMinHoldTime;
+  int            SclLowMinHoldTime;
+  //Timing1
+  int            NormRiseTime;
+  int            NormFallTime;
+  //Timing2
+  int            StartMinSetupTime;
+  int            StartMinHoldTime;
+  //Timing3
+  int            DataMinSetupTime;
+  int            DataMinHoldTime;
+  //Timing4
+  int            StopMinSetupTime;
+  int            StartStopMinTime;
+  //Timing5
+  int            ClockStretchTimeout;
+  int            ClockStretchEnable;
+
+  function void set_set_bus_frequency(int SclFrequency_i);
+    SclFrequency = SclFrequency_i; //in kHz
+  if ( SclFrequency>0 && SclFrequency<=100 ) begin
+    bus_speed_e = I2cStdSpeed;
+    set_std_speed_timing();
+  end else if (m_sclFrequency>100 && m_sclFrequency<=400) begin
+    bus_speed_e = I2cFastSpeed;
+    set_fast_speed_timing();
+  end else if (m_sclFrequency>400 && m_sclFrequency<=3400) begin
+    bus_speed_e = I2cFastSpeedPlus;
+    set_fast_speed_plus_timing();
+  end else
+    `uvm_fatal("I2C:set_set_bus_frequency",
+               $psprintf("SCL frquency setting illegal : %d kHz",SclFrequency))
+
+  //Create a SCL with 1:1 duty cycle
+  m_sclLowTime     = ( 10 ** 6/(2 * SclFrequency) ) ;   // ns
+  m_sclHighTime    = m_sclLowTime;                      // ns
+  //Default bit set-up time is m_sclLowTime/2
+  m_sdaChangePoint = m_sclLowTime/2;                    // ns
+
+  m_sclClockPeriod = ( (10**9)/m_sclFrequency) / 1000;  // ns
+
+  endfunction
+
+ always @(negedge sda_in )  begin
+  //START condition
+  if (rst==0)
+   if (scl_in==1) begin
+    #m_fHdStaMin;
+    busIsFree<=0;
+   end
+ end
+ always @(posedge sda_in) begin
+  //STOP condition
+  if (rst==0) begin
+   if (scl_in==1) begin
+    #m_tBufMin;
+    busIsFree<=1;
+   end
+  end
+ end
+*/
+
+endinterface : i2c_if
diff --git a/hw/dv/sv/i2c_agent/i2c_item.sv b/hw/dv/sv/i2c_agent/i2c_item.sv
new file mode 100644
index 0000000..97153b4
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_item.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 i2c_item extends uvm_sequence_item;
+
+  // random variables
+
+  `uvm_object_utils_begin(i2c_item)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/i2c_monitor.sv b/hw/dv/sv/i2c_agent/i2c_monitor.sv
new file mode 100644
index 0000000..8c6d30d
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/i2c_monitor.sv
@@ -0,0 +1,43 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_monitor extends dv_base_monitor #(
+    .ITEM_T (i2c_item),
+    .CFG_T  (i2c_agent_cfg),
+    .COV_T  (i2c_agent_cov)
+  );
+  `uvm_component_utils(i2c_monitor)
+
+  // the base class provides the following handles for use:
+  // i2c_agent_cfg: cfg
+  // i2c_agent_cov: cov
+  // uvm_analysis_port #(i2c_item): analysis_port
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+  endfunction
+
+  task run_phase(uvm_phase phase);
+    super.run_phase(phase);
+  endtask
+
+  // collect transactions forever - already forked in dv_base_moditor::run_phase
+  virtual protected task collect_trans(uvm_phase phase);
+    forever begin
+      // TODO: detect event
+
+      // TODO: sample the interface
+
+      // TODO: sample the covergroups
+
+      // TODO: write trans to analysis_port
+
+      // TODO: remove the line below: it is added to prevent zero delay loop in template code
+      #1us;
+    end
+  endtask
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv
new file mode 100644
index 0000000..458d0ee
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.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 i2c_base_seq extends dv_base_seq #(
+    .CFG_T       (i2c_agent_cfg),
+    .SEQUENCER_T (i2c_sequencer)
+  );
+  `uvm_object_utils(i2c_base_seq)
+
+  `uvm_object_new
+
+  virtual task body();
+    `uvm_fatal(`gtn, "Need to override this when you extend from this class!")
+  endtask
+
+endclass
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv
new file mode 100644
index 0000000..89f7fe7
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv
@@ -0,0 +1,5 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "i2c_base_seq.sv"
diff --git a/hw/ip/i2c/dv/Makefile b/hw/ip/i2c/dv/Makefile
new file mode 100644
index 0000000..f12564f
--- /dev/null
+++ b/hw/ip/i2c/dv/Makefile
@@ -0,0 +1,80 @@
+####################################################################################################
+## Copyright lowRISC contributors.                                                                ##
+## Licensed under the Apache License, Version 2.0, see LICENSE for details.                       ##
+## SPDX-License-Identifier: Apache-2.0                                                            ##
+####################################################################################################
+## Entry point test Makefile forr building and running tests.                                     ##
+## These are generic set of option groups that apply to all testbenches.                          ##
+## This flow requires the following options to be set:                                            ##
+## DV_DIR       - current dv directory that contains the test Makefile                            ##
+## DUT_TOP      - top level dut module name                                                       ##
+## TB_TOP       - top level tb module name                                                        ##
+## DOTF         - .f file used for compilation                                                    ##
+## COMPILE_KEY  - compile option set                                                              ##
+## TEST_NAME    - name of the test to run - this is supplied on the command line                  ##
+####################################################################################################
+DV_DIR          := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+export DUT_TOP  := i2c
+export TB_TOP   := tb
+FUSESOC_CORE    := lowrisc:dv:i2c_sim:0.1
+COMPILE_KEY     ?= default
+
+UVM_TEST        ?= i2c_base_test
+UVM_TEST_SEQ    ?= i2c_base_vseq
+
+####################################################################################################
+##                     A D D    I N D I V I D U A L    T E S T S    B E L O W                     ##
+####################################################################################################
+TEST_NAME       ?= i2c_wrap
+UVM_TEST        ?= i2c_base_test
+UVM_TEST_SEQ    ?= i2c_base_vseq
+
+ifeq (${TEST_NAME},i2c_wrap)
+  UVM_TEST_SEQ   = i2c_wrap_vseq
+endif
+
+ifeq (${TEST_NAME},i2c_sanity)
+  UVM_TEST_SEQ   = i2c_sanity_vseq
+endif
+
+ifeq (${TEST_NAME},i2c_intr_test)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +run_intr_test
+endif
+
+ifeq (${TEST_NAME},i2c_csr_hw_reset)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +csr_hw_reset
+  RUN_OPTS      += +en_scb=0
+endif
+
+ifeq (${TEST_NAME},i2c_csr_rw)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +csr_rw
+  RUN_OPTS      += +en_scb=0
+endif
+
+ifeq (${TEST_NAME},i2c_csr_bit_bash)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +csr_bit_bash
+  RUN_OPTS      += +en_scb=0
+endif
+
+ifeq (${TEST_NAME},i2c_csr_aliasing)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +csr_aliasing
+  RUN_OPTS      += +en_scb=0
+endif
+
+# TODO: remove this test if there are no memories in the DUT
+  ifeq (${TEST_NAME},i2c_mem_walk)
+  UVM_TEST_SEQ   = i2c_common_vseq
+  RUN_OPTS      += +csr_mem_walk
+  RUN_OPTS      += +en_scb=0
+endif
+
+####################################################################################################
+## Include the tool Makefile below                                                                ##
+## Dont add anything else below it!                                                               ##
+####################################################################################################
+include ${DV_DIR}/../../../dv/tools/Makefile
diff --git a/hw/ip/i2c/dv/README b/hw/ip/i2c/dv/README
new file mode 100755
index 0000000..4151195
--- /dev/null
+++ b/hw/ip/i2c/dv/README
@@ -0,0 +1,21 @@
+# UART IP Verification Environment
+UART IP verification environment is extended from the [Comportable IP
+verification environment](/hw/dv/sv/cip_lib/README.md) to leverage a lot of common
+code shared across all IPs that adhere to the
+[Comportable IP](/doc/rm/ComportabilitySpecification.md) specification.
+
+## Environment
+
+### Block Diagram
+
+## Key fetures verified
+
+## Testplan
+
+## How to run simulation
+Please run the following command to build and run tests:
+`make TEST_NAME=<test-name>`
+
+Please see adjoining Makefile file for list of available tests to run. Please
+see `hw/dv/tools/README.md` for additional details on options that can be passed
+(such as enabling waves, running with specific seed etc.).
diff --git a/hw/ip/i2c/dv/env/i2c_env.core b/hw/ip/i2c/dv/env/i2c_env.core
new file mode 100644
index 0000000..97357ef
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_env.core
@@ -0,0 +1,26 @@
+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:i2c_env:0.1"
+description: "I2C DV UVM environment"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:cip_lib
+      - lowrisc:dv:i2c_agent
+    files:
+      - i2c_env_pkg.sv
+      - i2c_env_cfg.sv: {is_include_file: true}
+      - i2c_env_cov.sv: {is_include_file: true}
+      - i2c_env.sv: {is_include_file: true}
+      - i2c_reg_block.sv: {is_include_file: true}
+      - i2c_virtual_sequencer.sv: {is_include_file: true}
+      - i2c_scoreboard.sv: {is_include_file: true}
+      - seq_lib/i2c_vseq_list.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/ip/i2c/dv/env/i2c_env.sv b/hw/ip/i2c/dv/env/i2c_env.sv
new file mode 100644
index 0000000..84c8048
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_env.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 i2c_env extends cip_base_env #(
+    .CFG_T              (i2c_env_cfg),
+    .COV_T              (i2c_env_cov),
+    .VIRTUAL_SEQUENCER_T(i2c_virtual_sequencer),
+    .SCOREBOARD_T       (i2c_scoreboard)
+  );
+  `uvm_component_utils(i2c_env)
+
+  i2c_agent m_i2c_agent;
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    m_i2c_agent = i2c_agent::type_id::create("m_i2c_agent", this);
+    uvm_config_db#(i2c_agent_cfg)::set(this, "m_i2c_agent*", "cfg", cfg.m_i2c_agent_cfg);
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+    if (cfg.en_scb) begin
+      m_i2c_agent.monitor.analysis_port.connect(scoreboard.i2c_fifo.analysis_export);
+    end
+    if (cfg.is_active && cfg.m_i2c_agent_cfg.is_active) begin
+      virtual_sequencer.i2c_sequencer_h = m_i2c_agent.sequencer;
+    end
+  endfunction
+
+endclass
diff --git a/hw/ip/i2c/dv/env/i2c_env_cfg.sv b/hw/ip/i2c/dv/env/i2c_env_cfg.sv
new file mode 100644
index 0000000..499c34b
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_env_cfg.sv
@@ -0,0 +1,27 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_env_cfg extends cip_base_env_cfg #(.RAL_T(i2c_reg_block));
+
+  // ext component cfgs
+  rand i2c_agent_cfg m_i2c_agent_cfg;
+
+  `uvm_object_utils_begin(i2c_env_cfg)
+    `uvm_field_object(m_i2c_agent_cfg, UVM_DEFAULT)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+  virtual function void initialize(bit [TL_AW-1:0] csr_base_addr = '1,
+                                   bit [TL_AW-1:0] csr_addr_map_size = 2048);
+    super.initialize();
+    // create i2c agent config obj
+    m_i2c_agent_cfg = i2c_agent_cfg::type_id::create("m_i2c_agent_cfg");
+
+    // set num_interrupts & num_alerts which will be used to create coverage and more
+    num_interrupts = ral.intr_state.get_n_used_bits();
+    num_alerts = 0;
+  endfunction
+
+endclass
diff --git a/hw/ip/i2c/dv/env/i2c_env_cov.sv b/hw/ip/i2c/dv/env/i2c_env_cov.sv
new file mode 100644
index 0000000..d080b14
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_env_cov.sv
@@ -0,0 +1,18 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_env_cov extends cip_base_env_cov #(.CFG_T(i2c_env_cfg));
+  `uvm_component_utils(i2c_env_cov)
+
+  // the base class provides the following handles for use:
+  // i2c_env_cfg: cfg
+
+  // covergroups
+
+  function new(string name, uvm_component parent);
+    super.new(name, parent);
+    // instantiate all covergroups here
+  endfunction : new
+
+endclass
diff --git a/hw/ip/i2c/dv/env/i2c_env_pkg.sv b/hw/ip/i2c/dv/env/i2c_env_pkg.sv
new file mode 100644
index 0000000..a82ce53
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_env_pkg.sv
@@ -0,0 +1,49 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package i2c_env_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import top_pkg::*;
+  import dv_utils_pkg::*;
+  import csr_utils_pkg::*;
+  import tl_agent_pkg::*;
+  import i2c_agent_pkg::*;
+  import dv_lib_pkg::*;
+  import cip_base_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // parameters
+  typedef enum int {
+    FmtWatermark   = 0,
+    RxWatermark    = 1,
+    FmtOverflow    = 2,
+    RxOverflow     = 3,
+    Nak            = 4,
+    SclInference   = 5,
+    SdaInference   = 6,
+    StretchTimeout = 7,
+    SdaUnstable    = 8,
+    NumI2cIntr     = 9
+  } i2c_intr_e;
+
+  // local types
+  parameter uint I2C_FMT_FIFO_DEPTH = 32;
+  parameter uint I2C_RX_FIFO_DEPTH  = 32;
+  
+  // functions
+
+  // package sources
+  `include "i2c_reg_block.sv"
+  `include "i2c_env_cfg.sv"
+  `include "i2c_env_cov.sv"
+  `include "i2c_virtual_sequencer.sv"
+  `include "i2c_scoreboard.sv"
+  `include "i2c_env.sv"
+  `include "i2c_vseq_list.sv"
+
+endpackage
diff --git a/hw/ip/i2c/dv/env/i2c_reg_block.sv b/hw/ip/i2c/dv/env/i2c_reg_block.sv
new file mode 100644
index 0000000..dc895fe
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_reg_block.sv
@@ -0,0 +1,957 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// UVM Registers auto-generated by `reggen` containing data structure
+// Do Not Edit directly
+
+// Forward declare all register/memory/block classes
+typedef class i2c_reg_intr_state;
+typedef class i2c_reg_intr_enable;
+typedef class i2c_reg_intr_test;
+typedef class i2c_reg_ctrl;
+typedef class i2c_reg_rdata;
+typedef class i2c_reg_fdata;
+typedef class i2c_reg_timing0;
+typedef class i2c_reg_timing1;
+typedef class i2c_reg_timing2;
+typedef class i2c_reg_timing3;
+typedef class i2c_reg_timing4;
+typedef class i2c_reg_timeout_ctrl;
+typedef class i2c_reg_block;
+
+// Block: i2c
+// Class: i2c_reg_intr_state
+class i2c_reg_intr_state extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field intr_fmt_watermark_o;
+  rand dv_base_reg_field intr_rx_watermark_o;
+  rand dv_base_reg_field intr_fmt_overflow_o;
+  rand dv_base_reg_field intr_rx_overflow_o;
+  rand dv_base_reg_field intr_nak_o;
+  rand dv_base_reg_field intr_scl_interference_o;
+  rand dv_base_reg_field intr_sda_interference_o;
+  rand dv_base_reg_field intr_stretch_timeout_o;
+  rand dv_base_reg_field intr_sda_unstable_o;
+
+  `uvm_object_utils(i2c_reg_intr_state)
+
+  function new(string       name = "i2c_reg_intr_state",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    intr_fmt_watermark_o = dv_base_reg_field::type_id::create("intr_fmt_watermark_o");
+    intr_fmt_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_watermark_o = dv_base_reg_field::type_id::create("intr_rx_watermark_o");
+    intr_rx_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_fmt_overflow_o = dv_base_reg_field::type_id::create("intr_fmt_overflow_o");
+    intr_fmt_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_overflow_o = dv_base_reg_field::type_id::create("intr_rx_overflow_o");
+    intr_rx_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_nak_o = dv_base_reg_field::type_id::create("intr_nak_o");
+    intr_nak_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(4),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_scl_interference_o = dv_base_reg_field::type_id::create("intr_scl_interference_o");
+    intr_scl_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(5),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_interference_o = dv_base_reg_field::type_id::create("intr_sda_interference_o");
+    intr_sda_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(6),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_stretch_timeout_o = dv_base_reg_field::type_id::create("intr_stretch_timeout_o");
+    intr_stretch_timeout_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(7),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_unstable_o = dv_base_reg_field::type_id::create("intr_sda_unstable_o");
+    intr_sda_unstable_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(8),
+      .access("W1C"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_intr_state
+
+// Class: i2c_reg_intr_enable
+class i2c_reg_intr_enable extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field intr_fmt_watermark_o;
+  rand dv_base_reg_field intr_rx_watermark_o;
+  rand dv_base_reg_field intr_fmt_overflow_o;
+  rand dv_base_reg_field intr_rx_overflow_o;
+  rand dv_base_reg_field intr_nak_o;
+  rand dv_base_reg_field intr_scl_interference_o;
+  rand dv_base_reg_field intr_sda_interference_o;
+  rand dv_base_reg_field intr_stretch_timeout_o;
+  rand dv_base_reg_field intr_sda_unstable_o;
+
+  `uvm_object_utils(i2c_reg_intr_enable)
+
+  function new(string       name = "i2c_reg_intr_enable",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    intr_fmt_watermark_o = dv_base_reg_field::type_id::create("intr_fmt_watermark_o");
+    intr_fmt_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_watermark_o = dv_base_reg_field::type_id::create("intr_rx_watermark_o");
+    intr_rx_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_fmt_overflow_o = dv_base_reg_field::type_id::create("intr_fmt_overflow_o");
+    intr_fmt_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_overflow_o = dv_base_reg_field::type_id::create("intr_rx_overflow_o");
+    intr_rx_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_nak_o = dv_base_reg_field::type_id::create("intr_nak_o");
+    intr_nak_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(4),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_scl_interference_o = dv_base_reg_field::type_id::create("intr_scl_interference_o");
+    intr_scl_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(5),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_interference_o = dv_base_reg_field::type_id::create("intr_sda_interference_o");
+    intr_sda_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(6),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_stretch_timeout_o = dv_base_reg_field::type_id::create("intr_stretch_timeout_o");
+    intr_stretch_timeout_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(7),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_unstable_o = dv_base_reg_field::type_id::create("intr_sda_unstable_o");
+    intr_sda_unstable_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(8),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_intr_enable
+
+// Class: i2c_reg_intr_test
+class i2c_reg_intr_test extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field intr_fmt_watermark_o;
+  rand dv_base_reg_field intr_rx_watermark_o;
+  rand dv_base_reg_field intr_fmt_overflow_o;
+  rand dv_base_reg_field intr_rx_overflow_o;
+  rand dv_base_reg_field intr_nak_o;
+  rand dv_base_reg_field intr_scl_interference_o;
+  rand dv_base_reg_field intr_sda_interference_o;
+  rand dv_base_reg_field intr_stretch_timeout_o;
+  rand dv_base_reg_field intr_sda_unstable_o;
+
+  `uvm_object_utils(i2c_reg_intr_test)
+
+  function new(string       name = "i2c_reg_intr_test",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    intr_fmt_watermark_o = dv_base_reg_field::type_id::create("intr_fmt_watermark_o");
+    intr_fmt_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_watermark_o = dv_base_reg_field::type_id::create("intr_rx_watermark_o");
+    intr_rx_watermark_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_fmt_overflow_o = dv_base_reg_field::type_id::create("intr_fmt_overflow_o");
+    intr_fmt_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_rx_overflow_o = dv_base_reg_field::type_id::create("intr_rx_overflow_o");
+    intr_rx_overflow_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_nak_o = dv_base_reg_field::type_id::create("intr_nak_o");
+    intr_nak_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(4),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_scl_interference_o = dv_base_reg_field::type_id::create("intr_scl_interference_o");
+    intr_scl_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(5),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_interference_o = dv_base_reg_field::type_id::create("intr_sda_interference_o");
+    intr_sda_interference_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(6),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_stretch_timeout_o = dv_base_reg_field::type_id::create("intr_stretch_timeout_o");
+    intr_stretch_timeout_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(7),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    intr_sda_unstable_o = dv_base_reg_field::type_id::create("intr_sda_unstable_o");
+    intr_sda_unstable_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(8),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_intr_test
+
+// Class: i2c_reg_ctrl
+class i2c_reg_ctrl extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field override;
+  rand dv_base_reg_field scl_o;
+  rand dv_base_reg_field sda_o;
+  rand dv_base_reg_field scl_i;
+  rand dv_base_reg_field sda_i;
+
+  `uvm_object_utils(i2c_reg_ctrl)
+
+  function new(string       name = "i2c_reg_ctrl",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    override = dv_base_reg_field::type_id::create("override");
+    override.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    scl_o = dv_base_reg_field::type_id::create("scl_o");
+    scl_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    sda_o = dv_base_reg_field::type_id::create("sda_o");
+    sda_o.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    scl_i = dv_base_reg_field::type_id::create("scl_i");
+    scl_i.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("RO"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    sda_i = dv_base_reg_field::type_id::create("sda_i");
+    sda_i.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(4),
+      .access("RO"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_ctrl
+
+// Class: i2c_reg_rdata
+class i2c_reg_rdata extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field data;
+
+  `uvm_object_utils(i2c_reg_rdata)
+
+  function new(string       name = "i2c_reg_rdata",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    data = dv_base_reg_field::type_id::create("data");
+    data.configure(
+      .parent(this),
+      .size(8),
+      .lsb_pos(0),
+      .access("RO"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_rdata
+
+// Class: i2c_reg_fdata
+class i2c_reg_fdata extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field byte_fmt;
+  rand dv_base_reg_field start;
+  rand dv_base_reg_field stop;
+  rand dv_base_reg_field read;
+  rand dv_base_reg_field rcont;
+  rand dv_base_reg_field nakok;
+
+  `uvm_object_utils(i2c_reg_fdata)
+
+  function new(string       name = "i2c_reg_fdata",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    byte_fmt = dv_base_reg_field::type_id::create("byte_fmt");
+    byte_fmt.configure(
+      .parent(this),
+      .size(8),
+      .lsb_pos(0),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    start = dv_base_reg_field::type_id::create("start");
+    start.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(8),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    stop = dv_base_reg_field::type_id::create("stop");
+    stop.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(9),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    read = dv_base_reg_field::type_id::create("read");
+    read.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(10),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    rcont = dv_base_reg_field::type_id::create("rcont");
+    rcont.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(11),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    nakok = dv_base_reg_field::type_id::create("nakok");
+    nakok.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(12),
+      .access("WO"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_fdata
+
+// Class: i2c_reg_timing0
+class i2c_reg_timing0 extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field thigh;
+  rand dv_base_reg_field tlow;
+
+  `uvm_object_utils(i2c_reg_timing0)
+
+  function new(string       name = "i2c_reg_timing0",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    thigh = dv_base_reg_field::type_id::create("thigh");
+    thigh.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    tlow = dv_base_reg_field::type_id::create("tlow");
+    tlow.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(16),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timing0
+
+// Class: i2c_reg_timing1
+class i2c_reg_timing1 extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field t_r;
+  rand dv_base_reg_field t_f;
+
+  `uvm_object_utils(i2c_reg_timing1)
+
+  function new(string       name = "i2c_reg_timing1",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    t_r = dv_base_reg_field::type_id::create("t_r");
+    t_r.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    t_f = dv_base_reg_field::type_id::create("t_f");
+    t_f.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(16),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timing1
+
+// Class: i2c_reg_timing2
+class i2c_reg_timing2 extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field tsu_sta;
+  rand dv_base_reg_field thd_st;
+
+  `uvm_object_utils(i2c_reg_timing2)
+
+  function new(string       name = "i2c_reg_timing2",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    tsu_sta = dv_base_reg_field::type_id::create("tsu_sta");
+    tsu_sta.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    thd_st = dv_base_reg_field::type_id::create("thd_st");
+    thd_st.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(16),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timing2
+
+// Class: i2c_reg_timing3
+class i2c_reg_timing3 extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field tsu_dat;
+  rand dv_base_reg_field thd_dat;
+
+  `uvm_object_utils(i2c_reg_timing3)
+
+  function new(string       name = "i2c_reg_timing3",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    tsu_dat = dv_base_reg_field::type_id::create("tsu_dat");
+    tsu_dat.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    thd_dat = dv_base_reg_field::type_id::create("thd_dat");
+    thd_dat.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(16),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timing3
+
+// Class: i2c_reg_timing4
+class i2c_reg_timing4 extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field tsu_sto;
+  rand dv_base_reg_field t_buf;
+
+  `uvm_object_utils(i2c_reg_timing4)
+
+  function new(string       name = "i2c_reg_timing4",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    tsu_sto = dv_base_reg_field::type_id::create("tsu_sto");
+    tsu_sto.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    t_buf = dv_base_reg_field::type_id::create("t_buf");
+    t_buf.configure(
+      .parent(this),
+      .size(16),
+      .lsb_pos(16),
+      .access("RW"),
+      .volatile(1),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timing4
+
+// Class: i2c_reg_timeout_ctrl
+class i2c_reg_timeout_ctrl extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field val;
+  rand dv_base_reg_field en;
+
+  `uvm_object_utils(i2c_reg_timeout_ctrl)
+
+  function new(string       name = "i2c_reg_timeout_ctrl",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    val = dv_base_reg_field::type_id::create("val");
+    val.configure(
+      .parent(this),
+      .size(31),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    en = dv_base_reg_field::type_id::create("en");
+    en.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(31),
+      .access("RW"),
+      .volatile(0),
+      .reset(0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : i2c_reg_timeout_ctrl
+
+// Class: i2c_reg_block
+class i2c_reg_block extends dv_base_reg_block;
+  // registers
+  rand i2c_reg_intr_state intr_state;
+  rand i2c_reg_intr_enable intr_enable;
+  rand i2c_reg_intr_test intr_test;
+  rand i2c_reg_ctrl ctrl;
+  rand i2c_reg_rdata rdata;
+  rand i2c_reg_fdata fdata;
+  rand i2c_reg_timing0 timing0;
+  rand i2c_reg_timing1 timing1;
+  rand i2c_reg_timing2 timing2;
+  rand i2c_reg_timing3 timing3;
+  rand i2c_reg_timing4 timing4;
+  rand i2c_reg_timeout_ctrl timeout_ctrl;
+
+  `uvm_object_utils(i2c_reg_block)
+
+  function new(string name = "i2c_reg_block",
+               int    has_coverage = UVM_NO_COVERAGE);
+    super.new(name, has_coverage);
+  endfunction : new
+
+  virtual function void build(uvm_reg_addr_t base_addr);
+    // create default map
+    this.default_map = create_map(.name("default_map"),
+                                  .base_addr(base_addr),
+                                  .n_bytes(4),
+                                  .endian(UVM_LITTLE_ENDIAN));
+
+    // create registers
+    intr_state = i2c_reg_intr_state::type_id::create("intr_state");
+    intr_state.configure(.blk_parent(this));
+    intr_state.build();
+    default_map.add_reg(.rg(intr_state),
+                        .offset(32'h0),
+                        .rights("RW"));
+    intr_enable = i2c_reg_intr_enable::type_id::create("intr_enable");
+    intr_enable.configure(.blk_parent(this));
+    intr_enable.build();
+    default_map.add_reg(.rg(intr_enable),
+                        .offset(32'h4),
+                        .rights("RW"));
+    intr_test = i2c_reg_intr_test::type_id::create("intr_test");
+    intr_test.configure(.blk_parent(this));
+    intr_test.build();
+    default_map.add_reg(.rg(intr_test),
+                        .offset(32'h8),
+                        .rights("WO"));
+    ctrl = i2c_reg_ctrl::type_id::create("ctrl");
+    ctrl.configure(.blk_parent(this));
+    ctrl.build();
+    default_map.add_reg(.rg(ctrl),
+                        .offset(32'hc),
+                        .rights("WO"));
+    rdata = i2c_reg_rdata::type_id::create("rdata");
+    rdata.configure(.blk_parent(this));
+    rdata.build();
+    default_map.add_reg(.rg(rdata),
+                        .offset(32'h10),
+                        .rights("RO"));
+    fdata = i2c_reg_fdata::type_id::create("fdata");
+    fdata.configure(.blk_parent(this));
+    fdata.build();
+    default_map.add_reg(.rg(fdata),
+                        .offset(32'h14),
+                        .rights("WO"));
+    timing0 = i2c_reg_timing0::type_id::create("timing0");
+    timing0.configure(.blk_parent(this));
+    timing0.build();
+    default_map.add_reg(.rg(timing0),
+                        .offset(32'h18),
+                        .rights("RW"));
+    timing1 = i2c_reg_timing1::type_id::create("timing1");
+    timing1.configure(.blk_parent(this));
+    timing1.build();
+    default_map.add_reg(.rg(timing1),
+                        .offset(32'h1c),
+                        .rights("RW"));
+    timing2 = i2c_reg_timing2::type_id::create("timing2");
+    timing2.configure(.blk_parent(this));
+    timing2.build();
+    default_map.add_reg(.rg(timing2),
+                        .offset(32'h20),
+                        .rights("RW"));
+    timing3 = i2c_reg_timing3::type_id::create("timing3");
+    timing3.configure(.blk_parent(this));
+    timing3.build();
+    default_map.add_reg(.rg(timing3),
+                        .offset(32'h24),
+                        .rights("RW"));
+    timing4 = i2c_reg_timing4::type_id::create("timing4");
+    timing4.configure(.blk_parent(this));
+    timing4.build();
+    default_map.add_reg(.rg(timing4),
+                        .offset(32'h28),
+                        .rights("RW"));
+    timeout_ctrl = i2c_reg_timeout_ctrl::type_id::create("timeout_ctrl");
+    timeout_ctrl.configure(.blk_parent(this));
+    timeout_ctrl.build();
+    default_map.add_reg(.rg(timeout_ctrl),
+                        .offset(32'h2c),
+                        .rights("RW"));
+  endfunction : build
+
+endclass : i2c_reg_block
diff --git a/hw/ip/i2c/dv/env/i2c_scoreboard.sv b/hw/ip/i2c/dv/env/i2c_scoreboard.sv
new file mode 100644
index 0000000..c66253c
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_scoreboard.sv
@@ -0,0 +1,97 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_scoreboard extends cip_base_scoreboard #(
+    .CFG_T(i2c_env_cfg),
+    .RAL_T(i2c_reg_block),
+    .COV_T(i2c_env_cov)
+  );
+  `uvm_component_utils(i2c_scoreboard)
+
+  // local variables
+
+  // TLM agent fifos
+  uvm_tlm_analysis_fifo #(i2c_item) i2c_fifo;
+
+  // local queues to hold incoming packets pending comparison
+  i2c_item i2c_q[$];
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    i2c_fifo = new("i2c_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_i2c_fifo();
+    join_none
+  endtask
+
+  virtual task process_i2c_fifo();
+    i2c_item item;
+    forever begin
+      i2c_fifo.get(item);
+      `uvm_info(`gfn, $sformatf("received i2c item:\n%0s", item.sprint()), UVM_HIGH)
+    end
+  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();
+
+    // if access was to a valid csr, get the csr handle
+    if (item.a_addr inside {cfg.csr_addrs}) begin
+      csr = ral.default_map.get_reg_by_offset(item.a_addr);
+      `DV_CHECK_NE_FATAL(csr, null)
+    end
+    if (csr == null) begin
+      // we hit an oob addr - expect error response and return
+      `DV_CHECK_EQ(item.d_error, 1'b1)
+      return;
+    end
+
+    if (channel == AddrChannel) begin
+      // if incoming access is a write to a valid csr, then make updates right away
+      if (write) 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
+      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 (!write && channel == DataChannel) 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
+      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/i2c/dv/env/i2c_virtual_sequencer.sv b/hw/ip/i2c/dv/env/i2c_virtual_sequencer.sv
new file mode 100644
index 0000000..16853f1
--- /dev/null
+++ b/hw/ip/i2c/dv/env/i2c_virtual_sequencer.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
+
+class i2c_virtual_sequencer extends cip_base_virtual_sequencer #(
+    .CFG_T(i2c_env_cfg),
+    .COV_T(i2c_env_cov)
+  );
+  `uvm_component_utils(i2c_virtual_sequencer)
+
+  i2c_sequencer i2c_sequencer_h;
+
+  `uvm_component_new
+
+endclass
diff --git a/hw/ip/i2c/dv/env/seq_lib/i2c_base_vseq.sv b/hw/ip/i2c/dv/env/seq_lib/i2c_base_vseq.sv
new file mode 100644
index 0000000..c4cfa59
--- /dev/null
+++ b/hw/ip/i2c/dv/env/seq_lib/i2c_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 i2c_base_vseq extends cip_base_vseq #(
+    .CFG_T               (i2c_env_cfg),
+    .RAL_T               (i2c_reg_block),
+    .COV_T               (i2c_env_cov),
+    .VIRTUAL_SEQUENCER_T (i2c_virtual_sequencer)
+  );
+  `uvm_object_utils(i2c_base_vseq)
+
+  // various knobs to enable certain routines
+  bit do_i2c_init = 1'b1;
+
+  `uvm_object_new
+
+  virtual task dut_init(string reset_kind = "HARD");
+    super.dut_init();
+    if (do_i2c_init) i2c_init();
+  endtask
+
+  virtual task dut_shutdown();
+    // check for pending i2c operations and wait for them to complete
+    // TODO
+  endtask
+
+  // setup basic i2c features
+  virtual task i2c_init();
+    //`uvm_info(`gfn, "Initialize I2C registers")
+  endtask
+
+endclass : i2c_base_vseq
diff --git a/hw/ip/i2c/dv/env/seq_lib/i2c_common_vseq.sv b/hw/ip/i2c/dv/env/seq_lib/i2c_common_vseq.sv
new file mode 100644
index 0000000..ab5dc6a
--- /dev/null
+++ b/hw/ip/i2c/dv/env/seq_lib/i2c_common_vseq.sv
@@ -0,0 +1,30 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_common_vseq extends i2c_base_vseq;
+  `uvm_object_utils(i2c_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
+
+  // function to add csr exclusions of the given type using the csr_excl_item item
+  virtual function void add_csr_exclusions(string           csr_test_type,
+                                           csr_excl_item    csr_excl,
+                                           string           scope = "ral");
+
+    // write exclusions - these should not apply to hw_reset test
+    if (csr_test_type != "hw_reset") begin
+      // TODO: below is a sample
+      // status reads back unexpected values due to writes to other csrs
+      // csr_excl.add_excl({scope, ".", "status"}, CsrExclWriteCheck);
+    end
+  endfunction
+
+endclass
diff --git a/hw/ip/i2c/dv/env/seq_lib/i2c_sanity_vseq.sv b/hw/ip/i2c/dv/env/seq_lib/i2c_sanity_vseq.sv
new file mode 100644
index 0000000..35c42ad
--- /dev/null
+++ b/hw/ip/i2c/dv/env/seq_lib/i2c_sanity_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
+
+// basic sanity test vseq
+class i2c_sanity_vseq extends i2c_base_vseq;
+  `uvm_object_utils(i2c_sanity_vseq)
+
+  `uvm_object_new
+
+  task body();
+    `uvm_error(`gfn, "FIXME")
+  endtask : body
+
+endclass : i2c_sanity_vseq
diff --git a/hw/ip/i2c/dv/env/seq_lib/i2c_vseq_list.sv b/hw/ip/i2c/dv/env/seq_lib/i2c_vseq_list.sv
new file mode 100644
index 0000000..06afade
--- /dev/null
+++ b/hw/ip/i2c/dv/env/seq_lib/i2c_vseq_list.sv
@@ -0,0 +1,8 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "i2c_base_vseq.sv"
+`include "i2c_wrap_vseq.sv"
+`include "i2c_sanity_vseq.sv"
+`include "i2c_common_vseq.sv"
diff --git a/hw/ip/i2c/dv/env/seq_lib/i2c_wrap_vseq.sv b/hw/ip/i2c/dv/env/seq_lib/i2c_wrap_vseq.sv
new file mode 100644
index 0000000..fbcc4b6
--- /dev/null
+++ b/hw/ip/i2c/dv/env/seq_lib/i2c_wrap_vseq.sv
@@ -0,0 +1,219 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_wrap_vseq extends i2c_base_vseq;
+  `uvm_object_utils(i2c_wrap_vseq)
+
+  rand uint num_tx_bytes;
+  rand uint num_rx_bytes;
+  rand uint dly_to_next_trans;
+  rand uint dly_to_access_intr;
+  rand bit  wait_for_idle;
+  rand uint weight_to_skip_rx_read;
+
+
+  constraint num_trans_c {
+    num_trans inside {[1:20]};
+  }
+
+  constraint num_tx_bytes_c {
+    num_tx_bytes dist {
+      1       :/ 2,
+      [2:10]  :/ 5,
+      [11:15] :/ 5,
+      [16:20] :/ 2
+    };
+  }
+
+  constraint num_rx_bytes_c {
+    num_rx_bytes dist {
+      1       :/ 2,
+      [2:10]  :/ 5,
+      [11:15] :/ 5,
+      [16:20] :/ 2
+    };
+  }
+
+  constraint dly_to_next_trans_c {
+    dly_to_next_trans dist {
+      0           :/ 5,  // more back2back transaction
+      [1:100]     :/ 5,
+      [100:10000] :/ 2
+    };
+  }
+
+  constraint dly_to_access_intr_c {
+    dly_to_access_intr dist {
+      0                   :/ 1,
+      [1      :100]       :/ 5,
+      [101    :10_000]    :/ 3,
+      [10_001 :1_000_000] :/ 1
+    };
+  }
+
+  constraint wait_for_idle_c {
+    wait_for_idle dist {
+      1       :/ 1,
+      0       :/ 10
+    };
+  }
+
+  constraint weight_to_skip_rx_read_c {
+    // 3: read, 7: skip
+    weight_to_skip_rx_read == 7;
+  }
+
+  `uvm_object_new
+
+  task pre_start();
+    super.pre_start();
+    num_trans.rand_mode(0);
+  endtask
+
+  task body();
+
+  endtask: body
+
+/*
+  task body();
+    fork
+      begin
+        while (do_interrupt) process_interrupts();
+      end
+      begin
+        // repeat test sequencing upto 50 times
+        for (int i = 1; i <= num_trans; i++) begin
+          // start each new run by randomizing dut parameters
+          `DV_CHECK_RANDOMIZE_FATAL(this)
+
+          i2c_init();
+
+          `uvm_info(`gfn, $sformatf("starting run %0d/%0d", i, num_trans), UVM_MEDIUM)
+          fork
+            begin
+              `uvm_info(`gfn, $sformatf("begin sending %0d tx bytes", num_tx_bytes), UVM_MEDIUM)
+              process_tx();
+              `uvm_info(`gfn, $sformatf("done sending %0d tx bytes", num_tx_bytes), UVM_HIGH)
+            end
+            begin
+              `uvm_info(`gfn, $sformatf("begin sending %0d rx bytes", num_rx_bytes), UVM_MEDIUM)
+              process_rx();
+              `uvm_info(`gfn, $sformatf("done sending %0d rx bytes", num_rx_bytes), UVM_HIGH)
+            end
+          join
+
+          process_remaining_data();
+          `uvm_info(`gfn, $sformatf("finished run %0d/%0d", i, num_trans), UVM_LOW)
+        end
+        do_interrupt = 0; // to end thread process_interrupts gracefully
+      end
+    join
+  endtask : body
+
+  task post_start();
+    bit [TL_DW-1:0] intr_status;
+    // dut_shutdown is must for each iteration, it's called in body
+    do_dut_shutdown = 0;
+    super.post_start();
+    // need to clear fifo when tx is disabled as data isn't sent out
+    if (ral.ctrl.tx.get_mirrored_value() == 0) begin
+      clear_fifos(.clear_tx_fifo(1), .clear_rx_fifo(0));
+    end
+    // read & check, then clear up all interrupts
+    csr_rd(.ptr(ral.intr_state), .value(intr_status));
+    csr_wr(.csr(ral.intr_state), .value(intr_status));
+  endtask
+
+  // read interrupts and randomly clear interrupts if set
+  task process_interrupts();
+    bit [TL_DW-1:0] intr_status, clear_intr;
+    bit clear_rx_intr, clear_tx_intr;
+    // read interrupt
+    `DV_CHECK_MEMBER_RANDOMIZE_FATAL(dly_to_access_intr)
+    cfg.clk_rst_vif.wait_clks(dly_to_access_intr);
+    csr_rd(.ptr(ral.intr_state), .value(intr_status));
+
+    // clear interrupt, randomly clear the interrupt that is set, and
+    // don't clear the interrupt which isn't set
+    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(clear_intr,
+                                       foreach (clear_intr[i]) {
+                                           clear_intr[i] -> intr_status[i] == 1;
+                                       })
+    `DV_CHECK_MEMBER_RANDOMIZE_FATAL(dly_to_access_intr)
+    cfg.clk_rst_vif.wait_clks(dly_to_access_intr);
+
+    // for fifo interrupt, parity/frame error, don't clear it at ignored period
+    // as it hasn't been checked
+    clear_tx_intr = clear_intr[TxWatermark] | clear_intr[TxWatermark];
+    clear_rx_intr = clear_intr[RxWatermark] | clear_intr[RxOverflow] | clear_intr[RxFrameErr] |
+                    clear_intr[RxParityErr];
+    wait_when_in_ignored_period(clear_tx_intr, clear_rx_intr);
+    csr_wr(.csr(ral.intr_state), .value(clear_intr));
+  endtask
+
+  virtual task process_tx();
+    for (int j = 1; j <= num_tx_bytes; j++) begin
+      byte tx_byte;
+      `DV_CHECK_MEMBER_RANDOMIZE_FATAL(dly_to_next_trans)
+      `DV_CHECK_MEMBER_RANDOMIZE_FATAL(wait_for_idle)
+
+      cfg.clk_rst_vif.wait_clks(dly_to_next_trans);
+      wait_for_tx_fifo_not_full();
+      wait_when_in_ignored_period(.tx(1));
+      `DV_CHECK_STD_RANDOMIZE_FATAL(tx_byte)
+      send_tx_byte(tx_byte);
+      if (wait_for_idle) spinwait_txidle();
+    end
+  endtask : process_tx
+
+  // control RX data from both sides independently
+  //   1. i2c device
+  //   2. register
+  virtual task process_rx();
+    bit send_rx_done = 0;
+    fork
+      begin // drive from i2c RX interface
+        for (int j = 1; j <= num_rx_bytes; j++) begin
+          byte rx_byte;
+          `DV_CHECK_MEMBER_RANDOMIZE_FATAL(dly_to_next_trans)
+          `DV_CHECK_MEMBER_RANDOMIZE_FATAL(wait_for_idle)
+          `DV_CHECK_STD_RANDOMIZE_FATAL(rx_byte)
+
+          cfg.clk_rst_vif.wait_clks(dly_to_next_trans);
+          wait_for_rx_fifo_not_full();
+          send_rx_byte(rx_byte);
+          if (wait_for_idle) spinwait_rxidle();
+        end
+        send_rx_done = 1; // to end reading RX thread
+      end
+      begin // read RX data through register
+        while (!send_rx_done) begin
+          `DV_CHECK_MEMBER_RANDOMIZE_FATAL(dly_to_next_trans)
+          cfg.clk_rst_vif.wait_clks(dly_to_next_trans);
+          wait_when_in_ignored_period(.rx(1));
+          rand_read_rx_byte(weight_to_skip_rx_read);
+        end
+      end
+    join
+  endtask : process_rx
+
+  virtual task process_remaining_data();
+
+    fork
+      begin // TX
+        wait_for_all_tx_bytes();
+        // tx fifo is empty but still need to wait for last tx item to be flushed out
+        cfg.m_i2c_agent_cfg.vif.wait_for_tx_idle();
+      end
+      begin // RX
+        // wait for last rx item to be completed before read all of them
+        cfg.m_i2c_agent_cfg.vif.wait_for_rx_idle();
+        read_all_rx_bytes();
+      end
+    join
+
+  endtask : process_remaining_data
+*/
+
+endclass : i2c_wrap_vseq
diff --git a/hw/ip/i2c/dv/i2c_sim.core b/hw/ip/i2c/dv/i2c_sim.core
new file mode 100644
index 0000000..d790828
--- /dev/null
+++ b/hw/ip/i2c/dv/i2c_sim.core
@@ -0,0 +1,28 @@
+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:i2c_sim:0.1"
+description: "I2C DV sim target"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:ip:i2c:0.1
+    files:
+      - tb/i2c_bind.sv
+    file_type: systemVerilogSource
+
+  files_dv:
+    depend:
+      - lowrisc:dv:i2c_test
+    files:
+      - tb/tb.sv
+    file_type: systemVerilogSource
+
+targets:
+  sim:
+    toplevel: tb
+    filesets:
+      - files_rtl
+      - files_dv
+    default_tool: vcs
diff --git a/hw/ip/i2c/dv/tb/i2c_bind.sv b/hw/ip/i2c/dv/tb/i2c_bind.sv
new file mode 100644
index 0000000..3f0606d
--- /dev/null
+++ b/hw/ip/i2c/dv/tb/i2c_bind.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
+
+module i2c_bind;
+
+  bind i2c tlul_assert tlul_assert (
+    .clk_i,
+    .rst_ni,
+    .h2d  (tl_i),
+    .d2h  (tl_o)
+  );
+
+endmodule
diff --git a/hw/ip/i2c/dv/tb/tb.sv b/hw/ip/i2c/dv/tb/tb.sv
new file mode 100644
index 0000000..8f80b91
--- /dev/null
+++ b/hw/ip/i2c/dv/tb/tb.sv
@@ -0,0 +1,86 @@
+// 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 i2c_env_pkg::*;
+  import i2c_test_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  wire clk, rst_n;
+  wire intr_fmt_watermark;
+  wire intr_rx_watermark;
+  wire intr_fmt_overflow;
+  wire intr_rx_overflow;
+  wire intr_nak;
+  wire intr_scl_interference;
+  wire intr_sda_interference;
+  wire intr_stretch_timeout;
+  wire intr_sda_unstable;
+  wire [NUM_MAX_INTERRUPTS-1:0] interrupts;
+  wire [NUM_MAX_ALERTS-1:0] alerts;
+
+  // interfaces
+  clk_rst_if clk_rst_if(.clk(clk), .rst_n(rst_n));
+  pins_if #(NUM_MAX_INTERRUPTS) intr_if(interrupts);
+  pins_if #(NUM_MAX_ALERTS) alerts_if(alerts);
+  tl_if tl_if(.clk(clk), .rst_n(rst_n));
+  i2c_if i2c_if();
+
+  // dut
+  i2c dut (
+    .clk_i                   (clk        ),
+    .rst_ni                  (rst_n      ),
+
+    .tl_i                    (tl_if.h2d  ),
+    .tl_o                    (tl_if.d2h  ),
+
+    .cio_scl_i               (i2c_if.scl_i          ),
+    .cio_scl_o               (i2c_if.scl_o          ),
+    .cio_scl_en_o            (i2c_if.scl_en_o       ),
+    .cio_sda_i               (i2c_if.sda_i          ),
+    .cio_sda_o               (i2c_if.sda_o          ),
+    .cio_sda_en_o            (i2c_if.sda_en_o       ),
+
+    .intr_fmt_watermark_o    (intr_fmt_watermark    ),
+    .intr_rx_watermark_o     (intr_rx_watermark     ),
+    .intr_fmt_overflow_o     (intr_fmt_overflow     ),
+    .intr_rx_overflow_o      (intr_rx_overflow      ),
+    .intr_nak_o              (intr_nak              ),
+    .intr_scl_interference_o (intr_scl_interference ),
+    .intr_sda_interference_o (intr_sda_interference ),
+    .intr_stretch_timeout_o  (intr_stretch_timeout  ),
+    .intr_sda_unstable_o     (intr_sda_unstable     )
+  );
+
+  /*
+  assign interrupts[FmtWatermark]   = intr_fmt_watermark;
+  assign interrupts[RxWatermark]    = intr_rx_watermark;
+  assign interrupts[FmtOverflow]    = intr_fmt_overflow;
+  assign interrupts[RxOverflow]     = intr_rx_overflow;
+  assign interrupts[Nak]            = intr_nak;
+  assign interrupts[SclInference]   = intr_scl_interference;
+  assign interrupts[SdaInference]   = intr_sda_interference;
+  assign interrupts[StretchTimeout] = intr_stretch_timeout;
+  assign interrupts[SdaUnstable]    = intr_sda_unstable;
+  */
+
+  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#(intr_vif)::set(null, "*.env", "intr_vif", intr_if);
+    uvm_config_db#(alerts_vif)::set(null, "*.env", "alerts_vif", alerts_if);
+    uvm_config_db#(virtual tl_if)::set(null, "*.env.m_tl_agent*", "vif", tl_if);
+    uvm_config_db#(virtual i2c_if)::set(null, "*.env.m_i2c_agent*", "vif", i2c_if);
+    $timeformat(-12, 0, " ps", 12);
+    run_test();
+  end
+
+endmodule
diff --git a/hw/ip/i2c/dv/tests/i2c_base_test.sv b/hw/ip/i2c/dv/tests/i2c_base_test.sv
new file mode 100644
index 0000000..d84b730
--- /dev/null
+++ b/hw/ip/i2c/dv/tests/i2c_base_test.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
+
+class i2c_base_test extends cip_base_test #(.ENV_T(i2c_env), .CFG_T(i2c_env_cfg));
+  `uvm_component_utils(i2c_base_test)
+  `uvm_component_new
+
+  // the base class dv_base_test creates the following instances:
+  // i2c_env_cfg: cfg
+  // i2c_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 : i2c_base_test
+
diff --git a/hw/ip/i2c/dv/tests/i2c_test.core b/hw/ip/i2c/dv/tests/i2c_test.core
new file mode 100644
index 0000000..c2fa5ca
--- /dev/null
+++ b/hw/ip/i2c/dv/tests/i2c_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:i2c_test:0.1"
+description: "I2C DV UVM test"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:i2c_env
+    files:
+      - i2c_test_pkg.sv
+      - i2c_base_test.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/ip/i2c/dv/tests/i2c_test_pkg.sv b/hw/ip/i2c/dv/tests/i2c_test_pkg.sv
new file mode 100644
index 0000000..98fce6c
--- /dev/null
+++ b/hw/ip/i2c/dv/tests/i2c_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 i2c_test_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import cip_base_pkg::*;
+  import i2c_env_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // local types
+
+  // functions
+
+  // package sources
+  `include "i2c_base_test.sv"
+
+endpackage