4. SoC硬件平台概览
在RISC-V SoC系统软件开发中,软件工程师面对的不是一颗抽象的CPU,而是一个由总线互连、地址空间、中断网络和一组外设寄存器共同构成的硬件平台。理解这些硬件对象的组织方式,是编写启动代码、设备驱动和板级支持包的前提。本章从软件视角出发,梳理SoC硬件平台的核心模型。
一、总线架构:软件看到的数据通路
SoC内部通常采用分层总线结构。以典型的RISC-V SoC为例,CPU核心通过本地总线连接到总线矩阵,再由总线矩阵分发到片上SRAM、Flash控制器、DMA以及各类外设。软件并不直接“操作总线”,但总线架构决定了访问延迟、带宽和并发能力。
常见总线包括AXI、AHB、APB等。AXI用于高带宽主从设备,如DDR控制器和DMA;APB用于低速外设,如UART、GPIO、定时器。软件在访问不同外设时,会感受到明显的速度差异。例如,连续读写UART数据寄存器与连续读写片上SRAM,其吞吐量可能相差两个数量级。
二、存储映射:地址即设备
RISC-V采用统一编址方式,外设寄存器被映射到物理地址空间中的固定区域。CPU通过load/store指令访问这些地址,就像访问内存一样。存储映射表是SoC硬件平台最重要的“地图”。
典型的存储映射如下表所示:
| 地址范围 | 区域 | 典型用途 |
|---|---|---|
| 0x0000_0000 - 0x0000_FFFF | Boot ROM | 上电启动代码 |
| 0x0200_0000 - 0x0200_FFFF | CLINT | 定时器与软件中断 |
| 0x0C00_0000 - 0x0C00_0FFF | PLIC | 平台级中断控制器 |
| 0x1000_0000 - 0x1000_0FFF | UART0 | 串口控制台 |
| 0x1001_0000 - 0x1001_0FFF | GPIO | 通用输入输出 |
| 0x8000_0000 - 0x8FFF_FFFF | DDR | 主内存 |
软件通过宏定义或设备树描述这些地址。例如:
#define UART0_BASE 0x10000000UL
#define UART0_RBR (UART0_BASE + 0x00)
#define UART0_THR (UART0_BASE + 0x00)
#define UART0_LSR (UART0_BASE + 0x05)
static inline void uart_putc(char c)
{
while ((readb(UART0_LSR) & 0x20) == 0)
;
writeb(c, UART0_THR);
}
三、中断控制器与定时器:事件驱动的基础
RISC-V的中断体系由CLINT和PLIC共同构成。CLINT负责软件中断和机器定时器中断,PLIC负责外部设备中断的汇聚与分发。软件通过配置PLIC的优先级、使能位和阈值,决定哪些外设中断可以送达CPU。
定时器通常分为两类:CLINT中的mtime/mtimecmp提供机器模式定时器;外设中的Timer模块可产生周期性中断。在裸机环境中,定时器中断常用于系统节拍;在Linux中,则通过时钟框架注册为clocksource和clockevent。
四、UART与GPIO:最基础的外设模型
UART和GPIO是SoC中最简单也最常用的外设。UART通常包含数据寄存器、状态寄存器和控制寄存器,软件通过轮询或中断方式收发数据。GPIO则通过方向寄存器和数据寄存器控制引脚电平,常用于LED、按键和片选信号。
以GPIO点灯为例,典型操作流程为:使能GPIO时钟、配置引脚为输出、写入电平值。在Linux中,这些操作被抽象为gpiod接口;在裸机中,则直接操作寄存器。
/* 裸机GPIO输出示例 */
#define GPIO_BASE 0x10010000UL
#define GPIO_DIR (GPIO_BASE + 0x00)
#define GPIO_OUT (GPIO_BASE + 0x04)
void gpio_led_on(void)
{
writel(readl(GPIO_DIR) | (1 << 3), GPIO_DIR);
writel(readl(GPIO_OUT) | (1 << 3), GPIO_OUT);
}
五、外设模型对比与选型思路
不同外设在软件模型上差异明显。下表从访问方式、中断能力和典型驱动框架三个维度进行对比:
| 外设 | 访问方式 | 中断能力 | 典型驱动框架 |
|---|---|---|---|
| UART | 寄存器读写 | 支持 | tty/serial |
| GPIO | 寄存器读写 | 部分支持 | gpiolib |
| Timer | 寄存器读写 | 支持 | clocksource/clockevent |
| PLIC | 寄存器读写 | 中断汇聚 | irqchip |
| DMA | 寄存器+描述符 | 支持 | dmaengine |
六、案例:从零搭建一个串口控制台
假设我们在一块RISC-V SoC上实现最小串口控制台。步骤如下:
- 根据存储映射表找到UART0基地址。
- 配置UART波特率、数据位和停止位。
- 实现putc和getc函数,分别写THR和读RBR。
- 在启动代码中初始化UART,并重定向printf。
- 若需中断接收,配置PLIC使能UART中断,并注册中断服务程序。
这一过程覆盖了存储映射、外设寄存器操作和中断控制三个核心知识点,是理解SoC硬件平台的最佳切入点。
七、常见误区与复盘
- 误区一:认为所有外设访问速度相同。实际上APB外设与DDR的访问延迟差异巨大,驱动中应避免在中断上下文频繁访问慢速外设。
- 误区二:忽略存储映射的地址对齐要求。部分外设寄存器要求32位对齐访问,使用8位访问可能导致总线错误。
- 误区三:中断使能顺序错误。应先配置外设中断源,再配置PLIC,最后打开CPU全局中断。
- 误区四:未处理中断清除。若中断服务程序未清除外设中断标志,会导致中断反复触发。