文章目录
前言
万兆以太网设计最终章节,巨型以太网数据帧传输设计。对于标准以太网而言,数据传输范围为46-1500字节,当大于1500字节后数据将无法传输。在IP层的报文描述当中,有一个分片字段,通过该字段即可实现将巨型数据帧拆分为多个小于1500字节的数据进行传输。
一、UDP_RX模块设计
在UDP接收模块当中增加以下新设计:
- 将接收到的数据先存入RAM当中,如果当前数据是一个单独的包,则立刻输出,如果是一个分片数据包,则继续等待所有分片输入后,一次性输出完整的巨型数据帧。通过w_recv_pkt_end 信号判断当前是否接收到了完整的数据包。看似简单,实则经历了极其漫长的调试工作。。
//完整的一个包输入指示信号//表示没有分片,那么last到来即一个包结束; 分片但是当前分片后没有更多片
assign w_recv_pkt_end = r_udp_pkt_valid &&((rs_axis_ip_last && r_udp_split ==0&& r_udp_offset ==0)||(rs_axis_ip_last && r_udp_split ==1&& r_udp_more_split ==0));
- r_udp_pkt_valid指示信号,该信号用于判断当前数据是否是一个有效的UDP数据包,只有当数据有效,我们才会将其数据存入RAM,并且将其长度信息存入FIFO。(看似简单,实则调试时候诸多BUG源于此信号判断)
//检测是否为UDP数据包,通过user当中的type字段判断即可
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_udp_pkt_valid <= 'd0;elseif(s_axis_ip_valid &&!rs_axis_ip_valid && s_axis_ip_user[36:29]==8'd17 && w_ip_flags[2]==0)//分片udp没有包头
r_udp_pkt_valid <= 'd1;elseif(s_axis_ip_valid &&!rs_axis_ip_valid &&((s_axis_ip_user[36:29]!=8'd17)||(s_axis_ip_data[47:32]!= ri_dymanic_src_port)))
r_udp_pkt_valid <= 'd0;elseif(s_axis_ip_valid &&!rs_axis_ip_valid && s_axis_ip_user[36:29]==8'd17 && s_axis_ip_data[47:32]== ri_dymanic_src_port)
r_udp_pkt_valid <= 'd1;else
r_udp_pkt_valid <= r_udp_pkt_valid;
end
- 在分片时,只有第一个分片有UDP包头信息,其余分片都没有,所以在判断UDP的源和目的端口号的时候需要选判断第一片数据。
//偏移为0才表示当前udp数据包是第一个包,当分片时只有第一个udp包带包头信息
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_udp_header <= 'd0;elseif(rs_axis_ip_last)
r_udp_header <= 'd0;elseif(s_axis_ip_valid &&!rs_axis_ip_valid && s_axis_ip_user[28:16]== 'd0)
r_udp_header <= 'd1;else
r_udp_header <= r_udp_header;
end
二、UDP_TX模块设计
该模块设计其实相对来说简单一点。
我的设计当中只用到了一个FIFO,没有其他FIFO和RAM资源的使用
FIFO_64X2048 FIFO_64X2048_UDP_TX(.clk(i_clk ),// input wire clk.srst(i_rst ),// input wire srst.din(rs_axis_user_data ),// input wire [63 : 0] din.wr_en(rs_axis_user_valid ),// input wire wr_en.rd_en(r_fifo_data_rden ),// input wire rd_en.dout(w_fifo_data_dout ),// output wire [63 : 0] dout.full(w_fifo_data_full ),// output wire full.empty(w_fifo_data_empty )// output wire empty);
注:说明一下发送端分片,当字节数大于1472即需要分片,因为还有20字节的IP头和8字节的UDP头,当传输完1472字节后,如果剩余字节大于1480,则继续分片,为什么不是1472了呢,因为后续的分片当中是不需要8字节的UDP头了。
- 用户输入数据进入FIFO,该模块会判断user信号当中的字节数目,即是否需要分片。
//用户数据字节长度,每次发送结束后更新,//如果是第一个包,当其小于1472则不用分片//如果不是第一个包,当其小于1480则不用继续分片
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_byte_len <= 'd0;elseif(s_axis_user_valid &&!rs_axis_user_valid)
r_byte_len <= s_axis_user_user[15:0];elseif(r_first_split && rm_axis_ip_last && r_split_flag)
r_byte_len <= r_byte_len -1472;elseif(rm_axis_ip_last && r_split_flag)
r_byte_len <= r_byte_len -1480;else
r_byte_len <= r_byte_len;
end
//根据r_byte_len数值判断是否继续分片
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_split_flag <= 'd0;elseif(s_axis_user_valid &&!rs_axis_user_valid && s_axis_user_user[15:0]<=1472)
r_split_flag <= 'd0;elseif(s_axis_user_valid &&!rs_axis_user_valid && s_axis_user_user[15:0]>1472)
r_split_flag <= 'd1;elseif(r_split_flag && rm_axis_ip_last_1d && r_byte_len >1480)
r_split_flag <= 'd1;elseif(r_split_flag && rm_axis_ip_last_1d && r_byte_len <=1480)
r_split_flag <= 'd0;else
r_split_flag <= r_split_flag;
end
- 计算字节偏移,这个偏移是要字节数目除以8的,第一个包发1480字节,则第二个包的字节偏移为1480字节,除以8即为185.
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_offset <= 'd0;elseif(r_split_flag && rm_axis_ip_last)
r_offset <= r_offset +16'd185;elseif(r_split_run &&!r_split_flag && rm_axis_ip_last)
r_offset <= 'd0;else
r_offset <= r_offset;
end
- 最麻烦的地方在于尾端处理,处于分片状态下时候的中间分片不需要包头,所以通过计数器产生尾端last时候要区别处理,该处理通过r_split_run进行区分。该过程当中不管是读使能信号还是发送计数器,都要区别处理,看着波形调试容易一点,自己想是有点抽象了。
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_split_run <= 'd0;elseif(r_split_flag)
r_split_run <= 'd1;elseif(!r_split_flag && rm_axis_ip_last)
r_split_run <= 'd0;else
r_split_run <= r_split_run;
end
- 长度信息,rm_axis_ip_user是我自定义的信号,包含字节长度信息,只有包头需要额外+8字节。
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
rm_axis_ip_user <= 'd0;elseif(r_first_split &&!r_split_flag)//只有包头需要+8
rm_axis_ip_user <={r_byte_len +16'd8, 3'b010,8'd17, r_offset, 16'd0};elseif(r_split_flag)
rm_axis_ip_user <={16'd1480, 3'b001,8'd17, r_offset, 16'd0};else
rm_axis_ip_user <={r_byte_len,3'b000, 8'd17, r_offset,16'd0};
end
三、TEN_GIG_MAC_RX模块
该模块的调试是最漫长的。。。。。。。。。。。
在实际上板后我发现,主机网卡在发送巨帧的时候,数据之间的间隔只有一个空闲位,也就是说俩帧数据头尾可能是直接挨着的,而之前的MAC接受模块存在一个隐性BUG,无法连续处理接收到的MAC数据,因此会丢失报文,而且CRC也会错乱,进而导致后续的CRC处理模块当中的RAM读取全部出错,大量数据累计在RAM当中无法被取出,在进行回环测试的时候,我上位机发送一个包,过了好久才能收到回环数据,并且还是错的。。
主要修改在于r_crc_run信号的判断。
之前的代码总是留有许多时钟余量,就是我一般都不会过于关注极限情况,觉得不影响后续数据的情况下晚几拍判断出结果更加稳妥,没想到这种思想导致后期BUG调试十分困难,
always @(posedge i_clk or posedge i_rst)begin
if(i_rst)
r_crc_run <= 'd0;elseif(r_sof_location ==7&& w_eof && w_eof_location >=2)
r_crc_run <= 'd0;elseif(r_sof_location ==7&& r_eof && r_eof_location <2)
r_crc_run <= 'd0;elseif(r_sof_location ==3&& w_eof && w_eof_location >=6)
r_crc_run <= 'd0;elseif(r_sof_location ==3&& r_eof && r_eof_location <6)
r_crc_run <= 'd0;// else if(r_crc_end)// r_crc_run <= 'd0;elseif(r_sof &&(r_sof_location ==7|| r_sof_location ==3))
r_crc_run <= 'd1;else
r_crc_run <= r_crc_run;
end
还有一些相关信号的修改,总之都是一些仿真难以暴露的问题。
四、上板实际效果
4.1、发送正常数据:
以下分别是ILA波形、wireshark和网口调试助手所获取的信息,勘验后无误。
4.1、发送巨型UDP数据帧:
以下分别是ILA波形、wireshark和网口调试助手所获取的信息,勘验后无误。
版权归原作者 顺子学不会FPGA 所有, 如有侵权,请联系我们删除。