
1. 从一根网线说起为什么FPGA工程师绕不开以太网很多人第一次接触FPGA以太网通信实验时脑子里想的都是我只要把数据从A点搬到B点就行了结果打开参考设计一看MAC、PHY、MII、RMII、MDIO、前导码、帧间隙……一堆名词砸过来直接懵了。我当初也是这个状态拿着一块开发板和一份原理图盯着那颗PHY芯片的引脚定义看了整整一个下午才慢慢把以太网通信到底是怎么一回事这条线捋清楚。这篇日记主要想解决三个问题第一以太网通信在物理层和链路层到底遵循什么规则为什么要有这些规则第二MAC和PHY这两个模块各自负责什么它们之间是怎么对话的第三在FPGA里做以太网实验时哪些细节是文档里一笔带过但实际会卡住你的。适合正在做FPGA网络通信入门实验、或者已经跑通了回环但说不清楚底层原理的读者。先把一个核心认知摆在前面以太网通信不是发一串数据出去这么简单它是一套有严格时序、有帧结构、有握手规则的协议体系。你在FPGA里写的逻辑本质上是在模拟MAC控制器的行为而MAC和PHY之间的接口就是你这套逻辑和外部世界之间的唯一通道。搞不清楚这个通道的规则后面写再多代码都是碰运气。2. 以太网帧的交通规则从比特流到有意义的数据2.1 前导码与SFD接收端如何找到起点以太网在物理介质上传输的是一连串的比特流接收端面对的是源源不断的电平变化。问题来了接收端怎么知道哪一段比特是一个帧的开始答案就是前导码Preamble和帧起始定界符SFD。前导码是7个字节的0x55也就是二进制01010101重复7次。这个 pattern 的作用是让接收端的时钟恢复电路锁定发送端的时钟频率。你可以把它理解成唱歌之前先打拍子让伴奏和歌手对齐节奏。SFD是1个字节的0xD5二进制是11010101最后两位01标志着正片开始后面的就是真正的帧数据了。这里有个实际调试中容易忽略的点前导码和SFD通常是由PHY芯片或者MAC硬件自动添加和剥离的你在FPGA逻辑里一般不需要手动去拼这8个字节。但如果你用的是纯逻辑实现的软MAC那就得自己处理。我在第一次做实验时因为参考设计里PHY配置成了自动添加前导码而我的逻辑又手动加了一遍结果抓包工具看到的帧全是错的排查了大半天才发现是重复添加。2.2 帧结构拆解每个字段都在解决一个具体问题一个标准以太网帧不含VLAN标签的结构如下字段长度作用前导码7字节时钟同步SFD1字节帧起始标志目的MAC地址6字节接收方硬件地址源MAC地址6字节发送方硬件地址类型/长度2字节标识上层协议如0x0800是IP数据载荷46~1500字节实际传输的数据FCS4字节帧校验序列目的MAC地址在前、源MAC地址在后这个顺序和很多人的直觉相反。为什么要这样设计因为接收端需要第一时间判断这个帧是不是发给自己的把目的地址放在最前面可以让硬件在收到前6个字节后就立刻做过滤决策不需要等整个帧收完。这是一个典型的为硬件实现优化的设计思路。数据载荷最小46字节这个限制也很有意思。如果实际数据不足46字节需要填充到46字节。为什么要设下限因为以太网早期是共享总线的帧太短可能导致发送端还没发完接收端就已经认为帧结束了碰撞检测机制会失效。虽然现在基本都是交换式全双工但这个最小帧长的规则一直保留了下来。FCS用的是CRC-32校验多项式是0x04C11DB7。在FPGA里实现CRC-32有两种常见做法一种是查表法用Block RAM存256个表项每个时钟处理1字节另一种是线性反馈移位寄存器LFSR法每个时钟处理1比特逻辑资源少但吞吐率低。如果要做千兆以太网建议用查表法或者并行CRC计算否则时钟频率跟不上。2.3 帧间隙IFG给接收端留一口喘息的时间以太网规定两个帧之间必须至少有12个字节时间的间隔叫做帧间隙Inter-Frame Gap, IFG。在千兆速率下12个字节时间就是96ns。这个规则的存在是因为接收端的PHY需要时间做时钟恢复、帧结束检测和状态复位。如果没有IFG连续不断的帧会让接收端来不及处理。在FPGA发送逻辑里IFG通常由MAC控制器自动插入。但如果你是自己写的发送状态机一定要记得在帧与帧之间留够这个间隔。我见过有同学写的发送模块背靠背连续发帧结果接收端丢帧严重就是因为忽略了IFG。3. MAC与PHY的分工谁负责什么边界在哪里3.1 用寄信来理解MAC和PHY的角色把以太网通信类比成寄信MAC负责写信封、贴邮票、决定这封信寄给谁PHY负责把信装进信封、送到邮局、通过物理线路发出去。MAC处理的是数字逻辑层面的帧组装和解析PHY处理的是模拟信号层面的电平转换和线路编码。具体来说MAC层Media Access Control的核心职责包括帧的封装与解封装、地址过滤、CRC校验的生成与验证、流量控制PAUSE帧、以及和上层协议对接。PHY层Physical Layer的核心职责包括并行数据与串行比特流的转换、线路编码如4B/5B、8B/10B、时钟恢复、信号驱动与接收、以及自协商Auto-Negotiation。在FPGA实验中MAC功能通常由FPGA内部逻辑实现或者用厂商提供的MAC IP核PHY则是开发板上那颗独立的芯片。两者之间通过MII系列接口连接。3.2 MII、RMII、GMII、RGMII接口选型的核心考量MAC和PHY之间的接口有好几种标准选哪种取决于你的速率需求和FPGA引脚资源接口类型速率数据位宽时钟频率引脚数约MII10/100Mbps4位25MHz/2.5MHz16RMII10/100Mbps2位50MHz8GMII1000Mbps8位125MHz24RGMII1000Mbps4位125MHz双沿12MII是最基础的标准发送和接收各用4位数据线外加时钟、使能、错误指示等控制信号。RMII是MII的简化版数据线从4位减到2位但时钟频率提高到50MHz引脚数少了一半适合引脚紧张的场合。GMII是千兆版本的MII8位数据线125MHz时钟。RGMII则在GMII基础上把数据线减到4位用双沿采样DDR来维持吞吐率引脚数大幅减少。选型的核心逻辑是速率要求决定基本接口类型引脚资源决定是否需要用简化版本。如果你的FPGA引脚富裕MII或GMII最省心时序余量大如果引脚紧张RMII或RGMII是必然选择但时序约束会更严格。我在实际项目里用过RGMII踩过一个典型的坑RGMII的发送端需要输出时钟TX_CLK和数据TXD[3:0]之间有一个约2ns的相位偏移这样接收端才能在时钟沿正确采样数据。很多PHY芯片内部可以配置这个延迟但如果配置不对就会出现数据采样错误。解决办法是仔细阅读PHY芯片的数据手册确认延迟是加在PHY侧还是FPGA侧然后相应地在FPGA的时序约束里做处理。3.3 MDIO管理接口MAC如何遥控PHY除了数据通道MAC和PHY之间还有一条管理通道叫做MDIOManagement Data Input/Output。这是一条两线接口MDC是时钟MDIO是双向数据线。MAC通过MDIO读写PHY内部的寄存器实现速率协商、双工模式设置、链路状态读取等功能。MDIO的帧格式是固定的前导码32个1 起始码01 操作码读10/写01 PHY地址5位 寄存器地址5位 数据16位。在FPGA里实现MDIO控制器不难本质上就是一个移位状态机。但要注意MDC的时钟频率不能超过2.5MHz而且MDIO是双向线需要正确处理三态缓冲。很多初学者会忽略MDIO的作用觉得我只要把数据通道跑通就行了。但实际上如果PHY没有通过MDIO正确配置数据通道根本不可能正常工作。比如PHY上电默认可能是10Mbps半双工模式你不通过MDIO把它设成1000Mbps全双工那通信速率和稳定性都会有问题。4. 上电之后发生了什么自协商与链路建立的完整过程4.1 自协商的本质双方在谈判当网线插入开发板的RJ45接口后PHY芯片并不会立刻开始传数据而是先进入**自协商Auto-Negotiation**阶段。这个过程是双方PHY通过发送一种叫做快速链路脉冲FLP的特殊信号来交换各自支持的能力集最终协商出一个双方都支持的最高速率和双工模式。FLP的编码方式比较特殊它把16位的能力信息编码成33个脉冲位置。这16位里包含了速率10/100/1000Mbps、双工模式半双工/全双工、以及是否支持PAUSE帧等信息。协商的规则是双方取交集然后选最高优先级。优先级从高到低依次是1000M全双工、1000M半双工、100M全双工、100M半双工、10M全双工、10M半双工。这里有个实际调试中的经验如果你的FPGA逻辑只支持100M但PHY自协商到了1000M通信就会失败。解决办法有两种一是通过MDIO强制PHY工作在100M模式关闭自协商二是在FPGA逻辑里实现千兆MAC。前者简单粗暴后者工作量大但更灵活。我在做第一个以太网实验时就是通过MDIO把PHY强制设成100M全双工先把数据通道跑通再慢慢研究千兆的实现。4.2 链路状态检测怎么知道网线插好了PHY芯片有一个链路状态寄存器通常在寄存器地址0x01其中的Link Status位会实时反映链路是否建立。FPGA可以通过MDIO轮询这个位或者使用PHY输出的Link Status引脚如果有的话来检测链路状态。在FPGA逻辑里一个常见的做法是在链路未建立时复位MAC发送逻辑链路建立后再释放复位。这样可以避免在链路不稳定时发送无效数据。另外链路状态变化时比如网线被拔掉PHY通常会产生一个中断信号FPGA可以利用这个中断来及时响应。4.3 从复位到第一个帧发出完整时序链路把上面的过程串起来一个完整的以太网上电启动流程大致是这样的系统上电FPGA和PHY各自完成内部复位。FPGA通过MDIO配置PHY的工作模式速率、双工、是否开启自协商等。PHY开始自协商与对端交换能力信息。自协商完成链路建立PHY的Link Status置位。FPGA检测到链路建立释放MAC逻辑的复位。MAC开始可以发送和接收帧。发送第一个帧时MAC按照帧格式组装数据通过MII/RGMII接口送给PHY。PHY对数据进行线路编码通过网线发送出去。这个流程看起来简单但每一步都有细节。比如第2步MDIO配置的时序必须符合标准MDC频率不能超标第5步释放复位的时机要对太早可能在链路还没稳定时就发数据太晚则影响通信效率。5. 实验中的那些坑从抓包异常到时钟约束5.1 抓包工具看到的帧全是错的排查思路我第一次用抓包工具观察FPGA发出的帧时发现所有帧的FCS都是错的抓包工具直接标红。排查过程如下首先确认FPGA发送的帧结构是否正确。用逻辑分析仪抓MII接口上的数据逐字节对照帧格式发现目的MAC、源MAC、类型字段都对数据载荷也对。那问题只能出在FCS上。然后检查CRC-32的实现。我用的是查表法表项是根据标准多项式生成的。仔细核对后发现CRC计算的初始值设错了。以太网CRC-32的初始值是0xFFFFFFFF而我设成了0x00000000。改过来之后FCS就对了。这个坑的教训是CRC这类校验算法初始值、多项式、输入输出是否反转这三个参数必须和标准完全一致差一个比特结果就全错。5.2 接收端丢帧可能是IFG不够另一个常见问题是接收端丢帧。我用FPGA连续发送1000个帧接收端只收到了900多个。排查后发现发送逻辑在帧与帧之间没有插入足够的IFG。在千兆模式下IFG是12个字节时间也就是96ns。我的发送状态机在发完一个帧后只等了几个时钟周期就开始发下一个帧远不够96ns。修改状态机在帧结束后插入一个计数器确保等待时间满足IFG要求后丢帧问题就解决了。这里有个经验IFG的计算要基于实际的数据速率。100M模式下12个字节时间是960ns10M模式下是9.6us。如果你的FPGA逻辑要支持多种速率IFG的等待时间需要根据当前速率动态调整。5.3 时序约束RGMII的建立保持时间怎么算RGMII接口的时序约束是另一个容易出问题的地方。因为RGMII用双沿采样时钟频率125MHz周期8ns每个沿都要正确采样数据时序余量很小。以发送方向为例FPGA输出TX_CLK和TXD[3:0]。PHY在TX_CLK的上升沿采样TXD[0]和TXD[2]下降沿采样TXD[1]和TXD[3]。为了保证PHY能正确采样TXD相对于TX_CLK需要有一个延迟。这个延迟通常由PHY内部提供也可以通过FPGA的IOB延迟单元实现。在时序约束里需要设置set_output_delay约束TXD相对于TX_CLK的偏移。具体数值要查PHY数据手册通常在1.5ns到2.5ns之间。如果约束设得不对综合出来的时序可能不满足导致通信不稳定。我的建议是先用PHY内部延迟把时序约束设宽松一些确保功能跑通然后再逐步收紧约束观察通信质量。如果PHY内部延迟可调可以通过MDIO尝试不同的延迟值找到最稳定的配置。6. 从实验到产品还需要补哪些课6.1 从MII到RGMII接口切换的注意事项如果你已经用MII跑通了实验想升级到RGMII做千兆通信需要注意几点第一RGMII的数据位宽从4位变成4位但时钟翻倍发送和接收逻辑的状态机需要重新设计第二RGMII的时序约束比MII严格得多必须做静态时序分析第三PHY的配置寄存器需要重新设置确保工作在RGMII模式。6.2 硬件设计上的配合PHY外围电路不能马虎FPGA逻辑写得再好如果PHY外围电路有问题通信照样不稳定。几个关键点PHY的电源滤波要做好模拟电源和数字电源要分开晶振的精度要够以太网对时钟精度要求是±50ppm以内RJ45接口的变压器中心抽头要正确连接否则信号完整性会出问题。6.3 调试手段没有抓包工具怎么办不是每个人都有专业的以太网抓包工具。在没有抓包工具的情况下可以用以下方法调试第一用FPGA内部的ILA集成逻辑分析仪抓MII接口上的数据直接看帧内容第二在接收逻辑里加计数器统计收到的帧数、错误帧数、CRC错误数等第三用LED指示链路状态和通信状态快速判断问题出在链路层还是数据层。我个人最常用的方法是ILA加计数器。ILA可以看到每一帧的详细内容计数器可以快速定位是偶发问题还是必现问题。两者结合大部分问题都能定位到。以太网通信实验是FPGA学习中的一个经典项目它涉及的知识面很广数字逻辑设计、时序约束、协议理解、硬件调试。把这一套跑通对FPGA工程师的成长非常有帮助。我在这个过程中最大的体会是不要急着写代码先把协议规则和硬件接口搞清楚后面会省很多时间。很多看似复杂的通信问题根源往往是对某个基础规则的理解不到位。