IUT de l'Indre
Département Génie Electrique et Informatique Industrielle
Module ENSL1 : Initiation au langage VHDL
Travaux Diriges
Eric PERONNIN
Chateauroux, le 13 octobre 2012
2
Table des matières
Exercice 1 : Décodeur binaire 4 bits vers a�cheur 7 segments . . . . . . . . . . . . . . . . . 5
Exercice 2 : Compteur/décompteur synchrone binaire 4 bits à reset asynchrone . . . . . . . 5
Exercice 3 : Unité arithmétique et logique 7 opérations . . . . . . . . . . . . . . . . . . . . . 10
Exercice 4 : Module de gestion des registres d'un processeur . . . . . . . . . . . . . . . . . . 13
Exercice 5 : Mémoire de programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Exercice 6 : Mémoire de données (Megawizard for Custom Megafonctions de Quartus) . . . 16
Exercice 7 : Unité d'exécution du microcontrôleur IUT . . . . . . . . . . . . . . . . . . . . . 16
4
Exercices de base
Exercice 1 : Décodeur binaire 4 bits vers a�cheur 7 segments
Objectif
A�cher les chi�res 0 à 9 et lettres allant de A à F pour représenter en hexadécimal la grandeur d'entrée
du décodeur.
Cahier des charges
Le système est totalement combinatoire. Le signal entre sous une forme vectorielle composée de 4 bits
(un std_logic_vector sur 4 bits).
Il en résulte en sortie 7 signaux scalaires représentant les di�érents segments d'un a�cheur 7 segments :
signaux a, b, c, d, e, f, g.
Travail à réaliser
• Dresser la table de vérité du décodeur.
• Décrire en VHDL le bloc fonctionnel correspondant de trois manières di�érentes.
• Tester chacune des versions du décodeur.
Exercice 2 : Compteur/décompteur synchrone binaire 4 bits à reset asynchrone
6
Problème : Processeur IUT
Ce problème propose la réalisation d'un coeur de microprocesseur tel que ceux présents dans de nombreux
microcontrôleurs. Ce processeur, désigné processeur IUT car ne reposant sur aucun modèle connu, possède
les caractéristiques suivantes :
• jeu d'instructions réduit (36 dans sa version pédagogique de base),
• mémoire de programme interne au processeur limitée à 1024 mots (10 bits d'adresse pour accéder aux
instructions),
• mémoire de données interne de 256 octets,
• calculs s'e�ectuant sur 8 bits et reposant sur l'emploi de 4 registres internes de calcul notés r0, r1, r2 et
r3(les registres sont des mémoires internes au processeur utilisées pour la réalisation de calculs divers).
Un avant goût du cours d'informatique industrielle du module I2
Introduction
Un coeur de processeur est une unité de traitement séquentielle dont le fonctionnement, rythmé par
un signal d'horloge, permet d'exécuter divers traitements élémentaires codés dans une mémoire appelée
mémoire de programme. L'ensemble des informations contenues dans la mémoire de programme constitue un
programme informatique. Les di�érents traitements élémentaires possibles reposent sur diverses instructions
pouvant nécessiter plusieurs cycles d'horloge pour être exécutées :
• a�ectations,
• opérations arithmétiques et logiques,
• comparaisons,
• sauts conditionnés ou non conditionnés vers une instruction autre que l'instruction immédiatement
suivante dans le processus séquentiel.
Ces traitements permettent de manipuler des données d'origines variées :
• mémoires internes du processeur connues sous le nom de registre,
• mémoire RAM de données adjointe au processeur et accessible en lecture et en écriture par l'intermé-
diaire des bus de données, d'adresse et de contrôle,
• mémoires de périphériques permettant des liaisons diverses avec l'environnement externe au processeur
et la réalisation de fonctions cablées spéci�ques en dehors du processeur dans le but de l'assister.
Exemple de programme - Assembleur
Voici un exemple simple de programme écrit en assembleur processeur IUT :
8
ad r e s s e s mnémoniques opérandes commentaires
0 mov r0 ,10 ; r0 <− 10 l e r e g i s t r e r0 r e ç o i t l a va l eur 10
1 mov r1 ,230 ; r1 <− 230
2 add r1 , r0 ; r1 <− r1 + r0
3 mov (25 ) , r1 ; l ' emplacement mémoire 25 r e ç o i t l a va l eur de r1
4 mov r2 , 17 ; r2 <− 17
5 sub (25 ) , r2 ; (25) <− (25) − r2
6 and r0 , (25) ; r0 <− r0 and (25)
7 jump 2 ; saute à l ' i n s t r u c t i o n de l ' ad r e s s e 2 : pc <− 2
Cet exemple montre un programme mettant en jeu quelques instructions du processeur IUT avec r0, r1,
r2 et la donnée située à l'adresse 25 de la ram comme opérandes. Le mnémonique mov permet de réaliser
des a�ectations, add assure des additions, sub des soustractions, and permet de réaliser un et logique bit
à bit alors que l'instruction jump permet de rompre le déroulement séquentiel en spéci�ant l'adresse de la
prochaine instruction à exécuter.
Présentation du jeu d'instructions
Le tableau présenté ci-dessous présente l'ensemble du jeu d'instructions du processeur IUT dans sa
version pédagogique de base (il est tout à fait envisageable de l'étendre en exploitant des possibilités de
codage laissées libres) :
Table 1 � jeu d'instructions du processeur IUT
Dans ce tableau, 16 colonnes numérotées de 0 à 15 représentent les 16 bits constituant le code machine
d'une instruction. Ces 16 bits sont les suivants (en partant du bit de poids fort) :
• alu (bit 15) indique si l'unité arithmétique et logique du processeur est utilisée (dans ce cas alu = '1' ).
• m1 et m0 (bits 14 et 13) précisent le mode d'adressage dans le cas où l'unité arithmétique et logique
est employée. Cela renseigne sur les opérandes mis en jeu, en particulier sur la nature de l'opérande de
destination et sur celle de l'opérande source.
� Pour m1 = '0' et m0 = '0', la destination est un registre et la source est un emplacement mémoire
de la mémoire de données.
9
� Pour m1 = '0' et m0 = '1', la destination est un registre et la source est une information littérale
codée sur 8 bits.
� Pour m1 = '1' et m0 = '0', la destination est un registre et la source est également un registre.
� Pour m1 = '1' et m0 = '1', la destination est un emplacement de la mémoire de données et la source
est un registre.
• op2, op1 et op0 (bits 12, 11 et 10) renseignent sur l'opération que doit réaliser l'unité arithmétique
et logique lorsque celle-ci est utilisée (les détails seront vus lors de l'étude de l'unité arithmétique et
logique au cours de l'exercice 1).
• rd1, rd0 (bits 9 et 8) indiquent le numéro de registre de destination lorsque l'instruction codée en
nécessite un.
• opérandes (bits 7 à 0) revêt di�érentes formes en fonction du mode d'adressage. La description qui suit
n'a de sens que si l'unité arithmétique et logique est utilisée :
� Pour m1 = '0' et m0 = '0', ces 8 bits donnent l'adresse dans la mémoire de données de la donnée à
exploiter comme source.
� Pour m1 = '0' et m0 = '1', les 8 bits d'opérandes fournissent l'opérande source sous la forme d'un
littéral codé sur 8 bits.
� Pour m1 = '1' et m0 = '0', seuls les bits 7 et 6 sont exploités. Ils fournissent le numéro du registre
source à utiliser lors d'une opération arithmétique ou logique.
� Pour m1 = '0' et m0 = '1', les 8 bits d'opérandes précisent l'adresse dans la mémoire de donnée de
la valeur codée sur 8 bits à exploiter comme opérande source.
Lorsque l'instruction à exécuter n'exploite pas l'unité arithmétique et logique, le découpage des 16 bits est
di�érent. Par exemple, l'instruction jump repose sur un code machine faisant apparaître le code �011111�
suivi de l'adresse de l'instruction vers laquelle e�ectuer le saut.
Codage d'un programme en langage machine
Dans la mémoire de programme, on trouve donc une liste de codes machines sur 16 bits réprésentant
chacun une instruction.
Reprenons le programme donné en exemple. A l'adresse 0, on a un mov r0,12, ce qui correspond à une
instruction de type mov rd, lit. Le code correspondant dans la mémoire de programme commencera donc
par �10100000 00001100�.
On peut appliquer cette technique de codage à chacune des instructions du programme :
ad r e s s e s codes machine mnémoniques opérandes
0 1 01 000 00 0000 1100 mov r0 ,12
1 1 01 000 01 1110 0110 mov r1 ,230
2 1 10 100 01 0000 0000 add r1 , r0
3 1 11 000 01 0001 0111 mov (25 ) , r1
4 1 01 000 10 0001 0001 mov r2 , 17
5 1 11 101 10 0001 0001 sub (25 ) , r2
6 1 00 001 00 0001 0001 and r0 , (25)
7 011111 0000000010 jump 2
Note : pour plus de lisibilité, le code machine des instructions utilisant l'unité arithmétique et logique a
été découpé par groupe de bits : le bit alu, les bits du mode d'adressage, les bits du code opération, le registre
de destination (source dans le cas d'une a�ectation vers la mémoire), puis les bits soit du registre source,
soit de l'adresse mémoire ciblée, soit du littéral à a�ecter. Dans le cas du branchement, le code machine
est proposé en 2 parties : une pour indiqué un jump et l'autre pour spéci�er l'adresse de l'instruction où
s'e�ectue le saut.
Présentation de la structure interne du microcontrôleur IUT
Pour son fonctionnement, ce processeur exploite di�érents modules interconnectés entre eux :
10
• une mémoire de programme qui contient les instructions à exécuter : cette mémoire possède une largeur
de 16 bits (elle renvoit des données sur 16 bits correspondant au codage d'une instruction à exécuter)
et une profondeur de 1024 mots (elle nécessite qu'on lui fournisse une adresse codée sur 10 bits). Le
�chier VHDL associé à la mémoire de programme s'appellera program_memory (étude et réalisation
pratique dans l'exercice 3).
• une mémoire de données utilisée pour stocker les variables du programme nommée data_memory et
réalisée à l'aide du MegaWizard Plug-in Manager de Quartus. Sa taille est limitée à 256 octets (bus
d'adresse et de données tous deux sur 8 bits). Voir l'exercice 4 pour plus de détails.
• l'unité arithmétique et logique nommée alu de l'exercice 1. C'est l'unité de calcul du processeur. Elle
travaille avec deux opérandes codés sur 8 bits, e�ectue une opération indiquée par le biais de 3 bits
(et, ou, ou exclusif, +, -) dont elle renvoit le résultat sur 8 bits
• le module de gestion des registres de calcul interne au processeur de l'exercice 2 (module registers).
• une unité d'exécution mettant en jeu di�érents processus. Elle constitue le module principal du pro-
cesseur.
La �gure 1 montre les liaisons entre les di�érents blocs constituant la version de base du microcontrôleur IUT
en intégrant les éléments propres au coeur de processeur ainsi que les mémoires de programme et de données.
Les di�érents exercices qui suivent proposent la réalisation progressive des éléments qui apparaîssent sur ce
schéma. Une grande importance sera donnée au test unitaire de chacun de ces modules a�n de garantir le
succès du fonctionnement de l'ensemble une fois constitué comme sur ce schéma.
Exercice 3 : Unité arithmétique et logique 7 opérations
Objectif
Réaliser la description d'une unité arithmétique et logique simple capable d'e�ectuer 7 opérations.
Cahier des charges
Le fonctionnement du système est totalement combinatoire.
Les entrées du module à réaliser sont :
• operand_a : premier opérande.
• operand_b : second opérande.
• operation : mot de 3 bits précisant l'opération à e�ectuer.
• reset : entrée de remise à 0.
• clk : entrée d'horloge du processeur.
• �ag_we : signal de validation pour la mémorisation des indicateurs c_out et z_out.
Les signaux de sortie sont :
• result : mot de 8 bits contenant le résultat de l'opération.
• c_out : indicateur de retenue sur 1 bit pour l'addition.
• z_out : indicateur sur 1 bit précisant que le résultat obtenu est nul.
Tableau des di�érentes opérations :
11
Figure 1 � structure du microcontrôleur IUT sous Quartus
12
Table 2 � tableau des opérations de l'unité arithmétique et logique
Remarques :
Avec les bibliothèques habituelles, les opérations arithmétiques sur des signaux de types std_logic_vector
ne sont pas possibles (on ne peut pas utiliser l'opérateur + de la somme avec des std_logic_vector). Par
contre, le paquetage numeric_std de la bibliothèque ieee propose des types, les types unsigned et signed,
tous deux basés sur des std_logic autorisant une conversion de ces types depuis et vers des std_logic_vector
de façon directe et pour lesquels les opérateurs arithmétiques ont été dé�nis.
L'exemple suivant montre comment les utiliser :
a r c h i t e c t u r e r t l o f exemple i s
s i g n a l a , b , c : s td_log ic_vector (7 downto 0 ) ;
begin
a <= "01001010";
b <= "11110000";
c <= std_log ic_vector ( unsigned ( a ) + unsigned (b) ) ;
end r t l ;
Dans cet exemple, on crée trois signaux a, b et c vectoriels de 8 std_logic de large.
Pour réaliser la somme de a et de b, on a recours à une double conversion. La première permet de convertir
a en unsigned et b en unsigned. Les deux unsigned résultants sont alors ajoutés puis convertis en retour en
std_logic_vector.
Est-il possible de faire plus simple ?
Bien sûr ... en utilisant un paquetage dé�nissant les additions sur les std_logic_vector !
l i b r a r y i e e e ;
use i e e e . s td_log i c_ar i th . a l l ;
use i e e e . std_logic_unsigned . a l l ;
use i e e e . std_logic_1164 . a l l ;
use i e e e . numeric_std . a l l ;
. . .
. . .
a r c h i t e c t u r e r t l o f somme i f
begin
a <= "01001010";
b <= "11110000";
c <= a + b ;
end r t l ;
Précision concernant la soustraction :
On peut réaliser la soustraction en utilisant l'addition du complément à 2 de la valeur à soustraire (ici
ce sera donc le complément à 2 de operand_b).
Travail exigé
1. Réalisation simple : modèle purement combinatoire ne prenant pas en charge la retenue et
le zero.
• Analyser le problème et proposer des idées pour le traitement.
13
• Coder le programme.
• Tester les di�érentes opérations à l'aide de vecteurs entrés dans un �chier de test VHDL.
2. Implémentation du zero et de la retenue.
La sortie z_out indique si le résultat d'une opération est nulle. Elle est calculée uniquement pour les
opérations 1 à 7 ce qui signi�e que son état ne doit pas changer lorsque l'opération 0 (result <= operand_b)
est sélectionnée.
La retenue c_out est calculée dans le cas des opérations arithmétiques uniquement. Lorsqu'une autre
opération est réalisée, l'état de c_out doit être conservé pour re�éter la dernière opération arithmétique
réalisée.
c_out et z_out sont donc des grandeurs mémorisées. Leur mémorisation utilise le signal d'horloge clk
d'une part et s'e�ectue lorsque l'entrée �ag_we est validée d'autre part.
Note 1 : le signal �ag_we est fourni par le module execution_unit à un moment où il est connu que les
données calculées pour c_out et z_out sont pertinentes.
Note 2 : lorsque reset = '1', les signaux c_out et z_out sont remis à 0 sans autre condition (on dit que
le reset est prioritaire).
Le calcul de c_out obéit aux équations suivantes :
• cas d'une soustraction : c_out <= operand_a7.operand_b7+operand_b7.result7+result7.operand_a7
• cas d'une addition : c_out <= operand_a7.operand_b7 + operand_b7.result7 + result7.operand_a7
Lorsque son calcul est nécessaire, z_out obéit quand à lui à l'équation suivante :
z_out <= result7.result6.result5.result4.result3.result2.result1.result0
• Implémenter ces deux indicateurs.
• Tester le fonctionnement du module �nalisé.
Exercice 4 : Module de gestion des registres d'un processeur
Objectif
Créer un module permettant de gérer les registres du futur processeur IUT.
Cahier des charges
Ce module permet de gérer 4 registres 8 bits dénommés : r0, r1, r2 et r3.
Il doit permettre de mettre à jour l'état de ces registres à partir de la sortie de l'unité arithmétique et
logique connectée à l'entrée reg_in et de conserver leurs valeurs le reste du temps.
Les entrées sont les suivantes :
• clk : horloge du processeur.
• reset : l'entrée d'initialisation du système (à la mise à 0, les 4 registres sont remis à 0 : r1 <= �00000000�
...).
• dest_reg(1..0) : mot de 2 std_logic indiquant le numéro du registre à mettre à jour (�00� pour r0 à
�11� pour r3).
• reg_in(7..0) : vecteur de 8 std_logic contenant la valeur sur 8 bits à stocker dans le registre de desti-
nation indiqué par dest_reg.
• reg_we : signal de type std_logic autorisant la mise à jour du registre spéci�é par dest_reg. Si reg_we
= '1' et qu'un front montant de l'horloge clk intervient alors le registre spéci�é par dest_reg reçoit la
valeur de reg_in.
Les sorties sont :
14
• r0(7..0), r1(7..0), r2(7..0) et r3(7..0) : les 4 registres 8 bits du processeur IUT dont l'état est mémorisé
dans l'attente d'une mise à jour indiquée par reg_we..
Fonctionnement :
• Le reset est prioritaire (donc asynchrone) et provoque la mise à 0 des di�érents registres.
• Lors d'un front montant d'horloge, si reg_we = '1' alors le registre dont le numéro est indiqué par
dest_reg reçoit la valeur présente sur l'entrée reg_in. Par exemple, au front montant d'horloge, si
reg_we = '1' et que dest_reg = �10� alors r2 <= reg_in.
Travail demandé
• Donner une table de vérité de synthèse du module registers.
• Décrire ce module en langage VHDL (on appellera l'entité registers et le �chier VHDL associé sera
nommé registers.vhd)
• Tester le module dans di�érents cas de �gures choisis su�samment exhaustifs.
Exercice 5 : Mémoire de programme
Il s'agit de décrire le module program_memory en langage VHDL.
Dans le respect des règles, il est bien clair que ce module devrait reposer sur une mémoire ROM telle
que celle que propose Altera dans son Megawizard. En e�et, dans le cas d'une implémentation par Quartus
avec un bloc dédié, c'est une portion de la mémoire embarquée dans le FPGA (les fameux M4K) qui sera
généralement exploitée. A contrario, la description combinatoire imposée dans cet exercice utilisera des
éléments logiques uniquement, consommant ainsi en grand nombre des portes logiques qui auraient pu servir
à toute autre chose (le but d'un M4K est exclusivement d'être exploité dans une fonction de mémoire).
Ainsi, le module program_memory est un programme VHDL combinatoire reposant sur une table de
vérité renvoyant un code machine sur 16 bits en sortie pour une adresse sur 10 bits fournie en entrée.
Dans le cas du programme fourni en exemple, la table de vérité mise en place est la suivante :
prg_address prg_data
0 1010 0000 0000 1100
1 1010 0001 1110 0110
2 1101 0001 0000 0000
3 1110 0001 0001 0111
4 1010 0010 0001 0001
5 1111 0110 0001 0001
6 1000 0100 0001 0001
7 0111 1100 0000 0010
Le codage de cette table de vérité en l'état présente un problème de lisibilité évident : qui se souviendra
que l'adresse 0 renferme une instruction mov r0, 12 ?
A des �ns d'essais et en attendant de disposer d'un logiciel assembleur capable de passer d'un source
contenant des mnémoniques à un �chier exécutable contenant une suite de codes machines, on aura très cer-
tainement besoin d'un moyen permettant de spéci�er les codes machines dans le �chier program_memory.vhd
avec davantage de souplesse.
Le code présenté ci-dessous propose une approche exploitant des constantes et l'opérateur de concaténa-
tion pour réaliser un codage plus explicite :
15
l i b r a r y i e e e ;
use i e e e . numeric_std . a l l ;
use i e e e . std_logic_1164 . a l l ;
use i e e e . s td_log i c_ar i th . a l l ;
use i e e e . std_logic_unsigned . a l l ;
e n t i t y program_memory i s port (
prg_address : in std_log ic_vector (9 downto 0 ) ;
prg_data : out std_log ic_vector (15 downto 0)
) ;
end en t i t y ;
a r c h i t e c t u r e r t l o f program_memory i s
constant r0 : s td_log ic_vector (1 downto 0) := "00" ;
constant r1 : s td_log ic_vector (1 downto 0) := "01" ;
constant r2 : s td_log ic_vector (1 downto 0) := "10" ;
constant r3 : s td_log ic_vector (1 downto 0) := "11" ;
constant i_reg_ram : std_log ic_vector (2 downto 0) := "100" ;
constant i_reg_reg : s td_log ic_vector (2 downto 0) := "110";
constant i_reg_l i t : s td_log ic_vector (2 downto 0) := "101";
constant i_ram_reg : s td_log ic_vector (2 downto 0) := "111" ;
constant op_mov : std_log ic_vector (2 downto 0) := "000" ;
constant op_and : std_log ic_vector (2 downto 0) := "001" ;
constant op_or : s td_log ic_vector (2 downto 0) := "010" ;
constant op_xor : s td_log ic_vector (2 downto 0) := "011" ;
constant op_add : std_log ic_vector (2 downto 0) := "100" ;
constant op_sub : std_log ic_vector (2 downto 0) := "101";
constant op_not : s td_log ic_vector (2 downto 0) := "110" ;
constant op_cmp : std_log ic_vector (2 downto 0) := "111" ;
constant i_jump : std_log ic_vector (5 downto 0) := "011111";
begin
proce s s ( prg_address )
begin
case prg_address (9 downto 0) i s
when "0000000000" =>
prg_data <= i_reg_l i t & op_mov & r0 & std_log ic_vector ( to_unsigned ( 1 2 , 8 ) ) ;
when "0000000001" =>
prg_data <= i_reg_l i t & op_mov & r1 & std_log ic_vector ( to_unsigned ( 2 3 0 , 8 ) ) ;
when "0000000010" =>
prg_data <= i_reg_reg & op_add & r1 & r0 & "000000";
when "0000000011" =>
prg_data <= i_ram_reg & op_mov & r1 & std_log ic_vector ( to_unsigned ( 2 5 , 8 ) ) ;
−−−− A vous de poursu iv re l e codage en exp l o i t an t ce s premiers exemples .
−−
when othe r s =>
prg_data <= "0000000000000000";
end case ;
end proce s s ;
end r t l ;
Travail demandé
• Ajouter le module program_memory.vhd à votre projet.
• Reprendre l'exemple fourni ci-dessous et le compléter.
• Proposer une procédure et la mettre en oeuvre pour valider le bon fonctionnement de la mémoire de
programme.
16
Exercice 6 : Mémoire de données (Megawizard for Custom Megafonctions de
Quartus)
Exercice 7 : Unité d'exécution du microcontrôleur IUT
Objectif
Réaliser l'unité d'exécution d'un processeur 8 bits disposant d'un jeu d'instructions réduit : le microcon-
trôleur IUT.
La réalisation de ce module va comporter de nombreuses étapes au cours desquelles di�érents processus
seront implémentés.
Présentation de l'unité d'exécution (execution_unit dans notre projet) - Fonc-
tionnement
L'unité d'exécution est le module qui va prendre en charge les instructions en provenance de la mémoire
de programme et dispatcher les informations utiles aux di�érents modules du processeur pour mener à bien
l'exécution de l'instruction. En particulier, elle comunique à l'unité arithmétique et logique ses di�érents
signaux d'entrée ; elle réalise l'interface avec la mémoire de données et elle assure la mémorisation des
registres en cas d'évolution de leur valeur lors de l'exécution d'une instruction.
Reprenons une version allégée du premier exemple de programme :
ad r e s s e s mnémoniques opérandes commentaires
0 mov r0 ,12 ; r0 <− 12 l e r e g i s t r e r0 r e ç o i t l a va l eur 12
1 mov r1 ,230 ; r1 <− 230
2 add r1 , r0 ; r1 <− r1 + r0
3 mov (25 ) , r1 ; l ' emplacement mémoire 25 r e ç o i t l a va l eur de r1
4 and r0 , (25) ; r0 <− r0 and (25)
5 jump 2 ; saute à l ' i n s t r u c t i o n de l ' ad r e s s e 2 : pc <− 2
L'exécution de ce programme s'e�ectue en de nombreuses étapes :
• Phase d'initialisation
� A la mise sous tension, une impulsion de reset est transmise au processeur IUT a�n de l'initialiser.
� Les registres (r0, r1, r2 et r3) sont tous remis à 0 ; le registre pc pour program counter est également
initialisé à 0 pour pointer vers l'instruction située à l'adresse 0 de la mémoire de programme (i.e. la
première instruction de notre programme).
� L'exécution d'une instruction s'e�ectuant systématiquement en 4 cycles d'horloge, un compteur de
cycle re�étant le niveau d'avancement dans l'exécution de l'instruction en cours est initialisé à un
état Q0 (il évoluera ensuite vers l'état Q1, puis Q2, puis Q3 pour revenir à Q0 ; le cycle se répétant
indé�niment).
• Adresse 0 : mov r0, 12
� Le compteur de cycle se trouve en Q0 et le front montant d'horloge apparaît :
∗ Le processeur charge l'instruction qui se trouve à l'adresse 0 : mov r0, 12 et incrémente immédia-
tement le compteur de programme pc (pc← pc+ 1). Note : cette instruction est stockée dans un
signal vectoriel noté instruction. On a donc instruction = �1010 0000 0000 1100�.
∗ L'unité d'exécution du processeur envoie r0 à l'operand_a de l'unité arithmétique et logique. Elle
attribue la valeur �00001100� à operand_b et transmet le code opération �000� au signal operation
de l'alu.
∗ La destination étant le registre r0, l'unité d'exécution �xe le signal dest_reg à �00� pour le module
registers.
� Au front descendant de l'horloge, le compteur de cycle passe en Q1 : rien à faire.
� Au front descendant de l'horloge, le compteur de cycle passe en Q2 : rien à faire.
� Au front descendant de l'horloge, le compteur de cycle passe en Q3 :
17
∗ La sortie de l'alu est dirigé vers l'entrée reg_in du module registers (dans les faits, la sortie de
l'alu sera toujours dirigée vers reg_in).
∗ Le signal reg_we est positionné à '1' pour autoriser l'écriture dans le registre r0 lors du prochain
front montant d'horloge (reg_we ne conservera cette valeur que durant ce cycle Q3).
� Au front montant d'horloge suivant, alors que le compteur de cycle est en Q3, le module registers
mémorise dans r0 la valeur présente sur reg_in.
� On repasse au Q0 au front descendant d'horloge.
• Adresse 1 : mov r1, 230
� même traitement en 4 cycles d'horloge que pour l'instruction précédente. Seul le registre de desti-
nation change (dest_reg = �01�). L'operand_b reçoit quand à lui la valeur 230 codée en binaire non
signé.
• Adresse 2 : add r1, r0
� on reprend les cycles Q0 à Q3 de l'instruction précédente en changeant ceci au cycle Q0 :
∗ operand_a reçoit la valeur stockée dans r1. operand_b reçoit la valeur présente dans r0. operation
reçoit la valeur �100� correspondant à l'addition.
∗ dest_reg reçoit �01� pour indiquer le registre de destination.
Notes :
∗ Ce sont les bits 9 et 8 du mot instruction qui renseigne sur le numéro de registre de destination.
∗ Les bits 7 et 6 du signal instruction indique le numéro du registre source.
∗ Les bits 12 à 10 du signal instruction donne le code de l'opération à réaliser dans l'unit arithmé-
tique et logique.
� Durant le cycle Q3 et uniquement pour ce cycle, l'unité d'exécution autorise l'écriture sur le registre
destination en �xant reg_we = '1'.
• Adresse 3 : mov (25), r1
� Cycle Q0, au front montant d'horloge :
∗ Le processeur charge l'instruction qui se trouve à l'adresse 3 : mov (25), r1 et incrémente immé-
diatement le compteur de programme pc (pc← pc+1). On a donc instruction = �1110 0001 0001
1001�.
∗ L'adresse 25 est transmise à la ram a�n d'accéder à la donnée qui y est stockée (pas utile dans le
cas d'un mov) et de préparer l'accès pour l'écriture du résultat qui interviendra au cycle Q3.
∗ Les données suivantes sont envoyées à l'unité arithmétique et logique :
· operand_a reçoit la donnée provenant de la ram (elle ne sera pas utile mais le traitement est le
même que pour les autres instructions faisant appel à l'unité arithmétique et logique dans le cas
d'une destination en mémoire).
Remarque : en Q0, cette donnée n'est pas nécessairement la bonne car la lecture nécessite un cycle
d'horloge supplémentaire et l'adresse vient seulement d'être fournie à la mémoire ram.
· operand_b est a�ecté par la valeur stockée dans le registre de travail r1.
· Le code operation est �xé à �000�.
� Rien de se passe au cycle Q1
� Au front descendant d'horloge durant le cycle Q2, le compteur de cycle passe en Q3 et l'autorisation
d'écriture dans la ram (ram_we = '1') est fournie à la mémoire de donnée. Cette autorisation sera
maintenue durant tout le cycle Q3.
� Cycle Q3, front montant d'horloge :
∗ Le signal result présent à la sortie de l'unité arithmétique et logique et transmis au début du cycle
Q3 à l'entrée data de la mémoire de données est mémorisée par cette même mémoire.
� Passage en Q0 au front descendant d'horloge.
• Adresse 4 : and r0, (25)
� Cycle Q0, au front montant d'horloge :
∗ Le processeur charge l'instruction qui se trouve à l'adresse 4 : mov r0, (25) et incrémente immé-
18
diatement le compteur de programme pc (pc← pc+1). On a donc instruction = �1000 0100 0001
1001�.
∗ L'adresse 25 est transmise à la ram a�n d'accéder à la donnée qui y est stockée.
∗ Les données suivantes sont envoyées à l'unité arithmétique et logique :
· operand_a est a�ecté par la valeur stockée dans le registre de travail r0.
· operand_b reçoit la donnée provenant de la ram.
Remarque : en Q0, cette donnée n'est pas nécessairement la bonne car la lecture nécessite un cycle
d'horloge supplémentaire et l'adresse vient seulement d'être fournie à la mémoire ram. Cependant,
operand_b étant calculé par un processus combinatoire (ni plus ni moins qu'un multiplexeur
comme nous le verrons plus loin), il prendra la valeur adéquate lorsqu'elle sera disponible à la
sortie q de la mémoire de données. La sortie result de l'unité arithmétique et logique étant elle
même décrite par un processus combinatoire, elle sera alors immédiatement mise à jour au moment
de la mise en place de la bonne valeur de l'operand_b.
· Le code operation est �xé à �001� (pour le and).
� Rien de se passe au cycle Q1
� Au front descendant d'horloge durant le cycle Q2, le compteur de cycle passe en Q3 et l'autorisation
de mise à jour des registres (reg_we = '1') est fournie au module registers. Cette autorisation sera
maintenue durant tout le cycle Q3.
� Cycle Q3, front montant d'horloge :
∗ Le signal result présent à la sortie de l'unité arithmétique et logique et transmis au début du cycle
Q3 à l'entrée data de la mémoire de données est mémorisée par cette même mémoire.
� Passage en Q0 au front descendant d'horloge.
• Adresse 5 : jump 2
� Le compteur de cycle se trouve en Q0 et le front montant d'horloge apparaît :
∗ Le processeur charge l'instruction qui se trouve à l'adresse 5 : jump 2 et incrémente immédiatement
le compteur de programme pc (pc← pc+ 1).
Note : on a instruction = �0111 1100 0000 0010�.
� Front montant d'horloge durant le cycle Q1 :
∗ l'unité d'exécution a�ecte pc avec la valeur de l'adresse à atteindre pour la prochaine instruction.
Ici, on saute à l'adresse 2 donc pc recevra la valeur 2.
� Pas de traitement supplémentaire durant les cycles restants.
La simulation temporelle du processeur exécutant ce programme donne les chronogrammes suivants :
19
Figure 2 � chronogrammes du processeur exécutant le programme exemple
20
Sur ce graphique, cycle représente cycle_counter sous la forme d'un std_logic_vector.
Conception, réalisation et test des di�érents processus du module de l'unité
d'exécution
Vue externe imposée de l'unité d'exécution
Elle est présentée �gure 3.
Figure 3 � symbôle de l'unité d'exécution
Les entrées/sorties de l'unité d'exécution sont les suivantes :
• Signaux généraux :
� clk : le signal d'horloge du processeur.
� cycle : signal de sortie sur 2 bits indiquant le numéro du cycle en cours dans le traitement d'une
instruction.
� reset : entrée de remise à 0 de l'unité d'exécution.
• Signaux réalisant l'interfaçage avec la mémoire de programme
� prg_data : entrée sur 16 bits correspondant à la donnée pointée dans la mémoire de programme.
� prg_address : sortie sur 10 bits spéci�ant l'adresse dans la mémoire de programme de la prochaine
instruction à exécuter.
• Signaux d'interfaçage avec la mémoire de données :
� ram_data : entrée sur 8 bits contenant la donnée actuellement lue dans la mémoire de données.
� ram_address : sortie sur 8 bits indiquant l'adresse de la donnée ciblée dans la mémoire de données.
� ram_we : signal binaire indiquant à la mémoire de données si une écriture doit être e�ectuée au
prochaine front montant d'horloge.
21
• Signaux d'interfaçage avec l'unité arithmétique et logique :
� alu_operand_a : sortie sur 8 bits fournissant l'operand_a (dit de destination) à l'unité arithmétique
et logique.
� alu_operand_b : sortie sur 8 bits fournissant l'operand_b (dit source) à l'unité arithmétique et
logique.
� alu_operation : entrée/sortie sur 3 bits spéci�ant l'opération à réaliser au sein de l'unité arithmétique
et logique.
� alu_�ag_we : sortie précisant à l'unité arithmétique et logique si les indicateurs doivent être mis à
jour.
� alu_c : entrée recevant la retenue actuellement mémorisée dans l'unité arithmétique et logique.
� alu_z : entrée recevant l'indicateur de zéro actuellement stocké dans l'unité arithmétique et logique.
• Signaux d'interfaçage avec le module de gestion des registres internes :
� r0, r1, r2, r3 : entrées reçues du module registers donnant les 4 registres internes du processeurs
(tous sur 8 bits).
� reg_we : sortie indiquant au module registers que la mise à jour d'un registre doit être réalisée au
prochain front montant d'horloge.
� dest_reg : entrée/sortie sur 2 bits spéci�ant le numéro du registre interne à mettre à jour.
Travail préparatoire
• Copier dans votre dossier ENSL1 le dossier processeur stocké dans le dossier @partage_lecture\ENSL1\
Ce dossier contient une version du projet corrigée avec les di�érents modules déjà développés jusqu'ici ainsi
qu'un �chier execution_unit.vhd contenant essentiellement la description externe du module execution_unit.
Processus numéro 1 : Séquenceur d'instruction (cycle_counter)
L'exécution d'une instruction s'e�ectue en 4 cycles d'horloge notés Q0 à Q3. A�n d'assurer le séquen-
cement de l'exécution, on doit disposer à tout instant d'un compteur à l'intérieur de l'unité d'exécution
indiquant le cycle de calcul en court.
• En vous inspirant du cours, créer un type énuméré pouvant prendre les valeurs Q0, Q1, Q2 et Q3.
• Réaliser un processus permettant de passer successivement de Q0 à Q1, de Q1 à Q2 ...... de Q3 à Q0 à
chaque front descendant de l'horloge.
Notes :
• le cycle en cours du processeur sera mémorisé dans un signal noté cycle_counter.
• le reset sera prioritaire et remettra le système dans son état Q0.
Processus numéro 2 : Processus séquentiel d'exécution des instructions
C'est le processus le plus important de l'unité d'exécution.
Il gère l'évolution du compteur de programme pc et prélève l'instruction présente sur le bus de données
de la mémoire de programme au front montant d'horloge qui intervient lorsque le compteur de cycle est à
l'état Q0.
Note : le code de l'instruction sera mémorisé dans un signal intermédiaire nommé instruction (vecteur
de 16 std_logic) et déclaré entre le mot clé architecture et son begin.
• Ajouter la déclaration du signal instruction au programme.
• Ecrire le processus de gestion de pc en tenant compte des points suivants :
� le reset est prioritaire et entraîne la mise à 0 de pc.
� Quand un front montant d'horloge se présente alors que le compteur de cycle est en Q0 :
∗ Mémoriser le signal prg_data en provenance de la mémoire de programme dans le signal interne
instruction.
∗ Incrémenter pc pour pointer vers l'instruction suivante.
22
� Lors d'un front montant d'horloge pendant le cycle Q2 :
∗ Tester si l'instruction en cours d'exécution est un jump.
∗ Si oui, changer la valeur de pc en imposant l'adresse indiquée dans le code instruction du jump.
� Pour l'instant, ne rien faire pour les cycles Q1 et Q3.
• Tester le fonctionnement de l'ébauche de processeur avec le programme exemple exploité jusqu'ici.
• En opérant comme pour l'instruction jump, mettre en place le décodage et l'exécution des instructions
de sauts conditionnés skipifz (on incrémente pc si z=1 ) et skipifc (incrémentation de pc si c=1 ).
Note : les instructions skipifz et skipifc seront testées plus tard.
Processus numéro 3 : Envoi des opérandes operand_a et operand_b à l'unité arithmétique et
logique
Il s'agit d'écrire un processus combinatoire (ceci implique que les sorties de ce processus reçoivent sys-
tématiquement une valeur lors du traitement du process) permettant de fournir les opérandes à l'unité
arithmétique et logique.
Note : l'exemple ci-dessous apporte une illustration sur le caractère combinatoire ou séquentiel d'un
process.
a r c h i t e c t u r e r t l o f exemple i s
s i g n a l a , b : s td_log ic_vector (3 downto 0 ) ;
begin
−− l e p roce s su s proc_1 e s t un proce s su s s é qu en t i e l car
−− s i a vaut "0101" a l o r s b e s t mémorisé
−− et ne r e ç o i t pas de nouve l l e va l eur .
proc_1 : p roce s s ( a )
begin
i f a /= "0101" then b <= a ; end i f ;
end proce s s ;
−− l e p roce s su s c i−dessous e s t combinato i re car b r e ç o i t
−− t ou jou r s une va l eur l o r s du tra i t ement du proce s su s .
p roce s s ( a )
begin
i f a = "0000" then b <= "1010"; e l s e b <= a ; end i f ;
end proce s s ;
end r t l ;
Analyse du jeu d'instructions En se référant au tableau décrivant les di�érentes instructions du pro-
cesseur IUT, on peut constater que :
• operand_a = rd et operand_b = data_ram pour les instructions mov rd,(ram) à cmp rd,(ram)
• operand_a = rd et operand_b = lit pour les instructions mov rd,lit à cmp rd,lit
• operand_a = rd et operand_b = rs pour les instructions mov rd,rs à cmp rd,rs
• operand_a = data_ram et operand_b = rs pour les instructions mov (ram),rs à cmp (ram),rs
où rd représente le registre de destination identi�é dans le code instruction par les bits 9 et 8 (�00� pour le
registre r0 ...),
où rs spéci�e le registre source identi�é dans le code instruction par les bits 7 et 6 (�00� pour le registre
r0 ...) dans le cas des instructions de la classe mov rd,rs et par les bits 9 et 8 dans le cas des instructions de
la catégorie mov (ram),rs,
et où data_ram est le mot de 8 bits à destination ou en provenance de la mémoire de données.
Travail demandé
• Dresser une table de vérité faisant apparaître :
� En entrées : les 3 bits de poids forts du code machine contenu dans le vecteur instruction.
� En sorties : operand_a et operand_b.
23
Notes : les sorties recevront di�érents signaux tels que data_ram, rd, rs, lit (le poids faible du code
machine instruction) ...Par ailleurs, il est possible de prélever les 3 bits de poids forts du signal
instruction en écrivant instruction(15 downto 13) dans le code VHDL.
• Décrire cette table de vérité dans un processus combinatoire.
Attention : on ne pourra pas écrire operand_a <= rd dans le programme. Il faudra utiliser les bits du signal
instruction spéci�ant rd (dest_reg en fait) pour faire le choix de r0, r1, r2 ou encore r3. La même démarche
devra être mise en oeuvre pour la source rs.
• Tester l'intégralité du processeur, observer l'évolution des signaux d'opérandes de l'unité arithmétique
et logique et véri�er leur cohérence avec le programme de test.
Processus numéro 4 : Génération du signal reg_we autorisant la mise à jour du registre de
destination
Il s'agit d'un processus combinatoire. Lorsque l'instruction en cours cible un registre appelé registre de
destination et repéré par le signal dest_reg, le signal reg_we doit être mis à '1' durant le cycle Q3 du
compteur de cycle. Le reste du temps, reg_we reçoit la valeur '0'.
Travail demandé
• Coder ce 4ième process et le tester.
Note : à ce stade, le processeur est capable de réaliser une a�ectation vers un registre.
Processus numéro 5 : Génération du signal ram_we autorisant la mise à jour d'une donnée
dans la mémoire de données
Il s'agit à nouveau d'un processus combinatoire. Lorsque l'instruction en cours cible ma mémoire de
donnée comme destination (instructions de type (ram),rs), le signal ram_we doit être mis à '1' durant le
cycle Q3 du compteur de cycle. Le reste du temps, ram_we reçoit la valeur '0'.
Travail demandé
• Coder ce 5ième process et le tester.
Note : à ce stade, le processeur est capable de mémoriser la valeur d'un registre à l'intérieur de la mémoire
de données.
Processus numéro 6 : Génération du signal �ag_we autorisant la mise à jour ds indicateurs
de l'unité arithmétique et logique
Il s'agit encore une fois d'un processus combinatoire. Lorsque l'instruction en cours utilise l'unité arith-
métique et logique, le signal �ag_we doit être mis à '1' durant le cycle Q3 du compteur de cycle. Le reste
du temps, �ag_we reçoit la valeur '0'.
Travail demandé
• Coder ce 6ième process et le tester.
Note : à ce stade, l'unité arithmétique et logique est rendue totalement fonctionnelle. Les indicateurs z et
c informent sur le résultat de certaines opérations dans l'unité arithmétique et logique et il est possible
d'exploiter les instructions de sauts conditionnés skipifz et skipifc.