[spi_device] Add Upload TB

This commit implements a TB runs simple upload function. It uploads a
couple of commands and checks the content inside the SRAM.

- Command only
- Command and Address
- Command and Payload
- Command / Address / Payload

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/pre_dv/spid_upload.core b/hw/ip/spi_device/pre_dv/spid_upload.core
new file mode 100644
index 0000000..43bff51
--- /dev/null
+++ b/hw/ip/spi_device/pre_dv/spid_upload.core
@@ -0,0 +1,32 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:spid_upload_sim:0.1"
+description: "SPI Device Flash mode: Upload functionality sim target"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:ip:spi_device
+    file_type: systemVerilogSource
+
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_utils
+      - lowrisc:dv:dv_test_status
+      - lowrisc:dv:common_ifs
+    files:
+      - tb/spid_common.sv
+      - tb/spid_upload_tb.sv
+    file_type: systemVerilogSource
+
+targets:
+  sim: &sim_target
+    toplevel: spid_upload_tb
+    filesets:
+      - files_rtl
+      - files_dv
+    default_tool: vcs
+
+  lint:
+    <<: *sim_target
diff --git a/hw/ip/spi_device/pre_dv/spid_upload_sim_cfg.hjson b/hw/ip/spi_device/pre_dv/spid_upload_sim_cfg.hjson
new file mode 100644
index 0000000..cf65c6c
--- /dev/null
+++ b/hw/ip/spi_device/pre_dv/spid_upload_sim_cfg.hjson
@@ -0,0 +1,33 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{
+  name: spid_upload
+  dut:  spid_upload
+  tb:   spid_upload_tb
+  tool: vcs
+  fusesoc_core: lowrisc:dv:spid_upload_sim:0.1
+  import_cfgs: ["{proj_root}/hw/dv/tools/dvsim/common_sim_cfg.hjson"]
+  reseed: 1
+
+  build_modes: [
+  ]
+
+  tests: [
+    {
+      name: spid_upload_smoke
+      //build_mode: spid_status_locality_1
+    }
+  ]
+
+  regressions: [
+    {
+      name: smoke
+      tests: ["spid_upload_smoke"]
+    }
+    {
+      name: nightly
+      tests: ["spid_upload_smoke"]
+    }
+  ]
+}
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 1ec181e..abf9738 100644
--- a/hw/ip/spi_device/pre_dv/tb/spid_common.sv
+++ b/hw/ip/spi_device/pre_dv/tb/spid_common.sv
@@ -47,6 +47,419 @@
     dir_e       dir;
   } spi_fifo_t;
 
+  // Command list parameters
+  import spi_device_pkg::cmd_info_t;
+  import spi_device_pkg::payload_dir_e;
+  import spi_device_pkg::PayloadIn;
+  import spi_device_pkg::PayloadOut;
+
+  parameter spi_device_pkg::cmd_info_t [23:0] CmdInfo = {
+    // 23:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h E6,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 22:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h E7,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 21:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h E8,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 20:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h E9,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 19:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h EA,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 18:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h EB,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 17:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h EC,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 16:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h EF,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 15:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F0,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 14:
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F1,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 13: Upload Cmd & Addr & Payload
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 02,
+      addr_en:          1'b 1,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 1,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0001, // MISO
+      payload_dir:      PayloadIn,
+      upload:           1'b 1,
+      busy:             1'b 1
+    },
+
+    // 12: Upload Cmd & Addr
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 52, // block erase (32kB)
+      addr_en:          1'b 1,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 1,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0000, // MISO
+      payload_dir:      PayloadIn,
+      upload:           1'b 1,
+      busy:             1'b 1
+    },
+
+    // 11: Upload Cmd Only
+    '{
+      valid:            1'b 1,
+      opcode:           8'h C7, // chip erase
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0000, // MISO
+      payload_dir:      PayloadIn,
+      upload:           1'b 1,
+      busy:             1'b 1
+    },
+
+    // 10: ReadCmd 6 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F5,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 9: ReadCmd 5 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F6,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 8: ReadCmd 4 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F7,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 7: ReadCmd 3 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F8,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 6: ReadCmd 2 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h F9,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 5: ReadCmd 1 TODO
+    '{
+      valid:            1'b 0,
+      opcode:           8'h FA,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 4: ReadSfdp
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 5A,
+      addr_en:          1'b 1,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 1,
+      dummy_size:       3'h 7,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 3: CmdInfoReadJedecId
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 9F,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 2: CmdInfoReadStatus3
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 15,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 1: CmdInfoReadStatus2
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 35,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    },
+
+    // 0: CmdInfoReadStatus1
+    '{
+      valid:            1'b 1,
+      opcode:           8'h 05,
+      addr_en:          1'b 0,
+      addr_swap_en:     1'b 0,
+      addr_4b_affected: 1'b 0,
+      mbyte_en:         1'b 0,
+      dummy_en:         1'b 0,
+      dummy_size:          '0,
+      payload_en:       4'b 0010, // MISO
+      payload_dir:      PayloadOut,
+      upload:           1'b 0,
+      busy:             1'b 0
+    }
+  };
+
+  // Temporarily only two cmds specified here
+  parameter logic [7:0] OpcodeCmdOnly [2] = '{
+    8'h C7, // Chip Erase
+    8'h 06  // Write Enable
+  };
+
+  parameter logic [7:0] OpcodeCmdAddr [2] = '{
+    8'h 52, // Block Erase (32kB)
+    8'h D8  // Block Erase (64kB)
+  };
+
+  parameter logic [7:0] OpcodeCmdAddrPayload [2] = '{
+    8'h 02, // Page Program
+    8'h 42  // Program Security Register
+  };
+
+  parameter logic [7:0] OpcodeCmdPayload [2] = '{
+    8'h 01, // Write Status 1
+    8'h 11  // Write Status 3
+  };
+
   // spi_transaction: Send/ Receive data using data fifo.
   task automatic spi_transaction(
     virtual spi_if.tb sif,
@@ -282,4 +695,86 @@
 
   endtask : spiflash_readjedec
 
+  task automatic spiflash_chiperase(
+    virtual spi_if.tb  sif,
+    input spi_data_t   opcode
+  );
+    automatic spi_fifo_t send_data [$];
+    automatic spi_data_t rcv_data  [$];
+
+    send_data.push_back('{data: opcode, dir: DirIn,  mode: IoSingle});
+
+    spi_transaction(sif, send_data, rcv_data);
+
+    $display("Chip Erase (%x) is sent", opcode);
+
+  endtask : spiflash_chiperase
+
+  task automatic spiflash_blockerase(
+    virtual spi_if.tb  sif,
+    input spi_data_t   opcode,
+    input logic [31:0] addr,
+    input bit          addr_4b_en
+  );
+    automatic spi_fifo_t send_data [$];
+    automatic spi_data_t rcv_data  [$];
+
+    send_data.push_back('{data: opcode, dir: DirIn, mode: IoSingle});
+    if (addr_4b_en) begin
+      send_data.push_back('{data: addr[31:24], dir: DirNone, mode: IoNone});
+    end
+    for (int i = 2 ; i >= 0; i--) begin
+      send_data.push_back('{data: addr[8*i+:8], dir: DirNone, mode: IoNone});
+    end
+
+    spi_transaction(sif, send_data, rcv_data);
+
+    if (addr_4b_en) begin
+      $display("Block Erase (%Xh) command is sent. 32bit addr (%Xh)",
+        opcode, addr);
+    end else begin
+      $display("Block Erase (%Xh) command is sent. 24bit addr (%Xh)",
+        opcode, addr[23:0]);
+    end
+
+  endtask : spiflash_blockerase
+
+  task automatic spiflash_program(
+    virtual spi_if.tb sif,
+    input spi_data_t  opcode,
+    input logic [31:0] addr,
+    input bit          addr_4b_en,
+    input spi_data_t   payload [$]
+  );
+    automatic spi_fifo_t send_data [$];
+    automatic spi_data_t rcv_data  [$];
+
+    send_data.push_back('{data: opcode, dir: DirIn, mode: IoSingle});
+    if (addr_4b_en) begin
+      send_data.push_back('{data: addr[31:24], dir: DirNone, mode: IoNone});
+    end
+    for (int i = 2 ; i >= 0; i--) begin
+      send_data.push_back('{data: addr[8*i+:8], dir: DirNone, mode: IoNone});
+    end
+
+    // Payload
+    foreach (payload[i]) begin
+      send_data.push_back('{
+        data: payload[i],
+        dir:  DirNone,
+        mode: IoNone
+      });
+    end
+
+    spi_transaction(sif, send_data, rcv_data);
+
+    $display("Program cmd (%Xh) to Addr (%Xh) is sent.",
+      opcode, (addr_4b_en) ? addr : addr[23:0]);
+
+    foreach (payload[i]) begin
+      $display("Payload %d : %Xh", i, payload[i]);
+    end
+
+  endtask : spiflash_program
+
 endpackage : spid_common
diff --git a/hw/ip/spi_device/pre_dv/tb/spid_upload_tb.sv b/hw/ip/spi_device/pre_dv/tb/spid_upload_tb.sv
new file mode 100644
index 0000000..bdeefa7
--- /dev/null
+++ b/hw/ip/spi_device/pre_dv/tb/spid_upload_tb.sv
@@ -0,0 +1,572 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Testbench module for spid_jedec
+
+module spid_upload_tb;
+
+  import spi_device_pkg::*;
+
+  import spid_common::*;
+
+  localparam time ClkPeriod = 10000; // 10ns
+  localparam time SckPeriod = 14000; // 14ns
+
+  wire clk, rst_n;
+  clk_rst_if main_clk (
+    .clk,
+    .rst_n
+  );
+
+  wire sck, sck_rst_n;
+  clk_rst_if sck_clk (
+    .clk   (sck),
+    .rst_n (sck_rst_n)
+  );
+
+  spi_if sif(sck);
+
+  virtual spi_if.tb  tb_sif  = sif.tb;
+  virtual spi_if.dut dut_sif = sif.dut;
+
+  logic [3:0] dut_sd_en, dut_sd;
+  logic [3:0] tb_sd_en,  tb_sd;
+
+  for (genvar i = 0 ; i < 4 ; i++) begin : g_dut_sif
+    assign sif.sd_out[i] = dut_sd_en[i] ? dut_sd[i] : 1'b Z;
+  end
+
+  wire sck_en, gated_sck, gated_sck_inverted;
+
+  assign gated_sck          = (sck_en) ? sck : 1'b 0;
+  assign gated_sck_inverted = ~gated_sck;
+
+  assign sck_en = ~sif.csb;
+
+  logic rst_spi_n;
+  assign rst_spi_n = sck_rst_n && ~sif.csb;
+
+  sel_datapath_e dut_sel_dp;
+
+  logic [CmdInfoIdxW-1:0] cmd_info_idx;
+  cmd_info_t cmd_info;
+
+  // CmdInfo is defined in spid_common.sv
+  // ReserveStart:ReserveStart+2 are for upload test
+
+  logic sck_busy_set; // SCK
+
+  spi_mode_e spi_mode;
+
+  logic               s2p_valid;
+  logic [7:0]         s2p_data;
+  logic [BitCntW-1:0] s2p_bitcnt;
+
+  // Memory interface
+  logic              bus_mem_req;
+  logic              bus_mem_write;
+  logic [SramAw-1:0] bus_mem_addr;
+  logic [SramDw-1:0] bus_mem_wdata;
+  logic [SramDw-1:0] bus_mem_wmask;
+  logic              bus_mem_rvalid;
+  logic [SramDw-1:0] bus_mem_rdata;
+  logic [1:0]        bus_mem_rerror;
+
+  localparam int unsigned ArbCnt = 3; // CmdAddr, Payload, Sw
+  logic [ArbCnt-1:0] arb_req           ;
+  logic [ArbCnt-1:0] arb_gnt           ;
+  logic [ArbCnt-1:0] arb_write         ;
+  logic [SramAw-1:0] arb_addr  [ArbCnt];
+  logic [SramDw-1:0] arb_wdata [ArbCnt];
+  logic [SramDw-1:0] arb_wmask [ArbCnt];
+  logic [ArbCnt-1:0] arb_rvalid        ;
+  logic [SramDw-1:0] arb_rdata [ArbCnt];
+  logic [1:0]        arb_error [ArbCnt];
+
+  spi_device_pkg::sram_l2m_t arb_l2m [ArbCnt];
+  spi_device_pkg::sram_m2l_t arb_m2l [ArbCnt];
+
+  spi_device_pkg::sram_l2m_t sw_l2m;
+  spi_device_pkg::sram_m2l_t sw_m2l;
+  assign arb_l2m[2] = sw_l2m;
+  assign sw_m2l = arb_m2l[2];
+
+  logic              spi_mem_req;
+  logic              spi_mem_write;
+  logic [SramAw-1:0] spi_mem_addr;
+  logic [SramDw-1:0] spi_mem_wdata;
+  logic [SramDw-1:0] spi_mem_wmask;
+  logic              spi_mem_rvalid;
+  logic [SramDw-1:0] spi_mem_rdata;
+  logic [1:0]        spi_mem_rerror;
+
+  spi_device_pkg::sram_l2m_t sck_l2m;
+  spi_device_pkg::sram_m2l_t sck_m2l;
+
+  // Connection arb
+  for (genvar i = 0 ; i < ArbCnt ; i++) begin: g_arb
+    assign arb_req  [i] = arb_l2m[i].req;
+    assign arb_write[i] = arb_l2m[i].we;
+    assign arb_addr [i] = arb_l2m[i].addr;
+    assign arb_wdata[i] = arb_l2m[i].wdata;
+    assign arb_wmask[i] = spi_device_pkg::sram_strb2mask(arb_l2m[i].wstrb);
+
+    assign arb_m2l[i] = '{
+      rvalid: arb_rvalid[i],
+      rdata:  arb_rdata[i],
+      rerror: arb_error[i]
+    };
+  end
+
+  // Connection sck <-> spi_mem
+  assign spi_mem_req   = sck_l2m.req;
+  assign spi_mem_write = sck_l2m.we;
+  assign spi_mem_addr  = sck_l2m.addr;
+  assign spi_mem_wdata = sck_l2m.wdata;
+  assign spi_mem_wmask = spi_device_pkg::sram_strb2mask(sck_l2m.wstrb);
+
+  assign sck_m2l = '{
+    rvalid: spi_mem_rvalid,
+    rdata:  spi_mem_rdata,
+    rerror: spi_mem_rerror
+  };
+
+  // FIFO
+  logic cmdfifo_rvalid, cmdfifo_rready;
+  logic [7:0] cmdfifo_rdata;
+  logic addrfifo_rvalid, addrfifo_rready;
+  logic [31:0] addrfifo_rdata;
+  logic cmdfifo_notempty, addrfifo_notempty;
+  logic [$clog2(SramCmdFifoDepth+1)-1:0]  cmdfifo_depth;
+  logic [$clog2(SramAddrFifoDepth+1)-1:0] addrfifo_depth;
+  logic [$clog2(SramPayloadDepth*(SramDw/8)+1)-1:0]  payload_depth;
+
+  // Upload module signals
+  logic cfg_addr_4b_en;
+
+
+  initial begin
+    sck_clk.set_period_ps(SckPeriod);
+    sck_clk.set_active();
+
+    main_clk.set_period_ps(ClkPeriod);
+    main_clk.set_active();
+
+    sif.csb = 1'b 1;
+
+    // Driving default inactive values on the interface
+    sw_l2m.req = 1'b 0;
+    cmdfifo_rready  = 1'b 0;
+    addrfifo_rready = 1'b 0;
+
+    cfg_addr_4b_en = 1'b 0;
+
+    spi_mode = FlashMode;
+
+    sck_clk.apply_reset();
+    main_clk.apply_reset();
+
+    fork
+      begin
+        #20us
+        $display("TEST TIMED OUT!!");
+        $finish();
+      end
+      host();
+      sw();
+    join_any
+
+    $finish();
+  end
+
+  static task host();
+    // Wait
+    #200ns
+
+    // Issue CmdOnly command
+    spiflash_chiperase(sif.tb, 8'h C7);
+
+    // Issue Cmd/Addr command
+    repeat (10) @(sck_clk.cbn);
+    spiflash_blockerase(sif.tb, 8'h 52, 32'h 00DE_ADBE, 1'b 0); // 32kB
+
+    // Issue Cmd/Addr/Payload command
+    repeat (30) @(sck_clk.cbn);
+    spiflash_program(
+      sif.tb,
+      8'h 02,         // opcode
+      32'h 00AD_BEEF, // addr
+      1'b 0,          // addr_4b_en
+
+      // payload
+      '{
+        8'h A5,
+        8'h 5A,
+        8'h AB,
+        8'h CD,
+        8'h EF,
+        8'h DE,
+        8'h AD,
+        8'h BE,
+        8'h EF
+      }
+    );
+
+    // Cmd & Payload : Write Status
+
+    #300ns
+    @(sck_clk.cbn);
+  endtask : host
+
+  static task sw();
+    automatic logic [ 7:0] cmd;
+    automatic logic [31:0] addr;
+    automatic logic [ 7:0] payload [$];
+
+    forever begin
+      // Check if fifo has entries inside
+      // cmdfifo_notempty asserted when FIFO write happens.
+      // it takes one more cycle to have valid read data
+      // due to the latency that FIFO reads from DPSRAM.
+      // Check cmdfifo_rvalid rather than cmdfifo_notempty
+      @(posedge clk iff (cmdfifo_rvalid == 1'b 1));
+      cmd = cmdfifo_rdata;
+      $display("Cmd %x is uploaded", cmd);
+      cmdfifo_rready = 1'b 1;
+      @(posedge clk);
+      cmdfifo_rready = 1'b 0;
+
+      // Decode command to check if addr/payload fields
+      case (cmd) inside
+        8'h C7, 8'h 06: begin
+          // Do nothing to HW
+          // Maybe checking busy set?
+        end
+
+        8'h 52, 8'h D8: begin
+          // Wait until Addr not empty
+          @(posedge clk iff (addrfifo_notempty == 1'b 1));
+          addr = addrfifo_rdata;
+          $display("Addr %x is received", addr);
+          addrfifo_rready = 1'b 1;
+          @(posedge clk);
+          addrfifo_rready = 1'b 0;
+        end
+
+        8'h 01, 8'h 11: begin
+          // Wait until CSb deasserted
+          @(posedge clk iff (sif.csb == 1'b 1));
+
+          // Due to CDC, payload depth updated later
+          repeat (5) @(posedge clk);
+
+          // fetch payload buffer
+          read_sram(sw_l2m, sw_m2l,
+            SramPayloadIdx, payload_depth, payload);
+        end
+
+        8'h 02, 8'h 42: begin
+          // Wait until Addr not empty
+          @(posedge clk iff (addrfifo_notempty == 1'b 1));
+          addr = addrfifo_rdata;
+          $display("Addr %x is received", addr);
+          addrfifo_rready = 1'b 1;
+          @(posedge clk);
+          addrfifo_rready = 1'b 0;
+
+          // Payload
+          // Wait until CSb deasserted
+          @(posedge clk iff (sif.csb == 1'b 1));
+
+          // Due to CDC, payload depth updated later
+          repeat (5) @(posedge clk);
+
+          // fetch payload buffer
+          read_sram(sw_l2m, sw_m2l,
+            SramPayloadIdx, payload_depth, payload);
+        end
+
+      endcase
+    end
+
+
+    forever @(posedge clk); // Wait host transaction done
+  endtask : sw
+
+  // CSb pulse
+  logic csb_sckin_sync_d, csb_sckin_sync_q, csb_asserted_pulse_sckin;
+  prim_flop_2sync #(
+    .Width      (1),
+    .ResetValue (1'b 1)
+  ) u_csb_sckin_sync (
+    .clk_i (gated_sck),
+    .rst_ni(rst_spi_n), //Use CSb as a reset
+    .d_i (1'b 0),
+    .q_o (csb_sckin_sync_d)
+  );
+  always_ff @(posedge gated_sck or negedge rst_spi_n) begin
+    if (!rst_spi_n) csb_sckin_sync_q <= 1'b 1;
+    else            csb_sckin_sync_q <= csb_sckin_sync_d;
+  end
+
+  assign csb_asserted_pulse_sckin = csb_sckin_sync_q && !csb_sckin_sync_d;
+
+  // CSb deassertion pulse generator
+  logic csb_sync, csb_sync_q, csb_deasserted_busclk;
+  prim_flop_2sync #(
+    .Width      (1),
+    .ResetValue (1'b 1)
+  ) u_csb_sync (
+    .clk_i   (clk),
+    .rst_ni  (rst_n),
+    .d_i     (sif.csb),
+    .q_o     (csb_sync)
+  );
+  always_ff @(posedge clk or negedge rst_n) begin
+    if (!rst_n) csb_sync_q <= 1'b 1;
+    else        csb_sync_q <= csb_sync;
+  end
+
+  assign csb_deasserted_busclk = !csb_sync_q && csb_sync;
+
+  logic p2s_valid, p2s_sent;
+  logic [7:0] p2s_data;
+
+  io_mode_e dut_iomode, s2p_iomode;
+
+  spid_upload #(
+    .CmdFifoBaseAddr  (SramCmdFifoIdx),
+    .CmdFifoDepth     (SramCmdFifoDepth),
+    .AddrFifoBaseAddr (SramAddrFifoIdx),
+    .AddrFifoDepth    (SramAddrFifoDepth),
+    .PayloadBaseAddr  (SramPayloadIdx),
+    .PayloadDepth     (SramPayloadDepth),
+
+    .SpiByte ($bits(spi_byte_t))
+  ) u_upload (
+    .clk_i  (gated_sck),
+    .rst_ni (rst_spi_n),
+
+    .sys_clk_i  (clk),
+    .sys_rst_ni (rst_n),
+
+    .sys_csb_deasserted_pulse_i (csb_deasserted_busclk),
+
+    .sel_dp_i (dut_sel_dp),
+
+    .sck_sram_o (sck_l2m),
+    .sck_sram_i (sck_m2l),
+
+    .sys_cmdfifo_sram_o (arb_l2m[0]),
+    .sys_cmdfifo_sram_i (arb_m2l[0]),
+
+    .sys_addrfifo_sram_o (arb_l2m[1]),
+    .sys_addrfifo_sram_i (arb_m2l[1]),
+
+    // SYS clock FIFO interface
+    .sys_cmdfifo_rvalid_o (cmdfifo_rvalid),
+    .sys_cmdfifo_rready_i (cmdfifo_rready),
+    .sys_cmdfifo_rdata_o  (cmdfifo_rdata),
+
+    .sys_addrfifo_rvalid_o (addrfifo_rvalid),
+    .sys_addrfifo_rready_i (addrfifo_rready),
+    .sys_addrfifo_rdata_o  (addrfifo_rdata),
+
+    // Interface: SPI to Parallel
+    .s2p_valid_i  (s2p_valid),
+    .s2p_byte_i   (s2p_data),
+    .s2p_bitcnt_i (s2p_bitcnt),
+
+    // Interface: Parallel to SPI
+    .p2s_valid_o (p2s_valid),
+    .p2s_data_o  (p2s_data ),
+    .p2s_sent_i  (p2s_sent ),
+
+    .spi_mode_i (spi_mode),
+
+    .cfg_addr_4b_en_i (cfg_addr_4b_en),
+
+    .cmd_info_i     (cmd_info),
+    .cmd_info_idx_i (cmd_info_idx),
+
+    .io_mode_o (dut_iomode),
+
+    .set_busy_o (sck_busy_set),
+
+    .sys_cmdfifo_notempty_o  (cmdfifo_notempty),
+    .sys_cmdfifo_full_o      (), // not used
+    .sys_addrfifo_notempty_o (addrfifo_notempty),
+    .sys_addrfifo_full_o     (), // not used
+
+    .sys_cmdfifo_depth_o  (cmdfifo_depth),
+    .sys_addrfifo_depth_o (addrfifo_depth),
+    .sys_payload_depth_o  (payload_depth)
+  );
+
+  spi_cmdparse cmdparse (
+    .clk_i  (gated_sck),
+    .rst_ni (rst_spi_n),
+
+    .data_valid_i (s2p_valid),
+    .data_i       (s2p_data ),
+
+    .spi_mode_i   (spi_mode),
+
+    .cmd_info_i   (spid_common::CmdInfo),
+
+    .io_mode_o    (s2p_iomode),
+
+    .sel_dp_o       (dut_sel_dp),
+    .cmd_info_o     (cmd_info),
+    .cmd_info_idx_o (cmd_info_idx),
+
+    .cmd_config_req_o (),
+    .cmd_config_idx_o ()
+  );
+
+  spi_s2p s2p (
+    .clk_i  (gated_sck),
+    .rst_ni (rst_spi_n),
+
+    .s_i    (sif.sd_in),
+
+    .data_valid_o (s2p_valid),
+    .data_o       (s2p_data),
+    .bitcnt_o     (s2p_bitcnt),
+
+    .order_i      (1'b 0),
+    .io_mode_i    (s2p_iomode)
+  );
+
+  spi_p2s p2s (
+    .clk_i  (gated_sck_inverted),
+    .rst_ni (rst_spi_n),
+
+    .data_valid_i (p2s_valid),
+    .data_i       (p2s_data),
+    .data_sent_o  (p2s_sent),
+
+    .csb_i        (sif.csb),
+    .s_en_o       (dut_sd_en),
+    .s_o          (dut_sd),
+
+    .cpha_i       (1'b 0),
+
+    .order_i      (1'b 0),
+
+    .io_mode_i    (dut_iomode)
+  );
+
+  // Memory (DPSRAM)
+  prim_ram_2p_async_adv #(
+    .Depth (1024),      // 4kB
+    .Width (SramDw),    // 32
+    .DataBitsPerMask (8),
+
+    .EnableECC           (0),
+    .EnableParity        (1),
+    .EnableInputPipeline (0),
+    .EnableOutputPipeline(0)
+  ) u_memory_2p (
+    .clk_a_i    (clk),
+    .rst_a_ni   (rst_n),
+
+    .clk_b_i    (gated_sck),
+    .rst_b_ni   (rst_n),
+
+    .a_req_i    (bus_mem_req),
+    .a_write_i  (bus_mem_write),
+    .a_addr_i   (bus_mem_addr),
+    .a_wdata_i  (bus_mem_wdata),
+    .a_wmask_i  (bus_mem_wmask),
+    .a_rvalid_o (bus_mem_rvalid),
+    .a_rdata_o  (bus_mem_rdata),
+    .a_rerror_o (bus_mem_rerror),
+
+    .b_req_i    (spi_mem_req),
+    .b_write_i  (spi_mem_write),
+    .b_addr_i   (spi_mem_addr),
+    .b_wdata_i  (spi_mem_wdata),
+    .b_wmask_i  (spi_mem_wmask),
+    .b_rvalid_o (spi_mem_rvalid),
+    .b_rdata_o  (spi_mem_rdata),
+    .b_rerror_o (spi_mem_rerror),
+
+    .cfg_i      ('0)
+  );
+
+  // Arbiter for bus clock
+  // 0: cmdaddr_sram
+  // 1: payload_sram
+  // 2: sw_sram
+  // condition, should be one-hot0 for the requests
+  prim_sram_arbiter #(
+    .N      (ArbCnt),
+    .SramDw (SramDw),
+    .SramAw (SramAw)
+  ) u_bus_arbiter (
+    .clk_i  (clk  ),
+    .rst_ni (rst_n),
+
+    .req_i       (arb_req  ),
+    .req_addr_i  (arb_addr ),
+    .req_write_i (arb_write),
+    .req_wdata_i (arb_wdata),
+    .req_wmask_i (arb_wmask),
+    .gnt_o       (arb_gnt  ),
+
+    .rsp_rvalid_o (arb_rvalid),
+    .rsp_rdata_o  (arb_rdata ),
+    .rsp_error_o  (arb_error ),
+
+    .sram_req_o    (bus_mem_req   ),
+    .sram_addr_o   (bus_mem_addr  ),
+    .sram_write_o  (bus_mem_write ),
+    .sram_wdata_o  (bus_mem_wdata ),
+    .sram_wmask_o  (bus_mem_wmask ),
+    .sram_rvalid_i (bus_mem_rvalid),
+    .sram_rdata_i  (bus_mem_rdata ),
+    .sram_rerror_i (bus_mem_rerror)
+  );
+
+  static task read_sram(
+    ref       sram_l2m_t l2m,
+    const ref sram_m2l_t m2l,
+    input logic [SramAw-1:0] addr,
+    input int unsigned       size,    // in byte
+    output logic [7:0]       data [$]
+  );
+    automatic logic [7:0] result [$];
+
+    automatic int unsigned loop = (size+(SramDw/8)-1)/(SramDw/8);
+
+    // Fetch
+    for (int i = 0 ; i < loop; i++) begin
+      l2m.addr  = addr + i;
+      l2m.we    = 1'b 0;
+      l2m.wstrb = '1;
+      l2m.wdata = '0;
+      l2m.req   = 1'b 1;
+      fork
+        begin
+          @(posedge clk);
+          l2m.req   = 1'b 0;
+        end
+        begin
+          @(posedge clk iff (m2l.rvalid == 1'b1));
+          for (int j = 0 ; j < SramDw/8 ; j++) begin
+            result.push_back(m2l.rdata[8*j+:8]);
+          end
+          $display("Read Sram @ %x: %x", (addr+i), m2l.rdata);
+
+          // Wait m2l.rvalid lowered
+          @(posedge clk iff (m2l.rvalid == 1'b 0));
+        end
+      join
+    end
+
+    data = result;
+  endtask : read_sram
+
+endmodule : spid_upload_tb