XC7A200T三速以太网UDP环回设计与Verilog实现

📅 发布时间:2026/9/14 14:27:28
XC7A200T三速以太网UDP环回设计与Verilog实现
简介基于XC7A200T FPGA的三速以太网UDP通信环回测试完整工程采用Verilog HDL实现面向FPGA开发工程师、网络协议学习者及数字系统设计人员可用于研究10M/100M/1G以太网数据通路以及UDP协议栈的硬件落地方式。工程覆盖PHY接口、MAC帧处理、UDP封装/解封装、校验和计算与顶层控制逻辑并给出Testbench测试平台便于在Vivado中直接进行仿真、综合与实现。压缩包共989个文件约90MB以Verilog/VHDL源码、Xilinx约束XDC、IP核配置XCI、仿真波形WCFG/WDB、综合实现报告RPT为主辅以Tcl/Bat脚本、BIT配置文件和内存初始化文件目录结构清晰。已有241人学习下载。借助该工程可获得完整源码、工程配置和测试环境能直观看到UDP环回从PHY收发、MAC校验到IP/UDP层处理的完整流程工程内多个模块和脚本可直接复用省去从零搭建时间对深入理解FPGA以太网协议栈实现具有很高参考价值。1. XC7A200T 三速 UDP 环回协议栈边界比 RTL 更值得先理清头一次把三速以太网环回工程放到 XC7A200T 上大多数人会先卡在“综合过了上板 PC 端却连 UDP 包都发不出去”。我拿到这套基于 Verilog HDL 的工程时也遇到了同样的问题Vivado 仿真全绿下载到板子后网络调试助手一直提示目标不可达。打开 Wireshark 才发现PC 在发 UDP 之前先广播了 ARP 请求而 FPGA 侧没有 ARP 应答逻辑链路层根本没建立起来。这个项目真正值钱的不是那几十行状态机而是它在 XC7A200T 上把 10M/100M/1000M 三档速率下的 RGMII 时钟切换、Tri-Mode Ethernet MAC IP 配置、UDP 解析与环回重封装串成了一条可完整排错的链路。适合正在用 Artix-7 做以太网接口、后面还想碰更高层协议栈的工程师读。2. 三速以太网层次切分PHY 职责与 RGMII 三档速率适配2.1 MAC 与 PHY 的分工边界三速以太网10/100/1000Mbps一个容易让人误解的地方是把 PHY 芯片和 FPGA 里 MAC 逻辑的职责混在一起。PHY 负责的是物理编码、自协商和时钟恢复真正生成 CRC32、插入和删除前导码、维护帧间隙的是 MAC 侧逻辑。XC7A200T 上这部分用 Xilinx 的 Tri-Mode Ethernet MAC IP 核实现Verilog HDL 只需要写 IP 核上层的协议逻辑和下层的时钟适配。MAC 侧FPGA IP 核PHY 侧外部芯片帧前导码生成与剥离8B/10B 线路编码1000MCRC32 校验与生成自协商与速度协商帧间隙与半双工退避时钟恢复与串并转换以太网帧收发的 AXI4-Stream 接口差分信号收发与均衡向用户逻辑上报错误帧 tuserMDIO 寄存器管理先确认外部 PHY 的 RGMII 模式有没有通过 MDIO 或引脚上拉配置好否则后边 UDP 状态机写得再干净也收不到数据。调试时优先读 PHY 状态寄存器看 link up 和速度档位而不是先怀疑自己的解析逻辑。2.2 RGMII 信号与 2.5/25/125MHz 三档时钟RGMII 在 XC7A200T 这种器件的 HP Bank 上走 4 位接口收、发各 4 根数据线、1 根控制线、1 根时钟。控制线在上升沿表示 RX_DV/TX_EN下降沿表示 RX_ERR/TX_ERR。很多人忽略的是RGMII 的速率档位不止是时钟频率在变采样模式同时也在变。链路速率RX_CLK/TX_CLK 频率采样方式等效数据率1000 Mbps125 MHzDDR上下沿各传 4 bit1000 Mbps100 Mbps25 MHzSDR仅上升沿传 4 bit100 Mbps10 Mbps2.5 MHzSDR仅上升沿传 4 bit10 Mbps实际 PHY 芯片基本都按这个约定运行。如果只按 125MHz DDR 去采样百兆和十兆档必然拿到重复甚至错位的 nibble。RGMII 标准明确规定 10/100M 下数据只在上升沿采样千兆才是 DDR所以接收侧要根据 MAC IP 输出的速度档位信号做 MUX。// RGMII 接收侧千兆 DDR、百兆/十兆 SDR 的分类采样 reg [3:0] rxd_rise, rxd_fall; reg rx_ctl_rise, rx_ctl_fall; always (posedge rgmii_rx_clk or negedge rst_n) begin if (!rst_n) begin rxd_rise 4d0; rx_ctl_rise 1b0; end else begin rxd_rise rgmii_rxd; rx_ctl_rise rgmii_rx_ctl; end end always (negedge rgmii_rx_clk or negedge rst_n) begin if (!rst_n) begin rxd_fall 4d0; rx_ctl_fall 1b0; end else begin rxd_fall rgmii_rxd; rx_ctl_fall rgmii_rx_ctl; end end // speed_is_1000 来自 Tri-Mode Ethernet MAC 的状态输出 wire [7:0] rxd_byte speed_is_1000 ? {rxd_fall, rxd_rise} : {4d0, rxd_rise}; wire rx_dv rx_ctl_rise; wire rx_err rx_ctl_fall;两个 always 块分别锁上升沿和下降沿的 4 bit 数据千兆时把{rxd_fall, rxd_rise}拼成完整字节百兆和十兆直接用上升沿采到的低 4 位。rx_ctl 的两根线分别对应当前字节有效和数据错误标志这是 RGMII 控制通道的 DDR 语义和普通数据通道的解复用方式一致。这里的坑在于 RGMII 数据与时钟之间存在固定的约 2ns 相位偏移采样位置不是随便取的。实际工程应使用 IP 核或约束文件自带的 RGMII 引脚延迟配置直接对引脚采样很容易在温度变化后出现间歇性错位现象是 Wireshark 里大量 bad checksum 报错。2.3 接收时钟域与用户时钟域的隔离三速模式下 RX_CLK 频率会随链路速率变化而用户逻辑通常固定跑 125MHz。接收侧采出来的字节先落到异步 FIFO再被用户时钟读走是标准做法。Tri-Mode Ethernet MAC IP 内部使能收发 FIFO 后会自动完成这层隔离如果为了把 FIFO 深度做成自定义值而关闭内部 FIFO就必须在 AXI4-Stream 接口前手动例化 XPM 异步 FIFO。异步 FIFO 的背压直接映射到 treadyFIFO 几乎满时拉低 tready上游 MAC 停止发送。三速切换瞬间RX_CLK 频率跳变会导致异步 FIFO 两端时钟关系短暂失效所以切换时必须对 MAC 做复位否则格雷码同步逻辑可能采到亚稳态现象是切换速率后需要重新上电才恢复。3. Tri-Mode Ethernet MAC IP从 IP 核配置到 AXI4-Stream 收发3.1 IP 核关键配置项在 Vivado IP Catalog 里搜索 Tri-Mode Ethernet MAC配置要点集中在物理接口、速率档位和用户接口三处。下面这组配置是 XC7A200T 上跑三速 UDP 环回最直接的组合。配置项推荐值说明Physical InterfaceRGMII引脚最少1G 下 125MHz 容易满足时序Speed ModeTri-speed10/100/1000M 自适应环回测试验证的就是切换User InterfaceAXI4-Stream与 IP 内部 FIFO 衔接最简单Data Path Width8 bit125MHz 跑 1G 有裕量解析逻辑也更直观RX/TX FIFOEnable自动处理跨时钟域少写一半 RTLManagementMDIO能读 PHY 状态、切 PHY 回环模式IP 生成后先看 Example Design 里的约束RGMII 引脚和时钟约束别自己拍脑袋写。三速模式下的发送参考时钟在 1G 时由 MAC 侧提供 125MHz10/100M 时发送时钟方向会切到由 PHY 提供这个切换是 IP 内部处理的用户逻辑不能假设发送时钟恒为 FPGA 输出。3.2 AXI4-Stream 收发接口时序MAC IP 的 AXI4-Stream 接口是标准 valid/ready 握手帧数据按字节流排列以太网头在前。和普通 AXI Stream 相比多了一个伴随帧末的 tuser用于标记错误帧。信号方向行为rx_axis_tdata[7:0]输出帧字节大端顺序rx_axis_tvalid输出本拍数据有效rx_axis_tready输入下游可接收握手成功条件是两者同时为 1rx_axis_tlast输出帧最后一个字节rx_axis_tuser输出在 tlast 同拍拉高表示该帧 CRC 或长度错误接收解析的第一步不是处理 payload而是按字节偏移把帧头字段提取出来。帧内偏移 12 字节是以太类型14 字节进入 IP 头目的 IP 在这个工程里固定在偏移 30UDP 目的端口在偏移 36。// 从 AXI4-Stream 字节流中提取回环需要的字段 reg [7:0] eth_type_hi; reg [31:0] dst_ip; reg [15:0] dst_port; reg [10:0] rx_byte_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) rx_byte_cnt 11d0; else if (rx_axis_tvalid rx_axis_tready) begin if (rx_axis_tlast) rx_byte_cnt 11d0; else rx_byte_cnt rx_byte_cnt 11d1; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin eth_type_hi 8d0; dst_ip 32d0; dst_port 16d0; end else if (rx_axis_tvalid rx_axis_tready) begin case (rx_byte_cnt) 11d12: eth_type_hi rx_axis_tdata; // 以太类型高字节 11d30: dst_ip[31:24] rx_axis_tdata; // 目的 IP 11d31: dst_ip[23:16] rx_axis_tdata; 11d32: dst_ip[15:8] rx_axis_tdata; 11d33: dst_ip[7:0] rx_axis_tdata; 11d36: dst_port[15:8] rx_axis_tdata; // UDP 目的端口 11d37: dst_port[7:0] rx_axis_tdata; endcase end endrx_byte_cnt 是帧内绝对偏移从 0 数到帧尾。以太头 14 字节后接 20 字节 IP 头因此目的 IP 落在 30 到 33UDP 头在 34 之后目的端口在 36 和 37。这个偏移表建议直接做成注释贴在代码里后面接 VLAN 标签或 IPv6 时只需把基础偏移加上对应头长度。3.3 错误帧的标记与丢弃接收侧必须处理 tuser否则回环出去的帧会带 CRC 错误PC 网卡会直接丢帧现象是发 1000 个包回来只剩零星几个。tuser 只在 tlast 同拍给出结果不能提前采样。// 帧末才采样 tuser错误帧不提交给回环通路 reg rx_crc_err; always (posedge clk or negedge rst_n) begin if (!rst_n) rx_crc_err 1b0; else if (rx_axis_tvalid rx_axis_tready rx_axis_tlast) rx_crc_err rx_axis_tuser; end这个寄存器的值在帧结束后的第一个周期稳定回环状态机可以用它决定是否把帧首地址递交出去。错误帧被后续帧覆盖不额外做 FIFO 回退也不影响正确性只是丢一次回环机会。4. UDP 收发状态机的 Verilog 实现帧组装、校验和与环回判定4.1 回环策略原样转发还是重新封装回环可以做成整帧镜像把收到的以太网帧塞进 BRAM只交换源目 MAC 后发回去。这种做法快IP/UDP 校验和全部沿用原帧但改不了端口、也做不了更多链路检查。更完整的做法是把以太网/IP/UDP 三层头剥离只缓存 UDP 载荷发送时重新生成头部。代价是要处理 IP 和 UDP 两处校验和换来的是三速链路从物理层到传输层的全链路验证工程里用的就是重封装方案。回环时源目 IP、源目端口对调TTL 减一IP 校验和与 UDP 校验和都必须重算。4.2 发送状态机的五段式组织发送通道按以太头、IP 头、UDP 头、载荷四个阶段走IDLE 等触发。头字段由一组寄存器保存发送时逐拍输出载荷从回环 FIFO 读出。localparam TX_IDLE 4d0; localparam TX_ETH 4d1; // 14 字节以太网头 localparam TX_IP 4d2; // 20 字节 IPv4 头 localparam TX_UDP 4d3; // 8 字节 UDP 头 localparam TX_DATA 4d4; // UDP 载荷 always (posedge clk or negedge rst_n) begin if (!rst_n) begin tx_state TX_IDLE; tx_byte_cnt 6d0; tx_last 1b0; end else if (tx_axis_tvalid tx_axis_tready) begin case (tx_state) TX_ETH: begin tx_byte_cnt tx_byte_cnt 6d1; if (tx_byte_cnt 6d13) begin tx_state TX_IP; tx_byte_cnt 6d0; end end TX_IP: begin tx_byte_cnt tx_byte_cnt 6d1; if (tx_byte_cnt 6d19) begin tx_state TX_UDP; tx_byte_cnt 6d0; end end TX_UDP: begin if (tx_byte_cnt 6d7) begin tx_state (payload_len 16d0) ? TX_IDLE : TX_DATA; tx_byte_cnt 6d0; end else tx_byte_cnt tx_byte_cnt 6d1; end TX_DATA: begin if (payload_cnt payload_len - 16d1) begin tx_state TX_IDLE; tx_last 1b1; end end endcase end end以太网头计数器走到 13 表示 14 字节走完IP 头同理。UDP 头结束后检查载荷长度为零则直接收尾否则进 TX_DATA。tx_last 必须在最后一个数据拍与 tvalid 同时为高实际工程里建议把 tlast 的生成放到握手条件外面避免被 tvalid/tready 的组合时序影响否则仿真过了上板偶发缺帧。4.3 IP 与 UDP 校验和的 Verilog 实现IP 头校验和只覆盖 20 字节 IP 头UDP 校验和则覆盖 12 字节伪头、8 字节 UDP 头和全部载荷。伪头的构造顺序是源 IP、目的 IP、保留字节 0、协议号 17、UDP 长度这个头在回环地址交换后必须同步重算。// RFC 1071 方式的 1 的补码校验和累加器 reg [31:0] sum_acc; reg [15:0] chk_data; always (posedge clk or negedge rst_n) begin if (!rst_n) sum_acc 32d0; else if (sum_clr) sum_acc 32d0; else if (sum_en) sum_acc sum_acc {16d0, chk_data}; end wire [16:0] fold1 {1b0, sum_acc[15:0]} {1b0, sum_acc[31:16]}; wire [16:0] fold2 {1b0, fold1[15:0]} {1b0, fold1[16]}; wire [15:0] checksum ~fold2[15:0];累加过程中把每两个字节拼成一个 16 位大端值送入 chk_data累加器用 32 位宽度暂时容纳进位结束时把高 16 位折叠回低 16 位再处理一次进位后取反就得到标准校验和。计算前必须把 IP 头里的校验和字段和 UDP 头里的校验和字段全部置 0这个步骤漏掉会让计算结果恒错。如果回环只是交换地址而载荷不变校验和可以走增量更新新校验和等于旧校验和减去旧字段再加新字段不必重新扫整段载荷。但增量更新对 TTL 减一这类单字节变化容易写错工程里载荷都不大全量重算反而更好排错。4.4 ARP 应答是最容易被忽略的前置UDP 环回调不通八成不是 UDP 的问题而是 ARP。PC 要发 UDP 给 192.168.1.10 之前会先查 ARP 缓存缓存里没有就先广播 ARP 请求FPGA 不回应答网络调试助手就会提示目标不可达UDP 包根本不会进入发送队列。在 FPGA 里加一个最小 ARP 应答逻辑以太网类型 0x0806ARP opcode 为 1 且目标 IP 等于本地 IP 时把 opcode 改为 2源目标 MAC 和 IP 对调原样回发。判断条件如下if (eth_type 16h0806 arp_opcode 16h0001 arp_tpa local_ip) begin arp_reply_req 1b1; endarp_tpa 是 ARP 头里的目标协议地址在帧内偏移 38。完整实现需要 28 字节的 ARP 回包但状态机比 UDP 发送还简单写完 UDP 状态机顺手就能加。不写 ARP 应答而靠 Windows 的 arp -s 静态绑定只能算临时方案换一台电脑就失效。5. 上板验证与排错Wireshark 间隔统计、iPerf3 打流与 CRC 错帧定位5.1 用 PHY 回环先断链路上板第一步不是直接对打而是把 PHY 配置成 digital loopback。通过 MDIO 写 PHY 的回环寄存器MAC 发出的帧会被 PHY 原样送回这样能先把 FPGA 到 PHY 芯片这半段链路固定下来。不同 PHY 的回环寄存器位置差异很大翻 datasheet 确认不要凭经验猜。回环模式下 Wireshark 能看到自己发出帧的拷贝说明 RGMII 发送和接收路径基本可用。5.2 单包回环与帧间隔统计PHY 回环通过后恢复正常模式PC 网卡设静态 IP 192.168.1.100FPGA 固定 192.168.1.10:5000。用网络调试助手发一串带序号的 UDP 报文观察回包序号是否连续。Wireshark 过滤条件用udp ip.addr 192.168.1.10在列头右键添加字段frame.time_delta_displayed就能看到相邻两包的时间差。现象可能原因排查位置调试助手提示目标不可达ARP 无应答ARP 应答状态机、静态绑定回包缺失或乱序错帧被 tuser 丢弃rx_axis_tuser 计数100M 正常、1000M 不通RGMII 千兆 DDR 相位RGMII 引脚延迟约束回包 checksum 错误校验和字段未置 0IP/UDP 校验和重算帧间时间差稳定在整数倍说明 FPGA 发包节拍均匀如果忽大忽小多半是接收侧异步 FIFO 反压抖动。5.3 用 iPerf3 和 Python 打流看带宽FPGA 不是标准的 iPerf 服务端iperf3 自带的统计结果没有意义但可以用它制造流量再让 Wireshark 统计实际到达的 UDP 报文数iperf3 -u -c 192.168.1.10 -b 500M -t 10Wireshark 的 Statistics - UDP 里读每秒报文数和带宽对比 PC 发送侧的数字就能得出丢包率。更简单的方式是用 Python 发不带分片的 UDP 包1472 字节正好是 MTU 1500 减去 IP 头和 UDP 头后的上限超过这个值 IP 层会分片FPGA 侧解析复杂度立刻上升。import socket, time s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) payload b\x5a * 1472 end time.time() 5 cnt 0 while time.time() end: s.sendto(payload, (192.168.1.10, 5000)) cnt 1 print(send %.1f Mbps % (cnt * 1500 * 8 / 5 / 1e6))脚本按最大不拆包长度持续发送Wireshark 里统计回环报文数量。如果发送端 500Mbps 而回环只到 300Mbps先看 FPGA 侧 tready 的反压占比再看 RX/TX FIFO 深度是否够吃下突发流量。5.4 用 ILA 抓 tuser 定位错帧CRC 错帧的直接证据在 rx_axis_tuser。ILA 的 probe 接 tvalid、tlast、tuser 和 tdatacapture condition 设为 tvalid tlast tuser一次触发就能看到出错帧的帧末字节。配合一个错误计数寄存器把计数连到 ILA 的触发端口可以判断错帧是连续出现还是零星出现连续出现基本是 RGMII 相位或时钟问题零星出现多半是板级噪声或跨时钟域亚稳态。沿这条路径走完三速 UDP 环回才算真正闭环验证完成。本文还有配套的精品资源点击获取