第46章:MDIO接口读写测试实验

从这一章开始,我们进入以太网实战系列。第一个实验不着急收发网络数据,而是先搞定一个更基础的问题:FPGA 怎么配置和"询问"以太网 PHY 芯片? 答案就是本章的主角——MDIO 接口。

本章在讲什么?(先看这个)

本章要做的事情很单纯:

  1. 弄懂 MDIO 接口是什么、读写时序长什么样;
  2. 认识开发板上的千兆 PHY 芯片 YT8531,知道它的 PHY 地址和几个关键寄存器;
  3. 用 Verilog 写一个 MDIO 驱动模块 + 控制模块,实现:触摸按键触发软复位、LED 灯实时指示网口协商速率

最终效果:开发板连上网线后,LED0 亮 = 10Mbps,LED1 亮 = 100Mbps,两个都亮 = 1000Mbps,两个都灭 = 自协商失败。

核心概念拆解

46.1 以太网与 PHY 芯片:先建立大局观

以太网(Ethernet)是局域网里应用最广的通信协议标准,最早由 Xerox 创建,后来被 IEEE 采纳为 802.3 标准。按速率分为标准以太网(10M)、快速以太网(100M)、千兆以太网(1000M),如今万兆也已普及。

我们开发板用的是最常见的 RJ45 接口(俗称"水晶头")。有个细节值得注意:

  • 10M/100M 模式:只用到 1、2、3、6 四根线(1/2 发送,3/6 接收);
  • 1000M 模式:8 根线全部用上,且都是双向的。

图46.1.2 RJ45插座10M/100M接口定义

从硬件角度看,以太网接口电路由两大块构成:

  • MAC(媒体访问控制)控制器:本实验中由 FPGA 实现;
  • PHY(物理层)芯片:板载的 YT8531/YT8511。

PHY 的作用可以打个比方:MAC 交给他的是"平行数据"(不 管 帧 格 式),PHY 负责把并行数据转成串行流,再按物理层编码规则变成模拟信号从网线发出去;接收时反着来。另外,网线一插上,PHY 会和对端设备互相发脉冲"聊天",协商出双方都支持的最高速率和双工模式——这就是自协商(Auto Negotiation)

46.2 MDIO 接口:MAC 和 PHY 之间的"对讲机"

PHY 芯片内部有一堆寄存器,用来配置工作模式、查询连接状态。FPGA 读写这些寄存器的通道,就是 MDIO 接口(也叫 SMI 接口,串行管理接口)。

图46.1.4 MDIO接口示意图

它只有两根线:

信号 作用 关键约束
ETH_MDC 管理时钟,由 MAC 提供 最高 12.5MHz
ETH_MDIO 双向数据线 空闲时靠外部上拉电阻保持高电平

46.3 MDIO 通信协议:一帧数据的"解剖"

MDIO 每次读写的帧结构如下:

图46.1.5 MDIO接口通信协议

字段 位数 说明
Preamble 32 前导码,32 位逻辑"1",用于同步 PHY
ST 2 帧开始信号,固定 01
OP 2 操作码:读=10,写=01
PHYAD 5 PHY 芯片地址(一个 MAC 可挂多个 PHY)
REGAD 5 寄存器地址(最多 32 个寄存器)
TA 2 转向位,读操作时数据线"换主人"
DATA 16 读出或写入的数据

最需要理解的字段是 TA(Turnaround)

  • 写操作:数据线方向不变,MAC 固定输出 2'b10,随后继续写数据;
  • 读操作:第一个 TA 位 MDIO 置为高阻,第二个 TA 位由 PHY 拉低表示"我听到了,准备发数据"。如果第二位是高电平,说明 PHY 没应答——大概率是 PHY 地址错了或者时序有问题。

还有一条贯穿全程的采样规则:PHY 在 MDC 上升沿采样,所以 MAC 要在 MDC 下降沿更新 MDIO 数据;读操作时反过来,MAC 在上升沿采样 PHY 输出的数据。

图46.1.6 MDIO接口读时序图

图46.1.7 MDIO接口写时序图

46.4 YT8531 芯片:PHY 地址与关键寄存器

PHY 地址由 PHYAD[2:0] 三个引脚的上下拉决定。开发板上只有 PHYAD2 接上拉,PHYAD1/PHYAD0 接下拉,所以地址是 5'h04

图46.1.9 PHY地址原理图

复位有两种方式:

  1. 硬件复位:PHY_RST_N 引脚拉低持续 10ms;
  2. 软件复位:向寄存器 0x00 的 Bit[15] 写 1,完成后自动清零。

本实验用到三个寄存器:

寄存器 地址 关键位
BCR(基本控制) 0x00 Bit[15] 软复位;Bit[12] 自协商使能
BSR(基本状态) 0x01 Bit[5] 自协商完成;Bit[2] 连接状态
PHYSR(特定状态) 0x11 Bit[15:14] 连接速度:10=1000M,01=100M,00=10M

注意:原书说 YT8531"共有 22 位寄存器",结合上下文应理解为寄存器地址位宽等细节,实际以芯片手册为准——这也是提醒大家,芯片寄存器细节永远要查数据手册。

46.4 程序设计:状态机搞定 MDIO 读写

总体架构

系统分为两个核心模块:

图46.4.1 MDIO读写测试实验系统架构图

  • mdio_dri(MDIO 驱动模块):按协议时序直接驱动 ETH_MDC/ETH_MDIO 两根线;
  • mdio_ctrl(MDIO 控制模块):定时轮询 PHY 状态寄存器,把速率显示到 LED;检测到触摸按键时发起软复位。

mdio_dri:六状态三段式状态机

状态跳转关系如下:

st_idle --op_exec=1--> st_pre(32位前导码) --> st_start(ST+OP)
        --> st_addr(PHY地址+寄存器地址) --写--> st_wr_data(TA+写数据)
                                        --读--> st_rd_data(TA+读数据)
        --> 回到 st_idle

要点 1:双向 IO 的控制eth_mdio 是 inout 类型,用方向信号切换:

assign eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
assign mdio_in  = eth_mdio;                   //MDIO数据输入

mdio_dir=1 时 FPGA 输出数据;为 0 时输出高阻,把总线让给 PHY(或让上拉电阻接管)。

要点 2:时钟分频。50MHz 系统时钟先分频出 dri_clk(12.5MHz),再二分频得到 eth_mdc(6.25MHz),远低于 12.5MHz 上限:

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        dri_clk <= 1'b0;
        clk_cnt <= 6'b0;
    end
    else if(clk_cnt == clk_divide[5:1] - 6'd1) begin
        clk_cnt <= 6'b0;
        dri_clk <= ~dri_clk;
    end
    else
        clk_cnt <= clk_cnt + 6'b1;
end

另一个小技巧:eth_mdc 只在 cnt[0]==0 时为高,即只在读写操作期间产生时钟,空闲时保持高电平:

always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n)
        eth_mdc <= 1'b1;
    else if(cnt[0] == 1'b0)
        eth_mdc <= 1'b1;
    else
        eth_mdc <= 1'b0;
end

要点 3:读写数据的核心状态。写操作在 MDC 下降沿逐位送出 TA 和 16 位数据;读操作则在 TA 第一位把 mdio_dir 切成输入,第二位采样应答:

st_rd_data : begin
    case(cnt)
        7'd1 : begin
            mdio_dir <= 1'b0;            //MDIO引脚切换至输入状态
            mdio_out  <= 1'b1;
        end
        7'd2 : ;                         //TA[1]位,高阻,不操作
        7'd4 : op_rd_ack <= mdio_in;     //TA[0]位,0(应答) 1(未应答)
        7'd6 : rd_data_t[15] <= mdio_in; //之后每两位采一位数据
        ...
        7'd40: begin
            op_done   <= 1'b1;           //读操作完成
            op_rd_data <= rd_data_t;
        end
        ...
    endcase
end

观察 case(cnt) 里的计数是 1、3、5、7……这是因为每两个 dri_clk 周期对应一个 MDC 周期,奇数计数点正好对应 MDC 的固定相位,从而实现"下降沿更新数据、上升沿采样"的协议要求。

mdio_ctrl:定时轮询 + 软复位

控制模块内部用一个小流程计数器(flow_cnt)串起整个工作流:

flow_cnt=0: 有软复位请求 → 写寄存器0x00,数据16'h9140(Bit[15]=1软复位+Bit[12]=1自协商使能+Bit[6]=1选1000M)
            定时到了    → 读寄存器0x01(BSR)
flow_cnt=2: 检查自协商完成(Bit[5])且连接成功(Bit[2]),都OK进入下一步,否则置link_error
flow_cnt=3: 读寄存器0x11(PHYSR)
flow_cnt=5: 根据Bit[15:14]译码出速率,赋给speed_status → LED

按键信号做了经典的三拍打圈处理来提取上升沿:

assign pos_rst_trig = ~rst_trig_d2 & rst_trig_d1;

定时器 TIMER_MAX=1,000,000,dri_clk 为 12.5MHz,所以每 80ms 轮询一次网口状态。

顶层与仿真

顶层 mdio_rw_test 就是把两个模块连起来,注意 PHY_ADDR 参数传 5'h04。仿真时有三个细节值得学习:

  1. 双向信号上拉:TB 里写 pullup(eth_mdio);,模拟板上的上拉电阻;
  2. 官方从机模型:直接例化 mdio_slave_interface 模块充当 PHY,通过类似 Wishbone 的接口给它预写入寄存器值;
  3. defparam 缩短仿真defparam mdio_rw_test_inst.u_mdio_ctrl.TIMER_MAX = 1000; 把 80ms 的定时缩短,避免仿真等到天荒地老。
pullup(eth_mdio);      //MDIO信号上拉
defparam mdio_rw_test_inst.u_mdio_ctrl.TIMER_MAX = 1000;

46.5 下载验证

管脚分配(PL 端网口):

信号 管脚 说明
sys_clk U18 50MHz 系统时钟
eth_mdc F20 管理时钟
eth_mdio F19 双向数据
eth_rst_n G15 PHY 复位
touch_key F16 触摸按键
led[0]/led[1] H15/L15 速率指示灯
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN F20 IOSTANDARD LVCMOS33} [get_ports eth_mdc]
set_property -dict {PACKAGE_PIN F19 IOSTANDARD LVCMOS33} [get_ports eth_mdio]
set_property -dict {PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports eth_rst_n]
set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33} [get_ports touch_key]

上板时把网线接到 GE_PL 网口,另一端连电脑。下载后稍等几秒,两个 LED 全亮表示自协商成了 1000Mbps。想进一步确认,可以在电脑上右击"以太网 → 状态",看到速度为 1.0Gbps 即验证成功。

初学者容易踩的坑

  1. MDC 时钟超频:MDIO 规范要求 MDC 不超过 12.5MHz。分频系数别乱改,改小了 PHY 可能不响应。
  2. PHY 地址想当然:YT8531 的地址由硬件上下拉决定,本板是 5'h04 不是 0!地址错了读到的全是"未应答"。
  3. TA 位处理不当:读操作第 7'd1 拍就要把 mdio_dir 切输入,切晚了会把 PHY 的应答位顶掉,导致 op_rd_ack 一直为"未应答"。
  4. 仿真忘上拉:TB 里不加 pullup(eth_mdio) 的话,空闲时 MDIO 是悬空的 'z',从机模型行为会异常。
  5. 软复位值写错:16'h9140 = Bit[15]软复位 + Bit[12]自协商使能 + Bit[6]速率选择,每一位都有含义,不要只写 0x8000。

小结 & 下一步

本章我们完成了以太网实战的"地基工程":

  • MDIO 两线接口的协议格式(Preamble/ST/OP/PHYAD/REGAD/TA/DATA)与读写时序;
  • 用六状态状态机实现了 MDIO 驱动,掌握 inout 双向总线的控制方法;
  • 通过轮询 BSR/PHYSR 寄存器获取网口速率并用 LED 指示。

不过要说明的是:PHY 出厂默认配置就能工作,日常做以太网通信其实可以不碰 MDIO。下一章开始玩真格的——以太网 ARP 测试实验,让开发板和电脑真正"对话"起来。