RISC-V学习笔记
Posted 夏风喃喃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RISC-V学习笔记相关的知识,希望对你有一定的参考价值。
RISC-V学习笔记(2)
作者:夏风喃喃
参考:计算机组成与设计:硬件/软件接口(RISC-V版)
文章目录
第2章 计算机的语言
2.1 引言
名称 | 寄存器号 | 用途 | 调用时是否保存 |
---|---|---|---|
x0 | 0 | 常数0 | 不适用 |
x1(ra) | 1 | 返回赋值(链接寄存器) | 是 |
x2(sp) | 2 | 栈指针 | 是 |
x3(gp) | 3 | 全局指针 | 是 |
x4(tp) | 4 | 线程指针 | 是 |
x5~x7 | 5~7 | 临时 | 否 |
x8~x9 | 8~9 | 保存 | 是 |
x10~x17 | 10~17 | 参数/结果 | 否 |
x18~x27 | 18~27 | 保存 | 是 |
x28~x31 | 28~31 | 临时 | 否 |
2.2 计算机硬件的操作
RISC-V一个算术指令只执行一个操作,并且必须总是只有三个变量。
类似于加法的操作一般有三个操作数:两个被加到一起的数和一个放置总和的位置。
add a, b, c //The sum of b and c is placed in a
2.3 计算机硬件的操作数(加减指令)
RISC-V算术指令的三个操作数必须从32个64位寄存器(RV64)选择,RISC-V约定寄存器编号为x01-x31。
加法:
add x19, x20, x21 //The sum of x20 and x21 is placed in x19
2.3.1 存储器操作数(存取指令)
内存和寄存器之间传输数据的指令,称为数据传输指令。
取双字:(取内存中的A[8]至寄存器x9,字节寻址8×8偏移地址为64)
ld x9, 64(x22) //x22存放内存中数组的基址,偏移64,取出的数据A[8]存放至寄存器x9
存双字:(将寄存器x9中数据存至内存的A[12],字节寻址8×12偏移地址为96)
sd x9, 96(x22) //x22存放内存中数组的基址,偏移96,寄存器x9的数据存至A[12]
2.3.2 常数或立即数操作数(立即数指令)
RISC-V算术指令有一个常数操作数的快速加指令称为立即数加。
立即数加:
addi x22, x22 4 //x22 = x22 + 4
2.4 有符号数与无符号数
64位无符号数:
(
x
63
×
2
63
)
+
(
x
62
×
2
62
)
+
…
…
+
(
x
1
×
2
1
)
+
(
x
0
×
2
0
)
(x_{63}×2^{63})+(x_{62}×2^{62})+……+(x_{1}×2^{1})+(x_{0}×2^{0})
(x63×263)+(x62×262)+……+(x1×21)+(x0×20)
64位有符号数:
(
x
63
×
−
2
63
)
+
(
x
62
×
2
62
)
+
…
…
+
(
x
1
×
2
1
)
+
(
x
0
×
2
0
)
(x_{63}×-2^{63})+(x_{62}×2^{62})+……+(x_{1}×2^{1})+(x_{0}×2^{0})
(x63×−263)+(x62×262)+……+(x1×21)+(x0×20)
2.5 计算机中的指令表示(指令格式)
RISC-V的指令均为32位。
R(寄存器)型格式指令:(用于三个操作数均在寄存器)
funct7 | rs2 | rs1 | funct3 | rd | opcode |
---|---|---|---|---|---|
7位 | 5位 | 5位 | 3位 | 5位 | 7位 |
opcode、funct3、funct7:操作码,指令的基本操作。
rd:目的操作数寄存器,用来存放操作结果。
rs1:第一个源操作数寄存器。
rs2:第二个源操作数寄存器。
I(立即数)型格式指令:(用于立即数操作或从内存取数据)
immediate | rs1 | funct3 | rd | opcode |
---|---|---|---|---|
12位 | 5位 | 3位 | 5位 | 7位 |
opcode、funct3:操作码,指令的基本操作。
rd:目的操作数寄存器,用来存放操作结果。
rs1:源操作数寄存器或基址寄存器。
immediate:立即数源操作数或偏移量。
S(存放)型格式指令:(用于向内存存数据)
immediate[11:5] | rs2 | rs1 | funct3 | immediate[4:0] | opcode |
---|---|---|---|---|---|
7位 | 5位 | 5位 | 3位 | 5位 | 7位 |
opcode、funct3:操作码,指令的基本操作。
rs1:基址寄存器。
rs2:源操作数寄存器。
immediate:偏移量。
2.6 逻辑操作(移位,逻辑运算指令)
RISC-V的移位操作通常使用 I 型格式指令,因为64位寄存器数据移位不会大于63位,所以immediate的低6位被使用。
funct6 | immediate | rs1 | funct3 | rd | opcode |
---|---|---|---|---|---|
6位 | 6位 | 5位 | 3位 | 5位 | 7位 |
逻辑左移与右移:(移动的空位用0补充)
sll x11, x19, x10 //reg x11 = reg x19 << reg x10
slli x11, x19, 4 //reg x11 = reg x19 << 4 bits
srl x11, x19, x10 //reg x11 = reg x19 >> reg x10
srli x11, x19, 4 //reg x11 = reg x19 >> 4 bits
算术右移:(移动的空位用符号扩展补充)
sra x11, x19, x10 //reg x11 = reg x19 >> reg x10
srai x11, x19, 4 //reg x11 = reg x19 >> 4 bits
按位与,按位或:
and x9, x10, x11 //reg x9 = reg x10 & reg x11
andi x9, x10, 4 //reg x9 = reg x10 & 4
or x9, x10, x11 //reg x9 = reg x10 | reg x11
ori x9, x10, 4 //reg x9 = reg x10 | 4
按位异或,按位取反:(取反等价于异或111……111)
xor x9, x10, x12 //reg x9 = reg x10 ^ reg x12
xori x9, x10, 4 //reg x9 = reg x10 ^ 4
xor x9, x10, x11 //reg x9 = ~ reg x10,x11 = 111……111
2.7 用于决策的指令(条件判断分支指令)
分支跳转语句常用于 if 语句。
相等则分支:
beq rs1, rs2, L1 //if rs1 == rs2, branch to label L1
不相等则分支:
bne rs1, rs2, L1 //if rs1 ≠ rs2, branch to label L1
例:( if 条件语句C语言编译为RISC-V代码)
【C】
if (i == j) f = g + h; else f = g - h;
f,g,h,i,j分别对应x19-x23这5个寄存器
【RISC-V】
bne x22, x23, Else //go to Else if i ≠ j
add x19, x20, x21 //f = g + h (skipped if i ≠ j)
beq x0, x0, Exit //if 0 == 0, go to Exit
Else: sub x19, x20, x21 //f = g - h (skipped if i = j)
Exit:
2.7.1 循环(循环判断分支指令)
循环的RISC-V代码也是通过分支跳转实现。
例:( while 循环语句C语言编译为RISC-V代码)
【C】
while (save[i] == k)
i += 1;
i,k分别对应x22和x24寄存器,数组的基址保存在x25
【RISC-V】
Loop: slli x10, x22, 3 //Temp reg x10 = i * 8
add x10, x10, x25 //x10 = address of save[i]
ld x9, 0(x10) //Temp reg x9 = save[i]
bne x9, x24, Exit //go to Exit if save[i] ≠ k
addi x22, x22, 1 //i = i + 1
beq x0, x0, Loop //go to Loop
Exit:
2.7.2 边界检查的简便方法(其他条件判断分支指令)
小于则分支:
blt rs1, rs2, L1 //if rs1 < rs2, branch to label L1
大于等于则分支:
bge rs1, rs2, L1 //if rs1 ≥ rs2, branch to label L1
无符号小于则分支:
bltu rs1, rs2, L1 //if rs1 < rs2, branch to label L1
无符号大于等于则分支:
bgeu rs1, rs2, L1 //if rs1 ≥ rs2, branch to label L1
2.7.3 case/switch语句(条件判断跳转指令)
case条件语句可以使用分支地址表高效实现,使用跳转-链接指令(jalr)对寄存器中指定的地址执行无条件跳转。
间接跳转:
jalr x0, 0(x1) //unconditionally branch to case address
分支地址表:也称作分支表,一种包含了不同指令序列地址的表。
2.8 计算机硬件对过程的支持(函数操作指令)
过程即函数的实现,RISC-V软件为过程调用分配寄存器时遵循约定:x10-x17八个参数寄存器,用于传递参数或返回值,x1一个返回地址寄存器,用于返回到起始点。
跳转-链接:
jal x1, ProcedureAddress
//jump to ProcedureAddress and write return address to x1
无条件跳转:
jal x0, Label //unconditionally branch to Label, discard return address
2.8.1 使用更多的寄存器(函数操作寄存器换出指令)
当函数需要更多的参数时,需要更多的寄存器,将寄存器旧值换出至存储器以满足参数传递,函数调用结束后,需要恢复寄存器旧值,换出寄存器的数据结构是栈(stack),栈按照从高到低的地址顺序(高地址在上,低地址在下)增长,栈指针(stack pointer)是寄存器x2,也称为sp。
例:( 没有调用其他函数的C语言函数编译为RISC-V代码)
【C】
long long int leaf_example(long long int g, long long int h,
long long int i, long long int j)
{
long long int f;
f = (g + h) - (i + j);
return f;
}
g,h,i,j分别对应x10-x13寄存器,f对应于x20
【RISC-V】
leaf_example:
addi sp, sp, -24 //adjust stack to make room for 3 times
sd x5, 16(sp) //save reg x5 for use afterwards
sd x6, 8(sp) //save reg x6 for use afterwards
sd x20, 0(sp) //save reg x20 for use afterwards
add x5, x10, x11 //reg x5 contains g + h
add x6, x12, x13 //reg x6 contains i + j
sub x20, x5, x6 //f = x5 - x6, which is (g + h) - (i + j)
addi x10, x20, 0 //returns f (x10 = x20 + 0)
ld x20, 0(sp) //restore reg x20 for caller
ld x6, 8(sp) //restore reg x6 for caller
ld x5, 16(sp) //restore reg x5 for caller
addi sp, sp, 24 //adjust stack to delete 3 times
jalr x0, 0(x1) //branch back to calling routine
RISC-V软件将19个寄存器分为两组:x5-x7以及x28-x31是临时寄存器,在过程调用中不被被调用者保存;x8-x9以及x18-x27是保存寄存器,在过程调用中必须被保存。
2.8.2 嵌套过程(函数操作递归指令)
例:( 计算阶乘的C语言递归函数编译为RISC-V代码)
【C】
long long int fact (long long int n)
{
if (n < 1) return (1);
else return (n * fact (n - 1))
}
n对应x10参数寄存器
【RISC-V】
fact:
addi sp, sp, -16 //adjust stack to make room for 2 times
sd x1, 8(sp) //save the return address
sd x10, 0(sp) //save the argument n
addi x5, x10, -1 //x5 = n - 1
bge x5, x0, L1 //if (n - 1) >= 0, go to L1
addi x10, x0, 1 //return 1
addi sp, sp, 16 //pop 2 items off stack
jalr x0, 0(x1) //return to caller
L1:
addi x10, x10, -1 //n >= 1: argument gets (n - 1)
jal x1, fact //call fact with (n - 1)
addi x6, x10, 0 //return from jal: move result of fact
(n - 1) to x6
ld x10, 0(sp) //restore argument n
ld x1, 8(sp) //restore the return address
addi sp, sp, 16 //adjust stack pointer to pop 2 items
mul x10, x10, x6 //return n * fact (n - 1)
jalr x0, 0(x1) //return to the caller
为了简化C语言中的静态全局变量的访问,RISC-V编译器保留了一个寄存器x3用作全局指针或gp。
例:( 计算累加的C语言递归函数编译为RISC-V代码)
【C】
long long int sum (long long int n, long long int acc)
{
if (n > 0)
return sum (n - 1, acc + n);
else
return acc
}
n,acc对应x10,x11寄存器, 结果放入x12
【RISC-V】
sum:
ble x10, x0, sum_exit //go to sum_exit if n <= 0
add x11, x11, x10 //add n to acc
addi x10, x10, -1 //subtract 1 from n
jal x0, sum //jump to sum
sum_exit:
addi x12, x11, 0 //return value acc
jalr x0, 0(x1) //return to caller
2.9 人机交互(字符串操作指令)
例:( 字符串复制的C语言编译为RISC-V代码)
【C】
void strcpy (char x[], char y[])
{
size_t i;
i = 0;
while ((x[i] = y[i] != '\\0') /*copy & test byte*/ )
i += 1;
}
x,y基址对应x10,x11寄存器, i对应x19寄存器
【RISC-V】
strcpy:
addi sp, sp, -8 //adjust stack for 1 more item
sd x19, 0(sp) //save x19
add x19, x0, x0 //i = 0 + 0
L1:
add x5, x19, x11 //address of x[i] in x5
lbu x6, 0(x5) //x[6] = y[i]
add x7, x19, x10 //address of x[i] in x7
sb x6, 0(x7) //x[i] = y[i]
beq x6, x0, L2
addi x19, x19, 1 //i = i + 1
jal x0, L1 //go to L1
L2:
ld x19, 0(sp) //restore old x19
addi sp, sp, 8 //pop 1 doubleword off stack
jalr x0, 0(x1) //return
加载无符号字节:
lbu x12, 0(x10) //Read byte from source
加载无符号字:
lwu x12, 0(x10) //Read byte from source
2.10 对大立即数的RISC-V编址和寻址
2.10.1 大立即数(大立即数加载指令)
将64位常量加载到寄存器,需要使用lui
(load upper immediate)指令,用于将20位常数加载到寄存器的第31位到第12位。将31位的值复制填充到最左边32位,最右边的12位用0填充。lui
使用U型格式指令。
例:( 64位常量加载到寄存器x19编译为RISC-V代码)
00000000 00000000 00000000 00000000 00000000 00111101 00000101 00000000
【RISC-V】
lui x19, 976 //976(decimal) = 0000 0000 0011 1101 0000
addi x19, x19, 1280 //1280(decimal) = 00000101 00000000
2.10.2 分支中的寻址
RISC-V分支指令格式为SB型。
SB(分支)型格式指令:(如bne x10, x11, 2000
)
imm[12] | imm[10:5] | rs2 | rs1 | funct3 | imm[4:1] | imm[11] | opcode |
---|---|---|---|---|---|---|---|
1位 | 6位 | 5位 | 5位 | 3位 | 4位 | 1位 | 7位 |
无条件跳转-链接指令(jal)是唯一使用UJ型格式的指令。
UJ(无条件跳转)型格式指令:(如jal x0, 2000
)
imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd | opcode |
---|---|---|---|---|---|
1位 | 10位 | 1位 | 8位 | 5位 | 7位 |
分支中的寻址使用PC相对寻址,它的地址是PC和指令中的常量之和。
2.10.3 RISC-V寻址模式总结
RISC-V有4种寻址模式:
- 立即数寻址:操作数是指令本身的常量。
- 寄存器寻址:操作数在寄存器中。
- 基址寻址:操作数于内存中,其地址是寄存器和指令中的常量之和。
- PC相对寻址:分支地址是PC和指令中常量之和。
2.10.4 机器语言译码
从机器语言到汇编语言的译码需要编码表和指令格式表来查找。
RISC-V指令的编码:
格式 | 指令 | 操作码 | funct3 | funct6/7 |
---|---|---|---|---|
R | add | 0110011 | 000 | 0000000 |
R | sub | 0110011 | 000 | 0100000 |
R | sll | 0110011 | 001 | 0000000 |
R | xor | 0110011 | 100 | 0000000 |
R | srl | 0110011 | 101 | 0000000 |
R | sra | 0110011 | 101 | 0000000 |
R | or | 0110011 | 110 | 0000000 |
R | and | 0110011 | 111 | 0000000 |
R | lr.d | 0110011 | 011 | 0001000 |
R | sc.d | 0110011 | 011 | 0001100 |
I | lb | 0000011 | 000 | n.a. |
I | lh | 0000011 | 001 | n.a. |
I | lw | 0000011 | 010 | n.a. |
I | ld | 0000011 | 011 | n.a. |
I | lbu | 0000011 | 100 | n.a. |
I | lhu | 0000011 | 101 | n.a. |
I | lwu | 0000011 | 110 | n.a. |
I | addi | 0010011 | 000 | n.a. |
I | slli | 0010011 | 001 | 000000 |
I | xori | 0010011 | 100 | n.a. |
I | srli | 0010011 | 101 | 000000 |
I | srai | 0010011 | 101 | 010000 |
I | ori | 0010011 | 110 | n.a. |
I | andi | 0010011 | 111 | n.a. |
I | jalr | 1100111 | 000 | n.a. |
S | sb | 0100011 | 000 | n.a. |
S | sh | 0100011 | 001 | n.a. |
S | sw | 0100011 | 010 | n.a. |
S | sd | 0100011 | 111 | n.a. |
SB | beq | 1100111 | 000 | n.a. |
SB | bne | 1100111 | 001 | n.a. |
SB | blt | 1100111 | 100 | n.a. |
SB | bge | 1100111 | 101 | n.a. |
SB | bltu | 1100111 | 110 | n.a. |
SB | bgeu | 1100111 | 111 | n.a. |
U | lui | 0110111 | n.a. | n.a. |
UJ | 1101111 | n.a. | n.a. |
RISC-V指令格式:
名称 (字段大小) | 字段 | 备注 | |||||
7位 | 5位 | 5位 | 3位 | 5位 | 7位 | ||
R型 | funct7 | rs2 | rs1 | funct3 | rd | opcode | 算术指令格式 |
I型 | immediate[11:0] | rs1 | funct3 | rd | opcode | 加载&立即数算术 | |
S型 | immediate[11:5] | rs2 | rs1 | funct3 | immediate[4:0] | opcode | 存储 |
SB型 | immediate[12,10:5] | rs2 | rs1 | funct3 | immediate[4:1,11] | opcode | 条件分支格式 |
UJ型 | immediate[20,10:1,11,19:12] | rd | opcode | 无条件跳转 | |||
U型 | immediate[31:12] | rd | opcode | 大立即数格式 |
2.11 指令与并行性:同步
当来自两个线程的访存请求出现数据竞争时,通常需要以原子交换原语形式构建基本同步原语。它是构建同步机制的典型操作,它将寄存器中的值与存储器中的值进行交换。
假设构建一个简单的锁变量,其中值0表示锁变量可用,值1表示锁变量被占用。处理器尝试通过将寄存器中的1与该锁变量对应的内存地址中的值进行交换来加锁。如果其他处理器已访问该锁变量,则交换指令返回值1,表明该锁已被占用,否则为0,表示加锁成功,且锁变量被交换为1,以防止其他处理器也加锁成功。
在RISC-V中,使用指令对保留加载(load-reserved)双字(lr.d
)和条件存储(store-conditional)双字(sc.d
)实现原子交换。
例:(在寄存器x20中指定的内存位置上实现原子交换编译为RISC-V代码)
【RISC-V】
addi x12, x0, 1 //copy locked value
again:
lr.d x10, (x20) //load-reserved to read lock
bne x10, x0, again //check if it is 0 yet
sc.d x11, x12, (x20) //attempt to store new value
bne x11, x0, again //branch if store fails
sd x0, 0(x20) //free lock by writting 0
2.12 翻译并启动程序
C程序转换为计算机上可运行程序需要四个步骤: