Trotz der Einfachheit der Maschine sollte es möglich sein, fortgeschrittene Programmiersprachen auf ihr zu übersetzen, da wichtige Mechanismen wie indirekte Adressierung unterstützt werden. Auch Eingabe/Ausgabefunktionen sind vorhanden, damit ein Programm auch Eingaben lesen und Ausgaben drucken kann.
Anschließend muß nur noch 'make' aufgerufen werden, der Rest funktioniert automatisch. Abschließend sollte sich das ausführbare Programm 'sm' im Verzeichnis befinden.
sm asmfilewobei 'asmfile' eine Datei mit einem gültigen Assemblerprogramm ist. Das Programm wird geladen und sofort anschließend ausgeführt. Zusätzlich versteht das Programm noch drei Parameter:
Die Maschine ist ein 32-Bit-Rechner mit 64kB Speicher. Integerarithmetik wird ebenso unterstützt wie Fließkommaarithmetik (letztere etwas unkomfortabel). Ein Programm teilt sich den Speicher mit dem Stack, wobei die Aufteilung, wo das Programm und wo der Stack im Speicher liegt, frei gewählt werden kann. Genau genommen sind sogar zwei Stacks vorhanden: neben dem 'Arithmetikstack', der mit den Stackbefehlen angesprochen werden kann auch noch der 'Call-Stack', auf dem Sprungadressen für Unterprogrammaufrufe abgelegt werden. Diese Differenzierung hat den Vorteil, daß der Stack auch über Unterprogramme hinweg verwendet werden kann, insbesondere ist eine Parameterübergabe über den Stack möglich. Da der Speicher aus 8-Bit großen Elementen besteht, ist darauf zu achten, daß eine Integerzahl 4 Bytes benötigt, und eine Fließkommazahl 8 Bytes. Da ein Stackelement und der Akku nur 4 Bytes groß sind, können Fließkommazahlen nicht direkt bearbeitet werden, sondern nur indirekt. Alle Fließkommabefehle arbeiten mit Adressen der eigentlichen Operanden.
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- load # zahl | * | 0 | Lädt den Akku mit der gegebenen Zahl load addr | * | 0 | Lädt den Akku mit der 4-Byte-Zahl, die an der | | | gegebenen Adresse steht load | * | -1 | Lädt den Akku mit der 4-Byte-Zahl an der Adresse, | | | auf die das oberste Stackelement zeigt store addr | | 0 | Speichert den Akku als 4-Byte-Zahl an die Adresse store | | -1 | Speichert den Akku als 4-Byte-Zahl an die Adresse, | | | auf die das oberste Stackelement zeigt | | | loadw addr | * | 0 | Lädt den Akku mit der 2-Byte-Zahl, die an der | | | gegebenen Adresse steht loadw | * | -1 | Lädt den Akku mit der 2-Byte-Zahl an der Adresse, | | | auf die das oberste Stackelement zeigt loadb addr | * | 0 | Lädt den Akku mit der 1-Byte-Zahl, die an der | | | gegebenen Adresse steht loadb | * | -1 | Lädt den Akku mit der 1-Byte-Zahl an der Adresse, | | | auf die das oberste Stackelement zeigt | | | storew addr | | 0 | Speichert den Akkuinhalt als 2-Byte-Zahl an die | | | Adresse. Der Akku wird dabei auch dann nicht ver- | | | ändert, wenn er grösser als 2 Bytes ist storew | | -1 | Speichert den Akkuinhalt als 2-Byte-Zahl an die | | | Adresse, auf die das oberste Stackelement zeigt storeb addr | | 0 | Speichert den Akkuinhalt als 1-Byte-Zahl an die | | | Adresse. Der Akku wird dabei auch dann nicht ver- | | | ändert, wenn er grösser als 1 Byte ist storeb | | -1 | Speichert den Akkuinhalt als 1-Byte-Zahl an die | | | Adresse, auf die das oberste Stackelement zeigt
Bemerkung: Die Befehle 'loadw # zahl' und 'loadb # zahl' gibt es nicht, weil sie mit 'load # zahl' äquivalent sind.
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- push | | +1 | Speichert Akku auf den Stack pop | * | -1 | Liest Wert vom Stack in den Akku dup | | +1 | Verdoppelt oberstes Stackelement swap | | 0 | Vertauscht die oberen beiden Stackelemente exch | * | 0 | Vertauscht Akku und oberstes Stackelement
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- mult # zahl | * | 0 | Multipliziert Akku mit Zahl (Akku = Akku * zahl) mult addr | * | 0 | Multipliziert Akku mit der Zahl, die an der | | | gegebenen Speicheradresse steht mult | * | -1 | Multipliziert Akku mit oberstem Stackelement | | | add # zahl | * | 0 | Addiert Zahl zum Akku (Akku = Akku + zahl) add addr | * | 0 | Addiert den Inhalt der Speicheradresse zum Akku add | * | -1 | Addiert oberstes Stackelement zum Akku | | | sub # zahl | * | 0 | Subtrahiert Zahl vom Akku (Akku = Akku - zahl) sub addr | * | 0 | Subtrahiert Inhalt der Speicheradresse vom Akku sub | * | -1 | Subtrahiert oberstes Stackelement vom Akku | | | div # zahl | * | 0 | Dividiert Akku durch Zahl (Akku = Akku / zahl) | | | Das Ergebnis ist die untere Gaussklammer dieser | | | Division div addr | * | 0 | Dividiert Akku durch Inhalt der Speicheradresse div | * | -1 | Dividiert Akku durch oberstes Stackelement
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- inot | * | 0 | Logisches not des Akku. Eine 0 wird zu -1, und | | | jede beliebige Zahl ungleich 0 wird zu 0 not | * | 0 | Bitweises not des Akku. Jedes Bit wird umgedreht | | | and # zahl | * | 0 | Bitweises und des Akku mit der Zahl and addr | * | 0 | Bitweises und des Akku mit Inhalt des Speichers and | * | -1 | Bitweises und des Akku mit oberstem Stackelement | | | or # zahl | * | 0 | Bitweises or des Akku mit der Zahl or addr | * | 0 | Bitweises or des Akku mit Inhalt des Speichers or | * | -1 | Bitweises or des Akku mit oberstem Stackelement | | | shl # zahl | * | 0 | Shift Left. Der Akku wird um zahl Bits nach links | | | verschoben. Rechts werden Null-Bits eingefügt shl addr | * | 0 | Verschiebt Akku um soviele Bits nach links wie in | | | der Speicherzelle angegeben shl | * | -1 | Verschiebt Akku um soviele Bits nach links wie im | | | obersten Stackelement angegeben | | | shr # zahl | * | 0 | Shift Right. Der Akku wird um zahl Bits nach | | | rechts verschoben. Links werden Null-Bits | | | eingefügt. shr addr | * | 0 | Verschiebt Akku um soviele Bits nach rechts wie | | | in der Speicherzelle angegeben shr | * | -1 | Verschiebt Akku um soviele Bits nach rechts wie | | | im obersten Stackelement angegeben
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- jump addr | | 0 | Springt an die Speicheradresse jump | | -1 | Springt an die Adresse, die durch das oberste | | | Stackelement gegeben ist call addr | | 0 | Unterprogrammaufruf. Die Programmausführung wird | | | an addr fortgesetzt. Führt das Unterprogramm ei- | | | nen 'ret'-Befehl aus, wird das Programm direkt | | | hinter dem 'call' fortgesetzt call | | -1 | Wie eben, doch wird hier an die Adresse verzweigt, | | | die im obersten Stackelement steht ret | | 0 | Rücksprung aus einem Unterprogramm genau hinter | | | den 'call'-Befehl, mit dem das Unterprogramm auf- | | | gerufen wurde bzero addr | | 0 | Wenn Akku = 0, so wird nach addr gesprungen | | | Wenn Akku != 0, so wird die Ausführung direkt | | | hinter dem Befehl fortgesetzt bzero | | -1 | Wie eben, doch wird, wenn der Akku=0 ist, an die | | | Adresse gesprungen, die im obersten Stackelement | | | steht bnzro addr | | 0 | Wenn Akku != 0, so wird nach addr gesprungen | | | Wenn Akku = 0, so wird die Ausführung direkt | | | hinter dem Befehl fortgesetzt bnzro | | -1 | Wie eben, doch wird, wenn der Akku!=0 ist, an die | | | Adresse gesprungen, die im obersten Stackelement | | | steht bpos addr | | 0 | Wenn Akku > 0, so wird nach addr gesprungen | | | Wenn Akku <= 0, so wird die Ausführung direkt | | | hinter dem Befehl fortgesetzt bpos | | -1 | Wie eben, doch wird, wenn der Akku>0 ist, an die | | | Adresse gesprungen, die im obersten Stackelement | | | steht bneg addr | | 0 | Wenn Akku < 0, so wird nach addr gesprungen | | | Wenn Akku >= 0, so wird die Ausführung direkt | | | hinter dem Befehl fortgesetzt bneg | | -1 | Wie eben, doch wird, wenn der Akku<0 ist, an die | | | Adresse gesprungen, die im obersten Stackelement | | | steht
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- fmult | | -1 | Liest Fliesskommazahl aus der Adresse, auf die | | | das oberste Stackelement zeigt, multipliziert | | | diese mit der Fliesskommazahl, auf die der Akku | | | zeigt, und speichert das Ergebnis an die Adresse, | | | auf die der Akku zeigt, zurück fdiv | | -1 | Liest Fliesskommazahl aus der Adresse, auf die | | | der Akku zeigt, und teilt durch die Fliesskomma- | | | zahl, auf die das oberste Stackelement zeigt fadd | | -1 | Addiert die Fliesskommazahl, auf die das oberste | | | Stackelement zeigt, zu der Fliesskommazahl hinzu, | | | auf die der Akku zeigt fsub | | -1 | Subtrahiert die Fliesskommazahl, auf die das | | | oberste Stackelement zeigt, zu der Fliesskomma- | | | zahl hinzu, auf die der Akku zeigt itof addr | | | Wandelt die 4-Byte-Integer-Zahl, die im Akku | | | steht, nach Fliesskomma um, und speichert diese | | | Fliesskommazahl an der gegebenen Adresse itof | | -1 | Wandelt die 4-Byte-Integer-Zahl, die im Akku | | | steht, nach Fliesskomma um, und speichert diese | | | Fliesskommazahl and der Adresse, auf die das | | | oberste Stackelement zeigt ftoi addr | | | Liest die Fliesskommazahl, die an der gegebenen | | | Adresse steht, wandelt sie in eine 4-Byte-Integer | | | Zahl (untere Gaussklammer) und speichert diese | | | Integerzahl im Akku ftoi | | -1 | Liest die Fliesskommazahl, auf die das oberste | | | Stackelement zeigt, wandelt sie per unterer | | | Gaussklammer in eine Integerzahl um und speichert | | | diese Integerzahl im Akku
Befehl | A | ST | Beschreibung ------------------+---+----+-------------------------------------------------- print | | 0 | Druckt die Zahl im Akku als 4-Byte-Zahl aus cprint | | 0 | Druckt den Wert im Akku als ASCII-Zeichen aus | | | Dabei muss der Akkuinhalt 0<=akku>256 sein fprint | | 0 | Druckt den Speicherinhalt, auf den der Akku | | | zeigt, als Fliesskomma aus. sprint | | 0 | Druckt den Speicherinhalt, auf den der Akku | | | zeigt, als Null-terminierten String aus. read | * | 0 | Liest eine 4-Byte-Zahl von der Konsole in den | | | Akku. Wird keine Zahl eingegeben, ist das Er- | | | gebnis 0. cread | * | 0 | Liest ein ASCII-Zeichen von der Konsole in den | | | Akku fread | | 0 | Liest eine Fliesskommazahl von der Konsole und | | | speichert diese an der Adresse, auf die der Akku | | | zeigt. Wird keine Zahl eingegeben, ist das Er- | | | gebnis 0. sread | | 0 | Liest einen Null-terminierten String von der | | | Konsole und speichert diesen beginnend mit der | | | Adresse, auf die der Akku zeigt, ab
Label:definiert werden. Dabei ist 'Label' ein beliebiger Text, der sich aus Grossbuchstaben, Kleinbuchstaben, Ziffern und dem Unterstrich zusammensetzen kann. Das erste Zeichen eines Labels muss ein Grossbuchstabe oder ein Unterstrich sein. Die Definition eines Labels muss eindeutig sein, und sie gilt auch rückwirkend (ein Label kann bereits vor seiner Definition verwendet werden). Label können unter ihrem Namen dann überall verwendet werden, insbesondere dort, wo ein Adresswert erwartet wird. Beispiel:
call Unterprogramm ... Unterprogramm: retAuch beim Laden eines expliziten Wertes kann ein Label verwendet werden:
load # Unterprogrammlädt die Adresse des Unterprogramms in den Akku.
Zusätzlich kann bei jedem Label noch ein Offset angegeben werden, also ein positiver oder negativer Ausdruck.
load # Unterprogramm + 4lädt die Adresse des Unterprogramms + 4 in den Akku.
:addrWobei addr eine Adresse ist. Von dort an wird das Assemblerlisting dann an diese Speicheradresse aufwärts geladen. Beispiel:
:100h add ; Steht an Speicheradresse 100h :1000h add ; Steht an Speicheradresse 1000hDieses Beispiel ist natürlich unsinnig.
Es ist zu bemerken, dass das 'data'-Kommando die einzige Möglichkeit ist, Fliesskommakonstanten, die ja nicht per 'load' geladen werden können, zu definieren. Ein Programm, welches mit Fliesskommazahlen arbeitet, kommt nicht drum herum, per 'data'-Befehl eine Tabelle mit verwendeten Zahlen anzulegen. In diesem Zusammenhang erkennen wir auch den Vorteil, bei einem Label ein Offset angeben zu können. Dies erspart uns, jedem Wert ein eigenes Label zuzuordnen. Hier ein Beispiel für die gemeinsame Verwendung dieser Features:
Tabelle: data 42.0, 6.2, "Dies ist ein Test" Programm: load # Tabelle ; Lade Adresse von 42.0 fprint ; Drucke "42" load # Tabelle + 8 ; Lade Adresse von 6.2, da jede Fliesskomma- ; zahl 8 Bytes belegt fprint ; Drucke "6.2" load # Tabelle + 16 ; Lade Adresse des Textes sprint ; Drucke Text
Es ist zu beachten: Jede Fliesskommazahl muss einen Dezimalpunkt enthalten, ansonsten wird sie als Integerzahl interpretiert. Würde oben die "42.0" durch "42" ersetzt, würde nichts mehr funktionieren, da sich dann auch die Adressen der folgenden Elemente verschieben. Words (also 2-Byte-Zahlen) werden von Integern durch ein führendes 'w' unterschieden. Ebenso erhalten Bytes (also 1-Byte-Zahlen) ein führendes 'x'.Beispiel für alle möglichen Daten:
data 100, w$100, x10h, Programm + 18, 42.0*24.0Dies speichert Dezimal 100 als 4-Byte-Integer, hex 100 (=256) als 2-Byte-Zahl, eine hexadezimale 10 (=16) als Byte, die Adresse auf das Label 'Programm' mit dem Offset 18 wird als Adresse (2 Bytes) gespeichert, der Ausdruck wird aufgelöst (1008.0) und als Fliesskommazahl abgespeichert.
#include dateinamewird eine weitere Datei genau an diese Stelle eingelesen. Zwischen dem Schlüsselwort 'include' und dem Dateinamen darf nur genau ein Leerzeichen sein. Die Datei darf ebenfalls weitere Dateien laden. Das ganze funktioniert nur, wenn das Programm mit Hilfe von flex übersetzt wurde.
Speicher +--------------+ 0 | PROGRAM CTR | 1 | PROGRAM CTR | 2 | STACKPTR | 3 | STACKPTR | 4 | CALLSTACKPTR | 5 | CALLSTACKPTR | 6-255 | RESERVED | 256 | | - | UNUSED | 65535 | | +--------------+Die Werte für PC, STACKPTR und CALLSTACKPTR, jeweils eine 2-Byte-Adresse, können im Assemblerlisting einfach mit dem data-Kommando gesetzt werden. Dabei wird der PC normalerweise auf die Adresse des Hauptprogramms gesetzt, und die anderen beiden Pointer auf relativ beliebige Adressen, so dass sie weder das Programm noch sich selber stören. Eventüll ist es ganz nützlich zu wissen, dass beide Stacks nach oben wachsen. Bei falscher Programmierung können beide Stacks die ursprünglichen Adressen auch unterschreiten.
Üblicherweise beginnt ein Listing deshalb wie folgt:
:0 data MAIN, we000h, wf000hDadurch wird der PC auf das Label MAIN gesetzt (was natürlich zu definieren ist), der STACKPTR auf die Adresse e000, und der CALLSTACKPTR auf f000. Da die Adressen bis 256 reserviert sind, sollte an dieser Stelle
:256folgen, damit keine Daten im reservierten Speicherbereich abgelegt werden.
:0 data MAIN, we000h, wf000h :1000h data "Hello World\n" MAIN: load # 1000h sprint jump 0Das Program lädt die Adresse des Strings in den Akku und gibt dann den String aus.
Frank Pilhofer <fp -AT- fpx.de> Back to the Homepage Last modified: Fri Jun 8 13:02:13 2001