ASM680X0 - Chapitre 1

Un article de GuruMed.

Sommaire

Quel environnement de développement et comment ça marche ?

L'assembleur se présente sous la forme de fichiers textes contenant le programme écrit en asm, nous verrons plus tard sa forme. Ces fichiers sont d'habitude terminés par .asm ou .s, parfois .i pour les fichiers includes. Ils peuvent être 'assemblé'. (on compile un programme C, on assemble de l'assembleur.) Dans un monde 'standard' dont fait partie le C, on n'assemble pas d'exécutable, on assemble un ou plusieurs '.o' (o pour objet) a partir des .asm, qu'on 'linke' (colle ensemble) ensuite pour créer un executable. Par exemple, l'assembleur gratuit phxass peut créer directement des exécutables, mais aussi des .o a partir de .asm:

si on tape la commande DOS : phxass monfichier.asm

...On vient d'assembler monfichier.o à partir de monfichier.asm. Les .o ne sont pas des exécutables. Ils contiennent effectivement du code machine, mais il manque des relations de noms qui ne sont résolu qu'au linkage, par une autre commande dos. (comme parfois ld utilisé par le compilateur C gcc, ou link.). Les commandes dos de linkages d'exécutables prennent en paramètres une série de fichier .o, par exemple: startup.o monfichier.o machin.o truc.o Le linker résout alors les liens de noms: les .o contiennent des noms de variables et de fonctions qui peuvent être demandé dans un autre .o. par exemple, un startup.o demande toujours une fonction 'main' qui est forcément fourni par un des autres .o. (voir commande assembleur XDEF et XREF plus tard). Si les résolutions de noms sont complétés, un exécutable est créée, et celui-ci ne contient plus que du code machine. Mais tout ceci ne constitue que la partie 'finale' de ce que réalise un compilateur C.( phxass est d'ailleurs souvent utilisé par les compilo C 680x0 sur amiga)

Cette méthode consistant à créer plusieurs .o (objet donc) pour ensuite créer son programme permet de bien s'organiser et séparer des parties de codes indépendantes, Mais il serait bien pratique de créer directement un exécutable à partir d'un fichier assembleur court: C'est tout à fait possible avec devpac ou asmone, qui ont une option pour spécifier si on créée un .o, ou un exécutable à partir du source asm.


Ou ça commence ? Ou ça fini un programme asm ?

Peut être savez-vous déjà ce qu'est une sous-routine, ou un appel de fonction en algorithmique: A un moment dans un programme, on 'saute' vers une autre fonction, qui une fois terminé reviendra d'ou elle est partie. ( juste après le saut.) Et bien l'AmigaOS traite tout lancement d'exécutable (on parlera de tâche) exactement comme si il s'agissait d'un saut en sous-routines dans une fonction: la tâche commence quand elle y rentre et termine sa vie quand elle en sort. l'analogie marche aussi pour les passages de paramètres, en entrée et en sortie: l'amigaos donne des paramètres en entrée au programme, et le programme donne un 'code d'erreur' en sortie. (voir précision ci-après.)

Mais alors ou se situe cette fonction qui 'fait' le programme ? Et bien c'est la première fonction au début de l'exécutable.

Dans le cas d'un programme linké, startup.o est toujours en premier dans la liste des .o: pas de mystère, c'est parce que cela doit être la première fonction lancé. habituellement un startup.o mache le travail bas niveau d'entrée/sortie des programmes C , réalise quelques initialisations, puis lance la fonction main que les programmeurs de C connaissent bien et qui est le début du programme C. (et qui elle peut se trouver plus loin dans l'exécutable.)

En assembleur pur, il est tout à fait possible de se passer de startup.o: Réalisons notre premier programme asm sous Devpac, qui permet de créer des exécutable directement.(ne pas activer l'option créer .o). Notez qu'on peut aussi créer le programme juste en mémoire avec devpac, sans créer l'image sur le disque, puis l'exécuter depuis la mémoire: attention alors: si on assemble en mémoire et qu'on exécute plus d'une fois, la valeurs des variables n'est pas remise à jour.(ceci n'est vrai que pour devpac.)

Pour tout les exemples qui suivront, réglez votre compilateur sur '68020' ou plus, car l'asm 68000 comprend moins d'instructions. tapez ces 4 lignes: (n'omettez pas les espaces quand il y en a, et n'en rajoutez pas quand il n'y en a pas en début de ligne !!!)

	
debut:     ; un label pour savoir ou on est. 
        move.l #844,d0 je met 844 en sortie 
; on retourne à la maison: 
        rts 

Il n'y a presque rien, pas de linkage, pourtant devpac compile un exécutable. Lancez-le depuis le workbench: vous aurez un code de retour d'erreur 844. Ca y est vous codez assembleur. ouaaaah que c'est beau. en même temps difficile de faire plus court. Ces 4 lignes seront expliqué plus loin: la question pour l'instant est pourquoi ce code accompagné par presque aucune déclaration s'exécute–t il ? Parce que comme je l'ai dit, c'est la première fonction en entrée dans l'exécutable. mais la réalité assemblé est un peu plus complexe: bienvenu dans le monde des 'sections' qui composent un exécutable, et qui vont se poser dans la mémoire au chargement de celui-ci. Quand devpac constate qu' aucune section n'est déclaré dans un source asm, il agit comme si il s'agissait d'une section code par défaut. pour être clair il aurait fallu écrire par exemple:


	
        section  nomdesectioncode 
debut:        ; un label pour savoir ou on est. 
        move.l #844,d0 je met 844 en sortie 

        ; on retourne à la maison: 
        rts 

Ici on voit que le code se trouve dans une section nommé nomdesectioncode: c'est la première de l'exécutable, donc l'entrée du programme . on peut en définir plusieurs, (c'est même conseillé) mais attention aux restrictions d'accès entre sections. (voir différences entre instructions bra et jmp, et entre bsr et jsr). Sur amiga, la commande section permet aussi de demander des préférences sur la façon dont la section va être chargé en mémoire au lancement et si il s'agit de donnée ou de code: (en assembleur, on assemble du code ET des données)


	
    section nomcode, code ; défini le début d'une section code chargé en fast
                          ; ou en chip si il n'y a pas assez de fast. 
    section nomdat, data ; défini le début d'une section data en fast ou en chip. 

    section nomcode, code_f ; défini le début d'une section code forcément en memoire fast 
    section nomdat, data_f ; défini le début d'une section data forcément en fast 

    section nomcode, code_c ; défini le début d'une section code forcément en memoire chip 
    section nomdat, data_c ; défini le début d'une section data forcément en chip 

Si vous connaissez l'amiga classic vous savez qu'il existe 2 types de mémoire accessible: La fast accessible uniquement depuis le 680x0 et la mémoire chip, plus lente mais accessible à la fois par le 680x0 et par tout les chipset graphiques. si nous utilisions certaines bidouille copperlist ou blitter, il faudrait forcer des data en chip. le code 68k peut s'exécuter en chip comme en fast, donc il n'y a aucune raison de le forcer en fast, ce qui empêcherait le code de s'exécuter sur des amigas non boosté en ram.


Youpi j'apprend la syntaxe de l'assembleur

Expliquons le code minimal écrit plus haut, Cela va nous familiariser à la syntaxe magique de l'assembleur:

Dans un source assembleur, on a des instructions (move.l, add.l, ...), des labels, qui sont des noms donné arbitrairement à un endroit du code pour le repérer (comme debut dans l'exemple), et des commandes (comme section dans l'exemple), et des commentaires:

On ne peut avoir qu'une instruction ou commande PAR LIGNE. On peut mettre des commentaires aprés un point virgule ; sur des lignes ou il n'y a pas d'instruction, ou sur la même ligne qu'une instruction, aprés l'instruction. (le point virgule est alors facultatif.)

Toute instruction assembleur doit commencer aprés au minimum un espace ou une tabulation sur sa ligne. Tout label commence toujours collé au début de la ligne, et peut ou non être suivi du caractère 2 point ':'. On peut mettre une instruction après un label puis un espace, sur la même ligne.

Attention: Un assembleur peut selon les options, faire la différence ou pas entre majuscule et minuscule pour reconnaître les instructions et les références aux labels. Attention ! je vous conseille de toujours faire comme si la différence existait, ainsi votre code passera partout. notez que dans ce cas 'Move' sera accepté comme 'move'. Pour la même raison je conseille de ne pas mettre de ':' aprés les labels: certains assembleurs ne veulent pas de ':' sauf option, ce qui est gênant quand on include un fichier .i formaté différemment. Voici une version a peine plus riche de l'exemple précédent:

	
        section nomdesectioncode , code 

debut   ; un label pour savoir ou le code est. 
        move.l  #844,d0 je met 844 dans le registre d0 
        move.l  variable1,d1 
        add.l   d1,d1 ; 22*2=44 
        sub.l   d1,d0 ; 844 - 44 = 800 
; on retourne à la maison: le code d'erreur retourné est 800. 
fin 
        rts 
;--------------------- déclarons une variable 32 bit: 
        cnop 0,16 ; alignement de la memoire sur 16 octet. (facultatif.) 
variable1 
        dc.l 22 ; 4 octet initialisé a 22 par defaut. 

Que se passe-t-il dans cet exemple ?

La valeur 844 est placé dans les 32 bit du registre volatile d0 de manière immédiate, Puis la valeur 22 est placé dans les 32 bit du registre d1 par lecture dans la mémoire. Une addition de d1 sur lui même permet de passer sa valeur à 44, Puis d0 est soustrait de 44, et la commande rts ferme le programme.

Tout cela n'est peut être pas clair pour vous. expliquons plus en détail ce qu'il se passe dans ces lignes:



L'instruction move

L'instruction move (déplacer en anglais) réalise de maniére générale la copie d'un bloc de mémoire vers un autre. (En basic ou en C, on pourrait la comparer à l'opération "=").

Ce bloc peut avoir 3 "taille" différentes. Move a toujours la forme:

move.? source, destination

Ce qui copie les bits de la source dans la destination. Les bits (bit in english) sont la plus petite donnée possible en informatique: Un bit peut être égal à 0 ou à 1. (2 valeurs possibles pour un bit.) En en regroupant plusieurs, plus de valeurs sont possibles: Avec 2 bits, 4 valeurs sont possibles, avec 8 bit 256 valeurs différentes sont possibles selon que chacun soit égal à 0 ou 1.

En fait "2 puissance le nombre de bit" sont possibles. La mémoire de l'ordinateur et les registres sont uniquement constitué de bits. Quand on copie une série de bit avec move, on copie au minimum 8 bits à la fois, ce qu'on apelle un octet (byte in english. don't confondre bite and byte.) La mémoire pouvant être représenté par une suite d'octet linéaire, on utilise des 'adresses' pour désigner tel emplacement d'un octet ou le début d'une suite d'octet dans la mémoire. (les labels que nous avons vu représentent une adresse.)

Avec move on peut aussi copier 16 bit (2 octet, ou 'mot' au sens 68000, en anglais word.) ou enfin 32 bit (4 octet, ou 'long' au sens 68000, en aglais long.)

Dans la forme ci dessus, ".?" peut etre .l (4 octet, 32 bit), .w (2 octet, 16 bit) ou .b (1 octet, 8 bit.) Exemple d'instructions possibles:

  • move.l d0,d1 ; copie des 32 bit du registre d0 dans d1
  • move.w d2,d3
  • move.b d4,d5 ; copie des 8 bits de poids faible de d4 à d5.

sources et destinations peuvent prendre plusieurs formes:


Les Registres internes du processeur

qu'est ce qu'un registre ?

Dans une instruction comme move, on peut utiliser un registre (d0...d7,a0...a6,...) Ce sont des "emplacements" de 32 bits chacun, qui résident dans le processeur et pas dans la mémoire. Ils ne sont pas dans l'espace adressable et sont juste référencé par leurs noms. Ils réagissent comme des variables toujours accessibles et c'est avec eux que le processeur réalise ses operations de calcul, ses comparaisons, etc...

Un calcul réalisé entre registres, sans accés à la ram (mémoire externe au processeur), sera toujours beaucoup plus rapide qu'un calcul qui fait des écritures et lectures en mémoire. (à cause des accés sur le BUS entre processeur et mémoire.)

Les voici:


Image:M68000reg.GIF

  • les registres de données: d0 d1 d2 d3 d4 d5 d6 d7
  • les registres d'adressages: a0 a1 a2 a3 a4 a5 a6

(pas touche à a7, aussi connu comme sp, le pointeur de pile). Il existe aussi des registres spéciaux. (voir plus tard. notez aussi qu'a partir du 68020, il y a plus de registre spéciaux que sur le schéma.) Les registres de données sont habituellement utilisé pour des calculs. La valeur contenu dans les registres d'adresses est d'habitude utilisé pour pointer (indiquer) un emplacement dans la mémoire.

note: d'une maniére générale, un source assembleur consiste presque toujours à donner des valeurs initiales à ces registres, puis à faire un calcul ou un algorithme avec, puis finir par recopier le résultat en mémoire.

L'adressage de valeur dans la mémoire

En lecture ou en écriture !

Autre forme possible pour une source ou une destination d'un move: Un adressage en mémoire: cela permet de lire ou d'écrire une valeur défini dans un emplacement de la mémoire adressable depuis un registre, ou meme de réaliser une copie de mémoire à mémoire sans même passer par un registre:

Attention ici, beaucoup de syntaxes sont possible pour réaliser la même operation:

	
    move.l   nomdelabeldesignantunedonne,d0   ; lecture de 32bit de la mémoire
					    ; vers un registre
    move.l   d0,nomdelabeldesignantunedonne   ; ecriture dans la memoire

    move.l   label1,label2   ; ecriture mémoire vers mémoire.
    move.l   (a0),d0	   ; a0 pointe une adresse mémoire. 
                           ; (a0) représente la valeur contenu à cette adresse.
			   ; ceci est donc une lecture de la mémoire dans un registre.

    move.l   d0,(a0)         ; écriture dans la mémoire "pointé" par a0.
    move.l   d0,a0	   ; ceci est trés différent et est juste une copie registre à registre.

 	; en 68020 ceci est possible:

  move.l    64(a0),d1	  ; identique a "(a0),d1" mais la valeur est lue 64 octet plus loin dans la mémoire.
  move.l    -32768(a0),d1  ; valeur lue 32768 octet avant a0.
  move.l    (a0,d0.w),d1   ; valeur lue en a0 + la valeur des premier 16 bits de d0.
  move.l    (a0,d0.w),d1   ; valeur lue en a0 + la valeur 32bits de d0. (mais un peu plus lent a l'execution)

  move.l    (a0)+,d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0+4 APRES la lecture.
  move.w    (a0)+,d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0+2 APRES la lecture.
  move.b    (a0)+,d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0+1 APRES la lecture.

  move.l    -(a0),d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0-4 * AVANT * la lecture.
  move.w    -(a0),d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0-2 * AVANT * la lecture.
  move.b    -(a0),d1       ; trés utile:  equivalent à "(a0),d1" mais a0=a0-1 * AVANT * la lecture.
	; On est bien d'accord, tout le monde à noté la subtilité du * AVANT *: dans le cas -(a0)
	; le registre a0 est d'abord reculé, puis la donnée lue a cet adresse.
	; dans le cas (a0)+, la donnée est lu en a0, PUIS aprés a0 est déplacé en avant.
	; ça semble bizarre comme ça mais vous verrez l'utilité quand on parlera de PILE.
	; notez aussi que dans ces cas, la calcul effectué sur a0 et représenté par - ou + est gratuit en temps machine.

        ; enfin le trés puissant mais bizarre:

        move.l    64( a0 , d0.l*8 ),d1
  • move.l 64( a0 , d0.l*8 ),d1

Signifie que les 32 bits contenu en mémoire à l'adresse: a0 + 64 octet + (la valeur de d0 multiplié par 8) sont copié dans d1. (on peut spécifier *2,*4 ou *8 sur ce registre de donné, cette multiplication est gratuite en temps machine.)

Nous y reviendront, mais sachez que parfois une forme d'adressage est possible ou pas, il est parfois necessaire de se munir d'une bonne documentation 68000 ou 68020 ou figurent des tableaux indiquant quelle adressage est possible selon qu'on utilise un registre de donnée ou d'adressage en source ou destination, ou dans un move ou une autre instruction. dans les fait, essayez toujours, votre assembleur vous dira si c'est possible ou non ! (attention le 68000 plantera devant certain modes d'adressages disponibles seulement depuis le 68020. reglez bien les options de vos assembleurs!)


L'assignation imédiate !

Derniére forme possible dans un move, mais uniquement avant la virgule (là ou on indique la source copié): l'assignation immédiate, ou directe, toujours représenté par un diése '#' précédent une valeur écrite telle quelle: C'est une façon trés simple de donner une valeur à un registre ou dans un emplacement mémoire:

  • move.l #666,d0 ; met la valeur 666 dans les 32 bit du registre d0.
  • move.w #25,label1 ; met la valeur 25 dans les 2 octet a l'adresse label1.
  • move.w #32,(a0) ; met la valeur 32 dans les 16 bit pointé par a0.

quand on veut spécifier une valeur immédiate aprés #, on peut le faire comme dans les exemple précédent, en décimal. Mais il est tout à fait possible de la spécifier en hexadécimal (voir chapitre2) avec un $:

  • move.l #$00078af2,d0 ; equivaut: move.l #494322,d0
  • move.w #$7fff,d1 ; equivaut: move.w #32767,d1
  • move.b #$7f,d2 ; equivaut: move.b #127,d2

ou meme en binaire avec %:

  • move.b #%11001111,d0 ; equivaut a move.b #207,d0

Note: C'est une commodité d'écriture de pouvoir représenter un nombre dans une base ou une autre (binaire,hexadecimal,decimal,...). Le code machine lui ne connait que des bits donc les codes equivalent seront les memes une fois assemblés. De la meme façon il est possible d'écrire des équations là ou l'assembleur demande un nombre constant:

  • move.w #(256+$100)/%0011,d0 ; 256+256/3= 170.666

cette écriture mettra '170' dans d0 ( les registres représentent des valeurs entiéres.)

ATTENTION: Il est fondamental de comprendre que:

  • move.l 340,d0

...va aller lire la mémoire à l'adresse 340 (dieu sait ce qu'il s'y trouve !) cet espace mémoire n'est pas à vous. si nous avions un label a l'adresse 340, cette écriture serait equivalente à:

  • move.l label,d0

Tout ceci est tout à fait différent de:

  • move.l #340,d0

...qui va juste mettre la valeur 340 dans d0. dans ce cas le nombre 340 est contenu dans l'instruction meme du move. (note: le valeur se trouve donc dans le cache instruction et non donnée, pour ceux qui ont une idée de ce qu'est un cache.)

Qu'est ce qu'une variable globale ?

Vous devez maintenant avoir une idée claire et nette de ce qu'est un registre et ce qu'est la mémoire, et un adressage...

Dans un exemple plus haut, nous créions une variable globale nommé variable1 . Une variable globale est un emplacement dans la mémoire, qui prend un nombre d'octet donné dans celle-ci et qui est accessible depuis n'importe quel endroit du code, par son label qui indique sa position en mémoire. En assembleur 68000 on peut en déclarer n'importe ou dans son code, pourvu qu'on soit sûr qu'il ne figure pas au beau milieu d'une fonction qui sera executé. (le code assembleur contient du code ET des données.) l'instruction:

  • dc.l valeurinitiale

va reserver 4 octet (32bits, .l ) à l'endroit ou on la déclare et l'initialiser d'aprés la valeur (dc: declare constant). On pourra ensuite lire ou ecrire des valeurs a cet endroit avec un move.l par exemple.

  • dc.w valeurinitiale ; fera pareil avec 2 octet (16 bit)
  • dc.b valeurinitiale ; fera pareil avec 1 octet (8 bit)

On peut aussi déclarer une suite de valeur initialisé en les séparant avec des virgules:

  • dc.l 0,256,4,9

Une des grandes différences entre l'assembleur et des langages comme le C est qu'en ASM les données ne sont déclaré que par la taille qu'elles prennent en octet et leur adresse, alors que dans les autres langages, on défini une donné par son 'type' (entier, flottant, caractéres, pointeurs,...) dont la taille en octet est implicite. Ici, en assembleur, si nous avons besoin d'un entier 32bit, on déclarera un espace de 4 octets. Si on a besoin d'une donnée d'un autre type qui necessite 4 octets aussi, on déclarera 4 octets de la même façon.

Pour déclarer des données dans un source on peut aussi utiliser l'instruction ds (declare storage):

  • ds.l 30 ; assemble 120 octet non-initialisé ici
  • ds.w 60 ; assemble 120 octet non-initialisé ici
  • ds.b 120 ; assemble 120 octet non-initialisé ici (aussi)

la valeur spécifié et alors le nombre de fois que la taille de la mémoire est répété (ici 4*30=2*60=120), mais contrairement à dc on ne sait pas quelle vont être les valeurs initiales écrit dans cet espace. (il faudra l'initialiser nous même.)

derniére variante: dcb (declare constant bloc) va déclarer une table de n éléments, comme ds, mais va initialiser chacun de ces éléments avec 'valeur':

  • dcb.l n, valeur ; forme
  • dcb.b 64,0 ; 64 octets initialisé a 0.
  • dcb.l 64,1 ; puis 64*4=256 octets représentant 64 long à 1.

Voyons ce que pourrait donner une déclaration d'un tas de variable et de tableau global déclaré dans un source asm: (dans sa propre section, tient:)




	
;--------- ma section de variables ----------
    section    datasection,data

    cnop 0,16

tableau16entier
    dcb.l    16,0

valeurx
    ds.l     1

valeury
    ds.l     1

booleen1
    dc.b     0
    
    even
entier2   dc.l    666
phrase    dc.b   'ben mon cochon',0

   cnop 0,16
cachelinedata   
          dcb.b 16,0


Alignement de la mémoire

Image:Mem68000.gif

Nous voyons dans l'exemple ci-dessus que des variables ont été déclaré en réservant de la place dans le source, et que des label désignent ces emplacements pour y faire référence. Mais des éléments nouveau apparaissent: D'abord les commandes even (signifie: pair) et cnop: ce sont des commandes qui forcent un alignement de mémoire. Nous avons vu qu'un emplacement dans la mémoire s'adresse (se désigne) par une valeur entiére désignant un emplacement mémoire, et que l'unité utilisée est l'octet. Ceci implique que:

label1 dc.l 0
label2:
label3:

Dans cet exemple, label2 = label1 + 4, puisque dc.l les espace de 4 octets, alors que label2 est identique à label3 (tient notez qu'il est possible est parfois interessant de mettre plusieurs label au meme endroit: un label à un nom qui lui donne un sens, rien n'empeche que le même emplacement ait plusieur sens. (comme fin de la boucle d'avant, début de la boucle d'aprés.))

Sachant ceci, voici une mauvaise nouvelle:

ATTENTION ! RETENEZ BIEN CA !

En 680X0, réaliser une lecture ou une ecriture en temps que .l ou .w sur une adresse mémoire IMPAIRE est interdit: mieux ça fait tout planter la machine dans 100% des cas (c'est une 'exception' au sens processeur, comme les division par zero ). avec des instruction .b la mémoire peut etre lue sur des adresses paires ou impaires.

VOILA J'ESPERE QUE C'EST BIEN RETENU !

Donc si vous avez compris, si on a par exmple:

  • var1 dc.l 0 ; aligné OK (adresse paire)
  • var2 dc.b 0 ; aligné OK (adresse paire)
  • var3 dc.l 0 ; FORCEMENT PAS ALIGNE SUR 2 OCTET (adresse impaire)

un 'move.l var3,d0' ferait planter. La commande even réalise si besoin est, l'addition d'octet inutile pour 'caler' var3:

  • var1 dc.l 0 ; aligné OK (adresse paire)
  • var2 dc.b 0 ; aligné OK (adresse paire)
  • (espace) even
var3 dc.l 0 ; aligné OK (adresse paire)

la commande cnop permet d'aligner de la même façon , l'instruction qui suit, mais sur des adresses 'multiple de n'. les octet inutiles sont initialisé par la valeur donné en premier.

  • cnop 0,2 ; équivaut à even (force adresse paire)!
  • cnop 0,16 ; aligne l'adresse sur un multiple de 16.

A quoi peut bien servir un alignement de mémoire superieur à 2 puisque les move.l et .w y ont accés ? Pour des raisons d'optimisations par exemple: a partir du 68030 jusqu'au 68060, le processeur lit la mémoire 16 octets par 16 octets. (c'est la taille du 'cacheline' qui rempli le cache de donnée. ) En clair, si 16 octets sont a la suite aligné a partir d'une adresse multiple de 16, si vous avez lu un premier octet, les 15 autres sont lu en même temps. (et donc disponible trés vite par la suite.)

l'autre nouveauté est la chaine de charactére:

phrase: dc.b 'une phrase ascii est ecrite',0
even

un charactére ascii étant un octet, on peut ecrire des suites des phrases en tant que tableau d'octet. une valeur 0 indique la fin de la chaine. comme la taille de cette chaine peut varier, une commande even viendra assurer un recalage ensuite.

Allez, pour votre culture, cette derniére instruction trés utile pour construire ses données globales:

labeltableau: incbin 'monfichierbinaire.bin'
(espace) even

incbin permet en effet d'inclure tel quel un fichier binaire du disque dans le source. (trés utile pour des tables mathematiques par exmple, ou des images)