2. RISC-V架构基础
在裸机外设开发中,理解RISC-V架构基础是编写启动代码、配置中断控制器、访问寄存器的前提。RISC-V采用模块化设计:基础整数指令集(RV32I/RV64I)负责通用计算,扩展指令集(M/A/F/D/C等)按需组合,特权架构则定义了机器模式、监管模式和用户模式下的控制寄存器与异常处理流程。本节围绕指令集、寄存器、特权模式、中断异常以及内存映射五个方面展开,为后续GPIO、UART、定时器等外设驱动打下基础。
一、RV32I/RV64I指令集:精简而规整
RV32I是32位基础整数指令集,RV64I是其64位版本,两者指令格式高度一致,主要区别在于寄存器宽度和部分立即数范围。RV32I包含约40条指令,分为六种格式:R型(寄存器-寄存器运算)、I型(立即数运算与加载)、S型(存储)、B型(分支)、U型(长立即数)、J型(跳转)。这种规整的编码让译码器实现简单,也便于裸机开发者手工编写汇编启动代码。
常用指令类别包括:算术逻辑(add、sub、and、or、xor、sll、srl、sra)、比较(slt、sltu)、加载存储(lb、lh、lw、sb、sh、sw)、分支跳转(beq、bne、blt、bge、jal、jalr)、立即数操作(addi、andi、ori、lui、auipc)以及环境调用(ecall、ebreak)。RV64I在此基础上增加ld、sd、lwu、addiw等64位操作。
# 典型RV32I启动片段:设置栈指针并跳转到C入口
la sp, _stack_top # 加载栈顶地址
la t0, main # 加载main函数地址
jalr zero, t0, 0 # 跳转并链接(ra=返回地址)
j . # 若main返回则死循环
二、寄存器组织:32个通用寄存器与CSR
RISC-V定义32个通用寄存器(x0~x31),其中x0恒为0,写入被忽略。ABI为它们分配了别名和调用约定:a0~a7用于参数与返回值,t0~t6为临时寄存器,s0~s11为被调用者保存寄存器,sp为栈指针,ra为返回地址,gp为全局指针,tp为线程指针。裸机开发中,启动代码必须初始化sp和gp,否则C函数无法正常运行。
除通用寄存器外,特权架构还提供控制状态寄存器(CSR),如mstatus、mtvec、mepc、mcause、mie、mip、mhartid等。CSR通过csrrw、csrrs、csrrc等指令访问,是配置中断、异常入口和特权级别的核心手段。
三、特权模式:M/S/U三级
RISC-V特权架构定义三个级别:机器模式(M,最高)、监管模式(S)和用户模式(U)。裸机程序通常运行在M模式,拥有全部CSR访问权限和外设控制权。M模式可配置mstatus.MPP决定异常返回后进入的模式,也可通过misa、mhartid等CSR获取硬件信息。对于简单外设开发,M模式足够;若需运行操作系统,则需实现S模式与M模式间的委托(delegation)机制。
四、中断与异常机制:统一入口与CSR协作
RISC-V将中断和异常统称为trap。发生trap时,硬件自动完成:将当前PC保存到mepc,将原因写入mcause,将trap入口地址从mtvec加载到PC,并更新mstatus中的MPIE、MIE等位。mtvec可配置为直接模式或向量模式;向量模式下不同中断号跳转到不同入口,适合外设中断较多的场景。
中断使能由mie(各中断源使能)和mstatus.MIE(全局使能)共同控制,挂起状态在mip中查看。外部中断通常经PLIC(平台级中断控制器)汇聚,再连接到M模式外部中断位MEIP。异常则包括指令非法、加载/存储访问错误、ecall等,mcause最高位区分中断与异常。
五、内存映射与地址空间
RISC-V采用统一编址,指令、数据和外设寄存器共享同一地址空间。典型SoC中,Flash映射在0x20000000附近,SRAM在0x80000000附近,外设寄存器在0x10000000或0x40000000区域。裸机代码通过volatile指针访问外设寄存器,例如GPIO输出寄存器可定义为#define GPIO_OUT (*(volatile uint32_t*)0x10012000)。
地址空间还涉及对齐要求:RV32I的lw/sw要求4字节对齐,RV64I的ld/sd要求8字节对齐,非对齐访问可能触发异常。此外,M模式可通过PMP(物理内存保护)配置地址区域的读/写/执行权限,防止误访问。
六、案例与误区复盘
以一个GPIO按键中断为例:按键连接GPIO,GPIO中断经PLIC汇聚到M模式外部中断。初始化时需设置mtvec指向ISR入口,使能mie.MEIE和mstatus.MIE,配置PLIC优先级与使能位,最后在GPIO模块中使能对应引脚中断。ISR中先读取mcause确认是外部中断,再读取PLIC声明寄存器获取中断源,清除GPIO中断标志,处理按键逻辑,最后写PLIC完成寄存器并mret。
| 常见误区 | 现象 | 正确做法 |
|---|---|---|
| 未初始化sp就调用C函数 | 程序跑飞或HardFault | 启动汇编中先设置sp指向栈顶 |
| ISR中未清除外设中断标志 | 中断反复触发,系统卡死 | 处理前读取并清除外设标志 |
| mtvec未对齐或模式配置错误 | trap入口跳转异常 | 确保mtvec低2位为0(直接模式) |
| 非对齐访问外设寄存器 | 触发加载/存储异常 | 使用volatile且按4字节对齐访问 |
| 忘记使能mstatus.MIE | 中断配置正确但不响应 | 全局使能位与源使能位都要置1 |