☰
FPGA+ARM异构SoPC架构:高速数据采集与传输系统设计实践
2026/10/21 7:40:07 网站建设 项目流程

开始做这个项目之前,我其实一直用的是纯FPGA方案做数据采集,后来项目越做越复杂,又要管理控制逻辑,又要跑网络协议栈,甚至还要接上位机做交互配置,纯FPGA开发效率确实扛不住了。这个基于SOPC(FPGA+ARM)的采集处理及传输系统,简单说就是把高速采集、实时信号处理放到FPGA里跑,把协议栈、任务调度、系统管理这些交给ARM核,一条PCIE或AXI总线把两边捏成一个片上系统。与其说这是一个项目,不如说是一套通用架构模板,它解决的核心问题就是:数据量大到单片机处理不过来、纯FPGA开发周期又太长的时候,怎么在成本、功耗、实时性三方面同时站稳脚跟。适合正在从单纯的FPGA开发往SoC方向转的工程师,也适合要做通用采集板卡的团队参考。

1. 整体设计思路:为什么非得是FPGA+ARM,而不是纯FPGA或者纯ARM

1.1 先从数据流的角度想清楚架构

做采集系统第一步不是选芯片,是画数据流。拿我们这套系统举例,输入信号经过前端调理电路后到ADC,ADC以几十兆甚至上百兆采样率出数据,这股数据流如果直接进ARM处理器,哪怕跑Linux用内核态驱动加上DMA,性能也勉强,一旦同时要做滤波、检波、触发判断,CPU占用率直接飙上去,后面网口传输又要排队,整个系统延迟不可控。

所以架构上必须把数据流分成两段:

  • 前端高速段:ADC采样数据直接进FPGA,FPGA内部完成降速、缓存、触发、预处理,数据整理成一个个数据帧;
  • 后端控制管理段:ARM负责接收FPGA整理好的数据,做深度分析或打包上传,同时承担外部命令解析、参数配置、设备状态上报等事务。

这两段分工一旦在需求阶段确立,后面的芯片选型、软硬件划分、接口定义全都顺了。

1.2 跟纯FPGA、纯ARM方案做个直观对比

很多刚接触SOPC的同事都会问:我纯FPGA加个软核不也行?或者ARM外面挂FPGA不也是这个效果?这问题问到点子上了,三种方案在开发效率、系统复杂度、硬件成本上确实各有取舍:

方案实时性灵活性开发效率系统复杂度适用场景
纯FPGA+软核高极高低高逻辑规模大、不需要复杂OS、便于快速修改逻辑
纯ARM+外部FPGA较高高中较高已有独立处理器和FPGA模块,级联组合
SOPC单芯片高高高低有采集控制和协议栈需求的通用板卡,比如本项目

用一张表说话,SOPC单芯片方案最大优势是省掉了外部两个芯片间的接口设计和信号完整性顾虑,板子面积小很多,功耗也直接落下来。另外软硬件协同工具链成熟,比如在可编程逻辑里挂个DMA控制器,然后通过AXI总线直接往DDR里写数据,这套东西纯FPGA做起来要费不少功夫,ARM那边写驱动的量也不小,但SOPC平台有现成的IP和例程,开发周期明显短。

1.3 软硬件任务划分才是核心设计

架构定了,接下来最核心的一步是软硬件任务划分。我习惯把任务分成三类:

  • 必须由FPGA做的任务:位宽宽、速率高、时延敏感、接口时序严格。比如ADC接口的LVDS数据接收、高速信号解调、多通道同步触发;
  • 适合交给ARM的任务:协议解析、文件系统、网络传输、用户配置管理。比如TCP/UDP协议栈、以太网收发、传感器参数校准、命令交互;
  • 两边协商共同完成的任务:比如数据帧格式定义、流控机制、中断与状态查询。

这里头最容易反复改的是数据帧格式。我强烈建议在项目启动第一天就要和上位机软件的人把帧格式冻结下来,包括帧头、数据长度、通道号、采样率标志、时间戳、校验字段。因为FPGA逻辑一旦按照帧格式做了状态机,后面改一次就是一次RTL改动,还要同步改驱动和上位机解析,三方联调成本远比想象中高。

2. 硬件平台选型与关键参数设计

2.1 芯片选型怎么平衡资源和性能

芯片选型是整个系统最不透明的部分,厂商非常多,选型要回归需求。我参与的这个项目要求是8路同步采样、单路不高于1 MSPS,但也要求有充裕的逻辑资源做数字滤波和触发,同时还要支持千兆网传输,因此选型思路就有了方向。

先考虑FPGA侧的资源量:

  • 逻辑单元:至少要做8通道的采样控制、一级降速滤波、触发模块、数据缓存FIFO,这些加起来大概占一个小规模芯片资源,但为了后续扩容留余量,建议选中等规模逻辑资源以上的型号;
  • DSP Slice:数字滤波如果选FIR结构,一个通道几十个乘法器,8通道累加也是不小开销,DSP资源必须单列出来估算;
  • Block RAM:每通道一秒的采样数据是1 M采样点,如果按32位浮点算就是4 MB,这显然不能全塞在FPGA内部,但作为FIFO和中间缓存,至少也要几十KB到几百KB的BRAM。

ARM侧的考虑就简单得多:Cortex-A9双核级别的性能对数据传输和协议栈完全够用,再往上选也不是不行,但功耗和成本都会上去。如果项目里有跑Linux做界面交互的需求,还要关注DDR容量,512 MB起步比较舒服。

选型时我会做一个资源对照表,把严格需求、复用需求、预留需求分开列,避免一上来就选个大芯片,后面发现很多资源闲置,成本压力全在单板上面,量产的时候每一个多余的成本都要负责的。

2.2 采集前端电路设计要点

ADC我是按逐次逼近型来做的,这类型在中低速高精度场景比较常见。前端要关注的几个细节:

  • 输入调理:信号进来要先做阻抗匹配和电平搬移,运放电路建议用差分输出驱动ADC的差分输入,共模噪声能压掉不少;
  • 参考电压:ADR系列的基准电压源或者更高精度的基准都要远离数字电路区域,参考引脚旁边必须放去耦电容,按数据手册要求的容值和放置位置来;
  • ADC驱动时钟:这是最容易出问题的地方。采样时钟抖动直接影响信纳比,不能用普通的晶振直接怼,最好经过FPGA内部或者专用的时钟芯片做一下抖动清理。

实际调试中出现过一个现象:采出来的波形在某个频率附近噪声明显变大,找了很久,结果发现是采样时钟跟数字逻辑共用了同一条供电支路,时钟芯片供电纹波大,后来单独加了一路LDO给时钟供电,问题才消停。

2.3 存储与传输路径的硬件规划

数据从ADC出来到最终上传到上位机,路径上要经过FPGA内部缓存、DDR大缓存、MAC/PHY到以太网,这条路径上有几个瓶颈点需要提前规划:

  • FPGA到ARM的数据通道:Zynq或者Cyclone V SoC这类芯片都有AXI High-Performance端口,可以做到几百MB/s的量级,这里关键要看DMA描述符和数据位宽的配置;
  • DDR带宽分配:DDR除了给数据采集缓存用,还要跑Linux系统和网口DMA,读带宽和写带宽都在抢,必须做带宽预算。简单的算法:采样率乘以字节宽度乘以通道数,再加上网口发送同等数据量的读写开销,另外再乘1.5倍余量;
  • 网口PHY芯片:用千兆PHY是主流选择,MAC在ARM侧或者FPGA侧都有IP可用,注意MDIO配置和PHY地址别弄错,这个在调试时特别常见,以为网口不通其实是PHY寄存器没配好。

存储方面,我给这套系统配了DDR3,容量和带宽都能满足缓存的余量要求。有的场景对连续采集存储时长有严格要求,要做长时间的波形记录,可以考虑在ARM侧挂SD卡或者eMMC然后再异步搬运,但实时性差一些,接入外挂存储会增加调试复杂度,初期先把DDR缓存路径跑通比较稳妥。

3. 软硬件协同开发的完整流程实战

3.1 建立工程骨架与硬件抽象

开发SOPC系统,第一步是搭硬核工程。拿常规SoC FPGA流程来说:

  1. 用集成开发环境创建硬件工程,先配置ARM处理器系统,包括时钟、DDR控制器、UART、网口、GPIO,再到PL端添加自定义外设;
  2. 在地址映射表里给自定义外设分配基地址,这个地址表到后面写驱动时候要用,建议整理维护好;
  3. 生成硬件描述文件和板级支持包,导出给软件开发环境使用。

这些步骤看起来流水账,但有几个关键点:

  • 时钟配置一定要规划清楚。ADC采样时钟、FPGA逻辑主时钟、AXI总线时钟、ARM外设时钟,域不同就要考虑跨时钟域处理,否则数据采样出错找不到原因;
  • DDR控制器参数要根据具体DDR芯片颗粒的时序参数来配,不能照抄例程。用开发板跑通不代表换颗DDR芯片还能稳跑,颗粒厂家的数据手册要对应着填;
  • 硬件平台里GPIO的默认方向、上下拉,也要提前规划好,否则后级电路逻辑可能受到默认状态影响。

3.2 PL端逻辑核心:采样控制与预处理

PL端逻辑我拆成几块模块:

第一块是ADC接口逻辑。这部分直接跟ADC芯片数据手册打交道,要严格按照时序图来写状态机或者直接用原语原语拼接。启动采集后,ADC的转换时钟、片选、输出数据有效信号,每个信号边沿都要分析和DC时序约束对齐。我习惯先用虚拟IO抓内部信号看ADC输出是否正常,再往下做数据处理。

第二块是预处理逻辑。这个项目里主要包括均值滤波和简单的触发判断。均值滤波本质上就是个累加器,根据配置的抽点数对相邻采样做累加取平均,这个能有效降低随机噪声,代价是带宽降低。触发判断则要做好防抖处理,不然一次毛刺就可能误触发。

第三块是FIFO缓存和写DDR逻辑。采集数据从ADC接口出来是连续流,但ARM那边不可能一直等,而且网络发送是按包组织的,所以必须要做缓存。这块设计我用的是深度较大的异步FIFO,写侧时钟用采样时钟域,读侧时钟用AXI总线时钟域,水线设置不能太高也不能太低,高了会增加延迟,低了容易FIFO空读。

3.3 AXI总线与DMA:让数据流动起来

PL端逻辑完成了采样,怎么把数据交到ARM侧,这是SOPC系统最考验基本功的地方。最直接的办法是让ARM用软件查询PL端寄存器然后读数据,但这样每读一个32位都产生一次总线事务,CPU利用率暴涨,完全不可取。

实际工程里要使用DMA搬运。流程是这样的:

  1. ARM侧软件准备一块内存缓冲区,把物理地址告诉DMA控制器;
  2. FPGA把采样数据写进FIFO,FIFO到阈值后自动通知DMA控制器;
  3. DMA控制器通过AXI总线直接把FIFO数据搬到内存缓冲区,搬完触发中断;
  4. ARM在中断回调里处理缓冲区里的数据。

DMA的配置是开发中的硬骨头。描述符链表怎么组织、字节对齐要求、中断如何清标志,这些都要仔细看IP核的手册。我记得第一次调DMA中断的时候,数据搬完了但中断一直不触发,最后发现是中断在控制器内部被mask住了,寄存器名字就在手册某页的小字注释里,这种细节不踩一次坑很难记住。

另外,数据一致性问题是ARM和FPGA协作时绕不开的坎。如果运行Linux,用户态程序访问DMA缓冲区前必须做DMA一致性映射或者直接分配一致性内存。最省心的方式是使用驱动框架的DMA API来申请缓冲区,避免cache一致性带来的一堆疑难杂症。

3.4 PS端软件:裸机还是Linux,怎么选

PS端软件方案我最后选了Linux,理由有三条:

  • 千兆网的TCP协议栈在裸机上写工程量太大,还不稳定,Linux里收发是系统调用级别的事;
  • 后续调试时ssh进板卡改配置、传文件都方便,调试效率高很多;
  • 项目后期要叠加算法、日志、远程升级,Linux的生态支持能省下大量时间。

当然裸机方案也有存在的价值。如果只做简单串口控制加上数据直接通过定制协议发出,裸机开发更可控,启动时间短,资源占用极低,没有调度延迟的问题。我见过不少量产设备就是裸机跑的,稳定得很。但在我们这个项目里,考虑到长时间稳定传输和上位机交互的需求,Linux的成熟网络栈价值更高。

应用层程序结构大致分成三层:

  • 采集管理线程:负责打开设备驱动节点,启动/停止采集,读取状态;
  • 数据处理线程:接收DMA缓冲区里的原始数据,解析帧头,做格式转换,把有效数据封装成应用层帧;
  • 网络传输线程:监听命令Socket,把数据帧发送给上位机或者存储到本地。

这三个线程之间用环形缓冲区和条件变量串起来,实际跑下来CPU占用不到10%,完全满足设计指标。

4. 传输链路的设计与优化

4.1 网络传输选型:UDP还是TCP

这是需求决定论的典型体现。我们最初在上位机联调时打算直接用TCP,因为TCP有重传机制,数据不丢,但是实测下来发现一个问题:网络拥塞或对端处理不及时的时候,TCP的拥塞控制会主动降低发送速率,导致采集端FIFO溢出,数据掉点,而且越掉越严重,因为采集端一停,整个时序就乱了。

换到UDP之后,我们用应用层序列号和丢包统计来监测,实测千兆网下单向连续发送可以达到600 Mbps以上,几乎没有丢包。代价是要自己在驱动层和应用层做一定的丢包补偿。对于实时采集系统来说,UDP加应用层确认的方案确实更加合适,牺牲一点可靠性换来的是确定性。

如果你的项目强需求是不能丢数据,那需要在TCP基础上加大采集端缓冲深度,并在应用层做发送流控,避免网络阻塞反压到采集端。但工程上往往没有完美的网络方案,能提前跟需求方约定好“实时优先还是可靠优先”会省去后面很多争论。

4.2 链路带宽的实测数据和瓶颈分析

实际跑通后我做了几组带宽测试,数据如下:

传输方式实测带宽CPU占用丢包情况
TCP单连接客户端-服务器430 Mbps35%偶尔重传
UDP单线程发送620 Mbps22%正常无丢包
UDP多线程分片发送700 Mbps30%少量丢包
UDP加大缓冲区650 Mbps25%稳定无丢包

结论很明显:在千兆网条件下,单线程UDP配合合适的发送缓冲,已经能稳定跑在600 Mbps以上,瓶颈主要不在网口,反而在应用层的数据封装和调度开销上。优化手段主要是使用sendmmsg批量发送,一次系统调用发多个UDP包,大幅减少用户态和内核态的切换次数。

还要强调一个容易忽略的点:上位机侧一定要开大接收缓冲区。Linux下UDP接收缓冲默认很小,上百兆流量一来就直接丢包,我一般通过setsockopt设置接收缓冲区到几MB级别才算稳定。

4.3 上位机对接协议设计

传输协议我采用“帧头+长度+通道标识+序列号+数据体+CRC校验”的结构。帧头固定若干字节,用于帧同步和校验。序列号是排查丢包的利器,上位机只管检查序列号是否连续,就能知道UDP链路丢了几个包。

协议设计的细节其实不多,但有一个建议非常实用:通道标识别用固定枚举值,最好用配置的方式动态下发,这样FPGA增加通道时,上位机不用改代码,只需读取配置表。我们在项目的第二版进化了这个设计,后面所有参数配置都走同一套配置接口,上位机跟板卡之间不再因版本不一致而出幺蛾子。

5. 调试阶段的高频故障与排查实录

5.1 时钟和复位带来的幺蛾子

开发过程中我在时钟复位上耗了最多的调试时间。常见问题是:

  • 时钟树配置不当:ADC采样时钟和FPGA内部逻辑时钟不是同源,导致采样数据偶尔连续错位。解决办法是示波器量时钟,确认主时钟频率稳定后再查逻辑;
  • 异步复位释放问题:复位释放的时序如果处理不好,寄存器进入亚稳态,表现出来就是逻辑偶尔状态跳飞。解决办法是使用同步复位释放电路,用一个简单的打拍逻辑做复位同步;
  • 跨时钟域信号没做同步:最简单的是用两级寄存器同步,交互数据用异步FIFO。老生常谈,但实际工程里真的到处都是因为图省事不处理而翻车的。

5.2 DMA和中断的疑难杂症

  • 中断频繁丢失:DMA中断触发太频繁,导致CPU长时间在中断上下文里忙不过来,丢中断或中断延迟放大。解决办法是中转机制,把数据在驱动里做成批量上报,而不是每个包都唤醒应用层;
  • 内存物理地址不连续:Linux下申请大块DMA缓冲区时,如果分散在不同的物理页,DMA描述符就要处理散列,难度大增。解决办法是申请连续内存,或使用经过验证的DMA分配接口;
  • 描述符更新不及时:DMA搬完数据后描述符要置位,写驱动时容易忘了加内存屏障,导致CPU读到旧数据。这类问题极度隐蔽,用数据对比工具耗时最多。

5.3 网络传输的排查套路

网络不通的时候,先别急着查驱动,我会按下面顺序来排查:

  1. 物理层:用网线和PC直连,看PHY的link灯是否亮,这能快速排除硬件接错和PHY配置问题;
  2. 链路层:在板卡上配置静态IP之后,ping网关,能通则局域网基本OK;
  3. 传输层:开一个简单的UDP回环测试程序,PC发数据到板卡,板卡再发回来,确认双向通路;
  4. 应用层:再查发送端的FIFO是否溢出、对端缓冲是否够大。

这套排查顺序我用了好几个项目,每次都有效,而且能避免陷入“上来就怀疑FPGA逻辑错误”的大坑。实际上有很大比例的网络问题都出在上位机防火墙或者PC网卡驱动配置上。

5.4 采集数据质量问题的常见原因

  • 电源纹波过大导致的ADC输出噪声,表现为底噪抬高。解决办法是检查电源去耦是否有遗漏,尤其在ADC参考电压和电源引脚附近;
  • 接地处理不当,数字地和模拟地未彻底分开。这是采集系统非常容易翻车的地方,板子上地平面被切割得不合理,噪声就全耦合进模拟前端了;
  • 采样时钟抖动过大,表现为频谱底噪变大或者杂散变多。解决办法是选用低抖动时钟源,或者增加时钟清理电路。

这些噪声问题的排查通常需要频谱仪或者示波器的FFT功能辅助。肉眼看不出来的底噪变化,频谱上非常直观,建议调试时把频谱仪放在手边。

6. FPGA和ARM协作的软件工程细节

6.1 驱动的设计思路

设备驱动要暴露给应用层的操作其实就三个:启动采集、停止采集、读数据。抽象成文件操作就很自然:

  • open:打开设备节点,申请DMA缓冲区,配置DMA描述符;
  • ioctl:启动/停止采集,设置采样率、通道数、滤波参数;
  • read:阻塞读取一帧数据,数据来自DMA完成中断填充的环形缓冲区;
  • release:停止DMA,释放缓冲区。

这个设计模式简单清晰,应用层不管硬件细节,只管读数据。后续如果更换底层硬件平台,只要驱动接口保持一致,应用层代码几乎不用动。

6.2 数据缓冲与流控

从FPGA到应用层的数据链路至少要经过两级缓冲:FPGA内部的FIFO是缓冲链路瞬态不均匀的手段,DMA环形缓冲区是缓冲应用层调度不均匀的手段。环形缓冲区的大小设置要在内存占用和延迟之间做取舍。

流控上,当应用层来不及读数据时,环形缓冲区会被写满,这时驱动层应该停止DMA搬运,等待应用层消费后再恢复。否则底层不管上层死活,数据在FPGA侧溢出,丢包顺序就全乱了。

我设计驱动时还加了一个统计接口:记录DMA搬运次数、丢帧数、环形缓冲区满的次数,这样在排查系统性能瓶颈时可以直接从驱动里读统计数据,比在上位机抓包分析高效太多。

6.3 如何组织工程代码才不会被自己坑到

这个项目代码量不算特别大,但涉及FPGA工程、驱动、应用层、上位机四个代码仓库,版本管理混乱的话,联调阶段会非常痛苦。我建议:

  • FPGA工程和软件工程分仓库管理,各自维护版本记录;
  • 帧格式和寄存器地址表单独建文档仓库,改动必须经过评审记录;
  • 驱动和应用的接口定义用头文件方式共享,避免各改各的造成对接不上;
  • 每次硬件改动后,除了更新代码,还要同步更新硬件抽象层的寄存器映射文件。

联调阶段最大的敌人其实是“以为改了但没编译进去”。所有涉及FPGA逻辑或驱动接口的改动,都要在提交记录里写清楚对应的固件镜像哈希,这样测试出一个怪问题时才可能快速定位是不是版本不匹配。

7. 亲测好用的工具和调试手段

7.1 硬件调试工具

排障工具方面,逻辑分析仪和示波器是底线,但真要快速定位问题,我会加一个虚拟IO工具,在FPGA里嵌一个调试探针核,通过JTAG实时读取内部节点。这比每次都把信号引出来再抓方便太多。尤其像ADC接口时序、DMA握手信号这种高频信号,逻辑分析仪那是真好用。

另一个非常实用的手段是利用ARM侧的资源做远程调试。系统跑起Linux之后,FPGA内部的采样数据可以直接映射到ARM的内存,我可以在ARM上跑一段小脚本周期性打印采样平均值,CPU跑起来之后很快就能判断FPGA那边数据是否在正常流动。或者在Linux上开一个网络调试接口,把寄存器状态通过网口发到PC上的调试脚本里。

7.2 软硬件联调的节奏把控

我的习惯是从小到大验证,不要一口气把整个系统全跑起来才看效果。节奏大概是:

  1. 先跑一个最小系统:ARM能启动,UART打印正常,GPIO翻转正常;
  2. 验证ADC接口:在FPGA里写一个简单的采集逻辑,把采样数据直接写到GPIO或者一个测试跟踪缓存,确认ADC数据没有毛刺和分析错误;
  3. 验证FIFO和DMA:开一个PC端不停读数据的程序,FPGA发固定pattern,对比PC收到的字节与预期是否一致;
  4. 加入完整的帧协议和网络上传,最后再跑真实信号。

这个过程每一步都有明确的通过标准,出了问题也能快速缩小排查范围。最忌讳的是直接跳到最后一步,在完整系统上空跑,一旦出了问题,FPGA逻辑、驱动、应用、上位机,每一环都有可能,排查成本直接翻倍。

8. 踩坑后总结的个人体会

这套项目做下来,最大的体会是SOPC的价值不在于单点性能,而在于系统整合度。以前纯FPGA方案里,网络协议栈要么用开源轮子,要么自己用状态机写简化版,始终不稳定。现在ARM核直接跑标准Linux网络栈,生态问题完全由软件生态解决,FPGA只需要专注做好采集处理这一件事。

另一个很深的体会是:工程里最普遍的问题不是芯片性能不够,而是接口定义和任务分工没想清楚就动手。如果让我重新做一遍,我一定会在软件写第一行代码之前,把寄存器映射表、帧格式、中断机制、缓冲区大小这些接口细节统统定死,哪怕前期多花一周时间都值得。

这套系统后续扩展的空间也很大,比如加多通道同步采集,或者直接把AI算法的一部分下沉到FPGA做预处理,再从ARM侧跑更复杂的模型,SOPC的架构优势会越来越明显。前期的软硬件协同设计规范一旦建好,后面再加功能就只是往骨架上添肉的事。

最后分享一个小技巧:做这种FPGA和ARM协同时,一定要在FPGA设计里留下一个版本寄存器,ARM启动后先读它,和期望值做比较,不一致就亮红灯。这个看似简单的东西,在团队协作、板卡维护、固件升级时会省掉无数低级沟通成本,强烈建议每个项目都加上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询