篇首
最近突发奇想,Xilinx 的集成开发环境已经很好了,很多必要的代码都直接生成了,这给开发者带来了巨大便利的同时,也让人错过了很多代码的精彩,可能有很多人用了很多年了,都还无法清楚的理解其中过程。博主准备以FSBL为例,与大家深入探讨一番,从而加深对ZYNQ的加载过程的理解,以便大家作出更精彩的设计!
** 通览 FSBL **
一、FSBL工程结构与核心文件
SDK生成的FSBL工程目录通常包含以下关键文件:
-
main.c
- 入口函数:
main()
,控制整个启动流程。 - 核心逻辑:初始化硬件 → 加载镜像 → 跳转到SSBL(如U-Boot)。
- 入口函数:
-
ps7_init.c
/ps7_init.h
- 硬件初始化:由Xilinx工具自动生成,包含Zynq PS(处理器系统)的底层配置代码,如:
- DDR控制器参数(
DDRSetup()
) - 时钟配置(
ClockConfig()
) - MIO(多功能IO)引脚初始化(
MIOInit()
)
- DDR控制器参数(
- 硬件初始化:由Xilinx工具自动生成,包含Zynq PS(处理器系统)的底层配置代码,如:
-
asm_vectors.S
- 异常向量表:定义ARM Cortex-A9的异常处理跳转指令,例如:
_vector_table: B _boot // 复位异常 B Undef_Handler // 未定义指令异常 B SVC_Handler // SVC调用 ...
- 异常向量表:定义ARM Cortex-A9的异常处理跳转指令,例如:
-
xil_exception.c
- 异常注册:通过
Xil_ExceptionRegisterHandler()
将自定义处理函数绑定到异常ID。
- 异常注册:通过
-
xil_io.h
/xil_printf.h
- 底层操作:提供内存读写(
Xil_Out32()
)、调试输出(xil_printf()
)等基础函数。
- 底层操作:提供内存读写(
二、FSBL工作流程详解
从复位到跳转至SSBL的全流程:
-
复位与BootROM阶段
- Zynq上电后,BootROM自动执行,检测启动设备(QSPI/SD/NAND),加载FSBL到OCM(On-Chip Memory)。
-
FSBL入口(
_boot
标签)- 汇编初始化(
asm_vectors.S
):- 设置栈指针(
SP
指向OCM)。 - 跳转到
main()
函数(C语言入口)。
- 设置栈指针(
- 汇编初始化(
-
硬件初始化(
main()
函数内)- 调用
ps7_init()
(位于ps7_init.c
):int main() { ps7_init(); // 初始化DDR、时钟、MIO等 ... }
- 关键配置:
- DDR控制器参数(决定内存地址映射)。
- UART初始化(若启用调试输出)。
- 调用
-
注册异常处理函数
- 代码片段(
main.c
中):Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_DATA_ABORT, DataAbortHandler, NULL); Xil_ExceptionEnable();
- 作用:确保DDR初始化失败等错误能被捕获。
- 代码片段(
-
加载并验证镜像(
BOOT.BIN
)- 步骤:
- 从启动设备读取镜像头(解析分区信息)。
- 按分区加载SSBL(如U-Boot)、PL比特流(
.bit
)到DDR。 - 若启用加密或签名,执行解密和认证。
- 步骤:
-
配置PL(可选)
- 代码触发:若镜像中包含PL比特流,调用
XFsbl_LoadPLBitstream()
通过PCAP接口配置FPGA逻辑。
- 代码触发:若镜像中包含PL比特流,调用
-
跳转到SSBL
- 关键操作:
void (*SSBL)(void) = (void (*)(void))SSBL_ENTRY_ADDR; SSBL(); // 跳转到U-Boot
- 关键操作:
三、核心函数代码解析
-
ps7_init()
函数(硬件初始化)- 功能:配置PS端所有关键硬件模块。
- 代码结构:
void ps7_init() { ps7_mio_init(); // MIO引脚配置 ps7_clock_init(); // PLL和时钟分配 ps7_ddr_init(); // DDR控制器参数 ps7_peripherals_init(); // 外设使能(如UART、QSPI) }
-
Xil_ExceptionRegisterHandler()
(异常注册)- 参数解析:
ExceptionId
:异常ID(如XIL_EXCEPTION_ID_DATA_ABORT
)。Handler
:自定义处理函数指针(如DataAbortHandler
)。CallBackRef
:传递给处理函数的参数(通常为NULL
)。
- 参数解析:
-
LoadBootImage()
(镜像加载)- 逻辑分支:
if (ImageHeader->EncryptionFlag) { DecryptImage(); // AES解密 } if (ImageHeader->AuthenticationType == RSA) { VerifyRSASignature(); // RSA签名验证 } LoadPartitions(); // 加载分区到内存
- 逻辑分支:
四、调试与问题排查
-
启用调试输出
- 方法:在
xparameters.h
中定义FSBL_DEBUG_INFO
,并确保UART初始化正确。 - 输出示例:
XFsbl_LoadPLBitstream: PL configuration started XFsbl_LoadPLBitstream: PL configuration success
- 方法:在
-
常见错误与解决
- DDR初始化失败:
- 检查
ps7_init.c
中的DDR参数是否与硬件匹配(容量、时序)。
- 检查
- 镜像加载超时:
- 确认启动设备(如SD卡)连接正常,镜像文件
BOOT.BIN
未损坏。
- 确认启动设备(如SD卡)连接正常,镜像文件
- PL配置错误:
- 检查比特流文件是否为Zynq 7000兼容格式,PCAP接口是否使能。
- DDR初始化失败:
五、扩展开发指南
-
添加自定义初始化代码
- Hook函数:在
main.c
的main()
函数中插入代码:ps7_init(); Custom_Init(); // 用户自定义外设初始化 LoadBootImage();
- Hook函数:在
-
跳过PL配置加速启动
- 修改代码:注释掉PL加载相关代码:
// if (BitstreamPresent) { // XFsbl_LoadPLBitstream(); // }
- 修改代码:注释掉PL加载相关代码:
-
实现自定义异常处理
- 示例:在数据中止异常中记录错误地址:
void DataAbortHandler(void *Callback) { u32 FaultAddr; __asm__("MRC p15, 0, %0, c6, c0, 0" : "=r" (FaultAddr)); xil_printf("Data Abort at 0x%08X\n", FaultAddr); FsblHookFallback(); }
- 示例:在数据中止异常中记录错误地址:
六、总结
SDK生成的FSBL代码是一个高度模块化的启动框架:
- 硬件初始化由
ps7_init()
全权负责,开发者需关注硬件参数配置; - 异常处理通过绑定向量表实现,是系统稳定的第一道防线;
- 镜像加载流程可定制(加密、多分区),但需严格遵循Xilinx镜像格式规范。
理解这些细节,可从容应对启动失败、性能优化、安全加固等实际挑战!