在以太网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的时间保持电平不变