- 前一篇文章分析了指令系统(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架构中的寄存器安排
- 32位通用寄存器GPRs:
- 31+1个(r0是机器零)
- 寄存器编号5位
- 32位浮点寄存器:
- 32个:
$f0~$f31
- 可以两个一起拼成64位的
- 专用特殊寄存器
- 无需编号
-
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型指令功能:
- 运算指令:包括各种算数、逻辑、移位运算。R型指令基本都是运算指令
- 控制转移指令:有
jr
和jalr
2. I型指令
- 一个操作数是立即数,另一个操作数和结果在寄存器
- 16位的立即数需要扩展到32位参与运算,依据具体指令不同可能要进行符号扩展或零扩展
- I型指令功能:
- 运算指令:类似R型指令,只是源操作数之一通过立即数给出
- 访存指令:
LOAD
系列和STORE
系列指令。寄存器RS给出基地址,16位立即数(符号扩展)给出偏移量 - 条件分支指令:如
beq
/bne
等。比较RS和RT寄存器的值,16位立即数(符号扩展)给出目标指令和当前指令偏差的条数
3. J型指令
- 操作数只有一个直接地址(用来控制跳转)
- J型指令功能:
- 控制转移:转移到
target address
所指示的指令执行 - 目标地址的构成
- 32位MIPS机器中,指令储存时按字地址(4字节)对齐,所有指令地址均为4的倍数,故其最后两位总为0
- 下图是MIPS架构的
memory map
,其中Text
段用于存储指令,可见指令地址范围是0x0040_0000
到0x0FFF_FFFC
,高四位恒为0。为了避免Text段浮动导致问题,我们直接用pc寄存器(当前指令)的高四位作为目标指令的高四位 - 综上,目标指令地址为:
pc高四位 + 26位target address + 0000
,共32位
4. 汇编指令和机器码指令字示例
- 从以下两个示例表中,可以看出汇编语言到机器指令的一一对应关系。汇编语言本质上就是和机器指令一一对应的,类似助记符的一种语言
(1)MIPS汇编示例
(2)机器码指令字示例
(3)汇编和反汇编
- 汇编:把汇编指令翻译为机器码
- 反汇编:把机器码翻译为汇编指令
- MIPS不同于IA-32,没有专门的寻址方式字段,各操作数的具体寻址方式由指令格式确定,而指令格式由 op来确定
1. R型指令的寻址
2. I型指令的寻址
3. J型指令的寻址
五、程序的机器级表示1. 算术和逻辑运算指令
- 没有全部列出,还有其他指令,如addu(不带溢出处理), addiu 等
- x86 / IA-32没有分add还是addu,因为它只产生各种标志(PSW),由软件根据标志信息来判断是否溢出。而MIPS是由硬件直接判溢出与否,要告诉CPU处不处理理溢出
- 示例
2. 访存指令
为什么指令必须支持不同长度的操作数:因为高级语言中的数据类型有char,short,int,long,……等,故需要存取不同长度的操作数;
指令中操作数长度由什么决定:由不同的操作码指定
示例
如果在一个循环体内执行:
g = h + A[i]
,则能否用基址寻址方式:不行,因为循环体内指令不能变,故首地址A不变,只能把下标 i 放在变址寄存器中,每循环一次下标加1,所以,不能用基址方式而应该用变址方式- 基址寻址是:基址是寄存器给出的,偏移是立即数定值;
- 变址寻址是:基址是立即数定值,偏移是寄存器给出的
3. 分支转移指令
4. 伪指令
5. 过程调用
- 过程调用的执行步骤(假定过程P调用过程Q)
MIPS中用于过程调用的指令:
beq
、j
、jr
、jal
、一些伪指令
…- 少量过程调用信息用寄存器传递
如果过程中用到的参数超过4个,返回值超过2个,怎么办
- 更多的参数和返回值要保存到存储器的特殊区域中
- 这个特殊区域为:栈(Stack)
- 一般用“栈”来传递
参数
、保存返回地址
,并用来临时存放过程中的局部变量
等。这样可以实现嵌套和递归调用
(1)MIPS中的栈
- 栈的基本概念
- MIPS中栈的实现
- 栈帧
(2)调用过程(假定P调用Q)
程序可访问的寄存器组是所有过程共享的资源,给定时刻只能被一个过程使用 ,因此过程中使用的寄存器的值不能被另一个过程覆盖!(主调过程使用的寄存器,被调过程要么不用,要么用完之后返回前把值还回去)
MIPS的寄存器使用约定
- 保存寄存器
$s0 ~$s7
的值在从被调用过程返回后还要被用,被调用者需要保留 - 临时寄存器
$t0 ~$t9
的值在从被调用过程返回后不需要被用(需要的话,由调用者保存) ,被调用者可以随意使用 - 参数寄存器
$a0~$a3
在从被调用过程返回后不需要被用(需要的话,由调用者保存在栈帧或其他寄存器中),被调用者可以随意使用 - 全局指针寄存器
$gp
的值不变 - 帧指针寄存器
$fp
用栈指针寄存器$sp-4
来初始化
- 保存寄存器
需在被调用过程Q中入栈保存的寄存器(称为被调用者保存)
- 返回地址
$ra
(如果Q又调用R,则$ra
内容会被破坏,故需保存) - 保存寄存器
$s0 ~$s7
(Q返后P可能还会用到,Q中用的话就被破坏,故需保存) - 除了上述寄存器以外,所有局部数组和结构体等复杂类型变量也要入栈保存
- 如果局部变量和临时变量发生寄存器溢出(寄存器不够分配),则也要入栈
- 返回地址
各处理器对栈帧规定的 ”调用者保存” 和 ”被调用者保存” 的寄存器可能不同
- 过程调用时MIPS中栈和栈帧的变化
- 过程调用协议
(3)调用的示例
swap函数示例
现有
swap
函数如下,主函数caller
要调用它temp对应
$t0
(局部变量),变量v 和 k分别对应$a0
和$a1
(传入参数)根据C语言的逻辑,可以写出以下核心逻辑代码
分析这段程序,
swap
用到了$t0
,$s2
和$s3
,所以caller
中这三个寄存器的值被破坏。根据约定,$t0
由caller
自己保护,$s2
和$s3
需要在swap
中保护使用
jal swap
指令调用swap
函数。等价于执行以下两条指令- 程序执行顺序如下
- 加上保护寄存器和返回指令,完整程序如下
如果swap是叶子过程,无需保存返回地址到栈中。因为
$ra
的内容不会被破坏;如果将所有内部寄存器都用临时寄存器 (如$t1
等),则叶子过程swap的栈帧为空,且上述黑色指令都可去掉
嵌套调用示例
原始C程序
过程调用时的变量分配
全局变量一般分配到寄存器或R/W存储区。
该例中只有一个简单变量
i
,假定分配给$s0
。无需保存和恢复!为减少指令条数,并减少访问内存次数,在每个过程的过程体中总是先使用临时寄存器
$t0~$t9
;临时寄存器不够或者某个值在调用过程返回后还需要用,就使用保存寄存器$s0~$s7
set_array
过程的栈帧分析- 入口参数为
num
,没有返回参数,有一个局部数组,被调用过程为compare
,因此,其栈帧中除了保留所用的保存寄存器外,必须保留返回地址(因为set_array
不是叶过程) - 是否保存
$fp
要看具体情况,如果确保后面都不用到$fp
,则可以不保存,但为了保证$fp
的值不被后面的过程覆盖,通常情况下,应该保存$fp
的值,并给局部数组(int array[10]
) 预留4×10=40个字节的空间。 - 从过程体来看,从
compare
返回后还需要用到数组基地址,故将其分配给$s1
。因此要用到的保存寄存器有两个:$s0
和$s1
,但只需将$s1
保存在栈帧中($s0
保存全局变量i
不用保护),另外加上返回地址$ra
(因为已经保存了前一个函数的返回地址),帧指针$fp
(因为已经保存了前一个栈帧的尾地址)、局部数组,其栈帧空间最少为3×4+40=52B
。
- 入口参数为
compare
过程的栈帧分析- 入口参数为
a
和b
,仅一个返回参数,没有局部变量,被调用过程为sub
。 过程体中没用到保存寄存器,所以,其栈帧中只需保留返回地址$ra
和$fp
的值
- 入口参数为
sub
过程的栈帧分析:叶子过程,其栈帧为空- 栈的变化示意图
6. 翻译C语言示例
判断等于
Loop循环
- 注意最后一句
bne $3,$2,Loop
怎么翻译机器码。注意imm16
的值一定是相对下一条指令说的
- 注意最后一句