国产FPGA上的AI推理原型搭建:从选型到性能调优
我一直觉得FPGA开发里头最容易被低估的不是触发器Flip-Flop占用率也不是时序收敛难度而是“选型那一刻的决策质量”。尤其当你面对的不再是一个纯技术问题而是整机国产化率、供应链安全、后续量产可替代性这些硬指标时FPGA选型就变成了一道综合题。这个项目源自我手里一个边缘端AI推理原型验证任务需要在一块国产FPGA板卡上跑通一个轻量级卷积神经网络完成图像分类或简单目标识别同时输出一份“国产化方案清单”给后续产品立项参考。经过一轮对比我最终选了复旦微的FMQL100TAI900板卡来做原型。这块板子的特殊之处在于它不是单纯的FPGA而是把ARM处理器和可编程逻辑集成在同一个芯片里的异构SoC跑AI推理原型时既能兼顾灵活定制硬件加速逻辑又能保留嵌入式软件生态的便利性。这篇文章我会把整个搭建过程拆开讲清楚从为什么选它到工具链搭建再到卷积算子实现、端到端推理链路、性能调优最后附上一份我调研时整理的国产化方案清单。如果你正准备做国产FPGA方向的AI推理原型或者正在纠结“国产芯片到底能不能跑神经网络”这篇文章应该能给你省下不少试错时间。1. 为什么我会选择FMQL100TAI900来搭AI推理原型1.1 一个再现实不过的起点整机国产化率先说最核心的驱动力。我接到的需求里有一条很明确“核心器件尽量国产化”。这里的“尽量”其实已经在暗示你不可能全部用国产芯片但至少关键的可编程逻辑器件得给国产方案留出位置。FPGA在整机里通常负责接口扩展、数据预处理和低延迟加速它不是CPU那样绝对不可替代的存在但又很难直接砍掉。所以选一块合适的国产FPGA直接影响整个项目的验收口径。FMQL100TAI900是复旦微的SoC级FPGA芯片内部集成了ARM处理器核心和可编程逻辑阵列。用行业里常见的话讲这就是对标Zynq UltraScale架构的产品形态。国产FPGA里能走到这一步的厂商不多复旦微在这一代产品上确实把生态补齐了不少。对AI推理原型来说这种ARMFPGA的异构架构几乎是量身定做的ARM跑Linux、做模型调度和结果解析FPGA做卷积、池化这些计算密集型的算子。我实际对比过纯FPGA和SoC FPGA两条路线。纯FPGA需要外挂CPU板级设计复杂度高调试时又要用逻辑分析仪又要用JTAG很不方便。而FMQL100TAI900这样的单芯片SoC方案PS处理系统和PL可编程逻辑之间的数据通路带宽高、延迟低原型阶段省掉了很多麻烦。1.2 单芯片里既有ARM又有FPGA给AI推理提供了什么便利跑AI推理原型最理想的状态是模型参数能灵活更新性能又能压到足够低的延迟。纯软件方案的瓶颈在计算效率纯硬件方案的瓶颈在灵活性而ARMFPGA的异构方案正好夹在中间。FMQL100TAI900的PS端跑Linux我可以把PyTorch训练好的权重直接导成二进制文件放在SD卡或DDR里。PL端则是真正的加速引擎我把卷积、池化、全连接这些算子用硬件逻辑实现。运行时PS端的应用程序负责把图像数据写入DDR然后通知PL端开始推理PL端通过DMA把权重和输入数据从DDR搬进FPGA内部的缓存计算完成后再把结果写回DDRPS端收到中断读结果打印分类结果。这个架构的便利之处在于你不需要在FPGA里实现一个完整的“AI调度器”那些复杂的控制流交给ARM硬件逻辑只干一件事——算。这种分工也符合FPGA的调试习惯因为我可以用Xilinx风格的Vivado调试工具复旦微的FMCAD也是同一套操作逻辑把PL端的信号拉出来看而不至于被Linux驱动和硬件逻辑的交叉问题缠住。1.3 板卡选型时我看重哪几项参数我拿到FMQL100TAI900板卡之后第一件事不是急着写代码而是把板卡资源表研究了一遍。以我当时的需求为例选型时重点关注这几个维度维度关注点为什么重要逻辑资源规模LUT、触发器、DSP Slice数量决定能塞下多大的CNN加速器资源太小只能做极简网络片上存储BRAM/URAM总量推理时权重和中间特征图的搬运频率取决于它片上缓存太大会频繁访问DDR速度直接拉胯PS端性能ARM核数量与频率跑Linux和调度任务是否流畅PL-PS互联带宽AXI接口数量与位宽参数从DDR到PL端的传输瓶颈在这里高速接口GTX/GTH等SerDes通道后续如果接入摄像头或PCIe需要预留FMQL100TAI900这块板卡在这几项上基本都能覆盖我需要的中等规模CNN推理。当然如果你只是跑一个手写数字识别级别的模型选资源量更小的芯片也够用但考虑到后续要扩展到实时图像处理我选择给原型留足余量。2. 开发环境搭建工具链、许可证和第一段代码2.1 FMCAD环境安装与兼容性细节这块板卡的开发环境是复旦微的FMCAD。用过Vivado的人上手会非常快因为界面、工程结构、语法体系基本延续了主流的开发习惯顶层用Verilog/VHDL综合、实现、生成比特流一套流程下来很顺。但有几点需要注意第一版本匹配。FMCAD不同版本对操作系统的支持略有差异我最初在Ubuntu 22.04上装某个较老版本结果综合阶段频繁崩溃后来换到官方推荐的Ubuntu 20.04才稳定下来。建议拿到开发环境后先看官方Release Note别凭感觉装最新系统。第二许可证License要提前申请。国产EDA工具的License流程不像国际大厂那么自动化有时候需要联系原厂或代理商发邮件申请周期可能一到三天。项目排期紧的话这一步要提前走。第三FPGA厂商的工具链通常自带仿真器但也支持ModelSim等第三方工具。我习惯用工具自带的仿真器做功能仿真因为它和综合库绑定更紧密不容易出现仿真通过、上板失败的情况。2.2 板卡资源摸底从点灯开始拿到一块新板卡我永远建议从最基础的工程开始。这一步不是浪费时间而是验证整个工具链、下载器、板卡时钟和复位电路是否正常工作。最小工程只需要几样东西一个时钟输入、一个复位按钮、一个LED输出。我把时钟约束做好写了一段计数器逻辑让LED每隔500ms翻转一次。综合、实现、生成比特流然后通过下载器烧进去。看到LED开始闪烁那一刻开发环境才算真正打通了。这个环节里最容易踩的坑是引脚约束。不同板卡的FPGA引脚分配不一样如果照抄别人的约束文件很可能把时钟引脚约束成普通IO。所以拿到板卡后第一件事是找到官方原理图核对LED、时钟、复位这些关键信号的引脚编号再写XDC或FDC约束文件。2.3 UART打印验证最小系统点灯之后紧接着做UART。这一步的意义在于验证PS端和PL端的协作也为后面的调试建立一条信息通道。FMQL100TAI900的PS端自带UART控制器我在Linux启动后通过串口终端能看到内核日志。如果我们想验证PL端的逻辑可以在PL里写一个UART_TX模块把FPGA内部的状态信息直接打到串口。这一步比JTAGILA更轻量因为ILA集成逻辑分析仪在调试大规模设计时会占用大量存储资源而UART打印几乎不占资源而且能长期保留在代码里做运行日志。我在原型阶段的做法是PL端每个算子的启动和完成信号都寄存到一个状态寄存器里通过UART定时上报。这样我在终端就能看到当前推理进行到了哪一步大大缩短了定位问题的时间。3. AI模型到FPGA的“翻译”量化、定点数与算子设计3.1 为什么要抛弃浮点改用定点这是FPGA做AI推理绕不开的第一个坎。神经网络在训练时用的是32位浮点FP32但FPGA里做浮点乘加运算消耗的DSP资源多、频率还上不去。一个更务实的做法是把模型量化成定点数最常见的是INT8。量化的本质很简单把浮点数的范围映射到一个有限的整数区间。假设我们要把[-1.0, 1.0]之间的浮点数量化成8位有符号整数那么缩放因子scale等于127因为2^7 - 1 127量化公式就是q round(x * scale)反量化公式则是x ≈ q / scale实际做模型量化时需要根据每一层的权重和激活值分布选一个合适的scale。更精细的做法是逐通道量化per-channel也就是每个输出通道单独算一个scale精度损失更小。我在原型里为了简化实现用的是全局scale方案精度损失在可接受范围内。FPGA里的定点数实现还有一个隐藏好处激活函数可以大幅简化。比如ReLU在浮点里要判断是否大于零在定点里只需要判断最高位符号位。这个优势在硬件面积和时序上都很明显。3.2 卷积层的FPGA实现思路以3x3卷积为例卷积层是整个CNN里计算量最大的一块。以常见的3x3卷积为例假设输入特征图是HxWxC_in输出是HxWxC_out那总的乘加次数大约是H * W * C_in * C_out * 3 * 3这是一个很可怕的数字。所以在FPGA里实现卷积核心不是“怎么算对”而是“怎么在有限的DSP和BRAM资源下把算力榨出来”。我采用的是一种很经典的设计行缓冲Line Buffer滑动窗口Sliding Window。行缓冲的思路是卷积计算一个输出像素时需要的是输入特征图上一个3x3的窗口。如果我们每次从DDR读一整行数据到FPGA内部缓存就能通过移位寄存器构造出这个3x3窗口而不用频繁访问DDR。对于C_in个输入通道每一通道都维护一组行缓冲。具体流程如下从DDR读取输入特征图的第i行、第i1行、第i2行分别存入三个行缓冲三个行缓冲的同一列数据组合成3x3窗口将窗口内的9个数据与对应的3x3权重做乘加运算每次读完一行新数据行缓冲向前滑动一行这里有一个关键参数行缓冲的深度等于输入特征图的宽度而宽度又不一定等于DDR端突发传输的对齐长度所以行缓冲的读写地址管理要格外小心。我在实现时干脆把行缓冲深度设为2的幂次比如输入宽度是32就设成32输入宽度是28就补齐到32代价是稍许BRAM浪费但换来的是地址计算的简化。卷积计算单元的并行度需要根据目标性能和资源预算来定。如果你有足够的DSP资源可以每个输出通道分配一个乘加阵列或者每个输入通道分配一个。我的原型里选了每个时钟周期算4个输出通道的并行度平衡了资源占用和吞吐率。3.3 全连接、ReLU与Softmax的取舍卷积层之后通常接全连接层。全连接的本质是矩阵乘向量实现起来比卷积简单因为它不需要行缓冲和滑动窗口只需要把输入特征图展开成向量然后依次和权重矩阵的每一行做乘加。全连接的并行策略更直接在DSP资源允许的情况下同时计算多个输出神经元。比如我一次算4个输出神经元每个神经元需要C_in次乘加那么我的乘加阵列就能全部用起来。激活函数方面ReLU在定点实现里只需要检查符号位几乎不占资源。我在所有卷积层和全连接层之后都接了一个ReLU唯一不加ReLU的是最后一层分类输出因为我要做Softmax。Softmax是个比较麻烦的东西它涉及指数运算和除法。FPGA里直接算指数函数不现实更常见的做法是查找表LUT。我把指数函数预先算好存到BRAM里运行时用定点数作为索引查表。如果分类类别数是10那Softmax的分母只需要算10个查表结果的和然后做一次除法。这个计算量很小用ARM核就能处理我干脆把Softmax放在了PS端和PL端的算子分离。这既是架构上的简化也是性能上的合理分配。4. 端到端推理从DDR读数据到结果落盘4.1 数据搬移往往比计算更贵很多第一次做FPGA AI加速的人会犯一个错误把注意力全部放在计算单元的设计上忽略了数据搬运。实际跑起来才会发现如果每次计算前都从DDR取一个小数据块DDR带宽会被白白浪费计算单元反而在那里空等。DDR访问有个特性突发传输效率高随机访问效率低。换句话说一次性读连续的大块数据比读很多次小块数据要快得多。所以我在设计里专门做了一个DMA模块负责把DDR里的整张输入特征图搬到PL端的缓存里计算完后再把整张输出特征图搬回DDR。以第一层卷积为例输入是一张64x64x3的图像。我让DMA一次性把这12,288个字节搬进PL端的AXI接口FIFO然后卷积模块再从FIFO里读数据。这样DDR端只发起一次大的突发读传输效率和计算模块的利用率都上来了。4.2 PL端DMA与PS端中断协调整个推理流程的协调我用的是AXI DMA PS中断的方式。FMQL100TAI900的PS端支持通用中断控制器GICPL端可以通过AXI互联把中断信号送到GICPS端运行Linux时可以用内核的通用DMA引擎框架或者简单的字符设备驱动来处理。流程是这样的PS把输入图像的物理地址、权重地址、输出缓冲区地址写到PL端的控制寄存器里PS向PL端发一个启动信号写控制寄存器PL端的DMA引擎按照配置从DDR读输入数据到PL侧BRAM/FIFO卷积算子从BRAM/FIFO读数据计算结果写入另一块BRAM一层计算完成后DMA把结果写回DDR同时给PS发中断PS收到中断后更新状态准备下一层的数据或直接启动下一层的DMA传输这套“PS配置 PL执行 中断反馈”的模型几乎是所有SoC FPGA加速方案的通用范式。它能复用PS端的Linux生态又能把性能敏感的计算放在PL端工程可实现性很高。我调试时最常用的手段是在PS端写一个小应用程序打印每一层推理的耗时。这样我能直观看到瓶颈在哪一层是计算慢还是数据搬运慢。4.3 实测一轮推理流程是什么样的跑一轮MNIST手写数字分类2028x28灰度图一个卷积层一个全连接层在我的原型上整个流程大约耗时几毫秒。听起来挺快但拆开看会发现DDR数据搬运占了将近一半的时间。这个结论给了我两个优化方向一是减少中间结果在DDR和PL之间的往返二是增大片上缓存把更多权重和特征图留在PL端。第一步优化的做法是“层间融合”卷积层算完的中间特征图不要写回DDR直接留在PL端的BRAM里给池化层用池化结果再写回DDR。这样把两次DDR访问压缩成一次延迟直接降了30%以上。我建议你也按这个思路来设计原型——先跑通再优化先分层再融合。否则一上来就追求极致优化调试复杂度会指数上升。5. 性能调优的三板斧5.1 并行度把窗口计算全部展开性能优化的第一板斧是把计算模块内部的并行度提上去。卷积层的并行度可以从两个维度看输入通道维度和输出通道维度。假设你每个时钟周期处理P个输出通道那么每个周期需要同时读取P组3x3窗口数据做P次9元素乘加。在DSP资源充足的情况下可以尝试更高的P。但P不能无限大因为BRAM的读端口数量限制了同时读出的数据量。我在设计里把输出通道并行度P设成了4输入通道并行度Q设成了2也就是每个周期同时做8次乘加。综合后DSP占用率大概60%BRAM占用率40%留出了后续扩展的空间。提高并行度还有一个直接好处降低了整体时钟频率的要求。同样的算力需求并行度高时频率可以低一点而频率低通常意味着时序更容易收敛功耗也更低。5.2 流水线让下一层提前开始第二板斧是流水线化。最简单的理解第一层卷积算完第一个输出块后第二层卷积就可以开始计算这个块而不必等第一层全部算完。这就像工厂流水线每个工位只处理自己负责的那一小步。我在实现时把卷积层的计算过程拆成了几个阶段行缓冲写入、窗口拼接、乘加运算、结果累加、写回缓存。这些阶段之间用FIFO或寄存器隔开每个阶段可以独立工作。流水线填满之后整体吞吐率显著提升因为每个周期都有一个输出像素在产生。不过流水线也会引入一个问题气泡Stall。当上一层数据供不上或下一层缓存写满时流水线会停摆。解决思路是在层间设置足够深的FIFO并做反压机制Backpressure让数据上下游能够协调。5.3 时序收敛复位与亚稳态的坑性能优化的最后一板斧完全是工程层面的——时序收敛。FPGA频率标称能达到200MHz但实际布局布线后能不能跑到这个数取决于你的代码风格和约束是否合理。我在这个项目里踩的第一个时序坑就是复位信号的异步处理。如果复位信号直接用在时序逻辑上而且没有做同步处理很容易出现亚稳态导致某些触发器复位到错误的状态表现出来就是系统跑一段时间突然卡死。正确的做法是把异步复位先经过两级触发器同步再用同步后的复位信号去复位各个模块。// 异步复位同步释放标准写法 reg rst_n_sync1, rst_n_sync2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_n_sync1 1b0; rst_n_sync2 1b0; end else begin rst_n_sync1 1b1; rst_n_sync2 rst_n_sync1; end end assign rst_n_synced rst_n_sync2;这个写法不是摆设。我在没有做同步复位之前板子跑几秒到几分钟就会随机死机加了这个同步电路之后问题彻底消失。建议所有FPGA项目都把这个当成标准模板。6. 踩坑记录那些文档里不会写的问题6.1 UART_RX接收仿真的经典误判如果你要从外部传感器接收数据不可避免要写UART_RX模块。UART的接收逻辑本身不难难点在于采样时机的判断。UART协议规定一个数据帧起始位拉低一个位时间然后才是8位数据。如果接收端在起始位的中间点采样就能避开信号边沿的不稳定区域。我在仿真时遇到的经典误判是因为起始位检测逻辑做得不够稳健在数据线噪声毛刺的干扰下把毛刺误判成起始位导致后续采到的数据全是乱的。解决方案也很经典倍频采样。我用了16倍波特率时钟在检测到可能的起始位下降沿后先等待8个采样周期也就是位时间的中间点然后连续采样3次取多数表决结果作为该位的值。这个方案能有效过滤掉毛刺噪声。6.2 复位信号亚稳态导致的随机死机前面提到过复位同步的问题这里再补充一个相关坑。当板卡刚上电时电源电压还没完全稳定时钟信号也可能有抖动。如果此时复位信号释放而时钟还在抖那所有触发器复位后的第一个状态就可能是不确定的。解决办法除了做同步复位之外还要确保复位释放时机晚于时钟稳定。如果板卡上有电源监控芯片可以把它的电源OK信号作为复位输入如果没有可以在复位逻辑前端加一个上电延时计数器让复位至少保持几百毫秒。6.3 高速接口与信号完整性的“玄学”我最初调试DDR接口时出现过一个很头疼的现象单独的读写测试都能通过但一旦CPU访问和DMA访问同时进行系统就会时不时报错。后来查了很久才发现是板卡上DDR布线不够优再加上我没有做正确的时序约束DDR控制器出错了勉强能跑压力一上来就暴露了问题。这类问题没有捷径只能老老实实按官方参考设计检查约束必要时降低DDR时钟频率。对于原型板卡来说稳定压倒一切没必要一上来就追求最高频率。7. 国产化方案清单不只复旦微一家7.1 国产FPGA主流型号对比既然标题里写了“附国产化方案清单”这部分就把我调研时整理的对比直接放出来。下面的表格是我根据公开资料和实际项目经验整理的适合做选型参考但具体参数务必以官方最新文档为准。厂商代表型号架构特点适合场景备注复旦微FMQL100TAI900ARMFPGA异构SoCAI推理、嵌入式控制、图像处理生态相对成熟FMCAD上手快紫光同创Logos系列 / Titan系列纯FPGA为主逻辑扩展、接口桥接性价比高中大容量型号较多安路科技Phoenix系列 / Elite系列低功耗FPGA / SoC FPGA消费电子、工业控制低功耗场景有优势高云半导体GW5A系列纯FPGA视频接口、工业通信小封装型号丰富京微齐力M系列 / H系列FPGA/SoC特种应用、工业在特定行业有积累如果你要跑AI推理我建议优先看SoC类国产FPGA比如复旦微或安路的SoC型号因为ARM核能帮你省掉很多调试工作。如果只是做数据采集和预处理纯FPGA就够用了。7.2 推理框架与工具链的国产化现状国产FPGA的编译工具链这几年进步明显但和国外成熟生态相比仍有差距。具体来说综合工具复旦微的FMCAD、紫光同创的PDS、安路的Tang Dynasty基本都支持Verilog/VHDL界面也与主流工具接近。但第三方IP生态相对薄弱很多成熟IP核需要自己写或向原厂申请。模型转换工具针对AI推理国产FPGA领域目前还没有一个类似Vitis AI那样完整的部署工具链。这意味着模型量化、算子实现、驱动开发都需要自己动手。我用的是一个很原始但有效的方案PyTorch训练 - 导出权重 - Python脚本做定点量化 - 生成coe文件 - PL端算子读取。调试工具各家的逻辑分析仪基本都够用但跨厂商兼容性差换一次平台就要重新熟悉一遍工具。如果团队之前没有FPGA开发经验我建议先在工具链上做个小Demo验证再决定是否立项因为工具链的学习曲线会直接影响项目周期。7.3 如果重新选型我会怎么权衡如果再给我一次选择机会我会按照下面的优先级来决策先用一个目标明确的Demo板做验证比如FPGA上跑通一个简单CNN评估算力是否满足要求再评估工具链的易用性包括综合时间、调试手段、IP可用性然后看供应链情况包括交期、技术支持响应速度最后才是比单价因为原型阶段节省的开发时间比芯片差价值钱得多国产FPGA做AI推理已经不是“能不能做”的问题而是“怎么做好”的问题。FMQL100TAI900这条路走通之后后续换到同厂商更高性能型号或者横向迁移到其他厂商平台核心思路都是通用的。我个人在实际操作中的体会是FPGA做AI推理最大的价值不在跑分而在你能完全掌控每一拍数据流。如果你也打算做类似的原型别急着追求性能极致先把数据通路打通把每一层算子的输入输出验证明白再去优化并行度和流水线。这个顺序换来的稳定性能让你在后面调试时少熬夜。