做国产FPGA的AI推理,最难的不是写代码,而是从零开始搭一个能跑通的软硬件协同系统。复旦微FMQL100TAI900这块板卡我前后折腾了快两个月,踩了不少坑,也把国产化器件清单理了一遍。这篇文章就把这套从硬件搭建到模型部署的完整流程拆开讲清楚,顺便把网上很少有人说透的几个关键问题——DSP资源分配、AXI总线带宽瓶颈、DDR读写调度、复位亚稳态处理——全部交代明白,给准备上手国产SoC FPGA做AI加速的工程师一条可以直接参考的路径。
1. 项目整体设计与方案选型思考
1.1 为什么选FMQL100TAI900做AI推理原型
先说结论:FMQL100TAI900不是性能最强的FPGA,但它是目前国产SoC FPGA里生态相对成熟、适合做AI推理原型验证的器件之一。它内部集成了四核ARM Cortex-A53处理器和可编程逻辑阵列,这个架构和Xilinx Zynq UltraScale+的思路是同一个路子——PS(处理系统)负责跑Linux、做任务调度和模型解析,PL(可编程逻辑)负责做卷积、池化这些计算密集型的操作。
选这块板子做AI推理原型,核心原因有三个:
第一,软硬件协同的开发模式对原型验证太友好了。AI推理拆开来看,既有一堆矩阵乘加运算,又有大量数据搬运和逻辑控制。全部丢给ARM跑,性能不够;全部用逻辑实现,开发周期又太长。FMQL100TAI900让我可以把YOLO这种模型拆开——卷积层给PL做,NMS后处理给ARM做,两边通过AXI总线高速交互,这种分工特别适合快速出原型。
第二,逻辑资源规模适合做中小型模型的推理加速。AI推理加速器本质上就是大量并行的乘加单元,而FMQL100TAI900集成了数百个DSP硬核切片和足够多的BRAM块,做INT8量化的YOLOv5s或者ResNet50这类模型,资源刚好卡在“能放得下但需要仔细规划”的区间。这个区间其实是最有工程价值的,因为真到了资源特别富余的平台上,很多电路设计问题会被资源冗余掩盖。
第三,国产化需求是硬指标。如果你做的项目有国产化率要求,FMQL100TAI900作为复旦微SoC FPGA旗舰型号,在供应链安全上比进口芯片有明显的不可替代性。这一点在政务、电力、安防等关键基础设施领域的边缘AI设备上尤为重要。
1.2 选型之前的必做功课:拆解AI推理的算力需求
我不是上来就拍板买板卡的。在选型之前,先做了一个算力需求拆解,把“AI推理”这个宽泛的概念落地成可量化的指标。
以YOLOv5s模型为例,输入分辨率640x640,INT8量化之后,单帧推理的运算量大约在16 GOPS(Giga Operations Per Second)左右。如果目标帧率是30 FPS,那么理论算力需求就是16乘以30,等于480 GOPS左右。但这是理论值,实际电路不可能达到100%的DSP利用率,还得预留数据搬运、控制开销这些损耗。
FMQL100TAI900的DSP硬核如果跑在200MHz,每个时钟周期做一次乘加运算,假设能布置200个DSP并行计算,那么理论算力大概是200乘以200MHz乘2(乘加算两次操作),等于80 GOPS。这个数字对比480 GOPS的需求,确实差了一个量级。所以一开始就要明确——这块板卡适合做AI推理的算法验证、数据通路测试、时延摸底,不适合追求高帧率的实时视频流分析场景。
这个判断非常重要,它决定了整个项目的预期管理。如果非要用它跑实时30FPS的YOLOv5s,那从一开始就走错了方向。实际上,我用它跑INT8量化的YOLOv5s,实测单帧推理时间在200毫秒到500毫秒之间浮动,5FPS左右,做原型验证和功能演示已经完全够了。
1.3 整体架构设计:一张图看清数据流向
整个AI推理系统的架构分为PS端和PL端两大部分。
PS端是四核ARM Cortex-A53,跑Linux系统,负责三件事:一是通过文件系统读取训练好的模型权重文件和输入图片;二是把图片预处理成模型需要的格式,比如缩放、归一化、通道重排;三是调用PL端的加速器做推理,然后接收推理结果做后处理,比如NMS去重、类别的置信度过滤。
PL端是FPGA逻辑部分,负责实际的计算密集任务。我设计了四个核心模块:DMA控制器负责从DDR里搬运图像数据到PL内部的BRAM;卷积计算阵列负责执行3x3卷积、1x1卷积和全连接层的乘加运算;池化与激活模块负责ReLU、最大池化这些操作;输出缓冲区负责暂存推理结果。
PS和PL之间的数据通路用的是AXI总线协议,PS端通过AXI-Lite接口配置PL端的寄存器,通过AXI-Full接口做大数据量的DMA传输。这套架构在Xilinx的Zynq平台上有大量成熟的参考设计,迁移到复旦微平台上,只要注意底层寄存器地址映射和中断号的区别就行。
2. 开发环境搭建与工程框架规划
2.1 工具链的选择:复旦微自有工具集与标准流程
开发FMQL100TAI900第一步就是搭建环境。这块板卡的开发工具链和Xilinx Vitis/Vivado有所不同,复旦微提供的是自己的FPGA集成开发环境,以及配套的嵌入式软件SDK。整体的开发流程倒是很相似,分为三个步骤:逻辑工程编写Verilog或VHDL代码、综合布线生成比特流文件;硬件工程导出硬件描述文件给SDK;软件工程在SDK里编写并编译ARM端的可执行程序。
搭建环境的时候有两点必须注意。第一,工具链版本和板卡的适配关系要在复旦微官网查清楚,不同版本的逻辑综合工具生成的比特流文件格式可能不兼容,这个坑我踩过——换了一台电脑装了新版本工具,旧工程重新综合后下载到板卡上,PS端死活起不来,最后发现是SDK里引用的硬件描述文件版本和比特流不一致。第二,License的申请要提前走流程,国产EDA工具的License管理方式各有不同,有的需要绑定MAC地址,有的需要插加密狗,别等到板卡到手了才发现License还没下来。
2.2 最小系统搭建:从点灯到串口打印
拿到一块陌生的FPGA开发板,第一步一定不是直接写AI加速器,而是先搭一个最小系统,把PS和PL的通信链路验证通。
我在FMQL100TAI900上做的最小系统包含三部分:PS端跑一个最简Linux内核,通过串口输出启动日志;PL端写了一个最简单的LED闪烁逻辑,通过AXI-Lite总线把一个GPIO模块挂到PS端;PS端通过写寄存器控制PL端的LED亮灭。
这个看起来简单的过程,实际上是整个项目最重要的一次“握手”。它验证了三件事:一是PS端的DDR和eMMC能不能正常启动Linux;二是PL端的比特流能不能正常加载;三是AXI-Lite总线的读写通路是不是通的。三步全部通过,才说明硬件平台是健康的,可以开始搭建AI推理系统。
这一阶段我遇到的比较典型的坑是复位信号的处理。FPGA的复位信号如果处理不好,会出现亚稳态,导致系统上电后偶尔起不来或者跑飞。这个问题在热搜词“fpga复位信号亚稳态”里也经常出现。正确的做法是使用同步复位,并且让复位信号持续时间足够长——比如上电后由PS端控制,延迟100毫秒再释放复位信号,确保板卡上所有电源轨都稳定了、时钟芯片也锁定完了,逻辑才开始运行。
2.3 工程目录结构与代码管理规范
AI推理系统是一个软硬件混合工程,工程目录如果不提前规划好,后面维护就是一场灾难。我建议按下面这个目录结构来组织:
fmql100_ai_inference/ ├── fpga/ # PL端逻辑工程 │ ├── rtl/ # 所有Verilog/VHDL源码 │ ├── ip/ # IP核定义文件 │ └── constraint/ # 引脚约束文件和时序约束文件 ├── sdk/ # PS端软件工程 │ ├── baremetal/ # 裸机测试程序 │ ├── linux/ # Linux内核和设备树 │ └── app/ # AI推理应用程序 ├── model/ # 模型文件和权重文件 │ ├── quantized/ # 量化后的模型 │ └── weights/ # 二进制权重文件 ├── data/ # 测试图片和数据集 └── docs/ # 设计文档和调试记录这个目录结构的好处是软硬件完全分离,逻辑工程师和软件工程师可以在同一个仓库里并行开发,互不干扰。模型和权重单独放一个目录,因为量化后的权重文件可能会很大,而且经常要更新。实测一个YOLOv5s的INT8权重文件大约在14MB左右,这还不算大,如果是更大的模型要注意管理仓库体积。
3. 核心环节实现:AI推理加速器的完整实操
3.1 INT8量化方案:让模型能在FPGA上跑起来的必经之路
AI模型原本是FP32精度的浮点模型,直接放到FPGA上做推理,资源开销大到不现实。所以第一步就是对模型做INT8量化——把权重和激活值从32位浮点数变成8位定点数,计算量直接减少四倍以上,DSP资源消耗也大幅降低。
我用的量化方法是“训练后量化”(Post-Training Quantization,PTQ),流程是这样的:准备几百张有代表性的图片作为校准数据集;把校准图片输入到模型里,统计每一层激活值的分布范围;根据分布范围确定每一层的量化参数——缩放因子和零点;用这些参数把FP32权重和激活值映射到INT8范围。
量化工具我用的是PyTorch官方的量化接口,写了一个简短的校准脚本。关键的代码逻辑是把模型的每一层挂上量化观察器(Observer),跑一遍校准数据,然后导出量化后的权重和量化参数。这部分工作在整个项目中占的时间比重很大,而且容易出问题——一旦某一层的激活值分布特别宽或者特别窄,量化误差就会急剧放大,导致精度掉得厉害。
实操中我必须强调一句:量化完了一定要用测试集验证精度,不要只看训练集上的表现。我第一次量化YOLOv5s的时候,训练集上mAP只掉了不到1%,以为自己捡到了宝,结果换到实际场景的测试图片上,mAP直接掉了十几个点。原因就是校准数据集选得太偏了,没有覆盖实际场景中的光线、角度和噪声分布。
3.2 PL端卷积加速器设计思路
卷积加速器是整个系统的核心,也是工作量最大的部分。FMQL100TAI900的PL端资源有限,设计的时候要时刻想着“每一块DSP都花在刀刃上”。
我设计的是一个脉动阵列架构的卷积计算单元,核心参数是按照16x16的阵列规模来的。也就是说,每个时钟周期可以并行计算256个乘加操作。如果跑在150MHz,那么理论算力就是256乘以150MHz乘以2(乘和加算两个操作),大约76.8 GOPS。
为了充分利用这256个DSP切片,做了三个层面的优化:
第一个是输入数据的复用。卷积计算的时候,同一个输入像素会被多个卷积核用到,所以我在数据通路设计上让输入数据在阵列里逐行流动,尽量减少从DDR重复读取数据的次数。这个优化直接把DDR的带宽占用降低了一大截。
第二个是权重数据的预加载。卷积核权重是固定的,推理过程中不会变化,所以我把所有层的权重都在推理开始前通过DMA提前加载到PL端的BRAM里,避免在计算过程中频繁访问DDR等待权重。
第三个是输出数据的乒乓缓冲。在PL端设计了两块输出缓冲区,一块在写DDR的同时,另一块已经在做下一轮计算了,用乒乓操作把数据搬运和计算重叠起来,隐藏延迟。
3.3 AXI总线DMA搬运的配置细节
PS和PL之间的大数据量交互,靠的是AXI-Full总线加DMA控制器。这一步的配置有不少讲究,直接决定了系统的实际吞吐量。
FMQL100TAI900上的DMA传输建议用SGDMA(Scatter-Gather DMA)的方式。SGDMA的好处是它通过在内存里维护一个描述符链表,可以一次性发起多次不连续地址的传输,不需要CPU逐个处理。我在使用中把输入图片拆成若干个16KB的数据块,每个数据块对应一个SGDMA描述符,DMA硬件会按照描述符的顺序依次搬运,整个搬运过程CPU只需要在最后处理一个完成中断。
这里有一个我在实操中反复验证过的经验:DMA传输的地址对齐特别关键。AXI总线的突发传输长度是有限制的,如果数据地址没有按照总线宽度对齐,一次突发传输就会被拆成多次,效率直接下降一半以上。我一开始没注意这个问题,把图片数据的起始地址随手一算就交给DMA了,结果DDR的读取效率只有理论值的三分之一。后来强制把图片数据在DDR里的存放地址按照64字节对齐,吞吐量立刻恢复了。
3.4 PS端应用层软件开发:从裸机测试到Linux应用
PS端的软件编程,我分成了两个阶段。
第一阶段是裸机测试。在Linux还没完全调通的时候,先用裸机程序验证PL端加速器的基础功能。裸机的好处是代码简单、直接操作寄存器,出了问题容易定位。我写了一个最基本的裸机测试程序:初始化DMA,把一张32x32的小图片送到加速器,计算一个简单的3x3卷积,然后把输出结果拿回来和CPU计算结果比对。这一步非常关键,它验证了PL端的计算阵列逻辑是正确的,排除了后面积累问题时的嫌疑范围。
第二阶段是Linux应用。系统启动到Linux之后,AI推理应用作为一个用户态进程运行。应用的工作流程是:读入一张图片;用OpenCV做预处理,包括缩放、通道转换、归一化;把预处理后的数据写入DDR的指定缓冲区;配置PL端加速器的寄存器,比如卷积层的输入尺寸、通道数等参数;启动DMA搬运;等待计算完成的中断;读取输出缓冲区数据;交给ARM端的NMS后处理代码;输出检测结果。
Linux应用的代码量比裸机测试大不少,但整体逻辑清晰。需要注意的是,在Linux下用DMA传输时,要确保缓冲区内存是物理内存连续且对DMA可见的。我在代码里用的是内核提供的DMA内存分配接口,从预留的CMA区域分配内存,这样虚拟地址和物理地址的映射是固定的,DMA才能安全访问。
3.5 模型部署的完整测试流程:以YOLOv5s检测一张图片为例
整个系统全部打通之后,完整跑一遍YOLOv5s推理是很有成就感的时刻。以检测一张包含行人和车辆的街道图片为例,整个流程耗时大约350毫秒,其中预处理耗时约30毫秒,PL端卷积计算耗时约260毫秒,后处理NMS耗时约30毫秒,其余是DMA搬运和中断处理的开销。
为了让PL端计算和PS端控制流程解耦,我实现了一个简化版的“任务流水”机制:PS端先把预处理好的数据准备好,然后一次性把整张图片的所有卷积层参数通过配置寄存器下发到PL端,PL端自动逐层执行卷积、池化和激活操作,不需要PS端中途干预。每做完一层,PL端会更新一个状态寄存器,PS端可以通过查询或者中断的方式获取进度。
这里要特别说明,我的PL端加速器目前支持的是固定结构的卷积网络——3x3卷积、1x1卷积、最大池化、ReLU激活和全连接层。对于像YOLOv5s里的Focus结构(切片操作)和上采样层,我是在PS端用软件方式完成的。这种方式在原型验证阶段是完全可以接受的,而且大大简化了PL端的逻辑设计。如果要进一步追求性能,可以考虑把Focus层也搬进PL端,但这会占用额外资源,从工程优先级来看不太划算。
4. 关键参数计算与资源规划经验
4.1 DDR读写带宽的瓶颈分析与解决
嵌入式AI推理系统里,最容易被低估的就是DDR带宽。做FPGA AI加速,计算资源不够时很容易量化——数一下DSP够不够下就行。但带宽瓶颈通常要等到系统真正跑起来才会暴露,而且一旦暴露,优化的成本非常高。
以我的系统为例,输入图片是640x640x3的RGB数据,存储为INT8格式,大小为640乘640乘3,约1.2MB。PL端卷积计算过程中,每个像素要被多个卷积核乘加,如果数据复用做得不好,一张图片的处理过程中可能要反复从DDR里读几十次甚至上百次数据,累计的DDR访问量会非常可观。
DDR的理论带宽和数据位宽、运行频率直接相关。FMQL100TAI900集成的DDR控制器位宽一般是32位或64位,运行频率如果到1066MHz,理论带宽大约在8.5GB/s到17GB/s之间。但实际能跑到的带宽,我实测下来只有理论值的六成左右,因为存在刷新开销、总线竞争和读写切换的惩罚。
解决带宽瓶颈的手段主要有三个:一是尽量提高PL端BRAM的缓存命中率,让重复读取的数据留在片上;二是把DDR的读写模式从随机访问变成突发访问,让DMA一次性搬运尽量多的连续数据;三是合理安排DMA的读写队列,避免读写频繁交替导致总线效率下降。我在这三个方向上都做了优化,最终把DDR的实际利用率从三成提升到了接近六成。
4.2 DSP与BRAM资源分配的取舍
FMQL100TAI900的PL端资源是固定的,设计加速器的时候必须在资源和性能之间找到平衡。我当时的分配方式是:DSP切片主要用在卷积计算阵列上,16x16的脉动阵列需要256个DSP,这已经占到了器件DSP总量的绝大部分;BRAM则全部用来做输入缓冲、权重缓存和输出缓冲,其中权重缓存的占用是最多的。
以YOLOv5s为例,第一个卷积层的权重数量是32乘3乘3乘3,等于864个8位权重,只占很少的BRAM。但到了后面层,比如第8层卷积,权重数量是256乘128乘3乘3,接近30万个8位权重,约300KB。如果不提前规划好权重在BRAM中的分布和交换策略,等到布线的时候才发现BRAM不够用,那就只能回头改架构了。
实操中的建议是:用Python写一个资源估算脚本,在写RTL之前先用脚本把每一层的权重、激活值、特征图尺寸算一遍,累加资源需求,和片上的BRAM、DSP总量对比,确定一个可行的数据切分和调度方案。这个工作看似麻烦,实际上能帮你避掉一半以上的返工。
4.3 时序收敛的经验:从布局布线结果反推代码优化
AI推理加速器包含大量并行处理单元,时序收敛是整个项目里最磨人的环节之一。FMQL100TAI900的PL端逻辑规模大、时钟频率要求高,如果代码风格不好,很容易出现时序不收敛,工具报告显示红色负裕量。
我总结的经验是三个“提前”:提前做时序预算,在写代码之前先确定每一条关键路径的延迟约束;提前约束时钟,用create_clock命令把所有时钟域都定义清楚,避免综合器自动推断时钟;提前处理异步跨时钟域的数据交换,所有跨时钟域的信号必须经过异步FIFO或者打拍同步处理,绝对不允许直接用一个时钟域的信号去采另一个时钟域的信号。
还有一点和热搜词“fpga布局和布线区别是什么”相关——布局和布线是两个不同的阶段。布局是决定逻辑单元在芯片上的物理位置,布线是决定它们之间的连线方式。时序不收敛的时候,很多工程师第一时间想到的是加约束,但实际上问题往往出在代码结构上——某个组合逻辑路径太深、扇出太大、或者跨了太远的物理区域。我的经验是先回代码看关键路径,实在不行再辅助布局约束。
特别是FMQL100TAI900这类资源量大的芯片,不同区域的物理距离导致的走线延迟差异很大。如果设计中有一条路径的起点和终点刚好被布局在芯片的两个对角,即使逻辑延迟没问题,走线延迟也会把时序拖跨。这种问题在“多die fpga”结构里更加明显,不同die之间的走线延迟会更高,一定要在约束文件里做好区域划分,尽量让相关的逻辑物理上靠近。
4.4 定点数运算的精度控制与溢出处理
FPGA做AI推理,本质上就是定点数运算。INT8量化之后,乘法结果会变成INT16甚至更高位宽,如果不做好溢出控制和截位处理,推理结果会出现肉眼可见的偏差。
我在设计PL端卷积累加器之前,先做了一轮定点数格式的仿真分析。以YOLOv5s为例,卷积层的累加器我用了INT32位宽,因为16x16的卷积核窗口,每个像素的乘加结果最大可能达到16个乘积累加,如果输入是INT8(范围-128到127),权重也是INT8,那么单个乘积累加最大是128乘127乘16,也就是大约260K,这个值在INT32范围内完全不会溢出。
但更大的问题是逐层传播的量化误差。每一层做完乘加之后,需要把INT32的累加结果通过移位和缩放,重新转回INT8,供下一层使用。这个缩小过程如果只是简单截位,会引入较大的精度损失。我在代码里用的是“四舍五入加饱和截断”的方式——先把累加结果加上一个偏置,再进行移位操作,最后在超出INT8范围时进行饱和处理,把值钳位到-128到127之间。这套处理方式让量化模型的精度损失控制在了2%以内。
5. 国产化配套方案清单与选型建议
5.1 围绕FMQL100TAI900的全套国产化清单
标题里提到“国产化方案清单”,这块我专门整理了一份。做国产化不是只换一颗主芯片就完事的,一个真正可交付的国产化AI推理板卡,需要整个物料清单(BOM)里的关键器件都做到自主可控。以下是我在这个项目里实际用到的国产化器件清单:
主控SoC FPGA用的是复旦微FMQL100TAI900。配套的DDR4内存颗粒,我选的是长鑫存储的DDR4,容量8GB,频率跑在2133MHz,稳定性和兼容性在复旦微平台上验证过。存储方面,eMMC和NAND Flash选的是长江存储的方案,用来放Linux内核、根文件系统和模型权重文件。
电源是容易忽略但绝对不能含糊的部分。SoC FPGA的内核电压、IO电压、DDR电压、PLL模拟电压,每一路都需要独立的电源芯片供电。我选用的是圣邦微的DC-DC和LDO组合,再搭配思瑞浦的电源监控芯片,确保各路电压上电时序满足数据手册要求。FPGA上电时序这个坑很大,如果内核电压和IO电压的上电顺序不对,轻则启动失败,重则损伤芯片。
时钟选的是大普通信的差分晶振,作为PS端和PL端的主时钟。接口芯片方面,我用到的千兆以太网PHY、USB PHY和CAN收发器,分别选的国微和北京微电子技术研究所的方案,都有稳定的供货渠道。
5.2 各器件的选型理由与供货风险控制
选择这些国产化器件,除了满足国产化率要求,还有几个工程上的考量。
长鑫DDR4在复旦微平台上的兼容性,我是实际测试过的。DDR控制器的初始化校准过程对颗粒的时序参数很敏感,不同厂商的颗粒在初始化流程上可能有微小差异,如果颗粒没有在厂商的兼容性列表里,就可能出现训练失败或者运行时不稳定的问题。所以在选型的时候,我优先选择了在复旦微官方评估板上出现过的器件型号。
电源芯片的选型要注意纹波和负载瞬态响应。FPGA在运行AI推理时,逻辑翻转率极高,电源的瞬态电流变化很快。如果电源芯片的动态响应能力不足,电压跌落一旦超过芯片允许的范围,就会出现随机性的逻辑错误。这个问题非常隐蔽,排查起来特别痛苦,因为问题不是每次都出现,而是高压负载的时候才出现。我在设计时给每一路电源都预留了足够的电流余量,并且在靠近FPGA电源引脚的位置放了足够多的去耦电容,实测电源纹波控制在50毫伏以内。
国产化器件的供货风险是另一个课题。做研发样机的时候,器件短缺问题不明显,但转入小批量生产之后,任何一个器件的交期拉长都会拖累整个项目进度。我的做法是:关键器件至少找两家以上国产供应商做第二供方验证,并且在设计阶段就布局好了兼容焊盘,这样即使某一颗器件断货,可以快速切换到替代型号。
5.3 国产化开发流程中的额外注意事项
国产化FPGA的开发流程和进口FPGA有相似之处,但也有不少不同的地方。这里提醒几个容易忽略的点:
第一,硬件描述文件与工具链版本的配套。复旦微的FPGA IDE工具更新频率不像海外大厂那么高,有时候硬件工程师升级了工具版本,SDK里的硬件描述文件也要同步升级,否则ARM端初始化代码会跑飞。我建议逻辑工程师和软件工程师每次更换工具版本时,都把完整的编译流程重跑一遍,不要混用旧版本的中间文件。
第二,IP核授权方式。复旦微的部分IP核是需要单独授权的,比如DDR控制器、高速串行收发器这些。做项目之前先和原厂确认清楚需要用到的IP核清单和授权流程,别到开发中期才发现某个IP核用不了,那真是叫天天不应。
第三,文档和例程的获取渠道。国产FPGA原厂的技术支持效率其实已经比前几年好很多了,但他们的例程和参考设计相对分散,有的在官网,有的在FAQ文档里,有的要让技术支持单独发。建议建立自己的“例程库”,凡是原厂提供的代码、配置脚本、设备树文件,统一归档,方便后续复用。
6. 常见问题与调试技巧实录
6.1 复位信号亚稳态问题
关于“fpga复位信号亚稳态”这个问题,我在实际项目中确实遇到过非常典型的案例。第一次给PL端加速器上电的时候,大约有十分之一的概率出现“卡死”现象——ARM端启动正常,但PL端的计算阵列一直没有响应,状态寄存器读回来全是一堆乱值。
排查过程很曲折。先怀疑是时钟问题,示波器看波形发现PLL输出正常的125MHz时钟;然后怀疑是DMA配置问题,把DMA相关寄存器全部换成了初始化代码再配置一遍,还是偶发;最后用逻辑分析仪抓了复位信号和状态机的启动时序,发现PL端逻辑在复位释放后立刻开始读取配置寄存器,但这个时候ARM端还没有完成对加速器寄存器的初始化,两边时序错位了。
解决办法有两个层面:第一,硬件复位信号用同步复位加长释放时间,确保复位释放时所有参考时钟都已经稳定;第二,ARM端在启动加速器之前,先读一次状态寄存器,确认加速器已经进入“空闲等待”状态,再下发配置数据。这个“先握手、再工作”的流程改动之后,偶发卡死的现象就再也没有出现过。
6.2 UART串口打印丢数据的排查
开发过程中我经常靠串口打印调试信息,但遇到过一个很诡异的丢数据问题——串口打印的前几十个字节总是偶发丢失,导致Linux启动日志不完整,应用程序的调试信息也偶尔缺失。
排查后定位到两个原因:第一是PS端的串口波特率时钟是从某个PLL分频出来的,如果PLL配置的精度不够,波特率会有一定偏差,数据量大之后累计误差就会导致采样点偏移、个别字节丢失。第二是串口驱动的FIFO中断阈值配置不合理,Linux下串口驱动默认的FIFO触发阈值比较低,如果应用程序打印频率高,中断处理不及时就会丢数据。
解决方案是:在设备树里把串口的FIFO触发阈值调高,同时给串口中断绑定了更高的中断优先级。这样改完之后,连续长时间打串口日志也没有再丢过数据。
6.3 LVDS接口做图像输入的端接与阻抗匹配
我的测试平台上有一个LVDS摄像头接口,用来做实时图像输入。LVDS信号的特点是高速差分信号,对端接电阻和阻抗匹配的要求很高。
一开始我对LVDS的端接不太在意,直接用板卡上预留的默认配置,结果图像采集出来的画面有严重的噪点,边缘特别明显有拖影。后来查了FPGA数据手册,发现LVDS输入输出引脚的电平标准和端接方式有严格的要求,必须根据板卡的走线阻抗来设置内部端接电阻,并且在靠近FPGA引脚的位置加上100欧姆的差分端接电阻。
这个问题解决之后,图像质量明显改善。所以做FPGA图像采集的时候,LVDS或者MIPI这类高速接口,一定要把信号完整性设计从原理图阶段就考虑进去,不要指望靠FPGA内部的补偿逻辑来弥补物理层的缺陷。
6.4 UART RX接收仿真的正确打开方式
有读者问过“fpga实现uart_rx接收仿真”怎么弄。UART接收的仿真其实比发送要复杂,因为要处理起始位检测、波特率时钟采样、数据位解析、停止位校验这一整套时序。
我的做法是用三段式状态机来设计UART RX模块:空闲状态等待起始位下降沿;检测到起始位后,从数据位的中点开始采样,每比特采样一次;采样完8个数据位和1个停止位之后,把收到的数据放到输出寄存器,同时拉高接收完成标志。
仿真的关键点在于测试激励怎么设计。我写了一个testbench,用Verilog的任务(task)来模拟发送端,先把串口数据按波特率一位一位地翻转串行信号,再送入UART RX模块。特别要测试的是边界情况——起始位刚好比理想位置早半个比特周期、数据位中间有毛刺,这些情况下模块还能不能正确解码。实测下来,我的UART RX设计在3%的波特率误差范围内都能正确接收数据。
6.5 FPGA相关基础概念释疑
项目过程中有朋友问我很多FPGA的基础概念,比如“fpga芯片的架构图”什么样、“cpld和fpga的区别是什么”、“fpga中rom的ip核的调用”这些。这里简单收个尾。
FPGA芯片的架构图,从上到下看,大致是:可编程逻辑单元阵列(CLB或LAB)铺满整个芯片;在这些逻辑单元之间分布着块状BRAM、DSP切片、时钟管理单元(PLL和MMCM);芯片四周分布着可编程IO块(IOB),负责和外部器件通信;现代SoC FPGA还会在同一个芯片上集成ARM处理器硬核和各种高速串行收发器。
CPLD和FPGA的区别其实很明显。CPLD是基于乘积项结构的非易失性逻辑器件,上电即工作,逻辑规模小,适合做胶合逻辑;FPGA是基于查找表结构的易失性SRAM器件,上电需要加载配置,逻辑规模大,适合做复杂的数字系统。FMQL100TAI900这种SoC FPGA则是把ARM处理器和FPGA融合在了一起,既能跑操作系统又能做高速并行计算。
FPGA里调用ROM的IP核,本质上就是使用BRAM资源初始化成只读存储器。Xilinx和复旦微的IP核配置工具里面都有ROM生成选项,配置初始化文件(.coe或.mif)的时候,要注意数据格式和地址位宽的匹配。ROM在AI推理系统里的典型应用就是存储非线性的激活函数查找表,比如把sigmoid或者tanh函数离散化成256个点放在ROM里,计算的时候直接查表,比实时计算快得多。
写在最后:一点个人体会
FMQL100TAI900这套方案做下来,我最深的体会是:国产FPGA做AI推理,缺的不是芯片本身的能力,而是生态和参考设计。进口FPGA巨头有大量现成的AI加速器IP和参考例程可以抄作业,而复旦微这边基本要靠自己从零搭起。这个过程当然辛苦,但也确实逼着我把AXI总线、DMA搬运、DSP调度这些底层的知识彻底搞通了。
最后再分享一个小技巧:做FPGA原型验证,一定不要一上来就追求高性能,优先把数据通路跑通。哪怕算得慢一点,先把“图片输入到结果输出”这条完整链路打通,后面再慢慢优化性能和资源利用率。大多数国产化AI项目最终没能按时交付,往往不是性能不够,而是系统级联调耗费了太多时间。先把路走通,再跑得快,这是我在这个项目里最大的收获。