M1 SESI – Architecture des Processeurs et Optimisation
TME1 – VHDL – Environnement de simulation
L'objectif de ce TME est de vous familiariser avec :
- La syntaxe de l'assembleur
- L'écriture de programme et les conventions utilisées par le compilateur
- L'obtention d'un exécutable à partir de fichiers assembleur
- L'environnement de simulation
- Le langage de description de composants matériels
En principe, les 3 premiers points ont été acquis en L3.
Le but de ce TME est de montrer comment on peut obtenir un fichier exécutable à partir d'un programme écrit en assembleur et comment on peut l'exécuter sur une plateforme matérielle.
Tous les fichiers nécessaires se trouvent sous /users/enseig/pirouz/M1/Archi/Enonce/S2_TME. Ces fichiers doivent être copiés dans un répertoire du compte de l'étudiant en respectant l'organisation en sous-répertoires.
Vous pouvez aussi récupérer les fichiers à travers l'archive suivante attachment:S2_TME.tar.gz et la décompresser avec la commande :
tar -xvzf S2_TME.tar.gz
Note : la partie sur le langage VHDL est facultative suivant les groupes.
Le langage de description de matériel VHDL.
Les exercices de ce TME sont construits autour d'un langage de description de matériel appelé VHDL (Very High Speed Integrated Circuits Hardware Description Language). Attention, il ne s'agit pas d'un langage de programmation. Ce langage permet de décrire le fonctionnement d'un circuit numérique. C'est un langage déclaratif.
La description d'un circuit comporte deux parties : une vue externe et une vue interne.
La vue externe définit ce que l'on peut voir à l'extérieur du circuit : le nom du circuit et son interface. L'interface d'un circuit est un ensemble de ports qui permettent de relier le circuit à d'autres circuits. Un port peut être composé d'un seul bit ou d'un vecteur de bits. Un port peut être en entrée (c'est-à-dire sa valeur est lue par le circuit) ou en sortie (c'est-à-dire sa valeur est écrite par le circuit) ou en entrée-sortie (si sa valeur est à la fois écrite et lue par le circuit).
La vue interne définit le fonctionnement du circuit : comment la valeur des sorties est calculée à partir des entrées.
On distingue deux types de vues internes. Lorsque le circuit est relativement complexe, on peut le décrire comme une interconnexion de composants plus simples. Il s'agit d'une description structurelle. Dans une description structurelle, on utilise donc des composants et des signaux. Un signal peut être composé d'un bit ou d'un vecteur de bits. Les signaux servent à relier des instances de composants. Une instance est un exemplaire d'un certain composant. Par exemple une carte bi-processeur contient deux instances du même composant (processeur).
Lorsque le circuit est relativement simple, on peut décrire directement l'expression (booléenne) des sorties. Il s'agit d'une description comportementale. Une description comportementale utilise donc des signaux. Un signal peut être composé d'un bit ou d'un vecteur de bits. Le comportement de chaque signal est définit par une expression booléenne ou par un vecteur d'expressions booléennes. Les expressions booléennes utilisent les opérateurs booléens : and, or, xor, nand, nor et not. On dispose également de l'opérateur de comparaison =.
En VHDL, on ne distingue pas les caractères minuscules des majuscules (toto, ToTo et TOTO désignent la même chose).
Exemple :
Voici la description comportementale d'un circuit simple qui calcule la parité de 4 signaux. Il a une seule sortie. La sortie vaut 1 si les 4 entrées présentent un nombre impair de 1.
entity PARITE is -- vue externe port ( signal a : in bit ; -- a : 1 bit en entree signal b : in bit ; -- b : 1 bit en entree signal c : in bit ; -- c : 1 bit en entree signal d : in bit ; -- d : 1 bit en entree signal p : out bit -- p : 1 bit en sortie ) ; end ; architecture COMPORTEMENTALE of PARITE is -- vue interne signal x : bit ; -- x : signal interne signal y : bit ; -- y : signal interne begin x <= a xor b ; y <= c xor d ; p <= x xor y ; end ;
Attention : le symbole <=
est une assignation. Un signal ne peut être assigné qu'une seule fois.
x <= a xor b ;
indique que x
est le ou-exclusif de a
et de b
. Dès que la valeur de a
ou de b
change, la valeur de x
est mise à jour. C'est une différence fondamentale entre un langage de description de matériel et un langage de programmation. Un langage de description de matériel est un langage parallèle. La description suivante est strictement équivalente à la précédente :
architecture COMPORTEMENTALE of PARITE is -- vue interne signal x : bit ; -- x : signal interne signal y : bit ; -- y : signal interne begin p <= x xor y ; y <= c xor d ; x <= a xor b ; end ;
Voici d'autres vues internes pour le même circuit.
architecture COMPORTEMENTALE of PARITE is -- vue interne signal x : bit ; -- x : signal interne signal y : bit ; -- y : signal interne begin p <= x xor y ; y <= '0' when ((c xor d) = '0') else -- assignation conditionnelle '1' ; x <= a xor b ; end ;
architecture COMPORTEMENTALE of PARITE is -- vue interne signal x : bit ; -- x : signal interne signal y : bit ; -- y : signal interne begin p <= x xor y ; y <= '0' when (c = '0' and d = '0') else '0' when (c = '1' and d = '1') else '1' ; x <= a xor b ; end ;
architecture COMPORTEMENTALE of PARITE is -- vue interne signal x : bit ; -- x : signal interne signal y : bit ; -- y : signal interne begin p <= x xor y ; with c & d select -- concatenation y <= '0' when B"00" , -- assignation selective '0' when B"11" , '1' when others; x <= a xor b ; end ;
architecture COMPORTEMENTALE of PARITE is -- vue interne begin p <= a xor b xor c xor d ; end ;
Voici la description du même circuit avec un vecteur de bits en entrée.
entity PARITE_VECT is -- vue externe port ( signal a : in bit_vector (3 downto 0); signal p : out bit ) ; end ; architecture COMPORTEMENTALE of PARITE_VECT is begin p <= a(0) xor a(1) xor a(2) xor a(3) ; end ;
L'environnement de simulation
Une description VHDL ne peut pas être exécutée directement. Elle ne représente que la manière dont un circuit fonctionne. Pour pouvoir exécuter une description VHDL il faut donc indiquer quelles sont les valeurs que l'on souhaite appliquer aux entrées du circuit. L'exécution du circuit – on parle plutôt de simulation du circuit – consiste à propager les valeurs appliquées aux entrées à travers le circuit pour calculer la valeur des tous les signaux. C'est le rôle du simulateur.
Les valeurs appliquées aux entrées du circuit sont décrites dans un fichier particulier qu'on appelle le fichier de stimuli ou de patterns. Ce fichier décrit les valeurs successives à appliquer aux entrées et les sorties que l'on souhaite observer.
Le simulateur prend en entrée le fichier qui décrit le circuit et le fichier qui décrit les patterns et produit le résultat de la simulation dans un fichier au même format que le fichier de pattern.
Exemple :
Voici la description d'un fichier de patterns en entrée pour le circuit Parite.
in a ; -- a : 1 bit en entree in b ; -- b : 1 bit en entree in c ; -- c : 1 bit en entree in d ;; -- d : 1 bit en entrée out p -trace; -- p : 1 bit a observer begin : 0000 ; -- on applique 0000 : 0001 ; -- puis 0001 : 1101 ; -- puis 1101 : 1100 ; -- etc end ;
Voici la description d'un fichier de patterns résultat de la simulation Parite.
in a ; -- a : 1 bit en entree in b ; -- b : 1 bit en entree in c ; -- c : 1 bit en entree in d ;; -- d : 1 bit en entrée out p ; -- p : 1 bit a observer begin : 0000 ?0; -- 0000 resultat -> 0 : 0001 ?1; -- 0001 resultat -> 1 : 1101 ?1; -- 1101 resultat -> 1 : 1100 ?0; -- etc end ;
On peut aussi demander à observer un signal interne.
signal parite.x -trace; -- x a observer
Voici la description d'un fichier de patterns en entrée pour le circuit Parite_Vect
.
in a (3 downto 0) X ;; -- a format hexa out p -trace; -- p a observer begin : 0 ; -- on applique 0000 : 1 ; -- puis 0001 : d ; -- puis 1101 : c ; -- etc end ;
Et le résultat de la simulation Parite_Vect
.
in a (3 downto 0) X ;; -- a format hexa out p ; -- p a observer begin : 0 ?0; -- 0000 resultat -> 0 : 1 ?1; -- 0001 resultat -> 1 : d ?1; -- 1101 resultat -> 1 : c ?0; -- etc end ;
Une description VHDL comportementale doit être écrite dans un fichier qui porte le même nom que le circuit (en minuscule) avec l'extension .vbe.
Une description VHDL structurelle doit être écrite dans un fichier qui porte le même nom que le circuit (en minuscule) avec l'extension .vst.
Une description des patterns doit être écrite dans un fichier avec l'extension .pat.
Pour lancer la simulation d'un circuit on utilise la commande suivante s'il s'agit d'un circuit décrit de manière comportementale. Les noms des fichiers sont donnés au simulateur sans l'extension.
simulad –b –bdd –p 50 –l 1 –nostrict <fichier_VHDL> <fichier_Patterns> <fichier_Resultat>
Pour lancer la simulation d'un circuit on utilise la commande suivante s'il s'agit d'un circuit décrit de manière structurelle. Les noms des fichiers sont donnés au simulateur sans l'extension.
simulad –bdd –p 50 –l 1 –nostrict <fichier_VHDL> <fichier_Patterns> <fichier_Resultat>
Pour accéder aux fichiers le simulateur a besoin de savoir pour chaque composant quel est le type de description utilisé (structurelle ou comportementale). Cette information est donnée dans le fichier CATAL. Ce fichier liste le nom des composants décrits de manière comportementale.
Exemple :
parite C parite_vect C
Enfin le simulateur a besoin de connaître les répertoires où il peut trouver les descriptions. Cette information est donnée à travers des variables d'environnement.
Exemple :
setenv MBK_IN_LO vst setenv MBK_CATA_LIB .:../cpu
Exercice 1 :
Ecrire la description comportementale d'un additionneur 32 bits et la simuler. L'additionneur reçoit en entrée deux vecteurs de 32 bits opa et opb et génère en sortie un vecteur 32 bits sum .
On rappelle que pour chaque bit de l'additionneur, il faut calculer la somme et la retenue sortante. La retenue sortante est utilisée comme retenue entrante par le bit suivant. La somme est le ou-exclusif des deux opérandes et de la retenue entrante. La retenue sortante vaut 1 si les deux opérandes et la retenue entrante présentent au moins deux 1.
Ecriture d'un programme en assembleur
A priori, il suffit de connaître le jeu d'instructions du Mips et les règles d'utilisation des registres pour pouvoir écrire des programmes en assembleur.
Toutefois, le langage assembleur du Mips est peu plus riche que le jeu d'instructions. Le langage assembleur contient un certain nombre de macro-instructions pour faciliter l'écriture des programmes. Une macro-instruction est une suite d'instructions (du jeu d'instructions du processseur).
Les macro-instructions :
Dans le jeu d'instruction du Mips, il n'y a pas d'instruction Nop (No Operation). Il s'agit d'une instruction dont l'exécution de modifie pas l'état de la machine. Par contre, le langage assembleur reconnaît la macro-instruction Nop et la traduit par Sll r0, r0, 0.
Les autres macro-instructions sont :
Move rx, ry traduit par Addu rx, ry, r0 Beqz rx, label traduit par Beq rx, r0, label Bnez rx, label traduit par Bne rx, r0, label La rx, label traduit par Lui rx, label_poids_fort Ori rx, rx, label_poids_faible Li rx, imd_32bits traduit par Lui rx, imd_32bits_poids_fort Ori rx, rx, imd_32bits_poids_faible
De plus, le langage assembleur reconnaît un certain nombre de directives qui permettent d'initialiser le contenu de la mémoire et de contrôler les adresses où les données et le code sont placés.
Directives de réservation de mémoire :
Ces directives permettent de réserver de la mémoire et éventuellement de l'initialiser.
.word imd_1_32bits, ...
permet de réserver des mots (4 octets) et de les initialiser. Les données sont placées à des adresses consécutives
.half imd_1_16bits, ...
permet de réserver des demi-mots (2 octets) et de les initialiser. Les données sont placées à des adresses consécutives
.byte imd_1_8bits, ...
permet de réserver des octets et de les initialiser. Les données sont placées à des adresses consécutives
.skip nbr_octets
permet de réserver nbr_octets octets
.space nbr_octets
permet de réserver nbr_octets octets
.asciiz "string"
permet de réserver et d'initialiser un chaîne de caractères. La chaîne string est complétée avec le caractère de fin de chaîne ('\0')
.ascii "string"
permet de réserver et d'initialiser une chaîne de caractères. La chaîne string n'est pas complétée avec le caractère de fin de chaîne ('\0')
Directives de placement mémoire :
Cette directive permet d'indiquer l'emplacement mémoire où doivent être placés les objets (codes ou données) qui suivent.
Exemple :
.text
place les objets qui suivent dans la section .text
En fait, la directive désigne une section d'entrée où placer l'objet.
Un fichier de paramètres associe à chaque section d'entrée une section de sortie et définit l'emplacement mémoire des sections de sortie. Ce fichier de paramètres est le fichier ldscript.
Le fichier ldscript contient la description des sections de sortie et leur emplacement en mémoire. De plus, ce fichier permet de définir des symboles et leur attribuer une valeur. Ces symboles peuvent être utilisés dans les fichiers assembleur.
Un fichier ldscript peut être relativement complexe. Ici, nous utilisons une version simplifiée de ce fichier.
Exemple :
/* ###--------------------------------------------------------------### */ /* file : ldscript */ /* date : Apr 29 2009 */ /* description : ldscript for mips assemply programs */ /* ###--------------------------------------------------------------### */ /* ###------------------------------------------------------### */ /* sections */ /* ###------------------------------------------------------### */ SECTIONS { /* ###------------------------------------------------------### */ /* addresses as symbols : */ /* */ /* - user text address */ /* - user stack address */ /* - user global data address */ /* - user controllers address */ /* */ /* - kernel global data address */ /* */ /* - exception text address */ /* - boot text address */ /* - boot exception text address */ /* ###------------------------------------------------------### */ user_text_begin = 0x00400000 ; user_stack_begin = 0x7fffe000 ; user_data_begin = 0x10000000 ; kernel_stack_begin = 0xffffe000 ; kernel_data_begin = 0xc0000000 ; kernel_term_begin = 0xc0001000 ; excp_text_base = 0x80000000 ; boot_excp_base = 0xbfc00100 ; excp_base_offset = 0x00000080 ; excp_text_begin = excp_text_base + excp_base_offset ; boot_excp_begin = boot_excp_base + excp_base_offset ; boot_text_begin = 0xbfc00000 ; /* ###------------------------------------------------------### */ /* section descriptions */ /* ###------------------------------------------------------### */ .text user_text_begin : { *(.text ) } .stack user_stack_begin : { *(.stack ) } .data user_data_begin : { *(.data .rodata ) } .reset boot_text_begin : { *(.boot .reset ) } .bexcep boot_excp_begin : { *( .bexcep ) } .ktext excp_text_begin : { *(.ktext .excep ) } .kstack kernel_stack_begin : { *(.kstack ) } .kdata kernel_data_begin : { *(.kdata .krodata) } }
La valeur des symboles peut être, soit une constante, soit calculée par une expression. La description des sections de sortie comporte le nom de la section, son adresse et la liste des sections d'entrée qui la compose. On peut voir dans l'exemple ci-dessus que la section de sortie .data se trouve à l'adresse 0x10000000
et qu'elle est composée des sections d'entrée .data
et .rodata
.
L'assemblage des programmes
L'outil assembleur permet de générer une image mémoire à partir de fichiers écrits en assembleur. Cet outil prend en entrée une liste de fichiers assembleur et le fichier ldscript et génère une image mémoire dans un fichier de sortie.
asm -set reorder -ld <ldscript> -mips32 -o <fichier_image> <fichier_ass> ...
Exercice 2 :
Le programme main
suivant (fichier strcpy.u
) affiche une chaîne de caractères src
puis la copie dans une chaîne trg
. Ensuite, il affiche la chaîne trg
et quitte le programme. L'affichage d'une chaîne de caractères se fait en appelant la fonction puts
. La fonction strcpy
permet de copier une chaîne de caractères. Enfin, la fonction exit
permet de quitter le programme main
.
Compléter ce fichier en écrivant la fonction strcpy
(les autres fonctions sont fournies dans d'autres fichiers assembleur -- le fichier std_32.u
contient les fonctions puts
et exit
, le fichier std_32.x
le programme de traitement des exceptions et std_32.r
le programme de reset).
Assembler votre programme avec la commande suivante et vérifier l'image mémoire obtenue (fichier strcpy.mem
)
asm -set reorder -ld mips32_ld -mips32 -o strcpy.mem strcpy.u std.u std.x std.r
En ajoutant l'option –s strcpy.sym
à la commande on obtient la liste des symboles utilisés dans le programme.
Cette commande peut être invoquée par le script ASM_32.csh
:
./ASM_32.csh strcpy
; ### -------------------------------------------------------------- ### ; # file : strcpy.u # ; # date : Sep 8 2009 # ; # descr. : functional test for Mips R3000 # ; ### -------------------------------------------------------------- ### ; ### ------------------------------------------------------ ### ; # data section # ; ### ------------------------------------------------------ ### .data source_str : .asciiz "Have a nice day" target_str : .space 128 ; ### ------------------------------------------------------ ### ; # text section # ; ### ------------------------------------------------------ ### .text ; ### ------------------------------------------------------ ### ; # make the label _main visible for other files # ; ### ------------------------------------------------------ ### .globl _main ; ### ------------------------------------------------------ ### ; # main # ; ### ------------------------------------------------------ ### _main : la r16, source_str la r17, target_str ; ### ------------------------------------------------------ ### ; # print the source string # ; ### ------------------------------------------------------ ### add r4 , r0 , r16 jal _puts ; ### ------------------------------------------------------ ### ; # call strcpy # ; ### ------------------------------------------------------ ### add r4 , r0 , r17 add r5 , r0 , r16 jal _strcpy ; ### ------------------------------------------------------ ### ; # print the target string # ; ### ------------------------------------------------------ ### add r4 , r0 , r17 jal _puts ; ### ------------------------------------------------------ ### ; # exit # ; ### ------------------------------------------------------ ### addi r4 , r0 , 0 ; exit value jal _exit ; ### ------------------------------------------------------ ### ; # strcpy # ; ### ------------------------------------------------------ ### _strcpy : ... .end
La plateforme de simulation pour le processeur MIPS
Une plateforme de simulation construite autour d'un processeur Mips permet de simuler l'exécution d'un programme assembleur. Outre le processeur Mips, cette plateforme comporte des bancs mémoires pour les instructions et les données, un terminal d'affichage et un décodeur d'adresses. Le décodeur d'adresses sélectionne, en fonction de l'adresse émise par le processeur, le banc mémoire qui doit répondre au processeur.
Sur cette plateforme les bancs mémoires sont initialisés à partir de l'image mémoire obtenue par l'assemblage d'un programme assembleur. Cette image doit se trouver dans le fichier ram.ini
et ce fichier doit être placé dans le répertoire où la simulation est lancée.
Le fichier de patterns nécessaire à la simulation est très simple. En pratique, cette plateforme n'a que deux entrées : l'horloge et le signal de reset. Pendant le premier cycle de simulation, le signal reset est actif (ce signal est actif à l'état bas) puis, il reste inactif pendant le reste de la simulation. L'horloge est signal périodique qui passe successivement de 0 à 1 et de 1 à 0.
Exercice 3 :
Simuler l'exécution du programme main (fichier strcpy.u
) sur la plateforme. Pour cela il suffit d'exécuter le script SIM.csh
: ./SIM.csh ../asm/strcpy
Exercice 4 :
Le programme main suivant (fichier strlen.u
) affiche une chaîne de caractères src
puis, calcule la longueur de cette chaîne en appelant la fonction strlen
. Ensuite, le résultat de fonction strlen est transformé en une chaîne de caractères par la fonction uitoa_d
. Enfin, la fonction main
affiche cette chaîne et sort en appelant la fonction exit
.
Compléter ce fichier en écrivant la fonction strlen
(les autres fonctions sont fournies dans d'autres fichiers assembleur). Vous pouvez essayer plusieurs versions de strlen
et vérifier la différence du nombre de cycles nécessaires à la simulation.
Assembler votre programme avec la commande ./ASM.csh strlen
; ### -------------------------------------------------------------- ### ; # file : strlen.u # ; # date : Sep 8 2009 # ; # descr. : functional test for Mips R3000 # ; ### -------------------------------------------------------------- ### ; ### ------------------------------------------------------ ### ; # data section # ; ### ------------------------------------------------------ ### .data source_str : .asciiz "Have a nice day" head_str : .ascii "longeur de la chaine : " tail_str : .space 128 ; ### ------------------------------------------------------ ### ; # text section # ; ### ------------------------------------------------------ ### .text ; ### ------------------------------------------------------ ### ; # make the label _main visible for other files # ; ### ------------------------------------------------------ ### .globl _main ; ### ------------------------------------------------------ ### ; # main # ; ### ------------------------------------------------------ ### _main : la r16, source_str la r17, head_str la r18, tail_str ; ### ------------------------------------------------------ ### ; # print the source string # ; ### ------------------------------------------------------ ### add r4 , r0 , r16 jal _puts ; ### ------------------------------------------------------ ### ; # call strlen # ; ### ------------------------------------------------------ ### add r4 , r0 , r16 jal _strlen ; ### ------------------------------------------------------ ### ; # convert the length into a string # ; ### ------------------------------------------------------ ### add r4 , r0 , r2 add r5 , r0 , r18 jal _uitoa_d ; ### ------------------------------------------------------ ### ; # print the result # ; ### ------------------------------------------------------ ### add r4 , r0 , r17 jal _puts ; ### ------------------------------------------------------ ### ; # exit # ; ### ------------------------------------------------------ ### addi r4 , r0 , 0 ; exit value jal _exit ; ### ------------------------------------------------------ ### ; # strlen # ; ### ------------------------------------------------------ ### _strlen : ... .end
Exercice 5 :
Ouvrez le fichier strupper.u
du répertoire asm
. Écrivez la fonction strupper
. Testez votre solution et vérifiez qu'elle est correcte sur plusieurs chaînes de caractères (plus ou moins de majuscules, d'autres types de caractères que des lettres). Vous pourrez tester différentes versions assembleur de la même fonction et comparer le temps nécessaire à l'exécution de chacune.
Exercice 6 :
Ouvrez le fichier strcmp.u
qui se trouve dans le répertoire asm
. Programmez une version simplifiée de la fonction strcmp
qui étant données deux chaines de caractères str1
et str2
rend 0
si les deux chaînes sont égales, rend -1
si str1
est inférieure à str2
et rend 1
sinon.
Dans le main vous afficherez le résultat en utilisant des chaînes de caractères globales. Vous afficherez 'chaines identiques
', ou 'première chaine inferieure
' ou 'premiere chaine superieure
' dans le main en fonction du résultat de votre fonction appelée sur deux chaînes déclarées comme globales. Testez votre solution en testant tous les cas possibles.
Pouvez vous réduire le temps d'exécution de la fonction en changeant le code de votre fonction ?
Exercice 7 :
En vous inspirant des fichiers précédents et du TD précédent, écrivez un programme principal qui affiche un tableau d'entiers, le tri et l'affiche une fois trié. On suppose que le tableau est une variable globale et que sa taille aussi.
Attachments (2)
-
plateforme.jpg (20.6 KB) - added by 12 years ago.
Plateforme de simulation
- S2_TME.tar.gz (5.5 KB) - added by 12 years ago.
Download all attachments as: .zip