FPGA 原型验证专题 30:Linux/RTOS 在原型上启动——启动链、降频补偿、超时与启动时间换算
更新时间:2026-09-13。本文是原型外围姊妹系列第五篇,模块 B(软硬件协同)收尾。29 固件 shift-left RTL 冻结前 bring-up/驱动开发把 bring-up 推进到 RTOS 之前;本篇讲最能体现原型价值、也最容易卡住的一步——把完整操作系统启动到 shell/root。它依赖 27 在电路仿真 ICE 与协同加速模式 接线/时钟纪律(OS 必须在自由时钟的 ICE 下跑)、28 处理器 JTAG/调试口上原型 GDB/trace/失真、32 DDR/存储接入原型 真实DRAM/降速模型/容量墙(编号见后续,DDR 是启动的关键依赖)。
这一篇在体系里的位置
启动一个现代 OS,等于把 SoC 几乎所有子系统在真实软件驱动下按真实顺序走一遍:复位释放、CPU 取指、片上 ROM/ SRAM、时钟/复位控制器、DDR 初始化、中断控制器、定时器、UART 控制台、存储或网络加载、页表/MMU、cache 一致性、外设枚举。任何一环不对,启动就停在那一级。这也是为什么"启动到登录提示符"是一个里程碑式的验收点——它一次性证明了大片 RTL 和软件的协同正确性。
本篇不讲某一架构的具体命令(那是各平台 BSP 文档的事),而讲在原型这个特殊载体上启动 OS 的共性工程:启动链每级要准备什么、原型的哪些非理想性会让它挂、怎么判断挂在哪一级、怎么解读启动时间。
本文要回答的问题
- 一条典型 ARM/RISC-V Linux 启动链(ROM→FSBL/SPL→ATF/SBI→U-Boot→kernel→rootfs)各级在原型上分别需要什么前提?
- 为什么 OS 只能在 ICE 模式跑、停时钟/锁步会怎样?
- 原型降频和跨片延迟会动到哪些"默认时间假设"(DDR 训练、定时器、看门狗、链路超时、校准循环)?
- 存储和 rootfs 在原型上用什么替代?NFS/tftp/内存盘/替换存储各适合哪个阶段?
- 怎么把"原型上的启动时间"正确换算回芯片预期?为什么不能直接用墙钟?
- 启动卡死时如何快速定位是哪一级、软件问题还是原型问题?
一、启动链分级:每一级在原型上的前提

逐级说明在原型上的前提:
- POR 与复位释放:全系统复位必须同步到达所有 FPGA 和核(28 处理器 JTAG/调试口上原型 GDB/trace/失真),启动核(boot hart/primary core)释放、其它核进等待状态(PSCI/spin-table 或 SBI HSM)。跨片复位不同步会导致次核提前取指、或启动核拿不到释放。
- Boot ROM:真芯片的片上 ROM 在原型里通常映射为一块只读存储(替换 SRAM/COE 初始化/外部 Flash 模型)。要确保复位向量地址、ROM 镜像、启动选择 strap 三者匹配。早期也可绕过 ROM,用 JTAG 直接把 FSBL 下载到 SRAM 运行,缩短迭代。
- FSBL/SPL(一阶启动):配时钟/复位控制器、初始化 DDR、配引脚复用(pinmux/I/O 电压)、把下一阶段镜像搬到内存。这是在原型上最容易卡死的一级,因为它直接依赖真实时序敏感的 DDR 训练/初始化和时钟树。
- ATF BL31 / RISC-V SBI(运行时固件):安全监控/机器态运行时,提供 PSCI(核上下电)、SBI 调用、定时器/IPI 抽象。它依赖定时器、核间中断(IPI/软件中断)、各核启动地址——跨片核间中断延迟变大时,多核启动握手偶发超时是典型问题。
- U-Boot(二阶 bootloader):初始化更多外设、从存储/网络加载内核与设备树(DTB)、传 bootargs。原型上常改走 tftp(经速率适配后的网络,见 75 篇)或直接从内存/替换存储加载,绕过尚未就绪的 SD/eMMC。
- Linux 内核:解析 DTB、建页表开 MMU、注册驱动、轮询/等待外设 ready、挂载 rootfs。这一级对"时间"最敏感(第三节展开)。
- rootfs 与 init:最小可用先 initramfs/内存盘(只依赖内存),再 NFS(依赖网络),最后才是真实块设备根文件系统。到 systemd/登录提示符意味着驱动、调度、文件系统、网络栈基本打通。
二、为什么 OS 必须在 ICE 自由时钟下跑
操作系统里充满了对"时间在自主流逝"的假设:jiffies/时钟节拍中断、定时器软中断、调度器时间片、看门狗、各类 mdelay/udelay 忙等、驱动的"轮询某状态位最多 X ms"、TCP 超时、DDR 训练的刷新与时序校准。这些代码假设:我不控制时钟时,硬件时钟仍在均匀前进。
27 在电路仿真 ICE 与协同加速模式 接线/时钟纪律的锁步/加速模式会按主机同步停时钟或让时间不均匀——一旦时钟停住:看门狗永远不喂→复位;udelay 忙等基于时钟周期计数,时钟一停它永远等不够→死循环;节拍中断不来→调度器认为没有时间流逝,任务全卡住。所以完整 OS 只能在 ICE 模式、时钟自由跑下启动。协同加速模式可以跑裸机片段、驱动状态机(70 篇阶段 A),但跑不了依赖真实时间流动的完整 OS。
唯一的例外是少数"周期精确锁步、软件不使用任何时间假设"的极简 RTOS 场景,代价是慢到没有实用价值,一般不用于 OS bring-up。
三、降频补偿:OS 在原型上的"时间观"
原型等效频率往往只有芯片目标频率的几十分之一,且跨片路径被 TDM 进一步拉长。OS 仍要正确运行,关键是让它知道自己跑在什么频率上,而不是硬编码标称频率。
**1) 时钟源/定时器频率必须如实配置。**内核和固件通过设备树(timebase-frequency、clock-frequency)、启动参数或运行时查询得到频率。原型版 DTB/配置填降频后的实际频率,则:
- 基于周期计数的
mtime/ARM generic timer换算成秒仍然正确——系统时钟和读它的代码一起变慢,"滴答"数到阈值对应的芯片周期数语义不变,只是墙钟变长; udelay这类基于"数周期实现延时"的函数仍然延迟正确数量的周期(在芯片预期上是 1µs 的周期数,在慢原型上要花更多墙钟时间,但软件逻辑不依赖它等于真实 1µs)。
2) 真正会出问题的是"墙钟/真实物理时间"假设。
- DDR/PHY 训练与校准:DDR 初始化里常有基于真实时延、刷新周期(tREFI 是绝对物理时间)、读写训练窗口的流程。若原型用真实 DDR + 速率适配(73 篇样式一),DRAM 刷新按真实时间走,而控制器/PHY 在慢时钟下配出来的刷新周期可能对不上真实 DRAM 的要求——很多团队在原型上用经过修改的训练/刷新参数或免训练(training bypass)配置,或干脆用 FPGA 片上存储建模,绕开真实 DRAM 时序敏感性。
- 外部器件超时/链路训练:PCIe/USB/以太网对端有自己的真实超时(74/75 篇),慢原型响应太慢会被对端判失败。这正是高速接口要速率适配/降速子卡的原因;OS 里相应枚举超时有时也要放宽。
- 绝对时间校准:某些驱动用固定循环数估时(而非读定时器),在降频下其真实时长改变;这类要改成读硬件定时器。
- 看门狗:喂狗周期按芯片频率设的阈值,在慢原型上要么按频率系数放宽、要么先关掉,否则 OS 反复复位(但保留对"系统是否真的卡死"的其他监测)。
3) 均匀降频 vs 不均匀抖动。均匀地整体变慢对软件基本透明(一切周期计数同比例);真正危险的是跨片 TDM 造成的局部、不均匀延迟——某次访存突然被拉长几十上百倍。依赖精细超时、自旋等待窗口紧的代码可能偶发失败。对策:关键路径尽量不跨片(分区时把启动相关子系统放在同一 FPGA,12 多 FPGA 分区算法 割集/容量/图划分),并对已知跨片路径在 DTB/超时参数上留裕量。
四、存储、镜像与 rootfs 的原型替代
启动需要"放镜像的地方"和"放根文件系统的地方",真实介质(SD/eMMC/NAND)在原型早期常没就绪。按启动阶段选择替代,越往后越接近真实:
| 启动要素 | 早期(最省事) | 中期 | 后期(最真实) |
|---|---|---|---|
| Boot ROM/FSBL | JTAG 直接下载到 SRAM | 替换 ROM(COE/SRAM 初始化) | 真实 Flash 模型 |
| 内核/DTB 加载 | JTAG/内存加载器 | U-Boot 从替换存储/内存加载 | tftp(经适配网络) |
| rootfs | initramfs/内存盘 | NFS(经适配网络) | 真实块设备(SD/NVMe 经速率适配) |
| 主内存 | FPGA 片上/板载 SRAM 建模(容量小) | 大容量替換存储 | 真实 DDR + 速率适配/训练 bypass |
要点:
- 先用 initramfs:它整体加载进内存,只依赖"有内存和能取到内核",不依赖任何块设备/网络,是第一次进 shell 的最快路径。
- NFS/tftp 需要网络子系统已通:通常要到 U-Boot 网络和驱动就绪,且网络经速率适配后对 TCP 超时要放宽;好处是改 rootfs/内核不用重新烧写,迭代快。
- 内存容量墙:片上 SRAM 装不下完整内核+rootfs 时,必须上板载大容量存储或真实 DDR(73 篇三种存储样式)。很多项目第一次启动失败不是逻辑错,而是链接地址/加载地址超出了原型可用内存。
- 设备树是原型适配的中心:频率、内存大小与基址、中断号、兼容串、哪些 IP present、替换 IP 的兼容驱动,几乎都通过 DTB 调整。为原型维护一份专门的
*-proto.dts,与真芯片 DTB 共享大部分、只改原型相关项。
五、启动时间怎么换算回芯片预期
管理层常问:"原型上启动到 shell 要 8 分钟,芯片会不会也这么慢?"——直接拿墙钟比较是错的。正确的换算口径:
- 以周期计数为准,不以墙钟为准。读硬件周期计数器(ARM
CNTPCT/RISC-Vtime/mcycle)记录从复位到各里程碑的周期数。只要软件看到的频率参数正确,同一份代码在芯片上的预期墙钟≈周期数 / 芯片目标频率。 - 区分三类时间开销:
- 纯 CPU 执行时间:与频率近似线性,按频率比缩放(原型 5MHz、芯片 1GHz,则芯片快约 200 倍)。
- 访问真实物理介质/对端的固定物理时间:如真实 DDR 刷新、真实网络 RTT、真实链路训练——这些不随 FPGA 频率缩放,在原型总时间里是"固定底噪",换算时应扣除或单列。
- 跨片/TDM 引入的额外延迟:不均匀、随分区变化,属原型失真,芯片上不存在,外推时必须扣除,否则会高估芯片延迟。
- 用比例法做 sanity check:对一个已知 CPU-bound 阶段(如解压内核),量原型周期数和墙钟,反推等效频率是否与预期降频一致;若实测等效频率远低于预期,说明时间被跨片/存储模型吃掉,应先优化分区/存储而非下结论。
- 性能结论分级:原型可较可信地给周期级相对比较(两种配置谁快、缓存命中行为,配合 24 活动覆盖率与性能调试闭环 热点/瓶颈/回放与后续 E3 篇校正);不要给"芯片绝对启动耗时""绝对中断延迟"下结论。
一句话:原型回答"这段软件逻辑要花多少周期/顺序对不对",不直接回答"在芯片上要多少毫秒",后者要靠周期数外推并扣除物理固定项和跨片失真。
六、启动卡死的分层定位
启动链是一条有序管线,"卡在哪一级"决定了找谁排查。自前向后逐级判定:
- 完全无任何迹象:查复位/时钟/启动 strap/启动核选择/JTAG 能否 halt(69 篇)。先用 JTAG 看 PC 在哪里、是否在取指。
- ROM/FSBL 都没反应:查复位向量、ROM 镜像映射、JTAG 直载 FSBL 是否可绕过;跨片复位同步。
- 卡在 FSBL/DDR 初始化:最高频。查存储模型/真实 DDR 训练、刷新参数、训练 bypass、时钟树、内存容量与加载地址(73 篇)。
- FSBL 过、ATF/SBI 或次核启动卡:查定时器、IPI/核间中断、PSCI/SBI 握手、跨片核间路径、次核启动地址。
- U-Boot 起、内核不搬或不起:查镜像加载地址/大小、DTB 地址覆盖、镜像头/架构、控制台配置。
- 内核早期挂(控制台无输出/earlycon 前):用 earlycon/轮询串口、JTAG 看 PC;常是 DTB 频率/内存/中断控制器配置错、或某驱动在慢原型上超时设太短。
- 内核起、挂 rootfs:查 initramfs 是否完整打进镜像、NFS/网络(链路与超时)、设备驱动 present、设备树 compatible。
- 能进 shell 但不稳/偶发重启:查看门狗喂狗周期、跨片不均匀延迟、真实接口掉线、长稳(进入 soak 阶段)。
每一级都优先用 69 篇的 JTAG halt/单步/trace 直接看现场,而不是盲改;并坚持先读 build_id(20 比特流烧录与四方版本一致 manifest/门禁)确认比特流与镜像/DTB 版本配套——启动期"玄学"很大比例是镜像、DTB、比特流三者版本错配。
七、让 OS 稳定长跑的检查清单
- ICE 自由时钟,确认无任何路径会门控系统时钟;
- DTB/启动参数频率填实际降频值,定时器/节拍正确;
- 看门狗按频率系数放宽或临时关闭,另有卡死监测;
- DDR/存储训练与刷新参数按原型介质处理(bypass/建模/适配);
- 关键启动子系统尽量同片,跨片超时留裕量;
- 控制台 earlycon 可用,JTAG halt/trace 全程可连;
- rootfs 从 initramfs→NFS→真实块设备分阶段推进;
- 镜像、DTB、比特流、固件(FSBL/ATF/SBI/U-Boot)版本一起锁定进 manifest;
- 进 shell 后安排自动冒烟(建文件、ping、跑负载)与反复复位重启测试。
七、板上调试与 bootlog 取证:把"没起来"变成可定位的问题
原型上 OS 启动失败和真机不同:可能是硬件/原型问题,也可能是软件问题,且每次失败代价高(重综合以小时计、重启以分钟计)。因此 boot 阶段的可观测性和取证习惯要在第一次尝试前就布好。
**分级输出,永远保留最低一级。**串口是最可靠、最不依赖待验证硬件的输出通道,bootrom→SPL→U-Boot→kernel 早期都应走串口,且即使后期接了真 console/网络,串口也不要关——系统死在 console 初始化之前时,串口是唯一的现场。内核 earlycon/earlyprintk、U-Boot 早期驱动、bootrom 的点灯码,组成从"黑"到"亮"的完整输出梯度。
**用 LED/代码区分"死在哪一段"。**原型常连完整串口日志都来不及建立。约定一组启动阶段码(bootrom=0x1、SPL=0x2、U-Boot=0x3、kernel start=0x4、userspace=0x5…)写在 GPIO/调试寄存器,配合 69 篇的 JTAG,死后 halt 一看 PC 和阶段码,立刻知道排查方向。这是 bring-up 期性价比最高的插桩。
**死后的现场取法。**死机能 JTAG halt 时:读 PC/LR、当前异常类型、栈回溯、关键结构体(U-Boot 的 GD、内核 init_task/log_buf、页表基址);pstore/RAMOOPS 思路在原型上同样适用——留一块复位不被清零的内存放 panic 日志。跑不到 halt 的(总线挂死、时钟停),上 ILA/探针抓最后一次总线事务(63 篇),并检查是不是跨片/访问模型超时。
典型故障到原因的对照表(经验向):
| 典型现象 | 更可能的原因 |
|---|---|
| 无任何串口字符 | 时钟/波特率/引脚、bootrom 未起、存储读取失败(看存储模型/通路) |
| bootrom 阶段码不动 | 启动介质配置、引脚/电平(73、76 篇)、镜像头格式 |
| U-Boot 解压内核后挂死 | 镜像地址/大小冲突、cache 一致性、MMU 页表、DUT 内存容量不足 |
| 内核早期日志后挂 | earlycon 之后的中断/GIC、节拍器、真实存储驱动第一个 DMA |
| 内核反复重启 | 看门狗仍在线(超时没喂)、内核 panic 且 reset 策略=重启 |
| 用户空间启动极慢但最终成功 | 不是 bug:原型慢+真实文件系统,用降频补偿后的时间预期判断 |
**看门狗与超时的特别提醒。**SoC 上的 watchdog、链路超时、PHY 自协商超时等真实计时,在降频原型上必须能关/能放宽/能由适配层处理——这是最常见的"代码没问题但反复复位"。启动阶段先全部关掉,稳定后再逐项恢复验证。
与系列及现有篇目的链接
- 软件左移总操作面:29 固件 shift-left RTL 冻结前 bring-up/驱动开发
- 两种上板模式(OS 必须 ICE):27 在电路仿真 ICE 与协同加速模式 接线/时钟纪律
- 处理器调试与复位:28 处理器 JTAG/调试口上原型 GDB/trace/失真
- 主内存/存储三种样式:73 DDR/存储接入原型
- 分区避免关键路径跨片:12 多 FPGA 分区算法 割集/容量/图划分、降频 19 原型频率与时序收敛 TDM 降频/关键路径
- 版本一致与性能/活动:20 比特流烧录与四方版本一致 manifest/门禁、24 活动覆盖率与性能调试闭环 热点/瓶颈/回放、全链路 04 ASIC 原型验证全链路 RTL ID/GateID/层次映射
总结
一句话总结:在原型上启动完整 OS 是一次性点亮复位链、启动核、ROM/FSBL、DDR、中断、定时器、控制台、加载链路、MMU 和外设枚举的总验收,必须在 ICE 自由时钟下进行——锁步/停时钟会让节拍中断、udelay 忙等、看门狗全部失效,因此协同加速只适合裸机片段而不适合完整 OS;启动链按 ROM→FSBL/SPL(最常卡在依赖真实时序的 DDR 训练/时钟)→ATF/SBI(依赖定时器与跨核 IPI,次核握手易被跨片延迟拖超时)→U-Boot→内核(对时间最敏感)→rootfs 逐级准备前提,存储从 JTAG 直载/initramfs 起步、再 NFS、最后真实块设备,原型差异集中在一份专门的 *-proto.dts(频率、内存、中断、compatible)里维护;原型相对芯片是"均匀变慢+局部跨片抖动+真实介质固定物理时间"三者叠加,软件只要拿到正确的降频频率参数,周期计数语义就自洽,所以启动时间必须以周期计数器为准、按芯片目标频率外推,并扣除不随 FPGA 频率缩放的真实物理固定项和芯片上根本不存在的 TDM 失真,绝不能拿原型墙钟当芯片性能;卡死时按启动管线逐级判位(无迹象查复位/strap、卡 FSBL 查 DDR、卡 SBI 查 IPI、卡内核早期查 DTB 频率与超时、卡 rootfs 查 initramfs/网络),全程用 JTAG 看 PC、先读 build_id 排除镜像/DTB/比特流错配,进 shell 后再上冒烟、复位循环和长稳——做到这些,流片回来时软件往往已在"和真芯片几乎无差别"的环境里跑了几个月。