Electronic – SPI interface on Xilinx FPGA, clock domains and timing constraints

clockfpgaspitiming-analysisxilinx

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.

Best Answer

The usual approach is to cross MOSI, CS and SCLK to the internal FPGA fabric clock (running at a far higher rate then the SPI bus) domain and do all the actual work there.

Crossing a clock domain with a parallel register output has at least the inherent possibility of an invalid state where doing it with a serial bus really does not. This is because it is possible for your metastability filter to register different levels on different bits if several bits change state within the setup or hold window. Also, taking the serial stream into the core clock domain lets you do things like implement glitch filters easily, which can be worth having.