Modes d'adressage
Le 6502 dispose de 13 modes d'adressage qui, malgré l'apparente simplicité du processeur, offrent une grande flexibilité pour accéder aux données en mémoire. Ces modes d'adressage sont l'une des forces du 6502 par rapport a ses concurrents 8 bits.
Les modes d'adressage du 6502 se repartissent en plusieurs catégories :
Modes sans opérande mémoire :
- Implicite (Implied) : l'instruction agit sur un registre ou un état interne sans opérande explicite
- Accumulateur (Accumulator) : l'instruction agit sur A
Modes avec opérande immédiat :
- Immédiat (Immediate) : la valeur est codée dans l'instruction elle-même
Modes avec accès a la page zéro ($0000-$00FF) :
- Page zéro (Zero Page) : adresse sur 8 bits
- Page zéro indexe par X (Zero Page,X)
- Page zéro indexe par Y (Zero Page,Y)
Modes avec accès absolu ($0000-$FFFF) :
- Absolu (Absolute) : adresse sur 16 bits
- Absolu indexe par X (Absolute,X)
- Absolu indexe par Y (Absolute,Y)
Modes indirects (via un pointeur en page zéro) :
- Indirect (Indirect) : utilise uniquement par JMP
- Indirect indexe par X (Indexed Indirect, (Ind,X))
- Indirect indexe par Y (Indirect Indexed, (Ind),Y)
Modes de branchement :
- Relatif (Relative) : déplacement signe de 8 bits par rapport au PC, utilise par les branchements conditionnels
L'adresse effective (Effective Address, EA) est l'adresse finale en mémoire a laquelle le processeur accède pour lire ou écrire une donnée. Le mode d'adressage détermine comment cette adresse est calculée.
Le choix du mode d'adressage affecte :
- La taille de l'instruction (1, 2 ou 3 octets)
- Le nombre de cycles d'exécution
- L'opcode (chaque combinaison instruction + mode d'adressage a un opcode unique)
Codage de l'opcode :
- La plupart des instructions du 6502 suivent un schéma de codage ou les 3 bits du milieu de l'opcode (bits 2-4) déterminant le mode d'adressage :
| Bits 4-2 | Mode d'adressage | |
|---|---|---|
| 000 | (Indexed Indirect,X) | ($ZP,X) |
| 001 | Zéro Page | $ZP |
| 010 | Immédiate | #$nn |
| 011 | Absolute | $nnnn |
| 100 | (Indirect Indexed),Y | ($ZP),Y |
| 101 | Zero Page,X | $ZP,X |
| 110 | Absolute,Y | $nnnn,Y |
| 111 | Absolute,X | $nnnn,X |
Ce schéma s'applique aux instructions du groupe "a" (ORA, AND, EOR, ADC, STA, LDA, CMP, SBC). Les autres groupes suivent des schémas différents avec un sous-ensemble de ces modes.
Adressage implicite (Implied)
L'instruction n'a pas d'opérande explicite. Elle agit sur un registre ou un état interne spécifique du processeur. L'opérande est implicitement déterminé par l'opcode.
Taille de l'instruction : 1 octet (opcode seul)
Nombre de cycles : 2
Instructions utilisant ce mode :
Transferts de registres :
| Instruction | Opération |
|---|---|
| TAX ($AA) | A → X |
| TAY ($A8) | A → Y |
| TXA ($8A) | X → A |
| TYA ($98) | Y → A |
| TSX ($BA) | SP → X |
| TXS ($9A) | X → SP |
Incrémentation/décrémentation de registres :
| Instruction | Opération |
|---|---|
| INX ($E8) | X := X + 1 |
| INY ($C8) | Y := Y + 1 |
| DEX ($CA) | X := X - 1 |
| DEY ($88) | Y := Y - 1 |
Opérations sur la pile :
| Instruction | Opération | Nombre de cycles d'horloges |
|---|---|---|
| PHA ($48) | Empiler A | 3 cycles |
| PLA ($68) | dépiler A | 4 cycles |
| PHP ($08) | empiler P | 3 cycles |
| PLP ($28) | dépiler P | 4 cycles |
Contrôle des drapeaux :
| Instruction | Opération |
|---|---|
| CLC ($18) | C := 0 |
| SEC ($38) | C := 1 |
| CLD ($D8) | D := 0 |
| SED ($F8) | D := 1 |
| CLI ($58) | I := 0 |
| SEI ($78) | I := 1 |
| CLV ($B8) | V := 0 |
Contrôle du flux :
| Instruction | Opération | Nombre de cycles d'horloges |
|---|---|---|
| RTS ($60) | Retour de sous-routine | 6 cycles |
| RTI ($40) | Retour d'interruption | 6 cycles |
| BRK ($00) | Interruption logicielle | 7 cycles |
| NOP ($EA) | Pas d'opération | 2 cycles |
Exemples :
Remarques :
- Les instructions de pile (PHA/PLA/PHP/PLP) prennent 3 ou 4 cycles au lieu de 2, car elles effectuent un accès mémoire a la pile
- Les instructions de retour (RTS/RTI) prennent 6 cycles car elles effectuent plusieurs accès mémoire a la pile
- BRK prend 7 cycles car il empile PC et P puis lit le vecteur d'interruption
- TXS est la seule instruction de transfert qui ne modifie PAS les drapeaux
Adressage par accumulateur (Accumulator)
L'instruction agit directement sur le registre A (accumulateur). Ce mode est utilise uniquement par les instructions de décalage et de rotation. Il est parfois considéré comme un cas particulier de l'adressage implicite.
Notation assembleur : A (ou parfois rien après l'instruction)
Taille de l'instruction : 1 octet (opcode seul)
Nombre de cycles : 2
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| ASL A ($0A) | décalage arithmétique a gauche de A C ← [76543210] ← 0 |
| LSR A ($4A) | décalage logique a droite de A 0 → [76543210] → C |
| ROL A ($2A) | rotation a gauche via C C ← [76543210] ← C |
| ROR A ($6A) | Rotation a droite via C C → [76543210] → C |
Schéma des opérations de décalage et rotation :
ASL A :
LSR A :
ROL A :
ROR A :
Exemples :
- ; Multiplier A par 2
- ASL A ; A = A * 2 (C recoit le bit de poids fort)
-
- ; Diviser A par 2 (non signe)
- LSR A ; A = A / 2 (C recoit le bit de poids faible)
-
- ; Multiplier A par 4
- ASL A ; A = A * 2
- ASL A ; A = A * 4
-
- ; Rotation 16 bits a gauche (poids fort dans $81, poids faible dans $80)
- ASL $80 ; decaler poids faible, bit 7 -> C
- ROL $81 ; decaler poids fort, C -> bit 0
-
- ; Tester le bit 7 de A
- ASL A ; bit 7 -> C
- BCS BitEstUn ; brancher si C = 1
Remarques :
- Ces 4 instructions existent aussi en mode page zéro, page zéro indexe par X, absolu et absolu indexe par X, ou elles opèrent directement sur un emplacement mémoire
- En mode accumulateur, l'opération est plus rapide (2 cycles) que sur un emplacement mémoire (5-7 cycles)
- La notation "ASL A" est la plus courante; certains assembleurs acceptent "ASL" sans opérande
Adressage immédiat (Immediate)
L'opérande est une constante de 8 bits codée directement dans l'instruction, immédiatement après l'opcode. La valeur n'est pas une adresse mémoire mais la donnée elle-même.
Notation assembleur : #valeur (le préfixe # indique l'immédiat)
Taille de l'instruction : 2 octets (opcode + valeur 8 bits)
Nombre de cycles : 2
Formule : opérande = octet suivant l'opcode
Instructions utilisant ce mode :
Chargement :
| Instruction | Opération |
|---|---|
| LDA #nn ($A9) | A := nn |
| LDX #nn ($A2) | X := nn |
| LDY #nn ($A0) | Y := nn |
Arithmétique :
| Instruction | Opération |
|---|---|
| ADC #nn ($69) | A := A + nn + C |
| SBC #nn ($E9) | A := A - nn - NOT(C) |
Logique :
| Instruction | Opération |
|---|---|
| AND #nn ($29) | A := A AND nn |
| ORA #nn ($09) | A := A OR nn |
| EOR #nn ($49) | A := A XOR nn |
Comparaison :
| Instruction | Opération |
|---|---|
| CMP #nn ($C9) | Comparer A avec nn (positionne N, Z, C) |
| CPX #nn ($E0) | Comparer X avec nn |
| CPY #nn ($C0) | Comparer Y avec nn |
Exemples :
- LDA #$42 ; A = $42 (66 en decimal)
- LDX #0 ; X = 0
- LDY #255 ; Y = $FF
-
- AND #$0F ; masquer les 4 bits de poids fort
- ORA #$80 ; forcer le bit 7 a 1
- EOR #$FF ; inverser tous les bits (complement a 1)
-
- CMP #'A' ; comparer A avec le code ASCII de 'A'
- BEQ EstUnA ; brancher si A = 'A'
-
- ADC #$01 ; A = A + 1 + C
- SBC #$10 ; A = A - $10 - NOT(C)
Formats numériques acceptes par les assembleurs :
- #$FF ; hexadecimal (prefixe $)
- #%10110011 ; binaire (prefixe %)
- #255 ; decimal (pas de prefixe)
- #'A' ; caractere ASCII
- #<$1234 ; poids faible ($34) de l'adresse $1234
- #>$1234 ; poids fort ($12) de l'adresse $1234
Remarques :
- L'adressage immédiat ne peut être utilise qu'en lecture (source). Il est impossible d'entreposer avec ce mode (pas de STA #nn).
- La valeur est toujours un octet (8 bits, 0 a 255 ou -128 a +127 en signe)
- Les opérateurs < et > sont fournis par l'assembleur pour extraire les octets d'une adresse 16 bits
- C'est le mode le plus rapide avec l'implicite (2 cycles)
Adressage page zéro (Zero Page)
L'opérande est un octet en page zéro ($0000-$00FF). Seul l'octet de poids faible de l'adresse est spécifié dans l'instruction; l'octet de poids fort est implicitement $00.
Notation assembleur : $nn (adresse 8 bits sans préfixe #)
Taille de l'instruction : 2 octets (opcode + adresse 8 bits)
Nombre de cycles : 3 (lecture), 3 (écriture), 5 (lecture-modification-écriture : ASL, LSR, ROL, ROR, INC, DEC)
Formule : EA = $00nn
Instructions utilisant ce mode (exemples) :
Chargement/entreposage :
| Instruction | Opération |
|---|---|
| LDA $nn ($A5) | A := [$00nn] |
| LDX $nn ($A6) | X := [$00nn] |
| LDY $nn ($A4) | Y := [$00nn] |
| STA $nn ($85) | [$00nn] := A |
| STX $nn ($86) | [$00nn] := X |
| STY $nn ($84) | [$00nn] := Y |
Arithmétique :
| Instruction | Opération |
|---|---|
| ADC $nn ($65) | A := A + [$00nn] + C |
| SBC $nn ($E5) | A := A - [$00nn] - NOT(C) |
Logique :
| Instruction | Opération |
|---|---|
| AND $nn ($25) | A := A AND [$00nn] |
| ORA $nn ($05) | A := A OR [$00nn] |
| EOR $nn ($45) | A := A XOR [$00nn] |
Comparaison :
| Instruction | Opération |
|---|---|
| CMP $nn ($C5) | Comparer A avec [$00nn] |
| CPX $nn ($E4) | Comparer X avec [$00nn] |
| CPY $nn ($C4) | Comparer Y avec [$00nn] |
Modification mémoire :
| Instruction | Opération |
|---|---|
| INC $nn ($E6) | [$00nn] := [$00nn] + 1 |
| DEC $nn ($C6) | [$00nn] := [$00nn] - 1 |
| ASL $nn ($06) | Décalage a gauche de [$00nn] |
| LSR $nn ($46) | Décalage a droite de [$00nn] |
| ROL $nn ($26) | Rotation a gauche de [$00nn] |
| ROR $nn ($66) | Rotation a droite de [$00nn] |
Test de bits :
| Instruction | Opération |
|---|---|
| BIT $nn ($24) | teste les bits de [$00nn] Z := A AND [$00nn] = 0 N := bit 7 de [$00nn] V := bit 6 de [$00nn] |
Exemples :
- LDA $80 ; A = contenu de l'adresse $0080
- STA $90 ; stocker A a l'adresse $0090
- INC $FF ; incrementer le contenu de $00FF
- BIT $20 ; tester les bits a l'adresse $0020
-
- ; Utiliser la page zero comme pseudo-registres
- LDA $80 ; charger le "pseudo-registre" a $80
- ADC $81 ; additionner avec le "pseudo-registre" a $81
- STA $82 ; stocker le resultat dans $82
Avantages par rapport a l'adressage absolu :
- 1 octet de moins par instruction (2 au lieu de 3)
- 1 cycle de moins (3 au lieu de 4 pour LDA/STA)
- Économies cumulées significatives dans le code
Remarques :
- La page zéro est limitée a 256 octets, c'est une ressource précieuse qui doit être gérée avec soin
- Sur la plupart des systèmes, une partie de la page zéro est réservée par le système d'exploitation ou le moniteur
- L'adressage page zéro est le mode le plus utilise pour les variables fréquemment accédées
- L'adressage "zero page" est équivalent au "direct page" du 6809, mais la page du 6809 peut être repositionnée dans l'espace d'adressage, tandis que la page zéro du 6502 est toujours fixe a $0000-$00FF
Adressage page zéro indexe par X (Zero Page,X)
L'adresse effective est calculée en ajoutant le contenu du registre X a l'adresse de base en page zéro. Le résultat est tronque a 8 bits (modulo 256), ce qui signifie que l'adresse reste TOUJOURS en page zéro, même en cas de débordement.
Notation assembleur : $nn,X
Taille de l'instruction : 2 octets (opcode + adresse 8 bits)
Nombre de cycles : 4 (lecture/écriture), 6 (lecture-modification-écriture)
Formule : EA = ($nn + X) AND $FF
(le résultat est toujours en page zéro)
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDA $nn,X ($B5) | A := [($nn + X) AND $FF] |
| STA $nn,X ($95) | [($nn + X) AND $FF] := A |
| LDY $nn,X ($B4) | Y := [($nn + X) AND $FF] |
| STY $nn,X ($94) | [($nn + X) AND $FF] := Y |
| ADC $nn,X ($75) | A := A + [($nn + X) AND $FF] + C |
| SBC $nn,X ($F5) | A := A - [($nn + X) AND $FF] - NOT(C) |
| AND $nn,X ($35) | A := A AND [($nn + X) AND $FF] |
| ORA $nn,X ($15) | A := A OR [($nn + X) AND $FF] |
| EOR $nn,X ($55) | A := A XOR [($nn + X) AND $FF] |
| CMP $nn,X ($D5) | Comparer A avec [($nn + X) AND $FF] |
| INC $nn,X ($F6) | [($nn + X) AND $FF] += 1 |
| DEC $nn,X ($D6) | [($nn + X) AND $FF] -= 1 |
| ASL $nn,X ($16) | Décalage a gauche de [($nn + X) AND $FF] |
| LSR $nn,X ($56) | Décalage a droite de [($nn + X) AND $FF] |
| ROL $nn,X ($36) | Rotation a gauche de [($nn + X) AND $FF] |
| ROR $nn,X ($76) | Rotation a droite de [($nn + X) AND $FF] |
Exemples :
- ; Tableau de 8 octets a partir de $80 en page zero
- LDX #$03 ; index = 3
- LDA $80,X ; A = contenu de $0083 (= $80 + 3)
- STA $88,X ; stocker A a $008B (= $88 + 3)
-
- ; Boucle sur un tableau en page zero
- LDX #$07 ; X = 7 (dernier element)
- Boucle:
- LDA $80,X ; lire l'element X du tableau
- STA $90,X ; copier dans un autre tableau
- DEX ; X = X - 1
- BPL Boucle ; boucler tant que X >= 0
-
- ; Debordement silencieux (wrapping)
- LDX #$80 ; X = $80 (128)
- LDA $C0,X ; EA = ($C0 + $80) AND $FF = $40
- ; lit a l'adresse $0040, PAS $0140
Remarques :
- Le "wrapping" a $FF est un comportement spécifique au 6502 pouvant surprendre. L'adresse ne déborde JAMAIS en page 1.
- Ce mode prend un cycle de plus que le mode page zéro simple car le processeur doit additionner X a l'adresse de base
- LDX $nn,X n'existe pas (ce serait redondant); a la place, utiliser LDX $nn,Y
Adressage page zéro indexe par Y (Zero Page,Y)
Similaire au mode Zero Page,X mais utilise le registre Y comme index. Ce mode n'est disponible que pour les instructions LDX et STX. C'est un mode spécialisé pour les cas ou X est déjà utilisé comme index et ou l'on veut indexer LDX/STX par Y.
Notation assembleur : $nn,Y
Taille de l'instruction : 2 octets (opcode + adresse 8 bits)
Nombre de cycles : 4
Formule : EA = ($nn + Y) AND $FF
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDX $nn,Y ($B6) | X := [($nn + Y) AND $FF] |
| STX $nn,Y ($96) | [($nn + Y) AND $FF] := X |
Exemples :
Remarques :
- Seules LDX et STX utilisent ce mode. Pour les autres instructions, utiliser Zero Page,X
- Le wrapping a $FF s'applique comme pour Zero Page,X
- Ce mode existe car LDX ne peut pas utiliser X comme index (ce serait un conflit : on chargerait X en utilisant X pour calculer l'adresse)
Adressage absolu (Absolute)
L'opérande se trouve a une adresse 16 bits quelconque dans l'espace d'adressage de 64 Ko. L'adresse complète (2 octets) est codée dans l'instruction en format little-endian (poids faible d'abord, poids fort ensuite).
Notation assembleur : $nnnn (adresse 16 bits)
Taille de l'instruction : 3 octets (opcode + adresse 16 bits)
Nombre de cycles : 4 (lecture/écriture), 6 (lecture-modification-écriture), 3 (JMP), 6 (JSR)
Formule : EA = $hhll (ou ll = poids faible, hh = poids fort)
Codage en mémoire :
| Adresse | Description |
|---|---|
| Adresse n | opcode |
| Adresse n+1 | Poids faible de l'adresse (ll) |
| Adresse n+2 | Poids fort de l'adresse (hh) |
Instructions utilisant ce mode (exemples) :
Chargement/entreposage :
| Instruction | Opération |
|---|---|
| LDA $nnnn ($AD) | A := [$nnnn] |
| LDX $nnnn ($AE) | X := [$nnnn] |
| LDY $nnnn ($AC) | Y := [$nnnn] |
| STA $nnnn ($8D) | [$nnnn] := A |
| STX $nnnn ($8E) | [$nnnn] := X |
| STY $nnnn ($8C) | [$nnnn] := Y |
Arithmétique et logique :
| Instruction | Opération |
|---|---|
| ADC $nnnn ($6D) | A := A + [$nnnn] + C |
| SBC $nnnn ($ED) | A := A - [$nnnn] - NOT(C) |
| AND $nnnn ($2D) | A := A AND [$nnnn] |
| ORA $nnnn ($0D) | A := A OR [$nnnn] |
| EOR $nnnn ($4D) | A := A XOR [$nnnn] |
Comparaison :
| Instruction | Opération |
|---|---|
| CMP $nnnn ($CD) | Comparer A avec [$nnnn] |
| CPX $nnnn ($EC) | Comparer X avec [$nnnn] |
| CPY $nnnn ($CC) | Comparer Y avec [$nnnn] |
Modification mémoire :
| Instruction | Opération |
|---|---|
| INC $nnnn ($EE) | [$nnnn] := [$nnnn] + 1 |
| DEC $nnnn ($CE) | [$nnnn] := [$nnnn] - 1 |
| ASL $nnnn ($0E) | Décalage a gauche de [$nnnn] |
| LSR $nnnn ($4E) | Décalage a droite de [$nnnn] |
| ROL $nnnn ($2E) | Rotation a gauche de [$nnnn] |
| ROR $nnnn ($6E) | Rotation a droite de [$nnnn] |
Test de bits :
| Instruction | Opération |
|---|---|
| BIT $nnnn ($2C) | Teste les bits de [$nnnn] |
Contrôle du flux :
| Instruction | Opération |
|---|---|
| JMP $nnnn ($4C) | PC := $nnnn (saut absolu, 3 cycles) |
| JSR $nnnn ($20) | Empiler PC-1, PC := $nnnn (6 cycles) |
Exemples :
- LDA $1234 ; A = contenu de l'adresse $1234
- STA $D020 ; stocker A dans le registre de couleur
- ; de bordure du C64 (VIC-II)
- INC $0400 ; incrementer l'octet a l'adresse $0400
- JMP $C000 ; sauter a l'adresse $C000
- JSR $FFD2 ; appeler la routine CHROUT du Kernal C64
-
- ; Codage de "LDA $1234" en memoire :
- ; $AD $34 $12
- ; (opcode, poids faible, poids fort = little-endian)
Remarques :
- L'adressage absolu est le mode le plus général, il permet d'accéder a toute la mémoire de 64 Ko
- Il est 1 octet plus long et 1 cycle plus lent que le mode page zéro équivalent
- JMP et JSR n'existent qu'en mode absolu (et indirect pour JMP)
- L'assembleur choisit automatiquement entre le mode page zéro et le mode absolu en fonction de la valeur de l'adresse : si l'adresse est dans $00-$FF, le mode page zéro est sélectionné pour optimiser la taille et la vitesse
Adressage absolu indexe par X (Absolute,X)
L'adresse effective est calculée en ajoutant le contenu du registre X a une adresse de base de 16 bits. Contrairement au mode Zero Page,X, le résultat n'est pas tronque : l'addition se fait sur 16 bits.
Notation assembleur : $nnnn,X
Taille de l'instruction : 3 octets (opcode + adresse 16 bits)
Nombre de cycles : 4* (lecture), 5 (écriture), 7 (lecture-modification-écriture)
* : +1 cycle si le franchissement de page se produit (page boundary crossing)
Formule : EA = $nnnn + X (addition 16 bits)
Franchissement de page (page boundary crossing) :
Une "page" dans le contexte du 6502 est un bloc de 256 octets aligne ($xx00 a $xxFF). Un franchissement de page se produit quand l'ajout de X fait passer l'adresse dans la page suivante (l'octet de poids fort de l'adresse change).
Exemple sans franchissement :
Base = $10F0, X = $05 → EA = $10F5 (même page $10xx) → pas de cycle supplémentaire
Exemple avec franchissement : Base = $10F0, X = $20 → EA = $1110 (page $11xx, différente) → +1 cycle supplémentaire
Le cycle supplémentaire est du au fait que le 6502 calcule d'abord l'adresse avec l'octet de poids faible seul. Si une retenue se produit vers l'octet de poids fort, un cycle supplémentaire est nécessaire pour corriger l'adresse.
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDA $nnnn,X ($BD) | A := [$nnnn + X] |
| STA $nnnn,X ($9D) | [$nnnn + X] := A (toujours 5 cycles) |
| LDY $nnnn,X ($BC) | Y := [$nnnn + X] |
| ADC $nnnn,X ($7D) | A := A + [$nnnn + X] + C |
| SBC $nnnn,X ($FD) | A := A - [$nnnn + X] - NOT(C) |
| AND $nnnn,X ($3D) | A := A AND [$nnnn + X] |
| ORA $nnnn,X ($1D) | A := A OR [$nnnn + X] |
| EOR $nnnn,X ($5D) | A := A XOR [$nnnn + X] |
| CMP $nnnn,X ($DD) | Comparer A avec [$nnnn + X] |
| INC $nnnn,X ($FE) | [$nnnn + X] += 1 (toujours 7 cycles) |
| DEC $nnnn,X ($DE) | [$nnnn + X] -= 1 |
| ASL $nnnn,X ($1E) | Décalage a gauche de [$nnnn + X] |
| LSR $nnnn,X ($5E) | Décalage a droite de [$nnnn + X] |
| ROL $nnnn,X ($3E) | Rotation a gauche de [$nnnn + X] |
| ROR $nnnn,X ($7E) | Rotation a droite de [$nnnn + X] |
Exemples :
- ; Parcourir un tableau de 256 octets a partir de $2000
- LDX #$00
- Boucle:
- LDA $2000,X ; lire l'octet X du tableau
- STA $0400,X ; copier vers l'ecran (C64)
- INX
- BNE Boucle ; boucler tant que X != 0
-
- ; Acces a un tableau de plus de 256 elements avec index
- ; sur 16 bits (poids faible dans X, poids fort ajoute a l'adresse)
- ; Lire l'element a l'offset $0103 du tableau :
- LDA Table+$01,X ; ou X = $03, Table est la base
- ; EA = Table + $0100 + $03 = Table + $0103
Remarques :
- Les instructions d'écriture (STA) et de lecture-modification-écriture (INC, DEC, ASL,...) prennent toujours le cycle supplémentaire, même sans franchissement de page
- LDX $nnnn,X n'existe pas (même raison que pour Zero Page,X)
- Ce mode est très utilise pour parcourir des tableaux et des tampons d'écran
Adressage absolu indexe par Y (Absolute,Y)
Identique a Absolute,X mais utilise le registre Y comme index. Ce mode est disponible pour un sous-ensemble d'instructions plus restreint que Absolute,X.
Notation assembleur : $nnnn,Y
Taille de l'instruction : 3 octets (opcode + adresse 16 bits)
Nombre de cycles : 4* (lecture), 5 (ecriture)
* : +1 cycle si franchissement de page
Formule : EA = $nnnn + Y (addition 16 bits)
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDA $nnnn,Y ($B9) | A := [$nnnn + Y] |
| STA $nnnn,Y ($99) | [$nnnn + Y] := A (toujours 5 cycles) |
| LDX $nnnn,Y ($BE) | X := [$nnnn + Y] |
| ADC $nnnn,Y ($79) | A := A + [$nnnn + Y] + C |
| SBC $nnnn,Y ($F9) | A := A - [$nnnn + Y] - NOT(C) |
| AND $nnnn,Y ($39) | A := A AND [$nnnn + Y] |
| ORA $nnnn,Y ($19) | A := A OR [$nnnn + Y] |
| EOR $nnnn,Y ($59) | A := A XOR [$nnnn + Y] |
| CMP $nnnn,Y ($D9) | comparer A avec [$nnnn + Y] |
Exemples :
Remarques :
- Ce mode n'est PAS disponible pour les instructions de modification mémoire (INC, DEC, ASL, LSR, ROL, ROR)
- LDY $nnnn,Y n'existe pas (conflit d'usage)
- Le franchissement de page fonctionne de la même manière que pour Absolute,X
- Ce mode est utile quand X est déjà occupe comme compteur ou index
Adressage indirect (Indirect)
L'adresse effective est lue depuis un emplacement mémoire de 16 bits. Le processeur lit d'abord un pointeur (2 octets) a l'adresse spécifiée, puis utilise cette valeur comme adresse effective. Ce mode n'est utilise que par l'instruction JMP.
Notation assembleur : ($nnnn)
Taille de l'instruction : 3 octets (opcode + adresse 16 bits)
Nombre de cycles : 5
Formule : EA = [[$nnnn+1] << 8 | [$nnnn]]
(lire un pointeur 16 bits little-endian a l'adresse $nnnn)
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| JMP ($nnnn) ($6C) | PC := [[$nnnn+1]:[$nnnn]] |
Exemples :
- ; Saut indirect via un vecteur en memoire
- JMP ($FFFC) ; sauter a l'adresse contenue dans le
- ; vecteur de RESET ($FFFC-$FFFD)
-
- ; Table de saut
- ; En memoire :
- ; $2000 : $00 $C0 -> pointe vers $C000
- ; $2002 : $00 $D0 -> pointe vers $D000
- JMP ($2000) ; PC := $C000
-
- ; Vecteur de saut modifiable
- LDA #<Routine ; poids faible de l'adresse de Routine
- STA VecteurJmp
- LDA #>Routine ; poids fort de l'adresse de Routine
- STA VecteurJmp+1
- JMP (VecteurJmp) ; sauter a Routine
BUG IMPORTANT du 6502 NMOS (JMP indirect sur limite de page) :
Si l'adresse du pointeur se trouve en fin de page (octet de poids faible = $FF), le 6502 NMOS ne franchit PAS la page pour lire l'octet de poids fort du pointeur. Au lieu de lire l'octet suivant a la page suivante, il lit l'octet au début de la MEME page.
Exemple du bug :
- $30FF : $80 (poids faible du pointeur)
- $3100 : $40 (poids fort attendu - IGNORE par le 6502 !)
- $3000 : $50 (lu a la place par le 6502 NMOS)
- JMP ($30FF) :
- Attendu : EA = $4080 (lit $30FF et $3100)
- Réel : EA = $5080 (lit $30FF et $3000 !)
Ce bug affecte TOUS les 6502 NMOS (y compris le 6510 du C64 et le 2A03 de la NES). Il est corrige sur le 65C02 (CMOS).
Contournement :
- Ne jamais placer un vecteur JMP indirect a une adresse dont l'octet de poids faible est $FF
- Aligner les tables de vecteurs sur des limites de pages
Remarques :
- C'est le seul mode d'adressage indirect "pur" du 6502
- Il n'est disponible que pour JMP (pas pour JSR ni pour les accès aux données)
- Pour un JSR indirect, la technique courante est :
Adressage indirect indexe par X (Indexed Indirect, (Ind,X))
Aussi appelé "Indexed Indirect" ou "Pre-Indexed Indirect". Le registre X est ajoute a une adresse en page zéro, et le résultat (tronque a 8 bits) pointe vers un emplacement en page zéro contenant l'adresse effective 16 bits (le pointeur).
Notation assembleur : ($nn,X)
Taille de l'instruction : 2 octets (opcode + adresse page zéro)
Nombre de cycles : 6
Formule :
|
pointeur = ($nn + X) AND $FF EA = [[$0000 + pointeur + 1] << 8 | [$0000 + pointeur]] |
Étapes détaillées :
- 1. Ajouter X a l'adresse de page zéro : ptr = ($nn + X) AND $FF
- 2. Lire l'octet de poids faible a $00:ptr
- 3. Lire l'octet de poids fort a $00:(ptr+1) AND $FF
- 4. Combiner pour former l'adresse effective 16 bits
- 5. Accéder a la donnée a cette adresse
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDA ($nn,X) ($A1) | A := [[($nn + X) AND $FF]] |
| STA ($nn,X) ($81) | [[($nn + X) AND $FF]] := A |
| ADC ($nn,X) ($61) | A := A + [[($nn + X) AND $FF]] + C |
| SBC ($nn,X) ($E1) | A := A - [[($nn + X) AND $FF]] - NOT(C) |
| AND ($nn,X) ($21) | A := A AND [[($nn + X) AND $FF]] |
| ORA ($nn,X) ($01) | A := A OR [[($nn + X) AND $FF]] |
| EOR ($nn,X) ($41) | A := A XOR [[($nn + X) AND $FF]] |
| CMP ($nn,X) ($C1) | Comparer A avec [[($nn + X) AND $FF]] |
Exemples :
- ; Table de pointeurs en page zero
- ; $80-$81 : pointeur 0 = $2000
- ; $82-$83 : pointeur 1 = $3000
- ; $84-$85 : pointeur 2 = $4000
- LDX #$02 ; index = 1 (2 octets par pointeur)
- LDA ($80,X) ; ptr = ($80+2) AND $FF = $82
- ; lit le pointeur a $82-$83 = $3000
- ; A = contenu de $3000
-
- ; Avec X = 0, equivalent a de l'indirect simple
- LDX #$00
- LDA ($80,X) ; ptr = $80, lit pointeur a $80-$81
- ; A = contenu de l'adresse pointee par $80-$81
-
- ; Parcourir une table de pointeurs
- LDX #$00
- Boucle:
- LDA ($80,X) ; lire la donnee pointee par le pointeur X/2
- ; ... traitement ...
- INX
- INX ; X += 2 (avancer au pointeur suivant)
- CPX #$06 ; 3 pointeurs * 2 octets = 6
- BNE Boucle
Schéma mémoire (exemple avec $80,X ou X=$02) :
Page zéro :
- $80 : $00 $20 ; pointeur 0 -> $2000
- $82 : $00 $30 ; pointeur 1 -> $3000 <- selectionne (X=2)
- $84 : $00 $40 ; pointeur 2 -> $4000
Mémoire :
- $3000 : $42 ; <- donnee lue par LDA ($80,X)
Remarques :
- Ce mode est relativement lent (6 cycles) car il nécessite plusieurs accès mémoire successifs
- Le wrapping s'applique : si ptr = $FF, le poids faible est lu a $FF et le poids fort a $00 (pas a $0100)
- Ce mode est principalement utile pour les tables de pointeurs ou le choix du pointeur dépend d'un index
- En pratique, ce mode est moins utilise que (Ind),Y car il est plus coûteux et moins flexible
Adressage indirect indexe par Y (Indirect Indexed, (Ind),Y)
Aussi appelé "Indirect Indexed" ou "Post-Indexed Indirect". C'est l'un des modes d'adressage les plus importants du 6502. Un pointeur 16 bits est d'abord lu depuis la page zero, puis le registre Y est ajoute a cette adresse pour former l'adresse effective.
Notation assembleur : ($nn),Y
Taille de l'instruction : 2 octets (opcode + adresse page zero)
Nombre de cycles : 5* (lecture), 6 (écriture)
* : +1 cycle si franchissement de page
Formule :
|
base = [[$0000 + $nn + 1] << 8 | [$0000 + $nn]] EA = base + Y (addition 16 bits) |
Étapes détaillées :
- 1. Lire l'octet de poids faible du pointeur a $00:$nn
- 2. Lire l'octet de poids fort du pointeur a $00:($nn+1) AND $FF
- 3. Combiner pour former l'adresse de base 16 bits
- 4. Ajouter Y pour obtenir l'adresse effective
- 5. Accéder a la donnée a cette adresse
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| LDA ($nn),Y ($B1) | A := [[$nn] + Y] (pointeur en page zéro) |
| STA ($nn),Y ($91) | [[$nn] + Y] := A (toujours 6 cycles) |
| ADC ($nn),Y ($71) | A := A + [[$nn] + Y] + C |
| SBC ($nn),Y ($F1) | A := A - [[$nn] + Y] - NOT(C) |
| AND ($nn),Y ($31) | A := A AND [[$nn] + Y] |
| ORA ($nn),Y ($11) | A := A OR [[$nn] + Y] |
| EOR ($nn),Y ($51) | A := A XOR [[$nn] + Y] |
| CMP ($nn),Y ($D1) | comparer A avec [[$nn] + Y] |
Exemples :
- ; Pointeur vers un buffer a $80-$81
- ; $80 = $00, $81 = $20 -> pointeur = $2000
- LDY #$05
- LDA ($80),Y ; lit le pointeur $2000 a $80-$81
- ; A = contenu de $2000 + $05 = $2005
-
- ; Copier un bloc de memoire avec pointeur source et destination
- ; $80-$81 = pointeur source
- ; $82-$83 = pointeur destination
- LDY #$00
- Boucle:
- LDA ($80),Y ; lire un octet depuis la source
- STA ($82),Y ; ecrire a la destination
- INY
- CPY #$40 ; copier 64 octets
- BNE Boucle
-
- ; Parcourir une chaine ASCIIZ pointee par $80-$81
- LDY #$00
- Boucle:
- LDA ($80),Y ; lire le caractere courant
- BEQ Fin ; si zero, fin de chaine
- JSR AfficherChar ; afficher le caractere
- INY
- BNE Boucle ; boucler (max 256 caracteres)
- Fin:
-
- ; Copier un bloc de plus de 256 octets
- ; (incrementer le poids fort du pointeur)
- LDY #$00
- LDX #$04 ; 4 pages = 1024 octets
- BouclePage:
- LDA ($80),Y ; lire depuis la source
- STA ($82),Y ; ecrire vers la destination
- INY
- BNE BouclePage ; boucle interne (256 octets)
- INC $81 ; page suivante (source)
- INC $83 ; page suivante (destination)
- DEX
- BNE BouclePage ; boucle externe (pages)
Schéma mémoire (exemple avec ($80),Y ou Y=$05) :
Page zéro :
- $80 : $00 ; poids faible du pointeur
- $81 : $20 ; poids fort du pointeur -> $2000
Mémoire :
- $2000 : ...
- $2005 : $42 ; <- donnee lue (base $2000 + Y $05)
Remarques :
- C'est le mode d'adressage le plus polyvalent du 6502 pour accéder à la mémoire via des pointeurs
- Il est l'équivalent approximatif de [An] + déplacement sur le 68000, ou de [reg + offset] sur le x86
- Le cycle supplémentaire pour le franchissement de page s'applique en lecture mais PAS en écriture (STA prend toujours 6 cycles)
- Comme pour (Ind,X), le wrapping s'applique en page zéro : si $nn = $FF, le poids faible est lu a $FF et le poids fort a $00
- Ce mode est massivement utilise en programmation 6502 pour toute manipulation de données via pointeurs
Différence entre ($nn,X) et ($nn),Y :
| Adressage | Description |
|---|---|
| ($nn,X) | Pre-indexe → X sélectionne QUEL pointeur utiliser EA = [table_de_pointeurs + X]. Utile pour : tables de pointeurs, dispatch |
| ($nn),Y | Post-indexe → Y est un OFFSET dans les données EA = [pointeur] + Y. Utile pour : parcours de tableaux, accès a des structures via un pointeur de base. |
Adressage relatif (Relative)
L'adresse effective est calculée en ajoutant un déplacement signe de 8 bits a l'adresse de l'instruction SUIVANT le branchement. Ce mode est utilisé exclusivement par les instructions de branchement conditionnel.
Notation assembleur : label (l'assembleur calcule le déplacement)
Taille de l'instruction : 2 octets (opcode + déplacement 8 bits)
Nombre de cycles : 2 (branchement non pris), 3 (branchement pris, même page), 4 (branchement pris, franchissement de page)
Formule : EA = PC + deplacement_signe (ou PC est l'adresse de l'instruction suivant le branchement, c'est-a-dire l'adresse du branchement + 2)
Déplacement :
- Valeur signée de 8 bits : -128 ($80) a +127 ($7F)
- Portée effective par rapport a l'instruction de branchement :
- En arrière : -126 octets (déplacement -128 + 2 = -126)
- En avant : +129 octets (déplacement +127 + 2 = +129)
Instructions utilisant ce mode :
| Instruction | Opération |
|---|---|
| BCC ($90) | Brancher si C = 0 (Carry Clear) |
| BCS ($B0) | Brancher si C = 1 (Carry Set) |
| BEQ ($F0) | Brancher si Z = 1 (Equal / Zero) |
| BNE ($D0) | Brancher si Z = 0 (Not Equal / Not Zero) |
| BMI ($30) | Brancher si N = 1 (Minus / Negative) |
| BPL ($10) | Brancher si N = 0 (Plus / Positive) |
| BVC ($50) | Brancher si V = 0 (Overflow Clear) |
| BVS ($70) | Brancher si V = 1 (Overflow Set) |
Utilisation pour les comparaisons :
| Instruction | Opération |
|---|---|
| CMP #val / BCC label | Brancher si A < val (non signe) |
| CMP #val / BCS label | Brancher si A >= val (non signe) |
| CMP #val / BEQ label | Brancher si A = val |
| CMP #val / BNE label | Brancher si A != val |
| CMP #val / BMI label | Brancher si A < val (signe, approx.) |
| CMP #val / BPL label | Brancher si A >= val (signe, approx.) |
Exemples :
- ; Boucle simple
- LDX #$10 ; X = 16
- Boucle:
- DEX ; X = X - 1
- BNE Boucle ; boucler tant que X != 0
-
- ; Test et branchement
- LDA Score
- CMP #100 ; score >= 100 ?
- BCS NiveauSuivant ; oui, passer au niveau suivant
-
- ; Branchement hors de portee (contournement)
- ; Si la cible est trop loin pour un deplacement de 8 bits :
- BEQ ProcheLabel ; si egal, sauter a ProcheLabel
- JMP Suite ; sinon, continuer
- ProcheLabel:
- JMP LoinLabel ; sauter a la cible eloignee
- Suite:
- ; ... code ...
-
- ; Equivalent de "BRA" (branchement inconditionnel) sur 6502 :
- ; Le 6502 n'a PAS d'instruction BRA. On peut simuler :
- CLC
- BCC Toujours ; BCC apres CLC branche toujours
- ; Ou :
- JMP Toujours ; mais prend 3 octets au lieu de 2
Calcul du déplacement par l'assembleur :
L'assembleur calcule automatiquement le déplacement :
| déplacement = adresse_cible - (adresse_branchement + 2) |
Exemple : branchement a $1000 depuis $0FF0
|
déplacement = $1000 - ($0FF0 + 2) = $1000 - $0FF2 = $0E → BNE $0E (déplacement positif de 14) |
Exemple : branchement a $0FE0 depuis $1000
|
déplacement = $0FE0 - ($1000 + 2) = $0FE0 - $1002 = $FFDE → En signe 8 bits : $DE = -34 → BNE $DE (déplacement négatif de 34) |
Timing des branchements :
- 2 cycles si le branchement n'est pas pris (la condition est fausse) : le processeur passe simplement a l'instruction suivante
- 3 cycles si le branchement est pris et que la cible est dans la même page de 256 octets
- 4 cycles si le branchement est pris et que la cible est dans une page différente (franchissement de page)
Ce comportement asymétrique est important pour l'optimisation :
- Placer le cas le plus fréquent dans le chemin "non pris"
- Aligner les boucles pour éviter les franchissements de page
Remarques :
- Le 6502 original n'a PAS d'instruction de branchement inconditionnel relatif (BRA). Le 65C02 ajoute BRA ($80).
- La portée limitée a -126/+129 octets oblige parfois a utiliser des sauts intermédiaires (trampolines)
- Les branchements relatifs produisent du code relogeable (position-independent) car ils ne codent pas d'adresse absolue
- Le 65C16 (65816) ajoute des branchements relatifs longs (BRL) avec un déplacement de 16 bits
Résumé et tableau comparatif
Les 13 modes d'adressage du 6502 :
| No | Mode | Syntaxe | Taille | Formule EA |
|---|---|---|---|---|
| 1 | Implicite | (aucune) | 1 octet | (pas d'EA) |
| 2 | Accumulateur | A | 1 octet | (pas d'EA, agit sur A) |
| 3 | Immediat | #$nn | 2 octets | operande = nn |
| 4 | Page zero | $nn | 2 octets | EA = $00nn |
| 5 | Page zero,X | $nn,X | 2 octets | EA = ($nn+X) AND $FF |
| 6 | Page zero,Y | $nn,Y | 2 octets | EA = ($nn+Y) AND $FF |
| 7 | Absolu | $nnnn | 3 octets | EA = $nnnn |
| 8 | Absolu,X | $nnnn,X | 3 octets | EA = $nnnn + X |
| 9 | Absolu,Y | $nnnn,Y | 3 octets | EA = $nnnn + Y |
| 10 | Indirect | ($nnnn) | 3 octets | EA = [$nnnn] |
| 11 | Indirect indexe X | ($nn,X) | 2 octets | EA = [($nn+X) AND $FF] |
| 12 | Indirect indexe Y | ($nn),Y | 2 octets | EA = [$nn] + Y |
| 13 | Relatif | $rr | 2 octets | EA = PC + déplacement |
Nombre de cycles par mode (lecture) :
| Mode | Cycles | Cycles (écriture) | Page cross |
|---|---|---|---|
| Implicite | 2 | - | - |
| Accumulateur | 2 | - | - |
| Immédiat | 2 | - | - |
| Page zéro | 3 | 3 | - |
| Page zero,X | 4 | 4 | - |
| Page zero,Y | 4 | 4 | - |
| Absolu | 4 | 4 | - |
| Absolu,X | 4(+1) | 5 | +1 cycle |
| Absolu,Y | 4(+1) | 5 | +1 cycle |
| Indirect | - | - | - |
| Indirect indexe X | 6 | 6 | - |
| Indirect indexe Y | 5(+1) | 6 | +1 cycle |
| Relatif (pris) | 3(+1) | - | +1 cycle |
| Relatif (non pris) | 2 | - | - |
Comparaison avec le Z80 :
| Aspect | 6502 | Z80 |
|---|---|---|
| Nombre de modes | 13 | ~10 |
| Page zero / directe | Oui ($00-$FF) | Non |
| Indexe avec offset | Non (sauf indirects) | Oui (IX+d, IY+d) |
| Indirect registre | Via page zero | (HL), (BC), (DE) |
| Post-increment | Non | Non (sauf LDI/LDD) |
| Pre-decrement | Non | Non (sauf LDI/LDD) |
| Relatif (données) | Non | Non |
| Relatif (branchements) | Oui (8 bits signe) | Oui (8 bits signe) |
| Immediat 16 bits | Non | Oui (LD rr,nn) |
| Bit indexation | Non | Oui (BIT/SET/RES) |
| Espace adressable | 64 Ko | 64 Ko |
Comparaison avec le 68000 :
| Aspect | 6502 | 68000 |
|---|---|---|
| Nombre de modes | 13 | 14 |
| Auto-increment | Non | (An)+ |
| Auto-decrement | Non | -(An) |
| Indirect registre | Via page zero | (An) |
| Indirect + offset | ($nn),Y (8 bits) | d16(An) |
| Indirect indexe | ($nn,X), ($nn),Y | d8(An,Xn) |
| Absolu court | Page zéro (8 bits) | (xxx).W (16 bits) |
| Absolu long | 16 bits | 32 bits |
| PC-relatif (données) | Non | d16(PC), d8(PC,Xn) |
| PC-relatif (branches) | Oui (8 bits) | Oui (8/16/32 bits) |
| Immediat | 8 bits | 8/16/32 bits |
| Page zéro | Oui (256 octets) | Non |
Le 6502 compense son faible nombre de registres par l'utilisation intensive de la page zéro comme extension des registres internes. Les modes d'adressage indirects via la page zéro permettent de manipuler des pointeurs 16 bits avec seulement des registres de 8 bits, ce qui est l'une des innovations clefs de l'architecture du 6502.