4. 行情数据协议解析:UDP/IP协议栈硬件实现、MAC帧解析、ARP与ICMP处理、Checksum计算

各位同学,咱们今天聊点硬核的。行情数据从网线进来,到你的FPGA逻辑能读懂,中间要过好几道关。说白了,就是要把网络协议栈用硬件搭出来。我刚开始做这个的时候,也觉得软件里几行代码的事,搬到硬件里怎么就那么麻烦?后来踩的坑多了,才明白硬件协议栈的精髓——流水线、低延迟、确定性

核心思路:行情加速卡不需要完整的TCP/IP协议栈。我们只关心UDP广播数据,ARP和ICMP也只是为了维持网络连通性。砍掉一切不必要的功能,延迟才能压到纳秒级。

4.1 MAC帧解析——第一道关卡

数据从PHY芯片过来,首先是MAC层。以太网帧长什么样?你想想看,前导码、帧起始定界符、目的MAC、源MAC、类型/长度,然后是载荷,最后是FCS校验。

在硬件里,我们其实只关心几个字段:

  • 目的MAC:是不是发给我的?广播帧?多播帧?
  • 类型/长度字段:0x0800是IPv4,0x0806是ARP,0x86DD是IPv6(行情里很少见)
  • FCS校验:这个必须做,但可以流水线处理

我个人习惯,在MAC层就做一个白名单过滤。只放行目的MAC为本机MAC、广播MAC(FF:FF:FF:FF:FF:FF)以及行情多播MAC的帧。其他的一律丢弃。这样能减少后续模块的处理压力。

避坑指南:我曾经遇到过一个问题——FCS校验在帧尾,但解析需要从头开始。如果等FCS算完再处理,延迟就上去了。我的做法是:边收边解析,FCS校验和帧解析并行流水。等整帧收完,FCS结果也出来了,如果校验失败,直接把解析结果作废就行。

4.2 ARP与ICMP处理——维持网络连通性

行情加速卡虽然是被动接收数据,但网络里其他设备需要知道你的存在。ARP和ICMP就是干这个的。

4.2.1 ARP协议硬件实现

ARP的逻辑其实很简单:收到ARP请求,如果目标IP是自己,就回复ARP应答。但硬件实现时有个细节——ARP表要不要做?

我的建议是:不做完整的ARP表。行情加速卡不需要主动发起连接,只需要应答。所以只需要一个寄存器存自己的MAC和IP,收到请求直接回复。这样逻辑资源消耗极小。

来看一个简化的状态机:

// ARP处理状态机(简化版)
IDLE: 
  收到MAC帧,类型=0x0806 → 进入ARP_PARSE
ARP_PARSE:
  解析ARP头部,检查操作码
  如果是请求(1),且目标IP匹配 → 进入ARP_REPLY
  否则 → 回到IDLE,丢弃
ARP_REPLY:
  构造ARP应答帧,源MAC/目标MAC互换
  发送出去 → 回到IDLE

注意:ARP应答帧的构造要快。我见过有些设计用CPU软核处理ARP,延迟能到毫秒级。在硬件里,从收到请求到发出应答,应该控制在几个时钟周期内。否则交换机会认为你的设备不可达,把端口关了。

4.2.2 ICMP处理——Ping通就行

ICMP在行情加速卡里只有一个用途:让别人能Ping通你。网络管理员需要确认设备在线。

我们只需要处理ICMP Echo Request(类型8),回复Echo Reply(类型0)。其他ICMP类型一律忽略。实现方式和ARP类似,也是状态机+构造回复帧。

嗯,这里要注意:ICMP的Checksum计算和IP头Checksum不一样,它是覆盖整个ICMP报文的。后面我们会统一讲Checksum的硬件实现。

4.3 UDP/IP协议栈硬件实现——核心中的核心

行情数据99%走的是UDP广播或多播。所以UDP/IP协议栈是咱们的重头戏。

4.3.1 IP层处理

IP头解析需要关注:

  • 版本号:必须是4,IPv6暂时不考虑
  • 头部长度:一般是20字节,如果有选项字段要特殊处理
  • 总长度:用于确定UDP载荷的起始位置
  • 协议字段:17是UDP,1是ICMP
  • 源/目的IP:目的IP必须匹配本机或组播组
  • 头部Checksum:必须验证

这里有个常见的坑——IP分片。行情数据帧一般不会超过MTU(1500字节),但万一遇到呢?我的做法是:直接丢弃分片包。因为行情数据要求低延迟,重组分片带来的延迟和复杂度不值得。如果上游发了分片包,那是配置问题,不是我们的问题。

4.3.2 UDP层处理

UDP头更简单:源端口、目的端口、长度、Checksum。我们只需要:

  • 检查目的端口是否是我们监听的行情端口
  • UDP Checksum可选,但建议开启验证
  • 提取UDP载荷,送给上层行情解码模块

关键优化:UDP载荷的起始位置可以在解析IP头时就计算出来,用寄存器流水传递。这样UDP层不需要重新计算偏移,省一个时钟周期。

4.4 Checksum计算——硬件加速的拿手好戏

Checksum计算是协议栈里最适合硬件加速的部分。软件里要循环累加,硬件里一个时钟周期就能算完。

4.4.1 IP头部Checksum

算法很简单:将IP头按16位一组累加,取反码。硬件实现用加法树+补码运算

// Verilog示例:IP头Checksum计算
always @(posedge clk) begin
  if (start) begin
    sum <= 0;
    for (i = 0; i < 10; i = i + 1) begin  // IP头20字节=10个16位字
      sum <= sum + ip_header[i];
    end
  end
  checksum <= ~sum;  // 取反码
end

注意:IP头里的Checksum字段本身在计算时要视为0。这个在硬件里要做一个掩码处理

4.4.2 UDP Checksum

UDP Checksum比较特殊,它要计算一个伪头部(源IP、目的IP、协议号、UDP长度)加上UDP头部和载荷。伪头部不是真的在网络里传输,而是计算时虚拟出来的。

硬件实现时,我建议把伪头部和UDP数据拼接成一个连续的比特流,然后用和IP Checksum同样的加法树计算。这样逻辑可以复用。

个人经验:UDP Checksum可以旁路。很多行情数据源会把UDP Checksum设为0(表示不校验)。如果你的网络环境可靠,可以跳过UDP Checksum计算,省下不少逻辑资源。但IP头Checksum必须做,这个没得商量。

4.5 整体架构与流水线设计

好了,我们把所有模块串起来。下面这张图展示了行情数据从网线到应用层的完整流水线:

行情数据硬件协议栈流水线架构 MAC接收 FCS校验 MAC过滤 白名单匹配 协议分类 0x0800/0x0806 ARP处理 ICMP处理 IP/UDP解析 Checksum验证 IP+UDP校验 行情数据解码 应用层处理 数据流方向 → 每个模块延迟控制在1-2个时钟周期 整条流水线延迟 < 100ns

这张图展示了数据从MAC层到应用层的完整路径。每个模块都是流水线设计,数据像流水一样流过。我设计时特别强调反压机制——如果下游处理不过来,上游要能暂停发送。否则数据会丢。

4.6 延迟优化技巧

最后分享几个我在项目中用到的优化技巧:

  1. 预解析:在收数据的同时,就把IP头、UDP头的关键字段提取出来。不要等收完再解析。
  2. Checksum流水:Checksum计算和帧接收并行。帧收完,Checksum也算完了。
  3. 寄存器打拍:关键路径上插入寄存器,提高时钟频率。行情加速卡一般跑200MHz以上。
  4. 避免FIFO:能不用的FIFO尽量不用。FIFO会增加延迟,而且深度不好控制。
  5. 多播过滤:在MAC层就做多播组过滤,减少无效数据进入后续模块。

重要提醒:硬件协议栈和软件协议栈最大的区别是——硬件没有“重传”机制。一旦丢包,就真的丢了。所以你的设计必须保证在满负载下不丢包。我建议在入口处做一个背压信号,通知PHY层暂停发送。

好了,这一章的内容就到这里。协议解析是行情加速卡的基石,这部分做扎实了,后面的行情解码才能跑得稳。记住一句话:硬件设计里,简单就是快,快就是一切


公众号:蓝海资料掘金营,微信deep3321