COpre——MIPS汇编指令与机器码、内存

在笔者进行COpre中MIPS部分的学习时,对于MIPS指令及其机器码转换,MIPS对应的内存计算等等感到十分头痛,这一篇文章主要目的是零碎的记录一些知识。

一. 数制

​ 在计组学习中最常用到的数值即为二进制(binary/BIN)和十六进制(hexadecimal/HEX),例如MIPS汇编指令机器码用二进制表示,而计算机内存的表示通常为十六进制。一位十六进制数相当于四位二进制数。一位二进制数通常又称为比特位(bit),关联出计算机系统中经典的换算关系。

​ 在32位系统中:

  • 1字节(byte)=8比特位(bit) 计算机中最小的寻址单位即为字节/B
  • 1个字(word)=4字节(byte)=32bits 即一个字就是32位,同样64位系统中一个字是64位
  • 1KB=1024B 计算机中K的概念是2^10即1024
  • 1MB=1024KB
  • 1G=1024MB

二. 浅析MIPS架构

​ MIPS是一种经典的RISC架构,具有精简的指令集,32位定长指令,五级流水线,延迟槽,32个通用寄存器等特点。这里我们主要谈及32位定长指令、32个通用寄存器、指令集等内容,其他的部分会在日后涉及。这里需要特殊说明的是我们的MIPS是32位系统

1.32位定长编码

​ 定长指令的优点是简化指令解析,减少解析时间。但同样的,由于指令为定长32位,这对于内存是不友好的。我们在写MIPS汇编程序时,编写的每一行指令代码均为32位,四个字节,一个字。这里指令具体的分为R型、I型、J型指令

RIJ

  1. R型指令(register type)

    R型指令用于寄存器之间的操作,常用于算术运算、逻辑操作和寄存器之间的数据传输,如add,sub.and.or等。

  2. I型指令(immediate type)

    ​ I型指令用于立即数(常数)与寄存器之间的操作,通常用于家在常熟、内存读写、分支跳转等操作。例如,addi,lw,sw,beq等指令

  3. J型指令(jump type)

    ​ J型指令用于无条件跳转到目标地址,常用于函数调用、循环跳转等控制流程的修改。例如,j,jar指令。

    对于以上指令,我们只需要记住他们都占32位,4个字节,其他的具体用法详见《MIPS-C指令集》。

2.32个通用寄存器

​ MIPS寄存器是32位寄存器,每个部分的功能如图所示

通用寄存器

​ 需要注意的是,每个寄存器既可以用名字表示,也可以用编号表示,如**$to<==>$8**。

3.特殊寄存器

1. HI(high)与LO(low)

​ HI与LO是MIPS中用于处理乘除法的特殊寄存器,在MIPS汇编指令中,乘除法指令的结果最多为64位,夫需要设置特殊寄存器进行保存。在乘法中,HI保存高32位,LO保存低32位;在除法中HI保存余数,LO保存商。

2.PC程序计数器

​ PC(program counter)程序计数器,是计算机系统中的一个寄存器,用于存储下一条指令的地址。程序计数器指向执行中的指令的内存地址,当处理器执行完当前指令后会自动将程序计数器的值增加,使其指向下一条指令的地址。具体地,在MIPS汇编指令中,每执行完一条指令 PC=PC+4,这是由于在MIPS中每一条指令所占的内存空间都是4个字节。程序计数器的初始值一般为程序的入口地址(首条指令的地址 最常见的为0x0000_3000)。同时分支指令也可以使程序计数器进行跳转。总的来说,PC相当于程序运行中的内存监控,通过PC可以了解程序的流程

1
2
3
4
5
6
7
8
9
//在我翻阅 MIPS-C指令集时,发现了如下出场率极高的代码
//BEQ: beq rs,rt,offset
//描述:if rs==rt then 转移
//以BEQ:相等时转移为例,功能的C语言描述为
if(GPR[rs]==GPR[rt])
    PC=PC+4+sign_extend(offset||0^2);//代表在offset后补两位0 即乘四
else
    PC=PC+4;
//我们可以发现,该指令至少会进行一条语句的跳转(四个字节),这也是我查询PC的起始

三.COpre中提供的部分题目具体分析

1.下列指令中需要在立即数后拼接两位0的是

1
答案: beq $s2,$s3,4

在立即数后拼接两位0,即将原立即数向左移动两位,立即数4,代表着按4对齐。在beq中,立即数n的意义是跳转到第n条指令,而实际操作中,一条指令占用四字节,地址访存的话需要跳转4n字节,所以需要拼接两位0。

2.与上一题类似的beq计算next立即数

1
2
3
4
5
ori $t0,$0,4
bne $t0,$t1,next
nop//no operation 占位符指令,不执行任何内容
next:
sw $t0, 4($t1)

功能分析:beq:相等时跳转,bne:不相等时跳转,在此题目中,当$t0与$t1不相等时跳转到next标签,我们之前在《MIPS-C》中发现了bne的C代码

1
2
3
4
if(GRF[rs]!=GRF[rt])
     PC=PC+4+sign_extend(nest|0^2);
else
    PC=PC+4;

可以知道,无论如何PC计数器都会+4,即向前迈出一条指令,这里next可以增加移动的指令数,在此题目中,可以发现,跳转到next标签需要跳两条语句,故next==1.此题的原型为表示16进制的八位机器码(32位二进制==8位十六进制,用电脑内置的计算器即可将首先得到的32位机器码转换为8位十六进制)。关于机器码只需要进行查表即可。

3.下列操作过程中,需要将立即数进行符号扩展的是

1
2
3
4
5
1. beq $s2, $s3, 4
2. lw $s2, 4($s3)
3. addi $s2, $s3, 4
    //拓展说明:temp<- (GPR[rs]||GPR[rs]31..0)+ sign_extend(immediate) 符号扩展
4. sltiu $s1, $s2, 0x8888

​ **通过R,I,J指令的结构可知,立即数通常是没有32位的,在有些指令中为26位,在有些指令中为16位。**而在ALU运算及读写存储等等操作都需要32位,所以需要将立即数拓展到32位,MIPS指令手册规定了每条指令的拓展方式(符号扩展/0扩展)。

4.下列操作过程中,需要将立即数无符号扩展的是

1
2
ori $s2, $s3, 4
//拓展说明: GPR[rt]<-GPR[rs] or zero_extend(immediate) 无符号扩展

以上两题均可以通过查英文版MIPS指令解决(MIPS-C过于简略),只需要查看Operation一栏

5.下列指令中属于R型的是

1
2
sub $s2, $s2, $s2
jarl $s0, $a0

需要注意的是:不是所有j开头的指令都是J型,jarl为R型指令,最好分辨的是I型指令,在指令当中有立即数

查表时分表R、I、J型指令的方法:看32位数据分割

  • R:6+5+5+5++5+6
  • I:6+5+5+16
  • J: 6+26

6.汇编代码流程分析

1.例一jal-jr

程序结束时 a2=0x00003001

jal指令用于跳转到目标地址,并将jal指令的下一条指令的地址存储到ra中,jr用于跳转到一个寄存器中存储的目标地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//程序的第一条指令的地址为0x0000_3000
ori $a0, $0, 1
jal loop //跳转到loop标签处,这里loop在编译时转换为立即数
add $a2, $a2, $a0 // 地址为0x0000_3008 即ra=0x0000_3008
li $v0, 10
syscall//最后两行为程序结束

loop:
sll $a1, $a0, 3// a1=a0<<3  (a0*8)
sub $a2, $ra,$a1// a2=ra-a1
jr $ra          //在执行jar跳转时,会将jal的下一条语句地址存入ra,此时跳回add一行
add $a2, $a1, $a0//未被执行
2.例二bne指令

程序结束时 a2=2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ori $a0,$0,1
ori $a1,$0,10
ori $a2,$0,20
loop:
addi $a0,$a0,1
addi $a2,$a2,-2
bne $a0,$a1,loop //循环条件 a0!=a1 继续跳回标签为loop处
nop
li $v0,10
syscall

7.load-store指令功能测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//在一个小端存储CPU中执行以下指令 t0 t1 t2寄存器中的值为多少
li $a0, 0x12345678
li $s0, 0x00001000
sw $a0, 0($s0)//向以s0中存储的数为基地址(0x1000)偏移量为0的地址,store word,即存储四个字节,将a0全部存入(0x1000为起始地址,低地址),$a0中的数为4byte。对应内存中也需要4byte.
    // 0x1000 存储 0x78
    // 0x1001 存储 0x56
    // 0x1002 存储 0x34
    // 0x1003 存储 0x12
lb $t0, 1($s0)//t0=0x56
sb $a0, 2($s0)// s0偏移两个字节->0x1002地址上sb(store byte) 0x34->0x78  
lw $t1, 0($s0)// load word 存储一个字 即为s0后4byte的值 t1=0x12785678
sh $a0, 2($s0)// sh(store halfword) 0x1002存储两个byte a0后两个byte存储到 0x1002 0x1003
lw $t2, 0($s0)// load word 经过以上操作 s0处开始存储的完整数为0x56785678 即为t2值

小端存储:数据的低位放在低地址,高位放在高地址;大端存储相反,这里面第一离谱的事情是li是MIPS扩展指令,而不是汇编指令,这导致我在MIPS指令集中查无此人。li扩展指令的含义为为寄存器赋值

1
li $a0,100//将100赋给a0寄存器

内存中每个单元只能存储一个字节,这导致初始时的a0被分成四个部分存储。

易混淆点:物理地址与内存地址

内存地址是对内存单元的编号(可以理解为门牌号),计算机中最小的寻址单位即内存单元为1byte,物理地址是真实的物理内存的地址

我们以上提及的0x1000与0x1001都是指内存地址(计算机中常用16进制表示内存地址),每两个内存地址之间的物理地址差异为一个字节。将以上内存地址与物理地址可以用图画联系起来,在每一个门牌号下都有一个大小为1byte的空间。

1
2
3
4
0x1000 : 0000_0000
0x1001 : 0000_0000
0x1002 : 0000_0000
0x1003 : 0000_0000

8.跳转指令范围

  1. j指令只有26位用于存储到跳转的地址,那么j指令能够跳转到的代码范围有多大?
1
2
3
2^26=64M
但是J指令有低位补00的拓展(即表示的是64M条指令,而不是64MB),所以应当表示256MB大小的代码
j 指令是 PC 相关的转移指令。当把 4GB 划分为 16256MB 区域,j 指令可以在当前PC 所在的 256MB 区域内任意跳转。  
  1. jr指令可以跳转的代码范围有多大?
1
2
jr跳转到的是寄存器(32位)中的值(jump to register),如之前的例子中 jr ra 4 * 2^10 * 2^10 * 2^10
但是没有低位拓展,所以表示4GB代码大小。
  1. beq指令可以跳转的代码范围有多大?
1
beq地址立即数为16位,2^16=64k条指令,即256KB大小 (2^10=1k)

由此可以知道 在operation部分出现|0^2意味着左移位两位,代表的是指令

9.数据溢出

1
addi $a0, $0, 0x8165

addi指令中,immediate是一个有符号的16位数,即原数最多15位,但是0x8165是16位数。