Einleitung
Wenn in einem Logik-Design eine PLL verwendet wird, um aus einem Takt einen anderen zu erzeugen, geht man häufig davon aus, dass die Timing-Werkzeuge die Frequenzen und die zeitlichen Beziehungen zwischen den verschiedenen Takten selbst herausfinden.
Bei Intels Quartus ist das standardmäßig nicht der Fall. Damit dies geschieht, muss in der SDC-Datei der Befehl derive_pll_clocks stehen. Und tatsächlich taucht dieser Befehl in praktisch jeder SDC-Datei auf, die von den Werkzeugen automatisch erzeugt wird.
Aber hier ist das Erschreckende: Wenn derive_pll_clocks weggelassen wird, würde man erwarten, dass auf die von der PLL erzeugten Takte korrekte Zeitvorgaben (timing constraints) angewendet werden. Eine andere Möglichkeit wäre, dass überhaupt keine Zeitvorgaben angewendet werden. Leider sieht die Realität anders aus: Wie unten gezeigt wird, werden für diese Takte durchaus Timing-Berechnungen durchgeführt, aber mit falschen Constraints. Man kann also leicht den Eindruck bekommen, die Timing-Constraints seien erfüllt und alles sei in Ordnung, obwohl in Wirklichkeit nichts richtig gemacht wurde.
Ein Beispiel
Nehmen wir an, das FPGA besitzt einen Oszillatoreingang mit 48 MHz (die Periodendauer beträgt also 20,833 ns). Die PLL erzeugt daraus einen Takt mit 240 MHz (also 4,166 ns).
Zunächst nehmen wir eine einfache und sauber geschriebene SDC-Datei, die Folgendes enthält:
create_clock -name root_clk -period 20.833 [get_ports {osc_clock}]
derive_pll_clocks
derive_clock_uncertainty
Man beachte, dass der Befehl derive_pll_clocks vorhanden ist.
Nun schauen wir uns den Timing-Report für einen Pfad (path) zwischen zwei Registern an, die mit dem von der PLL erzeugten Takt getaktet werden. Der einzig interessante Teil ist rot markiert:
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 0.000 ; 0.000 ; ; ; ; ; launch edge time ;
; 4.937 ; 4.937 ; ; ; ; ; clock path ;
; 0.000 ; 0.000 ; ; ; ; ; source latency ;
; 0.000 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 0.000 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 0.667 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 2.833 ; 2.166 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 1.119 ; -1.714 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 1.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 3.274 ; 2.155 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 3.274 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 4.336 ; 1.062 ; RR ; IC ; 1 ; FF_X40_Y24_N27 ; clkrst_ins|main_state[0]|clk ;
; 4.937 ; 0.601 ; RR ; CELL ; 1 ; FF_X40_Y24_N27 ; clkrst:clkrst_ins|main_state[0] ;
; 6.774 ; 1.837 ; ; ; ; ; data path ;
; 5.169 ; 0.232 ; ; uTco ; 1 ; FF_X40_Y24_N27 ; clkrst:clkrst_ins|main_state[0] ;
; 5.169 ; 0.000 ; FF ; CELL ; 5 ; FF_X40_Y24_N27 ; clkrst_ins|main_state[0]|q ;
; 5.591 ; 0.422 ; FF ; IC ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|dataa ;
; 6.002 ; 0.411 ; FR ; CELL ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|combout ;
; 6.370 ; 0.368 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|d ;
; 6.774 ; 0.404 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Required Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 4.166 ; 4.166 ; ; ; ; ; latch edge time ;
; 9.005 ; 4.839 ; ; ; ; ; clock path ;
; 4.166 ; 0.000 ; ; ; ; ; source latency ;
; 4.166 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 4.166 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 4.833 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 6.912 ; 2.079 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 5.119 ; -1.793 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 5.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 7.187 ; 2.068 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 7.187 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 8.199 ; 1.012 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|clk ;
; 8.736 ; 0.537 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
; 9.005 ; 0.269 ; ; ; ; ; clock pessimism removed ;
; 8.985 ; -0.020 ; ; ; ; ; clock uncertainty ;
; 8.890 ; -0.095 ; ; uTsu ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
Man beachte die Zeile „latch edge time“. Sie gibt den Zeitpunkt der Taktflanke an, die sich durch das Taktnetzwerk ausbreitet – und dabei die Verzögerung der Taktpuffer und Leiterbahnen mitnimmt – und letztlich zum Takt des empfangenden Registers wird. Die „latch edge time“ ist schlicht die Taktperiode, denn dieser Pfad verläuft zwischen zwei Registern, die mit derselben steigenden Flanke desselben Takts arbeiten. Und tatsächlich steht sie bei 4,166 ns. Bis hierhin also alles gut.
Doch dann das Desaster
Schauen wir uns an, was passiert, wenn der Befehl derive_pll_clocks weggelassen wird. Mit anderen Worten, die SDC-Datei lautet dann:
create_clock -name root_clk -period 20.833 [get_ports {osc_clock}]
derive_clock_uncertainty
Für exakt denselben Pfad liefert der Timing-Report Folgendes:
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 0.000 ; 0.000 ; ; ; ; ; launch edge time ;
; 4.937 ; 4.937 ; ; ; ; ; clock path ;
; 0.000 ; 0.000 ; ; ; ; ; source latency ;
; 0.000 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 0.000 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 0.667 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 2.833 ; 2.166 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 1.119 ; -1.714 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 1.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 3.274 ; 2.155 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 3.274 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 4.336 ; 1.062 ; RR ; IC ; 1 ; FF_X40_Y24_N19 ; clkrst_ins|main_state[0]|clk ;
; 4.937 ; 0.601 ; RR ; CELL ; 1 ; FF_X40_Y24_N19 ; clkrst:clkrst_ins|main_state[0] ;
; 6.765 ; 1.828 ; ; ; ; ; data path ;
; 5.169 ; 0.232 ; ; uTco ; 1 ; FF_X40_Y24_N19 ; clkrst:clkrst_ins|main_state[0] ;
; 5.169 ; 0.000 ; FF ; CELL ; 5 ; FF_X40_Y24_N19 ; clkrst_ins|main_state[0]|q ;
; 5.583 ; 0.414 ; FF ; IC ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|datab ;
; 5.994 ; 0.411 ; FR ; CELL ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|combout ;
; 6.361 ; 0.367 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|d ;
; 6.765 ; 0.404 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
+--------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Required Path ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 20.833 ; 20.833 ; ; ; ; ; latch edge time ;
; 25.672 ; 4.839 ; ; ; ; ; clock path ;
; 20.833 ; 0.000 ; ; ; ; ; source latency ;
; 20.833 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 20.833 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 21.500 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 23.579 ; 2.079 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 21.786 ; -1.793 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 21.786 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 23.855 ; 2.069 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 23.855 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 24.867 ; 1.012 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|clk ;
; 25.404 ; 0.537 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
; 25.672 ; 0.268 ; ; ; ; ; clock pessimism removed ;
; 25.572 ; -0.100 ; ; ; ; ; clock uncertainty ;
; 25.477 ; -0.095 ; ; uTsu ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
Es ist also exakt dieselbe Analyse, nur unter der Annahme, dass die Taktperiode 20,833 ns beträgt (man beachte erneut die „latch edge time“). Man beachte außerdem, dass die Analyse die PLL durchläuft, dabei aber einfach ignoriert, dass der Ausgang der PLL eine andere Frequenz hat. Es ist, als würden die Werkzeuge sagen: Du hast den Befehl derive_pll_clocks vergessen? Kein Problem. Wir tun einfach so, als ob der Eingangstakt der PLL direkt durch sie hindurchgeht.
Ehrlich gesagt fällt mir kein einziger Fall ein, in dem dieses Verhalten irgendeinen Sinn ergeben würde. Entweder man berechnet das Timing für Pfade, die den PLL-Takt verwenden, gar nicht – oder man macht es gleich richtig. Aber einfach die Periode des ursprünglichen Takts einzusetzen? Diese falsch berechneten Pfade tauchen nicht in der Zusammenfassung der Pfade ohne Timing-Constraints auf, und es gibt auch keinen anderen Hinweis darauf, dass die Timing-Werte hoffnungslos falsch sind.
Zur Verteidigung der Werkzeuge: Die Timing-Analyse erzeugt durchaus Warnungen zu diesem Thema, aber keine auf der Stufe Critical. Daher sind sie in dem Meer von Warnungen, das FPGA-Werkzeuge ohnehin immer ausspucken, leicht zu übersehen.
Fazit
- Man sollte sicherstellen, dass das Design den Befehl
derive_pll_clocksenthält, wenn eine PLL beteiligt ist (sofern man nicht explizite Constraints für die Takte der PLL hinzugefügt hat). - Man sollte unbedingt einen Timing-Report erzeugen und ihn auch lesen und verstehen.
- Man sollte seine Constraints stets testen, indem man Werte verlangt, die unmöglich zu erreichen sind, und dann verifizieren, dass die fehlschlagenden Pfade korrekt berechnet werden.