第五章 外设接口通信,举一反三
12.千兆网口实现MDIO接口读写
千兆网口是我们日常生活中经常见到的外设接口,在后面三个例程中,我们将会一起去动手实现千兆网口实现MDIO接口读写、ARP通信协议、ICMP和UDP通信协议等,这三个例程有一定的难度,通过实际分析、动手编码、模块划分、上板调试大家可以学到很多内容,涵盖了MDIO协议、报文层层解析、层层组报、时钟上下边沿采样数据、千兆网口报文CRC32校验、IP首部校验、UDP首部校验、ARP首部校验等,相信系统性学习实践后,大家的模块划分和编码调试能力又会提高一大步。
如图1所示是豌豆开发板Artix7上千兆LAN口电路,这里选取了经典的KSZ9031RN而作为PHY的主控芯片,KSZ9031RN相比于传统的PHY芯片例如RTL8211E,既可以支持10/100/1000 Mbps网络传输速率,又通过RGMII接口和FPGA相连进行数据通信,RGMII是GMII的精简版,相比于GMII接口,RGMII在硬件上极大地节约了引脚数量,数据的收发引脚由8根变成4根,所以在1000Mbps的传输速率下,即时钟频率是125Mhz时,在时钟的上下沿同时采样数据。
图1 豌豆开发板Artix7上KSZ9031RN电路
在以太网通信中设备之间的物理层链路均由PHY芯片即物理层芯片建立。PHY芯片都有一个配置接口即MDIO接口,通过这个接口可以配置PHY芯片的工作模式并且获取PHY芯片的若干状态信息,这个过程一般性地称为以太网的自协商,在这个例程中我们实现对PHY芯片的 MDIO接口的初始化。
仅仅从硬件的角度来说,以太网接口电路主要由MAC控制器和物理层接口PHY两大部分所组成,在这个例程中MAC控制器由FPGA实现,PHY芯片则指豌豆开发板上板载的以太网芯片。
PHY在发送数据时,接收MAC发过来的数据(对PHY来说已经没有帧的概念,不管是地址、报文、校验都看成数据),把并行数据转化为串行流数据,按照物理层的编码规则把数据编码转换为模拟信号发送出去,而接收数据时的流程则相反。
同时PHY还提供了和两端设备连接的重要功能,并通过LED灯显示出自己目前的连接状态和工作状态。当我们将网口RJ45座子接入网线的时候,PHY芯片不断发出脉冲信号来检测相连端口是否有设备,它们通过标准的“语言”来交流,互相协商并确定连接速度、双工模式、是否采用流控等,一般情况下,自协商的结果是两个设备中能同时支持的最大速度和最好的双工模式。
MAC控制器和PHY物理芯片之间有一个配置接口即MDIO接口,可以配置PHY芯片的工作模式以及获取PHY芯片的若干状态信息。PHY芯片内包含一系列寄存器,用户可以通过这些寄存器配置PHY芯片的工作模式,并获取PHY芯片的若干状态信息,例如连接速率、双工模式、自协商状态等。
FPGA通过MDIO接口对 PHY芯片内部的寄存器进行配置,实际上在一般地情况下,PHY芯片在默认状态下也可以正常工作,当然下结论之前笔者也都动手测试过,在FPGA作为MAC控制器时,只需要硬件上拉低一段时间的MDIO复位引脚,再通过MDIO接口通信协议即可读出相关寄存器,从而判断是否自协商正常。
在这个例程中我们即去实现这个过程,大家可以把它看成对千兆网口的初始化,只有在PC端和FPGA端进行自协商完成后,彼此间才可以建立以太网通信,如图2所示是MDIO接口示意图,在这个例程中我们用到了MDC、MDIO、RESET三个引脚。
图2 MDIO接口示意图
MDIO接口也称之为SMI接口即串行管理接口,包括数据管理时钟LAN_MDC和数据管理输入输出LAN_MDIO两根信号线。而LAN_MDC为LAN_MDIO提供时钟,LAN_MDIO为双向数据引脚,既用于发送数据,也用于接收数据。如图3所示LAN_MDC的最大时钟不能超过400ns即最大时钟为2.5Mhz。
图3 MDIO中MDC的配置说明
如下图4所示是MDIO接口通信协议,在这个例程中需要按照图中所示的协议格式去实现MDIO底层驱动的开发,再在上游模块中实现控制层逻辑,让其控制驱动模块工作,也类似于前面例程中FLASH的driver和control两层分离模式,其中MDIO协议的帧格式具体说明如下:
1. Preamble:32位前导码,由MAC控制器发送32位逻辑"1",用来同步 PHY 芯片;
2. ST:2位帧开始信号,规定用01表示;
3. OP:2位操作码,其中读命令是10,写命令是01;
4. PHYAD:5位PHY地址,用来表示MAC控制器与哪个PHY芯片通信,所以从这里能看出一个 MAC控制器硬件上可以连接多个PHY芯片;
5. REGAD:5位寄存器地址,这里一共可以表示32种寄存器;
6. TA:2位转向,在读命令中,MDIO在此时将由MAC驱动改为PHY驱动,在第一个TA位,MDIO引脚为高阻状态,第二个TA位,PHY将MDIO引脚拉低,准备发送数据;而在写命令中,则不需要MDIO方向发生变化,MAC固定输出 2'b10即可,随后开始写入数据;
7. DATA:16位数据,在读命令中,PHY芯片将读到的对应PHYAD的REGAD寄存器的数据写到DATA中;在写命令中,MAC将要写入对应PHYAD的REGAD寄存器的值写入DATA中;
8. IDLE:空闲状态,这时候MDIO为无源驱动,处于高阻状态,通常硬件通过上拉电阻使其上拉至高电平。
图4 MDIO接口通信协议
如下图5所示是MDIO接口读时序图,图中以PHY地址为0x01,从寄存器地址0x00读出数据举例说明。整个读操作过程的MDC时钟由MAC驱动,同时MAC也驱动MDIO引脚输出前导码+帧开始+操作码+PHY 地址+寄存器地址,随后MDIO引脚切换至PHY驱动。在第一个TA位,MDIO引脚为高阻状态,第二个TA位为低电平,表示PHY芯片成功响应,并且接下来会输出16位寄存器数据;而如果第二个TA位处于高电平,PHY芯片响应失败。
读操作中需要注意的是,PHY在MDC时钟的上升沿采集数据,为保证数据的稳定传输,MAC在MDC的下降沿更新MDIO引脚的数据。而当MDIO引脚切换至PHY驱动时,MDIO数据MDC时钟的下降沿更新,MAC在MDC时钟的上升沿采集数据。同时在读操作结束后,MAC将MDIO引脚输出高阻,此时MDIO引脚的外部上拉电阻会将MDIO引脚拉高,MDIO接口重新处于空闲状态。
图5 MDIO接口读时序图
如下图6所示是MDIO接口写时序图,以PHY地址为0x01,向寄存器地址0x00写入0x1340举例说明,在整个写操作过程中, MDC时钟和MDIO引脚一直由MAC端驱动,按照MDIO接口写通信协议开始传输数据。
写操作中需要注意的是,PHY在MDC 时钟的上升沿采集数据,为保证数据的稳定传输,MAC在MDC的下降沿将数据更新至MDIO引脚。在写操作结束后,MAC将MDIO引脚输出高阻,此时MDIO引脚的外部上拉电阻会将MDIO引脚拉高,MDIO接口重新处于空闲状态。
图6 MDIO接口写时序图
细心的同学一定会发现MDIO协议非常类似于IIC和SPI协议两者的结合体,也非常有趣,一方面在工作模式上和IIC协议相似,即MDIO引脚对于FPGA端是输入输出引脚,既通过该引脚发送数据给PHY,也通过该引脚从PHY接收数据,且包含有两个引脚,一个做输入输出总线,一个做参考时钟;另一方面在数据读写上和SPI协议相似,即在时钟上升沿采集数据,在时钟下降沿更新数据,所以大家可以通过这个例程把前面IIC和SPI协议的知识再复习下,如图7所示是KSZ9031RN芯片在MDIO协议下的32位寄存器说明。
图7 KSZ9031RN芯片在MDIO协议下的32位寄存器说明
下面我们来一起读一读KSZ9031RN芯片手册,并提取一些程序设计中的核心要点,其中有些寄存器是仅可读的,有些寄存器是仅可写的,还有些寄存器是可读可写的,当然在程序设计中我们不需要把这32个寄存器全部都用上,一般来说寄存器0x00、0x01、0x1F是3个最常用的寄存器,即基本控制寄存器、基本状态寄存器、PHY控制寄存器。
如图8所示是基本控制寄存器说明,这里主要有个软复位操作,即MAC控制器可以通过MDIO协议向PHY发送软复位请求,但遗憾的是笔者在两款PHY芯片RTL8211FD和KSZ9031RN分别实验软复位请求,经测试发现RTL8211FD在大多数情况下软复位一次后,即可读取到正确的自协商结果,然而KSZ9031RN在大多数情况下软复位一次后,总是读取到错误的自协商结果。
于是第一反应就是想在控制模块做一个状态机如果软复位一次不正常再复位一次直到正常为止,但个人觉得太麻烦了且没有必要,既然软复位不一定可靠,那么我们就索性用硬件复位的方法即用拉低RESET复位引脚,经过实际测试发现硬件复位的方法可以保证每次PHY芯片都复位正常。
如图9所示是基本状态寄存器说明,用户通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断目前自协商状态,注意到寄存器的第5位为1时,代表自协商完成,为0时代表自协商未完成;寄存器的第5位为1时,代表网线的物理层连接成功,为0时代表连接失败。
如图10所示是PHY控制寄存器说明,同样地用户通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断自协商后连接速度,注意到寄存器的第6位为1时,代表千兆速度;寄存器的第5位为1时,代表百兆速度;而寄存器的第4位为1时,代表十兆速度。
图8 基本控制寄存器说明
图9 基本状态寄存器说明
图10 PHY控制寄存器说明
如表1所示是mdio_driver模块信号列表,类似于前面读写FLASH例程,把整个逻辑细化成驱动层和控制层,在这个模块中主要去实现MDIO驱动层的逻辑,大家可以再回顾下前面如图11所示的MDIO接口通信协议。如图5-26所示是MDIO接口驱动层的代码设计,这里实现了MDIO接口的底层驱动,其实难度不大,但需要说明几点应该注意的地方:
1. 提到过MDIO的时钟频率不超过2.5Mhz,模块中对50Mhz时钟进行了50分频即为1Mhz;
2. 在MDIO接口通信协议中TA是2位转向位,所以注意在读操作时候进行MDIO的切换,把MDIO的控制端从MAC控制器交给PHY芯片。在进行读操作时,转向位的第2位读到PHY芯片发送的低电平才确定PHY端响应,读取到16位寄存器值才是有效的;
3. 在MDIO接口中,时钟上升沿采集数据,在时钟下降沿更新数据,也非常类似于SPI接口的时序逻辑;
表1 mdio_driver模块信号列表
图11 MDIO接口驱动模块的代码设计
如表2所示是mdio_control模块信号列表,在这个模块里实现了对MDIO接口协议的控制层逻辑,在控制模块中我们先后读取基本状态寄存器和PHY控制寄存器,再通过两个寄存器的值判断出目前自协商后的结果,通过读取基本状态寄存器,即可判断出自协商是否完成,以太网物理层是否连接成功;通过读取PHY控制寄存器,即可判断出自协商后的连接速度是否是默认的千兆网速,整个模块用一个基本状态机即可实现。
如图12所示是MDIO接口控制模块的代码设计,在这个模块中向驱动模块发送使能信号,并发送读操作指令和读寄存器地址,依次读取从寄存器地址0x01和0x1F读出数据,判断自协商过程是否完成,其结果是否为默认的千兆网速,如果自协商错误则置位错误标志位rd_mdio_err为1,这个读取状态结束后结束标志位rd_mdio_done也置1,上游模块直接在结束标志位时判断错误标志位即可,如果自协商正确,和初始化SD卡类似通过豌豆开发板上的一颗LED二极管点亮指示。
表2 mdio_control模块信号列表
图12 MDIO接口控制模块的代码设计
如图13所示是整个例程的顶层例化文件,豌豆开发板上电后立刻拉低一秒钟的PHY芯片复位引脚,用户按下按键即触发一次读取自协商寄存器操作,开发板在读取寄存器并判断自协商正确后即把板载的LED二极管点亮,此时开发板和PC端的网口可以正常通信,且RJ45网口自带的LED也在闪烁。
图13 千兆网口实现MDIO接口读写顶层文件的例化
如图14和15所示是在线调试ILA波形图,朋友们可以看到从MDIO接口中读取基本状态寄存器值为796d,读取PHY控制寄存器值为0348,对照相应寄存器标志位是满足自协商千兆网速要求的。
图14 MDIO接口读取基本状态寄存器ILA波形图
图15 MDIO接口读取PHY控制寄存器ILA波形图