[spi_device/dv] Test flash mode read buffer

1. add `spi_device_flash_mode_vseq` to enable flash_mode
2. update scb
  1. add upstream_spi_req_fifo to get the item when opcode and address
  are collected
  2. check read command on read buffer while payload is being collected
  3. extract `compare_mem_byte` to convert addr and compare with exp mem
3. rename `spi_device_pass_all` to `spi_device_flash_all` as it randomly
  test flash_mode and passthrough_mode

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/ip/spi_device/data/spi_device_testplan.hjson b/hw/ip/spi_device/data/spi_device_testplan.hjson
index 5e74d35..a467d84 100644
--- a/hw/ip/spi_device/data/spi_device_testplan.hjson
+++ b/hw/ip/spi_device/data/spi_device_testplan.hjson
@@ -215,7 +215,7 @@
             - Check opcode and address are passing through again.
             - Invalid opcode is also filtered'''
       milestone: V2
-      tests: ["spi_device_pass_cmd_filtering", "spi_device_pass_all"]
+      tests: ["spi_device_pass_cmd_filtering", "spi_device_flash_all"]
     }
     {
       name: pass_addr_translation
@@ -228,7 +228,7 @@
             - Disable address translation for given command.
             - Check address is now passing unchanged.'''
       milestone: V2
-      tests: ["spi_device_pass_addr_payload_swap", "spi_device_pass_all"]
+      tests: ["spi_device_pass_addr_payload_swap", "spi_device_flash_all"]
     }
     {
       name: pass_payload_translation
@@ -240,7 +240,7 @@
             - Disable payload translation for given command.
             - Check payload is now passing unchanged.'''
       milestone: V2
-      tests: ["spi_device_pass_addr_payload_swap", "spi_device_pass_all"]
+      tests: ["spi_device_pass_addr_payload_swap", "spi_device_flash_all"]
     }
     {
       name: cmd_info_slots
@@ -253,7 +253,7 @@
             - Disable some cmd info slots.
             - Check no propagation of disabled commands.'''
       milestone: V2
-      tests: ["spi_device_pass_all"]
+      tests: ["spi_device_flash_all"]
     }
     {
       name: cmd_read_status
@@ -264,7 +264,7 @@
             - Initiate response to the read status.
             - Check proper reception of response.'''
       milestone: V2
-      tests: ["spi_device_intercept", "spi_device_pass_all"]
+      tests: ["spi_device_intercept", "spi_device_flash_all"]
     }
     {
       name: cmd_read_jedec
@@ -275,7 +275,7 @@
             - Initiate response to the read jedec.
             - Check proper reception of response.'''
       milestone: V2
-      tests: ["spi_device_intercept", "spi_device_pass_all"]
+      tests: ["spi_device_intercept", "spi_device_flash_all"]
     }
     {
       name: cmd_read_sfdp
@@ -286,7 +286,7 @@
             - Initiate response to the read sfdp.
             - Check proper reception of response.'''
       milestone: V2
-      tests: ["spi_device_intercept", "spi_device_pass_all"]
+      tests: ["spi_device_intercept", "spi_device_flash_all"]
     }
     {
       name: cmd_fast_read
@@ -297,7 +297,7 @@
             - Initiate response to the fast read.
             - Check proper reception of response.'''
       milestone: V2
-      tests: ["spi_device_intercept", "spi_device_pass_all"]
+      tests: ["spi_device_intercept", "spi_device_flash_all"]
     }
     {
       name: flash_cmd_upload
@@ -346,15 +346,16 @@
     {
       name: cmd_read_buffer
       desc: '''
-            - Configure device in flash or passthrough mode.
-            - SW updates read buffer contents.
-            - Issue read command.
+            - Configure device in flash mode.
+            - Issue read commands.
+            - Create another parallel thread that SW updates read buffer contents after a watermark
+              or buffer flip event occurs.
             - Check proper read data.
-            - Issue new read command that crosses read buffer boundary.
-            - Behavior on crossing uncertain //TODO Clarify spec on this
-            - Check internal buffer index bit.'''
+            - Randomly issue read command that crosses read buffer boundary and switches back to
+              index 0.
+            - Check correctness of `last_read_addr`, `readbuf_watermark` and `readbuf_flip`.'''
       milestone: V2
-      tests: []
+      tests: ["spi_device_flash_mode"]
     }
     {
       name: cmd_dummy_cycle
@@ -368,7 +369,7 @@
             - Issue new read command that crosses read maibox boundary.
             - Check internal buffer index bit.'''
       milestone: V2
-      tests: ["spi_device_mailbox", "spi_device_pass_all"]
+      tests: ["spi_device_mailbox", "spi_device_flash_all"]
     }
     {
       name: quad_spi
@@ -379,7 +380,7 @@
             - Issue supported command.
             - Check data on all four lines.'''
       milestone: V2
-      tests: ["spi_device_pass_all"]
+      tests: ["spi_device_flash_all"]
     }
     {
       name: dual_spi
@@ -390,7 +391,7 @@
             - Issue supported command.
             - Check data on both lines.'''
       milestone: V2
-      tests: ["spi_device_pass_all"]
+      tests: ["spi_device_flash_all"]
     }
     {
       name: 4b_3b_feature
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_mode_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_mode_vseq.sv
new file mode 100644
index 0000000..5631cd6
--- /dev/null
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_mode_vseq.sv
@@ -0,0 +1,13 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// set mode to FlashMode to test read buffer along with other intercept commands
+class spi_device_flash_mode_vseq extends spi_device_intercept_vseq;
+  `uvm_object_utils(spi_device_flash_mode_vseq)
+  `uvm_object_new
+
+  constraint device_mode_c {
+    device_mode == FlashMode;
+  }
+endclass : spi_device_flash_mode_vseq
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_all_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_all_vseq.sv
deleted file mode 100644
index 4f53dd6..0000000
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_all_vseq.sv
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-// Enable all passthrough related features during init, and then randomly send valid commands
-class spi_device_pass_all_vseq extends spi_device_pass_base_vseq;
-  `uvm_object_utils(spi_device_pass_all_vseq)
-  `uvm_object_new
-
-  int write_flash_status_pct = 30;
-
-  virtual task body();
-    bit main_seq_done;
-
-    allow_addr_swap    = 1;
-    allow_payload_swap = 1;
-    allow_intercept    = 1;
-
-    // enable upload
-    allow_upload = 1;
-    always_set_busy_when_upload_contain_payload = 1;
-
-    allow_write_enable_disable = 1;
-    allow_addr_cfg_cmd = 1;
-
-    fork
-      // this thread runs until the main_seq completes
-      while (!main_seq_done) upload_fifo_read_seq();
-      // main seq that sends spi items
-      begin
-        main_seq();
-        main_seq_done = 1;
-      end
-    join
-  endtask : body
-
-  virtual task upload_fifo_read_seq();
-    int upload_read_dly;
-    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(upload_read_dly,
-        upload_read_dly dist {
-            [0:10]      :/ 1,
-            [11:100]    :/ 2,
-            [101:1000]  :/ 2,
-            [1000:5000] :/ 1};)
-    cfg.clk_rst_vif.wait_clks(upload_read_dly);
-    read_upload_fifos();
-  endtask : upload_fifo_read_seq
-
-  virtual task main_seq();
-    for (int i = 0; i < num_trans; ++i) begin
-      `uvm_info(`gfn, $sformatf("running iteration %0d/%0d", i, num_trans), UVM_LOW)
-
-      spi_device_flash_pass_init(PassthroughMode);
-      for (int j = 0; j < 20; ++j) begin
-        if ($urandom_range(0, 99) < write_flash_status_pct) begin
-          random_access_flash_status(.write(1), .busy(1));
-        end else if ($urandom_range(0, 1)) begin
-          random_access_flash_status(.write(0));
-        end
-
-        if ($urandom_range(0, 1)) read_and_check_4b_en();
-
-        randomize_op_addr_size();
-        `uvm_info(`gfn, $sformatf("Testing op_num %0d/20, op = 0x%0h", j, opcode), UVM_MEDIUM)
-
-        spi_host_xfer_flash_item(opcode, payload_size, read_start_addr);
-        cfg.clk_rst_vif.wait_clks(10);
-      end
-    end
-  endtask : main_seq
-endclass : spi_device_pass_all_vseq
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv
index c7dd9ed..cc389f6 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv
@@ -21,6 +21,13 @@
   // we can only hold one payload, set it busy to avoid payload is overwritten before read out.
   bit always_set_busy_when_upload_contain_payload;
 
+  rand device_mode_e device_mode;
+
+  // overide this to enable other modes
+  constraint device_mode_c {
+    device_mode == PassthroughMode;
+  }
+
   bit [7:0] valid_opcode_q[$];
   rand bit valid_op;
   rand bit [7:0] opcode;
@@ -80,6 +87,12 @@
     }
   }
 
+  constraint addr_size_and_device_mode_c {
+    // flash mode doesn't support mailbox boundary crossing.
+    device_mode == FlashMode ->
+      read_addr_size_type inside {ReadAddrWithinMailbox, ReadAddrOutsideMailbox};
+  }
+
   `uvm_object_utils(spi_device_pass_base_vseq)
   `uvm_object_new
 
@@ -247,7 +260,7 @@
   endfunction
 
   // Task for flash or pass init
-  virtual task spi_device_flash_pass_init(device_mode_e mode);
+  virtual task spi_device_flash_pass_init();
     spi_device_init();
     `uvm_info(`gfn, "Initialize flash/passthrough mode", UVM_MEDIUM)
     // TODO, fixed config for now
@@ -277,13 +290,8 @@
     ral.cfg.cpha.set(1'b0);
     csr_update(.csr(ral.cfg)); // TODO check if randomization possible
     // Set the passthrough or flash mode mode
-    `DV_CHECK(mode inside {FlashMode, PassthroughMode});
-    if (mode == FlashMode) begin
-      ral.control.mode.set(FlashMode);
-    end
-    if (mode == PassthroughMode) begin
-      ral.control.mode.set(PassthroughMode);
-    end
+    `DV_CHECK(device_mode inside {FlashMode, PassthroughMode});
+    ral.control.mode.set(device_mode);
     csr_update(.csr(ral.control));
 
     // addr/payload swap settting
@@ -308,7 +316,8 @@
 
     // in passthrough, if upload is enabled, need to enable status intercept, so that host side
     // can know if spi_device is busy or not
-    if (allow_upload && mode == PassthroughMode && (`gmv(ral.intercept_en.status) == 0)) begin
+    if (allow_upload && device_mode == PassthroughMode && (`gmv(ral.intercept_en.status) == 0))
+    begin
       ral.intercept_en.status.set(1);
       csr_update(ral.intercept_en);
     end
@@ -502,7 +511,7 @@
 
     if (payload_depth_val > PAYLOAD_FIFO_SIZE) payload_depth_val = PAYLOAD_FIFO_SIZE;
     // need to shift by 2 for the offset used at mem_rd
-    payload_base_offset = (READ_CMD_BUFFER_SIZE + MAILBOX_BUFFER_SIZE + SFDP_SIZE) / 4;
+    payload_base_offset = (READ_BUFFER_SIZE + MAILBOX_BUFFER_SIZE + SFDP_SIZE) / 4;
     payload_depth_val = payload_depth_val / 4;
     for (int i = 0; i < payload_depth_val; i++) begin
       bit [TL_DW-1:0] val;
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_cmd_filtering_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_cmd_filtering_vseq.sv
index 3140847..dba4071 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_cmd_filtering_vseq.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_cmd_filtering_vseq.sv
@@ -11,7 +11,7 @@
 
     allow_set_cmd_info_invalid = 1;
     allow_use_invalid_opcode = 1;
-    spi_device_flash_pass_init(PassthroughMode);
+    spi_device_flash_pass_init();
 
     for (int i = 0; i < num_trans; ++i) begin
       randomize_op_addr_size();
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_vseq_list.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_vseq_list.sv
index d1ad720..c8400ce 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_vseq_list.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_vseq_list.sv
@@ -29,4 +29,5 @@
 `include "spi_device_mailbox_vseq.sv"
 `include "spi_device_upload_vseq.sv"
 `include "spi_device_cfg_cmd_vseq.sv"
-`include "spi_device_pass_all_vseq.sv"
+`include "spi_device_flash_mode_vseq.sv"
+`include "spi_device_flash_all_vseq.sv"
diff --git a/hw/ip/spi_device/dv/env/spi_device_env.core b/hw/ip/spi_device/dv/env/spi_device_env.core
index a1a78ee..cbe77aa 100644
--- a/hw/ip/spi_device/dv/env/spi_device_env.core
+++ b/hw/ip/spi_device/dv/env/spi_device_env.core
@@ -45,7 +45,8 @@
       - seq_lib/spi_device_mailbox_vseq.sv: {is_include_file: true}
       - seq_lib/spi_device_upload_vseq.sv: {is_include_file: true}
       - seq_lib/spi_device_cfg_cmd_vseq.sv: {is_include_file: true}
-      - seq_lib/spi_device_pass_all_vseq.sv: {is_include_file: true}
+      - seq_lib/spi_device_flash_mode_vseq.sv: {is_include_file: true}
+      - seq_lib/spi_device_flash_all_vseq.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
 generate:
diff --git a/hw/ip/spi_device/dv/env/spi_device_env.sv b/hw/ip/spi_device/dv/env/spi_device_env.sv
index 60bc4bc..3a6fb80 100644
--- a/hw/ip/spi_device/dv/env/spi_device_env.sv
+++ b/hw/ip/spi_device/dv/env/spi_device_env.sv
@@ -33,6 +33,8 @@
           scoreboard.upstream_spi_host_fifo.analysis_export);
       spi_host_agent.monitor.device_analysis_port.connect(
           scoreboard.upstream_spi_device_fifo.analysis_export);
+      spi_host_agent.monitor.req_analysis_port.connect(
+          scoreboard.upstream_spi_req_fifo.analysis_export);
       spi_device_agent.monitor.host_analysis_port.connect(
           scoreboard.downstream_spi_host_fifo.analysis_export);
     end
diff --git a/hw/ip/spi_device/dv/env/spi_device_env_pkg.sv b/hw/ip/spi_device/dv/env/spi_device_env_pkg.sv
index 500521d..c096cf4 100644
--- a/hw/ip/spi_device/dv/env/spi_device_env_pkg.sv
+++ b/hw/ip/spi_device/dv/env/spi_device_env_pkg.sv
@@ -83,10 +83,10 @@
   parameter uint SRAM_PTR_PHASE_BIT              = SRAM_MSB + 1;
   parameter uint SRAM_WORD_SIZE                  = 4;
   parameter uint ASYNC_FIFO_SIZE                 = 8;
-  parameter uint READ_CMD_START_ADDR             = SRAM_OFFSET;
-  parameter uint READ_CMD_BUFFER_SIZE            = 2048;
+  parameter uint READ_BUFFER_START_ADDR          = SRAM_OFFSET;
+  parameter uint READ_BUFFER_SIZE                = 2048;
   // MAILBOX_START_ADDR is 0x800
-  parameter uint MAILBOX_START_ADDR              = READ_CMD_START_ADDR + READ_CMD_BUFFER_SIZE;
+  parameter uint MAILBOX_START_ADDR              = READ_BUFFER_START_ADDR + READ_BUFFER_SIZE;
   parameter uint MAILBOX_BUFFER_SIZE             = 1024;
   // SFDP_START_ADDR is 0xc00
   parameter uint SFDP_START_ADDR                 = MAILBOX_START_ADDR + MAILBOX_BUFFER_SIZE;
diff --git a/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv b/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv
index 00a4083..7a78992 100644
--- a/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv
+++ b/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv
@@ -27,6 +27,8 @@
   // TLM fifos to pick up the packets
   uvm_tlm_analysis_fifo #(spi_item) upstream_spi_host_fifo;
   uvm_tlm_analysis_fifo #(spi_item) upstream_spi_device_fifo;
+  // connect with req_analysis_port where it receives item when opcode and address are received
+  uvm_tlm_analysis_fifo #(spi_item) upstream_spi_req_fifo;
   uvm_tlm_analysis_fifo #(spi_item) downstream_spi_host_fifo;
 
   // mem model to save expected value
@@ -66,6 +68,7 @@
     super.build_phase(phase);
     upstream_spi_host_fifo = new("upstream_spi_host_fifo", this);
     upstream_spi_device_fifo = new("upstream_spi_device_fifo", this);
+    upstream_spi_req_fifo = new("upstream_spi_req_fifo", this);
     downstream_spi_host_fifo = new("downstream_spi_host_fifo", this);
     tx_mem = mem_model#()::type_id::create("tx_mem", this);
     rx_mem = mem_model#()::type_id::create("rx_mem", this);
@@ -80,6 +83,7 @@
       process_upstream_spi_host_fifo();
       process_upstream_spi_device_fifo();
       process_downstream_spi_fifo();
+      process_read_buffer_cmd();
       forever_latch_flash_status();
     join_none
   endtask
@@ -103,14 +107,17 @@
           bit is_intercepted;
           bit set_busy;
           `DV_CHECK_EQ(item.item_type, SpiFlashTrans)
-          // downstream item should be in the queue at the same time, add small delay
-          #1ps;
-          cmd_type = triage_flash_cmd(item.opcode, set_busy);
-          `uvm_info(`gfn, $sformatf("Triage flash cmd: %s, set_busy: %0d", cmd_type, set_busy),
-                    UVM_MEDIUM)
 
-          is_intercepted = 1;
-          case (cmd_type)
+          // read buffer is handled at `process_read_buffer_cmd`
+          if (!is_read_buffer_cmd(item)) begin
+            // downstream item should be in the queue at the same time, add small delay
+            #1ps;
+            cmd_type = triage_flash_cmd(item.opcode, set_busy);
+            `uvm_info(`gfn, $sformatf("Triage flash cmd: %s, set_busy: %0d", cmd_type, set_busy),
+                      UVM_MEDIUM)
+
+            is_intercepted = 1;
+            case (cmd_type)
               NoInternalProcess: begin
                 is_intercepted = 0;
               end
@@ -129,7 +136,7 @@
                   `DV_CHECK_EQ_FATAL(spi_passthrough_downstream_q.size, 1)
                   downstream_item = spi_passthrough_downstream_q[0];
                 end
-                check_read_cmd_data(item, downstream_item);
+                check_read_cmd_data_for_non_read_buffer(item, downstream_item);
               end
               InternalProcessCfgCmd: begin
                 if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_en4b, item.opcode)) begin
@@ -152,17 +159,18 @@
                 process_upload_cmd(item);
               end
               default: `uvm_fatal(`gfn, "can't get here")
-          endcase
+            endcase
 
-          // if busy, passthrough is blocked
-          if (is_opcode_passthrough(item.opcode) && !`gmv(ral.flash_status.busy)) begin
-            compare_passthru_item(.upstream_item(item), .is_intercepted(is_intercepted));
-          end
+            // if busy, passthrough is blocked
+            if (is_opcode_passthrough(item.opcode) && !`gmv(ral.flash_status.busy)) begin
+              compare_passthru_item(.upstream_item(item), .is_intercepted(is_intercepted));
+            end
+          end // if (!is_read_buffer_cmd(item))
 
           latch_flash_status(set_busy, update_wel, wel_val);
         end
         default: `uvm_fatal(`gfn, $sformatf("Unexpected mode: %0d", `gmv(ral.control.mode)))
-      endcase
+      endcase // case (`gmv(ral.control.mode))
     end // forever
   endtask
 
@@ -250,6 +258,8 @@
     end
   endtask
 
+  // this only triages post-process cmd. read buffer cmd isn't handled here as it needs to be
+  // processed during transaction.
   virtual function internal_process_cmd_e triage_flash_cmd(bit[7:0] opcode, output bit set_busy);
     internal_process_cmd_e cmd_type;
     bit is_status, is_jedec, is_sfdp;
@@ -366,29 +376,29 @@
       `DV_CHECK_EQ(item.address_q.size, 3)
       offset = (item.address_q[2] + i) % SFDP_SIZE;
 
-      // get_normalized_addr makes it word-aligned, but we need byte offset
-      addr = get_converted_addr(offset + SFDP_START_ADDR);
-
-      `DV_CHECK(spi_mem.addr_exists(addr))
-      spi_mem.compare_byte(addr, item.payload_q[i]);
-      `uvm_info(`gfn, $sformatf("compare sfdp idx %0d, act: 0x%0x, mem addr 0x%0x, exp: 0x%0x",
-                offset, item.payload_q[i], addr, spi_mem.read_byte(addr)), UVM_MEDIUM)
+      compare_mem_byte(SFDP_START_ADDR, offset, item.payload_q[i], i, "SFDP");
     end
   endfunction
 
+  virtual function void compare_mem_byte(bit [31:0] base_addr, bit [31:0] offset, bit[7:0] act_val,
+                                         // these 2 inputs are for log only
+                                         int payload_idx, string msg);
+    bit [31:0] addr = get_converted_addr(base_addr + offset);
+    `DV_CHECK(spi_mem.addr_exists(addr))
+    spi_mem.compare_byte(addr, act_val);
+      `uvm_info(`gfn, $sformatf(
+                "%s compare idx: %0d, offset %0d, mem addr 0x%0x, act: 0x%0x, exp: 0x%0x",
+                msg, payload_idx, offset, addr, act_val, spi_mem.read_byte(addr)), UVM_MEDIUM)
+  endfunction
+
   // check read return data again exp_mem or downstream item
-  virtual function void check_read_cmd_data(spi_item up_item, spi_item dn_item);
+  // this doesn't handle read cmd falling in read buffer
+  virtual function void check_read_cmd_data_for_non_read_buffer(spi_item up_item,
+                                                                spi_item dn_item);
     bit [31:0] start_addr, end_addr;
-    bit [31:0] mbx_base_addr = get_mbx_base_addr(ral);
     // TODO, sample this for coverage
     read_addr_size_type_e read_addr_size_type;
     bit is_passthru = `gmv(ral.control.mode) == PassthroughMode;
-    bit mailbox_en;
-
-    if (`gmv(ral.cfg.mailbox_en)) begin
-      mailbox_en = 1;
-      if (is_passthru) mailbox_en &= `gmv(ral.intercept_en.mbx);
-    end
 
     foreach (up_item.address_q[i]) begin
       if (i > 0) start_addr = start_addr << 8;
@@ -400,34 +410,24 @@
     foreach (up_item.payload_q[i]) begin
       bit [31:0] cur_addr = start_addr + i;
 
-      // out of mbx region
-      if ((cur_addr < mbx_base_addr) || (cur_addr >= mbx_base_addr + MAILBOX_BUFFER_SIZE) ||
-          !mailbox_en) begin
+      if (is_in_mailbox_region(cur_addr)) begin
+        bit [31:0] offset = cur_addr % MAILBOX_BUFFER_SIZE;
+        compare_mem_byte(MAILBOX_START_ADDR, offset, up_item.payload_q[i], i, "Mailbox");
+      end else begin // out of mbx region
         string str;
 
-        if (is_passthru) begin // passthrough mode
-          if (dn_item != null) begin
-            str = $sformatf("compare mbx data with downstread item. idx %0d, up: 0x%0x, dn: 0x%0x",
-                            i, up_item.payload_q[i], dn_item.payload_q[i]);
-            `DV_CHECK_CASE_EQ(up_item.payload_q[i], dn_item.payload_q[i], str)
-          end else begin // cmd is filtered
-            str = $sformatf("compare mbx data. idx %0d, value 0x%0x != z",
-                            i, up_item.payload_q[i]);
-            `DV_CHECK_CASE_EQ(up_item.payload_q[i], 8'dz, str)
-          end
-        end else begin // flash mode
-          // TODO, support later, let it fail
-          `DV_CHECK_EQ(0, 1)
+        `DV_CHECK_EQ_FATAL(is_passthru, 1)
+        if (dn_item != null) begin
+          str = $sformatf("compare mbx data with downstread item. idx %0d, up: 0x%0x, dn: 0x%0x",
+                          i, up_item.payload_q[i], dn_item.payload_q[i]);
+          `DV_CHECK_CASE_EQ(up_item.payload_q[i], dn_item.payload_q[i], str)
+        end else begin // cmd is filtered
+          str = $sformatf("compare mbx data. idx %0d, value 0x%0x != z",
+                          i, up_item.payload_q[i]);
+          `DV_CHECK_CASE_EQ(up_item.payload_q[i], 8'dz, str)
         end
-        `uvm_info(`gfn, str, UVM_MEDIUM)
-      end else begin // in mbx region
-        bit [31:0] offset = cur_addr % MAILBOX_BUFFER_SIZE + MAILBOX_START_ADDR;
-        bit [31:0] addr   = get_converted_addr(offset);
 
-        `DV_CHECK(spi_mem.addr_exists(addr))
-        spi_mem.compare_byte(addr, up_item.payload_q[i]);
-        `uvm_info(`gfn, $sformatf("compare mbx idx %0d, act: 0x%0x, mem addr 0x%0x, exp: 0x%0x",
-                  i, up_item.payload_q[i], addr, spi_mem.read_byte(addr)), UVM_MEDIUM)
+        `uvm_info(`gfn, str, UVM_MEDIUM)
       end
     end
 
@@ -467,6 +467,32 @@
     end
   endfunction
 
+  virtual function bit is_read_buffer_cmd(spi_item item);
+    if (`gmv(ral.control.mode) != FlashMode ||
+        item.item_type != SpiFlashTrans ||
+        !(item.opcode inside {READ_CMD_LIST}) ||
+        !is_internal_recog_cmd(item.opcode)) begin
+      return 0;
+    end
+    if (is_in_mailbox_region(convert_addr_from_byte_queue(item.address_q))) return 0;
+    return 1;
+  endfunction
+
+  virtual function bit [31:0] is_in_mailbox_region(bit [31:0] addr);
+    bit [31:0] mbx_base_addr = get_mbx_base_addr(ral);
+    bit is_passthru = `gmv(ral.control.mode) == PassthroughMode;
+    bit mailbox_en;
+
+    if (`gmv(ral.cfg.mailbox_en)) begin
+      mailbox_en = 1;
+      if (is_passthru) mailbox_en &= `gmv(ral.intercept_en.mbx);
+    end
+    if (addr inside {[mbx_base_addr: mbx_base_addr + MAILBOX_BUFFER_SIZE - 1]} && mailbox_en) begin
+      return 1;
+    end
+    return 0;
+  endfunction
+
   // convert offset to the mem address that is used to find the locaiton in exp_mem
   // lsb 2 bit will be kept
   virtual function bit [31:0] get_converted_addr(bit [31:0] offset);
@@ -518,7 +544,8 @@
     downstream_item = spi_passthrough_downstream_q.pop_front();
 
     if (is_intercepted) begin
-      // compare opcode and address. data is ignored as data is checked in check_read_cmd_data
+      // compare opcode and address. data is ignored as data is checked in
+      // check_read_cmd_data_for_non_read_buffer
       `DV_CHECK_EQ(upstream_item.opcode, downstream_item.opcode)
       `DV_CHECK_EQ(upstream_item.address_q.size, downstream_item.address_q.size)
       foreach (upstream_item.address_q[i]) begin
@@ -555,6 +582,35 @@
     end
   endtask
 
+  // read buffer cmd is handled separately as we can't wait until the item is completed.
+  // while upstream reads the buffer, SW may prepare the data on the other side of the buffer.
+  // when the item completes, the buffer may be overwritten with other data
+  virtual task process_read_buffer_cmd();
+    forever begin
+      spi_item item;
+      uint payload_idx;
+      bit [31:0] start_addr, offset;
+
+      upstream_spi_req_fifo.get(item);
+      if (!is_read_buffer_cmd(item)) continue;
+
+      start_addr = convert_addr_from_byte_queue(item.address_q);
+      `DV_SPINWAIT(
+        while (1) begin
+          `DV_WAIT(item.payload_q.size > payload_idx || item.mon_item_complete)
+          if (item.payload_q.size > payload_idx) begin
+            offset = (start_addr + payload_idx) % READ_BUFFER_SIZE;
+            compare_mem_byte(READ_BUFFER_START_ADDR, offset, item.payload_q[payload_idx],
+                             payload_idx, "Read buffer");
+            payload_idx++;
+            `DV_CHECK_EQ(item.payload_q.size, payload_idx)
+          end
+          if (item.mon_item_complete) break;
+        end
+      )
+    end
+  endtask
+
   // process_tl_access:this task processes incoming access into the IP over tl interface
   // this is already called in cip_base_scoreboard::process_tl_a/d_chan_fifo tasks
   virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
@@ -823,6 +879,7 @@
     super.check_phase(phase);
     `DV_EOT_PRINT_TLM_FIFO_CONTENTS(spi_item, upstream_spi_host_fifo)
     `DV_EOT_PRINT_TLM_FIFO_CONTENTS(spi_item, upstream_spi_device_fifo)
+    `DV_EOT_PRINT_TLM_FIFO_CONTENTS(spi_item, upstream_spi_req_fifo)
     `DV_EOT_PRINT_TLM_FIFO_CONTENTS(spi_item, downstream_spi_host_fifo)
     `DV_CHECK_EQ(tx_word_q.size, 0)
     `DV_CHECK_EQ(rx_word_q.size, 0)
diff --git a/hw/ip/spi_device/dv/spi_device_sim_cfg.hjson b/hw/ip/spi_device/dv/spi_device_sim_cfg.hjson
index cb0fcb3..841a13f 100644
--- a/hw/ip/spi_device/dv/spi_device_sim_cfg.hjson
+++ b/hw/ip/spi_device/dv/spi_device_sim_cfg.hjson
@@ -171,8 +171,13 @@
     }
 
     {
-      name: spi_device_pass_all
-      uvm_test_seq: spi_device_pass_all_vseq
+      name: spi_device_flash_mode
+      uvm_test_seq: spi_device_flash_mode_vseq
+    }
+
+    {
+      name: spi_device_flash_all
+      uvm_test_seq: spi_device_flash_all_vseq
     }
   ]