正点原子 发表于 2023-3-13 17:22:42

《ATK-DFPGL22G之FPGA开发指南_V1.0》第五十二章以太网ICMP测试实验

本帖最后由 正点原子 于 2023-3-13 17:22 编辑

1)实验平台:正点原子紫光PGL22G开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340253-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:435699340




第五十二章以太网ICMP测试实验
ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP使用IP的基本支持,就像它是一个更高级别的协议,但是,ICMP实际上是IP的一个组成部分,必须由每个IP模块实现。
52.1简介
52.2实验任务
52.3硬件设计
52.4程序设计
52.5下载验证

52.1简介
ICMP概述
ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。它属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。
ICMP是TCP/IP模型中网络层的重要成员,与IP协议、ARP协议、RARP协议及IGMP协议共同构成TCP/IP模型中的网络层。ping和tracert是两个常用网络管理命令,ping用来测试网络可达性,tracert 用来显示到达目的主机的路径。ping和tracert都利用ICMP协议来实现网络功能,它们是把网络协议应用到日常网络管理的典型实例。
从技术角度来说,ICMP就是一个“错误侦测与回报机制”,其目的就是让我们能够检测网路的连线状况﹐也能确保连线的准确性。当路由器在处理一个数据包的过程中发生了意外,可以通过ICMP向数据包的源端报告有关事件。
其功能主要有:侦测远端主机是否存在,建立及维护路由资料,重导资料传送路径(ICMP重定向),资料流量控制。ICMP在沟通之中,主要是透过不同的类别(Type)与代码(Code)让机器来识别不同的连线状况。
ICMP是个非常有用的协议﹐尤其是当我们要对网路连接状况进行判断的时候。
以太网 ICMP传输单包数据的格式如下图所示。从图中可以看出,以太网的数据包就是对各层协议的逐层封装来实现数据的传输。用户数据打包在ICMP协议中,ICMP协议又是基于IP协议之上的,IP 协议又是走MAC层发送的,即从包含关系来说:MAC帧中的数据段为IP数据报,IP报文中的数据段为ICMP报文,ICMP报文中的数据段为用户希望传输的数据内容。接下来我们逐个来向大家介绍不同层的数据格式。
图 52.1.1 以太网ICMP传输数据包格式
其中以太网的帧格式在“以太网ARP测试实验”中已经向大家作了详细的介绍,如果对以太网帧格式不熟悉的话,可以参考“以太网ARP测试实验”。IP协议(互联网分组交换协议)是TCP/IP协议簇中非常重要的一个协议,在“以太网UDP测试实验”中已经向大家作了详细的介绍,如果对IP协议不熟悉的话,可以参考“以太网UDP测试实验”中关于IP协议的介绍。
ICMP协议
ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面,所以一个ICMP报文包括IP头部、ICMP头部和ICMP报文,IP头部的Protocol值为1就说明这是一个ICMP报文,如下图所示,ICMP头部中的类型(Type)域用于说明ICMP报文的作用及格式,此外还有一个代码(Code)域用于详细说明某种ICMP报文的类型,所有数据都在ICMP头部后面。
ICMP报文格式具体可以阅读RFC 777,RFC 792 规范。
图 52.1.2 一帧ICMP报文
ICMP数据格式如下图所示:
图 52.1.3 ICMP数据格式
ICMP首部共8个字节,同IP首部一样,也是一行以32位(4个字节)为单位。
类型(type):占用了8 bit位,前面我们说,是ICMP报文类型,用于标识错误类型的差错报文或者查询类型的报告报文。
代码(code):占用了8 bit位,根据ICMP差错报文的类型,进一步分析错误的原因,代码值不同对应的错误也不同,例如:类型为11且代码为0,表示数据传输过程中超时了,超时的具体原因是TTL值为0,数据报被丢弃。
校验和(checksum):占用了16 bit位,数据发送到目的地后需要对ICMP数据报文做一个校验,用于检查数据报文是否有错误。
标识符(Identifier):占用了16 bit位,对于每一个发送的数据报进行标识
序列号(Sequence number):占用了16 bit位,对于发送的每一个数据报文进行编号,比如:发送的第一个数据报序列号为1,第二个序列号为2。
数据(Data):要发送的ICMP数据。
以ICMP请求报文为例,我们来看一下ICMP请求报文的封装格式:图 52.1.4 icmp请求报文的封装格式
这是我们刚才通过ping命令抓的ICMP协议包,其中request是ICMP请求数据报,reply是ICMP回答数据报,另外request和reply是一组ICMP请求回答数据报。
图 52.1.5 ICMP请求回答数据报
我们再针对一组ICMP请求回答数据报分析两个ICMP数据报是否为一组。Type的值为8'h08是请求类型报文如下左图所示,Type的值为8'h00是应答类型报文如下右图所示,序列号(Sequence number)一致是同一组ICMP请求回答数据报。
图 52.1.6 icmp的request和reply
下面我们来看一下,我们在用ping命令发送的ping包携带的是什么数据。
图 52.1.7 icmp数据部分
Data就是刚才ping命令所发送的ICMP数据报文里的数据部分,这些数据是ping命令发送的测试内容,左侧部分是以十六进制表示,右侧部分就是我们所发送的数据部分,这些数据长度正好是32字节,一个字母代表一个字节。
52.2实验任务
本节实验任务是电脑通过命令行窗口发送ping命令给FPGA,FPGA通过以太网接口接收数据并将接收到的数据发送给电脑,完成以电脑ping开发板的实验测试。
52.3硬件设计
千兆以太网接口部分的硬件设计原理及本实验中各端口信号的管脚分配,和“以太网ARP测试实验”完全相同,请参考“以太网ARP读写测试实验”中的硬件设计部分。
52.4程序设计
是根据本章实验任务画出的系统框图。和“以太网UDP测试实验”相比,本实验只有替换UDP顶层模块。本次实验虽然实现的是ICMP通信,但保留了ARP顶层模块,这是由于上位机应用程序只知道接收端的目的IP地址和端口号,却不知道接收端的MAC地址,因此这里通过ARP协议来获取接收端的MAC地址,否则需要在发送端手动绑定接收端MAC地址,而手动绑定的方法较为繁琐,因此这里保留了ARP协议。
本次实验同时实现了ARP协议和ICMP协议,GMII接收侧的引脚同时连接至ARP顶层模块和ICMP顶层模块,这个两个模块会分别根据ARP协议和ICMP协议解析数据。而GMII发送侧引脚只能和ARP顶层模块和ICMP顶层模块的其中一个连接,因此以太网控制模块会根据当前接收到的协议类型,选择切换GMII发送侧引脚和ARP顶层模块或者ICMP顶层模块连接。除此之外,以太网控制模块根据输入的ARP接收的类型,控制ARP顶层模块返回ARP应答信号。
图 52.4.1以太网ICMP测试系统框图
由上图可知,FPGA顶层模块例化了以下五个模块, GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)、ICMP顶层模块(icmp)、同步FIFO模块(sync_fifo_2048x32b)和以太网控制模块(eth_ctrl),实现了各模块之间的数据交互。
其中GMII TO RGMII(gmii_to_rgmii)模块和ARP顶层模块(arp)在“以太网ARP测试实验”中已经向大家作了详细的介绍,如果大家对这部分内容不熟悉的话,可以参考“以太网ARP测试实验”。
本章我们重点介绍ICMP顶层模块(icmp),ICMP顶层模块实现了整个以太网帧格式与ICMP协议的功能。
ICMP顶层模块例化了ICMP接收模块(icmp_rx)、ICMP发送模块(icmp_tx)和CRC校验模块(crc32_d8)。
ICMP接收模块(icmp_rx):ICMP接收模块较为简单,因为我们不需要对数据做IP首部校验也不需要做CRC循环冗余校验,只需要判断目的MAC地址与开发板MAC地址、目的IP地址与开发板IP地址是否一致即可。接收模块的解析顺序是:前导码+帧起始界定符→以太网帧头→IP首部→ICMP首部→ICMP数据(有效数据)→接收结束。IP数据报一般以32bit为单位,为了和IP数据报格式保持一致,所以要把8位数据转成32位数据,因此接收模块实际上是完成了8位数据转32位数据的功能。
ICMP发送模块(icmp_tx):ICMP发送模块和接收模块比较类似,但是多了IP首部校验和和CRC循环冗余校验的计算。CRC的校验并不是在发送模块完成,而是在CRC校验模块(crc32_d8)里完成的。发送模块的发送顺序是前导码+帧起始界定符→以太网帧头→IP首部→ICMP首部→ICMP数据(有效数据)→CRC校验。输入的有效数据为32位数据,GMII接口为8位数据接口,因此发送模块实际上完成的是32位数据转8位数据的功能。
CRC校验模块(crc32_d8):CRC校验模块是对ICMP发送模块的数据(不包括前导码和帧起始界定符)做校验,把校验结果值拼在以太网帧格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的)。CRC32校验在FPGA实现的原理是LFSR(Linear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。
其中CRC校验模块和ARP模块例化的校验模块完全相同,这里我们重点介绍ICMP接收模块和ICMP发送模块。
ICMP接收模块按照ICMP的数据格式解析数据,并实现将8位用户数据转成32位数据的功能。由ICMP的数据格式可知,解析ICMP数据很适合使用状态机来实现,下图为ICMP接收模块的状态跳转图。
图 52.4.2 ICMP接收模块的状态跳转图
接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、MAC地址错误以及IP地址错误时跳转到st_rx_end状态而不是跳转到st_idle状态。因为中间状态在解析到数据错误时,单包数据的接收还没有结束,如果此时跳转到st_idle状态会误把有效数据当成前导码来解析,所以状态跳转到st_rx_end。而eth_rxdv信号为0时,单包数据才算接收结束,所以st_rx_end跳转到st_idle的条件是eth_rxdv=0,准备接收下一包数据。因为代码较长,只粘贴了第三段状态机的接收数据状态和接收结束状态源代码,代码如下:
282 st_rx_data : begin         
283   //接收数据,转换成32bit            
284   if(gmii_rx_dv) begin
285         rec_en_cnt <= rec_en_cnt + 2'd1;
286         icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
287         if(rec_en_cnt == 2'd0)
288               rec_data <= gmii_rxd;
289             else if(rec_en_cnt == 2'd1)
290               rec_data <= gmii_rxd;
291             else if(rec_en_cnt == 2'd2)
292               rec_data <= gmii_rxd;      
293             else if(rec_en_cnt==2'd3) begin
294               rec_en <= 1'b1;
295               rec_data <= gmii_rxd;
296             end
297         if(icmp_rx_cnt < icmp_data_length) begin
298             icmp_rx_data_d0 <= gmii_rxd;
299             icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
300             if (icmp_rx_cnt == 1'b1)
301               reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd}
302                                    + reply_checksum_add;
303             else
304               reply_checksum_add <= reply_checksum_add;
305         end
306         else if (icmp_rx_cnt == icmp_data_length) begin
307             icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
308             icmp_rx_data_d0 <= 8'h00;
309             if(icmp_rx_cnt == 1'b1)
310               reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd}
311                                    + reply_checksum_add;
312             else
313               reply_checksum_add <= reply_checksum_add;
314         end
315         if(icmp_rx_cnt == icmp_data_length - 16'd1) begin
316             skip_en <= 1'b1;                  //有效数据接收完成
317             icmp_rx_cnt <= 16'd0;
318             rec_en_cnt <= 2'd0;
319             rec_pkt_done <= 1'b1;               
320             rec_en <= 1'b1;
321             rec_byte_num <= icmp_data_length;
322         end
323   end
324 end
325 st_rx_end : begin                               //单包数据接收完成
326   if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)begin
327         reply_checksum <= reply_checksum_add ;
328         skip_en <= 1'b1;
329         reply_checksum_add <= 32'd0;
330   end
st_rx_data状态表示接收ICMP的有效数据,程序中的287~296行代码将接收的8位数据转换位32位数据,297~314行代码是将接收的相邻两个8位数据拼接成一个16位数据,并将拼接的16位数据进行累加得都到一个32位的累加和reply_checksum_add。在接收完有效数据后,拉高rec_pkt_done(单包有效数据接收完成)信号,如程序中第319行代码所示。
Icmp的仿真代码如下图所示:
1   moduletb_icmp;
2   
3   //parameterdefine
4   parameterT = 8;                     //时钟周期为8ns
5   parameterOP_CYCLE = 100;            //操作周期(发送周期间隔)
6   
7   //开发板MAC地址 00-11-22-33-44-55
8   parameterBOARD_MAC = 48'h00_11_22_33_44_55;   
9   //开发板IP地址 192.168.1.10   
10parameterBOARD_IP= {8'd192,8'd168,8'd1,8'd10};
11//目的MAC地址 ff_ff_ff_ff_ff_ff
12parameterDES_MAC   = 48'hff_ff_ff_ff_ff_ff;
13//目的IP地址 192.168.1.10
14parameterDES_IP    = {8'd192,8'd168,8'd1,8'd10};
15
16//reg define
17reg         gmii_clk;    //时钟信号
18reg         sys_rst_n;   //复位信号
19
20reg         tx_start_en;
21reg   tx_data    ;
22reg   tx_byte_num;
23reg   des_mac    ;
24reg   des_ip   ;
25
26reg      flow_cnt   ;
27reg   delay_cnt;
28
29wire          gmii_rx_clk; //GMII接收时钟
30wire          gmii_rx_dv ; //GMII接收数据有效信号
31wire   gmii_rxd   ; //GMII接收数据
32wire          gmii_tx_clk; //GMII发送时钟
33wire          gmii_tx_en ; //GMII发送数据使能信号
34wire   gmii_txd   ; //GMII发送数据
35               
36wire          tx_done    ;
37wire          tx_req   ;
38
39//*****************************************************
40//**                  main code
41//*****************************************************
42
43assign gmii_rx_clk = gmii_clk   ;
44assign gmii_tx_clk = gmii_clk   ;
45assign gmii_rx_dv= gmii_tx_en ;
46assign gmii_rxd    = gmii_txd   ;
47
48//给输入信号初始值
49initial begin
50      gmii_clk         = 1'b0;
51      sys_rst_n          = 1'b0;   //复位
52      #(T+1)sys_rst_n= 1'b1;   //在第(T+1)ns的时候复位信号信号拉高
53end
54
55//125Mhz的时钟,周期则为1/125Mhz=8ns,所以每4ns,电平取反一次
56always #(T/2) gmii_clk = ~gmii_clk;
57
58always @(posedge gmii_clk or negedge sys_rst_n) begin
59      if(!sys_rst_n) begin
60          tx_start_en <= 1'b0;
61          tx_data <= 32'h_00_00_00_00;
62          tx_byte_num <= 1'b0;
63          des_mac <= 1'b0;
64          des_ip <= 1'b0;
65          delay_cnt <= 1'b0;
66          flow_cnt <= 1'b0;
67      end
68      else begin
69          case(flow_cnt)
70            'd0 : flow_cnt <= flow_cnt + 1'b1;
71            'd1 : begin
72                  tx_start_en <= 1'b1;//拉高开始发送使能信号
73                  tx_byte_num <= 16'd20;//设置发送的字节数
74                  flow_cnt <= flow_cnt + 1'b1;
75            end
76            'd2 : begin
77                  tx_start_en <= 1'b0;
78                  flow_cnt <= flow_cnt + 1'b1;
79            end   
80            'd3 : begin
81                  if(tx_req)
82                      tx_data <= tx_data ;
83                  if(tx_done) begin
84                      flow_cnt <= flow_cnt + 1'b1;
85                      tx_data <= 32'h_00_00_00_00;
86                  end   
87            end
88            'd4 : begin
89                  delay_cnt <= delay_cnt + 1'b1;
90                  if(delay_cnt == OP_CYCLE - 1'b1)
91                      flow_cnt <= flow_cnt + 1'b1;
92            end
93            'd5 : begin
94                  tx_start_en <= 1'b1;//拉高开始发送使能信号
95                  tx_byte_num <= 16'd28;//设置发送的字节数
96                  flow_cnt <= flow_cnt + 1'b1;               
97            end
98            'd6 : begin
99                  tx_start_en <= 1'b0;
100               flow_cnt <= flow_cnt + 1'b1;
101             end
102             'd7 : begin
103               if(tx_req)
104                     tx_data <= tx_data;
105               if(tx_done) begin
106                     flow_cnt <= flow_cnt + 1'b1;
107                     tx_data <= 32'h_00_00_00_00;
108               end
109             end
110             default:;
111         endcase   
112   end
113 end
114
115 //例化ICMP模块
116 icmp
117    #(
118   .BOARD_MAC   (BOARD_MAC),      //参数例化
119   .BOARD_IP      (BOARD_IP ),
120   .DES_MAC       (DES_MAC),
121   .DES_IP      (DES_IP   )
122   )
123    u_icmp(
124   .rst_n         (sys_rst_n   ),
125   
126   .gmii_rx_clk   (gmii_rx_clk ),
127   .gmii_rx_dv    (gmii_rx_dv),
128   .gmii_rxd      (gmii_rxd    ),
129   .gmii_tx_clk   (gmii_tx_clk ),
130   .gmii_tx_en    (gmii_tx_en),
131   .gmii_txd      (gmii_txd),
132   
133   .rec_pkt_done(),
134   .rec_en      (),
135   .rec_data      (),
136   .rec_byte_num(),
137   .tx_start_en   (tx_start_en ),
138   .tx_data       (tx_data   ),
139   .tx_byte_num   (tx_byte_num ),
140   .des_mac       (des_mac   ),
141   .des_ip      (des_ip      ),
142   .tx_done       (tx_done   ),
143   .tx_req      (tx_req      )
144   );
145
146 endmodule
在仿真icmp的接收过程中需要将icmp_tx.v模块修改为开发板发送请求模块,既将报文类型进行如下修改即可:
//ICMP报文类型:回显应答
//localparam ECHO_REPLY   = 8'h00;
localparam ECHO_REPLY   = 8'h08;   //用于仿真
图 52.4.3为接收过程中的仿真波形图。图中gmii_rx_dv和gmii_rxd为GMII接口的接收有效信号和数据,rec_byte_num为发送的有效数据的个数。每次单包数据接收完成都会产生rec_pkt_done信号,rec_en和rec_data为收到的数据有效信号和32位数据。
图 52.4.3 icmp接收的波形图
ICMP发送模块按照ICMP的数据格式发送数据,并将32位用户数据转成8位数据的功能,也就是接收模块的逆过程。同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
图 52.4.4 ICMP发送模块的状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储以太网的帧头、IP首部以及ICMP的首部,在复位时初始化数组的值,部分源代码如下。
57reg   preamble       ; //前导码
58reg   eth_head      ; //以太网首部
59regip_head      ; //IP首部 + ICMP首部
省略部分代码……
201         //初始化数组   
202         //前导码 7个8'h55 + 1个8'hd5
203         preamble <= 8'h55;
204         preamble <= 8'h55;
205         preamble <= 8'h55;
206         preamble <= 8'h55;
207         preamble <= 8'h55;
208         preamble <= 8'h55;
209         preamble <= 8'h55;
210         preamble <= 8'hd5;
211         //目的MAC地址
212         eth_head <= DES_MAC;
213         eth_head <= DES_MAC;
214         eth_head <= DES_MAC;
215         eth_head <= DES_MAC;
216         eth_head <= DES_MAC;
217         eth_head <= DES_MAC;
218         //源MAC地址
219         eth_head <= BOARD_MAC;
220         eth_head <= BOARD_MAC;
221         eth_head <= BOARD_MAC;
222         eth_head <= BOARD_MAC;
223         eth_head <= BOARD_MAC;
224         eth_head <= BOARD_MAC;
225         //以太网类型
226         eth_head <= ETH_TYPE;
227         eth_head <= ETH_TYPE;
以上代码在复位时对数组进行初始化。
236 st_idle   : begin
237   if(trig_tx_en) begin
238         skip_en <= 1'b1;
239         //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
240         ip_head <= {8'h45,8'h00,total_num};
241         //16位标识,每次发送累加1      
242         ip_head <= ip_head + 1'b1;
243         //bit: 010表示不分片
244         ip_head <= 16'h4000;
245         //8'h80:表示生存时间
246         //8'd01:1代表ICMP,2代表IGMP,6代表TCP,17代表UDP
247         ip_head <= {8'h80,8'd01,16'h0000};
248         //源IP地址               
249         ip_head <= BOARD_IP;
250         //目的IP地址   
251         if(des_ip != 32'd0)
252             ip_head <= des_ip;
253         else
254             ip_head <= DES_IP;
255         // 8位icmp TYPE ,8位 icmp CODE
256         ip_head <= {ECHO_REPLY,8'h00};
257         //16位identifier 16位sequence
258         ip_head <= {icmp_id,icmp_seq};
259         //更新MAC地址
260         if(des_mac != 48'b0) begin
261             //目的MAC地址
262             eth_head <= des_mac;
263             eth_head <= des_mac;
264             eth_head <= des_mac;
265             eth_head <= des_mac;
266             eth_head <= des_mac;
267             eth_head <= des_mac;
268         end
269   end
270 end
在程序的第240行至258行代码,为IP首部数组进行赋值。
291 st_check_icmp: begin                           //ICMP首部+数据校验
292   cnt <= cnt + 5'd1;
293   if(cnt == 5'd0) begin
294         check_buffer_icmp <= ip_head //首部中的类型与代码拼接成一个16位数据
295                         + ip_head + ip_head//标识符+序列号
296                         + reply_checksum;//ICMP数据相邻8位数据拼成一个16位再累加的和
297   end
298   else if(cnt == 5'd1)                      //可能出现进位,累加一次
299         check_buffer_icmp <= check_buffer_icmp + check_buffer_icmp;
300   else if(cnt == 5'd2) begin                //可能再次出现进位,累加一次
301         check_buffer_icmp <= check_buffer_icmp + check_buffer_icmp;
302   end                           
303   else if(cnt == 5'd3) begin                //按位取反
304         skip_en <= 1'b1;
305         cnt <= 5'd0;
306         // ICMP:16位校验和
307         ip_head <= ~check_buffer_icmp;
308   end
309 end
291~309行代码是将ICMP首部与数据进行加法运算,298~302行代码是消除加法运算的进位,307行代码给需要发送的icmp首部校验和赋值。
356 st_tx_data: begin                     //发送数据
357   crc_en <= 1'b1;
358   gmii_tx_en <= 1'b1;
359   tx_bit_sel <= tx_bit_sel + 3'd1;
360   if(data_cnt < tx_data_num - 16'd1)
361         data_cnt <= data_cnt + 16'd1;
362   else if(data_cnt == tx_data_num - 16'd1)begin
363         //如果发送的有效数据少于18个字节,在后面填补充位
364         //补充的值为最后一次发送的有效数据
365         gmii_txd <= 8'd0;
366         if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
367             real_add_cnt <= real_add_cnt + 5'd1;
368         else begin
369             skip_en <= 1'b1;
370             data_cnt <= 16'd0;
371             real_add_cnt <= 5'd0;
372             tx_bit_sel <= 3'd0;
373         end   
374   end
375   if(tx_bit_sel == 1'b0)
376         gmii_txd <= tx_data;
377   else if(tx_bit_sel == 3'd1)
378         gmii_txd <= tx_data;
379   else if(tx_bit_sel == 3'd2) begin
380         gmii_txd <= tx_data;   
381         if(data_cnt != tx_data_num - 16'd1)
382             tx_req <= 1'b1;
383   end
384   else if(tx_bit_sel == 3'd3)
385         gmii_txd <= tx_data;
386 end
程序第356行至386行代码为发送ICMP数据段的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,去掉IP首部字节和ICMP首部字节后,有效数据至少为18个字节,程序设计中已经考虑到这种情况,当发送的有效数据少于18个字节时,会在有效数据后面发送补充位,填充的数据为0。
387 st_crc      : begin                        //发送CRC校验值
388   gmii_tx_en <= 1'b1;
389   tx_bit_sel <= tx_bit_sel + 3'd1;
390   if(tx_bit_sel == 3'd0)
391         gmii_txd <= {~crc_next, ~crc_next, ~crc_next,~crc_next,
392                      ~crc_next, ~crc_next, ~crc_next,~crc_next};
393   else if(tx_bit_sel == 3'd1)
394         gmii_txd <= {~crc_data, ~crc_data, ~crc_data,~crc_data,
395                      ~crc_data, ~crc_data, ~crc_data,~crc_data};
396   else if(tx_bit_sel == 3'd2) begin
397         gmii_txd <= {~crc_data, ~crc_data, ~crc_data,~crc_data,
398                      ~crc_data, ~crc_data, ~crc_data,~crc_data};
399   end
400   else if(tx_bit_sel == 3'd3) begin
401         gmii_txd <= {~crc_data, ~crc_data, ~crc_data,~crc_data,
402                      ~crc_data, ~crc_data, ~crc_data,~crc_data};
403         tx_done_t <= 1'b1;
404         skip_en <= 1'b1;
405   end
406 end
程序的第387行至406行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d4模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去。
图 52.4.5为发送过程中的仿真波形图,图中tx_start_en作为开始发送的启动信号,gmii_tx_en和gmii_txd为GMII接口的发送接口。在开始发送以太网帧头时crc_en拉高,开始CRC校验的计算,在将要发送有效数据时拉高tx_req(发送数据请求)信号,tx_data即为待发送的有效数据,在所有数据发送完成后输出tx_done(发送完成)信号和crc_clr(CRC校验值复位)信号。
图 52.4.5 ICMP发送仿真的波形图
以太网控制模块的代码如下:
1module eth_ctrl(
2      input            clk       ,      //系统时钟
3      input            rst_n   ,      //系统复位信号,低电平有效
4      //ARP相关端口信号                                    
5      input            arp_rx_done,   //ARP接收完成信号
6      input            arp_rx_type,   //ARP接收类型 0:请求1:应答
7      outputreg      arp_tx_en,       //ARP发送使能信号
8      output             arp_tx_type,   //ARP发送类型 0:请求1:应答
9      input            arp_tx_done,   //ARP发送完成信号
10   input            arp_gmii_tx_en,//ARP GMII输出数据有效信号
11   input       arp_gmii_txd,    //ARP GMII输出数据
12   //ICMP相关端口信号
13   input            icmp_tx_start_en,//ICMP开始发送信号
14   input            icmp_tx_done,    //ICMP发送完成信号
15   input            icmp_gmii_tx_en, //ICMP GMII输出数据有效信号
16   input       icmp_gmii_txd,   //ICMP GMII输出数据   
17   //GMII发送引脚                     
18   output             gmii_tx_en,      //GMII输出数据有效信号
19   output        gmii_txd         //ICMP GMII输出数据
20   );
21
22 //reg define
23 reg      protocol_sw; //协议切换信号
24 reg      icmp_tx_busy; //ICMP正在发送数据标志信号
25 reg      arp_rx_flag; //接收到ARP请求信号的标志
26
27 //*****************************************************
28 //**                  main code
29 //*****************************************************
30
31 assign arp_tx_type = 1'b1;   //ARP发送类型固定为ARP应答                                 
32 assign gmii_tx_en = protocol_sw ? icmp_gmii_tx_en : arp_gmii_tx_en;
33 assign gmii_txd = protocol_sw ? icmp_gmii_txd : arp_gmii_txd;
34
35 //控制ICMP发送忙信号
36 always @(posedge clk or negedge rst_n) begin
37   if(!rst_n)
38         icmp_tx_busy <= 1'b0;
39   else if(icmp_tx_start_en)   
40         icmp_tx_busy <= 1'b1;
41   else if(icmp_tx_done)
42         icmp_tx_busy <= 1'b0;
43 end
44
45 //控制接收到ARP请求信号的标志
46 always @(posedge clk or negedge rst_n) begin
47   if(!rst_n)
48         arp_rx_flag <= 1'b0;
49   else if(arp_rx_done && (arp_rx_type == 1'b0))   
50         arp_rx_flag <= 1'b1;
51   else if(protocol_sw == 1'b0)
52         arp_rx_flag <= 1'b0;
53 end
54
55 //控制protocol_sw和arp_tx_en信号
56 always @(posedge clk or negedge rst_n) begin
57   if(!rst_n) begin
58         protocol_sw <= 1'b0;
59         arp_tx_en <= 1'b0;
60   end
61   else begin
62         arp_tx_en <= 1'b0;
63         if(icmp_tx_start_en)
64             protocol_sw <= 1'b1;
65         else if(arp_rx_flag && (icmp_tx_busy == 1'b0)) begin
66             protocol_sw <= 1'b0;
67             arp_tx_en <= 1'b1;
68         end   
69   end      
70 end
71
72 endmodule
以太网控制模块的代码较简单,如果输入的arp_rx_done(ARP接收完成信号)为高电平,且arp_rx_type为低电平(ARP接收类型为请求)时,表示接收到ARP请求数据包,此时拉高arp_rx_flag信号;当arp_rx_flag为高电平,且icmp_tx_busy(当前ICMP发送模块处于空闲状态)信号为低电平时,此时拉高arp_tx_en信号,开始控制ARP顶层模块发送ARP应答数据包,并拉低protocol_sw信号,此时GMII发送端口信号和ARP顶层模块的发送端口信号相连。
当protocol_sw等于1时,GMII发送引脚和ICMP GMII发送引脚相连,否则和ARP GMII发送引脚相连,如程序中第32行第33行代码所示。
52.5下载验证
编译工程并生成比特流.sbit文件后,此时将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,网口的位置如下图所示。
图 52.5.1 网口位置
点击PDS工具栏的下载按钮,在弹出的Fabric Configuration界面中双击“Boundary Scan”,我们将生成好的sbit流文件下载到开发板中去。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
图 52.5.2 点击网络图标
接下来就可以打开电脑命令提示符框就可以执行ping命令了,下图所示:
图 52.5.3 电脑命令提示符框
输入“ping 192.168.1.10”命令如下图所示打印如下信息表示电脑ping开发板成功。
图 52.5.4 ping命令输出信息
从上图可以看到开发板正常回复,电脑ping开发板成功。
接下来通过Wireshark软件抓取网口的数据包,界面如下图所示:
图 52.5.5 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
图 52.5.6 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候重新在电脑命令提示符框中发送“ping 192.168.1.10”命令,可以看到Wireshark软件中抓取的数据,如下图所示。
图 52.5.7 wireshark以太网抓取到的数据包
上图中第47行是上位机发送的ICMP请求数据包,第48行是开发板返回的ICMP应答数据包。点击开发板返回的数据包,可以看到开发板发送的详细数据,如下图所示:
图 52.5.8 Wireshark抓取到的详细数据
由上图可知,通信协议是ICMP协议,源IP地址(开发板IP地址)为192.168.1.10,目的IP地址(电脑IP地址)为192.168.1.102。上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,ICMP的用户数据段对应的ASIC码为“abcdefghijklmn opqrstuvwabcdefg hi”。
页: [1]
查看完整版本: 《ATK-DFPGL22G之FPGA开发指南_V1.0》第五十二章以太网ICMP测试实验