Chapitre 11 — Compteurs numériques
Objectif — Finalité du chapitre Concevoir des compteurs binaires, modulo N et BCD, ajouter des fonctions de contrôle, générer des impulsions périodiques et piloter un affichage décimal sur carte FPGA. |
Objectifs pédagogiques
À la fin de ce chapitre, l’étudiant devra être capable de :
- décrire un compteur binaire ascendant, descendant et bidirectionnel ;
- dimensionner un compteur en fonction d’une valeur maximale ;
- concevoir un compteur modulo N paramétrable ;
- gérer enable, reset et chargement parallèle ;
- distinguer enroulement, saturation et débordement ;
- générer une impulsion périodique à partir d’une horloge rapide ;
- éviter la création de mauvaises horloges secondaires ;
- concevoir un compteur BCD de 0 à 9 ;
- chaîner plusieurs chiffres BCD ;
- piloter un ou plusieurs afficheurs sept segments ;
- simuler puis valider les compteurs sur une carte FPGA.
Prérequis
- processus cadencés et rising_edge ;
- reset synchrone et asynchrone ;
- registres et types unsigned ;
- génériques et fonctions de conversion ;
- décodeur BCD vers sept segments.
Organisation du chapitre
Section | Contenu | Compétence principale |
|---|---|---|
| 11.1 | Compteurs binaires | Compter dans différents sens |
| 11.2 | Compteur modulo N | Limiter le comptage à une plage |
| 11.3 | Compteur avec contrôle | Ajouter reset, enable, load et saturation |
| 11.4 | Diviseur de fréquence | Créer un rythme périodique à partir de cycles |
| 11.5 | Compteur BCD | Compter et afficher en décimal |
| TP 8 | Compteurs et affichage | Simuler et valider sur FPGA |
Concept clé — Compteur = registre + logique de prochaine valeur Un compteur est un registre dont la prochaine valeur est calculée à partir de la valeur courante, généralement par addition ou soustraction. |
11.1 Compteur binaire
Un compteur binaire mémorise un mot et le modifie à chaque front d’horloge autorisé. Un compteur sur N bits possède 2^N états binaires possibles.
11.1.1 Compteur ascendant
Le compteur ascendant ajoute 1 à chaque front actif.
| Compteur ascendant — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_ascendant is port ( clk : in std_logic; reset : in std_logic; q : out unsigned(7 downto 0) ); end entity compteur_ascendant; |
| Compteur ascendant — Architecture | VHDL |
| architecture rtl of compteur_ascendant is signal compteur : unsigned(7 downto 0); begin process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then compteur <= compteur + 1; end if; end process; q <= compteur; end architecture rtl; |
11.1.2 Séquence de comptage
Cycle | Valeur décimale | Valeur sur 4 bits |
|---|---|---|
| Initial | 0 | 0000 |
| 1 | 1 | 0001 |
| 2 | 2 | 0010 |
| 3 | 3 | 0011 |
| 4 | 4 | 0100 |
| 15 | 15 | 1111 |
| 16 | 0 | 0000 |
11.1.3 Débordement naturel
Lorsqu’un unsigned atteint sa valeur maximale, l’addition de 1 produit un enroulement modulo 2^N si le résultat est conservé sur la même largeur.
Enroulement — Exemple Sur 4 bits, 1111 + 1 donne 0000. La retenue sortante est perdue si elle n’est pas stockée dans un bit supplémentaire. |
11.1.4 Compteur descendant
| Compteur descendant | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '1'); elsif rising_edge(clk) then compteur <= compteur - 1; end if; end process; |
Après 0000, le compteur revient à 1111 sur la même largeur.
11.1.5 Compteur ascendant-descendant
| Commande de direction | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if direction = '1' then compteur <= compteur + 1; else compteur <= compteur - 1; end if; end if; end process; |
direction | Opération |
|---|---|
| 1 | Comptage ascendant |
| 0 | Comptage descendant |
11.1.6 Compteur paramétrable
| Entité générique | VHDL |
| entity compteur_binaire is generic ( N : positive := 8 ); port ( clk : in std_logic; reset : in std_logic; direction : in std_logic; q : out unsigned(N-1 downto 0) ); end entity compteur_binaire; |
| Architecture générique — Processus | VHDL |
| architecture rtl of compteur_binaire is signal compteur : unsigned(N-1 downto 0); begin process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if direction = '1' then compteur <= compteur + 1; else compteur <= compteur - 1; end if; end if; end process; |
| Architecture générique — Sortie | VHDL |
| q <= compteur; end architecture rtl; |
11.1.7 Sortie terminale
Une sortie terminal_count peut indiquer que le compteur a atteint sa valeur maximale ou minimale.
| Détection du maximum | VHDL |
| terminal_max <= '1' when compteur = (compteur'range => '1') else '0'; |
| Détection de zéro | VHDL |
| terminal_zero <= '1' when compteur = 0 else '0'; |
11.1.8 Compteur avec impulsion de retenue
| Impulsion lors de l’enroulement | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); carry_out <= '0'; elsif rising_edge(clk) then carry_out <= '0'; if compteur = (compteur'range => '1') then compteur <= (others => '0'); carry_out <= '1'; else compteur <= compteur + 1; end if; end if; end process; |
carry_out vaut 1 pendant un seul cycle lors du passage du maximum à zéro.
11.1.9 Compteur asynchrone et compteur synchrone
Critère | Compteur synchrone | Compteur asynchrone en cascade |
|---|---|---|
| Horloge | Commune à toutes les bascules | Sortie d’une bascule vers la suivante |
| Vitesse | Meilleure | Limitée par la propagation |
| Analyse temporelle | Plus simple | Plus délicate |
| Usage FPGA | Recommandé | Généralement déconseillé |
Bonne pratique — FPGA Les compteurs FPGA doivent être décrits de manière synchrone, avec une horloge distribuée par le réseau d’horloge dédié. |
11.1.10 Ressources synthétisées
Fonction | Ressource probable |
|---|---|
| Compteur N bits | N bascules + additionneur/incrémenteur |
| Direction | Multiplexeur entre +1 et -1 |
| Enable | Clock enable ou multiplexeur de maintien |
| Reset | Entrée reset dédiée ou logique de données |
| Terminal count | Comparateur |
11.2 Compteur modulo N
Un compteur modulo N possède N états utiles, numérotés de 0 à N-1. Après N-1, il revient à zéro.
11.2.1 Définition du modulo
Le modulo indique le nombre d’états distincts de la séquence. Un compteur modulo 10 suit 0, 1, ..., 9, puis revient à 0.
Modulo | États utiles |
|---|---|
| 2 | 0 à 1 |
| 8 | 0 à 7 |
| 10 | 0 à 9 |
| 60 | 0 à 59 |
| 100 | 0 à 99 |
11.2.2 Retour à zéro
| Compteur modulo 10 | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= 0; elsif rising_edge(clk) then if compteur = 9 then compteur <= 0; else compteur <= compteur + 1; end if; end if; end process; |
11.2.3 Dimensionnement du registre
Il faut choisir le plus petit nombre de bits B tel que 2^B soit supérieur ou égal à N.
Modulo N | Bits minimaux | Plage binaire disponible |
|---|---|---|
| 2 | 1 | 0 à 1 |
| 3 à 4 | 2 | 0 à 3 |
| 5 à 8 | 3 | 0 à 7 |
| 9 à 16 | 4 | 0 à 15 |
| 17 à 32 | 5 | 0 à 31 |
| 33 à 64 | 6 | 0 à 63 |
| 100 | 7 | 0 à 127 |
Codage — États inutilisés Un compteur modulo 10 sur 4 bits possède six codes binaires non utilisés : 1010 à 1111. Le circuit doit normalement les éviter ou revenir vers un état valide. |
11.2.4 Utilisation d’un générique
| Entité modulo générique | VHDL |
| entity compteur_modulo is generic ( MODULO : positive := 10 ); port ( clk : in std_logic; reset : in std_logic; tick : out std_logic; q : out natural range 0 to MODULO-1 ); end entity compteur_modulo; |
11.2.5 Architecture avec natural
| Compteur modulo N — Processus | VHDL |
| architecture rtl of compteur_modulo is signal compteur : natural range 0 to MODULO-1 := 0; begin process(clk, reset) begin if reset = '1' then compteur <= 0; tick <= '0'; elsif rising_edge(clk) then tick <= '0'; if compteur = MODULO-1 then compteur <= 0; tick <= '1'; else compteur <= compteur + 1; end if; end if; end process; |
| Compteur modulo N — Sortie | VHDL |
| q <= compteur; end architecture rtl; |
11.2.6 Architecture avec unsigned
Une largeur explicite peut être fournie par un second générique, ou calculée dans un paquetage avec une fonction clog2.
| Génériques MODULO et LARGEUR | VHDL |
| entity compteur_modulo_u is generic ( MODULO : positive := 10; LARGEUR : positive := 4 ); port ( clk : in std_logic; reset : in std_logic; q : out unsigned(LARGEUR-1 downto 0) ); end entity compteur_modulo_u; |
| Vérification du dimensionnement | VHDL |
| assert (2 ** LARGEUR) >= MODULO report "LARGEUR insuffisante pour MODULO" severity failure; |
11.2.7 Fonction clog2
| Fonction de calcul du nombre de bits | 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; |
Réutilisation — Paquetage utilitaire Une fonction comme clog2 est généralement placée dans un paquetage partagé afin d’être réutilisée par plusieurs composants. |
11.2.8 Compteur modulo avec tick
La sortie tick est une impulsion d’un cycle lorsque le compteur revient à zéro. Elle peut activer un étage suivant sans créer une nouvelle horloge.
| Utilisation du tick | VHDL |
| if rising_edge(clk) then if tick_unites = '1' then dizaines <= dizaines + 1; end if; end if; |
11.2.9 États invalides
| Récupération d’un état invalide | VHDL |
| if compteur >= MODULO-1 then compteur <= 0; else compteur <= compteur + 1; end if; |
La comparaison >= ramène aussi un éventuel état supérieur à MODULO-1 vers zéro.
11.2.10 Applications
- compteur décimal ;
- base de temps ;
- temporisateur ;
- adresse circulaire ;
- balayage d’afficheurs ;
- division de fréquence ;
- comptage d’événements limités.
11.3 Compteur avec contrôle
Un compteur pratique comporte souvent plusieurs commandes : activation, remise à zéro, chargement parallèle, direction, saturation ou indication de débordement.
11.3.1 Activation
| Compteur avec enable | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if enable = '1' then compteur <= compteur + 1; end if; end if; end process; |
Lorsque enable vaut 0, le compteur conserve sa valeur.
11.3.2 Remise à zéro
Reset | Description | Action |
|---|---|---|
| Asynchrone | Testé avant rising_edge | Immédiate |
| Synchrone | Testé après rising_edge | Au front actif |
| Actif haut | reset='1' | Niveau 1 actif |
| Actif bas | reset_n='0' | Niveau 0 actif |
11.3.3 Chargement parallèle
| Compteur avec load | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if load = '1' then compteur <= valeur_chargee; elsif enable = '1' then compteur <= compteur + 1; end if; end if; end process; |
Le chargement est prioritaire sur l’incrémentation.
11.3.4 Ordre de priorité
Priorité | Commande | Effet |
|---|---|---|
| 1 | reset | Valeur initiale |
| 2 | load | Chargement parallèle |
| 3 | enable et direction | Comptage |
| 4 | aucune | Maintien |
11.3.5 Saturation
Un compteur saturant reste bloqué à sa valeur maximale au lieu de revenir à zéro.
| Compteur saturant ascendant | VHDL |
| if rising_edge(clk) then if enable = '1' then if compteur < MAXIMUM then compteur <= compteur + 1; else compteur <= compteur; end if; end if; end if; |
L’affectation explicite compteur <= compteur peut être omise dans un processus cadencé.
11.3.6 Saturation descendante
| Blocage à zéro | VHDL |
| if enable = '1' then if compteur > 0 then compteur <= compteur - 1; end if; end if; |
11.3.7 Débordement
Le débordement peut désigner l’enroulement d’un compteur non signé ou le dépassement de la plage signée.
| Détection d’enroulement non signé | VHDL |
| overflow <= '1' when compteur = (compteur'range => '1') and enable = '1' else '0'; |
11.3.8 Impulsion de débordement enregistrée
| Overflow d’un cycle | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); overflow <= '0'; elsif rising_edge(clk) then overflow <= '0'; if enable = '1' then if compteur = MAXIMUM then compteur <= (others => '0'); overflow <= '1'; else compteur <= compteur + 1; end if; end if; end if; end process; |
11.3.9 Underflow
| Sous-dépassement descendant | VHDL |
| if compteur = 0 and direction = '0' and enable = '1' then underflow <= '1'; else underflow <= '0'; end if; |
11.3.10 Compteur complet
| Compteur contrôlé — Partie 1 | VHDL |
| process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); overflow <= '0'; underflow <= '0'; elsif rising_edge(clk) then overflow <= '0'; underflow <= '0'; if load = '1' then compteur <= valeur_chargee; |
| Compteur contrôlé — Partie 2 | VHDL |
| elsif enable = '1' then if direction = '1' then if compteur = MAXIMUM then compteur <= (others => '0'); overflow <= '1'; else compteur <= compteur + 1; end if; else if compteur = 0 then compteur <= MAXIMUM; underflow <= '1'; else compteur <= compteur - 1; end if; end if; end if; end if; end process; |
11.3.11 Saturation ou enroulement
Politique | Au maximum + 1 | Usage |
|---|---|---|
| Enroulement | Retour à zéro | Comptage cyclique |
| Saturation | Reste au maximum | Mesure bornée |
| Signalement | Retour à zéro + overflow | Chaînage ou alarme |
| Chargement spécial | Valeur définie | Séquence personnalisée |
11.3.12 Anti-rebond pour comptage manuel
Un bouton mécanique peut produire plusieurs impulsions pour un seul appui. Un compteur manuel doit utiliser un circuit anti-rebond et une détection d’impulsion.
Attention — Validation sur carte Sans anti-rebond, le compteur peut avancer de plusieurs unités à chaque appui, même si la logique VHDL du compteur est correcte. |
11.4 Diviseur de fréquence
Un diviseur de fréquence utilise un compteur de cycles pour produire un événement moins fréquent à partir de l’horloge système.
11.4.1 Principe
Pour produire une impulsion toutes les K périodes, le compteur compte de 0 à K-1 puis revient à zéro.
Horloge source | Période visée | Nombre de cycles |
|---|---|---|
| 100 MHz | 1 ms | 100 000 |
| 100 MHz | 10 ms | 1 000 000 |
| 50 MHz | 1 s | 50 000 000 |
| 10 MHz | 100 µs | 1 000 |
11.4.2 Compteur de cycles
| Base de temps générique | VHDL |
| entity generateur_tick is generic ( NB_CYCLES : positive := 50_000_000 ); port ( clk : in std_logic; reset : in std_logic; tick : out std_logic ); end entity generateur_tick; |
| Générateur de tick — Initialisation | VHDL |
| architecture rtl of generateur_tick is signal compteur : natural range 0 to NB_CYCLES-1 := 0; begin process(clk, reset) begin if reset = '1' then compteur <= 0; tick <= '0'; elsif rising_edge(clk) then tick <= '0'; |
| Générateur de tick — Comptage | VHDL |
| if compteur = NB_CYCLES-1 then compteur <= 0; tick <= '1'; else compteur <= compteur + 1; end if; end if; end process; end architecture rtl; |
11.4.3 Génération d’une impulsion périodique
tick reste à 1 pendant un cycle d’horloge source. Il sert de clock enable pour les compteurs plus lents.
| Utilisation du tick | VHDL |
| process(clk, reset) begin if reset = '1' then compteur_lent <= 0; elsif rising_edge(clk) then if tick_1hz = '1' then compteur_lent <= compteur_lent + 1; end if; end if; end process; |
11.4.4 Sortie carrée divisée
Pour produire un signal carré, une sortie peut être inversée toutes les K périodes. Sa période complète vaut alors 2K périodes de l’horloge source.
| Sortie carrée | VHDL |
| if compteur = NB_CYCLES-1 then compteur <= 0; sortie_divisee <= not sortie_divisee; else compteur <= compteur + 1; end if; |
Objectif | Valeur NB_CYCLES |
|---|---|
| Impulsion à f_clk / K | K |
| Signal carré à f_clk / (2K) | K |
11.4.5 Limites des horloges secondaires
Une sortie de compteur utilisée directement comme horloge crée un nouveau domaine d’horloge et peut ne pas utiliser correctement le réseau global.
| Forme déconseillée | VHDL |
| process(horloge_divisee) begin if rising_edge(horloge_divisee) then q <= d; end if; end process; |
Bonne pratique — Préférer un enable Conserver l’horloge principale et utiliser une impulsion tick comme activation. Cette méthode simplifie l’analyse temporelle et évite un domaine d’horloge supplémentaire. |
11.4.6 Quand une horloge secondaire est nécessaire
- utiliser un PLL ou MMCM ;
- utiliser un buffer d’horloge dédié ;
- déclarer les contraintes temporelles correspondantes ;
- traiter correctement les traversées de domaines d’horloge ;
- suivre les recommandations du fabricant FPGA.
11.4.7 Précision de la base de temps
La précision dépend directement de l’oscillateur source. Un compteur ne corrige pas les tolérances ou dérives de fréquence.
Source d’erreur | Conséquence |
|---|---|
| Tolérance de l’oscillateur | Période légèrement différente |
| Température | Dérive possible |
| Valeur NB_CYCLES arrondie | Erreur systématique |
| Jitter | Variation instantanée des fronts |
11.4.8 Calcul de NB_CYCLES
| Formule | Calcul |
| NB_CYCLES = FREQUENCE_HORLOGE_HZ / FREQUENCE_TICK_HZ |
Pour une horloge de 50 MHz et un tick de 1 Hz, NB_CYCLES = 50 000 000.
11.4.9 Tick de balayage d’afficheurs
Un tick de quelques centaines d’hertz à quelques kilohertz peut commander le changement rapide entre plusieurs afficheurs.
11.4.10 Compteur libre et sélection d’un bit
Chaque bit d’un compteur binaire libre divise approximativement la fréquence par une puissance de deux.
Bit q(k) | Fréquence approximative |
|---|---|
| q(0) | f_clk / 2 |
| q(1) | f_clk / 4 |
| q(2) | f_clk / 8 |
| q(k) | f_clk / 2^(k+1) |
Nuance — Usage limité Sélectionner un bit de compteur peut convenir pour une LED ou un test simple, mais ne remplace pas une architecture d’horloge correctement contrainte. |
11.5 Compteur BCD
Le code BCD représente chaque chiffre décimal par quatre bits. Un compteur BCD parcourt uniquement les valeurs binaires de 0000 à 1001.
11.5.1 Comptage de 0 à 9
| Compteur BCD à un chiffre | VHDL |
| process(clk, reset) begin if reset = '1' then bcd <= (others => '0'); retenue <= '0'; elsif rising_edge(clk) then retenue <= '0'; if enable = '1' then if bcd = 9 then bcd <= (others => '0'); retenue <= '1'; else bcd <= bcd + 1; end if; end if; end if; end process; |
11.5.2 Codes valides et invalides
Décimal | BCD |
|---|---|
| 0 | 0000 |
| 1 | 0001 |
| 5 | 0101 |
| 9 | 1001 |
| 10 | Deux chiffres : 0001 0000 |
Les codes 1010 à 1111 ne représentent pas un chiffre BCD valide.
11.5.3 Chaînage de plusieurs chiffres
La retenue des unités active le compteur des dizaines. La retenue des dizaines peut activer les centaines.
| Chaînage BCD — Unités | VHDL |
| process(clk, reset) begin if reset = '1' then unites <= 0; dizaines <= 0; elsif rising_edge(clk) then if tick = '1' then if unites = 9 then unites <= 0; |
| Chaînage BCD — Dizaines | VHDL |
| if dizaines = 9 then dizaines <= 0; else dizaines <= dizaines + 1; end if; else unites <= unites + 1; end if; end if; end if; end process; |
11.5.4 Compteur 00 à 99
État | Dizaines | Unités |
|---|---|---|
| 00 | 0 | 0 |
| 09 | 0 | 9 |
| 10 | 1 | 0 |
| 42 | 4 | 2 |
| 99 | 9 | 9 |
| Après 99 | 0 | 0 |
11.5.5 Version hiérarchique
Deux instances d’un compteur BCD à un chiffre peuvent être chaînées. Le tick de retenue des unités devient l’enable des dizaines.
| Instances BCD | VHDL |
| U_UNITES : entity work.compteur_bcd(rtl) port map ( clk => clk, reset => reset, enable => tick_1hz, bcd => unites, retenue => tick_dizaines ); U_DIZAINES : entity work.compteur_bcd(rtl) port map ( clk => clk, reset => reset, enable => tick_dizaines, bcd => dizaines, retenue => open ); |
11.5.6 Affichage décimal
Chaque chiffre BCD est converti en sept signaux correspondant aux segments a à g.
| Décodeur BCD — Chiffres 0 à 4 | VHDL |
| with bcd select segments <= "1111110" when "0000", -- 0 "0110000" when "0001", -- 1 "1101101" when "0010", -- 2 "1111001" when "0011", -- 3 "0110011" when "0100", -- 4 |
| Décodeur BCD — Chiffres 5 à 9 | VHDL |
| "1011011" when "0101", -- 5 "1011111" when "0110", -- 6 "1110000" when "0111", -- 7 "1111111" when "1000", -- 8 "1111011" when "1001", -- 9 "0000000" when others; |
11.5.7 Afficheurs multiplexés
Sur de nombreuses cartes, les segments sont partagés et un signal d’activation sélectionne rapidement le chiffre visible.
Phase | Afficheur activé | Donnée BCD envoyée |
|---|---|---|
| 0 | Unités | unites |
| 1 | Dizaines | dizaines |
11.5.8 Balayage à deux chiffres
| Sélection du chiffre | VHDL |
| process(all) begin if selection = '0' then bcd_affiche <= std_logic_vector(unites); anodes <= "10"; else bcd_affiche <= std_logic_vector(dizaines); anodes <= "01"; end if; end process; |
La polarité des anodes ou cathodes dépend de la carte. Les valeurs doivent être adaptées.
11.5.9 Fréquence de multiplexage
Le balayage doit être suffisamment rapide pour éviter le scintillement visible, mais pas inutilement rapide.
Nombre de chiffres | Fréquence globale indicative | Fréquence par chiffre |
|---|---|---|
| 2 | 1 kHz | 500 Hz |
| 4 | 2 kHz | 500 Hz |
| 4 | 4 kHz | 1 kHz |
11.5.10 Affichage actif à 0
| Inversion des segments | VHDL |
| segments_sortie <= not segments_actifs_haut; |
Important — Carte cible Toujours vérifier si les segments et les sélections d’afficheurs sont actifs à 0 ou actifs à 1. |
11.5.11 Horloge, tick et affichage
Une architecture robuste conserve l’horloge principale pour tous les processus et utilise deux enables : un tick lent pour le comptage et un tick rapide pour le balayage.
Signal | Rôle |
|---|---|
| clk | Horloge principale unique |
| tick_1hz | Active le compteur décimal |
| tick_scan | Change l’afficheur actif |
| selection | Choisit unités ou dizaines |
Méthode de conception d’un compteur
1. Définir la séquence de comptage.
2. Déterminer le modulo ou la plage.
3. Calculer la largeur minimale.
4. Choisir la politique de limite : enroulement ou saturation.
5. Définir reset, enable, load et direction.
6. Définir les sorties terminales ou ticks.
7. Écrire un processus cadencé unique.
8. Initialiser les impulsions de sortie à zéro à chaque cycle.
9. Simuler les limites et les retours à zéro.
10. Vérifier les ressources et contraintes temporelles.
Checklist de validation
- Le compteur possède-t-il assez de bits ?
- Le retour à zéro se produit-il au bon état ?
- Le reset possède-t-il la bonne priorité ?
- Le chargement est-il prioritaire sur le comptage ?
- Le tick dure-t-il exactement un cycle ?
- Le compteur reste-t-il stable lorsque enable vaut 0 ?
- Les états invalides sont-ils récupérés ?
- Une horloge secondaire a-t-elle été créée inutilement ?
- Les afficheurs utilisent-ils la bonne polarité ?
Erreurs fréquentes
Erreur | Conséquence | Correction |
|---|---|---|
| Comparer à MODULO au lieu de MODULO-1 | Un état supplémentaire | Retour à zéro à N-1 |
| Largeur insuffisante | Valeur non représentable | Calculer clog2 |
| Tick non remis à zéro | Impulsion trop longue | Valeur par défaut à chaque cycle |
| Bouton utilisé sans anti-rebond | Comptages multiples | Filtrage et impulsion |
| Bit de compteur utilisé comme horloge | Nouveau domaine non contraint | Utiliser clock enable |
| Affichage BCD de codes 10 à 15 | Motifs invalides | Limiter à 0..9 |
| Polarité sept segments incorrecte | Affichage inversé | Inverser les sorties |
Travaux pratiques
TP 8 — Compteurs et affichage
1. Objectifs du TP
- réaliser un compteur binaire sur LED ;
- réaliser un compteur modulo 10 ;
- réaliser un compteur de 00 à 99 ;
- générer un tick lent ;
- piloter deux afficheurs sept segments ;
- simuler tous les retours à zéro ;
- valider le système sur une carte FPGA.
2. Organisation du projet
| Arborescence proposée | Arborescence |
| tp8_compteurs_affichage/ ├── src/ │ ├── compteur_binaire8.vhd │ ├── compteur_modulo10.vhd │ ├── compteur_00_99.vhd │ ├── generateur_tick.vhd │ ├── decodeur_7segments.vhd │ ├── afficheur_2digits.vhd │ └── top_fpga.vhd ├── sim/ │ ├── tb_compteur_binaire8.vhd │ ├── tb_compteur_modulo10.vhd │ ├── tb_compteur_00_99.vhd │ └── tb_afficheur_2digits.vhd └── constraints/ └── carte.xdc_ou_qsf |
3. Partie A — Compteur binaire sur LED
| compteur_binaire8.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_binaire8 is port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; q : out std_logic_vector(7 downto 0) ); end entity compteur_binaire8; |
| compteur_binaire8.vhd — Architecture | VHDL |
| architecture rtl of compteur_binaire8 is signal compteur : unsigned(7 downto 0); begin process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); elsif rising_edge(clk) then if enable = '1' then compteur <= compteur + 1; end if; end if; end process; q <= std_logic_vector(compteur); end architecture rtl; |
4. Banc de test du compteur binaire
| tb_compteur_binaire8.vhd — Test | VHDL |
| reset <= '1'; enable <= '0'; wait for 2 * PERIODE; reset <= '0'; enable <= '1'; for i in 1 to 20 loop wait until rising_edge(clk); wait for 1 ns; assert unsigned(q) = to_unsigned(i, 8) report "Comptage binaire incorrect" severity error; end loop; enable <= '0'; wait until rising_edge(clk); wait for 1 ns; assert unsigned(q) = to_unsigned(20, 8) report "Le compteur doit rester stable" severity error; |
5. Partie B — Compteur modulo 10
| compteur_modulo10.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_modulo10 is port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; chiffre : out unsigned(3 downto 0); retenue : out std_logic ); end entity compteur_modulo10; |
| compteur_modulo10.vhd — Architecture | VHDL |
| architecture rtl of compteur_modulo10 is signal compteur : unsigned(3 downto 0); begin process(clk, reset) begin if reset = '1' then compteur <= (others => '0'); retenue <= '0'; elsif rising_edge(clk) then retenue <= '0'; if enable = '1' then if compteur = 9 then compteur <= (others => '0'); retenue <= '1'; else compteur <= compteur + 1; end if; end if; end if; end process; chiffre <= compteur; end architecture rtl; |
6. Banc de test modulo 10
| Vérification du retour à zéro | VHDL |
| for i in 1 to 10 loop wait until rising_edge(clk); wait for 1 ns; if i < 10 then assert chiffre = to_unsigned(i, 4) severity error; assert retenue = '0' severity error; else assert chiffre = to_unsigned(0, 4) severity error; assert retenue = '1' report "La retenue doit signaler 9 vers 0" severity error; end if; end loop; |
7. Partie C — Compteur 00 à 99
| compteur_00_99.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity compteur_00_99 is port ( clk : in std_logic; reset : in std_logic; enable : in std_logic; unites : out unsigned(3 downto 0); dizaines : out unsigned(3 downto 0) ); end entity compteur_00_99; |
| compteur_00_99.vhd — Compteur des unités | VHDL |
| architecture structurelle of compteur_00_99 is signal tick_dizaines : std_logic; begin U_UNITES : entity work.compteur_modulo10(rtl) port map ( clk => clk, reset => reset, enable => enable, chiffre => unites, retenue => tick_dizaines ); |
| compteur_00_99.vhd — Compteur des dizaines | VHDL |
| U_DIZAINES : entity work.compteur_modulo10(rtl) port map ( clk => clk, reset => reset, enable => tick_dizaines, chiffre => dizaines, retenue => open ); end architecture structurelle; |
8. Banc de test 00 à 99
| Test de 105 impulsions | VHDL |
| for valeur in 1 to 105 loop wait until rising_edge(clk); wait for 1 ns; attendu := valeur mod 100; assert to_integer(dizaines) = attendu / 10 report "Dizaines incorrectes" severity error; assert to_integer(unites) = attendu mod 10 report "Unites incorrectes" severity error; end loop; |
9. Partie D — Diviseur de fréquence
| generateur_tick.vhd | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity generateur_tick is generic ( NB_CYCLES : positive := 50_000_000 ); port ( clk : in std_logic; reset : in std_logic; tick : out std_logic ); end entity generateur_tick; architecture rtl of generateur_tick is signal compteur : natural range 0 to NB_CYCLES-1; begin process(clk, reset) begin if reset = '1' then compteur <= 0; tick <= '0'; elsif rising_edge(clk) then tick <= '0'; if compteur = NB_CYCLES-1 then compteur <= 0; tick <= '1'; else compteur <= compteur + 1; end if; end if; end process; end architecture rtl; |
10. Configuration simulation et carte
Contexte | NB_CYCLES conseillé |
|---|---|
| Simulation rapide | 5 ou 10 |
| Carte 50 MHz, tick 1 Hz | 50 000 000 |
| Carte 100 MHz, tick 1 Hz | 100 000 000 |
| Carte 100 MHz, scan 1 kHz | 100 000 |
Méthode — Accélération de simulation Ne pas simuler des dizaines de millions de cycles pour vérifier la logique. Utiliser une petite valeur du générique dans le banc de test. |
11. Partie E — Décodeur sept segments
| decodeur_7segments.vhd — Entité | VHDL |
| library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity decodeur_7segments is port ( bcd : in std_logic_vector(3 downto 0); segments : out std_logic_vector(6 downto 0) ); end entity decodeur_7segments; |
| decodeur_7segments.vhd — Architecture | VHDL |
| architecture rtl of decodeur_7segments is begin with bcd select segments <= "1111110" when "0000", "0110000" when "0001", "1101101" when "0010", "1111001" when "0011", "0110011" when "0100", "1011011" when "0101", "1011111" when "0110", "1110000" when "0111", "1111111" when "1000", "1111011" when "1001", "0000000" when others; end architecture rtl; |
12. Partie F — Affichage à deux chiffres
| afficheur_2digits.vhd — Sélection | VHDL |
| process(clk, reset) begin if reset = '1' then selection <= '0'; elsif rising_edge(clk) then if tick_scan = '1' then selection <= not selection; end if; end if; end process; |
| Multiplexage des chiffres | VHDL |
| process(all) begin if selection = '0' then bcd_actif <= std_logic_vector(unites); anodes <= "10"; else bcd_actif <= std_logic_vector(dizaines); anodes <= "01"; end if; end process; U_DEC : entity work.decodeur_7segments(rtl) port map ( bcd => bcd_actif, segments => segments ); |
13. Niveau supérieur FPGA
| top_fpga.vhd — Tick de comptage | VHDL |
| architecture structurelle of top_fpga is signal tick_1hz : std_logic; signal tick_scan : std_logic; signal unites : unsigned(3 downto 0); signal dizaines : unsigned(3 downto 0); begin U_TICK_1HZ : entity work.generateur_tick(rtl) generic map ( NB_CYCLES => 50_000_000 ) port map ( clk => clk_carte, reset => reset, tick => tick_1hz ); |
| top_fpga.vhd — Compteur décimal | VHDL |
| U_COMPTEUR : entity work.compteur_00_99(structurelle) port map ( clk => clk_carte, reset => reset, enable => tick_1hz, unites => unites, dizaines => dizaines ); -- Une seconde instance produit tick_scan. -- Le bloc d'affichage utilise les deux chiffres. end architecture structurelle; |
14. Contraintes de carte
| Exemple conceptuel | Contraintes |
| create_clock -period 10.000 -name clk_carte [get_ports clk_carte] set_property PACKAGE_PIN <PIN_CLK> [get_ports clk_carte] set_property PACKAGE_PIN <PIN_SEG_A> [get_ports {segments[6]}] set_property PACKAGE_PIN <PIN_DIGIT0> [get_ports {anodes[0]}] set_property PACKAGE_PIN <PIN_DIGIT1> [get_ports {anodes[1]}] |
15. Procédure de validation sur carte
1. Définir la fréquence exacte de l’oscillateur.
2. Configurer NB_CYCLES pour le tick de comptage.
3. Configurer un tick de balayage rapide.
4. Associer les segments et anodes aux broches.
5. Vérifier les polarités.
6. Synthétiser et examiner les avertissements.
7. Vérifier la contrainte temporelle de l’horloge.
8. Programmer la carte.
9. Activer le reset.
10. Observer le comptage de 00 à 99.
16. Commandes GHDL
| Compilation | Terminal |
| ghdl -a --std=08 src/compteur_binaire8.vhd ghdl -a --std=08 src/compteur_modulo10.vhd ghdl -a --std=08 src/compteur_00_99.vhd ghdl -a --std=08 src/generateur_tick.vhd ghdl -a --std=08 src/decodeur_7segments.vhd |
| Simulation du compteur 00 à 99 | Terminal |
| ghdl -a --std=08 sim/tb_compteur_00_99.vhd ghdl -e --std=08 tb_compteur_00_99 ghdl -r --std=08 tb_compteur_00_99 --vcd=tb_compteur_00_99.vcd gtkwave tb_compteur_00_99.vcd |
17. Observations demandées
- Observer l’incrémentation binaire sur huit bits.
- Vérifier le retour 9 vers 0.
- Vérifier le passage 09 vers 10.
- Vérifier le passage 99 vers 00.
- Mesurer la durée du tick.
- Observer le balayage des afficheurs.
- Vérifier l’absence de scintillement visible.
- Comparer simulation et carte FPGA.
18. Tableau de résultats
Bloc | Test | Attendu | Observé | Conforme |
|---|---|---|---|---|
| Binaire | 20 cycles | q=20 | ||
| Modulo 10 | 10 cycles | 0 + retenue | ||
| 00 à 99 | 10 cycles | 10 | ||
| 00 à 99 | 100 cycles | 00 | ||
| Tick | NB_CYCLES | Impulsion d’un cycle | ||
| Affichage | Valeur 42 | 42 visible |
19. Questions d’analyse
1. Pourquoi un compteur sur N bits possède-t-il 2^N états ?
2. Quelle différence existe entre compteur modulo N et compteur binaire naturel ?
3. Pourquoi tester MODULO-1 avant l’incrémentation ?
4. Comment déterminer la largeur minimale d’un modulo 100 ?
5. Quelle différence existe entre saturation et enroulement ?
6. Pourquoi remettre tick à zéro au début de chaque cycle ?
7. Pourquoi préférer un clock enable à une horloge divisée par logique ?
8. Comment la retenue des unités commande-t-elle les dizaines ?
9. Pourquoi faut-il accélérer le diviseur dans le banc de test ?
10. Comment adapter le décodeur à des segments actifs à zéro ?
20. Extensions proposées
- Ajouter un comptage ascendant-descendant de 00 à 99.
- Ajouter un chargement parallèle.
- Ajouter un mode de saturation.
- Créer un compteur de 000 à 999.
- Créer un chronomètre minutes-secondes.
- Ajouter un bouton pause/reprise.
- Ajouter un anti-rebond.
- Afficher la valeur hexadécimale.
- Créer un compteur d’événements externes synchronisés.
21. Barème indicatif
Critère | Points |
|---|---|
| Compteur binaire | 2 |
| Compteur modulo 10 | 3 |
| Compteur 00 à 99 | 4 |
| Diviseur/générateur de tick | 3 |
| Affichage sept segments | 3 |
| Bancs de test et assertions | 2 |
| Validation sur carte | 2 |
| Qualité du compte rendu | 1 |
| Total | 20 |
Synthèse du chapitre
Notion | Résumé |
|---|---|
| Compteur ascendant | Ajoute 1 à chaque front autorisé. |
| Compteur descendant | Soustrait 1. |
| Modulo N | Séquence de 0 à N-1. |
| Enable | Autorise le comptage. |
| Load | Charge une valeur parallèle. |
| Saturation | Bloque le compteur à une limite. |
| Overflow | Signale un dépassement ou enroulement. |
| Tick | Impulsion périodique d’un cycle. |
| Diviseur | Compte les cycles de l’horloge source. |
| Compteur BCD | Compte de 0 à 9 par chiffre. |
| Chaînage BCD | La retenue active le chiffre suivant. |
| Multiplexage | Partage les segments entre plusieurs chiffres. |
Autoévaluation
1. Que devient un compteur 4 bits après 15 ?
Réponse : Il revient à 0 en mode enroulement.
2. Quelles valeurs parcourt un modulo 10 ?
Réponse : 0 à 9.
3. Combien de bits faut-il pour modulo 100 ?
Réponse : 7 bits.
4. Que fait enable=0 ?
Réponse : Le compteur conserve sa valeur.
5. Quelle commande est généralement prioritaire ?
Réponse : Le reset.
6. Qu’est-ce qu’un compteur saturant ?
Réponse : Il reste à la limite.
7. Quelle durée doit avoir un tick ?
Réponse : Un cycle d’horloge source.
8. Pourquoi éviter une horloge secondaire logique ?
Réponse : Elle crée un domaine mal distribué et difficile à contraindre.
9. Quel code BCD représente 9 ?
Réponse : 1001.
10. Comment afficher 42 ?
Réponse : Dizaines=4 et unités=2.
Exercice de consolidation
Concevoir un compteur modulo 60 avec reset synchrone, enable, chargement parallèle et tick lors du passage 59 vers 0.
| Correction proposée | VHDL |
| process(clk) begin if rising_edge(clk) then tick <= '0'; if reset = '1' then compteur <= 0; elsif load = '1' then compteur <= valeur_chargee; elsif enable = '1' then if compteur >= 59 then compteur <= 0; tick <= '1'; else compteur <= compteur + 1; end if; end if; end if; end process; |
Suite du cours — Transition vers la suite Après les compteurs, le cours peut aborder les machines à états finis, qui utilisent des registres d’état et une logique de transition pour piloter des systèmes séquentiels complexes. |