Alles illegal!

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

CBM prg StudioDie illegalen OpCodes des C64 bzw. 6510/6502 Prozessors.

Da sind wir also, bei den ‘sagenumwobenen‚ illegalen OpCodes. Eigentlich ist dies kein Thema für Einsteiger, aber ich möchte in den Grundlagen einmal alle Befehle erwähnt haben. Als erstes sollte ich aber darauf hinweisen und warnen (das mache ich hier noch häufiger), dass die hier aufgeführten Befehle teilweise nicht ganz unproblematisch sind. Ihr solltet sie also mit Bedacht einsetzen.

Wir haben jetzt alle 56 Befehle des Prozessors kennengelernt, die offiziell zur Verfügung stehen. Wie wir aber wissen, besitzt der C64 einen 8-Bit-Prozessor, somit sind theoretisch 256 unterschiedliche Befehle möglich. Bei unseren 56 bisherigen kommen wir, wenn man die unterschiedlichen Adressierungsarten mit einbezieht, auf 151 verwendete Bytes. Bleiben also noch 105 mögliche Befehle übrig. Dabei handelt es sich meistens um eine Kombination aus ‚legalen‚ Befehlen (z. B. erst ein ASL und dann ein ORA). Diese mehr oder weniger sinnvollen Befehle werden häufig als ‚illegal‚, ‚extra‚ oder ‚undocumented‚ bezeichnet. Einige Befehle haben sogar mehrere OpCodes, obwohl es sich immer um den selben Befehl handelt, dort wird also keine andere Adressierungsart verwendet.

Hardware-Exkurs: Wieso gibt es solche OpCodes überhaupt?

Befehlsdekodierung
Befehlsdekodierung

Ich will hier nicht wirklich in die Details gehen, es soll nur eine kurze oberflächliche Erklärung werden.
Der 6510/6502 benutzt keine MicrocodesinfoInfoDie Befehle der CPU sind nicht fest verdrahtet, wie z. B. bei RISC-Prozessoren, sondern in einem ROM gespeichert. um die Befehle zu dekodieren, wie andere CPUs. Das ‚Instruction-Register‚ und der Zyklus aus der ‚Timing Generation Logic‚ werden im ‚Decode ROM‚ zusammengefasst. Diese besteht aus 130 Zeilen zu je 21 Bits. Jede Zeile enthält nun Bit-Muster, die zur Identifikation der Befehle dienen. Dabei wird festgelegt, welche Bits im Muster 0 und welche 1 sein müssen und bei welchem Zyklus das stattfindet. Trifft ein Muster zu, so liefert die Zeile eine 1. Jeder Befehl ist hier also mit einem Bit-Muster hinterlegt, verwendet man einen illegalen OpCode, dann kann dieser auch dekodiert werden. Aber durch die ‚ungeplanten‚ Bit-Muster ist es nun möglich, dass mehrere Zeilen eine 1 liefern und wir so die eben erwähnte Kombination von bekannten Befehlen erhalten.

 

Wie kann man die Befehle nun verwenden?
Zunächst mal ist wichtig, dass das System auf dem das Programm laufen soll, diese Befehle versteht. Bei einem echten C64 ist das der Fall, auch die meisten Emulatoren können diese Befehle umsetzen, aber das muss nicht immer so sein.
Für uns als Programmierer ist es natürlich am schönsten, wenn unser Assembler diese Befehle kennt. Auch für die illegalen-Befehle gibt es Mnemonics, da diese aber nicht offiziell sind, gibt es teilweise unterschiedlichen Namen für einen Befehl. Hier muss man ggf. schauen, welche der verwendete Assembler versteht. Sollte der Assembler mit den Befehlen nichts anfangen können, dann müsst ihr wohl oder übel die gewünschten Befehle direkt als Bytes eingeben. Je nach Assembler ist es evtl. auch noch möglich sich eigene Macros für die Befehle zu bauen. Das CBM prg Studio versteht alle hier erklärten Befehle (nur einer geht z. Zt. nicht, aber dazu später mehr), es verwendet immer den erstgenannten Mnemonic.

Die Befehle sind z. T. echt strange, ich versuche erst gar nicht ein sinnvolles Beispiel anzubieten. Meiner Meinung nach sollte man diese Befehle nur im Notfall einsetzen, z. B. wenn sie dringend benötigten Platz sparen oder in einer zeitkritischen Routine einen Vorteil bringen.

Diese Liste wurde anhand der Infos auf verschiedenen Internetseiten und mit dem Buch „The Complete Commodore Inner Space Anthology„ erstellt. Ich bin trotzdem nicht ganz sicher, ob alles stimmt (z. B. habe ich bei den Taktzyklen nicht immer einen Hinweis darauf gefunden, ob und wie sich eine Überschreitung der Page-Grenze auswirkt).

 

Genug der Vorrede, werfen wir endlich einen Blick auf die letzten Befehle des C64:

ASO: ASL+ORA (alternatives Mnemonic: SLO)
ASO absolut ($0F, 3B, 6T, NZC)

ASO kombiniert ein ASL mit einem ORA. Dabei wird das über die Adressierung zufindene Byte zunächst per ASL bitweise nach links verschoben und anschließend mit dem Akku ODER-Verknüpft.

Man könnte also auch schreiben:

Wenn ihr jetzt mal in die Mnemonics schaut, werdet ihr feststellen, dass diese beiden Befehle zusammen 6Bytes und  10TZ benötigen. Wir können mit ASO also, 3Byte Speicher und 4TZ sparen, das ist schonmal nicht schlecht. Außerdem können wir Daten über Adressierungsarten ‘shiften‚, die es beim ASL nicht gibt (z. B. absolut,Y).

Übersicht der Adressierungsarten:

Sollte euer Assembler diesen Befehl nicht verstehen, könnt ihr den Befehl z. B. durch die angabe der Bytes  BYTE $0F, $2D, $08  (entspricht aso $082D ) trotzdem verwenden.

 

RLA: ROL+AND
RLA absolut ($2F, 3B, 6T, NZC)

RLA kombiniert ein ROL mit einem AND. Dabei wird das über die Adressierung zufindene Byte zunächst per ROL bitweise nach links verschoben (von rechts wird das C-Flag eingeschoben) und anschließend mit dem Akku UND-Verknüpft.

Man könnte also auch schreiben:

Übersicht der Adressierungsarten:

 

LSE: LSR+EOR (alternatives Mnemonic: SRE)
LSE absolut ($4F, 3B, 6T, NZC)

LSE kombiniert ein LSR mit einem EOR. Dabei wird das über die Adressierung zufindene Byte zunächst per LSR bitweise nach rechts verschoben und anschließend mit dem Akku exklusiv-ODER-Verknüpft.

Man könnte also auch schreiben:

 

Übersicht der Adressierungsarten:

 

RRA: ROR+ADC
RRA absolut ($6F, 3B, 6T, NZ)

RRA kombiniert ein ROR mit einem ADC. Dabei wird das über die Adressierung zufindene Byte zunächst per ROR bitweise nach rechts verschoben (das herausfallende Bit landet im C-Flag) und wird anschließend durch ADC mit dem Akku addiert (dabei wird natürlich auch das Carry-Flag vom ROR beachtet).

Man könnte also auch schreiben:

Da ADC nach dem ROR ausgeführt wird, ist das C-Flag also abhängig von der Addition, wir bekommen nach dem RRA also kein Carry-Flag für das mittels ROR hinausgeschobene Bit!!

 

Übersicht der Adressierungsarten:

 

AXS: Akku AND X-Register+Stored to memory (alternatives Mnemonic: SAX)
AXS absolut ($8F, 3B, 4T, <keine>)

AXS funktioniert so: Die Inhalte von Akku und X-Register werden UND-Verknüpft, aber OHNE eines der beiden Register zu ändern!  Das Ergbnis wird dann an der angegebenen Adresse abgelegt. Die Flags im Statusregister (SR) bleiben ebenfalls unverändert!

Wollte man das mit normalen Befehlen nachbilden, dann bräuchte man eine ganze Menge davon:

 

Übersicht der Adressierungsarten:

 

LAX: LDA+LDX
LAX absolut ($AF, 3B, 4T, NZ)

LAX lädt das Byte von der angegebenen Adresse gleichzeitig in den Akku und ins X-Register.

Mit regulären Befehlen sehe das so aus:

 

Übersicht der Adressierungsarten:

Hier wird bei zwei Adressierungsarten ein extra Taktzyklus benötigt, falls die Page-Grenze überschritten wird.

 

DCM: DEC+CMP (alternatives Mnemonic: DCP)
DCM absolut ($CF, 3B, 4T, NZC)

DCM verringert das Byte an der angegebenen Speicherstelle mit DEC und vergleicht dieses dann mittels CMP mit dem Akku.

Mit regulären Befehlen sehe das so aus:

 

Übersicht der Adressierungsarten:

 

 

INS: INC+SBC (alternatives Mnemonic: ISC)
INS absolut ($EF, 3B, 6T, NZC)

INS erhöht das Byte an der angegebenen Speicherstelle mit INC und subtrahiert es dann mit SBC vom Akku.

Mit regulären Befehlen sehe das so aus:

 

Übersicht der Adressierungsarten:

 

ALR: AND+LSR
ALR unmittelbar ($4B, 2B, 2T, NZC)

ALR führt per AND eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch. Abschließend wird per LSR der Akku bitweise nach rechts verschoben.

Mit regulären Befehlen sehe das so aus:

 

Hier gibt es nur die eben gezeigte Adressierungsart.

 

ARR: AND+ROR
ARR unmittelbar ($6B, 2B, 2T, NZC)

ARR führt per AND eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch. Abschließend wird per ROR der Akku bitweise nach rechts rotiert.

Mit regulären Befehlen sehe das so aus:

 

Hier gibt es ebenfalls nur die eben gezeigte Adressierungsart.

 

XAA: TXA+AND
XAA unmittelbar ($8B, 2B, 2T, NZ)

XAA kopiert den Inhalt des X-Register mit TAX in den Akku und führt dann eine UND-Verknüpfung mit dem angegebenen Wert und dem Akku durch.

Mit regulären Befehlen sehe das so aus:

 

Wieder gibt es nur die eben gezeigte Adressierungsart.

 

OAL: ORA+AND+LDX  (eigentlich TAX)
OAL unmittelbar ($AB, 2B, 2T, NZ)

Jetzt wird es langsam echt verrückt. 😉
OAL führt zuerst per ORA eine ODER-Verknüpfung  mit dem Akku und dem Wert #$EE durch. Dann wird der Akku durch AND mit dem angegebenen Wert UND-Verknüpft und anschließend ins X-Register kopiert TAX.

Mit regulären Befehlen sehe das so aus:

 

Wieder gibt es nur die eben gezeigte Adressierungsart.

 

SAX: Subtract+AND+TAX
SAX unmittelbar ($CB, 2B, 2T, NZ)
Hier wird zuerst eine UND-Verknüfung zwischen dem Akku und dem X-Register vorgenommen. Dann wird vom Ergebnis der UND-Verknüpfung der angegebenen Wert abgezogen (ohne das Carry-Flag zu beachten!) und das Ergebnis im X-Register gespeichert. Das C-Flag kann aber durch die Subtraktion wiederum gesetzt werden, das OVerflow-Flag bleibt allerdings, ebenso wie der Akku, unverändert!

Ein ‚Nachbau‚ ist nur mit Hilfe einer Speicheradresse möglich, nehmen wir $FB auf der Zero-Page (SAX kommt natürlich ohne $FB aus):

 

Wieder gibt es nur die eben gezeigte Adressierungsart.

SKB: SKip next Byte (nächstes Byte überspringen)
SKB implizit ($80, 1B, 2-4T, <keine>)
Der Befehl SKB überspringt einfach das nächste Byte. Ein Nachbau würde nur einen Sprungbefehl (z. B. JMP) verwenden. Der Befehl kann auch noch mit weiteren OpCodes ausgelöst werden:
$82, $C2, $E2, $04, $14, $34, $44, $54, $64, $74, $D4, $F4
Eine genaue Angabe der Taktzyklen konnte ich leider nicht finden, nur das sie zwischen 2 und 4 liegen.

 

SKW: SKip next Word (nächstes Word [zwei Byte] überspringen)
SKW implizit ($0C, 1B, 4-5T, <keine>)
Der Befehl SKW überspringt ein sog. Word (das wiederum zwei Bytes entspricht). Ein Nachbau könnte wie beim SKB mit einem Sprungbefehl (z. B. JMP) erfolgen oder mit dem BIT-OpCode (wobei der das Statusregister verändert!). SKW kann auch noch mit weiteren OpCodes ausgelöst werden: $1C, $3C, $5C, $7C, $DC, $FC
Intern ist SKW eine Leseoperation, nur dass das Ergebnis nirgends gespeichert wird. Beim OpCode $0C wird eine absolute Adressierung verwendet, bei den anderen wird die absolute X-Indizierte Adressierung verwendet, dort wird dann beim Überschreiten der Page-Grenze ein weiterer Taktzyklus benötigt.

Zum Schluß der ‚relativ‚ sicher verwendbaren OpCodes bleibt noch der uns bereits bekannte NOP-Befehl. Dieser wird nicht nur beim offiziellen OpCode ($EA) ausgeführt, sondern auch bei ( $1A, $3A, $5A, $7A, $DA, $FA ).

 

 

Die nun folgenden OpCodes sind im Buch „The Complete Commodore Inner Space Anthology„ nicht mehr beschrieben. Ich habe sie nur im Netz gefunden, allerdings ist ihr verhalten teilweise nicht vorhersagbar. Da das CBM prg Studio diese Befehle aber kennt und Vice sie verarbeitet, führe ich sie der Vollständigkeit halber auf.
Die kommen Befehle werden jetzt wie erwähnt immer ‚verrückter‚ und problematischer, ein sinnvoller Einsatz ist daher äußerst fragwürdig!

 

HLT: HaLT  (Absturz verursachen)
HLT implizit ($02, 1B, ?T, <keine>)
Tja, was soll man dazu sagen? Triff die CPU auf einen dieser OpCodes $02, $12, $22, $32, $42, $52, $62, $72, $92, $B2, $D2, $F2 , dann ‚hängt‚ sie sich auf. Nur ein Reset bzw. Neustart hilft dann weiter. Taktzyklen sind hier nicht auffindbar und wären auch vollkommen irrelevant, der Rechner steht dann eh!

 

TAS: TXS+AND+STA
TAS absolut Y-indiziert ($9B, 3B, 5T, <keine>)
Zunächst werden der Akku und das X-Register per UND-Verknüpft, aber ohne das sich eines der Register ändert! Das Ergebnis wird in den Stackpointer (SP / nicht auf den Stack!!!) geschrieben. Dann wird das Ergebnis mit dem (Wert des MSB)+1 der angegebenen Adresse UND-Verknüpft und im Speicher an der angegebenen Adresse + Y-Register abgelegt. Wenn ihr das ausprobieren wollt, achtet auf den Stackpointer. Da der sehr wahrscheinlich geändert wurde (das sollte schon ein ungeheurer Zufall sein, wenn die erste UND-Verknüpfung den selben Wert wie der aktuelle SP ergibt) solltet ihr ihn mit TSX / TXS in einer Speicherstelle sichern und wiederherstellen. Die Flags bleiben dabei unverändert.

Auch das läßt sich nur unter Zuhilfenahme weiterer Speicherstellen (wir nehmen $FB, $FC auf der Zero-Page) nachbauen:

Soviel Aufwand für nichts, hat jemand einen sinnvollen Einsatz hierfür???
Weitere Adressierungsarten gibt es übrigens nicht.

 

 

SAY: Store value AND Y-Register
SAY absolut X-indiziert ($9C, 3B, 5T, <keine>)
Es wird das Y-Register mit dem Wert des MSB+1 UND-Verknüpft und anschließend an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Nachbauen läßt sich das ganz einfach:

Auch hier gibt es keine anderen Adressierungsarten.

 

 

XAS: X-Reg. AND value Stored to memory
XAS absolut Y-indiziert ($9E, 3B, 5T, <keine>)
Es wird das X-Register mit dem Wert des MSB+1 UND-Verknüpft und anschließend an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Nachbauen läßt sich das ganz einfach:

Auch hier gibt es keine anderen Adressierungsarten.

 

 

AXA: Akku and X-Reg. STA
AXA absolut Y-indiziert ($9F, 3B, 5T, <keine>)
Es wird der Akku mit dem X-Register und anschließend mit dem Wert des MSB+1 UND-Verknüpft. Anschließend wird das Ergebnis an der angegebenen Adresse gespeichert. Die Flags bleiben dabei unverändert.

Zum Nachbauen benötigen wir wieder eine Speicherstelle, die der Befehl natürlich nicht verwendet, nehmen wir wieder $FB auf der Zero-Page:

 

Übersicht der Adressierungsarten:

 

 

ANC: Akku AND value influence Carry
ANC unmittelbar ($2B, 2B, 2T, NZC)
Der Akku wird mit dem angegebenen Wert UND-Verknüpft. Das Ergebnis steht dann auch wieder im Akku. Er sieht also zunächst wie ein normaler AND-Befehl aus, aber hier wird das N-Flag ins Carry-Flag kopiert.

Der OpCode $0B  führt zum selben Befehl, wie der eben erwähnte  $2B .

Ein Nachbau könnte so aussehen:

 

 

LAS: Hierzu fällt mir nur: Lade Alles mit Schrott ein  😉
LAS absolut Y-indiziert ($BB, 3B, 2T, NZ)
Der Befehl führt eine UND-Verknüpfung mit dem Byte das an der angegebenen Speicherstelle zufinden ist und dem Stackpointer (SP) durch. Das Ergebnis wird dann im Akku, X-Register und SP abgelegt.

Zum letzten Mal ein ‚Nachbau‚:

 

Zwei letzte OpCodes, für bekannte Befehle soll es noch geben, aber auch die sind nicht ‚abgesichert‚:

$89  soll ebenfalls ein  NOP  ausführen.

$EB  soll einem einem  SBC #value  entsprechen.


Wer ein Wenig ‚googelt‚ findet seitenweise Diskussionen über diese Befehle. Wie bereits erwähnt gibt es teilweise ein unterschiedliches Verhalten auf verschiedenen Rechnern  und teilweise bei verschiedenen Ausgangssituationen auf dem selben Computer.
Daher nochmal die Warnung mit diesen Befehlen sparsam umzugehen!
Nichts ist frustrierender als stundenlang einen Fehler zu suchen und dann festzustellen, dass der illigale OpCode sich diesmal anders verhält nur weil der Bildschirmspeicher andere Daten enthält.


So, jetzt haben wir aber wirklich alle Befehle einmal gesehen. Als nächstes gibt es noch einen ‚Abschluß‚-Beitrag und dann sind wir mit dem Assembler-Grundlagen-Tutorial durch. Danach geht es mit dem Programmieren endlich ‚richtig‚ los.

 


Schrott!!Naja...Geht so...Ganz gut...SUPER! (12 Bewertungen | Ø 4,75 von 5 | 95,00%)

Loading...


 

<<< zurück | weiter >>>

 

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

Schreibe einen Kommentar


Beachtet bitte, dass ich eure Kommentare erst manuell freigegeben muß, bevor sie auf der Seite erscheinen! Da ich nicht pausenlos am Rechner sitze, kann es schon mal etwas dauern, bis ein Kommentar für alle sichtbar ist.

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam