Leçon 21 sur 22

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.1Organisation du codeAméliorer la lisibilité
21.2Bibliothèques standardsAssurer la portabilité
21.3Conception synchroneConstruire une architecture robuste
21.4Prévention des erreursDétecter les défauts fréquents
21.5Réutilisation et paramétrageCréer une bibliothèque durable
AnnexesChecklists et exercicesAppliquer 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

1Bibliothèques et use
2Entité et génériques
3Types et sous-types
4Constantes
5Signaux internes
6Attributs de synthèse
7Instances et processus
8Affectations de sorties

 

21.1.3 Noms explicites

À éviter

À privilégier

x, xx, tmp1compteur_cycles, somme_etendue
p1registre_etat
sigbouton_synchronise
foverflow_addition
nlargeur_donnees
enenable_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 fonctionneluart_tx
Architecturertl, simulation, structurellearchitecture rtl
GénériquemajusculesLARGEUR_DONNEES
ConstantemajusculesPERIODE_CLK
Signalminusculesdata_valide
Typesuffixe _tetat_t
Instancepréfixe U_U_COMPTEUR
Processusnom fonctionnelregistre_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_etatMémoriser l’état courant
logique_etat_suivantCalculer la transition
sorties_fsmCalculer les sorties
compteur_tempsMesurer une durée
registre_sortieEnregistrer 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

UNon initialisé
XInconnu fort
0 / 1Niveaux logiques forts
ZHaute impédance
WInconnu faible
L / HNiveaux 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 unsignedunsigned(vecteur)
std_logic_vector vers signedsigned(vecteur)
unsigned vers std_logic_vectorstd_logic_vector(u)
integer vers unsignedto_unsigned(x, largeur)
integer vers signedto_signed(x, largeur)
unsigned/signed vers integerto_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 synchroneLa logique peut-elle attendre un front ?
Reset asynchroneUne remise à zéro immédiate est-elle requise ?
Valeur initialeLa cible garantit-elle l’initialisation à la configuration ?
Absence de resetLe 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

11111111255-1
10000000128-128
01111111127127

 

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 elseValeur par défaut
case avec nullAffecter la sortie
Variable non initialiséeInitialisation au début du processus
Tranche de vecteur oubliéeAffecter 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 RTLDélai physique non créé
wait forTemporisation de simulation
Fichier TextIO dynamiquePas de système de fichiers dans le FPGA
Boucle while non bornéeNombre de ressources indéterminé
Reset de toute une RAMBRAM 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 RTLLe matériel correspond-il à l’intention ?
LatchesLe nombre attendu est-il zéro ?
BasculesLes registres attendus sont-ils présents ?
RAM/DSPLes blocs dédiés sont-ils utilisés ?
WarningsChaque avertissement est-il expliqué ?
TimingLa 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

validLa source présente une donnée valide
readyLe destinataire peut accepter la donnée
TransfertEffectif lorsque valid=1 et ready=1

 

21.5.17 Interface start/busy/done

Signal

Rôle

startDemande d’opération, souvent une impulsion
busyLe composant est occupé
doneOpé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

FonctionCe que réalise le bloc
GénériquesPlages et unités
PortsSens, type, polarité
Horloge/resetDomaine et stratégie
LatenceNombre de cycles
DébitFréquence maximale des transactions
RestrictionsCas non supportés
VérificationNom 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 standardPortabilité réduite
Noms peu explicitesMaintenance difficile
Bouton utilisé comme horlogeRebonds et timing non maîtrisé
Signal c sans horlogeBoucle combinatoire/description incorrecte
Pas de resetÉtat initial incertain
Pas d’anti-rebondPlusieurs comptages par appui
Types std_logic_vector arithmétiquesIntention 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

pcompteur_prescaler
s1tick_1ms
xdiviseur_1ms
nF_CLK_HZ ou NB_CYCLES_1MS selon le rôle
ffin_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é

NommageLes noms décrivent la fonction, la polarité et l’unité.
IndentationLa structure du code reste immédiatement visible.
CommentairesIls expliquent les décisions, pas la syntaxe évidente.
numeric_stdPaquetage standard pour signed et unsigned.
Horloge principaleRéduit les domaines et simplifie le timing.
Clock enableRemplace la plupart des horloges logiques dérivées.
ResetStratégie cohérente et désactivation maîtrisée.
process(all)Évite les listes de sensibilité combinatoires incomplètes.
Valeurs par défautPréviennent les latches involontaires.
DimensionnementConserve report, signe et produit complet.
GénériquesParamètrent les variations utiles.
PaquetagesCentralisent types, constantes et fonctions partagés.
HiérarchieSépare les fonctions et facilite la réutilisation.
RevueVérifie code, synthèse, timing et tests.

 

Checklist finale de qualité

Catégorie

Vérification

StyleNoms, indentation et commentaires uniformes
BibliothèquesSTD_LOGIC_1164 + NUMERIC_STD
Typessigned/unsigned/std_logic_vector choisis selon le rôle
Combinatoireprocess(all), sorties complètes, aucun latch
SéquentielHorloges contraintes, enables et reset cohérents
DimensionnementLargeurs, extensions et conversions contrôlées
PilotesUn seul pilote par registre interne
RéutilisationGénériques validés et interface documentée
VérificationTestbench auto-vérifiant et cas limites
SynthèseWarnings, ressources et schéma RTL vérifiés
TimingContraintes et slack analysés
DocumentationLatence, 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.