浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用

最近迷上了FPGA的网络通信和GTP光通信,个人感觉光通信简单一些,那就从难得网络通信开始吧,先搞个最简单的,使用MDIO配置和读取网络PHY的信息。
板子:米联客的MA703FA(A7-35T板子);
参考例程:正点原子达芬奇开发板例程;
IDE:vivado2020.2;
具体的原理啥的建议去看正点原子的文档吧,讲得很好,但原子的例程感觉不贴近实际项目,所以我改了一下,使之适合真是项目。
先来看看这块芯片RTL8211FD的数据手册。
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第1张图片
这是官方给的应用架构,很简单,RTL8211FD与MAC通信,通过MDC和MDIO配置。
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第2张图片
芯片BD,没啥好说的,典型的rgmii接口,内部模块电路感觉没必要深究,反正也不懂,能用就行了。
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第3张图片
硬件复位这里要注意,官方说应至少保持10ms低电平,最好还是按官方说的做,菜就要多听话。
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第4张图片
芯片地址这里注意了,RTL8211FD器件地址由5位构成,高两位固定为2’b00,第三位后这三个引脚的上下拉电平决定,所以看看米联客板子的原理图:
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第5张图片
由此可得:此RTL8211FD器件地址为:5’b00001;
这块芯片是不需要配置也能使用的,参考官方给的寄存器默认值可知,默认状态下芯片开启自协商,速率1000M,这就行了,已经不需要配置了,但为了学习,我们可以读状态寄存器,从而获得连接状态和通信速率。需要读两个状态寄存器:
在这里插入图片描述
第一是这个,需要读他的第2和第5位,
浅谈FPGA网络PHY芯片RTL8211FD的配置和简单使用_第6张图片
在这里插入图片描述
第2位:芯片和MAC连接成功;
第5位:芯片和MAC自协商成功;
在这里插入图片描述
还有这个寄存器,主要读芯片的通信速率;
在这里插入图片描述
第4到5位是通信速率;
好了,在看代码:
MDIO控制器直接用原子的,写法和他们家的iic控制器类似,够骚,也够繁琐,但能用,菜就别讲究了吧。

//****************************************************************************************//

module mdio_dri #(
    parameter  PHY_ADDR = 5'b00001,//PHY地址
    parameter  CLK_DIV  = 6'd10    //分频系数
   )
    (
    input                clk         , //时钟信号
    input                rst_n       , //复位信号,低电平有效
    input                i_op_exec   , //触发开始信号
    input                i_op_rh_wl  , //低电平写,高电平读
    input        [4:0]   i_op_addr   , //寄存器地址
    input        [15:0]  i_op_wr_data, //写入寄存器的数据
    output  reg          o_op_done   , //读写完成
    output  reg  [15:0]  o_op_rd_data, //读出的数据
    output  reg          o_op_rd_ack , //读应答信号 0:应答 1:未应答
    output  reg          o_dri_clk   , //驱动时钟    
    output  reg          o_eth_mdc   , //PHY管理接口的时钟信号
    inout                i_eth_mdio    //PHY管理接口的双向数据信号
    );

//parameter define
localparam st_idle    = 6'b00_0001;  //空闲状态
localparam st_pre     = 6'b00_0010;  //发送PRE(前导码)
localparam st_start   = 6'b00_0100;  //开始状态,发送ST(开始)+OP(操作码)
localparam st_addr    = 6'b00_1000;  //写地址,发送PHY地址+寄存器地址
localparam st_wr_data = 6'b01_0000;  //TA+写数据
localparam st_rd_data = 6'b10_0000;  //TA+读数据

//reg define
reg    [5:0]  cur_state ;
reg    [5:0]  next_state;

reg    [5:0]  clk_cnt   ;  //分频计数                      
reg   [15:0]  wr_data_t ;  //缓存写寄存器的数据
reg    [4:0]  addr_t    ;  //缓存寄存器地址
reg    [6:0]  cnt       ;  //计数器
reg           st_done   ;  //状态开始跳转信号
reg    [1:0]  op_code   ;  //操作码  2'b01(写)  2'b10(读)                  
reg           mdio_dir  ;  //MDIO数据(SDA)方向控制
reg           mdio_out  ;  //MDIO输出信号
reg   [15:0]  rd_data_t ;  //缓存读寄存器数据

//wire define
wire          mdio_in    ; //MDIO数据输入
wire   [5:0]  clk_divide ; //PHY_CLK的分频系数

assign i_eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
assign mdio_in = i_eth_mdio;                    //MDIO数据输入
//将PHY_CLK的分频系数除以2,得到dri_clk的分频系数,方便对MDC和MDIO信号操作
assign clk_divide = CLK_DIV >> 1;

//分频得到dri_clk时钟
always @(posedge clk) begin
    if(!rst_n) begin
        o_dri_clk <=  1'b0;
        clk_cnt <= 1'b0;
    end
    else if(clk_cnt == clk_divide[5:1] - 1'd1) begin
        clk_cnt <= 1'b0;
        o_dri_clk <= ~o_dri_clk;
    end
    else clk_cnt <= clk_cnt + 1'b1;
end

//产生PHY_MDC时钟
always @(posedge o_dri_clk) begin
    if(!rst_n) o_eth_mdc <= 1'b1;
    else if(cnt[0] == 1'b0) o_eth_mdc <= 1'b1;
    else o_eth_mdc <= 1'b0;  
end

//(三段式状态机)同步时序描述状态转移
always @(posedge o_dri_clk) begin
    if(!rst_n) cur_state <= st_idle;
    else cur_state <= next_state;
end  

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle : begin
            if(i_op_exec) next_state = st_pre;
            else next_state = st_idle;   
        end  
        st_pre : begin	//发送前导码,32'hffffffff
            if(st_done) next_state = st_start;
            else next_state = st_pre;
        end
        st_start : begin	//ST+OP=2'b01+2'bxx
            if(st_done) next_state = st_addr;
            else next_state = st_start;
        end
        st_addr : begin	//PHYAD+REGAD
            if(st_done) begin
                if(op_code == 2'b01) next_state = st_wr_data;	//MDIO接口写操作                     
                else next_state = st_rd_data; //MDIO接口读操作                            
            end
            else next_state = st_addr;               
        end
        st_wr_data : begin
            if(st_done) next_state = st_idle;
            else next_state = st_wr_data;
        end        
        st_rd_data : begin
            if(st_done) next_state = st_idle;
            else next_state = st_rd_data;
        end                                                                          
        default : next_state = st_idle;
    endcase
  end

//时序电路描述状态输出
always @(posedge o_dri_clk) begin
    if(!rst_n) begin
        cnt <= 5'd0;
        op_code <= 1'b0;
        addr_t <= 1'b0;
        wr_data_t <= 1'b0;
        rd_data_t <= 1'b0;
        o_op_done <= 1'b0;
        st_done <= 1'b0; 
        o_op_rd_data <= 1'b0;
        o_op_rd_ack <= 1'b1;
        mdio_dir <= 1'b0;
        mdio_out <= 1'b1;
    end
    else begin
        st_done <= 1'b0 ;                            
        cnt     <= cnt +1'b1 ;          
        case(cur_state)
            st_idle : begin
                mdio_out <= 1'b1;                     
                mdio_dir <= 1'b0;                     
                o_op_done <= 1'b0;                     
                cnt <= 7'b0;  
                if(i_op_exec) begin
                    op_code <= {i_op_rh_wl,~i_op_rh_wl}; //OP_CODE: 2'b01(写)  2'b10(读) 
                    addr_t <= i_op_addr;
                    wr_data_t <= i_op_wr_data;
                    o_op_rd_ack <= 1'b1;
                end     
            end 
            st_pre : begin                          //发送前导码:32个1bit 
                mdio_dir <= 1'b1;                   //切换MDIO引脚方向:输出
                mdio_out <= 1'b1;                   //MDIO引脚输出高电平
                if(cnt == 7'd62) st_done <= 1'b1;
                else if(cnt == 7'd63) cnt <= 7'b0;
            end            
            st_start  : begin
                case(cnt)
                    7'd1 : mdio_out <= 1'b0;        //发送开始信号 2'b01
                    7'd3 : mdio_out <= 1'b1; 
                    7'd5 : mdio_out <= op_code[1];  //发送操作码
                    7'd6 : st_done <= 1'b1;
                    7'd7 : begin
                               mdio_out <= op_code[0];
                               cnt <= 7'b0;  
                           end    
                    default : ;
                endcase
            end    
            st_addr : begin
                case(cnt)
                    7'd1 : mdio_out <= PHY_ADDR[4]; //发送PHY地址
                    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_t[4];  //发送寄存器地址
                    7'd13: mdio_out <= addr_t[3];
                    7'd15: mdio_out <= addr_t[2];
                    7'd17: mdio_out <= addr_t[1];  
                    7'd18: st_done <= 1'b1;
                    7'd19: begin
                               mdio_out <= addr_t[0]; 
                               cnt <= 7'd0;
                           end    
                    default : ;
                endcase                
            end    
            st_wr_data : begin
                case(cnt)
                    7'd1 : mdio_out <= 1'b1;         //发送TA,写操作(2'b10)
                    7'd3 : mdio_out <= 1'b0;
                    7'd5 : mdio_out <= wr_data_t[15];//发送写寄存器数据
                    7'd7 : mdio_out <= wr_data_t[14];
                    7'd9 : mdio_out <= wr_data_t[13];
                    7'd11: mdio_out <= wr_data_t[12];
                    7'd13: mdio_out <= wr_data_t[11];
                    7'd15: mdio_out <= wr_data_t[10];
                    7'd17: mdio_out <= wr_data_t[9];
                    7'd19: mdio_out <= wr_data_t[8];
                    7'd21: mdio_out <= wr_data_t[7];
                    7'd23: mdio_out <= wr_data_t[6];
                    7'd25: mdio_out <= wr_data_t[5];
                    7'd27: mdio_out <= wr_data_t[4];
                    7'd29: mdio_out <= wr_data_t[3];
                    7'd31: mdio_out <= wr_data_t[2];
                    7'd33: mdio_out <= wr_data_t[1];
                    7'd35: mdio_out <= wr_data_t[0];
                    7'd37: begin
                        mdio_dir <= 1'b0;
                        mdio_out <= 1'b1;
                    end
                    7'd39: st_done <= 1'b1;           
                    7'd40: begin
                               cnt <= 7'b0;
                               o_op_done <= 1'b1;      //写操作完成,拉高op_done信号 
                           end    
                    default : ;
                endcase    
            end
            st_rd_data : begin
                case(cnt)
                    7'd1 : begin
                        mdio_dir <= 1'b0;            //MDIO引脚切换至输入状态
                        mdio_out <= 1'b1;
                    end
                    7'd2 : ;                         //TA[1]位,该位为高阻状态,不操作             
                    7'd4 : o_op_rd_ack <= mdio_in;     //TA[0]位,0(应答) 1(未应答)
                    7'd6 : rd_data_t[15] <= mdio_in; //接收寄存器数据
                    7'd8 : rd_data_t[14] <= mdio_in;
                    7'd10: rd_data_t[13] <= mdio_in;
                    7'd12: rd_data_t[12] <= mdio_in;
                    7'd14: rd_data_t[11] <= mdio_in;
                    7'd16: rd_data_t[10] <= mdio_in;
                    7'd18: rd_data_t[9] <= mdio_in;
                    7'd20: rd_data_t[8] <= mdio_in;
                    7'd22: rd_data_t[7] <= mdio_in;
                    7'd24: rd_data_t[6] <= mdio_in;
                    7'd26: rd_data_t[5] <= mdio_in;
                    7'd28: rd_data_t[4] <= mdio_in;
                    7'd30: rd_data_t[3] <= mdio_in;
                    7'd32: rd_data_t[2] <= mdio_in;
                    7'd34: rd_data_t[1] <= mdio_in;
                    7'd36: rd_data_t[0] <= mdio_in;
                    7'd39: st_done <= 1'b1;
                    7'd40: begin
                        o_op_done <= 1'b1;             //读操作完成,拉高op_done信号          
                        o_op_rd_data <= rd_data_t;
                        rd_data_t <= 16'd0;
                        cnt <= 7'd0;
                    end
                    default : ;
                endcase   
            end                
            default : ;
        endcase               
    end
end                    

endmodule

然后是控制逻辑,原子的例程是通过触摸按键软复位RTL8211FD,然后再读那两个寄存器,感觉没啥用,自己重写了一个,功能是硬件复位完成后直接循环读那两个寄存器,如果自协商完成且连接成功,led亮,否则灭,再用另个led来只是当前的通信速率,直接上代码:

//****************************************************************************************//
module mdio_ctrl(
(* mark_debug ="true" *)    input                clk           ,
(* mark_debug ="true" *)    input                rst_n         ,

(* mark_debug ="true" *)    input                i_op_done     , //读写完成
(* mark_debug ="true" *)    input        [15:0]  i_op_rd_data  , //读出的数据
(* mark_debug ="true" *)    input                i_op_rd_ack   , //读应答信号 0:应答 1:未应答
(* mark_debug ="true" *)    output  reg          o_op_exec     , //触发开始信号
(* mark_debug ="true" *)    output  reg          o_op_rh_wl    , //低电平写,高电平读
(* mark_debug ="true" *)    output  reg  [4:0]   o_op_addr     , //寄存器地址

(* mark_debug ="true" *)   input                i_eth_rst_n   ,
(* mark_debug ="true" *)   output  reg          o_link_led    ,
(* mark_debug ="true" *)   output  reg  [1:0]   o_speed_led 
    );

(* mark_debug ="true" *) reg  [2:0]   flow_cnt;        //流程控制计数器 

always @(posedge clk) begin
	if(!rst_n) begin
		flow_cnt <= 3'd0;
//		link_error <= 1'b0;
		o_op_exec <= 1'b0; 
	    o_op_rh_wl <= 1'b0; 
	    o_op_addr <= 1'b0;   
//	    op_wr_data <= 1'b0;
		o_link_led <= 1'b0;
		o_speed_led<= 2'b0;
	end
	else begin
		case(flow_cnt)
			3'd0:begin
				if(i_eth_rst_n) flow_cnt <= 3'd1;	//reste_n is ok
			end
			3'd1: begin
				o_op_exec <= 1'b1;	//开始操作MDIO
				o_op_rh_wl<= 1'b1;	//读操作
				o_op_addr <= 5'h01;	//读BMSR状态寄存器
				flow_cnt<= 3'd2;
			end
			3'd2: begin
				if(i_op_done) begin	//读操作完成
					if(!i_op_rd_ack) flow_cnt<= 3'd3; //phy应答,做后续判断
					else flow_cnt<= 3'd1;			//phy未应答,重新发起读BMSR状态寄存器				
				end
			end
			3'd3: begin
				if(i_op_rd_data[5] == 1'b1 && i_op_rd_data[2] == 1'b1) begin
					o_link_led<=1'b1;	//自协商完成,link完成
					flow_cnt<=3'd4;
				end
				else o_link_led<=1'b0;			
			end
			3'd4: begin		//读PHYSR特定状态寄存器
				o_op_addr <= 5'h1A;
				flow_cnt<= 3'd5;			
			end
			3'd5: begin
				if(i_op_done) begin	//读操作完成
					if(!i_op_rd_ack) flow_cnt<= 3'd6; //phy应答,做后续判断
					else flow_cnt<= 3'd4;			//phy未应答,重新发起读PHYSR特定状态寄存器
				end
			end						
			3'd6: begin
				flow_cnt<=3'd1;
				if(i_op_rd_data[5:4] == 2'b10) o_speed_led<=2'b11; 		//1000Mbps
				else if(i_op_rd_data[5:4] == 2'b01) o_speed_led<=2'b01; 	//100Mbps
				else if(i_op_rd_data[5:4] == 2'b00) o_speed_led<=2'b10; 	//100Mbps
				else o_speed_led<= 2'b0;                                 
			end
		default: flow_cnt <= 3'd0;
		endcase
	end
end

endmodule

最后是顶层,这里加了硬件复位的10ms,之前说了,菜就别嫌麻烦,跟官方走:

module mdio_rw_test(
    input          sys_clk    ,
    output         eth_mdc    , //PHY管理接口的时钟信号
    inout          eth_mdio   , //PHY管理接口的双向数据信号
    output         eth_rst_n  , //以太网复位信号
	output         o_link_led ,
    output  [1:0]  o_speed_led        //LED连接速率指示
    );
    
//wire define
wire          op_exec    ;  //触发开始信号
wire          op_rh_wl   ;  //低电平写,高电平读
wire  [4:0]   op_addr    ;  //寄存器地址
wire  [15:0]  op_wr_data ;  //写入寄存器的数据
wire          op_done    ;  //读写完成
wire  [15:0]  op_rd_data ;  //读出的数据
wire          op_rd_ack  ;  //读应答信号 0:应答 1:未应答
wire          dri_clk    ;  //驱动时钟
wire rsn_n;
wire clk_50m;

 clk_wiz_0 u_clk_wiz_0
   (
    // Clock out ports
    .clk_out1(clk_50m),     // output clk_out1
    // Status and control signals
    .locked(rsn_n),       // output locked
   // Clock in ports
    .clk_in1(sys_clk));      // input clk_in1

localparam T_10MS = 10_000_000;	//10ms=10_000_000ns
localparam T_P    = 20        ; //50M周期=20ns
localparam RST_CNT= T_10MS/T_P+10;
reg  [18:0]  eth_rsrn_cnt; 
reg o_eth_rst_n;
//根据数据手册,硬件复位至少10ms的低电平
always @(posedge clk_50m) begin
	if(!rsn_n) eth_rsrn_cnt<='d0;
	else if(eth_rsrn_cnt==RST_CNT) eth_rsrn_cnt<=RST_CNT;
	else eth_rsrn_cnt<=eth_rsrn_cnt+19'd1;
end

always @(posedge clk_50m) begin
	if(!rsn_n) o_eth_rst_n<='d0;
	else if(eth_rsrn_cnt==RST_CNT) o_eth_rst_n<=1'd1;
	else o_eth_rst_n<=1'd0;
end

//硬件复位
assign eth_rst_n = o_eth_rst_n;

//MDIO接口驱动
mdio_dri #(
    .PHY_ADDR    (5'h01),   //PHY地址
    .CLK_DIV     (6'd10)    //分频系数
    )
    u_mdio_dri(
    .clk         (clk_50m   ),
    .rst_n       (rsn_n     ),
    .i_op_exec   (op_exec   ),
    .i_op_rh_wl  (op_rh_wl  ),   
    .i_op_addr   (op_addr   ),   
    .i_op_wr_data(op_wr_data),   
    .o_op_done   (op_done   ),   
    .o_op_rd_data(op_rd_data),   
    .o_op_rd_ack (op_rd_ack ),   
    .o_dri_clk   (dri_clk   ),                   
    .o_eth_mdc   (eth_mdc   ),   
    .i_eth_mdio  (eth_mdio  )   
);      

mdio_ctrl u_mdio_ctrl(
    .clk           (dri_clk    ),
    .rst_n         (rsn_n      ),
    .i_op_done     (op_done    ), //读写完成
    .i_op_rd_data  (op_rd_data ), //读出的数据
    .i_op_rd_ack   (op_rd_ack  ), //读应答信号 0:应答 1:未应答
    .o_op_exec     (op_exec    ), //触发开始信号
    .o_op_rh_wl    (op_rh_wl   ), //低电平写,高电平读
    .o_op_addr     (op_addr    ), //寄存器地址
	.i_eth_rst_n   (o_eth_rst_n),
	.o_link_led    (o_link_led ),
	.o_speed_led   (o_speed_led)
    );
endmodule

结果:灯亮,成功

你可能感兴趣的:(fpga开发)