// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2025 RVX Project Contributors

module rvx_spi #(

  parameter SPI_NUM_CHIP_SELECT = 1

  )(

  // Global signals

  input   wire                            clock           ,
  input   wire                            reset           ,

  // IO interface

  input  wire   [4:0 ]                    rw_address      ,
  output reg    [31:0]                    read_data       ,
  input  wire                             read_request    ,
  output reg                              read_response   ,
  input  wire   [7:0 ]                    write_data      ,
  input  wire   [3:0 ]                    write_strobe    ,
  input  wire                             write_request   ,
  output reg                              write_response  ,

  // SPI signals

  output reg                              sclk            ,
  output reg                              pico            ,
  input  wire                             poci            ,
  output reg    [SPI_NUM_CHIP_SELECT-1:0] cs

  );

  reg tx_start;
  reg cpol;
  reg cpha;
  reg clk_edge;
  reg sclk_internal;
  reg pico_internal;
  reg [SPI_NUM_CHIP_SELECT-1:0] cs_internal;
  reg [3:0] curr_state;
  reg [3:0] next_state;
  reg [3:0] bit_count;
  reg [7:0] tx_reg;
  reg [7:0] rx_reg;
  reg [7:0] chip_select;
  reg [7:0] cycle_counter;
  reg [7:0] clock_div;

  // SPI State Machine
  localparam SPI_READY        = 4'b0001;
  localparam SPI_IDLE         = 4'b0010;
  localparam SPI_CPOL         = 4'b0100;
  localparam SPI_CPOL_N       = 4'b1000;

  // Register addresses
  localparam REG_CPOL           = 5'h00;
  localparam REG_CPHA           = 5'h04;
  localparam REG_CHIP_SELECT    = 5'h08;
  localparam REG_CLOCK_CONF     = 5'h0c;
  localparam REG_WDATA          = 5'h10;
  localparam REG_RDATA          = 5'h14;
  localparam REG_BUSY           = 5'h18;

  wire busy_bit = curr_state == SPI_CPOL || curr_state == SPI_CPOL_N;
  wire valid_write_request = write_request == 1'b1 && &write_strobe == 1'b1;

  integer i;

  always @(posedge clock) begin
    if (reset) begin
      read_response <= 1'b0;
      write_response <= 1'b0;
    end
    else begin
      read_response <= read_request;
      write_response <= write_request;
    end
  end

  always @(posedge clock) begin
    if (reset)
      read_data <= 32'hdeadbeef;
    else if (read_request == 1'b1) begin
      case (rw_address)
        REG_CPOL:         read_data <= {31'b0, cpol};
        REG_CPHA:         read_data <= {31'b0, cpha};
        REG_CHIP_SELECT:  read_data <= {24'b0, chip_select};
        REG_CLOCK_CONF:   read_data <= {24'b0, clock_div};
        REG_RDATA:        read_data <= {24'b0, rx_reg};
        REG_BUSY:         read_data <= {31'b0, busy_bit};
        default:          read_data <= 32'hdeadbeef;
      endcase
    end
    else
      read_data <= 32'hdeadbeef;
  end

  always @(posedge clock) begin
    if (reset)
      cpol <= 1'b0;
    else if (rw_address == REG_CPOL && valid_write_request == 1'b1)
      cpol <= write_data[0];
    else
      cpol <= cpol;
  end

  always @(posedge clock) begin
    if (reset)
      cpha <= 1'b0;
    else if (rw_address == REG_CPHA && valid_write_request == 1'b1)
      cpha <= write_data[0];
    else
      cpha <= cpha;
  end

  always @(posedge clock) begin
    if (reset)
      chip_select <= 8'hff;
    else if (rw_address == REG_CHIP_SELECT && valid_write_request == 1'b1)
      chip_select <= write_data[7:0];
    else
      chip_select <= chip_select;
  end

  always @(posedge clock) begin
    if (reset)
      clock_div <= 8'h00;
    else if (rw_address == REG_CLOCK_CONF && valid_write_request == 1'b1)
      clock_div <= write_data[7:0];
    else
      clock_div <= clock_div;
  end

  always @(posedge clock) begin
    if (reset) begin
      tx_reg <= 8'h00;
      tx_start <= 1'b0;
    end
    else if (rw_address == REG_WDATA && valid_write_request == 1'b1) begin
      tx_reg <= (curr_state == SPI_READY || curr_state == SPI_IDLE) ? write_data[7:0] : tx_reg;
      tx_start <= (curr_state == SPI_READY || curr_state == SPI_IDLE) ? 1'b1 : tx_start;
    end
    else begin
      tx_reg <= tx_reg;
      tx_start <= (curr_state == SPI_CPOL || curr_state == SPI_CPOL_N) ? 1'b0 : tx_start;
    end
  end

  always @(posedge clock) begin
    if (reset | chip_select == 8'hff)
      curr_state <= SPI_READY;
    else
      curr_state <= next_state;
  end

  always @(posedge clock) begin
    if (reset || curr_state == SPI_READY || curr_state == SPI_IDLE)
      cycle_counter <= 0;
    else if (curr_state == SPI_CPOL && next_state == SPI_CPOL_N)
      cycle_counter <= 0;
    else if (curr_state == SPI_CPOL_N && next_state == SPI_CPOL)
      cycle_counter <= 0;
    else
      cycle_counter <= cycle_counter + 1;
  end

  always @(posedge clock) begin
    if (reset || curr_state == SPI_READY || curr_state == SPI_IDLE)
      bit_count <= 7;
    else if (cpha == 1'b0 && curr_state == SPI_CPOL_N && next_state == SPI_CPOL)
      bit_count <= bit_count - 1;
    else if (cpha == 1'b1 && curr_state == SPI_CPOL && next_state == SPI_CPOL_N)
      bit_count <= bit_count - 1;
    else
      bit_count <= bit_count;
  end

  always @* begin
    for (i = 0; i < SPI_NUM_CHIP_SELECT; i=i+1) begin
      cs_internal[i] = ({24'd0, chip_select} == i) ? 1'b0 : 1'b1;
    end
    case (curr_state)
      SPI_READY: begin
        sclk_internal = cpol;
        pico_internal = tx_reg[7];
        next_state = tx_start == 1'b1 ? (cpha == 1'b1 ? SPI_CPOL_N : SPI_CPOL) : curr_state;
      end
      SPI_CPOL: begin
        sclk_internal = cpol;
        pico_internal = tx_reg[bit_count[2:0]];
        next_state = cycle_counter < clock_div ? curr_state : (bit_count == 0 && cpha == 1'b1 ? SPI_IDLE : SPI_CPOL_N);
      end
      SPI_CPOL_N: begin
        sclk_internal = !cpol;
        pico_internal = tx_reg[bit_count[2:0]];
        next_state = cycle_counter < clock_div ? curr_state : (bit_count == 0 && cpha == 1'b0 ? SPI_IDLE : SPI_CPOL);
      end
      SPI_IDLE: begin
        sclk_internal = cpol;
        pico_internal = tx_reg[0];
        next_state = chip_select == 8'hff ? SPI_READY : (tx_start == 1'b1 ? (cpha == 1'b1 ? SPI_CPOL_N : SPI_CPOL) : curr_state);
      end
      default: begin
        sclk_internal = cpol;
        pico_internal = tx_reg[7];
        next_state = tx_start == 1'b1 ? SPI_CPOL : curr_state;
      end
    endcase
  end

  always @(posedge clock) begin
    if (reset) begin
      sclk <= 1'b0;
      pico <= 1'b0;
      cs <= {SPI_NUM_CHIP_SELECT{1'b1}};
    end
    else begin
      sclk <= sclk_internal;
      pico <= pico_internal;
      cs <= cs_internal;
    end
  end

  always @(posedge clock) begin
    clk_edge <= cpol ^ cpha ? !sclk_internal : sclk_internal;
  end

  always @(posedge clk_edge) begin
    rx_reg[7:0] <= {rx_reg[6:0], poci};
  end

endmodule
