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 ]