系列文章目录
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以及PHYAD和MDIO管脚即可,其它的管脚再后续实现协议栈的时候再讲解。
三、MDIO帧时序介绍
3.1 MDIO帧格式
MDIO协议是一个标准的、广泛使用的协议,因此帧格式都是一致的。以我开发板上的RTL8211芯片手册为例,其帧格式如下:
- Preamble :32位的前导码;由MAC端发送32个连续的1用于同步PHY芯片。
- ST:2位的帧开始信号;由01表示新的一帧信号的到来。
- OP:2位的操作码;10表示读,01表示写。
- PHYAD :5位的PHY 地址;用于表示与哪个 PHY 芯片通信,因为一个 MAC 上可以连接多个 PHY 芯片。
- REGAD:5位的PHY芯片里面的寄存器地址;用于表示操作PHY芯片里的哪一个寄存器。
- TA:2位的转向信号;主要是这是寄存器地址和帧的数据字段之间的2位时间间隔,以避免在读事务期间争用。在读命令中,MDIO 在此时由 MAC 驱动改为 PHY 驱动,在第一个 TA位,MDIO 引脚为高阻状态,第二个 TA 位,PHY 将 MDIO 引脚拉低,准备发送数据;在写命令中,不需要 MDIO 方向发生变化,MAC 固定输出 2’b10,随后开始写入数据。
- DATA:16 位数据,在读命令中,PHY 芯片将对应的 PHYAD 的 REGAD 寄存器的数据写到 DATA中;在写命令中,PHY 芯片将接收到的 DATA 写入 REGAD 寄存器中。在 DATA 传输的过程中,高位在前,低位在后。
- IDLE:1位空闲态;此时 MDIO 为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高电平。
3.2 MDIO写时序
MDIO写时序如下所示:
需要注意的是,PHY 在 MDC 时钟的上升沿采集数据,为保证数据的稳定传输,MAC 在 MDC 的下降沿更新 MDIO 引脚的数据,当 MDIO 引脚切换至 PHY 驱动时,MDIO 数据在 MDC 时钟的下降沿更新,因此 MAC 在 MDC 时钟的上升沿采集数据。
- MAC在MDC下降沿发送32位的1。
- 然后MAC在MDC下降沿发送01,此时PHY芯片会在时钟上升沿采集,如上图的竖线位置。
- 因为是写操作,所以MAC接着发送了操作码01,表示当前为写操作。
- 接着MAC发送了5位的PHY地址,例子中的PHY地址为5’b00001。
- 然后MAC发送了5位需要操作的PHY芯片里面的寄存器地址,这里是5‘b00000。
- 因为是MAC侧写数据,因此发送了2位10的转向码,依然是MAC控制着MDIO总线。
- 最后MAC侧发送16位的数据。
- 最后MAC将MDIO拉成高阻态。
3.3 MDIO读时序
MDIO读时序如下所示:
上图依然是以PHY芯片的地址位5’b00001为例读取操作。
- MAC在MDC下降沿发送32位的1。
- 然后MAC在MDC下降沿发送01,此时PHY芯片会在时钟上升沿采集,如上图的竖线位置。
- 因为是读操作,所以MAC接着发送了操作码10,表示当前为读操作。
- 接着MAC发送了5位的PHY地址,例子中的PHY地址为5’b00001。
- 然后MAC发送了5位需要操作的PHY芯片里面的寄存器地址,这里是5‘b00000。
- 因为是MAC侧读数据,因此MAC拉高MDIO为高阻态Z,在第二个操作位的0是由PHY拉低的表示响应成功;如果第二个操作位是1,表示操作失败。
- 最后PHY侧发送16位的数据,MAC侧在MDC上升沿采集数据。
- 最后MAC将MDIO拉成高阻态。
四、PHY芯片常用寄存器描述
一个PHY芯片里有很多寄存器,每个寄存器的作用都是不一样的,我们这里只介绍我们用到的寄存器功能。
4.1 基本模式控制寄存器(0x00)
bit位 | 名称 | 类型 | 默认 | 描述 |
15 | 复位 | RW,SC | 0 | 1:PHY芯片软复位;0:正常模式(ps:复位后自动返回默认值) |
14 | 回环 | RW | 0 | 1:开启PHY芯片回环模式;0:关闭PHY芯片回环模式(ps:回环使RGMII从发送端路由到RGMII接收端) |
13 | Speed[0] | RW | 0 | 选择链接速度的低位;speed[1],speed[0]=11:保留;speed[1],speed[0]=10:1000Mbps ;speed[1],speed[0]=01:100Mbps ;speed[1],speed[0]=00:10Mbps |
12 | 自协商启用使能 | RW | 1 | 1:开启自协商;0:关闭自协商 |
11 | 断电 | RW | 0 | 1:断电;0:正常工作 |
10 | 信号隔离 | RW | 0 | 1: RGMII接口被隔离;串口管理接口(MDC、MDIO)仍处于活动状态。1:RTL8211忽略TXD[3:0]和TXCTL输入,并在TXC, RXC, RXCTL, RXD[3:0]上呈现高阻抗。0:正常 |
9 | 重启自协商 | RW,SC | 0 | 1:重启自动协商功能; 0:正常(ps:拉高后自动返回到默认值) |
8 | 双工模式 | RW | 1 | 1:全双工;0:半双工该位仅在强制模式下有效,即NWay未启用。 |
7 | 碰撞测试 | RW | 0 | 1:开启碰撞测试;0:正常 |
6 | Speed[1] | RW | 1 | 选择链接速度的最高位(ps:只有自动协商使能不开启的情况下有效) |
5 | 单向使能 | RW | 0 | 1:不考虑链路状态,允许报文发送;0:链路建立时允许报文发送 |
4,3,2,1,0 | 保留 | 00000 |
4.2 基本模式状态寄存器(0x01)
bit位 | 名称 | 类型 | 默认 | 描述 |
15 | 100Base-T4 | RO | 0 | RTL8211不支持100Base-T4,所以这位一直为0 |
14 | 100Base-TX (full) | RO | 1 | 1:表示设备在全双工模式下能够执行100Base-TX。0:表示设备在全双工模式下无法执行100Base-TX |
13 | 100Base-TX (half) | RO | 1 | 1:设备在半双工模式下能够执行100Base-TX。0:设备在半双工模式下无法执行100Base-TX |
12 | 10Base-T (full) | RO | 1 | 1:设备支持10Base-T全双工模式;0:设备不能在全双工模式下进行10Base-T |
11 | 10Base-T (half) | RO | 1 | 1:设备在半双工模式下能够执行10Base-T;0:设备在半双工模式下不能执行10Base-T |
10 | 10Base-T2 (full) | RO | 0 | RTL8211不支持100Base-T2,所以这位一直为0 |
9 | 10Base-T2 (half) | RO | 0 | RTL8211不支持100Base-T2,所以这位一直为0 |
8 | 1000Base-T Extended Status | RO | 1 | 1:表示设备支持扩展状态寄存器0x0F(15);0:表示设备不支持扩展状态寄存器0x0F。该寄存器是只读的,总是设为1。 |
7 | 单向的能力 | RO | 1 | 1:表示没有链路连接的PHY不能从RGMII发送。0:表示没有链路连接的PHY不能从RGMII发送 |
6 | 序言抑制 | RO | 1 | RTL8211总是接受前导被抑制的事务。一直为1 |
5 | 自动协商完成 | RO | 0 | 1:自协商进程完成,寄存器5、6、8、10的内容有效。0:自协商进程未完成 |
4 | 远程故障 | RC, LH | 0 | 1:检测到远程故障状态(读取清除或复位)。0:未检测到远程故障情况 |
3 | 自动协商能力 | RO | 1 | 1:表示设备能进行自协商。0:表示设备不能进行自协商 |
2 | 链接状态 | RO | 0 | 1:已链接;0:未链接 |
1 | Jabber检测 | RC, LH | 0 | 1:检测到Jabber条件;0:没有Jabber |
0 | 扩展能力 | RO | 1 | 1:扩展寄存器功能,总是1 |
4.3 PHY特定状态寄存器(0x1A)
这个寄存器我们只看协商后的速度即可,其它的以后用到了再看:
bit位 | 名称 | 类型 | 默认 | 描述 |
5 | 链接速度高位 | RO | 0 | 11:保留;10:1000Mbps ;01:100Mbps;00:10Mbps |
4 | 链接速度低位 | RO | 0 | 11:保留;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来控制操作改变速率以及读取状态,后续有时间可以再添加上来。