I am interfacing a Raspberry Pi board to a dev board with a Spartan 6. I want to do this using SPI. Because of the way the dev board is designed, I need to connect SPI CLK and DATA to standard IO pins.
I am aware of the need to cross clock domains with double-buffering to guard against metastability. The RPi and the SPI CLK are obviously in a separate domain to the internal FPGA fabric. I don't see too much problem : only one 8 bit register and the signal that says when a byte is ready need to be synchronised to the internal fabric clock. I am not trying to get high data rates. A byte will only get written about every 25us (this is because the RPi is slow to read a GPIO, but no problem for this project). I am thinking to clock the SPI at 15MHz, and even could reduce this if necessary.
This is my verilog. It simulates and bench tests fine.
module my_spi_in (
// RPI clock domain
input i_RPI_spi_data,
input i_RPI_spi_clk,
input i_RPI_reset,
// internal 64MHz domain
input i_sys_clk,
output [7:0] o_data,
output o_fifo_write
);
// registers in RPI clock domain
reg [7:0] r_RPI_shift_in = 8'b0;
reg [2:0] r_RPI_ctr = 3'b0;
reg r_RPI_word_done = 1'b0;
// synchronisation registers
reg [7:0]r_data_sync_1 = 8'b0;
reg [7:0]r_data_sync_2 = 8'b0;
reg [2:0] r_word_done_sync = 3'b0;
// RPI clock domain : input shift register logic
always @ (posedge i_RPI_spi_clk, posedge i_RPI_reset) begin
if (i_RPI_reset == 1'b1) begin
r_RPI_shift_in <= 8'b0;
r_RPI_ctr <= 3'b0;
end else begin
r_RPI_ctr <= r_RPI_ctr + 1'b1;
r_RPI_shift_in <= {i_RPI_spi_data, r_RPI_shift_in[7:1]};
end
end
// RPI clock domain : word done
always @ (negedge i_RPI_spi_clk) begin
if (~i_RPI_reset && r_RPI_ctr == 3'b000) r_RPI_word_done <= 1'b1;
else r_RPI_word_done <= 1'b0;
end
// sync registers
always @ (posedge i_sys_clk) begin
r_data_sync_1 <= r_RPI_shift_in;
r_data_sync_2 <= r_data_sync_1;
r_word_done_sync[0] <= r_RPI_word_done;
r_word_done_sync[1] <= r_word_done_sync[0];
r_word_done_sync[2] <= r_word_done_sync[1];
end
assign o_data = r_data_sync_2;
assign o_fifo_write = r_word_done_sync[1] && ~r_word_done_sync[2];
endmodule
In my .ucf file I only have the following, to tell ISE that this is not a "real"clock (it won't build without this):
NET "i_RPI_spi_clk" CLOCK_DEDICATED_ROUTE = FALSE;
NET "i_RPI_reset" CLOCK_DEDICATED_ROUTE = FALSE;
My question : is this the best approach? Do I need to do anything else? (Ideally it would be good to also set some timing constraints for the SPI clock and data, to make the tools aware of the SPI interface speed.)
Thanks in advance of your advice.
EDIT: I should maker clear that the RPi is only transferring a single byte before checking a GPIO pin. This turns out to be slow (takes around 25us), so there are never two bytes back to back on the SPI bus. There is SPI activity for about 0.5us (one byte at 15MHz), then nothing happens for about 24us until the RPi has read the GPIO. This is obviously far slower than SPI is capable of - the RPi read time is slowing the transfer quite a bit - but this is quite acceptable for this system.