伪指令与基本指令
伪指令就是并不是硬件实现的直接指令,只是给解释器看的,比如li
就是说向li这种指令,其实并不直接对硬件进行操作
实际上他就是一个addi指令
加法、减法
li t0, 20 # li: load immediate 加载立即数,现在这种写法是10进制
li t1, 22
add t2, t0, t1
li t0, 32
addi t0, t0, 32 # addi: add immediate
addi t0, t0, -16 # 没有subi指令,所以只能这样,但是sub还是有的
# f = (g + h) - (i + j)
# t6 = (t0 + t1) - (t3 + t4)
li t0, 0
li t1, 10
add t2, t0, t1
li t3, 30
li t4, 40
add t5, t3, t4
sub t6, t2, t5
系统调用
一共4步
# Step 1. Load the service number in register a7.
# Step 2. Load argument values, if any, in a0, a1, a2, a3, fa0, ... as specified.
# Step 3. Issue the ECALL instruction.
# Step 4. Retrieve return values, if any, from result registers as specified.
li t0, 16
li t1, 32
add t2, t0, t1
li a7, 1 # a7存放系统调用号,此处为输出
mv a0, t2 # argument1,伪指令,实际上是add a0, zero, t2
ecall
mv其实是cp并不是会把原来的地方的数据就清空了的
.data .text
.text就是代码指令开始的地方
带.的就是写给汇编器看的,比如.data编译器就会放到.data的块的地方
.text就会放到存放代码的地方(如0x0040 0000)
.word = 4byte
.data
g: .word 16
h: .word 48
i: .word 0
j: .word 0
result: .word 0
msg: .string "Hello world!:" # .string会在末尾加'\0'而.ascii不会
.text
la t0, g # load address to t0
lw t0, 0(t0) # load word t0中的地址+偏移量0
la t1, h # load address to t0
lw t1, 0(t1) # load word t0中的地址+偏移量0
add t5, t0, t1
la t0, result
sw t5, 0(t0) # t5的结果保存到t0的地址中
# la t1, msg
# li a7, 4
# mv a0, t1
# ecall
li a7, 4 # 打印字符串
la a0, msg # 其实可以直接la到a0里面就行,不需要再经过寄存器中转了,本身就是地址
ecall
li a7, 1 # 打印数字
mv a0, t5
ecall
数组
.data
nums: .word -30, -40, -50, -60, 60, 100
.text
la t0, nums
lw t1, 8(t0)
addi t1, t1, 50
sw t1, 8(t0) # 把-50变成了0
branch-max.asm
# 实现c = max(a,b)
.data
a: .word 100
b: .word 200
c: .word 0
.text
# la t0, a
# lw t0, 0(t0)
lw t0, a # 该RARS支持的一个语法糖,并不是RISC-V的语法,考试应该不能用
lw t1, b
bge t0, t1, greater_equal # t0 >= t1 就跳转到greater_equal
mv t2, t1
j end # 伪指令, 防止再执行到greater_equal
greater_equal:
mv t2, t0
end:
la t3, c
sw t2, 0(t3) # 存到c的地方,同样也有简便写法 sw t2, c, t3
array-index-bit.asm求一个数组正数的和与负数的和
.data
numbers: .word -30, 30, -20, 20, -10, 10, 0
size: .word 7
positive_sum: .word 0
negative_sum: .word 0
.text
la t0, numbers # t0: the address of the current array
lw t1, size
mv t2, zero # counter, initially 0
li t3, 0 # t3: sum of positive numbers <- 0
li t4, 0 # t4: sum of negative numbers <- 0
loop:
bge t2, t1, end_loop #计数器大于等于size就跳转到end_loop
# number[t2]
# mul t5, t2, 4
slli t5, t2, 2 # 逻辑左移,即t5*4
add t5, t0, t5
lw t5, 0(t5)
addi t2, t2, 1
bltz t5, negative # bltz: branch if less than zero
add t3, t3, t5
j loop
negative:
add t4, t4, t5
j loop
end_loop:
sw, t3, positive_sum, t5
sw, t3, positive_sum, t5
proc-max.asm函数调用
.data
max_result: .word 0
.text
.global main
max:
# a0 argument 0
# a1 argument 1
# 同时a0-a7的前两个寄存器也可以用来返回值的保存
blt a0, a1, smaller
# 没跳转说明a0 >= a1
# mv a0, a0 正好省略不用写
j end_max
smaller:
mv a0, a1
end_max:
ret # 等同
# jr ra # 等同
# jalr zero 0(ra) # jalr: jump and link register
# zero的作用:jalr会将下一条指令的地址存入
### main ###
.data
a: .word 100
b: .word 200
.text
main:
lw a0, a # 并非risc-v的指令,而是汇编器的伪指令,将a的地址存入a0
lw a1, b
# jal ra, max # jal: jump and link ra: Return Address register
# ra保存了返回地址,jal会将下一条指令的地址存入ra
# jal max
call max # 三种写法都一样
sw a0, max_result, t0
调用函数
参数多了可以考虑用栈来传递
jalr zero 0(ra) # jalr: jump and link register
# zero的作用:jalr会将下一条指令的地址存入
zero的作用:
jalr会做两件事情
-
一是按照后面给的0(ra)进行跳转
-
二是会把目前所在位置的下一条指令的地址存入给出的寄存器中,如果把zero换成t0就会保存到t0,写zero的话就表示不想存这个下一条地址(用不到),因为写到zero的东西都会被清零
proc-fact.asm递归
.text
.global main
factorial:
beqz a0, base_case # 为0跳转,f(0) = 1
# 先改变栈指针,再保存寄存器的值,先保存参数寄存器的值,再保存ra的值
addi sp, sp, -8 # 栈空间高地址往低地址增长,所以先减8
sw a0, 4(sp) # 先保存a0的值,即n
sw ra, 0(sp) # 还需要再保存ra的值
# n > 0: n * f(n-1)
addi a0, a0, -1 # 此时a0为n-1
call factorial # a0为f(n-1)
mv t0, a0 # t0:f(n-1)
lw ra, 0(sp) # 恢复ra的值
lw a0, 4(sp) # 恢复a0的值
addi sp, sp, 8 # 恢复栈指针
mul a0, a0, t0 # a0 = n * f(n-1)
j end # 返回, a0还要用来保存返回值
base_case:
li a0, 1
end:
ret
### main ###
.data
n: .word 5
.text
main:
lw a0, n
call factorial
注意ra的值会变