2. RISC-V基础架构回顾:重温核心概念
好,咱们正式开始之前,先花点时间把RISC-V的底子捋一遍。你可能会问:“都做GD32V调优了,还用得着回头翻基础?”
嗯,我个人的经验是——启动流程里的坑,十有八九都出在对特权级、异常机制的理解偏差上。说白了,你连CPU在哪个模式下跑、异常怎么来、寄存器怎么用都没吃透,调优就只能是瞎蒙。
2.1 寄存器模型:通用寄存器与控制状态寄存器
RISC-V的寄存器模型,比ARM Cortex-M要清爽得多。我刚开始从ARM切过来时,第一反应是:“就这?”
2.1.1 通用寄存器(GPR)
一共32个,编号x0到x31。每个都是32位宽(RV32)或64位宽(RV64)。GD32V用的是RV32IMAC,所以咱们聊32位。
| 寄存器 | ABI名称 | 用途说明 |
|---|---|---|
| x0 | zero | 硬连线为0,写它没效果。我见过新手拿它当普通寄存器用,结果死活写不进去 |
| x1 | ra | 返回地址寄存器,调用子程序时自动保存 |
| x2 | sp | 栈指针,启动代码里第一个要设的就是它 |
| x3 | gp | 全局指针,用于访问静态数据 |
| x4 | tp | 线程指针,RTOS里常用 |
| x5-x7 | t0-t2 | 临时寄存器,调用者保存 |
| x8-x9 | s0-s1 | 保存寄存器,被调用者保存 |
| x10-x17 | a0-a7 | 函数参数/返回值寄存器 |
| x18-x27 | s2-s11 | 更多保存寄存器 |
| x28-x31 | t3-t6 | 更多临时寄存器 |
我的习惯:在启动代码里,我会先把sp设到SRAM末尾,然后才敢碰C函数。有一次我忘了设sp,结果压栈直接写到了未初始化区域,系统跑飞了。嗯,从那以后我再也不敢跳过这一步。
2.1.2 控制状态寄存器(CSR)
CSR才是RISC-V的灵魂。它们控制着中断、异常、特权级、计时器等核心功能。访问CSR只能用csrr、csrw、csrs、csrc这些专用指令。
启动流程里最关键的几个CSR:
- mstatus:机器模式状态寄存器,控制全局中断使能、特权级切换等
- mtvec:机器模式异常入口基地址,启动时必须配好
- mepc:异常返回地址,异常发生时硬件自动写入
- mcause:异常原因,告诉你发生了什么
- mtval:异常附加信息,比如访存地址
注意:GD32V的mtvec寄存器支持直接模式和向量模式。直接模式下所有异常跳到同一个入口;向量模式下每种异常有独立入口。我建议在启动阶段先用直接模式,等异常处理框架搭好后再切向量模式,否则调试初期会手忙脚乱。
2.2 特权级架构:Machine / Supervisor / User
RISC-V定义了三个特权级,编号从高到低:
- M模式(Machine Mode,特权级3):最高权限,可以访问所有CSR和物理地址。GD32V上电后默认就在M模式。启动代码、异常处理、低功耗管理都在这里跑。
- S模式(Supervisor Mode,特权级1):支持虚拟内存的操作系统用。GD32V没有MMU,所以S模式基本用不上。
- U模式(User Mode,特权级0):应用程序跑的模式,权限最低。不能直接操作CSR,也不能访问物理地址。
你想想看,GD32V这种MCU,大部分场景都是裸机或RTOS,所以咱们基本只跟M模式打交道。但如果你跑的是Linux,那S模式就绕不开了。
核心要点:启动代码必须运行在M模式。所有对系统控制寄存器的初始化,比如配时钟、设中断向量、初始化内存保护单元(PMP),都只能在M模式下完成。一旦切到U模式,这些操作就全被硬件拦截了。
2.3 中断与异常机制
RISC-V把中断和异常统称为“异常”(Exception),但内部有区分:
- 同步异常:由指令执行直接引起,比如非法指令、访存对齐错误、环境调用(ecall)。
- 异步异常(中断):由外部事件引起,比如定时器中断、外部引脚中断。
2.3.1 异常处理流程
当异常发生时,硬件自动完成以下动作:
- 将当前PC保存到
mepc - 将异常原因编码写入
mcause - 将异常附加信息写入
mtval(如果有) - 将当前特权级保存到
mstatus的MPP字段 - 关闭全局中断(MIE位清零)
- 跳转到
mtvec指向的地址开始执行
我曾经在调试一个外设驱动时,发现中断响应总是不对。查了半天,原来是异常处理函数末尾忘了执行mret指令。硬件不会自动返回,你不写mret,它就永远卡在异常处理里。
避坑指南:我曾经在中断服务函数里修改了
mepc的值,想实现任务切换。结果忘了保存和恢复现场,导致返回后寄存器全乱了。正确的做法是:在进入异常时,先把所有被中断破坏的寄存器压栈,返回前再恢复。
2.3.2 中断嵌套与优先级
RISC-V标准没有规定中断优先级和嵌套机制,这由具体芯片实现决定。GD32V使用的是Nuclei N100系列内核,它支持:
- 可编程中断优先级:每个中断源可以配0-3级优先级
- 硬件中断嵌套:高优先级中断可以打断低优先级中断
- 快速中断(FIQ):专门有一条快速中断线,用于时间敏感场景
| 特性 | 标准RISC-V | GD32V (N100) |
|---|---|---|
| 中断优先级 | 未定义 | 4级可编程 |
| 硬件嵌套 | 未定义 | 支持 |
| 向量中断表 | 可选 | 支持(通过mtvec配置) |
| FIQ | 未定义 | 支持 |
2.4 知识结构总览
下面这张图把咱们刚才聊的内容串起来了。你可以把它当作启动流程分析的“地图”——每次遇到问题,先定位是哪个模块出的状况。
2.5 小结
好了,基础架构咱们就回顾到这儿。记住三件事:
- 寄存器:32个通用寄存器 + 一堆CSR,启动代码里最常碰的是mtvec、mstatus、mepc
- 特权级:GD32V上电在M模式,所有初始化操作都在这里完成
- 异常:硬件自动保存现场,但返回必须用mret,别指望它自己回去