Assemblerkurs Teil 2

In diesem Teil des Assemblerkurses wollen wir die Adressierungsarten abschließen. Daran anschließend werden wir uns dem Befehlssatz des 68000 zuwenden.

3.) Absolute Adressierung

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.

4.) Adressregister indirekt (ARI)

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.

5.) Programmzählerrelative Adressierung (PCR)

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.

5.2) PCR mit Index und Adressdistanz

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.

Der Befehlssatz des 68000

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

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.

JMP und BRA

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.

Bcc und DBcc

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

Die Wirkungsweise

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.

JSR und BSR

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, RTE und RTR

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.

TRAP

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.

Das Programm

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


Aus: ST-Computer 01 / 1987, Seite 92

Links

Copyright-Bestimmungen: siehe Über diese Seite