Tutoriel – Introduction au langage machine et à l’ASM C64 – Partie 1


Facebooktwitterredditpinterestlinkedintumblrmail

Tutoriel Commodore 64 AssembleurVous savez taper quelques programmes Basic ? Vous avez lu et compris le manuel utilisateur à 50% ? Vous avez compris les instructions POKE et PEEK du BASIC ? Vous savez faire sortir un son ou afficher un Sprite ? Si les réponses sont oui c’est que vous êtes mure pour commencer à parler directement à votre ordinateur (ici le C64) avec le langage machine.

En effet, le basic du Commodore 64 est si pauvre qu’il n’a, en fait, jamais été aussi proche du langage machine !

Le langage assembleur n’est pas compliqué : il est même très simple. Trop simple en effet les instructions élémentaires d’un microprocesseur sont très primaires et il faut un peu plus d’instructions pour effectuer un simple PRINT et afficher un simple message à l’écran. Mais comme tout, il faut pratiquer et saisir quelques points élémentaires, et contrairement à ce que l’on peut penser, il ne faut pas beaucoup de temps pour devenir productif.

Enfin maitriser l’assembleur du Commodore 64, c’est également maitriser le langage machine de la légendaire famille des processeurs MOS 6502 qui équipa nombreuses star comme les Atari 8bit, les Apple (I et II) ou encore la NES de Nintendo (La SNES utilise une version 16 bits du MOS65xx) et par … le Terminator T-800. La famille de processeur MOS65xx est encore de nos jours produit en version 8/16 bits par Western Design (cofondé par Bill Mensch l’un des créateurs du 6502) et utilisée principalement dans l’informatique embarqué.

L’Assembleur et le langage machine sont équivalents. La différence est qu’en assembleur on utilise des petits mots mnémotechniques qui vont remplacer les obscurs chiffres du langage machines. Par exemple en assembleur, l’instruction LDA #0 est traduit en langage machine par deux octets : 169,0. (Ou bien $A9, $00 en hexadécimale c’est-à-dire 1010 1001 0000 0000 en binaire).
Le problème est que le Commodore 64 ne dispose pas d’assembleur (un programme qui fait la conversion assembleur->langage machine) en ROM, et en l’absence d’un programme externe (assembleur ou moniteur) il nous faut traduire manuellement les instructions assembleurs en langage machine décimal pour pouvoir les charger avec le BASIC.

Les listings contiendrons le code assembleur suivit de la traduction en langage machine. Nous n’utiliserons pas l’hexadécimale mais chaque référence à une adresse contiendra entre parenthèse l’équivalant hexadécimal qui par convention commence par le caractère ‘$’ et sa décomposition en décimal tel que adresse=octet1+octet2*256. Exemple l’adresse de la couleur de bord 53280 (32,208 ; $D020)

Rassurez-vous la programmation en assembleur est beaucoup plus ergonomique et lors de prochain tutoriaux nous utiliserons bientôt un vrai assembleur ce qui nous facilitera beaucoup la tâche.

Ici, il s’agit surtout de bien sentir et comprendre ce qu’est le langage machine.

Afin d’éviter d’employer de l’hexadécimale, la première partie de ce tutoriel est prévue pour charger les instructions machine uniquement en BASIC. Mais j’ai conscience que cela semblera pour certain très rédhibitoire et même si le Monitor ne sera présenté que dans la seconde partie, on pourra quand même faire ce tutoriel avec un Monitor comme MON que l’on trouve dans les cartridges « Black-box » ou « Final » ou bien SUPERMON64 que vous trouverez ici.
Voilà pourquoi tout les listings contiennent 3 parties :

  • Le listing assembleur sans hexadécimale et sans labels.
  • Le listing en code machine en décimale pour charger dans le Basic
  • Le listing en assembleur pour les Monitor en hexadécimale.

Commençons…

 

PREMIERE PARTIE : LE LANGUAGE MACHINE

Fondamentaux :

La mémoire est subdivisée en cases qu’on appelle octet, c’est-à-dire que chaque case possède 8 bits pour la représentation de 256 valeurs différentes. Le commodore 64 contient 65536 cases. C’est dans la mémoire que le processeur va chercher les instructions machines à exécuter. Pour ce faire le processeur possède un registre spécial appelé PC (Program Counter) dans lequel est contenue l’adresse (ou le numéro de la case si vous préférez) de la mémoire qui contient la prochaine instruction à exécuter. En simplifiant, le processeur prélève tout d’abord de la mémoire, dans la case indiquée par son registre PC, l’instruction à exécuter, incrémente son PC en fonction, décode l’instruction et enfin l’exécute. Une fois fait, le processeur va chercher l’instruction suivante et le cycle se répète. C’est l’un des bases du fonctionnement d’un micro-processeur et tous fonctionnent ainsi, aussi bien le 6510 du C64 que le x86/64 de votre PC/MAC, le ARM de votre téléphone portable ou bien encore le RAD du rover Curiosity sur Mars. Une instruction machine est une suite de 0 et de 1 qui est la seul chose que le processeur comprenne et dont la taille peut varier en fonction de l’instruction (et bien sûr du processeur). Pour le 6510 (et tous les processeurs dérivés du 6502) les instructions varient de 1 à 3 octets. Voyons tout de suite quelques instructions du 6510.

 

ECRITURE MEMOIRE

Nous voulons changer la valeur du fond (cadre) et le bord de l’écran (overscan) en noire.

En basic on fait simplement :

POKE 53280,0 : POKE 53281,0

C’est-à-dire on écrit dans les cases mémoire 53280 (32,208 ; $D020) et 53281(33,208 ; $D021) la valeur de 0 (pour la couleur noire) ces cases représentent deux registres du circuit graphique (le VIC) pour sélectionner les couleurs de bord et de fond respectivement.

En assembleur la différence n’est pas énorme:

PROGRAMME 01

ADRESSE       ASM                MACHINE Décimal    ASM MONITOR
49152         LDA #0             169,0              C000 LDA #$00
49154         STA 53280          141, 32, 208       C002 STA $D020
49157         STA 53281          141, 33, 208       C005 STA $D021
49160         RTS                96                 C008 RTS

Chaque ligne correspond à une instruction spécifique au processeur du C64 le MOS6510. Le code, ci-dessus, se traduit donc en langage machine par la suite de nombre (Toujours entre 0 et 255) suivants :

169, 0, 141, 32, 208, 141, 33, 208, 96.

L’assembleur n’est qu’un moyen mnémotechnique pour écrire du langage machine. Celui-ci se logera dans les cases mémoire du C64. Ici, notre petite routine fait 9 octets et donc occupera 9 cases mémoire adjacentes.
Examinons en détail cette suite d’instructions.

LDA #0 : On charge dans A la valeur 0 avec l’instruction LDA #valeur (169,valeur), le # indique qu’il s’agit d’une valeur direct. A est un registre du microprocesseur, une case mémoire de 1 octet qui se trouve dans le processeur et non dans la RAM. Le processeur du MOS6510 possède encore 2 autres registres X et Y plus trois autres registres : le PC et les registre SR (Status) et SP (Pile) que nous verrons plus tard. Pour charger la valeur 2 dans A on aurait fait LDA #2 (169,2), pour charger 56 LDA #56 etc. Le registre A est le registre principal du 6510 et la pluparts des opérations passeront par ce registre.

STA 53280 : On utilise ensuite l’instruction STA mem (141,mem) ou mem représente une adresse mémoire sur 2 octets pour y écrire la valeur que contient le registre A. Ici 53280 (32,208 ; $D020) fait référence au un registre du VIC qui contrôle la couleur du bord de l’écran. Nous avons précédemment chargé dans A la valeur de 0 et ainsi on écrit 0 dans la case mémoire 53280 (32,208 ; $D020) ce qui provoque le changement de la couleur du bord de l’écran en noire de la même façon qu’un POKE 53280,0 en BASIC.

STA 53281 : Exactement la même chose sauf que l’adresse où l’on charge la valeur du registre A correspond à la couleur de fond du cadre 53281 (33,208) ($D021).

RTS : Enfin, l’instruction RTS (96) permet de quitter notre routine, on terminera presque toujours nos routines assembleurs avec cette instruction.

Pour charger notre routine il suffit juste de « POKEr » les « opcodes » machine dans une partie de la mémoire avec le BASIC.

10   PRINT « CHARGEMENT »
20   ME=49152             : REM ADRESSE DE NOTRE ROUTINE
30   READ OP              : REM LECTURE DATA
40   IF OP<0 THEN GOTO 80 : REM FIN DES DONNES
50   POKE ME,OP           : REM CHARGEMENT DU CODE MACHINE
60   ME=ME+1              : REM CASE MEMOIRE SUIVANTE
70   GOTO 30              : REM BOUCLE
80   SYS 49152            : REM EXECUTION DE NOTRE ROUTINE
99   END
100  DATA 169,0           : REM LDA #0
110  DATA 141,32,208      : REM STA 53280
120  DATA 141,33,208      : REM STA 53281
130  DATA 96              : REM RTS
999  DATA -1              : REM FIN DES DONNEES

Dans les DATA notre programme en langage machine. Les commentaires de chaque ligne DATA donnent l’équivalent en assembleur. La commande SYS 49152 force le processeur à sauter en 49152 ($C000) pour exécuter notre routine. Taper ensuite RUN et vous constater que le cadre et le bord sont bien passé à la couleur noire. Dorénavant, nous utiliserons le corps de ce programme BASIC pour charger nos routine machine. Sauvegardons donc ce programme, il nous suffira ensuite de juste de modifier les DATA.

Avec cette routine extrêmement simple nous avons découverts deux instructions fondamentales en assembleurs : LDA #valeur STA mémoire.

Grace à ces instructions nous savons écrire n’importe quelle valeur dans n’importe quel endroit de la mémoire.

Rabâchons et précisons quelques points :

En code machine, l’instruction LDA #val se code très simplement : 169, val ou val est un chiffre compris entre 0 et 255. Donc si on veut charger 5 dans A, on écrira en ASM LDA #5 (169,5). Si nous voulons charger 39 dans A on écrira LDA #39 (169,39) Etc.

Le codage machine de l’instruction STA mémoire est un tout petit plus compliquée : Le code 141 sera toujours le premier octet, mais la valeur mémoire (l’adresse mémoire) doit être codée sur 2 octets de façons à ce que : mémoire =b1+b2*256 où b1 et b2 désigne les 2 octets suivants (toujours entre 0 et 255).

Par exemple 53280=208*256+32 et 53281=208*256+33.

Voilà pourquoi STA 53280 en code machine donne 141, 32, 208 (32+208*256=53280). De même que STA 53281 est code par 141, 33, 208 (53281=208*256+33).

Pour trouver b1 et b2 d’une adresse quelconque on doit d’abord la diviser l’adresse par 256. Le résultat entier donne b2. Pour trouver b1 on soustrait à l’adresse le résultat b2*256.
Ci-dessous le code basic pour trouver B1 et B2 de MEM.

B2 = INT(MEM/256)
B1 = MEM – B2 * 256

Petite astuce, si nous écrivons dans les registre du VIC (le circuit graphique du C-64), l’adresse haute (b2) sera toujours représenté par 208 et le l’adresse basse (b1) sera donc le numéro du registre VIC.

Grace aux instructions STA mémoire et LDA #Val nous sommes déjà en mesure de remplacer avantageusement l’instruction POKE du basic.

L’instruction RTS est l’équivalant de l’instruction RETURN du BASIC toutes nos routines devront, pour l’instant, se terminer par cette instruction.

Peut-être vous demandez vous pourquoi nous utilisons l’adresse 49152 ($C000) pour charger la routine machine. Et bien la réponse est très simple : l’espace entre les adresses 49152 et 53247 ($C000-$CFFF) est, par défaut, inutilisé ni par la ROM Kernel ni le BASIC et donc libre pour l’utilisateur.

 

LECTURE MEMOIRE

Introduisons une nouvelle instruction à travers cette petite routine :

PROGRAMME 02

ADDRESSE      ASM           MACHINE            ASM MONITOR
49152         LDA 49664     173, 0, 194        C000 LDA $C200
49155         STA 53280     141, 32, 208       C003 STA $D020
49158         STA 53281     141, 33, 208       C006 STA $D021
49161         RTS           96                 C009 RTS

Cela ressemble beaucoup à notre première routine à une différence importante : l’instruction LDA mémoire ne charge plus une valeur directement dans le registre A mais une valeur que contient une case mémoire de la RAM, ici 49664 (0,194 ;$C200). Le reste du programme est identique.

La traduction de cette nouvelle instruction en langage machine sera : 173,b1,b2 où le codage de l’adresse représente par b1 et b2 est exactement la même que pour l’instruction STA mémoire.

Cette instruction est donc l’équivalant de PEEK(N) en BASIC.

En faisant ainsi nous utilisons l’adresse 49664 dont la valeur est une variable que nous pouvons contrôler avec le BASIC. Par exemple le programme ci-dessous charge notre routine et ensuite va l’utiliser en changeant la valeur contenue à l’adresse 49664 pour appel ensuite notre routine machine.

10   FOR I=0 TO 10 : READ OP : POKE 49152+I,OP : NEXT I
                                :REM CHARGEMENT DE NOTRE ROUTINE ASM !
20   DATA 173,0,194             :REM LDA 49664
30   DATA 141,32,208            :REM STA 53280
40   DATA 141,33,208            :REM STA 53281
50   DATA 96                    :REM RTS
100  X=0                        :REM VARIABLE X
110 POKE 49664,X                :REM ECRIT DANS 49664 LA VALEUR X
120 SYS 49152                   :REM APPEL NOTRE ROUTINE ASM
130 X=X+1 : IF X>255 THEN X=0   :REM INCREMENTE X DOIT TOUJOURS ETRE INFERIEUR A 255
140 GOTO 110                    :REM BOULCE

 

Registre X et Y :

Introduisons quelques nouvelles instructions. Ce sont quasiment les mêmes que nous avons vu précédemment à la différence près que ce n’est plus le registre A qui est concerné mais un autre registre en l’occurrence le registre X.

ADDRESSE      ASM           MACHINE            ASM MONITOR
49152         LDX #00       162, 0             C000 LDX #$00
49154         STX 53280     142, 32, 208       C002 STX $D020
49157         STX 53281     142, 33, 208       C005 STX $D021
49160         RTS           96                 C008 RTS

Le processeur du C64 contient en tout 3 registres avec lesquels nous pouvons travailler directement : le A, le X et le Y.

Le programme ci-dessus peut également s’écrire avec le registre Y :

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDY #00       160, 0             C000 LDY #$00
49154         STY 53280     140, 32, 208       C002 STY $D020
49157         STY 53281     140, 33, 208       C005 STY $D021
49160         RTS           96                 C008 RTS

De même que notre deuxième routine utilisant la lecture peut aussi s’écrire:

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDX 49664     174, 0, 194        C000 LDX $C100
49155         STX 53280     142, 32, 208       C003 STX $D020
49158         STX 53281     142, 33, 208       C006 STX $D021
49161         RTS           96                 C009 RTS

Ou bien en utilisant le registre Y:

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDY 49664     172, 0, 194        C000 LDY $C100
49155         STY 53280     140, 32, 208       C003 STY $D020
49158         STY 53281     140, 33, 208       C006 STY $D021
49161         RTS           96                 C009 RTS

Le registre A est le registre principal du 6510 dans lequel passe la majorité des opérations effectuable. Les registres X et Y sont des registres dits d’index et comprennent quelques limitations dans leur utilisation. Mais pour l’instant contentons-nous de considérer ces trois registres comme équivalant.

 

Branchement et boucle :

A présent que nous savons remplacer les POKEs et PEEK du BASIC étudions maintenant l’équivalant assembleur du GOTO en faisant un truc un peu plus marrant avec le programme ci-dessous qui introduit 2 nouvelles instructions:

PROGRAMME 03

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDX #0        162, 0             C000 LDX #$00
49154         STX 53280     142, 32, 208       C002 STX $D020
49157         STX 53281     142, 33, 208       C005 STX $D021
49160         INX           232                C008 INX
49161         JMP 49154     76, 2, 192         C009 JMP $C002

L’instruction INX (232) incrémente le registre X d’une unité. Un peu comme X=X+1 en BASIC sauf que X en BASIC désigne un variable alors qu’en Assembleur X est un registre du processeur.

JMP mem (76, mem2,mem1) oblige le processeur à sauter à l’adresse spécifié en paramètres mem. C’est l’équivalent de de GOTO en Basic. Ici, l’instruction JMP obligera le processeur à recommencer à partir de l’instruction STX 53280 c’est-à-dire a l’adresse 49154.

49152        LDX #0      
49154        STX 53280          
49157        STX 53281          
49160        INX                
49161        JMP 49154

Attention : la routine va ainsi boucler indéfiniment changeant les couleurs du fond et du bord à toute vitesse et il nous sera impossible d’interrompre l’exécution de notre routine.

Exécuter ce programme : pas trop mal non ?

Essayez d’écrire ce même programme en basic et constater la différence de vitesse !

10 A = A + 1 : IF A>255 THEN A=0
20 POKE 53280,A :POKE 53281,A
30 GOTO 10

Le programme BASIC fait clignoter l’écran laborieusement avec des artefacts sur les lignes verticales tandis qu’en Assembleur cela va tellement vite que les changements se produise carrément au sein des lignes vertical.

Pour vous donner un ordre de grandeur l’assembleur est de 100 à 800 fois plus rapide que la BASIC soit la différence de vitesse entre un marcheur et un Avion supersonique !
Chaque instruction machine prend un certain nombre de cycles d’exécution. Par exemple, les instructions prennent 2 cycles pour LDA/X/Y #val, 4 cycles pour STA/X/Y mem et 6 cycles pour JMP mem. Le processeur étant cadence à environ 1 MHZ il exécute environ 1 millions de cycles par seconde. La boucle principale de notre petit programme prend 16 cycles.

STX 53280     ; 4 CYCLES   
STX 53281     ; 4 CYCLES
INX           ; 2 CYCLES
JMP 49154     ; 6 CYCLES

C’est à dire que ce bout de code s’exécute en 16 microsecondes (0,000016 secondes) ! Ou bien il peut être exécuté 62500 fois par seconde !!!

En réalité cette boucle s’exécute plus lentement car des évènements extérieurs au processeur sont susceptibles de lui voler des cycles. Le dernier programme de ce tutorial désactivera ces « mangeurs de cycles » pour permettre au MOS6510 de s’exprimer à sa pleine vitesse.

 

APPEL DE SOUS-ROUTINES :

Pour ne pas à redémarrer notre C64 à chaque fois il faudrait que notre programme puisse également lire l’état du clavier pour pouvoir l’interrompre. La ROM de notre C64 contient un bon nombre de routines destinées à nous faciliter la tâche.

Par exemple, la routine 65508 (228,255) ($FFE4) nous permet de tester l’état du clavier.

Pour appeler une routine on utilise l’instruction JSR mem (32, mem2, mem1) où MEM est l’adresse de la routine à appeler c’est l’exacte équivalant de la commande SYS MEM en BASIC et l’équivalant relatif de l’instruction GOSUB.

PROGRAMME 04

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDX #00       162, 0             C000 LDX #$00
49154         STX 53280     142, 32, 208       C002 STX $D020
49157         STX 53281     142, 33, 208       C005 STX $D021
49160         INX           232                C008 INX
49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
49164         BNE 3         208, 3             C00C BNE $C011
49166         JMP 49154     76, 2, 192         C00E JMP $C002
49169         RTS           96                 C011 RTS

L’instruction JSR 65508 renvoi l’état du clavier dans le registre A.

L’instruction BNE offset (208,offset) est une instruction de branchement conditionnelle. C’est-à-dire elle branche si une condition est réalisée ou en d’autre mot elle force le processeur à changer son registre PC pour aller exécuter une autre instruction que la suivante.

Bon attention, c’est là que ça se corse mais dites-vous que lorsque vous aurez compris ce point vous aurez tout compris au branchement conditionnel.

En réalité, BNE va tester l’état d’un autre registre dont l’accessibilité diffère par rapport aux registres A, X et Y.

Ce registre appelé STATUS parfois FLAGS (drapeaux) que l’on abrègera par SR contient 8 bits ou chaque bit donne diverses indications sur la dernière opération effectuée. Ces indications peuvent prendre que deux valeurs possible : vrai (1) ou faux (0).

Voici quelques exemples d’indications les plus utilisées :

Z (ZERO) indique si la dernière opération a engendrée une valeur de 0 auquel cas le bit Z Vrai.

N (NEGATIF) indique si la dernière opération a engendrée un nombre négatif auquel cas N sera Vrai.

C (CARRY ou Retenue) indique si la dernière opération a engendrée une retenue auquel cas C sera Vrai.

Par exemple l’instruction LDX #0 initialise le registre X à 0 mais également l’état Z du registre FLAG passe à l’état vrai.

De même l’instruction INX incrémente le registre X et cette opération agira sur l’état Z en fonction de la valeur de X. Si X est égale à 0, Z sera VRAI et FAUX dans le cas contraire.

Attention toutes les instructions n’agissent pas forcement sur SR et pas forcément sur tous les états. Par exemple, les instructions LDA, LDX, INX n’agissent que sur les états N et Z.

L’instruction BNE branche si l’état Z est faux. En d’autres termes, elle branche si la dernière opération a donné un résultat diffèrent de zéro.

Pour brancher si l’état Z est vrai, on utilise l’instruction BEQ offset (240,offset) qui est donc l’inverse de l’instruction BNE.

La deuxième chose délicate est que les instructions de branchement conditionnelles utilisent comme argument non pas l’adresse où le processeur doit sauter mais l’offset : c’est-à-dire un déplacement relatif sur 1 octets signée avec donc comme valeur possible de -128 à 127 par rapport à l’adresse qui suit les 2 octets de l’instruction de branchement.

Dans notre exemple :

ASM           MACHINE            ASM MONITOR
JSR 65508     32, 228, 255       C009 JSR $FFE4
BNE 3         208, 3             C00C BNE $C011
JMP 49154     76, 2, 192         C00E JMP $C002
RTS           96                 C011 RTS

La fonction 65508 renvoi l’état du clavier en registre A et modifie également le registre SR. Si une touche du clavier est pressée, le FLAG Zero sera faux et l’instruction BNE 3 vérifiant l’état du drapeau Z et, s’il est faux, saute de 3 octets et évite ainsi l’instruction JMP 49152 pour atteindre directement l’instruction RTS qui rend la main au BASIC. Si le FLAG Zero est vrai, l’instruction BNE 3 ne fait rien et le processeur atteint l’instruction JMP 49154 qui saute à l’adresse 49154 pour recommencer à changer les couleurs de l’écran.

Nous reviendrons sur le codage des octets signés lors d’un tutorial consacré à l’arithmétique avec le MOS6502, mais pour avoir le nombre négatif il suffit juste d’utiliser la formule 256-N où N est la valeur absolue de notre nombre négatif. Exemple -1 est codé par 256-1=255 ; -16 par 256-16=240 etc.

En utilisant un déplacement négatif on aurait pu sauver une instruction dans notre code de cette façon :

PROGRAMME 05

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDX #00       162, 0             C000 LDX #$00
49154         STX 53280     141, 32, 208       C002 STX $D020
49157         STX 53281     141, 33, 208       C005 STX $D021
49160         INX           232                C008 INX
49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
49164         BEQ -12       240, 244           C00C BEQ $C002
49166         RTS           96                 C00F RTS

Noter que nous pouvons tester le clavier de façon plus précise, par exemple nous voulons quitter le programme seulement lorsque l’utilisateur appuie sur la touche ‘Q’.

On va utiliser pour cela l’instruction CMP #valeur (201,valeur). Cette instruction compare le registre A avec la valeur passée en paramètre en effectuant en interne une soustraction (A-valeur) et va affecter les drapeaux en conséquence mais sans changer la valeur du registre A.

Changeons donc légèrement notre routine :

ASM           MACHINE            ASM MONITOR
JSR 65508     32, 228, 255       C009 JSR $FFE4
CMP #81       201, 81            C00C CMP #$51
BEQ 3         240, 3             C00E BEQ $C013
JMP 49152     76, 0, 192         C010 JMP $C002
RTS           96                 C013 RTS

Maintenant seul l’appui de la touche Q (81 est la valeur retourne par la fonction 65508 lorsque la touche Q est pressée) permet de quitter le programme.

Notez que lorsque l’on presse une autre touche l’affichage du programme se modifie : cela est lié aux mangeurs de cycles évoqués plus tôt ici c’est une interruption qui s’enclenche lorsqu’une touche du clavier est pressée. Nous verrons dans un autre tutoriel ce que sont les interruptions.

On peut même améliorer notre petit programme en rétablissant l’état initial des couleurs lorsque nous quittons:

PROGRAMME 06

ADRESSE       ASM           MACHINE            ASM MONITOR
49152         LDX #00       162, 0             C000 LDX #$00
49154         STX 53280     141, 32, 208       C002 STX $D020
49157         STX 53281     141, 33, 208       C005 STX $D021
49160         INX           232                C008 INX
49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
49164         CMP #81       201,81             C00C CMP #$51
49166         BEQ 3         240, 3             C00E BEQ $C013
49168         JMP 49152     76, 2, 192         C010 JMP $C002
49171         LDX #254      162, 254           C013 LDX #$FE
49173         STX $D020     141, 32, 208       C015 STX $D020
49176         LDX #246      162, 246           C018 LDX #$F6
49178         STX $D021     141, 33, 208       C01A STX $D021
49181         RTS           96                 C01D RTS

Apres l’instruction JMP 49152 nous remettons les registres 53280 et 53281 à leur valeur d’origine (respectivement 254 et 246).

 

Un programme final

Voici un petit programme qui va clôturer ce tutoriel en utilisant trois nouvelles instructions SEI (120), CLI (88) et NOP (234) et agir sur un registre du VIC le 53265 (17,208 ; $D011) pour n’afficher que l’overscan.

Ce programme est fort similaire au précèdent. La différence est qu’il désactive une partie de VIC n’affichant plus que le bord d’écran et désactive les interruptions avec l’instruction SEI.

Enfin, la lecture du registre 56321 (01,220 ; $DC01) du CIA-I nous permets de tester les touches RUN/STOP-SPACE (et d’autres mais pas toutes) du clavier pour sortir de la boucle.

PROGRAMME 07

ADRESSE  ASM           MACHINE        ASM MONITOR      COMMENTAIRES
49152    LDA #0        169, 0         C000 LDA #$00    A = 0
49154    STA 53265     141, 17, 208   C002 STA $D011   Desactive le fond
49157    SEI           120            C005 SEI         Desactive les interruptions
49158    LDX #00       162, 0         C006 LDX #$00    X = 0
49160    STX 53280     142, 32, 208   C008 STX $D020   Couleur de fond noir
49163    INX           232            C00B INX         Incemente X
49164    NOP           234            C00C NOP         Rien
49165    LDA 56321     173, 1, 220    C00D LDA $DC01   Lecture CIA-1 clavier
                                                       colonne 0
49168    CMP #255      201, 255       C010 CMP #$FF    Si aucune touche est presse
                                                       tout les bits sont allumees
49170    BNE 3         208, 3         C012 BNE $C017   Sinon on saute a 49175 en
                                                       evitant la reboucle
49172    JMP 49163     76, 08, 192    C014 JMP $C008   Reboucle
49175    CLI           88             C017 CLI         Reactive les interruptions
49176    LDA  #27      169, 27        C018 LDA #$1B    Valeur orignial de $D011 en
                                                       A pour reactiver le VIC
49178    STA 53265     141, 17, 208   C01A STA $D020   Reactive l’ecran de fond   
49181    LDX #$254     162, 254       C01D LDX #$FE    X = 254 couleur de bord par
                                                       defaut
49183    STX 53280     142, 32, 208   C01F STX $D020   Couleur de bord = X = 254
49186    RTS           96             C012 RTS         Quitte

Les instructions SEI et CLI inhibe et rétablit respectivement les interruptions. On désactive les interruptions pour permettre l’exécution du programme à pleine vitesse. Nous verrons en détail dans un prochain tutorial ce que sont les interruptions.

Enfin pour quitter le programme nous n’utilisons pas la fonction du ROM pour ne pas perdre de temps et lisons directement dans le registre 56321 ($DC01) dont la valeur par défaut (255) change lorsque on appuie sur les touches ESC, SPACE, Q et d’autres (La lecture direct du clavier est un petit plus complexe mais dans notre cas cela suffit).

Enfin l’instruction NOP est une instruction qui ne fait rien ! Mais ce n’est pas parce qu’une instruction ne fait rien qu’elle ne sert à rien : ici, on l’utilise pour perdre des cycles.

Programme BASIC pour charger et exécuter cette routine :

10   PRINT « CHARGEMENT »
20   ME=49152             : REM ADRESSE DE NOTRE ROUTINE
30   READ OP              : REM LECTURE DATA
40   IF OP<0 THEN GOTO 80 : REM FIN DES DONNEES
50   POKE ME,OP           : REM CHARGEMENT DU CODE MACHINE
60   ME=ME+1              : REM CASE MEMOIRE SUIVANTE
70   GOTO 30              : REM BOUCLE
80   SYS 49152            : REM EXECUTION DE NOTRE ROUTINE
100  DATA 169,0           : REM LDA #$00
120  DATA 141,17,208      : REM STA $D011
130 DATA 120              : REM SEI
140  DATA 162,0           : REM LDX 0
150  DATA 142,32,208      : REM STX $D020
160 DATA 232              : REM INX
170  DATA 234             : REM NOP
180 DATA 173,1,220        : REM LDA $DC01
190  DATA 201,255         : REM CMP #$FF
200  DATA 208,3           : REM BNE 3
210  DATA 76,08,192       : REM JMP 49160 (LINE 150 DATA)
220  DATA 88              : REM CLI
240  DATA 169,27          : REM LDA #$1B
250  DATA 141,17,208      : REM STA $D011
260  DATA 162,254         : REM LDX #254
270  DATA 142,32,208      : REM STX $D020
280  DATA 96              : REM RTS
999  DATA -1              : REM FIN DES DONNEES

Pour illustrer le tutorial j’ai fait une vidéo qui, je pense, facilitera peut-être la lecture un peu lourde comme n’importe quel tutorial sur l’assembleur :)

La vidéo s’amuse avec le dernier programme à rajouter des NOP dans la boucle interne modifiant ainsi complètement les effets de l’affichage.

Je vous encourage à faire de même et modifier ce programme en rajoutant des NOP ou des INX dans la boucle interne (Entre la ligne 150 et la ligne 210) et observer les changements produit !

J’ai souvent lu que l’un des principale reproche que font les gens, voulant apprendre l’assembleur, était qu’ils se décourageaient très vite devant le flot d’information obscure et finalement rien de bien concret. Donc j’ai essayé, et sans m’attarder sur le binaire et l’Hexadécimale (pourtant indispensable), de faire découvrir l’assembleur du processeur MOS65xx et son langage machine avec des programmes simples qui produisent des effets à l’écran impossible à reproduire avec le BASIC.

Ainsi se termine ce tutoriel en espérant qu’il vous a donné envie.

Mais il est évident que nous n’allons pas nous amuser à écrire directement du langage machine avec du BASIC. Il nous faut un outil d’assemblage. On peut distinguer trois types d’assembleur par ordre d’ergonomie.

Monitor: FINAL (BLACK BOX), SUPERMON.
Assembleur: TURBO Assembleur.
Cross-Assembleur : TMPX.

Le prochain tutoriel présentera un Monitor, un programme pour assembler un programme en Mémoire. Ce sera beaucoup plus pratique que de passer par le BASIC (encore que…). Nous y verrons la notation hexadécimal, l’adressage indexe, la pile et quelques autres trucs sympas.

Enfin la troisième partie présentera l’assembleur TMP sous C-64 et un cross-assembleur (TMPX) pour une ergonomie maximale.

Tout en présentant ces outils nous en profiterons pour voir quelques points omis dans ce tutorial pourtant indispensables.

Une référence en français sur l’assembleur du MOS6510 est disponible sur l’incontournable site de Idoc64. Un cours d’Assembleur du MOS65xx en anglais sur AtariArchives dans lequel vous trouverez la description complète des instructions.

 

Pzawa

Tutoriel – Introduction au langage machine et à l’ASM C64 – Partie 1

  • Ce sujet contient 9 réponses, 7 participants et a été mis à jour pour la dernière fois par kyl83, le il y a 6 années et 7 mois.
  • Créateur
    Sujet
  • #9082
    Amiga France

      Tutoriel Commodore 64 AssembleurVous savez taper quelques programmes Basic ? Vous avez lu et compris le manuel utilisateur à 50% ? Vous avez compris les instructions POKE et PEEK du BASIC ? Vous savez faire sortir un son ou afficher un Sprite ? Si les réponses sont oui c’est que vous êtes mure pour commencer à parler directement à votre ordinateur (ici le C64) avec le langage machine.

      En effet, le basic du Commodore 64 est si pauvre qu’il n’a, en fait, jamais été aussi proche du langage machine !

      Le langage assembleur n’est pas compliqué : il est même très simple. Trop simple en effet les instructions élémentaires d’un microprocesseur sont très primaires et il faut un peu plus d’instructions pour effectuer un simple PRINT et afficher un simple message à l’écran. Mais comme tout, il faut pratiquer et saisir quelques points élémentaires, et contrairement à ce que l’on peut penser, il ne faut pas beaucoup de temps pour devenir productif.

      Enfin maitriser l’assembleur du Commodore 64, c’est également maitriser le langage machine de la légendaire famille des processeurs MOS 6502 qui équipa nombreuses star comme les Atari 8bit, les Apple (I et II) ou encore la NES de Nintendo (La SNES utilise une version 16 bits du MOS65xx) et par … le Terminator T-800. La famille de processeur MOS65xx est encore de nos jours produit en version 8/16 bits par Western Design (cofondé par Bill Mensch l’un des créateurs du 6502) et utilisée principalement dans l’informatique embarqué.

      L’Assembleur et le langage machine sont équivalents. La différence est qu’en assembleur on utilise des petits mots mnémotechniques qui vont remplacer les obscurs chiffres du langage machines. Par exemple en assembleur, l’instruction LDA #0 est traduit en langage machine par deux octets : 169,0. (Ou bien $A9, $00 en hexadécimale c’est-à-dire 1010 1001 0000 0000 en binaire).
      Le problème est que le Commodore 64 ne dispose pas d’assembleur (un programme qui fait la conversion assembleur->langage machine) en ROM, et en l’absence d’un programme externe (assembleur ou moniteur) il nous faut traduire manuellement les instructions assembleurs en langage machine décimal pour pouvoir les charger avec le BASIC.

      Les listings contiendrons le code assembleur suivit de la traduction en langage machine. Nous n’utiliserons pas l’hexadécimale mais chaque référence à une adresse contiendra entre parenthèse l’équivalant hexadécimal qui par convention commence par le caractère ‘$’ et sa décomposition en décimal tel que adresse=octet1+octet2*256. Exemple l’adresse de la couleur de bord 53280 (32,208 ; $D020)

      Rassurez-vous la programmation en assembleur est beaucoup plus ergonomique et lors de prochain tutoriaux nous utiliserons bientôt un vrai assembleur ce qui nous facilitera beaucoup la tâche.

      Ici, il s’agit surtout de bien sentir et comprendre ce qu’est le langage machine.

      Afin d’éviter d’employer de l’hexadécimale, la première partie de ce tutoriel est prévue pour charger les instructions machine uniquement en BASIC. Mais j’ai conscience que cela semblera pour certain très rédhibitoire et même si le Monitor ne sera présenté que dans la seconde partie, on pourra quand même faire ce tutoriel avec un Monitor comme MON que l’on trouve dans les cartridges « Black-box » ou « Final » ou bien SUPERMON64 que vous trouverez ici.
      Voilà pourquoi tout les listings contiennent 3 parties :

      • Le listing assembleur sans hexadécimale et sans labels.
      • Le listing en code machine en décimale pour charger dans le Basic
      • Le listing en assembleur pour les Monitor en hexadécimale.

      Commençons…

       

      PREMIERE PARTIE : LE LANGUAGE MACHINE

      Fondamentaux :

      La mémoire est subdivisée en cases qu’on appelle octet, c’est-à-dire que chaque case possède 8 bits pour la représentation de 256 valeurs différentes. Le commodore 64 contient 65536 cases. C’est dans la mémoire que le processeur va chercher les instructions machines à exécuter. Pour ce faire le processeur possède un registre spécial appelé PC (Program Counter) dans lequel est contenue l’adresse (ou le numéro de la case si vous préférez) de la mémoire qui contient la prochaine instruction à exécuter. En simplifiant, le processeur prélève tout d’abord de la mémoire, dans la case indiquée par son registre PC, l’instruction à exécuter, incrémente son PC en fonction, décode l’instruction et enfin l’exécute. Une fois fait, le processeur va chercher l’instruction suivante et le cycle se répète. C’est l’un des bases du fonctionnement d’un micro-processeur et tous fonctionnent ainsi, aussi bien le 6510 du C64 que le x86/64 de votre PC/MAC, le ARM de votre téléphone portable ou bien encore le RAD du rover Curiosity sur Mars. Une instruction machine est une suite de 0 et de 1 qui est la seul chose que le processeur comprenne et dont la taille peut varier en fonction de l’instruction (et bien sûr du processeur). Pour le 6510 (et tous les processeurs dérivés du 6502) les instructions varient de 1 à 3 octets. Voyons tout de suite quelques instructions du 6510.

       

      ECRITURE MEMOIRE

      Nous voulons changer la valeur du fond (cadre) et le bord de l’écran (overscan) en noire.

      En basic on fait simplement :

      POKE 53280,0 : POKE 53281,0

      C’est-à-dire on écrit dans les cases mémoire 53280 (32,208 ; $D020) et 53281(33,208 ; $D021) la valeur de 0 (pour la couleur noire) ces cases représentent deux registres du circuit graphique (le VIC) pour sélectionner les couleurs de bord et de fond respectivement.

      En assembleur la différence n’est pas énorme:

      PROGRAMME 01
      
      ADRESSE       ASM                MACHINE Décimal    ASM MONITOR
      49152         LDA #0             169,0              C000 LDA #$00
      49154         STA 53280          141, 32, 208       C002 STA $D020
      49157         STA 53281          141, 33, 208       C005 STA $D021
      49160         RTS                96                 C008 RTS

      Chaque ligne correspond à une instruction spécifique au processeur du C64 le MOS6510. Le code, ci-dessus, se traduit donc en langage machine par la suite de nombre (Toujours entre 0 et 255) suivants :

      169, 0, 141, 32, 208, 141, 33, 208, 96.

      L’assembleur n’est qu’un moyen mnémotechnique pour écrire du langage machine. Celui-ci se logera dans les cases mémoire du C64. Ici, notre petite routine fait 9 octets et donc occupera 9 cases mémoire adjacentes.
      Examinons en détail cette suite d’instructions.

      LDA #0 : On charge dans A la valeur 0 avec l’instruction LDA #valeur (169,valeur), le # indique qu’il s’agit d’une valeur direct. A est un registre du microprocesseur, une case mémoire de 1 octet qui se trouve dans le processeur et non dans la RAM. Le processeur du MOS6510 possède encore 2 autres registres X et Y plus trois autres registres : le PC et les registre SR (Status) et SP (Pile) que nous verrons plus tard. Pour charger la valeur 2 dans A on aurait fait LDA #2 (169,2), pour charger 56 LDA #56 etc. Le registre A est le registre principal du 6510 et la pluparts des opérations passeront par ce registre.

      STA 53280 : On utilise ensuite l’instruction STA mem (141,mem) ou mem représente une adresse mémoire sur 2 octets pour y écrire la valeur que contient le registre A. Ici 53280 (32,208 ; $D020) fait référence au un registre du VIC qui contrôle la couleur du bord de l’écran. Nous avons précédemment chargé dans A la valeur de 0 et ainsi on écrit 0 dans la case mémoire 53280 (32,208 ; $D020) ce qui provoque le changement de la couleur du bord de l’écran en noire de la même façon qu’un POKE 53280,0 en BASIC.

      STA 53281 : Exactement la même chose sauf que l’adresse où l’on charge la valeur du registre A correspond à la couleur de fond du cadre 53281 (33,208) ($D021).

      RTS : Enfin, l’instruction RTS (96) permet de quitter notre routine, on terminera presque toujours nos routines assembleurs avec cette instruction.

      Pour charger notre routine il suffit juste de « POKEr » les « opcodes » machine dans une partie de la mémoire avec le BASIC.

      10   PRINT « CHARGEMENT »
      20   ME=49152             : REM ADRESSE DE NOTRE ROUTINE
      30   READ OP              : REM LECTURE DATA
      40   IF OP<0 THEN GOTO 80 : REM FIN DES DONNES
      50   POKE ME,OP           : REM CHARGEMENT DU CODE MACHINE
      60   ME=ME+1              : REM CASE MEMOIRE SUIVANTE
      70   GOTO 30              : REM BOUCLE
      80   SYS 49152            : REM EXECUTION DE NOTRE ROUTINE
      99   END
      100  DATA 169,0           : REM LDA #0
      110  DATA 141,32,208      : REM STA 53280
      120  DATA 141,33,208      : REM STA 53281
      130  DATA 96              : REM RTS
      999  DATA -1              : REM FIN DES DONNEES

      Dans les DATA notre programme en langage machine. Les commentaires de chaque ligne DATA donnent l’équivalent en assembleur. La commande SYS 49152 force le processeur à sauter en 49152 ($C000) pour exécuter notre routine. Taper ensuite RUN et vous constater que le cadre et le bord sont bien passé à la couleur noire. Dorénavant, nous utiliserons le corps de ce programme BASIC pour charger nos routine machine. Sauvegardons donc ce programme, il nous suffira ensuite de juste de modifier les DATA.

      Avec cette routine extrêmement simple nous avons découverts deux instructions fondamentales en assembleurs : LDA #valeur STA mémoire.

      Grace à ces instructions nous savons écrire n’importe quelle valeur dans n’importe quel endroit de la mémoire.

      Rabâchons et précisons quelques points :

      En code machine, l’instruction LDA #val se code très simplement : 169, val ou val est un chiffre compris entre 0 et 255. Donc si on veut charger 5 dans A, on écrira en ASM LDA #5 (169,5). Si nous voulons charger 39 dans A on écrira LDA #39 (169,39) Etc.

      Le codage machine de l’instruction STA mémoire est un tout petit plus compliquée : Le code 141 sera toujours le premier octet, mais la valeur mémoire (l’adresse mémoire) doit être codée sur 2 octets de façons à ce que : mémoire =b1+b2*256 où b1 et b2 désigne les 2 octets suivants (toujours entre 0 et 255).

      Par exemple 53280=208*256+32 et 53281=208*256+33.

      Voilà pourquoi STA 53280 en code machine donne 141, 32, 208 (32+208*256=53280). De même que STA 53281 est code par 141, 33, 208 (53281=208*256+33).

      Pour trouver b1 et b2 d’une adresse quelconque on doit d’abord la diviser l’adresse par 256. Le résultat entier donne b2. Pour trouver b1 on soustrait à l’adresse le résultat b2*256.
      Ci-dessous le code basic pour trouver B1 et B2 de MEM.

      B2 = INT(MEM/256)
      B1 = MEM – B2*256

      Petite astuce, si nous écrivons dans les registre du VIC (le circuit graphique du C-64), l’adresse haute (b2) sera toujours représenté par 208 et le l’adresse basse (b1) sera donc le numéro du registre VIC.

      Grace aux instructions STA mémoire et LDA #Val nous sommes déjà en mesure de remplacer avantageusement l’instruction POKE du basic.

      L’instruction RTS est l’équivalant de l’instruction RETURN du BASIC toutes nos routines devront, pour l’instant, se terminer par cette instruction.

      Peut-être vous demandez vous pourquoi nous utilisons l’adresse 49152 ($C000) pour charger la routine machine. Et bien la réponse est très simple : l’espace entre les adresses 49152 et 53247 ($C000-$CFFF) est, par défaut, inutilisé ni par la ROM Kernel ni le BASIC et donc libre pour l’utilisateur.

       

      LECTURE MEMOIRE

      Introduisons une nouvelle instruction à travers cette petite routine :

      PROGRAMME 02
      
      ADDRESSE      ASM           MACHINE            ASM MONITOR
      49152         LDA 49664     173, 0, 194        C000 LDA $C200
      49155         STA 53280     141, 32, 208       C003 STA $D020
      49158         STA 53281     141, 33, 208       C006 STA $D021
      49161         RTS           96                 C009 RTS

      Cela ressemble beaucoup à notre première routine à une différence importante : l’instruction LDA mémoire ne charge plus une valeur directement dans le registre A mais une valeur que contient une case mémoire de la RAM, ici 49664 (0,194 ;$C200). Le reste du programme est identique.

      La traduction de cette nouvelle instruction en langage machine sera : 173,b1,b2 où le codage de l’adresse représente par b1 et b2 est exactement la même que pour l’instruction STA mémoire.

      Cette instruction est donc l’équivalant de PEEK(N) en BASIC.

      En faisant ainsi nous utilisons l’adresse 49664 dont la valeur est une variable que nous pouvons contrôler avec le BASIC. Par exemple le programme ci-dessous charge notre routine et ensuite va l’utiliser en changeant la valeur contenue à l’adresse 49664 pour appel ensuite notre routine machine.

      10   FOR I=0 TO 10 : READ OP : POKE 49152+I,OP : NEXT I
                                      :REM CHARGEMENT DE NOTRE ROUTINE ASM !
      20   DATA 173,0,194             :REM LDA 49664
      30   DATA 141,32,208            :REM STA 53280
      40   DATA 141,33,208            :REM STA 53281
      50   DATA 96                    :REM RTS
      100  X=0                        :REM VARIABLE X
      110 POKE 49664,X                :REM ECRIT DANS 49664 LA VALEUR X
      120 SYS 49152                   :REM APPEL NOTRE ROUTINE ASM
      130 X=X+1 : IF X>255 THEN X=0   :REM INCREMENTE X DOIT TOUJOURS ETRE INFERIEUR A 255
      140 GOTO 110                    :REM BOULCE

       

      Registre X et Y :

      Introduisons quelques nouvelles instructions. Ce sont quasiment les mêmes que nous avons vu précédemment à la différence près que ce n’est plus le registre A qui est concerné mais un autre registre en l’occurrence le registre X.

      ADDRESSE      ASM           MACHINE            ASM MONITOR
      49152         LDX #00       162, 0             C000 LDX #$00
      49154         STX 53280     142, 32, 208       C002 STX $D020
      49157         STX 53281     142, 33, 208       C005 STX $D021
      49160         RTS           96                 C008 RTS

      Le processeur du C64 contient en tout 3 registres avec lesquels nous pouvons travailler directement : le A, le X et le Y.

      Le programme ci-dessus peut également s’écrire avec le registre Y :

      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDY #00       160, 0             C000 LDY #$00
      49154         STY 53280     140, 32, 208       C002 STY $D020
      49157         STY 53281     140, 33, 208       C005 STY $D021
      49160         RTS           96                 C008 RTS

      De même que notre deuxième routine utilisant la lecture peut aussi s’écrire:

      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDX 49664     174, 0, 194        C000 LDX $C100
      49155         STX 53280     142, 32, 208       C003 STX $D020
      49158         STX 53281     142, 33, 208       C006 STX $D021
      49161         RTS           96                 C009 RTS

      Ou bien en utilisant le registre Y:

      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDY 49664     172, 0, 194        C000 LDY $C100
      49155         STY 53280     140, 32, 208       C003 STY $D020
      49158         STY 53281     140, 33, 208       C006 STY $D021
      49161         RTS           96                 C009 RTS

      Le registre A est le registre principal du 6510 dans lequel passe la majorité des opérations effectuable. Les registres X et Y sont des registres dits d’index et comprennent quelques limitations dans leur utilisation. Mais pour l’instant contentons-nous de considérer ces trois registres comme équivalant.

       

      Branchement et boucle :

      A présent que nous savons remplacer les POKEs et PEEK du BASIC étudions maintenant l’équivalant assembleur du GOTO en faisant un truc un peu plus marrant avec le programme ci-dessous qui introduit 2 nouvelles instructions:

      PROGRAMME 03
      
      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDX #0        162, 0             C000 LDX #$00
      49154         STX 53280     142, 32, 208       C002 STX $D020
      49157         STX 53281     142, 33, 208       C005 STX $D021
      49160         INX           232                C008 INX
      49161         JMP 49154     76, 2, 192         C009 JMP $C002

      L’instruction INX (232) incrémente le registre X d’une unité. Un peu comme X=X+1 en BASIC sauf que X en BASIC désigne un variable alors qu’en Assembleur X est un registre du processeur.

      JMP mem (76, mem2,mem1) oblige le processeur à sauter à l’adresse spécifié en paramètres mem. C’est l’équivalent de de GOTO en Basic. Ici, l’instruction JMP obligera le processeur à recommencer à partir de l’instruction STX 53280 c’est-à-dire a l’adresse 49154.

      49152        LDX #0      
      49154        STX 53280          
      49157        STX 53281          
      49160        INX                
      49161        JMP 49154

      Attention : la routine va ainsi boucler indéfiniment changeant les couleurs du fond et du bord à toute vitesse et il nous sera impossible d’interrompre l’exécution de notre routine.

      Exécuter ce programme : pas trop mal non ?

      Essayez d’écrire ce même programme en basic et constater la différence de vitesse !

      10 A = A + 1 : IF A>255 THEN A=0
      20 POKE 53280,A :POKE 53281,A
      30 GOTO 10

      Le programme BASIC fait clignoter l’écran laborieusement avec des artefacts sur les lignes verticales tandis qu’en Assembleur cela va tellement vite que les changements se produise carrément au sein des lignes vertical.

      Pour vous donner un ordre de grandeur l’assembleur est de 100 à 800 fois plus rapide que la BASIC soit la différence de vitesse entre un marcheur et un Avion supersonique !
      Chaque instruction machine prend un certain nombre de cycles d’exécution. Par exemple, les instructions prennent 2 cycles pour LDA/X/Y #val, 4 cycles pour STA/X/Y mem et 6 cycles pour JMP mem. Le processeur étant cadence à environ 1 MHZ il exécute environ 1 millions de cycles par seconde. La boucle principale de notre petit programme prend 16 cycles.

      STX 53280     ; 4 CYCLES   
      STX 53281     ; 4 CYCLES
      INX           ; 2 CYCLES
      JMP 49154     ; 6 CYCLES

      C’est à dire que ce bout de code s’exécute en 16 microsecondes (0,000016 secondes) ! Ou bien il peut être exécuté 62500 fois par seconde !!!

      En réalité cette boucle s’exécute plus lentement car des évènements extérieurs au processeur sont susceptibles de lui voler des cycles. Le dernier programme de ce tutorial désactivera ces « mangeurs de cycles » pour permettre au MOS6510 de s’exprimer à sa pleine vitesse.

       

      APPEL DE SOUS-ROUTINES :

      Pour ne pas à redémarrer notre C64 à chaque fois il faudrait que notre programme puisse également lire l’état du clavier pour pouvoir l’interrompre. La ROM de notre C64 contient un bon nombre de routines destinées à nous faciliter la tâche.

      Par exemple, la routine 65508 (228,255) ($FFE4) nous permet de tester l’état du clavier.

      Pour appeler une routine on utilise l’instruction JSR mem (32, mem2, mem1) où MEM est l’adresse de la routine à appeler c’est l’exacte équivalant de la commande SYS MEM en BASIC et l’équivalant relatif de l’instruction GOSUB.

      PROGRAMME 04
      
      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDX #00       162, 0             C000 LDX #$00
      49154         STX 53280     142, 32, 208       C002 STX $D020
      49157         STX 53281     142, 33, 208       C005 STX $D021
      49160         INX           232                C008 INX
      49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
      49164         BNE 3         208, 3             C00C BNE $C011
      49166         JMP 49154     76, 2, 192         C00E JMP $C002
      49169         RTS           96                 C011 RTS

      L’instruction JSR 65508 renvoi l’état du clavier dans le registre A.

      L’instruction BNE offset (208,offset) est une instruction de branchement conditionnelle. C’est-à-dire elle branche si une condition est réalisée ou en d’autre mot elle force le processeur à changer son registre PC pour aller exécuter une autre instruction que la suivante.

      Bon attention, c’est là que ça se corse mais dites-vous que lorsque vous aurez compris ce point vous aurez tout compris au branchement conditionnel.

      En réalité, BNE va tester l’état d’un autre registre dont l’accessibilité diffère par rapport aux registres A, X et Y.

      Ce registre appelé STATUS parfois FLAGS (drapeaux) que l’on abrègera par SR contient 8 bits ou chaque bit donne diverses indications sur la dernière opération effectuée. Ces indications peuvent prendre que deux valeurs possible : vrai (1) ou faux (0).

      Voici quelques exemples d’indications les plus utilisées :

      Z (ZERO) indique si la dernière opération a engendrée une valeur de 0 auquel cas le bit Z Vrai.

      N (NEGATIF) indique si la dernière opération a engendrée un nombre négatif auquel cas N sera Vrai.

      C (CARRY ou Retenue) indique si la dernière opération a engendrée une retenue auquel cas C sera Vrai.

      Par exemple l’instruction LDX #0 initialise le registre X à 0 mais également l’état Z du registre FLAG passe à l’état vrai.

      De même l’instruction INX incrémente le registre X et cette opération agira sur l’état Z en fonction de la valeur de X. Si X est égale à 0, Z sera VRAI et FAUX dans le cas contraire.

      Attention toutes les instructions n’agissent pas forcement sur SR et pas forcément sur tous les états. Par exemple, les instructions LDA, LDX, INX n’agissent que sur les états N et Z.

      L’instruction BNE branche si l’état Z est faux. En d’autres termes, elle branche si la dernière opération a donné un résultat diffèrent de zéro.

      Pour brancher si l’état Z est vrai, on utilise l’instruction BEQ offset (240,offset) qui est donc l’inverse de l’instruction BNE.

      La deuxième chose délicate est que les instructions de branchement conditionnelles utilisent comme argument non pas l’adresse où le processeur doit sauter mais l’offset : c’est-à-dire un déplacement relatif sur 1 octets signée avec donc comme valeur possible de -128 à 127 par rapport à l’adresse qui suit les 2 octets de l’instruction de branchement.

      Dans notre exemple :

      ASM           MACHINE            ASM MONITOR
      JSR 65508     32, 228, 255       C009 JSR $FFE4
      BNE 3         208, 3             C00C BNE $C011
      JMP 49154     76, 2, 192         C00E JMP $C002
      RTS           96                 C011 RTS

      La fonction 65508 renvoi l’état du clavier en registre A et modifie également le registre SR. Si une touche du clavier est pressée, le FLAG Zero sera faux et l’instruction BNE 3 vérifiant l’état du drapeau Z et, s’il est faux, saute de 3 octets et évite ainsi l’instruction JMP 49152 pour atteindre directement l’instruction RTS qui rend la main au BASIC. Si le FLAG Zero est vrai, l’instruction BNE 3 ne fait rien et le processeur atteint l’instruction JMP 49154 qui saute à l’adresse 49154 pour recommencer à changer les couleurs de l’écran.

      Nous reviendrons sur le codage des octets signés lors d’un tutorial consacré à l’arithmétique avec le MOS6502, mais pour avoir le nombre négatif il suffit juste d’utiliser la formule 256-N où N est la valeur absolue de notre nombre négatif. Exemple -1 est codé par 256-1=255 ; -16 par 256-16=240 etc.

      En utilisant un déplacement négatif on aurait pu sauver une instruction dans notre code de cette façon :

      PROGRAMME 05
      
      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDX #00       162, 0             C000 LDX #$00
      49154         STX 53280     141, 32, 208       C002 STX $D020
      49157         STX 53281     141, 33, 208       C005 STX $D021
      49160         INX           232                C008 INX
      49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
      49164         BEQ -12       240, 244           C00C BEQ $C002
      49166         RTS           96                 C00F RTS

      Noter que nous pouvons tester le clavier de façon plus précise, par exemple nous voulons quitter le programme seulement lorsque l’utilisateur appuie sur la touche ‘Q’.

      On va utiliser pour cela l’instruction CMP #valeur (201,valeur). Cette instruction compare le registre A avec la valeur passée en paramètre en effectuant en interne une soustraction (A-valeur) et va affecter les drapeaux en conséquence mais sans changer la valeur du registre A.

      Changeons donc légèrement notre routine :

      ASM           MACHINE            ASM MONITOR
      JSR 65508     32, 228, 255       C009 JSR $FFE4
      CMP #81       201, 81            C00C CMP #$51
      BEQ 3         240, 3             C00E BEQ $C013
      JMP 49152     76, 0, 192         C010 JMP $C002
      RTS           96                 C013 RTS

      Maintenant seul l’appui de la touche Q (81 est la valeur retourne par la fonction 65508 lorsque la touche Q est pressée) permet de quitter le programme.

      Notez que lorsque l’on presse une autre touche l’affichage du programme se modifie : cela est lié aux mangeurs de cycles évoqués plus tôt ici c’est une interruption qui s’enclenche lorsqu’une touche du clavier est pressée. Nous verrons dans un autre tutoriel ce que sont les interruptions.

      On peut même améliorer notre petit programme en rétablissant l’état initial des couleurs lorsque nous quittons:

      PROGRAMME 06
      
      ADRESSE       ASM           MACHINE            ASM MONITOR
      49152         LDX #00       162, 0             C000 LDX #$00
      49154         STX 53280     141, 32, 208       C002 STX $D020
      49157         STX 53281     141, 33, 208       C005 STX $D021
      49160         INX           232                C008 INX
      49161         JSR 65508     32, 228, 255       C009 JSR $FFE4
      49164         CMP #81       201,81             C00C CMP #$51
      49166         BEQ 3         240, 3             C00E BEQ $C013
      49168         JMP 49152     76, 2, 192         C010 JMP $C002
      49171         LDX #254      162, 254           C013 LDX #$FE
      49173         STX $D020     141, 32, 208       C015 STX $D020
      49176         LDX #246      162, 246           C018 LDX #$F6
      49178         STX $D021     141, 33, 208       C01A STX $D021
      49181         RTS           96                 C01D RTS

      Apres l’instruction JMP 49152 nous remettons les registres 53280 et 53281 à leur valeur d’origine (respectivement 254 et 246).

       

      Un programme final

      Voici un petit programme qui va clôturer ce tutoriel en utilisant trois nouvelles instructions SEI (120), CLI (88) et NOP (234) et agir sur un registre du VIC le 53265 (17,208 ; $D011) pour n’afficher que l’overscan.

      Ce programme est fort similaire au précèdent. La différence est qu’il désactive une partie de VIC n’affichant plus que le bord d’écran et désactive les interruptions avec l’instruction SEI.

      Enfin, la lecture du registre 56321 (01,220 ; $DC01) du CIA-I nous permets de tester les touches RUN/STOP-SPACE (et d’autres mais pas toutes) du clavier pour sortir de la boucle.

      PROGRAMME 07
      
      ADRESSE  ASM           MACHINE        ASM MONITOR      COMMENTAIRES
      49152    LDA #0        169, 0         C000 LDA #$00    A = 0
      49154    STA 53265     141, 17, 208   C002 STA $D011   Desactive le fond
      49157    SEI           120            C005 SEI         Desactive les interruptions
      49158    LDX #00       162, 0         C006 LDX #$00    X = 0
      49160    STX 53280     142, 32, 208   C008 STX $D020   Couleur de fond noir
      49163    INX           232            C00B INX         Incemente X
      49164    NOP           234            C00C NOP         Rien
      49165    LDA 56321     173, 1, 220    C00D LDA $DC01   Lecture CIA-1 clavier
                                                             colonne 0
      49168    CMP #255      201, 255       C010 CMP #$FF    Si aucune touche est presse
                                                             tout les bits sont allumees
      49170    BNE 3         208, 3         C012 BNE $C017   Sinon on saute a 49175 en
                                                             evitant la reboucle
      49172    JMP 49163     76, 08, 192    C014 JMP $C008   Reboucle
      49175    CLI           88             C017 CLI         Reactive les interruptions
      49176    LDA  #27      169, 27        C018 LDA #$1B    Valeur orignial de $D011 en
                                                             A pour reactiver le VIC
      49178    STA 53265     141, 17, 208   C01A STA $D020   Reactive l’ecran de fond   
      49181    LDX #$254     162, 254       C01D LDX #$FE    X = 254 couleur de bord par
                                                             defaut
      49183    STX 53280     142, 32, 208   C01F STX $D020   Couleur de bord = X = 254
      49186    RTS           96             C012 RTS         Quitte

      Les instructions SEI et CLI inhibe et rétablit respectivement les interruptions. On désactive les interruptions pour permettre l’exécution du programme à pleine vitesse. Nous verrons en détail dans un prochain tutorial ce que sont les interruptions.

      Enfin pour quitter le programme nous n’utilisons pas la fonction du ROM pour ne pas perdre de temps et lisons directement dans le registre 56321 ($DC01) dont la valeur par défaut (255) change lorsque on appuie sur les touches ESC, SPACE, Q et d’autres (La lecture direct du clavier est un petit plus complexe mais dans notre cas cela suffit).

      Enfin l’instruction NOP est une instruction qui ne fait rien ! Mais ce n’est pas parce qu’une instruction ne fait rien qu’elle ne sert à rien : ici, on l’utilise pour perdre des cycles.

      Programme BASIC pour charger et exécuter cette routine :

      10   PRINT « CHARGEMENT »
      20   ME=49152             : REM ADRESSE DE NOTRE ROUTINE
      30   READ OP              : REM LECTURE DATA
      40   IF OP<0 THEN GOTO 80 : REM FIN DES DONNEES
      50   POKE ME,OP           : REM CHARGEMENT DU CODE MACHINE
      60   ME=ME+1              : REM CASE MEMOIRE SUIVANTE
      70   GOTO 30              : REM BOUCLE
      80   SYS 49152            : REM EXECUTION DE NOTRE ROUTINE
      100  DATA 169,0           : REM LDA #$00
      120  DATA 141,17,208      : REM STA $D011
      130 DATA 120              : REM SEI
      140  DATA 162,0           : REM LDX 0
      150  DATA 142,32,208      : REM STX $D020
      160 DATA 232              : REM INX
      170  DATA 234             : REM NOP
      180 DATA 173,1,220        : REM LDA $DC01
      190  DATA 201,255         : REM CMP #$FF
      200  DATA 208,3           : REM BNE 3
      210  DATA 76,08,192       : REM JMP 49160 (LINE 150 DATA)
      220  DATA 88              : REM CLI
      240  DATA 169,27          : REM LDA #$1B
      250  DATA 141,17,208      : REM STA $D011
      260  DATA 162,254         : REM LDX #254
      270  DATA 142,32,208      : REM STX $D020
      280  DATA 96              : REM RTS
      999  DATA -1              : REM FIN DES DONNEES

      Pour illustrer le tutorial j’ai fait une vidéo qui, je pense, facilitera peut-être la lecture un peu lourde comme n’importe quel tutorial sur l’assembleur :)

      La vidéo s’amuse avec le dernier programme à rajouter des NOP dans la boucle interne modifiant ainsi complètement les effets de l’affichage.

      Je vous encourage à faire de même et modifier ce programme en rajoutant des NOP ou des INX dans la boucle interne (Entre la ligne 150 et la ligne 210) et observer les changements produit !

      J’ai souvent lu que l’un des principale reproche que font les gens, voulant apprendre l’assembleur, était qu’ils se décourageaient très vite devant le flot d’information obscure et finalement rien de bien concret. Donc j’ai essayé, et sans m’attarder sur le binaire et l’Hexadécimale (pourtant indispensable), de faire découvrir l’assembleur du processeur MOS65xx et son langage machine avec des programmes simples qui produisent des effets à l’écran impossible à reproduire avec le BASIC.

      Ainsi se termine ce tutoriel en espérant qu’il vous a donné envie.

      Mais il est évident que nous n’allons pas nous amuser à écrire directement du langage machine avec du BASIC. Il nous faut un outil d’assemblage. On peut distinguer trois types d’assembleur par ordre d’ergonomie.

      Monitor: FINAL (BLACK BOX), SUPERMON.
      Assembleur: TURBO Assembleur.
      Cross-Assembleur : TMPX.

      Le prochain tutoriel présentera un Monitor, un programme pour assembler un programme en Mémoire. Ce sera beaucoup plus pratique que de passer par le BASIC (encore que…). Nous y verrons la notation hexadécimal, l’adressage indexe, la pile et quelques autres trucs sympas.

      Enfin la troisième partie présentera l’assembleur TMP sous C-64 et un cross-assembleur (TMPX) pour une ergonomie maximale.

      Tout en présentant ces outils nous en profiterons pour voir quelques points omis dans ce tutorial pourtant indispensables.

      Une référence en français sur l’assembleur du MOS6510 est disponible sur l’incontournable site de Idoc64. Un cours d’Assembleur du MOS65xx en anglais sur AtariArchives dans lequel vous trouverez la description complète des instructions.

       

      Pzawa

      Vous aimez Amiga France ? Alors aidez nous en partageant et en participant au forum. =)

    Affichage de 9 réponses de 1 à 9 (sur un total de 9)

    Partager sur vos réseaux sociaux préférés :
    Facebooktwitterredditpinterestlinkedintumblrmail

    • Auteur
      Réponses
    • #9095
      gibs
      • Level 9
      • Messages : 978

        Excellent !

        :heart: Team Apollo :heart:



        #9099
        Staff
        Jim Neray
        • Level 21
        • Messages : 6979

          Oui gros boulot de Pzawa. Un gros bravo à lui  :good:

          A500 - A500 Plus - A600 HD - A1200 - A2000 - A4000T - CD32 - C=64 - 1040STE - CPC6128
          Mon Amiga 500 Plus : A590, 2MB Chip, 2MB Fast, HD 1,2GB, Floppy ext.
          Mon Amiga 1200 : Blizzard 1220/4, 2MB Chip, 4MB Fast, HD 80GB, Overdrive CD

          - Micromiga.com - La boutique Amiga -
          #9126
          gibs
          • Level 9
          • Messages : 978

            @jim je me suis permis de l’épingler.

            Je pense qu’il y a une erreur dans la formule :

            B2 = INT(MEM/256)
            B1 = MEM – B2

            qu’il faudrait remplacer par :

            B2 = INT(MEM/256)
            B1 = MEM – B2 * 256

            Ainsi pour l’adresse 53280

            B2 = INT(53280/256) = 208
            B1 = 53280 – 208 * 256 = 32

            :heart: Team Apollo :heart:

            #9137
            Staff
            Jim Neray
            • Level 21
            • Messages : 6979

              Oh oui je pense que tu as raison je corrige ca.

              Tu as eu raison de l’épingler provisoirement (en attendant le prochain). Sinon pour info le dernier tuto est dispo dans la colone de droite sur toutes les pages du site, sur la page d’accueil du site (avec la dernière interview, le dernier dossier, …).

              Et le menu en haut du site a aussi été mis à jour il suffit de cliquer sur rubriques puis tutoriels. Peux pas faire mieux là  :unsure:   :lol:

              A500 - A500 Plus - A600 HD - A1200 - A2000 - A4000T - CD32 - C=64 - 1040STE - CPC6128
              Mon Amiga 500 Plus : A590, 2MB Chip, 2MB Fast, HD 1,2GB, Floppy ext.
              Mon Amiga 1200 : Blizzard 1220/4, 2MB Chip, 4MB Fast, HD 80GB, Overdrive CD

              - Micromiga.com - La boutique Amiga -
              #9335
              PZAWA
              • Level 6
              • Messages : 316

                Merci :-)

                Oui effectivement je ne l’avais pas vu cette erreur, Merci gibs !

                 

                #9356
                Ben
                • Level 0 - Newbie
                • Messages : 5

                  Merci, vraiment sympa ce tuto.  :good:

                  J’espère qu’il aura une suite car il est vraiment intéressant et plutôt simple a comprendre. Donc Bravo et encore merci car le sujet est rarement traité sur le net (en français).

                  Ben

                  #9408
                  Tim
                  • Level 4
                  • Messages : 112

                    Salut les gars !

                     

                    Punaise ça devient sérieux ce site !! c’est pour les grosses pointures maintenant ;)

                    Bravo, c’est sûr que c’est du boulot ! Moi je suis nul en langage machine et j’y comprends que dalle !!! héhéhé mais bravo les gars ! :-)

                    Amiga 1200 / AmigaOS 3.1.4.1 KS 46.143 / ACA 1233n / HxC
                    C64 Reloaded / 1541 Ultimate 2+ / JiffyDos / SIDFX
                    ATARI 1040STE = Au placard, => MiSTer FPGA Powa !
                    Amstrad CPC6128 = Au placard => MiSTer FPGA Powa !

                    #9474
                    PZAWA
                    • Level 6
                    • Messages : 316

                      @Tim Merci :-) ce premier tuto est justement fait pour ceux qui ne comprennent rien du tout au langage machine et à l’ASM ;-)

                      @Ben: N’hésite pas si tu as des questions ou des suggestions.

                      Et rendons à César … l’article original est beaucoup plus laid et lourd: Jim a fait un super boulot de mise en page :good:

                      #9477
                      kyl83
                      • Level 3
                      • Messages : 62

                        vraiment excellent…suis comme Tim et j’en reste bouche bée …çà donne vraiment envie de s’y coller..encore merci pour ce tuto  :good:   :good:

                      Partager sur vos réseaux sociaux préférés :
                      Facebooktwitterredditpinterestlinkedintumblrmail
                      Affichage de 9 réponses de 1 à 9 (sur un total de 9)
                      • Vous devez être connecté pour répondre à ce sujet.