上一节大概介绍了MIPS32汇编语言的书写规则,这一节将继续深入,讲解机器码的书写规范
承接前文,汇编语言就是机器码的注记符,汇编语言的每一行代码都可以改写成一串数字的形式,这就是机器码。
在之前的内容中,可以将汇编指令简要分为三种:
1. 计算类指令,比如add,sub等
2. 存取类指令 比如sw lw等
3. 跳转类指令,比如beq bnq等
具体到机器码时,分类方式又有所不同。MIPS32的指令分类依靠的是指令的书写格式,由此分成了R,I,J型三种指令
出现常数的指令,比如sw,lw,beq,bnq等,但是不包含移位指令(Shift)
J,Jar等无条件指令,但不包含子程序调用指令(Jr)
其余所有都为R型指令,R型指令的特点是所有操作单元都是寄存器,例如add sub等
R型指令的操作域如下
为了简化操作每个空间都有独自命名,具体如下
每个空间都视为无符号整数,五位数可以表示0-31,六位数可以表示0-63
opcode:这里作为R型指令的识别码,计算机首先读入opcode确定指令为R型,之后再继续进行读入(注意计算机首先从高位读入)
funct:功能代码 这里写入具体的操作类型,辅助opcode进行识别
如前文所述,R型指令最多可以写64种
rs:register source 含有第一个信息源寄存器的代码
rt:register target 在R型指令中其实是第二个register source,含有第二个信息源寄存器的代码
rd:register destination 含有目的寄存器的代码
shamt:移位指示符,用来表示移位的位数。除了移位操作以外都置零
用加法操作举例
add $8,$9,$10
将其分解成机器码的规范格式(注意寄存器的对应)
add R[rd] = R[rs] + R[rt]
查表可知add指令的机器码是32,所以完整机器码如下
也可以写成16、十进制的形式
0x 012A 4020
19,546,144
NOP指令的机器码为0x00000000
汇编语言表示为
sll $0,$0,0
不难看出NOP指令并没有进行有效操作,其目的是让机器暂缓执行指令,等待一个机器周期
由于I型指令需要用到常数,而且常数的位数一般较多。因此五位的寄存器码是不能满足常数的需求的。由此,对R型指令的规范进行了改变,以便适应I型指令
I型指令的机器码和操作域如下
opcode:和R型不同,这里的opcode直接对应I型操作指令的机器码(类似R型funct的功能)
rs:与R型相同
rt:和R型操作的rd类似,存有目的地寄存器的机器码
以常数加法操作为例
addi $21,$22,-50
按照机器码的书写规范如下:(注意寄存器对应)
addi R[rt] = R[rs] + SignExtImm
查表可知addi的机器码是8,因此书写如下:
注意这里的-50是用补码形式书写
即使经过I型指令的调整,也可能会有超标的常数出现。对于高于16位的常数的情况,解决方法如下:
Lui指令
lui reg,imm
这条指令将一个多位数imm的高16位移入寄存器,同时将寄存器的低16位置零
因此,当我们面对较大常数时,比如下面的情况
addi $t0,$t0,0xABABCDCD
实际上计算机内部进行了如下指令
lui $at,0xABAB # upper 16bit
ori $at,$at,0xCDCD # lower 16bit
add $t0,$t0,$at # move
这样就把32位数载入寄存器了
对于存取指令,例如
sw $4, -64($6)
位于后面的寄存器永远为register source,前面的永远为目的寄存器
对于带条件的跳转指令,比如beq和bne指令也是I型指令。beq的操作码为4,bne的操作码为5.下面将详细介绍常数在跳转指令中的表现形式
由于跳转指令主要用于循环操作,因此指令需要地址空间较少。在MIPS32架构计算机和大部分现代计算机中,指令地址通常都存放在程序计数器(PC)中。跳转操作就是以PC的地址为基准实现相对跳转
首先给出beq操作的一个应用
Loop: beq $9,$0,End
addu $8,$8,$10
addiu $9,$9,-1
j Loop
End:
这样构成了一个简单的for循环操作,也可以知道beq操作一般写为
beq $a, $b, label
的形式,常数对应的助记符就是label的位置
如前文所述,PC应用的是计数器相对寻址,也就是说,如果条件成立,指令会跳转到PC+label代表的偏移量的位置。由于immediate一共有16位,所以可以表示的地址空间是64KB()
然而,由于MIPS架构是按字寻址,一条指令就要占据4个byte,64KB的寻址空间还是稍显渺小。因此,在使用中使用偏移量的地址指代偏移的字地址,这样就将寻址空间扩展为
对于beq指令来说,如果不进行跳转,则PC=PC+4;如果跳转,则PC=PC+4+immediate*4
以上面的循环例子来说,因为要跳过三条指令,所以immediate=3,机器码为
再看下面这条指令
beq $5 , $5, -1
偏移量为-1*4 = -4, 刚好跳转到这条指令之前,构成死循环
如果目的地过大,则可以用far指代过远的地址,应用如下:
beq $s0,$0,far bne $s0,$0,next
# next instr --> j far
next: # next instr
应用J型指令,可以实现在整个内存空间全部范围的跳转
然而,J型指令的指令空间为32位,其中OPCODE和上面一样都需要占据6位空间。因此,为了在4GB大小的内存空间实现寻址,需要使用特定方法对指令空间实现拓展
指令空间如下所示:
首先,和beq指令一样,可以用字地址代指字节地址。因此,26位空间就被拓展到了28位空间。对于剩下的四位,取PC计数器的高四位合并,这样就合成了32位地址
这样,指令运行后新的PC地址为:–New PC = { (PC+4)[31..28], target address, 00 }
三种类型的指令空间:
beq等指令使用PC相对寻址,j使用绝对寻址