【计算机组成原理】考纲第四章 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