FPGA实现精简版UDP通信,占资源很少但很稳定,提供2套工程源码
1.高端、中等和精简版UDP通信的选择
FPGA实现UDP协议可难可易,具体根据项目需求而定,目前项目上的需求大概有如下几种:
1、使用Xilinx系列FPGA实现UDP通信,且传输的数据量大,速率快,带宽高,这类的UDP通信必然要用到Xilinx的三速网IP,用户接口的数据必然是AXIS流,此类UDP协议功能齐全,无疑是很好的方案,关于这类的UDP通信介绍以及工程源码请参考我之前写的文章点击查看:高端UDP通信,附带工程源码
此类UDP协议虽然很好,但占用FPGA资源较多,对于A7、Spatan6、Altera之类的低端FPGA来说资源消耗太大,不太可能被采用,再者,对于国产FPGA来说,没有三速网IP也无法实现;
2、不使用三速网IP,通信速率相对较低,实现过程几乎使用纯verilog代码实现的中等UDP通信方案,关于这类的UDP通信介绍以及工程源码请参考我之前写的文章点击查看:中等UDP通信,附带工程源码
此类方案没有了IP的限制,任何品牌的FPGA均可实现,但占用FPGA资源还是较多,低端FPGA依然很难实现;
3、精简版UDP通信方案
就是此文介绍的方案
优点:
纯verilog代码实现,没有任何IP;
代码量很少,所占逻辑资源很少;
通用性好,Xilinx、Altera、国产FPGA均可使用;
稳定性好,本人反复测试,很稳定;
2.精简版UDP通信实现方案
总体实现方案如下:
RGMII-GMII模块实现网络PHY数据与FPGA接口的数据转换,由PHY的4位DDR双边沿采样数据到FPGA接口8位单边沿采样数据的转换;模块接口顶层如下:
module gmii_to_rgmii(
input idelay_clk , //IDELAY时钟
//以太网GMII接口
output gmii_rx_clk , //GMII接收时钟
output gmii_rx_dv , //GMII接收数据有效信号
output [7:0] gmii_rxd , //GMII接收数据
output gmii_tx_clk , //GMII发送时钟
input gmii_tx_en , //GMII发送数据使能信号
input [7:0] gmii_txd , //GMII发送数据
//以太网RGMII接口
input rgmii_rxc , //RGMII接收时钟
input rgmii_rx_ctl, //RGMII接收数据控制信号
input [3:0] rgmii_rxd , //RGMII接收数据
output rgmii_txc , //RGMII发送时钟
output rgmii_tx_ctl, //RGMII发送数据控制信号
output [3:0] rgmii_txd //RGMII发送数据
);
ARP模块顾名思义就是实现了ARP协议,该模块在不同应用中可有可无,在于上位机通信时需要,在FPGA板间通信时则可以不需要,模块接口顶层如下:
module arp(
input rst_n , //复位信号,低电平有效
//GMII接口
input gmii_rx_clk, //GMII接收数据时钟
input gmii_rx_dv , //GMII输入数据有效信号
input [7:0] gmii_rxd , //GMII输入数据
input gmii_tx_clk, //GMII发送数据时钟
output gmii_tx_en , //GMII输出数据有效信号
output [7:0] gmii_txd , //GMII输出数据
//用户接口
output arp_rx_done, //ARP接收完成信号
output arp_rx_type, //ARP接收类型 0:请求 1:应答
output [47:0] src_mac , //接收到目的MAC地址
output [31:0] src_ip , //接收到目的IP地址
input arp_tx_en , //ARP发送使能信号
input arp_tx_type, //ARP发送类型 0:请求 1:应答
input [47:0] des_mac , //发送的目标MAC地址
input [31:0] des_ip , //发送的目标IP地址
output tx_done //以太网发送完成信号
);
UDP模块顾名思义就是实现了UDP协议,该部分内部已经包含了IP协议的编解码,这部分时整个工程的核心,UDP协议其实真的很简单,接收部分就是根据协议进行数据解包,解出帧头部分并丢弃,解出有效数据并输出;发送部分就是根据协议进行数据组包,把发送数据加上帧头发出去就完事儿了,模块接口顶层如下:
module udp(
input rst_n , //复位信号,低电平有效
//GMII接口
input gmii_rx_clk , //GMII接收数据时钟
input gmii_rx_dv , //GMII输入数据有效信号
input [7:0] gmii_rxd , //GMII输入数据
input gmii_tx_clk , //GMII发送数据时钟
output gmii_tx_en , //GMII输出数据有效信号
output [7:0] gmii_txd , //GMII输出数据
//用户接口
output rec_pkt_done, //以太网单包数据接收完成信号
output rec_en , //以太网接收的数据使能信号
output [31:0] rec_data , //以太网接收的数据
output [15:0] rec_byte_num, //以太网接收的有效字节数 单位:byte
input tx_start_en , //以太网开始发送信号
input [31:0] tx_data , //以太网待发送数据
input [15:0] tx_byte_num , //以太网发送的有效字节数 单位:byte
input [47:0] des_mac , //发送的目标MAC地址
input [31:0] des_ip , //发送的目标IP地址
output tx_done , //以太网发送完成信号
output tx_req //读数据请求信号
);
工程介绍:
简而言之就是实现UDP自发自收,以验证UDP协议的正确性;
3.工程1介绍及资源占用率和性能表现
工程1介绍;
开发板:Kintex7开发板;
网络PHY:B50610
输出:RJ45网口
接收:电脑上位机;
B50610不需要配置,使用默认的配置,工作在延时模式,PHY的发送时钟rgmii_txc 与FPGA发送时钟gmii_tx_clk一致;
代码部分如下:
assign rgmii_txc = gmii_tx_clk;
工程顶层代码如下:
module eth_udp_loop(
input clk_50 , //系统时钟
//PL以太网RGMII接口
input phy1_rgmii_rx_clk , //RGMII接收数据时钟
input phy1_rgmii_rx_ctl, //RGMII输入数据有效信号
input [3:0] phy1_rgmii_rx_data , //RGMII输入数据
output phy1_rgmii_tx_clk , //RGMII发送数据时钟
output phy1_rgmii_tx_ctl, //RGMII输出数据有效信号
output [3:0] phy1_rgmii_tx_data , //RGMII输出数据
output phy_reset //以太网芯片复位信号,低电平有效
);
//parameter define
parameter BOARD_MAC = 48'h00_11_22_33_44_55; //开发板MAC地址
parameter BOARD_IP = {8'd192,8'd168,8'd10,8'd1}; //开发板IP地址
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff; //目的MAC地址
parameter DES_IP = {8'd192,8'd168,8'd10,8'd2}; //目的IP地址
parameter IDELAY_VALUE = 0; //输入数据IO延时,此处为0,即不延时(如果为n,表示延时n*78ps)
parameter BOARD_POART =16'd1234;
parameter DES_POART =16'd1234;
//wire define
wire clk_200m ; //用于IO延时的时钟
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 sys_rst_n;
//*****************************************************
//** main code
//*****************************************************
assign gmii_tx_clk = gmii_rx_clk ;
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 phy_reset = sys_rst_n;
clk_wiz_0 instance_name
(
// Clock out ports
.clk_200m(clk_200m), // output clk_200m
.clk_125m(), // output clk_125m
.clk_125m_90(), // output clk_125m_90
// Status and control signals
.locked(sys_rst_n), // output locked
// Clock in ports
.clk_in1(clk_50)); // input clk_in1
//GMII接口转RGMII接口
gmii_to_rgmii
#(
.IDELAY_VALUE (IDELAY_VALUE)
)
u_gmii_to_rgmii(
.idelay_clk (clk_200m ),
.gmii_rx_clk (gmii_rx_clk ),
.gmii_rx_dv (gmii_rx_dv ),
.gmii_rxd (gmii_rxd ),
.gmii_tx_clk (gmii_tx_clk ),
.gmii_tx_en (gmii_tx_en ),
.gmii_txd (gmii_txd ),
.rgmii_rxc (phy1_rgmii_rx_clk ),
.rgmii_rx_ctl (phy1_rgmii_rx_ctl ),
.rgmii_rxd (phy1_rgmii_rx_data ),
.rgmii_txc (phy1_rgmii_tx_clk ),
.rgmii_tx_ctl (phy1_rgmii_tx_ctl ),
.rgmii_txd (phy1_rgmii_tx_data )
);
//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 (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 ),
.BOARD_POART (BOARD_POART),
.DES_POART (DES_POART )
)
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 (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 )
);
//同步FIFO
sync_fifo_2048x32b u_sync_fifo_2048x32b (
.clk (gmii_rx_clk), // input wire clk
.rst (~sys_rst_n), // input wire rst
.din (rec_data ), // input wire [31 : 0] din
.wr_en (rec_en ), // input wire wr_en
.rd_en (tx_req ), // input wire rd_en
.dout (tx_data ), // output wire [31 : 0] dout
.full (), // output wire full
.empty () // output wire empty
);
//以太网控制模块
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 ),
.gmii_tx_en (gmii_tx_en ),
.gmii_txd (gmii_txd )
);
endmodule
K7上的资源消耗和性能表现如下:
由于工程中使用到了PLL和fifo,实际UDP部分所占资源更小;
4.工程2介绍及资源占用率和性能表现
工程2介绍;
开发板:Artix7开发板;
网络PHY:RTL8211
输出:RJ45网口
接收:电脑上位机;
RTL8211不需要配置,使用默认的配置,工作在延时模式,PHY的发送时钟rgmii_txc 与FPGA发送时钟gmii_tx_clk不一致;rgmii_txc与gmii_tx_clk相位偏差+90°;
代码部分如下:
module rgmii_tx(
//GMII发送端口
input gmii_tx_clk , //GMII发送时钟
input gmii_tx_clk_90 ,
input gmii_tx_en , //GMII输出数据有效信号
input [7:0] gmii_txd , //GMII输出数据
//RGMII发送端口
output rgmii_txc , //RGMII发送数据时钟
output rgmii_tx_ctl, //RGMII输出数据有效信号
output [3:0] rgmii_txd //RGMII输出数据
);
assign rgmii_txc = gmii_tx_clk_90;
//输出双沿采样寄存器 (rgmii_tx_ctl)
ODDR #(
.DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT (1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE ("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q (rgmii_tx_ctl), // 1-bit DDR output
.C (gmii_tx_clk), // 1-bit clock input
.CE (1'b1), // 1-bit clock enable input
.D1 (gmii_tx_en), // 1-bit data input (positive edge)
.D2 (gmii_tx_en), // 1-bit data input (negative edge)
.R (1'b0), // 1-bit reset
.S (1'b0) // 1-bit set
);
genvar i;
generate for (i=0; i<4; i=i+1)
begin : txdata_bus
//输出双沿采样寄存器 (rgmii_txd)
ODDR #(
.DDR_CLK_EDGE ("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT (1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE ("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q (rgmii_txd[i]), // 1-bit DDR output
.C (gmii_tx_clk), // 1-bit clock input
.CE (1'b1), // 1-bit clock enable input
.D1 (gmii_txd[i]), // 1-bit data input (positive edge)
.D2 (gmii_txd[4+i]),// 1-bit data input (negative edge)
.R (1'b0), // 1-bit reset
.S (1'b0) // 1-bit set
);
end
endgenerate
endmodule
相应的工程顶层代码的时钟部分也有改变,如下:
module eth_udp_loop(
input clk_50 , //系统时钟
//PL以太网RGMII接口
input phy1_rgmii_rx_clk , //RGMII接收数据时钟
input phy1_rgmii_rx_ctl, //RGMII输入数据有效信号
input [3:0] phy1_rgmii_rx_data , //RGMII输入数据
output phy1_rgmii_tx_clk , //RGMII发送数据时钟
output phy1_rgmii_tx_ctl, //RGMII输出数据有效信号
output [3:0] phy1_rgmii_tx_data , //RGMII输出数据
output phy_reset //以太网芯片复位信号,低电平有效
);
//parameter define
parameter BOARD_MAC = 48'h00_11_22_33_44_55; //开发板MAC地址
parameter BOARD_IP = {8'd192,8'd168,8'd10,8'd1}; //开发板IP地址
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff; //目的MAC地址
parameter DES_IP = {8'd192,8'd168,8'd10,8'd2}; //目的IP地址
parameter IDELAY_VALUE = 0; //输入数据IO延时,此处为0,即不延时(如果为n,表示延时n*78ps)
parameter BOARD_POART =16'd1234;
parameter DES_POART =16'd1234;
//wire define
wire clk_200m ; //用于IO延时的时钟
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 sys_rst_n;
wire clk_125m;
wire clk_125m_90;
//*****************************************************
//** main code
//*****************************************************
assign gmii_tx_clk = clk_125m ;
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 phy_reset = sys_rst_n;
clk_wiz_0 instance_name
(
// Clock out ports
.clk_200m(clk_200m), // output clk_200m
.clk_125m(clk_125m), // output clk_125m
.clk_125m_90(clk_125m_90), // output clk_125m_90
// Status and control signals
.locked(sys_rst_n), // output locked
// Clock in ports
.clk_in1(clk_50)); // input clk_in1
//GMII接口转RGMII接口
gmii_to_rgmii
#(
.IDELAY_VALUE (IDELAY_VALUE)
)
u_gmii_to_rgmii(
.idelay_clk (clk_200m ),
.gmii_rx_clk (gmii_rx_clk ),
.gmii_rx_dv (gmii_rx_dv ),
.gmii_rxd (gmii_rxd ),
.gmii_tx_clk (gmii_tx_clk ),
.gmii_tx_clk_90(clk_125m_90),
.gmii_tx_en (gmii_tx_en ),
.gmii_txd (gmii_txd ),
.rgmii_rxc (phy1_rgmii_rx_clk ),
.rgmii_rx_ctl (phy1_rgmii_rx_ctl ),
.rgmii_rxd (phy1_rgmii_rx_data ),
.rgmii_txc (phy1_rgmii_tx_clk ),
.rgmii_tx_ctl (phy1_rgmii_tx_ctl ),
.rgmii_txd (phy1_rgmii_tx_data )
);
//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 (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 ),
.BOARD_POART (BOARD_POART),
.DES_POART (DES_POART )
)
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 (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 )
);
//同步FIFO
sync_fifo_2048x32b u_sync_fifo_2048x32b (
.clk (gmii_rx_clk), // input wire clk
.rst (~sys_rst_n), // input wire rst
.din (rec_data ), // input wire [31 : 0] din
.wr_en (rec_en ), // input wire wr_en
.rd_en (tx_req ), // input wire rd_en
.dout (tx_data ), // output wire [31 : 0] dout
.full (), // output wire full
.empty () // output wire empty
);
//以太网控制模块
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 ),
.gmii_tx_en (gmii_tx_en ),
.gmii_txd (gmii_txd )
);
endmodule
A7上的资源消耗和性能表现如下:
由于工程中使用到了PLL和fifo,实际UDP部分所占资源更小;
代码架构如下:
5.上板调试验证
工程1验证:
开发板连接
工程2验证:
开发板连接
上位机收发显示如下:
6.福利:工程代码的获取
福利:工程代码的获取
代码太大,无法邮箱发送,以百度网盘链接方式发送,
通过微信获取资料:
资料如下: