01signal.com

Die Logik für das richtige Hochfahren und Zurücksetzen eines FPGA

Dies ist die dritte und letzte Seite einer Serie über Resets in FPGAs. Es wird empfohlen, zuerst die ersten beiden zu lesen.

Überblick

Es ist immer eine gute Idee, sich die Zeit zu nehmen, eine ordentliche Logik zum Erzeugen von Resets aufzubauen. Selbst wenn es beim aktuellen Design kein offensichtliches Problem gibt, wird das Überspringen dieser Designphase vermutlich seinen Tribut fordern: Später verbringen Sie vielleicht Tage damit, ein Stabilitätsproblem zu lösen, ohne zu erkennen, dass die Logik nicht ordnungsgemäß initialisiert wurde. Bei den Versuchen, solche Probleme im Laufe der Zeit zu beheben, sammelt das Design hässliche Workarounds an, die entstehen, ohne die Wurzel des Problems zu verstehen. Mehr dazu auf dieser Seite über merkwürdige Instabilitäten.

Auch ist es wichtig, die Reset-Signale von Projektbeginn an durchzudenken und zu entwerfen, um sicherzustellen, dass das Projekt ordentlich wächst, wenn neue Funktionalitäten hinzukommen. Wenn ein FPGA-Projekt von Grund auf gestartet wird, wird oft zuerst die Kernfunktionalität implementiert, und mit der Zeit kommen dann weitere Features hinzu. Da verschiedene Module oft getrennte Takte und Resets benötigen, tappt man leicht in die Falle, für jeden Teil schnell etwas zusammenzustricken. Das macht das Projekt im Verlauf oft immer chaotischer.

Ein unordentliches Projekt lässt sich leichter vermeiden, wenn es ein zentrales Modul gibt, das von Anfang an sauber geschrieben ist und Resets und Takte enthält. Und wie weiter unten erklärt wird, beeinflussen sich der Reset-Controller und die Taktressourcen (PLLs und Taktpuffer, falls erforderlich) gegenseitig. Daher zahlt es sich aus, sie in dasselbe Modul zu legen. Ich gebe diesem Modul üblicherweise den Namen clkrst.v.

Jedoch ist es oft nicht möglich, das alles in einem Modul zu konzentrieren, da einige IP-Cores (IP cores), Subsysteme oder Designblöcke ihre eigene Gruppe von Resets und Takten erzeugen können. Wenn das der Fall ist, muss man sorgfältig darüber nachdenken, was wovon abhängt und wie das Gesamtsystem auf Reset-Anforderungen aus verschiedenen Quellen reagieren soll. Zum Beispiel kommt der Reset für einen PCIe-Block fast immer vom Bus selbst, und der Block erzeugt ein Reset-Signal zur Verwendung durch die Logik, die mit diesem Block verbunden ist. In solchen Situationen muss man beispielsweise überlegen, wie das System allgemein reagieren soll, wenn ein Reset vom PCIe-Bus kommt (einschließlich der Möglichkeit, überhaupt nicht zu reagieren).

Da jedes Projekt seine eigene Geschichte hat, gibt es keine einzige Lösung für alle Fälle. Diese Seite beschreibt Konzepte und schlägt Ideen und Codefragmente vor, die als Bausteine für Ihren eigenen Reset-Controller dienen können. Diese Codefragmente sind jedoch nicht zum direkten Kopieren und Einfügen in ein Projekt gedacht, sondern als Demonstrationsbeispiele zu verstehen.

Der Einfachheit halber nehme ich an, dass kein anderer Block im System weder Takte noch Resets erzeugt; den Controller auf den allgemeinen Fall zu erweitern, ist jedoch ziemlich unkompliziert.

Die Reset-Zustandsmaschine

Bei den meisten Designs gibt es zwei Hauptszenarien, die ein Zurücksetzen erfordern:

Zusätzlich kann ein Reset erforderlich sein, wenn Watchdog-Timer ablaufen oder auf andere Weise ein schwerwiegender Systemfehler erkannt wird.

Die erwartete Reaktion auf alle diese Szenarien ist ein grundlegender Neustart, der sicherstellt, dass – egal was schiefgegangen ist – der Fehler behoben wird. Das lässt sich oft am besten mit einer Art Zustandsmaschine (state machine) erreichen, die eine konsistente und wiederholbare Reset-Sequenz gewährleistet, unabhängig vom Auslösegrund.

Allerdings gibt es Szenarien, in denen ein lokaler, möglicherweise wiederkehrender Reset Sinn ergibt. Beispielsweise kann Logik, die Videobilder verarbeitet, vor dem Beginn jedes Bildes zurückgesetzt werden. Das erfordert typischerweise einen leichten Mechanismus, der im Wesentlichen darin besteht, mehreren Registern Anfangswerte zuzuweisen, wenn ein lokaler synchroner Reset (synchronous reset) aktiviert wird. Das ist ein Reset-Mechanismus wie jeder andere und oft eine saubere und einfache Möglichkeit, einen robusten Betrieb sicherzustellen.

Aber da es zu dieser Möglichkeit nicht viel mehr zu sagen gibt, konzentriert sich der Rest dieser Seite auf den grundlegenden Reset, der das gesamte FPGA abdeckt.

Instabile Takte und die Notwendigkeit eines Resets

Es ist recht üblich (und meistens auch empfehlenswert), die PLLs des FPGA selbst zur Erzeugung von Takten zu verwenden. Aber diese PLLs beginnen normalerweise gleichzeitig zu arbeiten, wenn die gesamte übrige Logik im FPGA aktiv wird. Dadurch wird Logik, die von diesen Takten abhängt, mit einem instabilen Takt versorgt, dessen Frequenz deutlich höher als normal sein kann.

Wenn das passiert, ist das Timing der betreffenden Logikpfade (paths) nicht garantiert. Daher sollte jede Logik, die auf von PLLs erzeugten Takten beruht, so behandelt werden, als hätte sie die Timing-Constraints (timing constraints) nicht erfüllt, bis die PLL ihren Lock erreicht hat.

Die angemessene und übliche Lösung besteht darin, alle solche Logik so lange im Reset zu halten, bis die betreffende PLL ihren Lock erreicht hat. Alternativ kann diese Logik so eingestellt werden, dass sie den Takt ignoriert, bis die PLL ihren Lock erreicht hat, z. B. indem sichergestellt wird, dass der Taktfreigabeeingang (clock enable) der Flip-Flops – also CE – inaktiv ist.

Wenn ein externer Takt direkt vom Pin des FPGA zu seinen Logikelementen verbunden ist, stellt sich die Frage, ob der Taktgenerator (typischerweise eine PLL) seinen Lock schneller erreicht hat, als das FPGA mit seinem Bitstrom geladen wurde. Das ist nicht immer leicht zu beurteilen.

Folglich müssen in den meisten Designs viele der synchronen Elemente zurückgesetzt werden. Oder genauer gesagt: Bei der Frage, ob wegen eines instabilen Takts ein Reset nötig ist, kann man diese Elemente in drei Gruppen einteilen:

Erwähnenswert ist, dass einige FPGAs es erlauben, den Konfigurationsprozess (also das Laden des Bitstroms und die Initialisierung des FPGA) so einzurichten, dass das Aufwachen des FPGA verzögert wird, bis alle PLLs ihren Lock erreicht haben. Das könnte ein Weg sein, dieses Problem zu lösen. Der Nachteil dieser Lösung ist, dass das FPGA bei einem Problem mit dem externen Takt überhaupt nicht startet. Eine solche Situation kann sehr verwirrend sein.

Eine einfache Zustandsmaschine

Da der grundlegende Start oder Neustart eher seltene Ereignisse sind, spielt es keine Rolle, wenn es ein paar Mikrosekunden länger dauert als nötig. In vielen Fällen sind sogar bis zu etwa 100 ms in Ordnung, und das kann man sich zunutze machen. Ein einfacher Zähler ist daher ein simpler Weg, eine Sequenz von Ereignissen zu implementieren.

In seiner einfachsten Form läuft es auf etwa Folgendes hinaus:

reg [4:0] reset_count;
reg       rst_src_pll, rst_src_shreg, rst_src_debounce;
reg       clear_counter;
reg       master_reset;

initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;

    master_reset <= (reset_count != 31);

    if (clear_counter)
      reset_count <= 0;
    else if (reset_count != 31)
      reset_count <= reset_count + 1;
  end

@rst_src_pll, @rst_src_shreg und @rst_src_debounce repräsentieren verschiedene Gründe, das System zurückzusetzen. Diese Register erhalten ihre Werte von einer anderen Logik. Ich werde ein paar Beispiele für solche Logik durchgehen, aber im Moment ist der wichtige Punkt, dass diese Register synchron zu @wakeup_clk sind (es ist also kein Taktdomänenwechsel (clock domain crossing) nötig).

@clear_counter ist das logische ODER dieser Reset-Gründe. Dieses Register setzt @reset_count auf Null. Andernfalls zählt dieser Zähler in diesem Beispiel von 0 bis 31 und stoppt danach.

Schließlich ist @master_reset aktiv, solange @reset_count noch nicht fertig gezählt hat. Das ist die einfachste Form einer Reset-Zustandsmaschine, und bis 31 zu zählen ist recht bescheiden.

Wenn also eines der @rst_src_N-Signale aktiv ist – selbst nur für einen einzigen Taktzyklus –, bleibt der synchrone Reset (synchronous reset) 31 Taktzyklen lang aktiv.

Ein langer Reset-Puls hat zwei Vorteile: Erstens: Wenn das zugehörige @rst_src_N zufällig ein- und ausschaltet (zum Beispiel durch zitternde PLL-Lock-Detektoren, Taster oder Software, die mehrfach Resets anfordert), pflanzen sich diese Mehrfachaktivierungen nicht auf den sichtbaren synchronen Reset fort. Solche Mehrfachaktivierungen sind zwar normalerweise harmlos, können aber unnötige Aktivität an Ausgangspins verursachen. Das kann negative Auswirkungen haben, etwa eine Person bei der Elektronikprüfung zu der Annahme zu verleiten, dass etwas nicht stimmt.

In diesem Sinne ist das Beispiel mit 31 Taktzyklen ziemlich minimalistisch. Besser ist es, bis zu einem Wert zu zählen, der 10–100 ms entspricht, sofern diese Verzögerung akzeptabel ist. Dadurch werden alle Zittervorgänge, die kürzer sind, vom Reset-Controller verborgen.

Der zweite Grund für einen langen Reset-Puls ist, dass die Verteilung des ursprünglichen synchronen Resets in lokale Kopien (wie zuvor erklärt) eine Verzögerung von einem Taktzyklus mit sich bringt. Wenn die Verteilung des Resets über die Logik ein mehrfaches Kopieren des Signals erfordert, wird die Gesamtverzögerung nicht nur länger, sondern möglicherweise auch ungleichmäßig. Ein langer Reset-Puls stellt sicher, dass zu irgendeinem Zeitpunkt die gesamte Logik einem aktiven synchronen Reset ausgesetzt ist. Ungleichmäßige Verzögerungen im Reset-Pfad (path) sind trotzdem keine gute Idee, weil dann auch die Deaktivierung des Resets ungleichmäßig wird, aber manchmal ist das kein Problem. 31 Taktzyklen für den Reset-Puls sind übrigens viel länger als wahrscheinlich nötig – schaden tut es aber nicht.

Resets für andere Taktdomänen

@master_reset ist ein gewöhnlicher synchroner Reset, aber er ist mit einem Takt synchronisiert, der sich von den Takten der Anwendungslogik unterscheiden kann. Um synchrone Resets für andere Takte zu erzeugen, sollte für jeden Takt etwa Folgendes gemacht werden:

reg reset_clk_pre1, reset_clk_pre2;
reg reset_clk;

always @(posedge clk)
  begin
    reset_clk <= reset_clk_pre2;
    reset_clk_pre2 <= reset_clk_pre1;
    reset_clk_pre1 <= master_reset;
  end

Dabei handelt es sich nur um einen gewöhnlichen Taktdomänenwechsel (clock domain crossing) mit drei Stufen, der @reset_clk erzeugt. Dieses Signal ist der synchrone Reset, der zu @clk gehört. Zwei Stufen wären eigentlich genug, aber da es ein wichtiges Signal ist, habe ich ihm ein zusätzliches Register gegönnt, um ganz sicherzugehen.

Der Takt des Reset-Controllers

Es gibt im Prinzip drei Arten von Takten, die als @wakeup_clk verwendet werden können, also für die Reset-Zustandsmaschine:

Die erste Option lässt sich am einfachsten handhaben. Da es fast immer einen Referenztakt gibt, der die PLLs des FPGA speist, kann dieser Referenztakt oft direkt als Aufwach-Takt verwendet werden. Es ist jedoch wichtig zu überprüfen, dass der Takt tatsächlich stabil ist, wenn das FPGA aufwacht – also, dass die Zeit, die das FPGA zum Laden des Bitstroms benötigt, länger ist als die Zeit, die der externe Oszillator braucht, um einen gültigen Takt zu erzeugen. Laut Datenblättern ist das meist mit großem Abstand der Fall. Wenn die Einschaltsequenz der Platine jedoch nicht richtig geplant ist, kann es sehr gut sein, dass das FPGA die Freigabe zum Lesen seines Bitstroms lange bevor die Versorgungsspannung des Oszillators den korrekten Pegel erreicht hat.

Die zweite Option besteht darin, einen Takt zu verwenden, der von einer PLL auf dem FPGA selbst erzeugt wird. Der offensichtliche Vorteil ist, dass dieser Takt möglicherweise auch für die Anwendungslogik verwendet wird, was sowohl Taktressourcen als auch Energie spart. Bei dieser Wahl muss die Reset-Zustandsmaschine solange im Anfangszustand gehalten werden, bis der Takt stabil ist. Das lässt sich etwa so bewerkstelligen:

reg rst_src_pll;
reg rst_src_pll_pre;

initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_pll <= rst_src_pll_pre;
    rst_src_pll_pre <= !pll_locked;
  end

@pll_locked ist der Ausgang des Lock-Detektors der PLL, die @wakeup_clk erzeugt (aktiv High). Dieses Signal ist asynchron; es wird also zuerst mit @wakeup_clk synchronisiert und dann als einer der Gründe zum Aktivieren von @clear_counter verwendet, wie oben gezeigt (nämlich mit @rst_src_pll).

Ein weiterer Vorteil dieser Option ist: Wenn der Referenztakt vorübergehend instabil ist oder fehlt (insbesondere nach dem Einschalten der Platine), ist die Wahrscheinlichkeit groß, dass auch der Lock-Detektor instabil ist. Wenn @reset_count also bis zu einer großen Zahl zählt (sicherlich viel höher als 31), bevor @master_reset deaktiviert wird, besteht eine Chance, dass das FPGA zuverlässig im Reset bleibt, bis der Referenztakt brauchbar ist. Darauf verlassen kann man sich allerdings nicht.

Auf jeden Fall bedeutet die Tatsache, dass @wakeup_clk ein PLL-Ausgang ist, dass er Logik speist, bevor dieser Takt stabilisiert ist. Es ist daher nicht klar, ob die Zustandsmaschine in diesem Zeitraum ordnungsgemäß arbeitet. Man kann argumentieren, dass das keine Rolle spielt, weil der Takt irgendwann gut genug wird und dann ordentliche Resets erzeugt werden. Wenn nach einem Reset alles in der Vergangenheit liegt, wen kümmert es, was davor passiert ist?

Ein strengerer Ansatz besteht darin, die Resets so lange stabil aktiv zu halten, bis der Aufwach-Takt seinen Lock erreicht hat, damit das FPGA unmittelbar nach seiner Bitstrom-Konfiguration nicht seltsam reagiert. Dazu muss man darauf achten, mit diesem Takt nur sehr einfache Logik zu betreiben. Mit anderen Worten: Die Logik sollte so beschaffen sein, dass sie nicht nennenswert versagt, wenn der Takt eine höhere Frequenz als erwartet hat.

Um zu analysieren, was passiert, wenn @wakeup_clk vorübergehend eine zu hohe Frequenz hat, beachten Sie, dass @reset_count das einzige Vektor-Register in der Reset-Zustandsmaschine ist. Das bedeutet, dass alle anderen Flip-Flops im schlimmsten Fall ihren berechneten „nächsten Wert“ einen Takt zu spät übernehmen, weil das Timing verletzt ist. Insbesondere weil @pll_locked low ist, solange die PLL nicht eingerastet ist, wird @rst_src_pll bald dauerhaft high, und damit ist auch @clear_counter dauerhaft high. Wenn sich der D-Eingang (der nächste Wert) eines Flip-Flops nicht ändert, spielt die Taktrate keine Rolle.

Das einzige mögliche Problem betrifft daher @reset_count: Er könnte falsch weiterzählen, bis sein berechneter nächster Wert durch @clear_counter dauerhaft auf Null gehalten wird. Wenn sein aktueller Wert zum Beispiel 3 (binär 011) ist, ist der nächste berechnete Wert 4 (binär 100). Wenn aber wegen Timing-Problemen die beiden niederwertigsten Bits nicht übernommen werden und das dritte Bit dennoch übernommen wird, kann der Wert des Zählers stattdessen auf 7 (binär 111) springen.

Um das zu verhindern, werden die Anfangswerte der Registerkette von @pll_locked bis @clear_counter alle so vergeben, dass @reset_count zuverlässig auf Null gehalten wird. Wenn @pll_locked also bis zur Stabilisierung von @wakeup_clk dauerhaft low bleibt (wie es sein sollte), entfernt sich @reset_count nicht von Null, und @master_reset bleibt dauerhaft aktiv.

Was die letzte Option betrifft, nämlich den Ringoszillator des FPGA für den Reset-Controller zu verwenden: Ich habe es selbst nicht ausprobiert, daher bin ich mir nicht sicher, wie gut diese Idee ist. Aber falls es jemanden rettet, der keine andere Möglichkeit hat, geht das bei Xilinx-FPGAs mehr oder weniger so: Schlagen Sie in der Configuration User Guide Ihres FPGA nach einem Grundbaustein (primitive) namens STARTUPE2 (oder so ähnlich) nach. Er sollte einen Ausgang namens CFGMCLK haben, einen Takt von einem ungenauen Ringoszillator auf dem FPGA selbst. Seine Frequenz liegt bei etwa 50–65 MHz. Dieser Takt ist garantiert stabil, wenn das FPGA aufwacht. Ich würde die Timing-Constraint auf eine erheblich höhere Frequenz setzen (sagen wir 100 MHz).

Aber das wäre etwas, das ich nur tun würde, wenn es wirklich keine andere Wahl gäbe. Zum Beispiel, wenn der externe Referenztakt beim Aufwachen des FPGA nicht stabil ist und daher eine zusätzliche Verzögerung in Logik implementiert werden muss.

PLLs zurücksetzen

Es ist im Allgemeinen eine gute Idee, die PLLs als Teil der Reset-Sequenz zurückzusetzen. Das stellt sicher, dass sie zurückgesetzt werden, wenn ihr Referenztakt nachweislich gültig ist. Wenn der Reset außerdem vom Benutzer ausgelöst wird (z. B. durch Drücken eines Reset-Tasters), könnte das eine Reaktion auf ein Problem sein, das von einer nicht richtig eingerasteten PLL herrührt. Sollte nicht passieren, aber sicher ist sicher.

@clear_counter kann nicht zum Zurücksetzen der PLL verwendet werden, weil es aktiv wird, sobald eine PLL ihren Lock verliert. Würde man es verwenden, bliebe die PLL im Reset-Zustand, würde nie einrasten, und der Reset würde nie aufgehoben. Aus demselben Grund können die Resets der PLLs nicht von @reset_count abgeleitet werden: Er wird auf Null gehalten, außer wenn alle PLLs eingerastet sind.

Die Lösung besteht darin, ein separates Reset-Register für die PLL zu erzeugen, das @clear_counter ähnelt. Mit der obigen Notation, bei der nicht eingerastete PLLs durch @rst_src_pll repräsentiert werden, läuft das auf etwa Folgendes hinaus:

reg clear_counter;
reg reset_plls;

initial clear_counter = 1;
initial reset_plls = 1;

assign reset_sources = rst_src_shreg || rst_src_debounce;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || reset_sources;
    reset_plls <= reset_sources;

[ ... ]

In diesem Codefragment wurde @rst_src_pll aus @reset_sources ausgeschlossen und wird nur für @clear_counter verwendet. Dadurch werden die PLLs zusammen mit dem gesamten FPGA zurückgesetzt, außer als Folge davon, dass sie selbst nicht eingerastet sind.

Es ist erwähnenswert, dass @wakeup_clk selbst nicht von der zurückgesetzten PLL erzeugt werden kann; dieser Takt muss also auf Basis der anderen Möglichkeiten erzeugt werden.

Beachten Sie, dass @reset_plls nach dem hier gezeigten Code ebenfalls zufällig zwischen low und high wechselt, wenn @reset_sources das tut. Das ist normalerweise harmlos, denn PLLs nehmen keinen Schaden, wenn ihr Reset wild ein- und ausschaltet. Es gibt jedoch auch dafür eine Möglichkeit, wie im Folgenden erklärt wird.

Komplexere Startsequenzen

Die Verwendung eines einfachen Zählers (@reset_count) als Zustandsvariable macht es leicht, kompliziertere Startsequenzen zu implementieren. Zum Beispiel ist die Erzeugung ordnungsgemäßer asynchroner Resets (asynchronous resets), die bei abgeschalteten Takten aktiv sind, recht einfach: Man verwendet einfache Logikausdrücke, die die Zeitfenster definieren, in denen die Takte abgeschaltet sind und die Resets aktiv sind.

Diese Methode mit dem einfachen Zähler ist daher auch für Designs ein guter Ausgangspunkt, deren Anforderungen an die Reset-Zustandsmaschine zu Projektbeginn einfach wirken. Wenn sich später herausstellt, dass eine komplexe Startsequenz erforderlich ist, lässt sich die vorhandene Logik leicht erweitern, um dieses Ziel zu erreichen.

In jedem Fall läuft die Planung der Startsequenz darauf hinaus, die Zeiträume zu definieren, in denen jede Phase der Sequenz wirksam ist. Jeder solche Zeitraum wird in einen Wertebereich für @reset_count übersetzt.

Wenn das Design zum Beispiel zwei oder mehr nicht verwandte Takte (unrelated clocks) hat, ist es möglich, für jede Taktdomäne (clock domain) wie oben gezeigt Reset-Signale zu erzeugen (siehe „Resets für andere Taktdomänen“). Wenn diese Methode verwendet wird, verlässt jede Taktdomäne den Reset in praktisch zufälliger Reihenfolge. Das ist normalerweise kein Problem. Falls doch, kann der Reset jeder Taktdomäne zu einem definierten Zeitpunkt deaktiviert werden, basierend auf dem Fortschritt von @reset_count.

Es ist auch möglich, mehr als einen Zähler zu implementieren. Das ist nützlich, wenn die Reset-Sequenz erfordert, auf bestimmte Bedingungen zu warten, bevor es weitergeht. Wenn die Reset-Sequenz zum Beispiel das Zurücksetzen von PLLs umfasst, auf ihr Einrasten wartet und dann fortfährt, ist es sinnvoll, einen Zähler zu haben, der auf Null gehalten wird, bis alle PLLs eingerastet sind. Der zweite Zähler wird auf Null gehalten, bis der erste Zähler fertig gezählt hat.

Beachten Sie, dass die PLLs selbst nicht zurückgesetzt werden, wenn eine PLL ihren Lock verliert, wohl aber alles, was von ihnen abhängt. Das ist möglicherweise nicht das gewünschte Verhalten, denn eine PLL, die ihren Lock verliert, ist in den meisten Designs ein schwerwiegender Fehler. Um beim Verlust des Locks einen vollständigen Reset anzufordern, fügen Sie das unten definierte @pll_restart zu den Signalen hinzu, die per ODER zu @reset_sources zusammengeführt werden:

assign pll_restart = rst_src_pll && !master_reset;

Das bedeutet einfach: Wenn die PLL eingerastet war, nachdem der Master-Reset inaktiv war, und dann ihren Lock verliert, wird alles – einschließlich der PLLs – erneut zurückgesetzt. Dazu muss die Reset-Zähldauer lang genug sein, um mögliche Zittervorgänge der Lock-Detektoren zu überstehen. Mit anderen Worten: Der Reset-Zähler muss länger zählen als die Zeit, in der der Lock-Detektor der PLL high sein kann, obwohl die PLL noch nicht eingerastet ist. Das darf man nicht mit der Zeit verwechseln, die die PLL zum Erreichen des Locks benötigt. Manche Lock-Detektoren zittern überhaupt nicht, und diese Zittervorgänge sind höchstwahrscheinlich deutlich kürzer als die Lock-Zeit der PLL (laut Datenblatt).

Das Aufwach-Schieberegister

Es ist vielleicht ein bisschen übertrieben, aber ich füge in meinen Designs normalerweise ein Aufwach-Schieberegister dieser Art hinzu:

reg [15:0]   wakeup_shift;
reg          rst_src_shreg;

initial rst_src_shreg = 1;
initial wakeup_shift = 0;

always @(posedge wakeup_clk)
  begin
    rst_src_shreg <= !wakeup_shift[15];
    wakeup_shift <= { wakeup_shift, 1'b1 };
  end

Da die meisten FPGAs @wakeup_shift mit einem Schieberegister-Grundbaustein implementieren, der das Äquivalent einer LUT verbraucht, ist das ressourcengünstig und bietet einen weiteren Mechanismus, der sicherstellt, dass beim Einschalten ein Reset stattfindet. Das ist in einem Design ohne PLLs erforderlich, weil sonst nichts @clear_counter aktiviert. Aber selbst wenn PLLs vorhanden sind, besteht die Möglichkeit, dass sie bereits eingerastet sind, wenn das FPGA aufwacht, da dies bei manchen FPGAs eine Option für die Bitstrom-Konfiguration ist.

Also ist das in jedem Fall eine empfehlenswerte Ergänzung. Sicher ist sicher.

Externer Reset-Taster

Reset-Taster sind recht verbreitet. Was sie genau tun, unterscheidet sich von Design zu Design. Eine Möglichkeit ist, dass der Taster, den der Benutzer als „Reset“ betrachtet, mit dem FPGA-Pin verbunden ist, der den Prozess des Ladens des Bitstroms in das FPGA anstößt. Bei FPGAs mit eingebettetem Prozessor kann er auch mit dem Reset-Pin eines Prozessors verbunden sein.

Dieser Reset-Taster könnte auch mit einem allgemeinen I/O-Pin des FPGA verbunden sein, um die FPGA-Logik zurückzusetzen. In diesem Fall ist das ein weiterer Grund, @clear_counter zu aktivieren.

Wenn @reset_count bis zu einem Wert zählt, der 10 ms oder mehr entspricht, ist keine Entprellung (debounce) des Signals vom Eingangspin nötig, weil die Zittervorgänge vom Zähler selbst absorbiert werden. In diesem Fall reicht Folgendes aus:

reg rst_src_debounce;
reg rst_src_debounce_pre;

initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_debounce <= rst_src_debounce_pre;
    rst_src_debounce_pre <= reset_button_pin;
  end

Wenn der Zähler jedoch schnell fertig ist (wie im obigen Beispiel, das nur bis 31 zählt), muss der Taster entprellt werden. Es gibt mehrere Möglichkeiten, das zu tun, zum Beispiel:

reg [17:0] debounce_count = 0;
reg        reset_button_d, reset_button_d2, reset_button_d3;

wire       debounce_reached = (debounce_count == 250000);

initial debounce_count = 0;
initial rst_src_debounce = 1;

always @(posedge wakeup_clk)
  begin
    reset_button_d3 <= reset_button_d2;
    reset_button_d2 <= reset_button_d;
    reset_button_d <= reset_button_pin;

    if (reset_button_d2 != reset_button_d3)
      debounce_count <= 0;
    else if (!debounce_reached)
      debounce_count <= debounce_count + 1;

    if (debounce_reached)
      rst_src_debounce <= reset_button_d3;
  end

Das ist ein relativ strenger Entpreller (debouncer), der bei verrauschten Eingangssignalen nicht gut funktioniert. Je nach Bedarf ist das ein Vor- oder Nachteil.

Dieses Codebeispiel ist für einen 25-MHz-Takt geschrieben. Der Wert von @reset_button_pin wird nach @rst_src_debounce übernommen, wenn @reset_button_pin 10 ms lang denselben Wert hatte. Beachten Sie, dass @debounce_count in demselben Taktzyklus auf Null wechselt, in dem @reset_button_d3 seinen Wert ändert. Wenn sich @reset_button_d3 also ändert, wird er nicht sofort in @rst_src_debounce übernommen, sondern erst, nachdem er längere Zeit denselben Wert gehalten hat.

Zusammenfassung

Beim Entwurf der Initialisierung eines FPGA gibt es vieles zu bedenken. Diese Seite hat einige Konzepte und Ideen vorgestellt, aber es ist wichtig, sich vor Augen zu halten, dass die eigentliche Aufgabe darin besteht, die Ereignisse, die eine Aktion auslösen müssen, richtig zu erkennen. Ebenso wichtig ist es, die richtige Reaktion auf jedes dieser Ereignisse festzulegen, um die Logik zuverlässig in einen arbeitsfähigen Zustand zu bringen.

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