Menu

Machines à états finis en Verilog : le motif FSM standard

Comment écrire une FSM Verilog comme le font les pros - un registre d'état cadencé, un bloc combinatoire de prochain état, et une séparation propre facile à lire et à synthétiser.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Ce qu'est une FSM

Une machine à états finis est un contrôleur qui :

  • Tient un état parmi un ensemble fixe d'états nommés à tout instant donné.
  • Transitionne entre états selon les entrées (et possiblement l'état courant).
  • Produit des sorties qui dépendent de l'état courant (et peut-être des entrées courantes).

Ça couvre une quantité impressionnante de design numérique : contrôleurs de feux tricolores, émetteurs UART, contrôleurs mémoire, gestionnaires de protocoles réseau, décodeurs d'instructions, tout ce qui a des modes opératoires discrets.

Ce qui fait que les FSM se sentent différentes de la logique datapath, c'est qu'elles sont des états, pas des valeurs. Un compteur tique à travers 1, 2, 3, 4. Une FSM tique à travers IDLE, FETCHING, BUSY, DONE - même forme, mais les états ont des noms et les transitions ont du sens.

Le motif à deux processus

La FSM Verilog standard utilise deux blocs always :

  1. Un bloc cadencé qui capture le registre d'état à chaque front d'horloge. Utilise l'assignation non-blocking. Minuscule - généralement trois lignes.
  2. Un bloc combinatoire qui utilise un case sur l'état courant pour calculer le prochain état et les sorties. Utilise l'assignation blocking. Le code « intéressant » vit ici.

Cette séparation a trois bénéfices : elle te force à penser explicitement à l'état, elle produit du matériel qui mappe proprement à « un registre plus un bloc combinatoire », et c'est le standard - tous ceux qui liront ton Verilog le reconnaîtront instantanément.

Un exemple : détecteur de séquence

Une FSM d'enseignement classique : détecter la séquence binaire 1011 sur une entrée série. Sortir une pulse d'un cycle quand la séquence se complète.

La structure à deux blocs est le point clé ci-dessus. Les détails de propreté valent d'être soulignés :

  • Défauts en haut du bloc combinatoire. next_state = state (reste où tu es) et detected = 1'b0 (pas de pulse) sont les assignations « ne rien faire ». Chaque branche case ne fixe ensuite que ce qui diffère. Ça rend impossible l'inférence d'un latch.
  • localparam pour les noms d'états. Quiconque lit le module pense en S0, S1, S2, S3, pas en 3'd0, 3'd1. Le synthétiseur fait la substitution.
  • Pas de sorties depuis le bloc cadencé. Toute la logique « que fait cet état » vit dans le bloc combinatoire. Le bloc cadencé n'est responsable de rien sauf de tenir l'état courant.

Moore vs Mealy

Moore : la sortie ne dépend que de l'état courant. Mealy : la sortie dépend de l'état courant et des entrées courantes.

Dans l'exemple ci-dessus, detected est mis dans la branche S3 seulement quand in matche l'un des motifs complétant la séquence. C'est une sortie Mealy - elle dépend de in en plus de state. Une version Moore aurait un état séparé pour « vient de détecter » et mettrait detected = 1 chaque fois que cet état est courant ; la sortie pulserait un cycle plus tard mais ne serait jamais sensible à un glitch sur in.

Les deux styles sont valides. Moore est le défaut dans les manuels parce que les sorties sont garanties de ne pas glitcher quand les entrées changent en milieu de cycle. Mealy est plus rapide (pas de latence de registre pour les sorties pilotées par entrées) et produit du matériel plus petit dans beaucoup de cas. Choisis selon le protocole que tu implémentes.

Encodage d'état : binaire, one-hot, Gray

Le motif de bits que tu assignes à chaque état compte pour la surface et la vitesse :

  • Binaire (S0 = 3'd0, S1 = 3'd1, ...) : plus petit registre d'état - log2N\lceil \log_2 N \rceil bits pour NN états. Logique de décodage maximale.
  • One-hot (S0 = 4'b0001, S1 = 4'b0010, ...) : N bits pour N états. La logique de décodage est triviale (chaque état est un wire) ; les transitions sont rapides. Les FPGA défaultent souvent à ça.
  • Code Gray : les états consécutifs diffèrent d'un bit. Utile quand les bits d'état traversent des domaines d'horloge.

La plupart des outils de synthèse modernes choisissent l'encodage pour toi (Vivado, Quartus, Design Compiler ont tous un mode automatique qui essaie chacun et choisit le meilleur). Tu as rarement besoin de spécifier. Spécifie quand tu en as besoin : la plupart des outils acceptent une annotation attribute ou un pragma (* fsm_encoding = "one_hot" *).

Une variante à trois blocs

Tu verras occasionnellement la FSM divisée en trois : un bloc cadencé pour l'état, un bloc combinatoire pour le next-state, un bloc combinatoire pour les sorties. C'est juste le motif à deux blocs avec le calcul de sortie sorti dans son propre bloc :

// Registre d'état
always @(posedge clk) ...

// Logique next-state
always @(*) ...

// Logique de sortie
always @(*) begin
    case (state)
        ...
    endcase
end

Le style sortie séparée est utile quand les sorties sont grosses et que la logique next-state serait encombrée si elles étaient dans le même bloc. Pour les petites FSM c'est de la surenchère.

Ce que fait default dans une FSM

L'instruction case de chaque FSM devrait avoir une branche default. Deux raisons :

  1. Sécurité : si le registre d'état prend une valeur invalide pour quelque raison (corruption, bug, reset partiel), default le ramène à un état connu.
  2. Indice de synthèse : quand les cas explicites sont exhaustifs (un état 2 bits avec les 4 valeurs gérées, par exemple), default: next_state = 'x; dit au synthétiseur « je promets que le default est inatteignable, optimise librement ». Si le chemin inatteignable est effectivement touché en simulation, le x résultant se propage et fait surface au bug immédiatement.
default: begin
    next_state = S0;   // récupération sûre
    // ou
    next_state = 'x;   // inatteignable, optimise librement
end

Choisis selon que tu as prouvé ou non que le default est vraiment inatteignable.

Choses à surveiller

Oublier les défauts en haut du bloc combinatoire. Sans next_state = state et les défauts de sortie, une branche qui n'assigne pas tout fait fuir un latch.

Mettre les sorties dans le bloc cadencé. Si detected <= 1 vit dans le bloc always @(posedge clk), la sortie est registrée - elle apparaît un cycle en retard. Ça peut être intentionnel (une sortie « Mealy registrée »), mais c'est une erreur de design accidentelle courante quand la spec demande une pulse instantanée.

Mélanger blocking et non-blocking. Bloc cadencé : <=. Bloc combinatoire : =. Les mélanger dans un bloc est une race condition.

Un bloc combinatoire always qui référence next_state et assigne state. Ça construit une boucle de feedback que le simulateur ne peut pas résoudre. Le bloc cadencé possède state ; le bloc combinatoire possède next_state ; ne laisse jamais l'un toucher la variable de l'autre.

La suite

Tu peux maintenant construire n'importe quel contrôleur que tu peux décrire. Le prochain chapitre prend du recul par rapport au design synthétisable et couvre les testbenches qui l'exercent - comment piloter le stimulus, observer les sorties, dumper des waveforms, et valider que tes modules font vraiment ce que tu penses.

Questions fréquentes

Qu'est-ce qu'une machine à états finis en Verilog ?

Une FSM est un contrôleur qui tient un état parmi un petit ensemble d'états nommés et transitionne entre eux selon les entrées. En Verilog, l'implémentation standard a deux blocs : un always cadencé qui met à jour le registre d'état à chaque front d'horloge, et un always combinatoire qui calcule le prochain état et les sorties à partir de l'état courant et des entrées.

Quel est le motif FSM standard en Verilog ?

FSM à deux processus : un bloc cadencé always @(posedge clk) tient le registre d'état et utilise l'assignation non-blocking, et un bloc combinatoire always @(*) utilise un case sur l'état courant pour calculer le prochain état et les sorties. Cette séparation rend le code facile à lire, linter et synthétiser proprement.

Quelle est la différence entre une FSM Mealy et Moore ?

Dans une FSM Moore, les sorties ne dépendent que de l'état courant. Dans une FSM Mealy, les sorties dépendent à la fois de l'état courant et des entrées courantes. Les machines Mealy réagissent un cycle plus vite (pas de latence de registre pour les sorties dépendant des entrées) mais peuvent produire des glitches si les entrées changent en milieu de cycle. Les machines Moore sont plus lentes d'un cycle mais plus prévisibles - c'est le choix par défaut sauf si tu as besoin de la vitesse.

Comment encoder les états en Verilog ?

Utilise des constantes localparam à l'intérieur du module : localparam IDLE = 3'd0; etc. Trois encodages courants : binaire (états 0, 1, 2, ... - plus petit registre d'état), one-hot (un bit par état, moins de niveaux de logique par transition), et gray code (les états consécutifs diffèrent d'un bit - minimise les glitches). Les outils de synthèse choisissent généralement l'encodage pour toi ; le fixer est rarement nécessaire.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER