OurMIPS ist die beispielhaft verwendete Prozessorarchitektur. Sie besteht unter anderem aus folgenden Komponenten:
r0
-31
, wobei ebenfalls die bei MIPS üblichen Bezeichnungen gebräuchlich sind (siehe Vorlesung). Es ist wichtig zu wissen, dass r0
(das Nullregister) stets zu Beginn der Ausführung auf 0 gesetzt ist und darüber hinaus auch gar nicht verändert werden kann. r29
(der Stackpointer) ist zu Beginn auf 0x7FFFFFFF
(dezimal 2147483647) gesetzt. Der Inhalt aller restlichen Register, soweit nicht vorbelegt, ist zu Beginn der Ausführung des Programms zufällig.Weiterin gibt es die ALU (Arithmetic Logic Unit) für das Ausführen von Berechnungen, die Steuereinheit, welche andere Komponenten anhand der aktuellen Instruktion steuert, und weitere kleine Komponenten sowie Multiplexer und Datenleitungen, welche alle Komponenten verbinden.
Um den ourMIPS-Prozessor zu programmieren, wird hier eine Assemblersprache genutzt, die in der Vorlesung vorgestellt und nachfolgend nochmals erklärt wird. Der Compiler, welcher den eingegebenen Quellcode liest und das Maschinenprogramm generiert, heißt Assembler oder Assemblierer. Die Sprache selbst wird allerdings häufig ebenfalls als Assembler bezeichnet, sodass hier nur „Assemblierer“ als Bezeichung benutzt wird. Ein Assemblerprogramm wird fast eins-zu-eins in ein Maschinenprogramm übersetzt, also eine Abfolge von Bits, welche die Instruktion für den Prozessor codieren. Ein Befehl im Quellcode entspricht somit in der Regel einer Instruktion. In den Quellcode können allerdings auch spezielle Direktiven für den Assemblierer geschrieben werden.
Zur Einführung ein Ausschnitt aus einem Beispielprogramm. r2
enthält einen zuvor eingegebenen Wert.
Auffällig am Rand stehen Kommentare in grün. Der Assemblierer ignoriert den Text, der hinter dem ;
steht.
Sie eignen sich besonders gut, um zu dokumentieren, was ein Teil des Programms tut und was dessen Nutzen
ist. Weil Assemblersprachen im Gegensatz zu höheren Programmiersprachen nicht leicht nachzuvollziehen sind, muss
immer darauf geachtet werden, den Quellcode gut und aussagekräftig zu kommentieren.
Eine Instruktionen besteht aus einer Operation und deren Argumente. Nach dem Übersetzen befinden sie sich alle von oben beginnend bei Adresse 0 im Programmspeicher. Der Prozessor fängt bei Adresse 0 an die Instruktionen auszuführen:
r0
stets die Null enthält, hat r1
danach eine 5 als Inhalt.beq
-Instruktion als relative Angabe codiert wird. Der Prozessor kennt keine Labels, sie existieren nicht im
Maschinenprogramm.r2
auf den Wert r2
+r2
gesetzt.gleichheit
, es steht für Adresse 3 innerhalb des
Programmspeichers, zeigt also auf die Instruktion in Zeile 5.r3
auf den Wert r2
+1. Es ist nicht relevant, ob
eine Instruktion eingerückt ist oder nicht, allerdings kann man so den Quellcode besser strukturiert erfassen.
Einrücken verbessert ebenfalls die Lesbarkeit.Aus dem Eingabewert x
in r2
berechnet das obige Programm zusammengefasst den Wert
2x+1
in r3
, wenn x
nicht 5 ist, ansonsten den Wert 6.
;
oder #
überall eingeleitet werden und gelten den Rest der
Zeile.<Operation> <Argument1>, ...
, wobei
das Komma weggelassen werden darf (Komma und Leerzeichen dürfen aber nicht gemischt werden).r0
-r31
oder r[0]
-r[31]
oder mit den
alternativen MIPS-Namen genannt. Die alternativen Bezeichnungen können unter Umständen die Verständlichkeit erhöhen.420
), Binär (z.B. 0b1110
und
10010b
), Hexadezimal (z.B. 0x29A
und z.B. ABCDh
) geschrieben werden. Negative Zahlen mit Vorzeichen -
sind ebenfalls erlaubt und werden im Zweierkomplement codiert.<Name>:
.
Nach dem Doppelpunkt darf direkt die nächste Instruktion stehen. Labels dürfen nur mit Namen mit
A
-Z
, a
-z
und _
sowie Ziffern
0
-9
, die nicht direkt am Anfang stehen, bezeichnet werden. größer-als-4:
und 2big:
sind also keine gültigen Label, wohingegen groesserals_4:
eines ist.Neben den Assemblerinstruktionen können (und müssen) sogenannte Systemaufrufe verwendet werden, die durch das (hier versteckte und nicht näher erläuterte) Betriebssystem „bereitgestellt“ werden. Es stehen folgende Systemaufrufe zur Verfügung:
systerm
: Beendet das Programm. Es muss darauf geachtet werden, dass Programme mit
systerm
stets korrekt beendet werden, da der Prozessor sonst versucht, den Rest des
Programmspeicher auszuführen.sysout "<Text>"
: Gibt einen Text aus, der zwischen "
steht.
Zeichenketten müssen immer in "
umschlossen werden.sysout <Register>
: Interpretiert den Inhalt des Registers im Zweierkomplement und gibt den
Wert aus.sysin <Register>
: Wartet auf eine Nutzereingabe und schreibt den Wert codiert im
Zweierkomplement in das Register.Zur Linken sind verschiedene Buttons (nicht gleichzeitig) zu sehen, welche näher erläutert werden:
![]() | Hilfeseite: Verlinkt zu dieser Seite. |
![]() | Darstellung umschalten: Wenn Inhalte von Registern, Speicherzellen und Weiterem angezeigt werden, lässt sich deren Darstellung als Zahl nach Bedarf auswähen: Vorzeichenlos binär/dezimal/hexadezimal oder dezimal mit Vorzeichen. Adressen hingegen werden üblicherweise immer hexadezimal ausgedrückt (sowie auch dezimal). |
![]() | Testumgebungen: Es ist möglich, die Inhalte der Register und des Datenspeichers vorab zu initialisieren sowie nach erfolgreichem Programmlauf mit geforderten Ergebnissen zu vergleichen. |
![]() | Programm übersetzen: Übersetzt das Programm und generiert das Maschinenprogramm. Fehler und zusätzliche Informationen werden in der Konsole unten links angezeigt. |
![]() | Statische Analyse: Gibt Informationen und Hinweise zu dem Programm unter dem aktuell ausgewählten Test aus. |
![]() | Programm debuggen: Das Programm muss übersetzt sein. Initialisiert den Prozessor und wartet vor der ersten Instruktion. Es wird danach an Breakpoints gehalten. |
![]() | Programm starten: Das Programm muss übersetzt sein. Startet die Ausführung. Es wird nicht an Breakpoints gehalten. |
![]() | Ausführung anhalten/pausieren. |
![]() | Ausführung fortführen |
![]() | Einen Takt ausführen. |
![]() | Ausführung stoppen. |
Strg+C/V
im Editor: Kopieren/EinfügenStrg+Z/Y
im Editor: Rückgängig/WiederholenStrg+S
: Inhalt des Editors speichernDas Programm lässt sich ausführen, wenn der Assemblierer keine Syntaxfehler im Quellcode gefunden hat. Während der Ausführung kann die Ausführung angehalten oder gestoppt werden. Wenn die Ausführung angehalten wurde, kann der aktuelle Zustand des Prozessors eingesehen werden. Wenn die Ausführung mit „Programm debuggen“ gestartet wurde, wird ebenfalls automatisch an Breakpoints gehalten. Breakpoints können durch Klicken auf den Rand des Editors gesetzt werden. Der Prozessor wird dann direkt vor der entsprechenden Instruktion angehalten. Angehalten kann das Programm fortgesetzt oder ein einzelner Takt durchlaufen werden.
Zu jeder Instruktion des Programmspeichers wird deren Adresse, die Zeile, aus welcher die Instruktion im Quellcode stammt, sowie die Binärdarstellung und die Interpretation dargestellt. Die Bits sind dabei in zusammengehörende Teile gruppiert und nicht relevante Bits ausgeblendet. Dabei geben die vorderen Bits (Bits 26-31) den Op-Code an, der die Operation der Instruktion codiert.
Es können eine Menge von Tests aus einer bereitgestellten Datei geladen werden. Ein Test enthält eine Belegung einiger Register sowie des Datenspeichers vor Beginn der Ausführung und eine Belegung, die nach Ende des Programm zu erreichen ist. Nachdem ein Programm ausgeführt wurde, kann auf der Oberfläche der Ist-Zustand mit dem Soll-Zustand verglichen werden. Die statische Analyse bezieht sich auf den aktuell ausgewählten Test, wenn vorhanden.
Es ist manchmal sinnvoll, Register passend der aktuellen Situation zu benennen oder „magischen Zahlen“ einen Namen zu geben.
Deshalb gibt es die alias
-Direktive. Ebenfalls kann man arithmetische Ausdrücke formen. Diese
werden wohlgemerkt beim Übersetzen ausgewertet und nicht zur Laufzeit.
Makros (auch Pseudoinstruktionen genannt) können definiert und an beliebigen Stellen wie eine Instruktion benutzt werden.
Sie sind praktisch, wenn man mehrere Operationen zusammenfassen und nicht ständig ausschreiben oder ihnen
eine sofort ersichtliche Bedeutung geben möchte. Beispielsweise lässt sich eine mov
-Pseudoinstruktion schreiben, die ein Register
kopiert. Makros dürfen, wie im Beispiel, mehrere Platzhalter haben, in denen etwas eingesetzt wird. Es ist wichtig zu verstehen, dass Makros beim
Übersetzen durch deren Inhalt ersetzt und die Assemblerinstruktionen darin ausgeschrieben werden.
Nachfolgend werden alle Befehle des ourMIPS-Prozessors vorgestellt. ri
, rj
, rk
stehen dabei für beliebige Register, v
für eine beliebige Zahl
und a
für eine beliebige Programmadresse, ausgedrückt als Label. Grundsätzlich setzen alle Befehle, welche die ALU verwenden, das Overflow-Flag, wenn bei Addition oder Subtraktion ein Overflow auftrat.
Op-Code | Assemblercode | Beschreibung |
---|---|---|
111000 | ldd ri, rj, v | Lädt den Inhalt der Speicherzelle mit Adresse ri +v in rj . |
111001 | sto ri, rj, v | Schreibt den Inhalt von rj in die Speicherzelle mit Adresse ri +v . |
Op-Code | Assemblercode | Beschreibung |
---|---|---|
011000 | shli ri, rj, v | Schiebt alle Bits von ri um v mod 32 Stellen nach links, füllt rechts mit Nullen auf und legt das Ergebnis in rj ab. |
011001 | shri ri, rj, v | Schiebt alle Bits von ri um v mod 32 Stellen nach rechts, füllt links mit Nullen auf und legt das Ergebnis in rj ab. |
011010 | roli ri, rj, v | Schiebt alle Bits von ri um v mod 32 Stellen nach links und rechts wieder herein und legt das Ergebnis in rj ab. |
011011 | rori ri, rj, v | Schiebt alle Bits von ri um v mod 32 Stellen nach rechts und links wieder herein und legt das Ergebnis in rj ab. |
011100 | subi ri, rj, v | Subtrahiert von dem Wert in ri den Wert v und legt das Ergebnis in rj ab. Die Codierung von v wird zuerst von 16 Bit auf 32 Bit vorzeichenerweitert. Setzt das Overflow-Flag genau dann, wenn im Zweierkomplement das Vorzeichen beider Operanden verschieden war, aber das Ergebnis ein anderes Vorzeichen als ri hat. |
011101 | addi ri, rj, v | Addiert auf den Wert in ri den Wert v und legt das Ergebnis in rj ab. Die Codierung von v wird zuerst von 16 Bit auf 32 Bit vorzeichenerweitert. Setzt das Overflow-Flag genau dann, wenn im Zweierkomplement das Vorzeichen beider Summanden gleich war, aber das des Ergebnis' davon verschieden. |
Op-Code | Assemblercode | Beschreibung |
---|---|---|
010000 | shl ri, rj, rk | Schiebt alle Bits von ri um rj mod 32 Stellen nach links, füllt rechts mit Nullen auf und legt das Ergebnis in rk ab. |
010001 | shr ri, rj, rk | Schiebt alle Bits von ri um rj mod 32 Stellen nach rechts, füllt links mit Nullen auf und legt das Ergebnis in rk ab. |
010010 | rol ri, rj, rk | Schiebt alle Bits von ri um rj mod 32 Stellen nach links und rechts wieder herein und legt das Ergebnis in rk ab. |
010011 | ror ri, rj, rk | Schiebt alle Bits von ri um rj mod 32 Stellen nach rechts und links wieder herein und legt das Ergebnis in rk ab. |
010100 | sub ri, rj, rk | Subtrahiert von dem Wert in ri den Wert in rj und legt das Ergebnis in rk ab. Setzt das Overflow-Flag genau dann, wenn im Zweierkomplement das Vorzeichen beider Operanden verschieden war, aber das Ergebnis ein anderes Vorzeichen als ri hat. |
010101 | add ri, rj, rk | Addiert auf den Wert in ri den Wert in rj und legt das Ergebnis in rk ab. Setzt das Overflow-Flag genau dann, wenn im Zweierkomplement das Vorzeichen beider Summanden gleich war, aber das des Ergebnis' davon verschieden. |
Op-Code | Assemblercode | Beschreibung |
---|---|---|
010111..00 | or ri, rj, rk | Verknüpft die jeweiligen Bits in ri mit denen aus rj durch Oder und legt das Ergebnis in rk ab. |
010111..01 | and ri, rj, rk | Verknüpft die jeweiligen Bits in ri mit denen aus rj durch Und und legt das Ergebnis in rk ab. |
010111..10 | xor ri, rj, rk | Verknüpft die jeweiligen Bits in ri mit denen aus rj durch exklusives Oder und legt das Ergebnis in rk ab. |
010111..11 | xnor ri, rj, rk | Verknüpft die jeweiligen Bits in ri mit denen aus rj durch exklusives Nicht-Oder und legt das Ergebnis in rk ab. |
Op-Code | Assemblercode | Beschreibung |
---|---|---|
100000 | jmp a | Setzt den Befehlszähler auf die direkt angegebene Adresse (symbolisiert durch das angegebene Label), sodass im nächsten Takt die Instruktion an Adresse a ausgeführt wird. |
100010 | beq ri, rj, a | Springt zu Adresse a , wenn ri =rj . In der Instruktion wird die Adresse relativ zur Adresse dieses Sprungbefehls codiert. |
100011 | bneq ri, rj, a | Springt zu Adresse a , wenn ri ≠rj . In der Instruktion wird die Adresse relativ zur Adresse dieses Sprungbefehls codiert. |
100100 | bgt ri, rj, a | Springt zu Adresse a , wenn die Werte ri >rj . Die Bits in den Registern werden dabei jeweils als einen vorzeichenlosen Wert interpretiert. In der Instruktion wird die Adresse relativ zur Adresse dieses Sprungbefehls codiert. |
100101 | bo ri, rj, a | Springt zu Adresse a , wenn das Overflow-Flag gesetzt ist. In der Instruktion wird die Adresse relativ zur Adresse dieses Sprungbefehls codiert. |
Op-Code | Assemblercode | Beschreibung |
---|---|---|
001000 | ldpc ri | Der Inhalt des (16 Bit) Befehlszählers, also die Adresse dieser Instruktion, wird in die unteren 16 Bit von ri geschrieben. Die oberen 16 Bit werden auf 0 gesetzt. |
001001 | stpc ri | Die unteren 16 Bit von ri werden in den Befehlszähler geschrieben und geben somit die Adresse der nächsten auszuführenden Instruktion an. |