学习FPGA过程中~,有挺多不会的地方,有指正的我一定耐心接受。我跟着小梅哥课程视频学的。
不论是再MCU还是FPGA中都涉及有以太网项目,以前学单片机以太网的时候是用的W5500的以太网模块,网上有挺多开源项目,加上商家给的例程结合结合,改一改就可以完成单片机的以太网项目,那时候就会涉及到“OSI七层模型和TCP/IP五层模型”。可以在网上搜一搜,了解了解。
一般说的FPGA UDP通信,FPGA只做到了传输层,传输层以上的会话层、表示层等等,FPGA是没有的。FPGA 开发板通过一片 以太网PHY芯片 提供对以太网连接的支持,PHY芯片内提供物理层,进行4b/10b编码,PHY芯片提供MII/GMII/RGMII 接口的MAC连接。
在传输层中 TCP 和 UDP都是传输层协议,它们都属于TCP/IP协议族。
FPGA UDP通信硬件构成
FPGA 和 PHY 芯片实现以太网传输的数据链路两端,有三种接口形式:MII、GMII、RGMII,其中MII适用于百兆网口,GMII和RGMII都用于百兆网口,GMII的传输是四位四位传输,RGMII是八位数据传输,所以RGMII有更高的数据传输速率。
MII接口
MII的接口图
信号 | 位宽 | 说明 |
TX_ER | 1 | 发送数据错误提示信号,同步于 TX_CLK ,高电平 有效,表示 TX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,TX_ER 不起作用。 |
TX_EN | 1 | 发送使能信号,只有在 TX_EN 有效期内传的数 据才有效。 |
TX_CLK | 1 | 发送参考时钟, 100Mbps 速率下,时钟频率 25MHz , 10Mbps 速率 下,时钟频率为 2.5MHz 。需要注意的是, TX_CLK 时钟的方向是从 PHY 侧指向 MAC 侧的,此时钟是由 PHY 提供的 |
TXD | 4 | 数据发送 |
RX_ER | 1 | 接收数据错误提示信号,同步于 RX_CLK ,高电平 有效,表示 RX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,RX_ER 不起作用。 |
RX_DV | 1 | 接收数据有效信号,作用类似于发送通道的 TX_EN。 |
RXD | 4 | 数据接收 |
RX_CLK | 1 | 接收数据参考时钟, 100Mbps 速率下,时钟频率为 25MHz , 10Mbps 速率下,时钟频率为 2.5MHz 。 RX_CLK 也是由 PHY 侧提供的。 |
CRS | 1 | 即 Carrier Sense ,载波侦测信号,不需要同步于参考时钟,只要有数 据传输,CRS 就有效。需要注意的是 CRS 只有 PHY 在半双工模式下 有效。(感觉没啥用,可能是我没用上的缘故) |
COL | 1 | 冲突检测信号,不需要同步于参考时钟。需要 注意的是 COL 只有 PHY 在半双工模式下有效。(感觉没啥用,可能是我没用上的缘故) |
GMII接口
信号 | 位宽 | 说明 |
TX_ER | 1 | 发送数据错误提示信号,同步于G TX_CLK ,高电平 有效,表示 TX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,TX_ER 不起作用。 |
TX_EN | 1 | 发送使能信号,只有在 TX_EN 有效期内传的数 据才有效。 |
GTX_CLK | 1 | 发送参考时钟,时钟频率为 125MHz 。需要注意的是, GTX_CLK 时钟的方向是从 MAC 侧指向 PHY 侧的,此时钟是由 MAC 提供 的,这里与 MII 接口有所差别的地方。 |
TXD | 4 | 数据发送 |
RX_ER | 1 | 接收数据错误提示信号,同步于 RX_CLK ,高电平 有效,表示 RX_ER 有效期内传输的数据无效。对于 10Mbps 速率 下,RX_ER 不起作用。 |
RX_DV | 1 | 接收数据有效信号,作用类似于发送通道的 TX_EN。 |
RXD | 4 | 数据接收 |
RX_CLK | 1 | 接收数据参考时钟,时钟频率为 125MHz 。 RX_CLK 是由 PHY 侧提 供的。 |
CRS | 1 | 即 Carrier Sense ,载波侦测信号,不需要同步于参考时钟,只要有数 据传输,CRS 就有效。需要注意的是 CRS 只有 PHY 在半双工模式下 有效。(感觉没啥用,可能是我没用上的缘故) |
COL | 1 | 冲突检测信号,不需要同步于参考时钟。需要 注意的是 COL 只有 PHY 在半双工模式下有效。(感觉没啥用,可能是我没用上的缘故) |
RGMII接口
RGMII属于GMII的简化版,数据传输效率也没那么高。
信号 | 位宽 | 说明 |
TX_CLK | 1 | 发送参考时钟, 1000Mbps 速率下,时钟频率为 125MHz , TX_CLK 时钟是由 MAC 提供的 |
TXD | 4 | 在时钟 TX_CLK 的上升沿发送 GMII 接口中的 TXD[3:0] ,在时钟 TX_CLK 的下降沿发送 GMII 接口中的 TXD[7:4] 。 |
TX_CTL | 1 | 该信号线上传送 GMII 接口中的 TX_EN 和 TX_ER 两种信息,在 TX_CLK 的上升沿发送 TX_EN ,下降沿发送 TX_ER 。 |
RX_CTL | 1 | 该信号线上传送 GMII 接口中的 RX_DV 和 RX_ER 两种信息,在 RX_CLK 的上升沿传输 RX_DV ,下降沿传输 RX_ER 。 |
RXD | 4 | 时钟 RX_CLK 的上升沿传输 GMII 接口中的 RXD[3:0] ,在时钟 RX_CLK 的下降沿发送 GMII 接口中的 RXD[7:4] 。 |
RX_CLK | 1 | 接收数据参考时钟, 1000Mbps 速率下,时钟频率为 125MHz 。 RX_CLK 由 PHY 侧提供的。 |
根据以上介绍,我们使用RGMII接口的以太网 PHY 与 MAC的连接实现方法,解决了接口问题,后面学习的是以太网的数据组成。
以太网帧格式
前同步码(前导码):7 个字节的 0x55,用于帧的同步;
SFD(帧开始符):标明下一个字节为目的 MAC 字段;
目的地址:数据接收者;
源地址:数据发送者;
长度/类型:当这两个字节的值小于 1518 时,那么它就代表其后数据字 段的长度;如果这两个字节的值大于 1518,则表示该以太 网帧中的数据属于哪个上层协议(例如 0x800,代表 IP 数 据包;0x806,代表 ARP 数据包等)。
数据和填充:要发送的数据;
FCS(校验):判断是否帧发生错误,错误就丢弃。
想了解具体过程的可以去b站找一下课程看看。
RGMII和GMII转换电路设计
RGMII 是 IEEE802.3z 标准中定义的千兆媒体独立接口( Gigabit Medium Independent Interface ) GMII 的一个替代品。RGMII 数据在时钟的上升沿和下降沿均进行采样。对于 FPGA 来说,实现 RGMII 接口的发送是一个非常直接的过程, 整个发送逻辑框图如图:gmii_to_rgmii
设计实现时,我们需要使用 xilinx 的 ODDR ( Output Double Data Rate ,输出双倍数据速率)原语,将该接口使用 OLOGIC 块实现。 OLOGIC 块在 7 系列 FPGA 内的位置紧挨着 IOB ,其作用是 FPGA 通过 IOB 发送数据到器件外部的专用同步块。在 OLOGIC 块中,有着专用的 寄存器,用于实现输出 DDR 寄存器,当我们实例化 ODDR 原语时便会自动访问该功能。ODDR 原语只有一个时钟输入,下降沿数据由输入时钟的本地反转来计时,反馈到 I/O 块的所有的时钟被完全复用,ODDR 原语的框图如图。 各端口功能:Port Name | Function | Description |
Q | 数据输出 | ODDR寄存器输出 |
C | 时钟输入端口 | 时钟输入引脚 |
CE | 时钟使能端口 | CE 表示时钟使能引脚。当断言为低时,此端口禁用端口Q 上的输出时钟。 |
D1和D2 | 数据输入 | ODDR 寄存器输入 |
S/R | 设置/复位 | 同步 / 异步的设置 / 复位引脚。该引脚高电平有效 |
一开始用的mdy的板子,然后就开始在b站上看着课程学,但他课程里面没有适配于这个我用的板子的代码,然后看小梅哥还有网上现有的以太网代码的时候,就看到这个,在哪抓耳挠腮的像这个是啥啊,也没交过啊,然后后来看到小梅哥的课程时候才知道这是什么,然后小梅哥也交了该怎么用,真不戳啊(感觉小梅哥售后群回答问题挺快,相比之前前者就是不咋回,问售后的问题两天也没回,很气)
言归正传,说实话我还没用好这个,可以专门找原语的内容看一看。下面是gmii_to_rgmii的代码,这个代码不是我自己写的,我也是学习,大家一起学哈。对于 RGMII 发送,只需要例化 6 个 ODDR 就可以实现,其中 4 个用来发送 4 位的 TXD 信号; 一个用来发送 TXEN 和 TXER 信号。另一个则用来输出 TX_CLK 信号。
module gmii_to_rgmii(
reset_n,
gmii_tx_clk,
gmii_txd,
gmii_txen,
gmii_txer,
rgmii_tx_clk,
rgmii_txd,
rgmii_txen
);
input reset_n;
input gmii_tx_clk;
input [7:0] gmii_txd;
input gmii_txen;
input gmii_txer;
output rgmii_tx_clk;
output [3:0] rgmii_txd;
output rgmii_txen;
genvar i;
generate
for(i=0;i<4;i=i+1)
begin: rgmii_txd_o
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT (1'b0 ), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_rgmii_txd (
.Q (rgmii_txd[i] ), // 1-bit DDR output
.C (gmii_tx_clk ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (gmii_txd[i] ), // 1-bit data input (positive edge)
.D2 (gmii_txd[i+4] ), // 1-bit data input (negative edge)
.R (~reset_n ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
end
endgenerate
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT (1'b0 ), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_rgmii_txd (
.Q (rgmii_txen ), // 1-bit DDR output
.C (gmii_tx_clk ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (gmii_txen ), // 1-bit data input (positive edge)
.D2 (gmii_txen^gmii_txer ), // 1-bit data input (negative edge)
.R (~reset_n ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT (1'b0 ), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_rgmii_clk (
.Q (rgmii_tx_clk ), // 1-bit DDR output
.C (gmii_tx_clk ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (1'b1 ), // 1-bit data input (positive edge)
.D2 (1'b0 ), // 1-bit data input (negative edge)
.R (~reset_n ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
endmodule
对于
rgmii_tx_clk 的输出也是使用
ODDR
输出的,而非使用
PLL
产生并输出。
以下是课程教程里给出的解释:“使用 ODDR 输出 rgmii_tx_clk 信号的最大优势在于,可以最大程度上保证输出的rgmii_tx_clk 与 rgmii_txd[3:0]保持边沿对齐关系。因为从实现原理上来讲,rgmii_tx_clk 和rgmii_txd[3:0]都是 ODDR 在 gmii_tx_clk 的驱动下输出其高位和低位的数据,这样能够保证两者的传输路径的时序模型完全一致,从而使得 rgmii_tx_clk 和 rgmii_txd[3:0]都能在同一时刻发生变化, 实现更加可靠的时钟和数据边沿对齐特性。只是这种情况下,在 PHY 侧,要么通过设置,在 PHY 内部对 rgmii_tx_clk 加入时间延迟,要么通过控制 PCB 走线来为rgmii_tx_clk 增加一定时间的延迟。如果对于某些设计时时序没有严格控制好的平台板卡,在 FPGA 侧使用边沿对齐和中心都无法保证数据和时钟到达 PHY 时恰好呈中心对齐或边沿对齐模式,则需要使用 PLL 直接输出 rgmii_tx_clk,并通过实验调整其与 FPGA 内部使用的gmii_tx_clk 的相位来最终达到稳定输出数据的目的。在我们设计的开发板上,一般都是通过设置 PHY 内部对 rgmii_tx_clk 加入时间延迟来实现的。 无需使用 PLL 来调整发送时钟rgmii_tx_clk 的相位。
”
rgmii_to_gmii
实现 RGMII 接口的接收是一个非常直接的过程, 整个发送逻辑框图如图:
也涉及IDDR的原语,可取b站看一看这部分,这部分代码跟前一部分类似吧。对于 RGMII 发送,只需要例化 5 个 IDDR 就可以实现,其中 4 个用来接收 4 位的 RXD 信号;一个用来接收 rxdv 信号。这也有部分解释“对于 RGMII 接口来说,rgmii_rx_clk 时钟是由 PHY 产生,提供给 FPGA 的。 而该时钟 在到达 FPGA 内部接收 rgmii_rxd 和 rgmii_rxdv 的 IDDR 时需要与 rgmii_rxd 和 rgmii_rxdv 信号 呈中心对齐的关系。而 rgmii_rxdv 时钟不仅需要作为 IDDR 的接口时钟,在 FPGA 内部还会作 为以太网数据的解包逻辑时钟,是一个典型的时钟信号。所以如果直接使用普通的 FPGA IO 接入该时钟,势必会导致时钟质量下降,从而引起逻辑运行的不稳定。而且,一旦该时钟信 号和 rgmii_rxd 不满足中心对齐的关系,要调整会非常的麻烦。所以,综合考虑,将该时钟 信号接入了 FPGA 的专用时钟管脚,使其能够直接进入 FPGA 内部的 PLL,也能够直接上 FPGA 的全局时钟资源。在保证时钟质量的同时,还能确保万一该时钟相位无法满足 rgmii_rxd 的 捕获要求,还能通过 FPGA 内部的 PLL 进行相位调制,以确保最终 rgmii_rxd 能够被正确接 收。 所以在上述框图中,rgmii_rx_clk 接入 FPGA 后并没有直接接到 IDDR 的时钟脚,而是先 进入了 PLL,由 PLL 调整到合适的相位之后,再用来捕获 rgmii_rxd"。
module rgmii_to_gmii(
reset_n,
gmii_rx_clk,
gmii_rxdv,
gmii_rxd,
gmii_rxerr,
rgmii_rx_clk,
rgmii_rxd,
rgmii_rxdv
);
input reset_n;
output gmii_rx_clk;
output [7:0] gmii_rxd;
output gmii_rxdv;
output gmii_rxerr;
input rgmii_rx_clk;
input [3:0] rgmii_rxd;
input rgmii_rxdv;
wire gmii_rxer;
assign gmii_rx_clk = rgmii_rx_clk;
assign gmii_rxerr = gmii_rxer^gmii_rxdv ;
genvar i;
generate
for(i=0;i<4;i=i+1)
begin: rgmii_rxd_i
IDDR #(
// "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),
.INIT_Q1(1'b0 ), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0 ), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE ("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rxd (
.Q1(gmii_rxd[i]),//1-bit output for positive edge of clock
.Q2(gmii_rxd[i+4]),//1-bit output for negative edge of clock
.C (rgmii_rx_clk ), // 1-bit clock input
.CE (1'b1 ), // 1-breset_nit clock enable input
.D (rgmii_rxd[i] ), // 1-bit DDR data input
.R (!reset_n ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
end
endgenerate
IDDR #(
// "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),
.INIT_Q1(1'b0 ), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0 ), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE ("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rxdv (
.Q1(gmii_rxdv), // 1-bit output for positive edge of clock
.Q2(gmii_rxer), // 1-bit output for negative edge of clock
.C (rgmii_rx_clk ), // 1-bit clock input
.CE (1'b1 ), // 1-breset_nit clock enable input
.D (rgmii_rxdv ), // 1-bit DDR data input
.R (!reset_n ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
endmodule
接口部分的设计代码就是这些,看完这些代码,觉得这两部分是利用IDDR和ODDR模块的性质去例化然后使用完成功能的,不知道这么理解对不对,有懂得知道一下么。
网络层 ARP 协议
在网络通讯时,源主机的应用程序知道目的主机的 IP 地址和端口号,却不知道目的主 机的硬件(MAC) 地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数 据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP 协议就起到这个作用。 每台主机都维护一个 ARP 缓存表,可以用 arp -a 命令查看。由于网络上的 IP 地址本身是动态分配的(Ipv4 资源严重不足,所以采用动态分配机制),上网时每个设备的 IP 地址在不同的时间点其地址都有可能不同,所以某个网络设备在不同的时间,其 IP 地址可能不一样,所以需要用这种缓存表项定期失效的机制来避免 MAC 地址和不同时刻 IP 地址绑定的冲突。完整的 ARP 帧每个字段的数据意义可去百度一下哦。引入crc校验机制
数据信号在传输过程中不可避免地会受到噪声干扰 ,或者信道不理想 ,从而造成的码间干扰而产 生差错 ,即出现误码。 我们通常是通过一个校验码去判断接收的消息是否出现差错。信息 在发送时,发送端会通过某个算法得到一个值,这个值被称为校验码。 当数据报中任何一个字节的内容更改之后,CRC 的值都需要重新计算,这在实际应用中是不现实的。所以引入自动crc校验,直接用在线生成 CRC32 的 Verilog 代码的网站: https://www.easics.com/webtools/crctool但我登不上,等我登上再将这部分补全是怎么用的。 教程代码如下module crc32_d8
(
input clk ,
input reset_p ,
input [7:0] data ,
input crc_init ,
input crc_en ,
output [31:0] crc_result
);
wire [7:0] data_i;
reg [31:0] crc_result_o;
assign data_i = {data[ 0],data[ 1],data[ 2],data[ 3], data[ 4],data[ 5],data[ 6],data[ 7]};
assign crc_result = ~{crc_result_o[00],crc_result_o[01],crc_result_o[02],crc_result_o[03],crc_result_o[04],crc_result_o[05],crc_result_o[06],crc_result_o[07],
crc_result_o[08],crc_result_o[09],crc_result_o[10],crc_result_o[11],crc_result_o[12],crc_result_o[13],crc_result_o[14],crc_result_o[15],
crc_result_o[16],crc_result_o[17],crc_result_o[18],crc_result_o[19],crc_result_o[20],crc_result_o[21],crc_result_o[22],crc_result_o[23],
crc_result_o[24],crc_result_o[25],crc_result_o[26],crc_result_o[27],crc_result_o[28],crc_result_o[29],crc_result_o[30],crc_result_o[31]};
always @(posedge clk or posedge reset_p)
begin
if(reset_p)
crc_result_o <= 32'hffff_ffff;
else if(crc_init)
crc_result_o <= 32'hffff_ffff;
else if(crc_en)
crc_result_o <= nextCRC32_D8( data_i, crc_result_o);
else
crc_result_o <= crc_result_o;
end
// polynomial: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
// data width: 8
// convention: the first serial bit is D[7]
function [31:0] nextCRC32_D8;
input [7:0] Data;
input [31:0] crc;
reg [7:0] d;
reg [31:0] c;
reg [31:0] newcrc;
begin
d = Data;
c = crc;
newcrc[0] = d[6] ^ d[0] ^ c[24] ^ c[30];
newcrc[1] = d[7] ^ d[6] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[30] ^ c[31];
newcrc[2] = d[7] ^ d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[26] ^ c[30] ^ c[31];
newcrc[3] = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[27] ^ c[31];
newcrc[4] = d[6] ^ d[4] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[28] ^ c[30];
newcrc[5] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
newcrc[6] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30] ^ c[31];
newcrc[7] = d[7] ^ d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[24] ^ c[26] ^ c[27] ^ c[29] ^ c[31];
newcrc[8] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[0] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
newcrc[9] = d[5] ^ d[4] ^ d[2] ^ d[1] ^ c[1] ^ c[25] ^ c[26] ^ c[28] ^ c[29];
newcrc[10] = d[5] ^ d[3] ^ d[2] ^ d[0] ^ c[2] ^ c[24] ^ c[26] ^ c[27] ^ c[29];
newcrc[11] = d[4] ^ d[3] ^ d[1] ^ d[0] ^ c[3] ^ c[24] ^ c[25] ^ c[27] ^ c[28];
newcrc[12] = d[6] ^ d[5] ^ d[4] ^ d[2] ^ d[1] ^ d[0] ^ c[4] ^ c[24] ^ c[25] ^ c[26] ^ c[28] ^ c[29] ^ c[30];
newcrc[13] = d[7] ^ d[6] ^ d[5] ^ d[3] ^ d[2] ^ d[1] ^ c[5] ^ c[25] ^ c[26] ^ c[27] ^ c[29] ^ c[30] ^ c[31];
newcrc[14] = d[7] ^ d[6] ^ d[4] ^ d[3] ^ d[2] ^ c[6] ^ c[26] ^ c[27] ^ c[28] ^ c[30] ^ c[31];
newcrc[15] = d[7] ^ d[5] ^ d[4] ^ d[3] ^ c[7] ^ c[27] ^ c[28] ^ c[29] ^ c[31];
newcrc[16] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[24] ^ c[28] ^ c[29];
newcrc[17] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[25] ^ c[29] ^ c[30];
newcrc[18] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[26] ^ c[30] ^ c[31];
newcrc[19] = d[7] ^ d[3] ^ c[11] ^ c[27] ^ c[31];
newcrc[20] = d[4] ^ c[12] ^ c[28];
newcrc[21] = d[5] ^ c[13] ^ c[29];
newcrc[22] = d[0] ^ c[14] ^ c[24];
newcrc[23] = d[6] ^ d[1] ^ d[0] ^ c[15] ^ c[24] ^ c[25] ^ c[30];
newcrc[24] = d[7] ^ d[2] ^ d[1] ^ c[16] ^ c[25] ^ c[26] ^ c[31];
newcrc[25] = d[3] ^ d[2] ^ c[17] ^ c[26] ^ c[27];
newcrc[26] = d[6] ^ d[4] ^ d[3] ^ d[0] ^ c[18] ^ c[24] ^ c[27] ^ c[28] ^ c[30];
newcrc[27] = d[7] ^ d[5] ^ d[4] ^ d[1] ^ c[19] ^ c[25] ^ c[28] ^ c[29] ^ c[31];
newcrc[28] = d[6] ^ d[5] ^ d[2] ^ c[20] ^ c[26] ^ c[29] ^ c[30];
newcrc[29] = d[7] ^ d[6] ^ d[3] ^ c[21] ^ c[27] ^ c[30] ^ c[31];
newcrc[30] = d[7] ^ d[4] ^ c[22] ^ c[28] ^ c[31];
newcrc[31] = d[5] ^ c[23] ^ c[29];
nextCRC32_D8 = newcrc;
end
endfunction
endmodule
生成的代码里面仅为一个计算
CRC32
的一个函数
function
。
CRC
的计算与除了与生成多
项式相关外,还与
CRC
的初始值,输入输出数据是否位反向以及计算结果是否取反等操作
相关。
IP校验
IP 是 TCP/IP 协议族中最核心的协议,所有的 TCP 、 UDP 、 ICMP 、 IGMP 数据都以 IP 数据 报的格式传输。IP 数据报的格式如下,IP 数据报的长度/类型段的数值为 0x0800,数据和填充段包括IP 头部数据和 IP 数据两个部分。其中,和以太网帧具有帧头一样, IP 数据报也包含了一个 IP 报头部分,与 IP 协议相 关的一些信息如 IP 地址,数据包长度等会被打包进 IP 报头中,然后再与需要传输的 IP 报 文数据一起,作为 MAC 帧的数据和填充字段送往 MAC 层发送 。感兴趣的可以去了解了解具体过程,代码如下:
module ip_checksum(
input clk ,
input reset_p ,
input cal_en ,
input [3:0] IP_ver ,
input [3:0] IP_hdr_len ,
input [7:0] IP_tos ,
input [15:0] IP_total_len ,
input [15:0] IP_id ,
input IP_rsv ,
input IP_df ,
input IP_mf ,
input [12:0] IP_frag_offset ,
input [7:0] IP_ttl ,
input [7:0] IP_protocol ,
input [31:0] src_ip ,
input [31:0] dst_ip ,
output [15:0] checksum
);
reg [31:0]suma;
wire [16:0]sumb;
wire [15:0]sumc;
always@(posedge clk or posedge reset_p)
if(reset_p)
suma <= 32'd0;
else if(cal_en)
suma <= {IP_ver,IP_hdr_len,IP_tos}+IP_total_len+IP_id+
{IP_rsv,IP_df,IP_mf,IP_frag_offset}+{IP_ttl,IP_protocol}+
src_ip[31:16]+src_ip[15:0]+dst_ip[31:16]+dst_ip[15:0];
else
suma <= suma;
assign sumb = suma[31:16]+suma[15:0];
assign sumc = sumb[16]+sumb[15:0];
assign checksum = ~sumc;
endmodule
UDP协议及FPGA实现
UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种 无连接的协议,与所熟知的 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的 顶层。每一个数据包的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。从包含关系来说:MAC 帧中的数据段为 IP 数据报,IP 报文中的数据段为 UDP报文,UDP 报文中的数据段为用户希望传输的数据内容, 发送部分://这个代码是从整个代码上拆下来的,我尽量注释一下,有挺多不会,请大佬帮个忙
module eth_udp_tx_gmii(
clk125m,
reset_p,
tx_en_pulse,
tx_done,
dst_mac,
src_mac,
dst_ip,
src_ip,
dst_port,
src_port,
data_length,
payload_req_o,
payload_dat_i,
gmii_tx_clk,
gmii_txen,
gmii_txd
);
input clk125m;
input reset_p;
input tx_en_pulse;
output reg tx_done;
input [47:0] dst_mac;
input [47:0] src_mac;
input [31:0] dst_ip;
input [31:0] src_ip;
input [15:0] dst_port;
input [15:0] src_port;
input [15:0] data_length;
output payload_req_o;
input [7:0]payload_dat_i;
output gmii_tx_clk;
// (*IOB = "TRUE"*) output reg[7:0] gmii_txd;
// (*IOB = "TRUE"*) output reg gmii_txen;
output reg[7:0] gmii_txd;
output reg gmii_txen;
parameter ETH_type = 16'h0800,
IP_ver = 4'h4,
IP_hdr_len = 4'h5,
IP_tos = 8'h00,
IP_id = 16'h0000,
IP_rsv = 1'b0,
IP_df = 1'b0,
IP_mf = 1'b0,
IP_frag_offset = 13'h0000,
IP_ttl = 8'h40,
IP_protocol = 8'h11;
localparam
IDLE = 8'b00000001,
TX_PREAMBLE = 8'b00000010,
TX_ETH_HEADER = 8'b00000100,
TX_IP_HEADER = 8'b00001000,
TX_UDP_HEADER = 8'b00010000,
TX_DATA = 8'b00100000,
TX_FILL_DATA = 8'b01000000,
TX_CRC = 8'b10000000;
wire [15:0] IP_total_len;
wire [15:0] IP_check_sum;
wire [15:0] udp_length;
wire [15:0] udp_check_sum = 16'h0000;
reg [47:0] dst_mac_reg;
reg [47:0] src_mac_reg;
reg [15:0] dst_port_reg;
reg [15:0] src_port_reg;
reg [31:0] dst_ip_reg;
reg [31:0] src_ip_reg;
reg [15:0] data_length_reg;
reg [15:0] IP_total_len_reg;
reg [15:0] udp_length_reg;
reg [3:0] cnt_preamble;
reg [3:0] cnt_eth_header;
reg [4:0] cnt_ip_header;
reg [3:0] cnt_udp_header;
reg [15:0] cnt_data;
reg [4:0] cnt_fill_data;
reg [1:0] cnt_crc;
reg [7:0] tx_data;
reg tx_en;
reg [7:0] curr_state;
reg [7:0] next_state;
wire crc_state;
reg crc_state_dly1;
reg crc_state_dly2;
reg [1:0] cnt_crc_dly1;
reg [1:0] cnt_crc_dly2;
reg tx_en_dly1;
reg [7:0] tx_data_dly1;
reg crc_init;
reg crc_en_temp;
reg crc_en;
reg [7:0] crc_in;
wire [31:0] crc_result;
reg [7:0] gmii_txd_renewcrc;
reg gmii_txen_renewcrc;
assign gmii_tx_clk = clk125m;//这个是顶层有一个pll分频出来的125m的时钟,因为这个接口是需要125
assign udp_length = data_length + 8'd8; //udp_header: 8byte
assign IP_total_len = udp_length + 8'd20; //ip_header: 20byte
//校验IP的,说实话,我没弄太明白校验的代码
ip_checksum ip_checksum(
.clk (clk125m ),
.reset_p (reset_p ),
.cal_en (tx_en_pulse ),
.IP_ver (IP_ver ),
.IP_hdr_len (IP_hdr_len ),
.IP_tos (IP_tos ),
.IP_total_len (IP_total_len ),
.IP_id (IP_id ),
.IP_rsv (IP_rsv ),
.IP_df (IP_df ),
.IP_mf (IP_mf ),
.IP_frag_offset (IP_frag_offset ),
.IP_ttl (IP_ttl ),
.IP_protocol (IP_protocol ),
.src_ip (src_ip ),
.dst_ip (dst_ip ),
.checksum (IP_check_sum )
);
//将以太网报文源/目的参数寄存,防止跳变
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
dst_mac_reg <= 48'd0;
src_mac_reg <= 48'd0;
dst_ip_reg <= 32'd0;
src_ip_reg <= 32'd0;
dst_port_reg <= 16'd0;
src_port_reg <= 16'd0;
end
else if(tx_en_pulse)
begin
dst_mac_reg <= dst_mac;
src_mac_reg <= src_mac;
dst_port_reg <= dst_port;
src_port_reg <= src_port;
dst_ip_reg <= dst_ip;
src_ip_reg <= src_ip;
end
//将以太网报文数据部分长度参数寄存
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
data_length_reg <= 16'h0;
IP_total_len_reg <= 16'h0;
udp_length_reg <= 16'h0;
end
else if(tx_en_pulse)
begin
data_length_reg <= data_length;
IP_total_len_reg <= IP_total_len;
udp_length_reg <= udp_length;
end
//发送的状态机准备阶段
always@(posedge clk125m or posedge reset_p)
if(reset_p)//这块的条件是这个原因是在整个的顶层文件中定义了reset_p = ~reset_n所以才这么写。
curr_state <= IDLE;//这个curr_state是为了后面的组合逻辑状态机。
else
curr_state <= next_state;
always@(*)
begin
case(curr_state)
IDLE:
if(tx_en_pulse)
next_state = TX_PREAMBLE;
else
next_state = IDLE;
TX_PREAMBLE:
if(cnt_preamble == 4'd7)
next_state = TX_ETH_HEADER;
else
next_state = TX_PREAMBLE;
TX_ETH_HEADER:
if(cnt_eth_header == 4'd13)
next_state = TX_IP_HEADER;
else
next_state = TX_ETH_HEADER;
TX_IP_HEADER:
if(cnt_ip_header == 5'd19)
next_state = TX_UDP_HEADER;
else
next_state = TX_IP_HEADER;
TX_UDP_HEADER:
if(cnt_udp_header == 4'd7)
next_state = TX_DATA;
else
next_state = TX_UDP_HEADER;
TX_DATA:
if(data_length_reg <5'd18 && cnt_data == data_length_reg - 1'b1)
next_state = TX_FILL_DATA;
else if(cnt_data == data_length_reg - 1'b1)
next_state = TX_CRC;
else
next_state = TX_DATA;
TX_FILL_DATA:
if(cnt_fill_data == 5'd17 - data_length_reg)
next_state = TX_CRC;
else
next_state = TX_FILL_DATA;
TX_CRC:
if(cnt_crc == 2'd3)
next_state = IDLE;
else
next_state = TX_CRC;
default:next_state = IDLE;
endcase
end
//计数器需要达到对应部位才能跳转
//cnt_preamble
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_preamble <= 4'd0;
else if(curr_state == TX_PREAMBLE)
cnt_preamble <= cnt_preamble + 1'b1;
else
cnt_preamble <= 4'd0;
//cnt_eth_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_eth_header <= 4'd0;
else if(curr_state == TX_ETH_HEADER)
cnt_eth_header <= cnt_eth_header + 1'b1;
else
cnt_eth_header <= 4'd0;
//cnt_ip_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_ip_header <= 5'd0;
else if(curr_state == TX_IP_HEADER)
cnt_ip_header <= cnt_ip_header + 1'b1;
else
cnt_ip_header <= 5'd0;
//cnt_udp_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_udp_header <= 4'd0;
else if(curr_state == TX_UDP_HEADER)
cnt_udp_header <= cnt_udp_header + 1'b1;
else
cnt_udp_header <= 4'd0;
//cnt_data
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_data <= 16'd0;
else if(curr_state == TX_DATA)
cnt_data <= cnt_data + 1'b1;
else
cnt_data <= 16'd0;
//cnt_fill_data
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_fill_data <= 5'd0;
else if(curr_state == TX_FILL_DATA)
cnt_fill_data <= cnt_fill_data + 1'b1;
else
cnt_fill_data <= 5'd0;
//cnt_crc
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_crc <= 5'd0;
else if(curr_state == TX_CRC)
cnt_crc <= cnt_crc + 1'b1;
else
cnt_crc <= 5'd0;
//tx_data,tx_en
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
tx_en <= 1'b0;
tx_data <= 8'h00;
end
else
begin
case(curr_state)
IDLE:
begin
tx_en <= 1'b0;
tx_data <= 8'h00;
end
TX_PREAMBLE:
begin
tx_en <= 1'b1;
if(cnt_preamble <= 4'd6)
tx_data <= 8'h55;
else
tx_data <= 8'hd5;
end
TX_ETH_HEADER:
begin
tx_en <= 1'b1;
case(cnt_eth_header)
4'd0: tx_data <= dst_mac_reg[47:40];
4'd1: tx_data <= dst_mac_reg[39:32];
4'd2: tx_data <= dst_mac_reg[31:24];
4'd3: tx_data <= dst_mac_reg[23:16];
4'd4: tx_data <= dst_mac_reg[15:8];
4'd5: tx_data <= dst_mac_reg[7:0];
4'd6: tx_data <= src_mac_reg[47:40];
4'd7: tx_data <= src_mac_reg[39:32];
4'd8: tx_data <= src_mac_reg[31:24];
4'd9: tx_data <= src_mac_reg[23:16];
4'd10: tx_data <= src_mac_reg[15:8];
4'd11: tx_data <= src_mac_reg[7:0];
4'd12: tx_data <= ETH_type[15:8];
4'd13: tx_data <= ETH_type[7:0];
default:tx_data <= 8'h00;
endcase
end
TX_IP_HEADER:
begin
tx_en <= 1'b1;
case(cnt_ip_header)
5'd0: tx_data <= {IP_ver,IP_hdr_len};
5'd1: tx_data <= IP_tos;
5'd2: tx_data <= IP_total_len_reg[15:8];
5'd3: tx_data <= IP_total_len_reg[7:0];
5'd4: tx_data <= IP_id[15:8];
5'd5: tx_data <= IP_id[7:0];
5'd6: tx_data <= {IP_rsv,IP_df,IP_mf,IP_frag_offset[12:8]};
5'd7: tx_data <= IP_frag_offset[7:0];
5'd8: tx_data <= IP_ttl;
5'd9: tx_data <= IP_protocol;
5'd10: tx_data <= IP_check_sum[15:8];
5'd11: tx_data <= IP_check_sum[7:0];
5'd12: tx_data <= src_ip_reg[31:24];
5'd13: tx_data <= src_ip_reg[23:16];
5'd14: tx_data <= src_ip_reg[15:8];
5'd15: tx_data <= src_ip_reg[7:0];
5'd16: tx_data <= dst_ip_reg[31:24];
5'd17: tx_data <= dst_ip_reg[23:16];
5'd18: tx_data <= dst_ip_reg[15:8];
5'd19: tx_data <= dst_ip_reg[7:0];
default:tx_data <= 8'h00;
endcase
end
TX_UDP_HEADER:
begin
tx_en <= 1'b1;
case(cnt_udp_header)
4'd0: tx_data <= src_port_reg[15:8];
4'd1: tx_data <= src_port_reg[7:0];
4'd2: tx_data <= dst_port_reg[15:8];
4'd3: tx_data <= dst_port_reg[7:0];
4'd4: tx_data <= udp_length_reg[15:8];
4'd5: tx_data <= udp_length_reg[7:0];
4'd6: tx_data <= udp_check_sum[15:8];
4'd7: tx_data <= udp_check_sum[7:0];
default:tx_data <= 8'h00;
endcase
end
TX_DATA:
begin
tx_en <= 1'b1;
tx_data <= payload_dat_i;
end
TX_FILL_DATA:
begin
tx_en <= 1'b1;
tx_data <= 8'h00;
end
TX_CRC:
begin
tx_en <= 1'b1;
tx_data <= 8'h00;
end
default:
begin
tx_en <= 1'b0;
tx_data <= 8'h00;
end
endcase
end
//payload_req_o
assign payload_req_o = (curr_state == TX_DATA) ? 1'b1 : 1'b0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_init <= 1'b0;
else if (tx_en_pulse && (curr_state == IDLE))
crc_init <= 1'b1;
else
crc_init <= 1'b0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_en_temp <= 1'b0;
else if (curr_state == TX_ETH_HEADER || curr_state == TX_IP_HEADER ||
curr_state == TX_UDP_HEADER || curr_state == TX_FILL_DATA || curr_state == TX_DATA)
crc_en_temp <= 1'b1;
else
crc_en_temp <= 1'b0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_en <= 1'b0;
else if (crc_en_temp)
crc_en <= 1'b1;
else
crc_en <= 1'b0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_in <= 8'h00;
else if(crc_en_temp)
crc_in <= tx_data;
else
crc_in <= crc_in;
crc32_d8 crc32_d8
(
.clk (clk125m ),
.reset_p (reset_p ),
.data (crc_in ),
.crc_init (crc_init ),
.crc_en (crc_en ),
.crc_result (crc_result )//latency=1
);
assign crc_state = curr_state == TX_CRC;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
tx_en_dly1 <= 1'b0;
tx_data_dly1 <= 8'h00;
end
else
begin
tx_en_dly1 <= tx_en;
tx_data_dly1 <= tx_data;
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
crc_state_dly1 <= 1'b0;
crc_state_dly2 <= 1'b0;
cnt_crc_dly1 <= 2'd0;
cnt_crc_dly2 <= 2'd0;
end
else
begin
crc_state_dly1 <= crc_state;
crc_state_dly2 <= crc_state_dly1;
cnt_crc_dly1 <= cnt_crc;
cnt_crc_dly2 <= cnt_crc_dly1;
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
gmii_txd_renewcrc <= 8'h00;
else if(crc_state_dly2)
begin
case(cnt_crc_dly2)
2'd0:gmii_txd_renewcrc <= crc_result[7:0];
2'd1:gmii_txd_renewcrc <= crc_result[15:8];
2'd2:gmii_txd_renewcrc <= crc_result[23:16];
2'd3:gmii_txd_renewcrc <= crc_result[31:24];
endcase
end
else
gmii_txd_renewcrc <= tx_data_dly1;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
gmii_txen_renewcrc <= 1'b0;
else if(tx_en_dly1)
gmii_txen_renewcrc <= 1'b1;
else
gmii_txen_renewcrc <= 1'b0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
gmii_txen <= 1'b0;
gmii_txd <= 8'h00;
end
else
begin
gmii_txen <= gmii_txen_renewcrc;
gmii_txd <= gmii_txd_renewcrc;
end
//tx_done
always@(posedge clk125m or posedge reset_p)
if(reset_p)
tx_done <= 1'b0;
else if(curr_state == TX_CRC && cnt_crc == 2'd3)
tx_done <= 1'b1;
else
tx_done <= 1'b0;
endmodule
接收部分:
module eth_udp_rx_gmii(
reset_p,
local_mac,
local_ip,
local_port,
clk125m_o,
exter_mac,
exter_ip,
exter_port,
rx_data_length,
data_overflow_i,
payload_valid_o,
payload_dat_o,
one_pkt_done,
pkt_error,
debug_crc_check,
gmii_rx_clk,
gmii_rxdv,
gmii_rxd
);
input reset_p;
input [47:0] local_mac;
input [31:0] local_ip;
input [15:0] local_port;
output clk125m_o;
output reg[47:0] exter_mac;
output reg[31:0] exter_ip;
output reg[15:0] exter_port;
output reg[15:0] rx_data_length;
input data_overflow_i;
output reg payload_valid_o;
output reg[7:0] payload_dat_o;
output reg one_pkt_done;
output reg pkt_error;
output [31:0] debug_crc_check;
input gmii_rx_clk;
(*IOB = "TRUE"*) input [7:0] gmii_rxd;
(*IOB = "TRUE"*) input gmii_rxdv;
parameter ETH_type = 16'h0800,
IP_ver = 4'h4,
IP_hdr_len = 4'h5,
IP_protocol = 8'h11;
localparam
IDLE = 9'b000000001,
RX_PREAMBLE = 9'b000000010,
RX_ETH_HEADER = 9'b000000100,
RX_IP_HEADER = 9'b000001000,
RX_UDP_HEADER = 9'b000010000,
RX_DATA = 9'b000100000,
RX_DRP_DATA = 9'b001000000,
RX_CRC = 9'b010000000,
PKT_CHECK = 9'b100000000;
wire clk125m;
reg [7:0] reg_gmii_rxd;
reg reg_gmii_rxdv;
reg [7:0] rx_data_dly1;
reg [7:0] rx_data_dly2;
reg rx_datav_dly1;
reg rx_datav_dly2;
reg [47:0] local_mac_reg;
reg [31:0] local_ip_reg;
reg [15:0] local_port_reg;
reg [8:0] curr_state;
reg [8:0] next_state;
reg reg_data_overflow;
reg [47:0] rx_dst_mac;
reg [47:0] rx_src_mac;
reg [15:0] rx_eth_type;
reg eth_header_check_ok;
reg [3:0] rx_ip_ver;
reg [3:0] rx_ip_hdr_len;
reg [7:0] rx_ip_tos;
reg [15:0] rx_total_len;
reg [15:0] rx_ip_id;
reg rx_ip_rsv;
reg rx_ip_df;
reg rx_ip_mf;
reg [12:0] rx_ip_frag_offset;
reg [7:0] rx_ip_ttl;
reg [7:0] rx_ip_protocol;
reg [15:0] rx_ip_check_sum;
reg [31:0] rx_src_ip;
reg [31:0] rx_dst_ip;
reg ip_checksum_cal_en;
wire [15:0] cal_check_sum;
reg ip_header_check_ok;
reg [15:0] rx_src_port;
reg [15:0] rx_dst_port;
reg [15:0] rx_udp_length;
reg udp_header_check_ok;
reg crc_init;
reg crc_en;
reg [7:0] crc_data;
wire [31:0] crc_check;
reg [3:0] cnt_preamble;
reg [3:0] cnt_eth_header;
reg [4:0] cnt_ip_header;
reg [3:0] cnt_udp_header;
reg [15:0] cnt_data;
reg [4:0] cnt_drp_data;
assign clk125m_o = clk125m;
assign debug_crc_check = crc_check;
BUFG BUFG_pclk (
.O(clk125m ), // 1-bit output: Clock output
.I(gmii_rx_clk ) // 1-bit input: Clock input
);
//将本地MAC、IP、PORT寄存
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
local_mac_reg <= 48'h00_00_00_00_00_00;
local_ip_reg <= 32'h00_00_00_00;
local_port_reg <= 16'h00_00;
end
else if(curr_state == IDLE)
begin
local_mac_reg <= local_mac;
local_ip_reg <= local_ip;
local_port_reg <= local_port;
end
//将以太网输入的接收信号寄存
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
reg_gmii_rxd <= 8'h00;
reg_gmii_rxdv <= 1'b0;
end
else
begin
reg_gmii_rxd <= gmii_rxd;
reg_gmii_rxdv <= gmii_rxdv;
end
//将以太网输入的接收信号寄存后打拍
always@(posedge clk125m)
begin
rx_data_dly1 <= reg_gmii_rxd;
rx_data_dly2 <= rx_data_dly1;
rx_datav_dly1 <= reg_gmii_rxdv;
rx_datav_dly2 <= rx_datav_dly1;
end
//cnt_preamble
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_preamble <= 4'd0;
else if(curr_state == RX_PREAMBLE && rx_data_dly2 == 8'h55)
cnt_preamble <= cnt_preamble + 1'b1;
else
cnt_preamble <= 4'd0;
//cnt_eth_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_eth_header <= 4'd0;
else if(curr_state == RX_ETH_HEADER)
cnt_eth_header <= cnt_eth_header + 1'b1;
else
cnt_eth_header <= 4'd0;
//eth_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
rx_dst_mac <= 48'h00_00_00_00_00_00;
rx_src_mac <= 48'h00_00_00_00_00_00;
rx_eth_type <= 16'h0000;
end
else if(curr_state == RX_ETH_HEADER)
begin
case(cnt_eth_header)
4'd0 :rx_dst_mac[47:40] <= rx_data_dly2;
4'd1 :rx_dst_mac[39:32] <= rx_data_dly2;
4'd2 :rx_dst_mac[31:24] <= rx_data_dly2;
4'd3 :rx_dst_mac[23:16] <= rx_data_dly2;
4'd4 :rx_dst_mac[15:8] <= rx_data_dly2;
4'd5 :rx_dst_mac[7:0] <= rx_data_dly2;
4'd6 :rx_src_mac[47:40] <= rx_data_dly2;
4'd7 :rx_src_mac[39:32] <= rx_data_dly2;
4'd8 :rx_src_mac[31:24] <= rx_data_dly2;
4'd9 :rx_src_mac[23:16] <= rx_data_dly2;
4'd10:rx_src_mac[15:8] <= rx_data_dly2;
4'd11:rx_src_mac[7:0] <= rx_data_dly2;
4'd12:rx_eth_type[15:8] <= rx_data_dly2;
4'd13:rx_eth_type[7:0] <= rx_data_dly2;
default: ;
endcase
end
else
begin
rx_dst_mac <= rx_dst_mac;
rx_src_mac <= rx_src_mac;
rx_eth_type <= rx_eth_type;
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
eth_header_check_ok <= 1'b0;
else if(rx_eth_type == ETH_type && (rx_dst_mac == local_mac_reg || rx_dst_mac == 48'hFF_FF_FF_FF_FF_FF))
eth_header_check_ok <= 1'b1;
else
eth_header_check_ok <= 1'b0;
//cnt_ip_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_ip_header <= 5'd0;
else if(curr_state == RX_IP_HEADER)
cnt_ip_header <= cnt_ip_header + 1'b1;
else
cnt_ip_header <= 5'd0;
//ip_header
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
{rx_ip_ver,rx_ip_hdr_len} <= 8'h0;
rx_ip_tos <= 8'h0;
rx_total_len <= 16'h0;
rx_ip_id <= 16'h0;
{rx_ip_rsv,rx_ip_df,rx_ip_mf} <= 3'h0;
rx_ip_frag_offset <= 13'h0;
rx_ip_ttl <= 8'h0;
rx_ip_protocol <= 8'h0;
rx_ip_check_sum <= 16'h0;
rx_src_ip <= 32'h0;
rx_dst_ip <= 32'h0;
end
else if(curr_state == RX_IP_HEADER)
begin
case(cnt_ip_header)
5'd0: {rx_ip_ver,rx_ip_hdr_len} <= rx_data_dly2;
5'd1: rx_ip_tos <= rx_data_dly2;
5'd2: rx_total_len[15:8] <= rx_data_dly2;
5'd3: rx_total_len[7:0] <= rx_data_dly2;
5'd4: rx_ip_id[15:8] <= rx_data_dly2;
5'd5: rx_ip_id[7:0] <= rx_data_dly2;
5'd6: {rx_ip_rsv,rx_ip_df,rx_ip_mf,rx_ip_frag_offset[12:8]} <= rx_data_dly2;
5'd7: rx_ip_frag_offset[7:0] <= rx_data_dly2;
5'd8: rx_ip_ttl <= rx_data_dly2;
5'd9: rx_ip_protocol <= rx_data_dly2;
5'd10: rx_ip_check_sum[15:8] <= rx_data_dly2;
5'd11: rx_ip_check_sum[7:0] <= rx_data_dly2;
5'd12: rx_src_ip[31:24] <= rx_data_dly2;
5'd13: rx_src_ip[23:16] <= rx_data_dly2;
5'd14: rx_src_ip[15:8] <= rx_data_dly2;
5'd15: rx_src_ip[7:0] <= rx_data_dly2;
5'd16: rx_dst_ip[31:24] <= rx_data_dly2;
5'd17: rx_dst_ip[23:16] <= rx_data_dly2;
5'd18: rx_dst_ip[15:8] <= rx_data_dly2;
5'd19: rx_dst_ip[7:0] <= rx_data_dly2;
default: ;
endcase
end
//udp_header: 8byte
//ip_header: 20byte
//rx_data_length = rx_total_len - udp_header - ip_header;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
rx_data_length <= 16'd0;
else if(curr_state == RX_IP_HEADER && cnt_ip_header == 5'd19)
rx_data_length <= rx_total_len - 8'd20 - 8'd8;
else
rx_data_length <= rx_data_length;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
ip_checksum_cal_en <= 1'b0;
else if(curr_state == RX_IP_HEADER && cnt_ip_header == 5'd19)
ip_checksum_cal_en <= 1'b1;
else
ip_checksum_cal_en <= 1'b0;
ip_checksum ip_checksum(
.clk (clk125m ),
.reset_p (reset_p ),
.cal_en (ip_checksum_cal_en),
.IP_ver (rx_ip_ver ),
.IP_hdr_len (rx_ip_hdr_len ),
.IP_tos (rx_ip_tos ),
.IP_total_len (rx_total_len ),
.IP_id (rx_ip_id ),
.IP_rsv (rx_ip_rsv ),
.IP_df (rx_ip_df ),
.IP_mf (rx_ip_mf ),
.IP_frag_offset (rx_ip_frag_offset ),
.IP_ttl (rx_ip_ttl ),
.IP_protocol (rx_ip_protocol ),
.src_ip (rx_src_ip ),
.dst_ip (rx_dst_ip ),
.checksum (cal_check_sum )
);
always@(posedge clk125m or posedge reset_p)
if(reset_p)
ip_header_check_ok <= 1'b0;
else if({IP_ver,IP_hdr_len,IP_protocol,cal_check_sum,local_ip_reg} ==
{rx_ip_ver,rx_ip_hdr_len,rx_ip_protocol,rx_ip_check_sum,rx_dst_ip})
ip_header_check_ok <= 1'b1;
else
ip_header_check_ok <= 1'b0;
//cnt_udp_header信号在状态处于RX_ETH_HEADER 时,进入计数,否则清零,
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_udp_header <= 4'd0;
else if(curr_state == RX_UDP_HEADER)
cnt_udp_header <= cnt_udp_header + 1'b1;
else
cnt_udp_header <= 4'd0;
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
rx_src_port <= 16'h0;
rx_dst_port <= 16'h0;
rx_udp_length<= 16'h0;
end
else if(curr_state == RX_UDP_HEADER)
begin
case(cnt_udp_header)
4'd0: rx_src_port[15:8] <= rx_data_dly2;
4'd1: rx_src_port[7:0] <= rx_data_dly2;
4'd2: rx_dst_port[15:8] <= rx_data_dly2;
4'd3: rx_dst_port[7:0] <= rx_data_dly2;
4'd4: rx_udp_length[15:8] <= rx_data_dly2;
4'd5: rx_udp_length[7:0] <= rx_data_dly2;
default: ;
endcase
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
udp_header_check_ok <= 1'b0;
else if(rx_dst_port == local_port_reg)
udp_header_check_ok <= 1'b1;
else
udp_header_check_ok <= 1'b0;
//cnt_data
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_data <= 16'd0;
else if(curr_state == RX_DATA)
cnt_data <= cnt_data + 1'b1;
else
cnt_data <= 16'd0;
//cnt_drp_data
always@(posedge clk125m or posedge reset_p)
if(reset_p)
cnt_drp_data <= 5'd0;
else if(curr_state == RX_DRP_DATA)
cnt_drp_data <= cnt_drp_data + 1'b1;
else
cnt_drp_data <= 5'd0;
//FSM
always@(posedge clk125m or posedge reset_p)
if(reset_p)
curr_state <= IDLE;
else
curr_state <= next_state;
always@(*)
begin
case(curr_state)
IDLE:
if(!rx_datav_dly2 && rx_datav_dly1)//产生接受数据有效信号时进入下一个状态
next_state = RX_PREAMBLE;
else
next_state = IDLE;
//处于 RX_PREAMBLE 状态的时候,当以太网接收到帧界定符(D5)和 5 个的前导码(55)时,进入到RX_ETH_HEADER 状态,如果接收超过 7 个前导码,则表明此时数据接收错误,进入 IDLE 状态,
RX_PREAMBLE:
if(rx_data_dly2 == 8'hd5 && cnt_preamble > 4'd5)
next_state = RX_ETH_HEADER;
else if(cnt_preamble > 4'd7)
next_state = IDLE;
else
next_state = RX_PREAMBLE;
//处于 RX_ETH_HEADER 状态时,接收以太网头部数据,当接收完 14 个以太网头部数据之后,进入到 RX_IP_HEADER 状态,
RX_ETH_HEADER:
if(cnt_eth_header == 4'd13)
next_state = RX_IP_HEADER;
else
next_state = RX_ETH_HEADER;
RX_IP_HEADER:
if(cnt_ip_header == 5'd2 && eth_header_check_ok == 1'b0)
next_state = IDLE;
else if(cnt_ip_header == 5'd19)
next_state = RX_UDP_HEADER;
else
next_state = RX_IP_HEADER;
RX_UDP_HEADER:
if(cnt_udp_header == 4'd2 && ip_header_check_ok == 1'b0)
next_state = IDLE;
else if(cnt_udp_header == 4'd7 && udp_header_check_ok == 1'b0)
next_state = IDLE;
else if(cnt_udp_header == 4'd7)
next_state = RX_DATA;
else
next_state = RX_UDP_HEADER;
RX_DATA:
if((rx_data_length < 5'd18) && (cnt_data == rx_data_length - 1'b1))
next_state = RX_DRP_DATA;
else if(cnt_data == rx_data_length - 1'b1)
next_state = RX_CRC;
else
next_state = RX_DATA;
RX_DRP_DATA:
if(cnt_drp_data == 5'd17 - rx_data_length)
next_state = RX_CRC;
else
next_state = RX_DRP_DATA;
RX_CRC:
if(rx_datav_dly2 == 1'b0)
next_state = PKT_CHECK;
else
next_state = RX_CRC;
PKT_CHECK:
next_state = IDLE;
default:next_state = IDLE;
endcase
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_init <= 1'b0;
else if (rx_datav_dly1 && (~rx_datav_dly2))
crc_init <= 1'b1;
else
crc_init <= 1'b0;
//crc_en
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_en <= 1'b0;
else if(curr_state == IDLE)
crc_en <= 1'b0;
else if (curr_state != RX_PREAMBLE && rx_datav_dly2)
crc_en <= 1'b1;
else
crc_en <= 1'b0;
//crc_data
always@(posedge clk125m or posedge reset_p)
if(reset_p)
crc_data <= 8'd0;
else
crc_data <= rx_data_dly2;
crc32_d8 crc32_d8
(
.clk (clk125m ),
.reset_p (reset_p ),
.data (crc_data ),
.crc_init (crc_init ),
.crc_en (crc_en ),
.crc_result (crc_check )//latency=1
);
always@(posedge clk125m or posedge reset_p)
if(reset_p)
reg_data_overflow <= 1'b0;
else if(curr_state == RX_DATA && data_overflow_i == 1'b1)
reg_data_overflow <= 1'b1;
else
reg_data_overflow <= reg_data_overflow;
//payload output
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
payload_valid_o <= 1'b0;
payload_dat_o <= 8'h0;
end
else if(curr_state == RX_DATA)
begin
payload_valid_o <= 1'b1;
payload_dat_o <= rx_data_dly2;
end
else
begin
payload_valid_o <= 1'b0;
payload_dat_o <= 8'h0;
end
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
exter_mac <= 48'h0;
exter_ip <= 32'h0;
exter_port <= 16'h0;
end
else if(curr_state == PKT_CHECK)
begin
exter_mac <= rx_src_mac;
exter_ip <= rx_src_ip;
exter_port <= rx_src_port;
end
//done
always@(posedge clk125m or posedge reset_p)
if(reset_p)
begin
one_pkt_done <= 1'b0;
pkt_error <= 1'b0;
end
else if(curr_state == PKT_CHECK)
begin
one_pkt_done <= 1'b1;
if(crc_check == 32'h2144DF1C && reg_data_overflow == 1'b0)
pkt_error <= 1'b0;
else
pkt_error <= 1'b1;
end
else
begin
one_pkt_done <= 1'b0;
pkt_error <= 1'b0;
end
endmodule
最后再加一个顶层文件就完成以太网回环了,其他功能可自行更改,例化,然后使用
module eth_udp_loopback_rgmii(
input reset_n,
//eth_rx
input rgmii_rx_clk_i,
input [3:0] rgmii_rxd,
input rgmii_rxdv,
output eth_rst_n,
//eth_tx
output rgmii_tx_clk,
output [3:0] rgmii_txd,
output rgmii_txen,
//led
output [7:0] led
);
parameter LOCAL_MAC = 48'h00_0a_35_01_fe_c0;
parameter LOCAL_IP = 32'hc0_a8_00_02;
parameter LOCAL_PORT = 16'd5000;
//eth_rx
wire reset_p;
wire clk125m;
wire [47:0] exter_mac;
wire [31:0] exter_ip;
wire [15:0] exter_port;
wire [15:0] rx_data_length;
wire data_overflow;
wire [7:0] rx_payload_dat;
wire rx_payload_valid;
wire rx_pkt_done;
wire rx_pkt_err;
reg [3:0] pkt_right_cnt;
reg [3:0] pkt_err_cnt;
//eth_tx
wire tx_done;
wire tx_en_pulse;
wire [47:0] tx_dst_mac;
wire [31:0] tx_dst_ip;
wire [15:0] tx_dst_port;
wire [15:0] tx_data_length;
wire [7:0] tx_payload_dat;
wire tx_payload_req;
//eth_msg_buf interface
wire eth_msg_wr_en;
wire [111:0]eth_msg_din;
reg eth_msg_rd_en;
wire [111:0]eth_msg_dout;
wire [4 : 0]eth_msg_dat_cnt;
reg eth_msg_rd_en_dly1;
reg eth_tx_state; //1:tx 0:idle
// gmii interface
wire gmii_rx_clk;
wire [7:0] gmii_rxd;
wire gmii_rxdv;
wire gmii_tx_clk;
wire [7:0] gmii_txd;
wire gmii_txen;
wire rgmii_rx_clk;
wire pll_locked;
assign eth_rst_n = 1;
clk_wiz_0 pll //加锁相环调整相位,同时加强时钟扇出能力
(
// Clock out ports
.clk_out1(rgmii_rx_clk), // output clk_out1
.clk_in1(rgmii_rx_clk_i), // input clk_in1
.locked (pll_locked)
);
assign led = {pkt_err_cnt,pkt_right_cnt};
assign reset_p = ~reset_n;
always@(posedge clk125m or posedge reset_p)
begin
if(reset_p)
pkt_right_cnt <= 4'd0;
else if(~rx_pkt_err && rx_pkt_done)
pkt_right_cnt <= pkt_right_cnt + 1'b1;
else
pkt_right_cnt <= pkt_right_cnt;
end
always@(posedge clk125m or posedge reset_p)
begin
if(reset_p)
pkt_err_cnt <= 4'd0;
else if(rx_pkt_err && rx_pkt_done)
pkt_err_cnt <= pkt_err_cnt + 1'b1;
else
pkt_err_cnt <= pkt_err_cnt;
end
rgmii_to_gmii rgmii_to_gmii(
.reset_n(reset_n),
.gmii_rx_clk(gmii_rx_clk),
.gmii_rxdv(gmii_rxdv),
.gmii_rxd(gmii_rxd),
.gmii_rxerr(),
.rgmii_rx_clk(rgmii_rx_clk),
.rgmii_rxd(rgmii_rxd),
.rgmii_rxdv(rgmii_rxdv)
);
//以太网接收
eth_udp_rx_gmii eth_udp_rx_gmii(
.reset_p (reset_p ),
.local_mac (LOCAL_MAC ),
.local_ip (LOCAL_IP ),
.local_port (LOCAL_PORT ),
.clk125m_o (clk125m ),
.exter_mac (exter_mac ),
.exter_ip (exter_ip ),
.exter_port (exter_port ),
.rx_data_length (rx_data_length ),
.data_overflow_i (data_overflow ),
.payload_valid_o (rx_payload_valid ),
.payload_dat_o (rx_payload_dat ),
.one_pkt_done (rx_pkt_done ),
.pkt_error (rx_pkt_err ),
.debug_crc_check ( ),
.gmii_rx_clk (gmii_rx_clk ),
.gmii_rxdv (gmii_rxdv ),
.gmii_rxd (gmii_rxd )
);
//对以太网接收数据缓存
eth_data_buf eth_data_buf (
.clk (clk125m ), // input wire clk
.din (rx_payload_dat ), // input wire [7 : 0] din
.wr_en (rx_payload_valid ), // input wire wr_en
.rd_en (tx_payload_req ), // input wire rd_en
.dout (tx_payload_dat ), // output wire [7 : 0] dout
.full (data_overflow ), // output wire full
.empty ( ) // output wire empty
);
//同时对报文中MAC、IP等消息数据缓存
assign eth_msg_wr_en = rx_pkt_done;
assign eth_msg_din = {exter_mac,exter_ip,exter_port,rx_data_length};
eth_msg_buf eth_msg_buf (
.clk (clk125m ), // input wire clk
.din (eth_msg_din ), // input wire [111 : 0] din
.wr_en (eth_msg_wr_en ), // input wire wr_en
.rd_en (eth_msg_rd_en ), // input wire rd_en
.dout (eth_msg_dout ), // output wire [111 : 0] dout
.full ( ), // output wire full
.empty ( ), // output wire empty
.data_count (eth_msg_dat_cnt ) // output wire [4 : 0] data_count
);
always@(posedge clk125m or posedge reset_p)
begin
if(reset_p)
eth_tx_state <= 1'b0;
else if(tx_done)
eth_tx_state <= 1'b0;
else if(eth_msg_dat_cnt >0)
eth_tx_state <= 1'b1;
else
eth_tx_state <= eth_tx_state;
end
always@(posedge clk125m or posedge reset_p)
begin
if(reset_p)
eth_msg_rd_en <= 1'b0;
else if((eth_tx_state == 1'b0)&&(eth_msg_dat_cnt >0))
eth_msg_rd_en <= 1'b1;
else
eth_msg_rd_en <= 1'b0;
end
always@(posedge clk125m)
eth_msg_rd_en_dly1 <= eth_msg_rd_en;
assign tx_en_pulse = eth_msg_rd_en_dly1;
assign tx_dst_mac = eth_msg_dout[111:64];
assign tx_dst_ip = eth_msg_dout[63:32];
assign tx_dst_port = eth_msg_dout[31:16];
assign tx_data_length = eth_msg_dout[15:0];
eth_udp_tx_gmii eth_udp_tx_gmii
(
.clk125m (clk125m ),
.reset_p (reset_p ),
.tx_en_pulse (tx_en_pulse ),
.tx_done (tx_done ),
.dst_mac (tx_dst_mac ),
.src_mac (LOCAL_MAC ),
.dst_ip (tx_dst_ip ),
.src_ip (LOCAL_IP ),
.dst_port (tx_dst_port ),
.src_port (LOCAL_PORT ),
.data_length (tx_data_length ),
.payload_req_o (tx_payload_req ),
.payload_dat_i (tx_payload_dat ),
.gmii_tx_clk (gmii_tx_clk ),
.gmii_txen (gmii_txen ),
.gmii_txd (gmii_txd )
);
gmii_to_rgmii gmii_to_rgmii(
.reset_n(reset_n),
.gmii_tx_clk(gmii_tx_clk),
.gmii_txd(gmii_txd),
.gmii_txen(gmii_txen),
.gmii_txer(1'b0),
.rgmii_tx_clk(rgmii_tx_clk),
.rgmii_txd(rgmii_txd),
.rgmii_txen(rgmii_txen)
);
endmodule
代码基本都是小梅哥的代码,我属于纯学习,另外一块别家的板子我没弄好。。。。
标签:FPGA,IP,rx,千兆,gmii,crc,网口,input,reg From: https://blog.csdn.net/weixin_68654423/article/details/144109769