第49章:以太网UDP测试实验
ARP 让开发板"被找到",ICMP 让开发板"被 ping 通",这一章的 UDP 则让开发板真正"说人话"——收发用户数据。本章也是以太网三部曲的收官之战:ARP + ICMP + UDP 三协议共存。
本章在讲什么?(先看这个)
实验任务:电脑用网口调试助手发数据 → FPGA 收到后原样发回(UDP 环回)。
本章的核心增量只有一个——UDP 协议,但工程架构升级到了三协议并存:
- UDP 协议:传输层的"快递小哥",只管送不管签收;
- 三协议切换:eth_ctrl 模块从二选一升级为三选一;
- 异步 FIFO:为将来收发不同时钟域做好准备。
核心概念拆解
49.1 UDP 是什么,为什么不用 TCP
UDP(User Datagram Protocol,用户数据报协议)是面向无连接的传输层协议:发送端不确认对方是否存在就直接发,接收端也不回执。听起来很"不靠谱",但它的两个优点让它在硬件世界里几乎是唯一选择:
- 开销小、速度快:没有连接建立、确认重传这些负担;
- 丢一两个包无伤大雅:视频直播、音频传输等实时场景完全可以容忍。
那为什么不用更可靠的 TCP?原文的理由非常实在:TCP 是为软件灵活性设计的,用硬件逻辑实现工程量巨大,且功能和性能难以保证。所以 FPGA 以太网传输绝大多数基于 UDP。
还有个容易被问到的问题:数据直接封装在 IP 里不行吗?不行——一台服务器要同时提供多种服务(网页 80 端口、FTP 21 端口……),端口号才是区分服务的钥匙。TCP/UDP 都用 16 位端口号(0~65535)。
49.2 UDP 数据格式:8 字节首部,短小精悍

| 字段 | 位数 | 说明 |
|---|---|---|
| 源端口号 | 16 | 本实验固定 1234 |
| 目的端口号 | 16 | 本实验固定 1234 |
| UDP 长度 | 16 | 首部+数据总长 |
| UDP 校验和 | 16 | 含伪首部校验,接收端一般不检测,本实验直接填 0 |
完整的封装关系:MAC帧[ IP数据报[ UDP报文[ 用户数据 ] ] ],用户数据一包最多 1472 字节(1500 − 20 − 8)。
UDP 校验和的完整算法涉及"伪首部"(从 IP 头里抽出来的源/目的 IP、协议号、UDP 长度),本实验偷了个懒——置 0 不校验,实际接收端大多也不查,可以接受。
49.4 程序设计:UDP 模块 + 三协议切换
总体架构

相比 ICMP 实验,变化点:
- 新增 UDP 模块(udp_rx + udp_tx + crc32_d8);
- FIFO 从同步升级为异步 FIFO(虽然当前收发还是同时钟,但为后续例程留好了口子);
- eth_ctrl 的协议切换信号
protocol_sw从 1 位扩展为 2 位,四种取值:00=ARP、01=UDP、10=ICMP。
udp_rx:多一层首部而已
接收状态机与 ICMP 版本几乎一样,只是 st_ip_head 后面接的是 st_udp_head(解析 8 字节 UDP 首部)而不是 ICMP 首部。老规矩:中间状态出错跳 st_rx_end,等 gmii_rx_dv=0 再复位,避免把同一包的剩余字节误认为新的前导码。
接收有效数据的核心代码:
st_rx_data : begin
//接收数据
if(gmii_rx_dv) begin
udp_rx_cnt <= udp_rx_cnt + 16'd1;
rec_data <= gmii_rxd;
rec_en <= 1'b1;
if(udp_rx_cnt == data_byte_num - 16'd1) begin
udp_rx_cnt <= 16'd0;
rec_pkt_done <= 1'b1;
rec_byte_num <= data_byte_num;
end
end
end
注意校验和旁路累加在 UDP 版本里没有了——因为 UDP 校验和我们不算。
udp_tx:IP 首部协议号换成 17
发送状态机与 ICMP 版本同构,IP 首部初始化只有一处不同:
ip_head[2] <= {8'h40,8'd17,16'h0}; //协议:17(udp) ←ICMP实验里是8'd01
//UDP头部
udp_head[0] <= {16'd1234,16'd1234}; //源端口号:1234 目的端口号:1234
udp_head[1] <= {udp_num,16'h0000}; //16位UDP长度,校验和置0
发送数据状态同样处理了最小帧长问题:
st_tx_data : begin
crc_en <= 1'b1;
gmii_tx_en <= 1'b1;
gmii_txd <= (udp_tx_cnt < tx_data_num) ? tx_data : 8'd0; //不足18字节补0
...
end
以太网数据段最少 46 字节,去掉 IP 首部(20)和 UDP 首部(8)后,有效数据至少 18 字节,不足就补零。
eth_ctrl:二位宽开关 + 数据通路复用
三协议切换的核心是一个 2 位选择器:
case(protocol_sw)
2'b00: begin
gmii_tx_en <= arp_gmii_tx_en;
gmii_txd <= arp_gmii_txd;
end
2'b01: begin
gmii_tx_en <= udp_gmii_tx_en;
gmii_txd <= udp_gmii_txd;
end
2'b10: begin
gmii_tx_en <= icmp_gmii_tx_en;
gmii_txd <= icmp_gmii_txd;
end
endcase
优先级逻辑:UDP 请求 > ICMP 请求 > ARP,且 ARP 必须等 UDP 和 ICMP 都空闲才被响应。
除了发送总线,接收数据和 FIFO 读请求也要做三路复用:
//接收数据使能与数据判断(ICMP和UDP收到的数据统一送FIFO)
else if (icmp_rec_en) begin
rec_en <= icmp_rec_en;
rec_data <= icmp_rec_data;
end
else if(udp_rec_en) begin
rec_en <= udp_rec_en;
rec_data <= udp_rec_data;
end
//读请求仲裁
assign tx_req = udp_tx_req ? 1'b1 : icmp_tx_req;
这里有个容易忽略的细节:icmp_tx_req 和 udp_tx_req 各打了一拍再用于数据选择(icmp_tx_req_d0/udp_tx_req_d0),让数据选择与请求信号对齐,避免第一个字节读错。
顶层环回链路
assign icmp_tx_start_en = icmp_rec_pkt_done; //收到ICMP请求→发应答
assign tx_start_en = rec_pkt_done; //收到UDP包→原样发回
assign des_mac = src_mac; //目标地址从解析结果来
assign des_ip = src_ip;
整个系统本质是"收到什么发什么"的镜像结构,这也是所有回环测试的经典范式。
49.5 下载验证
管脚与 ARP 实验一致。验证流程:
- 电脑 IP 设 192.168.1.102,下载程序;
- 打开网口调试助手:协议选 UDP,本地 IP 192.168.1.102、本地端口 1234,点【打开】;
- 远程主机填 192.168.1.10:1234(开发板 IP 和端口号都是 1234,对应代码里
udp_head[0] <= {16'd1234,16'd1234}); - 发送
http://www.openedv.com/forum.php,接收窗口原样出现同样的字符串——环回成功!

用 Wireshark 抓包可以看到完整的四次交互:ARP 请求→ARP 应答→UDP 数据→UDP 回包,双击回包能看到源/目的 IP 和端口均为 1234,数据段正是发送的字符串。

初学者容易踩的坑
- 端口号不匹配:调试助手本地端口必须是 1234(与代码一致),否则发出去的包开发板不认。
- IP 协议号忘改:从 ICMP 例程改 UDP 时,IP 首部协议字段必须从 1 改成 17,否则电脑把包当 ICMP 解析失败。
- UDP 长度算错:UDP 长度 = 首部 8 字节 + 数据长度,不是以太网总长度,多算 20 字节会被丢弃。
- 协议切换丢包:UDP 发送过程中来了 ARP 请求,如果不判
udp_tx_busy就切走protocol_sw,正在发的 UDP 包会被截断。 - FIFO 读请求没对齐:
tx_req与数据选择之间差一拍,导致发送的第一个字节是 FIFO 里残留的旧数据。 - 最小 18 字节有效数据:发送数据不足 18 字节又不补零,帧长不足 46 字节,电脑网卡直接丢弃。
小结 & 下一步
本章是以太网协议栈的"毕业设计":
- UDP 首部只有 8 字节,端口号是服务区分的核心;UDP 校验和含伪首部,简单场景可置 0;
- 三协议共存架构:协议解析并行独立、发送总线用 2 位开关串行复用、优先级 UDP > ICMP > ARP;
- 异步 FIFO 替代同步 FIFO,为跨时钟域应用做好准备;
- 完整的环回验证 + Wireshark 抓包分析,四包流程(ARP×2 + UDP×2)一气呵成。
至此,FPGA 已经具备了完整的以太网通信能力。下一章开始玩法升级:基于 OV5640 摄像头的 PL 以太网视频传输实验——把摄像头画面通过网络实时传出去,前面所有的协议模块都将派上用场。