本系列专栏面向数字 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 字节字段承担双重职责,靠数值范围来区分:
| 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 速查
| 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 打破规律,标志"帧从这里开始" |
两个必须记住的事实:
这也是为什么很多 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 每一级的验证要点
| 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 发送侧对应动作
发送通路的检查点相对少,但边界同样密集:
- 100 Mbps:960 ns
- 1 Gbps:96 ns
- 10 Gbps:9.6 ns
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 工程。




