计算机组成原理(4.3)—— MIPS指令系统(RSIC)


一、MIPS架构基础

  • 1981年出现,由MIPS科技公司开发并授权,广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。
  • 并行化程度:流水线
  • 指令集类型:RISC

1. 寄存器数据指定

(1)MIPS架构中的寄存器安排

  1. 32位通用寄存器GPRs
    • 31+1个(r0是机器零)
    • 寄存器编号5位
  2. 32位浮点寄存器
    • 32个:$f0~$f31
    • 可以两个一起拼成64位的
  3. 专用特殊寄存器
    • 无需编号
    • HI, LO, PC

(2)寄存器名称、编号和功能

  • 通用寄存器汇总表
    在这里插入图片描述
  • 寄存器的汇编表示用$符号,可以接名称或编号(如$a0$4都表示寄存器a0)
  • 在MIPS指令字中,用5位二进制编码指示通用寄存器
  • 被调用函数把值保存在s0~s7,被掉函数结束后调用函数还可以用这些值;如果存在t0~t7就不能用了(C翻译汇编时要小心)

2. 存储器数据指定

  • 32位机器:可访问主存空间: 2^32bytes(4GB)
  • MIPS使用装入-存储型指令风格:运算的操作数只能是寄存器,只能通过Load/Store指令访问存储器数据
  • 数据地址通过一个32位寄存器内容(基地址)加16位偏移量得到,16位偏移量是带符号整数,故应符号扩展
  • 数据要求按边界对齐(地址是4的倍数)
  • Big Endian(大端方式)或小端
    在这里插入图片描述

二、操作数类型和表示方式

1. 操作数类型

在这里插入图片描述

2. 操作数表示方式

在这里插入图片描述

三、指令格式

  • 指令字长度:定长指令字,32位宽
    • 按字地址对齐(字地址为4的倍数,即指令地址的最后两位为0
  • 操作码长度:定长操作码编码(op段),6位宽
    • 一般通过对操作码进行不同的编码来定义;
    • 操作码相同时,再由功能码(func段)进行区分(例如MIPS的R型指令)

1. R型指令

在这里插入图片描述

  • 两个操作数和结果都在寄存器
  • R型指令功能:
    1. 运算指令:包括各种算数、逻辑、移位运算。R型指令基本都是运算指令
    2. 控制转移指令:有jrjalr

2. I型指令

在这里插入图片描述

  • 一个操作数是立即数,另一个操作数和结果在寄存器
  • 16位的立即数需要扩展到32位参与运算,依据具体指令不同可能要进行符号扩展或零扩展
  • I型指令功能:
    1. 运算指令:类似R型指令,只是源操作数之一通过立即数给出
    2. 访存指令:LOAD系列和STORE系列指令。寄存器RS给出基地址,16位立即数(符号扩展)给出偏移量
    3. 条件分支指令:如beq/bne等。比较RS和RT寄存器的值,16位立即数(符号扩展)给出目标指令和当前指令偏差的条数

3. J型指令

在这里插入图片描述

  • 操作数只有一个直接地址(用来控制跳转)
  • J型指令功能:
    1. 控制转移:转移到target address所指示的指令执行
  • 目标地址的构成
    1. 32位MIPS机器中,指令储存时按字地址(4字节)对齐,所有指令地址均为4的倍数,故其最后两位总为0
    2. 下图是MIPS架构的memory map,其中Text段用于存储指令,可见指令地址范围是0x0040_00000x0FFF_FFFC,高四位恒为0。为了避免Text段浮动导致问题,我们直接用pc寄存器(当前指令)的高四位作为目标指令的高四位
      在这里插入图片描述
    3. 综上,目标指令地址为:pc高四位 + 26位target address + 0000,共32位

4. 汇编指令和机器码指令字示例

  • 从以下两个示例表中,可以看出汇编语言到机器指令的一一对应关系。汇编语言本质上就是和机器指令一一对应的,类似助记符的一种语言

(1)MIPS汇编示例

在这里插入图片描述

(2)机器码指令字示例

在这里插入图片描述

(3)汇编和反汇编

  1. 汇编:把汇编指令翻译为机器码
    在这里插入图片描述

  2. 反汇编:把机器码翻译为汇编指令
    在这里插入图片描述

四、寻址方式

  • MIPS不同于IA-32,没有专门的寻址方式字段,各操作数的具体寻址方式由指令格式确定,而指令格式由 op来确定

1. R型指令的寻址

在这里插入图片描述

2. I型指令的寻址

在这里插入图片描述

3. J型指令的寻址

在这里插入图片描述

五、程序的机器级表示

1. 算术和逻辑运算指令

  • 没有全部列出,还有其他指令,如addu(不带溢出处理), addiu 等
  • x86 / IA-32没有分add还是addu,因为它只产生各种标志(PSW),由软件根据标志信息来判断是否溢出。而MIPS是由硬件直接判溢出与否,要告诉CPU处不处理理溢出
    在这里插入图片描述
  • 示例
//示例1:假定给 f, g, h, i, j分别分配 $s1, $s2, $s3, $s4, $s5
f = (g+h)-(i+j);

add $t0, $s2, $s3
add $t1, $s4, $s5  
sub $s1, $t0, $t1

//示例2:16位有符号立即数[-32768,32767]
f = (g+100) - (i+50);

addi $7, $2, 100  
addi $8, $4, 50  
sub $1, $7, $8

//示例3:出现的常数超过16位有符号数范围
f = (g+65000) - (i+50) 

addi $7, $2, 65000	//错了,因为超过16位立即数表示范围
addi $8, $4, 50
sub $1, $7, $8

lui	$3, 0x0  		//正确写法
ori	$3, 0Xfde8
addi $8, $4, 50
sub $1, $7, $8

2. 访存指令

在这里插入图片描述

  • 为什么指令必须支持不同长度的操作数:因为高级语言中的数据类型有char,short,int,long,……等,故需要存取不同长度的操作数;

  • 指令中操作数长度由什么决定:由不同的操作码指定

  • 示例

    //A是100个字的数组(32位),g在$1, h在$2, A基址在$3
    g = h + A[8];
    
    lw $4, 32($3)  	//注意是4*8 = 32
    add $1, $2, $4
    
  • 如果在一个循环体内执行:g = h + A[i],则能否用基址寻址方式:不行,因为循环体内指令不能变,故首地址A不变,只能把下标 i 放在变址寄存器中,每循环一次下标加1,所以,不能用基址方式而应该用变址方式

    1. 基址寻址是:基址是寄存器给出的,偏移是立即数定值;
    2. 变址寻址是:基址是立即数定值,偏移是寄存器给出的
      在这里插入图片描述
//A是100个字的数组(32位),g在$1, i在$5, A基址在$3
g = g+A[i]

3. 分支转移指令

在这里插入图片描述

4. 伪指令

在这里插入图片描述

5. 过程调用

  1. 过程调用的执行步骤(假定过程P调用过程Q)
    在这里插入图片描述

  2. MIPS中用于过程调用的指令:beqjjrjal一些伪指令

  3. 少量过程调用信息用寄存器传递
    在这里插入图片描述

  4. 如果过程中用到的参数超过4个,返回值超过2个,怎么办

    • 更多的参数和返回值要保存到存储器的特殊区域中
    • 这个特殊区域为:栈(Stack)
    • 一般用“栈”来传递参数、保存返回地址,并用来临时存放过程中的局部变量等。这样可以实现嵌套和递归调用

(1)MIPS中的栈

  • 栈的基本概念
    在这里插入图片描述
  • MIPS中栈的实现

在这里插入图片描述

  • 栈帧
    在这里插入图片描述

(2)调用过程(假定P调用Q)

  • 程序可访问的寄存器组是所有过程共享的资源,给定时刻只能被一个过程使用 ,因此过程中使用的寄存器的值不能被另一个过程覆盖!(主调过程使用的寄存器,被调过程要么不用,要么用完之后返回前把值还回去)

  • MIPS的寄存器使用约定

    1. 保存寄存器$s0 ~$s7的值在从被调用过程返回后还要被用,被调用者需要保留
    2. 临时寄存器$t0 ~$t9的值在从被调用过程返回后不需要被用(需要的话,由调用者保存) ,被调用者可以随意使用
    3. 参数寄存器$a0~$a3在从被调用过程返回后不需要被用(需要的话,由调用者保存在栈帧或其他寄存器中),被调用者可以随意使用
    4. 全局指针寄存器$gp的值不变
    5. 帧指针寄存器$fp用栈指针寄存器$sp-4来初始化
  • 需在被调用过程Q中入栈保存的寄存器(称为被调用者保存

    1. 返回地址$ra (如果Q又调用R,则$ra内容会被破坏,故需保存)
    2. 保存寄存器$s0 ~$s7(Q返后P可能还会用到,Q中用的话就被破坏,故需保存)
    3. 除了上述寄存器以外,所有局部数组结构体等复杂类型变量也要入栈保存
    4. 如果局部变量和临时变量发生寄存器溢出(寄存器不够分配),则也要入栈
  • 各处理器对栈帧规定的 ”调用者保存” 和 ”被调用者保存” 的寄存器可能不同

  • 过程调用时MIPS中栈和栈帧的变化
    在这里插入图片描述
    在这里插入图片描述

  • 过程调用协议
    在这里插入图片描述

(3)调用的示例

  1. swap函数示例

    1. 现有swap函数如下,主函数caller要调用它

      swap(int v[ ], int k)
      {
              
              
      	int temp;  
      	temp = v[k];  
      	v[k] = v[k+1];  
      	v[k+1] = temp;
      }
      
    2. temp对应$t0(局部变量),变量v 和 k分别对应$a0$a1(传入参数)

    3. 根据C语言的逻辑,可以写出以下核心逻辑代码

      sll	$s2, $a1, 2		; $a1=k, mulitply k by 4
      addu $s2 $s2, $a0	; address of v[k]
      lw $t0, 0($s2)		; load v[k]
      lw $s3, 4($s2)		; load v[k+1]
      sw $s3, 0($s2)		; store v[k+1] into v[k]
      sw $t0, 4($s2)		; store old v[k] into v[k+1]
      

      分析这段程序,swap用到了$t0$s2$s3,所以caller中这三个寄存器的值被破坏。根据约定,$t0caller自己保护,$s2$s3需要在swap中保护

    4. 使用jal swap指令调用swap函数。等价于执行以下两条指令

      //jal swap
      $31 = PC+4		; $31=$ra  
      goto swap
      
    5. 程序执行顺序如下
      在这里插入图片描述

    6. 加上保护寄存器和返回指令,完整程序如下
      在这里插入图片描述

    7. 如果swap是叶子过程,无需保存返回地址到栈中。因为$ra的内容不会被破坏;如果将所有内部寄存器都用临时寄存器 (如$t1等),则叶子过程swap的栈帧为空,且上述黑色指令都可去掉

  2. 嵌套调用示例

    1. 原始C程序

      int i;				// 全局变量
      void set_array(int num)
      {
              
              
      	int array[10];	// 局部变量
      	for (i = 0; i < 10; i ++) 
      		arrar[i] = compare (num, i);
      }
      
      int compare (int a, int b)
      {
              
              
      	if (sub (a, b) >= 0)
      		return 1;	
      	else
      		return 0;
      }
      
      int sub (int a, int b)
      {
              
              
      	return a-b;
      }
      
    2. 过程调用时的变量分配

      1. 全局变量一般分配到寄存器R/W存储区

      2. 该例中只有一个简单变量i,假定分配给$s0。无需保存和恢复!

      3. 为减少指令条数,并减少访问内存次数,在每个过程的过程体中总是先使用临时寄存器$t0~$t9;临时寄存器不够或者某个值在调用过程返回后还需要用,就使用保存寄存器$s0~$s7

    3. set_array过程的栈帧分析

      1. 入口参数为num,没有返回参数,有一个局部数组,被调用过程为compare,因此,其栈帧中除了保留所用的保存寄存器外,必须保留返回地址(因为set_array不是叶过程)
      2. 是否保存$fp要看具体情况,如果确保后面都不用到$fp,则可以不保存,但为了保证$fp的值不被后面的过程覆盖,通常情况下,应该保存$fp的值,并给局部数组(int array[10]) 预留4×10=40个字节的空间。
      3. 从过程体来看,从compare返回后还需要用到数组基地址,故将其分配给$s1。因此要用到的保存寄存器有两个:$s0$s1,但只需将$s1保存在栈帧中($s0保存全局变量i不用保护),另外加上返回地址$ra (因为已经保存了前一个函数的返回地址),帧指针$fp(因为已经保存了前一个栈帧的尾地址)、局部数组,其栈帧空间最少为3×4+40=52B
    4. compare过程的栈帧分析

      1. 入口参数为ab,仅一个返回参数,没有局部变量,被调用过程为sub。 过程体中没用到保存寄存器,所以,其栈帧中只需保留返回地址$ra$fp的值
    5. sub过程的栈帧分析:叶子过程,其栈帧为空

    6. 栈的变化示意图
      在这里插入图片描述

6. 翻译C语言示例

  1. 判断等于

    //i, j, f, g, h, 分别存在 $s1, $s2, $s3, $s4, $s5
    if (i == j)
    	f = g+h ;
    else
    	f = g-h ;
    
    //翻译为汇编
    		bne $s1, $s2, else	//与C语句相反, i!=j, jump to else  
    		add $s3, $s4, $s5
    		j	exit			//jump to exit  
    else:	sub $s3, $s4, $s5
    exit:
    
  2. Loop循环

    //g, h, i, j ~ $1, $2, $3, $4 and base address of array is in $5
    //数组元素为int类型,sizeof(int)=4
    Loop:	g = g +A[i];
    		i = i+ j;
    		if (i != h) go to Loop:
    
    //翻译为汇编
    Loop:	add $7, $3, $3		//i*2,加法快
    		add $7, $7, $7  	//i*4, 得到偏移量,也可用移位
    		add $7, $7, $5  	//加上数组基地址
    		lw $6, 0($7)  		// $6=A[i]
    		add $1, $1, $6  	//g= g+A[i]
    		add $3, $3, $4		//i = i+j;
    		bne $3, $2, Loop	//程序员不必计算分支指令的地址,而只要用标号即可!汇编 器完成地址计算
    
    • 注意最后一句bne $3,$2,Loop怎么翻译机器码。注意imm16的值一定是相对下一条指令说的

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wxc971231/article/details/108032595