[top/dv] Add spi_tpm test

- The test issues writes from the host to spi device.
- Spi device then loops back data back to the host
  on a read command.

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv
index fa3a6c0..3096a8b 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv
@@ -48,7 +48,7 @@
                                    foreach (address_q[i]) {
                                      address_q[i] == local::address_q[i];
                                    }
-                                   payload_q.size() == 0; // no used for tpm
+                                   payload_q.size() == 0; // not used for tpm
                                    data.size == local::data_q.size();
                                    foreach (data_q[i]) {
                                      data[i] == local::data_q[i];
diff --git a/hw/ip/spi_device/doc/_index.md b/hw/ip/spi_device/doc/_index.md
index 31c4e82..f0ae59b 100644
--- a/hw/ip/spi_device/doc/_index.md
+++ b/hw/ip/spi_device/doc/_index.md
@@ -48,7 +48,7 @@
 - Shared SPI with other SPI Device functionalities. Unique CS# for the TPM
   - Flash or Passthrough mode can be active with TPM mode.
     Generic and TPM modes are mutually exclusive.
-- HW processed registers for the read requests
+- HW processed registers for read requests during FIFO mode
   - TPM_ACCESS_x, TPM_STS_x, TPM_INTF_CAPABILITY, TPM_INT_ENABLE, TPM_INT_STATUS, TPM_INT_VECTOR, TPM_DID_VID, TPM_RID
   - TPM_HASH_START returns FFh
 - 5 Locality (compile-time parameter)
@@ -866,8 +866,12 @@
 
 ### TPM mode: FIFO and CRB
 
-The HW returns the return-by-HW registers for the read request in the TPM FIFO mode.
-In the TPM CRB mode (TPM_CFG.tpm_mode is 1), the logic always upload the command and address to the SW and waits for the read FIFO data even the received address falls into the managed address.
+The TPM protocol supports two protocol interfaces: FIFO and CRB (Command Response Buffer).
+In terms of hardware design, these two interfaces differ in how return-by-HW registers are handled.
+
+In FIFO mode, when {{< regref "TPM_CFG.tpm_mode" >}} is set to 0, HW registers reads must be returned after a maximum of 1 wait state.
+In CRB mode, when {{< regref "TPM_CFG.tpm_mode" >}} is set to 1, there are no such restrictions.
+The logic always uploads both the command and address to the SW and waits for the return data in CRB mode.
 
 ### Return-by-HW register update
 
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index d6e1cdb..e54849a 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -213,6 +213,19 @@
       stage: V2
       tests: []
     }
+    {
+      name: chip_sw_spi_device_tpm
+      desc: '''Verify the basic operation of the spi tpm mode..
+
+            - The testbench sends a known payload over the chip's SPI device tpm input port.
+            - The testbench sends a read command.
+            - The software test should playback the data received in the write command as the read
+              response.
+            - The testbench should check if the written and read data match.
+            '''
+      stage: V2
+      tests: ["chip_sw_spi_device_tpm_test"]
+    }
 
     // SPI_HOST (pre-verified IP) integration tests:
     {
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index eaaa3de..2afb1c8 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -496,6 +496,12 @@
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
+      name: chip_sw_spi_device_tpm
+      uvm_test_seq: chip_sw_spi_device_tpm_vseq
+      sw_images: ["//sw/device/tests/sim_dv:spi_device_tpm_tx_rx_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+    }
+    {
       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"]
diff --git a/hw/top_earlgrey/dv/env/chip_env.core b/hw/top_earlgrey/dv/env/chip_env.core
index aaf089c..61a9911 100644
--- a/hw/top_earlgrey/dv/env/chip_env.core
+++ b/hw/top_earlgrey/dv/env/chip_env.core
@@ -105,6 +105,7 @@
       - seq_lib/chip_sw_csrng_lc_hw_debug_en_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_usb_ast_clk_calib_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_i2c_host_tx_rx_vseq.sv: {is_include_file: true}
+      - seq_lib/chip_sw_spi_device_tpm_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_patt_ios_vseq.sv: {is_include_file: true}
       - seq_lib/chip_sw_rv_core_ibex_lockstep_glitch_vseq.sv: {is_include_file: true}
       - autogen/chip_env_pkg__params.sv: {is_include_file: true}
diff --git a/hw/top_earlgrey/dv/env/chip_if.sv b/hw/top_earlgrey/dv/env/chip_if.sv
index fc648bb..661cf98 100644
--- a/hw/top_earlgrey/dv/env/chip_if.sv
+++ b/hw/top_earlgrey/dv/env/chip_if.sv
@@ -173,14 +173,16 @@
 
   // Functional (dedicated) interface: SPI host interface (drives traffic into the chip).
   bit enable_spi_host = 1;
+  bit enable_spi_tpm  = 0;
   spi_if spi_host_if(.rst_n(`SPI_DEVICE_HIER.rst_ni),
                      .sio({ios[SpiDevD3], ios[SpiDevD2], ios[SpiDevD1], ios[SpiDevD0]}));
-  assign ios[SpiDevClk] = enable_spi_host ? spi_host_if.sck : 1'bz;
-  assign ios[SpiDevCsL] = enable_spi_host ? spi_host_if.csb : 1'bz;
+  assign ios[SpiDevClk] = enable_spi_host | enable_spi_tpm ? spi_host_if.sck : 1'bz;
+  assign ios[SpiDevCsL] = enable_spi_host ? spi_host_if.csb[0] : 1'bz;
+  assign ios[IoA7]      = enable_spi_tpm  ? spi_host_if.csb[1] : 1'bz;
   initial begin
     do begin
-      spi_host_if.disconnect(!enable_spi_host);
-      @(enable_spi_host);
+      spi_host_if.disconnect(!enable_spi_host & !enable_spi_tpm);
+      @(enable_spi_host | enable_spi_tpm);
     end while (1);
   end
 
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_spi_device_tpm_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_spi_device_tpm_vseq.sv
new file mode 100644
index 0000000..d237dc0
--- /dev/null
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_spi_device_tpm_vseq.sv
@@ -0,0 +1,79 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class chip_sw_spi_device_tpm_vseq extends chip_sw_base_vseq;
+  `uvm_object_utils(chip_sw_spi_device_tpm_vseq)
+
+  `uvm_object_new
+
+  rand bit [23:0] addr;
+  rand bit [7:0] data_q[$];
+  constraint size_c  { data_q.size() <= 64; }
+
+  virtual task tpm_txn (bit wr, bit [23:0] addr, bit [7:0] data_q[$] = {0},
+                        int len, output logic [7:0] rdata_q[]);
+    spi_host_tpm_seq m_host_tpm_seq;
+    `uvm_create_on(m_host_tpm_seq, p_sequencer.spi_sequencer_h)
+
+    // Common attribute assignments
+    m_host_tpm_seq.write_command = wr;
+    m_host_tpm_seq.addr = addr;
+
+    // This is a write transaction
+    if (wr) begin
+      m_host_tpm_seq.data_q = data_q;
+    end else begin
+      m_host_tpm_seq.read_size = len;
+    end
+
+    `uvm_send(m_host_tpm_seq)
+    `uvm_info(`gfn, $sformatf("TPM transaction sent"), UVM_MEDIUM)
+
+    // This is a read trasnaction
+    if (!wr) begin
+      rdata_q = m_host_tpm_seq.rsp.data;
+    end
+  endtask
+
+  virtual task body();
+    logic [7:0] rdata_q[];
+    super.body();
+
+    // Enable desired modes
+    cfg.chip_vif.enable_spi_host = 0;
+    cfg.chip_vif.enable_spi_tpm = 1;
+
+    // Directly set the expected cs_id
+    cfg.m_spi_agent_cfg.csb_sel_in_cfg = 1;
+    cfg.m_spi_agent_cfg.csid = 1;
+
+    // enable spi agent interface to begin
+    `DV_WAIT(cfg.sw_logger_vif.printed_log == "Begin TPM Test",
+             "Timedout waiting for spi host c configuration.")
+
+    for (int i = 0; i < 10; i++) begin
+      `uvm_info(`gfn, $sformatf("Begin transaction %d", i), UVM_MEDIUM)
+
+      // Write transaction
+      `DV_CHECK_MEMBER_RANDOMIZE_FATAL(data_q)
+      `DV_CHECK_MEMBER_RANDOMIZE_FATAL(addr)
+      foreach (data_q[i]) begin
+        `uvm_info(`gfn, $sformatf("Expected data: 0x%x", data_q[i]), UVM_MEDIUM)
+      end
+      tpm_txn (.wr(1), .addr(addr), .data_q(data_q), .len(data_q.size()), .rdata_q(rdata_q));
+
+      // Read transaction
+      tpm_txn (.wr(0), .addr(addr), .len(data_q.size()), .rdata_q(rdata_q));
+      foreach (rdata_q[i]) begin
+        `uvm_info(`gfn, $sformatf("Read data: 0x%x", data_q[i]), UVM_MEDIUM)
+      end
+
+      // Confirm that data is looped back
+      `DV_CHECK_Q_EQ(data_q, rdata_q);
+      `uvm_info(`gfn, $sformatf("End transaction %d", i), UVM_MEDIUM)
+    end
+  endtask
+
+
+endclass : chip_sw_spi_device_tpm_vseq
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
index 4db054b..957c638 100644
--- a/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_vseq_list.sv
@@ -68,3 +68,4 @@
 `include "chip_sw_inject_scramble_seed_vseq.sv"
 `include "chip_sw_exit_test_unlocked_bootstrap_vseq.sv"
 `include "chip_sw_patt_ios_vseq.sv"
+`include "chip_sw_spi_device_tpm_vseq.sv"
diff --git a/sw/device/lib/dif/dif_spi_device.c b/sw/device/lib/dif/dif_spi_device.c
index e88fe4f..3b56839 100644
--- a/sw/device/lib/dif/dif_spi_device.c
+++ b/sw/device/lib/dif/dif_spi_device.c
@@ -10,7 +10,7 @@
 
 #include "spi_device_regs.h"  // Generated.
 
-#define DIF_SPI_DEVICE_TPM_FIFO_DEPTH 4
+#define DIF_SPI_DEVICE_TPM_FIFO_DEPTH 16
 
 const uint16_t kDifSpiDeviceBufferLen = SPI_DEVICE_BUFFER_SIZE_BYTES;
 
@@ -1550,7 +1550,9 @@
   if (result != kDifOk) {
     return result;
   }
-  if (DIF_SPI_DEVICE_TPM_FIFO_DEPTH - status.read_fifo_occupancy < length) {
+  if ((DIF_SPI_DEVICE_TPM_FIFO_DEPTH - status.read_fifo_occupancy) *
+          sizeof(uint32_t) <
+      length) {
     return kDifOutOfRange;
   }
   for (int i = 0; i < length; i += 4) {
diff --git a/sw/device/lib/dif/dif_spi_device_unittest.cc b/sw/device/lib/dif/dif_spi_device_unittest.cc
index 3b99437..bc0c54d 100644
--- a/sw/device/lib/dif/dif_spi_device_unittest.cc
+++ b/sw/device/lib/dif/dif_spi_device_unittest.cc
@@ -1792,17 +1792,17 @@
                 {
                     {SPI_DEVICE_TPM_STATUS_CMDADDR_NOTEMPTY_BIT, 0},
                     {SPI_DEVICE_TPM_STATUS_RDFIFO_NOTEMPTY_BIT, 1},
-                    {SPI_DEVICE_TPM_STATUS_RDFIFO_DEPTH_OFFSET, 3},
+                    {SPI_DEVICE_TPM_STATUS_RDFIFO_DEPTH_OFFSET, 14},
                     {SPI_DEVICE_TPM_STATUS_WRFIFO_DEPTH_OFFSET, 4},
                 });
-  EXPECT_EQ(dif_spi_device_tpm_write_data(&spi_, /*length=*/2, data),
+  EXPECT_EQ(dif_spi_device_tpm_write_data(&spi_, /*length=*/9, data),
             kDifOutOfRange);
 
   EXPECT_READ32(SPI_DEVICE_TPM_STATUS_REG_OFFSET,
                 {
                     {SPI_DEVICE_TPM_STATUS_CMDADDR_NOTEMPTY_BIT, 0},
-                    {SPI_DEVICE_TPM_STATUS_RDFIFO_NOTEMPTY_BIT, 0},
-                    {SPI_DEVICE_TPM_STATUS_RDFIFO_DEPTH_OFFSET, 0},
+                    {SPI_DEVICE_TPM_STATUS_RDFIFO_NOTEMPTY_BIT, 1},
+                    {SPI_DEVICE_TPM_STATUS_RDFIFO_DEPTH_OFFSET, 15},
                     {SPI_DEVICE_TPM_STATUS_WRFIFO_DEPTH_OFFSET, 4},
                 });
   EXPECT_EQ(dif_spi_device_tpm_write_data(&spi_, /*length=*/5, data),
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index 8d488c2..6709378 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -724,6 +724,7 @@
         "//hw/top_earlgrey/sw/autogen:top_earlgrey",
         "//sw/device/lib/arch:device",
         "//sw/device/lib/base:mmio",
+        "//sw/device/lib/dif:base",
         "//sw/device/lib/dif:rv_plic",
         "//sw/device/lib/dif:spi_device",
         "//sw/device/lib/runtime:hart",
@@ -735,6 +736,26 @@
 )
 
 opentitan_functest(
+    name = "spi_device_tpm_tx_rx_test",
+    srcs = ["spi_device_tpm_tx_rx_test.c"],
+    targets = ["dv"],
+    deps = [
+        "//hw/top_earlgrey/sw/autogen:top_earlgrey",
+        "//sw/device/lib/arch:device",
+        "//sw/device/lib/base:mmio",
+        "//sw/device/lib/dif:pinmux",
+        "//sw/device/lib/dif:rv_plic",
+        "//sw/device/lib/dif:spi_device",
+        "//sw/device/lib/runtime:hart",
+        "//sw/device/lib/runtime:irq",
+        "//sw/device/lib/runtime:log",
+        "//sw/device/lib/testing:isr_testutils",
+        "//sw/device/lib/testing/test_framework:ottf_main",
+        "//sw/device/lib/testing/test_framework:status",
+    ],
+)
+
+opentitan_functest(
     name = "spi_host_tx_rx_test",
     srcs = ["spi_host_tx_rx_test.c"],
     targets = ["dv"],
diff --git a/sw/device/tests/sim_dv/spi_device_tpm_tx_rx_test.c b/sw/device/tests/sim_dv/spi_device_tpm_tx_rx_test.c
new file mode 100644
index 0000000..0d93a42
--- /dev/null
+++ b/sw/device/tests/sim_dv/spi_device_tpm_tx_rx_test.c
@@ -0,0 +1,224 @@
+// 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/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_rv_plic.h"
+#include "sw/device/lib/dif/dif_spi_device.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/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+#include "sw/device/lib/testing/test_framework/status.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "sw/device/lib/testing/autogen/isr_testutils.h"
+
+OTTF_DEFINE_TEST_CONFIG();
+
+static dif_spi_device_handle_t spi_device;
+static dif_pinmux_t pinmux;
+static dif_rv_plic_t plic;
+
+// Enum for TPM command
+typedef enum {
+  kTpmWriteCommand = 0x0,
+  kTpmReadCommand = 0x80,
+} tpm_cmd_t;
+
+const static uint8_t kIterations = 10;
+const static uint8_t kTpmCommandRwMask = 0x80;
+const static uint8_t kTpmCommandSizeMask = 0x3f;
+
+const static dif_spi_device_tpm_config_t tpm_config = {
+    .interface = kDifSpiDeviceTpmInterfaceCrb,
+    .disable_return_by_hardware = false,
+    .disable_address_prefix_check = false,
+    .disable_locality_check = false};
+
+static volatile bool header_interrupt_received = false;
+
+static void en_plic_irqs(dif_rv_plic_t *plic) {
+  // Enable functional interrupts as well as error interrupts to make sure
+  // everything is behaving as expected.
+  top_earlgrey_plic_irq_id_t plic_irqs[] = {
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericRxFull,
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericRxWatermark,
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericTxWatermark,
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericRxError,
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericRxOverflow,
+      kTopEarlgreyPlicIrqIdSpiDeviceGenericTxUnderflow,
+      kTopEarlgreyPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty,
+      kTopEarlgreyPlicIrqIdSpiDeviceUploadPayloadNotEmpty,
+      kTopEarlgreyPlicIrqIdSpiDeviceUploadPayloadOverflow,
+      kTopEarlgreyPlicIrqIdSpiDeviceReadbufWatermark,
+      kTopEarlgreyPlicIrqIdSpiDeviceReadbufFlip,
+      kTopEarlgreyPlicIrqIdSpiDeviceTpmHeaderNotEmpty};
+
+  for (uint32_t i = 0; i < ARRAYSIZE(plic_irqs); ++i) {
+    CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
+        plic, plic_irqs[i], kTopEarlgreyPlicTargetIbex0, kDifToggleEnabled));
+
+    // Assign a default priority
+    CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, plic_irqs[i],
+                                              kDifRvPlicMaxPriority));
+  }
+
+  // Enable the external IRQ at Ibex.
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+}
+
+static void en_spi_device_irqs(dif_spi_device_t *spi_device) {
+  dif_spi_device_irq_t spi_device_irqs[] = {
+      kDifSpiDeviceIrqGenericRxFull,
+      kDifSpiDeviceIrqGenericRxWatermark,
+      kDifSpiDeviceIrqGenericTxWatermark,
+      kDifSpiDeviceIrqGenericRxError,
+      kDifSpiDeviceIrqGenericRxOverflow,
+      kDifSpiDeviceIrqGenericTxUnderflow,
+      kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
+      kDifSpiDeviceIrqUploadPayloadNotEmpty,
+      kDifSpiDeviceIrqUploadPayloadOverflow,
+      kDifSpiDeviceIrqReadbufWatermark,
+      kDifSpiDeviceIrqReadbufFlip,
+      kDifSpiDeviceIrqTpmHeaderNotEmpty};
+
+  for (uint32_t i = 0; i <= ARRAYSIZE(spi_device_irqs); ++i) {
+    CHECK_DIF_OK(dif_spi_device_irq_set_enabled(spi_device, spi_device_irqs[i],
+                                                kDifToggleEnabled));
+  }
+}
+
+void ottf_external_isr(void) {
+  plic_isr_ctx_t plic_ctx = {.rv_plic = &plic,
+                             .hart_id = kTopEarlgreyPlicTargetIbex0};
+
+  // We should only be receiving the tpm header interrupt during this test.
+  spi_device_isr_ctx_t spi_device_ctx = {
+      .spi_device = &spi_device.dev,
+      .plic_spi_device_start_irq_id =
+          kTopEarlgreyPlicIrqIdSpiDeviceGenericRxFull,
+      .expected_irq = kDifSpiDeviceIrqTpmHeaderNotEmpty,
+      .is_only_irq = true};
+
+  top_earlgrey_plic_peripheral_t peripheral;
+  dif_spi_device_irq_t spi_device_irq;
+  isr_testutils_spi_device_isr(plic_ctx, spi_device_ctx, &peripheral,
+                               &spi_device_irq);
+
+  switch (spi_device_irq) {
+    case kDifSpiDeviceIrqTpmHeaderNotEmpty:
+      header_interrupt_received = true;
+      // Disable interrupt until work is handled.
+      CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
+          &spi_device.dev, kDifSpiDeviceIrqTpmHeaderNotEmpty,
+          kDifToggleDisabled));
+      break;
+    default:
+      LOG_ERROR("Unexpected interrupt: %d", spi_device_irq);
+      break;
+  }
+}
+
+static void ack_spi_tpm_header_irq(dif_spi_device_handle_t *spi_device) {
+  // Clear interrupt state and re-enable interrupt.
+  header_interrupt_received = false;
+  CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
+      &spi_device->dev, kDifSpiDeviceIrqTpmHeaderNotEmpty));
+  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
+      &spi_device->dev, kDifSpiDeviceIrqTpmHeaderNotEmpty, kDifToggleEnabled));
+}
+
+// This routine is needed to make sure that an interrupt does not sneak in
+// and jump excution away between the boolean check and the actual invocation
+// of wait_for_interrupt.
+static void atomic_wait_for_interrupt() {
+  irq_global_ctrl(false);
+  if (!header_interrupt_received) {
+    wait_for_interrupt();
+  }
+  irq_global_ctrl(true);
+}
+
+bool test_main(void) {
+  CHECK_DIF_OK(dif_pinmux_init(
+      mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux));
+
+  CHECK_DIF_OK(dif_spi_device_init_handle(
+      mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR), &spi_device));
+
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic));
+
+  // Set IoA7 for tpm csb.
+  // Longer term this needs to migrate to a top specific, platform specific
+  // setting.
+  CHECK_DIF_OK(dif_pinmux_input_select(
+      &pinmux, kTopEarlgreyPinmuxPeripheralInSpiDeviceTpmCsb,
+      kTopEarlgreyPinmuxInselIoa7));
+
+  CHECK_DIF_OK(
+      dif_spi_device_tpm_configure(&spi_device, kDifToggleEnabled, tpm_config));
+
+  // enable interrupts
+  en_plic_irqs(&plic);
+  en_spi_device_irqs(&spi_device.dev);
+
+  // Sync message with testbench to begin.
+  LOG_INFO("Begin TPM Test");
+
+  for (uint32_t i = 0; i < kIterations; i++) {
+    LOG_INFO("Iteration %d", i);
+
+    // Wait for write interrupt.
+    atomic_wait_for_interrupt();
+
+    // Check what comamnd we have received. Store it as expected variables
+    // and compare when the read command is issued.
+    uint8_t write_command;
+    uint32_t write_addr;
+    CHECK_DIF_OK(dif_spi_device_tpm_get_command(&spi_device, &write_command,
+                                                &write_addr));
+    CHECK((write_command & kTpmCommandRwMask) == kTpmWriteCommand,
+          "Expected write command, received read");
+
+    // Poll for write data to complete.
+    uint32_t num_bytes = (write_command & kTpmCommandSizeMask) + 1;
+    LOG_INFO("Expecting %d bytes from tpm write", num_bytes);
+
+    uint8_t buf[64];
+    dif_result_t status = kDifOutOfRange;
+    while (status == kDifOutOfRange) {
+      status = dif_spi_device_tpm_read_data(&spi_device, num_bytes, buf);
+    };
+    CHECK_DIF_OK(status);
+
+    // Finished processing the write command
+    ack_spi_tpm_header_irq(&spi_device);
+
+    // Wait for read interrupt.
+    atomic_wait_for_interrupt();
+    // Send the written data right back out for reads.
+    CHECK_DIF_OK(dif_spi_device_tpm_write_data(&spi_device, num_bytes, buf));
+
+    uint8_t read_command;
+    uint32_t read_addr;
+    CHECK_DIF_OK(
+        dif_spi_device_tpm_get_command(&spi_device, &read_command, &read_addr));
+    ack_spi_tpm_header_irq(&spi_device);
+
+    // Make sure the received command matches expectation
+    LOG_INFO("Expected 0x%x, received 0x%x",
+             (kTpmReadCommand | (num_bytes - 1)), read_command);
+    CHECK((kTpmReadCommand | (num_bytes - 1)) == read_command,
+          "Expected read command, received write");
+    CHECK(write_addr == read_addr, "Received address did not match");
+  }
+
+  return true;
+};