第三章 交易系统网络协议栈:UDP/IP硬件卸载、MAC层与PHY层设计、ARP/ICMP协议硬件实现、PTP精确时间同步

做交易系统的人都知道,网络延迟就是真金白银。我见过太多软件协议栈的坑——中断风暴、上下文切换、内存拷贝,每一层都在吃掉你的微秒。说白了,硬件卸载就是把那些本该CPU干的活,扔给FPGA去干。

这一章,我们聊聊交易系统里最核心的网络部分。嗯,内容有点多,但都是实战干货。

3.1 为什么需要硬件协议栈?

先问个问题:为什么软件协议栈不够快?

你想想看,一个标准的Linux网络收包流程:网卡收到数据→DMA到内存→中断通知CPU→驱动处理→协议栈解析→socket交给应用。这一套下来,少说几十微秒。对于高频交易来说,这简直是天文数字。

我在一个项目中遇到过,客户要求端到端延迟小于1微秒。软件方案?想都别想。只能上硬件卸载。

硬件卸载的核心思想:把协议处理逻辑全部用数字电路实现,数据从网线进来,直接解析、过滤、转发,CPU只负责最上层的业务逻辑。

说白了,FPGA就是一块可以编程的硬件。你给它配好逻辑,它就能在几个时钟周期内完成一个数据包的处理。这速度,软件拍马也赶不上。

3.2 UDP/IP硬件卸载

UDP是交易系统里最常用的传输层协议。为什么?因为它简单、无状态、延迟低。TCP那套三次握手、拥塞控制,在交易场景下反而是累赘。

硬件卸载UDP/IP,主要做三件事:

  • IP校验和计算——发送时自动计算,接收时自动校验
  • UDP校验和计算——同上,硬件搞定
  • IP分片重组——虽然UDP很少分片,但硬件也得支持

我个人的习惯是,把UDP卸载引擎设计成一个流水线结构。数据进来,先过MAC层,再过IP层,最后到UDP层。每一层只做自己的事,互不干扰。

避坑指南:我曾经在设计UDP卸载时,忽略了IP选项字段的处理。结果某些特殊配置的交换机发来的包,校验和总是算不对。后来花了整整两天才定位到问题。记住:IP头长度是可变的,千万别写死。

3.2.1 UDP卸载引擎架构

一个典型的UDP卸载引擎包含以下模块:

模块 功能 延迟(时钟周期)
MAC接收 解析以太网帧头,提取MAC地址 2-3
IP解析 校验IP头,提取源/目的IP 3-5
UDP解析 校验UDP头,提取端口号 2-3
校验和计算 并行计算IP和UDP校验和 1-2
数据转发 根据规则转发到对应FIFO 1

你看,整个流水线加起来也就10个时钟周期左右。如果跑200MHz,那就是50纳秒。软件能做到吗?

3.3 MAC层与PHY层设计

MAC层和PHY层,是FPGA和物理世界的接口。很多人觉得这层很简单,不就是接个网口吗?其实坑很多。

PHY层负责把数字信号变成模拟信号,通过网线发出去。FPGA这边,通常通过GMII/RGMII/SGMII接口和PHY芯片通信。

MAC层呢?负责组帧、解帧、CRC校验、流量控制。在FPGA里,MAC层可以用IP核,也可以自己写。我个人建议:自己写MAC层

注意:商用IP核虽然稳定,但延迟往往偏高。自己做MAC层,可以针对交易场景做极致优化。比如,你可以把CRC计算和帧解析并行起来,省掉一个时钟周期。

3.3.1 RGMII接口时序

RGMII是1000M以太网最常用的接口。它用DDR方式在时钟的上升沿和下降沿各传4位数据,合起来就是8位。

// RGMII接收时序示例
always @(posedge rgmii_rxc or negedge rst_n) begin
    if (!rst_n) begin
        rgmii_rd_valid <= 1'b0;
        rgmii_rd_data  <= 8'd0;
    end else begin
        // 上升沿采样低4位
        rgmii_rd_data[3:0] <= rgmii_rxd[3:0];
        // 下降沿采样高4位
        rgmii_rd_data[7:4] <= rgmii_rxd[3:0];
        rgmii_rd_valid <= rgmii_rx_ctl;
    end
end

嗯,这里要注意:RGMII的时钟是125MHz,DDR模式下数据速率是250Mbps。但实际线速率是1000Mbps,因为数据位宽是4位。

3.4 ARP/ICMP协议硬件实现

ARP和ICMP,这两个协议在交易系统里看似不重要,但少了它们,网络根本跑不起来。

ARP负责把IP地址解析成MAC地址。你的FPGA要能响应ARP请求,也要能主动发送ARP请求。ICMP呢?主要是Ping。交易系统里,Ping用来做链路健康检查。

我在一个项目中遇到过,FPGA不响应ARP,结果交换机一直发ARP请求,把带宽都占满了。后来加了一个硬件ARP响应模块,问题解决。

3.4.1 硬件ARP表设计

硬件ARP表,说白了就是一个CAM(内容可寻址存储器)。输入IP地址,输出MAC地址。我建议用FPGA内部的BRAM实现,查找延迟只有1个时钟周期。

// ARP表查找逻辑
always @(posedge clk) begin
    arp_hit <= 1'b0;
    for (i = 0; i < ARP_TABLE_DEPTH; i = i + 1) begin
        if (arp_table[i].ip_addr == target_ip && arp_table[i].valid) begin
            arp_hit     <= 1'b1;
            arp_mac_out <= arp_table[i].mac_addr;
        end
    end
end

避坑指南:我曾经把ARP表深度设成256,结果发现BRAM不够用。后来改成64,配合老化机制,完全够用。记住:交易网络里,你只需要和少数几个对端通信,ARP表不用太大。

3.4.2 ICMP Ping响应

ICMP Ping响应,实现起来很简单:收到Echo Request,把类型改成Echo Reply,交换源和目的地址,重新计算校验和,发回去。

但要注意:ICMP校验和是覆盖整个ICMP报文的,包括数据部分。如果你只是简单地把类型改了,校验和就错了。

3.5 PTP精确时间同步

PTP(Precision Time Protocol),这是交易系统的命脉。没有精确的时间同步,你的订单时间戳就是废纸。

PTP的精度可以做到亚微秒级别。怎么做到的?靠硬件时间戳。在MAC层收到PTP报文的那一刻,硬件打上当前时间戳。这个时间戳的精度,决定了整个系统的同步精度。

3.5.1 PTP时钟架构

一个完整的PTP硬件实现包含:

  • 时钟伺服——锁相环,调整本地时钟频率
  • 时间戳单元——在MAC层捕获报文到达/离开时间
  • 报文解析器——识别PTP报文类型(Sync、Follow_Up、Delay_Req等)
  • 状态机——管理PTP协议状态

我个人习惯用两步时钟模式。为什么?因为一步时钟需要在报文发送时修改报文内容,对硬件要求更高。两步时钟只需要在Follow_Up报文里带上精确的发送时间,实现起来更简单。

关键点:PTP的精度,90%取决于时间戳的捕获位置。一定要在MAC层和PHY层之间打时间戳,越靠近物理层越好。我曾经见过有人在应用层打时间戳,那精度,惨不忍睹。

3.5.2 时钟同步流程

PTP的同步流程,说白了就是主时钟和从时钟之间交换时间信息,然后从时钟调整自己的时钟。

  1. 主时钟发送Sync报文,记录发送时间t1
  2. 从时钟收到Sync报文,记录接收时间t2
  3. 主时钟发送Follow_Up报文,带上t1
  4. 从时钟发送Delay_Req报文,记录发送时间t3
  5. 主时钟收到Delay_Req,记录接收时间t4
  6. 主时钟发送Delay_Resp报文,带上t4
  7. 从时钟根据t1、t2、t3、t4计算偏移和延迟

嗯,这里要注意:计算偏移时,要假设网络延迟是对称的。如果不对称,精度就会下降。在交易网络里,我们通常用专用网络,保证对称性。

3.6 本章知识体系

说了这么多,我画了一张图,把这一章的核心内容串起来。你一看就明白了。

交易系统网络协议栈硬件架构 交易应用层(订单处理、行情解析) UDP/IP硬件卸载引擎 校验和计算 | 分片重组 | 端口过滤 ARP/ICMP协议硬件实现 ARP表查找 | Ping响应 | 协议状态机 MAC层设计 帧解析 | CRC校验 | 流量控制 PHY层接口(RGMII/SGMII) 时钟恢复 | 串并转换 | 信号编码 PTP精确 时间同步 时钟伺服 时间戳单元 报文解析 状态机 亚微秒精度 硬件时间戳 图例 应用层 传输层 网络层 MAC层 PHY层

这张图把整个协议栈的层次关系画清楚了。从下往上:PHY层→MAC层→网络层(ARP/ICMP)→传输层(UDP卸载)→应用层。右侧的PTP时间同步,贯穿整个协议栈,为每一层提供精确的时间基准。

好了,这一章的内容就到这里。网络协议栈是交易系统的血管,每一层都值得你花时间去打磨。下一章,我们聊聊更深入的东西。