01、引 言
循环冗余校验码,即Cyclic Redundancy Check (CRC), 是一种在各种通信系统中广泛应用的检错机制。CRC算法的工作原理和哈希函数类似,具体来说,其对任意长度的数据计算出一段唯一的标识(校验和), 然后根据这个标识来判断该数据在传输过程中是否发生变化。CRC检错码在实际生活中有着广泛的应用,诸如网络通信,存储系统等场景下都需要CRC来保证数据传输的正确性。而不同的应用场景往往需要采用不同的CRC配置参数,同时对计算的性能也有不同的需求。例如,在基于Ethernet协议的网络传输中需要采用IEEE802-3协议所规定的CRC参数,同时需要高吞吐率的CRC实现以和网络带宽相匹配。
对于一个具体的通信系统,CRC既可以通过软件编程也可以硬件电路的形态来实现。相较于网络上丰富的软件库,开源的CRC硬件实现却相对落后,尤其是面向高性能的应用场景。例如,下述链接都提供了参数可配置的CRC硬件电路生成器,但这些实现方式都是直接将CRC算法映射到组合逻辑电路上,这往往会导致较长的组合逻辑延时进而降低电路的整体工作频率,无法满足高吞吐率的需求。
- Easics:http://www.easics.com/webtools/crctool
- Outputlogic:http://outputlogic.com/
- SpinalCrypto:https://github.com/SpinalHDL/SpinalCrypto
而另外一些开源的电路实现虽然引入了流水线技术对时序进行优化,但其实现只针对某一特定的CRC配置参数,应用场景相对有限:
- Pipelined Implementation:https://bitbucket.org/spandeygit/crc32_verilog/src/master/
针对现有CRC开源硬件实现的不足,blue-crc项目基于Bluespec SystemVerilog硬件描述语言实现了一个参数可配置同时满足高吞吐率需求的CRC硬件电路生成器。blue-crc在架构和功能上的具体特点包括:
- 支持完整的CRC配置参数包括:polynominal (生成多项式),initial_value (初始CRC值),finalXor (与输出CRC值异或), reverseInput (是否翻转每个输入字节的比特顺序),reverseOutput (是否翻转输出CRC结果的比特顺序);
- 标准I/O接口: 输入端支持AxiStream协议 (位宽可配置), 输出端基于valid-ready握手机制传输CRC校验和;
- 并行计算+流水线: 每个周期可处理多个字节数据,在Xilinx xcvu9p FPGA上可达到500MHz的运行频率;
- 高吞吐率: 在256-bit输入数据总线的配置下吞吐率可达128Gb/s;
- 计算模式: 支持发送端CRC生成和接收CRC检测两种不同的计算模式
下文将分别从算法和架构两个方面介绍blue-crc背后的实现原理及其具体的使用方式。
02、算法原理
CRC计算的定义
生成CRC校验和本质上是在计算基于多项式的模2除法。将原始数据每个比特所确定的多项式除以一个通信双方规定好的生成多项式,得到的余数即为校验和。生成CRC校验值算法的详细定义如下:对于m-bit原始数据
可以表示为如下多项式:
若需要生成n-bit的CRC校验值, 则通信双方需要规定一个 (n+1)-bit的生成多项式:
则原始数据的CRC校验值为多项式A(x)乘上x^n 后除以G(x)得到的余数,具体表达式为:
生成的CRC校验值附加在原始数据的尾部后传输至接收端, 即实际发出的数据为
基于CRC的生成原理,数据接收端可以采用两种不同的方式完成校验: 1) 提取出附加在发送数据尾部的校验值后生成新的CRC校验值,将该值与提取出的进行比较,如果二者相同则表明数据在传输过程中没有出错;2)由于校验值为除法运算中得到的余数,这就意味着附加上检验值后的发送数据可被生成多项式整除,因此也可以直接对接收数据进行模2除法,若得到的余数为0则表明数据传输成功。
在基于多项式的模2算术运算中,变量的取值范围只有0和1,且运算过程不存在进位或借位。因此,加减法都可以视为异或运算, 而乘除法可以由加减法构成,因此实际上也等同于一系列的异或运算。下图展示了一个具体的模2除法取余数计算CRC校验和的例子,其中原始数据为101001,生成多项式为1101。由该例子可见,模2除法的运算法则和普通的除法类似,不同点在于模2除法用异或操作替换了减法且不存在借位。
从另外一个角度理解,模二除法取余数的过程实际上是逐位地对原始数据进行缩减: 当输入数据的最高位为1时,原始数据和除数左对齐异或后移除最高位,得到下一轮缩减的输入;当最高位为0时,直接移除最高位得到下一轮缩减的输入。不断重复上述缩减步骤直至输入数据的位宽小于除数位宽,该输入值即为所求余数。根据上述取模步骤,可以很容易地将CRC算法映射到基于线性负反馈移位寄存器 (LFSR) 结构的硬件上,一种具体的实现如下图所示, 其对应的生成多项式为11011。原始数据从最高位开始逐位从Din输入,当所有数据都传入后,寄存器中的值即为所要求的校验和。
上图展示的电路结构是一种非常经典的CRC算法的硬件实现方式,其可以用极小的面积和功耗生成校验值,但由于每个周期只能处理1-bit输入数据,其很难达到较高的吞吐率。为了提升CRC校验电路的计算性能,很多面向硬件的CRC并行算法被提出。blue-crc项目的实现主要参考了论文[1]和[2]中提出的并行算法和电路架构, 并在此基础上进一步优化流水线结构以提升工作频率,同时提供了参数可配置的设计以及标准的输入输出接口。
并行算法
blue-crc项目所采用的并行算法主要是建立在模2除法(取余)运算的两个定理之上,这两个定理分别是(备注:下文所列公式中的算术运算都是在模2域下完成,即加减法都等同于异或运算):
- 若多项式
则
由定理1可知, 对于任意长度的输入数据, 都可以将其拆分成若干小段,每小段数据的CRC校验值可以独立地并行计算,然后通过异或操作将所有校验值累加在一起,即可得到完整数据对应的CRC校验值。在blue-crc的实现中,每小段数据Ai(x)的长度为8-bit, 设原始数据A(x)的总长度为8n-bit, 则:
根据定理1,我们可以并行地计算每个输入字节Ai(x)的校验值
,然后将每个字节对应的校验和累加在一起就可得到完整数据的校验和,即
对于每个输入字节的CRC校验和的计算,除了依照算法实现对应的硬件电路, 另一种更加高效的方式是: 提前计算好8-bit数所有可能值对应的CRC输出并制作成硬件查找表,当电路运行时,以输入数据为地址从查找表中取出对应的表项即可得到校验和。若输入原始数据有n个字节,则需要制作n张长度为
的查找表,其中第
张查找表的第x个表项的值即为
的CRC校验和。
虽然有了定理1我们可以在一个周期内并行处理多个字节数据,但基于此还不能够完成CRC的硬件实现。在实际电路中数据总线的位宽是有限的,对于较长的输入数据,需要根据总线位宽将其分成多个帧并分配到多个周期进行传输。因此,我们还需要基于定理2累加不同周期计算得到的CRC校验值进而获得最终结果。在blue-crc的实现中,数据以大端字节序进行传输,即高位数据先传入进行处理, 假设输入数据总线位宽为256-bit, 当前周期输入数据对应的多项式为A(x), 该周期之前已经输入的数据为A'(x), 每个周期我们除了计算CRC[A(x)],还需要将该值累加到已经计算好的中间校验和CRC[A'(x)]上,得到数据
的校验和。根据定理1和2,可以推导出累加的计算公式如下:
即需要将中间校验和CRC[A'(x)]左移256-bit,对其再次计算CRC校验值后和CRC[A(x)]相加。同样我们可以通过硬件查找表的方式完成这里校验和的计算。
实际CRC的计算中原始数据的长度并不一定都是256-bit的整数倍,因此在处理最后一帧输入时不能直接使用上面的公式进行累加。我们需要动态地计算每个数据包最后一帧的有效数据的宽度,设宽度为m, 则可以采用如下公式进行累加:
03、电路架构与性能
架构设计
基于上述并行算法,CRC硬件电路的架构设计如下图所示。为了提升吞吐率,电路设计时将CRC算法需要的组合逻辑实现划分成了八级单向传递的流水线, 其中前五级流水线计算每个周期输入数据的CRC校验和并累加到CRC中间值上,后三级流水线用于处理最后一帧数据非对齐(数据位宽非总线位宽的整数倍)情况下CRC校验值的累加。每一级流水线的完成的具体功能如下:
- Stage-1: 对AXI-Stream总线输入数据进行预处理,包括大小端转换、根据CRC配置参数reverse_input调换比特顺序、根据AXI-Stream总线tkeep字段计算数据总线上有效的字节数(用于处理数据长度非总线位宽整数倍的情况);
- Stage-2: 对于数据长度非总线位宽整数倍的情况,需要对最后一帧数据进行移位和下一级的查找表对齐;
- Stage-3: 从查找表中取出输入数据每个字节对应的CRC校验值;
- Stage-4: 通过树状结构将各个字节的CRC值进行异或合并得到该周期输入数据对应的CRC校验和;
- Stage-5: 根据上文提到的定理2,将输入数据CRC校验和累加到中间校验和之上。最后一帧输入数据(有效数据位宽可能小于总线位宽)需要特殊的处理,因此不在该流水级直接进行累加,需要将中间CRC值和上一级传来的CRC校验和传递到后续流水级进行处理;
- Stage-6: 根据Stage-1中计算的最后一帧数据的实际有效位宽对中间CRC值进行移位和下一级的查找表对齐;
- Stage-7: 从查找表中取出中间CRC值移位后对应的校验和;
- Stage-8: 将上一级的查找表输出和Stage-5传递来的最后一帧输入数据的CRC校验和通过树状结构进行异或合并得到全部数据的校验和
性能与面积
CRC硬件电路的实际性能和资源开销与具体的配置参数有关。大部分情况下,硬件电路的吞吐率随输入总线数据位宽增大而提升,硬件资源开销则同时和总线宽度以及CRC校验和宽度有关。以IEEE 802-3协议规定的32位CRC校验和为例,其在256位输入总线位宽的配置下,可在Xilinx xcvu9p FPGA器件上达到500MHz的工作频率,总吞吐率达128Gb/s,实际的硬件资源开销如下。
相较于其他硬件实现方式,blue-crc主要关注于计算性能上的提升,因此在硬件资源上的开销相对较大。其中最主要的开销来源于用于实现CRC计算的查找表,其容量大小随数据总线位宽以及校验和位宽的增大而增大,具体的查找表容量的计算方式如下(设总线字节宽度为m, CRC校验和字节宽度为n):
对于上文提到的IEEE 802-3协议规定的 32-bit CRC校验,在256-bit输入总线位宽的配置下,理论上所需的查找表容量为36KB.
04、使用指南
本文的最后一部分将介绍blue-crc项目的使用指南,包括CRC电路的配置参数、输入输出接口、面向BlueSpec SystemVerilog的使用接口,以及面向Verilog的使用接口。
配置参数
在blue-crc中,CRC硬件电路完整的配置参数如下表所示:
| 名称 | 含义 | 限制 |
| crc_width | CRC校验和的宽度 | 目前只支持8-bit整数倍的校验和宽度 |
| axi\_keep\_width | AXI-Stream总线tkeep字段宽度, 即数据字段tdata的字节宽度 | / |
| polynomial | CRC生成多项式 | 不能超过crc_width决定的取值范围 |
| init_value | CRC初始值 | 不能超过crc_width决定的取值范围 |
| final_xor | 与输出校验和相异或的值 | 不能超过crc_width决定的取值范围 |
| reverse_input | 是否翻转每个输入字节的比特顺序 | 两种取值: 翻转/不翻转 |
| reverse_output | 是否翻转输出CRC校验值的比特顺序 | 两种取值: 翻转/不翻转 |
| mem\_file\_prefix | 查找表文件名前缀 | / |
| crc_mode | CRC计算模式 | 两种可选模式:(1) SEND:自动在输入原始数据尾部补零后进行除法取余, 通常用于发送端生成CRC校验和;(2)RECV:直接计算输入原始数据除法取余的结果,通常用于接收端校验; |
输入输出接口
在blue-crc项目中,CRC硬件电路基于AXI-Stream总线协议接收上游模块传入的数据,校验和输出端口采用valid-ready握手机制和下游模块进行交互。电路顶层模块生成的Verilog端口如下:
module mkCrcRawAxiStreamCustom(
input CLK,
input RST_N,
input s_axis_tvalid,
input s_axis_tdata,
input s_axis_tkeep,
input s_axis_tlast,
input s_axis_tuser,
output s_axis_tready,
output m_crc_stream_data,
output m_crc_stream_valid,
input m_crc_stream_ready
);
发起CRC计算时原始数据需要按照大端字节序进行传输,即高位字节需要优先传输。假设CRC电路输入AXI-Stream总线数据位宽为32-bit (4-byte), 若要传输80-bit (10-byte)的数据,那么每一帧需要传输的内容如下图所示:
BSV使用接口
blue-crc项目基于Bluespec SystemVerilog硬件描述语言实现,因此对于使用BSV的设计者,可以直接通过实例化的方式使用CRC模块。详细的使用步骤如下:
- 获取blue-crc源码: blue-crc使用了blue-wrapper项目提供的AXI-Stream接口,克隆时需要加上--recursive选项获得这部分代码:
git clone --recursive https://github.com/datenlord/blue-crc.git
2. 在代码中导入所需模块:
import CrcDefines :: *;
import CrcAxiStream :: *;
import AxiStreamTypes :: *;
3. 确定CRC配置参数:CrcConfig结构体封装了电路的各项配置参数, CrcConfig结构体的定义如下, 其中每个字段的具体含义可参考前文列出的表格。其中,revInput和revOutput字段为IsReverseBitOrder枚举类型,可选取值包括BIT\_ORDER\_REVERSE 和 BIT\_ORDER\_NOT\_REVERSE; crcMode字段为CrcMode枚举类型, 可选取值包括CRC\_MODE\_RECV和CRC\_MODE_SEND。
typedef struct {
Bit#(crcWidth) polynominal;
Bit#(crcWidth) initVal;
Bit#(crcWidth) finalXor;
IsReverseBitOrder revInput;
IsReverseBitOrder revOutput;
String memFilePrefix;
CrcMode crcMode;
} CrcConfig#(numeric type crcWidth) deriving(Eq, FShow);
typedef enum {
CRC_MODE_RECV,
CRC_MODE_SEND
} CrcMode deriving(Eq, FShow);
typedef enum {
BIT_ORDER_REVERSE,
BIT_ORDER_NOT_REVERSE
} IsReverseBitOrder deriving(Eq, FShow);
4. 实例化mkCrcAxiStream模块: 顶层模块接口CrcAxiStream的定义如下,数据的输入和输出分别由Get和Put接口实现
typedef Bit#(width) CrcResult#(numeric type width);
typedef Get#(CrcResult#(crcWidth)) CrcResultGet#(numeric type crcWidth);
typedef Put#(AxiStream#(keepWidth, AXIS_USER_WIDTH)) AxiStreamPut#(numeric type keepWidth);
interface CrcAxiStream#(numeric type crcWidth, numeric type axiKeepWidth);
interface AxiStreamPut#(axiKeepWidth) crcReq;
interface CrcResultGet#(crcWidth) crcResp;
endinterface
以IEEE 802-3协议中规定的32-bit CRC为例,若要实现输入位宽为256-bit的CRC校验值生成电路,则实例化码如下:
CrcConfig#(32) conf = CrcConfig {
polynominal: 32'h04C11DB7,
initVal : 32'hFFFFFFFF,
finalXor : 32'hFFFFFFFF,
revInput : BIT_ORDER_REVERSE,
revOutput : BIT_ORDER_REVERSE,
memFilePrefix: "mem_tab",
crcMode: CRC_MODE_SEND
};
CrcAxiStream#(32, 256) crc <- mkCrcAxiStream(conf);
5. 生成查找表文件: 查找表文件的生成脚本为scripts/gen\_crc\_tab.py, 使用该脚本前需要先配置.json格式的CRC配置文件,该文件的内容需要和BSV代码中的配置一致:
{
"crc_width": 32,
"axi_keep_width": 32,
"polynomial": "0x04C11DB7",
"init_value": "0xFFFFFFFF",
"final_xor": "0xFFFFFFFF",
"reverse_input": true,
"reverse_output": true,
"mem_file_prefix": "crc_tab",
"crc_mode": "CRC_MODE_SEND"
}
配置好.json文件后,使用python执行该脚本(需要传入.json配置文件路径和输出文件路径):
python3 gen_crc_tab.py JSON文件路径 文件输出路径
6. 编译项目时需要在编译选项中加入blue-crc源代码的路径, 假设blue-crc的根目录为$(ROOT), 则需要在编译选项中加上:
bsc -p +:$(BLUE_CRC)/src:$(ROOT)/lib/blue-wrapper/src
Verilog使用接口
虽然blue-crc项目基于BSV实现,但同时也提供了生成可配的Verilog代码的脚本scripts/gen_crc.py。该脚本需要在blue-crc项目的根目录下执行,同时需要传入.json格式的CRC配置文件(具体文件格式见上文):
python3 scripts/gen_crc.py JSON配置文件 [Verilog生成路径] [查找表生成路径]
若在执行脚本时没有配置Verilog代码和查找表文件生成路径,那么这些文件会默认生成到根目录下的verilog文件夹。生成Verilog代码需要使用到BSV编译器,因此在执行脚本前还需要保证已经安装并配置好该编译工具。
欢迎大家关注和支持blue-crc项目,GitHub仓库地址:https://github.com/datenlord/blue-crc
05、引用和链接
[1] Y. Sun and M. S. Kim, "A Table-Based Algorithm for Pipelined CRC Calculation," 2010 IEEE International Conference on Communications, Cape Town, South Africa, 2010, pp. 1-5, doi: 10.1109/ICC.2010.5501903.
[2] Sun Y, Kim M S. A pipelined CRC calculation using lookup tables[C]//2010 7th IEEE Consumer Communications and Networking Conference. IEEE, 2010: 1-2.
06、往期推荐
计算机体系结构 l MIT Training Q&A及小贴士
达坦科技(DatenLord)专注下一代云计算——“天空计算”的基础设施技术,致力于拓宽云计算的边界。达坦科技打造的新一代开源跨云存储平台DatenLord,通过软硬件深度融合的方式打通云间壁垒,实现数据高效跨云访问,建立海量异地、异构数据的统一存储访问机制,为云上应用提供高性能安全存储支持。以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。
公众号:达坦科技DatenLord
DatenLord官网:www.datenlord.io
知乎账号:
https://www.zhihu.com/org/da-...
B站: