SuperCPU durchleuchtet - Folge 4
Wie wir im letzten Kursteil herausgefunden haben, gibt es einige
Besonderheiten zu beachten, wenn man mit 8 und 16 Bit breiten Registern
arbeitet. In diesem Kursteil geben wir noch ein paar weitere wichtige Tips
zur Arbeit mit den doppelt breiten Registern. Ausserdem gibt's eine kleine
Routine zum Staunen, und letztendlich lernen wir noch ein weiteres tolles
Feature des 65816-Prozessors in der SuperCPU kennen.



Machen wir da weiter, wo wir letztes Mal aufgehoert haben: Als wir die
Transfers zwischen 8 und 16 Bit-Registern besprochen haben, kam ganz kurz
auch das zu den Transferbefehlen gehoerige TXS (Transfer X to Stackpointer)
zur Sprache. Und richtig: Man kann ein 16 Bit-Indexregister in den
Stackpointer transferieren. Dieser ist naemlich beim 65816 auch 16 Bit breit
- natiuerlich nur im Native Mode, im Emulation Mode bleibt er auf 8 Bit und
das Highbyte ist immer $01, so wie wir es kennen. Im Native Mode aber kann
somit der Stack ueberall innerhalb der 64K liegen. Damit verliert
die Page 1 ihre spezielle Bedeutung im Native Mode, und kann im
Grunde wie jeder andere Speicherbereich behandelt werden. Ausserdem bedeutet
ein 16 Bit breiter Stackpointer, dass der Stack von der Groesse her nicht
mehr auf $0100 Bytes beschraenkt ist!


Hochstapler?

So lassen sich fuer bestimmte Routinen eigene Stacks einrichten oder einfach
der Stack dorthinlegen, wo er einem gerade am besten passt. Natuerlich muss
man mit soetwas immer vorsichtig sein, da der Stack fuer Ruecksprungadressen
benutzt wird. Verlegt man in einer mit JSR angesprungenen Subroutine den
Stack, und merkt sich nicht den Inhalt des alten Stackpointers, um ihn vor
dem RTS zu restaurieren, wird der C64 nicht wissen, wohin er
(zurueck)springen soll - es sei denn, man hat es ihm vorher mit klargemacht,
in dem man z.B. die Ruecksprungadresse mittels Push-Befehlen auf den neuen
Stack gebracht hat. Um den Stackpointer zu handlen, gibt es einige Befehle:
TXS und TSX sind dem 6510-Coder wohl bekannt. Schaltet man vorher die
Indexregister auf 16 Bit, arbeiten diese Befehle auch mit 16 Bit-Werten.
Zusaetzlich gibt es aber noch ein neues Befehlspaar: TCS und TSC. "C" steht
dabei fuer Akku - es steht hier fuer die Tatsache, dass auf jeden Fall 16 Bit
vom oder in den Akku transferiert werden - egal ob er gerade auf 8 oder 16
Bit steht! Wie das gehen soll? Erinnern wir uns: Im Native Mode gibt es, wenn
der Akku auf 8 Bit steht, noch den "versteckten" Akku "B". Er stellt das
Highbyte des 16 Bit-Akkus dar. Schaltet man naemlich den Akku von 16 nach 8
Bit herunter, geht das Akku-Highbyte nicht verloren, sondern verbleibt
solange in "B", bis wieder auf 16 Bit zurueckgeschaltet wird - dann wird es
wieder zum Highbyte des Akkus. "C" steht fuer Low- und Highbyte des 16
Bit-Akku zusammen. TSC transferiert den 16 Bit-Stackpointer in den Akku. Ist
der auf 8 Bit, landet das Stackpointer-Lowbyte darin - das Highbyte gelangt
in den "B"-Akku. Ist er auf 16 Bit, hat man gleich den ganzen Stackpointer im
Akku.

Schiebe-Spiele

Natuerlich wird, wenn der Akku im 16 Bit-Modus ist, auch bei einem PHA (Push
Akku onto Stack) ein 16 Bit-Wert, also zwei Byte, auf den Stack geschoben.
Hierbei sollte man wissen, dass zuerst das Highbyte, dann das Lowbyte gepusht
wird. Umgekehrt holt sich ein PLA im 16 Bit-Mode natuerlich zuerst das
Lowbyte wieder, danach das Highbyte. Doch der 65816 hat noch mehr auf dem
Kasten: Die bekannte Befehlssequenz, in der man erst den Akku pusht, dann das
X-Register in den Akku schiebt, diesen wiederum pusht und dasselbe nochmal
mit dem Y-Register durchzieht, gehoert ab jetzt der Vergangenheit an. Es gibt
naemlich die neuen Befehle PHX, PHY und die Gegenstuecke dazu PLX und PLY.
Somit kann man die Index-Register direkt auf den Stack schieben. Auch hier
gilt wie beim Akku: Befinden sich die Index-Register auf 16 Bit (also das
X-Flag ist 0), werden auch 16 Bit aufeinmal gepusht, wieder zuerst das
Highbyte, dann das Lowbyte. Beim Pull wird analog zum Akku auch wieder
erst Lowbyte, dann Highbyte geholt. 
Das Kommando PHP (Push Processor-Status), welches die Flags auf den Stack
bringt, arbeitet uebrigens so wie immer, da das Status-Register (mit den
Flags) immer 8 Bit breit ist. Doch Achtung: Das "neunte Bit", naemlich des
E-Bit, welches zwischen Emulation- und Native-Mode umschaltet, wird nicht
gepusht! Die einzige Moeglichkeit, es zu beeinflussen, ist der uns
mittlerweile ja schon gut bekannte XCE-Befehl. 

Ob das so gedacht war?

Neben "normalen" Stack-Anwendungen und Verlegen des Stacks kann man sich
diese spezielle Eigenschaft des 65816 aber auch noch ganz anders zunutze
machen. Denken wir doch mal nach: Normalerweise steht der Stackpointer auf
$01FF, nach einem PHA verringert er sich automatisch um eins, steht also auf
$01FE. Bei einem 16-Bit-PHA verringert er sich immer um zwei, also z.B. von
$01FF auf $01FD. Wie waere es denn nun, den Stackpointer mal auf $07E7 zu
setzen? Den Akku schalten wir auf 16 Bit und fuehren einen LDA #$2020 aus -
Low- und Highbyte enthalten damit den Wert fuer ein Leerzeichen. Dann kann's
auch schon losgehen: PHA - und schon haben wir zwei Zeichen geloescht, und
ohne uns mit irgendeinem DEX oder aehnlichem herumschlagen zu muessen, hat
unser Prozessor den Zeiger schon weitergesetzt - auf $07E5! Da schicken wir
doch gleich noch einen PHA hinterher. So, wie weit sind wir? Holen wir uns
mal den Stackpointer ins X-Register - TSX - und vergleichen, ob er kleiner
als $0400 (obere Ecke des Screens) ist - denn der 16 Bit-Stackpointer wird
sich, bei $0700 angekommen, weiter auf $06FF bzw. $06FE bewegen! Ok, ist er
noch groesser? Dann weiter! Soeben haben wir mit Hilfe des Stacks den
Bildschirm geloescht - und zwar schneller, als es mit irgendeiner anderen
Routine moeglich waere! Selbst ein Riesenhaufen STA's waere nicht schneller -
vom geringfuegig laengeren Code dann mal ganz abgesehen! Schauen Sie sich das
ganze in Listing 4.1 an - es funktioniert tatsaechlich. Listing 4.2 zeigt
dieselbe Routine, allerdings mit TSC- und PHX-Befehl. Obwohl sie fuer uns
etwas ungewohnt aussieht, funktioniert sie dennoch genauso. Nebenbei darf
waehrend des Ausfuehrens der Routine natuerlich kein Interrupt auftreten -
dadurch wuerde automatisch die Ruecksprungadresse fuer den RTI auf dem Stack
- also mitten auf unserem Screen - landen. Uebrigens laesst sich so nicht nur
das Screenram mit Leerzeichen fuellen - es spricht nichts dagegen, auch eine
ganze Bitmap auf diese Art zu loeschen...


Die ganze Wahrheit

Und wo wir schon dabei sind, ueber neue Befehle zum Manipulieren des Stacks
zu sprechen, koennen wir auch gleich ein paar weitere Stack-Befehle
vorstellen. Zum Beispiel den PEA ("Push Effective Absolute Address").
Dahinter steckt nichts anderes als ein Befehl, mit dem man einen bestimmten
Wert auf den Stack schieben kann, ohne ihn erst in irgendein Register zu
laden. Wie der Name schon sagt, ist der Wert, der auf den Stack geschoben
wird, eine Adresse - ob das wirklich so ist, bleibt aber ganz dem
Programmierer ueberlassen. Es wird jedoch immer ein 16 Bit-Wert auf den Stack
gebracht, ganz egal, ob das M- oder X-Flag auf 8 oder 16 Bit stehen.
Vorsichtig sein muss man bei der Syntax des Befehls: So bedeutet ein PEA
$5000, dass der 16 Bit-Wert $5000 auf den Stack geschoben wird, nicht etwa
der Wert, der an $5000 und $5001 im Speicher steht! Ein naher Verwandter des
PEA ist der Befehl PEI ("Push Effective Indirect Address"). Damit kann man
eine Adresse, die als Zeiger in der Zeropage steht, auf den Stack bringen.
Steht in $FE beispielsweise eine $00, in $FF ist eine $60, so bewirkt ein PEI
($FE), dass die $6000 auf den Stack geschoben wird. Wieder ist es egal, ob
man sich im 16- oder 8-Bit-Modus befindet, es werden immer 16 Bit auf den
Stack gebracht. Auch hier muss man mit der Syntax etwas vorsichtig sein: Der
Befehl bezieht sich nicht etwa auf den Inhalt der Adresse, auf welche der
angegebene Zeropage-Pointer zeigt! Es wird tatsaechlich die Adresse selbst
auf den Stack gepusht. 

Relocatet sich selbst!

Absolut genial ist dann aber der PER-Befehl. PER steht fuer "Push Effective
PC Relative Indirect Address" und funkioniert so: Ein PER $1000 arbeitet wie
der PEI, nur dass, bevor die $1000 auf dem Stack landet, der aktuelle Wert
des Program Counters hinzuaddiert wird! Damit bietet der Befehl die geniale
Moeglichkeit, Code zu schreiben, der ueberall im Speicher laufen kann. Denn
der Wert, der im Augenblick des PERs auf den Stack gebracht wird, wird erst
zur Laufzeit des Programms errechnet - somit kann man z.B. auf einen
Datenbereich zugreifen, was sonst nur ueber direkte Adressierung moeglich
waere. Die Adresse hinter dem PER ist keine konkrete Adresse, sondern wie bei
den Branchbefehlen ein Offset, ein Abstand "von hier aus" also. Dies soll
dazu aber erstmal reichen - wenn wir in einem der spaeteren Kursteile zur
"Stack-relativen indizierten indirekten Adressierung" (nur keine Panik!)
kommen, werden wir auf diesen Befehl nochmal zu sprechen kommen. Uebrigens
gibt es logischerweise zu keinem dieser drei Spezialbefehle (PEA, PEI und
PER) ein Gegenstueck (etwa wie bei PHA-PLA oder TCS-TSC).

Sicher mit 16 Bit

So, das soll zum Stack reichen! Nun ersteinmal noch ein paar feine Tips zum
Umgang mit 16 Bit. Vielleicht war Ihnen das Problem noch gar nicht so
bewusst, aber was ist bei 16 Bit eigentlich das, was bei 8 Bit der
Unterschied zwischen $7F und $80 ist? Nein, gemeint ist nicht 1, sondern
positiv und negativ. Wie der erfahrene 6510-Coder weiss, wird beim Uebergang
von einem "positiven" zu einem "negativen" Wert das Negativ-Flag gesetzt.
Hier wird das 7.Bit (also das hoechstwertigste) als Vorzeichen-Bit gesehen:
Kommt bei irgendeiner Berechnung oder auch nur durch das Laden eines
Registers ein Wert zustande, bei dem dieses Bit gesetzt ist, wird auch das
Negativ-Flag gesetzt. Dies wird manchmal ausgenutzt, um in Schleifen die Null
noch "mitzukriegen": Man luegt z.B. das X-Register von $27 herunterzaehlen
und verzweigt nicht mit einem BNE- sondern einem BPL-Befehl. Wie laeuft das
ganze nun, wenn die Index-Register auf 16 Bit sind? Eigentlich ist es ganz
logisch: Wieder wird das hoechstwertige, also jetzt das 15.Bit, als Indikator
fuer negativ oder positiv angesehen - und dementsprechend wird nun auch das
Negativ-Flag gesetzt!

Dass Additions- und Subtraktionsbefehle sich mit 16 Bit gut machen, haben wir
schon in einem der vorangegangenen Kursteile gesehen. Aber dies sind bei
weitem nicht alle Opcodes, die durch das Loeschen des M- bzw. X-Flags
beeinflusst werden! Z.B. wird beim 16 Bit-INC solange das Byte an der
angegebenen Adresse hochgezaehlt, bis es $FF erreicht hat - ein weiterer INC
sorgt dafuer, das es wieder $00 wird (das kennen wir) - doch nun wird auch
das Byte an der Adresse+1 um eins erhoeht - klar, denn es wird als Highbyte
eines 16 Bit-Wertes angesehen! Bei einem ASL wird im 16 Bit-Modus durch zwei
Bytes nach links geschoben, mit einem einzigen STA kann man Low- und Highbyte
einer Adresse in die Zeropage schreiben oder gleichzeitig Rahmen- und
Hintergrundfarbe aendern. Auch AND und ORA funktionieren selbstverstaendlich
mit 16 Bit breiten Argumenten, egal ob absolut angegeben oder aus dem
Speicher geholt. In Listing 4.3 sehen Sie ein paar angewandte Befehle im 16
Bit-Modus.

Direkter Zugriff

Nun aber zu dem versprochenen weiteren Feature des Prozessors in der
SuperCPU. Denn der Stack ist nicht das einzige, was sich beliebig woanders
hinlegen laesst. Auch die Zeropage kann verschoben werden! Sie ist nicht mehr
auf Page 0 im Speicher festgelegt, deshalb wird sie beim 65816 auch haeufig
"Direct Page" genannt. Denn man kann direkt - naemlich ohne Angabe eines
Highbytes - auf diese zugreifen. Zeropage-Adressen waren und sind ja gerade
so beliebt, weil man auf diese Speicherzellen sehr schnell und effizient
zugreifen kann. Schliesslich ist durch das Wegfallen des Highbytes nicht nur
der Code kuerzer, sondern vor allem auch schneller! Deshalb nimmt man
Zeropage-Adressen vor allem fuer Werte, auf die man haeufig und schnell
zugreifen will. Doch nun kann man den gesamten Speicher wie die Zeropage
ansprechen - natuerlich immer nur einen Block auf einmal - aber jeden, den
man gerade will! Es gibt naemlich das sogenannte "Direct Page Register".
Dieses enthaelt defaultmaessig (und im Emulation Mode sowieso) natuerlich den
Wert $0000. Aber das koennen wir aendern: Der Befehl TCD transferiert den
Inhalt des 16 Bit-Akkus in das Direct Page Register. Laden wir also den Akku
mit dem Wert #$1000 und fuehren einen TCD aus, liegt unsere "Zeropage"
(besser: Direct Page) nun ab $1000! Ein LDA $50 bezieht sich nun auf die
Adresse $1050. An die Adresse $0050 kommen wir natuerlich nach wie vor heran:
Entweder wir schalten wieder zurueck, indem wir eine #$0000 in den Akku holen
und mittels TCD in das Direct Page Register transferieren, oder wir greifen
z.B. mittels LDA $0050 darauf zu. Zum TCD gibt es auch ein Gegenstueck, den
TDC. Uebrigens steht das "C" hier wieder fuer die Tatsache, dass auf jeden
Fall immer 16 Bit transferiert werden, wie es oben schon bei TCS/TSC
beschrieben ist. Man kann auch voellig ohne auf 16 Bit hochzuschalten die
Zeropage verlegen: Hier hilft uns der PLD-Befehl (Pull Direct Page Register
from Stack). Packen wir also zunaechst das Highbyte, also $10, auf den Stack
(mit PHA), dann genauso das Lowbyte ($00). Danach ein PLD - dieser holt auf
jeden Fall 16 Bit, also zwei Byte, vom Stack in das Direct Page Register,
egal, ob das M-Flag gerade auf 8 oder 16 Bit gestellt ist. Umgekehrt kann man
den Wert dieses Registers mit dem PHD auf den Stack bringen, um ihn von dort
in ein beliebiges Register zu holen. Uebrigens lassen sich natuerlich auch
Werte in das Direct Page Register packen, bei denen das Lowbyte nicht null
ist (der Prozessor muss dann aber immer erst die Adresse berechnen, was den
Geschwindigkeitsvorteil wieder zunichte macht).

Wahnsinns-Speed

Durch die Eigenschaft der beliebig verlegbaren Zeropage (Direct
Page) eroeffnen sich ungeahnte Moeglichkeiten. Nicht nur, dass man seinem
Sound endlich eine eigene Zeropage zur Verfuegung stellen kann (der wird
nichteinmal etwas davon merken!), es ergeben sich noch weitere Vorteile. So
kann man auf jeden Speicherbereich, auf den man in einer bestimmten Routine
am haeufigsten zugreift, mit der Zeropage-Adressierung (Direct
Page-Adressierung) zugreifen und somit einige Geschwindigkeitsvorteile
verbuchen. So koennte man z.B. in einer Bitmap einen Bereich nach dem anderen
zur Zeropage machen und somit schneller darauf zugreifen. Dasselbe gilt fuer
Sounds - eine Musik, mit einer entsprechend programmierten Routine
abgespielt, koennte mit der SuperCPU weniger als eine Rasterzeile
verbrauchen. In Listing 4.4 und Listing 4.5 sehen Sie, wie das Direct Page Register
funktioniert.

Uns ist kein Computer bekannt, dessen Prozessor eine aehnliche Moeglichkeiten
bietet - schon gar nicht im Windows-PC-Bereich, wo eine extrem aufwendige
Hardware aus diversen Segmentzeigern und Offsets die Adressen ermitteln muss.
Im naechsten Kursteil geht es weiter - wir sind noch lange nicht an den
Grenzen des 65816 angelangt!


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

[ Zum 3. Teil ][ Zum Index ][ Zum 5. Teil ]