FPGA学习篇——以太网学习之通过MDIO接口配置物理层PHY芯片实现
2026/8/22 20:59:33 网站建设 项目流程

了解知识:

1 网口线

常用于以太网的网口线有RJ45接口、RJ11 接口(电话线接口)、SC 光纤接口等。

RJ45插头如下:

RJ45 接口定义以及各引脚功能在不同通信速率下是有区别的。

基础知识:

1 通信知识

百兆和万兆以太网:百兆,千兆和万兆以太网指的是传输速率的区别

  • 十兆以太网 = 10 Mbps
  • 百兆以太网 = 100 Mbps
  • 千兆以太网 = 1000 Mbps = 1 Gbps
  • 万兆以太网 = 10,000 Mbps = 10 Gbps

通信相关的单位知识:

码元:承载信息量的基本单元。
一个码元不等于1个bit。要看你怎么设置,码元即信息基本单位可以是1个bit,2个bit甚至更多bit。

码元速率(波特率Baud):每秒传输的码元数,单位“波特每秒(Bps)”。
串口常用的波特率有4800,9600,115200。

比特率:每秒传输的比特数,单位“每秒比特数(bps)”显然,根据定义有:

比特率= 波特率 ✖ 每个码元承载的比特数

2 MDIO 接口及其通信协议

MAC端(这里是FPGA)可通过MDIO接口配置以太网的PHY芯片(物理层)。并且一个MAC可连接多个PHY芯片(在后面MDIO通信协议上可以看到)

PHY 芯片内部包含一系列寄存器,用户通过这些寄存器来配置 PHY 芯片的工作模式以及获取 PHY 芯片的状态信息,如连接速率、双工模式、自协商状态等。

而FPGA 就是通过 MDIO 接口来对 PHY 芯片内部的寄存器进行配置。

(1) MDIO信号线

MDIO只有两根信号线:

  • MDC 时钟线:最高不超过12.5MHZ
  • MDIO双向数据线:接收+发送

(2) MDIO接口的通信协议如下:

  • Preamble32位前导码,由MAC端发送32位逻辑“1”,用于同步PHY芯片。
  • STStart of Frame):2位帧开始信号,用01表示。
  • OPOperation Code):2位操作码,读:10,写:01
  • PHYADPHY Address):5PHY地址,用于表示与哪个PHY芯片通信,因此一个MAC上可以连接多个PHY芯片。
  • REGADRegister Address):5位寄存器地址,可以表示共32个寄存器。
  • TATurnaround):2位转向,在读命令中,MDIO在此时由MAC驱动改为PHY芯片驱动,在第一个TA位,MDIO引脚为高阻状态,第二个TA位,PHY芯片将MDIO引脚拉低,准备发送数据;在写命令中,不需要MDIO方向发生变化,MAC固定输出2b10,随后开始写入数据。
  • DATA16位数据,在读命令中,PHY芯片将读到的对应PHYADREGAD寄存器的数据写到DATA中;在写命令中,MAC将要写入对应PHYADREGAD寄存器的值写入DATA中。
  • IDLE:空闲状态,此时MDIO为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高阻Z。

a. 读时序

PS:这里的读写时序是相对于MAC层来说的,即读时序是MAC读;写时序是MAC写。

下图以 PHY 地址为 0x01,从寄存器地址 0x00 读出数据为例。

整个读时序符合MDIO的通信协议,值得注意的是:

1.读时序时TA段之前由MAC层驱动MDIO线(即TA段之前的MDIO线上的数据都是MAC层管的),在TA段的第一位MAC拉高MDIO为高阻Z,然后TA段第二位为0表示PHY芯片成功响应(PHY芯片开始管理MDIO线),TA段之后也是由PHY芯片驱动MDIO线。写时序一直都是MAC驱动MDIO线

如下图,MDIO线其实只有一根,这里只是为了方便理解画成了两根,黄色框为MAC驱动MDIO,红色为PHY驱动MDIO。

2. 规定MDIO线:发送方(驱动方)在MDC时钟的下降沿更新数据,接收方在MDC的上升沿采样。

如下图,MAC驱动MDIO的时候MAC是发送方,在下降沿更新数据,而此时PHY时接收方,在上升沿采样。PHY驱动MDIO的时候则相反。


b. 写时序

以 PHY 地址为 0x01,向寄存器地址 0x00 写入 0x1340 为例,在整个写操作过程中,MDC 时钟和MDIO 引脚一直由 MAC 端驱动,按照 MDIO 接口写通信协议开始传输数据。


c. MDIO接口协议中的PHY地址选择

以下主要为补充内容,之前说过一个MAC可连接多个PHY芯片,而我使用的板子是通过引脚来确定PHY芯片的地址的,以下想说一下怎么通过原理图的引脚来看。

如下图,首先只有三根地址线,而通信协议5位PHY地址,说明这块芯片只用了低三位。

左边接地右边电源,然后电阻的NC表示不连接的意思,那么从上往下第一根线就左边断开右边接电源表示1,第二根线就左边接地右边断开表示0,第二根线就左边接地右边断开表示0,三根线就左边断开右边接电源也表示1,那么地址就是100 = 5‘h04 (因为前面通信协议时5位PHY地址)




实战

1.设计规划及波形绘制

1.1设计规划

实验任务:

本节实验任务是使用 FPGA开发板上的以太网接口,完成MDIO接口的读写测试实验。板载的触摸按 键(TPAD)控制MDIO接口进行软复位,并通过两个LED灯实时指示当前网口的传输速度。
当 LED0灯亮的时候,表示当前的网口速率为10Mbps;当LED1灯亮的时候,表示当前网口的速率为 100Mbps;当两个LED灯都亮的时候,表示当前网口的速率为1000Mbps;当两个LED灯都熄灭时,说明当前网络自协商失败,硬件或者网络存在异常。

根据实验任务可画出以下系统框架:


1. 为什么需要主机?

按照实验任务, 其实好像我们只需要FPGA这一块开发板就可以完成任务了:板子上有MDIO接口(就是MDC和MDIO两根信号线),然后FPGA板子上还有板载PHY芯片,只需要给FPGA编写控制MDIO接口模块(下图的蓝色部分)。

但是架构图还有主机,是因为板载的PHY芯片需要与另一台设备通过网线才能交换自动协商信息,比如 :

开发板PHY支持:10/100/1000 Mbps
电脑PHY支持:10/100/1000 Mbps
最终协商结果:1000 Mbps

如果不插网线,或者网线另一端没有有效设备,开发板PHY找不到协商对端就无法建立Link,因此主机就是提供与板载PHY自协商的。在实际上板操作时:也只是用网线把FPGA板子和主机连接起来没有做其他操作。

因此外部设备不一定非得是电脑,也可以是:

  • 路由器LAN口
  • 交换机
  • 另一块开发板
  • 其他具有以太网PHY的设备

因此实际上框图可以理解为:主机只是给板载PHY提供一个自协商的作用

2. FPGA 模块侧为什么要写驱动dri + 控制 ctrl 的形式

PHY会听MDIO协议(它内部有硬件),但FPGA不会发MDIO 协议,因此就要编写一个MDIO控制器(包含dri + ctrl)的代码烧录进FPGA里,让他能够发送和接收MDIO协议。其中

  • dri 模块 : 解析mdio协议的(简单理解成描述一阵mdio包)。
  • ctrl 模块 : 控制FPGA什么时候发mdio包什么时候手,遇到异常怎么处理。

1.2 波形绘制

1.2.1 时钟分频模块

这里懒得画图啦,借正点原子的一用!

这里巧妙的时钟设计:

要求:MDIO接口的mdc时钟线规定最大频率不超过12.5MHZ,FPGA的系统时钟

是50MHZ,并且还要求下降沿更新上升沿采样。

解决:给系统时钟先进行二分频得到dri_clk(25MHZ)进一步得再给dri_clk进行二分频得到eth_mdc(12.5MHZ)。但是mdio驱动模块得读写还是按照dri_clk时钟来控制,通过计数器计数dri_clk时钟周期数,使得cnt为奇数时是eth_mdc得下降沿,nt为偶数时是eth_mdc得上升沿,这样驱动的代码就统一为dri_clk(见下一节代码编写)。

1.2.2 mdio驱动模块

以下自己画的波形图,可能有些粗糙敬请见谅指正~

写时序

读时序

1.2.3 mdio控制模块

以下自己画的波形图,可能有些粗糙敬请见谅指正~

2 代码编写

2.1 rtl

module mdio_top ( input touch_key , input sys_clk , input sys_rst_n , output [1:0] led , inout eth_mdio, output eth_mdc , output eth_rst_n ); wire dri_clk; wire mdc_clk; assign eth_rst_n = sys_rst_n; clk_divide # ( .CLK_DIV ( 4'd4 ) ) u_clk_divide ( .sys_clk ( sys_clk ), .sys_rst_n ( sys_rst_n ), .dri_clk ( dri_clk ) // .mdc_clk ( mdc_clk ) ); wire [15:0] op_rd_data ; wire op_rd_ack ; wire op_done ; wire op_exec ; wire op_RW ; wire [4:0] op_addr ; wire [15:0] op_wr_data ; mdio_ctrl #( .POLLING_CYCLE (24'd100_0000 ) ) u_mdio_ctrl ( .dri_clk (dri_clk ), .sys_rst_n (sys_rst_n ), .touch_key (touch_key ), .op_rd_data (op_rd_data ), .op_rd_ack (op_rd_ack ), .op_done (op_done ), .led (led ), .op_exec (op_exec ), .op_RW (op_RW ), .op_addr (op_addr ), .op_wr_data (op_wr_data ) ); mdio_dri u_mdio_dri( .dri_clk (dri_clk ), // .mdc_clk (mdc_clk ), .sys_rst_n (sys_rst_n ), .op_exec (op_exec ), .op_RW (op_RW ), //Write : 1 , Read : 0 .op_addr (op_addr ), .op_wr_data (op_wr_data ), .eth_mdio (eth_mdio ), .eth_mdc (eth_mdc ), .op_rd_data (op_rd_data ), .op_done (op_done ), .op_rd_ack (op_rd_ack ) ); endmodule
module clk_divide # ( parameter CLK_DIV = 4'd4 )( input sys_clk , input sys_rst_n , output reg dri_clk // output reg mdc_clk ); reg [3:0] clk_cnt_dri; always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin clk_cnt_dri <= 4'b0; dri_clk <= 1'b0; end else if( clk_cnt_dri == ((CLK_DIV/2) - 4'd1)) begin clk_cnt_dri <= 4'b0; dri_clk <= ~ dri_clk; end else clk_cnt_dri <= clk_cnt_dri + 1; end

这里控制模块的状态机写的有点乱,当时被别的事耽搁了,小伙伴们可只做参考,自己优化!

module mdio_ctrl #( parameter POLLING_CYCLE = 24'd100_0000 //每隔一段时间轮询PHY寄存器 )( input dri_clk , input sys_rst_n , input touch_key , input [15:0] op_rd_data , input op_rd_ack , input op_done , output reg [1:0] led , output reg op_exec , output reg op_RW , output reg [4:0] op_addr , output reg [15:0] op_wr_data ); reg touch_key_D1; reg touch_key_D2; reg touch_key_D3; reg rst_done_flag ; reg [20:0] cycle_cnt ; reg [2:0] flow_flag ; reg link_error ; reg ack_flag ; reg [1:0] speed_status ; // reg error_done ; wire touch_key_pos; assign touch_key_pos = touch_key_D2 & ~touch_key_D3; //触摸按键打三拍 always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin touch_key_D1 <= 1'b0; touch_key_D2 <= 1'b0; touch_key_D3 <= 1'b0; end else begin //touch_key 按下输出高电平 touch_key_D1 <= touch_key; touch_key_D2 <= touch_key_D1; touch_key_D3 <= touch_key_D2; end end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) cycle_cnt <= 'b0; else if(rst_done_flag) cycle_cnt <= 'b0; else if(link_error) cycle_cnt <= 'b0; else if(cycle_cnt == (POLLING_CYCLE - 1) ) cycle_cnt <= 'b0; else cycle_cnt <= cycle_cnt + 1; end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) ack_flag <= 1'b0; else if(op_rd_ack) ack_flag <= 1'b1; else if(op_done) ack_flag <= 1'b0; else ack_flag <= ack_flag; end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin op_exec <= 1'b0; flow_flag <= 2'b0; //表示当前控制执行到哪一步了 op_RW <= 1'b0; op_addr <= 5'b00000; op_wr_data <= 16'h0000; rst_done_flag <= 1'b0; // error_done <= 1'b0; speed_status<=2'b00; led <= 2'b00; link_error <= 1'b0 ; end //软件复位 else if(touch_key_pos) begin //touch_key 按下输出高电平 op_exec <= 1'b1; flow_flag <= 3'b000; rst_done_flag <= 1'b1; op_RW <= 1'b1; op_addr <= 5'b00000; op_wr_data <= 16'h9000; led <= 2'b00; link_error <= 1'b0; end else begin case(flow_flag) 3'b000:begin //软件复位 op_exec <= 1'b0; op_RW <= 1'b0; if(op_done) rst_done_flag <= 1'b0 ; else if(cycle_cnt == (POLLING_CYCLE - 1)) begin op_exec <= 1'b1 ; flow_flag <= 3'b001 ; op_addr <= 5'h01 ; op_wr_data <= 16'h0000 ; end else begin op_exec <= 1'b0; flow_flag <= flow_flag; rst_done_flag <= rst_done_flag; op_addr <= op_addr ; op_wr_data <= op_wr_data ; led <= led ; end end 3'b001:begin //读取基本状态寄存器获取自协商或者连接状态 op_exec <= 1'b0 ; op_RW <= 1'b0 ; if(op_done) begin if( ( ack_flag && op_rd_data[5] && op_rd_data[2]) ) begin flow_flag <= 3'b010 ; op_exec <= 1'b1 ; link_error <= 1'b0 ; op_RW <= 1'b0 ; op_addr <= 5'h11 ; op_wr_data <= 16'h0000 ; end else begin link_error <= 1'b1 ; flow_flag <= 3'b111 ; end end end 3'b010:begin op_exec <= 1'b0 ; //读取特殊状态寄存器获取当前网速 // if(op_done && ack_flag) begin // flow_flag <= 3'b011 ; // speed_status <= {op_rd_data[15] , op_rd_data[14]}; // end if(op_done) begin if(ack_flag) begin flow_flag <= 3'b011; speed_status <= {op_rd_data[15] , op_rd_data[14]}; end else begin flow_flag <= 3'b111; link_error <= 1'b1; end end end 3'b011:begin //根据网速点亮对应的led灯 op_exec <= 1'b0 ; if(speed_status == 2'b00) //10Mbps (LED灯为1时点亮) led <= 2'b01; else if(speed_status == 2'b01) //100Mbps led <= 2'b10; else if(speed_status == 2'b10) //1000Mbps led <= 2'b11; else led <= 2'b00; if(cycle_cnt == (POLLING_CYCLE - 1)) begin op_exec <= 1'b1 ; flow_flag <= 3'b001 ; op_addr <= 5'h01 ; op_wr_data <= 16'h0000 ; led <= 2'b00; end end 3'b111:begin //错误状态(自协商或者连接失败):等一轮轮询时间后,再重新轮询基本状态寄存器获取自协商或者连接状态 op_exec <= 1'b0 ; led <= 2'b00 ; link_error <= 1'b0 ; if(cycle_cnt == (POLLING_CYCLE - 1)) begin op_exec <= 1'b1 ; flow_flag <= 3'b001; op_RW <= 1'b0; op_addr <= 5'h01; end end default: begin flow_flag <= 3'b000; op_exec <= 1'b0; end endcase end end endmodule
module mdio_dri ( input dri_clk , // input mdc_clk , input sys_rst_n , input op_exec , input op_RW , //Write : 1 , Read : 0 input [4:0] op_addr , input [15:0] op_wr_data , inout wire eth_mdio , output reg eth_mdc , output reg[15:0] op_rd_data , output reg op_done , output reg op_rd_ack ); reg [4:0] op_addr_t ; reg [15:0] op_wr_data_t ; reg [1:0] op_RW_t ; reg [15:0] op_rd_data_t ; reg mdio_out ; localparam IDLE = 5'b00000; //00 localparam PRE = 5'b00001; //01 localparam ST_OP = 5'b00010; //01 localparam PHY_REG_ADDR = 5'b00100; //04 localparam W_TA_DATA = 5'b01000; //08 localparam R_TA_DATA = 5'b10000; //16 reg [4:0] cur_state; reg [4:0] next_state; reg [5:0] cnt; reg st_done; // An inout port must be a net. Drive it through an internal register so // that 1'bz releases the MDIO bus during PHY-driven read cycles. assign eth_mdio = mdio_out; //save the addr and data always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin op_addr_t <= 'b0 ; op_wr_data_t <= 'b0 ; end else op_addr_t <= op_addr; op_wr_data_t <= op_wr_data; // Write: 01 Read: 10 end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) op_RW_t <= 2'b00 ; else if(op_exec) op_RW_t <= {~op_RW,op_RW}; else op_RW_t <= {op_RW_t}; end // always @(posedge dri_clk or negedge sys_rst_n) begin // if(!sys_rst_n) begin // cnt <= 6'b0; // end // else if( (cnt == 6'd63) | (cnt == 6'd7) | (cnt == 6'd19) | (cnt == 6'd38)) // cnt <= 6'd0; // else if(op_exec) // cnt <= cnt + 1; // end // assign st_done = ( (cnt == 6'd63) | (cnt == 6'd7) | (cnt == 6'd19) | (cnt == 6'd38) ) ? 1'b1 : 1'b0; // generate ETH_MDC // always @(posedge dri_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // eth_mdc <= 1'b1; // else // eth_mdc <= mdc_clk; // end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) eth_mdc <= 1'b1; else if(cnt[0] == 1'b0) eth_mdc <= 1'b1; else eth_mdc <= 1'b0; end // FSM always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) cur_state <= IDLE; else cur_state <= next_state; end always @(*) begin next_state = IDLE; case (cur_state) IDLE: begin if(op_exec) next_state = PRE; else next_state = IDLE; end PRE: begin if(st_done) next_state = ST_OP; else next_state = PRE; end ST_OP: begin if(st_done) next_state = PHY_REG_ADDR; else next_state = ST_OP; end PHY_REG_ADDR: begin if(st_done) begin if(op_RW_t == 2'b01) next_state = W_TA_DATA; else if (op_RW_t == 2'b10) next_state = R_TA_DATA; end else next_state = PHY_REG_ADDR; end W_TA_DATA: begin if(st_done) next_state = IDLE; else next_state = W_TA_DATA; end R_TA_DATA: begin if(st_done) next_state = IDLE; else next_state = R_TA_DATA; end default: next_state = IDLE; endcase end always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin mdio_out <= 1'b0; cnt <= 6'd0; op_rd_ack <= 1'b0; op_done <= 1'b0; op_rd_data <= 16'd0; op_rd_data_t <= 16'd0; st_done <= 1'b0; end else begin cnt <= cnt + 1; case (cur_state) IDLE: begin mdio_out <= 1'bz; cnt <= 6'd0; end PRE : begin case(cnt) 6'd1 : mdio_out <= 1'b1; 6'd62 : st_done <= 1'b1; 6'd63 : begin st_done <= 1'b0; cnt <= 6'b0; end default: ; endcase end ST_OP: begin case(cnt) 6'd1 : mdio_out <= 1'b0; 6'd3 : mdio_out <= 1'b1; 6'd5 : mdio_out <= op_RW_t[1]; 6'd6 : st_done <= 1'b1; 6'd7 : begin mdio_out <= op_RW_t[0]; st_done <= 1'b0; cnt <= 6'b0; end default: ; endcase end PHY_REG_ADDR : begin case(cnt) 6'd1 : mdio_out <= 1'b0; 6'd3 : mdio_out <= 1'b0; 6'd5 : mdio_out <= 1'b1; 6'd7 : mdio_out <= 1'b0; 6'd9 : mdio_out <= 1'b0; 6'd11 : mdio_out <= op_addr_t[4]; 6'd13 : mdio_out <= op_addr_t[3]; 6'd15 : mdio_out <= op_addr_t[2]; 6'd17 : mdio_out <= op_addr_t[1]; 6'd18 : st_done <= 1'b1; 6'd19 : begin mdio_out <= op_addr_t[0]; cnt <= 6'b0; st_done <= 1'b0; end default: ; endcase end W_TA_DATA : begin case(cnt) //下降沿更新 6'd1 : mdio_out <= 1'b1; 6'd3 : mdio_out <= 1'b0; 6'd5 : mdio_out <= op_wr_data_t[15]; 6'd7 : mdio_out <= op_wr_data_t[14]; 6'd9 : mdio_out <= op_wr_data_t[13]; 6'd11 : mdio_out <= op_wr_data_t[12]; 6'd13 : mdio_out <= op_wr_data_t[11]; 6'd15 : mdio_out <= op_wr_data_t[10]; 6'd17 : mdio_out <= op_wr_data_t[9]; 6'd19 : mdio_out <= op_wr_data_t[8]; 6'd21 : mdio_out <= op_wr_data_t[7]; 6'd23 : mdio_out <= op_wr_data_t[6]; 6'd25 : mdio_out <= op_wr_data_t[5]; 6'd27 : mdio_out <= op_wr_data_t[4]; 6'd29 : mdio_out <= op_wr_data_t[3]; 6'd31 : mdio_out <= op_wr_data_t[2]; 6'd33 : mdio_out <= op_wr_data_t[1]; 6'd35 : mdio_out <= op_wr_data_t[0]; 6'd37 : begin mdio_out <= 1'bz; op_done <= 1'b1; end 6'd38 : begin op_done <= 1'b0; end 6'd39: st_done <= 1'b1; 6'd40 : begin st_done <= 1'b0; end default: ; endcase end R_TA_DATA : begin case(cnt) //上升沿采样 6'd1 : mdio_out <= 1'bZ; 6'd2 : ; 6'd4 : op_rd_ack <= ~eth_mdio; //上升沿采样所以在cnt=4 的时候采样而不是 6'd6 : begin op_rd_data_t[15] <= eth_mdio; op_rd_ack <= 1'b0; end //以下时间均是下降沿采样 6'd8 : op_rd_data_t[14] <= eth_mdio; 6'd10 : op_rd_data_t[13] <= eth_mdio; 6'd12 : op_rd_data_t[12] <= eth_mdio; 6'd14 : op_rd_data_t[11] <= eth_mdio; 6'd16 : op_rd_data_t[10] <= eth_mdio; 6'd18 : op_rd_data_t[9] <= eth_mdio; 6'd20 : op_rd_data_t[8] <= eth_mdio; 6'd22 : op_rd_data_t[7] <= eth_mdio; 6'd24 : op_rd_data_t[6] <= eth_mdio; 6'd26 : op_rd_data_t[5] <= eth_mdio; 6'd28 : op_rd_data_t[4] <= eth_mdio; 6'd30 : op_rd_data_t[3] <= eth_mdio; 6'd32 : op_rd_data_t[2] <= eth_mdio; 6'd34 : op_rd_data_t[1] <= eth_mdio; 6'd36 : op_rd_data_t[0] <= eth_mdio; 6'd38 : begin op_rd_data <= op_rd_data_t; end 6'd39 : begin op_done <= 1'b1; st_done <= 1'b1; end 6'd40 : begin op_done <= 1'b0; st_done <= 1'b0; end default: ; endcase end default: ; endcase end end endmodule

2.2 tb

PS:这里mdio_slave_interface是网上找的PHY芯片的仿真模型,相当于IP,这个大家可上网自行查找。

`timescale 1ns/1ps module tb_mdio (); parameter PHY_ADDR = 5'h04; reg touch_key; reg sys_clk ; reg sys_rst_n; wire [1:0] led ; wire eth_mdio; wire eth_mdc ; // pullup(eth_mdio); //模拟没有FPGA/PHY芯片驱动时,就mdio线由上拉电阻管理 always #10 sys_clk = ~sys_clk; initial begin sys_clk <= 1'b1; sys_rst_n <= 1'b0; touch_key <= 1'b0; #120 sys_rst_n <= 1'b1; #500 touch_key <= 1'b1; #100 touch_key <= 1'b0; end mdio_top u_mdio_top( .touch_key (touch_key ), .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .led (led ), .eth_mdio (eth_mdio ), .eth_mdc (eth_mdc ) ); reg [7:0]address_i ; reg [7:0]data_i ; reg strobe_i ; reg we_i ; wire [7:0]data_o ; initial begin address_i <= 8'h00; data_i <= 8'h00; strobe_i <= 1'b0; we_i <= 1'b0; #200 address_i <= 8'h00; data_i <= 8'h00; strobe_i <= 1'b1; we_i <= 1'b1; //配置自协商完成 #20 address_i <= 8'h02; data_i <= 8'h00; #20 address_i <= 8'h03; data_i <= 8'h24; //配置网速 #20 address_i <= 8'h22; data_i <= 8'h80; #20 address_i <= 8'h23; data_i <= 8'h00; //配置PHY芯片地址 #20 address_i <= 8'h40; data_i <= PHY_ADDR; //结束配置 #20 address_i <= 8'h00; data_i <= 8'h00; strobe_i <= 1'b0; we_i <= 1'b0; end //PHY仿真模型 //MDIO协议接口:模拟真实PHY与FPGA通信 //Wishbone配置接口:testbench用来预设虚拟PHY寄存器(两套接口需要对应两头独立的时钟) mdio_slave_interface u_mdio_salve_interface( .rst_n_i (sys_rst_n), .mdc_i (eth_mdc ), .mdio (eth_mdio ), .clk_i (sys_clk ), .rst_i (~sys_rst_n), .address_i (address_i), .data_i (data_i ), .data_o (data_o ), .strobe_i (strobe_i ), .we_i (we_i ), .ack_o () ); endmodule

为什么需要PHY芯片的仿真模型和预配置PHY里面的寄存器呢?

因为mdio读时序的时候从TA的第二位开始就由PHY芯片来驱动mdio线了,因此仿真的时候必须需要PHY仿真模型,来模拟上板后板载PHY的行为。

此前我们说过需要主机来与板载PHY进行自协商,而tb仿真的时候没有主机,那么就只能手动先去配置PHY里面的相关寄存器,使其自协商完成,模拟主机已经和板载PHY自协商成功后的场景。

3 逻辑仿真及波形验证

仿真中遇到的错误:时钟问题

前面说的先系统时钟二分频得到dri_clk,再二分频得到mdc。

一开始我的做法是:系统时钟二分频得到dri_clk , 再二分频得到mdc_clk , 最后再赋值给eth_mdc。

这样做的问题是: 在驱动模块中靠的是cnt 和dri _clk来实现mdio协议包,然后二分频得到mdc_clk再赋值输出eth_mdc给板载PHY,作为PHY的工作时钟。这里的问题就是本来应该FPGA和板载PHY要在同一条eth_mdc时钟线上工作,但是这样做的话就在FPGA和板载PHY就在两条独立的时间上工作了,cnt 和dri _clk组合没有实现和eth_mdc是同一条时间线。

module clk_divide ... // always @(posedge dri_clk or negedge sys_rst_n) begin // if(!sys_rst_n) begin // mdc_clk <= 1'b1; // end // else // mdc_clk <= ~ mdc_clk; // end endmodule module mdio_dri // generate ETH_MDC // always @(posedge dri_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // eth_mdc <= 1'b1; // else // eth_mdc <= mdc_clk; // end endmodule

修改:删掉没用的mdc_clk线,直接使用cnt来对dri _clk进一步二分频得到eth_mdc,这样就使得:cnt为奇数时是eth_mdc得下降沿,nt为偶数时是eth_mdc得上升沿。这样驱动模块靠cnt和dri _clk来收发mdio包就是在eth_mdc时钟下进行的,

module mdio_dri always @(posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) eth_mdc <= 1'b1; else if(cnt[0] == 1'b0) eth_mdc <= 1'b1; else eth_mdc <= 1'b0; end endmodule

最后debug完与上面波形图一致。


(本帖子部分图片来源于《领航者 ZYNQ 之 FPGA 开发指南 V3.4》,在学习该指南的基础上写的个人经验贴,如有侵权请联系我~)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询