文章目录

  • 前言
    • 反汇编
  • .data
    • frequent use syscall arguments
    • type
      • char
      • string
      • int
      • float
      • double
  • .text
    • calculate
      • add
      • sub
      • mul
      • div
    • input
    • function
      • basic function
      • add arguments and return values
      • stack(the key of mips function)
    • branch
      • basic instructions
      • first standard programme
      • **slt:set if less than**
    • loop
    • array
      • 最基本使用
      • 数组的初始化(so easy)
      • 2D Array
    • float/double comparison
  • basic_algorithm
    • recursion
      • factorial(阶乘)
      • other more
  • bit manipulation

前言

本文为博主在学习mips时的一些基本笔记(苦于计组实验需要用到mips,而全网对mips小白的基础教程是在太少,有也不全面),因为是全英文课程,所以索性笔记也用了英文来记(当然都是很通俗易懂初中英语水平就差不多能看懂了嗷),是油管博主Amell录制的mips基础视频(可以说是全网最最基础的mips教程了,各种变量、代码结构、循环、分支、数组、栈使用、递归都有涉及)

注意:本文知识点很全,但是比较适合已经理解mips基本常识的童鞋学习(因为是学习笔记嘛,博主也不可能事无巨细的把所有东西都记录一遍,纯小白可以直接看下面的mips学习视频,把我的笔记当做对照来用就行)

YouTube原视频链接(全英文字幕)

没有vpn的童鞋可以看下面这个,不过哔哩哔哩上少了最后一课时(二维数组的应用)

bilibili搬运链接(附带机翻字幕)


关于算法实现部分,之后会更新一些相关实例
让我康康有多少Buaaer👀

反汇编

这里提供一个反汇编的网站,可以把你的高级语言程序直接反汇编为mips,对理解mips中的栈思想非常有帮助,
也可以更好的理解变量等等在mips中是怎么传递的,理解$s0-$s7为什么被定义为需要恢复为原值的寄存器
反汇编

.data

存放在内存中的数据类型声明

frequent use syscall arguments

Code in $v0Servicenotes(注意点)
1print integer in $a0-
2print float in $f12-
3print double in $f12-
4print string(across address) in $a0-
5read integer into $v0-
6read float into $f0-
7read double into $f0(exactly saved in $f0 and in $f1)-
8$a0 = address of input buffer,$a1 = maximum number of characters to readif $a0==n,so the max number input is n-1,because the final character is ‘\0’,and if n==1,input is ignored and null byte placed at buffer address. If n<1, input is ignored and nothing is written to the buffer. (what’s more,’\n’ will be read)
10exit the whole programm-

type

char

.data # 数据声明区域
    # name: .type values
    char: .byte 'M' # 将一个字符保存在一个字节的存储空间里
    endline: .byte '\n' # 常用,换行符
    # 记得字符要使用单引号声明
.text # 代码区域
main:
    li $v0 4 # 表示输出$a0中char类型的内容
    la $a0 char # 将char常量的地址保存在$a0中
    syscall

string

.data
    string1: .ascii "12345" # 将字符串存储在存储空间里,根据字符串长度自动分配空间
    string2: .asciiz "12345" # 与ascii的区别是会自动在末尾添加'\0',避免了字符串的连接
.text
main:
    li $v0 4 # byte和ascii都是字符类型,参数为4
    la $a0 string2 # 将string2的地址传入
    syscall

int

.data
    age: .word 23 # 将一个整数保存在一个4字节的存储空间里
.text
main:
    li $v0 1
    lw $a0 age # 可能是因为int的原因,只能传入int的值,无法传入地址
    syscall

float

关于float的保存位置(下图为Mars中的Coproc1)

.data
    PI: .float 3.1415926 # 同样将浮点数保存在存储空间中
.text
main:
    li $v0 2 # float的syscall参数
    # 就像计算机组成原理说的那样,对于浮点型的数据并不是保存在通用寄存器里,所以不能使用lw指令导入
    # 浮点数保存在Coproc1中,register的右侧,所以需要使用指令lwcl
    lwc1 $f12 PI # 以$f12作为输出寄存器
    syscall

double

.data
    mydouble: .double 7.202
    zerodouble: .double 1.303
    # 随便在内存中分配的两个double数据,注意这里分别分配的是64位内存
.text
main:
    ldc1 $f2 mydouble # 因为是64位存储,所以需要占用$f2,$f3两个寄存器
    ldc1 $f0 zerodouble # load double into Coproc1
    # 注意double的存储有特殊的指令:ldc1
    li $v0 3 # attention
    add.d $f12 $f0 $f2 # 将$f0,$f2中的两个数据相加存入$f12,与add指令类似

.text

calculate

add

add $a0 $t1 $t2 # 整数加法
add.d # double加法
add.s # float加法
addi # 立即数加法
addu # 无符号数加法
addiu # 无符号立即数加法

sub

减法同上(与加法几乎相同)

mul

.data
data1: .word 12
data2: .word 34
.text
init:
    lw $t0 data1
    lw $t1 data2
mul:
    # 总共有三种方式来实现乘法操作1.mul 2.mult 3.sll
    mul $a0 $t0 $t1 # $a0=$t0*$t1
    # 这种方法有一个问题就是最大的结果分为依旧是32位
mult:
    li $v0 1
    mult $t0 $t1 # 将两个寄存器中的内容相乘并且存放在32位乘寄存器Hi、Lo中,这样就可以吧乘法范围拓展到64位
    # 注意是高32位存储在Hi,低32位存储在Lo
    mflo $a0 # 将低位寄存器中的值转移入$a0寄存器中
    syscall
    # mfhi $a1 同理,只不过是高位寄存器
    # 虽然可以做更大数的乘法,但是貌似还不知道如何输出这个更大数的值
    # 猜测1:存储在浮点数寄存器里???
sll:
    # 就是逻辑左移乘2

div

.data
    data1: .word 4
    data2: .word 10
.text
main:
    li $v0 1
    lw $t0 data1
    lw $t1 data2
    div $a0 $t1 $t0
    div $t1 $t0 # Hi寄存器存储余数,Lo寄存器存储商
    # 同理 利用mflo和mflh来使用Hi和Lo寄存器
    syscall

input

int、float、double都肥肠简单嗷,感觉记一下syscall的参数就行了

稍微讲解一下字符串的读取即可

.data
	message: .asciiz "Hello,"
	userInput: .space 20 # variable:allow the user to input 20 characters
.text
main:
	li $v0 8
	la $a0 userInput
	li $a1 20 # input max 19 characters
	syscall
	
	li $v0 4
	la $a0 message
	syscall
	
	li $v0 4
	la $a0 userInput
	syscall

	# the end of the programm
	li $v0 10
	syscall
	

function

basic function

we could see these functions as modules or components of a programme
which is reusable,just like in the C
but in MIPS we call function as procedure

# function 1.0

.data
message: asciiz "Hi,everybody,i'm Peter\n"
.text
main:
    jal display_message
    nop
    # Tell the system that the porgramme is done
    li $v0 10
    syscall
display_message:
    li $v0 4
    la $a0 message
    syscall
    jr $ra
# 这是一个最最基础的mips程序员所写的mips程序,完全延续的是c语言的思想

add arguments and return values

.data
.text
main:
	# $v0 and $v1 are set to store return values
	# $a0-$a7 are set to transmit the arguments
    li $v0 1
    li $a1 50
    li $a2 100
    jal addnumbers
    nop
    move $a0 $v1
    syscall
    li $v0 10
    syscall
addnumbers:
    add $v1 $a1 $a2
    jr $ra    

stack(the key of mips function)

about the useage of all kinds of registers

just take an example of the following code segment

# 1.0 只是简单地使用了栈的知识
.data
	newline: .byte '\n'
.text
main:
	li $s0 10
	# call function
	jal increase_print_MyRe
	nop
	li $v0 10
	syscall
	
increase_print_MyRe:
	# apply stack
	addiu $sp $sp -8 # addiu 和 addi的唯一区别就是是否检测溢出
	sw $fp 4($sp)
	move $fp $sp
	sw $s0 0($fp)
	
	addi $s0 $s0 20
	move $a0 $s0
	li $v0 1
	syscall
	
	lw $s0 0($fp)
	move $sp $fp
	lw $fp 4($sp)
	addiu $sp $sp 8
	
	jr $ra

nested procedure(嵌套函数)

## 两步操作,第一步将$s0中保存的内容+=number,第二步输出$s0中保存的内容
.data
number: .word 39
new_line: .asciiz "\n" # you can alse use byte to save the '\n' character
.text
# saving registers to the stack
main:
	# print the init value in $s0
	li $v0 1
	li $s0 10
	move $a0 $s0
	syscall
	
	jal add_print
	nop
	move $a0 $s0
	syscall
	
	# this is going to signal the end of programme
	li $v0 10
	syscall
add_print:
	# 个人认为变量的保存可以全部在子函数里面完成
	# init
	addiu $sp $sp -12 # apply 12 bytes
	sw $fp 4($sp) # save $fp in $sp+4
	move $fp $sp # 将$sp地址赋值给$fp
	sw $ra 8($fp) # save the $ra
	sw $s0 0($fp) # $s0-$s7是子函数调用时需要保存和恢复的寄存器变量
	# $a0-$a3是函数调用参数
	# $t0-$t7是临时变量,任何子函数在任何时候都可以使用,同样也不太安全
	# $v0-$v1用于保存子函数调用的返回结果
	
	lw $t0 number
	add $s0 $s0 $t0
	jal print
	nop
	
	lw $s0 0($fp)
	lw $ra 8($fp)
	move $sp $fp
	lw $fp 4($sp)
	addiu $sp $sp 12
	
	jr $ra
print:
	addiu $sp $sp -12
	sw $fp 4($sp)
	move $fp $sp
	sw $s0 8($fp)
	sw $ra 0($fp)
	
	li $v0 1
	move $a0 $s0
	syscall
	
	lw $ra 0($fp)
	lw $s0 8($fp)
	move $sp $fp
	lw $fp 4($sp)
	addiu $sp $sp 12
	
	jr $ra
	

branch

condition in the mips

basic instructions

Branch TypeUseageFunction
bb labelthe same as j label
beqbeq r1 r2 labelbranch if r1==r2
bnesame as beqbranch if r1!=r2
beqzbeqz r1 labelbranch if r1==0
bgesame as beqbranch if r1>=r2
bgtsame as beqbranch if r1>r2
blesame as beqbranch if r1<=r2
bltsame as beqbranch if r1<r2
slt/(i)slt r1 r2 r3/immediateset r1=1 if r2<r3 else set r1=0
sle/(i)same as sltset r1=1 if r2<=r3 else set r1=0

first standard programme

.data
	message: .asciiz"the numbers are equal"
	message2: .asciiz"Nothing happened"
.text
main:
	li $t0 5
	li $t1 5
	
	beq $t0 $t1 numbersEqual
	li $v0 4
	la $a0 message2
	syscall
	
	j end
numbersEqual:
	li $v0 4
	la $a0 message
	syscall
	j end
end: # 防止重复调用,索性最后面来一个end函数
	li $v0 10
	syscall

slt:set if less than

简单介绍一下slt,set if less than,如果寄存器2小于寄存器3,就将寄存器1的值设置为1,否则设置为0,

该指令让我们能够判断两个寄存器值的大小(不需要通过输出)

loop

so easy

格局有时候不要太小,衡多时候while和exit完全不需要使用跳转指令调用,直接写在main主函数内部顺序执行即可

int i=0;
while(i<10)
    i++;

接下来仿照上面的c程序写一段等效的mips代码

.data
	
.text
main:
	li $v0 1
	jal while
	nop
	li $v0 10
	syscall
# mips中使用一个循环时,需要用到两个label,一个循环label和一个退出label
while:
	bgt $t0 10 exit
	move $a0 $t0
	syscall
	addiu $t0 $t0 1
exit:
	jr $ra

array

最基本使用

.data
	myArray: .space 12 # 1 integer 4 bytes,1 character 1 byte
.text
main:
	li $s0 4
	li $s1 10
	li $s2 12
	
	# index = $t0
	li $t0 0
	sw $s0 myArray($t0)
	addiu $t0 $t0 4
	sw $s1 myArray($t0)
	addiu $t0 $t0 4
	sw $s2 myArray($t0)
	# output values in array reversed
	li $v0 1
	while:
		blez $t0 exit
		lw $a0 myArray($t0)
		addiu $t0 $t0 -4
        syscall
        j while
	exit:
		li $v0 10
		syscall

数组的初始化(so easy)

.data
	myArray1: .word 100:3 # 初始化了长度为3的数组,且数组中每一个元素都是100
	myArray2: .word 1,2,3,4,5,6 # 当然也可以全部初始化
.text
main
	......

2D Array

C a l c u l a t e   f o r m u l a : A d d r = B a s e A d d r + ( R o w I n d e x ∗ C o l S i z e + C o l I n d e x ) ∗ D a t a S i z e Calculate\ formula:Addr=BaseAddr+(RowIndex*ColSize+ColIndex)*DataSize Calculate formula:Addr=BaseAddr+(RowIndexColSize+ColIndex)DataSize

In mips,the label is also a BaseAddr.

实例:二维数组求和

.data
	mdArray:.word 2,5 # first row
			.word 3,7 # second row
	size:.word 2 # 行列长度定义,这里因为一样就只定义了2
	# define a constant常量定义
	.eqv DataSize 4 
.text
# 地址计算公式Addr=BaseAddr+(RowIndex*RowSize+ColIndex)*DataSize
main:
	la $a0 mdArray # BaseAddr
	lw $a1 size # range
	jal sumDiagonal # return the sum of mdArray
	nop
	
	move $a0 $v0
	li $v0 1
	syscall
	
	li $v0 10
	syscall
sumDiagonal:
	li $v0 0 # return value
	li $t0 0 # use $t0 as the RowIndex
	li $t1 0 # use $t1 as the ColIndex
	li $t2 0 # current addr
	# init
	mul $t2 $t0 $a1 # RowIndex*RowSize
	mul $t2 $t2 DataSize # (RowIndex*RowSize+0)*DataSize
	addu $t2 $t2 $a0 # Addr_init=BaseAddr+(RowIndex*RowSize+0)*DataSize
	
	# 其实可以直接一个总循环解决,但是感觉那样的话就不像二维数组的思路了
	rowloop:
		li $t1 0 # reset the ColIndex
		colloop:
			lw $t3 0($t2) # 取值
			add $v0 $v0 $t3
			
			addiu $t1 $t1 1
			addiu $t2 $t2 DataSize
			blt $t1 $a1 colloop
		addiu $t0 $t0 1
		blt $t0 $a1 rowloop
	
	jr $ra

float/double comparison

浮点数的比较

instructionusagefunction
c.eq.sc.eq.s (i) $f0 $f2if $f0==$f2 set Coprocessor 1 condition flag 0(specied to immediate) True else set it false
bc1tbc1t (i) labelif Coprocessor 1 condition flag 0 True than jump to label

condition flags

.data
	message1: .asciiz "it was true\n"
	message2: .asciiz "it was false\n"
	number1: .float 3.14
	number2: .float 2.712
.text
main:
	lwc1 $f0 number1
	lwc1 $f2 number2
	
	c.eq.s $f0 $f2  # 浮点数的比较方式
	# c.eq.s if $f0==$f2 set Coprocessor 1 condition flag 0 True else set it false
	
	# bc1t
	# bc1f

basic_algorithm

introduction to some basic algorithm(like recursion)

recursion

factorial(阶乘)

let’s take an easiest example,try to achieve factorial n ! n! n!

f a c t o r i a l ( n ) = { 1 if n=0 n ∗ f a c t o r i a l ( n ) if n>=1 factorial(n)=\begin{cases} 1&\text{if n=0}\\n*factorial(n) &\text{if n>=1}\end{cases} factorial(n)={1nfactorial(n)if n=0if n>=1

下面的这个实现严格来讲只能算迭代,不算递归

.data
	string1: .asciiz "Please input a number:"
	string2: .asciiz "\nThe result of factorial(n) is:"
	n: .word 6
.text
main:
	# use $a0 to transmit the arguments
	# lw $a0 n
	
	li $v0 4
	la $a0 string1
	syscall
	
	li $v0 5
	syscall
	move $a0 $v0
	
	li $v1 1 # init
	jal factorial
	nop
	# output the result of recursion
	li $v0 4
	la $a0 string2
	syscall
	
	move $a0 $v1
	li $v0 1
	syscall
	
	j end
factorial:
	# use $v0 to return the result
	addiu $sp $sp -16
	sw $fp 8($sp)
	move $fp $sp
	sw $ra 12($fp)
	
	ble $a0 1 L2 
	L1:
		mul $v1 $v1 $a0
		addiu $a0 $a0 -1
		bne $a0 1 L1
	L2:
		nop
	
	lw $ra 12($fp)
	move $sp $fp
	lw $fp 8($sp)
	addiu $sp $sp 16
	jr $ra
end:
	li $v0 10
	syscall

递归实现

factorial:
	addiu $sp $sp -12
	sw $fp 4($sp)
	move $fp $sp
	sw $ra 8($fp)
	sw $s0 0($fp)
	
	# Base case
	beq $a0 0 factorialDone
	
	# recursion
	move $s0 $a0
	sub $a0 $a0 1 # once the $a0 changes,call the recursion
	jal factorial
	nop
	
	# magic part
	mul $v0 $v0 $s0
	
	factorialDone:	
		lw $s0 0($fp)
		lw $ra 8($fp)
		move $sp $fp
		lw $fp 4($sp)
		addiu $sp $sp 12
		
		jr $ra

other more

bit manipulation

instructionusagefunction
or (i)or $t0 $t1 $t2或运算
and (i)the same as or与运算
northe same as or或非运算
xor (i)the same as or异或运算
luilui $t0 i高16位置为立即数,低16位置0
sllsll $t0 $t1 i左移
srlthe same as sll逻辑右移
srasra $t0 $t1 i算数右移,右补符号位
sllvsllv $t0 $t1 $t2只不过数存在了寄存器里(sll/srl/sra均可扩展)

非常简单,学过位运算的都会算

更多推荐

近万字MIPS小白进阶教程!(包含变量使用、代码结构、循环、分支、数组、栈使用、递归位运算等等知识)