作者:沒落騎士
一、前言
本文設計思想采用明德揚至簡設計法。以太網這一高效實用的數據傳輸方式應用于各個領域,如網絡交換設備,高速網絡相機等。雖然各FPGA廠商都提供MACIP核,但大多收費,有時無法破解。不同廠家之間無法移植,而且為了通用性考慮犧牲了效率,因此自己動手寫一個以太網MAC是個不錯的選擇。
本博文討論通過MDIO接口管理PHY芯片來驗證其正確工作,為在此基礎上設計MAC邏輯開個頭。PHY芯片采用RTL8211EGVB,選用GMII接口與MAC連接。下面我們來開始第一步,在此之前明確設計目的:檢測PHY芯片是否完成自動協商鏈路速率是否達到1000M。所以要從datasheet中了解到芯片引腳寄存器地址接口時序。
二、設計分析
管理幀格式如下:
讀寫操作時序:
MDC為MAC驅動時鐘信號,MDIO是串行數據總線,需要連接上拉電阻保證idle狀態下高電平。其中前導碼包含32個比特“1”,PHY地址根據芯片引腳連接而定,此處為01.turnaround域是為了防止讀操作時數據沖突,在讀操作過程中MAC和PHY均在第1比特處進入高阻態,PHY在第2比特處驅動MDIO接口為低電平以占據總線控制權。注意兩點:第一如果時鐘信號在讀寫操作后停止,時鐘必須保證至少7個時鐘周期持續翻轉且MDIO高電平從而保證之前的操作完成。故在設計中可以等待一段時間后再拉低時鐘使能信號。第二兩個操作之間至少一個idle比特。
正確驅動接口時序需要關注ACcharacterisics.
很明顯MAC驅動總線時,在MDC下降沿更新數據。而PHY驅動總線時,MDC上升沿后更新數據。根據datasheet中的timing參數設定MDC時鐘周期是800ns,MAC接收PHY數據時下降沿采樣。
接下來關注要訪問的內部寄存器地址,首先讀取PHY寄存器數據以檢測其工作狀態,若發現異常再考慮寫入數據。這里讀取基本模式狀態寄存器0X01的bit5,若為1說明自動協商完成。第二個寄存器是PHY特定狀態寄存器0X11中的[15:14]和13,分別是當前速率和全/半雙工通信模式。若檢測到自動協商完成,且工作在1000M全雙工模式下,說明工作正確。
三、硬件架構與狀態機設計
所有準備工作完成,現在開始設計。按照“自頂向下”設計原則,規劃好整體結構和模塊間接口,再設計內部狀態機一步步實現邏輯功能。
Mdio_ctrl模塊負責完成PHY芯片的配置與檢測邏輯,Mdio接口模塊完成讀寫操作時序。此處僅通過讀操作簡單檢測PHY狀態,暫不進行配置,故兩模塊工作狀態跳轉如圖所示:
剩下的工作就是把兩個狀態機實現出來,非常簡單。有需要的朋友可以參考一下,關于芯片的具體參數詳見:RealtekRTL8211E(G)-VB(VL)-CGDatasheet1.8.上代碼!
四、代碼編寫
MDIO控制模塊:
`timescale1ns/1ps
modulemdio_ctrl(
inputclk,//100M
inputrst_n,
inputen,
outputregchk_result=0,
outputregchk_vld=0,
inputrdy,
outputregrd_en=0,
outputreg[5-1:0]phy_addr=0,
outputreg[5-1:0]reg_addr=0,
input[16-1:0]rd_data,
inputrd_vld
);
parameterMS_CYC=100_000;
localparamIDLE=0;
localparamWAIT=1;
localparamRD_PHY=2;
localparamCHECK=3;
localparamWAIT_MS=10;
localparamBMSR=5'h01,
PHYSR=5'h11;
reg[4-1:0]state_c=0,state_n=0;
wireidle2wait,wait2rd_phy,rd_phy2check,check2idle,check2wait;
wirelink_up;
reg[16-1:0]rd_memory[0:1];
reg[(17-1):0]ms_cnt=0;
wireadd_ms_cnt;
wireend_ms_cnt;
reg[(4-1):0]wait_cnt=0;
wireadd_wait_cnt;
wireend_wait_cnt;
reg[(2-1):0]rd_cnt=0;
wireadd_rd_cnt;
wireend_rd_cnt;
reg[(2-1):0]rdata_cnt=0;
wireadd_rdata_cnt;
wireend_rdata_cnt;
wire[5*2-1:0]registers;
regrd_finish=0;
initialbegin
rd_memory[0]=0;
rd_memory[1]=0;
end
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
state_cend
elsebegin
state_cend
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;
elseif(check2wait)
state_n=WAIT;
else
state_n=state_c;
end
default:state_n=IDLE;
endcase
end
assignidle2wait=state_c==IDLE&&(en);
assignwait2rd_phy=state_c==WAIT&&(end_wait_cnt);
assignrd_phy2check=state_c==RD_PHY&&(end_rdata_cnt);
assigncheck2idle=state_c==CHECK&&(link_up);
assigncheck2wait=state_c==CHECK&&(!link_up);
assignlink_up=rd_memory[0][5]==1'b1&&rd_memory[1][15:13]==3'b10_1;//auto_nego&&gigabit&&full_duplex
//計數器
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
ms_cntend
elseif(add_ms_cnt)begin
if(end_ms_cnt)
ms_cntelse
ms_cntend
end
assignadd_ms_cnt=(state_c==WAIT);
assignend_ms_cnt=add_ms_cnt&&ms_cnt==(MS_CYC)-1;//100MHZ時鐘100_000
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
wait_cntend
elseif(add_wait_cnt)begin
if(end_wait_cnt)
wait_cntelse
wait_cntend
end
assignadd_wait_cnt=(end_ms_cnt);
assignend_wait_cnt=add_wait_cnt&&wait_cnt==(WAIT_MS)-1;
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
rd_cntend
elseif(add_rd_cnt)begin
if(end_rd_cnt)
rd_cntelse
rd_cntend
end
assignadd_rd_cnt=(state_c==RD_PHY&&rdy&&!rd_finish);
assignend_rd_cnt=add_rd_cnt&&rd_cnt==(2)-1;
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
rd_finishend
elseif(end_rd_cnt)begin
rd_finishend
elseif(state_c==CHECK)
rd_finishend
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
rdata_cntend
elseif(add_rdata_cnt)begin
if(end_rdata_cnt)
rdata_cntelse
rdata_cntend
end
assignadd_rdata_cnt=(rd_vld);
assignend_rdata_cnt=add_rdata_cnt&&rdata_cnt==(2)-1;
//接口信號邏輯
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
rd_enphy_addrreg_addrend
elseif(add_rd_cnt)begin
rd_enphy_addrreg_addrend
elsebegin
rd_enphy_addrreg_addrend
end
assignregisters={BMSR,PHYSR};//5'h01,5'h11
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
rd_memory[0]rd_memory[1]end
elseif(add_rdata_cnt)begin
rd_memory[rdata_cnt]end
end
//用戶側輸出檢測結果
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
chk_vldend
elseif(state_c==CHECK)begin
chk_vldend
else
chk_vldend
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
chk_resultend
elseif(check2idle)begin
chk_resultend
elseif(check2wait)
chk_resultend
endmodule
mdio_ctrl
MDIO時序接口模塊:
`timescale1ns/1ps
modulemdio_interface#(parameterMDC_CYC=800)//ns
(
inputclk,//100M時鐘
inputrst_n,
inputrd_en,
input[5-1:0]phy_addr,
input[5-1:0]reg_addr,
outputreg[16-1:0]rd_data=0,
outputregrd_vld=0,
outputregrdy=0,
outputregmdo=1,
outputregmdo_en=0,
inputmdi,
outputregmdc=1
);
localparamN=MDC_CYC/10;
localparamIDLE=0;
localparamWRI_COM=1;
localparamRD_DATA=2;
localparamPRE=32'hffff_ffff,
START=2'b01,
OP=2'b10,
TA=2'b11;
reg[3-1:0]state_c=0,state_n=0;
wireidle2wri_com,wri_com2rd_data,rd_data2idle;
reg[(7-1):0]div_cnt=0;
wireadd_div_cnt;
wireend_div_cnt;
reg[(6-1):0]bit_cnt=0;
wireadd_bit_cnt;
wireend_bit_cnt;
reg[6-1:0]M=0;
wire[48-1:0]command;
regrd_flag=0;
reg[5-1:0]phy_addr_tmp=0;
reg[5-1:0]reg_addr_tmp=0;
//寄存地址
always@(posedgeclkornegedgerst_n)begin
if(rst_n==1'b0)begin
phy_addr_tmpreg_addr_tmpend
elseif(rd_en)begin
phy_addr_tmpreg_addr_tmpend
end
always@(*)begin
if(state_c==IDLE&&!rd_en&&!rd_flag)
rdyelse
rdyend
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
state_cend
elsebegin
state_cend
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
assignidle2wri_com=state_c==IDLE&&end_div_cnt&&(rd_flag||rd_en);
assignwri_com2rd_data=state_c==WRI_COM&&end_bit_cnt;
assignrd_data2idle=state_c==RD_DATA&&end_bit_cnt;
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
rd_flagend
elseif(state_c==IDLE&&rd_en)begin
rd_flagend
elseif(state_c==WRI_COM)
rd_flagend
//分頻計數器
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
div_cntend
elseif(add_div_cnt)begin
if(end_div_cnt)
div_cntelse
div_cntend
end
assignadd_div_cnt=(1);
assignend_div_cnt=add_div_cnt&&div_cnt==(N)-1;
//比特計數器
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
bit_cntend
elseif(add_bit_cnt)begin
if(end_bit_cnt)
bit_cntelse
bit_cntend
end
assignadd_bit_cnt=(end_div_cnt&&state_c!=IDLE);
assignend_bit_cnt=add_bit_cnt&&bit_cnt==(M)-1;
always@(*)begin
case(state_c)
WRI_COM:M=48;
RD_DATA:M=16;
default:M=10;
endcase
end
//mdc時鐘
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
mdcend
elseif(add_div_cnt&&div_cnt==(N>>1)-1)begin
mdcend
elseif(end_div_cnt)
mdcend
//mdio輸出
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
mdoend
elseif(add_bit_cnt&&state_c==WRI_COM)begin
mdoend
elseif(state_c!=WRI_COM)
mdoend
assigncommand={PRE,START,OP,phy_addr_tmp,reg_addr_tmp,TA};
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
mdo_enend
elseif(state_c==WRI_COM&&add_bit_cnt)
case(bit_cnt)
0:mdo_en46:mdo_endefault:;
endcase
end
//mdio輸入
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
rd_dataend
elseif(add_bit_cnt&&state_c==RD_DATA)begin
rd_data[16-1-bit_cnt]end
end
always@(posedgeclkornegedgerst_n)begin
if(rst_n==0)begin
rd_vldend
elseif(rd_data2idle)begin
rd_vldend
else
rd_vldend
endmodule
mdio_interface
頂層封裝:
`timescale1ns/1ps
modulephy_manage(
inputclk,
inputrst_n,
inputmdio_en,
outputlink_up,
outputchk_done,
outputmdc,
inoutmdio
);
wirerdy;
wirerd_en;
wire[5-1:0]phy_addr;
wire[5-1:0]reg_addr;
(*DONT_TOUCH="TRUE"*)wire[16-1:0]rd_data;
wirerd_vld;
wiremdo_en,mdo,mdi;
mdio_ctrlmdio_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(800))//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)
);
//三態門
assignmdio=mdo_en?mdo:1'bz;
assignmdi=mdio;
endmodule
phy_manage
五、功能仿真
之后編寫testbench進行行為仿真:
`timescale1ns/1ps
`defineBIT_CNTuut.mdio_interface.bit_cnt
modulephy_manage_tb();
//時鐘和復位
regclk;
regrst_n;
//uut的輸入信號
regmdio_en;
//uut的輸出信號
wirelink_up;
wirechk_done;
wiremdc;
wiremdio;
wire[16-1:0]back_data1,back_data2;
//時鐘周期,單位為ns,可在此修改時鐘周期。
parameterCYCLE=10;
//復位時間,此時表示復位3個時鐘周期的時間。
parameterRST_TIME=2;
defparamuut.mdio_ctrl.MS_CYC=100;
//待測試的模塊例化
phy_manageuut(
.clk(clk),
.rst_n(rst_n),
.mdio_en(mdio_en),
.link_up(link_up),
.chk_done(chk_done),
.mdc(mdc),
.mdio(mdio)
);
//生成本地時鐘50M
initialbegin
clk=1;
forever
#(CYCLE/2)
clk=~clk;
end
//產生復位信號
initialbegin
rst_n=1;
#1;
rst_n=0;
#(CYCLE*RST_TIME);
rst_n=1;
end
//輸入信號din0賦值方式
initialbegin
#1;
//賦初值
mdio_en=0;
#(10*CYCLE);
mdio_en=1;
#(1*CYCLE);
mdio_en=0;
//開始賦值
#100_000;
$stop;
end
//模擬PHY響應
//data
assignback_data1={16'b0000_0000_0010_0000};
assignback_data2={16'b1010_0000_0000_0000};
integeri=0,j=0;
initialbegin
foreverbegin
wait(uut.mdio_interface.state_c==1&&`BIT_CNT==47);
@(posedgemdc);
forcemdio=0;
@(posedgemdc);
j=j+1;
if(j==1)
forcemdio=back_data1[16-1-i+1];
else
forcemdio=back_data2[16-1-i+1];
wait(uut.mdio_interface.state_c==0);
@(posedgemdc);
releasemdio;
end
end
initialbegin
foreverbegin
@(posedgemdc);
if(uut.mdio_interface.state_c==2)begin
#10;
i=i+1;
end
else
i=0;
end
end
endmodule
phy_manage_tb
testbench中利用force強迫更新mdio雙向端口方式模擬PHY芯片響應。仿真波形上半部分為MDIO控制模塊信號,下半部分則是MDIO時序接口模塊信號。可見當讀取寄存器數值滿足PHY工作需求時,link_up信號拉高,證明此時MAC可以傳輸數據給PHY。
六、板級調試
完整的設計,板級調試是必不可少的。真正地將接口調通,PHY芯片正確響應才能說明達到設計目的。頂層封裝測試工程,內部例化:差分時鐘緩沖原語、PLL、PHY管理頂層封裝以及VIOILA調試IP。我們來看下原理圖頂層:
測試工程頂層:
`timescale1ns/1ps
modulemdio_test(
inputsys_clk_p,
inputsys_clk_n,
inputrst_n,
outputmdc,
inoutmdio,
outputphy_reset//PHY芯片復位信號低有效
);
wiresys_clk_ibufg;
wireclk;
wireen;
wirechk_done;
wirelink_up;
assignphy_reset=1'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_0u_clk
(
//Clockoutports
.clk_out1(clk),//outputclk_out1100Mhz
//Clockinports
.clk_in1(sys_clk_ibufg));//inputclk_in1
vio_0u_vio(
.clk(clk),//inputwireclk
.probe_out0(en)//outputwire[0:0]probe_out0
);
phy_managephy_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-period5.000[get_portssys_clk_p]
set_propertyPACKAGE_PINR4[get_portssys_clk_p]
set_propertyIOSTANDARDDIFF_SSTL15[get_portssys_clk_p]
set_propertyPACKAGE_PINT6[get_portsrst_n]
set_propertyIOSTANDARDLVCMOS15[get_portsrst_n]
set_propertyPACKAGE_PINW10[get_portsmdc]
set_propertyIOSTANDARDLVCMOS33[get_portsmdc]
set_propertyPACKAGE_PINV10[get_portsmdio]
set_propertyIOSTANDARDLVCMOS33[get_portsmdio]
set_propertyPACKAGE_PINL15[get_portsphy_reset]
set_propertyIOSTANDARDLVCMOS33[get_portsphy_reset]
clk_pin
有一點相信調試過以太網的人大多都跳過一個坑:沒有驅動PHY的復位輸入信號。本人也在此處栽過跟頭,這里直接連續賦值拉高PHY芯片復位信號。關于板級調試還有個小技巧,根據高亞軍老師的書籍得知,將setupdebug生成的ILA探針相關約束命令單獨放入一個約束文件便于調試IP的管理和修改,debug約束文件就不貼出來了。
查看debug波形,MDIO時序接口模塊在釋放MDIO串行總線時,由于存在上拉電阻為高電平,下一個MDC時鐘上升沿時刻,PHY拉低MDIO信號響應并得到總線控制權,開始輸出數據。
得到讀取的兩個寄存器數據,根據數值分析滿足:PHY自動協商完成,且工作在全雙工1000Mbps速率下。
評論