diff --git a/rtl/ip/spi_slave.v b/rtl/ip/spi_slave.v new file mode 100644 index 00000000..fe578820 --- /dev/null +++ b/rtl/ip/spi_slave.v @@ -0,0 +1,223 @@ +//------------------------------------------------ +// spi_slave.v +//------------------------------------------------ +// General purpose SPI slave module +//------------------------------------------------ +// Written by Tim Edwards +// efabless, inc., april 23, 2017 +//------------------------------------------------ +// This file is distributed free and open source +//------------------------------------------------ + +// reset --- Global (power-on) reset (active high) +// SCK --- Clock input +// SDI --- Data input +// SDO --- Data output +// CSB --- Chip select (sense negative) +// idata --- Data from chip to transmit out, in 8 bits +// odata --- Input data to chip, in 8 bits +// addr --- Decoded address to upstream circuits +// rdstb --- Read strobe, tells upstream circuit to supply next byte to idata +// wrstb --- Write strobe, tells upstream circuit to latch odata. + +// Data format (general purpose): +// 8 bit format +// 1st byte: Command word (see below) +// 2nd byte: Address word (register 0 to 255) +// 3rd byte: Data word (value 0 to 255) + +// Command format: +// 00000000 No operation +// 10000000 Write until CSB raised +// 01000000 Read until CSB raised +// 11000000 Simultaneous read/write until CSB raised +// 11000100 Pass-through read/write until CSB raised +// wrnnn000 Read/write as above, for nnn = 1 to 7 bytes, then terminate + +// Lower three bits are reserved for future use. +// All serial bytes are read and written msb first. + +// Fixed control and status registers + +// Address 0 is reserved and contains flags for SPI mode. This is +// currently undefined and is always value 0. +// Address 1 is reserved and contains manufacturer ID low 8 bits. +// Address 2 is reserved and contains manufacturer ID high 4 bits +// and mask revision number (4 bits). +// Address 3 is reserved and contains product ID (8 bits). +// Addresses 4 to 255 are available for general purpose use. + +`define COMMAND 2'b00 +`define ADDRESS 2'b01 +`define DATA 2'b10 +`define PASSTHRU 2'b11 + +module spi_slave ( + reset, + SCK, + SDI, + CSB, + SDO, + sdoenb, + idata, + odata, + oaddr, + rdstb, + wrstb, + pass_thru, + pass_thru_delay, + pass_thru_reset +); + input reset; + input SCK; + input SDI; + input CSB; + output SDO; + output sdoenb; + input [7:0] idata; + output [7:0] odata; + output [7:0] oaddr; + output rdstb; + output wrstb; + output pass_thru; + output pass_thru_delay; + output pass_thru_reset; + + reg [7:0] addr; + reg wrstb; + reg rdstb; + reg sdoenb; + reg [1:0] state; + reg [2:0] count; + reg writemode; + reg readmode; + reg [2:0] fixed; + wire [7:0] odata; + reg [6:0] predata; + wire [7:0] oaddr; + reg [7:0] ldata; + reg pass_thru; + reg pass_thru_delay; + reg pre_pass_thru; + wire csb_reset; + + assign odata = {predata, SDI}; + assign oaddr = (state == `ADDRESS) ? {addr[6:0], SDI} : addr; + assign SDO = ldata[7]; + assign csb_reset = CSB | reset; // Reset either on CSB or global reset + assign pass_thru_reset = pass_thru_delay | pre_pass_thru; + + // Readback data is captured on the falling edge of SCK so that + // it is guaranteed valid at the next rising edge. + always @(negedge SCK or posedge csb_reset) begin + if (csb_reset == 1'b1) begin + wrstb <= 1'b0; + ldata <= 8'b00000000; + sdoenb <= 1'b1; + end else begin + + // After CSB low, 1st SCK starts command + + if (state == `DATA) begin + if (readmode == 1'b1) begin + sdoenb <= 1'b0; + if (count == 3'b000) begin + ldata <= idata; + end else begin + ldata <= {ldata[6:0], 1'b0}; // Shift out + end + end else begin + sdoenb <= 1'b1; + end + + // Apply write strobe on SCK negative edge on the next-to-last + // data bit so that it updates data on the rising edge of SCK + // on the last data bit. + + if (count == 3'b111) begin + if (writemode == 1'b1) begin + wrstb <= 1'b1; + end + end else begin + wrstb <= 1'b0; + end + end else if (state == `PASSTHRU) begin + wrstb <= 1'b0; + sdoenb <= 1'b0; + end else begin + wrstb <= 1'b0; + sdoenb <= 1'b1; + end // ! state `DATA + end // ! CSB + end // always @ ~SCK + + always @(posedge SCK or posedge csb_reset) begin + if (csb_reset == 1'b1) begin + // Default state on reset + addr <= 8'h00; + rdstb <= 1'b0; + predata <= 7'b0000000; + state <= `COMMAND; + count <= 3'b000; + readmode <= 1'b0; + writemode <= 1'b0; + fixed <= 3'b000; + pass_thru <= 1'b0; + pass_thru_delay <= 1'b0; + pre_pass_thru <= 1'b0; + end else begin + // After CSB low, 1st SCK starts command + if (state == `COMMAND) begin + rdstb <= 1'b0; + count <= count + 1; + if (count == 3'b000) begin + writemode <= SDI; + end else if (count == 3'b001) begin + readmode <= SDI; + end else if (count < 3'b101) begin + fixed <= {fixed[1:0], SDI}; + end else if (count == 3'b101) begin + pre_pass_thru <= SDI; + end else if (count == 3'b110) begin + pass_thru_delay <= pre_pass_thru; + end else if (count == 3'b111) begin + if (pre_pass_thru == 1'b1) begin + state <= `PASSTHRU; + pre_pass_thru = 1'b0; + end else begin + state <= `ADDRESS; + end + end + end else if (state == `ADDRESS) begin + count <= count + 1; + addr <= {addr[6:0], SDI}; + if (count == 3'b111) begin + if (readmode == 1'b1) begin + rdstb <= 1'b1; + end + state <= `DATA; + end else begin + rdstb <= 1'b0; + end + end else if (state == `DATA) begin + predata <= {predata[6:0], SDI}; + count <= count + 1; + if (count == 3'b111) begin + if (fixed == 3'b001) begin + state <= `COMMAND; + end else if (fixed != 3'b000) begin + fixed <= fixed - 1; + addr <= addr + 1; // Auto increment address (fixed) + end else begin + addr <= addr + 1; // Auto increment address (streaming) + end + end else begin + rdstb <= 1'b0; + end + end else begin // state == `PASSTHRU + pass_thru <= 1'b1; + end // ! state `DATA + end // ! CSB + end // always @ SCK + +endmodule // spi_slave