Verilog实现以太网接收部分的学习笔记

2023-12-18 10:44:56

前言

最近需要进行aurora接口的报文解析,所以学了一下以太网的报文解析方法。
基于verilog实现以太网收发通信时,主要包括四个模块:GMII 转 RGMII接口模块(4bit转8bit)、以太网接收模块、以太网发送模块以及以太网的控制模块。

1 以太网基础知识的掌握

首先大概了解一下以太网的基础概念以及相关的IP五层模型

如下图所示,为模型的各个层。各层之间可以简单理解成不断组包和拆包的过程。
另外对于数据包以及报文或者帧差不多,主要是在不同的层有不同的称号。比如说一般在数据链路层,我们称为数据帧,而在网络层一般称为报文。
在这里插入图片描述


理解七层之后,我们要了解每层要重点掌握的东西。

  • 物理层的话,我理解的是主要掌握通信接口的外部输入输出引脚的分配,比如说RX和TX端的连接等。
  • 另外重点掌握以太网的传输格式(数据链路层、网络层、传输层),这也是后续设计收发状态机以及编写代码的关键。
  • 应用层是计算机用户,以及各种应用程序和网络之间的接口。我的理解是这方面又软件或驱动来控制,主要负责在应用层下报文。

2 以太网的数据传输格式

2.1 数据链路层的数据帧

如下图所示,为整个以太网的数据传输格式:
其存在于数据链路层。前面说了,各层之间就是组包和拆包的过程,所以将数据链路层的前导码、SFD、以太网帧头以及FCS去掉之后,剩下的以太网数据就是网络层的报文了。

在这里插入图片描述

每个字段的含义(重点看一下加粗部分即可):
前导码(Preamble):帧头,用于数据同步。物理层使用固定的连续7Byte的0x55实现。

帧起始定界符(SFD,Start Frame Delimiter):用于区分前导码与数据段,为固定1Byte的0xd5

目的MAC地址:即接收端物理MAC地址,占6Byte,为固定值,因为每个设备都对应唯一个MAC地址。MAC地址从应用上可分为单播地址、组播地址和广播地址。
单播地址:第一个字节的最低位为0,比如0-00-00-11-11-11,一般用于标志唯一的设备;
组播地址:第一个字节的最低位为1,比如01-00-00-11-11-11,一般用于标志同属一组的多个设备;
广播地址:全为1,即FF-FF-FF-FF-FF-FF,用于标志同一网段中的所有设备。

源MAC地址:即发送端物理MAC地址6Byte

数据帧类型/长度2B,当这两个字节的值小于1536 (十六进制为0x0600)时代表该以太网中数据段的长度;如果这两个字节的值大于1536,则表示与以太网帧相关的MAC客户端协议的类型,例如0x0800代表IP协议(网际协议)、0x0806 代表ARP协议(地址解析协议) 等

以太网数据长度为46-1500Byte。最大值1500称为以太网的最大传输单元(MTU,Maximum Transmission Unit)。接收到的数据包如果少于64字节会被认为发生冲突,数据包被自动丢弃

校验(FCS,Frame Check Sequence):确保数据的正确传输,在数据的尾部加入了4Byte的循环冗余校验码(CRC)来检验数据是否传输错误。CRC数据校验从目的MAC地址开始。

帧间隙(IFG,Interpacket Gap):以太网相邻两帧之间的时间间隔,即网络设备和组件在接收一帧之后,需要短暂的时间来恢复并为接收下一帧做准备的时间,最小值是96 bit time(媒介中发送96位原始数据所需要的时间)。


2.2 网络层的IP报文格式

接下来分析以太网数据部分,即网络层报文。由于网络层需要进行IP寻址操作,因此传入网络层的以太网数据中包含20 字节的IP协议首部(IP报文头) + 报文数据。同样,将IP报文头拆掉之后,传入传输层。

如下为IP报文的格式:
在这里插入图片描述
重点掌握IP报文头中各字段的含义:

  1. 版本:定义IP协议版本,二进制0100表示IPv4,二进制0110表示IPv6.

  2. 首部长度:IP报头长度,单位为DW。协议头最小值为5DW,最大值为15DW。

  3. 服务类型 :定义上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。前3位成为优先位,后面4位成为服务类型,最后1位预留位。即这8位字段用于分配优先级、延迟、吞吐量以及可靠性。

  4. 总长度定义整个IP报文的字节长度,即包括IP报文头及数据,最大不超过65535字节

  5. 标识:用于标识主机发送的数据报。即报文序号,每个报文递增1

  6. 标记:由3位字段构成,其中最低位为ME (用于表示是否为最后一个分片报文。若当前报文不是最后一个分片报文,ME=1,否则若当前报文是最后一个分片报文,则ME=0。中间位为DF,用于表示当前数据报文是否为分片报文(分片报文使能),若为1表示单片报文,否则为分片报文。最高位为预留位。

  7. 分段偏移:在接收方进行数据报重组时用来标识分段的顺序,即分片报文序号

  8. 生存时间:一种计数器,在丢弃数据报的每个点值依次减1直至减少为0。这样确保数据报拥有有限的环路过程(即TTL),限制了数据报的寿命。

  9. 协议 :该字段表示处理完成后,由哪种上层协议(UDP、ARP)接收报文。

  10. 首部校验和:以确保IP报文头的完整性。进行首部校验和计算,计算后将值填充到该字段即可。【计算方法

  11. 源地址发送端的IP地址,该地址在传输期间必须保持不变。

  12. 目的地址接收端的IP地址,同上。


2.3 传输层的UDP协议

传输层使用UDP协议,那么除去协议头之外,传入传输层的数据包为UDP数据包。
前面的字段和上面一样,我们直接看UDP首部及数据段。其中UDP首部即UDP数据包头,数据段则是最终的用户数据。
在这里插入图片描述

2.3.1 UDP数据传输的格式

在这里插入图片描述
首部包含:

● 源端口号:用于区分不同服务的端口,范围:0~ 65535。
● 目的端口号:接收端端口号。
UDP长度整个UDP数据报的长度,含 UDP 首部长度+数据长度
● UDP校验和:注意需要对UDP伪首部、首部以及数据进行校验和计算。


如下是UDP校验和的计算范围:(注意伪首部的内容)
在这里插入图片描述

2.3.2 UDP以太网传输的设计方法

本篇主要讲UDP以太网接收部分的状态机设计(单片报文的情况):
由于以太网头部的前导码、MAC地址以及IP地址都是固定值,所以如果固定值信息错误,当前以太网数据帧可以直接丢弃,源主机重发。所以在状态跳转时,各头状态下错误会跳转到空闲状态。

在这里插入图片描述


2.3.3 ARP协议(地址解析协议)

ARP协议分为ARP请求和ARP应答
具体的通信方式:源主机发起查询目的MAC地址和目的IP地址的报文(ARP请求报文)目的主机响应源主机发送包含本地MAC地址的报文称为ARP应答

2.3.3.1 ARP传输数据格式

由于以太网数据段最少为46个字节,而ARP数据包总长度为28个字节,因此在ARP数据段后面需要填充18字节的数据,以满足以太网传输格式的要求,填充的数据可以为任意值,但一般为0。
在这里插入图片描述
下面分析ARP数据段的内容:
在这里插入图片描述
● 硬件类型(Hardware type):硬件地址的类型,1 表示以太网地址。

● 协议类型:要映射的协议地址类型,ARP协议的上层协议为IP协议,因此该协议类型为IP协议,其值为0x0800

● 硬件地址长度 (Hardware size):MAC 地址长度,为6Byte。

● 协议地址长度 (Protocol size):IP地址的长度,为4Byte。

OP(Opcode):操作码,用于表示该数据包为ARP请求或者ARP应答。1表示ARP请求,2表示ARP应答

● 目的MAC地址:接收端的硬件地址,在ARP请求时由于不知道接收端MAC地址,因此该字段为广播地址,即48’hff_ff_ff_ff_ff_ff。广播后,局域网内所有的主机都会接收这个地址并处理该请求报文,根据请求进行验证,从而查看接收到的ip地址是不是自己,是的话,ARP应答的时候就把接收方的MAC地址和IP地址返回去。

关于广播:
在这里插入图片描述


3 UDP以太网传输的接收部分的verilog代码编写

下面重点对UDP以太网传输的接收部分进行verilog代码编写:
主要用计数器数节拍来实现(不够灵活),是一种锻炼吧,后续尽量在实际应用中学习更好的实现方法。


注意:
由于IP报文的传输单位是32bit,所以我们要进行数据的8bit到32bit转换。这里转换时要注意,如果IP报文的数据部分不是整32bit的情况

这里我们可以用接受完成时对应的计数器值来分析。比如说8bit转32bit。应该是每4个8bit数据得到一个32bit。

那么设计一个0-3计数器,每次cnt = 3得到一个32bit数据。
当完成时,判断对应的计数器值.
如果是0,说明还差三个8bit数据,则低位补24‘b0
如果是1,表示还差两个8bit数据,则低位补16’b0
以此类推…

下面的整体的代码:

//规定IP头的长度为5DW
module udp_rx(
    input                               CLK                                     ,    //时钟信号
    input                               RST_N                                   ,    //复位信号,低电平有效
    
    input                               GMII_RX_DV                              ,    //GMII输入数据有效信号
    input        [ 7:0]                 GMII_RXD                                ,    //GMII输入数据
    output                              REC_PKT_DONE                            ,    //以太网单包数据接收完成信号

    output       [31:0]                 REC_DATA                                ,     //以太网接收的数据
    output       [ 3:0]                 KEEP_NUM                                      //最后一个移位数据的有效字节数

);

//定义参数                                                  
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  
                                                     
localparam           ST_IDLE             = 7'b000_0001                             ; //初始状态,等待接收有效前导码0x55
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                             ; //接收有效用户数据 (UDP数据)     
localparam           ST_RX_END           = 7'b100_0000                             ; //接收结束        
                                                     
localparam           ETH_TYPE            = 16'h0800                                ; //以太网协议类型 IP协议
localparam           UDP_TYPE            = 8'h11                                   ; //UDP协议类型   

//定义内部信号 
reg                 r_GMII_RX_DV        = 1'h0                                     ;  
reg  [7:0]          r_GMII_RXD          = 8'h0                                     ;

reg  [6:0]          r_CUR_ST            = ST_IDLE                                  ;
reg  [6:0]          r_NXT_ST                                                       ;

reg  [ 7:0]         r_CNT              = 8'h0                                      ; //传输期间字节计数器
reg  [47:0]         r_des_mac          = 48'h0                                     ; //解析出来的目的MAC地址
reg  [15:0]         r_eth_type         = 16'hff                                    ; //解析出来的以太网类型
reg  [ 5:0]         r_ip_head_byte_num = 6'h0                                      ; //解析出来的IP首部长度,单位DW
reg  [31:0]         r_des_ip           = 32'h0                                     ; //解析出来的IP地址
reg  [ 7:0]         r_ip_type          = 8'h0                                      ; //解析出来的IP协议类型
reg  [15:0]         r_udp_byte_num     = 16'h0                                     ; //解析出来的UDP长度
reg  [ 7:0]         r_data_cnt         = 8'h0                                      ; //有效数据计数器
reg                 r_rec_pkt_done     = 1'h0                                      ; //有效数据接收完成信号
reg                 r_shift_en         = 1'h0                                      ; //移位输出使能信号
reg  [ 7:0]         r_shif_cnt         = 8'h0                                      ; //移位输出计数器  
reg  [31:0]         r_rec_data         = 32'h0                                     ; //有效32bit移位数据
reg  [31:0]         r_rec_data1        = 32'h0                                     ; //非有效字数补0后的32bit移位数据
wire [31:0]         REC_DATA                                                       ; //有效udp数据转成32bit
reg  [ 3:0]         r_keep_num         = 4'h0                                      ; //最后一个移位数据的有效字节数
reg                 r_shift_done                                                   ; //全部接收数据移位完成
      
//输出       
    assign REC_PKT_DONE                =  r_rec_pkt_done                            ;       
    assign REC_DATA                    =  r_rec_data1                               ;         
    assign KEEP_NUM                    =  r_keep_num                                ;  
    
//以太网接收有效信号及数据打一拍
    always @ (posedge CLK)
        if(~RST_N)
        begin                              
            r_GMII_RX_DV                <= 1'h0;  
            r_GMII_RXD                  <= 8'h0;  
        end             
        else          
        begin                                      
            r_GMII_RX_DV                <= GMII_RX_DV; 
            r_GMII_RXD                  <= GMII_RXD;
        end
    
//传输字节计数器
    always @ (posedge CLK)
    begin
        if(r_NXT_ST == ST_IDLE)
            r_CNT                      <= 1'h0;
        else
        begin
            if(r_GMII_RX_DV)
                r_CNT                  <= r_CNT + 1'h1; 
            else
               r_CNT                   <= r_CNT; 
        end
    end

//有效数据计数器
    always @ (posedge CLK)
        if(r_NXT_ST == ST_RX_DATA)
            r_data_cnt                 <= r_data_cnt + 1'h1;
        else
            r_data_cnt                 <= 8'h0;
    
//解析所需信息——目的MAC地址、以太网类型
//IP头长度、IP协议类型、目的IP地址
//UDP长度
always @ (posedge CLK)
begin
    case(r_NXT_ST)
        ST_ETH_HEAD : //目的MAC地址、以太网类型
        begin
            if((r_CNT >= 8'd8) && (r_CNT <= 8'd13)) //8-13共6B目的MAC地址
                r_des_mac           <= {r_des_mac[39:0],r_GMII_RXD};
            else if(r_CNT == 8'd20)
                r_eth_type[15:8]    <= r_GMII_RXD; 
            else if(r_CNT == 8'd21)
                r_eth_type[7:0]     <= r_GMII_RXD; 
            else
            begin
                r_des_mac           <= r_des_mac; 
                r_eth_type          <= r_eth_type;
            end  
         end
        ST_IP_HEAD :
        begin
            if(r_CNT == 8'd22)
                r_ip_head_byte_num <= {r_GMII_RXD[3:0],2'b0}; //解析出来IP头长度放高位20B,由于IP头最大15DW,即60字节,所以位宽设成6bit
            else if(r_CNT == 8'd31)
                r_ip_type          <= r_GMII_RXD;
            else if((r_CNT >= 8'd38) && (r_CNT <= 8'd41))
                r_des_ip <= {r_des_ip[23:0],r_GMII_RXD};      //寄存目的IP地址 4B,先进来放高位
            else
            begin
                r_ip_head_byte_num <= r_ip_head_byte_num; 
                r_ip_type          <= r_ip_type;
                r_des_ip           <= r_des_ip;
            end
        end   
        ST_UDP_HEAD :
        begin
            if(r_CNT == 8'd46)
                r_udp_byte_num[15:8] <= r_GMII_RXD;
            else if(r_CNT == 8'd47)
                r_udp_byte_num[7:0] <= r_GMII_RXD;
        end  
        ST_RX_DATA : 
        begin
            r_udp_byte_num <= r_udp_byte_num;
        end   
        default :
        begin
            r_des_mac          <= 0;
            r_eth_type         <= 16'hffff;
            r_ip_head_byte_num <= 0;
            r_des_ip           <= 0;
            r_ip_type          <= 0;
            r_udp_byte_num     <= 0;
        end
    endcase
end

//状态机
    always @ (posedge CLK)
        if(~RST_N)                              
            r_CUR_ST                                 <= ST_IDLE;               
        else                                                                    
            r_CUR_ST                                 <= r_NXT_ST;            

    always @(*) begin                                                                                                                                                                 
        case(r_CUR_ST)                                                                 
            ST_IDLE : 
            begin                                              //等待接收第一个有效数据8'h55             
                if( r_GMII_RX_DV && (r_GMII_RXD == 8'h55) )     
                    r_NXT_ST = ST_PREAMBLE;                                           
                else                                                                    
                    r_NXT_ST = ST_IDLE;                                               
            end                                                                         
            ST_PREAMBLE : 
            begin                                              //接收前导码 +SFD   
                if(r_CNT == 8'd7) 
                begin           
                    if(r_GMII_RX_DV && (r_GMII_RXD == 8'hd5) )                                                             
                        r_NXT_ST = ST_ETH_HEAD;  
                    else
                        r_NXT_ST = ST_IDLE;                    //SFD接收错误
                end                                          
                else if(r_GMII_RX_DV && (r_GMII_RXD != 8'h55))                                                       
                    r_NXT_ST = ST_IDLE;                        //前导码接收错误                                        
                else                                                                    
                    r_NXT_ST = ST_PREAMBLE;                                           
            end                                                                         
            ST_ETH_HEAD : begin                                //接收以太网帧头(比对目的MAC地址和类型) 
                if(r_GMII_RX_DV && (r_CNT == 8'd22))  
                begin      
                    //判断MAC地址是否为开发板MAC地址或者公共广播地址                                       
                    if((r_eth_type == ETH_TYPE) &&( (r_des_mac == BOARD_MAC)||(r_des_mac == 48'hff_ff_ff_ff_ff_ff)))                                                            
                        r_NXT_ST = ST_IP_HEAD;                                            
                    else                                                      
                        r_NXT_ST = ST_IDLE;                   //目的MAC地址或类型错误                                 
                end
                else
                begin
                    r_NXT_ST = ST_ETH_HEAD; 
                end                                          
            end                                                                         
            ST_IP_HEAD : begin                                  //接收IP头(比对IP头的协议类型以及IP目的地址 )                
                if(r_CNT == 8'd42)  //r_GMII_RX_DV && (r_CNT == 8'd42)
                    begin  
                        if((r_des_ip == BOARD_IP) && (r_ip_type == UDP_TYPE))                                                          
                            r_NXT_ST = ST_UDP_HEAD;  
                        else   
                            r_NXT_ST = ST_IDLE  ;               //IP头协议类型或目的地址错误  
                    end                                                                               
                else                                                                    
                    r_NXT_ST = ST_IP_HEAD;                                            
            end                                                                         
            ST_UDP_HEAD : begin                                 //接收UDP头               
                if(r_GMII_RX_DV &&  (r_CNT == 8'd49))           //跳转接收UDP数据                                                          
                    r_NXT_ST = ST_RX_DATA;                                            
                else                                                                    
                    r_NXT_ST = ST_UDP_HEAD;                                           
            end                                                                         
            ST_RX_DATA : begin                                  //接收有效数据                
                if( r_GMII_RX_DV && (r_data_cnt == (r_udp_byte_num - 16'd8)))    //接受完所有的有效数据 (当前:r_udp_byte_num = 26B)                                                   
                    r_NXT_ST = ST_RX_END;                                                    
                else                                                                    
                    r_NXT_ST = ST_RX_DATA;                                            
            end                                                                         
            ST_RX_END : 
            begin 
                if(r_rec_pkt_done)                              //单包数据接收结束                                                                 
                    r_NXT_ST = ST_IDLE;
                else
                    r_NXT_ST = ST_RX_END; 
            end                                               
                                                                            
            default : r_NXT_ST = ST_IDLE;                                             
        endcase                                                                         
    end   

//生成单包数据接收完成信号
always @ (posedge CLK)
    if(r_data_cnt == (r_udp_byte_num - 16'd8))
        r_rec_pkt_done <=  1'h1;
    else
        r_rec_pkt_done <= 1'h0;
        
//生成输出移位使能、计数器及移位数据
    always @ (posedge CLK)
        if(r_CUR_ST == ST_RX_DATA)
        begin
             r_shift_en                <= 1'h1;
             r_rec_data <= {r_rec_data[23:0],r_GMII_RXD}; //先入放高位
        end
        else
        begin
            r_shift_en                 <= 1'h0;
            r_rec_data                 <= 32'h0;
        end
                   
    always @ (posedge CLK)
        if(r_shift_en)
        begin
            if(r_shif_cnt == 8'd3)
                r_shif_cnt             <= 8'h0;
            else 
                r_shif_cnt             <= r_shif_cnt + 1'h1;
        end
        else
            r_shif_cnt                 <= 8'h0;

//最后一个移位数据不满32bit判断
    always @ (posedge CLK)
        if(r_CUR_ST == ST_RX_DATA)
        begin
            if(r_shif_cnt == 8'h3)
                r_rec_data1           <= r_rec_data;
            else
                r_rec_data1           <= r_rec_data1;
        end
        else if(r_CUR_ST == ST_RX_END)
        begin
            case(r_shif_cnt) //判断最后一个32bit数据的字节有效
                8'd0   : r_rec_data1  <= {r_rec_data[7:0],24'h0} ; //仅1个有效字节
                8'd1   : r_rec_data1  <= {r_rec_data[15:0],16'h0}; //仅2个有效字节
                8'd2   : r_rec_data1  <= {r_rec_data[23:0],8'h0} ; //仅3个有效字节
                8'd3   : r_rec_data1  <= r_rec_data              ; //全字节有效  
                default: r_rec_data1  <= 0;
            endcase
        end   
        else
            r_rec_data1 <=r_rec_data1;
    
//最后一个移位数据的有效字节数
     always @ (posedge CLK)
         if(r_CUR_ST == ST_RX_END)  
         begin
            r_shift_done           <= 1'h1;
            case(r_shif_cnt)
                8'd0   : r_keep_num <= 4'd1 ; //仅1个有效字节 
                8'd1   : r_keep_num <= 4'd2 ; //仅2个有效字节 
                8'd2   : r_keep_num <= 4'd3 ; //仅3个有效字节 
                8'd3   : r_keep_num <= 4'd4 ; //全字节有效   
                default: r_keep_num <= 0;
            endcase
         end        
         else
         begin
             r_keep_num             <= r_keep_num; 
             r_shift_done           <= 1'h0;
         end
endmodule  
//已仿真

以上是对于以太网接收部分的学习笔记,发送部分还没学,就是拼数据,等有时候在记录~
FPGA小白,若有不足或错误之处请指正。

文章来源:https://blog.csdn.net/H19981118/article/details/135000049
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。