01signal.com

En savoir plus sur la contrainte de période d’horloge

Cette page fait partie d’une série de pages consacrée au timing. Après une brève introduction à la théorie qui se cache derrière les contraintes temporelles (timing constraints), puis la première page consacrée à la contrainte de période d’horloge, il est temps d’examiner quelques scénarios réalistes autour de cette contrainte.

L’étape suivante : utiliser une PLL

Dans l’exemple présenté sur la page précédente, la broche d’horloge externe était connectée directement à la logique. Dans la plupart des conceptions réelles, une sorte de PLL sert à créer l’horloge utilisée par la logique. La raison la plus évidente est que la logique a besoin d’une fréquence différente de celle de l’horloge externe. Mais une PLL peut aussi aider en débarrassant l’horloge externe de ses imperfections, en particulier la gigue (jitter).

Une PLL peut être ajoutée à la conception avec le code Verilog suivant :

module top(
    input clk,
    input foo,
    output reg bar_reg
);
    reg foo_reg;
    reg bar;
    wire pll_clk;

   clk_wiz_0 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk));

always @(posedge pll_clk)
  begin
    foo_reg <= foo;
    bar <= !foo_reg;
    bar_reg <= bar;
  end
endmodule

Ceci ressemble à l’exemple précédent, mais cette fois l’horloge des bascules est @pll_clk au lieu de @clk. La PLL est générée par l’IP Clocking Wizard de Vivado — un cœur IP (IP core) —, qui est utilisé dans le code Verilog comme un module nommé clk_wiz_0.

Pour cet exemple, le Clocking Wizard a été configuré pour accepter une horloge de référence à 250 MHz et pour produire une horloge à 125 MHz sur sa sortie (c’est-à-dire @clk_out1). Pour que l’exemple reste simple, clk_wiz_0 ne possède ni entrée de remise à zéro, ni sortie « locked ». Dans la plupart des conceptions réelles, il est recommandé d’activer ces ports et de les utiliser.

Autre particularité de clk_wiz_0 : l’option d’alignement de phase est activée, de sorte que les fronts de @pll_clk sont alignés sur ceux de @clk. Cette option est utile lorsque la conception comporte des ports d’entrée-sortie synchronisés avec l’horloge externe : grâce à l’alignement de phase, la relation temporelle entre l’horloge externe et l’horloge interne devient prévisible. C’est utile pour les ports d’entrée-sortie qui doivent respecter des exigences temporelles relatives à l’horloge externe.

Pour être précis avec la terminologie de Xilinx, les FPGA Xilinx comportent deux types de PLL : l’un est appelé PLL et l’autre MMCM. La différence importe peu pour cet exemple. clk_wiz_0 est un MMCM, mais par souci de clarté, je l’appellerai PLL.

Il vaut la peine de répéter que tout ce qui est dit ici à propos de la PLL s’applique à tous les FPGA du marché. L’exemple est montré avec Vivado, mais une PLL qui fait exactement la même chose que clk_wiz_0 peut être générée pour tous les FPGA.

La contrainte temporelle avec une PLL

Le point le plus important à comprendre à propos des contraintes temporelles avec une PLL est qu’il n’y a rien de particulier à faire. La contrainte est écrite par rapport à la broche externe (@clk dans cet exemple) ; si la PLL produit une horloge à une autre fréquence, c’est à l’outil de le prendre en compte.

Encore une fois : il ne devrait jamais être nécessaire d’écrire une contrainte temporelle supplémentaire parce qu’une PLL est utilisée. Si vous ressentez le besoin de le faire, il y a de fortes chances que quelque chose ne va pas dans votre conception ; « corriger » le problème avec une contrainte supplémentaire ne résoudra pas le vrai problème.

Donc, comme précédemment, la contrainte temporelle est simplement :

create_clock -period 4.000 -name clk [get_ports clk]

Ceux qui utilisent Quartus devraient prendre connaissance du piège décrit sur cette page.

Le rapport de timing avec une PLL

Examinons maintenant le rapport de timing du même chemin que dans l’exemple de la page précédente. La seule différence est l’ajout de la PLL, comme montré plus haut. L’analyse du chemin concerné est présentée ici dans le même ordre qu’elle apparaît dans le rapport. Commençons donc par le résumé de l’analyse :

Slack (MET) :             7.288ns  (required time - arrival time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_0  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_0  {rise@0.000ns fall@4.000ns period=8.000ns})
  Path Group:             clk_out1_clk_wiz_0
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            8.000ns  (clk_out1_clk_wiz_0 rise@8.000ns - clk_out1_clk_wiz_0 rise@0.000ns)
  Data Path Delay:        0.669ns  (logic 0.382ns (57.100%)  route 0.287ns (42.900%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        -0.048ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    -0.715ns = ( 7.285 - 8.000 )
    Source Clock Delay      (SCD):    -0.616ns
    Clock Pessimism Removal (CPR):    0.051ns
  Clock Uncertainty:      0.062ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      1.389ns (routing 0.002ns, distribution 1.387ns)
  Clock Net Delay (Destination): 1.218ns (routing 0.002ns, distribution 1.216ns)

On remarque plusieurs différences notables. D’abord, la ligne Requirement indique 8 ns au lieu de 4 ns auparavant. C’est normal, puisque la sortie de la PLL est à 125 MHz. L’outil a créé automatiquement une contrainte temporelle supplémentaire pour cette sortie, avec une période d’horloge de 8 ns. La période ayant augmenté de 4 ns et le chemin de données restant inchangé, la marge (slack) a augmenté d’environ 4 ns.

Autre signe de la contrainte automatique : le Path Group indique « clk_out1_clk_wiz_0 » dans ce rapport, alors qu’il indiquait « clk » auparavant. En fait, toutes les occurrences qui indiquaient « clk » indiquent maintenant « clk_out1_clk_wiz_0 ». La manière dont les contraintes automatiques apparaissent dans le rapport est abordée plus bas, dans le contexte des horloges multiples.

Une conséquence plus mineure de la PLL est que l’incertitude d’horloge (clock uncertainty) est passée à 0,062 ns, contre 0,035 ns dans l’exemple précédent. Cela vient de ce que la gigue discrète (Discrete Jitter) vaut désormais 0,103 ns, au lieu de zéro.

Passons maintenant à l’analyse de timing elle-même. Cette fois, le chemin d’horloge source, le chemin de données et le chemin d’horloge de destination sont montrés ensemble, exactement comme ils apparaissent dans le rapport réel :

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_0 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.975     1.867    pll_i/inst/clk_in1_clk_wiz_0
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -4.474    -2.607 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.501    -2.106    pll_i/inst/clk_out1_clk_wiz_0
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101    -2.005 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=3, routed)           1.389    -0.616    pll_clk
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF2_SLICEL_C_Q)
                                                      0.138    -0.478 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.241    -0.237    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.244     0.007 r  bar__0_i_1/O
                         net (fo=1, routed)           0.046     0.053    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out1_clk_wiz_0 rise edge)
                                                      8.000     8.000 r
    AG12                                              0.000     8.000 r  clk (IN)
                         net (fo=0)                   0.000     8.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     8.515 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.066     8.581    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     8.615 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.873     9.488    pll_i/inst/clk_in1_clk_wiz_0
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -3.934     5.554 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.422     5.976    pll_i/inst/clk_out1_clk_wiz_0
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     6.067 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=3, routed)           1.218     7.285    pll_clk
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism              0.051     7.336
                         clock uncertainty           -0.062     7.274
    SLICE_X49Y58         FDRE (Setup_DFF2_SLICEL_C_D)
                                                      0.067     7.341    bar_reg__0
  -------------------------------------------------------------------
                         required time                          7.341
                         arrival time                          -0.053
  -------------------------------------------------------------------
                         slack                                  7.288

Par rapport à l’exemple précédent, les chemins ne diffèrent que par un seul élément : la PLL (qui apparaît comme MMCME3_ADV_X1Y0 dans le rapport) a été insérée entre la broche d’horloge externe et le buffer d’horloge global.

Cette PLL a un effet spectaculaire : dans le chemin d’horloge source, son délai est de −4,474 ns, et dans le chemin d’horloge de destination, le même délai vaut −3,934 ns. Ce délai négatif traduit le fait que la PLL ajuste le front d’horloge de manière à ce que l’horloge globale soit légèrement en avance sur l’horloge de la broche d’entrée. On notera que le délai total de l’horloge en sortie du buffer global vaut −0,616 ns dans le chemin source, et 7,285 ns dans le chemin de destination. Cela fait 0,715 ns avant le second front d’horloge (à 8 ns).

En d’autres termes, la différence de temps entre la broche d’horloge externe et l’horloge globale (celle qui est distribuée à la logique) est presque la même dans les deux chemins. Même si le premier chemin est calculé avec des délais maximaux et le second avec des délais minimaux, le résultat global est presque identique.

Ce n’est pas une coïncidence : la PLL utilise la sortie de l’horloge globale comme référence ; la phase à l’entrée du buffer d’horloge globale est donc ajustée pour garantir la relation avec la broche d’entrée. Les écarts entre délais minimaux et maximaux sont compensés par la PLL. Les calculs de timing le reflètent : la différence entre le cas le plus rapide et le cas le plus lent n’est que de 0,1 ns. Dans l’exemple précédent, cette différence était bien plus grande parce qu’il n’y avait pas de PLL (0,575 ns ; voir « Clock pessimism removal » sur la page précédente).

Pourquoi l’horloge globale est avancée d’environ 0,6 ns par rapport à l’entrée externe, plutôt que d’une autre valeur, est une autre histoire. Ce choix facilite souvent le respect des contraintes temporelles liées aux broches d’entrée-sortie ; les outils le font donc automatiquement. Cela dit, tous les FPGA offrent la possibilité de manipuler ce délai.

Deux horloges liées

Bien souvent, une conception logique nécessite plus d’une horloge. La présence de plusieurs horloges dans un design FPGA est un sujet en soi, traité dans l’introduction aux domaines d’horloge (clock domains). Domaines d’horloge et timing sont indissociables ; il est donc recommandé de parcourir cette introduction (même rapidement) avant de continuer ici. En raison de cette relation étroite, cette introduction et la présente série se recouvrent en partie.

Dans la suite, j’emploierai souvent des expressions comme « le signal X est synchrone avec @clk ». Cela signifie que X est la sortie d’une bascule qui ne change d’état qu’en réponse à un front montant d’une horloge nommée « clk » (sauf pour les remises à zéro asynchrones (asynchronous resets), mais cela n’a pas d’importance ici). Naturellement, si deux signaux sont les sorties de bascules qui répondent à la même horloge, ces deux signaux sont « synchrones avec la même horloge ».

Examinons maintenant deux horloges générées par la même PLL. C’est intéressant car, dans la plupart des cas, ces deux horloges sont considérées comme des horloges liées (related clocks). Pour comprendre pourquoi c’est intéressant, supposons qu’une bascule soit synchrone avec l’une de ces horloges, et qu’une autre bascule soit synchrone avec la seconde. Dans ce cas, on peut connecter des signaux entre ces deux bascules comme si elles étaient synchrones avec la même horloge. Les outils FPGA garantissent alors le respect des exigences temporelles.

Pour mieux comprendre comment travailler avec plusieurs horloges, une page dédiée traite des horloges liées et des horloges non liées (unrelated clocks). Cette page sert d’introduction au changement de domaine d’horloge (clock domain crossing). Nous nous concentrons ici sur les aspects temporels des horloges liées, et en particulier sur le rapport de timing associé à de telles horloges.

Pour produire les rapports de timing ci-dessous, une PLL différente (toujours un IP Clocking Wizard, c’est-à-dire un cœur IP (IP core)) a été utilisée dans l’exemple. Cette nouvelle PLL s’appelle clk_wiz_1. Le code Verilog suivant a été utilisé :

module top(
    input clk,
    input foo,
    output reg bar_reg
);
    reg foo_reg;
    reg bar;
    wire pll_clk_8, pll_clk_6;

   clk_wiz_1 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk_8),
    .clk_out2(pll_clk_6));

always @(posedge pll_clk_8)
  foo_reg <= foo;

always @(posedge pll_clk_6)
  begin
    bar <= !foo_reg;
    bar_reg <= bar;
  end

Comme précédemment, l’entrée de cette PLL (@clk) est à 250 MHz, mais elle possède deux sorties : l’une est connectée à @pll_clk_8, qui fonctionne à 125 MHz (la période d’horloge est donc de 8 ns, d’où le nom du signal). C’est exactement comme @pll_clk dans l’exemple précédent. La seconde sortie est connectée à @pll_clk_6, dont la période vaut 6 ns, soit environ 166,67 MHz.

Comme @pll_clk_8 et @pll_clk_6 sont générées par la même PLL, ce sont des horloges liées.

clk_wiz_1 a également l’option d’alignement de phase activée, notamment pour rester cohérent avec l’exemple précédent. Et au cas où il y aurait un doute : il n’y a toujours qu’une seule contrainte temporelle, celle d’avant :

create_clock -period 4.000 -name clk [get_ports clk]

Comprendre le résumé des horloges

Les informations sur toutes les horloges sont résumées au début du rapport de timing. Je n’en parle qu’à présent, parce que ce résumé devient plus intéressant lorsqu’il y a plusieurs horloges. Tous les outils FPGA génèrent un résumé de ce genre dans le rapport, et il est toujours bon d’y jeter un œil :

-------------------------------------------------------------------------
| Clock Summary
| -------------
-------------------------------------------------------------------------

Clock                 Waveform(ns)         Period(ns)      Frequency(MHz)
-----                 ------------         ----------      --------------
clk                   {0.000 2.000}        4.000           250.000
  clk_out1_clk_wiz_1  {0.000 4.000}        8.000           125.000
  clk_out2_clk_wiz_1  {0.000 3.000}        6.000           166.667
  clkfbout_clk_wiz_1  {0.000 2.000}        4.000           250.000

Le principal avantage de cette partie est qu’elle est facile à comprendre et qu’elle permet de vérifier l’erreur la plus courante en matière de contraintes temporelles : que chaque horloge ait la bonne fréquence.

Ce résumé montre que la contrainte temporelle a été correctement interprétée : il y a une horloge externe nommée clk, avec une période de 4 ns. S’y ajoutent trois horloges dérivées : clk_out1_clk_wiz_1, clk_out2_clk_wiz_1 et clkfbout_clk_wiz_1. Comme leurs noms l’indiquent, elles ont été générées automatiquement à cause de la PLL nommée clk_wiz_1.

Les deux premières horloges (clk_out1_clk_wiz_1 et clk_out2_clk_wiz_1) sont les deux sorties de la PLL. La troisième (clkfbout_clk_wiz_1) sert à la PLL pour ajuster la phase des horloges globales sur l’horloge externe. clkfbout_clk_wiz_1 a la même fréquence que @clk.

Lorsque l’option d’alignement de phase est activée, l’horloge de retour de la PLL (clkfbout_clk_wiz_1) est connectée au buffer d’horloge global. Comme une PLL synchronise toujours son horloge d’entrée avec son horloge de retour (en les alignant ou en maintenant un délai fixe entre elles), le fait que clkfbout_clk_wiz_1 soit une horloge globale garantit aussi un délai connu entre l’horloge d’entrée et les autres horloges de sortie.

Point important : clk_out1_clk_wiz_1, clk_out2_clk_wiz_1 et clkfbout_clk_wiz_1 ne définissent pas des horloges qui existent réellement. Ce sont seulement des symboles d’horloges utilisés par le logiciel pour les calculs de timing. Leurs formes d’onde, visibles dans le résumé, montrent d’ailleurs qu’il s’agit d’horloges théoriques : ces formes d’onde reflètent le rapport cyclique de chacune. Mais le fait que les quatre horloges (clk et les trois horloges théoriques) aient un front montant à 0 ns exactement ne signifie rien. En particulier, cela ne veut pas dire que ces quatre horloges sont parfaitement alignées. Même si elles le sont, les calculs de timing le montrent autrement, et l’alignement réel n’est pas parfait.

La manière dont ces horloges théoriques sont utilisées est examinée plus bas, à propos du calcul de timing qui implique deux de ces horloges.

Autre point important à propos de ces horloges théoriques : aucune contrainte temporelle explicite ne préside à leur création. La création de clk_out1_clk_wiz_* n’apparaît nulle part dans les sources de la conception ni dans les fichiers générés par les outils. Dans la plupart des cas, tenter d’écrire une telle contrainte serait incorrect, car les délais du chemin d’horloge ne seraient alors pas pris en compte correctement. Si de telles contraintes sont écrites, l’outil calculera mal les relations temporelles entre les horloges, et les chemins entre domaines d’horloge liés ne seront pas calculés correctement.

Donc, encore une fois, une seule contrainte temporelle doit suffire à générer automatiquement les contraintes pour toutes les horloges de sortie de la PLL.

Le rapport de timing avec deux horloges liées

Comme le montre le code Verilog ci-dessus, @foo_reg est synchrone avec @pll_clk_8, et @bar est synchrone avec @pll_clk_6. Par conséquent, l’instruction qui met à jour @bar implique un changement de domaine d’horloge (clock domain crossing) :

bar <= !foo_reg;

Les deux horloges étant liées, les outils garantissent que les exigences temporelles de @bar sont respectées grâce à ce calcul (pour tsu) :

Slack (MET) :             0.475ns  (required time - arrival time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_1  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out2_clk_wiz_1  {rise@0.000ns fall@3.000ns period=6.000ns})
  Path Group:             clk_out2_clk_wiz_1
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            2.000ns  (clk_out2_clk_wiz_1 rise@18.000ns - clk_out1_clk_wiz_1 rise@16.000ns)
  Data Path Delay:        1.160ns  (logic 0.307ns (26.466%)  route 0.853ns (73.534%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        -0.250ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    -0.681ns = ( 17.319 - 18.000 )
    Source Clock Delay      (SCD):    -0.600ns = ( 15.400 - 16.000 )
    Clock Pessimism Removal (CPR):    -0.169ns
  Clock Uncertainty:      0.182ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.120ns
  Clock Net Delay (Source):      1.369ns (routing 0.002ns, distribution 1.367ns)
  Clock Net Delay (Destination): 1.208ns (routing 0.002ns, distribution 1.206ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_1 rise edge)
                                                     16.000    16.000 r
    AG12                                              0.000    16.000 r  clk (IN)
                         net (fo=0)                   0.000    16.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738    16.738 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.105    16.843    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049    16.892 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.975    17.867    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -4.438    13.429 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.501    13.930    pll_i/inst/clk_out1_clk_wiz_1
    BUFGCE_X1Y1          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101    14.031 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=1, routed)           1.369    15.400    pll_clk_8
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF_SLICEL_C_Q)
                                                      0.139    15.539 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.807    16.346    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.168    16.514 r  bar__0_i_1/O
                         net (fo=1, routed)           0.046    16.560    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out2_clk_wiz_1 rise edge)
                                                     18.000    18.000 r
    AG12                                              0.000    18.000 r  clk (IN)
                         net (fo=0)                   0.000    18.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515    18.515 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.066    18.581    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034    18.615 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.873    19.488    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT1)
                                                     -3.890    15.598 r  pll_i/inst/mmcme3_adv_inst/CLKOUT1
                         net (fo=1, routed)           0.422    16.020    pll_i/inst/clk_out2_clk_wiz_1
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091    16.111 r  pll_i/inst/clkout2_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=2, routed)           1.208    17.319    pll_clk_6
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism             -0.169    17.150
                         clock uncertainty           -0.182    16.967
    SLICE_X49Y58         FDRE (Setup_DFF2_SLICEL_C_D)
                                                      0.067    17.034    bar_reg__0
  -------------------------------------------------------------------
                         required time                         17.034
                         arrival time                         -16.560
  -------------------------------------------------------------------
                         slack                                  0.475

Comme mentionné plus haut, un calcul de timing est une expérience imaginaire dans laquelle un chronomètre démarre avec les fronts d’horloge. Dans l’exemple précédent, le chronomètre partait de 0 ns. Ici, il part avec le front situé à 16 ns. La raison en est que les périodes des deux horloges ne sont pas égales. Le calcul porte sur la combinaison la plus défavorable entre la première horloge et la seconde.

Les fronts montants de la première horloge ont lieu à 0 ns, 8 ns, 16 ns, 24 ns, etc. Ceux de la seconde ont lieu à 0 ns, 6 ns, 12 ns, 18 ns, 24 ns, etc. L’intervalle le plus court entre un front de la première et un front de la seconde est donc celui entre le front de la première à 16 ns et celui de la seconde à 18 ns. C’est précisément la situation examinée par ce calcul de timing.

Cela signifie que le chemin de données ne dispose que d’environ 2 ns, ce qui équivaut à 500 MHz. C’est une exigence très sévère, mais comme il n’y a qu’une seule LUT dans le chemin de données et que les deux bascules sont dans la même slice, l’objectif est facilement atteint. Cet exemple montre néanmoins pourquoi il est important de choisir, pour des horloges liées, des fréquences qui s’accordent bien. On choisit souvent une fréquence multiple de l’autre. Cela évite des scénarios comme l’intervalle de 2 ns de cet exemple.

Au fait, s’il est impossible de choisir des fréquences qui s’accordent bien, la solution consiste à traiter les horloges comme non liées (unrelated clocks).

Les horloges dérivées

J’ai dit plus haut que clk_out1_clk_wiz_1 et clk_out2_clk_wiz_1 sont des horloges théoriques. Voici comment cela se manifeste dans le rapport de timing : notez que les deux chemins sont calculés avec la broche externe (AG12) comme point de départ. Comment cela peut-il avoir un sens ? En réalité, le signal sur cette broche est l’horloge de référence, et non l’une ou l’autre de ces deux horloges.

Prenons clk_out1_clk_wiz_1 comme exemple : l’idée du calcul est de faire comme s’il y avait une horloge à 125 MHz sur la broche externe. En réalité, l’horloge à 125 MHz n’existe qu’en sortie de la PLL, c’est-à-dire à la sortie de MMCME3_ADV_X1Y0. Mais plutôt que de faire intervenir dans le calcul la transformation de fréquence opérée par la PLL, on utilise une horloge théorique. Cette horloge possède une forme d’onde idéale qui commence à 0 ns. La PLL est alors traitée comme si elle ne changeait aucune fréquence, mais se contentait d’ajouter des délais.

L’horloge théorique (par exemple clk_out1_clk_wiz_1) définit donc la forme d’onde de l’horloge (fréquence, rapport cyclique, gigue, etc.). En revanche, elle ne définit pas les relations temporelles avec les autres horloges qui existent en tant que signaux réels sur le FPGA.

Comment voit-on alors que @pll_clk_6 et @pll_clk_8 sont effectivement alignées ? La réponse se trouve dans la comparaison entre le timing réel et le timing idéal de ces horloges. Par exemple, dans le calcul ci-dessus, le front théorique de clk_out1_clk_wiz_1 est à 16 ns, tandis que celui de clk_out2_clk_wiz_1 est à 18 ns, soit 2 ns plus tard. Comparons maintenant les fronts en sortie de l’arbre d’horloge global : selon le rapport, le front de la première horloge arrive à 15,400 ns, et celui de la seconde à 17,319 ns. D’après le calcul, la différence est donc de 1,919 ns au lieu de 2 ns. Ce n’est que 0,081 ns de moins que la différence idéale. Les horloges sont donc bel et bien alignées.

Comparons avec l’exemple précédent, où il n’y avait qu’une seule horloge : la différence idéale entre deux fronts était la période d’horloge, soit 8 ns. D’après le rapport de timing correspondant (voir plus haut), le premier front arrivait à −0,616 ns et le second à 7,285 ns. La différence idéale était de 8 ns, mais elle était en réalité de 7,901 ns. Le second front arrivait donc 0,099 ns plus tôt que prévu.

Avec deux horloges liées, l’écart par rapport à la différence idéale est donc de 0,081 ns. Avec une seule horloge, cet écart était du même ordre, 0,099 ns. Dans les deux cas, cet écart s’explique par le fait que le calcul du premier front utilise les délais maximaux, tandis que les délais minimaux sont utilisés pour le second front.

La conclusion est donc que @pll_clk_6 et @pll_clk_8 sont alignées avec à peu près la même précision que s’il s’agissait d’une seule horloge.

À noter que ces horloges seraient alignées entre elles même sans l’option d’alignement de phase de la PLL : les sorties d’une PLL sont généralement alignées de toute façon. Cet alignement est assuré par l’utilisation de buffers d’horloge globaux dont les délais sont presque identiques, quel que soit leur fan-out. Autrement dit, le nombre d’éléments logiques connectés à chacun de ces buffers n’a pas d’importance : le délai entre la sortie de la PLL et la destination est à peu près le même.

Ce que nous apprend l’étude des horloges liées

L’analyse de ce rapport de timing montre qu’un chemin entre deux domaines d’horloge liés est à peu près équivalent à un chemin interne à un domaine d’horloge. Ce n’est pourtant pas exactement la même chose. En particulier, l’incertitude d’horloge passe de 0,062 ns à 0,182 ns : chaque horloge a sa propre gigue, et l’alignement n’est pas parfait.

Ce rapport de timing montre aussi comment l’alignement des deux horloges se répercute dans les calculs.

Lorsqu’une conception FPGA comporte des changements de domaine d’horloge, il est bon d’examiner dans le rapport de timing les interactions entre ces horloges. Le but est de s’assurer que les outils traitent les horloges comme prévu. Voici les deux points les plus importants à vérifier :

Vivado peut créer un Clock Interaction Report, qui affiche graphiquement les changements de domaine d’horloge de la conception et la manière dont l’outil traite chacun d’eux. D’autres outils FPGA offrent une fonctionnalité comparable, par exemple le CDC Viewer de Quartus Pro. Lorsque c’est possible, il est recommandé de générer et d’examiner ce rapport.

Autre point à examiner : l’alignement des horloges. Si la conception utilise par erreur une horloge non alignée, cela peut créer des difficultés inutiles pour respecter les contraintes temporelles. Par exemple, s’il existe un chemin entre @clk et @pll_clk_8, les outils appliqueront les contraintes et veilleront à ce que ce chemin fonctionne de manière fiable. Remarquez toutefois que @clk est l’horloge de référence qui entre dans la PLL, tandis que @pll_clk_8 est la sortie de cette PLL. Ces deux horloges ne sont donc pas alignées. Par conséquent, les outils peuvent travailler inutilement dur pour respecter les contraintes sur ce chemin, et d’autres chemins peuvent échouer à cause de cet effort superflu.

Et, encore une fois, le sujet des domaines d’horloge est traité dans cette série de pages.

thold est important lui aussi

Tous les calculs de timing que j’ai montrés jusqu’ici concernaient la condition sur tsu. Il est naturel de se concentrer sur cette condition, car lorsque les outils n’arrivent pas à respecter les contraintes temporelles, c’est presque toujours parce qu’au moins un chemin n’a pas satisfait la condition sur tsu.

Il est néanmoins important de garder thold à l’esprit : pour respecter cette condition, les outils ralentissent parfois artificiellement le chemin de données en ajoutant du délai de routage. Même si la condition sur thold est rarement citée comme raison d’un échec de timing, elle peut en être la cause cachée.

En fait, un phénomène de ce genre s’est produit avec le délai de routage du fil qui relie les deux bascules : dans tous les rapports de timing ne faisant intervenir qu’une seule horloge, ce fil se trouvait dans la même slice (SLICE_X49Y58). Le délai de ce fil y était donc de 0,241 ns. Mais lorsque deux horloges étaient impliquées dans le chemin, ce délai est monté à 0,807 ns.

L’explication est que 0,241 ns est le délai minimal que l’on peut obtenir entre deux bascules placées dans la même slice. Le délai plus long (0,807 ns) résulte d’un routage différent de ce fil. Cela n’a rien d’accidentel : les outils ont délibérément allongé ce routage afin de respecter la condition sur thold. Cela se voit aussi dans la ligne « Data Path Delay » du rapport : la part de la logique n’est que de 26,5 %, le reste étant du routage. Cela suffit à indiquer qu’il s’est passé quelque chose. Nous y reviendrons plus bas.

Analyse de la condition sur thold

Rappelons, d’après la page théorique de cette série, que thold est la durée pendant laquelle l’entrée de données de la deuxième bascule doit rester stable après le front d’horloge. Décrivons une situation où thold n’est pas respecté : un front d’horloge arrive sur la première bascule et la deuxième en reçoit un à peu près au même moment (ces fronts peuvent provenir de la même horloge ou d’horloges différentes). Sous l’effet de ce front, la première bascule met à jour sa sortie après un certain délai (de l’horloge vers la sortie). Mais la valeur mise à jour atteint la deuxième bascule trop tôt. Celle-ci ne peut donc pas échantillonner la valeur précédente de manière fiable : la nouvelle valeur est arrivée avant que la deuxième bascule ait fini de réagir à son front. Autrement dit, la condition sur thold n’est pas respectée.

Lorsque les deux bascules utilisent la même horloge, ce problème survient principalement à cause d’un décalage d’horloge (clock skew) : si le front arrive plus tôt sur la première bascule, celle-ci peut changer sa sortie trop tôt. Le signal mis à jour peut alors atteindre la deuxième bascule assez vite pour violer la condition sur thold. Notons que c’est le même front qui atteint les deux bascules ; ni la fréquence ni la gigue d’horloge n’ont donc d’importance. Seul un délai différent (c’est-à-dire le décalage d’horloge) entre en jeu.

Mais pour l’analyse qui suit, nous restons sur le dernier exemple, qui fait intervenir deux horloges : @pll_clk_8 et @pll_clk_6. Une analyse avec une seule horloge serait semblable, mais moins intéressante, car il est trop facile de respecter thold avec une seule horloge.

Le rapport que nous allons examiner porte donc sur deux horloges liées. Ces deux horloges ont des fréquences différentes et, comme précédemment, il existe différentes combinaisons entre les instants des fronts de la première et de la seconde. Mais contrairement au calcul de tsu, le pire cas pour thold se produit lorsque les deux fronts sont à 0 ns : les violations de thold surviennent quand les deux fronts sont presque simultanés ; aucune autre combinaison n’est donc pire.

Fort de ces observations, regardons le rapport de timing :

Slack (MET) :             0.093ns  (arrival time - required time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_1  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out2_clk_wiz_1  {rise@0.000ns fall@3.000ns period=6.000ns})
  Path Group:             clk_out2_clk_wiz_1
  Path Type:              Hold (Min at Fast Process Corner)
  Requirement:            0.000ns  (clk_out2_clk_wiz_1 rise@0.000ns - clk_out1_clk_wiz_1 rise@0.000ns)
  Data Path Delay:        0.458ns  (logic 0.104ns (22.707%)  route 0.354ns (77.293%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        0.127ns (DCD - SCD - CPR)
    Destination Clock Delay (DCD):    -0.542ns
    Source Clock Delay      (SCD):    -0.248ns
    Clock Pessimism Removal (CPR):    -0.421ns
  Clock Uncertainty:      0.182ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.120ns
  Clock Net Delay (Source):      0.495ns (routing 0.002ns, distribution 0.493ns)
  Clock Net Delay (Destination): 0.576ns (routing 0.002ns, distribution 0.574ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_1 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.339     0.339 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.025     0.364    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.015     0.379 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.405     0.784    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -1.721    -0.937 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.167    -0.770    pll_i/inst/clk_out1_clk_wiz_1
    BUFGCE_X1Y1          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.027    -0.743 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=1, routed)           0.495    -0.248    pll_clk_8
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF_SLICEL_C_Q)
                                                      0.049    -0.199 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.343     0.144    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.055     0.199 r  bar__0_i_1/O
                         net (fo=1, routed)           0.011     0.210    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out2_clk_wiz_1 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.595     0.595 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.042     0.637    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.022     0.659 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.457     1.116    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT1)
                                                     -2.474    -1.358 r  pll_i/inst/mmcme3_adv_inst/CLKOUT1
                         net (fo=1, routed)           0.209    -1.149    pll_i/inst/clk_out2_clk_wiz_1
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.031    -1.118 r  pll_i/inst/clkout2_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=2, routed)           0.576    -0.542    pll_clk_6
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism              0.421    -0.121
                         clock uncertainty            0.182     0.061
    SLICE_X49Y58         FDRE (Hold_DFF2_SLICEL_C_D)
                                                      0.056     0.117    bar_reg__0
  -------------------------------------------------------------------
                         required time                         -0.117
                         arrival time                           0.210
  -------------------------------------------------------------------
                         slack                                  0.093

D’abord, quelques différences évidentes : le Path Type est Hold, et non Setup comme précédemment. C’est normal. On lit ensuite « Min at Fast Process Corner », soit l’inverse de ce qu’indiquait le chemin de setup : ce calcul utilise les délais minimaux, et non maximaux, pour le chemin de données. Cela convient pour une analyse dans le pire cas de la situation où l’entrée de données change trop tôt.

La Requirement est de 0 ns, ce qui est typique pour un chemin de hold. Les fréquences des horloges n’y jouent aucun rôle : le scénario examiné est celui où les deux horloges ont un front montant au même instant.

Quant aux chemins d’horloge, remarquez que les délais des composants du chemin source sont systématiquement plus courts que ceux des mêmes composants du chemin de destination (et non plus longs). Cela aussi est cohérent avec l’objectif du calcul : le pire cas se produit quand la donnée change trop tôt par rapport à l’arrivée du front d’horloge sur la deuxième bascule. L’analyse multi-corner a été expliquée sur la page précédente.

Mais le plus important dans ce rapport est que la marge est faible : seulement 0,093 ns. C’est souvent le signe que les outils ont dû faire un effort pour respecter l’exigence. Cependant, une petite marge n’indique pas forcément qu’il y avait un problème à résoudre.

Une petite marge est assez courante lorsqu’on fait une analyse de thold. Pourquoi ce chemin est-il alors suspect ? Principalement à cause du délai de routage plus important entre deux bascules de la même slice (SLICE_X49Y58), comme mentionné plus haut. Il est probable qu’au début du placement et du routage (place and route), le logiciel ait détecté un non-respect de thold entre ces deux bascules. Comment cela a-t-il été corrigé ?

Un non-respect de thold signifie que le signal de données arrive trop tôt par rapport au front d’horloge de la deuxième bascule. On corrige cela en ajoutant artificiellement du délai sur le chemin de données. L’entrée de données change alors de valeur un peu plus tard sur la deuxième bascule. Les outils ont probablement allongé le câblage entre les deux bascules. Cela augmente le délai de routage et résout le problème de thold. Une telle augmentation de délai aurait pu poser un problème pour respecter tsu, mais ce ne fut pas le cas ici : la contrainte temporelle a été respectée pour ce chemin (autrement dit, les conditions sur tsu et thold sont toutes deux satisfaites).

Cet exemple montre néanmoins comment la nécessité de résoudre un problème de thold peut créer un problème de tsu en apparence sans rapport. Il faut y penser lorsqu’un chemin présente un rapport vraiment faible entre délai logique et délai de routage. Un grand délai de routage peut bien entendu avoir d’autres causes, notamment un fan-out élevé. Mais lorsque le fan-out est faible (comme ici, où il est de 1), il vaut la peine de se demander si ce grand délai de routage n’a pas été introduit délibérément par les outils pour résoudre un problème de thold.

Pourquoi, alors, n’y a-t-il eu un problème de thold que dans le cas à deux horloges ? Parce qu’avec deux horloges, l’incertitude sur l’instant d’arrivée des fronts sur chacune des bascules est plus grande. Plusieurs facteurs y contribuent, notamment le décalage d’horloge et la gigue. Comme le calcul de thold se fait de 0 ns à 0 ns, il est plus sensible aux petites incertitudes sur l’arrivée des fronts.

L’incertitude associée à deux horloges liées exige donc souvent des mesures correctives pour respecter thold. Ces corrections sont naturellement effectuées automatiquement par les outils. Il est néanmoins important d’en avoir conscience lorsqu’on rencontre des difficultés à respecter les contraintes temporelles.

Résumé

Les deux dernières pages de cette série ont montré quelques exemples de rapports de timing, tous issus d’une seule contrainte temporelle particulière. Ces rapports concernaient aussi un exemple de logique simple et précis. Espérons que ces exemples auront contribué à établir les bases de la compréhension du timing.

À ce stade, il est recommandé d’examiner les rapports de timing de vos propres conceptions, afin de voir comment les mêmes principes s’appliquent à des chemins contenant plus d’une LUT.

La page suivante ouvre la discussion sur les problèmes de timing et la manière de les résoudre.

Cette page a été traduite de l’anglais par une machine. En cas de doute, veuillez vous reporter au texte original
Copyright © 2021-2026. All rights reserved. (dcc38493)