千兆网数据CRC检验和过滤

项目简述

      本次项目在计算机将图像数据信息通过千兆网发送给FPGA后,由于接收到的数据可能混乱和无效,需要对数据CRC校验和无效包过滤。

项目原理及框图

对iddr_ctrl模块的输入数据和使能信号,分成两部分处理:第一部分数据通过包有效检验,CRC32校验(单独建一个模块例化使用),包长度统计(通过rx_en和一个计数器来实现)后,把这三部分处理后的标志信号放在一个status_fifo中缓存,用一个16位位宽的status_value寄存器缓存;第二部分,将数据和使能信号缓存在一个数据FIFO中data_fifo模块中,待第一部分的status_fifo中缓存好了数据的状态信息如包有效标志,包长度,CRC32校验正确标志。通过判断status_value寄存器中的包有效标志,CRC32校验正确标志拉高,则数据校验和过滤完成,就将数据frx_data和使能信号frx_en输出。

 大概的设计思路,先设计CRC32校验模块,然后再设计上面的rx_filter_buffer用来对数据进行过滤和有效包检验。

CRC32校验原理是本次设计的核心,对于CRC-32的理解:第一步,刚开始先看看CRC的原理,第二步看书上对CRC-8的两种推导,一是实际手算推导,二是移位寄存器方式实现CRC(硬件的实现方式)。第三步,则是在理解CRC-8的基础上,CRC-32与它类似,主要是理解移位寄存器方式和对应的CRC校验码。

1.CRC的原理是通过网上博客讲解,我的理解如下:

根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为“模2除法”)。到达接收端后,再把接收到的新帧除以(同样采用“模2除法”)这个选定的除数。因为在发送端发送数据帧之前就已通过附加一个数,做了“去余”处理(也就已经能整除了),所以结果应该是没有余数。如果有余数,则表明该帧在传输过程中出现了差错。

CRC校验原理就是以下几个步骤:

   (1)先选择(可以随机选择,也可按标准选择,具体在后面介绍)一个用于在接收端进行校验时,对接收的帧进行除法运算的除数(是二进制比较特串,通常是以多项方式表示,所以CRC又称多项式编码方法,这个多项式也称之为“生成多项式”)。

   (2)看所选定的除数二进制位数(假设为k位),然后在要发送的数据帧(假设为m位)后面加上k-1位“0”,然后以这个加了k-1个“0“的新帧(一共是m+k-1位)以“模2除法”方式除以上面这个除数,所得到的余数(也是二进制的比特串)就是该帧的CRC校验码,也称之为FCS(帧校验序列)。但要注意的是,余数的位数一定要是比除数位数只能少一位,哪怕前面位是0,甚至是全为0(附带好整除时)也都不能省略

   (3)再把这个校验码附加在原数据帧(就是m位的帧,注意不是在后面形成的m+k-1位的帧)后面,构建一个新帧发送到接收端,最后在接收端再把这个新帧以“模2除法”方式除以前面选择的除数,如果没有余数,则表明该帧在传输过程中没出错,否则出现了差错。

    通过以上介绍,大家一定可以理解CRC校验的原理,并且不再认为很复杂吧。

从上面可以看出,CRC校验中有两个关键点:一是要预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式);二是把原始帧与上面选定的除进行二进制除法运算,计算出FCS。前者可以随机选择,也可按国际上通行的标准选择,但最高位和最低位必须均为“1”,如在IBM的SDLC(同步数据链路控制)规程中使用的CRC-16(也就是这个除数一共是17位)生成多项式g(x)= x16 + x15 + x2 +1(对应二进制比特串为:11000000000000101);而在ISO HDLC(高级数据链路控制)规程、ITU的SDLC、X.25、V.34、V.41、V.42等中使用CCITT-16生成多项式g(x)=x16 + x15 + x5 +1(对应二进制比特串为:11000000000100001)。

2.CRC-8的实现

  对于FPGA来言,主要是通过移位寄存器实现CRC校验码。

 对于CRC-32的原理,我们直接给出它的校验码的化简结果,原理和CRC-8类似

对于以上原理的分析,设计的CRC-32的Verilog代码如下:主要是对校验码生成原理的理解,而校验结果直接通过查找表实现即可。

module crc32_d8_rec_02(
input wire restb,
input wire sclk,
input wire dsin, //输入数据有效标志
input wire[7:0] din,
output wire crc32_cal_end,
output wire crc_err

);

reg dsin_r,crc32_end_r,crc_err_r;
wire [7:0]d;
reg [31:0]crc32_value;
wire[31:0]c;

assign d = din;
always @(posedge sclk ) begin
begin
// reset
dsin_r<=dsin;
end

end
assign c =crc32_value ;

assign crc32_cal_end = crc32_end_r;
assign crc_err = crc_err_r;

always@(posedge sclk)begin
if(dsin==1'b0)begin
crc32_value <=32'hffffffff;

end
else begin
crc32_value[0]<=c[24]^c[30]^d[1]^d[7];
crc32_value[1]<=c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];

crc32_value[2]<=c[26]^d[5]^c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[3]<=c[27]^d[4]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[4]<=c[28]^d[3]^c[27]^d[4]^c[26]^d[5]^c[24]^c[30]^d[1]^d[7];
crc32_value[5]<=c[29]^d[2]^c[28]^d[3]^c[27]^d[4]^c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[6]<=c[30]^d[1]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[7]<=c[31]^d[0]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[24]^d[7];
crc32_value[8]<=c[0]^c[28]^d[3]^c[27]^d[4]^c[25]^d[6]^c[24]^d[7];
crc32_value[9]<=c[1]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^d[6];
crc32_value[10]<=c[2]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[24]^d[7];
crc32_value[11]<=c[3]^c[28]^d[3]^c[27]^d[4]^c[25]^d[6]^c[24]^d[7];
crc32_value[12]<=c[4]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[13]<=c[5]^c[30]^d[1]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[14]<=c[6]^c[31]^d[0]^c[30]^d[1]^c[28]^d[3]^c[27]^d[4]^c[26]^d[5];
crc32_value[15]<=c[7]^c[31]^d[0]^c[29]^d[2]^c[28]^d[3]^c[27]^d[4];
crc32_value[16]<=c[8]^c[29]^d[2]^c[28]^d[3]^c[24]^d[7];
crc32_value[17]<=c[9]^c[30]^d[1]^c[29]^d[2]^c[25]^d[6];
crc32_value[18]<=c[10]^c[31]^d[0]^c[30]^d[1]^c[26]^d[5];
crc32_value[19]<=c[11]^c[31]^d[0]^c[27]^d[4];
crc32_value[20]<=c[12]^c[28]^d[3];
crc32_value[21]<=c[13]^c[29]^d[2];
crc32_value[22]<=c[14]^c[24]^d[7];
crc32_value[23]<=c[15]^c[25]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[24]<=c[16]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[25]<=c[17]^c[27]^d[4]^c[26]^d[5];
crc32_value[26]<=c[18]^c[28]^d[3]^c[27]^d[4]^c[24]^c[30]^d[1]^d[7];
crc32_value[27]<=c[19]^c[29]^d[2]^c[28]^d[3]^c[25]^c[31]^d[0]^d[6];
crc32_value[28]<=c[20]^c[30]^d[1]^c[29]^d[2]^c[26]^d[5];
crc32_value[29]<=c[21]^c[31]^d[0]^c[30]^d[1]^c[27]^d[4];
crc32_value[30]<=c[22]^c[31]^d[0]^c[28]^d[3];
crc32_value[31]<=c[23]^c[29]^d[2];

end

end

//数据有效标志下降沿标志数据结束
always@(dsin or dsin_r)begin
if(!dsin && dsin_r)
crc32_end_r<=1'b1;
else begin
crc32_end_r<=1'b0;
end
end

//数据校验出错标志
always @(posedge sclk or negedge restb) begin
if (restb==1'b0) begin
// reset
crc_err_r<=1'b0;
end
else if (crc32_end_r==1'b1 && crc32_value!=32'hc704dd7b) begin
crc_err_r<=1'b1;
end
else
crc_err_r<=1'b0;
end


endmodule

第二个模块,数据的包有效检验和过滤模块的设计

1、检测有效包,目前我们定义上位机软件设置源端口为 1 234 ,目的端口 1 23
U DP 协议字段为 8 ’h11 这样可以证明此包为来自于我们需要的应用 。 我们
在代码中需要检测这三个字段 检测到了后需要把此包的 vld_pkg 有效 这
样将来读取此包数据时会下发到下一级 。针对上图就是红色圈着的五个字节,只需要和标准值匹配则表明包有效。

关键代码如下

always @(posedge sclk) begin
if(rst) begin
pkg_value <= 40'd0;
end
else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin
pkg_value <= {pkg_value[31:0],PHY_rxd};
end
end

always @(posedge sclk) begin
if(rst) begin
pkg_vld <= 1'b0;
end
else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin
pkg_vld <= 1'b1;
end
else begin
pkg_vld <= 1'b0;
end
end


2、C RC 是为了证明一个数据包是否出错 此 C RC 为 32 位校验码 只能用于验证
数据是否出错不能纠正错误 。 CRC 校验的区域为去掉 一帧 前 8 字节的所有数
据(去掉 7 个 0x 55 和 1 个 0xd 5

关键代码如下:rx_en_new为CRC校验模块的使能信号,是在去掉帧头后驱动这个模块

reg rx_en_new;
always @(posedge sclk or negedge rx_en) begin
if (rx_en == 1'b0) begin
rx_en_new <= 1'b0;
end
else if (PHY_rx_cnt == 'd7) begin
rx_en_new <= 1'b1;
end
end

 rx_filter_buffer模块的时序图如上所示,根据时序图和原理框图可以很快设计出本模块代码如下所示:

module rx_filter_buffer(
input wire sclk,
input wire rst,
input wire [7:0]PHY_rxd,
input wire rx_en,
output reg frx_en,
output reg [7:0]frx_data

);

reg data_rd_en_r1;
wire [6 : 0] rd_data_count;
wire status_fifo_empty;
wire status_rd_en;
wire [15:0]status_dout;
wire [7:0]data_dout;
reg [15:0] status_value;

wire crc32_ok;
wire crc_err;
wire crc32_end;

assign crc32_ok =~crc_err ;
reg pkg_vld;

wire pkg_end;
reg [15:0] pkg_status;
assign pkg_end= pkg_vld;
assign status_rd_en = ~status_fifo_empty;
parameter PKG_LENTH = 1077;

reg [13:0] data_rd_en_cnt;
wire data_rd_en;
reg data_rd_en_r;
assign data_rd_en = data_rd_en_r1;

reg [39:0] pkg_value;
reg [10:0] pkg_len;
reg [12:0] PHY_rx_cnt;
reg rx_en_new;
always @(posedge sclk or negedge rx_en) begin
if (rx_en == 1'b0) begin
rx_en_new <= 1'b0;
end
else if (PHY_rx_cnt == 'd7) begin
rx_en_new <= 1'b1;
end
end
//status_fifo
status_fifo status_wr16rd16_8192 (
.wr_clk(sclk), // input wire wr_clk
.rd_clk(sclk), // input wire rd_clk
.din(pkg_status), // input wire [15 : 0] din
.wr_en(pkg_end), // input wire wr_en
.rd_en(status_rd_en), // input wire rd_en
.dout(status_dout), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(status_fifo_empty), // output wire empty
.rd_data_count(rd_data_count) // output wire [6 : 0] rd_data_count
);

crc32_d8_rec_02 inst_crc32_d8_rec_02
(
.restb (1'b1),
.sclk (sclk),
.dsin (rx_en_new),
.din (PHY_rxd),
.crc32_cal_end (crc32_end),
.crc_err (crc_err)
);

//data_rd_cnt;dataFIFO读出的数据个数
always @(posedge sclk )
if(rst==1'b1) begin
// reset
data_rd_en_cnt<='d0;
end

else if(data_rd_en)begin
data_rd_en_cnt<=data_rd_en_cnt+'d1;
end
else begin//datafifo刚好读完
data_rd_en_cnt<='d0;
end


always @(posedge sclk )
data_rd_en_r1<=data_rd_en_r;


//data_rd_en
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
data_rd_en_r<='d0;
end
else if(status_rd_en)begin
data_rd_en_r<='b1;
end
else if((data_rd_en_cnt==PKG_LENTH-1) && data_rd_en)begin
data_rd_en_r<='d0;
end
end


always @(posedge sclk ) begin
if (rst==1'b1) begin
// reset
PHY_rx_cnt<=13'd0;
end
else if (rx_en==1'b1) begin
PHY_rx_cnt<=PHY_rx_cnt+1'b1;
end

end

// always @(posedge sclk ) begin
// if (rst==1'b1) begin
// // reset
// PHY_rx_cnt<=11'd0;
// end
// else if (rx_en==1'b1) begin
// if(PHY_rx_cnt==PKG_LENTH )
// PHY_rx_cnt<=11'd0;
// else
// PHY_rx_cnt<=PHY_rx_cnt+1'b1;
// end
// else begin
// PHY_rx_cnt<=11'd0;
// end
// end

reg phy_rx_ctrl_r;
always @(posedge sclk ) begin
begin
// reset
phy_rx_ctrl_r<=rx_en;
end
end

reg PHY_rx_neg;
always @(posedge sclk ) begin
if(!rx_en &&phy_rx_ctrl_r ) begin
// reset
PHY_rx_neg<=1'b1;
end
else begin
PHY_rx_neg<=1'b0;
end
end
//pkg_len
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
pkg_len<=11'd0;
end
else if(PHY_rx_neg) begin
pkg_len<=PKG_LENTH;
end
end


//包有效字段检验
//5byte-40bit数据和标准值一样
always @(posedge sclk) begin
if(rst) begin
pkg_value <= 40'd0;
end
else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin
pkg_value <= {pkg_value[31:0],PHY_rxd};
end
end

always @(posedge sclk) begin
if(rst) begin
pkg_vld <= 1'b0;
end
else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin
pkg_vld <= 1'b1;
end
else begin
pkg_vld <= 1'b0;
end
end


always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
pkg_status<=16'd0;
end
else if(pkg_vld) begin
pkg_status<={crc32_ok,pkg_vld,pkg_len};
end
else begin
pkg_status<=16'd0;
end
end

//status_value
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
status_value<=16'd0;
end
else if(status_rd_en) begin
status_value<=status_dout;
end

end

//data_fifo
data_fifo asfifo_wr8X8192_rd8X8192 (
.wr_clk(sclk), // input wire wr_clk
.rd_clk(sclk), // input wire rd_clk
.din(PHY_rxd), // input wire [7 : 0] din
.wr_en(rx_en), // input wire wr_en
.rd_en(data_rd_en), // input wire rd_en
.dout(data_dout), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(empty) // output wire empty
);

always @(posedge sclk) begin
if(rst==1'b1) begin
frx_data <= 'd0;
frx_en <=1'b0;
end
else if(data_rd_en) begin
frx_data <= data_dout;
frx_en <=data_rd_en;
end
// else begin
// frx_data <= 'd0;
// frx_en <=1'b0;
// end
end

endmodule

由于没有A7的开发板,故本次设计了仿真文件验证:

仿真程序如下:

`timescale 1ns / 1ps
`define CLOCK 8

module tb_gigbit;

reg rst_n ;
reg gb_tx_data_en ;
reg gb_tx_clk ;
reg [ 7:0] gb_tx_data ;
wire [7:0] frx_data;
wire frx_en;


reg [7:0] mem[1073:0];
initial begin
$readmemb("./data.txt",mem);
end
// //D:FPGA_class3ZQ10qianz
// initial begin
// $readmemb("D:/FPGA_class/3ZQ/10qianz/data.txt",mem);
// end

initial begin
rst_n = 1'b0;
gb_tx_clk = 1'b0;
gb_tx_data = 8'd0;
gb_tx_data_en = 1'b0;
#1000;
rst_n = 1'b1;
gen_data();
#100;
gb_tx_data_en = 1'b0;

end

always #(`CLOCK/2) gb_tx_clk = ~gb_tx_clk;
// task gen_data;
// integer i;
// integer j;
// begin
// @(posedge gb_tx_clk);
// gb_tx_data_en = 1'b1;
// for(j=0;j<1073;j=j+1)begin
// for (i=0;i<1073;i=i+1)begin
// gb_tx_data=mem[i];
// @(posedge gb_tx_clk);
// end
// end
// gb_tx_data = 8'h0;
// gb_tx_data_en = 1'b0;
// #(1000*`CLOCK);
// @(posedge gb_tx_clk);
// end
// endtask

task gen_data;
integer i;
begin
@(posedge gb_tx_clk);
gb_tx_data_en = 1'b1;
for (i=0;i<1073;i=i+1)begin
gb_tx_data=mem[i];
@(posedge gb_tx_clk);
end
@(posedge gb_tx_clk);
gb_tx_data = 8'h0;
gb_tx_data_en = 1'b0;
#(100*`CLOCK);
@(posedge gb_tx_clk);
end
endtask


rx_filter_buffer inst_rx_filter_buffer (
.sclk (gb_tx_clk),
.rst (~rst_n),
.PHY_rxd (gb_tx_data),
.rx_en (gb_tx_data_en),
.frx_en (frx_en),
.frx_data (frx_data)
);

endmodule

仿真波形如下:

这次千兆网的校验和数据包有效的测验,我领悟到,针对一个新的设计,关键是两点:第一是拆分。第二是针对同一个知识点,用多种方式去理解。

参考资料:

V3学院

CSDN博客

https://www.cnblogs.com/liushui-sky/p/9962123.html

咸鱼FPGA

https://www.cnblogs.com/xianyufpga/p/12147156.html

原文地址:https://www.cnblogs.com/Xwangzi66/p/14185143.html