Chapitre 7 — Processus et instructions séquentielles
Objectif — Finalité du chapitre Maîtriser le processus VHDL et ses instructions séquentielles afin de décrire des circuits combinatoires structurés, des choix complexes et des traitements répétitifs, tout en contrôlant les listes de sensibilité et l’usage des variables. |
Objectifs pédagogiques
À la fin de ce chapitre, l’étudiant devra être capable de :
- identifier la partie déclarative et la partie exécutive d’un processus ;
- construire une liste de sensibilité correcte ;
- utiliser les instructions if, elsif, else et case ;
- choisir entre if et case selon le besoin fonctionnel ;
- utiliser les boucles for, while et loop dans un contexte synthétisable ;
- arrêter une boucle avec exit ou exit when ;
- déclarer et utiliser des variables locales ;
- expliquer la différence de mise à jour entre une variable et un signal ;
- distinguer un processus combinatoire d’un processus séquentiel cadencé ;
- utiliser process(all) en VHDL-2008 ;
- détecter les listes de sensibilité incomplètes ;
- concevoir et simuler des circuits combinatoires à l’aide de processus.
Prérequis
- entités, architectures et signaux internes ;
- affectations concurrentes ;
- types std_logic, std_logic_vector, signed et unsigned ;
- opérateurs logiques, relationnels et arithmétiques ;
- principes de simulation et bancs de test.
Organisation du chapitre
Section | Contenu | Compétence principale |
|---|---|---|
| 7.1 | Structure d’un processus | Organiser un bloc séquentiel |
| 7.2 | Instructions if/elsif/else | Exprimer des décisions prioritaires |
| 7.3 | Instruction case | Sélectionner entre des choix exclusifs |
| 7.4 | Boucles | Répéter un traitement de manière statique ou contrôlée |
| 7.5 | Variables | Effectuer des calculs locaux immédiats |
| 7.6 | Sensibilité | Garantir une simulation combinatoire correcte |
| TP 4 | Circuits combinatoires avec processus | Concevoir, tester et corriger plusieurs blocs |
Concept clé — Deux niveaux de concurrence Un processus entier est concurrent par rapport aux autres instructions de l’architecture. En revanche, les instructions situées à l’intérieur du processus sont exécutées séquentiellement, dans l’ordre où elles sont écrites. |
7.1 Définition d’un processus
Un processus est une instruction concurrente qui regroupe une suite d’instructions séquentielles. Il permet d’exprimer des décisions, des calculs intermédiaires, des boucles et des comportements cadencés qui seraient difficiles à écrire avec de simples affectations concurrentes.
7.1.1 Structure générale d’un processus
| Forme générale | VHDL |
| etiquette : process(liste_de_sensibilite) -- Partie déclarative variable temp : std_logic; begin -- Instructions séquentielles temp := a and b; y <= temp; end process etiquette; |
L’étiquette est facultative, mais elle améliore la lisibilité et les messages du simulateur.
7.1.2 Éléments constitutifs
Élément | Rôle | Exemple |
|---|---|---|
| Étiquette | Nomme le processus. | logique_sortie : process(...) |
| Mot-clé process | Débute la déclaration du processus. | process(a, b, sel) |
| Liste de sensibilité | Indique les signaux dont un événement réveille le processus. | (a, b, sel) |
| Partie déclarative | Contient variables, constantes, types et sous-programmes locaux. | variable temp : std_logic; |
| Mot-clé begin | Sépare déclarations et instructions. | begin |
| Instructions séquentielles | Décrivent les opérations dans l’ordre. | if, case, boucles, affectations |
| Fin du processus | Termine le bloc. | end process; |
7.1.3 Liste de sensibilité
La liste de sensibilité est une liste de signaux. Le processus est exécuté une première fois au début de la simulation, puis à chaque événement sur l’un de ces signaux. Pour un processus combinatoire, elle doit contenir tous les signaux lus.
| Processus combinatoire | VHDL |
| process(a, b, sel) begin if sel = '0' then y <= a; else y <= b; end if; end process; |
Règle — Un signal lu doit être déclaré Dans cet exemple, a, b et sel sont lus. Tous doivent donc apparaître dans la liste de sensibilité afin que y soit recalculé après chaque changement pertinent. |
7.1.4 Partie déclarative
La partie déclarative est située entre process(...) et begin. Elle peut contenir des objets et déclarations visibles uniquement à l’intérieur du processus.
- variables locales ;
- constantes locales ;
- types et sous-types locaux ;
- alias ;
- fonctions et procédures locales ;
- fichiers pour un processus de simulation.
| Partie déclarative détaillée | VHDL |
| process(a, b, commande) variable resultat_v : std_logic_vector(7 downto 0); constant ZERO : std_logic_vector(7 downto 0) := (others => '0'); begin resultat_v := ZERO; if commande = '1' then resultat_v := a and b; end if; y <= resultat_v; end process; |
7.1.5 Instructions séquentielles
Les instructions du corps sont exécutées dans l’ordre. Une variable affectée par := change immédiatement, alors qu’une affectation de signal avec <= planifie une mise à jour.
| Ordre séquentiel | VHDL |
| process(a, b, c) variable temp : std_logic; begin temp := a and b; temp := temp or c; y <= temp; end process; |
La deuxième instruction utilise immédiatement la valeur de temp calculée par la première.
7.1.6 Processus équivalent à une affectation concurrente
| Affectation concurrente | VHDL |
| y <= a and b; |
| Processus équivalent | VHDL |
| process(a, b) begin y <= a and b; end process; |
Le matériel inféré est généralement identique. Le processus devient avantageux lorsque plusieurs branches ou calculs intermédiaires sont nécessaires.
7.1.7 Processus avec instruction wait
Un processus sans liste de sensibilité peut utiliser une ou plusieurs instructions wait. Cette forme est courante dans les bancs de test.
| Génération de stimuli | VHDL |
| stimuli : process begin a <= '0'; b <= '0'; wait for 10 ns; a <= '1'; wait for 10 ns; wait; end process; |
Attention — Exclusion mutuelle Un même processus ne doit pas combiner une liste de sensibilité et une instruction wait. On choisit l’une ou l’autre méthode de suspension. |
7.1.8 Processus combinatoire et processus cadencé
Critère | Processus combinatoire | Processus cadencé |
|---|---|---|
| Déclenchement | Tout changement d’un signal lu | Front d’horloge, éventuellement reset asynchrone |
| Mémoire souhaitée | Non | Oui, registres ou bascules |
| Liste de sensibilité | Toutes les entrées lues ou all | Horloge et reset asynchrone |
| Affectation complète | Toutes les sorties dans tous les chemins | Les registres peuvent conserver leur valeur |
| Exemple | Multiplexeur, décodeur, comparateur | Compteur, registre, machine à états |
7.1.9 Processus et pilote de signal
Toutes les affectations d’un signal réalisées dans un même processus appartiennent à un seul pilote.
| Deux affectations séquentielles, un seul pilote | VHDL |
| process(a, b, sel) begin y <= a; if sel = '1' then y <= b; end if; end process; |
Si sel vaut 1, la dernière affectation exécutée détermine la transaction finale de y. Si sel vaut 0, la valeur par défaut a est conservée.
7.1.10 Nommage et organisation
| Processus nommé | VHDL |
| combinaison_sortie : process(all) variable valeur_v : std_logic_vector(7 downto 0); begin -- Calculs end process combinaison_sortie; |
- Choisir un nom décrivant le rôle du bloc : calcul_sortie, registre_etat, decode_commande.
- Éviter des processus trop longs regroupant des fonctions indépendantes.
- Séparer clairement logique combinatoire et logique cadencée.
- Définir des valeurs par défaut au début des processus combinatoires.
7.2 Instructions conditionnelles
Les instructions if, elsif et else permettent d’exécuter différentes séquences selon des conditions booléennes. L’ordre des branches introduit une priorité lorsque plusieurs conditions peuvent être vraies.
7.2.1 Instruction if simple
| If sans else | VHDL |
| if enable = '1' then y <= a; end if; |
Dans un processus combinatoire, cette forme est incomplète si y n’a pas reçu de valeur par défaut. Elle peut entraîner la conservation de la valeur précédente et l’inférence d’un latch.
| Correction avec valeur par défaut | VHDL |
| y <= '0'; if enable = '1' then y <= a; end if; |
Bonne pratique — Valeur par défaut Affecter les sorties au début d’un processus combinatoire simplifie la couverture de tous les chemins et évite les latches involontaires. |
7.2.2 Structure if / else
| Multiplexeur 2 vers 1 | VHDL |
| if sel = '0' then y <= a; else y <= b; end if; |
Les deux branches affectent y : la description est combinatoire et complète.
7.2.3 Structure if / elsif / else
| Sélection prioritaire | VHDL |
| if entree(3) = '1' then code <= "11"; elsif entree(2) = '1' then code <= "10"; elsif entree(1) = '1' then code <= "01"; else code <= "00"; end if; |
La première condition vraie est sélectionnée. Les branches suivantes ne sont pas évaluées pour ce passage.
7.2.4 Conditions booléennes
| Comparaisons numériques | VHDL |
| if unsigned(a) > unsigned(b) then maximum <= a; else maximum <= b; end if; |
| Combinaison de conditions | VHDL |
| if enable = '1' and erreur = '0' and compteur < LIMITE then autorisation <= '1'; else autorisation <= '0'; end if; |
7.2.5 Conditions imbriquées
| If imbriqué | VHDL |
| if enable = '1' then if mode = "00" then y <= a; elsif mode = "01" then y <= b; else y <= c; end if; else y <= (others => '0'); end if; |
L’imbrication est utile lorsque certaines décisions ne sont pertinentes que dans un contexte donné. Une profondeur excessive nuit toutefois à la lisibilité et peut créer une longue chaîne prioritaire.
7.2.6 Priorité matérielle
Structure | Priorité | Matériel probable |
|---|---|---|
| if / else | Une condition | Multiplexeur 2 vers 1 |
| if / elsif / else | Priorité de haut en bas | Chaîne de multiplexeurs prioritaires |
| if imbriqués | Priorité hiérarchique | Réseau de comparateurs et multiplexeurs |
| Conditions exclusives | Toujours écrites prioritairement | Optimisation possible par le synthétiseur |
7.2.7 Affectation complète et latch
| Description incomplète | VHDL |
| process(a, enable) begin if enable = '1' then y <= a; end if; end process; |
Quand enable vaut 0, aucune nouvelle valeur n’est affectée à y. Pour reproduire ce comportement, le matériel doit mémoriser l’ancienne valeur : un latch est inféré.
| Description combinatoire corrigée | VHDL |
| process(a, enable) begin y <= '0'; if enable = '1' then y <= a; end if; end process; |
7.2.8 If dans un processus cadencé
| Registre avec validation | VHDL |
| process(clk) begin if rising_edge(clk) then if enable = '1' then q <= d; end if; end if; end process; |
Ici, l’absence de else est volontaire : lorsque enable vaut 0, le registre conserve sa valeur. Il ne s’agit pas d’un latch, mais d’une bascule avec signal d’activation.
Distinction — Le contexte compte Une affectation incomplète est généralement une erreur dans un processus combinatoire, mais elle peut exprimer une conservation légitime dans un processus cadencé. |
7.2.9 Reset asynchrone et synchrone
| Reset asynchrone | VHDL |
| process(clk, reset) begin if reset = '1' then q <= (others => '0'); elsif rising_edge(clk) then q <= d; end if; end process; |
| Reset synchrone | VHDL |
| process(clk) begin if rising_edge(clk) then if reset = '1' then q <= (others => '0'); else q <= d; end if; end if; end process; |
Le reset asynchrone apparaît dans la liste de sensibilité ; le reset synchrone est testé uniquement au front d’horloge.
7.2.10 Lisibilité des conditions
| Condition complexe nommée | VHDL |
| condition_valide <= enable and not erreur and donnees_disponibles; process(all) begin if condition_valide = '1' then y <= resultat; else y <= (others => '0'); end if; end process; |
- Nommer les conditions fonctionnelles complexes.
- Aligner les opérateurs logiques.
- Limiter l’imbrication lorsque des signaux intermédiaires suffisent.
- Conserver une branche else explicite dans les processus combinatoires.
7.3 Instruction case
L’instruction case sélectionne une séquence selon la valeur d’une expression discrète. Les choix sont mutuellement exclusifs et aucune priorité n’est associée à leur ordre.
7.3.1 Syntaxe générale
| Forme générale | VHDL |
| case expression is when choix_1 => instructions_1; when choix_2 => instructions_2; when others => instructions_par_defaut; end case; |
7.3.2 Multiplexeur avec case
| Multiplexeur 4 vers 1 | VHDL |
| case sel is when "00" => y <= a; when "01" => y <= b; when "10" => y <= c; when others => y <= d; end case; |
Tous les choix affectent y. Le processus est combinatoire si la liste de sensibilité est complète.
7.3.3 Sélection entre plusieurs choix
| Unité logique | VHDL |
| case operation is when "000" => y <= a and b; when "001" => y <= a or b; when "010" => y <= a xor b; when "011" => y <= not a; when others => y <= (others => '0'); end case; |
7.3.4 Cas multiples
| Regroupement de choix | VHDL |
| case commande is when "001" | "010" | "100" => actif <= '1'; when others => actif <= '0'; end case; |
Le symbole | associe plusieurs valeurs au même ensemble d’instructions.
7.3.5 Utilisation de others
La branche others couvre toutes les valeurs non écrites. Elle est souvent indispensable avec std_logic_vector, car les bits peuvent prendre des valeurs comme U, X ou Z.
| Valeur de sécurité | VHDL |
| case sel is when "00" => y <= a; when "01" => y <= b; when "10" => y <= c; when "11" => y <= d; when others => y <= (others => 'X'); end case; |
Choix de conception — Simulation défensive Une valeur X dans others facilite la détection d’un sélecteur non initialisé. Dans une conception de production, une valeur sûre peut être préférable selon l’analyse de risque. |
7.3.6 Couverture complète
Les choix d’un case doivent être complets et non recouvrants.
Propriété | Exigence |
|---|---|
| Complétude | Toute valeur possible de l’expression doit être couverte. |
| Exclusivité | Une valeur ne doit apparaître que dans une seule alternative. |
| Type | Les choix doivent être compatibles avec le type de l’expression. |
| Affectations | Pour un processus combinatoire, toutes les sorties doivent être définies dans tous les cas. |
7.3.7 Case sur un type énuméré
| Machine à états combinatoire | VHDL |
| type etat_t is (ATTENTE, CHARGE, CALCUL, FIN); signal etat : etat_t; process(all) begin pret <= '0'; case etat is when ATTENTE => pret <= '1'; when CHARGE => pret <= '0'; when CALCUL => pret <= '0'; when FIN => pret <= '1'; end case; end process; |
Un type énuméré rend les choix plus lisibles et permet au compilateur de vérifier la couverture.
7.3.8 Différence entre if et case
Critère | if / elsif | case |
|---|---|---|
| Nature des conditions | Conditions quelconques et différentes | Valeurs d’une même expression |
| Priorité | Oui, de haut en bas | Non |
| Choix multiples | Par conditions combinées | Avec | |
| Complétude | Branche else ou valeurs par défaut | Tous les choix ou others |
| Usage typique | Comparaisons, seuils, priorité | Opcode, état, sélecteur |
| Lisibilité | Meilleure pour décisions hétérogènes | Meilleure pour table de sélection |
7.3.9 Même multiplexeur avec if et case
| Version if | VHDL |
| if sel = "00" then y <= a; elsif sel = "01" then y <= b; elsif sel = "10" then y <= c; else y <= d; end if; |
| Version case | VHDL |
| case sel is when "00" => y <= a; when "01" => y <= b; when "10" => y <= c; when others => y <= d; end case; |
Les deux descriptions peuvent produire le même matériel. La version case exprime plus directement une sélection par code.
7.3.10 Case et valeurs par défaut
| Valeurs par défaut avant case | VHDL |
| process(all) begin y <= (others => '0'); valide <= '0'; case operation is when "00" => y <= a; valide <= '1'; when "01" => y <= b; valide <= '1'; when others => null; end case; end process; |
L’instruction null indique qu’aucune autre action n’est réalisée ; les valeurs par défaut restent actives.
7.4 Boucles
Les boucles répètent des instructions séquentielles. En synthèse, elles ne représentent pas nécessairement une exécution dans le temps : lorsque les limites sont statiques, le synthétiseur déroule souvent la boucle et crée plusieurs ressources matérielles.
7.4.1 Boucle for
| Forme générale | VHDL |
| for i in plage loop instructions; end loop; |
| Calcul de parité | VHDL |
| process(donnees) variable parite_v : std_logic; begin parite_v := '0'; for i in donnees'range loop parite_v := parite_v xor donnees(i); end loop; parite <= parite_v; end process; |
La boucle parcourt tous les bits du vecteur, quelle que soit son orientation.
7.4.2 Indice de boucle
L’indice de boucle est déclaré implicitement et n’a pas besoin d’être déclaré dans la partie déclarative. Sa portée est limitée au corps de la boucle.
| Initialisation d’un tableau | VHDL |
| for i in registre'range loop registre(i) <= '0'; end loop; |
7.4.3 Déroulement matériel
Une boucle for dont la plage est connue lors de l’élaboration est généralement déroulée. Par exemple, une boucle de huit itérations calculant huit bits décrit huit opérations matérielles, potentiellement parallèles.
Boucle | Interprétation synthétique probable |
|---|---|
| for i in 0 to 7 | Huit copies ou huit étapes de logique combinatoire |
| for i in vecteur'range | Une copie par élément du vecteur |
| Boucle dépendant d’un générique N | N copies après élaboration |
| Boucle avec limites variables à l’exécution | Souvent non synthétisable ou coûteuse |
7.4.4 Boucle while
| Forme générale | VHDL |
| while condition loop instructions; end loop; |
| Recherche simulée | VHDL |
| variable i : integer := 0; while i < 8 loop -- Traitement i := i + 1; end loop; |
La boucle while est naturelle en algorithmique, mais sa synthèse est plus restrictive. L’outil doit pouvoir déterminer une borne maximale statique et garantir la terminaison.
Attention — Synthèse prudente Pour les descriptions RTL portables, préférer une boucle for à bornes statiques. Un traitement itératif dépendant des données doit souvent être réalisé avec un compteur et une machine à états sur plusieurs cycles. |
7.4.5 Instruction loop
| Boucle générale | VHDL |
| loop instructions; exit when condition; end loop; |
Une boucle loop n’a pas de condition intégrée. Elle doit être interrompue par exit, return, wait ou une autre structure adaptée.
7.4.6 Instruction exit
| Sortie conditionnelle | VHDL |
| for i in donnees'range loop if donnees(i) = '1' then position_v := i; trouve_v := true; exit; end if; end loop; |
| Forme exit when | VHDL |
| loop compteur := compteur + 1; exit when compteur = 10; end loop; |
7.4.7 Boucles imbriquées
| Traitement d’une matrice | VHDL |
| for ligne in 0 to 3 loop for colonne in 0 to 3 loop sortie(ligne, colonne) <= entree(ligne, colonne); end loop; end loop; |
Le nombre total d’itérations est le produit des tailles. Une boucle imbriquée peut donc entraîner une forte croissance des ressources.
7.4.8 Boucle et addition cumulative
| Somme des bits d’un vecteur | VHDL |
| process(donnees) variable somme_v : natural range 0 to donnees'length; begin somme_v := 0; for i in donnees'range loop if donnees(i) = '1' then somme_v := somme_v + 1; end if; end loop; nombre_uns <= somme_v; end process; |
Le synthétiseur peut construire un arbre ou une chaîne d’additionneurs selon ses optimisations.
7.4.9 Boucle generate et boucle séquentielle
Boucle dans un processus | Instruction generate |
|---|---|
| Séquentielle dans le code du processus | Concurrente au niveau de l’architecture |
| Utilise for, while ou loop | Utilise for generate ou if generate |
| Manipule variables et signaux | Crée des instances ou structures répétées |
| Décrit un calcul regroupé | Décrit explicitement plusieurs blocs matériels |
Distinction — Ne pas confondre La boucle for d’un processus est une instruction séquentielle. La construction for ... generate, étudiée plus tard, est une instruction concurrente de génération matérielle. |
7.4.10 Règles de synthèse
- Utiliser des limites statiques ou déterminées par des génériques.
- Garantir une terminaison finie et démontrable.
- Éviter les boucles while dépendant de données non bornées.
- Évaluer le nombre de ressources créées après déroulement.
- Pour un calcul séquentiel sur plusieurs cycles, utiliser une machine à états et un compteur.
- Vérifier le rapport de synthèse et le chemin critique.
7.5 Variables dans un processus
Une variable est un objet local mis à jour immédiatement. Elle est particulièrement utile pour accumuler un résultat, mémoriser un calcul intermédiaire ou construire une décision avant l’affectation finale d’un signal.
7.5.1 Déclaration
| Variable locale | VHDL |
| process(a, b, c) variable temp : std_logic; begin temp := a and b; temp := temp or c; y <= temp; end process; |
La variable temp existe à chaque activation du processus et conserve sa valeur entre activations si elle n’est pas réaffectée, ce qui peut avoir des conséquences en simulation et en synthèse.
7.5.2 Affectation avec :=
| Affectation immédiate | VHDL |
| temp := nouvelle_valeur; |
L’opérateur := est utilisé pour les variables et certaines initialisations. Il ne doit pas être confondu avec <=, réservé à l’affectation de signal.
7.5.3 Mise à jour immédiate
| Deux incréments de variable | VHDL |
| process(clk) variable compteur_v : integer := 0; begin if rising_edge(clk) then compteur_v := compteur_v + 1; compteur_v := compteur_v + 1; sortie <= compteur_v; end if; end process; |
À chaque front, compteur_v augmente de deux avant d’être copié vers sortie.
7.5.4 Signal affecté plusieurs fois
| Deux affectations de signal | VHDL |
| process(clk) begin if rising_edge(clk) then compteur_s <= compteur_s + 1; compteur_s <= compteur_s + 1; sortie <= compteur_s; end if; end process; |
Les deux expressions utilisent l’ancienne valeur de compteur_s. La dernière transaction remplace la précédente pour le même instant. compteur_s n’augmente donc pas de deux.
7.5.5 Comparaison signal/variable
Critère | Variable | Signal |
|---|---|---|
| Opérateur | := | <= |
| Mise à jour | Immédiate | Planifiée |
| Portée typique | Processus ou sous-programme | Architecture, entité, paquetage |
| Communication entre processus | Non directement | Oui |
| Déclenchement d’un processus | Non | Oui, par événement |
| Usage | Calcul intermédiaire | Connexion, sortie, registre |
7.5.6 Variable avec valeur par défaut
| Calcul combinatoire sûr | VHDL |
| process(all) variable resultat_v : std_logic_vector(7 downto 0); begin resultat_v := (others => '0'); if enable = '1' then resultat_v := a xor b; end if; y <= resultat_v; end process; |
La variable est définie dans tous les chemins avant d’être utilisée.
7.5.7 Variable non initialisée dans un chemin
| Description dangereuse | VHDL |
| process(a, enable) variable temp : std_logic; begin if enable = '1' then temp := a; end if; y <= temp; end process; |
Lorsque enable vaut 0, temp conserve sa valeur précédente. Cette description peut inférer une mémoire. Il faut initialiser temp au début du processus si un circuit combinatoire est souhaité.
7.5.8 Accumulateur dans une boucle
| Calcul de parité avec variable | VHDL |
| process(all) variable p : std_logic; begin p := '0'; for i in donnees'range loop p := p xor donnees(i); end loop; parite <= p; end process; |
La mise à jour immédiate permet à chaque itération d’utiliser le résultat de l’itération précédente.
7.5.9 Variable et signal dans un processus cadencé
| Calcul de prochaine valeur | VHDL |
| process(clk) variable prochaine_v : unsigned(7 downto 0); begin if rising_edge(clk) then prochaine_v := compteur + 1; if prochaine_v = 100 then compteur <= (others => '0'); else compteur <= prochaine_v; end if; end if; end process; |
La variable calcule immédiatement la prochaine valeur, tandis que compteur reste un registre mis à jour après le front.
7.5.10 Variables partagées
Les variables partagées existent en VHDL, mais leur utilisation demande des mécanismes protégés et une grande prudence pour éviter les accès concurrents indéterminés. Elles sont rarement nécessaires dans un cours RTL d’introduction.
Prudence — Recommandation Pour communiquer entre processus synthétisables, utiliser des signaux. Réserver les variables aux calculs locaux et les variables partagées aux modèles avancés maîtrisés. |
7.6 Sensibilité d’un processus
La sensibilité détermine quand un processus est réexécuté en simulation. Une liste incorrecte peut produire une simulation différente du circuit synthétisé, car le synthétiseur analyse généralement les dépendances logiques indépendamment de cette liste.
7.6.1 Processus combinatoire
| Liste complète | VHDL |
| process(a, b, sel) begin if sel = '0' then y <= a; else y <= b; end if; end process; |
Les trois signaux a, b et sel sont lus et figurent dans la liste.
7.6.2 Liste incomplète
| Erreur classique | VHDL |
| process(a, sel) begin if sel = '0' then y <= a; else y <= b; end if; end process; |
Le signal b est lu mais absent. Si b change alors que a et sel restent constants, le processus ne se réexécute pas : y reste incorrect en simulation.
Erreur critique — Simulation/synthèse Le matériel synthétisé peut tout de même dépendre de b. Une liste incomplète peut donc créer un écart dangereux entre la simulation RTL et le fonctionnement réel. |
7.6.3 Liste complète des signaux lus
- Inclure tous les ports et signaux lus directement.
- Inclure les signaux lus dans les conditions if ou case.
- Inclure les signaux lus dans les expressions de calcul.
- Inclure les signaux lus dans les appels de fonction, lorsque leur valeur est passée comme argument.
- Ne pas ajouter les variables locales.
- Ne pas ajouter les signaux uniquement écrits.
7.6.4 Utilisation de process(all) en VHDL-2008
| Processus combinatoire automatique | VHDL |
| process(all) begin if sel = '0' then y <= a; else y <= b; end if; end process; |
Le mot all demande à l’outil d’inclure automatiquement tous les signaux lus par le processus. Cette forme réduit les erreurs de maintenance lorsque l’expression change.
Compatibilité — Version du langage process(all) nécessite VHDL-2008. Le projet et le simulateur doivent être configurés avec la norme correspondante, par exemple --std=08 avec GHDL. |
7.6.5 Processus séquentiel cadencé
| Processus sur front d’horloge | VHDL |
| process(clk) begin if rising_edge(clk) then q <= d; end if; end process; |
La donnée d n’apparaît pas dans la liste. Le registre ne doit être mis à jour qu’au front d’horloge, même si d change entre deux fronts.
7.6.6 Processus avec reset asynchrone
| Horloge et reset | VHDL |
| process(clk, reset) begin if reset = '1' then q <= (others => '0'); elsif rising_edge(clk) then q <= d; end if; end process; |
Le reset doit figurer dans la liste, car il agit immédiatement sans attendre un front d’horloge.
7.6.7 Processus avec reset synchrone
| Horloge seule | VHDL |
| process(clk) begin if rising_edge(clk) then if reset = '1' then q <= (others => '0'); else q <= d; end if; end if; end process; |
Le reset synchrone est lu uniquement lorsque le processus est réveillé par clk ; il n’apparaît donc pas dans la liste.
7.6.8 Tableau récapitulatif
Type de processus | Liste de sensibilité recommandée |
|---|---|
| Combinatoire VHDL antérieur à 2008 | Tous les signaux lus |
| Combinatoire VHDL-2008 | all |
| Cadencé sans reset asynchrone | clk |
| Cadencé avec reset asynchrone | clk, reset |
| Banc de test avec wait | Aucune liste |
7.6.9 Détection d’une liste incomplète
- Identifier tous les signaux lus dans le processus.
- Comparer cette liste à la liste de sensibilité.
- Simuler un changement isolé de chaque entrée.
- Vérifier que la sortie est mise à jour.
- Utiliser les avertissements du compilateur ou du linter.
- Préférer process(all) pour les nouveaux projets VHDL-2008.
7.6.10 Exemple de correction
| Version incorrecte | VHDL |
| process(a, sel) begin y <= a when sel = '0' else b; end process; |
| Version corrigée classique | VHDL |
| process(a, b, sel) begin y <= a when sel = '0' else b; end process; |
| Version corrigée VHDL-2008 | VHDL |
| process(all) begin y <= a when sel = '0' else b; end process; |
7.6.11 Sensibilité et fonctions
Lorsque le processus appelle une fonction pure, les signaux passés en arguments doivent être pris en compte. Avec process(all), l’outil déduit ces dépendances.
| Fonction et process(all) | VHDL |
| process(all) begin y <= fonction_logique(a, b, commande); end process; |
7.6.12 Bonnes pratiques de processus combinatoire
- Utiliser process(all) lorsque VHDL-2008 est disponible.
- Affecter des valeurs par défaut à toutes les sorties et variables.
- Couvrir toutes les branches de if et case.
- Éviter les latches involontaires.
- Limiter un processus à une fonction cohérente.
- Tester chaque entrée indépendamment dans le banc de test.
Méthode de conception d’un processus combinatoire
1. Établir la table de vérité ou l’algorithme combinatoire.
2. Identifier toutes les entrées lues et toutes les sorties pilotées.
3. Choisir process(all) ou construire une liste complète.
4. Déclarer les variables nécessaires.
5. Attribuer des valeurs par défaut aux sorties et variables.
6. Écrire les décisions avec if ou case.
7. Vérifier que chaque chemin affecte les sorties.
8. Simuler toutes les combinaisons pertinentes.
9. Vérifier le schéma RTL et l’absence de latch.
Choisir entre if et case
Question | Réponse | Choix conseillé |
|---|---|---|
| Les décisions portent-elles sur une seule expression ? | Oui | case |
| Les conditions sont-elles des comparaisons différentes ? | Oui | if / elsif |
| Une priorité est-elle nécessaire ? | Oui | if / elsif |
| Les choix sont-ils exclusifs et codés ? | Oui | case |
| Le traitement comporte-t-il des seuils ? | Oui | if / elsif |
| Le traitement correspond-il à un opcode ? | Oui | case |
Erreurs fréquentes
Erreur | Conséquence | Correction |
|---|---|---|
| Signal lu absent de la sensibilité | Simulation obsolète | Ajouter le signal ou utiliser all |
| Sortie non affectée dans un chemin | Latch involontaire | Valeur par défaut ou branche complète |
| Variable non initialisée | Mémoire ou valeur indéterminée | Initialiser à chaque activation |
| Confusion := et <= | Erreur ou comportement différent | Variable :=, signal <= |
| Case incomplet | Erreur d’analyse ou comportement incomplet | Ajouter tous les choix ou others |
| While non borné | Non-synthétisable | For statique ou machine à états |
| If prioritaire non voulu | Chemin long ou fonction incorrecte | Case ou conditions exclusives |
Travaux pratiques
TP 4 — Circuits combinatoires avec processus
1. Objectifs du TP
- réaliser un multiplexeur avec if ;
- réaliser un multiplexeur avec case ;
- concevoir un comparateur numérique ;
- concevoir un encodeur de priorité ;
- écrire des bancs de test auto-vérifiants ;
- reproduire une erreur de liste de sensibilité ;
- corriger la description avec une liste complète et process(all).
2. Organisation proposée
| Arborescence du projet | Arborescence |
| tp4_processus_combinatoires/ ├── src/ │ ├── mux2_if.vhd │ ├── mux4_case.vhd │ ├── comparateur8.vhd │ ├── encodeur_priorite.vhd │ ├── mux_sensibilite_incorrect.vhd │ └── mux_sensibilite_corrige.vhd └── sim/ ├── tb_mux2_if.vhd ├── tb_mux4_case.vhd ├── tb_comparateur8.vhd ├── tb_encodeur_priorite.vhd └── tb_sensibilite.vhd |
3. Partie A — Multiplexeur utilisant if
| mux2_if.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity mux2_if is port ( a, b : in std_logic_vector(7 downto 0); sel : in std_logic; y : out std_logic_vector(7 downto 0) ); end entity mux2_if; architecture rtl of mux2_if is begin process(all) begin if sel = '0' then y <= a; else y <= b; end if; end process; end architecture rtl; |
4. Banc de test du multiplexeur if
| tb_mux2_if.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use STD.ENV.ALL; entity tb_mux2_if is end entity tb_mux2_if; architecture simulation of tb_mux2_if is signal a : std_logic_vector(7 downto 0) := x"12"; signal b : std_logic_vector(7 downto 0) := x"A5"; signal sel : std_logic := '0'; signal y : std_logic_vector(7 downto 0); begin DUT : entity work.mux2_if(rtl) port map (a => a, b => b, sel => sel, y => y); |
| tb_mux2_if.vhd — Stimuli | VHDL |
| stimuli : process begin sel <= '0'; wait for 10 ns; assert y = x"12" report "Erreur mux2_if pour sel=0" severity error; sel <= '1'; wait for 10 ns; assert y = x"A5" report "Erreur mux2_if pour sel=1" severity error; report "Test mux2_if termine" severity note; stop; wait; end process; end architecture simulation; |
5. Partie B — Multiplexeur utilisant case
| mux4_case.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity mux4_case is port ( a, b, c, d : in std_logic_vector(7 downto 0); sel : in std_logic_vector(1 downto 0); y : out std_logic_vector(7 downto 0) ); end entity mux4_case; architecture rtl of mux4_case is begin process(all) begin case sel is when "00" => y <= a; when "01" => y <= b; when "10" => y <= c; when others => y <= d; end case; end process; end architecture rtl; |
6. Banc de test du multiplexeur case
| tb_mux4_case.vhd — Partie 1 | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use STD.ENV.ALL; entity tb_mux4_case is end entity tb_mux4_case; architecture simulation of tb_mux4_case is signal a : std_logic_vector(7 downto 0) := x"11"; signal b : std_logic_vector(7 downto 0) := x"22"; signal c : std_logic_vector(7 downto 0) := x"44"; signal d : std_logic_vector(7 downto 0) := x"88"; signal sel : std_logic_vector(1 downto 0) := "00"; signal y : std_logic_vector(7 downto 0); begin DUT : entity work.mux4_case(rtl) port map ( a => a, b => b, c => c, d => d, sel => sel, y => y ); |
| tb_mux4_case.vhd — Partie 2 | VHDL |
| stimuli : process begin sel <= "00"; wait for 10 ns; assert y = x"11" severity error; sel <= "01"; wait for 10 ns; assert y = x"22" severity error; sel <= "10"; wait for 10 ns; assert y = x"44" severity error; sel <= "11"; wait for 10 ns; assert y = x"88" severity error; report "Test mux4_case termine" severity note; stop; wait; end process; end architecture simulation; |
7. Partie C — Comparateur numérique
Le comparateur reçoit deux mots non signés sur 8 bits et produit trois indicateurs mutuellement exclusifs.
| comparateur8.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity comparateur8 is port ( a, b : in std_logic_vector(7 downto 0); egal : out std_logic; inferieur : out std_logic; superieur : out std_logic ); end entity comparateur8; architecture rtl of comparateur8 is begin process(all) variable a_u : unsigned(7 downto 0); variable b_u : unsigned(7 downto 0); begin a_u := unsigned(a); b_u := unsigned(b); egal <= '0'; inferieur <= '0'; superieur <= '0'; if a_u = b_u then egal <= '1'; elsif a_u < b_u then inferieur <= '1'; else superieur <= '1'; end if; end process; end architecture rtl; |
8. Banc de test du comparateur
| tb_comparateur8.vhd — Partie 1 | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use STD.ENV.ALL; entity tb_comparateur8 is end entity tb_comparateur8; architecture simulation of tb_comparateur8 is signal a, b : std_logic_vector(7 downto 0) := (others => '0'); signal egal, inferieur, superieur : std_logic; begin DUT : entity work.comparateur8(rtl) port map ( a => a, b => b, egal => egal, inferieur => inferieur, superieur => superieur ); |
| tb_comparateur8.vhd — Boucles de test | VHDL |
| stimuli : process begin for va in 0 to 15 loop for vb in 0 to 15 loop a <= std_logic_vector(to_unsigned(va, 8)); b <= std_logic_vector(to_unsigned(vb, 8)); wait for 1 ns; assert egal = ('1' when va = vb else '0') report "Erreur egal" severity error; assert inferieur = ('1' when va < vb else '0') report "Erreur inferieur" severity error; assert superieur = ('1' when va > vb else '0') report "Erreur superieur" severity error; end loop; end loop; report "Test comparateur termine" severity note; stop; wait; end process; end architecture simulation; |
9. Partie D — Encodeur de priorité
L’entrée de poids fort est prioritaire. Une sortie valide indique qu’au moins une entrée est active.
| encodeur_priorite.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity encodeur_priorite is port ( entree : in std_logic_vector(3 downto 0); code : out std_logic_vector(1 downto 0); valide : out std_logic ); end entity encodeur_priorite; architecture rtl of encodeur_priorite is begin process(all) begin code <= "00"; valide <= '1'; if entree(3) = '1' then code <= "11"; elsif entree(2) = '1' then code <= "10"; elsif entree(1) = '1' then code <= "01"; elsif entree(0) = '1' then code <= "00"; else valide <= '0'; end if; end process; end architecture rtl; |
10. Banc de test de l’encodeur
| tb_encodeur_priorite.vhd — Partie 1 | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use STD.ENV.ALL; entity tb_encodeur_priorite is end entity tb_encodeur_priorite; architecture simulation of tb_encodeur_priorite is signal entree : std_logic_vector(3 downto 0) := (others => '0'); signal code : std_logic_vector(1 downto 0); signal valide : std_logic; begin DUT : entity work.encodeur_priorite(rtl) port map ( entree => entree, code => code, valide => valide ); |
| tb_encodeur_priorite.vhd — Scénarios | VHDL |
| stimuli : process begin entree <= "0000"; wait for 10 ns; assert valide = '0' severity error; entree <= "0001"; wait for 10 ns; assert valide = '1' and code = "00" severity error; entree <= "0010"; wait for 10 ns; assert code = "01" severity error; entree <= "0110"; wait for 10 ns; assert code = "10" report "La priorite de l'entree 2 est incorrecte" severity error; entree <= "1111"; wait for 10 ns; assert code = "11" report "La priorite de l'entree 3 est incorrecte" severity error; report "Test encodeur termine" severity note; stop; wait; end process; end architecture simulation; |
11. Partie E — Liste de sensibilité incomplète
| mux_sensibilite_incorrect.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity mux_sensibilite_incorrect is port ( a, b : in std_logic; sel : in std_logic; y : out std_logic ); end entity mux_sensibilite_incorrect; architecture rtl of mux_sensibilite_incorrect is begin process(a, sel) begin if sel = '0' then y <= a; else y <= b; end if; end process; end architecture rtl; |
Le signal b est lu mais absent de la liste de sensibilité.
12. Banc de test révélant le défaut
| tb_sensibilite.vhd — Scénario de diagnostic | VHDL |
| stimuli : process begin a <= '0'; b <= '0'; sel <= '1'; wait for 10 ns; assert y_incorrect = '0' severity error; -- Seul b change. Le processus incorrect -- ne se réveille pas. b <= '1'; wait for 10 ns; assert y_incorrect = '1' report "Defaut detecte : b manque dans la sensibilite" severity warning; wait; end process; |
13. Correction classique
| mux_sensibilite_corrige.vhd | VHDL |
| architecture rtl_liste of mux_sensibilite_corrige is begin process(a, b, sel) begin if sel = '0' then y <= a; else y <= b; end if; end process; end architecture rtl_liste; |
14. Correction VHDL-2008
| Correction avec process(all) | VHDL |
| architecture rtl_all of mux_sensibilite_corrige is begin process(all) begin if sel = '0' then y <= a; else y <= b; end if; end process; end architecture rtl_all; |
15. Compilation avec GHDL
| Compilation des sources | Terminal |
| ghdl -a --std=08 src/mux2_if.vhd ghdl -a --std=08 src/mux4_case.vhd ghdl -a --std=08 src/comparateur8.vhd ghdl -a --std=08 src/encodeur_priorite.vhd ghdl -a --std=08 src/mux_sensibilite_incorrect.vhd ghdl -a --std=08 src/mux_sensibilite_corrige.vhd |
| Exemple de simulation | Terminal |
| ghdl -a --std=08 sim/tb_mux4_case.vhd ghdl -e --std=08 tb_mux4_case ghdl -r --std=08 tb_mux4_case --vcd=tb_mux4_case.vcd gtkwave tb_mux4_case.vcd |
16. Observations demandées
- Vérifier les deux positions du multiplexeur if.
- Vérifier les quatre valeurs du sélecteur du multiplexeur case.
- Confirmer qu’un seul indicateur du comparateur vaut 1.
- Observer la priorité de l’entrée 3 dans l’encodeur.
- Modifier uniquement b dans la version à sensibilité incomplète.
- Comparer les chronogrammes des versions incorrecte et corrigée.
- Vérifier l’absence de latch dans le schéma RTL.
17. Tableau de résultats
Circuit | Entrées | Résultat attendu | Résultat observé | Conforme |
|---|---|---|---|---|
| MUX if | sel=0 | y=a | ||
| MUX if | sel=1 | y=b | ||
| MUX case | sel="10" | y=c | ||
| Comparateur | a=12, b=20 | inferieur=1 | ||
| Encodeur | entree="0110" | code="10" | ||
| Sensibilité incorrecte | b change seul | y doit changer |
18. Questions d’analyse
1. Pourquoi le processus du multiplexeur est-il concurrent alors que son if est séquentiel ?
2. Quelle différence matérielle peut exister entre un if prioritaire et un case ?
3. Pourquoi des valeurs par défaut sont-elles affectées dans le comparateur ?
4. Pourquoi l’encodeur de priorité doit-il utiliser if/elsif plutôt que case sur les quatre bits ?
5. Quel défaut apparaît lorsque b manque dans la liste de sensibilité ?
6. Pourquoi le synthétiseur peut-il produire un circuit correct malgré une simulation erronée ?
7. Quel avantage offre process(all) ?
8. Quand une absence de else est-elle volontaire dans un processus cadencé ?
9. Comment vérifier qu’une boucle for est synthétisable ?
10. Pourquoi une variable est-elle adaptée au calcul de parité ?
19. Extensions proposées
- Créer un multiplexeur 8 vers 1 générique.
- Ajouter un comparateur signé.
- Créer un encodeur prioritaire 8 vers 3.
- Ajouter un indicateur d’entrée invalide X ou Z.
- Comparer les ressources d’une version if et d’une version case.
- Écrire un détecteur du premier bit à 1 avec une boucle for et exit.
- Implanter les circuits sur une carte FPGA.
20. Barème indicatif
Critère | Points |
|---|---|
| Multiplexeur avec if | 2 |
| Multiplexeur avec case | 2 |
| Comparateur numérique | 3 |
| Encodeur de priorité | 3 |
| Bancs de test et assertions | 3 |
| Diagnostic de sensibilité | 3 |
| Correction avec liste complète et all | 2 |
| Analyse et qualité du compte rendu | 2 |
| Total | 20 |
Synthèse du chapitre
Notion | Résumé |
|---|---|
| Processus | Instruction concurrente contenant des instructions séquentielles. |
| Liste de sensibilité | Détermine les événements qui réveillent le processus. |
| Partie déclarative | Contient variables, constantes et types locaux. |
| if / elsif / else | Décisions potentiellement prioritaires. |
| case | Choix mutuellement exclusifs selon une expression. |
| for | Boucle à plage généralement statique et synthétisable. |
| while / loop | Boucles plus générales, à utiliser prudemment en synthèse. |
| exit | Interrompt une boucle. |
| Variable | Objet local mis à jour immédiatement avec :=. |
| Signal | Objet de communication mis à jour avec <=. |
| process(all) | Sensibilité automatique en VHDL-2008. |
| Latch involontaire | Résulte d’une affectation combinatoire incomplète. |
Autoévaluation
1. Un processus est-il concurrent ou séquentiel ?
Réponse : Il est concurrent comme bloc ; ses instructions internes sont séquentielles.
2. Où déclare-t-on une variable locale ?
Réponse : Entre process(...) et begin.
3. Quel opérateur affecte une variable ?
Réponse : :=.
4. Quel opérateur affecte un signal ?
Réponse : <=.
5. Quel choix convient à des conditions prioritaires ?
Réponse : if / elsif.
6. Quel choix convient à un opcode ?
Réponse : case.
7. Que doit contenir la sensibilité d’un processus combinatoire ?
Réponse : Tous les signaux lus, ou all en VHDL-2008.
8. Quel risque produit une sortie non affectée dans tous les chemins ?
Réponse : L’inférence d’un latch.
9. Une boucle for est-elle exécutée dans le temps matériel ?
Réponse : Pas nécessairement ; elle est souvent déroulée en matériel parallèle.
10. Quand faut-il inclure reset dans la sensibilité ?
Réponse : Lorsqu’il est asynchrone.
Exercice de consolidation
Écrire un processus combinatoire qui reçoit un vecteur de 8 bits, compte le nombre de bits à 1 et produit un résultat unsigned sur 4 bits. Utiliser une variable et une boucle for.
| Correction proposée | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_uns is port ( donnees : in std_logic_vector(7 downto 0); nombre : out unsigned(3 downto 0) ); end entity compteur_uns; architecture rtl of compteur_uns is begin process(all) variable total_v : natural range 0 to 8; begin total_v := 0; for i in donnees'range loop if donnees(i) = '1' then total_v := total_v + 1; end if; end loop; nombre <= to_unsigned(total_v, nombre'length); end process; end architecture rtl; |
Suite du cours — Transition vers la suite Après les processus combinatoires, le cours peut comparer les styles de description VHDL : flot de données, comportemental et structurel, puis introduire la conception hiérarchique. |