mardi 3 avril 2018

Chapitre 22 : Manipulations de bits, affichage des flags


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.
Enfin nous terminons par des exemples des instructions tst et tsq.
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