5G中CRC table的生成

5G信道编码之CRC

CRC介绍

参照38.212 第5章
信道编码是一种组合,这种组合包含了检错,纠错,速率匹配,交织,以及传输信道和控制信息向物理信道的映射。
CRC是信道编码的重要组成部分。CRC的原理是通过多项式除法获得校验码,但是实际代码实现是通过查表法,以空间换时间。
关于CRC基本原理的介绍可以参考这篇专家博文:
https://blog.csdn.net/liyuanbhu/article/details/7882789
简单说,就是把输入数据除以一个给定的二进制序列,所得到的余数就是CRC码。例如:
在这里插入图片描述
图中的remainder:1110就是输入数据1101011011除以给定序列(专业名称叫:生成多项式)10011后所得到的CRC校验码,它应该附在输入数据后面形成11010110111110的最终数据。
需要注意的是,这个过程中的减法运算都是通过模2减法(也就是异或)完成的。

CRC查表法

为了获得运行效率,一般都是要使用查表法来计算CRC的,也就是预先计算出一定位数的CRC码,实际计算时,将输入数据切分成对应的位数,通过查表获得最终的CRC码。
其推导过程如下:
在给定生成多项式的情况下,CRC的计算过程其实可以通过异或(XOR)来完成。
例如:
给定输入数据 A=11001, 给定生成多项式 G=10101, 那么计算CRC校验位的过程如下:
在这里插入图片描述
所以CRC_A=(A<<5)^ (G<<5)^ (G<<4)^ (G<<3)^ (G<<2)^ (G<<1)=(A<<5)^G’
记A’=A<<5=1100100000, B=10100, B’=A’^B=1100110100
那么CRC_A=A’^G’
在来看B’的CRC计算过程:
在这里插入图片描述
所以CRC_B’=(B’<<5)^ (G<<10)^ (G<<9)^ (G<<8)^ (G<<7)^ (G<<6)^ (G<<5)^ (G<<3)^ (G<<2)^G
=(A’<<5)^ (B<<5)^ (G’<<5)^ (G<<5)^ (G<<3)^ (G<<2)^G
=((A’^ G’)<<5)^ (B<<5)^ (G<<5)^ (G<<3)^ (G<<2)^G
=(CRC_A<<5)^ (B<<5)^ (G<<5)^ (G<<3)^ (G<<2)^G
=((CRC_A^ B)<<5)^ (G<<5)^ (G<<3)^ (G<<2)^G
如果我们把CRC_A^ B看作一个新的输入数据,记做D,我们可以发现,D其实是跟A一样的一个5位数据。如果我们提前把所有5位数据的CRC结果计算出来并保存到一个表里,那么,当我们要计算B’=(A<<5)^B这个10位的输入数据的CRC的时候,只需要查找A对应的CRC结果,然后再用它和B做异或得到D,然后查D的CRC结果就是最终结果。这就是CRC的查表法。
另一个图文并茂的对CRC及查表法讲解通俗易通的博文在这里:
http://www.cnblogs.com/esestt/archive/2007/08/09/848856.html

OAI中的代码实现

接下来我们来看在OAI的5G代码中用到的CRC table及其生成过程。
5G中CRC table的生成是通过crcTableInit (void)函数实现的。它的调用关系如下:

Created with Raphaël 2.2.0 init_nr_ue_vars() init_nr_ue_signal() phy_init_nr_top() crcTableInit()

没错,这个init_nr_ue_vars()函数就是UE的主函数中调用的那个。也就是说CRC table的初始化也是伴随主函数中的初始化函数完成的。
在介绍这个CRC的实现函数之前,先看一下协议中对于CRC生成多项式的定义。3G,4G,5G的CRC生成多项式略有不同,具体定义如下:

/*ref 36-212 v8.6.0 , pp 8-9 */
/* the highest degree is set by default */
//38.212 5.1中定义了CRC24A,CRC24B,CRC24C,CRC16,CRC11,CRC6等6种生成多项式。
//36.212 5.1.1定义了CRC24A,CRC24B,CRC16,CRC8共4中生成多项式
//25.212 4.2.1.1定义了CRC24,CRC16,CRC12,CRC8共4中生成多项式
//为方便计算,生成多项式已经去掉了最高位,所得到的即为一位除法的余数,例如:poly24a=0x100000000^0x1864cfb00
unsigned int             poly24a = 0x864cfb00;   // 1000 0110 0100 1100 1111 1011
												 // D^24 + D^23 + D^18 + D^17 + D^14 + D^11 + D^10 + D^7 + D^6 + D^5 + D^4 + D^3 + D + 1
unsigned int             poly24b = 0x80006300;   // 1000 0000 0000 0000 0110 0011
											     // D^24 + D^23 + D^6 + D^5 + D + 1
unsigned int             poly24c = 0xb2b11700;   // 1011 0010 1011 0001 0001 0111
												 // D^24+D^23+D^21+D^20+D^17+D^15+D^13+D^12+D^8+D^4+D^2+D+1

unsigned int             poly16 = 0x10210000;    // 0001 0000 0010 0001            D^16 + D^12 + D^5 + 1
unsigned int             poly12 = 0x80F00000;    // 1000 0000 1111                 D^12 + D^11 + D^3 + D^2 + D + 1
unsigned int             poly8 = 0x9B000000;     // 1001 1011                      D^8  + D^7  + D^4 + D^3 + D + 1
uint32_t poly6 = 0x84000000; // 10000100000... -> D^6+D^5+1
uint32_t poly11 = 0xc4200000; //11000100001000... -> D^11+D^10+D^9+D^5+1

需要注意的一点就是这里的生成多项式实际是为了便于计算而经过处理的,如注释所述。
然后,为每种不同的生成多项式分配一个table数组,如下:

/*********************************************************

crc table initialization

*********************************************************/
static unsigned int        crc24aTable[256];    //所有crc table的大小都定义为256,也就是2^8,说明这是一个8位数据的table
static unsigned int        crc24bTable[256];    //里面保存了所有8位数据的CRC结果
static unsigned int        crc24cTable[256];
static unsigned short      crc16Table[256];
static unsigned short      crc12Table[256];
static unsigned char       crc8Table[256];

接下来我们进入到函数主体:

void crcTableInit (void)
{
  unsigned char c = 0;   //定义一个无符号字符变量,因为占用1个字节,因此它的最大值只能是255

  do {
    crc24aTable[c] = crcbit (&c, 1, poly24a);
    crc24bTable[c] = crcbit (&c, 1, poly24b);
    crc24cTable[c] = crcbit (&c, 1, poly24c);
    crc16Table[c] = (unsigned short) (crcbit (&c, 1, poly16) >> 16);
    crc12Table[c] = (unsigned short) (crcbit (&c, 1, poly12) >> 16);
    crc8Table[c] = (unsigned char) (crcbit (&c, 1, poly8) >> 24);
  } while (++c);   //从0到255循环,为每一个crc table填写00000000~11111111的crc校验码
}

我们发现,crc table中真正的crc校验码其实是在另一个叫crcbit()的函数中完成的。
crcbit()是一个逐位计算crc校验码的实现函数,它根据输入参数不同,每次计算一个8位数据的对应生成多项式的crc校验码。

/*********************************************************

For initialization && verification purposes,
   bit by bit implementation with any polynomial

The first bit is in the MSB of each byte

*********************************************************/
unsigned int crcbit (unsigned char * inputptr,    //crc按位计算实现
		     int octetlen,
		     unsigned int poly)
{
  unsigned int i, crc = 0, c;   //i,循环变量; crc,用来存储上一位计算的余数; c,是从外部输入的需要生成校验码的数据。

  while (octetlen-- > 0) {    //octetlen表示需要计算的输入数据的个数,因为是1, 所以这个循环只执行一次,也就是每次计算一个数据的crc
    c = (*inputptr++) << 24;   //因为生成多项式最大是24位的,因此输入数据左移24位,补零

    for (i = 8; i != 0; i--) {   //计算8位数据的crc,每次计算1bit
      if ((1 << 31) & (c ^ crc))   //如果c的最高位,也就是当前计算的数据位和crc的最高位,也就是上一位计算的余数的最高位其中之一为1
        crc = (crc << 1) ^ poly;   //上一位计算的余数乘2(crc<<1)再加上当前位计算的余数(poly)(模2加法,即异或)
      else
        crc <<= 1;   //如果不满足上述条件,crc直接左移一位,进入下一位计算

      c <<= 1;   //输入数据左移一位,准备计算下一位
    }
  }

  return crc;
}

这里的crc计算方法与前面专家博文中给出的方法不太相同,这里的不容易理解,推荐另一篇解释文章:
https://wenku.baidu.com/view/9f7b55876f1aff00bed51ec0.html)

猜你喜欢

转载自blog.csdn.net/qq_44113393/article/details/89351728
5G