以太网常见硬件组成:
fpga首先发送数据到经过udp层、ip层以及mac层的封装
主要有MAC控制器、PHY芯片、网络变压器和RJ45接头组成,有的系统会有DMA控制。一般的系统中CPU和MAC以及DMA控制器都是集成在一块芯片上的,为了节省空间简化设计,很多时候网口的变压器和RJ45的接头集成在一起。
MAC 及 PHY 工作在 OSI 七层模型的数据链路层和物理层。
mii接口(Media Independent Interface):
MII 接口提供了 MAC 与 PHY 之间、PHY 与 STA(Station Management)之间的互联技术。
MII 接口主要包括四个部分。一是从 MAC 层到 PHY 层的发送数据接口,二是从 PHY 层到 MAC 层的接收数据接口,三是从PHY 层到 MAC 层的状态指示信号,四是 MAC 层和 PHY 层之间传送控制和状态信息的 MDIO 接口。
mdio接口
MDIO为双向信号,用来在MAC和PHY芯片之间的传递控制和状态信息。写寄存器MAC驱动,读寄存器时PHY驱动。
数据传输时先传高位(MSB),后传低位(LSB)。输出采用三态电路设计,MDIO需要接1.5K~10K的上拉电阻。通过MDIO线上是否有上拉电阻,来检测MDIO接口是否连接到PHY芯片上。
MDIO前后有两种协议, 包括Clause22 以及之后的Clause45, Clause 45 兼容Clause 22。
MDIO: 是 PHY 和 STA 之间的双向信号。 它用于在 PHY 和 STA 之间传输控制信息和状态。 控制信息由 STA 同步地针对 MDC 驱动并且由 PHY 同步地采样。 状态信息由 PHY 针对 MDC 同步驱动并由 STA 同步采样。
MDC: 由站管理实体向 PHY 提供,作为在 MDIO 信号上传送信息的定时参考。 MDC 是一种非周期性的信号,没有最高或最低时间。 无论 TX_CLK 和 RX_CLK 的标称周期如何,MDC 的最小高低时间应为 160 ns,MDC 的最小周期为 400 ns。
MDIO-Clause22帧格式:
依次为空闲标志、前导标志、开始标志、操作码、PHY地址、寄存器地址,翻转标志位和数据。
空闲标志(idle):无MDIO帧发送时,MDIO接口输出高阻(外部有上拉电阻,总线上看到的是高电平)。
前导标志(PRE):每帧发送前,MAC通过MDIO连续发送32个MDC周期的高电平1,同时通过MDC输出32个时钟周期。前导的作用是为PHY建立同步提供时间。如果STA能够确定PHY可以接收管理帧,可以压缩前导的发送。
开启标志(st):长度2Bits,必须为01,标志该数据帧开始。
操作码(op):长度2Bits,10标志为读操作,01标志为写操作。
PHY地址(phyad):长度5Bits,表示所访问的PHY地址,一个MDIO总线最大支持32个PHY。
寄存器地址(regad):长度5Bits,表示所访问的寄存器的地址,共计32个寄存器。IEEE802.3协议中对前16个寄存器进行了定义,其中比较常用的如下表。其余为PHY芯片自定寄存器。
翻转标志位(ta):长度2Bits,固定为10。该标志位为PHY芯片地址传输和数据传输处理预留处理时间,设置2bit TA的目的就是为了防止MDIO总线上产生竞争。
数据(data):长度16Bits,操作符为读操作时,该数据为对于地址PHY的特定寄存器的数值;操作符为写时,该数据为对该寄存器写入的数值。
帧格式Clause45:
MDIO Clause45为了访问更多的寄存器在Clause22基础上做了一些扩展,修改如下:
1)ST由01修改为00
2)OP进行了重新定义。00:地址帧 01:写 11:读 10:增量读(Post-read-increment-address)
3)PHYAD域修改名称为PRTAD,端口地址但仍代表PHY地址
4)REGAD修改为DEVAD,Clause45将PHY内部子模块的地址进行细分,这些子模块用DEVAD寻址。子模块内部的寄存器则使用地址帧进行寻址。
mdio时序:
当MDIO通信出现问题,可依次检查以下方面:
确保MDC工作在合适的频率,MDC以及MDIO有上拉.
PHYAD(PRTAD)没有搞错。
MMD 没有处于复位状态。
适当调整MDC的相位。
有些MMD要求帧与帧之间一定要用高阻态分隔
以太网芯片YT8511支持两种复位方式,一种是硬件复位,另外一种是软件复位。硬件复位时通过ETH_RST_N引脚实现对PHY芯片的复位,当ETH_RST_N引脚持续10ms的低电平时,即可实现对PHY芯片的复位。软件复位通过向寄存器地址0x00的Bit[15]写入1进行复位,并且在完成复位后,该位会自动清零。YT8511共有22位寄存器,有些寄存器是仅可读的,有些寄存器是仅可写的,还有些寄存器是可读可写的,当然在程序设计中我们不需要把这些寄存器全部都用上,一般来说寄存器0x00、0x01、0x1F是3个最常用的寄存器,即基本控制寄存器、基本状态寄存器、PHY控制寄存器。
对基本控制寄存器有个软复位操作,即MAC控制器可以通过MDIO协议向PHY发送软复位请求,在大多数情况下软复位一次后,即可读取到正确的自协商结果。
对基本状态寄存器,用户通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断目前自协商状态,注意到寄存器的第5位为1时,代表自协商完成,为0时代表自协商未完成;寄存器的第5位为1时,代表网线的物理层连接成功,为0时代表连接失败。
对PHY控制寄存器,通过MDIO协议从PHY中读取该寄存器的值,即可从16位的寄存器值中判断自协商后连接速度,注意到寄存器的第6位为1时,代表千兆速度;寄存器的第5位为1时,代表百兆速度;而寄存器的第4位为1时,代表十兆速
首先每隔一段时间通过MDIO接口从PHY内部寄存器中读取基本状态寄存器(BMSR)和特定状态寄存器(PHYSR)的值,从而获取到自协商完成状态、连接状态和连接速度,将网口的连接速度通过LED灯进行指示;当FPGA检测到TPAD触摸按键按下时,开始通过MDIO接口对PHY进行软复位,在软复位完成后,PHY会重新开始自协商,此时LED灯会定时获取当前网口的连接状态以及连接速度。
mdio驱动模块代码:
module mdio_driver
(input clk,input rstn,
input en,input wl_rh,input[4:0]addr,
input [15:0]wr_data,
output reg rd_ack,op_done,dri_clk,eth_mdc,
output reg[15:0]rd_data,inout eth_mdio);
parameter PHY_ADDR = 5'b00001,//PHY地址
CLK_DIV = 6'd10 ; //分频系数
localparam st_idle = 6'b00_0001, //空闲状态
st_pre = 6'b00_0010, //发送PRE(前导码)
st_start = 6'b00_0100, //开始状态,发送ST(开始)+OP(操作码)
st_addr = 6'b00_1000, //写地址,发送PHY地址+寄存器地址
st_wr_data = 6'b01_0000, //TA+写数据
st_rd_data = 6'b10_0000; //TA+读数据
reg[5:0]cstate,nstate,clk_cnt;
reg[6:0]cnt;
reg[15:0]wr_data_d0,rd_data_d0;
reg[1:0]op_code;
reg mdio_dir,mdio_out;
reg[4:0]addr_d0;
wire[5:0]clk_divide;
reg done_flag;
wire mdio_in;
assign mdio_in=eth_mdio,
eth_mdio=mdio_dir?mdio_out:1'bz,
clk_divide = CLK_DIV >> 2;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
dri_clk='d0;
clk_cnt<='d0;
end
else if(clk_cnt==clk_divide-'d1)
begin
clk_cnt<='d0;
dri_clk<=~dri_clk;
end
else
clk_cnt<=clk_cnt+'d1;
end
always @(posedge dri_clk or negedge rstn)
begin
if(!rstn)
eth_mdc<='d1;
else if(cnt[0]=='d0)
eth_mdc<='d1;
else
eth_mdc<='d0;
end
always @(posedge dri_clk or negedge rstn)
begin
if(!rstn)
cstate<=st_idle;
else
cstate<=nstate;
end
always @(*)
begin
case(cstate)
st_idle:
if(en)
nstate=st_pre;
else
nstate=st_idle;
st_pre:
if(done_flag=='d1)
nstate=st_start;
else
nstate=st_pre ;
st_start:
if(done_flag=='d1)
nstate=st_addr;
else
nstate=st_start;
st_addr:
if(done_flag=='d1)
begin
if(op_code==2'b01)
nstate=st_wr_data;
else
nstate=st_rd_data;
end
else
nstate=st_addr ;
st_wr_data:
if(done_flag=='d1)
nstate=st_idle;
else
nstate=st_wr_data;
st_rd_data:
if(done_flag=='d1)
nstate=st_idle;
else
nstate=st_rd_data ;
default :
nstate=st_idle;
endcase
end
always @(posedge dri_clk or negedge rstn)
begin
if(!rstn)
begin
cnt<='d0;
op_code<='d0;
mdio_dir<='d0;
mdio_out<='d1;
addr_d0<='d0;
rd_data<='d0;
wr_data_d0<='d0;
rd_data_d0<='d0;
rd_ack<='d1;
op_done<='d0;
done_flag<='d0;
end
else
begin
done_flag<='d0;
cnt<=cnt+'d1;
case(cstate)
st_idle:
begin
mdio_out<='d1;
mdio_dir<='d0;
op_done<='d0;
cnt<='d0;
if(en)
begin
op_code<={wl_rh,~wl_rh};
addr_d0<=addr;
wr_data_d0<=wr_data;
rd_ack<='d1;
end
end
st_pre:
begin
mdio_dir<='d1;
mdio_out<='d1;
if(cnt=='d62)
done_flag<='d1;
else if(cnt=='d63)
cnt<='d0;
end
st_start:
begin
case(cnt)
7'd1:
mdio_out<='d0;
7'd3:
mdio_out<='d1;
7'd5:
mdio_out<=op_code[1];
7'd6:
done_flag<='d1;
7'd7:
begin
mdio_out<=op_code[0];
cnt<='d0;
end
default:
;
endcase
end
st_addr:
begin
case(cnt)
7'd1:
mdio_out<=PHY_ADDR[4];
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_d0[4];
7'd13:
mdio_out<=addr_d0[3];
7'd15:
mdio_out<=addr_d0[2];
7'd17:
mdio_out<=addr_d0[1];
7'd18:
done_flag<='d1;
7'd19:
begin
mdio_out<=addr_d0[0];
cnt<='d0;
end
default:
;
endcase
end
st_wr_data:
begin
case(cnt)
7'd1:
mdio_out<='d1;
7'd3:
mdio_out<='d0;//op_code 10
7'd5:
mdio_out<=wr_data_d0[15];
7'd7:
mdio_out<=wr_data_d0[14];
7'd9:
mdio_out<=wr_data_d0[13];
7'd11:
mdio_out<=wr_data_d0[12];
7'd13:
mdio_out<=wr_data_d0[11];
7'd15:
mdio_out<=wr_data_d0[10];
7'd17:
mdio_out<=wr_data_d0[9];
7'd19:
mdio_out<=wr_data_d0[8];
7'd21:
mdio_out<=wr_data_d0[7];
7'd23:
mdio_out<=wr_data_d0[6];
7'd25:
mdio_out<=wr_data_d0[5];
7'd27:
mdio_out<=wr_data_d0[4];
7'd29:
mdio_out<=wr_data_d0[3];
7'd31:
mdio_out<=wr_data_d0[2];
7'd33:
mdio_out<=wr_data_d0[1];
7'd35:
mdio_out<=wr_data_d0[0];
7'd37:
begin
mdio_dir<='d0;
mdio_out<='d1;
end
7'd39:
done_flag<='d1;
7'd40:
begin
cnt<='d0;
op_done<='d1;
end
default:
;
endcase
end
st_rd_data:
begin
case(cnt)
7'd1:
begin
mdio_dir<='d0;
mdio_out<='d1;//op_code 01
end
7'd4:
rd_ack<=mdio_in;
7'd6:
rd_data_d0[15]<=mdio_in;
7'd8:
rd_data_d0[14]<=mdio_in;
7'd10:
rd_data_d0[13]<=mdio_in;
7'd12:
rd_data_d0[12]<=mdio_in;
7'd14:
rd_data_d0[11]<=mdio_in;
7'd16:
rd_data_d0[10]<=mdio_in;
7'd18:
rd_data_d0[9]<=mdio_in;
7'd20:
rd_data_d0[8]<=mdio_in;
7'd22:
rd_data_d0[7]<=mdio_in;
7'd24:
rd_data_d0[6]<=mdio_in;
7'd26:
rd_data_d0[5]<=mdio_in;
7'd28:
rd_data_d0[4]<=mdio_in;
7'd30:
rd_data_d0[3]<=mdio_in;
7'd32:
rd_data_d0[2]<=mdio_in;
7'd34:
rd_data_d0[1]<=mdio_in;
7'd36:
rd_data_d0[0]<=mdio_in;
7'd39:
done_flag<='d1;
7'd40:
begin
op_done<='d1;
rd_data<=rd_data_d0;
rd_data_d0<='d0;
cnt<='d0;
end
default:
;
endcase
end
default:
;
endcase
end
end
endmodule
在st_idle状态中,如果en信号为高(或者说en = 1),则状态会转换到st_pre。否则,状态保持为st_idle。
在st_pre状态中,如果done_flag信号为’d1’(可能表示某个特定操作完成),则状态会转换到st_start。否则,状态保持为st_pre。
类似地,在接下来的每个状态中,都有特定的条件用于转换到下一个状态或保持当前状态。
在所有分支之后,有一个默认分支,如果cstate的值没有匹配任何已知的状态,那么状态将转换到st_idle。
于eth_mdc需要在输入时钟的基础上进行分频,为了方便操作,这里先对输入的时钟进行分频,得到一个dri_clk时钟,作为MDIO驱动模块和MDIO控制模块的操作时钟。eth_mdc在dri_clk的基础上进行2分频,由于输入的参数CLK_DIV为eth_mdc相对于输入时钟的分频系数,因此为了得到dri_clk的分频系数,需要将CLK_DIV除以2,如代码中第72行所示。
根据分频系数clk_divide,得到dri_clk的时钟。当cnt一直累加时,eth_mdc的时钟相当于对dri_clk进行2分频。当开始对MDIO接口进行读写操作时,cnt累加,此时才会产生eth_mdc时钟;当读写操作结束后eth_mdc将一直处于高电平。
在st_wr_data状态下,数据是在eth_mdc的下降沿写入,而在st_rd_data状态,数据在erth_mdc的上升沿读出。值得一提是,在st_rd_data状态下,程序中根据TA的第二位,判断PHY芯片有没有应答,如果没有应答,则说明读取数据失败。
mdio控制模块代码:
module ctrl_mdio(
input clk,input rstn,
input soft_trig,op_done,
input[15:0]rd_data,input rd_ack,
output reg[4:0]addr,
output reg en,wl_rh,wr_data,output [1:0]led);
reg rst_trig_d0,rst_trig_d1,rst_trig_flag;
reg time_done,st_next,rd_next,error;
reg[1:0]speed_status;
reg[2:0]flow_cnt;
reg[23:0]time_cnt;
wire rst_trig_p;
assign rst_trig_p=~rst_trig_d1&rst_trig_d0,
led=error?2'd0:speed_status;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
rst_trig_d0<='d0;
rst_trig_d1<='d0;
end
else
begin
rst_trig_d0<=soft_trig;
rst_trig_d1<=rst_trig_d0;
end
end
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
time_cnt<='d0;
time_done<='d0;
end
else
begin
if(time_cnt=='d1_000_000-'d1)
begin
time_done<='d1;
time_cnt<='d0;
end
else
begin
time_cnt<=time_cnt+'d1;
time_done<='d0;
end
end
end
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
flow_cnt<='d0;
rst_trig_flag<='d0;
speed_status<='d0;
en<='d0;
wl_rh<='d0;
addr<='d0;
wr_data<='d0;
st_next<='d0;
rd_next<='d0;
error<='d0;
end
else
begin
en<='d0;
if(rst_trig_p=='d1)
rst_trig_flag<='d1;
case(flow_cnt)
3'd0:
begin
if(rst_trig_flag=='d1)
begin
en<='d1;
wl_rh<='d0;
addr<=5'h0;
wr_data<=16'h9140;
flow_cnt<=3'd1;
end
else if(time_done)
begin
en<='d1;
wl_rh<='d1;
addr<=5'd1;
flow_cnt<=3'd2;
end
else if(st_next)
begin
en<='d1;
wl_rh<='d1;
addr<=5'h11;
flow_cnt<=3'd2;
st_next<='d0;
rd_next<='d1;
end
else
;
end
3'd1:
if(op_done)
begin
flow_cnt<=3'd0;
rst_trig_flag<='d0;
end
else
;
3'd2:
if(op_done)
begin
if(rd_ack=='d0&&rd_next=='d0)
flow_cnt<=3'd3;
else if(rd_ack=='d0&&rd_next=='d1)
begin
rd_next<='d0;
flow_cnt<=3'd4;
end
else
flow_cnt<=3'd0;
end
3'd3:
begin
flow_cnt<=3'd0;
if(rd_data[5]==1&&rd_data[2]==1)
begin
st_next<='d1;
error<='d0;
end
else
error<='d1;
end
3'd4:
begin
flow_cnt<=3'd0;
case(rd_data[15:14])
2'd2:
speed_status<=2'b11;
2'd1:
speed_status<=2'b10;
2'd0:
speed_status<=2'b01;
default :
speed_status<='d0;
endcase
end
default:
;
endcase
end
end
endmodule
根据软复位信号对MDIO接口进行软复位,并定时读取以太网的连接状态。根据状态寄存器的值,为连接速率状态位speed_status赋值。
顶层模块:
module mdio_rw_top(
input clk ,
input rstn,
//MDIO接口
output eth_mdc , //PHY管理接口的时钟信号
inout eth_mdio , //PHY管理接口的双向数据信号
output eth_rstn, //以太网复位信号
input touch_key, //触摸按键
output [1:0] led //LED连接速率指示
);
//wire define
wire en ; //触发开始信号
wire wl_rh ; //低电平写,高电平读
wire [4:0] addr ; //寄存器地址
wire [15:0] wr_data ; //写入寄存器的数据
wire op_done ; //读写完成
wire [15:0] rd_data ; //读出的数据
wire rd_ack ; //读应答信号 0:应答 1:未应答
wire dri_clk ; //驱动时钟
//硬件复位
assign eth_rstn = rstn;
//MDIO接口驱动
mdio_driver #(
.PHY_ADDR (5'h04), //PHY地址 3'b100
.CLK_DIV (6'd10) //分频系数
)
u_mdio_dri(
.clk (clk),
.rstn (rstn),
.en (en),
.wl_rh (wl_rh ),
.addr (addr ),
.wr_data (wr_data),
.op_done (op_done ),
.rd_data (rd_data),
.rd_ack (rd_ack ),
.dri_clk (dri_clk ),
.eth_mdc (eth_mdc ),
.eth_mdio (eth_mdio )
);
//MDIO接口读写控制
ctrl_mdio u_mdio_ctrl(
.clk (dri_clk),
.rstn (rstn ),
.soft_trig (touch_key ),
.op_done (op_done ),
.rd_data (rd_data),
.rd_ack (rd_ack ),
.en (en ),
.wl_rh (wl_rh ),
.addr (addr ),
.wr_data (wr_data),
.led (led )
);
endmodule
上板运行:
QQ视频20231104163807