[spi_device] Double Buffer Manager

This commit implements the buffer management logic. It checkes the flip
and watermark events.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/data/spi_device.hjson b/hw/ip/spi_device/data/spi_device.hjson
index ffe4cee..65cd841 100644
--- a/hw/ip/spi_device/data/spi_device.hjson
+++ b/hw/ip/spi_device/data/spi_device.hjson
@@ -446,6 +446,21 @@
         } // f: device id
       ]
     } // R: JEDEC_ID
+    { name: "READ_THRESHOLD"
+      desc: '''Read Buffer threshold register.
+
+        '''
+      swaccess: "rw"
+      hwaccess: "hro"
+      fields: [
+        { bits: "9:0"
+          name: "threshold"
+          desc: '''If 0, disable the watermark. If non-zero, when the host
+            access above or equal to the threshold, it reports an interrupt.
+            '''
+        } // f: threshold
+      ]
+    } // R: READ_THRESHOLD
     { multireg: {
         cname: "SPI_DEVICE"
         name:  "CMD_FILTER"
diff --git a/hw/ip/spi_device/lint/spi_device.waiver b/hw/ip/spi_device/lint/spi_device.waiver
index d211764..12d9abd 100644
--- a/hw/ip/spi_device/lint/spi_device.waiver
+++ b/hw/ip/spi_device/lint/spi_device.waiver
@@ -175,3 +175,8 @@
 waive -rules {CALC_NEXT_STATE} -location {spid_status.sv} \
       -regexp {'byte_sel_d' is assigned a non-constant expression 'i'} \
       -comment "byte_sel_q is not a state but mux selection register. but coded similar to the state machine"
+
+## Terminal
+waive -rules {TERMINAL_STATE} -location {spid_readbuffer.sv} \
+      -regexp {'StActive' is detected.} \
+      -comment "StActive is final state waiting CSb de-assertion"
diff --git a/hw/ip/spi_device/rtl/spi_device.sv b/hw/ip/spi_device/rtl/spi_device.sv
index b005127..94d4169 100644
--- a/hw/ip/spi_device/rtl/spi_device.sv
+++ b/hw/ip/spi_device/rtl/spi_device.sv
@@ -60,6 +60,11 @@
   localparam int PtrW = SramAw + 1 + SDW;
   localparam int AsFifoDepthW = $clog2(FifoDepth+1);
 
+  localparam int unsigned ReadBufferDepth = spi_device_pkg::SramMsgDepth;
+  localparam int unsigned BufferAw        = $clog2(ReadBufferDepth);
+
+  // Derived parameters
+
   logic clk_spi_in, clk_spi_in_muxed, clk_spi_in_buf;   // clock for latch SDI
   logic clk_spi_out, clk_spi_out_muxed, clk_spi_out_buf; // clock for driving SDO
 
@@ -179,6 +184,9 @@
   // Mailbox in Passthrough needs to take SPI if readcmd hits mailbox address
   logic mailbox_assumed, passthrough_assumed_by_internal;
 
+  // Threshold value of a buffer in bytes
+  logic [BufferAw:0] readbuf_threshold;
+
   // Passthrouth config signals
   logic [255:0] cmd_filter;
 
@@ -388,6 +396,8 @@
   // Jedec ID
   assign jedec_id = {reg2hw.jedec_id.mf.q, reg2hw.jedec_id.id.q};
 
+  assign readbuf_threshold = reg2hw.read_threshold.q[BufferAw:0];
+
   // Passthrough config: value shall be stable while SPI transaction is active
   //assign cmd_filter = reg2hw.cmd_filter.q;
   always_comb begin
@@ -902,7 +912,7 @@
     .cmd_info_i     (cmd_info_broadcast),
     .cmd_info_idx_i (cmd_info_idx_broadcast),
 
-    .readbuf_threshold_i ('0), //$clog2(ReadBufferDepth)-1
+    .readbuf_threshold_i (readbuf_threshold),
 
     .addr_4b_en_i (cfg_addr_4b_en),
 
diff --git a/hw/ip/spi_device/rtl/spi_device_reg_pkg.sv b/hw/ip/spi_device/rtl/spi_device_reg_pkg.sv
index 6f32f7f..abd5c27 100644
--- a/hw/ip/spi_device/rtl/spi_device_reg_pkg.sv
+++ b/hw/ip/spi_device/rtl/spi_device_reg_pkg.sv
@@ -191,6 +191,10 @@
   } spi_device_reg2hw_jedec_id_reg_t;
 
   typedef struct packed {
+    logic [9:0] q;
+  } spi_device_reg2hw_read_threshold_reg_t;
+
+  typedef struct packed {
     logic        q;
   } spi_device_reg2hw_cmd_filter_mreg_t;
 
@@ -318,19 +322,20 @@
 
   // Register -> HW type
   typedef struct packed {
-    spi_device_reg2hw_intr_state_reg_t intr_state; // [878:873]
-    spi_device_reg2hw_intr_enable_reg_t intr_enable; // [872:867]
-    spi_device_reg2hw_intr_test_reg_t intr_test; // [866:855]
-    spi_device_reg2hw_alert_test_reg_t alert_test; // [854:853]
-    spi_device_reg2hw_control_reg_t control; // [852:847]
-    spi_device_reg2hw_cfg_reg_t cfg; // [846:834]
-    spi_device_reg2hw_fifo_level_reg_t fifo_level; // [833:802]
-    spi_device_reg2hw_rxf_ptr_reg_t rxf_ptr; // [801:786]
-    spi_device_reg2hw_txf_ptr_reg_t txf_ptr; // [785:770]
-    spi_device_reg2hw_rxf_addr_reg_t rxf_addr; // [769:738]
-    spi_device_reg2hw_txf_addr_reg_t txf_addr; // [737:706]
-    spi_device_reg2hw_flash_status_reg_t flash_status; // [705:680]
-    spi_device_reg2hw_jedec_id_reg_t jedec_id; // [679:656]
+    spi_device_reg2hw_intr_state_reg_t intr_state; // [888:883]
+    spi_device_reg2hw_intr_enable_reg_t intr_enable; // [882:877]
+    spi_device_reg2hw_intr_test_reg_t intr_test; // [876:865]
+    spi_device_reg2hw_alert_test_reg_t alert_test; // [864:863]
+    spi_device_reg2hw_control_reg_t control; // [862:857]
+    spi_device_reg2hw_cfg_reg_t cfg; // [856:844]
+    spi_device_reg2hw_fifo_level_reg_t fifo_level; // [843:812]
+    spi_device_reg2hw_rxf_ptr_reg_t rxf_ptr; // [811:796]
+    spi_device_reg2hw_txf_ptr_reg_t txf_ptr; // [795:780]
+    spi_device_reg2hw_rxf_addr_reg_t rxf_addr; // [779:748]
+    spi_device_reg2hw_txf_addr_reg_t txf_addr; // [747:716]
+    spi_device_reg2hw_flash_status_reg_t flash_status; // [715:690]
+    spi_device_reg2hw_jedec_id_reg_t jedec_id; // [689:666]
+    spi_device_reg2hw_read_threshold_reg_t read_threshold; // [665:656]
     spi_device_reg2hw_cmd_filter_mreg_t [255:0] cmd_filter; // [655:400]
     spi_device_reg2hw_addr_swap_mask_reg_t addr_swap_mask; // [399:368]
     spi_device_reg2hw_addr_swap_data_reg_t addr_swap_data; // [367:336]
@@ -365,32 +370,33 @@
   parameter logic [BlockAw-1:0] SPI_DEVICE_LAST_READ_ADDR_OFFSET = 13'h 34;
   parameter logic [BlockAw-1:0] SPI_DEVICE_FLASH_STATUS_OFFSET = 13'h 38;
   parameter logic [BlockAw-1:0] SPI_DEVICE_JEDEC_ID_OFFSET = 13'h 3c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_0_OFFSET = 13'h 40;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_1_OFFSET = 13'h 44;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_2_OFFSET = 13'h 48;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_3_OFFSET = 13'h 4c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_4_OFFSET = 13'h 50;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_5_OFFSET = 13'h 54;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_6_OFFSET = 13'h 58;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_7_OFFSET = 13'h 5c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_ADDR_SWAP_MASK_OFFSET = 13'h 60;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_ADDR_SWAP_DATA_OFFSET = 13'h 64;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_0_OFFSET = 13'h 68;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_1_OFFSET = 13'h 6c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_2_OFFSET = 13'h 70;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_3_OFFSET = 13'h 74;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_4_OFFSET = 13'h 78;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_5_OFFSET = 13'h 7c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_6_OFFSET = 13'h 80;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_7_OFFSET = 13'h 84;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_8_OFFSET = 13'h 88;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_9_OFFSET = 13'h 8c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_10_OFFSET = 13'h 90;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_11_OFFSET = 13'h 94;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_12_OFFSET = 13'h 98;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_13_OFFSET = 13'h 9c;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_14_OFFSET = 13'h a0;
-  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_15_OFFSET = 13'h a4;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_READ_THRESHOLD_OFFSET = 13'h 40;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_0_OFFSET = 13'h 44;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_1_OFFSET = 13'h 48;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_2_OFFSET = 13'h 4c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_3_OFFSET = 13'h 50;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_4_OFFSET = 13'h 54;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_5_OFFSET = 13'h 58;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_6_OFFSET = 13'h 5c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_FILTER_7_OFFSET = 13'h 60;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_ADDR_SWAP_MASK_OFFSET = 13'h 64;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_ADDR_SWAP_DATA_OFFSET = 13'h 68;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_0_OFFSET = 13'h 6c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_1_OFFSET = 13'h 70;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_2_OFFSET = 13'h 74;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_3_OFFSET = 13'h 78;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_4_OFFSET = 13'h 7c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_5_OFFSET = 13'h 80;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_6_OFFSET = 13'h 84;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_7_OFFSET = 13'h 88;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_8_OFFSET = 13'h 8c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_9_OFFSET = 13'h 90;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_10_OFFSET = 13'h 94;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_11_OFFSET = 13'h 98;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_12_OFFSET = 13'h 9c;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_13_OFFSET = 13'h a0;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_14_OFFSET = 13'h a4;
+  parameter logic [BlockAw-1:0] SPI_DEVICE_CMD_INFO_15_OFFSET = 13'h a8;
 
   // Reset values for hwext registers and their fields
   parameter logic [5:0] SPI_DEVICE_INTR_TEST_RESVAL = 6'h 0;
@@ -433,6 +439,7 @@
     SPI_DEVICE_LAST_READ_ADDR,
     SPI_DEVICE_FLASH_STATUS,
     SPI_DEVICE_JEDEC_ID,
+    SPI_DEVICE_READ_THRESHOLD,
     SPI_DEVICE_CMD_FILTER_0,
     SPI_DEVICE_CMD_FILTER_1,
     SPI_DEVICE_CMD_FILTER_2,
@@ -462,7 +469,7 @@
   } spi_device_id_e;
 
   // Register width information to check illegal writes
-  parameter logic [3:0] SPI_DEVICE_PERMIT [42] = '{
+  parameter logic [3:0] SPI_DEVICE_PERMIT [43] = '{
     4'b 0001, // index[ 0] SPI_DEVICE_INTR_STATE
     4'b 0001, // index[ 1] SPI_DEVICE_INTR_ENABLE
     4'b 0001, // index[ 2] SPI_DEVICE_INTR_TEST
@@ -479,32 +486,33 @@
     4'b 1111, // index[13] SPI_DEVICE_LAST_READ_ADDR
     4'b 0111, // index[14] SPI_DEVICE_FLASH_STATUS
     4'b 0111, // index[15] SPI_DEVICE_JEDEC_ID
-    4'b 1111, // index[16] SPI_DEVICE_CMD_FILTER_0
-    4'b 1111, // index[17] SPI_DEVICE_CMD_FILTER_1
-    4'b 1111, // index[18] SPI_DEVICE_CMD_FILTER_2
-    4'b 1111, // index[19] SPI_DEVICE_CMD_FILTER_3
-    4'b 1111, // index[20] SPI_DEVICE_CMD_FILTER_4
-    4'b 1111, // index[21] SPI_DEVICE_CMD_FILTER_5
-    4'b 1111, // index[22] SPI_DEVICE_CMD_FILTER_6
-    4'b 1111, // index[23] SPI_DEVICE_CMD_FILTER_7
-    4'b 1111, // index[24] SPI_DEVICE_ADDR_SWAP_MASK
-    4'b 1111, // index[25] SPI_DEVICE_ADDR_SWAP_DATA
-    4'b 0111, // index[26] SPI_DEVICE_CMD_INFO_0
-    4'b 0111, // index[27] SPI_DEVICE_CMD_INFO_1
-    4'b 0111, // index[28] SPI_DEVICE_CMD_INFO_2
-    4'b 0111, // index[29] SPI_DEVICE_CMD_INFO_3
-    4'b 0111, // index[30] SPI_DEVICE_CMD_INFO_4
-    4'b 0111, // index[31] SPI_DEVICE_CMD_INFO_5
-    4'b 0111, // index[32] SPI_DEVICE_CMD_INFO_6
-    4'b 0111, // index[33] SPI_DEVICE_CMD_INFO_7
-    4'b 0111, // index[34] SPI_DEVICE_CMD_INFO_8
-    4'b 0111, // index[35] SPI_DEVICE_CMD_INFO_9
-    4'b 0111, // index[36] SPI_DEVICE_CMD_INFO_10
-    4'b 0111, // index[37] SPI_DEVICE_CMD_INFO_11
-    4'b 0111, // index[38] SPI_DEVICE_CMD_INFO_12
-    4'b 0111, // index[39] SPI_DEVICE_CMD_INFO_13
-    4'b 0111, // index[40] SPI_DEVICE_CMD_INFO_14
-    4'b 0111  // index[41] SPI_DEVICE_CMD_INFO_15
+    4'b 0011, // index[16] SPI_DEVICE_READ_THRESHOLD
+    4'b 1111, // index[17] SPI_DEVICE_CMD_FILTER_0
+    4'b 1111, // index[18] SPI_DEVICE_CMD_FILTER_1
+    4'b 1111, // index[19] SPI_DEVICE_CMD_FILTER_2
+    4'b 1111, // index[20] SPI_DEVICE_CMD_FILTER_3
+    4'b 1111, // index[21] SPI_DEVICE_CMD_FILTER_4
+    4'b 1111, // index[22] SPI_DEVICE_CMD_FILTER_5
+    4'b 1111, // index[23] SPI_DEVICE_CMD_FILTER_6
+    4'b 1111, // index[24] SPI_DEVICE_CMD_FILTER_7
+    4'b 1111, // index[25] SPI_DEVICE_ADDR_SWAP_MASK
+    4'b 1111, // index[26] SPI_DEVICE_ADDR_SWAP_DATA
+    4'b 0111, // index[27] SPI_DEVICE_CMD_INFO_0
+    4'b 0111, // index[28] SPI_DEVICE_CMD_INFO_1
+    4'b 0111, // index[29] SPI_DEVICE_CMD_INFO_2
+    4'b 0111, // index[30] SPI_DEVICE_CMD_INFO_3
+    4'b 0111, // index[31] SPI_DEVICE_CMD_INFO_4
+    4'b 0111, // index[32] SPI_DEVICE_CMD_INFO_5
+    4'b 0111, // index[33] SPI_DEVICE_CMD_INFO_6
+    4'b 0111, // index[34] SPI_DEVICE_CMD_INFO_7
+    4'b 0111, // index[35] SPI_DEVICE_CMD_INFO_8
+    4'b 0111, // index[36] SPI_DEVICE_CMD_INFO_9
+    4'b 0111, // index[37] SPI_DEVICE_CMD_INFO_10
+    4'b 0111, // index[38] SPI_DEVICE_CMD_INFO_11
+    4'b 0111, // index[39] SPI_DEVICE_CMD_INFO_12
+    4'b 0111, // index[40] SPI_DEVICE_CMD_INFO_13
+    4'b 0111, // index[41] SPI_DEVICE_CMD_INFO_14
+    4'b 0111  // index[42] SPI_DEVICE_CMD_INFO_15
   };
 
 endpackage
diff --git a/hw/ip/spi_device/rtl/spi_device_reg_top.sv b/hw/ip/spi_device/rtl/spi_device_reg_top.sv
index 7c4d712..d9ff853 100644
--- a/hw/ip/spi_device/rtl/spi_device_reg_top.sv
+++ b/hw/ip/spi_device/rtl/spi_device_reg_top.sv
@@ -262,6 +262,9 @@
   logic [15:0] jedec_id_id_wd;
   logic [7:0] jedec_id_mf_qs;
   logic [7:0] jedec_id_mf_wd;
+  logic read_threshold_we;
+  logic [9:0] read_threshold_qs;
+  logic [9:0] read_threshold_wd;
   logic cmd_filter_0_we;
   logic cmd_filter_0_filter_0_qs;
   logic cmd_filter_0_filter_0_wd;
@@ -2304,6 +2307,33 @@
   );
 
 
+  // R[read_threshold]: V(False)
+
+  prim_subreg #(
+    .DW      (10),
+    .SwAccess(prim_subreg_pkg::SwAccessRW),
+    .RESVAL  (10'h0)
+  ) u_read_threshold (
+    .clk_i   (clk_i),
+    .rst_ni  (rst_ni),
+
+    // from register interface
+    .we     (read_threshold_we),
+    .wd     (read_threshold_wd),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.read_threshold.q),
+
+    // to register interface (read)
+    .qs     (read_threshold_qs)
+  );
+
+
 
   // Subregister 0 of Multireg cmd_filter
   // R[cmd_filter_0]: V(False)
@@ -12836,7 +12866,7 @@
 
 
 
-  logic [41:0] addr_hit;
+  logic [42:0] addr_hit;
   always_comb begin
     addr_hit = '0;
     addr_hit[ 0] = (reg_addr == SPI_DEVICE_INTR_STATE_OFFSET);
@@ -12855,32 +12885,33 @@
     addr_hit[13] = (reg_addr == SPI_DEVICE_LAST_READ_ADDR_OFFSET);
     addr_hit[14] = (reg_addr == SPI_DEVICE_FLASH_STATUS_OFFSET);
     addr_hit[15] = (reg_addr == SPI_DEVICE_JEDEC_ID_OFFSET);
-    addr_hit[16] = (reg_addr == SPI_DEVICE_CMD_FILTER_0_OFFSET);
-    addr_hit[17] = (reg_addr == SPI_DEVICE_CMD_FILTER_1_OFFSET);
-    addr_hit[18] = (reg_addr == SPI_DEVICE_CMD_FILTER_2_OFFSET);
-    addr_hit[19] = (reg_addr == SPI_DEVICE_CMD_FILTER_3_OFFSET);
-    addr_hit[20] = (reg_addr == SPI_DEVICE_CMD_FILTER_4_OFFSET);
-    addr_hit[21] = (reg_addr == SPI_DEVICE_CMD_FILTER_5_OFFSET);
-    addr_hit[22] = (reg_addr == SPI_DEVICE_CMD_FILTER_6_OFFSET);
-    addr_hit[23] = (reg_addr == SPI_DEVICE_CMD_FILTER_7_OFFSET);
-    addr_hit[24] = (reg_addr == SPI_DEVICE_ADDR_SWAP_MASK_OFFSET);
-    addr_hit[25] = (reg_addr == SPI_DEVICE_ADDR_SWAP_DATA_OFFSET);
-    addr_hit[26] = (reg_addr == SPI_DEVICE_CMD_INFO_0_OFFSET);
-    addr_hit[27] = (reg_addr == SPI_DEVICE_CMD_INFO_1_OFFSET);
-    addr_hit[28] = (reg_addr == SPI_DEVICE_CMD_INFO_2_OFFSET);
-    addr_hit[29] = (reg_addr == SPI_DEVICE_CMD_INFO_3_OFFSET);
-    addr_hit[30] = (reg_addr == SPI_DEVICE_CMD_INFO_4_OFFSET);
-    addr_hit[31] = (reg_addr == SPI_DEVICE_CMD_INFO_5_OFFSET);
-    addr_hit[32] = (reg_addr == SPI_DEVICE_CMD_INFO_6_OFFSET);
-    addr_hit[33] = (reg_addr == SPI_DEVICE_CMD_INFO_7_OFFSET);
-    addr_hit[34] = (reg_addr == SPI_DEVICE_CMD_INFO_8_OFFSET);
-    addr_hit[35] = (reg_addr == SPI_DEVICE_CMD_INFO_9_OFFSET);
-    addr_hit[36] = (reg_addr == SPI_DEVICE_CMD_INFO_10_OFFSET);
-    addr_hit[37] = (reg_addr == SPI_DEVICE_CMD_INFO_11_OFFSET);
-    addr_hit[38] = (reg_addr == SPI_DEVICE_CMD_INFO_12_OFFSET);
-    addr_hit[39] = (reg_addr == SPI_DEVICE_CMD_INFO_13_OFFSET);
-    addr_hit[40] = (reg_addr == SPI_DEVICE_CMD_INFO_14_OFFSET);
-    addr_hit[41] = (reg_addr == SPI_DEVICE_CMD_INFO_15_OFFSET);
+    addr_hit[16] = (reg_addr == SPI_DEVICE_READ_THRESHOLD_OFFSET);
+    addr_hit[17] = (reg_addr == SPI_DEVICE_CMD_FILTER_0_OFFSET);
+    addr_hit[18] = (reg_addr == SPI_DEVICE_CMD_FILTER_1_OFFSET);
+    addr_hit[19] = (reg_addr == SPI_DEVICE_CMD_FILTER_2_OFFSET);
+    addr_hit[20] = (reg_addr == SPI_DEVICE_CMD_FILTER_3_OFFSET);
+    addr_hit[21] = (reg_addr == SPI_DEVICE_CMD_FILTER_4_OFFSET);
+    addr_hit[22] = (reg_addr == SPI_DEVICE_CMD_FILTER_5_OFFSET);
+    addr_hit[23] = (reg_addr == SPI_DEVICE_CMD_FILTER_6_OFFSET);
+    addr_hit[24] = (reg_addr == SPI_DEVICE_CMD_FILTER_7_OFFSET);
+    addr_hit[25] = (reg_addr == SPI_DEVICE_ADDR_SWAP_MASK_OFFSET);
+    addr_hit[26] = (reg_addr == SPI_DEVICE_ADDR_SWAP_DATA_OFFSET);
+    addr_hit[27] = (reg_addr == SPI_DEVICE_CMD_INFO_0_OFFSET);
+    addr_hit[28] = (reg_addr == SPI_DEVICE_CMD_INFO_1_OFFSET);
+    addr_hit[29] = (reg_addr == SPI_DEVICE_CMD_INFO_2_OFFSET);
+    addr_hit[30] = (reg_addr == SPI_DEVICE_CMD_INFO_3_OFFSET);
+    addr_hit[31] = (reg_addr == SPI_DEVICE_CMD_INFO_4_OFFSET);
+    addr_hit[32] = (reg_addr == SPI_DEVICE_CMD_INFO_5_OFFSET);
+    addr_hit[33] = (reg_addr == SPI_DEVICE_CMD_INFO_6_OFFSET);
+    addr_hit[34] = (reg_addr == SPI_DEVICE_CMD_INFO_7_OFFSET);
+    addr_hit[35] = (reg_addr == SPI_DEVICE_CMD_INFO_8_OFFSET);
+    addr_hit[36] = (reg_addr == SPI_DEVICE_CMD_INFO_9_OFFSET);
+    addr_hit[37] = (reg_addr == SPI_DEVICE_CMD_INFO_10_OFFSET);
+    addr_hit[38] = (reg_addr == SPI_DEVICE_CMD_INFO_11_OFFSET);
+    addr_hit[39] = (reg_addr == SPI_DEVICE_CMD_INFO_12_OFFSET);
+    addr_hit[40] = (reg_addr == SPI_DEVICE_CMD_INFO_13_OFFSET);
+    addr_hit[41] = (reg_addr == SPI_DEVICE_CMD_INFO_14_OFFSET);
+    addr_hit[42] = (reg_addr == SPI_DEVICE_CMD_INFO_15_OFFSET);
   end
 
   assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ;
@@ -12929,7 +12960,8 @@
                (addr_hit[38] & (|(SPI_DEVICE_PERMIT[38] & ~reg_be))) |
                (addr_hit[39] & (|(SPI_DEVICE_PERMIT[39] & ~reg_be))) |
                (addr_hit[40] & (|(SPI_DEVICE_PERMIT[40] & ~reg_be))) |
-               (addr_hit[41] & (|(SPI_DEVICE_PERMIT[41] & ~reg_be)))));
+               (addr_hit[41] & (|(SPI_DEVICE_PERMIT[41] & ~reg_be))) |
+               (addr_hit[42] & (|(SPI_DEVICE_PERMIT[42] & ~reg_be)))));
   end
   assign intr_state_we = addr_hit[0] & reg_we & !reg_error;
 
@@ -13032,7 +13064,10 @@
   assign jedec_id_id_wd = reg_wdata[15:0];
 
   assign jedec_id_mf_wd = reg_wdata[23:16];
-  assign cmd_filter_0_we = addr_hit[16] & reg_we & !reg_error;
+  assign read_threshold_we = addr_hit[16] & reg_we & !reg_error;
+
+  assign read_threshold_wd = reg_wdata[9:0];
+  assign cmd_filter_0_we = addr_hit[17] & reg_we & !reg_error;
 
   assign cmd_filter_0_filter_0_wd = reg_wdata[0];
 
@@ -13097,7 +13132,7 @@
   assign cmd_filter_0_filter_30_wd = reg_wdata[30];
 
   assign cmd_filter_0_filter_31_wd = reg_wdata[31];
-  assign cmd_filter_1_we = addr_hit[17] & reg_we & !reg_error;
+  assign cmd_filter_1_we = addr_hit[18] & reg_we & !reg_error;
 
   assign cmd_filter_1_filter_32_wd = reg_wdata[0];
 
@@ -13162,7 +13197,7 @@
   assign cmd_filter_1_filter_62_wd = reg_wdata[30];
 
   assign cmd_filter_1_filter_63_wd = reg_wdata[31];
-  assign cmd_filter_2_we = addr_hit[18] & reg_we & !reg_error;
+  assign cmd_filter_2_we = addr_hit[19] & reg_we & !reg_error;
 
   assign cmd_filter_2_filter_64_wd = reg_wdata[0];
 
@@ -13227,7 +13262,7 @@
   assign cmd_filter_2_filter_94_wd = reg_wdata[30];
 
   assign cmd_filter_2_filter_95_wd = reg_wdata[31];
-  assign cmd_filter_3_we = addr_hit[19] & reg_we & !reg_error;
+  assign cmd_filter_3_we = addr_hit[20] & reg_we & !reg_error;
 
   assign cmd_filter_3_filter_96_wd = reg_wdata[0];
 
@@ -13292,7 +13327,7 @@
   assign cmd_filter_3_filter_126_wd = reg_wdata[30];
 
   assign cmd_filter_3_filter_127_wd = reg_wdata[31];
-  assign cmd_filter_4_we = addr_hit[20] & reg_we & !reg_error;
+  assign cmd_filter_4_we = addr_hit[21] & reg_we & !reg_error;
 
   assign cmd_filter_4_filter_128_wd = reg_wdata[0];
 
@@ -13357,7 +13392,7 @@
   assign cmd_filter_4_filter_158_wd = reg_wdata[30];
 
   assign cmd_filter_4_filter_159_wd = reg_wdata[31];
-  assign cmd_filter_5_we = addr_hit[21] & reg_we & !reg_error;
+  assign cmd_filter_5_we = addr_hit[22] & reg_we & !reg_error;
 
   assign cmd_filter_5_filter_160_wd = reg_wdata[0];
 
@@ -13422,7 +13457,7 @@
   assign cmd_filter_5_filter_190_wd = reg_wdata[30];
 
   assign cmd_filter_5_filter_191_wd = reg_wdata[31];
-  assign cmd_filter_6_we = addr_hit[22] & reg_we & !reg_error;
+  assign cmd_filter_6_we = addr_hit[23] & reg_we & !reg_error;
 
   assign cmd_filter_6_filter_192_wd = reg_wdata[0];
 
@@ -13487,7 +13522,7 @@
   assign cmd_filter_6_filter_222_wd = reg_wdata[30];
 
   assign cmd_filter_6_filter_223_wd = reg_wdata[31];
-  assign cmd_filter_7_we = addr_hit[23] & reg_we & !reg_error;
+  assign cmd_filter_7_we = addr_hit[24] & reg_we & !reg_error;
 
   assign cmd_filter_7_filter_224_wd = reg_wdata[0];
 
@@ -13552,13 +13587,13 @@
   assign cmd_filter_7_filter_254_wd = reg_wdata[30];
 
   assign cmd_filter_7_filter_255_wd = reg_wdata[31];
-  assign addr_swap_mask_we = addr_hit[24] & reg_we & !reg_error;
+  assign addr_swap_mask_we = addr_hit[25] & reg_we & !reg_error;
 
   assign addr_swap_mask_wd = reg_wdata[31:0];
-  assign addr_swap_data_we = addr_hit[25] & reg_we & !reg_error;
+  assign addr_swap_data_we = addr_hit[26] & reg_we & !reg_error;
 
   assign addr_swap_data_wd = reg_wdata[31:0];
-  assign cmd_info_0_we = addr_hit[26] & reg_we & !reg_error;
+  assign cmd_info_0_we = addr_hit[27] & reg_we & !reg_error;
 
   assign cmd_info_0_opcode_0_wd = reg_wdata[7:0];
 
@@ -13577,7 +13612,7 @@
   assign cmd_info_0_payload_en_0_wd = reg_wdata[19:16];
 
   assign cmd_info_0_payload_dir_0_wd = reg_wdata[20];
-  assign cmd_info_1_we = addr_hit[27] & reg_we & !reg_error;
+  assign cmd_info_1_we = addr_hit[28] & reg_we & !reg_error;
 
   assign cmd_info_1_opcode_1_wd = reg_wdata[7:0];
 
@@ -13596,7 +13631,7 @@
   assign cmd_info_1_payload_en_1_wd = reg_wdata[19:16];
 
   assign cmd_info_1_payload_dir_1_wd = reg_wdata[20];
-  assign cmd_info_2_we = addr_hit[28] & reg_we & !reg_error;
+  assign cmd_info_2_we = addr_hit[29] & reg_we & !reg_error;
 
   assign cmd_info_2_opcode_2_wd = reg_wdata[7:0];
 
@@ -13615,7 +13650,7 @@
   assign cmd_info_2_payload_en_2_wd = reg_wdata[19:16];
 
   assign cmd_info_2_payload_dir_2_wd = reg_wdata[20];
-  assign cmd_info_3_we = addr_hit[29] & reg_we & !reg_error;
+  assign cmd_info_3_we = addr_hit[30] & reg_we & !reg_error;
 
   assign cmd_info_3_opcode_3_wd = reg_wdata[7:0];
 
@@ -13634,7 +13669,7 @@
   assign cmd_info_3_payload_en_3_wd = reg_wdata[19:16];
 
   assign cmd_info_3_payload_dir_3_wd = reg_wdata[20];
-  assign cmd_info_4_we = addr_hit[30] & reg_we & !reg_error;
+  assign cmd_info_4_we = addr_hit[31] & reg_we & !reg_error;
 
   assign cmd_info_4_opcode_4_wd = reg_wdata[7:0];
 
@@ -13653,7 +13688,7 @@
   assign cmd_info_4_payload_en_4_wd = reg_wdata[19:16];
 
   assign cmd_info_4_payload_dir_4_wd = reg_wdata[20];
-  assign cmd_info_5_we = addr_hit[31] & reg_we & !reg_error;
+  assign cmd_info_5_we = addr_hit[32] & reg_we & !reg_error;
 
   assign cmd_info_5_opcode_5_wd = reg_wdata[7:0];
 
@@ -13672,7 +13707,7 @@
   assign cmd_info_5_payload_en_5_wd = reg_wdata[19:16];
 
   assign cmd_info_5_payload_dir_5_wd = reg_wdata[20];
-  assign cmd_info_6_we = addr_hit[32] & reg_we & !reg_error;
+  assign cmd_info_6_we = addr_hit[33] & reg_we & !reg_error;
 
   assign cmd_info_6_opcode_6_wd = reg_wdata[7:0];
 
@@ -13691,7 +13726,7 @@
   assign cmd_info_6_payload_en_6_wd = reg_wdata[19:16];
 
   assign cmd_info_6_payload_dir_6_wd = reg_wdata[20];
-  assign cmd_info_7_we = addr_hit[33] & reg_we & !reg_error;
+  assign cmd_info_7_we = addr_hit[34] & reg_we & !reg_error;
 
   assign cmd_info_7_opcode_7_wd = reg_wdata[7:0];
 
@@ -13710,7 +13745,7 @@
   assign cmd_info_7_payload_en_7_wd = reg_wdata[19:16];
 
   assign cmd_info_7_payload_dir_7_wd = reg_wdata[20];
-  assign cmd_info_8_we = addr_hit[34] & reg_we & !reg_error;
+  assign cmd_info_8_we = addr_hit[35] & reg_we & !reg_error;
 
   assign cmd_info_8_opcode_8_wd = reg_wdata[7:0];
 
@@ -13729,7 +13764,7 @@
   assign cmd_info_8_payload_en_8_wd = reg_wdata[19:16];
 
   assign cmd_info_8_payload_dir_8_wd = reg_wdata[20];
-  assign cmd_info_9_we = addr_hit[35] & reg_we & !reg_error;
+  assign cmd_info_9_we = addr_hit[36] & reg_we & !reg_error;
 
   assign cmd_info_9_opcode_9_wd = reg_wdata[7:0];
 
@@ -13748,7 +13783,7 @@
   assign cmd_info_9_payload_en_9_wd = reg_wdata[19:16];
 
   assign cmd_info_9_payload_dir_9_wd = reg_wdata[20];
-  assign cmd_info_10_we = addr_hit[36] & reg_we & !reg_error;
+  assign cmd_info_10_we = addr_hit[37] & reg_we & !reg_error;
 
   assign cmd_info_10_opcode_10_wd = reg_wdata[7:0];
 
@@ -13767,7 +13802,7 @@
   assign cmd_info_10_payload_en_10_wd = reg_wdata[19:16];
 
   assign cmd_info_10_payload_dir_10_wd = reg_wdata[20];
-  assign cmd_info_11_we = addr_hit[37] & reg_we & !reg_error;
+  assign cmd_info_11_we = addr_hit[38] & reg_we & !reg_error;
 
   assign cmd_info_11_opcode_11_wd = reg_wdata[7:0];
 
@@ -13786,7 +13821,7 @@
   assign cmd_info_11_payload_en_11_wd = reg_wdata[19:16];
 
   assign cmd_info_11_payload_dir_11_wd = reg_wdata[20];
-  assign cmd_info_12_we = addr_hit[38] & reg_we & !reg_error;
+  assign cmd_info_12_we = addr_hit[39] & reg_we & !reg_error;
 
   assign cmd_info_12_opcode_12_wd = reg_wdata[7:0];
 
@@ -13805,7 +13840,7 @@
   assign cmd_info_12_payload_en_12_wd = reg_wdata[19:16];
 
   assign cmd_info_12_payload_dir_12_wd = reg_wdata[20];
-  assign cmd_info_13_we = addr_hit[39] & reg_we & !reg_error;
+  assign cmd_info_13_we = addr_hit[40] & reg_we & !reg_error;
 
   assign cmd_info_13_opcode_13_wd = reg_wdata[7:0];
 
@@ -13824,7 +13859,7 @@
   assign cmd_info_13_payload_en_13_wd = reg_wdata[19:16];
 
   assign cmd_info_13_payload_dir_13_wd = reg_wdata[20];
-  assign cmd_info_14_we = addr_hit[40] & reg_we & !reg_error;
+  assign cmd_info_14_we = addr_hit[41] & reg_we & !reg_error;
 
   assign cmd_info_14_opcode_14_wd = reg_wdata[7:0];
 
@@ -13843,7 +13878,7 @@
   assign cmd_info_14_payload_en_14_wd = reg_wdata[19:16];
 
   assign cmd_info_14_payload_dir_14_wd = reg_wdata[20];
-  assign cmd_info_15_we = addr_hit[41] & reg_we & !reg_error;
+  assign cmd_info_15_we = addr_hit[42] & reg_we & !reg_error;
 
   assign cmd_info_15_opcode_15_wd = reg_wdata[7:0];
 
@@ -13969,6 +14004,10 @@
       end
 
       addr_hit[16]: begin
+        reg_rdata_next[9:0] = read_threshold_qs;
+      end
+
+      addr_hit[17]: begin
         reg_rdata_next[0] = cmd_filter_0_filter_0_qs;
         reg_rdata_next[1] = cmd_filter_0_filter_1_qs;
         reg_rdata_next[2] = cmd_filter_0_filter_2_qs;
@@ -14003,7 +14042,7 @@
         reg_rdata_next[31] = cmd_filter_0_filter_31_qs;
       end
 
-      addr_hit[17]: begin
+      addr_hit[18]: begin
         reg_rdata_next[0] = cmd_filter_1_filter_32_qs;
         reg_rdata_next[1] = cmd_filter_1_filter_33_qs;
         reg_rdata_next[2] = cmd_filter_1_filter_34_qs;
@@ -14038,7 +14077,7 @@
         reg_rdata_next[31] = cmd_filter_1_filter_63_qs;
       end
 
-      addr_hit[18]: begin
+      addr_hit[19]: begin
         reg_rdata_next[0] = cmd_filter_2_filter_64_qs;
         reg_rdata_next[1] = cmd_filter_2_filter_65_qs;
         reg_rdata_next[2] = cmd_filter_2_filter_66_qs;
@@ -14073,7 +14112,7 @@
         reg_rdata_next[31] = cmd_filter_2_filter_95_qs;
       end
 
-      addr_hit[19]: begin
+      addr_hit[20]: begin
         reg_rdata_next[0] = cmd_filter_3_filter_96_qs;
         reg_rdata_next[1] = cmd_filter_3_filter_97_qs;
         reg_rdata_next[2] = cmd_filter_3_filter_98_qs;
@@ -14108,7 +14147,7 @@
         reg_rdata_next[31] = cmd_filter_3_filter_127_qs;
       end
 
-      addr_hit[20]: begin
+      addr_hit[21]: begin
         reg_rdata_next[0] = cmd_filter_4_filter_128_qs;
         reg_rdata_next[1] = cmd_filter_4_filter_129_qs;
         reg_rdata_next[2] = cmd_filter_4_filter_130_qs;
@@ -14143,7 +14182,7 @@
         reg_rdata_next[31] = cmd_filter_4_filter_159_qs;
       end
 
-      addr_hit[21]: begin
+      addr_hit[22]: begin
         reg_rdata_next[0] = cmd_filter_5_filter_160_qs;
         reg_rdata_next[1] = cmd_filter_5_filter_161_qs;
         reg_rdata_next[2] = cmd_filter_5_filter_162_qs;
@@ -14178,7 +14217,7 @@
         reg_rdata_next[31] = cmd_filter_5_filter_191_qs;
       end
 
-      addr_hit[22]: begin
+      addr_hit[23]: begin
         reg_rdata_next[0] = cmd_filter_6_filter_192_qs;
         reg_rdata_next[1] = cmd_filter_6_filter_193_qs;
         reg_rdata_next[2] = cmd_filter_6_filter_194_qs;
@@ -14213,7 +14252,7 @@
         reg_rdata_next[31] = cmd_filter_6_filter_223_qs;
       end
 
-      addr_hit[23]: begin
+      addr_hit[24]: begin
         reg_rdata_next[0] = cmd_filter_7_filter_224_qs;
         reg_rdata_next[1] = cmd_filter_7_filter_225_qs;
         reg_rdata_next[2] = cmd_filter_7_filter_226_qs;
@@ -14248,15 +14287,15 @@
         reg_rdata_next[31] = cmd_filter_7_filter_255_qs;
       end
 
-      addr_hit[24]: begin
+      addr_hit[25]: begin
         reg_rdata_next[31:0] = addr_swap_mask_qs;
       end
 
-      addr_hit[25]: begin
+      addr_hit[26]: begin
         reg_rdata_next[31:0] = addr_swap_data_qs;
       end
 
-      addr_hit[26]: begin
+      addr_hit[27]: begin
         reg_rdata_next[7:0] = cmd_info_0_opcode_0_qs;
         reg_rdata_next[8] = cmd_info_0_addr_en_0_qs;
         reg_rdata_next[9] = cmd_info_0_addr_swap_en_0_qs;
@@ -14268,7 +14307,7 @@
         reg_rdata_next[20] = cmd_info_0_payload_dir_0_qs;
       end
 
-      addr_hit[27]: begin
+      addr_hit[28]: begin
         reg_rdata_next[7:0] = cmd_info_1_opcode_1_qs;
         reg_rdata_next[8] = cmd_info_1_addr_en_1_qs;
         reg_rdata_next[9] = cmd_info_1_addr_swap_en_1_qs;
@@ -14280,7 +14319,7 @@
         reg_rdata_next[20] = cmd_info_1_payload_dir_1_qs;
       end
 
-      addr_hit[28]: begin
+      addr_hit[29]: begin
         reg_rdata_next[7:0] = cmd_info_2_opcode_2_qs;
         reg_rdata_next[8] = cmd_info_2_addr_en_2_qs;
         reg_rdata_next[9] = cmd_info_2_addr_swap_en_2_qs;
@@ -14292,7 +14331,7 @@
         reg_rdata_next[20] = cmd_info_2_payload_dir_2_qs;
       end
 
-      addr_hit[29]: begin
+      addr_hit[30]: begin
         reg_rdata_next[7:0] = cmd_info_3_opcode_3_qs;
         reg_rdata_next[8] = cmd_info_3_addr_en_3_qs;
         reg_rdata_next[9] = cmd_info_3_addr_swap_en_3_qs;
@@ -14304,7 +14343,7 @@
         reg_rdata_next[20] = cmd_info_3_payload_dir_3_qs;
       end
 
-      addr_hit[30]: begin
+      addr_hit[31]: begin
         reg_rdata_next[7:0] = cmd_info_4_opcode_4_qs;
         reg_rdata_next[8] = cmd_info_4_addr_en_4_qs;
         reg_rdata_next[9] = cmd_info_4_addr_swap_en_4_qs;
@@ -14316,7 +14355,7 @@
         reg_rdata_next[20] = cmd_info_4_payload_dir_4_qs;
       end
 
-      addr_hit[31]: begin
+      addr_hit[32]: begin
         reg_rdata_next[7:0] = cmd_info_5_opcode_5_qs;
         reg_rdata_next[8] = cmd_info_5_addr_en_5_qs;
         reg_rdata_next[9] = cmd_info_5_addr_swap_en_5_qs;
@@ -14328,7 +14367,7 @@
         reg_rdata_next[20] = cmd_info_5_payload_dir_5_qs;
       end
 
-      addr_hit[32]: begin
+      addr_hit[33]: begin
         reg_rdata_next[7:0] = cmd_info_6_opcode_6_qs;
         reg_rdata_next[8] = cmd_info_6_addr_en_6_qs;
         reg_rdata_next[9] = cmd_info_6_addr_swap_en_6_qs;
@@ -14340,7 +14379,7 @@
         reg_rdata_next[20] = cmd_info_6_payload_dir_6_qs;
       end
 
-      addr_hit[33]: begin
+      addr_hit[34]: begin
         reg_rdata_next[7:0] = cmd_info_7_opcode_7_qs;
         reg_rdata_next[8] = cmd_info_7_addr_en_7_qs;
         reg_rdata_next[9] = cmd_info_7_addr_swap_en_7_qs;
@@ -14352,7 +14391,7 @@
         reg_rdata_next[20] = cmd_info_7_payload_dir_7_qs;
       end
 
-      addr_hit[34]: begin
+      addr_hit[35]: begin
         reg_rdata_next[7:0] = cmd_info_8_opcode_8_qs;
         reg_rdata_next[8] = cmd_info_8_addr_en_8_qs;
         reg_rdata_next[9] = cmd_info_8_addr_swap_en_8_qs;
@@ -14364,7 +14403,7 @@
         reg_rdata_next[20] = cmd_info_8_payload_dir_8_qs;
       end
 
-      addr_hit[35]: begin
+      addr_hit[36]: begin
         reg_rdata_next[7:0] = cmd_info_9_opcode_9_qs;
         reg_rdata_next[8] = cmd_info_9_addr_en_9_qs;
         reg_rdata_next[9] = cmd_info_9_addr_swap_en_9_qs;
@@ -14376,7 +14415,7 @@
         reg_rdata_next[20] = cmd_info_9_payload_dir_9_qs;
       end
 
-      addr_hit[36]: begin
+      addr_hit[37]: begin
         reg_rdata_next[7:0] = cmd_info_10_opcode_10_qs;
         reg_rdata_next[8] = cmd_info_10_addr_en_10_qs;
         reg_rdata_next[9] = cmd_info_10_addr_swap_en_10_qs;
@@ -14388,7 +14427,7 @@
         reg_rdata_next[20] = cmd_info_10_payload_dir_10_qs;
       end
 
-      addr_hit[37]: begin
+      addr_hit[38]: begin
         reg_rdata_next[7:0] = cmd_info_11_opcode_11_qs;
         reg_rdata_next[8] = cmd_info_11_addr_en_11_qs;
         reg_rdata_next[9] = cmd_info_11_addr_swap_en_11_qs;
@@ -14400,7 +14439,7 @@
         reg_rdata_next[20] = cmd_info_11_payload_dir_11_qs;
       end
 
-      addr_hit[38]: begin
+      addr_hit[39]: begin
         reg_rdata_next[7:0] = cmd_info_12_opcode_12_qs;
         reg_rdata_next[8] = cmd_info_12_addr_en_12_qs;
         reg_rdata_next[9] = cmd_info_12_addr_swap_en_12_qs;
@@ -14412,7 +14451,7 @@
         reg_rdata_next[20] = cmd_info_12_payload_dir_12_qs;
       end
 
-      addr_hit[39]: begin
+      addr_hit[40]: begin
         reg_rdata_next[7:0] = cmd_info_13_opcode_13_qs;
         reg_rdata_next[8] = cmd_info_13_addr_en_13_qs;
         reg_rdata_next[9] = cmd_info_13_addr_swap_en_13_qs;
@@ -14424,7 +14463,7 @@
         reg_rdata_next[20] = cmd_info_13_payload_dir_13_qs;
       end
 
-      addr_hit[40]: begin
+      addr_hit[41]: begin
         reg_rdata_next[7:0] = cmd_info_14_opcode_14_qs;
         reg_rdata_next[8] = cmd_info_14_addr_en_14_qs;
         reg_rdata_next[9] = cmd_info_14_addr_swap_en_14_qs;
@@ -14436,7 +14475,7 @@
         reg_rdata_next[20] = cmd_info_14_payload_dir_14_qs;
       end
 
-      addr_hit[41]: begin
+      addr_hit[42]: begin
         reg_rdata_next[7:0] = cmd_info_15_opcode_15_qs;
         reg_rdata_next[8] = cmd_info_15_addr_en_15_qs;
         reg_rdata_next[9] = cmd_info_15_addr_swap_en_15_qs;
diff --git a/hw/ip/spi_device/rtl/spi_readcmd.sv b/hw/ip/spi_device/rtl/spi_readcmd.sv
index 41c28f2..2edf501 100644
--- a/hw/ip/spi_device/rtl/spi_readcmd.sv
+++ b/hw/ip/spi_device/rtl/spi_readcmd.sv
@@ -105,7 +105,9 @@
   // SFDP Base Addr: the beginning index of the SFDP region in DPSRAM
   // SFDP Depth: The size of the SFDP buffer (64 fixed in the spi_device_pkg)
   parameter sram_addr_t  SfdpBaseAddr    = spi_device_pkg::SramSfdpIdx,
-  parameter int unsigned SfdpDepth       = spi_device_pkg::SramSfdpDepth
+  parameter int unsigned SfdpDepth       = spi_device_pkg::SramSfdpDepth,
+
+  localparam int unsigned BufferAw = $clog2(ReadBufferDepth)
 ) (
   input clk_i,
   input rst_ni,
@@ -142,8 +144,8 @@
   input cmd_info_t              cmd_info_i,
   input logic [CmdInfoIdxW-1:0] cmd_info_idx_i,
 
-  // Double buffering
-  input [$clog2(ReadBufferDepth)-2:0] readbuf_threshold_i,
+  // Double buffering in bytes
+  input [BufferAw:0] readbuf_threshold_i,
 
   // The command mode is 4B mode. Every read command receives 4B address
   input addr_4b_en_i,
@@ -171,10 +173,6 @@
 
 );
 
-  logic unused_threshold;
-  assign unused_threshold = ^readbuf_threshold_i;
-  assign read_watermark_o = 1'b 0;
-
   logic unused_p2s_sent ;
   assign unused_p2s_sent = p2s_sent_i;
 
@@ -324,6 +322,12 @@
   logic [7:0] p2s_byte;
   logic       p2s_valid_inclk;
 
+  logic sfdp_hit;
+  assign sfdp_hit = sel_dp_i == DpReadSFDP;
+
+  // Indication of data output phase
+  logic output_start;
+
   //////////////
   // Datapath //
   //////////////
@@ -353,7 +357,7 @@
   assign readbuf_address_o = readbuf_addr;
 
   always_comb begin
-    addr_d = '0; // default value. In 3B mode, upper most byte is 0
+    addr_d = addr_q; // default value. In 3B mode, upper most byte is 0
     addr_latch_en = 1'b0;
 
     // TODO: Handle the case of IO command
@@ -539,6 +543,8 @@
 
     io_mode_o = SingleIO;
 
+    output_start = 1'b 0;
+
     unique case (main_st)
       MainReset: begin
         if (sel_dp_i inside {DpReadCmd, DpReadSFDP}) begin
@@ -611,6 +617,8 @@
       MainOutput: begin
         bitcnt_dec = 1'b 1;
 
+        output_start = 1'b 1;
+
         // Note: p2s accepts the byte and latch inside at the first beat.
         // So, it is safe to change the data at the next cycle.
         p2s_valid_inclk = 1'b 1;
@@ -676,6 +684,28 @@
     .fifo_rdata_o  (fifo_rdata)
   );
 
+  // Double Buffer Management logic
+  spid_readbuffer #(
+    .ReadBufferDepth (ReadBufferDepth)
+  ) u_readbuffer (
+    .clk_i,
+    .rst_ni,
+
+    .sys_rst_ni,
+
+    .current_address_i (addr_d),
+    .threshold_i       (readbuf_threshold_i),
+
+    .sfdp_hit_i    (sfdp_hit),
+    .mailbox_hit_i (addr_in_mailbox),
+    .mailbox_en_i  (mailbox_en_i),
+
+    .start_i (output_start),
+
+    .event_watermark_o (read_watermark_o),
+    .event_flip_o      ()
+  );
+
   ////////////////
   // Assertions //
   ////////////////
diff --git a/hw/ip/spi_device/rtl/spid_readbuffer.sv b/hw/ip/spi_device/rtl/spid_readbuffer.sv
new file mode 100644
index 0000000..51e62f0
--- /dev/null
+++ b/hw/ip/spi_device/rtl/spid_readbuffer.sv
@@ -0,0 +1,169 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// SPI Flash Read Command: Read Buffer Manager
+/*
+The SPI Device IP uses the first half of the DPSRAM as a read buffer. The IP
+returns data from the read buffer based on the given address in the received
+read command. The read buffer size is 2kB in current design. The IP only uses
+lower 11 bits ($clog2(2048)) of the received read command address and uses the
+address to issue the read request to the DPSRAM.
+
+It is the SW responsibility to update the read buffer contents. Ths module
+provides a SW configurable read watermark CSR and read only
+`LAST_READ_ADDRESS` CSR. SW may use those registers to get the time to update
+the buffer contents.
+
+The module receives the current sent address and the threshold to determine
+the buffer flip and the watermark event. Buffer flip event occurs when the
+host system accesses the other side of the buffer. For example, in current
+total buffer size 2kB. If the host accesses 0xFFC then 0x1000. The buffer flip
+occurs (from Buffer 1 to Buffer 0). In contrast, if the host system crosses
+the buffer boundary in opposite direction(0x1000 --> 0xFFC), it is not assumed
+as a buffer flip event.
+
+The watermark event happens when the host system accesses the address greater
+than the watermark CSR for the first time in a buffer. Let's assume the
+threshold (watermark) CSR value is 0x400 (512B). If the host system issues
+a SPI read command at the address 0x3FC and read 16B data, the IP keeps
+checking the sent address (from 0x3FC then 0x3FD, 0x3FE, 0x3FF) and when it
+sends the 0x400 data, it raises a watermark event.
+
+The watermark event is sticky event. It is reported to the SW once and cleared
+by the flip event. So even the host system keeps reading 0x3FC and 0x400
+multiple times, the event won't be notified to the SW after the first report.
+*/
+
+`include "prim_assert.sv"
+
+module spid_readbuffer #(
+  // Buffer size: # of indices assigned to Read Buffer.
+  //    This is used to calculate double buffering and threshold.
+  parameter int unsigned ReadBufferDepth = spi_device_pkg::SramMsgDepth,
+
+  // Derived parameters
+  localparam int unsigned BufferAw = $clog2(ReadBufferDepth)
+) (
+  input clk_i,
+  input rst_ni,
+
+  input sys_rst_ni, // to keep the addr, bufidx, flip signals
+
+  input [31:0]       current_address_i,
+  input [BufferAw:0] threshold_i, // A buffer size among two buffers (in bytes)
+
+  input sfdp_hit_i,
+  input mailbox_hit_i,
+  input mailbox_en_i,
+
+  // start: data Output phase indicator. Either pulse or level are fine.
+  input start_i,
+
+  output logic event_watermark_o,
+  output logic event_flip_o
+);
+
+  ////////////////
+  // Definition //
+  ////////////////
+
+  localparam int unsigned BufferSize    = ReadBufferDepth
+                                          * spi_device_pkg::SramDw / 8; // bytes
+  localparam int unsigned OneBufferSize = BufferSize / 2;
+  localparam int unsigned OneBufferAw   = $clog2(OneBufferSize);
+
+  typedef enum logic {
+    StIdle,
+    StActive
+  } st_e;
+  st_e st_q, st_d;
+
+  ////////////
+  // Signal //
+  ////////////
+
+  logic watermark_cross;
+  logic watermark_crossed; // set by event / clear by flip
+
+  logic flip;
+
+  // The logic keeps next buffer address. Compare this with the
+  // current_address and if it hits with mask, then the flip event occurs.
+  // TODO: If the device goes sleep, the next_buffer_addr should be recoverable.
+  logic [31-OneBufferAw:0] next_buffer_addr;
+
+  logic active;
+
+  //////////////
+  // Datapath //
+  //////////////
+
+  assign active = (st_q == StActive);
+
+  // Flip event handling
+  always_ff @(posedge clk_i or negedge sys_rst_ni) begin
+    if (!sys_rst_ni) begin
+      next_buffer_addr <= (32-OneBufferAw)'(1); // pointing to next buffer
+    end else if (active && flip) begin
+      next_buffer_addr <= next_buffer_addr + 1'b 1;
+    end
+  end
+
+  logic [31-OneBufferAw:0] current_buffer_idx;
+  assign current_buffer_idx = current_address_i[31:OneBufferAw];
+  assign flip = current_buffer_idx == next_buffer_addr;
+
+  assign event_flip_o = active && flip;
+
+  // TODO: Consider the case if host jumps the address? (report error?)
+
+  // TODO: Consider the case of sleep and recover. Provide a way to recover
+  // `next_buffer_addr`?
+
+  // Watermark event: Threshold should not be 0 to enable the event
+  assign watermark_cross = (current_address_i[BufferAw:0] >= threshold_i)
+                         && |threshold_i;
+
+  always_ff @(posedge clk_i or negedge sys_rst_ni) begin
+    if (!sys_rst_ni) begin
+      watermark_crossed <= 1'b 0;
+    end else if (active && watermark_cross) begin
+      watermark_crossed <= 1'b 1;
+    end else if (active && flip) begin
+      watermark_crossed <= 1'b 0;
+    end
+  end
+
+  assign event_watermark_o = !watermark_crossed && watermark_cross ;
+
+  ///////////////////
+  // State Machine //
+  ///////////////////
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) st_q <= StIdle;
+    else         st_q <= st_d;
+  end
+
+  always_comb begin
+    st_d = st_q;
+
+    unique case (st_q)
+      StIdle: begin
+        if (start_i && !sfdp_hit_i && !(mailbox_en_i && mailbox_hit_i)) begin
+          st_d = StActive;
+        end
+      end
+
+      StActive: begin
+        // Deadend waiting CSb de-assertion
+        st_d = StActive;
+      end
+
+      default: begin
+        st_d = StIdle;
+      end
+    endcase
+  end
+
+endmodule : spid_readbuffer
diff --git a/hw/ip/spi_device/spi_device.core b/hw/ip/spi_device/spi_device.core
index 20a22de..c0366c3 100644
--- a/hw/ip/spi_device/spi_device.core
+++ b/hw/ip/spi_device/spi_device.core
@@ -24,6 +24,7 @@
       - rtl/spi_fwmode.sv
       - rtl/spi_cmdparse.sv
       - rtl/spid_readsram.sv
+      - rtl/spid_readbuffer.sv
       - rtl/spi_readcmd.sv
       - rtl/spi_passthrough.sv
       - rtl/spid_status.sv