Add chip_sw_i2c_device_tx_rx to edacloud and fix other problem with i2c
tests

Change-Id: Ic2b672233727c03b7e900958352e7c05df355760
diff --git a/hw/top_matcha/dv/chip_sim_cfg.hjson b/hw/top_matcha/dv/chip_sim_cfg.hjson
index 7d7f041..0e75e2a 100644
--- a/hw/top_matcha/dv/chip_sim_cfg.hjson
+++ b/hw/top_matcha/dv/chip_sim_cfg.hjson
@@ -800,7 +800,7 @@
     {
       name: chip_sw_i2c_device_tx_rx
       uvm_test_seq: chip_sw_i2c_device_tx_rx_vseq
-      sw_images: ["//sw/device/tests/sim_dv:i2c_device_tx_rx_test:1"]
+      sw_images: ["//sw/device/tests/sim_dv:i2c_device_tx_rx_test:1:matcha"]
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
@@ -2059,6 +2059,7 @@
         "chip_sw_example_flash",
         "chip_sw_example_rom",
         "chip_sw_gpio",
+        "chip_sw_i2c_device_tx_rx",
         "chip_sw_i2c_host_tx_rx",
         "chip_sw_mem_test",
         "chip_sw_rom_ctrl_integrity_check",
@@ -2140,6 +2141,7 @@
         "chip_sw_gpio",
         "chip_sw_hmac_enc",
         "chip_sw_hmac_smoketest",
+        "chip_sw_i2c_device_tx_rx",
         "chip_sw_i2c_host_tx_rx",
         "chip_sw_mem_test",
         "chip_sw_keymgr_key_derivation",
@@ -2259,6 +2261,7 @@
         "chip_sw_gpio",
         "chip_sw_hmac_enc",
         "chip_sw_hmac_smoketest",
+        "chip_sw_i2c_device_tx_rx",
         "chip_sw_i2c_host_tx_rx",
         "chip_sw_mem_test",
         "chip_sw_keymgr_key_derivation",
diff --git a/hw/top_matcha/dv/env/seq_lib/chip_sw_i2c_device_tx_rx_vseq.sv b/hw/top_matcha/dv/env/seq_lib/chip_sw_i2c_device_tx_rx_vseq.sv
index 9e9a796..2937ec0 100644
--- a/hw/top_matcha/dv/env/seq_lib/chip_sw_i2c_device_tx_rx_vseq.sv
+++ b/hw/top_matcha/dv/env/seq_lib/chip_sw_i2c_device_tx_rx_vseq.sv
@@ -1,3 +1,4 @@
+// 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
@@ -77,6 +78,24 @@
     cfg.m_i2c_agent_cfgs[i2c_idx].if_mode = Host;
 
     // Enbale appropriate interface
+    case(i2c_idx)
+      0: begin
+        cfg.chip_vif.gpios_if.set_pulldown_en_pin(7, 1'b0);
+        cfg.chip_vif.gpios_if.set_pulldown_en_pin(8, 1'b0);
+      end
+      1: begin
+        cfg.chip_vif.gpios_if.set_pulldown_en_pin(18, 1'b0);
+      end
+      2: begin
+        cfg.chip_vif.gpios_if.set_pulldown_en_pin(19, 1'b0);
+        cfg.chip_vif.gpios_if.set_pulldown_en_pin(20, 1'b0);
+      end
+      default: begin
+        `uvm_error(`gfn, $sformatf("Unexpected i2c index: %0d", i2c_idx))
+      end
+    endcase
+
+    // Enbale appropriate interface
     cfg.chip_vif.enable_i2c(.inst_num(i2c_idx), .enable(1));
 
     // tClockLow needs to be "slightly" shorter than the actual clock low period
diff --git a/sw/device/lib/testing/BUILD b/sw/device/lib/testing/BUILD
index 305c3c3..813a87d 100644
--- a/sw/device/lib/testing/BUILD
+++ b/sw/device/lib/testing/BUILD
@@ -81,3 +81,18 @@
         "@lowrisc_opentitan//sw/device/lib/testing/test_framework:check",
     ],
 )
+
+cc_library(
+    name = "i2c_testutils",
+    srcs = ["i2c_testutils.c"],
+    hdrs = ["@lowrisc_opentitan//sw/device/lib/testing:i2c_testutils.h"],
+    target_compatible_with = [OPENTITAN_CPU],
+    deps = [
+        "//hw/top_matcha/sw/autogen:top_matcha",
+        "//sw/device/lib/dif:pinmux",
+        "@lowrisc_opentitan//hw/ip/i2c/data:i2c_regs",
+        "@lowrisc_opentitan//sw/device/lib/dif:i2c",
+        "@lowrisc_opentitan//sw/device/lib/runtime:ibex",
+        "@lowrisc_opentitan//sw/device/lib/testing/test_framework:check",
+    ],
+)
diff --git a/sw/device/lib/testing/i2c_testutils.c b/sw/device/lib/testing/i2c_testutils.c
new file mode 100644
index 0000000..a845463
--- /dev/null
+++ b/sw/device/lib/testing/i2c_testutils.c
@@ -0,0 +1,217 @@
+// 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 "sw/device/lib/testing/i2c_testutils.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "i2c_regs.h"  // Generated.
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_i2c.h"
+#include "sw/device/lib/dif/dif_pinmux.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+
+static const uint8_t kI2cWrite = 0;
+static const uint8_t kI2cRead = 1;
+
+// Default flags for i2c operations.
+static const dif_i2c_fmt_flags_t kDefaultFlags = {.start = false,
+                                                  .stop = false,
+                                                  .read = false,
+                                                  .read_cont = false,
+                                                  .suppress_nak_irq = false};
+
+typedef struct i2c_connect_conf {
+  const top_matcha_pinmux_mio_out_t pins_mio_out[2];
+  const top_matcha_pinmux_insel_t pins_insel[2];
+  const top_matcha_pinmux_peripheral_in_t pins_peripheral_in[2];
+  const top_matcha_pinmux_outsel_t pins_outsel[2];
+} i2c_connect_conf_t;
+
+const i2c_connect_conf_t i2c_conf[] = {
+    {.pins_mio_out = {kTopMatchaPinmuxMioOutIoa7, kTopMatchaPinmuxMioOutIoa8},
+     .pins_insel = {kTopMatchaPinmuxInselIoa7, kTopMatchaPinmuxInselIoa8},
+     .pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c0Scl,
+                            kTopMatchaPinmuxPeripheralInI2c0Sda},
+     .pins_outsel = {kTopMatchaPinmuxOutselI2c0Scl,
+                     kTopMatchaPinmuxOutselI2c0Sda}},
+    {.pins_mio_out = {kTopMatchaPinmuxMioOutIob9, kTopMatchaPinmuxMioOutIob10},
+     .pins_insel = {kTopMatchaPinmuxInselIob9, kTopMatchaPinmuxInselIob10},
+     .pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c1Scl,
+                            kTopMatchaPinmuxPeripheralInI2c1Sda},
+     .pins_outsel = {kTopMatchaPinmuxOutselI2c1Scl,
+                     kTopMatchaPinmuxOutselI2c1Sda}},
+    {.pins_mio_out = {kTopMatchaPinmuxMioOutIob11, kTopMatchaPinmuxMioOutIob12},
+     .pins_insel = {kTopMatchaPinmuxInselIob11, kTopMatchaPinmuxInselIob12},
+     .pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c2Scl,
+                            kTopMatchaPinmuxPeripheralInI2c2Sda},
+     .pins_outsel = {kTopMatchaPinmuxOutselI2c2Scl,
+                     kTopMatchaPinmuxOutselI2c2Sda}}};
+
+void i2c_testutils_wr(const dif_i2c_t *i2c, uint8_t addr, uint8_t byte_count,
+                      const uint8_t *data, bool skip_stop) {
+  dif_i2c_fmt_flags_t flags = kDefaultFlags;
+  uint8_t data_frame;
+
+  // The current function does not support initializing a write while another
+  // transaction is in progress
+
+  // TODO: The current function does not support write payloads
+  // larger than the fifo depth.
+  CHECK(byte_count <= I2C_PARAM_FIFO_DEPTH);
+
+  // TODO: #15377 The I2C DIF says: "Callers should prefer
+  // `dif_i2c_write_byte()` instead, since that function provides clearer
+  // semantics. This function should only really be used for testing or
+  // troubleshooting a device.
+
+  // First write the address.
+  flags.start = true;
+  data_frame = (addr << 1) | kI2cWrite;
+  CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data_frame, flags));
+
+  // Once address phase is through, blast the rest as generic data.
+  flags = kDefaultFlags;
+  for (uint8_t i = 0; i < byte_count; ++i) {
+    // Issue a stop for the last byte.
+    flags.stop = ((i == byte_count - 1) && !skip_stop);
+    CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data[i], flags));
+  }
+
+  // TODO: Check for errors / status.
+}
+
+void i2c_testutils_rd(const dif_i2c_t *i2c, uint8_t addr, uint8_t byte_count) {
+  dif_i2c_fmt_flags_t flags = kDefaultFlags;
+  uint8_t data_frame;
+  uint8_t fifo_level;
+
+  // The current function doesn't check for space in the FIFOs
+
+  // First write the address.
+  flags.start = true;
+  data_frame = (addr << 1) | kI2cRead;
+  CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data_frame, flags));
+
+  // Schedule the read transaction by writing flags to the fifo.
+  flags = kDefaultFlags;
+  flags.read = true;
+  flags.stop = true;
+
+  // Inform the controller how many bytes to read overall.
+  CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, byte_count, flags));
+
+  // TODO: Check for errors / status.
+}
+
+bool i2c_testutils_target_check_start(const dif_i2c_t *i2c, uint8_t *addr) {
+  uint8_t acq_fifo_lvl;
+  CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
+  CHECK(acq_fifo_lvl > 1);
+
+  dif_i2c_signal_t signal;
+  uint8_t byte;
+  CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, &byte, &signal));
+  // Check acq_fifo is as expected and write addr and continue
+  CHECK(signal == kDifI2cSignalStart);
+  *addr = byte >> 1;
+  return byte & kI2cRead;
+}
+
+bool i2c_testutils_target_check_end(const dif_i2c_t *i2c, uint8_t *cont_byte) {
+  uint8_t acq_fifo_lvl;
+  CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
+  CHECK(acq_fifo_lvl >= 1);
+
+  dif_i2c_signal_t signal;
+  uint8_t byte;
+  CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, &byte, &signal));
+  // Check transaction is terminated with a stop or a continue that the caller
+  // is prepared to handle
+  if (signal == kDifI2cSignalStop) {
+    return false;
+  }
+  CHECK(cont_byte != NULL);
+  *cont_byte = byte;
+  CHECK(signal == kDifI2cSignalRepeat);
+  return true;
+}
+
+void i2c_testutils_target_rd(const dif_i2c_t *i2c, uint8_t byte_count,
+                             const uint8_t *data) {
+  uint8_t tx_fifo_lvl, acq_fifo_lvl;
+  CHECK_DIF_OK(
+      dif_i2c_get_fifo_levels(i2c, NULL, NULL, &tx_fifo_lvl, &acq_fifo_lvl));
+  // Check there's space in tx_fifo and acq_fifo
+  CHECK(tx_fifo_lvl + byte_count <= I2C_PARAM_FIFO_DEPTH);
+  CHECK(acq_fifo_lvl + 2 <= I2C_PARAM_FIFO_DEPTH);
+
+  for (uint8_t i = 0; i < byte_count; ++i) {
+    CHECK_DIF_OK(dif_i2c_transmit_byte(i2c, data[i]));
+  }
+  // TODO: Check for errors / status.
+}
+
+bool i2c_testutils_target_check_rd(const dif_i2c_t *i2c, uint8_t *addr,
+                                   uint8_t *cont_byte) {
+  CHECK(i2c_testutils_target_check_start(i2c, addr) == kI2cRead);
+  // TODO: Check for errors / status.
+  return i2c_testutils_target_check_end(i2c, cont_byte);
+}
+
+void i2c_testutils_target_wr(const dif_i2c_t *i2c, uint8_t byte_count) {
+  uint8_t acq_fifo_lvl;
+  CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
+  CHECK(acq_fifo_lvl + 2 + byte_count <= I2C_PARAM_FIFO_DEPTH);
+
+  // TODO: Check for errors / status.
+}
+
+bool i2c_testutils_target_check_wr(const dif_i2c_t *i2c, uint8_t byte_count,
+                                   uint8_t *addr, uint8_t *bytes,
+                                   uint8_t *cont_byte) {
+  uint8_t acq_fifo_lvl;
+  CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
+  CHECK(acq_fifo_lvl >= 2 + byte_count);
+
+  CHECK(i2c_testutils_target_check_start(i2c, addr) == kI2cWrite);
+
+  for (uint8_t i = 0; i < byte_count; ++i) {
+    dif_i2c_signal_t signal;
+    CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, bytes + i, &signal));
+    CHECK(signal == kDifI2cSignalNone);
+  }
+
+  // TODO: Check for errors / status.
+
+  return i2c_testutils_target_check_end(i2c, cont_byte);
+}
+
+void i2c_testutils_connect_i2c_to_pinmux_pins(const dif_pinmux_t *pinmux,
+                                              uint8_t kI2cIdx) {
+  top_matcha_pinmux_mio_out_t i2c_pinmux_out1_id, i2c_pinmux_out2_id;
+  top_matcha_pinmux_insel_t i2c_pinmux_in1_id, i2c_pinmux_in2_id;
+  top_matcha_pinmux_peripheral_in_t i2c_pinmux_insel_scl_id,
+      i2c_pinmux_insel_sda_id;
+  top_matcha_pinmux_outsel_t i2c_pinmux_outsel_scl_id, i2c_pinmux_outsel_sda_id;
+
+  CHECK_DIF_OK(dif_pinmux_input_select(pinmux,
+                                       i2c_conf[kI2cIdx].pins_peripheral_in[0],
+                                       i2c_conf[kI2cIdx].pins_insel[0]));
+  CHECK_DIF_OK(dif_pinmux_input_select(pinmux,
+                                       i2c_conf[kI2cIdx].pins_peripheral_in[1],
+                                       i2c_conf[kI2cIdx].pins_insel[1]));
+  CHECK_DIF_OK(dif_pinmux_output_select(pinmux,
+                                        i2c_conf[kI2cIdx].pins_mio_out[0],
+                                        i2c_conf[kI2cIdx].pins_outsel[0]));
+  CHECK_DIF_OK(dif_pinmux_output_select(pinmux,
+                                        i2c_conf[kI2cIdx].pins_mio_out[1],
+                                        i2c_conf[kI2cIdx].pins_outsel[1]));
+}
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index ee50442..b50b41d 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -47,10 +47,10 @@
     deps = [
         "//sw/device/lib/dif:pinmux",
         "//sw/device/lib/dif:rstmgr",
+        "//sw/device/lib/testing:i2c_testutils",
         "//sw/device/lib/testing:isr_testutils",
         "//sw/device/tests:test_dv_lib",
         "@lowrisc_opentitan//sw/device/lib/dif:i2c",
-        "@lowrisc_opentitan//sw/device/lib/testing:i2c_testutils",
         "@lowrisc_opentitan//sw/device/lib/testing:rand_testutils",
     ],
 )
@@ -380,6 +380,22 @@
     ],
 )
 
+matcha_dv_test(
+    name = "i2c_device_tx_rx_test",
+    srcs = ["i2c_device_tx_rx_test.c"],
+    deps = [
+        "//hw/top_matcha/ip/clkmgr/data/autogen:clkmgr_regs",
+        "//sw/device/lib/dif:pinmux",
+        "//sw/device/lib/dif:rv_plic_sec",
+        "//sw/device/lib/testing:i2c_testutils",
+        "//sw/device/lib/testing:isr_testutils",
+        "//sw/device/tests:test_dv_lib",
+        "@lowrisc_opentitan//hw/ip/lc_ctrl/data:lc_ctrl_regs",
+        "@lowrisc_opentitan//sw/device/lib/dif:i2c",
+        "@lowrisc_opentitan//sw/device/lib/testing:rand_testutils",
+    ],
+)
+
 ################################################################################
 #  Place the Opentitan-sourced DV test below.                                  #
 #  Opentitan DV binary build flow does not support centOS7, so we pull the     #
diff --git a/sw/device/tests/sim_dv/i2c_device_tx_rx_test.c b/sw/device/tests/sim_dv/i2c_device_tx_rx_test.c
new file mode 100644
index 0000000..f21838a
--- /dev/null
+++ b/sw/device/tests/sim_dv/i2c_device_tx_rx_test.c
@@ -0,0 +1,265 @@
+// 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 "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_base.h"
+#include "sw/device/lib/dif/dif_i2c.h"
+#include "sw/device/lib/dif/dif_pinmux.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/ibex.h"
+#include "sw/device/lib/runtime/irq.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/autogen/isr_testutils.h"
+#include "sw/device/lib/testing/i2c_testutils.h"
+#include "sw/device/lib/testing/rand_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+#include "sw/device/lib/testing/test_framework/status.h"
+
+// TODO #14111, remove it once pinout configuration is provided
+#include "i2c_regs.h"
+#include "pinmux_regs.h"
+
+static dif_i2c_t i2c;
+static dif_pinmux_t pinmux;
+static dif_rv_plic_t plic;
+
+OTTF_DEFINE_TEST_CONFIG();
+
+/**
+ * This symbol is meant to be backdoor loaded by the testbench.
+ * The testbench will inform the test the rough speed of the clock
+ * used by the I2C modules.
+ *
+ * The I2C Device state machine does depend on the I2C timing configuration
+ */
+static volatile const uint8_t kClockPeriodNanos = 0;
+static volatile const uint8_t kI2cRiseFallNanos = 0;
+static volatile const uint32_t kI2cClockPeriodNanos = 0;
+
+/**
+ * This symbol is meant to be backdoor loaded by the testbench.
+ * to indicate which I2c is actually under test.
+ */
+static volatile const uint8_t kI2cIdx = 0;
+
+/**
+ * This set of symbols is meant to be backdoor loaded by the testbench.
+ * to indicate the address that will be listened to by the device.
+ */
+static volatile const uint8_t kI2cDeviceAddress0 = 0x55;
+static volatile const uint8_t kI2cDeviceMask0 = 0x7f;
+static volatile const uint8_t kI2cDeviceAddress1 = 0x7f;  // disable match on
+                                                          // second address
+static volatile const uint8_t kI2cDeviceMask1 = 0x7f;
+
+/**
+ * This symbol is meant to be backdoor loaded by the testbench.
+ * to indicate the number of bytes that should be sent.
+ *
+ * Because the test doesn't manage the FIFO during transaction, there's a limit
+ * to the number of bytes we can loopback in the test. I2C_PARAM_FIFO_DEPTH - 4
+ */
+static volatile const uint8_t kI2cByteCount = 0;
+
+static volatile bool tx_empty_irq_seen = false;
+static volatile bool cmd_complete_irq_seen = false;
+
+/**
+ * This constant indicates the number of interrupt requests.
+ */
+enum {
+  kNumI2cIrqs = 6,
+};
+
+typedef struct i2c_conf {
+  const int unsigned base_addr;
+  const uint32_t i2c_irq_fmt_threshold_id;
+  const top_matcha_plic_irq_id_t plic_irqs[kNumI2cIrqs];
+} i2c_conf_t;
+
+const i2c_conf_t i2c_configuration[] = {
+    {.base_addr = TOP_MATCHA_I2C0_BASE_ADDR,
+     .i2c_irq_fmt_threshold_id = kTopMatchaPlicIrqIdI2c0FmtThreshold,
+     .plic_irqs = {kTopMatchaPlicIrqIdI2c0CmdComplete,
+                   kTopMatchaPlicIrqIdI2c0TxStretch,
+                   kTopMatchaPlicIrqIdI2c0TxOverflow,
+                   kTopMatchaPlicIrqIdI2c0AcqFull,
+                   kTopMatchaPlicIrqIdI2c0UnexpStop,
+                   kTopMatchaPlicIrqIdI2c0HostTimeout}},
+    {.base_addr = TOP_MATCHA_I2C1_BASE_ADDR,
+     .i2c_irq_fmt_threshold_id = kTopMatchaPlicIrqIdI2c1FmtThreshold,
+     .plic_irqs = {kTopMatchaPlicIrqIdI2c1CmdComplete,
+                   kTopMatchaPlicIrqIdI2c1TxStretch,
+                   kTopMatchaPlicIrqIdI2c1TxOverflow,
+                   kTopMatchaPlicIrqIdI2c1AcqFull,
+                   kTopMatchaPlicIrqIdI2c1UnexpStop,
+                   kTopMatchaPlicIrqIdI2c1HostTimeout}},
+    {.base_addr = TOP_MATCHA_I2C2_BASE_ADDR,
+     .i2c_irq_fmt_threshold_id = kTopMatchaPlicIrqIdI2c2FmtThreshold,
+     .plic_irqs = {
+         kTopMatchaPlicIrqIdI2c2CmdComplete, kTopMatchaPlicIrqIdI2c2TxStretch,
+         kTopMatchaPlicIrqIdI2c2TxOverflow, kTopMatchaPlicIrqIdI2c2AcqFull,
+         kTopMatchaPlicIrqIdI2c2UnexpStop,
+         kTopMatchaPlicIrqIdI2c2HostTimeout}}};
+
+/**
+ * Provides external irq handling for this test.
+ *
+ * This function overrides the default OTTF external ISR.
+ */
+void ottf_external_isr(void) {
+  uint32_t i2c_irq_fmt_threshold_id;
+
+  plic_isr_ctx_t plic_ctx = {.rv_plic = &plic,
+                             .hart_id = kTopMatchaPlicTargetIbex0};
+
+  i2c_isr_ctx_t i2c_ctx = {
+      .i2c = &i2c,
+      .plic_i2c_start_irq_id =
+          i2c_configuration[kI2cIdx].i2c_irq_fmt_threshold_id,
+      .expected_irq = 0,
+      .is_only_irq = false};
+
+  top_matcha_plic_peripheral_t peripheral;
+  dif_i2c_irq_t i2c_irq;
+  isr_testutils_i2c_isr(plic_ctx, i2c_ctx, &peripheral, &i2c_irq);
+
+  switch (i2c_irq) {
+    case kDifI2cIrqTxStretch:
+      tx_empty_irq_seen = true;
+      i2c_irq = kDifI2cIrqTxStretch;
+      break;
+    case kDifI2cIrqCmdComplete:
+      cmd_complete_irq_seen = true;
+      i2c_irq = kDifI2cIrqCmdComplete;
+      break;
+    default:
+      LOG_ERROR("Unexpected interrupt (at I2C): %d", i2c_irq);
+      break;
+  }
+}
+
+void check_addr(uint8_t addr, dif_i2c_id_t id0, dif_i2c_id_t id1) {
+  CHECK(((addr & id0.mask) == id0.address) ||
+        ((addr & id1.mask) == id1.address));
+}
+
+bool test_main(void) {
+  LOG_INFO("Testing I2C index %d", kI2cIdx);
+
+  if (kI2cByteCount > I2C_PARAM_FIFO_DEPTH - 4) {
+    LOG_ERROR(
+        "Test cannot fit %d bytes, 2 START records, and 2 STOP records in "
+        "buffers of depth %d",
+        kI2cByteCount, I2C_PARAM_FIFO_DEPTH);
+  }
+
+  CHECK_DIF_OK(dif_i2c_init(
+      mmio_region_from_addr(i2c_configuration[kI2cIdx].base_addr), &i2c));
+
+  CHECK_DIF_OK(dif_pinmux_init(
+      mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux));
+
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &plic));
+
+  i2c_testutils_connect_i2c_to_pinmux_pins(&pinmux, kI2cIdx);
+
+  // Enable functional interrupts as well as error interrupts to make sure
+  // everything is behaving as expected.
+  for (uint32_t i = 0; i < kNumI2cIrqs; ++i) {
+    CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
+        &plic, i2c_configuration[kI2cIdx].plic_irqs[i],
+        kTopMatchaPlicTargetIbex0, kDifToggleEnabled));
+
+    // Assign a default priority
+    CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+        &plic, i2c_configuration[kI2cIdx].plic_irqs[i], kDifRvPlicMaxPriority));
+  }
+
+  // Enable the external IRQ at Ibex.
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // I2C speed parameters.
+  dif_i2c_timing_config_t timing_config = {
+      .lowest_target_device_speed = kDifI2cSpeedFastPlus,
+      .clock_period_nanos = kClockPeriodNanos,
+      .sda_rise_nanos = kI2cRiseFallNanos,
+      .sda_fall_nanos = kI2cRiseFallNanos,
+      .scl_period_nanos = kI2cClockPeriodNanos};
+
+  dif_i2c_config_t config;
+  CHECK_DIF_OK(dif_i2c_compute_timing(timing_config, &config));
+  CHECK_DIF_OK(dif_i2c_configure(&i2c, config));
+  dif_i2c_id_t id0 = {.mask = kI2cDeviceMask0, .address = kI2cDeviceAddress0},
+               id1 = {.mask = kI2cDeviceMask1, .address = kI2cDeviceAddress1};
+  CHECK_DIF_OK(dif_i2c_set_device_id(&i2c, &id0, &id1));
+  CHECK_DIF_OK(dif_i2c_device_set_enabled(&i2c, kDifToggleEnabled));
+
+  // TODO #15081, transaction complete may not be set by i2c device.
+  CHECK(!cmd_complete_irq_seen);
+
+  CHECK_DIF_OK(
+      dif_i2c_irq_set_enabled(&i2c, kDifI2cIrqTxStretch, kDifToggleEnabled));
+  CHECK_DIF_OK(
+      dif_i2c_irq_set_enabled(&i2c, kDifI2cIrqCmdComplete, kDifToggleEnabled));
+
+  // Randomize variables.
+  uint8_t expected_data[kI2cByteCount];
+  LOG_INFO("Loopback %d bytes with addresses %0h, %0h", kI2cByteCount,
+           kI2cDeviceAddress0, kI2cDeviceAddress1);
+
+  // Controlling the randomization from C side is a bit slow, but might be
+  // easier for portability to a different setup later.
+  for (uint32_t i = 0; i < kI2cByteCount; ++i) {
+    expected_data[i] = rand_testutils_gen32_range(0, 0xff);
+  };
+
+  uint8_t tx_fifo_lvl;
+  CHECK_DIF_OK(dif_i2c_get_fifo_levels(&i2c, NULL, NULL, &tx_fifo_lvl, NULL));
+  IBEX_SPIN_FOR(!(tx_fifo_lvl > 0 && tx_empty_irq_seen == false), 100);
+  i2c_testutils_target_rd(&i2c, kI2cByteCount, expected_data);
+  tx_empty_irq_seen = false;
+
+  LOG_INFO("Data written to fifo");
+
+  uint8_t acq_fifo_lvl;
+  do {
+    CHECK_DIF_OK(
+        dif_i2c_get_fifo_levels(&i2c, NULL, NULL, &tx_fifo_lvl, &acq_fifo_lvl));
+  } while (acq_fifo_lvl < 2);
+
+  CHECK(tx_fifo_lvl == 0);
+
+  uint8_t addr;
+  i2c_testutils_target_check_rd(&i2c, &addr, NULL);
+  check_addr(addr, id0, id1);
+
+  // Read data from i2c device.
+  i2c_testutils_target_wr(&i2c, kI2cByteCount);
+  do {
+    CHECK_DIF_OK(
+        dif_i2c_get_fifo_levels(&i2c, NULL, NULL, &tx_fifo_lvl, &acq_fifo_lvl));
+  } while (acq_fifo_lvl < kI2cByteCount + 2);  // acquired message, address and
+                                               // junk
+
+  uint8_t received_data[kI2cByteCount];
+  i2c_testutils_target_check_wr(&i2c, kI2cByteCount, &addr, received_data,
+                                NULL);
+  check_addr(addr, id0, id1);
+
+  for (uint8_t i = 0; i < kI2cByteCount; ++i) {
+    CHECK(expected_data[i] == received_data[i]);
+  }
+
+  CHECK(cmd_complete_irq_seen);
+
+  return true;
+}