MDIO(clause 22 与 clause 45)接口简介以及FPGA Verilog 实现

MDIO
MDIO(Management Data Input/Output),对G比特以太网而言,串行通信总线称为管理数据输入输出 (MDIO)。
MDIO由两根线组成,MDC线是数据的随路时钟,最高速率可达几MHz(各PHY芯片有异)。MDIO线是数据的输入输出双向总线,数据是与MDC时钟同步的。
MDIO前后有两种协议, 包括之前的Clause22 以及之后为了弥补Clause22 寄存器空间不足而出的Clause45, Clause 45 向前兼容Clause 22。

Clause 22
Clause 22 的时序图如下:
在这里插入图片描述
在这里插入图片描述
STA:Station Management
MMD:MDIO Managed Device

PRE:帧前导码,为32个连续“1”比特。
ST:帧开始标志, Clause22 的开始标志为比特“01”。
OP:操作码,CL22中比特“10”表示此帧为一读操作帧,比特“01”表示此帧为一写操作帧。
PHYAD:MMD的物理地址,5个比特,每个MMD都把自己的地址与这5个比特进行比较,若匹配则响应后面的操作,若不匹配,则忽略掉后面的操作。
REGAD:用来选MMD的32个寄存器中的某个寄存器的地址。
TA:状态转换域,若为读操作,则第一比特时MDIO为高阻态,第二比特时由MMD使MDIO置“0”。若为写操作,则MDIO仍由STA控制,连续输出“10”两个比特。
DATA:帧的寄存器的数据域,16比特,若为读操作,则为MMD送到STA的数据,若为写操作,则为STA送到MMD数据。
IDLE:帧结束后的空闲状态,此时MDIO无源驱动,处高阻状态。

Clause 45
Clause 45 的时序图如下:

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

STA:Station Management
MMD:MDIO Managed Device

PRE:帧前导码,为32个连续“1”比特。
ST:帧开始标志, 为了区别CL22,Clause45 的开始标志为比特“00”。
OP:操作码,Clause45有4种操作码,比特“00”表示设置当前寄存器地址,比特“01”表示写当前寄存器。比特“10”表示读当前寄存器,比特“11”表示读当前寄存器读完后把当前寄存器的值加1,用于顺序读。
PRTAD:Port Address,端口地址, 也称物理地址。
DEVAD:器件地址,CL45新增概念,各值与器件对应如下。
在这里插入图片描述
REGAD:用来选MMD的65536个寄存器中的某个寄存器的地址。
TA:状态转换域,若为读操作,则第一比特时MDIO为高阻态,第二比特时由MMD使MDIO置“0”。若为写操作,则MDIO仍由STA控制,连续输出“10”两个比特。
DATA:帧的寄存器的数据域,16比特,若为读操作,则为MMD送到STA的数据,若为写操作,则为STA送到MMD数据。
IDLE:帧结束后的空闲状态,此时MDIO无源驱动,处高阻状态。

Problems with the MDIO
当MDIO通信出现问题,可依次检查以下方面

  • 确保MDC工作在合适的频率
  • 确保MDC以及MDIO有上拉
  • PHYAD(PRTAD)没有搞错。
  • MMD 没有处于复位状态。
  • 适当调整MDC的相位。
  • 有些MMD要求帧与帧之间一定要用高阻态分隔

STA MDIO 接口 Verilog 代码如下

   `timescale 1ns / 1ps
	//
	// Company: 
	// Engineer: 
	// 
	// Create Date: 2019/08/12 10:39:51
	// Design Name: 
	// Module Name: eth_mdio_interface
	// Project Name: 
	// Target Devices: 
	// Tool Versions: 
	// Description: 
	// 
	// Dependencies: 
	// 
	// Revision:
	// Revision 0.01 - File Created
	// Additional Comments:  
	//      
	//

	module eth_mdio_interface #(

	  parameter MDC_DIVISOR = 100      MDC frequency is clk_i / MDC_DIVISOR.  

	)(

	 input               clk_i,
	 input               rstn_i,

	 input               clause_sel_i,1 : Clause 45;  0 : Clause 22;

	 output  reg         ready_o,
	 input               valid_i,
	 input   [1:0]       cmd_i,
	 input   [25:0]      addr_i,      Cl45: PHY Addr(5 bit) + Devices Addr(5 bit) + Reg Addr(16 bit); Cl22 : PHY Addr(5 bit) + Reg Addr(5 bit) + invalid(16 bit)//
	 input   [15:0]      wdata_i,  

	 output  reg         rdata_vld_o,
     output  reg [15:0]  rdata_o,

     output  reg         mdc_o,
     input               mdio_i,
	 output  reg         mdio_o,
	 output  reg         mdio_oen_o

	);

	localparam  WR_CMD = 2'b01;
	localparam  RD_CMD = 2'b11; 
	localparam  RD_INC = 2'b10;  not supported
  
	//---------------------------------------------------------------------------------------------------
	// Function - Calculates the log2ceil of the input value
	//---------------------------------------------------------------------------------------------------
	function integer log2ceil;
	  input integer val;
	  integer i;
	  begin
  		 i = 1;
  		 log2ceil = 0;
  
  		 while (i < val) begin
      		log2ceil = log2ceil + 1;
      		i = i << 1; 
  		 end
	  end
	endfunction  
	
	 
	localparam P_MDC_DIVIDE_BITS = log2ceil(MDC_DIVISOR) - 1;    // Need to count to (MDC_DIVISOR/2) - 1   

	reg [P_MDC_DIVIDE_BITS-1:0] mdc_divide;  
	reg mdc_tick;     
	reg mdc_sample;   

	 Divide clock to make MDIO clock  
	always @(posedge clk_i, negedge rstn_i) 
	  if(!rstn_i)
  		 mdc_divide <= 0;
	  else if(mdc_divide == 0)      
        mdc_divide <= (MDC_DIVISOR/2) - 1'b1;
      else 
        mdc_divide <= mdc_divide - 1'b1;
  
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)    
        mdc_o <= 1'b0;
	  else if(mdc_divide == 0)
  		 mdc_o <= ~mdc_o;

	 Data is output on mdc_tick. Delay it slightly from the clock to ensure setup and hold timing is met
	 Sample read data just before rising edge of MDC
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i) begin
  	    mdc_tick <= 1'b0;
  	    mdc_sample <= 1'b0;
	  end
	  else begin
  		 mdc_tick <= ~mdc_o & (mdc_divide==(MDC_DIVISOR/2) - (MDC_DIVISOR/4));
 	    mdc_sample <= ~mdc_o & (mdc_divide==2);
	  end

	 
	wire   [4:0]    cl45_phy_addr = addr_i[25:21];           
	wire   [4:0]    cl45_dev_addr = addr_i[20:16];           
	wire   [15:0]   cl45_reg_addr = addr_i[15:0];          
	wire   [4:0]    cl22_phy_addr = addr_i[25:21];              
	wire   [4:0]    cl22_reg_addr = addr_i[20:16];                                                                           

	reg    [3:0]    state;
	localparam      ST_PREAMBLE1        = 4'd0;
	localparam      ST_IDLE1            = 4'd1;
	localparam      ST_WR_ADDR_CL45     = 4'd2;
	localparam      ST_IDLE2            = 4'd3;
	localparam      ST_PREAMBLE2        = 4'd4;
	localparam      ST_REWR_ADDR_CL45   = 4'd5;
	localparam      ST_WR_ADDR_CL22     = 4'd6;
 	localparam      ST_WR_DATA          = 4'd7;
	localparam      ST_RD_DATA          = 4'd8;

	wire  preamble_set_done;
	wire  wr_addr_cl45_done;
	wire  wr_addr_cl22_done;
	wire  rewr_addr_cl45_done;
	wire  wr_data_done;
	wire  rd_data_done;
	reg   wr_rdn_en;
	reg   cmd_pending;

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  		 state <= ST_IDLE1;
	  else  
  	  case (state)
        ST_IDLE1               :   if(cmd_pending && mdc_tick)
            	                      state <= ST_PREAMBLE1; 
                                   else 
                                     state <= ST_IDLE1;
        ST_PREAMBLE1           :   if(preamble_set_done)     
                                     state <= clause_sel_i? ST_WR_ADDR_CL45 : ST_WR_ADDR_CL22;
                                   else 
                                     state <= ST_PREAMBLE1;                              
        ST_WR_ADDR_CL45        :   if(wr_addr_cl45_done)
                                     state <= ST_IDLE2;
                                   else 
                                     state <= ST_WR_ADDR_CL45;
        ST_IDLE2               :   if(mdc_tick)    
                                     state <= ST_PREAMBLE2;
                                   else 
                                     state <= ST_IDLE2;                           
        ST_PREAMBLE2           :   if(preamble_set_done)
                                     state <= ST_REWR_ADDR_CL45;
                                   else 
                                     state <= ST_PREAMBLE2;
        ST_REWR_ADDR_CL45      :   if(rewr_addr_cl45_done)
                                     state <= wr_rdn_en? ST_WR_DATA : ST_RD_DATA;  
                                   else 
                                     state <= ST_REWR_ADDR_CL45;
        ST_WR_ADDR_CL22        :   if(wr_addr_cl22_done)
                                     state <= wr_rdn_en? ST_WR_DATA : ST_RD_DATA;   
                                   else 
                                     state <= ST_WR_ADDR_CL22;                          
        ST_WR_DATA             :   if(wr_data_done)                               
                                     state <= ST_IDLE1;
                                   else 
                                     state <= ST_WR_DATA;                                 
        ST_RD_DATA             :   if(rd_data_done)   
                                     state <= ST_IDLE1;
                                   else 
                                     state <= ST_RD_DATA;       
        default                :   state <= ST_IDLE1;
      endcase

	wire latch_en;
	assign latch_en = (ready_o && valid_i);  
 
	 wait mdc_tick to start
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    cmd_pending <= 1'b0;
	  else if(latch_en)
  		 cmd_pending <= 1'b1;
	  else if(mdc_tick)
     	 cmd_pending <= 1'b0;

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    wr_rdn_en <= 1'b0;
	  else if(latch_en)
  		 wr_rdn_en <= (cmd_i == WR_CMD);
  
	reg [5:0]  preamble_cnt;
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    preamble_cnt <= 6'b0;
	  else if(state != ST_PREAMBLE1 && state != ST_PREAMBLE2)   
  		 preamble_cnt <= 6'b0; 
	  else if(mdc_tick)
  	    preamble_cnt <= preamble_cnt + 6'b1;

	assign preamble_set_done = (preamble_cnt == 6'd31) && mdc_tick;

	reg [5:0] wr_addr_cl45_cnt;
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    wr_addr_cl45_cnt <= 6'b0;
	  else if(state != ST_WR_ADDR_CL45)
  	    wr_addr_cl45_cnt <= 6'b0;
	  else if(mdc_tick)
  	    wr_addr_cl45_cnt <= wr_addr_cl45_cnt + 6'b1;

	assign wr_addr_cl45_done = (wr_addr_cl45_cnt == 6'd31) && mdc_tick; 

	reg [4:0] rewr_addr_cl45_cnt;  
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    rewr_addr_cl45_cnt <= 5'b0;
	  else if(state != ST_REWR_ADDR_CL45)      
  	    rewr_addr_cl45_cnt <= 5'b0;
	  else if(mdc_tick)
  		 rewr_addr_cl45_cnt <= rewr_addr_cl45_cnt + 5'b1;

	assign rewr_addr_cl45_done = (rewr_addr_cl45_cnt == 5'd13) && mdc_tick;

	reg [4:0] wr_addr_cl22_cnt;
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    wr_addr_cl22_cnt <= 5'b0;
	  else if(state != ST_WR_ADDR_CL22)
  	    wr_addr_cl22_cnt <= 5'b0;
	  else if(mdc_tick)
  	    wr_addr_cl22_cnt <= wr_addr_cl22_cnt + 5'b1;
  
	assign wr_addr_cl22_done = (wr_addr_cl22_cnt == 5'd13) && mdc_tick;

	reg [4:0] wr_data_cnt;
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  	    wr_data_cnt <= 5'b0;
	  else if(state != ST_WR_DATA)
  	    wr_data_cnt <= 5'b0;
	  else if(mdc_tick)
  	    wr_data_cnt <= wr_data_cnt + 5'b1;

	assign wr_data_done = (wr_data_cnt == 5'd17) && mdc_tick;

	reg [4:0] rd_data_cnt;
	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  		 rd_data_cnt <= 5'b0;
	  else if(state != ST_RD_DATA)
  	    rd_data_cnt <= 5'b0;
	  else if(mdc_tick)
  	    rd_data_cnt <= rd_data_cnt + 5'b1;

	assign rd_data_done = (rd_data_cnt == 5'd17) && mdc_tick; 

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  		 ready_o = 1'b0;
	  else 
  	    ready_o = (state == ST_IDLE1) && (!valid_i) && (!cmd_pending);
  
	reg [1:0] op_code_cl45;
	always @(*)
	  case (cmd_i)
  		 WR_CMD : op_code_cl45 = 2'b01;
 		 RD_CMD : op_code_cl45 = 2'b11;
 	    RD_INC : op_code_cl45 = 2'b10;
		 default : op_code_cl45 = 2'b11;
	  endcase

	reg [1:0] op_code_cl22;
	always @(*)
	  case (cmd_i)
  		 WR_CMD : op_code_cl22 = 2'b01;
 		 RD_CMD : op_code_cl22 = 2'b10;
		 default : op_code_cl22 = 2'b11;
	  endcase

	reg [31:0] first_wraddr_sfr_cl45;
	always @(posedge clk_i)
	  if(latch_en)
  	    first_wraddr_sfr_cl45 <= {2'b00, 2'b00, cl45_phy_addr, cl45_dev_addr, 2'b10, cl45_reg_addr};
	  else if((state == ST_WR_ADDR_CL45) && mdc_tick)
 		 first_wraddr_sfr_cl45 <= {first_wraddr_sfr_cl45[30:0], 1'b0};
  
	reg [13:0] second_wraddr_sfr_cl45;
	always @(posedge clk_i)
	  if(latch_en)
  		 second_wraddr_sfr_cl45 <= {2'b00, op_code_cl45, cl45_phy_addr, cl45_dev_addr};
	  else if((state == ST_REWR_ADDR_CL45) && mdc_tick)
  		 second_wraddr_sfr_cl45 <= {second_wraddr_sfr_cl45[12:0], 1'b0};

	reg [13:0]  wraddr_sfr_cl22;
	always @(posedge clk_i)  
	  if(latch_en)
  		 wraddr_sfr_cl22 <= {2'b01, op_code_cl22, cl22_phy_addr, cl22_reg_addr};
	  else if((state == ST_WR_ADDR_CL22) && mdc_tick)
  		 wraddr_sfr_cl22 <= {wraddr_sfr_cl22[12:0], 1'b0};
  
	reg [17:0] wrdata_sfr;
	always @(posedge clk_i)
	  if(latch_en)
  		 wrdata_sfr <= {2'b10, wdata_i};
      else if((state == ST_WR_DATA) && mdc_tick)      
  		 wrdata_sfr <= {wrdata_sfr[16:0], 1'b0};

	reg [15:0] rddata_sfr;
	always @(posedge clk_i)
	  if((state == ST_RD_DATA) && mdc_sample)  
  		 rddata_sfr <= {rddata_sfr[14:0], mdio_i};

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
  		 mdio_o <= 1'b0;
	  else case (state)
  		 ST_WR_ADDR_CL45    : mdio_o <= first_wraddr_sfr_cl45[31];
 	    ST_REWR_ADDR_CL45  : mdio_o <= second_wraddr_sfr_cl45[13];
 		 ST_WR_ADDR_CL22    : mdio_o <= wraddr_sfr_cl22[13];
 		 ST_WR_DATA         : mdio_o <= wrdata_sfr[17];
 		 default            : mdio_o <= 1'b1;
	  endcase

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i)
 		 mdio_oen_o <= 1'b1;
	  else 
 		 mdio_oen_o <= (state != ST_RD_DATA)&&(state != ST_IDLE1)&&(state != ST_IDLE2);

	always @(posedge clk_i, negedge rstn_i)
	  if(!rstn_i) begin
	    rdata_vld_o <= 1'b0;
 	    rdata_o <= 16'b0;
	   end
	  else if(rd_data_done) begin
 		 rdata_vld_o <= 1'b1;
	    rdata_o <= rddata_sfr;
	  end
	  else begin
	    rdata_vld_o <= 1'b0;
	    rdata_o <= rdata_o;
	  end


	endmodule

实际使用时抓取的波形
在这里插入图片描述
在这里插入图片描述

`

<think>好的,用户想了解如何配置PHY芯片的设置教程。首先,我需要整理一下已有的引用资料。引用1提到PHY芯片通过MDIO接口进行配置,引用2详细说明了可以通过MIIM或I2C配置寄存器,并以88E1111为例。引用3讨论了PHY地址的设置,特别是LAN8720A的地址配置。引用4虽然讲的是光模块耦合,但可能不相关。 用户的问题是关于配置方法的教程,所以需要分步骤说明。首先应该介绍硬件连接,比如MDIO接口和PHY地址的设置,这部分引用3提到PHY地址的重要性。然后是配置方式,引用2提到的硬件配置和软件配置,比如上下拉电阻设置,以及通过MIIM(即MDIO)或I2C访问寄存器。接下来需要详细步骤,比如确定PHY地址、初始化接口、配置寄存器如速率双工模式、LED设置、收发延时等,这些引用2有例子。另外,测试验证部分也很重要,检查链接状态和速率。可能还要提一下不同PHY芯片的差异,比如88E1111和LAN8720A的不同配置方法,引用2和3正好有这两个例子。最后,用户可能遇到问题,比如地址错误或寄存器配置不当,需要给出常见解决方法。确保步骤清晰,结合具体例子,用户更容易理解。需要检查是否有遗漏,比如硬件配置和软件配置的区别,以及如何访问寄存器,这些都要明确。可能还需要提醒用户参考数据手册,因为不同PHY的寄存器定义不同。这样整理下来,回答应该能覆盖用户的需求。</think>### PHY芯片配置方法分步说明 PHY芯片配置主要通过管理接口MDIO/MIIM)或I2C实现,核心步骤包含硬件连接、寄存器配置和功能验证三个主要阶段。以下是具体配置流程: #### 一、硬件基础配置 1. **PHY地址设置** - 通过芯片引脚电平组合确定PHY地址,如LAN8720A通过RXER/PHYAD引脚电平设置5位地址[^3] - 典型配置案例: ```verilog // FPGA引脚连接示例 assign phy_addr[4:0] = {pullup, pulldown, pulldown, pullup, pulldown}; ``` - 地址范围限制:SMI接口最多支持32个不同地址的PHY芯片 2. **管理接口选择** - 双模式接口支持: * MDIO/MIIM接口:标准IEEE 802.3 Clause 22/45协议 * I2C接口:需配置模式选择引脚(如88E1111的INTN/RSTN引脚) #### 二、寄存器配置流程 1. **基础通信配置** - 通过控制寄存器(地址0x00)设置: ```c // 设置自动协商使能(Bit12)、重启自动协商(Bit9) write_phy_reg(0x00, 0x1140); ``` - 速率/双工模式配置路径: ``` 自动协商模式 → 广告能力寄存器(0x04) → 链路伙伴能力寄存器(0x05) ``` 2. **高级功能配置 - **延迟调整(RGMII接口必备)**: ```c // 88E1111 RX/TX延迟设置(寄存器0x14) write_phy_reg(0x14, 0x003F); // 2ns RX delay write_phy_reg(0x14, 0x00BF); // 2ns TX delay ``` - **LED指示灯配置**: ```c // LAN8720A LED模式设置(特殊模式寄存器31) write_phy_reg(31, 0x0006); // LED1=链路状态,LED2=活动指示 ``` #### 三、配置验证方法 1. **状态寄存器检测**: - 链路状态:读取基本状态寄存器(0x01)Bit2 - 协商结果:读取特定状态寄存器(0x1F)获取实际速率 2. **环回测试模式**: ```c // 启用数字环回测试 write_phy_reg(0x00, read_phy_reg(0x00) | 0x4000); // 发送测试帧验证收发通路 ``` #### 四、典型故障处理 | 故障现象 | 检测点 | 解决方法 | |-----------------|-------------------------|---------------------------| | 链路无法建立 | 自动协商完成位(0x01.5)| 检查广告能力寄存器设置 | | 传输误码率高 | 错误计数器(0x1A-0x1B) | 调整RGMII时序延迟 | | 管理接口无响应 | PHY地址配置 | 测量引脚电平确认地址设置 | **重要提示**:不同厂商PHY芯片的特殊寄存器布局需参考对应数据手册,如Marvell 88E1111的扩展寄存器需先设置Page Select寄存器(0x16)。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值