A la réflexion, le programme précèdent n’est pas un bon
exemple pour apprendre l’assembleur sauf à montrer ce qu’il ne faut pas faire
sur un long programme : routines trop longues, mauvaise utilisation des
registres, etc.
Puis après 6 mois de programmation sur le Raspberry, j’ai
changé sur la perception de certaines instructions. Et donc dans ce chapitre,
je vais repartir sur les instructions de base de manipulation des registres.
Pour cela, dans un programme de test, nous écrivons 2
routines, une pour afficher les 32 bits du registre r0, et l’autre pour
afficher l’état de chaque drapeau du registre d’état (Flag Z zéro N négatif C
carry et V overflow). Dans le programme nous appelons 2 fois ces routines pour
vérifier leur neutralité. Remarquez que pour assurer la sauvegarde restauration
du registre d’état, nous utilisons les instructions mrs et msr peu fréquentes.
Vous remarquerez aussi que la routine d’affichage des drapeaux n’utilise aucun
branchement mais seulement des instructions conditionnelles.
Ensuite nous alimentons le registre r0 avec la valeur 0, 1
et -1 en utilisant l’instruction movs, le s servant à positionner les drapeaux
(son oubli est une erreur fréquente !!!). Pour la valeur zéro nous voyons
bien le flag Z passer à 1, pour la valeur -1 le flag N passer à 1, pour la
valeur 1 les 2 flags sont à zéro. Reste le cas du carry qui reste à 1 quelle
que soit la valeur !! L’instruction
mov ne doit pas le faire intervenir. Vous remarquerez que la valeur -1 est
représenté par les 32 bits à 1 ce qui représente bien cette valeur en
complément à 2 pour les valeurs signées. Si ces 32 bits représentent une valeur
non signée, c’est à vous de le savoir.
Puis nous mettons la valeur maximum 2 puissance 31 – 1 dans
r0 puis la valeur positive maximum 2 puissance 30 -1. Mais attention,
l’instruction ldr ne met pas à jour les flags, il faut pour cela effectuer une
comparaison cmp r0,#0.
Nous remarquons dans le premier cas que la valeur est
identique au – 1 précédent et le flag N à 1. Et pour la valeur maximum
positive, le flag N est bien à 0. Ajoutons 1 aux deux valeurs. Dans le premier
cas le registre contient zéro et le flag Z est à 1 et le flag N à 0. Pour
l’autre cas, le registre ne contient que des bits à 1 donc la valeur -1 et le
flag V overflow est passé à 1 ce qui indique bien qu’il y a un débordement (puisque
la valeur positive maxi + 1 ne peut pas être négative !!).
Et tiens le carry est passé à 0 !! En effet le carry
est touché lors des opérations arithmétiques (et bien sur par les comparaisons
et les déplacements/rotations de bits) sur les valeurs non signées. Dans le
premier cas, on ne voit pas qu’il est passé à 1 car il l’était déjà. Il passe à
1 car la valeur qui était à la valeur maximum non signée passe à 0 après
l’ajout du 1 ce qui est anormal et le carry le signale. Dans le 2ième
cas il passe à 0 car en non signée, l’addition est correcte. Nous pouvons revérifier
en recommençant l’ajout de 1 à la valeur maxi FFFFFFFF et le carry passe bien
de 0 à 1.
Quelquefois il est intéressant de récupérer le signe d’un
nombre dans un registre. Il suffit d’utilise pour cela l’instruction asr
r0,#31 et si la valeur est positive r0
contiendra 0 et -1 si la valeur est négative :
Voyons maintenant l’utilisation de l’instruction mvn. Elle
transfère tous les bits d’un registre en les inversant (0 devient 1 et 1
devient 0). Cela permet de l’utiliser pour convertir une valeur de positif à
négatif et inversement en ajoutant 1 au résultat.
Après, nous regardons les instructions and (ET), orr (OU),
eor(OU exclusif), et bic qui permet de mettre un bit à zero.
Et bien sûr, si nous voulons savoir si le résultat est égal
à 0 ou négatif, il faut ajouter un s à chaque instruction
Déplacement des bits :
pour déplacer les bits dans un registre nous utilisons lsl vers la
gauche et lsr vers la droite en indiquant le nombre de déplacement soir par une
valeur immédiate soit par un registre. Mais c’est bizarre dans mon premiers
cas, ce n’est pas 2 bits que j’ai
déplacé mais les bits 1011 . Mais c’est
bien sûr j’ai mis 11 dans le registre r0 alors que j’aurais dû mettre 3 soit 11
en binaire (ou j’aurais dû mettre 0b11 ce qui est plus compréhensible).
Le programme montre différents déplacements dont les
derniers avec test des flags pour voir l’alimentation du carry. Ces
instructions servent aussi à effectuer des multiplications par des puissances
de 2 (lsl) ou des divisions par des puissances de 2 (lsr).Par exemple prenons 3
soit 11 en binaire et si nous le déplaçons d’un bit à gauche par lsr r0,#1 il
devient 110 en binaire ce qui correspond à 6. Mais attention cela ne marche que
pour les valeurs non signées. Pour les valeurs signées, il n’y a que l’instruction
asr qui permet d’effectuer des déplacements vers la droite.
Enfin il existe une possibilité de modifier le registre
servant de 2ième opérande avant l’opération principale en utilisant
les instructions lsl,lsr (barrel shifter en anglais) Par exemple, multiplier le
2ième opérande par 2 avant de le mettre dans le registre
r0 : mov r0,r1,lsl #1 . Nous avons
déjà vu cette possibilité lors des accès à un tableau en mémoire : ldr
r0,[r1,r2, lsl #2] qui permet d’accéder au poste d’indice R2 et situé à 4 * r2
octets du début de la table donnée par r1. Cela aussi permet d’effectuer des
multiplications rapides pour les valeurs supérieure de 1 à une puissance de 2.
Par exemple pour multiplier par 3 la valeur de r1 il faut saisir add r0,r1,r1
lsl #1 ou par 5 add r0,r1,r1 lsl
#2.
Puis nous essayons les différentes possibilités de
modification d’un ou plusieurs bits. Ces manipulations permettent d’avoir dans
un seul registre 32 valeurs logiques ce qui occupe peu de place et avec peu
d’instructions.
Exercice : Sur
un site internet, j’ai vu qu’en plus de l’état des flags, la routine affichait
l’état (0 ou 1) de chaque condition (eq, ne, gt, lt, etc), faire pareil pour
les 15 conditions (inutile pour al et ne).
Le deuxième programme teste les instructions de
rotation : soit ror pour effectuer des rotations vers la droite du nombre
de bits indiqués et rrx qui effectue une seule rotation à droite, et le contenu du Carry passe dans la position 31 et le bit 0
passe dans le Carry (et il faut ajouter s pour le trouver dans les flags). Il
n’y a pas de rotations vers la gauche (mais une rotation à gauche équivaut à 32
– 1 rotations à droite).
Enfin voici des exemples pour stocker et récupérer 4 octets
dans un registre. En effet quelquefois, les données à gérer ne dépassent par la
valeur 255 et donc il est possible de stocker 4 valeurs dans un registre ce qui
économise les registres. Danc cette exemple, nous voyons comment stocker les
octets un par un sans toucher aux autres octets du registre et les récupérer
sans dégrader le registre d’origine.
Enfin nous terminons le programme par quelques instructions
particulières : comptage des zéros à gauche dans un registre.
Aucun commentaire:
Enregistrer un commentaire