Menu Close

以太网PHY 之 MDIO接口及应用(Verilog IP)

以太网PHY 之 MDIO接口及应用文章中,我们介绍了MDIO 的原理,本节中,主要讲述MDIO 的Verilog 语言实现。 MDIO可以通过Verilog ,VHDL, C语言等多种方法实现, 根据用户的不同需求和硬件资源情况自行处理。

由于MDIO读写过程相对比较复杂,更多细节会在视频里进行讲解。本节课通过Verilog代码分为三个部分实现,分别为MDIO IP框架设计,MDIO端口定义及描述,MDIO代码实现。下文就这三个部分展开讲解。

 

1.  MDIO IP核框架设计

MDIO的IP 框架图如下:

 

%title插图%num

图1 IP框架图

本节课将使用的MDIO  协议格式(SMI)如下:

%title插图%num

图2

%title插图%num

图3

%title插图%num

图4

根据图2,3,4中所示的MDIO协议设计FPGA程序。

 

2. 输入和输出端口定义及描述

  • phy_addr[4:0]:指定以太网PHY 的地址(根据硬件原理图连线),软件或者FPGA上层设置这个端口。
  • smi_data[24:0]:输入数据接口,25bit 包括: {st_op, phy_reg, phy_reg_val_w} <= smi_data;
  • smi_en:输入信号(enable),当smi_done == 1 时,启动数据发送,smi_en 是单时钟周期的信号。
  • smi_dout [15:0]:从以太网PHY中读出的数据, 将被送给上层软件或者FPGA。当smi_done == 1 后, 软件或者上层的FPGA就可以读取这个值了。
  • smi_done:MDIO 操作完成指示信号。 指示smi_dout 是否有效,以及上层软件或FPGA是否可以发送下一个命令。
  • sim_err:指示本次输入命令无效。
  • e_mdc:    连接 MDC pin 到以太网PHY。
  • e_mdio_i: 输入信号到MDIO pin 三态门中。
  • e_mdio_o:从MDIO pin 三态中得到的输出信号。
  • e_mdio_t :输出信号到MDIO pin 三态门中,指示当前三态门的状态(输出,输入)。

3.Verilog代码设计

以下代码基于图1中的框架设计,程序如下:

  • mdio_phy.v 代码:
`timescale 1ns / 1ps

module mdio_phy
(
   input    sys_clk,
   input    mdio_en,

   input    [3:0] st_op,  // 4'b0110 = read ; 4'b0101 = write
   input    [4:0] phy_addr,
   input    [4:0] phy_reg,

   input    [15:0] phy_reg_val_w,
   output   reg [15:0] phy_reg_val_r = 0,
   output   reg val_r_rdy = 0,
   input    us_sec,

   output   e_mdc,
   input    e_mdio_i,
   output   e_mdio_o,
   output   reg e_mdio_t = 1,

   output   reg mdio_end = 0,
   output   reg mdio_err = 0,
   input    reset
);  

//------------------------------------------------------------
reg mdc_clk_r = 0;
always @ (posedge sys_clk)
if(us_sec) mdc_clk_r <= ~mdc_clk_r;

assign   e_mdc = mdc_clk_r;


//assign   e_mdio_o = e_mdio_t ? 1'bz : 1'b0;
assign   e_mdio_o = 1'b0;

reg e_mdio_r = 1;
always @ (posedge sys_clk)
    e_mdio_r <= e_mdio_i;

//------------------------------------------------------------
reg [1:0] e_mdc_r = 0;
reg e_mdc_pos = 0;
reg e_mdc_neg = 0;
always @ (posedge sys_clk)
begin
    e_mdc_r <= {e_mdc_r[0],mdc_clk_r};
    case (e_mdc_r)
    2'b01:  begin e_mdc_pos <= 1; e_mdc_neg <= 0; end
    2'b10:  begin e_mdc_pos <= 0; e_mdc_neg <= 1; end
    default:  begin e_mdc_pos <= 0; e_mdc_neg <= 0; end 
    endcase
end
//------------------------------------------------------------
wire  [1:0] turn_ard = 2'b10;
wire  [15:0] smi_cmd_h = {st_op[3:0], phy_addr[4:0], phy_reg[4:0], turn_ard[1:0]};
//------------------------------------------------------------

reg [4:0] mdio_cnt = 0;
reg [3:0] mdio_st = 0;
always @ (posedge sys_clk or posedge reset)
if(reset)
begin
   mdio_err <= 0;
   e_mdio_t <= 1;
   mdio_cnt <= 0;
   mdio_end <= 0;
   mdio_st <= 0;
end
else case (mdio_st)
0:
begin
    val_r_rdy <= 0;
    e_mdio_t <= 1;
    mdio_cnt <= 0;
    mdio_end <= 0;
    
    if(mdio_en & (|st_op[1:0])) 
    begin
        mdio_end <= 0;
        mdio_err <= 0;
        mdio_st <= 1;
    end
end
1:  // 32 preamble
begin
    if(e_mdc_neg)
    begin
        e_mdio_t <= 1'b1;
        mdio_cnt <= mdio_cnt + 1;
    end
    
    if(&mdio_cnt) 
        mdio_st <= 2;
end
2:
begin    
    mdio_cnt <= 0;
    
    case(st_op[1:0])
    2'b10: mdio_st <= 3; //read
    2'b01: mdio_st <= 8; //write 
    default:  begin mdio_st <= 12; mdio_err <= 1; end
    endcase
end
3: //read
begin
   if(e_mdc_neg)
   begin
      e_mdio_t <= smi_cmd_h[15 - mdio_cnt];
      mdio_cnt <= mdio_cnt + 1;
      mdio_st <= 4;
   end
end
4:
begin
   if(mdio_cnt == 15) mdio_st <= 5;
   else mdio_st <= 3;
end
5: // turn around
begin
    mdio_cnt <= 0;
    if(e_mdc_neg) 
    begin
        e_mdio_t <= 1'b1;
        mdio_st <= 6;
    end
end
6:
begin
    if(e_mdc_neg) 
    begin
        phy_reg_val_r[15 - mdio_cnt] <= e_mdio_r;
        mdio_cnt <= mdio_cnt + 1;
        mdio_st <= 7;
    end
end
7:
begin
    if(mdio_cnt == 16)
    begin
        val_r_rdy <= 1;
        mdio_st <= 14;
    end
    else mdio_st <= 6;
end

8: //write
begin
   if(e_mdc_neg)
   begin
      e_mdio_t <= smi_cmd_h[15 - mdio_cnt];
      mdio_cnt <= mdio_cnt + 1;
      mdio_st <= 9;
   end
end
9:
begin
    if(mdio_cnt == 16) 
    begin
        mdio_cnt <= 0;
        mdio_st <= 10;
    end
    else mdio_st <= 8;
end
10:
begin
    if(e_mdc_neg)
    begin
        e_mdio_t <= phy_reg_val_w[15 - mdio_cnt];
        mdio_cnt <= mdio_cnt + 1;
        mdio_st <= 11;
    end
end
11:
begin
    if(mdio_cnt == 16)
        mdio_st <= 14;
    else mdio_st <= 10;
end
12: // error
begin
    mdio_err <= 0;
    mdio_cnt <= 0;
    mdio_st <= 13;
end
13:
begin
   if(e_mdc_neg) mdio_cnt <= mdio_cnt + 1;
   if(mdio_cnt == 31) 
   begin
      mdio_end <= 1;
      mdio_st <= 0;
   end
end
14: // end
begin
    val_r_rdy <= 0;
    if(e_mdc_neg)
    begin
        e_mdio_t <= 1'b1;
        mdio_st <= 15;
    end
end
15:
begin
    if(e_mdc_neg)
    begin
        mdio_end <= 1;
        mdio_st <= 0;
    end
end
endcase


endmodule

 

  • mdio_core.v 代码:
`timescale 1ns / 1ps


module mdio_core #
(
    parameter   MAIN_CLK = 125,
    parameter   PHY_SPEED = "AUTO"  // "AUTO", "1G", "100M", "10M"
)
(
    input   sys_clk,
    input   [4:0] phy_addr,
    
    input   [24:0] smi_data,
    input   smi_en,

    output  e_mdc,
    input   e_mdio_i,
    output  e_mdio_o,
    output  e_mdio_t,

    output  reg [15:0] smi_dout = 0,
    
    output  reg smi_done = 1'b0,
    output  smi_err,
    
    output  [1:0] test_pin,
    input   reset
);

//------------------------------------------------------------
localparam SMI_ST = 2'b01;
localparam SMI_IDLE = 2'b11;

localparam WR = 2'b01;
localparam RD = 2'b10;
localparam NO = 2'b00;
//------------------------------------------------------------
localparam US_CNT = (MAIN_CLK == 125) ? 124 : (MAIN_CLK == 100) ? 99 : (MAIN_CLK == 50) ? 49 : 49;

reg [7:0] us_cnt_r = 0;
wire us_sec = (us_cnt_r == US_CNT) ? 1'b1 : 1'b0;

always @ (posedge sys_clk)
if(us_sec) us_cnt_r <= 0;
else us_cnt_r <= us_cnt_r + 1;
//------------------------------------------------------------
wire  mdio_end;
wire  phy_err;
reg   cmd_err = 0;

reg mdio_en = 0;
reg [3:0] st_op = {SMI_IDLE,NO};
reg [4:0] phy_reg = 0;
reg [15:0] phy_reg_val_w = 0;

reg [2:0] ctrl_st = 0;
always @ (posedge sys_clk or posedge reset)
if(reset)
begin
    cmd_err <= 0;
    smi_done <= 1'b1;
    st_op <= {SMI_IDLE,NO};
    mdio_en <= 1'b0;
    ctrl_st <= 0;
end
else case(ctrl_st)
0:
begin
    mdio_en <= 1'b0;
    smi_done <= 1'b1;
    if(smi_en)
    begin
        smi_done <= 1'b0;
        cmd_err <= 0;

        {st_op, phy_reg, phy_reg_val_w} <= smi_data;
        ctrl_st <= 1;
    end
end
1:
begin
    if(st_op[3:2] == SMI_ST) //SFD
    begin
        mdio_en <= 1'b1;
        ctrl_st <= 2;
    end
    else     
    begin
        cmd_err <= 1;
        ctrl_st <= 0;
    end

end
2:
begin
    mdio_en <= 1'b0;
    if(~mdio_end)
        ctrl_st <= 3;
end
3:
begin
    if(mdio_end)
    begin
        smi_done <= 1'b1;
        ctrl_st <= 0;
    end
end
default: ctrl_st <= 0;
endcase

wire [15:0] phy_reg_val_r;
wire val_r_rdy;
//------------------------------------------------------------
mdio_phy   mdio_phy_inst
(
    .sys_clk        (sys_clk),
    .mdio_en        (mdio_en),

    .st_op          (st_op),
    .phy_addr       (phy_addr),
    .phy_reg        (phy_reg),

    .phy_reg_val_w  (phy_reg_val_w),
    .phy_reg_val_r  (phy_reg_val_r),
    .val_r_rdy      (val_r_rdy),

    .us_sec         (us_sec),
    .e_mdc          (e_mdc),
    .e_mdio_i       (e_mdio_i),
    .e_mdio_o       (e_mdio_o),
    .e_mdio_t       (e_mdio_t),

    .mdio_end       (mdio_end),
    .mdio_err       (phy_err),
    .reset          (reset)
);
//------------------------------------------------------------
always @ (posedge sys_clk)
if(val_r_rdy) 
    smi_dout <= phy_reg_val_r;
//------------------------------------------------------------
assign smi_err = phy_err | cmd_err;

assign test_pin = {mdio_end, smi_done};

endmodule

 

mdio_core.v 模块主要是控制接口中的数据信息,转换为MDIO PHY层所需要的数据。 本模块主要是和软件或者FPGA上层做命令层面的交互。同时,和下方的MDIO PHY信号握手,通过这个模块,隔离了命令层和PHY层的数据交互。

  • mdio_app.v  代码:
`timescale 1ns / 1ps


module mdio_app #
(
    parameter   MAIN_CLK    = 125,
    parameter   PHY_SPEED   = "AUTO"    // "AUTO", "1G", "100M", "10M"
)
(
    input   sys_clk,

    output  e_mdc,
    input   e_mdio_i,
    output  e_mdio_o,
    output  e_mdio_t,
    
    output  smi_err,
    
    output  reg mdio_done = 0,
    input   reset
);

//------------------------------------------------------------
// generate 1ms 1 clock  pulse
localparam MS_CNT = (MAIN_CLK == 125) ? 125_000 - 1 : (MAIN_CLK == 100) ? 100_000 - 1 : (MAIN_CLK == 50) ? 50_000 - 1 : 50_000 - 1;

reg [23:0] ms_cnt_r = 0;
wire ms_sec = (ms_cnt_r == MS_CNT) ? 1'b1 : 1'b0;

always @ (posedge sys_clk)
if(ms_sec) ms_cnt_r <= 0;
else ms_cnt_r <= ms_cnt_r + 1;

// generate 16ms 1 clock  pulse
reg [3:0] ms_cnt = 0;
always @ (posedge sys_clk)
    if(ms_sec) ms_cnt <= ms_cnt + 1;

reg [1:0] ms_r = 0;
always @ (posedge sys_clk)
ms_r <= {ms_r[0], &ms_cnt};

wire ms16_flag = (ms_r == 2'b01) ? 1'b1 : 1'b0;
//------------------------------------------------------------
localparam SMI_ST = 2'b01;
localparam SMI_IDLE = 2'b11;

localparam WR = 2'b01;
localparam RD = 2'b10;
localparam NO = 2'b00;
//------------------------------------------------------------
reg [24:0] init_reg [9:0];
initial
begin
//                {st_op,   phy_reg,      reg_val};
    if(PHY_SPEED == "10M")
        init_reg [0] = {  SMI_ST,WR,5'h00, 16'h0100}; // 16'b0010_0001_0000_0000
    else if(PHY_SPEED == "100M")
        init_reg [0] = {  SMI_ST,WR,5'h00, 16'h2100}; // 16'b0010_0001_0000_0000
    else
        init_reg [0] = {  SMI_ST,WR,5'b00, 16'h1140}; // 16'b0001_0001_0100_0000
//  disable EEE
    init_reg [1] = {  SMI_ST,WR,5'h0d, 16'h0007}; // 16'b0000_0000_0000_0000
    init_reg [2] = {  SMI_ST,WR,5'h0e, 16'h003c}; // 16'b0000_0000_0000_0000
    init_reg [3] = {  SMI_ST,WR,5'h0d, 16'h4007}; // 16'b0000_0000_0000_0000
    init_reg [4] = {  SMI_ST,WR,5'h0e, 16'h0000}; // 16'b0000_0000_0000_0000


    if(PHY_SPEED == "1G")
        init_reg [5] = {  SMI_ST,WR,5'h04, 16'h0C01}; // 16'b0000_1100_0000_0001  ;  1G
    else
        init_reg [5] = {  SMI_ST,WR,5'h04, 16'h0d41}; // 16'b0000_1101_0100_0001  ;  1G/100M/10M

    if(PHY_SPEED == "10M")
        init_reg [6] = {  SMI_ST,WR,5'h00, 16'h0100}; // 16'b0001_0011_0100_0000
    else if(PHY_SPEED == "100M")
        init_reg [6] = {  SMI_ST,WR,5'h00, 16'h2100}; // 16'b0001_0011_0100_0000
    else
        init_reg [6] = {  SMI_ST,WR,5'h00, 16'h1340}; // 16'b0001_0011_0100_0000    

    init_reg [7] = {SMI_ST,RD,5'h02, 16'h0000}; // 16'b0000_0000_0000_0000
    init_reg [8] = {SMI_ST,RD,5'h03, 16'h0000}; // 16'b0000_0000_0000_0000
    init_reg [9] = {SMI_IDLE,NO,5'h00, 16'h0000}; // 16'b0000_0000_0000_0000
end
//------------------------------------------------------------
wire smi_done;
wire [15:0] smi_dout;

reg [4:0]  phy_addr = 5'b0_0001; // phy chip address
reg [3:0]  smi_id = 0;
reg [24:0] smi_data = 0;
reg smi_en = 0;

reg [2:0] app_st = 0;
always @ (posedge sys_clk or posedge reset)
if(reset)
begin
    mdio_done <= 0;
    smi_en <= 1'b0;
    smi_id <= 0;
    app_st <= 0;
end
else case(app_st)
0:
begin
    smi_en <= 1'b0;
    if(smi_done)
        app_st <= 1;
end
1:
begin
    phy_addr = 5'b0_0001; // phy chip address
    smi_data <= init_reg[smi_id];
    //{st_op, phy_reg, phy_reg_val_w} <= smi_data;
    app_st <= 2;
end
2:
begin
    if(smi_data[24:23] == SMI_ST)
    begin
        smi_en <= 1'b1;
        app_st <= 3;
    end
    else app_st <= 7;
end
3:
begin
    smi_en <= 1'b0;
    if(~smi_done)
        app_st <= 4;
end
4:
begin
    if(smi_done)
    begin
        if(~smi_err)  smi_id <= smi_id + 1'b1;

        app_st <= 5;
    end
end
5:
begin
    if(ms16_flag) 
        app_st <= 6;
end
6:
begin
    if(ms16_flag) 
        app_st <= 0;
end
7:
begin
    app_st <= 7;  // initial phy register end
    mdio_done <= 1;
end
default: app_st <= 0;
endcase

//------------------------------------------------------------
mdio_core   #
(
    .MAIN_CLK   (MAIN_CLK),
    .PHY_SPEED  (PHY_SPEED)
)
mdio_core_inst 
(
    .sys_clk    (sys_clk),
    .phy_addr   (phy_addr),

    .smi_data   (smi_data),
    .smi_en     (smi_en),

    .e_mdc      (e_mdc),
    .e_mdio_i   (e_mdio_i),
    .e_mdio_o   (e_mdio_o),
    .e_mdio_t   (e_mdio_t),
    
    .smi_dout   (smi_dout),
    .smi_done   (smi_done),
    .smi_err    (smi_err),

    .reset      (reset)
);


endmodule

 

 

 

4.总结

根据MDIO帧格式,MAC层向PHY层发送数据或者从PHY芯片中接收数据。同时这个代码具有可封装性,以使用于FPGA独立使用、CPU 接口 或者ASIC  时封装IP 使用。

 

注:具体Verilog 代码流程讲解,请参考相关视频。

 

 

Posted in FPGA, FPGA 教材教案, Verilog, 教材与教案, 文章

1 Comment

  1. CMU

    老师,你的mdio_phy.v的30行,用sys_clk去得到一个us级别的MDC。语法好像有问题。
    如果sys_clk是10MHz,假设us_sec是1us。那么在一次us_sec高电平上有5个sys_clk。会导致MDC翻转5次,在一次us_sec低电平上有5个sys_clk。会导致MDC保持5个sys_clk的周期电平。结果就是MDC的5个sys_clk时间是sys_clk波形,紧接着5个sys_clk的时间保持电平不变

发表回复

相关链接