FPGA学习篇——以太网学习之通过MDIO接口配置物理层PHY芯片实现
了解知识1 网口线常用于以太网的网口线有RJ45接口、RJ11 接口电话线接口、SC 光纤接口等。RJ45插头如下RJ45 接口定义以及各引脚功能在不同通信速率下是有区别的。基础知识1 通信知识百兆和万兆以太网百兆千兆和万兆以太网指的是传输速率的区别。十兆以太网 10 Mbps百兆以太网 100 Mbps千兆以太网 1000 Mbps 1 Gbps万兆以太网 10,000 Mbps 10 Gbps通信相关的单位知识码元承载信息量的基本单元。一个码元不等于1个bit。要看你怎么设置码元即信息基本单位可以是1个bit2个bit甚至更多bit。码元速率波特率Baud每秒传输的码元数单位“波特每秒Bps”。串口常用的波特率有48009600115200。比特率每秒传输的比特数单位“每秒比特数bps”显然根据定义有比特率 波特率 ✖ 每个码元承载的比特数2 MDIO 接口及其通信协议MAC端这里是FPGA可通过MDIO接口配置以太网的PHY芯片物理层。并且一个MAC可连接多个PHY芯片在后面MDIO通信协议上可以看到PHY 芯片内部包含一系列寄存器用户通过这些寄存器来配置 PHY 芯片的工作模式以及获取 PHY 芯片的状态信息如连接速率、双工模式、自协商状态等。而FPGA 就是通过 MDIO 接口来对 PHY 芯片内部的寄存器进行配置。1 MDIO信号线MDIO只有两根信号线MDC 时钟线最高不超过12.5MHZMDIO双向数据线接收发送2 MDIO接口的通信协议如下Preamble32位前导码由MAC端发送32位逻辑“1”用于同步PHY芯片。STStart of Frame2位帧开始信号用01表示。OPOperation Code2位操作码读10写01。PHYADPHY Address5位PHY地址用于表示与哪个PHY芯片通信因此一个MAC上可以连接多个PHY芯片。REGADRegister Address5位寄存器地址可以表示共32个寄存器。TATurnaround2位转向在读命令中MDIO在此时由MAC驱动改为PHY芯片驱动在第一个TA位MDIO引脚为高阻状态第二个TA位PHY芯片将MDIO引脚拉低准备发送数据在写命令中不需要MDIO方向发生变化MAC固定输出2’b10随后开始写入数据。DATA16位数据在读命令中PHY芯片将读到的对应PHYAD的REGAD寄存器的数据写到DATA中在写命令中MAC将要写入对应PHYAD的REGAD寄存器的值写入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.5MHZFPGA的系统时钟是50MHZ并且还要求下降沿更新上升沿采样。解决给系统时钟先进行二分频得到dri_clk25MHZ进一步得再给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 rtlmodule 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 ( 4d4 ) ) 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 (24d100_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 ) ); endmodulemodule clk_divide # ( parameter CLK_DIV 4d4 )( 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 4b0; dri_clk 1b0; end else if( clk_cnt_dri ((CLK_DIV/2) - 4d1)) begin clk_cnt_dri 4b0; dri_clk ~ dri_clk; end else clk_cnt_dri clk_cnt_dri 1; end这里控制模块的状态机写的有点乱当时被别的事耽搁了小伙伴们可只做参考自己优化module mdio_ctrl #( parameter POLLING_CYCLE 24d100_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 1b0; touch_key_D2 1b0; touch_key_D3 1b0; 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 1b0; else if(op_rd_ack) ack_flag 1b1; else if(op_done) ack_flag 1b0; else ack_flag ack_flag; end always (posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin op_exec 1b0; flow_flag 2b0; //表示当前控制执行到哪一步了 op_RW 1b0; op_addr 5b00000; op_wr_data 16h0000; rst_done_flag 1b0; // error_done 1b0; speed_status2b00; led 2b00; link_error 1b0 ; end //软件复位 else if(touch_key_pos) begin //touch_key 按下输出高电平 op_exec 1b1; flow_flag 3b000; rst_done_flag 1b1; op_RW 1b1; op_addr 5b00000; op_wr_data 16h9000; led 2b00; link_error 1b0; end else begin case(flow_flag) 3b000:begin //软件复位 op_exec 1b0; op_RW 1b0; if(op_done) rst_done_flag 1b0 ; else if(cycle_cnt (POLLING_CYCLE - 1)) begin op_exec 1b1 ; flow_flag 3b001 ; op_addr 5h01 ; op_wr_data 16h0000 ; end else begin op_exec 1b0; flow_flag flow_flag; rst_done_flag rst_done_flag; op_addr op_addr ; op_wr_data op_wr_data ; led led ; end end 3b001:begin //读取基本状态寄存器获取自协商或者连接状态 op_exec 1b0 ; op_RW 1b0 ; if(op_done) begin if( ( ack_flag op_rd_data[5] op_rd_data[2]) ) begin flow_flag 3b010 ; op_exec 1b1 ; link_error 1b0 ; op_RW 1b0 ; op_addr 5h11 ; op_wr_data 16h0000 ; end else begin link_error 1b1 ; flow_flag 3b111 ; end end end 3b010:begin op_exec 1b0 ; //读取特殊状态寄存器获取当前网速 // if(op_done ack_flag) begin // flow_flag 3b011 ; // speed_status {op_rd_data[15] , op_rd_data[14]}; // end if(op_done) begin if(ack_flag) begin flow_flag 3b011; speed_status {op_rd_data[15] , op_rd_data[14]}; end else begin flow_flag 3b111; link_error 1b1; end end end 3b011:begin //根据网速点亮对应的led灯 op_exec 1b0 ; if(speed_status 2b00) //10Mbps (LED灯为1时点亮) led 2b01; else if(speed_status 2b01) //100Mbps led 2b10; else if(speed_status 2b10) //1000Mbps led 2b11; else led 2b00; if(cycle_cnt (POLLING_CYCLE - 1)) begin op_exec 1b1 ; flow_flag 3b001 ; op_addr 5h01 ; op_wr_data 16h0000 ; led 2b00; end end 3b111:begin //错误状态(自协商或者连接失败)等一轮轮询时间后再重新轮询基本状态寄存器获取自协商或者连接状态 op_exec 1b0 ; led 2b00 ; link_error 1b0 ; if(cycle_cnt (POLLING_CYCLE - 1)) begin op_exec 1b1 ; flow_flag 3b001; op_RW 1b0; op_addr 5h01; end end default: begin flow_flag 3b000; op_exec 1b0; end endcase end end endmodulemodule 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 5b00000; //00 localparam PRE 5b00001; //01 localparam ST_OP 5b00010; //01 localparam PHY_REG_ADDR 5b00100; //04 localparam W_TA_DATA 5b01000; //08 localparam R_TA_DATA 5b10000; //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 1bz 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 2b00 ; 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 6b0; // end // else if( (cnt 6d63) | (cnt 6d7) | (cnt 6d19) | (cnt 6d38)) // cnt 6d0; // else if(op_exec) // cnt cnt 1; // end // assign st_done ( (cnt 6d63) | (cnt 6d7) | (cnt 6d19) | (cnt 6d38) ) ? 1b1 : 1b0; // generate ETH_MDC // always (posedge dri_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // eth_mdc 1b1; // else // eth_mdc mdc_clk; // end always (posedge dri_clk or negedge sys_rst_n) begin if(!sys_rst_n) eth_mdc 1b1; else if(cnt[0] 1b0) eth_mdc 1b1; else eth_mdc 1b0; 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 2b01) next_state W_TA_DATA; else if (op_RW_t 2b10) 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 1b0; cnt 6d0; op_rd_ack 1b0; op_done 1b0; op_rd_data 16d0; op_rd_data_t 16d0; st_done 1b0; end else begin cnt cnt 1; case (cur_state) IDLE: begin mdio_out 1bz; cnt 6d0; end PRE : begin case(cnt) 6d1 : mdio_out 1b1; 6d62 : st_done 1b1; 6d63 : begin st_done 1b0; cnt 6b0; end default: ; endcase end ST_OP: begin case(cnt) 6d1 : mdio_out 1b0; 6d3 : mdio_out 1b1; 6d5 : mdio_out op_RW_t[1]; 6d6 : st_done 1b1; 6d7 : begin mdio_out op_RW_t[0]; st_done 1b0; cnt 6b0; end default: ; endcase end PHY_REG_ADDR : begin case(cnt) 6d1 : mdio_out 1b0; 6d3 : mdio_out 1b0; 6d5 : mdio_out 1b1; 6d7 : mdio_out 1b0; 6d9 : mdio_out 1b0; 6d11 : mdio_out op_addr_t[4]; 6d13 : mdio_out op_addr_t[3]; 6d15 : mdio_out op_addr_t[2]; 6d17 : mdio_out op_addr_t[1]; 6d18 : st_done 1b1; 6d19 : begin mdio_out op_addr_t[0]; cnt 6b0; st_done 1b0; end default: ; endcase end W_TA_DATA : begin case(cnt) //下降沿更新 6d1 : mdio_out 1b1; 6d3 : mdio_out 1b0; 6d5 : mdio_out op_wr_data_t[15]; 6d7 : mdio_out op_wr_data_t[14]; 6d9 : mdio_out op_wr_data_t[13]; 6d11 : mdio_out op_wr_data_t[12]; 6d13 : mdio_out op_wr_data_t[11]; 6d15 : mdio_out op_wr_data_t[10]; 6d17 : mdio_out op_wr_data_t[9]; 6d19 : mdio_out op_wr_data_t[8]; 6d21 : mdio_out op_wr_data_t[7]; 6d23 : mdio_out op_wr_data_t[6]; 6d25 : mdio_out op_wr_data_t[5]; 6d27 : mdio_out op_wr_data_t[4]; 6d29 : mdio_out op_wr_data_t[3]; 6d31 : mdio_out op_wr_data_t[2]; 6d33 : mdio_out op_wr_data_t[1]; 6d35 : mdio_out op_wr_data_t[0]; 6d37 : begin mdio_out 1bz; op_done 1b1; end 6d38 : begin op_done 1b0; end 6d39: st_done 1b1; 6d40 : begin st_done 1b0; end default: ; endcase end R_TA_DATA : begin case(cnt) //上升沿采样 6d1 : mdio_out 1bZ; 6d2 : ; 6d4 : op_rd_ack ~eth_mdio; //上升沿采样所以在cnt4 的时候采样而不是 6d6 : begin op_rd_data_t[15] eth_mdio; op_rd_ack 1b0; end //以下时间均是下降沿采样 6d8 : op_rd_data_t[14] eth_mdio; 6d10 : op_rd_data_t[13] eth_mdio; 6d12 : op_rd_data_t[12] eth_mdio; 6d14 : op_rd_data_t[11] eth_mdio; 6d16 : op_rd_data_t[10] eth_mdio; 6d18 : op_rd_data_t[9] eth_mdio; 6d20 : op_rd_data_t[8] eth_mdio; 6d22 : op_rd_data_t[7] eth_mdio; 6d24 : op_rd_data_t[6] eth_mdio; 6d26 : op_rd_data_t[5] eth_mdio; 6d28 : op_rd_data_t[4] eth_mdio; 6d30 : op_rd_data_t[3] eth_mdio; 6d32 : op_rd_data_t[2] eth_mdio; 6d34 : op_rd_data_t[1] eth_mdio; 6d36 : op_rd_data_t[0] eth_mdio; 6d38 : begin op_rd_data op_rd_data_t; end 6d39 : begin op_done 1b1; st_done 1b1; end 6d40 : begin op_done 1b0; st_done 1b0; end default: ; endcase end default: ; endcase end end endmodule2.2 tbPS这里mdio_slave_interface是网上找的PHY芯片的仿真模型相当于IP这个大家可上网自行查找。timescale 1ns/1ps module tb_mdio (); parameter PHY_ADDR 5h04; 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 1b1; sys_rst_n 1b0; touch_key 1b0; #120 sys_rst_n 1b1; #500 touch_key 1b1; #100 touch_key 1b0; 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 8h00; data_i 8h00; strobe_i 1b0; we_i 1b0; #200 address_i 8h00; data_i 8h00; strobe_i 1b1; we_i 1b1; //配置自协商完成 #20 address_i 8h02; data_i 8h00; #20 address_i 8h03; data_i 8h24; //配置网速 #20 address_i 8h22; data_i 8h80; #20 address_i 8h23; data_i 8h00; //配置PHY芯片地址 #20 address_i 8h40; data_i PHY_ADDR; //结束配置 #20 address_i 8h00; data_i 8h00; strobe_i 1b0; we_i 1b0; 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 1b1; // 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 1b1; // 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 1b1; else if(cnt[0] 1b0) eth_mdc 1b1; else eth_mdc 1b0; end endmodule最后debug完与上面波形图一致。本帖子部分图片来源于《领航者 ZYNQ 之 FPGA 开发指南 V3.4》在学习该指南的基础上写的个人经验贴如有侵权请联系我~