Ceci est la troisième et dernière page d’une série consacrée aux resets dans les FPGA. Il est recommandé de lire les deux premières avant celle-ci.
Vue d’ensemble
C’est toujours une bonne idée de prendre le temps de mettre en place une logique appropriée pour créer les resets. Même si le design ne présente apparemment aucun problème, sauter cette étape risque de se faire payer : plus tard, vous pourrez passer des jours à essayer de résoudre un problème d’instabilité sans réaliser que la cause est une initialisation incorrecte de la logique. Au fil des tentatives pour résoudre ce genre de problèmes, le design accumule des contournements laids, effectués sans comprendre la racine du problème. Voir cette page au sujet des instabilités bizarres.
Réfléchir et concevoir les signaux de reset dès le début du projet est également important pour garantir que le projet grandisse correctement au fur et à mesure que de nouvelles fonctionnalités sont ajoutées. Quand un projet FPGA démarre de zéro, on implémente souvent d’abord sa fonctionnalité de base, puis on ajoute des fonctions au fil du temps. Comme les différents modules exigent souvent des horloges et des resets séparés, il est très facile de tomber dans le piège de bricoler rapidement quelque chose pour chaque partie séparément. Cela rend souvent le projet de plus en plus chaotique au fur et à mesure.
Il est plus facile d’éviter un projet brouillon en ayant un module central, correctement écrit dès le premier jour, pour regrouper les resets et les horloges. Et comme expliqué plus loin, le contrôleur de reset et les ressources d’horloge (PLL et buffers d’horloge si nécessaire) s’influencent mutuellement, donc les mettre dans le même module est payant. Je donne généralement à ce module le nom clkrst.v.
Cependant, il n’est souvent pas possible de tout concentrer dans un seul module, car certains cœurs IP (IP cores), sous-systèmes ou blocs de design peuvent générer leur propre groupe de resets et d’horloges. Dans ce cas, il faut réfléchir soigneusement à ce qui dépend de quoi et à la manière dont le système global doit réagir aux demandes de reset provenant de différentes sources. Par exemple, le reset d’un bloc PCIe arrive presque toujours du bus lui-même, et le bloc produit un signal de reset destiné à la logique connectée à ce bloc. Dans des situations comme celle-ci, il faut se demander, par exemple, comment le système en général doit réagir si un reset arrive depuis le bus PCIe (y compris la possibilité de ne pas réagir du tout).
Chaque projet ayant son histoire, il n’existe pas de solution unique pour tous les cas. Cette page décrit des concepts et propose des idées et des extraits de code qui peuvent servir de briques pour votre propre contrôleur de reset. Ces extraits ne sont toutefois pas destinés à être copiés-collés directement dans un projet, mais doivent être considérés comme des démonstrations.
Pour simplifier, je supposerai qu’aucun autre bloc du système ne génère ni horloges ni resets ; étendre le contrôleur au cas général est cependant assez simple.
La machine à états du reset
Dans la plupart des designs, deux scénarios principaux exigent un reset :
- Immédiatement après la mise sous tension du FPGA et la configuration du flux binaire (bitstream), afin de garantir un démarrage propre.
- Quand un reset est explicitement demandé, soit par un bouton poussoir, soit par une commande d’un processeur ou d’un ordinateur.
En outre, un reset peut être nécessaire lorsqu’un temporisateur de chien de garde expire ou lorsqu’une défaillance majeure du système est détectée par un autre moyen.
La réponse attendue à tous ces scénarios est un redémarrage fondamental, du genre qui garantit que quoi qu’il soit arrivé, c’est corrigé. Cela se fait souvent au mieux avec une sorte de machine à états (state machine), qui assure une séquence de reset cohérente et reproductible, quelle que soit la raison du déclenchement.
Cela dit, il existe des scénarios où un reset local, éventuellement récurrent, a du sens. Par exemple, la logique qui traite des images vidéo peut être réinitialisée avant le début de chaque trame. Cela demande typiquement un mécanisme léger, qui revient à attribuer des valeurs initiales à plusieurs registres lorsqu’un reset synchrone local est activé. C’est un mécanisme de reset comme un autre, et c’est souvent un moyen propre et simple de garantir un fonctionnement robuste.
Mais comme il n’y a pas grand-chose de plus à ajouter sur cette possibilité, le reste de cette page se concentre sur le reset fondamental, qui couvre tout le FPGA.
Horloges instables et nécessité d’un reset
Il est assez courant (et généralement recommandé) d’utiliser les PLL du FPGA pour générer les horloges. Mais ces PLL commencent généralement à fonctionner en même temps que toute la logique du FPGA devient active. En conséquence, la logique qui dépend de ces horloges est alimentée par une horloge instable, dont la fréquence peut être nettement plus élevée que la normale.
Quand cela se produit, le timing des chemins (paths) logiques concernés n’est pas garanti. Toute logique qui repose sur des horloges générées par les PLL doit donc être traitée comme si elle ne satisfaisait pas les contraintes temporelles (timing constraints) tant que la PLL n’est pas verrouillée.
La solution adéquate et courante consiste à maintenir toute cette logique en reset jusqu’à ce que la PLL concernée soit verrouillée. On peut aussi faire en sorte que cette logique ignore l’horloge jusqu’au verrouillage de la PLL, par exemple en garantissant que l’entrée d’activation d’horloge (clock enable) des bascules soit inactive.
Si une horloge externe est connectée directement d’une broche du FPGA aux éléments logiques, la question est de savoir si le générateur d’horloge (typiquement une PLL) s’est verrouillé plus vite que le FPGA n’a été chargé avec son flux binaire. Il n’est pas toujours facile d’en être sûr.
Par conséquent, dans la plupart des designs, beaucoup d’éléments synchrones doivent être réinitialisés. Ou plus précisément, sur la question de savoir si un reset est nécessaire à cause d’une horloge instable, on peut diviser ces éléments en trois groupes :
- Ceux qui peuvent contenir des données parasites, et cela ne pose pas de problème.
- Ceux dont on garantit qu’ils ignorent l’horloge tant qu’elle est instable (par exemple grâce à une activation d’horloge).
- Ceux qui doivent être réinitialisés.
Il convient de mentionner que certains FPGA permettent de configurer le processus de configuration (c’est-à-dire le processus de chargement du flux binaire et d’initialisation du FPGA) de manière à retarder le réveil du FPGA jusqu’à ce que toutes les PLL soient verrouillées. Cela pourrait être un moyen de résoudre ce problème. L’inconvénient de cette solution est que s’il y a un problème avec une horloge externe, le FPGA ne démarrera pas du tout. Une telle situation peut être très déroutante.
Une machine à états simple
Comme le démarrage ou le redémarrage fondamental sont des événements assez rares, peu importe que cela prenne quelques microsecondes de plus que nécessaire. Dans bien des cas, même 100 ms environ conviennent, et on peut en tirer parti. Un simple compteur est donc un moyen simple d’implémenter une séquence d’événements.
Dans sa forme la plus simple, cela revient à quelque chose comme :
reg [4:0] reset_count;
reg rst_src_pll, rst_src_shreg, rst_src_debounce;
reg clear_counter;
reg master_reset;
initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;
always @(posedge wakeup_clk)
begin
clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;
master_reset <= (reset_count != 31);
if (clear_counter)
reset_count <= 0;
else if (reset_count != 31)
reset_count <= reset_count + 1;
end
@rst_src_pll, @rst_src_shreg et @rst_src_debounce représentent différentes raisons de réinitialiser le système. Ces registres reçoivent leurs valeurs d’une autre logique. Je vais passer en revue quelques exemples d’une telle logique, mais pour l’instant, le point important est que ces registres sont synchrones avec @wakeup_clk (il n’y a donc pas besoin de traversée de domaine d’horloge (clock domain crossing)).
@clear_counter est le OU logique de ces raisons de reset. Ce registre met @reset_count à zéro. Sinon, ce compteur va de 0 à 31 (dans cet exemple), puis s’arrête.
Enfin, @master_reset est actif tant que @reset_count n’a pas fini de compter. C’est la forme la plus simple d’une machine à états de reset, et compter jusqu’à 31 est aussi assez modeste.
Donc si l’un des signaux @rst_src_N est actif, ne serait-ce que pendant un cycle d’horloge, le reset synchrone reste actif pendant 31 cycles d’horloge.
Une longue impulsion de reset présente deux avantages. Premièrement, si le @rst_src_N correspondant s’active et se désactive de manière aléatoire (par exemple, des détecteurs de verrouillage de PLL instables, des boutons poussoirs, des logiciels qui demandent des resets à plusieurs reprises), ces multiples activations ne se propagent pas jusqu’au reset synchrone visible. Bien que ces activations multiples soient généralement sans danger, elles peuvent provoquer une activité inutile sur les broches de sortie. Une telle activité peut avoir un effet négatif, par exemple faire croire à une personne qui teste l’électronique que quelque chose ne va pas.
En ce sens, l’exemple de 31 cycles d’horloge est assez minimaliste. Il est encore préférable de compter jusqu’à une valeur correspondant à 10–100 ms, si un tel délai est acceptable. Ainsi, tout tremblement plus court que cette durée est masqué par le contrôleur de reset.
La deuxième raison d’une longue impulsion de reset est que la répartition du reset synchrone d’origine en copies locales (comme expliqué précédemment) introduit un délai d’un cycle d’horloge. Si la répartition du reset dans toute la logique exige de copier le signal plusieurs fois, le délai total ne fait pas que s’allonger, il peut aussi devenir inégal. Une longue impulsion de reset garantit qu’à un moment donné, toute la logique est exposée à un reset synchrone actif. Il reste toutefois une mauvaise idée d’avoir des délais inégaux dans le chemin de reset, car la désactivation du reset devient inégale elle aussi, mais parfois ce n’est pas un problème. D’ailleurs, 31 cycles d’horloge pour l’impulsion de reset est probablement beaucoup plus long que nécessaire, mais cela ne fait pas de mal.
Resets pour les autres domaines d’horloge
@master_reset est un reset synchrone ordinaire, mais il est synchronisé avec une horloge qui peut être différente de celles utilisées par la logique applicative. Pour produire des resets synchrones pour d’autres horloges, il faut faire quelque chose comme ceci pour chaque horloge :
reg reset_clk_pre1, reset_clk_pre2;
reg reset_clk;
always @(posedge clk)
begin
reset_clk <= reset_clk_pre2;
reset_clk_pre2 <= reset_clk_pre1;
reset_clk_pre1 <= master_reset;
end
Ce n’est qu’une traversée de domaine d’horloge (clock domain crossing) ordinaire à trois étages, qui produit @reset_clk. Ce signal est le reset synchrone associé à @clk. Deux étages suffisent en réalité, mais comme c’est un signal important, je lui ai accordé un registre supplémentaire, par sécurité.
L’horloge du contrôleur de reset
Il existe en principe trois sortes d’horloges qui peuvent servir de @wakeup_clk, c’est-à-dire pour la machine à états de reset :
- Une horloge générée à l’extérieur du FPGA, et que l’on sait stable au réveil du FPGA.
- Une horloge générée par une PLL du FPGA, et dont on ne s’attend donc pas à ce qu’elle soit prête au réveil du FPGA.
- Sur certains FPGA, il est possible d’utiliser l’horloge de configuration, générée par un oscillateur en anneau à l’intérieur du FPGA.
La première option est la plus facile à mettre en œuvre. Comme il y a presque toujours une horloge de référence qui alimente les PLL du FPGA, cette horloge de référence peut souvent servir directement d’horloge de réveil. Il est toutefois important de vérifier qu’elle est bien stable au réveil du FPGA, c’est-à-dire que le temps nécessaire au FPGA pour charger le flux binaire est plus long que le temps que met l’oscillateur externe à produire une horloge valide. C’est généralement le cas avec une large marge d’après les fiches techniques. Mais si la séquence de mise sous tension de la carte n’est pas planifiée correctement, il se peut très bien que le FPGA reçoive l’autorisation de lire son flux binaire bien avant que la tension d’alimentation de l’oscillateur ait atteint son niveau correct.
La deuxième option consiste à utiliser une horloge générée par une PLL du FPGA. L’avantage évident est que cette horloge sert peut-être aussi à la logique applicative, ce qui est plus efficace pour les ressources d’horloge et pour la consommation. Ce choix oblige à maintenir la machine à états de reset dans son état initial jusqu’à ce que l’horloge soit stable, ce qui peut se faire avec quelque chose comme ceci :
reg rst_src_pll;
reg rst_src_pll_pre;
initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;
always @(posedge wakeup_clk)
begin
rst_src_pll <= rst_src_pll_pre;
rst_src_pll_pre <= !pll_locked;
end
@pll_locked est la sortie du détecteur de verrouillage de la PLL qui génère @wakeup_clk (active à l’état haut). Ce signal est asynchrone ; il est donc d’abord synchronisé avec @wakeup_clk, puis utilisé comme l’une des raisons d’activer @clear_counter, comme indiqué plus haut (c’est-à-dire par l’intermédiaire de @rst_src_pll).
Un autre avantage de cette option est que si l’horloge de référence est temporairement instable ou absente (en particulier après la mise sous tension de la carte), il y a de bonnes chances que le détecteur de verrouillage soit également instable. Donc, si @reset_count compte jusqu’à une valeur élevée (sûrement bien supérieure à 31) avant de désactiver @master_reset, il y a une chance que le FPGA reste fermement en reset jusqu’à ce que l’horloge de référence soit exploitable. On ne peut toutefois pas s’y fier.
Dans tous les cas, le fait que @wakeup_clk soit une sortie de PLL signifie nécessairement qu’elle alimente la logique avant que cette horloge ne soit stabilisée. On ne sait donc pas si la machine à états fonctionne correctement pendant cette période. On peut soutenir que cela n’a pas d’importance, car à un moment donné l’horloge deviendra suffisamment bonne et des resets corrects seront alors générés. Si tout ce qui s’est passé avant un reset est laissé derrière, qu’importe ce qui s’est passé avant ?
Une approche plus rigoureuse consiste à maintenir les resets fermement actifs jusqu’à ce que l’horloge de réveil soit verrouillée, afin que le FPGA ne se comporte pas bizarrement juste après la configuration de son flux binaire. Cela exige de veiller à n’utiliser que de la logique très simple avec cette horloge. Autrement dit, cette logique ne doit pas échouer de manière significative si l’horloge a une fréquence plus élevée que prévu.
Pour analyser ce qui se passe si @wakeup_clk a temporairement une fréquence trop élevée, notez que @reset_count est le seul registre vectoriel dans la machine à états de reset. Cela signifie que toutes les autres bascules peuvent au pire échantillonner leur « valeur suivante » calculée un cycle d’horloge trop tard, à cause d’une violation de contraintes temporelles. En particulier, comme @pll_locked est à l’état bas tant que la PLL n’est pas verrouillée, @rst_src_pll devient rapidement stable à l’état haut, et donc @clear_counter est lui aussi stable à l’état haut. Lorsque l’entrée D (valeur suivante) d’une bascule ne change pas, la rapidité de l’horloge n’a aucune importance.
Le seul problème possible concerne donc @reset_count, qui pourrait compter de manière incorrecte jusqu’à ce que sa valeur suivante calculée soit maintenue stable à zéro par @clear_counter. Par exemple, si sa valeur courante est 3 (binaire 011), sa prochaine valeur calculée est 4 (binaire 100). Mais si les deux bits de poids faible ne sont pas échantillonnés à cause du timing, tandis que le troisième bit l’est, la valeur du compteur peut sauter à 7 (binaire 111) à la place.
Pour éviter cela, les valeurs initiales de la chaîne de registres allant de @pll_locked à @clear_counter sont toutes choisies de manière à maintenir @reset_count stable à zéro. Ainsi, si @pll_locked reste stable à l’état bas (comme il se doit) jusqu’à ce que @wakeup_clk soit stable, @reset_count ne s’éloignera pas de zéro et @master_reset restera fermement actif.
Quant à la dernière option, utiliser l’oscillateur en anneau du FPGA pour le contrôleur de reset : je ne l’ai pas essayé moi-même, donc je ne sais pas dans quelle mesure c’est une bonne idée. Mais si cela peut aider quelqu’un qui n’a pas d’autre solution, voici à peu près comment faire avec les FPGA Xilinx : cherchez une primitive appelée STARTUPE2 (ou quelque chose du genre) dans le Configuration User Guide de votre FPGA. Elle devrait avoir une sortie nommée CFGMCLK, qui est une horloge produite par un oscillateur en anneau imprécis à l’intérieur du FPGA. Sa fréquence est d’environ 50 à 65 MHz. Cette horloge est garantie stable au réveil du FPGA, et je fixerais la contrainte temporelle à une fréquence nettement supérieure (disons 100 MHz).
Mais c’est quelque chose que je ferais seulement s’il n’y avait vraiment pas d’autre choix. Par exemple, si l’horloge de référence externe n’est pas stable au réveil du FPGA et qu’il faut donc implémenter un délai supplémentaire en logique.
Réinitialiser les PLL
En général, c’est une bonne idée de réinitialiser les PLL dans le cadre de la séquence de reset. Cela garantit qu’elles sont réinitialisées lorsque leur horloge de référence est connue comme valide. De plus, si le reset est déclenché par l’utilisateur (par exemple en appuyant sur un bouton de reset), cela peut être en réponse à un problème provenant d’une PLL mal verrouillée. Cela ne devrait pas arriver, mais au cas où.
On ne peut pas utiliser @clear_counter pour réinitialiser la PLL, parce qu’il devient actif dès qu’une PLL cesse d’être verrouillée. S’il était utilisé, la PLL resterait en état de reset, ne se verrouillerait jamais, et le reset ne serait jamais relâché. Pour la même raison, les resets des PLL ne peuvent pas être dérivés de @reset_count : il est maintenu à zéro sauf lorsque toutes les PLL sont verrouillées.
La solution est de créer un registre de reset séparé pour la PLL, similaire à @clear_counter. En reprenant la notation ci-dessus, où les PLL non verrouillées se reflètent dans @rst_src_pll, cela revient à quelque chose comme ceci :
reg clear_counter;
reg reset_plls;
initial clear_counter = 1;
initial reset_plls = 1;
assign reset_sources = rst_src_shreg || rst_src_debounce;
always @(posedge wakeup_clk)
begin
clear_counter <= rst_src_pll || reset_sources;
reset_plls <= reset_sources;
[ ... ]
Dans cet extrait, @rst_src_pll a été exclu de @reset_sources et n’est utilisé que pour @clear_counter. En conséquence, les PLL sont réinitialisées en même temps que tout le FPGA, sauf lorsque c’est précisément parce qu’elles ne sont pas verrouillées.
Il vaut la peine de préciser que @wakeup_clk lui-même ne peut pas être généré par la PLL que l’on réinitialise ; cette horloge doit donc être produite à partir des autres possibilités.
Notez que si @reset_sources passe de manière aléatoire à l’état bas et à l’état haut, @reset_plls fera de même d’après le code présenté. C’est généralement sans danger, car il n’arrive rien de grave aux PLL quand leur reset s’agite dans tous les sens. Il existe néanmoins un moyen d’éviter cela, comme expliqué ci-dessous.
Séquences de démarrage plus complexes
L’utilisation d’un simple compteur (@reset_count) comme variable d’état facilite l’implémentation de séquences de démarrage plus compliquées. Par exemple, générer de bons resets asynchrones (asynchronous resets), actifs alors que les horloges sont coupées, est assez simple : on utilise des expressions logiques simples qui définissent les créneaux temporels pendant lesquels les horloges sont coupées et les resets actifs.
Cette méthode du compteur simple est donc un bon point de départ, même pour des designs qui paraissent avoir des besoins simples en matière de machine à états de reset au début du projet. Si l’on découvre plus tard qu’une séquence de démarrage complexe est nécessaire, il est facile d’étendre la logique existante pour y parvenir.
Dans tous les cas, planifier la séquence de démarrage revient à définir les périodes de temps pendant lesquelles chaque phase de la séquence est effective. Chaque période se traduit par une plage de valeurs que @reset_count doit prendre.
Par exemple, si le design a deux horloges indépendantes ou plus, il est possible de créer des signaux de reset pour chaque domaine d’horloge (clock domain) comme indiqué plus haut (voir « Resets pour les autres domaines d’horloge »). Mais si l’on adopte cette méthode, chaque domaine d’horloge sort du reset dans un ordre effectivement aléatoire. Ce n’est généralement pas un problème, mais si cela en est un, on peut désactiver le reset de chaque domaine d’horloge à un moment défini, en fonction de la progression de @reset_count.
Il est aussi possible d’implémenter plus d’un compteur. Cela est utile lorsque la séquence de reset exige d’attendre que certaines conditions soient satisfaites avant de continuer. Par exemple, si la séquence de reset implique de réinitialiser des PLL, d’attendre leur verrouillage, puis de poursuivre la séquence, il est sensé d’avoir un compteur maintenu à zéro jusqu’à ce que toutes les PLL soient verrouillées. Le second compteur est maintenu à zéro jusqu’à ce que le premier ait fini de compter.
Notez que si une PLL perd son verrouillage, les PLL elles-mêmes ne seront pas réinitialisées, mais tout ce qui en dépend le sera. Ce n’est peut-être pas le comportement souhaité, car une PLL qui perd son verrouillage est une faute grave dans la plupart des designs. Pour demander un reset complet en cas de perte de verrouillage, ajoutez @pll_restart, défini ci-dessous, aux signaux combinés par un OU pour obtenir @reset_sources :
assign pll_restart = rst_src_pll && !master_reset;
Cela signifie simplement : si la PLL s’est déverrouillée alors que le reset principal était inactif, tout est réinitialisé à nouveau, y compris les PLL. Pour que cela fonctionne, la durée du compteur de reset doit être assez longue pour supporter les éventuels tremblements des détecteurs de verrouillage. Autrement dit, le compte de reset doit être plus long que le temps pendant lequel le détecteur de verrouillage d’une PLL peut rester à l’état haut alors que la PLL n’est pas encore verrouillée. Il ne faut pas confondre cela avec le temps nécessaire à la PLL pour acquérir le verrouillage. Certains détecteurs de verrouillage ne tremblent pas du tout, et ces tremblements sont le plus souvent nettement plus courts que le temps de verrouillage de la PLL (comme indiqué dans la fiche technique).
Le registre à décalage de réveil
C’est peut-être un peu excessif, mais j’ajoute généralement un registre à décalage de réveil de ce genre dans mes designs :
reg [15:0] wakeup_shift;
reg rst_src_shreg;
initial rst_src_shreg = 1;
initial wakeup_shift = 0;
always @(posedge wakeup_clk)
begin
rst_src_shreg <= !wakeup_shift[15];
wakeup_shift <= { wakeup_shift, 1'b1 };
end
Comme la plupart des FPGA implémentent @wakeup_shift sous forme de primitive de registre à décalage, qui consomme l’équivalent d’une LUT, cela coûte peu de ressources et offre un mécanisme supplémentaire pour garantir qu’un reset a lieu pendant la mise sous tension. C’est nécessaire dans un design sans PLL, car rien d’autre n’activera @clear_counter. Mais même avec des PLL, il est possible qu’elles soient déjà verrouillées au réveil du FPGA, car c’est une option possible pour la configuration du flux binaire sur certains FPGA.
Quoi qu’il en soit, c’est un ajout recommandé. Mieux vaut prévenir que guérir.
Bouton de reset externe
Les boutons de reset sont très courants. Ce qu’ils font exactement diffère d’un design à l’autre. Une possibilité est que le bouton que l’utilisateur considère comme « reset » soit connecté à la broche du FPGA qui déclenche le processus de chargement du flux binaire dans le FPGA. Il peut aussi être connecté à la broche de reset d’un processeur, sur les FPGA dotés d’un processeur intégré.
Ce bouton de reset pourrait aussi être connecté à une broche d’entrée-sortie générale du FPGA, dans le but de réinitialiser la logique du FPGA. Dans ce cas, c’est encore une raison d’activer @clear_counter.
Si @reset_count compte jusqu’à une valeur correspondant à 10 ms ou plus, il n’est pas nécessaire de supprimer les rebonds du signal provenant de la broche d’entrée, car le compteur absorbera les tremblements. Dans ce cas, cela suffit :
reg rst_src_debounce;
reg rst_src_debounce_pre;
initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;
always @(posedge wakeup_clk)
begin
rst_src_debounce <= rst_src_debounce_pre;
rst_src_debounce_pre <= reset_button_pin;
end
En revanche, si le compteur finit rapidement (comme dans l’exemple précédent, avec seulement 31), le bouton poussoir a besoin d’un anti-rebond. Il y a plusieurs façons de faire, par exemple :
reg [17:0] debounce_count = 0;
reg reset_button_d, reset_button_d2, reset_button_d3;
wire debounce_reached = (debounce_count == 250000);
initial debounce_count = 0;
initial rst_src_debounce = 1;
always @(posedge wakeup_clk)
begin
reset_button_d3 <= reset_button_d2;
reset_button_d2 <= reset_button_d;
reset_button_d <= reset_button_pin;
if (reset_button_d2 != reset_button_d3)
debounce_count <= 0;
else if (!debounce_reached)
debounce_count <= debounce_count + 1;
if (debounce_reached)
rst_src_debounce <= reset_button_d3;
end
C’est un anti-rebond relativement strict, qui ne fonctionne pas bien avec des signaux d’entrée bruités. C’est un avantage ou un inconvénient, selon vos besoins.
Cet exemple de code est écrit pour une horloge de 25 MHz. La valeur de @reset_button_pin est recopiée dans @rst_src_debounce si @reset_button_pin a gardé la même valeur pendant 10 ms. Notez que @debounce_count repasse à zéro au même cycle d’horloge que celui où @reset_button_d3 change de valeur. Ainsi, lorsque @reset_button_d3 change de valeur, il n’est jamais recopié immédiatement dans @rst_src_debounce, mais seulement après avoir gardé la même valeur pendant un bon moment.
Résumé
Il y a beaucoup de choses à considérer lors de la conception de l’initialisation d’un FPGA. Cette page a présenté quelques concepts et idées, mais il est important de garder à l’esprit que la vraie tâche consiste à reconnaître correctement les événements qui doivent déclencher une action. Il est également important de définir la réponse correcte à chacun de ces événements afin d’amener de manière fiable la logique dans un état de fonctionnement.