//
// spi_master.sv
//
// Simple byte-oriented SPI master unit with optional multiwidth support
// (1, 2, 4, 8).
//
// The output SPI clock equals the system clock /2 unless clk_en is used
// to throttle the output clock.
//
// spi_io[0] = DI, spi_io[1] = DO in single bit mode.
//
// By default the output is latched; if not, it is only valid in
// the cycle that eack is asserted (useful for FIFOs etc.)
//

//
// XXX: CHPA option (mode 1/3)
//


`define IO_MAX max(ilog2c(width)-1, 1)

function reg [7:0] shift_in
(
 input [7:0] prev,
 input [7:0] in,
 input [1:0] xwidth,		// log2(transaction width)
 input [0:0] lsb
 );

   (* full_case *)
   case (xwidth)
     2'd0:
       shift_in = lsb ? { in[0], prev[7:1] } : { prev[6:0], in[0] };
     2'd1:
       shift_in = lsb ? { in[1:0], prev[7:2] } : { prev[5:0], in[1:0] };
     2'd2:
       shift_in = lsb ? { in[3:0], prev[7:4] } : { prev[3:0], in[3:0] };
     2'd3:
       shift_in = in;
   endcase // case (width)
endfunction // shift_in

module spi_master
#(
  parameter width = 1,		// Max width of SPI bus (1, 2, 4, or 8)
  parameter n_cs  = 1,		// Number of CS# outputs
  parameter cs_delay = 1,	// Time from CS# low to first data
  parameter latch_q = 1		// Latch output
  )
(
 input		   rst_n,	// Unit reset
 input		   clk,		// System clock
 input		   clk_en,	// SPI clock enable
 input [7:0]	   d,		// System data in
 output [7:0]	   q,		// System data out
 input		   req,		// Session request
 input		   dir,		// Session is write (for multibit)
 input [1:0]	   iowidth,	// Session width (lg2)
 output	reg	   sack,	// Session started
 output	reg	   eack,	// Session ended

 input		   idle_io,	// Signal level for I/Os at idle
 input		   cpol,	// Clock polarity (usually constant)
 input		   lsb,		// Littleendian mode (usually constant)

 input [n_cs-1:0]  cs, // Device select (active high)

 output	reg	   spi_sck, // SPI clock
 inout [`IO_MAX:0] spi_io, // SPI data
 output [n_cs-1:0] spi_cs_n	// SPI CS# lines
 );

   localparam io_max = `IO_MAX;
   localparam iowidth_max = ilog2c(width);
   localparam ctr_max = max(ilog2c(cs_delay)-1,2);

   reg				   spi_active;
   reg [((width > 2) ? 1 : 0):0]   spi_width;
   reg [ctr_max:0]		   spi_ctr;
   reg [n_cs-1:0]		   spi_cs_q;
   reg [io_max:0]		   spi_out_q;
   reg [1:0]			   spi_oe_q;

   reg				   d_dir;
   reg [7:0]			   d_out; // Output shift register
   reg [7:0]			   d_in;  // Input shift register
   reg [7:0]			   q_q;	  // Latched output data

   assign spi_cs_n = ~spi_cs_q;
   assign q = latch_q ? q_q : d_in;

   wire spi_cs_changed = |(spi_cs_q ^ cs);

   // Always 2'b10 for single-bit SPI
   wire [1:0] spi_oe = (width > 1) ? spi_oe_q : 2'b10;

   assign spi_io[0] = spi_oe[0] ? spi_out_q[0] : 1'bz;
   assign spi_io[io_max:1] = spi_oe[1] ? spi_out_q[io_max:1] : {io_max{1'bz}};

   // Make it clear to the compiler that iowidth is undefined if
   // it is too large
   wire [1:0] iowidth_in  = (iowidth > iowidth_max) ? 2'bxx : iowidth;

   // Just for convenience...
   wire [3:0] spi_width_n = 1'b1 << spi_width;
   wire [io_max:0] idle_allio = {(io_max+1){idle_io}};

   always @(negedge rst_n or posedge clk)
     if (~rst_n)
       begin
	  spi_sck     <= 1'b0;
	  spi_active  <= 1'b0;
	  spi_width   <= 4'b0001;
	  spi_ctr     <= 1'b0;
	  spi_cs_q    <= 1'b0;
	  spi_out_q   <= idle_allio;
	  spi_oe_q    <= 2'b10;
	  sack        <= 1'b0;
	  eack        <= 1'b0;
	  d_out       <= idle_allio;
	  d_in        <= 8'hxx;
	  q_q         <= 8'hxx;
       end
     else
       begin
	  // These are always single system clock pulses
	  sack <= 1'b0;
	  eack <= 1'b0;

	  if (clk_en)
	    begin
	       spi_ctr <= spi_ctr - 1'b1;
	       spi_sck <= (spi_ctr[0] & spi_active) ^ cpol;

	       if (~spi_ctr[0])
		 begin
		    d_in <= shift_in(d_in, spi_io, spi_width, lsb);
		 end
	       else
		 begin
		    spi_out_q <= idle_allio;

		    if (spi_active)
		      begin
		         if (~|spi_ctr[ctr_max:1])
		           begin
			      eack     <= 1'b1;
			      q_q        <= d_in;
			      spi_active <= 1'b0;
			   end

			 if (spi_width > iowidth_max)
			   begin
			      // Invalid width, let the compiler do whatever
			      d_out <= 8'hxx;
			      spi_out_q <= {(io_max+1){1'bx}};
			   end
			 else
			   begin
			      d_out <= shift_in(d_out, {8{idle_io}},
						spi_width, lsb);

			      // Note: 1-byte SPI uses IO1 as output (DO)
			      (* full_case *)
			      casez ( {spi_width, lsb} )
				3'b00_0: spi_out_q[1]   <= d_out[7];
				3'b00_1: spi_out_q[1]   <= d_out[0];
				3'b01_0: spi_out_q[1:0] <= d_out[7:6];
				3'b01_1: spi_out_q[1:0] <= d_out[1:0];
				3'b10_0: spi_out_q[3:0] <= d_out[7:4];
				3'b10_1: spi_out_q[3:0] <= d_out[3:0];
				3'b11_?: spi_out_q      <= d_out;
			      endcase // casez ( {spi_width, lsb} )
			   end
		      end // if (spi_active)
		    else
		      begin
			 spi_cs_q <= cs;
			 d_out <= d;

			 if (cs_delay != 0 &&
			     (spi_cs_changed | ~|spi_ctr[ctr_max:1]))
			   begin
			      if (spi_cs_changed)
				spi_ctr[ctr_max:1] <= cs_delay;
			      spi_oe_q  <= 2'b10; // As for 1-bit mode
			   end
			 else if (req &&
				  (cs_delay == 0 || ~|spi_ctr[ctr_max:1]))
			   begin
			      if (iowidth <= iowidth_max)
				begin
				   spi_width <= iowidth;
				   spi_ctr[ctr_max:1] <= 3'd8 >> iowidth;
				   spi_oe_q  <=
					       |iowidth ? {2{dir}} : 2'b10;
				   spi_active <= 1'b1;
				   sack <= 1'b1;
				end
			      else
				begin
				   // Invalid width, let the compiler
				   // do whatever it wants...
				   spi_width <= 2'bxx;
				   spi_ctr[ctr_max:1] <= {ctr_max{1'bx}};
				   spi_oe_q <= 2'bxx;
				   spi_active <= 1'bx;
				   sack <= 1'bx;
				end
			   end // if (req &&...
		      end // else: !if(spi_active)
		 end // else: !if(~spi_ctr[0])
	    end // if (clk_en)
       end // else: !if(~rst_n)
endmodule // spi_master
