4、数据链路层(MAC)设计:MAC控制器架构、帧格式解析、CRC校验实现、流量控制机制
数据链路层,说白了就是MAC(Media Access Control)层。很多刚入行的朋友容易把MAC和PHY搞混,我简单解释一下:PHY负责把数字信号变成模拟信号扔到线上去,而MAC负责管“什么时候发、发什么、发完怎么确认”。今天咱们就聊聊MAC设计的几个核心模块。
4.1 MAC控制器架构
MAC控制器的架构,我个人习惯把它分成三块:发送通路、接收通路、以及管理控制单元。这三块各司其职,但又紧密配合。
发送通路:从上层(比如TCP/IP协议栈)拿到数据,加上MAC头、FCS(帧校验序列),然后按位送给PHY。这里有个关键点——发送时机。以太网是CSMA/CD机制,发之前得先听一下信道是不是空闲。我在项目中遇到过一个问题:发送通路在信道忙时没有做退避,结果连续碰撞,导致丢包率飙升。后来加了个状态机,严格按截断二进制指数退避算法来,问题就解决了。
接收通路:从PHY收数据,先做CRC校验,校验通过就剥掉MAC头和FCS,把净荷往上送。如果校验失败,直接丢弃。嗯,这里要注意:接收通路必须能处理“短帧”和“长帧”。短帧小于64字节,属于冲突碎片,直接扔掉;长帧超过1518字节(或者1522字节,如果带VLAN标签),也得丢弃。
管理控制单元:负责配置MAC的工作模式(全双工/半双工)、速率(10/100/1000M)、以及统计信息(比如发送帧数、接收帧数、CRC错误数)。这些统计信息对调试和运维非常有用。
核心要点:MAC控制器本质上是一个状态机驱动的数据管道。发送和接收通路必须独立,否则全双工模式下会出问题。
4.2 帧格式解析:Ethernet II vs 802.3
以太网帧格式主要有两种:Ethernet II(也叫DIX以太网)和IEEE 802.3。你想想看,这两种格式长得挺像,但有一个关键区别——类型/长度字段。
Ethernet II帧格式:
| 前导码(7B) | 帧起始定界符(1B) | 目的MAC(6B) | 源MAC(6B) | 类型(2B) | 数据(46-1500B) | FCS(4B) |
类型字段(EtherType)用来标识上层协议,比如0x0800代表IP,0x0806代表ARP。这个字段值大于0x0600(1536),所以不会和长度字段混淆。
IEEE 802.3帧格式:
| 前导码(7B) | 帧起始定界符(1B) | 目的MAC(6B) | 源MAC(6B) | 长度(2B) | LLC(3B) | SNAP(5B) | 数据(38-1492B) | FCS(4B) |
长度字段表示后面数据的字节数,最大值是1500。802.3帧还多了LLC和SNAP子层,用来标识上层协议。说实话,现在绝大多数网络设备都支持Ethernet II,802.3帧用得比较少。但我建议你在设计MAC时两种格式都要支持,因为有些工业协议(比如EtherCAT)会用到802.3帧。
避坑指南:我曾经在设计时只考虑了Ethernet II,结果客户用了一个老设备,发的是802.3帧,MAC解析出来全是乱码。后来加了一个自动检测逻辑:如果类型/长度字段值大于1536,按Ethernet II解析;否则按802.3解析。这样两种格式都能兼容。
4.3 CRC校验实现
CRC(循环冗余校验)是MAC层的最后一道防线。以太网用的是CRC-32,多项式是0x04C11DB7。说白了,就是发送端对帧的所有字节(从目的MAC到数据末尾)算一个32位的校验值,放在FCS字段里。接收端用同样的多项式再算一遍,如果结果等于0x2144DF1C(或者叫“残差”),说明帧没出错。
CRC的实现方式有两种:串行和并行。
串行CRC:按位计算,每个时钟周期处理1位。优点是逻辑简单,缺点是速度慢。10M/100M以太网用串行CRC没问题,但到了千兆,一个时钟周期要处理8位甚至更多,串行就不行了。
并行CRC:一次处理多个位。比如千兆以太网,每个时钟周期处理8位(一个字节)。并行CRC的推导比较复杂,但网上有现成的工具可以生成Verilog代码。我个人习惯用Python脚本生成并行CRC的查找表,然后转成硬件描述。
下面是一个简单的CRC-32串行计算示例(Verilog风格):
// CRC-32 串行计算
// 多项式: 0x04C11DB7
reg [31:0] crc_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
crc_reg <= 32'hFFFFFFFF;
else if (crc_en) begin
if (data_in ^ crc_reg[31]) begin
crc_reg <= {crc_reg[30:0], 1'b0} ^ 32'h04C11DB7;
end else begin
crc_reg <= {crc_reg[30:0], 1'b0};
end
end
end
重要提醒:CRC计算完成后,需要按位取反再发送。接收端收到后,用同样的多项式计算,如果结果等于0x2144DF1C(取反后的残差),说明帧正确。这个细节很容易被忽略,我见过有人直接拿CRC结果去比对,结果死活对不上。
4.4 流量控制机制
流量控制,说白了就是防止发送方太快,把接收方给撑死。以太网有两种流量控制方式:半双工下的背压(Backpressure)和全双工下的暂停帧(Pause Frame)。
半双工背压:当接收方快撑不住时,就故意制造碰撞信号,让发送方退避。这种方式比较粗暴,但实现简单。不过现在全双工是主流,背压用得越来越少了。
全双工暂停帧:这是IEEE 802.3x标准定义的。接收方发一个特殊的MAC控制帧,告诉发送方“停一停,等我消化一下”。暂停帧的格式如下:
| 目的MAC(6B) | 源MAC(6B) | 类型(2B) = 0x8808 | MAC控制操作码(2B) = 0x0001 | 暂停时间(2B) | 填充(42B) | FCS(4B) |
暂停时间单位是“512位时间”。比如千兆以太网,512位时间就是512ns。如果暂停时间设为0,表示恢复发送。
我在项目中遇到过一个问题:流量控制开启后,发送方收到暂停帧,但暂停时间还没到就又开始发了。后来查了一下,发现是MAC的暂停定时器实现有bug——它用的是连续计数,而不是加载计数。改成加载计数后,问题就解决了。
设计建议:流量控制不是必须的。如果你的应用场景是点对点通信,且接收方有足够的缓冲,可以关闭流量控制。但如果是交换网络,建议开启,否则丢包率会很高。
好了,MAC层的这几个核心点就聊到这儿。下一章咱们聊聊PHY层的设计,那又是另一番天地了。