[spi_device/dv] Enhance read buffer sequences

Created a parallel thread to update read buffer based on watermark or
flip event.

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_cfg_cmd_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_cfg_cmd_vseq.sv
index ba1badd..c6b25ee 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_cfg_cmd_vseq.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_cfg_cmd_vseq.sv
@@ -8,7 +8,8 @@
   `uvm_object_new
 
   function void pre_randomize();
-    intercept_ops[$] = {WREN, WRDI, EN4B, EX4B};
+    super.pre_randomize();
+    target_ops = {WREN, WRDI, EN4B, EX4B};
   endfunction
 
   virtual task pre_start();
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_all_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_all_vseq.sv
index d54ee20..f83aef0 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_all_vseq.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_flash_all_vseq.sv
@@ -27,6 +27,7 @@
     allow_write_enable_disable = 1;
     allow_addr_cfg_cmd = 1;
 
+    forever_read_buffer_update_nonblocking();
     fork
       // this thread runs until the main_seq completes
       while (!main_seq_done) upload_fifo_read_seq();
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
index 5631cd6..5d0db31 100644
--- 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
@@ -10,4 +10,17 @@
   constraint device_mode_c {
     device_mode == FlashMode;
   }
+
+  function void pre_randomize();
+    super.pre_randomize();
+    target_ops = {READ_CMD_LIST};
+  endfunction
+
+  task body();
+    // increase the chance (15%->50%) to send large payload,
+    // in order to exercise watermark and flip events
+    large_payload_weight = 6;
+    forever_read_buffer_update_nonblocking();
+    super.body();
+  endtask
 endclass : spi_device_flash_mode_vseq
diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv
index 2cb5455..30204df 100644
--- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv
+++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv
@@ -7,19 +7,21 @@
 class spi_device_intercept_vseq extends spi_device_pass_cmd_filtering_vseq;
   `uvm_object_utils(spi_device_intercept_vseq)
   `uvm_object_new
-  bit [7:0] intercept_ops[$] = {READ_STATUS_1, READ_STATUS_2, READ_STATUS_3,
-                                READ_JEDEC,
-                                READ_SFDP,
-                                READ_CMD_LIST};
 
-  rand bit use_intercept_op;
+  // can override this queue to increase the chance to test these opcodes in extended vseq
+  bit [7:0] target_ops[$] = {READ_STATUS_1, READ_STATUS_2, READ_STATUS_3,
+                             READ_JEDEC,
+                             READ_SFDP,
+                             READ_CMD_LIST};
+
+  rand bit use_target_op;
   constraint opcode_c {
-    solve use_intercept_op before opcode;
-    if (use_intercept_op) {
-      opcode inside {intercept_ops};
+    solve use_target_op before opcode;
+    if (use_target_op) {
+      opcode inside {target_ops};
     } else {
       opcode inside {valid_opcode_q} &&
-      !(opcode inside {intercept_ops});
+      !(opcode inside {target_ops});
     }
   }
 
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 cc389f6..9c7ce35 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,10 @@
   // 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;
 
+  // knob to control read_buffer_update thread
+  bit stop_forever_read_buffer_update;
+  bit read_buffer_update_ongoing;
+
   rand device_mode_e device_mode;
 
   // overide this to enable other modes
@@ -55,6 +59,7 @@
   rand uint payload_size;
   rand read_addr_size_type_e read_addr_size_type;
 
+  int large_payload_weight = 1;
   constraint payload_size_c {
     payload_size dist {
         [0:5]    :/ 2,
@@ -341,7 +346,7 @@
     config_all_cmd_infos();
 
     spi_device_flash_auto_rsp_nonblocking();
-    randomize_mem();
+    random_write_spi_mem(.start_addr(0), .end_addr(SRAM_SIZE - 1));
     randomize_all_cmd_filters();
   endtask : spi_device_flash_pass_init
 
@@ -363,17 +368,6 @@
     end
   endtask : randomize_all_cmd_filters
 
-  // Task for preparing memory buffer for read commands
-  virtual task randomize_mem();
-    bit [31:0] buffer_data [1024];
-    `DV_CHECK_STD_RANDOMIZE_FATAL(buffer_data)
-    // Prepare Buffer
-    for (int i = 0; i < SRAM_SIZE / 4; i++) begin // Fill buffer with random data
-      mem_wr(.ptr(ral.buffer), .offset(i), .data(buffer_data[i]));
-      `uvm_info(`gfn, $sformatf("write mem addr 0x%0x: 0x%0x", i << 2, buffer_data[i]), UVM_MEDIUM)
-    end
-  endtask : randomize_mem
-
   // Task for configuring cmd info slot
   virtual task add_cmd_info(spi_flash_cmd_info info, bit [4:0] idx);
     bit [3:0] lanes_en;
@@ -533,6 +527,12 @@
     super.post_start();
     // read flash_status for check
     random_access_flash_status(.write(0));
+
+    // stop the read buffer thread and allow it to complete gracefully
+    if (read_buffer_update_ongoing) begin
+      stop_forever_read_buffer_update = 1;
+      `DV_WAIT(!read_buffer_update_ongoing)
+    end
   endtask
 
   virtual task read_and_check_4b_en();
@@ -540,4 +540,95 @@
     csr_rd_check(.ptr(ral.cfg.addr_4b_en),
                  .compare_value(cfg.spi_device_agent_cfg.flash_addr_4b_en));
   endtask
+
+  // read buffer can be divided into 2 half by flip events if threshold isn't set
+  // |____________half0___________|____________half1___________|
+  // |                          flip                         flip
+  //
+  // if threshold isn't 0, read buffer can be divided into 4 regions by watermark and flip events
+  // |___region0___|___region1____|___region2___|___region3____|
+  // |           watermark      flip          watermark      flip
+  // once SW receives an interrupt, update the previous region, so that it won't affect SPI read
+  // e.g. when 1st watermark occurs, update region0, then flip occurs, update 2nd region.
+  virtual task  update_read_buffer(
+      bit is_watermark_event, bit[TL_AW-1:0] last_read_addr);
+    bit [TL_AW-1:0] start_addr;
+    bit [TL_AW-1:0] end_addr;
+    int threshold = `gmv(ral.read_threshold);
+    int half_size = READ_BUFFER_SIZE / 2;
+
+    if (threshold == 0) begin // 2 halfs
+      if (last_read_addr inside {[half_size : READ_BUFFER_SIZE - 1]}) begin
+        // half0
+        start_addr = 0;
+        end_addr   = half_size - 1;
+      end else begin
+        // half1
+        start_addr = half_size;
+        end_addr   = READ_BUFFER_SIZE - 1;
+      end
+    end else begin // 4 regions
+      if (is_watermark_event) begin
+        if (last_read_addr inside {[threshold : threshold + half_size - 1]}) begin
+          // region0
+          start_addr = 0;
+          end_addr   = threshold - 1;
+        end else begin
+          // region2
+          start_addr = half_size;
+          end_addr   = half_size + threshold - 1;
+        end
+      end else begin // flip event
+        if (last_read_addr inside {[half_size : READ_BUFFER_SIZE - 1]}) begin
+          // region1
+          start_addr = threshold;
+          end_addr   = half_size - 1;
+        end else begin
+          // region3
+          start_addr = half_size + threshold;
+          end_addr   = READ_BUFFER_SIZE - 1;
+        end
+      end
+    end
+    random_write_spi_mem(start_addr, end_addr, "read buffer");
+  endtask : update_read_buffer
+
+  virtual task random_write_spi_mem(int start_addr, int end_addr, string msg_region = "mem");
+    for (int i = start_addr / 4; i <= end_addr / 4; i++) begin
+      bit [TL_DW-1:0] data = $urandom();
+      mem_wr(.ptr(ral.buffer), .offset(i), .data(data));
+      `uvm_info(`gfn, $sformatf("write %s offset 0x%0x: 0x%0x", msg_region, i << 2, data),
+                UVM_MEDIUM)
+    end
+  endtask : random_write_spi_mem
+
+  virtual task forever_read_buffer_update_nonblocking();
+    fork
+      begin
+        read_buffer_update_ongoing = 1;
+        while (!stop_forever_read_buffer_update) begin
+          bit [TL_DW-1] intr_state_val, last_read_addr;
+          bit is_watermark;
+          bit is_flip;
+
+          cfg.clk_rst_vif.wait_clks($urandom_range(10, 200));
+
+          csr_rd(ral.intr_state, intr_state_val);
+          is_watermark = get_field_val(ral.intr_state.readbuf_watermark, intr_state_val);
+          is_flip = get_field_val(ral.intr_state.readbuf_flip, intr_state_val);
+          csr_rd(ral.last_read_addr, last_read_addr);
+          if (!is_watermark && !is_flip) continue;
+
+          if (is_watermark) begin
+            update_read_buffer(.is_watermark_event(1), .last_read_addr(last_read_addr));
+          end
+          // we may receive both watermark and flip if watermark threshold is close to half size
+          if (is_flip) update_read_buffer(.is_watermark_event(0), .last_read_addr(last_read_addr));
+
+          csr_wr(ral.intr_state, intr_state_val);
+        end
+        read_buffer_update_ongoing = 0;
+      end
+    join_none
+  endtask : forever_read_buffer_update_nonblocking
 endclass : spi_device_pass_base_vseq