01signal.com

Die Kunst des Timing-Closure

Diese Seite gehört zu einer Serie von Seiten über Timing. Die vorherigen Seiten haben einige grundlegende Themen erklärt: die Theorie hinter Timing-Berechnungen und die Taktperioden-Timing-Vorgabe. Außerdem wurden einige Timing-Reports gezeigt und erläutert. Nun ist es Zeit, über einen der wichtigsten Zwecke dieses Wissens zu sprechen: das Lösen von Timing-Problemen.

Einführung

Die größte Herausforderung für die FPGA-Tools besteht darin, die Anforderungen der Timing-Vorgaben (timing constraints) zu erfüllen. Hoffentlich gelingt das – aber manchmal scheitert es. Und wenn es scheitert, liegt es an uns Menschen herauszufinden, warum, und das Problem zu beheben. Diese Aufgabe hat einen Namen: Wir nennen sie das Timing-Closure (englisch: timing closure). Und sie ist nicht einfach.

Warum ist Timing-Closure schwierig? Die Sache ist die: Die Tools verfügen über einen Algorithmus für Platzierung und Verdrahtung (Place and Route), der versucht, die Ressourcen des FPGAs optimal zu nutzen. Normalerweise beginnt dieser Algorithmus damit, die Logikelemente ohne allzu großen Aufwand auf dem FPGA zu platzieren. Dann beginnt ein iterativer Prozess: Die Tools gehen alle Pfade (paths) durch und suchen diejenigen, welche die Timing-Vorgaben nicht erfüllen. Um diese Fehlschläge zu beheben, werden an diesen Pfaden Korrekturmaßnahmen ergriffen. Am wichtigsten ist, dass Logikelemente auf andere Positionen im FPGA verschoben und die Verdrahtung angepasst wird. Darüber hinaus hat jedes FPGA-Tool eigene weiterführende Korrekturmethoden.

Wenn alle Pfade die Timing-Vorgaben erfüllen, gilt die Implementierung als abgeschlossen. Die Implementierung kann aber auch enden, weil die Tools dieses Ziel nicht erreichen konnten und den Versuch daraufhin aufgegeben haben. In dieser Situation bekommen wir das, was die Tools erreicht haben, als die Bemühungen eingestellt wurden. Dieses Ergebnis ist nicht unbedingt optimal: Es könnte Pfade in der Implementierung geben, die man hätte verbessern können, aber die Tools waren damit beschäftigt, etwas anderes zu reparieren. Und als das scheiterte, gaben die Tools auf, ohne andere Dinge zu verbessern. Es ist, als würden die Tools sagen: „Es hat keinen Sinn, Zeit in eine Implementierung zu stecken, die ohnehin scheitert.“

Unsere Aufgabe als FPGA-Entwickler ist es, sich dieses suboptimale Ergebnis anzusehen und den Grund zu finden, warum die Ziele der Timing-Vorgaben nicht erreicht wurden.

Die Algorithmen werden mit der Zeit besser. Wenn es einen häufigen Grund dafür gibt, dass Timing-Vorgaben nicht erreicht werden, enthält die nächste Version der Software eine spezielle Lösung für diese Situation. Wenn die Tools also scheitern, gibt es dafür meist einen guten Grund.

Wir sehen uns also an, was die Tools erreicht haben, und fragen uns: Warum sind die Tools gescheitert? Haben wir etwas Unmögliches verlangt? Noch wichtiger: Haben wir etwas Unnötiges verlangt? Vielleicht ist das Hindernis, an dem die Tools gescheitert sind, etwas, das wir gar nicht brauchen. Oder vielleicht hat der Optimierungsalgorithmus nicht so gut funktioniert? Manchmal ist es einfach Pech: Die anfängliche Platzierung der Logikelemente kann so schlecht sein, dass die anschließenden Verbesserungsversuche zum Scheitern verurteilt sind.

Was auch immer das Problem ist: Das Finden des Grundes für das Scheitern ähnelt der Arbeit eines Detektivs am Tatort: Die Fakten liegen vor uns, aber der Grund ist oft verborgen. Die meisten dieser Fakten stehen in den Timing-Reports, aber die Hinweise verraten sich nicht leicht. Die Frage, die man sich immer stellen muss, ist, was im Timing-Report falsch, ungewöhnlich oder abnormal ist. Wie der Detektiv, der den Täter finden will, ist das Ziel, das Detail zu finden, das zum Problem führt.

Aber um das Abnormale zu finden, muss man wissen, was normal ist: Was ist zum Beispiel eine normale Verzögerung für eine Leitung mit einem bestimmten Fan-out? Wie viele Logikstufen sind normal, um eine bestimmte Logikfunktion zu implementieren? Die Antworten auf solche Fragen sind von FPGA zu FPGA unterschiedlich. Es ist daher notwendig, Erfahrung zu sammeln, indem man die Timing-Reports liest und versteht, auch wenn alles in Ordnung ist. Man muss wissen, wie ein Timing-Report aussieht, wenn alles in Ordnung ist, um die Stellen zu finden, an denen der Report ein Problem anzeigt. Falls Sie sich gefragt haben, warum ich auf den vorherigen Seiten so sehr ins Detail gegangen bin: Das ist einer der Gründe.

Der kritische Pfad

Wenn die Tools die Timing-Vorgaben nicht erfüllen, bedeutet das, dass es mindestens einen Pfad mit negativer Zeitmarge (slack) gibt. Der Pfad mit der negativsten Zeitmarge wird als kritischer Pfad (critical path) bezeichnet. Dieser Name spiegelt die übliche Strategie für das Timing-Closure wider: Sich auf den kritischen Pfad zu konzentrieren, ist oft der Weg, ein Timing-Problem zu lösen. Aber ich werde im Folgenden zeigen, dass diese Strategie auch Zeitverschwendung sein kann.

Wenn die Timing-Vorgaben erreicht werden, ist der kritische Pfad der Pfad mit der minimalen Zeitmarge. Dieser Pfad ist oft nicht interessant, weil die Tools nicht versuchen, Pfade mit positiver Zeitmarge zu verbessern. Wenn also der schlechteste Pfad eine positive Zeitmarge hat, kann es Zufall sein, dass ausgerechnet dieser Pfad der schlechteste ist.

Ist die Zeitmarge jedoch positiv und nahezu null (sagen wir, weniger als 0,2 ns), kann das ein Hinweis darauf sein, dass es schwierig war, diesen Pfad die Timing-Vorgaben erfüllen zu lassen. Solche kritischen Pfade kann man als Warnungen betrachten, dass diese Pfade in Zukunft Probleme bereiten können (insbesondere, wenn das FPGA mit mehr Logik gefüllt wird, sodass die Bemühungen der Tools auf andere Pfade umgeleitet werden).

Der Timing-Report enthält normalerweise eine begrenzte Anzahl kritischer Pfade für jeden Takt. Die Voreinstellung der meisten FPGA-Tools ist es, einige kritische Pfade anzuzeigen, auch wenn ihre Zeitmarge positiv ist (wenn die Timing-Vorgaben also erreicht wurden). Diese Einstellung ist zu empfehlen.

Ein Beispiel für einen kritischen Pfad

Ich beginne mit einem Beispiel für die Analyse eines kritischen Pfads. Für dieses Beispiel lautet der Verilog-Code wie folgt:

   reg [24:0] calc, result;
   reg [11:0] x, y, z;

   always @(posedge clk)
     begin
	calc <= x * y + z;
	result <= calc;
     end

In diesem Beispiel beträgt die Frequenz von @clk 250 MHz, und zur Erzeugung dieses Takts wird keine PLL verwendet. Nehmen wir außerdem an, dass @x, @y und @z Register sind, die mit @clk synchron sind. Der Verilog-Code, der diesen Registern Werte zuweist, ist nicht gezeigt, weil er unerheblich ist.

Beim Versuch, diesen Code mit Vivado umzusetzen, wurden die Timing-Vorgaben nicht erreicht. Im Timing-Report war dies der kritische Pfad:

Slack (VIOLATED) :        -0.239ns  (required time - arrival time)
  Source:                 x_reg[1]__0_replica_2/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            calc_reg[23]/D
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            4.000ns  (clk rise@4.000ns - clk rise@0.000ns)
  Data Path Delay:        4.180ns  (logic 1.642ns (39.282%)  route 2.538ns (60.718%))
  Logic Levels:           7  (CARRY8=4 LUT3=1 LUT4=1 LUT6=1)
  Clock Path Skew:        -0.087ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    3.176ns = ( 7.176 - 4.000 )
    Source Clock Delay      (SCD):    3.864ns
    Clock Pessimism Removal (CPR):    0.601ns
  Clock Uncertainty:      0.035ns  ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Total Input Jitter      (TIJ):    0.000ns
    Discrete Jitter          (DJ):    0.000ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      2.032ns (routing 0.396ns, distribution 1.636ns)
  Clock Net Delay (Destination): 1.748ns (routing 0.365ns, distribution 1.383ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.839     1.731    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101     1.832 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=106, routed)         2.032     3.864    clk_IBUF_BUFG
    SLICE_X54Y54         FDRE                                         r  x_reg[1]__0_replica_2/C
  -------------------------------------------------------------------    -------------------
    SLICE_X54Y54         FDRE (Prop_HFF2_SLICEL_C_Q)
                                                      0.137     4.001 r  x_reg[1]__0_replica_2/Q
                         net (fo=21, routed)          0.371     4.372    x[1]_repN_2
    SLICE_X56Y53         LUT6 (Prop_E6LUT_SLICEL_I1_O)
                                                      0.219     4.591 r  calc[23]_i_101/O
                         net (fo=2, routed)           0.550     5.141    calc[23]_i_101_n_0
    SLICE_X54Y57         CARRY8 (Prop_CARRY8_SLICEL_DI[5]_CO[7])
                                                      0.228     5.369 r  calc_reg[23]_i_30/CO[7]
                         net (fo=1, routed)           0.030     5.399    calc_reg[23]_i_30_n_0
    SLICE_X54Y58         CARRY8 (Prop_CARRY8_SLICEL_CI_O[1])
                                                      0.163     5.562 r  calc_reg[23]_i_22/O[1]
                         net (fo=3, routed)           0.351     5.913    calc_reg[23]_i_22_n_14
    SLICE_X56Y57         LUT3 (Prop_C6LUT_SLICEL_I1_O)
                                                      0.146     6.059 r  calc[23]_i_26/O
                         net (fo=3, routed)           0.240     6.299    calc[23]_i_26_n_0
    SLICE_X55Y58         LUT4 (Prop_A6LUT_SLICEM_I0_O)
                                                      0.089     6.388 r  calc[23]_i_7/O
                         net (fo=1, routed)           0.407     6.795    calc[23]_i_7_n_0
    SLICE_X53Y57         CARRY8 (Prop_CARRY8_SLICEM_DI[2]_O[4])
                                                      0.308     7.103 r  calc_reg[23]_i_2/O[4]
                         net (fo=1, routed)           0.538     7.641    P[20]
    SLICE_X54Y56         CARRY8 (Prop_CARRY8_SLICEL_S[4]_O[7])
                                                      0.352     7.993 r  calc_reg[23]_i_1/O[7]
                         net (fo=1, routed)           0.051     8.044    P0_out[23]
    SLICE_X54Y56         FDRE                                         r  calc_reg[23]/D
  -------------------------------------------------------------------    -------------------

                         (clock clk rise edge)        4.000     4.000 r
    AG12                                              0.000     4.000 r  clk (IN)
                         net (fo=0)                   0.000     4.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     4.515 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.066     4.581    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     4.615 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.722     5.337    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     5.428 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=106, routed)         1.748     7.176    clk_IBUF_BUFG
    SLICE_X54Y56         FDRE                                         r  calc_reg[23]/C
                         clock pessimism              0.601     7.777
                         clock uncertainty           -0.035     7.741
    SLICE_X54Y56         FDRE (Setup_HFF_SLICEL_C_D)
                                                      0.063     7.804    calc_reg[23]
  -------------------------------------------------------------------
                         required time                          7.804
                         arrival time                          -8.044
  -------------------------------------------------------------------
                         slack                                 -0.239

Die Zeitmarge dieses Pfads betrug −0,239 ns, das ist also eine knappe Verfehlung der Timing-Vorgaben. Das Erste, was man untersuchen sollte, sind Anfang und Ende dieses Pfads: Wir schauen auf „Source“ und „Destination“ im Kopfbereich des Reports und finden x_reg und calc_reg. Die Problemquelle ist also eindeutig dieser Teil:

calc <= x * y + z;

Das sollte keine Überraschung sein, denn es ist die einzige nennenswerte Operation im Verilog-Code. In einem realen Szenario ist es nicht so offensichtlich, welcher Teil der Logik das Problem verursacht hat.

Aus dem Timing-Report geht außerdem hervor, dass die Anzahl der Logikstufen groß ist: 7. Der kombinatorische Pfad (combinatorial path) ist zu lang. Mit anderen Worten: Zwischen zwei Taktflanken von @clk muss zu viel erledigt werden.

Aber was ist der eigentliche Grund für das Scheitern? Vielleicht die Tatsache, dass das Routing für 61 % der Verzögerung verantwortlich ist? Zur Erinnerung: Es gibt die Faustregel, dass die Routing-Verzögerung normalerweise etwa 40 % der Gesamtverzögerung ausmacht. Vielleicht sollte man also versuchen, die FPGA-Tools zu besserer Arbeit zu bewegen? Das ist aber wahrscheinlich keine erfolgreiche Lösung, denn die Tools arbeiten normalerweise hart, bevor sie die Versuche aufgeben, die Timing-Vorgaben eines Pfads zu erreichen.

Auch der Versuch, die Logikfunktion zwischen @x und @calc zu verändern, ist zwecklos: Die Multiplikation ist wesentlich, es gibt also keinen Weg, etwas Einfacheres einzusetzen.

Auf der nächsten Seite werde ich andere mögliche Ansätze zur Lösung eines solchen Problems vorstellen. Aber eine Liste von Techniken durchzugehen, wird in diesem Fall nicht helfen. Dieses einfache Beispiel zeigt, dass wir manchmal wie Detektive denken müssen.

Kein Ersatz für den eigenen Verstand

Die erste Frage beim Lesen eines Timing-Reports ist, was daran ungewöhnlich ist. In diesem Beispiel lautet die Antwort, dass der kombinatorische Pfad nur aus Slices besteht: Praktisch alle FPGAs besitzen dedizierte Recheneinheiten (DSPs, ALUs – die Namen variieren), die bei einer Multiplikation verwendet werden. Tatsächlich ist Multiplizieren-und-Addieren die häufigste Funktion in solchen dedizierten Logikblöcken. Die einfachste Lösung ist also in den meisten Fällen, die Tools dazu zu bringen, eine dedizierte Recheneinheit zu verwenden. Der Timing-Report für diese Lösung ist unten auf dieser Seite zu sehen.

Aber die eigentliche Frage, die wir uns stellen sollten, ist, warum Slices anstelle einer dedizierten Recheneinheit verwendet wurden. Der häufigste Grund ist, dass alle verfügbaren Recheneinheiten des FPGAs bereits von einem anderen Teil des Designs belegt sind. In diesem Fall könnte die nötige Änderung überhaupt nichts mit dem kritischen Pfad zu tun haben: Vielleicht müssen ein paar Recheneinheiten freigemacht werden, indem man etwas Logik aus dem Design entfernt. Eine andere Möglichkeit wäre, den Tools vorzugeben, diese Recheneinheiten anders auf die verschiedenen Teile des Designs zu verteilen.

In diesem Beispiel wurden Slices statt Recheneinheiten verwendet, weil ich das so herbeigeführt habe: Ich habe die Nutzung von Recheneinheiten absichtlich deaktiviert (mit einem Parameter von Vivados Synthesizer: max_dsp auf null gesetzt). Aber das macht dieses Beispiel nicht künstlich. Manchmal werden falsche Parameter bei den FPGA-Tools verwendet, die genau so eine Situation herbeiführen. Tatsächlich ist es manchmal richtig, Recheneinheiten absichtlich nicht zu verwenden, weil sie an anderer Stelle im Design dringender gebraucht werden.

Die einfache Lösung war also, dedizierte Recheneinheiten zu verwenden. Aber was, wenn wir Slices verwenden müssen? Auch hier ist die Lösung indirekt. Zur Erinnerung: Der problematische Teil war:

calc <= x * y + z;

Aber beachten Sie, dass unmittelbar danach Folgendes kommt:

result <= calc;

Wenn @calc nur in dieser Zeile verwendet wird und sonst nirgends, kann man die Berechnung in zwei Stufen aufteilen. Diese Technik wird oft als Pipelining bezeichnet. Der Verilog-Code ändert sich dann zu Folgendem:

   reg [24:0] calc, result;
   reg [11:0] x, y, z, z_d;

   always @(posedge clk)
     begin
	z_d <= z;

	calc <= x * y;
	result <= calc + z_d;
     end

In dieser Lösung erhält @calc nur das Ergebnis der Multiplikation. Der Wert von @z wird erst in der nächsten Stufe zu @calc addiert. Genauer gesagt findet die Additionsoperation zwischen @calc und @z_d statt, weil diese Operation einen Taktzyklus später passiert. Der Wert von @result ist also exakt wie zuvor.

Es war leicht, das Problem auf diese Weise zu lösen, weil @result im ursprünglichen Verilog-Code nur eine verzögerte Kopie von @calc war. Im echten Leben haben wir normalerweise nicht so viel Glück.

Beachten Sie, dass der kritische Pfad nur @calc und @x betraf. @z wurde in dem Pfad nicht einmal erwähnt. Der Zweck dieser Umstellung ist es also, die Last der Rechenoperation zu verringern. Genauer gesagt: die Anzahl der Logikstufen zu reduzieren.

Zur Erinnerung: Der kritische Pfad ist der schlechteste Pfad nach der Ausführung eines Optimierungsalgorithmus. Dieser Algorithmus fragt nicht nach dem Grund des Problems. Er versucht vielmehr, die Pfade zu verbessern, die eine negative Zeitmarge haben. Selbst wenn die Lösung des Problems eine Änderung an @z erfordert, war der mit @z zusammenhängende Pfad nicht der kritische Pfad. Das war nur ein Zufall, aber so etwas passiert häufig.

Der Timing-Report mit dem kritischen Pfad dieser Lösung ist ebenfalls unten auf dieser Seite zu sehen. Er zeigt, dass die Anzahl der Logikstufen von 7 auf 6 reduziert wurde. Dadurch verringerte sich die Datenpfadverzögerung um 0,715 ns, was mehr als genug war, um die Timing-Vorgaben zu erfüllen.

Die Lektion aus diesem Beispiel ist, dass der kritische Pfad nicht immer der unmittelbare Grund für das Problem ist. Es ist trotzdem richtig zu fragen, warum dieser Pfad scheiterte, aber die Lösung kann woanders liegen. Denken Sie daran, dass jedes FPGA-Tool eigene Hilfsmittel hat, die Informationen liefern, welche beim Finden der eigentlichen Problemursache helfen können. Es lohnt sich, diese Hilfsmittel zu erkunden und ihre Dokumentation zu lesen.

Das Problem besser früh vermeiden, als es später zu lösen

Ein großer Teil der Arbeit am Timing-Closure lässt sich vermeiden, wenn das Logikdesign von Anfang an richtig gemacht wird. Das erfordert ein ständiges Bewusstsein dafür, dass ein Logikdesign keine Software ist. Der Zweck des Verilog-Codes ist es nicht, während der Simulation das richtige Ergebnis zu erzeugen. Was wirklich zählt, ist die Ausgabe, die der Synthesizer aus dem Verilog-Code erzeugt.

Ein gutes Logikdesign beginnt damit, durchzudenken, wie die Logik ihren Zweck am besten erfüllt. Dazu gehört auch, die potenziellen timingbezogenen Hindernisse zu erkennen.

Weniger erfahrene FPGA-Entwickler entwickeln Verilog-Code oft nach Versuch und Irrtum. Die Simulation wird verwendet, um zu sehen, ob die Logik wie erwartet funktioniert, und es werden schrittweise Korrekturen vorgenommen, bis die Ausgabe der Simulation stimmt. Das Ergebnis kann Logik sein, die auf Hardware nicht verwendbar ist: Der Verilog-Code muss vollständig neu geschrieben werden, um die Timing-Vorgaben zu erreichen.

Es ist wichtig, im Voraus über die kombinatorischen Pfade nachzudenken, die der Verilog-Code erzeugt. Die Idee ist, sich jedes Register anzusehen und dem kombinatorischen Pfad bis zu seinem Ende zu folgen. Zur Erinnerung: Ein kombinatorischer Pfad beginnt immer an einem Register und endet an einem Register.

Sehen wir uns dieses Beispiel an:

reg [15:0] a, b;
wire [16:0] x, y;
reg [33:0] z;

assign x = a + 2;
assign y = b + 3;

always @(posedge clk)
  z <= x * y;

Was @a betrifft: Wenn sich dieses Register ändert, erreicht der kombinatorische Pfad zuerst @x. Aber @x ist kein Register. @x wird durch eine kontinuierliche Zuweisung aktualisiert. Der Pfad setzt sich also bis @z fort. Die Logik führt in diesem kombinatorischen Pfad also zwei wesentliche Operationen aus: eine arithmetische Addition und eine arithmetische Multiplikation. Ist das zu viel? Muss man das mit Pipelining in zwei Taktzyklen aufteilen? Das hängt von der Taktfrequenz und vom verwendeten FPGA ab.

Ein weiterer wichtiger Faktor ist, wie schwierig es ist, die kombinatorischen Pfade zu verkürzen. Manchmal ist ein langer kombinatorischer Pfad unvermeidlich. Wenn es aber leicht ist, das Timing eines Pfads zu verbessern, sollte man das tun, selbst wenn es im Design viel schlechtere Pfade gibt: Wenn es in einem Design einige problematische Pfade gibt, können die Tools die Timing-Vorgaben oft erreichen, indem sie ihre Anstrengungen auf diese Pfade konzentrieren. Es hilft sehr, wenn die Timing-Anforderungen der anderen Pfade leicht zu erfüllen sind.

Es gibt also keine Regeln dafür, was erlaubt ist und was nicht, um ein Design zu erhalten, das die Timing-Vorgaben erfüllt. Es erfordert Erfahrung mit FPGA-Design, um auf diesem Gebiet richtige Entscheidungen zu treffen. Auch Erfahrung mit den konkreten FPGA-Tools ist wichtig. Die einzige Regel, die immer richtig ist: Wenn sich das Timing durch eine einfache Änderung im Verilog-Code verbessern lässt, dann tu es. Sei nicht faul und mach diese Änderung von Anfang an. Habe das Timing immer im Kopf.

Schnelle Logik schreiben

Wie bereits erwähnt, ist das Ziel, kurze kombinatorische Pfade zwischen Registern zu erreichen: Die Logikfunktionen, die den nächsten Wert von Registern berechnen, sollten einfach sein. Mit anderen Worten: Es sollten nur wenige Logikstufen nötig sein, um diese Logikfunktionen zu implementieren.

Unsere Aufgabe als FPGA-Entwickler ist es, den Verilog-Code anzusehen und abzuschätzen, wie kompliziert die Logikfunktionen sein werden. Dafür ist Kenntnis darüber nötig, wie der Synthesizer Verilog in Logikelemente übersetzt – in LUTs und andere Logik-Primitive (primitives). Dieses Wissen erwirbt man durch Erfahrung, die zum Teil durch die Analyse der Timing-Reports entsteht. Um die Sache noch schwieriger zu machen: Diese Übersetzung ist von FPGA zu FPGA unterschiedlich. Es ist also keine leichte Aufgabe, Verilog-Code zu schreiben, der schnelle Logik ergibt.

Wenn Sie ein FPGA-Neuling sind, empfiehlt es sich, sich zum Lernen die Ergebnisse der Implementierung anzusehen. Der Timing-Report zeigt Beispiele dafür, wie das Logikdesign in einfache Logikelemente zerlegt wird. Die FPGA-Tools bieten auch andere Hilfsmittel an, um die zugrundeliegenden Logikelemente zu betrachten.

Außerdem gibt es ein paar einfache Regeln, die helfen können:

Zwei zusätzliche Timing-Reports

Ich habe in dem Abschnitt „Kein Ersatz für den eigenen Verstand“ weiter oben zwei Timing-Reports versprochen. Ich habe sie hierher gestellt und nicht dorthin, wo sie erwähnt werden, weil sie lang und nicht vollständig relevant sind.

Beachten Sie, dass jeder dieser beiden Timing-Reports der kritische Pfad für das jeweilige Szenario ist. Der Pfad beginnt und endet also nicht an denselben Registern wie der oben gezeigte Pfad.

Der erste Timing-Report bezieht sich auf das erste Verilog-Code-Beispiel. Im Gegensatz zum Timing-Report oben durften die Tools dedizierte Recheneinheiten verwenden. Das Ergebnis ist, dass die Timing-Vorgaben problemlos erreicht wurden.

Dieser Timing-Report wurde für ein Kintex-Ultrascale-FPGA erzeugt. In dieser FPGA-Familie heißt eine dedizierte Recheneinheit DSP48E2. Beachten Sie, dass der Pfad auf derselben DSP48E2-Einheit beginnt und endet. Die Logikverzögerung beträgt daher 100 %.

Slack (MET) :             1.406ns  (required time - arrival time)
  Source:                 calc_reg/DSP_A_B_DATA_INST/CLK
                            (rising edge-triggered cell DSP_A_B_DATA clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            calc_reg/DSP_OUTPUT_INST/ALU_OUT[10]
                            (rising edge-triggered cell DSP_OUTPUT clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            4.000ns  (clk rise@4.000ns - clk rise@0.000ns)
  Data Path Delay:        2.445ns  (logic 2.445ns (100.000%)  route 0.000ns (0.000%))
  Logic Levels:           4  (DSP_ALU=1 DSP_M_DATA=1 DSP_MULTIPLIER=1 DSP_PREADD_DATA=1)
  Clock Path Skew:        -0.010ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    3.392ns = ( 7.392 - 4.000 )
    Source Clock Delay      (SCD):    4.096ns
    Clock Pessimism Removal (CPR):    0.694ns
  Clock Uncertainty:      0.035ns  ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Total Input Jitter      (TIJ):    0.000ns
    Discrete Jitter          (DJ):    0.000ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      2.264ns (routing 0.756ns, distribution 1.508ns)
  Clock Net Delay (Destination): 1.964ns (routing 0.696ns, distribution 1.268ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.839     1.731    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101     1.832 r  clk_IBUF_BUFG_inst/O
    X2Y1 (CLOCK_ROOT)    net (fo=80, routed)          2.264     4.096    calc_reg/CLK
    DSP48E2_X11Y34       DSP_A_B_DATA                                 r  calc_reg/DSP_A_B_DATA_INST/CLK
  -------------------------------------------------------------------    -------------------
    DSP48E2_X11Y34       DSP_A_B_DATA (Prop_DSP_A_B_DATA_DSP48E2_CLK_A2_DATA[9])
                                                      0.302     4.398 r  calc_reg/DSP_A_B_DATA_INST/A2_DATA[9]
                         net (fo=1, routed)           0.000     4.398    calc_reg/DSP_A_B_DATA.A2_DATA<9>
    DSP48E2_X11Y34       DSP_PREADD_DATA (Prop_DSP_PREADD_DATA_DSP48E2_A2_DATA[9]_A2A1[9])
                                                      0.182     4.580 r  calc_reg/DSP_PREADD_DATA_INST/A2A1[9]
                         net (fo=1, routed)           0.000     4.580    calc_reg/DSP_PREADD_DATA.A2A1<9>
    DSP48E2_X11Y34       DSP_MULTIPLIER (Prop_DSP_MULTIPLIER_DSP48E2_A2A1[9]_U[10])
                                                      0.994     5.574 f  calc_reg/DSP_MULTIPLIER_INST/U[10]
                         net (fo=1, routed)           0.000     5.574    calc_reg/DSP_MULTIPLIER.U<10>
    DSP48E2_X11Y34       DSP_M_DATA (Prop_DSP_M_DATA_DSP48E2_U[10]_U_DATA[10])
                                                      0.164     5.738 r  calc_reg/DSP_M_DATA_INST/U_DATA[10]
                         net (fo=1, routed)           0.000     5.738    calc_reg/DSP_M_DATA.U_DATA<10>
    DSP48E2_X11Y34       DSP_ALU (Prop_DSP_ALU_DSP48E2_U_DATA[10]_ALU_OUT[10])
                                                      0.803     6.541 r  calc_reg/DSP_ALU_INST/ALU_OUT[10]
                         net (fo=1, routed)           0.000     6.541    calc_reg/DSP_ALU.ALU_OUT<10>
    DSP48E2_X11Y34       DSP_OUTPUT                                   r  calc_reg/DSP_OUTPUT_INST/ALU_OUT[10]
  -------------------------------------------------------------------    -------------------

                         (clock clk rise edge)        4.000     4.000 r
    AG12                                              0.000     4.000 r  clk (IN)
                         net (fo=0)                   0.000     4.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     4.515 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.066     4.581    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     4.615 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.722     5.337    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     5.428 r  clk_IBUF_BUFG_inst/O
    X2Y1 (CLOCK_ROOT)    net (fo=80, routed)          1.964     7.392    calc_reg/CLK
    DSP48E2_X11Y34       DSP_OUTPUT                                   r  calc_reg/DSP_OUTPUT_INST/CLK
                         clock pessimism              0.694     8.086
                         clock uncertainty           -0.035     8.050
    DSP48E2_X11Y34       DSP_OUTPUT (Setup_DSP_OUTPUT_DSP48E2_CLK_ALU_OUT[10])
                                                     -0.104     7.946    calc_reg/DSP_OUTPUT_INST
  -------------------------------------------------------------------
                         required time                          7.946
                         arrival time                          -6.541
  -------------------------------------------------------------------
                         slack                                  1.406

Der zweite Timing-Report bezieht sich auf das zweite Beispiel des Verilog-Codes. In diesem Beispiel wurde die Situation durch Pipelining verbessert:

Slack (MET) :             0.433ns  (required time - arrival time)
  Source:                 y_reg[1]__0/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            calc_reg[23]/D
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            4.000ns  (clk rise@4.000ns - clk rise@0.000ns)
  Data Path Delay:        3.465ns  (logic 1.653ns (47.706%)  route 1.812ns (52.294%))
  Logic Levels:           6  (CARRY8=4 LUT4=2)
  Clock Path Skew:        -0.129ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    3.373ns = ( 7.373 - 4.000 )
    Source Clock Delay      (SCD):    4.040ns
    Clock Pessimism Removal (CPR):    0.538ns
  Clock Uncertainty:      0.035ns  ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Total Input Jitter      (TIJ):    0.000ns
    Discrete Jitter          (DJ):    0.000ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      2.208ns (routing 0.756ns, distribution 1.452ns)
  Clock Net Delay (Destination): 1.945ns (routing 0.696ns, distribution 1.249ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.839     1.731    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101     1.832 r  clk_IBUF_BUFG_inst/O
    X2Y1 (CLOCK_ROOT)    net (fo=121, routed)         2.208     4.040    clk_IBUF_BUFG
    SLICE_X54Y88         FDRE                                         r  y_reg[1]__0/C
  -------------------------------------------------------------------    -------------------
    SLICE_X54Y88         FDRE (Prop_EFF2_SLICEL_C_Q)
                                                      0.138     4.178 r  y_reg[1]__0/Q
                         net (fo=25, routed)          0.505     4.683    y[1]
    SLICE_X53Y91         LUT4 (Prop_B6LUT_SLICEM_I0_O)
                                                      0.150     4.833 r  calc[7]_i_28/O
                         net (fo=1, routed)           0.344     5.177    calc[7]_i_28_n_0
    SLICE_X53Y89         CARRY8 (Prop_CARRY8_SLICEM_DI[2]_CO[7])
                                                      0.424     5.601 r  calc_reg[7]_i_9/CO[7]
                         net (fo=1, routed)           0.043     5.644    calc_reg[7]_i_9_n_0
    SLICE_X53Y90         CARRY8 (Prop_CARRY8_SLICEM_CI_O[0])
                                                      0.122     5.766 r  calc_reg[23]_i_30/O[0]
                         net (fo=3, routed)           0.402     6.168    calc_reg[23]_i_30_n_15
    SLICE_X51Y88         LUT4 (Prop_C5LUT_SLICEL_I0_O)
                                                      0.169     6.337 r  calc[15]_i_8/O
                         net (fo=1, routed)           0.437     6.774    calc[15]_i_8_n_0
    SLICE_X51Y92         CARRY8 (Prop_CARRY8_SLICEL_DI[1]_CO[7])
                                                      0.422     7.196 r  calc_reg[15]_i_1/CO[7]
                         net (fo=1, routed)           0.030     7.226    calc_reg[15]_i_1_n_0
    SLICE_X51Y93         CARRY8 (Prop_CARRY8_SLICEL_CI_O[7])
                                                      0.228     7.454 r  calc_reg[23]_i_1/O[7]
                         net (fo=1, routed)           0.051     7.505    calc_reg[23]_i_1_n_8
    SLICE_X51Y93         FDRE                                         r  calc_reg[23]/D
  -------------------------------------------------------------------    -------------------

                         (clock clk rise edge)        4.000     4.000 r
    AG12                                              0.000     4.000 r  clk (IN)
                         net (fo=0)                   0.000     4.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     4.515 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.066     4.581    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     4.615 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.722     5.337    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     5.428 r  clk_IBUF_BUFG_inst/O
    X2Y1 (CLOCK_ROOT)    net (fo=121, routed)         1.945     7.373    clk_IBUF_BUFG
    SLICE_X51Y93         FDRE                                         r  calc_reg[23]/C
                         clock pessimism              0.538     7.910
                         clock uncertainty           -0.035     7.875
    SLICE_X51Y93         FDRE (Setup_HFF_SLICEL_C_D)
                                                      0.063     7.938    calc_reg[23]
  -------------------------------------------------------------------
                         required time                          7.938
                         arrival time                          -7.505
  -------------------------------------------------------------------
                         slack                                  0.433

Der Unterschied ist nicht so dramatisch wie bei der Verwendung einer dedizierten Recheneinheit. Aber er ist immer noch gut genug, um die Timing-Vorgabe zu erfüllen.


Damit ist die allgemeine Diskussion zum Timing-Closure abgeschlossen. Die nächste Seite schlägt einige praktische Strategien zu diesem Thema vor.

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