FPGA3 分钟阅读1,362 字

FPGA 实现以太网 PHY 芯片配置接口 —— MDIO 的学习与应用

MDIO 是以太网 PHY 芯片的寄存器配置接口。本文介绍 MDC/MDIO 接线、读写帧格式与时序细节, 并给出 FPGA 中配置 PHY 芯片的 Verilog 实现。

  • #FPGA
  • #Verilog
  • #嵌入式
  • #通信接口
  • #以太网
  • #经验分享

前言

MDIO 接口是配置以太网 PHY 芯片片上寄存器的一种通用接口,几乎所有的以太网 PHY 芯片使用的都是 MDIO 接口来配置寄存器。

接线方式

  • ETH_MDC: 时钟信号线,频率不大于 12.5MHz
  • ETH_MDIO: 双向数据线 主从式、半双工通信,所有通信都是由主设备发起,从设备被动响应(和 IIC 类似)

数据帧格式

读数据

PreambleSTOPPHYADREGADTADATAIDLE
1…10110AAAAARRRRRZ0DDDDDDDDDDDDDDDDZ
  • Preamble: 前导码,由主设备发送的连续的 32 个逻辑 1,用于与从设备建立同步
  • ST: 数据帧的开始,固定为 2位数据 01,代表有效数据的起始位置
  • OP: 操作码,定义操作类型,01:写操作,10:读操作
  • PHYAD:5 位 PHY 芯片地址,用于选择总线上的目标 PHY 芯片
  • REGAD:片上寄存器地址
  • TA:转向域,2 位的空闲周期,用于读写方向切换;写操作:主设备持续驱动总线输出 “10”; 读操作:第一个时钟周期 MDIO 程高阻态 (主、从设备均释放总线), 在第二个时钟周期下,从设备将接管 MDIO 总线,开始驱动 MDIO 输出数据。
  • DATA:16 位即将要写入或读取的数据
  • IDLE:空闲状态位,帧结束后 MDIO 总线拉高进入高阻态 表中的 Z 高阻态,在外部上拉电阻的情况下最终信号体现为高电平

在读数据操作时,直到 TA 阶段之前,数均由主设备(FPGA)驱动,TA 阶段第一个时钟周期主设备控制 MDIO 为高阻态,释放总线,从第二个时钟周期开始,从设备接管 MDIO,数据为低电平,表示数据将在下一个时钟周期开始发送。

MDIO 读数据时序图 MDIO 读数据时序图-note

写数据

PreambleSTOPPHYADREGADTADATAIDLE
1…10101AAAAARRRRR10DDDDDDDDDDDDDDDDZ

在写数据时,MDIO 总线全程由主设备控制 MDIO 写数据时序图

实现代码

双向 IO 口的驱动

在这里使用 IOBUF 原语驱动双向 IO 口。

verilog
IOBUF # (    .DRIVE          (   12              ),    .IBUF_LOW_PWR   (   "TRUE"          ),    .IOSTANDARD     (   "DEFAULT"       ),    .SLEW           (   "SLOW"          )) mdio_obuf (    .O              (   eth_mdio_in     ),    .IO             (   eth_mdio        ),    .I              (   eth_mdio_out    ),    .T              (   eth_mdio_dir    )   //  1'b1: input, 1'b0: output);

或者使用三态门进行逻辑判断。

verilog
module eth_mdio (	//...	inout eth_mdio; // 双向 io 口	//...);wire eth_mdio_in ; // MDIO 从机输入的比特位数据wire eth_mdio_out; // MDIO 主机输出的比特位数据reg  eth_mdio_dir; // 控制 MDIO 的输入/输出方向, 1'b1: 输入, 1'b0:输出assign eth_mdio = eth_mdio_dir ? eth_mdio_in : eth_mdio_out; // 三态门根据内部的 dir 信号控制端口的输入输出方向//... 其它代码 ... 如时钟信号、驱动 eth_mdio_dir 的逻辑等等

时钟信号的产生

和之前有一篇讲 FPGA SPI 通信 文章中的代码类似,也是产生一个双边沿的脉冲信号,然后根据这个双边沿脉冲信号去翻转 MDC 信号就得到了驱动时钟。

verilog
// MDC inv Generation, inv pulse signal is active-high when MDC rising edge and falling edgealways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_mdc_cnt <=  32'd0               ;        eth_mdc_inv <=   1'b0               ;    end    else if (eth_mdc_cnt == (CLK_DIV - 1'd1)) begin         // MDC falling edge        eth_mdc_cnt <=  8'd0                ;        eth_mdc_inv <=  1'b1                ;    end    else if (eth_mdc_cnt == (CLK_DIV[7:1] - 1'd1)) begin    // MDC rising  edge        eth_mdc_cnt <=  eth_mdc_cnt + 1'd1  ;        eth_mdc_inv <=   1'b1               ;    end    else begin        eth_mdc_cnt <=  eth_mdc_cnt + 1'd1  ;        eth_mdc_inv <=  1'b0                ;    endend// generate MDCalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_mdc     <=  1'b0                ;    end else if (eth_mdc_inv) begin        eth_mdc     <=  ~eth_mdc            ;    end else begin        eth_mdc     <=   eth_mdc            ;    endend

上升沿和下降沿的判断

判断逻辑如下:

  • 当 eth_mdc 为高电平并且产生了 eth_mdc_inv 的脉冲信号,表示 eth_mdc 将会在下一个时钟周期跳变到低电平,此时则为 eth_mdc 的下降沿
  • 当 eth_mdc 为低电平并且产生了 eth_mdc_inv 的脉冲信号,表示 eth_mdc 将会在下一个时钟周期跳变到高电平,此时则为 eth_mdc 的上升沿
verilog
wire eth_mdc_negedge =  eth_mdc & eth_mdc_inv;   // indicates MDC negedgewire eth_mdc_posedge = !eth_mdc & eth_mdc_inv;   // indicates MDC posedge

完整的 MDIO 驱动代码

已折叠部分代码 (点击即可展开)

verilog
module mdio_dri # (    parameter   CLK_DIV = 8'd10                 // for MDC generation, The max frequency of MDC is 12.500MHz)(    input                   clk             ,   // [ I] [    ] module clock, 100.000MHz is recommended    input                   rst_n           ,   // [ I] [    ] reset signal, active-low    input                   eth_start_op    ,   // [ I] [    ] start MDIO configuration pulse    input   wire            rh_wl           ,   // [ I] [    ] high level for read, and low level for write.    input   wire    [ 4:0]  phy_addr        ,   // [ I] [ 4:0] 5-bit phy address    input   wire    [ 4:0]  reg_addr        ,   // [ I] [ 4:0] 5-bit register address     input   wire    [15:0]  dat_tx          ,   // [ I] [15:0] 16-bit tx data    output  reg     [15:0]  dat_rx          ,   // [ O] [15:0] 16-bit rx data    output  reg             eth_end         ,   // [ O] [    ] pulse signal when transmit done, active-high    output  reg             eth_module_busy ,   // [ O] [    ] module busy siganl    output  reg             eth_mdc         ,   // [ O] [    ] serial clock    inout   wire            eth_mdio            // [IO] [    ] data port );//================================================================================// Local Parameter Declarations//================================================================================// 32-bit preamble  , 2-bit ST      , 2-bit OP  , //  5-bit PHYAD     , 5-bit REGAD   , 2-bit TA  ,// 16-bit DATA      , Then higi-impedance IDLE state.localparam  DAT_WIDTH       =   7'd64       ;//================================================================================// Register Declarations//================================================================================reg [7:0]   eth_mdc_cnt     ;reg         eth_mdc_inv     ;reg         eth_start_op_d0 ;reg         eth_start_op_d1 ;reg [ 7:0]  data_bit_cnt    ;reg [31:0]  dat_tx_buffer   ;reg [15:0]  dat_rx_buffer   ;reg         eth_mdio_out    ; //================================================================================// Wire Declarations//================================================================================wire        eth_mdio_in                                                 ;   // mdio data inwire        eth_mdio_dir                                                ;wire        eth_start_op_pulse  =  !eth_start_op_d0 & eth_start_op_d1   ;   wire        eth_mdc_negedge     =   eth_mdc & eth_mdc_inv               ;   // indicates MDC negedgewire        eth_mdc_posedge     =  !eth_mdc & eth_mdc_inv               ;   // indicates MDC posedge//================================================================================// Assign Declarations//================================================================================// always output until we transmit/receive 16-bit data stageassign  eth_mdio_dir    =   (data_bit_cnt <= (8'd48)) ? 1'b0 : rh_wl;//================================================================================// implements//================================================================================IOBUF # (    .DRIVE          (   12              ),    .IBUF_LOW_PWR   (   "TRUE"          ),    .IOSTANDARD     (   "DEFAULT"       ),    .SLEW           (   "SLOW"          )) mdio_obuf (    .O              (   eth_mdio_in     ),    .IO             (   eth_mdio        ),    .I              (   eth_mdio_out    ),    .T              (   eth_mdio_dir    )   //  1'b1: input, 1'b0: output);//================================================================================// MAIN CODE//================================================================================// sync eth_start_op signalalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_start_op_d0 <=  1'b0            ;        eth_start_op_d1 <=  1'b0            ;    end else begin        eth_start_op_d0 <=  eth_start_op    ;        eth_start_op_d1 <=  eth_start_op_d0 ;    endend// module busy indicatoralways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_module_busy <=  1'b0            ;    end else if (eth_start_op_pulse) begin        eth_module_busy <=  1'b1            ;    end else if (eth_end) begin        eth_module_busy <=  1'b0            ;    end else begin        eth_module_busy <=  eth_module_busy ;    endend// MDC inv Generation, inv pulse signal is active-high when MDC rising edge and falling edgealways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_mdc_cnt <=  32'd0               ;        eth_mdc_inv <=   1'b0               ;    end    else if (eth_mdc_cnt == (CLK_DIV - 1'd1)) begin         // MDC falling edge        eth_mdc_cnt <=  8'd0                ;        eth_mdc_inv <=  1'b1                ;    end    else if (eth_mdc_cnt == (CLK_DIV[7:1] - 1'd1)) begin    // MDC rising  edge        eth_mdc_cnt <=  eth_mdc_cnt + 1'd1  ;        eth_mdc_inv <=   1'b1               ;    end    else begin        eth_mdc_cnt <=  eth_mdc_cnt + 1'd1  ;        eth_mdc_inv <=  1'b0                ;    endend// generate MDCalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_mdc     <=  1'b0                ;    end else if (eth_mdc_inv) begin        eth_mdc     <=  ~eth_mdc            ;    end else begin        eth_mdc     <=   eth_mdc            ;    endend// count data bitalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        data_bit_cnt    <=  8'd0            ;    end else if (        eth_module_busy     &&        eth_mdc_negedge     &&        (data_bit_cnt < DAT_WIDTH)    ) begin        data_bit_cnt <=  data_bit_cnt + 1'b1;    end else if (eth_mdc_negedge && data_bit_cnt == DAT_WIDTH) begin        data_bit_cnt    <=  8'd0            ;    end else begin        data_bit_cnt    <=  data_bit_cnt    ;    endend// send dataalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_mdio_out    <=  1'b0            ;        dat_tx_buffer   <=  32'b0           ;    end else if (eth_start_op_pulse) begin         dat_tx_buffer   <=  {            2'b01                       ,   // start of frame            rh_wl                       ,   // Operation code bit 1            !rh_wl                      ,   // Operation code bit 2            phy_addr                    ,   // PHY address            reg_addr                    ,   // Register address            (rh_wl) ?  2'b00  : 2'b10   ,   // Turnaround            (rh_wl) ? 16'b0000: dat_tx      // 16-bit data        };    end else if (        eth_module_busy     &&        eth_mdc_negedge     &&        (data_bit_cnt < 8'd32)                          ) begin        eth_mdio_out    <=  1'b1                ;   // 32-bit preamble    end else if (        eth_module_busy     &&        eth_mdc_negedge     &&        (data_bit_cnt < DAT_WIDTH)    ) begin        eth_mdio_out    <=  dat_tx_buffer[31]   ;   // MSB First        dat_tx_buffer   <=  {            dat_tx_buffer[30:0],            dat_tx_buffer[  31]        };    end else if (        eth_module_busy     &&        eth_mdc_negedge     &&        (data_bit_cnt >= DAT_WIDTH)    ) begin        eth_mdio_out    <=  1'b0                ;        dat_tx_buffer   <=  {32{1'b0}}          ;    endend// receive dataalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        dat_rx_buffer       <=  16'b0               ;    end else if (eth_mdio_dir && eth_mdc_posedge) begin        dat_rx_buffer[0]    <=  eth_mdio_in         ;        dat_rx_buffer[15:1] <=  dat_rx_buffer[14:0] ;    end else begin        dat_rx_buffer       <=  dat_rx_buffer       ;    endend// move data from dat_rx_buffer to dat_rx output and generate eth_end signalalways  @(posedge clk or negedge rst_n) begin    if (!rst_n) begin        eth_end     <=      1'b0                    ;        dat_rx      <=      16'b0                   ;    end else if (eth_mdc_negedge && (data_bit_cnt == DAT_WIDTH)) begin        eth_end     <=      1'b1                    ;        dat_rx      <=      dat_rx_buffer           ;    end else begin        eth_end     <=      1'b0                    ;    endendendmodule

← 返回文章列表

© 2026 硬件日记 · 技能分享 · 随笔日记

《FPGA 实现以太网 PHY 芯片配置接口 —— MDIO 的学习与应用》采用 CC-BY-NC-SA 4.0 许可协议, 转载请注明出处。

博客框架: Astro

博客已运行 计算中…