一文看懂RISC-V的RV32I指令集
reference: <riscv-spec.pdf>1.RV32I指令集格式六种基本指令格式,分别是:目的类型用于寄存器-寄存器操作R 类型指令用于短立即数和访存 load 操作I 型指令用于访存 store 操作S 型指令用于条件跳转操作B 类型指令用于长立即数U 型指令用于无条件跳转J 型指令 如下所述,分支指令(B 类型)的立即数字段在 S 类型的基础上旋转了 1 位。跳转指
reference: <riscv-spec.pdf>
1.RV32I指令集格式
六种基本指令格式,分别是:
目的 | 类型 |
---|---|
用于寄存器-寄存器操作 | R 类型指令 |
用于短立即数和访存 load 操作 | I 型指令 |
用于访存 store 操作 | S 型指令 |
用于条件跳转操作 | B 类型指令 |
用于长立即数 | U 型指令 |
用于无条件跳转 | J 型指令 |
如下所述,分支指令(B 类型)的立即数字段在 S 类型的基础上旋转了 1 位。跳转指令(J类型)的直接字段在 U 类型的基础上旋转了 12 位。因此,RISC-V 实际上只有四种基本格式,但我们可以保守地认为它有六种格式。
B和 J 格式的分支和跳转地址必须向左移动 1 位以将地址乘以 2,从而给予分支和跳转指令更大的跳转范围。 RISC-V 将立即数中的位从自然排布进行了一些移位轮换,将指令信号的扇出和立即数多路复用的成本降低了近两倍, 这也简化了低端实现中的数据通路逻辑。
RISC-V的指令有几个特点:
- 指令只有六种格式,并且所有的指令都是 32 位长,这简化了指令解码.
- RISC-V 指令提供三个寄存器操作数,而不是像 x86-32 一样,让源操作数和目的操作数共享一个字段.
- 在 RISC-V 中对于所有指令,要读写的寄存器的标识符总是在同一位置,意味着在解码指令之前,就可以先开始访问寄存器.在许多其他的 ISA 中,某些指令字段在部分指令中被重用作为源目的地, 在其他指令中又被作为目的操作数(例如, ARM-32 和 MIPS-32) 。因此,为了取出正确的指令字段,我们需要时序本就可能紧张的解码路径上添加额外的解码逻辑,使得解码路径的时序更为紧张.
- 这些格式的立即数字段总是符号扩展,符号位总是在指令中最高位。这意味着可能成为关键路径的立即数符号扩展,可以在指令解码之前进行.
2.RV32I指令集清单
RV32I 指令如下图示。 把带下划线的字母从左到右连接就组成了 RV32I 指令。花括号{}表示集合中垂直方向上的每个项目都是指令的不同变体。集合中的下划线_意味着不包含这个字母的也是一个指令名称。例如,左上角附近的符号表示以下六个指令: and, or, xor, andi, ori, xori。
可见能分为四大类:(何其优雅的设计!)
3.汇编相关寄存器
下表为寄存器x和寄存器f的汇编助记符,以及 RISC-V 调用约定,各种指针(sp/gp/tp/fp),返回地址(ra),保存寄存器(s0-s11)和临时寄存器(t0-t6).统一使用程序二进制接口(ABI)所定义的寄存器名称使其更易阅读:
4.RV32I相关操作
4.1 RV32I的整数操作
简单的算术指令(add, sub)、逻辑指令(and, or, xor),以及移位指令(sll, srl, sra) 和其他 ISA 差不多。他们从寄存器读取两个 32 位的值,并将 32 位结果写入目标寄存器。 RV32I 还提供了这些指令的立即数版本。
程序可以根据比较结果生成布尔值,为应对这种使用场景, RV32I 提供一个"当小于时置位"的指令。如果第一个操作数小于第二个操作数,它将目标寄存器设置为 1,否则为0。不出所料,对这个指令,有一个有符号版本(slt)和无符号版本(sltu),分别用于处理有符号和无符号整数比较。相应的,上述两条指令也有立即数版本的(slti, sltiu)。正如我们将要看到的,虽然 RV32I 分支指令可以检查两个寄存器之间的所有关系,但一些条件表达式涉及多对寄存器之间的关系。对于这些表达式,编译器或汇编语言程序员可以将slt 以及与或异或等逻辑指令组合使用来解决更复杂的条件表达式。
lui和auipc这两条整数计算指令主要用于构造大的常量数值和链接。 加载立即数到高位(lui)将 20 位常量加载到寄存器的高 20 位。 接着便可以使用标准的立即指令来创建 32位常量。这样子, 仅使用 2 条 32 位 RV32I 指令,便可构造一个 32 位常量。向 PC 高位加上立即数(auipc) 让我们仅用两条指令, 便可以基于当前 PC 以任意偏移量转移控制流或者访问数据。将 auipc 中的 20 位立即数与 jalr 中 12 位立即数的组合,我们可以将执行流转移到任何 32 位 PC 相对地址。 而 auipc 加上普通加载或存储指令中的 12位立即数偏移量,使我们可以访问任何 32 位 PC 相对地址的数据。
4.2 RV32I的Load和Store操作
除了提供 32 位字(lw, sw)的加载和存储外,RV32I 支持加载有符号和无符号字节和半字(lb, lbu, lh, lhu)和存储字节和半字(sb, sh)。有符号字节和半字符号扩展为 32 位再写入目的寄存器。即使是自然数据类型更窄,低位宽数据也是被扩展后再处理,这使得后续的整数计算指令能正确处理所有的 32 位。在文本和无符号整数中常用的无符号字节和半字,在写入目标寄存器之前都被无符号扩展到 32 位。加载和存储支持的唯一寻址模式是符号扩展 12 位立即数到基地址寄存器.
4.3 RV32I的控制转移之条件分支跳转
RV32I 可以比较两个寄存器并根据比较结果上进行分支跳转。比较可以是: 相等(beq),不相等 (bne),大于等于(bge),或小于(blt) 。 最后两种比较有符号比较, RV32I 也提供相应的无符号版本比较的: bgeu 和 bltu。剩下的两个比较关系(大于和小于等于)可以通过简单地交换两个操作数,即可完成比较。 因为 x < y 表示 y > x 且 x ≥ y表示 y ≤ x。
由于 RISC-V 指令长度必须是两个字节的倍数——关于可选的双字节指令 ,请参考压缩指令——分支指令的寻址方式是 12 位的立即数乘以 2, 符号扩展它,然后将得到值加到PC 上作为分支的跳转地址。 PC 相对寻址可用于位置无关的代码, 简化了链接器和加载器的工作 。
4.4 RV32I的控制转移之无条件跳转
跳转并链接指令(jal)具有双重功能。若将下一条指令 PC + 4 的地址保存到目标寄存器中,通常是返回地址寄存器 ra,便可以用它来实现过程调用。如果使用零寄存器(x0) 替换 ra 作为目标寄存器,则可以实现无条件跳转,因为 x0 不能更改。像分支一样, jal 将其 20 位分支地址乘以 2,进行符号扩展后再添加到 PC 上,便得到了跳转地址。
跳转和链接指令的寄存器版本(jalr)同样是多用途的。它可以调用地址是动态计算出来的函数,或者也可以实现调用返回(只需 ra 作为源寄存器,零寄存器(x0)作为目的寄存器)。 Switch 和 case 语句的地址跳转,也可以使用 jalr 指令,目的寄存器设为 x0。
4.5 RV32I的杂项操作
控制状态寄存器指令 (csrrc、 csrrs、 csrrw、 csrrci、 csrrsi、 csrrwi),使我们可以轻松地访问一些程序性能计数器。 对于这些 64 位计数器, 我们一次可以读取 32位。这些计数器包括了系统时间, 时钟周期以及执行的指令数目。
在 RISC-V 指令集中, ecall 指令用于向运行时环境发出请求,例如系统调用。调试器使用 ebreak 指令将控制转移到调试环境。
fence 指令对外部可见的访存请求,如设备 I/O 和内存访问等进行串行化。外部可见指对处理器的其他核心、线程,外部设备或协处理器可见。 fence.i 指令同步指令和数据流。在执行 fence.i 指令之前,对于同一个硬件线程, RISC-V 不保证用存储指令写到内存指令区的数据可以被取指令取到。
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)