【計算機組成原理】考綱第四章 MIPS指令系統及組合語言
(四)、MIPS指令系統及組合語言
(1)指令系統的基本知識(指令格式、定址方式)
(2)MIPS組合語言
4.1 指令系統的基本知識
4.1.1 指令系統概述
4.1.2 指令格式
機器指令是計算機硬體可以執行的、表示一種基本操作的二進位制程式碼。
-
指令格式:操作碼 + 運算元(運算元地址)
- 操作碼:指明指令的操作性質
- 運算元:指明運算元的位置(或運算元本身)
-
指令表示:
- 機器表示:二級制程式碼
- 符號化表示:助記符,如
MOV AX, BX
-
操作碼結構:
-
固定長度操作碼:操作碼長度固定不變
①硬體設計簡單;②指令譯碼時間開銷較小;③指令空間效率較低
-
可變長度操作碼:操作碼長度隨指令地址數目的不同而不同
①硬體設計複雜;②指令譯碼時間開銷較大;③指令空間利用率較高
-
-
指令長度:
- 定長指令系統:如MIPS指令
- 變長指令系統:一般為位元組的整數倍,如X86指令
4.1.3 定址方式
(1) 什麼是定址?
定址方式 就是根據 形式地址 計算出運算元 有效地址 的方法。
- 形式地址:指令給出直接的運算元的地址編碼
- 有效地址:運算元實際在記憶體中儲存的地址
(2) 怎麼定址?
-
定址方式的確定
-
在操作碼中給定定址方式:如 MIPS 指令,指令中僅有一個主(虛)存地址的,且指令中僅有幾種定址方式。 Load/store 型機器指令屬於這種情況。
-
指令中專門的定址方式位:如 X86 指令,指令中有多個運算元,且定址方式各不相同,需要各自說明定址方式。
-
-
按照指定方式進行定址
定址方式 示意圖 說明 運算元位置 訪問運算元所需訪存次數 立即定址 指令中 0 暫存器直接定址 暫存器 0 暫存器間接定址 儲存器 1 基址定址 儲存器 1 變址定址 儲存器 1 相對定址 儲存器 1 堆疊定址 儲存器 1
4.2 MIPS組合語言
4.2.1 MIPS指令系統
(1) MIPS R2000/R3000 暫存器結構
(2) MIPS 暫存器使用約定
- 為了保持硬體的簡單,組合語言不使用變數,運算元都是暫存器(registers),暫存器沒有數值型別(與C語言等高階語言不同)
- MIPS有 32
(3) MIPS 指令格式
-
MIPS 只有3種指令格式,32位固定長度指令格式
原因:馮·諾伊曼計算機建立在兩個原則之上——①指令與數值的表示形式一致;②全部程式可以被儲存在記憶體中,像資料一樣被讀寫。為了簡化計算機系統的軟/硬體,使得適用於資料操作的記憶體技術完全適用於指令操作,MIPS將指令也按照資料相同的”按字儲存“的方式儲存,即一條指令佔32位,同時將32位劃分為不同的欄位,每一個欄位提供指令的一部分資訊,不同的劃分方式形成了不同的指令格式——R、I、J,詳細見下表。
-
MIPS 指令格式一覽表
R (Register ) I (Immediate) J(Jump) opcode:與 func 組合起來,決定該條指令名(操作符),opcode等於0時代表所有R型別指令;
rs (Source Register):通常指定存放第一個運算元;
rt (Target Register):通常指定存放第二個運算元;
rd (Destination Register):通常指定存放計算結果的暫存器;
shamt:儲存執行移位運算時要移的位數,該欄位在不進行移位的指令中通常置0opcode : I型別指令中opcode可以唯一確定一條指令;
rs:表示唯一的運算元暫存器;
rt:指定儲存計算結果的暫存器;
立即數:16bits,可表示$2^{16}$ 個不同的整數值,在addi
,slti
,sltiu
等指令中中立即數通過位擴充套件(符號擴充套件)方式擴充套件到32位opcode
:與前兩者一致;
target address:26bits,利用字對齊,可以表示出32-bit地址的28位add
,addu
,sub
,subu
,and
,or
,jr
addi
,ori
,lui
,lw
,sw
,beq
,bnq
j
,jal
說明:5bits指定暫存器位置正好可以對應32 ($2^5$)個暫存器 說明:如果立即數超過16bits所能表示的範圍,可以藉助 lui
指令——將一個16bit的立即數存入暫存器的高16位,並將暫存器的低16位置 0$New PC = {\ PC[31..28],Addr,00\}$
(4) MIPS 指令語法
- 注意 “指令語法” 與 “指令格式” 的不同:指令語法是程式設計師書寫的語法規範,語法是固定的,用過約定好的規則使硬體實現更簡單;而指令格式是指機器碼(二進位制程式碼)的格式
- 指令語法:
操作符, 目標, 源1, 源2
- 操作符:指明操作的名稱
- 目標:存放操作的結果
- 源1 (2):第一 (二) 個運算元
(5) MIPS 定址方式
-
MIPS 沒有間接定址,且Load/Store指令採用單一定址模式(基址定址)
-
MIPS 定址方式
(6) MIPS 指令介紹
-
Load / Store 指令(取數/儲存指令)
-
格式:I 型別指令
-
分類:①取數指令:
LB
(取位元組),LBU
(取不帶符號位元組),LH
(取半字),LHU
(取不帶符號的半字),LW
(取字),LWL
,LWR
等;②儲存指令:SB
(存位元組),SH
(存半字),SW
(存字),SWL
,SWR
等
-
-
運算指令
- 格式:R 型別指令, I 型別指令
- 分類:①算數運算:
add
,addu
,addi
,addiu
,sub
,subu
,mul
,mulu
,div
,divu
,mfhi
,mflo
等;②邏輯運算:and
,andi
,or
,ori
,xor
,xori
,nor
等;③移位運算:sll
,srl
,sra
,sllv
,srlv
,srav
等
-
跳轉指令
- 格式:J 型別指令,R 型別指令
- 分類:
j
,jal
,jr
,jalr
-
轉移指令
- 格式:I 型別指令
- 分類:
beq
(相等轉移),bne
(不等轉移),blez
(小於或等於0轉移),bgtz
(大於0轉移),bltz
(小於0轉移),bltzal
,bgezal
等
-
特殊指令
- 格式:R 型別指令
- 分類:
syscall
(系統呼叫),break
(斷點)
-
R型別指令編碼示例
-
使用MIPS實現swap遞迴函式
4.2.2 MIPS組合語言詳解
(1) MIPS的資料通路與記憶體佈局
(2) MIPS的暫存器傳送的控制邏輯
(3) MIPS 組合語言中的 算術、邏輯、移位運算
-
整數加減法
- 加法:
add $s0, $s1, $s2
- 減法:
sub $s3, $s4, $s5
- 加法:
-
0號暫存器、立即數
-
0號暫存器:定義
$0
為數字0
,也可寫作$zero
-
立即數:即數值常量,MIPS針對立即數設定了專門指令,如
addi $s0, $s1, 10
需要注意的是,MIPS沒有立即數的減法,可以用立即數加實現
-
-
算數運算的溢位
- 可檢測出溢位異常的指令:
add
,sub
,addi
- 不會檢測出溢位異常的指令:
addu
,subu
,addiu
- MIPS中的C編譯器會使用
addu
,addiu
,subu
,即不檢查溢位異常
- 可檢測出溢位異常的指令:
-
移位運算
- 邏輯左移
sll
:左移並且補0,位移量為立即數 - 邏輯右移
srl
:右移並且補0,位移量為立即數 - 算數右移
sra
:右移並且在空位做符號擴充套件填充,位移量為立即數 sllv
,srlv
,srav
:移位量儲存在暫存器中,處理方式與立即數位移量類似
- 邏輯左移
(4) MIPS 組合語言中的 資料存取
-
MIPS算術指令只能操作暫存器,不能直接操作記憶體,需要通過資料存取指令在記憶體與暫存器之間傳輸資料
-
資料存取指令
-
記憶體到暫存器:
lw
lw $t0, 12($s0)
:$s0
稱為基址暫存器,12
稱為偏移量 -
暫存器到記憶體:
sw
sw $t0, 12($s0)
:與lw
相同
-
-
位元組的存取指令
lb $s0, 3($s1)
:把記憶體中的某個地址(3 + s1中的地址值
)所儲存的數值拷貝到s0
的低地址位元組上(其餘24位使用符號擴充套件)sb $s0, 3($s1)
:把s0
的低地址位元組所儲存的數值儲存到記憶體中的某個地址(3 + s1中的地址值
)上
-
定址:現代計算機按位元組編址,32-bit 字地址按 4 遞增
-
字對齊:物件的起始位置一定要是字長的整數倍,故MIPS中
lw
和sw
指令的基址暫存器的值與偏移量都應該是4的倍數
(5) MIPS彙編中的 分支、迴圈
-
條件分支指令
beq $1, $2, Label1
:相當於C語言中的if($1==$2) goto L1;
bne $1, $2, Label1
:相當於C語言中的if($1!=$2) goto L2;
-
無條件分支指令
j Label
:無條件跳轉到標籤label所在的程式碼,相當於goto Label
-
不等式指令
slt $1, $2, $3
:相當於$1 = ($2 < $3) ? 1 : 0;
slti
:slt
的立即數版本
(6) 一些小練習
-
IF - ELSE 語句
// C語言原始碼 if (i == j) f = g + h; else f = g - h;
# MIPS彙編實現程式碼如下 beq $s3, $s4, Label_True # branch i==j sub $s0, $s1, $s2 # f=g-h (false) j Label_Finish # goto Fin Label_True: add $s0, $s1, $s2 # f=g+h (True) Label_Finish:
-
SWITCH 語句
// C語言原始碼 switch (k) { case 0: f = i + j; break; case 1: f = g + h; break; case 2: f = g - h; break; case 3: f = i - j; break; } // 簡化為IF-ELSE語句 if (k == 0) f = i + j; else if (k == 1) f = g + h; else if (k == 2) f = g - h; else f = i - j;
# MIPS彙編實現程式碼如下 bne $s5, $0, Label1 # branch k!=0 add $s0, $s3, $s4 # k==0 so f=i+j j Label_Exit # end of case so Exit Label1: addi $t0, $s5, -1 # $t0=k-1 bne $t0, $0, Label2 # branch k!=1 add $s0, $s1, $s2 # k==1 so f=g+h j Label_Exit # end of case so Exit Label2: addi $t0, $s5, -2 # $t0=k-2 bne $t0, $0, Label3 # branch k!=2 sub $s0, $s1, $s2 # k==2 so f=g-h j Label_Exit # end of case so Exit Label3: addi $t0, $s5, -3 # $t0=k-3 bne $t0, $0, Label_Exit # branch k!=3 sub $s0, $s3, $s4 # k==3 so f=i-j Label_Exit:
-
DO - WHILE 語句
// C語言原始碼 do { g = g + A[i]; i = i + j; } while (i != h); // 改寫為GOTO語句 Label_Loop: g = g + A[i]; i = i + j; if (i != h) goto Label_Loop;
# MIPS彙編實現程式碼如下 Label_Loop: sll $t1, $s3, 2 #$t1= 4*i add $t1, $t1, $s5 #$t1=addr A lw $t1, 0($t1) #$t1=A[i] add $s1, $s1, $t1 #g=g+A[i] bne $s3, $s2, Label_Loop #if i!= h goto Label_Loop
(7) MIPS 支援函式功能的指令
-
可同時執行跳轉和儲存返回地址的指令
jal Label
:執行步驟 - ①link:將下一條指令地址存入$ra
;②jump:向指定的Label
跳轉- 一般配合
jr $ra
使用,即jal Label
存指令到$ra
,並跳轉到呼叫函式的頭部,函式執行完畢後使用jr $ra
跳轉到呼叫處的下一條指令,完成一次函式的呼叫
-
巢狀呼叫的實現
- 巢狀呼叫會覆蓋
$ra
中的返回地址資訊,故需要另尋他路——使用 Stack 棧
- 巢狀呼叫會覆蓋
-
暫存器
$sp
始終指向棧空間最後被使用的位置(可以理解為”棧指標“,棧從下往上遞增,棧指標地址遞減)- 呼叫規則:①將需要儲存的值壓入棧中;②指定引數(如果需要的話);③
jal
呼叫函式;④從棧中恢復相關的值 - 呼叫過程中的規則:①通過
jal
指令呼叫 , 使用jr $ra
指令返回;②最多可接受4個入口引數——$a0
,$a1
,$a2
,$a3
;③
# C語言 原始碼 int sumSquare(int x, int y) { return mult(x, x) + y; } # MIPS彙編實現 sumSqure: addi $sp, $sp, -8 # space on stack sw $ra, 4($sp) # save ret addr sw $a1, 0($sp) # save y add $a1, $a0, $zero # prep args jal mult # call mult lw $a1, 0($sp) # restore y add $v0, $v0, $a1 # mult()+y lw $ra, 4($sp) # get ret addr addi $sp, $sp, 8 # restore stack jr $ra mult: #mult函式
- 呼叫規則:①將需要儲存的值壓入棧中;②指定引數(如果需要的話);③
(8) MIPS 通用暫存器使用規範
- 通用暫存器分配(見 4.2.1(2)暫存器使用約定 )
- 特殊暫存器(系統維護)
$at
:編譯器隨時可能使用,最好不要用$k0~$k1
:操作系用隨時可能使用,最好不要用$gp,$fp
:自動維護,無需操作
- 儲存暫存器(程式設計師維護)
$0
:不能改變,恆為 0$s0-$s7
:如果被修改了需要恢復。如果被呼叫函式改變了這些暫存器的值,則必須在函式返回之前將這些暫存器的原始值恢復$sp
:如果被修改了需要恢復。棧指標在jal
執行之前或之後必須是指向的同一地址,不然呼叫函式無法從棧里正確恢復資料
- 易變暫存器(程式設計師維護)
$ra
:會改變。jal
會自動更改這個暫存器值,但呼叫函式需要手動將其值儲存在棧上$v0~$v1
:會改變。始終儲存最新的返回值$a0~$a3
:會改變。呼叫函式如果在呼叫完成後還要用到這些暫存器中的值,就要在呼叫前將這些值儲存在自己的棧空間內$t0~$t7
:會改變。任何函式在任何時候都可以更新這些暫存器中的值;與$a0~$a3
類似,呼叫函式時需手動將這些值儲存在棧空間內以防止丟失(被覆蓋)
(9) MIPS 彙編程式
-
組合語言語句
- 可執行指令:為處理器生成在執行時執行的機器碼,告訴處理器該做什麼
- 偽指令、巨集:由彙編程式翻譯成真正的指令,簡化程式設計人員的工作
- 彙編偽指令:當翻譯程式碼時為彙編程式提供資訊,用來定義段、分配記憶體變數等;不可執行 ——彙編偽指令不是指令集的一部分
-
程式模板
-
.DATA
:偽指令,定義程式的資料段,程式變數需要在該偽指令下定義,彙編程式會分配和初始化變數的儲存空間 -
.TEXT
:定義程式的程式碼段 -
.GLOBL
:偽指令,宣告 一個符號為全域性的,可被其它檔案引用,通常用來宣告一個程式的 main 過程
-
-
資料定義
-
目的:為變數的儲存劃分記憶體(可能會有選擇的為資料分配標籤)
-
語法:[名字:] 偽指令 初始值 [, 初始值 ] ……
-
資料偽指令
.BYTE
:以 8 位位元組儲存數值表.HALF
:以 16 位(半字長)儲存數值表.WORD
:以 32 位(一個字長)儲存數值表.WORD w:n
:將 32 位數值 w 存入 n 個邊界對齊的連續的字中.FLOAT
:以 單精度浮點數儲存數值表.DOUBLE
:以 雙精度浮點數儲存數值表 -
字串偽指令
.ASCII
:為 一個 ASCII 字串分配位元組序列.ASCIIZ
:與 .ASCII 偽指令類似 , 但字串 以 NULL 結尾.SPACE n
:為 資料段中 n 個未初始化的位元組分配空間 -
例項
.DATA var1: .BYTE 1, 2,'Z' str1: .ASCIIZ "My String\n" var2: .WORD 0x12345678
彙編程式會為標籤(也可理解為變數)構建符號表,為每一個數據段的標籤計算地址,如下圖。
-
-
記憶體對齊、位元組序
-
.ALIGN n
- 對下一個定義的資料做 $2^n$ 位元組對齊 -
對齊:字的地址是4的整數倍(即地址的2位最低有效位必須是 $00_b$),半字的地址是2的整數倍
-
位元組序、端
①小端位元組排序:記憶體地址 = 最低有效位元組 的地址,例子 : Intel IA-32, Alpha
②大端位元組排序:記憶體地址 = 最高有效位元組 的地址,例子 : SPARC, PA-RISC
▲注意:MIPS 可以操作以上兩種位元組序
-
-
系統呼叫
-
syscall
:MIPS提供特殊的syscall
指令,從作業系統獲取服務 -
作用:程式通過系統呼叫實現輸入輸出
-
syscall
系統服務流程:①從$v0
暫存器中讀取服務數;②從$a0
,$a1
等暫存器中讀取引數值(如果有)③傳送syscall
指令;④從結果暫存器中取回返回值(如果有) -
程式示例 與 系統服務列表
move $a0, $s0 #copy value of $s0 to $a0 li $v0, 1 #load the service number syscall #system service start
-
-
引數傳遞
-
暫存器方法(使用通用暫存器
$a0~$a3
) -
棧方法(使用棧
$sp
)棧適用於以下情況:①不適用使用暫存器時,用來儲存變數 / 資料結構;②過程呼叫中儲存和恢復暫存器;③實現遞迴
-
(10) 一些小練習
-
選擇排序
# Objective: Sort array using selection sort algorithm # Input: $a0 = pointer to first, $a1 = pointer to last # Output: array is sorted in place ########################################################## sort: addiu $sp, $sp, 4 # allocate one word on stack sw $ra, 0($sp) # save return address on stack top: jal max # call max procedure lw $t0, 0($a1) # $t0 = last value sw $t0, 0($v0) # swap last and max values sw $v1, 0($a1) addiu $a1, $a1, 4 # decrement pointer to last bne $a0, $a1, top # more elements to sort lw $ra, 0($ sp) # pop return address addiu $sp, $sp, 4 jr $ra # return to caller # Objective: Find the address and value of maximum element # Input: $a0 = pointer to first, $a1 = pointer to last # Output: $v0 = pointer to max, $v1 = value of max ########################################################## max: move $v0, $a0 # max pointer = first pointer lw $v1, 0($v0) # $v1 = first value beq $a0, $a1, ret # if (first == last) return move $t0, $a0 # $t0 = array pointer loop: addi $t0, $t0, 4 # point to next array element lw $t1, 0($t0) # $t1 = value of A[ ble $t1, $v1, skip # if (A[i] <= max) then skip move $v0, $t0 # found new maximum move $v1, $t1 skip: bne $t0, $a1, loop # loop back if more elements ret: jr $ra
-
遞迴過程
int fact(int n) { if (n < 2) return 1; else return (n*fact(n-1)); }
fact: slti $t0,$a0,2 # (n<2)? beq $t0,$0,else # if false branch to else li $v0,1 # $v0 = 1 jr $ra # return to caller else: addiu $sp,$sp, -8 # allocate 2 words on stack sw $a0,4($sp) # save argument n sw $ra,0($sp) # save return address addiu $a0,$a0, -1 # argument = n-1 jal fact # call fact(n-1) lw $a0,4($sp) # restore argument lw $ra,0($sp) # restore return address mul $v0,$a0,$v0 # $v0 = n*fact(n-1) addi $sp,$sp,8 # free stack frame jr $ra # return to caller