Plutôt que de long discours, voici quelques exemples de modules écrits en VHDL. Chaque exemple introduit un nouveau type de construction / type de donnée.
Pour chaque exemple, passez la souris sur le code pour avoir des explications sur la façon dont il est construit.
Commençons par le plus simple des modules ou
presque : une porte combinatoire
AND à trois entrées. En VHDL, les modules sont
appelés "entités" (mot-clef entity).
Voici une description possible d'une porte AND à 3
entrées.
Passez la souris sur les différents éléments du code pour avoir des explications sur la signification de chaque instruction.
Voici une description possible d'un additionneur complet 1 bit.
Rappel : un additionneur complet 1 bit a trois entrées (a, b, et cin la retenue entrante), et deux sorties (le bit de somme s et le bit de retenue sortante cout).
Les bibliothèques contiennent des objets dont la visibilité externe est assurée les par le biais des paquetages ou packages.
Le paquetage std_logic_1164 de la bibliothèque standard IEEE définit le type std_logic qui correspond à un type énuméré sur 9 états. Ceci étoffe le réalisme du modèle par rapport au type bit qui n'a que 2 états binaires. Par exemple les états haute imédance 'Z' et inconnu 'X' font partie de ces 9 états.
Le paquetage numeric_std
déclare et décrit des fonctions sur les variables
de type std_logic , unsigned (vecteur de std_logic non signé), signed (vecteur de std_logic signé). Parmi
celles ci figurent la plupart des fonctions
arithmétiques, de comparaison et de conversion de type.
VHDL, comme tous les HDL, permet de décrire les
système sous forme structurelle.
C'est-à-dire, au lieu de décrire un gros
système dans un seul gros
fichier, de le séparer en sous-systèmes
(sous-modules), eux-mêmes
subdivisés en sous-modules, jusqu'à obtenir des
modules facilement
compréhensibles.
Nous allons ici décrire un additionneur 4 bits en assemblant des additionneurs 1 bit. On aurait aussi pu décrire la fonctionnalité de l'additionneur 4 bits (par opposition à sa structure), cela sera vu à la fin de cet exemple-ci.
On suppose ici que le code de l'additionneur 1 bit vu
précédement est disponible dans un fichier
à part.
L'additionneur 4 bits peut être compilé (on dit aussi "analysé") indépendamment de l'additionneur 1 bit. Il est donc nécessaire d'indiquer le composant "prototype" de l'additionneur 1 bit grâce au mot clé component de façon à l'utiliser dans l'architecture
Les instances des additionneurs 1 bits sont reliés entre eux : la retenue sortante de l'un est la retenue entrante du suivant.
Pour relier les additionneurs entre eux par la chaîne de retenue, on créée un signal c correspondant à un vecteur unsigned constitué des 3 retenues internes. On aurait pu tout aussi bien déclarer 3 signaux de type std_logic.
On instancie un module de cette façon :
nom_de_l'instance composant_a_instancier port map(signaux connectés à l'instance);
Chaque instance doit avoir un nom unique.
Pour relier le ports de l'instance aux signaux du module courant, on utilise ici la connexion par position : le premier signal est relié au premier port, le deuxième au deuxième, etc.
Cette façon de faire oblige à respecter l'ordre dans lequel les ports ont été déclarés. Il existe une autre façon de relier les signaux, plus propre, elle sera étudiée plus tard.
De même il aurait été possible d'avoir une boucle de génération de code automatique qui aurait évité d'écrire 4 fois a peu près la même chose.
Exercice : plutôt que de décrire l'additionneur 4 bits sous forme d'assemblage d'additionneurs 1 bits, on aurait pu directement décrire son comportement (sa fonctionalité). En vous inspirant du code de l'additionneur 1 bit, écrivez une description concise de l'additionneur 4 bits.
Le codage comportemental de la bascule fait appel à un processus. Un processus est un enchaînement d'instructions décrivant un comportement. Il possède une liste de sensibilité spécifiant sur quel signal le processus doit s'exécuter. Le processus en soit est une instruction concurrente mais son contenu est une suite d'instructions séquentielles. Il peut être utilisé aussi bien pour la logique séquentielle que pour la logique combinatoire.
Dans le cas de la logique synchrone faisant des calculs au rythme d'une horloge clk, la syntaxe est donc
process(clk).
Enfin, le corps du processus, codé entre begin et end, contient les instructions à exécuter. Pour une bascule D, cela consiste à recopier la valeur de l'entrée sur la sortie.La liste de sensibilité doit donc contenir ces 2 signaux :
process(clk, resetn)
Conformément à l'utilisation du reset asynchrone sur les bascule, le signal resetn est prioritaire par rapport au front d'horloge, il faut donc commencer le codage du processus par l'instruction
if resetn='0' then
Il aurait été possible de mettre à 0 q_int par l'instruction q_int <= "00000000"; mais l'instruction q_int <= (others=> '0'); est plus générique. Il faut ensuite indiquer la condition sur le front montant de l'horloge pour déclencher le comptage :
elsif rising_edge(clk) then
Il faut noter que l'instruction de comptage s'effectue en additionnant un signal unsigned sur 8 bit avec un entier :
q_int <= q_int +1
qui est une opération définie dans le paquetage numeric_std de IEEE. L'addition entre un unsigned et un std_logic n'est pas définie (donc l'écriture q_int <= q_int + '1' n'est pas possible)
Après avoir décalré le signal
reg est
nécesaire pour définir les 8 bascules
internes nécessaires au décalage, un processus
synchrone (donc sensible à clk) est utilisé.
Les instructions d'affectation utilisées dans
le processus
sont exécutées séquentiellement.
L'execution consiste à remplir un
échéancier
d'affectation et non à affecter immédiatement le
signal.
Les affectations
des signaux sont différées
elles ne prendront effet qu'une fois que tous les processus auront
été
exécutés, c'est à dire un temps
symbolique après le front montant d'horloge
dans notre cas.
reg <= reg(6 downto 0) & d_in;
La génération de la sortie match est un processus combinatoire, de même pour d_out, qui est équivalent à reg(7).
Ce module comporte en tout 3 processus :
Commençons par un premier exemple. Ouvrez un
terminal (xterm),
récupérez l'archive vhdl_first_steps.tgz en
cliquant sur ce lien, et sauvez-la dans votre
répertoire de
travail.
Décompressez cette archive (tar
zxvf
vhdl_first_steps.tgz), et placez-vous dans le
répertoire du
premier exemple (cd
ex1). Vous y trouverez deux fichiers :
Le fichier full_add.vhd ne contient que la description de l'additionneur. Pour vérifier que cette description est correcte, on peut en effectuer une simulation. Pour cela nous allons lui présenter des vecteurs d'entrée soigneusement choisis, et vérifier que les sorties correspondent bien à nos attentes.
Pour cela, nous allons modéliser (en vhdl toujours) un environnement de test. Pour cela, ouvrez le fichier testbench.vhd.
Comme pour l'additionneur, l'environnement de test est un module. Mais ce module n'a ni entrées ni sorties : il est autonome, produit lui-même les signaux à envoyer à l'additionneur, et vérifie que les sorties de l'additionneur sont bien correctes, qui correspond au schéma suivant :
Environnement de test de l'additionneur.
Nous ne testerons pas toutes les combinaisons d'entrées possibles, mais juste 4 d'entre elles. Entre chaque combinaison, nous ferons une pause (virtuelle) de 5ns, donnée par l'instruction wait for 5ns;
Notez qu'il n'y a pas de liste de sensibilité car le processus utilise l'instruction wait pour spécifier les points d'arrêt. Dans ce ca c'est lla valeur du temps physique time qui est utilisé comme point d'arrêt du processus.
Le module additionneur et son environnement de test étant écrit, reste à lancer effectivement la simulation. Pour cela, nous allons utiliser le simulateur vhdl modelsim.
Dans un premier temps il faut analyser (compiler) les fichiers. Avant toute chose il faut créer la bibliothèque work qui est la bibliothèque par défaut qui va contenir les résultats de compilation. Placez-vous dans le répertoire contenant les fichiers testbench.vhd et full_add.vhd et lancez la commande :
> vlib work
La compilation se fait avec les commandes suivantes :
> vcom full_add.vhd
> vcom testbench.vhd
Corriger d'éventuelles erreur avant de passer à la simulation.
Il faut maintenant créer un simulateur de l'entité "test" de plus haut niveau décrite dans testbench.vhd.
Pour cela lancez la commande
> vsim test
L'élaboration de test est effectuée et la fenêtre ModelSim apparaît pour lancer la simulation en graphique.
A partir de la console ModelSim, Utilisez la commande suivante pour demander le chronogramme des signaux de test :
VSIM > add wave *
Une fenêtre contenant les signaux à tracer apparaît de façon à visualiser les chronogrammes. Lancez l'execution de la simulation pendant 20 ns par
VSIM > run 20 ns
Examinez les chronogrammes. Sont ils corrects ?
Vous pouvez vous reporter au didacticiel ModelSim simplifié pour utiliser les fonctionnalités de base de la simulation en mode graphique.
Dans le répertoire ex2, vous trouverez un autre exemple de système vhdl_second_exo.tgz en cliquant sur ce lien. Nous reprenons le code de l'additionneur 4 bits, mais sous 2 formes :
Dand l'environnement de test, testbench.vhd, les 2 types d'additionneur sont instanciés et vérifiés.
Vous avez vu ici quelques exemples de modules en vhdl, comment instancier un composant et lui fournir rapidement quelques vecteurs de test.
L'objectif des prochains chapitres est d'étudier plus précisément l'utilisation et l'organisation du langage, les différents types de données disponibles, la façon de décrire structurellement un système (entité, architecture, signaux, ports), puis fonctionnellement (processus, événements).
Déclaration de l'entité
On commence par déclarer ce qu'on va décrire, ici une "entité" (un module, un bloc, un composant) :Le mot-clef entity est suivi du nom (ici AND_3), et de la liste des entrées-sorties du module
.
Ces entrées-sorties sont appelées ports. L'ordre des entrées-sorties n'a pas d'importance.
Contrairement au C et à Verilog, ll n'y a pas de distinction entre les majuscules et minuscules.
Puis viennent les déclarations du sens des ports :
il faut noter que chaque port est typé. Ici les ports sont de type bit , c'est dire qu'ils peuvent prendre 2 états binaires. Au passage, on doit aussi déclarer la taille des ports. Par défaut, ils sont sur 1 bit. Pour un bus d'entrée A sur 8 bits, on aurait ce style de déclaration :
Description de l'architecture
En VHDL le comportement du composant est décrit dans une Architecture contenant la partie interne du module. L'architecture doit être nommée, ici "arc" , ce qui permet de définir plusieurs architectures pour la même entité. Le code est défini entre les mots-clef begin et endl'instruction <= est une instruction d'affectation qui se dit aussi "reçoit" permettant d'exprimer la causalité du calcul entre les entrées et la sortie combinatoire. Le mot-clef = existe aussi mais est utilisé comme opérateur de comparaison entre 2 variables.
Les expressions logiques utilisables sont propres à chaque type de variable. Pour le type prédéfini bit, les opérateurs utilisables sont les mêmes qu'en C :