程式設計 · 2025 年 5 月 26 日 0

櫛風的MIPS教學

關於本文

整體參考MIPS官方使用教學:https://dpetersanderson.github.io/Help/MarsHelpIntro.html

MIPS是什麼

https://zh.wikipedia.org/wiki/MIPS%E6%9E%B6%E6%A7%8B

記憶體與暫存器

MIPS有兩種存資料的方法,一個是記憶體,一個是暫存器。

MIPS的程式結構

.data
    #數據宣告區
.text
    #程式碼區

數據區的宣告方法

數據區是記憶體宣告的地方。

變數名稱: .型態 資料

範例:
如果我要宣告一個叫做a的整數,存15

a: .word 15

常見型態

  • word ,差不多就是C++的int
  • space,空間,後面接的值代表要多少byte(1word = 4byte)
  • asciiz,字串

(其他用不到就不列了)

陣列宣告可以使用space,比如5格陣列的array就這樣寫

array: .space 20

怎麼把資料從記憶體取出來或是放進去

從暫存器放到記憶體(load word)

lw 暫存器,記憶體位址

從記憶體放到暫存器(save word)

sw 暫存器,記憶體位址

比如要把變數a的資料存進$t0

.data
    a: .word 12
.text
    sw $t0,a

記憶體位址

記憶體位址是一個值,除了像上面的範例中直接用數據的變數名稱。
也可以用la(load address)放到暫存器裡。

la 暫存器,記憶體名稱

此時暫存器存的不是記憶體裡的值,而是記憶體的位址。

記憶體的位址本身也是值,所以可以後面提到的運算來改變它,但要記得1word = 4 byte,所以通常會是加減4的倍數。

暫存器

MIPS有提供很多個暫存器供人使用,暫存器本身有名字跟定義。

下面是比較常會用到的暫存器:

名字 象徵含義
$t0~9 臨時暫存器
$s0~7 靜止暫存器
$a0~3 函式參數
$v0~3 函式回傳值

t跟s在寫程式上基本上沒啥差別,不過你可以當成i、j、k跟num這種變數名稱的差別來思考。

要注意的是,以上這些暫存器他們沒有高階語言的生命週期之類的性質,你還是要當作全部都是全域變數來做思考。

另外還有f開頭的,好像是可以拿來存浮點數,不太清楚。

還有三個特殊的暫存器會被使用到,\$sp、\$hi、\$lo,等使用到時會再提到功能。

暫存器的操作

設值

load immediate

li 暫存器,數值
li $t0,12

i 是即時immediate的意思,你可以理解為「被寫在程式碼的數值」,而不是像之前存在記憶體。

複製

move A暫存器,B暫存器

把B暫存器的值存入A暫存器

運算

反正就是A = B@C,@代入你要的運算子

加法

add A暫存器,B暫存器,C暫存器
addi A暫存器,B暫存器,數值

減法

sub A暫存器,B暫存器,C暫存器
subi A暫存器,B暫存器,數值

(addi數值寫負的也可以當作減法使用)

乘法

mul A暫存器,B暫存器,C暫存器

除法

div A暫存器,B暫存器,C暫存器

如果需要餘數,可以用這個方法

div A暫存器,B暫存器

他的商會存在\$lo,餘數存在\$hi
這兩個暫存器比較特別,你只能用mflo跟mfhi取出來放到暫存器裡。

mflo 暫存器

左移、右移

左移1格(可以當成x2來理解)

sll $t0,1

右移1格(可以當成//2來理解)

srl $t0,1

址取值

如果今天\$t0存的是一個記憶體位址,可以用(\$t0)來當作記憶體位址的值。

基本上是搭配上面的lw跟sw來用。

lw $t1,($t0)

也可以在小括號寫數值,代表往後位移幾格

lw $t1,4($t0)

但是如果今天是要位移$t2格,必須要這樣寫喔

sll $t2,2
add $t0,$t0,$t2
lw $t1,($t0)

不能直接$t2($t0)這樣寫。

syscall

你可以呼叫一些系統服務來做到讀取、寫入等等。
使用方法是先把你的參數存好,然後把$v0設成你要的服務對應的數值,然後執行syscall

$v0 功能 參數 回傳值
1 整數輸出 把要輸出的整數存到$a0
2 浮點數輸出 把要輸出的整數存到$f12
3 雙倍精讀浮點數輸出 把要輸出的整數存到$f12
4 字串輸出 把要輸出的字串記憶體位址存到$a0
5 輸入整數 數值會存到$v0
6 輸入浮點數 數值存到$v0
7 輸入雙倍精讀浮點數 數值存到$v0
8 輸入字串 讀到的字串記憶體位址放在$v0
10 結束,跟C++的exit(0)同個功能
li $a0,10
li $v0,1
syscall

跳轉

基本上就是goto,使用方法是labal
跳轉有三種,j、jal、jr

指令 意思
j 跳轉到指定的label
jal 跳轉到指定的label並把現在位址存進$ra(具體原理不用管,但反正如果需要跳回來就用jal)
jr 前面用過jal,現在要跳回去
main:
    jal other_label
    j end

other_label:
    jr $ra

end:

判斷

判斷基本上就是滿足條件時,j到指定label

指令 意思
beq 如果相等
bne 如果不相等
blt 如果小於
ble 如果小於等於
bgt 如果大於
bge 如果大於等於
beq $t1,$t2,if_equal

函式

MIPS本身是沒有子程式或是函式的功能,但我們可以自己去實現。

堆疊

你可以直接當作MIPS有給你一個堆疊來存資料,\$sp是這個堆疊的記憶體位,使用方法是先把\$sp-4,然後放入值。

放入

addi $sp,$sp,-4
sw $t0,($sp)

取出就完全相反

lw $t0,($sp)
addi $sp,$sp,4

所以我們可以透過先把暫存器丟如堆疊來實現區域變數。(函式結束後暫存器恢復原樣)

func:
    addi $sp,$sp,-4
    sw $t0,($sp)

    #先把$t0的值存起來,這時不管怎麼使用$t0,函式結束都可以回去

    lw $t0,($sp)
    addi $sp,$sp,4
    jr $ra

如果這樣思考的話遞回其實就很簡單了。

總範例

sigma 1~12

.data
var: .word 12

.text
main:
    lw $a0,var

    jal sigma

    move $a0,$v0
    li $v0,1
    syscall
    j end

sigma:
    sigma_begin:
        addi $sp,$sp,-8
        sw $a0,0($sp)
        sw $t0,4($sp)

        li $t0,1
        beq $a0,t0,is_one

        jal sigma

        add $v0,$v0,$a0

        j sigma_end

    is_one:
        li $v0,1
        j sigma_end

    sigma_end:
        lw $t0,4($sp)
        lw $a0,0($sp)
        addi $sp,$sp,8
        jr $ra    

end: