这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
千兆以太网实验 [2019/12/21 09:56] zgf |
千兆以太网实验 [2022/03/18 15:49] (当前版本) sean |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ***银杏科技有限公司/Gingko Technology Co.,Ltd.** | + | | **银杏科技有限公司旗下技术文档发布平台** |||| |
- | ***技术支持论坛:http://www.eeschool.org** | + | |技术支持电话|**0379-69926675-801**||| |
- | ***旗舰店:http://icore.taobao.com** | + | |技术支持邮件|Gingko@vip.163.com||| |
- | ***技术支持邮件:GINGKO@vip.163.com** | + | ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ |
- | ***电话:0379-69926675** | + | | V1.0 | 2019-12-25 | gingko | 初次建立 | |
- | ***更新日期:12/17/2019** | + | |
- | ***版本号:v1.0** | + | |
- | ***更新说明:无** | + | |
===== 实验三十二:千兆以太网传输 ===== | ===== 实验三十二:千兆以太网传输 ===== | ||
行 15: | 行 12: | ||
-掌握GMII接口千兆以太网的UDP协议通信模块设计。 | -掌握GMII接口千兆以太网的UDP协议通信模块设计。 | ||
=== 二、实验设备及平台 === | === 二、实验设备及平台 === | ||
- | -iCore3 双核心板( FPGA型号为EP4CE10F17)。 | + | |
- | -千兆网传输模块 | + | -iCore3 双核心板( FPGA型号为EP4CE10F17)[[https://item.taobao.com/item.htm?id=524229438677|点击购买]] |
- | -Blaster(或相同功能的)仿真器和USB线缆 | + | -千兆网传输模块[[https://item.taobao.com/item.htm?id=574787244706|点击购买]] |
+ | -Blaster(或相同功能的)仿真器和USB线缆[[https://item.taobao.com/item.htm?id=554869837940|点击购买]] | ||
-Micro USB线缆和千兆速率网线。 | -Micro USB线缆和千兆速率网线。 | ||
-QuartusII开发软件(本实验中使用的是13.1版本)、TCP&UDP测试工具软件、wireshark抓包软件。 | -QuartusII开发软件(本实验中使用的是13.1版本)、TCP&UDP测试工具软件、wireshark抓包软件。 | ||
行 41: | 行 39: | ||
*通过阅读RTL8211EG芯片手册中GMII模式下对应引脚的介绍,可以知道各引脚的编号,输入输出类型以及功能描述等。包括数据收发对应的引脚以及模式配置引脚。 | *通过阅读RTL8211EG芯片手册中GMII模式下对应引脚的介绍,可以知道各引脚的编号,输入输出类型以及功能描述等。包括数据收发对应的引脚以及模式配置引脚。 | ||
- | {{ :icore3:图32-4_gmii模式引脚命名及定义1.png?direct&600 |图32-4_gmii模式引脚命名及定义1}} | + | {{ :icore3:图32-4_gmii模式引脚命名及定义1.png?direct&800 |图32-4_gmii模式引脚命名及定义1}} |
- | {{ :icore3:图32-5_gmii模式引脚命名及定义2.png?direct&600 |图32-5_gmii模式引脚命名及定义2}} | + | {{ :icore3:图32-5_gmii模式引脚命名及定义2.png?direct&800 |图32-5_gmii模式引脚命名及定义2}} |
*从芯片手册的表格中可以看到,RGMII模式和GMII模式是通过配置31号引脚来控制的。如下图红框中所示,拉低31号引脚的电平,则表示选择GMII模式,拉高则表示选择RGMII模式。从表格下方的原理图可以看到31号引脚接地,接口模式为 GMII接口。 | *从芯片手册的表格中可以看到,RGMII模式和GMII模式是通过配置31号引脚来控制的。如下图红框中所示,拉低31号引脚的电平,则表示选择GMII模式,拉高则表示选择RGMII模式。从表格下方的原理图可以看到31号引脚接地,接口模式为 GMII接口。 | ||
行 174: | 行 172: | ||
*在解析UDP包头的时候,也对目标端口进行了判断。要保证TCP&UDP测试软件设置的时候的端口值和此处代码中的端口值保持一致,否则会导致数据接收失败。 | *在解析UDP包头的时候,也对目标端口进行了判断。要保证TCP&UDP测试软件设置的时候的端口值和此处代码中的端口值保持一致,否则会导致数据接收失败。 | ||
<code verilog> | <code verilog> | ||
- | UDP_R:// 接收UDP包头 8 byte | + | UDP_R:// 接收UDP包头 8 byte |
- | begin | + | begin |
- | if(cnt_R == 8'd7) | + | if(cnt_R == 8'd7) |
+ | begin | ||
+ | udp_head <= {udp_head[55:0],data1}; | ||
+ | cnt_R <= 8'd0; | ||
+ | if(udp_head[39:24] == 16'h7530)//16进制的7530是十进制的30000,即设定的端口号 | ||
+ | begin | ||
+ | data_lenght <= udp_head[23:8] - 16'd8;//提取数据长度,udp_head的[23:8]定义了UDP包的数据长度,包括了UDP头部,所以这里要减去UDP头部的数据长度8 | ||
+ | STATE_R <= APP_R; | ||
+ | end | ||
+ | else | ||
begin | begin | ||
- | udp_head <= {udp_head[55:0],data1}; | + | STATE_R <= PRE_R; //如果端口不匹配,则返回PRE_R状态 |
- | cnt_R <= 8'd0; | + | |
- | if(udp_head[39:24] == 16'h7530)//16进制的7530是十进制的30000,即设定的端口号 | + | |
- | begin | + | |
- | data_lenght <= udp_head[23:8] - 16'd8;//提取数据长度,udp_head的[23:8]定义了UDP包的数据长度,包括了UDP头部,所以这里要减去UDP头部的数据长度8 | + | |
- | STATE_R <= APP_R; | + | |
- | end | + | |
- | else | + | |
- | begin | + | |
- | STATE_R <= PRE_R; //如果端口不匹配,则返回PRE_R状态 | + | |
- | end | + | |
end | end | ||
- | else | + | end |
- | begin | + | else |
- | udp_head <= {udp_head[55:0],data1}; | + | begin |
- | cnt_R <= cnt_R +1'd1; | + | udp_head <= {udp_head[55:0],data1}; |
- | end | + | cnt_R <= cnt_R +1'd1; |
- | end | + | end |
+ | end | ||
</code> | </code> | ||
*在这个状态机中还有个标志信号UDP_SD_EN,在存取有效数据完毕的同时,拉高UDP_SD_EN这个信号,在帧结束状态拉低这个信号,如果感兴趣,可以用SignalTap II Logic Anlyzer(quartus II 自带的逻辑分析仪)查看一下这个信号持续了几个时钟周期。这个标志位的作用是表示一个完整的帧数据包接收完成了。在发送数据的时候,以这个信号判断数据是否接收完毕。 | *在这个状态机中还有个标志信号UDP_SD_EN,在存取有效数据完毕的同时,拉高UDP_SD_EN这个信号,在帧结束状态拉低这个信号,如果感兴趣,可以用SignalTap II Logic Anlyzer(quartus II 自带的逻辑分析仪)查看一下这个信号持续了几个时钟周期。这个标志位的作用是表示一个完整的帧数据包接收完成了。在发送数据的时候,以这个信号判断数据是否接收完毕。 | ||
行 207: | 行 205: | ||
always@(posedge RX_CLK or negedge rst_N ) | always@(posedge RX_CLK or negedge rst_N ) | ||
if(rst_N==1'b0) | if(rst_N==1'b0) | ||
+ | flag_h<=1'b0; | ||
+ | else if((buffer[0]==8'h33)&&(STATE_UT==APP_R))//连发开始位标志3 | ||
+ | flag_h<=1'b1; | ||
+ | else | ||
flag_h<=1'b0; | flag_h<=1'b0; | ||
- | else if((buffer[0]==8'h33)&&(STATE_UT==APP_R))//连发开始位标志3 | ||
- | flag_h<=1'b1; | ||
- | else | ||
- | flag_h<=1'b0; | ||
always@(posedge RX_CLK or negedge rst_N ) | always@(posedge RX_CLK or negedge rst_N ) | ||
if(rst_N==1'b0) | if(rst_N==1'b0) | ||
- | flag_l<=1'b0; | + | flag_l<=1'b0; |
else if((buffer[0]==8'h73)&&(STATE_UT==APP_R))//连发停止位标志s | else if((buffer[0]==8'h73)&&(STATE_UT==APP_R))//连发停止位标志s | ||
- | flag_l<=1'b1; | + | flag_l<=1'b1; |
- | else | + | else |
- | flag_l<=1'b0; | + | flag_l<=1'b0; |
always@(posedge RX_CLK or negedge rst_N ) | always@(posedge RX_CLK or negedge rst_N ) | ||
if(rst_N==1'b0) | if(rst_N==1'b0) | ||
+ | flag<=1'b0; | ||
+ | else if(flag_l) //连发状态停止 | ||
flag<=1'b0; | flag<=1'b0; | ||
- | else if(flag_l) //连发状态停止 | + | else if(flag_h==1'b1)//连发状态开始 |
- | flag<=1'b0; | + | flag<=1'b1; |
- | else if(flag_h==1'b1)//连发状态开始 | + | |
- | flag<=1'b1; | + | |
</code> | </code> | ||
*看这段代码,定义了另外两个寄存器变量,用于控制flag信号,那么控制flag变量的flag_h和flag_l又是如何产生的呢?在代码中,通过对数据接收模块状态和接收的第一个字符的值进行综合判断,当接收模块处于接收有效数据,且第一个接受到的数值为“3”的时候,拉高一个时钟周期的flag_h信号。flag_l则根据接收数据的第一个字符为“s”的时候拉高一个时钟周期。这里大家应该有注意到,当我们对数据接收缓存器的第一个值buffer[0]进行判断时,对比值是8’h33和8’h73。这里的8’h33和8’h73分别是ASCII码“3”和“s”对应的16进制数。这是因为我们使用TCP&UDP测试工具给FPGA发送数据的时候选择的是ASCII格式。 | *看这段代码,定义了另外两个寄存器变量,用于控制flag信号,那么控制flag变量的flag_h和flag_l又是如何产生的呢?在代码中,通过对数据接收模块状态和接收的第一个字符的值进行综合判断,当接收模块处于接收有效数据,且第一个接受到的数值为“3”的时候,拉高一个时钟周期的flag_h信号。flag_l则根据接收数据的第一个字符为“s”的时候拉高一个时钟周期。这里大家应该有注意到,当我们对数据接收缓存器的第一个值buffer[0]进行判断时,对比值是8’h33和8’h73。这里的8’h33和8’h73分别是ASCII码“3”和“s”对应的16进制数。这是因为我们使用TCP&UDP测试工具给FPGA发送数据的时候选择的是ASCII格式。 | ||
行 235: | 行 233: | ||
always@(posedge clk_125M or negedge rst_N) | always@(posedge clk_125M or negedge rst_N) | ||
if(!rst_N) | if(!rst_N) | ||
- | SD_EN<=1'b0; | + | SD_EN<=1'b0; |
else if(flag) | else if(flag) | ||
- | SD_EN<=1'b1; | + | SD_EN<=1'b1; |
- | else if(!flag) | + | else if(!flag) |
- | SD_EN<=UDP_SD_EN; | + | SD_EN<=UDP_SD_EN; |
</code> | </code> | ||
*进入发送状态以后,是将帧头部和IP头部以及校验和的值存入对应的寄存器。准备工作做好之后,开始进入发送状态。在发送数据之前,先进行IP首部校验和的运算。IP校验的目的是为了保证IP首部的完整性。算法也比较简单,反码求和。反码求和的意思是先将IP首部的数据每两个字节拼成一个16位数,并对这些16位数求和。将求和的结果存入一个32位寄存器中,然后将寄存器的高16位和低16位求和取反即可得到IP首部校验和。代码如下: | *进入发送状态以后,是将帧头部和IP头部以及校验和的值存入对应的寄存器。准备工作做好之后,开始进入发送状态。在发送数据之前,先进行IP首部校验和的运算。IP校验的目的是为了保证IP首部的完整性。算法也比较简单,反码求和。反码求和的意思是先将IP首部的数据每两个字节拼成一个16位数,并对这些16位数求和。将求和的结果存入一个32位寄存器中,然后将寄存器的高16位和低16位求和取反即可得到IP首部校验和。代码如下: | ||
行 245: | 行 243: | ||
check1: | check1: | ||
begin | begin | ||
- | if(cnt == 8'd6) | + | if(cnt == 8'd6) |
- | begin//IP计算首部校验和:以16位相加方式加到32位校验和中 | + | begin//IP计算首部校验和:以16位相加方式加到32位校验和中 |
- | ip_check_sum <= {ip_send[0],ip_send[1]} + {ip_send[2],ip_send[3]}+{ip_send[4],ip_send[5]} + {ip_send[6],ip_send[7]} + {ip_send[8],ip_send[9]}+{ip_send[12],ip_send[13]}+{ip_send[14],ip_send[15]}+{ip_send[16],ip_send[17]}+{ip_send[18],ip_send[19]}; | + | ip_check_sum <= {ip_send[0],ip_send[1]} + {ip_send[2],ip_send[3]}+{ip_send[4],ip_send[5]} + {ip_send[6],ip_send[7]} + {ip_send[8],ip_send[9]}+{ip_send[12],ip_send[13]}+{ip_send[14],ip_send[15]}+{ip_send[16],ip_send[17]}+{ip_send[18],ip_send[19]}; |
- | crc_reset <= 1'd1; | + | crc_reset <= 1'd1; |
- | cnt <= 8'd0; | + | cnt <= 8'd0; |
- | STATE_UT <= check2; | + | STATE_UT <= check2; |
- | end | + | end |
- | else | + | else |
- | begin | + | begin |
- | cnt <= cnt + 1'd1; | + | cnt <= cnt + 1'd1; |
- | end | + | end |
end | end | ||
check2: | check2: | ||
begin//IP计算首部校验和:16位相加取反 | begin//IP计算首部校验和:16位相加取反 | ||
- | {ip_send[10],ip_send[11]} = ~(ip_check_sum[15:0] + ip_check_sum[31:16]); | + | {ip_send[10],ip_send[11]} = ~(ip_check_sum[15:0] + ip_check_sum[31:16]); |
- | STATE_UT <= send55; | + | STATE_UT <= send55; |
end | end | ||
</code> | </code> | ||
行 267: | 行 265: | ||
<code verilog> | <code verilog> | ||
send_udp://发送UDP包头 | send_udp://发送UDP包头 | ||
- | begin | + | begin |
- | if(cnt == 8'd7) | + | if(cnt == 8'd7) |
- | begin | + | begin |
- | data_out <= udp_send[cnt]; | + | data_out <= udp_send[cnt]; |
- | cnt <= 8'd0; | + | cnt <= 8'd0; |
- | data_counter <= 16'd0; | + | data_counter <= 16'd0; |
- | if(flag) //控制发送状态跳转至send_data1 | + | if(flag) //控制发送状态跳转至send_data1 |
- | STATE_UT<=send_data1; | + | STATE_UT<=send_data1; |
- | else | + | else |
- | STATE_UT <= send_data; | + | STATE_UT <= send_data; |
- | end | + | end |
- | else | + | else |
- | begin | + | begin |
- | data_out <= udp_send[cnt]; | + | data_out <= udp_send[cnt]; |
- | cnt <= cnt +1'd1; | + | cnt <= cnt +1'd1; |
- | end | + | end |
end | end | ||
</code> | </code> | ||
行 288: | 行 286: | ||
<code verilog> | <code verilog> | ||
send_data://发送数据 | send_data://发送数据 | ||
- | begin | + | begin |
- | if(data_lenght < 16'd19) | + | if(data_lenght < 16'd19) |
- | begin | + | begin |
- | if(data_counter == 16'd20) | + | if(data_counter == 16'd20) |
- | begin | + | begin |
- | data_out <= buffer[data_counter]; | + | data_out <= buffer[data_counter]; |
- | data_counter <= 16'd0; | + | data_counter <= 16'd0; |
- | STATE_UT <= send_crc; | + | STATE_UT <= send_crc; |
- | end | + | end |
- | else | + | else |
- | begin | + | begin |
- | data_out <= buffer[data_counter]; | + | data_out <= buffer[data_counter]; data_counter <= data_counter + 1'd1; |
- | data_counter <= data_counter + 1'd1; | + | end |
- | end | + | end |
- | end | + | else |
- | else | + | begin |
- | begin | + | if(data_counter == data_lenght - 16'd1) |
- | if(data_counter == data_lenght - 16'd1) | + | begin |
- | begin | + | data_out <= buffer[data_counter]; |
- | data_out <= buffer[data_counter]; | + | data_counter <= 16'd0; |
- | data_counter <= 16'd0; | + | STATE_UT <= send_crc; |
- | STATE_UT <= send_crc; | + | end |
- | end | + | else |
- | else | + | begin |
- | begin | + | data_out <= buffer[data_counter]; data_counter <= data_counter + 1'd1; |
- | data_out <= buffer[data_counter]; data_counter <= data_counter + 1'd1; | + | end |
- | end | + | end |
- | end | + | |
end | end | ||
+ | |||
send_data1: | send_data1: | ||
- | begin | + | begin |
- | if(cnt_l==1200) | + | if(cnt_l==1200) |
- | begin | + | begin |
- | STATE_UT <=send_crc; | + | STATE_UT <=send_crc; |
- | cnt_l<=12'd0; | + | cnt_l<=12'd0; |
- | end | + | end |
- | else | + | else |
- | begin | + | begin |
- | data_out<=8'h38;//输出数值随便定义,这里设定的“8” | + | data_out<=8'h38;//输出数值随便定义,这里设定的“8” |
- | cnt_l<=cnt_l+1'b1; | + | cnt_l<=cnt_l+1'b1; |
- | end | + | end |
end | end | ||
</code> | </code> | ||
行 347: | 行 345: | ||
*3)连接好网线和调试工具,供电并编译下载。 | *3)连接好网线和调试工具,供电并编译下载。 | ||
*4)打开wireshark抓包软件,双击以太网那行,进入抓数据包界面。 | *4)打开wireshark抓包软件,双击以太网那行,进入抓数据包界面。 | ||
- | + | ||
- | {{ :icore3:图32-4_gmii模式引脚命名及定义1.png?direct&600 |图32-4_gmii模式引脚命名及定义1}} | + | {{ :icore3:图32-14_wireshark界面.png?direct&600 |图32-14_wireshark界面}} |
*5)打开PC端的任务管理器,在性能界面点击【以太网】,在此界面可以实时动态显示PC端以太网接口发送和接收数据的速率。 | *5)打开PC端的任务管理器,在性能界面点击【以太网】,在此界面可以实时动态显示PC端以太网接口发送和接收数据的速率。 | ||
行 361: | 行 359: | ||
*出现下图界面,从界面中红框处可以看到IP、端口、类型等信息,点击蓝色框中的【创建】创立连接。如果打开软件后发送区为灰色,可以点击绿色框中的【√】,取消发送文件选项(本实验暂不支持文件的传输)。这时会看到TCP&UDP测试工具软件的发送区可以输入内容了。在发送区输入任意内容,点击发送或者自动发送(此处自动发送为PC端持续向iCore3发送数据,iCore3接收并返回数据到PC端),即可实现PC端向FPGA发送数据。 | *出现下图界面,从界面中红框处可以看到IP、端口、类型等信息,点击蓝色框中的【创建】创立连接。如果打开软件后发送区为灰色,可以点击绿色框中的【√】,取消发送文件选项(本实验暂不支持文件的传输)。这时会看到TCP&UDP测试工具软件的发送区可以输入内容了。在发送区输入任意内容,点击发送或者自动发送(此处自动发送为PC端持续向iCore3发送数据,iCore3接收并返回数据到PC端),即可实现PC端向FPGA发送数据。 | ||
- | {{ :图32-17_tcp_udp测试工具测试界面.png?direct&600 |图32-17 TCP&UDP测试工具测试界面}} | + | {{ :icore3:图32-17_tcp_udp测试工具测试界面.png?direct&600 |图32-17 TCP&UDP测试工具测试界面}} |
*本实验中使用3和s(小写)作为高速发送模式的标识符,即当PC发送的内容第一个字符为3这个数字时,FPGA开始连续大量发送数字8到PC,此时可以测试FPGA的千兆网模块发送数据的能力。 | *本实验中使用3和s(小写)作为高速发送模式的标识符,即当PC发送的内容第一个字符为3这个数字时,FPGA开始连续大量发送数字8到PC,此时可以测试FPGA的千兆网模块发送数据的能力。 | ||
*FPGA大量发送数据的过程中,如果PC端通过TCP&UDP测试软件给千兆网模块发送的内容第一个字符为s(小写),则FPGA停止发送数字8,返回到普通模式。 | *FPGA大量发送数据的过程中,如果PC端通过TCP&UDP测试软件给千兆网模块发送的内容第一个字符为s(小写),则FPGA停止发送数字8,返回到普通模式。 |