一起学RISC-V汇编第3讲之寄存器
寄存器是处理器中最常用的处理单元,RISC-V指令的操作数除了立即数就是寄存器。
RISC-V指令集包含了多种不同类型的寄存器,用于不同目的和功能:
对于rv32imafd架构而言,包含如下寄存器:
- 通用寄存器:32个通用整数寄存器,分别标记为x0-x31,如果是fd扩展,还有32 个独立的浮点寄存器,分别标记为f0-f31
- 控制与状态寄存器CSR:用来记录或配置一些CPU的运行状态
- 独立的程序计数器PC
1 通用寄存器
1.1 通用整数寄存器
RISC-V 架构提供32个通用寄存器x0-x31,其中x0 有些特殊,x0 寄存器被设置为硬件连线的常数0,读恒为0,写无效,这个寄存器在一些地方很有作用,因为程序运行中常数0的使用频率非常高,所以专门用一个寄存器来存放常数0,并没有浪费寄存器数量,并且使得编译器工作更加简便,这一点也是RISC-V架构优雅性的体现,比如后面讲到的伪指令。
下图来自riscv spec,附带PC寄存器(程序计数器,用来存储指向下一条指令的地址),寄存器的长度XLEN与架构有关(对于rv32:XLEN=32,对于rv64:XLEN=64)。
对于这些通用寄存器,除了x0外(x0 寄存器的值读恒为0,写无效),其它寄存器都本质上是等价的。但在实际使用过程中,我们约定了这些寄存器的用法,即ABI规范,见下表,我们将x0-x31以及f0-f31 按照常用功能分别取了别名,附带有简单的描述,在汇编中使用原名和别名是等价的,比如:在汇编中我们使用x1或使用ra,编译器都是认识的,建议写汇编时使用别名,这样方便我们阅读代码,关于表中ABI的描述以及Caller Callee的概念见后续章节。
将上表重新抄录如下:
寄存器 | ABI 名称 | 描述 | Saver |
---|---|---|---|
x0 | zero | 零值 | - |
x1 | ra | 返回地址 | Caller |
x2 | sp | 堆栈指针 | Callee |
x3 | gp | 全局指针 | - |
x4 | tp | 线程指针 | - |
x5 | t0 | 临时/备用链接寄存器 | Caller |
x6-x7 | t1-t2 | 临时寄存器 | Caller |
x8 | s0/fp | 保存寄存器/帧指针 | Callee |
x9 | s1/gp | 保存寄存器/全局指针 | Callee |
x10-x11 | a0-a1 | 函数参数/返回值 | Caller |
x12-x17 | a2-a7 | 函数参数 | Caller |
x18-x27 | s2-s11 | 保存寄存器 | Callee |
x28-x31 | t3-t6 | 临时寄存器 | Caller |
1.2 通用浮点寄存器
对于带有fpu的架构,如rv32imafd 或 rv64imafd(F表示单精度浮点,D表示双精度浮点,D扩展依赖于F扩展) 架构处理器而言,它们有32个浮点寄存器,见下图,浮点寄存器长度FLEN与"F" 或 “D”扩展有关,对于“F扩展”,无论是rv32imaf还是 rv64imaf架构,FLEN = 32,而对于“D扩展”,无论是rv32imafd还是 rv64imafd,FLEN=64。这一点与通用寄存器是不同的。
同样的,在实际使用过程中,我们约定了这些浮点寄存器的用法,见下表。
寄存器 | ABI 名称 | 描述 | Saver |
---|---|---|---|
f0-f7 | ft0-ft7 | 浮点 临时存储 | Caller |
f8-f9 | fs0-fs1 | 浮点 保存的寄存器 | Callee |
f10-f11 | fa0-fa1 | 浮点 函数参数/返回值 | Caller |
f12-f17 | fa2-fa7 | 浮点 函数参数 | Caller |
f18-f27 | fs2-fs11 | 浮点 保存的寄存器 | Callee |
f28-f31 | ft8-ft11 | 浮点 临时存储 | Caller |
查看XLEN与FLEN:
如下,我们可以查看riscv 编译器定义的xlen与flen宏,来了解XLEN与FLEN的差异,简单来说:xlen取决于是rv32还是rv64,flen取决于是F扩展还是D扩展。
$ riscv64-unknown-elf-gcc -march=rv32imafc -mabi=ilp32f -dM -E - < /dev/null | grep len
#define __riscv_xlen 32
#define __riscv_flen 32
$ riscv64-unknown-elf-gcc -march=rv32imafdc -mabi=ilp32d -dM -E - < /dev/null | grep len
#define __riscv_xlen 32
#define __riscv_flen 64
$ riscv64-unknown-elf-gcc -march=rv64imafc -mabi=lp64f -dM -E - < /dev/null | grep len
#define __riscv_xlen 64
#define __riscv_flen 32
$ riscv64-unknown-elf-gcc -march=rv64imafdc -mabi=lp64d -dM -E - < /dev/null | grep len
#define __riscv_xlen 64
#define __riscv_flen 64
2 控制状态寄存器CSR
RISC-V 定义了控制和状态寄存器CSR (Control and Status Register),用于配置或记录处理器的运行状态, RISC-V 特权指令就是通过控制CSR来实现的。CSR 使用专有的 12 位地址空间,因此理论上能够支持最多 4096 个 CSR。但实际上,这个地址空间大部分是空的,RISC-V 手册中实际只定义了数十个 CSR,访问不存在的 CSR 将触发无效指令异常。
CSR寄存器的种类:
RISC-V 的 CSR 寄存器非常多,在机器模式(Machine Mode)下这些存储器主要包括以下六类:
- 处理器信息相关:如:MHARTID(Machine Hartid),表示机器模式信息寄存器组
- 中断配置相关:如:MIE(Machine Interrupt Enable), MTVEC(Machine Trap Vector)等
- 异常处理相关:如:MCAUSE(Machine Cause), MTVAL(Machine Trap Value),MSTATUS(Machine Status)等
- 存储器保护相关:设置不同地址空间的存储器的访问属性,例如可读可写可执行等等。
- 性能统计相关和调试接口相关,如MCYCLE(Machine Cycle)等。
CSR寄存器列表如下:
寄存器名称 | 全称 | 作用 |
---|---|---|
处理器信息相关寄存器 | ||
misa | Machine ISA Register | 机器模式指令集架构寄存器,可以查看处理器的位宽,以及所支持的扩展 |
mvendorid | Machine Vendor ID Register | 机器模式供应商编号寄存器 |
marchid | Machine Architecture ID Register | 机器模式架构编号寄存器 |
mimpid | Machine Implementation ID Register | 机器模式硬件实现编号寄存器 |
mhartid | Hart ID Register | Hart编号寄存器 |
中断配置相关寄存器 | ||
mie | Machine Interrupt Enable Registers | 机器模式中断使能寄存器,维护处理器的中断使能状态 |
mip | Machine Interrupt Pending Registers | 机器模式中断等待寄存器,记录当前的中断请求 |
mtvec | Machine Trap-Vector Base-Address Register | 机器模式异常入口基地址寄存器,低2位可以设置为直接模式或向量模式 |
异常处理相关寄存器 | ||
mcause | Machine Cause Register | 机器模式异常原因寄存器 |
mtval | Machine Trap Value Register | 又名mbadaddr, 机器模式异常值寄存器,存放当前自陷相关的额外信息,如地址异常的故障地址、非法指令异常的指令,发生其他异常时其值为 0 |
mstatus | Machine Status Registers | 机器模式状态寄存器 |
mepc | Machine Exception Program Counter | 机器模式异常PC寄存器,指向发生异常的指令 |
性能统计相关 | ||
mtime | Machine Timer Registers | 机器模式计时寄存器 |
mcycle | Cycle counter | cycle 计数器(rv32 寄存器值为32位,需配合mcycleh使用,rv64 寄存器值为64位) |
mcycleh | Cycle counter | cycle 计数器高32位(rv64 无) |
CSR寄存器的读写
访问CSR寄存器需要特别的CSR指令,CSR 指令在 RISC-V 的 Zicsr 扩展模块中定义。
3 程序计数器PC
有些处理器架构中用通用寄存器来存放PC,这样使用普通的指令即可改变PC值,从而引起跳转,这样并不是一个很好的做法,RISC-V中,定义了独立的程序计数器,可以通过专门的指令(AUIPC)来间接获取PC值。
参考:
- 《riscv-spec-20191213.pdf》
- 3、寄存器 — RISC-V RT-Thread 编程指南 0.0.1 文档 (riscv-rtthread-programming-manual.readthedocs.io)