在了解CRC16之前,我们可以参观一下Koopman的网站,这位老师对CRC研究的非常深入,也通过网站的形式列举了他找到的比较好的二项式。当然CRC的长度越大,寻找的代价也就越高,这样是为什么库老师目前在CRC64附近就停住了,估计下一波要等到量子计算机上市了,:) 
我们今天来研究一下CRC16。在网站的第一个Block,列举了不少CRC16的二项式,二项式的写法与我们之前介绍的还不太一样,例如在16列: 
0x8d95 
0xd175 
0xac9a 
等等 
0x8d95也可以写成: 
1 0001 1011 0010 1011b (需要在最后面补一个1b) 
或: 
X16+X12+X11+X9+X8+X5+X3+X+1 
Note: 
只有除数是17位的才能产生16位的CRC。 
下面我们来了解一下CRC16的计算方法。假设我们准备传送Helo! (比Hello!少一个l节约原始数据长度)这一串字符。首先我们将字符通过ASCII编码转换成16进制的数: 
Helo! 
0x48 0x65 0x6C 0x6F 0x21 
转换成二进制数: 
0100 1000 0110 0101 0110 1100 0010 0001b 
按照第一篇文章中介绍的长除我们将这一串二进制进行长除: 
最后得到16-bit的CRC值:
1000 0010 1110 1011b
当发送端发送数据的时候,将原数据左移16位然后进行二进制长除,得到的16-bit CRC将代替原本左移后补016-bit位置。发送端将组合后的数据一起发送给接收端。这时候接收端可以直接对组合后的数据进行二进制长除,当然双方要约定好相同的二项式,即‘除数’。如果余数是0,说明数据和CRC很大可能没有出现反转。CRC一般都有一定的漏检概率,随着保护数据长度的增加而增加。如果余数不是0,那么说明组合后的数据一定有bit反转了。 
接下来我们来看看如何设计一个电路来产生CRC16。我们再来观察一下二进制长除的过程,基本上就是二项式从左到右进行移动,当二进制最左侧的1和数据的1重合的时候,将数据中与二项式中对应1的位置进行反转。然后二项式继续向右运动,当再次遇到数据中的1的时候停下来,再将与二项式中1对齐的位置的数据bit进行反转,一直到二项式的最右侧bit与数据的最右侧bit重合的时候停止。这时候保留在数据最右侧的16-bit内容就是CRC16了。如下面动画所示: 
整个过程中组合数据左向移动我们可以通过一个16-bit移位寄存器来模拟(关于线性移位寄存器的相关内容可以关注手把手教你伪随机一文),如下面动画所示: 
伴随Clock的跳动,组合数据中的数据bit会依次进入到整个16-bit移位寄存器中,直到组合数据的最右边bit进入Q0,我们停止Clock的跳动。
我们再回顾一下长除的过程,首先二项式有17位,而移位寄存器有16位。移位寄存器和除数二项式的关系如下图所示:

二进制长除在组合数据左移过程中,需要做以下操作:

  1. 当组合数据的bitQ15管脚Q出来的时候是1b时,启动异或操作。
  2. 具体操作是将二项式中1所对应的组合数据位置进行反转。具体过程请参考前面的动画。

需要注意的是我们没有采用17位的寄存器主要是因为,Q15输出是1b的时候,再与二项式的最左边的1b进行异或,所得到的结果是0b,所以我们不需要记录该信息,因为永远这个第17bit处的操作结果都是0b

因此,CRC16的整个操作流程如下:

  1. 组合数据需要按照Clock的上升沿进行持续左移
  2. 当Q15的Q管脚输出1b的时候启动整个二项式与16位寄存器间的异或操作
  3. 当Q15的Q管脚输出0b的时候不做异或操作,而只是按照clock进行左移
  4. 当组合数据的最后一个bit进入Q0的管脚D后,停止Clock
  5. 这时候存放在16位移位寄存器中的内容即CRC16结果

为了满足上述流程,我们可以利用Q15的管脚Q输出作为条件,来控制某个具体位置的异或操作。拿Q0来说,当Q15.Q1b的时候,Q0.Q = Q0.D xor 1;当Q15.Q是0b的时候,Q0.Q = Q0.D xor 0

实际上上面的逻辑与下面逻辑等效:

Q0.Q = Q0.D xor Q15.Q

整个电路的设计如下:

我们将Q15.Q作为异或门的一个输入,用来控制什么时候需要反转组合数据的bit。而在剩下二项式为1b的位置,我们放置一个异或门用来反转组合数据的bit
整个过程将会和我们上面希望达成的操作一致:
  1. 所有移位寄存器中初始化0b
  2. 组合数据根据clock开始有节奏的一个bit,一个bit的进入Q0
  3. 这时,由于Q15.Q输出的还是0b,所以所有的异或门的上面的输入端都是0b,异或门的输出和异或门下面的输入端的值保持一致,即没有反转。
  4. 这个时候组合数据持续进入Q0,一直到组合数据中从左侧开始的第一个1b出现在Q15.Q的时候,所有的异或门开始反转组合数据相应位置的bit
  5. 当Q15.Q是0b的时候,所有的异或门不反转组合数据的bit
  6. 这样直到组合数据最后一个bit经过异或门进入Q0,clock停止,保留在Q15到Q1的管脚Q上的值即为CRC16的值。

下面是使用Python语言计算CRC16的代码: 

def crc16_8d95(data=0x48656C6F21, debug=1): 
    “”” 
    crc16_8d95 will help to calculate CRC16 based on original data 
    and the polynominal is 0x8d95 
    0x8d95 equals to 1 0001 1011 0010 1011b 
    input: 
    data – input data 
    “”” 
    print ‘This is crc16_8d95 scripts’ 
     
    crc_o  = 16*[0] 
    crc16  = 16*[0] 
    #Add 16 zero on right: 
    data = data << 16 
    datain, bits = getbit(data, reverse = 1) 
    for i in range(bits): 
        crc16[15]   =   crc_o[14] 
        crc16[14]   =   crc_o[13] 
        crc16[13]   =   crc_o[12] 
        crc16[12]   =   crc_o[11]  ^ crc_o[15] 
        crc16[11]   =   crc_o[10]  ^ crc_o[15] 
        crc16[10]   =   crc_o[9]  
        crc16[9]    =   crc_o[8]  ^ crc_o[15]  
        crc16[8]    =   crc_o[7]  ^ crc_o[15]  
        crc16[7]    =   crc_o[6]  
        crc16[6]    =   crc_o[5]  
        crc16[5]    =   crc_o[4]  ^ crc_o[15] 
        crc16[4]    =   crc_o[3]  
        crc16[3]    =   crc_o[2]  ^ crc_o[15] 
        crc16[2]    =   crc_o[1]  
        crc16[1]    =   crc_o[0]  ^ crc_o[15] 
        crc16[0]    =   datain[i] ^ crc_o[15] 
        crc_o[15]   =   crc16[15] 
        crc_o[14]   =   crc16[14] 
        crc_o[13]   =   crc16[13] 
        crc_o[12]   =   crc16[12] 
        crc_o[11]   =   crc16[11] 
        crc_o[10]   =   crc16[10] 
        crc_o[9]    =   crc16[9]  
        crc_o[8]    =   crc16[8]  
        crc_o[7]    =   crc16[7]  
        crc_o[6]    =   crc16[6]  
        crc_o[5]    =   crc16[5]  
        crc_o[4]    =   crc16[4]  
        crc_o[3]    =   crc16[3]  
        crc_o[2]    =   crc16[2]  
        crc_o[1]    =   crc16[1]  
        crc_o[0]    =   crc16[0]  
        if debug: 
            print “#%d  —- “%i, crc_o 
    #put bit15 on left and bit0 on right 
    crc16.reverse()  
    return crc16 
def getbit(data = 0, reverse = 0, debug = 0): 
    “”” 
    getbit() will help to convert data to a bit list. 
    Inputs: 
    data    : Input data (integer) 
    reverse : If reverse==1, then the most significant bit of data is output[0] 
    Outputs: 
    output  : a list to store bit0 to bit(n-1) 
              data length in binary 
    “”” 
     
    output = list() 
    #get data length in binary 
    datalen = len(bin(data))-2 
    for i in range(datalen): 
        output.append((data>>i) & 1) 
    if reverse: 
        output.reverse() 
    if debug: 
        print (output) 
    return output, datalen 
下面是使用System Verilog语言实现CRC16的代码: 
module crc16_8d95( 
    input   clk, 
    input   reset_b, 
    input   datain 
); 
reg [15:0]  Q; 
integer counter; 
always @(posedge clk) begin 
    if(!reset_b) begin 
        Q = 16’h0; 
        counter = 0; 
    end 
    else begin 
        if (counter <= 64) begin 
            Q[15]   <= Q[14]; 
            Q[14]   <= Q[13]; 
            Q[13]   <= Q[12]; 
            Q[12]   <= Q[11]  ^ Q[15]; 
            Q[11]   <= Q[10]  ^ Q[15]; 
            Q[10]   <= Q[9] ; 
            Q[9]    <= Q[8]   ^ Q[15]; 
            Q[8]    <= Q[7]   ^ Q[15]; 
            Q[7]    <= Q[6] ; 
            Q[6]    <= Q[5] ; 
            Q[5]    <= Q[4]   ^ Q[15]; 
            Q[4]    <= Q[3] ; 
            Q[3]    <= Q[2]   ^ Q[15]; 
            Q[2]    <= Q[1] ; 
            Q[1]    <= Q[0]   ^ Q[15]; 
            Q[0]    <= datain ^ Q[15]; 
            counter = counter + 1; 
        end 
    end 
end 
endmodule 

测试用例: 

`timescale 1ns/1ns 
module crc16_8d95_tb(); 
reg         clk; 
reg         reset_b; 
reg         datain; 
reg [63:0]  p_data; 
integer i; 
crc16_8d95 crc16_8d95( 
    .clk        (clk), 
    .reset_b    (reset_b), 
    .datain     (datain) 
); 
//initial block 
initial begin 
    reset_b = 0; 
    #50 
    reset_b = 1; 
end 
//clock generator 
initial begin 
    clk = 0; 
    #5 forever #5 clk = !clk; 
end 
always @(negedge clk or negedge reset_b) begin 
    if (!reset_b) begin 
        datain = 0; 
        i = 0; 
        p_data = 64’h48656C6F210000; 
    end 
    else begin 
        if(i < 64) begin 
            datain = p_data[63-i]; 
            i = i + 1; 
        end 
    end 
end 
endmodule 

Waveform:

Reference 
Scroll to Top