RISC-V体系结构的寄存器
通用寄存器
名字 | 别名 | 作用 |
---|---|---|
x0 |
zero |
全是0 |
x1 |
ra |
保存跳出函数后需要跳到的地址 |
x2 |
sp |
指向栈顶 |
x3 |
gp |
用于链接器松弛优化 |
x4 |
tp |
用于保存进程控制块(PCB)的地址 |
x5~x7 |
t0-t2 |
临时寄存器 |
x8-x9 |
s0-s1 |
s系列寄存器, 函数调用需要使用这些寄存器需要保存到栈中 |
x10-x17 |
a0-a7 |
a系列寄存器, 函数调用时用来传递参数和返回值 |
x18-x27 |
s2-s11 |
s系列寄存器, 函数调用需要使用这些寄存器需要保存到栈中 |
x28-x31 |
t0-t6 |
临时寄存器 |
CSR寄存器
CSR寄存器有三类:
- M模式的CSR.
- S模式的CSR.
- U模式的CSR.
程序可以通过CSR指令访问CSR寄存器.
下列行为会出发Illegal Instruction Exception:
- 访问不存在/没有实现的CSR.
- 写入具有只读属性的CSR.
- 低权限访问高权限的CSR.
U态的CSR
fflags, frm, fcsr
: 用于控制浮点相关的异常/舍入模式信息.cycle
: 存储了CPU自从复位以来, 一共运行了多少个时钟周期, 但是时钟频率可能会发生调整, 例如繁忙状态时钟频率为100MHz, 空闲状态时钟频率为10MHz, 所以根据cycle
无法判断CPU运行了多少时间.time
: 存储了CPU自从复位以来, 一共运行了多少时间, 驱动time
的一定是固定频率的时钟.instret
: 存储了CPU自从复位以来, 一共运行了多少条指令.hpmcounter3-hpmcounter31
: HPM的全称是Hardware Performance Monitor, 用于记录某些系统事件的数据, 可以通过一些额外的特权寄存器去配置记录哪些事件.
S态的CSR
寄存器 | 作用 |
---|---|
sstatus |
用来保存S态的一些状态, 例如中断使能等 |
sie |
配置S态中断使能 |
stvec |
S态发生trap后, 跳到哪里 |
scounteren |
配置是否使能U态的性能相关的寄存器 |
sscratch |
系统运行在U态下时, 它保存S态下的PCB的地址 |
sepc |
保存引起中断指令下一条指令地址/异常指令的地址 |
scause |
保存引起异常的原因 |
stval |
保存处理异常的辅助信息. |
sip |
用来配置S态哪些中断处于pending模式 |
satp |
用于地址转换/内存保护 |
scontext |
用于debug模式使用 |
M态的CSR
寄存器 | 作用 |
---|---|
mvendorid |
制造厂商ID寄存器 |
marchid |
体系结构ID寄存器 |
mimpid |
处理器实现版本的唯一ID |
mhartid |
硬件线程的ID |
mconfigptr |
数据结构的地址 |
mstatus |
M态下处理器的一些状态 |
misa |
Hart支持的ISA扩展是什么 |
medeleg |
异常委托寄存器 |
mideleg |
中断委托寄存器 |
mie |
中断使能寄存器 |
mtvec |
trap后跳到哪里 |
mcounteren |
计数器使能寄存器 |
mscratch |
处理器运行在S/U态时, 它用来保存PCB的指针 |
mepc |
保存引起中断指令下一条指令地址/异常指令的地址 |
mcause |
引起中断/异常的原因 |
mtval |
辅助处理中断/异常的信息 |
mip |
存储哪些中断处于pending |
RISC-V的基础指令
指令类型
RISC-V的指令都是32位, 可以分为6种指令类型:
- R: 寄存器运算
- I: 寄存器和立即数运算/加载
- S: 存储指令
- B: 条件跳转
- J: 无条件跳转
- U: 长立即数操作
其中, 指令编码中的opcode
和funct3/funct7
可以唯一定位一条指令.
RISC-V中的立即数都是符号扩展的.
加载指令
注意, 加载指令中的offset
是12位的有符号数, 可以表示的范围是$[-2^{11}, 2^{11} - 1]$, 也就是$[-2048, 2047]$, 也就是上下2KB的范围.
lb rd, offset(rs1)
: 有符号寻址, 加载1个Byte, 然后符号扩展.lbu rd, offset(rs1)
: 无符号寻址, 加载1个Byte, 然后零扩展.lh rd, offset(rs1)
: 有符号寻址, 加载2个Byte, 然后符号扩展.lhu rd, offset(rs1)
: 无符号寻址, 加载2个Byte, 然后零扩展.lw rd, offset(rs1)
: 有符号寻址, 加载4个Byte, 然后符号扩展.lwu rd, offset(rs1)
: 无符号寻址, 加载4个Byte, 然后零扩展.ld rd, offset(rs1)
: 有符号寻址, 加载8个Byte, 然后符号扩展.ldu rd, offset(rs1)
: 无符号寻址, 加载8个Byte, 然后零扩展.
上面的指令都是I型指令.
存储指令
存储指令的offset
和加载指令一样, 都是12位有符号数.
sb rs2, offset(rs1)
: 存储rs2
低1个字节.sh rs2, offset(rs1)
: 存储rs2
低2个字节.sw rs2, offset(rs1)
: 存储rs2
低4个字节.sd rs2, offset(rs1)
: 存储rs2
低8个字节.
上面都是S型指令.
长立即数操作指令
lui rd, imm
: 其中, 这个imm
是20位的立即数, 这个指令会把立即数先左移12位, 然后符号扩展, 然后写入rd
中.auipc rd, imm
: 这个imm
也是20位的立即数, 这个指令先将立即数左移12位, 然后符号扩展, 然后加到pc
上, 然后存储到rd
中.
无条件跳转指令
RV64I中提供了两个无条件跳转指令:
jal rd, offset
: offset是21位的有符号数, 可以表示的范围是$[-2^{20}, 2^{20} - 1]$, 也就是PC上下1MB的范围, 这个指令的意思是, 首先把当前的PC+4保存到rd
中, 然后跳转到pc + offset
中, 这个指令是J型指令jalr rd, offset(rs1)
: offset是12位的有符号立即数, 可以表示范围是PC上下2KB, 这个指令的意思是, 把当前的PC + 4保存到rd
中, 然后跳转到以rs1
为base address, 加上offset
的位置.
根据这两个指令, RISC-V衍生出了很多伪指令:
j label
: 翻译成jal x0, offset
jal label
: 翻译成jal ra, offset
jr rs
: 翻译成jalr x0, 0(rs)
ret
: 翻译成jalr x0, 0(ra)
call func
: 调用子函数func
, 返回地址保留到ra
中.# offset通过链接重定位计算得到 auipc ra, offset[31:12] + offset[11] jalr ra, offset[11:0](ra)
tail func
: 调用子函数func
, 不保存返回地址auipc x6, offset[31:12] + offset[11] jalr x6, offset[11:0](ra)
与PC相关的加载/存储伪指令
la rd, symbol
将symbol的绝对地址放到
rd
中.如果编译器采用生成位置无关代码的选项(
-fpic
), 那么la
指令会被翻译成:
auipc rd, offset[31:12] + offset[11]
# GOT存储在内存中, 用lw/ld获取GOT的内容
l{w|d} rd, offset[11:0](rd)
其中, offset = symbol - pc
, 这个symbol
的地址链接器会从全局偏移量表(Global Offset Table, GOT, 存储在内存中)中获取, 一般用在动态库中.
- 如果编译器没有采用生成位置无关代码的选项, 那么
la
指令会被翻译成:
auipc rd, offset[31:12] + offset[11]
addi rd, rd, offset[11:0](rd)
其中, symbol
的地址就会由链接器直接计算.