Man hört immer wieder vom Eintakten oder Einsynchronisieren von Signalen. Mache man das nicht, so heißt es, komme man in Teufels Küche.
Und es ist tatsächlich so: Wer irgendwelche (asynchronen) externen Signale in seinem FPGA-Design verwendet, wird früher oder später das Problem bekommen, dass "es eigentlich funktioniert". Aber einmal pro Stunde, oder Tag, oder Woche passieren eigenartige Sachen. Das Unangenehme dabei ist, dass die Fehlerforschung so unwahrscheinlich schwer ist, weil
1. Die Fehler nur selten und nicht unbedingt reproduzierbar auftreten,
und
2. Die Fehlerbilder gar nicht auf die eigentliche Fehlerursache hindeuten.
Was ist aber jetzt dieses Eintakten?
Fangen wir mit einem einfachen aber trotzdem treffsicheren mechanischen Modell an. Eine Kugel liegt in einer gebogenen Bahn und kann dort nur 2 definierte Zustände annehmen. Nennen wir diese Zustände '1' und '0'.
Die Kugel, die den Zustand des Flipflops darstellt, muß über den Berg von Low nach High "geschubst" werden. Es ist also eine Mindestenergie nötig, die zum Umschalten des Zustands verwendet wird.
Reicht die "Anschubs"-Energie nicht aus, dann kann es sein, dass die Kugel nicht ganz zum anderen Zustand überwechselt. Sie bleibt dann u.U. auf einem undefinierten, metastabilen Zustand, und findet erst nach einiger Zeit einen definierten Pegel.
Auch ein Flipflop kann 2 nur definierte Pegelzustände annehmen: High und Low. Die Energie, die zum zuverlässigen Umschalten vom einen in den anderen Zustand nötig ist, wird hier über die Zeit festgelegt, die das Signal vor und nach der Taktflanke stabil sein muß. Halt, sagt jetzt einer: elektrische Energie berechnet sich aus E = 1/2*C*U², da ist keine Zeit drin! Richtig, aber trotzdem muss die Eingangskapazität des Flipflops auf die Spannung für die nötige Energie aufgeladen werden. Und weil man die internen Widerstände vom Pin zum Flipflop nicht kennt und nicht messen kann, hat sich etabliert, dass man sagt: wenn am Eingangspin der Pegel mindestens x Picosekunden vor dem Taktsignal und y Picosekunden nachher stabil ist, konnte sich der Kondensator des Flipflops soweit aufladen, dass die Energie zum Umschalten sicher reicht. Diese Mindestzeiten heißen
Setup-Zeit tsu (Dateneingang stabil vor der Taktflanke) und
Hold-Zeit th (Dateneingang stabil nach der Taktflanke).
Werden diese Zeiten verletzt, kann es sein, dass der Ausgang (jeweils nach Ablauf der Clock-To-Output Zeit tco) keinen stabilen Zustand erreicht hat und erst wieder auf einen definierten Pegel zurückfinden muss. Dieses Stabilisieren geht relativ schnell, so dass bei der nächsten Taktflanke idR. bereits wieder ein definierter Pegel vorliegt. Bei aktuellen FPGAs sind deshalb bei Taktfrequenzen bis 200MHz keine metastabilen Effekte zu beobachten!
Jetzt könnte man ja sagen:
Dann ist ja alles gut, warum die ganze Aufregung?
Hier die Auflösung: in einem FPGA sind viele FFs an irgendwelche Eingänge angeschlossen. Und wenn dann im Zweifelsfall einige dieser FFs (ausgehend vom gleichen Eingangssignal) nach dem Stabilisieren je nach Platzierung oder Bauteiltoleranzen eine '1' haben und andere eine '0', dann kann man sich vorstellen, dass hier irgendwann mal was schiefgehen wird. Und zwar unabhängig von der Taktfrequenz!
Zum oberen FF kommt die Flanke des Eingangssignals früher, zum unteren FF später. Auch wenn das nur Bruchteile von ns sind, ergeben sich bei einem "gleichzeitigen" Takt durch Verletzung der Setup- und Hold-Zeiten an den FF-Ausgängen Out1 und Out2 u.U. unterschiedliche Zustände.
Im Besonderen gilt dies, wenn so ein asynchrones Eingangssignal zum Weiterschalten in einer State-Machine verwendet wird. Wenn hier 2 FFs einen unterschiedlichen Zustand einnehmen, nimmt evtl. die ganze State-Machine einen falschen Zustand ein.
Dieser sehr beliebte Fehler hat annähernd die selben Auswirkungen und Symptome wie metastabile Flipflops. Im Gegensatz zur Metastabilität, die erst ab >>300MHz eine Rolle spielt, kann der hier beschiebene Effekt aber bei jeder Taktfrequenz auftreten, also z.B. auch in einem schnarchlangsamen 1MHz-Design! Und er wird es garantiert irgendwann mal.
Kurz zusammengefasst: für der Übergang zwischen zwei Taktdomänen und allgemein zum Eintakten von externen asynchronen Signalen (Taster, Schalter, Encoder, ...) müssen diese Signale daher über zwei Flipflops eingetaktet werden. Dies kann z.B. so gemacht werden:
Hier der VHDL-Code dazu:
signal ff1_out : std_logic; signal ff2_out : std_logic; : process begin -- Einsynchronisieren wait until rising_edge(clk); ff1_out <= async_in; ff2_out <= ff1_out; end process; process begin wait until rising_edge(clk); if (ff2_out = '1') then -- Synchronisierten Eingang verwenden : end if; end process;
Schöner liest sich sowas allerdings, wenn ein Schieberegister (hier: insr) eingesetzt wird:
signal insr : std_logic_vector(1 downto 0); : process begin -- Einsynchronisieren wait until rising_edge(clk); -- Schieberegister insr <= insr(0) & input; end process; process begin wait until rising_edge(clk); if (insr(1) = '1') then -- Synchronisierten Eingang verwenden : end if; end process;
Daraus lässt sich dann auch schnell eine synchrone Flankenerkennung basteln. Dazu muss nur ein weiteres FF dazugehängt werden, damit Pegelwechsel erkannt werden können:
signal insr : std_logic_vector(2 downto 0); : process begin wait until rising_edge(clk); -- Schieberegister insr <= insr(1 downto 0) & input; end process; process begin wait until rising_edge(clk); if (insr(2 downto 1) = "10") then -- fallende Flanke von input end if; if (insr(2 downto 1) = "01") then -- steigende Flanke von input end if; end process;
Natürlich kann mit einem Schieberegister auch ein paralleler Bus eingetaktet werden:
bus : in std_logic_vector(7 downto 0); : type SRBusTyp is array (1 downto 0) of std_logic_vector(7 downto 0); signal srbus : FeldTyp; : process begin -- Schieberegister wait until rising_edge(clk); srbus <= srbus(0) & bus; end process; process begin wait until rising_edge(clk); if (srbus(1)(3)='1') then : end if; end process;
Allerdings ist es im Allgemeinen nicht sinnvoll, z.B. einen Datenbus oder einen Adressbus einzusynchronisieren. Beim Einsynchronisieren eines 4-Bit Datenbusses, der z.B. von "0000" nach "1111" wechselt, können zwischendurch auch z.B. die Zustände "0001", "0101", "1010", oder jeder der insgesamt 14 "ungültigen" Zustände auftreten.
Hier ist der bessere Weg, die Daten vom Bus mit einem Validierungssignal (z.B. steigende Flanke von Write#) in FFs zu übernehmen (also direkt in FFs eintakten), und nur das Write# Signal einzusynchronisieren. Daran anschließend wird dann eine synchrone Flankenerkennung auf dem Write# Signal zur Datenweitergabe oder -weiterverarbeitung verwendet:
db : in std_logic_vector(7 downto 0); -- Datenbus wrn : in std_logic; -- Schreibsignal, low-aktiv : signal intdb : std_logic_vector(7 downto 0); signal wrnsr : std_logic_vector(2 downto 0); : process begin -- Daten lokal mit wrn eintaken wait until rising_edge(wrn); intdb <= db; end process; process begin -- Schieberegister fuer Schreibsignal wait until rising_edge(clk); wrnsr <= wrnsr(1 downto 0) & wrn; end process; process begin -- synchrone steigende Flanke wrn wait until rising_edge(clk); if (wrnsr(2 downto 1) = "01") then -- jetzt kann der eingelesene intdb verwendet werden : end if; end process;
Nicht zu vergessen:
Das asynchronste Signal schlechthin ist der externe RESET.
Der gehört zwingend einsynchronisiert, denn sonst kann z.B. durch einen kurzen ESD-Spike das FPGA nur partiell zurückgesetzt werden. Am besten ist, nur dort einen Reset zu verwenden, wo wirklich einer nötig ist. Und dann den Reset einzusynchronisieren und nur synchron zu verwenden:
reset : in std_logic; -- Resetsignal : signal resetsr : std_logic_vector(1 downto 0); : process begin -- Einsynchronisieren wait until rising_edge(clk); resetsr <= resetsr(0) & reset; end process; process begin wait until rising_edge(clk); : -- normale Bearbeitung if (resetsr(1)='1') then : -- Reset-Bearbeitung, letzte Zuweisung im Prozess gilt. end if; end process;
Wieviele Flipflops sind nötig?
Solange die Taktfrequenz im
moderaten Rahmen (um oder unter 100MHz) ist, reicht es theoretisch aus,
wenn Signale mit 1 Flipflop einsynchronisiert werden. Allerdings kann da
eine gern verwendete Syntheseoption unsichtbar ganz kräftig in die
Suppe spucken: das Register Doubling. Die Flipflops im FPGA haben einen
maximalen Fan-Out, der besagt, wie viele Nachfolger (Gatter oder
Flipflops) angeschlossen werden dürfen. Sind mehr Nachfolger da,
verdoppelt die Synthese einfach das Flipflop und schließt das
Eingangssignal parallel an beide Flipflops an. Und genau das macht ja
die Probleme: ein asynchrones Signal, das mit unterschiedlichen
Laufzeiten an 2 Flipflops angeschlossen ist.
Das zweite Flipflop sorgt da für Klarheit: das erste Flipflop der Synchronisierungsstufe wird sicher nicht verdoppelt, weil sein Fan-Out locker ausreicht, um viele Nachfolger des evtl. verdoppelten zweiten Flipflops zu treiben. Die unterschiedliche Laufzeit macht hier kein Problem und kann von der Toolchain beherrscht werden, das dann nur noch eine Worst-Case Betrachtung nötig ist: wie lange dauert es schlimmstenfalls, bis alle Flipflops der zweiten Stufe einen stabilen Pegel sehen?
Bei externen Signalen wird für die erste Stufe gleich das Flipflop im IO-Block verwendet, so kann dann das Signal gleich danach auf evtl. verdoppelte Flipflops der zweiten Stufe weitergeführt werden.
Obacht!
Eines sollte man sich trotzdem immer im Hinterkopf behalten: eine metastabile Situation lässt sich durch keine wie auch immer geartete Maßnahme vermeiden. Jeder "Lösungsvorschlag" in dieser Richtung beruht stets auf einem Denkfehler, der das Auftreten der Metastabilität an irgendeiner Stelle ignoriert. Durch das Hintereinanderschalten von mehreren Abtaststufen (Flip-Flops) kann lediglich die Wahrscheinlichkeit und die Auswirkungen des Fehlers beliebig stark reduziert werden.