INF2170

ORGANISATION DES ORDINATEURS ET ASSEMBLEUR

evolution

'Quelque part, quelque chose a terriblement mal tourné ...'


Liste des exemples

Exemples simples

Instructions un peu plus complexes

Mode indexé et pointeurs

Pile et fonctions

Nombres à virgule flottante

Autres


Exemple simple d'addition dans pep8

        deci    nb,d    ; Saisir un nombre décimal au clavier, et le stocker dans
                        ;  la variable "nb".
                        ; Deci stocke le nombre sur 16 bits (1 word), alors la variable
                        ;  doit être de type ".WORD"

        lda     nb,d    ; On charge ce nombre dans le registre A, car le processeur
                        ;  peut seulement faire des opérations sur ce qui est contenu dans
                        ;  ses registres.

        adda    10,i    ; On ajoute 10 au nombre qui est contenu dans A

        sta     nb,d    ; Finalement, on transfère le contenu de A dans la variable "nb"
                        ;  pour la mettre à jour avec la nouvelle valeur.

        deco    nb,d    ; "Deco" affiche en décimal le nombre contenu dans "nb"

        stop            ; Arrêt de l'exécution. Sans cette instruction, le processeur
                        ;  continuerait à exécuter le reste de la mémoire, variables
                        ;  comprises!


nb:     .WORD   0       ; "nb" est une variable de type WORD, qui est initialisée à 0 au
                        ; début de l'exécution.


.END                    ; ".END" est une directive qui délimite la fin du programme.

Remarquez que tout ce qui apparaît après la directive .END est considéré comme
un commentaire par l'assembleur. Il n'est donc pas nécessaire de précéder ces lignes
par des ";".

Exemple simple d'addition avec affichage de caractères

            stro    msg1,d
            deci    nb1,d
            deci    nb2,d

            lda     nb1,d
            adda    nb2,d
            sta     res,d

            stro    msg2,d
            deco    res,d
            stop



msg1:       .ASCII  "Veuillez entrer deux nombres \n\x00"
msg2:       .ASCII  "\nLa somme de"                         ; On peut ecrire un message
            .ASCII  " ces deux nombres est \x00"            ;  sur plusieurs lignes...

nb1:        .WORD   0
nb2:        .WORD   0
res:        .WORD   0

.END

Exemple de boucle

         ldx    -5,i        ;On se sert de X comme compteur de boucle

boucle:  stx    temp,d      ;Puisqu'on ne peut pas afficher le contenu
                            ; de X directement, il faut le stocker dans une
                            ; variable intermédiaire.
         deco   temp,d      ;Afficher le compteur de boucle
         charo  0x0A,i      ;Retour à la ligne

         addx   1,i
         cpx    5,i
         brle   boucle      ;BRLE = BR si plus petit ou égal

         stop

temp:    .WORD  0           ;Variable bidon qui sert à l'affichage


         .END

Multiplication par additions successives

            deci    I,d         ;1er nombre (positif!)
            deci    J,d         ;2e nombre

            ldx     I,d         ;On fera I tours de boucle. X servira de compteur
            lda     0,i         ;Initialiser l'accumulateur
encore:     adda    J,d
            subx    1,i
            cpx     0x0000,i 
            brne    encore      ; Faire un autre tour si non nul
            sta     IJ,d
            deco    IJ,d
            stop

I:          .WORD   0
J:          .WORD   0
IJ:         .WORD   0


            .END

Division par soustractions successives

;Division entière, calcul du Quotient (Total / Nb) et du Reste
;Entrée des données
            deci    Total,d
            deci    Nb, d

            lda     Total, d    ;Registre A pour Total
            ldx     Quot, d     ;Registre X pour Quot

encore:     cpa     Nb,d 
            brlt    fin         ;Aller à fin si zéro
            suba    Nb, d       
            addx    0x0001,i 
            br      encore
fin:        stx     Quot, d
            sta     Reste, d
            deco    Quot, d
            charo   0x0A,i      ;Ligne suivante         
            deco    Reste, d            
            stop

Total:      .WORD   0
Nb:         .WORD   0
Quot:       .WORD   0
Reste:      .WORD   0


            .END

Exemple de saisie et d'affichage d'un caractère

            stro    msg1,d
            chari   car,d
            
            stro    msg2,d
            charo   car,d
            stro    msg3,d


            ldbytea car,d       ;* Ne regardez pas ces quatre instructions de
            sta     -2,s        ;*  trop près: elles servent à  afficher le code
            subsp   2,i         ;*  ascii hexadécimal du caractère.
            call    Hexoutb     ;*

            charo   '\"',i
            stop


car:        .BYTE   0

msg1:       .ASCII  "Veuillez entrer un caractère: \n\x00"
msg2:       .ASCII  "\nLe caractère entre est: \"\x00"
msg3:       .ASCII  "\"\nEt son code ascii est: \"\x00"

            ; LA FONCTION HEXOUTB EST INCLUSE DANS LE .TXT

Déterminer si le caractère saisi est une lettre ou un chiffre

          chari    car,d
          charo    '\n',i
          lda      0,i          ; Effacer la partie haute du registre A
          ldbytea  car,d        ; Et charger le caractere dans la partie basse

          cpa      0x30,i       ;'0' On aurait aussi pu ecrire "cpa   '0',i"
          brlt     invalide
          cpa      0x39,i       ;'9'
          brle     chiffre

          cpa      0x41,i       ;'A'
          brlt     invalide
          cpa      0x5A,i       ;'Z'
          brle     lettre

          cpa      0x61,i       ;'a'
          brlt     invalide
          cpa      0x7A,i       ;'z'
          brle     lettre

invalide: stro     minval,d
          br       fin


lettre:   stro     mlettre,d
          br       fin

chiffre:  stro     mchiffre,d

fin:      stop

car:      .BYTE    0

mlettre:  .ASCII   "Lettre\x00"
mchiffre: .ASCII   "Chiffre\x00"
minval:   .ASCII   "Invalide\x00"

          .END


Changer des minuscules en majuscules, ou inversement

          ldx      -1,i         ;-1 parce que l'on rajoute 1 dès le début

          lda      0,i
next:     addx     1,i
          ldbytea  chaine,x
          breq     fin

          cpa      0x41,i       ;'A'
          brlt     next         ;pas une lettre?, on branche
          cpa      0x5A,i       ;'Z'
          brle     lettre       ; c'est une lettre -> on branche

          cpa      0x61,i       ;'a'
          brlt     next         ;pas une lettre?, on branche
          cpa      0x7A,i       ;'z'
          brle     lettre       ;c'est une lettre -> on branche

          br       next         ;arrivé ici, ça ne peut plus être une lettre...

lettre:   anda     0xDF,i       ;A AND 11011111, pour faire une lettre majuscule
;lettre:   ora      0x20,i       ;A OR  00100000, pour faire une lettre minuscule
          stbytea  chaine,x     ;remplacer le caractère dans la string
          br       next         ;et recommencer la boucle...

fin:      stro     chaine,d
          stop

chaine:   .ASCII   "Bonjour TOUT le Monde 123\x00"

          .END

Programme de test pour les instructions de shift (ASRR et ASLR) et de rotation (RORR et ROLR)

Essais intéressants à faire:


init:   .EQUATE 0x0001
nbrep:  .EQUATE 18


        ldx     nbrep,i     ; Nombre de répétitions de la boucle
        lda     init,i      ; Valeur initiale dans A
        adda    0,i         ; Reset les flags
        call    Affres

;Choisir ASLA, ASRA, ROLA ou RORA
rec:     asla                ; Décalage à gauche du registre A
;rec:     asra                ; Décalage à droite du registre A
;rec:     rola                ; Rotation à gauche du registre A
;rec:     rora                ; Rotation à droite du registre A

        call    Affres

        subx    1,i         ; Décrémenter notre compteur de 1
        brne    rec         ; Est ce que X = 0 ? si non, aller à "rec"
        stop



; Fonction d'affichage d'une ligne de résultat
; Affiche les flags et le nombre contenu dans A.
; Format: Flags, bin, hex, déc.
Affres: call    PrntFlag
        charo   ' ',i
        charo   ' ',i
        sta     -2,s
        subsp   2,i
        call    Binout      ; Afficher le nombre en base binaire
        charo   ' ',i
        charo   ' ',i
        sta     -2,s
        subsp   2,i
        call    Hexoutw     ; Afficher le nombre en base hexadécimale
        charo   ' ',i
        charo   ' ',i
        sta     -2,s
        deco    -2,s        ; Afficher le nombre en base décimale
        charo   '\n',i      ; CR/LF
        ret0

            ; LES FONCTIONS SONT INCLUSES DANS LE .TXT

Le problème du hamburger

;Le probleme du hamburger :)
;Pour preparer un hamburger, on encode 6 bits de la maniere suivante:

;bit0:      salade
;bit1:      champignons
;bits2&3:   nombre de tranches de tomates
;bit4:      piments
;bit5:      oignons

;note: les numeros de bits se lisent de droite a gauche. le bit0 est donc
;le bit le plus faible.

;Ces bits nous donnent la composition du hamburger, c'est a dire que si
;le bit4 est a 1, on veut des piments dans le hamburger, s'il est a 0,
;on ne veut pas de piments.
;Une exception est faite pour les tomates; deux bits sont utilises pour
;donner le nombre de tranches de tomates a mettre (0,1,2,3)

;On veut faire un programme qui prend en entree un nombre et qui imprime
;a l'ecran la composition du hamburger.

;Exemples:

;Le nombre entre est 19 (010011).

; 0      1       00      1       1
;bit5   bit4    bit3&2  bit1    bit0

;Le hamburger sera compose de:
;piments, champignons et salade.
;Ce que l'on verra a l'ecran:  SALADE CHAMPIGNONS PIMENTS

;Le nombre entre est 24 (011000). Le hamburger sera compose de:
;piments et deux tranches de tomate.
;Ce que l'on verra a l'ecran:  2TOMATE(S) PIMENTS


          deci     nb,d

          stro     msgrep,d

          lda      nb,d
          anda     1,i          ;A AND 000001 =>A=000001 (bin) -> salade
;                               ;               A=000000 (bin) -> pas de salade
          cpa      1,i          ;est ce que A=1 ?
          brne     psalade      ;pas de salade?
          stro     msgsal,d

psalade:  lda      nb,d         ;le nombre a ete modifie, alors on le recharge
          anda     2,i          ;A AND 000010 =>A=000010 -> champignons,
;                               ;               A=000000 -> pas de champignons
          cpa      2,i          ;est ce que A=10 ?
          brne     pchamp       ;pas de champignons?
          stro     msgcham,d

pchamp:   lda      nb,d
          anda     12,i         ;A AND 001100 =>A=000000 -> pas de tomates,
;                               ;               A=000100 -> 1 tomate,
;                               ;               A=001000 -> 2 tomates,
;                               ;               A=001100 -> 3 tomates,
          asra                  ;decalage de A a droite deux fois, pour que
          asra                  ;le nombre de tomates arrive dans les premiers bits
;         ;cpa      0,i         ;on veut des tomates? (instruction pas nécessaire)
          breq     ptomates     ;non? ->branchement, oui? -> on continue
          sta      tom,d        ;stocker pour imprimer
          deco     tom,d
          stro     msgtom,d

ptomates: lda      nb,d
          anda     16,i         ;A AND 010000 =>A=010000 -> piments,
;                               ;               A=000000 -> pas de piments
;                               ;PAS DE cpa  RAISON: On aurait pu suivre le
;                               ;meme schema que pour la salade et les champignons
;                               ;mais il existe une maniere plus efficace de le
;                               ;faire: apres avoir fait le AND, si A vaut zero,
;                               ;le flag Z (zero) sera leve (c-a-d que Z vaudra 1).
;                               ;On peut donc determiner le resultat de
;                               ;l'operation sans faire de cpa  raison.
;                               ;Mais il faut utiliser l'instruction BREQ
;                               ;au lieu de BRNE.
;                               ;BRNE fait un branchement si Z=0 (nombre non-nul)
;                               ;BREQ fait un branchement si Z=1 (zero)
          breq     ppiments     ;pas de piments?
          stro     msgpim,d

ppiments: lda      nb,d
          anda     32,i         ;A AND 100000 =>A=100000 -> oignons,
;                               ;               A=000000 -> pas d'oignons
          breq     poignons     ;pas d'oignons?
          stro     msgoign,d

poignons: stop



nb:       .WORD    0
tom:      .WORD    0

msgrep:   .ASCII   "Le hamburger sera constitue de:\n\x00"
msgsal:   .ASCII   "Salade  \x00"
msgcham:  .ASCII   "Champignons  \x00"
msgtom:   .ASCII   " Tomate(s)  \x00"
msgpim:   .ASCII   "Piments  \x00"
msgoign:  .ASCII   "Oignons\x00"


          .END

Exemple simple d'affichage d'une chaine de caractères et d'utilisation du mode indexé.

         ldx     0,i          ;X servira de compteur et d'index
         lda     0,i          ;A servira à vérifier si on est à la fin
                              ; de la string
rep:     ldbytea chaine,x
         breq    fin
         charo   chaine,x
         addx    1,i
         br      rep

fin:     stop

chaine:  .ASCII  "Bonjour\x00"

         .END

Exemple simple de saisie de string et d'utilisation du mode indexé

          ldx      0,i
          lda      0,i

nxtinput: chari    chaine,x     ;charger le caractère dans la string
          ldbytea  chaine,x
          breq     sortie       ;A = 0000 ? (Pour compatibilité avec le mode Batch IO)
          cpa      0x0A,i       ;A = 000A ? (0A est un "ENTER" en ASCII)
          breq     sortie       ;Oui -> on sort de la boucle
          addx     1,i          ;+1 à notre compteur
          br       nxtinput

sortie:   lda      0,i
          stbytea  chaine,x     ;Remplacer le caractère "0A" par "00" pour marquer la fin
;                               ;de la string

          stro     str,d
          stro     chaine,d
          charo    '"',i

          stop

str:      .ASCII   "La chaine saisie est: \"\x00"
chaine:   .BLOCK   50           ;50 caractères maximum

          .END

Exemple de structure CASE en Pep8

          stro     msgQuest,d
          chari    car,d
          ldbytex  car,d
          andx     0x00DF,i     ;Majuscule + effacer partie haute de X

          cpx      'A',i
          brlt     invalide
          cpx      'C',i
          brgt     invalide     ;'A' <= X <= 'C'

          subx     'A',i        ;A=0, B=1, C=2
          aslx                  ;A=0, B=2, C=4  (aligne selon WORDS)

          br       tablebr,x    ;A->casA, B->casB, C->casC

casA:     lda      10,i
          adda     10,i
          br       fincase

casB:     lda      -20,i
          asla
          br       fincase

casC:     lda      5,i
          sta      temp,d
          asla                  ; = *2
          asla                  ; = *4
          adda     temp,d       ; = *5
          asla                  ; = *10
          br       fincase

invalide: stro     msgInval,d
          lda      0,i


fincase:  sta      temp,d
          stro     msgRepon,d

          deco     temp,d
          stop


;-------------------------Variables-----------------------------

car:      .BYTE    0
temp:     .WORD    0


tablebr:  .ADDRSS  casA         ;Tableau des branchements pour le CASE
          .ADDRSS  casB
          .ADDRSS  casC


msgQuest: .ASCII   "Voulez vous que pep8 calcule:\n"
          .ASCII   "A) 10 + 10 = ?\n"
          .ASCII   "B) -20 * 2 = ?\n"
          .ASCII   "C) 5 * 10 = ?\n"
          .ASCII   "\nChoix: \x00"

msgRepon: .ASCII   "\n\nReponse: \x00"

msgInval: .ASCII   "     *Choix invalide*\x00"


          .END

Recherche d'un nombre à  l'intérieur de plusieurs tableaux

          lda      0,i
          sta      tabact,d     ; On commence au 1er tableau
          deci     nb,d

nexttab:  ldx      tabact,d
          cpx      MAXTAB,i     ; est ce qu'on a dépassé le dernier tableau?
          brge     pastrouv
          ldx      tabadrs,x    ; X = tabadrs[tabact]
          lda      tabact,d
          adda     2,i          ; On incrémente la variable pour le prochain tour de boucle
          sta      tabact,d

          lda      nb,d
          cpa      0,x
          breq     trouve0
          cpa      2,x
          breq     trouve1
          cpa      4,x
          breq     trouve2
          cpa      6,x
          breq     trouve3
          br       nexttab

pastrouv: stro     mpastrou,d
          br       fin
trouve0:  lda      1,i
          br       trouve
trouve1:  lda      2,i
          br       trouve
trouve2:  lda      3,i
          br       trouve
trouve3:  lda      4,i
          br       trouve

trouve:   stro     mtrouve1,d
          sta      tmp,d
          deco     tmp,d
          stro     mtrouve2,d
          lda      tabact,d
          asra
          sta      tmp,d
          deco     tmp,d
fin:      stop


tabact:   .WORD    0
tmp:      .WORD    0
nb:       .WORD    0

mtrouve1: .ASCII   "Le nombre a ete trouve a la case #\x00"
mtrouve2: .ASCII   " du tableau #\x00"
mpastrou: .ASCII   "Le nombre n'a pas ete trouve\x00"

tbl1:     .WORD    100
          .WORD    200
          .WORD    300
          .WORD    400
tbl2:     .WORD    10
          .WORD    20
          .WORD    30
          .WORD    40
tbl3:     .WORD    -1
          .WORD    -2
          .WORD    -3
          .WORD    -4
tbl4:     .WORD    10000
          .WORD    20000
          .WORD    45
          .WORD    240
tbl5:     .WORD    33
          .WORD    44
          .WORD    55
          .WORD    66

MAXTAB:   .EQUATE  10           ; Nombre de tableaux *2

tabadrs:  .ADDRSS  tbl1
          .ADDRSS  tbl2
          .ADDRSS  tbl3
          .ADDRSS  tbl4
          .ADDRSS  tbl5

          .END


Comment créer une fonction complète

  1. Déterminer quelles sont les entrées et sorties

    Entrées: Côté1 et Côté2 (longueurs des 2 côtés différents du rectangle)
    Sortie: Périmètre du rectangle
  2. Écrire la fonction avec des "variables" en mode Stack sans s'occuper de rien

                lda     COTE1,s
                asla            ;cote1 * 2
                sta     TEMP,s
                lda     COTE2,s
                asla            ;cote2 * 2
                adda    TEMP,s  ;cote2 + cote1
                sta     PERIM,s ;stocker la réponse dans la variable qui sera retournée

    Notes:

    • Si vous vous servez de variables globales (mais ce n'est pas recommandé), il faut les adresser en mode direct (",d" pas ",s") pour accéder à leur contenu
    • Vous pouvez inventer des variables au fur et à mesure. On les déclarera plus tard.
  3. Dessiner la pile

    À gauche: index de la pile AVANT l'appel de la fonction
    À droite: index de la pile à l'INTÉRIEUR de la fonction

    ???
    REGX 0 Registre X original
    REGA 2 Registre A original
    TEMP 4 Variable temporaire
    RET 6 Adresse de retour
    -6 COTE2 8 Longueur du deuxième côté
    -4 COTE1 10 Longueur du premier côté
    -2 PERIM 12 Périmètre du Rectangle
    0 ???

    Notes:

    • Placer les variables qui sont retournées par la fonction le plus bas possible dans la pile (en rouge)
    • Placer les variables passées en Entrée à la fonction juste au dessus (en vert)
    • L'adresse de retour arrive juste au-dessus des paramètres de la fonction
    • Après l'adresse de retour, placez vos variables locales dans l'ordre qui vous semble le plus logique (en bleu)
    • Écrire les index pour l'appel de la fonction et pour l'intérieur de la fonction
  4. Écrire les EQUATEs

    REGX:       .EQUATE 0       ;Registre X original
    REGA:       .EQUATE 2       ;Registre A original
    TEMP:       .EQUATE 4       ;Variable temporaire
    RET:        .EQUATE 6       ;Adresse de retour
    COTE2:      .EQUATE 8       ;Longueur du 2e coté    (Entrée)
    COTE1:      .EQUATE 10      ;Longueur du 1er coté   (Entrée)
    PERIM:      .EQUATE 12      ;Périmètre du rectangle (Sortie)
  5. Écrire le début et la fin de la fonction

    ;Début de la fonction:
    PerimRec:   subsp   6,i     ;réserver 6 octets pour les variables locales (REGX, REGA et TEMP)
                sta     REGA,s  ;sauver l'état du registre A
                stx     REGX,s  ;sauver l'état du registre X
    
    ;Fin de la fonction:
                lda     RET,s   ;récupérer l'adresse de retour
                sta     COTE1,s ;et la stocker juste au dessus de PERIM
                lda     REGA,s  ;récupérer les registres A et X originaux
                ldx     REGX,s
                addsp   10,i    ;et nettoyer la pile (on veut que SP pointe le nouvel emplacement
                                ; de RET (à COTE1, donc +10)
                ret0

    Note:

    • On déplace l'adresse de retour le plus près possible des paramètres de sortie (c'est pour ça qu'on les a placés en bas de la pile), de manière à ce que la pile ne contienne que les variables à retourner après l'exécution de la fonction.
    • Si le nombre d'octets qu'on doit redonner à la pile avant de revenir de la fonction est inférieur à 8, on peut utiliser l'instruction "retN" où N est le nombre d'octets à dépiler avant le retour. Dans cet exemple, on doit dépiler 10 octets. Puisque l'instruction "ret10" n'existe pas, on doit faire:
                  addsp   10,i
                  ret0
    • Le chiffre suivant l'instruction "ret" indique le nombre d'octets à dépiler, mais il est limité à 7.
  6. Pour finir, tout rassembler (et placer une description de la fonction!)

    REGX:       .EQUATE 0       ;Registre X original
    REGA:       .EQUATE 2       ;Registre A original
    TEMP:       .EQUATE 4       ;Variable temporaire
    RET:        .EQUATE 6       ;Adresse de retour
    COTE2:      .EQUATE 8       ;Longueur du 2e coté    (Entrée)
    COTE1:      .EQUATE 10      ;Longueur du 1er coté   (Entrée)
    PERIM:      .EQUATE 12      ;Périmètre du rectangle (Sortie)
    
    ;Début de la fonction:
    PerimRec:   subsp   6,i     ;réserver 6 octets pour les variables locales (REGX, REGA et TEMP)
                sta     REGA,s  ;sauver l'état du registre A
                stx     REGX,s  ;sauver l'état du registre X
    
                lda     COTE1,s
                asla            ;cote1 * 2
                sta     TEMP,s
                lda     COTE2,s
                asla            ;cote2 * 2
                adda    TEMP,s  ;cote2 + cote1
                sta     PERIM,s ;stocker la réponse dans la variable qui sera retournée
    
    ;Fin de la fonction:
                lda     RET,s   ;récupérer l'adresse de retour
                sta     COTE1,s ;et la stocker juste au dessus de PERIM
                lda     REGA,s  ;récupérer les registres A et X originaux
                ldx     REGX,s
                addsp   10,i    ;et nettoyer la pile (on veut que SP pointe le nouvel emplacement
                                ; de RET (à COTE1, donc +10)
                ret0

    Notes:

    • Ne jamais faire de branchement vers du code qui est à l'extérieur de la fonction.
    • Toute sortie de la fonction doit nécessairement passer par la "Fin de la fonction"
  7. Exemple d'appel de la fonction

                lda     lgcote1,d
                sta     -4,s      ;stocker le coté 1 à la case COTE1
                lda     lgcote2,d
                sta     -6,s      ;stocker le coté 2 à la case COTE2
                subsp   6,i       ;mettre à jour le pointeur de pile (3 variables -> 6 octets)
                call    PerimRec
                lda     0,s       ;A = Périmètre
                sta     lgperim,d
                addsp   2,i       ;nettoyer la pile

    Notes:

    • Avant l'appel de la fonction, on fait un "subsp 6,i" parce qu'il y a 3 paramètres à la fonction (PERIM, COTE1 et COTE2). On compte autant les paramètres en entrée qu'en sortie. 3 Words, donc 6 octets.
    • Après l'appel de la fonction, il ne reste sur la pile que les paramètres retournés. Dans cet exemple, on rajoute 2 au pointeur de pile, puisque PERIM est le seul paramètre qui est retourné. 1 Word = 2 octets, donc +2 au pointeur de pile.

Exemple d'accès aux éléments d'un tableau dont l'adresse est sur la pile

          lda      tab,i
          sta      -4,s
          subsp    4,i
          call     somme
          deco     0,s
          addsp    2,i
          stop

tab:      .WORD    10
          .WORD    20
          .WORD    30


; Fonction somme: additionne les 3 nombres du tableau dont l'adresse est sur la pile
ADRSRET:  .EQUATE  0
ADRSTAB:  .EQUATE  2
SOMME:    .EQUATE  4
somme:    ldx      0,i
          lda      ADRSTAB,sxf  ; "À la case ADRSTAB de la pile, il y a l'adresse d'un tableau. Va chercher le
          addx     2,i          ; contenu de la case X de ce tableau."
          adda     ADRSTAB,sxf  ; case 2
          addx     2,i
          adda     ADRSTAB,sxf  ; case 4
          sta      SOMME,s

          lda      ADRSRET,s
          sta      ADRSTAB,s
          ret2

          .END


Fonction de calcul de taxe: Exemple d'utilisation des différents modes d'adressage sur la pile

          lda      prix,i
          sta      -2,s         ; TXP_PRIX
          lda      4,i
          sta      -4,s         ; TX_NBITM
          lda      total,i
          sta      -6,s         ; TXP_TOT
          subsp    6,i
          call     TotTaxes
          deco     total,d
          stop

prix:     .WORD    100
          .WORD    200
          .WORD    300
          .WORD    400          ; 100+200+300+400 = 1000$ au total.

total:    .WORD    0


; **************************************************************************
; TotTaxes: Fonction qui calcule la taxe sur tous les items d'un tableau
; d'entiers (TXP_PRIX), et qui stocke la somme des prix avec taxe des items
; dans la variable demandée (TXP_TOT)

; Paramètres:
; TXP_PRIX: Pointeur sur un tableau d'entiers contenant les prix des items
; TX_NBITM: Nombre d'items dans ce tableau (TXP_PRIX)
; TXP_TOT:  Pointeur sur une variable qui recevra le grand total.

; Fonctionnement de TotTaxes:
; 1) Pour chaque item de TXP_PRIX, on calcule la taxe et on stocke le résultat dans un
;    tableau temporaire TX_TBTMP
; 2) On fait la somme des prix de tous les items de TXP_PRIX et toutes les taxes
;    calculées de TX_TBTMP
; 3) On stocke cette somme à l'adresse demandée (TXP_TOT)

TX_MXITM: .EQUATE  10           ; Nombre max d'items dans le tableau TX_TBTMP

; Pile de la fonction:
TX_TBTMP: .EQUATE  0            ; Tableau temporaire stocké directement sur la pile
TX_REGA:  .EQUATE  20           ; Le tableau précédent fait 20 octets, alors cette
;                               ;  variable est à l'adresse 20
TX_REGX:  .EQUATE  22
TX_TMP:   .EQUATE  24
TX_RET:   .EQUATE  26
TXP_TOT:  .EQUATE  28           ; Pointeur sur la variable qui doit contenir le total,
;                               ;  toutes taxes incluses.
TX_NBITM: .EQUATE  30           ; Nombre d'éléments dans le tableau
TXP_PRIX: .EQUATE  32           ; Pointeur sur le tableau des prix


TotTaxes: subsp    TX_RET,i
          sta      TX_REGA,s
          stx      TX_REGX,s

          ldx      TX_NBITM,s
          cpx      0,i
          brle     tx_err
          cpx      TX_MXITM,i
          brgt     tx_err       ; 0 < TX_NBITM <= TX_MXITM

          subx     1,i
          aslx                  ; X est positionné sur la dernière case du tableau pointé
;                               ;  par TXP_PRIX

tx_taxe:  lda      TXP_PRIX,sxf ; On se sert du pointeur qui est sur la pile pour accéder
;                               ;  à la case X du tableau de prix
          asra                  ; = 50%
          asra                  ; = 25%
          asra                  ; = 12,5%
          sta      TX_TMP,s
          asra                  ; = 6,25%
          asra                  ; = 3,125%
          adda     TX_TMP,s     ; 12,5% + 3,125% ~= 15% de taxe. La marge d'erreur est très
;                               ;  grande! ;)
          sta      TX_TBTMP,sx  ; On stocke le résultat à la case X du tableau temporaire
;                               ;  qu'on a sur la pile.

          subx     2,i
          brge     tx_taxe      ; Boucler tant que X est positif


          ldx      TX_NBITM,s
          subx     1,i
          aslx
          lda      0,i


;   On fait la somme de tous les prix et taxes pour calculer le grand total

tx_tot:   adda     TXP_PRIX,sxf ; prix de l'item
          adda     TX_TBTMP,sx  ; + taxe sur cet item
          subx     2,i
          brge     tx_tot

          sta      TXP_TOT,sf   ; Et on se sert du pointeur pour stocker le résultat
;                               ;  directement dans la variable du programme appelant

tx_err:   lda      TX_RET,s
          sta      TXP_PRIX,s
          lda      TX_REGA,s
          ldx      TX_REGX,s
          addsp    TXP_PRIX,i

          ret0

          .END



La fonction Fibonacci

          deci     -4,s
          subsp    4,i
          call     Fibo
          deco     0,s
          addsp    2,i
          stop


;*******************************************************************************************
; Fonction Fibonacci récursive

RET:      .EQUATE  0
N:        .EQUATE  2
FN:       .EQUATE  4

Fibo:     lda      N,s          ; Si N==0 ou N==1 -> br suite
          breq     zero
          cpa      1,i
          breq     un

          suba     1,i
          sta      -4,s
          subsp    4,i
          call     Fibo         ; Fibo(N-1)
;                               ; On laisse la réponse sur la pile pour plus tard
          lda      4,s          ; La pile est maintenant décalée de 2, alors on ne doit pas
;                               ;  utiliser les .EQUATEs
          suba     2,i
          sta      -4,s
          subsp    4,i
          call     Fibo         ; Fibo(N-2)

          lda      0,s          ; A = Fibo(N-2)
          adda     2,s          ; A = Fibo(N-2) + Fibo(N-1)
          addsp    4,i          ; Dépiler les retours de Fibo(N-2) et Fibo(N-1)
          br       suite

; 0 et 1: On retoure le paramêtre. Remarquez que le registre A contient déjà la bonne valeur
;  alors on pourrait tout simplement brancher directement à "suite"...
zero:     lda      0,i          ; Fibo(0) = 0
          br       suite
un:       lda      1,i          ; Fibo(1) = 1

; Ici, le registre A doit contenir la réponse.
suite:    sta      FN,s
          lda      RET,s
          sta      N,s          ; Déplacer l'adresse de retour
          ret2                  ; = ADDSP 2,i   suivi de   RET0

          .END

Fonction factorielle récursive


          deci     -4,s
          subsp    4,i
          call     Facto
          deco     0,s
          addsp    2,i
          stop


RET:      .EQUATE  0
N:        .EQUATE  2
FN:       .EQUATE  4

Facto:    lda      N,s
          cpa      1,i
          breq     un           ; Facto(1) = 1

          suba     1,i
          sta      -4,s
          subsp    4,i
          call     Facto        ; Facto(n-1)
          lda      0,s
          addsp    2,i

          sta      -8,s
          lda      N,s
          sta      -6,s
          subsp    8,i
          call     Mulss        ; n * Facto(n-1)
          lda      2,s
          addsp    4,i

un:       sta      FN,s

          lda      RET,s
          sta      N,s
          ret2

            ; LA FONCTION MULSS EST INCLUSE DANS LE .TXT

Structure des nombres à virgule flottante

Structure des nombres

Struct nb VF

Exceptions aux nombres normalisés

Exceptions nb VF

Convertir 19,328125 en binaire

Pour convertir la partie du nombre qui est après la virgule, on fait des multiplications successives par 2:

0,328125 * 2 = 0,65625
0,65625 * 2 = 1,3125
0,3125 * 2 = 0,625         -- Attention, on change la partie entière de 1 à 0 avant de continuer les multiplications
0,625 * 2 = 1,25
0,25 * 2 = 0,5
0,5 * 2 = 1,0

Il ne nous reste plus qu'à prendre les chiffres en vert : 010101
(On remplit de gauche à droite pour la partie décimale, de droite à gauche pour la partie entière. Autrement dit, on remplit les bits à partir de la virgule.)

Vous savez comment convertir le 19, mais c'est interessant de voir l'analogie avec la méthode d'en haut:

19 / 2 = 9 Reste 1
9 / 2 = 4 Reste 1
4 / 2 = 2 Reste 0
2 / 2 = 1 Reste 0
1 / 2 = 0 Reste 1

On remplit de droite à gauche: 10011
Et on a la réponse: 19,328125d = 10011,010101b

Si on veut retrouver le nombre original à partir du nombre binaire:
10011,010101b = 24 + 21 + 20 + 2-2 + 2-4 + 2-6 = 16 + 2 + 1 + 0,25 + 0,0625 + 0.015625 = 19,328125


Exemple 1: Convertir -18,75 en nombre binaire à virgule flottante à 32 bits

18d = 10010b
0,75d = 2-1 + 2-2 = 1/2 + 1/4 = 0,11b
18,75d = 10010,11b

On décale la virgule jusqu'au premier bit (le plus à gauche) valant 1. On se rappelle que déplacer la virgule à gauche revient à diviser le nombre par 2
(imaginez que les bits à droite de la virgule sont ceux qu'on perdrait en faisant un shift à droite d'un registre):
10010,11 = 1,001011 * 24

On a maintenant tout ce qu'il nous faut:
Signe: Négatif
Exposant: 4
Mantisse: 001011

On encode pour le format des nombres à virgule flottante:
Signe = 1
Exposant = 4d + 7Fh = 83h = 10000011b
Mantisse = 001 0110 0000 0000 0000 0000

Donc, -18,75 en nombre binaire à virgule flottante à 32 bits est: 1100 0001 1001 0110 0000 0000 0000 0000b ou C196000h

Notes:


Exemple 2: Convertir le nombre hexadécimal à virgule flottante sur 32 bits 3E340000 en nombre décimal

On trouve sa valeur binaire: 0011 1110 0011 0100 0000 0000 0000 0000
Signe = 0
Exposant = 0111 1100
Mantisse = 011 0100 0000 0000 0000 0000

On décode:
Signe = Positif
Exposant = 7Ch - 7Fh = -3h = -3d
Mantisse = 01101b

On reconstruit le nombre:
+ 1,01101b * 2-3 = + 0,00101101b
En décimal, 0,00101101b = 2-3 + 2-5 + 2-6 + 2-8 = 0,17578125

Donc, le nombre à virgule flottante sur 32 bits 3E320000h vaut 0,17578125 en décimal.

Notes:


L'histoire du bug du pentium

Une histoire vraie! :)

En 1994, on découvre un bug plutôt embêtant dans les processeur Pentium 60Mhz à 100Mhz. L'instruction FDIV (Floating point DIVision) retournait, dans certains cas, des valeurs dont la perte de précision dépassait de loin les limites de l'acceptable. L'ordinateur pouvait ainsi calculer:
4,195,835 / 3,145,727 * 3,145,727 - 4,195,835 = 256 (au lieu de 0...)

Un bug dans un processeur n'est ni chose nouvelle, ni surprenant. Mais la plupart du temps, ils sont mineurs ou n'arrivent que dans des circonstances très rares. Intel décida sur le coup que le bug n'était pas assez important pour échanger les processeurs affectés, sans se douter de tout ce qui allait leur tomber sur la tête.

Jusqu'à la fin du mois de décembre 1994, où Intel ordonna un rappel des processeurs défectueux, flèches et couteaux ont volé entre des clients mécontents et Intel, qui même jusqu'à ce jour préfère appeler le problème de l'instruction FDIV une simple "faille" plutôt qu'un "bug". :)

Voici quelques blagues qu'on retrouve sur internet à ce sujet (en anglais seulement - désolé) :

Q: What algorithm did Intel use in the Pentium's floating point divider?
A: "Life is like a box of chocolates." (Source: F. Gump of Intel)

Q : What do you call a series of FDIV instructions on a Pentium?
A1: Successive approximations.
A2: A random number generator.

New Intel slogans for the Pentium:
- Redefining the PC--and Mathematics As Well
- We give you the most megaflops.
- Pentium, the computer your kids can relate to: it can't do fractions either.

Reasons to buy a Pentium
- Math errors add zest to life
- You need an alibi for the I.R.S.

I wonder...
- Did Intel use a buggy Pentium to calculate the quarterly dividends for its stockholders? An interesting question, to be sure.
- Did Intel use a Pentium to calculate the 27000 years it takes to give an error?


Programme à débugger

; Ce programme affiche les 16 premières puissances de 2 en décimal.
; Mais il ne fonctionne pas... Trouvez les bugs!


;Déclaration des variables
nombre:  .WORD   0x0001

;Programme principal
         lda     nombre,i     ;Initialisation du nombre original
         ldx     0x16,i       ;16 tours de boucle


    ;Boucle d'affichage des puissances
    ;À l'entrée, X contient le nb de tours de boucle à faire,
    ; et A le nombre à partir duquel on commence l'affichage.

boucle:  deco    nombre,d     ;Afficher le nombre actuel
         charo   0x0A,i       ;Prochaine ligne
         asla                 ;A*2
         sta     nombre,d
         subx    1,i          ;Prochain tour de boucle
         cpx     0,i
         breq    boucle       ;Continuer la boucle tant que X != 0
                        
         stop

         .END

Quelques questions de révision

  1. Quels sont les noms des 6 registres du processeur, et quelle est leur utilité?
  2. Quelle est la différence entre un overflow et un carry?
  3. À quel moment l'instruction SUBR peut-elle provoquer un overflow (V=1)? Et/ou un carry (C=1)?
  4. Qu'est ce que le registre X contiendra après l'exécution de l'instruction suivante: (lisez bien)
            ldx   str,d    ;X=????
            ...
    
    str:    .ASCII "ALLO\x00"
    
  5. Quelles sont les limites des nombres signés?
  6. Quelle est la différence entre les registres A et X?
  7. Que va afficher l'instruction suivante:
            deco    0x100,i
    
  8. Le code source qui suit est donné à pep8. Si on fait "Assemble/Load/Execute", est ce que le processeur sera capable d'exécuter le programme? Quelles instructions seront exécutées par le processeur? (Vérifiez dans votre manuel, sur la première page pour avoir les codes d'instructions, et sur la dernière page pour avoir la correspondance des codes ascii.)
            .ASCII "xyz\x00"
            .END
    
  9. Quelle est la différence (s'il y en a une) entre les déclarations suivantes:
            .ASCII "AB"
    
            .WORD 0x4142
    
            .BYTE 0x41
            .BYTE 0x42
    
            .BYTE 'A'
            .BYTE 'B'
    
            .WORD 16706
    
  10. Pourquoi on met habituellement un caractère nul (\x00) à la fin des chaînes de caractères?
  11. Écrivez les commentaires manquants au bout de code suivant:
            deci   nb,d    ;On récupère un nombre au clavier
            brlt   point1  ;???
            breq   point2  ;???
    point3: ...            ;???
    

Réponses aux questions de révision

    • A: Accumulateur (16 bits)
      Sert à effectuer les opérations logiques, arithmétiques ou d'accès mémoire.

    • X: Index (16 bits)
      Comme pour le registre A, et peut aussi servir à indexer la mémoire quand on l'utilise avec le mode indexé.

    • SP: Stack Pointer (16 bits)
      Pointe le sommet de la pile. (Attention: la pile grandit à l'envers!)

    • PC: Program Counter (16 bits)
      Pointe la prochaine instruction à exécuter.

    • IR: Instruction Register (24 bits)
      Contient l'instruction actuellement en cours d'exécution

    • NZVC: Status Bits (4 bits)
      Registre d'état du processeur. Donne des renseignements sur le résultat des instructions exécutées par le processeur. Contient les flags Négatif, Zéro, oVerflow et Carry.

  1. Les deux servent à indiquer un débordement. L'Overflow (V=1) indique un débordement en supposant que le nombre était SIGNÉ, et le Carry (C=1) en supposant que le nombre était NON-SIGNÉ.

  2. Un débordement arrive quand l'opération fait sortir le nombre des bornes. Par exemple, sur 16 bits, un nombre signé doit être compris entre -32768 (8000 hex) et 32767 (7FFF hex). Un nombre non signé, (sur 16 bits aussi) doit être compris entre 0 (0000 hex) et 65535 (FFFF hex).
    Exemple d'Overflow (V=1) avec la soustraction: (-32768) - 1
    Exemple de Carry (C=1) avec la soustraction: 0 - 1
    Exemple où V=1 et C=1 (Attention, dans cet exemple, les nombres n'ont pas la même valeur selon qu'on les considère comme signés ou non signés!): 9000 (hex) + 8000 (hex)

  3. X = 414C
    (414C correspond à "AL")

  4. En décimal: -32768 à 32767
    En hexadécimal: 8000 à 7FFF

  5. Les registres A et X sont des registres "généraux", c'est à dire que la plupart des instructions vont en avoir besoin. Ils peuvent servir à effectuer des opérations logiques (NOTR, ORR, ANDR) et arithmétiques (ADDR, SUBR, ASLR, ASRR, RORR, ROLR, NEGR), ou des opérations de chargement/stockage (LDR, STR, LDBYTER, STBYTER). En plus de pouvoir faire les mêmes opérations que le registre A, le registre X peut aussi servir à indexer la mémoire à partir d'un pointeur de base qui est fourni en opérande: c'est le mode indexé (,x).
    (Notez qu'on pourrait tout aussi bien décider de mettre RORR, ROLR et ASLR dans la catégorie des instructions logiques...)

  6. "256" sera affiché (sans les guillemets)
    L'instruction demandait d'afficher le nombre hexadécimal 100 en décimal.

  7. Programme assemblé: 78 79 7A 00
    Ce qui sera exécuté:

            ADDX 0x797A,i
            STOP
    Le processeur ne peut pas faire la différence entre le code et les variables. Pour lui, ce ne sont que des octets en mémoire. Tout ce qui se retrouve sous son registre PC sera décodé et exécuté comme une instruction.
  8. Il n'y a aucune différence. Toutes les déclarations réserveront 16 bits initialisés avec la valeur hexadécimale 4142.

  9. Parce que c'est ce "caractère" qui marque la fin de la chaine. Sans lui, l'instruction d'affichage (STRO) imprimerait la mémoire au complet, ou au moins jusqu'à ce qu'elle rencontre un autre caractère nul.

  10.         deci   nb,d    ;On récupère un nombre au clavier
            brlt   point1  ;nb est négatif? (BRLT = BR si N=1)
            breq   point2  ;nb est nul? (BREQ = BR si Z=1)
    point3: ...            ;nb est positif

Exemple d'utilisation de la fonction New et de création d'une liste chaînée

; On veut créer une liste chaînée contenant 2 éléments.
; Chaque élément contient 2 entiers et un pointeur sur l'élément suivant
;
; Structure d'un élément:
; champ1:   int
; champ2:   int
; champ3:   pointeur sur un élément
;
;
; On veut que la liste soit dans cet état:
; (tête de liste) ----> (123;456) ----> (789;987) ----|



; État initial de la liste:
; (tête de liste) ----|

; ------------- Créer le 1er élément
          lda      6,i
          sta      -2,s         ; Allouer 6 octets
          lda      ptr,i
          sta      -4,s         ; Et stocker le pointeur sur ces 6 octets dans la variable ptr.
          subsp    4,i
          call     new          ; new(ADRESSE(ptr),6)

          ldx      ptr,d
          stx      tete,d       ; Nouvelle tête de liste

; État de la liste:
; (tête de liste) ---->  (?;?) ----?

          lda      123,i
          sta      0,x          ; élément1,champ1
          lda      456,i
          sta      2,x          ; élément1,champ2
          lda      0,i
          sta      4,x          ; élément1,champ3 (on initialise avec un pointeur nul.)

; État de la liste:
; (tête de liste) ---->  (123;456) ----|


; ------------- Créer le 2e élément
          lda      6,i
          sta      -2,s         ; Allouer 6 octets
          lda      ptr,i
          sta      -4,s         ; Et stocker le pointeur sur ces 6 octets dans la variable ptr.
          subsp    4,i
          call     new          ; new(ADRESSE(ptr),6)

          ldx      tete,d
          lda      ptr,d
          sta      4,x          ; élément1, champ3 (on enchaine l'élément2 après l'élément1)

; État de la liste:
; (tête de liste) ---->  (123;456) ----> (?;?) ----?

          ldx      4,x          ; On suit la liste
          lda      789,i
          sta      0,x          ; élément2,champ1
          lda      987,i
          sta      2,x          ; élément2,champ2
          lda      0,i
          sta      4,x          ; élément2,champ3

; État de la liste:
; (tête de liste) ---->  (123;456) ----> (789;987) ----|


; ----------------- Affichage des 2 éléments

          ldx      tete,d       ; Élément1
          deco     0,x
          charo    ';',i
          deco     2,x
          charo    '\n',i

          ldx      4,x          ; Élément2
          deco     0,x
          charo    ';',i
          deco     2,x
          charo    '\n',i


          stop

ptr:      .WORD    0            ; Variable temporaire recevant l'adresse de la mémoire allouée
tete:     .WORD    0            ; Tête de liste


;
;Sous-programme new
; Alloue la taille demandÈe et place l'adresse de la
; zone dans le pointeur dont l'adresse est passéÈe en paramËtre
NvieuxX:  .EQUATE  0            ; sauvegarde X
NvieuxA:  .EQUATE  2            ; sauvegarde A
NadRet:   .EQUATE  4            ; adresse retour
Npoint:   .EQUATE  6            ; adresse pointeur ? remplir
Ntaille:  .EQUATE  8            ; taille requise
;
new:      SUBSP    4,i          ;  espace local
          STA      NvieuxA,s    ;  sauve
          STX      NvieuxX,s    ;  sauve
          LDA      heappnt,d    ;
          STA      Npoint,sf    ;
          LDA      Ntaille,s    ;  taille du noeud
          ADDA     1,i          ;  arrondir ? pair
          ANDA     0xFFFE,i     ;
          ADDA     heappnt,d    ;
          CPA      heaplmt,i    ;
          BRGT     new0         ;
          STA      heappnt,d    ;  prÈparer heappnt pour appel suivant
          BR       new1         ;
new0:     LDA      0,i          ;
          STA      Npoint,sf    ;  pointeur NULL
new1:     LDA      NadRet,s     ;  dÈplace adresse retour
          STA      Ntaille,s    ;
          LDA      NvieuxA,s    ;  restaure
          LDX      NvieuxX,s    ;
          ADDSP    8,i          ;  nettoyer pile
          RET0
heappnt:  .ADDRSS  heap         ; initialiser  heappnt
heap:     .BLOCK   255          ; espace heap; selon système
          .BLOCK   255          ; espace heap; selon système
heaplmt:  .BYTE    0            ;

          .END



Questions et commentaires:
Vincent Gournay - vincent2170@sympatico.ca