Utilisation des fonctions impures en VHDL : Améliorer la lisibilité et la maintenabilité des FSM
En VHDL, une fonction impure peut accéder ou modifier n'importe quel signal dans sa portée lexicale, y compris les signaux qui ne sont pas listés comme paramètres. Cette capacité donne à la fonction des effets de bord - sa valeur de retour peut varier même quand les mêmes arguments sont fournis, et elle peut altérer des signaux externes qui ne sont pas directement retournés.
Alors que les fonctions pures sont confinées à un comportement déterministe, les fonctions impures sont inestimables pour nettoyer la logique complexe de machine d'état en déplaçant les tâches répétitives ou à effets de bord dans un sous-programme unique et bien nommé.
Note : Les fonctions impures sont mieux déclarées à l'intérieur d'un processus. Les déclarer au niveau de l'architecture ou du package n'offre aucun avantage, car la portée de la fonction serait limitée aux signaux qui sont déjà dans la portée au moment de la compilation.
Quand et pourquoi utiliser les fonctions impures
Scénarios typiques :
- Réinitialiser un compteur quand un minuteur expire.
- Vérifier si un compte à rebours est terminé sans dupliquer la logique de comparaison du compteur.
- Encapsuler la manipulation de signaux qui encombrerait autrement la FSM principale.
L'utilisation d'une fonction impure garde la logique principale de la machine d'état lisible tout en préservant le comportement à effets de bord requis par la conception.
Syntaxe
Pour déclarer une fonction impure, il suffit de préfixer le mot-clé impure avant function :
impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean;
Consultez le tutoriel sur les fonctions pour les détails complets de la syntaxe.
Exercice : Refactorisation FSM avec une fonction impure
Précédemment, nous calculions les délais temporels en utilisant une fonction pure CounterVal et réinitialisions manuellement le compteur chaque fois que le minuteur expirait. La fonction impure suivante consolide cette logique :
Testbench (T22_ImpureFunctionTb)
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity T22_ImpureFunctionTb is
end entity;
architecture sim of T22_ImpureFunctionTb is
-- Fréquence d'horloge basse pour accélérer la simulation
constant ClockFrequencyHz : integer := 100; -- 100 Hz
constant ClockPeriod : time := 1000 ms / ClockFrequencyHz;
signal Clk : std_logic := '1';
signal nRst : std_logic := '0';
signal NorthRed : std_logic;
signal NorthYellow : std_logic;
signal NorthGreen : std_logic;
signal WestRed : std_logic;
signal WestYellow : std_logic;
signal WestGreen : std_logic;
begin
-- Instanciation du DUT
i_TrafficLights : entity work.T22_TrafficLights(rtl)
generic map(ClockFrequencyHz => ClockFrequencyHz)
port map (
Clk => Clk,
nRst => nRst,
NorthRed => NorthRed,
NorthYellow => NorthYellow,
NorthGreen => NorthGreen,
WestRed => WestRed,
WestYellow => WestYellow,
WestGreen => WestGreen
);
-- Génération d'horloge
Clk <= not Clk after ClockPeriod / 2;
-- Séquence de testbench
process is
begin
wait until rising_edge(Clk);
wait until rising_edge(Clk);
-- Relâcher le reset
nRst <= '1';
wait;
end process;
end architecture;
Module Feux Tricolores (T22_TrafficLights)
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity T22_TrafficLights is
generic(ClockFrequencyHz : integer);
port(
Clk : in std_logic;
nRst : in std_logic; -- Reset négatif
NorthRed : out std_logic;
NorthYellow : out std_logic;
NorthGreen : out std_logic;
WestRed : out std_logic;
WestYellow : out std_logic;
WestGreen : out std_logic
);
end entity;
architecture rtl of T22_TrafficLights is
-- Fonction pure pour calculer les cycles d'horloge pour un temps donné
function CounterVal(Minutes : integer := 0; Seconds : integer := 0) return integer is
variable TotalSeconds : integer;
begin
TotalSeconds := Seconds + Minutes * 60;
return TotalSeconds * ClockFrequencyHz - 1;
end function;
-- Définition d'état FSM
type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest);
signal State : t_State;
-- Compteur pour périodes d'horloge, jusqu'à une minute
signal Counter : integer range 0 to ClockFrequencyHz * 60;
begin
process(Clk) is
-- Fonction impure qui lit et pilote Counter
impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean is
begin
if Counter = CounterVal(Minutes, Seconds) then
Counter <= 0;
return true;
else
return false;
end if;
end function;
begin
if rising_edge(Clk) then
if nRst = '0' then
-- État et signaux de reset
State <= NorthNext;
Counter <= 0;
NorthRed <= '1';
NorthYellow <= '0';
NorthGreen <= '0';
WestRed <= '1';
WestYellow <= '0';
WestGreen <= '0';
else
-- Sorties par défaut
NorthRed <= '0';
NorthYellow <= '0';
NorthGreen <= '0';
WestRed <= '0';
WestYellow <= '0';
WestGreen <= '0';
Counter <= Counter + 1;
case State is
when NorthNext =>
NorthRed <= '1';
WestRed <= '1';
if CounterExpired(Seconds => 5) then
State <= StartNorth;
end if;
when StartNorth =>
NorthRed <= '1';
NorthYellow <= '1';
WestRed <= '1';
if CounterExpired(Seconds => 5) then
State <= North;
end if;
when North =>
NorthGreen <= '1';
WestRed <= '1';
if CounterExpired(Minutes => 1) then
State <= StopNorth;
end if;
when StopNorth =>
NorthYellow <= '1';
WestRed <= '1';
if CounterExpired(Seconds => 5) then
State <= WestNext;
end if;
when WestNext =>
NorthRed <= '1';
WestRed <= '1';
if CounterExpired(Seconds => 5) then
State <= StartWest;
end if;
when StartWest =>
NorthRed <= '1';
WestRed <= '1';
WestYellow <= '1';
if CounterExpired(Seconds => 5) then
State <= West;
end if;
when West =>
NorthRed <= '1';
WestGreen <= '1';
if CounterExpired(Minutes => 1) then
State <= StopWest;
end if;
when StopWest =>
NorthRed <= '1';
WestYellow <= '1';
if CounterExpired(Seconds => 5) then
State <= NorthNext;
end if;
end case;
end if;
end if;
end process;
end architecture;
Analyse de la forme d'onde
Après avoir émis la commande run 5 min dans ModelSim, la simulation montre un comportement identique à la FSM originale - seul le code est devenu plus propre. La logique de réinitialisation du compteur est maintenant encapsulée dans CounterExpired, réduisant la duplication et les erreurs potentielles.
Analyse
La fonction impure transfère la comparaison et la réinitialisation du compteur depuis plusieurs états FSM dans un seul sous-programme. Ce changement améliore la lisibilité : CounterExpired(Seconds => 5) exprime clairement l'intention, alors que Counter = CounterVal(Seconds => 5) est plus verbeux.
Bien que les fonctions impures puissent obscurcir les effets de bord si elles sont trop utilisées, les garder locales à un processus assure que le code environnant reste déterministe et facile à auditer. En pratique, elles sont plus fréquemment employées dans les testbench où la correction est importante mais le risque de bogues subtils est plus faible que dans le RTL synthétisable.
Points clés à retenir
- Les fonctions impures peuvent lire ou piloter des signaux non listés comme paramètres.
- Les déclarer à l'intérieur d'un processus pour tirer parti de la portée locale des signaux.
- Les utiliser pour encapsuler la logique répétitive à effets de bord, améliorant la clarté du code et réduisant la duplication.
Passez au tutoriel suivant pour explorer des techniques VHDL avancées.
VHDL
- Comment le molybdène façonne l'industrie moderne : usages, avantages et dynamiques d'approvisionnement
- Créer des listes de chaînes en VHDL : Bonnes pratiques et exemple
- Terminer proprement une simulation de banc de test VHDL
- Implémenter un contrôleur PWM en VHDL : Conception, simulation et démo FPGA
- Générer des nombres aléatoires en VHDL : De l'uniforme à OSVVM
- Exploiter les procédures in-process pour un design FSM VHDL plus propre
- Utilisation de realloc() en C : Syntaxe, bonnes pratiques et exemple
- Maîtriser la fonction free() en C : Guide pratique et exemple
- Maîtriser les techniques de la rectifieuse d'outils : guide pratique