OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 999|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第五十二章 以太网ICMP测试实验

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4666
金钱
4666
注册时间
2019-5-8
在线时间
1224 小时
发表于 2024-1-6 18:04:40 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2024-1-5 17:53 编辑

第五十二章 以太网ICMP测试实验

1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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

1.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报文中的数据段为用户希望传输的数据内容。接下来我们逐个来向大家介绍不同层的数据格式。                              
image001.png
图 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 777RFC 792 规范。
image003.png
图 52.1.2 一帧ICMP报文
ICMP数据格式如下图所示:
image005.png
图 52.1.3 ICMP数据格式
ICMP首部共8个字节,同IP首部一样,也是一行以32位(4个字节)为单位。
类型(type):占用了8bit位,前面我们说,是ICMP报文类型,用于标识错误类型的差错报文或者查询类型的报告报文。
代码(code):占用了8bit位,根据ICMP差错报文的类型,进一步分析错误的原因,代码值不同对应的错误也不同,例如:类型为11且代码为0,表示数据传输过程中超时了,超时的具体原因是TTL值为0,数据报被丢弃。
校验和(checksum):占用了16bit位,数据发送到目的地后需要对ICMP数据报文做一个校验,用于检查数据报文是否有错误。
标识符(Identifier):占用了16bit位,对于每一个发送的数据报进行标识
序列号(Sequencenumber):占用了16 bit位,对于发送的每一个数据报文进行编号,比如:发送的第一个数据报序列号为1,第二个序列号为2。
数据(Data):要发送的ICMP数据。
以ICMP请求报文为例,我们来看一下ICMP请求报文的封装格式:
image007.png
图 52.1.4 icmp请求报文的封装格式
这是我们刚才通过ping命令抓的ICMP协议包,其中request是ICMP请求数据报,reply是ICMP回答数据报,另外request和reply是一组ICMP请求回答数据报。
image009.png
图 52.1.5 ICMP请求回答数据报
我们再针对一组ICMP请求回答数据报分析两个ICMP数据报是否为一组。Type的值为8'h08是请求类型报文如下左图所示,Type的值为8'h00是应答类型报文如下右图所示,序列号(Sequence number)一致是同一组ICMP请求回答数据报。
image011.png       image013.png
图 52.1.6 icmp的request和reply
下面我们来看一下,我们在用ping命令发送的ping包携带的是什么数据。
image015.png
图 52.1.7 icmp数据部分
Data就是刚才ping命令所发送的ICMP数据报文里的数据部分,这些数据是ping命令发送的测试内容,左侧部分是以十六进制表示,右侧部分就是我们所发送的数据部分,这些数据长度正好是32字节,一个字母代表一个字节。

1.2 实验任务
本节实验任务是电脑通过命令行窗口发送ping命令给FPGA,FPGA通过以太网接口接收数据并将接收到的数据发送给电脑,完成以电脑ping开发板的实验测试。

1.3 硬件设计
千兆以太网接口部分的硬件设计原理及本实验中各端口信号的管脚分配,和“以太网ARP测试实验”完全相同,请参考“以太网ARP读写测试实验”中的硬件设计部分。

1.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应答信号。
image016.png
图 52.4.1  以太网ICMP测试系统框图
由上图可知,FPGA顶层模块例化了以下五个模块, GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)、ICMP顶层模块(icmp)、同步FIFO模块(sync_fifo_2048x32b)和以太网控制模块(eth_ctrl),实现了各模块之间的数据交互。
其中GMII TORGMII(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接收模块的状态跳转图。
image018.png
图 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,准备接收下一包数据。因为代码较长,只粘贴了第三段状态机的接收数据状态和接收结束状态源代码,代码如下:
  1. 282 st_rx_data : begin         
  2. 283     //接收数据,转换成32bit            
  3. 284     if(gmii_rx_dv) begin
  4. 285         rec_en_cnt <= rec_en_cnt + 2'd1;
  5. 286         icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
  6. 287         if(rec_en_cnt == 2'd0)
  7. 288                 rec_data[31:24 <= gmii_rxd;
  8. 289             else if(rec_en_cnt == 2'd1)
  9. 290                 rec_data[23:16 <= gmii_rxd;
  10. 291             else if(rec_en_cnt == 2'd2)
  11. 292                 rec_data[15:8 <= gmii_rxd;        
  12. 293             else if(rec_en_cnt==2'd3) begin
  13. 294                 rec_en <= 1'b1;
  14. 295                 rec_data[7:0 <= gmii_rxd;
  15. 296             end
  16. 297         if(icmp_rx_cnt < icmp_data_length) begin
  17. 298             icmp_rx_data_d0 <= gmii_rxd;
  18. 299             icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
  19. 300             if (icmp_rx_cnt[0 == 1'b1)
  20. 301                 reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd}
  21. 302                                    + reply_checksum_add;
  22. 303             else
  23. 304                 reply_checksum_add <= reply_checksum_add;
  24. 305         end
  25. 306         else if (icmp_rx_cnt == icmp_data_length) begin
  26. 307             icmp_rx_cnt <= icmp_rx_cnt + 16'd1;
  27. 308             icmp_rx_data_d0 <= 8'h00;
  28. 309             if(icmp_rx_cnt[0 == 1'b1)
  29. 310                 reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd}
  30. 311                                    + reply_checksum_add;
  31. 312             else
  32. 313                 reply_checksum_add <= reply_checksum_add;
  33. 314         end
  34. 315         if(icmp_rx_cnt == icmp_data_length - 16'd1) begin
  35. 316             skip_en <= 1'b1;                    //有效数据接收完成
  36. 317             icmp_rx_cnt <= 16'd0;
  37. 318             rec_en_cnt <= 2'd0;
  38. 319             rec_pkt_done <= 1'b1;               
  39. 320             rec_en <= 1'b1;
  40. 321             rec_byte_num <= icmp_data_length;
  41. 322         end
  42. 323     end
  43. 324 end
  44. 325 st_rx_end : begin                               //单包数据接收完成
  45. 326     if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)begin
  46. 327         reply_checksum <= reply_checksum_add ;
  47. 328         skip_en <= 1'b1;
  48. 329         reply_checksum_add <= 32'd0;
  49. 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. 1  module  tb_icmp;
  2. 2  
  3. 3  //parameter  define
  4. 4  parameter  T = 8;                      //时钟周期为8ns
  5. 5  parameter  OP_CYCLE = 100;              //操作周期(发送周期间隔)
  6. 6  
  7. 7  //开发板MAC地址00-11-22-33-44-55
  8. 8  parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
  9. 9  //开发板IP地址192.168.1.10     
  10. 10 parameter  BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
  11. 11 //目的MAC地址ff_ff_ff_ff_ff_ff
  12. 12 parameter  DES_MAC  = 48'hff_ff_ff_ff_ff_ff;
  13. 13 //目的IP地址192.168.1.10
  14. 14 parameter  DES_IP   = {8'd192,8'd168,8'd1,8'd10};
  15. 15
  16. 16 //reg define
  17. 17 reg           gmii_clk;    //时钟信号
  18. 18 reg           sys_rst_n;   //复位信号
  19. 19
  20. 20 reg           tx_start_en;
  21. 21 reg   [31:0  tx_data    ;
  22. 22 reg   [15:0  tx_byte_num;
  23. 23 reg   [47:0  des_mac    ;
  24. 24 reg   [31:0  des_ip     ;
  25. 25
  26. 26 reg   [3:0  flow_cnt   ;
  27. 27 reg   [13:0 delay_cnt  ;
  28. 28
  29. 29 wire          gmii_rx_clk; //GMII接收时钟
  30. 30 wire          gmii_rx_dv ; //GMII接收数据有效信号
  31. 31 wire  [7:0   gmii_rxd  ; //GMII接收数据
  32. 32 wire          gmii_tx_clk; //GMII发送时钟
  33. 33 wire          gmii_tx_en ; //GMII发送数据使能信号
  34. 34 wire  [7:0   gmii_txd  ; //GMII发送数据
  35. 35               
  36. 36 wire          tx_done    ;
  37. 37 wire          tx_req     ;
  38. 38
  39. 39 //*****************************************************
  40. 40 //**                    main code
  41. 41 //*****************************************************
  42. 42
  43. 43 assign gmii_rx_clk = gmii_clk   ;
  44. 44 assign gmii_tx_clk = gmii_clk   ;
  45. 45 assign gmii_rx_dv  = gmii_tx_en ;
  46. 46 assign gmii_rxd    = gmii_txd   ;
  47. 47
  48. 48 //给输入信号初始值
  49. 49 initial begin
  50. 50      gmii_clk           = 1'b0;
  51. 51      sys_rst_n          = 1'b0;     //复位
  52. 52      #(T+1)  sys_rst_n = 1'b1;     //在第(T+1)ns的时候复位信号信号拉高
  53. 53 end
  54. 54
  55. 55 //125Mhz的时钟,周期则为1/125Mhz=8ns,所以每4ns,电平取反一次
  56. 56 always #(T/2) gmii_clk = ~gmii_clk;
  57. 57
  58. 58 always @(posedge gmii_clk or negedge sys_rst_n) begin
  59. 59      if(!sys_rst_n) begin
  60. 60          tx_start_en <= 1'b0;
  61. 61          tx_data <= 32'h_00_00_00_00;
  62. 62          tx_byte_num <= 1'b0;
  63. 63          des_mac <= 1'b0;
  64. 64          des_ip <= 1'b0;
  65. 65          delay_cnt <= 1'b0;
  66. 66          flow_cnt <= 1'b0;
  67. 67      end
  68. 68      else begin
  69. 69          case(flow_cnt)
  70. 70              'd0 : flow_cnt <= flow_cnt + 1'b1;
  71. 71              'd1 : begin
  72. 72                  tx_start_en <= 1'b1;  //拉高开始发送使能信号
  73. 73                  tx_byte_num <= 16'd20;//设置发送的字节数
  74. 74                  flow_cnt <= flow_cnt + 1'b1;
  75. 75              end
  76. 76              'd2 : begin
  77. 77                  tx_start_en <= 1'b0;
  78. 78                  flow_cnt <= flow_cnt + 1'b1;
  79. 79              end   
  80. 80              'd3 : begin
  81. 81                  if(tx_req)
  82. 82                      tx_data <= tx_data ;
  83. 83                  if(tx_done) begin
  84. 84                      flow_cnt <= flow_cnt + 1'b1;
  85. 85                      tx_data <= 32'h_00_00_00_00;
  86. 86                  end   
  87. 87              end
  88. 88              'd4 : begin
  89. 89                  delay_cnt <= delay_cnt + 1'b1;
  90. 90                  if(delay_cnt == OP_CYCLE - 1'b1)
  91. 91                      flow_cnt <= flow_cnt + 1'b1;
  92. 92              end
  93. 93              'd5 : begin
  94. 94                  tx_start_en <= 1'b1;  //拉高开始发送使能信号
  95. 95                  tx_byte_num <= 16'd28;//设置发送的字节数
  96. 96                  flow_cnt <= flow_cnt + 1'b1;               
  97. 97              end
  98. 98              'd6 : begin
  99. 99                  tx_start_en <= 1'b0;
  100. 100                 flow_cnt <= flow_cnt + 1'b1;
  101. 101             end
  102. 102             'd7 : begin
  103. 103                 if(tx_req)
  104. 104                     tx_data <= tx_data;
  105. 105                 if(tx_done) begin
  106. 106                     flow_cnt <= flow_cnt + 1'b1;
  107. 107                     tx_data <= 32'h_00_00_00_00;
  108. 108                 end  
  109. 109             end
  110. 110             default:;
  111. 111         endcase   
  112. 112     end
  113. 113 end
  114. 114
  115. 115 //例化ICMP模块
  116. 116 icmp
  117. 117   #(
  118. 118     .BOARD_MAC     (BOARD_MAC),      //参数例化
  119. 119     .BOARD_IP      (BOARD_IP ),
  120. 120     .DES_MAC       (DES_MAC  ),
  121. 121     .DES_IP        (DES_IP   )
  122. 122     )
  123. 123   u_icmp(
  124. 124     .rst_n         (sys_rst_n   ),
  125. 125     
  126. 126     .gmii_rx_clk   (gmii_rx_clk ),
  127. 127     .gmii_rx_dv    (gmii_rx_dv  ),
  128. 128     .gmii_rxd      (gmii_rxd    ),
  129. 129     .gmii_tx_clk   (gmii_tx_clk ),
  130. 130     .gmii_tx_en    (gmii_tx_en),
  131. 131     .gmii_txd      (gmii_txd  ),
  132. 132   
  133. 133     .rec_pkt_done  (),
  134. 134     .rec_en        (),
  135. 135     .rec_data      (),
  136. 136     .rec_byte_num  (),
  137. 137     .tx_start_en   (tx_start_en ),
  138. 138     .tx_data       (tx_data     ),
  139. 139     .tx_byte_num   (tx_byte_num ),
  140. 140     .des_mac       (des_mac     ),
  141. 141     .des_ip        (des_ip      ),
  142. 142     .tx_done       (tx_done     ),
  143. 143     .tx_req        (tx_req      )
  144. 144     );
  145. 145
  146. 146 endmodule
复制代码

在仿真icmp的接收过程中需要将icmp_tx.v模块修改为开发板发送请求模块,既将报文类型进行如下修改即可:
  1. //ICMP报文类型:回显应答
  2. //localparam ECHO_REPLY   = 8'h00;
  3.   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位数据。
image020.png
图 52.4.3 icmp接收的波形图
ICMP发送模块按照ICMP的数据格式发送数据,并将32位用户数据转成8位数据的功能,也就是接收模块的逆过程。同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
image022.png
图 52.4.4 ICMP发送模块的状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储以太网的帧头、IP首部以及ICMP的首部,在复位时初始化数组的值,部分源代码如下。
  1. 57  reg  [7:0   preamble[7:0       ; //前导码
  2. 58  reg  [7:0   eth_head[13:0      ; //以太网首部
  3. 59  reg  [31:0  ip_head[6:0        ; //IP首部+ ICMP首部
复制代码
省略部分代码……
  1. 201         //初始化数组   
  2. 202         //前导码 7个8'h55 + 1个8'hd5
  3. 203         preamble[0<= 8'h55;
  4. 204         preamble[1<= 8'h55;
  5. 205         preamble[2<= 8'h55;
  6. 206         preamble[3<= 8'h55;
  7. 207         preamble[4<= 8'h55;
  8. 208         preamble[5<= 8'h55;
  9. 209         preamble[6<= 8'h55;
  10. 210         preamble[7<= 8'hd5;
  11. 211         //目的MAC地址
  12. 212         eth_head[0<= DES_MAC[47:40];
  13. 213         eth_head[1<= DES_MAC[39:32];
  14. 214         eth_head[2<= DES_MAC[31:24];
  15. 215         eth_head[3<= DES_MAC[23:16];
  16. 216         eth_head[4<= DES_MAC[15:8];
  17. 217         eth_head[5<= DES_MAC[7:0];
  18. 218         //源MAC地址
  19. 219         eth_head[6<= BOARD_MAC[47:40];
  20. 220         eth_head[7<= BOARD_MAC[39:32];
  21. 221         eth_head[8<= BOARD_MAC[31:24];
  22. 222         eth_head[9<= BOARD_MAC[23:16];
  23. 223         eth_head[10<= BOARD_MAC[15:8];
  24. 224         eth_head[11<= BOARD_MAC[7:0];
  25. 225         //以太网类型
  26. 226         eth_head[12<= ETH_TYPE[15:8];
  27. 227         eth_head[13<= ETH_TYPE[7:0];
复制代码
以上代码在复位时对数组进行初始化。
  1. 236 st_idle     : begin
  2. 237     if(trig_tx_en) begin
  3. 238         skip_en <= 1'b1;
  4. 239         //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
  5. 240         ip_head[0 <= {8'h45,8'h00,total_num};
  6. 241         //16位标识,每次发送累加1      
  7. 242         ip_head[1][31:16 <= ip_head[1][31:16 + 1'b1;
  8. 243         //bit[15:13]: 010表示不分片
  9. 244         ip_head[1][15:0 <= 16'h4000;
  10. 245         //8'h80:表示生存时间
  11. 246         //8'd01:1代表ICMP,2代表IGMP,6代表TCP,17代表UDP
  12. 247         ip_head[2 <= {8'h80,8'd01,16'h0000};
  13. 248         //源IP地址               
  14. 249         ip_head[3 <= BOARD_IP;
  15. 250         //目的IP地址   
  16. 251         if(des_ip != 32'd0)
  17. 252             ip_head[4 <= des_ip;
  18. 253         else
  19. 254             ip_head[4 <= DES_IP;
  20. 255         // 8位icmpTYPE ,8位 icmp CODE
  21. 256         ip_head[5][31:16 <= {ECHO_REPLY,8'h00};
  22. 257         //16位identifier16位sequence
  23. 258         ip_head[6 <= {icmp_id,icmp_seq};
  24. 259         //更新MAC地址
  25. 260         if(des_mac != 48'b0) begin
  26. 261             //目的MAC地址
  27. 262             eth_head[0 <= des_mac[47:40];
  28. 263             eth_head[1 <= des_mac[39:32];
  29. 264             eth_head[2 <= des_mac[31:24];
  30. 265             eth_head[3 <= des_mac[23:16];
  31. 266             eth_head[4 <= des_mac[15:8];
  32. 267             eth_head[5 <= des_mac[7:0];
  33. 268         end
  34. 269     end
  35. 270 end
复制代码
在程序的第240行至258行代码,为IP首部数组进行赋值。
  1. 291 st_check_icmp: begin                           //ICMP首部+数据校验
  2. 292     cnt <= cnt + 5'd1;
  3. 293     if(cnt == 5'd0) begin
  4. 294         check_buffer_icmp <= ip_head[5][31:16 //首部中的类型与代码拼接成一个16位数据
  5. 295                         + ip_head[6][31:16 + ip_head[6][15:0]  //标识符+序列号
  6. 296                         + reply_checksum;  //ICMP数据相邻8位数据拼成一个16位再累加的和
  7. 297     end
  8. 298     else if(cnt == 5'd1)                      //可能出现进位,累加一次
  9. 299         check_buffer_icmp <= check_buffer_icmp[31:16 + check_buffer_icmp[15:0];
  10. 300     else if(cnt == 5'd2) begin                //可能再次出现进位,累加一次
  11. 301         check_buffer_icmp <= check_buffer_icmp[31:16 + check_buffer_icmp[15:0];
  12. 302     end                             
  13. 303     else if(cnt == 5'd3) begin                //按位取反
  14. 304         skip_en <= 1'b1;
  15. 305         cnt <= 5'd0;
  16. 306         // ICMP:16位校验和
  17. 307         ip_head[5][15:0 <= ~check_buffer_icmp[15:0];
  18. 308     end
  19. 309 end
复制代码
291~309行代码是将ICMP首部与数据进行加法运算,298~302行代码是消除加法运算的进位,307行代码给需要发送的icmp首部校验和赋值。
  1. 356 st_tx_data  : begin                     //发送数据
  2. 357     crc_en <= 1'b1;
  3. 358     gmii_tx_en <= 1'b1;
  4. 359     tx_bit_sel <= tx_bit_sel + 3'd1;  
  5. 360     if(data_cnt < tx_data_num - 16'd1)
  6. 361         data_cnt <= data_cnt + 16'd1;
  7. 362     else if(data_cnt ==tx_data_num - 16'd1)begin
  8. 363         //如果发送的有效数据少于18个字节,在后面填补充位
  9. 364         //补充的值为最后一次发送的有效数据
  10. 365         gmii_txd <= 8'd0;
  11. 366         if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
  12. 367             real_add_cnt <= real_add_cnt + 5'd1;  
  13. 368         else begin
  14. 369             skip_en <= 1'b1;
  15. 370             data_cnt <= 16'd0;
  16. 371             real_add_cnt <= 5'd0;
  17. 372             tx_bit_sel <= 3'd0;
  18. 373         end   
  19. 374     end
  20. 375     if(tx_bit_sel == 1'b0)
  21. 376         gmii_txd <= tx_data[31:24];
  22. 377     else if(tx_bit_sel == 3'd1)
  23. 378         gmii_txd <= tx_data[23:16];
  24. 379     else if(tx_bit_sel == 3'd2) begin
  25. 380         gmii_txd <= tx_data[15:8];   
  26. 381         if(data_cnt != tx_data_num - 16'd1)
  27. 382             tx_req <= 1'b1;  
  28. 383     end
  29. 384     else if(tx_bit_sel == 3'd3)
  30. 385         gmii_txd <= tx_data[7:0];
  31. 386 end
复制代码
程序第356行至386行代码为发送ICMP数据段的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,去掉IP首部字节和ICMP首部字节后,有效数据至少为18个字节,程序设计中已经考虑到这种情况,当发送的有效数据少于18个字节时,会在有效数据后面发送补充位,填充的数据为0。
  1. 387 st_crc      : begin                          //发送CRC校验值
  2. 388     gmii_tx_en <= 1'b1;
  3. 389     tx_bit_sel <= tx_bit_sel + 3'd1;
  4. 390     if(tx_bit_sel == 3'd0)
  5. 391         gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
  6. 392                      ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
  7. 393     else if(tx_bit_sel == 3'd1)
  8. 394         gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],~crc_data[19],
  9. 395                      ~crc_data[20],~crc_data[21], ~crc_data[22],~crc_data[23]};
  10. 396     else if(tx_bit_sel == 3'd2) begin
  11. 397         gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
  12. 398                      ~crc_data[12],~crc_data[13], ~crc_data[14],~crc_data[15]};
  13. 399     end
  14. 400     else if(tx_bit_sel == 3'd3) begin
  15. 401         gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
  16. 402                      ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
  17. 403         tx_done_t <= 1'b1;
  18. 404         skip_en <= 1'b1;
  19. 405     end
  20. 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校验值复位)信号。
image024.png
图 52.4.5 ICMP发送仿真的波形图
以太网控制模块的代码如下:
  1. 1 module eth_ctrl(
  2. 2      input             clk       ,      //系统时钟
  3. 3      input             rst_n     ,      //系统复位信号,低电平有效
  4. 4      //ARP相关端口信号                                    
  5. 5      input             arp_rx_done,     //ARP接收完成信号
  6. 6      input             arp_rx_type,     //ARP接收类型 0:请求  1:应答
  7. 7      output  reg       arp_tx_en,       //ARP发送使能信号
  8. 8      output            arp_tx_type,     //ARP发送类型 0:请求  1:应答
  9. 9      input             arp_tx_done,     //ARP发送完成信号
  10. 10     input             arp_gmii_tx_en,  //ARPGMII输出数据有效信号
  11. 11     input     [7:0   arp_gmii_txd,    //ARPGMII输出数据
  12. 12     //ICMP相关端口信号
  13. 13     input             icmp_tx_start_en,//ICMP开始发送信号
  14. 14     input             icmp_tx_done,    //ICMP发送完成信号
  15. 15     input             icmp_gmii_tx_en, //ICMP GMII输出数据有效信号  
  16. 16     input     [7:0   icmp_gmii_txd,   //ICMPGMII输出数据   
  17. 17     //GMII发送引脚                     
  18. 18     output            gmii_tx_en,      //GMII输出数据有效信号
  19. 19     output    [7:0   gmii_txd         //ICMP GMII输出数据
  20. 20     );
  21. 21
  22. 22 //reg define
  23. 23 reg       protocol_sw; //协议切换信号
  24. 24 reg       icmp_tx_busy; //ICMP正在发送数据标志信号
  25. 25 reg       arp_rx_flag; //接收到ARP请求信号的标志
  26. 26
  27. 27 //*****************************************************
  28. 28 //**                   main code
  29. 29 //*****************************************************
  30. 30
  31. 31 assign arp_tx_type = 1'b1;   //ARP发送类型固定为ARP应答                                   
  32. 32 assign gmii_tx_en = protocol_sw ? icmp_gmii_tx_en : arp_gmii_tx_en;
  33. 33 assign gmii_txd = protocol_sw ? icmp_gmii_txd : arp_gmii_txd;
  34. 34
  35. 35 //控制ICMP发送忙信号
  36. 36 always @(posedge clk or negedge rst_n) begin
  37. 37     if(!rst_n)
  38. 38         icmp_tx_busy <= 1'b0;
  39. 39     else if(icmp_tx_start_en)   
  40. 40         icmp_tx_busy <= 1'b1;
  41. 41     else if(icmp_tx_done)
  42. 42         icmp_tx_busy <= 1'b0;
  43. 43 end
  44. 44
  45. 45 //控制接收到ARP请求信号的标志
  46. 46 always @(posedge clk or negedge rst_n) begin
  47. 47     if(!rst_n)
  48. 48         arp_rx_flag <= 1'b0;
  49. 49     else if(arp_rx_done && (arp_rx_type == 1'b0))   
  50. 50         arp_rx_flag <= 1'b1;
  51. 51     else if(protocol_sw == 1'b0)
  52. 52         arp_rx_flag <= 1'b0;
  53. 53 end
  54. 54
  55. 55 //控制protocol_sw和arp_tx_en信号
  56. 56 always @(posedge clk or negedge rst_n) begin
  57. 57     if(!rst_n) begin
  58. 58         protocol_sw <= 1'b0;
  59. 59         arp_tx_en <= 1'b0;
  60. 60     end
  61. 61     else begin
  62. 62         arp_tx_en <= 1'b0;
  63. 63         if(icmp_tx_start_en)
  64. 64             protocol_sw <= 1'b1;
  65. 65         else if(arp_rx_flag && (icmp_tx_busy == 1'b0)) begin
  66. 66             protocol_sw <= 1'b0;
  67. 67             arp_tx_en <= 1'b1;
  68. 68         end   
  69. 69     end        
  70. 70 end
  71. 71
  72. 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行代码所示。

1.5 下载验证
编译工程并生成比特流.sbit文件后,此时将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,网口的位置如下图所示。
image026.png
图 52.5.1 网口位置
点击PDS工具栏的下载按钮,在弹出的Fabric Configuration界面中双击“Boundary Scan”,我们将生成好的sbit流文件下载到开发板中去。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
image028.png
图 52.5.2 点击网络图标
接下来就可以打开电脑命令提示符框就可以执行ping命令了,下图所示:
image030.png
图 52.5.3 电脑命令提示符框
输入“ping192.168.1.10”命令如下图所示打印如下信息表示电脑ping开发板成功。
image031.png
图 52.5.4 ping命令输出信息
从上图可以看到开发板正常回复,电脑ping开发板成功。
接下来通过Wireshark软件抓取网口的数据包,界面如下图所示:
image032.png
图 52.5.5 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
image034.png
图 52.5.6 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候重新在电脑命令提示符框中发送“ping 192.168.1.10”命令,可以看到Wireshark软件中抓取的数据,如下图所示。
image036.png
图 52.5.7 wireshark以太网抓取到的数据包
上图中第47行是上位机发送的ICMP请求数据包,第48行是开发板返回的ICMP应答数据包。点击开发板返回的数据包,可以看到开发板发送的详细数据,如下图所示:
image038.png
图 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”。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 12:02

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表