|  | @@ -0,0 +1,226 @@
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// 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.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// All unused spi_io are driven to 1.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// XXX: add clock polarity, MSB/LSB options
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +module spi_master
 | 
	
		
			
				|  |  | +#(
 | 
	
		
			
				|  |  | +  parameter width = 1,		// Width of SPI data
 | 
	
		
			
				|  |  | +  parameter n_cs  = 1,		// Number of CS# outputs
 | 
	
		
			
				|  |  | +  parameter cs_delay = 1,
 | 
	
		
			
				|  |  | +  parameter io_max  = max(ilog2c(width)-1, 1)
 | 
	
		
			
				|  |  | +  )
 | 
	
		
			
				|  |  | +(
 | 
	
		
			
				|  |  | + 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				   sack, // Session started
 | 
	
		
			
				|  |  | + output				   eack, // Session ended
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | + input [n_cs-1:0]		   cs, // Device select (active high)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | + output				   spi_sck, // SPI clock
 | 
	
		
			
				|  |  | + inout [min(ilog2c(width)-1, 1):0] spi_io,  // SPI data
 | 
	
		
			
				|  |  | + output [n_cs-1:0]		   spi_cs_n	// SPI CS# lines
 | 
	
		
			
				|  |  | + );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   localparam ctr_max = max(ilog2c(cs_delay)-1,2);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   reg				   spi_clk;
 | 
	
		
			
				|  |  | +   reg				   spi_sck_q;
 | 
	
		
			
				|  |  | +   reg				   spi_active;
 | 
	
		
			
				|  |  | +   reg [ilog2c(width)-1:0]	   spi_width;
 | 
	
		
			
				|  |  | +   reg [ctr_max:0]		   spi_ctr;
 | 
	
		
			
				|  |  | +   reg [n_cs-1:0]		   spi_cs_q;
 | 
	
		
			
				|  |  | +   reg [7:0]			   spi_out_q;
 | 
	
		
			
				|  |  | +   reg [1:0]			   spi_oe_q;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   reg				   d_dir;
 | 
	
		
			
				|  |  | +   reg [7:0]			   d_out;
 | 
	
		
			
				|  |  | +   reg [7:0]			   d_in;
 | 
	
		
			
				|  |  | +   reg [7:0]			   q_q;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   reg				   sack_q;
 | 
	
		
			
				|  |  | +   reg				   eack_q;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   assign spi_cs_n = ~spi_cs_q;
 | 
	
		
			
				|  |  | +   assign spi_sck  = spi_sck_q;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   wire spi_cs_changed = |(spi_cs_q ^ cs);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   assign spi_io[0] = spi_oe_q[0] ? spi_out_q[0] : 1'bz;
 | 
	
		
			
				|  |  | +   assign spi_io[io_max:1] = spi_oe_q[1] ? spi_out_q[io_max:1] : {io_max{1'bz}};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +   always @(negedge rst_n or posedge clk)
 | 
	
		
			
				|  |  | +     if (~rst_n)
 | 
	
		
			
				|  |  | +       begin
 | 
	
		
			
				|  |  | +	  spi_clk     <= 1'b0;
 | 
	
		
			
				|  |  | +	  spi_active  <= 1'b0;
 | 
	
		
			
				|  |  | +	  spi_width   <= 4'b0001;
 | 
	
		
			
				|  |  | +	  spi_ctr     <= 1'b0;
 | 
	
		
			
				|  |  | +	  spi_cs_q    <= 1'b0;
 | 
	
		
			
				|  |  | +	  spi_out_q   <= 8'hFF;
 | 
	
		
			
				|  |  | +	  spi_oe_q    <= 2'b10;
 | 
	
		
			
				|  |  | +	  sack_q      <= 1'b0;
 | 
	
		
			
				|  |  | +	  eack_q      <= 1'b0;
 | 
	
		
			
				|  |  | +	  d_out       <= 8'hFF;
 | 
	
		
			
				|  |  | +	  d_in        <= 8'hxx;
 | 
	
		
			
				|  |  | +	  q_q         <= 8'hxx;
 | 
	
		
			
				|  |  | +       end
 | 
	
		
			
				|  |  | +     else
 | 
	
		
			
				|  |  | +       begin
 | 
	
		
			
				|  |  | +	  sack_q <= 1'b0;
 | 
	
		
			
				|  |  | +	  eack_q <= 1'b0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	  if (clk_en)
 | 
	
		
			
				|  |  | +	    begin
 | 
	
		
			
				|  |  | +	       spi_ctr <= spi_ctr - 1'b1;
 | 
	
		
			
				|  |  | +	       spi_clk <= spi_ctr[0] & spi_active;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	       if (~spi_ctr[0])
 | 
	
		
			
				|  |  | +		 begin
 | 
	
		
			
				|  |  | +		    case (spi_width)
 | 
	
		
			
				|  |  | +		      4'd1:
 | 
	
		
			
				|  |  | +			d_in <= { d_in[6:0], spi_io[0] };
 | 
	
		
			
				|  |  | +		      4'd2:
 | 
	
		
			
				|  |  | +			d_in <= { d_in[5:0], spi_io[1:0] };
 | 
	
		
			
				|  |  | +		      4'd4:
 | 
	
		
			
				|  |  | +		        d_in <= { d_in[3:0], spi_io[3:0] };
 | 
	
		
			
				|  |  | +		      4'd8:
 | 
	
		
			
				|  |  | +			d_in <= spi_io;
 | 
	
		
			
				|  |  | +		      default:
 | 
	
		
			
				|  |  | +			d_in <= 8'hxx;
 | 
	
		
			
				|  |  | +		    endcase // case 2'd3
 | 
	
		
			
				|  |  | +		 end // if (~spi_ctr[0])
 | 
	
		
			
				|  |  | +	       else
 | 
	
		
			
				|  |  | +		 begin
 | 
	
		
			
				|  |  | +		    if (spi_active)
 | 
	
		
			
				|  |  | +		      begin
 | 
	
		
			
				|  |  | +			 spi_out_q <= 8'hFF;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		         if (~|spi_ctr[ctr_max:1])
 | 
	
		
			
				|  |  | +		           begin
 | 
	
		
			
				|  |  | +			      eack_q     <= 1'b1;
 | 
	
		
			
				|  |  | +			      q_q        <= d_in;
 | 
	
		
			
				|  |  | +			      spi_active <= 1'b0;
 | 
	
		
			
				|  |  | +			   end
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			 case (spi_width)
 | 
	
		
			
				|  |  | +			   4'd1:
 | 
	
		
			
				|  |  | +			     begin
 | 
	
		
			
				|  |  | +				d_out <= { d_out[6:0], 1'b1 };
 | 
	
		
			
				|  |  | +				spi_out_q[1] <= d_out[7];
 | 
	
		
			
				|  |  | +			     end
 | 
	
		
			
				|  |  | +			   4'd2:
 | 
	
		
			
				|  |  | +			     begin
 | 
	
		
			
				|  |  | +				d_out <= { d_out[5:0], 2'b11 };
 | 
	
		
			
				|  |  | +				spi_out_q[1:0] <= d_out[7:6];
 | 
	
		
			
				|  |  | +			     end
 | 
	
		
			
				|  |  | +			   4'd4:
 | 
	
		
			
				|  |  | +			     begin
 | 
	
		
			
				|  |  | +				d_out <= { d_out[3:0], 4'b1111 };
 | 
	
		
			
				|  |  | +				spi_out_q[3:0] <= d_out[7:4];
 | 
	
		
			
				|  |  | +			     end
 | 
	
		
			
				|  |  | +			   4'd8:
 | 
	
		
			
				|  |  | +			     begin
 | 
	
		
			
				|  |  | +				d_out <= 8'hFF;
 | 
	
		
			
				|  |  | +				spi_out_q <= d_out;
 | 
	
		
			
				|  |  | +			     end
 | 
	
		
			
				|  |  | +			   default:
 | 
	
		
			
				|  |  | +			     begin
 | 
	
		
			
				|  |  | +				d_out <= 8'hxx;
 | 
	
		
			
				|  |  | +				spi_out_q <= 8'hxx;
 | 
	
		
			
				|  |  | +			     end
 | 
	
		
			
				|  |  | +			 endcase // case (spi_width)
 | 
	
		
			
				|  |  | +		      end // if (spi_active)
 | 
	
		
			
				|  |  | +		    else
 | 
	
		
			
				|  |  | +		      begin
 | 
	
		
			
				|  |  | +			 spi_cs_q <= cs;
 | 
	
		
			
				|  |  | +			 d_out <= d;
 | 
	
		
			
				|  |  | +			 spi_out_q <= 8'hFF;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			 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 (cs_delay == 0 || ~|spi_ctr[ctr_max:1])
 | 
	
		
			
				|  |  | +			   begin
 | 
	
		
			
				|  |  | +			      if (req)
 | 
	
		
			
				|  |  | +				begin
 | 
	
		
			
				|  |  | +				   case (iowidth)
 | 
	
		
			
				|  |  | +				     2'd0:
 | 
	
		
			
				|  |  | +				       begin
 | 
	
		
			
				|  |  | +					  spi_width   <= 4'b0001;
 | 
	
		
			
				|  |  | +					  spi_ctr[ctr_max:1] <= 3'd8;
 | 
	
		
			
				|  |  | +					  spi_oe_q <= 2'b10;
 | 
	
		
			
				|  |  | +				       end
 | 
	
		
			
				|  |  | +				     2'd1:
 | 
	
		
			
				|  |  | +				       if (width < 2)
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b000x;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 1'bx;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= 2'bxx;
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				       else
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b0010;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 3'd4;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= {2{dir}};
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				     2'd2:
 | 
	
		
			
				|  |  | +				       if (width < 4)
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b00xx;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 1'bx;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= 2'bxx;
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				       else
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b0100;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 3'd2;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= {2{dir}};
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				     2'd3:
 | 
	
		
			
				|  |  | +				       if (width < 8)
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b0xxx;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 1'bx;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= 2'bxx;
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				       else
 | 
	
		
			
				|  |  | +					 begin
 | 
	
		
			
				|  |  | +					    spi_width <= 4'b1000;
 | 
	
		
			
				|  |  | +					    spi_ctr[ctr_max:1] <= 3'd1;
 | 
	
		
			
				|  |  | +					    spi_oe_q <= {2{dir}};
 | 
	
		
			
				|  |  | +					 end
 | 
	
		
			
				|  |  | +				   endcase // case (iowidth)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				   spi_active <= 1'b1;
 | 
	
		
			
				|  |  | +				   sack_q <= 1'b1;
 | 
	
		
			
				|  |  | +				end // if (req)
 | 
	
		
			
				|  |  | +			   end // if (cs_delay == 0 || ~|spi_ctr[ctr_max:1])
 | 
	
		
			
				|  |  | +		      end // else: !if(spi_active)
 | 
	
		
			
				|  |  | +		 end // else: !if(~spi_ctr[0])
 | 
	
		
			
				|  |  | +	    end // if (clk_en)
 | 
	
		
			
				|  |  | +       end // else: !if(~rst_n)
 | 
	
		
			
				|  |  | +endmodule // spi_master
 |