MIPS Assembler – Hello World mit Qtspim

Nach der ersten Vorlesung in „Technische Grundlagen der Informatik 2“ wurde uns heute verraten, dass wir mit dem MIPS Assembler arbeiten werden, um die Funktionsweise von Rechenmaschinen zu verstehen. Ich wollte mal ein Hello World vorstellen.

Ich denke, ich lehne mich mit diesem Artikel diesmal ein wenig aus dem Fenster, da ich mich bisher nicht viel mit dem MIPS Assembler auseinandergesetzt habe, und nur auf mein Allgemeinwissen über Assembler bzw. eine kurze Recherche bezüglich MIPS zurückgreifen kann. Fehler in der Nutzung der vers. Register mit ggf. vorhandenen Besonderheiten seien mir bitte (vorerst) verziehen und ggf. durch konstruktive Kritik ausgebügelt ;)

Was ist der MIPS Assembler

MIPS steht für „Microprocessor without Interlocked Pipeline Stages“ – scheint also ein Mikroprozessor zu sein, welchem man oft in eingebetteten Systemen begegnen kann. Der Assembler ist ein Befehlssatz zur Programmierung dieses Mikroprozessors.

Assembler ist eine Zwischenstufe zwischen dem Quellcode einer Hochsprache (z.B. C/C++/Java) und dem direkten Maschinencode (lauter 00111en ;) ). Daraus kann man sich ableiten, dass Assembler nicht sehr „leicht“ zu lesen ist, und es ein wenig Aufwand sein kann, bis man ein tolles Programm geschrieben hat. Man sieht beim Assemblercode die einzelnen Operatoren (Opcodes), sowie die Operanden (Register), welche vom Prozessor sequentiell abgearbeitet werden. Bei einer Hochsprache übernimmt ein Compiler die Übersetzung des Quelltextes in Assembler bzw. den Maschinencode.

Bei einem MIPS stehen einem 32 Register mit jeweils 32 Bit Breite (1 Wort) zur Verfügung. Ein Wort = 4 Byte = 32 Bits.

MIPS Simulator Qtspim installieren

Zum Simulieren des MIPS Prozessors benötigen wir einen entsprechenden Simulator. Das Paket nennt sich Qtspim und sollte leicht über apt bzw. den Paketmanager installierbar sein.

sudo apt-get install qtspim

Den Quelltext bzw. ein vorkompiliertes Paket findet man hier: http://spimsimulator.sourceforge.net/ (Auch Windows). Die Simulationsumgebung lässt sich danach einfach über den Befehl „qtspim“ starten.

Ein Hello World

Ein Assemblerprorgamm besteht im Quelltext oft aus mehreren Segmenten, welche zur Trennung der Daten bzw. des Codes dienen. Beim MIPS Assembler heißen diese u.a.

  • .data
  • .text

wobei, der Datenteil immer über dem Codeteil steht.

Im Datensegment werden Ressourcen festgelegt, welche später aus dem RAM in die Register geladen werden können. Das umfasst Strings, Integers, Characters, Arrays, usw.

Unser kleines Hello-World-Programm kann so aussehen:

.data    #Datensegment
    hello: .asciiz "Hello world!\n" #String hello festlegen

.text    #Codesegment
main:    #Mainfunktion
    la $a0, hello   #Adresse von hello in $a0 laden (load address)
    li $v0, 4       #print string (Anweisung #4) laden (load immediately)
    syscall         #string ausgeben (Hello world) (systemfunktion aufrufen)
    j exit          #Programm beenden (jump)
exit:   #Exitfunktion
    li $v0, 10  #exit code (Anweisung #10)
    syscall     #exit programm (Anweisung ausführen)

Im Datensegment legen wir einen Speicherbereich „hello“ vom Typ „asciiz“, also ein Null-terminierten String, mit dem Hallo-Welt Inhalt an. Alle Speicherreservierungen laufen nach diesem Schema ab.

[NAME]: [TYP] [WERT]

In dem Codesegment definieren wir zwei Funktionen:

  • main
  • exit

wobei die main-Funktion als Einstiegspunkt des Programms gilt, und die exit Funktion später aufgerufen wird, um das Programm zu beenden.

In der main-Funktion laden wir zunächst die Adresse (Offset) des Hello-World Strings in $a0 geladen, was später als Parameter für die Printanweisung dienen wird. Als nächstes laden wir die gewünschte Anweisung ins $v0-Register, wobei 4 für „print string“ steht. Der Aufruf „syscall“ führt diese zuvor festgelegte Anweisung mit den in $a0-$a3 abgelegten Daten auf. Hier wird unser String dann ausgegeben.

Ist dieser Schritt getan, springen wir aus der Main-Funktion zur exit-Funktion, welche das Programm mit einem exit (Anweisung: 10) beendet.

Damit haben wir unser erstes Hello-World im MIPS-Assembler geschrieben :)

Wenn ihr das Prorgamm ausführen wollt, dann müsst ihr Qtspim starten, und folgende Schritte durchführen:

  1. File -> Reinitialize and Load File
  2. Datei suchen und öffnen
  3. |> (Playbutton) drücken
  4. Zum erneuten Ausführen „Clear registers“ drücken und zu Schritt 3 zurückkehren.

Buchstaben einzeln ausgeben

Es folgt noch ein kleines weiteres Beispiel mit einer Schleife, welches jedes Zeichen einzeln ausgibt.

.data
    hello: .asciiz "Hello world!\n" 
.text
main:
    la $t0, hello           #Adresse von hello in stack laden
    move $t1, $0            #counter $t1 mit 0 initialisieren

    jal printchar           #char ausgeben

    slt $t2, $t1, 13       #Counter < 13? Ja - $t2 ist 1; andernfalls 0
    beq $t2,1, printchar    #Ist $t2 1? Springe zu printchar

    j exit                  #Programm beenden

printchar:
    add $t2, $t0, $t1       #Offset berechnen (Offset+counter)
    lb $a0, ($t2)           #Zeichen laden

    li $v0, 11              #Char ausgeben
    syscall                 #

    add $t1, $t1,1          #Counter erhoehen (counter++)

    jr $ra                  #Zuruek zum Aufruf springen

exit:
    li $v0, 10              #exit code
    syscall                 #exit programm

Dabei wird ein Counter immer erhöht, welcher dann auf den Offset des Strings addiert wird, um das aktuelle Zeichen zu ermitteln. Ist dieser Counter kleiner als 13, so wird Register $t2 auf 1 gesetzt. (slt = set on lower than) Wenn das der Fall ist, so wird wieder in die printchar-Funktion gesprungen (beq = branch on equal). Ist dies irgendwann nicht mehr der Fall, dann wird nicht gesprungen, und wir erreichen das Programmende.

Fazit

Eigentlich schreibt man Programme in Assembler, wenn man das letzte Quäntchen Geschwindigkeit aus einem Algorithmus quetschen möchte. Heutzutage optimieren die Compiler manchmal den Assemblercode besser, als die eigene Implementierung einer Funktion. Trotzdem macht es Spaß einmal „hinter den Kulissen“ einer Hochsprache selbst ein hardwarenahes Programm zu entwickeln und selbst auf die Belegung der Register achten zu müssen.

~ Sebastian

Quellen:

Syscall Liste: http://courses.missouristate.edu/kenvollmar/mars/help/SyscallHelp.html
Subroutinen: http://people.cs.pitt.edu/~xujie/cs447/Mips/sub.html
Befehlsübersicht: http://www.mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html
Quick Tutorial/Register: http://logos.cs.uic.edu/366/notes/mips%20quick%20tutorial.htm