基于FPGA的百兆以太网UDP回环测试

基于前文的百兆以太网ARP测试实验,需要进一步在FPGA中实现百兆网下的UDP通信。

文章的大部分代码基于正点原子开拓者中的代码,本文只是起到一个整理,改进,记录的作用,相关未展示的代码可以参考正点原子的开拓者手册。

内容:

上位机通过网口调试助手发送数据给 FPGA, FPGA 通过千兆以太网接口接收数据并将接收到的数据发送给上位机,完成以太网 UDP 数据的环回。

环境:

Quartus II 13.1 Altera FPGA EP4CE10F17C8N

若是Xilinx环境,则将相关的IP核在基于Xilinx进行更改重新生成,其余RTL级代码基本相同。

代码更改:

正点原子的论坛中找到过一份百兆网下的UDP回环测试样例,相关的代码可以在百兆以太网ARP测试实验中的代码链接中查看,具体位置是在代码中的参考资料下的eth_udp_loop_davinci_100mbps文件夹。

但是文件夹中的代码,没有添加上位机与FPGA板子的ARP协议通信代码,导致需要手动绑定上位机电脑与开发板的Mac地址,这样非常的不方便,并且如果是在多机同步通信下,则更加烦琐了。

而目前正点原子教程中完整实现UDP通信并且包含数据链路层的ARP协议的例程,是基于千兆网的,而本文所做的主要工作则是基于此例程进行修改适配于百兆网的协议,目前由于时间原因,还不支持百兆网与千兆网之间的动态切换。

主要需要修改的代码主要是其中的RGMII接收模块,RGMII发送模块,RGMIIGMII模块与上层的ARP通信模块,顶层UDP回环模块。

其中的RGMII接收模块,RGMII发送模块,RGMIIGMII模块与上层的ARP通信模块的相关修改可以查看另一篇教程:百兆以太网ARP测试实验

而顶层的UDP回环模块代码如下所示:

/************************************************************************ 
 * 项目名称 :  $FPGA实验箱$   
 * 类 名 称 :  $UDP测试实验$ 
 * 版 本 号 :  1.0        
 * 作    者 :  kxq
 * 邮    箱 :  2543697634@qq.com
 * 网    站 :  https://www.cnblogs.com/kxqblog/
 * 创建时间 :  $2022/8/9$
 * 项目描述 :  UDP顶层模块
************************************************************************/

module eth_udp_loop(
    input              sys_rst_n , //系统复位信号,低电平有效 
    //以太网RGMII接口   
    input              eth_rxc   , //RGMII接收数据时钟
    input              eth_rx_ctl, //RGMII输入数据有效信号
    input       [3:0]  eth_rxd   , //RGMII输入数据
    output             eth_txc   , //RGMII发送数据时钟    
    output             eth_tx_ctl, //RGMII输出数据有效信号
    output      [3:0]  eth_txd   , //RGMII输出数据          
    output             eth_rst_n   //以太网芯片复位信号,低电平有效   
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;    
//开发板IP地址 192.168.137.10 
parameter BOARD_IP  = {8'd192,8'd168,8'd137,8'd10};   
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.137.11     
parameter  DES_IP    = {8'd192,8'd168,8'd137,8'd11};

//wire define      
wire          eth_rxc_deg   ; //eth_rxc时钟相位偏移
        
wire          gmii_rx_clk   ; //GMII接收时钟
wire          gmii_rx_dv    ; //GMII接收数据有效信号
wire  [7:0]   gmii_rxd      ; //GMII接收数据
wire          gmii_tx_clk   ; //GMII发送时钟
wire          gmii_tx_en    ; //GMII发送数据使能信号
wire  [7:0]   gmii_txd      ; //GMII发送数据     

wire          arp_gmii_tx_en; //ARP GMII输出数据有效信号 
wire  [7:0]   arp_gmii_txd  ; //ARP GMII输出数据
wire          arp_rx_done   ; //ARP接收完成信号
wire          arp_rx_type   ; //ARP接收类型 0:请求  1:应答
wire  [47:0]  src_mac       ; //接收到目的MAC地址
wire  [31:0]  src_ip        ; //接收到目的IP地址    
wire          arp_tx_en     ; //ARP发送使能信号
wire          arp_tx_type   ; //ARP发送类型 0:请求  1:应答
wire  [47:0]  des_mac       ; //发送的目标MAC地址
wire  [31:0]  des_ip        ; //发送的目标IP地址   
wire          arp_tx_done   ; //ARP发送完成信号

wire          udp_gmii_tx_en; //UDP GMII输出数据有效信号 
wire  [7:0]   udp_gmii_txd  ; //UDP GMII输出数据
wire          rec_pkt_done  ; //UDP单包数据接收完成信号
wire          rec_en        ; //UDP接收的数据使能信号
wire  [31:0]  rec_data      ; //UDP接收的数据
wire  [15:0]  rec_byte_num  ; //UDP接收的有效字节数 单位:byte 
wire  [15:0]  tx_byte_num   ; //UDP发送的有效字节数 单位:byte 
wire          udp_tx_done   ; //UDP发送完成信号
wire          tx_req        ; //UDP读数据请求信号
wire  [31:0]  tx_data       ; //UDP待发送数据

wire			  clk_25Mhz		 ;

//*****************************************************
//**                    main code
//*****************************************************

assign tx_start_en = rec_pkt_done;
assign tx_byte_num = rec_byte_num;
assign des_mac = src_mac;
assign des_ip = src_ip;
assign eth_rst_n = sys_rst_n;

//PLL
pll u_pll(
    .inclk0  (eth_rxc),
    .c0      (eth_rxc_deg),					//12.5Mhz时钟 rgmii接收参考时钟
	 .c1		 (clk_25Mhz),						//  25Mhz时钟  gmii发送参考时钟	  
    .locked  ()
    );

//GMII接口转RGMII接口
gmii_to_rgmii u_gmii_to_rgmii(

    .gmii_rx_clk       (gmii_rx_clk ),
    .gmii_rx_dv        (gmii_rx_dv  ),
    .gmii_rxd          (gmii_rxd    ),
    .gmii_tx_clk       (clk_25Mhz 	),   	//gmii_tx_clk
    .gmii_tx_en        (gmii_tx_en  ),
    .gmii_txd          (gmii_txd    ),
        
    .rgmii_rxc         (eth_rxc_deg ),
    .rgmii_rx_ctl      (eth_rx_ctl  ),
    .rgmii_rxd         (eth_rxd     ),
    .rgmii_txc         (eth_txc     ),
    .rgmii_tx_ctl      (eth_tx_ctl  ),
    .rgmii_txd         (eth_txd     ),
	 .sys_rst_n			  (sys_rst_n)
    );

//ARP通信
arp                                             
   #(
    .BOARD_MAC     (BOARD_MAC),      //参数例化
    .BOARD_IP      (BOARD_IP ),
    .DES_MAC       (DES_MAC  ),
    .DES_IP        (DES_IP   )
    )
   u_arp(
    .rst_n         (sys_rst_n  ),
                    
    .gmii_rx_clk   (gmii_rx_clk),
    .gmii_rx_dv    (gmii_rx_dv ),
    .gmii_rxd      (gmii_rxd   ),
    .gmii_tx_clk   (eth_rxc_deg),			//gmii_tx_clk
    .gmii_tx_en    (arp_gmii_tx_en ),
    .gmii_txd      (arp_gmii_txd),
                    
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .src_mac       (src_mac    ),
    .src_ip        (src_ip     ),
    .arp_tx_en     (arp_tx_en  ),
    .arp_tx_type   (arp_tx_type),
    .des_mac       (des_mac    ),
    .des_ip        (des_ip     ),
    .tx_done       (arp_tx_done)
    );

//UDP通信
udp                                             
   #(
    .BOARD_MAC     (BOARD_MAC),      //参数例化
    .BOARD_IP      (BOARD_IP ),
    .DES_MAC       (DES_MAC  ),
    .DES_IP        (DES_IP   )
    )
   u_udp(
    .rst_n         (sys_rst_n   ),  
    
    .gmii_rx_clk   (gmii_rx_clk ),           
    .gmii_rx_dv    (gmii_rx_dv  ),         
    .gmii_rxd      (gmii_rxd    ),                   
    .gmii_tx_clk   (eth_rxc_deg ),		//gmii_tx_clk 
    .gmii_tx_en    (udp_gmii_tx_en),         
    .gmii_txd      (udp_gmii_txd),  

    .rec_pkt_done  (rec_pkt_done),    
    .rec_en        (rec_en      ),     
    .rec_data      (rec_data    ),         
    .rec_byte_num  (rec_byte_num),      
    .tx_start_en   (tx_start_en ),        
    .tx_data       (tx_data     ),         
    .tx_byte_num   (tx_byte_num ),  
    .des_mac       (des_mac     ),
    .des_ip        (des_ip      ),    
    .tx_done       (udp_tx_done ),        
    .tx_req        (tx_req      )           
    ); 

//跨时钟处理不需要
//pulse_sync_pro u_pulse_sync_pro(
//    .clk_a          (gmii_rx_clk),
//    .rst_n          (sys_rst_n),
//    .pulse_a        (rec_pkt_done),
//    .clk_b          (eth_rxc_deg),		//gmii_tx_clk
//    .pulse_b        (tx_start_en)
//    );
	 
//异步FIFO
async_fifo u_sync_fifo(
	 .aclr		(~sys_rst_n),
	 .data		(rec_data),
	 .rdclk		(eth_rxc_deg),				//gmii_tx_clk
	 .rdreq		(tx_req),
	 .wrclk		(gmii_rx_clk),
	 .wrreq		(rec_en),
	 .q			(tx_data),
	 .rdempty	(),
	 .wrfull		()       
    );

//以太网控制模块
eth_ctrl u_eth_ctrl(
    .clk            (gmii_rx_clk),
    .rst_n          (sys_rst_n),

    .arp_rx_done    (arp_rx_done   ),
    .arp_rx_type    (arp_rx_type   ),
    .arp_tx_en      (arp_tx_en     ),
    .arp_tx_type    (arp_tx_type   ),
    .arp_tx_done    (arp_tx_done   ),
    .arp_gmii_tx_en (arp_gmii_tx_en),
    .arp_gmii_txd   (arp_gmii_txd  ),
                     
    .udp_gmii_tx_en (udp_gmii_tx_en),
    .udp_gmii_txd   (udp_gmii_txd  ),
    .udp_tx_start_en(tx_start_en),
    .udp_tx_done    (udp_tx_done),
                     
    .gmii_tx_en     (gmii_tx_en    ),
    .gmii_txd       (gmii_txd      )
    );
     
endmodule

其中的锁相环模块PLL,为了适配百兆网,生成了两路时钟,一路12.5Mhz,一路25Mhz,两者与之前的百兆以太网ARP测试实验中的锁相环时钟一致,可以参考前文。

GMII接口转RGMII接口是修改幅度较大的模块,同时也是百兆网通信成功的关键,相关的修改思路与方法也可以参考前文的百兆以太网ARP测试实验

ARP通信模块接口与UDP通信模块接口中主要修改了gmii_tx_clk接口,注意百兆网下的发送模块的参考时钟是25Mhz下工作。

百兆网下,由于GMII接口转RGMII接口模块中的发送参考时钟与接收参考时钟不一致,因此在两个不同时钟下的信号之间若需要传输,需要作跨时钟处理,但因为之前在ARP测试实验下,巧妙地利用12.5Mhz上下沿采样实现的25Mhz下单沿采样的效果。而后面的gmii_rx_clk也需要的是在12.5Mhz下的参考时钟,这样,两个信号rec_pkt_donetx_start_en就不需要再作时钟同步了。

异步FIFO模块注意由于gmii_rx_clketh_rxc_deg是一路时钟(eth_rxc_deg不是网口时钟25Mhz的偏移,是网口时钟25Mhz两分频后的偏移,这里的起名有些问题,需要改进),因此本质上可以换成同步FIFO也是可以的,这个是在起初设计时未想到可以用前面上下沿采样实现GMII接口转RGMII接口的效果,因此这里就还没有改成同步FIFO。

以太网控制模块实现的是动态切换GMII接口转RGMII接口模块ARP模块相连或是UDP模块相连,默认下是与ARP模块相连接,在解析到ARP请求并动态绑定了Mac地址后,再发送ARP应答包。这里代码中固定ARP发送应答包了。因此需要一开始上位机发送ARP请求给FPGA板子。

在FPGA板子接收并成功解析了上位机发送的UDP数据包(在接收UDP消息前需要板子正确解析ARP请求包与发送ARP应答包),将拉高UDP数据完成信号,此时以太网控制模块将把UDP的发送模块与GMII发送模块相连,使FPGA板子发送一个同样的UDP包给上位机,完成UDP消息回环。

测试结果

最终经过测试,修改后的代码在上位机软件(可以在后面代码中的参考资料中找到或在网络中的一些网络调试助手也是通用的)在上位机发送UDP消息给板子后,板子会解析UDP消息中的ARP请求,并响应请求,发送Mac地址给上位机完成动态绑定,同时将一份同样的UDP消息再回传给上位机,而上位机也能解析此UDP消息。

上位机发送UDP消息给板子

板子解析ARP请求并响应:

FPGA板子回环UDP消息:

代码链接 :

链接: https://pan.baidu.com/s/1dkgzuyQhx3_uqwStZT9_6g?pwd=cwws

提取码: cwws

参考博客:

https://zhuanlan.zhihu.com/p/182661401

https://www.cnblogs.com/kxqblog/p/16972403.html

posted @ 2022-12-10 21:54  Deceiver_Ker  阅读(1934)  评论(0编辑  收藏  举报