【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片
原创博客,转载请注明出处:【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片 – 没落骑士 – 博客园
一、前言
本文设计思想采用明德扬至简设计法。以太网这一高效实用的数据传输方式应用于各个领域,如网络交换设备,高速网络相机等。虽然各FPGA厂商都提供MAC IP核,但大多收费,有时无法破解。不同厂家之间无法移植,而且为了通用性考虑牺牲了效率,因此自己动手写一个以太网MAC是个不错的选择。
本博文讨论通过MDIO接口管理PHY芯片来验证其正确工作,为在此基础上设计MAC逻辑开个头。PHY芯片采用RTL8211EGVB,选用GMII接口与MAC连接。下面我们来开始第一步,在此之前明确设计目的:检测PHY芯片是否完成自动协商 链路速率是否达到1000M。所以要从datasheet中了解到芯片引脚 寄存器地址 接口时序。
二、设计分析
管理帧格式如下:
读写操作时序:
MDC为MAC驱动时钟信号,MDIO是串行数据总线,需要连接上拉电阻保证idle状态下高电平。其中前导码包含32个比特“1”,PHY地址根据芯片引脚连接而定,此处为01.turn around域是为了防止读操作时数据冲突,在读操作过程中MAC和PHY均在第1比特处进入高阻态,PHY在第2比特处驱动MDIO接口为低电平以占据总线控制权。注意两点:第一如果时钟信号在读写操作后停止,时钟必须保证至少7个时钟周期持续翻转且MDIO高电平从而保证之前的操作完成。故在设计中可以等待一段时间后再拉低时钟使能信号。第二两个操作之间至少一个idle比特。
正确驱动接口时序需要关注AC characterisics.
很明显MAC驱动总线时,在MDC下降沿更新数据。而PHY驱动总线时,MDC上升沿后更新数据。根据datasheet中的timing参数设定MDC时钟周期是800ns,MAC接收PHY数据时下降沿采样。
接下来关注要访问的内部寄存器地址,首先读取PHY寄存器数据以检测其工作状态,若发现异常再考虑写入数据。这里读取基本模式状态寄存器0X01的bit5,若为1说明自动协商完成。第二个寄存器是PHY特定状态寄存器0X11中的[15:14]和13,分别是当前速率和全/半双工通信模式。若检测到自动协商完成,且工作在1000M全双工模式下,说明工作正确。
三、硬件架构与状态机设计
所有准备工作完成,现在开始设计。按照“自顶向下”设计原则,规划好整体结构和模块间接口,再设计内部状态机一步步实现逻辑功能。
Mdio_ctrl模块负责完成PHY芯片的配置与检测逻辑,Mdio接口模块完成读写操作时序。此处仅通过读操作简单检测PHY状态,暂不进行配置,故两模块工作状态跳转如图所示:
剩下的工作就是把两个状态机实现出来,非常简单。有需要的朋友可以参考一下,关于芯片的具体参数详见:Realtek RTL8211E(G)-VB(VL)-CG Datasheet 1.8.上代码!
四、代码编写
MDIO控制模块:
1 `timescale 1ns / 1ps 2 3 module mdio_ctrl( 4 input clk,//100M 5 input rst_n, 6 7 input en, 8 output reg chk_result =0, 9 output reg chk_vld =0, 10 11 input rdy, 12 output reg rd_en =0, 13 output reg [5-1:0] phy_addr =0, 14 output reg [5-1:0] reg_addr =0, 15 input [16-1:0] rd_data, 16 input rd_vld 17 ); 18 19 parameter MS_CYC = 100_000; 20 21 22 localparam IDLE = 0 ; 23 localparam WAIT = 1 ; 24 localparam RD_PHY = 2 ; 25 localparam CHECK = 3 ; 26 27 localparam WAIT_MS = 10; 28 29 localparam BMSR = 5\'h01, 30 PHYSR = 5\'h11; 31 32 reg [4-1:0] state_c = 0,state_n = 0; 33 wire idle2wait,wait2rd_phy,rd_phy2check,check2idle,check2wait; 34 wire link_up; 35 reg [16-1:0] rd_memory [0:1]; 36 reg [ (17-1):0] ms_cnt =0 ; 37 wire add_ms_cnt ; 38 wire end_ms_cnt ; 39 reg [ (4-1):0] wait_cnt =0 ; 40 wire add_wait_cnt ; 41 wire end_wait_cnt ; 42 reg [ (2-1):0] rd_cnt =0 ; 43 wire add_rd_cnt ; 44 wire end_rd_cnt ; 45 reg [ (2-1):0] rdata_cnt =0 ; 46 wire add_rdata_cnt ; 47 wire end_rdata_cnt ; 48 wire [5*2-1:0] registers; 49 reg rd_finish = 0; 50 51 initial begin 52 rd_memory[0] = 0; 53 rd_memory[1] = 0; 54 end 55 56 always @(posedge clk or negedge rst_n) begin 57 if (rst_n==0) begin 58 state_c <= IDLE ; 59 end 60 else begin 61 state_c <= state_n; 62 end 63 end 64 65 always @(*) begin 66 case(state_c) 67 IDLE :begin 68 if(idle2wait) 69 state_n = WAIT ; 70 else 71 state_n = state_c ; 72 end 73 WAIT :begin 74 if(wait2rd_phy) 75 state_n = RD_PHY ; 76 else 77 state_n = state_c ; 78 end 79 RD_PHY :begin 80 if(rd_phy2check) 81 state_n = CHECK ; 82 else 83 state_n = state_c ; 84 end 85 CHECK :begin 86 if(check2idle) 87 state_n = IDLE ; 88 else if(check2wait) 89 state_n = WAIT ; 90 else 91 state_n = state_c ; 92 end 93 default : state_n = IDLE ; 94 endcase 95 end 96 97 assign idle2wait = state_c==IDLE && (en); 98 assign wait2rd_phy = state_c==WAIT && (end_wait_cnt); 99 assign rd_phy2check = state_c==RD_PHY && (end_rdata_cnt); 100 assign check2idle = state_c==CHECK && (link_up); 101 assign check2wait = state_c==CHECK && (!link_up); 102 103 104 assign link_up = rd_memory[0][5] == 1\'b1 && rd_memory[1][15:13] == 3\'b10_1;//auto_nego && gigabit && full_duplex 105 106 107 //计数器 108 always @(posedge clk or negedge rst_n) begin 109 if (rst_n==0) begin 110 ms_cnt <= 0; 111 end 112 else if(add_ms_cnt) begin 113 if(end_ms_cnt) 114 ms_cnt <= 0; 115 else 116 ms_cnt <= ms_cnt+1 ; 117 end 118 end 119 assign add_ms_cnt = (state_c == WAIT); 120 assign end_ms_cnt = add_ms_cnt && ms_cnt == (MS_CYC)-1 ;//100MHZ时钟100_000 121 122 always @(posedge clk or negedge rst_n) begin 123 if (rst_n==0) begin 124 wait_cnt <= 0; 125 end 126 else if(add_wait_cnt) begin 127 if(end_wait_cnt) 128 wait_cnt <= 0; 129 else 130 wait_cnt <= wait_cnt+1 ; 131 end 132 end 133 assign add_wait_cnt = (end_ms_cnt); 134 assign end_wait_cnt = add_wait_cnt && wait_cnt == (WAIT_MS)-1 ; 135 136 always @(posedge clk or negedge rst_n) begin 137 if (rst_n==0) begin 138 rd_cnt <= 0; 139 end 140 else if(add_rd_cnt) begin 141 if(end_rd_cnt) 142 rd_cnt <= 0; 143 else 144 rd_cnt <= rd_cnt+1 ; 145 end 146 end 147 assign add_rd_cnt = (state_c == RD_PHY && rdy && !rd_finish); 148 assign end_rd_cnt = add_rd_cnt && rd_cnt == (2)-1 ; 149 150 always @(posedge clk or negedge rst_n)begin 151 if(rst_n==1\'b0)begin 152 rd_finish <= 0; 153 end 154 else if(end_rd_cnt)begin 155 rd_finish <= 1\'b1; 156 end 157 else if(state_c == CHECK) 158 rd_finish <= 0; 159 end 160 161 162 always @(posedge clk or negedge rst_n) begin 163 if (rst_n==0) begin 164 rdata_cnt <= 0; 165 end 166 else if(add_rdata_cnt) begin 167 if(end_rdata_cnt) 168 rdata_cnt <= 0; 169 else 170 rdata_cnt <= rdata_cnt+1 ; 171 end 172 end 173 assign add_rdata_cnt = (rd_vld); 174 assign end_rdata_cnt = add_rdata_cnt && rdata_cnt == (2)-1 ; 175 176 //接口信号逻辑 177 always @(posedge clk or negedge rst_n)begin 178 if(rst_n==1\'b0)begin 179 rd_en <= 0; 180 phy_addr <= 0; 181 reg_addr <= 0; 182 end 183 else if(add_rd_cnt)begin 184 rd_en <= 1\'b1; 185 phy_addr <= 5\'b00001; 186 reg_addr <= registers[10-5*rd_cnt-1 -:5]; 187 end 188 else begin 189 rd_en <= 0; 190 phy_addr <= 0; 191 reg_addr <= 0; 192 end 193 end 194 195 assign registers = {BMSR,PHYSR};//5\'h01,5\'h11 196 197 always @(posedge clk or negedge rst_n)begin 198 if(rst_n==1\'b0)begin 199 rd_memory[0] <= 0; 200 rd_memory[1] <= 0; 201 end 202 else if(add_rdata_cnt)begin 203 rd_memory[rdata_cnt] <= rd_data; 204 end 205 end 206 207 //用户侧输出检测结果 208 always @(posedge clk or negedge rst_n)begin 209 if(rst_n==1\'b0)begin 210 chk_vld <= 0; 211 end 212 else if(state_c == CHECK)begin 213 chk_vld <= 1\'b1; 214 end 215 else 216 chk_vld <= 0; 217 end 218 219 always @(posedge clk or negedge rst_n)begin 220 if(rst_n==1\'b0)begin 221 chk_result <= 0; 222 end 223 else if(check2idle)begin 224 chk_result <= 1\'b1; 225 end 226 else if(check2wait) 227 chk_result <= 0; 228 end 229 230 endmodule
mdio_ctrl
MDIO时序接口模块:
1 `timescale 1ns / 1ps 2 3 module mdio_interface#(parameter MDC_CYC = 800)//ns 4 ( 5 input clk,//100M时钟 6 input rst_n, 7 8 input rd_en, 9 input [5-1:0] phy_addr, 10 input [5-1:0] reg_addr, 11 output reg [16-1:0] rd_data =0, 12 output reg rd_vld =0, 13 output reg rdy =0, 14 15 output reg mdo =1, 16 output reg mdo_en =0, 17 input mdi, 18 output reg mdc =1 19 ); 20 21 localparam N = MDC_CYC/10; 22 23 24 localparam IDLE = 0 ; 25 localparam WRI_COM = 1 ; 26 localparam RD_DATA = 2 ; 27 28 localparam PRE = 32\'hffff_ffff, 29 START = 2\'b01, 30 OP = 2\'b10, 31 TA = 2\'b11; 32 33 reg [3-1:0] state_c =0,state_n =0; 34 wire idle2wri_com,wri_com2rd_data,rd_data2idle; 35 reg [ (7-1):0] div_cnt =0 ; 36 wire add_div_cnt ; 37 wire end_div_cnt ; 38 reg [ (6-1):0] bit_cnt =0 ; 39 wire add_bit_cnt ; 40 wire end_bit_cnt ; 41 reg [6-1:0] M =0; 42 wire [48-1:0] command; 43 reg rd_flag =0 ; 44 reg [5-1:0] phy_addr_tmp = 0; 45 reg [5-1:0] reg_addr_tmp = 0; 46 47 48 //寄存地址 49 always @(posedge clk or negedge rst_n)begin 50 if(rst_n==1\'b0)begin 51 phy_addr_tmp <= 0; 52 reg_addr_tmp <= 0; 53 end 54 else if(rd_en)begin 55 phy_addr_tmp <= phy_addr; 56 reg_addr_tmp <= reg_addr; 57 end 58 end 59 60 61 always@(*)begin 62 if(state_c == IDLE && !rd_en && !rd_flag) 63 rdy <= 1; 64 else 65 rdy <= 0; 66 end 67 68 always @(posedge clk or negedge rst_n) begin 69 if (rst_n==0) begin 70 state_c <= IDLE ; 71 end 72 else begin 73 state_c <= state_n; 74 end 75 end 76 77 always @(*) begin 78 case(state_c) 79 IDLE :begin 80 if(idle2wri_com) 81 state_n = WRI_COM ; 82 else 83 state_n = state_c ; 84 end 85 WRI_COM :begin 86 if(wri_com2rd_data) 87 state_n = RD_DATA ; 88 else 89 state_n = state_c ; 90 end 91 RD_DATA :begin 92 if(rd_data2idle) 93 state_n = IDLE ; 94 else 95 state_n = state_c ; 96 end 97 default : state_n = IDLE ; 98 endcase 99 end 100 101 102 assign idle2wri_com = state_c==IDLE && end_div_cnt && (rd_flag || rd_en); 103 assign wri_com2rd_data = state_c==WRI_COM && end_bit_cnt; 104 assign rd_data2idle = state_c==RD_DATA && end_bit_cnt; 105 106 107 always @(posedge clk or negedge rst_n )begin 108 if(rst_n==0) begin 109 rd_flag <= (0) ; 110 end 111 else if(state_c == IDLE && rd_en)begin 112 rd_flag <= (1\'b1) ; 113 end 114 else if(state_c == WRI_COM) 115 rd_flag <= 0; 116 end 117 118 119 //分频计数器 120 always @(posedge clk or negedge rst_n) begin 121 if (rst_n==0) begin 122 div_cnt <= 0; 123 end 124 else if(add_div_cnt) begin 125 if(end_div_cnt) 126 div_cnt <= 0; 127 else 128 div_cnt <= div_cnt+1 ; 129 end 130 end 131 assign add_div_cnt = (1); 132 assign end_div_cnt = add_div_cnt && div_cnt == (N)-1 ; 133 134 //比特计数器 135 always @(posedge clk or negedge rst_n) begin 136 if (rst_n==0) begin 137 bit_cnt <= 0; 138 end 139 else if(add_bit_cnt) begin 140 if(end_bit_cnt) 141 bit_cnt <= 0; 142 else 143 bit_cnt <= bit_cnt+1 ; 144 end 145 end 146 assign add_bit_cnt = (end_div_cnt && state_c != IDLE); 147 assign end_bit_cnt = add_bit_cnt && bit_cnt == (M)-1 ; 148 149 always@(*)begin 150 case(state_c) 151 WRI_COM:M = 48; 152 RD_DATA:M = 16; 153 default:M = 10; 154 endcase 155 end 156 157 //mdc时钟 158 always @(posedge clk or negedge rst_n )begin 159 if(rst_n==0) begin 160 mdc <= (1\'b1) ; 161 end 162 else if(add_div_cnt && div_cnt == (N>>1) - 1)begin 163 mdc <= (1\'b1) ; 164 end 165 else if(end_div_cnt) 166 mdc <= 0; 167 end 168 169 170 //mdio输出 171 always @(posedge clk or negedge rst_n )begin 172 if(rst_n==0) begin 173 mdo <= (1\'b1) ; 174 end 175 else if(add_bit_cnt && state_c == WRI_COM)begin 176 mdo <= command[48-1-bit_cnt] ; 177 end 178 else if(state_c != WRI_COM) 179 mdo <= 1\'b1; 180 end 181 182 assign command = {PRE,START,OP,phy_addr_tmp,reg_addr_tmp,TA}; 183 184 always @(posedge clk or negedge rst_n )begin 185 if(rst_n==0) begin 186 mdo_en <= (0) ; 187 end 188 else if(state_c == WRI_COM && add_bit_cnt) 189 case(bit_cnt) 190 0: mdo_en <= 1\'b1; 191 46:mdo_en <= 0; 192 default:; 193 endcase 194 end 195 196 //mdio输入 197 always @(posedge clk or negedge rst_n )begin 198 if(rst_n==0) begin 199 rd_data <= (0) ; 200 end 201 else if(add_bit_cnt && state_c == RD_DATA)begin 202 rd_data[16-1-bit_cnt] <= (mdi) ; 203 end 204 end 205 206 always @(posedge clk or negedge rst_n )begin 207 if(rst_n==0) begin 208 rd_vld <= (0) ; 209 end 210 else if(rd_data2idle)begin 211 rd_vld <= (1\'b1) ; 212 end 213 else 214 rd_vld <= 0; 215 end 216 217 218 endmodule
mdio_interface
顶层封装:
1 `timescale 1ns / 1ps 2 3 module phy_manage( 4 input clk, 5 input rst_n, 6 7 input mdio_en, 8 output link_up, 9 output chk_done, 10 11 output mdc, 12 inout mdio 13 ); 14 15 wire rdy; 16 wire rd_en; 17 wire [5-1:0] phy_addr; 18 wire [5-1:0] reg_addr; 19 (*DONT_TOUCH = "TRUE"*)wire [16-1:0] rd_data; 20 wire rd_vld; 21 wire mdo_en,mdo,mdi; 22 23 24 mdio_ctrl mdio_ctrl( 25 .clk (clk) ,//100M 26 .rst_n (rst_n) , 27 28 .en (mdio_en) , 29 .chk_result(link_up) , 30 .chk_vld (chk_done) , 31 32 .rdy (rdy) , 33 .rd_en (rd_en) , 34 .phy_addr (phy_addr) , 35 .reg_addr (reg_addr) , 36 .rd_data (rd_data) , 37 .rd_vld (rd_vld) 38 ); 39 40 mdio_interface#(.MDC_CYC(800))//ns 41 mdio_interface 42 ( 43 .clk (clk) ,//100M时钟 44 .rst_n (rst_n) , 45 46 .rd_en (rd_en) , 47 .phy_addr (phy_addr) , 48 .reg_addr (reg_addr) , 49 .rd_data (rd_data) , 50 .rd_vld (rd_vld) , 51 .rdy (rdy) , 52 53 .mdo (mdo) , 54 .mdo_en (mdo_en) , 55 .mdi (mdi) , 56 .mdc (mdc) 57 ); 58 59 //三态门 60 assign mdio = mdo_en ? mdo : 1\'bz; 61 assign mdi = mdio; 62 63 endmodule
phy_manage
五、功能仿真
之后编写testbench进行行为仿真:
1 `timescale 1 ns/1 ps 2 3 `define BIT_CNT uut.mdio_interface.bit_cnt 4 5 module phy_manage_tb(); 6 7 //时钟和复位 8 reg clk ; 9 reg rst_n; 10 11 //uut的输入信号 12 reg mdio_en; 13 14 //uut的输出信号 15 wire link_up; 16 wire chk_done; 17 wire mdc; 18 wire mdio; 19 wire [16-1:0] back_data1,back_data2; 20 21 //时钟周期,单位为ns,可在此修改时钟周期。 22 parameter CYCLE = 10; 23 24 //复位时间,此时表示复位3个时钟周期的时间。 25 parameter RST_TIME = 2 ; 26 27 defparam uut.mdio_ctrl.MS_CYC = 100; 28 29 //待测试的模块例化 30 phy_manage uut( 31 .clk (clk) , 32 .rst_n (rst_n) , 33 34 .mdio_en (mdio_en) , 35 .link_up (link_up) , 36 .chk_done (chk_done) , 37 38 .mdc (mdc) , 39 .mdio (mdio) 40 ); 41 42 43 //生成本地时钟50M 44 initial begin 45 clk = 1; 46 forever 47 #(CYCLE/2) 48 clk=~clk; 49 end 50 51 //产生复位信号 52 initial begin 53 rst_n = 1; 54 #1; 55 rst_n = 0; 56 #(CYCLE*RST_TIME); 57 rst_n = 1; 58 end 59 60 //输入信号din0赋值方式 61 initial begin 62 #1; 63 //赋初值 64 mdio_en = 0; 65 #(10*CYCLE); 66 mdio_en = 1; 67 #(1*CYCLE); 68 mdio_en = 0; 69 //开始赋值 70 #100_000; 71 $stop; 72 end 73 74 //模拟PHY响应 75 76 //data 77 assign back_data1 = {16\'b0000_0000_0010_0000}; 78 assign back_data2 = {16\'b1010_0000_0000_0000}; 79 80 integer i = 0,j = 0; 81 initial begin 82 forever begin 83 wait(uut.mdio_interface.state_c == 1 && `BIT_CNT == 47 ); 84 @(posedge mdc); 85 force mdio = 0; 86 @(posedge mdc); 87 j = j+1; 88 if(j == 1) 89 force mdio = back_data1[16-1-i+1]; 90 else 91 force mdio = back_data2[16-1-i+1]; 92 93 wait(uut.mdio_interface.state_c == 0); 94 @(posedge mdc); 95 release mdio; 96 end 97 end 98 99 initial begin 100 forever begin 101 @(posedge mdc); 102 if(uut.mdio_interface.state_c == 2)begin 103 #10; 104 i = i+1; 105 end 106 else 107 i = 0; 108 end 109 end 110 111 112 endmodule
phy_manage_tb
testbench中利用force强迫更新mdio双向端口方式模拟PHY芯片响应。仿真波形上半部分为MDIO控制模块信号,下半部分则是MDIO时序接口模块信号。可见当读取寄存器数值满足PHY工作需求时,link_up信号拉高,证明此时MAC可以传输数据给PHY。
六、板级调试
完整的设计,板级调试是必不可少的。真正地将接口调通,PHY芯片正确响应才能说明达到设计目的。顶层封装测试工程,内部例化:差分时钟缓冲原语、PLL、PHY管理顶层封装以及VIO ILA调试IP。我们来看下原理图顶层:
测试工程顶层:
1 `timescale 1ns / 1ps 2 3 4 module mdio_test( 5 input sys_clk_p, 6 input sys_clk_n, 7 input rst_n, 8 9 output mdc, 10 inout mdio, 11 12 output phy_reset//PHY芯片复位信号 低有效 13 ); 14 15 16 wire sys_clk_ibufg; 17 wire clk; 18 wire en; 19 wire chk_done; 20 wire link_up; 21 22 assign phy_reset = 1\'b1;//始终不复位 23 24 IBUFGDS # 25 ( 26 .DIFF_TERM ("FALSE"), 27 .IBUF_LOW_PWR ("FALSE") 28 ) 29 u_ibufg_sys_clk 30 ( 31 .I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接 32 .IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接 33 .O (sys_clk_ibufg) //时钟缓冲输出 34 ); 35 36 clk_wiz_0 u_clk 37 ( 38 // Clock out ports 39 .clk_out1(clk), // output clk_out1 100Mhz 40 // Clock in ports 41 .clk_in1(sys_clk_ibufg)); // input clk_in1 42 43 vio_0 u_vio ( 44 .clk(clk), // input wire clk 45 .probe_out0(en) // output wire [0 : 0] probe_out0 46 ); 47 48 phy_manage phy_manage( 49 .clk (clk) , 50 .rst_n (rst_n) , 51 52 .mdio_en (en) , 53 .link_up (link_up) , 54 .chk_done (chk_done) , 55 56 .mdc (mdc) , 57 .mdio (mdio) 58 ); 59 60 61 endmodule
mdio_test
时钟引脚约束文件:
1 create_clock -period 5.000 [get_ports sys_clk_p] 2 set_property PACKAGE_PIN R4 [get_ports sys_clk_p] 3 set_property IOSTANDARD DIFF_SSTL15 [get_ports sys_clk_p] 4 5 set_property PACKAGE_PIN T6 [get_ports rst_n] 6 set_property IOSTANDARD LVCMOS15 [get_ports rst_n] 7 8 set_property PACKAGE_PIN W10 [get_ports mdc] 9 set_property IOSTANDARD LVCMOS33 [get_ports mdc] 10 11 set_property PACKAGE_PIN V10 [get_ports mdio] 12 set_property IOSTANDARD LVCMOS33 [get_ports mdio] 13 14 set_property PACKAGE_PIN L15 [get_ports phy_reset] 15 set_property IOSTANDARD LVCMOS33 [get_ports phy_reset]
clk_pin
有一点相信调试过以太网的人大多都跳过一个坑:没有驱动PHY的复位输入信号。本人也在此处栽过跟头,这里直接连续赋值拉高PHY芯片复位信号。关于板级调试还有个小技巧,根据高亚军老师的书籍得知,将set up debug生成的ILA探针相关约束命令单独放入一个约束文件便于调试IP的管理和修改,debug约束文件就不贴出来了。
查看debug波形,MDIO时序接口模块在释放MDIO串行总线时,由于存在上拉电阻为高电平,下一个MDC时钟上升沿时刻,PHY拉低MDIO信号响应并得到总线控制权,开始输出数据。
得到读取的两个寄存器数据,根据数值分析满足:PHY自动协商完成,且工作在全双工1000Mbps速率下。
最终RJ45接口绿色指示灯常亮,表明自动协商完成,网络连接正确。到此简易的PHY芯片检测管理模块设计完成。