/* spi.v
   KISS ("keep it simple & stupid") SPI master engine. Configured for 
   2 slaves, occupies just 38 LCs in an Intel MAX10 or 34 in a Xilinx 
   Spatan6.

   Parameter
    DIVPWR:  SPI sck is derived from input clk divided by 2^DIVPWR.
    SELECTS: Number of outgoing nSS signals, i.e. number of slaves hooked to 
             the bus.

   Interface Registers
                      Write          Read 
    Address 1: ctrl   nSS            SPIready
    Address 0: data   byte to xmit   received byte

  You may have slaves at the bus that are routed to different FPGA pins. While
  MOSI and SCK can simply be made to drive multiple pins, MISO lines have to 
  be combined. Since only the selected slave will drive MISO low, we can simply 
  'and' them togehther. 

  SW actions (or how to use it)
    Write 0xFF to ctrl to DESELECT all slaves. To select a slave, write a 0 to
    the appropriate bit and 1s to all other bits. The data register may be 
    written/read when SPIready is true. - Hint: your SW may spend enough time 
    between successive SPI actions anyhow to not have to check SPIready every
    time.

  (C) 2021 binär industrie-informatik Bernd Kohler
  
  bk 25Mar21
*/

module spi #(parameter DIVPWR = 3, parameter SELECTS = 1) (
    // Global
    input reset, 
    input clk,
    // uP
    input wr,
    input rd, 
    input addr, 
    input [7:0] din,            output [7:0] dout,
    // SPI
                                output reg [SELECTS-1:0] nss,
                                output sck,
    input [SELECTS-1:0] miso,   output mosi
  );
  
  reg [DIVPWR-1:0] clks;
  wire spiclk;
  reg prev_spiclk;
  wire rise_spiclk = !prev_spiclk  & spiclk;  // Time to sample miso line(s)
  wire trail_spiclk = prev_spiclk  & !spiclk; // Time to shift in/out
  reg state;
  reg [7:0] shift;
  reg [2:0] bits = 3'b0;
  wire [7:0] rddat = addr ? {8{!state}} : shift;
  
  reg int_miso;                     // 'And' of incoming miso lines.

  assign spiclk = clks[DIVPWR-1];
  assign sck = state ? spiclk : 1'b0;
  assign mosi = shift[7];
  assign dout = rd ? rddat : 8'hAA; // Shouldn't see the hex AA!
  
  always @(posedge clk) begin
    if (state) begin
      clks <= clks + 1;
      prev_spiclk <= spiclk;
    end
  end

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= 0;
      nss <= {SELECTS{1'b1}};
      shift <= 8'b0;
    end // if (reset)
  
    else begin // !reset
      if (wr) begin
        if (addr) begin
          nss <= din[SELECTS-1:0];
        end // if (address)
        else begin
          state <= 1;
          shift <= din;
          bits <= 3'b000;
        end // if (!address)
      end // if (wr)
      
      if (state) begin
        if (rise_spiclk)
          int_miso <= &miso;                // Sample incoming data
        if (trail_spiclk) begin
          shift <= {shift[6:0], int_miso};  // Shift out
          if (bits == 3'h7) begin
            state <= 0;
          end // if (bits == 7)
          else
            bits <= bits + 1;
        end // if trail_spiclk
      end // if (state)
    end // !reset
  end // @(posedge clk or posedge reset)

endmodule