在仿真环境使用两个UDP实体实现全栈仿真的方法:实例分析

本文详细介绍了在通信协议仿真中,如何通过状态机分析MAC管理层的运作,特别是在处理ARP请求和响应过程。从接收IP发送请求开始,解析MAC地址,发送ARP请求,到接收并解析ARP回复,最后成功发送和接收UDP数据包。在实体B端,展示了MAC层如何解析UDP包,并将相关信息传递给上层。整个过程中,状态机的每个状态和转换都有清晰的解释,为理解网络协议的底层工作原理提供了深入的洞察。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

书接上篇。

有了上篇分析,这篇主要上图,实现以下这个过程。

仿真运行后,我们要找个起点来观察,一般是从后往前,也就是看结果是否正确,不正确再往前推理找源头。另外一种起点是中最关键的控制部分,分析他的状态转换。

这里我们找到mac_hub这个mac管理层的状态机,


wire arp_in_sending = (s_udp_ip == sending_arp_ip ) && ( sending_time_cntr != ARP_TIMEOUT ) ;

always@(posedge clk)if (rst)st<=0;else case (st)
0:st<=10;
10 : if (arp_reply_valid)st<=100;else st<=200; //if there is a ARP reply to send out .
100,101,102,103:st<=st+1; 
104: if (mac_s_busy==0)st<=111;
111: if (mac_s_busy==1)st<=112;
112: if (mac_s_busy==0)st<=113;
113: st<=200;

200: if (udp_m_ip_pack_valid==0) st<=400;else st<=205;
205: st<=210;
210: if (arp_cache_req_done) st<=(arp_cache_req_hit)?220:300;// with IP find out MAC  //
220: st<=221; //mac_tx_dst_mac  <= arp_cache_mac_out  ;
221: st<=222; //s_udp_tx_start=1 in this state otherwise will be 0 
222: if (mac_s_busy==0)st<=223;
223: if (mac_s_busy==1)st<=224; 
224: if (mac_s_busy==0)st<=225; 

// 3state prevous while connect upd_ip to mac_tx 
225: st<=300;
300: st<=301;//we need send a arp request 
301: st <= (arp_in_sending) ? 313:302; 
302,303,304,305,306,307,308,309:st<=st+1;
310: if (mac_s_busy==0)st<=311;
311: if (mac_s_busy==1)st<=312;
312: if (mac_s_busy==0)st<=313;//record sending ip 
313: st<=314;
314: st<=10; 

400: st<=10;
default st<=0; 
endcase 

 这里结合代码我们看到确实在收到IP发送请求后从arp_cache里面找到对应ip的mac就是发送了一个arp_request请求。

之后我们看以下发送出去的以太网数据包,这里注意长度是66个字节因为mac设置代码填充,不满足64自己的数据包被自动填充上多了0凑足66个字节。

之后我们在实体B端查看接收到的传递给MAC层的数据:

		 reg [7:0] st1   ;
		 always@(posedge clk) if (rst)st1<=0; else case (st1) 
		 0:st1<=10;
		 10: if ( s_valid & s_sof & (s_din == 'h0|| s_din == get_byte5( cfg_my_mac )) ) st1<=11;
		 11: if ( s_din == 'h0 ||  s_din == get_byte4( cfg_my_mac ) )  st1<=12;else st1<=10;
		 12: if ( s_din == 'h0 ||  s_din == get_byte3( cfg_my_mac ) ) st1<=13;else st1<=10;
		 13: if ( s_din == 'h0 ||  s_din == get_byte2( cfg_my_mac ) ) st1<=14;else st1<=10;
		 14: if ( s_din == 'h0 ||  s_din == get_byte1( cfg_my_mac ) ) st1<=15;else st1<=10;
		 15: if ( s_din == 'h0 ||  s_din == get_byte0( cfg_my_mac ) ) st1<=20;else st1<=10;
		 20,21,22,23,24:st1<=st1+1; // skip src mac address
		 25:st1<=30;
		 30:st1<=31;
		 31:case ( {s_din_r,s_din} ) ARP_TYPE : st1<=100; IP_TYPE :st1<=200; default st1<=250;endcase 
		 100, // deal with arp
		 200, // deal with ip (upper layer icmp or udp)
		 250:  if ( s_valid  & s_eof ) st1<=10; 
		 default st1<=0; endcase  
		 

我们看到很显然st转到了100等待状态机st2处理arp包的发送。

 之后我们看st2状态机的运转情况:

		 reg [7:0]  st2 ; // deal with arp   //check if it is my ip ,then 
		 always @ (posedge clk )if (rst)st2<=0; else case (st2) 
		 0:st2<=10;
		 10: if (st1==100&&s_din==0) st2<=14;
		 14: if (s_din == 8'h01) st2 <= 15;else st2<=10; //arp_hdr 
		 15: if (s_din == 8'h08) st2 <= 16;else st2<=10;
		 16: if (s_din == 8'h00) st2 <= 17;else st2<=10; //arp_pro
		 17: if (s_din == 8'h06) st2 <= 18;else st2<=10; //arp_hln
		 18: if (s_din == 8'h04) st2 <= 19;else st2<=10; //apr_pln
		 19: if (s_din == 8'h00) st2 <= 20;else st2<=10; //opt hi==0
		 20: begin st2<=40;  need_reply <=(s_din==1)?1:0 ;end 
		 40: begin arp_cache_mac [5*8+7:5*8] <= s_din;st2<=41 ;end
		 41: begin arp_cache_mac [4*8+7:4*8] <= s_din;st2<=42 ;end
		 42: begin arp_cache_mac [3*8+7:3*8] <= s_din;st2<=43 ;end
		 43: begin arp_cache_mac [2*8+7:2*8] <= s_din;st2<=44 ;end
		 44: begin arp_cache_mac [1*8+7:1*8] <= s_din;st2<=45 ;end
		 45: begin arp_cache_mac [0*8+7:0*8] <= s_din;st2<=46 ;end
		 
		 46: begin arp_cache_ip [3*8+7:3*8] <=s_din ;st2<=47 ;end
		 47: begin arp_cache_ip [2*8+7:2*8] <=s_din ;st2<=48 ;end
		 48: begin arp_cache_ip [1*8+7:1*8] <=s_din ;st2<=49 ;end
		 49: begin arp_cache_ip [0*8+7:0*8] <=s_din ; st2<=50 ;end
		 
		 50,51,52,53,54:st2<=st2+1;
		 55:st2<=60;
		 60: if (  s_din ==  cfg_my_ip[3*8+7:3*8]   )  st2<=st2+1;  else st2<=10;
		 61: if (  s_din ==  cfg_my_ip[2*8+7:2*8]   )  st2<=st2+1;  else st2<=10;
		 62: if (  s_din ==  cfg_my_ip[1*8+7:1*8]   )  st2<=st2+1;  else st2<=10;
		 63: if (  s_din ==  cfg_my_ip[0*8+7:0*8]   )  st2<=66;  else st2<=10; 
		 66: st2<=70; // 
		 70: if (need_reply) st2 <= 71 ;else st2<=10;
		 71: begin need_reply <=0; st2 <= 10 ;end  // wait replay 
		 default st2<=0;
		 endcase 

st2在状态10接棒st1状态机判别到的arp包开始处理。

 之后我们再去实体A看ARP_REPLY包是否收到并正确解析。

我们直接找MAC_RX输出给CACHE的接口看波形:

 至此ARP_REQUEST和ARP_REPLY一个完整过程结束。

我们通过ARP请求MAC地址的目的就是为了发送上述提到了IP包,现在这不就可以发送了。看mac管理层的状态机st.

 这个数据包正确性我们在之前的仿真已经做了校验,这里根据各个字段简单对照没有问题,可以在之后下FPGA板子实际跑遇到问题再抓包分析。

{{aAxvOXMOIvVUoXMxvoxiowMwWV8xxWTxoxOIOVIUUOvwVOUiIoUvvTMMVMwovWHWX8vOUOWm8Wo8VoTOwTOIVTvOmMowxIIiM8IXMMiVIU8UITwVHOMoiHIoVU8VvmvIWXTvvOvv8xvMovOWoVOox8Mi8UmooOTvvwUIoTwvmWUoiTw8VmvoHWwMIUWOixiowiUoiXwiwwMMIiIXHwUmOWUVmXXwV8iHWOTUiwTU8xwOoV8HVmTWZz}}

-----------------------------------------------------------第二天继续写的-------------------------------------------------------

我们再看一下实体B收到这个板子做了怎么样处理。

首先eth_rx收到了以太网线路发来的原始数据流,解析出来MAC层的数据,如下:

 我们看一下在mac_rx中对UDP包接受和解析的状态机代码:



always @ (posedge clk )m_udp_dout<= s_din ;

always @ (posedge clk ) if (st3 ==  34) m_udp_len [15:8]  <=s_din ;
always @ (posedge clk ) if (st3 ==  35) m_udp_len [7:0]  <=s_din ;

always @ (posedge clk ) if (st3 ==12) m_ip_len [15:8]  <=s_din ;
always @ (posedge clk ) if (st3 ==13 ) m_ip_len [7:0]  <=s_din ;

always @ (posedge clk ) if (st3 == 22) m_udp_src_ip[3*8+7:3*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 23) m_udp_src_ip[2*8+7:2*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 24) m_udp_src_ip[1*8+7:1*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 25) m_udp_src_ip[0*8+7:0*8+0] <=s_din ;

always @ (posedge clk ) if (st3 == 26) m_udp_dst_ip[3*8+7:3*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 27) m_udp_dst_ip[2*8+7:2*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 28) m_udp_dst_ip[1*8+7:1*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 29) m_udp_dst_ip[0*8+7:0*8+0] <=s_din ;

always @ (posedge clk ) if (st3 == 30) m_udp_src_port[1*8+7:1*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 31) m_udp_src_port[0*8+7:0*8+0] <=s_din ;

always @ (posedge clk ) if (st3 == 32) m_udp_dst_port[1*8+7:1*8+0] <=s_din ;
always @ (posedge clk ) if (st3 == 33) m_udp_dst_port[0*8+7:0*8+0] <=s_din ;

		 
		 reg [7:0]  st3 ; // deal with ip (UDP or ICMP)
		 always @ (posedge clk )if (rst)st3<=0; else case (st3) 
		  0 : st3<=10;
		  10: if (st1 == 200 && s_din == 'h45 ) st3<=11;
		  11,12,13,14,15,16,17,18 : st3 <= st3+1 ;
		  19 :   if (s_din == 'h11) st3<=20;else st3<=10 ;
		  20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36:st3<=st3+1;
			37:st3<=38;
			38: st3<=40;
			40:if (s_eof) st3<=50;
			50:st3<=60;
			60:st3<=10; 		 		 //输出total_len eof sof以及valid .给udp_rx层
		 default st3<=0; endcase  
		 // m_udp_valid,m_udp_sof,m_udp_eof,m_udp_chksum_ok, 
		 always @ (posedge clk ) if (st3 == 38) m_udp_valid<= 1;else if (s_eof_r ) m_udp_valid<=0;
		 always @(posedge clk)  m_udp_eof <= s_eof ;
		 always @ (posedge clk)  m_udp_sof <= st3 == 38 ;
		 always @(posedge clk)  m_udp_chksum_ok <=1; 

代码的思路和之前一脉相承,m_udp_chksum这里暂时不做处理。

我们直接看解析出来的udp数据包:

这里我们可以看到解析出来收发方IP和端口,

对照我们仿真平台的设置来看都是正确的: 

 按照层次化的网络协议思想,我们在上层是能看到下层的细节,比如我们在实体A的IP层面发送数据包,就不需要知道MAC以及下面的以太网层面进行怎么的添充和校验,在实体B的MAC层接受就是收到了实体A的IP传来的数据格式,以太网地址的请求处理过程在mac层也看不到。按照这个思路UDP层也看不到IP地址的情况,协议中UDP包头里面确实没有IP地址的字段(只有收发PORT,字节长度,校验和这8字节),但是IP地址相当关键,标识着数据是网络节点哪个主机,因此这里我在MAC_RX模块里面解析出来,上层代码可用可不用(不用的画对应的逻辑会被自动优化掉,不产生额外FPGA逻辑资源开销)。

我们可以看到这里完美解析出了传输的UDP数据包,其中前18个字节是实际内容,后面的有多个字节填充。我们在测试平台里面修改要发送的自己数,设置大一些,这样就可以不必在amc_rx中被自动填充了。

产生测试向量的代码如下:

 

wire [15:0] test_len = 32;
 

  
reg [11:0]  c;always @(posedge clk) if (rst) c<=0; else c<=(c==(1000-1))?0:c+1; // system counter for debug  
reg [7:0] st  = 0 ;
reg [7:0] r;

always @(posedge clk) if (rst)st<=0; else  case (st)
0:st<=10;
10:if(s_udp_tx_busy==0)st<=20;
20:st<=30;
30:if(r==test_len)st<=40;
40:st<=10;
default st<=0; 
endcase 

reg s_udp_tx_start=0;
always @(posedge clk)  if (rst){s_udp_tx_start,r}<=0; else case (st)
20:begin  r<=0;s_udp_tx_start <=1; end 
30:begin r<=r+1; s_udp_tx_start <=0; end 
default  begin r<=0;s_udp_tx_start <=0;end  
endcase 

这里我们完美看到收到了32个字节

至此两个实体进行测试的仿真实验完成,接下来就可以下板子运行了。 

ICMP的实现,需要在MAC_RX模块加入对ICMP请求的解析,得到必要信息连同padding传递跟ICMP_REPLY模块,ICMP应答数据包传递给IP层,之后给mac管理层,在mac_hub模块里面排队等待发出。

在发送ICMP请求时,我们的IP地址已经被对方记录,说明之前对方发来过arp请求,这样的话我们也保存过他的ip和mac地址,不需要发送arp请求。但是也有一个例外,就是我们在和对方arp对话过之后,从新上电了,就不会有对方ip以及mac地址了,这时候还是要发送arp请求包:因此这部分在mac_hub被处理的时候还是要考虑arp_request的请求。在发送udp协议时候已经做好了arp_request的发送状态序列,icmp请求引起的arp_request直接设置几个寄存器后转跳到这些执行序列就可以,不会增加很多额外的逻辑。

{{aAoUUvUWw8ooWiUxHOmwoIoivHX8WXIXiOmUmMIVUTxUXwvmOmxWwUmXUUVVUwTmW8xxxmVMwwMHVOoxX8iMxxvwiMTTxUIUiWMXIMHUHT8OHTXWZz}}

----------------------------------------------------------

接下来就要写代码并下板子运行了,撸了一周的代码要跑起来了,还是有点小激动,紧张搓搓小手~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值