SuperCPU durchleuchtet - Folge 3
Mittlerweile wissen wir, wie man eine SuperCPU erkennt und haben erfahren,
was der Native- und der Emulation-Mode des 65816-Prozessors sind und wie man
die 16 Bit-Register benutzt. In dieser Ausgabe geben wir einige ergaenzende
bzw. vertiefende Informationen.


Modi ueber Modi

Mit der SuperCPU hat man ein wahres Modus-Wunder am C64. Da gibt es nicht nur
den softwaremaessigen 1MHz- und 20MHz-Modus, sondern auch noch diverse
Optimization-Modes, mit denen man, wie im ersten Kursteil besprochen, die CPU
anweisen kann, nur bestimmte Speicherbereiche dem VIC durch Kopieren
zugaenglich zu machen, und dadurch den Programmablauf nochmals zu
beschleunigen. Ausserdem laesst sich abfragen, in welchen Modus (Turbo oder
Normal) die SuperCPU gerade mit dem Schalter eingestellt ist. Damit der
Ueberblick nicht verloren geht, sind alle diese Dinge in Tabelle 1 nocheinmal
zusammengefasst! Alle Register, die beschrieben werden muessen, begnuegen
sich mit einem beliebigen Wert, sie sind Write-Sensitive, wissen also bei
einem Schreibzugriff schon Bescheid, was zu tun ist.

Ausserdem hat der Prozessor in der SuperCPU zwei Modi, den Native- und den
Emulation-Mode. Zum Umschalten gab es hier ein neues Flag, das E-Flag.
Im Native-Mode kann man dann den Akku und/oder die Index-Register zwischen 8
und 16 Bit umschalten - dazu hatten wir die Befehle REP und SEP
kennengelernt. Desweiteren musste dann noch dem Assembler mitgeteilt werden,
ob er nun 16 oder 8 Bit zu assemblieren hat. Tabelle 2 zeigt das nochmal im
Ueberblick.


Andere Vektoren

Im vorigen Kursteil haben wir, immer bevor wir in den Native Mode gegangen
sind, einen SEI ausgefuehrt, um zu verhindern, dass waehrend des Abarbeitens
der Routine im Native Mode ein Interrupt kommt. Denn wuerde dies passieren,
waere ein Absturz die Folge - doch warum genau eigentlich? Wie schon
angedeutet, liegen im Native Mode die Sprungvektoren etwas anders im
Speicher, inklusive dem fuer den IRQ. Denn noch bevor die Interruptroutine
die Gelegenheit hat, ueber den beliebten Vektor bei $0314/$0315 zu springen,
war sie vorher kurz im ROM. Bei $FFFE/$FFFF ist nemlich der eigentliche
IRQ-Vektor zu finden, den der Prozessor immer anspringt, wenn er einen
Interrupt bekommt. Hier steht die Adresse $FF48, eine ROM-Routine, welche die
Register und den Status auf den Stack rettet und dann ueberprueft, ob es sich
um einen IRQ- oder BRK-Interrupt gehandelt hat. Entsprechend wird ueber
$0314/$0315 oder $0316/$0317 verzweigt. Im Native Mode des 65816 jedoch
liegen die Vektoren alle etwas anders. Die Entwickler wollten so die
Moeglichkeit zur Verfuegung stellen, fuer Emulation- und Native-Mode jeweils
eigene Interrupt-Routinen schreiben zu koennen. Tabelle 3 zeigt, wie die
Vektoren nun liegen.


Neuigkeiten
Auch hier gibt es einiges Neues. Wie man sieht, liegt der Vektor fuer den IRQ im Native Mode bei $FFEE/$FFEF. Im ROM steht dort die Zahlenkombination $E5/$4C , womit sich bei einem IRQ ein Sprung nach $4CE5 ergeben wuerde. Muss man also dort immer seine IRQ-Routine hinlegen? Natuerlich nicht. Eine Moeglichkeit ist, wie schon gesagt, dafuer zu sorgen, dass waehrend eine Native Mode Routine laeuft, kein IRQ erfolgen kann (SEI). Das waere auch gegeben, wenn man INNERHALB einer IRQ-Routine auf Native Mode schaltet und danach wieder zurueck, schliesslich kann waehrend der Abarbeitung des IRQs kein neuer kommen - und wenn doch, muss er warten, bis der alte abgearbeitet ist. Dies ist wohl die Loesung, die man am haeufigsten waehlen wird, wenn man fuer Demos oder Games Native-Routinen einsetzen will, schliesslich arbeitet man dort in der Regel Frame-orientiert und somit laeuft das meiste innerhalb der IRQ-Routine ab. Will man aber auch im richtigen Hauptprogramm den Native Mode nutzen (den man ja einschalten muss, um an die 16 Bit-Faehigkeiten zu kommen), so muss der IRQ gesperrt werden. Dies macht meistens keine Probleme, da bei 20 MHz und 16 Bit die Zeit, in der der IRQ gesperrt ist, sicherlich recht kurz sein wird. Doch wenn der Interrupt nun aber wirklich sofort abgearbeitet werden soll, sobald er anliegt? Dann muss man das ROM deaktivieren und eine eigene IRQ-Routine schreiben, und der Vektor bei $FFEE/$FFEF muss genau auf diese Routine zeigen. Da ergeben sich jedoch einige Fragen: Was zum Beispiel passiert, wenn im Augenblick des IRQs gerade 16 Bit aktiviert waren, die IRQ-Routine aber auf 8 herunterschaltet? Diese und weitere Fragen (z.B. 16-Bit-Werte auf den Stack schieben) werden wir in einem spaeteren Kursteil ausfuehrlicher besprechen, wenn es an's "Eingemachte" geht. Top Cops Es sind neue Interrupt-Moeglichkeiten hinzugekommen: So beispielsweise der "COP"-Interrupt. Dieser ist eigentlich dazu da, die Kontrolle an einen COProzessor zu uebergeben. Es gibt naemlich einen speziellen COP-Befehl, dem noch eine Zahl folgt. Damit koennte man, falls ein Coprozessor vorhanden waere, diesen ansprechen und mit dem Parameter eine bestimmte Aufgabe geben. Doch von Seiten CMD's hat man von soetwas noch nichts gehoert. Ist der COP-Befehl und der dazugehoerige Vektor dann vollkommen sinnlos? Nein, denn warum muss ein Coprozessor vorhanden sein, um spezielle Aufgaben erledigen zu koennen? Man kann den COP-Befehl auch zum Aufrufen von Unterroutinen benutzen. Ein "COP"-Interrupt wird ausgeloest und der Programm-Counter auf den Stack gerettet. Ueber ihn holt man sich den Parameter, fuehrt eine entsprechende Routine aus und kehrt mit RTI (Return from Interrupt) zum Programm zurueck! Auffaellig ist auch, dass es fuer den Native Mode keinen Reset-Vektor gibt. Dies liegt daran, daŻ der 65816 in den Emulation Mode schaltet, sobald er ein Reset-Signal bekommt, und somit dann auch ueber den entsprechenden Vektor springt. Fuer den BRK (der gleichnamige Befehl loest eben diesen Interrupt aus) gibt es im Emulation Mode keinen Vektor, weil ja dafuer das Break-Flag existiert. Im Native Mode ist es nicht mehr vorhanden, stattdessen gibt es einen eigenen Vektor, und man braucht nicht mehr, wie die ROM-Routine, das Break-Flag zu ueberpruefen. Weiterhin neu ist der ABORT. Hierbei handelt es sich um eine Art besseren NMI - der Prozessor kann dieses Signal von ausserhalb bekommen, fuehrt den aktuellen Befehl zuende aus und verzweigt dann ueber den ABORT-Vektor. Die Entwickler von Cartridges wie Action Replay waeren sicher froh gewesen, wenn der 6510 schon diesen Interrupt gehabt haette! Mehr ueber 16 Bit Durch die Tatsache, dass man Akku und Index-Register voellig unabhaengig voneinander auf 8 oder 16 Bit setzen kann, ergeben sich natuerlich diverse Kombinationsmoeglichkeiten. Interessant wird es spaetestens dann, wenn man mit den Befehlen wie TAX,TAY,TXA,TYA oder TXS,TSX arbeiten will. Genau wie die Load-Befehle (LDA,LDX,LDY) beeinflussen auch die Transferbefehle das N (Negative) und das Z (Zero) Flag. (TXS beeinflusst die Flags nicht, da man hier davon ausgehen kann, dass der Programmierer weiss, was da transferiert wird, und es nichts abzuchecken gibt.) Doch was passiert nun beispielsweise bei einem TAY, wenn der Akku 16 Bit breit ist, waehrend die Index-Register-Groesse auf 8 Bit festgelegt ist? Die erste Regel dabei ist, dass die Art des Transfers immer vom ZIEL-Register abhaengig ist. In diesem Fall werden also nur die unteren 8 Bit des 16 Bit-Akkus in das Y-Register transferiert. Eine zweite Regel gilt hier uebrigens auch: Solange die Index-Register auf 8 Bit gesetzt sind, sind deren Highbytes immer 0, solange bis sie (wieder) auf 16 Bit geschaltet werden. Dann enthaelt das Lowbyte des Indexregisters die 8 Bit, die es vorher auch hatte. Listing 3.1 zeigt, was passiert. In diesem Beispiel ist der Wert, der an die Adresse ab Label DATA2 gespeichert wird $0033 - nur die unteren 8 Bit wurden aus dem Akku transferiert, waehrend das Highbyte auf 0 gesetzt wurde. Schiebereien Der Akku arbeitet anders. Wenn seine Breite von 16 auf 8 Bit geschaltet wird, wird das ehemalige Highbyte zum "versteckten" Akkumulator B. An dieses kommt man sogar noch heran, ohne irgendwie den Modus wieder zu aendern, und zwar indem man den Befehl XBA (eXchange B with A) ausfuehrt. Listing 3.2 zeigt das Verhalten des Akkus. Nach dem Ablauf enthalten die Speicherstellen bei Ergebnis und Ergebnis+1 den Wert $7F33, oder $33,$7F in Low-Highbyte-Reihenfolge. Mit anderen Worten: Das Highbyte des Akkus, $7F, blieb trotz der Modus-Umschaltung auf 8 Bit erhalten. Versucht man nun einmal, ein 16-Bit-Index-Register in den Akku zu transferieren, waehrend dieser gerade auf 8 Bit geschaltet ist, wird anhand von Listing 3.3 klar, was passiert: Das Ergebnis hier ist naemlich $33FF, wodurch deutlich wird, dass das inaktive Highbyte des Akkus in diesem Augenblick vollkommen unberuehrt bleibt - das High-Byte des Y-Registers wurde nicht transferiert. Die Regel, dass sich ein Transfer immer nach dem Zielregister richtet, gilt also auch hier - der versteckte B-Akku wird nicht beeinflusst. Transferiert man in die andere Richtung, also von einem 8 Bit- zu einem 16 Bit-Register, muss man sich auch hier ueber die Auswirkungen im Klaren sein. Transferiert man den Akku im 8 Bit-Modus in ein Index-Register, das 16 Bit hat, werden beide 8 Bit-Akkus (also auch der "versteckte" als Highbyte) transferiert! Das Ergebnis, das in Listing 3.4 ab Label Ergebnis gespeichert wird, ist $7FFF, woraus ersichtlich wird, dass nicht nur der 8 Bit-Akku A transferiert und zum Lowbyte des 16 Bit-Index-Registers wurde, sondern auch der versteckte B Akku ebenfalls transferiert und somit das Highbyte des Index-Registers wurde. Dies bedeutet, dass wir beispielsweise einen 16 Bit-Index mit dem Akku im 8 Bit-Modus generieren koennen, jeweils immer ein Byte, und dann das ganze in das Index-Register transferieren koennen, ohne den Akku erst auf 16 Bit umschalten zu muessen. Doch Achtung: Man sollte nicht versehentlich ein unbekanntes Highbyte (B-Akku) in ein Index-Register transferieren! Transfers von einem 8 Bit-Index-Register in den 16 Bit-Akkumulator fuehren dazu, dass der Inhalt des Index-Registers zum Akku-Lowbyte wird, waehrend das Akku-Highbyte auf 0 gesetzt wird. Dies entspricht der Tatsache, dass beim Umschalten der Index-Register von 8 auf 16 Bit das Highbyte ebenfalls auf 0 gesetzt wird. In Listing 3.5 ergibt sich somit ein Ergebnis von $0033. Noch ein paar Worte zum Transfer von X zum Stackpointer (TXS-Befehl) und in die andere Richtung (TSX). Beim 65816 hat der Stackpointer 16 Bit, das heisst, der Stack muss nicht mehr zwangslaeufig ab $0100 liegen! Er kann ueberall innerhalb der 64K liegen, und natuerlich nun auch groesser als 256 Byte werden. Auch hier gilt konsequent, dass der Transfer sich nach dem Ziel richtet. Transferiert man den 16 Bit-Stackpointer in ein 8 Bit-Indexregister, hat man natuerlich dort dann nur das Lowbyte. Dies stoert nicht weiter, wenn man den Stack defaultmaessig ab $0100 belaesst. Will man ihn aber irgendwo anders hinverlegen oder gar beispielsweise fuer Interrupt-Routinen einen eigenen Stack einrichten. Das geht natuerlich nur im Native Mode! Man schaltet die Index-Register auf 16 Bit, laedt das X-Register mit einem 16 Bit-Wert, welches die neue Adresse des Stackpointers darstellen soll, und fuehrt einen TXS aus - schon liegt der Stack woanders. Doch wie immer muss man immer beachten, in welchem Modus der Prozessor ist. Schaltet man naemlich wieder auf Emulation Mode, wird das Highbyte des Stackpointers natuerlich wieder auf $01 gesetzt! Und zum Schluss noch eine kleine Ueberraschung (doch wen wundert's): Neben den bekannten Transferbefehlen zwischen den Registern bietet der 65816 noch den TXY und TYX, womit man direkt Werte zwischen den Indexregistern hin- und hertransferieren kann, ohne den Umweg ueber den Akku machen zu muessen! Je laenger man sich mit dem 16 Bit-Modus beschaeftigt, je mehr wird einem klar, was da fuer Power drinsteckt, denn als alter 6502-Freak wuerde man wahrscheinlich ersteinmal gar nicht auf die Idee kommen, zu ueberlegen, was denn bei einem Transfer von 16 nach 8 und umgekehrt passiert. Doch nun kennen wir die Hintergruende, was nicht nur beim Coden, sondern auch beim Finden von Bugs sicherlich hilfreich sein wird. Das Wissen aus dem zweiten Kursteil wurde hier gefestigt und ausgebaut - experimentieren Sie ruhig kraeftig damit! Im naechsten Kursteil gehen wir wieder in die Vollen und werden einige weitere tolle Eigenschaften des 65816-Prozessors in der SuperCPU kennenlernen!


[Tabelle 1]---> Complete V2 SuperCPU Memory Locations Register Funktion $D074 VIC Bank 2 Optimization-Mode, nur $8000-$C000 wird gemirrored $D075 VIC Bank 1 Optimization-Mode, nur $4000-$8000 wird gemirrored $D076 Screen Ram Optimization-Mode, nur $0400-$0800 wird gemirrored $D077 KEINE Optimization, der gesamte Speicher wird gemirrored (default) $D07A softwaremaessig auf 1 MHz schalten $D07B softwaremaessig auf 20 MHz schalten $D07E SuperCPU-Register im I/O-Bereich einblenden (fuer Optimization) $D07F SuperCPU-Register im I/O-Bereich ausblenden (fuer Optimization) $D0B8 Bit 6 =1: CPU ist im 1MHz-Modus =0: CPU ist im Turbomodus $D0BC Bit 7 =1: SuperCPU vorhanden =0: normaler C64


[Tabelle 2] Schalten in den Native Mode: CLC XCE Schalten in den Emulation Mode: SEC XCE Akku auf 16 Bit: REP #%00100000 oder REP #$20 Akku auf 8 Bit: SEP #%00100000 oder SEP #$20 Index-Register auf 16 Bit: REP #%00010000 oder REP #$10 Index-Register auf 8 Bit: REP #%00010000 oder SEP #$10 Akku und Index-Reg. auf 16 Bit: REP #%00110000 oder REP #$30 Akku und Index-Reg. auf 8 Bit: SEP #%00110000 oder SEP #$30 Assembler soll 16 Bit A assembl.: £al (Akku Long) Assembler soll 16 Bit X/Y assembl.: £rl (Register Long) Assembler soll 8 Bit A assembl.: £as (Akku Short) Assembler soll 8 Bit X/Y assembl.: £rs (Register Short) Gilt nur fuer F8-Assblaster !


[Tabelle 3] Vektor Emulation Mode Native Mode IRQ $FFFE/$FFFF $FFEE/$FFEF RESET $FFFC/$FFFD - NMI $FFFA/$FFFB $FFEA/$FFEB ABORT $FFF8/$FFF9 $FFE8/$FFE9 BRK - $FFE6/$FFE7 COP $FFF4/$FFF5 $FFE4/$FFE5


(w) Malte Mundt
©1999 Go64 Redax! & Count Zero/SCS*TRC for all HTML Stuff

[ Zum 2. Teil ][ Zum Index ][ Zum 4. Teil ]