3、启动流程基础:CPU复位行为、启动ROM(Boot ROM)设计、多级启动链(Bootloader分级)、典型RISC-V芯片启动时序。

好,咱们今天聊聊芯片上电之后,到底发生了什么。

很多刚入行的朋友,拿到一块RISC-V开发板,第一件事就是点灯。点完灯就觉得自己会了。但说实话,如果不懂启动流程,你连芯片是怎么“活过来”的都不知道。出了问题,你连从哪下手查都不知道。

我个人习惯,拿到一个新芯片,第一件事不是看外设驱动,而是先看它的启动流程文档。这就像你开一辆新车,得先知道钥匙插哪、怎么点火,对吧?

3.1 CPU复位行为:芯片的“第一口呼吸”

芯片上电或复位信号拉低的那一刻,CPU内部会发生什么?

说白了,就是CPU被强制“归零”,然后去一个固定的地址取第一条指令。这个地址,就是复位向量(Reset Vector)。

核心要点:复位向量是CPU生命的起点。对于RISC-V架构,复位向量通常由硬件设计决定,常见的有0x00000000或0x80000000等。

我记得有一次调试一块国产RISC-V芯片,死活跑不起来。查了半天,发现是复位向量地址和Boot ROM的映射地址没对上。嗯,这种坑,踩过一次就记住了。

复位时,CPU内部寄存器会进入默认状态:

  • PC(程序计数器):被设置为复位向量地址
  • 通用寄存器:大部分清零,部分可能有默认值
  • CSR(控制状态寄存器):进入M模式(机器模式),中断关闭
  • 缓存/MMU:默认关闭,需要软件初始化

注意:不同厂商的RISC-V核,复位行为可能有细微差别。比如SiFive的核和Syntacore的核,复位后某些CSR的默认值就不一样。一定要看数据手册,别想当然。

3.2 启动ROM(Boot ROM)设计:芯片出厂自带的“小系统”

CPU复位后,去取第一条指令。但这时候内存还没初始化,Flash里也没代码。那指令从哪来?

答案就是Boot ROM。它是芯片内部的一块只读存储器,出厂时就已经固化好了代码。你改不了它,但它能帮你启动。

Boot ROM通常做这几件事:

  1. 基本硬件初始化:配置时钟、PLL,让CPU先跑起来
  2. 检测启动模式:通过管脚电平或efuse,判断从哪启动(比如SPI Flash、SD卡、UART)
  3. 加载下一级Bootloader:从启动介质中读取代码,放到SRAM或DDR中,然后跳转过去

一个小技巧:我曾经在调试一个量产项目时,发现某些芯片Boot ROM读取SPI Flash总是超时。后来发现是SPI Flash的初始化时序和Boot ROM预期的不一致。解决办法?换了一款兼容性更好的Flash。这种坑,设计阶段就要考虑进去。

Boot ROM的设计原则很简单:越小越好,越稳越好。因为它占的是芯片面积,面积就是成本。所以Boot ROM通常只有几KB到几十KB,功能极其精简。

3.3 多级启动链:为什么需要“分级”启动?

你想想看,Boot ROM那么小,能干的事有限。它不可能认识文件系统,也不可能解析FAT32或者ext4。那怎么办?

答案是分级。就像接力赛,一棒传一棒。

典型的RISC-V芯片启动链是这样的:

级别 名称 存储位置 主要任务
第0级 Boot ROM 芯片内部ROM 初始化时钟、检测启动介质、加载第1级
第1级 SPL / MBR SPI Flash / SD卡 初始化DDR、加载第2级
第2级 U-Boot / OpenSBI DDR中运行 加载操作系统内核、设备树
第3级 操作系统内核 DDR中运行 启动Linux/RTOS

为什么要分这么多级?

  • Boot ROM太小,放不下复杂逻辑
  • DDR需要初始化,而初始化DDR的代码本身就需要运行空间
  • 安全考虑:每一级都可以校验下一级的签名,防止被篡改

避坑指南:我曾经遇到一个项目,SPL代码太大,超过了Boot ROM能加载的大小限制。结果芯片一直复位循环。后来把SPL拆成了两部分,一部分做最基础的DDR初始化,另一部分等DDR起来后再加载。这就是分级的灵活性所在。

3.4 典型RISC-V芯片启动时序:从复位到操作系统

好,咱们把上面这些串起来,看看一个典型的RISC-V芯片从复位到跑起Linux,到底经历了什么。

下面这张图,是我自己画的启动时序图,你可以对照着看:

RISC-V芯片启动时序图 时间 阶段1:复位释放,CPU从复位向量取指 t0 阶段2:Boot ROM执行,初始化时钟,检测启动介质 t1 阶段3:Boot ROM从SPI Flash加载SPL到SRAM t2 阶段4:SPL执行,初始化DDR,加载U-Boot到DDR t3 阶段5:U-Boot执行,加载Linux内核和设备树 t4

这张图里,每个阶段都有明确的分工。你想想看,如果没有分级,Boot ROM直接去加载Linux内核,那它得认识文件系统、得初始化DDR、还得解析设备树。这代码量,Boot ROM那几KB根本放不下。

实际项目中,启动时序的调试往往是最头疼的。我遇到过一个问题:芯片在低温环境下,DDR初始化总是失败。查了很久,发现是SPL里DDR的时序参数在低温下需要调整。后来在SPL里加了一个温度检测逻辑,根据温度选择不同的时序参数,问题才解决。

经验之谈:调试启动流程时,最常用的工具就是GPIO点灯和串口打印。在Boot ROM和SPL里,加几个GPIO翻转的代码,用示波器一看,就知道代码跑到哪一步了。这招虽然土,但真的管用。

好了,这一章的内容就到这里。启动流程是固件开发的地基,地基不稳,上面盖什么都白搭。下一章咱们会深入Boot ROM的代码实现,看看那些“出厂即固化”的代码到底是怎么写的。

本章小结:

  • CPU复位后从复位向量取指,进入M模式
  • Boot ROM是芯片内部固化的启动代码,负责最基础的初始化
  • 多级启动链解决了存储空间和功能复杂度的矛盾
  • 典型启动时序:复位 → Boot ROM → SPL → U-Boot → 内核

专注资料整理