在以太网PHY 之 MDIO接口及应用文章中,我们介绍了MDIO 的原理,本节中,主要讲述MDIO 的Verilog 语言实现。 MDIO可以通过Verilog ,VHDL, C语言等多种方法实现, 根据用户的不同需求和硬件资源情况自行处理。
由于MDIO读写过程相对比较复杂,更多细节会在视频里进行讲解。本节课通过Verilog代码分为三个部分实现,分别为MDIO IP框架设计,MDIO端口定义及描述,MDIO代码实现。下文就这三个部分展开讲解。
1. MDIO IP核框架设计
MDIO的IP 框架图如下:
图1 IP框架图
本节课将使用的MDIO 协议格式(SMI)如下:

图2

图3

图4
根据图2,3,4中所示的MDIO协议设计FPGA程序。
2. 输入和输出端口定义及描述
- phy_addr[4:0]:指定以太网PHY 的地址(根据硬件原理图连线),软件或者FPGA上层设置这个端口。
- smi_data[24:0]:输入数据接口,25bit 包括: {st_op, phy_reg, phy_reg_val_w} <= smi_data;
- smi_en:输入信号(enable),当smi_done == 1 时,启动数据发送,smi_en 是单时钟周期的信号。
- smi_dout [15:0]:从以太网PHY中读出的数据, 将被送给上层软件或者FPGA。当smi_done == 1 后, 软件或者上层的FPGA就可以读取这个值了。
- smi_done:MDIO 操作完成指示信号。 指示smi_dout 是否有效,以及上层软件或FPGA是否可以发送下一个命令。
- sim_err:指示本次输入命令无效。
- e_mdc: 连接 MDC pin 到以太网PHY。
- e_mdio_i: 输入信号到MDIO pin 三态门中。
- e_mdio_o:从MDIO pin 三态中得到的输出信号。
- e_mdio_t :输出信号到MDIO pin 三态门中,指示当前三态门的状态(输出,输入)。
3.Verilog代码设计
以下代码基于图1中的框架设计,程序如下:
- mdio_phy.v 代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
`timescale 1ns / 1ps module mdio_phy ( input sys_clk, input mdio_en, input [3:0] st_op, // 4'b0110 = read ; 4'b0101 = write input [4:0] phy_addr, input [4:0] phy_reg, input [15:0] phy_reg_val_w, output reg [15:0] phy_reg_val_r = 0, output reg val_r_rdy = 0, input us_sec, output e_mdc, input e_mdio_i, output e_mdio_o, output reg e_mdio_t = 1, output reg mdio_end = 0, output reg mdio_err = 0, input reset ); //------------------------------------------------------------ reg mdc_clk_r = 0; always @ (posedge sys_clk) if(us_sec) mdc_clk_r <= ~mdc_clk_r; assign e_mdc = mdc_clk_r; //assign e_mdio_o = e_mdio_t ? 1'bz : 1'b0; assign e_mdio_o = 1'b0; reg e_mdio_r = 1; always @ (posedge sys_clk) e_mdio_r <= e_mdio_i; //------------------------------------------------------------ reg [1:0] e_mdc_r = 0; reg e_mdc_pos = 0; reg e_mdc_neg = 0; always @ (posedge sys_clk) begin e_mdc_r <= {e_mdc_r[0],mdc_clk_r}; case (e_mdc_r) 2'b01: begin e_mdc_pos <= 1; e_mdc_neg <= 0; end 2'b10: begin e_mdc_pos <= 0; e_mdc_neg <= 1; end default: begin e_mdc_pos <= 0; e_mdc_neg <= 0; end endcase end //------------------------------------------------------------ wire [1:0] turn_ard = 2'b10; wire [15:0] smi_cmd_h = {st_op[3:0], phy_addr[4:0], phy_reg[4:0], turn_ard[1:0]}; //------------------------------------------------------------ reg [4:0] mdio_cnt = 0; reg [3:0] mdio_st = 0; always @ (posedge sys_clk or posedge reset) if(reset) begin mdio_err <= 0; e_mdio_t <= 1; mdio_cnt <= 0; mdio_end <= 0; mdio_st <= 0; end else case (mdio_st) 0: begin val_r_rdy <= 0; e_mdio_t <= 1; mdio_cnt <= 0; mdio_end <= 0; if(mdio_en & (|st_op[1:0])) begin mdio_end <= 0; mdio_err <= 0; mdio_st <= 1; end end 1: // 32 preamble begin if(e_mdc_neg) begin e_mdio_t <= 1'b1; mdio_cnt <= mdio_cnt + 1; end if(&mdio_cnt) mdio_st <= 2; end 2: begin mdio_cnt <= 0; case(st_op[1:0]) 2'b10: mdio_st <= 3; //read 2'b01: mdio_st <= 8; //write default: begin mdio_st <= 12; mdio_err <= 1; end endcase end 3: //read begin if(e_mdc_neg) begin e_mdio_t <= smi_cmd_h[15 - mdio_cnt]; mdio_cnt <= mdio_cnt + 1; mdio_st <= 4; end end 4: begin if(mdio_cnt == 15) mdio_st <= 5; else mdio_st <= 3; end 5: // turn around begin mdio_cnt <= 0; if(e_mdc_neg) begin e_mdio_t <= 1'b1; mdio_st <= 6; end end 6: begin if(e_mdc_neg) begin phy_reg_val_r[15 - mdio_cnt] <= e_mdio_r; mdio_cnt <= mdio_cnt + 1; mdio_st <= 7; end end 7: begin if(mdio_cnt == 16) begin val_r_rdy <= 1; mdio_st <= 14; end else mdio_st <= 6; end 8: //write begin if(e_mdc_neg) begin e_mdio_t <= smi_cmd_h[15 - mdio_cnt]; mdio_cnt <= mdio_cnt + 1; mdio_st <= 9; end end 9: begin if(mdio_cnt == 16) begin mdio_cnt <= 0; mdio_st <= 10; end else mdio_st <= 8; end 10: begin if(e_mdc_neg) begin e_mdio_t <= phy_reg_val_w[15 - mdio_cnt]; mdio_cnt <= mdio_cnt + 1; mdio_st <= 11; end end 11: begin if(mdio_cnt == 16) mdio_st <= 14; else mdio_st <= 10; end 12: // error begin mdio_err <= 0; mdio_cnt <= 0; mdio_st <= 13; end 13: begin if(e_mdc_neg) mdio_cnt <= mdio_cnt + 1; if(mdio_cnt == 31) begin mdio_end <= 1; mdio_st <= 0; end end 14: // end begin val_r_rdy <= 0; if(e_mdc_neg) begin e_mdio_t <= 1'b1; mdio_st <= 15; end end 15: begin if(e_mdc_neg) begin mdio_end <= 1; mdio_st <= 0; end end endcase endmodule |
- mdio_core.v 代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
`timescale 1ns / 1ps module mdio_core # ( parameter MAIN_CLK = 125, parameter PHY_SPEED = "AUTO" // "AUTO", "1G", "100M", "10M" ) ( input sys_clk, input [4:0] phy_addr, input [24:0] smi_data, input smi_en, output e_mdc, input e_mdio_i, output e_mdio_o, output e_mdio_t, output reg [15:0] smi_dout = 0, output reg smi_done = 1'b0, output smi_err, output [1:0] test_pin, input reset ); //------------------------------------------------------------ localparam SMI_ST = 2'b01; localparam SMI_IDLE = 2'b11; localparam WR = 2'b01; localparam RD = 2'b10; localparam NO = 2'b00; //------------------------------------------------------------ localparam US_CNT = (MAIN_CLK == 125) ? 124 : (MAIN_CLK == 100) ? 99 : (MAIN_CLK == 50) ? 49 : 49; reg [7:0] us_cnt_r = 0; wire us_sec = (us_cnt_r == US_CNT) ? 1'b1 : 1'b0; always @ (posedge sys_clk) if(us_sec) us_cnt_r <= 0; else us_cnt_r <= us_cnt_r + 1; //------------------------------------------------------------ wire mdio_end; wire phy_err; reg cmd_err = 0; reg mdio_en = 0; reg [3:0] st_op = {SMI_IDLE,NO}; reg [4:0] phy_reg = 0; reg [15:0] phy_reg_val_w = 0; reg [2:0] ctrl_st = 0; always @ (posedge sys_clk or posedge reset) if(reset) begin cmd_err <= 0; smi_done <= 1'b1; st_op <= {SMI_IDLE,NO}; mdio_en <= 1'b0; ctrl_st <= 0; end else case(ctrl_st) 0: begin mdio_en <= 1'b0; smi_done <= 1'b1; if(smi_en) begin smi_done <= 1'b0; cmd_err <= 0; {st_op, phy_reg, phy_reg_val_w} <= smi_data; ctrl_st <= 1; end end 1: begin if(st_op[3:2] == SMI_ST) //SFD begin mdio_en <= 1'b1; ctrl_st <= 2; end else begin cmd_err <= 1; ctrl_st <= 0; end end 2: begin mdio_en <= 1'b0; if(~mdio_end) ctrl_st <= 3; end 3: begin if(mdio_end) begin smi_done <= 1'b1; ctrl_st <= 0; end end default: ctrl_st <= 0; endcase wire [15:0] phy_reg_val_r; wire val_r_rdy; //------------------------------------------------------------ mdio_phy mdio_phy_inst ( .sys_clk (sys_clk), .mdio_en (mdio_en), .st_op (st_op), .phy_addr (phy_addr), .phy_reg (phy_reg), .phy_reg_val_w (phy_reg_val_w), .phy_reg_val_r (phy_reg_val_r), .val_r_rdy (val_r_rdy), .us_sec (us_sec), .e_mdc (e_mdc), .e_mdio_i (e_mdio_i), .e_mdio_o (e_mdio_o), .e_mdio_t (e_mdio_t), .mdio_end (mdio_end), .mdio_err (phy_err), .reset (reset) ); //------------------------------------------------------------ always @ (posedge sys_clk) if(val_r_rdy) smi_dout <= phy_reg_val_r; //------------------------------------------------------------ assign smi_err = phy_err | cmd_err; assign test_pin = {mdio_end, smi_done}; endmodule |
mdio_core.v 模块主要是控制接口中的数据信息,转换为MDIO PHY层所需要的数据。 本模块主要是和软件或者FPGA上层做命令层面的交互。同时,和下方的MDIO PHY信号握手,通过这个模块,隔离了命令层和PHY层的数据交互。
- mdio_app.v 代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
`timescale 1ns / 1ps module mdio_app # ( parameter MAIN_CLK = 125, parameter PHY_SPEED = "AUTO" // "AUTO", "1G", "100M", "10M" ) ( input sys_clk, output e_mdc, input e_mdio_i, output e_mdio_o, output e_mdio_t, output smi_err, output reg mdio_done = 0, input reset ); //------------------------------------------------------------ // generate 1ms 1 clock pulse localparam MS_CNT = (MAIN_CLK == 125) ? 125_000 - 1 : (MAIN_CLK == 100) ? 100_000 - 1 : (MAIN_CLK == 50) ? 50_000 - 1 : 50_000 - 1; reg [23:0] ms_cnt_r = 0; wire ms_sec = (ms_cnt_r == MS_CNT) ? 1'b1 : 1'b0; always @ (posedge sys_clk) if(ms_sec) ms_cnt_r <= 0; else ms_cnt_r <= ms_cnt_r + 1; // generate 16ms 1 clock pulse reg [3:0] ms_cnt = 0; always @ (posedge sys_clk) if(ms_sec) ms_cnt <= ms_cnt + 1; reg [1:0] ms_r = 0; always @ (posedge sys_clk) ms_r <= {ms_r[0], &ms_cnt}; wire ms16_flag = (ms_r == 2'b01) ? 1'b1 : 1'b0; //------------------------------------------------------------ localparam SMI_ST = 2'b01; localparam SMI_IDLE = 2'b11; localparam WR = 2'b01; localparam RD = 2'b10; localparam NO = 2'b00; //------------------------------------------------------------ reg [24:0] init_reg [9:0]; initial begin // {st_op, phy_reg, reg_val}; if(PHY_SPEED == "10M") init_reg [0] = { SMI_ST,WR,5'h00, 16'h0100}; // 16'b0010_0001_0000_0000 else if(PHY_SPEED == "100M") init_reg [0] = { SMI_ST,WR,5'h00, 16'h2100}; // 16'b0010_0001_0000_0000 else init_reg [0] = { SMI_ST,WR,5'b00, 16'h1140}; // 16'b0001_0001_0100_0000 // disable EEE init_reg [1] = { SMI_ST,WR,5'h0d, 16'h0007}; // 16'b0000_0000_0000_0000 init_reg [2] = { SMI_ST,WR,5'h0e, 16'h003c}; // 16'b0000_0000_0000_0000 init_reg [3] = { SMI_ST,WR,5'h0d, 16'h4007}; // 16'b0000_0000_0000_0000 init_reg [4] = { SMI_ST,WR,5'h0e, 16'h0000}; // 16'b0000_0000_0000_0000 if(PHY_SPEED == "1G") init_reg [5] = { SMI_ST,WR,5'h04, 16'h0C01}; // 16'b0000_1100_0000_0001 ; 1G else init_reg [5] = { SMI_ST,WR,5'h04, 16'h0d41}; // 16'b0000_1101_0100_0001 ; 1G/100M/10M if(PHY_SPEED == "10M") init_reg [6] = { SMI_ST,WR,5'h00, 16'h0100}; // 16'b0001_0011_0100_0000 else if(PHY_SPEED == "100M") init_reg [6] = { SMI_ST,WR,5'h00, 16'h2100}; // 16'b0001_0011_0100_0000 else init_reg [6] = { SMI_ST,WR,5'h00, 16'h1340}; // 16'b0001_0011_0100_0000 init_reg [7] = {SMI_ST,RD,5'h02, 16'h0000}; // 16'b0000_0000_0000_0000 init_reg [8] = {SMI_ST,RD,5'h03, 16'h0000}; // 16'b0000_0000_0000_0000 init_reg [9] = {SMI_IDLE,NO,5'h00, 16'h0000}; // 16'b0000_0000_0000_0000 end //------------------------------------------------------------ wire smi_done; wire [15:0] smi_dout; reg [4:0] phy_addr = 5'b0_0001; // phy chip address reg [3:0] smi_id = 0; reg [24:0] smi_data = 0; reg smi_en = 0; reg [2:0] app_st = 0; always @ (posedge sys_clk or posedge reset) if(reset) begin mdio_done <= 0; smi_en <= 1'b0; smi_id <= 0; app_st <= 0; end else case(app_st) 0: begin smi_en <= 1'b0; if(smi_done) app_st <= 1; end 1: begin phy_addr = 5'b0_0001; // phy chip address smi_data <= init_reg[smi_id]; //{st_op, phy_reg, phy_reg_val_w} <= smi_data; app_st <= 2; end 2: begin if(smi_data[24:23] == SMI_ST) begin smi_en <= 1'b1; app_st <= 3; end else app_st <= 7; end 3: begin smi_en <= 1'b0; if(~smi_done) app_st <= 4; end 4: begin if(smi_done) begin if(~smi_err) smi_id <= smi_id + 1'b1; app_st <= 5; end end 5: begin if(ms16_flag) app_st <= 6; end 6: begin if(ms16_flag) app_st <= 0; end 7: begin app_st <= 7; // initial phy register end mdio_done <= 1; end default: app_st <= 0; endcase //------------------------------------------------------------ mdio_core # ( .MAIN_CLK (MAIN_CLK), .PHY_SPEED (PHY_SPEED) ) mdio_core_inst ( .sys_clk (sys_clk), .phy_addr (phy_addr), .smi_data (smi_data), .smi_en (smi_en), .e_mdc (e_mdc), .e_mdio_i (e_mdio_i), .e_mdio_o (e_mdio_o), .e_mdio_t (e_mdio_t), .smi_dout (smi_dout), .smi_done (smi_done), .smi_err (smi_err), .reset (reset) ); endmodule |
4.总结
根据MDIO帧格式,MAC层向PHY层发送数据或者从PHY芯片中接收数据。同时这个代码具有可封装性,以使用于FPGA独立使用、CPU 接口 或者ASIC 时封装IP 使用。
注:具体Verilog 代码流程讲解,请参考相关视频。