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.mzph.cn/diannao/60251.shtml

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

相关文章

Spring资源加载模块,原来XML就这,活该被注解踩在脚下 手写Spring第六篇了

这一篇让我想起来学习 Spring 的时&#xff0c;被 XML 支配的恐惧。明明是写Java&#xff0c;为啥要搞个XML呢&#xff1f;大佬们永远不知道&#xff0c;我认为最难的是 XML 头&#xff0c;但凡 Spring 用 JSON来做配置文件&#xff0c;Java 界都有可能再诞生一个扛把子。 <…

SpringCloud框架学习(第二部分:Consul、LoadBalancer和openFeign)

目录 六、Consul服务注册和发现 1.基本介绍 2.下载运行 3.服务注册与发现 &#xff08;1&#xff09;支付服务provider8001注册进consul &#xff08;2&#xff09;修改订单服务cloud-consumer-order80 4.CAP &#xff08;1&#xff09;CAP理论 &#xff08;2&#x…

ssm094学生宿舍管理+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;学生宿舍管理系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本学生宿舍管理系统…

机器学习——贝叶斯

&#x1f33a;历史文章列表&#x1f33a; 机器学习——损失函数、代价函数、KL散度机器学习——特征工程、正则化、强化学习机器学习——常见算法汇总机器学习——感知机、MLP、SVM机器学习——KNN机器学习——贝叶斯机器学习——决策树机器学习——随机森林、Bagging、Boostin…

403 Request Entity Too Lager(请求体太大啦)

昨天收到 QA 的生产报障&#xff0c;说是测试环境的附件上传功能报了 403 的错误&#xff0c;错误信息&#xff1a;403 Request Entity Too Lager。我尝试复现问题&#xff0c;发现传个几兆的文件都费劲啊&#xff0c;一传一个失败。不用说&#xff0c;项目用到 ng 代理&#x…

232转485模块测试

概述 常用的PLC一般会有两个左右的232口&#xff0c;以及两个左右的485口&#xff0c;CAN口等&#xff0c;但是PLC一般控制的设备可能会有很多&#xff0c;会超出通讯口的数量&#xff0c;此时我们一般会采用一个口接多个设备&#xff0c;这种情况下要注意干扰等因素&#xff0…

科技资讯|Matter 1.4 标准正式发布,低功耗蓝牙助力其发展

连接标准联盟&#xff08;CSA&#xff09;宣布推出最新的 Matter 1.4 版本&#xff0c;引入了一系列新的设备类型和功能增强&#xff0c;有望提高包括 HomeKit 在内的智能家居生态系统之间的互操作性。 设备供应商和平台能够依靠增强的多管理员功能改善多生态系统下的用户体验&…

SpringBoot实现文件上传并返回url链接

检查依赖 确保pom.xml包含了Spring Boot Web的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>创建Controller 创建公用上传文件控制器 package…

FPGA学习笔记#7 Vitis HLS 数组优化和函数优化

本笔记使用的Vitis HLS版本为2022.2&#xff0c;在windows11下运行&#xff0c;仿真part为xcku15p_CIV-ffva1156-2LV-e&#xff0c;主要根据教程&#xff1a;跟Xilinx SAE 学HLS系列视频讲座-高亚军进行学习 学习笔记&#xff1a;《FPGA学习笔记》索引 FPGA学习笔记#1 HLS简介及…

苍穹外卖05-Redis相关知识点

目录 什么是Redis&#xff1f; redis中的一些常用指令 value的5种常用数据类型 各种数据类型的特点 Redis中数据操作的常用命令 字符串类型常用命令&#xff1a; 哈希类型常用命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis 环境…

SpringBoot(八)使用AES库对字符串进行加密解密

博客的文章详情页面传递参数是使用AES加密过得,如下图所示: 这个AES加密是通用的加密方式,使用同一套算法,前端和后端都可以对加密之后的字符串进行加密解密操作。 目前线上正在使用的是前端javascript进行加密操作,将加密之后的字符串再传递到后端,PHP再进行解密操作。…

【Windows erver】配置高性能电源管理

操作场景 在 Windows Server 操作系统上&#xff0c;需要配置高性能电源管理&#xff0c;才能支持实例软关机&#xff0c;否则云服务器控制台只能通过硬关机的方式关闭实例。本文档以 Windows Server 2012 操作系统为例&#xff0c;介绍配置电源管理的方法。 操作说明 修改电…

【JAVA基础】MAVEN的安装及idea的引用说明

本篇文章主要讲解&#xff0c;maven的安装及集成在idea中进行构建项目的详细操作教程。 日期&#xff1a;2024年11月11日 作者&#xff1a;任聪聪 所需材料&#xff1a; 1、idea 2024版本及以上 2、maven 3.9.9安装包 3、一个空java springBoot项目&#xff0c;可以使用阿里云…

AI变现,做数字游民

在数字化时代&#xff0c;AI技术的迅猛发展不仅改变了各行各业的生产方式&#xff0c;还为普通人提供了前所未有的变现机会。本文将探讨如何利用AI技术实现变现&#xff0c;成为一名数字游民&#xff0c;享受自由职业带来的便利与乐趣。 一、AI技术的变现潜力 AI技术以其强大…

解非线性方程

实验类型&#xff1a;●验证性实验 ○综合性实验 ○设计性实验 实验目的&#xff1a;进一步熟练掌握解非线性方程的二分法算法、牛顿迭代法&#xff0c;提高编程能力和解算非线性方程问题的实践技能。 实验内容&#xff1a;用二分法算法(取[a,b][1,2])、牛顿迭代法解算非线性…

详细分析Guava库中的注解@VisibleForTesting,用于标记提醒私有(附Demo)

目录 前言1. 基本知识2. Demo 前言 对于Java基本知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09; 从实战中学习&#xff1a; 源码如下&…

Hadoop(YARN)

文章目录 YARN基础架构YARN工作原理YARN调度器和调度算法先进先出调度器容量调度器公平调度器 YARN常用命令 YARN基础架构 YARN是Hadoop集群的资源管理和调度系统&#xff0c;它负责为各种分布式计算任务分配和管理资源,包含以下组件&#xff1a;ResourceManager&#xff0c;N…

【GoWeb示例】通过示例学习 Go 的 Web 编程

文章目录 你好世界HTTP 服务器路由&#xff08;使用 gorilla/mux&#xff09;连接到 MySQL 数据库MySQL 数据库简单操作模板静态资源和文件操作表单处理中间件&#xff08;基础&#xff09;中间件&#xff08;高级&#xff09;会话JSONWebsockets密码哈希 你好世界 Go语言创建…

【C语言】Union

一.Union的用法 1.什么是Union? union 共用体名{ 成员列表 }; union&#xff0c;“联合体、共用体”&#xff0c;在某种程度上类似结构体struct的一种数据结构&#xff0c;共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。 2.为什么使用union&#xff1…

2024最新版JavaScript逆向爬虫教程-------基础篇之Chrome开发者工具学习

目录 一、打开Chrome DevTools的三种方式二、Elements元素面板三、Console控制台面板四、Sources面板五、Network面板六、Application面板七、逆向调试技巧7.1 善用搜索7.2 查看请求调用堆栈7.3 XHR 请求断点7.4 Console 插桩7.5 堆内存函数调用7.6 复制Console面板输出 工欲善…