本帖最后由 正点原子运营 于 2024-1-5 17:53 编辑
第五十二章 以太网ICMP测试实验
1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
6)FPGA技术交流QQ群:435699340
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报文中的数据段为用户希望传输的数据内容。接下来我们逐个来向大家介绍不同层的数据格式。 其中以太网的帧格式在“以太网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数据格式如下图所示: 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请求报文的封装格式: 这是我们刚才通过ping命令抓的ICMP协议包,其中request是ICMP请求数据报,reply是ICMP回答数据报,另外request和reply是一组ICMP请求回答数据报。 我们再针对一组ICMP请求回答数据报分析两个ICMP数据报是否为一组。Type的值为8'h08是请求类型报文如下左图所示,Type的值为8'h00是应答类型报文如下右图所示,序列号(Sequence number)一致是同一组ICMP请求回答数据报。 图 52.1.6 icmp的request和reply 下面我们来看一下,我们在用ping命令发送的ping包携带的是什么数据。 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应答信号。 由上图可知,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接收模块的状态跳转图。 接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、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[31:24 <= gmii_rxd;
- 289 else if(rec_en_cnt == 2'd1)
- 290 rec_data[23:16 <= gmii_rxd;
- 291 else if(rec_en_cnt == 2'd2)
- 292 rec_data[15:8 <= gmii_rxd;
- 293 else if(rec_en_cnt==2'd3) begin
- 294 rec_en <= 1'b1;
- 295 rec_data[7:0 <= 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[0 == 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[0 == 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 module tb_icmp;
- 2
- 3 //parameter define
- 4 parameter T = 8; //时钟周期为8ns
- 5 parameter OP_CYCLE = 100; //操作周期(发送周期间隔)
- 6
- 7 //开发板MAC地址00-11-22-33-44-55
- 8 parameter BOARD_MAC = 48'h00_11_22_33_44_55;
- 9 //开发板IP地址192.168.1.10
- 10 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
- 11 //目的MAC地址ff_ff_ff_ff_ff_ff
- 12 parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
- 13 //目的IP地址192.168.1.10
- 14 parameter DES_IP = {8'd192,8'd168,8'd1,8'd10};
- 15
- 16 //reg define
- 17 reg gmii_clk; //时钟信号
- 18 reg sys_rst_n; //复位信号
- 19
- 20 reg tx_start_en;
- 21 reg [31:0 tx_data ;
- 22 reg [15:0 tx_byte_num;
- 23 reg [47:0 des_mac ;
- 24 reg [31:0 des_ip ;
- 25
- 26 reg [3:0 flow_cnt ;
- 27 reg [13:0 delay_cnt ;
- 28
- 29 wire gmii_rx_clk; //GMII接收时钟
- 30 wire gmii_rx_dv ; //GMII接收数据有效信号
- 31 wire [7:0 gmii_rxd ; //GMII接收数据
- 32 wire gmii_tx_clk; //GMII发送时钟
- 33 wire gmii_tx_en ; //GMII发送数据使能信号
- 34 wire [7:0 gmii_txd ; //GMII发送数据
- 35
- 36 wire tx_done ;
- 37 wire tx_req ;
- 38
- 39 //*****************************************************
- 40 //** main code
- 41 //*****************************************************
- 42
- 43 assign gmii_rx_clk = gmii_clk ;
- 44 assign gmii_tx_clk = gmii_clk ;
- 45 assign gmii_rx_dv = gmii_tx_en ;
- 46 assign gmii_rxd = gmii_txd ;
- 47
- 48 //给输入信号初始值
- 49 initial begin
- 50 gmii_clk = 1'b0;
- 51 sys_rst_n = 1'b0; //复位
- 52 #(T+1) sys_rst_n = 1'b1; //在第(T+1)ns的时候复位信号信号拉高
- 53 end
- 54
- 55 //125Mhz的时钟,周期则为1/125Mhz=8ns,所以每4ns,电平取反一次
- 56 always #(T/2) gmii_clk = ~gmii_clk;
- 57
- 58 always @(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位数据。 ICMP发送模块按照ICMP的数据格式发送数据,并将32位用户数据转成8位数据的功能,也就是接收模块的逆过程。同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示: 发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。 发送模块的代码中定义了数组来存储以太网的帧头、IP首部以及ICMP的首部,在复位时初始化数组的值,部分源代码如下。 - 57 reg [7:0 preamble[7:0 ; //前导码
- 58 reg [7:0 eth_head[13:0 ; //以太网首部
- 59 reg [31:0 ip_head[6:0 ; //IP首部+ ICMP首部
复制代码省略部分代码…… - 201 //初始化数组
- 202 //前导码 7个8'h55 + 1个8'hd5
- 203 preamble[0<= 8'h55;
- 204 preamble[1<= 8'h55;
- 205 preamble[2<= 8'h55;
- 206 preamble[3<= 8'h55;
- 207 preamble[4<= 8'h55;
- 208 preamble[5<= 8'h55;
- 209 preamble[6<= 8'h55;
- 210 preamble[7<= 8'hd5;
- 211 //目的MAC地址
- 212 eth_head[0<= DES_MAC[47:40];
- 213 eth_head[1<= DES_MAC[39:32];
- 214 eth_head[2<= DES_MAC[31:24];
- 215 eth_head[3<= DES_MAC[23:16];
- 216 eth_head[4<= DES_MAC[15:8];
- 217 eth_head[5<= DES_MAC[7:0];
- 218 //源MAC地址
- 219 eth_head[6<= BOARD_MAC[47:40];
- 220 eth_head[7<= BOARD_MAC[39:32];
- 221 eth_head[8<= BOARD_MAC[31:24];
- 222 eth_head[9<= BOARD_MAC[23:16];
- 223 eth_head[10<= BOARD_MAC[15:8];
- 224 eth_head[11<= BOARD_MAC[7:0];
- 225 //以太网类型
- 226 eth_head[12<= ETH_TYPE[15:8];
- 227 eth_head[13<= ETH_TYPE[7:0];
复制代码以上代码在复位时对数组进行初始化。 - 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[0 <= {8'h45,8'h00,total_num};
- 241 //16位标识,每次发送累加1
- 242 ip_head[1][31:16 <= ip_head[1][31:16 + 1'b1;
- 243 //bit[15:13]: 010表示不分片
- 244 ip_head[1][15:0 <= 16'h4000;
- 245 //8'h80:表示生存时间
- 246 //8'd01:1代表ICMP,2代表IGMP,6代表TCP,17代表UDP
- 247 ip_head[2 <= {8'h80,8'd01,16'h0000};
- 248 //源IP地址
- 249 ip_head[3 <= BOARD_IP;
- 250 //目的IP地址
- 251 if(des_ip != 32'd0)
- 252 ip_head[4 <= des_ip;
- 253 else
- 254 ip_head[4 <= DES_IP;
- 255 // 8位icmpTYPE ,8位 icmp CODE
- 256 ip_head[5][31:16 <= {ECHO_REPLY,8'h00};
- 257 //16位identifier16位sequence
- 258 ip_head[6 <= {icmp_id,icmp_seq};
- 259 //更新MAC地址
- 260 if(des_mac != 48'b0) begin
- 261 //目的MAC地址
- 262 eth_head[0 <= des_mac[47:40];
- 263 eth_head[1 <= des_mac[39:32];
- 264 eth_head[2 <= des_mac[31:24];
- 265 eth_head[3 <= des_mac[23:16];
- 266 eth_head[4 <= des_mac[15:8];
- 267 eth_head[5 <= des_mac[7:0];
- 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[5][31:16 //首部中的类型与代码拼接成一个16位数据
- 295 + ip_head[6][31:16 + ip_head[6][15:0] //标识符+序列号
- 296 + reply_checksum; //ICMP数据相邻8位数据拼成一个16位再累加的和
- 297 end
- 298 else if(cnt == 5'd1) //可能出现进位,累加一次
- 299 check_buffer_icmp <= check_buffer_icmp[31:16 + check_buffer_icmp[15:0];
- 300 else if(cnt == 5'd2) begin //可能再次出现进位,累加一次
- 301 check_buffer_icmp <= check_buffer_icmp[31:16 + check_buffer_icmp[15:0];
- 302 end
- 303 else if(cnt == 5'd3) begin //按位取反
- 304 skip_en <= 1'b1;
- 305 cnt <= 5'd0;
- 306 // ICMP:16位校验和
- 307 ip_head[5][15:0 <= ~check_buffer_icmp[15:0];
- 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[31:24];
- 377 else if(tx_bit_sel == 3'd1)
- 378 gmii_txd <= tx_data[23:16];
- 379 else if(tx_bit_sel == 3'd2) begin
- 380 gmii_txd <= tx_data[15:8];
- 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[7:0];
- 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[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
- 392 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
- 393 else if(tx_bit_sel == 3'd1)
- 394 gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],~crc_data[19],
- 395 ~crc_data[20],~crc_data[21], ~crc_data[22],~crc_data[23]};
- 396 else if(tx_bit_sel == 3'd2) begin
- 397 gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
- 398 ~crc_data[12],~crc_data[13], ~crc_data[14],~crc_data[15]};
- 399 end
- 400 else if(tx_bit_sel == 3'd3) begin
- 401 gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
- 402 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
- 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校验值复位)信号。 以太网控制模块的代码如下: - 1 module 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 output reg arp_tx_en, //ARP发送使能信号
- 8 output arp_tx_type, //ARP发送类型 0:请求 1:应答
- 9 input arp_tx_done, //ARP发送完成信号
- 10 input arp_gmii_tx_en, //ARPGMII输出数据有效信号
- 11 input [7:0 arp_gmii_txd, //ARPGMII输出数据
- 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 [7:0 icmp_gmii_txd, //ICMPGMII输出数据
- 17 //GMII发送引脚
- 18 output gmii_tx_en, //GMII输出数据有效信号
- 19 output [7:0 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行代码所示。
1.5 下载验证编译工程并生成比特流.sbit文件后,此时将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,网口的位置如下图所示。 点击PDS工具栏的下载按钮,在弹出的Fabric Configuration界面中双击“Boundary Scan”,我们将生成好的sbit流文件下载到开发板中去。 程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。 接下来就可以打开电脑命令提示符框就可以执行ping命令了,下图所示: 输入“ping192.168.1.10”命令如下图所示打印如下信息表示电脑ping开发板成功。 从上图可以看到开发板正常回复,电脑ping开发板成功。 接下来通过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”。 |