Chapitre 21 — Bonnes pratiques de programmation VHDL
Objectif — Finalité du chapitre Écrire un code VHDL lisible, portable, synthétisable, vérifiable et réutilisable, tout en réduisant les erreurs d’architecture et de maintenance. |
Objectifs pédagogiques
À la fin de ce chapitre, l’étudiant devra être capable de :
- organiser un fichier VHDL de manière cohérente ;
- adopter des noms explicites et une convention uniforme ;
- utiliser IEEE.STD_LOGIC_1164 et IEEE.NUMERIC_STD ;
- choisir correctement std_logic_vector, unsigned, signed et integer ;
- concevoir un système principalement synchrone ;
- utiliser des signaux d’activation plutôt que des horloges logiques dérivées ;
- gérer correctement le reset ;
- détecter les erreurs classiques de simulation et de synthèse ;
- construire des composants génériques et hiérarchiques ;
- créer des paquetages et une bibliothèque de composants ;
- réaliser une revue de code VHDL avec une checklist structurée.
Prérequis
- entités, architectures et processus ;
- types, tableaux, fonctions et paquetages ;
- conception combinatoire et séquentielle ;
- synthèse, simulation et analyse temporelle ;
- génériques et hiérarchie.
Organisation du chapitre
Section | Contenu | Compétence principale |
|---|---|---|
| 21.1 | Organisation du code | Améliorer la lisibilité |
| 21.2 | Bibliothèques standards | Assurer la portabilité |
| 21.3 | Conception synchrone | Construire une architecture robuste |
| 21.4 | Prévention des erreurs | Détecter les défauts fréquents |
| 21.5 | Réutilisation et paramétrage | Créer une bibliothèque durable |
| Annexes | Checklists et exercices | Appliquer les règles |
Principe central — Le bon code décrit une intention matérielle Une bonne pratique ne vise pas seulement l’esthétique. Elle doit rendre le matériel attendu évident, réduire les ambiguïtés et faciliter la simulation, la synthèse et la maintenance. |
21.1 Organisation du code
Un code bien organisé permet à un lecteur de comprendre rapidement l’interface, l’architecture, les priorités et les hypothèses temporelles.
21.1.1 Structure recommandée d’un fichier
| Squelette d’un composant | VHDL |
| -- ============================================================ -- Nom : compteur_modulo_n -- Fonction : compteur paramétrable avec enable et tick terminal -- Auteur : MofidTech -- Standard : VHDL-2008 -- ============================================================ library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_modulo_n is generic ( MODULO : positive := 10 ); port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; q : out unsigned(3 downto 0); tick : out std_logic ); end entity compteur_modulo_n; architecture rtl of compteur_modulo_n is -- Types, constantes et signaux internes. begin -- Processus, affectations et instances. end architecture rtl; |
21.1.2 Ordre des déclarations
Ordre conseillé | Élément |
|---|---|
| 1 | Bibliothèques et use |
| 2 | Entité et génériques |
| 3 | Types et sous-types |
| 4 | Constantes |
| 5 | Signaux internes |
| 6 | Attributs de synthèse |
| 7 | Instances et processus |
| 8 | Affectations de sorties |
21.1.3 Noms explicites
À éviter | À privilégier |
|---|---|
| x, xx, tmp1 | compteur_cycles, somme_etendue |
| p1 | registre_etat |
| sig | bouton_synchronise |
| f | overflow_addition |
| n | largeur_donnees |
| en | enable_ecriture, si plusieurs enables existent |
21.1.4 Convention de nommage
Une équipe doit choisir une convention unique. L’exemple suivant utilise des minuscules et des underscores.
Objet | Convention proposée | Exemple |
|---|---|---|
| Entité | nom fonctionnel | uart_tx |
| Architecture | rtl, simulation, structurelle | architecture rtl |
| Générique | majuscules | LARGEUR_DONNEES |
| Constante | majuscules | PERIODE_CLK |
| Signal | minuscules | data_valide |
| Type | suffixe _t | etat_t |
| Instance | préfixe U_ | U_COMPTEUR |
| Processus | nom fonctionnel | registre_etat |
21.1.5 Nommer selon le rôle
| Signal exprimant sa fonction | VHDL |
| signal compteur_division : natural range 0 to DIVISEUR-1; signal tick_baud : std_logic; signal octet_a_emettre : std_logic_vector(7 downto 0); signal transmission_busy : std_logic; |
21.1.6 Polarité visible
Le suffixe _n permet d’identifier un signal actif à zéro.
| Signaux actifs bas | VHDL |
| signal reset_n : std_logic; signal cs_n : std_logic; signal we_n : std_logic; |
21.1.7 Unités dans les noms
| Génériques sans ambiguïté | VHDL |
| generic ( F_CLK_HZ : positive := 50_000_000; TIMEOUT_CYCLES : positive := 1000; PERIODE_NS : positive := 20 ); |
Nommage — Unité explicite Un nom tel que DUREE ne précise pas s’il s’agit de secondes, nanosecondes ou cycles. Ajouter _NS, _US, _HZ ou _CYCLES évite des erreurs. |
21.1.8 Indentation
| Indentation cohérente | VHDL |
| process(clk) begin if rising_edge(clk) then if reset = '1' then compteur <= (others => '0'); elsif enable = '1' then compteur <= compteur + 1; end if; end if; end process; |
21.1.9 Exemple difficile à lire
| À éviter | VHDL |
| process(clk) begin if rising_edge(clk) then if reset='1' then q<=(others=>'0'); elsif en='1' then q<=q+1;end if;end if;end process; |
21.1.10 Alignement des associations
| Port map lisible | VHDL |
| U_UART : entity work.uart_tx(rtl) port map ( clk => clk, reset => reset, baud_tick => tick_baud, start => start_uart, data_in => octet_tx, tx => uart_tx_o, busy => uart_busy, done => uart_done ); |
21.1.11 Commentaires utiles
- expliquer une décision d’architecture ;
- documenter une priorité ;
- indiquer une unité ou une plage ;
- préciser un protocole ou une latence ;
- signaler une contrainte propre à l’outil.
21.1.12 Commentaires inutiles
| Commentaire redondant | VHDL |
| -- Incrémenter le compteur. compteur <= compteur + 1; |
| Commentaire utile | VHDL |
| -- Le compteur produit un tick d'un cycle. -- La sortie reste dans le domaine clk principal. compteur <= compteur + 1; |
21.1.13 Commenter la priorité
| Priorité explicitée | VHDL |
| -- Priorité : reset > chargement > comptage > maintien. if reset = '1' then q <= (others => '0'); elsif load = '1' then q <= data_in; elsif enable = '1' then q <= q + 1; end if; |
21.1.14 Séparer les blocs fonctionnels
Un processus ne doit pas regrouper arbitrairement des fonctions indépendantes. Les blocs doivent correspondre à des rôles matériels cohérents.
Bloc | Rôle |
|---|---|
| registre_etat | Mémoriser l’état courant |
| logique_etat_suivant | Calculer la transition |
| sorties_fsm | Calculer les sorties |
| compteur_temps | Mesurer une durée |
| registre_sortie | Enregistrer l’interface |
21.1.15 Processus trop volumineux
Un processus très long mélangeant protocole, calcul, temporisation et sorties est difficile à tester. Il convient de séparer les sous-blocs ou d’utiliser une hiérarchie.
21.1.16 Uniformité du style
- même indentation dans tout le projet ;
- mêmes suffixes de polarité et de type ;
- même style pour les resets ;
- même style de FSM ;
- mêmes conventions de génériques ;
- même langue dans les commentaires d’un projet.
21.1.17 Longueur des lignes
Une ligne modérément courte facilite les revues et les comparaisons. Découper les expressions longues selon leur structure logique.
| Expression découpée | VHDL |
| condition_valide <= enable and data_ready and not fifo_full and not erreur; |
21.1.18 Constantes nommées
| À éviter | VHDL |
| if compteur = 49999999 then |
| À privilégier | VHDL |
| constant DIVISEUR_1S : positive := F_CLK_HZ; if compteur = DIVISEUR_1S-1 then |
21.1.19 Valeurs magiques
Une valeur numérique répétée sans nom masque sa signification. Utiliser une constante, un générique ou un type énuméré.
21.1.20 Un fichier par unité principale
- un fichier par entité réutilisable ;
- un fichier par paquetage ;
- un banc de test séparé ;
- un niveau supérieur séparé des composants ;
- des noms de fichiers proches des noms d’entités.
21.2 Utilisation des bibliothèques standards
Les bibliothèques IEEE standards améliorent la portabilité entre simulateurs et synthétiseurs.
21.2.1 std_logic_1164
| Import standard | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; |
Ce paquetage fournit std_logic, std_logic_vector, les valeurs multi-états et les opérateurs logiques.
21.2.2 Valeurs de std_logic
Valeur | Signification simplifiée |
|---|---|
| U | Non initialisé |
| X | Inconnu fort |
| 0 / 1 | Niveaux logiques forts |
| Z | Haute impédance |
| W | Inconnu faible |
| L / H | Niveaux faibles |
| - | Indifférent |
21.2.3 numeric_std
| Arithmétique standard | VHDL |
| use IEEE.NUMERIC_STD.ALL; signal a, b : unsigned(7 downto 0); signal s : unsigned(8 downto 0); s <= resize(a, 9) + resize(b, 9); |
21.2.4 Pourquoi numeric_std
- définit signed et unsigned ;
- rend l’interprétation numérique explicite ;
- fournit resize, to_unsigned, to_signed et to_integer ;
- est standard IEEE ;
- réduit les conflits de surcharge.
21.2.5 Bibliothèques non standards à éviter
| À éviter dans un nouveau projet | VHDL |
| use IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.STD_LOGIC_ARITH.ALL; |
Ces paquetages historiques ne font pas partie de la solution IEEE standard recommandée pour l’arithmétique moderne.
21.2.6 Conflit de bibliothèques
Importer simultanément plusieurs paquetages arithmétiques concurrents peut rendre un opérateur ambigu ou produire des comportements dépendants de l’outil.
Portabilité — Règle simple Utiliser STD_LOGIC_1164 pour la logique et NUMERIC_STD pour l’arithmétique. |
21.2.7 Choix du type std_logic_vector
std_logic_vector convient à un ensemble de bits sans interprétation arithmétique intrinsèque : trame, registre de contrôle, code d’opération ou bus logique.
| Bus logique | VHDL |
| signal opcode : std_logic_vector(2 downto 0); signal trame : std_logic_vector(9 downto 0); |
21.2.8 Choix du type unsigned
| Quantité non négative | VHDL |
| signal compteur : unsigned(15 downto 0); signal adresse : unsigned(9 downto 0); |
unsigned convient aux compteurs, adresses, tailles et valeurs non négatives.
21.2.9 Choix du type signed
| Valeur signée | VHDL |
| signal erreur_regulation : signed(15 downto 0); signal echantillon_audio : signed(23 downto 0); |
21.2.10 Choix du type integer ou natural
Un integer contraint est pratique pour les compteurs de contrôle et les indices.
| Plage explicite | VHDL |
| signal index_bit : natural range 0 to 7 := 0; signal compteur : natural range 0 to DIVISEUR-1 := 0; |
Dimensionnement — Limiter la plage Une plage contrainte documente l’intention et permet au synthétiseur de dimensionner la ressource. |
21.2.11 Conversion aux frontières
Il est efficace de conserver les signaux internes dans leur type numérique naturel, puis de convertir uniquement aux interfaces.
| Frontière d’entité | VHDL |
| compteur_o <= std_logic_vector(compteur_i); adresse_i <= unsigned(adresse_i_slv); |
21.2.12 Conversions explicites
Conversion | Écriture |
|---|---|
| std_logic_vector vers unsigned | unsigned(vecteur) |
| std_logic_vector vers signed | signed(vecteur) |
| unsigned vers std_logic_vector | std_logic_vector(u) |
| integer vers unsigned | to_unsigned(x, largeur) |
| integer vers signed | to_signed(x, largeur) |
| unsigned/signed vers integer | to_integer(x) |
21.2.13 Éviter les conversions en cascade
| À éviter | VHDL |
| resultat <= std_logic_vector( unsigned(std_logic_vector(unsigned(a))) + unsigned(b) ); |
| À privilégier | VHDL |
| signal a_u, b_u : unsigned(7 downto 0); signal r_u : unsigned(7 downto 0); r_u <= a_u + b_u; |
21.2.14 Largeurs explicites
| Addition complète | VHDL |
| somme_ext <= resize(a, somme_ext'length) + resize(b, somme_ext'length); |
21.2.15 Sous-types
| Sous-types documentaires | VHDL |
| subtype octet_t is std_logic_vector(7 downto 0); subtype adresse_t is unsigned(11 downto 0); subtype echantillon_t is signed(15 downto 0); |
Les sous-types améliorent la lisibilité sans créer un type incompatible.
21.2.16 Types personnalisés
| Type énuméré | VHDL |
| type etat_t is ( IDLE, START_BIT, DATA_BITS, STOP_BIT ); signal etat : etat_t := IDLE; |
21.2.17 Don’t care
La valeur '-' est utile dans certains motifs de comparaison, mais son interprétation en synthèse doit être maîtrisée. Ne pas l’utiliser comme valeur d’initialisation fonctionnelle.
21.2.18 Paquetages fixed et float
Les paquetages IEEE de virgule fixe et flottante peuvent être utiles, mais leur prise en charge doit être validée sur le flux de synthèse ciblé.
21.3 Conception synchrone
Une conception synchrone organise les registres autour d’un nombre limité d’horloges clairement contraintes.
21.3.1 Utilisation d’une horloge principale
- réseau d’horloge dédié ;
- contraintes temporelles simples ;
- transferts internes synchrones ;
- analyse de setup et hold maîtrisée ;
- moins de CDC.
21.3.2 Processus cadencé standard
| Style recommandé | VHDL |
| process(clk) begin if rising_edge(clk) then if reset = '1' then q <= (others => '0'); elsif enable = '1' then q <= d; end if; end if; end process; |
21.3.3 Signaux d’activation
Un clock enable conserve l’horloge principale et conditionne la mise à jour du registre.
| Tick utilisé comme enable | VHDL |
| process(clk) begin if rising_edge(clk) then if tick_1ms = '1' then compteur_ms <= compteur_ms + 1; end if; end if; end process; |
21.3.4 Horloge logique générée
| À éviter | VHDL |
| clk_lent <= compteur(23); process(clk_lent) begin if rising_edge(clk_lent) then q <= d; end if; end process; |
Le bit de compteur devient une horloge secondaire passant par du routage logique, avec contraintes et skew potentiellement non maîtrisés.
21.3.5 Remplacement par un enable
| À privilégier | VHDL |
| process(clk) begin if rising_edge(clk) then if compteur = DIVISEUR-1 then compteur <= 0; tick_lent <= '1'; else compteur <= compteur + 1; tick_lent <= '0'; end if; if tick_lent = '1' then q <= d; end if; end if; end process; |
Signal différé — Attention au tick du même processus Dans cet exemple, q voit l’ancienne valeur de tick_lent. Pour une action exactement au terminal, tester directement compteur = DIVISEUR-1 ou séparer clairement les processus. |
21.3.6 Version corrigée
| Condition terminale directe | VHDL |
| process(clk) begin if rising_edge(clk) then if reset = '1' then compteur <= 0; q <= (others => '0'); elsif compteur = DIVISEUR-1 then compteur <= 0; q <= d; else compteur <= compteur + 1; end if; end if; end process; |
21.3.7 Quand créer une horloge dérivée
Une horloge dérivée réelle doit être créée avec une ressource dédiée telle qu’une PLL/MMCM ou un buffer d’horloge, puis déclarée dans les contraintes.
21.3.8 Gating d’horloge
| Gating combinatoire déconseillé | VHDL |
| clk_gate <= clk and enable; |
Cette logique peut produire des impulsions raccourcies ou des glitches. Utiliser une ressource dédiée de clock gating si la technologie l’exige, sinon un clock enable.
21.3.9 Reset synchrone
| 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 est appliqué au front actif. Il s’intègre naturellement à l’analyse synchrone.
21.3.10 Reset asynchrone
| 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; |
L’activation est immédiate. La désactivation doit respecter les règles de récupération et de retrait de la bascule.
21.3.11 Désactivation synchronisée
| Reset local | VHDL |
| process(clk, reset_async) begin if reset_async = '1' then r1 <= '1'; r2 <= '1'; elsif rising_edge(clk) then r1 <= '0'; r2 <= r1; end if; end process; reset_local <= r2; |
21.3.12 Choisir une stratégie uniforme
Choix | Question |
|---|---|
| Reset synchrone | La logique peut-elle attendre un front ? |
| Reset asynchrone | Une remise à zéro immédiate est-elle requise ? |
| Valeur initiale | La cible garantit-elle l’initialisation à la configuration ? |
| Absence de reset | Le registre est-il initialisé par le protocole ? |
21.3.13 Ne pas tout réinitialiser
Réinitialiser chaque registre augmente le fanout et peut empêcher certaines inférences, notamment pour les mémoires. Réinitialiser les états de contrôle indispensables.
21.3.14 Priorité du reset
| Priorité claire | VHDL |
| if rising_edge(clk) then if reset = '1' then etat <= IDLE; elsif enable = '1' then etat <= etat_suivant; end if; end if; |
21.3.15 Entrées asynchrones
Un bouton, un capteur ou un signal d’un autre domaine ne doit pas entrer directement dans la logique synchrone. Utiliser la méthode du chapitre 17.
21.3.16 Une seule écriture par registre
Un registre interne doit idéalement être piloté par un seul processus cadencé. Cela rend la priorité et le matériel évidents.
21.3.17 Regrouper ou séparer les registres
Des registres partageant exactement la même horloge, le même reset et le même enable peuvent être regroupés. Des fonctions indépendantes peuvent rester séparées pour la lisibilité.
21.3.18 Éviter les boucles combinatoires
| Boucle problématique | VHDL |
| a <= not b; b <= not a; |
Une boucle combinatoire peut osciller en simulation ou créer un chemin non analysable. Introduire un registre ou reformuler l’architecture.
21.3.19 Pipeline
Insérer des registres dans un chemin combinatoire long améliore souvent la fréquence, mais modifie la latence. Cette latence doit être documentée et vérifiée.
21.3.20 Contrats temporels
- latence en cycles ;
- débit maximal ;
- durée des impulsions valid/done ;
- moment d’échantillonnage des entrées ;
- stabilité des données autour de valid.
21.4 Prévention des erreurs fréquentes
Les défauts les plus coûteux proviennent souvent d’une divergence entre l’intention, la simulation et le matériel synthétisé.
21.4.1 Liste de sensibilité incomplète
| Erreur | VHDL |
| process(a) begin y <= a and b; end process; |
Un changement de b ne réexécute pas le processus en simulation.
| Correction VHDL-2008 | VHDL |
| process(all) begin y <= a and b; end process; |
21.4.2 Affectation incomplète
| Latch involontaire | VHDL |
| process(all) begin if enable = '1' then y <= a; end if; end process; |
| Correction | VHDL |
| process(all) begin y <= '0'; if enable = '1' then y <= a; end if; end process; |
21.4.3 Branche case incomplète
| À éviter | VHDL |
| case sel is when "00" => y <= a; when "01" => y <= b; when others => null; end case; |
| À privilégier | VHDL |
| case sel is when "00" => y <= a; when "01" => y <= b; when "10" => y <= c; when others => y <= d; end case; |
21.4.4 signed et unsigned mélangés
| Conversion ambiguë ou incorrecte | VHDL |
| resultat <= signed(a) + unsigned(b); |
| Choix explicite | VHDL |
| resultat_s <= signed(a) + signed(b); -- ou resultat_u <= unsigned(a) + unsigned(b); |
21.4.5 Même motif, valeur différente
Motif | unsigned | signed |
|---|---|---|
| 11111111 | 255 | -1 |
| 10000000 | 128 | -128 |
| 01111111 | 127 | 127 |
21.4.6 Signal et variable
| Signal mis à jour plus tard | VHDL |
| process(clk) begin if rising_edge(clk) then a <= b; c <= a; -- ancienne valeur de a end if; end process; |
| Variable mise à jour immédiatement | VHDL |
| process(all) variable temp : std_logic; begin temp := a and b; temp := temp or c; y <= temp; end process; |
21.4.7 Mauvais dimensionnement
| Report perdu | VHDL |
| signal a, b : unsigned(7 downto 0); signal s : unsigned(7 downto 0); s <= a + b; |
| Somme complète | VHDL |
| signal s_ext : unsigned(8 downto 0); s_ext <= resize(a, 9) + resize(b, 9); |
21.4.8 Produit tronqué
| À éviter si le produit complet est requis | VHDL |
| signal produit : unsigned(7 downto 0); produit <= a * b; |
| Largeur correcte pour 8 × 8 | VHDL |
| signal produit : unsigned(15 downto 0); produit <= a * b; |
21.4.9 Multiples pilotes
| Deux processus pilotent q | VHDL |
| P1 : process(clk) begin if rising_edge(clk) then q <= a; end if; end process; P2 : process(clk) begin if rising_edge(clk) then q <= b; end if; end process; |
Pour std_logic, la simulation peut résoudre un conflit en X, mais le matériel interne n’accepte généralement pas deux pilotes concurrents pour un registre.
21.4.10 Correction des pilotes
| Une seule priorité | VHDL |
| process(clk) begin if rising_edge(clk) then if selection = '0' then q <= a; else q <= b; end if; end if; end process; |
21.4.11 Latch involontaire
Cause | Prévention |
|---|---|
| if sans else | Valeur par défaut |
| case avec null | Affecter la sortie |
| Variable non initialisée | Initialisation au début du processus |
| Tranche de vecteur oubliée | Affecter le vecteur complet |
21.4.12 Simulation correcte, synthèse incorrecte
Certaines constructions simulent mais ne correspondent pas au matériel souhaité.
Construction | Problème |
|---|---|
| after dans le RTL | Délai physique non créé |
| wait for | Temporisation de simulation |
| Fichier TextIO dynamique | Pas de système de fichiers dans le FPGA |
| Boucle while non bornée | Nombre de ressources indéterminé |
| Reset de toute une RAM | BRAM non inférée |
21.4.13 Signal non initialisé
Une simulation avec U ou X révèle souvent un reset manquant, une branche incomplète ou un pilote conflictuel. Ne pas masquer systématiquement ces valeurs.
21.4.14 Conversion hors plage
| Risque | VHDL |
| petit <= to_unsigned(valeur, 4); |
Si valeur dépasse 15, le résultat ou le diagnostic dépend de l’opération. Vérifier la plage avant conversion.
21.4.15 Indice hors plage
| Protection d’une ROM | VHDL |
| indice := to_integer(unsigned(adresse)); if indice <= ROM'high then data_out <= ROM(indice); else data_out <= (others => '0'); end if; |
21.4.16 Priorité involontaire
| Chaîne prioritaire | VHDL |
| if req0 = '1' then y <= a; elsif req1 = '1' then y <= b; elsif req2 = '1' then y <= c; else y <= d; end if; |
Cette forme donne la priorité à req0. Utiliser case si les choix sont exclusifs et sans priorité.
21.4.17 Utilisation de others
others sécurise les cas non listés, mais peut masquer un état illégal si aucune assertion ne le signale.
| Récupération et diagnostic | VHDL |
| when others => etat_suivant <= IDLE; assert false report "Etat illegal detecte" severity warning; |
21.4.18 Erreurs d’horloge
- plusieurs horloges non contraintes ;
- horloge créée dans la logique ;
- front montant et descendant mélangés ;
- signal asynchrone utilisé comme horloge ;
- CDC non synchronisé.
21.4.19 Reset incohérent
- polarité différente entre composants ;
- reset synchrone dans un bloc, asynchrone dans un autre sans justification ;
- désactivation asynchrone ;
- sorties non définies au reset ;
- mémoire entière réinitialisée inutilement.
21.4.20 Ignorer les avertissements
Les warnings de synthèse et de simulation doivent être classés : latch, signal supprimé, port non connecté, largeur tronquée, horloge absente ou contrainte ignorée.
21.4.21 Assertions
| Vérification d’un générique | VHDL |
| assert LARGEUR >= 2 report "LARGEUR doit etre >= 2" severity failure; |
| Vérification fonctionnelle | VHDL |
| assert not (write_en = '1' and full = '1') report "Ecriture demandee alors que la FIFO est pleine" severity error; |
21.4.22 Revue de synthèse
Élément | Question |
|---|---|
| Schéma RTL | Le matériel correspond-il à l’intention ? |
| Latches | Le nombre attendu est-il zéro ? |
| Bascules | Les registres attendus sont-ils présents ? |
| RAM/DSP | Les blocs dédiés sont-ils utilisés ? |
| Warnings | Chaque avertissement est-il expliqué ? |
| Timing | La fréquence et le slack sont-ils respectés ? |
21.5 Réutilisation et paramétrage
Un composant réutilisable possède une interface claire, des paramètres contrôlés, un comportement documenté et un banc de test.
21.5.1 Utilisation des génériques
| Registre paramétrable | VHDL |
| entity registre_n is generic ( N : positive := 8 ); port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; d : in std_logic_vector(N-1 downto 0); q : out std_logic_vector(N-1 downto 0) ); end entity registre_n; |
21.5.2 Architecture générique
| Registre N bits | VHDL |
| architecture rtl of registre_n is begin process(clk) begin if rising_edge(clk) then if reset = '1' then q <= (others => '0'); elsif enable = '1' then q <= d; end if; end if; end process; end architecture rtl; |
21.5.3 Génériques de temps
| Paramétrage indépendant de la carte | VHDL |
| generic ( F_CLK_HZ : positive := 50_000_000; BAUD : positive := 115_200 ); |
Le composant calcule ses diviseurs à partir des paramètres au lieu de contenir une valeur propre à une carte.
21.5.4 Validation des génériques
| Assertions d’élaboration | VHDL |
| assert MODULO >= 2 report "MODULO doit etre >= 2" severity failure; assert (2 ** ADR_BITS) >= PROFONDEUR report "ADR_BITS insuffisant" severity failure; |
21.5.5 Ne pas sur-paramétrer
Chaque générique augmente les combinaisons à tester. Paramétrer ce qui varie réellement, pas chaque détail interne.
21.5.6 Générique de type
VHDL-2008 permet des paquetages et sous-programmes plus génériques, mais la compatibilité de synthèse doit être vérifiée.
21.5.7 Création de paquetages
| projet_pkg.vhd — Déclarations | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; package projet_pkg is subtype octet_t is std_logic_vector(7 downto 0); subtype mot16_t is std_logic_vector(15 downto 0); type etat_uart_t is ( UART_IDLE, UART_START, UART_DATA, UART_STOP ); function max_nat( a : natural; b : natural ) return natural; function clog2( n : positive ) return natural; end package projet_pkg; |
21.5.8 Corps du paquetage
| projet_pkg.vhd — Corps, partie 1 | VHDL |
| package body projet_pkg is function max_nat( a : natural; b : natural ) return natural is begin if a > b then return a; else return b; end if; end function; |
| projet_pkg.vhd — Corps, partie 2 | VHDL |
| function clog2( n : positive ) return natural is variable valeur : natural := n - 1; variable bits : natural := 0; begin while valeur > 0 loop valeur := valeur / 2; bits := bits + 1; end loop; return bits; end function; end package body projet_pkg; |
21.5.9 Utiliser le paquetage
| Import | VHDL |
| library work; use work.projet_pkg.ALL; signal octet_rx : octet_t; signal etat : etat_uart_t; |
21.5.10 Contenu adapté à un paquetage
- types communs ;
- sous-types ;
- constantes partagées ;
- fonctions pures ;
- déclarations de composants lorsque nécessaires ;
- procédures de test réutilisables.
21.5.11 Contenu à éviter dans un paquetage global
- constantes propres à une seule instance ;
- signaux d’architecture ;
- dépendances circulaires ;
- paramètres de carte mélangés à la logique générique ;
- fonctions non documentées.
21.5.12 Conception hiérarchique
Le niveau supérieur relie des blocs spécialisés plutôt que d’implémenter tous les détails dans un seul processus.
| Niveau supérieur | VHDL |
| architecture structurelle of systeme_top is begin U_SYNC : entity work.synchroniseur_bit(rtl) port map (...); U_FILTRE : entity work.anti_rebond(rtl) generic map (...) port map (...); U_COMPTEUR : entity work.compteur_modulo_n(rtl) generic map (...) port map (...); U_AFFICHEUR : entity work.afficheur_7seg(rtl) port map (...); end architecture structurelle; |
21.5.13 Cohésion d’un composant
Un composant doit accomplir une fonction clairement définie. Un bloc qui synchronise, filtre, compte, affiche et communique est difficile à réutiliser.
21.5.14 Faible couplage
- interfaces simples ;
- signaux de contrôle génériques : valid, ready, start, busy, done ;
- pas de dépendance directe à une broche de carte ;
- fréquence fournie par un générique ;
- types d’interface documentés.
21.5.15 Séparer logique et carte
| Composant générique | VHDL |
| entity compteur_appuis is port ( clk : in std_logic; reset : in std_logic; impulsion : in std_logic; q : out unsigned(7 downto 0) ); end entity; |
| Niveau carte | Architecture |
| -- top_fpga associe : -- bouton physique -> chaîne de traitement -- q -> LED de la carte -- broches -> fichier de contraintes |
21.5.16 Interfaces valid/ready
Signal | Rôle |
|---|---|
| valid | La source présente une donnée valide |
| ready | Le destinataire peut accepter la donnée |
| Transfert | Effectif lorsque valid=1 et ready=1 |
21.5.17 Interface start/busy/done
Signal | Rôle |
|---|---|
| start | Demande d’opération, souvent une impulsion |
| busy | Le composant est occupé |
| done | Opération terminée, souvent une impulsion |
21.5.18 Bibliothèque de composants
| Organisation proposée | Arborescence |
| vhdl_lib/ ├── common/ │ ├── common_pkg.vhd │ └── types_pkg.vhd ├── clocking/ │ ├── tick_generator.vhd │ └── reset_synchronizer.vhd ├── io/ │ ├── synchronizer.vhd │ ├── debounce.vhd │ └── edge_detector.vhd ├── arithmetic/ │ ├── alu.vhd │ └── saturating_adder.vhd ├── memory/ │ ├── rom.vhd │ └── ram_simple_port.vhd ├── interfaces/ │ ├── uart_tx.vhd │ ├── uart_rx.vhd │ └── spi_master.vhd └── tests/ └── tb_*.vhd |
21.5.19 Fiche d’un composant
Rubrique | Contenu |
|---|---|
| Fonction | Ce que réalise le bloc |
| Génériques | Plages et unités |
| Ports | Sens, type, polarité |
| Horloge/reset | Domaine et stratégie |
| Latence | Nombre de cycles |
| Débit | Fréquence maximale des transactions |
| Restrictions | Cas non supportés |
| Vérification | Nom et couverture du testbench |
21.5.20 Banc de test par composant
Chaque composant réutilisable doit posséder des tests nominaux, limites et invalides. Une bibliothèque sans tests devient rapidement difficile à maintenir.
21.5.21 Versionnement
- utiliser un système de gestion de versions ;
- faire des commits cohérents ;
- ne pas mélanger une refonte et une correction fonctionnelle ;
- conserver les tests dans le dépôt ;
- documenter les changements d’interface.
21.5.22 Compatibilité
Préciser le standard VHDL, les synthétiseurs validés et les dépendances spécifiques au fabricant.
Guide de revue de code VHDL
1. Interface
- Les noms de ports expriment-ils leur rôle et leur polarité ?
- Les unités des génériques sont-elles explicites ?
- Les largeurs et types sont-ils adaptés ?
- La latence et le protocole sont-ils documentés ?
2. Combinatoire
- process(all) est-il utilisé ?
- Toutes les sorties sont-elles affectées ?
- Les valeurs par défaut sont-elles présentes ?
- Une priorité est-elle intentionnelle ?
- Aucun latch n’est-il inféré ?
3. Séquentiel
- L’horloge est-elle clairement identifiée ?
- Le reset est-il cohérent ?
- Les enables remplacent-ils les horloges logiques ?
- Chaque registre possède-t-il un seul pilote ?
- La latence correspond-elle au cahier des charges ?
4. Numérique
- signed et unsigned sont-ils correctement choisis ?
- Les résultats sont-ils dimensionnés ?
- Carry, overflow et saturation sont-ils distingués ?
- Les conversions sont-elles limitées aux frontières ?
5. Synthèse et implantation
- Les warnings sont-ils expliqués ?
- Les BRAM et DSP attendus sont-ils inférés ?
- Le schéma RTL est-il cohérent ?
- Les horloges sont-elles contraintes ?
- Le timing est-il respecté ?
6. Vérification
- Le testbench est-il auto-vérifiant ?
- Les cas limites sont-ils couverts ?
- Un timeout est-il présent ?
- Les assertions fournissent-elles un diagnostic précis ?
- Les régressions sont-elles reproductibles ?
Étude de cas — Refactorisation d’un compteur de bouton
Version initiale problématique
| À éviter | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity top is port( clk : in std_logic; b : in std_logic; l : out std_logic_vector(7 downto 0) ); end top; architecture a of top is signal c : std_logic_vector(23 downto 0); signal q : std_logic_vector(7 downto 0); begin c <= c + 1; process(b) begin if rising_edge(b) then q <= q + 1; end if; end process; l <= q; end a; |
Problèmes détectés
Problème | Conséquence |
|---|---|
| Bibliothèque non standard | Portabilité réduite |
| Noms peu explicites | Maintenance difficile |
| Bouton utilisé comme horloge | Rebonds et timing non maîtrisé |
| Signal c sans horloge | Boucle combinatoire/description incorrecte |
| Pas de reset | État initial incertain |
| Pas d’anti-rebond | Plusieurs comptages par appui |
| Types std_logic_vector arithmétiques | Intention numérique floue |
Architecture corrigée
| top_compteur_bouton.vhd — Partie 1 | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity top_compteur_bouton is generic ( NB_CYCLES_STABLES : positive := 1_000_000 ); port ( clk_carte : in std_logic; reset : in std_logic; bouton_async : in std_logic; leds : out std_logic_vector(7 downto 0) ); end entity top_compteur_bouton; architecture structurelle of top_compteur_bouton is signal bouton_sync : std_logic; signal bouton_filtre : std_logic; signal impulsion : std_logic; signal compteur : unsigned(7 downto 0); begin |
| top_compteur_bouton.vhd — Partie 2 | VHDL |
| U_SYNC : entity work.synchroniseur_bit(rtl) port map ( clk => clk_carte, reset => reset, entree_async => bouton_async, sortie_sync => bouton_sync ); U_FILTRE : entity work.anti_rebond(rtl) generic map ( NB_CYCLES_STABLES => NB_CYCLES_STABLES ) port map ( clk => clk_carte, reset => reset, entree => bouton_sync, sortie => bouton_filtre ); |
| top_compteur_bouton.vhd — Partie 3 | VHDL |
| U_FRONT : entity work.detecteur_front(rtl) port map ( clk => clk_carte, reset => reset, entree => bouton_filtre, montee => impulsion ); process(clk_carte) begin if rising_edge(clk_carte) then if reset = '1' then compteur <= (others => '0'); elsif impulsion = '1' then compteur <= compteur + 1; end if; end if; end process; leds <= std_logic_vector(compteur); end architecture structurelle; |
Bénéfices
- une horloge principale ;
- entrée synchronisée et filtrée ;
- un comptage par appui ;
- types numériques standards ;
- composants réutilisables ;
- noms explicites ;
- testbench possible par bloc et au niveau système.
Travaux dirigés — Bonnes pratiques
Exercice 1 — Revue de nommage
Proposer des noms améliorés pour les signaux p, s1, x, n et f dans un bloc qui divise l’horloge et produit un tick d’une milliseconde.
Nom initial | Proposition |
|---|---|
| p | compteur_prescaler |
| s1 | tick_1ms |
| x | diviseur_1ms |
| n | F_CLK_HZ ou NB_CYCLES_1MS selon le rôle |
| f | fin_comptage |
Exercice 2 — Bibliothèques
Corriger le code suivant afin d’utiliser les bibliothèques standards.
| Énoncé | VHDL |
| use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; signal compteur : std_logic_vector(7 downto 0); compteur <= compteur + 1; |
| Correction | VHDL |
| use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; signal compteur : unsigned(7 downto 0); compteur <= compteur + 1; |
Exercice 3 — Latch
| Énoncé | VHDL |
| process(all) begin if sel = '0' then y <= a; end if; end process; |
| Correction | VHDL |
| process(all) begin y <= b; if sel = '0' then y <= a; end if; end process; |
Exercice 4 — Horloge dérivée
Remplacer le processus cadencé par compteur(20) par un enable synchrone produit dans le domaine clk.
| Correction proposée | VHDL |
| process(clk) begin if rising_edge(clk) then tick <= '0'; if diviseur = LIMITE-1 then diviseur <= 0; tick <= '1'; else diviseur <= diviseur + 1; end if; if diviseur = LIMITE-1 then registre <= data_in; end if; end if; end process; |
Exercice 5 — Dimensionnement
Deux entrées unsigned de 12 bits sont additionnées. Quelle largeur faut-il pour conserver le report ?
| Réponse | VHDL |
| signal somme : unsigned(12 downto 0); somme <= resize(a, 13) + resize(b, 13); |
Exercice 6 — Paquetage
Créer un paquetage contenant un type d’état, un sous-type octet et une fonction min_nat.
| Correction — Déclaration | VHDL |
| package outils_pkg is subtype octet_t is std_logic_vector(7 downto 0); type etat_t is ( IDLE, ACTIVE, ERROR ); function min_nat( a : natural; b : natural ) return natural; end package outils_pkg; |
| Correction — Corps | VHDL |
| package body outils_pkg is function min_nat( a : natural; b : natural ) return natural is begin if a < b then return a; else return b; end if; end function; end package body outils_pkg; |
Synthèse du chapitre
Notion | Résumé |
|---|---|
| Nommage | Les noms décrivent la fonction, la polarité et l’unité. |
| Indentation | La structure du code reste immédiatement visible. |
| Commentaires | Ils expliquent les décisions, pas la syntaxe évidente. |
| numeric_std | Paquetage standard pour signed et unsigned. |
| Horloge principale | Réduit les domaines et simplifie le timing. |
| Clock enable | Remplace la plupart des horloges logiques dérivées. |
| Reset | Stratégie cohérente et désactivation maîtrisée. |
| process(all) | Évite les listes de sensibilité combinatoires incomplètes. |
| Valeurs par défaut | Préviennent les latches involontaires. |
| Dimensionnement | Conserve report, signe et produit complet. |
| Génériques | Paramètrent les variations utiles. |
| Paquetages | Centralisent types, constantes et fonctions partagés. |
| Hiérarchie | Sépare les fonctions et facilite la réutilisation. |
| Revue | Vérifie code, synthèse, timing et tests. |
Checklist finale de qualité
Catégorie | Vérification |
|---|---|
| Style | Noms, indentation et commentaires uniformes |
| Bibliothèques | STD_LOGIC_1164 + NUMERIC_STD |
| Types | signed/unsigned/std_logic_vector choisis selon le rôle |
| Combinatoire | process(all), sorties complètes, aucun latch |
| Séquentiel | Horloges contraintes, enables et reset cohérents |
| Dimensionnement | Largeurs, extensions et conversions contrôlées |
| Pilotes | Un seul pilote par registre interne |
| Réutilisation | Génériques validés et interface documentée |
| Vérification | Testbench auto-vérifiant et cas limites |
| Synthèse | Warnings, ressources et schéma RTL vérifiés |
| Timing | Contraintes et slack analysés |
| Documentation | Latence, débit, polarité et unités précisées |
Autoévaluation
1. Quelle bibliothèque utiliser pour l’arithmétique ?
Réponse : IEEE.NUMERIC_STD.
2. Quel type convient à une adresse ?
Réponse : unsigned.
3. Quel style évite une sensibilité combinatoire incomplète ?
Réponse : process(all).
4. Comment prévenir un latch ?
Réponse : Affecter toutes les sorties dans tous les chemins.
5. Pourquoi éviter une horloge créée par une LUT ?
Réponse : Routage et timing non maîtrisés.
6. Que faut-il utiliser à la place ?
Réponse : Un clock enable.
7. Comment nommer un signal actif bas ?
Réponse : Avec un suffixe tel que _n.
8. Quel est l’intérêt d’un paquetage ?
Réponse : Partager types, constantes et fonctions.
9. Pourquoi limiter la plage d’un natural ?
Réponse : Documenter et dimensionner le matériel.
10. Une simulation correcte suffit-elle ?
Réponse : Non, il faut analyser synthèse et timing.
Exercice de consolidation
Réaliser la revue du composant suivant, identifier au moins six problèmes et proposer une architecture corrigée.
| Énoncé | VHDL |
| use IEEE.STD_LOGIC_UNSIGNED.ALL; entity x is port( c : in std_logic; r : in std_logic; a : in std_logic_vector(7 downto 0); b : out std_logic_vector(7 downto 0) ); end; architecture z of x is signal t : std_logic_vector(7 downto 0); begin process(c) begin if c'event and c='1' then if r='1' then t <= "00000000"; else t <= t + a; end if; end if; end process; b <= t; end; |
| Correction proposée — Bibliothèques et entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity accumulateur8 is port ( clk : in std_logic; reset : in std_logic; data_in : in unsigned(7 downto 0); data_out : out unsigned(7 downto 0) ); end entity accumulateur8; |
| Correction proposée — Architecture | VHDL |
| architecture rtl of accumulateur8 is signal accumulateur : unsigned(7 downto 0) := (others => '0'); begin process(clk) begin if rising_edge(clk) then if reset = '1' then accumulateur <= (others => '0'); else accumulateur <= accumulateur + data_in; end if; end if; end process; data_out <= accumulateur; end architecture rtl; |
Suite du cours — Transition vers la suite Après les bonnes pratiques, le cours peut approfondir le débogage méthodique, l’utilisation des analyseurs logiques intégrés, les assertions et la recherche des causes d’un échec sur FPGA. |