MIPS汇编语言学习

数据类型和方法:

  • 数据类型:字节,byte占用8bit,halfword占用2byte=16bit,word占用4byte=32bit

  • 一个字符需要一个Byte的空间

  • 一个整数需要1个Word(4Byte)的空间

  • MIPS结构的每条指令长度都是32bit

寄存器:

  • 有32个通用寄存器,用编号$0到$31来表示,也可以用寄存器名字来表示,例如$sp,$t1,$ra
  • 有两个特殊的寄存器Lo、Hi,用来保存乘法/除法的运算结果,这两寄存器不能直接寻址,只能用特殊的置零:mfhi和mflo来aceess其中的内容(mfhi = move from Hi, mflo = more from Lo)
  • 堆栈的增长方向是:从内存的高地址方向,向低地址方向。

1

汇编程序结构框架

数据声明:

以.data 开始,声明了在代码中使用的变量的名字。同时,也在主存RAM中创建了对应的空间。

程序代码:

以.text 开始,这部分包含了由指令构成的程序功能代码。代码以main: 函数开始。main的结束点应该调用exit system call

程序的注释:

使用#符号进行注释,每行以#引导的部分都会被视作注释

一个MIPS汇编程序框架:

1
2
3
4
5
6
7
8
9
10
11
# Comment giving name of program and description of function
# Template.s
# Bare-bones outline of MIPS assembly language program
.data # variable declarations follow this line
# ...

.text # instructions follow this line
main: # indicates start of code(first instruction to execute)
# ...

# End of program, leave a blank line afterwards to make SPIM happy

编写MIPS汇编程序:

Content:

  • Part1: 数据的声明

  • Part2: 数据的装载和保存(Load/Store指令)

  • Part3: 寻址

  • Part4: 算数运算指令: Arithmetic Instructions

  • Part5: 程序控制指令: Control Instructions

  • Part6: 系统调用和I/O操作(SPIM仿真)

Part1: 数据的声明

格式:

1
name:       storage_type    value(s)

创建一个以name为变量名称,values通常为初始值,storage_type代表存储类型。

注意:变量名后要跟一个冒号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# example

var1: .word 3 # create a single integer:
# variable with initial value 3

array1: .byte 'a','b' # create a 2-element character
# array with elements initialized:
# to a and b

array2: .space 40 # allocate 40 consecutive bytes,
# with storage uninitialized
# could be used as a 40-element
# character array, or a
# 10-element integer array;
# a comment should indicate it.

string1 .asciiz "Print this.\n" # declare a string

Part2: 数据的装载和保存(Load/Store指令)

  • 主存RAM的存取access只能用load/store指令来完成。
  • 所有其他的指令都使用的是存储器作为操作数。
  1. load指令:
1
2
3
4
5
6
7
8
9
10
11
lw          register_destination, RAM_source    
# copy word (4 bytes) at source_RAM location to destination register
# load word -> lw

lb register_destination, RAM_source
# copy byte at source RAM location to low-order byte of destination register, and sign -e.g. tend to higher-order bytes
# load byte -> lb

li register_destination, value
# load immediate value into destination register
# load immediate -> li
  1. store 指令:
1
2
3
4
5
sw          register_source, RAM_destination
# store word in source register into RAM destination

sb register_source, RAM_destination
# store byte (low-order) in source register into RAM destination

example:

1
2
3
4
5
6
7
8
9
10
11
12
.data
var1: .word 23 # declare storage for var1; initial value is 23

.text
__start:
lw $t0, var1 # load contents of RAM location into register $t0;
# $t0 = var1

li $t1, 5 # $t1 = 5 ("load immediate")
sw $t1, var1 # store contents of register $t1 into RAM:
# var1 = $t1 done
done

Part3: 寻址

MIPS系统结构只能用load/store相关指令来实现寻址操作,包括三种寻址方式:

  • 装载地址: load address,相当于直接寻址,把数据地址直接载入寄存器。
  • 间接寻址: indirect addressing,间接寻址,把寄存器内容作为地址。
  • 基线寻址/索引寻址: based or indexed addressing, 相对寻址, 利用补偿值(offset)寻址
  1. 直接寻址/装载地址:load address:
    1
    la      $t0, var1

把var1在主存RAM中的地址拷贝到寄存器t0中,var1也可以是程序中定义的一个子程序标签的地址。

  1. 间接寻址: indirect addressing:
    1
    lw      $t2, ($t0)

主存中有一个字的地址存在t0中,按这个地址找到那个字,把字拷贝到寄存器t2中。

1
sw      $t2, ($to)

把t2中的字存入t0中的地址指向的主存位置。

  1. 基线寻址/索引寻址: based or indexed addressing:
    1
    lw      $t2, 4($t0)

把t0中地址+4所得的地址所对应的主存中的字载入寄存器t2中,4为包含在t0中的地址的偏移量。

1
sw      $t2, -12($t0)   # offset can be negative

把t2中的内容存入t0中的地址-12所得的地址所对应的主存。

基线寻址在以下场合特别有用:
1、 数组: 从基址出发,通过使用偏移量,存取数组元素。
2、 堆栈: 利用从堆栈指针或者框架指针的偏移量来存取元素。

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.data
array1: .space 12 # declare 12 bytes of storage to hold array of 3 integers

.text
__start:
la $t0, array1 # load base address of array into register $t0
li $t1, 5 # $t1 = 5 ("load immediate")
sw $t1, ($t0) # first array element set to 5;
# indirect addressing
li $t1, 13 # $t1 = 13
sw $t1, 4($t0) # second array element set to 13

li $t1, -7 # $t1 = -7
sw $t1, 8($t0) # third array element set to -7

done

四位四位地走
存入一个字,占用4个字节,消耗4个内存号。且地址偏移量可正可负

Part5 算术运算指令: Arithmetic Instructions

  • 算术运算指令地所有操作数都是寄存器,不能直接使用RAM地址或间接寻址。
  • 操作数地大小都为Word(4-Byte)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
add     $t0,$t1,$t2         # $t0 = $t1 + $t2; add as signed
# (2's complement) integers
sub $t2,$t3,$t4 # $t2 = $t3 - $t4
addi $t2,$t3, 5 # $t2 = $t3 + 5; "add immediate"
# (no sub immediate)
addu $t1,$t6,$t7 # $t1 = $t6 + $t7;
addu $t1,$t6,5 # $t1 = $t6 + 5;
# add as unsigned intergers
subu $t1,$t6,$t7 # $t1 = $t6 - $t7;
subu $t1,$t6,5 # $t1 = $t6 - 5;
# subtract as unsigned intergers

mult $t3,$t4 # multiply 32-bit quantities in $t3 and $4,
# and store 64-bit result in special registers Lo and Hi: (Hi, Lo) = $t3 * $t4

div $t5,$t6 # Lo = $t5 / $t6 (integer quotient)
# Hi = $t5 mod $t6 (remainder)
mfhi $t0 # move quantity in special register Hi to $t0:
# $t0= Hi
mflo $t1 # move quantity in special register Lo to $t1:
# $t1 = Lo, used to get at result of product or quotient

more $t2,$t3 # $t2 = $t3

Part6: 程序控制指令: Control Instructions

  1. 分支指令(Branches)

条件分支的比较机制已经内建在指令中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
b           target # unconditional branch to program label target

beq $t0,$t1,target # branch to target if $t0 = $t1
blt $t0,$t1,target # branch to target if $t0 < $t1
ble $t0,$t1,target # branch to target if $t0 <= $t1
bgt $t0,$t1,target # branch to target if $t0 > $t1
bge $t0,$t1,target # branch to target if $t0 >= $t1
bne $t0,$t1,target # branch to target if $t0 <> $t1

beqz $t0, lab # branch to lab if $t0 = 0.
bnez $t0, lab # branch to lab if $t0 != 0.
bgez $t0, lab # branch to lab if $t0 >= 0.
bgtz $t0, lab # branch to lab if $t0 > 0.
blez $t0, lab # branch to lab if $t0 <= 0.
bltz $t0, lab # branch to lab if $t0 < 0.

bgezal $t0, lab # if $t0 >= 0, then put the address of the next instruction into $ra and branch to lab
bgtzal $t0, lab # if $t0 > 0, then put the address of the next instruction into $ra and branch to lab
bltzal $t0, lab # if $t0 < 0, then put the address of the next instruction into $ra and branch to lab
  1. 跳转指令(Jumps)

    1
    2
    j       target  # unconditional jump to program label target
    jr $t3 # jump to address contained in $t3 ("jump register")
  2. 子程序调用指令

子程序调用指令的实质是跳转并链接(Jump and Link),它把当前程序计数器的值保留到$ra中,以备跳回)

跳到子程序:

1
jal     sub_label       # "jump and link", preserve pc to $ra

sub_label为子程序的标签,如LOOP,SUB_ROUTINE

从子程序返回:

1
jr      $ra     # "jump register" jump as the value of $ra

返回到$ra中储存的返回地址对应的位置,$ra中的返回地址由jal指令保存

注意,返回地址存放在$ra寄存器中,如果子程序调用了下一级程序,或者递归调用,此时需要将返回地址保存在堆栈中,因为没执行依次jal指令就会覆盖$ra中的返回地址。

Part6:系统调用和I/O操作(SPIM仿真)

系统调用是指调用操作系统的特定子程序

系统调用用来在仿真器的窗口中打印或者读入字符串string,并可显示程序是否结束

syscall指令进行对系统子程序的调用

本操作首先支持$v0 and $a0-$a1中的相对值

调用以后的返回值(如果存在)会保存在$v0中。

1

e.g.

1
2
3
4
# 打印在$t2中的整数
li $v0, 1
move $a0, $t2
syscall

1
2
3
4
5
# read integer value, store in RAM location with label int_value (presumably declared in data section)
li $v0, 5
syscall

sw $v0, int_value
1
2
3
4
5
6
7
8
9
# print out string
.data
string1: .asciiz "print this.\n"

.text
main:
li $v0, 4
la $a0, string1
syscall
1
2
3
# to indicate end of program, use exit system call
li $v0, 10
syscall

阶乘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.data     
msg: .asciiz"Enter a Number "
msg1: .asciiz"The result is: "

.text
main:
li $v0,4
la $a0,msg
syscall

li $v0,5
syscall

move $s0,$v0
li $s7, 1

factorial:
mult $s0,$s7
mflo $s7
sub $s0,$s0,1
bgt $s0,1,factorial

done:
li $v0,4
la $a0,msg1
syscall

move $a0,$s7
li $v0,1
syscall

li $v0, 10
syscall

冒泡排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.data
Array: .word 21,4,12,6,89,17,33,10,9,51
seperate:.asciiz " "

# for i(0~n-1)
# for j(0~n-i)

.text
main:
la $t6,Array # t6 = a[0]
move $t7,$zero # t7 = 0
li $t8,10 # t8 = 10 (size)
move $t9,$zero # t9 = 0

loop1:
move $t9,$zero # t9 = 0

loop2:
move $t0,$t9 # t0 = t9
mul $t0,$t0,4 # t0 = 4*t9
addu $t1,$t0,$t6
lw $t2,0($t1) # t2 = a[t9]

addi $t0,$t9,1 # t0 = t9 + 1
mul $t0,$t0,4
addu $t4,$t0,$t6
lw $t3,0($t4) # t3 = a[t9+1]

bge $t2,$t3,skip # if(t2 >= t3) goto skip
sw $t3,0($t1) # if(t2 < t3) swap
sw $t2,0($t4)

skip:
addi $t9,$t9,1 # t9++
addi $t0,$t9,1 # t0 = t9 + 1
sub $t1,$t8,$t7 # t1 = t8 - t7
blt $t0,$t1,loop2
addi $t7,$t7,1
sub $t2,$t8,1
blt $t7,$t2,loop1

set:
move $t7,$zero

done:
move $t0,$t7
mul $t0,$t0,4
addu $t1,$t0,$t6
lw $a0,0($t1)
li $v0,1
syscall

la $a0,seperate
li $v0,4
syscall

addi $t7,$t7,1
blt $t7,10,done

函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
.text
.globl main
main:
addi $a1, $0, 2
addi $a2, $0, 3

jal print_two_number
jal addfunction
j result

addfunction:
add $v1, $a1, $a2
jr $ra

print_two_number:
move $a0, $a1
li $v0, 1
syscall
la $a0, change
li $v0, 4
syscall

move $a0, $a2
li $v0, 1
syscall
la $a0, change
li $v0 4
syscall

jr $ra

result:
move $a0, $v1
li $v0, 1
syscall

la $a0, change
li $v0, 4
syscall

li $v0, 10
syscall


.data
change:
.asciiz "\n"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
###########################################################################
#
# #include<iostream>
# using namespace std;
# int add(int a, int b) {
# int sum;
# sum = a + b;
# return sum;
# }
#
# int main() {
# int a = 2;
# int b = 3;
# int sum = add(a,b)
# cout << sum << endl;
# return 0;
# }
#
###########################################################################
.text
.globl main
main:
addi $a1,$0,2 # $a1: the register to save 2, parameters are stored in registers $a0 - $a3
addi $a2,$0,3 # $a2: the register to save 3

jal print_two_number # print two immediates
jal addfunction # jump to a function addfunction
j result # print result

addfunction: # add function
add $v1,$a1,$a2 # Calculate the sum and save it into $v1, the registers $v0 and $v1 save the return value
jr $ra # The return address is saved in $ra

print_two_number:
move $a0,$a1 # move the value into $a0
li $v0, 1 # load appropriate system call code into register $v0
syscall
la $a0, change # load address of string "change"
li $v0, 4 # load appropriate system call code into register $v0
syscall

move $a0,$a2
li $v0, 1 # load appropriate system call code into register $v0
syscall
la $a0, change # load address of string "change"
li $v0, 4 # load appropriate system call code into register $v0
syscall

jr $ra

result:
move $a0,$v1
li $v0, 1 # load appropriate system call code into register $v0
syscall

la $a0, change # load address of string "change"
li $v0, 4 # load appropriate system call code into register $v0
syscall

li $v0, 10 # exit
syscall

.data # statement of data
change: # name of string
.asciiz "\n" # definition of string
Donate? comment?