ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FPGA直接驱动PHY芯片:RGMII与MDIO接口实现以太网收发全解析

FPGA直接驱动PHY芯片:RGMII与MDIO接口实现以太网收发全解析 简介面向FPGA开发者尤其是需要直接驱动以太网PHY芯片的工程师。参考代码以AR8031为例提供基于Altera EP3C40系列FPGA的完整工程方案可通过寄存器配置快速迁移至其他PHY芯片解决MAC层与PHY芯片对接的底层驱动问题。压缩包共2000个文件以Quartus工程文件为主包含896个cdb、893个hdb工程数据库文件315个v及81个tdf等HDL源码文件另有qip、qsf等工程配置与sdc时序约束文件整体大小19.88MB便于按需查找和修改。已有1892人学习下载。代码中涉及ddio_in、ddio_out等DDR接口模块可支撑RGMII收发时序处理readme与txt文件提供寄存器配置说明便于针对不同PHY调整适配。工程文件组织完整还包含编译与仿真过程记录适合中高级FPGA开发者作为以太网通信底层驱动的直接参考能够有效缩短PHY芯片驱动调试周期。 做了这么多年FPGA通信开发一个特别常见的现象是工程师拿到RTL8211或者KSZ9031这类PHY芯片心里默认“FPGA连着PHY、代码一写网口自然就能通”。真上手之后才发现PHY根本不帮你干MAC层的活它不认识前导码不计算CRC也不会自动把你的用户数据打包成标准以太网帧。所谓“FPGA直接驱动PHY”本质上是让你在FPGA内部把MAC层的逻辑全部补完再通过RGMII、MDIO这两条物理通道和PHY打交道。这篇文章就是针对这套需求整理的参考代码思路从接口时序、寄存器操作、状态机结构到实际调试中的坑一次性说清楚。这篇内容适合刚接触以太网PHY的FPGA开发者也适合那些打算把视觉数据、高速采集数据直接用FPGA打包上网络、不想在中间塞一颗MCU的工程师。核心目标只有一个让你能在自己的板子上用FPGA把PHY芯片驱动起来跑通真正的以太网收发。1. 为什么会有“FPGA直接驱动PHY”这种需求1.1 没有CPU介入的纯数据通路很多工程师第一反应是以太网不是有现成的MAC控制器吗单片机、ARM、Zynq的PS端动不动就自带千兆MAC何必让FPGA去造轮子。但对一部分应用来说还真没有别的选择。最典型的是视觉驱动、高速数据采集这类场景。比如一个工业相机通过MIPI接口进来一帧图像数据是持续流式的延迟要求是微秒级。如果你走CPU转发数据要先进DDR、再被操作系统调度、再由协议栈打包这个延迟抖动在硬实时系统里很难接受。更麻烦的是很多嵌入式平台根本没有独立的MAC控制器或者MAC已经和某种USB/PCIe桥绑死了。这时候FPGA的价值就出来了数据从传感器进来直接过FIFO你用自己的状态机把数据装进以太网帧从RGMII口推给PHY芯片一条纯硬件路径完成整个数据搬运。CPU如果有的话只负责配置寄存器不做数据面的事。这也是FPGA驱动PHY最常见的动因。1.2 对时延和时序确定性要求高的场景还有一种需求是低时延控制。比如车载以太网里的传感器数据回传、运动控制里的周期性同步报文这类场景对数据包发送时刻的确定性要求极高。如果你用的是Linux加普通网卡网络栈的调度抖动是纳秒级起步的一旦系统繁忙抖动量会非常难看。而在FPGA里你可以用一个定时器触发发送状态机精度到时钟周期级别。所以“FPGA直接驱动PHY”不是闲得无聊的重复造轮子它是在没有合适MAC方案、或者对延迟有硬性要求时最务实的做法。理解了这一点后面看代码就知道每个模块为什么长这样了。2. RGMII和MDIO这两条“对话通道”必须门儿清2.1 RGMII上的源同步双沿数据FPGA和PHY之间的数据接口最常用的是RGMII。它把GMII的8位数据线砍成了4位通过DDR双沿采样在125MHz时钟下仍然能跑满千兆。引脚上你只需要关心TXD[3:0]、TXC、TX_CTL、RXD[3:0]、RXC、RX_CTL一共12根信号线。发送方向上TXC由FPGA产生TXD[3:0]和TX_CTL都和它同步。时钟上升沿送出字节的高4位bit7到bit4下降沿送出低4位bit3到bit0。TX_CTL更特殊上升沿对应TX_EN下降沿对应TX_EN和TX_ER的异或。对一般只做正常收发的设计TX_ER拉低即可这样下降沿的TX_CTL等于TX_EN的反相。接收方向则是PHY把RXC恢复好提供给FPGA数据同样在双沿上。要注意RGMII是源同步接口RXC和数据是同步的。FPGA直接用RXC采样即可不需要自己再做时钟恢复。但这里有一个屡见不鲜的坑RXC和数据之间可能有相位偏差不同PHY的RGMII输出延迟特性还不一样后面调试部分我会专门说。第三点必须记住RGMII只是物理接口不代表MAC功能。PHY在发送方向上做的事情是把RGMIII上的并行数据串行化、编码、加到差分线上去在接收方向上则是从差分线恢复数据、解码、转成RGMII交给FPGA。协议帧的内容、校验、组装全部是FPGA自己的事。2.2 MDIO管理通道的读写时序拆解MDIO是管理接口用于配置和读取PHY芯片的内部寄存器。它只有两根线MDC时钟和MDIO数据。很多FPGA开发者习惯性地把MDIO当普通SPI写实际它有自己的帧格式看下面的表格字段位数说明PREAMBLE3232个连续1建立同步ST2固定01标识这是MDIO帧OP2写为01读为10PHYAD5PHY芯片地址由硬件引脚决定REGAD5寄存器地址TA2读时第一拍高阻、第二拍PHY拉低写时固定10DATA16读写的数据MDC时钟最高建议不超过2.5MHz保险做法是用FPGA主时钟分频产生越低越稳。MDIO是半双工双向线读操作时在TA阶段要把输出置为高阻然后采样PHY返回的数据。写操作则全程由FPGA驱动。一个很关键的实操细节PHY地址不是固定的。RTL8211系列常见是0x01KSZ9031是0x07但具体要看原理图上PHYAD引脚的上下拉。最省事的办法是在FPGA里写一个MDIO扫描逻辑上电后对0到31的地址逐个尝试读取PHY ID寄存器哪个能读回合理ID就用哪个。2.3 上电后PHY寄存器里最先要确认的几件事驱动代码跑起来第一件事不是发数据而是把PHY状态搞清楚。寄存器0BCR是控制寄存器bit15是软件复位置1后写寄存器的动作本身就会触发复位复位完成后这位自动清零。bit12是自协商使能bit9是重启自协商。一般初始化顺序就是读BCR - 置bit15复位 - 等待复位完成 - 确认bit12置1 - 置bit9重启自协商。寄存器1BSR是状态寄存器这里有两个非常容易误判的位。bit5表示自协商完成bit2表示链路状态。坑在于很多PHY的bit2是锁存型的链路断掉之后不会自动变0必须先读一次寄存器1才能刷新。如果你调试时发现链路状态一直显示up但网线明明拔了八成就是这个原因。寄存器2和3是PHY ID读取它们可以快速确认MDIO通路是否正常。寄存器4、5是自协商能力通告。对直接用自协商模式的参考代码来说这几个寄存器就够用了。厂商私有寄存器一般不用碰除非你要调RGMII延迟、LED配置这些特殊功能。3. FPGA侧各功能模块的拆分与参考代码设计3.1 时钟树设计125M、25M、2.5M和延迟参考时钟FPGA驱动RGMII时钟是整个设计的骨架。千兆模式下TXC必须由FPGA给PHY提供125MHz百兆是25MHz十兆是2.5MHz。如果你的代码只跑千兆时钟最好办了一个125MHz有源晶振或者用25MHz晶振经过PLL倍频都行。我的建议是如果板上有25MHz的PHY参考晶振FPGA也接一份到全局时钟引脚然后用PLL/MMCM同时生成125MHzRGMII发送和2.5MHz或者12.5MHzMDC分频用。注意125MHz要经过BUFG进全局时钟网络ODDR原语输出的TXC本身可以直接驱动PHY不需要额外BUFG。也可以直接使用ODDR把125MHz的上升沿和下降沿都输出到TXC保证和TXD的时序一致。还有一路时钟容易被忽略接收侧。RXC是PHY恢复出来的时钟必须接在FPGA的时钟专用引脚上通过BUFG进全局网络。很多代码里接收数据用组合逻辑打拍等到时序收敛不过关才知道RXC进不了BUFG有多痛苦。另外如果你用IDELAYE2做接收相位调整还需要一个200MHz左右的参考时钟一般也从PLL出。3.2 发送链路MAC层数据到RGMII引脚的完整拼装发送链路是整个参考代码里工作量最大的一块。FPGA内部需要有一个“MAC发送”逻辑从用户接口拿到要发的数据包组前缀、补CRC、转成RGMII信号。用户接口我建议用最简单的“请求-应答”模式用户模块给出包长度和数据MAC发送状态机负责读数据、拼帧、发完回一个done脉冲比AXI-Stream等总线结构简单很多适合做纯数据搬运。状态机的顺序大致是空闲 - 发7字节前导码0x55 - 发1字节SFD 0xD5 - 发目标MAC和源MAC共12字节 - 发2字节长度/类型 - 发用户数据 - 如果数据不到46字节则填充到46 - 发4字节CRC - 回空闲。这里有两个细节值得强调。第一以太网帧最小64字节不含前导码和SFD。所以数据不足46字节时必须填充否则对端交换机会直接丢包。第二CRC的计算范围从目标MAC开始到数据末字节结束不包含前导码和填充逻辑产生的CRC本身。经典并行CRC32模块很容易找到但要注意输入字节序是按帧的先后顺序来的初始值为0xFFFFFFFF输出结果要按位取反然后先发CRC的高字节还是低字节要看你的数据宽度这一块建议对照参考代码仔细核错一个bit整个帧就废了。拼装好的8位数据流最终要通过ODDR等原语转换成RGMII的双沿输出。Xilinx 7系列里典型的写法是这样ODDR #(.DDR_CLK_EDGE(SAME_EDGE)) oddr_txd0 ( .Q(txd[0]), .C(txc_125m), .CE(1b1), .D1(gmii_txd[0]), // 上升沿输出byte bit7 .D2(gmii_txd[4]), // 下降沿输出byte bit3 .R(1b0), .S(1b0) );TX_CTL同理D1接TX_END2接TX_EN与TX_ER的异或。假如你发现用组合逻辑直接在时钟双沿改变TXD也能通大概率是运气好或者PCB走线短时序裕量很小换一个片子就可能出问题不建议那么写。3.3 接收链路双沿采样、字节对齐和CRC校验职责接收方向相对发送简单但坑也不少。FPGA用RXC上升沿采低4位下降沿采高4位拼成8位数据。核心代码可以是两个寄存器组一个用上升沿打拍一个用下降沿打拍。接收侧的RX_CTL也需要同样处理上升沿采的是RX_DV下降沿采的是RX_DV与RX_ER的异或。根据RGMII标准上升沿的RX_CTL就是数据有效标志下降沿的信号只在报错误时置位。你可以只关心上升沿采到的那个bit作为后续字节有效标志。字节对齐的问题是很多新手会忽略的。因为RGMII每拍只有4比特双沿采完拼成8比特之后理论上第一个字节就是前导码的0x55第二个之后的字节怎么拼取决于你从哪一拍开始拼。前导码加SFD的作用就是定位同步发送端的SFD是0xD5和之前7个0x55不同接收端检测到0x55后面跟着0xD5就知道后面是MAC帧的起始位置。如果你的代码里把高低4bit拼反了你会看到0x55变成了0x55但顺序不对或者0xD5变0x5D。这是最容易定位的表象之一。CRC校验在接收端不是必须做的。如果你是为了做数据直通转发可以先不管CRC对错专注把数据流恢复出来。等链路稳定之后再加一个CRC检查模块用来判断收到的帧是否完整。这样分层调试比一上来就全做通要现实得多。3.4 MDIO状态机示例代码与关键参数MDIO驱动的状态机思路很简单就是按帧格式逐bit拼。下面这段是我习惯用的写操作核心结构读操作大同小异区别主要在TA阶段和方向切换。localparam MDIO_IDLE 4d0; localparam MDIO_PRE 4d1; localparam MDIO_ST 4d2; localparam MDIO_OP 4d3; localparam MDIO_PA 4d4; localparam MDIO_RA 4d5; localparam MDIO_TA 4d6; localparam MDIO_DATA 4d7; localparam MDIO_DONE 4d8; // 写操作示例state在PRE时输出32个1 // ST阶段输出2b01OP阶段输出2b01 // PA阶段按bit依次输出PHYAD[4:0]MSB先发 // RA阶段输出REGAD[4:0] // TA阶段输出2b10 // DATA阶段按bit输出wr_data[15:0]。MDC时钟生成如果你有125MHz直接分频50得到2.5MHz注意MDC高低电平时间要对称。一个常见的小坑是进行MDIO操作时MDC的边沿和MDIO数据的变化时刻需要满足建立保持时间简单的做法是让MDIO在MDC低电平期间改变在MDC上升沿被PHY采样。关于MDIO读操作还有一个经验读回来的数据如果第一次读是0xFFFF第二次读是正常值通常说明MDIO时序边界很紧张或者PHY还处于复位状态没准备好。建议上电后先做一次复位等待再操作寄存器。4. 调试实录link up了但收不到数据问题出在哪4.1 第一轮排查RX_CLK和数据采样的相位窗口最常见的现象是PHY自协商成功BSR寄存器显示链路正常但FPGA发出去的包对端抓包软件一个都看不到。先把范围缩小。这时候不要急着量差分信号也别怀疑PHY芯片坏了。我的习惯是先在PHY的寄存器0里把bit14loopback置1这样PHY会把发送侧的数据直接环回接收侧。如果FPGA发出去的帧自己能够原样收回来说明RGMII收发链路和时钟基本是通的。这个测试不经过网线可以屏蔽物理层的干扰。如果环回都不通重点检查接收时钟相位。不同PHY的RGMII输出延迟策略不一样有的PHY输出的RXC领先数据有的则是RXC和数据对齐。你可以在FPGA里对RXD打一拍或者用IDELAYE2做延迟扫描尝试不同的延迟值找到能稳定采样的窗口。如果嫌麻烦先检查PCB上PHY的RXDLY引脚有没有按要求接上拉或下拉这个引脚通常决定了PHY是否在RXC上自动叠加约2ns延迟。调完之后用环回测试重新验证。4.2 第二轮排查以太网帧最小长度和填充逻辑环回测试通过之后接下来大概率栽在协议帧的完整度上。用FPGA发ARP请求是最快的验证方式因为ARP请求不需要IP校验和做起来最简单。但如果你发的帧凑不够64字节交换机和PC网卡会直接丢弃Wireshark里什么都看不到。我当时排过一个案例帧内容看起来都对前导码有、MAC地址正确、CRC也算对但电脑就是收不到。后来把抓包数据导出hex仔细看才发现整帧只有42字节缺了填充。很多PHY和交换机会把这种短帧当成残帧丢掉。解决方式是发送状态机里加一个判断load长度小于46字节时自动发送0x00填充到46字节再算CRC。这个逻辑必须有且CRC的计算要包含填充字节。另一个容易踩的是帧间隙。以太网规定两个帧之间至少要有96比特时间的间隔。如果你的发送FIFO连续不断地把数据推出去一点空隙都不留对端同样会出问题。在状态机里一帧发完回IDLE后至少要等待一个完整的帧间隙再开始下一帧。4.3 第三轮排查CRC计算和字节序问题环回和填充都对了还是抓不到包第三轮重点看CRC。CRC32用软件算很好写用Verilog实现并行版本也简单但字节序问题很容易出错。以太网帧里CRC的32位结果在线上是先发高位还是先发低位如果你的设计是按字节为单位组帧的那么每个字节内部是低位在前发送但整个字节序列是从帧首到帧尾顺序发送。很多人的并行CRC模块输入输出位序和这个不匹配导致算出来的CRC和真实值差一个bit反转甚至字节反转。我的做法是先用一个已知正确的以太网帧样例比如ARP请求包的hex把CRC字段去掉用FPGA算一遍和数据包里带的CRC对比。不对就调输入输出的位序组合一般两三次就能试对。这种问题最烦人因为硬件逻辑看着都对但结果就是不对。4.4 能大大提高效率的调试工具和方法调试以太网驱动强推两个工具Wireshark加一个支持混杂模式的USB网卡以及逻辑分析仪。前者用来验证FPGA发出去的包对端能不能收到、内容对不对后者用来抓RGMII的12根信号线看FPGA侧波形。用Wireshark抓包时注意PC网卡可能自动丢弃校验和的包。在Wireshark的“校验和”列如果显示incorrect其实不代表物理层收到的是错的可能是网卡卸载了校验和计算抓包软件拿不到原始校验和。这种情况下别紧张重点是看帧结构、长度、MAC地址这些信息。逻辑分析仪至少要能抓200MHz以上的信号RXC和TXC是125MHz采样率不够会看到一堆假波形。抓的时候把PHY的MDC和MDIO也一起抓到能直接看到寄存器读写是否符合预期。5. 给直接上手的开发者的几条落地建议5.1 最小验证系统的硬件连接如果你是从零开始画板子或者用开发板验证几个关键硬件点确认好能少走很多弯路。PHY芯片的复位信号最好接到FPGA的GPIO上不要和系统复位简单并联这样你可以用状态机精确控制PHY的上电复位时序避免掉电瞬间的毛刺。PHYAD引脚决定MDIO地址上电复位时被锁存。画板时要通过上下拉电阻把它固定到一个确定的地址并在代码注释里写上调试时就不用逐地址扫描了。RGMII接口的TXC和RXC走线尽量等长四根数据线要和对应时钟保持等长关系否则高速下时序收敛会很吃力。供电方面PHY的数字和模拟电源往往有好几路电源去耦电容不能省。复位时序是硬约束电源稳定后复位拉低时间至少满足芯片手册要求常见是10ms以上。我给的状态机会让PHY复位拉低100ms再释放然后等待150ms再做MDIO配置实测很稳。5.2 驱动代码的层次化设计思路参考代码不建议全部塞在一个顶层文件里按照功能拆成四块最舒服一是时钟模块负责PLL、BUFG、ODDR/IDELAY例化二是MDIO控制模块对上提供一个简单的寄存器读写接口对下产生MDC和MDIO时序三是MAC发送模块输入用户数据和长度输出RGMII发送信号四是MAC接收模块输入RGMII接收信号输出恢复的帧数据。顶层只做例化和时钟分配。用户接口尽量简单比如发送侧就是一个数据fifofifo非空就开始拼帧接收侧就是一个解帧模块把目标MAC、源MAC、协议类型和数据分别存到不同寄存器里。这样每部分都能单独仿真验证最后联调时才不会四处救火。5.3 进阶方向从能通到好用把基础的收发跑通之后后续还有很多可以扩展的方向。比如加入ARP和ICMP响应逻辑让PC能够ping通FPGA这会涉及IP层的处理。再比如加入UDP协议栈把采集的数据封装成UDP包发送上位机直接用Wireshark或者自写软件接收。这些都是在现有MAC层代码之上叠加逻辑工作量可控。如果做车载以太网或者工业控制场景还可以进一步研究IEEE 1588精确时间同步、TSN时间敏感网络这些应用。这些技术对PHY芯片本身没什么额外要求主要靠FPGA内部的逻辑实现驱动参考代码的框架依然可以复用。在我实际的项目经验里这套“先通链路、再通协议、最后做应用”的路径是最稳的。尤其是链路阶段寄存器能读、环回能过、外部能抓包三者都满足之后后面出问题的概率就很小了。本文还有配套的精品资源点击获取
返回列表