以太网帧结构解析:从物理层到MAC层的握手
各位同学,今天我们来聊聊以太网帧结构。说实话,这个主题看起来简单,但我在实际项目中踩过不少坑。记得刚做第一版RTL时,我以为帧结构就是背背字段长度就完事了,结果仿真死活对不上——后来才发现,前导码的处理时序就让我折腾了一周。
好,我们正式开始。以太网帧,说白了就是数据在网线上跑的「包裹」。这个包裹有固定的打包规则,我们做RTL设计,就是要精确地拆包和打包。
前导码与帧起始定界符:物理层的敲门砖
先看帧的最开头。前导码(Preamble)是7个字节的0x55,也就是二进制01010101。为什么是交替的01?我个人的理解是,这相当于给接收端做「时钟同步训练」。你想想看,接收端刚上电,时钟相位还没锁定,突然来一串数据,它怎么知道什么时候采样?
前导码之后是帧起始定界符(SFD),1个字节的0xD5,也就是11010101。注意最后两位是11,不是01。这个变化就是告诉接收端:「准备好了,下一字节就是真正的MAC帧了」。
关键点:在RTL实现中,前导码和SFD通常由PHY芯片处理,MAC层不关心。但如果你做的是FPGA直接接SGMII接口,那就得自己处理这8个字节。我曾经在项目中遇到过PHY芯片配置错误,导致前导码长度不对,结果MAC层一直收不到有效帧——排查了整整两天。
MAC地址与类型字段:谁发给谁,干什么用
跳过前导码和SFD,接下来就是真正的MAC帧头了。一共14个字节:
| 字段 | 长度 | 说明 |
|---|---|---|
| 目的MAC地址 | 6字节 | 接收方网卡地址 |
| 源MAC地址 | 6字节 | 发送方网卡地址 |
| 类型/长度 | 2字节 | 0x0800表示IP,0x0806表示ARP |
MAC地址是48位的,通常写成十六进制,比如 00:1A:2B:3C:4D:5E。在RTL里,我们一般用两个32位寄存器来存,或者干脆用一个64位寄存器,低16位不用。
类型字段很有意思。如果值大于等于0x0600,就表示上层协议类型;如果小于0x0600,就表示帧长度。这是IEEE 802.3和Ethernet II两种帧格式的区别。我个人习惯在RTL里统一按Ethernet II处理,因为现在99%的网络设备都用这个。
小技巧:在做MAC地址过滤时,别忘了处理广播地址(全F)和多播地址(第一位为1)。我曾经在项目里只做了单播过滤,结果广播帧全部被丢弃,交换机直接断连——嗯,这个教训挺深刻的。
CRC校验原理:最后的守护者
帧尾是4字节的CRC32校验码。它覆盖的范围是从目的MAC地址开始,到数据字段结束(不包括前导码和SFD)。
CRC32的生成多项式是:0x04C11DB7。说白了,就是把数据当作一个巨大的二进制数,除以这个多项式,余数就是CRC。接收端做同样的除法,如果余数为0,说明数据没出错。
在RTL里实现CRC32,我建议用并行CRC计算,而不是串行移位。为什么?因为串行移位每个时钟只能处理1位,对于千兆以太网来说,一个时钟要处理8位甚至32位数据,串行根本来不及。我常用的方法是查表法或者直接用组合逻辑展开。
注意:CRC校验在RTL里容易犯的一个错误是字节序问题。以太网传输是大端模式,但很多处理器是小端。如果你直接把CPU算出来的CRC塞进去,大概率会出错。我曾经在仿真时发现CRC一直报错,最后发现是字节序搞反了——改了一行代码就解决了。
RTL接收状态机设计:从比特流到帧解析
好了,前面都是理论知识。现在我们来设计真正的RTL接收状态机。这是整个以太网MAC接收端的核心。
我常用的状态机有5个状态:
IDLE → PREAMBLE → SFD → HEADER → DATA → CRC_CHECK → IDLE
每个状态干什么?
- IDLE:等待接收使能信号。检测到RX_DV有效,跳转到PREAMBLE。
- PREAMBLE:接收7个字节的0x55。注意要计数,不能多也不能少。
- SFD:接收1个字节的0xD5。如果收到别的值,说明帧错误,回到IDLE。
- HEADER:接收14个字节的MAC头。这里可以并行做MAC地址过滤。
- DATA:接收数据载荷,同时计算CRC。注意最小帧长64字节,不够的要填充。
- CRC_CHECK:接收4字节CRC,与本地计算值比较。匹配则帧有效,否则丢弃。
核心设计要点:状态机一定要有超时保护。我曾经遇到过PHY芯片异常,RX_DV一直拉高,结果状态机卡在DATA状态出不来。后来加了一个字节计数器,超过最大帧长(1518字节)就强制回到IDLE——这个保护机制后来救了我好几次。
下面是我画的一个简化版接收状态机流程图,用SVG展示:
这个状态机看起来简单,但实际编码时有很多细节。比如在HEADER状态,你可以并行做MAC地址过滤——如果目的MAC不是本机地址,直接丢弃,不用等到CRC校验完。这样可以节省不少处理时间。
另外,DATA状态要同时做两件事:存储数据和计算CRC。我建议用双端口RAM或者FIFO来缓存数据,CRC计算器则用流水线结构。这样每个时钟都能处理一个字节,不会成为瓶颈。
个人经验:在写RTL代码时,我习惯把状态机的每个状态单独写一个always块,这样调试时看波形一目了然。虽然代码量会大一些,但可读性和可维护性都好很多。你想想看,如果所有状态挤在一个always块里,出错了找bug得多痛苦?
好了,这一章的内容就到这里。以太网帧结构是网络协议解析的基础,前导码、MAC地址、类型字段、CRC校验,每个环节都有它的设计考量。下一章我们会深入MAC层的具体RTL实现,包括地址过滤、帧校验和流量控制。记住,做RTL设计,细节决定成败。
公众号:蓝海资料掘金营,微信deep3321