【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

原创博客,转载请注明出处:【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片 - 没落骑士 - 博客园

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片https://www.cnblogs.com/moluoqishi/p/9118283.html

一、前言  

  本文设计思想采用明德扬至简设计法。以太网这一高效实用的数据传输方式应用于各个领域,如网络交换设备,高速网络相机等。虽然各FPGA厂商都提供MAC IP核,但大多收费,有时无法破解。不同厂家之间无法移植,而且为了通用性考虑牺牲了效率,因此自己动手写一个以太网MAC是个不错的选择。

  本博文讨论通过MDIO接口管理PHY芯片来验证其正确工作,为在此基础上设计MAC逻辑开个头。PHY芯片采用RTL8211EGVB,选用GMII接口与MAC连接。下面我们来开始第一步,在此之前明确设计目的:检测PHY芯片是否完成自动协商 链路速率是否达到1000M。所以要从datasheet中了解到芯片引脚 寄存器地址 接口时序。

二、设计分析

  管理帧格式如下:

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  读写操作时序:

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  MDC为MAC驱动时钟信号,MDIO是串行数据总线,需要连接上拉电阻保证idle状态下高电平。其中前导码包含32个比特“1”,PHY地址根据芯片引脚连接而定,此处为01.turn around域是为了防止读操作时数据冲突,在读操作过程中MAC和PHY均在第1比特处进入高阻态,PHY在第2比特处驱动MDIO接口为低电平以占据总线控制权。注意两点:第一如果时钟信号在读写操作后停止,时钟必须保证至少7个时钟周期持续翻转且MDIO高电平从而保证之前的操作完成。故在设计中可以等待一段时间后再拉低时钟使能信号。第二两个操作之间至少一个idle比特。

  正确驱动接口时序需要关注AC characterisics.

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  很明显MAC驱动总线时,在MDC下降沿更新数据。而PHY驱动总线时,MDC上升沿后更新数据。根据datasheet中的timing参数设定MDC时钟周期是800ns,MAC接收PHY数据时下降沿采样。

  接下来关注要访问的内部寄存器地址,首先读取PHY寄存器数据以检测其工作状态,若发现异常再考虑写入数据。这里读取基本模式状态寄存器0X01的bit5,若为1说明自动协商完成。第二个寄存器是PHY特定状态寄存器0X11中的[15:14]和13,分别是当前速率和全/半双工通信模式。若检测到自动协商完成,且工作在1000M全双工模式下,说明工作正确。

三、硬件架构与状态机设计

  所有准备工作完成,现在开始设计。按照“自顶向下”设计原则,规划好整体结构和模块间接口,再设计内部状态机一步步实现逻辑功能。

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  Mdio_ctrl模块负责完成PHY芯片的配置与检测逻辑,Mdio接口模块完成读写操作时序。此处仅通过读操作简单检测PHY状态,暂不进行配置,故两模块工作状态跳转如图所示:

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  剩下的工作就是把两个状态机实现出来,非常简单。有需要的朋友可以参考一下,关于芯片的具体参数详见:Realtek RTL8211E(G)-VB(VL)-CG Datasheet 1.8.上代码!

四、代码编写

MDIO控制模块:

 `timescale 1ns / 1ps

 module mdio_ctrl(
input clk,//100M
input rst_n, input en,
output reg chk_result =,
output reg chk_vld =, input rdy,
output reg rd_en =,
output reg [-:] phy_addr =,
output reg [-:] reg_addr =,
input [-:] rd_data,
input rd_vld
); parameter MS_CYC = 100_000; localparam IDLE = ;
localparam WAIT = ;
localparam RD_PHY = ;
localparam CHECK = ; localparam WAIT_MS = ; localparam BMSR = 'h01,
PHYSR = 'h11; reg [-:] state_c = ,state_n = ;
wire idle2wait,wait2rd_phy,rd_phy2check,check2idle,check2wait;
wire link_up;
reg [-:] rd_memory [:];
reg [ (-):] ms_cnt = ;
wire add_ms_cnt ;
wire end_ms_cnt ;
reg [ (-):] wait_cnt = ;
wire add_wait_cnt ;
wire end_wait_cnt ;
reg [ (-):] rd_cnt = ;
wire add_rd_cnt ;
wire end_rd_cnt ;
reg [ (-):] rdata_cnt = ;
wire add_rdata_cnt ;
wire end_rdata_cnt ;
wire [*-:] registers;
reg rd_finish = ; initial begin
rd_memory[] = ;
rd_memory[] = ;
end always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end always @(*) begin
case(state_c)
IDLE :begin
if(idle2wait)
state_n = WAIT ;
else
state_n = state_c ;
end
WAIT :begin
if(wait2rd_phy)
state_n = RD_PHY ;
else
state_n = state_c ;
end
RD_PHY :begin
if(rd_phy2check)
state_n = CHECK ;
else
state_n = state_c ;
end
CHECK :begin
if(check2idle)
state_n = IDLE ;
else if(check2wait)
state_n = WAIT ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end assign idle2wait = state_c==IDLE && (en);
assign wait2rd_phy = state_c==WAIT && (end_wait_cnt);
assign rd_phy2check = state_c==RD_PHY && (end_rdata_cnt);
assign check2idle = state_c==CHECK && (link_up);
assign check2wait = state_c==CHECK && (!link_up); assign link_up = rd_memory[][] == 'b1 && rd_memory[1][15:13] == 3'b10_1;//auto_nego && gigabit && full_duplex //计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
ms_cnt <= ;
end
else if(add_ms_cnt) begin
if(end_ms_cnt)
ms_cnt <= ;
else
ms_cnt <= ms_cnt+ ;
end
end
assign add_ms_cnt = (state_c == WAIT);
assign end_ms_cnt = add_ms_cnt && ms_cnt == (MS_CYC)- ;//100MHZ时钟100_000 always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
wait_cnt <= ;
end
else if(add_wait_cnt) begin
if(end_wait_cnt)
wait_cnt <= ;
else
wait_cnt <= wait_cnt+ ;
end
end
assign add_wait_cnt = (end_ms_cnt);
assign end_wait_cnt = add_wait_cnt && wait_cnt == (WAIT_MS)- ; always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
rd_cnt <= ;
end
else if(add_rd_cnt) begin
if(end_rd_cnt)
rd_cnt <= ;
else
rd_cnt <= rd_cnt+ ;
end
end
assign add_rd_cnt = (state_c == RD_PHY && rdy && !rd_finish);
assign end_rd_cnt = add_rd_cnt && rd_cnt == ()- ; always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
rd_finish <= ;
end
else if(end_rd_cnt)begin
rd_finish <= 'b1;
end
else if(state_c == CHECK)
rd_finish <= ;
end always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
rdata_cnt <= ;
end
else if(add_rdata_cnt) begin
if(end_rdata_cnt)
rdata_cnt <= ;
else
rdata_cnt <= rdata_cnt+ ;
end
end
assign add_rdata_cnt = (rd_vld);
assign end_rdata_cnt = add_rdata_cnt && rdata_cnt == ()- ; //接口信号逻辑
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
rd_en <= ;
phy_addr <= ;
reg_addr <= ;
end
else if(add_rd_cnt)begin
rd_en <= 'b1;
phy_addr <= 'b00001;
reg_addr <= registers[-*rd_cnt- -:];
end
else begin
rd_en <= ;
phy_addr <= ;
reg_addr <= ;
end
end assign registers = {BMSR,PHYSR};//5'h01,5'h11 always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
rd_memory[] <= ;
rd_memory[] <= ;
end
else if(add_rdata_cnt)begin
rd_memory[rdata_cnt] <= rd_data;
end
end //用户侧输出检测结果
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
chk_vld <= ;
end
else if(state_c == CHECK)begin
chk_vld <= 'b1;
end
else
chk_vld <= ;
end always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
chk_result <= ;
end
else if(check2idle)begin
chk_result <= 'b1;
end
else if(check2wait)
chk_result <= ;
end endmodule

mdio_ctrl

MDIO时序接口模块:

 `timescale 1ns / 1ps

 module mdio_interface#(parameter MDC_CYC = )//ns
(
input clk,//100M时钟
input rst_n, input rd_en,
input [-:] phy_addr,
input [-:] reg_addr,
output reg [-:] rd_data =,
output reg rd_vld =,
output reg rdy =, output reg mdo =,
output reg mdo_en =,
input mdi,
output reg mdc =
); localparam N = MDC_CYC/; localparam IDLE = ;
localparam WRI_COM = ;
localparam RD_DATA = ; localparam PRE = 'hffff_ffff,
START = 'b01,
OP = 'b10,
TA = 'b11; reg [-:] state_c =,state_n =;
wire idle2wri_com,wri_com2rd_data,rd_data2idle;
reg [ (-):] div_cnt = ;
wire add_div_cnt ;
wire end_div_cnt ;
reg [ (-):] bit_cnt = ;
wire add_bit_cnt ;
wire end_bit_cnt ;
reg [-:] M =;
wire [-:] command;
reg rd_flag = ;
reg [-:] phy_addr_tmp = ;
reg [-:] reg_addr_tmp = ; //寄存地址
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
phy_addr_tmp <= ;
reg_addr_tmp <= ;
end
else if(rd_en)begin
phy_addr_tmp <= phy_addr;
reg_addr_tmp <= reg_addr;
end
end always@(*)begin
if(state_c == IDLE && !rd_en && !rd_flag)
rdy <= ;
else
rdy <= ;
end always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end always @(*) begin
case(state_c)
IDLE :begin
if(idle2wri_com)
state_n = WRI_COM ;
else
state_n = state_c ;
end
WRI_COM :begin
if(wri_com2rd_data)
state_n = RD_DATA ;
else
state_n = state_c ;
end
RD_DATA :begin
if(rd_data2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end assign idle2wri_com = state_c==IDLE && end_div_cnt && (rd_flag || rd_en);
assign wri_com2rd_data = state_c==WRI_COM && end_bit_cnt;
assign rd_data2idle = state_c==RD_DATA && end_bit_cnt; always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
rd_flag <= () ;
end
else if(state_c == IDLE && rd_en)begin
rd_flag <= ('b1) ;
end
else if(state_c == WRI_COM)
rd_flag <= ;
end //分频计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
div_cnt <= ;
end
else if(add_div_cnt) begin
if(end_div_cnt)
div_cnt <= ;
else
div_cnt <= div_cnt+ ;
end
end
assign add_div_cnt = ();
assign end_div_cnt = add_div_cnt && div_cnt == (N)- ; //比特计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
bit_cnt <= ;
end
else if(add_bit_cnt) begin
if(end_bit_cnt)
bit_cnt <= ;
else
bit_cnt <= bit_cnt+ ;
end
end
assign add_bit_cnt = (end_div_cnt && state_c != IDLE);
assign end_bit_cnt = add_bit_cnt && bit_cnt == (M)- ; always@(*)begin
case(state_c)
WRI_COM:M = ;
RD_DATA:M = ;
default:M = ;
endcase
end //mdc时钟
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
mdc <= ('b1) ;
end
else if(add_div_cnt && div_cnt == (N>>) - )begin
mdc <= ('b1) ;
end
else if(end_div_cnt)
mdc <= ;
end //mdio输出
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
mdo <= ('b1) ;
end
else if(add_bit_cnt && state_c == WRI_COM)begin
mdo <= command[--bit_cnt] ;
end
else if(state_c != WRI_COM)
mdo <= 'b1;
end assign command = {PRE,START,OP,phy_addr_tmp,reg_addr_tmp,TA}; always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
mdo_en <= () ;
end
else if(state_c == WRI_COM && add_bit_cnt)
case(bit_cnt)
: mdo_en <= 'b1;
:mdo_en <= ;
default:;
endcase
end //mdio输入
always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
rd_data <= () ;
end
else if(add_bit_cnt && state_c == RD_DATA)begin
rd_data[--bit_cnt] <= (mdi) ;
end
end always @(posedge clk or negedge rst_n )begin
if(rst_n==) begin
rd_vld <= () ;
end
else if(rd_data2idle)begin
rd_vld <= ('b1) ;
end
else
rd_vld <= ;
end endmodule

mdio_interface

顶层封装:

 `timescale 1ns / 1ps

 module phy_manage(
input clk,
input rst_n, input mdio_en,
output link_up,
output chk_done, output mdc,
inout mdio
); wire rdy;
wire rd_en;
wire [-:] phy_addr;
wire [-:] reg_addr;
(*DONT_TOUCH = "TRUE"*)wire [-:] rd_data;
wire rd_vld;
wire mdo_en,mdo,mdi; mdio_ctrl mdio_ctrl(
.clk (clk) ,//100M
.rst_n (rst_n) , .en (mdio_en) ,
.chk_result(link_up) ,
.chk_vld (chk_done) , .rdy (rdy) ,
.rd_en (rd_en) ,
.phy_addr (phy_addr) ,
.reg_addr (reg_addr) ,
.rd_data (rd_data) ,
.rd_vld (rd_vld)
); mdio_interface#(.MDC_CYC())//ns
mdio_interface
(
.clk (clk) ,//100M时钟
.rst_n (rst_n) , .rd_en (rd_en) ,
.phy_addr (phy_addr) ,
.reg_addr (reg_addr) ,
.rd_data (rd_data) ,
.rd_vld (rd_vld) ,
.rdy (rdy) , .mdo (mdo) ,
.mdo_en (mdo_en) ,
.mdi (mdi) ,
.mdc (mdc)
); //三态门
assign mdio = mdo_en ? mdo : 'bz;
assign mdi = mdio; endmodule

phy_manage

五、功能仿真

之后编写testbench进行行为仿真:

 `timescale  ns/ ps

 `define BIT_CNT uut.mdio_interface.bit_cnt

 module phy_manage_tb();

 //时钟和复位
reg clk ;
reg rst_n; //uut的输入信号
reg mdio_en; //uut的输出信号
wire link_up;
wire chk_done;
wire mdc;
wire mdio;
wire [-:] back_data1,back_data2; //时钟周期,单位为ns,可在此修改时钟周期。
parameter CYCLE = ; //复位时间,此时表示复位3个时钟周期的时间。
parameter RST_TIME = ; defparam uut.mdio_ctrl.MS_CYC = ; //待测试的模块例化
phy_manage uut(
.clk (clk) ,
.rst_n (rst_n) , .mdio_en (mdio_en) ,
.link_up (link_up) ,
.chk_done (chk_done) , .mdc (mdc) ,
.mdio (mdio)
); //生成本地时钟50M
initial begin
clk = ;
forever
#(CYCLE/)
clk=~clk;
end //产生复位信号
initial begin
rst_n = ;
#;
rst_n = ;
#(CYCLE*RST_TIME);
rst_n = ;
end //输入信号din0赋值方式
initial begin
#;
//赋初值
mdio_en = ;
#(*CYCLE);
mdio_en = ;
#(*CYCLE);
mdio_en = ;
//开始赋值
#100_000;
$stop;
end //模拟PHY响应 //data
assign back_data1 = {'b0000_0000_0010_0000};
assign back_data2 = {'b1010_0000_0000_0000}; integer i = ,j = ;
initial begin
forever begin
wait(uut.mdio_interface.state_c == && `BIT_CNT == );
@(posedge mdc);
force mdio = ;
@(posedge mdc);
j = j+;
if(j == )
force mdio = back_data1[--i+];
else
force mdio = back_data2[--i+]; wait(uut.mdio_interface.state_c == );
@(posedge mdc);
release mdio;
end
end initial begin
forever begin
@(posedge mdc);
if(uut.mdio_interface.state_c == )begin
#;
i = i+;
end
else
i = ;
end
end endmodule

phy_manage_tb

  testbench中利用force强迫更新mdio双向端口方式模拟PHY芯片响应。仿真波形上半部分为MDIO控制模块信号,下半部分则是MDIO时序接口模块信号。可见当读取寄存器数值满足PHY工作需求时,link_up信号拉高,证明此时MAC可以传输数据给PHY。【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

六、板级调试

  完整的设计,板级调试是必不可少的。真正地将接口调通,PHY芯片正确响应才能说明达到设计目的。顶层封装测试工程,内部例化:差分时钟缓冲原语、PLL、PHY管理顶层封装以及VIO ILA调试IP。我们来看下原理图顶层:

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

测试工程顶层:

 `timescale 1ns / 1ps

 module mdio_test(
input sys_clk_p,
input sys_clk_n,
input rst_n, output mdc,
inout mdio, output phy_reset//PHY芯片复位信号 低有效
); wire sys_clk_ibufg;
wire clk;
wire en;
wire chk_done;
wire link_up; assign phy_reset = 'b1;//始终不复位 IBUFGDS #
(
.DIFF_TERM ("FALSE"),
.IBUF_LOW_PWR ("FALSE")
)
u_ibufg_sys_clk
(
.I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接
.IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接
.O (sys_clk_ibufg) //时钟缓冲输出
); clk_wiz_0 u_clk
(
// Clock out ports
.clk_out1(clk), // output clk_out1 100Mhz
// Clock in ports
.clk_in1(sys_clk_ibufg)); // input clk_in1 vio_0 u_vio (
.clk(clk), // input wire clk
.probe_out0(en) // output wire [0 : 0] probe_out0
); phy_manage phy_manage(
.clk (clk) ,
.rst_n (rst_n) , .mdio_en (en) ,
.link_up (link_up) ,
.chk_done (chk_done) , .mdc (mdc) ,
.mdio (mdio)
); endmodule

mdio_test

时钟引脚约束文件:

 create_clock -period 5.000 [get_ports sys_clk_p]
set_property PACKAGE_PIN R4 [get_ports sys_clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports sys_clk_p] set_property PACKAGE_PIN T6 [get_ports rst_n]
set_property IOSTANDARD LVCMOS15 [get_ports rst_n] set_property PACKAGE_PIN W10 [get_ports mdc]
set_property IOSTANDARD LVCMOS33 [get_ports mdc] set_property PACKAGE_PIN V10 [get_ports mdio]
set_property IOSTANDARD LVCMOS33 [get_ports mdio] set_property PACKAGE_PIN L15 [get_ports phy_reset]
set_property IOSTANDARD LVCMOS33 [get_ports phy_reset]

clk_pin 

  有一点相信调试过以太网的人大多都跳过一个坑:没有驱动PHY的复位输入信号。本人也在此处栽过跟头,这里直接连续赋值拉高PHY芯片复位信号。关于板级调试还有个小技巧,根据高亚军老师的书籍得知,将set up debug生成的ILA探针相关约束命令单独放入一个约束文件便于调试IP的管理和修改,debug约束文件就不贴出来了。

  查看debug波形,MDIO时序接口模块在释放MDIO串行总线时,由于存在上拉电阻为高电平,下一个MDC时钟上升沿时刻,PHY拉低MDIO信号响应并得到总线控制权,开始输出数据。

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  得到读取的两个寄存器数据,根据数值分析满足:PHY自动协商完成,且工作在全双工1000Mbps速率下。

【重新发布,代码开源】FPGA设计千兆以太网MAC(1)——通过MDIO接口配置与检测PHY芯片

  最终RJ45接口绿色指示灯常亮,表明自动协商完成,网络连接正确。到此简易的PHY芯片检测管理模块设计完成。

上一篇:Android View 绘制流程(Draw) 完全解析


下一篇:带你读《计算思维导论实验 与习题指导》之一:初识计算思维