Examen pratique intra INF2171
Note: généreusement contribué par Nicholas Cantin
Objectifs
- Vous faire réviser la matière des cours 1 à 7
- Vous familiariser avec la feuille d’aide mémoire pour les appels systèmes, les instructions et les codes ASCII importants.
- Vous faire travailler avec du code d’assembleur
note : Cet examen pratique ne peut pas toucher à tous les concepts. Prennez le temps de relire vos notes et de refaire les labos sur des concepts qui ont moins été compris.
conseil : Faites-vous une feuille qui contient une table de conversion rapide entre binaire-décimal-hexadécimal des nombres 1 à 16 pour éviter de perdre trop de temps à convertir chaque valeur à la main, surtout quand vous faites de l’arithmétique.
Question 1 :
Répondez aux questions suivantes (vrai/faux, QCM et très court développement)
i. Pour déterminer la taille maximale d’un nombre entier NON SIGNÉ sur n bits, il faut utiliser la formule suivante : 2^(n-1) - 1.
- vrai
- faux
C’est faux. Puisqu’il s’agit d’un entier non signé, on peut utiliser tous les bits. La formule est donc 2^n - 1.
ii. Je suis un caractère qui représente 4 bits.
- a) un word
- b) un code ASCII
- c) un octet
- d) un chiffre hexadécimal
C’est un chiffre hexadécimal. Chaque chiffre hexadécimal est défini sur 4 bits, 0 = 0000 et F = 1111.
iii. Que se passe-t-il lorsque le résultat d’une opération est hors domaine?
Un débordement se produit. Notre résultat n’est donc pas celui attendu.
iv. Dans quel registre faut-il passer l’argument lorsqu’on utilise printInt ?
Dans le registre a0
v. Quelle est l’utilité du registre a7?
Il reçoit le code de l’instruction à éxécuter
vi. Vous circulez dans un tableau qui contient des caractères ASCII (.byte
). Vous voulez accéder à 6 éléments plus loin dans le tableau. Quelle valeur allez vous devoir additionner au registre contenant l’adresse du tableau?
- a) 6
- b) 12
- c) 24
- d) 48
6. 1 byte = 1 octet, 6 byte = 6 octet. Nous devons donc additionner 6 au registre en question.
vii. Encore avec un tableau contenant des caractères ASCII, quelle instruction devez-vous utiliser pour extraire le caractère de la case dans laquelle vous vous trouvez?
- a) lb
- b) lbu
- c) lw
- d) lwu
Nous savons qu’un char est l’équivalent d’un byte. D’ailleurs, les chars doivent être unsigned puisqu’ils peuvent aller jusqu’a 127, il ne font pas avoir de bit significatif. La réponse est donc : lbu.
iix. slli
peut être utilisé pour multiplier la valeur d’un registre.
- vrai
- faux
Vrai, chaque décalage de 1 vers la gauche multiplie par 2.
ix. À quel bit doit-on se référer pour déterminer si un nombre est positif ou négatif
Au bit de poids fort (par exemple, le bit de poids fort en mode 64 bit est le 64ème bit).
x. Une pseudoinstruction consiste d’une ou plusieurs vraies instructions machines.
- vrai
- faux
Vrai
xi. On veut définir un tableau de 10 entiers 16 bits signés. Pour garantir que les loads et stores des éléments soient naturellement alignés, l’adresse du tableau devrait être un multiple de combien ?
De 2. Puisque 16 bits = 2 octets, il faudra faire des multiples de 2.
xii. La taille des registres dépends de?
De l’architecture. Par exemple, RISC-V 64 aura des registres de 64 bits!
Question 2 :
Dans votre nouvel emploi, on vous demande de créer un programme qui permettra aux utilisateur de transformer des degrés Kelvin en degrés Celsius. Lorsqu’un utilisateur rentre le nombre 0
, on lui retourne -273. Considérant que votre outil utilise des registres de 16 bits, répondez aux questions suivantes en laissant des traces de votre démarche :
a) Quelle est la représentation en binaire du registre contenant “-273” si on travaille en complément de deux? Écrivez le résultat sur 16 bits.
1- Trouver 273 en binaire :
273 = 2^8 + 2^4 + 2^0
273 = 0000 0001 0001 0001
2- lui appliquer l’inversion :
0000 0001 0001 0001 deviendra
1111 1110 1110 1110
3- on additionne 1 au résultat
1111 1110 1110 1110
0000 0000 0000 0001
ce qui donne :
1111 1110 1110 1111
Réponse finale : 1111 1110 1110 1111
b) Quelle est sa représentation sous format hexadécimal (toujours sur 16 bits)?
Puisqu’on a notre résultat binaire, on peut simplement remplacer les blocs de 4 bits par leur valeur hexadécimale associée.
Réponse finale : 0xFEEF
c) Un bug dans le système est survenu et votre programme à imprimé le résultat en binaire. Cependant, il s’agissait d’une donnée très importante et vous devez le convertir en décimal pour vos collègues avant de corriger ce bug. Voici le nombre en question : 0000 0001 0111 0011
2^0 + 2^1 + 2^4 + 2^5 + 2^6 + 2^8
1 + 2 + 16 + 32 + 64 + 256 = 371
Réponse finale : 371
d) Vous installez une nouvelle fonctionnalité qui permet d’additionner des registres dans votre programme. Pour le tester, vous décidez d’additionner un registre contenant la valeur 0xCAFE et un autre registre contenant la valeur 0xA15. Quelle est la valeur obtenue en hexadécimale (toujours sur 16 bits) ?
CAFE
0A15
E + 5 = 19, on a une retenue de 1 et 0003 comme résultat (19 - 16).
F + 1 (retenue) + 1 = 17, on a encore une retenue et 0013 comme résultat (17 - 16).
A + 1 (retenue) + A = 21, toujours une retenue et 0513 comme résultat (21-16).
C + 1 (retenue) = D, pas de retenue donc c’est le résultat final.
La réponse est : 0xD513.
Question 3 :
Voici un programme mystère :
indice : regardez les codes ascii importants
#Appels système utilisées
.eqv printInt, 1
.eqv exit, 10
li s0,0
loop:
li a7,12
ecall
li t0,33 #33 = ! dans la table ASCII
beq a0,t0,fin
li t0,65
blt a0,t0,loop
li t0,90
bgt a0,t0,loop
addi s0,s0,1
j loop
fin:
li a7,printInt
mv a0,s0
ecall
li a7,exit
li a0,0
ecall
a) Décrivez ce que ce programme fait en quelques phrases.
En faisant référence à notre feuille aide mémoire RISC-V, on peut observer que 65 est le char ASCII A
. Comme 90 est A + 25, on sait qu’il s’agit de Z
. Ainsi, quand le char saisie est une lettre majuscule, on incrémente le compteur s0 de 1. Ce programme compte donc le nombre de lettres majuscules présentes jusqu’à ce que l’utilisateur saisisse !
, ou le programme se termine.
b) Qu’allons nous obtenir si on entre dans ce programme “BONJOUR tout le monde!” ?
7, puisqu’il y a 7 lettres majuscules.
Question 4 :
Un problème survient dans votre ordinateur et vous perdez votre dernière heure de travail. Des morceaux de prochainElement
et de finProg
ont été effacés. Voici ce qu’il vous reste comme code :
#Appels système utilisées
.eqv printInt, 1
.eqv exit, 10
.data
# Tableau de 10 nombres (64bits signés)
tab: .dword 10, 10, -6, 20, 1, 1, 8, 800, -800, -2
.eqv tablen, 10 # Taille du tableau (en nombre d'éléments)
.text
la s1, tab # Adresse de l'élément courant
li s2, tablen # Nombre d'éléments restants à traiter
ld s0, 0(s1) # on initialise avec le premier élément
loop:
ld s3, 0(s1) # Valeur de l'élément courant
blt s0, s3, prochainElement
mv s0, s3
prochainElement:
addi s1,s1,8 #on travaille en .dword, donc 8 octets
addi s2,s2,-1
bgtz s2, loop #après les 10 éléments circulés, on ne rentre plus dans cette condition, on continue dans finProg
finProg:
mv a0,s0
call printInt
call exit
a) Expliquez ce que fait ce programme (pas besoin des fonctions effacées pour répondre).
Il lit un tableau de nombre entiers et retourne la valeur la plus petite (les nombres négatifs inclus).
b) Qu’est-ce que ce programme retourne si l’on prends les données présentes dans tab
?
Il retournerait le nombre -800
puisqu’il s’agit de la plus petite valeur présente
c) Écrivez les fonctions prochainElement
et finProg
directement dans le code. Vous devriez avoir 6 ou 8 lignes d’instruction, pas plus (6 avec libs.s, 8 sans).
d) Vous souhaitez passer ceci dans votre programme : tab2: .word 11, 5, -19, 24, 30, -30, 90, -150, 400, -12
.
Cependant, votre programme se termine avec des erreurs. Pourquoi? Comment peut-on corriger cette erreur?
Le format du tableau fourni n’est pas correct. Nous avons donc deux options. 1- Changer notre tableau en tableau de .dword pour correspondre à l’alignement de 8 octets. 2- On pourrait aussi modifier le code afin d’avoir addi s1,s1,4
pour s’adapter à un tableau de .word. Ce n’est cependant pas la solution idéale puisqu’il faut modifier le code, mais ça reste une option valide.
Question 5 :
Soit le morceau de programme suivant exécuté sur une architecture RISC-V 64 bits. Sachant que l’étiquette foo est associée à l’adresse 0x10010000, indiquez quel est le contenu (hexadécimal) final des registres demandés. Voici le code en question :
.data
.eqv bar, 0x95
foo: .byte bar, 0x87, 0x10, 0x01
.text
la s0, foo
lw s1, 0(s0)
li s2, bar
lb s3, 0(s0)
ebreak # Fait une pause dans l'éxécution
Lors du ebreak, quelle valeur a….
- Le registre s0?
0x10010000 pas d’attrapes, on reprends l’adresse tel qu’elle est - Le registre s1?
0x1108795 via le petit boutisme - Le registre s2?
0x95 pas d’attrapes, c’est un load immediate - Le registre s3?
0xFFFFFFFFFFFFFF95 le lb copie le bit de poids fort de 0x95 et 9 = 1001 donc on remplit les 7 autres octets avec des 1
Question 6 :
En faisant référence à la feuille aide mémoire des instructions RARS :
a) Transformez l’instruction assembleur slli s0,s8,12
en code machine (donnez la valeur hexadécimale).
L’instruction SLLI est une instruction de type I, son format est donc :
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
son opcode est représenté par 0x13, soit 0001 0011 en binaire.
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
001 0011 |
Ensuite, observons le rd (s0 dans ce cas-ci).
Il s’agit de s0, soit x8. 8 représenté en binaire est = 1000.
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
0 1000 | 001 0011 |
fn3 est représenté par 0x1, soit 001.
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
001 | 0 1000 | 001 0011 |
notre rs1 ici s’agit de s8. s8 est le registre # 24, 24 en binaire est : 11000
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
11000 | 001 | 0 1000 | 001 0011 |
finalement, on transforme notre valeur imm en binaire. 12 = 1100. Le registre est sur 12 bits, on à donc :
imm [11:0] | rs1 | fn3 | rd | opcode |
---|---|---|---|---|
0000 0000 1100 | 11000 | 001 | 0 1000 | 001 0011 |
Ainsi, transformons 0000 0000 1100 1100 0001 0100 0001 0011
en hexadécimal. En se fiant à notre table de conversion nous avons : 0x00CC1413
.
b) Quelle instruction assembleur correspond à l’instruction machine de valeur 0x02948433
?
On devra effectuer le contraire de ce que nous avons fait précédemment. Commencons donc par transformer 0x02948433
en binaire :
0x02948433
= 0000 0010 1001 0100 1000 0100 0011 0011
.
On peut déterminer quel type d’instruction c’est en observant les 7 derniers bits qui désignent toujours le opcode. 011 0011
est égal à 0x33
en hexa. C’est un format de type R, soit :
fn7 | rs2 | rs1 | fn3 | rd | opcode |
---|---|---|---|---|---|
011 0011 |
rd = 0 1000, soit 8 en décimal. C’est donc le registre s0 (x8).
fn3 = 000, soit 0x0.
rs1 et rs2 = 0 1001, soit 9 en décimal. C’est donc le registre s1 (x9).
fn7 = 000 0001, soit 0x01 en hexa.
le format final sera donc :
fn7 | rs2 | rs1 | fn3 | rd | opcode |
---|---|---|---|---|---|
000 0001 | 0 1001 | 0 1001 | 000 | 0 1000 | 011 0011 |
ou :
fn7 | rs2 | rs1 | fn3 | rd | opcode |
---|---|---|---|---|---|
0x01 | x9 | x9 | 0x0 | x8 | 0x33 |
Quand on regarde notre feuille RISC-V, on voit que cette instruction correspond à mul
.
L’instruction finale serait donc : mul s0,s1,s1
.
On peut vérifier le tout en retransformant notre format en code hexadécimal pour s’assurer qu’on à la bonne réponse :
0000 0010 1001 0100 1000 0100 0011 0011
= 0x02948433
Question 7 (extra) :
On travaille avec un programme qui change l’intensité d’une des couleurs présente dans un registre de 32 bits ayant le format « 0xAARRGGBB » qui correspond à un “Hex color code”. A = Alpha, R = Red, G =Green et B = Blue. Un exemple pour vous aider à comprendre serait la valeur 0x0000FF00
où la couleur verte a une valeur de 255 (le maximum) et alpha, rouge et bleu ont 0 (le minimum). Pour vous faciliter la vie, on vous demander de seulement travailler avec la couleur rouge et une partie du code est déjà présente.
Vous allez devoir implémenter deux fonctions. Voici le code qu’on vous donne :
.eqv printHex,34
.eqv exit,10
li s1,0xAABBCCDD #Registre que l'on veut modifier (ARGB = 0xAARRGGBB), fixe dans ce contexte
userInput:
li a7,5
ecall
mv s0,a0 #saisie de l'utilisateur (peut être négatif)
isolerCouleurRouge:
andi s2,s1,0x00FF0000 #isoler rouge (RR) dans s2
srli s2,s2,16 #décaler le masque à droite du registre pour
addi s2,s2,s0 #lui faire subir le addi
verifierMax:
li t0,255 #valeur max possible pour une couleur
ble s2,t0,verifierMin #si s2 > 255 on mets la valeur 255 dans s2
li s2,255
verifierMin:
li t0,0
bge s2,t0,replacerCouleur #valeur min possible pour une couleur
li s2,0 #si s2 < 0 on mets la valeur 0 dans s2
replacerCouleur:
slli s2,s2,16 #aligner le registre s2 avec la couleur rouge
andi s1, s1, 0xFF00FFFF #On doit préparer le registre s2 à l'accueil
or s2, s2, s1 #on fusionne les registres 0xAA00GGBB et 0x00RR0000
#avec l'instruction or
finProg:
mv a0,s2
li a7,printHex #print hexadecimal value (appel système de RARS)
ecall
li a0,0 #fermer le prog
li a7,exit
ecall
a) Complétez les fonctions isolerCouleurRouge
et replacerCouleur
.
b) S’il n’y avait pas de gestion d’erreur (dépassement min et max), qu’arriverait-il au registre s1
lorsqu’on lui ajoute la couleur rouge modifié (On sous-entends ici qu’on dépasse les bornes, soi une valeur plus petite que 0 ou plus grande que 255)?
La couleur rouge impacterait plus que 8 bits seulement, hors une fois l’instruction or
effectué, elle affecterait les autres couleurs, chose qui ne doit pas arriver.