RV32I指令集基础
对于RV32I而言,有4种核心的指令类型(R/I/S/U)。所有的32位的指令必须在内存中四字对齐。
- 地址未对齐异常将被确定要跳转的跳转指令或无条件跳转指令所报告,而不是被目标指令报告,这一点和MIPS不同。不跳转的指令不会报告地址未对齐异常。
- 对于保留指令,并不指明其行为,不会触发异常。这一点和MIPS不同。在实现中,我们可以直接Invalidate这条指令。
- 除了CSR指令,其他的指令的立即数全部都会做符号扩展
- 符号位总是在指令的第31位,为了简化电路的逻辑
立即数的编码
若考虑立即数编码,严格来说,指令类型应该分为以下几种:
从另一个角度看,各类指令编码出的立即数如下所示:
我们可以看到,符号扩展总是使用的第31位。
指令
整数运算指令
- 整数运算指令不造成任何异常,因为结果溢出的异常通常是可以使用软件机制来进行检测的
寄存器-立即数指令
ADDI
将符号扩展的12位立即数加到rs1
,并且会忽略溢出。SLTI
在rs1
小于符号扩展后的立即数时,将rd
设置为1.SLITU
还是做的符号扩展,只是之后做的无符号比较ANDI/ORI/XORI
还是做的符号扩展,然后进行相应的逻辑运算
rs1
存放要被移位的操作数- 需要被移动的位数在低5位的
IMM
中(注意RV64I不一样!) 30
位表示右移的类型- SLLI: 逻辑左移(填0)
- SRLI: 逻辑右移(填0)
- SRAI:算术左移(填符号位)
- LUI: 不对称的立即数载入,将
imm
载入rd
的高20位,低12位填0. - AUIPC: 将当前的PC和
imm[31:12], 12'b0
相加,结果存入rd
寄存器-寄存器指令
- 比较简单,不解释了
NOP
- NOP === ADDI x0,x0,0
控制转移指令
- RV32I提供了两种控制转移指令:无条件跳转和条件分支。没有延迟槽。
- 如果指令访问异常或缺页异常,则会在目标指令汇报异常,而不是那条跳转指令
无条件转移指令
偏移量被符号扩展,并且加到跳转指令的PC上,获得跳转目标地址。
JAL
将跳转指令的pc+4
存入到rd
寄存器里面。J指令被编码为
JAL x0
,也就是说不写入寄存器
JALR
是间接转移指令,目标地址是rs1
加上符号扩展的12位的立即数,最后把目标的最低位置0,然后将pc+4
写入rd
。如果不需要保存返回地址,直接把
rd
设置成0就可以了。JAL和JALR指令会产生地址未对齐异常
关于RAS
RISC-V规定,JAL
仅在rd=x1/x5
的时候将地址放入RAS中,其余时刻不做操作。
JALR
的操作遵循下表,link
在寄存器是x1
或x5
的时候为真:
条件转移指令
- 所有的条件转移指令使用B类指令编码
- 偏移量在符号扩展之后,和当前指令的PC相加
- 在预测的时候,应当在第一次预测时,猜测往后会跳转,往前不跳转。
- 无条件转移在RV中永远都是JAL,不会使用那些条件永远为真的无条件转移指令,例如MIPS中的x0,x0
- 只有在跳转的时候,才有可能生成地址未对齐异常
Load/Store
EEI会定义地址空间的哪些部分可以被指令读取,哪些部分只能以字读取等待。
如果Load指令的目的地址是x0
,则必须要造成异常!
Load属于I类指令,Store属于S类指令。
地址没有对齐,将造成的行为取决于EEI的定义。
我们定义,地址未对齐,将造成异常。
ECALL和断点
RV64I指令集
- RV64I指令集在上面RV32的基础上,将寄存器宽度拓展到了64位。
- 在RV64I中,多出了一些
*W
指令,这些指令忽略操作数的高32位,并且总是输出32位有符号数,并符号扩展到64位。也就是说,从XLEN-1
到31
位都是一样的。 - 注意RV64I中的移位指令,其
shamt
域多了1位 - 对于LUI而言,是将原来32位的结果扩展到了64位,AUIPC也是如此
Zicsr控制指令
- 所有的CSR指令原子地读、改写单个CSR寄存器。
- CSR寄存器被编码到上面的
csr
域中,31-20
位 - 无符号整数使用了
rs1
域进行了编码 CSRRW
指令在CSR
和整数寄存器之间做原子交换,从CSR中读取源寄存器,并做零扩展,写回到rd
,并且rs1
中的值被写入了CSR。注意,如果rd
为x0
,则指令不应该读CSR
寄存器,但是会写入CSR寄存器,不应该造成任何的副作用。CSRRS
指令读CSR
寄存器的值,并做零扩展,写回到rd
,并且rs1
被作为位掩码,其位1的对应位在CSR中也会被设置为1.CSRRC
的作用和CSRRS
相反,对应位1的位会被清除- 如果上述2个指令的源寄存器是x0,那么什么都不会做
- 对于CSR的读写,在RISC-V中,定义是不会对后续指令的执行产生相应的影响的
- 某些CSR,例如:
instret
指令,有可能会因为指令被执行而更改。在这种情况下,如果CSR指令读了CSR,他应该读出指令执行之前的值,如果CSR指令写了CSR,那么应该覆盖原来的值。特别地,如果某个指令写了instret
,那么这条指令后续的指令读出的应当是其写入的那个值。