作者:没落骑士
一、前言
本文设计思想采用明德扬至简设计法。以太网这一高效实用的数据传输方式应用于各个领域,如网络交换设备,高速网络相机等。虽然各fpga厂商都提供mac ip核,但大多收费,有时无法破解。不同厂家之间无法移植,而且为了通用性考虑牺牲了效率,因此自己动手写一个以太网mac是个不错的选择。
本博文讨论通过mdio接口管理phy芯片来验证其正确工作,为在此基础上设计mac逻辑开个头。phy芯片采用rtl8211egvb,选用gmii接口与mac连接。下面我们来开始第一步,在此之前明确设计目的:检测phy芯片是否完成自动协商 链路速率是否达到1000m。所以要从datasheet中了解到芯片引脚 寄存器地址 接口时序。
二、设计分析
管理帧格式如下:
读写操作时序:
mdc为mac驱动时钟信号,mdio是串行数据总线,需要连接上拉电阻保证idle状态下高电平。其中前导码包含32个比特“1”,phy地址根据芯片引脚连接而定,此处为01.turn around域是为了防止读操作时数据冲突,在读操作过程中mac和phy均在第1比特处进入高阻态,phy在第2比特处驱动mdio接口为低电平以占据总线控制权。注意两点:第一如果时钟信号在读写操作后停止,时钟必须保证至少7个时钟周期持续翻转且mdio高电平从而保证之前的操作完成。故在设计中可以等待一段时间后再拉低时钟使能信号。第二两个操作之间至少一个idle比特。
正确驱动接口时序需要关注ac characterisics.
很明显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状态,暂不进行配置,故两模块工作状态跳转如图所示:
剩下的工作就是把两个状态机实现出来,非常简单。有需要的朋友可以参考一下,关于芯片的具体参数详见: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 =0,
output reg chk_vld =0,
input rdy,
output reg rd_en =0,
output reg [5-1:0] phy_addr =0,
output reg [5-1:0] reg_addr =0,
input [16-1:0] rd_data,
input rd_vld
);
parameter ms_cyc = 100_000;
localparam idle = 0 ;
localparam wait = 1 ;
localparam rd_phy = 2 ;
localparam check = 3 ;
localparam wait_ms = 10;
localparam bmsr = 5'h01,
physr = 5'h11;
reg [4-1:0] state_c = 0,state_n = 0;
wire idle2wait,wait2rd_phy,rd_phy2check,check2idle,check2wait;
wire link_up;
reg [16-1:0] rd_memory [0:1];
reg [ (17-1):0] ms_cnt =0 ;
wire add_ms_cnt ;
wire end_ms_cnt ;
reg [ (4-1):0] wait_cnt =0 ;
wire add_wait_cnt ;
wire end_wait_cnt ;
reg [ (2-1):0] rd_cnt =0 ;
wire add_rd_cnt ;
wire end_rd_cnt ;
reg [ (2-1):0] rdata_cnt =0 ;
wire add_rdata_cnt ;
wire end_rdata_cnt ;
wire [5*2-1:0] registers;
reg rd_finish = 0;
initial begin
rd_memory[0] = 0;
rd_memory[1] = 0;
end
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c end
else begin
state_c 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[0][5] == 1'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==0) begin
ms_cnt end
else if(add_ms_cnt) begin
if(end_ms_cnt)
ms_cnt else
ms_cnt end
end
assign add_ms_cnt = (state_c == wait);
assign end_ms_cnt = add_ms_cnt && ms_cnt == (ms_cyc)-1 ;//100mhz时钟100_000
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
wait_cnt end
else if(add_wait_cnt) begin
if(end_wait_cnt)
wait_cnt else
wait_cnt end
end
assign add_wait_cnt = (end_ms_cnt);
assign end_wait_cnt = add_wait_cnt && wait_cnt == (wait_ms)-1 ;
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
rd_cnt end
else if(add_rd_cnt) begin
if(end_rd_cnt)
rd_cnt else
rd_cnt end
end
assign add_rd_cnt = (state_c == rd_phy && rdy && !rd_finish);
assign end_rd_cnt = add_rd_cnt && rd_cnt == (2)-1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_finish end
else if(end_rd_cnt)begin
rd_finish end
else if(state_c == check)
rd_finish end
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
rdata_cnt end
else if(add_rdata_cnt) begin
if(end_rdata_cnt)
rdata_cnt else
rdata_cnt end
end
assign add_rdata_cnt = (rd_vld);
assign end_rdata_cnt = add_rdata_cnt && rdata_cnt == (2)-1 ;
//接口信号逻辑
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_en phy_addr reg_addr end
else if(add_rd_cnt)begin
rd_en phy_addr reg_addr 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==1'b0)begin
rd_memory[0] rd_memory[1] end
else if(add_rdata_cnt)begin
rd_memory[rdata_cnt] end
end
//用户侧输出检测结果
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
chk_vld end
else if(state_c == check)begin
chk_vld end
else
chk_vld end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
chk_result end
else if(check2idle)begin
chk_result end
else if(check2wait)
chk_result end
endmodule
mdio_ctrl
mdio时序接口模块:
`timescale 1ns / 1ps
module mdio_interface#(parameter mdc_cyc = 800)//ns
(
input clk,//100m时钟
input rst_n,
input rd_en,
input [5-1:0] phy_addr,
input [5-1:0] reg_addr,
output reg [16-1:0] rd_data =0,
output reg rd_vld =0,
output reg rdy =0,
output reg mdo =1,
output reg mdo_en =0,
input mdi,
output reg mdc =1
);
localparam n = mdc_cyc/10;
localparam idle = 0 ;
localparam wri_com = 1 ;
localparam rd_data = 2 ;
localparam pre = 32'hffff_ffff,
start = 2'b01,
op = 2'b10,
ta = 2'b11;
reg [3-1:0] state_c =0,state_n =0;
wire idle2wri_com,wri_com2rd_data,rd_data2idle;
reg [ (7-1):0] div_cnt =0 ;
wire add_div_cnt ;
wire end_div_cnt ;
reg [ (6-1):0] bit_cnt =0 ;
wire add_bit_cnt ;
wire end_bit_cnt ;
reg [6-1:0] m =0;
wire [48-1:0] command;
reg rd_flag =0 ;
reg [5-1:0] phy_addr_tmp = 0;
reg [5-1:0] reg_addr_tmp = 0;
//寄存地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
phy_addr_tmp reg_addr_tmp end
else if(rd_en)begin
phy_addr_tmp reg_addr_tmp 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==0) begin
state_c end
else begin
state_c 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==0) begin
rd_flag end
else if(state_c == idle && rd_en)begin
rd_flag end
else if(state_c == wri_com)
rd_flag end
//分频计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
div_cnt end
else if(add_div_cnt) begin
if(end_div_cnt)
div_cnt else
div_cnt end
end
assign add_div_cnt = (1);
assign end_div_cnt = add_div_cnt && div_cnt == (n)-1 ;
//比特计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
bit_cnt end
else if(add_bit_cnt) begin
if(end_bit_cnt)
bit_cnt else
bit_cnt end
end
assign add_bit_cnt = (end_div_cnt && state_c != idle);
assign end_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 @(posedge clk or negedge rst_n )begin
if(rst_n==0) begin
mdc end
else if(add_div_cnt && div_cnt == (n>>1) - 1)begin
mdc end
else if(end_div_cnt)
mdc end
//mdio输出
always @(posedge clk or negedge rst_n )begin
if(rst_n==0) begin
mdo end
else if(add_bit_cnt && state_c == wri_com)begin
mdo end
else if(state_c != wri_com)
mdo end
assign command = {pre,start,op,phy_addr_tmp,reg_addr_tmp,ta};
always @(posedge clk or negedge rst_n )begin
if(rst_n==0) begin
mdo_en end
else if(state_c == wri_com && add_bit_cnt)
case(bit_cnt)
0: mdo_en 46:mdo_en default:;
endcase
end
//mdio输入
always @(posedge clk or negedge rst_n )begin
if(rst_n==0) begin
rd_data end
else if(add_bit_cnt && state_c == rd_data)begin
rd_data[16-1-bit_cnt] end
end
always @(posedge clk or negedge rst_n )begin
if(rst_n==0) begin
rd_vld end
else if(rd_data2idle)begin
rd_vld 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 [5-1:0] phy_addr;
wire [5-1:0] reg_addr;
(*dont_touch = true*)wire [16-1:0] 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(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)
);
//三态门
assign mdio = mdo_en ? mdo : 1'bz;
assign mdi = mdio;
endmodule
phy_manage
五、功能仿真
之后编写testbench进行行为仿真:
`timescale 1 ns/1 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 [16-1:0] back_data1,back_data2;
//时钟周期,单位为ns,可在此修改时钟周期。
parameter cycle = 10;
//复位时间,此时表示复位3个时钟周期的时间。
parameter rst_time = 2 ;
defparam uut.mdio_ctrl.ms_cyc = 100;
//待测试的模块例化
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 = 1;
forever
#(cycle/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#1;
rst_n = 0;
#(cycle*rst_time);
rst_n = 1;
end
//输入信号din0赋值方式
initial begin
#1;
//赋初值
mdio_en = 0;
#(10*cycle);
mdio_en = 1;
#(1*cycle);
mdio_en = 0;
//开始赋值
#100_000;
$stop;
end
//模拟phy响应
//data
assign back_data1 = {16'b0000_0000_0010_0000};
assign back_data2 = {16'b1010_0000_0000_0000};
integer i = 0,j = 0;
initial begin
forever begin
wait(uut.mdio_interface.state_c == 1 && `bit_cnt == 47 );
@(posedge mdc);
force mdio = 0;
@(posedge mdc);
j = j+1;
if(j == 1)
force mdio = back_data1[16-1-i+1];
else
force mdio = back_data2[16-1-i+1];
wait(uut.mdio_interface.state_c == 0);
@(posedge mdc);
release mdio;
end
end
initial begin
forever begin
@(posedge mdc);
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管理顶层封装以及vio ila调试ip。我们来看下原理图顶层:
测试工程顶层:
`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 = 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_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信号响应并得到总线控制权,开始输出数据。
得到读取的两个寄存器数据,根据数值分析满足:phy自动协商完成,且工作在全双工1000mbps速率下。
最终rj45接口绿色指示灯常亮,表明自动协商完成,网络连接正确。到此简易的phy芯片检测管理模块设计完成。
大气负氧离子浓度检测仪HM-FY4的优势特点是什么
特斯拉第二工厂将在上海开设?官方回应:不是新建而是扩产
iOS 11的新功能可能会有多人FaceTime
13B模型全方位碾压GPT-4?这背后有什么猫腻
区块链能否解决拜占庭将军的问题
通过MDIO接口管理PHY芯片的验证设计方案
2018年VR/AR行业数据报告,VR/AR在各自的领域开疆扩土
【曦哥论币】币圈投资如何从根摆脱亏损!原来关键在这里!
三星环绕全面屏的滑盖智能手机概念图
C/C++ 宏详解
三星玄龙骑士显示器新品Neo G9:体验再升级,开启游戏未来
五款常用的Linux操作系统
物通博联工业智能网关联网有什么方式
百度与吉利合作,百度造车优势何在?
中国“5G车联网”大动作!如此精彩内容!您舍得错过?
HID Global®正式宣布推出HID® FARGO® INK1000打印机
氢氧燃料电池的使用范围及存在问题
电子系统中电源如何搞定EMI电磁干扰
一文带你浅析谐波
Telefonica与华为携手 验证URLLC可支持5G-V2X