以太网实验1.mdio接口读写实验

以太网常见硬件组成:

fpga首先发送数据到经过udp层、ip层以及mac层的封装
主要有MAC控制器、PHY芯片、网络变压器和RJ45接头组成,有的系统会有DMA控制。一般的系统中CPU和MAC以及DMA控制器都是集成在一块芯片上的,为了节省空间简化设计,很多时候网口的变压器和RJ45的接头集成在一起。
MAC 及 PHY 工作在 OSI 七层模型的数据链路层和物理层。
mii接口(Media Independent Interface):
MII 接口提供了 MAC 与 PHY 之间、PHY 与 STA(Station Management)之间的互联技术。
MII 接口主要包括四个部分。一是从 MAC 层到 PHY 层的发送数据接口,二是从 PHY 层到 MAC 层的接收数据接口,三是从PHY 层到 MAC 层的状态指示信号,四是 MAC 层和 PHY 层之间传送控制和状态信息的 MDIO 接口。

mdio接口

MDIO为双向信号,用来在MAC和PHY芯片之间的传递控制和状态信息。写寄存器MAC驱动,读寄存器时PHY驱动。
数据传输时先传高位(MSB),后传低位(LSB)。输出采用三态电路设计,MDIO需要接1.5K~10K的上拉电阻。通过MDIO线上是否有上拉电阻,来检测MDIO接口是否连接到PHY芯片上。
MDIO前后有两种协议, 包括Clause22 以及之后的Clause45, Clause 45 兼容Clause 22。
以太网实验1.mdio接口读写实验_第1张图片
MDIO: 是 PHY 和 STA 之间的双向信号。 它用于在 PHY 和 STA 之间传输控制信息和状态。 控制信息由 STA 同步地针对 MDC 驱动并且由 PHY 同步地采样。 状态信息由 PHY 针对 MDC 同步驱动并由 STA 同步采样。
MDC: 由站管理实体向 PHY 提供,作为在 MDIO 信号上传送信息的定时参考。 MDC 是一种非周期性的信号,没有最高或最低时间。 无论 TX_CLK 和 RX_CLK 的标称周期如何,MDC 的最小高低时间应为 160 ns,MDC 的最小周期为 400 ns。

MDIO-Clause22帧格式:
依次为空闲标志、前导标志、开始标志、操作码、PHY地址、寄存器地址,翻转标志位和数据。
空闲标志(idle):无MDIO帧发送时,MDIO接口输出高阻(外部有上拉电阻,总线上看到的是高电平)。

前导标志(PRE):每帧发送前,MAC通过MDIO连续发送32个MDC周期的高电平1,同时通过MDC输出32个时钟周期。前导的作用是为PHY建立同步提供时间。如果STA能够确定PHY可以接收管理帧,可以压缩前导的发送。

开启标志(st):长度2Bits,必须为01,标志该数据帧开始。

操作码(op):长度2Bits,10标志为读操作,01标志为写操作。

PHY地址(phyad):长度5Bits,表示所访问的PHY地址,一个MDIO总线最大支持32个PHY。

寄存器地址(regad):长度5Bits,表示所访问的寄存器的地址,共计32个寄存器。IEEE802.3协议中对前16个寄存器进行了定义,其中比较常用的如下表。其余为PHY芯片自定寄存器。

翻转标志位(ta):长度2Bits,固定为10。该标志位为PHY芯片地址传输和数据传输处理预留处理时间,设置2bit TA的目的就是为了防止MDIO总线上产生竞争。

数据(data):长度16Bits,操作符为读操作时,该数据为对于地址PHY的特定寄存器的数值;操作符为写时,该数据为对该寄存器写入的数值。

帧格式Clause45:

MDIO Clause45为了访问更多的寄存器在Clause22基础上做了一些扩展,修改如下:

1)ST由01修改为00

2)OP进行了重新定义。00:地址帧 01:写 11:读 10:增量读(Post-read-increment-address)

3)PHYAD域修改名称为PRTAD,端口地址但仍代表PHY地址

4)REGAD修改为DEVAD,Clause45将PHY内部子模块的地址进行细分,这些子模块用DEVAD寻址。子模块内部的寄存器则使用地址帧进行寻址。

mdio时序:
以太网实验1.mdio接口读写实验_第2张图片

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

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

以太网芯片YT8511支持两种复位方式,一种是硬件复位,另外一种是软件复位。硬件复位时通过ETH_RST_N引脚实现对PHY芯片的复位,当ETH_RST_N引脚持续10ms的低电平时,即可实现对PHY芯片的复位。软件复位通过向寄存器地址0x00的Bit[15]写入1进行复位,并且在完成复位后,该位会自动清零。YT8511共有22位寄存器,有些寄存器是仅可读的,有些寄存器是仅可写的,还有些寄存器是可读可写的,当然在程序设计中我们不需要把这些寄存器全部都用上,一般来说寄存器0x00、0x01、0x1F是3个最常用的寄存器,即基本控制寄存器、基本状态寄存器、PHY控制寄存器。

对基本控制寄存器有个软复位操作,即MAC控制器可以通过MDIO协议向PHY发送软复位请求,在大多数情况下软复位一次后,即可读取到正确的自协商结果。

对基本状态寄存器,用户通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断目前自协商状态,注意到寄存器的第5位为1时,代表自协商完成,为0时代表自协商未完成;寄存器的第5位为1时,代表网线的物理层连接成功,为0时代表连接失败。

对PHY控制寄存器,通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断自协商后连接速度,注意到寄存器的第6位为1时,代表千兆速度;寄存器的第5位为1时,代表百兆速度;而寄存器的第4位为1时,代表十兆速

首先每隔一段时间通过MDIO接口从PHY内部寄存器中读取基本状态寄存器(BMSR)和特定状态寄存器(PHYSR)的值,从而获取到自协商完成状态、连接状态和连接速度,将网口的连接速度通过LED灯进行指示;当FPGA检测到TPAD触摸按键按下时,开始通过MDIO接口对PHY进行软复位,在软复位完成后,PHY会重新开始自协商,此时LED灯会定时获取当前网口的连接状态以及连接速度。
mdio驱动模块代码:

module mdio_driver
  (input clk,input rstn,
   input en,input wl_rh,input[4:0]addr,
   input [15:0]wr_data,
   output reg rd_ack,op_done,dri_clk,eth_mdc,
   output reg[15:0]rd_data,inout eth_mdio);
  parameter  PHY_ADDR = 5'b00001,//PHY地址
             CLK_DIV  = 6'd10 ;   //分频系数
  localparam st_idle    = 6'b00_0001,  //空闲状态
             st_pre     = 6'b00_0010,  //发送PRE(前导码)
             st_start   = 6'b00_0100,  //开始状态,发送ST(开始)+OP(操作码)
             st_addr    = 6'b00_1000,  //写地址,发送PHY地址+寄存器地址
             st_wr_data = 6'b01_0000,  //TA+写数据
             st_rd_data = 6'b10_0000;  //TA+读数据
  reg[5:0]cstate,nstate,clk_cnt;
  reg[6:0]cnt;
  reg[15:0]wr_data_d0,rd_data_d0;
  reg[1:0]op_code;
  reg mdio_dir,mdio_out;
  reg[4:0]addr_d0;
  wire[5:0]clk_divide;
  reg done_flag;
  wire mdio_in;
  assign mdio_in=eth_mdio,
         eth_mdio=mdio_dir?mdio_out:1'bz,
         clk_divide = CLK_DIV >> 2;

  always @(posedge clk or negedge rstn)
  begin
    if(!rstn)
    begin
      dri_clk='d0;
      clk_cnt<='d0;
    end
    else if(clk_cnt==clk_divide-'d1)
    begin
      clk_cnt<='d0;
      dri_clk<=~dri_clk;
    end
    else
      clk_cnt<=clk_cnt+'d1;
  end
  always @(posedge dri_clk or negedge rstn)
  begin
    if(!rstn)
      eth_mdc<='d1;
    else if(cnt[0]=='d0)
      eth_mdc<='d1;
    else
      eth_mdc<='d0;
  end
  always @(posedge dri_clk or negedge rstn)
  begin
    if(!rstn)
      cstate<=st_idle;
    else
      cstate<=nstate;
  end
  always @(*)
  begin
   
    case(cstate)
      st_idle:
        if(en)
          nstate=st_pre;
        else
          nstate=st_idle;
      st_pre:
        if(done_flag=='d1)
          nstate=st_start;
        else
         nstate=st_pre ;
      st_start:
        if(done_flag=='d1)
          nstate=st_addr;
        else
          nstate=st_start;
      st_addr:
        if(done_flag=='d1)
        begin
          if(op_code==2'b01)
            nstate=st_wr_data;
          else
            nstate=st_rd_data;
        end
        else
         nstate=st_addr ;
      st_wr_data:
        if(done_flag=='d1)
          nstate=st_idle;
        else
          nstate=st_wr_data;
      st_rd_data:
        if(done_flag=='d1)
          nstate=st_idle;
        else
         nstate=st_rd_data ;
      default :
      nstate=st_idle;
    endcase
  end
  always @(posedge dri_clk or negedge rstn)
  begin
    if(!rstn)
    begin
      cnt<='d0;
      op_code<='d0;
      mdio_dir<='d0;
      mdio_out<='d1;
      addr_d0<='d0;
      rd_data<='d0;
      wr_data_d0<='d0;
      rd_data_d0<='d0;
      rd_ack<='d1;
      op_done<='d0;
      done_flag<='d0;
    end
    else
    begin
      done_flag<='d0;
      cnt<=cnt+'d1;
      case(cstate)
        st_idle:
        begin
          mdio_out<='d1;
          mdio_dir<='d0;
          op_done<='d0;
          cnt<='d0;
          if(en)
          begin
            op_code<={wl_rh,~wl_rh};
            addr_d0<=addr;
            wr_data_d0<=wr_data;
            rd_ack<='d1;
          end
        end
        st_pre:
        begin
          mdio_dir<='d1;
          mdio_out<='d1;
          if(cnt=='d62)
            done_flag<='d1;
          else if(cnt=='d63)
            cnt<='d0;
        end
        st_start:
        begin
          case(cnt)
            7'd1:
              mdio_out<='d0;
            7'd3:
              mdio_out<='d1;
            7'd5:
              mdio_out<=op_code[1];
            7'd6:
              done_flag<='d1;
            7'd7:
            begin
              mdio_out<=op_code[0];
              cnt<='d0;
            end
            default:
              ;
          endcase
        end
        st_addr:
        begin
          case(cnt)
            7'd1:
              mdio_out<=PHY_ADDR[4];
            7'd3:
              mdio_out<=PHY_ADDR[3];
            7'd5:
              mdio_out<=PHY_ADDR[2];
            7'd7:
              mdio_out<=PHY_ADDR[1];
            7'd9:
              mdio_out<=PHY_ADDR[0];
            7'd11:
              mdio_out<=addr_d0[4];
            7'd13:
              mdio_out<=addr_d0[3];
            7'd15:
              mdio_out<=addr_d0[2];
            7'd17:
              mdio_out<=addr_d0[1];
            7'd18:
              done_flag<='d1;
            7'd19:
            begin
              mdio_out<=addr_d0[0];
              cnt<='d0;
            end
            default:
              ;
          endcase
        end
        st_wr_data:
        begin
          case(cnt)
            7'd1:
              mdio_out<='d1;
            7'd3:
              mdio_out<='d0;//op_code 10
            7'd5:
              mdio_out<=wr_data_d0[15];
            7'd7:
              mdio_out<=wr_data_d0[14];
            7'd9:
              mdio_out<=wr_data_d0[13];
            7'd11:
              mdio_out<=wr_data_d0[12];
            7'd13:
              mdio_out<=wr_data_d0[11];
            7'd15:
              mdio_out<=wr_data_d0[10];
            7'd17:
              mdio_out<=wr_data_d0[9];
            7'd19:
              mdio_out<=wr_data_d0[8];
            7'd21:
              mdio_out<=wr_data_d0[7];
            7'd23:
              mdio_out<=wr_data_d0[6];
            7'd25:
              mdio_out<=wr_data_d0[5];
            7'd27:
              mdio_out<=wr_data_d0[4];
            7'd29:
              mdio_out<=wr_data_d0[3];
            7'd31:
              mdio_out<=wr_data_d0[2];
            7'd33:
              mdio_out<=wr_data_d0[1];
            7'd35:
              mdio_out<=wr_data_d0[0];
            7'd37:
            begin
              mdio_dir<='d0;
              mdio_out<='d1;
            end
            7'd39:
              done_flag<='d1;
            7'd40:
            begin
              cnt<='d0;
              op_done<='d1;
            end
            default:
              ;
          endcase
        end
        st_rd_data:
        begin
          case(cnt)
            7'd1:
            begin
              mdio_dir<='d0;
              mdio_out<='d1;//op_code 01
            end
            7'd4:
              rd_ack<=mdio_in;
            7'd6:
              rd_data_d0[15]<=mdio_in;
            7'd8:
              rd_data_d0[14]<=mdio_in;
            7'd10:
              rd_data_d0[13]<=mdio_in;
            7'd12:
              rd_data_d0[12]<=mdio_in;
            7'd14:
              rd_data_d0[11]<=mdio_in;
            7'd16:
              rd_data_d0[10]<=mdio_in;
            7'd18:
              rd_data_d0[9]<=mdio_in;
            7'd20:
              rd_data_d0[8]<=mdio_in;
            7'd22:
              rd_data_d0[7]<=mdio_in;
            7'd24:
              rd_data_d0[6]<=mdio_in;
            7'd26:
              rd_data_d0[5]<=mdio_in;
            7'd28:
              rd_data_d0[4]<=mdio_in;
            7'd30:
              rd_data_d0[3]<=mdio_in;
            7'd32:
              rd_data_d0[2]<=mdio_in;
            7'd34:
              rd_data_d0[1]<=mdio_in;
            7'd36:
              rd_data_d0[0]<=mdio_in;
            7'd39:
              done_flag<='d1;
            7'd40:
            begin
              op_done<='d1;
              rd_data<=rd_data_d0;
              rd_data_d0<='d0;
              cnt<='d0;
            end
            default:
              ;
          endcase
        end
        default:
          ;
      endcase
    end
  end
endmodule

在st_idle状态中,如果en信号为高(或者说en = 1),则状态会转换到st_pre。否则,状态保持为st_idle。
在st_pre状态中,如果done_flag信号为’d1’(可能表示某个特定操作完成),则状态会转换到st_start。否则,状态保持为st_pre。
类似地,在接下来的每个状态中,都有特定的条件用于转换到下一个状态或保持当前状态。
在所有分支之后,有一个默认分支,如果cstate的值没有匹配任何已知的状态,那么状态将转换到st_idle。
于eth_mdc需要在输入时钟的基础上进行分频,为了方便操作,这里先对输入的时钟进行分频,得到一个dri_clk时钟,作为MDIO驱动模块和MDIO控制模块的操作时钟。eth_mdc在dri_clk的基础上进行2分频,由于输入的参数CLK_DIV为eth_mdc相对于输入时钟的分频系数,因此为了得到dri_clk的分频系数,需要将CLK_DIV除以2,如代码中第72行所示。
根据分频系数clk_divide,得到dri_clk的时钟。当cnt一直累加时,eth_mdc的时钟相当于对dri_clk进行2分频。当开始对MDIO接口进行读写操作时,cnt累加,此时才会产生eth_mdc时钟;当读写操作结束后eth_mdc将一直处于高电平。

在st_wr_data状态下,数据是在eth_mdc的下降沿写入,而在st_rd_data状态,数据在erth_mdc的上升沿读出。值得一提是,在st_rd_data状态下,程序中根据TA的第二位,判断PHY芯片有没有应答,如果没有应答,则说明读取数据失败。
mdio控制模块代码:

module ctrl_mdio(
    input clk,input rstn,
    input soft_trig,op_done,
    input[15:0]rd_data,input rd_ack,
    output reg[4:0]addr,
    output reg en,wl_rh,wr_data,output [1:0]led);
  reg rst_trig_d0,rst_trig_d1,rst_trig_flag;
  reg time_done,st_next,rd_next,error;
  reg[1:0]speed_status;
  reg[2:0]flow_cnt;
  reg[23:0]time_cnt;
  wire rst_trig_p;
  assign rst_trig_p=~rst_trig_d1&rst_trig_d0,
         led=error?2'd0:speed_status;
  always @(posedge clk or negedge rstn)
  begin
    if(!rstn)
    begin
      rst_trig_d0<='d0;
      rst_trig_d1<='d0;
    end
    else
    begin
      rst_trig_d0<=soft_trig;
      rst_trig_d1<=rst_trig_d0;
    end
  end
  always @(posedge clk or negedge rstn)
  begin
    if(!rstn)
    begin
      time_cnt<='d0;
      time_done<='d0;
    end
    else
    begin
      if(time_cnt=='d1_000_000-'d1)
      begin
        time_done<='d1;
        time_cnt<='d0;
      end
      else
      begin
        time_cnt<=time_cnt+'d1;
        time_done<='d0;
      end
    end
  end
  always @(posedge clk or negedge rstn)
  begin
    if(!rstn)
    begin
      flow_cnt<='d0;
      rst_trig_flag<='d0;
      speed_status<='d0;
      en<='d0;
      wl_rh<='d0;
      addr<='d0;
      wr_data<='d0;
      st_next<='d0;
      rd_next<='d0;
      error<='d0;
    end
    else
    begin
      en<='d0;
      if(rst_trig_p=='d1)
        rst_trig_flag<='d1;
      case(flow_cnt)
        3'd0:
        begin
          if(rst_trig_flag=='d1)
          begin
            en<='d1;
            wl_rh<='d0;
            addr<=5'h0;
            wr_data<=16'h9140;
            flow_cnt<=3'd1;
          end
          else if(time_done)
          begin
            en<='d1;
            wl_rh<='d1;
            addr<=5'd1;
            flow_cnt<=3'd2;
          end
          else if(st_next)
          begin
            en<='d1;
            wl_rh<='d1;
            addr<=5'h11;
            flow_cnt<=3'd2;
            st_next<='d0;
            rd_next<='d1;
          end
          else
            ;
        end
        3'd1:
          if(op_done)
          begin
            flow_cnt<=3'd0;
            rst_trig_flag<='d0;
          end
          else
            ;
        3'd2:
          if(op_done)
          begin
            if(rd_ack=='d0&&rd_next=='d0)
              flow_cnt<=3'd3;
            else if(rd_ack=='d0&&rd_next=='d1)
            begin
              rd_next<='d0;
              flow_cnt<=3'd4;
            end
            else
              flow_cnt<=3'd0;
          end
        3'd3:
        begin
          flow_cnt<=3'd0;
          if(rd_data[5]==1&&rd_data[2]==1)
          begin
            st_next<='d1;
            error<='d0;
          end
          else
            error<='d1;
        end
        3'd4:
        begin
          flow_cnt<=3'd0;
          case(rd_data[15:14])
            2'd2:
              speed_status<=2'b11;
            2'd1:
              speed_status<=2'b10;
            2'd0:
              speed_status<=2'b01;
            default :
              speed_status<='d0;
          endcase
        end
        default:
          ;
      endcase
    end
  end
endmodule

根据软复位信号对MDIO接口进行软复位,并定时读取以太网的连接状态。根据状态寄存器的值,为连接速率状态位speed_status赋值。
顶层模块:


module mdio_rw_top(
    input          clk  ,
    input          rstn,
    //MDIO接口
    output         eth_mdc  , //PHY管理接口的时钟信号
    inout          eth_mdio , //PHY管理接口的双向数据信号
    output         eth_rstn, //以太网复位信号
    
    input          touch_key, //触摸按键
    output  [1:0]  led        //LED连接速率指示
    );
    
//wire define
wire          en    ; //触发开始信号
wire          wl_rh   ;  //低电平写,高电平读
wire  [4:0]   addr    ;  //寄存器地址
wire  [15:0]  wr_data ;  //写入寄存器的数据
wire          op_done    ;  //读写完成
wire  [15:0]  rd_data ;  //读出的数据
wire          rd_ack  ;  //读应答信号 0:应答 1:未应答
wire          dri_clk    ;  //驱动时钟

//硬件复位
assign eth_rstn = rstn;

//MDIO接口驱动
mdio_driver #(
    .PHY_ADDR    (5'h04),    //PHY地址 3'b100
    .CLK_DIV     (6'd10)     //分频系数
    )
    u_mdio_dri(
    .clk        (clk),
    .rstn      (rstn),
    .en    (en),
    .wl_rh   (wl_rh  ),   
    .addr    (addr   ),   
    .wr_data (wr_data),   
    .op_done    (op_done   ),   
    .rd_data (rd_data),   
    .rd_ack  (rd_ack ),   
    .dri_clk    (dri_clk   ),  
                 
    .eth_mdc    (eth_mdc   ),   
    .eth_mdio   (eth_mdio  )   
);       

//MDIO接口读写控制    
ctrl_mdio  u_mdio_ctrl(
    .clk           (dri_clk),  
    .rstn         (rstn ),  
    .soft_trig (touch_key ),
    .op_done       (op_done   ),  
    .rd_data    (rd_data),  
    .rd_ack     (rd_ack ),  
    .en       (en   ),  
    .wl_rh      (wl_rh  ),  
    .addr       (addr   ),  
    .wr_data    (wr_data),  
    .led           (led       )
);      
     
endmodule

上板运行:

QQ视频20231104163807

你可能感兴趣的:(fpga学习,fpga开发)