CRC校验码产生器
差错检测
在数据传输过程中,无论传输系统的设计再怎么完美,差错总会存在,这种差错可能会导致在链路上传输的一个或者多个帧被破坏(出现比特差错,0变为1,或者1变为0),从而接受方接收到错误的数据。为尽量提高接受方收到数据的正确率,在接收方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。
在一个 [p位二进制数据序列] 之后附加一个 [r位二进制校检码] ,构成一个总长为 [p+r的二进制序列] 。附加在数据序列之后的这个校检码与p位二进制序列之间存在一个特定的关系,如果因干扰等原因使得数据序列中的一些位发生错误,这种特性的关系就会破坏。因此,可以通过检查该特定关系,实现对接收到的数据正确性的检验。
检测的方式有多种,常见的有奇偶校验、累加和校检和循环冗余校验等。
(1)奇偶校验,检测错误概率大约为50%,简单但传输效率低。奇偶校验多用于低速度数据通讯,如RS232。
(2)累加和校验,检测错误概率大概为1/256,实现简单,也被广泛的采用。
(3)CRC校检,只要选择的除数多项式位数足够多,检测错误的概率几乎不存在。
循环冗余校验
CRC校验码产生器
不同的生成多项式有不同的检错能力,选用CRC-16生成多项式:
G(X) = x16+x15 + x2 + 1
CRC校验码产生器分为两种:串行CRC校验码产生器和并行CRC校验码产生器。
通常,CRC校验码的值可以通过线性移位寄存器和异或门求得,线性移位寄存器一次移一位,完成除法功能,异或门完成不带进位的减法功能。如果商数为1,则从被除数的高阶位异或除数,同时移位寄存器右移一位,准备为被除数的较低位进行运算;如果商数为0,则移位寄存器直接右移一位。
电路结构图如下所示:
串行CRC校验码产生器每个时钟移1位,并行CRC校验码产生器每个时钟周期移16位(用组合逻辑实现)。
在如下网站可以在线计算CRC:
http://www.ip33.com/crc.html
00AA -> 03FC
AA89 -> 7F3F
2853 -> F1EA
并行CRC-16检验码产生器
parallelCRC16.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/14
// Author Name: Sniper
// Module Name: parallelCRC16
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module parallelCRC16
#(
parameter DATA_WIDTH = 16
)
(
input clk,
input rst_n,
input [DATA_WIDTH-1:0] data,
output reg [15:0] crc_out
);
reg [15:0] crc_temp;
reg temp;
integer i;
//CRC out
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
crc_out <= 0;
else
crc_out <= crc_temp;
end
//CRC temp
//G(X) = x16 + x15 + x2 + 1
always@(*)
begin
if(!rst_n)
begin
crc_temp = 0;
temp = 0;
end
else
begin
crc_temp = 0;//init
for(i=DATA_WIDTH-1; i>=0; i=i-1)
begin
temp = data[i] ^ crc_temp[15];
crc_temp[15] = temp ^ crc_temp[14];
crc_temp[14:3] = crc_temp[13:2];
crc_temp[2] = temp ^ crc_temp[1];
crc_temp[1] = crc_temp[0];
crc_temp[0] = temp;
end
end
end
endmodule
tb_parallelCRC16.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/14
// Author Name: Sniper
// Module Name: tb_parallelCRC16
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module tb_parallelCRC16;
//parameter
parameter DATA_WIDTH = 16;
//input
reg clk;
reg rst_n;
reg [DATA_WIDTH-1:0] data;
//output
wire [15:0] crc_out;
initial
begin
clk = 0;
rst_n = 0;
data[DATA_WIDTH-1:0] = 0;
#100;
rst_n = 1;
@(posedge clk);
data <= 16'h00AA;
@(posedge clk);
data <= 16'hAA89;
@(posedge clk);
data <= 16'h2853;
end
//clock
always #5 clk = ~clk;
//DUT
parallelCRC16
#(
.DATA_WIDTH(DATA_WIDTH)
)
DUT
(
.clk(clk),
.rst_n(rst_n),
.data(data),
.crc_out(crc_out)
);
initial
begin
$dumpfile("tb_parallelCRC16.vcd");
$dumpvars(0,tb_parallelCRC16);
end
initial #1000 $finish;
endmodule
运行结果
vcs -R parallelCRC16.v tb_parallelCRC16.v
CRC结果在数据输入的下一个时钟周期产生。
串行CRC-16检验码产生器
serialCRC16.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/14
// Author Name: Sniper
// Module Name: serialCRC16
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module serialCRC16
#(
parameter DATA_WIDTH = 16
)
(
input clk,
input rst_n,
input clear,
input data,
output reg [15:0] crc_out
);
wire temp;
assign temp = data ^ crc_out[15];
//CRC out
//G(X) = x16 + x15 + x2 + 1
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
crc_out <= 0;
else if(clear)
crc_out <= 0;
else
begin
crc_out[15] <= temp ^ crc_out[14];
crc_out[14:3] <= crc_out[13:2];
crc_out[2] <= temp ^ crc_out[1];
crc_out[1] <= crc_out[0];
crc_out[0] <= temp;
end
end
endmodule
tb_serialCRC16.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/15
// Author Name: Sniper
// Module Name: tb_serialCRC16
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module tb_serialCRC16;
//parameter
parameter DATA_WIDTH = 16;
//input
reg clk;
reg rst_n;
reg clear;
reg data;
//output
wire [15:0] crc_out;
task write_data(input [DATA_WIDTH-1:0] data_all);
begin
@(posedge clk);
clear <= 1;
for(int i=DATA_WIDTH-1;i>=0;i=i-1)
begin
@(posedge clk);
data <= data_all[i];
clear <= 0;
end
repeat(3) @(posedge clk) clear <= 1;
end
endtask
initial
begin
clk = 0;
rst_n = 0;
clear = 0;
data = 0;
#100;
rst_n = 1;
repeat(3) @(posedge clk);
write_data(16'h00AA);
write_data(16'hAA89);
write_data(16'h2853);
end
//clock
always #5 clk = ~clk;
//DUT
serialCRC16
#(
.DATA_WIDTH(DATA_WIDTH)
)
DUT
(
.clk(clk),
.rst_n(rst_n),
.clear(clear),
.data(data),
.crc_out(crc_out)
);
initial
begin
$dumpfile("tb_serialCRC16.vcd");
$dumpvars(0,tb_serialCRC16);
end
initial #1000 $finish;
endmodule
运行结果
vcs -R -sverilog serialCRC16.v tb_serialCRC16.v
CRC结果在数据最后一个bit输入的下一个时钟周期产生。