这一篇在干嘛?

你的 FPSoC 设计(比如 DR1 系列)在 TD 里综合、布局布线完之后,PS 端那些时钟、IO、DDR 的寄存器配置还只存在于 TD 工程里,嵌入式软件完全”不知道”。HPF(Hardware Platform File)就是把这份硬件信息打包递给 FD(嵌入式集成开发环境)的快递箱。本篇基于官方《HPF Tool User Guide》(SWUG_503,TD5.9.1 / 2024.7),讲清导出操作、包内文件分工和软件侧的正确用法。同系列目录见 HPF 工具首页

HPF 是什么,为什么需要它

导出流程:从 TD 工程到 .hpf 文件

拆包:HPF 里到底装了什么

核心文件 soc_plat.c:PS IP 的寄存器初始化

soc_plat.tcl 与 FD 调试:Run Platform Init

soc_plat.html 与 driver:查配置、拿基地址

HPF 是什么,为什么需要它

FPSoC 应用开发是一条”硬件 + 软件”的流水线:硬件工程师在 TD 里搭好 PS IP 和 PL 逻辑,软件工程师在 FD 里写跑在 CPU 上的程序。问题来了——PS 端上电后并不是”开箱即用”的:PLL 要配、外设时钟要开、PS IO 要设复用功能、DDR 要按你板子上那颗颗粒的时序初始化。这些配置全部来自 TD 工程里 PS IP 的图形化配置界面,软件侧无从得知。

HPF 文件就是这条信息通道:它把硬件设计信息、硬件配置信息从 TD 传递到软件设计。

HPF 在硬件设计与软件设计之间传递信息

具体来说,HPF 里携带的信息分两大类:

  • 硬件配置信息:主要是 PS 端的寄存器配置,包括 clock(PLL 配置、外设时钟配置)、psio(PS 侧 IO 复用)、ps-pl(PS 与 PL 之间的接口总线)、ddr、peripheral(外设)、debug,以及 PL IP 驱动等部分。
  • 硬件设计信息:器件信息、比特流文件、pl2ps 码点信息等。

一个重要前提:该功能仅支持 FPSoC 系列芯片(如 DR1 系列)。原因很直白——其他 FPGA 器件里根本没有 PS IP,没有 PS 寄存器可配,自然也就没有 HPF 之说。如果你的竞赛设计只用 EG4S20 这类纯 FPGA 器件,这一套工具用不上;一旦切到 DR1 这类 FPSoC 做软硬协同设计,HPF 就是必经之路。

导出流程:从 TD 工程到 .hpf 文件

第一步:确保工程里有且只有一个 PS IP

HPF 的导出依赖工程中包含 CPU 资源的设计。这里有一条硬性规则:

常见坑:PS IP 数量不对,导出按钮就"罢工"

  • 设计中不包含 PS IP 时,TD 顶部菜单的 “Export Hardware Platform File” 按钮是灰色的,点了没反应——先检查是不是忘了加 PS IP。
  • 设计中包含两个或以上 PS IP 时,TD 会直接报错提醒。一个 SoC 只有一颗处理子系统, duplicated 的 PS IP 没有意义,TD 也不允许。

第二步:进入导出页面

操作路径在 TD 顶部菜单栏:

  1. 点击 “Project” 菜单。
  2. 在弹出的选项卡中点击 “Export Hardware Platform File”,进入导出功能页面。

点击 Export Hardware Platform File 按钮示意图

第三步:在导出页面完成三件事

导出页面本身非常简单,只有三个选项:

导出 HPF 操作流程

  1. 选择是否将比特流文件包含在 HPF 文件中(这个选择有讲究,见下)。
  2. 选择保存 HPF 的路径
  3. 点击 “OK”,完成导出。

要不要包含比特流?

这是导出时唯一需要动脑的选择。官方规则是:以下情况必须包含比特流文件——

  • 在设计中使用了 PL 端的逻辑资源。比特流要随 HPF 一起交给软件侧,最终由软件流程加载 PL。
  • 使用了 Fast AHB 接口,需要传递码点信息(这一条仅针对包含 RISCV 的器件)。pl2ps 的码点信息也打包在比特流/HPF 里传递。

反过来说,如果你的设计纯粹只用 PS、PL 完全空闲(很少见),才可以选择不含比特流。拿不准就选包含,多带不会出错,少带必出问题。

保存位置与文件名

  • 保存位置不限制,默认为 TD 的工程目录下。
  • 文件名自动生成,规则为:TD 工程名.hpf
  • 导出成功后会弹出提示窗口,里面标注了文件的保存位置,找不到文件时回来看这个提示即可。

拆包:HPF 里到底装了什么

HPF 文件的本质是一个 zip 压缩包——是的,你可以直接把后缀改成 .zip 解压,或者用解压软件打开。解压后的内容如下:

HPF 文件解压后包含的文件

每个文件的职责见下表(保留原书表 3-1):

项目文件名描述
寄存器配置soc_plat.c.c 和 .h 文件将用户在 IP 配置界面的配置项对应到寄存器配置,供 FSBL 使用
寄存器配置soc_plat.h(与 soc_plat.c 配套的头文件)
寄存器配置soc_plat.tcltcl 文件用于 OpenOCD 初始化 SoC
配置结果可视化soc_plat.html通过表格的方式将配置结果呈现出来
硬件描述文件design.xml描述了硬件的器件信息、外部端口、接口、模块信息
比特流文件projectName.bitFPGA 比特流文件

常见坑:把 HPF 当"黑盒"直接拷走就完事

HPF 不是给 FD “看一眼”的普通文件,软件侧真正消费的是包内的 soc_plat.c/.tcl。如果不理解每个文件的分工,很容易出现”APP 跑不起来、外设没反应”却查不到原因的情况——多数时候是 PS IP 压根没被初始化(见下一节)。

核心文件 soc_plat.c:PS IP 的寄存器初始化

HPF 中最重要的是 soc_plat.c 和 soc_plat.tcl 中的寄存器配置,作用是初始化 PS IP,让它进入正常工作状态。这一节只讲 .c/.h,.tcl 单独一节讲。

关键函数 Soc_PlatInit()

嵌入式开发环境(FD)的 SDK 中并没有隐式初始化 PS IP 的寄存器——SDK 不会替你做这件事,需要你显式在代码中调用:

Soc_PlatInit();

这个函数定义在 soc_plat.c 中,把你在 IP 配置界面勾选的所有配置项,翻译成一条条寄存器写操作。

但注意适用范围:这种”代码里调用”的方式仅适用于运行在非 DDR 空间的 APP,例如跑在 TCM、OCM、FLASH 里的程序。逻辑是:程序自己管自己的初始化,上电先跑 Soc_PlatInit() 把 PS 配好,再继续跑业务代码。

如果 APP 运行在 DDR,则无需(也不能)在代码里加入该函数——因为 APP 要先被加载到 DDR 里才能运行,而加载 APP 本身就要求 DDR 已经处于正常工作状态。所以 DDR 的初始化必须发生在 APP 加载之前,由加载流程(如 FSBL)负责完成,轮不到 APP 自己来。

配置项 → 代码:七个部分

你在 PS IP 配置界面里的每一类配置,都会对应生成 soc_plat.c 中的代码段:

1. PSIO。PS 侧的 IO 具备复用功能,每个 IO 都要设置对应的参数和功能。配置界面长这样:

PSIO 参数配置示意图

对于每一个 IO,都会生成对应的寄存器配置代码。

2. Clock。在 IP 界面中配置各个模块(CPU、SDIO 等)的时钟后,系统会自动生成 PLL 寄存器配置代码、各个模块时钟分频系数的代码:

Clock 时钟配置界面

3. PS_PL。使能 PS_PL 接口后,自动生成的主要是总线使能代码。配置界面如下:

PS_PL 配置界面示意图

生成的代码是标准的”寄存器写序列”格式,值得仔细读一遍,因为它揭示了这套机制的底层原理(原书图 3-6 代码):

/* (3) ps_pl data */
UL ps_pl_0[] = {
    // Config PLS_PROT, gp normal access to pl
    // register: PLS_PROT.gp_proten = 0x0
    // 0xF8800080[1:1] = 0x0
    CONFIG_REG_MASK(0xF8800080, 0x00000002, 0x00000000),
    // Config CRG_EN, pl2ps max fbk info: 0
    // register: FAHB_DPLL_PARA.fbk = 0x00000000
    // 0xF8801058[31:24] = 0x00000000
    CONFIG_REG_MASK(0xF8801058, 0xff000000, 0x00000000),
    // Config CRG_EN, pl2ps max ref info: 0
    // register: FAHB_DPLL_PARA.ref = 0x00000000
    // 0xF8801058[23:16] = 0x00000000
    CONFIG_REG_MASK(0xF8801058, 0x0ff0000, 0x00000000),
    /* configuration complete */
    CONFIG_REG_EXIT()
};

读法:每条 CONFIG_REG_MASK(地址, 掩码, 值) 对应一次”按掩码写寄存器”的操作——注释里写明了目标寄存器名、位段和物理地址(如 0xF8800080 的 bit1),最后以 CONFIG_REG_EXIT() 标记配置序列结束。所谓”图形化配置 PS IP”,最终就是生成这样一张寄存器操作表。

4. DDR。系统按照 DDR 配置页面填的参数生成 DDR 初始化代码,用户配置的参数以的形式传递到 DDR 初始化代码中。

5. Peripherals。根据你选择的模块配置,自动生成某些外设正常工作所必需的代码。

6. Debug。根据 debug 模块的参数自动生成对应模块的初始化代码。

7. System。生成让 SoC 系统工作在最佳正常状态的初始化代码。

常见坑:忘了调用 Soc_PlatInit()

如果没有调用 Soc_PlatInit(),PS IP 未被初始化,某些外设的功能可能不正常——而且这种”不正常”往往不是完全瘫痪,而是个别外设行为诡异,非常难查。如果你的 APP 跑在 TCM/OCM/FLASH 且不经过 FD 的 Platform Init 流程,务必在 main 最早处调用它。

soc_plat.tcl 与 FD 调试:Run Platform Init

soc_plat.tcl 的内容与 soc_plat.c 功能完全一样,区别只是实现方式:它用 tcl 脚本去写 PS IP 的寄存器,供 OpenOCD 调用,实现在运行用户 APP 之前初始化 PS IP 寄存器。

使用入口在 FD 的调试配置里——勾选 “Run Platform Init” 功能后,FD 会在加载被调试(或运行)的 APP 之前,执行对应 Platform 工程下的 soc_plat.tcl 文件:

Run Platform Init 界面

于是问题来了:初始化到底该放在 APP 代码里(Soc_PlatInit())还是放在调试器的 tcl 里(Run Platform Init)?官方给出的规则是二选一,绝不重复

  • 如果 APP 代码中没有调用 Soc_PlatInit() → 调试时必须使能 Run Platform Init,否则 PS 没人初始化。
  • 如果 APP 中显式调用Soc_PlatInit()切勿使能 Run Platform Init,否则 PS IP 寄存器被二次初始化,某些外设会工作异常——手册特别点名了 DDR 这类对初始化时序敏感的模块。

可以画一张速查表:

APP 位置初始化方式Run Platform Init
跑在 TCM/OCM/FLASH代码里调用 Soc_PlatInit()关闭
跑在 TCM/OCM/FLASH不调用,靠调试器开启
跑在 DDR由加载流程在加载前完成按 FD 工程实际配置,不能与代码内初始化叠加

soc_plat.html 与 driver:查配置、拿基地址

soc_plat.html:配置结果的人读版

该文件以 html 格式记录了 PS IP 的寄存器配置结果,用于配置结果的检查。用浏览器打开,就能以表格方式查看所有寄存器的配置。页面顶部有分类条目:PSIO TABLE、DDR、CLOCK、REGISTER VIEW,点击即可跳到对应的配置详情。调试寄存器问题时,先翻这个文件比反汇编 soc_plat.c 快得多。

driver:PL IP 的驱动信息

driver 文件夹中包含 PL IP 的驱动信息(仅针对 AXI 总线型 IP):该 IP 在 SoC 地址空间中的基地址、地址空间范围、中断号等信息。写裸机驱动或 Linux 设备树时,这些就是你 ioremap 和挂中断要用的参数。注意:该功能与软件版本有关,以你实际使用的版本为准。

通关标准:

  • 能不看资料,在 TD 中完成”含 PS IP 工程导出 HPF”的全流程,并说出必须包含比特流的两种情况。
  • 能把 .hpf 解压,说清 soc_plat.c / .h / .tcl / .html、design.xml、.bit 各自的角色。
  • 能针对”APP 跑在 OCM”和”APP 跑在 DDR”两种场景,分别给出正确的 PS 初始化方案(代码内 or Platform Init),并解释为什么两者不能同时使用。