1. 程式人生 > 實用技巧 >【計算機組成原理】考綱第四章 MIPS指令系統及組合語言

【計算機組成原理】考綱第四章 MIPS指令系統及組合語言

(四)、MIPS指令系統及組合語言

(1)指令系統的基本知識(指令格式、定址方式)

(2)MIPS組合語言

4.1 指令系統的基本知識

4.1.1 指令系統概述

4.1.2 指令格式

機器指令是計算機硬體可以執行的、表示一種基本操作的二進位制程式碼

  • 指令格式:操作碼 + 運算元(運算元地址)

    • 操作碼:指明指令的操作性質
    • 運算元:指明運算元的位置(或運算元本身)
  • 指令表示:

    • 機器表示:二級制程式碼
    • 符號化表示:助記符,如 MOV AX, BX
  • 操作碼結構:

    • 固定長度操作碼:操作碼長度固定不變

      ①硬體設計簡單;②指令譯碼時間開銷較小;③指令空間效率較低

    • 可變長度操作碼:操作碼長度隨指令地址數目的不同而不同

      ①硬體設計複雜;②指令譯碼時間開銷較大;③指令空間利用率較高

  • 指令長度:

    • 定長指令系統:如MIPS指令
    • 變長指令系統:一般為位元組的整數倍,如X86指令

4.1.3 定址方式

(1) 什麼是定址?

定址方式 就是根據 形式地址 計算出運算元 有效地址 的方法。

  • 形式地址:指令給出直接的運算元的地址編碼
  • 有效地址:運算元實際在記憶體中儲存的地址
(2) 怎麼定址?
  1. 定址方式的確定

    • 在操作碼中給定定址方式:如 MIPS 指令,指令中僅有一個主(虛)存地址的,且指令中僅有幾種定址方式。 Load/store 型機器指令屬於這種情況。

    • 指令中專門的定址方式位:如 X86 指令,指令中有多個運算元,且定址方式各不相同,需要各自說明定址方式。

  2. 按照指定方式進行定址

    定址方式 示意圖 說明 運算元位置 訪問運算元所需訪存次數
    立即定址 指令中 0
    暫存器直接定址 暫存器 0
    暫存器間接定址 儲存器 1
    基址定址 儲存器 1
    變址定址 儲存器 1
    相對定址 儲存器 1
    堆疊定址 儲存器 1

4.2 MIPS組合語言

4.2.1 MIPS指令系統

(1) MIPS R2000/R3000 暫存器結構

(2) MIPS 暫存器使用約定
  • 為了保持硬體的簡單,組合語言不使用變數,運算元都是暫存器(registers),暫存器沒有數值型別(與C語言等高階語言不同)
  • MIPS有 32
    個暫存器,每個暫存器存放資料的寬度為 32 bits(1個字)

(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:儲存執行移位運算時要移的位數,該欄位在不進行移位的指令中通常置0
    opcode : 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中lwsw指令的基址暫存器的值與偏移量都應該是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;
    • sltislt的立即數版本
(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