In diesem Abschnitt wird zunächst die Assembler-Sprache beschrieben, dann das Assembler-Programm kurz erläutert und schließlich ein paar Hinweise zur Programmierung gegeben.
Prinzipiell wird zwischen "Anweisungen" und "Befehlen" unterschieden. Anweisungen sind nur für den Assembler bestimmt, damit dieser weiß, was er zu tun hat. Anweisungen werden vom Entwickler des Assembler-Programms definiert und sind nicht vom Prozessor vorgegeben. Befehle dagegen werden vom Assembler unmittelbar in Maschinencode umgewandelt. Sie sind damit vom Prozessor vorgegeben. Einzig sogenannte "Pseudo-Befehle" können und sollen vom Assembler-Entwickler zusätzlich definiert werden. Dieses sind Befehle, die eigentlich Spezialfälle anderer Befehle sind. Beispiele sind:
Kommentare beginnen wie üblich mit ";" und enden am Zeilenende.
Bei Anweisungen und Befehlen wird nicht zwischen Groß- und Kleinschreibung unterschieden, jedoch muss das gesamte reservierte Wort groß oder klein geschrieben sein.
Ein Parser zur Berechnung von Ausdrücken zur Assemblierzeit ist nicht implementiert. Wenn eine Konstante erwartet wird, kann diese also nicht erst aus anderen Symbolen berechnet werden.
Eine Marke muss immer am Zeilenanfang stehen.
Im Adress-Teil von Befehlen ist die Verwendung des "+" bzw. "-" optional, es ist also erlaubt, ld r1, [r2] anstelle von ld r1, [r2+0] zu schreiben oder stb [27], r1 anstelle von stb [r0+27], r1.
Der ssfp16asm kennt die in Tabelle 1 dargestellten Anweisungen.
Tabelle 1: Anweisungen
| .DATA | ||
| .EDATA | ||
| .CODE | ||
| .ORG | Dezzahl | Hexzahl | Symbol | |
| .ALIGN | Dezzahl | Hexzahl | Symbol (1, 2, 4, 8,..., 4096) | |
| Marke[:] | .EQU | Dezzahl | Hexzahl |
| [Marke[:]] | .DB | Dezzahl | Hexzahl | Symbol | Zeichen | Zeichenkette [,Dezzahl | Hexzahl | Symbol | Zeichen | Zeichenkette]... |
| [Marke[:]] | .DW | Dezzahl | Hexzahl | Symbol | Zeichen | Zeichenkette [,Dezzahl | Hexzahl | Symbol | Zeichen | Zeichenkette]... |
| [Marke[:]] | .RESB | Dezzahl | Hexzahl | Symbol |
| [Marke[:]] | .RESW | Dezzahl | Hexzahl | Symbol |
.DB und .DW reservieren und initialisieren Bytes oder Words entweder im internen Datenspeicher oder im externen Datenspeicher. Hinweis: Zeichen und Zeichenketten können auch mit .DW initialisiert werden. In diesem Fall wird ein Wort pro Zeichen geschrieben. Dies ist im Zusammenhang mit Paritätsprüfungen sinnvoll (s. u.)
.RESB und .RESW reserviert die durch die folgende Zahl angegebene Anzahl Bytes oder Words entweder im internen Datenspeicher oder im externen Datenspeicher.
Mit den Anweisungen .DATA, .EDATA und .CODE wird dem Assembler mitgeteilt, auf welche Speicherbereiche sich die folgenden Anweisungen beziehen. Befehle dürfen dabei nur nach .CODE stehen, Speicherinitialisierungen und -reservierungen nur in .DATA oder .EDATA.
Mit .EQU werden Konstanten definiert. Diese dürfen in allen Bereichen definiert werden.
.ORG setzt den Adresszähler des aktuellen Bereichs auf den angegebenen Wert. Dieser Wert darf nicht kleiner sein, als der aktuelle Wert.
Mit .ALIGN wird die Ausrichtung der Daten im Datenspeicher definiert:
Bei mehreren Werten hinter .DB oder .DW gilt die Ausrichtung nur für den ersten Wert, alle weiteren werden direkt angehängt.
Es können mehrere .ALIGN-Anweisungen unmittelbar hintereinander stehen. In diesem Fall wird der Adresszähler gemäß der Anweisung mit der größten Ausrichtung gesetzt, für folgende Anweisungen gilt der letzte Ausrichtungswert. Beispiel:
.align 0x10
.align 2
var1: .db 5
text1: .db "Das ist Text1!", 0
text2: .dw "Das ist Text2!", 0
var1 wird an eine durch 0x10 teilbare Adresse geschrieben. text1 und text2 werden an Wort-Grenzen begonnen.
Wichtiger Hinweis: Werden bei Nutzung der Paritätsprüfung einzelne Bytes initialisiert oder vom Programm geschrieben, so wird das andere Byte eines Worts nicht als initialisiert gekennzeichnet. Bei lesendem Zugriff auf das Wort wird daher ein Paritätsfehler generiert. Daher muss vom Programmierer sichergestellt werden, dass nur auf vollständig initialisierte Worte lesend zugegriffen wird. Dies kann beispielsweise durch Verzicht auf .DB geschehen oder dadurch, dass hinter .DB immer eine gerade Anzahl von Werten steht. Bei Verwendung der Assembler-Option "-i" muss entsprechend nach .RESB eine gerade Zahl folgen. Ohne Verwendung der Option "-i" muss sichergestellt werden, dass immer erst beide Bytes eines Wortes vom Programm geschrieben wurden, bevor auf dieses Wort lesend zugegriffen wird.
Befehle haben immer folgende Syntax:
| [Marke[:]] | Befehl | [Operanden] |
Befehle mit unmittelbaren Operanden (z. B. addi, sbbi etc.) werden nur durch die angegebenen Operanden unterschieden, nicht durch den Befehlsnahmen. Anstelle von addi kann und muss also add reg, imm geschrieben werden.
Entsprechend ihrer Operanden lassen sich die Befehle in Gruppen gemäß Tabelle 2 einteilen. Diese Befehlsgruppen werden vom Assembler zur Syntaxprüfung und Zuweisung in die einzelnen Befehlsfelder herangezogen.
Tabelle 2: Befehlsgruppen
| Gruppe | Syntax | Befehle | Feld-Zuweisungen |
| OP_RR_RI | "befehl reg1, reg2" "befehl reg1, wert": |
add, sub, adc, sbb, cmp and, or, xor, andn, test mul, imul, div, idiv |
reg1 => RegA reg2 => RegB_imm4 wert => RegB_imm4 und ggf. Imm12 |
| OP_R | "befehl reg" | shl, slc, sra, srl, src, bitswap, byteswap, setf |
reg => RegA |
| OP_LEA | "lea reg1, reg2+wert" | lea | reg1 => RegA reg2 => RegB wert => Imm4 und ggf. Imm12 |
| OP_MOV | "mov reg1, reg2" "mov reg1, wert" |
mov | reg1 => RegA reg2 => RegB wert => RegB_imm4 und ggf. Imm12 |
| OP_MOVS | "movs reg, regspec" | movs | reg => RegA regspec = multh, multl, quot, rest, flags => Subsubcode |
| OP_LD | "befehl reg1, [reg2+wert]" "befehl reg1, [reg2]" "befehl reg1, [wert]" |
ld, ldx, in | reg1 => RegA reg2 => RegB wert => Imm4 und ggf. Imm12 |
| OP_ST | "befehl [reg1+wert], reg2" "befehl reg1, [reg2]" "befehl reg1, [wert]" |
st, stb, stx, stxb, out | reg1 => RegB reg2 => RegA wert => Imm4 und ggf. Imm12 |
| OP_CALLJMP | "befehl reg+wert" "befehl reg" "befehl wert" |
call, jmp, int | reg => RegB wert => Imm4 und ggf. Imm12 call: 15 => RegA int: 14 => RegA jmp: 0 => RegA |
| OP_JXX | "befehl wert" | ja, jae, jb, jbe, jg, jge, jl, jle, jz, jnz, je, jne, jo, jno, js, jns, jc, jnc |
Bedingung => Bedingung wert => disp |
| NO_OP | "befehl" | ret, iret |
0 => RegA iret: 14 => RegB ret: 15 => RegB 0 => Imm4 |
Ist mit dem Befehl eine schreibende Operation verbunden, so steht das Ziel immer an erster Stelle. Ist mit dem Befehl eine logische oder arithmetische Verknüpfung verbunden, so gilt stets (Ziel :=) Operand1 Verknüpfung Operand2. Beispiele:
| SUB | R1, R2 | ; R1 := R1 - R2 | |
| STX | [R1+0x1234], R2 | ; externer Speicher [R1+0x1234] := R2 | |
| LD | R1, [R2+0x1234] | ; R1 := interner Speicher[R2+0x1234] |
Der Assembler liest genau eine Eingangsdatei mit Assembler-Quelltext. Gemäß dieser Datei werden erzeugt
Folgende Optionen stehen derzeit zur Verfügung:
Das Hauptprogramm des Assemblers befindet sich in der Datei ssfp16asm.c. Die lexikalischen Eigenschaften einer Assembler-Quelltext-Datei werden in der Datei ssfp16asm.lex beschrieben. Diese Datei ist Eingangsdatei für das Programm "flex", welches daraus einen C-Quelltext lex.yy.c generiert. In lex.yy.c befindet sich eine Funktion yylex(), welche durch die Assembler-Quelltext-Datei geht und bei jedem Erkennen eines der in ssfp16asm.lex beschriebenen Elemente eine entsprechende Funktion aufruft. Diese Funktionen wiederum sind in lex_fun.c definiert. Bereits in dieser Stufe werden alle Initialisierungen der .DATA und .EDATA-Bereiche in die entsprechende Memory-Datei geschrieben.
Nach Durchlauf von yylex() befinden sich eine Symboltabelle und eine Befehlstabelle im Speicher. Einige der Symbole (die Marken im Code) müssen nun noch aktualisiert werden, da sie entweder beim Durchgehen der Quelltext-Datei erst später definiert wurden oder weil sich durch benötigte imm-Befehle Marken verschoben haben. Sind alle Werte der Symbole stabil, können die Befehle mit aktualisierten Werte für unmittelbare Konstanten in die .mem-Datei geschrieben werden.
Hinweis: Der Assembler produziert Daten (.mem-Datei), welche von dem im ISE Webpack enthaltenen Programm "data2mem" in die FPGA-Konfigurationsdatei eingefügt werden. Diese Daten enthalten in der derzeitigen Version grundsätzlich Parity-Bits für interne Befehls- und Datenspeicher (Anzahl der gesetzten Bits incl. Parity ist ungerade). Aufgrund eines Fehlers in der mit ISE Webpack 8.1 gelieferten Version von "data2mem" kann jedoch bei Benutzung der Parity-Bits nur zwei Drittel des internen Adressraums initialisiert werden. Weitere Werte in der .mem-Datei werden als Fehler gewertet. Daher muss derzeit in der (eigentlich korrekten) .mem-Datei manuell mindestens das letzte Drittel der Initialisierungswerte des Daten-RAM mittels "/*" und "*/" auskommentiert werden. Dadurch kann natürlich jeweils ein Drittel des Befehls- und Datenspeichers nicht initialisiert werden.
Es erfolgt keinerlei automatische Sicherung von Registern. Erste Aufgabe eines Interrupts ist es daher, die Rücksprungadresse aus r14 auf dem Stack zu speichern, dann die Flags in r14 zu laden (das einzige Register, welches in diesem Augenblick zur Verfügung steht) und von dort ebenfalls auf den Stack zu legen. Wichtig hierbei ist, dass beim Sichern der Rücksprungadresse auf dem Stack die Flags nicht verändert werden dürfen. Somit darf der Stack-Zeiger noch nicht erniedrigt werden, daher st [r13-2], r14 benutzen und erst nach Kopieren der Flags in r14 den Stack-Zeiger um 4 erniedrigen. Beim Beenden des Interrupts entsprechend umgekehrt.
Dieser Befehl kann genutzt werden, um Interrupt-Handler vom Programm aus aufzurufen. Das Sprungziel wird dabei in der Regel nicht 0 sein. In wie weit dieser Befehl sinnvoll ist sei dahingestellt. Es ist zu beachten, dass das IE-Flag durch den INT-Befehl im Gegensatz zu externen Interrupts NICHT automatisch gelöscht wird. Dies muss daher manuell vor Aufruf des Interrupts getan werden. Vom iret-Befehl wird es wie auch bei externen Aufrufen wieder gesetzt.
Ein nop-Befehl ist zwar nicht definiert, jedoch hat z. B. lea rx, rx+0 mit einem beliebigen Register (außer r0) genau diese Wirkung.
| Zurück: Prozessor | Index | Weiter: Simulator/Debugger |
Stand 16.09.2006
Copyright 2006 by Thomas Brunnengräber
Diese Dokumentation unterliegt den Bestimmungen der GNU Free Document License