自学内容网 自学内容网

FPGA实现以太网(二)、初始化和配置PHY芯片

系列文章目录

FPGA实现以太网(一)、以太网基础知识


一、MDIO协议介绍

  在前一文FPGA实现以太网(一)、以太网基础知识我们知道,以太网通信中的物理层链路基本上是由 PHY 芯片建立。PHY 芯片有一个配置接口,即 MDIO接口,可以配置 PHY 芯片的工作模式以及获取 PHY 芯片的若干状态信息。PHY芯片里面有很多寄存器,里面存放着PHY芯片的工作模式以及工作状态,比如链接情况、链接速率等等。MAC侧和PHY芯片的链接示意图如下:

在这里插入图片描述
  MDIO 接口也称为 SMI 接口(Serial Management Interface,串行管理接口),包括 ETH_MDC(数据管理时钟)和 ETH_MDIO(数据管理输入输出)两条信号线。ETH_MDC 为 ETH_MDIO 提供时钟,ETH_MDC 的最大时钟不能超过 12.5Mhz。ETH_MDIO 为双向数据引脚,既用于发送数据,也用于接收数据。实际上的PHY芯片与MAC侧之间的通信连接图如下所示:

在这里插入图片描述

二、PHY芯片管脚以及结构框图

  整个芯片的内部结构图如下所示:

在这里插入图片描述

在这里插入图片描述
  本文主要是讲MDIO时序,因此只关心复位管脚MDC以及PHYADMDIO管脚即可,其它的管脚再后续实现协议栈的时候再讲解。

三、MDIO帧时序介绍

3.1 MDIO帧格式

  MDIO协议是一个标准的、广泛使用的协议,因此帧格式都是一致的。以我开发板上的RTL8211芯片手册为例,其帧格式如下:

在这里插入图片描述

  1. Preamble :32位的前导码;由MAC端发送32个连续的1用于同步PHY芯片。
  2. ST:2位的帧开始信号;由01表示新的一帧信号的到来。
  3. OP:2位的操作码;10表示读,01表示写。
  4. PHYAD :5位的PHY 地址;用于表示与哪个 PHY 芯片通信,因为一个 MAC 上可以连接多个 PHY 芯片。
  5. REGAD:5位的PHY芯片里面的寄存器地址;用于表示操作PHY芯片里的哪一个寄存器。
  6. TA:2位的转向信号;主要是这是寄存器地址和帧的数据字段之间的2位时间间隔,以避免在读事务期间争用。在读命令中,MDIO 在此时由 MAC 驱动改为 PHY 驱动,在第一个 TA位,MDIO 引脚为高阻状态,第二个 TA 位,PHY 将 MDIO 引脚拉低,准备发送数据;在写命令中,不需要 MDIO 方向发生变化,MAC 固定输出 2’b10,随后开始写入数据。
  7. DATA:16 位数据,在读命令中,PHY 芯片将对应的 PHYAD 的 REGAD 寄存器的数据写到 DATA中;在写命令中,PHY 芯片将接收到的 DATA 写入 REGAD 寄存器中。在 DATA 传输的过程中,高位在前,低位在后。
  8. IDLE:1位空闲态;此时 MDIO 为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高电平。

3.2 MDIO写时序

  MDIO写时序如下所示:

在这里插入图片描述
  需要注意的是,PHY 在 MDC 时钟的上升沿采集数据,为保证数据的稳定传输,MAC 在 MDC 的下降沿更新 MDIO 引脚的数据,当 MDIO 引脚切换至 PHY 驱动时,MDIO 数据在 MDC 时钟的下降沿更新,因此 MAC 在 MDC 时钟的上升沿采集数据。

  1. MAC在MDC下降沿发送32位的1。
  2. 然后MAC在MDC下降沿发送01,此时PHY芯片会在时钟上升沿采集,如上图的竖线位置。
  3. 因为是写操作,所以MAC接着发送了操作码01,表示当前为写操作。
  4. 接着MAC发送了5位的PHY地址,例子中的PHY地址为5’b00001。
  5. 然后MAC发送了5位需要操作的PHY芯片里面的寄存器地址,这里是5‘b00000。
  6. 因为是MAC侧写数据,因此发送了2位10的转向码,依然是MAC控制着MDIO总线。
  7. 最后MAC侧发送16位的数据。
  8. 最后MAC将MDIO拉成高阻态。

3.3 MDIO读时序

  MDIO读时序如下所示:
在这里插入图片描述
  上图依然是以PHY芯片的地址位5’b00001为例读取操作。

  1. MAC在MDC下降沿发送32位的1。
  2. 然后MAC在MDC下降沿发送01,此时PHY芯片会在时钟上升沿采集,如上图的竖线位置。
  3. 因为是读操作,所以MAC接着发送了操作码10,表示当前为读操作。
  4. 接着MAC发送了5位的PHY地址,例子中的PHY地址为5’b00001。
  5. 然后MAC发送了5位需要操作的PHY芯片里面的寄存器地址,这里是5‘b00000。
  6. 因为是MAC侧读数据,因此MAC拉高MDIO为高阻态Z,在第二个操作位的0是由PHY拉低的表示响应成功;如果第二个操作位是1,表示操作失败。
  7. 最后PHY侧发送16位的数据,MAC侧在MDC上升沿采集数据。
  8. 最后MAC将MDIO拉成高阻态。

四、PHY芯片常用寄存器描述

  一个PHY芯片里有很多寄存器,每个寄存器的作用都是不一样的,我们这里只介绍我们用到的寄存器功能。

4.1 基本模式控制寄存器(0x00)

bit位名称类型 默认描述
15 复位 RW,SC01:PHY芯片软复位;0:正常模式(ps:复位后自动返回默认值)
14 回环 RW01:开启PHY芯片回环模式;0:关闭PHY芯片回环模式(ps:回环使RGMII从发送端路由到RGMII接收端)
13 Speed[0] RW0选择链接速度的低位;speed[1],speed[0]=11:保留;speed[1],speed[0]=10:1000Mbps ;speed[1],speed[0]=01:100Mbps ;speed[1],speed[0]=00:10Mbps
12 自协商启用使能 RW11:开启自协商;0:关闭自协商
11断电 RW01:断电;0:正常工作
10信号隔离 RW01: RGMII接口被隔离;串口管理接口(MDC、MDIO)仍处于活动状态。1:RTL8211忽略TXD[3:0]和TXCTL输入,并在TXC, RXC, RXCTL, RXD[3:0]上呈现高阻抗。0:正常
9重启自协商 RW,SC01:重启自动协商功能; 0:正常(ps:拉高后自动返回到默认值)
8双工模式 RW11:全双工;0:半双工该位仅在强制模式下有效,即NWay未启用。
7碰撞测试 RW01:开启碰撞测试;0:正常
6Speed[1] RW1选择链接速度的最高位(ps:只有自动协商使能不开启的情况下有效)
5单向使能RW01:不考虑链路状态,允许报文发送;0:链路建立时允许报文发送
4,3,2,1,0保留00000

4.2 基本模式状态寄存器(0x01)

bit位名称类型 默认描述
15 100Base-T4 RO0RTL8211不支持100Base-T4,所以这位一直为0
14 100Base-TX (full) RO11:表示设备在全双工模式下能够执行100Base-TX。0:表示设备在全双工模式下无法执行100Base-TX
13 100Base-TX (half)RO11:设备在半双工模式下能够执行100Base-TX。0:设备在半双工模式下无法执行100Base-TX
12 10Base-T (full) RO11:设备支持10Base-T全双工模式;0:设备不能在全双工模式下进行10Base-T
1110Base-T (half) RO11:设备在半双工模式下能够执行10Base-T;0:设备在半双工模式下不能执行10Base-T
1010Base-T2 (full) RO0RTL8211不支持100Base-T2,所以这位一直为0
910Base-T2 (half) RO0RTL8211不支持100Base-T2,所以这位一直为0
81000Base-T Extended Status RO11:表示设备支持扩展状态寄存器0x0F(15);0:表示设备不支持扩展状态寄存器0x0F。该寄存器是只读的,总是设为1。
7单向的能力 RO11:表示没有链路连接的PHY不能从RGMII发送。0:表示没有链路连接的PHY不能从RGMII发送
6序言抑制RO1RTL8211总是接受前导被抑制的事务。一直为1
5自动协商完成RO01:自协商进程完成,寄存器5、6、8、10的内容有效。0:自协商进程未完成
4远程故障RC, LH01:检测到远程故障状态(读取清除或复位)。0:未检测到远程故障情况
3自动协商能力RO11:表示设备能进行自协商。0:表示设备不能进行自协商
2链接状态RO01:已链接;0:未链接
1Jabber检测RC, LH01:检测到Jabber条件;0:没有Jabber
0扩展能力RO11:扩展寄存器功能,总是1

4.3 PHY特定状态寄存器(0x1A)

  这个寄存器我们只看协商后的速度即可,其它的以后用到了再看:

bit位名称类型 默认描述
5 链接速度高位 RO011:保留;10:1000Mbps ;01:100Mbps;00:10Mbps
4 链接速度低位 RO011:保留;10:1000Mbps ;01:100Mbps;00:10Mbps

4.4 PHY芯片复位

  PHY芯片的复位管脚为PHYRSTB,低电平有效;复位必须持续10ms。

五、FPGA实现MDIO通信

  我们先实现读取PHY的状态,看连接速度、是否链接成功,后续再增加其它的操作,系统框图如下:

5.1 使用MDIO读取PHY芯片状态系统框图

在这里插入图片描述
  mdio_drive模块实现的是整个MDIO时序功能,他接受来自外部的读写请求信号(w0_r1),需要操作的寄存器地址(reg_addr),需要写入的数据(wr_data)以及操作命令有效信号(op_valid);他对外输出的是寄存器读出来的数据(read_data)和读数据有效信号(read_data_valid)以及读写操作准备信号(op_ready)。
  mdio_ctrl模块实现的是给出整个phy芯片的读写指令以及数据,然后接受读出来的数据再进行判断,最后输出我们的链接状态(link)以及链接速度(speed)。

5.2 mdio_drive模块的代码编写

  驱动代码如下:

module mdio_drive #(
    parameter   SYS_CLK     =   100_000_000,    //输入的系统时钟频率
    parameter   MDC_CLK     =   10_000_000,     //输出的MDC时钟频率
    parameter   PHY_ADDR    =   5'b0_0001       //PHY芯片的物理地址
)
(
    input                                               sys_clk             ,//输入系统时钟
    input                                               sys_rst             ,//输入系统时钟复位
    //输出MDC和MDIO         
    output                                              o_mdc               ,//输出MDC,最高不超过12.5MHz
    inout                                               o_mdio              ,//三态门的mdio
    //输入读写指令和寄存器地址和数据            
    input                                               i_w0_r1             ,//输入读写指令,写0,读1
    input           [4:0]                               i_reg_addr          ,//输入需要操作的寄存器地址
    input           [15:0]                              i_wr_data           ,//输入需要写入的数据
    input                                               i_op_valid          ,//输入操作有效信号
    //输出读出的数据        
    output          [15:0]                              o_read_data         ,//输出读数据
    output                                              o_read_data_valid   ,//读数据有效信号
    //输出指令准备信号
    output                                              o_op_ready           //准备接受指令信号
);
/***************parameter*************/
localparam  mdc_cnt_max         = SYS_CLK / MDC_CLK     ;   //一个mdc时钟所需要的计数周期
localparam  mdc_cnt_max_div2    = SYS_CLK / MDC_CLK / 2 ;   //半个mdc时钟所需要的计数周期
/***************reg*******************/
reg     [15:0]      ro_read_data        ;
reg                 ro_read_data_valid  ;
reg                 ro_mdc              ;
reg                 r_mdio_ctrl         ;//三态控制信号
reg                 r_mdio_out          ;//三态输出
reg     [7:0]       r_mdc_cnt           ;//mdc时钟计数器
reg     [63:0]      r_reg_mido_out_data ;//暂存所有要输出的数据
reg     [6:0]       r_data_cnt          ;//输出的数据个数计数器
reg                 r_op_start          ;//读写操作开始信号
reg    [1:0]        r_op_cmd            ;//10表示读,01表示写
/***************wire******************/
wire                w_mdio_in           ;//三态输入
wire                w_op_act            ;//指令握手信号
/***************assign****************/
assign  o_read_data              =   ro_read_data               ;
assign  o_read_data_valid        =   ro_read_data_valid         ;
assign  o_op_ready               =   ~r_op_start                ;
assign  o_mdc                    =   ~ro_mdc                    ;
assign  o_mdio      = (r_mdio_ctrl == 1'b1) ? r_mdio_out : 1'bz ;   
assign  w_mdio_in   = (r_mdio_ctrl == 1'b1) ? 1'b0 : o_mdio     ;  
assign  w_op_act    = i_op_valid & o_op_ready                   ;
/***************always****************/
always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_mdc_cnt <= 'd0;
    else if(r_mdc_cnt == mdc_cnt_max - 1)
        r_mdc_cnt <= 'd0;
    else
        r_mdc_cnt <= r_mdc_cnt + 1'b1;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        ro_mdc <= 1'b0;
    else if(r_mdc_cnt == mdc_cnt_max - 1)
        ro_mdc <= 1'b0;
    else if(r_mdc_cnt == mdc_cnt_max_div2 - 1)
        ro_mdc <= 1'b1;
    else
        ro_mdc <= ro_mdc;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_op_cmd <= 'd0;
    else if(w_op_act == 1'b1)
        r_op_cmd <= {i_w0_r1,~i_w0_r1};
    else
        r_op_cmd <= r_op_cmd;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_op_start <= 1'b0;
    else if((r_data_cnt == 'd65) && (r_mdc_cnt == mdc_cnt_max_div2 - 1))
        r_op_start <= 1'b0;
    else if(w_op_act == 1'b1)
        r_op_start <= 1'b1;
    else
        r_op_start <= r_op_start;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_reg_mido_out_data <= 'd0;
    else if(w_op_act == 1'b1)
        r_reg_mido_out_data <= {{32{1'b1}},2'b01,{i_w0_r1,~i_w0_r1},PHY_ADDR,i_reg_addr,2'b10,i_wr_data};
    else if((r_op_start == 1'b1)&&(r_mdc_cnt == mdc_cnt_max_div2 - 1))
        r_reg_mido_out_data <= r_reg_mido_out_data << 1;
    else
        r_reg_mido_out_data <= r_reg_mido_out_data;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_data_cnt <= 'd0;
    else if((r_data_cnt == 'd65) && (r_mdc_cnt == mdc_cnt_max_div2 - 1))
        r_data_cnt <= 'd0;
    else if((r_op_start == 1'b1) && (r_mdc_cnt == mdc_cnt_max_div2 - 1))
        r_data_cnt <= r_data_cnt + 1'b1;
    else
        r_data_cnt <= r_data_cnt;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_mdio_ctrl <= 1'b0;
    else if(w_op_act == 1'b1)
        r_mdio_ctrl <= 1'b1;
    else if((r_op_start == 1'b1)&&(r_op_cmd == 2'b01)&&(r_data_cnt <= 64))
        r_mdio_ctrl <= 1'b1;
    else if((r_op_start == 1'b1)&&(r_op_cmd == 2'b10)&&(r_data_cnt <= 46))
        r_mdio_ctrl <= 1'b1;
    else
        r_mdio_ctrl <= 1'b0;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_mdio_out <= 1'b0;
    else if((r_op_start == 1'b1)&&(r_mdc_cnt == mdc_cnt_max_div2 - 1))
        r_mdio_out <= r_reg_mido_out_data[63];
    else
        r_mdio_out <= r_mdio_out;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        ro_read_data <= 'd0;
    else if((r_op_cmd == 2'b10)&&(r_mdc_cnt == mdc_cnt_max_div2 - 1)&&(r_data_cnt >=48)&&(r_data_cnt <64))
        ro_read_data <= {ro_read_data[14:0],w_mdio_in};
    else
        ro_read_data <= ro_read_data;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        ro_read_data_valid <= 1'b0;
    else if((r_op_cmd == 2'b10)&&(r_data_cnt == 64)&&(r_mdc_cnt == mdc_cnt_max_div2 - 1))
        ro_read_data_valid <= 1'b1;
    else
        ro_read_data_valid <= 1'b0;
end

endmodule

5.3 mdio_drive模块仿真

5.3.1 写操作仿真

   我们先试一下写操作,假如需要在寄存器(0x00)里面写入数据(0x1234),仿真代码如下:

`timescale 1ns / 1ps
module tb_mdio_drive();

reg                                                 sys_clk     ;
reg                                                 sys_rst     ;
wire                                                o_op_ready  ;
reg                                                 i_w0_r1     ;
reg             [4:0]                               i_reg_addr  ;
reg             [15:0]                              i_wr_data   ;
reg                                                 i_op_valid  ;

initial begin
    sys_clk <= 0;
    sys_rst <= 1;
    i_w0_r1    <= 0;
    i_reg_addr <= 0;
    i_wr_data  <= 0;
    i_op_valid <= 0;
    #500 
    @(posedge sys_clk)
    sys_rst = 0;
end

always #5 sys_clk = ~sys_clk;

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)begin
        i_w0_r1    <= 'd0;
        i_reg_addr <= 'd0;
        i_wr_data  <= 'd0;
        i_op_valid <= 'd0;
    end
    else if(i_op_valid && o_op_ready) begin
        i_w0_r1    <= 'd0;
        i_reg_addr <= 'd0;
        i_wr_data  <= 'd0;
        i_op_valid <= 'd0;
    end
    else begin
        i_w0_r1    <= 'd0;
        i_reg_addr <= 'd0;
        i_wr_data  <= 15'h1234;
        i_op_valid <= 'd1;
    end
end


mdio_drive#(
    .SYS_CLK            ( 100_000_000 ),
    .MDC_CLK            ( 10_000_000 ),
    .PHY_ADDR           ( 5'b0_0000 )
)u_mdio_drive(
    .sys_clk            ( sys_clk            ),
    .sys_rst            ( sys_rst            ),
    .o_mdc              (                    ),
    .o_mdio             (                    ),
    .i_w0_r1            ( i_w0_r1            ),
    .i_reg_addr         ( i_reg_addr         ),
    .i_wr_data          ( i_wr_data          ),
    .i_op_valid         ( i_op_valid         ),
    .o_read_data        (                    ),
    .o_read_data_valid  (                    ),
    .o_op_ready         ( o_op_ready         )
);

endmodule

在这里插入图片描述
   在此刻握手成功,然后将读写操作指令,寄存器地址,写数据以及PHY芯片地址暂存到r_reg_mido_out_data里,此时r_reg_mido_out_data数据更新为{32’hffffffff,2’b01,2’b01,5’b00000,5’b00000,2’b10,16’h1234}最终等于 (ffff_ffff_5002_1234),和设定的一致。

在这里插入图片描述
   我们再来看输出的MDC时钟频率:

在这里插入图片描述
   输出的MDC周期为100ns,和我们设定的10MHz一致,接下来我们看mdio的写时序:

在这里插入图片描述
   在握手成功后,先获取到mdio的控制权,然后先输出32个1,然后接着输出数据,和手册上的时序图一致。

5.3.2读操作仿真

   接下来我们仿真读操作,仿真代码就改一下读写指令,其它的都不用改,仿真代码如下:

`timescale 1ns / 1ps
module tb_mdio_drive();

reg                                                 sys_clk     ;
reg                                                 sys_rst     ;
wire                                                o_op_ready  ;
reg                                                 i_w0_r1     ;
reg             [4:0]                               i_reg_addr  ;
reg             [15:0]                              i_wr_data   ;
reg                                                 i_op_valid  ;

initial begin
    sys_clk <= 0;
    sys_rst <= 1;
    i_w0_r1    <= 0;
    i_reg_addr <= 0;
    i_wr_data  <= 0;
    i_op_valid <= 0;
    #500 
    @(posedge sys_clk)
    sys_rst = 0;
end

always #5 sys_clk = ~sys_clk;

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)begin
        i_w0_r1    <= 'd0;
        i_reg_addr <= 'd0;
        i_wr_data  <= 'd0;
        i_op_valid <= 'd0;
    end
    else if(i_op_valid && o_op_ready) begin
        i_w0_r1    <= 'd0;
        i_reg_addr <= 'd0;
        i_wr_data  <= 'd0;
        i_op_valid <= 'd0;
    end
    else begin
        i_w0_r1    <= 'd1;
        i_reg_addr <= 'd0;
        i_wr_data  <= 15'h1234;
        i_op_valid <= 'd1;
    end
end


mdio_drive#(
    .SYS_CLK            ( 100_000_000 ),
    .MDC_CLK            ( 10_000_000 ),
    .PHY_ADDR           ( 5'b0_0000 )
)u_mdio_drive(
    .sys_clk            ( sys_clk            ),
    .sys_rst            ( sys_rst            ),
    .o_mdc              (                    ),
    .o_mdio             (                    ),
    .i_w0_r1            ( i_w0_r1            ),
    .i_reg_addr         ( i_reg_addr         ),
    .i_wr_data          ( i_wr_data          ),
    .i_op_valid         ( i_op_valid         ),
    .o_read_data        (                    ),
    .o_read_data_valid  (                    ),
    .o_op_ready         ( o_op_ready         )
);

endmodule

在这里插入图片描述
   在此刻握手成功,然后将读写操作,寄存器地址,写数据以及PHY芯片地址暂存到r_reg_mido_out_data里,此时r_reg_mido_out_data数据更新为{32’hffffffff,2’b01,2’b10,5’b00000,5’b00000,2’b10,16’h1234}最终等于 (ffff_ffff_6002_1234),和设定的一致。

在这里插入图片描述

在这里插入图片描述
   和写操作不一样的地方就在于:fpga在写完32个1,以及2位的开始信号01,和2位读指令10,5位的phy地址5‘b00000,5位的寄存器地址5’b00000后,就要释放总线控制权给PHY芯片输出数据,然后fpga再在mdc时钟上升沿采集mdio上的数据。

5.4 mido_ctrl模块仿真

5.4.1 Verilog代码

   控制模块代码如下:

`timescale 1ns / 1ps
module mdio_ctrl#(
    parameter       SYS_CLK   = 100_000_000
)
(
    input                                               sys_clk             ,
    input                                               sys_rst             ,
    //读寄存器数据
    input           [15:0]                              i_read_data         ,
    input                                               i_read_data_valid   ,
    input                                               i_op_ready          ,
    //输出控制读写指令和数据
    output                                              o_w0_r1             ,
    output          [4:0]                               o_reg_addr          ,
    output          [15:0]                              o_wr_data           ,
    output                                              o_op_valid          ,
    output                                              o_phy_rstn          ,       //phy芯片物理复位
    //输出状态指示信号
    output          [1:0]                               o_speed             ,
    output                                              o_link    
);
/***************parameter*************/
parameter   
            RST         = 4'd0,
            IDLE        = 4'd1,
            READ_LINK   = 4'd2,
            READ_WAIT1  = 4'd3,
            READ_SPEED  = 4'd4,
            READ_WAIT2  = 4'd5; 
parameter   CLK_CYCLE   = 1000000000 / SYS_CLK;     //当前一个系统时钟的周期是多少ns
parameter   RST_TIME    = 20_000_000 / CLK_CYCLE;    //设置20ms的PHY芯片的复位时间
parameter   WAIT_TIME   = 1000_000  / CLK_CYCLE;    //设置1ms的时间,去读PHY芯片状态          

/***************mechine***************/
reg [3:0]       r_cur_state     ;
reg [3:0]       r_next_state    ;
/***************reg*******************/
reg             ro_w0_r1        ;
reg  [4:0]      ro_reg_addr     ;
reg  [15:0]     ro_wr_data      ;
reg             ro_op_valid     ;
reg  [1:0]      ro_speed        ;
reg             ro_link         ;
reg  [27:0]     r_st_cnt        ; 
reg             ro_phy_rstn     ;
/***************wire******************/
wire            w_op_act        ;       //操作指令握手信号
/***************assign****************/
assign o_w0_r1      =   ro_w0_r1    ; 
assign o_phy_rstn   =   ro_phy_rstn ;
assign o_reg_addr   =   ro_reg_addr ; 
assign o_wr_data    =   ro_wr_data  ; 
assign o_op_valid   =   ro_op_valid ; 
assign o_speed      =   ro_speed    ; 
assign o_link       =   ro_link     ; 
assign w_op_act     =   i_op_ready & ro_op_valid;
/***************always****************/
always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_cur_state <= RST;
    else
        r_cur_state <= r_next_state;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        r_st_cnt <= 'd0;
    else if(r_cur_state != r_next_state)
        r_st_cnt <= 'd0;
    else
        r_st_cnt <= r_st_cnt + 1'b1;
end

always @(*) begin
    case (r_cur_state)
        RST       : r_next_state <= (r_st_cnt == RST_TIME) ? IDLE : RST;             //上电开始,复位20ms
        IDLE      : r_next_state <= (r_st_cnt == WAIT_TIME) ? READ_LINK : IDLE;      //等待1毫秒后先去读是否LINK成功
        READ_LINK : r_next_state <= (w_op_act == 1'b1) ?  READ_WAIT1 :  READ_LINK;   //握手成功后,就跳去等待
        READ_WAIT1: r_next_state <= (i_op_ready == 1'b1) ?  READ_SPEED : READ_WAIT1 ;//下游的ready信号拉高表示上次操作已经完成,就跳去读速度状态
        READ_SPEED: r_next_state <= (w_op_act == 1'b1) ?  READ_WAIT2 : READ_SPEED;  
        READ_WAIT2: r_next_state <= (i_op_ready == 1'b1) ? IDLE :  READ_WAIT2 ; 
        default: r_next_state <= IDLE;
    endcase
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)begin
        ro_w0_r1     <= 'd0;
        ro_reg_addr  <= 'd0;
        ro_wr_data   <= 'd0;
        ro_op_valid  <= 'd0;
    end
    else if(r_cur_state == READ_LINK) begin         
        ro_w0_r1     <= 'd1;        //读指令
        ro_reg_addr  <= 'd1;        //读基本模式状态寄存器,地址是0x01
        ro_wr_data   <= 'd0;        //因为是读,所以不用写数据
        ro_op_valid  <= 'd1;
    end
    else if(r_cur_state == READ_SPEED) begin
        ro_w0_r1     <= 'd1;        //读指令
        ro_reg_addr  <= 'd0;        //读基本模式控制寄存器,地址是0x00
        ro_wr_data   <= 'd0;        //因为是读,所以不用写数据
        ro_op_valid  <= 'd1;
    end
    else begin
        ro_w0_r1     <= 'd0;
        ro_reg_addr  <= 'd0;
        ro_wr_data   <= 'd0;
        ro_op_valid  <= 'd0;
    end
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        ro_link <= 1'b0;
    else if((r_cur_state == READ_WAIT1) && (i_read_data_valid))     //读数据有效信号拉高时候,获取0x01寄存器的第2位,查看链接状态
        ro_link <= i_read_data[2];
    else 
        ro_link <= ro_link;
end

always @(posedge sys_clk) begin
    if(sys_rst == 1'b1)
        ro_speed <= 1'b0;
    else if((r_cur_state == READ_WAIT2) && (i_read_data_valid))     //读数据有效信号拉高的时候,获取0x00寄存器的第6位和第13位,获取当前链接速度
        ro_speed <= {i_read_data[6],i_read_data[13]};
    else 
        ro_speed <= ro_speed;
end

always @(posedge sys_clk) begin     
    if(sys_rst == 1'b1)
        ro_phy_rstn <= 1'b1;
    else if(r_cur_state == RST) //在复位状态拉低复位信号20ms
        ro_phy_rstn <= 1'b0;
    else
        ro_phy_rstn <= 1'b1;
end

endmodule

5.4.2 仿真验证

   我们先把两个模块用顶层连上,然后给模块输入时钟即可,仿真代码如下:

`timescale 1ns / 1ps

module tb_mdio_top();

reg                                                 sys_clk     ;

initial begin
    sys_clk <= 0;
end

always #10 sys_clk = ~sys_clk;

mdio_top u_mdio_top(
    .sys_clk  ( sys_clk  ),
    .o_mdc    (     ),
    .o_mdio  (    )
);

endmodule

  打开仿真波形,我们先来看PHY芯片的复位引脚:

在这里插入图片描述
  PHY芯片的复位时间为20ms,符合预期,接下来我看放大看状态机:

在这里插入图片描述
  我们给出读指令以及有效信号后,隔一段时间就出来了读数据,因为我们仿真没有写PHY芯片的参数模型,因此在驱动后,释放三态门总线后,没有PHY芯片拉低mdio来响应,因此读出来的数据全是高阻态,我们接下来直接上板测试。

5.5 上板验证

   我们仿真完后,添加一些信号的ILA进行debug,然后用网线连接开发板,下载程序后打开波形窗口

在这里插入图片描述

   下板后我们打开网络适配器看当前已经链接上了,速率为1000Mbps;我们debug读出的0x01寄存器的数据是0x796d对应的2进制是{0111_1001_0110_1101},读取0x00寄存器的数据是0x1140对应的2进制是{0001_0001_0100_0000};我们打开前面的寄存器描述来看

在这里插入图片描述
   我们可以看到读出来的状态是已经链接,我们再看0x00寄存器

在这里插入图片描述
  我们可以看到从寄存器0x00可以读出我们当前链接的是1000Mbps的速度,接下来我们将网线断开:

在这里插入图片描述
  可以看到我们断开网线后,link信号直接拉低表示已断开,speed保留上一次的数据依然是10。

六、按键调整PHY芯片速率以及复位操作

  前面我们已经验证了MDIO的操作时序正确,这次我们添加外部按键来控制整个PHY芯片来实现速率可调,复位可调等操作。

6.1 系统框图

  我们使用外部4个按键,按下可以设置速率10M,100M,1000M以及复位操作;通过四个LED灯来显示,第一个LED亮表示当前速率是10M,第二个LED亮表示当前速率是100M,第三个LED亮表示当前速率是1000M,第四个LED亮表示当前链接成功(ps:如果没有link成功,所有灯都不会亮),系统框图如下:

在这里插入图片描述

6.2 下板验证

  因为前面MDIO读写操作已经验证成功,所以我们这里直接下板,debug看关键信号就可以了,下板打开debug窗口:

在这里插入图片描述
在这里插入图片描述
  下板成功后,由上图可见:LED4和LED3点亮,表示当前自协商的是1000M速率并且已经LINK成功;我们在debug窗口也看到当前speed=10,link拉高;同时打开网络适配器看网络状态已经链接1000M成功。

在这里插入图片描述
在这里插入图片描述
  我们按下KEY1后,由上图可见:LED4和LED1点亮,表示当前是10M速率并且已经LINK成功;我们在debug窗口也看到当前speed=00,link拉高;同时打开网络适配器看网络状态已经链接10M成功。

在这里插入图片描述
在这里插入图片描述
  我们按下KEY2后,由上图可见:LED4和LED2点亮,表示当前是100M速率并且已经LINK成功;我们在debug窗口也看到当前speed=01,link拉高;同时打开网络适配器看网络状态已经链接100M成功。

在这里插入图片描述
在这里插入图片描述
  我们按下KEY4后,由上图可见:LED4和LED3点亮,表示当前复位后,自协商是1000M速率并且已经LINK成功;我们在debug窗口也看到当前speed=10,link拉高;同时打开网络适配器看网络状态已经链接1000M成功。因此整个PHY芯片的初始化和配置已经完成,整个操作还可以使用UART或者SPI来控制操作改变速率以及读取状态,后续有时间可以再添加上来。


原文地址:https://blog.csdn.net/qq_43156031/article/details/143557679

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!