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.5MHzinout                                               o_mdio              ,//三态门的mdio//输入读写指令和寄存器地址和数据            input                                               i_w0_r1             ,//输入读写指令,写0,读1input           [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) beginif(sys_rst == 1'b1)r_mdc_cnt <= 'd0;else if(r_mdc_cnt == mdc_cnt_max - 1)r_mdc_cnt <= 'd0;elser_mdc_cnt <= r_mdc_cnt + 1'b1;
endalways @(posedge sys_clk) beginif(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;elsero_mdc <= ro_mdc;
endalways @(posedge sys_clk) beginif(sys_rst == 1'b1)r_op_cmd <= 'd0;else if(w_op_act == 1'b1)r_op_cmd <= {i_w0_r1,~i_w0_r1};elser_op_cmd <= r_op_cmd;
endalways @(posedge sys_clk) beginif(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;elser_op_start <= r_op_start;
endalways @(posedge sys_clk) beginif(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;elser_reg_mido_out_data <= r_reg_mido_out_data;
endalways @(posedge sys_clk) beginif(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;elser_data_cnt <= r_data_cnt;
endalways @(posedge sys_clk) beginif(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;elser_mdio_ctrl <= 1'b0;
endalways @(posedge sys_clk) beginif(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];elser_mdio_out <= r_mdio_out;
endalways @(posedge sys_clk) beginif(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};elsero_read_data <= ro_read_data;
endalways @(posedge sys_clk) beginif(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;elsero_read_data_valid <= 1'b0;
endendmodule

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 beginsys_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;
endalways #5 sys_clk = ~sys_clk;always @(posedge sys_clk) beginif(sys_rst == 1'b1)begini_w0_r1    <= 'd0;i_reg_addr <= 'd0;i_wr_data  <= 'd0;i_op_valid <= 'd0;endelse if(i_op_valid && o_op_ready) begini_w0_r1    <= 'd0;i_reg_addr <= 'd0;i_wr_data  <= 'd0;i_op_valid <= 'd0;endelse begini_w0_r1    <= 'd0;i_reg_addr <= 'd0;i_wr_data  <= 15'h1234;i_op_valid <= 'd1;end
endmdio_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 beginsys_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;
endalways #5 sys_clk = ~sys_clk;always @(posedge sys_clk) beginif(sys_rst == 1'b1)begini_w0_r1    <= 'd0;i_reg_addr <= 'd0;i_wr_data  <= 'd0;i_op_valid <= 'd0;endelse if(i_op_valid && o_op_ready) begini_w0_r1    <= 'd0;i_reg_addr <= 'd0;i_wr_data  <= 'd0;i_op_valid <= 'd0;endelse begini_w0_r1    <= 'd1;i_reg_addr <= 'd0;i_wr_data  <= 15'h1234;i_op_valid <= 'd1;end
endmdio_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) beginif(sys_rst == 1'b1)r_cur_state <= RST;elser_cur_state <= r_next_state;
endalways @(posedge sys_clk) beginif(sys_rst == 1'b1)r_st_cnt <= 'd0;else if(r_cur_state != r_next_state)r_st_cnt <= 'd0;elser_st_cnt <= r_st_cnt + 1'b1;
endalways @(*) begincase (r_cur_state)RST       : r_next_state <= (r_st_cnt == RST_TIME) ? IDLE : RST;             //上电开始,复位20msIDLE      : 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
endalways @(posedge sys_clk) beginif(sys_rst == 1'b1)beginro_w0_r1     <= 'd0;ro_reg_addr  <= 'd0;ro_wr_data   <= 'd0;ro_op_valid  <= 'd0;endelse if(r_cur_state == READ_LINK) begin         ro_w0_r1     <= 'd1;        //读指令ro_reg_addr  <= 'd1;        //读基本模式状态寄存器,地址是0x01ro_wr_data   <= 'd0;        //因为是读,所以不用写数据ro_op_valid  <= 'd1;endelse if(r_cur_state == READ_SPEED) beginro_w0_r1     <= 'd1;        //读指令ro_reg_addr  <= 'd0;        //读基本模式控制寄存器,地址是0x00ro_wr_data   <= 'd0;        //因为是读,所以不用写数据ro_op_valid  <= 'd1;endelse beginro_w0_r1     <= 'd0;ro_reg_addr  <= 'd0;ro_wr_data   <= 'd0;ro_op_valid  <= 'd0;end
endalways @(posedge sys_clk) beginif(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;
endalways @(posedge sys_clk) beginif(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;
endalways @(posedge sys_clk) begin     if(sys_rst == 1'b1)ro_phy_rstn <= 1'b1;else if(r_cur_state == RST) //在复位状态拉低复位信号20msro_phy_rstn <= 1'b0;elsero_phy_rstn <= 1'b1;
endendmodule

5.4.2 仿真验证

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

`timescale 1ns / 1psmodule tb_mdio_top();reg                                                 sys_clk     ;initial beginsys_clk <= 0;
endalways #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来控制操作改变速率以及读取状态,后续有时间可以再添加上来。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【韩老师零基础30天学会Java 】06章 数组、排序和查找

第六章 数组、排序和查找 1. 数组&#x1f6a9;&#x1f6a9; 数组介绍&#xff1a; 数组可以存放多个同一类型的数据。数组也是一种数据类型&#xff0c;是引用类型。即:数组就是一组数据。 示例&#xff1a; double [] hens{3,5,1,3,4,2,50,7.8,88.8,1.1,5}; double totalWe…

基于Zynq FPGA对雷龙SD NAND的测试

文章目录 SD NAND特征SD卡简介1.2 SD卡块图 SD卡样片Zynq测试平台搭建测试流程SOC搭建软件搭建 测试结果总结 SD NAND特征 SD卡简介 雷龙的SD NAND有很多型号&#xff0c;在测试中使用的是CSNP4GCR01-AMW与CSNP32GCR01-AOW。芯片是基于 NAND FLASH 和 SD控制器实现的SD卡。具…

在Linux上部署(MySQL Redis Elasticsearch等)各类软件

实战章节&#xff1a;在Linux上部署各类软件 前言 为什么学习各类软件在Linux上的部署 在前面&#xff0c;我们学习了许多的Linux命令和高级技巧&#xff0c;这些知识点比较零散&#xff0c;同学们跟随着课程的内容进行练习虽然可以基础掌握这些命令和技巧的使用&#xff0c…

电脑不显示wifi列表怎么办?电脑不显示WiF列表的解决办法

有用户会遇到电脑总是不显示wifi列表的问题&#xff0c;但是不知道要怎么解决。随着无线网络的普及和使用&#xff0c;电脑无法显示WiFi列表的问题有时会让人感到困扰。电脑不显示WiFi列表是很常见的问题&#xff0c;但这并不意味着你无法连接到网络。不用担心&#xff0c;这个…

Android中Activity启动的模式

在 Android 开发中&#xff0c;Activity 的启动模式&#xff08;Launch Mode&#xff09;定义了当启动一个 Activity 时&#xff0c;系统会如何处理它的实例。不同的启动模式可以影响 Activity 在任务栈中的管理方式&#xff0c;对用户的使用体验产生直接影响。下面详细介绍四种…

Xshell 7 偏好设置

1 Xshell7 工具——更改用户数据文件夹 就是此电脑目录下的文档 该目录下的7 Xshell下的 applog ColorScheme Files 配色方案文件目录 HighlightSet Files 突出显示集目录 Logs 日志 QuickButton Files 快速命令集 Scripts 脚本文件 Sessions 会话文件 会话文件目录就…

丹摩征文活动 | 丹摩智算:大数据治理的智慧引擎与实践探索

丹摩DAMODEL&#xff5c;让AI开发更简单&#xff01;算力租赁上丹摩&#xff01; 目录 一、引言 二、大数据治理的挑战与重要性 &#xff08;一&#xff09;数据质量问题 &#xff08;二&#xff09;数据安全威胁 &#xff08;三&#xff09;数据管理复杂性 三、丹摩智算…

企业级容器技术docker之一键生成 Docker Compose

案例: 一键生成 Docker Compose 利用网站将docker 命令自动生成 Docker Compse Composerizehttps://www.composerize.com/ 基于docker-compose编译多服务镜像并启动容器案例 输入docker命令就可以自动转换为 docker-compose的格式

C++《stack与queue》

在之前的章节我们学习了C当中string、vector和list三种容器并且试着模拟实现这三种容器&#xff0c;那么接下来在本篇当中我们将STL当中的stack和queue&#xff0c;并且在学习stack和queue的使用之后和之前一样还会试着模拟实现stck和queue。由于stck和queue的模拟实现较为简单…

【Linux】常用命令(2.6万字汇总)

文章目录 Linux常用命令汇总1. 基础知识1.1. Linux系统命令行的含义1.2. 命令的组成 2. 基础知识2.1. 关闭系统2.2. 关闭重启2.3. 帮助命令&#xff08;help&#xff09;2.4. 命令说明书&#xff08;man&#xff09;2.5. 切换用户&#xff08;su&#xff09;2.6.历史指令 3.目录…

Selenium+Pytest自动化测试框架 ------ 禅道实战

前言 有人问我登录携带登录的测试框架该怎么处理&#xff0c;今天就对框架做一点小升级吧&#xff0c;加入登录的测试功能。 选用的测试网址为我电脑本地搭建的禅道 更改了以下的一些文件,框架为原文章框架主体 conftest.py更改 conftest.py #!/usr/bin/env python3 # -*…

java---认识异常(详解)

还有大家来到权权的博客~欢迎大家对我的博客提出意见哦&#xff0c;有错误会及时改进的~点击进入我的博客主页 目录 一、异常的概念及体系结构1.1 异常的概念1.2 异常的体系结构1.3异常的分类 二、异常的处理2.1防御式编程2.2 异常的抛出2.3 异常的捕获2.3.1异常声明throws2.3.…

鸿蒙多线程开发——并发模型对比(Actor与内存共享)

1、概 述 并发是指在同一时间段内&#xff0c;能够处理多个任务的能力。为了提升应用的响应速度与帧率&#xff0c;以及防止耗时任务对主线程的干扰&#xff0c;HarmonyOS系统提供了异步并发和多线程并发两种处理策略。 异步并发&#xff1a;指异步代码在执行到一定程度后会被…

Axure是什么软件?全方位解读助力设计入门

在产品设计和开发领域&#xff0c;Axure是一款大名鼎鼎且功能强大的软件&#xff0c;它为专业人士和团队提供了卓越的设计支持&#xff0c;帮助他们将创意转化为实际可操作的产品原型。 一、Axure 的基本介绍 Axure是一款专业的原型设计工具&#xff0c;主要用于创建交互式的…

客户手机号收集小程序有什么用

客户手机号收集小程序具有多方面的重要作用&#xff0c;主要体现在以下几个领域&#xff1a; 商业营销与客户关系管理 精准营销&#xff1a;通过收集客户手机号&#xff0c;企业能够依据客户的消费行为、偏好等信息&#xff0c;进行精准的个性化营销。例如&#xff0c;电商企业…

Spring Boot集成SQL Server快速入门Demo

1.什么是SQL Server&#xff1f; SQL Server是由Microsoft开发和推广的以客户/服务器&#xff08;c/s&#xff09;模式访问、使用Transact-SQL语言的关系数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;它最初是由Microsoft、Sybase和Ashton-Tate三家公司共同开发的&…

[CKS] Create/Read/Mount a Secret in K8S

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于读取、创建以及挂载secret的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[C…

深入理解Java虚拟机:你真的了解JVM吗?

Java虚拟机(JVM) 是 Java 技术的核心,它帮助 Java 实现了一次编译,到处运行的梦想。然而,你真的理解 JVM 的工作原理吗?今天,我们就从 JVM 的内部架构、垃圾回收机制、性能调优等角度,深入探讨这个“神秘黑盒”。 1. JVM 的基本架构:探索虚拟机内部 JVM 是运行 Java …

大模型就业收入高吗?大模型入门到精通,收藏这篇就够了

目前&#xff0c;已经可以说人工智能&#xff08;AI&#xff09;是推动社会进步和产业升级的重要力量。 其中&#xff0c;AI大模型作为人工智能领域的核心技术之一&#xff0c;正引领着新一轮的技术革命。 2024年&#xff0c;AI大模型开发工程师无疑成为了IT行业中最炙手可热…

el-table 纵向垂直表头处理

项目中表格展示会遇到需要纵向垂直表头情况&#xff0c;下面&#xff0c;我们基于el-table组件来实现这种表格。 以下是这次需要用到的数据表格&#xff0c;已知左侧违章名称是固定的&#xff0c;而月份是不固定的&#xff0c;在后端返回数据格式已确定的情况下&#xff0c;需…