RV32I是最基本的32位Base指令集,它支持32位寻址空间,支持字节地址访问,仅支持小端格式(little-endian,高地址高位,低地址地位),寄存器也是32位整数寄存器。RV32I指令集的目的是尽量简化硬件的实施设计,所以它只有47条指令。
在RV32I指令集架构中,包括32个通用目的寄存器,其中x0被预留为常数0,其它31个寄存器(x1-x31)是普通的通用整数寄存器。在Risc-V汇编语言中,每个通用寄存器都有一个对应的ABI名字,也就是说在汇编语言中,x1等价于ra,它们都会编译成相同的机器码。
在Risc-V架构中,要得到当前指令pc(指令在存储器中的位置,instruction program counter),可以通过AUIPC指令,把它读入到一个通用寄存器中。
寄存器 | ABI名字(别名) | 注释 | Saver |
x0 | zero | Hard-wired zero,常数0 | |
x1 | ra | Return address | caller,调用函数的指令pc |
x2 | sp | Stack pointer | callee,被调用的函数指令pc |
x3 | gp | Global pointer | |
x4 | tp | Thread pointer | |
x5 | t0 | Temporary/alternate link register | caller |
x6 | t1 | Temporaries | caller |
x7 | t2 | Temporaries | caller |
x8 | s0/fp | Saved register/frame pointer | caller |
x9 | s1 | Saved register | caller |
x10 | a0 | Function arguments/return values | caller |
x11 | a1 | Function arguments/return values | caller |
x12 | a2 | Function arguments | caller |
x13 | a3 | Function arguments | caller |
x14 | a4 | Function arguments | caller |
x15 | a5 | Function arguments | caller |
x16 | a6 | Function arguments | caller |
x17 | a7 | Function arguments | caller |
x18 | s2 | Saved registers | caller |
x19 | s3 | Saved registers | caller |
x20 | s4 | Saved registers | caller |
x21 | s5 | Saved registers | caller |
x22 | s6 | Saved registers | caller |
x23 | s7 | Saved registers | caller |
x24 | s8 | Saved registers | caller |
x25 | s9 | Saved registers | caller |
x26 | s10 | Saved registers | caller |
x27 | s11 | Saved registers | caller |
x28 | t3 | Temporaries | caller |
x29 | t4 | Temporaries | caller |
x30 | t5 | Temporaries | caller |
x31 | t6 | Temporaries | caller |
Base指令格式:
RV32I指令格式包括以下6种,每种指令格式都是固定的32位指令,所以指令在内存中必须4字节对齐,否则将触发异常。其中rd表示目的寄存器,rs1是源操作数寄存器1,rs2是源操作数寄存器2。
imm表示指令中的立即数,比如imm[11:0],表示一个12位的立即数,它的高20位会符号位扩展,imm[31:12]表示一个32位的立即数,它的低12位会补0。
下图是各种指令格式扩展后的32位立即数。
RV32I整数指令集
RV32I整数指令集分为几个种类:
1.Load和store指令
Category | Fmt | RV32I base | machine code(bin) | comment | |
Loads | load byte | I | lb rd, rs1, imm | [31-20,imm][19-15,rs1]000[11-7,rd]0000011 | rd=mem[rs1+imm], 8bit数据符号位扩展后返回rd |
load half word | I | lh rd, rs1, imm | [31-20,imm][19-15,rs1]001[11-7,rd]0000011 | rd=mem[rs1+imm],16bit数据符号扩展后返回rd,应该保证地址对齐,否则产生异常 | |
load word | I | lw rd, rs1, imm | [31-20,imm][19-15,rs1]010[11-7,rd]0000011 | rd=mem[rs1+imm],32bit数据返回rd,应该保证地址对齐,否则产生异常 | |
load byte unsinged | I | lbu rd, rs1, imm | [31-20,imm][19-15,rs1]100[11-7,rd]0000011 | rd=mem[rs1+imm], 8bit数据高位补0后返回rd | |
load half unsinged | I | lhu rd, rs1, imm | [31-20,imm][19-15,rs1]101[11-7,rd]0000011 | rd=mem[rs1+imm], 16bit数据高位补0后返回rd |
stores | store byte | S | sb rs1, rs2, imm | [31-25,imm[11-5]][24-20,rs2,][19-15,rs1]000[11-7,imm[4-0]]0100011 | mem[rs1+imm]=rs2的低8bit数据 |
store half word | S | sh rs1, rs2, imm | [31-25,imm[11-5]][24-20,rs2,][19-15,rs1]001[11-7,imm[4-0]]0100011 | mem[rs1+imm]=rs2的低16bit数据 | |
store word | S | sw rs1, rs2, imm | [31-25,imm[11-5]][24-20,rs2,][19-15,rs1]010[11-7,imm[4-0]]0100011 | mem[rs1+imm]=rs2的32bit数据 |
RV32I是一个load /store架构,所有的memory访问都是通过load/store指令,其它指令都是在寄存器之间进行运算,比如加法指令,减法指令等等。注意,装入目的寄存器如果为x0,将会产生一个异常。Load/Store指令在memory和寄存器之间传输数据,Load指令编码为I型,store指令编码为S型。计算memory地址时候,imm都会符号扩展成32位,然后和rs1相加,得到memory地址。为了提高性能,load/store指令应该尽量对齐地址,比如lw指令,应该4字节对齐访问地址,lh应该双字节对齐访问地址。根据微架构实现的不同,不对齐地址的访问可能会比较慢,而且地址对齐访问,能够确保是原子操作,不对齐的话为了读取和存储数据正确,还有进行额外的同步操作。
2.整数计算指令(算术,逻辑指令,比较指令以及移位指令)
Category | Fmt | RV32I base | machine code(bin) | comment |
arithmetric | add | R | add rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]000[11-7,rd]0110011 | rd=rs1+rs2,如果溢出,舍弃高位,保留低32bit |
add immediate | I | addi rd, rs1, imm | [31-20,imm][19-15,rs1]000[11-7,rd]0010011 | rd=rs1+imm,12bit立即数会符号位扩展,结果写回rd时如果溢出,则舍弃高位,保留低32bit | |
subtract | R | sub rd, rs1, rs2 | 0100000[24-20,rs2][19-15,rs1]000[11-7,rd]0110011 | rd=rs1-rs2,如果溢出,舍弃高位,保留低32bit | |
load upper imm | U | lui rd, imm | [31-12,imm][11-7,rd]0110111 | load imm to high 20 bits of rd, and place 0 in low 12 bits | |
add upper imm to PC | U | auipc rd, imm | [31-12,imm][11-7,rd]0010111 | pc=pc+high 20 bits imm&low 12 bits 0, then result save to rd. | |
logical | xor | R | xor rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]100[11-7,rd]0110011 | rd=rs1^rs2, bitwise operation |
xor immediate | I | xori rd, rs1, imm | [31-20,imm][19-15,rs1]100[11-7,rd]0010011 | rd=rs1^imm, i符号扩展12bit数imm,结果放在rd。 | |
or | R | or rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]110[11-7,rd]0110011 | rd=rs1|rs2, bitwise operation | |
or immediate | I | ori rd, rs1, imm | [31-20,imm][19-15,rs1]110[11-7,rd]0010011 | rd=rs1|imm, 符号扩展12bit数imm,结果放在rd。 | |
and | R | and rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]111[11-7,rd]0110011 | rd=rs1&rs2, bitwise operation | |
and immediate | I | andi rd, rs1, imm | [31-20,imm][19-15,rs1]111[11-7,rd]0010011 | rd=rs1&imm, i符号扩展12bit数imm,结果放在rd。 |
shifts | shift left | R | SLL rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]001[11-7,rd]0110011 | rd=rs1<<rs2,rs1左移值为rs2的低5bit,低位补0 |
shift left immediate | I | SLLI rd, rs1, shamt | 0000000[24-20,imm][19-15,rs1]001[11-7,rd]0010011 | rd=rs1<<shamt,rs1移动5bit立即数,低位补0 | |
shift right | R | SRL rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]101[11-7,rd]0110011 | rd=rs1>>rs2,高位补0,右移值为rs2中的低5位 | |
shift right immediate | I | SRLI rd, rs1, shamt | 0000000[24-20,imm][19-15,rs1]101[11-7,rd]0010011 | rd=r1>>shamt,高位补0,右移值为5bit立即数 | |
shift right arithmetirc | R | SRA rd, rs1, rs2 | 0100000[24-20,rs2][19-15,rs1]101[11-7,rd]0110011 | rd=rs1>>rs2,高位补符号位,右移值为rs2中的低5位 | |
shift right arith imm | I | SRAI rd, rs1, shamt | 0100000[24-20,imm][19-15,rs1]101[11-7,rd]0010011 | 右移时,右边补符号位 |
compare | set < | R | slt rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]010[11-7,rd]0110011 | if rs1< rs2, write 1 to rd, 有符号数比较 |
set < immediate | I | slti rd, rs1, imm | [31-20,imm][19-15,rs1]010[11-7,rd]0010011 | if rs1<imm, write 1 to rd,imm会符号位扩展,所以进行有符号数比较 | |
set < unsigned | R | sltu rd, rs1, rs2 | 0000000[24-20,rs2][19-15,rs1]011[11-7,rd]0110011 | if rs1< rs2, write 1 to rd, 无符号数比较 | |
set < imm unsigned | I | sltiu rd, rs1, imm | [31-20,imm][19-15,rs1]011[11-7,rd]0010011 | if rs1<imm, write 1 to rd,imm会符号位扩展,所以进行有符号数比较 |
计算指令在寄存器和寄存器之间,或者在寄存器和立即数之间进行算术或逻辑运算。指令格式为I,R或者U型。整数计算指令不会产生异常。我们能够通过ADDI x0, x0, 0来模拟NOP指令,该指令除了改变pc值外,不会改变其它任何用户状态。
3. 控制指令,包括无条件跳转指令和条件跳转指令
Category | Fmt | RV32I base | machine code(bin) | comment |
branch | branch = | SB | beq rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]000[11-7, imm[4:1][11]]1100011 | if(rs1==rs2) pc=pc+imm*2,跳转指令 |
branch <> | SB | bne rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]001[11-7, imm[4:1][11]]1100011 | if(rs1!=rs2) pc=pc+imm*2,跳转指令 | |
branch < | SB | blt rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]100[11-7, imm[4:1][11]]1100011 | if(rs1<rs2) pc=pc+imm*2,跳转指令 , 有符号数 | |
branch >= | SB | bge rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]101[11-7, imm[4:1][11]]1100011 | if(rs1>rs2) pc=pc+imm*2,跳转指令,有符号数 | |
branch < unsigned | SB | bltu rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]110[11-7, imm[4:1][11]]1100011 | if(rs1<rs2) pc=pc+imm*2,跳转指令,无符号数 | |
branch >=unsigned | SB | bgeu rs1, rs2,imm | [31-25, imm[12][10:5]][24-20, rs2][19-15, rs1]111[11-7, imm[4:1][11]]1100011 | if(rs1>rs2) pc=pc+imm*2,跳转指令,无符号数 | |
jump and link | J&L | UJ | JAL rd, imm | [31-12, imm[20][10:1][11][19:12]][11-7,rd]1101111 | pc=pc+imm*2, 现在的pc+4写入rd寄存器 |
Jump and link register | UJ | JALR, rd, rs1, imm | [31-20,imm[11:0]][19-15,rs1]000[11-7,rd]1100111 | pc=rs1+imm, pc+4写入到rd寄存器 |
JAL指令使用20位的立即数作为偏移,所以它仅可以跳转到前后1M的地址空间。JALR使用12位的立即数,其值和rs1中的值相加,得到偏移量。
4. 同步指令
Category | Fmt | RV32I base | machine code(bin) | comment |
Synch | synch thread | I | FENCE iorw,iorw | 0000[27-24,pred][23-20,succ]00000000000000001111 | pred/succ分别表示fence之前和之后的 iorw |
synch instr and data | I | FENCE.i | 00000000000000000001000000001111 | 用于同步指令和数据流 |
Risc-V在多个hart(硬件线程)之间使用的是松散一致性模型,所以需要存储器fence指令。
fence指令能够保证存储器访问的执行顺序。在fence指令之前的所有存储器访问指令,比该fence之后的所有数据存储器访问指令先执行。
Risc-V架构将数据存储器的地址空间分为设备IO(device IO)和普通存储器空间,因此其读写访问分为四种类型:
I:设备读(device-input)
O:设备写(device-ouput)
R:存储器读(memory-reads)
W:存储器写(memory-writes)
PI/PO/PR/PW,分别f表示ence指令之前的四种读写访问类型,SI/SO/SR/SW分别表示fence指令之后的四种读写访问类型。
fence.i指令用于同步指令和数据流。如果程序中添加一个fence.i,则该指令能够保证fence.i之前所有指令的访存结果能被fence.i之后的所有指令访问到。通常说来,处理器的微架构硬件实现时,一旦遇到一条fence.i指令,便会先等到之前的所有访存指令执行完,然后冲刷流水线,包括Icache,使其后的所有指令,能够重新取指,从而得到最新的值。
注意:fence.i只能保证同一个hart(硬件线程)执行的指令流和数据流顺序,不能保证多个hart之间的指令流和数据流访问。
5.控制状态寄存器指令
Category | Fmt | RV32I base | machine code(bin) | comment |
CSR | CSRRW | I | CSRRW | [31-20,csr][19-15, rs1]001[11-7,rd]1110011 | csrrw rd, csr, rs1, 将csr索引的寄存器值读出,写到rd,将rs1中的值写入csr |
CSRRS | I | CSRRS | [31-20,csr][19-15, rs1]010[11-7,rd]1110011 | csrrs rd, csr, rs1, 将csr索引寄存器值读出写到rd,以rs1为参考,如果rs1中某位为1,则置位对应的csr寄存器位,其他位不受影响。 | |
CSRRC | I | CSRRC | [31-20,csr][19-15, rs1]011[11-7,rd]1110011 | csrrc rd, csr, rs1, 将csr索引寄存器值读出写到rd,以rs1为参考,如果rs1中某位为1,则清零对应的csr寄存器位,其他位不受影响。 | |
CSRRWI | I | CSRRWI | [31-20,csr][19-15, zimm]101[11-7,rd]1110011 | csrrwi rd, csr, imm[4:0], 将csr索引寄存器值读出写到rd,5位立即数的值写入csr,高位补0 | |
CSRRSI | I | CSRRSI | [31-20,csr][19-15, zimm]110[11-7,rd]1110011 | csrrsi rd, csr, imm[4:0], 将csr索引寄存器值读出写到rd,5位立即数的值(高位补0)逐位比较,如果为1,则置位csr相应位 | |
CSRRCI | I | CSRRCI | [31-20,csr][19-15, zimm]111[11-7,rd]1110011 | csrrci rd, csr, imm[4:0], 将csr索引寄存器值读出写到rd,5位立即数的值(高位补0)逐位比较,如果为1,则清零csr相应位 |
CSR指令包括上面表中的6条指令,通常在用户层,不会访问写csr的指令。
这些CSR指令实现都是原子操作。对于CSRRW和CSRRWI来说,如果rd为x0,则不会有任何写操作。对于CSRS和CSRRC指令,如果rs1为x0,则不会有任何写操作。对于CSRRSI和CSRRCI来说,如果立即数为0,则不会有任何写操作。
用CSR指令可以实现一些伪指令,例如读取始终频率等。详细见伪指令列表。
6.环境调用和断点指令
Category | Fmt | RV32I base | machine code(bin) | comment |
System | system call | I | ECALL | 00000000000000000000000001110011 | 用于生成环境调用异常,产生异常时候,mepc寄存器将保存当前指令pc值。 |
system break | I | EBREAK | 00000000000100000000000001110011 | 生成断点,产生异常时候,mepc寄存器将保存当前指令pc值。 |
这两条指令能够产生环境调用异常和生成断点异常,产生异常时候,当前指令的pc值被写入mepc寄存器。
这两条指令在调试代码时候有用。