4. MDIO 控制器硬件设计:寄存器接口、时钟要求、PCB 布局注意事项
设计 MDIO 控制器,很多人上来就写代码。我劝你先想清楚硬件接口怎么做。寄存器配置错了,时钟抖了,PCB 上串扰了,软件怎么调都白搭。这一节我把三个关键点拆开讲,每个都是我在项目里踩过的坑。
4.1 寄存器接口:APB 还是 AHB?地址分配怎么定?
MDIO 控制器一般挂在外设总线上。我个人习惯用 APB,因为它简单、时序好约束。但如果你要跑 50 MHz 以上的 MDC,建议用 AHB 加上 FIFO 缓冲,否则 CPU 会被频繁中断拖死。
| 偏移地址 | 寄存器名 | 功能 |
|---|---|---|
| 0x00 | MDIO_CTRL | 控制:使能、模式选择、中断使能 |
| 0x04 | MDIO_CLK_DIV | 分频系数:MDC = 总线时钟 / (2*(div+1)) |
| 0x08 | MDIO_ADDR | PHY 地址 + 寄存器地址 |
| 0x0C | MDIO_WDATA | 写数据寄存器 |
| 0x10 | MDIO_RDATA | 读数据寄存器(只读) |
| 0x14 | MDIO_STATUS | 状态:忙、错误、完成 |
注意一个细节:读数据寄存器一定要加同步逻辑。曾有一次,我的 MDC 域和总线域没处理好亚稳态,读回来的数据偶尔跳变。我加了两级触发器做跨时钟同步,问题才解决。记住,MDIO 是双时钟域——总线时钟和 MDC 时钟可能不同源,寄存器接口是第一个亚稳态陷阱。
4.2 时钟要求:MDC 怎么生成?抖动怎么控制?
MDC 由控制器内部分频产生。标准规定 MDC 最大 2.5 MHz,但实际操作中,如果你只接一个 PHY,跑到 12.5 MHz 也常见。不过别盲目提速——PCB 走线一长,反射和串扰会让眼图闭合。
分频器的实现很简单:
// Verilog 示例:MDC 分频生成(双沿采样时可减半)
reg [7:0] clk_cnt;
reg mdc_reg;
always @(posedge bus_clk) begin
if (clk_cnt < CLK_DIV - 1)
clk_cnt <= clk_cnt + 1;
else begin
clk_cnt <= 0;
mdc_reg <= ~mdc_reg;
end
end
assign mdc = mdc_reg;
如果分频系数是奇数,MDC 高电平与低电平宽度差一个周期。过大的占空比偏差会让 PHY 采样出错。我建议只用偶数分频,或者用双边沿计数保证 50% 占空比。
再说抖动。MDC 的抖动主要来自分频电路的相位噪声和供电噪声。我有一次在 FPGA 上验证,发现 MDC 上升沿抖动达 2 ns,PHY 那边直接丢包。后来我在 MDC 输出路径上加了一个 33 Ω 的串联电阻,并靠近 FPGA 引脚放置,抖动降到了 500 ps 以内。说到 PCB……下面重点讲。
4.3 PCB 布局注意事项
MDIO 是低速信号(2.5 MHz),但很多人忽视它。你想想看,MDC 和 MDIO 两条线的长度差超过 2 英寸,时序窗口就偏了。我归纳了三条核心规则:
- 等长走线:MDC 与 MDIO 长度差控制在 500 mil 内。如果有多个 PHY,每个 PHY 的分支也要等长。
- 远离噪声源:别跟开关电源的 DC-DC 电感、高频时钟线平行走。我在一个项目中碰到过,MDIO 线挨着 100 MHz DDR 时钟走,上拉电阻都救不了。后来割线绕开,问题消失。
- 上拉与端接:MDIO 是开漏,必须外部上拉到 1.8V 或 3.3V。上拉电阻通常 1.5kΩ ~ 4.7kΩ,根据线长和负载调整。我一般用 2.2kΩ,效果最均衡。如果 MDC 走线超过 6 英寸,建议在源端加 22 Ω 串联电阻抑制过冲。
嗯,关于时钟还有一个常见误区——有些人喜欢用 PLL 分频出 MDC。我个人不推荐,除非你有专用的 IO 时钟输出管脚。用普通 GPIO 输出 MDC,抖动会比 PLL 专用时钟输出大一个数量级。如果必须用 PLL,记得在靠近 PHY 的地方加一个 74LVC1G125 做时钟整形。
最后提一下寄存器接口的测试点。设计时一定要把 MDIO_CTRL 和 MDIO_STATUS 的各个位引出到测试焊盘。我在调试 FPGA 原型时,靠一个逻辑分析仪抓 MDC 和 MDIO 线上的波形,同时读 STATUS 寄存器里的 “忙” 位,才快速定位到分频系数算错了。硬件设计时多留几个测试点,能省你一周的调试时间。