4. MAC层核心功能解析:帧封装与解封装、地址过滤、帧校验(CRC32)、帧间距(IFG)管理

各位同学,咱们今天聊点实在的。MAC层,说白了就是以太网通信的「交通警察」。它不负责数据怎么打包成比特流,那是PCS层的事。它管的是:这帧数据合不合法?该不该收?发出去有没有错?发完一帧后歇多久?

我当年刚接触千兆以太网时,总觉得MAC层不就是加个头部、算个CRC嘛,有啥难的?结果第一次调通链路时,发现对端设备就是不认我的数据。查了三天,最后发现是IFG少了一个时钟周期。嗯,从那以后我再也不敢小看这些「细节」了。

4.1 帧封装:把数据包穿上「马甲」

先看发送方向。上层协议栈(比如IP层)丢下来一个数据包,MAC层得给它套上以太网帧的格式。标准以太网帧长这样:

字段 长度(字节) 说明
前导码(Preamble) 7 0x55重复7次,用于时钟同步
帧起始定界符(SFD) 1 0xD5,标志帧正式开始
目的MAC地址 6 接收方地址
源MAC地址 6 发送方地址
长度/类型 2 小于1536表示长度,大于等于1536表示类型
数据载荷 46~1500 不够46字节要填充
帧校验序列(FCS) 4 CRC32校验值

在FPGA里实现时,我习惯用一个状态机来驱动。状态跳转很简单:IDLE → PREAMBLE → SFD → HEADER → DATA → CRC → IFG。每个状态对应一个计数器,计到该字段的字节数就跳转。

这里有个坑:前导码和SFD是MAC层生成的,不是上层给的。我见过有人把上层数据直接往GMII接口上怼,结果对端PHY芯片根本锁不住时钟。你想想看,没有前导码做同步,接收端怎么知道数据从哪开始?

核心要点:帧封装时,前导码和SFD必须由MAC层硬件自动插入。数据载荷如果小于46字节,必须填充到46字节,否则接收端会认为是「残帧」直接丢弃。

4.2 帧解封装:把收到的「马甲」扒下来

接收方向正好反过来。从GMII/RGMII接口拿到数据后,第一步是检测SFD。检测到0xD5后,下一个字节开始就是目的MAC地址。

我个人习惯在接收路径上做一个「帧头提取器」。它干三件事:

  • 剥离前导码和SFD
  • 提取目的MAC、源MAC、长度/类型字段
  • 把数据载荷和FCS分开缓存

为什么要分开缓存?因为FCS要在整个帧收完后才能校验。你不能边收边把数据往上送,万一CRC校验失败,已经送出去的数据就收不回来了。我早期的一个项目就犯过这个错,结果上层协议栈经常收到坏包,重传率高达30%。

正确的做法是:先收完一整个帧,校验CRC通过后,再把数据载荷交给上层。这需要至少一个帧大小的FIFO做缓冲。千兆以太网最大帧是1518字节(不含前导码),所以FIFO深度至少要2K字节,我一般留到4K以防万一。

4.3 地址过滤:不是我的包,我不要

以太网是共享介质吗?在交换式网络中,每个端口只收发给自己的包。地址过滤就是干这个的。

FPGA里实现地址过滤,通常有三种模式:

  1. 完美匹配:目的MAC必须等于配置的MAC地址,完全相等才收
  2. 哈希过滤:用CRC或哈希算法,把48位MAC映射到一个哈希表里,查表决定收不收
  3. 混杂模式:所有包都收,不管目的MAC是谁

我项目中用得最多的是完美匹配+混杂模式可配置。为什么?因为调试时你需要混杂模式抓所有包,上线后切回完美匹配。哈希过滤虽然省资源,但会有「假阳性」——不是你的包也被收进来了,上层还得再过滤一遍。

小技巧:地址过滤模块最好支持多播和广播地址的识别。广播地址(FF:FF:FF:FF:FF:FF)必须收,多播地址根据配置决定。我曾经遇到一个bug,广播包被过滤掉了,结果ARP协议永远无法响应,整个网络不通。

4.4 帧校验(CRC32):数据完整性的最后防线

CRC32,以太网用的多项式是0x04C11DB7。计算方式网上资料一大堆,我不啰嗦。我只说FPGA实现时的几个关键点:

  • 初始值:0xFFFFFFFF,不是0
  • 异或输出:计算完的结果要再异或0xFFFFFFFF
  • 字节顺序:先发送的是高位字节,但CRC计算是按位进行的,注意比特顺序

在千兆以太网中,数据位宽是8位(GMII)或4位(RGMII),时钟125MHz。你不能一个时钟算一个字节,那样时序太紧张。我一般用并行CRC,一个时钟算8个字节(64位),这样时钟频率可以降到156.25MHz,时序好做很多。

并行CRC的推导方法很简单:把串行CRC的反馈逻辑展开,用组合逻辑实现。网上有现成的工具可以生成Verilog代码,但我建议你自己推一遍,理解其中的原理。我记得有一次,我用工具生成的CRC代码,结果在仿真时发现和标准以太网测试向量对不上,最后发现是工具把比特顺序搞反了。

避坑指南:我曾经在接收端CRC校验时,把FCS字段也算进了CRC计算范围,结果永远校验失败。正确的做法是:CRC计算范围只包括目的MAC、源MAC、长度/类型、数据载荷,不包括FCS本身。接收端计算完CRC后,和收到的FCS比较,相等则通过。

4.5 帧间距(IFG)管理:给网络一点喘息时间

IFG,全称Inter-Frame Gap,就是两帧之间的空闲时间。标准规定:IFG最小为96个比特时间。对于千兆以太网,一个比特时间是1ns,所以IFG就是96ns。

在FPGA里,IFG管理其实就是一个计数器。发送完一帧的FCS后,计数器开始从0数到95(或者更精确的时钟周期数),期间GMII/TX接口保持IDLE状态(发送0x07,即IDLE符号)。

为什么要有IFG?两个原因:

  • 给接收端足够的时间处理上一帧,比如把数据从FIFO搬到内存
  • 防止某个设备长时间占用总线,实现公平访问

我遇到过一个问题:IFG设得太小,对端交换机直接丢包;设得太大,带宽利用率下降。千兆以太网的理论最大帧速率是每秒约81274帧(64字节小包),如果IFG多加10ns,帧速率就降到约80000帧,损失约1.5%的带宽。

所以我的建议是:IFG严格按标准实现,96比特时间,不多不少。不要自作聪明去优化,网络设备之间的互操作性比那点带宽重要得多。

总结一下:MAC层的这四个核心功能——帧封装解封装、地址过滤、CRC32校验、IFG管理——是千兆以太网通信的基石。任何一个环节出问题,轻则丢包重连,重则整个网络瘫痪。我见过太多「看起来能通,但跑业务就崩」的案例,最后查下来都是MAC层实现有瑕疵。

下一章,咱们聊聊如何用状态机把这些功能串起来,写一个完整的千兆以太网MAC控制器。到时候我会给出完整的Verilog代码框架,咱们一起动手调通它。