p. 1/28 Programowanie w asemblerze MIPSa (ciąg dalszy)
p. 2/28 Przypomnienie Trzy poziomy języka: Język maszynowy - kody operacji, wszystkie adresy, numery rejestrów i stałe zakodowane za pomoca zer i jedynek; Prawdziwy asembler - kody operacji zapisywane symbolicznie (np. add, lw,...), symboliczne nazwy rejestrów, adresy zastapione etykietami, stałe podawane dziesiatkowo; Pseudoasembler - mamy dodatkowe instrukcje nie majace bezpośrednich odpowiedników w języku maszynowym, tłumaczone na (krótka, czasem jednoelementowa!) sekwencje rozkazów maszynowych
p. 3/28 Typy rozkazów typu R (rejestrowe) add $t0, $s1, $s2, mult $s1, $s2, slt $t3, $s2, $s1,... 6 bitów kodu operacji trzy pola po 5 bitów numery rejestrów 5 bitów shamt 6 bitów funkyjnych - dodatkowe szczegóły operacji typu I (z argumentem stałym) addi $t0, $s1, -7, bne $s1, $s2, etyk, lw $s1, 4($s6),... 6 bitów kodu operacji dwa pola po 5 bitów na numery rejestrów 16 bitów na stała typu J (skoki) j etyk 6 bitów na kod operacji + 26 bitów na stała
p. 4/28 Przykładowa organizacja pamięci $sp 7fff fffc hex Stack $gp pc 1000 8000 hex 1000 0000 hex 0040 0000 hex 0 Dynamic data Static data Text Reserved
p. 5/28 Przykład programu Przykład kompletnego programu (z duża liczba pseudorozkazów). Program został zaczerpnięty z Krotkiego wprowadzenia do SPIM-a autorstwa Salah A. Almajdouba. Jego zadaniem jest dodanie elementów tablicy.
p. 6/28.text.globl main main: lw $s0, Size # czyta rozmiar tablicy li $s1, 0 # indeks i li $s2, 0 # s2 bedzie przechowywac sume li $t2, 4 # t2 zawiera stala 4 loop: mul $t1, $s1, $t2 # t1 dostaje i * 4 lw $s3, Nstart($t1) # s3 = N[i] add $s2, $s2, $s3 # sum = sum + N[i] addi $s1, $s1, 1 # i = i + 1 beq $s1, $s0, STOR # skaczemy do STOR na koniec j loop STOR: sw $s2, Result # wynik zachowujemy w Result.data Nstart:.word 8, 25, -5, 55, 33, 12, -78 Size:.word 7 Result:.word 0
p. 7/28 Tłumaczenie pseudoinstrukcji rozkaz tłumaczenie komentarz lw $s0, Size lui $1, 4097 load upper immediate lw $16, 28($1) li $s1, 4 ori $17, $0, 4 logiczna alternatywa ze stała mul $t1, $s1, $t2 mult $s1, $t2 mflo $t1 lw $s3, Nstart($t1) lui $1, 4097 addu $1, $1, $9 lw $19, 0($1) SPIM traktuje mul jak rozkaz
p. 8/28 Wspomaganie definiowania procedur i funkcji int funkcja (int n){ main(){ int i,j; int i,j; (...) i=funkcja(i); return j;... } j=funkcja(i+6); } po skoku do funkcji i jej wykonaniu musimy wrócić w odpowiednie miejsce do funkcji należy jakoś przekazać parametry i jakoś odebrać wyniki funkcja operuje na pewnych rejestrach, być może tych samych co program główny
p. 9/28 Wywołanie i powrót z funkcji Rejestr $ra służy do przechowywania adresu powrotu z funkcji. Rozkaz jal fun (jump and link) skacze do etykiety fun i zapamiętuje aktualna wartość licznika rozkazów pc. Powrót z funkcji zapewnia rozkaz jr $ra (jump register) - skocz pod adres zapisany w rejestrze (w ogólnym przypadku nie musi to być rejestr $ra.
p. 10/28 Argumenty i wyniki funkcji Zalecana jest następujaca konwencja: Parametry sa przekazywane w rejestrach $a0-$a3. Wyniki zwracane sa w rejestrach $v0, $v1. Jeśli potrzebujemy większej licbzy parametrów lub wyników możemy je przekazać za pomoca stosu
p. 11/28 Zmienne lokalne W funkcji chcemy użyć $s1, $s2, $s3. Ich wartości moga być jeszcze potrzebne w miejscu wywołania. Zapamiętujemy ich aktualna wartość na stosie, wykonujemy obliczenia i na koniec odtworzy wartości rejestrów. Fun: addi $sp, $sp, -12 sw $s1, 8($sp) sw $s2, 4($sp) sw $s3, 0($sp) (...) lw $s3, 0($sp) lw $s2, 4($sp) lw $s1, 8($sp) addi $sp, $sp, 12 jr $ra
Argumenty, wyniki i wywołanie (...) addi $a0, $zero, 13 add $a1, $zero, $s2 jal Fun addi $s1, $zero, $v0 (...) Fun: addi $sp, $sp, -12 sw $s1, 8($sp) sw $s2, 4($sp) sw $s3, 0($sp) (...) lw $s3, 0($sp) lw $s2, 4($sp) lw $s1, 8($sp) addi $sp, $sp, 12 jr $ra p. 12/28
p. 13/28 Zagnieżdżone wywołania Problem: jeśli funkcja wywołuje koleja funkcję to tracimy adres powrotu Jeśli zatem zezwalamy na zagnieżdżone wywoływanie funkcji, to musimy odkładać adres powrotu ($ra) również na stosie. Na następnym slajdzie pokażemy przykład programu z funkcja wyliczajac a rekurencyjnie sumę liczb od 1 do n (n jest parametrem funkcji).
sumton: addi $sp,$sp,-8 # miejsce na dwa elementy na stosie sw $ra,4($sp) # odkladamy adres powrotu sw $a0,0($sp) # odkladamy argument bne $0,$a0,rec # skocz jesli arg rozny od 0 addi $v0,$zero,0 # ustawiamy wynik na 0 lw $ra,4($sp) # odtworzenie adresu powrotu # (w tym miejscu niekonieczne, ale # tak jest bardziej elegancko...) addi $sp,$sp,8 # zwalniamy dwa miejsca na stosie jr $ra # powrot z funkcji rec: addi $a0,$a0,-1 # n := n-1 jal sumton # wywolanie z parametrem n-1 lw $a0,0($sp) # odtworzenie a0 # w $v0 jest wynik sumton(n-1) add $v0,$a0,$v0 # $v0 = n + sumton(n-1) lw $ra,4($sp) # odtworzenie adresu powrotu addi $sp,$sp,8 # zwalniamy dwa miejsca na stosie jr $ra # powrot z funkcji main: addi $a0,$zero, 10 # wywolanie z parametrem 10 jal sumton addi $a0, $v0, 0 # wypisanie wyniku li $v0, 1 # wypisanie wyniku p. 14/28
p. 15/28 Konwencja Strona wywoływana powinna zachowywać (odtworzyć przy powrocie): wartości rejestów $s0-$s7 wartość $sp adres powrotu $ra oczywiście nie wolno jej naruszyć pamięci powyżej $sp Nie musi się natomiast troszczyć o: wartości rejestów $t0-$t9 argumenty wywołania $a0-$a3 zawartość stosu poniżej $sp Jest to oczywiście tylko jedna z kilku możliwych konwencji.
p. 16/28 Uwaga o rejestrze $fp Podczas wywoływania funkcji na stosie odkładana jest tak zwana ramka wywołania (zawierajaca potrzebne wartości rejestrów, ewentualnie dodatkowe zmienne lokalne, argumenty wywołania czy zwracane wartości). My odwoływaliśmy się do wartości z ramki tylko za pomoca rejestru $sp. Czasem używa się dodatkowo rejestru $fp (frame pointer) wskazujacego na najwyższy adres ramki. Wtedy w funkcji możemy np. modyfikować wartość $sp. Użycie $fp nie jest jednak konieczne. I tak np. kompilator GNU MIPS C używa $fp, ale kompilator C z MIPS/Silicon Graphics radzi sobie bez niego (i używa $fp jako kolejnego rejestru na zmienne)
p. 17/28 Operacje na napisach Dotychczas operowaliśmy na słowach (32-bitowych). Istnieje też dostęp do poszczególnych bajtów. lb $s2, 3($s1) - load byte rozkaz typu I kopiuje bajt o podanym adresie do 8 najmniej znaczacych bitów rejestru i wypełnia resztę rejestru najbardziej znaczacym bitem bajtu (działa przy reprezentacji uzupełnień do 2, jeśli chcemy uzupełnić zerami używamy lbu load byte unsigned). sb $s2, -2($s1) - store byte - kopiuje 8 najmniej znaczacych bitów rejestru. Zazwyczaj powyższe rozkazy używane do operacji na tekstach.
p. 18/28 str1:.data.asciiz "Przykładowy napis.".text.globl main strlen: addi $v0, $zero, 0 # -v0 - licznik loop : lb $t0, 0($a0) beq $t0, $zero, EXIT addi $a0, $a0, 1 addi $v0, $v0, 1 j loop EXIT jr $ra main: (...) la $a0, str1 #pseudoroz., tlumaczony na lui i o jal strlen (...)
p. 19/28 Rozkazy logiczne Zestaw operacji bitowych: and, or, nor, andi, ori, nor, xor, xori. W jaki sposób załadować do rejestru konkretny wzorzec bitowy, np. 0x5467a7ba? Nie może być jednej instrukcji - 32 bity! lui $t0, 0x5467 # niższe bity rejestru ustawia na 0. ori $t0, $t0, 0xa7ba Operacje przesunięcia: sll $t0, $t1, 7, analogicznie srl (dopełnia zerami) rozkazy typu R - przesuwamy o maksymalnie 32 pozycje przesunięcie kodowane w polu shamt, (jeden rejestr wolny).
p. 20/28 Mnożenie i dzielenie Mnożenie: mult $s0, $s1 64 bity wyniku. Wynik zapisywany w rejestrach HI i LO. Bardziej znaczac a częśc wyniku można uzyskać rozkazem mfhi $t0, mniej znaczac a: mflo $t1. Także w wersji dla liczb bez znaku multu $s0, $s1 Istnieje pseudorozkaz mul r1, r2, r3 zapisywana jest mniej znaczaca częśc wyniku Dzielenie: div $s2, $s3. Wynik w rejestrze LO, reszta z dzielenia w rejestrze HI. divu dzielenie bez znaku
Liczby zmiennopozycyjne Na liczbach zmiennopozycyjnych operuja inne układy cyfrowe niż na całkowitych Mamy więc inne rozkazy oraz specjalny zestaw rejestrów zmiennopozycyjnych - $f0, $f31. Jednostka zmiennopozycyjna nazywana jest koprocesorem 1. Rozkazy add.s, sub.s, add.d, sub.d (format R), odpowiednio single i double precision. Musza być nowe rozkazy przesyłania - 32 rejestry - 5 bitów! lwc1 $f1, 40($s1), swc1, ldc1, swc1 dla trybu double. Uwaga: rejestry sa 32-bitowe, a format zmiennopozycyjny double 64-bitowy. W przypadku double wartości przechowywane sa w parach rejestrów odwołujemy się tylko do rejestrów parzystych. p. 21/28
Liczby zmiennopozycyjne -cd. Przykład wczytywania liczb zmiennopozycyjnych:.data.text floats:.float -0.123, 3.14 (...) lwc1 $f0, floats lwc1 $f1, floats + 4 Dla liczb zmiennopozycyjnych sa osobne rozkazy skoku: c.eq.s $f1, $f2 bc1t bc1f Label Label2 Jeśli rejestry $f1 i $f2 przechowuja ta sama wartość to skocz pod Label, w przeciwnym wypadku pod Label2. (wynik porównania w specjalnym jednobitowym rejestrze niedostępnym dla programisty). p. 22/28
p. 23/28 Liczby zmiennopozycyjne -cd. Mnożenie: mul.s $f0, $f1, $f2 Dzielenie: div.s...
p. 24/28 Wejście-wyjście Prawdziwe procesory MIPS używaja tzw. wejścia-wyjścia odwzorowanego w pamięci (memory mapped IO), tzn. operuja na IO za pomoca lw, sw. Jest tylko jedno urzadzenie wejściowe (klawiatura) oraz jedno wyjściowe (monitor). Każde z nich ma dwa rejestry: 0xffff0000 - rejestr sterujacy wejściem (tylko dwa ostatnie bity maja znaczenie) 0xffff0004 - rejestr danych wejściowych (przechowuje jeden bajt) 0xffff0008 - rejestr sterujacy wyjściem (tylko dwa ostatnie bity maja znaczenie) 0xffff000c - rejestr danych wyjściowych
p. 25/28 Wejście-wyjście cd. Załóżmy, ze chcemy wczytać pojedynczy bajt do rejestru $s2: lui $t0, oxffff lw $s2, 4 ($t0) Problem: chcemy odczytywać tylko gdy urzadzenie wejściowe jest gotowe. Musimy wićc najpierw sprawdzić stan urzadzenia. A konkretnie pierwszy bit rejestru 0xffff0000. Procesor musi zaczekac aż ten bit przyjmie wartość 1: lui $t0, 0xffff Wait: lw $t1, 0($t0) # ladujemy rejestr do t1 andi $t1, $t1, 0x0001 beq lw t1, $zero, Wait $s2, 4 ($t0) # zeruj wszystkie bity oproc
p. 26/28 Funkcje systemowe Operacje wejścia-wyjścia za pomoca funkcji systemowych: 1 Print an Integer ($a0 = wartość do wydrukowania) 2 Float 3 Print Double 4 Print String ($a0 = adres napisu) 5 Read an Integer (wczytany wynik zwracany w $v0) 6 Read Float 7 Read Double 8 Read a String ($a0 = adres pod jaki należy wpisać, $a1 = długość przydzielonej pamięci) + kilka innych
p. 27/28 Użycie funkcji systemowych.data str:.asciiz "the answer = ".text li $v0, 4 la $a0, str syscall li $v0, 1 li $a0, 5 syscall
p. 28/28 Rysunek z następnego wykładu 0 4 Add Add ALU result M u x 1 Instruction [31 26] Control RegDst Branch MemRead MemtoReg ALUOp MemWrite ALUSrc RegWrite Shift left 2 PC Read address Instruction [25 21] Read register 1 Read Instruction [20 16] data 1 Read register 2 Zero Instruction 0 [31 0] ALU M Read ALU Write 0 data 2 result Instruction u Instruction [15 11] x register M memory u 1 x Write 1 data Registers Address Write data Read data Data memory 1 M u x 0 Instruction [15 0] 16 Sign 32 extend ALU control Instruction [5 0]