国人讲CRC的没有什么能讲明白的文章,除了一篇《我学习 CRC32、CRC16、CRC 原理和算法的总结(与 WINRAR 结果一致)》,这里先感谢他,另,他也有一些没有说明白的地方,怎么说呢,还是鄙人自己来吧。
我弄明白CRC这个原理和算法主要参考的是上面的国人的那篇和这个外国的《A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS INDEX V3.00 (9/24/96)》,地址 http://www.repairfaq.org/filipg/LINK/F_crc_v3.html 这两篇大作。
首先要明确的是CRC只是一种错误检错的码,而没有纠正的作用,那么纠错码可以通过对原数据的+/-/*/÷等进行,但是+/-的问题太大,比如都是对原数据每个bit进行累加运算,如果出错了的话,比如有1bit的1变为0,1bit的0变为1,+/-运算完全识别不出这种错误,显然这种错误除法的辨认概率是非常大的,于是不知道哪位大神就把CRC这种校验方式给搞出来了。
这里我先介绍一下CRC的运算方式,其实就是一个数去除以生成多项式,余数就是CRC校验码,但是这里的除法和现实意义上面的除法有点出入。
首先先看一下原始的除法取余运算。正常除法,120÷9=13…3,这里的余数即是我们所要的校验位的码字。
1 1 0 1
______________
1 0 0 1/ 1 1 1 1 0 0 0 被除数120是1111000,除数9是1001
1 0 0 1
-------------
1 1 0 0 第一次减法后得到011
1 0 0 1
-------------
1 1 0 0 第二次减法后得到0101
1 0 0 1
-------------
1 1 -> 余数是3
这里用的是真正的减法,不好用,要用减法真的,于是将所有减法运算都用XOR运算替换掉,CRC就横空出世了,这里还用120这个数举例子,CRC所用的除法里,120÷9=14…6
1 1 1 0
______________
1 0 0 1/ 1 1 1 1 0 0 0 被除数120是1111000,除数9是1001
1 0 0 1
-------------
1 1 0 0 第一次XOR后得到011
1 0 0 1
-------------
1 0 1 0 第二次XOR后得到0101
1 0 0 1
-------------
1 1 0 -> 余数是6
这里的6就是真正的CRC校验码,那么他是谁的CRC呢?注意,他不是120的CRC校验码,而是15(0x0F)的CRC校验码,这里为啥是这样,只能说是规定,因为CRC相当于是余数,如果说110b是1111b的CRC校验码,那么将1111 110b排成一排送入CRC模块,剩余的余数就应该是0,规定就是这样的。所以对于110b只能说他是1111b的CRC校验码,不能说他是1111000b的CRC校验码。
通过这个例子,可以说明CRC这种算法的结构,对1111b进行CRC校验运算,校验码是110b,其中,1111b为数据,1001b(9)就是生成多项式,这里的CRC位数为3,记为W=3,因为是在1111b后面追加3bit的数据,所以是CRC3,生成多项式可以写作g(x) = x3+1。以此为例来循序渐进的介绍CRC的3种计算方法。
一、直接计算法
直接计算法就是用寄存器去模拟上面的CRC除法过程,我们假设待测数据是11 0101 1011,生成项是10011(g(x) = x4+x+1),需要有一个4bit的寄存器(因为每次的最高位都必定被XOR为0或者是移位直到最高位为0再XOR),通过反复的移位和进行CRC的除法,最终该寄存器中的值就是我们所要求的余数。开始
1) 待测数据后扩展W=4个比特0,变成1101011011 0000(augmented message);
2) 寄存器初始化置0;
3) 先在寄存器中移入数据1101;
4) 寄存器左移一位,并且右边移入下一位数据0。这样最高位1移出,由于最高位是1,故本次的商是1,要用除数1001来进行XOR,最高位肯定XOR得0,故不管它,只要用低4位0011来进行XOR就可以,即0011对此时寄存器进行XOR,寄存器中得到1001,即第一次 XOR 后的结果(相当于是数据11010与生成项10011进行了一次XOR,并把最高位0消掉了)。 如果移出的最高位是0,则用0000来进行XOR(相当于只是进行了移位)。
5) 一直重复这个过程,就能得到最后余数了。
3 2 1 0 Bits
+---+---+---+---+
Pop! <-- | | | | | <----- Augmented message(1101011011 0000)
+---+---+---+---+
代码描述如下
#define CRC_WIDTH 4 #define CRC_POLY 0x3 // 0011b// load datadata = 0x35B; // 1101011011b // append W zeros to the datadata <<= CRC_WIDTH; // initial the regsregs = 0; // processingfor(shift_bit=DATA_WIDTH+CRC_WIDTH; shift_bit>0; shift_bit--){// shiftregs = (regs<<1) | ((data>>(shift_bit-1))&0x1);// xorif(regs>>CRC_WIDTH) regs = regs ^ CRC_POLY;}
这种方法是最直观的方法,先理解了这种方法才能理解后面的方法,从这里的C代码到硬件实现就更简单了,画出电路图
+---+ +---+ +---+ +---+
+<--| |<--| |<--| |<--XOR<--| |<--XOR<-- Augmented message
| +---+ +---+ +---+ ^ +---+ ^
| | |
| | |
+----------------------------+-------------+
稍微体会一下就会明白,看清XOR的位置,对应除法的时候异或运算的处理,就好。
二、驱动表法(table drive)
故名思议,驱动表法就是查表,预先生成一个表,就省得每次都进行XOR费时费力,驱动表法的概念推导却是全靠了直接计算法得来的。(但是其实驱动表法并不是我们常用的查表法,常用的查表法这里被称为“直接查表法”,这是后话。)具体怎么来的呢,听我慢慢道来
首先,举个例子,待测数据是1011 0100b,生成多项式比如是1 0001 1100b,那么我们比如想做的是4bit的查找表,这里查找表的大小其实是无关紧要的,只不过一般都用的是8bit的查找表,因为一般的数据都是以byte为单位的,如果是4bit的查找表那么我们的表大小就是16,但是每个表单元还是根据CRC的宽度定的,话不多说,先来看看直接法计算的过程,
1 0 1 1 1 0 0 0
________________________________
1 0 0 0 1 1 1 0 0/ 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0
1 0 0 0 1 1 1 0 0
-------------------------------
1 1 1 0 1 0 0 0 0
1 0 0 0 1 1 1 0 0
-------------------------------
1 1 0 0 1 1 0 0 0
1 0 0 0 1 1 1 0 0
-------------------------------
1 0 0 0 0 1 0 0 0
1 0 0 0 1 1 1 0 0
-------------------------------
1 0 1 0 0 0 0 0
首先,要知道的是A^B^C = A^(B^C),那么我们在使用直接计算法的时候,针对前4bit,我们需要计算4次,3次都是要计算与生成多项式的XOR结果,那么查表法应运而生,看下面的式子
1 0 1 1 1 0 0 0 ________________________________ 1 0 0 0 1 1 1 0 0/ 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 01 0 0 0 1 1 1 0 0 --->0 0 0 0 0 0 0 0 0 --->1 0 0 0 1 1 1 0 0 --->1 0 0 0 1 1 1 0 0 --->-------------------------------1 0 0 0 0 1 0 0 01 0 0 0 1 1 1 0 0-------------------------------1 0 1 0 0 0 0 0
我们用空间去换时间,计算4次,并且知道要计算的高4bit是1011,那么我们把根据生成多项式进行移位,得出的高4bit为index的table中查出后面的8bit就可以相当于原来的4次高4bit的XOR运算,就可以变成这样
1 0 1 1 1 0 0 0 ________________________________ 1 0 0 0 1 1 1 0 0/ 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0(1 0 1 1)1 1 0 0 0 1 0 0-------------------------------1 0 0 0 0 1 0 0 01 0 0 0 1 1 1 0 0-------------------------------1 0 1 0 0 0 0 0
即是说,应用POLY(生成多项式)来搞出4bit的查找表,index是由生成多项式的最高bit进行移位和XOR运算得到,内容当然也就是上面这样的XOR的后面8bit的结果,针对上面的例子,查找表的生成函数是这样的
#define LUT_WIDTH 4 #define CRC_WIDTH 8 #define CRC_POLY 0x11Cfor(index=0; index<(1<<LUT_WIDTH); index++){temp = 0;for(bit_cnt=LUT_WIDTH; bit_cnt>0; bit_cnt--){if((index>>(bit_cnt-1) ^ temp>>(CRC_WIDTH-1))&0x1) temp = (temp<<1) ^ CRC_POLY;else temp <<= 1;}table[index] = (unsigned char)temp; }
这样,表就制作完成了,好进入正题,接着就开始驱动表法的计算方法了,拿到表之后要怎么查呢?(4bit的没人用,为了描述方便,以常用的8bit index的查找表为例叙述)
1)register左移一个字节,从原始数据中读入一个新的字节.
2)利用刚从register移出的字节作为index定位table中的一个值
3)把这个值XOR到register中。
4)如果还有未处理的数据则回到第一步继续执行。
1 0 Bytes+----+----++-----<| | | <----- Augmented message| +----+----+| ^| || XOR| || 0+----+----+v +----+----+| +----+----+| +----+----+| +----+----+| +----+----+| +----+----++----->+----+----++----+----++----+----++----+----++----+----+255+----+----+
这里举个计算的例子,计算0x31, 0x32, 0x33, 0x34对CRC-CCITT(CRC16,POLY=0x1021)的计算。高亮的字体就是每一步的寄存器中的数据。
__________________
10 21/ 31 32 33 34 00 00
26 72 --> index=31
------------------
14 41 34 00 00
52 B5 --> index=14
------------------
13 81 00 00
22 52 --> index=13
------------------
A3 52 00
85 89 --> index=A3
------------------
D7 89
计算过程代码描述
unsigned char buff[] = {0x31, 0x32, 0x33, 0x34}; unsigned int len = sizeof(buff); unsigned char *pointer; unsigned int regs;pointer = buff; regs = 0; while(len--){regs = ((regs<<8)|*pointer++) ^ table[(regs>>8)&0xFF]; } // append zeros to finish the calculation for(i=0; i<2; i++){regs = (regs<<8) ^ table[(regs>>8)&0xFF]; }
三、直驱表法(direct table)
这才是平常软硬件正常使用的方法,先来看看他的庐山真面目(对比图,CRC32),先看下CRC32的驱动表法的图形
3 2 1 0 Bytes
+----+----+----+----+
+-----<| | | | | <----- Augmented message
| +----+----+----+----+
| ^
| |
| XOR
| |
| 0+----+----+----+----+
v +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
+----->+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
255+----+----+----+----+
首先要明确的就是,驱动表法,寄存器的初始值必须是0,之后开始运算,如果不是0的话相当于是在原数据的最高位前面insert了数据,之后,就是后面加上去的0,其实没有真正的作用观察前面{31 32 33 34}计算的表就能得出这个结论,0的实际意义就是要为了把前面的数据都送进register里面进行运算。英文原文解释如下(但是我没看懂 后来自己琢磨明白的)
TAIL
The W/4 augmented zero bytes that appear at the end of the message will be pushed into the register from the right as all the other bytes are, but their values (0) will have no effect whatsoever on the register because 1) XORing with zero does not change the target byte, and 2) the four bytes are never propagated out the left side of the register where their zeroness might have some sort of influence. Thus, the sole function of the W/4 augmented zero bytes is to drive the calculation for another W/4 byte cycles so that the end of the REAL data passes all the way through the register.
HEAD
If the initial value of the register is zero, the first four iterations of the loop will have the sole effect of shifting in the first four bytes of the message from the right. This is because the first 32 control bits are all zero and so nothing is XORed into the register. Even if the initial value is not zero, the first 4 byte iterations of the algorithm will have the sole effect of shifting the first 4 bytes of the message into the register and then XORing them with some constant value (that is a function of the initial value of the register).
These facts, combined with the XOR property
(A xor B) xor C = A xor (B xor C)
31 32 33 34 00 00 --> data(1)=31, data(2)=32, data(3)=33, data(4)=34
26 72 --> index(1)=31, 31 = data(1) ^ 00
------------------
14 41 34 00 00
52 B5 --> index(2)=14, 14 = data(2) ^ table(index(1))[15:8]
------------------
13 81 00 00
22 52 --> index(3)=13, 13 = data(3) ^ table(index(2))[15:8] ^ table(index(1))[7:0]
------------------
A3 52 00
85 89 --> index(4)=A3, A3 = data(4) ^ table(index(3))[15:8] ^ table(index(2))[7:0]
------------------
D7 89
观察每一步的index,就会发现,如果是CRC-CCITT的计算,其实的话每一步的index和最终结果都可以这么表示
index(n) = data(n)[7:0] ^ table(index(n-1))[15:8] ^ table(index(n-2))[7:0]; result = index(last);
于是再看直驱表法的图形
+-----<Message (non augmented)
|
v 3 2 1 0 Bytes
| +----+----+----+----+
XOR----<| | | | |
| +----+----+----+----+
| ^
| |
| XOR
| |
| 0+----+----+----+----+
v +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
| +----+----+----+----+
+----->+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
255+----+----+----+----+
算法描述
1) Shift the register left by one byte(this byte is called top byte), reading in a new message byte.
2) XOR the top byte with the next message byte to yield an index into the table ([0,255]).
3) XOR the table value into the register.
4) Goto 1 if more message to be processed.
代码这样
// direct table crc calculation, CRC16pointer = buff;regs = CRC_INIT;for(i=0; i<len; i++){regs = (regs<<8) ^ table[(regs>>8)&0xFF ^ *pointer++];}
这样的话如果使用这种方法,那么最后append zero的0,因为XOR运算,所以XOR0还是原来的数,于是最后两步的0就省掉了(注:这个过程是首先有了驱动表法,根据计算过程推出了直驱表法,再接着由于推出的直驱表法而省略掉了最后面的augmented zeros)。
还举例子为31323334,POLY还是0x1021的例子
31 --> 00 00
31 26 72
------------------
32 -----> 26 72
14 52 B5
------------------
33 --------> 20 B5
13 22 52
------------------
34 -----------> 97 52
A3 85 89
------------------
D7 89
下划线的是每一步的index,高亮的是每一步寄存器中留下的值,可以看出,每一步留下的值相当于都是前面的数据计算所得的CRC校验码。而这里的00,是真正意义上的CRC_INIT,就是寄存器的初始值,其实我觉得反映的就是接着前面计算CRC值再继续进行计算,因为寄存器中每次的值相当于都是前面输入数据的CRC值,那么寄存器中有啥都是前面数据留下的真正的CRC值,所以寄存器的初始值就是这个意思,在什么样的基础上继续进行CRC的计算。
四、CRC计算模型
CRC不止这点东西,还有一些其他的东西要处理,比如CRC其实都是有model的,不光是CRC的POLY和INIT
Name : "CRC-32"
Width : 32
Poly : 04C11DB7
Init : FFFFFFFF
RefIn : True
RefOut : True
XorOut : FFFFFFFF
Check : CBF43926
这里面知道的不解释,
RefIn 指的就是如果这个值是FALSE,表示待测数据的每个字节都不用“颠倒”,即BIT7仍是作为最高位,BIT0作为最低位。 如果这个值是TRUE,表示待测数据的每个字节都要先“颠倒”,即BIT7作为最低位,BIT0作为最高位。
RefOut 是说如果这个值是FALSE,表示计算结束后,寄存器中的值直接进入XOROUT处理即可。 如果这个值是TRUE,表示计算结束后,寄存器中的值要先“颠倒”,再进入XOROUT处理。注意,这是将整个寄存器的值颠倒,由最高bit到最低bit进行颠倒。
XorOut 这个值与经RefOut后的寄存器的值相XOR,得到的值就是最终正式的CRC值!
Check 就是字符串"123456789"(就是16进制的0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39)经过这个CRC运算的结果作为一个标准,来帮助你检查你的算法是否正确的指标。
常用的CRC model还有
Name : "CRC-16"
Width : 16
Poly : 8005
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : BB3D
Name : "CRC-16/CCITT"
Width : 16
Poly : 1021
Init : FFFF
RefIn : False
RefOut : False
XorOut : 0000
Check : ?
Name : "XMODEM"
Width : 16
Poly : 8408
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?
Name : "ARC"
Width : 16
Poly : 8005
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?
好,至此就介绍完了,C的全程代码后面附上,verilog的代码稍候带来。
注:我编译的机器是64位的,如果是32位的话有的变量类型需要改变
直接计算法
#include <stdio.h> #define CRC_POLY 0x00011021 #define CRC_WIDTH 16 #define CRC_INIT 0x00000000 #define DATA_POLY 0x31323334L #define DATA_WIDTH 32void print_regs(int, int, int); int main() {unsigned long data = DATA_POLY;unsigned long regs = CRC_INIT;int shift_bit;int i;// append zeros to the datadata <<= CRC_WIDTH; // print for debugprintf("data -->\t0x%lx\t", data);for(i=DATA_WIDTH+CRC_WIDTH-1; i>=0; i--){printf("%d ", (data>>i)&0x1);}printf("\n"); // processingfor(shift_bit=DATA_WIDTH+CRC_WIDTH; shift_bit>0; shift_bit--){// shift of 1 cycleregs = (regs<<1) | ((data>>(shift_bit-1))&0x1);// xor of 1 cycleif(regs>>CRC_WIDTH) regs = regs ^ CRC_POLY;// resultprint_regs(DATA_WIDTH+CRC_WIDTH-shift_bit+1, CRC_WIDTH, regs);}return 0; }void print_regs(int crc_step, int crc_width, int crc_regs) {int i;printf("Step %d\t\t", crc_step);for(i=crc_width; i>0; i--){printf("%d ", (crc_regs>>(i-1)&0x1));}printf("\n"); }
驱动表法
#include <stdio.h> #define BYTE_L 8 //#define CRC32 // default CRC16, unless define CRC32#ifdef CRC32#define CRC_WIDTH 32#define CRC_POLY 0x04C11DB7#define CRC_INIT 0x00000000 #else#define CRC_WIDTH 16#define CRC_POLY 0x1021#define CRC_INIT 0x0000 #endifvoid print_regs(unsigned int, unsigned int); int main() {unsigned char bit_cnt ;unsigned short index ;unsigned int table[256] ;unsigned int temp ;unsigned char buff[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};unsigned int len = sizeof(buff);unsigned char *pointer ;unsigned int regs ;unsigned int i ;// crc LUT generationfor(index=0; index<256; index++){temp = 0;for(bit_cnt=BYTE_L; bit_cnt>0; bit_cnt--){if((index>>(bit_cnt-1) ^ temp>>(CRC_WIDTH-1))&0x1) temp = (temp<<1) ^ CRC_POLY;else temp <<= 1;} #ifdef CRC32table[index] = (unsigned int)temp; #elsetable[index] = (unsigned short)temp; #endif} // crc calculationpointer = buff;regs = CRC_INIT;while(len--){print_regs(CRC_WIDTH, regs);regs = ((regs<<BYTE_L)|*pointer++) ^ table[(regs>>(CRC_WIDTH-BYTE_L))&0xFF];}for(i=0; i<CRC_WIDTH/BYTE_L; i++){print_regs(CRC_WIDTH, regs);regs = (regs<<BYTE_L) ^ table[(regs>>(CRC_WIDTH-BYTE_L))&0xFF];} #ifdef CRC32printf("result --> 0x%Xh\n", regs); #elseprintf("result --> 0x%Xh\n", (unsigned short)regs); #endifreturn 0; }void print_regs(unsigned int crc_width, unsigned int crc_regs) {int i;for(i=crc_width; i>0; i--){printf("%d ", (crc_regs>>(i-1)&0x1));}printf("\n"); }
直驱表法
#include <stdio.h> #define BYTE_L 8 //#define CRC32 // default CRC16, unless define CRC32#ifdef CRC32#define CRC_WIDTH 32#define CRC_POLY 0x04C11DB7#define CRC_INIT 0xFFFFFFFF #else#define CRC_WIDTH 16#define CRC_POLY 0x1021#define CRC_INIT 0x0000 #endifvoid print_regs(unsigned int, unsigned int); int main() {unsigned char bit_cnt ;unsigned short index ;unsigned int table[256] ;unsigned int temp ;unsigned char buff[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};unsigned int len = sizeof(buff);unsigned char *pointer ;unsigned int regs ;unsigned int i ;// crc LUT generationfor(index=0; index<256; index++){temp = 0;for(bit_cnt=BYTE_L; bit_cnt>0; bit_cnt--){if((index>>(bit_cnt-1) ^ temp>>(CRC_WIDTH-1))&0x1) temp = (temp<<1) ^ CRC_POLY;else temp <<= 1;} #ifdef CRC32table[index] = (unsigned int)temp; #elsetable[index] = (unsigned short)temp; #endif} // crc calculationpointer = buff;regs = CRC_INIT;for(i=0; i<len; i++){print_regs(CRC_WIDTH, regs);regs = (regs<<BYTE_L) ^ table[(regs>>(CRC_WIDTH-BYTE_L))&0xFF ^ *pointer++];} #ifdef CRC32printf("result --> 0x%Xh\n", regs); #elseprintf("result --> 0x%Xh\n", (unsigned short)regs); #endifreturn 0; }void print_regs(unsigned int crc_width, unsigned int crc_regs) {int i;for(i=crc_width; i>0; i--){printf("%d ", (crc_regs>>(i-1)&0x1));}printf("\n"); }