• 前一篇文章分析了指令系统(ISA)的设计方法,这里以MIPS指令系统为例进行分析
  • 前文链接:​​计算机组成原理(4.1)—— 指令系统设计​​

文章目录

  • ​​一、MIPS架构基础​​
  • ​​1. 寄存器数据指定​​
  • ​​(1)MIPS架构中的寄存器安排​​
  • ​​(2)寄存器名称、编号和功能​​
  • ​​2. 存储器数据指定​​
  • ​​二、操作数类型和表示方式​​
  • ​​1. 操作数类型​​
  • ​​2. 操作数表示方式​​
  • ​​三、指令格式​​
  • ​​1. R型指令​​
  • ​​2. I型指令​​
  • ​​3. J型指令​​
  • ​​4. 汇编指令和机器码指令字示例​​
  • ​​(1)MIPS汇编示例​​
  • ​​(2)机器码指令字示例​​
  • ​​(3)汇编和反汇编​​
  • ​​四、寻址方式​​
  • ​​1. R型指令的寻址​​
  • ​​2. I型指令的寻址​​
  • ​​3. J型指令的寻址​​
  • ​​五、程序的机器级表示​​
  • ​​1. 算术和逻辑运算指令​​
  • ​​2. 访存指令​​
  • ​​3. 分支转移指令​​
  • ​​4. 伪指令​​
  • ​​5. 过程调用​​
  • ​​(1)MIPS中的栈​​
  • ​​(2)调用过程(假定P调用Q)​​
  • ​​(3)调用的示例​​
  • ​​6. 翻译C语言示例​​

一、MIPS架构基础

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

1. 寄存器数据指定

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

  1. 32位通用寄存器GPRs
  • 31+1个(r0是机器零)
  • 寄存器编号5位
  1. 32位浮点寄存器
  • 32个:​​$f0~$f31​
  • 可以两个一起拼成64位的
  1. 专用特殊寄存器
  • 无需编号
  • ​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. 操作数类型

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

2. 操作数表示方式

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

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

1. R型指令

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

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

2. I型指令

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

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

3. J型指令

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

  • 操作数只有一个直接地址(用来控制跳转)
  • J型指令功能:
    1. 控制转移:转移到target address所指示的指令执行
  • 目标地址的构成
    1. 32位MIPS机器中,指令储存时按字地址(4字节)对齐,所有指令地址均为4的倍数,故其最后两位总为0
    2. 下图是MIPS架构的​​memory map​​,其中​​Text​​段用于存储指令,可见指令地址范围是​​0x0040_0000​​到​​0x0FFF_FFFC​​,高四位恒为0。为了避免Text段浮动导致问题,我们直接用pc寄存器(当前指令)的高四位作为目标指令的高四位
    3. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_寄存器_06


    4. 综上,目标指令地址为:​​pc高四位 + 26位target address + 0000​​,共32位

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

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

(1)MIPS汇编示例

计算机组成原理(4.3)—— MIPS指令系统(RSIC)_寄存器_07

(2)机器码指令字示例

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

(3)汇编和反汇编

  1. 汇编:把汇编指令翻译为机器码
  2. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_MIPS_09

  3. 反汇编:把机器码翻译为汇编指令
  4. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_计算机组成原理_10

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

1. R型指令的寻址

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

2. I型指令的寻址

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

3. J型指令的寻址

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

五、程序的机器级表示

1. 算术和逻辑运算指令

  • 没有全部列出,还有其他指令,如addu(不带溢出处理), addiu 等
  • x86 / IA-32没有分add还是addu,因为它只产生各种标志(PSW),由软件根据标志信息来判断是否溢出。而MIPS是由硬件直接判溢出与否,要告诉CPU处不处理理溢出
  • 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_ISA_14


  • 示例
//示例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. 访存指令

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

  • 为什么指令必须支持不同长度的操作数:因为高级语言中的数据类型有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. 变址寻址是:基址是立即数定值,偏移是寄存器给出的
    3. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_ISA_16


//A是100个字的数组(32位),g在$1, i在$5, A基址在$3
g = g+A[i]

3. 分支转移指令

计算机组成原理(4.3)—— MIPS指令系统(RSIC)_寄存器_17

4. 伪指令

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

5. 过程调用

  1. 过程调用的执行步骤(假定过程P调用过程Q)
  2. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_ISA_19

  3. MIPS中用于过程调用的指令:​​beq​​、​​j​​、​​jr​​、​​jal​​、​​一些伪指令​​…

  4. 少量过程调用信息用寄存器传递
  5. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_指令系统_20

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

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

(1)MIPS中的栈

  • 栈的基本概念
  • 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_计算机组成原理_21


  • MIPS中栈的实现

计算机组成原理(4.3)—— MIPS指令系统(RSIC)_寄存器_22

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


(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中栈和栈帧的变化
  • 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_计算机组成原理_24


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

  • 过程调用协议
  • 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_指令系统_26

(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​​中这三个寄存器的值被破坏。根据约定,​​$t0​​由​​caller​​自己保护,​​$s2​​和​​$s3​​需要在​​swap​​中保护

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

      //jal swap
      $31 = PC+4 ; $31=$ra
      goto swap
    5. 程序执行顺序如下
    6. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_ISA_27

    7. 加上保护寄存器和返回指令,完整程序如下
    8. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_指令系统_28

    9. 如果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. 入口参数为​​a​​和​​b​​,仅一个返回参数,没有局部变量,被调用过程为​​sub​​。 过程体中没用到保存寄存器,所以,其栈帧中只需保留返回地址$ra​$fp​的值
    5. ​sub​​过程的栈帧分析:叶子过程,其栈帧为空

    6. 栈的变化示意图
    7. 计算机组成原理(4.3)—— MIPS指令系统(RSIC)_计算机组成原理_29

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的值一定是相对下一条指令说的

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