news 2026/10/20 8:12:26

FMQL100T SoC FPGA开发实战:从Procise安装到BOOT.bin启动与AXI总线调试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FMQL100T SoC FPGA开发实战:从Procise安装到BOOT.bin启动与AXI总线调试

拿到 FMQL100T 板卡的那天下午,我以为这一轮开发会像以前做纯 FPGA 项目一样顺畅:装好软件、新建工程、写两段 RTL、综合布局布线、下载 bit 流,结束。结果足足耗了三天,才第一次从 JTAG 把整个异构系统跑起来。

复旦微 FMQL100T 是一颗内置 ARM 硬核处理器的 SoC FPGA,和单纯的 FPGA 最大区别就是:芯片上除了可编程逻辑(PL),还带了一个完整的处理系统(PS)。我第一次用这类器件,遇到的坑基本都集中在工具链连通性、启动镜像构造、ARM 与 FPGA 总线交互这几个环节上。这些经验对刚接触 FMQL100T、打算从纯 FPGA 转向 SoC 开发的人来说,应该有点参考价值。这篇文章就按我实际操作的顺序,把安装 Procise 到跑通一个最小工程的关键步骤和踩坑点捋一遍。

1. 先想清楚一个问题:我到底是在做 FPGA,还是在做嵌入式

1.1 FMQL100T 的器件结构给我带来的第一个认知冲击

拿到 FMQL100T 之前,我脑子里的模型还是“FPGA 为主,ARM 只是个辅助”。实际看数据手册和引脚定义的时候才发现,这颗芯片的设计重心完全是反过来的:ARM 处理系统是主控,可编程逻辑更像是一块挂在处理器总线上的“可重构外设”。

FMQL100T 的 PS 端集成了 ARM 处理器核心,支持从多种介质启动,比如 QSPI Flash、SD 卡、JTAG。PL 端是 100T 量级的逻辑资源,可以用来做高速数据通路、硬件加速器、自定义接口协议等。两者之间靠 AXI 总线连接,总线上的地址空间由 PS 端统一管理。

这意味着一个很关键的思路转变:以前做 FPGA,最后交付物是 bit 流;现在做 FMQL100T,交付物是 BOOT.bin,也就是包含 FSBL、FPGA bit 流和用户程序的启动镜像。一个工程能不能跑,并不仅仅是 RTL 仿真综合通过就行的,还得把启动链路完整打通。

1.2 我给自己划定的第一个实战范围

刚开始上手,我给自己定的目标非常保守:不碰 Linux,不碰复杂的多核任务调度,先解决三个问题。

一是把 Procise 装好,能建工程、能综合、能出 bit 流;二是把 bit 流和一段最简单的 ARM 裸机程序打包成 BOOT.bin,能从 JTAG 启动;三是让 ARM 通过 AXI 总线读写 PL 端的寄存器,验证 PS 和 PL 之间的数据通路正常工作。

这个范围看起来简单,但实际操作中牵扯到的东西比预想多得多。特别是工具链的细节:Procise 的许可证管理、芯片型号选择、DDR 初始化、启动模式配置、BIF 文件格式,每个环节单独看都不难,串在一起就容易翻车。

2. Procise 安装与工程创建:我把坑都踩了一遍

2.1 安装前的环境准备和许可证配置

Procise 是复旦微 FPGA 和 SoC 器件的集成开发环境,FMQL100T 的完整开发流程都在这个工具里完成。安装本身不复杂,但有几个细节会直接影响后面能不能用。

我是在 Windows 环境下装的。安装包解压后,第一步要装驱动,这一步千万别跳过。很多新手装完软件直接打开,结果 JTAG 下载器识别不到,其实就是驱动没装好。安装完成后,把下载器插上,打开设备管理器,如果不能看到一个符合规范的串行调试设备,那驱动多半没生效。

许可证这一块踩过的坑比较典型。Procise 和很多商业 EDA 工具一样,许可证文件要和电脑的 MAC 地址绑定。我一开始没注意,拿别人给的通用许可证试了一下,打开软件一切正常,但一跑综合就报 license 错误,提示某个 feature 不可用。后来规规矩矩申请了本机许可证文件,放到工具指定的路径下,才真正解除限制。

操作建议:拿到安装包后,先不要急着安装,先看一遍随包附带的 release note。我在安装时忽略了一份环境变量说明,导致命令行的 procise 命令在 PowerShell 里找不到。需要手动把安装目录下的 bin 路径加入系统 PATH,命令行工具才能直接用。

2.2 创建 PL 工程的细节:时钟和引脚是第一道坎

用 Procise 创建 FMQL100T 工程,流程上和创建普通 FPGA 工程相似,但有两点必须注意。

第一是芯片型号选择。新建工程时,器件选择对话框里要精确选到 FMQL100T 对应的封装和速度等级。如果选错型号,后续的引脚约束文件基本都会报错,管脚物理位置对不上。

第二是时钟输入。FMQL100T 板卡上通常有一颗外部晶振作为 PL 端参考时钟,频率常见的是 50MHz 或者 100MHz,具体数值要看板卡原理图。我一开始默认参考设计里的时钟频率,结果时序分析一堆违规,定位半天才发现板卡的晶振频率和参考工程不一致。

在创建工程的阶段,时钟规划就得想清楚:

  • 板载晶振频率先查原理图,不要猜;
  • 需要用 PLL 或 MMCM 生成多路时钟时,输入时钟频率按照实际晶振配置,否则输出频率全部偏离;
  • 复位信号是高有效还是低有效,也早早在引脚约束里固定下来。

2.3 工程目录结构:哪些文件必须手动留底

Procise 生成的工程目录结构比较常规:源码目录、约束目录、综合结果目录、实现结果目录。但和很多 EDA 工具一样,一次综合跑完后,中间文件非常多。吃过一次亏之后,我给自己定了规矩:以下内容必须手动备份到项目仓库。

一是 RTL 源码和约束文件,这个不用说;二是 BIF 文件(Boot Image Format,启动镜像描述文件),这是生成 BOOT.bin 的关键描述文件;三是最终生成的 bit 流文件;四是 Procise 生成的综合和布局布线报告。中间过程文件比如 .jou 日志、临时网表,这些不用进版本管理,占空间且容易冲突。

备份这个习惯,在后续反复修改启动镜像时帮了我大忙。因为生成 BOOT.bin 的过程并不会每次都重新综合,很多次迭代其实只需要替换用户的应用程序,而 bit 流不变。

3. 从 BIT 流到 BOOT.bin:异构启动链路到底发生了什么

3.1 FSBL、SSBL 和用户程序的分工逻辑

如果只做纯 FPGA 开发,生成 bit 流之后直接用 JTAG 下载就能跑。但在 FMQL100T 上,bit 流只是整个系统的一部分。处理器上电后首先要执行引导程序,引导程序负责初始化 DDR、配置时钟、加载 bit 流到 PL,然后才把控制权交给用户程序。

在复旦微的这套体系里,FSBL(First Stage Boot Loader,第一阶段引导程序)的作用就是初始化最基本的环境,包括 ARM 核心的基础配置、DDR 控制器的初始化、PS 端的引脚复用功能配置,以及将 bit 流数据装载到 PL 端。SSBL(Second Stage Boot Loader,第二阶段引导程序)则是在 FSBL 完成基础工作之后运行。

最开始的裸机工程,其实 SSBL 可以直接就是我的用户程序。比如一个简单的点灯程序,或者一个 AXI 读写的测试程序。FSBL 跑完,跳转到 SSBL 的入口地址,用户程序开始执行,整个异构系统就算正式启动了。

搞清楚这个链路之后,我就理解了为什么 Procise 的软件工程里编译裸机程序时,链接地址往往要指定在 DDR 映射区域。因为 FSBL 已经把 DDR 初始化好了,用户程序是从 DDR 里加载执行的。

3.2 BIF 文件写法与生成 BOOT.bin 的实操要点

生成 BOOT.bin 时,Procise 的 bootgen 工具会读取一个 BIF 文件。这个文件是文本格式,内容非常直白。

一个典型的 BIF 文件内容大致如下:

the_ROM_image: { [bootloader]fmql_fsbl.elf fpga_top.bit app.elf }

第一行是指定 FSBL 的可执行文件,第二行是 FPGA 的 bit 流文件,第三行是应用程序的 ELF 文件。文件顺序不能乱,FSBL 必须在最前面,否则启动时 CPU 跳转地址就错了。

我在这个文件上踩过两个具体问题。第一次是 BIF 文件里的路径用了相对路径,但 bootgen 工具的当前工作目录不对,导致报错找不到文件。改成绝对路径之后,问题解决。第二次是 BIF 文件编码问题,文本编辑器保存成了带 BOM 头的内容,工具解析第一行时报错。解决办法是用纯 ASCII 格式另存。

Procise 里生成 BOOT.bin 的操作入口在工具菜单里,选中 BIF 文件后直接执行生成命令,也可以从命令行调用 bootgen 工具手动生成。手动方式的优势是可以在脚本里集成,批量生成多个版本的镜像。

3.3 启动模式的选择和实战验证顺序

FMQL100T 支持多种启动模式,具体以板卡上的拨码开关状态为准。常见的有 JTAG 启动、QSPI Flash 启动、SD 卡启动。第一次做验证,我强烈建议先用 JTAG 模式,原因有两个。

JTAG 模式不需要预先烧写任何非易失性存储介质,直接通过下载器把启动镜像加载到内存里执行。这意味着我可以把 BOOT.bin 的生成流程跑通,又不用反复擦写 Flash,迭代速度最快。代价是每次上电都需要重新下载,不能独立运行,但这恰恰是前期调试最需要的。

等镜像验证稳定之后,再切换到 QSPI 或者 SD 卡启动,把 BOOT.bin 固化到介质里。固化之后如果发现启动失败,排查起来会比 JTAG 模式麻烦得多,因为你很难看到一个即时的错误反馈。

注意:切换启动模式之前,一定先断电操作。FMQL100T 的启动模式引脚在上电瞬间采样,带电切换拨码开关不会生效,甚至可能因为引脚电平跳变引起异常。

4. 让 ARM 和 FPGA 真正对话:AXI 总线的工程化写法

4.1 用 AXI BRAM 快速验证 PS 到 PL 的数据通路

系统刚跑起来,第一件想做的事,就是确认 PS 端能不能读写 PL 端的东西。FMQL100T 的 PS 和 PL 之间通过 AXI 总线连接,最简单可靠的验证方案是例化一个 AXI BRAM 控制器,把 BRAM 挂在 PS 端地址空间的某个区间上。

大概的流程是这样:在 Procise 的 FPGA 工程里添加 AXI BRAM Controller IP,把地址区间的起始地址设为某个固定的基地址,比如 0xA0000000。综合布局布线后生成 bit 流,带入 BOOT.bin。ARM 端程序里,直接对这段地址进行读写操作:

#define PL_BRAM_BASE 0xA0000000 void bram_test(void) { volatile unsigned int *p = (volatile unsigned int *)PL_BRAM_BASE; unsigned int val; p[0] = 0x5A5A5A5A; asm volatile("dmb"); // 确保写操作完成 val = p[0]; if (val == 0x5A5A5A5A) { // 读写一致,通路正常 } }

这段代码虽然简单,但背后有个隐性问题在 4.3 里讲。先记住,验证 PS 到 PL 数据通路时,读写的数据最好带明显的特征值,比如 0x5A5A5A5A、0xA5A5A5A5,比随便写个变量值更容易定位问题。

从实际操作的层面看,AXI BRAM 这个方案的优势是几乎没有额外的时序风险。BRAM 本身是 FPGA 内的存储单元,AXI BRAM 控制器把总线协议转换成 BRAM 接口时序,逻辑简单,速度不高时基本不会出现时序收敛问题。

4.2 地址分配方式:固定映射和动态映射的取舍

给 PS 端外设分配地址,在 FMQL100T 上无非两种思路。一种是直接把 PL 端外设映射到固定的地址区间,ARM 程序里用绝对地址访问;另一种是通过地址转换机制动态映射,更灵活但也更复杂。

裸机程序阶段,我选择的是固定映射。原因很简单:裸机程序没有 MMU,所有地址都是物理地址。固定映射让指针操作直接落在总线地址上,代码直观,调试时用下载器的内存查看窗口也能直接观测数据变化。

从 AXI BRAM 验证到自定义外设,地址分配的思路是一致的。我习惯把寄存器组的基地址规划在一段连续区域,比如 0xA0000000 到 0xA0000FFF 放控制寄存器区,0xA0001000 之后放数据缓冲区区。这样规划,后续 ARM 端访问时,只需要把基地址宏定义好,其余操作都通过偏移量完成。

4.3 Cache 一致性:读回全 0 的诡异问题

这一步是实际调试中最让我抓狂的问题。ARM 端通过 AXI 总线写 PL 端 BRAM,写完之后立刻读同一个地址,结果读回来的数据是全 0。反复检查地址、总线路由、寄存器配置,都没发现问题。

后来才意识到,这很可能和 CPU 的缓存相关。处理器核心对内存的读写会经过一级或二级缓存,PL 端 BRAM 映射在内存地址空间里,CPU 写操作写在缓存里,尚未回写到实际内存,紧接着读操作命中的还是缓存。但在这个场景里,读回全 0 还有一种可能:我访问的地址范围被配置成了写回缓存属性,写的值留在在 cache line 里,总线上的 BRAM 实际没有收到数据。

解决方案有两种。最直接的是把这段地址空间配置成非缓存属性,也就是关闭该内存区域的 cache 功能。在不同的裸机平台上有对应的属性配置表项,修改之后,读写操作每次都直接走总线。另一种是软件上做 cache clean 和 invalidate,写完之后把 cache 数据回写,读之前把 cache 无效化。

经验:前期调试 AXI 通信,先关闭对应地址空间的缓存,或者用 volatile 修饰指针,并且穿插必要的内存屏障指令。等通信逻辑完全正确之后,再研究优化性能时的缓存策略。

4.4 自定义 AXI Lite 外设的编写要点

AXI BRAM 验证通过以后,下一步就是写一个自己的寄存器型外设,比如一个带使能寄存器和状态寄存器的硬件模块。

编写自定义 AXI Lite 从设备最关键的是理解寄存器读写时序。AXI Lite 协议比 AXI Full 简单,不支持突发传输,每次读或写只传输一个数据。寄存器内部信号如下:

// 写地址通道 axi_awvalid -> register write address axi_wvalid -> register write data // 写响应通道 awready, wready, bvalid, bresp // 读通道 axi_arvalid -> register read address axi_rvalid -> register read data axi_rdata -> read data bus

一个常见的错误是把 AXI 的握手信号处理成交互不关联的独立事件。实际上,AXI Lite 协议要求地址通道和数据通道独立握手,而且写响应必须在写地址和写数据都完成之后给出。如果写响应逻辑写得太简单,只看着写地址完成就给 bvalid,那么后续总线上挂多个设备时很容易出协议问题。

设计寄存器模块时,把每一类寄存器按功能分组很有用。控制寄存器放使能位、复位位;状态寄存器放硬件运行状态;数据寄存器放需要 CPU 处理的数值。这样 ARM 端程序写起来结构清晰,也方便写成驱动层函数。

5. 实测排坑记录:这几天里面试过的几个具体问题

5.1 启动镜像生成时报文件找不到,问题出在路径和格式

现象:使用 Procise 生成 BOOT.bin 时,报错信息提示某个 ELF 文件或 bit 文件找不到,但文件明明就在指定的目录里。

排查链路很简单。先看 BIF 文件里的路径是否包含空格或中文字符,这两个因素在某些版本的工具里都会导致解析异常。再看路径分隔符是否统一,Windows 下有时用反斜杠,有时用正斜杠,混用会让工具识别出问题。最后检查文件编码,用支持 BOM 的编辑器另存为无 BOM 格式。

修复顺序按上述排查顺序执行即可。我的实际情况是 BIF 文件编码带了 BOM 头,工具解析首行时把 BOM 字符当成文件名的一部分,导致找不到 FSBL 文件。看起来像路径问题,实际上完全是编码问题。

5.2 DDR 初始化报错,实际上和 PL 端时钟约束有关

现象:FSBL 执行到 DDR 初始化阶段卡住,或者报出内存控制器初始化失败的提示。单独验证 DDR 相关的参考设计时又是正常的。

这个坑比较隐蔽。FMQL100T 的 DDR 初始化由 FSBL 完成,但某些板卡设计会把 DDR 参考时钟和 PL 端锁相环的参考时钟关联在一起。FSBL 初始化 DDR 时,如果对应的 PL 端时钟资源没有配置好,整个初始化时序就会异常。

解决方向是把工程里的时钟约束补完整。尤其是当 PS 和 PL 有共享时钟引脚时,不要因为只用 PS 端的功能就忽略 PL 端的时钟约束。综合和实现时的时序约束文件,把所有输入时钟、生成时钟的约束写完,确保 FSBL 运行之前的硬件状态是可预期的。

5.3 AXI 读回全 0 或全 1,第一反应不应该是怀疑总线

现象:PS 端写 PL 寄存器,PL 端手动改寄存器值,PS 端读回来的还是 0 或者 1。

这个问题的可能性非常多,但根据我的经验,排查顺序应该是:先排除缓存一致性,再查地址映射是否落在实际存在的逻辑地址区间,然后查 AXI 从设备是否真的完成了读写响应。最容易被忽略的是第二个:地址映射区域对应的地址解码器使能信号是否拉高。如果地址区间匹配但寄存器模块的时钟没有启动,写操作不会生效,读操作返回默认值。

调试时最有用的手段是逻辑分析仪和 Vivado 自带的硬件管理器之类的调试功能。挂一个 ILA 核在 AXI 接口上,直接看握手信号,比肉眼读代码靠谱得多。

5.4 PL 端引脚电平标准不匹配,导致外部设备毫无响应

现象:PL 端引脚通过阻容或直接连接到外部芯片,逻辑电平已经配置,但外部设备完全没有反应。

排查后发现是引脚的电平标准选择错误。FMQL100T 的不同 IO BANK 支持的电平标准不同,有的 BANK 只能支持 1.8V,有的支持 3.3V。引脚约束文件里如果不显式指定电平标准,工具可能按默认值处理,这时如果板卡外部电路按照 3.3V 设计而内部默认是 1.8V,就会出现电平不匹配。

这个问题的典型特征就是“功能逻辑没问题,物理层没反应”。解决方法是查原理图确认对应 BANK 的供电电压,然后在引脚约束文件里明确设置电平标准、驱动能力和上下拉属性。

6. 后面拿 FMQL100T 做进阶工程时,我的规划

6.1 裸机验证之后,Linux 系统带来的复杂度增量

在裸机环境里验证通了 AXI 通路,下一步就该面对 Linux 了。FMQL100T 跑 Linux 相比裸机复杂的地方,主要体现在启动链路的层次增多:FSBL 之后加载的 SSBL 变成了 U-Boot,U-Boot 再加载内核和设备树,设备树里要描述 PL 端的 IP 资源,驱动层才能正确访问硬件外设。

我给自己留的这几个进阶任务顺序是:先在 Linux 下把 BOOT.bin 启动链路跑通,然后重点研究设备树里 PL 端外设节点的写法,最后再考虑中断控制器和 DMA 的接入。每一步之间的依赖关系很强,跳着学容易两头不讨好。

6.2 我习惯的迭代管理方法

整个 FMQL100T 开发流程跑下来,我对自己最大的提醒是:把 RTL 工程、裸机程序、BIF 文件、脚本打包成一个整体来管理版本。因为这三者之间是耦合的:RTL 改了,bit 流变了,BIF 文件里引用同一个 bit 文件;裸机程序改了,BIF 文件里的应用 ELF 跟着变。

我现在的做法是每次迭代都生成一个新的 BOOT.bin 并命名保留,命名规则包含日期和功能标识。这样出了问题可以快速回退到上一个验证通过的镜像,而不是在代码里反复找原因。

6.3 最后给新上手同型号芯片的人一个建议

如果你也有 FMQL100T 板卡在手边,刚装好 Procise,我的建议是不要一上来就想着搭一个完整的大工程。先把 JTAG 模式跑通,再跑一个最小的 BOOT.bin,哪怕里面只有一个点灯程序都行。整个工具链的连通性是所有后续开发的前提,这一步顺畅了,后面遇到的更多是算法和系统设计层面的问题,而不是环境问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 8:11:59

模型越强,Prompt和Skill越该做减法

模型变强之后,Skill 和 Prompt 该做减法了最近几个月,我花了不少时间在做同一件事:把之前精心调试过的 Prompt 和 Skill 拆开,逐行审视,然后删掉一大半。起因是一次实测经历——我拿一个打磨了好几周的工作流去跑新版本…

作者头像 李华
网站建设 2026/10/20 8:10:32

从日报Skill看AI技能设计:提示词、流程模板与可复用智能体实战

1. 一个skill到底长什么样很多人以为skill是某种神秘的高阶玩法,得会编程才能碰。其实拆开看,一个skill就是“把一段固定的做事流程,打包成一个可复用的模板”。日报skill听起来很小,却是最好的解剖样本——因为它流程固定、输入简…

作者头像 李华
网站建设 2026/10/20 8:10:20

AGI核心突破已实现?从技术切面到工程落地的冷静解读

最近AI圈里讨论最频繁的一个判断,是某头部AI机构前首席研究官公开表示:AGI的核心突破已经实现。我第一眼看到这句话时,脑子里冒出来的不是“信”或“不信”,而是先把说话人身份和场景过了一遍。一个还坐在牌桌上的人说“我们已经赢…

作者头像 李华
网站建设 2026/10/20 8:10:20

Jmeter接口自动化测试完整指南:从脚本设计到Jenkins集成

做接口自动化的同学,十有八九都绕不开Jmeter。我最早用Jmeter只是为了压测,后来发现拿它做接口自动化其实非常顺手——不用写一行代码就能把流程跑通,又能通过Beanshell、JSR223补上复杂逻辑,测试计划和脚本本身还能当作团队协作的…

作者头像 李华
网站建设 2026/10/20 8:08:27

AI专著写作提效指南:从资料管理到Markdown的工具链实践

写AI方面的专著,最难的地方往往不在“懂不懂AI”,而在“怎么写完”。我身边不少朋友一开始满怀热情,列了很详细的目录,结果写到第三章就卡住了。原因很一致:资料越攒越乱,术语前后对不上,图表改…

作者头像 李华