Machines à États (Mealy/Moore)

Conception et implémentation des machines à états finis (FSM) en VHDL : Moore et Mealy.

Machines à États Finis (FSM)

Une FSM (Finite State Machine) est un circuit séquentiel qui :

  • Se trouve dans un état à la fois
  • Passe d'un état à l'autre selon des transitions (dépendent des entrées)
  • Produit des sorties selon l'état (et éventuellement les entrées)

Moore vs Mealy

MooreMealy
Sorties dépendent deÉtat uniquementÉtat + Entrées
SortiesPlus stablesRéagit plus vite
Latence1 cycle supplémentaireRéponse immédiate
Recommandé pour FPGAOui (plus simple à synthétiser)Possible mais attention aux glitches

Modèle 2 process (recommandé)

Exemple : détecteur de séquence 101 (Moore).

État actuelEntrée i_dataÉtat suivantSortie o_found
IDLE0IDLE0
IDLE1GOT_10
GOT_11GOT_10
GOT_10GOT_100
GOT_101FOUND0
GOT_100IDLE0
FOUNDxIDLE1

Le style le plus courant sur FPGA : un process pour l'état, un pour les sorties.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
 
entity fsm_detector is
  port (
    i_clk   : in  std_logic;
    i_rst   : in  std_logic;
    i_data  : in  std_logic;
    o_found : out std_logic
  );
end entity fsm_detector;
 
architecture rtl of fsm_detector is
 
  -- Déclaration du type état
  type t_state is (IDLE, GOT_1, GOT_10, FOUND);
 
  signal r_state : t_state;
  signal w_next  : t_state;
 
begin
 
  -- Process 1 : registre d'état (séquentiel)
  p_state_reg : process(i_clk)
  begin
    if rising_edge(i_clk) then
      if i_rst = '1' then
        r_state <= IDLE;
      else
        r_state <= w_next;
      end if;
    end if;
  end process p_state_reg;
 
  -- Process 2 : logique de transition (combinatoire)
  p_next_state : process(r_state, i_data)
  begin
    w_next <= r_state;  -- par défaut : rester dans l'état
 
    case r_state is
      when IDLE =>
        if i_data = '1' then
          w_next <= GOT_1;
        end if;
 
      when GOT_1 =>
        if i_data = '0' then
          w_next <= GOT_10;
        else
          w_next <= GOT_1;
        end if;
 
      when GOT_10 =>
        if i_data = '1' then
          w_next <= FOUND;
        else
          w_next <= IDLE;
        end if;
 
      when FOUND =>
        w_next <= IDLE;
 
      when others =>
        w_next <= IDLE;
    end case;
  end process p_next_state;
 
  -- Sorties Moore : dépendent uniquement de r_state
  o_found <= '1' when r_state = FOUND else '0';
 
end architecture rtl;

Modèle 3 process (variante)

Un troisième process pour les sorties (utile si les sorties sont complexes).

-- Process 3 : logique de sortie (combinatoire - Moore)
p_outputs : process(r_state)
begin
  -- Valeurs par défaut (évite les latches)
  o_found  <= '0';
  o_active <= '0';
 
  case r_state is
    when FOUND =>
      o_found <= '1';
    when GOT_1 | GOT_10 =>
      o_active <= '1';
    when others =>
      null;
  end case;
end process p_outputs;

Exemple complet : FSM feux de circulation

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
 
entity traffic_light is
  generic (
    g_CLK_FREQ : integer := 100_000_000  -- 100 MHz
  );
  port (
    i_clk   : in  std_logic;
    i_rst   : in  std_logic;
    o_rouge : out std_logic;
    o_orange: out std_logic;
    o_vert  : out std_logic
  );
end entity traffic_light;
 
architecture rtl of traffic_light is
 
  type t_light is (ROUGE, ORANGE, VERT);
 
  constant c_ROUGE_DUR  : integer := 30 * g_CLK_FREQ;  -- 30s
  constant c_ORANGE_DUR : integer := 3  * g_CLK_FREQ;  -- 3s
  constant c_VERT_DUR   : integer := 25 * g_CLK_FREQ;  -- 25s
 
  signal r_state   : t_light;
  signal r_counter : integer range 0 to c_ROUGE_DUR;
 
begin
 
  p_fsm : process(i_clk)
  begin
    if rising_edge(i_clk) then
      if i_rst = '1' then
        r_state   <= ROUGE;
        r_counter <= 0;
      else
        r_counter <= r_counter + 1;
 
        case r_state is
          when ROUGE =>
            if r_counter = c_ROUGE_DUR - 1 then
              r_state   <= VERT;
              r_counter <= 0;
            end if;
          when VERT =>
            if r_counter = c_VERT_DUR - 1 then
              r_state   <= ORANGE;
              r_counter <= 0;
            end if;
          when ORANGE =>
            if r_counter = c_ORANGE_DUR - 1 then
              r_state   <= ROUGE;
              r_counter <= 0;
            end if;
        end case;
      end if;
    end if;
  end process p_fsm;
 
  -- Sorties Moore
  o_rouge  <= '1' when r_state = ROUGE  else '0';
  o_orange <= '1' when r_state = ORANGE else '0';
  o_vert   <= '1' when r_state = VERT   else '0';
 
end architecture rtl;
ÉtatDuréeÉtat suivant
ROUGE30 sVERT
VERT25 sORANGE
ORANGE3 sROUGE

Bonnes pratiques FSM

1. Toujours initialiser au reset

if i_rst = '1' then
  r_state <= IDLE;  -- état de départ défini
end if;

2. Couvrir tous les états avec when others

when others =>
  r_state <= IDLE;  -- état sûr par défaut

3. Valeurs de sortie par défaut

-- En début de process de sortie
o_valid <= '0';
o_data  <= (others => '0');
-- Puis surcharger dans chaque case

4. Un type énuméré par FSM

type t_uart_state is (IDLE, START, DATA, PARITY, STOP);
-- Chaque FSM a son propre type → nommage clair

Encodage des états

Le synthétiseur choisit automatiquement l'encodage. Il est possible de le forcer :

EncodageUsageRessources
BinairePeu d'étatsMinimum de bascules
One-hotBeaucoup d'étatsPlus de bascules, plus rapide
GrayCompteurs séquentielsTransitions d'un seul bit

Sur FPGA moderne, laisser le synthétiseur décider est généralement optimal.


Catégories de FSM (Pedroni)

Il est utile de classer les machines à états en trois catégories selon leur complexité :

Catégorie 1 — FSM régulière (simple)

La machine passe d'un état à l'autre uniquement selon des signaux d'entrée externes. Pas de temporisation, pas de récursivité.

C'est la FSM classique : détecteur de séquence, contrôleur de protocole, décodeur de commandes.

-- Exemple : détecteur de flanc montant
type t_edge is (IDLE, DETECT);
signal r_state : t_edge;
 
process(i_clk)
begin
  if rising_edge(i_clk) then
    if i_rst = '1' then
      r_state <= IDLE;
    else
      case r_state is
        when IDLE   => if i_sig = '1' then r_state <= DETECT; end if;
        when DETECT => r_state <= IDLE;
        when others => r_state <= IDLE;
      end case;
    end if;
  end if;
end process;
 
o_pulse <= '1' when r_state = DETECT else '0';

Catégorie 2 — FSM temporisée (avec compteur)

La machine attend un nombre de cycles défini avant de changer d'état. Un compteur interne mesure le temps écoulé.

C'est le cas des feux de circulation, des générateurs PWM, des délais de démarrage.

-- FSM avec compteur interne
type t_timed is (WAIT_LOW, WAIT_HIGH);
signal r_state   : t_timed;
signal r_timer   : unsigned(19 downto 0) := (others => '0');
 
constant c_T_LOW  : unsigned(19 downto 0) := to_unsigned(499_999, 20); -- 5 ms @ 100 MHz
constant c_T_HIGH : unsigned(19 downto 0) := to_unsigned(999_999, 20); -- 10 ms @ 100 MHz
 
process(i_clk)
begin
  if rising_edge(i_clk) then
    if i_rst = '1' then
      r_state <= WAIT_LOW;
      r_timer <= (others => '0');
    else
      r_timer <= r_timer + 1;
 
      case r_state is
        when WAIT_LOW =>
          if r_timer = c_T_LOW then
            r_state <= WAIT_HIGH;
            r_timer <= (others => '0');
          end if;
 
        when WAIT_HIGH =>
          if r_timer = c_T_HIGH then
            r_state <= WAIT_LOW;
            r_timer <= (others => '0');
          end if;
 
        when others =>
          r_state <= WAIT_LOW;
          r_timer <= (others => '0');
      end case;
    end if;
  end if;
end process;
 
o_out <= '1' when r_state = WAIT_HIGH else '0';

Bonne pratique : remettre le compteur à zéro (r_timer <= (others => '0')) dès qu'on change d'état pour garantir un timing précis.


Catégorie 3 — FSM récursive (avec paramètre variable)

La machine passe par des états un nombre variable de fois selon un paramètre d'entrée (boucle sur N cycles, traitement de N octets, etc.).

C'est le cas des sérialiseurs, des contrôleurs SPI/UART, des moteurs de compression.

-- Sérialiseur 8 bits : envoie 8 bits un par un
type t_ser is (IDLE, SHIFT);
signal r_state  : t_ser;
signal r_shreg  : std_logic_vector(7 downto 0);
signal r_count  : unsigned(2 downto 0);
 
process(i_clk)
begin
  if rising_edge(i_clk) then
    if i_rst = '1' then
      r_state  <= IDLE;
      r_count  <= (others => '0');
      o_bit    <= '0';
      o_done   <= '0';
    else
      o_done <= '0';
 
      case r_state is
        when IDLE =>
          if i_valid = '1' then
            r_shreg <= i_data;
            r_count <= (others => '0');
            r_state <= SHIFT;
          end if;
 
        when SHIFT =>
          o_bit   <= r_shreg(7);
          r_shreg <= r_shreg(6 downto 0) & '0';  -- décalage gauche
          r_count <= r_count + 1;
          if r_count = 7 then
            o_done  <= '1';
            r_state <= IDLE;
          end if;
 
        when others =>
          r_state <= IDLE;
      end case;
    end if;
  end if;
end process;

Tableau de synthèse

CatégorieDéclencheur de transitionExemples
RégulièreEntrées externesDétecteur de séquence, décodeur
TemporiséeCompteur interne (N cycles fixes)Feux, PWM, délai de démarrage
RécursiveCompteur interne (N variable)UART TX, SPI, sérialiseur

Machines à états sûres (safe state machines)

Un FPGA peut se retrouver dans un état non défini au démarrage ou suite à un glitch. Une FSM sûre gère explicitement tous les cas imprévus.

-- Toujours inclure when others
case r_state is
  when IDLE    => ...
  when RUN     => ...
  when DONE    => ...
  when others  =>
    -- État inconnu : retour à l'état sûr
    r_state <= IDLE;
end case;
-- Forcer l'encodage one-hot pour réduire les états illégaux (Vivado)
attribute fsm_encoding : string;
attribute fsm_encoding of r_state : signal is "one_hot";

Sur FPGA, les bascules ne se réinitialisent pas automatiquement à la mise sous tension. Toujours initialiser au reset et toujours couvrir when others.