Der MOS Technology 6502 ist ein 8-Bit-Mikroprozessor vom Unternehmen MOS Technology, der 1975[1] zunächst im Keramikgehäuse als MOS MCS 6502[2] auf den Markt kam. Wegen seines einfachen Aufbaus und vor allem seines im Vergleich zu den damals etablierten Intel- und Motorola-Prozessoren sehr niedrigen Preises bei vergleichbarer Leistungsfähigkeit wurde er in vielen Heimcomputern (zum Beispiel dem Commodore PET 2001 und dessen Nachfolger VC 20, dem Atari 800, Apple I, Apple II und BBC Micro), zahlreichen Schachcomputern (zum Beispiel dem Mephisto Polgar mit 5 oder 10 MHz), im weltweit ersten Skatcomputer Skat Champion, vielen Peripheriegeräten und zahlreichen Einplatinencomputern für Steuerungs- und Entwicklungszwecke eingesetzt. Der Prozessor wurde unter der Leitung von Chuck Peddle entwickelt.[3]
Auch andere Mitglieder dieser Prozessorfamilie waren sehr erfolgreich, so der 6510, der Prozessor des Commodore 64, und der 6507 in den Atari-Spielkonsolen. Der Hauptkonkurrent von MOS Technology war damals Zilog, dessen Z80 zum Beispiel in vielen CP/M-Rechnern sowie in den Heimcomputern von Sinclair und Amstrad/Schneider zu finden war.
Das Design des 6502 wurde an das des 8-Bit-Prozessors Motorola 6800 angelehnt (nicht zu verwechseln mit dem jüngeren Motorola 68000) und orientierte sich an einem Einsatz des 6502 als Teil eines Mikrocontrollers.[4] Die Befehlssätze von 6502 und MC6800 sind sich ähnlich, aber anstatt des einen 16-Bit-Index-Registers beim MC6800 werden beim 6502 zwei 8-Bit-Index-Register verwendet, deren Wert im Rahmen einer Befehlsausführung auf eine im Programmcode (absolute indizierte Adressierung) oder im Speicher (indirekte indizierte Adressierung) vorgegebene 16-Bit-Adresse addiert werden kann.[5] Deswegen besteht hier für 6502-Programme die Notwendigkeit, Arrays, die größer als 256 Bytes sind, abschnittsweise anzusprechen.
Im Gegensatz dazu weisen im gleichen Zusammenhang andere 8-Bit-CPUs wie etwa der MC6800 oder der Z80 16-Bit-Index-Register auf, deren Wert zu einer im Programmcode vorgegebenen 8-Bit-Adresse addiert werden kann; die Möglichkeit, den Wert eines Indexregisters auf eine im Speicher vorhandene Adresse zu addieren, besteht bei diesen CPUs nicht.
Beispielsweise kann für einen Kopiervorgang ein Indexregister als Adresszähler verwendet werden, das innerhalb einer Schleife im Programmcode mit einem dedizierten Befehl um eins erhöht bzw. erniedrigt und anschließend noch vom selben Befehl auf den Wert null getestet wird. Der unmittelbar folgende Befehl beendet die Ausführung der Schleife, wenn zuvor der Wert null festgestellt wurde.[6] Einen Befehl, der einen solchen Anwendungsfall noch effizienter ausführt, wie z. B. der LDIR-Befehl des Z80, besitzt der 6502 nicht.
Der einfacher gehaltene Befehlssatz führt allerdings dazu, dass 6502-Programme im Normalfall deutlich mehr Speicher benötigen als das Gleiche leistende MC6800- oder Z80-Programme; zudem sind standardkonforme Compiler für höhere Programmiersprachen wie etwa Pascal oder C für den 6502 deutlich schwieriger zu implementieren und erzeugen langsameren Code als entsprechende Compiler für andere 8-Bit-Prozessoren. Die Ursache hierfür ist vor allem die auf 256 Bytes beschränkte Größe des Stapelspeichers (siehe unten) des 6502, so dass der für die meisten modernen Hochsprachen nötige größere Stapelspeicher per Software nachgebildet werden muss. Um die Chipfläche klein zu halten, hat der 6502 des Weiteren nur einen Akkumulator im Gegensatz zu den zwei Akkumulatoren A und B im MC6800.
Als Cross-Compiler für die Programmiersprache C kann für den 6502 cc65 eingesetzt werden.[7] Alternativ dazu kann mit dem 6502 eine einfache virtuelle CPU emuliert werden, womit dann sein Befehlssatz als Mikrocode behandelt wird.[8]
Der 64 KB große Adressraum des 6502 teilt sich in mehrere Bereiche auf, die sich an den Page-Grenzen orientieren, an denen das High-Byte der 16-Bit-Adresse seinen Wert wechselt, d. h. jede Page stellt einen zusammenhängenden Block von 256 Bytes dar:[9]
Für die Durchführung eines DMA zusammen mit dem Speicherzugriff des 6502 kommen neben der Verwendung des RDY-Signals noch weitere Methoden zum Einsatz:
Insofern es die Zugriffszeit des angesprochenen Speichers zulässt, kann ein DMA während der ersten Hälfte des Taktzyklus des 6502 stattfinden, da der 6502 nur während der zweiten Hälfte seines Taktzyklus eine Datenübertragung mit dem Speicher durchführt. Dieses Verfahren wird z. B. vom Video-Chip VIC II im C64 angewendet.[18]
Weiterhin ist es möglich, das Signal am Takteingang Φ0 des 6502 auf einem niedrigen Pegel zu halten, so dass der interne Zustand des 6502 einfriert. Dies kann beim 6502 allerdings nur zeitlich begrenzt aufrechterhalten werden, da sich sein dynamischer Speicher, d. h. seine Kapazitäten, mit der Zeit entlädt.[19] Diese Methode wird z. B. vom ANTIC-Chip im Atari 400 und Atari 800 eingesetzt,[20] der den 6502 so bis zu ca. 54 μs anhält.[21][22]
Technisch war MOS Technology mit dem 6502 durchaus innovativ:
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 | (Bitposition) |
Hauptregister | ||||||||||||||||
A | Accumulator | |||||||||||||||
Indexregister | ||||||||||||||||
X | X Index | |||||||||||||||
Y | Y Index | |||||||||||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | S | Stack Pointer | |||||||
Programmzähler | ||||||||||||||||
PC | Program Counter | |||||||||||||||
Statusregister | ||||||||||||||||
N | V | - | B | D | I | Z | C | Processor Flags |
Die Zeropage (Adressbereich 00xy16) kann durch spezielle, schnellere Adressierungsarten angesprochen werden. Durch ihre Verwendung bei indizierter Adressierung kann sie auch als Registerbank oder Register-File angesehen werden.
Der Zustand des Break-Flags kann nur auf dem Stapel nach einem BRK-Befehl[15] ausgelesen werden.[32] Es hat keine eigenen Setz- und Testbefehle und wird auch von den Befehlen PLP, RTI und PHP nicht berücksichtigt, wobei PHP das Break-Flag auf dem Stapel als Teil des Prozessorstatus immer mit dem Wert eins angibt.[33] Die CPU setzt bei einem erkannten NMI oder IRQ (sofern der IRQ über das Interrupt-Flag zugelassen ist) das Break-Flag auf null. Hierdurch wird als nächster Befehl ein BRK-Befehl ausgeführt (wenn nicht gerade schon ein BRK-Befehl ausgeführt wird) und somit nicht der gemäß dem Programmzähler nächste Befehl. Ein BRK-Befehl, der aufgrund eines IRQ oder Opcodes ausgeführt wird, verwendet denselben Interruptvektor. Anhand des Break-Flags lassen sich in diesem Fall dennoch die beiden Ursachen im Interrupt-Handler unterscheiden.[34] Am Ende des BRK-Befehls hat das Break-Flag stets den Wert eins.
Beispiele von Maschinenbefehlen bzw. deren Opcode und Mnemonics in Verbindung mit verschiedenen Adressierungsarten.[35]
Opcode | Mnemonic | Funktion |
---|---|---|
$A9 |
LDA #$FF |
Lädt ("LoaD") das Register "Akkumulator" mit FF16 (255 dezimal).
|
$AD |
LDA $C000 |
Lädt den Akkumulator mit dem Speicherinhalt an der absoluten Adresse C00016 (49152 dezimal).
|
$A1 |
LDA ($24),Y |
Lädt den Akkumulator mit dem Inhalt an jener Speicheradresse, welche sich ergibt, indem zu der 16-Bit-Zahl in den Adressen 2416 und 2516 der Inhalt des Y-Registers addiert wird. Siehe Zeropage, indirekte Adressierung.
|
$8D |
STA $C000 |
Speichert ("STore") den Inhalt des Akkumulators (ein Byte) an der Speicheradresse C00016 .
|
$6D |
ADC $C001 |
Addiert ("ADd with Carry-Flag") den Inhalt an der Speicheradresse C00116 (ein Byte) zum Inhalt des Akkumulators (plus eins, falls das Carry-Flag gesetzt ist).
|
$C9 |
CMP #$7F |
Vergleicht ("CoMPare") den Inhalt des Akkumulators mit dem Zahlenwert 7F16 (127 dezimal) und setzt die Flags entsprechend.
|
$E4 |
CPX $C0 |
Vergleicht den Inhalt des Registers X ("ComPare X") mit dem Inhalt der Speicherzelle an der absoluten Adresse C016 .
|
$C0 |
CPY #$C0 |
Vergleicht den Inhalt des Registers Y ("ComPare Y") mit dem Zahlenwert C016 .
|
$F0 |
BEQ $FC00 |
("Branch if result is EQual") Verzweigt, wenn beispielsweise der letzte vorausgegangene Vergleich eine Gleichheit ergab (wird anhand des Z-Flags ermittelt). In diesem Fall würde das Maschinenprogramm an der Adresse FC0016 (auch als Sprungadresse bezeichnet) weiter ausgeführt. Andernfalls wird der auf BEQ unmittelbar folgende Befehl als nächstes ausgeführt. Die Branch-Befehle erlauben nur die relative Adressierung mit einem vorzeichenbehafteten Offset im Bereich von -128 bis 127, also mit einem einzelnen Byte als Argument. Die CPU ermittelt die Sprungadresse, indem sie das Offset zur Adresse des auf den Branch-Befehl unmittelbar folgenden Befehls addiert.[36]
|
$D0 |
BNE $FC00 |
("Branch if result is Not Equal") Verzweigt, wenn beispielsweise der letzte vorausgegangene Vergleich eine Ungleichheit ergab. |
$E8 |
INX |
("INcrement X") Erhöht den Inhalt des Registers X um eins. |
$88 |
DEY |
("DEcrement Y") Verringert den Inhalt des Registers Y um eins. |
$20 |
JSR $FC00 |
("Jump to SubRoutine") Springt zur Subroutine (Unterprogramm) an der Adresse FC0016 . Die Rücksprungadresse, welche aus zwei Byte besteht und auf dem Stapel abgelegt wird, ist die Adresse des letzten Bytes des drei Byte langen JSR -Befehls.[37]
|
$60 |
RTS |
("ReTurn from Subroutine") Kehrt aus der Subroutine zurück und führt das Maschinenprogramm an der Rücksprungadresse aus, welche zuvor vom Stapel genommen und um eins erhöht wurde.[37] |
$00 |
BRK |
("BReaK") Speichert am Anfang einer Interrupt-Behandlung bzw. als BRK-Befehl im Programmcode erst den Programmzähler bzw. den Programmzähler erhöht um zwei auf dem Stapel, dann das Statusbyte. Lädt den passenden Interrupt-Vektor in den Programmzähler. Hierbei hat das Laden des NMI-Vektors Priorität vor dem Laden des IRQ/BRK-Vektors. Setzt das I- und B-Flag und hebt die Registrierung eines NMI auf.[38][15] |
$EA |
NOP |
("No OPeration") Befehl ohne Funktion, erhöht den Programmzähler um eins. |
In Assembler-Programmtexten des 6502 haben das Dollar- und Nummern-Zeichen sowie die Klammern folgende Bedeutung:
Alle Opcodes des dokumentierten 6502-Befehlssatzes[35] | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Adressierungsarten: A – Accumulator, # – unmittelbar, zpg – Zeropage, abs – absolut, ind – indirekt, X – indiziert indirekt mit X-Register, Y – indirekt indiziert mit Y-Register, rel – relativ | ||||||||||||
Höherwertiges Nibble | Niederwertiges Nibble | |||||||||||
0 | 1 | 2 | 4 | 5 | 6 | 8 | 9 | A | C | D | E | |
0 | BRK | ORA (ind,X) | ORA zpg | ASL zpg | PHP | ORA # | ASL A | ORA abs | ASL abs | |||
1 | BPL rel | ORA (ind),Y | ORA zpg,X | ASL zpg,X | CLC | ORA abs,Y | ORA abs,X | ASL abs,X | ||||
2 | JSR abs | AND (ind,X) | BIT zpg | AND zpg | ROL zpg | PLP | AND # | ROL A | BIT abs | AND abs | ROL abs | |
3 | BMI rel | AND (ind),Y | AND zpg,X | ROL zpg,X | SEC | AND abs,Y | AND abs,X | ROL abs,X | ||||
4 | RTI | EOR (ind,X) | EOR zpg | LSR zpg | PHA | EOR # | LSR A | JMP abs | EOR abs | LSR abs | ||
5 | BVC rel | EOR (ind),Y | EOR zpg,X | LSR zpg,X | CLI | EOR abs,Y | EOR abs,X | LSR abs,X | ||||
6 | RTS | ADC (ind,X) | ADC zpg | ROR zpg | PLA | ADC # | ROR A | JMP (ind) | ADC abs | ROR abs | ||
7 | BVS rel | ADC (ind),Y | ADC zpg,X | ROR zpg,X | SEI | ADC abs,Y | ADC abs,X | ROR abs,X | ||||
8 | STA (ind,X) | STY zpg | STA zpg | STX zpg | DEY | TXA | STY abs | STA abs | STX abs | |||
9 | BCC rel | STA (ind),Y | STY zpg,X | STA zpg,X | STX zpg,Y | TYA | STA abs,Y | TXS | STA abs,X | |||
A | LDY # | LDA (ind,X) | LDX # | LDY zpg | LDA zpg | LDX zpg | TAY | LDA # | TAX | LDY abs | LDA abs | LDX abs |
B | BCS rel | LDA (ind),Y | LDY zpg,X | LDA zpg,X | LDX zpg,Y | CLV | LDA abs,Y | TSX | LDY abs,X | LDA abs,X | LDX abs,Y | |
C | CPY # | CMP (ind,X) | CPY zpg | CMP zpg | DEC zpg | INY | CMP # | DEX | CPY abs | CMP abs | DEC abs | |
D | BNE rel | CMP (ind),Y | CMP zpg,X | DEC zpg,X | CLD | CMP abs,Y | CMP abs,X | DEC abs,X | ||||
E | CPX # | SBC (ind,X) | CPX zpg | SBC zpg | INC zpg | INX | SBC # | NOP | CPX abs | SBC abs | INC abs | |
F | BEQ rel | SBC (ind),Y | SBC zpg,X | INC zpg,X | SED | SBC abs,Y | SBC abs,X | INC abs,X | ||||
Fehlende Opcodes (z. B. 02) und alle Opcodes mit den niederwertigen Nibbles 3, 7, B und F sind nicht Bestandteil des 6502-Befehlssatzes. |
Es gibt von verschiedenen Herstellern CMOS-Versionen des 6502, die einen erweiterten Befehlssatz bzw. weitere Adressierungsarten haben. Dadurch kann es zu Änderungen bei den nachfolgend beschriebenen undokumentierten Opcodes kommen.
Der 6502 ist bekannt für eine ganze Reihe von Befehlen, die nicht in der offiziellen Dokumentation stehen, aber dennoch existieren und funktionieren. Umgangssprachlich nannte man solche Befehle illegale Opcodes. Nur 151 der prinzipiell 256 möglichen Opcodes sind reguläre Befehle. Aber auch unter den verbleibenden 105 nicht dokumentierten Codes gibt es etliche, die Funktionen haben, darunter durchaus nützliche. Einige Assembler unterstützen solche Befehle, es gibt jedoch keine einheitliche Vorschrift für ihre Benennung mit Mnemonics.
Mit vielen undokumentierten Opcodes lässt sich die Datenverarbeitung beschleunigen, da sie in wenigen Taktzyklen Funktionen erledigen, die sonst nur mit mehreren aufeinanderfolgenden Befehlen möglich sind, was insgesamt erheblich mehr Taktzyklen verbraucht. Dies birgt allerdings das Risiko, dass solche Befehle nicht auf allen produzierten CPUs gleichermaßen funktionieren oder ein mögliches Nachfolgemodell diese Befehle gar nicht beherrscht oder andere Funktionen ausführt, wodurch das Programm nicht mehr lauffähig wäre.
Wie bei den meisten regulären Befehlen gibt es auch für viele der undokumentierten Funktionen unterschiedliche Adressierungsarten und demzufolge auch unterschiedliche Opcodes. In der nachstehenden Tabelle werden – sofern nichts anderes angegeben ist – beispielhaft Opcodes für die Adressierungsart „absolut“ aufgeführt, die beiden auf den Opcode folgenden Bytes werden also als eine absolute Adresse interpretiert. Die Mnemonics sind inoffiziell.[39][40]
Opcode | Mnemonic | Funktion |
---|---|---|
$02 |
HLT |
("HaLT") Ein-Byte-Befehl, hält den Prozessor an. Führt faktisch zum Absturz, der nur durch einen Reset zu beheben ist. |
$0B |
ANC #n |
("AND #n, N-Flag → C-Flag") Adressierungsart „immediate“. Führt eine UND-Verknüpfung des Akkumulators mit n durch, speichert das Ergebnis im Akkumulator und übernimmt danach den Wert des Negative-Flags (der mit dem Wert des siebten Bits des Akkumulators übereinstimmt) als neuen Wert für das Carry-Flag. |
$0C |
SKW |
("SKip next Word") Befehl ohne Funktion, erhöht den Programmzähler um drei und wird zum Überspringen der unmittelbar auf den Opcode folgenden zwei Bytes benutzt. |
$0F |
ASO $C000 |
("ASL, ORA") Führt den Befehl ASL mit dem Speicherinhalt an der Adresse C00016 aus, anschließend wird eine ODER-Verknüpfung des Akkumulators mit dem neuen Speicherinhalt durchgeführt und das Ergebnis im Akkumulator gespeichert.
|
$2F |
RLA $C001 |
("ROL, AND") Führt den Befehl ROL mit dem Speicherinhalt an der Adresse C00116 aus, anschließend wird eine UND-Verknüpfung des Akkumulators mit dem neuen Speicherinhalt durchgeführt und das Ergebnis im Akkumulator gespeichert.
|
$4F |
LSE $C002 |
("LSR, EOR") Führt den Befehl LSR mit dem Speicherinhalt an der Adresse C00216 aus, anschließend wird eine EXKLUSIV-ODER-Verknüpfung des Akkumulators mit dem neuen Speicherinhalt durchgeführt und das Ergebnis im Akkumulator gespeichert.
|
$6F |
RRA $C003 |
("ROR, ADC") Führt den Befehl ROR mit dem Speicherinhalt an der Adresse C00316 aus, anschließend wird der neue Speicherinhalt zum Akkumulator addiert (plus eins, falls das Carry-Flag gesetzt ist).
|
$8B |
XAA #n |
("TXA, AND #n") Adressierungsart „immediate“. Transferiert den Inhalt des Registers X in den Akkumulator, führt danach eine UND-Verknüpfung des Akkumulators mit n aus und speichert das Ergebnis im Akkumulator. |
$8F |
SAX $C004 |
("Store (A & X)") Soll den Inhalt des Akkumulators und des Registers X gleichzeitig an die Speicheradresse C00416 schreiben. Dadurch, dass beide Registerinhalte gleichzeitig am internen Datenbus anliegen, ergibt sich eine UND-Verknüpfung der beiden Registerinhalte, deren Ergebnis an der Adresse C00416 gespeichert wird.
|
$AF |
LAX $C005 |
("LDA, LDX") Lädt den Speicherinhalt an der Adresse C00516 in den Akkumulator sowie in das Register X. Einige Adressierungsarten von LAX können instabil sein, der Befehl also zu unvorhersehbaren Seiteneffekten führen.
|
$CF |
DCM $C006 |
("DEC, CMP") Verringert den Wert an der Speicheradresse C00616 um eins und vergleicht anschließend den neuen Speicherinhalt mit dem Inhalt des Akkumulators.
|
$EF |
INS $C007 |
("INC, SBC") Erhöht den Wert an der Speicheradresse C00716 um eins und subtrahiert anschließend den neuen Speicherinhalt vom Inhalt des Akkumulators (zusätzlich um eins, falls das Carry-Flag den Wert null besitzt).
|
Das nachfolgende Beispielprogramm ermittelt das Maximum einer Bytefolge.[41] Die erste Spalte zeigt die Speicheradressen gefolgt vom Maschinencode als Hexadezimalzahlen. Die zweite Spalte zeigt den dazugehörigen Quellcode für einen 6502-Assembler.
0040
0040 00
0041 03
0042 D5
0043 1C
0044 39
0200
0200 A6 41
0202 A9 00
0204 D5 41
0206 B0 02
0208 B5 41
020A CA
020B D0 F7
020D 85 40
|
; Das Programm ab der Adresse findmax findet das Maximum einer Folge von Bytes,
; wobei jedes Byte einen Wert von 0 bis 255 annehmen kann.
; Die Länge der Zahlenfolge befindet sich an der Adresse len und muss einen Wert
; im Bereich von 1 bis 255 aufweisen.
; Die Folgenglieder sind unmittelbar nach dem Längenbyte gespeichert.
; Das ermittelte Maximum im Bereich von 0 bis 255 wird an der Adresse max gespeichert.
; Die im Programmcode gewählten Adressen und die Bytefolge sind beispielhaft gewählt.
ORG $40
max DS 1 ; Speicherplatz für Maximum
len DC 3 ; Länge der Zahlenfolge
DC 213 ; 1. Folgenglied
DC 28 ; 2. Folgenglied
DC 57 ; 3. Folgenglied
ORG $0200 ; Startadresse des Programmcodes
findmax LDX len ; X-Register (X) mit Folgenlänge laden
LDA #0 ; Akkumulator (A) mit null (kleinster Wert) initialisieren
next CMP len,X ; A mit aktuellem Folgenglied vergleichen
BCS notmax ; verzweige an die Stelle notmax, falls Folgenglied nicht größer als A ist
LDA len,X ; A mit aktuellem Folgenglied laden, ist neues Maximum
notmax DEX ; X um eins vermindern
BNE next ; verzweige an die Stelle next, falls X größer als null ist
STA max ; speichere gefundenes Maximum an der Adresse max
|
Der Prozessor existiert in vier Varianten mit jeweils unterschiedlicher Taktfrequenz:[42]