【计算机组成原理】汇编语言[mips-c指令]
- 1.什么是汇编语言
- 1.1 从机器语言谈起
- 1.2 汇编语言
- 1.3 高级语言
- 2. mips-c指令
- 2.1 指令基本分类
- 2.1.1 R类指令
- 2.1.2 I类指令
- 2.1.3 J类指令
- 2.2 六大基本指令
- 2.2.1 add指令
- 2.2.2 lui指令
- 2.2.3 sw指令
- 2.2.4 lw指令
- 2.2.5 beq指令
- 2.2.6 j指令
- 2.3 mars中的syscall指令
- 3. 编写汇编程序
- 3.1 操作对象
- 3.2 基本指令
- 3.3 程序基本结构
- 3.3.1 数据段与代码段
- 3.3.2 标签
- 3.3.3 宏定义
- 3.3.4 缩进、花括号?
- 3.4 编程实例
- 3.4.1 循环读入与输出
- cpp代码
- 汇编代码
- 3.4.2 斐波那契数列
- 3.4.2.1循环版本
- 3.4.2.2递归版本
1.什么是汇编语言
1.1 从机器语言谈起
计算机语言分为三类:高级语言、汇编语言和机器语言。
机器语言就是0和1的数字编码,是机器能读懂的东西,用一定长度(32或64位)的二进制数代表一条指令。
举个例子,如果我想让机器执行一条这样的指令:将1号(寄存器编号的二进制形式为
0000
1
2
00001_2
000012)寄存器的值和2号(寄存器编号的二进制形式为
0001
0
2
00010_2
000102)寄存器的值相加,运算结果存入3号(寄存器编号的二进制形式为
0001
1
2
00011_2
000112)寄存器。(寄存器:CPU内部有一定数目的寄存器(一般为32个),用来存储正在操作的数值)
那么这条机器码就是: 00000000001000100001100000100000。
这条指令的编码规则如下:
- 最高6位称为OpCode,OpCode = 0表示这是一条R型指令
- 最低6位是R型指令的Function,表示这条指令执行什么运算,Function = 10000 0 2 = 3 2 10 100000_{2} = 32_{10} 1000002=3210表示这条指令是执行加法运算的指令。
- 中间的20位中有15位用来表示这条指令所要操作的寄存器编号。(共32个寄存器,每个寄存器的编号是0-31的整数,因此用5位二进制数表示即可区分出所有寄存器)。rs,rt表示储存两个加数的寄存器,rd表示储存结果的寄存器。shamat部分在加法指令中没有用,置0。
这条加法指令的结构如下所示:
规则 | OpCode | rs | rt | rd | shamat | Function |
---|---|---|---|---|---|---|
编码 | 000000 | 00001 | 00010 | 00011 | 00000 | 100000 |
含义 | R指令 | 1号寄存器 | 2号寄存器 | 3号寄存器 | 0 | 加法 |
其他指令也是按照类似的规则进行编码的。
显然人类直接用机器语言来写程序是很困难的,于是人类发明了汇编语言。
1.2 汇编语言
汇编语言其实是机器码的简明记录形式。
在上述例子中我们可以发现按照这样的规则进行机器编码的好处:对于加法指令而言,OpCode、shamat、Function这几部分都是一样的,有区别的仅仅是寄存器的编号,所以,这条指令记录指令的运算类型和三个寄存器编号即可。
在汇编语言中,我们将上述例子中的编码记录成如下形式:add $3, $1, $2
。
同理,减法:sub $3, $1, $2
,或:or $3, $1, $2
,异或:xor $3, $1, $2
……
1.3 高级语言
虽然变得简单直观了,但是用汇编语言写程序还是很反人类的,于是人们又发明了高级语言例如C、python等,就是程序猿经常写的东西,语法上比较接近人类的自然语言。这些高级语言是汇编语言的进一步整合与简化。
举一个C语言与汇编语言转化的例子
C 语言:
if(x = 1)
x = x + 1;
else
x = 0;
汇编语言(mips-c)
#假设address_x是内存中储存变量x的位置的地址
addi $t1, $0, 1 # $t1 = 0 + 1 = 1
lw $t2, address_x #从内存中取出x的值,放入寄存器t2中
bne $t1, $t2, label_if #如果$t1与$t2不相等,跳到标签label_if处,否则继续执行
label_else:
addi $t2, $0, 0 # x = 0
j label_if_end #跳转到标签label_if_end处
label_if:
addi $t2, $t2, 1 #x = x + 1
label_if_end:
sw $t2, address_x #将x的值放回到内存中
2. mips-c指令
现在,我们正式开始了解mips-c指令集。mips-c指令根据编码形式的不同分为三类:R类、I类、J类。每一条指令都可以转化成一个32位的二进制数。
2.1 指令基本分类
2.1.1 R类指令
R类指令有add(加法)、sub(减法)、or(或)、xor(异或)、slt(小于置1)、srl(无符号右移)、sra(符号右移)、sll(左移)等。一般表示为如下形式:
OpCode | rs | rt | rd | shamat | Function |
---|---|---|---|---|---|
6位 | 5位 | 5位 | 5位 | 5位 | 6位 |
- R类指令OpCode为0。
- Function区别不同的运算
- rs、rt、rd为三个寄存器,通常用rd表示结果寄存器
- shamat用于可变左(右)移时表示左(右)移的位数,由于操作的是32位数,因此只需5位二进制数即可
2.1.2 I类指令
I类指令有addi、subi、lui、lw、sw、beq等。一般表示为如下形式:
OpCode | rs | rt | immediate |
---|---|---|---|
6位 | 5位 | 5位 | 16位 |
- I类指令以OpCode区别不同的指令类型,例如addi的OpCode是 00100 0 2 001000_{2} 0010002,lui的OpCode是 00111 1 2 001111_{2} 0011112。
- rs、rt为运算所需的两个寄存器,通常用rt表示结果寄存器
- immediate表示16位2进制立即数,是直接附加在指令中传递给CPU的,不来自内存或寄存器。
举个例子:addi $1, $2, 100,即 $1 = $2 + 100,其机器码为:
OpCode | rs | rt | immediate |
---|---|---|---|
001000 | 00001 | 00010 | 0000 0000 0110 0100 |
2.1.3 J类指令
I类指令有j、jr、jal、jalr。
j、jal表示为如下形式:
OpCode | address |
---|---|
6位 | 26位 |
- J类指令以OpCode区别不同的指令类型。
- address表示跳转的指令的地址
jr、jalr指令表示为如下形式:
OpCode | rs | 0 |
---|---|---|
6位 | 5位 | 26位 |
- JOpCode区别不同的指令类型。
- t跳转的地址为rs寄存器中的值
2.2 六大基本指令
如果能够理解以下六个指令,那么就能基本理解Mips-C指令的编码思维。
2.2.1 add指令
加法指令
OpCode | rs | rt | rd | shamat | Function |
---|---|---|---|---|---|
6位 | 5位 | 5位 | 5位 | 5位 | 6位 |
操作:$rd = $rs + $rt
2.2.2 lui指令
置高位指令,将某寄存器的值的高16为置为指定立即数,低16位置为0。
OpCode | 0 | rt | immediate |
---|---|---|---|
6位 | 5位 | 5位 | 16位 |
操作:$rt = {immediate, 0000_0000_0000_0000}
例如:lui $t1, 7
运算后,$1的值为111_0000_0000_0000_0000
2
_{2}
2
2.2.3 sw指令
store word 指令,向内存存入数据的指令。
OpCode | rs | rt | immediate |
---|---|---|---|
6位 | 5位 | 5位 | 16位 |
操作:
address = $rs + immediate
memory[address] = $rt
例如:sw $t0, 100( $t1 )
运算后,将 $rt 的值存入内存地址为 $rs + immediate 的位置。
2.2.4 lw指令
load word 指令,从内存取出数据的指令。
OpCode | rs | rt | immediate |
---|---|---|---|
6位 | 5位 | 5位 | 16位 |
操作:
address = $rs + immediate
$rt = memory[address]
例如:lw $t0, 100( $t1)
运算后,将 内存地址为 $rs + immediate 的位置的值存入 $rt 。
2.2.5 beq指令
分支指令,如果分支条件中两数相等,程序跳转到指定位置。
OpCode | rs | rt | immediate |
---|---|---|---|
6位 | 5位 | 5位 | 16位 |
操作:
if($rs == $rt)
PC = PC + 4 + sign_ext({immediate, 00
2
_{2}
2 })
例如:beq $t1, $t0, label
结果:如果$t1 == $t2,程序跳转到label处
2.2.6 j指令
跳转指令,程序跳转到指定位置。
OpCode | address |
---|---|
6位 | 26位 |
操作:
PC = {PC
31
…
28
_{31…28}
31…28,address,00}
例如:j label
结果:程序跳转到label处
2.3 mars中的syscall指令
syscall指令是以类特殊的指令,当 $ v0、$ a0 的值不同时,执行不同的操作。例如:$ v0 = 10时,执行syscall,程序结束,相当于C中的return 0;$ v0 = 5时,执行syscall,读入一个整数,存入$ a0中。
syscall对照表如下:
3. 编写汇编程序
3.1 操作对象
哈佛体系计算机中代码与数据分开存储。程序计数器记录程序运行到了哪一条指令,CPU运行时,读取程序计数器的值,根据指令地址,找到对应指令,然后识别指令,对寄存器或内存进行操作。
运算类的操作对象是内存和CPU内的32个寄存器。CPU完成的运算可以简单看成:从内存中取出所需的数据,暂时存到寄存器中,做运算,再将寄存器中的值放回内存中。
分支、跳转类的操作对象是程序计数器。程序计数器记录程序运行到了哪一条指令,分支、跳转类指令决定程序计数器的值如何改变。
3.2 基本指令
你需要了解一些基本语句的功能,例如add、sub、or、lui、beq、lw、sw、j、jal、jr等。详情请参见MIPS—C指令集。这里有一个31条的版本:MIPS 指令集(共31条)。这边版本缺少关键指令jr、jalr,在此解释。
jr $ra #跳转到ra寄存器中的数所代表的位置
jalr $ra #跳转到ra寄存器,并将当前程序地址存入ra中
3.3 程序基本结构
3.3.1 数据段与代码段
程序分为数据段(以.data标识)和代码段(以.text标识)两个部分。
.data # 在.data以下声明数据
.text # 在.text以下写代码
下面是一个例子:
.data # 在.data以下声明数据
matrix: .space 36 # 申请一个长度为9的数组,一个数4字节,所以空间大小为9 * 4字节
enter: .asciiz "\n" #存储换行符号
space: .asciiz " " #存储空格符号
.text # 在.text以下写代码
main:
la $s1, space # int *p = matrix
add $t0, $0, $0 # int i = 0;
3.3.2 标签
在代码段,除了指令,你还可以看到类似于main:
、loop:
之类的标签,这些标签主要是用于方便记录跳转指令(j、jal等)、分支指令(beq、bnq等)的跳转位置。
在下面这个程序中,其实仅仅执行了指令二,而没有执行指令一,因为j main
这一行代表直接跳转到main:
标签这一行,中间的部分被略过了。
j main
add $t0, $0, $0 # 指令一
main:
add $t1, $0, $0 # 指令二
标签方便我们构造条件语句和循环语句,思考一下该如何构造?
3.3.3 宏定义
我们可以借助宏定义完成一些函数,格式为:
.macro 函数名(参数)
.end_macro
如果我们想定义一个scanf
函数,可以写成这样:
.macro scanf(%x) #宏定义,定义一个scanf函数,传递参数为%x,%x是一个寄存器变量
li $v0, 5 #li指令为赋立即数指令,$v0 = 1
syscall #syscall,当$v0为5时,syscall执行结果为:读入一个整数,存入$v0中
move %x, $v0 #move指令,将$v0中的值移动到%x寄存器中
.end_macro
3.3.4 缩进、花括号?
so sad ! 不同于高级语言,这里没有缩进和花括号,程序只是一条一条的执行,条件语句、循环语句、函数跳转都将由标签和宏定义来完成。
3.4 编程实例
我们通过实例来感受一些汇编语言是如何编写的。
3.4.1 循环读入与输出
要求:读入一个整数n,再读入n个数,存入内存,再依次输出。
输入:
9
1 2 3 4 5 6 7 8 9
输出:
1 2 3 4 5 6 7 8 9
cpp代码
int matrix[9];
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &matrix[i]);
for(int i = 0; i < n; i++)
printf("%d ", matrix[i]);
}
汇编代码
.data #数据空间段
matrix: .space 36 # 申请一个长度为9的数组,一个数4字节,所以空间大小为9 * 4字节
enter: .asciiz "\n" #存储换行符号
space: .asciiz " " #存储空格符号
.text #代码段
.macro scanf(%x) #宏定义,定义一个scanf函数,传递参数为%x,%x是一个寄存器变量
li $v0, 5 #li指令为赋立即数指令,$v0 = 1
syscall #syscall,当$v0为5时,syscall执行结果为:读入一个整数,存入$v0中
move %x, $v0 #move指令,将$v0中的值移动到%x寄存器中
.end_macro
.macro printf(%x)
move $a0, %x #move指令,将%x中的值移动到$a0中
li $v0, 1
syscall #syscall,当$v0为1时,syscall执行结果为,输出$a0中的整数
.end_macro
.macro printfspace()
la $a0, enter
li $v0, 4
syscall #syscall,当$v0为1时,syscall执行结果为,输出$a0中的整数
.end_macro
main:
scanf($s0) #scanf("%d", &n);
la $s1, space # int *p = matrix
add $t0, $0, $0 # int i = 0;
loop_in:
beq $t0, $s0, end_loop_in # if(i == n) break;
scanf($t1) #scanf("%d", &x);
mul $t2, $t0, 4
sw $t1, matrix($t2)
add $t0, $t0, 1 # i = i + 1;
j loop_in
end_loop_in:
add $t0, $0, $0 # int i = 0;
loop_out:
beq $t0, $s0, end_loop_out # if(i == n) break;
mul $t2, $t0, 4
lw $t1, matrix($t2)
printf($t1)
printfspace()
add $t0, $t0, 1
j loop_out
end_loop_out:
3.4.2 斐波那契数列
3.4.2.1循环版本
.data
fibs: .space 48
size: .word 12
space: .asciiz " "
head: .asciiz "The Fibonacci numbers are: \n"
.text
la $t0, fibs
la $t5, size
lw $t5, 0($t5)
li $t2, 1
sw $t2, 0($t0)
sw $t2, 4($t0)
addi $t1, $t5, -2
loop:
lw $t3, 0($t0)
lw $t4, 4($t0)
add $t2, $t3, $t4
sw $t2, 8($t0)
addi $t0, $t0, 4
addi $t1, $t1, -1
bgtz $t1, loop
la $a0, fibs
add $a1, $zero, $t5
jal print
li $v0, 10
syscall
print:
add $t0, $zero, $a0
add $t1, $zero, $a1
la $a0, head
li $v0, 4
syscall
out:
lw $a0, 0($t0)
li $v0, 1
syscall
la $a0, space
li $v0, 4
syscall
addi $t0, $t0, 4
addi $t1, $t1, -1
bgtz $t1, out
jr $ra #return
3.4.2.2递归版本
.data
.text
.macro scanf(%x)
li $v0, 5
syscall
move %x, $v0
.end_macro
.macro printf(%x)
move $a0, %x
li $v0, 1
syscall
.end_macro
main:
scanf($s0)
li $t0, 0
li $t1, 1
jal fib
printf($t1)
li $v0, 10
syscall
fib:
bgt $s0, 1, next
jr $ra
next:
addi $sp, $sp, -4
sw $ra, 0($sp)
addi $s0, $s0, -1
jal fib
add $t2, $t0, $t1
add $t0, $0, $t1
add $t1, $0, $t2
return:
lw $ra, 0($sp)
addi $sp, $sp, 4
jr $ra