文章目录
- 1 MIPS简介
- 2 MIPS指令系统的特点
- 3 MIPS寄存器
- 4 MIPS指令格式
- 4.1 R-Type型指令
- 4.2 l-Type型指令
- 4.3 J-Type型指令
- 4.4 三类指令小结
- 5 MIPS例题
- 6 MIPS过程调用
- 6.1 过程调用和栈
- 6.2 MIPS中栈的实现
- 6.3 栈帧的概念
- 6.4 MIPS中的过程调用(假定P调用Q)
- 6.5 过程调用举例
1 MIPS简介
-
MIPS是(
Microcomputer without interlocked pipeline stages
)的缩写,含义是无互锁流水级微处理器。MIPS采用的是精简指令系统计算结构(RISC
结构)(与之对应的:(复杂指令集)CISC
结构)。RISC
比CISC
的设计更加简单,由于其授权费用低,被INTEL
外的大多数厂商使用。同时在设计理念上MIPS强调软硬件协同提高计算机性能,并简化硬件设计。 -
MIPS 是最早的,最成功的
RISC
处理器之一,MIPS处理器广泛的应用于:数字电视、机顶盒、蓝光播放器、游戏机、网络设备。
2 MIPS指令系统的特点
- 指令的类型较少;
- 指令的复杂度较低;
- 这些特点使得CPU运行的更快;
- 用标号、变量名称、寄存器名称、常数表示操作数或地址码。
- 固定的指令长度(32-bit,即1word)
简化了从存储器取指令
- 简单的寻址模式
简化了从存储器取操作数
- 指令数量少,指令功能简单(一条指令只完成一个操作)
简化指令的执行过程
- 只有
Load
和Store
指令可以访问存储器
例如,不支持
×86
指令的这种操作:ADD AX,[3000H]
3 MIPS寄存器
MIPS 包含32个通用寄存器 (
$0-$31
均为32 位), 硬件没有强制性的指定寄存器使用规则,但是在实际使用中,这些寄存器的用法都遵循一系列约定,寄存器约定用法引入了一系列的寄存器约定名。在使用寄存器的时候,要尽量用这些约定名或助记符,而不直接引用寄存器编号。
REGISTER | NAME | USAGE |
---|---|---|
$0 | $zero | 常量0(constant value 0 ) |
$1 | $at | 保留给汇编程序(Reserved for assembler ) |
$2-$3 | $v0-$v1 | 函数调用返回值(values for results and expression evaluation ) |
$4-$7 | $a0-$a3 | 函数调用参数(arguments ) |
$8-$15 | $t0-$t7 | 暂时的(或随便用的) |
$16-$23 | $s0-$s7 | 保存的(或如果用,需要SAVE/RESTORE 的)(saved ) |
$24-$25 | $t8-$t9 | 暂时的(或随便用的) |
$28 | $gp | 全局指针(Global Pointer ) |
$29 | $sp | 堆栈指针(Stack Pointer ) |
$30 | $fp | 帧指针(Frame Pointer ) |
$31 | $ra | 过程调用返回地址(return address ) |
(1)两个特殊寄存器:
$0
:不管你存放什么值,其返回值永远是零。
$31
:永远存放着正常函数调用指令(jal)
的返回地址。
(2)$at :
由编译器生成的复合指令使用
(3)$v0, $v1:
用来存放一个子程序 (函数) 的非浮点运算的结果或返回值。如果这两个寄存器不够存放需要返回的值,编译器将会通过内存来完成。
(4)$ a0-a3:
用来传递子函数调用时前4个非浮点参数。
(5)$ t0-t9:
依照约定,一个子函数可以不用保存并随便的使用这些寄存器。在作表达式计算时,这些寄存器是非常好的暂时变量。当调用一个子函数时,这些寄存器中的值有可能被子函数破坏掉。所以也是最不安全的。
(6)$ s0-s8:
依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到函数调用以前的值, 或者在子函数里不用这些寄存器或把它们保存 在堆栈上并在函数退出时恢复。 这种约定使得这些寄存器非常适合作为寄存器变量、 或存放一些在函数调用期间必须保存的原来的值。(类比:x86汇编中的函数序言和函数尾声)
(7)$ k0, k1:
被OS的异常或中断处理程序使用。被使用后将不会恢复原来的值。因此它们很少在别的地方被使用。
(8)$gp:
如果存在一个全局指针,它将指向运行时决定的静态数据(
static data
)区域的一个位置。这意味着,利用gp
作基指针,在gp
指针32K
左右的数 据存取,系统只需要一条指令就可完成
(9)$ sp:
堆栈指针的上下需要显 式的通过指令来实现。因此
MIPS
通常只在子函数进入和 退出的时刻才调整堆栈的指针。 这通过被调用的子函数来实现。SP
通常被调整到这个被调用 的子函数需要的堆栈的最低的地方,从而编译器可以通过相对sp的偏移量来存取堆栈上的堆栈变量。
(10)$ fp(另外的约定名是s8):
fp
作为框架指针可以被函数用来记录堆栈的情况,在一 个过程中变量相对于函数指针的偏移量是不变的。(相对地址)一些编程语言显示的支持这一点。汇编编程员经常会利用fp的这个用法。C语言的库函数 alloca()就是利用了fp来动态调整堆栈的。
(11)$ ra:
当调用任何一个子函数时,返回地址存放在
ra
寄存器中,因此通常 一个子程序的最后一个 指令是:jr ra
.子函数如果还要调用其他的子函数,必须保存ra
的值,通常通过堆栈。
(12)其他方面:
MIPS
里没有状态码。CPU状态寄存器或内 部都不包含任何用户程序计算的结果状态信息。
hi
和lo
是与乘法运算器相关的两个寄存器,是用来存放结果的地方。 它们并不是通用寄存器,除了用在乘除法之 外,也不能有做其他用途。MIPS
里定义了一些指令可以往hi
和lo
里存入任何值。
浮点运算协处理器 (浮点加速器,
FPA
),如果存在的话,有32个浮点寄存器。按汇编语言的简单约定讲, 是从$ f0
到$f31
。
4 MIPS指令格式
- 所有指令都是32位宽,须按字地址对齐字地址为4的倍数!
- 有三种指令格式
4.1 R-Type型指令
R-Type:一条32位的MIPS R型指令按下表bit数划分为 6个字段:6 + 5 + 5 + 5 + 5 + 6 = 32bit
(1)R型指令一共有6个域;
(2)shamt
字段表示的是移位操作的位数;
(3)R指令中opcode
的值一般是000000,操作类型由funct
字段指定;
(4)目的操作数是用来保存运算结果的操作数,送寄存器rd
;
(5)R型指令的寻址方式仅仅一种,就是寄存器寻址。两个操作数和结果都在寄存器的运算指令。
实例: add $8, $17, $18
$8 = $17 + $18
第一个操作数是寄存器 $17,第二个寄存器是 $18,目的寄存器结果是 $8.该指令没有移位。
因为,加法是运算指令,指令操作类型码op
是0,funct
是32。
所以格式是:
0 | $17 | $18 | $8 | 0 | 32 |
---|
4.2 l-Type型指令
l-Type:立即数型指令。6+5+5+16=32bit
I-型指令分类:装入/存储指令、分支指令和 立即数运算指令
运算指令:一个寄存器、一个立即数。如:ori rt,rs,imm16(Immediate,立即数)
LOAD
和STORE
指令。如:lw rt,rs,imm16
条件分支指令。如:beqrs,rt,imm16
4.3 J-Type型指令
J-Type:无条件跳转指令。6+26=32bit。
4.4 三类指令小结
R:Register,寄存器I:Immediate,立即数J:Jump,无条件转移
- OP:操作码
- rs:第一个源操作数寄存器
- rt:第二个源操作数寄存器
- rd:结果寄存器
- shamt:移位指令的位移量
- func:
R-Type
指令的OP
字段是特定的“000000”,具体操作由func
字段给定。例如:func=“100000”
时表示“加法”运算。操作码的不同编码定义不同的含义,操作码相同时,再由功能码定义不同的含义!
- immediate:立即数或
load/store
指令和分支指令的偏移地址- target address:无条件转移地址的低26位。将PC高4位拼上26位直接地址,最后添2个“0”就是32位目标地址。为何最后J-型指令最后两位要添“0”?
回答:指令按字地址对齐,所以每条指令的地址都是4的倍数(最后两位为0)。
5 MIPS例题
- 若从存储器取来一条指令为00AF8020H,则对应的汇编形式是什么?
- 32位指令代码:0000 0000 1010 1111 1000 0000 0010 0000
指令的前6位为000000,根据指令解码表知,是一条R-Type
指令,按照R-Type
指令的格式划分:
- 得到:rs=00101,rt=01111,rd=10000,shamt=00000,funct=100000。根据
R-Type
指令解码表,知是“add”操作(非移位操作)- rs、rt、rd的十进制值分别为5、15、16,从MIPS寄存器功能表知:
rs、rt、rd分别为:$ a1、$ t7、$ s0
- 故对应的汇编形式为:add $ sq,$ a1,St7
功能:Sa1+St7→$s0
这个过程称为“反汇编”,可用来破解他人的二进制代码(可执行程序)
- 若MIPS Assembly Instruction:Add $ t0,$ s1,$ s2则对应的指令机器代码是什么?
- 从助记符表中查到Add是R型指令,即:
- 转化为寄存器单元和二进制指令:
MIPS中最重要的寄存器及其编号与功能与其助记
6 MIPS过程调用
引出问题:过程调用对应的机器代码如何表示?
- 如何从调用程序把参数传递到被调用程序?
- 如何从调用程序执行转移到被调用程序执行?
- 如何从被调用程序返回到调用程序执行?并把返回结果传递给调用程序?
- 如何保证调用程序中寄存器内容不被破坏?
6.1 过程调用和栈
过程调用的执行步骤(假定过程P调用过程a):
(1)将参数放到Q能访问到的地方;
(2)将P中的返回地址存到特定的地方,将控制转移到过程Q;
(3)为Q的局部变量分配空间(局部变量临时保存在栈中);
(4)执行过程Q;
(5)将Q执行的返回结果放到P能访问到的地方;
(6)取出返回地址,将控制转移到P,即返回到P中执行 ;
(1)(2)在调用过程P中完成;(3)(4)(5)(6)在被调用过程Q中完成。
如果过程中用到的参数超过4个,返回值超过2个,怎么办?
- 更多的参数和返回值要保存到存储器的特殊区域中
- 这个特殊区域为:栈(Stack)
- 栈的基本概念
- 是一个“先进后出”队列
- 需一个栈指针指向栈顶元素
- 每个元素长度一致
- 用“入栈”(push)和“出栈”(pop)操作访问栈元素
6.2 MIPS中栈的实现
- 用栈指针寄存器
$sp
来指示栈顶元素 - 每个元素的长度为32位,即:一个字(4个字节)
- “入栈”和“出栈”操作用
sw/lw
指令来实现,需用add/sub
指令调整$sp
的值,不能像×86那样自动进行栈指针的调整
(有些处理器有专门的push/pop
指令,能自动调整栈指针。如×86) - 栈生长方向
从高→低地址“增长”,而取数/存数的方向是低→高地址(大端方式)
每入栈1字,
$sp-4→$sp
;每出栈1字,$sp+4→$sp
6.3 栈帧的概念
- 各过程有自己的栈区,称为栈帧(
Stack frame
),即过程的帧(procedure frame
) - 栈由若干栈帧组成
- 用专门的帧指针寄存器指定起始位置,当前栈帧范围在帧指针和栈指针之间
- 程序执行时,栈指针可移动,帧指针不变。所以过程内对栈信息的访问可通过帧指针进行
- 复杂局部变量一定分配在栈帧中
6.4 MIPS中的过程调用(假定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中用的话就被破坏,故需保存)
- 除了上述寄存器以外,所有局部数组和结构等复杂类型变量也要入栈保存
- 如果局部变量和临时变量发生寄存器溢出(寄存器不够分配),则也要入栈
6.5 过程调用举例
假定swap作为一个过程被调用,temp对应$t0
,变量v和k分别对应$a0
和$a1
。写出对应的MIPS汇编代码。
swap(int v[],int k)
{int temp; temp=v[k]; v[k]=v[k+1];v[k+1]=temp;
}
sll $s2,$a1,2 //mulitply k by 4 ,int类型,k是下标,乘4→左移两位
add $s2 $s2,$a0; //address of v[k] ,k的地址
lw$to,0($s2); //load v[]
lw$s3,4($s2); //load v[k+1]
sw$s3,0($s2); //store v[k+1] into v[k]
swSt0,4($s2); //store old v[k] into v[k+1]