欢迎光临
我们一直在努力

以太网帧格式详解:从一个 ping 包说起

本系列专栏面向数字 IC 验证工程师。这是第 01 篇,属于免费试读章节。 读完之后,你应该能准确回答:MAC 收到一串比特流后,凭什么判断"这是一个合法的帧",以及"这个帧该不该被我收下"。


引言:一条 ping 命令背后,帧长什么样

在终端敲下 ping 192.168.1.1,操作系统会构造一个 ICMP Echo Request,一路向下封装到数据链路层。到了 MAC 这一层,它被拼成一个以太网帧,交给 PHY 变成差分信号发到网线上。

对验证工程师来说,问题就从这里开始:

  • 一个帧到底由哪些字段组成,每个字段几字节?
  • 帧有长度限制吗?为什么是这个数字?
  • MAC 收到一个帧,从第一个比特到"交给上层",中间做了哪几次判断?每次判断不通过会发生什么?

第 01 篇把这三个问题讲透。这是后面所有内容的地基——不管是流控、VLAN、还是描述符机制,本质上都是在处理帧的某些字段。


一、两种帧格式:Ethernet II 与 IEEE 802.3

以太网帧有两套几乎一样、但语义不同的封装,区别只在第 13~14 字节这个字段上。

1.1 Length/Type 字段的两义性

这个 2 字节字段承担双重职责,靠数值范围来区分:

字段值语义帧格式名称Payload 长度判据
0x0000 ~ 0x05DC(0~1500) Length(长度) IEEE 802.3 由 Length 字段决定,MAC 据此截取 payload
0x0600 ~ 0xFFFF(1536~65535) Type(类型) Ethernet II(DIX) 由帧结束(EOP)决定,长度字段不参与判断

判定阈值 1536(0x0600)不是随意定的:802.3 的最大 payload 是 1500 字节,而最小有意义的上层协议类型值从 1536 开始,中间留了空隙——这就是经典以太网的"1536 分界线"。

配图 1:Length/Type 字段的判定分界(自绘,源脚本见文末说明)

验证提示:边界值 1500 与 1536 之间,以及 0x05DD~`0x05FF` 这段"无人区",是很好的定向测试激励。

1.2 常见 EtherType 速查

EtherType协议
0x0800 IPv4
0x0806 ARP
0x86DD IPv6
0x8100 802.1Q VLAN Tag
0x88A8 802.1ad QinQ(外层 tag)
0x8808 MAC Control(含 PAUSE 帧)

这份表请记住 0x8100 和 0x8808——第 05 篇(流控)和第 06 篇(VLAN)会反复用到。


二、帧的完整结构:逐字节拆解

完整的一帧从线缆上看是这样的(前导码与 IFG 严格说不是"帧"的一部分,但验证时必须管):

配图 2:以太网帧结构(自绘)。注意 Preamble / SFD 不计入帧长,IFG 是帧与帧之间的间隔、也不在帧里。

2.1 前导码与 SFD:为什么需要 8 字节的"热身"

字段长度值作用
Preamble 7 B 0x55 × 7 一串 10101010,让接收端 CDR(时钟数据恢复)锁定频率、PHY 达到稳定状态
SFD 1 B 0xD5 10101011,末尾的 11 打破规律,标志"帧从这里开始"

两个必须记住的事实:

  • 帧从 DA 开始计数,不包含前导码和 SFD。 所以"帧长 64 字节"指的是 DA 到 FCS。
  • 前导码重复 1010 是为了制造 DC 平衡的周期信号供时钟恢复;SFD 的 11 结尾是唯一的"定界符"。
  • 这也是为什么很多 MAC 的接收状态机第一级就是 SFD 搜索:在字节流里找 0xD5,找到才开始装载。

    2.2 DA / SA:6 字节地址里的两个隐藏比特

    MAC 地址 48 bit,通常写成 00:1A:2B:3C:4D:5E。第一个字节(第一个八位组)里藏着两个控制位:

    比特名称含义
    bit 0(最低位) I/G(Individual / Group) 0 = 单播,1 = 组播(广播 FF:FF:FF:FF:FF:FF 是组播的特例)
    bit 1 U/L(Universal / Local) 0 = 全球唯一(厂商烧录),1 = 本地管理(软件指派)

    所以判断一个地址是不是组播,看的是第一个字节的最低位:

    • 01:00:5E:… → 第一个字节 0x01,bit0 = 1 → 组播
    • FF:FF:FF:FF:FF:FF → 广播
    • 02:… → bit1 = 1 → 本地管理单播(虚拟环境里常用)

    易错点:组播判断看的是 bit0 而不是整个字节,很多初学者的地址过滤模型在这里写错,覆盖率跑满但功能是错的。

    2.3 Payload 与 Pad:46 字节的由来

    Payload 名义上是 46 ~ 1500 字节。但"46"不是上层协议的要求,而是整个帧最小 64 字节的副产品:

    64(最小帧长) – 6(DA) – 6(SA) – 2(Type) – 4(FCS) = 46

    当上层给的数据不足 46 字节(比如 ARP 请求实际只要 28 字节,加上填充才够),MAC 负责补零(Pad)到 46 字节。

    这里有个经典的坑:

    场景现象
    Ethernet II(Type ≥ 0x0600) 接收端按 EOP(帧结束)判断 payload 长度,Pad 的零字节会被上层(如 IP 头里的 Total Length)自行剔除
    IEEE 802.3(Length ≤ 1500) 接收端按 Length 字段截取,Pad 对齐后不进入上层

    所以 MAC 是否自动 Pad、Pad 的时机在 FCS 计算之前还是之后,是一个必须写进验证计划的特性点。Pad 必须在 FCS 之前——否则 FCS 就不覆盖被填充的内容了。定向测试时构造 1 字节 payload,是最容易暴露 Pad 实现错误的激励。

    2.4 FCS:CRC-32 与那个容易踩的位序问题

    FCS(Frame Check Sequence)是 4 字节的 CRC-32,生成多项式:

    G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1

    对应的标准写法(反射形式)是 0xEDB88320,初值 0xFFFF_FFFF,结果取反。

    覆盖范围:DA 到 Payload 结束,不含前导码、SFD。

    这里的坑在位序。 协议规定:帧内每个八位组都是"最低位先上线"。FCS 是全帧唯一的例外——它从 x^31 的系数开始往上发,也就是整个 32 bit 由高到低依次上线。于是同一个 FCS 会以两种面貌出现在你面前:

    • 软件 crc32() 算出的规范化 32 bit 值;
    • 接口上按时间顺序抓到的 4 个字节序列。

    这两者之间差一次字节序处理。不统一约定的话,后果很直接:所有正常帧都会被判成 FCS 错。

    我的建议是定一条平台内的约定,而不是每次现场推导:

    • 参考模型内部统一使用"软件 CRC-32 的规范化值"(即 crc32 算法输出)。
    • 只在 interface 层(driver 与 monitor)做一次字节序转换。
    • 约定写进 TB 的注释和文档,避免不同人各写一版。

    下面这个是可运行的 SystemVerilog 实现(反射算法,与标准一字节一字节的参考实现等价):

    // 文件:eth_pkg/eth_crc32.svh
    // 计算以太网 FCS:输入为 DA ~ Payload 的字节数组,输出为规范化 CRC-32 值
    function automatic bit [31:0] eth_fcs(byte unsigned data[], int unsigned len);
    bit [31:0] crc = 32'hFFFF_FFFF;
    bit inv;

    for (int unsigned i = 0; i < len; i++) begin
    for (int b = 0; b < 8; b++) begin // 每个字节低位先处理
    inv = crc[0] ^ data[i][b]; // data[i][b] 即第 b 位(LSB 优先)
    crc = crc >> 1;
    if (inv) crc ^= 32'hEDB8_8320; // 反射多项式
    end
    end

    return ~crc; // 结果取反
    endfunction

    关于 FCS 的确切位序与覆盖范围,最终请以 IEEE 802.3 标准原文(Clause 3 相关条款)为准;本文给出的是工程上可直接落地的约定与实现。


    三、长度边界:64 / 1518 / 1522 三个数字的由来

    边界数值由来违反后果
    最小帧长 64 B 早期共享式以太网依靠 CSMA/CD 碰撞检测:帧必须足够长,保证发送端在发完之前仍能感知到最远端反射回来的碰撞信号 短于 64 B 的帧称 runt,直接丢弃
    最大帧长 1518 B 802.3 的历史约定,兼顾早期缓冲内存成本与信道公平性 超出称 oversize / giant,丢弃
    带 VLAN Tag 1522 B 802.1Q 插入 4 字节 tag,若上位机对帧长敏感,需相应放宽 未配置放宽时,带 tag 的长帧会被误判为超长

    验证提示:最大帧长的边界测试必须清楚 DUT 用的是 1518 还是 1522 的判据——很多 MAC 有一个"最大帧长"配置寄存器,默认值不同会导致同一个激励在不同工程里结论相反。这类"激励一样、结论相反"的争议,在以前的环境里遇到过不止一次,根因基本都是配置寄存器没在验证计划里显式列出来。

    另外还有两个容易漏的错误类型:

    错误类型判据常见成因
    Alignment Error 帧长度不是整数字节(比特数不是 8 的倍数) 只可能出现在 MII 这类 4 bit(半字节)接口上,是接口层错位的典型症状
    Code Error / RX_ER PHY 在收帧期间拉高错误指示 线路噪声或 PHY 内部解码失败,MAC 应丢弃并计数

    四、收帧流水线:MAC 收下一个帧时做了什么

    这是本文的核心。MAC 的接收通路可以抽象成一条带否决权的流水线:

    flowchart TD
    A[字节流进入] –> B[SFD 搜索<br/>找到 0xD5]
    B –> C[装载 DA / SA / Type]
    C –> D{地址过滤<br/>DA 是否匹配?}
    D –>|不匹配| X1[丢弃 / 不计数]
    D –>|匹配| E[装载 Payload<br/>Pad 处理]
    E –> F{FCS 校验}
    F –>|错| X2[丢弃 + 错误计数<br/>FCS Error]
    F –>|对| G{长度检查<br/>runt / oversize?}
    G –>|异常| X3[丢弃 + 错误计数]
    G –>|正常| H{其他检查<br/>Alignment / VLAN / Length}
    H –>|异常| X4[丢弃 + 错误计数]
    H –>|通过| I[提交至 FIFO<br/>RX 通路]
    I –> J[中断 / 描述符状态回写<br/>参见第 08 篇]

    配图 3:MAC 接收流水线。CSDN 编辑器原生支持 mermaid,上面的源码会直接渲染成流程图;如需在别的平台用,把它导出成图片即可。

    4.1 每一级的验证要点

    流水线级DUT 该做的事验证关注点
    SFD 搜索 识别 0xD5 并开始装载 连续无效前导码、前导码被截断、0x55 中出现 0xD5 的误判
    地址过滤 单播精确匹配 / 组播匹配 / 广播 / 混杂模式 / 哈希过滤 组播 bit0 判据、混杂模式开关、哈希冲突行为、地址表容量边界
    Payload 装载 按 Length 或 EOP 截取,处理 Pad 最小 payload(1 B)时的 Pad 正确性、Length 与实际长度不符时的行为
    FCS 校验 CRC-32 计算并与 FCS 字段比对 单比特翻转能否检出、FCS 错误是否只计数不中断后续收帧
    长度检查 runt / oversize 判定 63 B(runt)与 64 B 的边界、1518 与 1519 的边界、配置可调范围
    提交与回写 状态、长度、错误位写入描述符 错误帧的 buffer 是否正确释放(参见第 10 篇的内存泄漏检查)

    4.2 发送侧对应动作

    发送通路的检查点相对少,但边界同样密集:

  • Pad 补齐:payload < 46 B 时补零。
  • FCS 计算与插入:覆盖 DA~Payload。
  • 前导码与 SFD 生成:0x55×7 + 0xD5。
  • IFG 保持:帧间隔不小于 96 bit times,即 12 字节时间。
    • 100 Mbps:960 ns
    • 1 Gbps:96 ns
    • 10 Gbps:9.6 ns
  • 流控响应:收到 PAUSE 帧后按 pause_time 暂停发送(详见第 05 篇)。
  • IFG 的验证有一个实操难点:仿真里时间精度不够时,96 ns 的间隔容易因为采样点误差产生假失败。建议在 monitor 里用位时间而非绝对时间戳来计算间隔,并把误差容限单独做成可配置参数。


    五、从验证视角看:帧格式相关的测试点清单

    把上面的内容落成一张验证计划表(可直接粘进你的验证计划文档):

    #特性检查点激励构造方式建议覆盖点
    1 帧长边界 runt(<64)/ 64 / 1518 / 1519 / 1522 参数化帧长随机 帧长 bins:63,64,65,1517,1518,1519,1522
    2 Payload 边界 1 B / 45 B / 46 B / 47 B / 1500 B 直接指定 payload 长度 payload 长度跨 46 的跨界 bin
    3 Pad 行为 payload < 46 时是否正确补零且计入 FCS 定向短帧序列 pad 开 / 关 × 长度类型
    4 Length/Type 语义 ≤1500 按长度截取;≥1536 按 EOP 双向构造 length/type 值域(1500/1535/1536/0xFFFF)
    5 地址过滤 单播命中/未命中、组播、广播、混杂、哈希 地址池随机 DA 类型 × 过滤开关交叉
    6 FCS 正确帧通过;单比特/双比特错误被检出 错误注入(逐字节翻转) 错误位置 × 错误位数
    7 Alignment Error 非整字节帧被丢弃并计数 仅在 nibble 接口可构造 错误类型 bin
    8 IFG 发送侧帧间隔 ≥ 96 bit times 连续背靠背发送 IFG 值:96/97/95
    9 统计计数 各类错误计数器与预期一致 混合激励后读寄存器 计数器类型 × 事件数
    10 连续收帧 错误帧不阻塞后续正常帧 错误帧插在正常帧流中间 错误帧位置

    这张表覆盖了帧格式层约 80% 的功能点。建议在环境搭好后先把这张表跑成回归,再往下做描述符和流控。


    六、踩坑实录

    坑 1:FCS 位序约定不统一,比对全挂 早先环境里的参考模型和接口层各写了一套 FCS 计算,一个用规范化值、一个用线上字节序,结果所有正常帧都被判为 FCS 错。定位花了半天,根因是两个模块对"FCS 的 32 bit 怎么拼"理解不同。教训:位序约定必须写进 TB 的 interface 注释里,且只能有一个模块负责转换。

    坑 2:Pad 与 Length 字段的配合 构造 32 字节 payload 的 IEEE 802.3 帧时,DUT 自动补零到 46 字节,但接收端按 Length 字段截取 32 字节——上层拿到的是 32 字节而不是 46 字节。测试用例按 46 字节预期比对,导致假失败。根因是没分清 Length 语义和 EOP 语义。教训:任何涉及 Pad 的测试,先明确 payload 长度的判据来源。

    坑 3:IFG 的假失败 在 1 Gbps 配置下验证 IFG,monitor 用 $time 绝对时间戳比对 96 ns,仿真时间精度设置不当,测出的间隔是 95.8 ns,判定违反。真实信号没问题,是测量方法有问题。教训:时钟域相关的时序检查,优先用位时间计数,并把容限参数化。


    七、自检清单

    读完之后,对照下面每一条,能讲清楚就打勾:

    • 能说出 Length/Type 字段的判定分界值,以及两种格式下 payload 长度分别由谁决定
    • 知道帧长从哪个字段开始计数,64 / 1518 / 1522 分别怎么来的
    • 能从 MAC 地址第一个字节读出单播/组播、全局/本地管理
    • 知道 Pad 的作用、发生在 FCS 之前还是之后
    • 能用一句话解释 FCS 位序的特殊之处,以及为什么要在平台内统一约定
    • 能画出收帧流水线,并说出每一级的否决后果
    • 知道 IFG 在 100M / 1G / 10G 下分别等于多少纳秒

    八、下一篇预告

    我们已经知道 MAC 会检查地址、算 CRC、判长度。但这些检查分别在哪个模块里发生?"混杂模式"打开后流水线怎么变?错误计数寄存器到底统计哪些事件?

    第 02 篇《MAC 子层到底在干什么:成帧、过滤、校验、统计》会把这条流水线拆到模块级,并给出地址过滤与统计计数的完整验证方案。


    *本文为《以太网 MAC/IP 验证实战》专栏第 01 篇(免费试读)。文中图表全部自绘:配图 1、配图 2 由 Python(matplotlib)脚本 gen_eth_figs.py 生成,可改参数复现;配图 3 为 mermaid 源码。代码均为原创示例,DUT 载体建议使用开源 xge_mac 或自建 toy MAC 工程。

    赞(0)
    未经允许不得转载:171主机测评 » 以太网帧格式详解:从一个 ping 包说起
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址