ソースを参照

spi_master: move some configs to ports; add latch_q option

H. Peter Anvin 3 年 前
コミット
b1330f1322
1 ファイル変更106 行追加130 行削除
  1. 106 130
      spi_master.sv

+ 106 - 130
spi_master.sv

@@ -9,65 +9,88 @@
 //
 // spi_io[0] = DI, spi_io[1] = DO in single bit mode.
 //
-// All unused spi_io are driven to 1.
+// By default the output is latched; if not, it is only valid in
+// the cycle that eack is asserted (useful for FIFOs etc.)
 //
 
 //
-// XXX: add clock polarity, MSB/LSB options
+// XXX: CHPA option (mode 1/3)
 //
+
+
+`define IO_MAX max(ilog2c(width)-1, 1)
+
 module spi_master
 #(
-  parameter width = 1,		// Width of SPI data
+  parameter width = 1,		// Max width of SPI bus (1, 2, 4, or 8)
   parameter n_cs  = 1,		// Number of CS# outputs
-  parameter CPOL = 0,		// Inverted SPI clock polarity
-  parameter cs_delay = 1,
-  parameter io_max  = max(ilog2c(width)-1, 1)
+  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				   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
+ 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		   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		   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_clk;
    reg				   spi_sck_q;
    reg				   spi_active;
-   reg [ilog2c(width)-1:0]	   spi_width;
+   reg [((width > 2) ? 1 : 0):0]   spi_width;
    reg [ctr_max:0]		   spi_ctr;
    reg [n_cs-1:0]		   spi_cs_q;
-   reg [7:0]			   spi_out_q;
+   reg [io_max: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 [7:0]			   d_out; // Output shift register
+   reg [7:0]			   d_in;  // Input shift register
+   reg [7:0]			   q_q;	  // Latched output data
 
    reg				   sack_q;
    reg				   eack_q;
 
+
    assign spi_cs_n = ~spi_cs_q;
-   assign spi_sck  = spi_sck_q ^ CPOL;
+   assign spi_sck  = spi_sck_q;
+
+   assign q = latch_q ? q_q : d_in;
 
    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 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)
@@ -77,45 +100,37 @@ module spi_master
 	  spi_width   <= 4'b0001;
 	  spi_ctr     <= 1'b0;
 	  spi_cs_q    <= 1'b0;
-	  spi_out_q   <= 8'hFF;
+	  spi_out_q   <= idle_allio;
 	  spi_oe_q    <= 2'b10;
 	  sack_q      <= 1'b0;
 	  eack_q      <= 1'b0;
-	  d_out       <= 8'hFF;
+	  d_out       <= idle_allio;
 	  d_in        <= 8'hxx;
 	  q_q         <= 8'hxx;
        end
      else
        begin
+	  // These are always single system clock pulses
 	  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;
+	       spi_clk <= (spi_ctr[0] & spi_active) ^ cpol;
 
 	       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])
+		 if (lsb)
+		   d_in <=  ( spi_io[spi_width_n-1:0] << (8-spi_width_n)) |
+			    (d_in >> spi_width_n);
+		 else
+		   d_in <= (d_in << spi_width_n) | spi_io[spi_width_n-1:0];
 	       else
 		 begin
+		    spi_out_q <= idle_allio;
+
 		    if (spi_active)
 		      begin
-			 spi_out_q <= 8'hFF;
-
 		         if (~|spi_ctr[ctr_max:1])
 		           begin
 			      eack_q     <= 1'b1;
@@ -123,39 +138,33 @@ module spi_master
 			      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)
+			 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
+			      if (lsb)
+				d_out <= {spi_width_n{io_idle}} |
+					 (d_out >> spi_width_n);
+			      else
+				d_out <= (d_out << spi_width_n) |
+					 {spi_width_n{io_idle}};
+
+			      // 1-byte SPI uses IO1 as output (DO)
+			      if (spi_width == 2'd0)
+				   spi_out_q[1] <= d_out[lsb ? 0 : 7];
+			      else
+				   spi_out_q[spi_width_n-1:0]
+				     <= d_out >> (lsb ? 0 : 8-spi_width_n);
+			   end
 		      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]))
@@ -164,62 +173,29 @@ module spi_master
 				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])
+			 else if (req &&
+				  (cs_delay == 0 || ~|spi_ctr[ctr_max:1]))
 			   begin
-			      if (req)
+			      if (iowidth <= iowidth_max)
 				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_width <= iowidth;
+				   spi_ctr[ctr_max:1] <= 3'd8 >> iowidth;
+				   spi_oe_q  <=
+					       |iowidth ? {2{dir}} : 2'b10;
 				   spi_active <= 1'b1;
 				   sack_q <= 1'b1;
-				end // if (req)
-			   end // if (cs_delay == 0 || ~|spi_ctr[ctr_max:1])
+				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_q <= 1'bx;
+				end
+			   end // if (req &&...
 		      end // else: !if(spi_active)
 		 end // else: !if(~spi_ctr[0])
 	    end // if (clk_en)