【计算机组成原理】汇编语言[mips-c指令]

时间:2024-11-10 14:22:13

【计算机组成原理】汇编语言[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。
  这条指令的编码规则如下:

  1. 最高6位称为OpCode,OpCode = 0表示这是一条R型指令
  2. 最低6位是R型指令的Function,表示这条指令执行什么运算,Function = 10000 0 2 = 3 2 10 100000_{2} = 32_{10} 1000002=3210表示这条指令是执行加法运算的指令。
  3. 中间的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位
  1. R类指令OpCode为0。
  2. Function区别不同的运算
  3. rs、rt、rd为三个寄存器,通常用rd表示结果寄存器
  4. shamat用于可变左(右)移时表示左(右)移的位数,由于操作的是32位数,因此只需5位二进制数即可

2.1.2 I类指令

I类指令有addi、subi、lui、lw、sw、beq等。一般表示为如下形式:

OpCode rs rt immediate
6位 5位 5位 16位
  1. I类指令以OpCode区别不同的指令类型,例如addi的OpCode是 00100 0 2 001000_{2} 0010002,lui的OpCode是 00111 1 2 001111_{2} 0011112
  2. rs、rt为运算所需的两个寄存器,通常用rt表示结果寄存器
  3. 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位
  1. J类指令以OpCode区别不同的指令类型。
  2. address表示跳转的指令的地址

jr、jalr指令表示为如下形式:

OpCode rs 0
6位 5位 26位
  1. JOpCode区别不同的指令类型。
  2. 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} 3128,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