01signal.com

Contraintes temporelles pour les chemins multicycles

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, montré comment écrire plusieurs contraintes temporelles et abordé les principes de la convergence temporelle (timing closure). Cette page traite des contraintes temporelles pour les chemins multicycles (multi-cycle paths).

Introduction

La première chose à savoir à propos des chemins multicycles (multi-cycle paths) est que c’est en général une mauvaise idée. Même si cette page explique comment utiliser les contraintes de chemins multicycles, la conclusion devrait être d’éviter complètement cette technique. Il existe des raisons de l’employer dans le monde des ASIC, mais dans un design FPGA, il est généralement préférable d’ajouter une horloge à la place.

Cela dit, voyons quand ce type d’exception de timing est réellement pertinent.

Prenons cet exemple de code Verilog :

   reg foo, bar;
   reg en, pre_en;

   always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;

	if (en)
	  begin
	     foo <= !foo;
	     bar <= foo;
	  end
     end

Notez que cet exemple est incomplet : il vous faudra probablement ajouter des attributs de synthèse à @pre_en et @en. Sinon, des comportements inattendus peuvent survenir à cause des optimisations du synthétiseur (synthesizer). Nous y reviendrons plus bas.

@pre_en bascule entre « 0 » et « 1 » à chaque cycle d’horloge. @en fait de même, avec un léger retard.

La partie « if (en) » signifie que @en joue le rôle d’un signal d’activation d’horloge (clock enable) pour tout ce qui se trouve entre « begin » et « end » : lorsque @en est à l’état bas, rien ne se passe dans cette partie du code Verilog. Autrement dit, @foo et @bar se comportent comme si le front d’horloge n’existait pas lorsque @en est à l’état bas.

Dans cet exemple, @en est à l’état haut une fois tous les deux cycles d’horloge. Par conséquent, @foo et @bar se comportent comme si la fréquence de l’horloge était la moitié de sa valeur réelle. Les exigences temporelles peuvent donc être assouplies en conséquence : les calculs pour tsu peuvent être faits avec une période d’horloge deux fois plus grande.

Quant à thold, rien ne change : le calcul de cette exigence temporelle suppose que le même front d’horloge atteint les deux bascules. La période d’horloge n’y joue donc aucun rôle, comme cela a déjà été discuté dans l’exemple d’une analyse de thold. Par conséquent, l’illusion d’une horloge plus lente ne change rien pour thold.

Quand utiliser une activation d’horloge

Il n’y a que deux bonnes raisons d’utiliser un signal d’activation d’horloge (clock enable) :

On ne sait pas vraiment si une activation d’horloge améliore la consommation sur un FPGA. On pourrait soutenir qu’une horloge supplémentaire gaspille de l’énergie. Mais le signal d’activation d’horloge est aussi un signal à fort fan-out. En moyenne, il change d’état aussi souvent que l’horloge qu’il remplace. Du point de vue des changements d’état logique (qui est le principal facteur de consommation), il n’y a donc aucune différence.

N’hésitez pas à sauter cette page, à moins d’avoir une raison particulière d’utiliser des chemins multicycles. Même si votre design comporte une activation d’horloge techniquement appropriée, il est parfois préférable de ne pas utiliser l’exception de timing correspondante. En particulier, si les contraintes temporelles sont facilement respectées sans cette exception, le risque de se tromper n’en vaut pas la peine.

L’exception de timing

En rapport avec le code Verilog ci-dessus, voici les contraintes de chemins multicycles pour Vivado :

set en_regs [all_fanout -endpoints_only -only_cells -flat [get_nets en]]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

L’équivalent pour Quartus :

set en_regs [get_fanouts en]
set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

La différence entre Vivado et Quartus tient uniquement à la première ligne : la commande « all_fanout » est utilisée avec Vivado, et « get_fanouts » avec Quartus. Les contraintes sont analogues avec les autres outils qui travaillent avec SDC.

La première ligne trouve tous les éléments synchrones (cellules) qui sont l’extrémité d’un chemin partant de @en. Cette liste d’objets de cellules est stockée dans $en_regs. Les deux lignes suivantes modifient les exigences temporelles pour les chemins qui commencent et se terminent sur des cellules de cette liste.

Tout ceci demande beaucoup d’explications. Pourquoi ai-je écrit la définition de $en_regs de cette façon ? Pourquoi y a-t-il deux commandes set_multicycle_path ? Pourquoi la seconde commande contient-elle « -hold », alors que j’ai dit que les exigences temporelles sur thold ne sont pas affectées par les chemins multicycles ?

Je vais commencer par les commandes set_multicycle_path, car c’est la partie la plus simple à expliquer.

Les commandes set_multicycle_path

Comme montré plus haut, les commandes sont :

set_multicycle_path -setup -from $en_regs -to $en_regs 2
set_multicycle_path -hold -from $en_regs -to $en_regs 1

Pour généraliser un peu l’exemple ci-dessus, considérons aussi ceci : si l’activation d’horloge était active une fois tous les huit cycles d’horloge, le code Verilog aurait été :

   reg en;
   reg [2:0] pre_en;

   always @(posedge clk)
     begin
	pre_en <= pre_en + 1;
	en <= (pre_en == 0);
     end

Notons que @en se comporte comme une impulsion et n’est actif que pendant un seul cycle d’horloge à chaque fois. Ce n’est pas le bit de poids fort d’un diviseur d’horloge.

Les contraintes multicycles pour cette éventualité seraient :

set_multicycle_path -setup -from $en_regs -to $en_regs 8
set_multicycle_path -hold -from $en_regs -to $en_regs 7

À la lumière de ces deux exemples, il est clair que le nombre dans la première commande set_multicycle_path est simplement le rapport de division de l’activation d’horloge.

Pour la seconde commande, c’est le même rapport de division, moins un. C’est donc toujours N et N-1.

Il n’y a pas grand-chose à expliquer pour la première commande : si l’activation d’horloge est active un cycle sur N, le délai autorisé est multiplié par N. C’est ce qui importe pour l’exigence de tsu.

Mais pourquoi une seconde commande ? Pourquoi faut-il dire quoi que ce soit à propos de thold ? La réponse est que la première commande modifie aussi l’exigence sur le délai minimal. Autrement dit, le calcul de thold est également affecté par la commande avec l’option « -setup ». Pourquoi ? Il n’y a probablement pas de bonne explication.

La seconde commande corrige cela : elle rétablit l’exigence du délai minimal à sa valeur d’origine. Après la seconde commande, le calcul de thold est donc effectué comme auparavant.

La documentation contient de longues explications sur la raison pour laquelle on utilise N-1 dans la seconde commande. À vrai dire, ces informations supplémentaires n’ont rien d’intéressant. Le comportement de set_multicycle_path en ce qui concerne thold est étrange, et comprendre pourquoi le nombre doit être N-1 ne le rend pas moins étrange.

Mais set_multicycle_path était la partie facile. Voici maintenant la véritable difficulté : générer la bonne liste d’objets de cellules à utiliser pour $en_regs.

Sélectionner les registres

Pour sélectionner les registres qui doivent apparaître dans $en_regs, il faut comprendre ce qui rend un chemin éligible en tant que chemin multicycle. Voici donc la règle : il n’est permis d’assouplir l’exigence temporelle d’un chemin que si l’activation d’horloge commande les deux côtés. Cela signifie que lorsque l’activation d’horloge est inactive, il est garanti qu’aucun des deux éléments séquentiels ne change de valeur après le front d’horloge.

Pensez-y en termes de domaines d’horloge (clock domains) : tous les éléments séquentiels commandés par l’activation d’horloge appartiennent à un domaine d’horloge imaginaire. L’horloge à l’intérieur de ce domaine imaginaire a une fréquence plus basse, de sorte que les exigences temporelles à l’intérieur de ce domaine peuvent être ajustées.

Mais si l’un des côtés du chemin n’appartient pas à ce domaine imaginaire, c’est un changement de domaine d’horloge imaginaire entre deux horloges liées (related clocks). Il n’y a rien de spécial à faire pour un tel chemin, car il est déjà pris en charge par les contraintes temporelles existantes. En revanche, il est incorrect d’appliquer une exception multicycle à un chemin de ce genre.

Les contraintes temporelles montrées plus haut reflètent cette idée : tous les éléments séquentiels commandés par @en sont listés dans $en_regs comme objets de cellules. Ensuite, les deux commandes set_multicycle_path s’appliquent aux chemins qui commencent et se terminent sur des éléments séquentiels de cette liste.

La partie la plus délicate des chemins multicycles consiste à garantir que cette liste d’éléments séquentiels est correcte : elle doit contenir tous les éléments séquentiels commandés par l’activation d’horloge, mais elle ne doit en contenir aucun autre.

Si un élément séquentiel manque dans cette liste, les exigences temporelles imposées sur les chemins correspondants seront plus sévères que nécessaire. Ce n’est pas une catastrophe, mais cela rend l’exception de timing moins efficace.

Mais si un élément séquentiel qui ne devrait pas s’y trouver est ajouté par erreur, les conséquences peuvent être graves : certains chemins n’auront pas des exigences temporelles assez strictes. Autrement dit, les outils ne garantissent pas le bon fonctionnement des éléments séquentiels concernés. Et lorsque les exigences temporelles ne sont pas respectées, des choses étranges peuvent se produire.

J’ai choisi d’utiliser la commande « all_fanout » (ou « get_fanouts ») pour créer cette liste d’éléments séquentiels. Ce n’est pas toujours garanti de fonctionner correctement, comme je vais l’expliquer. Ensuite, j’examinerai d’autres options pour créer cette liste. Ces autres options sont particulièrement pertinentes lorsque les outils FPGA ne prennent pas en charge « all_fanout » ou des commandes analogues.

Problèmes possibles avec all_fanout et get_fanouts

L’erreur la plus probable avec une contrainte multicycle est d’inclure l’activation d’horloge elle-même (c’est-à-dire @en) dans la liste $en_regs. Si cela arrive, l’exception multicycle s’applique à tous les chemins qui vont de @en lui-même vers les éléments séquentiels qu’il commande. Cela revient en pratique à ne pas garantir les exigences temporelles de ces chemins. L’effet peut être visible, car l’activation d’horloge est souvent un signal à fort fan-out.

Dans l’exemple ci-dessus, on évite cela avec @pre_en. Vous vous êtes peut-être demandé pourquoi @en n’avait pas été défini tout simplement ainsi :

always @(posedge clk)
  en <= !en; // Wrong!

Si @en avait été défini de cette façon, il y aurait eu un chemin allant de @en à lui-même. Par conséquent, @en aurait été inclus dans $en_regs.

@pre_en résout donc ce problème. Mais il est important de s’assurer que le synthétiseur n’élimine pas ce registre pour les besoins de l’optimisation. Par exemple, le synthétiseur de Quartus détecte que la seule utilité de @pre_en est de donner une valeur à @en (dans le code Verilog en haut de cette page). Il supprime donc @pre_en et continue comme si l’on avait écrit « en <= !en ». Par conséquent, @en se retrouve dans $en_regs. Une solution possible consiste à déclarer @pre_en comme suit :

reg pre_en /* synthesis preserve */;

Ce simple exemple montre comment une optimisation inattendue du synthétiseur peut avoir un résultat désastreux. Même si la solution est simple, il est facile de ne pas penser à empêcher cette optimisation.

Un autre incident possible avec l’activation d’horloge est lié au fait que ce signal a souvent un fan-out élevé. Les outils peuvent donc répliquer le registre automatiquement, afin que chaque réplique ait un fan-out inférieur à une certaine limite. Mais comment cela affecte-t-il $en_regs ? Le critère d’inclusion dans cette liste reposait sur un fil (net) précis. Les éléments séquentiels commandés par des répliques de @en ne sont donc pas inclus.

Le résultat d’une telle situation n’est toutefois pas désastreux : comme déjà mentionné, cela signifie seulement que l’application des exigences sera plus stricte que nécessaire sur certains chemins. La fiabilité du design n’est pas affectée.

La réplication des registres a déjà été évoquée dans le contexte des fan-outs élevés. Comme mentionné là-bas, il vaut mieux répliquer @en manuellement que d’attendre que le synthétiseur le fasse. Pour éviter les mauvaises surprises, ajoutez toujours un attribut de synthèse qui interdit la réplication de ce registre. Si un fan-out élevé pose plus tard des problèmes de convergence temporelle, résolvez-les par une réplication manuelle. Il sera ainsi plus facile de comprendre la source du problème. Si le synthétiseur réplique soudainement @en parce que le projet a grandi, il sera moins facile de comprendre pourquoi les contraintes temporelles n’ont pas été respectées.

Dans tous les cas, la commande qui définit $en_regs doit être mise à jour pour inclure les répliques de @en.

À ce propos, la définition de $en_regs repose sur le nom d’un fil. Comme déjà discuté, cela signifie que $en_regs devient une liste vide si le synthétiseur change simplement le nom du fil pour quelque chose d’autre que « en ». Par conséquent, les contraintes de chemins multicycles deviennent totalement inutiles. Cette éventualité n’est pas non plus une catastrophe : le design reste fiable, mais il devient plus difficile de respecter les contraintes temporelles.

Un autre problème possible est que @en ne doit servir à rien d’autre qu’à une activation d’horloge, en raison de la manière dont $en_regs est défini. Par exemple, considérons ce code Verilog :

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;

Dans cet exemple, @en est clairement utilisé comme activation d’horloge. Il est donc normal que tous les chemins liés à @counter soient des chemins multicycles. Mais qu’en est-il de ceci ?

reg [7:0] counter;

always @(posedge clk)
  if (en)
    counter <= counter + 1;
  else
    counter <= counter - 1;

Ici, @en est utilisé comme n’importe quel autre registre. La valeur de @counter change à chaque cycle d’horloge. @counter ne devrait donc absolument pas être candidat à un chemin multicycle. Et pourtant, toutes les bascules de @counter se retrouvent dans $en_regs : il existe des chemins depuis @en vers chacune de ces bascules.

C’est relativement facile à résoudre en créant une réplique de @en :

reg [7:0] counter;
reg non_ce_en;

always @(posedge clk)
  non_ce_en <= pre_en;

always @(posedge clk)
  if (non_ce_en)
    counter <= counter + 1;
  else
    counter <= counter + 2;

Notez qu’un attribut de synthèse est nécessaire pour empêcher le synthétiseur de fusionner @en et @non_ce_en en un seul registre.

En conclusion, la définition de $en_regs fondée sur tous les chemins qui partent de @en est simple et concise. Mais cette définition est aussi un champ de mines. Examinons donc quelques alternatives.

D’autres manières de créer $en_regs

La manière la plus sûre de créer une liste d’éléments séquentiels pour une exception multicycle consiste à s’appuyer sur la hiérarchie du design : toute la logique commandée par l’activation d’horloge doit se trouver dans un module séparé (et éventuellement dans des sous-modules). Cela permet de créer $en_regs en recherchant les cellules d’après le nom complet de l’objet cellule. Par exemple, avec Vivado :

set all_sync [all_fanout -endpoints_only -only_cells -flat \
  [get_nets -of_objects [get_clocks clk]]]
set en_regs [filter $all_sync {name =~ module_ins/multicycle_ins/* }]

La première commande trouve tous les éléments logiques connectés à @clk, à l’exception du buffer d’horloge lui-même (cette horloge est représentée par l’objet d’horloge nommé « clk »). Le résultat est stocké dans $all_sync. C’est une manière possible d’obtenir une liste contenant tous les éléments synchrones susceptibles d’être pertinents. La seconde commande crée une liste de tous les éléments logiques de $all_sync qui se trouvent dans le module séparé en question.

Notez que cette méthode repose sur le nom de l’objet d’horloge et les noms des instanciations (instantiations). Rien ne laisse attendre qu’ils changent. Avec cette méthode, peu importe que l’activation d’horloge soit répliquée ou que son nom soit modifié par le synthétiseur.

Un autre avantage d’un module séparé est qu’il est plus facile de travailler avec le code Verilog : le risque de confusion entre les éléments séquentiels commandés par l’activation d’horloge et les autres est plus faible.

Cependant, il n’est pas toujours naturel de séparer la logique qui dépend de l’activation d’horloge pour la placer dans un module distinct. De plus, si le code Verilog est déjà écrit et fonctionne correctement, il n’est peut-être pas judicieux d’y apporter des modifications.

Je mentionnerai aussi une autre alternative, qui peut convenir dans certaines situations : une convention de nommage pour tous les registres. Par exemple, on peut donner à tous les registres commandés par l’activation d’horloge un nom commençant par « MC_ ». Ce choix simplifie la commande qui crée $en_regs : il suffit de rechercher les objets de cellules d’après leur nom. D’autres éléments logiques (par exemple des block RAM) peuvent aussi être inclus en choisissant le nom de leur instanciation en conséquence. Certains diront que cela enlaidit le code Verilog, d’autres que cela le rend plus facile à travailler. Aucune méthode n’est parfaite.

Pourquoi ne pas utiliser -of_objects

Il peut sembler tentant de définir $en_regs selon un critère simple : trouver le fil nommé « en » et ajouter tous les registres connectés à ce fil. Par exemple, sous Vivado, cela peut s’écrire :

set en_regs [get_cells -of_objects [get_nets en]]

Plusieurs raisons expliquent pourquoi c’est faux. La première est que cela inclut @en lui-même. Les contraintes multicycles s’appliquent donc à tous les chemins qui vont de l’activation d’horloge vers les éléments séquentiels qu’elle commande. Comme mentionné plus haut, c’est une grave erreur.

La seconde raison est que certains éléments séquentiels peuvent être oubliés. D’après la commande ci-dessus, le critère d’inclusion est que la cellule soit connectée à un fil précis (@en). Cela fonctionne lorsque ce fil est directement relié à l’entrée CE de la bascule. Mais souvent, le synthétiseur choisit d’utiliser une fonction combinatoire (combinatorial logic) fondée sur @en à la place.

Par exemple, le synthétiseur peut choisir d’implémenter @foo comme si le code Verilog était :

foo <= foo ^ en;

Cela est fonctionnellement équivalent à l’expression d’origine :

if (en)
  foo <= !foo;

Une optimisation de ce genre est légitime et même prévisible : le synthétiseur doit de toute façon utiliser une LUT pour implémenter la porte NON. Pourquoi ne pas utiliser cette LUT pour obtenir directement la valeur suivante de la bascule ? Pourquoi ajouter un autre fil vers l’entrée CE de la bascule ?

L’effet secondaire de cette optimisation est que la bascule elle-même n’est pas connectée directement à @en. Elle ne sera donc pas incluse dans $en_regs. Les conséquences d’une telle situation ont déjà été évoquées.

Il est souvent possible de demander au synthétiseur de n’utiliser @en que comme entrée d’activation d’horloge des éléments synchrones. Certains synthétiseurs prennent en charge, par exemple, un attribut de synthèse appelé « direct_enable » ou quelque chose d’approchant. Notez que lorsque cette fonctionnalité est utilisée, la liberté du synthétiseur pour optimiser la logique est réduite. Les performances du design peuvent donc être affectées négativement pour résoudre un problème technique lié aux outils.

Par-dessus tout, si @en est répliqué ou renommé, les mêmes problèmes que ceux évoqués plus haut apparaissent.

Pour toutes ces raisons, la connexion directe à un fil est un mauvais critère de sélection.

Interaction avec une remise à zéro

Supposons que nous ajoutions une remise à zéro synchrone (synchronous reset) à l’exemple Verilog ci-dessus :

   always @(posedge clk)
     begin
	pre_en <= !pre_en;
	en <= pre_en;
     end

   always @(posedge clk)
     if (reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

Rappelons que l’idée derrière une exception multicycle était que tous les éléments synchrones se comportent comme s’ils faisaient partie d’un domaine d’horloge imaginaire. L’horloge imaginaire de ce domaine a une fréquence moitié de celle de @clk. Tous les registres doivent donc ignorer @clk lorsque @en est bas. Ce n’est pas le cas dans ce dernier exemple de code Verilog : @reset a un effet indépendamment de @en.

Par exemple, regardons ce qui se passe si @reset est défini ainsi :

assign reset = foo;

C’est une remise à zéro synchrone légitime, même si elle n’a probablement aucune utilité pratique. Mais cette définition montre le problème d’une exception multicycle : lorsque @en est haut, @foo passe à l’état haut au cycle d’horloge suivant. Cela met aussi @reset à l’état haut. Au cycle suivant, @foo repasse donc à l’état bas. @foo change de valeur à chaque cycle d’horloge. Le chemin multicycle allant de @foo à lui-même impose alors une exigence temporelle insuffisamment stricte sur ce chemin.

On résout facilement ce problème en faisant en sorte que @en commande aussi la remise à zéro synchrone :

   always @(posedge clk)
     if (en && reset)
       begin
	  foo <= 0;
	  bar <= 0;
       end
     else if (en)
       begin
	  foo <= !foo;
	  bar <= foo;
       end

C’est correct, mais @reset doit être actif en même temps que @en. Une solution simple consiste à maintenir @reset à l’état haut pendant plusieurs cycles d’horloge.

Les mêmes principes s’appliquent pour une remise à zéro asynchrone (asynchronous reset). Tout ce qui est écrit sur la page consacrée aux remises à zéro asynchrones est aussi pertinent ici, mais c’est encore plus compliqué avec des contraintes de chemins multicycles. La solution la plus simple consiste probablement à utiliser un synchroniseur, comme suggéré sur une autre page.

@en et les remises à zéro asynchrones

L’un des avantages de @pre_en est de garantir que toutes les répliques de @en ont toujours le même niveau logique. Cela semble évident, mais ce n’est pas garanti si une remise à zéro asynchrone est utilisée incorrectement. Par exemple, supposons que le code Verilog d’origine soit :

reg en;

always @(posedge clk or posedge reset)
  if (reset)
    en <= 0;
  else
    en <= !en; // This is not recommended!

Si @en est répliqué, le résultat peut être équivalent à ceci :

reg en, en_1, en_2;

always @(posedge clk or posedge reset)
  if (reset)
    begin
      en <= 0;
      en_1 <= 0;
      en_2 <= 0;
    end
  else
    begin
      en <= !en;
      en_1 <= !en_1;
      en_2 <= !en_2;
    end

Notez que la valeur suivante de @en_1 dépend de sa propre valeur, et non de celle de @en. C’est un résultat réaliste de la réplication d’un registre.

Que se passe-t-il si la remise à zéro asynchrone est désactivée d’une manière non sûre ? Il est possible que certaines répliques de @en réagissent au premier front d’horloge après la remise à zéro, et que d’autres l’ignorent. Il en résultera que les états logiques des répliques ne redeviendront jamais identiques (jusqu’à la prochaine remise à zéro).

@pre_en résout cela, car toutes les répliques recopient leur valeur suivante depuis la même source. Cela garantit un fonctionnement correct à long terme, même si le démarrage est chaotique.

Résumé

Il est facile d’utiliser une activation d’horloge. Il est facile d’utiliser set_multicycle_path comme commande d’exception de timing. Mais faire fonctionner le tout de manière fiable n’est pas facile du tout. Beaucoup de choses peuvent mal tourner, et la raison est parfois que le synthétiseur change de comportement lorsque la conception logique grossit.

Ces problèmes inattendus peuvent faire en sorte que le chemin multicycle ne remplisse pas son rôle : si les exigences temporelles assouplies ne s’appliquent pas à certains chemins, l’intérêt de la méthode est discutable. Pire encore, une erreur peut rendre la conception logique non fiable si l’exception multicycle s’applique à des chemins qui n’auraient pas dû être affectés.

Il est donc crucial de lire les rapports de timing des chemins concernés afin de s’assurer que rien d’inattendu ne s’est produit. Malheureusement, cela n’empêche pas les mauvaises surprises à l’avenir : le comportement du synthétiseur est difficile à prédire à mesure que le design évolue.

Si possible, il est donc bien préférable de générer une horloge supplémentaire à partir de la même PLL plutôt que d’utiliser une activation d’horloge. Les changements de domaine d’horloge avec cette nouvelle horloge sont fiables, car les deux horloges sont des horloges liées (related clocks). Les contraintes temporelles pour cette nouvelle horloge sont générées automatiquement par les outils. Il n’y a ainsi aucun risque de mauvaise surprise.

Si vous lisez cette page parce que vous souhaitez ajouter une activation d’horloge et une exception multicycle à votre design, j’espère qu’elle vous aura donné matière à réflexion.


Ceci conclut la partie consacrée aux contraintes temporelles pour les chemins internes au FPGA. Mais qu’en est-il des entrées-sorties ? C’est ce que commence à aborder la page suivante.

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)