作者:没落骑士
一、前言
本文设计思想采用明德扬至简设计法。以太网这一高效实用的数据传输方式应用于各个领域,如网络交换设备,高速网络相机等。虽然各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
//interwetten与威廉的赔率体系 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速率下。
- 
                                以太网
                                +关注关注 40文章 5414浏览量 171544
- 
                                PHY
                                +关注关注 2文章 301浏览量 51728
- 
                                MDIO
                                +关注关注 0文章 12浏览量 11205
发布评论请先 登录
相关推荐
PHY的管理接口MDIO/MDC时序图解析
 
    
基于DWC_ether_qos的以太网驱动开发-MDIO驱动编写与测试
 
    
请教关于c6748的MDIO接口读写phy的问题
MAC和内部PHY之间的MDIO连接疑问
【正点原子FPGA连载】 第二十四章MDIO接口读写测试实验-领航者ZYNQ之FPGA开发指南
【正点原子FPGA连载】 第二十四章MDIO接口读写测试实验-领航者ZYNQ之FPGA开发指南
无法使用MDIO总线配置PHY怎么解决?
MDIO接口的基础知识详解
PHY的控制器驱动框架分析
以太网PHY芯片的MII接口和MDIO接口介绍
 
    
 
           
        
 
         通过MDIO接口管理PHY芯片的验证设计方案
通过MDIO接口管理PHY芯片的验证设计方案 
  
     
     
     
            
             
             
                 
             工商网监
工商网监
        
评论