01signal.com

Timing-Vorgaben für Multicycle-Pfade

Diese Seite gehört zu einer Serie von Seiten über Timing. Die vorherigen Seiten haben die Theorie hinter Timing-Berechnungen erklärt, das Schreiben mehrerer Timing-Vorgaben gezeigt und die Prinzipien des Timing-Closure besprochen. Diese Seite behandelt Timing-Vorgaben für Multicycle-Pfade.

Einführung

Das Erste, was man über Multicycle-Pfade (multi-cycle paths) wissen sollte, ist, dass ihre Verwendung meist eine schlechte Idee ist. Auch wenn diese Seite erklärt, wie man Multicycle-Pfad-Vorgaben einsetzt, sollte die Schlussfolgerung sein, diese Technik insgesamt zu vermeiden. Es gibt Gründe für diese Technik in der ASIC-Welt, aber in einem FPGA-Design ist es meist besser, stattdessen einen Takt hinzuzufügen.

Das gesagt, schauen wir uns an, wann diese Art von Timing-Ausnahme überhaupt relevant ist.

Betrachten Sie dieses Beispiel für Verilog-Code:

   reg foo, bar;
   reg en, pre_en;

   always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;

	if (en)
	  begin
	     foo <= !foo;
	     bar <= foo;
	  end
     end

Beachten Sie, dass dieses Beispiel unvollständig ist: Vermutlich müssen Sie zu @pre_en und @en Synthese-Attribute hinzufügen. Sonst können wegen der Optimierungen des Synthesizers unerwartete Dinge passieren. Mehr dazu weiter unten.

@pre_en wechselt in jedem Taktzyklus zwischen '0' und '1'. @en macht dasselbe, mit einer kleinen Verzögerung.

Der Teil „if (en)" bedeutet, dass @en als Taktfreigabe (clock enable) für alles zwischen „begin" und „end" dient: Wenn @en niedrig ist, passiert in diesem Teil des Verilog-Codes nichts. Mit anderen Worten: @foo und @bar verhalten sich so, als gäbe es die Taktflanke nicht, wenn @en niedrig ist.

In diesem Beispiel ist @en in jedem zweiten Taktzyklus einmal hoch. Daher verhalten sich @foo und @bar so, als wäre die Taktfrequenz halb so hoch wie in Wirklichkeit. Die Timing-Anforderungen können entsprechend gelockert werden: Die Berechnungen für tsetup können mit einer doppelt so großen Taktperiode durchgeführt werden.

Was thold betrifft, gibt es keine Änderung: Die Berechnung für diese Timing-Anforderung nimmt an, dass dieselbe Taktflanke beide Flipflops erreicht. Die Taktperiode spielt daher keine Rolle, wie bereits im Beispiel einer thold-Analyse besprochen wurde. Entsprechend macht die Illusion eines langsameren Takts bei thold keinen Unterschied.

Wann sollte man eine Taktfreigabe verwenden?

Es gibt nur zwei gute Gründe, eine Taktfreigabe (clock enable) zu verwenden:

Es ist nicht klar, ob eine Taktfreigabe den Stromverbrauch auf einem FPGA verbessert. Man kann argumentieren, dass ein zusätzlicher Takt Strom verschwendet. Aber die Taktfreigabe ist auch ein Signal mit hohem Fan-out. Im Durchschnitt ändert eine Taktfreigabe ihren Wert genauso oft wie der Takt, den sie ersetzt. In Bezug auf Logikzustandswechsel (die den Hauptbeitrag zum Stromverbrauch leisten) gibt es also keinen Unterschied.

Sie können diese Seite gerne überspringen, wenn Sie keinen besonderen Grund haben, Multicycle-Pfade verwenden zu müssen. Selbst wenn Ihr Design eine technisch geeignete Taktfreigabe enthält, ist es möglicherweise besser, die zugehörige Timing-Ausnahme nicht zu verwenden. Insbesondere wenn die Timing-Vorgaben auch ohne diese Timing-Ausnahme leicht erreicht werden, ist das Risiko eines Fehlers den Nutzen nicht wert.

Die Timing-Ausnahme

Im Zusammenhang mit dem obigen Verilog-Code lauten die Multicycle-Pfad-Vorgaben für Vivado:

set en_regs [all_fanout -endpoints_only -only_cells -flat [get_nets en]]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Dasselbe für Quartus:

set en_regs [get_fanouts en]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Der Unterschied zwischen Vivado und Quartus liegt nur in der ersten Zeile: Bei Vivado wird der Befehl „all_fanout" verwendet, bei Quartus „get_fanouts". Bei anderen Tools, die mit SDC arbeiten, sind die Vorgaben ähnlich.

Die erste Zeile findet alle synchronen Elemente (Cells), die das Ende eines Pfads bilden, der bei @en beginnt. Diese Liste von Cell-Objekten wird in $en_regs gespeichert. Die nächsten beiden Zeilen verändern die Timing-Anforderungen für Pfade, die sowohl an Cells beginnen als auch enden, die in dieser Liste stehen.

Das erfordert viele Erklärungen. Warum habe ich die Definition für $en_regs so geschrieben? Warum gibt es zwei set_multicycle_path-Befehle? Warum steht beim zweiten Befehl „-hold", obwohl ich gesagt habe, dass die Timing-Anforderungen für thold von Multicycle-Pfaden nicht betroffen sind?

Ich beginne mit den set_multicycle_path-Befehlen, weil das der leichter zu erklärende Teil ist.

Die set_multicycle_path-Befehle

Wie oben gezeigt, lauten die Befehle:

set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Um das obige Beispiel etwas zu verallgemeinern, betrachten wir auch Folgendes: Wäre die Taktfreigabe nur in jedem achten Taktzyklus aktiv, hätte der Verilog-Code so ausgesehen:

   reg en;
   reg [2:0] pre_en;

   always @(posedge clk)
     begin
	pre_en <= pre_en + 1;
	en <= (pre_en == 0);
     end

Beachten Sie, dass sich @en wie ein Stroboskopsignal verhält und jeweils nur während eines Taktzyklus aktiv ist. Es ist nicht das oberste Bit eines Taktteilers.

Die Multicycle-Vorgaben für diese Möglichkeit wären:

set_multicycle_path -setup -from $en_regs -to $en_regs 8
set_multicycle_path -hold -from $en_regs -to $en_regs 7

Bei diesen beiden Beispielen ist klar, dass die Zahl im ersten set_multicycle_path-Befehl schlicht das Teilungsverhältnis der Taktfreigabe ist.

Beim zweiten Befehl ist es dasselbe Teilungsverhältnis, aber minus eins. Es ist also immer N und N−1.

Zum ersten Befehl gibt es nicht viel zu erklären: Wenn die Taktfreigabe in einem von N Taktzyklen aktiv ist, wird die erlaubte Verzögerung mit N multipliziert. Das ist für die Timing-Anforderung von tsetup relevant.

Aber warum gibt es einen zweiten Befehl? Warum muss man etwas zu thold sagen? Die Antwort ist, dass der erste Befehl auch die Anforderung für die Mindestverzögerung verändert. Mit anderen Worten: Auch die Berechnung für thold wird von dem Befehl mit der Option „-setup" beeinflusst. Warum? Dafür gibt es vermutlich keine gute Erklärung.

Der zweite Befehl korrigiert das: Er setzt die Anforderung für die Mindestverzögerung wieder auf den ursprünglichen Wert zurück. Nach dem zweiten Befehl wird die Berechnung für thold also so durchgeführt wie zuvor.

In der Dokumentation gibt es lange Erklärungen, warum im zweiten Befehl N−1 verwendet wird. Aber um ehrlich zu sein: An diesen Zusatzinformationen ist nichts Interessantes. Das Verhalten von set_multicycle_path in Bezug auf thold ist eigenartig, und das Verständnis, warum die Zahl N−1 sein sollte, macht es nicht weniger eigenartig.

Aber set_multicycle_path war der einfache Teil. Jetzt kommt die eigentliche Schwierigkeit: Die korrekte Liste von Cell-Objekten für $en_regs zu erzeugen.

Auswahl der Register

Um auszuwählen, welche Register in $en_regs aufgelistet werden sollen, muss man verstehen, was einen Pfad für eine Multicycle-Vorgabe qualifiziert. Die Regel lautet: Es ist nur dann erlaubt, die Timing-Anforderung eines Pfads zu lockern, wenn die Taktfreigabe beide Seiten steuert. Das bedeutet: Wenn die Taktfreigabe inaktiv ist, ist garantiert, dass keines der beiden sequentiellen Elemente seinen Wert nach der Taktflanke ändert.

Denken Sie in Begriffen von Taktdomänen (clock domains): Alle sequentiellen Elemente, die von der Taktfreigabe gesteuert werden, gehören zu einer gedachten Taktdomäne. Der Takt innerhalb dieser gedachten Taktdomäne hat eine niedrigere Frequenz, daher können die Timing-Anforderungen innerhalb dieser Taktdomäne angepasst werden.

Wenn aber eine der beiden Seiten des Pfads nicht zu dieser gedachten Taktdomäne gehört, handelt es sich um einen gedachten Taktdomänenübergang zwischen zwei zusammenhängenden Takten (related clocks). Für einen solchen Pfad muss nichts Besonderes getan werden, weil er bereits durch die vorhandenen Timing-Vorgaben abgedeckt ist. Es ist jedoch falsch, auf einen solchen Pfad eine Multicycle-Ausnahme anzuwenden.

Die oben gezeigten Timing-Vorgaben spiegeln diese Idee wider: Alle sequentiellen Elemente, die von @en gesteuert werden, werden als Cell-Objekte in $en_regs aufgelistet. Dann werden die beiden set_multicycle_path-Befehle auf Pfade angewendet, die sowohl an sequentiellen Elementen beginnen als auch enden, die zu dieser Liste gehören.

Der schwierigste Teil bei Multicycle-Pfaden ist sicherzustellen, dass diese Liste sequentieller Elemente korrekt ist: Die Liste sollte alle sequentiellen Elemente enthalten, die von der Taktfreigabe gesteuert werden. Aber es sollten keine anderen sequentiellen Elemente in dieser Liste stehen.

Wenn ein sequentielles Element in der Liste fehlt, wird die Timing-Durchsetzung für die betreffenden Pfade strenger als nötig sein. Das ist keine Katastrophe, macht die Timing-Ausnahme aber weniger effektiv.

Wenn aber versehentlich ein sequentielles Element in die Liste aufgenommen wird, das dort nicht hingehört, kann das ernste Folgen haben: Dann gibt es Pfade, für die die Timing-Anforderungen nicht streng genug sind. Mit anderen Worten: Die Tools garantieren die Anforderungen für den ordnungsgemäßen Betrieb der sequentiellen Elemente nicht. Und wenn Timing-Anforderungen nicht erfüllt werden, können seltsame Dinge passieren.

Ich habe für die Erstellung dieser Liste sequentieller Elemente den Befehl „all_fanout" (bzw. „get_fanouts") gewählt. Das funktioniert nicht immer zuverlässig, und genau darum geht es als Nächstes. Danach bespreche ich andere Möglichkeiten, diese Liste zu erzeugen. Diese anderen Möglichkeiten sind insbesondere dann relevant, wenn die FPGA-Tools „all_fanout" oder ähnliche Befehle nicht unterstützen.

Mögliche Probleme mit all_fanout und get_fanouts

Der wahrscheinlichste Fehler bei einer Multicycle-Vorgabe ist, dass die Taktfreigabe selbst (also @en) in die Liste (also $en_regs) aufgenommen wird. Wenn das passiert, wird die Multicycle-Ausnahme auf alle Pfade von @en selbst zu den sequentiellen Elementen angewendet, die es steuert. Das bedeutet faktisch, dass die Timing-Anforderungen dieser Pfade nicht garantiert sind. Das kann sichtbare Auswirkungen haben, weil die Taktfreigabe oft ein Signal mit hohem Fan-out ist.

Im obigen Beispiel wird das mit @pre_en vermieden. Möglicherweise haben Sie sich gefragt, warum @en nicht einfach so definiert wurde:

always @(posedge clk)
  en <= !en; // Wrong!

Wäre @en so definiert worden, gäbe es einen Pfad von @en zu sich selbst. Infolgedessen wäre @en in $en_regs enthalten.

@pre_en löst dieses Problem also. Aber es ist wichtig sicherzustellen, dass der Synthesizer dieses Register nicht im Rahmen einer Optimierung entfernt. Der Synthesizer von Quartus erkennt zum Beispiel, dass @pre_en im Verilog-Code am Anfang dieser Seite nur dazu dient, @en einen Wert zu geben. Der Synthesizer entfernt @pre_en daher und macht weiter, als stünde dort „en <= !en". Infolgedessen ist @en in $en_regs enthalten. Eine mögliche Lösung besteht darin, @pre_en wie folgt zu deklarieren:

reg pre_en /* synthesis preserve */;

Dieses einfache Beispiel zeigt, wie eine unerwartete Optimierung des Synthesizers ein katastrophales Ergebnis haben kann. Auch wenn die Lösung einfach ist, übersieht man leicht die Notwendigkeit, diese Optimierung zu verhindern.

Ein weiteres mögliches Missgeschick mit der Taktfreigabe hängt damit zusammen, dass dieses Signal oft einen hohen Fan-out hat. Die Tools können das Register daher automatisch vervielfältigen, sodass jede Kopie einen Fan-out unterhalb einer bestimmten Grenze hat. Wie wirkt sich das aber auf $en_regs aus? Das Kriterium für die Aufnahme in die Liste basierte auf einem bestimmten Netz. Die sequentiellen Elemente, die von Kopien von @en gesteuert werden, sind daher nicht enthalten.

Das Ergebnis einer solchen Situation ist allerdings nicht katastrophal: Wie bereits erwähnt, bedeutet das nur, dass die Timing-Durchsetzung für einige Pfade strenger als nötig sein wird. Die Zuverlässigkeit des Designs ist nicht beeinträchtigt.

Die Vervielfältigung von Registern wurde bereits im Zusammenhang mit hohen Fan-outs besprochen. Wie dort erwähnt, ist es besser, @en manuell zu vervielfältigen, als darauf zu warten, dass der Synthesizer es tut. Um Überraschungen zu vermeiden, fügen Sie immer ein Synthese-Attribut hinzu, das die Vervielfältigung dieses Registers verbietet. Wenn ein hoher Fan-out später Probleme beim Timing-Closure verursacht, lösen Sie diese mit einer manuellen Vervielfältigung. So lässt sich die Ursache des Problems leichter verstehen. Wenn der Synthesizer @en plötzlich vervielfältigt, weil das Projekt gewachsen ist, wird es nicht so leicht zu erkennen sein, warum die Timing-Vorgaben nicht erreicht wurden.

Wie auch immer: Der Befehl, der $en_regs definiert, muss aktualisiert werden, damit die Kopien von @en eingeschlossen sind.

Übrigens: Die Definition von $en_regs hängt vom Namen eines Netzes ab. Wie bereits besprochen, bedeutet das, dass $en_regs eine leere Liste wird, wenn der Synthesizer den Namen des Netzes in etwas anderes als „en" ändert. Infolgedessen werden die Multicycle-Pfad-Vorgaben völlig nutzlos. Diese Möglichkeit ist auch keine Katastrophe: Das Design bleibt zuverlässig, aber es wird schwieriger, die Timing-Vorgaben zu erreichen.

Ein weiteres mögliches Problem ist, dass @en wegen der Art, wie $en_regs definiert ist, nur als Taktfreigabe verwendet werden darf. Betrachten Sie zum Beispiel diesen Verilog-Code:

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;

In diesem Beispiel wird @en eindeutig als Taktfreigabe verwendet. Es ist daher in Ordnung, dass alle Pfade, die mit @counter zusammenhängen, Multicycle-Pfade sind. Aber was ist mit diesem Beispiel?

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;
  else
    counter <= counter - 1;

Hier wird @en wie ein gewöhnliches Register verwendet. Der Wert von @counter ändert sich in jedem Taktzyklus. @counter sollte also keinesfalls ein Kandidat für einen Multicycle-Pfad sein. Dennoch sind alle Flipflops von @counter in $en_regs enthalten: Es gibt Pfade von @en zu allen diesen Flipflops.

Das lässt sich relativ einfach lösen, indem man eine Kopie von @en erzeugt:

reg [7:0] counter;
reg non_ce_en;

always @(posedge clk)
  non_ce_en <= pre_en;

always @(posedge clk)
  if (non_ce_en)
    counter <= counter + 1;
  else
    counter <= counter + 2;

Beachten Sie, dass ein Synthese-Attribut erforderlich ist, um zu verhindern, dass der Synthesizer @en und @non_ce_en zu einem Register zusammenfasst.

Zusammenfassend ist die Definition von $en_regs auf der Basis aller Pfade, die bei @en beginnen, einfach und prägnant. Diese Definition ist aber auch ein Minenfeld. Sehen wir uns daher einige Alternativen an.

Alternative Wege zur Erzeugung von $en_regs

Der sicherste Weg, eine Liste sequentieller Elemente für eine Multicycle-Pfad-Timing-Ausnahme zu erzeugen, ist, sich auf die Designhierarchie zu stützen: Die gesamte Logik, die von der Taktfreigabe gesteuert wird, sollte in einem eigenen Modul liegen (und möglichst in Untermodulen). Dadurch lässt sich $en_regs erzeugen, indem man Cells anhand des vollständigen Namens des Cell-Objekts findet. Zum Beispiel mit Vivado:

set all_sync [all_fanout -endpoints_only -only_cells -flat \
  [get_nets -of_objects [get_clocks clk]]]
set en_regs [filter $all_sync {name =~ module_ins/multicycle_ins/* }]

Der erste Befehl findet alle Logikelemente, die mit @clk verbunden sind, außer dem Taktpuffer selbst (dieser Takt wird durch das Taktobjekt mit dem Namen „clk" repräsentiert). Das Ergebnis wird in $all_sync gespeichert. Das ist eine mögliche Methode, eine Liste zu erstellen, die alle relevanten synchronen Elemente enthält. Der zweite Befehl erstellt eine Liste aller Logikelemente in $all_sync, die sich innerhalb des erwähnten eigenen Moduls befinden.

Beachten Sie, dass diese Methode vom Namen des Taktobjekts und den Namen der Instanziierungen abhängt. Diese ändern sich voraussichtlich nicht. Bei dieser Methode spielt es keine Rolle, ob die Taktfreigabe vervielfältigt wird oder ob ihr Name vom Synthesizer geändert wird.

Ein weiterer Vorteil eines eigenen Moduls ist, dass der Verilog-Code leichter zu handhaben ist: Die Wahrscheinlichkeit von Verwechslungen zwischen sequentiellen Elementen, die von der Taktfreigabe gesteuert werden, und solchen, die es nicht sind, ist geringer.

Es ist jedoch nicht immer natürlich, die Logik, die von der Taktfreigabe abhängt, zu trennen und in ein eigenes Modul zu legen. Außerdem ist es, wenn der Verilog-Code bereits geschrieben ist und nachweislich korrekt funktioniert, möglicherweise keine gute Idee, daran Änderungen vorzunehmen.

Ich möchte noch eine weitere Alternative erwähnen, die in manchen Situationen geeignet sein kann: eine Namenskonvention für alle Register. Zum Beispiel kann man allen Registern, die von der Taktfreigabe gesteuert werden, einen Namen geben, der mit „MC_" beginnt. Diese Wahl macht den Befehl zur Erzeugung von $en_regs einfach: Man muss nur nach Cell-Objekten anhand ihres Namens suchen. Andere Logikelemente (z.B. Block-RAMs) können ebenfalls einbezogen werden, indem man den Namen ihrer Instanziierung entsprechend wählt. Manche werden sagen, dass diese Methode den Verilog-Code hässlich macht, andere werden sagen, dass sie die Arbeit erleichtert. Keine Methode ist perfekt.

Warum nicht -of_objects verwenden?

Es mag verlockend erscheinen, $en_regs nach einem einfachen Kriterium zu definieren: Man sucht das Netz mit dem Namen „en" und fügt alle Register hinzu, die mit diesem Netz verbunden sind. In Vivado könnte man das zum Beispiel so schreiben:

set en_regs [get_cells -of_objects [get_nets en]]

Es gibt mehrere Gründe, warum das falsch ist. Der erste Grund ist, dass dabei @en selbst eingeschlossen wird. Die Multicycle-Vorgaben werden also auf alle Pfade von der Taktfreigabe selbst zu den sequentiellen Elementen angewendet, die sie steuert. Wie bereits erwähnt, ist das ein ernster Fehler.

Der zweite Grund ist, dass einige sequentielle Elemente übersehen werden können. Dem obigen Befehl zufolge ist das Kriterium für die Aufnahme, dass die Cell mit einem bestimmten Netz (@en) verbunden sein muss. Das funktioniert, wenn dieses Netz direkt mit dem CE-Eingang des Flipflops verbunden ist. Oft entscheidet sich der Synthesizer jedoch dafür, stattdessen eine kombinatorische Funktion zu verwenden, die auf @en basiert.

Der Synthesizer kann zum Beispiel beschließen, @foo so zu implementieren, als wäre der Verilog-Code so gewesen:

foo <= foo ^ en;

Das ist funktional äquivalent zum ursprünglichen Ausdruck:

if (en)
  foo <= !foo;

Eine solche Optimierung ist legitim und sollte erwartet werden: Der Synthesizer muss ohnehin eine LUT verwenden, um das NOT-Gatter zu implementieren. Warum also nicht diese LUT nutzen, um den nächsten Wert des Flipflops direkt zu erhalten? Warum eine weitere Leitung zum CE-Eingang des Flipflops hinzufügen?

Der Nebeneffekt dieser Optimierung ist, dass das Flipflop selbst nicht direkt mit @en verbunden ist. Es wird daher nicht in $en_regs enthalten sein. Die Auswirkungen einer solchen Situation wurden bereits besprochen.

Oft kann man dem Synthesizer sagen, dass er @en nur als Taktfreigabe-Eingang der sequentiellen Elemente verwenden soll. Einige Synthesizer unterstützen zum Beispiel ein Synthese-Attribut namens „direct_enable" oder ähnlich. Beachten Sie, dass bei Verwendung dieser Funktion der Spielraum des Synthesizers zur Optimierung der Logik eingeschränkt wird. Die Leistung des Designs kann also negativ beeinflusst werden, um ein technisches Problem mit den Tools zu lösen.

Zu allem Überfluss treten dieselben Probleme wie oben auf, wenn @en vervielfältigt oder umbenannt wird.

Aus all diesen Gründen ist die direkte Verbindung zu einem Netz als Kriterium eine schlechte Wahl.

Zusammenspiel mit einem Reset

Angenommen, wir fügen dem obigen Verilog-Beispiel einen synchronen Reset (synchronous reset) hinzu:

   always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;
     end

   always @(posedge clk)
     if (reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

Zur Erinnerung: Der Gedanke hinter einer Multicycle-Timing-Ausnahme war, dass sich alle synchronen Elemente so verhalten, als gehörten sie zu einer gedachten Taktdomäne. Der gedachte Takt in dieser Taktdomäne hat die halbe Frequenz von @clk. Daher müssen alle Register @clk ignorieren, wenn @en niedrig ist. In diesem letzten Verilog-Beispiel ist das nicht der Fall: @reset wirkt unabhängig von @en.

Betrachten Sie zum Beispiel, was passiert, wenn @reset so definiert ist:

assign reset = foo;

Das ist ein legitimer synchroner Reset, auch wenn er praktisch wohl keinen Nutzen hat. Aber diese Definition zeigt das Problem mit einer Multicycle-Pfad-Ausnahme: Wenn @en hoch ist, wird @foo im darauffolgenden Taktzyklus hoch. Dadurch wird aber auch @reset hoch. Im nächsten Taktzyklus wird @foo also wieder niedrig. @foo ändert seinen Wert in jedem Taktzyklus. Der Multicycle-Pfad von @foo zu sich selbst führt also dazu, dass auf diesem Pfad eine nicht ausreichend strenge Timing-Anforderung gilt.

Das lässt sich leicht lösen, indem man @en auch den synchronen Reset steuern lässt:

   always @(posedge clk)
     if (en && reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

Das ist korrekt, allerdings muss @reset zusammen mit @en aktiv sein. Eine einfache Lösung besteht darin, @reset mehrere Taktzyklen lang hoch zu halten.

Dieselben Prinzipien gelten für einen asynchronen Reset (asynchronous reset). Alles, was auf der Seite über asynchrone Resets steht, ist auch hier relevant, aber mit Multicycle-Pfad-Vorgaben ist es noch komplizierter. Die einfachste Lösung ist wahrscheinlich die Verwendung eines Synchronisators, wie auf einer anderen Seite vorgeschlagen.

@en und asynchrone Resets

Einer der Vorteile von @pre_en besteht darin, dass sichergestellt wird, dass alle Kopien von @en jederzeit denselben Logikpegel haben. Das mag selbstverständlich klingen, ist aber nicht garantiert, wenn ein asynchroner Reset falsch verwendet wird. Angenommen, der ursprüngliche Verilog-Code war:

reg en;

always @(posedge clk or posedge reset)
  if (reset)
    en <= 0;
  else
    en <= !en; // This is not recommended!

Wenn @en vervielfältigt wird, kann das Ergebnis diesem Code entsprechen:

reg en, en_1, en_2;

always @(posedge clk or posedge reset)
  if (reset)
    begin
      en <= 0;
      en_1 <= 0;
      en_2 <= 0;
    end
  else
    begin
      en <= !en;
      en_1 <= !en_1;
      en_2 <= !en_2;
    end

Beachten Sie, dass der nächste Wert von @en_1 von seinem eigenen Wert abhängt, nicht vom Wert von @en. Das ist ein realistisches Ergebnis einer Register-Vervielfältigung.

Was passiert, wenn der asynchrone Reset auf unsichere Weise deaktiviert wird? Es ist möglich, dass einige Kopien von @en auf die erste Taktflanke nach dem Reset reagieren und andere Kopien diese Taktflanke ignorieren. Das Ergebnis ist, dass die Logikzustände der Kopien niemals gleich werden (bis zum nächsten Reset).

@pre_en löst das, weil alle Kopien ihren nächsten Wert von derselben Quelle übernehmen. Das gewährleistet langfristig einen ordnungsgemäßen Betrieb, selbst wenn der Start holprig ist.

Zusammenfassung

Eine Taktfreigabe (clock enable) zu verwenden, ist einfach. set_multicycle_path als Befehl für eine Timing-Ausnahme zu verwenden, ist ebenfalls einfach. Aber das Zuverlässigmachen dieses Konstrukts ist überhaupt nicht einfach. Es gibt viele Dinge, die schiefgehen können, und manchmal liegt der Grund darin, dass der Synthesizer sein Verhalten ändert, wenn das Logikdesign wächst.

Das Ergebnis dieser unerwarteten Probleme kann sein, dass der Multicycle-Pfad seinen Zweck nicht erfüllt: Wenn die gelockerten Timing-Anforderungen auf manchen Pfaden nicht angewendet werden, ist der Nutzen dieser Methode fragwürdig. Schlimmer noch: Ein Fehler kann zu einem unzuverlässigen Logikdesign führen, wenn die Multicycle-Pfad-Ausnahme auf Pfade angewendet wird, die nicht hätten betroffen werden dürfen.

Es ist daher entscheidend, die Timing-Reports für die betreffenden Pfade zu lesen, um sicherzustellen, dass nichts Unerwartetes passiert ist. Leider verhindert das keine Überraschungen in der Zukunft: Das Verhalten des Synthesizers ist schwer vorherzusagen, während sich das Design weiterentwickelt.

Wenn es also möglich ist, ist es viel besser, aus derselben PLL einen zusätzlichen Takt zu erzeugen, anstatt eine Taktfreigabe zu verwenden. Die Taktdomänenübergänge mit diesem neuen Takt sind zuverlässig, weil die beiden Takte zusammenhängende Takte (related clocks) sind. Die Timing-Vorgaben für diesen neuen Takt werden automatisch von den Tools erzeugt. So besteht keine Gefahr von Überraschungen.

Wenn Sie diese Seite also lesen, weil Sie eine Taktfreigabe und eine Multicycle-Ausnahme zu Ihrem Design hinzufügen möchten, hoffe ich, dass diese Seite Ihnen etwas zum Nachdenken gegeben hat.


Damit ist der Teil über Timing-Vorgaben für Pfade innerhalb des FPGAs abgeschlossen. Aber was ist mit I/O? Genau damit beginnt die nächste Seite.

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