2、ECC纠错码基础:汉明码原理、奇偶校验、单比特纠错与双比特检测(SECDED)
各位好,我是老张。在存储芯片领域摸爬滚打了十几年,今天咱们来聊聊ECC纠错码。说实话,这玩意儿是存储芯片的「保命符」。没有它,你存进去的数据可能读出来就变了样。我当年刚入行时,就吃过这个亏——一块NAND Flash,读出来的数据偶尔会跳几个bit,排查了三天才发现是没开ECC。
好,咱们直接进入正题。
2.1 奇偶校验:最简单的保护
奇偶校验,说白了就是给数据加一个「校验位」。比如你存一个字节0x55(二进制01010101),我数一下里面1的个数——是4个,偶数。那校验位就设成0。如果1的个数是奇数,校验位就设成1。这叫「偶校验」。反过来也行,叫「奇校验」。
我习惯用偶校验,因为硬件实现起来更简单。你想想看,一个异或门就搞定了。
奇偶校验的致命缺陷:它只能检测奇数个bit的错误。如果两个bit同时翻转,校验位还是对的,你就完全不知道数据已经坏了。
我在项目中遇到过一件事:一块SRAM芯片,偶尔出现双bit错误,奇偶校验完全没反应。后来换了汉明码才解决问题。所以,奇偶校验只适合对可靠性要求不高的场景,比如一些控制寄存器。
2.2 汉明码原理:从「冗余」到「纠错」
汉明码是Richard Hamming在1950年提出的。它的核心思想很简单:多放几个校验位,每个校验位管一组数据位。这样,当某个bit出错时,多个校验位会同时报错,通过报错组合就能定位到具体是哪个bit坏了。
嗯,这里要注意:校验位的数量不是随便定的。它和数据位长度有关系。公式是:
2^k ≥ m + k + 1
其中k是校验位数量,m是数据位数量。比如你要保护8个数据位,那k至少是4(因为2^4=16 ≥ 8+4+1=13)。
我个人习惯用(12,8)汉明码——8个数据位,4个校验位。为什么?因为8位刚好是一个字节,在存储芯片里最常见。
2.3 校验位的位置:2的幂次方
汉明码的校验位放在2的幂次方位置上,也就是第1、2、4、8、16...位。数据位填在剩下的位置。比如(12,8)汉明码,12个bit的排列是这样的:
| 位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 类型 | P1 | P2 | D1 | P3 | D2 | D3 | D4 | P4 | D5 | D6 | D7 | D8 |
P1、P2、P3、P4是校验位,D1~D8是数据位。为什么这么放?因为每个校验位只覆盖那些「位置编号的二进制表示中,对应位为1」的数据位。比如P1(位置1,二进制0001)覆盖所有位置编号最低位为1的位:位置1、3、5、7、9、11。P2(位置2,二进制0010)覆盖所有位置编号第二位为1的位:位置2、3、6、7、10、11。以此类推。
你想想看,这种设计有什么好处?如果某个bit出错,比如位置5坏了,那P1和P3会同时报错(因为5的二进制是0101,最低位和第三位是1)。通过报错组合,你就能精确知道是位置5出了问题。
2.4 单比特纠错(SEC)的实现
单比特纠错,英文叫Single Error Correction,简称SEC。实现起来分三步:
- 写入时:计算校验位,和数据一起存进去。
- 读出时:重新计算校验位,和存储的校验位做异或,得到「校正子」(Syndrome)。
- 纠错:如果校正子不为0,它的值就是出错bit的位置编号。直接翻转那个bit就行。
举个例子。假设我们存的数据是0x55(二进制01010101),对应的汉明码编码结果如下:
数据位:D1=0, D2=1, D3=0, D4=1, D5=0, D6=1, D7=0, D8=1
校验位计算:
P1 = D1 ⊕ D2 ⊕ D4 ⊕ D5 ⊕ D7 = 0⊕1⊕1⊕0⊕0 = 0
P2 = D1 ⊕ D3 ⊕ D4 ⊕ D6 ⊕ D7 = 0⊕0⊕1⊕1⊕0 = 0
P3 = D2 ⊕ D3 ⊕ D4 ⊕ D8 = 1⊕0⊕1⊕1 = 1
P4 = D5 ⊕ D6 ⊕ D7 ⊕ D8 = 0⊕1⊕0⊕1 = 0
最终编码:P1=0, P2=0, D1=0, P3=1, D2=1, D3=0, D4=1, P4=0, D5=0, D6=1, D7=0, D8=1
即:0011 0100 0101
读出时,如果数据没变,校正子就是0。如果某个bit翻转了,比如D3从0变成1,那重新计算的校验位会变,校正子就是出错位置。直接翻转回来就行。
我的经验:在实际芯片中,校正子的计算是用组合逻辑实现的,一个时钟周期就能出结果。但要注意,如果校正子指向的是校验位本身,那说明校验位出错了——这种情况不需要纠错,因为数据是对的,只需要把校验位修正就行。
2.5 双比特检测(DED):从SEC到SECDED
单比特纠错够用吗?说实话,在大多数场景下不够。因为存储芯片里,双比特错误并不罕见。比如宇宙射线打中一个存储单元,可能同时影响相邻的两个bit。或者芯片老化后,某个字线出问题,也可能导致多个bit同时出错。
所以,我们需要在SEC的基础上再加一个功能:检测双比特错误。这就是SECDED——Single Error Correction, Double Error Detection。
怎么做?很简单:在汉明码的基础上,再加一个全局奇偶校验位。这个校验位覆盖所有数据位和原有的校验位。
我举个例子。还是(12,8)汉明码,加上全局奇偶位后变成(13,8)码。读出时:
- 如果校正子为0,且全局奇偶校验正确:数据没问题。
- 如果校正子不为0,且全局奇偶校验错误:单比特错误,可以纠正。
- 如果校正子不为0,但全局奇偶校验正确:双比特错误,无法纠正,只能报错。
- 如果校正子为0,但全局奇偶校验错误:奇偶校验位本身出错了,忽略。
注意:SECDED只能检测双比特错误,不能纠正。如果出现三比特或更多错误,它可能会误判。不过在实际应用中,三比特错误的概率极低,SECDED已经足够用了。
我曾经在一个项目中,客户要求必须支持SECDED。我们用的是(39,32)码——32个数据位,7个汉明校验位,再加1个全局奇偶位。这样每个64位数据块(两个32位字)共享一组校验位,面积和功耗都控制得很好。
2.6 硬件实现要点
说完了原理,咱们聊聊硬件实现。我总结了几点经验:
- 校验位计算用异或树:不要用循环,用组合逻辑的异或树。一个32位的校验位计算,三级异或门就够了。
- 校正子解码用查找表:校正子到出错位置的映射,用ROM或者逻辑门实现。我习惯用case语句综合,面积小。
- 纠错操作要小心:翻转bit的操作,一定要在数据被使用之前完成。否则你纠了错,但CPU已经读到了错误数据,那就白干了。
- 流水线设计:如果时钟频率高,可以把校验位计算和纠错分成两个流水级。第一级算校正子,第二级纠错。这样不会影响关键路径。
嗯,这里还要提一句:有些芯片会把ECC和BIST(内建自测试)结合起来。在测试模式下,ECC可以故意注入错误,验证纠错逻辑是否正常工作。这个我在后面的章节会详细讲。
2.7 性能与面积权衡
最后,咱们聊聊权衡。ECC不是免费的,它要付出代价:
| 参数 | 无ECC | SEC (7,4)码 | SECDED (8,4)码 | SECDED (13,8)码 | SECDED (39,32)码 |
|---|---|---|---|---|---|
| 冗余率 | 0% | 75% | 100% | 62.5% | 21.9% |
| 纠错能力 | 无 | 单比特 | 单比特纠错+双比特检测 | 单比特纠错+双比特检测 | 单比特纠错+双比特检测 |
| 面积开销 | 1x | ~1.5x | ~1.8x | ~1.4x | ~1.15x |
| 延迟 | 0 | 1-2周期 | 1-2周期 | 1-2周期 | 2-3周期 |
你看,数据位越宽,冗余率越低。所以大位宽的ECC更划算。我建议在存储芯片设计中,尽量用32位或64位的ECC,而不是8位的。面积效率高得多。
好了,这一章就到这里。ECC纠错码是存储芯片数据完整性的基石。理解了汉明码和SECDED,后面的BCH码、RS码、LDPC码就好学了。下一章咱们聊聊更高级的纠错码——BCH码,它在NAND Flash里用得特别多。
有什么问题,欢迎交流。我是老张,咱们下章见。