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

系列文章目录

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

文章目录

  • 系列文章目录
  • 一、MDIO协议介绍
  • 二、PHY芯片管脚以及结构框图
  • 三、MDIO帧时序介绍
    • 3.1 MDIO帧格式
    • 3.2 MDIO写时序
    • 3.3 MDIO读时序
  • 四、PHY芯片常用寄存器描述
    • 4.1 基本模式控制寄存器(0x00)
    • 4.2 基本模式状态寄存器(0x01)
    • 4.3 PHY特定状态寄存器(0x1A)
    • 4.4 PHY芯片复位
  • 五、FPGA实现MDIO通信
    • 5.1 使用MDIO读取PHY芯片状态系统框图
    • 5.2 mdio_drive模块的代码编写
    • 5.3 mdio_drive模块仿真
      • 5.3.1 写操作仿真
      • 5.3.2读操作仿真
    • 5.4 mido_ctrl模块仿真
      • 5.4.1 Verilog代码
      • 5.4.2 仿真验证
    • 5.5 上板验证
  • 六、按键调整PHY芯片速率以及复位操作
    • 6.1 系统框图
    • 6.2 下板验证


一、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来控制操作改变速率以及读取状态,后续有时间可以再添加上来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/914039.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode day10 动态规划篇 64+139

64 最小路径和 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 m grid.lengthn grid[i].length1 < m, n < 2000 < grid[i][j]…

【系统面试篇】其他相关题目——虚拟内存、局部性原理、分页、分块、页面置换算法

目录 一、相关问题 1. 什么是虚拟内存&#xff1f;为什么需要虚拟内存&#xff1f; &#xff08;1&#xff09;内存扩展 &#xff08;2&#xff09;内存隔离 &#xff08;3&#xff09;物理内存管理 &#xff08;4&#xff09;页面交换 &#xff08;5&#xff09;内存映…

【论文复现】ChatGPT多模态命名实体识别

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ChatGPT ChatGPT辅助细化知识增强&#xff01;1. 研究背景2. 模型结构和代码3. 任务流程第一阶段&#xff1a;辅助精炼知识启发式生成第二阶段…

深度学习之循环神经网络(RNN)

1 为什么需要RNN&#xff1f; ​ 时间序列数据是指在不同时间点上收集到的数据&#xff0c;这类数据反映了某一事物、现象等随时间的变化状态或程度。一般的神经网络&#xff0c;在训练数据足够、算法模型优越的情况下&#xff0c;给定特定的x&#xff0c;就能得到期望y。其一…

【从零开始的LeetCode-算法】3238. 求出胜利玩家的数目

给你一个整数 n &#xff0c;表示在一个游戏中的玩家数目。同时给你一个二维整数数组 pick &#xff0c;其中 pick[i] [xi, yi] 表示玩家 xi 获得了一个颜色为 yi 的球。 如果玩家 i 获得的球中任何一种颜色球的数目 严格大于 i 个&#xff0c;那么我们说玩家 i 是胜利玩家。…

GIT的基本使用与进阶

GIT的简单入门 一.什么是git&#xff1f; Git 是一个开源的分布式版本控制系统&#xff0c;用于跟踪文件更改、管理代码版本以及协作开发。它主要由 Linus Torvalds 于 2005 年创建&#xff0c;最初是为 Linux 内核开发而设计的。如今&#xff0c;Git 已经成为现代软件开发中…

ReactPress:功能全面的开源发布平台

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 此项目是用于构建博客网站的&#xff0c;包含前台展示、管理后台和后端。 此项目是基于 React antd NestJS NextJS MySQL 的&#xff0c;项目已经开源&#xff0c;项目地址在 …

C++初阶——vector

一、什么是vector vector是表示可变大小的数组的序列容器&#xff0c;就像数组一样&#xff0c;vector也采用连续空间来存储元素。也就是说它的访问和数组一样高效&#xff0c;但是它的大小是动态可变的&#xff0c;并且它的大小会被容器自动处理。 二、vector的构造 常用的构…

sql专题 之 where和join on

文章目录 前言where介绍使用过滤结果集关联两个表 连接外连接内连接自然连接 使用inner join和直接使用where关联两个表的区别总结 前言 从数据库查询数据时&#xff0c;一张表不足以查询到我们想要的数据&#xff0c;更多的时候我们需要联表查询。 联表查询我们一般会使用连接…

服务器数据恢复—分区结构被破坏的reiserfs文件系统数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器中有一组由4块SAS硬盘组建的RAID5阵列&#xff0c;上层安装linux操作系统统。分区结构&#xff1a;boot分区LVM卷swap分区&#xff08;按照顺序&#xff09;&#xff0c;LVM卷中划分了一个reiserfs文件系统作为根分区。 服务器故障…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于稳态信息和条件分布自适应的风电场阻抗智能辨识和稳定性评估》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

vue反向代理配置及宝塔配置

vue生产环境和开发环境 反向代理 正向代理 宝塔面板配置 本地小皮面板---NginxApache解决方案_小皮面板反向代理-CSDN博客 上面这个链接供大家参考&#xff0c;我这里面提取vue配置反向代理格式 在vite.config.js页面写入 server: {proxy: {"/api": {target: "…

【NOIP普及组】统计单词数

【NOIP普及组】统计单词数 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 一般的文本编辑器都有查找单词的功能&#xff0c;该功能可以快速定位特定单词在文章中的位置&#xff0c;有的还能统计出特定单词在文章中出现的次数。 现在&#x…

uni-app中使用 unicloud 云开发平台③

文章目录 六、hbuilderX 中使用 unicloud 云开发平台文档传统业务开发流程什么是 unicloudunicloud 优点开发流程uncloud 构成云数据库云存储及 CDN创建云函数工程七、unicloud api 操作云函数调用云函数实现云数据库基本增删改查1. 获取数据库引用云存储操作六、hbuilderX 中使…

STM32 极速入门第一天 点亮一个LED( 使用PlatformIO开发STM32单片机 ) 2024/11/11

什么是STM32? STM32是STMicroelectronics&#xff08;意法半导体&#xff09;推出的一系列基于ARM Cortex-M内核的32位Flash微控制器。它们因高性能、低功耗、易于编程和广泛的外设集成而广泛应用于各种嵌入式系统项目中。 使用设备: STM32F103C6T6 我的 keil 装的是 C51 所以…

微信小程序使用阿里巴巴矢量图标库正确姿势

1、打开官网&#xff1a;https://www.iconfont.cn/&#xff0c;把整理好的图标下载解压。 2、由于微信小程序不支持直接在wxss中引入.ttf/.woff/.woff2&#xff08;在开发工具生效&#xff0c;手机不生效&#xff09;。我们需要对下载的文件进一步处理。 eot&#xff1a;IE系列…

kafka面试题解答(四)

5、消费者组和分区数之间的关系是怎样的&#xff1f; 消费者组数小于等于分区数&#xff0c;消费者组内每个消费者负责消费不同分区的数据&#xff0c;一个分区只能由一个组内消费者消费。 6、kafka如何知道哪个消费者消费哪个分区&#xff1f; 生产者把数据发送给各个分区&…

Android Profiler 内存分析

Android studio&#xff08;下面简称AS&#xff09;为App提供的性能分析工具&#xff0c;在AS3.0替换掉旧的分析工具&#xff0c;对于其使用方法&#xff0c;官方也有对应的介绍&#xff1a;Android Profiler 对于使用方法&#xff0c;我只用到比较简单的功能&#xff0c;高级的…

DXF-模型空间和图纸空间、图层冷冻标志位

‌DXF文件中操作环境的标志码是组代码67 CAD-模型空间和图纸空间-是CAD中两种不同的操作环境 模型空间主要用于建模&#xff0c;是一个没有界限的三维空间&#xff0c;用户在这个空间中以任意尺寸绘制图形&#xff0c;通常按照1&#xff1a;1的比例&#xff0c;以实际尺寸绘制…

前端开发调试之 PC 端调试

以下内容来自稀土掘金青训营课程 bug 与 debug 点击.cls开启动态修改元素的class输入字符串可以动态的给元素添加类名勾选/取消类名可以动态的查看类名生效效果点击具体的样式值&#xff08;字号、颜色、宽度高度等&#xff09;可以进行编辑&#xff0c;浏览器内容区域实时预览…