赞
踩
前文链接:FPGA - 以太网UDP通信(一)
在上文章中介绍了以太网简介,以太网UDP通信硬件结构,以及PHY芯片RGMII接口-GMII接口转换逻辑,接下来介绍UDP通信结构框图以及数据链路层(MAC层)接受发送逻辑。
在上一篇文章中介绍了以太网UDP通信组包过程图:
可以看到用户数据通过UDP层添加udp头部,然后经过IP层添加ip头部,在经过数据链路层(MAC层)添加mac头部,送到PHY芯片物理层。一步步完成组包发送过程。同样的,从PHT芯片输出的数据经过MAC层,IP层,UDP层一步步完成解包校验过程。因此根据此思路画出简易结构框图如下:
在上篇文章中实现了rgmii_interface(rgmii_receive和rgmii_send)模块,接下来实现mac_layer(mac_receive和mac_send)模块。
MAC不是物理层,MAC层是数据链路层的两个子层之一。
以太网V2的MAC帧格式:
------------------------------------- mac数据包头 22byte -------------------------------------
| 前导码(前同步码) 7 byte |
| 8'h55 |
| SFD(帧开始定结符) 1byte |
| 8'hd5 |
| 目的mac地址 6byte |
| 源mac地址 6byte |
| 类型/长度 2byte |
| 小于1536表示长度, |
| 大于1536表示类型 arp:16'h0806 , ip: 16'h0800 |
| 数据 46-1500byte |
| FCS(帧校验序列CRC) 4byte |
----------------------------------------------------------------------------------------------------------
在上面的结构框图中,可以看到,mac_layer中包括mac_receive和mac_send模块。
---------------------------------------------------------- 思路 ------------------------------------------------------------
1,在MAC帧格式中,最后4byte 是FCS(帧校验序列CRC), 所以在接受和发送过程中必须把帧校验序列前面的数据存储(fifo)起来,等到CRC校验完成,再进行数据传输。
2,在以太网UDP通信中,用户端数据传输是在用户时钟下进行的,而PHY芯片数据传输是在PHY芯片的时钟下进行的。所以在这里我们要做一个跨时钟域的处理。
3,跨时钟域处理:在多bit信号跨时钟域中常常使用异步fifo来处理,但如何很好的设计这一处理过程呢?在这里我们进行双fifo跨时钟域(其实在之前文章中也用过这样的处理方式)。
首先第一个是数据fifo,缓存有效数据+last数据(用来指示有效数据的最后一个数据)
其次第二个是控制fifo,缓存数据对应的地址,长度,类型.....(在mac_layer中缓存的是帧类型和CRC校验结果)
然后判断fifo非空(rdemoty信号为低), 就把有效数据和控制信息从fifo里面读出来,
控制fifo的读使能rden只需要拉高一拍,数据fifo读使能rden要一直拉高,直到读出来最后一个数据,通过判断读出来的last信号,来拉低rden。
4,CRC校验
5,如何准确有效的进行数据组包,解包校验呢?
设置计数器,接受端设置rx_cnt,发送端设置tx_cnt。用计数器控制组包解包校验过程。
下图是mac_lay层的结构框图:
-----------------------------------------------------------------------------------------------------------------------------
CRC32校验:
- `timescale 1ns / 1ps
-
- module crc32_d8(
- input clk,
- input reset,
-
- input crc_din_vld,
- input [7:0] crc_din ,
- input crc_done ,
- output [31:0] crc_dout
-
- );
-
- wire [7:0] crc_din_r;
- reg [31:0] crc_dout_r;
- wire [31:0] crc_data;
-
- assign crc_din_r = {crc_din[0],crc_din[1],crc_din[2],crc_din[3],crc_din[4],crc_din[5],crc_din[6],crc_din[7]};
- assign crc_dout = ~{crc_dout_r[0],crc_dout_r[1],crc_dout_r[2],crc_dout_r[3],crc_dout_r[4],crc_dout_r[5],crc_dout_r[6],crc_dout_r[7],
- crc_dout_r[8],crc_dout_r[9],crc_dout_r[10],crc_dout_r[11],crc_dout_r[12],crc_dout_r[13],crc_dout_r[14],crc_dout_r[15],
- crc_dout_r[16],crc_dout_r[17],crc_dout_r[18],crc_dout_r[19],crc_dout_r[20],crc_dout_r[21],crc_dout_r[22],crc_dout_r[23],
- crc_dout_r[24],crc_dout_r[25],crc_dout_r[26],crc_dout_r[27],crc_dout_r[28],crc_dout_r[29],crc_dout_r[30],crc_dout_r[31]
- };
-
- assign crc_data[0] = crc_din_r[6] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[30];
- assign crc_data[1] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[2] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[3] = crc_din_r[7] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[31];
- assign crc_data[4] = crc_din_r[6] ^ crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[28] ^ crc_dout_r[30];
- assign crc_data[5] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[27] ^ crc_dout_r[28] ^ crc_dout_r[29] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[6] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[28] ^ crc_dout_r[29] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[7] = crc_din_r[7] ^ crc_din_r[5] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_din_r[0] ^ crc_dout_r[24] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[29] ^ crc_dout_r[31];
- assign crc_data[8] = crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[0] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[27] ^ crc_dout_r[28];
- assign crc_data[9] = crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_dout_r[1] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[28] ^ crc_dout_r[29];
- assign crc_data[10] = crc_din_r[5] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_din_r[0] ^ crc_dout_r[2] ^ crc_dout_r[24] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[29];
- assign crc_data[11] = crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[3] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[27] ^ crc_dout_r[28];
- assign crc_data[12] = crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[4] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[28] ^ crc_dout_r[29] ^ crc_dout_r[30];
- assign crc_data[13] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_dout_r[5] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[29] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[14] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[2] ^ crc_dout_r[6] ^ crc_dout_r[26] ^ crc_dout_r[27] ^ crc_dout_r[28] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[15] = crc_din_r[7] ^ crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[3] ^ crc_dout_r[7] ^ crc_dout_r[27] ^ crc_dout_r[28] ^ crc_dout_r[29] ^ crc_dout_r[31];
- assign crc_data[16] = crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[0] ^ crc_dout_r[8] ^ crc_dout_r[24] ^ crc_dout_r[28] ^ crc_dout_r[29];
- assign crc_data[17] = crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[1] ^ crc_dout_r[9] ^ crc_dout_r[25] ^ crc_dout_r[29] ^ crc_dout_r[30];
- assign crc_data[18] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[2] ^ crc_dout_r[10] ^ crc_dout_r[26] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[19] = crc_din_r[7] ^ crc_din_r[3] ^ crc_dout_r[11] ^ crc_dout_r[27] ^ crc_dout_r[31];
- assign crc_data[20] = crc_din_r[4] ^ crc_dout_r[12] ^ crc_dout_r[28];
- assign crc_data[21] = crc_din_r[5] ^ crc_dout_r[13] ^ crc_dout_r[29];
- assign crc_data[22] = crc_din_r[0] ^ crc_dout_r[14] ^ crc_dout_r[24];
- assign crc_data[23] = crc_din_r[6] ^ crc_din_r[1] ^ crc_din_r[0] ^ crc_dout_r[15] ^ crc_dout_r[24] ^ crc_dout_r[25] ^ crc_dout_r[30];
- assign crc_data[24] = crc_din_r[7] ^ crc_din_r[2] ^ crc_din_r[1] ^ crc_dout_r[16] ^ crc_dout_r[25] ^ crc_dout_r[26] ^ crc_dout_r[31];
- assign crc_data[25] = crc_din_r[3] ^ crc_din_r[2] ^ crc_dout_r[17] ^ crc_dout_r[26] ^ crc_dout_r[27];
- assign crc_data[26] = crc_din_r[6] ^ crc_din_r[4] ^ crc_din_r[3] ^ crc_din_r[0] ^ crc_dout_r[18] ^ crc_dout_r[24] ^ crc_dout_r[27] ^ crc_dout_r[28] ^ crc_dout_r[30];
- assign crc_data[27] = crc_din_r[7] ^ crc_din_r[5] ^ crc_din_r[4] ^ crc_din_r[1] ^ crc_dout_r[19] ^ crc_dout_r[25] ^ crc_dout_r[28] ^ crc_dout_r[29] ^ crc_dout_r[31];
- assign crc_data[28] = crc_din_r[6] ^ crc_din_r[5] ^ crc_din_r[2] ^ crc_dout_r[20] ^ crc_dout_r[26] ^ crc_dout_r[29] ^ crc_dout_r[30];
- assign crc_data[29] = crc_din_r[7] ^ crc_din_r[6] ^ crc_din_r[3] ^ crc_dout_r[21] ^ crc_dout_r[27] ^ crc_dout_r[30] ^ crc_dout_r[31];
- assign crc_data[30] = crc_din_r[7] ^ crc_din_r[4] ^ crc_dout_r[22] ^ crc_dout_r[28] ^ crc_dout_r[31];
- assign crc_data[31] = crc_din_r[5] ^ crc_dout_r[23] ^ crc_dout_r[29];
-
- always @(posedge clk) begin
- if (reset)
- crc_dout_r <= 32'hffffffff;
- else if (crc_done)
- crc_dout_r <= 32'hffffffff;
- else if (crc_din_vld)
- crc_dout_r <= crc_data;
- else
- crc_dout_r <= crc_dout_r;
- end
-
- endmodule
mac_receive代码:
- //功能 : ①完成校验,将有效数据和MAC头部分离出来
- // ②跨时钟域
- // -----------------------------------------------------------------------------
- `timescale 1ns / 1ps
-
- module mac_receive #(
- parameter LOCAL_MAC_ADDR = 48'hffffff_ffffff,
- parameter CRC_CHACK_EN = 1
- )(
- input clk , //用户接受端时钟
- input phy_rx_clk , //phy芯片提供的时钟
- input reset , //用户端复位信号
- input phy_rx_reset , //phy接受端复位
-
- /*-------rgmii_recive模块交互的信号----------------*/
- input gmii_rx_data_vld ,
- input [7:0] gmii_rx_data ,
- /*-------mac_to_arp_ip模块交互的信号----------------*/
- output reg mac_rx_data_vld ,
- output reg mac_rx_data_last ,
- output reg [7:0] mac_rx_data ,
- output reg [15:0] mac_rx_frame_type,
- /*-------rx_crc32_d8模块交互的信号----------------*/
- output reg rx_crc_din_vld ,
- output reg [7:0] rx_crc_din ,
- output reg rx_crc_done ,
- input [31:0] rx_crc_dout
- );
-
- endmodule
mac_send代码:
- //功能 ① 完成crc校验 mac头部 有效数据 crc校验 ...等组包
- // ② 跨时钟域
- // -----------------------------------------------------------------------------
- `timescale 1ns / 1ps
- module mac_send #(
- parameter LOCAL_MAC_ADDR = 48'hffffff_ffffff,
- parameter TARGET_MAC_ADDR = 48'hffffff_ffffff
-
- )(
- input clk , //用户发送端时钟
- input phy_tx_clk ,
-
- input reset ,
- input phy_tx_reset ,
-
- /*-------rgmii_send模块交互的信号----------------*/
- output reg gmii_tx_data_vld ,
- output reg [7:0] gmii_tx_data ,
-
- /*-------ip_send模块交互的信号--------------------*/
- input mac_tx_data_vld ,
- input mac_tx_data_last ,
- input [7:0] mac_tx_data ,
- input [15:0] mac_tx_frame_type,
- input [15:0] mac_tx_length ,
-
- /*-------tx_crc32_d8模块交互的信号----------------*/
- output reg tx_crc_din_vld ,
- output [7:0] tx_crc_din ,
- output reg tx_crc_done ,
- input [31:0] tx_crc_dout
-
- );
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- endmodule
mac层分别实现了接收和发送两部分,将两部分例化封装顶层mac_layer:
- `timescale 1ns / 1ps
-
- module mac_layer #(
- parameter LOCAL_MAC_ADDR = 48'hffffff_ffffff ,
- parameter TARGET_MAC_ADDR = 48'hffffff_ffffff ,
- parameter CRC_CHACK_EN = 1
-
- )(
- input app_tx_clk ,
- input app_rx_clk ,
- input phy_tx_clk ,
- input phy_rx_clk ,
-
- input app_tx_reset ,
- input app_rx_reset ,
- input phy_tx_reset ,
- input phy_rx_reset ,
-
- input gmii_rx_data_vld ,
- input [7:0] gmii_rx_data ,
- output gmii_tx_data_vld ,
- output [7:0] gmii_tx_data ,
-
- output mac_rx_data_vld ,
- output mac_rx_data_last ,
- output [7:0] mac_rx_data ,
- output [15:0] mac_rx_frame_type,
-
- input mac_tx_data_vld ,
- input mac_tx_data_last ,
- input [7:0] mac_tx_data ,
- input [15:0] mac_tx_frame_type,
- input [15:0] mac_tx_length
-
- );
-
-
-
-
- wire tx_crc_din_vld;
- wire [7:0] tx_crc_din ;
- wire tx_crc_done ;
- wire [31:0] tx_crc_dout ;
-
- wire rx_crc_din_vld;
- wire [7:0] rx_crc_din ;
- wire rx_crc_done ;
- wire [31:0] rx_crc_dout ;
-
-
- mac_send #(
- .LOCAL_MAC_ADDR(LOCAL_MAC_ADDR),
- .TARGET_MAC_ADDR(TARGET_MAC_ADDR)
- ) mac_send (
- .clk (app_tx_clk),
- .phy_tx_clk (phy_tx_clk),
-
- .reset (app_tx_reset),
- .phy_tx_reset (phy_tx_reset),
-
- .gmii_tx_data_vld (gmii_tx_data_vld),
- .gmii_tx_data (gmii_tx_data),
-
- .mac_tx_data_vld (mac_tx_data_vld),
- .mac_tx_data_last (mac_tx_data_last),
- .mac_tx_data (mac_tx_data),
- .mac_tx_frame_type (mac_tx_frame_type),
- .mac_tx_length (mac_tx_length),
-
- .tx_crc_din_vld (tx_crc_din_vld),
- .tx_crc_din (tx_crc_din),
- .tx_crc_done (tx_crc_done),
- .tx_crc_dout (tx_crc_dout)
- );
-
- mac_receive #(
- .LOCAL_MAC_ADDR(LOCAL_MAC_ADDR),
- .CRC_CHACK_EN (CRC_CHACK_EN)
- ) mac_receive (
- .clk (app_rx_clk),
- .phy_rx_clk (phy_rx_clk),
-
- .reset (app_rx_reset),
- .phy_rx_reset (phy_rx_reset),
-
- .gmii_rx_data_vld (gmii_rx_data_vld),
- .gmii_rx_data (gmii_rx_data),
-
- .mac_rx_data_vld (mac_rx_data_vld),
- .mac_rx_data_last (mac_rx_data_last),
- .mac_rx_data (mac_rx_data),
- .mac_rx_frame_type (mac_rx_frame_type),
-
- .rx_crc_din_vld (rx_crc_din_vld),
- .rx_crc_din (rx_crc_din),
- .rx_crc_done (rx_crc_done),
- .rx_crc_dout (rx_crc_dout)
- );
-
- crc32_d8 tx_crc32_d8
- (
- .clk (phy_tx_clk),
- .reset (phy_tx_reset),
-
- .crc_din_vld (tx_crc_din_vld),
- .crc_din (tx_crc_din),
- .crc_done (tx_crc_done),
- .crc_dout (tx_crc_dout)
- );
-
- crc32_d8 rx_crc32_d8
- (
- .clk (phy_rx_clk),
- .reset (phy_rx_reset),
-
- .crc_din_vld (rx_crc_din_vld),
- .crc_din (rx_crc_din),
- .crc_done (rx_crc_done),
- .crc_dout (rx_crc_dout)
- );
-
-
-
- endmodule
至此,我们完成了以太网发送过程中最底层 ,也是最重要的部分MAC层的发送与接受。 关于本节中的CRC校验部分没有过多介绍,这部分可通过CRC生成网页来生成CRC校验代码。
接下来,在下一篇博客中将会实现ip层的接收与发送。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。