一、ECC原理说明
ECC(Error Correcting Code)全称为错误纠正码,用于对存储器的数据进行完整性检查和纠正,主要用在SRAM、DDR、NAND等存储器设备上。ECC可以对数据进行单比特的纠错和多比特的检错,其原理基于汉明码编码而来。下图是ECC编码的主要方法示例,其中蓝色位置为插入的ECC校验位,灰色位置为原始数据。数据下面的蓝色部分为进行奇偶校验计算的数据段。
ECC编码基于二分法原理,图中第0个(bit[0])ECC位是对全数据段(包括数据+ECC位)进行奇偶校验后的结果,填入的数值保证全数据段奇偶校验值为0。bit[16]、bit[8]、bit[4]、bit[2]和bit[1] ECC位分别对数据进行二分并对一半数据进行奇偶校验,例如bit[16]校验的是数据的后半段,即数据高16bits,并在数据最高位插入ECC校验数值。实际编码过程首先从bit[16]开始,最终完成bit[0]的全数据段奇偶校验位插入。产生的ECC位可以和数据一起放置,存储在数据高位或者按照图示位置放入,但无论ECC位放在哪里,都需要保证编码和解码的方式一致。
从上图可以看出,如果需要检测8bits原始数据,需要额外添加5bits ECC校验位;如果需要检测16bits原始数据,则只需在5bits ECC校验的基础上再添加1bit即可。也就是说,原始数据翻倍而ECC校验位加1,这样的特性使得ECC校验在数据位宽较宽时更有优势。
ECC解码过程和编码过程相反,在接收到上图所示的数据后,首先从bit[16]ECC位置进行判断,如果计算的奇偶校验值和接收的一致,则表明高16bits数据没有错误;如果不一致,则表明高16bits数据可能存在错误。再继续二分检查bit[8] ECC位,依次完成所有ECC位的计算和比较,直到bit[1] ECC位。在计算完bit[1] ECC位后,如果数据中存在单bit错误,就可以最终确定出错的数据位置在哪,并完成对应的数据纠错。但如果原始数据中存在2bit翻转或ECC位本身出错,这个就需要bit[0]的ECC位对全数据段(包括数据+ECC位)进行一次奇偶校验。如果bit[0]是判断一致(全数据段奇偶校验正确),但各个ECC位不一致,则表明置数据可能出现了2bit或以上的错误。
对于2bits以上数据位出错,那么各个ECC位判断的结果会有冲突的地方,比如全数据段奇偶校验正确,但各二分点ECC位校验不一致,或者各个ECC位校验出现不一致和冲突的地方。ECC只能纠正1bit错误,对于2bit以上错误,只能检测而无法完全定位2bit错误位置。对于一般数据的软错误而言,例如瞬态的电磁干扰,产生多bit错误的概率非常低,因此ECC在数据完整性校验上商用广泛。
目前ECC在内存中用途非常广,多数DDR内存条采用64bits数据+8bits ECC的方案,这样在做DDR颗粒时,可以使用8个1G颗粒再加1个1G颗粒组成ECC冗余。ECC在SRAM中也用途广泛,用于功能安全车载和航天级电子电路中,一般采用32bits数据+7bits ECC方案,根据具体SRAM位宽决定。在NAND/NOR Flash中,一般采用256bytes数据+22bits ECC的方案。
二、RocketChip Cache ECC配置
RISC-V RocketChip生成器带有Cache ECC选项,默认情况下是关闭状态,有三种类型的ECC校验可以选择,分别是:parity、sec、secded,其各个类型的含义:
parity:对输入Cache SRAM的数据进行奇偶校验,每8bits产生一个奇偶校验位;
sec:对输入Cache SRAM的数据进行单比特纠错,硬件自动纠错;
secded:对输入Cache SRAM的数据进行单比特纠错,多比特检错,硬件自动纠错;
除了可以选择ECC类型,DCache还可以配置纠错的数据长度,通过dataECCBytes选择每多少bits进行ECC校验,例如dataECCBytes设置成1,对于原始32bits位宽的SRAM,添加ECC后会变成52bits((8bits+5bits)x4)位宽,如果设置成4,则会变成39bits(32bits+7bits)位宽。
除了dataECCBytes参数对位宽有影响外,配置的ECC类型对位宽也有影响,这里以dataECCByte=1为例,说明不同ECC类型对应的SRAM读写数据位宽,假设原始读写位宽为32bits,则:
parity:读写位宽(8bits数据+1bit奇偶校验)x4 = 36bits;
sec:读写位宽(8bits数据+4bits ECC)x4 = 48bits;
secded:读写位宽(8bits数据+5bits ECC)x4 = 52bits;
位宽越宽,需要的SRAM大小越大,而其中有用的SRAM数据占用比例越小,如果dataECCBytes设置成1,意味着实际有效数据只占实际面积的一半。由于ICache和DCache不会太大,一般嵌入式系统16KB起步,推荐dataECCBytes配置成4。
最终配置的subsyste/Config.scala项如下所示:
dcache = Some(DCacheParams(
rowBits = site(SystemBusKey).beatBits,
nSets = 64,
nWays = 1,
nTLBSets = 1,
nTLBWays = 4,
nMSHRs = 0,
//add none/identity/parity/sec/secded/
tagECC = Some("secded"),
dataECC = Some("secded"),
dataECCBytes = 4,
blockBytes = site(CacheBlockBytes))),
icache = Some(ICacheParams(
rowBits = site(SystemBusKey).beatBits,
nSets = 64,
nWays = 1,
nTLBSets = 1,
nTLBWays = 4,
//add none/identity/parity/sec/secded/
tagECC = Some("secded"),
dataECC = Some("secded"),
blockBytes = site(CacheBlockBytes))))
三、RocketChip Cache ECC实现
在系统复位释放后,RISC-V内核会从BootROM中取出程序段,并填入ICache中,一次填入一个CacheLine的数据。加入ECC后,BootROM中的原始数据会首先添加ECC校验位,并放在数据的高位和数据一起写入ICache的SRAM中。随后,内核的指令取指模块会从ICache中取出数据,数据取出来后会首先检查ECC,保证数据的正确性,单bit的数据错误会直接纠正。
ECC校验会产生可纠正错误和不可纠正错误信号,对于多bit错误,ICache和DCache的处理方法不同。如果ECC输出端检测到不可纠正错误,ICache会通过io_resp_bits_replay信号上报内核,内核会判定为数据损坏,并会重新发出对应地址的读请求命令。如果ECC输出一直出错,内核会一直重复对该地址进行取指请求。对于DCache,如果出现不可纠正错误,则会通过io_errors_uncorrectable_valid信号上报错误,但需要注意该信号默认并没有作为中断接入RISC-V中断输入端,用户可能需要手动处理该error信号的上报事情。
四、仿真问题
在对带有ECC的Cache进行仿真时,可能会遇到ICache hang住的情况,观察波形会发现BootROM中的程序写不进ICache的SRAM中,内核一直重复从BootROM取数据但写使能一直无效,而读出来的数据一直产生不可纠正错误和disparity有效,从而导致写使能一直无效。
该问题可参考下篇链接解决,但这并不是最终的解决方法,因为SRAM中初始数值在流片后就是随机值,很难通过复位后在对SRAM进行一次全0的初始化。该问题可以通过修改生成的RTL代码解决,将不可纠正错误在上电复位后到BootROM写入第16个数之前屏蔽掉,等BootROM中写入16个数后,读出来的数据是经过ECC编码和ECC解码的,后续就不会出现仿真的问题。
https://github.com/chipsalliance/rocket-chip/issues/3019