MAC与PHY之间的通信通过了MDIO的接口
与PHY芯片的通信则通过MAC(Media Access Control)控制器来实现。MAC控制器与PHY芯片之间的通信通常包括两部分:
1.数据传输接口
:比如MII(Media Independent Interface)、RMII(Reduced Media Independent Interface)、GMII(Gigabit Media Independent Interface)等,用于传输实际的数据包。
2.管理接口
:MDIO接口和MDC(Management Data Clock)用于对PHY芯片进行配置、状态监控和管理。通过MDIO接口,MAC控制器可以访问PHY芯片的寄存器,读取和修改PHY的工作模式、状态等信息。
MDIO接口的作用
MDIO接口是标准的IEEE 802.3规范定义的,用于在MAC和PHY之间进行管理和配置的低速控制通道。其主要功能包括:
Preamble:32 位前导码,由 MAC 端发送 32 位逻辑“1”,用于同步 PHY 芯片。
ST(Start of Frame):2 位帧开始信号,用 01 表示。
OP(Operation Code):2 位操作码,读:10 写:01。
PHYAD(PHY Address):5 位 PHY 地址,用于表示与哪个 PHY 芯片通信,因此一个 MAC 上可以连
接多个 PHY 芯片。
REGAD(Register Address):5 位寄存器地址,可以表示 32 个寄存器。
TA(Turnaround):2 位转向,在读命令中,MDIO 在此时由 MAC 驱动改为 PHY 驱动,在第一个 TA
位,MDIO 引脚为高阻状态,第二个 TA 位,PHY 将 MDIO 引脚拉低,准备发送数据;在写命令中,不需
要 MDIO 方向发生变化,MAC 固定输出 2’b10,随后开始写入数据。
DATA:16 位数据,在读命令中,PHY 芯片将对应的 PHYAD 的 REGAD 寄存器的数据写到 DATA 中;
在写命令中,PHY 芯片将接收到的 DATA 写入 REGAD 寄存器中。需要注意的是,在 DATA 传输的过程中,
高位在前,低位在后。
IDLE:空闲状态,此时 MDIO 为无源驱动,处于高阻状态,但一般用上拉电阻使其上拉至高电平。
其实不过是读还是写,都是在时钟的上升沿完成采样,在下降沿完成对数据的更新。只是不同的是,读数据需要将后面的控制权切换给PHY芯片,而写是完整的MAC控制。
module mdio_rw_test(
input sys_clk ,
input sys_rst_n,
//MDIO接口
output eth_mdc , //PHY管理接口的时钟信号
inout eth_mdio , //PHY管理接口的双向数据信号
output eth_rst_n, //以太网复位信号
input touch_key, //触摸按键
output [1:0] led //LED连接速率指示
);
//wire define
wire op_exec ; //触发开始信号
wire op_rh_wl ; //低电平写,高电平读
wire [4:0] op_addr ; //寄存器地址
wire [15:0] op_wr_data ; //写入寄存器的数据
wire op_done ; //读写完成
wire [15:0] op_rd_data ; //读出的数据
wire op_rd_ack ; //读应答信号 0:应答 1:未应答
wire dri_clk ; //驱动时钟
//硬件复位
assign eth_rst_n = sys_rst_n;
//MDIO接口驱动
mdio_dri #(
.PHY_ADDR (5'h04), //PHY地址 3'b100
.CLK_DIV (6'd10) //分频系数
)
u_mdio_dri(
.clk (sys_clk),
.rst_n (sys_rst_n),
.op_exec (op_exec ),
.op_rh_wl (op_rh_wl ),
.op_addr (op_addr ),
.op_wr_data (op_wr_data),
.op_done (op_done ),
.op_rd_data (op_rd_data),
.op_rd_ack (op_rd_ack ),
.dri_clk (dri_clk ),
.eth_mdc (eth_mdc ),
.eth_mdio (eth_mdio )
);
//MDIO接口读写控制
mdio_ctrl u_mdio_ctrl(
.clk (dri_clk),
.rst_n (sys_rst_n ),
.soft_rst_trig (touch_key ),
.op_done (op_done ),
.op_rd_data (op_rd_data),
.op_rd_ack (op_rd_ack ),
.op_exec (op_exec ),
.op_rh_wl (op_rh_wl ),
.op_addr (op_addr ),
.op_wr_data (op_wr_data),
.led (led )
);
endmodule
module mdio_dri #(
parameter PHY_ADDR = 5'b00001,//PHY地址
parameter CLK_DIV = 6'd10 //分频系数
)
(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input op_exec , //触发开始信号
input op_rh_wl , //低电平写,高电平读
input [4:0] op_addr , //寄存器地址
input [15:0] op_wr_data, //写入寄存器的数据
output reg op_done , //读写完成
output reg [15:0] op_rd_data, //读出的数据
output reg op_rd_ack , //读应答信号 0:应答 1:未应答
output reg dri_clk , //驱动时钟
output reg eth_mdc , //PHY管理接口的时钟信号
inout eth_mdio //PHY管理接口的双向数据信号
);
//parameter define
localparam st_idle = 6'b00_0001; //空闲状态
localparam st_pre = 6'b00_0010; //发送PRE(前导码)
localparam st_start = 6'b00_0100; //开始状态,发送ST(开始)+OP(操作码)
localparam st_addr = 6'b00_1000; //写地址,发送PHY地址+寄存器地址
localparam st_wr_data = 6'b01_0000; //TA+写数据
localparam st_rd_data = 6'b10_0000; //TA+读数据
//reg define
reg [5:0] cur_state ;
reg [5:0] next_state;
reg [5:0] clk_cnt ; //分频计数
reg [15:0] wr_data_t ; //缓存写寄存器的数据
reg [4:0] addr_t ; //缓存寄存器地址
reg [6:0] cnt ; //计数器
reg st_done ; //状态开始跳转信号
reg [1:0] op_code ; //操作码 2'b01(写) 2'b10(读)
reg mdio_dir ; //MDIO数据(SDA)方向控制
reg mdio_out ; //MDIO输出信号
reg [15:0] rd_data_t ; //缓存读寄存器数据
//wire define
wire mdio_in ; //MDIO数据输入
wire [5:0] clk_divide ; //PHY_CLK的分频系数
assign eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
assign mdio_in = eth_mdio; //MDIO数据输入
//将PHY_CLK的分频系数除以2,得到dri_clk的分频系数,方便对MDC和MDIO信号操作
assign clk_divide = CLK_DIV >> 1;
//分频得到dri_clk时钟
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
dri_clk <= 1'b0;
clk_cnt <= 1'b0;
end
else if(clk_cnt == clk_divide[5:1] - 1'd1)
begin
clk_cnt <= 1'b0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//产生PHY_MDC时钟
always @(posedge dri_clk or negedge rst_n)
begin
if(!rst_n)
eth_mdc <= 1'b1;
else if(cnt[0] == 1'b0)
eth_mdc <= 1'b1;
else
eth_mdc <= 1'b0;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n)
begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @(*)
begin
next_state = st_idle;
case(cur_state)
st_idle :
begin
if(op_exec)
next_state = st_pre;
else
next_state = st_idle;
end
st_pre :
begin
if(st_done)
next_state = st_start;
else
next_state = st_pre;
end
st_start :
begin
if(st_done)
next_state = st_addr;
else
next_state = st_start;
end
st_addr :
begin
if(st_done)
begin
if(op_code == 2'b01) //MDIO接口写操作
next_state = st_wr_data;
else
next_state = st_rd_data; //MDIO接口读操作
end
else
next_state = st_addr;
end
st_wr_data :
begin
if(st_done)
next_state = st_idle;
else
next_state = st_wr_data;
end
st_rd_data :
begin
if(st_done)
next_state = st_idle;
else
next_state = st_rd_data;
end
default :
next_state = st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 5'd0;
op_code <= 1'b0;
addr_t <= 1'b0;
wr_data_t <= 1'b0;
rd_data_t <= 1'b0;
op_done <= 1'b0;
st_done <= 1'b0;
op_rd_data <= 1'b0;
op_rd_ack <= 1'b1;
mdio_dir <= 1'b0;
mdio_out <= 1'b1;
end
else
begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle :
begin
mdio_out <= 1'b1;
mdio_dir <= 1'b0;
op_done <= 1'b0;
cnt <= 7'b0;
if(op_exec)
begin
op_code <= {op_rh_wl,~op_rh_wl}; //OP_CODE: 2'b01(写) 2'b10(读)
addr_t <= op_addr;
wr_data_t <= op_wr_data;
op_rd_ack <= 1'b1;
end
end
st_pre :
begin //发送前导码:32个1bit
mdio_dir <= 1'b1; //切换MDIO引脚方向:输出
mdio_out <= 1'b1; //MDIO引脚输出高电平
if(cnt == 7'd62)
st_done <= 1'b1;
else if(cnt == 7'd63)
cnt <= 7'b0;
end
st_start :
begin
case(cnt)
7'd1 :
mdio_out <= 1'b0; //发送开始信号 2'b01
7'd3 :
mdio_out <= 1'b1;
7'd5 :
mdio_out <= op_code[1]; //发送操作码
7'd6 :
st_done <= 1'b1;
7'd7 :
begin
mdio_out <= op_code[0];
cnt <= 7'b0;
end
default :
;
endcase
end
st_addr :
begin
case(cnt)
7'd1 :
mdio_out <= PHY_ADDR[4]; //发送PHY地址
7'd3 :
mdio_out <= PHY_ADDR[3];
7'd5 :
mdio_out <= PHY_ADDR[2];
7'd7 :
mdio_out <= PHY_ADDR[1];
7'd9 :
mdio_out <= PHY_ADDR[0];
7'd11:
mdio_out <= addr_t[4]; //发送寄存器地址
7'd13:
mdio_out <= addr_t[3];
7'd15:
mdio_out <= addr_t[2];
7'd17:
mdio_out <= addr_t[1];
7'd18:
st_done <= 1'b1;
7'd19:
begin
mdio_out <= addr_t[0];
cnt <= 7'd0;
end
default :
;
endcase
end
st_wr_data :
begin
case(cnt)
7'd1 :
mdio_out <= 1'b1; //发送TA,写操作(2'b10)
7'd3 :
mdio_out <= 1'b0;
7'd5 :
mdio_out <= wr_data_t[15];//发送写寄存器数据
7'd7 :
mdio_out <= wr_data_t[14];
7'd9 :
mdio_out <= wr_data_t[13];
7'd11:
mdio_out <= wr_data_t[12];
7'd13:
mdio_out <= wr_data_t[11];
7'd15:
mdio_out <= wr_data_t[10];
7'd17:
mdio_out <= wr_data_t[9];
7'd19:
mdio_out <= wr_data_t[8];
7'd21:
mdio_out <= wr_data_t[7];
7'd23:
mdio_out <= wr_data_t[6];
7'd25:
mdio_out <= wr_data_t[5];
7'd27:
mdio_out <= wr_data_t[4];
7'd29:
mdio_out <= wr_data_t[3];
7'd31:
mdio_out <= wr_data_t[2];
7'd33:
mdio_out <= wr_data_t[1];
7'd35:
mdio_out <= wr_data_t[0];
7'd37:
begin
mdio_dir <= 1'b0;
mdio_out <= 1'b1;
end
7'd39:
st_done <= 1'b1;
7'd40:
begin
cnt <= 7'b0;
op_done <= 1'b1; //写操作完成,拉高op_done信号
end
default :
;
endcase
end
st_rd_data :
begin
case(cnt)
7'd1 :
begin
mdio_dir <= 1'b0; //MDIO引脚切换至输入状态
mdio_out <= 1'b1;
end
7'd2 :
; //TA[1]位,该位为高阻状态,不操作
7'd4 :
op_rd_ack <= mdio_in; //TA[0]位,0(应答) 1(未应答)
7'd6 :
rd_data_t[15] <= mdio_in; //接收寄存器数据
7'd8 :
rd_data_t[14] <= mdio_in;
7'd10:
rd_data_t[13] <= mdio_in;
7'd12:
rd_data_t[12] <= mdio_in;
7'd14:
rd_data_t[11] <= mdio_in;
7'd16:
rd_data_t[10] <= mdio_in;
7'd18:
rd_data_t[9] <= mdio_in;
7'd20:
rd_data_t[8] <= mdio_in;
7'd22:
rd_data_t[7] <= mdio_in;
7'd24:
rd_data_t[6] <= mdio_in;
7'd26:
rd_data_t[5] <= mdio_in;
7'd28:
rd_data_t[4] <= mdio_in;
7'd30:
rd_data_t[3] <= mdio_in;
7'd32:
rd_data_t[2] <= mdio_in;
7'd34:
rd_data_t[1] <= mdio_in;
7'd36:
rd_data_t[0] <= mdio_in;
7'd39:
st_done <= 1'b1;
7'd40:
begin
op_done <= 1'b1; //读操作完成,拉高op_done信号
op_rd_data <= rd_data_t;
rd_data_t <= 16'd0;
cnt <= 7'd0;
end
default :
;
endcase
end
default :
;
endcase
end
end
endmodule
module mdio_ctrl(
input clk ,
input rst_n ,
input soft_rst_trig , //软复位触发信号
input op_done , //读写完成
input [15:0] op_rd_data , //读出的数据
input op_rd_ack , //读应答信号 0:应答 1:未应答
output reg op_exec , //触发开始信号
output reg op_rh_wl , //低电平写,高电平读
output reg [4:0] op_addr , //寄存器地址
output reg [15:0] op_wr_data , //写入寄存器的数据
output [1:0] led //LED灯指示以太网连接状态
);
//reg define
reg rst_trig_d0;
reg rst_trig_d1;
reg rst_trig_flag; //soft_rst_trig信号触发标志
reg [23:0] timer_cnt; //定时计数器
reg timer_done; //定时完成信号
reg start_next; //开始读下一个寄存器标致
reg read_next; //处于读下一个寄存器的过程
reg link_error; //链路断开或者自协商未完成
reg [2:0] flow_cnt; //流程控制计数器
reg [1:0] speed_status; //连接速率
//wire define
wire pos_rst_trig; //soft_rst_trig信号上升沿
//采soft_rst_trig信号上升沿
assign pos_rst_trig = ~rst_trig_d1 & rst_trig_d0;
//未连接或连接失败时led赋值00
// 01:10Mbps 10:100Mbps 11:1000Mbps 00:其他情况
assign led = link_error ? 2'b00: speed_status;
//对soft_rst_trig信号延时打拍
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rst_trig_d0 <= 1'b0;
rst_trig_d1 <= 1'b0;
end
else
begin
rst_trig_d0 <= soft_rst_trig;
rst_trig_d1 <= rst_trig_d0;
end
end
//定时计数
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
timer_cnt <= 1'b0;
timer_done <= 1'b0;
end
else
begin
if(timer_cnt == 24'd1_000_000 - 1'b1)
begin
timer_done <= 1'b1;
timer_cnt <= 1'b0;
end
else
begin
timer_done <= 1'b0;
timer_cnt <= timer_cnt + 1'b1;
end
end
end
//根据软复位信号对MDIO接口进行软复位,并定时读取以太网的连接状态
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
flow_cnt <= 3'd0;
rst_trig_flag <= 1'b0;
speed_status <= 2'b00;
op_exec <= 1'b0;
op_rh_wl <= 1'b0;
op_addr <= 1'b0;
op_wr_data <= 1'b0;
start_next <= 1'b0;
read_next <= 1'b0;
link_error <= 1'b0;
end
else
begin
op_exec <= 1'b0;
if(pos_rst_trig)
rst_trig_flag <= 1'b1; //拉高软复位触发标志
case(flow_cnt)
2'd0 :
begin
if(rst_trig_flag)
begin //开始对MDIO接口进行软复位
op_exec <= 1'b1;
op_rh_wl <= 1'b0;
op_addr <= 5'h00;
op_wr_data <= 16'h9140; //Bit[15]=1'b1,表示软复位
flow_cnt <= 3'd1;
end
else if(timer_done)
begin //定时完成,获取以太网连接状态
op_exec <= 1'b1;
op_rh_wl <= 1'b1;
op_addr <= 5'h01;
flow_cnt <= 3'd2;
end
else if(start_next)
begin //开始读下一个寄存器,获取以太网通信速度
op_exec <= 1'b1;
op_rh_wl <= 1'b1;
op_addr <= 5'h11;
flow_cnt <= 3'd2;
start_next <= 1'b0;
read_next <= 1'b1;
end
end
2'd1 :
begin
if(op_done)
begin //MDIO接口软复位完成
flow_cnt <= 3'd0;
rst_trig_flag <= 1'b0;
end
end
2'd2 :
begin
if(op_done)
begin //MDIO接口读操作完成
if(op_rd_ack == 1'b0 && read_next == 1'b0) //读第一个寄存器,接口成功应答,
flow_cnt <= 3'd3; //读第下一个寄存器,接口成功应答
else if(op_rd_ack == 1'b0 && read_next == 1'b1)
begin
read_next <= 1'b0;
flow_cnt <= 3'd4;
end
else
begin
flow_cnt <= 3'd0;
end
end
end
2'd3 :
begin
flow_cnt <= 3'd0; //链路正常并且自协商完成
if(op_rd_data[5] == 1'b1 && op_rd_data[2] == 1'b1)
begin
start_next <= 1;
link_error <= 0;
end
else
begin
link_error <= 1'b1;
end
end
3'd4:
begin
flow_cnt <= 3'd0;
if(op_rd_data[15:14] == 2'b10)
speed_status <= 2'b11; //1000Mbps
else if(op_rd_data[15:14] == 2'b01)
speed_status <= 2'b10; //100Mbps
else if(op_rd_data[15:14] == 2'b00)
speed_status <= 2'b01; //10Mbps
else
speed_status <= 2'b00; //其他情况
end
endcase
end
end
endmodule