第四章:ARP协议解析——从报文到RTL实现
各位同学,今天我们来聊聊ARP协议解析。说实话,ARP这个协议看起来简单,但要在硬件里把它实现得既高效又可靠,里面门道还真不少。我在做第一个网络芯片项目时,就因为在ARP缓存表的老化机制上栽了跟头,导致整板网络不通,排查了整整三天……嗯,从那以后我对ARP就格外上心了。
4.1 ARP报文格式——你得看懂这28个字节
ARP报文,说白了就是28个字节的固定结构。你想想看,以太网帧头14字节加上ARP报文28字节,总共42字节,再补6个字节的填充,凑够64字节的最小帧长。这个细节我在项目里吃过亏——当时没注意填充,结果交换机直接把我的ARP请求给丢了。
来看ARP报文的详细格式:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 硬件类型 | 2 | 以太网为0x0001 |
| 协议类型 | 2 | IPv4为0x0800 |
| 硬件地址长度 | 1 | MAC地址为6 |
| 协议地址长度 | 1 | IPv4地址为4 |
| 操作码 | 2 | 1=请求,2=响应 |
| 发送方MAC | 6 | 源MAC地址 |
| 发送方IP | 4 | 源IP地址 |
| 目标MAC | 6 | 请求时为全0 |
| 目标IP | 4 | 要查询的IP地址 |
我个人习惯在RTL设计里,把这28个字节按64位宽度对齐,这样一拍就能处理4个字段。为什么?因为AXI总线通常是64位或128位,对齐后处理逻辑会干净很多。
4.2 请求与响应流程——状态机里的两个关键路径
ARP的请求响应流程,说白了就是两个动作:
- 收到ARP请求:检查目标IP是否匹配本机。匹配则回复ARP响应,不匹配则丢弃。
- 收到ARP响应:提取发送方的MAC-IP映射,更新缓存表。
这里有个坑——我曾经以为ARP请求只发一次就够了。实际上,标准规定如果没收到响应,需要重试3次,每次间隔1秒。这个重试逻辑在RTL里实现时,你得小心状态机的设计,别让重试把缓存表给污染了。
下面是我常用的ARP解析状态机:
// ARP解析状态机(简化版)
typedef enum {
IDLE,
PARSE_HEADER, // 解析硬件类型和协议类型
CHECK_OPCODE, // 检查操作码
EXTRACT_SENDER, // 提取发送方信息
CHECK_TARGET, // 检查目标IP
GEN_REPLY, // 生成响应
UPDATE_CACHE, // 更新缓存
DROP_PACKET // 丢弃报文
} arp_state_t;
你想想看,这个状态机里最关键的其实是CHECK_TARGET状态。如果目标IP匹配,就进入GEN_REPLY;如果不匹配,但操作码是响应,就进入UPDATE_CACHE。这个分支判断,我建议用组合逻辑做,别拖到时钟沿,否则会浪费一个周期。
重要提示:ARP响应报文里,发送方MAC和IP就是我们要缓存的信息。但注意——ARP请求里也有发送方信息,同样需要缓存。这叫"免费ARP"学习机制,很多新手会漏掉。
4.3 RTL缓存表设计——CAM还是RAM?这是个问题
ARP缓存表,说白了就是个MAC-IP映射表。但硬件实现时,有两种主流方案:
- CAM(内容可寻址存储器):用IP地址查MAC,一拍出结果。速度快,但面积大、功耗高。
- RAM + 哈希:对IP做哈希,存到RAM里。面积小,但可能有哈希冲突。
我个人习惯在小型设计里用CAM,因为逻辑简单,不容易出bug。但如果你要支持256条以上的缓存条目,那还是用哈希RAM吧,否则芯片面积会爆炸。
来看一个简单的CAM实现思路:
// 4条目CAM示例(Verilog风格)
// 每个条目包含:valid, ip_addr, mac_addr, age
reg [31:0] cam_ip [0:3];
reg [47:0] cam_mac [0:3];
reg cam_valid[0:3];
reg [3:0] cam_age [0:3]; // 老化计数器
// 查找逻辑:并行比较所有条目
always @(*) begin
hit = 1'b0;
hit_idx = 2'b0;
for (i = 0; i < 4; i = i + 1) begin
if (cam_valid[i] && cam_ip[i] == target_ip) begin
hit = 1'b1;
hit_idx = i;
end
end
end
嗯,这里要注意——CAM的并行比较会消耗大量组合逻辑。如果你用FPGA实现,LUT资源会吃得很厉害。我建议条目数控制在16以内,超过16就老老实实用哈希。
4.4 老化机制——别让缓存变成"死缓存"
ARP缓存是有生命周期的。标准建议超时时间为20分钟,但实际项目中,我一般设成5分钟。为什么?因为网络拓扑变化时,20分钟的老化时间太长了,设备断网后要等20分钟才能恢复通信,这谁能忍?
老化机制的核心是:每个缓存条目配一个计数器,每隔一定时间(比如1秒)减1。减到0时,标记为无效。如果收到新的ARP响应,重置计数器。
设计技巧:我习惯把老化计数器做成4位,最大计数值15。如果1秒减1,那么最大老化时间是15秒。这个时间对于局域网来说足够了。如果你需要更长的老化时间,可以扩展计数器位数,或者降低时钟频率。
但有个特殊情况——静态ARP条目。比如网关的MAC地址,你肯定不希望它被老化掉。所以缓存表里要加一个static标志位,静态条目不参与老化。
4.5 状态机整合——把上面所有东西串起来
好了,我们把ARP解析、缓存查找、老化机制整合到一个状态机里。这是我实际项目中用过的架构:
// 主状态机(带缓存管理)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
end else begin
case (state)
IDLE: begin
if (arp_pkt_valid) state <= PARSE_HDR;
else if (age_tick) state <= AGE_CHECK; // 每秒触发一次老化
end
PARSE_HDR: begin
// 解析硬件类型和协议类型
if (hw_type == 16'h0001 && proto_type == 16'h0800)
state <= CHECK_OP;
else
state <= DROP;
end
CHECK_OP: begin
if (opcode == 16'h0001) // ARP请求
state <= CHECK_TARGET;
else if (opcode == 16'h0002) // ARP响应
state <= UPDATE_CACHE;
else
state <= DROP;
end
// ... 后续状态省略
endcase
end
end
避坑指南:我曾经在AGE_CHECK状态里直接修改了CAM条目,结果导致正在进行的ARP查找得到了错误结果。正确的做法是:老化操作和查找操作要互斥,或者用双端口RAM。我的建议是——老化用单独的时钟域,或者用"写优先"的RAM。
4.6 知识体系总览
下面这张图是我手绘的ARP协议解析引擎架构,你可以看到各个模块之间的关系:
从这张图你可以看到,ARP解析引擎的核心就是三个模块的协同:报文解析、状态机控制、缓存管理。老化机制作为后台任务,定期扫描缓存表。我个人建议在实现时,把老化模块做成独立的有限状态机,这样主状态机不会被老化操作阻塞。
好了,ARP协议解析的内容就讲到这里。记住一句话:ARP看似简单,但RTL实现时处处是坑。多想想边界情况,比如广播MAC、全0IP、重复IP等,你的设计才能经得起考验。
公众号:蓝海资料掘金营,微信deep3321