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


(四)、MIPS指令系統及匯編語言

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

(2)MIPS匯編語言

4.1 指令系統的基本知識

4.1.1 指令系統概述

image-20201020174502093

4.1.2 指令格式

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

  • 指令格式:操作碼 + 操作數(操作數地址)

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

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

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

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

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

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

  • 指令長度:

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

4.1.3 尋址方式

(1) 什么是尋址?

尋址方式 就是根據 形式地址 計算出操作數 有效地址 的方法。

  • 形式地址:指令給出直接的操作數的地址編碼
  • 有效地址:操作數實際在內存中存儲的地址
(2) 怎么尋址?
  1. 尋址方式的確定

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

    • 指令中專門的尋址方式位:如 X86 指令,指令中有多個操作數,且尋址方式各不相同,需要各自說明尋址方式。

      image-20201020202223602

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

    尋址方式 示意圖 說明 操作數位置 訪問操作數所需訪存次數
    立即尋址 image-20201020202344630 image-20201020202708944 指令中 0
    寄存器直接尋址 image-20201020202355521 image-20201020202721699 寄存器 0
    寄存器間接尋址 image-20201020202407657 image-20201020202733645 存儲器 1
    基址尋址 image-20201020202416157 image-20201020202746061 存儲器 1
    變址尋址 image-20201020202516488 image-20201020202755018 存儲器 1
    相對尋址 image-20201020202523635 image-20201020202807345 存儲器 1
    堆棧尋址 image-20201020203015603 image-20201020202846921 存儲器 1

4.2 MIPS匯編語言

4.2.1 MIPS指令系統

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

image-20201020211446917

(2) MIPS 寄存器使用約定
  • 為了保持硬件的簡單,匯編語言不使用變量,操作數都是寄存器(registers),寄存器沒有數值類型(與C語言等高級語言不同)
  • MIPS有 32 個寄存器,每個寄存器存放數據的寬度為 32 bits(1個字)

image-20201020211604562

(3) MIPS 指令格式
  • MIPS 只有3種指令格式32位固定長度指令格式

    原因:馮·諾伊曼計算機建立在兩個原則之上——①指令與數值的表示形式一致;②全部程序可以被存儲在內存中,像數據一樣被讀寫。為了簡化計算機系統的軟/硬件,使得適用於數據操作的內存技術完全適用於指令操作,MIPS將指令也按照數據相同的”按字存儲“的方式存儲,即一條指令占32位,同時將32位划分為不同的字段,每一個字段提供指令的一部分信息,不同的划分方式形成了不同的指令格式——R、I、J,詳細見下表。

  • MIPS 指令格式一覽表

    R (Register ) I (Immediate) J(Jump)
    image-20201020212525677 image-20201020212612818 image-20201020212700024
    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 尋址方式

    image-20201020213211600

(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的數據通路與內存布局

image-20201021120837190

(2) MIPS的寄存器傳送的控制邏輯

image-20201021115826816

(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 過程

    image-20201023114851165

  • 數據定義

    • 目的:為變量的存儲划分內存(可能會有選擇的為數據分配標簽)

    • 語法:[名字:] 偽指令 初始值 [, 初始值 ] ……

    • 數據偽指令

      .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
      

      匯編程序會為標簽(也可理解為變量)構建符號表,為每一個數據段的標簽計算地址,如下圖。

      image-20201023152646359

  • 內存對齊、字節序

    • .ALIGN n - 對下一個定義的數據做 $2^n$ 字節對齊

    • 對齊:字的地址是4的整數倍(即地址的2位最低有效位必須是 $00_b$),半字的地址是2的整數倍

    • 字節序、端

      小端字節排序:內存地址 = 最低有效字節 的地址,例子 : Intel IA-32, Alpha

      image-20201023153250944

      大端字節排序:內存地址 = 最高有效字節 的地址,例子 : SPARC, PA-RISC

      image-20201023153314396

      ▲注意: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
      

    image-20201023154214712

  • 參數傳遞

    • 寄存器方法(使用通用寄存器$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
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM