【【通信协议之MDIO读写的FPGA实现】】

通信协议之MDIO读写的FPGA实现

介绍

MAC与PHY之间的通信通过了MDIO的接口
与PHY芯片的通信则通过MAC(Media Access Control)控制器来实现。MAC控制器与PHY芯片之间的通信通常包括两部分:
1.数据传输接口
:比如MII(Media Independent Interface)、RMII(Reduced Media Independent Interface)、GMII(Gigabit Media Independent Interface)等,用于传输实际的数据包。
2.管理接口
:MDIO接口和MDC(Management Data Clock)用于对PHY芯片进行配置、状态监控和管理。通过MDIO接口,MAC控制器可以访问PHY芯片的寄存器,读取和修改PHY的工作模式、状态等信息。
MDIO接口的作用
MDIO接口是标准的IEEE 802.3规范定义的,用于在MAC和PHY之间进行管理和配置的低速控制通道。其主要功能包括:

  • 配置PHY寄存器:通过MDIO接口,可以设置PHY的工作模式(如速度、双工模式等)。
  • 读取PHY状态:MAC可以通过MDIO接口查询PHY的状态,如链路是否建立、连接速率等。
    工作原理
    MDIO接口由两条信号线组成:
  • MDC(Management Data Clock):时钟信号,由MAC控制器生成,用于同步MDIO数据传输。
  • MDIO(Management Data Input/Output):双向数据线,用于传输PHY寄存器地址、数据等信息。
    通过MDIO接口,MAC控制器可以在PHY芯片的多个寄存器之间进行读写操作,从而实现对PHY的管理。

MDIO接口的传输格式:

Preamble:32 位前导码,由 MAC 端发送 32 位逻辑“1”,用于同步 PHY 芯片。
ST(Start of Frame):2 位帧开始信号,用 01 表示。
OP(Operation Code):2 位操作码,读:10 写:01。
PHYAD(PHY Address):5 位 PHY 地址,用于表示与哪个 PHY 芯片通信,因此一个 MAC 上可以连
接多个 PHY 芯片。
REGAD(Register Address):5 位寄存器地址,可以表示 32 个寄存器。
TA(Turnaround):2 位转向,在读命令中,MDIO 在此时由 MAC 驱动改为 PHY 驱动,在第一个 TA
位,MDIO 引脚为高阻状态,第二个 TA 位,PHY 将 MDIO 引脚拉低,准备发送数据;在写命令中,不需
要 MDIO 方向发生变化,MAC 固定输出 2’b10,随后开始写入数据。
DATA:16 位数据,在读命令中,PHY 芯片将对应的 PHYAD 的 REGAD 寄存器的数据写到 DATA 中;
在写命令中,PHY 芯片将接收到的 DATA 写入 REGAD 寄存器中。需要注意的是,在 DATA 传输的过程中,
高位在前,低位在后。
IDLE:空闲状态,此时 MDIO 为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高电平。
【【通信协议之MDIO读写的FPGA实现】】_第1张图片

其实不过是读还是写,都是在时钟的上升沿完成采样,在下降沿完成对数据的更新。只是不同的是,读数据需要将后面的控制权切换给PHY芯片,而写是完整的MAC控制。

verilog代码

mdo_rw_test.v — top


module mdio_rw_test(
    input          sys_clk  ,
    input          sys_rst_n,
    //MDIO接口
    output         eth_mdc  , //PHY管理接口的时钟信号
    inout          eth_mdio , //PHY管理接口的双向数据信号
    output         eth_rst_n, //以太网复位信号

    input          touch_key, //触摸按键
    output  [1:0]  led        //LED连接速率指示
  );

  //wire define
  wire          op_exec    ;  //触发开始信号
  wire          op_rh_wl   ;  //低电平写,高电平读
  wire  [4:0]   op_addr    ;  //寄存器地址
  wire  [15:0]  op_wr_data ;  //写入寄存器的数据
  wire          op_done    ;  //读写完成
  wire  [15:0]  op_rd_data ;  //读出的数据
  wire          op_rd_ack  ;  //读应答信号 0:应答 1:未应答
  wire          dri_clk    ;  //驱动时钟

  //硬件复位
  assign eth_rst_n = sys_rst_n;

  //MDIO接口驱动
  mdio_dri #(
             .PHY_ADDR    (5'h04),    //PHY地址 3'b100
             .CLK_DIV     (6'd10)     //分频系数
           )
           u_mdio_dri(
             .clk        (sys_clk),
             .rst_n      (sys_rst_n),
             .op_exec    (op_exec   ),
             .op_rh_wl   (op_rh_wl  ),
             .op_addr    (op_addr   ),
             .op_wr_data (op_wr_data),
             .op_done    (op_done   ),
             .op_rd_data (op_rd_data),
             .op_rd_ack  (op_rd_ack ),
             .dri_clk    (dri_clk   ),

             .eth_mdc    (eth_mdc   ),
             .eth_mdio   (eth_mdio  )
           );

  //MDIO接口读写控制
  mdio_ctrl  u_mdio_ctrl(
               .clk           (dri_clk),
               .rst_n         (sys_rst_n ),
               .soft_rst_trig (touch_key ),
               .op_done       (op_done   ),
               .op_rd_data    (op_rd_data),
               .op_rd_ack     (op_rd_ack ),
               .op_exec       (op_exec   ),
               .op_rh_wl      (op_rh_wl  ),
               .op_addr       (op_addr   ),
               .op_wr_data    (op_wr_data),
               .led           (led       )
             );

endmodule

mdio_dri.v

module mdio_dri #(
    parameter  PHY_ADDR = 5'b00001,//PHY地址
    parameter  CLK_DIV  = 6'd10    //分频系数
  )
  (
    input                clk       , //时钟信号
    input                rst_n     , //复位信号,低电平有效
    input                op_exec   , //触发开始信号
    input                op_rh_wl  , //低电平写,高电平读
    input        [4:0]   op_addr   , //寄存器地址
    input        [15:0]  op_wr_data, //写入寄存器的数据
    output  reg          op_done   , //读写完成
    output  reg  [15:0]  op_rd_data, //读出的数据
    output  reg          op_rd_ack , //读应答信号 0:应答 1:未应答
    output  reg          dri_clk   , //驱动时钟

    output  reg          eth_mdc   , //PHY管理接口的时钟信号
    inout                eth_mdio    //PHY管理接口的双向数据信号
  );

  //parameter define
  localparam st_idle    = 6'b00_0001;  //空闲状态
  localparam st_pre     = 6'b00_0010;  //发送PRE(前导码)
  localparam st_start   = 6'b00_0100;  //开始状态,发送ST(开始)+OP(操作码)
  localparam st_addr    = 6'b00_1000;  //写地址,发送PHY地址+寄存器地址
  localparam st_wr_data = 6'b01_0000;  //TA+写数据
  localparam st_rd_data = 6'b10_0000;  //TA+读数据

  //reg define
  reg    [5:0]  cur_state ;
  reg    [5:0]  next_state;

  reg    [5:0]  clk_cnt   ;  //分频计数
  reg   [15:0]  wr_data_t ;  //缓存写寄存器的数据
  reg    [4:0]  addr_t    ;  //缓存寄存器地址
  reg    [6:0]  cnt       ;  //计数器
  reg           st_done   ;  //状态开始跳转信号
  reg    [1:0]  op_code   ;  //操作码  2'b01(写)  2'b10()
  reg           mdio_dir  ;  //MDIO数据(SDA)方向控制
  reg           mdio_out  ;  //MDIO输出信号
  reg   [15:0]  rd_data_t ;  //缓存读寄存器数据

  //wire define
  wire          mdio_in    ; //MDIO数据输入
  wire   [5:0]  clk_divide ; //PHY_CLK的分频系数

  assign eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
  assign mdio_in = eth_mdio;                    //MDIO数据输入
  //将PHY_CLK的分频系数除以2,得到dri_clk的分频系数,方便对MDC和MDIO信号操作
  assign clk_divide = CLK_DIV >> 1;

  //分频得到dri_clk时钟
  always @(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      dri_clk <=  1'b0;
      clk_cnt <= 1'b0;
    end
    else if(clk_cnt == clk_divide[5:1] - 1'd1)
    begin
      clk_cnt <= 1'b0;
      dri_clk <= ~dri_clk;
    end
    else
      clk_cnt <= clk_cnt + 1'b1;
  end

  //产生PHY_MDC时钟
  always @(posedge dri_clk or negedge rst_n)
  begin
    if(!rst_n)
      eth_mdc <= 1'b1;
    else if(cnt[0] == 1'b0)
      eth_mdc <= 1'b1;
    else
      eth_mdc <= 1'b0;
  end

  //(三段式状态机)同步时序描述状态转移
  always @(posedge dri_clk or negedge rst_n)
  begin
    if(!rst_n)
      cur_state <= st_idle;
    else
      cur_state <= next_state;
  end

  //组合逻辑判断状态转移条件
  always @(*)
  begin
    next_state = st_idle;
    case(cur_state)
      st_idle :
      begin
        if(op_exec)
          next_state = st_pre;
        else
          next_state = st_idle;
      end
      st_pre :
      begin
        if(st_done)
          next_state = st_start;
        else
          next_state = st_pre;
      end
      st_start :
      begin
        if(st_done)
          next_state = st_addr;
        else
          next_state = st_start;
      end
      st_addr :
      begin
        if(st_done)
        begin
          if(op_code == 2'b01)                //MDIO接口写操作
            next_state = st_wr_data;
          else
            next_state = st_rd_data;        //MDIO接口读操作
        end
        else
          next_state = st_addr;
      end
      st_wr_data :
      begin
        if(st_done)
          next_state = st_idle;
        else
          next_state = st_wr_data;
      end
      st_rd_data :
      begin
        if(st_done)
          next_state = st_idle;
        else
          next_state = st_rd_data;
      end
      default :
        next_state = st_idle;
    endcase
  end

  //时序电路描述状态输出
  always @(posedge dri_clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      cnt <= 5'd0;
      op_code <= 1'b0;
      addr_t <= 1'b0;
      wr_data_t <= 1'b0;
      rd_data_t <= 1'b0;
      op_done <= 1'b0;
      st_done <= 1'b0;
      op_rd_data <= 1'b0;
      op_rd_ack <= 1'b1;
      mdio_dir <= 1'b0;
      mdio_out <= 1'b1;
    end
    else
    begin
      st_done <= 1'b0 ;
      cnt     <= cnt +1'b1 ;
      case(cur_state)
        st_idle :
        begin
          mdio_out <= 1'b1;
          mdio_dir <= 1'b0;
          op_done <= 1'b0;
          cnt <= 7'b0;
          if(op_exec)
          begin
            op_code <= {op_rh_wl,~op_rh_wl}; //OP_CODE: 2'b01(写)  2'b10()
            addr_t <= op_addr;
            wr_data_t <= op_wr_data;
            op_rd_ack <= 1'b1;
          end
        end
        st_pre :
        begin                          //发送前导码:32个1bit
          mdio_dir <= 1'b1;                   //切换MDIO引脚方向:输出
          mdio_out <= 1'b1;                   //MDIO引脚输出高电平
          if(cnt == 7'd62)
            st_done <= 1'b1;
          else if(cnt == 7'd63)
            cnt <= 7'b0;
        end
        st_start  :
        begin
          case(cnt)
            7'd1 :
              mdio_out <= 1'b0;        //发送开始信号 2'b01
            7'd3 :
              mdio_out <= 1'b1;
            7'd5 :
              mdio_out <= op_code[1];  //发送操作码
            7'd6 :
              st_done <= 1'b1;
            7'd7 :
            begin
              mdio_out <= op_code[0];
              cnt <= 7'b0;
            end
            default :
              ;
          endcase
        end
        st_addr :
        begin
          case(cnt)
            7'd1 :
              mdio_out <= PHY_ADDR[4]; //发送PHY地址
            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_t[4];  //发送寄存器地址
            7'd13:
              mdio_out <= addr_t[3];
            7'd15:
              mdio_out <= addr_t[2];
            7'd17:
              mdio_out <= addr_t[1];
            7'd18:
              st_done <= 1'b1;
            7'd19:
            begin
              mdio_out <= addr_t[0];
              cnt <= 7'd0;
            end
            default :
              ;
          endcase
        end
        st_wr_data :
        begin
          case(cnt)
            7'd1 :
              mdio_out <= 1'b1;         //发送TA,写操作(2'b10)
            7'd3 :
              mdio_out <= 1'b0;
            7'd5 :
              mdio_out <= wr_data_t[15];//发送写寄存器数据
            7'd7 :
              mdio_out <= wr_data_t[14];
            7'd9 :
              mdio_out <= wr_data_t[13];
            7'd11:
              mdio_out <= wr_data_t[12];
            7'd13:
              mdio_out <= wr_data_t[11];
            7'd15:
              mdio_out <= wr_data_t[10];
            7'd17:
              mdio_out <= wr_data_t[9];
            7'd19:
              mdio_out <= wr_data_t[8];
            7'd21:
              mdio_out <= wr_data_t[7];
            7'd23:
              mdio_out <= wr_data_t[6];
            7'd25:
              mdio_out <= wr_data_t[5];
            7'd27:
              mdio_out <= wr_data_t[4];
            7'd29:
              mdio_out <= wr_data_t[3];
            7'd31:
              mdio_out <= wr_data_t[2];
            7'd33:
              mdio_out <= wr_data_t[1];
            7'd35:
              mdio_out <= wr_data_t[0];
            7'd37:
            begin
              mdio_dir <= 1'b0;
              mdio_out <= 1'b1;
            end
            7'd39:
              st_done <= 1'b1;
            7'd40:
            begin
              cnt <= 7'b0;
              op_done <= 1'b1;      //写操作完成,拉高op_done信号
            end
            default :
              ;
          endcase
        end
        st_rd_data :
        begin
          case(cnt)
            7'd1 :
            begin
              mdio_dir <= 1'b0;            //MDIO引脚切换至输入状态
              mdio_out <= 1'b1;
            end
            7'd2 :
              ;                         //TA[1]位,该位为高阻状态,不操作
            7'd4 :
              op_rd_ack <= mdio_in;     //TA[0]位,0(应答) 1(未应答)
            7'd6 :
              rd_data_t[15] <= mdio_in; //接收寄存器数据
            7'd8 :
              rd_data_t[14] <= mdio_in;
            7'd10:
              rd_data_t[13] <= mdio_in;
            7'd12:
              rd_data_t[12] <= mdio_in;
            7'd14:
              rd_data_t[11] <= mdio_in;
            7'd16:
              rd_data_t[10] <= mdio_in;
            7'd18:
              rd_data_t[9] <= mdio_in;
            7'd20:
              rd_data_t[8] <= mdio_in;
            7'd22:
              rd_data_t[7] <= mdio_in;
            7'd24:
              rd_data_t[6] <= mdio_in;
            7'd26:
              rd_data_t[5] <= mdio_in;
            7'd28:
              rd_data_t[4] <= mdio_in;
            7'd30:
              rd_data_t[3] <= mdio_in;
            7'd32:
              rd_data_t[2] <= mdio_in;
            7'd34:
              rd_data_t[1] <= mdio_in;
            7'd36:
              rd_data_t[0] <= mdio_in;
            7'd39:
              st_done <= 1'b1;
            7'd40:
            begin
              op_done <= 1'b1;             //读操作完成,拉高op_done信号
              op_rd_data <= rd_data_t;
              rd_data_t <= 16'd0;
              cnt <= 7'd0;
            end
            default :
              ;
          endcase
        end
        default :
          ;
      endcase
    end
  end

endmodule

mdio_ctrl.v

module mdio_ctrl(
    input                clk           ,
    input                rst_n         ,
    input                soft_rst_trig , //软复位触发信号
    input                op_done       , //读写完成
    input        [15:0]  op_rd_data    , //读出的数据
    input                op_rd_ack     , //读应答信号 0:应答 1:未应答
    output  reg          op_exec       , //触发开始信号
    output  reg          op_rh_wl      , //低电平写,高电平读
    output  reg  [4:0]   op_addr       , //寄存器地址
    output  reg  [15:0]  op_wr_data    , //写入寄存器的数据
    output       [1:0]   led             //LED灯指示以太网连接状态
  );

  //reg define
  reg          rst_trig_d0;
  reg          rst_trig_d1;
  reg          rst_trig_flag;   //soft_rst_trig信号触发标志
  reg  [23:0]  timer_cnt;       //定时计数器
  reg          timer_done;      //定时完成信号
  reg          start_next;      //开始读下一个寄存器标致
  reg          read_next;       //处于读下一个寄存器的过程
  reg          link_error;      //链路断开或者自协商未完成
  reg  [2:0]   flow_cnt;        //流程控制计数器
  reg  [1:0]   speed_status;    //连接速率

  //wire define
  wire         pos_rst_trig;    //soft_rst_trig信号上升沿

  //采soft_rst_trig信号上升沿
  assign pos_rst_trig = ~rst_trig_d1 & rst_trig_d0;
  //未连接或连接失败时led赋值00
  // 01:10Mbps  10:100Mbps  11:1000Mbps 00:其他情况
  assign led = link_error ? 2'b00: speed_status;
  //对soft_rst_trig信号延时打拍
  always @(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      rst_trig_d0 <= 1'b0;
      rst_trig_d1 <= 1'b0;
    end
    else
    begin
      rst_trig_d0 <= soft_rst_trig;
      rst_trig_d1 <= rst_trig_d0;
    end
  end

  //定时计数
  always @(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      timer_cnt <= 1'b0;
      timer_done <= 1'b0;
    end
    else
    begin
      if(timer_cnt == 24'd1_000_000 - 1'b1)
      begin
        timer_done <= 1'b1;
        timer_cnt <= 1'b0;
      end
      else
      begin
        timer_done <= 1'b0;
        timer_cnt <= timer_cnt + 1'b1;
      end
    end
  end

  //根据软复位信号对MDIO接口进行软复位,并定时读取以太网的连接状态
  always @(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
    begin
      flow_cnt <= 3'd0;
      rst_trig_flag <= 1'b0;
      speed_status <= 2'b00;
      op_exec <= 1'b0;
      op_rh_wl <= 1'b0;
      op_addr <= 1'b0;
      op_wr_data <= 1'b0;
      start_next <= 1'b0;
      read_next <= 1'b0;
      link_error <= 1'b0;
    end
    else
    begin
      op_exec <= 1'b0;
      if(pos_rst_trig)
        rst_trig_flag <= 1'b1;             //拉高软复位触发标志
      case(flow_cnt)
        2'd0 :
        begin
          if(rst_trig_flag)
          begin        //开始对MDIO接口进行软复位
            op_exec <= 1'b1;
            op_rh_wl <= 1'b0;
            op_addr <= 5'h00;
            op_wr_data <= 16'h9140;    //Bit[15]=1'b1,表示软复位
            flow_cnt <= 3'd1;
          end
          else if(timer_done)
          begin      //定时完成,获取以太网连接状态
            op_exec <= 1'b1;
            op_rh_wl <= 1'b1;
            op_addr <= 5'h01;
            flow_cnt <= 3'd2;
          end
          else if(start_next)
          begin       //开始读下一个寄存器,获取以太网通信速度
            op_exec <= 1'b1;
            op_rh_wl <= 1'b1;
            op_addr <= 5'h11;
            flow_cnt <= 3'd2;
            start_next <= 1'b0;
            read_next <= 1'b1;
          end
        end
        2'd1 :
        begin
          if(op_done)
          begin              //MDIO接口软复位完成
            flow_cnt <= 3'd0;
            rst_trig_flag <= 1'b0;
          end
        end
        2'd2 :
        begin
          if(op_done)
          begin              //MDIO接口读操作完成
            if(op_rd_ack == 1'b0 && read_next == 1'b0) //读第一个寄存器,接口成功应答,
              flow_cnt <= 3'd3;                      //读第下一个寄存器,接口成功应答
            else if(op_rd_ack == 1'b0 && read_next == 1'b1)
            begin
              read_next <= 1'b0;
              flow_cnt <= 3'd4;
            end
            else
            begin
              flow_cnt <= 3'd0;
            end
          end
        end
        2'd3 :
        begin
          flow_cnt <= 3'd0;          //链路正常并且自协商完成
          if(op_rd_data[5] == 1'b1 && op_rd_data[2] == 1'b1)
          begin
            start_next <= 1;
            link_error <= 0;
          end
          else
          begin
            link_error <= 1'b1;
          end
        end
        3'd4:
        begin
          flow_cnt <= 3'd0;
          if(op_rd_data[15:14] == 2'b10)
            speed_status <= 2'b11; //1000Mbps
          else if(op_rd_data[15:14] == 2'b01)
            speed_status <= 2'b10; //100Mbps
          else if(op_rd_data[15:14] == 2'b00)
            speed_status <= 2'b01; //10Mbps
          else
            speed_status <= 2'b00; //其他情况
        end
      endcase
    end
  end

endmodule

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