一、引言
PCIE908 是北京科恒盛远科技有限公司推出的一款高性能数据采集卡,主芯片采用 Xilinx Zynq UltraScale+ RFSoC Gen3 系列 XCZU47DR,板载 8 路 ADC、8 路 DAC、两组 PL 端 DDR4,并提供一个 QSFP28 100G 以太网接口。
对于高速数据采集系统,100G 光口"能连通、能收到数据"只是起点。真正的工程问题是:持续数据能否无缺口进入计算机,32 路 UDP Flow 是否均匀分配到多个 CPU,接收、重组和写盘是否保持原始包序,文件回放到 FPGA 后是否仍逐字节连续,以及出现丢包时能否区分 FPGA、光链路、网卡/驱动、操作系统调度和应用程序的责任边界。
本项目围绕 PCIE908 完成了一套双向 100G UDP 数据通路:FPGA 可将测试数据或采集数据通过 32 Flow 上传至 Windows 上位机,上位机采用 Winsock Registered I/O(RIO)接收、检查并按顺序写入文件;反向可将超大文件分段回放到 FPGA DDR,再通过 play_en、dat_vld 和 dat 输出给后级逻辑。
调试过程也暴露出一个很重要的事实:100G 是链路能力,不是任何一台普通电脑都能自动获得的应用层吞吐量。 本文使用的 i7-12700K 主机在日常桌面应用中并不算低端,但面对 100GbE 持续巨帧数据面,它仍属于 CPU 核数、RSS 队列数和存储能力均受限的平台。最终结果必须同时说明板卡能力、主机能力和协议可靠性,不能把三者混为一谈。

图 1 PCIE908 RFSoC XCZU47DR 板卡
二、系统组成与测试条件
本次调试采用点对点 100G 光纤连接。FPGA 和 PC 使用静态 IPv4 地址,UDP payload 设为 8000 字节,网卡 MTU 设为 9000,从而减少每单位有效载荷对应的包数和协议开销。
| 项目 | 配置 |
|---|---|
| FPGA 板卡 | PCIE908,XCZU47DR-2FFVG1517I |
| 网络接口 | QSFP28,100GbE,FPGA 端 CMAC |
| PC 网卡 | Intel Ethernet Controller E810-C for QSFP |
| PC / FPGA 地址 | 192.168.1.3 / 192.168.1.128 |
| MTU / UDP payload | 9000 / 8000 字节 |
| 上传 Flow | 32 个源端口,由运行时 LUT 配置 |
| 接收架构 | RSS8 + RIO8,8 个独立 CQ/线程 |
| 注册接收深度 | 总计 32768,单分片 4096 |
| 测试节流 | FPGA GAP 参数,正式基准 GAP=200 |
| 默认诊断路径 | PURE-RIO,Npcap/WPR/写盘默认关闭 |
这里的"上传"是 FPGA 向 PC 发送采集数据,"下传"或"回放"是 PC 将文件发送给 FPGA。两条通路共用 100G 物理链路,但瓶颈和完整性判据并不相同。

图 2 PCIE908 100G UDP 双向数据通路
三、FPGA端100G UDP实现
FPGA 侧并不是把 ADC 数据直接接到 CMAC。为了支持持续采集、突发吸收和回放,系统在 UDP 核与两路 DDR4 之间建立了 FIFO/DDR 数据通道:DDR0 主要服务上传,DDR1 主要服务文件回放。两路 DDR 当前都使用模式 3,即 FIFO 模式。
1. 8000字节巨帧负载
每个 UDP 数据报携带 8000 字节有效载荷。数据通路宽度为 512 bit,因此每个 UDP 包对应:8000 / 64 = 125 个 512-bit 数据拍。
125 并不是 DDR AXI 64 拍 burst 的整数倍。这一看似普通的算术关系,后来成为 DDR1 包边界重复故障的关键线索。
2. 32 Flow运行时端口LUT
如果所有 UDP 包都使用同一组五元组,Windows RSS 通常会把它们哈希到同一个接收队列。此时即使计算机有很多核心,实际处理中断和 DPC 的仍可能只是一个核心,100G 流量很快就会在网卡或驱动层丢弃。
为此,FPGA 按运行时 LUT 轮换 32 个 UDP 源端口。上位机先测量 E810 的 RSS 哈希结果,再选择能够均匀落到 8 个 RSS CPU 的端口组合,并通过控制命令写入 FPGA。最终每个 RSS CPU 承担 4 个 Flow,每 8 个数据包完成一次跨队列轮询。
这种设计比"固定写死32个连续端口"更可靠,因为不同主机、网卡驱动和 RSS key 可能产生不同映射。板卡可以不重新综合 bitstream,就适配另一台电脑的 RSS 环境。
3. 控制面与数据面分离
控制 UDP 负责连接、模式设置、启动/停止、回放长度和运行时 Flow LUT;数据 UDP 只承载大块数据。开始记录、开始回放和停止都采用明确的状态转换,上位机逐条检查控制报文发送结果,避免界面显示"开始"而 FPGA 状态机并未真正进入数据态。
四、Windows上位机:从单线程收包到RSS8/RIO8
早期方案使用普通 Winsock 或 Npcap 接收,能够证明链路和数据格式,但很难稳定承受高包速。最终正式路径采用 Winsock RIO:预先注册大块内存,循环复用固定的接收切片,避免在热路径中频繁分配内存和建立内核映射。
1. 八个独立接收分片
上位机为 8 个 RSS shard 分别建立 Socket、RIO completion queue 和 worker 线程,总注册接收数 32768。每个分片维持 4096 个已投递接收请求,并记录最低可见水位、单次出队峰值、最大完成间隔、通知错误、CQ 损坏和重投失败。
这套统计非常重要。发生丢包时,如果 RIO posted receive 仍接近 4096、完成队列没有满批次、没有重投错误,就不能把原因简单归结为"应用缓冲不够"。它更可能发生在网卡接收队列、驱动/DPC调度或进入 RIO 之前。
2. 记录必须按包序,而不是按数据内容
测试阶段 FPGA 产生从 0 开始的 32 位递增数,便于发现重复、前跳和倒退。但真实业务数据可能是 ADC 采样值,不能依赖"数值递增"决定存储位置。
正式记录逻辑以每个 Flow 的包序号和跨 Flow 归并窗口恢复 FPGA 发送顺序,再写入界面指定文件。递增载荷只作为可选完整性校验,不参与排序。写盘队列溢出或 WriteFile 失败时,程序不会静默丢弃,而是自动请求 FPGA 停流、排空已入队数据、刷新文件并保存错误诊断。
3. 首个完整包缺口自动停流
100G UDP 故障往往持续时间很短。如果等人工观察界面再停止,关键现场已经被后续数据覆盖。因此程序在发现首个可确认的完整 UDP 缺口时自动停流,并保存:
- 32 Flow 的包数、缺包数和缺口位置;
- 8 个 RSS CPU 的分布;
- RIO 水位、CQ批次、完成间隔和错误;
- InDiscards 起止值及 E810 丢弃计数;
- 可选的 Npcap、WPR 或 ILA 对照结果。
五、电脑配置为什么决定100G UDP上限
本次实测主机配置如下。
| 项目 | 本机配置 | 对 100G UDP 的影响 |
|---|---|---|
| CPU | Intel Core i7-12700K,12 核 20 线程(8P+4E混合架构) | 日常性能较强,但持续100G收包时可用于RSS/DPC的物理核心有限;P核、E核和超线程不能等价看待 |
| 内存 | 31.7 GiB | 可建立256 MiB RIO注册池和512 MiB记录队列,但不能依靠内存长期吸收数GB/s写盘落差 |
| 网卡 | Intel E810-C QSFP | 支持100GbE,但当前驱动只暴露8个RSS接收队列和8个MaxProcessors |
| RSS模式 | RSS启用,NUMAStatic,队列数8 | 32 Flow最终只能分摊到8个接收CPU;继续提高流量时单队列/DPC可能先达到上限 |
| 操作系统 | Windows 11 专业版 64位 | 混合核调度、后台任务、驱动DPC和用户线程亲和性都会影响微秒级处理空窗 |
| 驱动 | E810 1.18.86.0 | 更新驱动可能改善实现细节,但如果RSS队列上限仍为8,就不会凭空增加物理并行度 |
需要特别说明:i7-12700K 并不是通常意义上的"低端电脑",但相对于 100GbE 的 12.5 GB/s 原始线速,它确实属于受限主机。最终 GAP=200 的有效载荷约为 4569.5 MiB/s,即约 38.3 Gb/s,只达到100G线速的一部分。这不代表 PCIE908 的 CMAC 只能工作在 38 Gb/s,而是当前主机、E810 RSS8和Windows接收路径共同形成的系统上限。
继续降低 GAP 后,丢包曾集中出现在单个 RSS CPU 对应的若干 Flow;与此同时 RIO 接收池仍有明显余量。这个现象说明,瓶颈可能出现在网卡/驱动队列或系统调度窗口,而不是 FPGA 停发,也不是应用层32768个缓冲耗尽。
如果目标是进一步逼近100GbE线速,电脑升级应关注以下条件,而不只是"CPU主频更高":
- 更多高性能物理核心,并能为网卡 RSS/DPC、RIO worker、文件重组和写盘线程预留独立核心;
- 网卡和驱动能够提供 16 或更多 RSS 队列,而不是仍限制为 RSS8;
- CPU、E810 和内存处于合理的 NUMA/PCIe 拓扑,避免跨 NUMA 内存访问;
- 足够的内存通道带宽和容量,用于注册缓冲、重组队列和短时突发吸收;
- 若要求持续写盘达到数 GB/s,需要多块高性能 NVMe、RAID0/条带化或专用存储系统;单盘顺序写入通常会成为新的瓶颈;
- 控制后台任务、电源策略、CPU节能状态和安全软件干扰,以减少毫秒级调度空窗。
为适应不同电脑,软件已加入硬件识别和自动调优框架:读取 CPU/NUMA、网卡、RSS 队列和硬件指纹,在 1~16 个 RIO shard 范围内选择配置,再校准 32 Flow 到各 RSS CPU 的映射。自动调优能把每台机器推到其自身合理上限,但它不能突破物理核心、RSS队列、PCIe和存储的硬件边界。
六、丢包定位:不能只看一个计数器
UDP 没有端到端确认和重传。一个包没有出现在应用程序里,可能发生在多个位置:FPGA没有生成、CMAC/光链路异常、NIC入口丢弃、驱动/DPC来不及处理、RIO队列错误、应用重组错误,或者写盘队列溢出。
| 观察量 | 能说明什么 | 不能单独证明什么 |
|---|---|---|
| 32 Flow序号缺口 | 应用最终少收了哪些完整UDP包 | 丢失具体发生在FPGA、链路还是主机 |
| InDiscards增量 | IP层/接口统计存在接收丢弃 | 不能覆盖所有硬件位置,也可能延迟更新 |
| E810丢弃计数 | 网卡/驱动报告的接收异常 | 计数器可能回绕、延迟或统计口径不同 |
| RIO posted/CQ/error | 应用注册池和完成队列是否耗尽或报错 | 数值正常不能证明NIC之前没有丢包 |
| Npcap/Wireshark | 可对照包ID、端口和接口抓包 | 抓包本身会增加主机负担,不适合最终高速基准 |
| FPGA ILA | 能观察UDP入口、DDR pre/post和业务输出连续性 | 只观察post侧时,不能自动区分UDP入口与DDR写入前 |
曾有一轮 GAP=200 测试在约 6.7 秒时缺少 120 个 UDP 包,全部集中到同一个 RSS CPU 的4个 Flow;InDiscards 同步增加 120,而 RIO posted receive 仍接近满深度,没有 CQ 损坏和重投失败。这组证据支持"NIC/驱动/调度域发生丢弃",但工程结论仍需结合重复试验、RSS分布和FPGA端证据,不能仅凭 InDiscards 一个计数器就断言网卡硬件有故障。
后续通过运行时 Flow LUT 重新均衡32个端口,并保持接收热路径静默,GAP=200 的10秒和60秒复验均实现32 Flow零缺包、InDiscards零增量。
七、一次典型FPGA故障:DDR1包边界重复一拍
回放32位连续数文件时,ILA 曾在 DDR1 post 侧捕获到:FIFO读至空边界时,最后一个有效512-bit数据与前一个数据重复,导致多产生一拍。
排查时不能因为"上传也使用DDR且上传正常"就排除DDR控制。上传和回放虽然共用相同的通用 DDR 模块,但包边界、读写方向、有效信号和停止条件不同,故障可能只在 DDR1 写入边界出现。
最终根因与两个边界条件有关:
- 8000字节UDP包为125个512-bit拍,而DDR AXI burst为64拍;包边界处 Standard FIFO 会短暂变空;
- FIFO没有新数据时,下游逻辑仍完成了一次 AXI W 握手,把上一拍再次写入DDR;停止命令还曾因无效字节判据过严而偶尔进入数据路径。
修复方法不是简单把 FIFO 改为 FWFT。FWFT 的 valid 语义不同,直接替换反而会破坏已有的上传/停止逻辑。最终方案保持 Standard FIFO,在临时为空时同时阻断 axi_wvalid 和反馈给上游的 axi_wready,直到下一包真实数据到达;文件真正结束时使用独立的 end pending 放行最后一拍。同时,停止命令只按有效字节识别,不再要求无效字节为零。
修复后,诊断 bit 对超大文件连续回放180秒,完成约84.42 GB主机提交,监视窗口未再触发DDR1写入不连续;最终 bit 的1 MiB完整文件也通过功能复测。
八、一次典型主机"优化陷阱":速度翻倍但数据缺了一整包
为了提升下传速度,上位机曾试验 Windows UDP Segmentation Offload(USO)。batch=4 时主机发送速率达到 1298.2 MiB/s,明显高于非USO的624.0 MiB/s;IOCP排队量与完成量一致,sendErrors=0,E810发送包计数也符合预期。
如果只看PC界面,这似乎是一次成功优化。但FPGA hw_ila_3 在 DDR1 post 侧捕获到4次确定的前向跳变,每次恰好缺少125个512-bit拍,也就是一个完整的8000字节UDP包。源文件对应位置连续,排除了文件自身缺口。
这说明主机"发送成功"只证明数据交给了协议栈或网卡,不能证明FPGA完整收到。USO形成的巨帧微突发超过了当前无ACK、无重传UDP接收链路的可靠承受能力。因此正式版本将 playback_uso 恢复为0,USO仅保留作显式诊断A/B,1298.2 MiB/s不再作为有效带宽宣传。

图 3 下传优化必须以FPGA端数据完整性为判据
九、实测结果
1. FPGA上传到PC
运行时32 Flow LUT、RSS8/RIO8、payload=8000、MTU=9000、GAP=200条件下,60秒正式轮结果如下:
| 指标 | 实测结果 |
|---|---|
| 有效运行时间 | 60.016 s |
| 有效载荷吞吐量 | 4569.453 MiB/s,约38.3 Gb/s |
| UDP包数 | 35,945,226 |
| 有效载荷总量 | 287,561,808,000 字节 |
| 32 Flow缺包 | 0 |
| payload连续性错误/倒退 | 0 / 0 |
| RIO错误/RSS错配 | 0 / 0 |
| InDiscards起止 | 0 → 0 |
| 8个RSS CPU包数差异 | 最大仅9包 |
| 数据完整率 | 100% |

图 4 GAP=1000与GAP=200上传有效载荷吞吐量对比
GAP=1000 的早期30秒基准约为996.4 MiB/s;通过32 Flow均衡、RIO8独立CQ/线程和运行时LUT,GAP=200达到约4569.5 MiB/s。继续降低GAP并不能保证继续线性提速,因为本机只有RSS8,CPU/驱动队列会先出现局部饱和。
2. PC下传到FPGA
主机侧非USO顺序预读优化将完整82.3 GiB有效文件部分的发送速度提升到约624.0 MiB/s,3个分段全部完成,发送错误为0;尾部182,720字节因不足1 MiB按协议规则丢弃。
需要区分两个验收层次:上述结果证明文件读取、分段、IOCP完成和尾部处理正确;FPGA端最终长期连续性仍应使用 ddr1_err、dat_vld/dat 或业务校验再次确认。USO的1298.2 MiB/s已经因FPGA端缺失整包而明确否决。
十、当前边界与下一步RDMA方向
本次100G UDP调试已经形成可运行、可诊断、可记录和可回放的工程基线,但仍有清晰边界:
- 当前主机受RSS8和消费级混合核CPU限制,上传有效载荷约38.3 Gb/s,不代表100G物理链路或FPGA CMAC上限;
- UDP本身没有ACK、信用流控和重传,微突发下即使PC发送成功,FPGA仍可能缺少完整数据报;
- 高速持续写盘需要独立验证存储阵列,不应把"内存接收速度"当作"长期落盘速度";
- 当前功能bit曾在时序未完全收敛的条件下用于功能测试,正式产品发布前仍需完成时序收敛和更长时间稳定性验收;
- 非USO高带宽回放还应补充长时间FPGA端连续性复验。
下一阶段计划在这套稳定的板卡数据通路、DDR缓存、运行时硬件识别和完整性诊断基础上研究RDMA。RDMA的价值不是简单把UDP换一个名字,而是利用队列、完成、内存注册、流控和拥塞管理语义,减少CPU参与,并把"已提交"推进到更接近"对端可靠接收"的工程模型。
十一、结论
PCIE908 的100G UDP实现证明,高速数据系统的难点并不只在FPGA写出一个UDP包。真正决定可用性的,是从RFSoC数据源、DDR边界、CMAC、光链路、E810 RSS、Windows RIO、CPU调度、顺序重组到文件存储的整条链路。
本次调试取得的核心结果是:在一台 i7-12700K、32 GB内存、E810 RSS8的普通工作站上,FPGA上传在GAP=200时达到4569.453 MiB/s,60秒接收35,945,226个8000字节UDP包,32 Flow零缺包、InDiscards零增量、数据完整率100%。同时,我们也用DDR边界重复和USO整包缺失两个实例说明:速度数字只有与端到端数据完整性放在一起,才有工程意义。
对于需要接近100GbE线速的应用,应配置更多高性能物理核心、更多RSS队列、合理NUMA/PCIe拓扑和高吞吐NVMe阵列;软件自动调优可以充分利用硬件,但无法替代硬件并行度。
下期预告
PCIE908 RFSoC XCZU47DR 板卡技术系列(四): AMD RF Analyzer 工具的成功移植