4. HPS启动流程与Preloader

在SoC FPGA(如Intel Cyclone V、Arria 10)中,HPS(Hard Processor System)的启动并不是“上电即运行Linux”这么简单,而是一条由硬件固化代码、用户可编程引导程序、操作系统加载器逐级接力的链式过程。理解这条链路,是使用SoC EDS生成Preloader、配置引脚复用与SDRAM参数、最终通过SD卡启动系统的前提。本节将沿着“Boot ROM → Preloader → U-Boot → Kernel”的顺序,把每一级的职责、交接方式和关键配置点讲清楚。

一、四级启动链的职责划分

HPS上电或复位后,CPU首先从芯片内部一块只读存储器执行代码,这就是Boot ROM。Boot ROM是芯片出厂固化的,用户无法修改,它的任务是:根据BOOTSEL引脚或熔丝设置确定启动介质(SD卡、QSPI Flash、NAND等),从该介质的固定偏移处读取一个很小的引导头,校验并加载下一级程序到片上RAM(OCRAM)中执行。由于Boot ROM能加载的镜像尺寸非常有限(通常几十KB量级),它无法直接加载Linux内核,因此必须有一个“承上启下”的中间层,这就是Preloader。

Preloader(在U-Boot体系中对应SPL,Second Program Loader)运行在OCRAM中,是用户可完全控制的第一段代码。它承担了大量“把硬件真正初始化起来”的工作:配置时钟树(PLL)、初始化引脚复用(Pin Muxing)、初始化SDRAM控制器并完成内存训练、配置外设引脚、最后从启动介质中把完整的U-Boot镜像加载到SDRAM并跳转执行。可以说,Preloader是HPS启动过程中最“硬件相关”的一环,也是SoC EDS工具链重点辅助生成的对象。

U-Boot运行在SDRAM中,此时内存已经可用,空间不再受限。U-Boot负责更完整的设备初始化(网口、USB、MMC等)、提供命令行交互环境、加载设备树(Device Tree)和Linux内核镜像(zImage或Image),并把控制权交给内核。Kernel启动后完成驱动加载、挂载根文件系统,最终进入用户空间。整条链路可以用下面的信号与数据流图表示。

HPS 四级启动链:介质 → 内存 → 交接 Boot ROM 芯片固化 · 不可修改 读BOOTSEL选介质 加载引导头到OCRAM Preloader (SPL) 运行于OCRAM 时钟/PinMux/SDRAM初始化 加载U-Boot到SDRAM U-Boot 运行于SDRAM 外设初始化 · 命令行 加载DTB + Kernel Kernel 驱动加载 挂载根文件系统 进入用户空间 运行位置:Boot ROM 运行位置:OCRAM (片上RAM) 运行位置:SDRAM 运行位置:SDRAM 启动介质(由 BOOTSEL 选择) SD/MMC 卡 QSPI Flash NAND Flash FPGA 配置 ← 本节以 SD 卡为例 Preloader 阶段的关键配置(SoC EDS 生成时指定) 引脚复用 Pin Muxing SDRAM 参数与训练 时钟 PLL 配置 启动介质偏移

二、用SoC EDS生成Preloader

SoC EDS(SoC Embedded Design Suite)中的bsp-editor是生成Preloader的核心工具。它的工作方式是:读取一个板级描述文件(通常是Quartus工程导出的.sopcinfo或手写的.xml),结合用户选择的配置,生成一个Preloader源码工程,再调用交叉编译器编译出preloader-mkpimage.bin。整个过程可以概括为“选板级描述 → 配参数 → 生成源码 → 编译 → 得到镜像”。

实际操作中,典型命令流程如下(以Linux主机为例):

# 1. 启动 bsp-editor(图形界面)
bsp-editor

# 2. 在界面中:File → New BSP
#    选择 .sopcinfo 文件(来自 Quartus 工程)
#    选择操作系统类型:Linux
#    选择 Preloader 设置

# 3. 配置完成后点击 Generate,生成目录:
#    ~/preloader_bsp/
#      ├── settings.bsp
#      ├── Makefile
#      └── software/preloader/   ← Preloader 源码

# 4. 进入目录编译
cd ~/preloader_bsp
make

# 5. 生成镜像
#    software/preloader/preloader-mkpimage.bin

生成的Preloader源码中,最值得关注的是board/<board_name>/下的板级文件,尤其是iocsr_config_cyclone5.c(引脚复用配置)和pll_config.h(时钟配置)。这些文件的内容直接来自bsp-editor中的设置,如果后续硬件改动(比如换了SDRAM型号或改了引脚分配),需要重新生成或手动修改这些文件。

三、引脚复用与SDRAM参数配置

引脚复用(Pin Muxing)是HPS启动中最容易出错的一环。HPS的许多引脚是复用的:同一个物理引脚可能既可作为GPIO,也可作为UART、I2C、SPI或SDRAM数据线。Preloader必须在访问这些外设之前,把引脚配置到正确的功能上。在bsp-editor中,这一配置通过“Pin Muxing”选项卡完成,用户为每个引脚选择功能。生成的iocsr_config_cyclone5.c中会以寄存器写入的形式体现,例如:

/* 示例:将 HPS_GPIO48 配置为 UART0_RX */
writel(0x00000002, &iocsr->io48);
/* 示例:将 HPS_GPIO49 配置为 UART0_TX */
writel(0x00000002, &iocsr->io49);

如果引脚复用配置错误,最典型的症状是串口无输出——因为UART引脚没有正确复用,Preloader的调试信息根本发不出来。因此,第一次调试时务必先确认UART引脚配置正确,这是观察后续所有启动阶段的“眼睛”。

SDRAM参数配置同样在bsp-editor中完成,主要包括:SDRAM容量、数据位宽、行/列地址位宽、Bank数量、时序参数(tRCD、tRP、tRAS、CL等)、刷新周期。这些参数必须与板上实际焊接的SDRAM颗粒一致。如果参数错误,Preloader在SDRAM初始化阶段就会失败,表现为U-Boot无法加载或系统反复复位。Cyclone V SoC通常使用DDR3,典型参数如:容量1GB、位宽32bit、CL=6、tRCD=6、tRP=6,这些值需要根据SDRAM数据手册填写。

bsp-editor中有一个“SDRAM”选项卡,可以逐项填写时序参数。如果不确定,可以先使用开发板厂商提供的默认值,再根据实际运行稳定性微调。Preloader启动时会打印SDRAM训练结果,如果训练失败会明确报错。

四、通过SD卡启动的完整操作

SD卡启动是SoC FPGA最常用的调试方式,因为烧写方便、可反复修改。SD卡上的分区布局有固定要求:分区3(A2分区)用于存放Preloader镜像,分区1(A1分区)用于存放U-Boot、设备树和内核。具体操作步骤如下:

  1. 分区:用fdisk或parted把SD卡分为两个分区。分区1从偏移1MB开始,格式化为FAT32;分区3从偏移3MB开始,类型为0xA2(裸分区),不格式化。
  2. 写入Preloader:使用dd把preloader-mkpimage.bin写入分区3的起始位置:
    sudo dd if=preloader-mkpimage.bin of=/dev/sdX3 bs=64k seek=0
    sync
  3. 写入U-Boot与内核:把u-boot.img、socfpga.dtb、zImage复制到分区1(FAT32分区)根目录。
  4. 设置BOOTSEL:把开发板上的BOOTSEL拨码开关设置为SD卡启动模式(通常为01010或参考板卡手册)。
  5. 上电观察:连接串口(如115200-8N1),上电后应依次看到Preloader、U-Boot、Kernel的打印信息。

判断启动是否成功,最直接的依据是串口输出。一个正常的启动日志会依次出现:Preloader的SDRAM训练信息、U-Boot版本号与命令行提示符、内核解压与启动信息,最后出现登录提示。如果卡在某一级,就针对该级排查:卡在Preloader通常是SDRAM参数或引脚复用问题;卡在U-Boot通常是镜像文件缺失或分区格式问题;卡在内核通常是设备树与硬件不匹配。

写入Preloader时务必确认/dev/sdX3是SD卡的分区3,而不是主机硬盘。建议先用lsblk确认设备名,避免误写导致主机系统损坏。

五、启动链的验证与常见问题定位

为了快速定位启动失败发生在哪一级,可以建立一个简单的判断路径:上电后串口完全无输出 → 检查BOOTSEL、UART引脚复用、Preloader是否写入分区3;有Preloader输出但无U-Boot输出 → 检查SDRAM参数、U-Boot镜像是否在分区1;有U-Boot但无内核输出 → 检查设备树、内核镜像路径和U-Boot环境变量bootargs。这条判断路径把四级启动链变成了可逐级隔离的调试对象,是实际工程中最常用的排障方法。