万兆以太网MAC设计(14)FPGA实现巨型以太网数据帧传输

前言

万兆以太网设计最终章节,巨型以太网数据帧传输设计。对于标准以太网而言,数据传输范围为46-1500字节,当大于1500字节后数据将无法传输。在IP层的报文描述当中,有一个分片字段,通过该字段即可实现将巨型数据帧拆分为多个小于1500字节的数据进行传输。

一、UDP_RX模块设计

在UDP接收模块当中增加以下新设计:

  1. 将接收到的数据先存入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));
  1. 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;
    else if(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;
    else if(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;
    else if(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
  1. 在分片时,只有第一个分片有UDP包头信息,其余分片都没有,所以在判断UDP的源和目的端口号的时候需要选判断第一片数据。
//偏移为0才表示当前udp数据包是第一个包,当分片时只有第一个udp包带包头信息
always @(posedge i_clk or posedge i_rst) begin
    if(i_rst)
        r_udp_header <= 'd0;
    else if(rs_axis_ip_last)
        r_udp_header <= 'd0;
    else if(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头了。

  1. 用户输入数据进入FIFO,该模块会判断user信号当中的字节数目,即是否需要分片。
//用户数据字节长度,每次发送结束后更新,
//如果是第一个包,当其小于1472则不用分片
//如果不是第一个包,当其小于1480则不用继续分片
always @(posedge i_clk or posedge i_rst) begin
    if(i_rst)
        r_byte_len <= 'd0;
    else if(s_axis_user_valid && !rs_axis_user_valid)
        r_byte_len <= s_axis_user_user[15:0];
    else if(r_first_split && rm_axis_ip_last && r_split_flag)
        r_byte_len <= r_byte_len - 1472;
    else if(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;
    else if(s_axis_user_valid && !rs_axis_user_valid && s_axis_user_user[15:0] <= 1472)
        r_split_flag <= 'd0;
    else if(s_axis_user_valid && !rs_axis_user_valid && s_axis_user_user[15:0] > 1472)
        r_split_flag <= 'd1;
    else if(r_split_flag && rm_axis_ip_last_1d && r_byte_len > 1480)
        r_split_flag <= 'd1;
    else if(r_split_flag && rm_axis_ip_last_1d && r_byte_len <= 1480)
        r_split_flag <= 'd0;
    else
        r_split_flag <= r_split_flag;
end
  1. 计算字节偏移,这个偏移是要字节数目除以8的,第一个包发1480字节,则第二个包的字节偏移为1480字节,除以8即为185.
always @(posedge i_clk or posedge i_rst) begin
    if(i_rst)
        r_offset <= 'd0;
    else if(r_split_flag && rm_axis_ip_last)
        r_offset <= r_offset + 16'd185;
    else if(r_split_run && !r_split_flag && rm_axis_ip_last)
        r_offset <= 'd0;
    else
        r_offset <= r_offset;
end
  1. 最麻烦的地方在于尾端处理,处于分片状态下时候的中间分片不需要包头,所以通过计数器产生尾端last时候要区别处理,该处理通过r_split_run进行区分。该过程当中不管是读使能信号还是发送计数器,都要区别处理,看着波形调试容易一点,自己想是有点抽象了。
always @(posedge i_clk or posedge i_rst) begin
    if(i_rst)
        r_split_run <= 'd0;
    else if(r_split_flag)
        r_split_run <= 'd1;
    else if(!r_split_flag && rm_axis_ip_last)
        r_split_run <= 'd0;
    else
        r_split_run <= r_split_run;
end
  1. 长度信息,rm_axis_ip_user是我自定义的信号,包含字节长度信息,只有包头需要额外+8字节。
always @(posedge i_clk or posedge i_rst) begin
    if(i_rst)
        rm_axis_ip_user <= 'd0;
    else if(r_first_split && !r_split_flag)//只有包头需要+8
        rm_axis_ip_user <= {r_byte_len + 16'd8, 3'b010, 8'd17, r_offset, 16'd0};
    else if(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;
    else if(r_sof_location == 7 && w_eof && w_eof_location >= 2)
        r_crc_run <= 'd0;
    else if(r_sof_location == 7 && r_eof && r_eof_location < 2)
        r_crc_run <= 'd0;
    else if(r_sof_location == 3 && w_eof && w_eof_location >= 6)
        r_crc_run <= 'd0;
    else if(r_sof_location == 3 && r_eof && r_eof_location < 6)
        r_crc_run <= 'd0;
    // else if(r_crc_end)
    //     r_crc_run <= 'd0;
    else if(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和网口调试助手所获取的信息,勘验后无误。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顺子学不会FPGA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值