RV32I
完整的RV32I指令集可以用下面的式子中出现单词的首字母表示:
比如这一条:
set less than {immediate} {unsigned}
也就是slt slti sltu sltiu这4个指令。
RISCV指令格式如下。R 寄存器操作,I 立即数或load访存,S store访存,B 条件跳转,U长立即数,J无条件跳转。
RV和其他指令集的优势:
-
首先格式统一,解码速度快;
-
然后提供3个寄存器存数据,提供少于3个寄存器但是需要用到3个操作数的ISA指令需要多用一条mv指令转移数据。
-
寄存器位置固定,不像其他比如arm的寄存器一会能做源操作数一会做dest操作数,这样判断逻辑简单,甚至在解码完成之前就可以去寄存器里把数取完。
-
总是包含符号扩展,这样符号扩展也可以在解码完成前就执行。
全0和全1是非法的异常和错误,帮助调试。
可以看出RV32I用的相当节省,给后面扩展指令留了很多可能。而且还有很多微操作来提高性能,比如这些指令的数值大多数位是相等的,简化控制逻辑;BJ 指令地址需要<<1来 *2 使得节约位数的情况下表示更大范围的地址;立即数的排布也有讲究。
整数计算
add, sub, and, or, xor 和大多数ISA一样,两个32位寄存器做运算。并且立即数总是进行符号扩展,这样sub其实可以用负数的add代替。
slt 也有立即数和有无符号的扩展。比较结果生成01的布尔值。
lui 是加载立即数到高20位,而auipc是加载到pc的高20位,和12位jalr立即数偏移量结合跳转到32位pc地址。
和其他指令集相比的优化之处在于:
- RV 中对寄存器操作没有按半字或字节的计算操作,全是整个寄存器计算操作。虽然按部分位访存会节省能量,但是按部分位进行算术运算比较耗能量。RV采用单独移位指令来处理。
- RV 用移位和加减代替乘除指令,缩小芯片面积。
我们可以使用异或操作来不使用额外寄存器的情况下交换两个寄存器的值。
r1=r1^r2
r2=r1^r2
r1=r1^r2
不过其实RV寄存器数量充足,一般用不到这个骚操作。
Load Store
lw sw 处理字
lh sh 处理半字
lb sb 处理 byte
加u是有符号
不管数据是整字,半字还是字节,都先被扩展为32位再处理,保证计算可以正确进行。
ls的寻址方式是和x86类似的12位位偏移寻址模式。
这一段不是完全理解,等学过arm和x86汇编也许理解会更为透彻。
相比arm和x86,rv支持未对齐数据的访问。简化了整体设计。
RV 选择了小字节序。
条件分支
bne beq bge blt 以及大于等于和小于的无符号版。
分支指令的寻址方式是 12 位的立即数乘以 2,符号扩展它,然后将得到值加到 PC 上作为分支的跳转地址。这种跳转方式被称为是相对寻址。为什么可以通过乘2的方式扩展?因为RV指令长度都是2字节的倍数,这点后面还会讲。因此可以用立即数*2的方式表示相对地址。
rv省去了条件码(比如0000代表EQ 这样表示判断结果状态)和x86中的一些循环指令(如loop),省去了一些隐式状态。
无条件跳转
jal,把当前pc+4存入ra且跳转。
如何实施无返回的无条件跳转?把全0的x0寄存器替换ra,因为x0无法修改。
jal x0, 0x1000 # 跳转到0x1000,不返回的跳转
减少了复杂的跳转指令。
杂项
csrrc、csrrs、csrrw、csrrci、csrrsi、csrrwi 这些是访问64位时钟的,我们一次就能访问32位。
ecall 是像运行时环境发出请求的,比如系统调用,转调试模式。
fence 是外部可见的访存请求。RV 的 IO 是和访存方式一样的访问方式,而不是专门设计一些单独指令。EBU5476 Microprocessor System Design 知识点总结_5 GPIO_灰海宽松的博客-CSDN博客
而且为实现字符串处理,RV 实现了字节存取而不是设计单独指令(x86:别按着我骂)。
总结优势
-
32 位字节可寻址的地址空间
-
所有指令均为 32 位长
-
31 个寄存器,全部 32 位宽,寄存器 0 硬连线为零
-
所有操作都在寄存器之间(没有寄存器到内存的操作。这里我不太理解,可能lw和sw不算吧)
-
加载/存储字加上有符号和无符号加载/存储字节和半字
-
所有算术,逻辑和移位指令都有立即数版本的指令
-
立即数总是符号扩展
-
仅提供一种数据寻址模式(寄存器+立即数)和 PC 相对分支
-
无乘法或除法指令
-
一个指令,用于将大立即数加载到寄存器的高位,这样加载 32 位常量到寄存器只需要两条指令