I wrote an SPI slave in Verilog. There are some implementations out there, but I am still learning Verilog and digital logic in general, and so I decided I'd try to write it on my own.
My implementation works. But I had to make a change to get it to work, and making the change (I think) puts my implementation at odds with the Motorola SPI spec. I'm wondering: Am I right in thinking this is strange, or do I just not understand how something works?
The SPI signals come in on four wires called SCK, SS, MOSI, and MISO. No surprise there. The FPGA is running at 12MHz and the SPI bus is running at 1MHz. The strategy is to sample bus SPI bus on every FPGA clock posedge and keep track of both the current and last value for SCK and SS so I can detect edges. I also pass each signal through a buffer reg. So the logic is always operating two FPGA clocks behind actual events: On clock 1, I latch the signal in the buffer; on clock 2, I copy it into the reg I use for logic, and on clock 3, I act upon it.
I'm using SPI mode 0. The according to the SPI spec, in mode 0, the slave should sample the MOSI line on the posedge of SCK and then transmit on the negedge of SCK.
So I wrote it just that way:
reg [511:0] data; // I exchange 512-bit messages with the master.
reg [2:0] SCK_buffer = 0;
always @(posedge clock) begin // clock is my FPGA clock
SCK_buffer <= {SCK_buffer[1:0], SCK};
end
wire SCK_posedge = SCK_buffer[2:1] == 2'b01;
wire SCK_negedge = SCK_buffer[2:1] == 2'b10;
reg [2:0] SS_buffer = 0;
always @(posedge clock) begin
SS_buffer <= {SS_buffer[1:0], SS};
end
wire SS_posedge = SS_buffer[2:1] == 2'b01;
wire SS_negedge = SS_buffer[2:1] == 2'b10;
wire SS_active = ~SS_buffer[1];
reg [1:0] MOSI_buffer = 0;
always @(posedge clock) begin
MOSI_buffer = {MOSI_buffer[0], MOSI};
end
wire MOSI_in = MOSI_buffer[1];
assign MISO = data[511];
always @(posedge clock) begin
if (SS_active) begin
if (SCK_posedge) begin
// Clock goes high: capture one bit from MOSI.
MOSI_capture <= MOSI_in;
end
else if (SCK_negedge) begin
// Shift the captured MOSI bit into the LSB of data and output
// the MSB from data. Note: MISO is a wire that outputs data[511].
data <= {data[510:0], MOSI_capture};
end
end
end
The code should work like this:
- When SS goes active (low), data[511] is already output on MISO by virtue of the wire. (No tristate here because I'm the only thing on the bus.)
- On posedge of SCK, the slave samples MOSI, and the master gets the data[511] on MISO.
- On negedge of SCK, the slave shifts the bit sampled from MOSI into data, causing a new bit to shift into the MISO output position.
When I ran this version, it dropped bits all over the place. Literally half of the 512-bit messages I sent from master to slave wound up corrupted.
I looked at an open-source SPI slave implementation and noticed that it didn't use the negedge of SCK. So I changed my code to this:
always @(posedge clock) begin
if (SS_active) begin
if (SCK_posedge) begin
// Skip that "capture" business and just shift MOSI right into the
// data reg.
data <= {data[510:0], MOSI_in};
end
end
end
After making this change, it worked perfectly. Totally bulletproof.
But I'm thinking, huh?
I get that this is okay. MISO gets its new value immediately after the posedge of SCK, and it's still there on the next posedge, when the master samples it. But what was wrong with changing MISO on the negedge? Even though I'm running two FPGA cycles behind the SPI bus due to buffering, I still have 12 FPGA clocks for every one tick of SCK, which means I have six FPGA cycles between SCK negedge and the next posedge. I should be able to get the bit out there on MISO in time, right?
In practice does everyone just (in SPI mode 0) update MISO right after the SCK posedge and not worry about the negedge?
Thanks!