以太网UDP数据回环实验

这篇具有很好参考价值的文章主要介绍了以太网UDP数据回环实验。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、TCP/IP协议簇

        前面说到TCP/IP是一个协议簇,其中包含有IP协议、TCP协议、UDP协议、ARP协议、DNS协议、FTP协议等。设备之间要想完成通信,就必须通过这些网络通信协议。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

         物理层的主要作用就是传输比特流(将1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,即数模转换与模数转换)。
        数据链路层接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。MAC数据包位于数据链路层,当MAC数据包经过数据链路层到达网络层时,前导码、帧起始界定符、目的 MAC 地址、源 MAC 地址、类型/长度以及校验字节均被去除,只有有效数据传入了网络层。
        网路层通过路由选择算法,为报文(该层的数据单位,由上一层数据打包而来)通过通信子网选择最适当的路径。这一层定义的是IP地址,通过IP地址寻址,所以产生了IP协议。传入网络层的数据包并不完全是需要传输的有效数据,他的前面还包含着 20 字节的IP协议首部。网络层在接收到数据包后,取下数据包的IP首部,将剩余有效数据包发送到传输层。
        而传输层提供了主机应用程序进程之间的端到端的服务,基本功能是:分割与重组数据、按端口号寻址、连接管理、差错控制和流量控制、纠错功能。若传输层使用UDP协议,那么传入传输层的数据包为UDP数据包。
        应用层是计算机用户,以及各种应用程序和网络之间的接口。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

         以太网数据包就是对各层协议的逐层封装来实现数据传输,MAC帧中的数据段为IP数据报文,IP报文中的数据段位UDP报文,UDP报文中的数据段为传输数据。

1.1 IP

        IP协议规定了数据传输时的基本单元和格式,位于以太网MAC格式的数据段,由IP首部和数据字段组成。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

  • 版本(4bit):定义IP协议版本,设置为二进制的0100时表示IPv4,设置为二进制的0110时表示IPv6。
  • 首部长度 (4bit):定义数据报协议头长度,表示IP首部一共有多少个32位。协议头最小值为5,最大值为15。
  • 服务类型 (8bit):定义上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。前3位成为优先位,后面4位成为服务类型,最后1位没有定义。这些8位字段用于分配优先级、延迟、吞吐量以及可靠性。
  • 总长度(16bit):定义整个IP数据报的字节长度,包括协议头部和数据。其最大值为65535字节。
  • 标识 (16bit):包含一个整数,用于标识主机发送的数据报,通常每发送一份数据包值加一。
  • 标记(3bit):由3位字段构成,其中最低位 (ME控制分段,存在下一个分段置为1,否则置0代表该分段是最后一个分段。中间位(DF) 指出数据报是否可进行分段,如果为1则机器不能将该数据报进行分段。第三位即最高位保留不使用,值为0。
  • 分段偏移 (13bit):在接收方进行数据报重组时用来标识分段的顺序。
  • 生存时间 (8bit):一种计数器,在丢弃数据报的每个点值依次减1直至减少为0。这样确保数据报拥有有限的环路过程(即TTL),限制了数据报的寿命。
  • 协议 (8bit):该字段指出在IP处理过程完成之后,有哪种上层协议接收导入数据报。
  • 首部校验和(16bit):该字段帮助确保IP协议头的完整性。由于某些协议头字段的改变,这就需要对每个点重新计算和检验。计算过程是先将校验和字段置为0,然后将整个头部每 16 位划分为一部分,将个部分相加,再将计算结果取反码,插入到校验和字段中。
  • 源地址 (32bit):发送端IP地址,该字段在IPV4数据报从源主机到目的主机传输期间必须保持不变。
  • 目的地址 (32bit):接收端IP地址,该字段在IPv4数据报从源主机到目的主机传输期间同样必须保持不变。

1.1.1 IP首部校验和

IP首部校验和计算步骤:

  1. 将16位检验和字段置为0,然后将IP首部按照16位分成多个单元;
  2. 对各个单元采用反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位);
  3. 此时仍然可能出现进位的情况,将得到的和再次分成高16位和低16位进行累加;
  4. 最后将得到的和的反码填入校验和字段。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

IP首部校验和校验

对IP首部中每个16bit进行二进制反码求和,将计算结果再取反码,若结果为0,通过检验,否则不通过检验。

例:验证IP首部 45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d
(1)对 IP 首部进行反码求和:4500+0030+804c+4000+8006+b52e+d343+117b+cb51+153d=3fffc
     0300+3fffc=ffff
(2)对和结果取反码:~ffff=0,校验正确。

1.2 UDP

        UDP (User Datagram Protocol),即用户数据报协议,是一种面向无连接的传输层协议。无连接是指在传输数据时,数据的发送端和接收端不建立逻辑连接。即发送端只管发送,不会管接收端到底有没有接收到数据,而接收端也不会向发送端反馈反馈是否收到数据。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发UDP首部共8个字节,同IP首部一样,也是一行以32位(4个字节)为单位。

  •  源端口号(16byte):用于区分不同服务的端口,端口号的范围从0到 65535。
  • 目的端口号(16byte):16位接收端端口号。
  • UDP长度(16byte):包含 UDP 首部长度+数据长度。
  • UDP校验和(16byte):提供了与TCP校验字段相同的功能,可选。

1.2.1 UDP校验和

        UDP校验和的计算需要三部分数据:UDP 伪首部、UDP首部和有效数据。伪首部包含IP首部一些字段,其目的是让UDP两次检查数据是否已经正确到达目的地,只是单纯做校验使用。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

UDP校验和计算步骤:校验字节强制置0,将三部分数据按2字节, 即16比特,分开分别相加,若如果大于 FFFF 那么把高16位与低16位相加,直到最终结果为16比特数据。将计算结果取反作为 UDP 校验和字节。 

例:

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

(1) 将校验和字段00 92置为00 00:
a9 fe bf lf a9 fe 01 17 00 11 00 28 04 d2 04 d2 00 28 00 00 68 74 74 70 3a 2f 2f 77 7777 2e 63 6d 73 6f 66 74 2e 63 6e 20 51 51 3a 31 30 38 36 35 36 30 30
(2) 以2字节为单位,数据反码求和:
a9fe + bflf + a9fe + 0117 + 0011 + 0028 + 04d2 + 04d2 + 0028 + 0000 + 6874 + 7470+ 3a2f + 2f77 + 7777 + 2e63 + 6d73 + 6f66 + 742e + 636e + 2051 + 513a + 3130 +3836 + 3536 + 3030 = 6 ff67
(3) 将进位(6)加到低 16 位(f67)上:6 + ff67 = ff6d
(4)将 ff6d 取反得: checksum = 0092 

二、以太网UDP数据回环实验

        上位机通过网口调试助手发送数据给FPGA,FPGA通过以太网接口接收数据并将收到的数据发送给上位机,完成以太网UDP数据的回环。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

        GMII TO RGMII模块负责将双沿 (DDR) 数据和单沿 (SDR) 数据之间的转换,GMII接收侧的引脚同时连接至 ARP顶层模块和UDP顶层模块,这个两个模块会分别根据ARP协议和UDP协议解析数据。而GMII发送侧引脚只能和ARP顶层模块和UDP顶层模块的其中一个连接,因此以太网控制模块会根据当前接收到的协议类型,选择切换GMII发送侧引脚和ARP顶层模块或者UDP顶层模块连接,并且根据输入的ARP接收的类型,控制ARP顶层模块返回ARP应答信号。ARP顶层模块解析ARP 请求命令,并返回开发板的MAC地址。UDP顶层模块实现了以太网 UDP 数据包的接收、发送以及 CRC 校验的功能。以太网单次会接收到大量数据,因此需要FIFO模块用来缓存数据,由于所使用的GMII接收时钟和GMII发送时钟实际上为同一个时钟,因此这里使用同步FIFO。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

2.1 UDP模块

        UDP模块实现了以太网帧格式和UDP协议功能,由UDP接收模块、UDP发送模块和CRC校验模块组成。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

UDP接收模块:接收模块较为简单只需要判断目的MAC地址与开发板MAC地址、目的IP地址与开发板IP地址是否一致。接收模块的解析顺序是:前导码+顺起始界定符→以太网头→IP首部→UDP首部→UDP数据(有效数据)→接收结束。IP数据报一般以32bit为单位,为了和IP数据报格式保持一致,所以要把8位数据转成32位数据,因此接收模块实际上是完成了8位数据转32位数据的功能。

UDP发送模块:发送模块多了IP首部校验和和CRC循环冗余校验的计算,CRC的校验在CRC校验模块里完成。发送模块的发送顺序是前导码+起始界定符→以太网头→IP首部→UDP首部→UDP数据(有效数据)→CRC校验。输入的有效数据为32位数据,GMII接口为8位数据接口,因此发送模块实际上完成的是32位数据转8位数据的功能。

CRC校验模块:CRC校验模块是对UDP发送模块的数据(不包括前导码和起始界定符)做校验,把校验结果值拼在以太网倾格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该顿导致收不到数据。CRC32 校验在FPGA实现的原理是LFSR (Linear Feedback Shif Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。

2.1.1 UDP接收模块

        UDP接收模块按照UDP的数据格式解析数据,并实现将8位用户数据转成32位数据的功能,通过三段式状态机来解析以太网包。

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

module udp_rx(
    input                clk         ,    //时钟信号
    input                rst_n       ,    //复位信号,低电平有效
    
    input                gmii_rx_dv  ,    //GMII输入数据有效信号
    input        [7:0]   gmii_rxd    ,    //GMII输入数据
    output  reg          rec_pkt_done,    //以太网单包数据接收完成信号
    output  reg          rec_en      ,    //以太网接收的数据使能信号
    output  reg  [31:0]  rec_data    ,    //以太网接收的数据
    output  reg  [15:0]  rec_byte_num     //以太网接收的有效字节数 单位:byte     
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55; 
//开发板IP地址 192.168.1.10 
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};

localparam  st_idle     = 7'b000_0001; //初始状态,等待接收前导码
localparam  st_preamble = 7'b000_0010; //接收前导码状态 
localparam  st_eth_head = 7'b000_0100; //接收以太网帧头
localparam  st_ip_head  = 7'b000_1000; //接收IP首部
localparam  st_udp_head = 7'b001_0000; //接收UDP首部
localparam  st_rx_data  = 7'b010_0000; //接收有效数据
localparam  st_rx_end   = 7'b100_0000; //接收结束

localparam  ETH_TYPE    = 16'h0800   ; //以太网协议类型 IP协议
localparam  UDP_TYPE    = 8'd17      ; //UDP协议类型

//reg define
reg  [6:0]   cur_state       ;
reg  [6:0]   next_state      ;
                             
reg          skip_en         ; //控制状态跳转使能信号
reg          error_en        ; //解析错误使能信号
reg  [4:0]   cnt             ; //解析数据计数器
reg  [47:0]  des_mac         ; //目的MAC地址
reg  [15:0]  eth_type        ; //以太网类型
reg  [31:0]  des_ip          ; //目的IP地址
reg  [5:0]   ip_head_byte_num; //IP首部长度
reg  [15:0]  udp_byte_num    ; //UDP长度
reg  [15:0]  data_byte_num   ; //数据长度
reg  [15:0]  data_cnt        ; //有效数据计数    
reg  [1:0]   rec_en_cnt      ; //8bit转32bit计数器

//*****************************************************
//**                    main code
//*****************************************************

//(三段式状态机)同步时序描述状态转移
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;  
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle : begin                                     //等待接收前导码
            if(skip_en) 
                next_state = st_preamble;
            else
                next_state = st_idle;    
        end
        st_preamble : begin                                 //接收前导码
            if(skip_en) 
                next_state = st_eth_head;
            else if(error_en) 
                next_state = st_rx_end;    
            else
                next_state = st_preamble;    
        end
        st_eth_head : begin                                 //接收以太网帧头
            if(skip_en) 
                next_state = st_ip_head;
            else if(error_en) 
                next_state = st_rx_end;
            else
                next_state = st_eth_head;           
        end  
        st_ip_head : begin                                  //接收IP首部
            if(skip_en)
                next_state = st_udp_head;
            else if(error_en)
                next_state = st_rx_end;
            else
                next_state = st_ip_head;       
        end 
        st_udp_head : begin                                 //接收UDP首部
            if(skip_en)
                next_state = st_rx_data;
            else
                next_state = st_udp_head;    
        end                
        st_rx_data : begin                                  //接收有效数据
            if(skip_en)
                next_state = st_rx_end;
            else
                next_state = st_rx_data;    
        end                           
        st_rx_end : begin                                   //接收结束
            if(skip_en)
                next_state = st_idle;
            else
                next_state = st_rx_end;          
        end
        default : next_state = st_idle;
    endcase                                          
end    

//时序电路描述状态输出,解析以太网数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        skip_en <= 1'b0;
        error_en <= 1'b0;
        cnt <= 5'd0;
        des_mac <= 48'd0;
        eth_type <= 16'd0;
        des_ip <= 32'd0;
        ip_head_byte_num <= 6'd0;
        udp_byte_num <= 16'd0;
        data_byte_num <= 16'd0;
        data_cnt <= 16'd0;
        rec_en_cnt <= 2'd0;
        rec_en <= 1'b0;
        rec_data <= 32'd0;
        rec_pkt_done <= 1'b0;
        rec_byte_num <= 16'd0;
    end
    else begin
        skip_en <= 1'b0;
        error_en <= 1'b0;  
        rec_en <= 1'b0;
        rec_pkt_done <= 1'b0;
        case(next_state)
            st_idle : begin
                if((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55)) 
                    skip_en <= 1'b1;
            end
            st_preamble : begin
                if(gmii_rx_dv) begin                         //解析前导码
                    cnt <= cnt + 5'd1;
                    if((cnt < 5'd6) && (gmii_rxd != 8'h55))  //7个8'h55  
                        error_en <= 1'b1;
                    else if(cnt==5'd6) begin
                        cnt <= 5'd0;
                        if(gmii_rxd==8'hd5)                  //1个8'hd5
                            skip_en <= 1'b1;
                        else
                            error_en <= 1'b1;    
                    end  
                end  
            end
            st_eth_head : begin
                if(gmii_rx_dv) begin
                    cnt <= cnt + 5'b1;
                    if(cnt < 5'd6) 
                        des_mac <= {des_mac[39:0],gmii_rxd}; //目的MAC地址
                    else if(cnt == 5'd12) 
                        eth_type[15:8] <= gmii_rxd;          //以太网协议类型
                    else if(cnt == 5'd13) begin
                        eth_type[7:0] <= gmii_rxd;
                        cnt <= 5'd0;
                        //判断MAC地址是否为开发板MAC地址或者公共地址
                        if(((des_mac == BOARD_MAC) ||(des_mac == 48'hff_ff_ff_ff_ff_ff))
                       && eth_type[15:8] == ETH_TYPE[15:8] && gmii_rxd == ETH_TYPE[7:0])            
                            skip_en <= 1'b1;
                        else
                            error_en <= 1'b1;
                    end        
                end  
            end
            st_ip_head : begin
                if(gmii_rx_dv) begin
                    cnt <= cnt + 5'd1;
                    if(cnt == 5'd0)
                        ip_head_byte_num <= {gmii_rxd[3:0],2'd0};  //寄存IP首部长度
                    else if(cnt == 5'd9) begin
                        if(gmii_rxd != UDP_TYPE) begin
                            //如果当前接收的数据不是UDP协议,停止解析数据                        
                            error_en <= 1'b1;               
                            cnt <= 5'd0;                        
                        end
                    end                    
                    else if((cnt >= 5'd16) && (cnt <= 5'd18))
                        des_ip <= {des_ip[23:0],gmii_rxd};         //寄存目的IP地址
                    else if(cnt == 5'd19) begin
                        des_ip <= {des_ip[23:0],gmii_rxd}; 
                        //判断IP地址是否为开发板IP地址
                        if((des_ip[23:0] == BOARD_IP[31:8])
                            && (gmii_rxd == BOARD_IP[7:0])) begin  
                            if(cnt == ip_head_byte_num - 1'b1) begin
                                skip_en <=1'b1;                     
                                cnt <= 5'd0;
                            end                             
                        end    
                        else begin            
                            //IP错误,停止解析数据                        
                            error_en <= 1'b1;               
                            cnt <= 5'd0;
                        end                                                  
                    end                          
                    else if(cnt == ip_head_byte_num - 1'b1) begin 
                        skip_en <=1'b1;                      //IP首部解析完成
                        cnt <= 5'd0;                    
                    end    
                end                                
            end 
            st_udp_head : begin
                if(gmii_rx_dv) begin
                    cnt <= cnt + 5'd1;
                    if(cnt == 5'd4)
                        udp_byte_num[15:8] <= gmii_rxd;      //解析UDP字节长度 
                    else if(cnt == 5'd5)
                        udp_byte_num[7:0] <= gmii_rxd;
                    else if(cnt == 5'd7) begin
                        //有效数据字节长度,(UDP首部8个字节,所以减去8)
                        data_byte_num <= udp_byte_num - 16'd8;    
                        skip_en <= 1'b1;
                        cnt <= 5'd0;
                    end  
                end                 
            end          
            st_rx_data : begin         
                //接收数据,转换成32bit            
                if(gmii_rx_dv) begin
                    data_cnt <= data_cnt + 16'd1;
                    rec_en_cnt <= rec_en_cnt + 2'd1;
                    if(data_cnt == data_byte_num - 16'd1) begin
                        skip_en <= 1'b1;                    //有效数据接收完成
                        data_cnt <= 16'd0;
                        rec_en_cnt <= 2'd0;
                        rec_pkt_done <= 1'b1;               
                        rec_en <= 1'b1;                     
                        rec_byte_num <= data_byte_num;
                    end    
                    //先收到的数据放在了rec_data的高位,所以当数据不是4的倍数时,
                    //低位数据为无效数据,可根据有效字节数来判断(rec_byte_num)
                    if(rec_en_cnt == 2'd0)
                        rec_data[31:24] <= gmii_rxd;
                    else if(rec_en_cnt == 2'd1)
                        rec_data[23:16] <= gmii_rxd;
                    else if(rec_en_cnt == 2'd2) 
                        rec_data[15:8] <= gmii_rxd;        
                    else if(rec_en_cnt==2'd3) begin
                        rec_en <= 1'b1;
                        rec_data[7:0] <= gmii_rxd;
                    end    
                end  
            end    
            st_rx_end : begin                               //单包数据接收完成   
                if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
                    skip_en <= 1'b1; 
            end    
            default : ;
        endcase                                                        
    end
end

endmodule

2.1.2 UDP发送模块

udp 环回测试,一般人学不会的FPGA,udp,网络协议,fpga,fpga开发

module udp_tx(    
    input                clk        , //时钟信号
    input                rst_n      , //复位信号,低电平有效
    
    input                tx_start_en, //以太网开始发送信号
    input        [31:0]  tx_data    , //以太网待发送数据  
    input        [15:0]  tx_byte_num, //以太网发送的有效字节数
    input        [47:0]  des_mac    , //发送的目标MAC地址
    input        [31:0]  des_ip     , //发送的目标IP地址    
    input        [31:0]  crc_data   , //CRC校验数据
    input         [7:0]  crc_next   , //CRC下次校验完成数据
    output  reg          tx_done    , //以太网发送完成信号
    output  reg          tx_req     , //读数据请求信号
    output  reg          gmii_tx_en , //GMII输出数据有效信号
    output  reg  [7:0]   gmii_txd   , //GMII输出数据
    output  reg          crc_en     , //CRC开始校验使能
    output  reg          crc_clr      //CRC数据复位信号 
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10   
parameter BOARD_IP  = {8'd192,8'd168,8'd1,8'd10}; 
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102     
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};

localparam  st_idle      = 7'b000_0001; //初始状态,等待开始发送信号
localparam  st_check_sum = 7'b000_0010; //IP首部校验和
localparam  st_preamble  = 7'b000_0100; //发送前导码+帧起始界定符
localparam  st_eth_head  = 7'b000_1000; //发送以太网帧头
localparam  st_ip_head   = 7'b001_0000; //发送IP首部+UDP首部
localparam  st_tx_data   = 7'b010_0000; //发送数据
localparam  st_crc       = 7'b100_0000; //发送CRC校验值

localparam  ETH_TYPE     = 16'h0800   ; //以太网协议类型 IP协议
//以太网数据最小46个字节,IP首部20个字节+UDP首部8个字节
//所以数据至少46-20-8=18个字节
localparam  MIN_DATA_NUM = 16'd18     ;  
localparam  UDP_TYPE    = 8'd17       ; //UDP协议类型  

//reg define
reg  [6:0]   cur_state      ;
reg  [6:0]   next_state     ;
                            
reg  [7:0]   preamble[7:0]  ; //前导码
reg  [7:0]   eth_head[13:0] ; //以太网首部
reg  [31:0]  ip_head[6:0]   ; //IP首部 + UDP首部
                            
reg          start_en_d0    ;
reg          start_en_d1    ;
reg  [15:0]  tx_data_num    ; //发送的有效数据字节个数
reg  [15:0]  total_num      ; //总字节数
reg          trig_tx_en     ;
reg  [15:0]  udp_num        ; //UDP字节数
reg          skip_en        ; //控制状态跳转使能信号
reg  [4:0]   cnt            ;
reg  [31:0]  check_buffer   ; //首部校验和
reg  [1:0]   tx_byte_sel    ; //32位数据转8位数据计数器
reg  [15:0]  data_cnt       ; //发送数据个数计数器
reg          tx_done_t      ;
reg  [4:0]   real_add_cnt   ; //以太网数据实际多发的字节数
                                    
//wire define                       
wire         pos_start_en    ;//开始发送数据上升沿
wire [15:0]  real_tx_data_num;//实际发送的字节数(以太网最少字节要求)
//*****************************************************
//**                    main code
//*****************************************************

assign  pos_start_en = (~start_en_d1) & start_en_d0;
assign  real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) 
                           ? tx_data_num : MIN_DATA_NUM; 
                           
//采tx_start_en的上升沿
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        start_en_d0 <= 1'b0;
        start_en_d1 <= 1'b0;
    end    
    else begin
        start_en_d0 <= tx_start_en;
        start_en_d1 <= start_en_d0;
    end
end 

//寄存数据有效字节
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_data_num <= 16'd0;
        total_num <= 16'd0;
        udp_num <= 16'd0;
    end
    else begin
        if(pos_start_en && cur_state==st_idle) begin
            //数据长度
            tx_data_num <= tx_byte_num;     
            //UDP长度:UDP首部长度 + 有效数据            
            udp_num <= tx_byte_num + 16'd8;               
            //IP长度:IP首部长度 + UDP首部 + 有效数据             
            total_num <= tx_byte_num + 16'd20 + 16'd8;  
        end    
    end
end

//触发发送信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        trig_tx_en <= 1'b0;
    else
        trig_tx_en <= pos_start_en;

end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;  
    else
        cur_state <= next_state;
end

always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle     : begin                               //等待发送数据
            if(skip_en)                
                next_state = st_check_sum;
            else
                next_state = st_idle;
        end  
        st_check_sum: begin                               //IP首部校验
            if(skip_en)
                next_state = st_preamble;
            else
                next_state = st_check_sum;    
        end                             
        st_preamble : begin                               //发送前导码+帧起始界定符
            if(skip_en)
                next_state = st_eth_head;
            else
                next_state = st_preamble;      
        end
        st_eth_head : begin                               //发送以太网首部
            if(skip_en)
                next_state = st_ip_head;
            else
                next_state = st_eth_head;      
        end              
        st_ip_head : begin                                //发送IP首部+UDP首部               
            if(skip_en)
                next_state = st_tx_data;
            else
                next_state = st_ip_head;      
        end
        st_tx_data : begin                                //发送数据                  
            if(skip_en)
                next_state = st_crc;
            else
                next_state = st_tx_data;      
        end
        st_crc: begin                                     //发送CRC校验值
            if(skip_en)
                next_state = st_idle;
            else
                next_state = st_crc;      
        end
        default : next_state = st_idle;   
    endcase
end                      

//发送数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        skip_en <= 1'b0; 
        cnt <= 5'd0;
        check_buffer <= 32'd0;
        ip_head[1][31:16] <= 16'd0;
        tx_byte_sel <= 2'b0;
        crc_en <= 1'b0;
        gmii_tx_en <= 1'b0;
        gmii_txd <= 8'd0;
        tx_req <= 1'b0;
        tx_done_t <= 1'b0; 
        data_cnt <= 16'd0;
        real_add_cnt <= 5'd0;
        //初始化数组    
        //前导码 7个8'h55 + 1个8'hd5
        preamble[0] <= 8'h55;                 
        preamble[1] <= 8'h55;
        preamble[2] <= 8'h55;
        preamble[3] <= 8'h55;
        preamble[4] <= 8'h55;
        preamble[5] <= 8'h55;
        preamble[6] <= 8'h55;
        preamble[7] <= 8'hd5;
        //目的MAC地址
        eth_head[0] <= DES_MAC[47:40];
        eth_head[1] <= DES_MAC[39:32];
        eth_head[2] <= DES_MAC[31:24];
        eth_head[3] <= DES_MAC[23:16];
        eth_head[4] <= DES_MAC[15:8];
        eth_head[5] <= DES_MAC[7:0];
        //源MAC地址
        eth_head[6] <= BOARD_MAC[47:40];
        eth_head[7] <= BOARD_MAC[39:32];
        eth_head[8] <= BOARD_MAC[31:24];
        eth_head[9] <= BOARD_MAC[23:16];
        eth_head[10] <= BOARD_MAC[15:8];
        eth_head[11] <= BOARD_MAC[7:0];
        //以太网类型
        eth_head[12] <= ETH_TYPE[15:8];
        eth_head[13] <= ETH_TYPE[7:0];        
    end
    else begin
        skip_en <= 1'b0;
        tx_req <= 1'b0;
        crc_en <= 1'b0;
        gmii_tx_en <= 1'b0;
        tx_done_t <= 1'b0;
        case(next_state)
            st_idle     : begin
                if(trig_tx_en) begin
                    skip_en <= 1'b1; 
                    //版本号:4 首部长度:5(单位:32bit,20byte/4=5)
                    ip_head[0] <= {8'h45,8'h00,total_num};   
                    //16位标识,每次发送累加1      
                    ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1; 
                    //bit[15:13]: 010表示不分片
                    ip_head[1][15:0] <= 16'h4000;    
                    //协议:17(udp)                  
                    ip_head[2] <= {8'h40,UDP_TYPE,16'h0};   
                    //源IP地址               
                    ip_head[3] <= BOARD_IP;
                    //目的IP地址    
                    if(des_ip != 32'd0)
                        ip_head[4] <= des_ip;
                    else
                        ip_head[4] <= DES_IP;       
                    //16位源端口号:1234  16位目的端口号:1234                      
                    ip_head[5] <= {16'd1234,16'd1234};  
                    //16位udp长度,16位udp校验和              
                    ip_head[6] <= {udp_num,16'h0000};  
                    //更新MAC地址
                    if(des_mac != 48'b0) begin
                        //目的MAC地址
                        eth_head[0] <= des_mac[47:40];
                        eth_head[1] <= des_mac[39:32];
                        eth_head[2] <= des_mac[31:24];
                        eth_head[3] <= des_mac[23:16];
                        eth_head[4] <= des_mac[15:8];
                        eth_head[5] <= des_mac[7:0];
                    end
                end    
            end                                                       
            st_check_sum: begin                           //IP首部校验
                cnt <= cnt + 5'd1;
                if(cnt == 5'd0) begin                   
                    check_buffer <= ip_head[0][31:16] + ip_head[0][15:0]
                                    + ip_head[1][31:16] + ip_head[1][15:0]
                                    + ip_head[2][31:16] + ip_head[2][15:0]
                                    + ip_head[3][31:16] + ip_head[3][15:0]
                                    + ip_head[4][31:16] + ip_head[4][15:0];
                end
                else if(cnt == 5'd1)                      //可能出现进位,累加一次
                    check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                else if(cnt == 5'd2) begin                //可能再次出现进位,累加一次
                    check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                end                             
                else if(cnt == 5'd3) begin                //按位取反 
                    skip_en <= 1'b1;
                    cnt <= 5'd0;            
                    ip_head[2][15:0] <= ~check_buffer[15:0];
                end    
            end              
            st_preamble : begin                           //发送前导码+帧起始界定符
                gmii_tx_en <= 1'b1;
                gmii_txd <= preamble[cnt];
                if(cnt == 5'd7) begin                        
                    skip_en <= 1'b1;
                    cnt <= 5'd0;    
                end
                else    
                    cnt <= cnt + 5'd1;                     
            end
            st_eth_head : begin                           //发送以太网首部
                gmii_tx_en <= 1'b1;
                crc_en <= 1'b1;
                gmii_txd <= eth_head[cnt];
                if (cnt == 5'd13) begin
                    skip_en <= 1'b1;
                    cnt <= 5'd0;
                end    
                else    
                    cnt <= cnt + 5'd1;    
            end                    
            st_ip_head  : begin                           //发送IP首部 + UDP首部
                crc_en <= 1'b1;
                gmii_tx_en <= 1'b1;
                tx_byte_sel <= tx_byte_sel + 2'd1;
                if(tx_byte_sel == 2'd0)
                    gmii_txd <= ip_head[cnt][31:24];
                else if(tx_byte_sel == 2'd1)
                    gmii_txd <= ip_head[cnt][23:16];
                else if(tx_byte_sel == 2'd2) begin
                    gmii_txd <= ip_head[cnt][15:8];
                    if(cnt == 5'd6) begin
                        //提前读请求数据,等待数据有效时发送
                        tx_req <= 1'b1;                     
                    end
                end 
                else if(tx_byte_sel == 2'd3) begin
                    gmii_txd <= ip_head[cnt][7:0];  
                    if(cnt == 5'd6) begin
                        skip_en <= 1'b1;   
                        cnt <= 5'd0;
                    end    
                    else
                        cnt <= cnt + 5'd1;  
                end        
            end
           st_tx_data  : begin                           //发送数据
                crc_en <= 1'b1;
                gmii_tx_en <= 1'b1;
                tx_byte_sel <= tx_byte_sel + 2'd1;  
                if(tx_byte_sel == 1'b0)
                    gmii_txd <= tx_data[31:24];
                else if(tx_byte_sel == 2'd1)
                    gmii_txd <= tx_data[23:16];                   
                else if(tx_byte_sel == 2'd2) begin
                    gmii_txd <= tx_data[15:8];   
                    if(data_cnt != tx_data_num - 16'd2)
                        tx_req <= 1'b1;  
                end
                else if(tx_byte_sel == 2'd3)
                    gmii_txd <= tx_data[7:0];    
                if(data_cnt < tx_data_num - 16'd1)
                    data_cnt <= data_cnt + 16'd1;                        
                else if(data_cnt == tx_data_num - 16'd1)begin
                    //如果发送的有效数据少于18个字节,在后面填补充位
                    //填充的值为0
                    tx_req <= 1'b0;
                    if(data_cnt + real_add_cnt < real_tx_data_num - 16'd1)
                        real_add_cnt <= real_add_cnt + 5'd1;  
                    else begin
                        skip_en <= 1'b1;
                        data_cnt <= 16'd0;
                        real_add_cnt <= 5'd0;
                        tx_byte_sel <= 2'd0;                        
                    end   
                    if(real_add_cnt > 0) begin
                        gmii_txd <= 8'd0;
                    end    
                end   
            end   
            st_crc      : begin                          //发送CRC校验值
                gmii_tx_en <= 1'b1;
                tx_byte_sel <= tx_byte_sel + 2'd1;
                if(tx_byte_sel == 2'd0)
                    gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
                                 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
                else if(tx_byte_sel == 2'd1)
                    gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],~crc_data[19],
                                 ~crc_data[20], ~crc_data[21], ~crc_data[22],~crc_data[23]};
                else if(tx_byte_sel == 2'd2) begin
                    gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],
                                 ~crc_data[12], ~crc_data[13], ~crc_data[14],~crc_data[15]};                              
                end
                else if(tx_byte_sel == 2'd3) begin
                    gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
                                 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};  
                    tx_done_t <= 1'b1;
                    skip_en <= 1'b1;
                end                                                                                                                                            
            end                          
            default :;  
        endcase                                             
    end
end            

//发送完成信号及crc值复位信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_done <= 1'b0;
        crc_clr <= 1'b0;
    end
    else begin
        tx_done <= tx_done_t;
        crc_clr <= tx_done_t;
    end
end

endmodule

2.2 UDP控制模块

module eth_ctrl(
    input              clk       ,     //系统时钟
    input              rst_n     ,     //系统复位信号,低电平有效 
    //ARP相关端口信号                                   
    input              arp_rx_done,    //ARP接收完成信号
    input              arp_rx_type,    //ARP接收类型 0:请求  1:应答
    output  reg        arp_tx_en,      //ARP发送使能信号
    output             arp_tx_type,    //ARP发送类型 0:请求  1:应答
    input              arp_tx_done,    //ARP发送完成信号
    input              arp_gmii_tx_en, //ARP GMII输出数据有效信号 
    input     [7:0]    arp_gmii_txd,   //ARP GMII输出数据
    //UDP相关端口信号
    input              udp_tx_start_en,//UDP开始发送信号
    input              udp_tx_done,    //UDP发送完成信号
    input              udp_gmii_tx_en, //UDP GMII输出数据有效信号  
    input     [7:0]    udp_gmii_txd,   //UDP GMII输出数据   
    //GMII发送引脚                     
    output             gmii_tx_en,     //GMII输出数据有效信号 
    output    [7:0]    gmii_txd        //UDP GMII输出数据 
    );

//reg define
reg        protocol_sw; //协议切换信号
reg        udp_tx_busy; //UDP正在发送数据标志信号
reg        arp_rx_flag; //接收到ARP请求信号的标志

//*****************************************************
//**                    main code
//*****************************************************

assign arp_tx_type = 1'b1;   //ARP发送类型固定为ARP应答                                   
assign gmii_tx_en = protocol_sw ? udp_gmii_tx_en : arp_gmii_tx_en;
assign gmii_txd = protocol_sw ? udp_gmii_txd : arp_gmii_txd;

//控制UDP发送忙信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        udp_tx_busy <= 1'b0;
    else if(udp_tx_start_en)   
        udp_tx_busy <= 1'b1;
    else if(udp_tx_done)
        udp_tx_busy <= 1'b0;
end

//控制接收到ARP请求信号的标志
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        arp_rx_flag <= 1'b0;
    else if(arp_rx_done && (arp_rx_type == 1'b0))   
        arp_rx_flag <= 1'b1;
    else if(protocol_sw == 1'b0)
        arp_rx_flag <= 1'b0;
end

//控制protocol_sw和arp_tx_en信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        protocol_sw <= 1'b0;
        arp_tx_en <= 1'b0;
    end
    else begin
        arp_tx_en <= 1'b0;
        if(udp_tx_start_en)
            protocol_sw <= 1'b1;
        else if(arp_rx_flag && (udp_tx_busy == 1'b0)) begin
            protocol_sw <= 1'b0;
            arp_tx_en <= 1'b1;
        end    
    end        
end


endmodule

其他模块和以太网ARP测试实验一样

 文章来源地址https://www.toymoban.com/news/detail-816989.html

到了这里,关于以太网UDP数据回环实验的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 千兆以太网传输层 UDP 协议原理与 FPGA 实现(UDP接收)

    相关文章: (1)千兆以太网网络层 ARP 协议的原理与 FPGA 实现 (2)千兆以太网硬件设计及链路层 MAC 协议格式 (3)CRC校验原理及实现 (4)RGMII 与 GMII 转换电路设计 (5)千兆以太网网络层 IP 协议介绍与 IP 校 验和算法实现 (6)千兆以太网传输层 UDP 协议原理与 FPGA 实现(

    2024年02月04日
    浏览(67)
  • 以太网Checksum-UDP手算教学

    tcp/ip的checksum计算步骤及方法请见如下链接: 以太网Checksum-TCP/IP手算拆解教学-CSDN博客 1、UDP数据结构如图 在计算UDP首部校验和时,计算范围包括了伪首部、UDP首部、UDP数据报,其中各个部分数据容量如下: 伪首部—---------12字节=48字符 其中 源IP地址---------4字节=16字符 目的

    2024年04月28日
    浏览(9)
  • 基于UDP协议的千兆以太网传输(FPGA)

    @[TOC]基于UDP协议的千兆以太网传输(FPGA) UDP协议是一种基于无连接协议,即发送端发送数据无需确认接收端是否存在;接收端收到数据后也无需给发送端反馈是否收到,所以UDP在数据发送过程中允许丢失一两包数据。用于对丢包不严格的场合,比如视频流,偶有一两帧的丢

    2024年02月12日
    浏览(36)
  • 以太网协议介绍(ARP、UDP、ICMP、IP)

    请求: 应答: ARP协议: 以太网ARP数据包格式: FCS:校验 在以太网帧头部分也包含了目的地址的mac和ip地址。 以太网通信是通过包的形式进行传输的,不像串口协议一样单位是byte。 帧间隙: TCP和UDP的区别 ​ osi 7层模型:实现通信的任务 对于fpga开发,只需要关注传输层,

    2024年02月03日
    浏览(20)
  • FPGA优质开源项目 - UDP RGMII千兆以太网

    本文介绍一个FPGA开源项目:UDP RGMII千兆以太网通信。该项目在我之前的工作中主要是用于FPGA和电脑端之间进行图像数据传输。本文简要介绍一下该项目的千兆以太网通信方案、以太网IP核的使用以及Vivado工程源代码结构。 Vivado 的 Tri Mode Ethernet MAC IP核需要付费才能使用,因

    2024年02月14日
    浏览(19)
  • FPGA优质开源项目 – UDP万兆光纤以太网通信

    本文开源一个FPGA项目:UDP万兆光通信。该项目实现了万兆光纤以太网数据回环传输功能。Vivado工程代码结构和之前开源的《UDP RGMII千兆以太网》类似,只不过万兆以太网是调用了Xilinx的10G Ethernet Subsystem IP核实现。 下面围绕该IP核的使用、用户接口,以及数据传输方案展开介

    2024年02月10日
    浏览(16)
  • FPGA实现千兆/百兆自适应以太网UDP传输

    笔者最近在项目中需要使用到ZYNQ中PL端做以太网UDP传输并且需要支持100M/1000M自适应切换。使用的PHY型号为RTL8211。以下分享的主要为利用已有的1000M协议栈修改为100M并且实现二者自适应切换,IP核主要实现以下功能 1、实现100M/1000M自适应 2、回环测试 PS:完整的IP核文件下载地

    2024年01月21日
    浏览(21)
  • 米联客udp_stack以太网协议栈使用教程

    实验室有一块米联客出的Kintex-7的板子,被我用来做毕业设计,其中使用到了千兆以太网的外设,于是打算使用米联客自己教程中所使用的协议栈来开发千兆以太网通信,接着就接触到了米联客的教程udp_stack.dcp。 //如果帮到你,一定要点赞收藏哦!// 博主在很多设计部分参考

    2024年02月16日
    浏览(45)
  • ZYNQ AXI_DMA_UDP以太网传输(二)问题记录

    上一篇文章只是简单的记录一下调试成功的代码 但调试成功这个过程很痛苦,踩了很多坑,特此记录,留眼以后查看 参考博客 xilinx dma调试笔记 ZYNQ AXI DMA调试细节 在调试过程中出现这类问题基本上都是这一句代码出了问题: 再往里面跳可以看见这样一个函数,在正点原子提

    2024年03月14日
    浏览(26)
  • LwIP系列(3):以太网帧、IP、TCP、UDP、IGMP、ICMP帧格式详解

    TCP/IP 本质上是软件协议,而LwIP也是对软件协议进行解析处理,所以我们有必要了解下以太网帧、IP、TCP、UDP、IGMP、ICMP帧格式,这样在代码中,才能有的放矢。 以太网帧是最底层的原始数据,帧框架如下图所示: 其中【前同步码】和【以太网尾部】我们在抓包的时候,并不

    2024年02月14日
    浏览(17)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包