01signal.com

Metastabilität und die Grundlagen des Taktdomänenwechsels

Diese Seite ist die zweite von drei Seiten in einer Serie über Taktdomänen.

Umfang

Wie auf der vorherigen Seite bereits erwähnt, ist bei einem Pfad (path) über Taktdomänen (clock domains) von verwandten Takten (related clocks) in der Logik keine besondere Behandlung erforderlich. Trotzdem muss sichergestellt werden, dass die FPGA-Tools auf diesem Pfad die Timing-Constraints (timing constraints) durchsetzen, damit die Timing-Anforderungen (Setup und Hold) des synchronen Elements am Ziel eingehalten werden. Dieser Fall wird also so behandelt, als gäbe es überhaupt keinen Taktdomänenwechsel (clock domain crossing) – der Pfad wird ganz normal zeitlich analysiert.

Wenn es sich dagegen um nicht verwandte Takte (unrelated clocks) handelt, ist Resynchronisationslogik erforderlich: Es muss Verilog-Code (oder Code in einer anderen Sprache) vorhanden sein, der genau diesem Problem gewidmet ist. Diese Seite behandelt die Grundlagen, wie man das macht.

Benutzen Sie nach Möglichkeit einen FIFO

Ich hielt es für fair, zunächst damit zu beginnen, wie man sich diesen ganzen Ärger ganz erspart – für alle, die die Möglichkeit haben, zu entkommen. Nämlich:

Dual-Clock-FIFOs, die von den FPGA-Tools erzeugt werden, sind mit Abstand der sicherste Weg, um zwischen Taktdomänen zu wechseln. Das ist die gebräuchlichste Lösung, und die Tatsache, dass diese Methode so weit verbreitet ist, ist für sich genommen schon ein Grund, ihr zu vertrauen.

Insbesondere wenn Sie neu bei FPGAs sind, gibt es guten Grund, einen FIFO zu bevorzugen, selbst wenn Sie dafür den eigenen Entwurf ein wenig umbauen müssen. Ein FIFO verbraucht möglicherweise etwas mehr Ressourcen als eine speziell auf Ihren Zweck zugeschnittene Logik. Beachten Sie jedoch, dass kein Block-RAM nötig ist, wenn die Tiefe des FIFO gering ist. Der Unterschied ist also nicht unbedingt das Risiko wert, etwas zu vermasseln.

Oft ist der FIFO die natürliche Lösung für den Übergang zwischen Taktdomänen, insbesondere wenn ein Datenstrom von einer Funktionseinheit zu einer anderen fließt. Aber selbst wenn das nicht ganz so natürlich ist, lässt sich die Logik häufig so umorganisieren, dass es passt. Wenn zum Beispiel Logik in einer Taktdomäne Logik in einer anderen Taktdomäne über ein Ereignis benachrichtigen muss, kann man ein Nachrichtenwort codieren und in einen flachen FIFO schreiben. Eine solche Lösung ist nicht nur weniger fehleranfällig, sondern führt wahrscheinlich auch zu einem aufgeräumteren und besser lesbaren Entwurf.

Wie man FIFOs anschließt und verwendet, wird ausführlich in dieser Serie von Seiten behandelt.

Resynchronisationslogik für ein einzelnes Bit

Wenn ein FIFO Ihr Problem nicht löst, ist es Zeit, die Ärmel hochzukrempeln und die Resynchronisationslogik für einen sicheren Taktdomänenwechsel zu implementieren. Der Rest dieser Seite beschränkt sich auf den Wechsel zwischen Taktdomänen mit einem Signal, das genau ein Bit breit ist. Das ist der Grundbaustein für jede Resynchronisationslogik, und über diesen scheinbar einfachen Fall gibt es einiges zu sagen.

Die nächste Seite dieser Serie zeigt, wie man mit breiteren Signalen zwischen Taktdomänen wechselt, aufbauend auf der unten vorgestellten Technik der Metastabilitätsabschirmung.

Metastabilität

Bevor wir zu Lösungen kommen, ist es wichtig zu verstehen, was passiert, wenn die Timing-Anforderungen eines Flip-Flops verletzt werden. Anders gesagt: wenn das Signal an seinem Dateneingang nicht in dem Zeitfenster stabil ist, das tsu vor der Taktflanke beginnt und thold nach ihr endet. Die betreffende Taktflanke ist natürlich die am Takteingang des Flip-Flops. Genau diese Taktflanke veranlasst es, den Dateneingang abzutasten.

Ändert sich das Datensignal während dieses Zeitfensters, dann ist der Ausgang des Flip-Flops nach dieser Taktflanke unvorhersagbar. Aber es kommt noch schlimmer: Es kann deutlich länger als üblich dauern, bis das Flip-Flop an seinem Ausgang einen gültigen Pegel „0“ oder „1“ präsentiert. Obwohl das Flip-Flop dafür ausgelegt ist, den Ausgang stabil auf einem dieser beiden Werte zu halten, kann eine Verletzung der Timing-Anforderungen dazu führen, dass das Flip-Flop kurzzeitig in einem instabilen Zustand verharrt. Oder, um den offiziellen Begriff zu verwenden: Das Flip-Flop ist metastabil.

Ich will das Klischeebild vom Ball auf der Spitze eines Hügels nicht weglassen; es wird oft verwendet, um die instabile Situation zu veranschaulichen, in die ein Flip-Flop geraten kann:

Ball on tip of hill, illustrating metastability

Metastabilität ist ein viel größeres Problem, als nur die Ungewissheit darüber, auf welchem Wert das Flip-Flop schließlich landet. Das gilt besonders, wenn der Ausgang des Flip-Flops mit mehr als einem Ziel verbunden ist: Weil es länger dauert, bis es auf „0“ oder „1“ landet, könnte das Signal für die Flip-Flops am Ziel zu spät stabil werden. Infolgedessen kann ein Teil der Flip-Flops am Ziel das schwankende Signal als „0“ empfangen, während andere es als „1“ empfangen. Diese Uneinheitlichkeit kann die Logik in einen unzulässigen Zustand bringen, und aus dieser Situation heraus kann sich die Logik auf Schwarze-Magie-Art verhalten. Ein Flip-Flop, das von Metastabilität bedroht ist, sollte also niemals mit mehr als einem Logikelement verbunden werden.

Um Metastabilität in den Griff zu bekommen, wäre es schön zu wissen, wie lange ein Flip-Flop braucht, um in einen seiner stabilen Zustände zu kippen. Leider gibt es darauf keine eindeutige Antwort. Das ist genau wie die Frage, wie lange der Ball auf dem Hügel stehen bleibt: Es hängt von vielen Faktoren ab, und vermutlich wird ihn irgendeine zufällige Erschütterung schließlich die eine oder andere Seite hinunterrollen lassen. Genauso ist es mit der Metastabilität eines Flip-Flops: Es wird durch zufälliges Rauschen in den elektronischen Schaltungen oder was auch immer aus diesem Zustand geholt.

Theoretisch kann ein Flip-Flop also unbegrenzt im Zustand der Metastabilität verharren, in der Praxis kippt es aber nach kurzer Zeit in einen seiner stabilen Zustände. Die Dauer dieses Zustands ist eine Zufallsvariable. Und es wurden viele Experimente und Simulationen durchgeführt, um das Verhalten dieser Zufallsvariable abzuschätzen. Keiner dieser Versuche ist jedoch wirklich belastbar, da das Verhalten von der Fertigungstechnik des Siliziums, der Temperatur, dem Rauschpegel durch Übersprechen und vielem mehr abhängt.

Also noch einmal: Auch wenn es bequem wäre, eine spezifizierte Maximalzeit zu haben, die ein Flip-Flop im metastabilen Zustand nie überschreitet, gibt es eine solche Grenze nicht. Es ist nicht einmal möglich, einen ungefähren Wert anzugeben, denn neuere Fertigungsprozesse erzeugen Flip-Flops, die den metastabilen Zustand schneller verlassen.

Stattdessen ist das die Vorstellung, an die man sich gewöhnen muss: Wenn man mit nicht verwandten Takten zwischen Taktdomänen wechselt, besteht immer eine Chance, dass ein Flip-Flop länger im metastabilen Zustand bleibt, als der Entwurf verkraften kann, und dass dadurch etwas schiefgeht. Das Einzige, was wir als Entwickler tun können, ist, dieses Risiko zu verringern. Wir können bestenfalls eine MTBF (Mean Time Between Failure) erreichen, mit der man leben kann.

Zum Glück gibt es eine etablierte Technik, genau das zu erreichen; damit kommen wir zum nächsten Thema.

Der Metastabilitäts-Guard

Um es kurz zu machen, schauen wir uns noch einmal das erste Codebeispiel von der vorherigen Seite an. Wenn @clk1 und @clk2 nicht verwandte Takte sind, ist die übliche Resynchronisationslogik, um aus @foo ein stabiles @bar zu gewinnen, Folgende:

reg foo, bar, bar_metaguard;

always @(posedge clk1)
  foo <= !foo;

always @(posedge clk2)
  begin
    bar_metaguard <= foo;
    bar <= bar_metaguard;
  end

Wie der Name andeutet, ist @bar_metaguard ein Metastabilitäts-Guard (metastability guard). Die Timing-Anforderungen des Flip-Flops, das @bar_metaguard implementiert, werden beim Abtasten von @foo gelegentlich verletzt. @bar_metaguard kann also kurze Momente der Metastabilität aufweisen. Da dieses Flip-Flop voraussichtlich schnell aus diesem Zustand zurückkehrt, wird es schnell genug stabil, um die Setup-Anforderung von @bar zu erfüllen. @bar kann also innerhalb der Taktdomäne von @clk2 zuverlässig verwendet werden.

Diese Erklärung klingt möglicherweise ungenau, und das ist sie auch. Sie widerspricht sogar dem, was ich oben über Metastabilität geschrieben habe, denn es gibt keine wirklich ausfallsichere Lösung gegen Metastabilität. Darauf komme ich weiter unten zurück. Aber jetzt bleiben wir erst einmal bei der üblichen Praxis: Metastabilitäts-Guard wie oben hinzufügen und sich keine weiteren Sorgen machen. Und um die Wahrheit zu sagen: Ich habe nie von jemandem gehört, der damit Probleme hatte.

Wer ganz besonders sicher gehen will, fügt weitere Register hinzu. Ein doppelter Metastabilitäts-Guard sieht dann etwa so aus:

reg foo, bar, bar_metaguard_a, bar_metaguard_b;

always @(posedge clk1)
  foo <= !foo;

always @(posedge clk2)
  begin
    bar_metaguard_a <= foo;
    bar_metaguard_b <= bar_metaguard_a;
    bar <= bar_metaguard_b;
  end

@bar_metaguard_a ist der erste Metastabilitäts-Guard. Wenn wir Pech haben, bleibt er zu lange im metastabilen Zustand, und die Timing-Anforderungen von @bar_metaguard_b werden verletzt. Infolgedessen hat @bar_metaguard_b im nächsten Taktzyklus eine Phase der Metastabilität. Diesmal ist die Phase hoffentlich kurz: Der Blitz schlägt bekanntlich nicht zweimal an derselben Stelle ein. Aber natürlich kann auch @bar_metaguard_b lange genug metastabil bleiben, um das Timing von @bar zu verletzen. Doch wie groß ist die Wahrscheinlichkeit dafür? Denken Sie daran, dass das Ziel eine vernünftige MTBF ist.

Zusammenfassung: Wenn Sie nach der Kochrezept-Lösung suchen, um mit einem einzelnen Bit eine Taktdomäne zu wechseln, dann ist sie das. Ein oder zwei Metastabilitäts-Guards erledigen die Aufgabe zuverlässig. Wenn Sie nach einer Lösung suchen, die niemals versagt, ist das leider unmöglich. Wenn Sie die Wahrscheinlichkeit von Pannen aber so weit wie möglich reduzieren wollen, lesen Sie weiter.

Timing-Analyse der Metastabilität

Kommen wir nun zu dem obigen Beispiel mit einem einzelnen Metastabilitäts-Guard zurück und fragen, warum ich so sicher war, dass @bar_metaguard schnell genug aus der Metastabilität zurückkehrt. Die Antwort ist, wie bereits erwähnt: Es gibt keinen Grund, sicher zu sein.

Trotzdem wollen wir eine kleine Timing-Analyse machen. Sowohl @bar_metaguard als auch @bar sind mit demselben Takt synchron. Unter der Annahme, dass für diese Register keine besonderen Timing-Constraints gesetzt sind, wird der Pfad zwischen ihnen durch die Timing-Constraint von @clk2 (Taktperiode) begrenzt. Mit anderen Worten: Die Tools stellen sicher, dass in Abwesenheit von Metastabilität das Eingangssignal von @bar mit korrektem Timing abgetastet wird.

Aber der ganze Sinn von @bar_metaguard war ja, dass es sich gelegentlich Metastabilität erlauben darf. Die Timing-Analyse reicht also nicht aus: In Wirklichkeit addiert sich die Zeit, die das Flip-Flop im metastabilen Zustand verbringt, zu seiner Clock-to-Output-Zeit. Metastabilität bedeutet also, dass der Ausgang des Flip-Flops später stabil wird, als es der üblichen Verzögerung entspricht. Wenn der Pfad zwischen @bar_metaguard und @bar also einen nahezu verschwindenden Slack (slack) hat – das heißt, seine Ausbreitungsverzögerung (propagation delay) reicht gerade so für das Timing, aber ohne Reserve –, kann Metastabilität die Gesamtverzögerung des Pfads über das erlaubte Limit bringen. Ein solches Ereignis würde eine Timing-Verletzung am Eingang von @bar verursachen. Das gilt natürlich für den ungünstigsten Fall von Temperatur, Spannungen und Fertigungsprozess, und trotzdem: Die Tatsache, dass die Tools die gewöhnliche Timing-Constraint auf den Ausgang des Metastabilitäts-Guards anwenden, bedeutet nicht, dass die nötigen Timing-Anforderungen erfüllt sind.

Zum Glück ist das leicht zu beheben. Oder besser gesagt: zu verbessern. Die Methode besteht darin, auf allen Pfaden von den Metastabilitäts-Guards zu den Registern, die zuverlässig sein müssen, eine strengere Timing-Constraint zu setzen. Die Idee ist also, auf diesen Pfaden weniger Ausbreitungsverzögerung zu erlauben. Indem man die für die Ausbreitungsverzögerung erlaubte Zeit verringert, gewinnt man diese Zeit für die Erholung von der Metastabilität.

Wenn zum Beispiel alle Metastabilitäts-Guard-Register das Suffix *_metaguard tragen, kann eine einzige Timing-Constraint geschrieben werden, die verlangt, dass alle Pfade, die von ihnen ausgehen, etwas zusätzliche Zeit zum Stabilisieren bekommen. Für Vivado würde eine solche Constraint so lauten:

set_max_delay -from [ get_cells -hier -filter {name=~*_metaguard*} ] 0.75

(set_max_delay wird auf der Seite über Timing-Ausnahmen erklärt)

Diese Timing-Constraint bedeutet, dass jeder Pfad, der an einem Metastabilitäts-Guard beginnt, 0,75 ns Zeit hat, um sein Ziel zu erreichen (diese Pfade werden über das Suffix der Registernamen ausgewählt). Das ist die Mindestverzögerung, die ich erreichen konnte, ohne die Timing-Constraint auf einem bestimmten FPGA zu verletzen, an dem ich das ausprobiert habe (auf anderen FPGAs kann das also anders sein). Um auf diesen Wert zu kommen, wählt man eine kleinere Zahl, bis die Tools die Constraint nicht mehr erfüllen können. Bei diesem Fehlschlag erhöht man den Wert so weit, dass die Constraint mit einem sehr kleinen Slack (z. B. 0,2 ns) erfüllt wird. Das zwingt die Tools, auf diesen Pfaden ihr Bestes zu geben.

Dieser set_max_delay-Befehl kommt einer geforderten Taktperiode von 0,75 ns (1333 MHz) gleich. Wenn die tatsächliche Taktperiode also zum Beispiel 250 MHz (4 ns) beträgt, ergibt sich bei Erfüllung dieser Timing-Constraint ein Überschuss von mindestens 4 – 0,75 = 3,25 ns. Bleiben die Metastabilitäts-Guards also bis zu 3,25 ns im metastabilen Zustand, entsteht dank dieser set_max_delay-Constraint keine Timing-Verletzung.

Das ist natürlich besser als gar keine Sicherheit, aber reichen 3,25 ns? Ist das viel? Reicht das, um einen praktisch ausfallsicheren Übergang zwischen Taktdomänen zu gewährleisten?

Wie bereits gesagt, hängt die Antwort davon ab. Nach veröffentlichten Experimenten zu urteilen, ist es nach meinem persönlichen Eindruck äußerst unwahrscheinlich, dass ein Flip-Flop auf einem heute praktisch verwendeten FPGA auch nur annähernd 1 ns im metastabilen Zustand bleibt. Aber zu diesem Thema kann man kaum etwas mit Sicherheit sagen.

Indem Sie diese Timing-Constraint hinzufügen und die Tools damit zu ihrem Besten anspornen, bringen Sie Ihren FPGA-Entwurf auf den gleichen Stand wie alle anderen – oder sogar darüber hinaus. Also, im Geiste der Goldenen Regel Nr. 4 (man soll das Glück nicht herausfordern): Bringen Sie sich in die Lage, dass im Falle eines Fehlers auch viele andere Leute Grund zur Beschwerde hätten.

Eine weitere Erkenntnis aus dieser Timing-Diskussion ist, dass ein doppelter Metastabilitäts-Guard eine gute Idee sein kann, wenn die Taktfrequenz für das verwendete FPGA hoch ist. Das gilt besonders, wenn die oben vorgeschlagene zusätzliche Timing-Constraint nicht verwendet wird: Wie bereits erwähnt, verbraucht Metastabilität den Slack des Pfads. Je kleiner die Taktperiode wird (je höher die Frequenz), desto weniger Slack bleibt oft übrig, und damit weniger zusätzliche Zeit für Metastabilität.

Abschließend wollen wir fragen, wie es kommt, dass praktisch alle das ganze Problem des Timings ignorieren und trotzdem niemand über Probleme klagt. Dafür habe ich ein paar mögliche Erklärungen, aber das sind nur Spekulationen.

Die erste Erklärung ist, dass die Tools den Metastabilitäts-Guard physisch sehr nahe an das andere Flip-Flop auf der Logikstruktur des FPGAs platzieren. Die Ausbreitungsverzögerung zwischen diesen beiden Flip-Flops (z. B. von @bar_metaguard zu @bar) ist also in Bezug auf dieses FPGA ohnehin sehr kurz. Diese enge Platzierung ist jedoch ohne explizite Timing-Constraints nicht sichergestellt, wie oben besprochen.

In den offiziellen FIFOs, die von den FPGA-Herstellern bereitgestellt werden, findet man solche Flip-Flop-Paare oft auf derselben Slice (slice). Wie auch immer: Den offiziellen FIFOs kann man vertrauen, dass dieses Problem geklärt ist.

Ein weiterer Punkt ist, dass mit schneller werdenden FPGAs und höheren möglichen Taktfrequenzen auch die Flip-Flops schneller aus der Metastabilität herauskommen. So bleibt die Zeit für die Erholung von der Metastabilität plus die Ausbreitungsverzögerung weiterhin deutlich kürzer als die Taktperiode.

Es ist also keine Überraschung, dass die meisten einfach den Metastabilitäts-Guard einbauen und sich nicht weiter darum kümmern. Das ist jedoch keine Entschuldigung dafür, beim Hinzufügen einer Constraint nachlässig zu sein.

Grenzen der Taktfrequenzen

Ich habe bisher wenig über die Frequenzen der Takte in den beiden Taktdomänen gesagt. Was passiert zum Beispiel, wenn der Takt in der Taktdomäne der Quelle (@clk1) schneller ist als der Takt des Ziels (@clk2)?

Da keine Synchronisation zwischen den Takten erforderlich ist, spielt es an sich keine Rolle, ob die Frequenz der Quelle höher oder niedriger ist. Wenn sich aber das Signal der Quelle (@foo) zu schnell ändert, könnte es hin- und herspringen, bevor das Flip-Flop am Ziel die Gelegenheit hat, diese Änderung abzutasten. Wenn also alle Übergänge am Ziel sichtbar sein müssen, muss die Taktperiode der Quelle etwas länger sein (also eine niedrigere Frequenz haben) als die Taktperiode des Zieltakts.

Wie viel länger? Nicht viel. Oder wenn Sie es genau wissen wollen: Das hängt in erster Linie von den Timing-Anforderungen des Flip-Flops am Ziel ab. Hier ist eine kurze Rechnung (überspringen Sie sie, wenn sie Sie nicht wirklich interessiert):

Wenn sich der Eingang dieses Flip-Flops genau tsu vor dessen Taktflanke ändert, ist das gerade noch an der Grenze zum ordnungsgemäßen Abtasten; die Änderung muss also im nächsten Taktzyklus abgetastet werden. Andernfalls wurde diese Änderung womöglich verpasst. Damit diese Änderung im nächsten Taktzyklus zuverlässig abgetastet werden kann, muss das Eingangssignal bis thold nach der nächsten Taktflanke stabil bleiben. Die Taktperiode der Quelle muss also um tsu + thold + 2tj länger sein als die Taktperiode des Ziels. tj ist die Unsicherheit der Taktperiode (Jitter, engl. jitter). Der Jitter wird doppelt gezählt, einmal für den Takt der Quelle und einmal für den Takt des Ziels.

tsu + thold + 2tj ist also das, was ich vorhin mit einer „etwas längeren“ Taktperiode für den Quelltakt meinte.

Die Tools über die Metastabilitäts-Guards informieren

Die Dokumentation mancher FPGA-Tools empfiehlt, Flip-Flops, die als Metastabilitäts-Guards dienen, mit einem Attribut zu versehen. Das verhindert, dass die Tools diese Flip-Flops verändern. Außerdem regt es die Tools an, den Metastabilitäts-Guard auf dieselbe Slice (slice) zu legen wie das nächste Flip-Flop, damit das Routing so kurz wie möglich ist.

Vivado hat zum Beispiel ein Attribut namens ASYNC_REG. Wenn also – wie zuvor – alle Metastabilitäts-Guard-Register das Suffix *_metaguard haben, setzt diese Zeile in der XDC-Datei das Attribut wie nötig:

set_property ASYNC_REG true [get_cells -hier -filter {name=~*_metaguard*}]

Es ist auch möglich, dieses Attribut im Verilog-Code zu setzen.

Dieses Attribut dürfte allerdings kaum einen Unterschied machen, weil die Tools die Metastabilitäts-Guards ohnehin normalerweise korrekt behandeln.

Damit endet die zweite Seite in dieser Serie. Die nächste Seite zeigt Techniken, wie man Daten über Taktdomänen hinweg überträgt.

Diese Seite wurde maschinell aus dem Englischen übersetzt. Im Zweifelsfall siehe den Originaltext.
Copyright © 2021-2026. All rights reserved. (dcc38493)