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层到应用层的完整路径。每个模块都是流水线设计,数据像流水一样流过。我设计时特别强调反压机制——如果下游处理不过来,上游要能暂停发送。否则数据会丢。
4.6 延迟优化技巧
最后分享几个我在项目中用到的优化技巧:
- 预解析:在收数据的同时,就把IP头、UDP头的关键字段提取出来。不要等收完再解析。
- Checksum流水:Checksum计算和帧接收并行。帧收完,Checksum也算完了。
- 寄存器打拍:关键路径上插入寄存器,提高时钟频率。行情加速卡一般跑200MHz以上。
- 避免FIFO:能不用的FIFO尽量不用。FIFO会增加延迟,而且深度不好控制。
- 多播过滤:在MAC层就做多播组过滤,减少无效数据进入后续模块。
重要提醒:硬件协议栈和软件协议栈最大的区别是——硬件没有“重传”机制。一旦丢包,就真的丢了。所以你的设计必须保证在满负载下不丢包。我建议在入口处做一个背压信号,通知PHY层暂停发送。
好了,这一章的内容就到这里。协议解析是行情加速卡的基石,这部分做扎实了,后面的行情解码才能跑得稳。记住一句话:硬件设计里,简单就是快,快就是一切。
公众号:蓝海资料掘金营,微信deep3321