Warum müssen externe Signale einsynchronisiert werden?
Meist wird dabei auf das Hammerargument "Metastabilität" von Flipflops verwiesen und damit jede weitere Frage automatisch abgewürgt. In der Praxis ist Metastabilität aber weniger verbreitet als man denkt. Moderne FFs in FPGAs fangen sich nach kürzester Zeit wieder (<3ns) und haben dann am Ausgang einen stabilen Pegel. 333MHz? Da kann eigentlich nichts mehr schiefgehen. Damit sind wir also wieder bei der Frage:
Warum müssen externe Signale einsynchronisiert werden?
Externe Signale müssen einsynchronisiert werden, weil sie idR. zwar an einer Stelle (am Pin) ins FPGA hineingehen, dort aber dann im Routing wild hin- und herverdrahtet werden und u.U. über Kombinatorik an etlichen FFs ankommen. Und hier kann es dann durchaus sein, dass ein paar FFs bei der "nächsten" Taktflenke schon eine '1' erkennen, wenn andere noch meinen es gelte die vorhergehende '0'. Als Beispiel hier eine kleine State-Machine, die das verdeutlicht.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity FSM_AsyncInp is Port ( clk : in STD_LOGIC; inp : in STD_LOGIC; outp : out STD_LOGIC_VECTOR (1 downto 0)); end FSM_AsyncInp; architecture Behavioral of FSM_AsyncInp is signal state : std_logic_vector (1 downto 0) := "00"; begin process begin wait until rising_edge(clk); case (state) is when "00" => if (inp='0') then state<="01"; end if; when "01" => if (inp='1') then state<="10"; end if; when "10" => state<="00"; when others => Null; end case; end process; outp <= state; end Behavioral;
Ausgehend vom Zustand "00" geht es abhängig vom inp zum Zustand "01", von dort weiter zum Zustand "10", um danach wieder bei "00" anzufangen. Der Zustand "11" ist nicht definiert und kann eigentlich nicht auftreten. Soweit alles klar.
Wenn wir aber mal den erzeugten Plan dieser FSM ansehen, dann sind dort die beiden FF zu sehen und davor jeweils die Kombinatorik, die zum Weiterschalten verwendet wird. Hier ist das jeweils ein AND, auf das die entsprechenden Zustandssignale und der Eingang inp aufgeschaltet sind. Sehen wir uns mal den Zustand "01" genauer an:
Der Zustand ist stabil, solange am Eingang inp '0' anliegt. Dann wird über die beiden AND der Pegel an den Eingängen der FFs weiterhin auf "10" gehalten. Mit einem Wechsel des Eingangs inp nach '1' kurz vor einer steigenden Flanke des Taktsignals kann jetzt folgendes passieren:
Das AND B erkennt schon die '1' und schaltet an den Eingang des linken FF eine '1'.
Am AND A ist aber noch die '0'. Zusammen mit der '0' vom aktuellen Zustand ergibt das auch am Eingang des rechten FF eine '1'. Und genau jetzt kommt der Takt. Ergebnis: beide FF schalten eine '1' auf ihren Ausgang durch, die FSM hat den ungültigen Zustand "11" erreicht.
Der Folgezustand, der mit der nächsten Taktflanke eingenommen wird, ist danach übrigens nicht "00" sondern wieder "10", die FSM ist komplett aus dem Tritt geraten. Auch interessant ist, dass die FSM den Zustand "11" keineswegs hält (so etwas könnte man ja aus dem VHDL-Code ableiten), sondern irgendwie weitermacht.
Und natürlich ist auch jeder Zähler eine FSM. Auch so ein kleiner 8-Bit-Aufwärts-Zähler wird mit asynchronem Eingang garantiert wirr zählen und beliebige Sprünge machen. Denn auch hier wird das asynchrone ENABLE-Signal über unterschiedliche Wege an jedes einzelne der Flipflops geführt:
... ENABLE : in STD_LOGIC; ... signal cnt : integer range 0 to 255 := 0; -- 8-Bit-Zähler ... process(CLK) begin if rising_edge(CLK) then if ENABLE = '1' then cnt <= cnt+1; end if; end if; end process;
So können z.B. beim Übergang vom Zählerstand "01111111" zu "10000000" praktisch beliebige Folgezustände eingenommen werden. Denn bei ENABLE='0' müssen die Eingänge aller Flipflops des Zählers den aktuellen Zustand beibehalten und bei ENABLE='1' müssen die Eingänge aller Flipflops des Zählers auf den Komplementärwert umschalten.
Kommt das ENABLE-Signal nicht gleich- und rechtzeitig an allen Eingängen an, dann übernehmen einige Flipflops den neuen Wert, andere behalten den alten. Und damit sind dann x-beliebige Zählerstände möglich.
Und Achtung: der asynchronste Eingang schlechthin ist der Reset!
Bei der gern in der Literatur verwendeten Schreibweise
process(CLK, RESET) begin if RESET = '1' then ... elsif rising_edge(CLK) then ... end if; end process;
wird nämlich genau das oben Beschriebene passieren: wenn der Reset zu einem ungünstigen Zeitpunkt inaktiv, also weggenommen(!) wird, legen ein paar Flipflops einer FSM schon mit der Arbeit los, andere sehen noch einen aktiven Reset. Das gibt ein herrliches Durcheinander und sorgt für amoklaufende FSMs und Zähler.
Fazit: natürlich muss insbesondere auch der Reset einsynchronisiert werden!