Ich verbinde ein Raspberry Pi-Board mit einem Entwicklungsboard mit einem Spartan 6. Ich möchte dies mit SPI tun. Aufgrund des Designs des Entwicklungsboards muss ich SPI CLK und DATA mit Standard-IO-Pins verbinden.
Ich bin mir der Notwendigkeit bewusst, Taktdomänen mit Doppelpufferung zu kreuzen, um Metastabilität zu vermeiden. Das RPi und der SPI CLK befinden sich offensichtlich in einer separaten Domäne zum internen FPGA-Fabric. Ich sehe kein allzu großes Problem: Nur ein 8-Bit-Register und das Signal, das besagt, wann ein Byte bereit ist, müssen mit der internen Fabric-Uhr synchronisiert werden. Ich versuche nicht, hohe Datenraten zu bekommen. Ein Byte wird nur etwa alle 25 us geschrieben (das liegt daran, dass das RPi ein GPIO langsam liest, aber kein Problem für dieses Projekt). Ich denke darüber nach, den SPI mit 15 MHz zu takten, und könnte dies bei Bedarf sogar reduzieren.
Das ist mein Verilog. Es simuliert und testet gut.
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 meiner .ucf-Datei habe ich nur Folgendes, um ISE mitzuteilen, dass dies keine "echte" Uhr ist (sie wird ohne diese nicht erstellt):
NET "i_RPI_spi_clk" CLOCK_DEDICATED_ROUTE = FALSE;
NET "i_RPI_reset" CLOCK_DEDICATED_ROUTE = FALSE;
Meine Frage: Ist das die beste Vorgehensweise? Muss ich noch etwas tun? (Idealerweise wäre es gut, auch einige Zeitbeschränkungen für die SPI-Uhr und -Daten festzulegen, um die Tools auf die Geschwindigkeit der SPI-Schnittstelle aufmerksam zu machen.)
Vielen Dank im Voraus für Ihren Rat.
BEARBEITEN: Ich sollte klarstellen, dass das RPi nur ein einzelnes Byte überträgt, bevor ein GPIO-Pin überprüft wird. Dies erweist sich als langsam (dauert etwa 25 us), sodass auf dem SPI-Bus niemals zwei Bytes hintereinander liegen. Es gibt SPI-Aktivität für etwa 0,5 us (ein Byte bei 15 MHz), dann passiert für etwa 24 us nichts, bis das RPi den GPIO gelesen hat. Dies ist offensichtlich viel langsamer als SPI es kann – die RPi-Lesezeit verlangsamt die Übertragung ziemlich – aber das ist für dieses System durchaus akzeptabel.
Der übliche Ansatz besteht darin, MOSI, CS und SCLK zur internen FPGA-Fabric-Clock-Domäne (die mit einer weitaus höheren Rate als der SPI-Bus läuft) zu kreuzen und dort die gesamte eigentliche Arbeit zu erledigen.
Das Überqueren einer Taktdomäne mit einem parallelen Registerausgang hat zumindest die inhärente Möglichkeit eines ungültigen Zustands, wo dies bei einem seriellen Bus wirklich nicht der Fall ist. Dies liegt daran, dass Ihr Metastabilitätsfilter möglicherweise verschiedene Pegel auf verschiedenen Bits registriert, wenn mehrere Bits den Status innerhalb des Setup- oder Haltefensters ändern. Wenn Sie den seriellen Stream in die Core-Clock-Domäne bringen, können Sie auch Dinge wie die Implementierung von Glitch-Filtern einfach tun, was sich lohnen kann.
Nein, Sie haben eine völlig falsche Vorstellung, wenn es darum geht, einen Multi-Bit-Bus über eine Taktdomänengrenze zu übertragen.
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;
Tatsächlich ist das Verzögern der Daten sehr kontraproduktiv, da es praktisch garantiert , dass Sie die Daten zu einem Zeitpunkt abtasten, an dem sie sich ändern, was dazu führt, dass einige Bits aus einem Wort und einige Bits aus dem nächsten Wort erfasst werden.
David Tweed
danmcb
danmcb
danmcb
CapnJJ
danmcb