2、以太网基础回顾:OSI模型与TCP/IP模型、MAC层在其中的位置、以太网帧结构详解

各位同学,咱们今天聊点基础但极其重要的东西。做FPGA的以太网开发,说白了就是跟MAC层打交道。你要是连MAC层在哪儿、帧结构长什么样都搞不清楚,那后面写代码肯定要踩坑。我当年刚入行时,就吃过这个亏——上来就撸RTL代码,结果仿真死活对不上,最后发现是对前导码的理解出了偏差。嗯,咱们先把地基打牢。

2.1 OSI模型与TCP/IP模型:MAC层到底在哪一层?

先看这张经典的网络分层图。OSI七层模型,从上到下分别是:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。而咱们实际工程中更常用的是TCP/IP四层模型:应用层、传输层、网络层、网络接口层。

MAC层属于哪一层?答案是数据链路层。在TCP/IP模型里,它被归到网络接口层。我个人习惯把数据链路层再拆成两个子层:逻辑链路控制子层(LLC)和介质访问控制子层(MAC)。咱们FPGA工程师主要跟MAC子层打交道,LLC通常由上层软件处理。

你想想看,数据从应用层一路往下封装,到了网络层加上IP头,到了数据链路层加上MAC头和FCS尾巴,最后通过物理层变成电信号或光信号发出去。接收端反过来,一层层剥掉头部,直到拿到原始数据。MAC层就是负责在相邻节点之间可靠地传输数据帧。

核心要点:MAC层是数据链路层的下半部分,负责帧的封装/解封装、寻址、差错检测。FPGA实现的就是这部分硬件逻辑。

2.2 MAC层在其中的位置:为什么FPGA适合做MAC?

MAC层的工作有两个特点:一是实时性要求高,二是处理逻辑相对固定。你想想看,每个以太网帧进来,你得在几十纳秒内完成前导码检测、地址匹配、FCS校验。这种活儿让CPU用软件做,延迟大、吞吐量上不去。让FPGA做,正好——硬件流水线处理,一拍一个节拍,效率极高。

我在项目中遇到过这样的情况:客户要求实现一个万兆以太网接口,如果用CPU软核做MAC层处理,吞吐量最多跑到2-3Gbps,CPU占用率还飙到90%以上。后来改用FPGA硬核MAC,轻松跑到线速10Gbps,CPU只负责管理面配置。这就是硬件加速的魅力。

个人经验:做千兆以太网时,我建议直接用FPGA内部的硬核MAC(比如Xilinx的GEM或Intel的EMAC)。如果芯片没有硬核,用RTL自己写一个千兆MAC也不难,但要注意时序收敛。我曾经在Artix-7上纯逻辑实现过千兆MAC,跑在125MHz时钟下,时序余量还有0.3ns,完全够用。

2.3 以太网帧结构详解:从比特流到完整帧

好,咱们进入重头戏。一个标准的以太网帧长什么样?我直接给你画出来:

| 前导码(7B) | SFD(1B) | 目的MAC(6B) | 源MAC(6B) | 类型/长度(2B) | 数据(46-1500B) | FCS(4B) |

一共8个字段,咱们一个一个说。

2.3.1 前导码(Preamble)—— 7字节的同步信号

前导码是7个字节的固定模式:0x55 0x55 0x55 0x55 0x55 0x55 0x55。每个字节是10101010,也就是交替的1和0。为什么要这样设计?说白了就是为了让接收端的时钟恢复电路能锁定到发送端的时钟频率。你想想看,发送端和接收端的时钟不可能完全一样,总有点偏差。前导码这种交替模式,能让接收端的PLL快速锁定相位。

在FPGA实现时,我一般会用一个状态机来检测前导码。检测到连续的7个0x55后,就准备接收SFD。这里有个坑:有些PHY芯片会把前导码吃掉,直接给你SFD之后的数据。所以写代码前一定要先确认PHY的行为。

避坑指南:我曾经在调试时发现,接收到的数据总是少7个字节。查了半天,原来是PHY芯片默认开启了"前导码移除"功能。后来在PHY的寄存器里关掉这个选项,数据就正常了。所以,做FPGA MAC时,一定要先读PHY的数据手册,搞清楚它会不会对前导码做处理。

2.3.2 帧起始定界符(SFD)—— 1字节的"开始信号"

SFD是1个字节:0xD5,二进制是11010101。注意看,最后两位是11,跟前导码的交替模式不一样。这就是一个明显的标志,告诉接收端:"前导码结束了,真正的帧头要来了!"

在FPGA里,我通常用移位寄存器来检测SFD。每收到一个字节,就把它移入8位寄存器,然后跟0xD5比较。匹配上了,就拉高一个标志信号,通知后续模块开始接收MAC地址。

2.3.3 MAC地址 —— 6字节的设备身份证

MAC地址一共6个字节,前3字节是OUI(组织唯一标识符),后3字节是设备厂商分配的。目的MAC地址是接收方的地址,源MAC地址是发送方的地址。

在FPGA实现中,地址过滤是个关键点。我一般会实现三种模式:

  • 单播模式:只接收目的MAC等于本机MAC的帧
  • 广播模式:接收目的MAC为全F的帧(FF:FF:FF:FF:FF:FF)
  • 组播模式:接收目的MAC属于特定组播组的帧

地址比较可以用LUT实现,6字节的比较在FPGA里就是48个LUT的事,延迟很小。我习惯用流水线比较,一个时钟周期比较一个字节,6个时钟周期出结果,不占资源。

2.3.4 类型/长度字段 —— 2字节的"身份标识"

这个字段有两个含义:

  • 如果值大于等于0x0600(1536),表示上层协议类型。比如0x0800是IPv4,0x0806是ARP,0x86DD是IPv6。
  • 如果值小于等于0x05DC(1500),表示数据字段的长度。

在FPGA里,我一般会把这个字段解析出来,送给上层模块做协议分发。比如检测到0x0800,就把数据送给IP处理模块;检测到0x0806,就送给ARP处理模块。

2.3.5 数据字段 —— 46到1500字节的有效载荷

数据字段最少46字节,最多1500字节。为什么最少46字节?因为以太网规定帧的最小长度是64字节(从目的MAC到FCS)。减去14字节的头部和4字节的FCS,数据最少要46字节。如果上层数据不够46字节,就要填充0到46字节。

在FPGA实现时,我一般会用一个计数器来统计接收到的数据字节数。如果帧结束时的数据长度小于46字节,就认为这是一个短帧(Runt Frame),直接丢弃。同样,如果超过1500字节,就是超长帧(Jumbo Frame),也要根据配置决定是否丢弃。

个人习惯:我通常会在MAC层实现一个"最小帧长检查"模块。接收完整个帧后,检查数据长度是否在46-1500之间。不在这个范围的,直接拉高错误标志,不往上层转发。这样可以避免上层协议栈处理异常帧。

2.3.6 帧校验序列(FCS)—— 4字节的"防伪标签"

FCS是4字节的CRC32校验值,覆盖从目的MAC到数据字段的所有内容。生成多项式是:0x04C11DB7。接收端收到帧后,用同样的多项式重新计算CRC,如果结果等于0xC704DD7B(这是IEEE 802.3规定的"残差"),就说明帧没有错误。

在FPGA里实现CRC32,我推荐用并行CRC算法。千兆以太网的数据位宽是8位,每个时钟周期处理一个字节。你可以用查找表法,或者直接用组合逻辑实现。我习惯用Xilinx的CRC硬核,或者自己写一个8位并行CRC模块,延迟只有1个时钟周期。

// 8位并行CRC32计算示例(Verilog)
// 生成多项式: 0x04C11DB7
// 初始值: 0xFFFFFFFF
// 输出异或值: 0xFFFFFFFF

wire [31:0] crc_next;
assign crc_next[0] = crc_reg[24] ^ crc_reg[30] ^ data_in[6] ^ data_in[0];
assign crc_next[1] = crc_reg[25] ^ crc_reg[31] ^ data_in[7] ^ data_in[1] ^ crc_reg[24] ^ data_in[6];
// ... 省略中间位 ...
assign crc_next[31] = crc_reg[23] ^ crc_reg[29] ^ data_in[5] ^ data_in[31];

注意:发送时,FCS字段的字节顺序是反的。比如CRC计算结果为0x12345678,在线上发送的顺序是0x78、0x56、0x34、0x12。接收端也要按这个顺序处理。我刚开始做时没注意这个字节序,结果仿真一直报CRC错误,查了两天才发现是字节序搞反了。

2.4 帧间间隔(IFG)—— 帧与帧之间的"呼吸时间"

除了帧本身,还有一个重要的概念叫帧间间隔。千兆以太网要求帧与帧之间至少有96比特时间的空闲。对于千兆速率,一个比特时间是1ns,所以IFG就是96ns。在FPGA里,我一般用一个计数器来保证发送IFG。每发送完一帧,就等待96个时钟周期(125MHz时钟下,96个周期正好96ns),然后再发送下一帧。

注意:IFG不能太短,否则接收端来不及处理上一帧。也不能太长,否则会降低链路利用率。我曾经在调试时发现,发送端IFG设成了100ns,结果对端交换机一直丢包。后来用抓包工具一看,原来是IFG超标了,交换机认为链路不稳定。改成96ns后,问题解决。

2.5 小结:FPGA实现MAC层的核心要点

好了,以太网帧结构咱们就讲到这里。总结一下FPGA实现MAC层时需要注意的几个点:

  1. 前导码和SFD:搞清楚PHY会不会处理它们,避免数据错位
  2. MAC地址过滤:实现单播、广播、组播三种模式,用流水线比较节省资源
  3. 类型/长度解析:根据值判断是协议类型还是数据长度,做协议分发
  4. 数据长度检查:确保帧长在64-1518字节之间(含头部和FCS)
  5. CRC32校验:用并行算法,注意字节序和初始值
  6. IFG控制:严格保证96比特时间的帧间隔

下一章,咱们就开始动手写RTL代码了。我会带你从零实现一个千兆以太网MAC的发送模块。到时候,这些帧结构的知识就会变成一行行实际的Verilog代码。准备好了吗?