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里实现地址过滤,通常有三种模式:
- 完美匹配:目的MAC必须等于配置的MAC地址,完全相等才收
- 哈希过滤:用CRC或哈希算法,把48位MAC映射到一个哈希表里,查表决定收不收
- 混杂模式:所有包都收,不管目的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代码框架,咱们一起动手调通它。