RISC-V
编译工具链
-
工具链仓库
# 仓库地址
https://github.com/riscv-collab/riscv-gnu-toolchain
git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git
-
显式添加默认的平台支持并附加特定的平台
./configure --prefix=/opt/riscv \
--enable-multilib \
--with-multilib-generator="rv64gc-lp64d--;rv64imac-lp64--;rv32gc-ilp32d--;rv32imac-ilp32--;rv32ima-ilp32--"
-
验证支持的多库组合
riscv64-unknown-elf-gcc --print-multi-lib
RISC-V主要内容
-
ISA命名规范
- RV用于标识RISC-V 。
- [###]: {32, 64, 128} 用于标识处理器的字宽(寄存器的宽度)。
- [abc...xyz]: 标识该处理器支持的指令集模块集合。
-
模块化的ISA
- RISC-V ISA = 1个基本整数指令集 + 多个可选的扩展指令集。
-
通用寄存器
- 定义了32个通用寄存器以及一个PC。
- 寄存器宽度由ISA指定。
- 每个寄存器具体编程时有特定的用途以及各自的别名。由RISC-V ABI定义。
-
Hart
- HARdware Thread(相当于一个基本的CPU单元)
-
特权级别
- 机器模式(M Machine Mode):最高权限,用于启动、配置硬件和异常处理。
- 监督模式(S Supervisor Mode):运行操作系统内核。
- 用户模式(U User/Application Mode):运行应用程序,权限受限。
(可选H Mode:支持虚拟化扩展的超监模式。)
-
Control and Status Registers(CSR)
- 不同的特权级别下时分别对应各自的一套Register。
- 高级别的特权级别下可以访问低级别的CSR。
- RISC-V 定义了专门用于操作CSR的指令。
- RISC-V 定义了特定的指令可以用于在不同特权级别之间进行切换。
-
内存管理和保护(物理内存保护、虚拟内存)
- 物理内存保护(Physical Memory Protection, PMP)
- 虚拟内存
RISC-V 的虚拟内存(Virtual Memory)既依赖硬件支持,也依赖软件实现。其 ISA(指令集架构)提供了明确的硬件机制,包括特权模式、控制状态寄存器(CSR)和地址转换机制,而操作系统(如 Linux)则负责页表管理、缺页处理等软件层面的实现。
-
异常和中断
risc-v的大多数异常会中断返回后会再执行一次异常指令。而中断后本身返回后会执行下一条语句。
主机字节序
- 二进制码: 左高右底
- 内存布局: 上高下底
- 大端序: 高位二进制码存入底地址
- 小端序: 高位二进制码存入高地址
- risc-v使用小端序
- 注意:字节序是否反转以字节为单位,一个字节8位,8位内部不反转
risc-v 的6种指令格式
- R-Type: 用于寄存器间的算术逻辑运算(如
add x1, x2, x3
)。
- I-Type: 用于立即数操作、加载指令和跳转(如
addi x1, x2, 5
、lw x1, 4(x2)
)。
- S-Type: 专用于存储指令到内存(如
sw x1, 8(x2)
)。
- B-Type: 用于条件分支跳转(如
beq x1, x2, label
)。
- U-Type: 用于将长立即数装入寄存器高位(如
lui x1, 0x12345
)。
- J-Type: 用于无条件长跳转(如
jal x1, far_away_label
)。
汇编相关命令
- 编写汇编
nvim test.s
- 编译
[rgcc] -march=[rv32im] -mabi=ilp32 -c test.s -o test.o
- 链接
[rld] -m elf32lriscv -o test.elf test.o
- 查看段头信息
[rreadelf] -S test.elf
- 查看 .text 段原始内容
[rreadelf] -x .text test.elf
- 反汇编所有段
[robjdump] -D test.elf
- 反汇编代码段
[robjdump] -d test.elf
- 展开所有伪指令
[robjdump] -d -M no-aliases test.elf
模拟器运行与调试
- 模拟器运行程序。启动时暂停CPU等待GDB连接,默认1234端口。
qemu-system-riscv32 -nographic -smp 1 -machine virt -bios none -kernel kernel -S -s
-S 启动时暂停CPU,等待GDB的 continue 命令才开始执行
-s 是 -gdb tcp:127.0.0.1:1234 的缩写
- 运行gdb
[rgdb] test.elf
- gdb连接远程1234端口
target remote : 1234
monitor quit
让QEMU立即停止运行
- gdb运行参数
-q 安静模式,不显示介绍信息
, -x <file> 执行指定文件中的 GDB 命令
示例代码
-
makefile
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32im -mabi=ilp32 -g -Wall
CC = ${CROSS_COMPILE}gcc
OBJDUMP = ${CROSS_COMPILE}objdump
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none
GDBINIT = gdbinit
GDB = ${CROSS_COMPILE}gdb
EXEC = test
SRC = ${EXEC}.s
all:
@${CC} ${CFLAGS} ${SRC} -Ttext=0x80000000 -o ${EXEC}.elf
run: all
@${QEMU} ${QFLAGS} -kernel ./${EXEC}.elf
debug: all
@${QEMU} ${QFLAGS} -kernel ${EXEC}.elf -s -S &
@${GDB} ${EXEC}.elf -q -x ${GDBINIT}
clean:
rm -rf *.o *.bin *.elf
-
gdbinit
# /z 十六进制显示寄存器中的值
# 每一步都显示
display/z $x5
display/z $x6
display/z $x7
# 自动反汇编,每一步都反汇编
set disassemble-next-line on
b _start
target remote : 1234
c
RISC-V 常用指令和伪指令系统分类总结
算术运算指令
指令 |
格式 |
功能描述 |
add |
add rd, rs1, rs2 |
寄存器加法 (rd = rs1 + rs2) |
sub |
sub rd, rs1, rs2 |
寄存器减法 (rd = rs1 - rs2) |
addi |
addi rd, rs1, imm |
立即数加法 (rd = rs1 + sign-extended imm) |
mul |
mul rd, rs1, rs2 |
有符号乘法 (RV32M/RV64M) |
div |
div rd, rs1, rs2 |
有符号除法 (RV32M/RV64M) |
指令 |
格式 |
功能描述 |
and |
and rd, rs1, rs2 |
按位与 |
or |
or rd, rs1, rs2 |
按位或 |
xor |
xor rd, rs1, rs2 |
按位异或 |
andi |
andi rd, rs1, imm |
立即数按位与 |
ori |
ori rd, rs1, imm |
立即数按位或 |
xori |
xori rd, rs1, imm |
立即数按位异或 |
指令 |
格式 |
功能描述 |
sll |
sll rd, rs1, rs2 |
逻辑左移 (rd = rs1 << rs2[4:0]) |
srl |
srl rd, rs1, rs2 |
逻辑右移 |
sra |
sra rd, rs1, rs2 |
算术右移 |
slli |
slli rd, rs1, shamt |
立即数逻辑左移 |
srli |
srli rd, rs1, shamt |
立即数逻辑右移 |
srai |
srai rd, rs1, shamt |
立即数算术右移 |
数据传输指令
指令 |
格式 |
功能描述 |
lb |
lb rd, offset(rs1) |
加载字节 (符号扩展) |
lh |
lh rd, offset(rs1) |
加载半字 (符号扩展) |
lw |
lw rd, offset(rs1) |
加载字 (32位) |
ld |
ld rd, offset(rs1) |
加载双字 (RV64) |
lbu |
lbu rd, offset(rs1) |
加载无符号字节 |
lhu |
lhu rd, offset(rs1) |
加载无符号半字 |
lwu |
lwu rd, offset(rs1) |
加载无符号字 (RV64) |
指令 |
格式 |
功能描述 |
sb |
sb rs2, offset(rs1) |
存储字节 |
sh |
sh rs2, offset(rs1) |
存储半字 |
sw |
sw rs2, offset(rs1) |
存储字 |
sd |
sd rs2, offset(rs1) |
存储双字 (RV64) |
控制流指令
指令 |
格式 |
功能描述 |
jal |
jal rd, offset |
跳转并链接 (rd = pc+4) |
jalr |
jalr rd, offset(rs1) |
间接跳转并链接 |
j |
j offset |
直接跳转 (伪指令) |
指令 |
格式 |
功能描述 |
beq |
beq rs1, rs2, offset |
相等时分支 |
bne |
bne rs1, rs2, offset |
不等时分支 |
blt |
blt rs1, rs2, offset |
有符号小于时分支 |
bge |
bge rs1, rs2, offset |
有符号大于等于时分支 |
bltu |
bltu rs1, rs2, offset |
无符号小于时分支 |
bgeu |
bgeu rs1, rs2, offset |
无符号大于等于时分支 |
伪指令
伪指令 |
实际指令 |
功能描述 |
nop |
addi x0, x0, 0 |
空操作 |
mv rd, rs |
addi rd, rs, 0 |
寄存器复制 |
not rd, rs |
xori rd, rs, -1 |
按位取反 |
neg rd, rs |
sub rd, x0, rs |
取负值 |
j offset |
jal x0, offset |
无条件跳转 |
ret |
jalr x0, 0(x1) |
从子程序返回 |
call offset |
auipc x1, offset[31:12] jalr x1, offset[11:0](x1) |
调用远过程 |
tail offset |
auipc x0, offset[31:12] jalr x0, offset[11:0](x0) |
尾调用 |
伪指令 |
实际指令 |
功能描述 |
la rd, symbol |
auipc rd, symbol[31:12] addi rd, rd, symbol[11:0] |
加载绝对地址 |
li rd, imm |
根据立即数大小选择不同指令序列 |
加载立即数 |
系统指令
指令 |
格式 |
功能描述 |
csrrw |
csrrw rd, csr, rs1 |
CSR读后写 |
csrrs |
csrrs rd, csr, rs1 |
CSR读后置位 |
csrrc |
csrrc rd, csr, rs1 |
CSR读后清除 |
csrr |
csrr rd, csr |
CSR读 (伪指令 = csrrs rd, csr, x0) |
csrw |
csrw csr, rs1 |
CSR写 (伪指令 = csrrw x0, csr, rs1) |
指令 |
格式 |
功能描述 |
ecall |
ecall |
环境调用 |
ebreak |
ebreak |
断点 |
wfi |
wfi |
等待中断 |
mret |
mret |
从机器模式异常返回 |
sret |
sret |
从监管模式异常返回 |
原子操作指令 (A扩展)
指令 |
格式 |
功能描述 |
lr.w |
lr.w rd, (rs1) |
加载保留 (32位) |
sc.w |
sc.w rd, rs2, (rs1) |
条件存储 (32位) |
amoswap.w |
amoswap.w rd, rs2, (rs1) |
原子交换 (32位) |
amoadd.w |
amoadd.w rd, rs2, (rs1) |
原子加 (32位) |
amoand.w |
amoand.w rd, rs2, (rs1) |
原子与 (32位) |
浮点指令 (F/D扩展)
指令 |
格式 |
功能描述 |
fadd.s |
fadd.s rd, rs1, rs2 |
单精度浮点加 |
fsub.s |
fsub.s rd, rs1, rs2 |
单精度浮点减 |
fmul.s |
fmul.s rd, rs1, rs2 |
单精度浮点乘 |
fdiv.s |
fdiv.s rd, rs1, rs2 |
单精度浮点除 |
fsqrt.s |
fsqrt.s rd, rs1 |
单精度平方根 |
指令 |
格式 |
功能描述 |
fcvt.w.s |
fcvt.w.s rd, rs1 |
单精度转有符号字 |
fcvt.s.w |
fcvt.s.w rd, rs1 |
有符号字转单精度 |
RISC-V 32位寄存器分类说明
基础寄存器(x0-x31)
寄存器 |
别名 |
用途 |
调用约定 |
说明 |
x0 |
zero |
硬编码零 |
- |
读取始终为0,写入无效 |
x1 |
ra |
返回地址 |
Caller |
存储函数返回地址 |
x2 |
sp |
栈指针 |
Callee |
当前栈指针 |
x3 |
gp |
全局指针 |
- |
指向全局数据区 |
x4 |
tp |
线程指针 |
- |
线程局部存储 |
x5 |
t0 |
临时寄存器 |
Caller |
临时数据 |
x6 |
t1 |
临时寄存器 |
Caller |
临时数据 |
x7 |
t2 |
临时寄存器 |
Caller |
临时数据 |
x8 |
s0/fp |
保存寄存器/帧指针 |
Callee |
函数帧指针 |
x9 |
s1 |
保存寄存器 |
Callee |
跨调用保存 |
x10 |
a0 |
函数参数/返回值 |
Caller |
第一个参数/返回值 |
x11 |
a1 |
函数参数/返回值 |
Caller |
第二个参数/返回值 |
x12 |
a2 |
函数参数 |
Caller |
第三个参数 |
x13 |
a3 |
函数参数 |
Caller |
第四个参数 |
x14 |
a4 |
函数参数 |
Caller |
第五个参数 |
x15 |
a5 |
函数参数 |
Caller |
第六个参数 |
x16 |
a6 |
函数参数 |
Caller |
第七个参数 |
x17 |
a7 |
函数参数 |
Caller |
第八个参数 |
x18 |
s2 |
保存寄存器 |
Callee |
跨调用保存 |
x19 |
s3 |
保存寄存器 |
Callee |
跨调用保存 |
x20 |
s4 |
保存寄存器 |
Callee |
跨调用保存 |
x21 |
s5 |
保存寄存器 |
Callee |
跨调用保存 |
x22 |
s6 |
保存寄存器 |
Callee |
跨调用保存 |
x23 |
s7 |
保存寄存器 |
Callee |
跨调用保存 |
x24 |
s8 |
保存寄存器 |
Callee |
跨调用保存 |
x25 |
s9 |
保存寄存器 |
Callee |
跨调用保存 |
x26 |
s10 |
保存寄存器 |
Callee |
跨调用保存 |
x27 |
s11 |
保存寄存器 |
Callee |
跨调用保存 |
x28 |
t3 |
临时寄存器 |
Caller |
临时数据 |
x29 |
t4 |
临时寄存器 |
Caller |
临时数据 |
x30 |
t5 |
临时寄存器 |
Caller |
临时数据 |
x31 |
t6 |
临时寄存器 |
Caller |
临时数据 |
按功能分类
1. 特殊功能寄存器
类别 |
寄存器 |
别名 |
主要用途 |
控制寄存器 |
x0 |
zero |
常数零值 |
栈指针 |
x2 |
sp |
栈操作 |
链接寄存器 |
x1 |
ra |
函数返回 |
全局指针 |
x3 |
gp |
全局数据访问 |
线程指针 |
x4 |
tp |
线程局部存储 |
2. 参数传递寄存器
用途 |
寄存器 |
数量 |
说明 |
参数传递 |
a0-a7 (x10-x17) |
8个 |
函数调用参数 |
返回值 |
a0-a1 (x10-x11) |
2个 |
函数返回值 |
3. 临时寄存器(调用者保存)
类型 |
寄存器 |
数量 |
说明 |
临时寄存器 |
t0-t2 (x5-x7) |
3个 |
短期临时数据 |
临时寄存器 |
t3-t6 (x28-x31) |
4个 |
短期临时数据 |
小计 |
t0-t6 |
7个 |
调用者负责保存 |
4. 保存寄存器(被调用者保存)
类型 |
寄存器 |
数量 |
说明 |
帧指针 |
s0/fp (x8) |
1个 |
栈帧指针 |
保存寄存器 |
s1-s11 (x9-x27) |
11个 |
跨调用保持 |
小计 |
s0-s11 |
12个 |
被调用者负责保存 |
调用约定总结
Caller-Saved(调用者保存)
- 临时寄存器: t0-t6 (x5-x7, x28-x31)
- 参数寄存器: a0-a7 (x10-x17)
- 返回地址: ra (x1)
Callee-Saved(被调用者保存)
- 保存寄存器: s0-s11 (x8-x27)
- 栈指针: sp (x2) - 通常由硬件维护
汇编代码中的使用示例
# 函数调用示例
func:
addi sp, sp, -16 # 分配栈空间
sw ra, 12(sp) # 保存返回地址 (Caller-saved, 但被调用者保存)
sw s0, 8(sp) # 保存s0 (Callee-saved)
mv s0, a0 # 保存参数到s0
li t0, 100 # 使用临时寄存器t0
# 函数体...
lw s0, 8(sp) # 恢复s0
lw ra, 12(sp) # 恢复返回地址
addi sp, sp, 16 # 释放栈空间
ret # 返回到ra地址
重要说明
- RV32与RV64寄存器:寄存器编号和别名相同,只是位宽不同(32位 vs 64位)
- 压缩指令:某些指令只能访问x8-x15寄存器(更短的编码)
- ABI名称:在汇编代码中建议使用ABI名称(如
a0
而不是x10
)提高可读性
CSR(控制状态寄存器)分类表格
CSR 地址空间布局
地址范围 |
特权级 |
描述 |
示例 |
0x000-0x0FF |
机器模式 |
机器信息寄存器 |
mhartid , mstatus |
0x300-0x3FF |
机器模式 |
机器模式控制和状态 |
mtvec , mepc |
0x700-0x7FF |
机器模式 |
机器模式计数器 |
mcycle , minstret |
0x800-0x8FF |
监管模式 |
监管模式CSR |
stvec , sepc |
0xC00-0xCFF |
用户模式 |
用户模式CSR |
cycle , time |
0x1000-0x1FFF |
自定义 |
厂商自定义 |
实现特定功能 |
重要的机器模式(M-mode)CSR
CSR名称 |
地址 |
读写权限 |
功能描述 |
mhartid |
0xF14 |
RO |
硬件线程ID(核心编号) |
mstatus |
0x300 |
RW |
机器模式状态寄存器 |
misa |
0x301 |
RO |
ISA信息(架构特性) |
medeleg |
0x302 |
RW |
异常委托给监管模式 |
mideleg |
0x303 |
RW |
中断委托给监管模式 |
mie |
0x304 |
RW |
机器模式中断使能 |
mtvec |
0x305 |
RW |
陷阱向量基地址 |
mcounteren |
0x306 |
RW |
计数器使能 |
mscratch |
0x340 |
RW |
机器模式临时存储 |
mepc |
0x341 |
RW |
异常程序计数器 |
mcause |
0x342 |
RW |
异常原因 |
mtval |
0x343 |
RW |
陷阱值(错误地址等) |
mip |
0x344 |
RW |
机器模式中断等待 |
计数器相关CSR
CSR名称 |
地址 |
特权级 |
功能描述 |
mcycle |
0xB00 |
M |
机器周期计数器(低32位) |
mcycleh |
0xB80 |
M |
机器周期计数器(高32位) |
minstret |
0xB02 |
M |
退休指令计数器(低32位) |
minstreth |
0xB82 |
M |
退休指令计数器(高32位) |
cycle |
0xC00 |
U |
用户模式周期计数器 |
time |
0xC01 |
U |
实时时钟计数器 |
instret |
0xC02 |
U |
用户模式指令计数器 |
CSR 访问指令
指令格式 |
功能 |
示例 |
描述 |
csrr rd, csr |
读CSR |
csrr t0, mhartid |
读取CSR到寄存器 |
csrw csr, rs |
写CSR |
csrw mtvec, t0 |
将寄存器写入CSR |
csrs csr, rs |
置位CSR |
csrs mie, t0 |
将寄存器中为1的位在CSR中置位 |
csrc csr, rs |
清零CSR |
csrc mie, t0 |
将寄存器中为1的位在CSR中清零 |
csrwi csr, imm |
立即数写 |
csrwi mstatus, 0 |
用立即数写CSR |
csrsi csr, imm |
立即数置位 |
csrsi mie, 0x8 |
用立即数置位CSR |
csrci csr, imm |
立即数清零 |
csrci mie, 0x8 |
用立即数清零CSR |
内存映射关系
RISC-V 系统内存视图:
+-------------------+ 高地址
| 内存映射I/O |
| (设备寄存器) |
+-------------------+
| 主内存 |
| (DRAM) |
+-------------------+
| CSR空间 | ← 通过csr指令访问,不在常规内存中
| (控制状态寄存器) | 独立于32个通用寄存器
+-------------------+ 低地址
重要特性总结
- 独立空间:CSR与32个通用寄存器(x0-x31)完全独立
- 特权分级:不同特权级别(M/S/U)可访问的CSR不同
- 硬件直接实现:由CPU硬件提供,不是内存映射
- 系统控制:用于中断、异常、内存管理、性能计数等
- 原子操作:CSR指令提供原子读-修改-写操作
使用示例
# 读取当前核心ID
csrr a0, mhartid
# 设置陷阱向量表
la t0, trap_handler
csrw mtvec, t0
# 启用机器模式中断
li t0, 0x8
csrs mie, t0
# 保存/恢复临时数据
csrw mscratch, sp # 保存sp到mscratch
csrr sp, mscratch # 从mscratch恢复sp
CSR是RISC-V架构中用于系统控制和状态监控的核心机制,对于操作系统和底层开发至关重要。