Add chip_sw_spi_host_tx_rx and 2 other tests to edacloud

Change-Id: Ida365559c0b3bb280456fcb9bac6199c0ee22f74
diff --git a/hw/top_matcha/dv/chip_sim_cfg.hjson b/hw/top_matcha/dv/chip_sim_cfg.hjson
index 3a4964e..cc64188 100644
--- a/hw/top_matcha/dv/chip_sim_cfg.hjson
+++ b/hw/top_matcha/dv/chip_sim_cfg.hjson
@@ -808,13 +808,13 @@
     {
       name: chip_sw_spi_host_tx_rx
       uvm_test_seq: chip_sw_spi_host_tx_rx_vseq
-      sw_images: ["//sw/device/tests/sim_dv:spi_host_tx_rx_test:1"]
+      sw_images: ["//sw/device/tests/sim_dv:spi_host_tx_rx_test:1:matcha"]
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
       name: chip_sw_spi_device_pass_through
       uvm_test_seq: chip_sw_spi_passthrough_vseq
-      sw_images: ["//sw/device/tests/sim_dv:spi_passthrough_test:1"]
+      sw_images: ["//sw/device/tests/sim_dv:spi_passthrough_test:1:matcha"]
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
@@ -1981,7 +1981,7 @@
     {
       name: chip_sw_ast_clk_rst_inputs
       uvm_test_seq: chip_sw_ast_clk_rst_inputs_vseq
-      sw_images: ["//sw/device/tests/sim_dv:ast_clk_rst_inputs:1"]
+      sw_images: ["//sw/device/tests/sim_dv:ast_clk_rst_inputs:1:matcha"]
       en_run_modes: ["sw_test_mode_test_rom"]
       run_opts: ["+sw_test_timeout_ns=200_000_000"]
     }
@@ -2044,6 +2044,7 @@
         "chip_sw_smc_supervisor_mode_test",
         "chip_sw_smc_virtual_memory_test",
         "chip_sw_spi_device_tx_rx",
+        "chip_sw_spi_host_tx_rx",
         "chip_sw_sram_ctrl_smoketest",
         "chip_sw_uart_smoketest",
         "chip_sw_uart_tx_rx",
@@ -2060,6 +2061,7 @@
         "chip_sw_alert_handler_ping_timeout",
         "chip_sw_alert_test",
         "chip_sw_aon_timer_smoketest",
+        "chip_sw_ast_clk_rst_inputs",
         "chip_sw_cam_ctrl_test",
         "chip_sw_clkmgr_off_peri",
         "chip_sw_clkmgr_smoketest",
@@ -2115,7 +2117,9 @@
         "chip_sw_smc_rv_timer_test",
         "chip_sw_smc_supervisor_mode_test",
         "chip_sw_smc_virtual_memory_test",
+        "chip_sw_spi_device_pass_through",
         "chip_sw_spi_device_tx_rx",
+        "chip_sw_spi_host_tx_rx",
         "chip_sw_sram_ctrl_smoketest",
         "chip_sw_uart_smoketest",
         "chip_sw_uart_tx_rx",
@@ -2134,6 +2138,7 @@
         "chip_sw_alert_handler_ping_timeout",
         "chip_sw_alert_test",
         "chip_sw_aon_timer_smoketest",
+        "chip_sw_ast_clk_rst_inputs",
         "chip_sw_atomics_test",
         "chip_sw_cam_ctrl_test",
         "chip_sw_clkmgr_off_peri",
@@ -2201,7 +2206,9 @@
         "chip_sw_smc_supervisor_mode_test",
         "chip_sw_smc_tlul_mailbox_test",
         "chip_sw_smc_virtual_memory_test",
+        "chip_sw_spi_device_pass_through",
         "chip_sw_spi_device_tx_rx",
+        "chip_sw_spi_host_tx_rx",
         "chip_sw_sram_ctrl_smoketest",
         "chip_sw_uart_smoketest",
         "chip_sw_uart_tx_rx",
diff --git a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv
index 392e9f4..e67a4f9 100644
--- a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_tx_rx_vseq.sv
+++ b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_host_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
@@ -57,6 +58,10 @@
     // enable spi agent
     cfg.chip_vif.enable_spi_device(.inst_num(spi_host_idx), .enable(1));
 
+    // Wait until we reach the SW test state.
+    `DV_WAIT(cfg.sw_test_status_vif.sw_test_status == SwTestStatusInTest)
+    cfg.chip_vif.gpios_if.disconnect();
+
     // Wait for the sw to finish configuring the spi_host DUT
     `DV_WAIT(cfg.sw_logger_vif.printed_log == "spi host configuration complete",
              "Timedout waiting for spi host c configuration.")
diff --git a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv
index 7b5b905..a4d10cc 100644
--- a/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_vseq.sv
+++ b/hw/top_matcha/dv/env/seq_lib/chip_sw_spi_passthrough_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
@@ -158,6 +159,8 @@
     `DV_WAIT(cfg.sw_test_status_vif.sw_test_status == SwTestStatusInTest,
              "Timed out waiting for SwTestStatusInTest",
              10_000_000)
+    cfg.chip_vif.gpios_if.disconnect();
+
     `DV_WAIT(cfg.sw_logger_vif.printed_log == "Test setup complete.",
              "Timeout waiting for software to complete setup.")
 
diff --git a/sw/device/lib/testing/pinmux_testutils.c b/sw/device/lib/testing/pinmux_testutils.c
index d71a23e..8a058b2 100644
--- a/sw/device/lib/testing/pinmux_testutils.c
+++ b/sw/device/lib/testing/pinmux_testutils.c
@@ -106,3 +106,16 @@
 }
 
 uint32_t pinmux_testutils_get_testable_gpios_mask(void) { return 0xffffffff; }
+
+void pinmux_testutils_configure_pads(const dif_pinmux_t *pinmux,
+                                     const pinmux_pad_attributes_t *attrs,
+                                     size_t num_attrs) {
+  for (size_t i = 0; i < num_attrs; ++i) {
+    dif_pinmux_pad_attr_t desired_attr, actual_attr;
+    CHECK_DIF_OK(dif_pinmux_pad_get_attrs(pinmux, attrs[i].pad, attrs[i].kind,
+                                          &desired_attr));
+    desired_attr.flags = attrs[i].flags;
+    CHECK_DIF_OK(dif_pinmux_pad_write_attrs(pinmux, attrs[i].pad, attrs[i].kind,
+                                            desired_attr, &actual_attr));
+  }
+}
diff --git a/sw/device/lib/testing/pinmux_testutils.h b/sw/device/lib/testing/pinmux_testutils.h
index 6b401b4..5028683 100644
--- a/sw/device/lib/testing/pinmux_testutils.h
+++ b/sw/device/lib/testing/pinmux_testutils.h
@@ -33,4 +33,20 @@
  */
 uint32_t pinmux_testutils_get_testable_gpios_mask(void);
 
+/**
+ * A convenience struct to associate pad attributes with a specific pad.
+ */
+typedef struct pinmux_pad_attributes {
+  dif_pinmux_index_t pad;
+  dif_pinmux_pad_kind_t kind;
+  dif_pinmux_pad_attr_flags_t flags;
+} pinmux_pad_attributes_t;
+
+/**
+ * Configures several pad attributes.
+ */
+void pinmux_testutils_configure_pads(const dif_pinmux_t *pinmux,
+                                     const pinmux_pad_attributes_t *attrs,
+                                     size_t num_attrs);
+
 #endif  // SW_DEVICE_LIB_TESTING_PINMUX_TESTUTILS_H_
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index 8c516e3..99a1de3 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -181,6 +181,57 @@
     ],
 )
 
+matcha_dv_test(
+    name = "ast_clk_rst_inputs",
+    srcs = ["ast_clk_rst_inputs.c"],
+    deps = [
+        "//sw/device/lib/dif:alert_handler",
+        "//sw/device/lib/dif:clkmgr",
+        "//sw/device/lib/dif:rstmgr",
+        "//sw/device/lib/dif:rv_plic_sec",
+        "//sw/device/lib/testing:clkmgr_testutils",
+        "//sw/device/lib/testing:isr_testutils",
+        "//sw/device/tests:test_dv_lib",
+        "@lowrisc_opentitan//sw/device/lib/dif:aon_timer",
+        "@lowrisc_opentitan//sw/device/lib/dif:entropy_src",
+        "@lowrisc_opentitan//sw/device/lib/dif:pwrmgr",
+        "@lowrisc_opentitan//sw/device/lib/dif:sensor_ctrl",
+        "@lowrisc_opentitan//sw/device/lib/testing:alert_handler_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:aon_timer_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:pwrmgr_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:rstmgr_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:rv_plic_testutils",
+    ],
+)
+
+matcha_dv_test(
+    name = "spi_host_tx_rx_test",
+    srcs = ["spi_host_tx_rx_test.c"],
+    deps = [
+        "//sw/device/lib/dif:pinmux",
+        "//sw/device/lib/testing:pinmux_testutils",
+        "//sw/device/tests:test_dv_lib",
+        "@lowrisc_opentitan//sw/device/lib/dif:spi_host",
+        "@lowrisc_opentitan//sw/device/lib/testing:rand_testutils",
+    ],
+)
+
+matcha_dv_test(
+    name = "spi_passthrough_test",
+    srcs = ["spi_passthrough_test.c"],
+    deps = [
+        "//sw/device/lib/dif:pinmux",
+        "//sw/device/lib/dif:rv_plic_sec",
+        "//sw/device/lib/testing:pinmux_testutils",
+        "//sw/device/tests:test_dv_lib",
+        "@lowrisc_opentitan//sw/device/lib/base:bitfield",
+        "@lowrisc_opentitan//sw/device/lib/dif:spi_device",
+        "@lowrisc_opentitan//sw/device/lib/dif:spi_host",
+        "@lowrisc_opentitan//sw/device/lib/testing:spi_device_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:spi_flash_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/ast_clk_rst_inputs.c b/sw/device/tests/sim_dv/ast_clk_rst_inputs.c
new file mode 100644
index 0000000..7f863db
--- /dev/null
+++ b/sw/device/tests/sim_dv/ast_clk_rst_inputs.c
@@ -0,0 +1,578 @@
+// 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 "sensor_ctrl_regs.h"
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_alert_handler.h"
+#include "sw/device/lib/dif/dif_aon_timer.h"
+#include "sw/device/lib/dif/dif_clkmgr.h"
+#include "sw/device/lib/dif/dif_entropy_src.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/dif/dif_sensor_ctrl.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/alert_handler_testutils.h"
+#include "sw/device/lib/testing/aon_timer_testutils.h"
+#include "sw/device/lib/testing/autogen/isr_testutils.h"
+#include "sw/device/lib/testing/clkmgr_testutils.h"
+#include "sw/device/lib/testing/pwrmgr_testutils.h"
+#include "sw/device/lib/testing/rstmgr_testutils.h"
+#include "sw/device/lib/testing/rv_plic_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+
+#define kAlertSet true
+#define kAlertClear false
+#define kAlertVal7 7
+#define kAlertVal8 8
+#define kDifNoWakeup 0
+
+OTTF_DEFINE_TEST_CONFIG();
+
+static volatile const uint8_t kNumLowPowerSamples;
+static volatile const uint8_t kNumNormalPowerSamples;
+static volatile const uint8_t kWakeUpTimeInUs;
+
+static volatile const uint8_t kChannel0MaxLowByte;
+static volatile const uint8_t kChannel0MaxHighByte;
+static volatile const uint8_t kChannel0MinLowByte;
+static volatile const uint8_t kChannel0MinHighByte;
+
+static volatile const uint8_t kChannel1MaxLowByte;
+static volatile const uint8_t kChannel1MaxHighByte;
+static volatile const uint8_t kChannel1MinLowByte;
+static volatile const uint8_t kChannel1MinHighByte;
+
+static const uint32_t kPlicTarget = kTopMatchaPlicTargetIbex0;
+
+static dif_sensor_ctrl_t sensor_ctrl;
+static dif_alert_handler_t alert_handler;
+static dif_aon_timer_t aon_timer;
+static dif_rv_plic_t rv_plic;
+static dif_rv_plic_t plic;
+static dif_pwrmgr_t pwrmgr;
+static dif_rstmgr_t rstmgr;
+static dif_entropy_src_t entropy_src;
+static dif_pwrmgr_wakeup_reason_t wakeup_reason;
+
+static dif_clkmgr_t clkmgr;
+static dif_adc_ctrl_t adc_ctrl;
+
+static volatile bool interrupt_serviced = false;
+static bool first_adc_setup = true;
+
+static plic_isr_ctx_t plic_ctx = {.rv_plic = &plic,
+                                  .hart_id = kTopMatchaPlicTargetIbex0};
+
+static pwrmgr_isr_ctx_t pwrmgr_isr_ctx = {
+    .pwrmgr = &pwrmgr,
+    .plic_pwrmgr_start_irq_id = kTopMatchaPlicIrqIdPwrmgrAonWakeup,
+    .expected_irq = kDifPwrmgrIrqWakeup,
+    .is_only_irq = true};
+
+enum {
+  /**
+   * The size of the buffer used in firmware to process the entropy bits in
+   * firmware override mode.
+   */
+  kEntropyFifoBufferSize = 32,
+};
+
+enum {
+  kPowerUpTimeInUs = 30,
+};
+
+static uint32_t read_fifo_depth(dif_entropy_src_t *entropy) {
+  uint32_t fifo_depth = 0;
+  CHECK_DIF_OK(dif_entropy_src_get_fifo_depth(entropy, &fifo_depth));
+  return fifo_depth;
+}
+
+static uint32_t get_events(dif_toggle_t fatal) {
+  dif_sensor_ctrl_events_t events = 0;
+  if (dif_toggle_to_bool(fatal)) {
+    CHECK_DIF_OK(dif_sensor_ctrl_get_fatal_events(&sensor_ctrl, &events));
+  } else {
+    CHECK_DIF_OK(dif_sensor_ctrl_get_recov_events(&sensor_ctrl, &events));
+  }
+  return events;
+}
+
+/**
+ *  Clear event trigger and recoverable status.
+ */
+static void clear_event(uint32_t idx, dif_toggle_t fatal) {
+  CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx,
+                                                     kDifToggleDisabled));
+  if (!dif_toggle_to_bool(fatal)) {
+    CHECK_DIF_OK(dif_sensor_ctrl_clear_recov_event(&sensor_ctrl, idx));
+  }
+}
+
+/**
+ *  Check alert cause registers are correctly set
+ */
+static void check_alert_state(dif_toggle_t fatal) {
+  bool fatal_cause = false;
+  bool recov_cause = false;
+
+  CHECK_DIF_OK(dif_alert_handler_alert_is_cause(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert, &fatal_cause));
+
+  CHECK_DIF_OK(dif_alert_handler_alert_is_cause(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert, &recov_cause));
+
+  if (dif_toggle_to_bool(fatal)) {
+    CHECK(fatal_cause & !recov_cause,
+          "Fatal alert not correctly observed in alert handler");
+  } else {
+    CHECK(recov_cause & !fatal_cause,
+          "Recov alert not correctly observed in alert handler");
+  }
+
+  CHECK_DIF_OK(dif_alert_handler_alert_acknowledge(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert));
+  CHECK_DIF_OK(dif_alert_handler_alert_acknowledge(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert));
+};
+
+/**
+ *  First configure fatality of the desired event.
+ *  Then trigger the event from sensor_ctrl to ast.
+ *  Next poll for setting of correct events inside sensor_ctrl status.
+ *  When a recoverable event is triggerd, make sure only recoverable
+ *  status is seen, likewise for fatal events.
+ *  Finally, check for correct capture of cause in alert handler.
+ */
+static void test_event(uint32_t idx, dif_toggle_t fatal, bool set_event) {
+  if (set_event) {
+    // Configure event fatality
+    CHECK_DIF_OK(dif_sensor_ctrl_set_alert_fatal(&sensor_ctrl, idx, fatal));
+
+    // Trigger event
+    CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx,
+                                                       kDifToggleEnabled));
+    // wait for events to set
+    IBEX_SPIN_FOR(get_events(fatal) > 0, 1);
+
+    // Check for the event in ast sensor_ctrl
+    // if the event is not set, error
+    CHECK(((get_events(fatal) >> idx) & 0x1) == 1,
+          "Event %d not observed in AST", idx);
+
+    // check the opposite fatality setting, should not be set
+    CHECK(((get_events(!fatal) >> idx) & 0x1) == 0,
+          "Event %d observed in AST when it should not be", idx);
+  } else {
+    // clear event trigger
+    clear_event(idx, fatal);
+
+    // check whether alert handler captured the event
+    check_alert_state(fatal);
+  }
+};
+
+void init_units() {
+  CHECK_DIF_OK(dif_pwrmgr_init(
+      mmio_region_from_addr(TOP_MATCHA_PWRMGR_AON_BASE_ADDR), &pwrmgr));
+  CHECK_DIF_OK(dif_rstmgr_init(
+      mmio_region_from_addr(TOP_MATCHA_RSTMGR_AON_BASE_ADDR), &rstmgr));
+  CHECK_DIF_OK(dif_entropy_src_init(
+      mmio_region_from_addr(TOP_MATCHA_ENTROPY_SRC_BASE_ADDR), &entropy_src));
+  CHECK_DIF_OK(dif_aon_timer_init(
+      mmio_region_from_addr(TOP_MATCHA_AON_TIMER_AON_BASE_ADDR), &aon_timer));
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic));
+  CHECK_DIF_OK(dif_sensor_ctrl_init(
+      mmio_region_from_addr(TOP_MATCHA_SENSOR_CTRL_BASE_ADDR), &sensor_ctrl));
+  CHECK_DIF_OK(dif_alert_handler_init(
+      mmio_region_from_addr(TOP_MATCHA_ALERT_HANDLER_BASE_ADDR),
+      &alert_handler));
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &plic));
+  CHECK_DIF_OK(dif_clkmgr_init(
+      mmio_region_from_addr(TOP_MATCHA_CLKMGR_AON_BASE_ADDR), &clkmgr));
+  CHECK_DIF_OK(dif_adc_ctrl_init(
+      mmio_region_from_addr(TOP_MATCHA_ADC_CTRL_AON_BASE_ADDR), &adc_ctrl));
+}
+
+/**
+ *  configure adc module
+ */
+static void configure_adc_ctrl(const dif_adc_ctrl_t *adc_ctrl) {
+  int wake_up_time_aon_cycles;
+  int power_up_time_aon_cycles;
+
+  power_up_time_aon_cycles =
+      aon_timer_testutils_get_aon_cycles_from_us(kPowerUpTimeInUs);
+  wake_up_time_aon_cycles =
+      aon_timer_testutils_get_aon_cycles_from_us(kWakeUpTimeInUs);
+  CHECK_DIF_OK(dif_adc_ctrl_set_enabled(adc_ctrl, kDifToggleDisabled));
+  CHECK_DIF_OK(dif_adc_ctrl_reset(adc_ctrl));
+  CHECK_DIF_OK(dif_adc_ctrl_configure(
+      adc_ctrl, (dif_adc_ctrl_config_t){
+                    .mode = kDifAdcCtrlLowPowerScanMode,
+                    .num_low_power_samples = kNumLowPowerSamples,
+                    .num_normal_power_samples = kNumNormalPowerSamples,
+                    .power_up_time_aon_cycles = power_up_time_aon_cycles,
+                    .wake_up_time_aon_cycles = wake_up_time_aon_cycles}));
+}
+
+static void en_plic_irqs(dif_rv_plic_t *plic) {
+  top_matcha_plic_irq_id_t plic_irqs[] = {
+      kTopMatchaPlicIrqIdAdcCtrlAonMatchDone};
+
+  for (uint32_t i = 0; i < ARRAYSIZE(plic_irqs); ++i) {
+    CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
+        plic, plic_irqs[i], kTopMatchaPlicTargetIbex0, kDifToggleEnabled));
+
+    // Assign a default priority
+    CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, plic_irqs[i], 0x1));
+  }
+
+  // Enable the external IRQ at Ibex.
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+}
+
+void adc_setup(bool first_adc_setup) {
+  // Enable adc interrupts.
+  CHECK_DIF_OK(dif_adc_ctrl_irq_set_enabled(&adc_ctrl, kDifAdcCtrlIrqMatchDone,
+                                            kDifToggleEnabled));
+
+  uint16_t channel0_filter0_max =
+      (kChannel0MaxHighByte << 8) | kChannel0MaxLowByte;
+  uint16_t channel0_filter0_min =
+      (kChannel0MinHighByte << 8) | kChannel0MinLowByte;
+  uint16_t channel1_filter0_max =
+      (kChannel1MaxHighByte << 8) | kChannel1MaxLowByte;
+  uint16_t channel1_filter0_min =
+      (kChannel1MinHighByte << 8) | kChannel1MinLowByte;
+
+  if (first_adc_setup) {
+    // Setup ADC configuration.
+    configure_adc_ctrl(&adc_ctrl);
+  } else {
+    CHECK_DIF_OK(dif_adc_ctrl_reset(&adc_ctrl));
+  }
+
+  en_plic_irqs(&plic);
+  // Setup ADC filters. There is one filter for each channel.
+  CHECK_DIF_OK(dif_adc_ctrl_configure_filter(
+      &adc_ctrl, kDifAdcCtrlChannel0,
+      (dif_adc_ctrl_filter_config_t){.filter = kDifAdcCtrlFilter0,
+                                     .generate_irq_on_match = true,
+                                     .generate_wakeup_on_match = true,
+                                     .in_range = true,
+                                     .max_voltage = channel0_filter0_max,
+                                     .min_voltage = channel0_filter0_min},
+      kDifToggleDisabled));
+  CHECK_DIF_OK(dif_adc_ctrl_configure_filter(
+      &adc_ctrl, kDifAdcCtrlChannel1,
+      (dif_adc_ctrl_filter_config_t){.filter = kDifAdcCtrlFilter0,
+                                     .generate_irq_on_match = true,
+                                     .generate_wakeup_on_match = true,
+                                     .in_range = true,
+                                     .max_voltage = channel1_filter0_max,
+                                     .min_voltage = channel1_filter0_min},
+      kDifToggleDisabled));
+
+  // enable filters.
+  CHECK_DIF_OK(dif_adc_ctrl_filter_set_enabled(
+      &adc_ctrl, kDifAdcCtrlChannel0, kDifAdcCtrlFilter0, kDifToggleEnabled));
+  CHECK_DIF_OK(dif_adc_ctrl_filter_set_enabled(
+      &adc_ctrl, kDifAdcCtrlChannel1, kDifAdcCtrlFilter0, kDifToggleEnabled));
+
+  if (first_adc_setup) {
+    CHECK_DIF_OK(dif_adc_ctrl_set_enabled(&adc_ctrl, kDifToggleEnabled));
+  }
+}
+
+void entropy_config(dif_entropy_src_config_t entropy_src_config) {
+  CHECK_DIF_OK(dif_entropy_src_set_enabled(&entropy_src, kDifToggleDisabled));
+
+  const dif_entropy_src_fw_override_config_t fw_override_config = {
+      .entropy_insert_enable = true,
+      .buffer_threshold = kEntropyFifoBufferSize,
+  };
+
+  CHECK_DIF_OK(dif_entropy_src_fw_override_configure(
+      &entropy_src, fw_override_config, kDifToggleEnabled));
+
+  CHECK_DIF_OK(dif_entropy_src_configure(&entropy_src, entropy_src_config,
+                                         kDifToggleEnabled));
+}
+
+void ast_enter_sleep_states_and_check_functionality(
+    dif_pwrmgr_domain_config_t pwrmgr_config,
+    dif_entropy_src_config_t entropy_src_config, uint32_t alert_idx) {
+  bool deepsleep;
+  int read_fifo_depth_val;
+  bool clock_check_status;
+  uint32_t unhealthy_fifos, errors, alerts;
+
+  const dif_edn_t edn0 = {.base_addr =
+                              mmio_region_from_addr(TOP_MATCHA_EDN0_BASE_ADDR)};
+  const dif_edn_t edn1 = {.base_addr =
+                              mmio_region_from_addr(TOP_MATCHA_EDN1_BASE_ADDR)};
+
+  if ((pwrmgr_config & (~kDifPwrmgrDomainOptionUsbClockInActivePower)) == 0) {
+    deepsleep = true;
+  } else {
+    deepsleep = false;
+  }
+
+  if (pwrmgr_testutils_is_wakeup_reason(&pwrmgr, kDifNoWakeup)) {
+    entropy_config(entropy_src_config);
+
+    // Verify that the FIFO depth is non-zero via SW - indicating the reception
+    // of data over the AST RNG interface.
+    IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000);
+
+    // test recoverable event
+    test_event(alert_idx, kDifToggleDisabled, kAlertSet);
+
+    // Enable all the AON interrupts used in this test.
+    rv_plic_testutils_irq_range_enable(&rv_plic, kTopMatchaPlicTargetIbex0,
+                                       kTopMatchaPlicIrqIdAdcCtrlAonMatchDone,
+                                       kTopMatchaPlicIrqIdAdcCtrlAonMatchDone);
+    CHECK_DIF_OK(dif_pwrmgr_irq_set_enabled(&pwrmgr, 0, kDifToggleEnabled));
+
+    // Setup low power.
+    rstmgr_testutils_pre_reset(&rstmgr);
+
+    if (!deepsleep) {
+      // read fifo depth before enter sleep mode
+      read_fifo_depth_val = read_fifo_depth(&entropy_src);
+    }
+
+    // Configure ADC
+    adc_setup(first_adc_setup);
+
+    // set sleep mode
+    pwrmgr_testutils_enable_low_power(&pwrmgr, kDifPwrmgrWakeupRequestSourceTwo,
+                                      pwrmgr_config);
+
+    // Enter low power mode.
+    LOG_INFO("Issued WFI to enter sleep.");
+
+    wait_for_interrupt();
+
+    // Interrupt should have been serviced.
+    CHECK(interrupt_serviced);
+
+    interrupt_serviced = false;
+
+  } else if (pwrmgr_testutils_is_wakeup_reason(
+                 &pwrmgr, kDifPwrmgrWakeupRequestSourceTwo)) {
+    if (deepsleep) {
+      if (read_fifo_depth(&entropy_src) != 0)
+        LOG_ERROR("read_fifo_depth after reset=%0d should be 0",
+                  read_fifo_depth(&entropy_src));
+
+      first_adc_setup = false;
+      // Configure ADC after deep sleep
+      adc_setup(first_adc_setup);
+    }
+
+    entropy_config(entropy_src_config);
+
+    IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000);
+  }
+
+  if (!deepsleep) {
+    if (read_fifo_depth_val >= read_fifo_depth(&entropy_src))
+      LOG_ERROR(
+          "read_fifo_depth after exit from idle=%0d should be equal/greater "
+          "than previous read value (%0d)",
+          read_fifo_depth(&entropy_src), read_fifo_depth_val);
+  }
+
+  IBEX_SPIN_FOR(read_fifo_depth(&entropy_src) > 0, 1000);
+
+  test_event(alert_idx, kDifToggleDisabled, kAlertClear);
+
+  CHECK_DIF_OK(dif_pwrmgr_wakeup_reason_clear(&pwrmgr));
+
+  // test event after exit from low power
+  test_event(alert_idx, kDifToggleDisabled, kAlertSet);
+  test_event(alert_idx, kDifToggleDisabled, kAlertClear);
+
+  // verify there are no any edn alerts/errors
+  CHECK_DIF_OK(dif_edn_get_errors(&edn0, &unhealthy_fifos, &errors));
+  CHECK_DIF_OK(dif_edn_get_recoverable_alerts(&edn0, &alerts));
+  if (unhealthy_fifos != 0 || errors != 0 || alerts != 0)
+    LOG_ERROR("edn0: error=0x%x, unhealthy_fifos=0x%x, alerts=0x%x", errors,
+              unhealthy_fifos, alerts);
+
+  CHECK_DIF_OK(dif_edn_get_errors(&edn1, &unhealthy_fifos, &errors));
+  CHECK_DIF_OK(dif_edn_get_recoverable_alerts(&edn1, &alerts));
+  if (unhealthy_fifos != 0 || errors != 0 || alerts != 0)
+    LOG_ERROR("edn1: error=0x%x, unhealthy_fifos=0x%x, alerts=0x%x", errors,
+              unhealthy_fifos, alerts);
+}
+
+/**
+ *  set edn auto mode
+ */
+void set_edn_auto_mode() {
+  const dif_edn_t edn0 = {.base_addr =
+                              mmio_region_from_addr(TOP_MATCHA_EDN0_BASE_ADDR)};
+  const dif_edn_t edn1 = {.base_addr =
+                              mmio_region_from_addr(TOP_MATCHA_EDN1_BASE_ADDR)};
+
+  // entropy_testutils_stop_all();
+  CHECK_DIF_OK(dif_edn_stop(&edn0));
+  CHECK_DIF_OK(dif_edn_stop(&edn1));
+
+  // Re-enable EDN0 in auto mode.
+  const dif_edn_auto_params_t edn0_params = {
+      // EDN0 provides lower-quality entropy.  Let one generate command return 8
+      // blocks, and reseed every 32 generates.
+      .instantiate_cmd =
+          {
+              .cmd = 0x00000001 |  // Reseed from entropy source only.
+                     kMultiBitBool4False << 8,
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .reseed_cmd =
+          {
+              .cmd = 0x00008002 |  // One generate returns 8 blocks, reseed
+                                   // from entropy source only.
+                     kMultiBitBool4False << 8,
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .generate_cmd =
+          {
+              .cmd = 0x00008003,  // One generate returns 8 blocks.
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .reseed_interval = 32,  // Reseed every 32 generates.
+  };
+  CHECK_DIF_OK(dif_edn_set_auto_mode(&edn0, edn0_params));
+
+  // Re-enable EDN1 in auto mode.
+  const dif_edn_auto_params_t edn1_params = {
+      // EDN1 provides highest-quality entropy.  Let one generate command
+      // return 1 block, and reseed after every generate.
+      .instantiate_cmd =
+          {
+              .cmd = 0x00000001 |  // Reseed from entropy source only.
+                     kMultiBitBool4False << 8,
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .reseed_cmd =
+          {
+              .cmd = 0x00001002 |  // One generate returns 1 block, reseed
+                                   // from entropy source only.
+                     kMultiBitBool4False << 8,
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .generate_cmd =
+          {
+              .cmd = 0x00001003,  // One generate returns 1 block.
+              .seed_material =
+                  {
+                      .len = 0,
+                  },
+          },
+      .reseed_interval = 4,  // Reseed after every 4 generates.
+  };
+  CHECK_DIF_OK(dif_edn_set_auto_mode(&edn1, edn1_params));
+}
+
+void ottf_external_isr(void) {
+  plic_isr_ctx_t plic_ctx = {.rv_plic = &plic,
+                             .hart_id = kTopMatchaPlicTargetIbex0};
+
+  adc_ctrl_isr_ctx_t adc_ctrl_ctx = {
+      .adc_ctrl = &adc_ctrl,
+      .plic_adc_ctrl_start_irq_id = kTopMatchaPlicIrqIdAdcCtrlAonMatchDone,
+      .expected_irq = 0,
+      .is_only_irq = true};
+
+  top_matcha_plic_peripheral_t peripheral;
+  dif_adc_ctrl_irq_t adc_ctrl_irq;
+  isr_testutils_adc_ctrl_isr(plic_ctx, adc_ctrl_ctx, &peripheral,
+                             &adc_ctrl_irq);
+
+  CHECK(peripheral == kTopMatchaPlicPeripheralAdcCtrlAon);
+  CHECK(adc_ctrl_irq == kDifAdcCtrlIrqMatchDone);
+  interrupt_serviced = true;
+}
+
+bool test_main() {
+  uint32_t event_idx;
+  int idx = 1;
+  int read_fifo_depth_val;
+
+  bool deepsleep;
+  dif_pwrmgr_domain_config_t pwrmgr_config;
+
+  const dif_entropy_src_config_t entropy_src_config = {
+      .fips_enable = true,
+      // Route the entropy data received from RNG to the FIFO.
+      .route_to_firmware = true,
+      .single_bit_mode = kDifEntropySrcSingleBitModeDisabled,
+      .health_test_threshold_scope = false,
+      .health_test_window_size = 0x0200,
+      .alert_threshold = 2,
+  };
+
+  init_units();
+
+  set_edn_auto_mode();
+  CHECK_DIF_OK(dif_clkmgr_jitter_set_enabled(&clkmgr, kDifToggleEnabled));
+
+  // Enable both recoverable and fatal alerts
+  CHECK_DIF_OK(dif_alert_handler_configure_alert(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlRecovAlert,
+      kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled));
+  CHECK_DIF_OK(dif_alert_handler_configure_alert(
+      &alert_handler, kTopMatchaAlertIdSensorCtrlFatalAlert,
+      kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled));
+
+  LOG_INFO("1 test alert/rng after Deep sleep 1");
+  pwrmgr_config = kDifPwrmgrDomainOptionUsbClockInActivePower;
+  ast_enter_sleep_states_and_check_functionality(
+      pwrmgr_config, entropy_src_config, kAlertVal7);
+
+  LOG_INFO("2 test alert/rng after regular sleep (usb clk enabled)");
+  LOG_INFO("force new adc conv set");
+  pwrmgr_config = kDifPwrmgrDomainOptionUsbClockInActivePower |
+                  kDifPwrmgrDomainOptionUsbClockInLowPower |
+                  kDifPwrmgrDomainOptionMainPowerInLowPower;
+  ast_enter_sleep_states_and_check_functionality(
+      pwrmgr_config, entropy_src_config, kAlertVal8);
+
+  LOG_INFO("3 test alert/rng after regular sleep (all clk disabled in lp)");
+  LOG_INFO("force new adc conv set");
+  pwrmgr_config = kDifPwrmgrDomainOptionMainPowerInLowPower |
+                  kDifPwrmgrDomainOptionUsbClockInActivePower;
+  ast_enter_sleep_states_and_check_functionality(
+      pwrmgr_config, entropy_src_config, kAlertVal7);
+
+  LOG_INFO("c code is finished");
+
+  return true;
+}
diff --git a/sw/device/tests/sim_dv/spi_host_tx_rx_test.c b/sw/device/tests/sim_dv/spi_host_tx_rx_test.c
new file mode 100644
index 0000000..b413cb3
--- /dev/null
+++ b/sw/device/tests/sim_dv/spi_host_tx_rx_test.c
@@ -0,0 +1,268 @@
+// 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_pinmux.h"
+#include "sw/device/lib/dif/dif_spi_host.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/pinmux_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"
+
+OTTF_DEFINE_TEST_CONFIG();
+
+/**
+ * Indicates the spi_host instance under test.
+ *
+ * This constant is backdoor_overwritten by the vseq.
+ * (See comment in uart_tx_rx_test.c for details on 'static volatile const'
+ * qualifiers)
+ */
+static volatile const uint8_t kSPIHostIdx = 0x0;
+
+#define DATA_SET_SIZE 16
+
+static dif_spi_host_t spi_host;
+static dif_pinmux_t pinmux;
+
+static const top_matcha_direct_pads_t spi_host0_direct_pads[6] = {
+    kTopMatchaDirectPadsSpiHost0Sck,   // sck
+    kTopMatchaDirectPadsSpiHost0Csb,   // csb
+    kTopMatchaDirectPadsSpiHost0Sd3,   // sio[3]
+    kTopMatchaDirectPadsSpiHost0Sd2,   // sio[2]
+    kTopMatchaDirectPadsSpiHost0Sd1,   // sio[1]
+    kTopMatchaDirectPadsSpiHost0Sd0};  // sio[0]
+
+// pinmap defined in chip_if.sv (spi_device1_if)
+static const top_matcha_muxed_pads_t spi_host1_muxed_pads[6] = {
+    kTopMatchaMuxedPadsIob0,  // sck
+    kTopMatchaMuxedPadsIob1,  // csb
+    kTopMatchaMuxedPadsIob6,  // sio[3]
+    kTopMatchaMuxedPadsIob5,  // sio[2]
+    kTopMatchaMuxedPadsIob4,  // sio[1]
+    kTopMatchaMuxedPadsIob3,  // sio[0]
+};
+
+// For spi_host1
+// sck       output
+// csb       output
+// sio[0:3]  bidir (input+output)
+
+/** To setup the pinmux using the enum's in top_matcha.h ...
+ *
+ *                                             - Choose corresponding pad/periph
+ * from...
+ *
+ * dif_result_t dif_pinmux_output_select(...,
+ *   dif_pinmux_index_t mio_pad_output,        | top_matcha_pinmux_mio_out_t
+ *   dif_pinmux_index_t outsel)                | top_matcha_pinmux_outsel_t
+ * dif_result_t dif_pinmux_input_select(...,
+ *   dif_pinmux_index_t peripheral_input,      |
+ * top_matcha_pinmux_peripheral_in_t dif_pinmux_index_t insel) |
+ * top_matcha_pinmux_insel_t
+ *
+ */
+
+typedef struct pinmux_select {
+  dif_pinmux_index_t pad;
+  dif_pinmux_index_t peripheral;
+} pinmux_select_t;
+
+static const pinmux_select_t pinmux_out_config[] = {
+    // spi_host1
+    {
+        .pad = kTopMatchaPinmuxMioOutIob0,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sck,  // SCK
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob1,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Csb,  // CSB
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob3,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0,  // sio[0]
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob4,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1,  // sio[1]
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob5,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2,  // sio[2]
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob6,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3,  // sio[3]
+    },
+};
+
+static const pinmux_select_t pinmux_in_config[] = {
+    // spi_host1
+    {
+        .pad = kTopMatchaPinmuxInselIob3,
+        .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd0,  // sio[0]
+    },
+    {
+        .pad = kTopMatchaPinmuxInselIob4,
+        .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd1,  // sio[1]
+    },
+    {
+        .pad = kTopMatchaPinmuxInselIob5,
+        .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd2,  // sio[2]
+    },
+    {
+        .pad = kTopMatchaPinmuxInselIob6,
+        .peripheral = kTopMatchaPinmuxPeripheralInSpiHost1Sd3,  // sio[3]
+    },
+};
+
+/**
+ * Initialize the provided SPI host.
+ */
+void init_spi_host(dif_spi_host_t *spi_host,
+                   uint32_t peripheral_clock_freq_hz) {
+  dif_spi_host_config_t config = {
+      .spi_clock = peripheral_clock_freq_hz / 2,
+      .peripheral_clock_freq_hz = peripheral_clock_freq_hz,
+      .chip_select = {.idle = 2, .trail = 2, .lead = 2},
+      .full_cycle = true,
+      .cpha = true,
+      .cpol = true,
+  };
+  CHECK_DIF_OK(dif_spi_host_configure(spi_host, config));
+  CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true));
+}
+
+/**
+ * Setup pads for spi_host0
+ *
+ * This peripheral is 'direct' connected to the pads.
+ */
+void setup_pads_spi_host0(void) {
+  // set weak pull-ups for all the pads
+  dif_pinmux_pad_attr_t out_attr;
+  dif_pinmux_pad_attr_t in_attr = {
+      .slew_rate = 0,
+      .drive_strength = 0,
+      .flags = kDifPinmuxPadAttrPullResistorEnable |
+               kDifPinmuxPadAttrPullResistorUp};
+  for (uint32_t i = 0; i <= ARRAYSIZE(spi_host0_direct_pads); ++i) {
+    CHECK_DIF_OK(dif_pinmux_pad_write_attrs(&pinmux, spi_host0_direct_pads[i],
+                                            kDifPinmuxPadKindDio, in_attr,
+                                            &out_attr));
+  }
+}
+
+/**
+ * Setup pads and pinmux for spi_host0
+ *
+ * This peripheral is 'muxed', so configure the pinmux as well as pads.
+ */
+void setup_pinmux_pads_spi_host1(void) {
+  // Set weak pull-ups for the pads
+  dif_pinmux_pad_attr_t out_attr;
+  dif_pinmux_pad_attr_t in_attr = {
+      .slew_rate = 0,
+      .drive_strength = 0,
+      // set weak pull-ups for all the pads
+      .flags = kDifPinmuxPadAttrPullResistorEnable |
+               kDifPinmuxPadAttrPullResistorUp};
+  for (uint32_t i = 0; i <= ARRAYSIZE(spi_host1_muxed_pads); ++i) {
+    CHECK_DIF_OK(dif_pinmux_pad_write_attrs(&pinmux, spi_host1_muxed_pads[i],
+                                            kDifPinmuxPadKindMio, in_attr,
+                                            &out_attr));
+  }
+
+  // Setup Inputs
+  for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) {
+    pinmux_select_t setting = pinmux_in_config[i];
+    CHECK_DIF_OK(
+        dif_pinmux_input_select(&pinmux, setting.peripheral, setting.pad));
+  }
+  // Setup Outputs
+  for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) {
+    pinmux_select_t setting = pinmux_out_config[i];
+    CHECK_DIF_OK(
+        dif_pinmux_output_select(&pinmux, setting.pad, setting.peripheral));
+  }
+}
+
+bool test_main(void) {
+  // Initialize the pinmux.
+  CHECK_DIF_OK(dif_pinmux_init(
+      mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux));
+  pinmux_testutils_init(&pinmux);
+
+  // Setup pinmux if required, enable weak pull-up on relevant pads
+  setup_pads_spi_host0();         // direct
+  setup_pinmux_pads_spi_host1();  // muxed
+
+  // Setup spi host configuration
+  LOG_INFO("Testing spi_host%0d", kSPIHostIdx);
+  uintptr_t base_addr;
+  uint64_t clkHz;
+  switch (kSPIHostIdx) {
+    case 0: {
+      base_addr = TOP_MATCHA_SPI_HOST0_BASE_ADDR;
+      clkHz = kClockFreqPeripheralHz * 4;
+      break;
+    }
+    case 1: {
+      base_addr = TOP_MATCHA_SPI_HOST1_BASE_ADDR;
+      clkHz = kClockFreqPeripheralHz;
+      break;
+    }
+    default:
+      LOG_FATAL("Invalid kSPIHostIdx: %d", kSPIHostIdx);
+  }
+  CHECK_DIF_OK(dif_spi_host_init(mmio_region_from_addr(base_addr), &spi_host));
+  init_spi_host(&spi_host, (uint32_t)clkHz);
+
+  // DV sync message
+  LOG_INFO("spi host configuration complete");
+
+  uint32_t expected_data[DATA_SET_SIZE];
+  uint32_t received_data[DATA_SET_SIZE];
+  for (uint32_t i = 0; i < ARRAYSIZE(expected_data); ++i) {
+    expected_data[i] = rand_testutils_gen32();
+  }
+
+  // Define the segments:
+  // 1st segment, TX only, host sends out the first word.
+  // 2nd segment, Bidirectional.  The external device begins sending back data
+  // that it received. So it always lags the TX by 1 word.
+  // 3rd segment, RX only, final word readback.
+  dif_spi_host_segment_t host_operations[3] = {
+      {.type = kDifSpiHostSegmentTypeTx,
+       .tx = {.width = kDifSpiHostWidthStandard,
+              .buf = &expected_data[0],
+              .length = 4}},
+
+      {.type = kDifSpiHostSegmentTypeBidirectional,
+       .bidir = {.width = kDifSpiHostWidthStandard,
+                 .txbuf = &expected_data[1],
+                 .rxbuf = received_data,
+                 .length = (DATA_SET_SIZE - 1) * sizeof(uint32_t)}},
+
+      {.type = kDifSpiHostSegmentTypeRx,
+       .tx = {.width = kDifSpiHostWidthStandard,
+              .buf = &received_data[DATA_SET_SIZE - 1],
+              .length = 4}},
+  };
+
+  // Issue the transaction
+  CHECK_DIF_OK(dif_spi_host_transaction(&spi_host, /*csid=*/0, host_operations,
+                                        ARRAYSIZE(host_operations)));
+
+  CHECK_ARRAYS_EQ(received_data, expected_data, ARRAYSIZE(expected_data));
+
+  return true;
+}
diff --git a/sw/device/tests/sim_dv/spi_passthrough_test.c b/sw/device/tests/sim_dv/spi_passthrough_test.c
new file mode 100644
index 0000000..e8bb80c
--- /dev/null
+++ b/sw/device/tests/sim_dv/spi_passthrough_test.c
@@ -0,0 +1,460 @@
+// 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 <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/bitfield.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_pinmux.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/dif/dif_spi_device.h"
+#include "sw/device/lib/dif/dif_spi_host.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/irq.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/pinmux_testutils.h"
+#include "sw/device/lib/testing/spi_device_testutils.h"
+#include "sw/device/lib/testing/spi_flash_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+
+OTTF_DEFINE_TEST_CONFIG();
+
+// Bit map of command slots to be filtered. This is supplied by the DV
+// environment.
+const volatile uint32_t kFilteredCommands;
+
+// Whether to upload write commands and have software relay them.
+const volatile uint8_t kUploadWriteCommands;
+
+static dif_pinmux_t pinmux;
+static dif_rv_plic_t rv_plic;
+static dif_spi_device_handle_t spi_device;
+static dif_spi_host_t spi_host0;
+static dif_spi_host_t spi_host1;
+
+// Enable pull-ups for spi_host data pins to avoid floating inputs.
+static const pinmux_pad_attributes_t pinmux_pad_config[] = {
+    {
+        .pad = kTopMatchaMuxedPadsIob1,
+        .kind = kDifPinmuxPadKindMio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+    {
+        .pad = kTopMatchaMuxedPadsIob3,
+        .kind = kDifPinmuxPadKindMio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+    {
+        .pad = kTopMatchaDirectPadsSpiHost0Sd0,
+        .kind = kDifPinmuxPadKindDio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+    {
+        .pad = kTopMatchaDirectPadsSpiHost0Sd1,
+        .kind = kDifPinmuxPadKindDio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+    {
+        .pad = kTopMatchaDirectPadsSpiHost0Sd2,
+        .kind = kDifPinmuxPadKindDio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+    {
+        .pad = kTopMatchaDirectPadsSpiHost0Sd3,
+        .kind = kDifPinmuxPadKindDio,
+        .flags = kDifPinmuxPadAttrPullResistorEnable |
+                 kDifPinmuxPadAttrPullResistorUp,
+    },
+};
+
+/**
+ * A convenience struct to associate a mux selection that connects a pad and
+ * peripheral. This can be used for an input mux or an output mux.
+ */
+typedef struct pinmux_select {
+  dif_pinmux_index_t pad;
+  dif_pinmux_index_t peripheral;
+} pinmux_select_t;
+
+static const pinmux_select_t pinmux_out_config[] = {
+    {
+        .pad = kTopMatchaPinmuxMioOutIob0,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Csb,
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob2,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sck,
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob1,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0,
+    },
+    {
+        .pad = kTopMatchaPinmuxMioOutIob3,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1,
+    },
+    // These peripheral I/Os are not assigned for tests.
+    //     {
+    //         .pad = ???,
+    //         .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2,
+    //     },
+    //     {
+    //         .pad = ???,
+    //         .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3,
+    //     },
+};
+
+static const pinmux_select_t pinmux_in_config[] = {
+    {
+        .pad = kTopMatchaPinmuxInselIob1,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0,
+    },
+    {
+        .pad = kTopMatchaPinmuxInselIob3,
+        .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1,
+    },
+    // These peripheral I/Os are not assigned for tests.
+    //     {
+    //         .pad = ???,
+    //         .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2,
+    //     },
+    //     {
+    //         .pad = ???,
+    //         .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3,
+    //     },
+};
+
+/**
+ * Initialize the provided SPI host. For the most part, the values provided are
+ * filler, as spi_host0 will be in passthrough mode and spi_host1 will remain
+ * unused throughout the test.
+ */
+void init_spi_host(dif_spi_host_t *spi_host,
+                   uint32_t peripheral_clock_freq_hz) {
+  dif_spi_host_config_t config = {
+      .spi_clock = peripheral_clock_freq_hz / 2,
+      .peripheral_clock_freq_hz = peripheral_clock_freq_hz,
+      .chip_select =
+          {
+              .idle = 2,
+              .trail = 2,
+              .lead = 2,
+          },
+  };
+  CHECK_DIF_OK(dif_spi_host_configure(spi_host, config));
+  CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true));
+}
+
+/**
+ * Handle an incoming Write Status command.
+ *
+ * Modifies the internal status register and relays the command out to the
+ * downstream SPI flash.
+ *
+ * @param status The aggregated value of all three flash status registers prior
+ * to this command's execution.
+ * @param offset The bit offset for the byte to be modified by the payload.
+ * @param opcode The opcode corresponding to the incoming command.
+ */
+void handle_write_status(uint32_t status, uint8_t offset, uint8_t opcode) {
+  uint8_t payload;
+  uint16_t occupancy;
+  uint32_t start_offset;
+  CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy(
+      &spi_device, &occupancy, &start_offset));
+  CHECK(occupancy == 1);
+  CHECK_DIF_OK(dif_spi_device_read_flash_buffer(
+      &spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset, occupancy,
+      &payload));
+
+  status &= (0xffu << offset);
+  status |= (payload << offset);
+  CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spi_device, status));
+
+  spi_flash_testutils_issue_write_enable(&spi_host0);
+
+  dif_spi_host_segment_t transaction[] = {
+      {
+          .type = kDifSpiHostSegmentTypeOpcode,
+          .opcode = opcode,
+      },
+      {
+          .type = kDifSpiHostSegmentTypeTx,
+          .tx =
+              {
+                  .width = kDifSpiHostWidthStandard,
+                  .buf = &payload,
+                  .length = 1,
+              },
+      },
+  };
+  CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
+                                        ARRAYSIZE(transaction)));
+  spi_flash_testutils_wait_until_not_busy(&spi_host0);
+  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
+}
+
+/**
+ * Handle an incoming Chip Erase command.
+ *
+ * Relays the command out to the downstream SPI flash.
+ */
+void handle_chip_erase(void) {
+  spi_flash_testutils_erase_chip(&spi_host0);
+  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
+}
+
+/**
+ * Handle an incoming Sector Erase command.
+ *
+ * Relays the command out to the downstream SPI flash.
+ */
+void handle_sector_erase(void) {
+  uint8_t occupancy;
+  CHECK_DIF_OK(
+      dif_spi_device_get_flash_address_fifo_occupancy(&spi_device, &occupancy));
+  CHECK(occupancy == 1);
+
+  uint32_t address;
+  CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
+
+  dif_toggle_t addr4b_enabled;
+  CHECK_DIF_OK(
+      dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
+
+  bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
+  spi_flash_testutils_erase_sector(&spi_host0, address, addr_is_4b);
+  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
+}
+
+/**
+ * Handle an incoming Page Program command.
+ *
+ * Relays the command out to the downstream SPI flash.
+ */
+void handle_page_program(void) {
+  uint8_t address_occupancy;
+  CHECK_DIF_OK(dif_spi_device_get_flash_address_fifo_occupancy(
+      &spi_device, &address_occupancy));
+  CHECK(address_occupancy == 1);
+
+  uint32_t address;
+  CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
+
+  uint8_t payload[256];
+  uint16_t payload_occupancy;
+  uint32_t start_offset;
+  CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy(
+      &spi_device, &payload_occupancy, &start_offset));
+  CHECK(start_offset == 0);
+  CHECK(payload_occupancy <= sizeof(payload));
+  CHECK_DIF_OK(dif_spi_device_read_flash_buffer(
+      &spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset,
+      payload_occupancy, payload));
+
+  dif_toggle_t addr4b_enabled;
+  CHECK_DIF_OK(
+      dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
+
+  bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
+  spi_flash_testutils_program_page(&spi_host0, payload, payload_occupancy,
+                                   address, addr_is_4b);
+  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
+}
+
+// This function assumes only one command will ever be uploaded to the FIFO at a
+// time. The IP does not currently make any such guarantee.
+void spi_device_process_upload_fifo(void) {
+  dif_irq_type_t irq_type;
+  CHECK_DIF_OK(dif_spi_device_irq_get_type(
+      &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &irq_type));
+  if (irq_type == kDifIrqTypeEvent) {
+    CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
+        &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty));
+  }
+
+  // Uploaded commands should all set the busy bit, and WREN should have been
+  // submitted before the uploaded command.
+  uint32_t status;
+  CHECK_DIF_OK(dif_spi_device_get_flash_status_registers(&spi_device, &status));
+  CHECK(status & kSpiFlashStatusBitWip);
+  CHECK(status & kSpiFlashStatusBitWel);
+
+  uint8_t command;
+  CHECK_DIF_OK(dif_spi_device_pop_flash_command_fifo(&spi_device, &command));
+  // Check command against the ones we expect.
+  // Call command-specific handlers, probably, which validate the commands. Then
+  // execute.
+  if (command == kSpiDeviceFlashOpWriteStatus1) {
+    handle_write_status(status, /*offset=*/0, command);
+  } else if (command == kSpiDeviceFlashOpWriteStatus2) {
+    handle_write_status(status, /*offset=*/8, command);
+  } else if (command == kSpiDeviceFlashOpWriteStatus3) {
+    handle_write_status(status, /*offset=*/16, command);
+  } else if (command == kSpiDeviceFlashOpChipErase) {
+    handle_chip_erase();
+  } else if (command == kSpiDeviceFlashOpSectorErase) {
+    handle_sector_erase();
+  } else if (command == kSpiDeviceFlashOpPageProgram) {
+    handle_page_program();
+  } else {
+    CHECK(false, "Received unexpected command 0x%x", command);
+  }
+
+  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
+      &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
+      kDifToggleEnabled));
+}
+
+/**
+ * Check that the command FIFO is not empty, and mask the interrupt for deferred
+ * processing.
+ *
+ * Runs in interrupt context.
+ */
+void spi_device_isr(void) {
+  bool cmdfifo_not_empty;
+  CHECK_DIF_OK(dif_spi_device_irq_is_pending(
+      &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
+      &cmdfifo_not_empty));
+  CHECK(cmdfifo_not_empty);
+  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
+      &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
+      kDifToggleDisabled));
+}
+
+/**
+ * Handle SPI device interrupts.
+ *
+ * Runs in interrupt context.
+ */
+void ottf_external_isr(void) {
+  const uint32_t kPlicTarget = kTopMatchaPlicTargetIbex0;
+  dif_rv_plic_irq_id_t plic_irq_id;
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&rv_plic, kPlicTarget, &plic_irq_id));
+
+  top_matcha_plic_peripheral_t peripheral = (top_matcha_plic_peripheral_t)
+      top_matcha_plic_interrupt_for_peripheral[plic_irq_id];
+
+  switch (peripheral) {
+    case kTopMatchaPlicPeripheralSpiDevice:
+      // Only the UploadCmdfifoNotEmpty interrupt is expected.
+      CHECK(plic_irq_id == kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty);
+      spi_device_isr();
+      break;
+    default:
+      break;
+  }
+
+  // Complete the IRQ at PLIC.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&rv_plic, kPlicTarget, plic_irq_id));
+}
+
+bool test_main(void) {
+  // Initialize the pinmux.
+  CHECK_DIF_OK(dif_pinmux_init(
+      mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux));
+  pinmux_testutils_init(&pinmux);
+  pinmux_testutils_configure_pads(&pinmux, pinmux_pad_config,
+                                  ARRAYSIZE(pinmux_pad_config));
+  for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) {
+    pinmux_select_t setting = pinmux_in_config[i];
+    CHECK_DIF_OK(
+        dif_pinmux_input_select(&pinmux, setting.peripheral, setting.pad));
+  }
+  for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) {
+    pinmux_select_t setting = pinmux_out_config[i];
+    CHECK_DIF_OK(
+        dif_pinmux_output_select(&pinmux, setting.pad, setting.peripheral));
+  }
+
+  // Initialize the PLIC.
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic));
+
+  // Initialize the spi_host devices.
+  CHECK_DIF_OK(dif_spi_host_init(
+      mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0));
+  CHECK_DIF_OK(dif_spi_host_init(
+      mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host1));
+  init_spi_host(&spi_host0, (uint32_t)kClockFreqPeripheralHz * 4);
+  init_spi_host(&spi_host1, (uint32_t)kClockFreqPeripheralHz);
+
+  // Initialize spi_device.
+  mmio_region_t spi_device_base_addr =
+      mmio_region_from_addr(TOP_MATCHA_SPI_DEVICE_BASE_ADDR);
+  CHECK_DIF_OK(dif_spi_device_init_handle(spi_device_base_addr, &spi_device));
+  bool upload_write_commands = (kUploadWriteCommands != 0);
+  spi_device_testutils_configure_passthrough(&spi_device, kFilteredCommands,
+                                             upload_write_commands);
+
+  // Enable all spi_device and spi_host interrupts, and check that they do not
+  // trigger unless command upload is enabled.
+  dif_spi_device_irq_t all_spi_device_irqs[] = {
+      kDifSpiDeviceIrqGenericRxFull,         kDifSpiDeviceIrqGenericRxWatermark,
+      kDifSpiDeviceIrqGenericTxWatermark,    kDifSpiDeviceIrqGenericRxError,
+      kDifSpiDeviceIrqGenericRxOverflow,     kDifSpiDeviceIrqGenericTxUnderflow,
+      kDifSpiDeviceIrqUploadCmdfifoNotEmpty, kDifSpiDeviceIrqReadbufWatermark,
+      kDifSpiDeviceIrqReadbufFlip,           kDifSpiDeviceIrqTpmHeaderNotEmpty,
+  };
+  for (int i = 0; i < ARRAYSIZE(all_spi_device_irqs); ++i) {
+    dif_spi_device_irq_t irq = all_spi_device_irqs[i];
+    CHECK_DIF_OK(dif_spi_device_irq_set_enabled(&spi_device.dev, irq,
+                                                kDifToggleEnabled));
+  }
+  CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqError,
+                                            kDifToggleEnabled));
+  CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqSpiEvent,
+                                            kDifToggleEnabled));
+
+  dif_rv_plic_irq_id_t spi_irqs[] = {
+      kTopMatchaPlicIrqIdSpiDeviceGenericRxFull,
+      kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark,
+      kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark,
+      kTopMatchaPlicIrqIdSpiDeviceGenericRxError,
+      kTopMatchaPlicIrqIdSpiDeviceGenericRxOverflow,
+      kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow,
+      kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty,
+      kTopMatchaPlicIrqIdSpiDeviceReadbufWatermark,
+      kTopMatchaPlicIrqIdSpiDeviceReadbufFlip,
+      kTopMatchaPlicIrqIdSpiHost0Error,
+      kTopMatchaPlicIrqIdSpiHost0SpiEvent,
+  };
+  for (int i = 0; i < ARRAYSIZE(spi_irqs); ++i) {
+    dif_rv_plic_irq_id_t irq = spi_irqs[i];
+    CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
+        &rv_plic, irq, kTopMatchaPlicTargetIbex0, kDifToggleEnabled));
+    CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, irq, 0x1));
+  }
+  irq_external_ctrl(/*en=*/true);
+
+  // Send the DV environment a specific message for synchronization. The
+  // sequencer can pick this up and know it can begin sending SPI flash
+  // transactions.
+  LOG_INFO("Test setup complete.");
+
+  while (true) {
+    uint8_t spi_device_cmdfifo_occupancy;
+    irq_global_ctrl(/*en=*/false);
+    CHECK_DIF_OK(dif_spi_device_get_flash_command_fifo_occupancy(
+        &spi_device, &spi_device_cmdfifo_occupancy));
+    if (spi_device_cmdfifo_occupancy == 0) {
+      wait_for_interrupt();
+    }
+    irq_global_ctrl(/*en=*/true);
+    spi_device_process_upload_fifo();
+  }
+  return true;
+}