Einführung
Auf dieser Seite wird die 01-Signal-Abtastung (englisch: 01-signal sampling) als Methode für die Anbindung eines source-synchronen Eingangs (englisch: source-synchronous input) erläutert. Andere Strategien für die Anbindung solcher Datenquellen sind auf einer Seite über source-synchrone Eingänge im Allgemeinen vorgestellt. Auf dieser Seite wird auch erklärt, was ein source-synchroner Eingang ist.
Der Grundgedanke der 01-Signal-Abtastung besteht darin, den externen Takt wie ein Datensignal zu behandeln: Der Takt wird entsprechend von einem Register abgetastet. Dieses Register verwendet einen internen Takt, der stabil und unabhängig vom externen Takt ist.
Die Datensignale werden von weiteren Registern abgetastet. Dabei wird derselbe interne Takt verwendet. Dieser interne Takt versorgt auch die gesamte Logik, die die 01-Signal-Abtastung implementiert.
Diese Logik erkennt Änderungen an dem Register, das den externen Takt abtastet. Wenn dieses Register von „0“ auf „1“ wechselt, bedeutet das, dass eine steigende Flanke am externen Takt aufgetreten ist. Die Logik reagiert darauf, indem sie die Werte der übrigen Register in einen FIFO schreibt. Diese Register enthalten die Werte der Datensignale, die zum Zeitpunkt der steigenden Flanke des externen Takts anlagen.
Diese Logik erzielt dasselbe Ergebnis, als ob der Takt des FIFOs der externe Takt wäre und die Dateneingänge des FIFOs direkt mit den Datensignalen verbunden wären. Der Unterschied liegt darin, welcher Takt von der Logik verwendet wird: der externe oder der interne Takt. Der Vorteil der 01-Signal-Abtastung besteht darin, dass die gesamte Logik nur vom internen Takt abhängt. Dieser Takt ist stabil und zuverlässig. Selbst wenn sich der externe Takt unangemessen verhält, arbeitet die Logik weiterhin sinnvoll.
Das folgende Bild verdeutlicht die 01-Signal-Abtastung:
In diesem Bild ist @stable_clk der interne Takt des FPGAs. @data_clk und @data sind Signale, die am FPGA ankommen. @data_clk_samp und @data_samp sind Register innerhalb des FPGAs. Das externe Signal @data_clk wird durch @data_clk_samp repräsentiert; Entsprechendes gilt für @data_samp im Verhältnis zu @data.
Das Bild zeigt, dass der Wert von @data_samp in einen FIFO geschrieben wird, wenn in @data_clk_samp ein Muster „0 1“ erscheint. Das ist der Grund, warum diese Methode als „01-Signal-Abtastung“ bezeichnet wird.
Der FIFO wird hier als Beispiel dafür genannt, was mit den ankommenden Daten geschehen kann. Das ist oft eine zweckmäßige Lösung, weil die Frequenz des internen Takts deutlich höher ist als die Datenrate. Daher ist es häufig sinnvoll, die Daten anschließend mit einer Logik zu verarbeiten, die auf einer niedrigeren Taktfrequenz basiert. Ein FIFO bietet sich an, um die Daten an eine Logik in einer anderen Taktdomäne (englisch: clock domain) zu übergeben.
Es ist allerdings möglich, die übrige Logik mit demselben internen Takt zu implementieren, der auch für die 01-Signal-Abtastung verwendet wird. Der Einsatz eines FIFOs ist nur eine Möglichkeit.
Ein Beispiel in Verilog
Der folgende Verilog-Code verdeutlicht die Idee. Der externe Takt ist @data_clk.
module top (
input stable_clk,
input data_clk,
input [7:0] data
);
reg [7:0] data_guard, data_samp;
reg data_clk_guard, data_clk_samp, data_clk_samp_d;
wire fifo_wr_en;
always @(posedge stable_clk)
begin
data_guard <= data;
data_clk_guard <= data_clk;
data_samp <= data_guard;
data_clk_samp <= data_clk_guard;
data_clk_samp_d <= data_clk_samp;
end
assign fifo_wr_en = data_clk_samp && !data_clk_samp_d;
data_fifo fifo_i
(
.wr_clk(stable_clk),
.din(data_samp),
.wr_en(fifo_wr_en),
[ ... other ports connected here ... ]
);
endmodule
Der wichtige Teil ist @fifo_wr_en, also das Schreibsignal (wr_en) des FIFOs. Dieses Signal entspricht „data_clk_samp && !data_clk_samp_d“. Es ist also high, wenn ein „0 1“-Muster an @data_clk_samp erkannt wird. Dadurch wird der Wert von @data_samp in den FIFO geschrieben.
Beachten Sie, dass die Logik ausschließlich @stable_clk als Takt verwendet. @data_clk wird wie ein gewöhnlicher I/O-Eingang behandelt.
Die Timing-Anforderungen von @data_guard und @data_clk_guard sind nicht garantiert: Die Eingangs-Ports sind asynchron in Bezug auf @stable_clk. @data_guard und @data_clk_guard sind also Metastabilitäts-Schutzregister (englisch: metastability). Die Logik verlässt sich nicht direkt auf die Werte dieser beiden Register. Dagegen werden @data_samp und @data_clk_samp direkt von der Logik verwendet, denn diese Register sind die zweite Stufe des Metastabilitäts-Schutzes.
Funktioniert das überhaupt? Die Antwort liegt in der Timing-Analyse.
Timing-Analyse
Die Timing-Analyse der 01-Signal-Abtastung unterscheidet sich von der üblichen Vorgehensweise: Normalerweise besteht eine konstante Zeitdifferenz zwischen der Taktflanke und dem Zeitpunkt, an dem die Datensignale abgetastet werden. Das liegt daran, dass der für die Abtastung verwendete Takt synchron zu den Datensignalen ist. Bei der 01-Signal-Abtastung wird @data jedoch mit @stable_clk abgetastet. Der Abtastzeitpunkt hat daher nichts mit dem eigenen Timing der Datensignale zu tun. Stattdessen wählt die Logik nur die Werte von @data_clk_samp aus, die nahe an einer steigenden Taktflanke liegen.
Zwischen dem Zeitpunkt der steigenden Flanke des Datentakts und dem tatsächlichen Abtastzeitpunkt liegt also eine zufällige Zeitdifferenz. Wie kann diese Methode dann zuverlässig sein?
Es gibt eine separate Seite über die Grundlagen des Timings in einem Logikdesign. Auf dieser Seite wird die Bedeutung von tsu und thold erläutert. Kurz gesagt: Der Eingang eines Flip-Flops muss vor und nach der steigenden Taktflanke stabil sein (vorausgesetzt, das Flip-Flop wird mit der steigenden Flanke aktiviert). tsu legt fest, wie lange der Eingang vor der steigenden Flanke stabil sein muss. thold legt fest, wie lange der Eingang nach der steigenden Flanke stabil sein muss. Wenn eine dieser Bedingungen verletzt wird, ist das Verhalten des Flip-Flops als Reaktion auf die steigende Flanke unvorhersehbar.
Man kann das auch anders betrachten: Der Eingang muss während eines bestimmten Zeitfensters um die steigende Flanke stabil sein. Nennen wir dieses Zeitfenster Δt = tsu + thold. Auf der Grundlage von Δt und weiterer Parameter werden wir nun die Timing-Anforderungen ermitteln, die einen zuverlässigen Betrieb gewährleisten.
Die gesamte Timing-Analyse beruht auf der Situation, dass @data_clk_guard high und @data_clk_samp low ist. Wenn das eintritt, wird das Muster „0 1“ einen Taktzyklus später erkannt. Anders ausgedrückt: Im nächsten Taktzyklus ist @data_clk_samp_d = „0“ und @data_clk_samp = „1“. Dadurch wird @fifo_wr_en high, also muss @data_samp den Wert enthalten, den die Datenquelle übertragen wollte.
Die Analyse konzentriert sich daher auf folgende Frage: Welche Anforderungen gelten an @data, wenn @data_clk_guard high und @data_clk_samp low ist, damit die Informationen korrekt ankommen?
Die Analyse erfolgt in zwei Teilen. In beiden Teilen gehe ich davon aus, dass @data_clk_guard high und @data_clk_samp low ist. Im ersten Teil lautet die Frage: Was ist der späteste Zeitpunkt, zu dem @data_clk von low auf high wechseln kann, ohne diese Annahme zu verletzen? Danach ermittle ich die Timing-Anforderung an @data, die sicherstellt, dass @data_samp den korrekten Wert enthält.
Im zweiten Teil der Analyse stelle ich die entgegengesetzte Frage: Was ist der früheste Zeitpunkt, zu dem @data_clk von low auf high wechseln kann, ohne die Annahme zu @data_clk_guard und @data_clk_samp zu verletzen? Anschließend führe ich eine ähnliche Analyse der Timing-Anforderungen durch.
Zunächst definieren wir jedoch einige Symbole:
- tclk: Die Taktperiode von @stable_clk.
- tskew: Dies ist der zeitliche Versatz (englisch: skew) zwischen den Ports von @data_clk und @data. Es ist die maximale Differenz der Signalverzögerungen für alle Verbindungen von der Signalquelle zu den Flip-Flops im FPGA.
- tj: Der maximale Beitrag des Jitters (englisch: jitter) zur Taktperiode. Anders ausgedrückt liegt die Zeitdifferenz zwischen zwei Taktflanken immer zwischen tclk - tj und tclk + tj. Oder genauer: Es besteht nur eine vernachlässigbare Wahrscheinlichkeit dafür, dass eine Taktperiode außerhalb dieser Grenzen liegt.
Erster Teil der Analyse
Dieses Timing-Diagramm zeigt zwei Taktzyklen von @stable_clk. In der folgenden Diskussion übernimmt @data_clk_guard seinen neuen Wert von @data_clk bei der steigenden Flanke rechts im Diagramm. Ebenso übernimmt @data_guard seinen neuen Wert von @data bei derselben Taktflanke.
Nach demselben Prinzip sind @data_clk_samp und @data_samp der steigenden Taktflanke von @stable_clk links im Diagramm zugeordnet.
In diesem Szenario wechselt @data_clk genau am Ende des gelben Bereichs auf high. Die Timing-Anforderungen des Flip-Flops sind verletzt, das Ergebnis ist unvorhersehbar. Es ist jedoch möglich, dass @data_clk_guard high und @data_clk_samp low sind.
Wenn @data_clk seinen Wert jedoch etwas später ändert, ist @data_clk_guard mit Sicherheit low, weil das Verhalten des Flip-Flops dann vorhersagbar ist: Der Eingang ist während des gesamten gelben Bereichs low. Deshalb gilt: Wenn @data_clk_guard high und @data_clk_samp low ist, ist @data_clk früher von low auf high gewechselt, als in obigem Timing-Diagramm gezeigt. Das Diagramm zeigt also den Fall, in dem @data_clk zum spätestmöglichen Zeitpunkt gewechselt ist.
Damit @data_guard einen zuverlässigen Wert enthält, muss @data vor dem gelben Bereich stabil sein. Daraus ergibt sich die erste Timing-Anforderung: @data muss für eine Zeitdauer von Δt vor der steigenden Flanke von @data_clk stabil sein.
Beachten Sie: Wenn @data_clk früher wechselt, erfüllt diese Timing-Anforderung weiterhin die tsu-Bedingung von @data_guard. Die tsu-Anforderung der Flip-Flops ist also für alle Szenarien erfüllt, in denen @data_clk_guard high und @data_clk_samp low ist.
Das Timing-Diagramm zeigt keinen Skew. Um den Skew zu berücksichtigen, lautet die Timing-Anforderung: @data muss für eine Zeitdauer von Δt + tskew vor der steigenden Flanke von @data_clk stabil sein.
In diesem Szenario muss kein Jitter berücksichtigt werden, weil nur eine einzige Taktflanke beteiligt ist.
Zweiter Teil der Analyse
Das folgende Timing-Diagramm zeigt dieses Szenario:
In diesem Szenario wechselt @data_clk genau am Anfang des gelben Bereichs der vorherigen Taktflanke von @stable_clk auf high.
Es besteht kein Zweifel, dass @data_clk_guard high sein wird. Der Wert von @data_clk_samp ist jedoch nicht vorhersagbar, weil die Timing-Anforderungen im vorherigen Taktzyklus verletzt wurden. Wie zuvor ist es möglich, dass @data_clk_samp low ist. Wenn @data_clk seinen Wert jedoch früher ändert, ist @data_clk_samp mit Sicherheit high.
Wenn also @data_clk_guard high und @data_clk_samp low ist, dann ist @data_clk später von low auf high gewechselt, als in obigem Timing-Diagramm gezeigt. Das Diagramm zeigt also den Fall, in dem @data_clk zum frühestmöglichen Zeitpunkt gewechselt ist.
Damit @data_guard den korrekten Wert enthält, muss @data nach dem gelben Bereich rechts stabil sein.
Dem Timing-Diagramm zufolge beträgt die Zeitdifferenz zwischen der steigenden Flanke von @data_clk und dem Ende des zweiten gelben Bereichs Δt + tclk. Zusätzlich müssen Skew und Jitter berücksichtigt werden. Daher muss @data für eine Zeitdauer von Δt + tclk + tskew + tj nach @data_clk stabil sein.
Beachten Sie: Wenn @data_clk früher wechselt, deckt diese Timing-Anforderung weiterhin die thold-Bedingung von @data_guard ab. Die thold-Anforderung der Flip-Flops ist also für alle Szenarien erfüllt, in denen @data_clk_guard high und @data_clk_samp low ist.
Die Timing-Anforderungen
Als Ergebnis der obigen Timing-Analyse ergeben sich zwei Timing-Anforderungen für einen zuverlässigen Betrieb:
- @data muss für eine Zeitdauer von mindestens Δt + tskew vor der steigenden Flanke von @data_clk stabil sein.
- @data muss für eine Zeitdauer von mindestens Δt + tclk + tskew + tj nach der steigenden Flanke von @data_clk stabil sein.
Diese Anforderungen mögen kompliziert erscheinen, doch sie sind häufig leicht zu erfüllen. Wenn sich @data beispielsweise mit der fallenden Flanke von @data_clk ändert, sind diese Anforderungen meist problemlos einzuhalten. Wenn @stable_clk dreimal so schnell ist wie @data_clk, reicht das oft bereits aus.
In den meisten Fällen müssen die genauen Werte für Δt, tskew oder tj nicht bekannt sein. Es genügt oft, anhand der beiden obigen Anforderungen zu berechnen, wie groß Δt beziehungsweise Δt + tskew + tj sein dürfen. Wenn die Frequenz von @stable_clk hoch genug ist, sind diese beiden Werte häufig größer, als es für ein FPGA realistisch möglich ist.
Für die Minimierung der Timing-Unterschiede zwischen den Eingangs-Ports des FPGAs (tskew) sollten IOB-Register verwendet werden. Außerdem sollten für diese Eingangs-Ports Timing-Constraints geschrieben werden, um sicherzustellen, dass IOB-Register verwendet werden.
Varianten der 01-Signal-Abtastung
Bisher bin ich davon ausgegangen, dass sich @data mit der fallenden Flanke von @data_clk ändert. Wenn sich @data mit der steigenden Flanke von @data_clk ändert, sollte die Logik so angepasst werden, dass sie aktiv wird, wenn @data_clk_guard low und @data_clk_samp high ist. Mit anderen Worten: Die Logik erkennt die fallende Flanke von @data_clk anhand eines „1 0“-Musters.
Es ist auch möglich, ein anderes Kriterium dafür zu wählen, wann der Wert von @data übernommen wird. Beispielsweise kann es besser sein, @fifo_wr_en um einige Taktzyklen zu verzögern. Manchmal ist es günstiger, wenn @fifo_wr_en früher aktiv wird. Das hängt von den Timing-Beziehungen zwischen @data_clk und @data ab. Ziehen Sie das Datenblatt der Signalquelle zu Rate und analysieren Sie das Timing wie oben gezeigt.
Wenn die Frequenz von @data_clk relativ hoch ist, können DDR-Register verwendet werden, um @data_clk und @data abzutasten. Die Logik, die diese Lösung implementiert, ist etwas komplexer, aber es gelten dieselben Prinzipien.
Ist @data_guard wirklich ein Metastabilitäts-Schutzregister?
Die kurze Antwort lautet: Ja. @data ist asynchron zu @stable_clk, daher sind die Timing-Anforderungen von @data_guard nicht garantiert.
Aber schränken wir die Betrachtung auf die Taktzyklen ein, in denen der Inhalt von @data_guard tatsächlich verwendet wird: Die beiden oben genannten Timing-Anforderungen stellen sicher, dass die Flip-Flops, die @data_guard implementieren, zuverlässig arbeiten. Sowohl tsu als auch thold dieser Flip-Flops sind garantiert.
@data_guard wird also nicht wirklich als Metastabilitäts-Schutzregister verwendet. In der Art, wie dieses Register eingesetzt wird, fungiert es lediglich als Verzögerungsregister. Es schadet jedoch nicht, dass ein zusätzliches Register gegen Timing-Verletzungen schützt, die auftreten können, wenn sich das physische Signal (@data) nicht korrekt verhält. Das ist insbesondere dann relevant, wenn das Signal über einen Steckverbinder mit dem FPGA verbunden ist.
Ein Beispiel aus der Praxis
Auf einer anderen Seite gibt es ein Beispiel für eine Logik, die mit einem OV7670-Kamerasensor verbunden wird. Diese Logik gewinnt die Pixeldaten des Kamerasensors mithilfe der 01-Signal-Abtastung. Die Eingänge des Kamerasensors lauten wie folgt:
input pclk_in;
input [7:0] D_in;
input hsync_in, vsync_in;
Die Logik, die die 01-Signal-Abtastung durchführt, sieht folgendermaßen aus:
(* IOB = "TRUE" *) reg [7:0] D_guard;
(* IOB = "TRUE" *) reg pclk_guard, hsync_guard, vsync_guard;
reg [7:0] D;
reg pclk, hsync, vsync;
wire sample_valid;
reg previous_pclk;
always @(posedge stable_clk)
begin
// Metastability guards on asynchronous inputs
D_guard <= D_in;
pclk_guard <= pclk_in;
hsync_guard <= hsync_in;
vsync_guard <= vsync_in;
D <= D_guard;
pclk <= pclk_guard;
hsync <= hsync_guard;
vsync <= vsync_guard;
previous_pclk <= pclk;
end
assign sample_valid = pclk && !previous_pclk;
Zur besseren Übersicht gibt es leichte Unterschiede zwischen dem hier gezeigten Verilog-Code und dem Verilog-Code auf der Seite über OV7670. Diese Unterschiede haben keinerlei Einfluss auf die Funktionsweise der Logik.
Vergleichen wir diesen Verilog-Code mit dem Code am Anfang dieser Seite. Die Signalnamen sind anders, bedeuten aber dasselbe: Statt @data haben wir nun @D_in, @hsync_in und @vsync_in. Statt @data_clk haben wir @pclk_in. Und statt @fifo_wr_en haben wir @sample_valid. Die geänderten Namen können verwirren, aber gegenüber dem Verilog-Code oben hat sich nichts geändert.
Beachten Sie die „(* IOB = "TRUE" *)“-Vermerke vor den Registerdeklarationen. Bei Verwendung von Vivado ist dies eine Möglichkeit, die Tools zu veranlassen, die Register in den IOBs unterzubringen.
In diesem Beispiel wird kein FIFO gezeigt, denn nicht alle Daten sollen in den FIFO geschrieben werden: Wenn @sample_valid high ist, heißt das, dass @D, @hsync und @vsync korrekte Werte vom Kamerasensor enthalten. Das bedeutet aber nicht, dass @D in den FIFO geschrieben werden soll; das hängt von @hsync und @vsync ab. Im OV7670-Beispiel sorgt daher eine zusätzliche Logik dafür, dass nur Pixel in den FIFO geschrieben werden.
Kommen wir aber zum interessanten Teil: der Timing-Analyse.
Die Frequenz von @stable_clk beträgt in diesem Beispiel 100 MHz. Die Frequenz von @pclk_in beträgt 25 MHz. Laut Datenblatt des OV7670 sind die Signale @D_in, @hsync_in und @vsync_in während der 5 ns garantiert stabil, nachdem @pclk_in von high auf low gewechselt ist (fallende Flanke).
Die Taktperiode von @pclk_in beträgt 40 ns. Der Abstand von der fallenden zur steigenden Flanke beträgt also 20 ns. Vergleichen wir das nun mit den Timing-Anforderungen.
Die erste Timing-Anforderung besagte, dass @D_in, @hsync_in und @vsync_in für eine Zeitdauer von Δt vor der steigenden Flanke von @pclk_in stabil sein müssen. In der Realität sind diese Signale ab 5 ns nach der fallenden Flanke stabil. Sie sind also mindestens 15 ns vor der nächsten steigenden Flanke stabil. Zur Erinnerung: Δt = tsu + thold. Die eigentliche Anforderung lautet also, dass tsu + thold kleiner als 15 ns ist. Das gilt für jedes FPGA.
Die zweite Anforderung besagt, dass diese Signale für mindestens Δt + tclk + tskew + tj nach der steigenden Flanke von @pclk_in stabil sein müssen. Diese Signale ändern sich jedoch nur als Folge einer fallenden Flanke. Die Anforderung lautet also, dass Δt + tclk + tskew + tj kleiner als 20 ns ist. tclk beträgt 10 ns, weil @stable_clk mit 100 MHz arbeitet. Die eigentliche Anforderung lautet damit, dass Δt + tskew + tj kleiner als 10 ns ist. Auch das ist bei jedem FPGA ohne Weiteres erfüllt.
Dieses Beispiel zeigt, wie sich die Timing-Anforderungen problemlos erfüllen lassen, ohne die genauen Timing-Parameter des FPGAs zu kennen.
Fazit
Die 01-Signal-Abtastung ist eine hervorragende Lösung, wenn die Datenrate im Verhältnis zur unterstützten Taktfrequenz des FPGAs niedrig ist: Der Datentakt muss nicht stabil sein. Auch die exakte Frequenz des Datentakts muss nicht im Voraus bekannt sein. Es genügt, die beiden Timing-Anforderungen zu gewährleisten.
Diese Methode hat weitere Vorteile: Wenn dieser Takt kurzzeitig inaktiv wird, hat das nur zur Folge, dass in diesem Zeitraum keine Daten erfasst werden. Der Schaden durch Fehlverhalten dieses Takts beschränkt sich auf eine Störung des Datenflusses. Das führt zu einer sichtbaren Fehlfunktion des Systems, aber diese Fehlfunktion sieht nach einem Taktproblem aus (und nicht danach, als ob das FPGA von Geistern heimgesucht würde).
Auch wenn die Abtastung der Datensignale also eine gewisse inhärente Zufälligkeit aufweist, ist die 01-Signal-Abtastung eine zuverlässige und robuste Lösung für einen source-synchronen Eingang. Der einzige echte Nachteil ist die Begrenzung der Datenrate.


