3 votos

Interfaz SPI en la FPGA de Xilinx, dominios de reloj y restricciones de tiempo

Estoy interconectando una placa Raspberry Pi a una placa de desarrollo con un Spartan 6. Quiero hacer esto usando SPI. Debido a la forma en que la placa de desarrollo está diseñado, tengo que conectar SPI CLK y datos a los pines IO estándar.

Soy consciente de la necesidad de cruzar los dominios del reloj con el doble búfer para evitar la metaestabilidad. La RPi y el SPI CLK están obviamente en un dominio separado del tejido interno de la FPGA. No veo mucho problema: sólo un registro de 8 bits y la señal que dice cuando un byte está listo necesitan ser sincronizados con el reloj interno de la fábrica. No estoy tratando de obtener altas tasas de datos. Un byte sólo se escribirá cada 25us (esto es porque la RPi es lenta para leer un GPIO, pero no es un problema para este proyecto). Estoy pensando en sincronizar el SPI a 15MHz, e incluso podría reducir esto si es necesario.

Este es mi verilog. Simula y hace 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 sólo tengo lo siguiente, para decirle a ISE que esto no es un reloj "real" (no 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? ¿Tengo que hacer algo más? (Idealmente sería bueno también establecer algunas restricciones de tiempo para el reloj y los datos SPI, para que las herramientas sean conscientes de la velocidad de la interfaz SPI).

Gracias de antemano por sus consejos.

EDITAR: Debería aclarar que la RPi sólo transfiere un único byte antes de comprobar un pin GPIO. Esto resulta ser lento (toma alrededor de 25us), por lo que nunca hay dos bytes espalda con espalda en el bus SPI. Hay actividad SPI durante unos 0,5us (un byte a 15MHz), luego no pasa nada durante unos 24us hasta que la RPi ha leído el GPIO. Obviamente, esto es mucho más lento de lo que el SPI es capaz de hacer - el tiempo de lectura de la RPi está ralentizando la transferencia bastante - pero esto es bastante aceptable para este sistema.

5voto

Dan Mills Puntos 381

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

Cruzar un dominio de reloj con la salida de un registro paralelo tiene al menos la posibilidad inherente de un estado inválido, mientras que hacerlo con un bus serie realmente no. Esto se debe a que es posible que tu filtro de metaestabilidad registre diferentes niveles en diferentes bits si varios bits cambian de estado dentro de la ventana de configuración o retención. Además, llevar el flujo serie al dominio del reloj del núcleo te permite hacer cosas como implementar filtros de fallos fácilmente, lo que puede valer la pena.

2voto

GSerg Puntos 33571

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

En este caso, el problema no es la metaestabilidad, sino el muestreo de los bits en el bus en un momento en el que se sabe que no cambian, de modo que siempre se obtiene un valor autoconsistente.

Por lo tanto, es correcto sincronizar y retrasar el r_RPI_word_done señal antes de hacer la detección de bordes en ella, pero NO es correcto poner los datos mismos 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 para cuando el o_fifo_write se produce, usted SABE que los bits de datos son estables y pueden ser muestreados con seguridad. No necesita el r_data_sync_1 y r_data_sync_2 registros, y debería directamente

assign o_data = r_RPI_shift_in;

De hecho, retrasar los datos es muy contraproducente, porque prácticamente garantiza que estás muestreando los datos en un momento en que están cambiando, lo que resulta en la captura de algunos bits de una palabra y algunos bits de la siguiente palabra.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X