interfaz SPI en Xilinx FPGA, dominios de reloj y restricciones de tiempo

1

Estoy conectando una placa Raspberry Pi a una placa dev con un Spartan 6. Quiero hacer esto usando SPI. Debido a la forma en que está diseñada la placa de desarrollo, necesito conectar SPI CLK y DATA a los pines IO estándar.

Soy consciente de la necesidad de cruzar dominios de reloj con doble búfer para proteger contra la metastabilidad. El RPi y el SPI CLK están obviamente en un dominio separado del tejido interno de FPGA. No veo demasiados problemas: solo un registro de 8 bits y la señal que dice cuando un byte está listo deben sincronizarse con el reloj interno de la estructura. No estoy tratando de obtener altas tasas de datos. Solo se escribirá un byte cada 25us (esto se debe a que el RPi es lento para leer un GPIO, pero no hay problema para este proyecto). Estoy pensando en sincronizar el SPI a 15MHz, e incluso podría reducirlo si es necesario.

Este es mi verilog. Simula y realiza pruebas de banco bien.

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

En mi archivo .ucf solo tengo lo siguiente, para decirle a ISE que este no es un reloj "real" (no se construirá sin esto):

NET "i_RPI_spi_clk" CLOCK_DEDICATED_ROUTE = FALSE;
NET "i_RPI_reset" CLOCK_DEDICATED_ROUTE = FALSE;

Mi pregunta: ¿es este el mejor enfoque? ¿Necesito hacer algo más? (Idealmente, sería bueno establecer también algunas restricciones de tiempo para el reloj y los datos de SPI, para que las herramientas conozcan la velocidad de la interfaz de SPI).

Gracias por su consejo.

EDITAR: Debería dejar claro que el RPi solo está transfiriendo un solo byte antes de verificar un pin GPIO. Esto resulta lento (toma alrededor de 25 us), por lo que nunca hay dos bytes seguidos en el bus SPI. Hay actividad de SPI para aproximadamente 0.5us (un byte a 15MHz), luego no pasa nada durante aproximadamente 24us hasta que el RPi ha leído el GPIO. Esto es obviamente mucho más lento de lo que es capaz SPI - el RPi lee el tiempo está ralentizando bastante la transferencia, pero esto es bastante aceptable para este sistema.

    
pregunta dmb

2 respuestas

4

El enfoque habitual es cruzar MOSI, CS y SCLK con el dominio interno del reloj FPGA (que se ejecuta a una velocidad mucho mayor que la del bus SPI) y hacer todo el trabajo allí.

Cruzar un dominio de reloj con una salida de registro paralela tiene al menos la posibilidad inherente de un estado no válido en el que hacerlo con un bus serie realmente no lo hace. Esto se debe a que su filtro de metastabilidad puede registrar diferentes niveles en diferentes bits si varios bits cambian de estado dentro de la ventana de configuración o retención. Además, llevar la secuencia en serie al dominio del reloj central le permite hacer cosas como implementar filtros de falla fácilmente, lo que puede valer la pena.

    
respondido por el Dan Mills
2

No, tienes una idea totalmente equivocada cuando se trata de transferir un bus de múltiples bits a través de un límite de dominio de reloj.

Aquí, el problema no es la metastabilidad, sino el muestreo de los bits en el bus en un momento en que se sabe que no están cambiando, para que siempre obtenga un valor autoconsistente.

Por lo tanto, es correcto sincronizar y retrasar la señal r_RPI_word_done antes de realizar la detección de bordes en ella, pero NO es correcto colocar los datos a través de múltiples registros.

Su reloj interno es varias veces más rápido que el reloj SPI (¿verdad?), por lo que, cuando se produce el pulso o_fifo_write , SABE que los bits de datos son estables y se pueden muestrear de manera segura. No necesita los registros r_data_sync_1 y r_data_sync_2 , y debería hacerlo directamente

assign o_data = r_RPI_shift_in;

De hecho, el retraso de los datos es muy contraproducente, porque prácticamente garantiza que está muestreando los datos en un momento en que están cambiando, lo que da como resultado la captura de algunos bits de una palabra y otros bits de la siguiente palabra.

    
respondido por el Dave Tweed

Lea otras preguntas en las etiquetas