Automates

Dans une section précédente, nous avons défini la notion d’automate fini et son application à la réalisation de circuits logiques séquentiels. À travers un exemple, nous avons identifié deux étapes permettant d’aboutir à un circuit :

  1. Modéliser le comportement du système sous la forme d’un graphe d’états.
  2. Mettre en équations ce graphe d’états après avoir choisi un système de codage binaire des états.

En utilisant le langage VHDL et un outil de synthèse logique, l’étape 2 peut être partiellement automatisée à condition de respecter certaines règles d’écriture. L’exercice consiste à traduire le graphe d’états en instructions VHDL qui soient à la fois :

En reprenant l’exemple du robot autonome, notre description VHDL doit, d’une part, capturer les éléments du graphe ci-dessous :

Graphe d'états du robot autonome (Mealy)

Et d’autre part, elle doit décrire un circuit respectant cette structure :

Structure typique d'un circuit réalisant un automate fini

Entité et squelette de l’architecture

La partie commande du robot sera décrite par une entité RobotController et une architecture StateMachine. L’entité aura les ports suivants :

Port Direction Type Rôle
clk_i Entrée Logique Signal d’horloge
reset_i Entrée Logique Commande de réinitialisation
front_sensor_i Entrée Logique Détecteur d’obstacle à l’avant
left_sensor_i Entrée Logique Détecteur d’obstacle à gauche
right_sensor_i Entrée Logique Détecteur d’obstacle à droite
left_motor_forward_o Sortie Logique Commande du moteur gauche vers l’avant
left_motor_back_o Sortie Logique Commande du moteur gauche vers l’arrière
right_motor_forward_o Sortie Logique Commande du moteur droit vers l’avant
right_motor_back_o Sortie Logique Commande du moteur droit vers l’arrière

L’indicateur de fin de rotation (signal rotation_done) sera géré en interne (voir plus bas).

library ieee;
use ieee.std_logic_1164.all;

entity RobotController is
    port(
        clk_i, reset_i                                : in std_logic;
        front_sensor_i, left_sensor_i, right_sensor_i : in std_logic;
        left_motor_forward_o, left_motor_back_o       : out std_logic;
        right_motor_forward_o, right_motor_back_o     : out std_logic
    );
end RobotController;

architecture StateMachine of RobotController is
    ...
    signal rotation_done : std_logic;
    ...
begin
    ...
end StateMachine;

Énumération des états

Nous allons commencer par déclarer la liste des états de l’automate en utilisant un type énuméré state_t avec les valeurs suivantes :

Symbole Rôle
FIND_WALL État « Recherche d’un mur »
FOLLOW_WALL État « Suivi d’un mur »
TURN_LEFT État « Rotation à gauche »
TURN_RIGHT État « Rotation à droite »

Dans la description ci-dessous, les signaux state_reg et state_next, de type state_t, peuvent prendre quatre valeurs possibles représentées par ces symboles. En procédant ainsi, nous laissons à l’outil de synthèse la liberté d’associer un codage binaire de son choix à chacun des symboles.

architecture StateMachine of RobotController is
    type state_t is (FIND_WALL, FOLLOW_WALL, TURN_LEFT, TURN_RIGHT);
    signal state_reg, state_next : state_t;
    signal rotation_done         : std_logic;
    ...
begin
    ...
end StateMachine;

Fonction de transition

La fonction de transition calcule l’état suivant state_next en fonction de l’état courant state_reg, des entrées front_sensor_i, left_sensor_i, right_sensor_i, et de l’indicateur rotation_done. Elle est décrite au moyen des instructions suivantes :

architecture StateMachine of RobotController is
    type state_t is (FIND_WALL, FOLLOW_WALL, TURN_LEFT, TURN_RIGHT);
    signal state_reg, state_next : state_t;
    signal rotation_done         : std_logic;
    ...
begin
    p_state_next : process(state_reg, front_sensor_i, left_sensor_i, rotation_done)
    begin
        case state_reg is
            when FIND_WALL =>
                if front_sensor_i = '1' then
                    state_next <= TURN_RIGHT;
                elsif left_sensor_i = '1' then
                    state_next <= TURN_LEFT;
                else
                    state_next <= FIND_WALL;
                end if;
            when FOLLOW_WALL =>
                if left_sensor_i = '0' then
                    state_next <= TURN_LEFT;
                elsif front_sensor_i = '1' then
                    state_next <= TURN_RIGHT;
                else
                    state_next <= FOLLOW_WALL;
                end if;
            when TURN_LEFT | TURN_RIGHT =>
                if rotation_done = '1' then
                    state_next <= FIND_WALL;
                else
                    state_next <= state_reg;
                end if;
        end case;
    end process p_state_next;

    ...
end StateMachine;

Registre d’état

Le signal state_reg est mis à jour sur les fronts montants de clk_i. La réinitialisation asynchrone ramène l’automate dans son état initial FIND_WALL :

p_state_reg : process(clk_i, reset_i)
begin
    if reset_i = '1' then
        state_reg <= FIND_WALL;
    elsif rising_edge(clk_i) then
        state_reg <= state_next;
    end if;
end process p_state_reg;

Comme pour la description des compteurs, il est possible de regrouper le registre d’état et la fonction de transition dans un même processus.

Fonction de sortie

Modèle de Moore

Dans le modèle de Moore, les sorties ne dépendent que de l’état courant. Ici, on dispose d’une grande liberté pour écrire les commandes des moteurs.

left_motor_forward_o  <= '0' when state_reg = TURN_LEFT  else '1';
right_motor_forward_o <= '0' when state_reg = TURN_RIGHT else '1';
left_motor_back_o     <= '1' when state_reg = TURN_LEFT  else '0';
right_motor_back_o    <= '1' when state_reg = TURN_RIGHT else '0';
with state_reg select
    left_motor_forward_o <= '1' when FIND_WALL | FOLLOW_WALL | TURN_RIGHT,
                            '0' when TURN_LEFT;

with state_reg select
    right_motor_forward_o <= '1' when FIND_WALL | FOLLOW_WALL | TURN_LEFT,
                             '0' when TURN_RIGHT;

with state_reg select
    left_motor_back_o <= '1' when TURN_LEFT,
                         '0' when FIND_WALL | FOLLOW_WALL | TURN_RIGHT;

with state_reg select
    right_motor_back_o <= '1' when TURN_RIGHT,
                          '0' when FIND_WALL | FOLLOW_WALL | TURN_LEFT;
p_motors_o : process(state_reg)
begin
    case state_reg is
        when FOLLOW_WALL | FIND_WALL =>
            left_motor_forward_o  <= '1';
            right_motor_forward_o <= '1';
            left_motor_back_o     <= '0';
            right_motor_back_o    <= '0';
        when TURN_LEFT =>
            left_motor_forward_o  <= '0';
            right_motor_forward_o <= '1';
            left_motor_back_o     <= '1';
            right_motor_back_o    <= '0';
        when TURN_RIGHT =>
            left_motor_forward_o  <= '1';
            right_motor_forward_o <= '0';
            left_motor_back_o     <= '0';
            right_motor_back_o    <= '1';
    end case;
end process p_motors_o;

Modèle de Mealy

Dans le modèle de Mealy, les sorties dépendent de l’état courant et des entrées de l’automate. Par exemple, dans les états où le robot doit avancer, on bloque les commandes left_motor_forward_o et right_motor_forward_o lorsqu’un obstacle est détecté à l’avant (front_sensor_i) ; dans les états où le robot doit tourner, on bloque les commandes des moteurs à la fin de la rotation (rotation_done).

with state_reg select
    left_motor_forward_o  <= not front_sensor_i when FIND_WALL | FOLLOW_WALL,
                             not rotation_done  when TURN_RIGHT,
                             '0'                when TURN_LEFT;
with state_reg select
    right_motor_forward_o <= not front_sensor_i when FIND_WALL | FOLLOW_WALL,
                             not rotation_done  when TURN_LEFT,
                             '0'                when TURN_RIGHT;

left_motor_back_o  <= not rotation_done when state_reg = TURN_LEFT  else '0';
right_motor_back_o <= not rotation_done when state_reg = TURN_RIGHT else '0';

Commande d’un compteur à partir d’un automate

Pour terminer notre architecture, nous devons à présent gérer le signal rotation_done indiquant la fin de rotation.

Pour simplifier le raisonnement, supposons que nous puissions déterminer précisément le temps que met le robot pour tourner d’un quart de tour. Ce temps sera fourni comme un nombre entier de périodes d’horloge par l’intermédiaire d’un paramètre générique ROTATION_TIME :

entity RobotController is
    generic(
        ROTATION_TIME : positive
    );
    port(
        clk_i, reset_i                                : in std_logic;
        front_sensor_i, left_sensor_i, right_sensor_i : in std_logic;
        left_motor_forward_o, left_motor_back_o       : out std_logic;
        right_motor_forward_o, right_motor_back_o     : out std_logic
    );
end RobotController;

Pour détecter la fin de rotation, il suffit de compter le temps passé dans les états TURN_LEFT et TURN_RIGHT et de comparer ce temps avec ROTATION_TIME.

Dans un circuit logique, la mesure du temps se fait au moyen d’un compteur que nous appellerons timer_reg. Ici, la nouveauté consiste à commander la mise à jour du compteur en utilisant les états de l’automate. On le met à zéro dans les états FIND_WALL et FOLLOW_WALL ; on l’incrémente dans les états TURN_LEFT et TURN_RIGHT :

architecture StateMachine of RobotController is
    type state_t is (FIND_WALL, FOLLOW_WALL, TURN_LEFT, TURN_RIGHT);
    signal state_reg, state_next : state_t;
    signal rotation_done         : std_logic;
    signal timer_reg, timer_next : natural range 0 to ROTATION_TIME - 1;
begin
    ...

    p_timer_next : process(state_reg, rotation_done, timer_reg)
    begin
        case state_reg is
            when FIND_WALL | FOLLOW_WALL =>
                timer_next <= 0;
            when TURN_LEFT | TURN_RIGHT =>
                if rotation_done = '1' then
                    timer_next <= 0;
                else
                    timer_next <= timer_reg + 1;
                end if;
        end case;
    end process p_timer_next;

    p_timer_reg : process(clk_i)
    begin
        if rising_edge(clk_i) then
            timer_reg <= timer_next;
        end if;
    end process p_timer_reg;

    rotation_done <= '1' when timer_reg = ROTATION_TIME - 1 else '0';
end StateMachine;

Architecture complète

Nous proposons ci-dessous une version compacte de l’architecture pour le modèle de Mealy, dans laquelle nous avons éliminé les signaux state_next et timer_next et regroupé leurs processus :

architecture StateMachine of RobotController is
    type state_t is (FIND_WALL, FOLLOW_WALL, TURN_LEFT, TURN_RIGHT);
    signal state_reg     : state_t;
    signal rotation_done : std_logic;
    signal timer_reg     : natural range 0 to ROTATION_TIME - 1;
begin
    p_state_reg : process(clk_i, reset_i)
    begin
        if reset_i = '1' then
            state_reg <= FIND_WALL;
        elsif rising_edge(clk_i) then
            case state_reg is
                when FIND_WALL =>
                    if front_sensor_i = '1' then
                        state_reg <= TURN_RIGHT;
                    elsif left_sensor_i = '1' then
                        state_reg <= TURN_LEFT;
                    end if;
                when FOLLOW_WALL =>
                    if left_sensor_i = '0' then
                        state_reg <= TURN_LEFT;
                    elsif front_sensor_i = '1' then
                        state_reg <= TURN_RIGHT;
                    end if;
                when TURN_LEFT | TURN_RIGHT =>
                    if rotation_done = '1' then
                        state_reg <= FIND_WALL;
                    end if;
            end case;
        end if;
    end process p_state_reg;

    with state_reg select
        left_motor_forward_o  <= not front_sensor_i when FIND_WALL | FOLLOW_WALL,
                                 not rotation_done  when TURN_RIGHT,
                                 '0'                when TURN_LEFT;
    with state_reg select
        right_motor_forward_o <= not front_sensor_i when FIND_WALL | FOLLOW_WALL,
                                 not rotation_done  when TURN_LEFT,
                                 '0'                when TURN_RIGHT;

    left_motor_back_o  <= not rotation_done when state_reg = TURN_LEFT  else '0';
    right_motor_back_o <= not rotation_done when state_reg = TURN_RIGHT else '0';

    p_timer_reg : process(clk_i)
    begin
        if rising_edge(clk_i) then
            case state_reg is
                when FIND_WALL | FOLLOW_WALL =>
                    timer_reg <= 0;
                when TURN_LEFT | TURN_RIGHT =>
                    if rotation_done = '1' then
                        timer_reg <= 0;
                    else
                        timer_reg <= timer_reg + 1;
                    end if;
            end case;
        end if;
    end process p_timer_reg;

    rotation_done <= '1' when timer_reg = ROTATION_TIME - 1 else '0';
end StateMachine;