是六个比特,最低位的funct域也是六个比特,
中间的四个域,均为五个比特,咱们分别来看各个域的用途。
opcode域,用于指定指令的类型,对于全部的R型指令,这个域的值,均为零,
但这并非说明R型指令只有一种,它还须要用funct域来更为精确的指定指令的类型。因此说,对
于R型指令,实际上一共有12个比特操做码,
那你们能够思考一下,为何不将opcode域和funct域合并成一个12比特的域呢?
那样岂不是更直观明了吗?咱们再来看这些5比特的域。
RS域,这个域一般用来指定第一个源操做数所在的寄存器编号,
rt域一般用来指定第二个源操做数所在的寄存器的编号,
rd域一般用来指定目的操做数的寄存器编号,也就是保存运算结果的地方。
5个比特的域能够表示0-31的数,正好对应MIPS的体系结构中的32个通用寄存器,
还剩下最后一个域,它指示的是一位操做的位数。由于对于
32比特的数,5比特的域正好能够表示0-31的移位位数。
那这个域只是对于移位指令有用,对于非移位指令,这个域被设为0,
咱们来看一个例子,这是将9号寄存器和10号寄存器中的数相加,把运算结果保存在8号寄存器中,
那咱们经过这条汇编指令的描述,如何获得MIPS指令的二进制编码呢?
这其实很容易。首先,咱们查询MIPS指令编码表,就能够获得
加法指令的opcode域应该是0,funct域应该是32,
由于它不是移位指令,因此移位的域被设为0,而后咱们根据这条指令的操做数
能够获得目的操做数,也就说rd这个域
等于8,第一个源操做数应该是9,第二个源操做数应该是10,
这样咱们把各个域的数值转换成二进制数,填写到对应的位置,就能够获得这条指令的二进制编码了。
MIPS指令系统简洁明了的规则可让咱们很是容易的对指令进行这样的手工编码转换,
一样也说明了CPU对这样的指令进行硬件的译码也会很是的方便。
若是指令中须要用到当即数,那么就要用到I型指令,
由于R型指令当中只有一个5比特的域,也就说移位这个域 能够用来表示当即数,那能表示的数的范围为0-31,
在程序中经常使用的当即数远大于这个范围,因此R型指令
并不适用,咱们须要新的指令格式。这就是I型指令,I型指令的大部分域与R型指令是相同的,
I型指令的第一个域,也是opcode域,用于
指定指令的类型,但它没有funct域,因此不一样的I型指令,及opcode域是不同的。
第二个域rs,指定了第一个源操做数所在的寄存器编号,
第三个数rt用于指定目的操做数,I型指令与R型指令不一样,它只有两个寄存器数域,
剩下的16位被整合成了一个完整的域,能够存放16位的当即数,
能够表示2的十六次方个不一样的数值。对通常的访存指令,咱们须要用一个寄存器,加上一个当即数来指示一个内存单元,那么这个
当即数就是访存地址的偏移量,16位的当即数,能够访问正负32K的空间,
对于通常的访存指令来讲,就能够知足了。而对于运算指令,虽然没法知足所有的需求,可是大多数状况下,
16位也可使用了。在这一点上,就能够体现出X86这样的CISC指令系统的优点,对于X86指令来讲,
若是它想使用更大宽度的当即数,它能够很容易的扩展,由于它的指令原本就没有限制长度,可是
MIPS指令就不行。它的指令总长度就是32位的,再加上各个寄存器位域的使用,
因此I型指令最多只能使用十六位的当即数。
咱们来看一个例子,对于加法,若是咱们想让其中的源操做数是一个当即数的话,就能够用add
i这个指令,注意它和add指令是不同的。add指令的操做数必须都是寄存器。
咱们再来练习一下手工转换指令的编码。咱们经过查指令编码表,能够发现add
i指令的opcode域是8,从这一点咱们也能够看出,add
i和add虽然只有一个字母的差异,可是他们指令格式是彻底不同的。
剩下的域咱们经过分析这条指令的操做数就能够获得,
rs域,等于22,rt域,等于21,当即数域,
等于-50,咱们将这些数转换成二进制,就能够获得这条指令的编码了。
而后咱们来看全部的分支指令,分支指令
是用于改变控制流的指令,其实就至关于X86当中的转移指令。
在MIPS中,分支指令也分为条件分支和非条件分支
两种。对于条件分支有两条指令,beq和bne,对于非条件分支,只有一条指令——j,
咱们先来看条件分支指令,条件分支指令其实是i型指令。
这就是两条条件分支指令,他们的opcode域分别是4和5,
咱们以beq指令为例,它共有三个操做数,前两个是寄存器操做数,
第三个操做数是存储器地址,也就说一个当即数,CPU会判断第一个寄存器当中的数
和第二个寄存器当中的数是否相等。若是相等就跳转到 LE所指向的寄存器单元取出下一条指令,不然,
顺序执行deq以后的那条指令。咱们须要注意,
这里和X86的条件转移指令有很大的不一样。MIPS没有标志寄存器,它就在
一条指令当中即进行了比较,又完成了转移,
咱们还记得MIPS的全称,就是为了减小指令流水线的互锁,也就说要尽可能
避免不一样指令之间相互的影响。而标志位这件事,很明显就是前一条指令运行的结果,可能
会对后面的某一条指令产生影响,这是MIPS指令设计时要尽可能避免的。因此BEQ指令也很好的体现了MIPS的这一设计理念。
咱们来看一个例子。这段C语言代码是咱们常常会写的。
若是把它转换为MIPS指令,是这样的,第一条BEQ指令,
若是S3寄存器和S4寄存器内容相同,则转移到
Q所对应的这行指令。那么S3和S4中保存了I和J这两个变量,
若是他们内容相同,会转移到这里,执行加法指令,也就对应于F=G+H,若是他们不等,
则会顺序的执行下一条指令,也就一条减法指令对应于F=G-H
执行完以后,会跳过这条加法指令,而后进入后面的代码,
从条件分支指令的格式能够看出,目标地址只能使用十六位的位移量,
这是一个很大的局限,可是咱们还得考虑如何充分发挥这十六位的做用。
若是以当前的PC寄存器为基准,在MIPS中,指向下一条指令地址的寄存器称为PC,
相似于X86中的IP寄存器。这个寄存器,是指向32位寄存地址的。
若是以它为基准,十六位位移量能够表示出当前指令先后2的15次方字节这么一个范围,
可是咱们要注意一点,MIPS的指令长度固定位32个比特,所以每条指令的位置,
必定会在四个字节对齐的地方,这样地址,最低两位确定为0。
因此咱们实际上能够用十六位的位移量去指示每四个字节为一个单位的地址。
这样就能够把目标地址的范围扩大四倍,能够达到先后128KB。
在这样的条件下,目标地址应该这么计算,
当分支条件不成立时,下一条指令的地址就等于当前的pc+4。
若是分支条件成立,那下一条指令的地址就等于已经加了4的pc,再加上这个当即数乘以四。
而后咱们来看非条件分支指令,相比于条件分支指令,有两个寄存器域用于
比较条件,那若是咱们不须要判断条件,咱们就能够想办法扩大目标地址的范围。
固然理想状况下是直接使用32位的地址,但仍是由于MIPS的指令长度固定为32位,而每条指令
至少须要有opcode域,指示它指令类型。
这就占用了六个bit。那咱们把剩下的26个bit全都用于目标地址,
这就是J型指令。在考虑到MIPS指令是四字节对齐的这个状况,
对于这一行指令,下一条指令的地址的计算方法能够是将当前的pc加四以后,
取最高的四位,再加上J型指令编码中的26位,
而后在末尾填上两个零,虽然目标地址的范围还不能达到整个4G的空间,但比以前的条件分支指令已经扩大了不少。
咱们用一个例子来进行进一步的说明。
假设咱们在高级语言中用的若干变量与寄存器的对应关系是这样的,
那咱们就能够用这样一种方式来实现这段c语言的代码,
第一条指令是判断i和j是否相等,若是不相等,则转移到else这个标号所对应的位置,
也就是执行一条减法指令对应于f=g-h,若是判断条件不成立,
也就是i=j的时候,顺序地执行下一条加法指令,
也就对应于f=g+h。而后用无条件分支指令
跳到else条件以后继续执行后面的程序。
咱们如今已经知道这个J型指令的目标地址能够是当前指令
先后256MB的范围,那若是咱们还想跳转到更远的地址,应该怎么办呢?有一个很简单的方法
就是两次调用J指令,第一条J指令尽量跳到最远的地方,
而后在那个目标地址再放一条J指令,像接力同样再跳一次。
这个方法很简单,可是用起来不算太方便,那么还能够用什么方法呢?
你们还记得咱们曾说过间接转移指令吗?MIPS中也能够用一样的方法,
这就是jr指令。jr指令有一个寄存器操做数,
能够把要转移的目标地址放到寄存器当中,这样就可使用32位的目标地址了,
可是这样的指令显然没法用J型指令来实现,
那么须要新增一种指令类行吗?其实也不须要,咱们就用原来的r型指令就能够很好的实现。
只用占用其中的一个寄存器位域,而后新增一种function的编码就能够了。
这就是MIPS指令系统的核心内容,咱们只用熟悉这两页的内容就能够轻松的掌握MIPS的指令了。
咱们已经介绍完了MIPS制定系统体系结构,
它不愧为精简指令系统的经典设计,指令简洁,并且精巧。