In diesem Teil des Assemblerkurses wollen wir die Adressierungsarten abschließen. Daran anschließend werden wir uns dem Befehlssatz des 68000 zuwenden.
Mit dieser Adressierungsart hat man die Möglichkeit, direkt auf den Speicher zuzugreifen. Dazu existieren zwei Versionen:
Diese beiden Adressierungsarten unterscheiden sich nur in ihrer Länge und Geschwindigkeit. Außerdem ist bei der Adressierungsart die Größe des Adressbereiches auf 64 K-Byte beschränkt.
3.1) Absolut kurz
Die Schreibweise ist $XXXX oder $FFYYYY, wobei XXXX oder FFYYYY im Bereich von -32768 und +32767 liegt. Dies bedeutet, daß sich nur zwei Teile des Speichers ansprechen lassen. Der eine Teil liegt im unteren Teil des Speichers (0000 - 7FFF) und der andere im oberen (FF8000-FFFFFF). Jeder Teil ist jeweils 32 K-Byte groß. Der Adressraum zwischen diesen beiden Teilen kann nicht angesprochen werden.
Beispiel:
NEG.B $1000
vorher nachher
1000 12 EE
MOVE.W $1000,52000
vorher nachher
1000 5678 5678
2000 XXXX 5678
In dem ersten Beispiel wird der Inhalt der Speicherstelle $1000 negiert (Zweierkomplement). Wenn bei dieser Adressierungsart Byteverarbeitung vorliegt, so können auch ungerade Adressen angesprochen werden. Im zweiten Beispiel werden die Speicherstellen $1000 und $1001 nach $2000 und $2001 umgespeichert. Ein Zugriff auf eine ungerade Adresse mittels Wortverarbeitung, wie z. B. MOVE.W $1001,$2001, ist hier nicht möglich.
3.2) Absolut lang
Die Schreibweise ist $XXXXXXXX
Diese Adressierungsart erlaubt einem den Zugriff auf den gesamten Speicher. Allerdings ist die Schreibweise mit 32 Bit nicht erforderlich, da der 68000 nur 24 Adressierungen besitzt. Diese Schreibweise würde einen Adressraum von 2 hoch 32, gleich 4 294 967 295, gleich 4 Gigabyte erlauben. Der wahre Adressraum beträgt aber nur 16 Megabyte. Ein weiterer Unterschied zu der kurzen Adressierung ist die Geschwindigkeit. Da der Prozessor einmal mehr auf den Speicher beim Lesen der Adresse zugreifen muß, ist diese Adressierungsart langsamer. Auch hier gilt: Zugriff auf ungerade Adressen nur mittels Byteverarbeitung.
Um die Effizienz in einigen Softwarebereichen zu erhöhen, wurde diese Adressierungsart mit verschiedenen Abwandlungen realisiert. Bei diesen Adressierungsarten enthält ein Adressregister die Adresse der Daten.
Die Gruppe dieser Adressierungsart:
4.1) Adressregister indirekt
Die Schreibweise ist (An)
Mit der geklammerten Schreibweise ist immer der Inhalt der adressierten Adresse gemeint. Damit hat man den Zugriff auf den ganzen Speicher mit den Adressregistern.
Beispiel:
MOVE.W (A1),D3
vorher nachher
A1 00001000 00001000
D3 xxxxxxxx XXXX1234
1000 1234 1234
Bringt den Inhalt der Adresse, die im Adressregister A1 steht, in das Datenregister D3. Das bedeutet, weil in A1 $1000 steht, wird der Inhalt der Adresse $1000 nach D3 gebracht. Der Inhalt von A1, $1000, und dem höherwertigen Wort von D3 bleiben unverändert.
Die Anwendung dieser Adressierungsart liegt z. B. in der Abarbeitung von Sprungtabellen mittels eines JMP (An).
4.2) ARI mit Postinkrement
Die Schreibweise ist (An) +
Die Wirkung dieser Adressierungsart ist im Prinzip genauso wie bei der Adressregister indirekt. Nachdem die Adresse über das Adressregister An angesprochen wurde, wird diese erhöht. Um wieviel es erhöht wird, hängt von der Verarbeitung ab. Ist es Byteverarbeitung, so wird es um 1 erhöht. Wortverarbeitung ergibt eine Erhöhung um 2 und Langwortverarbeitung eine um 4.
Beispiel:
MOVE.W #$1234,(A3)+
vorher nachher
A3 000010000 00001002
1000 XXXX 1234
Dieses Beispiel zeigt das Abspeichern einer Konstanten nach einer Adresse, die im Adressregister A3 angegeben ist. Anschließend wird dieses um 2 erhöht, da Wortverarbeitung vorliegt.
Eine Anwendung findet diese Adressierungsart zur Nachbildung von Stackpointern, zum Abarbeiten von Tabellen und um Daten zu verschieben (Blockmoves).
4.3) ARI mit Predekrement
Die Schreibweise ist -(An)
Wenn man die letzten zwei Beispiele verstanden hat, so werden Sie anhand dieser Schreibweise schon die Funktion erkennen.
Beispiel:
MOVE.L D7,-(A5)
vorher nachher
A5 00001004 00001000
D7 12345678 12345678
1000 XXXX 1234
1002 XXXX 5678
Um Ihnen hier noch einmal die Darstellung des Speichers zu erläutern, habe ich ihn in gerade Adressen aufgeteilt. Jede dieser Adressen ist gemaßt der Datenbusbreite 16 Bit lang. Die höherwertigen 8 Bit sind in den geraden Adressen und die Niederwertigen in den Ungeraden.
Als erstes wird bei einem ARI mit Predekrement das entsprechende Adressregister, um die Zahl der Bytes die verarbeitet werden sollen, erniedrigt. An dieser Adresse kann dann das Datenregister abgespeichert werden.
Auch hier gelten die gleichen Anwendungsgebiete wie unter 4.2 beschrieben. Hier an dieser Stelle möchte ich noch eine wichtige Anmerkung machen. Wird bei Byteverarbeitung das Register A7 bzw. SP angesprochen, so wird um 2 in- bzw. dekrementiert, um den SP auf geraden Adressen zu halten!
4.4) ARI mit Adressdistanz
Die Schreibweise ist d16(An)
Die Angabe dl6 steht für ein Displacement (Adressdistanz) von 16 Bit. Diese 16 Bit Zahl ist vorzeichenbehaftet (Zweierkomplement). Mit dieser Adressierungsart hat man die Möglichkeit, den Adresszeiger um einen konstanten Wert zu versetzen.
Beispiel:
MOVE.W $20(A2),$6000
A2 000040000 00004000
4020 8765 8765
6000 XXXX 8765
Adressrechnung:
Adressregister An $00004000
Displacement d16 $00000020
EA $00004020
Mit dieser Adressierungsart wird zum erstenmal eine etwas aufwendigere Adressrechnung notwendig. Wie Sie sehen, wird der Inhalt des Adressregisters, mit dem Displacement einfach addiert. Dies ergibt die Adresse, deren Inhalt nach $6000 gespeichert wird.
Die Leistungsfähigkeit einer solchen Adressierung zeigt sich in der Vielfalt der Anwendungsmöglichkeiten. Man ist damit in der Lage, z. B. einen Peripheriebaustein mit Werten aus einer Tabelle zu versorgen, wobei die Adressdistanz die verschiedensten Anwendungen ermöglicht. Oder man will, mittels eines Zeigers, auf zwei oder mehrere Tabellen zugreifen, usw...
4.5) ARI mit Index und Adressdistanz
Die Schreibweise ist d8(An,Rx.X)
Diesmal darf das Displacement nur 8 Bit lang sein. Auch diese Zahl ist vorzeichenbehaftet. Außerdem kommt noch ein beliebiges Register (Index) mit einer Angabe der Verarbeitungslänge hinzu. Der Index ist ebenfalls vorzeichenbehaftet.
Beispiel:
MOVE.L $F0(A6,D3.W),$3000
vorher nachher
A6 00004000 00004000
D3 15423008 15423008
6FF8 ABCD4321 ABCD4321
3000 XXXXXXXX ABCD4321
Adressrechnung:
Adressregister An $00004000
Index Rx.W $00003008
Displacement d8 $FFFFFFF0
EA $00006FF8
Bei dieser Adressierungsart ist die Adressrechnung noch etwas aufwendiger. Hier sehen Sie auch den Umgang mit vorzeichenbehafteten Zahlen. Rechnet man mit ihnen, so muß man sie in ihrer Stellenzahl auf die Bitbreite (32 Bit) des Ergebnisses erweitern. Es gelten die Regeln zur Addition mit Zweierkomplementzahlen.
Oft wird man diese Adressierungsart in der Tabellenbearbeitung einsetzen, mit der man variabel auf mehrere Tabellen zugreifen möchte. Dabei zeigt das Adressregister auf den Tabellenanfang, das Indexregister auf den Wert und das Displacement auf die entsprechende Tabelle. Das Displacement kann, wenn es nicht benötigt wird, in der Assemblerschreibweise weggelassen werden. Der Assembler setzt dann als Displacement Null ein.
Dies ist eine Adressierungsart, mit der der Atari ST-Anwender normalerweise nicht in Berührung kommt. Das Anwendungsgebiet dieser Adressierungsart ist ein System, daß ohne eine MMU arbeitet und das programmunabhängig von der Position im Speicher laufen soll. Diese Art der Programmierung nennt man PIC (Position-Independent-Code). Gute Assembler bieten die Möglichkeit, normale absolute Programme als programmzählerrelative Programme zu übersetzen. Der Anwender braucht sich dadurch keine Gedanken über die Lauffähigkeit zu machen.
Systeme oder Maschinen, die nicht die Voraussetzung lieferten, die Programme oder Programmodule einfach und schnell zu verwalten, benötigten vom Betriebssystem aus einen PIC-Programm. Diese Programme machten einem die Programmierung schwerer, aber die Verwaltung war schneller, da die Programme im Speicher nicht verschoben werden mußten.
Wie schreibt man PIC?
Da man den Adressbereich des Programmes nicht kennt, während man es schreibt, muß man auf die Verwendung von absoluten Adressen, die sich auf das Programm beziehen, verzichten. Sämtliche Sprungbefehle und Adressrechnungen müssen daher immer auf den Programmzähler bezogen sein.
5.1) PCR mit Adressdistanz
Die Schreibweise ist d16(PC)
Den Trick, der bei dieser Adressierungsart angewendet wird, will ich Ihnen an einem kleinen Beispiel erläutern. Nehmen wir einmal an, wir stünden in einer Straße vor der Hausnummer 10. Drei Häuser weiter, in der Hausnummer 16, wohnt ein Freund von uns. Will nun ein anderer Freund wissen, wo mein Freund wohnt, sage ich ihm „Hausnummer 16“. Mittlerweile aber haben die Leute von der Stadtverwaltung die Hausnummern ändern lassen. Daraus folgt, daß meine Freunde sich nicht treffen werden. Hätte ich aber gesagt: „Er wohnt drei Häuser weiter als ich“, so hätten sie sich gefunden, unabhängig von der Hausnummer.
Beispiel:
MOVE.W $0A(PC),D6
vorher nachher
PC 00003000 00003004
D6 XXXXXXXX XXXXFEDC
300C FEDC FEDC
Adressrechnung:
Programmcounter PC $00003000
Plus 2 2
Displacement d16 $0000000A
EA $0000300C
Das Displacement ist vorzeichenbehaftet. Dadurch ist es möglich, Daten vor und nach dem Befehl anzusprechen. Dieser Offset errechnet sich aus der Differenz zwischen dem PC+ 2 und der Adresse. Egal, in welchen Speicherbereich man das Programm verschiebt, die Differenz bleibt immer dieselbe.
Die Schreibweise ist d8(PC,Rx.X)
Ebenso wie in 4.5 und den Informationen aus 5 über die PCR-Programmierung, ist diese Adressierungsart zu erklären.
Beispiel:
MOVE.W $FE(PC,A5),D3
vorher nachher
PC 00005000 00003004
A5 006A0000 006A0000
D3 XXXXXXXX 00112233
6A5000 00112233 00112233
Adressrechnung:
Programmcounter PC $00005000
Plus 2 2
Index Rx.L $006A0000
Displacement d8 $FFFFFFFE
EA $006A5000
Auch hier findet eine Adressrechnung durch Addition mit dem Programmcounter+2, und dem Index und Displacement statt, die ja bei der Addition vorzeichenrichtig auf 32 Bit erweitert worden sind.
Nun haben wir alle Adressierungsarten besprochen und können uns jetzt den Befehlen zuwenden. Dies ist wieder ein recht großer Block.
Um die insgesamt 56 Befehle des 68000 zu beschreiben und zu erläutern, werden wir die Befehle erst einmal in bestimmte Kategorien zusammenfassen. Die Einteilung der Gruppen erfolgt aufgrund ihrer Funktion. Dadurch ergeben sich die folgenden sieben Gruppen:
In der Beschreibung der Befehle werde ich Ihnen die Funktion und Wirkungsweise, die Assemblerschreibweise, die Beeinflussung der Flags, die Größe der Operanden, die zugelasenen Adressierungsarten und die Anwendungsmöglichkeiten aufzeigen.
Einige Befehle will ich Ihnen außerhalb der Reihe schon vorab erklären, da sie für die Programme in diesem Kurs von Bedeutung sind.
Der Move Befehl ist wohl der am meisten und am vielseitigsten gebrauchte Befehl überhaupt. Er gehört zu der Gruppe der Datenübertragungsbefehle. Diesen Befehl hatte ich im Teil 1 schon angesprochen, nun folgt aber die ausführliche Beschreibung in allen Varianten. Zu diesen Varianten gehört:
MOVE
MOVEA
MOVEQ
MOVEM
MOVEP
MOVE to CCR
MOVE to SR
MOVE fr SR
MOVE USP
Sie sehen, dieser eine Befehl hat viele verschiedene Aufgaben, die in der Schreibweise deutlich gemacht wird.
Da ich den Befehl in seiner Funktion schon teilweise erklärt habe, werde ich die Beschreibung des Befehls nur noch zusammenfassen. Der MOVE-Befehl übernimmt alle Aufgaben der Datentransporte. Mit ihm lassen sich die Register laden, Daten vom Speicher holen oder dort speichern und Daten zwischen den Registern oder dem Speicher hin- und hertransportieren.-Der MOVEA-Befehl wird benutzt, wenn ein Adressregister das Ziel der Operation ist. Der MOVE-Befehl erlaubt fast alle Kombinationen der Adressierung, außer Adressregister direkt als Ziel. Dies ist in der exakten Schreibweise dem MOVEA-Befehl Vorbehalten.
Ebenso wie der MOVEA sind der MO-VEM und MOVEP spezielle Befehle. Der MOVEM (M = Memory) wird benutzt, um einen, mehrere oder alle Daten- und Adressregister des 68000 im Speicher abzulegen und wieder zu holen. Die Auswahl der Register erfolgt durch die Angabe des jeweiligen Registers, das durch ein / getrennt oder in einem Block durch einen - getrennt wird.
Beispiel:
MOVEM.W A7/A5/D3-D6,-(SP)
A7 00001100
10FE A7
10FD A5
10FB D6
10FA D5
10F8 D4
10F6 D3
Die Reihenfolge läßt sich nicht bestimmen. Sie ist immer D0-D7 und dann A0-A7. Die einzige Ausnahme ist die Adressierungsart ARI mit Predekrement als Ziel. So wird in unserem Beispiel erst A7, dann A5,D6,D5,D4 und D3 auf den Stack abgelegt. Genau in der anderen Reihenfolge.
Mit dem MOVEP-Befehl (P = Peripherie) kann man Daten zu oder von den alternierenden Adressen des Speichers oder der Peripherie gespeichert oder geholt werden. Der MOVEP-Befehl überträgt die Daten immer byteweise, dadurch ist es möglich, die 8-Bit Peripherie einfach zu versorgen. Die 8-Bit-Peripherie ist so organisiert, daß die Adressen der Peripherieregister im 16 Bit breiten Speicher immer um zwei differieren und somit immer an geraden oder ungeraden Adressen liegen. Der Befehl schreibt oder liest somit immer byteweise an den geraden oder ungeraden Adressen. Diese byteweise Übertragung läuft so, daß das höchstwertige Byte des Datenregisters zuerst übertragen wird, und das niedrigstwertige zuletzt.
Beispiel:
MOVEP.W D3,5(A3)
A3 005000
D3 1234
5004 XX12
5006 XX34
Hier wird das Datenregister D3 an die mit ARI und Displacement adressierten Adresse geschrieben. Dabei sieht man deutlich das Schreiben an ungeraden Adressen, wobei die geraden Adressen unverändert bleiben, ebenso wie das Adressregister A3.
Die nächste Gruppe des MOVE-Befehls dient dazu, das Statusregister (SR) bzw. das Condition-Code-Register (CCR) zu lesen oder zu schreiben. Ebenso benötigt man einen Befehl, um im Supervisormodus an den Userstackpointer (USP) zu gelangen. Der MOVE to SR und der MOVE USP Befehl sind privilegierte Befehle. Dies bedeutet, sie dürfen nur im Supervisor Modus angewendet werden. Verletzt man diese Regel, so führt dies zu einer Ausnahmebehandlung (Exception).
Der MOVE to CCR arbeitet nur mit Wortdaten, kann aber nur die untersten fünf Bit des CCR verändern. Er dient zur gezielten Vorbesetzung der Flags des CCR.
Beispiel:
MOVE.W #%11001,CCR
XNZVC (Flags)
Ebenso arbeitet der MOVE to SR Befehl, wobei er aber alle funktionalen Bits des SR verändern kann. Die restlichen, nicht benötigten Bits bleiben Null. In der lesenden Richtung arbeitet der MOVE from SR Befehl. Mit diesem Befehl kann ein Anwender den Status des Systems erfahren und entsprechend darauf reagieren.
Beispiel:
MOVE.W SR,D3
Es wird das Statusregister exakt nach D3 abgebildet, wobei nur das niederwertige Wort benutzt wird. Die restlichen oberen 16 Bits bleiben erhalten.
Mit dem MOVE USP kann der Anwenderstackpointer im Supervisormodus gerettet bzw. mit dem alten oder einem neuen Wert wieder an das User-Programm übergeben werden. Nun bleibt als letztes nur noch der MOVEQ Befehl (Q = Quick = schnell). Mit ihm lassen sich die Datenregister schnell mit einem Startwert von -128 bis +127 belegen. Der Befehl arbeitet immer mit einer 8-Bit-Zweierkomplementzahl und grundsätzlich wie bei der Langwortverarbeitung. D. h., daß das entsprechende Datenregister vorzeichenrichtig erweitert wird.
Nun folgt die Zusammenstellung der erlaubten Verarbeitungsbreite, der Flags, die beeinflußt werden, der erlaubten Adressierungsarten und der Assemblerschreibweise. Die Zeichen unter den Flags bedeuten:
Bei der Schreibweise der erlaubten Adressierungsarten, bedeutet:
ARI alle Adressregister indirekt
abs Absolut kurz und lang
PCR alle Programmcounter relativ
SR Statusregister
CCR Condition Code Register
USP User Stack Pointer
Um alle Adressierungsarten zu ermitteln, kann man jede Quelle mit jedem Ziel verknüpfen.
Die nächsten Befehle, die ich Ihnen nun vorstellen möchte, gehören zu der Gruppe der Programmsteuer-Befehle. Diese Gruppe ist eine sehr wichtige, da mit ihnen der Ablauf des Programms gesteuert werden kann. Zu ihnen gehören die Sprung- oder Verzweigungs-Befehle. Diese teilen sich wieder in bedingte und unbedingte Sprünge auf. Hierzu sind auch die Unterprogrammaufrufe und die Systemaufrufe hinzuzurechnen.
Diese beiden Befehle dienen zum unbedingten Verzweigen in Programmen (ähnlich dem Basic Befehl GOTO). Der Unterschied liegt in der Art des Sprunges. Der BRA (Branch) Befehl arbeitet mit einer 8 oder 16 Bit Adressdistanz (relativ). Dies bedeutet eine Sprungsweite von -128 bis +127 (8-Bit), oder -32767 bis 32768 (16-Bit) vom augenblicklichen Stand des Programmzählers. Der JMP (Jump) Befehl arbeitet absolut. D. h. es wird direkt an die Adresse, die über die EA adressiert worden ist, gesprungen. Man kann dadurch in dem gesamten Speicherraum herumspringen. Mittels der Adressierung der EA, wird der Befehl vielseitig. Es ist auch möglich, über die programmzählerrelative Programmierung, einen relativen Sprung zu machen.
Syntax Flags .X Quelle Ziel
XNZVC
MOVE.x (ea),(ea) -★★00 B,W,L Alle Dn,ARI,abs
MOVEA.x (ea),An ----- W,L Alle An
MOVEQ #Kons,Dn -★★00 B # Dn
MOVEM.x Rlist,(ea) ----- W,L Dn,An abs, ARI/(An) +
MOVEM.x (ea),Rlist ----- W,L abs,PCR,ARI/-(An) Dn,An
MOVEP.x Dn,d(An) ----- W,L Dn D(An)
MOVEP.x d(An),Dn ----- W,L d(An) Dn
MOVE (ea),CCR ★★★★★ W Alle/An CCR
MOVE (ea),SR ★★★★★ W Alle/An SR
MOVE SR,(ea) ----- W SR Dn,ARI,abs
MOVE USP,An ----- L USP An
MOVE An,USP ----- L An USP
Beispiel:
CLR.L D0
BRA Marke
Marke MOVE.W D0,D3
MOVEA.L Marke,A3
JMP (A3)
Marke MOVE.W D0,D3
Mit dem Branch-Befehl kann man das Programm an einer anderen Stelle fortsetzen. Ebenso wäre dies mit Jump möglich gewesen, solange man keinen PIC schreiben muß. Das zweite Beispiel zeigt einen Sprung über das Adressregister A3. Damit können Sie variabel in verschiedene Programmteile verzweigen, indem Sie das Register A3 mit der Adresse des Programmteils versorgen.
Das Gegenstück zu den unbedingten Verzweigungen sind die bedingten Verzweigungen. Die Verzweigung ist vom CCR abhängig. Welche Bedingung gültig ist, bestimmt folgende Tabelle. Aus dieser Tabelle brauchen Sie nur die entsprechende Mnemonik für cc einzusetzen.
Das mit dem ★ gekennzeichnete cc, F und T, gilt nur für den DBcc Befehl. Manche Assembler erlauben auch DBRA anstatt DBF. Der Unterschied zwischen BGT und BHI, liegt in der Abfrage der Flags. Die mit einem K am Ende der Zeile markierten Bedingungen bedeuten, daß eine Abfrage für die Zweierkomplementarithmetik vorgenommen wird.
Mnemonik | Bedeutung |
---|---|
CC | Carryflag = 0 |
CS | Carryflag = 1 |
EQ | Gleich (Z=1) |
F ★ | Falsch,Nie erfüllt |
GE | Größer oder gleich K |
GT | Größer K |
HI | Höher |
LO | Kleiner oder gleich K |
LS | Niedriger oder identisch |
LT | Kleiner K |
MI | Negativ |
NE | Ungleich |
PL | Positiv |
T ★ | Wahr, immer erfüllt |
VC | Kein Überlauf K |
VS | Überlauf K |
Kommt das Programm an einen Bcc Befehl, so wird zuerst die Bedingung überprüft. Ist diese wahr, so wird an die Stelle verzweigt, die hinter dem Befehl angegeben ist. Dies ist, wie beim BRA Befehl, eine 8- oder 16-Bit Adressdistanz im Zweierkomplement. Ist die Bedingung nicht erfüllt, so setzt das Programm seinen Verlauf mit dem nächsten Befehl fort.
Der DBcc Befehl prüft ebenfalls als erstes die Bedingung. Ist diese Bedingung erfüllt, so wird nicht wie beim Bcc Befehl verzeigt, sondern beim nächsten Befehl das Programm fortgesetzt. Ist die Bedingung nicht erfüllt, so wird das angegebene Datenregister um eins erniedrigt. Ist der Inhalt dieses Datenregisters gleich -1, so geht das Programm zum nächsten Befehl. Ansonsten wird das Programm an dem genannten Label fortgesetzt. Diese interessante Variante wird benutzt, um eine Schleife aufzubauen, die nur mit einer bestimmten Anzahl von Durchläufen abgearbeitet wird. Außerdem kann mit der Bedingung ein Abbruch aus der Schleife erzwungen werden.
Beispiel:
Marke Add.L #$FF,D3
.
.
.
CMP.L #$8000,D3
BLT Marke
.
Marke MOVE.W D3,D1
.
.
.
CMP.W #0,D1
DBEQ D3,Marke
In dem oberen Beispiel wird die Schleife so lange abgearbeitet, bis das Datenregister D3 noch kleiner als $8000 ist. Das untere Beispiel enthält als Abbruchbedingungen:
1.) D1 = 0
2.) D3 = - 1
Ist eine dieser Bedingungen erfüllt, so wird der nächste Befehl abgearbeitet. Bei dieser Konstruktion der Schleife muß man beachten, daß die Schleife einmal mehr als der Inhalt von D3 durchlaufen wird. Soll das nicht der Fall sein, so sollte man vor Eintritt in die Schleife das entsprechende Register um eins erniedrigen.
Diese beiden Befehle dienen dazu, Unterprogramme aufzurufen (BASIC = GOSUB). Die Ausführung der Befehle unterscheidet sich von den Befehlen JMP und BRA nur in einer winzigen Kleinigkeit. Bevor der Sprung zu der angegebenen Stelle erfolgt, wird die Adresse des nächsten Befehls auf dem Stack abgelegt und der Stackpointer um vier (Langwort) erniedrigt. Mit dieser Adresse ist dann eine Fortsetzung des Programms an dieser Adresse möglich. Dazu gibt es einen Befehl mit verschiedenen Varianten, der den Rücksprung bewirkt.
RTS (Return from Subroutine) beendet jedes Unterprogramm. Dieser Befehl holt sich vom Stack die abgelegte Adresse, erhöht diesen um vier und lädt den Programmzähler mit dieser Adresse. Dadurch wird das Programm an dieser Stelle vorgesetzt. Unterprogramme können beliebig ineinander geschachtelt werden. Benutzt man den Stack, um Daten abzulegen, so muß man ihn vor dem Rücksprung bereinigen, damit die richtige Adresse vom Stack geholt werden kann.
RTE (Return from Subroutine) beendet jedes Unterprogramm. Dieser Befehl holt sich vom Stack die abgelegte Adresse, erhöht diesen um vier und lädt den Programmzähler mit dieser Adrese. Dadurch wird das Programm an dieser Stelle vorgesetzt. Unterprogramme können beliebig ineinander geschachtelt werden. Benutzt man den Stack, um Daten abzulegen, so muß man ihn vor dem Rücksprung bereinigen, damit die richtige Adresse vom Stack geholt werden kann.
RTE (Return from Exception) ist ein privilegierter Befehl. Er dient zum Rücksprung aus Interrupt-, Trap- und Exceptionbehandlungen. Bevor der Rücksprung erfolgt, wird das Statusregister mit dem Wert geladen, der sich auf dem Stack befindet. Dieser muß also, am Anfang der Exception, als erstes auf den Stack gebracht werden.
Der RTR (Return mit Laden der Flags) Befehl funktioniert genauso wie der RTE Befehl. Allerdings wird nur das CCR behandelt. Da der RTR kein privilegierter Befehl ist, kann er auch als Rücksprung aus normalen Unterprogrammen benutzt werden. Auch hier muß das SR auf den Stack abgelegt werden. Trifft das Programm auf ein RTR, so wird nur das CCR wiederhergestellt.
Beispiel:
BSR Marke
.
.
.
Marke MOVEM.L D1-D3/A5,-(A7)
.
.
MOVEM.L (A7)+,D1 - D3/A5
RTS
JSR Marke
.
Marke MOVE SR,-(A7)
.
.
RTR
Hier in den ersten Beispielen sieht man den Umgang mit Unterprogrammen. Um die Arbeit etwas zu vereinfachen, kann man bei Unterprogrammen eine „Versorgung“ und eine „Entsorgung“ definieren. Zum Beispiel benötigt ein Unterprogramm als Versorgung die Adresse eines Puffers in A1, einige Register, mit denen man arbeitet, und als Entsorgung das Register D0, das anzeigt, ob die Operation erfolgreich verlaufen ist. Da die Regisdter zum Arbeiten auch im Hauptprogramm verändert sind, sollte man sie vorher abspeichern, und am Ende des Unterprogramms wieder laden, damit im Hauptprogramm die alten Werte wieder verfügbar sind. Sollen ebenfalls die Flags gerettet werden, so bietet sich die nächste Konstruktion an. Als erstes wird das SR auf den Stack gebracht, um danach freie Hand zu haben.
Durch diesen Befehl wird eine Excep-tionsbehandlung ausgelöst. Dieser Be-tehl bewirkt, daß der Inhalt des PC und des SR auf den Stack gerettet werden. Danach wird der PC mit der Adresse geladen, die durch eine Vektornummer spezifiziert worden ist. Dies ist eine Nummer zwischen 0 und 15. Damit sind der Vektor und auch die Adresse festgelegt, auf die der Vektor zeigt, an dem die Exception beginnt. Die Vektoren liegen an den Adressen $80 bis SBF. Dies sind die Vektoren $20 bis $2F.
Zum Beispiel verzweigt der TRAP #1 zu der Adresse, die im Speicher an Adresse $84 (Langwort) steht.
Nun kommen wir zu dem Programm dieser Ausgabe. Dieses Programm soll Ihnen einige grundlegende Arbeiten der Programmierung zeigen. Zu diesen Arbeiten gehört das Planen der Unterprogramme sowie deren Aufbau. Durch die Verwendung der Unterprogramme wird das Programm kürzer und übersichtlicher.
Das Programm soll zwei positive Dezimalzahlen addieren. Als erstes geben wir dazu eine Information aus (Einleitung). Dann soll die erste Zahl eingegeben werden. Dies machen wir, indem wir den Benutzer dazu auffordern. Ebenso geschieht dies mit der zweiten Zahl. Danach wird das Ergebnis, mit einem Text versehen, ausgegeben.
Die ersten beiden Texte auszugeben, dürfte Ihnen keine Schwierigkeiten bereiten. Danach muß man dem Benutzer die Eingabe ermöglichen. Dies erledigt eine Betriebssystemroutine (RLINE) für uns. Da wir diese Routine noch einmal benötigen, gestalten wir sie als Unterprogramm. Als Parameter benötigt diese Routine die Adresse eines Puffers. Der Puffer ist folgendermaßen aufgebaut: Das erste Byte des Puffers enthält vor Aufruf die maximale Anzahl der Zeichen, die eingelesen werden sollen. In dem zweiten Byte erhält man die tatsächliche Anzahl. Ab dem dritten Byte stehen die Zeichen. Die Eingabe wird durch die maximale Länge oder RETURN beendet, wobei RETURN nicht mit übergeben wird.
Da die Funktion RLINE Zeichen einliest, muß man noch überprüfen, ob nur Zahlen eingegeben worden sind. Die Umrechnung einer ASCII-Zahl in eine Binärzahl, ist recht einfach (ASCII-$30 = Bin). Die stellenrichtige Zusammenfassung der Binärziffern erfolgt mittels dem Hornerschema (Siehe Ausgabe 9 „So rechnen Computer“). Die Testroutine wird, da sie zweimal benötigt wird, ebenfalls als Unterprogramm ausgeführt. Sie muß, bei Auftreten eines Fehlers, dies entsprechend kenntlich machen. Erstens durch Ausgabe eines Textes, und zweitens in einem Register, damit das Programm darauf reagieren kann.
Die Addition der beiden Zahlen erfolgt binär, und zwar mit dem ADD Befehl. Die Umrechnung des Ergebnisses in Dezimalziffern geht in genau der umgekehrten Reihenfolge. Da der DIVU Befehl maximal ein 16 Bit Ergebnis liefert, muß, damit kein Fehler auftritt, der Puffer entsprechend lang sein. Daraus folgt: 16 Bit = 65 535; Multipliziert mit zehn gleich 655 350; dann durch zwei geteilt ergibt 327 675. Dies entspricht der größten Zahl für jede Eingabe. Da die Eingabe auf die Anzahl der Ziffern beschränkt wird, folgt daraus: 5 Ziffern. Um dieses Programm leistungsfähiger zu machen, müßte man eine bessere Umwandlungsroutine entwickeln oder vor der Umwandlung auf die größte Zahl testen.
Natürlich wäre es auch möglich gewesen, die Eingabe in einer Zeile, mit einem -(- Zeichen zu trennen.
Dieses Programm wurde mit dem ST-Assembler geschrieben.
Sven Schüler
Svntax Flags Marke,(ea),n
XNZVC
Bcc Marke ----- Offset 8 oder 16 Bit
DBcc
Dn,Marke ----- Offset 8 oder 16 Bit
BRA Marke ----- Offset 8 oder 16 Bit
BSR Marke ----- Offset 8 oder 16 Bit
JMP (ea) ----- (An),d(An),d(An,Rx),abs,PCR
JSR (ea) ----- (An),d(An),d(An,Rx),abs,PCR
RTE ★★★★★ wird gesetzt
RTR ★★★★★ wird gesetzt
RTS ------
TRAP ------ 0 bis 15
CD
; Additionsprogmm
; Definitionen
conin equ 1
pline equ 9
rline equ 10
; Programmstart
start ; Begrüßungstext ausgeben
move.l #btxt,-(a7)
move.w #pline,-(a7)
trap #1
addq.l #6,a7
einszahl ; zur Eingabe auffordern
move.l #bltxt,-(a7)
move.w #pline,-(a7)
trap #1
addq.l #6,a7
jsr eingabe ; maximal 5 Zeilchen einiesen
jsr test ; nur Zahlen?
cmp.1 #-1,d0 ; Fehler aufgetreten
beq einszahl ; noch mal zur Eingabe
jsr umrechnung ; richtig, dann umrechnen
move.l d0,d7 ; Zwischenspeichern
zweizahl ; Text ausgeben
move.l #b2txt,-(a7)
move.w #pline,-(a7)
trap #1
addq.l #6,a7
jsr eingabe ; maximal 5 Zeichen
jsr test ; nur Zahlen
cmp.l #-1,d0 ; Fehler aufgetreten?
beq zweizahl ; noch ein mal
jsr Umrechnung ; ansonsten umrechnen
add.l d0,d7 ; und dazuaddieren
move.l #austxt,-(a7) ; Ausgabetext ausgeben
move.w #pline,-(a7)
trap #1
addq.l #6,a7
jsr bindez ; d7 nach ASCII Dez und ausgeben
move.w #conin,-(a7) ; auf Taste warten
trap #1
addq.l #2,a7
clr.w -(a7) ; Ende des Hauptprogramms
trap #1
; Unterprogramme
eingabe ; feste Parameter
move.l #puffer,-(a7)
move.w #rline,-(a7)
trap #1
addq.l #6,a7
rts
test ; testet den Puffer auf unerlaubte Zeichen
; Ausgang: d0
movem.l a1/d1,-(a7) ; Register retten
move.b puffer+1,d1 ; Anzahl der tatsächlichen Zeichen
cmp.b #0,d1 ; Kein Zeichen?
beq fehler ; Fehlerbehandlung
move.l #puffer+2,a1 ; Adresse des ersten Zeichens
tloop
move.b (a1)+,d0 ; Zeichen holen
cmp.b #'0,d0 ; mit 0 vergleichen
blt fehler1 ; kleiner? dann Fehler
cmp.b #'9,d0 ; mit 9 vergleichen
bgt fehler1 ; größer? dann Fehler
sub.b #1,d1 ; nächstes Zeichen
bne tloop ; noch welche, ansonsten weiter
movem.l (a7)+,a1/d1 ; Register wieder laden
rts ; zurück
umrechnung
; Ausgang: d0
movem.l a1/d1/d2,-(a7) ; Register speichern
move.l #puffer+2,a1 ; Adresse des ersten Zeichens
move.b puffer+1,d1 ; Anzahl der Zeichen
clr d0 ; Anfangswert =0
uloop
mulu #10,d0 ; mit 10 multiplizieren
move.b (a1)+,d2 ; Zeichen holen
and.b #$0f,d2 ; oberes nibble ausblenden (-$30)
add.l d2,d0 ; aufaddieren
sub.b #1,d1 ; nächstes Zeichen
bne uloop ; noch? ansonsten weiter
movem.l (a7)+,a1/d1/d2 ; Register wieder laden
rts ; ende
fehler ; ftxt ausgeben
move.l #ftxt,-(a7)
move.w #pline,-(a7)
trap #1
addq.l #6,a7
bra aus
fehler1 ; f1txt ausgeben
move.l #f1txt,-(a7)
move.w #pline,-(a7)
trap #1,a7
addq.l #6,a7
aus
move.l #-1,d0 ; Fehler aufgetreten setzen
movem.l (a7)+,a1/d1 ; Register retten
rts ; ende
bindez ; bin nach dez mit Ausgabe
; Eingang: d7
movem.l a2/d7,-(a7) ; Register retten
movea.l #puffer+7,a2 ; Pufferadresse für letzten Zeichen
dloop
divu #10,d7 ; durch 10
swap d7 ; Rest ins Lowword
add.b #$30,d7 ; +$30 ASCII
move.b d7,-(a2) ; im Puffer ablegen
clr.w d7 ; Rest löschen
swap d7 ; wieder tauschen
cmp.w #0,d7 ; noch was da?
bne dloop ; dann weiter
ausgabe
move.l a2,-(a7) ; ab letztem Zeichen ausgeben
move.w #pline,-(a7)
trap #1
addq.l #6,a7
movem.l (a7)+,a2/d7 ; Register wieder laden
rts
.data ; Datenbereich
btxt
dc.b 27,"E",27,"e"
dc.b "Dieses Programm addiert zwei positive Zahlen miteinander"
dc.b 10,10,10,13,0
.even
b1txt
dc.b 10,13,"Bitte die erste Zahl",10,13,0
.even
b2txt
dc.b 10,13,"Bitte die Zweite Zahl",10,13.0
.even
ftxt
dc.b 10,13,"Bitte mindestens eine!!!",10,33,0
.even
f1txt
dc.b 10,13,"Bitte Zahlen!!!",10,13,0
.even
austxt
dc.b 10,13,"Das Ergebnis ist "
.even
puffer
dc.b 5,0
dc.b 0,0,0,0,0,0
.even
.BSS ; Ende des Datenbereichs