☰
ZYNQ FPGA纯逻辑实现千兆以太网ARP协议:从RGMII到上板抓包
2026/10/16 22:16:45 网站建设 项目流程

简介:本资源是面向嵌入式FPGA开发工程师与ZYNQ平台学习者的实战项目,聚焦于在Xilinx ZYNQ-7020 SoC上通过纯FPGA逻辑实现以太网ARP协议硬件加速及配套Linux驱动开发,解决传统软件ARP处理效率低、实时性差的问题。压缩包共242个文件,涵盖29个Verilog源码(核心ARP硬件模块、MAC帧解析/构造逻辑)、18个XDC约束文件(时序与引脚定义)、14个TCL/Shell脚本(综合与烧录自动化)、13个RPT报告(时序分析与资源占用)以及完整bit流与DCP工程文件,总大小1.91MB。已有169人下载学习,资源提供可直接编译运行的完整软硬协同方案:包含FPGA侧ARP请求/响应状态机、ARP缓存表硬件管理模块、Linux内核态驱动代码(含中断注册与ioctl接口)、以及配套测试用例与验证脚本,特别适合深入理解ZYNQ PS-PL协同架构与网络协议硬件化设计方法。 先交代一下这个项目的来龙去脉。ZYNQ 7020把双核ARM Cortex-A9和Artix-7架构的FPGA封装在同一颗芯片里,PS端跑系统,PL端做逻辑加速,两者之间用AXI总线打通。之前我接到一个需求:让板卡在PS端还没完全启动、ARM核处于空闲状态时,就能响应上位机的网络请求。这就意味着以太网链路层的活儿得全部下沉到FPGA逻辑里干,首当其冲的就是ARP地址解析。

当时第一反应是“这也太折腾了,直接用PS端的GEM控制器不香吗”,但仔细想想这个场景其实很真实。ARM从Flash加载镜像到Linux起来,通常需要好几秒,而某些系统要求设备一上电就能被交换机发现、能被上位机Ping通,这时候PL端的硬件ARP处理器就派上用场了。如果再把看门狗、远程更新、设备管理这些场景一起考虑,PL里做ARP解析几乎是最合理的方案。

本文就把这个项目完整拆开讲一遍,从协议细节到Verilog实现,从RGMII时序到上板抓包验证,中间穿插我踩过的坑和调出来的教训。适合手里有ZYNQ开发板、想深入理解以太网底层原理的读者,哪怕你只想给现有的RGMII收发通路加一个ARP自动响应功能,也能直接抄作业。

1. 项目背景与整体架构设计

1.1 为什么要把ARP下沉到FPGA逻辑

很多人会问,ZYNQ的PS端自带GEM(Gigabit Ethernet MAC)控制器,跑个裸机或者Linux之后,ARP不都是协议栈自动处理的吗,为什么还要在PL里自己写一套?

答案是:软件协议栈的ARP响应依赖CPU介入。PS上电后需要初始化DDR、加载Bootloader、搬运应用程序,这一套流程下来快则几百毫秒、慢则好几秒。但PHY在上电后几百毫秒内就能完成链路协商,如果此时上位机发出ARP请求,ZYNQ的PS根本来不及响应。更极端的情况是CPU死机或者应用程序卡死,硬件看门狗触发复位之前,设备在网络层面已经“失联”了。

把ARP解析做成一个纯硬件模块挂在PL端,相当于给板卡装了一个永不掉线的网络管家。只要PHY链路是通的,FPGA逻辑就能独立完成ARP请求的接收、校验、查表、响应帧生成和发送,整个过程CPU完全不用参与,延迟也只有几十个时钟周期。

另外,这个模块可以复用在更多场景里。比如你后面的项目要做UDP硬加速,同样的帧解析、CRC校验逻辑直接升级扩展就行,ARP模块就相当于整个以太网硬协议栈的第一级地基。

1.2 整体架构与模块划分

整个设计围绕“不依赖PS,纯PL实现千兆以太网ARP响应”这个目标来搭。板卡上的PHY芯片走RGMII接口连接到PL的bank,FPGA内部逻辑接收上位机发来的以太网帧,解析出ARP请求后生成响应帧原路返回。

模块划分上,我把它拆成五个部分:

  • RGMII接口层:负责RGMII信号到GMII内部信号的双向转换,完成DDR采样和时钟对齐
  • 接收通道:把字节流按以太网帧格式缓存,提取关键字段用于过滤
  • ARP协议引擎:核对操作码、目标IP,判断是否需要本机响应
  • 响应帧生成器:交换源目的MAC和IP,回填本机MAC地址,计算CRC后按帧格式组包
  • 发送通道:按IFG帧间距要求把响应帧打出去

这五个部分里,ARP协议引擎是核心,但RGMII接口层才是最容易翻车的地方。很多第一次写RGMII的人都会在数据的DDR采样上吃大亏,后面我会专门展开讲。

1.3 ZYNQ 7020的资源开销评估

我最初预估这个设计会吃掉不少逻辑资源,实际综合下来,整个ARP模块只用了大概1500个LUT和1200个FF,BRAM用了一个9Kb的FIFO。对于XC7Z020这种拥有53000个LUT、106400个FF的芯片来说,资源占用不到5%,完全可以和用户业务逻辑共存。

这说明一个道理:纯组合逻辑加小型状态机实现的协议处理,在资源上并不奢侈。真正吃资源的是大缓存、复杂算法,而ARP这种“看几眼帧头、回一个包”的活儿,用FPGA做反而比CPU跑软件更轻快。

2. ARP协议细节与FPGA处理映射

2.1 以太网帧格式梳理

在写FPGA逻辑之前,先把以太网帧的结构捋清楚。一个不带VLAN标签的标准以太网帧,从PHY收上来的数据依次是:

  • 前导码(Preamble):7字节的0x55,用于时钟同步
  • SFD:1字节的0xD5,标志帧起始
  • 目的MAC地址:6字节
  • 源MAC地址:6字节
  • 类型/长度字段:2字节,0x0806表示ARP
  • 数据字段:46到1500字节
  • FCS校验:4字节的CRC32

FPGA接收数据的时候,前导码和SFD通常在RGMII接口层就被剥离了,进入内部逻辑的字节流直接从目的MAC开始。这里有个关键点:ARP请求帧的目的MAC地址一定是广播地址FF:FF:FF:FF:FF:FF,因为在不知道对方MAC的情况下只能广播查找。这个特性在后面做帧过滤时非常有用。

2.2 ARP报文格式与字段过滤策略

ARP报文嵌在以太网帧的数据字段里,共28个字节。我把它列成一张表,方便对照Verilog代码看:

字段长度内容本设计中的处理
硬件类型2字节以太网为0x0001校验,不匹配则丢弃
协议类型2字节IP为0x0800校验,不匹配则丢弃
硬件地址长度1字节0x06校验
协议地址长度1字节0x04校验
操作码2字节1为请求,2为响应只处理请求
发送端硬件地址6字节请求方的MAC存入寄存器
发送端协议地址4字节请求方的IP存入寄存器
目标硬件地址6字节请求时通常全0不关心
目标协议地址4字节本机IP必须匹配,否则丢弃

FPGA做解析的优点是可以把过滤条件写成并行逻辑:帧类型、硬件类型、操作码、目标IP这些条件同时比对,全部通过才进入响应流程。这比CPU逐字节处理快得多,也更容易用状态机描述。

有一点我一开始没注意:以太网帧有最小长度64字节(不含前导码和SFD),而ARP报文的28字节加上14字节以太网头只有42字节,因此ARP帧尾部必然带着18字节的填充数据。FPGA接收的时候不能因为这些填充字节是无关数据就乱掉状态机的节奏,必须等CRC校验结束后才把整个帧加入处理队列。

2.3 CRC32计算细节

CRC32是FPGA以太网设计里最容易出错的部分,没有之一。以太网采用的CRC32算法有几个容易混淆的细节:

首先,多项式是0x04C11DB7,初始值0xFFFFFFFF,最终结果需要做一次异或0xFFFFFFFF。其次,数据是按字节送入的,但每个字节内的bit顺序要反转,也就是LSB先发。最后,CRC结果的4个字节在线上传输时也是按小端顺序,即先发CRC[7:0],再发CRC[15:8],以此类推。

我写CRC的时候先调了一个纯组合逻辑的CRC32计算器,用python验证中间向量,再转成Verilog。这里有一个很实用的原则:千万不要直接手写一个看起来很“优化”的CRC逻辑然后上板测试,先做软硬协同验证,能省掉一整天的排查时间。

// 按字节输入、LSB-first的CRC32核心示意 function [31:0] crc32_byte; input [7:0] data; input [31:0] crc_in; integer i; reg [31:0] crc; begin crc = crc_in; for (i = 0; i < 8; i = i + 1) begin if ((data[i] ^ crc[31]) == 1'b1) crc = {crc[30:0], 1'b0} ^ 32'h04C11DB7; else crc = {crc[30:0], 1'b0}; end crc32_byte = crc; end endfunction // 帧结束后:crc_final = ~crc_reg;

3. 核心模块的Verilog实现与关键参数

3.1 RGMII接口与时钟管理

RGMII是Reduced Gigabit Media Independent Interface的缩写,它把GMII的8位数据线压缩成4位,用DDR双沿采样的方式在125MHz时钟下实现千兆速率。具体来说,发送时钟TXC由FPGA输出,TXD[3:0]在TXC的上升沿和下降沿各发半个字节;接收时钟RXC由PHY恢复,RXD[3:0]同样在上升沿和下降沿采样。

这里最需要小心的是RXC的相位问题。RGMII标准要求PHY输出的RXC与RXD数据之间有一个约2ns的偏置,FPGA内部需要做延迟校准。实际工程里最稳妥的做法是让RGMII接口层输出的字节流先进入一个异步FIFO,用本地生成的125MHz时钟读取,彻底隔离外部时钟抖动。

我的做法是:先用IBUFDS原语把RXC接入,然后通过一个IDELAYE2原语调整延迟,把采样点对准数据眼的中心。这个延迟值的确定不能拍脑袋,要在仿真的SDF反标和上板实测之间反复确认,不同批次的PHY芯片延迟特性会有差异。

3.2 接收解析状态机

接收通道的核心是一个状态机,我给它起了个名字叫rx_fsm。状态跳转的路径是这样的:

  • IDLE:等待RGMII接口层输出rx_valid信号。一旦帧开始,跳转到CHECK_DA
  • CHECK_DA:逐个字节接收目的MAC地址,判断是否为广播地址或本机MAC。不匹配直接跳到DROP
  • CHECK_TYPE:接收类型字段,如果是0x0806则进入ARP解析流程,否则跳到DROP
  • PARSE_ARP:逐字节解析ARP报文的操作码、发送端信息、目标IP
  • MATCH_IP:比较目标IP是否等于本机配置的IP,相等则进入SEND_RESP,否则DROP
  • DROP:丢弃整帧,等待rx_done信号后回到IDLE

这里有一个隐含条件:目的MAC地址的过滤必须支持广播地址,否则ARP请求根本进不了解析流程。很多初学者会漏掉这个细节,导致逻辑里明明写了目标IP匹配,但抓包一看FPGA就是没响应。

接收通道的数据通路用了双口RAM做缓存。帧头解析完成后,有效的数据被写入RAM,响应帧生成时从RAM中读取请求帧的发送端MAC和IP。这套“边接收边判断,接收完马上响应”的流水线方式,保证在帧结束后的几个时钟周期内就能开始发送响应。

3.3 响应帧生成与发送控制

响应帧生成逻辑要做的事情很纯粹:把请求帧里的发送端MAC放到响应帧的目的MAC,把本机MAC放到源MAC,把操作码从1改成2,再把发送端和目标的IP对调,最后补齐以太网帧头、填充字节和CRC。

发送通道的状态机可以用一个更简单的三态结构:

  • IDLE:等待响应帧数据准备好
  • SEND_PREAMBLE:输出7字节0x55加1字节0xD5
  • SEND_FRAME:按字节从FIFO中读出响应帧内容,同时实时计算CRC
  • SEND_CRC:发出4字节CRC,然后拉高tx_done信号

需要注意的是帧间距IFG。以太网标准要求两个帧之间至少有96bit的时间间隔,在千兆速率下就是960ns。我实现的时候用一个计数器控制,在SEND_CRC完成后强制等待IFG计数结束才回到IDLE,避免连续响应时背靠背发包导致部分交换机丢弃。

3.4 XDC约束要点

给Vivado写约束的时候,除了常规的引脚位置约束和I/O电平标准,还需要特别注意时钟约束。RGMII接口里有两个时钟:PHY输入的TXC是FPGA输出的时钟,对外是OCLK;进来的RXC则由PHY恢复,必须作为异步时钟处理。

set_property PACKAGE_PIN AE16 [get_ports {rgmii_txc}] set_property IOSTANDARD LVCMOS18 [get_ports {rgmii_txc}] create_generated_clock -name rgmii_txc_gen -source [get_pins clk_gen_inst/clk_out] -divide_by 1 [get_ports rgmii_txc] set_property PACKAGE_PIN AF16 [get_ports {rgmii_rxc}] set_property IOSTANDARD LVCMOS18 [get_ports {rgmii_rxc}] set_clock_groups -asynchronous -group [get_clocks rgmii_txc_gen] -group [get_clocks rgmii_rxc]

另外,RGMII数据线和控制线都要设置最大延迟约束,否则时序收敛会很痛苦。我习惯把input delay和output delay按2ns来约束,这是RGMII标准里的典型值,上板实测也能满足要求。

4. 仿真与上板验证流程

4.1 Testbench设计与仿真结果

仿真阶段的核心任务是验证ARP请求进来后,响应帧的字段是否正确、时序是否满足以太网帧格式要求。我构造了一个testbench,模拟MAC层往RGMII接口发送一帧ARP请求,关键代码片段如下:

task send_arp_request; input [47:0] sender_mac; input [31:0] sender_ip; input [31:0] target_ip; begin send_rgmii_byte(8'hFF); // 目的MAC: 广播 send_rgmii_byte(8'hFF); send_rgmii_byte(8'hFF); send_rgmii_byte(8'hFF); send_rgmii_byte(8'hFF); send_rgmii_byte(8'hFF); send_rgmii_byte(sender_mac[47:40]); // 源MAC // ... 后续字段按ARP格式发送 end endtask

仿真跑通后,我保存了响应帧的VCD波形,重点检查三个地方:CRC计算结果是否等于预计算的正确值、响应帧的目的MAC是否等于请求方的源MAC、帧间IFG是否满足要求。全部通过后我再考虑上板。

这里多提醒一句:仿真的目的是验证逻辑,不是验证物理层。RGMII的延迟、引脚到PHY的走线长度差异,这些在仿真里看不出来,必须靠上板实测。

4.2 上板抓包验证(Wireshark)

把bitstream下载到ZYNQ之后,我用一根网线连接板子和电脑,电脑IP设置成192.168.1.10,ZYNQ本机IP配置成192.168.1.20。然后在Wireshark里开启抓包,在命令行执行ping 192.168.1.20。

正常的结果应该是:Wireshark先看到电脑发出ARP请求“who has 192.168.1.20”,紧接着看到ZYNQ返回ARP响应“192.168.1.20 is at xx:xx:xx:xx:xx:xx”,随后电脑发出ICMP Echo Request,ZYNQ返回ICMP Echo Reply。

这里有一个很有意思的细节:纯FPGA实现的ARP只是打通了链路层,如果上面没有跑IP协议栈,Ping是永远不会通的,ICMP回声请求同样需要硬件逻辑去处理。我这次测试的板卡上其实有一段简单的UDP/ICMP处理逻辑,如果有读者只想测ARP模块,用Wireshark过滤“arp”就能观察到完整的请求响应交互。

4.3 Ping测试链路排查

如果Ping不通,不要急着改代码,按照下面这个顺序排查:

  • 看Wireshark有没有收到ZYNQ的ARP响应。没有的话,问题大概率在FPGA接收或发送链路上
  • 看PHY的link指示灯是否正常。链路没协商好,一切都白搭
  • 用逻辑分析仪抓RGMII接口信号,确认FPGA确实发出了数据
  • 确认FPGA内部的MAC地址和IP地址配置是否和抓包端一致

踩过几次坑之后,我的经验是:先解决物理层,再解决数据链路层,最后才查协议字段。不要在ARP报文格式上反复纠结,却忽略了RGMII引脚约束错了这种低级问题。

5. 常见问题与排查技巧实录

5.1 典型问题对照表

我把调试过程中遇到的坑整理成一张速查表,给同行们省点时间:

现象根因解决方法
抓不到任何ARP请求RXC时钟相位未对准,数据采样错误调整IDELAYE2延迟值,改用异步FIFO隔离时钟
收到请求但无响应目的MAC过滤条件不包含广播地址检查CHECK_DA状态,加入广播地址判断
响应帧CRC报错CRC计算初始值或bit顺序错误用Python软参考核对中间向量
响应帧目的MAC正确但PC不认帧间隔IFG不足发送CRC后强制等待IFG计数
上板后偶尔能通有时不通RXC时钟抖动引起亚稳态增加两级同步器,接收数据打拍缓存
引脚约束报错RGMII数据线约束遗漏检查XDC中所有TXD/RXD/TX_CTL/RX_CTL是否都有PACKAGE_PIN

5.2 几个关键教训

第一个教训:RGMII的TX_CTL信号千万不要当作普通信号处理。在千兆模式下,TX_CTL在上升沿是TX_EN,在下降沿是TX_ER,必须按双沿逻辑设计。如果只按单沿发使能信号,传输会不稳定,表现为偶发性丢包。

第二个教训:ARP模块的MAC地址和IP地址最好做成可配置寄存器,通过AXI-Lite总线从PS端写入,而不是在Verilog里写死。等你联调的时候就会发现,每次改IP都要重新综合一遍工程,实在太痛苦了。用寄存器配置的话,PS端跑一个简单的裸机程序就能动态修改,调试效率提升一个数量级。

第三个教训:不要信任PHY的默认配置。有些PHY上电后默认工作在百兆模式或者半双工模式,如果不对它做寄存器初始化,千兆ARP链路根本建立不起来。FPGA这边至少要能通过MDIO接口写PHY的控制寄存器,把速率、双工模式强制设置成期望值。

5.3 从ARP到完整以太网协议栈

做完这个ARP模块,最直接的扩展方向就是往上叠加ICMP和UDP。ICMP的Echo Reply逻辑和ARP响应几乎同构:接收请求、解析负载、交换地址、生成响应。UDP则需要处理端口号和长度字段,复杂度稍高但原理一样。

如果想把FPGA里的硬协议栈做深,还可以加ARP缓存表管理。FPGA维护一个小的CAM表,把收到的ARP请求“谁的IP对应谁的MAC”记录下来,后续发UDP报文时直接查表,避免每次都发广播请求。这个功能在纯软件协议栈里稀松平常,但在硬件实现里涉及存储管理和老化策略,反而更容易体现功底。

我个人在实际操作中的体会是:用FPGA实现ARP协议,最大的收获不是那几百行Verilog,而是强迫你把以太网协议的结构、时序、字段语义彻底吃透。CPU上写协议栈,很多东西被操作系统封装的太好,你根本接触不到底层细节;到了FPGA里,每一根信号线、每一个字节的bit顺序都得自己管,这才是做底层技术的真实状态。如果你也想在ZYNQ上做以太网硬加速,建议从这个ARP模块开始,投资回报比非常高。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询