Cette page fait partie d’une série de pages consacrée au timing. Les pages précédentes ont expliqué la théorie derrière les calculs de timing, abordé la contrainte de période d’horloge et introduit la convergence temporelle (timing closure). Mais que faire si vous avez essayé de bien faire les choses et qu’un problème de timing subsiste malgré tout ? Cette page tente de répondre à cette question.
Introduction
Sur la page précédente, j’ai tenté de vous convaincre qu’il n’existe pas de méthode unique pour résoudre un problème de convergence temporelle (timing closure). Il est parfois correct de se concentrer sur le chemin critique (critical path), et parfois non. Parfois, le problème se règle facilement par une simple modification des réglages de l’outil, et parfois c’est bien plus difficile que cela. Rien ne remplace l’expérience et le jugement qu’il faut mobiliser pour trouver la cause profonde du problème. Il est impossible de résumer la convergence temporelle à une simple liste de vérifications.
Et pourtant, avoir une liste de stratégies possibles aide souvent. J’ai donc rassemblé sur cette page quelques pistes qui valent la peine d’être explorées lorsqu’on est confronté à un problème de timing. Si vous lisez ceci parce que vous avez un problème de timing précis à résoudre, il est possible que l’une de ces idées vous conduise à la solution. Mais n’attendez pas de trouver votre solution toute écrite ici.
Rappelez-vous aussi que cette série de pages ne s’arrête pas ici. J’ai choisi de parler de convergence temporelle avant beaucoup d’autres sujets, par souci de motivation. Cependant, les informations données dans les pages suivantes sont également pertinentes.
Pour la même raison, la discussion sur les contraintes temporelles d’entrées-sorties est reportée à plus tard. Pour l’instant, je me concentre sur les chemins (paths) qui commencent et se terminent à l’intérieur du FPGA.
Voici donc quelques idées à considérer pour la convergence temporelle.
Idée n° 1 : Corriger la conception logique
C’est toujours la solution la moins séduisante. C’est particulièrement vrai lorsque la conception fonctionne déjà correctement. On n’a pas envie de modifier quelque chose qui marche. Pourtant, la raison profonde du problème est souvent que le code Verilog n’a pas été écrit avec suffisamment de soin pour atteindre les performances demandées. Une modification de la conception logique règle le problème une fois pour toutes, au lieu de traîner des difficultés sans fin.
La page précédente contient quelques suggestions pour écrire une logique rapide. Et il vaut la peine de le répéter : gardez toujours le timing à l’esprit pendant le développement. Il est bien plus difficile de corriger des problèmes de timing que d’écrire correctement le code Verilog dès le départ.
Idée n° 2 : Réduire le fan-out
Lorsqu’un fil (net) a un fan-out élevé, le délai de propagation (propagation delay) augmente pour deux raisons principales :
- La capacité du fil physique est plus grande, donc il faut plus de charge électrique pour changer l’état logique.
- Pour les outils, il devient plus difficile de trouver un routage à faible délai vers toutes les destinations du fil : ces destinations sont des éléments logiques dispersés sur la matrice logique. Plus il y a de destinations, plus il est difficile d’optimiser le timing pour que tous les fils aient un faible délai.
Si une remise à zéro synchrone (synchronous reset) est utilisée dans la conception, ce signal aura probablement un fan-out élevé. Ce sujet est traité sur une page dédiée.
Cependant, tout signal qui atteint beaucoup d’éléments logiques peut provoquer des problèmes de timing à cause d’un fan-out élevé. Parfois, ce fan-out élevé saute aux yeux (par exemple les signaux d’activation d’horloge, clock enable) ; parfois, il n’est pas si facile de le prévoir. Les outils FPGA peuvent généralement aider en listant les fils qui ont les fan-outs les plus élevés.
Il existe deux méthodes pour garder le fan-out faible :
- Les outils FPGA ont une limite de fan-out. Lorsque cette limite est atteinte, les outils dupliquent le registre qui est à la source du fil. Il est possible de modifier la valeur de cette limite pour chaque registre grâce à des contraintes de synthèse. On peut aussi modifier la limite globale en changeant les paramètres du synthétiseur (synthesizer).
- Modifier le code Verilog : répliquer explicitement le registre à fort fan-out en plusieurs registres.
Bien entendu, les deux méthodes aboutissent au même résultat : le registre à fort fan-out est répliqué en plusieurs registres. Si les outils peuvent le faire automatiquement (première méthode), pourquoi s’embêter à le faire à la main (seconde méthode) ?
La seconde méthode demande plus d’efforts, mais elle présente un avantage important : il est possible de répliquer le registre de manière réfléchie. Gardez à l’esprit que le but n’est pas seulement de réduire le fan-out. Il est aussi important que la sortie de chaque registre soit distribuée à des éléments logiques situés dans une petite zone de la matrice logique. Sinon, on se retrouve avec des délais de routage importants à cause de la distance physique. Si le code Verilog est écrit en pensant au fan-out, on peut garantir des connexions courtes entre les éléments logiques. Cela est démontré pour une remise à zéro synchrone sur une autre page.
En revanche, si ce sont les outils FPGA qui se chargent de répliquer les registres, le résultat peut être moins efficace. L’amélioration du délai de routage dépend de l’algorithme qui décide comment chaque registre répliqué est utilisé. La qualité du résultat dépend de l’outil FPGA employé.
À noter que, par défaut, lorsque le synthétiseur (synthesizer) détecte deux registres qui se comportent exactement de la même manière, il les fusionne automatiquement en un seul registre. Ainsi, si un registre est répliqué dans le code Verilog, le synthétiseur remplacera toutes les répliques par un seul registre. C’est souvent le cas même si ces registres équivalents sont définis dans des modules différents. Pour éviter cette fusion, il faut désactiver explicitement cette fonctionnalité. Un moyen courant d’y parvenir consiste à utiliser des attributs de synthèse, par exemple « dont_touch », « dont_merge » ou « keep ».
Idée n° 3 : Vérifier le floorplanning
Par défaut, le placement des éléments logiques sur la matrice logique est déterminé automatiquement par les outils FPGA (plus précisément par le placeur). Il est néanmoins possible de demander que certains éléments logiques soient placés dans des zones précises du FPGA. On peut aussi demander qu’un élément logique particulier soit placé à une position précise. Ces demandes portent le nom de « floorplanning ». Elles s’expriment par des contraintes de placement, souvent des commandes Tcl dont la syntaxe ressemble à celle des contraintes temporelles (timing constraints).
Dans la plupart des cas, les contraintes de placement rendent plus difficile le respect des contraintes temporelles. La première raison est évidente : lorsque les choix du placeur sont limités, le résultat ne peut qu’être moins bon que sans limitation. Il y a cependant des raisons plus concrètes :
- Le floorplanning peut forcer beaucoup de logique dans une petite zone du FPGA. Cela peut entraîner une congestion du routage : les éléments logiques de cette zone exigent plus de ressources de routage que d’habitude. Le routeur doit alors utiliser des ressources sous-optimales, d’où un délai de routage sous-optimal et éventuellement un échec à respecter les exigences.
- Les contraintes de placement peuvent forcer des éléments logiques à être éloignés les uns des autres, alors qu’il serait préférable de les rapprocher. Le placeur est donc empêché de déplacer les éléments pour réduire le délai de routage.
- Le floorplanning peut créer des zones qui deviennent des obstacles au routage. Par exemple, supposons que le floorplanning ait créé une zone densément peuplée d’éléments logiques. Le routage d’autres signaux peut être obligé de contourner cette zone congestionnée. Ce routage doit alors parcourir une plus grande distance, et le délai de routage augmente.
Dans la plupart des designs, il vaut mieux éviter le floorplanning et laisser ainsi au placeur la liberté d’optimiser le placement. Il existe cependant des cas où l’utilisation de contraintes de placement est courante, par exemple :
- Un bloc IP (IP core) peut contenir des contraintes de placement pour les éléments logiques qu’il génère. Par exemple, une IP qui implémente une interface PCIe crée souvent des contraintes de placement déterminant la position de ses composants les plus importants : les émetteurs-récepteurs, les PLL et le cœur matériel PCIe dédié. De telles contraintes sont généralement nécessaires et correctes.
- Le FPGA peut être divisé en régions, chacune ne contenant que certains modules. C’est le sens originel du floorplanning. L’intérêt d’une telle division peut être de permettre à différentes équipes d’un projet de travailler indépendamment.
- La reconfiguration partielle (partial reconfiguration) est une fonctionnalité qui permet de charger un bitstream dans le FPGA pendant qu’il fonctionne, de sorte que seule une partie du FPGA est affectée. Pour que cela fonctionne, le floorplanning est nécessaire : le FPGA est divisé en zones qui restent intactes à l’arrivée du nouveau bitstream, et en zones mises à jour par celui-ci.
Pour la convergence temporelle, il est important d’avoir conscience que les contraintes de placement peuvent être une cause de problèmes. En particulier, si les délais de routage sont plus grands que prévu, la cause profonde peut être l’incapacité du placeur à optimiser les positions des éléments logiques. Rappelez-vous que le floorplanning peut avoir un effet négatif sur des chemins sans rapport avec les éléments logiques dont le placement est contraint.
Idée n° 4 : Vérifier les contraintes temporelles
Les contraintes temporelles (timing constraints) sont cruciales pour le fonctionnement fiable du FPGA. Elles devraient donc être vérifiées avant l’implémentation du projet. Pourtant, il arrive qu’elles se révèlent fausses malgré tout. Les erreurs peuvent devenir évidentes pendant le processus de convergence temporelle. Cela ne devrait pas arriver, mais lorsque cela arrive, mieux vaut évidemment corriger le problème.
Il existe une page entière consacrée à la vérification des contraintes temporelles. Ici, je ne mentionnerai que deux erreurs courantes qui peuvent mener à des problèmes de convergence temporelle :
- Application inutile de contraintes temporelles sur des chemins entre horloges non liées (unrelated clocks).
- Application inutile de contraintes temporelles sur des remises à zéro asynchrones (asynchronous resets).
D’abord, au sujet des horloges non liées : le sujet des changements de domaine d’horloge (clock domain crossing) a déjà été abordé plus tôt. Il y a plusieurs raisons pour lesquelles il importe que les contraintes temporelles reflètent quelles horloges sont liées (related clocks) et lesquelles ne le sont pas. La raison la plus importante est de garantir le bon fonctionnement de la logique, mais la convergence temporelle est aussi concernée : si deux horloges sont traitées inutilement comme liées par les outils, cela conduit à appliquer inutilement des exigences temporelles aux chemins entre ces deux horloges. Les outils gaspillent alors leurs efforts sur ces chemins, au détriment des chemins qui en ont réellement besoin.
La contrainte temporelle qui corrige ce problème est expliquée plus loin dans cette série de pages.
À propos des remises à zéro asynchrones : dans la plupart des cas, il est nécessaire d’imposer des contraintes temporelles sur un chemin qui se termine sur une remise à zéro asynchrone. Mais il arrive que cela ne soit pas nécessaire. Par exemple, s’il est garanti que l’horloge ne sera pas active au moment où la remise à zéro repasse à l’état inactif. Un autre cas est celui où la bascule qui reçoit la remise à zéro asynchrone dispose d’un mécanisme de protection contre les violations temporelles, comme dans un changement de domaine d’horloge. Dans ces situations, les efforts des outils pour respecter les exigences sont inutiles.
Il peut être difficile de constater que de tels problèmes compliquent la convergence temporelle : parfois, le chemin critique n’a rien à voir avec les deux horloges inutilement traitées comme liées. Si une remise à zéro asynchrone détourne les efforts des outils, c’est encore plus dur à repérer. Dans de telles situations, essayer d’améliorer le timing en se concentrant sur le chemin critique peut être vain.
Les contraintes temporelles peuvent bien entendu être fausses de bien d’autres manières. La situation décrite ici n’est qu’une possibilité. Un problème de timing peut donc être une bonne occasion de revoir les contraintes temporelles en général.
Idée n° 5 : Réessayer, tout simplement
Rappelons que le processus de placement et routage (place and route) commence par disperser les éléments logiques de façon assez arbitraire sur la matrice logique. Les outils essaient ensuite d’améliorer le timing par des tentatives répétées. La réussite de ce processus repose donc sur une certaine part de chance. Il est également possible qu’un comportement un peu différent de l’algorithme de placement et routage donne de meilleurs résultats, même sans explication logique.
Si les contraintes temporelles échouent et que la marge (slack) négative est relativement faible (environ 10 à 20 % du délai total), il peut suffire de réessayer. Mais relancer simplement l’implémentation ne donnera probablement rien : la plupart des logiciels FPGA sont conçus pour reproduire exactement le même résultat lorsqu’on les exécute avec la même entrée. Il faut donc changer quelque chose avant de relancer. Ce changement n’a pas à être lié au chemin critique. Le but est seulement d’éviter une répétition à l’identique de l’implémentation précédente.
Par exemple, dans Vivado, chaque exécution possède un attribut appelé « strategy ». Comme son nom l’indique, cet attribut contrôle la stratégie appliquée par les outils pendant l’implémentation. Modifier cet attribut garantit que la prochaine implémentation ne sera pas identique à la précédente. Il est aussi possible qu’une autre stratégie soit plus adaptée à la conception logique concernée.
Tous les outils FPGA offrent des possibilités analogues pour modifier les paramètres du processus d’implémentation. Il est souvent possible de demander un niveau d’effort plus élevé pour atteindre les objectifs. Un effort supérieur est parfois réellement nécessaire, mais souvent demander un effort plus important aide simplement parce que les outils font autre chose.
Une autre façon d’éviter la répétition consiste à modifier le code Verilog. Là encore, le changement n’a pas à être lié au chemin critique. Parfois, il suffit de renommer un registre pour obtenir une implémentation suffisamment différente de la précédente.
On peut pousser cette idée à l’extrême : il est possible de lancer l’implémentation en parallèle sur plusieurs ordinateurs, chacun avec des paramètres légèrement différents. Cela peut avoir du sens lorsque le prix du FPGA compte. Dans ce scénario, il vaut la peine de laisser les ordinateurs travailler dur pour que les contraintes soient respectées sur un FPGA moins cher.
En résumé, cette méthode repose avant tout sur la chance. Il faut donc en attendre ce qu’elle peut donner : réessayer n’aide que lorsque les outils échouent de temps en temps à respecter les contraintes. Mais il est toujours préférable d’améliorer le timing par d’autres moyens, lorsque c’est possible.
Idée n° 6 : Le FPGA est-il plein ?
Il est assez courant que les problèmes de contraintes temporelles apparaissent lorsque le taux d’occupation du FPGA atteint environ 70 %. Il y a trois raisons principales à cela :
- Les éléments logiques sont tassés plus densément dans la matrice logique du FPGA. Le placeur a donc moins de liberté pour améliorer le timing, car il est plus difficile de déplacer les éléments logiques d’un endroit à un autre. Cela se traduit par des délais de routage plus importants.
- La quantité de câblage (ressources de routage) dans un FPGA est limitée. Tant que le FPGA est relativement vide, le routeur peut choisir le chemin le plus adapté entre les éléments logiques. Lorsqu’on ajoute de la logique, des choix sous-optimaux conduisent à des délais de routage plus grands.
- Les éléments logiques spécialisés du FPGA peuvent venir à manquer. Par exemple, la plupart des FPGA disposent de block RAM et de composants dédiés à la multiplication arithmétique. Lorsque ces ressources manquent, les outils implémentent la fonctionnalité demandée avec de simples éléments logiques (slices). Cela mène souvent à un grand nombre de niveaux logiques et donc à un délai logique accru. Le taux d’occupation peut alors commencer à augmenter plus vite que prévu, car des slices sont utilisées à la place des éléments logiques spécialisés.
Parmi les trois raisons ci-dessus, seule la troisième a une forme de solution : par exemple, il peut être utile de décider manuellement quelle logique utilise les block RAM du FPGA et autres ressources analogues. Pour le reste, la seule solution à un FPGA plein est d’en choisir un plus grand. Ce n’est pourtant pas toujours possible. Il est donc important d’anticiper le fait que la convergence temporelle deviendra plus difficile à mesure que l’on ajoute de la logique.
Idée n° 7 : Et s’il fallait un autre FPGA ?
Parfois, il faut se rendre à l’évidence : le FPGA n’est pas à la hauteur de la tâche. Si le même FPGA existe avec une gamme de vitesse (speed grade) supérieure, la solution peut être de passer à un modèle plus rapide. Cette décision augmente bien sûr le coût d’achat, mais elle peut aussi avoir une autre conséquence moins attendue : il peut y avoir une pénurie de FPGA de gamme de vitesse supérieure. Même si ces FPGA plus rapides sont abondants à un moment donné, ce sont généralement les premiers à disparaître du marché lorsque la demande dépasse l’offre.
C’est assez naturel : les FPGA plus rapides sont ceux qui ont mieux passé les tests, et ils peuvent toujours remplacer les plus lents. Parfois, le fabricant n’arrive pas à produire les FPGA plus rapides, et parfois un gros client achète tout ce qui peut lui convenir.
Si vous travaillez sur un produit destiné à une production de longue durée, préférez donc toujours la gamme de vitesse la plus basse avec laquelle votre design peut fonctionner. Cela vaut même si l’argent n’est pas un problème.
Un type de mise à niveau complètement différent consiste à choisir un FPGA d’une famille plus récente. Ou peut-être un FPGA d’un autre fabricant. Ce changement est plus radical, mais il mérite d’être envisagé si le projet en est à ses débuts. Nous avons tous tendance à nous attacher aux outils et aux composants que nous connaissons. Mais lorsque tout semble trop familier, il est bon de regarder ce qui existe ailleurs.
Cela dit, les ingénieurs FPGA expérimentés savent que choisir le tout dernier FPGA et ses outils flambant neufs est un pari risqué. Mais il existe souvent une alternative assez établie, bien meilleure que le FPGA actuellement choisi. Dans une telle situation, il vaut mieux sortir de sa zone de confort et essayer quelque chose de nouveau.
Idée n° 8 : Réduire la plage de température
Si j’ai mis cette possibilité presque en dernier, c’est que c’est la plus laide de toutes les solutions. Mais parfois, on n’a pas le choix.
Par défaut, l’application des contraintes temporelles par les outils garantit que le FPGA fonctionne de manière fiable sur toute la plage de température définie dans la fiche technique. Certains outils FPGA permettent de choisir une autre plage de température pour un projet (par exemple, Quartus dispose pour cela d’un attribut nommé « MAX_CORE_JUNCTION_TEMP »). On peut ainsi indiquer aux outils qu’il n’est pas nécessaire de couvrir toute la plage de température.
D’une manière générale, les délais des éléments logiques dans les FPGA augmentent avec la température. Si la température maximale est réduite, les valeurs de délais dans les calculs de timing sont plus faibles. Il devient donc plus facile aux outils de respecter les exigences sur tsetup. C’est parfois le seul moyen de faire respecter les contraintes temporelles aux outils.
Il est important de bien comprendre les risques de cette méthode. En particulier, notez qu’il s’agit de la température de jonction. Autrement dit, c’est la température sur le silicium du FPGA, et non la température ambiante.
Ainsi, lorsque la température maximale est de 85 °C, cela ne signifie pas que le FPGA fonctionne dans un four à cette température. Cette température peut aussi être atteinte à température ambiante (25 °C), surtout si le FPGA n’a pas de dissipateur thermique. Il y a toujours un écart entre la température de jonction et la température ambiante. L’ampleur de cet écart dépend de la consommation du FPGA et de la solution de refroidissement.
Si vous travaillez sur un produit commercial, sachez que la température ambiante autour du FPGA peut être nettement plus élevée que la température de la pièce. En particulier, si le FPGA se trouve dans un boîtier mal ventilé, la température à l’intérieur peut monter bien au-dessus de celle de l’extérieur. Pour compliquer les choses, la plupart des produits électroniques doivent fonctionner entre 0 °C et 40 °C environ (ces chiffres varient selon les produits). Lorsque le produit final est testé à la température maximale, avec le FPGA à l’intérieur du boîtier, quelle est la température de jonction ? C’est la question à se poser. Les calculs de timing doivent se baser sur cette température (ou une température supérieure).
Autrement dit, si vous réduisez la température maximale pour réussir les contraintes temporelles et que tout fonctionne au laboratoire, cela ne veut rien dire. Rappelons qu’en matière de contraintes temporelles, le fait qu’un design FPGA fonctionne au laboratoire ne prouve jamais rien. Mais réduire la température maximale est encore pire à cet égard. Rétrécir la plage de température sans y réfléchir, c’est s’exposer à de sérieux ennuis : le test final du produit avant production, effectué à la température maximale, risque d’échouer, et il sera impossible de respecter les contraintes si la plage est corrigée. La seule façon de résoudre ce problème sera alors de réécrire entièrement le design FPGA.
Avant de modifier la plage de température pour les besoins de la convergence temporelle, assurez-vous donc que cela est sans risque : faites une évaluation rigoureuse de la température de jonction dans toutes les conditions de fonctionnement possibles.
À noter qu’il est impossible d’étendre la plage de température au-delà de la valeur par défaut en changeant les paramètres d’implémentation. La plage par défaut des outils correspond toujours à celle de la fiche technique. Il n’est donc pas possible de garantir un fonctionnement fiable du FPGA au-delà de cette plage.
Idée n° 9 : Horloges liées non alignées
C’est une situation plutôt ésotérique, et elle est aussi un peu difficile à comprendre. C’est pourquoi j’ai mis ce sujet en dernier.
Supposons qu’il existe un changement de domaine d’horloge (clock domain crossing) entre deux horloges liées (related clocks) qui ne sont pas alignées. Autrement dit, les deux horloges proviennent de la même horloge de référence, mais aucun mécanisme ne permet de contrôler le décalage d’horloge (clock skew) entre elles.
Par conséquent, il devient plus difficile pour les outils de respecter les exigences temporelles sur les chemins entre ces deux horloges. Deux types de difficultés peuvent se présenter (rappelons que tsetup et thold ont été expliqués plus tôt) :
- Lorsque l’horloge arrive plus tard sur la première bascule à cause du décalage d’horloge : il reste donc moins de temps avant que le front suivant n’atteigne la deuxième bascule. Il est par conséquent plus difficile de respecter la condition sur tsetup.
- Lorsque l’horloge arrive plus tôt sur la première bascule à cause du décalage d’horloge : la première bascule met alors sa sortie à jour avant que le même front n’ait atteint la deuxième bascule. La condition sur thold peut donc être violée sur la deuxième bascule. Les outils l’évitent en allongeant artificiellement le routage du chemin. Cela peut conduire à un échec sur la condition de tsetup. C’est aussi un gaspillage de ressources de routage.
Il est important de noter que lorsque les outils doivent travailler plus dur que d’habitude pour surmonter ces difficultés, cela peut se faire au détriment de l’optimisation des autres chemins.
Mais des horloges liées non alignées ne sont pas une erreur, et cela peut parfois être inévitable. Cette situation signifie seulement que les outils doivent travailler davantage. Si les contraintes temporelles sont respectées, il n’y a pas de problème dans le design. Il faut néanmoins l’éviter autant que possible, lorsque cela peut se faire avec un effort raisonnable.
À noter que l’erreur mentionnée dans l’« Idée n° 4 » ci-dessus est différente, même si dans les deux cas les outils travaillent inutilement dur et si les deux cas concernent un changement de domaine d’horloge.
La meilleure solution face à des horloges liées non alignées est d’aligner ces horloges. Cela se fait en général en ajoutant une PLL, ou en ajoutant une sortie d’horloge à une PLL existante. L’objectif est que les deux horloges concernées soient des sorties de la même PLL.
Une autre solution possible consiste à traiter les horloges comme non liées (unrelated clocks). Cela demande à la fois une modification de la conception logique et une modification des contraintes temporelles. L’effort peut en valoir la peine si cela n’est pas trop difficile. Nous reviendrons sur ce sujet plus loin.
Nous allons maintenant examiner un exemple de changement de domaine d’horloge entre horloges liées non alignées.
wire pll_clk;
reg [24:0] result;
reg [11:0] x, y, x1, y1;
clk_wiz_0 pll_i
(.clk_in1(clk),
.clk_out1(pll_clk));
always @(posedge clk)
begin
x1 <= x;
y1 <= y;
end
always @(posedge pll_clk)
result <= x1 * y1;
On notera qu’il y a une PLL (clk_wiz_0). Cette PLL utilise @clk comme horloge de référence, avec une fréquence de 250 MHz. C’est la même PLL que celle présentée dans l’exemple en haut d’une page précédente. La fréquence de @pll_clk est de 125 MHz.
La partie importante de cet exemple est le changement de domaine d’horloge entre deux horloges liées (@clk et @pll_clk). Comme seul @pll_clk est généré par la PLL, ces deux horloges ne sont pas alignées. Il y a donc un décalage d’horloge sur les chemins vers @result (à partir de @x1 et @y1). Malgré ce décalage, ce sont bien des horloges liées, et les outils essaieront de respecter les exigences temporelles.
Si la seule raison d’utiliser @clk est le besoin d’une horloge à 250 MHz, la bonne solution est de générer une autre horloge avec la PLL. Produire une horloge à la même fréquence que l’horloge de référence n’est pas un gaspillage de ressources. Au contraire, cela évite beaucoup d’efforts aux outils. Il n’y a qu’une bonne raison d’utiliser directement @clk comme dans l’exemple : lorsque la logique qui utilise @clk doit fonctionner avant que la PLL ne produise des horloges utilisables.
Le rapport de timing généré par Vivado est le suivant. Dans ce cas précis, il n’y avait qu’un problème avec la condition sur tsetup.
Slack (VIOLATED) : -1.456ns (required time - arrival time) Source: x1_reg[7]/C (rising edge-triggered cell FDRE clocked by clk {rise@0.000ns fall@2.000ns period=4.000ns}) Destination: result_reg/DSP_OUTPUT_INST/ALU_OUT[10] (rising edge-triggered cell DSP_OUTPUT 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: 4.000ns (clk_out1_clk_wiz_0 rise@8.000ns - clk rise@4.000ns) Data Path Delay: 3.012ns (logic 2.677ns (88.878%) route 0.335ns (11.122%)) Logic Levels: 5 (DSP_A_B_DATA=1 DSP_ALU=1 DSP_M_DATA=1 DSP_MULTIPLIER=1 DSP_PREADD_DATA=1) Clock Path Skew: -2.192ns (DCD - SCD + CPR) Destination Clock Delay (DCD): 0.998ns = ( 8.998 - 8.000 ) Source Clock Delay (SCD): 3.202ns = ( 7.202 - 4.000 ) Clock Pessimism Removal (CPR): 0.012ns Clock Uncertainty: 0.148ns ((TSJ^2 + DJ^2)^1/2) / 2 + PE Total System Jitter (TSJ): 0.071ns Discrete Jitter (DJ): 0.103ns Phase Error (PE): 0.086ns Clock Net Delay (Source): 1.414ns (routing 0.002ns, distribution 1.412ns) Clock Net Delay (Destination): 1.184ns (routing 0.002ns, distribution 1.182ns) Clock Domain Crossing: Inter clock paths are considered valid unless explicitly excluded by timing constraints such as set_clock_groups or set_false_path. Location Delay type Incr(ns) Path(ns) Netlist Resource(s) ------------------------------------------------------------------- ------------------- (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.738 4.738 r clk_IBUF_inst/INBUF_INST/O net (fo=1, routed) 0.105 4.843 clk_IBUF_inst/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 4.892 r clk_IBUF_inst/IBUFCTRL_INST/O net (fo=1, routed) 0.795 5.687 clk_IBUF BUFGCE_X1Y2 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 5.788 r clk_IBUF_BUFG_inst/O X2Y0 (CLOCK_ROOT) net (fo=62, routed) 1.414 7.202 clk_IBUF_BUFGCE SLICE_X52Y45 FDRE r x1_reg[7]/C ------------------------------------------------------------------- ------------------- SLICE_X52Y45 FDRE (Prop_HFF_SLICEM_C_Q) 0.138 7.340 f x1_reg[7]/Q net (fo=1, routed) 0.335 7.675 result_reg/A[7] DSP48E2_X8Y18 DSP_A_B_DATA (Prop_DSP_A_B_DATA_DSP48E2_A[7]_A2_DATA[7]) 0.396 8.071 r result_reg/DSP_A_B_DATA_INST/A2_DATA[7] net (fo=1, routed) 0.000 8.071 result_reg/DSP_A_B_DATA.A2_DATA<7> DSP48E2_X8Y18 DSP_PREADD_DATA (Prop_DSP_PREADD_DATA_DSP48E2_A2_DATA[7]_A2A1[7]) 0.182 8.253 r result_reg/DSP_PREADD_DATA_INST/A2A1[7] net (fo=1, routed) 0.000 8.253 result_reg/DSP_PREADD_DATA.A2A1<7> DSP48E2_X8Y18 DSP_MULTIPLIER (Prop_DSP_MULTIPLIER_DSP48E2_A2A1[7]_U[10]) 0.994 9.247 f result_reg/DSP_MULTIPLIER_INST/U[10] net (fo=1, routed) 0.000 9.247 result_reg/DSP_MULTIPLIER.U<10> DSP48E2_X8Y18 DSP_M_DATA (Prop_DSP_M_DATA_DSP48E2_U[10]_U_DATA[10]) 0.164 9.411 r result_reg/DSP_M_DATA_INST/U_DATA[10] net (fo=1, routed) 0.000 9.411 result_reg/DSP_M_DATA.U_DATA<10> DSP48E2_X8Y18 DSP_ALU (Prop_DSP_ALU_DSP48E2_U_DATA[10]_ALU_OUT[10]) 0.803 10.214 r result_reg/DSP_ALU_INST/ALU_OUT[10] net (fo=1, routed) 0.000 10.214 result_reg/DSP_ALU.ALU_OUT<10> DSP48E2_X8Y18 DSP_OUTPUT r result_reg/DSP_OUTPUT_INST/ALU_OUT[10] ------------------------------------------------------------------- ------------------- (clock clk_out1_clk_wiz_0 rise edge) 8.000 8.000 r BUFGCE_X1Y2 BUFGCE 0.000 8.000 r clk_IBUF_BUFG_inst/O net (fo=62, routed) 1.078 9.078 pll_i/inst/clk_in1 MMCME3_ADV_X1Y0 MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0) -1.777 7.301 r pll_i/inst/mmcme3_adv_inst/CLKOUT0 net (fo=1, routed) 0.422 7.723 pll_i/inst/clk_out1_clk_wiz_0 BUFGCE_X1Y0 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.091 7.814 r pll_i/inst/clkout1_buf/O X2Y0 (CLOCK_ROOT) net (fo=6, routed) 1.184 8.998 result_reg/CLK DSP48E2_X8Y18 DSP_OUTPUT r result_reg/DSP_OUTPUT_INST/CLK clock pessimism 0.012 9.010 clock uncertainty -0.148 8.862 DSP48E2_X8Y18 DSP_OUTPUT (Setup_DSP_OUTPUT_DSP48E2_CLK_ALU_OUT[10]) -0.104 8.758 result_reg/DSP_OUTPUT_INST ------------------------------------------------------------------- required time 8.758 arrival time -10.214 ------------------------------------------------------------------- slack -1.456
Ce rapport montre que les outils n’ont pas réussi à respecter les contraintes. Le chemin montré part du front montant de @clk à 4 ns et se termine au front montant de @pll_clk à 8 ns. Le problème vient du temps mis par le front d’horloge pour aller de la broche d’entrée jusqu’à l’entrée d’horloge de la première bascule : 3,2 ns. L’instant d’arrivée de ce front est donc 7,2 ns.
Mais @pll_clk est générée par la PLL, elle est donc alignée sur la broche d’entrée de @clk. Son délai n’est donc que de 1,0 ns. @pll_clk arrive donc sur la deuxième bascule à 9,0 ns. Il reste alors pour le chemin de données 9,0 – 7,2 = 1,8 ns (approximativement, à cause de l’incertitude d’horloge, etc.). Ce n’est pas suffisant pour une multiplication arithmétique, même en utilisant l’unité arithmétique dédiée. Les exigences temporelles n’ont donc pas pu être respectées.
Dans cet exemple, l’horloge arrive donc en retard sur la première bascule à cause du décalage d’horloge. Cela se traduit par un échec sur la condition de tsetup.
Notons que l’on peut résoudre ce problème en jouant sur l’alignement de @pll_clk. Par exemple, l’horloge de référence de la PLL peut être la sortie du buffer d’horloge global qui distribue @clk. On peut aussi définir un déphasage de la PLL pour obtenir un meilleur alignement. Ce sont toutefois des solutions à n’utiliser qu’en dernier recours.
Résumé
Encore une fois, ce ne sont que quelques idées susceptibles d’aider à résoudre un problème de convergence temporelle. Malheureusement, résoudre un tel problème peut demander bien plus que cela. En réalité, il n’existe pratiquement aucun domaine du FPGA qui n’ait pas, d’une manière ou d’une autre, un rapport avec la convergence temporelle.
Comme déjà mentionné, la meilleure stratégie consiste à soigner la conception logique dès le départ. La meilleure façon d’aborder la convergence temporelle, c’est de l’éviter.
Jusqu’à présent, cette série de pages a parlé de timing, mais n’a pas dit grand-chose sur les contraintes temporelles. Cela va changer : À partir de la page suivante, la discussion devient plus technique.