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 :
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 :
Et d’autre part, elle doit décrire un circuit respectant cette structure :
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;
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;
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 :
case
qui permet d’identifier l’état courant ;if
pour tester les conditions associées aux transitions sortant de chaque état ;state_next
.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;
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.
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;
case
: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;
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';
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;
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;