CPU-RISC-V的基础知识以及一些基本指令

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: 长立即数操作

其中, 指令编码中的opcodefunct3/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的地址就会由链接器直接计算.