MIPS Archives - Technik - Blogbasis.net https://technik.blogbasis.net/tag/mips Die Basis des freien Wissens – Technik Tue, 21 May 2013 18:22:59 +0000 de hourly 1 https://wordpress.org/?v=6.8.1 Schleifen in MIPS Assembler umsetzen https://technik.blogbasis.net/schleifen-in-mips-assembler-umsetzen-21-05-2013 https://technik.blogbasis.net/schleifen-in-mips-assembler-umsetzen-21-05-2013#respond Tue, 21 May 2013 18:22:46 +0000 http://technik.blogbasis.net/?p=545 In diesem Artikel soll es um die verschiedenen Schleifentypen und deren Umsetzung in MIPS Assembler gehen. Die Schleifen sehen in Assembler alle relativ gleich aus.

Für jeden Schleifentyp werde ich einen kleinen C-Code angeben, und diesen daraufhin in MIPS Assembler übersetzen.

For Schleife

Die wohl am weitesten verbreitete Schleife ist die sog. For-Schleife. Diese wird oft genutzt, um n-mal einen Befehlsblock auszuführen.

C-Code:

int i;
for(i = 0; i < 10; i++) {
//Mach was 10 mal
}

Wenn wir diese Schleife in MIPS Assembler übersetzen wollen, können wir das wie folgt tun:

MIPS ASM:

add $t0, $zero, $zero #$t0 = i = 0
addi $t1, $zero, 10 #$t1 = 10
for:
    bge $t0, $t1, endfor # IF ($t0 >= $t1) GOTO endfor
    #Befehle
    addi $t0, $t0, 1 #i++
    j for #GOTO for
endfor:
    #Weiter gehts

Wir müssen zunächst in zwei temporären Registern den Startwert von i bzw. die obere Grenze (10) festlegen. Als nächstes benötigen wir zwei Labels (for, endfor) zu denen wir springen können. In der For-Schleife prüfen wir zunächst, ob der Zähler größer gleich 10 ist. Ist dies der Fall, dann springen wir aus der Schleife zum Label „endfor“. Andernfalls werden die Befehle in der Schleife ausgeführt, und der Zähler inkrementiert, sowie wieder zum „for“-Label gesprungen.

While-Schleife

Mit einer While-Schleife kann man Befehle solange ausführen, bis eine bestimmte Abbruchbedingung eingetreten ist.

C-Code:

int sum = 0
while(sum < 20) {
sum = sum*2;
}

MIPS ASM:

add $t0, $zero, $zero #$t0 = sum = 0
addi $t1, $zero, 20 #$t1 = 20
while:
    bge $t0, $t1, endwhile #IF($t0 >= $t1) GOTO endwhile
    mult $t0, $t0, 2 #$t0 = $t0 * 2
    j while #GOTO while
endwhile:
#Weitere Befehle

Wie man hier erkennt, gibt es keinen großen Unterschied zur For-Schleife, außer das der Schleifenrumpf anderen Code enthält.

Repeat until bzw. Do while Schleifen

Bei diesem Schleifentyp wird die Prüfung des Schleifenkopfes am Ende des Schleifendurchlaufs ausgeführt, sodass die Schleife mindestens einmal durchlaufen wird. Dies können wir im Assembler  erreichen, in dem wir die „branch“-Anweisung nach dem Schleifenrumpf auswerten.

Fazit

In Assembler lassen sich Schleifen sehr leicht umsetzen, da sie immer auf dem gleichen Schema basieren, und es kaum Unterschiede zwischen diesen gibt.

~ Sebastian

]]>
https://technik.blogbasis.net/schleifen-in-mips-assembler-umsetzen-21-05-2013/feed 0
MIPS Cross-Compiler einrichten https://technik.blogbasis.net/mips-cross-compiler-einrichten-24-04-2013 https://technik.blogbasis.net/mips-cross-compiler-einrichten-24-04-2013#respond Wed, 24 Apr 2013 18:00:17 +0000 http://technik.blogbasis.net/?p=462 Ich hatte vor einigen Tagen einen Artikel über den MIPS Assembler geschrieben. Ich habe seit der letzten Woche dazu eine Hausaufgabe aufbekommen, in der steht, man solle den C Code in MIPS Assembler umschreiben, sodass das Ergebnis 1:1 wie Compilerproduziert aussieht.

Da ich dieses Anspruch für etwas utopisch halte, habe ich mir fix einen MIPS Cross-Compiler zugelegt, um die beiden Quelltexte zu vergleichen.

Installation des MIPS Cross-Compilers

Diesmal hatte ich nicht viel Lust mir die ganzen Pakete vom Source zu kompilieren, und habe auf die vorkompilierten Pakete zurückgegriffen. Diese können wir uns ganz einfach ins /opt Verzeichnis herunterladen und dort entpacken.

cd /opt
sudo mkdir mips-gcc
cd mips-gcc
sudo wget http://d2gosfejro2q5l.cloudfront.net/Mips_linux_toolchain_bin-1.1.tar.bz2
sudo tar xfvj Mips_linux_toolchain_bin-1.1.tar.bz2

Damit ist die Installation fast schon getan. Wenn wir den Cross-Compiler nutzen möchten, müssen wir noch die PATH-Variable anpassen. Das klappt einfach mit einem export:

export PATH="$PATH:/opt/mips-gcc/mips_linux_toolchain/bin"

MIPS Cross-Compiler nutzen

Wenn wir den von uns geschriebenen Assemblercode mit dem des Compilers vergleichen wollen, müssen wir erstmal den zu übersetzenden Code in C implementieren. Ein kleines Beispiel dafür wäre dieser Code:

int add(int a, int b);
int main(char **args) {
    (void) add(1,2);
}
int add(int a, int b) {
    return a+b;
}

Wenn wir diesen Quelltext abspeichern und danach den mips-linux-gnu-gcc aufrufen, haben wir die Binary, welche auf einem MIPS laufen würde.

mips-linux-gnu-gcc code.c -o code 

Die Binary decompilen

Möchten wir wieder an den Assemblercode gelangen, so nutzen wir einfach das Tool namens objdump:

mips-linux-gnu-objdump -d code > code.asm

Wenn man nun die .asm mit einem Editor öffnet, findet man darin unsere „add“ Funktion:

004005ac : <add>:
  4005ac:       27bdfff8        addiu   sp,sp,-8
  4005b0:       afbe0004        sw      s8,4(sp)
  4005b4:       03a0f021        move    s8,sp
  4005b8:       afc40008        sw      a0,8(s8)
  4005bc:       afc5000c        sw      a1,12(s8)
  4005c0:       8fc30008        lw      v1,8(s8)
  4005c4:       8fc2000c        lw      v0,12(s8)
  4005c8:       00621021        addu    v0,v1,v0
  4005cc:       03c0e821        move    sp,s8
  4005d0:       8fbe0004        lw      s8,4(sp)
  4005d4:       27bd0008        addiu   sp,sp,8
  4005d8:       03e00008        jr      ra
  4005dc:       00000000        nop

Wie wir sehen, macht der Compiler kräftigen Gebrauch vom Stack. Wir können hingegen ein viel kürzeres Programm schreiben. Folgende Zeilen beinhalten das komplette Programm inklusive der zweizeiligen Add (hier umbenannt, da sonst der Simulator meckert) Funktion:

.data
aa:  .word   1
bb:  .word   2

.text
main:
    lw $a0, aa  #Variable a ins $a0 laden
    lw $a1, bb  #Variable b ins $a1 laden
    jal ownadd     #Add aufrufen
    j exit      #Beenden

ownadd:
    add $v0, $a0, $a1   #a+b rechnen und ins $v0 ablegen
    jr $ra              #Zurückspringen

exit:
    li $v0, 10          #Exitcode
    syscall

Fazit

Ich denke die Forderung, einen Code 1:1 wie vom Compiler zu schreiben ist bzw. war übertrieben oder nicht so gemeint. Trotzdem ist es interessant zu sehen, welche Unterschiede zwischen dem Compiler generierten Code und dem selbst geschriebenen Code entstehen können.

~ Sebastian

]]>
https://technik.blogbasis.net/mips-cross-compiler-einrichten-24-04-2013/feed 0
MIPS Assembler – Hello World mit Qtspim https://technik.blogbasis.net/mips-assembler-hello-world-mit-qtspim-09-04-2013 https://technik.blogbasis.net/mips-assembler-hello-world-mit-qtspim-09-04-2013#respond Tue, 09 Apr 2013 20:43:04 +0000 http://technik.blogbasis.net/?p=423 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

]]>
https://technik.blogbasis.net/mips-assembler-hello-world-mit-qtspim-09-04-2013/feed 0