CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 1 1 Einführung Seit über zehn Jahren existieren Mikroprozessoren. Aber jeder Mikroprozessor besitzt seine eigene Assemblersprache, die sich oft erheblich von den anderen unterscheidet. Diese Tatsache ist insofern seltsam, da sich alle Mikroprozessoren - zumindest funktionell gesehen - hochgradig gleichen. Aber eine einheitliche Assemblersprache konnte bis vor wenigen Jahren schon allein deshalb nur schwer definiert werden, da mit jedem neuen Mikroprozessor gänzlich neue Möglichkeiten (Operationen, Adressierungen) angeboten wurden. Mit der 16/32 Bit Ära verlangsamte sich diese Entwicklung, da immer mehr feststand, was man von einem modernen Mikroprozessor an Operationen und Adressierungsmöglichkeiten erwartete. Daher unterscheiden sich die heutigen 16/32 Bit Mikroprozessoren nur noch geringfügig. Man stellt eine zunehmende Symmetrie der Adressierungsarten, Datenlängen und Datentypen innerhalb eines Befehls fest. Eine dieser universellen Assemblersprachen soll hier vorgestellt werden. Sie nennt sich CALM, das für Common Assembly Language for Microprocessors steht (deutsch: siehe Titel). 1.1 Was ist CALM ? CALM ist keine neue Programmiersprache, sondern eine andere Schreibweise der Befehle. Aufgrund langjähriger Erfahrung hat es sich gezeigt, dass die von CALM definierten Befehle und deren Schreibweise ausreichen, um rund 95% der Befehle eines Prozessors zu beschreiben. Das Problem ist an sich ganz einfach: Man (er)finde eine Schreib- weise, die einerseits präzise ausdrückt, was die Hardware tut, und andererseits nicht zu kompliziert wird. Diesen Anforderungen genügt CALM fast immer. Die Ausnahmen sind auf ausgefallene Operationen/ Adressierungsarten zurückzuführen, die keine eigene Schreibweise rechtfertigen und deshalb vereinfacht dargestellt werden. Aber diese Fälle können mittels einer Folge von Befehlen in der CALM-Schreibweise erklärt werden. Es bedarf also nicht einer weiteren Metasprache. 1.2 Vorteile einer einheitlichen Assemblersprache Die Assemblerprogrammierung ist heute relativ umständlich und zeit- raubend: Neben den erhöhten Anforderungen, die die Programmierung in Assembler mit sich bringt, kommen künstliche Schwierigkeiten hinzu: Jeder Hersteller benützt andere Begriffe und definiert eine eigene Schreibweise der Befehle. Diese unterschiedliche Terminologie verunsichert die Benutzer, verunmöglicht Prozessorvergleiche und erschwert unnötig Prozessorwechsel. CALM definiert eine einheitliche Syntax für Befehle und Pseudobefehle. Somit bildet CALM eine gemeinsame Grundlage für Ausbildung und Verständigung. Beispielsweise wird ein Assemblerprogramm auch für jene verständlich, die den Prozessor, für den die Befehle geschrieben wurden, nicht kennen. Ausserdem erlaubt eine einheitliche Schreibweise einen objektiven Vergleich von Mikroprozessoren. Vergleichstests sind effizienter und einfacher durchführbar, da das Erlernen der prozessorspezifischen Assemblersprache entfällt. Auch werden Prozessorwechsel vereinfacht, da sich die Schreibweise nicht ändert. Ausbildungskosten können spürbar gesenkt werden. Dies ist besonders bei "aufwärtskompatiblen" Mikroprozessoren interessant. Eine einheitliche Syntax vereinfacht zudem den Assembler. Man kann einen Assembler konstruieren, der aus einem prozessorunabhängigen Hauptteil und einem prozessorspezifischen Teil aufgebaut ist. Im Hauptteil sind folgende Funktionen zusammengefasst: Behandlung von Symbolen und Pseudobefehlen, Berechnung von Ausdrücken, Dateiverwaltung und allgemeine Funktionen zur Textanalyse. Im prozessorspezifischen Teil werden die Syntax und die Semantik eines Befehls, sowie die Regeln zur Objektgenerierung festgelegt. CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 2 1.3 Sinn der Assemblerprogrammierung Man kann sich natürlich fragen, ob die Assemblerprogrammierung auch heute noch zweckmässig ist. Immer leistungsfähigere Hardware und Software drängen die Assemblerprogrammierung ständig zurück. Aber die Assemblerprogrammierung bleibt in vielen Fällen unersetzbar, man denke nur an Hardwareschnittstellen, Optimisierungen aller Art, Mikrocomputer, mikroprogrammierbare Einheiten, usw. Allerdings übernehmen vermehrt Spezialisten diese Aufgaben und stellen auf der nächsthöheren Programmierebene einen Unterprogrammaufruf zur Verfügung. Aber wäre es gerade in solchen Fällen nicht von Vorteil, wenn man die wenigen Assemblerprogramme, die man noch schreiben müsste, in einer einheitlichen Assemblersprache programmieren könnte? 1.4 Entstehungsgeschichte von CALM CALM hat seine Existenz indirekt dem 8080 zu verdanken. Als Intel diesen Mikroprozessor 1974 herausbrachte, erschien vielen die zugehörige Assemblersprache zu primitiv. Zahlreiche Alternativlösungen schossen wie Pilze aus dem Boden. CALM wurde an der Eidgenössischen Technischen Hochschule in Lausanne (Schweiz) definiert und bewies seine Leistungsfähigkeit u.a. durch Konsistenz und Zweckmässigkeit im praktischen Einsatz. Insbesondere konnten auch andere 8 Bit Prozessoren zufriedenstellend mit CALM ausgedrückt werden. Mit der 16/32 Bit Ära wurde auch CALM erweitert und verbessert. Vor allem definierte CALM eine eindeutige Schreibweise für die Adressierungsarten und die Datenangaben. 1983 wurde in Zusammenarbeit mit DIN die hier beschriebene Version ausgearbeitet [1]. Unter den weltweiten Bemühungen, eine allgemeine Assemblersprache zu definieren, sei die IEEE P694 Arbeitsgruppe erwähnt. Sie begann ihre Arbeit 1977 und brachte 1984 die 18. Version heraus. Auch fand zeitweise ein reger Erfahrungsaustausch mit CALM statt. Aber P694 blieb zu stark auf 4 und 8 Bit Mikroprozessorarchitekturen fixiert und verkannte insbesondere die Probleme einer expliziten Schreibweise der Adressierungsarten. Ausserdem ist man noch immer der Auffassung, eine Assemblersprache müsse aus möglichst kurzen Namen bestehen und bei jedem Befehl sei nur das absolute Minimum anzugeben. 2 Befehle Ein Befehl führt eine in sich abgeschlossene, vom Hersteller des Mikroprozessors definierte Handlung aus. Der folgende Text zeigt, wie ein Befehl zerlegt wird, und wie die einzelnen Teile in CALM bezeichnet und welche Schreibweisen hierfür verwendet werden. 2.1 Aufbau eines Befehls Ein Befehl setzt sich aus einem Operationscode und Operanden zusammen. Hinzu kommen je nach Komplexität Bedingungscode, Adressen- und Datenangaben. Dieser modulare Aufbau eines Befehls und die Unabhängigkeit der Bedeutungen der einzelnen Bausteine erlaubt beliebige Erweiterungen. Dieses Konzept erfordert aber, dass auch bei Befehlen mit eingeschränkten Möglichkeiten alle Angaben vorhanden sein müssen. CALM definiert 49 Grundbefehle (Abb. 1). Mit diesen können rund 95% der Befehle eines Mikroprozessors beschrieben werden. Der Rest entfällt auf spezielle Befehle, bei denen der Operationscode des Herstellers übernommen wird, aber die CALM-Schreibweise für die Operanden, den Bedingungscode, die Adressen- und die Datenangaben eingesetzt wird. Der Befehl MOVE bestimmt auch die Reihenfolge Quelle -> Ziel (von links nach rechts). Diese Reihenfolge muss dann aber auch bei den anderen Befehlen eingehalten werden, wie beispielsweise beim Befehl SUB. CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 3 Transferbefehle MOVE Quelle,Ziel transferiert den Quell- in den Zieloperanden. PUSH Quelle stapeln (äquivalent zu MOVE Quelle,{-SP}). POP Ziel entstapeln (äquivalent zu MOVE {SP+},Ziel). CONV Quelle,Ziel führt eine Datentypumwandlung aus. CLR Ziel löscht den Zieloperanden (= MOVE #0,Ziel). SET Ziel setzt den Zieloperanden (= MOVE #-1,Ziel). EX Operand1,Operand2 tauscht die beiden Operanden aus. SWAP QuelleZiel tauscht die beiden Hälften des Operanden aus. Arithmetische Befehle ADD Quelle,QuelleZiel addiert zwei Operanden. Quelle1,Quelle2,Ziel ADDC wie bei ADD addiert 2 Operanden und das Übertragsbit C. SUB wie bei ADD subtrahiert den ersten Operanden vom zweiten. SUBC wie bei ADD subtrahiert den ersten Operanden und das Übertragsbit C vom zweiten Operanden. ACOC wie bei ADD wie SUBC, setzt aber Bedingungsbits anders. NEG QuelleZiel negiert den Quelloperanden (Zweier- Quelle,Ziel komplement oder Subtraktion von Null). NEGC wie bei NEG negiert mit Übertragsbit C, d.h. subtrahiert Quelloperanden und das Übertragsbit C von 0. MUL Quelle,QuelleZiel multipliziert die beiden Quelloperanden. Quelle1,Quelle2,Ziel Wird bei Ziel keine explizite Daten- länge angegeben, ist die Ziellänge unklar. DIV Divisor,DividendQuotient dividiert den zweiten Operanden Divisor,Dividend,Quotient durch den ersten. Divisor,Dividend,Quotient,Rest INC QuelleZiel erhöht den Operanden um Eins. DEC QuelleZiel vermindert den Operanden um Eins. COMP Quelle1,Quelle2 vergleicht den 2. Operanden mit dem ersten. = SUB, aber nur die Bedingungsbits ändern. CHECK Untergrenze,Obergrenze,Quelle prüft Wert gegen 2 Grenzen. Logische Befehle AND Quelle,QuelleZiel führt ein logisches UND der beiden Quell- Quelle1,Quelle2,Ziel operanden bitweise aus. OR wie bei AND führt ein logisches ODER bitweise aus. XOR wie bei AND logisches, ausschliessendes ODER. NOT QuelleZiel invertiert jedes Bit des Quelloperanden Quelle,Ziel (Einerkomplement). Verschiebebefehle SR QuelleZiel verschiebt den Quelloperanden nach rechts. Amplitude,QuelleZiel Das MSB wird durch 0, das Übertragsbit Amplitude,Quelle,Ziel C durch LSB ersetzt. ASR wie bei SR verschiebt den Quelloperanden mit Vorzeichen nach rechts. Das MSB bleibt und C = LSB. SL wie bei SR verschiebt Quelloperanden nach links. Das LSB wird durch Null, das Bit C durch MSB ersetzt. ASL wie bei SR verschiebt den Quelloperanden nach links. entspricht SL; V wird oft anders verändert. RR wie bei SR rotiert den Quelloperanden nach rechts. MSB wird durch LSB ersetzt (auch in C kopiert). RRC wie bei SR rotiert Quelle mit Übertragsbit nach rechts. MSB wird durch C, und C durch LSB ersetzt. RL wie bei SR wie bei RR, jedoch nach links. RLC wie bei SR wie bei RRC, jedoch nach links. Testbefehle TEST Quelle testet das Vorzeichen und den Wert (ob Null) Quelle:Bitadresse des Quelloperanden. TCLR QuelleZiel testet und löscht das (die) Operandenbit(s). QuelleZiel:Bitadresse Abb. 1. CALM-Befehle (Teil 1) CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 4 TSET wie bei TCLR testet und setzt das (die) Operandenbit(s). TNOT wie bei TCLR testet und invertiert das (die) Op.-Bit(s). Programmablaufbefehle JUMP Sprungadresse springt zur angegebenen Adresse. JUMP,b Sprungadresse springt zur angegebenen Adresse, wenn b wahr. DJ,b QuelleZiel,Sprungadresse vermindert den 1. Operanden um Eins und springt zur Adresse falls b wahr ist. SKIP,b überspringt den nächsten Befehl, wenn b wahr. CALL wie bei JUMP Unterprogrammaufruf (rettet Rückkehradresse CALL,b wie bei JUMP auf den Stapel und springt zur Adresse). RET Unterprogrammrückkehr (springt zu der vom RET,b Stapel geholten Adresse). TRAP Ausdruck spezieller Unterprogrammaufruf. TRAP,b WAIT wartet auf eine Unterbrechung. HALT hält den Prozessor an. RESET externe Peripheriebausteine zurücksetzen. NOP Nulloperation. ION IOFF erlaubt / sperrt Unterbrechungen. Spezielle Befehle besondere Befehle wie DAA, LINK, FFS, usw. Abb. 1. CALM-Befehle (Teil 2) 2.2 Operationscode Der Operationscode drückt die Operation aus, die ausgeführt wird. Der Operationscode ist ein Name, der von einem englischen Tätigkeitswort abgeleitet ist. In einigen Fällen werden auch Abkürzungen und deren Kombinationen verwendet. Im Allgemeinen finden sich die in CALM definierten Operationscodes auch in den Assemblersprachen der einzelnen Mikroprozessoren. Die einzigen Unterschiede betreffen solche Operationscodes, in deren Namen der Hersteller eine Adressierungsart vorwegnimmt (z.B.: BRANCH, LEA, XLAT, ADDI). Kleine Orthographieunterschiede sind hingegen häufig: COMP anstatt CMP, usw.). 2.3 Operanden Der Operand gibt an, wer (Register, Speicherzelle, usw.) an der Operation beteiligt ist und wie auf die Information zugegriffen wird. Der Operand adressiert diese Information unter Zuhilfenahme einer festgelegten Schreibweise. Diese Schreibweise muss ausreichen, um alle möglichen (sinnvollen) Adressierungen ausdrücken zu können. Unter Abschnitt 3 wird diese Schreibweise eingehend erläutert. Diese prozessorunabhängige Schreibweise ist bis heute einmalig. Man vergleiche dazu die Herstellerschreibweise der Adressierungen einiger Mikroprozessoren: Ein Mikroprozessor besitzt nur eine begrenzte Anzahl von Adressierungsmöglichkeiten. Dies führt oft zu einer kurzen Schreibweise, die zwar die Arbeit des Assemblers vereinfacht, aber ein Adressierungskonzept wird nicht angeboten. Eine präzise Schreibweise ist auch deshalb notwendig, weil man nicht mehr die Art der Adressierung in den Operationscode miteinbeziehen kann. Auch sind die Zeiten, als der Akkumulator der Mittelpunkt eines Mikroprozessors war, endgültig vorbei. Immer leistungsfähigere Adressierungsmöglichkeiten bei bald allen Operationscodes benötigen eine präzise Angabe. 2.4 Bedingungscode Ein Bedingungscode drückt einen Bedingungsbitzustand oder eine Kombination von Bedingungsbitzuständen aus. Bedingungsbits werden durch Befehle verändert. Hängen Befehle ihrerseits von Bedingungsbits ab, wird ein Bedingungscode, getrennt durch ein Komma, an den Operationscode angehängt. Bedingungscodes sind Namen, die normalerweise aus 2 Buchstaben bestehen (Abb. 2). CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 5 Allgemein: EQ gleich NE ungleich BS Bit gesetzt BC Bit nicht gesetzt CS Übertrag gesetzt CC Übertrag nicht gesetzt VS Überlauf gesetzt VC Überlauf nicht gesetzt MI Minus PL Plus Nach vorzeichenlosem Vergleich: LO kleiner LS kleiner oder gleich HI grösser HS grösser oder gleich Nach arithmetischem Vergleich: LT kleiner als LE kleiner als oder gleich GT grösser als GE grösser als oder gleich Spezielle: PE Parität gerade PO Parität ungerade NV nie AL immer NMO nicht minus Eins Manche Bedingungscodes sind äquivalent: EQ=BC, NE=BS, CS=LO, CC=HS. Abb. 2. Bedingungscodes Bedingungsbits sind üblicherweise im Bedingungsregister F gespeichert (Abb. 3). Weitere Bedingungsbits (I, S, T) können existieren und sind vielfach in einem speziellen Zustandsregister S gespeichert. Buchstabe/Funktion Verwendung C Übertrag Binäraddierer und Binärverschiebung H Halb-Übertrag 4 Bit Übertrag (nur bei 8 Bit Prozessoren) L Verbindung nur, falls nie als Übertrag eingesetzt N Vorzeichen gesetzt: MSB gesetzt (negative Zahl) V Überlauf gesetzt: Überlauf bei arithmetischen Zahlen Z Null gesetzt: Ergebnis ist gleich null I Unterbrechung gesetzt: Unterbrechungen erlaubt S Überwachung gesetzt: Überwachungszustand (Supervisor) T Ablaufverfolger gesetzt: Ablaufverfolgerzustand (Trace) Abb. 3. Bedingungs- und Zustandsregisterbits Die Namen der Bedingungscodes sind mit denen der Hersteller häufig identisch. Einzig die Art und Weise, wie sie an den Operationscode angehängt werden, unterscheidet die beiden Schreibweisen: Bei den Herstellern wird der Bedingungscode direkt an den Operationscodenamen (oft nur ein Buchstabe) angehängt. Für die CALM-Schreibweise spricht, dass ein beliebiger Operationscode mit einem Bedingungscode verknüpft werden kann und somit die beiden Informationen eindeutig voneinander getrennt werden können (Abb. 4). JUMP,NE Sprungadresse RET,LO CALL,CS Sprungadresse DJ.16,NE CX,Sprungadresse SKIP,LO DJ.16,NMO D0,Sprungadresse Abb. 4. Beispiele mit Bedingungscodes 2.5 Datenangaben Datenangaben wurden erst mit den 16/32 Bit Prozessoren erforderlich Bei diesen Prozessoren muss man Datenlänge und Typ angeben (Abb. 5). U oder nichts vorzeichenlos oder nicht näher spezifiziert A arithmetisch (Zweierkomplementdarstellung) D dezimal (vorzeichenlos, 2 Ziffern pro Byte: BCD) F Gleitpunkt (IEEE 754 Format) O Abstand (um 2n-1 erhöht) S vorzeichenbehaftet (Vorzeichen und Betrag) CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 6 --- -------- --->( . )------------------------------------------->[ Zahl ]---> --- | | | | | | ^ -------- --- --- --- --- --- --- | ( U ) ( A ) ( D ) ( F ) ( O ) ( S ) | --- --- --- --- --- --- | | | | | | | | `-----------------------------------' Abb. 5. Datenangaben Die Datenlänge gibt an, wieviele Bits an einer Operation beteiligt sind. Diese Angabe erfolgt je nach Befehl beim Operationscode (Datenlänge gilt für alle folgenden Operanden) oder bei jedem Operanden individuell. Die Datenlänge wird direkt in Bits angegeben. Der Datentyp sagt etwas über die verarbeiteten Daten aus. Die Angabe besteht aus einem Buchstaben, der zwischen dem Punkt und der Datenlänge eingefügt wird. Erfolgt keine Datentypangabe, so handelt es sich um nicht speziell zu verarbeitende Daten. Daher ist eine Datentypangabe erforderlich, wenn eine bestimmte Einheit (z.B. BCD- oder FPU-Addierer) gemeint ist (Abb.6). MOVE.32 R6,R1 ; Trennzeichen: Punkt ADD.D32 R5,R0 CONV R1.A16,R2.F64 Abb. 6. Beispiele mit Datenangaben Auch die Herstellerschreibweise der Befehle kennt Datenangaben. Allerdings wird nur der Datenlänge ihre Unabhängigkeit eingeräumt. Die Datenlänge wird durch einen Buchstaben angegeben, der entweder direkt oder durch einen Punkt getrennt an den Operationscode angehängt wird. Die Verwendung von Buchstaben bleibt begrenzt: Einerseits kommen nicht nur die Datenlängen 8, 16 und 32 Bit vor und andererseits wird man sich kaum über deren Zuordnung einig. Den verschiedenen Datentypen entsprechen bei den Herstellern andere Operationscodes, z.B. IMUL für MUL.A16, ABCD für ADDX.D8. 2.6 Adressenangaben Adressenangaben sind mit zunehmenden Adressierungsmöglichkeiten immer notwendiger geworden. Eine Adresse wird vermehrt aus Unteradressen zusammengesetzt, die nicht die volle Adresslänge haben. Hier muss angegeben werden, welche Längen diese Unteradressen haben und wie sie auf die volle Adresslänge erweitert werden sollen. All diese Unterscheidungsmerkmale liefern die Adressenangaben. Der Programmierer kann auf dem Papier nachvollziehen, was im Prozessor geschieht, d.h. wie eine Adresse gebildet wird. Er kann somit auch dem Assembler genau angeben, welche Adressierung zu wählen ist. U, nichts vorzeichenlos, Nullerweiterung A arithmetisch, Vorzeichenerweiterung R rel., kodierter Wert vorzeichenerweitert zum PC addiert -------- --- -------->--------->[ Zahl ]------->( ^ )---> | ^ -------- ^ --- | --- | | |->( A )-------------------' | --- | |->( R )-| | --- | `->( U )-' --- Abb. 7. Adressenangaben CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 7 Eine Adressenangabe enthält eine Adresslänge und einen Adresstyp (Abb. 7). Die Adresslänge gibt die Anzahl Bits an, die eine Unteradresse beisteuert. Die Adresslänge wird ebenfalls in Bits angegeben. Als Trennzeichen dient ein Zirkumflex. Eine Adressenangabe steht vor einer Unteradresse im Operanden. JUMP 32^Sprungadresse CALL A16^Sprungadresse JUMP,NE R8^Sprungadresse MOVE.32 32^{A0}+A16^{D0}+A8^Abstand,D1 MOVE.16 32^{A5}+R8^Marke,D4 Abb. 8. Beispiele mit Adressenangaben Der Adresstyp gibt an, wie die Adresse interpretiert wird. Er unterscheidet absolute und relative Adressierung. Steuert eine Adresse nicht die volle Adresslänge bei, wird die Art der Erweiterung auf die volle Adresslänge angegeben. Bei der absoluten Adressierung erfolgt dies mit oder ohne Vorzeichen. Bei der relativen Adressierung wird der Befehlszähler zum vorzeichenerweiterten Abstandswert hinzuaddiert. Adressenangaben kommen ebenfalls in den Assemblersprachen der Hersteller vor. Allerdings sind sie durch uneinheitliche Syntax nicht als solche erkennbar. Unterschiedliche Adresslängen werden oft durch Prä- oder Postfixe angegeben (z.B.: LBRA, BRA.S, disp(A0,D3.L), BNE LABEL:W). Adresstypen werden durch Operationscodes oder spezielle Zeichen unterschieden (z.B.: BSR, LABEL(PC), @LABEL). Wobei erfreulicherweise nur noch bei Sprungbefehlen zwei unterschiedliche Operationscodes (BRANCH und JUMP) zur relativen und absoluten Adressierung verwendet werden. Diese Verteilung der Interpretierung eines Befehls auf verschiedene Bausteine eines Befehls kompliziert und verunmöglicht im Endeffekt Erweiterungen. 3 Schreibweise der Adressierung Aus dem vorangegangenen Text wird deutlich, dass klare Regeln formuliert werden müssen, um die Art der Adressierung eindeutig angeben zu können. Hierzu hat der Programmierer ein vereinfachtes Prozessormodell vor Augen, das im wesentlichen die Registerstruktur und die allgemeine Speicherzellenarchitektur beinhaltet. 3.1 Register und Speicherzellen Register und Speicherzellen bestehen aus einer Anzahl von Bits. Bei Mikroprozessoren sind Vielfache von 8 Bit (= 1 Byte) üblich. So ergeben sich Registerlängen von 8, 16 und 32 Bits. Aufeinanderfolgende Speicherzellen sind numeriert, deren Nummer man Adresse nennt. Wird ein 16 Bitwort in zwei aufeinanderfolgende Speicherzellen abgelegt, existieren je nach Prozessor 2 unterschiedliche Bytefolgen (Abb. 9). ---------- ---------- [ Byte ] 0 [ Byte ] 0 .... .... Bytefolge: [ ] Bytefolge: [ ] ---------- ---------- niederwertiges [ 16 ] n höherwertiges [ 16 ] n - Bit - - Bit - höherwertiges [ Wort ] n+1 niederwertiges [ Wort ] n+1 ---------- ---------- Abb. 9. Unterschiedliche Bytefolge Wenn man zudem eine Bytefolge bitadressiert, erweist sich die Bytefolge mit höherwertigem Byte zuerst als nachteilig, da die Bits unterschiedlich numeriert werden. Zudem ist die Bitnumerierung innerhalb eines Registers und im Speicher ebenfalls nicht dieselbe. CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 8 3.2 Adressräume Register und Speicherzellen kann man als Teil eines Register- oder Speicheradressraums ansehen. Der Registeradressraum besteht oft nur aus wenigen Speicherzellen, aber ist direkt in den Mikroprozessor eingebaut und weist daher eine kleine Zugriffszeit auf. Aufgrund der geringen Anzahl und ihrer technischen Sonderstellung spricht man nicht von Speicherzellen sondern von Registern. Ausserdem erhält jedes Register einen reservierten Namen. Wenn möglich, werden die Registernamen der Hersteller übernommen. Zusätzlich sind nur die in Abb. 10 aufgeführten Symbole in CALM reserviert. PC Befehlszähler zur Programmausführung (Program Counter) SP Stapelzeiger (Stack Pointer) F arithmetisches Bedingungsregister (Flags) S Zustandsregister (System, Status) APC Wert des Befehlszählers, Beginn des Befehls in dem APC steht TRUE -1 (alle Bits auf 1) FALSE 0 (alle Bits auf 0) Abb. 10. Reservierte und vordefinierte Symbole Der Speicheradressraum ist dagegen sehr gross und extern aufgebaut. Speicherzellen sind numeriert und erhalten anwenderdefinierte Symbole. Bei einigen Mikroprozessoren exisitieren weitere Adressräume: Ein-/ Ausgabe und Daten. Um diese Adressräume vom Speicheradressraum unter- scheiden zu können, wird ein Dollarzeichen beim Ein-/Ausgabeadress- raum und ein Prozentzeichen beim Datenadressraum benutzt (Abb. 11). MOVE B,A Register -> Register MOVE ADSPEICHER,A Speicheradressraum -> Register MOVE $ADEINGABE,A Ein-/Ausgabeadressraum -> Register MOVE %ADDATEN,A Datenadressraum -> Register Abb. 11. Beispiele: Adressräume 3.3 Direkte Adressierung Register und Speicherzellen bestehen, wie bereits erwähnt, aus einer Anzahl von Bits. Diese Bitzustände stellen ein Binärwort dar, das den Inhalt des Registers oder der Speicherzelle bezeichnet. Allerdings ist es in Assemblersprachen üblich, nur den Namen eines Inhalts anzugeben, obwohl man sich auf dessen Inhalt bezieht. Man sagt: "Erhöhe D", anstatt: "Erhöhe den Inhalt von Register D". Diese implizite Bezugnahme gilt in CALM immer und für alle vier erwähnten Adressräume. Es wird direkt der reservierte Name des Registers oder die Adresse angegeben (direkte Adressierung, Abb 12). ADD B,A Inhalt(B) + Inhalt(A) -> Inhalt(A) ADD ADSPEICHER,A Inhalt(ADSPEICHER) + Inhalt(A) -> Inhalt(A) ADD 16'1234,A Inhalt(16'1234) + Inhalt(A) -> Inhalt(A) Abb. 12. Beispiele: direkte Adressierung 3.4 Unmittelbare Adressierung Ein Nummernzeichen vor einer Konstanten, einem Symbol oder einem komplexen Ausdruck hebt die implizite Bezugnahme auf und bezeichnet die unmittelbar gebildete Adresse (und nicht deren Inhalt). Dies nennt man unmittelbare Adressierung (Abb. 13). ADD #16'1234,A 16'1234 + Inhalt(A) -> Inhalt(A) ADD #ADSPEICHER+1,A ADSPEICHER + 1 + Inhalt(A) -> Inhalt(A) JUMP ADSPEICHER ADSPEICHER -> Inhalt(PC) Abb. 13. Beispiele: unmittelbare Adressierung CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 9 3.5 Indirekte Adressierung Will man den Inhalt einer beliebigen Adresse erneut als Adresse verwenden, umgeben geschweifte Klammern den betreffenden Adressausdruck. Dies nennt man indirekte Adressierung (Abb. 14). ADD {HL},A Inhalt(Inhalt(HL)) + Inhalt(A) -> Inhalt(A) MOVE #16'1234,{HL} 16'1234 -> Inhalt(Inhalt(HL)) MOVE #16'1234,{ADSPEICHER} 16'1234 -> Inhalt(Inhalt(ADSPEICHER)) Abb. 14. Beispiele: indirekte Adressierung Es besteht kein funktioneller Unterschied, ob zur indirekten Adressierung ein Register oder eine Speicherzelle verwendet wird. Viele Prozessoren erlauben aber nur eine indirekte Adressierung mit Registern. 3.6 Kombinierte Adressierungen Mit der direkten, unmittelbaren und indirekten Adressierung haben wir bereits die Grundbausteine für beliebig komplexe Adressierungen definiert. Die möglichen Operationen zwischen den einzelnen Adresstermen sind Addition, Subtraktion und Multiplikation (Abb. 15). MOVE #16'FE,{A0}+8 254 -> Inhalt(Inhalt(A0)+8) MOVE #10'98,{A0}-8 98 -> Inhalt(Inhalt(A0)-8) MOVE #8'177,{A0}*8 127 -> Inhalt(Inhalt(A0)*8) MOVE #2'1011,{A0}* 11 -> Inhalt(Inhalt(A0)*Datenlänge) MOVE R0,{{SB}+5}+3 Inhalt(R0) -> Inhalt(Inhalt(Inhalt(SB)+5)+3) MOVE {A0}+{D0}+10,A1 Inhalt(Inhalt(A0)+Inhalt(D0)+10) -> Inhalt(A1) MOVE #{A0}+{D0}+10,A1 Inhalt(A0)+Inhalt(D0)+10 -> Inhalt(A1) Abb. 15. Beispiele: kombinierte Adressierungen Beachten Sie, dass die indirekte Adressierung nur eine explizite Schreibweise der direkten Adressierung ist, aber sich beliebig steigern lässt. Die implizite Bezugnahme (= Inhalt einer gebildeten Adresse) wird erst beim ganzen Adressausdruck angewendet. Wenn ein Nummernzeichen vor dem ganzen Adressausdruck steht, wird diese implizite Bezugnahme aufgehoben. 3.7 Spezielle Adressierungen Die relative Adressierung ist eigentlich nur eine kürzere Schreibweise einer kombinierten Adressierung wie Abb. 16 verdeutlicht. Einige zusätzlichen Adressierungsarten haben eine gewisse Bedeutung erlangt und erhielten daher eine eigene Schreibweise. Bei der Bitadressierung gibt der erste Ausdruck die Byteadresse an. Der Ausdruck nach dem Doppelpunkt ist die Bitadresse. Die Bitadresse Null entspricht dem Bit 0 im adressierten Byte. Befindet sich die Byteadresse im Speicheradressraum, so kann die Bitadresse "byteüberschreitend" sein. Schreibweise Umschreibung Bezeichnung MOVE R^ADRESSE,D0 MOVE {PC}+ABSTAND,D0 rel. Adressierung MOVE {A0+},D0 MOVE {A0},D0 automatische ADD #Datenlänge,A0 Modifikation (+) MOVE D0,{-A0} SUB #Datenlänge,A0 automatische MOVE D0,{A0} Modifikation (-) CLR D0:#1 Bit 1 in D0 wird gelöscht Bitadressierung PUSH R1..R3|R5 PUSH R1 PUSH R2 Registerliste PUSH R3 PUSH R5 MOVE R0,R1:#1..#4 transferiert Bits 0 bis 3 von Bitliste R0 zu R1 (Bits 1 bis 4) Abb. 16. Beispiele: spezielle Adressierungen CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 10 4 Pseudobefehle Pseudobefehle sind Befehle an den Assembler, die die Codegenerierung, bedingte Assemblierung und Auflistung, Makros, usw. steuern. Jeder Pseudobefehl beginnt mit einem Punkt (Abb. 17). Nur bei den Pseudobefehlen .ASCII, .ASCIZ, .BLK, .FILL, .n und .STRING sind vorangehende Marken erlaubt. Die Bytefolge bei den Pseudobefehlen .16, .32, usw. bestimmt der Prozessor (.PROC). Programmauflistung .TITLE Zeichenfolge neue Seite mit Zeichenfolge (Überschrift). .CHAP Zeichenfolge neuer Untertitel in der Überschrift. .END beendet ein Assemblerprogramm. .TEXT bis zu .ENDTEXT werden alle folgenden Zeilen ignoriert, aber in die Auflistung kopiert. .ENDTEXT beendet .TEXT. .LIST Ausdruck listet die folgenden Anweisungen auf, wenn Ausdruck wahr. .LIST können geschaltet sein. .ENDLIST beendet einen bedingten Auflistungsabschnitt. .LAYOUT definiert die Programmauflistung. Assemblierung .BASE Ausdruck definiert die neue Zahlenbasis. .START Ausdruck definiert die Startadresse. .EXPORT Symbol1, Symbol2, ... definiert die exportierten Symbole. .IMPORT Symbol1, Symbol2, ... definiert die importierten Symbole. Befehlszähler .APC Ausdruck wählt einen der Assemblerbefehlszähler aus. .LOC Ausdruck weist einen neuen Wert dem aktuellen Assemblerbefehlszähler (APC) zu. .ALIGN Ausdruck richtet den Assemblerbefehlszähler (APC) auf das nächste Vielfache des Wertes aus. .EVEN richtet den APC auf nächsten geraden Wert. .ODD wie .EVEN, jedoch ungerader Wert. .BLK.8 Ausdruck (Anzahl) addiert das Produkt der Datenlänge mit .BLK.8.16.32 Ausdruck der Datenanzahl zum APC. Einfügen von Dateien .INS Dateiname fügt die angegebene Quelldatei ein. .REF Dateiname fügt die angegebene Symboltabelle ein. .PROC Dateiname fügt die Prozessorbeschreibung ein. Codegenerierung .n Ausdruck, Ausdruck, ... fügt den (die) angegebenen Wert(e) .8.32 Ausdruck8, Ausdruck32, Ausdruck8, ... in das Objekt ein. .FILL.n Ausdruck (Anzahl), Wert füllt die Anzahl mit Wert .ASCII "ascii Text" fügt die ASCII-Codes in das Objekt ein. .ASCIZ "ascii Text" wie .ASCII, mit ASCII-Code Null am Schluss. .STRING "ascii Text" wie .ASCII, mit der Länge (8 Bits) zu Beginn. Bedingte Assemblierung und Makros .IF Ausdruck die folgenden Anweisungen bis zum .ELSE oder .ENDIF werden assembliert: Ausdruck wahr. .ELSE die folgenden Anweisungen bis zum .ENDIF werden assembliert, wenn Ausdruck falsch war. .ENDIF beendet einen .IF oder .ELSE Abschnitt. .MACRO Name,Parameter1,... beginnt eine Makrodefinition. .ENDMACRO beendet eine Makrodefinition. .LOCALMACRO Symbol1,... deklariert die lokalen Marken (Makrodef.). Abb. 17. CALM-Pseudobefehle 5 Einsatz von CALM - nicht ganz problemlos Wie bei jeder neuen Programmiersprache braucht es seine Zeit, bis sie akzeptiert wird. Aber im Gegensatz zu höheren Programmiersprachen kommt erschwerend hinzu, dass auch eine allgemeine Assemblersprache verständlicherweise von den Mikroprozessoren abhängt, die sie beschreiben soll. CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 11 5.1 Dokumentation Zur Zeit wird die CALM-Schreibweise der Befehle eines Prozessors in sogenannten Befehlslisten dokumentiert. In diesen Befehlslisten sind sämtliche Befehle eines Prozessors in gedrängter Form aufgeführt. Aber zusätzliche Angaben, wie Befehlscodes, Ausführungszeit, Eigenheiten, usw. zu einem Mikroprozessor sind nur der Herstellerdokumentation zu entnehmen. Somit ist der Benutzer gezwungen, beide Schreibweisen zu kennen. Deshalb wird in den CALM-Befehlslisten auch die Herstellerbezeichnung der Operationscodes angegeben. 5.2 Eine CALM-Schreibweise - viele mögliche Befehlscodes Bei modernen 16/32 Bit Mikroprozessoren gibt es mehrere, 100% identische Befehle. Die CALM-Schreibweise führt dies exemplarisch vor Augen. Die Frage lautet nun: Welchen Befehlscode soll der Assembler wählen? Generell betrachtet wird man es dem Assembler überlassen, den kompaktesten und damit auch schnellsten Befehlscode auszuwählen. Ansonsten richten sich die Unterscheidungsmerkmale nach den Operanden. Der resultierende Befehlscode hängt von Grösse und Typ des Operanden, sowie von zusätzlichen Adressen- und Datenangaben ab. 5.3 Assembler Trotz der vielen Vorzüge, die CALM besitzt, ist eine Programmierung in CALM natürlich sinnlos, wenn die entsprechenden Arbeitsmittel (sprich: Assembler, Linker) fehlen. Zurzeit sind CALM-Assembler, die direkt Maschinencode (ohne Linker) für fast alle gängigen Mikroprozessoren erzeugen, vom Autor erhältlich. 5.4 Objektformat Die Maschinencodegenerierung bleibt weiterhin sehr kompliziert, da für jeden Prozessor neben dem eigenen Maschinencodeformat ein spezielles Objektformat definiert wurde. Diese Vielfalt verunmöglicht praktisch eine Verallgemeinerung des Assemblers. Immerhin wurde ein einheitliches Objektformat [2] definiert. 6 Beispiele In den folgenden Beispielen werden Befehle in der CALM-Schreibweise mit der der Hersteller verglichen. Es sind keine zusammenhängende Programme, sondern eine Auswahl von jeweils zwanzig Befehlen der Prozessoren iAPX86, M68000 und NS32000. Zusätzliche Feinheiten der Assemblerschreibweise hängen stark vom verwendeten Assembler ab. Hier wurde von den Angaben der jeweiligen Herstellerassembler ausgegangen. 6.1 iAPX86 Der iAPX86 von Intel ist aufgrund seiner Segmentierung einer der komplexesten Mikroprozessoren überhaupt. Der Assembler ASM-86 benötigt zahlreiche Angaben bezüglich dieser Segmentregister (ASSUME, NEAR, Deklaration der Variablen, usw.). Daher sind die folgenden Beispiele unvollständig. Wegen dieser aussergewöhnlichen Architektur kann eine allgemeine Assemblersprache für diesen Prozessor nicht einfach sein (Abb. 18). CALM gibt bei jedem Befehl das verwendete Segmentregister an. Ausserdem wird bei Sprungbefehlen durch eine Datenangabe die Weite des Sprunges bestimmt (innerhalb oder ausserhalb eines Segments). Auch wird eine vereinfachte Schreibweise für die automatische Verschiebung des Segmentregisters eingeführt. CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 12 CALM Intel MOVE.16 #16'1000,BX MOV BX,1000H MOVE.16 AX,${DX} OUT DX,AX MOVE.16 #[ES]+NEXT,DX LEA DX,ES:NEXT MOVE.8 DL,BH MOV BH,DL MOVE.8 [DS]+DATA,AL MOV AL,DATA MOVE.16 [CS]+{SI},AX MOV AX,CS:[SI] MOVE.8 AH,F SAHF PUSH.16 SF PUSHF MOVE.8 [DS]+{BX}+{AL},AL XLAT CONV_TAB MOVE.32 [DS]+TARGET,DSBX LDS BX,TARGET CONV.A8.16 AX CBW INC.8 AL INC AL DIV.A16 CX,DXAX,AX,DX IDIV CX OR.16 #ERROR,[DS]+STATUS OR STATUS,ERROR RL.8 AL ROL AL,1 TEST.16 #MASK,AX TEST AX,MASK JUMP,LO R^LOW JB LOW CALL.32 20^ROUTINE JSR FAR ROUTINE DJ.16,NE CX,ADDRESS LOOP ADDRESS TRAP.32,VS INTO Abb. 18. Beispiele: iAPX86 6.2 M68000 Die Schreibweise der Befehle des M68000 von Motorola unterscheidet sich nur geringfügig von CALM (Abb. 19). Aber gerade bei diesem Prozessor wird deutlich, dass "aufwärtskompatibel" wohl für den Maschinencode (allerdings auch nur teilweise), nicht aber für die Assemblersprache gilt: Die zusätzlichen Adressierungsmöglichkeiten des M68020 zwangen Motorola zu einer Änderung der Schreibweise einiger Adressierungsarten. CALM Motorola MOVE.32 #16'1000,D1 MOVE.L #$1000,D1 MOVE.32 #A8^WERT,D0 MOVEQ #WERT,D0 MOVE.32 #{A6}+100,A3 LEA 100(A6),A3 MOVE.8 D4,D6 MOVE.B D4,D6 MOVE.8 {A4}+4,D2 MOVE.B 4(A4),D2 MOVE.16 {A0}+A16^{D4}+2,{A2}+32^{A3}+4 MOVE 2(A0,D4),4(A2,A3.L) MOVE.16 D0,F MOVE D0,CCR PUSH.16 SF MOVE SR,-(A7) PUSH.32 #TABLE PEA TABLE PUSH.32 D0..D3|D5 MOVEM.L D0-D3/D5,-(A7) CONV.A8.16 D4 EXT.W D4 INC.8 D1 ADDQ.B #1,D1 DIV.A16 #10,D6 DIVS #10,D6 OR.16 #MASK,{A4}+A16^{D4}-4 OR #MASK,-4(A4,D4.W) RL.32 #8,D4 ROL.L #8,D4 TCLR.8 {A0+}:D4 BCLR D4,(A0)+ JUMP,LO R^LOW BLO LOW CALL U^ROUTINE JSR ROUTINE DJ.16,NMO D0,ADDRESS DBRA D0,ADDRESS TRAP,VS TRAPV Abb. 19. Beispiele: M68000 6.3 NS32000 Eigentlich handelt sich hier um eine Familie, die von National Semiconductor "NS32000 series" genannt wird. Sie besteht aus den 100% identischen Mitgliedern NS32032, NS32016 und NS32008. Als Besonderheit muss die vollkommene Symmetrie der Adressierungsmöglichkeiten bei allen Befehlen gelten (Abb. 20). CALM - eine allgemeine Assemblersprache für Mikroprozessoren - 13 CALM NS MOVE.32 #16'1000,R1 MOVD H'1000,R1 MOVE.32 #A4^WERT,R0 MOVQD WERT,R0 MOVE.32 #{R6}+100,{SB}-20 ADDR 100(R6),-20(SB) MOVE.8 R4,R6 MOVB R4,R6 MOVE.8 {SB}+4,R2 MOVB 4(SB),R2 MOVE.16 {{FP}+2}+4,{SB}+{R0}*1+2 MOVW 4(2(FP)),2(SB)[R0:B] MOVE.8 R0,F LPRB R0,UPSR PUSH.16 SF SPRW PSR,TOS PUSH.32 #R^TABLE ADDR TABLE,TOS PUSH.32 R0..R3|R5 SAVE [R0,R1,R2,R3,R5] CONV R4.8,R2.32 MOVZBD R4,R2 INC.8 R1 ADDQB 1,R1 DIV.A32 #10,R6 QUOD 10,R6 OR.16 #MASK,{{SB}-2}+{R5}*8+4 ORW MASK,4(-2(SB))[R5:Q] RL.32 #-10,R4 ROTD -10,R4 TCLR {R0}+20:{{SB}+10}+4.A32 CBITD 4(10(SB)),20(R0) JUMP,LO R^LOW BLO LOW CALL U^ROUTINE JSR @ROUTINE AJ.32,NE #2,{R0}+4,ADDRESS ACBD 2,4(R0),ADDRESS TRAP,VS FLAG Abb. 20. Beispiele: NS32000 7 Schlussbemerkungen CALM ist eine allgemeine Assemblersprache, die sich für praktisch alle Mikroprozessoren, aber auch Miniprozessoren und Grossrechner eignet. Die CALM Schreibweise ist zukunftsorientiert, da sie gezielt und funktionell erweitert werden kann. Die klare Trennung der einzelnen Bausteine eines Befehls ermöglicht eine individuelle Anpassung an die unterschiedlichsten Prozessoren. Prozessorspezifische Erweiterungen sind jederzeit möglich. Zudem versteht man einen Befehl in der CALM-Schreibweise oft viel besser. An rund 24 Prozessoren wurde geprüft, ob die CALM-Schreibweise aus- reichend ist, um alle diese Prozessoren umfassend auszudrücken. Als Resultat dieser Tests sind CALM-Befehlslisten für folgende Prozessoren erstellt worden: Z80, 65x02, 680x, 6805, 6809, 8048, 8051, 808x, iAPXx86, NS32000, und 680xx. Es wurde auf kleinem Raum versucht, die Grundzüge von CALM aufzuzeigen. Die Assemblerprogrammierung an sich wird in der CALM-Schreibweise nicht einfacher, aber sie wird für den Benutzer verständlicher, da sie nach logischen Gesichtspunkten aufgebaut ist. 8 Bibliographie [1] DIN 66283, Allgemeine Assemblierer-Sprache für Mikroprozessoren CALM, nationale dt. Norm, Beuth Verlag GmbH, Pf 1145, D-Berlin. [2] "The Microprocessor Universal Format for Object Modules", Prop. Stand.: IEEE P695 Working Group. IEEE Micro, 8/1983, pp. 48-66. [3] Nicoud, J.D. and Fäh, P. Common Assembly Language for Micro- processors CALM, Internes Dokument. LAMI-EPFL, INF-Ecublens, CH-1015 Lausanne, Dec. 1986. English version of DIN 66283. [4] Nicoud, J.D. and Fäh, P. Explanations and Comments, Related to the Common Assembly Language for Microprocessors. LAMI-EPFL. [5] Zeltwanger, H. "Genormte Assemblersprache für Ps", ELEKTRONIK, 35. Jahrg. (1986), Nr. 8, S. 66-71. [6] Nicoud, J.D. Calculatrices, Volume XIV du Traité d'Electricité, Lausanne: Presses polytechniques romandes, 1983. [7] Nicoud, J.D. and Wagner, F. Major Microprocessors, A unified approach using CALM. North Holland, 1987. [8] Strohmeier, A. Le matériel informatique, concepts et principes Lausanne: Presses polytechniques romandes, 1986. [9] Fäh, P. "Die (Un-)Logik von Assemblersprachen", Elektroniker, 26. Jahrg. (1987), Nr. 5, S. 97-100.