Assembler-Kurs Teil 8
Der Stapel
Der Stapel ist der Speicherbereich von
$0100 bis $01 FF, der direkt von der CPU
verwaltet wird. Der Zugriff auf diese
" Page 1" ist daher sehr schnell.
Ein Stapel wächst von oben nach unten, d. h. in unserem Fall, daß zuerst ein
Byte in $01 FF, das nächste in $01 FE usw.
abgelegt wird.
Bemerkenswert ist beim Stapel, daß er
nach dem LIFO-Prinzip ( last infirst
out) arbeitet. Das zuletzt auf den
Stapel gebrachte Byte muß also als
erstef wieder heruntergeholt werden, wenn die darunterliegenden Bytes gelesen
werden sollen.
Wenn Sie Schwierigkeiten haben, sich das
LIFO-Prinzip zu verdeutlichen, dann
denken Sie sich den Stapel doch einfach
als einen Stapel von Getränkekisten.
Dann wird Ihnen klar, daß Sie keinesfalls eine der unteren Kisten herausziehen können. Die zuletzt auf den
Stapel gebrachte Kiste, das ist die
Kiste ganz oben auf dem Stapel, muß als
erste entfernt werden, um an die
darunterliegende Kiste heranzukommen.
Aber woher weiß der Computer, welcher
Wert auf dem Stapel der oberste ist?
Dazu benötigen wir einen Zeiger
( Pointer), der auf die jeweilige Spitze
des Stapels zeigt, den sogenannten
Stapelzeiger ( Stackpointer) . Unser
Stapelzeiger ist 8 Bits breit, also
eigentlich um ein Byte zu klein für die
Adressierung des Stapelbereichs ($01 FF
bis $0100) . Sicher fällt Ihnen auf, daß
das höherwertige Byte ($01) über dem
gesamten Stapelbereich gleich bleibt. Es
ist daher unnötig, diesen Wert immer im
Stapelzeiger mit abzuspeichern. Stattdessen begnügt man sich damit, daß der
Stapelzeiger das niederwertige Byte der
Spitze des Stapels enthält.
Die effektive Adresse, auf die der
Zeiger deutet, kann man sich folglich
mit $0100+( Inhalt des Stapelzeigers) selbst errechnen.
Gültige Werte sind für den Stapelzeiger
zwischen $00 und $ FF.
So, jetzt wissen wir, wie ein Stapel
funktioniert, aber wozu man einen Stapel
benötigt, das wissen wir noch nicht.
Es gibt drei wichtige Anwendungsgebiete
für einen Stapel:
1 . Er übernimmt die Verwaltung der Rücksprungadressen bei Unterprogrammaufrufen. Diese Eigenschaft wird später
noch näher erläutert werden.
2 . Zur Zwischenspeicherung von Daten bei
Interrupts.
3 . Kurzzeitige Sicherung von Daten.
Diesen letzte Punkt wollen wir uns
nun genau ansehen.
Die Stapeloperationen
Im Grunde genommen benötigt man nur zwei
verschiedene Stapelbefehle, nämlich
einen, um etwas auf den Stapel zulegen
( PUSH) und einen, um einen Wert vom
Stapel zu holen ( PULL, POP) .
Stapel nach PUSH nach PUSH vorher: Wert1: Wert2: $01FD | | | | | |<-SP | | |--| |--| $01FE | | | |<-SP |W2| |--| |--| |--| $01FF | |<-SP |W1| |W1| ---- ---- ----
Wenn noch keine Stapeloperationen durchgeführt wurden, zeigt der Stapelzeiger
SP auf die Speicherstelle $01 FF. Nun
wird WERT1 auf den Stapel gelegt, und
zwar an der Adresse, auf die der
Stapelzeiger weist. Nach diesem Schreib- zugriff auf den Stapel wird der Stapelzeiger um 1 erniedrigt und zeigt so auf
die nächste freie Speicherstelle des
Stapels ($01 FE) . Eine weiters PUSH-Anweisung legt den WERT2 auf die Spitze
des Stapels ( Top of Stack), was der
Position des Stapelzeigers entspricht.
Anschließend erniedrigt sich der
Stapelzeiger wieder um 1 .
nach PULL Wert2 : nach PULL Wert1 :
$01FD | | | | | | |--| $01FE | |<-SP | | |--| |--| $01FF |W1| | |<-SP ---- ----
Während bei PUSH-Befehlen des Stapels zu
beobachten war, daß sich der Stapelzeiger erst nach dem Schreiben in die
Stapelspeicherstelle erniedrigt hat, stellt man nun fest, daß bei PULL- Anweisungen der Stapelzeiger vor dem
Lesezugriff inkrementiert wird. Das ist
auch nötig, da der Stapelzeiger immer
auf die nächste FREIE Speicherstelle des
Stapels zeigt. Wird der Stapelzeiger
zuvor inkrementiert, dann weist er nun
auf das zuletzt auf den Stapel geschobene Element. Dieser Wert kann nun
vom Stapel geholt werden.
Das obige Beispiel verdeutlicht, daß der
zuerst auf den Stapel gelegte Wert
( Wert1) erst als letzter vom Stapel
geholt werden kann.
Die PUSH-Befehle
PHA (PusH Accumulator)
Mit diesem Befehl wird der Akkuinhalt
auf den Stapel geschoben. Anschließend
wird der Stapelzeiger um 1 erniedrigt.
Der Akkuinhalt bleibt dabei, ebenso wie
die Flaggen, unverändert.
PHP (PusH Processor status)
Anstelle des Akkuninhaltes wird jetzt
das gesamte Statusregister des
Prozessors mit allen Flaggen auf den
Stapel gelegt.
Danach wird der Stapelzeiger um 1 erniedrigt.
Die PULL-Befehle
PLA (PuLl Accumulator)
Diese Anweisung ist das Gegenstück zu
PHA. Der Stapelzeiger wird zuerst um 1 erhöht. Nun wird der Inhalt der
Speicherstelle, auf die der Stapelzeiger
deutet, in den Akku eingelesen. Neben
dem Akkuinhalt können sich auch die
Flaggen N und Z ändern.
PLP (PuLl Processor status)
Der Stapelzeiger wird inkrementiert und der aktuelle Stapelwert wird in das
Prozessor-Statusregister übertragen.
Dabei ändern sich selbstverständlich
alle Flaggen.
Zusätzlich zu den Stapeloperationen gibt
es auch noch zwei Transferbefehle, die
sich direkt auf den Stapelzeiger
beziehen.
Die Stapel-Transferbefehle
TSX (Transfer Stackpointer to X)
Wie der Name der Anweisung schon aussagt, überträgt der Befehl den Stapelzeiger in das X-Register. Somit kann die
Adresse, auf die der Stapelzeiger weist, ermittelt werden. Das X-Register kann
dementsprechend Werte zwischen $00 und
$ FF enthalten.
TXS (Transfer X to Stackpointer)
Dieser gegenteilige Befehl zu TSX ermöglicht eine direkte Einflußnahme des
Programmierers auf den Stapelzeiger, indem der Wert des X-Registers in den
Stapelzeiger übertragen wird.
Alle oben genannten Befehle können nur
impliziert adressiert werden, d. h. sie
haben keinen Adressteil.
Der Einsatz des Stapels bei Unterprg's --------------------------------------
Bisher haben wir es als selbstverständlich erachtet, daß unser Assembler-Programm nach einem RTS bei der nächsten
Anweisung nach dem JSR im aufrufenden
Programm fortfährt.
Das Programm hat sich also die Stelle, von der aus der Unterprogrammsprung erfolgte, gemerkt. Womit wir wieder
beim Stapel wären.
Der Prozessor muß also vor jeder Verzweigung in ein Unterprogramm die momentane Adresse auf den Stapel
speichert, was der Adresse die
Instruktion nach dem JSR-Befehl entspricht. Diese Adresse muß nach einer
speziellen Regel " gestackt" werden, da
unser Stapel bekanntlich immer nur ein
Byte aufnehmen kann, eine Adresse jedoch
aus 2 Bytes besteht. Zuerst wird das
höherwertige Byte mit einem PUSH auf den
Stapel gelegt und anschließend der
Stapelzeiger um 1 vermindert. Erst jetzt
wird das niederwertige Byte auf den
Stapel gebracht. Erneut muß der Stapelzeiger dekrementiert werden.
Hauptprogramm Unterprogramm
----------- ----- | . | -> $C200 | . | | . | | | . | $C100 | JSR $C200 | --- | . | $C103 | . | <----------- | RTS | | . | -----
-----------
Der Stapel nach JSR $ C200 :
| |<-SP |---| $01FE |$03| |---| $01FF |$C1| -----
Nach einem RTS-Befehl läuft der umgekehrte Prozess ab. Zuerst wird der
Stapelzeiger um 1 erhöht und das niederwertige Byte vom Stapel geholt. Im
Anschluß daran holt man das höherwertige
Byte vom Stapel und der Stapelzeiger
wird wieder erhöht. nun kann die
aktuelle Adresse wieder zusammengesetzt
werden und das Hauptprogramm fährt mit
seiner Abarbeitung an der gewünschten
Stelle fort.
Es fällt uns auf, daß diese abgelegten
Rücksprung-Adressen denselben Stapelbereich belegen, den auch der Programmierer zur vorübergehenden Datenspeicherung benutzen kann. Da der Speicherbereich des Stapels begrenzt ist, müssen
wir auch die Struktur dieser automatisch
ablaufenden Prozesse der Unterprogramm-Verwaltung kennen.
( rt/ wk)