//
// Copyright 2021 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: spi_slave
//
// Description:
//
//   SPI slave for configuration CPOL = CPHA = 0.
//   Transfers 8 bit = 1 byte MSB first. Parallel data has to be
//   provided and consumed immediately when flags are asserted.
//
//   Limitation: clk frequency <= 2*sclk frequency
//
//   Data request from sclk domain is triggered towards the clk domain ahead of
//   time. This is due to the clock domain crossing using the synchronizer and
//   processing pipeline stages.
//
//   The worst case propagation delay of the used synchronizer is:
//
//     4 'clk' clock cycles:
//     1 clock cycle of signal propagation to synchronizer
//        (data_request_sclk assertion)
//     1 clock cycle to capture data with instability in first stage
//     1 clock cycle to stabilize first stage
//     1 clock cycle to capture data in second stage
//        (data_request_clk available in 'clk' domain)
//
//   Once synchronized in 'clk' domain, there is one additional clock cycle to
//   derive data_out_valid and data_in_required. To ensure that transmit data
//   is registered a 'clk' cycle ahead of the actual transmission we need 2
//   more 'clk' clock cycles. This ensures that transmit_word has changed and
//   is stable for at least one 'clk' cycle before 'sclk' asserts again. Any
//   additional time required externally to respond to the control port
//   requests should be considered in this crossing as well. This is a total of
//   7 clock cycles (+ctrlport response margin) @ clk domain. The minimum
//   required time in sclk domain to issue the request is calculated based on
//   the clock frequencies.
//
// Parameters:
//
//   CLK_FREQUENCY : Frequency of "clk"
//   SPI_FREQUENCY : Frequency of "sclk"
//

`default_nettype none


module spi_slave #(
  parameter CLK_FREQUENCY = 50000000,
  parameter SPI_FREQUENCY = 10000000
) (
  //---------------------------------------------------------------
  // SPI Interface
  //---------------------------------------------------------------

  input  wire sclk,
  input  wire cs_n,
  input  wire mosi,
  output wire miso,

  //---------------------------------------------------------------
  // Parallel Interface
  //---------------------------------------------------------------

  input  wire clk,
  input  wire rst,

  output reg  data_in_required,
  input  wire data_in_valid,
  input  wire [7:0] data_in,

  output reg  data_out_valid,
  output reg  [7:0] data_out,

  output wire active
);

  wire [0:0] data_request_clk;
  wire [0:0] reception_complete_clk;

  //---------------------------------------------------------------
  // SPI Receiver @ sclk
  //---------------------------------------------------------------

  reg [7:0] receiver_reg;
  reg [2:0] current_bit_index;
  reg       reception_complete_sclk = 1'b0;
  reg [7:0] received_word;

  always @(posedge sclk or posedge cs_n) begin
    // Reset logic on positive cs_n edge = slave idle
    if (cs_n) begin
      receiver_reg <= 8'b0;
    end
    // Rising edge of sclk
    else begin
      // Capture bits into shift register MSBs first
      receiver_reg <= {receiver_reg[6:0], mosi};
    end
  end

  // Reset with cs_n might occur too early during clk sync.
  // Reset half way through the reception.
  always @(posedge sclk) begin
    case (current_bit_index)
      3: begin
        // Reset after half transaction
        reception_complete_sclk <= 1'b0;
      end
      7: begin
        // Save received word
        received_word <= {receiver_reg[6:0], mosi};
        // Set after complete word was received
        reception_complete_sclk <= 1'b1;
      end
    endcase
  end

  //---------------------------------------------------------------
  // Handover of data sclk -> clk
  //---------------------------------------------------------------

  synchronizer #(
    .WIDTH            (1),
    .STAGES           (2),
    .INITIAL_VAL      (1'b0),
    .FALSE_PATH_TO_IN (1)
  ) data_sync_inst (
    .clk (clk),
    .rst (1'b0),
    .in  (reception_complete_sclk),
    .out (reception_complete_clk)
  );

  //---------------------------------------------------------------
  // Parallel interface data output @ clk
  //---------------------------------------------------------------

  reg reception_complete_clk_delayed = 1'b0;

  // Propagate toggling signal without reset to ensure stability on reset
  always @(posedge clk) begin
    // Capture last state of reception
    reception_complete_clk_delayed <= reception_complete_clk;
  end

  // Derive data and control signal
  always @(posedge clk) begin
    if (rst) begin
      data_out_valid <= 1'b0;
      data_out <= 8'b0;
    end
    else begin
      // Default assignment
      data_out_valid <= 1'b0;

      // Provide data to output on rising_edge
      if (reception_complete_clk & ~reception_complete_clk_delayed) begin
        // Data can simply be captured as the reception complete signal
        // indicates stable values in received_word.
        data_out <= received_word;
        data_out_valid <= 1'b1;
      end
    end
  end

  //---------------------------------------------------------------
  // SPI Transmitter @ sclk
  //---------------------------------------------------------------

  // Data request calculation:
  // SCLK_CYCLES_DURING_DATA_REQ = 8 clk period / sclk period
  // Clock periods are expressed by reciprocal of frequencies.
  // Term "+CLK_FREQUENCY-1" is used to round up the result in integer logic.
  localparam SCLK_CYCLES_DURING_DATA_REQ  = (8*SPI_FREQUENCY + CLK_FREQUENCY-1)/CLK_FREQUENCY;
  // subtract from 8 bits per transfer to get target index
  localparam DATA_REQ_BIT_INDEX = 8 - SCLK_CYCLES_DURING_DATA_REQ;

  reg [7:0] transmit_bits;
  reg [7:0] transmit_word;
  reg       data_request_sclk = 1'b0;

  always @(negedge sclk or posedge cs_n) begin
    // Reset logic on positive cs_n edge = slave idle
    if (cs_n) begin
      current_bit_index <= 3'b0;
      data_request_sclk <= 1'b0;
      transmit_bits <= 8'b0;
    end
    // Falling edge of sclk
    else begin
      // Fill or move shift register for byte transmissions
      if (current_bit_index == 7) begin
        transmit_bits <= transmit_word;
      end else begin
        transmit_bits <= {transmit_bits[6:0], 1'b0};
      end

      // Update bit index
      current_bit_index <= current_bit_index + 1'b1;

      // Trigger request for new word at start of calculated index
      if (current_bit_index == DATA_REQ_BIT_INDEX-1) begin
        data_request_sclk <= 1'b1;
      // Reset after half the reception in case cs_n is not changed in between
      // two transactions.
      end else if (current_bit_index == (DATA_REQ_BIT_INDEX+4-1)%8) begin
        data_request_sclk <= 1'b0;
      end
    end
  end

  // Drive miso output with data when cs_n low
  assign miso = cs_n ? 1'bz : transmit_bits[7];

  //---------------------------------------------------------------
  // Handover of Data Request sclk -> clk
  //---------------------------------------------------------------

  synchronizer #(
    .WIDTH            (1),
    .STAGES           (2),
    .INITIAL_VAL      (1'b0),
    .FALSE_PATH_TO_IN (1)
  ) request_sync_inst (
    .clk (clk),
    .rst (rst),
    .in  (data_request_sclk),
    .out (data_request_clk)
  );

  //---------------------------------------------------------------
  // Parallel Interface Data Input Control
  //---------------------------------------------------------------

  reg data_request_clk_delayed;

  always @(posedge clk) begin
    if (rst) begin
      data_request_clk_delayed <= 1'b0;
      data_in_required <= 1'b0;
      transmit_word <= 8'b0;
    end
    else begin
      // Default assignment
      data_in_required <= 1'b0;

      // Capture last state of data request
      data_request_clk_delayed <= data_request_clk;

      // Request data from input
      if (~data_request_clk_delayed & data_request_clk) begin
        data_in_required <= 1'b1;
      end

      // Capture new data if valid data available, 0 otherwise.
      if (data_in_required) begin
        if (data_in_valid) begin
          transmit_word <= data_in;
        end else begin
          transmit_word <= 8'b0;
        end
      end
    end
  end

  //---------------------------------------------------------------
  // Chip Select
  //---------------------------------------------------------------
  // Driven as active signal in parallel clock domain

  wire cs_n_clk;
  assign active = ~cs_n_clk;
  synchronizer #(
    .WIDTH            (1),
    .STAGES           (2),
    .INITIAL_VAL      (1'b1),
    .FALSE_PATH_TO_IN (1)
  ) active_sync_inst (
    .clk (clk),
    .rst (rst),
    .in  (cs_n),
    .out (cs_n_clk)
  );

endmodule


`default_nettype wire
