[spi_device] Add serial to parallel logic

This commit changes the serial-to-parllel logic in SPI_DEVICE to support
Dual/ Quad IO modes.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/rtl/spi_device_pkg.sv b/hw/ip/spi_device/rtl/spi_device_pkg.sv
index cf77709..b15626c 100644
--- a/hw/ip/spi_device/rtl/spi_device_pkg.sv
+++ b/hw/ip/spi_device/rtl/spi_device_pkg.sv
@@ -11,10 +11,27 @@
   typedef enum logic [1:0] {
     FwMode      = 'h0,
     EepromRam   = 'h1,
-    EepromFlash = 'h2,
-    PassThrough = 'h3
+    PassThrough = 'h2
   } spi_mode_e;
 
+  // SPI IO mode
+  typedef enum logic [1:0] {
+    SingleIO = 2'h 0,
+    DualIO   = 2'h 1,
+    QuadIO   = 2'h 2
+  } io_mode_e;
+
+  // SPI Line Mode (Mode0 <-> Mode3)
+  // This HWIP does not support Mode1 and Mode2
+  typedef enum logic {
+    // Mode0: CPOL=0, CPHA=0
+    //  Data sampled on rising edge and shifted on falling edge
+    LineMode0 = 1'b 0,
+    // Mode3: CPOL=1, CPHA=1
+    //  Data sampled on falling edge and shifted on rising edge
+    LineMode3 = 1'b 1
+  } line_mode_e;
+
   // SPI Read mode. QUAD uses additional two pins to read
   // Bit 0: Single, Bit 1: Dual Bit 2: Quad
   typedef logic [2:0] spi_rdmode_t;
diff --git a/hw/ip/spi_device/rtl/spi_s2p.sv b/hw/ip/spi_device/rtl/spi_s2p.sv
new file mode 100644
index 0000000..8b3693e
--- /dev/null
+++ b/hw/ip/spi_device/rtl/spi_s2p.sv
@@ -0,0 +1,126 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// SPI Serial-to-Parallel interface
+
+module spi_s2p
+  import spi_device_pkg::*;
+(
+  input clk_i,
+  input rst_ni, // inverted CSb input
+
+  // SPI data
+  input [3:0] s_i,
+
+  // to following logic
+  output logic        data_valid_o,
+  output spi_byte_t   data_o,
+  output logic [11:0] bitcnt_o, // up to 256B payload
+
+  // Configuration
+  input                             order_i,
+  input spi_device_pkg::io_mode_e   io_mode_i
+);
+
+  /////////////////
+  // Definitions //
+  /////////////////
+  // Maximum Length of a transaction is:
+  // 8 bit opcode + 24 or 32 bit address +
+  // max 8 bit dummy cycle + 256B payload
+  localparam int unsigned Length   = 8 + 32 + 8 + 2048;
+  localparam int unsigned BitCntW  = $clog2(Length+1);
+  localparam int unsigned Bits     = $bits(spi_byte_t);
+  localparam int unsigned BitWidth = $clog2(Bits);
+
+  typedef logic [BitWidth-1:0] count_t;
+  typedef logic [$clog2(Length+1)-1:0] bitcount_t;
+
+  count_t cnt;
+  bitcount_t bitcnt;
+
+  spi_byte_t data_d, data_q;
+
+  always_comb begin
+    unique case (io_mode_i)
+      SingleIO: begin
+        data_d = (order_i) ? {s_i[0], data_q[7:1]} : {data_q[6:0], s_i[0]};
+      end
+
+      DualIO: begin
+        data_d = (order_i) ? {s_i[1:0], data_q[7:2]} : {data_q[5:0], s_i[1:0]};
+      end
+
+      QuadIO: begin
+        data_d = (order_i) ? {s_i[3:0], data_q[7:4]} : {data_q[3:0], s_i[3:0]};
+      end
+
+      default: begin
+        data_d = data_q;
+      end
+    endcase
+  end
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) begin
+      data_q <= '0;
+    end else begin
+      data_q <= data_d;
+    end
+  end
+
+  // send un-latched data
+  assign data_o = data_d;
+
+  // total bit count
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) begin
+      bitcnt <= '0;
+    end else begin
+      unique case (io_mode_i)
+        SingleIO: bitcnt <= bitcnt + bitcount_t'('h1);
+        DualIO:   bitcnt <= bitcnt + bitcount_t'('h2);
+        QuadIO:   bitcnt <= bitcnt + bitcount_t'('h4);
+        default:  bitcnt <= bitcnt;
+      endcase
+    end
+  end
+  assign bitcnt_o = bitcnt;
+
+  // Bitcount in a byte
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) begin
+      cnt <= count_t'(Bits-1);
+    end else if (cnt == '0) begin
+      cnt <= count_t'(Bits-1);
+    end else begin
+      unique case (io_mode_i)
+        SingleIO: cnt <= cnt - count_t'('h1);
+        DualIO:   cnt <= cnt - count_t'('h2);
+        QuadIO:   cnt <= cnt - count_t'('h4);
+        default:  cnt <= cnt;
+      endcase
+    end
+  end
+
+  // data valid
+  always_comb begin
+    unique case (io_mode_i)
+      SingleIO: data_valid_o = (cnt == 'h0);
+      DualIO:   data_valid_o = (cnt == 'h1);
+      QuadIO:   data_valid_o = (cnt == 'h3);
+      default:  data_valid_o = 1'b 0;
+    endcase
+  end
+
+  ////////////////
+  // Assertions //
+  ////////////////
+
+  // Right after reset (CSb assert), the io_mode_i shall be Single IO
+  // to decode SPI Opcode.
+  `ASSERT(IoModeDefault_A, $rose(rst_ni) |-> io_mode_i == SingleIO, clk_i, 0)
+
+
+endmodule