vendredi 19 avril 2019

Suite : registres et nombres entiers


Nous avons vu que les bits du registre peuvent aussi représenter des nombres entiers qui vont de 2 puissance 0 à 2 puissance 31 -1  soit 4 294 967 295 et donc l’assembleur permet d’effectuer des opérations arithmétiques sur ces nombres. Pour faciliter les explications nous allons ajouter à notre programme une procédure qui effectue l’affichage du registre en base 10. Je vous demande donc de recopier tel quel ces instructions à la fin du programme précédent.

       
/******************************************************************/
/*     Affichage d'un registre en décimal                                 */
/******************************************************************/
/* r0 contient la valeur  */
affichageReg10:
            push {r0-r6,lr}    /* save des registres */
            ldr r5,iAdrsZoneDec
            mov r4,#10
            mov r2,r0
    mov r1,#10   /* conversion decimale */
1:        /* debut de boucle de conversion */
    mov r0,r2    /* copie nombre départ ou quotients successifs */
            bl division /* division par le facteur de conversion */
            add r3,#48   /* car c'est un chiffre */
    strb r3,[r5,r4]  /* stockage du byte au debut zone (r5) + la position (r4) */
            sub r4,r4,#1   /* position précedente */
            cmp r2,#0      /* arret si quotient est égale à zero */
            bne 1b         
            /* mais il faut completer le debut de la zone avec des blancs */
            mov r3,#' '   /* caractere espace */    
2:       
    strb r3,[r5,r4]  /* stockage du byte  */
            subs r4,r4,#1   /* position précedente */
            bge 2b        /* boucle si r4 plus grand ou egal a zero */
           
            @ affichage
            ldr r0,iAdrsZonemessDec 
            bl affichageMess
           
100:  
            pop {r0-r6,lr}    /* restaur des  2 registres */
    bx lr                   /* retour procedure */                  
iAdrsZoneDec: .int sZoneDec      
iAdrsZonemessDec: .int sMessAffDec
/*=============================================*/
/* division entiere non signée                */
/*============================================*/
division:
    /* r0 contains N */
    /* r1 contains D */
    /* r2 contains Q */
    /* r3 contains R */
    push {r4, lr}
    mov r2, #0                 /* r2 ← 0 */
    mov r3, #0                 /* r3 ← 0 */
    mov r4, #32                /* r4 ← 32 */
    b 2f
1:
    movs r0, r0, LSL #1    /* r0 ← r0 << 1 updating cpsr (sets C if 31st bit of r0 was 1) */
    adc r3, r3, r3         /* r3 ← r3 + r3 + C. This is equivalent to r3 ← (r3 << 1) + C */
    cmp r3, r1             /* compute r3 - r1 and update cpsr */
    subhs r3, r3, r1       /* if r3 >= r1 (C=1) then r3 ← r3 - r1 */
    adc r2, r2, r2         /* r2 ← r2 + r2 + C. This is equivalent to r2 ← (r2 << 1) + C */
2:
    subs r4, r4, #1        /* r4 ← r4 - 1 */
    bpl 1b            /* if r4 >= 0 (N=0) then branch to .Lloop1 */
    pop {r4, lr}
    bx lr          

et de remplacer l'instruction bl affichageReg2 par bl affichageReg10 dans la partie main.
 
Addition :
Nous pouvons additionner une valeur immédiate au registre r0  par exemple mettre 10 dans r0 puis ajouter 25  par l’instruction add r0,#25 et afficher le contenu du registre en décimal et le résultat est bien 35.  Maintenant nous mettons la valeur maximale 2 puissance 31 – 1 dans le registre et ajoutons 5. Maintenant le résultat est 4 !! car nous avons dépassé la capacité maximale du registre. Mais heureusement c’est prévu : l’indicateur de retenue (carry) peut être positionné (en ajoutant le s au code instruction) pour signaler ce dépassement. Et il y a même une instruction adc qui permet d’ajouter le carry à l’addition suivante.
Soustraction :
De même nous pouvons effectuer des soustractions avec l’instruction sub r0,#5. Mais là aussi, il peut se poser le problème inverse, le nombre calculé peut être inférieur à 0. Dans ce cas un nouvel indicateur du registre d’état (N pour négatif) le signale. Mais l’assembleur permet de travailler avec des nombres négatifs en considérant tous les nombres qui ont le bit 31 à 1 comme négatif et en calculant leur valeur comme complément à deux.
Par exemple le nombre négatif – 1 sera représenté par 2 << 31 – 1 soit FFFFFFFF en hexadécimal et le plus petit nombre négatif par 80000000 en hexa soit -2 147 483 648 Et comme nous avons utilisé le bit 31 comme signe, le plus grand nombre positif possible sera 2<<30 – 1 soit 7FFFFFFF en hexa soit 2 147 483 647 en décimal.
Ceci pose un problème car si on effectue l’addition 2147483647 + 1 soit cela représentera le nombre + 2147483648 si on considéré que les nombres sont non signés soit le nombre – 2147483648 si on considère des nombres signés et bien sûr ce résultat est faux dans ce cas. Là aussi un indicateur du registre d’état (V comme overflow dépassement en français) indiquera ce dépassement et bien sûr ce sera à nous de décider quoi faire dans ce cas.
Mais comment le processeur sait-il s’il s’agit de nombres signés ou non signés ? Mais il ne le sait pas et il s’en moque !!! c’est à vous de savoir à tout moment si vous voulez effectuer des opérations sur des nombres signés ou sur des nombres non signés et en fonction des indicateurs positionnés de prendre les mesures qui s’imposent. Et il faudra bien choisir les codes conditions qui devront s 'appliquer. Nous trouvons :
Pour les nombres non signés :
Hs :  pour supérieur ou égal
Hi : pour supérieur
Ls : pour inférieur ou égal
Lo : pour inférieur
Et pour les nombres signés :
Ge : pour supérieur ou égal
Gt : pour supérieur
Le : pour inférieur ou égal
Lt : pour inférieur
Il existe aussi une instruction de soustraction intéressante RSB qui permet de soustraire le contenu du registre d’une valeur immédiate :
Mov r0,#4
Rsb r0,#32    @ soustrait de 32 la valeur de r0

Multiplication :
Il n’est pas possible d’effectuer une multiplication avec un seul registre et une valeur immédiate.
Mais nous avons vu que nous pouvons déplacer les bits du registre à gauche et à droite avec les instructions lsl et lsr.
Par exemple :
Mov r0,#5
Lsl r0,#2    déplacement de 2 positions sur la gauche
Le résultat est 5 * (2puissance 2) = 5 * 4 = 20
Et maintenant si on déplace les bits à droite d’une position
Lsr r0,#1 le résultat est 20 / (2 puissance 1) = 10
Attention cela ne fonctionne que pour des nombres non signés.  Pour diviser un nombre signé il faut utiliser l’instruction asr qui effectue un déplacement à droite mais en dupliquant aussi sur la droite le bit 31 cad le bit du signe.
Pour multiplier un nombre signé, il n’y a pas d’instruction spéciale car l’instruction fonctionne si le résultat ne dépasse pas 2 puissance 30 ( dans ce cas le flag V overflow est positionné)
Division :
Il n’y a pas d’instruction de  division pour la plupart des processeurs Arm.

Et voilà, nous avons vu tout ce qu’un registre peut traiter : des bits et des nombres entiers. Mais et le reste !!!  Et bien tout ce que peut contenir un registre est soit géré par une convention soit défini par vous-même.
Par exemple les caractères A à Z sont codés sur un octet suivant le code ASCII mais ils peuvent être codés suivant d’autres codes, UFT8 par exemple.
Vous pouvez mettre dans un registre une couleur de pixel suivant la codification RGB 1 octet pour la luminosité, 1 octet pour la couleur rouge 1 pour le vert et 1 pour le bleu.
Il est aussi possible de mettre un nombre en virgule flottante en respectant la norme IEEE 754 mais ne croyez pas effectuer une addition avec l’instruction add sur ces nombres !! Nous verrons cela dans l’utilisation du coprocesseur.
Et il existe bien d’autres conventions. Et vous, vous pouvez stocker ce que vous voulez dans un registre : des secondes, des adresses mémoires, des caractères etc mais ce sera toujours à vous de savoir ce qu’il contient pour éviter d’additionner des poireaux et des carottes !!

Un dernier point avec les valeurs immédiates : ce sont souvent des constantes et l’assembleur dispose d’une pseudo instruction pour définir celles ci. C’est l’instruction :
.equ  NOM,  valeur  qui permet d’affecter à NOM la valeur valeur
Par exemple .equ NBPOINTS, 5 et nous l’utilisons comme ceci
Mov r0,#NBPOINTS
Le nom d’une constante est souvent en majuscule ce qui permet de l’identifier rapidement. La déclaration est faite aussi souvent  en début du programme.

Nous pouvons aussi nous interroger sur le registre d’état : c’est un registre de 32 bits dont le nom est cpsr (Current Program Status Register). Il n’est pas possible d’effectuer directement des opérations logiques sur ce registre. Mais nous disposons de 2 instructions spéciales :
mrs r0,cpsr  qui permet de récupérer dans r0 le contenu du registre d’état
Et msr cpsr,r0 qui permet de mettre à jour ce registre. Mais attention ces manipulations peuvent être dangereuses en cas de modification d’autres bits.
De toute façon, nous aurons très rarement à modifier ce registre directement.

Aucun commentaire:

Enregistrer un commentaire