在以太网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 代码:
`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 代码:
`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 代码:
`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 代码流程讲解,请参考相关视频。
老师,你的mdio_phy.v的30行,用sys_clk去得到一个us级别的MDC。语法好像有问题。
如果sys_clk是10MHz,假设us_sec是1us。那么在一次us_sec高电平上有5个sys_clk。会导致MDC翻转5次,在一次us_sec低电平上有5个sys_clk。会导致MDC保持5个sys_clk的周期电平。结果就是MDC的5个sys_clk时间是sys_clk波形,紧接着5个sys_clk的时间保持电平不变