test(spi_device): Upload sequence

This commit adds failed upload sequence to the test.
https://github.com/lowRISC/opentitan/issues/11871#issuecomment-1127940485

The Page Program sees incorrect command with this test.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv b/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
index 149d10e..c43baa1 100644
--- a/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
+++ b/hw/ip/spi_device/pre_dv/program/prog_passthrough_host.sv
@@ -46,7 +46,8 @@
          spi_device_pkg::CmdEn4B,
          spi_device_pkg::CmdEx4B,
          spi_device_pkg::CmdReadSfdp,
-         spi_device_pkg::CmdResetDevice;
+         spi_device_pkg::CmdResetDevice,
+         spi_device_pkg::CmdChipErase;
 
   // Timeout
   initial begin
@@ -91,6 +92,10 @@
         test_program(subtask_pass);
       end
 
+      "upload": begin
+        test_upload(subtask_pass);
+      end
+
       default: begin
         $display("Passthrough test requires '+TESTNAME=%%s'");
       end
@@ -457,6 +462,48 @@
 
   endtask : test_program
 
+  static task test_upload(output bit pass);
+    SpiTransProgram trans;
+    // Sequence
+
+    // Issue Chip Erase
+    spiflash_oponly(
+      sif.tb,
+      CmdChipErase
+    );
+
+    wait_trans();
+
+    // Issue two ~ more Read Status until BUSY is cleared
+
+    read_status();
+
+    do begin
+      #1us
+      @(negedge clk);
+      read_status();
+    end while (status[0][0] == 1'b 1);
+
+    // Issue Page Program
+    trans = new();
+    trans.randomize() with {
+        address < (local::FlashSize - size);
+    };
+    trans.display();
+
+    spiflash_program(
+      sif.tb,
+      trans.cmd,
+      trans.address,
+      trans.addr_mode,
+      trans.program_data
+    );
+
+    wait_trans();
+
+    // TODO: Check in scoreboard if opcode/ payload matches
+  endtask : test_upload
+
   // Access to Mirrored storage
   function automatic void write_byte(int unsigned addr, spi_data_t data);
     mirrored_storage[addr] = data;
diff --git a/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv b/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
index 24d1410..c0388a8 100644
--- a/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
+++ b/hw/ip/spi_device/pre_dv/program/prog_passthrough_sw.sv
@@ -12,7 +12,9 @@
   input logic rst_n,
 
   output tlul_pkg::tl_h2d_t h2d,
-  input  tlul_pkg::tl_d2h_t d2h
+  input  tlul_pkg::tl_d2h_t d2h,
+
+  input interrupt_t intr
 );
 
   initial begin
@@ -44,6 +46,11 @@
         // SPIFlash
         test_program();
       end
+
+      "upload": begin
+        // TODO: Read and check if cmds...
+        test_upload();
+      end
     endcase
 
     forever begin
@@ -141,6 +148,8 @@
 
     init_dpsram();
 
+    init_interrupts();
+
   endtask : init_spidevice_passthrough
 
   task automatic init_cmdinfo_list();
@@ -207,6 +216,16 @@
     end
   endfunction : init_dpsram
 
+  task automatic init_interrupts();
+    // Enable CMDFIFO Not Empty
+    tlul_write(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_INTR_ENABLE_OFFSET),
+      32'h 0000_0000 | (1 << BitCmdfifoNotEmpty),
+      4'b 1111
+    );
+  endtask : init_interrupts
+
   static task test_program();
     // Turning off INTERCEPT
     tlul_write(
@@ -227,4 +246,87 @@
 
   endtask : test_program
 
+  static task test_upload();
+    automatic logic [31:0] tl_rdata;
+    automatic logic [ 7:0] opcode; // cmd opcode
+    automatic logic [31:0] address;
+    automatic spi_data_t payload [256];
+
+    // Wait Upload Command FIFO
+    wait(intr.upload_cmdfifo_not_empty);
+
+    // Fetch CMDFIFO
+    tlul_read(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_UPLOAD_CMDFIFO_OFFSET),
+      tl_rdata
+    );
+
+    opcode = tl_rdata[7:0];
+
+    $display("SW: Received Command: %2Xh", opcode);
+
+    if (opcode != spi_device_pkg::CmdChipErase) begin
+      $display("SW: Unexpected Command: %2Xh / EXP(%2Xh)",
+        opcode,
+        spi_device_pkg::CmdChipErase);
+    end
+
+    // Process cmd (or push to TLM to process later)
+
+    // Clear Interrupt
+    tlul_write(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_INTR_STATE_OFFSET),
+      1 << BitCmdfifoNotEmpty,
+      4'b 1111
+    );
+
+    // Expect CMDFIFO Depth 0 (the delay is short so that Page Program should not arrived yet)
+    tlul_read(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_UPLOAD_STATUS_OFFSET),
+      tl_rdata
+    );
+
+    if (tl_rdata[4:0] != 0) begin
+      $display("SW: CMDFIFO not empty after fetching: %2d", tl_rdata[4:0]);
+    end
+
+    // Will have two or more Read Status
+
+    #($urandom_range(5,2) * 1us);
+
+    @(negedge clk);
+
+    // Clear BUSY
+    tlul_rmw(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_FLASH_STATUS_OFFSET),
+      32'h 0, // value
+      32'h 1  // mask
+    );
+
+    // Wait for Page Program
+    wait(intr.upload_cmdfifo_not_empty);
+
+    // Fetch CMDFIFO
+    tlul_read(
+      clk, h2d, d2h,
+      32'(spi_device_reg_pkg::SPI_DEVICE_UPLOAD_CMDFIFO_OFFSET),
+      tl_rdata
+    );
+
+    opcode = tl_rdata[7:0];
+
+    $display("SW: Received Command: %2Xh", opcode);
+
+    if (opcode != spi_device_pkg::CmdPageProgram) begin
+      $display("SW: Unexpected Command: %2Xh / EXP(%2Xh)",
+        opcode,
+        spi_device_pkg::CmdChipErase);
+    end
+
+  endtask : test_upload
+
 endprogram : prog_passthrough_sw
diff --git a/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson b/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
index e7b5aca..432ebd6 100644
--- a/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
+++ b/hw/ip/spi_device/pre_dv/spid_passthrough_sim_cfg.hjson
@@ -43,6 +43,13 @@
         "+TESTNAME=program"
       ]
     }
+    {
+      name: spid_passthrough_upload
+      // Chip Erase + Read Status + Page Program test
+      run_opts: [
+        "+TESTNAME=upload"
+      ]
+    }
   ]
 
   regressions: [
diff --git a/hw/ip/spi_device/pre_dv/tb/spid_common.sv b/hw/ip/spi_device/pre_dv/tb/spid_common.sv
index e8117fd..854fb9f 100644
--- a/hw/ip/spi_device/pre_dv/tb/spid_common.sv
+++ b/hw/ip/spi_device/pre_dv/tb/spid_common.sv
@@ -60,6 +60,26 @@
     dir_e       dir;
   } spi_fifo_t;
 
+  typedef struct packed {
+    logic generic_rx_full;
+    logic generic_rx_watermark;
+    logic generic_tx_watermark;
+    logic generic_rx_error;
+    logic generic_rx_overflow;
+    logic generic_tx_underflow;
+    logic upload_cmdfifo_not_empty;
+    logic upload_payload_not_empty;
+    logic upload_payload_overflow;
+    logic readbuf_watermark;
+    logic readbuf_flip;
+    logic tpm_header_not_empty;
+  } interrupt_t;
+
+  // Register parameters
+  parameter int unsigned BitCmdfifoNotEmpty  = 6;
+  parameter int unsigned BitReadbufWatermark = 9;
+  parameter int unsigned BitReadbufFlip      = 10;
+
   // Command list parameters
   import spi_device_pkg::cmd_info_t;
   import spi_device_pkg::NumTotalCmdInfo;
@@ -1123,6 +1143,24 @@
 
   endtask : tlul_read
 
+  task automatic tlul_rmw(
+    const ref logic clk,
+
+    ref tlul_pkg::tl_h2d_t       h2d,
+    const ref tlul_pkg::tl_d2h_t d2h,
+
+    input logic [31:0] address,
+    input logic [31:0] data,
+    input logic [31:0] mask
+  );
+
+    automatic logic [31:0] tl_data;
+
+    tlul_read(clk, h2d, d2h, address, tl_data);
+    tl_data = (tl_data & ~mask) | (mask & data);
+    tlul_write(clk, h2d, d2h, address, tl_data, 4'b 1111);
+  endtask : tlul_rmw
+
   // classes
   class SpiTrans;
     rand spi_device_pkg::spi_cmd_e cmd;
diff --git a/hw/ip/spi_device/pre_dv/tb/spid_passthrough_tb.sv b/hw/ip/spi_device/pre_dv/tb/spid_passthrough_tb.sv
index 948064e..0c8ea71 100644
--- a/hw/ip/spi_device/pre_dv/tb/spid_passthrough_tb.sv
+++ b/hw/ip/spi_device/pre_dv/tb/spid_passthrough_tb.sv
@@ -79,6 +79,8 @@
 
   prim_ram_2p_pkg::ram_2p_cfg_t ram_cfg; // tied
 
+  interrupt_t intr;
+
   // TB
   initial begin
     sck_clk.set_period_ps(SckPeriod);
@@ -104,7 +106,9 @@
 
     // TL ports
     .h2d (tl_h2d),
-    .d2h (tl_d2h)
+    .d2h (tl_d2h),
+
+    .intr (intr)
   );
 
   // Passthrough SPI Flash device
@@ -154,22 +158,22 @@
 
     // Interrupts
     // INTR: Generic mode : Not Testing here
-    .intr_generic_rx_full_o     (), // RX FIFO Full
-    .intr_generic_rx_watermark_o(), // RX FIFO above level
-    .intr_generic_tx_watermark_o(), // TX FIFO below level
-    .intr_generic_rx_error_o    (), // RX Frame error
-    .intr_generic_rx_overflow_o (), // RX Async FIFO Overflow
-    .intr_generic_tx_underflow_o(), // TX Async FIFO Underflow
+    .intr_generic_rx_full_o     (intr.generic_rx_full),
+    .intr_generic_rx_watermark_o(intr.generic_rx_watermark),
+    .intr_generic_tx_watermark_o(intr.generic_tx_watermark),
+    .intr_generic_rx_error_o    (intr.generic_rx_error),
+    .intr_generic_rx_overflow_o (intr.generic_rx_overflow),
+    .intr_generic_tx_underflow_o(intr.generic_tx_underflow),
 
     // INTR: Flash mode : Not testing here
-    .intr_upload_cmdfifo_not_empty_o(),
-    .intr_upload_payload_not_empty_o(),
-    .intr_upload_payload_overflow_o (),
-    .intr_readbuf_watermark_o       (),
-    .intr_readbuf_flip_o            (),
+    .intr_upload_cmdfifo_not_empty_o(intr.upload_cmdfifo_not_empty),
+    .intr_upload_payload_not_empty_o(intr.upload_payload_not_empty),
+    .intr_upload_payload_overflow_o (intr.upload_payload_overflow),
+    .intr_readbuf_watermark_o       (intr.readbuf_watermark),
+    .intr_readbuf_flip_o            (intr.readbuf_flip),
 
     // INTR: TPM mode : Not Testing here
-    .intr_tpm_header_not_empty_o(),
+    .intr_tpm_header_not_empty_o(intr.tpm_header_not_empty),
 
     // Memory configuration
     .ram_cfg_i (ram_cfg),
diff --git a/hw/ip/spi_device/rtl/spi_device_pkg.sv b/hw/ip/spi_device/rtl/spi_device_pkg.sv
index af14149..9d0a05c 100644
--- a/hw/ip/spi_device/rtl/spi_device_pkg.sv
+++ b/hw/ip/spi_device/rtl/spi_device_pkg.sv
@@ -368,6 +368,8 @@
 
     CmdReadSfdp = 8'h 5A,
 
+    CmdChipErase = 8'h C7,
+
     CmdEnableReset = 8'h 66,
     CmdResetDevice = 8'h 99
   } spi_cmd_e;