2
\$\begingroup\$

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.

\$\endgroup\$
5
\$\begingroup\$

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ This is also good advice. My answer addresses a more direct fix to the existing code. \$\endgroup\$ – Dave Tweed Sep 18 '18 at 21:34
  • \$\begingroup\$ Thanks for this. My internal clock is only 64MHz, and I don't particularly want to increase it. I would guess I want it to be at least 8 or 10 times higher than SPI rate? Indeed, the shifter output will have some invalid values if read at the wrong moment - but there is a signal there to stop that happening. I could also add one more register to prevent that. But "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" - this I don't quite understand. Could you explain please? many thanks. \$\endgroup\$ – dmb Sep 19 '18 at 3:43
  • \$\begingroup\$ I could drop SPI speed to about 4MHz, and sample CLK/MOSI with fabric clock of 64MHz. The data rate is hardly affected, and the increase in simplicity is quite attractive. Thank you. \$\endgroup\$ – dmb Sep 19 '18 at 4:47
  • \$\begingroup\$ OK, I see the idea. In fact, like this I can avoid clocking with the SPI CLk completely - by using two of the signals in the registers to edge detect. Thanks! great idea. \$\endgroup\$ – dmb Sep 19 '18 at 9:34
  • 1
    \$\begingroup\$ @dmb Always remember you can only reliably synchronize a single bit across a clock boundary. This is the same reason gray code is used for pointers on asynchronous FIFOs. In your case, you synchronize a/the control bit with the receiving clock domain then use it as an enable to capture the other signals in the new domain. \$\endgroup\$ – CapnJJ Sep 19 '18 at 16:32
2
\$\begingroup\$

No, you've got entirely the wrong idea when it comes to transferring a mulit-bit bus across a clock domain boundary.

Here, the problem is not metastability, but rather sampling the bits on the bus at a time when they are known not to be changing, so that you always get a self-consistent value.

Therefore, it is correct to synchronize and delay the r_RPI_word_done signal before doing edge-detection on it, but it is NOT correct to put the data itself through multiple registers.

Your internal clock is several times faster than the SPI clock (right?), so by the time the o_fifo_write pulse occurs, you KNOW that the data bits are stable and can be sampled safely. You do not need the r_data_sync_1 and r_data_sync_2 registers, and you should directly

assign o_data = r_RPI_shift_in;

In fact, delaying the data is very much counterproductive, because it practically guarantees that you're sampling the data at a time when it is changing, resulting in capturing some bits from one word and some bits from the next word.

\$\endgroup\$
  • \$\begingroup\$ OK, thank you. In fact, because each byte is separated by a long inactive time (transferring a byte takes about 0.5us, there is about 24us before the next one) but indeed there is no point to the registers for the data as you state. \$\endgroup\$ – dmb Sep 19 '18 at 4:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.