FPGA新手必看:手把手教你用Verilog实现MDIO接口读写PHY寄存器

张开发
2026/6/30 22:58:26 15 分钟阅读
FPGA新手必看:手把手教你用Verilog实现MDIO接口读写PHY寄存器
FPGA实战Verilog实现MDIO接口控制PHY寄存器的完整指南第一次在FPGA项目里遇到需要配置以太网PHY芯片时看着手册里密密麻麻的寄存器列表和MDIO接口时序图我盯着示波器上那些跳动的波形发呆了整整一个下午。作为FPGA开发者我们常常需要和各种接口协议打交道而MDIO这个看似简单的两线接口在实际调试时却藏着不少坑。本文将用最直白的代码和实测波形带你从零实现一个可靠的MDIO控制器。1. 理解MDIO协议的本质MDIO接口就像FPGA与PHY芯片之间的秘密通话通道。它只需要两根线——MDC时钟和MDIO数据却能完成PHY芯片内部所有寄存器的读写操作。这种简约设计背后是精妙的时序配合同步舞蹈MDC时钟由MAC我们的FPGA主导但数据变化和采样时刻有严格约定角色切换读操作时需要双向切换数据线控制权就像两个舞伴轮流领舞精确节奏每个bit的传输都必须在特定时钟边沿完成时序误差超过10ns就可能导致通信失败实际项目中我遇到过因为MDC时钟相位设置不当导致PHY始终无响应的案例。后来用逻辑分析仪抓取信号才发现PHY芯片在TA阶段没有正确接管MDIO线。这个教训让我明白理解协议细节比会写代码更重要。2. 搭建MDIO控制器的硬件框架2.1 接口信号定义首先定义模块的输入输出端口这是与外部PHY芯片连接的物理接口module mdio_controller ( input wire clk, // 系统时钟 (50MHz) input wire reset, // 异步复位 output reg mdc, // MDIO时钟输出 inout wire mdio, // 双向数据线 // 用户配置接口 input wire start, // 启动传输脉冲 input wire wr_rd, // 1写 0读 input wire [4:0] phy_addr, // PHY芯片地址 input wire [4:0] reg_addr, // 寄存器地址 input wire [15:0] data_in, // 写入数据 output reg [15:0] data_out, // 读取数据 output reg busy, // 忙状态指示 output reg done // 传输完成脉冲 );注意MDIO信号必须声明为inout类型并在代码中正确处理三态控制。这是新手最容易出错的地方之一。2.2 时钟生成逻辑MDC时钟通常限制在2.5MHz以下根据IEEE 802.3标准。我们需要从系统时钟分频得到合适的MDC// 生成1.25MHz的MDC时钟 (50MHz/40) reg [5:0] clk_div; always (posedge clk or posedge reset) begin if (reset) begin clk_div 0; mdc 0; end else begin if (clk_div 39) begin clk_div 0; mdc ~mdc; // 翻转MDC时钟 end else begin clk_div clk_div 1; end end end关键参数对比PHY型号最大MDC频率建立时间要求保持时间要求DP838482.5MHz10ns10nsRTL821112.5MHz5ns5nsKSZ90318.3MHz7ns7ns3. 状态机设计与实现MDIO协议本质上是基于状态机的串行通信。我们需要明确定义每个状态及其转换条件3.1 状态定义与转换localparam [3:0] IDLE 4d0, PREAMBLE 4d1, START 4d2, OPCODE 4d3, PHY_ADDR 4d4, REG_ADDR 4d5, TA 4d6, DATA 4d7, COMPLETE 4d8; reg [3:0] current_state, next_state; reg [4:0] bit_count; reg [15:0] shift_reg; reg mdio_dir; // 方向控制: 1FPGA驱动, 0PHY驱动 reg mdio_out; // FPGA输出值3.2 状态机核心逻辑always (posedge mdc or posedge reset) begin if (reset) begin current_state IDLE; bit_count 0; end else begin current_state next_state; case (current_state) PREAMBLE: begin if (bit_count 31) begin bit_count 0; end else begin bit_count bit_count 1; end end // 其他状态处理... endcase end end always (*) begin case (current_state) IDLE: next_state start ? PREAMBLE : IDLE; PREAMBLE: next_state (bit_count 31) ? START : PREAMBLE; START: next_state (bit_count 1) ? OPCODE : START; // 完整的状态转换逻辑... default: next_state IDLE; end end提示状态机的设计要特别注意跨时钟域问题。这里我们使用MDC时钟驱动状态转移确保与PHY芯片严格同步。4. 读写操作的Verilog实现4.1 写寄存器完整流程// 在DATA状态处理写操作 DATA: begin if (wr_rd) begin // 写操作 if (bit_count 15) begin mdio_out data_in[15 - bit_count]; bit_count bit_count 1; end else begin mdio_out data_in[0]; bit_count 0; next_state COMPLETE; end end // 读操作处理... end4.2 读寄存器与TA处理读操作的关键在于正确处理TA阶段的控制权切换TA: begin if (!wr_rd) begin // 读操作才需要TA if (bit_count 0) begin mdio_dir 0; // 释放MDIO控制权 bit_count 1; end else begin if (mdio_in 0) begin // 检查PHY是否响应 bit_count 0; next_state DATA; end else begin next_state COMPLETE; // PHY无响应 end end end end常见错误排查表现象可能原因解决方法PHY无响应PHY地址错误检查硬件原理图确认PHY_ADDR读回数据全0TA阶段未释放MDIO确认mdio_dir在TA第一个周期置0数据位错位采样边沿错误确保在MDC上升沿采样输入数据随机错误时序不满足降低MDC频率或检查PCB走线5. 仿真验证与调试技巧5.1 Testbench编写要点initial begin // 初始化 reset 1; start 0; wr_rd 0; phy_addr 5h01; reg_addr 5h00; #100 reset 0; // 启动读操作 #200 start 1; #20 start 0; // 等待操作完成 wait(done); $display(Read data: %h, data_out); $finish; end5.2 实际调试中的经验波形捕获使用逻辑分析仪同时抓取MDC和MDIO信号重点关注TA阶段MDIO线是否成功切换为高阻数据变化是否发生在MDC下降沿建立保持时间是否满足PHY要求上拉电阻MDIO线必须接上拉电阻通常4.7kΩ否则高阻状态时会浮空电源干扰PHY芯片的供电噪声可能导致MDIO通信不稳定建议用示波器检查电源纹波跨时钟域如果用户接口与MDC不同源需要添加适当的同步处理// 跨时钟域同步示例 reg [1:0] start_sync; always (posedge mdc or posedge reset) begin if (reset) begin start_sync 2b0; end else begin start_sync {start_sync[0], start}; end end wire start_pulse (start_sync 2b01);6. 性能优化与高级应用6.1 自动重试机制在实际应用中建议添加通信失败时的自动重试功能reg [2:0] retry_count; always (posedge mdc) begin if (current_state COMPLETE) begin if (!done retry_count 5) begin retry_count retry_count 1; next_state PREAMBLE; end end end6.2 多PHY管理通过PHY地址轮询可以实现单个MDIO接口管理多个PHY芯片reg [4:0] phy_addr_list [0:3]; reg [1:0] current_phy; always (posedge clk) begin if (done) begin if (current_phy 3) begin current_phy current_phy 1; start 1; end end end7. 完整工程结构建议一个健壮的MDIO控制器项目应包含以下模块mdio_project/ ├── rtl/ │ ├── mdio_controller.v // 主控制器 │ ├── mdio_clock_gen.v // MDC时钟生成 │ └── mdio_phy_if.v // PHY接口适配 ├── sim/ │ ├── tb_mdio.v // 测试平台 │ └── mdio_phy_model.v // PHY行为模型 └── docs/ ├── timing_constraints.sdc // 时序约束 └── register_map.md // PHY寄存器文档在真实项目中调试MDIO接口时我习惯先用ModelSim做功能仿真然后用Signaltap抓取实际运行波形对比。曾经遇到过一个棘手的问题——读回的数据总是比预期晚一个时钟周期最终发现是状态机在TA阶段的判断条件写反了。这种协议级的bug往往最难发现但也最能加深对MDIO工作原理的理解。

更多文章