写在前面:本篇内容来自胡振波先生所著的《手把手教你设计CPU——RISC-V处理器》,并附加个人学习心得,纯用以个人学习笔记。强烈推荐想要系统学习RISC-V处理器的同学们去看一下这本书,深入浅出、干货满满。
目录
RISC-V架构设计理念
首先,整体设计简洁、安全可靠。在RISC-V基金会网站上可以下载RISC-V架构文档,包括指令集文档和特权架构文档,两个文档页数都在100页左右,内容短小精悍。网址见专栏上一篇文章。
其次,架构模块化组织,通过统一架构满足不同应用。这是x86和ARM架构不具备的,如ARM架构包含A、R、M三个系列,对应应用操作系统(Application)、实时(Real-Time)、嵌入式(Embedded)三个领域,彼此不兼容。而对于RISC-V架构,如在小面积低功耗应用场景下,可以选择RV32IC组合的指令集,并仅使用机器模式;在高性能应用场景下,可以选择RV32IMFDC指令集,使用机器模式和用户模式。
最后,指令数目简介。基本的RISC-V指令共40多条,加上模块化扩展指令也只有几十条。
RISC-V指令集简介
这一章主要是与早期/当下主流RISC架构比较,指出RISC-V架构的部分不同之处,突出RISC-V在硬件设计与开销方面的优势。
1. 指令子集:简洁、模块化
RISC-V指令集被分为多个模块,每个模块用一个字母表示,如:最基本且强制实现的是字母“I”表示的基本整数指令子集,其他均为可选模块。常见指令子集如下:
基本指令集 | 指令数 | 描述 |
RV32I | 47 | 32位地址空间与整数指令,支持32个通用整数寄存器 |
RV32E | 47 | RV32I子集,支持16个通用整数寄存器 |
RV64I | 59 | 64位地址空间、整数指令、部分32位整数指令 |
RV128I | 71 | 128位地址空间、整数指令、部分64位/32位指令 |
扩展指令集 | 指令数 | 描述 |
M | 8 | 整数乘法、除法指令 |
A | 11 | 存储器原子操作指令、Load-Reserved/Store-Conditonal指令 |
F | 26 | 单精度(32bit)浮点指令 |
D | 26 | 双精度(64bit)浮点指令,必须支持F指令 |
C | 46 | 压缩指令,长度为16位的指令 |
最基本的整数指令子集(I):支持加减法、移位、按位逻辑、比较等操作,通过合理组合或函数库方式可完成大部分软件操作。
整数乘除法指令子集(M):支持有/无符号的乘除法。
乘法操作 | 除法操作 |
支持2个32位的整数相乘,得到1个64位结果 | 支持2个32位整数相除,得到1个32位的商和1个32位的余数 |
单精度浮点指令子集(F)与双精度浮点指令子集(D):支持浮点加减法、乘除法、乘累加、开平方根、比较等操作,同时支持整数与浮点、单精度浮点与双精度浮点格式转换。
与RISC架构在运算指令出错时可能产生的上溢、下溢、非规格化浮点数、除零等问题不同,RISC-V架构对任何运算错误无异常,而是产生特殊默认值,同时设置相关状态寄存器的状态位,使软件通过其他方法查错。
注:书中附录有详细的运算指令描述,感兴趣的朋友自行查阅。
通用组合“IMAFD”用字母G表示:当需要普通的应用时,可选用RV32G(RV32IMAFD)、RV64G(RV64IMAFD)架构。
可选的“压缩”指令子集(C):指令编码长度为16bit,普通非压缩指令长度为32bit。这一模块可以有效提高代码密度。(后文有具体介绍)
“嵌入式”架构(E):仅支持16个通用整数寄存器,普通非“嵌入式”架构则支持32个。这一架构可以有效减少面积与功耗。当需要小面积、低功耗应用时,可选用RVxxEC架构。
2. 通用寄存器组:可配置
RV32指32位架构,每个通用寄存器宽32bit;RV64指64位架构,每个通用寄存器宽64bit。
整数通用寄存器组:包括32个I架构或者16个E架构整数通用寄存器。其中,整数寄存器0被预留为常数0,其他31个I架构或者15个E架构为普通整数通用寄存器。
浮点模块(F/D):需要独立的浮点寄存器组,包括32个通用浮点寄存器。仅使用F模块时,各浮点寄存器宽32bit;只要使用了D模块,各浮点寄存器宽64bit。
3. 指令编码:规整
流水线总是希望尽快读取通用寄存器。RISC-V的指令集编码因此非常规整,指令所需的通用寄存器索引放在固定位置,使得指令译码器能便捷地译码出索引,从而进行读取。
注:书中附录有各指令编码的具体格式,感兴趣的朋友可自行参考。
4. 存储器访问指令:简洁
RISC-V架构使用专用读(Load)指令、写(Store)指令访问存储器,其他指令无法访问。存储器访问的基本单位是字节,读/写操作支持1个字节(8bit)、半字(16bit)、单字(32bit),64位架构额外支持1个双字(64bit)。
存储器读/写操作时,一般推荐地址对齐指令,但也支持地址非对齐,不支持地址自增自减模式。处理器即可以选择硬件支持,也可以选择软件支持。RISC-V的存储器仅支持小端格式。RISC-V架构采用松散存储器模型,对于访问不同地址的读/写操作顺序无要求,除非使用存储器屏障指令。
5. 分支跳转指令:高效
RISC-V架构有两条无条件跳转指令:jal指令与jalr指令。
jal指令 (跳转链接指令) | jalr指令 (跳转链接寄存器指令) |
用于调用进入子程序,同时将子程序返回地址存在链接寄存器(一个通用整数寄存器) | 将jal指令保存的链接寄存器作为jalr指令的基地址寄存器,用于从子程序返回 |
RISC-V架构有六条带条件跳转指令。与普通运算指令一样,此类指令需要使用两个整数操作数并比较它们,满足条件时则跳转,比较、跳转两个操作放在一句指令里。这相比需要将比较和跳转指令分开的RISC架构方便许多。
RISC-V架构有默认的静态分支预测机构:向后跳转的条件指令,预测为“跳”;向前跳转的条件指令,预测为“不跳”。这要求编译器生成相应的汇编代码,使低端CPU获得较好性能。此外,RISC-V架构定义了条件跳转指令跳转目标的偏移量为有符号数,且符号位在固定位置。若硬件译码器判断该符号位为“1”(负数),则向后跳转,预测为跳;为“0”(正数),则向前跳转,预测为不跳。
6. 子程序调用:简洁
一般RISC架构中,有“保存现场”、“恢复现场”两个过程。
保存现场 | 恢复现场 |
进入子函数后,使用存储器写(Store)将当前上下文(通用寄存器值)保存到系统存储器的堆栈区内 | 退出子程序时,使用存储器读(Load)将保存的上下文从系统存储器的堆栈区读出 |
这两个过程是由底层编译器生成的指令完成,高层语言(C++等)直接使用子函数调用即可。
在RISC-V架构中,不采用“一次写多个寄存器指令”、“一次读多个寄存器指令”,而使用公用程序库实现多次保存现场和恢复现场的操作。这一方法不仅能节省调用指令数,而且硬件设计更为简单。
7. 条件码执行:无
早期RISC架构中,会用指令前几位编码条件,当条件为真时才执行这一指令。这种带条件码的指令使得短小分支块省去了跳转,但会增加硬件开销。
在RISC-V架构中,不采用带条件码的指令,对每一个条件判断都使用普通的分支跳转,使得硬件开销减小。
8. 分支延迟槽:无
早期RISC架构中,存在“分支延迟槽(Delay Slot)”,指在每一条分支指令后紧跟若干条不受分支跳转影响的指令,即不论分支是否跳转,这几条指令一定会执行。这样做能解决早期没有高级动态分支预测器的问题,但大大增加硬件设计难度。
在RISC-V架构中,有高效的分支预测电路,因此不采用分支延迟槽。
9. 零开销硬件循环指令:无
很多RISC架构中,支持零开销硬件循环指令,指通过硬件直接设置某些循环次数寄存器,让程序自动循环,循环一次相关寄存器次数减1,直到相关寄存器次数变为0退出循环。这种指令的存在是为了解决常见for循环编译后产生多条加法指令、分支跳转指令的代码量较大问题,但会导致硬件设计难度增加。
在RISC-V架构中,为了简化硬件设计,不采用此类指令。
10. 压缩指令子集
RISC-V架构的基本整数指令子集(I)规定指令长为32位,不适用于对代码体积要求较小时。压缩指令子集(C),也可以用RVC表示,指令长规定为16位,两种长度的指令可以写在一起。
压缩策略:将部分最常用的32位指令中的信息压缩重排,每一条16位压缩指令都有一一对应的32位指令。如一条指令使用两个同样操作数索引时,可以省去一个索引。
11. 特权模式
RISC-V架构有3种工作模式(特权模式):
Machine Mode (机器模式,M Mode) | 必选模式 |
Supervisor Mode (监督模式,S Mode) | 可选模式 |
User Mode (用户模式,U Mode) | 可选模式 |
通过不同模式组合,可以实现不同系统。
注:书中附录有详细的工作模式描述,感兴趣的朋友自行查阅。
RISC-V架构同时支持不同的存储器地址管理机制,包括物理地址和虚拟地址管理两方面。
注:书中附录有详细的存储器地址管理机制描述,感兴趣的朋友自行查阅。
12. CSR寄存器
RISC-V架构定义了一些控制和状态寄存器(CSR),用于配置/记录运行状态。CSR寄存器位于处理器核内部,使用独立的地址编码空间,与存储器寻址的地址区间无关。因此,访问CSR寄存器需要专门的CSR指令,如CSRRW、CSRRS、CSRRC等。
注:书中附录有详细的CSR寄存器及其指令描述,感兴趣的朋友自行查阅。
13. 中断和异常
注:书中附录有详细的中断和异常机制描述,感兴趣的朋友自行查阅。
14. 自定制指令扩展
RISC-V架构预留了大量指令编码空间,支持第三方自定义扩展自己的指令子集,同时定义了4条Custom指令供用户直接使用。每条Custom指令预留了几个bit位的子编码空间,因此,用户可使用这4条指令扩展出几十条自定义指令。
注:这是本人毕设的关键部分,后续将详细介绍。
RISC-V软件工具链
RISC-V软件工具链由开源社区维护,详见RISC-V基金会官网——RISC-V Tools。在GitHub上搜索riscv-tools(一个宏项目),包含了RISC-V相关工具链、仿真器、测试套件等,此处简单介绍:
基本工具 | 作用 |
riscv-fesvr | 实现上位机和CPU通信的库 |
riscv-pk | 提供RISC-V的程序运行环境,提供最简单的bootloader(初始化) |
riscv-isa-sim | 基于C/C++的指令集模拟器,即“Spike” |
三种基本工具协作,即可在Spike模拟器上运行一个完整程序。
riscv-gnu-toolchain(GNU工具链) | |
工具名称 | 作用 |
riscv-gcc | GCC编译器 |
riscv-binutils-gdb | 二进制工具(链接器、汇编器等)、GDB调试工具等 |
riscv-glibc | GNU C标准库实现 |
其他 | 作用 |
riscv-llvm | 基于LLVM编译器的框架 |
riscv-openocd | 基于OpenOCD的RISC-V调试器 |
riscv-opcodes | 一个RISC-V操作码信息转换脚本 |
riscv-tests | 一组RISC-V指令集测试用例 |
riscv-qemu | 一个支持RISC-V的QEMU模拟器 |
其他工具自行查阅。除了在GitHub上下载相关源代码编译外,还可以在网上下载编译好的GNU工具链和Windows IDE开发工具。
注:书中在此处介绍了RISC-V架构与其他两个代表性开放架构(OpenRISC、SPARC)的不同之处,感兴趣的朋友可以自己去看一下。