了解RISCV体系结构以及其基础指令集RV32I指令格式组成。
Programmers’ Model for Base Integer ISA
RISC-V的体系结构是模块化的。即可以在基础指令之上添加扩展指令,这一点相比于ARM32以及x86-32体系具有绝大的优势。
RISC-V最基础的指令是RV32I,即32位指令。这是所有RISC-V处理器都需实现的指令。RISC-V体系结构可以在这个基础指令集上进行扩展,例如:RV64I,64位的基础指令扩展;RV32M,乘法指令扩展;RV32F,单精度浮点扩展;RV32D,双精度浮点扩展等。下表列出了RISC-V体系的一些指令集。
Name | Type | Descrtion |
---|---|---|
RV32I | 基础指令 | 整数指令,包含算术、分支、逻辑、访存指令,有32个32位寄存器,能寻址32位地址空间 |
RV32E | 基础指令 | 与RV32I一样,只不过只能使用前16个32位寄存器 |
RV64I | 基础指令 | 整数指令,包含算术、分支、逻辑、访存指令,有32个64位寄存器,能寻址64位地址空间 |
RV128I | 基础指令 | 整数指令,包含算术、分支、逻辑、访存指令,有32个128位寄存器,能寻址128位地址空间 |
M | 扩展指令 | 包含乘法、除法、求模取余指令 |
F | 扩展指令 | 单精度(32bit)浮点指令 |
D | 扩展指令 | 双精度(32bit)浮点指令,必须要同时支持F扩展指令 |
Q | 扩展指令 | 四倍精度浮点指令 |
A | 扩展指令 | 存储器原子操作指令,比如比较并交换,读-改-写等指令 |
C | 扩展指令 | 压缩指令,指令长度为16位,主要用于改善程序大小 |
P | 扩展指令 | 单指令多数据(Packed-SIMD)指令 |
B | 扩展指令 | 位操作指令 |
H | 扩展指令 | 支持 Hypervisor 管理指令 |
J | 扩展指令 | 动态翻译语言的指令 |
L | 扩展指令 | 十进制浮点指令 |
N | 扩展指令 | 用户中断指令 |
G | 通用指令 | 包含 I、M、A、F、D指令 |
Register
将RISC-V通用寄存器(RV32I)作为典型。
对于RV32I而言,CPU拥有32个通用寄存器,每个寄存器的宽度为32bit(XLEN=32)。其中x0寄存器硬编码恒为0,即x0寄存器上的值恒为0。另外,RISC-V还有一个pc寄存器用于保存当前指令的地址。
标准软件调用约定规定x1用于保存调用的返回地址,x2作为堆栈指针,x5用作临时寄存器或备用链接寄存器。下表列出了标准调用约定下的寄存器使用规范。(可能存在勘误)
这里需要注意Saver有以下定义
- Caller:由调用者保存
- Callee:由被调用者保存
Register | ABI Name | Descrtion | Saver |
---|---|---|---|
x0 | zero | 0值寄存器,硬编码为0,写入数据忽略,读取永远为0(Hardwired Zero) | – |
x1 | ra | 返回地址(Return Address) | Caller |
x2 | sp | 栈指针(Stack Pointer) | Callee |
x3 | gp | 全局指针(Global Pointer) | – |
x4 | tp | 线程指针(Thread Pointer) | – |
x5 | t0 | 临时寄存器或备用链接寄存器(Temporary / alternate Link Register) | Caller |
x6-x7 | t1-t2 | 临时寄存器(Temporary) | Caller |
x8 | s0/fp | 需要保存的寄存器或者帧指针寄存器(Saved Register / Frame Pointer) | Callee |
x9 | s1 | 需要保存的寄存器,保存原进程中的关键数据,避免在函数调用过程中被破坏(Saved Register) | Callee |
x10-x11 | a0-a1 | 函数参数/返回值(Function Arguments / Return Value) | Caller |
x12-x17 | a2-a7 | 函数参数(Function Arguments) | Caller |
x18-x27 | s2-s11 | 需要保存的寄存器(Saved Register) | Callee |
x28-x31 | t3-t6 | 临时寄存器(Temporary) | Caller |
pc |
Base Instruction Formats
在RV32I ISA中,有四种核心指令格式(R/I/S/U),如下图所示。
很明显,指令格式有:
- major opcode位于0-6bit上
- 当存在目标寄存器时,其保存于7-11bit上
- 当存在第一个源寄存器时,其保存在15-19bit上
- 当存在第二个源寄存器时,其保存在20-24bit上
- 余下的位用于minor opcode或指令的其他数据(func3位于12-14bit,func7位于25-31bit)
而多少位用于立即数取决于指令中存在的寄存器号个数:
- R-type:存在一个目标寄存器和两个源寄存器的指令时没有立即数。例如两个寄存器相加(
ADD
) - L-type:存在一个目标寄存器和一个源寄存器的指令拥有12bits的立即数,例如一个寄存器相加(
ADDI
) - S-type:存在两个源寄存器且没有目标寄存器的指令,例如
STORE
指令,也有12bits的立即数,但因为各个寄存器位于不同的位置,立即数也必须位于不同的位置。 - U-type:只存在一个目标寄存器,没有minor opcode的指令,例如
LUI
指令,拥有20bits的立即数(major opcode和目标寄存器号需要12bits)
Immediate Encoding Variants
额外的,RV32I还有两种变种指令格式(B/J),如下图所示。
S与B之间的唯一区别在于B-type将12bits的立即数,左移了一位。而不是将整条指令移位。按照惯例,中间的imm[10:1]
和符号位imm[12]
保持不变,而S-type中的最低位inst[7]
在B中将作为最高位进行编码。
类似,J-type的立即数字段是在U-type基础上左移了12位。
因为B-type和J-type的立即数都没有bit 0位,所以它们的立即数都是2的整数倍。
RISC-V实际上只有四种基本格式,但可以保守的认为有六种格式。
- 源寄存器和目标寄存器都设计固定在所有 RISC-V 指令同样的位置上,指令译码相对简单,所以指令在 CPU 流水线中执行时,可以先开始访问寄存器,然后再完成指令解码。
- 所有立即数的符号位总是在指令的最高位。这么做的好处是,有可能成为关键路径的立即数符号扩展可以在指令解码前进行,有利于 CPU 流水线的时序优化。
RV32I Instruction Formats
如下图所示,对于每幅图,将有下划线的字母从左往右连接起来,就组成了完整的RV32I指令集。对于每一个图,集合标志{}
内列举了指令的所有变体,变体用加下划线的字母或下划线字符_
表示。
下图展示了RV32I的基础指令组成。
Integer Operate Instructions
Register – Register
下图展示了寄存器-寄存器的整数运算指令布局,操作码,格式类型和名称的操作码映射。
相应的指令汇编格式如下表所示
Instruction Name | asm | Pseudo C |
---|---|---|
add | add rd, rs1, rs2 | rd = rs1 + rs2 |
sub | sub rd, rs1, rs2 | rd = rs1 - rs2 |
and | and rd, rs1, rs2 | rd = rs1 & rs2 |
or | or rd, rs1, rs2 | rd = rs1 | rs2 |
xor | xor rd, rs1, rs2 | rd = rs1 ^ rs2 |
slt | slt rd, rs1, rs2 | rd = rs1 < rs2 (signed) |
sltu | sltu rd, rs1, rs2 | rd = rs1 < rs2 (unsigned) |
sll | sll rd, rs1, rs2 | rd = rs1 << rs2 (Logical) |
srl | srl rd, rs1, rs2 | rd = rs1 >> rs2 (Logical) |
sra | sra rd, rs1, rs2 | rd = rs1 >> rs2 (Arithmetic) |
Immediate
下图展示了立即数的整数运算指令布局,操作码,格式类型和名称的操作码映射。
注:在立即数算术指令中,没有减法运算。
相应的指令汇编格式如下表所示
Instruction Name | asm | Pseudo C |
---|---|---|
addi | addi rd, rs1, imm[11:0] | rd = rs1 + imm[11:0] |
andi | andi rd, rs1, imm[11:0] | rd = rs1 & imm[11:0] |
ori | ori rd, rs1, imm[11:0] | rd = rs1 | imm[11:0] |
xori | xori rd, rs1, imm[11:0] | rd = rs1 ^ imm[11:0] |
slti | slti rd, rs1, imm[11:0] | rd = rs1 < imm[11:0] (signed) |
sltiu | sltiu rd, rs1, imm[11:0] | rd = rs1 < imm[11:0] (unsigned) |
slli | sll rd, rs1, shamt[4:0] | rd = rs1 << shamt[4:0] (Logical) |
srli | srl rd, rs1, shamt[4:0] | rd = rs1 >> shamt[4:0] (Logical) |
srai | sra rd, rs1, shamt[4:0] | rd = rs1 >> shamt[4:0] (Arithmetic) |
Upper Immediate
下图展示了U-type 32bits指令布局,操作码,格式类型和名称的操作码映射。
lui(Load Upper Immediate)
lui rd, imm # 将20bits的立即数左移12位,低12位补零写回rd中
配合addi
指令(设置低12bit)可将寄存器设置为任意32bits的立即数
lui x12, 0x12345 # x12 = 0x12345000 addi x12, x12, 0x321 # x12 = 0x12345321
但是,当这个12bits的立即数为负数(即最高比特位是1)时,得到的结果是高20位减1后与低12位拼接
lui x10, 0x12345 # x10 = 0x12345000 addi x10, x10, 0xeef # x10 = 0x12344eef
解决这个问题的方法之一:如果低12位的立即数的符号位是1,我们可以使用li
指令预先给高20位的数加 1。
auipc(Add Upper Immediate to PC)
auipc rd, imm # 将20bits的立即数左移12位,低12位补零得到的32bits数与pc的值相加写到寄存器rd
例如
Label: auipc x10, 0 # 将Label的地址保存在x10寄存器中
Load / Store
下图展示了Load/Store 指令布局,操作码,格式类型和名称的操作码映射。
Load和Store的寻址模式是符号扩展 12 位立即数加上基地址寄存器的形式,这在 x86-32 中被称为位偏移寻址模式,不同于x86-32或ARM-32,RV32I省略了ARM-32和x86-32的复杂寻址模式。使得CPU流水线可以对数据冲突提前做出判断,并通过流水线各级之间的转送加以处理,而不需要插入空操作(NOP),极大提高了代码的执行效率。
补充:Load指令属于 I 型指令,而 Store 指令属于 S 型指令。
相应的指令汇编格式如下表所示
Instruction Name | asm | Description |
---|---|---|
lw | lw rd, offset[11:0](rs1) | 字加载(load word) |
lh | lh rd, offset[11:0](rs1) | 半字加载(load halfword) |
lhu | lhu rd, offset[11:0](rs1) | 无符号半字加载(load halfword unsigned) |
lb | lb rd, offset[11:0](rs1) | 字节加载(load byte) |
lbu | lbu rd, offset[11:0](rs1) | 无符号字节加载(load byte unsigned) |
sw | sw rs2, offset[11:0](rs1) | 字存储(store word) |
sh | sh rs2, offset[11:0](rs1) | 半字存储(store halfword) |
sb | sb rs2, offset[11:0](rs1) | 字节存储(store byte) |
Control Transfer Instructions
Unconditional Jumps
无条件跳转分为直接跳转和相对跳转。
注:直接跳转是 J 型指令,而相对跳转是 I 型指令。
直接跳转
jal
指令的执行过程:
- 将20bits的立即数作符号扩展并左移一位,产生一个32bits的符号数
- 将该32bits符号数和
pc
相加得到跳转地址(这样jal
可以作为短跳转指令,跳转至 pc±1mb 范围内) - 同时,
jal
会把紧随其后的那条指令的地址,即pc+4
,存入目标寄存器(通常为ra
)中。如果目标寄存器为0,则jal
等同于goto
指令;如果目标寄存器不为0,jal
可以实现函数调用的功能。
相对跳转
jalr
指令会把12bits的立即数和源寄存器相加(通常ra作为源寄存器,x0作为目标寄存器),并把相加的结果末位清零,作为新的跳转地址。和jal
指令一样,jalr
也会把紧随其后的那条指令的地址,存入目标寄存器中。
相应的指令汇编格式如下所示
jal rd, label # rd=pc+4, pc+=offset
伪指令 j
实际上是jal指令的变体,rd被设置为x0,即丢弃返回地址。
jalr rd, rs1, imm # rd=pc+4, pc=rs1+imm
跳转到任意32位绝对地址
lui x1, <hi 20 bits> jalr ra, x1, <lo 12 bits>
相对pc的偏移跳转
auipc x1, <hi 20 bits> jalr x0, x1, <lo 12 bits> # 无条件跳转
Conditional Branches
有条件跳转
相应的指令汇编格式如下表所示
Instruction Name | asm | Pseudo C |
---|---|---|
beq | beq rs1, rs2, label | 相等跳转 |
bne | bne rs1, rs2, label | 不等跳转 |
blt | blt rs1, rs2, label | 小于跳转 |
bltu | bltu rs1, rs2, label | 无符号小于跳转 |
bge | bge rs1, rs2, label | 大于等于跳转 |
bgeu | bgeu rs1, rs2, label | 无符号大于等于跳转 |
CSR Instructions
除了内存地址空间和通用寄存器地址空间外,RISC-V 中还定义了一个独立的控制与状态寄存器(CSR)地址空间。
专用的CSR指令
这6条CSR指令又可以分为两类
- 寄存器操作:CSRRW,CSRRS,CSRRC
- 立即数操作:CSRRWI,CSRRSI,CSRRCI
对于立即数操作的3个指令,5bits的立即数被放在了rs1的位置,即上图所示的imm,同时也被称为uimm[4:0],因为这些立即数是无符号(unsigned immediate)的。
CSRRW
CSRRS
CSRRC
CSRRWI
CSRRSI
CSRRCI
Other Instructions
- 系统调用
ecall
指令 - 调试时用于将控制转移到调试环境的
ebreak
指令
Refenrence
[1] https://lab.cs.tsinghua.edu.cn/cod-lab-docs/labs/4-riscv-inst
[2] https://github.com/riscv/riscv-isa-manual/releases/download/draft-20200727-8088ba4/riscv-spec.pdf
[3] http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf
[4] https://suda-morris.github.io/blog/cs/risc-v.html
[6] https://www.cs.cornell.edu/courses/cs3410/2019sp/riscv/interpreter
[7] D. Patterson and A. Waterman, The RISC-V reader. Berkeley: Strawberry Canyon LLC, 2018.
发表回复