TD3 VHDL Compteurs Et Registres - Wikilivres
Maybe your like
| TD2 VHDL et logique programmable | << | Conception et VHDL | >> | TD4 VHDL Logique Sequentielle |
La logique abordée dans ce chapitre est parfois appelée logique séquentielle régulière.
| Définition |
| On appelle logique séquentielle régulière toute logique séquentielle pour laquelle le calcul de l'état futur en fonction de l'état présent s'exprime facilement avec des opérateurs simples et classiques, par exemple l'opérateur addition pour décrire un compteur. |
Les compteurs sont des éléments très utiles en VHDL. Il permettent de gérer tout ce qui est temporisation et évidemment le comptage.
Le compteur simple
[modifier | modifier le wikicode]Il est possible d'utiliser un style "case when" (présenté en début de ce livre) pour programmer un compteur. Cela devient vite fastidieux cependant, lorsque le nombre de bits du compteur augmente.
Exercice 1
[modifier | modifier le wikicode]Combien d'états comporte un compteur de n bits et donc combien de lignes pour chacun des « case » ? Application numérique : prendre n=16.
Solution de l'exercice 1On notera ** la puissance : 2**n signifie 2 puissance n.
Il possède 2**n états. Pour n=16 cela fait 65536 états donc autant de lignes dans votre compteur. Bonne chance pour l'écriture du programme, on se retrouve ... dans quelques mois. Si c'est dans trois mois, vous avez 655 lignes à écrire par jour... même les vieux instituteurs n'utilisaient pas de punitions si longues...
Eviter une programmation trop fastidieuse
[modifier | modifier le wikicode]L'idéal serait donc de pouvoir écrire quelque chose du style
compteur<=compteur+1;Cela peut se faire en respectant les conditions suivantes :
- utilisation de la librairie IEEE 1164
- utilisation de la librairie IEEE ARITH
- utilisation de la librairie IEEE UNSIGNED
- déclaration de compteur comme std_logic_vector
Avec XILINX cela se fait avec les lignes (devant chacune des entités concernées) :
libraryieee; useieee.std_logic_1164.all; useieee.std_logic_arith.all;-- WARP : use work.std_arith.all; useieee.std_logic_unsigned.all;Mais cette façon de faire n'est pas portable comme le montre le commentaire ci-dessus. Elle reste pourtant très simple par rapport à la façon portable que l'on va présenter maintenant. Les différences commencent par l'utilisation d'autres librairies. Il s'agit maintenant d'utiliser :
libraryieee; useieee.std_logic_1164.all; useieee.numeric_std.all;Jusque là les changements sont limités mais tout commence à se dégrader avec la suite : remplacer systématiquement
compteur<=compteur+1;par
compteur<=std_logic_vector(unsigned(compteur)+1);Oui, nous savons bien que c'est douloureux, mais comme on dit, on a rien sans rien.
Remarque : La bonne solution est en fait de déclarer le signal de la manière suivante :
signalcompteur:unsigned;et de l'incrémenter avec
compteur<=compteur+1;De cette façon votre code sera portable car respectant le standard défini par l'IEEE (malgré leurs noms les bibliothèques ieee.std_logic_arith, ieee.std_logic_unsigned et ieee.std_logic_signed ne font pas partie du standard IEEE mais sont des extensions propriétaires développées par Synopsys et dont l'implémentation peut varier suivant les outils de développement).
Voici schématiquement le calcul de l'état futur en fonction de l'état présent pour un compteur :
Remarquez que la fonction de calcul s'écrit de manière très simple à l'aide de l'opérateur d'addition.
Exercice 2
[modifier | modifier le wikicode]Vous disposez d'une horloge rapide et vous voulez en réaliser une plus lente dont la fréquence est divisée par 32768. Proposez un compteur avec comme entrée horloge et comme sortie h_lente (toutes deux sur un bit). Le compteur intermédiaire sera réalisé par un signal.
Solution de l'exercice 2Il y a une toute petite subtilité dans cet exercice car le poids 0 divise déjà par 2. Donc le poids 1 par 4 et le poids n par 2**n+1. Comme on veut une division par 32768=2**15, il nous faut un poids de 14, d'où le programme ci-dessous :
-- compteur/timer libraryieee; useieee.std_logic_1164.all; useieee.std_logic_arith.all; useieee.std_logic_unsigned.all; entitylentis port(horloge:instd_logic; h_lente:outstd_logic); endlent; architecturealentoflentis signalcompteur:std_logic_vector(14downto0); begin --division de l'horloge par 32768 process(horloge)begin if(horloge'eventandhorloge='1')then compteur<=compteur+1; endif; endprocess; h_lente<=compteur(14); endalent;Possibilité d'utiliser un signal de type integer
[modifier | modifier le wikicode]Plutôt que de déclarer des std_logic_vector vous pouvez utiliser le type integer. Dans ce cas le compilateur peut avoir des problèmes pour trouver le nombre de bits nécessaire pour le compteur. Il faudra donc utiliser une comparaison supplémentaire :
architecturea_cmptofcmptis signalCount:integer:=0; processbegin if(Clk'eventandClk='0')then if(Count=7)thenCount<=0;-- sur 3 bits elseCount<=Count+1; endif; endif; endprocess; enda_cmpt;Nous n'utiliserons pas ce type de programmation dans ce document. Il est juste donné par souci d'exhaustivité.
Compteur avec Remise à Zéro (RAZ)
[modifier | modifier le wikicode]L'entrée RAZ( Remise à Zéro) (Reset en anglais) sur un compteur est une entrée qui permet de mettre la valeur du compteur à 0. Elle peut être synchrone (prise en compte seulement sur front d'horloge) ou asynchrone. Pour la suite de la section nous utiliserons l'entité que l'on donne maintenant :
libraryieee; useieee.std_logic_1164.all; useieee.std_logic_arith.all; useieee.std_logic_unsigned.all; ENTITYCompteurIS PORT( clk,raz:INstd_logic; q:BUFFERstd_logic_vector(3downto0)); ENDCompteur;Cette entité va nous permettre de présenter la technique synchrone et asynchrone (process seulement) :
-- ******* methode synchrone ********** PROCESS(clk)BEGIN IFclk'eventandclk='1'THEN IFraz='1'THEN q<=(OTHERS=>'0');-- ou q <= "0000"; ELSE q<=q+1; ENDIF; ENDIF; ENDPROCESS;et
-- ******** methode asynchrone ********* PROCESS(clk,raz)BEGIN IFraz='1'THEN q<=(OTHERS=>'0');-- ou q <= "0000"; ELSIFclk'eventandclk='1'THEN q<=q+1; ENDIF; ENDPROCESS;On peut remarquer que la liste de sensibilité n'est pas la même dans les deux cas : "clk" pour le synchrone et "clk,raz" pour l'asynchrone.
Remarque sur le type BUFFER : Xilinx déconseille d'utiliser le type BUFFER dans une entité, particulièrement quand il s'agit d'un signal interne au FPGA (pas une sortie physique). Il conseille plutôt d'utiliser un signal pour compter et une sortie spécifique pour sortir le ou les bits utiles. Pour l'initialisation synchrone, par exemple, le programme complet sera la suivant :
libraryieee; useieee.std_logic_1164.all; useieee.std_logic_arith.all; useieee.std_logic_unsigned.all; ENTITYCompteurIS PORT( clk,raz:INstd_logic; qs:OUTstd_logic_vector(3downto0));-- sortie véritable ENDCompteur; ARCHITECTUREaCmptOFCompteurIS SIGNALq:std_logic_vector(3downto0);-- signal intermédiaire BEGIN -- RAZ synchrone PROCESS(clk)BEGIN IFclk'eventandclk='1'THEN IFraz='1'THEN q<=(OTHERS=>'0'); ELSE q<=q+1; ENDIF; ENDIF; ENDPROCESS; -- toujours faire : qs<=q; ENDaCmpt;On s'efforcera de respecter cette mise en garde par la suite.
Exercice 3
[modifier | modifier le wikicode]Réaliser un compteur avec SET et RESET synchrones et asynchrones.
Modifier ce compteur pour qu'il compte jusqu'à 24.
Solution de l'exercice 3Voici la solution asynchrone :
PROCESS(clk,raz,set)BEGIN IFraz='1'THEN q<=(OTHERS=>'0'); ELSIFset='1'THEN q<=(OTHERS=>'1'); ELSIFclk'eventandclk='1'THEN q<=std_logic_vector(unsigned(q)+1); ENDIF; ENDPROCESS;et le compteur qui compte jusqu'à 24 (compris) sur 5 bits donc :
PROCESS(clk,raz,set)BEGIN IFraz='1'THEN q<=(OTHERS=>'0'); ELSIFset='1'THEN q<=(OTHERS=>'1'); ELSIFclk'eventandclk='1'THEN IFunsigned(q)<25then q<=std_logic_vector(unsigned(q)+1); ELSE q<=(OTHERS=>'0'); ENDIF; ENDIF; ENDPROCESS;Compteur avec chargement parallèle
[modifier | modifier le wikicode]Le chargement parallèle est en général asynchrone dans les circuits existants. Nous allons le conserver comme tel :
libraryieee; useieee.std_logic_1164.all; useieee.std_logic_arith.all; useieee.std_logic_unsigned.all; ENTITYCompteurIS PORT( clk,load:INstd_logic; qs:OUTstd_logic_vector(3downto0); qe:INstd_logic_vector(3downto0)); ENDCompteur; ARCHITECTUREacmptOFCompteurIS SIGNALq:std_logic_vector(3downto0); BEGIN qs<=q; PROCESS(clk,load)BEGIN IFload='1'THEN q<=qe; -- ou q<=31; valeur predefinie ELSIFclk'eventandclk='1'THEN q<=q+1; ENDIF; ENDPROCESS; ENDacmpt;Compteur 4 bits BCD avec validation d'horloge
[modifier | modifier le wikicode]Pour terminer ce chapitre on présente un compteur BCD, c'est à dire qui compte de 0 à 9 qui est emprunté au WikiBook anglais VHDL for FPGA Design.
libraryIEEE; useIEEE.STD_LOGIC_1164.ALL; useIEEE.STD_LOGIC_ARITH.ALL; useIEEE.STD_LOGIC_UNSIGNED.ALL; entityCounter2_VHDLis port(Clock_enable:instd_logic; Clock:instd_logic; Reset:instd_logic; Output:outstd_logic_vector(3downto0)); endCounter2_VHDL; architectureBehavioralofCounter2_VHDLis signaltemp:std_logic_vector(3downto0); begin process(Clock,Reset)begin ifReset='1'then temp<="0000"; elsif(Clock'eventandClock='1')then ifClock_enable='0'then iftemp="1001"then temp<="0000"; else temp<=temp+1; endif; else temp<=temp; endif; endif; endprocess; Output<=temp; endBehavioral;Résultats de simulation
[modifier | modifier le wikicode]
Vous pouvez remarquer que dans cet exemple c'est Output(3) qui est le poids faible, ce qui est conforme à sa déclaration "0 to 3". Je préfère quant à moi utiliser un "3 downto 0" ce qui laisse le poids faible avec le numéro 0.
Compteur BCD cascadable
[modifier | modifier le wikicode]Il n'est pas difficile de trouver le code correspondant sur Internet. Voici par exemple :
-- Fichier : compteur_bcd10.vhdl -- Description : Compteur BCD de 0 a 9 libraryIEEE;-- On inclus la librairie IEEE useIEEE.std_logic_1164.all; useIEEE.STD_LOGIC_ARITH.ALL; useIEEE.STD_LOGIC_UNSIGNED.ALL; ENTITYcompteur_bcd10IS-- Définition des entrées/sorties -- !!!!! ATTENTION le poids fort est en 0 !!!!!!!!!!!! PORT(clk,en,clr:INstd_logic; rco:OUTstd_logic; q:OUTINTEGERRANGE0TO9); ENDcompteur_bcd10; ARCHITECTUREbehavOFcompteur_bcd10IS SIGNALcnt:INTEGERRANGE0TO9;-- signal interne BEGIN q<=cnt;-- q, la sortie vaut la valeur du compte actuel en tout temps PROCESS(clk,clr)-- Process sensible à l’horloge et au “clear” BEGIN IF(clr='1')THEN-- “clear” asynchrone cnt<=0; --rco <= ’0’; ELSIF(clk'EVENTANDclk='1')THEN-- au front montant IF(en='1')THEN-- si enable est à 1 IF(cnt=9)THEN-- Si on atteint 9 on fait un rco cnt<=0;-- et on remet le compteur a 0 --rco <= ’1’; ELSE-- Sinon on compte cnt<=cnt+1; --rco <= ’0’; ENDIF; ENDIF; ENDIF; ENDPROCESS; PROCESS(cnt,en)BEGIN IFcnt=9anden='1'then rco<='1'; ELSE rco<='0'; ENDIF; ENDPROCESS; ENDbehav;Compteur décompteur BCD cascadable
[modifier | modifier le wikicode]Une entrée supplémentaire doit être ajoutée. On l'appellera "ud" pour "Down/up" qui permet de choisir entre un comptage et un décomptage. Voici par exemple :
-- description du composant compteur/decompteur cascadable libraryieee; useieee.std_logic_1164.all; useIEEE.STD_LOGIC_ARITH.ALL; useIEEE.STD_LOGIC_UNSIGNED.ALL; entitycompteurbcdisport( clk:instd_logic; en:instd_logic; init:instd_logic; --ud=1 up, ud=0 down ud:instd_logic; enout:outstd_logic; s:outstd_logic_vector(3downto0) ); endentity; architecturebehaviorofcompteurbcdis signaln:std_logic_vector(3downto0); begin changement:process(clk)begin ifclk'eventandclk='1'then ifinit='1'then n<=(others=>'0'); elsifen='1'then ifud='1'then--up ifn<9then n<=n+1; else n<=(others=>'0'); endif; else-- down ifn>0then n<=n-1; else n<="1001"; endif; endif; endif; endif; endprocess; -- gestion enout enout<='1'whenen='1'andud='1'andn=9else '1'whenen='1'andud='0'andn=0else '0'; -- toujours s<=n; endbehavior;Comme vous pouvez le voir dans le code, ud=1 permet un comptage tandis que ud=0 permet un décomptage.
Nous avons choisi une autre manière de gérer la sortie "enout" que dans la section précédente : utilisation d'un when else au lieu d'un if. Cela permet d'éviter le process. En effet la gestion avec un if devrait s'écrire :
enableout:process(n,en,ud) begin ifen='1'then ifud='1'andn=9then enout<='1'; elsifud='0'andn=0then enout<='1'; else enout<='0'; endif; else-- ajouté 4/5/2011 pour eviter comptage intempestif sur dizaine enout<='0'; endif; endprocess;En voici pour finir une version avec la partie combinatoire réalisée avec un with select when :
libraryIEEE; useIEEE.STD_LOGIC_1164.ALL; useIEEE.STD_LOGIC_ARITH.ALL; useIEEE.STD_LOGIC_UNSIGNED.ALL; entityCounterBCDis port(EN:instd_logic; Clock:instd_logic; Reset:instd_logic; du:instd_logic; ENO:outstd_logic; Output:outstd_logic_vector(3downto0)); endCounterBCD; architectureBehavioralofCounterBCDis signalcmpt:std_logic_vector(3downto0); signals_en_cmpt:std_logic_vector(5downto0); beginprocess(Clock,Reset) begin ifReset='1'then cmpt<="0000"; elsif(rising_edge(Clock))then ifEN='1'then ifdu='1'then ifcmpt="1001"then cmpt<="0000"; else cmpt<=cmpt+1; endif; else ifcmpt="0000"then cmpt<="1001"; else cmpt<=cmpt-1; endif; endif; endif; endif; endprocess; Output<=cmpt; s_en_cmpt<=en&du&cmpt; withs_en_cmptselect ENO<='1'when"111001", '1'when"100000", '0'whenothers; endBehavioral;Temporisation
[modifier | modifier le wikicode]L'application la plus courante des compteurs est la temporisation.
Exercice 4 : réalisation des signaux de synchronisation d'un écran VGA
[modifier | modifier le wikicode]On désire réaliser les deux signaux hsynch et vsynch nécessaire au bon fonctionnement d'un écran VGA. Ils sont caractérisés par les durées suivantes :

On peut distinguer sur cette spécification un exemple de signaux rouge, vert et bleu en haut, puis les deux signaux qui nous intéressent vraiment "hsynch" et "vsynch". Techniquement la réalisation de ces deux signaux est faite à l'aide de deux compteurs de la manière suivante :

On vous demande de répondre aux questions suivantes :
1°) Calculer la période de l'horloge à 25MHz (P88).
2°) Le compteur 0 -> XXX commence à compter au début des 25,6 μs. Jusqu'à combien doit-il compter pour réaliser ces 25,6 μs?
3°) Il lui faut réaliser ensuite 0,64 μs, jusqu'à combien doit-il compter ? Il lui faut réaliser ensuite 3,8 ms, jusqu'à combien doit-il compter ? Il lui faut réaliser ensuite la période complète 31,75 ms, jusqu'à combien doit-il compter ? (C'est la valeur de XXX à un près) On arrondit en général XXX à 799. Déduire de tout cela la valeur de ZZZ et TTT.
4°) Ce sont les hsynch qui incrémentent le compteur 0->YYY. Quelle est la période correspondante (si l'on prend XXX=799) ?
5°) Combien de temps dure la période des 480 lignes avec le résultat de la question 4° (à comparer à 15,24 ms de la spécification VGA).
6°) À l'aide du résultat de 4°) trouver de combien doit compter le compteur pour réaliser le temps de 0,35 ms.
7°) À l'aide du résultat de 4°) trouver de combien doit compter le compteur pour réaliser le temps de 64 ms.
8°) À l'aide du résultat de 4°) trouver de combien doit compter le compteur pour réaliser la période complète de 16,6 ms. Est-il normal d'arrondir à 520 ?
9°) En déduire les valeurs de UUU et VVV ?
Solution de l'exercice 41°) Fh = 25 Mhz => Th = 40 ns
2°) Nb compteur pour 25,6 μs : 25,6 μs/ 40 ns = 640
3°) Nb compteur pour 0,64 μs : 0,64 μs/ 40 ns = 16 Période 31,75 μs / 40 ns = 793,75 arrondi à 800 donc XXX=799.
Durée de la valeur 0 : 3,8 μs soit 95.
Donc en horizontal : 0 <--------->639 <--->655 (639+16) <-->750(655+95)<--->799 donc ZZZ=654 et TTT=751 (car comparaisons strictes) XXX=799 soit 1100011111 en binaire, ce qui nécessite 10 bits.
4°) Période verticale : Tv = 40 ns x 800 = 32 μs
5°) Le signal doit être périodique à 16,6 ms soit un compteur 518,75 arrondi à 520 donc YYY=519 480 lignes durée 480 x 32 μs = 15,36 ms (au lieu de 15,24 ms)
6°) Le temps de 0,35 ms est réalisé par comptage 0,35ms/32 μs = 11
7°) Durée de la valeur 0 pendant 64 μs est réalisée avec comptage de 2
8°) 0 <-------->479 <----->490 (479+11) <---->492 (490+2)<---->519
519 est égal à 1000000111 en binaire (encore 10 bits)
9°) donc UUU=489 et VVV=493 (car comparaisons strictes).
Pour votre information, les valeurs typiques que j'utilise dans mes projets sont : XXX=799, ZZZ=658, TTT=756, et YYY=524, UUU=492, VVV=495
-- Horiz_sync ------------------------------------__________-------- -- H_count 0 640 659 755 799 -- Vert_sync -----------------------------------------------_______------------ -- V_count 0 480 493-494 524Remarque 1 : Il faut éviter une réalisation avec deux horloges différentes comme c'est le cas dans le schéma proposé ici. Pour votre information vous pouvez trouver un cours sur VHDL et particulièrement dans le chapitre Interfaces VGA et PS/2 la bonne façon de procéder. Cela ne change en rien les valeurs qui sont données ici.
Remarque 2 : Le chapitre Interfaces VGA et PS/2 du cours WIKIVERSITE:VHDL présente un exercice donnant le code VHDL réalisant cette synchronisation.
Registre à décalage
[modifier | modifier le wikicode]L'opérateur de concaténation "&" est utile pour ce genre de registre. Voici un exemple de registre à décalage vers la droite (vers les poids faible) : le bit de poids faible (indice 0) est perdu, le bit d'entrée est ajouté aux bits restants (indices 7 à 1).
libraryIEEE; useIEEE.STD_LOGIC_1164.ALL; entityShiftRegis port(clk,entree:instd_logic; q:outstd_logic_vector(7downto0)); endShiftReg; architectureaShiftRegofShiftRegis signaldataq:std_logic_vector(7downto0); begin process(clk)begin ifclk'eventandclk='0'then -- c'est ici que l'on concatène le bit d'entrée au registre décalé dataq<=entree&dataq(7downto1); endif; endprocess; process(dataq)begin q<=dataq; endprocess; endaShiftReg;Autre exemple de registre à décalage 4 bits
[modifier | modifier le wikicode]Cet exemple est encore tiré du WikiBook VHDL for FPGA Design.
libraryIEEE; useIEEE.STD_LOGIC_1164.ALL; useIEEE.STD_LOGIC_ARITH.ALL; useIEEE.STD_LOGIC_UNSIGNED.ALL; entityShift_register_VHDLis port(Clock:instd_logic; L,w:instd_logic; Output:outstd_logic_vector(3downto0); Input:instd_logic_vector(3downto0)); endShift_register_VHDL; architectureBehavioralofShift_register_VHDLis signaltemp:std_logic_vector(3downto0); begin process begin waituntilClock'eventandClock='1'; ifL='1'then temp<=Input; else foriin0to2loop temp(i)<=temp(i+1); endloop; temp(3)<=w; endif; endprocess; Output<=temp; endBehavioral;Résultats de simulation
[modifier | modifier le wikicode]
Et maintenant quelques définitions avant de passer à l'exercice suivant.
| Définition |
|
Nous proposons une méthode qui utilise un registre à décalage à travers un exercice.
Exercice 5 (filtrage de rebonds et/ou aléas)
[modifier | modifier le wikicode]L'architecture peut être décrite comme suit : un registre à décalage 4 bits sensible aux fronts descendants de T9, une bascule D qui mémorise l'état de notre sortie et une partie combinatoire qui génère un 1 à l'entrée de la bascule D dès que le registre est rempli par 4 bits à 1 et que sortie vaut 0.

Cette figure peut être expliquée de la manière suivante :
- si l'on est avec une horloge à 0 (sortie de la bascule D) seuls une série de 1 dans tout le registre peut le mettre à un.
- si l'on est avec une horloge à 1 (sortie de la bascule D) seuls une série de 0 dans tout le registre peut le mettre à zéro.
Compléter les chronogrammes ci-dessous.

Ceci termine le TD3.
| TD2 VHDL et logique programmable | << | Conception et VHDL | >> | TD4 VHDL Logique Sequentielle |
Tag » Code Vhdl Compteur Modulo 10
-
[PDF] Exemples De Code VHDL Pour Les Compteurs - UQAC
-
[PDF] VHDL – TD2
-
[VHDL] Compteur Modulo N - Forum Futura Sciences
-
Flash VHDL : Description D'un Compteur Synchrone Comptant De 0 à ...
-
VHDL En Pratique : Compteur 0 à 15 Sans Process - YouTube
-
[PDF] Les Systèmes Séquentiels : - REDS
-
Exemples De Code VHDL Pour Les Compteurs
-
[PDF] Vhdl.pdf
-
[PDF] VHDL - Logique Programmable
-
[PDF] V.Tourtchine Et M.Izouine. Initiation Au Langage VHDL. Application ...
-
[PDF] Enseignes Et Afficheurs à LED - GitHub Pages
-
[PDF] VHDL - Logique Programmable
-
[PDF] ENSL1 :