实验将实现AX7103开发板和PC之间进行以太网数据通信, 通信协议采用Ethernet UDP通信协议。开发板上FPGA通过以太网PHY芯片KSZ9031RNX和网口连接, 通过RGMII接口跟FPGA进行数据通信。
从硬件角度来看以太网是由CPU,MAC,PHY三部分组成的,如下图:
上图中DMA集成在CPU,CPU,MAC,PHY并不是集成在同一个芯片内,由于PHY包含大量模拟器件,而MAC是典型的数字电路,考虑到芯片面积及模拟/数字混合架构的原因,将MAC集成进CPU而将PHY留在片外,这种结构是最常见的。
MAC(Media Access Control) 即媒体访问控制层协议。MAC由硬件控制器及MAC通信协议构成。其硬件框架如下图所示:
一般以太网MAC芯片的一端连接PCI总线,另一端连接PHY芯片上通过MII接口连接。
PHY(Physical Layer)是IEEE802.3中定义的一个标准模块,STA(Station Management Entity,管理实体,一般为MAC或CPU)通过MIIM(MII Manage Interface)对PHY的行为、状态进行管理和控制,而具体管理和控制动作是通过读写PHY内部的寄存器实现的。PHY的基本结构如下图:
(1)MII接口
MII(Media Independent interface)即介质无关接口,它是IEEE-802.3定义的行业标准,是MAC与PHY之间的接口。MII数据接口包含16个信号和2个管理接口信号,数据接口位宽4bit,时钟周期25Mhz,支持百兆以太网通信。如下图所示:
(2)GMII接口
GMII(Gigabit Media Independant Interface),千兆MII接口。GMII采用8位接口数据,工作时钟125MHz,因此传输速率可达1000Mbps。同时兼容MII所规定的10/100 Mbps工作方式。GMII接口数据结构符合IEEE以太网标准,该接口定义见IEEE 802.3-2000。信号定义如下:
(3)RGMII接口
RGMII(Reduced Gigabit Media Independant Interface),精简GMII接口。相对于GMII相比,RGMII具有如下特征:
在物理层上看,一个完整的以太网帧有7个字段,事实上,前两个字段并不能算是真正意义上的以太网数据帧,它们是以太网在物理层上发送以太网数据时添加上去的。
字段 | 字段长度(字节) | 说明 |
---|---|---|
前导码 | 7 | 7个字节的0x55,用于帧同步 |
帧开始符(SFD) | 1 | 标明下一个字节为目的MAC字段(0x5D) |
目的MAC地址 | 6 | 指明帧接收者 |
源MAC地址 | 6 | 指明帧发送者 |
长度/类型 | 2 | 当这两个字节的值小于1518时,那么它就代表其后数据字段的长度;如果这两个字节的值大于1518,则表示该以太网帧中的数据属于哪个上层协议(例如0x800,代表IP数据包;0x806,代表ARP数据包等。 |
数据和填充 | 46~1500 | 高层的数据,通常为3层协议数据单元。对于TCP/IP是IP数据包 |
帧校验序列(FCS) | 4 | 对接收网卡提供判断是否传输错误的一种方法,如果发现错误,丢弃此帧 |
在以太网帧中,目的MAC地址可以分为三类:单播地址、多播地址和广播地址。广播地址的所有48位全为1(即FF-FF-FF-FF-FF-FF),同一局域网中的所有网卡可以接收广播数据包。
UDP 是User Datagram Protocol的简称,中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议。
(1)IP数据报格式
IP是TCP/IP协议族中最核心的协议,所有的TCP、UDP、ICMP、IGMP数据都以IP数据报的格式传输。IP仅提供尽力而为的传输服务,如果发生某种错误,IP会丢失该数据,然后发送ICMP消息给信源端。
其中IP报文头部定义如下:
字段 | 字段长度(字节) | 说明 |
---|---|---|
版本+首部长度 | 1 | IP 协议版本:版本就是IPV4或者IPV6,一般选择IPV4,即版本值为4。IP首部长度:IP首部长度即有多少个32位数,当IP首部长度为20时(即无可选字段),该值为5.(5*4 =20) |
服务类型 | 1 | 指示了报文的优先权等,默认全部置0即可 |
总长度 | 2 | IP报文长度,包括IP报文报头+IP报数据 |
分段标识 | 2 | 是否属于同一数据段,IP报文的分段ID |
段标识和段偏移 | 2 | 可以设为0 |
生存周期(TTL) | 1 | 可以经过的最大路由数,生存时间字段设置了数据报可以经过的最多路由器数,表示数据包在网络上生存多久。TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃。 |
上层协议类型 | 1 | 指该封包所使用的网络协议类型,如ICMP、DNS等,占8位。常用协议号有00:IP01:ICMP06:TCP17:UDP |
报头校验和 | 2 | IP报头部分数据校验和 |
源IP地址 | 4 | 发送端的IP地址,如192.168.0.2 |
目的IP地址 | 4 | 接收端的IP地址,如192.168.0.3 |
可选字段 | 0~40 | 可选,没有的时候长度可以为0,该字段最大字长度40字节。 |
(2)UDP数据报格式
UDP数据报分为首部和用户数据部分,整个UDP数据报作为IP数据报的数据部分封装在IP数据报中,UDP报文的以太网帧结构如图所示。
UDP报文包括UDP报文首部数据和UDP报文数据(数据负载),其中UDP首部有8个字节,由4个字段构成,每个字段都是两个字节。
UDP报文描述如下:
字段 | 字段长度(字节) | 说明 |
---|---|---|
源端口 | 2 | 源端口号,需要对方回信时选用,不需要时全部置0 |
目的端口 | 2 | 目的端口号,在终点交付报文的时候需要用到 |
长度 | 2 | UDP的数据报的长度(包括UDP首部和数据)其最小值为8(只有首部) |
校验和 | 2 | 检测UDP数据报在传输中是否有错,有错则丢弃。该字段是可选的,当源主机不想计算校验和,则直接令该字段全为0。 |
以太网PHY芯片与FPGA的IP接口相连,KSZ9031芯片支持10/100/1000Mbps网络传输速率,通过RGMII接口与FPGA进行数据通信。另外FPGA可以通过MDI/MDIO管理接叔来配置戒读叏PHY芯片内部癿寄存器。
整个udp发送部分主要由一个顶层模块ethernet_test.v,UDP测试程序udp.v和三个子模块:UDP収送模块(ipsend.v),UDP接收模块(iprecieve.v)和CRC校验模块(crc.v)组成。
(1)ethernet_test.v
其中例化了两个模块,UDP控制模块和RAM模块,任务是将通过UDP接收回来的数据包存储在FPGA内部的RAM中,再不断把RAM中的数据包通过网口发送回ethernet网络。
(1.1) 对于双端口RAM来说,共有6根信号线:
ram ram_inst (
.clka(gmii_rx_clk), // input clka(时钟上升沿到来时对数据进行写入)
.wea(wea), // input [0 : 0] wea (“wea == 1”时只写不读,“wea == 0”时只读不写)
.addra(addra), // input [8 : 0] addra (输入地址线)
.dina(dina), // input [31 : 0] dina (输入数据线)
.clkb(gmii_tx_clk), // input clkb(时钟上升沿到来时对数据进行读出)
.addrb(ram_rd_addr), // input [8 : 0] addrb (输出地址线)
.doutb(ram_rd_data) // output [31 : 0] doutb (输出数据线)
);
对于双端口的时钟来说,数据写入时钟由接收端时钟gmii_rx_clk提供,数据读出时钟由接收端时钟gmii_tx_clk提供。对于使能信号、输入数据和地址需要进行一个判断:
//当ram_wr_finish为1时,将以太网接收回的数据写入到RAM中
//当ram_wr_finish为0时,将预先存好的数据写入到RAM中
assign wea = ram_wr_finish ? data_o_valid : ram_wren_i;
assign addra = ram_wr_finish ? ram_wr_addr : ram_addr_i;
assign dina = ram_wr_finish ? ram_wr_data : ram_data_i;
(1.2) 对于UDP收发模块来说,其中例化了三个模块,UDP发送模块、UDP接收模块和CRC校验模块,在进行发送前要进行CRC校验,接收不需要。
(1.2.1) 对于发送模块来说,接口采用GMII接口,整个发送过程通过一个状态机来控制。
最开始将状态机进入到idle状态,与此同时将7个前导码55、一个帧开始符d5、目的MAC地址ff-ff-ff-ff-ff-ff(广播包)、源MAC地址00-0A-35-01-FE-C0和IP包类型0800都准备好。
可以看到在刚开始通过一拍时钟将相应的所有寄存器的值都配置好了,包括输入数据datain,前导码和开始符preamble,MAC地址mac_addr.
idle:该状态下将所有信号全部复位并进行一段时间的延时,以控制隔一段时间发送一个数据,延迟完成后将状态过渡到start状态下。
为了仿真方便,这里将计数器的值设为125,换算成16进制为D7,从图中可以看到timer_counter寄存器记到该值,状态切换到start。
start:该状态下将ip报头部分准备好(版本号、报头长度等),并将状态过渡到make状态下。
对IP报头信息ip_header通过一拍进行赋值。
make:根据ip报头部分的内容生成ip报头的校验和,并将状态过渡到send55状态下。
ip校验和计算出来后,对ip_header[2]进行替换,由之前的80110000变为80117966,计算方法如下:
0x4500 + 0x0030 + 0x0001 + 0x4000 + 0x8011 + 0x0000(计算时置0) + 0xc0a8 + 0x0002 + 0xc0a8 + 0x0003 = 0x28697
0x0002 + 0x8697 = 0x8699
checksum = ~0x8699 = 7966(将结果换回到校验和对应的位置处即得到结果)
send55:发送发送8个IP前导码:7个55, 1个d5 ,并将状态过渡到sendmac状态下。
时钟下降沿写入55,55,55,55,55,55,55,d5。
sendmac:发送目标MAC address和源MAC address和IP包类型 ,使能crc校验,并将状态过渡到sendheader状态下。
时钟下降沿写入目标MAC地址ff,ff,ff,ff,ff,ff和源MAC地址00-0A-35-01-FE-C0以及IP包类型08,00,同时使能CRC校验,将crcen拉高。
sendheader:发送7个32bit的IP包头,{版本号+包头长度} {包头序列+段偏移} {生存周期+上层协议+校验和} {源IP地址} {目的IP地址} {源端口号+目的端口号} {UDP数据长度+UDP数据校验和},准备要发送的数据,并将状态过渡到senddata状态下。
senddata:将RAM中的数据作为UDP的数据发送出去,发送完毕将状态过渡到sendcrc状态下。
这里将tx_data_length设为28,tx_total_length设为48(首部长度为20),该状态结束条件为tx_data_counter == tx_data_length-9。此处RAM中存放的数据有20个字节,通过地址=ram_rd_addr==进行索引,共4个地址下的数据,每个地址存放4字节的内容。
我为了仿真清楚,地址中的数据是自己定的,内容如下:001中存放0x01020304,002中存放0x04050607,003中存放0x04030201,004中存放0x07060504,005中存放0x0a0b0c0d,其余均为0xffffffff。
计数器i的作用是将每个地址中4字节的内容1字节1字节发出去每当i为3时表示1个地址内容发送完成,则将下一个地址的内容给到寄存器datain_reg中,tx_data_counter的作用是记满20个字节的内容,表示将RAM中的数据都发出去。
sendcrc:发送32位的crc校验,并将状态过渡到idle状态下。
当crc计算完成后,crcen拉低,然后将CRC校验结果取反后1字节1字节发送出去。
(1.2.2) 对于接收模块来说,接口采用GMII接口,整个接收过程仍通过一个状态机来控制。
(1.2.3) 对于CRC校验来说,可以通过网站自动生成,这里不赘述。
参考:
https://blog.csdn.net/qq_35569806/article/details/84949741