|
@@ -9,65 +9,88 @@
|
|
//
|
|
//
|
|
// spi_io[0] = DI, spi_io[1] = DO in single bit mode.
|
|
// 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
|
|
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 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);
|
|
localparam ctr_max = max(ilog2c(cs_delay)-1,2);
|
|
|
|
|
|
- reg spi_clk;
|
|
|
|
reg spi_sck_q;
|
|
reg spi_sck_q;
|
|
reg spi_active;
|
|
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 [ctr_max:0] spi_ctr;
|
|
reg [n_cs-1:0] spi_cs_q;
|
|
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 [1:0] spi_oe_q;
|
|
|
|
|
|
reg d_dir;
|
|
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 sack_q;
|
|
reg eack_q;
|
|
reg eack_q;
|
|
|
|
|
|
|
|
+
|
|
assign spi_cs_n = ~spi_cs_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);
|
|
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)
|
|
always @(negedge rst_n or posedge clk)
|
|
if (~rst_n)
|
|
if (~rst_n)
|
|
@@ -77,45 +100,37 @@ module spi_master
|
|
spi_width <= 4'b0001;
|
|
spi_width <= 4'b0001;
|
|
spi_ctr <= 1'b0;
|
|
spi_ctr <= 1'b0;
|
|
spi_cs_q <= 1'b0;
|
|
spi_cs_q <= 1'b0;
|
|
- spi_out_q <= 8'hFF;
|
|
|
|
|
|
+ spi_out_q <= idle_allio;
|
|
spi_oe_q <= 2'b10;
|
|
spi_oe_q <= 2'b10;
|
|
sack_q <= 1'b0;
|
|
sack_q <= 1'b0;
|
|
eack_q <= 1'b0;
|
|
eack_q <= 1'b0;
|
|
- d_out <= 8'hFF;
|
|
|
|
|
|
+ d_out <= idle_allio;
|
|
d_in <= 8'hxx;
|
|
d_in <= 8'hxx;
|
|
q_q <= 8'hxx;
|
|
q_q <= 8'hxx;
|
|
end
|
|
end
|
|
else
|
|
else
|
|
begin
|
|
begin
|
|
|
|
+ // These are always single system clock pulses
|
|
sack_q <= 1'b0;
|
|
sack_q <= 1'b0;
|
|
eack_q <= 1'b0;
|
|
eack_q <= 1'b0;
|
|
|
|
|
|
if (clk_en)
|
|
if (clk_en)
|
|
begin
|
|
begin
|
|
spi_ctr <= spi_ctr - 1'b1;
|
|
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])
|
|
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
|
|
else
|
|
begin
|
|
begin
|
|
|
|
+ spi_out_q <= idle_allio;
|
|
|
|
+
|
|
if (spi_active)
|
|
if (spi_active)
|
|
begin
|
|
begin
|
|
- spi_out_q <= 8'hFF;
|
|
|
|
-
|
|
|
|
if (~|spi_ctr[ctr_max:1])
|
|
if (~|spi_ctr[ctr_max:1])
|
|
begin
|
|
begin
|
|
eack_q <= 1'b1;
|
|
eack_q <= 1'b1;
|
|
@@ -123,39 +138,33 @@ module spi_master
|
|
spi_active <= 1'b0;
|
|
spi_active <= 1'b0;
|
|
end
|
|
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)
|
|
end // if (spi_active)
|
|
else
|
|
else
|
|
begin
|
|
begin
|
|
spi_cs_q <= cs;
|
|
spi_cs_q <= cs;
|
|
d_out <= d;
|
|
d_out <= d;
|
|
- spi_out_q <= 8'hFF;
|
|
|
|
|
|
|
|
if (cs_delay != 0 &&
|
|
if (cs_delay != 0 &&
|
|
(spi_cs_changed | ~|spi_ctr[ctr_max:1]))
|
|
(spi_cs_changed | ~|spi_ctr[ctr_max:1]))
|
|
@@ -164,62 +173,29 @@ module spi_master
|
|
spi_ctr[ctr_max:1] <= cs_delay;
|
|
spi_ctr[ctr_max:1] <= cs_delay;
|
|
spi_oe_q <= 2'b10; // As for 1-bit mode
|
|
spi_oe_q <= 2'b10; // As for 1-bit mode
|
|
end
|
|
end
|
|
- else if (cs_delay == 0 || ~|spi_ctr[ctr_max:1])
|
|
|
|
|
|
+ else if (req &&
|
|
|
|
+ (cs_delay == 0 || ~|spi_ctr[ctr_max:1]))
|
|
begin
|
|
begin
|
|
- if (req)
|
|
|
|
|
|
+ if (iowidth <= iowidth_max)
|
|
begin
|
|
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;
|
|
spi_active <= 1'b1;
|
|
sack_q <= 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_active)
|
|
end // else: !if(~spi_ctr[0])
|
|
end // else: !if(~spi_ctr[0])
|
|
end // if (clk_en)
|
|
end // if (clk_en)
|