Gated Clocks

Was ist eine Clock-Domain

Im FPGA gibt es ja Lookup-Tables, die erzeugen kombinatorische Signale und Register (Flip-Flops) für erzeugen getaktete Signale.

Die getakteten Signale stehen am Anfang eines Taktzyklus zur Verfügung, die kombinatorischen Signale werden durch die Laufzeiten durch die Lookup-Tables und die Verbindungen innerhalb des FPGAs erst zu einem späteren Zeitpunkt stabil.Je mehr Lookup-Tables hintereinander durchlaufen werden müssen um ein kombinatorisches Signal zu erzeugen, desto später wird dieses Signal stabil. Letztendlich wird dieses Ergebnis wieder in ein Register übernommen (oder auf einem Pin ausgegeben).

Eine Clock-Domain bezeichnet alle Register, die vom gleichen Takt betroffen sind. Hat man z. B. einen 100 MHz Verarbeitungstakt und einen 33 MHz IO-Takt, so hat man zwei Clock-Domains. 

Damit eine Schaltung nachvollziehbar funktioniert, ist es essentiell, dass alle kombinatorischen Signale rechtzeitig zur Verfügung stehen, bevor sie vom Register übernommen werden. Bei der 100 MHz Domain hat man also knapp 10 ns Zeit, bei der 33 MHz Domain die dreifache Zeit.

Bei der Übersetzung des FPGAs wird für jedes Signal überprüft, ob das Zutrifft und eine Warnung ausgegeben, wenn das nicht der Fall ist.

Wasist ein Latch

Jetzt muss man sich oft einen  Zustand merken, wenn ein bestimmtes Ereignis eintritt. Z. B. ich drücke einen Taster und möchte, dass jetzt ein Eingang gelatched also eingefroren wird.

p_bad : process (TriggerC)
begin
    if TriggerC = '1' then
        my_latch <= my_input;
    end if;
end process;

Wer noch nicht mit VHDL vertraut ist, sollte erst die SimpleLed und BlinkingLed Lektionen anschauen.

Im Prozess haben wir ein Latch welches my_input in my_latch speichert wenn der Trigger gesetzt ist.

Da nur TriggerC in der Sensitivitätsliste steht passiert folgendes:

  • Der prozess wird ausgeführt wenn sich TriggerC ändert.
  • Nur wenn TriggerC dabei auf '1' gewechselt hat (normalerweise also von '0' kommend) wird my_input übernommen.
  • Es wird also ein D-Register mit TriggerC als Taktsignal, my_input als Eingang und my_latch als Ausgang erzeugt.

Was sind gated Clocks

Genau das was wir eben erzeugt haben ist ein gated Clock. Das Taktsignal kommt nicht vom synchronen Taktgeber sondern aus einem normalen Gate (gated clocks kommt noch aus der Zeit, wo es nur ASICs gab).

Warum ist das schlecht?

  1. Es ist undefiniert
  2. Es versaut uns das Timing
  3. Es tut vielleicht nicht, was wir wollten.

1. TriggerC könnte ja aus einer Kaskade von Lookup-Tables kommen. Sagen wir mal es war '0', jetzt gibt es eine Bedingung, die es potentiell auf '1' setzt. Diese kommt schneller am Ausgang TriggerC an. Eine weitere Bedingung, die länger braucht, setzt es aber auf '0'. Also war in diesem Takt die Bedinung gar nicht gegeben. Trotzdem wird jetzt gelatched, weil ja TriggerC kurz gezuckt hat.

2. Jetzt ändert sich das Signal my_latch nicht mehr am Anfang des Zyklus der alten Clock-Domain, sondern eine neue Clock-Domain wurde geschaffen. Der zeitliche Bezug zur alten Clock-Domain ist aber weitgehend verloren gegangen, da jetzt der FPGA-Compiler eine zeitliche Unschärfe für diese neue Clock-Domain erkennen könnte (minimale Laufzeit für eine Änderung bis maximale Laufzeit für eine Änderung von TriggerC). Wenn jetzt my_latch einfach in der alten Clock-Domain Verwendung finden würde, wird man u. U. Timing-Fehler sehen, weil die Zeit von my_latch bis Ende 100 MHz nicht mehr 10 ns, sonderun u. U. nur 1 ns ist.

3. Für den unbedarften Leser steht ja im Prozess oben:  Wenn der Trigger aktiv ist übernimm das Signal. Was passiert aber, wenn der Trigger durch einen Tastendruck gesetzt wird, das Signal von einem Schiebeschalte kommt, dieser die Position während des Tastendrucks ändert und dann der Taster wieder losgelassen wird?

Die alte Position des Schiebeschalters wird gespeichert, der prozess wird je beim drücken und beim loslassen des Tasters aufgerufen, da sich nur dann TriggerC ändert. Bei Ereignis "Drücken" wird TriggerC zu '1' und dementsprechend das Signal übernommen. Beim "Loslassen" wird TriggerC aber zu '0' und bei '0' passiert ja nichts.

Wie löst man das Problem?

Ganz einfach durch Clock-Enable.

p_good : process (Clk)
begin
    if rising_edge(Clk) then
        if TriggerC = '1' then
            my_latch <= my_input;
        end if;
    end if;
end process;

Jetzt passiert nur noch etwas synchron zur Clock-Domain von Clk.

TriggerC ist jetzt ein Clock-Enable, da sich my_latch nur noch ändern kann, wenn TriggerC gesetzt ist. TriggerC erlaubt also dem Register zu arbeiten.

Damit haben wir alle Probleme von oben gelöst:

  1. TriggerC kann während des Taktzyklus so oft Zucken wie es will, zum Ende ist es aber stabil.
  2. Alle Signale sind synchron zu Clk, my_latch ist also zum Anfang des nächsten Zyklus stabil.
  3. Solange der Taster gedrückt wird, wird der eingang übernommen. Gespeichert wird also der letzte Zustand bevor man den Taster losgelassen hat.

Vorsicht bei kombinatorischen Prozessen!

Ein kombinatorischer Prozess ist ein Prozess, bei dem Ausgänge von Eingängen abgeleitet werden  und zwar logische Verknüpfungen. Es sind also keine Speicherelemente vorhanden. Der Prozess hat also kein Gedächtnis.

Dazu müssen alle einbezogenen Eingänge in der Sensitivitätsliste des Prozesses aufgeführt sein. Falls einer fehlt, wird dieser Eingang gelatched sobald sich ein anderer Eingang ändert. Das führt zu gated Clocks (und normalerweise unerwünschtem Verhalten)!

Deswegen verwende ich kaum kombinatorische Prozesse.