在国内金融 IT 圈,FPGA 早就算不上什么冷门技术了,但我发现很多人对它还是停留在“知道能加速”的阶段:到底怎么加速、能快多少、实际工程里要踩多少坑,能讲清楚的人并不多。前两年我完整参与了一套面向沪深 A 股 Level-2 行情的 FPGA 加速方案,从需求分析、板卡选型、RTL 设计到灰度上线全程跟了下来,这中间积累的东西,确实值得好好写一篇。
这篇文章就以沪深行情加速为例,拆解 FPGA 在金融行业落地时的核心思路、关键设计和实操步骤。无论你是刚开始接触 FPGA 的软硬件工程师,还是正在为极速交易系统选型的技术负责人,这篇文章都会给你一份贴近真实工程的经验参考。
1. 为什么金融行情加速绕不开 FPGA
1.1 软件处理行情的“天花板”在哪里
很多人以为行情慢是网络问题,实际上网络只是其中一环。更主要的瓶颈在 CPU 的处理方式上。一套 X86 服务器跑行情网关,哪怕用上了 DPDK、busy polling、CPU 绑核这些优化手段,单条行情的解析延迟通常也就压到 3 到 7 微秒。这个数字看起来还行,但要注意,行情是持续洪峰式到达的,尤其是开盘集合竞价那几秒,几十路组播流同时在打,CPU 要负责中断、内存拷贝、TCP/IP 协议栈解析、应用层业务处理,任何一个环节发生调度抖动、cache miss,延迟就瞬间从微秒级变成几十微秒甚至上百微秒。
我实测过一套纯软件行情系统,在 9:15 到 9:25 这个时段,P99 延迟从平时的 6 微秒直接飙升到 58 微秒。对于手工交易或者普通程序化交易来说,这个数字也许还能接受,但对高频做市、套利策略来说,几微秒的差异就意味着能不能抢到单子,这就是致命的。
1.2 CPU 串行执行与 FPGA 并行流水线的本质差异
FPGA 之所以能打破天花板,核心是其“硬件并行”的执行模型。CPU 是串行取指、译码、执行的,哪怕多核并行,核与核之间的通信也要走共享总线;而 FPGA 里的逻辑资源可以同时跑几十路独立的解析模块,每一路都只做一件事,数据像流水线一样一级一级往下传,处理完第一帧的第 N 个字段时,第二帧的第 N-1 个字段也刚好在同一拍处理完毕。
用一个不恰当的类比来说:CPU 像是只有一个收银台的超市,无论顾客排队多整齐,一次结账只能服务一个人;FPGA 则像是直接开了几十个窗口,每个窗口只扫一种商品,整条队伍以极高的速度整体往前挪。行情解析这种任务正好适合这种模式——它不是那种需要复杂分支跳转的“思考型”任务,而是大量重复的、规则明确的“体力型”任务。
当然,FPGA 也有短板:开发周期长、调试门槛高、逻辑资源受限。它不适合做“什么都能处理”的通用计算,而更适合做“特定规则下的极致加工”。行情解析恰好就在这个甜蜜区里,这也是它能在金融低延迟场景里站稳脚跟的根本原因。
2. 沪深行情加速的整体架构与方案选型
2.1 一条完整的行情链路是怎么走的
在聊 FPGA 具体怎么做之前,有必要先把沪深行情链路里的角色梳理清楚。上交所和深交所分别通过各自的行情系统对外发布行情,上交所主要是 STEP 协议承载的 Level-2 行情,深交所则是基于二进制格式的行情流。两者底层都跑在 UDP 组播之上,但业务字段的组织方式完全不同。
从券商或者私募的角度看,一条完整行情链路大致是:交易所 → 交易所前置机 → 光模块/交换机 → 行情网关 → 内存总线 → 策略进程。传统方案里,行情网关运行在 X86 服务器上,做组播接收、报文重组、协议解析、逐笔快照数据重组,最后通过共享内存或者低延迟消息总线把数据推给策略端。
FPGA 介入后,架构变成了:交易所 → 光模块 → FPGA 板卡(内嵌行情解析引擎) → PCIe DMA → 策略进程。在 FPGA 板卡上完成网卡收包、组播过滤、协议解析、业务字段提取、行情快照维护等全部工作,CPU 只负责策略逻辑本身。
2.2 FPGA 金融加速方案的三种常见形态
我见过的 FPGA 行情加速方案大致分三类,我在这里整理一下各自的定位:
| 方案形态 | 部署位置 | 主要工作 | 适用场景 |
|---|---|---|---|
| 智能网卡形态 | 服务器内 PCIe 插槽 | 收包、过滤、解析,标准网卡功能+业务卸载 | 对现有系统改动最小,快速见效 |
| 独立行情解析卡 | 行情前置机房,串接在组播链路中 | 接收原始行情,重生成低延迟私有行情流 | 搭建独立极速行情总线,适合自建系统 |
| 一体化极速交易卡 | 交易服务器内 | 同时处理行情解析和交易报单、风控逻辑 | 高频做市、抢单策略,追求亚微秒级闭环 |
对绝大多数团队来说,第一次试水建议从第一种或者第二种开始,因为它们不需要改动策略端的核心代码,风险可控。直接上第三种一体化卡,对团队的 FPGA 能力和业务理解要求都非常高,我见过不少团队在第三步上栽了跟头。
2.3 选型时要重点关注的 5 个指标
选 FPGA 板卡,不能光看芯片型号和逻辑资源,有几个指标在金融行情场景里比资源量更关键:
第一是网口延迟和收发 FIFO 深度。行情场景下流量往往是突发性的,发送队列深度不足会导致丢包;接收侧要特别注意 PHY 芯片的接收延迟参数,同样一颗 FPGA,不同的 PHY 搭配会差出一两百纳秒。
第二是 DDR 带宽与 Bank 数量。维护 Level-2 行情的逐笔委托和逐笔成交快照时,需要频繁读写外部存储。DDR 带宽不足会导致阻塞,Bank 数量过少则无法满足多路并行访问需求。建议至少选择支持 64bit DDR4 且拥有 4 个以上独立 Bank 的板卡。
第三是 PCIe 通道数和 DMA 引擎。行情解析结果要快速送到 CPU 策略进程,PCIe 带宽和 DMA 描述符管理能力决定了数据上行速度。Gen3 x8 是底线,x16 更稳妥。
第四是时钟管理能力。金融行情系统对时间同步要求极高,板卡必须支持外部 1588/PTP 时钟同步或 IRIG-B 码输入,否则输出数据的时间戳在跨节点对比时就是乱的。
第五是接口标准兼容性。沪深两所的行情接入虽然都是千兆/万兆以太网,但实际部署中可能面临光口、电口混用的情况,板卡至少要支持 SFP+ 光口,最好同时兼容 10GBASE-R 和 1000BASE-X,方便适配不同的交易所接入柜。
3. 核心细节解析与实操要点
3.1 硬件解析行情帧:从以太网到应用层的一体化流水线
FPGA 解析行情,最核心的思想就是把软件里“一层一层剥协议头”的过程变成一条硬件流水线。以深交所 Level-2 行情为例,原始报文从 PHY 进来后,会经过这样几级处理:
以太网帧头校验(MAC)→ IP 头解析 → UDP 头解析 → 业务报文头解析 → 逐笔数据提取 → 快照维护 → 输出结果。
每一级对应一段独立的组合逻辑或者时序逻辑,级与级之间用 FIFO 或者寄存器打拍衔接。这里有一个容易被忽略的细节:软件解析时,协议头字段是一个字节一个字节顺序读的;而在 FPGA 里,我们应该用“并行位宽提取”的方式,把关键字段(证券代码、行情类别、时间戳、价格字段)一次性截取出来,送到下一级做处理。这样做的好处是,不同层级之间不再互相等待,整条流水线只需要处理最慢一级的时间。
我拿自己做过的项目举个例子,深交所 Level-2 行情快照报文长度通常是 200 到 1500 字节不等,解析时我们用 64bit 位宽做数据总线,一个 1500 字节的报文在 FPGA 内部只需要约 188 个时钟周期即可完成全部字段提取,而在一个 3GHz 的 X86 上,仅内存拷贝就需要至少 300 个周期起步,这就是硬件并行带来的差距。
3.2 跨时钟域与时序收敛,两个决定成败的设计点
FPGA 工程里,时序违例是家常便饭,行情加速板上表现更明显。我见过不少第一次做 FPGA 行情解析的工程师,把全部精力放在解析逻辑上,结果综合实现后一堆时序违例,跑起来要么丢数据要么偶发错误。
第一个重点:跨时钟域(CDC)处理。行情板卡里通常存在多个时钟域:PHY 恢复出的 125MHz/156.25MHz 时钟、用户逻辑的 200MHz 主时钟、DDR 控制器相关的时钟。帧数据从 PHY 过来时,是在恢复时钟域里的,如果直接拿主时钟去采,数据相位完全不确定,必然出错。处理办法是:在 PHY 侧先把数据同步到本地可用时钟域,通过异步 FIFO 做数据缓冲,并通过两级同步器消除亚稳态风险。我个人的习惯是,所有跨时钟域信号必须建立一张专门的 CDC 清单,逐条确认同步方案,不允许凭空跳过。
第二个重点:时序约束要提前做,不要最后才补。很多工程师喜欢写完 RTL 再补 XDC,这是非常危险的操作。正确的流程是:建工程时就把时钟约束、输入输出延迟约束写清楚,同时按频率规划好关键路径的流水线级数。行情解析这种长流水线结构,如果中间某一段组合逻辑过深,比如行情头解析那一段实现了上百个条件判断,时序大概率会挂。此时该做的不是盲目优化代码,而是重新审视数据处理粒度,在字段提取之间插入额外寄存器,让每一级组合逻辑长度控制在 10 到 15 级以内的 LUT 级联。
3.3 快照与订单簿重建:FPGA 里的存储管理艺术
沪深 Level-2 行情除了含逐笔成交和逐笔委托之外,还会周期性发送十档快照。FPGA 里重建订单簿和维护快照,比软件复杂得多,因为 FPGA 的存储体系是分层的:LUTRAM、BRAM、URAM、DDR,每一层的容量和延迟都不同。
我采用的方案是:用 BRAM 维护一个环形缓冲池,缓存最近一分钟内的逐笔委托数据;用 URAM 维护证券代码到快照索引的映射表;用 DDR 存放完整的多档快照数据。之所以分层,是因为快照索引查询是高频操作,必须做到纳秒级;而完整快照的维护相对低频,允许微秒级访问。
做快照更新时要特别注意乒乓操作。FPGA 里更新一份快照数据时,如果读方正需要读取某个字段,而你同时在做写覆盖,读出来的数据可能是旧的也可能是新的,完全不确定。解决方法是做双缓冲(Double Buffer):一份叫生效表,一份叫影子表,写入方先改写影子表,改完通过一个状态标识切换,读方始终操作生效表。这个机制在软件里用锁或者原子操作就能做,在 FPGA 里就必须靠硬件时序控制,务必在 RTL 设计阶段就把它固定下来。
4. 实操过程与核心环节实现
4.1 从零搭建行情解析工程:环境与代码结构准备
我会以 Xilinx UltraScale+ 系列 FPGA 和 Vivado 2022.1 工具链为例,介绍一套完整的实现流程。准备工作方面,建议在工程开始前先把下面这几个文件备齐:
- 板卡的原理图 PDF 和引脚约束文件(XDC)
- 光口 PHY 芯片手册(重点看 MDIO 配置寄存器和 RX 时钟恢复方式)
- 交易所行情接口文档(重点是报文头字段、字节序、时间戳格式)
- Vivado 工程模板,如果公司没有现成的,可以用 Xilinx 官方的 Ethernet Subsystem 示例工程打底
工程目录建议按功能模块拆分:rtl 目录放业务逻辑、ip 目录放 Xilinx 官方 IP(Ethernet MAC、DDR Controller、PCIe)、sim 目录放仿真测试平台、syn 目录放综合约束脚本。我在第一个 FPGA 项目里吃了目录混乱的亏,后来统一结构后,问题定位速度快了一倍。
4.2 最小可用的行情解析模块长什么样
我在这里给一个极简的行情头部解析模块示例,原理是抓取 UDP 头之后的业务字段,按照深交所二进制协议提取证券代码和行情时间戳。完整代码量很大,这里只保留关键逻辑,目的是让大家看清 FPGA 行情处理的基本写法:
module market_data_parser ( input wire clk_200m, input wire rst_n, input wire [63:0] data_in, input wire data_valid, input wire [15:0] data_len, // 帧的有效长度 output reg [23:0] security_id, // 证券代码 output reg [31:0] trade_time, // 行情时间戳 output reg frame_done ); localparam IDLE = 2'd0; localparam WAIT_APP = 2'd1; localparam EXTRACT = 2'd2; reg [1:0] state; reg [15:0] byte_cnt; always @(posedge clk_200m or negedge rst_n) begin if (!rst_n) begin state <= IDLE; byte_cnt <= 16'd0; frame_done <= 1'b0; end else begin case (state) IDLE: begin frame_done <= 1'b0; if (data_valid) state <= WAIT_APP; end WAIT_APP: begin // 假定以太网+IP+UDP固定头长度为42字节, // 业务头部从这里开始累积计数 if (data_valid) begin byte_cnt <= byte_cnt + 16'd8; // 在业务头偏移处提取字段,实际工程中需根据协议调整 if (byte_cnt >= 16'd42 && byte_cnt < 16'd50) security_id <= data_in[23:0]; if (byte_cnt >= 16'd50 && byte_cnt < 16'd60) trade_time <= data_in[31:0]; if (byte_cnt >= data_len) state <= EXTRACT; end end EXTRACT: begin frame_done <= 1'b1; state <= IDLE; byte_cnt <= 16'd0; end default: state <= IDLE; endcase end end endmodule这段代码展示的是一种最朴素的状态机解析方式,真实工程里我会用移位寄存器和流式解析替代,性能高很多,但理解难度也更大。初学者先跑通这种基础版本,再去优化性能,才是稳妥的路径。
4.3 性能验证:如何量化延迟和吞吐
写完解析模块,怎么证明它真的“快”?我一般采用两个指标:单帧解析延迟和持续吞吐带宽。
单帧解析延迟用 ILA(集成逻辑分析仪)在线抓取,打一个硬件时间戳标记在帧头进入解析模块的时刻,再用另一个标记记录完成时刻,两者差值除以时钟频率就是延迟。我实测下来,在 300MHz 时钟下,单帧 1500 字节行情报文的解析延迟约为 0.9 微秒,这里边包含 42 字节协议头的处理和字段提取逻辑,相比软件方案有 3 倍以上的提升。
持续吞吐带宽则需要用计数器统计一段时间内处理的总字节数。在 10Gbps 满速行情灌入时,核心解析模块维持在 300MHz 且连续有效数据的占空比达到 95% 以上,基本就能跑满。如果有效占空比偏低,要检查是不是有反压信号频繁拉高,这说明流水线中间有一级处理不过来,需要拆细那一级的逻辑。
延迟验证这一步,最容易碰到的坑是:ILA 抓信号本身会插入探针逻辑,导致被观测路径延迟增加。我通常的做法是,在 RTL 里预先留出专门给探针用的寄存器,让 ILA 挂在独立的观测总线上,而不是直接截取业务路径的关键信号,这样才能测到相对真实的延迟数据。
5. 常见问题与排查技巧实录
5.1 时序违例:三大高效排查法
时序违例是 FPGA 开发者的噩梦,行情加速项目尤甚。遇到时序违例,先别急着改小代码,按顺序做这三件事:
第一,检查时钟约束是否准确。很多违例都是约束不对导致的误报。比如 PHY 恢复时钟必须在 XDC 里定义为 generated_clock,并正确指定 source 对象,否则工具会把恢复时钟当作无约束异步时钟,时序分析结果完全失真。
第二,确认关键路径在哪个模块。查看综合报告里的关键时序路径,如果集中在解析模块的某个 32 位比较器上,那说明这个比较器的组合逻辑太长了。解决方案是把比较拆成两级,先用高 16 位过滤,再比较低 16 位,能有效缩短组合延迟。
第三,调整综合策略。Vivado 里把综合策略设为 PerformanceOptimized,并开启物理优化,很多情况下能把 WNS(最差负余量)从负数拉回正值。我在一个项目里用这个方法把原本 -0.25ns 的违例修复到 +0.02ns,代价是编译时间长了约 40%。
5.2 行情断流、乱序、重复包怎么处理
FPGA 处理组播行情,最让人头疼的往往不是延迟,而是数据的完整性。交易所组播链路难免会有瞬时拥塞,出现丢包、乱序、重复包的情况。FPGA 逻辑必须把这些处理干净,否则策略端拿到脏数据,再快的加速都是白搭。
对于乱序包,主流做法是维护一个序号区间表,FPGA 根据每个报文的序列号判断其在序列中的位置,乱序到达的报文先缓存在重排缓冲区,等到序列完整后按顺序输出。这个缓冲区我用 BRAM 实现,容量根据实际乱序深度设置,深交所 Level-2 行情乱序一般不深,64 到 128 槽位足够了。
对于重复包,建立一个最近报文序号缓存,收到的报文如果序号已在缓存中直接丢弃。缓存用移位寄存器组即可,每次校验时并行比对,代价是消耗少量 LUT,换来的是逻辑简单可靠。
这里有一点要特别提醒:处理重排和去重逻辑必须放在核心业务字段提取之后,否则一旦误判顺序,会导致快照内容错配,这种错误极难定位,因为数据本身是符合协议格式的,只是内容错了。
5.3 工具链、环境与团队协作避坑
金融场景的 FPGA 项目通常工期紧、需求变动快,软件团队和硬件团队协作时容易出现摩擦。我强烈的体会是:硬件团队不能闭门造车,设计过程中一定要拉上行情业务专家一起过字段映射表,每一版固件升级时都要准备一份详细的“字段提取对照文档”。
另外,板卡在实验室里跑得再稳,到生产环境也可能翻车。机房现场的电磁环境、光模块质量、交换机配置都可能引入额外干扰。我建议在项目一开始就准备一套软硬件联合仿真环境,把交易所的行情回放数据灌进去,模拟极端行情场景,比如涨跌停瞬间的超级洪峰,在正式部署前就把边界情况测透。
最后,版本管理也是重灾区。FPGA 固件不存在“热补丁”,每次升级都要重新综合布局布线,耗时十几小时到几十小时不等。团队里一定要有一份严格的版本命名规则,比如用日期加序号加 git 短哈希,上线之后要能快速回滚到上一版本,否则行情一断,技术团队在巨大压力下做改动,只会越搞越乱。
6. 最后再说一点实战体会
做了几年 FPGA 金融加速项目,我最大的感悟是:FPGA 方案能不能成功,其实不取决于 RTL 写得多漂亮,而取决于团队对业务链路的理解有多深。行情报文解析这种活儿,软件工程师只要懂得 TCP/IP 和业务协议就能干活,但 FPGA 工程师还必须精通时序、存储、时钟架构,同时理解交易所撮合机制和数据流特征,这种人很难培养,也特别稀缺。
对于想入门 FPGA 的人来说,我的建议是不要一上来就追各种花哨算法和高速接口,先把一个简单的行情帧解析模块跑通,从连续正确的处理一个报文开始,再逐步解决跨时钟域、乱序重排这些进阶问题。这个过程很枯燥,但一旦完整走通,你对 FPGA 的能力边界和金融系统的低延迟需求会形成非常直观的认知,这会比任何培训课程都管用。
如果你所在的团队已经在评估或者使用 FPGA 做行情加速,我也建议你把需求和现状梳理清楚后再动手,不要因为“别人都在做”就盲目上马。FPGA 是手段,业务才是目的,想清楚这句话,很多弯路都可以绕开。