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 présenté les principes de la convergence temporelle (timing closure). Il est maintenant temps d’entrer dans les détails techniques des contraintes temporelles (timing constraints).
La signification de create_clock en tant que commande Tcl
Les pages précédentes ont toutes tourné autour de cette contrainte temporelle :
create_clock -period 4 -name clk [get_ports clk]
J’ai délibérément attendu jusqu’à présent pour discuter de la syntaxe de cette ligne. Il est donc temps d’expliquer ce qu’elle signifie vraiment.
Cette contrainte temporelle est écrite au format SDC (Synopsys Design Constraints), qui est le format le plus courant pour les contraintes temporelles (timing constraints). Vivado et Quartus utilisent ce format, ainsi que plusieurs autres outils FPGA.
Un fichier SDC est essentiellement un script (script) écrit en Tcl. Le contenu d’un fichier SDC est donc un petit programme informatique, et pas seulement un ensemble d’informations. Cela dit, les possibilités d’un fichier SDC en tant que script se limitent à un petit sous-ensemble de commandes destinées à écrire des contraintes : tout ce qui est autorisé dans un script Tcl ne l’est pas dans un fichier SDC.
La commande create_clock sert à définir une contrainte temporelle. Mais en réalité, cette commande demande aux outils FPGA de créer un objet d’horloge (clock object). Et le mot « objet » a ici son sens habituel en génie logiciel. Le nouvel objet d’horloge est donc quelque chose qui est stocké dans la mémoire de l’interpréteur Tcl comme un objet possédant ses propres propriétés.
Par exemple, la partie « -name clk » de la commande create_clock donne la valeur « clk » à la propriété appelée « name ». Rappelons, d’après l’une des pages précédentes, que ce nom apparaissait dans les rapports de timing : le nom « clk » accompagnait les chemins de timing calculés à cause de cette contrainte (ou plus précisément, à cause de cet objet d’horloge).
Plus loin, nous avons vu d’autres noms d’horloges, par exemple clk_out1_clk_wiz_1 et clk_out2_clk_wiz_1. C’étaient en réalité les noms d’autres objets d’horloge, créés automatiquement par les outils.
Il existe une commande Tcl pour lister toutes les horloges : get_clocks. Avec l’exemple à deux horloges de la page précédente, voici une session sur la console Tcl de Vivado :
> get_clocks clk clkfbout_clk_wiz_1 clk_out1_clk_wiz_1 clk_out2_clk_wiz_1
get_clocks et les commandes analogues sont expliquées plus en détail sur la page suivante.
Il est aussi possible de consulter les propriétés de ces objets. Il n’est pas nécessaire de comprendre toutes ces propriétés : je les montre simplement pour illustrer qu’une horloge est un objet. Personnellement, je n’ai jamais eu besoin de manipuler directement une propriété d’objet.
> report_property [get_clocks clk] Property Type Read-only Value CLASS string true clock INPUT_JITTER double true 0.040 IS_GENERATED bool true 0 IS_PROPAGATED bool true 1 IS_USER_GENERATED bool true 0 IS_VIRTUAL bool true 0 NAME string true clk PERIOD double true 4.000 SOURCE_PINS string* true clk SYSTEM_JITTER double true 0.050 WAVEFORM double* true 0.000 2.000 > report_property [get_clocks clk_out1_clk_wiz_1] Property Type Read-only Value CLASS string true clock EDGES int* true 1 2 3 EDGE_SHIFT double* true 0.000 2.000 4.000 INPUT_JITTER double true 0.000 IS_GENERATED bool true 1 IS_INVERTED bool true 0 IS_PROPAGATED bool true 1 IS_RENAMED bool true 0 IS_USER_GENERATED bool true 0 IS_VIRTUAL bool true 0 MASTER_CLOCK clock true clk NAME string true clk_out1_clk_wiz_1 PERIOD double true 8.000 SOURCE pin true pll_i/inst/mmcme3_adv_inst/CLKIN1 SOURCE_PINS string* true pll_i/inst/mmcme3_adv_inst/CLKOUT0 SYSTEM_JITTER double true 0.050 WAVEFORM double* true 0.000 4.000
L’important est de comprendre que create_clock se contente de créer un objet. Les paramètres de cette commande ne font que déterminer comment régler les propriétés de cet objet. Par exemple, la partie « -period 4 » (dans la contrainte que j’ai montrée à plusieurs reprises) signifie simplement qu’une propriété appelée « PERIOD » doit avoir la valeur 4.
Si vous voulez essayer ces commandes Tcl vous-même, notez qu’il existe des différences entre les outils FPGA.
Dans Vivado, ces commandes ne peuvent être utilisées qu’après avoir ouvert l’implémentation terminée (Implemented Design).
Dans Quartus, ouvrez d’abord l’analyseur de timing TimeQuest, puis cliquez sur Create Timing Netlist, Read SDC File et Update Timing Netlist. Essayez ensuite quelques commandes sur la console Tcl, par exemple :
> join [ query_collection -all [ get_clocks ] ] "\n" > get_clock_info -waveform [get_clocks clk]
Le sens du mot « horloge »
Lorsque les outils FPGA emploient le mot « clock », il s’agit généralement d’un objet d’horloge, et non du signal physique présent dans le FPGA. C’est particulièrement vrai dans les rapports de timing.
Rappelons, d’après la page précédente, que j’ai employé plusieurs fois l’expression « horloges théoriques ». Ce sont en réalité des objets d’horloge. Elles sont appelées « clocks » dans le rapport de timing, mais leur rôle dans l’analyse indique qu’elles ne sont que des contenants d’informations.
Quel est donc le lien entre ces objets d’horloge et les signaux réels ? Nous avons déjà vu les noms des objets d’horloge dans l’analyse de timing. Comment tout cela s’articule-t-il ?
Lorsque les outils effectuent l’analyse statique du timing du design, tous les chemins (paths) sont examinés. Si un chemin commence par une bascule, les outils examinent le signal (c’est-à-dire le fil, net) connecté à l’entrée d’horloge de la bascule : existe-t-il un objet d’horloge associé à ce signal ? Par exemple, lorsque le signal est @clk, l’objet d’horloge associé est celui qui a reçu le nom « clk » par la commande create_clock. Une fois l’objet trouvé, les outils peuvent extraire de ses propriétés les informations nécessaires.
La même chose se produit avec la bascule à l’extrémité du chemin. Les outils ont alors les deux objets d’horloge correspondant au chemin. Grâce aux informations de ces objets, ils effectuent l’analyse de timing.
Et bien sûr, la même procédure vaut pour tout élément séquentiel, pas seulement pour les bascules.
Pourquoi est-il important de comprendre cela ? Entre autres, parce que le rapport de timing contient parfois un message d’erreur indiquant qu’il y a des registres sans horloge. En général, cela ne signifie pas qu’une bascule n’a rien de connecté à son entrée d’horloge. Cela signifie plutôt que les outils n’ont trouvé aucun objet d’horloge lié à cette entrée. Autrement dit, ils n’ont trouvé aucune information sur l’horloge de cette bascule. Le problème ne vient donc généralement pas de la conception logique, mais d’une contrainte temporelle manquante (ou mal écrite).
Il vaut la peine de le répéter : quand un rapport de timing mentionne « clock », cela ne veut pas dire qu’il existe un signal portant ce nom dans le design, mais qu’un objet d’horloge a été créé avec ce nom. Comment savoir à quel signal cela correspond ? C’est le sujet suivant.
À qui est cette horloge ?
Ce qui rend le rapport de timing difficile à lire tient aussi aux noms des horloges. La plupart des signaux d’horloge d’un design sont créés par une PLL, et nous avons déjà vu que le nom qui apparaît dans le rapport peut être peu parlant. La plupart des outils FPGA permettent de renommer les objets d’horloge en ajoutant des commandes au fichier SDC, mais ce n’est pas fait dans la plupart des projets. Et la règle d’or n° 4 recommande d’éviter ce qui est spécifique à votre projet.
Le problème des noms devient encore plus délicat lorsque la source de l’horloge est un bloc IP — un cœur IP (IP core), par exemple un émetteur-récepteur Gigabit, un bloc PCIe ou un cœur de processeur sur puce. Dans ce cas, le nom de l’horloge en dit souvent très peu sur sa provenance ou sur ce à quoi elle se rapporte.
Alors, comment résoudre ce problème ? Commençons par la situation la plus simple : lorsque le nom de l’horloge provient de la commande create_clock de notre propre fichier SDC. Voici une fois de plus la même contrainte temporelle :
create_clock -period 4 -name clk [get_ports clk]
La dernière partie de cette commande, « [get_ports clk] », utilise une particularité du langage Tcl : les crochets indiquent qu’il faut exécuter le contenu comme une commande Tcl, puis utiliser le résultat de cette commande à la place des crochets.
La commande get_ports recherche un port d’entrée-sortie nommé « clk ». Son résultat est l’objet qui représente ce port. Dans la commande create_clock ci-dessus, cet objet est donc passé en argument à la commande. C’est ainsi que create_clock établit le lien entre l’objet d’horloge et un signal réel.
Notez que le nom du port et celui de l’objet sont tous deux « clk ». Rien n’oblige à ce qu’ils soient identiques, mais il est recommandé qu’ils le soient : le nom de l’objet apparaît dans les rapports de timing. Le nom du port est donc généralement le meilleur choix.
Il est aussi possible d’utiliser des objets de type fil (net) ou broche (pin) pour identifier un signal. C’est courant dans les contraintes temporelles générées automatiquement pour le compte de blocs IP — de cœurs IP (IP cores). En revanche, si vous ressentez le besoin de le faire dans vos propres contraintes, il y a de bonnes chances que vous fassiez quelque chose de travers.
Ainsi, lorsqu’une commande create_clock est utilisée dans un fichier SDC, il est facile de dire à quel signal l’objet d’horloge se rapporte. Mais qu’en est-il des objets d’horloge créés automatiquement par les outils ?
Dans ce cas, le meilleur moyen de reconnaître l’horloge est de regarder le rapport de timing. Par exemple, quel objet d’horloge est associé à @pll_clk_8 dans l’exemple de la page précédente ? Un moyen simple est de faire une recherche textuelle dans le rapport. En cherchant « pll_clk_8 », on trouve le passage suivant :
Location Delay type Incr(ns) Path(ns) Netlist Resource(s)
-------------------------------------------------------------- -------------------
(clock clk_out1_clk_wiz_1 rise edge)
16.000 16.000
AG12 0.000 16.000 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 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 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 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 pll_i/inst/clkout1_buf/O
X2Y0 (CLOCK_ROOT) net (fo=1, routed) 1.369 15.400 pll_clk_8
SLICE_X49Y58 FDRE foo_reg_reg/C
C’est le chemin d’horloge source (source clock path) pour clk_out1_clk_wiz_1, et voilà la réponse à la question.
Une autre méthode consiste à obtenir l’information par une commande Tcl. La manière exacte de procéder varie d’un outil FPGA à l’autre. Dans Vivado, la commande suivante peut être utilisée après avoir ouvert l’implémentation terminée :
> get_clocks -of_objects [ get_nets pll_clk_8 ] clk_out1_clk_wiz_1
Cette méthode exige de connaître le nom du fil (net). Parfois c’est aussi simple que dans cet exemple, parfois il faut d’abord trouver ce nom. Les outils FPGA offrent généralement un moyen de le faire par l’interface graphique. Des commandes Tcl peuvent aussi être employées.
En fait, j’espère que ces quelques exemples en Tcl vous ont convaincu de l’importance de savoir travailler correctement en Tcl. C’est précisément le sujet de la page suivante.
L’importance d’utiliser get_port
Dans l’exemple ci-dessus, la commande create_clock s’appuie sur get_port pour relier l’objet d’horloge à une broche d’entrée physique. Comme mentionné plus haut, ce lien est nécessaire pour savoir quels éléments logiques sont reliés à cette horloge (ou aux horloges qui en sont dérivées).
Mais get_port n’est pas la seule possibilité. On peut, par exemple, se référer à la broche de sortie d’un buffer d’horloge global. Quelque chose comme :
create_clock -name clk -period 4 [get_pins my_BUFG_inst/O]
La différence est que les outils considèrent la broche de sortie du buffer d’horloge global comme l’origine de l’horloge. Autrement dit, le calcul des chemins d’horloge commence à cette position. Le premier front d’horloge à cette origine a lieu à 0 ns ; cette broche de sortie devient donc la référence de temps.
C’est une contrainte temporelle légale, mais elle présente deux inconvénients importants :
- Il faut demander aux outils de considérer cette horloge comme une horloge non liée (unrelated clock) par rapport à toutes les autres. Cela vaut même pour des horloges générées par la même PLL. La raison est que les outils considèrent la broche de sortie comme la référence de temps. Il n’y a donc aucune compensation des différences de délais des chemins d’horloge entre les horloges (autrement dit, les décalages d’horloge (clock skews) ne sont pas pris en compte).
- Il est impossible (ou très difficile) de définir des contraintes temporelles d’entrées-sorties liées à une horloge visible depuis l’extérieur du FPGA. Cela tient à ce que de telles contraintes s’appuient sur un objet d’horloge comme référence de temps. Or, ici encore, la référence est la broche de sortie du buffer d’horloge global. Le décalage d’horloge entre l’horloge externe et ce buffer est inconnu (il varie par exemple avec la température).
Il faut donc toujours utiliser get_port lorsque c’est possible. Dans le cas contraire, le timing de l’horloge doit être considéré comme inconnu par rapport à tout ce qui n’est pas elle-même.
Dans cette page, beaucoup de commandes Tcl ont été présentées, mais sans être expliquées convenablement. La page suivante comble cette lacune.