以太网UDP协议栈实现(支持ARP、ICMP、UDP)--FPGA学习笔记26

纯verilog实现,仅使用锁相环IP、FIFO IP,方便跨平台移植。支持ping指令。

以太网系列文章:

以太网ICMP协议(ping指令)------FPGA学习笔记25-CSDN博客

以太网ARP协议------FPGA学习笔记23-CSDN博客

以太网PHY_MDIO通信(基于RTL8211)--FPGA学习笔记22_mdio前导码-CSDN博客

FPGA千兆网口数据传输MDIO接口------FPGA学习笔记3_yt8531sh原理图-CSDN博客

一、UDP简介

UDP(User Datagram Protocol),即用户数据报协议, 是一种面向无连接的传输层协议。无连接是指在传输数据时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用 UDP 协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输(如视频会议等)都会采用 UDP 协议进行传输,这种情况即使偶尔丢失一两个数据包,也不会对接收结果

产生太大影响。

二、UDP协议

UDP 首部共 8 个字节,同 IP 首部一样,也是一行以 32 位(4 个字节)为单位。
源端口号: 16 位发送端端口号,用于区分不同服务的端口,端口号的范围从 0 到 65535。
目的端口号: 16 位接收端端口号。
UDP 长度: 16 位 UDP 长度,包含 UDP 首部长度+数据长度,单位是字节(byte)。
UDP 校验和: 16 位 UDP 校验和。 UDP 计算校验和的方法和计算 IP 数据报首部校验和的方法相似,但不同的是 IP 数据报的校验和只检验 IP 数据报的首部,而 UDP 校验和包含三个部分:UDP 伪首部, UDP 首部和 UDP 的数据部分 。伪首部的数据是从 IP 数据报头和 UDP 数据报头获取的,包括源 IP 地址,目的 IP地址,协议类型和 UDP 长度,其目的是让 UDP 两次检查数据是否已经正确到达目的地,只是单纯为了做校验用的。在大多数使用场景中接收端并不检测 UDP 校验和,因此这里不做过多介绍。

用户数据打包在 UDP 协议中, UDP 协议又是基于 IP 协议之上的, IP 协议又是走 MAC 层发送的,即从包含关系来说: MAC 帧中的数据段为 IP 数据报, IP 报文中的数据段为 UDP 报文, UDP 报文中的数据段为用户希望传输的数据内容。

三、目标实现UDP协议(含ICMP协议)

四、代码编写

1、UDP框图

(1)udp_rx

cs 复制代码
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  [7 :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  [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        ; //有效数据计数    

//*****************************************************
//**                    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
        
    end 
    else begin
        skip_en         <=  1'b0;
        error_en        <=  1'b0;
        rec_pkt_done    <=  1'b0;
        case (next_state)
            st_idle    :begin
                if ((gmii_rx_dv == 1'b1)&&(gmii_rxd == 8'h55)) begin
                    skip_en     <=  1'b1;
                end 
            end
            st_preamble:begin
                if (gmii_rx_dv) begin
                    cnt <= cnt + 1'b1;
                    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 + 1'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 + 1'b1;
                    if (cnt == 5'd0) begin
                        ip_head_byte_num <= {gmii_rxd[3:0],2'd0};
                    end
                    else if (cnt == 5'd9) begin
                        if (gmii_rxd != UDP_TYPE) begin
                            error_en    <= 1'b1;
                            cnt         <= 5'd0;
                        end
                    end 
                    else if ((cnt >= 5'd16)&&(cnt <= 5'd18))begin       //目的IP地址
                        des_ip  <=  {des_ip[23:0],gmii_rxd};
                    end
                    else if (cnt == 5'd19) begin
                        des_ip  <=  {des_ip[23:0],gmii_rxd};        
                        if ({des_ip[23:0],gmii_rxd} == BOARD_IP) begin  //判断目的IP是否为开发板
                            skip_en     <= 1'b1;
                            cnt         <= 5'd0;
                        end
                        else begin                                      //IP错误
                            error_en    <=  1'b0;
                            cnt         <=  5'd0;
                        end 
                    end 
                end 
            end
            st_udp_head:begin
                if (gmii_rx_dv) begin
                    cnt <= cnt + 1'b1;
                    if ((cnt >= 5'd4)&&(cnt <= 5'd5)) begin             //解析UDP字节长度
                        udp_byte_num    <=  {udp_byte_num[7:0],gmii_rxd};
                    end 
                    else if(cnt == 5'd7) begin                          //有效长度=字节长度-首部长度
                        data_byte_num   <=  udp_byte_num - 16'd8;
                        skip_en         <=  1'b1;
                        cnt             <=  5'd0;
                    end
                end 
            end
            st_rx_data :begin
                if (gmii_rx_dv) begin
                    data_cnt    <=  data_cnt + 1'b1 ;                   //接收数据计数器
                    rec_data    <=  gmii_rxd        ;                   //以太网接收数据
                    rec_en      <=  1'b1            ;                   //接收数据使能信号
                    if (data_cnt == data_byte_num - 1'b1) begin
                        skip_en         <=  1'b1;
                        data_cnt        <=  16'd0;
                        rec_pkt_done    <=  1'b1;
                        rec_byte_num    <=  data_byte_num;              //以太网接收数据有效数量
                    end 
                end 
            end
            st_rx_end  :begin
                rec_en  <=  1'b0;                                       //单包数据传输完成
                if ((gmii_rx_dv == 1'b0) && (skip_en == 1'b0 ) ) begin
                    skip_en <=  1'b1;
                end
            end 
            default: ;
        endcase
    end
end
endmodule

仿真结果:

(2)udp_tx

cpp 复制代码
module udp_tx(    
    input                clk        , //时钟信号
    input                rst_n      , //复位信号,低电平有效
    
    input                tx_start_en, //以太网开始发送信号
	input        [ 7: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.123     
parameter BOARD_IP  = {8'd192,8'd168,8'd1,8'd123}; 
//目的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    ;  

//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          start_en_d2        ;
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_bit_sel         ;
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   ;//实际发送的字节数(以太网最少字节要求)

//采tx_start_en上升沿
assign  pos_start_en        = (!start_en_d2) & start_en_d1 ;
//判断实际发送数据
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'd0;
        start_en_d1 <=  1'd0;
        start_en_d2 <=  1'd0;
    end 
    else begin
        start_en_d0 <=  tx_start_en;
        start_en_d1 <=  start_en_d0;
        start_en_d2 <=  start_en_d1;
    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;      //UDP字节数
    end 
    else begin
        if (pos_start_en && cur_state == st_idle) begin
            //有效数据长度
            tx_data_num     <=  tx_byte_num ;
            //IP长度:有效数据长度 + IP首部长度
            total_num       <=  tx_data_num + 16'd28;
            //UDP长度:有效数据 + UDP首部长度
            udp_num         <=  tx_byte_num + 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;       //IP首部表示
        tx_bit_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;
        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,8'd17,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 + 1'b1;
                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 begin
                    cnt <= cnt + 1'b1;
                end
            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 begin
                    cnt <= cnt + 1'b1;
                end
            end
            st_ip_head   :begin                                         //发送IP首部 + UDP首部
                gmii_tx_en  <=  1'b1;
                crc_en      <=  1'b1;
                tx_bit_sel  <=  tx_bit_sel + 1'b1;
                if (tx_bit_sel == 2'd0) begin
                    gmii_txd    <=  ip_head[cnt][31:24];
                end
                else if (tx_bit_sel == 2'd1) begin
                    gmii_txd    <=  ip_head[cnt][23:16];
                end 
                else if (tx_bit_sel == 2'd2) begin
                    gmii_txd    <=  ip_head[cnt][15:8];
                    if (cnt == 5'd6) begin
                        //提前读请求数据,等待数据有效时发送
                        tx_req <= 1'b1;     
                    end 
                    else begin
                        tx_req <= 1'b0;   
                    end
                end
                else if (tx_bit_sel == 2'd3) begin      //tx_bit_sel自动溢出
                    gmii_txd    <=  ip_head[cnt][7:0];
                    if (cnt == 5'd6) begin
                        skip_en <=  1'b1;
                        cnt     <=  5'd0;
                    end 
                    else begin
                        cnt     <=  cnt + 1'b1;    
                    end    
                end 
            end
            st_tx_data   :begin                                         //发送数据
                gmii_tx_en  <=  1'b1;
                crc_en      <=  1'b1;
                gmii_txd    <=  tx_data;
                tx_bit_sel  <=  tx_bit_sel + 1'b1;
                if (data_cnt < tx_data_num - 16'd1) begin
                    data_cnt <= data_cnt + 1'b1;
                end
                else if (data_cnt == tx_data_num - 16'd1) begin
                    //如果发送的有效数据少于18个字节,在后面填补充位
                    //补充的值为最后一次发送的有效数据
                    if (data_cnt + real_add_cnt < real_tx_data_num - 16'd1) begin
                        real_add_cnt <= real_add_cnt + 1'b1;
                    end 
                    else begin
                        skip_en         <=  1'b1;
                        data_cnt        <=  16'd0;
                        real_add_cnt    <=  5'd0;
                        tx_bit_sel      <=  3'd0;    
                    end
                end 

                if (data_cnt == tx_byte_num - 16'd2) begin
                    tx_req <= 1'b0;
                end
            end
            st_crc      : begin                                         //发送CRC校验值
                gmii_tx_en <= 1'b1;
                tx_bit_sel <= tx_bit_sel + 3'd1;
				tx_req <= 1'b0;  
                if(tx_bit_sel == 3'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_bit_sel == 3'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_bit_sel == 3'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_bit_sel == 3'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 
				else ;
            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、协议栈顶层

(1)框图及顶层

cs 复制代码
module eth_udp_loop(
    input                   sys_rst_n           ,   //系统复位信号,低电平有效 
    //PL以太网RGMII接口                     
    input                   eth_rxc             ,   //RGMII接收数据时钟
    input                   eth_rx_ctl          ,   //RGMII输入数据有效信号
    input       [3:0]       eth_rxd             ,   //RGMII输入数据
    output                  eth_txc             ,   //RGMII发送数据时钟    
    output                  eth_tx_ctl          ,   //RGMII输出数据有效信号
    output      [3:0]       eth_txd                 //RGMII输出数据          
);

//parameter define
parameter  BOARD_MAC = 48'h00_11_22_33_44_55;       //开发板MAC地址 00-11-22-33-44-55
parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};  //开发板IP地址 192.168.1.10
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;       //目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102}; //目的IP地址 192.168.1.102     
parameter  IDELAY_VALUE = 15;                       //输入数据IO延时,此处为0,即不延时(如果为n,表示延时n*78ps) 

//wire define
wire                    clk_200m   		        ;   //用于IO延时的时钟 
                
wire                    gmii_rx_clk             ;   //GMII接收时钟
wire                    gmii_rx_dv              ;   //GMII接收数据有效信号
wire          [7:0]     gmii_rxd                ;   //GMII接收数据
wire                    gmii_tx_clk             ;   //GMII发送时钟
wire                    gmii_tx_en              ;   //GMII发送数据使能信号
wire          [7:0]     gmii_txd                ;   //GMII发送数据     
                
wire                    arp_gmii_tx_en	        ;   //ARP GMII输出数据有效信号 
wire          [7:0]     arp_gmii_txd  	        ;   //ARP GMII输出数据
wire                    arp_rx_done   	        ;   //ARP接收完成信号
wire                    arp_rx_type   	        ;   //ARP接收类型 0:请求  1:应答
wire          [47:0]    src_mac       	        ;   //接收到目的MAC地址
wire          [31:0]    src_ip        	        ;   //接收到目的IP地址    
wire                    arp_tx_en     	        ;   //ARP发送使能信号
wire                    arp_tx_type   	        ;   //ARP发送类型 0:请求  1:应答
wire          [47:0]    des_mac       	        ;   //发送的目标MAC地址
wire          [31:0]    des_ip        	        ;   //发送的目标IP地址   
wire                    arp_tx_done   	        ;   //ARP发送完成信号
                
wire                    icmp_gmii_tx_en	        ;   //ICMP GMII输出数据有效信号 
wire          [7:0]     icmp_gmii_txd  	        ;   //ICMP GMII输出数据
wire                    icmp_rec_pkt_done       ;   //ICMP单包数据接收完成信号
wire                    icmp_rec_en             ;   //ICMP接收的数据使能信号
wire          [ 7:0]    icmp_rec_data           ;   //ICMP接收的数据
wire          [15:0]    icmp_rec_byte_num       ;   //ICMP接收的有效字节数 单位:byte 
wire          [15:0]    icmp_tx_byte_num        ;   //ICMP发送的有效字节数 单位:byte 
wire                    icmp_tx_done   	        ;   //ICMP发送完成信号
wire                    icmp_tx_req             ;   //ICMP读数据请求信号
wire          [ 7:0]    icmp_tx_data            ;   //ICMP待发送数据
wire                    icmp_tx_start_en        ;   //ICMP发送开始使能信号
                
wire                    udp_gmii_tx_en	        ;   //UDP GMII输出数据有效信号 
wire          [7:0]     udp_gmii_txd  	        ;   //UDP GMII输出数据
wire                    rec_pkt_done  	        ;   //UDP单包数据接收完成信号
wire                    udp_rec_en    	        ;   //UDP接收的数据使能信号
wire          [ 7:0]    udp_rec_data  	        ;   //UDP接收的数据
wire          [15:0]    rec_byte_num  	        ;   //UDP接收的有效字节数 单位:byte 
wire          [15:0]    tx_byte_num   	        ;   //UDP发送的有效字节数 单位:byte 
wire                    udp_tx_done   	        ;   //UDP发送完成信号
wire                    udp_tx_req    	        ;   //UDP读数据请求信号
wire          [ 7:0]    udp_tx_data   	        ;   //UDP待发送数据
wire                    tx_start_en   	        ;   //UDP发送开始使能信号
                
wire          [7:0]	    rec_data			    ;   //FIFO写入数据
wire        		    rec_en			        ;   //FIFO写使能
wire        		    tx_req			        ;   //FIFO读使能
wire          [7:0]	    tx_data	    	        ;   //FIFO读出数据


assign icmp_tx_start_en =   icmp_rec_pkt_done   ;   //ICMP 接收端结束标志,作为 ICMP发送端开始标志
assign icmp_tx_byte_num =   icmp_rec_byte_num   ;   //ICMP 接收端数据个数,作为 ICMP发送端发送数据 

assign tx_start_en      =   rec_pkt_done        ;   //UDP 接收端结束标志,作为 UDP发送开始使能信号
assign tx_byte_num      =   rec_byte_num        ;   //UDP 接收端数据个数,作为 UDP发送端发送数据个数

assign des_mac          =   src_mac             ;   //ARP 接收到的 源MAC,作为 ICMP\UDP 目的MAC,实际为电脑端MAC
assign des_ip           =   src_ip              ;   //ARP 接收到的 源IP ,作为 ICMP\UDP 目的IP ,实际为电脑端IP

                                                    //数据位于异步FIFO之中,如需单独使用一侧功能,可以修改FIFO数据
                                                    //需要注意写入有效数据数量要与此处相同,一定要注意 数据个数与FIFO读出数据对齐!!!!!!! 

//MMCM/PLL 产生200Mhz时钟--> gmii2rgmii
clk_wiz_0 u_clk_wiz_0
(
    .clk_out1           (clk_200m           ),      // output clk_out1
    .reset              (~sys_rst_n         ),      // input reset
    .locked             (locked             ),      // output locked
    .clk_in1            (eth_rxc            )       // PHY侧提供eth_rxc时钟125Mhz
);  

//GMII接口转RGMII接口
gmii_to_rgmii 
#(
    .IDELAY_VALUE       (IDELAY_VALUE       )
)      
u_gmii_to_rgmii(        
    .idelay_clk         (clk_200m           ),      //IDELAY时钟
    //以太网GMII接口    
    .gmii_rx_clk        (gmii_rx_clk        ),      //GMII接收时钟
    .gmii_rx_dv         (gmii_rx_dv         ),      //GMII接收数据有效信号
    .gmii_rxd           (gmii_rxd           ),      //GMII接收数据
    .gmii_tx_clk        (gmii_tx_clk        ),      //GMII发送时钟
    .gmii_tx_en         (gmii_tx_en         ),      //GMII发送数据使能信号
    .gmii_txd           (gmii_txd           ),      //GMII发送数据   
    //以太网RGMII接口   
    .rgmii_rxc          (eth_rxc            ),      //RGMII接收时钟
    .rgmii_rx_ctl       (eth_rx_ctl         ),      //RGMII接收数据控制信号
    .rgmii_rxd          (eth_rxd            ),      //RGMII接收数据
    .rgmii_txc          (eth_txc            ),      //RGMII发送时钟    
    .rgmii_tx_ctl       (eth_tx_ctl         ),      //RGMII发送数据控制信号
    .rgmii_txd          (eth_txd            )       //RGMII发送数据   
);

//ARP通信
arp                                             
#(
    .BOARD_MAC          (BOARD_MAC          ),      //参数例化
    .BOARD_IP           (BOARD_IP           ),
    .DES_MAC            (DES_MAC            ),
    .DES_IP             (DES_IP             )
)   
u_arp(  
    .rst_n              (sys_rst_n  	    ),      //复位信号,低电平有效

    //GMII接口  
    //input
    .gmii_rx_clk        (gmii_rx_clk	    ),      //GMII接收数据时钟
    .gmii_rx_dv         (gmii_rx_dv 	    ),      //GMII输入数据有效信号
    .gmii_rxd           (gmii_rxd   	    ),      //GMII输入数据
    .gmii_tx_clk        (gmii_tx_clk	    ),      //GMII发送数据时钟
    //output
    .gmii_tx_en         (arp_gmii_tx_en     ),      //GMII输出数据有效信号
    .gmii_txd           (arp_gmii_txd       ),      //GMII输出数据         

    //用户接口                           
    //output
    .arp_rx_done        (arp_rx_done	    ),      //ARP接收完成信号
    .arp_rx_type        (arp_rx_type	    ),      //ARP接收类型 0:请求  1:应答
    .src_mac            (src_mac    	    ),      //接收到目的MAC地址
    .src_ip             (src_ip     	    ),      //接收到目的IP地址 
    //input   
    .arp_tx_en          (arp_tx_en  	    ),      //ARP发送使能信号
    .arp_tx_type        (arp_tx_type	    ),      //ARP发送类型 0:请求  1:应答
    .des_mac            (des_mac    	    ),      //发送的目标MAC地址
    .des_ip             (des_ip     	    ),      //发送的目标IP地址

    //output
    .tx_done            (arp_tx_done	    )       //以太网发送完成信号    
);  

//ICMP通信  
icmp                                             
#(
    .BOARD_MAC          (BOARD_MAC          ),      //参数例化
    .BOARD_IP           (BOARD_IP           ),
    .DES_MAC            (DES_MAC            ),
    .DES_IP             (DES_IP             )
)
u_icmp(
    .rst_n              (sys_rst_n   	    ),      //复位信号,低电平有效

    //GMII接口
    //input
    .gmii_rx_clk        (gmii_rx_clk 	    ),      //GMII接收数据时钟         
    .gmii_rx_dv         (gmii_rx_dv  	    ),      //GMII输入数据有效信号       
    .gmii_rxd           (gmii_rxd    	    ),      //GMII输入数据                 
    .gmii_tx_clk        (gmii_tx_clk 	    ),      //GMII发送数据时钟
    //output
    .gmii_tx_en         (icmp_gmii_tx_en	),      //GMII输出数据有效信号       
    .gmii_txd           (icmp_gmii_txd	    ),      //GMII输出数据

    //用户接口
    //output
    .rec_pkt_done       (icmp_rec_pkt_done  ),      //以太网单包数据接收完成信号  
    .rec_en             (icmp_rec_en        ), 	    //以太网接收的数据使能信号				  
    .rec_data           (icmp_rec_data      ),      //以太网接收的数据				 	    
    .rec_byte_num       (icmp_rec_byte_num  ),      //以太网接收的有效字节数 单位:byte   
    //input
    .tx_start_en        (icmp_tx_start_en   ),      //以太网开始发送信号      
    .tx_data            (icmp_tx_data       ),      //以太网待发送数据					     
    .tx_byte_num        (icmp_tx_byte_num   ),      //以太网发送的有效字节数 单位:byte
    .des_mac            (des_mac     	    ),      //发送的目标MAC地址
    .des_ip             (des_ip      	    ),      //发送的目标IP地址  

    //output
    .tx_done            (icmp_tx_done	    ),      //以太网发送完成信号      
    .tx_req             (icmp_tx_req        )       //读数据请求信号					     
); 

//UDP通信
udp                                             
#(
    .BOARD_MAC          (BOARD_MAC          ),      //参数例化
    .BOARD_IP           (BOARD_IP           ),
    .DES_MAC            (DES_MAC            ),
    .DES_IP             (DES_IP             )
)
u_udp(
    .rst_n              (sys_rst_n          ),      //复位信号,低电平有效

    //GMII接口
    //input
    .gmii_rx_clk        (gmii_rx_clk        ),      //GMII接收数据时钟 
    .gmii_rx_dv         (gmii_rx_dv         ),      //GMII输入数据有效信号
    .gmii_rxd           (gmii_rxd           ),      //GMII输入数据
    .gmii_tx_clk        (gmii_tx_clk        ),      //GMII发送数据时钟   
    //output 
    .gmii_tx_en         (udp_gmii_tx_en     ),      //GMII输出数据有效信号
    .gmii_txd           (udp_gmii_txd       ),      //GMII输出数据 

    //用户接口
    //outpur
    .rec_pkt_done       (rec_pkt_done       ),      //以太网单包数据接收完成信号 
    .rec_en             (udp_rec_en         ),      //以太网接收的数据使能信号
    .rec_data           (udp_rec_data       ),      //以太网接收的数据      
    .rec_byte_num       (rec_byte_num       ),      //以太网接收的有效字节数 单位:byte      
    //input
    .tx_start_en        (tx_start_en        ),      //以太网开始发送信号      
    .tx_data            (udp_tx_data        ),      //以太网待发送数据        
    .tx_byte_num        (tx_byte_num        ),      //以太网发送的有效字节数 单位:byte      
    .des_mac            (des_mac            ),      //发送的目标MAC地址      
    .des_ip             (des_ip             ),      //发送的目标IP地址    

    //output 
    .tx_done            (udp_tx_done        ),      //以太网发送完成信号 
    .tx_req             (udp_tx_req         )       //读数据请求信号   
); 

//异步FIFO,实际做同步FIFO使用
async_fifo_2048x8b u_async_fifo_2048x8b (
    //input
    .rst                (~sys_rst_n	        ),      //input wire rst
    .wr_clk             (gmii_rx_clk        ),  	//input wire wr_clk
    .rd_clk             (gmii_rx_clk        ),  	//input wire rd_clk
    .din                (rec_data	        ),      //input wire [7 : 0] din
    .wr_en              (rec_en		        ),    	//input wire wr_en
    .rd_en              (tx_req		        ),    	//input wire rd_en
    //output
    .dout               (tx_data	        ),      //output wire [7 : 0] dout
    .full               (                   ),      //output wire full
    .empty              (                   )    	//output wire empty
);

//以太网控制模块
eth_ctrl u_eth_ctrl(
    //input
    .clk            	(gmii_rx_clk	    ),      //时钟
    .rst_n          	(sys_rst_n		    ),      //系统复位信号,低电平有效 

    //ARP相关端口信号 
    //input
    .arp_rx_done    	(arp_rx_done   	    ),      //ARP接收完成信号
    .arp_rx_type    	(arp_rx_type   	    ),      //ARP接收类型 0:请求  1:应答
    .arp_tx_done    	(arp_tx_done   	    ),      //ARP发送完成信号
    .arp_gmii_tx_en 	(arp_gmii_tx_en	    ),      //ARP GMII输出数据有效信号 
    .arp_gmii_txd   	(arp_gmii_txd  	    ),      //ARP GMII输出数据
    //output
    .arp_tx_en      	(arp_tx_en     	    ),      //ARP发送使能信号
    .arp_tx_type    	(arp_tx_type   	    ),      //ARP发送类型 0:请求  1:应答

    //ICMP相关端口信号
    //input
    .icmp_tx_start_en	(icmp_tx_start_en   ),      //ICMP开始发送信号
    .icmp_tx_done		(icmp_tx_done	    ),      //ICMP发送完成信号
    .icmp_gmii_tx_en	(icmp_gmii_tx_en    ),      //ICMP GMII输出数据有效信号  
    .icmp_gmii_txd		(icmp_gmii_txd	    ),      //ICMP GMII输出数据 
    //ICMP fifo接口信号
    //input
	.icmp_rec_en       	(icmp_rec_en        ),      //ICMP接收的数据使能信号
	.icmp_rec_data     	(icmp_rec_data      ),      //ICMP接收的数据
	.icmp_tx_req       	(icmp_tx_req        ),      //ICMP读数据请求信号
    //output
	.icmp_tx_data      	(icmp_tx_data       ),      //ICMP待发送数据

    //UDP相关端口信号
    //input
    .udp_tx_start_en	(tx_start_en   	    ),      //UDP开始发送信号
    .udp_tx_done    	(udp_tx_done   	    ),      //UDP发送完成信号    
    .udp_gmii_tx_en 	(udp_gmii_tx_en	    ),      //UDP GMII输出数据有效信号  
    .udp_gmii_txd   	(udp_gmii_txd  	    ),      //UDP GMII输出数据   
    //UDP fifo接口信号
    //input
	.udp_rec_data		(udp_rec_data	    ),      //UDP接收的数据		
	.udp_rec_en			(udp_rec_en		    ),      //UDP接收的数据使能信号     
	.udp_tx_req			(udp_tx_req		    ),      //UDP读数据请求信号   
    //output 
	.udp_tx_data		(udp_tx_data	    ),      //UDP待发送数据		

    //fifo接口信号
    //output
	.rec_data			(rec_data	        ),      //待发送的数据	
	.rec_en	        	(rec_en	            ),      //读数据请求信号 
    .tx_req	        	(tx_req	            ),      //接收的数据使能信号
    //input
	.tx_data	    	(tx_data	        ),      //接收的数据

    //GMII发送引脚  
    //output
    .gmii_tx_en     	(gmii_tx_en    	    ),      //GMII输出数据有效信号 
    .gmii_txd       	(gmii_txd      	    )       //GMII输出数据 
);      

endmodule

(2)代码框架

3、资源消耗情况

五、下载验证

ping测试:

网口环回测试:

六、福利获取

后台私信 UDP协议分析表,即可获得 UDP协议分析表原件!!!!

七、工程移植、源码获取请后台私信

相关推荐
StickToForever4 小时前
第4章 信息系统架构(五)
经验分享·笔记·学习·职场和发展
leegong231117 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
Moonnnn.8 小时前
51单片机学习——动态数码管显示
笔记·嵌入式硬件·学习·51单片机
南宫生9 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
技术小齐9 小时前
网络运维学习笔记 016网工初级(HCIA-Datacom与CCNA-EI)PPP点对点协议和PPPoE以太网上的点对点协议(此处只讲华为)
运维·网络·学习
竹言笙熙9 小时前
代码审计初探
学习·web安全
日记成书9 小时前
物联网智能项目
物联网·学习
虾球xz10 小时前
游戏引擎学习第118天
学习·游戏引擎
gz927cool10 小时前
大模型做导师之开源项目学习(lightRAG)
学习·开源·mfc
电棍23311 小时前
verilog笔记
笔记·fpga开发