基于FPGA的以太网TCP协议的数据回环实验

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、相关概念
    • 1.以太网协议报文
    • 2.TCP报文
  • 二、主要部分的实现
    • 1.tcp_ctrl
      • 1.1建立连接
      • 1.2关闭连接
    • 2.确认应答
    • 3.超时重传
    • 4.发送仲裁
    • 5.数据回环
  • 总结


前言

最近在大佬们的帮助下学习了TCP,并独立实现了TCP的数据回环实验,网上也基本没有FPGA关于TCP实现的记录,所以想打算写这篇文章,也算是对自己所学的总结和梳理。

本文只对TCP/IP理论基础进行简单介绍,主要记录工程的实现思路和方法,仅供参考,欢迎指出错误。


一、相关概念

1.以太网协议报文

基于FPGA的以太网TCP协议的数据回环实验_第1张图片
在这里插入图片描述

想必大家都很了解,所以就直接看看UDP和和TCP报文的区别吧。

2.TCP报文

基于FPGA的以太网TCP协议的数据回环实验_第2张图片
tcp详细报文
基于FPGA的以太网TCP协议的数据回环实验_第3张图片TCP头部

TCP首部没有options字段时为20字节。建立连接和关闭连接主要靠的是报文序列号SEQ number 和确认应答号ACKnumber,分别在SYN位和ACK位为1时有效。

三次握手与四次挥手参考博文:作者:爱前端不爱恋爱。卧槽!牛皮了,头一次见有大佬把TCP/IP三次握手四次挥手解释的这么明白

基于FPGA的以太网TCP协议的数据回环实验_第4张图片
基于FPGA的以太网TCP协议的数据回环实验_第5张图片

URG 紧急标志位,紧急指针有效
ACK 确认序列号有效
PSH 表示尽快将信息推给应用层
RST 重新连接
SYN 连接
FIN 关闭连接
Window Size 可以理解为下一次可以收到数据报文的最大字节数
TCP Checksum 为TCP伪首部 = Source IP+Destination IP + TCP header+TCP DATA的校验和,tcp checksum强制为0,不然就是套娃了

TCP Checksum 计算方法为TCP伪首部16位16位相加,如果结果大于16位继续高16位与低16位相加

二、主要部分的实现

1.tcp_ctrl

基于FPGA的以太网TCP协议的数据回环实验_第6张图片
状态机分为两条主线,一条是FPGA作为服务端的,一条是FPGA作为客户端的。转移条件与三次握手四次挥手条件一样。

1.1建立连接

下面只展示当FPGA作为服务端时的连接与关闭(因为在作为客户机时的关闭,作为服务端的上位机发送的第一条报文的与协议不同,不知是否是上位机bug),在串口软件上点击连接,显示连接成功,wireshark抓到了三条报文如图基于FPGA的以太网TCP协议的数据回环实验_第7张图片
上位机作为客户端首先发送了一条包含初始化序列号的SYN报文,FPGA回复SYN + ACK报文,客户端收到后再通知给服务端,自此连接成功。

1.2关闭连接

在这里插入图片描述
关闭连接四次挥手,最初是FIN报文,最后FPGA状态机也恢复到IDLE 0001,关闭成功。

2.确认应答

  always @ (posedge sys_clk,negedge sys_rst_n)
         if(!sys_rst_n)     begin   
		                     tack     <= 1'b0;
                             tack_seq <= 32'd0;
							 ack_start<= 1'b0;
							end	
	else if(tx_done & tras_ready)	
                            begin   					 
                             tack     <= 1'b0;
                             tack_seq <= 32'd0;
							 ack_start<= 1'b0;
                            end											
	else if(rec_pkt_done& tras_ready)	           //应答包不再次应答
	        if(rec_byte_num  == 32'd0)
                		    begin   					 
                             tack     <= 1'b0;
                             tack_seq <= 32'd0;
							 ack_start<= 1'b0;
                            end		
		else 	            begin   					 
                             tack     <= 1'b1;
                             tack_seq <= rseq_num + rec_byte_num; 
							 ack_start<= 1'b1;
                            end		

应答模块比较简单,应答时ACK位为1,应答号为收到的序列号 + 接收到的字节数,表明下次发送的序列号从这个开始,要注意的是应答包不用应答,所以判断这个包收到的有效数据字节数rec_byte_num是否为0即可。

3.超时重传

因为TCP接收方超时不应答发送方的话,发送方会重传几次。然而fifo读完后数据就不存在了,所以不能用fifo作为发送的缓存,只能用ram,具体代码如下

  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      send_done_flag <= 1'b0;
	else if(time_500ms == CNT_MAX)
                         send_done_flag <= 1'b0;
    else if(ACK)         send_done_flag <= 1'b0;						 
    else if(send_done)   send_done_flag <= 1'b1;
	 
  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      time_500ms <= 32'd0;
    else if(ACK)         time_500ms <= 32'd0;
    else if(re_send)   
	                     time_500ms <= 32'd0;	
    else if(send_done_flag)   
	                     time_500ms <= time_500ms + 1'b1;

  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      re_send <= 1'b0;
    else if(time_500ms == CNT_MAX && re_send_cnt < 4'd5)                
	                     re_send <= 1'b1;
    else                 re_send <= 1'b0;						 

  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      re_send_cnt <= 4'd0;
	else if(ACK)         re_send_cnt <= 4'd0;	 
    else if(re_send)                
	                     re_send_cnt <= re_send_cnt + 1'b1;
    else                 re_send_cnt <= re_send_cnt;

  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      wr_addr <= 9'd0;
	else if(send_done)	 wr_addr <= 9'd0;		 
	else if(wea)	     wr_addr <= wr_addr + 1'b1;
    	
  always @ (posedge clka,negedge rst_n)
         if(!rst_n)      rd_addr <= 9'd0;
	else if(send_done)	 rd_addr <= 9'd0;		 
	else if(enb)	     rd_addr <= rd_addr + 1'b1;

如果发送完0.5s还是没接收应答就会再次发送,直至有应答或者发了四五次,说明需要排查问题了。

4.发送仲裁

always @ (*) begin 
      if(!sys_rst_n)
      next_state = IDLE;
	else 
   	case(cur_state)   	
	    IDLE    :   if(cnt == CNT_MAX)begin 
		             if(tx_start_edge)
                         next_state = TX_START;
                else if(tx_ack_edge)
                         next_state = TX_ACK;
                else if(tx_data_en_edge)
                         next_state = TX_DATA;				
                else     next_state = IDLE; end
				    else next_state = IDLE;                       				
        TX_START:    if(tx_done)
		                 next_state = IDLE; 
                else     next_state = TX_START;  
        TX_ACK  :    if(tx_done)
		                 next_state = IDLE; 
                else     next_state = TX_ACK;  
        TX_DATA :    if(tx_done)
		                 next_state = IDLE; 
                else     next_state = TX_DATA;  
        default :        next_state = IDLE;
    endcase 
end	

TX_START为三握四挥的时候的以太网发送,TX_ACK为应答时的以太网发送,TX_DATA为有效数据时候的发送,可以看出三握四挥的时优先级最高。

5.数据回环

实现上面的几个模块就可以做到数据回环的实现了,当然前提是其他都没问题。
上位机发送数据是默认先应答,再回传。从仿真来看:基于FPGA的以太网TCP协议的数据回环实验_第8张图片
1是建立连接的三个包,2是发送数据,3是发送成功收到的应答包,4是数据回传过来,5是收到数据包的应答,仿真是没问题的。
基于FPGA的以太网TCP协议的数据回环实验_第9张图片
如图上位机发送了一条数据,也收到了数据相同的数据,wireshark也抓到了两条应答包两条数据包。说明回环实验成功了。

总结

以上结束,包括学习和实现断断续续用了一个月,调试了也花了许多时间,还是感觉相当不易,虽然只做了tcp的一些基础内容,但是互相通信也没有问题,今后有机会再好好完善吧,TCP/ip毕竟是个很复杂的东西,硬件全部实现也不可能,不然FPGA关于tcp这方面的东西也不会这么少。

你可能感兴趣的:(fpga开发,tcp/ip,tcp,udp)