简介:面向FPGA与网络协议学习者的千兆/百兆自适应以太网UDP传输工程,聚焦链路速率检测与UDP硬件收发,适合希望理解RGMII/GMII接口转换、以太网帧封装及低延迟传输机制的中级读者。压缩包共19个文件,以12个Verilog源文件为主体,搭配Xilinx IP配置文件(xci)、XML描述、Tcl脚本与XDC约束,整体约69KB,便于在Vivado中重建或对照工程结构。已有1302人学习下载。读者可从中梳理速率自适应控制、UDP发送接收状态机、CRC32校验、FIFO缓冲与时钟管理等模块的协作关系,并参考顶层连接与约束文件完成仿真、综合和上板调试。工程虽小,但覆盖从物理层接口到传输层组包的关键路径,适合作为FPGA以太网通信入门与课程设计参考。
1. 千兆百兆自适应不是多写一个 if 的事:一块板子要同时活在两个时钟里
有个做图像采集的同学找我,说他的板子插在千兆交换机上一切正常,换个百兆的老交换机就彻底哑了,抓包软件里连 ARP 都看不到。拆开看代码,接收通路写死了 125 MHz、8 bit 位宽,前导码按 8 位一拍去匹配,百兆链路下 PHY 只给 25 MHz、4 bit 半字节,整个状态机的节拍全错位,收到的自然全是垃圾。这就是千兆-百兆自适应以太网 UDP 传输这类 FPGA + Verilog 工程真正的难点:它要把自协商结果、时钟域、位宽转换、协议栈组包和校验和这几件事串成一条稳定的流水线,并不是在模块里加个速率寄存器就能解决。这套方案适合三类人:做毕设需要一块板子跑通 UDP 收发并讲清架构的;做课设要用 Verilog 从零搭 MAC 和 UDP 的;以及手里已有千兆通路、想补上百兆兼容性的从业者。下面按需求、架构、自适应、协议栈、避坑、验收的顺序,把这份工程完整拆一遍。
2. 总体架构:双速率 MAC 与 UDP 协议栈的六个模块怎么切
2.1 从 PHY 引脚到用户数据口的四段通路
整条通路我一般切成四段:PHY 接口适配层、MAC 收发层、协议解析层、用户缓冲层。PHY 接口适配层负责把 GMII 的 8 bit 和 MII 的 4 bit 统一成「一拍一个字节 + 一个 valid」的内部约定,上层模块不需要关心当前跑的是哪种速率。MAC 收发层处理前导码 7 个 0x55 加 1 个 SFD 0xD5 的识别、帧尾 4 字节 FCS 的校验与生成、以及帧间隔 IFG 的计时。协议解析层按以太网类型字段分流:0x0806 走 ARP,0x0800 走 IPv4 再解 UDP,其余丢弃。用户缓冲层就是收发各一组异步 FIFO,加上一组控制状态寄存器,让用户逻辑用自己熟悉的时钟读写数据,不用直接对接 PHY 的恢复时钟。
这样切的好处是每一层都能单独仿真。PHY 适配层用行为级模型灌 GMII 和 MII 两套时序;MAC 层灌固定帧长的激励;协议层直接把以太网帧喂进去,看解出来的 UDP 载荷对不对。等到上板的时候,哪一层出问题,波形上定位非常快。比较忌讳的写法是把前导码检测、类型判断、校验和计算全塞在一个 always 块里,一个信号出错整段逻辑都废,调试时只能靠猜。
2.2 时钟方案:125 MHz 8 bit 与 25 MHz 4 bit 的换算关系
速率自适应的核心其实是时钟和位宽两个维度同时变化,先把这个换算关系列清楚,后面所有设计都有依据。
| 链路模式 | PHY 接口 | 时钟频率 | 数据位宽 | 单拍字节数 | 理论线速 |
|---|---|---|---|---|---|
| 1000BASE-T | GMII | 125 MHz | 8 bit | 1 | 1000 Mbps |
| 100BASE-TX | MII | 25 MHz | 4 bit | 0.5 | 100 Mbps |
| 100BASE-TX | GMII(部分 PHY 支持) | 25 MHz | 8 bit | 1 | 100 Mbps |
接收方向的时钟来自 PHY 的 rx_clk,是恢复出来的时钟,千兆 125 MHz、百兆 25 MHz,不能拿板上的晶振去替代。发送方向千兆用 gtx_clk(125 MHz,由 FPGA 提供或 PHY 回送),百兆用 tx_clk(25 MHz)。用户侧我通常统一在 100 MHz 或 50 MHz 的系統时钟上,收发各用一个异步 FIFO 跨过去,FIFO 深度按最大帧 1518 字节再加一点余量,做到 2K 深度、8 bit 位宽基本够用。
提示:百兆模式下如果 PHY 配置成 GMII 接口但速率是 100M,时钟是 25 MHz、位宽仍是 8 bit,这种情况最容易在参数化时被漏掉,判断逻辑要按「时钟频率」和「位宽」两个独立维度来做,不能只用一个 gigabit 标志位。
2.3 模块清单与接口约定
把六个模块的职责和时钟域钉死,后面写代码基本就是填空题:
| 模块 | 时钟域 | 主要接口 | 职责 |
|---|---|---|---|
| phy_if | rx_clk / tx_clk | GMII、MII、内部 8bit+valid | 位宽归一化、速率选择 |
| mac_rx | rx_clk | 8bit+valid、帧长、FCS 结果 | 前导码对齐、FCS 校验、包边界 |
| mac_tx | tx_clk | 8bit+valid、帧长 | 前导码、FCS 生成、IFG 计时 |
| proto_rx | rx_clk 或用户时钟 | 帧数据、类型、UDP 载荷 | ARP/IP/UDP 解析、地址过滤 |
| proto_tx | 用户时钟 | 用户数据、目标 IP/端口 | ARP 查表、首部组装、校验和 |
| udp_fifo | 跨时钟 | 用户侧读写 | 收发缓冲、背压 |
接口约定里最容易含糊的是「包边界」怎么表达。我的做法是每条通路都带一个 sop(帧首拍)和 eop(帧末拍)标志,配一个帧长计数器,上层模块只要看到 sop 就能复位自己的解析状态,看到 eop 就结账。这个约定一旦统一,ARP、IP、UDP 三层解析器可以写成同一套模板,改改偏移量就行。
2.4 参数化还是运行时判决
两种做法都见过。参数化是把速率做成parameter,综合时生成两套逻辑,靠一个顶层开关切换,优点是时序确定、资源可控,缺点是要重新编译才能换速率,本质上是「手动自适应」。运行时判决是让速率信号从 PHY 的自协商结果里实时读出来,走同一套逻辑,切换时靠多路选择器切换时钟和位宽处理分支。
工程上我倾向运行时判决,因为交换机端口速率是插上网线那一刻才确定的,参数化方案在现场根本没法用。代价是多路选择器会引入一点组合延迟,以及切换瞬间必须做状态清理,这两点在第三章会展开。真正的自适应逻辑只有一处需要小心:不是「知道现在是百兆」,而是「知道现在从千兆变成了百兆」那个瞬间该做什么。
3. 速率自适应:自协商结果怎么读、通路怎么切
3.1 MDIO 读 PHY 状态寄存器,别只信 link 灯
PHY 的自协商结果放在标准寄存器里,通过 MDIO 两线接口读。MDIO 帧结构是 32 拍前导 1、2 bit 起始码 01、2 bit 操作码(读 10、写 01)、5 bit PHY 地址、5 bit 寄存器地址、2 bit 转向位、16 bit 数据。读的时候 FPGA 出前导和地址,之后要把数据线放开交给 PHY 驱动,转向位这一段如果方向切换晚了半个周期,读回来的数据就会整体偏移一位,这是新手最常见的翻车点。
需要读的寄存器主要是两个:寄存器 1(BMSR)的 bit15 是链路状态、bit11:10 是速率(00 为 10M、01 为 100M、10 为 1000M)、bit9 是双工;寄存器 0(BMCR)用来发重启自协商命令。判定流程是:上电后先等 BMSR 的 bit15 变 1,再等自协商完成位,然后锁存速率字段。这里有个玄学现象:热插拔网线时 link 位会抖一下,如果直接拿它当时钟切换的使能,状态机会被反复复位。
稳妥的做法是给 link 和速率信号各打两拍同步,再做一次「稳定计数」:连续 20 ms 内速率字段不变才认为有效,变化时先拉低内部的 link_ok,清空 FIFO、复位 MAC 状态机、把 ARP 表全部标脏,等稳定后再拉起。这段逻辑不长,但能省掉大量「板子有时候通有时候不通」的扯皮。
有些板子会把 PHY 的速率指示引脚直接引到 FPGA 的普通 IO 上,读引脚比读寄存器省事。这种做法能跑,但换一颗 PHY 就得改约束,可移植性差,毕设答辩时也不太好讲清楚自协商过程,我还是推荐走 MDIO。
3.2 接收通路:前导码对齐与双位宽拆包状态机
接收侧最关键的是把 4 bit 半字节流安全地拼成字节。MII 传输时字节是低位先出,一个字节的低半字节先到、高半字节后到;GMII 则是整字节一拍给到。下面这段打包逻辑是整条接收通路的入口,两种模式都走同一个 always 块,靠phy_is_gigabit分流:
// 半字节打包:MII 两拍拼一个字节,GMII 直通 // rx_clk 由 PHY 提供,千兆 125MHz、百兆 25MHz always @(posedge rx_clk or negedge rst_n) begin if (!rst_n) begin nib_lo <= 4'd0; nib_half <= 1'b0; rx_dv8 <= 1'b0; rx_data8 <= 8'd0; end else if (phy_is_gigabit) begin // 千兆:8bit 直通,不参与拼包 rx_dv8 <= gmii_rx_dv; rx_data8 <= gmii_rxd; nib_half <= 1'b0; end else begin if (mii_rx_dv) begin if (!nib_half) begin nib_lo <= mii_rxd; // 低半字节先到,暂存 nib_half <= 1'b1; rx_dv8 <= 1'b0; // 拼包期间不出 valid end else begin rx_data8 <= {mii_rxd, nib_lo}; // 高半字节拼上,组成完整字节 nib_half <= 1'b0; rx_dv8 <= 1'b1; end end else begin nib_half <= 1'b0; // dv 撤销,丢弃半个字节 rx_dv8 <= 1'b0; end end end这段代码有两个设计点值得说明。一是nib_half在半字节状态下遇到 dv 撤销会强制清零,防止一个被截断的帧把下一个帧的字节对齐搞乱;二是rx_dv8比数据晚一拍出,下游状态机拿 valid 打拍,天然对齐。位宽换算完成后,前导码检测就简单了:连续数到 0x55 达到 6 个以上,再来一个 0xD5 就认为帧开始,之后的数据按字节进状态机。
状态机本身只需要五个状态:IDLE 等前导、PREAMBLE 数 0x55、HEADER 收前 14 字节(目的 MAC 6 + 源 MAC 6 + 类型 2)、DATA 按类型字段把剩余字节分发、FCS 收最后 4 字节并比对。帧长计数在 HEADER 结束时就锁存总长度,DATA 状态里用「剩余字节数」递减到 4 时自动切 FCS,不需要再解析上层长度字段,这样即使上层长度写错,也不会把帧边界搞乱。
3.3 发送通路:位宽转换与 IFG 计时按速率重算
发送侧的位宽转换是反过来的:内部是 8 bit 字节流,百兆时要拆成两个半字节分两拍送出去,千兆时直接送。拆分时钟用 tx_clk,同样由速率决定。这里有个细节——内部字节流的产生时钟通常是用户时钟,需要先过异步 FIFO 到 tx_clk 域再拆,不能在用户时钟域直接拆半字节,否则跨时钟域的组合逻辑会被综合出奇怪的东西。
IFG 的计时别用固定拍数。千兆下 12 字节帧间隔等于 96 拍 125 MHz,百兆下 4 bit 位宽等于 24 拍 25 MHz,看起来不一样,但本质都是「12 个字节时间」。我一般用一个按字节计数的定时器,tx_clk 下每送出一个字节加一,加到 12 就允许发下一帧,这样两种速率共用一套逻辑,改速率不用改常数。前导码同样按字节发,7 个 0x55 加 0xD5,百兆时拆半字节,千兆时整字节,写成同一个发送子状态即可。
还有一个容易忽略的点:最后 4 字节 FCS 是低位先出的,CRC 计算时如果按 MSB 移位,上板后抓包工具会报「CRC 错误但内容正确」,很多人会误以为是 PHY 配置问题,实际是位序反了。
3.4 切换瞬间的处理:清 FIFO、丢半包、复位状态机
速率切换不是无缝的,链路重新协商期间会有几十毫秒到几秒的数据中断,这段时间里 FIFO 里可能残留半个帧。处理顺序我固定成三步:先把内部 link_ok 拉低,让用户侧写 FIFO 的使能失效;再对 MAC 收发状态机做一次同步复位,把所有状态拉回 IDLE;最后把收发 FIFO 整体清空,ARP 表 valid 位清零。等自协商稳定、速率锁存完成后再把 link_ok 拉起,用户侧重新开始发包。
顺序不能反。先清 FIFO 再复位状态机,复位过程中状态机可能又往 FIFO 里写几个字节,残留就清不掉了。这个坑我在早期项目里踩过一次,表现是百兆和千兆来回插拔几次之后,偶尔会发出一两个源 MAC 错乱的帧,抓包看像是地址被改写,实际是 FIFO 里的旧数据和新帧头拼在了一起。
4. UDP 协议栈落地:ARP 表、IP 首部与校验和流水线
4.1 ARP:先答后问,一张小表够不够
以太网发 UDP 之前必须先知道对端 MAC,所以 ARP 是绕不开的。设计上要同时支持两个方向:作为应答方,收到目标 IP 是自己的 ARP 请求就回一个应答;作为请求方,用户给了目标 IP 但表里没有 MAC 时,主动发请求并等应答。表的大小按实际场景定,做点对点传输、上位机固定几个 IP 的话,4 到 8 项完全够用。
| 字段 | 位宽 | 说明 |
|---|---|---|
| valid | 1 | 表项有效标志,link 切换时清零 |
| ip_addr | 32 | 对端 IPv4 地址 |
| mac_addr | 48 | 对应对端 MAC |
| age_cnt | 24 | 老化计数,按秒递减,归零清 valid |
查表用组合逻辑并行比较,命中就把 MAC 送进发送引擎,没命中就触发 ARP 请求并把当前待发数据包挂起。挂起策略有两种:一是缓存最近一包,等应答回来再发;二是直接丢弃,让用户侧重发。毕设场景我推荐缓存一包,因为抓包时能看到完整的「ARP 请求 → ARP 应答 → UDP 数据」时序,答辩演示很直观。缓存深度按最大帧算,一块 2 KB 的块 RAM 就够。
ARP 应答方向要注意两点:一是请求帧的帧长是 42 字节,小于以太网最小帧 64 字节,必须补齐到 60 字节再算 FCS,否则交换机直接丢;二是应答里的源 MAC 要填自己的 MAC,不能照抄请求里的。
4.2 IP 首部组装与增量校验和
IPv4 首部 20 字节,字段固定:版本加首部长度 0x45、服务类型 0x00、总长度(首部加 UDP 长度)、标识符递增、标志位 0x4000(允许分片但实际不分片)、TTL 64、协议号 17、首部校验和、源 IP、目的 IP。总长度和校验和是每包都要重算的,其余字段可以预先存成常量。
校验和用 16 位反码和,先对 10 个 16 位字求和,把高 16 位折回低 16 位,再取反。写法上我倾向用函数一次算完:
// IP 首部校验和:20 字节按 16bit 大端切分求和,折叠进位后取反 // 调用前校验和字段必须先填 0 function [15:0] ip_csum; input [159:0] hdr; // 20 字节首部,高位在前 integer i; reg [31:0] sum; begin sum = 32'd0; for (i = 0; i < 10; i = i + 1) sum = sum + {16'd0, hdr[i*16 +: 16]}; // 逐字累加 sum = sum[31:16] + sum[15:0]; // 第一次折叠 sum = sum[31:16] + sum[15:0]; // 第二次折叠,吃掉残余进位 ip_csum = ~sum[15:0]; // 反码 end endfunction这段是纯组合逻辑,10 次加法串起来在 125 MHz 下时序会紧。量产代码里通常改成两级流水:第一级算 5 对的和,第二级合并折叠,或者干脆用增量更新——只有标识符和总长度在变,其他字段固定,校验和的差值可以预先算好,每包只做一次加法。毕设阶段先用组合版本跑通,时序不收敛再改流水线,别一上来就过度设计。
4.3 UDP 校验和:为什么很多实现直接写 0
IPv4 下 UDP 校验和是可选的,发送方填 0 表示「未计算」,接收方看到 0 就跳过校验。这给了一个很实用的取舍:如果对端是 PC 上的普通 socket 程序,填 0 完全能通,省掉一整套伪首部累加逻辑;如果对端是严格的嵌入式协议栈或者要做完整性验证,就必须算。
真要算的话,伪首部是 12 字节:源 IP 4 字节、目的 IP 4 字节、一个字节 0、一个字节协议号 17、两字节 UDP 长度。把伪首部、UDP 首部 8 字节、载荷连起来做 16 位反码和,同样折叠取反,结果为 0 时要写成 0xFFFF。载荷长度不是偶数时末尾补一个 0 字节参与计算,但不计入长度字段。
我的建议是:毕设和课设直接填 0,把精力放在速率自适应和时序上,答辩时能说清楚「IPv4 下可选、置 0 表示不校验」这个设计取舍,比堆一个跑不通的校验逻辑分高得多。要是项目要求端到端完整性,再补这段,逻辑不复杂但要注意字节序。
4.4 发送引擎:三种帧共用一条流水
ARP 应答、ARP 请求、UDP 数据这三种帧的头部长度不同,但发送流程完全一致:前导码 → 帧头 → 载荷 → 补齐到 60 字节 → FCS → IFG。所以发送引擎只写一条状态机,用一个「帧描述」寄存器组告诉它这包是什么类型、头部从哪取、载荷多长。
// 发送主状态机:三种帧共用,帧类型只影响头部来源 localparam S_IDLE = 4'd0, // 等发送请求 S_PRE = 4'd1, // 7 个 0x55 + 1 个 0xD5 S_HDR = 4'd2, // 以太网头 + IP/UDP 头(或 ARP 头) S_PAY = 4'd3, // 用户载荷,从 FIFO 读 S_PAD = 4'd4, // 不足 46 字节补 0 S_FCS = 4'd5, // 4 字节 CRC S_IFG = 4'd6; // 12 字节间隔 always @(posedge tx_clk or negedge rst_n) begin if (!rst_n) begin state <= S_IDLE; byte_cnt <= 16'd0; crc_en <= 1'b0; end else begin case (state) S_IDLE: if (tx_req) begin frame_len <= tx_len; // 锁存本帧总长 state <= S_PRE; byte_cnt <= 16'd0; end S_PRE : if (byte_cnt == 16'd7) state <= S_HDR; // 前导码结束 S_HDR : if (hdr_done) state <= S_PAY; S_PAY : if (pay_done) state <= (frame_len < 16'd60) ? S_PAD : S_FCS; S_PAD : if (byte_cnt >= 16'd60) state <= S_FCS; // 补齐到最小帧 S_FCS : if (byte_cnt == 16'd3) state <= S_IFG; S_IFG : if (byte_cnt == 16'd11) state <= S_IDLE; default: state <= S_IDLE; endcase end end状态机里frame_len锁存这一刻很关键,后面所有补齐判断、FCS 触发都用它,不能在 S_PAY 阶段再去看用户侧的长度信号——用户侧可能已经改了。crc_en在前导码结束后拉高、FCS 开始时拉低,CRC 计算模块只在窗口内累加,避免把前导码算进去。
发送背压的处理方式是:用户侧 FIFO 空了但帧还没发完,状态机停在 S_PAY 等,同时输出一个 underflow 标志给用户侧;连续两个周期读不到数据就主动中止本帧,回到 IDLE 并把 CRC 结果丢弃。宁可丢一帧也不能发半帧,半帧会让对端的 MAC 一直等 eop,严重的会直接把对端网卡挂死。
5. 避坑与排查:五个让板子「灯亮不通包」的现场
5.1 链路协商成百兆,逻辑还按千兆跑
现象:插千兆交换机一切正常,插百兆交换机后抓不到任何包,或者偶尔收到一两个明显乱码的帧,PHY 的 link 灯是亮的。
原因:速率信息只在复位时采样了一次,或者采样用的是 BMCR(控制寄存器)而不是 BMSR(状态寄存器),读到的永远是上电默认值。热插拔之后 PHY 重新协商成百兆,逻辑里phy_is_gigabit还是 1,接收侧按 8 bit 一拍去解 4 bit 半字节流,字节全错位。
解决:速率判定只在 link 稳定上升沿锁存,加 20 ms 稳定窗口过滤抖动,并且锁存的是 BMSR 的 bit11:10 解码结果。锁存值变化时走 3.4 节的三步清理流程。验证方法很简单,在复位后手动强制 PHY 只广播百兆能力,看逻辑是否跟着切。
5.2 抓包显示 CRC 错误但内容完全正确
现象:抓包软件里能看到完整的 UDP 载荷,内容和预期一致,但每条帧都被标记为校验和错误,接收端直接丢弃。
原因:以太网 FCS 用的是 CRC-32,多项式 0x04C11DB7,实际实现用反射形式 0xEDB88320,初值全 1,结果取反,字节内低位先出。常见错误有两种:一是用非反射多项式按 MSB 移位,二是忘了最后取反,三是输出的 4 字节顺序反了。
解决:核对三个参数——多项式用反射式、初值 32'hFFFFFFFF、结果取反;发送时先发 CRC 的最低字节。快速自检的办法是拿标准测试向量喂进 CRC 模块,比如对全 0 的 60 字节算一次,和抓包软件里已知的正确值对一下,比上板试错快得多。
5.3 小包被交换机静默丢弃
现象:发大包(比如 1000 字节载荷)正常,发几个字节的短包(比如心跳包)对方收不到,抓包工具在本地能看到发出,对端就是没有。
原因:以太网最小帧是 64 字节(不含前导码),减去 14 字节帧头和 4 字节 FCS,载荷至少 46 字节。UDP 载荷小于 18 字节时整帧就不够 60 字节,很多交换机直接丢弃,不报错。
解决:发送引擎里加补齐状态,帧总长不足 60 字节时补 0 到 60,但 UDP 长度字段仍然填真实长度。注意补齐的 0 不参与 UDP 校验和计算——如果做了校验和的话。
5.4 IP 首部校验和报错,PC 直接丢包
现象:抓包工具提示 header checksum incorrect,PC 侧 ping 都不通,但 ARP 是通的。
原因:一是算法漏了「折叠进位」这一步,只做了一次 32 位加法就取低 16 位;二是字节序搞反,Verilog 里按 16 位拼接时把高低字节写反了,网络字节序是大端。
解决:按 4.2 节的函数写,两次折叠之后取反;拼接时记住第一个字节在高位,比如首部的45 00要拼成16'h4500。校验和字段本身在计算时必须先填 0,算完再写回去,顺序不能颠倒。
5.5 第二个包开始头部错位
现象:第一帧正常,从第二帧起目的 MAC 和 IP 全乱,或者载荷里混进了上一帧的尾部。
原因:帧边界没对齐。常见的是发送状态机在 S_IFG 结束后没有复位字节计数器,或者接收侧 FIFO 里残留了上一帧的半个包,用户侧读的时候没按 sop/eop 对齐,把两帧拼在一起。
解决:所有字节计数器在进入 S_PRE 和 S_IDLE 时强制清零;FIFO 写入时用 sop/eop 打标记,读出侧看到 eop 就强制结束当前包,宁可丢数据也不能越界。另外 FIFO 的读写指针在 link 切换时必须一起复位,只复位一侧等于没复位。
6. 验收与答辩:三层测试把「能通」变成「可复现的能通」
验收我固定分三层,从下往上,每层不过不往下走。第一层是内部环回:在 MAC 发送和接收之间做数字环回,不经过 PHY,灌一组递增数据,比对收发是否一致,这一层验证的是组包、CRC、状态机本身,跟硬件无关,仿真里就能跑完。第二层是 PHY 环回:把 PHY 配成内部环回模式,数据从 FPGA 发出、经 PHY 绕回来,这一层验证 GMII/MII 时序和时钟是否正确,尤其是百兆模式下 4 bit 半字节的采样沿。第三层是外部打流:两块板子对接,或者板子接 PC,用 ping 验证 ARP 和 ICMP 通路,再用打流工具压满带宽,同时开着抓包软件看有没有 CRC 错、有没有乱序。
| 指标 | 测试方法 | 合格线 |
|---|---|---|
| 链路自适应 | 分别在千兆、百兆交换机下插拔 5 次 | 每次 3 秒内恢复收发,无残留错帧 |
| 吞吐 | 打流工具发 1400 字节 UDP 包,持续 60 秒 | 千兆 ≥ 900 Mbps,百兆 ≥ 90 Mbps |
| 丢包率 | 同上,统计收发计数差 | 连续 60 秒丢包数 ≤ 10 |
| 帧完整性 | 抓包软件过滤 CRC 错误 | 错误帧数为 0 |
| 最小帧 | 发 8 字节载荷的心跳包 | 对端可正常接收 |
答辩演示我一般准备两条脚本,一条 ping 一条打流,现场切换交换机端口做速率演示。ping 用指定包长和次数,别用默认无限 ping,现场手忙脚乱容易忘停:
# 先通 ARP:发 4 个 64 字节包,2 秒超时 ping -n 4 -l 36 -w 2000 192.168.1.10 # 再压吞吐:UDP 模式、1400 字节、跑 30 秒,观察两端计数 iperf -c 192.168.1.10 -u -b 900M -l 1400 -t 30讲的时候重点讲三张图:自协商状态机的波形(能看到 link 上升沿和速率锁存)、发送状态机从 S_PRE 到 S_IFG 的完整跳转、以及抓包软件里 ARP 请求应答加 UDP 数据的三帧时序。资源占用讲一张表就够:LUT、FF、BRAM、以及最高可达时钟频率,百兆和千兆各报一组。有老师会问「百兆下为什么不用 GMII 的 25 MHz 8 bit 模式」,这时候能答出「取决于 PHY 支不支持,逻辑上两种都要兼容」就说明真的动手调过。
还有个小技巧:提前把两种速率下的抓包文件各存一份,现场万一网络环境出问题,直接放录好的包讲时序,比现场救火体面得多。
从那以后我每次交板子前都强制走一遍「拔插五次 + 双速率打流」的流程,哪怕需求里只写了千兆——因为你永远不知道对面那台交换机是多少年前的货色。希望帮到你。
本文还有配套的精品资源,点击获取