Verilog实现以太网接收部分的学习笔记
文章目录
前言
最近需要进行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报文头中各字段的含义:
-
版本:定义IP协议版本,二进制0100表示IPv4,二进制0110表示IPv6.
-
首部长度:IP报头长度,单位为DW。协议头最小值为5DW,最大值为15DW。
-
服务类型 :定义上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。前3位成为优先位,后面4位成为服务类型,最后1位预留位。即这8位字段用于分配优先级、延迟、吞吐量以及可靠性。
-
总长度:定义整个IP报文的字节长度,即包括IP报文头及数据,最大不超过65535字节
-
标识:用于标识主机发送的数据报。即报文序号,每个报文递增1
-
标记:由3位字段构成,其中最低位为ME (用于表示是否为最后一个分片报文。若当前报文不是最后一个分片报文,ME=1,否则若当前报文是最后一个分片报文,则ME=0。中间位为DF,用于表示当前数据报文是否为分片报文(分片报文使能),若为1表示单片报文,否则为分片报文。最高位为预留位。
-
分段偏移:在接收方进行数据报重组时用来标识分段的顺序,即分片报文序号
-
生存时间:一种计数器,在丢弃数据报的每个点值依次减1直至减少为0。这样确保数据报拥有有限的环路过程(即TTL),限制了数据报的寿命。
-
协议 :该字段表示处理完成后,由哪种上层协议(UDP、ARP)接收报文。
-
首部校验和:以确保IP报文头的完整性。进行首部校验和计算,计算后将值填充到该字段即可。【计算方法】
-
源地址:发送端的IP地址,该地址在传输期间必须保持不变。
-
目的地址:接收端的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小白,若有不足或错误之处请指正。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!