基于FPGA的以太网设计(五)

之前简单介绍并实现了ARP协议,今天简单介绍一下IP协议和ICMP协议。

1.IP协议

IP协议即Internet Protocol **,**是网络层的协议。

IP协议是TCP/IP协议族的核心协议,其主要包含两个方面:

  • IP头部信息。IP头部信息出现在每个IP数据报中,用于指定IP通信的源端IP地址、目的端IP地址,指导IP分片和重组,以及指定部分通信行为。
  • IP数据报的路由和转发。IP数据报的路由和转发发生在除目标机器之外的所有主机和路由器上。它们决定数据报是否应该转发以及如何转发。

IPv4的头部结构如下所示,其长度通常为20 个字节(最多60个字节),除非含有可变长的选项部分

版本号4位,用于指定ip协议的版本。

IHL(4位):标识该IP头部有多少个32bit字(4字节),此处默认头部长度为20字节,故IHL为0101

服务类型(8位):包括一个3位的优先权字段,4位的TOS字段和1位的保留字段(必须置0)。4位的TOS字段分别表示:最小延时,最大吞吐量,最高可靠性和最小费用。其中最多有一个能置位1,应用程序应该根据实际需要来设置它。

总长度(16位)是指整个IP数据报的长度,以字节为单位,因此IP数据报的最大长度为65535字节。但由于MTU的限制,长度超过MTU的数据报都将被分片传输,所以实际传输的IP数据报的长度都远远没有达到最大值。

标识符(16位)唯一标识主机发送的每一个数据报

标志字段(3位)地第一位保留。第二位表示"禁止分片"。如果设置了这个位,IP模块将不对数据报进行分片。在这种情况下,如果IP数据报长度超过MTU的话,IP模块将丢弃该数据报并返回一个ICMP差错报文。第三位表示"更多分片"。除了数据报的最后一个分片外,其他分片都要把它置1。

分片偏移(13位)是分片相对原始IP数据报开始处(仅指数据部分)的偏移。实际的偏移值是该值左移3位(乘8)后得到的。由于这个原因,除了最后一个IP分片外,每个IP分片的数据部分的长度必须是8的整数倍(这样才能保证后面的IP分片拥有一个合适的偏移量)。

生存时间(即TTL)(8位)是数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置(常见值位64)。数据报在转发过程中每经过一个路由,该值就被路由器减1。当TTL值减为0时,路由器将丢弃数据报,并向源端发送一个ICMP差错报文。TTL值可以防止数据报陷入路由循环。

协议(8位)用来区分上层协议,/etc/protocols文件定义了所有上层协议对应的protocol字段的数值。其中ICMP是1,TCP是6,UDP是17。

头部校验和(16位)由发送端填充,接收端对其使用CRC算法以检验IP数据报头部在传输过程中是否损坏。

源端IP地址(32位)和目的端IP地址(32位)用来标识数据报的发送端和接收端。一般情况下,这两个地址在整个数据报的传递过程中保持不变,而不论它中间经过多少个中转路由器。

IPv4最后一个选项字段是可变长的可选信息。这部分最多包含40个字节,因为IP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)

通过下图分析一下首部校验和的计算规则:

首部校验和的计算规则为:首先假设16位检验和为0,然后将IP首部按照16位分成多个单元,把所有单元相加,如果得到的低16位数据出现进位,则把低16位数据与高16位数据相加,相加后如果低16位还有进位,则继续把低16位与高16位数据相加,然后低16位数据取反得到首部校验和。

例如上图发送总长度为500个字节的IP数据报,发送端IP地址为192.168.1.189,接收端IP地址为192.168.1.10,则IP首部数据如下:

按照上述提到的 IP 首部校验和的方法计算 IP 首部校验和,即:

16'h4500 + 16'h01f4 + 16'h0000 + 16'h4000 + 16'h4001 + 16'h0000(计算时为0) + 16'hc0a8 + 16'h01bd + 16'hc0a8 + 16'h010a = 32'h00024b0c(低16位出现进位)。

16'h0002 + 16'h4b0c = 32'h00004b0e(低16位未出现进位)。

16'h0000 + 16'h4b0e = 32'h00004b0e(低16位未出现进位)

最终得到校验码check_sum = ~16'h4b0e = 16'b4f1

其实第一次相加低16位可能出现进位,取相加后的低16位与高16位相加结果也可能出现进位,相加结果的高低位相加两次后就不可能出现进位了,所以在程序设计时,就直接把第一次的计算结果的高16位数据与低16位数据相加两次后得到的低16位数据取反,得到首部校验和,省去判断进位的步骤,简化代码。

2.ICMP协议

ICMP 协议是网络层协议,封装在IP **数据报中。**主要功能就是ping命令和tracert命令,可以检查网络的连通性和显示经过路径。

ICMP协议是TCP/IP 模型中网络层的重要成员,与 IP 协议、ARP 协议、RARP 协议及 IGMP 协议共同构成 TCP/IP 模型中的网络层ping 主机的ip后,得到不同的回复,对应不同的结果。

  • 无法访问目标主机:没有网关或缺少目标主机MAC
  • 请求超时:对方不在线或屏蔽 (即对方防火墙拦截)
  • 传输失败:未获取MAC地址,导致无法进行ICMP封装

ICMP首部总共8个字节,所以可以通过IP首部的总长度和IP首部长度、ICMP首部长度计算出ICMP数据长度。

类型(type):8位数表示错误类型的差错报文或者查询类型的报告报文。通常与代码结合使用

代码(code):占用8位数据,根据ICMP差错报文的类型,进一步分析错误的原因。下表就是代码和类型部分组合的含义,后文主要使用回显请求和回显应答。

种类 类型 代码 报文含义
查询报文 0 0 回显应答(ping应答)
查询报文 8 0 回显请求(ping请求)
差错报文 3 0 网络不可达
差错报文 3 1 主机不可达
差错报文 3 2 协议不可达
差错报文 3 3 端口不可达
差错报文 3 7 目的主机未知
差错报文 12 0 坏的IP首部
差错报文 12 1 缺少必须选项

校验和(checksum):16 位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验。

标识符(Identifier):16位标识符对每一个发送的数据报进行标识。

序列号(Sequence number):16位对发送的每一个数据报文进行编号。

标识符和序列号其实是为了区分相同类型的不同两个数据报,比如主机A向主机B发送了两个回显请求,然后主机B针对两个回显请求分别做了回显应答。

回显应答的标识符和序列号必须与回显请求中的标识符和序列号保持一致, 这样主机A收到回显应答后,才知道主机B响应的是哪一个回显请求,进而对比回显应答数据与回显请求的数据是否一致,一致则表示ping成功,否则失败。

数据(Data):要发送的ICMP数据,注意回显应答数据段的数据必须与回显请求数据段的数据保持一致,否则ping失败。

总体的ICMP数据报格式如下图:

3.代码实现

3.1发送部分:

`timescale 1ns / 1ps

module ICMP_TX(
    input                                   clk                         ,   //系统时钟
    input                                   rst_n                       ,   //系统复位
    //input         
    input                   [31:0]          reply_checksum              ,   //ICMP数据部分校验和
    input                   [15:0]          icmp_id                     ,   //ICMP标识符
    input                   [15:0]          icmp_seq                    ,   //ICMP序列符
    input                                   tx_start                    ,   //以太网发送开始信号
    input                   [7:0]           tx_data                     ,   //以太网发送数据
    input                   [15:0]          tx_byte_num                 ,   //以太网发送有效字节数
    input                   [31:0]          des_ip                      ,   //目的ip地址
    input                   [47:0]          des_mac                     ,   //目的MAC地址
    input                   [31:0]          crc_data                    ,   //CRC校验数据
    input                   [7:0]           crc_next                    ,   //CRC下次校验数据
    //output            
    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       BOARD_IP        =       {8'd192,8'd168,8'd1,8'd10}  ;   //开发板ip地址
    parameter       BOARD_MAC       =       48'h00_11_22_33_44_55       ;   //开发板MAC地址
    parameter       DES_IP          =       {8'd192,8'd168,8'd1,8'd102} ;   //源ip地址
    parameter       DES_MAC         =       48'hff_ff_ff_ff_ff_ff       ;   //源MAC地址设置为广播地址

    localparam      IDLE            =       8'b0000_0001                ;   //空闲状态
    localparam      CHECK_SUM       =       8'b0000_0010                ;   //ip首部校验和
    localparam      CHECK_ICMP      =       8'b0000_0100                ;   //ICMP首部加校验数据       
    localparam      PREAMBLE        =       8'b0000_1000                ;   //发送前导码加帧起始界定符
    localparam      ETH_HEAD        =       8'b0001_0000                ;   //发送以太网帧头
    localparam      IP_HEAD         =       8'b0010_0000                ;   //发送ip首部加ICMP首部
    localparam      TX_DATA         =       8'b0100_0000                ;   //发送数据
    localparam      CRC             =       8'b1000_0000                ;   //发送CRC校验值
    localparam      ETH_TYPE        =       16'h0800                    ;   //以太网类型
    localparam      MIN_DATA_NUM    =       16'd18                      ;   //以太网数据最小46字节,ip首部20字节+ICMP首部8字节
    parameter       ECHO_REPLY      =       8'h00                       ;   //ICMP报文类型:回显应答

    reg                                     state_en                    ;   //状态跳转使能
    reg             [7:0]                   cur_state                   ;   //现态
    reg             [7:0]                   next_state                  ;   //次态
    reg                                     tx_start_r0                 ;   
    reg                                     tx_start_r1                 ;   
    reg                                     tx_start_r2                 ;   
    reg             [15:0]                  tx_data_num                 ;   //发送有效数据字节
    reg             [15:0]                  total_num                   ;   //总字节数据
    reg                                     trig_tx_en                  ;   //触发发送信号
    reg             [4:0]                   cnt                         ;   //计数器
    reg             [7:0]                   preamble[7:0]               ;   //前导码
    reg             [7:0]                   eth_head[13:0]              ;   //以太网首部
    reg             [31:0]                  ip_head[6:0]                ;   //IP首部 + ICMP首部               
    reg             [31:0]                  check_buffer                ;   //ip首部校验和
    reg             [31:0]                  check_buffer_icmp           ;   //ip首部校验和
    reg             [1:0]                   tx_bit_sel                  ;   //发送字节选择
    reg             [15:0]                  data_cnt                    ;   //发送字节计数器
    reg                                     tx_done_t                   ;   //发送结束信号
    reg             [4:0]                   real_add_cnt                ;   //以太网实际多发字节数据

    wire                                    posedge_tx_start            ;   //发送开始信号上升沿 
    wire            [15:0]                  real_tx_data_num            ;   //实际发送数据的数量

    assign  posedge_tx_start = ~(tx_start_r2) && tx_start_r1;       
    assign  real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;

    //检测发送开始信号上升沿
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_start_r0 <= 1'b0;
            tx_start_r1 <= 1'b0;
            tx_start_r2 <= 1'b0;
        end
        else begin
            tx_start_r0 <= tx_start;
            tx_start_r1 <= tx_start_r0;
            tx_start_r2 <= tx_start_r1;
        end
    end

    //寄存数据有效字节
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_data_num <= 16'd0;
            total_num <= 16'd0;
        end
        else if(posedge_tx_start && cur_state == IDLE)begin
            tx_data_num <= tx_byte_num;
            total_num <= tx_byte_num + 16'd28;
        end
        else;
    end

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

    //三段式状态机第一段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            cur_state <= IDLE;
        else
            cur_state <= next_state;
    end

    //三段式状态机第二段
    always @(*) begin
        next_state = IDLE;
        case (cur_state)
            IDLE: begin
                if(state_en)
                    next_state = CHECK_SUM;
                else
                    next_state = IDLE;
            end
            CHECK_SUM: begin
                if(state_en)
                    next_state = CHECK_ICMP;
                else
                    next_state = CHECK_SUM;  
            end
            CHECK_ICMP: begin
                if(state_en)
                    next_state = PREAMBLE;
                else
                    next_state = CHECK_ICMP;
            end
            PREAMBLE: begin
                if(state_en)
                    next_state = ETH_HEAD;
                else 
                    next_state = PREAMBLE;
            end
            ETH_HEAD: begin
                if(state_en)
                    next_state = IP_HEAD;
                else
                    next_state = ETH_HEAD;
            end
            IP_HEAD: begin
                if(state_en)
                    next_state = TX_DATA;
                else
                    next_state = IP_HEAD; 
            end
            TX_DATA: begin
                if(state_en)
                    next_state = CRC; 
                else
                    next_state = TX_DATA;
            end
            CRC: begin
                if(state_en)
                    next_state = IDLE;
                else
                    next_state = CRC; 
            end
            default: ;
        endcase
    end

    //三段式状态机第三段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_en <= 1'b0;
            check_buffer <= 32'd0;
            check_buffer_icmp <= 32'd0;
            cnt <= 5'd0;
            ip_head[1][31:16] <= 16'd0;//标识位
            tx_bit_sel <= 2'd0;
            crc_en <= 1'b0;
            gmii_tx_en <= 1'b0;
            gmii_txd <= 8'd0;
            tx_done_t <= 1'b0;
            tx_req <= 1'b0;
            data_cnt <= 16'd0;
            real_add_cnt <= 5'd0;
            //初始化数组
            //前导码+帧起始界定符
            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
            state_en <= 1'b0;
            crc_en <= 1'b0;
            gmii_tx_en <= 1'b0;
            tx_done_t <= 1'b0;
            case (next_state)
                IDLE: begin
                    if(trig_tx_en)begin
                        state_en <= 1'b1;
                        //版本号: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;
                        //8'h80表示生存时间
                        //8'd01:1代表ICMP,2代表IGMP,6代表TCP,17代表UDP
                        ip_head[2] <= {8'h80,8'h01,16'h0000};
                        ip_head[3] <= BOARD_IP;
                        //目的ip地址
                        if(des_ip != 32'd0)
                            ip_head[4] <= des_ip;
                        else
                            ip_head[4] <= DES_IP;
                        //8位 icmp type,8位 icmp code
                        ip_head[5][31:16] <= {ECHO_REPLY,8'h00};
                        //16位标识符 16位序列号
                        ip_head[6] <= {icmp_id,icmp_seq};
                        //更新MAC地址
                        if(des_ip != 48'd0)begin
                            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
                        else;
                    end
                    else;
                end 
                CHECK_SUM: begin
                    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)begin//可能出现进位,高16位、低16位进行相加
                        check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                    end
                    else if(cnt == 5'd2)begin//可能再次出现进位,再次相加
                        check_buffer <= check_buffer[31:16] + check_buffer[15:0];
                    end
                    else if(cnt == 5'd3)begin
                        cnt <= 5'd0;
                        state_en <= 1'b1;
                        ip_head[2][15:0] <= ~check_buffer[15:0];//按位取反
                    end
                    else;
                end
                CHECK_ICMP: begin//ICMP首部+数据校验
                    cnt <= cnt + 1'b1;
                    if(cnt == 5'd0)begin
                        check_buffer_icmp <= ip_head[5][31:16] + ip_head[6][31:16]
                                             +ip_head[6][15:0] + reply_checksum;
                    end
                    else if(cnt == 5'd1)begin
                        check_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];
                    end
                    else if(cnt == 5'd2)begin
                        check_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];
                    end
                    if(cnt == 5'd3)begin
                        cnt <= 5'd0;
                        state_en <= 1'b1;
                        //ICMP 16位校验和
                        ip_head[5][15:0] <= ~check_buffer_icmp[15:0];
                    end
                    else;
                end
                PREAMBLE: begin
                    gmii_tx_en <= 1'b1;
                    gmii_txd <= preamble[cnt];
                    if(cnt == 5'd7)begin
                        cnt <= 5'd0;
                        state_en <= 1'b1;
                    end
                    else
                        cnt <= cnt + 1'b1;
                end
                ETH_HEAD:begin
                    gmii_tx_en <= 1'b1;
                    gmii_txd <= eth_head[cnt];
                    crc_en <= 1'b1;
                    if(cnt == 5'd13)begin
                        cnt <= 5'd0;
                        state_en <= 1'b1;
                    end
                    else
                        cnt <= cnt + 1'b1;
                end
                IP_HEAD: begin
                    crc_en <= 1'b1;
                    gmii_tx_en <= 1'b1;
                    tx_bit_sel <= tx_bit_sel + 1'b1;
                    if(tx_bit_sel == 2'd0)
                        gmii_txd <= ip_head[cnt][31:24];
                    else if(tx_bit_sel == 2'd1)
                        gmii_txd <= ip_head[cnt][23:16];
                    else if(tx_bit_sel == 2'd2)begin
                        gmii_txd <= ip_head[cnt][15:8];
                        if(cnt == 5'd6)
                            tx_req <= 1'b1;//提前拉高发送请求数据,数据有效就将数据发送
                        else;
                    end
                    else if(tx_bit_sel == 2'd3)begin
                        gmii_txd <= ip_head[cnt][7:0];
                        if(cnt == 5'd6)begin
                            cnt <= 5'd0;
                            state_en <= 1'b1;
                        end
                        else
                            cnt <= cnt + 1'b1;
                    end
                    else;
                end
                TX_DATA: begin
                    crc_en <= 1'b1;
                    gmii_tx_en <= 1'b1;
                    gmii_txd <= tx_data;
                    tx_bit_sel <= 3'd0;
                    if(data_cnt < tx_data_num - 1'b1)begin
                        data_cnt <= data_cnt + 1'b1;
                    end
                    else if(data_cnt == tx_data_num - 1'b1)begin
                        if(data_cnt + real_add_cnt < real_tx_data_num - 1'b1)
                            real_add_cnt <= real_add_cnt + 1'b1;
                        else begin
                            state_en <= 1'b1;
                            data_cnt <= 16'd0;
                            real_add_cnt <= 'd0;
                        end
                    end
                    else;

                    if(data_cnt == tx_data_num - 16'd2)
                        tx_req <= 1'b0;
                    else;
                end
                CRC:begin
                    gmii_tx_en <= 1'b1;
                    tx_bit_sel <= tx_bit_sel + 1'b1;
                    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;
                        state_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

仿真结果如下所示:

检测到开始发送信号上升沿,触发信号拉高,进入校验和检测状态。

累加结果。

发送以太网帧头部分不再赘述,因为之前就已经详细说过,重点关注ip首部发送过程。需要注意的是ip首部总共包含20字节数据,并且一定要搞清楚每一部分的含义是啥。加上ICMP首部总共是28字节,发送完成之后再发送20字节数据,最后进入CRC状态。

3.2接收部分:

`timescale 1ns / 1ps

module ICMP_RX(
    input                               clk             ,   //系统时钟
    input                               rst_n           ,   //系统复位
    //input 
    input                               gmii_rx_dv      ,   //接收数据有效信号
    input               [7:0]           gmii_rxd        ,   //接收数据
    //output    
    output          reg                 rec_pkt_done    ,   //以太网单包数据接收完成信号
    output          reg                 rec_en          ,   //接收使能信号
    output          reg [7:0]           rec_data        ,   //以太网接收数据
    output          reg [15:0]          rec_byte_num    ,   //以太网接收的有效字节数
    output          reg [15:0]           icmp_id         ,   //ICMP标识符
    output          reg [15:0]           icmp_seq        ,   //ICMP序列号
    output          reg [31:0]          reply_checksum      //接收数据校验                  
);
    parameter       BOARD_IP    =       {8'd192,8'd168,8'd1,8'd10};
    parameter       BOARD_MAC   =       48'h00_11_22_33_44_55;

    localparam      IDLE        =       7'b000_0001     ;   //空闲状态    
    localparam      PREAMBLE    =       7'b000_0010     ;   //接收前导码和帧起始界定符
    localparam      ETH_HEAD    =       7'b000_0100     ;   //接收以太网帧头
    localparam      IP_HEAD     =       7'b000_1000     ;   //接收ip帧头
    localparam      ICMP_HEAD   =       7'b001_0000     ;   //接收ICMP帧头
    localparam      RX_DATA     =       7'b010_0000     ;   //接收数据
    localparam      RX_DONE     =       7'b100_0000     ;   //接收数据结束
    localparam      ETH_TYPE    =       16'h0800        ;   //以太网类型
    localparam      ICMP_TYPE   =       8'd1            ;   //ICMP协议类型
    localparam      ECHO_REQUEST=       8'h08           ;   //ICMP报文类型回显请求

    reg             [6:0]               cur_state       ;   //现态
    reg             [6:0]               next_state      ;   //次态
    reg                                 state_en        ;   //状态跳转使能信号   
    reg                                 error_flag      ;   //错误指示信号
    reg             [4:0]               cnt             ;   //解析数据计数器
    reg             [47:0]              des_mac         ;   //目的MAC地址
    reg             [31:0]              des_ip          ;   //目的ip地址
    reg             [15:0]              eth_type        ;   //以太网类型
    reg             [5:0]               ip_head_byte_num;   //ip首部长度
    reg             [15:0]              total_length    ;   //ip长度
    reg             [1:0]               rec_en_cnt      ;   //8位转32位计数器
    reg             [7:0]               icmp_type       ;   //ICMP报文类型:用于表示错误类型的差错报文或查询类型的报告论文
    reg             [7:0]               icmp_code       ;   //ICMP报文代码:根究差错报文查询错误原因
    reg             [15:0]              icmp_checksum   ;   //接收数据校验和:数据被接收后需要对ICMP数据报文做一个校验,用于检查数据是否出错      
    reg             [15:0]              icmp_data_length;   
    reg             [15:0]              icmp_rx_cnt     ;   //接收数据计数
    reg             [7:0]               icmp_rx_data_d0 ;     
    reg             [31:0]              reply_checksum_add; // 

    //三段式状态机第一段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            cur_state <= IDLE;
        else
            cur_state <= next_state;
    end

    //三段式状态机第二段
    always @(*) begin
        next_state = IDLE;
        case (cur_state)
            IDLE: begin
                if(state_en)
                    next_state = PREAMBLE;
                else if(error_flag)
                    next_state = RX_DONE;
                else
                    next_state = IDLE;
            end 
            PREAMBLE:begin
                if(state_en)
                    next_state = ETH_HEAD;
                else if(error_flag)
                    next_state = RX_DONE;
                else
                    next_state = PREAMBLE; 
            end
            ETH_HEAD:begin
                if(state_en)
                    next_state = IP_HEAD;
                else if(error_flag)
                    next_state = RX_DONE;
                else
                    next_state = ETH_HEAD; 
            end
            IP_HEAD:begin
                if(state_en)
                    next_state = ICMP_HEAD;
                else if(error_flag)
                    next_state = RX_DONE;
                else
                    next_state = IP_HEAD; 
            end
            ICMP_HEAD:begin
                if(state_en)
                    next_state = RX_DATA;
                else if(error_flag)
                    next_state = RX_DONE;
                else
                    next_state = ICMP_HEAD; 
            end
            RX_DATA:begin
                if(state_en)
                    next_state = RX_DONE;
                else
                    next_state = RX_DATA; 
            end
            RX_DONE:begin
                if(state_en)
                    next_state = IDLE;
                else
                    next_state = RX_DONE;
            end
            default:next_state = IDLE;
        endcase
    end

    //三段式状态机第三段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_en <= 1'b0;
            error_flag <= 1'b0;
            cnt <= 5'd0;
            des_ip <= 32'd0;
            des_mac <= 48'd0;
            eth_type <= 16'd0;
            ip_head_byte_num <= 6'd0;
            total_length <= 16'd0;
            icmp_type <= 8'd0;
            icmp_code <= 8'd0;
            icmp_checksum <= 16'd0;
            icmp_id <= 16'd0;
            icmp_seq <= 16'd0;
            icmp_rx_data_d0 <= 8'd0;
            reply_checksum <= 32'd0;
            reply_checksum_add <= 32'd0;
            icmp_rx_cnt <= 16'd0;
            icmp_data_length <= 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
            state_en <= 1'b0;
            error_flag <= 1'b0;
            rec_pkt_done <= 1'b0;
            case (next_state)
                IDLE:begin
                    if((gmii_rx_dv) && gmii_rxd == 8'h55)
                        state_en <= 1'b1;
                    else;
                end 
                PREAMBLE:begin
                    if(cnt < 5'd6 && gmii_rxd != 8'h55)
                        error_flag <= 1'b1;
                    else if(cnt == 5'd6)begin
                        cnt <= 5'd0;
                        if(gmii_rxd == 8'hd5)
                            state_en <= 1'b1;
                        else
                            error_flag <= 1'b1;
                    end
                    else
                        cnt <= cnt + 1'b1;
                end
                ETH_HEAD:begin
                    if(gmii_rx_dv)begin
                        cnt <= cnt + 1'b1;
                        if(cnt < 5'd6)
                            des_mac <= {des_mac[39:0],gmii_rxd};//接收目的ip地址
                        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;
                            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])//判断ip地址和以太网协议类型
                                state_en <= 1'b1;
                            else
                                error_flag <= 1'b1;
                        end
                        else;
                    end
                    else;
                end
                IP_HEAD:begin
                    if(gmii_rx_dv)begin//ip帧头位20字节
                        cnt <= cnt + 1'b1;
                        if(cnt == 5'd0)
                            ip_head_byte_num <= {gmii_rxd[3:0],2'h00};
                        else if(cnt == 5'd2)
                            total_length[15:8] <= gmii_rxd;//接收总长度
                        else if(cnt == 5'd3)
                            total_length[7:0] <= gmii_rxd; 
                        else if(cnt == 5'd4)begin
                            icmp_data_length <= total_length - 16'd28;//ip帧头20字节+ICMP帧头8字节
                        end
                        else if(cnt == 5'd9)begin
                            //如果当前接收协议不是ICMP协议则停止解析协议
                            if(gmii_rxd != ICMP_TYPE)begin
                                error_flag <= 1'b1;
                                cnt <= 5'd0;
                            end
                            else;
                        end
                        else if((cnt >= 5'd16) && (cnt <= 5'd18))
                            des_ip <= {des_ip[23:0],gmii_rxd};
                        else if(cnt == 5'd19)begin
                            des_ip <= {des_ip[23:0],gmii_rxd};
                            if((des_ip[23:0] == BOARD_IP[31:8]) && gmii_rxd <= BOARD_IP[7:0])begin
                                state_en <= 1'b1;
                                cnt <= 5'd0;
                            end
                            else begin
                                //ip不是开发板地址
                                error_flag <= 1'b1;
                                cnt <= 5'd0;
                            end
                        end
                        else;
                    end
                    else;
                end
                ICMP_HEAD:begin
                    if(gmii_rx_dv)begin
                        cnt <= cnt + 1'b1;
                        if(cnt == 5'd0)
                            icmp_type <= gmii_rxd;
                        else if(cnt == 5'd1)
                            icmp_code <= gmii_rxd;
                        else if(cnt == 5'd2)
                            icmp_checksum[15:8] <= gmii_rxd;
                        else if(cnt == 5'd3)
                            icmp_checksum[7:0] <= gmii_rxd;
                        else if(cnt == 5'd4)
                            icmp_id[15:8] <= gmii_rxd;
                        else if(cnt == 5'd5)
                            icmp_id[7:0] <= gmii_rxd;
                        else if(cnt == 5'd6)
                            icmp_seq[15:8] <= gmii_rxd;
                        else if(cnt == 5'd7)begin
                            icmp_seq[7:0] <= gmii_rxd;
                            //判断ICMP报文类型是否是回显请求
                            if(icmp_type == ECHO_REQUEST)begin
                                state_en <= 1'b1;
                                cnt <= 5'd0;
                            end
                            else begin
                                error_flag <= 1'b1;
                                cnt <= 5'd0;
                            end
                        end
                        else;                    
                    end
                    else;
                end
                RX_DATA:begin
                    if(gmii_rx_dv)begin
                        rec_en_cnt <= rec_en_cnt + 1'b1;
                        rec_en <= 1'b1;
                        icmp_rx_cnt <= icmp_rx_cnt + 1'b1;
                        rec_data <= gmii_rxd;
                        //判断接收数据的奇偶个数
                        if(icmp_rx_cnt == icmp_data_length - 1'b1)begin
                            icmp_rx_data_d0 <= 8'd0;
                            if(icmp_data_length[0])//判断接收数据个数是否为奇数
                                reply_checksum_add <= {8'd0,gmii_rxd} + reply_checksum_add;
                            else 
                                reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add;
                        end
                        else if(icmp_rx_cnt < icmp_data_length)begin
                            icmp_rx_data_d0 <= gmii_rxd;
                            icmp_rx_cnt <= icmp_rx_cnt + 1'b1;
                            if(icmp_rx_cnt[0] == 1'b1)
                                reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add;
                            else
                                reply_checksum_add <= reply_checksum_add;
                        end
                        else;

                        if(icmp_rx_cnt == icmp_data_length - 1'b1)begin
                            state_en <= 1'b1;
                            icmp_rx_cnt <= 16'd0;
                            rec_en_cnt <= 2'd0;
                            rec_pkt_done <= 1'b1;
                            rec_byte_num <= icmp_data_length;
                        end
                        else;
                    end
                    else;
                end
                RX_DONE:begin   //单包数据接收完成
                    rec_en <= 1'b0;
                    if(gmii_rx_dv == 1'b0 && state_en == 1'b0)begin
                        state_en <= 1'b1;
                        reply_checksum <= reply_checksum_add;
                        reply_checksum_add <= 32'd0;
                    end
                    else;
                end
                default: ;
            endcase
        end
    end

endmodule

仿真接如下:

接收部分和ARP大同小异故不再赘述。

4.上板测试:

IP协议和ICMP协议 - eiSouthBoy - 博客园 (cnblogs.com)

IP协议及ICMP协议简介_ip与icmp-CSDN博客

相关推荐
周湘zx16 小时前
项目三:信号源的FPGA实现
fpga开发
9527华安17 小时前
FPGA多路MIPI转FPD-Link视频缩放拼接显示,基于IMX327+FPD953架构,提供2套工程源码和技术支持
fpga开发·架构·音视频
上理考研周导师17 小时前
【FPGA】ISE13.4操作手册,新建工程示例
fpga开发
技术小白爱FPGA20 小时前
Xilinx 平台 drp 动态调节 mmcm
fpga开发
北京太速科技股份有限公司1 天前
太速科技-889-基于RFSOC XCZU49DR的 16T16R的软件无线电硬件
fpga开发
stm 学习ing1 天前
HDLBits训练5
c语言·fpga开发·fpga·eda·hdlbits·pld·hdl语言
超能力MAX1 天前
IIC驱动EEPROM
单片机·嵌入式硬件·fpga开发
吉大一菜鸡2 天前
FPGA学习(基于小梅哥Xilinx FPGA)学习笔记
笔记·学习·fpga开发
9527华安2 天前
FPGA实现MIPI转FPD-Link车载同轴视频传输方案,基于IMX327+FPD953架构,提供工程源码和技术支持
fpga开发·架构·mipi·imx327·fpd-link·fpd953