ECC 与 LDPC 纠错引擎 · 技术探讨
编制日期 2026-10-14 | 版本号 Rev 1.0
从错误来源到译码落地:确定性代数码与迭代图码的工程取舍
结论:纠错码的选择不是「谁更先进」,而是「谁更适合这条信道的噪声形态」。 BCH 用代数结构换取确定的译码结果与可证明的失败判据, LDPC 用稀疏随机图换取逼近极限的性能, 而 LDPC 的这部分性能只有在软判决喂进来时才真正存在。 两者真正的分水岭不是性能数字,而是确定性对概率性。
报告总结
存储介质的原始误码率从来不是零,并且随着磨损、温度与单元状态漂移持续变化。纠错码是把 「不可接受的原始误码率」翻译成「可接受的最终误码率」的唯一手段。但工程上真正消耗时间的,往往不是选出码型, 而是回答后面这一串问题:译码失败之后怎么办、冗余数据放在哪里、元数据自己掉电了怎么办、软判决到底要付多少代价。
这份文档沿一条判断链展开:错误从哪来 → 信道模型长什么样 → 码怎么构造 → 译码怎么走 → 工程上怎么取舍。 BCH 与 LDPC 都会被放回这条链里比较,而不是被当成两个互代的方案。附录给出码型速查、选型清单与排查决策树,可以在正文读完后直接当手册用。
读者路径
1. 纠错码在存储系统中的位置
纠错码在存储系统里不是可选的增值功能,而是数据完整性得以成立的前提条件。这一章先把它的位置说清楚:纠错发生在链路的哪一步、它替代了什么、它又付出了什么。
1.1 从原始错误率到可纠正错误率
存储介质给出的原始误码率描述的是「一个比特在被读出时被翻转的概率」。这个数字本身没有意义,因为它描述的是纠错之前的信道。真正决定产品可用性的是纠错之后的最终误码率:数据被写到介质、经历任意长的时间、以任意合法的使用方式被读回,最终交给上层的比特仍然正确的概率。
这两者之间的差距,就是纠错码要填的坑。差距越大,需要的冗余越多;冗余越多,面积、带宽、功耗的开销越大。整条判断链的第一环因此是:把问题定义成「原始误码率 → 目标最终误码率」的转换问题,而不是「选一个最强的码」。
| 层级指标 | 含义 | 由什么决定 | 工程注意 |
|---|---|---|---|
| 原始误码率 BER_raw | 信道直接输出中错误比特的占比 | 介质工艺、单元状态、磨损、温度 | 随时间缓慢劣化,无法直接测量,只能统计估计 |
| 目标最终误码率 | 交给上层的比特中错误比特的占比 | 产品数据完整性要求反推 | 硬约束,超出即判设计不成立 |
| 纠错增益 | 两者之比,通常以数量级计 | 码型、冗余度、噪声形态共同决定 | 软判决下显著高于硬判决,见第 7 章 |
| 可纠正错误数 t | 单个码字内可保证纠正的比特数上限 | 由最小距离 d 决定,上限为 (d-1)/2 | 是一个确定值,不是概率 |
纠错码不改变信道的物理误码率,它只改变「错误能否被还原」。当错误数超过码的纠正能力时,译码要么失败、要么误纠正 —— 两种结果都比不纠错更难排查。这就是为什么第 9 章要专门讨论阈值之外的行为。
1.2 裸流数据为什么不能直接用
所谓裸流,指的是没有任何附加保护的原始比特序列。主机把数据写进去,介质原样保存;主机再读回来,介质原样吐出。裸流在实验室里永远是对的,因为它没有经历真实的噪声、磨损与掉电。
问题在于:介质一定会出错,而裸流没有能力表达「我出错了」。一个被翻转的比特从介质里读出来时,和一个正确的比特在电气上、时序上、协议上完全没有区别。上层拿到的不是「错误的数据」,而是「看起来完全正常的数据」—— 这就是静默数据损坏。静默损坏之所以是最难对付的一类故障,正是因为它不会触发任何异常。
纠错码做的事情,本质上就是给数据附加一个可以被校验的结构。有了这个结构,接收端才有能力判断「收到的数据是否自洽」,才谈得上「把它改回去」。换句话说,纠错码是可验证性的来源,而不只是「冗余」。
系统里所有「报错」路径都有日志、有告警、有重试;而静默损坏走的是正常路径,没有任何一处会记录异常。等到上层应用发现数据不对时,错误可能已经在介质里存在了数月,受影响的范围是所有在这段时间读过这块介质的设备。
1.3 纠错码在存储栈中的层级位置
把纠错码放进完整的存储栈里看,它的位置是相当明确的:在控制器内部、靠近介质那一侧。往上的每一层 —— 文件系统、协议栈、应用 —— 都不具备也不需要具备介质级的错误知识;往下的每一层 —— 阵列、单元、感测放大器 —— 只负责把模拟量忠实地搬出来,不做任何判断。
这个位置不是随意选的。把纠错放在控制器里,有三个直接的好处:第一,错误信息在最短路径上被消费掉,主机侧不需要感知介质噪声;第二,纠错可以与坏块管理、磨损均衡、重映射共享同一份状态信息,纠错计数天然就是重映射的输入;第三,纠错逻辑与具体的接口协议解耦,同一套引擎可以服务不同的上位接口。
但这个位置也带来一个后果:当纠错在控制器内部发生时,主机侧是看不见的。这既是优点(透明)也是风险(无法自行判断健康度)。第 11 章会讲三条产品线在这个位置上的差异,第 12 章会讲怎么把控制器内部的计数暴露出来。
1.4 硬判决与软判决的分工
判决是把模拟量变成数字的过程。读出电压、参考电压、感测放大器的输出,最终都要变成 0 或 1 才能进入数字域。判决方式决定了有多少信息被保留下来:
- 硬判决:与一个判决门限比较,输出 1 个比特。信息量最小,电路最省,延迟最低。
- 软判决:输出该比特「有多大概率是 1」,通常是量化后的多比特对数似然比。信息量最大,电路与数据通路都更贵。
这个选择之所以关键,是因为译码器需要知道的恰恰不是「你是 1 还是 0」,而是「你有多确定自己是 1」。一个刚好落在门限附近的比特和一个稳稳落在分布中心的比特,即使硬判决结果相同,它们对译码的价值天差地别。硬判决把这个差别直接抹掉了。
由此可以推出一个贯穿全文的结论:软判决是 LDPC 性能的前提,而不是可选项。把硬判决的比特喂给 LDPC,等于在译码器门口就把大部分信息扔掉了,得到的性能会退化到接近码的代数性能,而不是它的实际性能。这也是第 7 章要专门用一整章讨论的原因。
| 维度 | 硬判决 | 软判决 | 谁受影响 |
|---|---|---|---|
| 输出形式 | 1 bit(0 或 1) | 多比特量化 LLR | 译码器输入接口 |
| 保留的信息 | 只有判决结果 | 结果 + 置信度 | 译码增益 |
| 判决电路 | 一个比较器 | 多电平比较或 ADC + 量化 | 模拟前端与版图 |
| 数据通路 | 1 bit / 比特 | 数 bit / 比特,典型 4~6 | 缓存、总线、译码器输入 |
| 读出次数 | 一次 | 常需多次采样或更长的驻留时间 | 读出时序与吞吐 |
| BCH 的收益 | 有限,BCH 在硬判决下已接近其代数性能 | 有一定提升 | 第 8 章 8.1 |
| LDPC 的收益 | 损失大部分,接近代数性能 | 提升数个数量级 | 第 8 章 8.1 |
1.5 冗余度的代价:面积、带宽、功耗
冗余度就是「校验位占整个码字的比例」。它不是免费的:每一份冗余都要在三个方向上付出代价。这也是为什么「多加校验位」从来不是一个可以顺手做的决定。
- 面积:冗余位本身要占据存储容量,译码器也要为它付出逻辑。这部分面积在低并行度的接口上是隐形的,但在带宽受限的接口上会直接挤占有效载荷。
- 带宽:写入时要把数据位与校验位一起送出去,读出时要把两者一起取回来。如果软判决量化位宽较高,冗余本身还会被放大 —— 一个 4 bit 量化的冗余位,实际占用是硬判决冗余位的四倍。
- 功耗:冗余位不只需要被写入和读出,还要被译码器反复运算。在迭代译码里,冗余度直接决定每轮迭代要触碰多少条边,这是功耗的主要来源之一。
把这三张账单放在一起看,就能理解为什么「刚好够用」的冗余是最优解。冗余不足则无法把最终误码率压到目标以下;冗余过剩则白白消耗面积、带宽与功耗,而且会增加校验子本身的出错概率(更大的码字意味着更多可能被翻转的位)。这一点在第 10 章讨论备用区预算时会再次出现。
| 账单 | 谁在付 | 怎么衡量 | 过度冗余的后果 |
|---|---|---|---|
| 面积 | 芯片设计方 | 译码器逻辑规模 + 介质上的冗余位 | 译码器过大、时钟难以收敛、介质有效容量下降 |
| 带宽 | 接口与控制器 | 每次读写传输的总比特数 | 有效吞吐下降、读出时间拉长 |
| 功耗 | 芯片设计方 | 每次访问的操作次数 | 写放大与读延迟上升,电池场景代价明显 |
| 可靠性 | 介质 | 冗余位自身的翻转概率 | 码字变大,误纠正概率不降反升 |
1.6 本文要回答的问题与主线
全文收敛到一条判断链:错误从哪来 → 信道模型长什么样 → 码怎么构造 → 译码怎么走 → 工程上怎么取舍。每一章都会回扣这条链上的某一环,附录则把可以直接查用的部分抽出来。
按这条链,有六个问题必须在全文结束时能回答:
- 纠错码在存储系统里到底解决什么问题,它不解决什么?
- 错误是怎么产生的,哪些错误可以靠重读解决,哪些不能?
- BCH 的代数结构为什么能给出确定的译码结果?
- LDPC 的稀疏图为什么能逼近极限,代价是什么?
- 为什么厂商一定要上软读,硬判决到底丢掉了什么?
- 译码失败之后,产品应该怎么处置才算做完了?
其中第 5 个问题经常被当成「性能优化问题」,第 6 个问题经常被当成「异常处理问题」。本文的立场是:这两者都是架构问题。软判决决定了纠错能力能不能兑现,失败处置决定了纠错能不能真正保护数据。
2. 错误来源与信道模型
码的选择不能脱离信道。这一章把错误按成因和形态两个维度拆开,并说明这些属性如何一步步收窄码的选择范围。
2.1 硬错误:晶格缺陷与固定坏点
硬错误指的是与某一批物理单元绑定、重复出现、不会因为重读而消失的错误。它的成因通常在制造阶段的薄膜沉积、刻蚀或掺杂均匀性上,也可能是封装与应力导致的机械损伤。在成品器件里,它们表现为固定坏点、固定坏页,以及一小片区域内成簇出现的缺陷。
硬错误最关键的工程特征是时间上稳定。同一地址读十次,十次都错在同一处。这意味着重读对它无效 —— 重读能对付的是随机瞬态错误,对付不了结构性缺陷。因此硬错误的唯一出路是重映射:把出错的块标记为坏块,把数据搬到别处。
但纠错码仍然要在硬错误上发挥作用,因为纠错码能为重映射争取时间。一个块的缺陷不会一次就多到无法纠正,纠错码让它在缺陷扩散到整块之前,先把还能读的数据救出来,同时通过计数上升的信号提前触发重映射。纠错计数是重映射的输入,不是重映射的替代。
| 维度 | 硬错误 | 处置方式 |
|---|---|---|
| 成因 | 制造缺陷、封装应力、局部材料不均 | 无法在系统侧修复,只能隔离 |
| 时间特征 | 稳定,重复出现 | 靠计数增长趋势发现 |
| 空间特征 | 成簇、相邻,多与页或块边界相关 | 整块或整页重映射 |
| 重读效果 | 无效,读几次都错在同一处 | 不作为处置手段 |
| 纠错码的作用 | 争取时间、提前告警 | 提供计数与告警依据 |
| 最终处置 | 标记坏块、迁移数据 | 与坏块管理协同,见第 9 章 |
2.2 软错误:亚稳态与诱发射电
软错误是暂时性的错误:读一次错,再读一次就对。它来自电路本身的电气行为,主要有三类来源:
- 亚稳态:触发器或判决电路在输入翻转的临界窗口内采样,输出在一个不确定的中间态停留,最终落到随机的一侧。这类错误与时序裕量直接相关。
- 诱发射电:阿尔法粒子或宇宙射线产生的次级粒子穿过存储阵列,在电荷存储节点里注入少量电荷,导致存储电压轻微偏移。偏移量通常很小,只有在比特本来就靠近判决门限时才会翻转。
- 电源与地弹噪声:大电流切换时在供电网络上产生的瞬态扰动,会让一组相邻的读出电路同时受影响,形成成组的比特错误。
软错误的关键特征是随机性,它在时间上不重复、在空间上分散。正因为随机,纠错码对它是高度有效的 —— 只要错误数不超过纠正能力,就能被完全还原。也正因为随机,重读对它非常有效,这是重试策略在第 9 章成立的物理基础。
| 软错误来源 | 物理机制 | 时间特征 | 主要对策 |
|---|---|---|---|
| 亚稳态 | 触发器在临界窗口采样 | 随机,重读大概率恢复 | 加大时序裕量、软判决 |
| 诱发射电 | 次级粒子注入电荷导致电压偏移 | 随机且低频 | 纠错码 + 多次采样读 |
| 电源地弹噪声 | 大电流切换引起的供电扰动 | 成组发生,时间相关 | 去耦、隔离、多读比较 |
| 读出电路失调 | 比较器与感测放大器本身的不匹配 | 与温度相关,缓慢变化 | 温度补偿、校准 |
| 噪声底抬升 | 介质损耗或单元老化导致信噪比下降 | 随寿命推移 | 纠错码,见第 2.6 节 |
2.3 擦除错误与错误纠正错误的本质区别
这是存储语境里最常被混用的一对概念,必须切干净。
擦除错误说的是「一个块已经擦不干净」。表现为擦除操作之后,块内的残余电荷没有降到足够低,或者某些单元的擦除电压落在分布的拖尾上。它描述的是存储操作本身是否成功,是一个写入侧的问题,由擦除电压的脉冲次数与验证读出决定。
错误纠正错误说的是「读出来的数据能不能被还原」。它描述的是读取过程的结果,是一个读取侧的问题,由信道模型与码的纠正能力决定。
两者的关系是因果而非等同:擦除不干净会让这个块的读出分布整体偏移,从而让错误纠正错误的概率上升;但错误纠正错误也可能纯粹由软错误引起,与擦除无关。工程上常见的错误是:把读出错误都归因于擦除失败,于是一味调整擦除电压,而不去查读出裕量与纠错配置。
| 对比项 | 擦除错误 | 错误纠正错误 |
|---|---|---|
| 发生侧 | 写入侧(擦除操作) | 读取侧(读出与译码) |
| 本质 | 块内残余电荷未降到目标水平 | 读出比特与原始比特不一致 |
| 可观测现象 | 擦除后验证读出落在目标分布之外 | 译码计数上升或译码失败 |
| 主要成因 | 擦除电压脉冲不足、单元老化、拖尾分布 | 软错误、硬错误、分布整体偏移 |
| 检出手段 | 擦除后的验证读出 | 校验子非零、译码失败上报 |
| 处置手段 | 调整擦除策略、判定坏块 | 重读、重映射、提高纠错能力 |
| 关联性 | 擦除异常会抬高读出错误率,但两者不等价 | 不能把读出错误一律归因于擦除 |
把读出错误当作擦除失败来处理,通常表现为「不断加大擦除电压与验证次数」,结果是磨损加速、擦除耗时上升、坏块率上升,而读出错误率并没有改善。第 12 章的排查决策树把这条误判路径单独列了出来。
2.4 随机错误与突发错误
按错误在空间上的分布,可以把信道模型分成两类。它们的区别不只是「错几个」的问题,而是直接决定码型:线性码擅长处理随机错误,突发错误需要交织或专门设计的结构。
随机错误中,各个位置出错与否互相独立。这是最经典的分析模型,也是所有纠错码性能曲线的默认前提。BCH 码、Hamming 码、卷积码的分析都以随机错误为前提。
突发错误则是一段连续位置的比特整体出错。成因通常是局部的:电源弹噪声、一次感测放大器的异常、或者一小片区域的缺陷。此时即使错误的总数不多,也可能超出线性码的纠正能力 —— 因为对随机纠错码而言,重量相同的分散错误与突发错误同为可纠正的错误图样;突发码的真正优势,是用更少的冗余纠正更长的突发。
应对突发错误有三条路:交织(把突发打散成随机错误)、突发错误纠正码(专门设计的长度约束)、或者多次读出比较(用时间维度换空间维度)。第 9 章会说明重试与多次采样读在这条线上的位置。
| 维度 | 随机错误 | 突发错误 |
|---|---|---|
| 空间形态 | 分散,无关联 | 一段连续位置同时出错 |
| 典型成因 | 软错误、诱发射电 | 电源弹噪声、感测异常、局部缺陷 |
| 典型码型 | BCH、LDPC 等线性分组码 | 交织 + 线性码,或突发专用码 |
| 关键参数 | 错误数 t、原始误码率 | 突发长度 B、突发发生概率 |
| 纠错关注点 | 最小距离 d | 突发纠错能力与交织深度 |
| 误纠正风险 | 错误数接近 t 时上升 | 同样错误数下风险显著更高 |
2.5 信道模型参数如何决定码的选择
把前面几个维度合起来,就能得到一张码型选择表。这张表的逻辑是:先看噪声形态定大类,再看所需纠正能力定档位,最后看实现约束定取舍。
决策的第一步是确定噪声的主成分。如果错误以随机分散为主,线性分组码(家族 1)就够用;如果错误以突发为主,需要加上交织(家族 2);如果信噪比本身就很低、需要逼近理论极限,才考虑 LDPC 这类图码(家族 3)。
第二步是确定纠正能力的档位。这里要注意一个常被忽略的事实:提高一档纠正能力带来的冗余增量是跳变的,不是线性的。从一个档位跳到下一个档位,冗余往往要增加相当可观的一块,而能力提升却只有一档。因此档位选择要贴着实际需要选,而不是往上多买一档。
第三步是加入实现约束:延迟预算是多少、面积预算是多少、是否允许迭代、是否需要确定性的失败判据。这一步往往才是真正做出决定的一步 —— 第 8 章整章都在处理这一层。
| 噪声主成分 | 码型家族 | 判决方式 | 选择理由 | 主要风险 |
|---|---|---|---|---|
| 随机分散,硬错误少 | BCH(中低档) | 硬判决即可 | 结构确定、实现简单、失败可判定 | 误纠正风险随错误数接近 t 而上升 |
| 随机分散,硬错误多 | BCH(高档)或 LDPC | 软判决 | 需要更高的冗余效率 | 高档 BCH 译码延迟与面积上升 |
| 突发为主 | 交织 + BCH / LDPC | 软判决 | 交织把突发打散为随机 | 交织深度占用额外缓存 |
| 信噪比低,接近极限 | LDPC | 软判决(必需) | 同等冗余下性能显著更好 | 迭代延迟、失败判据、收敛不保证 |
| 面积极度受限 | BCH(缩短到最小可用) | 硬判决 | 译码器规模最小 | 冗余不足,阈值很低 |
| 延迟极度敏感 | BCH(低档) | 硬判决或浅软判决 | 无迭代,延迟确定 | 能力有限,需要更多重试配合 |
2.6 关键指标:原始误码率与目标纠错能力
最后把第 1.1 节埋下的伏笔收掉:这两个指标是同一枚硬币的两面。原始误码率描述信道的坏到程度,目标纠错能力描述你打算用多大的冗余去覆盖它。两者之间的映射关系,就是码型选型的定量依据。
需要强调的是,原始误码率不是一个常数。它随寿命推移、温度变化、读出条件(电压、次数、驻留时间)而变化。规格书里给出的通常是特定条件下的典型值或上界,而设计必须按寿命末期的最坏情况来算。用初期误码率做设计、在末期才发现纠不过来,是这条线上最常见的一类事故。
目标纠错能力的表述也有讲究。规格书里常见的写法是「可纠正 t 个错误位」,这指的是单个码字内的错误数,而不是整页。换算时要结合码字的划分方式 —— 一个页面被分成多少个码字,决定了 t 是在多大的范围内生效。这个换算关系在附录 A 有完整对照。
| 指标 | 定义 | 获取方式 | 设计时的取值原则 |
|---|---|---|---|
| 原始误码率 | 信道输出中错误比特的占比 | 统计大量读出结果,寿命末期数据才有意义 | 按寿命末期 + 最高温度的最坏情况取 |
| 可纠正错误数 t | 单个码字内保证可纠正的错误位数 | 由码的最小距离决定 | 按码字划分方式换算到页级 |
| 码字划分 | 一页被划分为多少个独立纠错单元 | 由码长与页大小的关系决定 | 码字越大越抗突发,但冗余利用率越低 |
| 目标最终误码率 | 上层可见的比特错误占比 | 由产品完整性要求反推 | 取最严格的那条要求 |
| 纠错增益 | 原始误码率与最终误码率之比 | 由仿真与实测获得 | 按最坏信道条件评估 |
| 余量倍数 | 实际纠正能力相对需求的富余 | 由上述两项相除得到 | 留出寿命与温度的退化名 |
3. BCH 码的代数结构
BCH 的价值不在于性能最好,而在于它的结构是算得出来的。给定纠正能力 t,可以在纸面上把校验位长度、译码运算量与失败判据全部算清楚。这一章讲清楚它凭什么能做到这一点。
3.1 汉明码的思路与它的局限
一切线性分组码的起点都是校验矩阵。汉明码的做法最朴素:取 GF(2) 上所有非零列向量排成矩阵,每一列的非零位置就是「这个比特参与哪几个校验方程」。因为 GF(2) 上恰好有 2^m - 1 个非零向量,所以汉明码的自然码长是 n = 2^m - 1。
汉明码的结构能保证任意两码字之间的最小距离至少为 3,因此可以纠正 1 个错误、同时检测 2 个错误。它简单、译码只需一次奇偶校验,非常适合最初的低速接口。但它的局限同样明显:纠正能力被钉死在 t = 1。
想要更强的纠错能力,就要让校验矩阵的列之间「更不相似」—— 具体地说,要让任意 t+1 列线性无关。汉明码的构造方式做不到这一点,于是需要一种能系统地提高距离的构造思路,这就是 BCH。
| 维度 | 汉明码 | BCH 码 | 为什么不同 |
|---|---|---|---|
| 构造起点 | GF(2) 上全部非零列 | 本原多项式的连续幂次根 | 根的结构化带来距离保证 |
| 自然码长 | n = 2^m - 1 | n = 2^m - 1(不缩短时) | 两者共用同一个长度序列 |
| 典型纠正能力 | t = 1 | t 可按需设计,取值连续 | BCH 的 t 是设计参数而非副产品 |
| 最小距离 | d 大于等于 3 | d 大于等于 2t+1 | BCH 码是近似最优的码 |
| 译码方式 | 按列号直接定位,闭式 | BM + Chien + Forney | t 变大后闭式解不存在 |
| 失败判据 | 校验子非零即失败 | 根数不符或复算非零即失败 | 两者都是确定性判据 |
| 适用档位 | 冗余极低、错误率极低的场景 | 需要 t 到数十的绝大多数存储场景 | 实际存储几乎都用 BCH |
3.2 BCH 的构造:以连续幂次为首的本原多项式
BCH 码是循环码:一个码字循环左移一位,得到的仍然是码字。这个性质把「按位置写方程」变成了「按多项式取模」—— 用一个生成多项式 g(x) 描述整个码。工程上这意味着编码器可以做成移位寄存器加组合逻辑,而不需要一张巨大的矩阵乘法器。
构造过程分三步:
- 选域:取 GF(2^m) 上的本原元 alpha,它生成整个域,因此 alpha 的幂次可以取遍 2^m - 1 个非零元素。域的大小 m 决定自然码长 n = 2^m - 1。
- 选连续幂次:取 alpha 的连续幂次 alpha^1 到 alpha^(2t) 作为码字的根。「连续」是 BCH 码名字的来源,也是它能给出距离保证的关键 —— 码字必须在这 2t 个点上同时取零。
- 求生成多项式:g(x) 取这些根对应的最小多项式的最小公倍式。g(x) 的次数就是冗余位数 n - k。
「连续幂次」这一步是 BCH 与一般循环码的分水岭。正因为根是连续的、个数是 2t 个,才能证明出码的最小距离至少是 2t + 1。这意味着想纠 t 个错,所需的最小距离有明确的代数保证,而不是靠搜索试出来的。
有一个细节值得单独提出来:窄义 BCH(窄义定义)取的是从 alpha^1 开始的连续幂次,这一类码的误码性能在同一族里最好,因此工程上用的绝大多数是窄义 BCH。如果对某几个根的指数做偏移,得到的是宽义 BCH,在某些与既有生成多项式兼容的场合会用到,但性能通常略差。
| 构造要素 | 含义 | 取值方式 | 对硬件的影响 |
|---|---|---|---|
| 域 GF(2^m) | 所有运算所在的有限域 | m 决定自然码长 n = 2^m - 1 | 域乘法器是译码器的核心算子 |
| 本原元 alpha | 生成整个域的元素 | 从本原多项式的根中取一个 | 决定 Chien 搜索的遍历顺序 |
| 本原多项式 | 描述 GF(2^m) 的不可约多项式 | 查标准表选取 | 只影响常量表,不影响结构 |
| 连续幂次根 | alpha^1 到 alpha^(2t) | 由目标纠正能力 t 决定 | 根的个数即迭代次数上限 |
| 最小多项式 | 某个根所在的最小不可约因子 | 查表或在线计算 | 决定校验子计算的分组方式 |
| 生成多项式 g(x) | 所有最小多项式的最小公倍式 | 次数即冗余位数 | 编码器是一个以 g(x) 为模的除法器 |
| n 与 k | n = deg(g) + k | k = n - deg(g) | 决定页内如何切分码字 |
因为 g(x) 的次数可以查表得到,n 又是设计时就选定的,所以 n - k 是一个确定的数。这意味着冗余预算在项目立项阶段就可以精确地列进 BOM,而不需要「先做个原型试试能纠几个」。这是 BCH 在工程上长期占位的首要原因,也是第 10 章讨论备用区预算时可以直接引用它的原因。
3.3 最小距离与纠错能力的关系
最小距离 d 是任意两个不同码字之间汉明距离的最小值。它是一个纯粹的结构量:只看码怎么构造,与噪声、器件、判决方式全都无关。正因为如此,它才可以被写进规格书、被用来做可证明的承诺。
d 与纠正能力的关系来自一个简单的计数论证:如果两个码字距离为 d,那么以第一个码字为中心、半径 (d-1)/2 的球与第二个码字的球不会相交,因此半径 (d-1)/2 以内的错误可以被无歧义地纠正。于是得到两条基本结论:
- 纠错能力:t 的上限为 (d-1)/2 向下取整;
- 检错能力:在 t 之外,最多还能可靠检测 d - 1 个错误。
BCH 码被设计成 d 大于等于 2t + 1,即恰好取在能保证纠正 t 个错误的下界上。在给定冗余的前提下这是划算的选择,但对存储产品来说往往偏保守:规格书里标称的 t 通常只是理论保证,实际期望的错误数要小得多。第 8 章会把这个「理论 t」与「工程实际需求」之间的差值算出来。
还要提醒一个容易被忽略的事实:d 不随使用而改变,但可用的 d 会因码字划分方式改变。如果把两个码字交叉起来放(同一条校验同时覆盖两页的一部分),单条校验的 d 不变,但整页的检错能力会下降。因此「码字怎么切」和「码怎么构造」同样重要。
| 最小距离 d | 可纠正 t | 可检测 e | 剩余能力 | 工程含义 |
|---|---|---|---|---|
| 3 | 1 | 2 | 无 | 汉明码级别,只能对付偶发单比特错误 |
| 5 | 2 | 4 | 无 | 覆盖成簇的少量错误,仍需重试配合 |
| 7 | 3 | 6 | 无 | 存储中常见的最低实用档位 |
| 9 | 4 | 8 | 无 | 开始有冗余余量,但不多 |
| 11 | 5 | 10 | 无 | BCH 的主流起步档位 |
| 2t+2 | t | 2t+1 | 1 | 扩展码的整体校验位,可额外拦住偶发双错 |
| 2t+3 | t | 2t+2 | 2 | 多一位冗余换取更好的突发抗性 |
3.4 缩短与扩展:实用码长的取得
理论上码长 n = 2^m - 1 只有 7、15、31、63、127、255、511、1023 这些值,而实际页大小千变万化。这个落差不靠「随便裁一下」解决,而是靠两个严格定义的操作。
缩短(shortening):固定若干个高位为 0,只在剩下的位置里取码字。由于这些位恒为 0、不提供任何信息,从统计上等价于把它们从码字里删掉。关键性质是:缩短不改变最小距离 d,只同时减小 n 和 k。也就是说,缩短让你拿到任意需要的码长,却不损失纠错能力 —— 代价是冗余的相对比例会略微上升。
扩展(extension):在整个码字后面再补一位整体校验位,使全体比特的奇偶校验为零。这一位带来的增量是距离恰好加 1。扩展码的典型用途是:多一个检错位,用来拦住那些刚好落在纠正边界上的误纠正。在错误数接近 t 的边缘区域,这一位的价值很大。
把两者组合起来就能覆盖几乎所有实用场景:先用缩短对齐码长到页大小的整除关系,再用扩展买一层检错保险。附录 A 列出的常见存储用 BCH 码型,绝大多数是缩短后的结果,少数在存储语义上会用扩展或双扩展的形式。
| 操作 | n 变化 | k 变化 | d 变化 | t 变化 | 冗余率变化 | 典型用途 |
|---|---|---|---|---|---|---|
| 不缩短 | 2^m - 1 | n - deg(g) | 基准 | 不变 | 基准 | 码长天然对齐的场合 |
| 缩短 s 位 | n - s | k - s | 不变 | 不变 | 略有上升 | 对齐页大小与扇区 |
| 扩展 1 位 | n + 1 | k | d 加 1 | 不变 | 略下降 | 加一层检错,拦误纠正 |
| 双扩展 | n + 2 | k | d 加 2 | 不变 | 略上升 | 高档位下强化边缘保护 |
| 缩短后扩展 | n - s + 1 | k - s | d 加 1 | 不变 | 接近基准 | 存储中最常见的组合 |
规格书里常见「短化到 559 位、纠正 24 位错误」这类写法,其中 559 与 511 的关系、24 位错误对应的生成多项式次数,都是缩短与选型的结果。读这类参数时一定要问清三个问题:缩短自哪个自然码长、t 对应的最小距离是多少、是否含扩展位。同样一句「纠 24 位」,在不同约定下对应的冗余开销差别很大。
3.5 常见 BCH 码型与适用档位
把 t 从 1 往上排,就得到一条档位阶梯。选型的实质是:根据寿命末期实测的原始误码率,选一个「刚好够用」的档位。下面这张表给出各档位的大致位置(示意分档,边界按常见工程实践大致划分,不作为精确依据),具体参数以正式 datasheet 与规范为准。
从表里能读出一条重要的工程规律:冗余率随 t 上升得比线性更快。t 翻倍,冗余率往往要涨一倍以上。这直接解释了为什么「档位买高一档」不是一件划算的事 —— 付出的是不成比例的面积、带宽与功耗,换来的只是错误数多一倍的容忍度。
另一条规律是关于码字长度的:码长越长,单个码字里的校验位越多、冗余率越低(因为 deg(g) 增长慢于 n),抗突发能力也越好;但码字越长,一次译码的延迟越高、需要的缓冲越大。这是第 8 章讨论延迟时的一个主要权衡点。
| 档位 | 纠正能力 t | 码长量级 | 冗余率量级 | 典型适用 | 选它的理由 |
|---|---|---|---|---|---|
| 低档 | 1 ~ 4 | 数百到一千 | 百分之几 | 低磨损负载、缓存类数据 | 冗余开销可忽略,实现最简 |
| 中档 | 5 ~ 12 | 数百到一千 | 百分之几到十几个百分点 | 通用消费类存储的默认选择 | 能力与开销的平衡点 |
| 高档 | 16 ~ 24 | 一千左右 | 十几个百分点 | 车规、工业宽温、高磨损负载 | 末期误码率明显抬升,需要余量 |
| 很高档 | 32 以上 | 一千以上并缩短 | 接近或超过两成 | 特殊场景,需重新核算收益 | 收益递减,需重新论证 |
一是按初期误码率选型。初期分布窄,用低档就够,但寿命末期分布一宽,低档立刻不够。正确做法是按末期数据选,或者留出明确的温度与磨损余量。
二是把「能纠 t 个」当成「一定能纠 t 个」。t 是硬上限而不是平均值;一旦某次读出超过 t 个错误,译码就会失败。
三是多档并行。同时支持多套码型看似灵活,实际会大幅增加验证矩阵与硅面积。档位越少,验证与维护成本越低。
3.6 BCH 的结构优势与代价
现在可以把 BCH 的账算清楚了。它的优势有四条,每一条都直接对应一个工程收益:
- 结构可计算:n、k、d、t 全都是设计时定下的数,不需要实验搜索。这让冗余预算与验证计划可以在项目早期就确定。
- 译码确定:给定的接收码字必然给出给定的纠正结果与给定的失败判定,不存在「这次跑出来对了、下次跑出来不确定」的情况。
- 失败可判定:纠正后的码字如果仍不满足校验,失败就是确定的。这个性质在第 9 章的处置流程里非常值钱 —— 误纠正概率可以算出来,而不是只靠实测统计。
- 误纠正概率可解析估计:由码的多项式可以给出误纠正概率的上界,这对可靠性论证非常有用。
代价也有四条:
- 冗余效率不如 LDPC:在同等冗余下,BCH 的性能距离香农极限有明显差距,尤其在低码率与长码时。
- 软判决增益有限:BCH 的代数结构在硬判决下就已经接近它的设计能力,软判决能带来的提升远小于 LDPC。这是 BCH 的一个重要特点,第 7 章会量化这一点。
- 高档位的译码复杂度上升快:BM 迭代次数与 t 成正比、错误定位子与错误数成正比,高档位的延迟与面积增长都很快。
- 档位是离散的:不能要「纠 5.5 个错」,只能向上取整买一档,或者用缩短的方式凑。
| 特性 | BCH 的表现 | 对工程的影响 |
|---|---|---|
| 结构可计算性 | n / k / d / t 全为设计参数 | 冗余预算可提前确定,验证可提前规划 |
| 译码确定性 | 无迭代,一次定解 | 延迟可预测,便于时序收敛 |
| 失败判定 | 由校验子与根数确定地判定 | 第 9 章的处置流程可以直接信任它 |
| 误纠正概率 | 有解析上界 | 可做可靠性论证,不必只靠实测 |
| 冗余效率 | 低于 LDPC | 同等能力下冗余更多 |
| 软判决增益 | 有限 | 不必为 BCH 强行上软读 |
| 高档位复杂度 | 随 t 快速增长 | 高档位延迟与面积上升明显 |
| 档位粒度 | 离散,只能向上取整 | 需要按末期数据选,避免多买档位 |
4. BCH 译码的完整流程
这一章把 BCH 译码的五个步骤逐个讲清楚。重点不在于记住公式,而在于理解每一步在解决什么问题、每一步的输出如何成为下一步的输入。最后一步的失败判据,是本章与工程现场联系最紧的部分。
4.1 软判决输入的表示:LLR
译码器拿到的不是比特,而是每个比特的置信度。最常见的表示方式是对数似然比 LLR(log-likelihood ratio),它把「证据的方向」和「证据的强度」编码在同一个数里:LLR 为正说明证据指向 0,为负说明指向 1;绝对值越大说明证据越强,接近 0 说明这个比特怎么判都差不多。
LLR = log( P(比特为 0 | 读出量) / P(比特为 1 | 读出量) )
用对数形式而不是概率比,纯粹是为了硬件:对数把乘法变成加法,而且避免了大量数值的动态范围问题。在软判决路径上,LLR 通常还要再做一次量化,从浮点或高位宽定点压到 3~6 比特的有符号整数。量化本身会损失一点信息,这正是第 7 章要讨论的软判决代价的一部分。
需要明确的一点是:BCH 译码在数学上并不要求软判决。它完全可以吃硬判决的 0/1 序列,代价是失去置信度信息。但把 LLR 送进去仍然有收益 —— 幅值可以帮助识别那些最不可信的比特,从而在失败判定时更有把握。收益的量级远小于 LDPC,详见 8.1。
| 输入形式 | 每比特占用 | 含义 | BCH 的使用方式 | LDPC 的使用方式 |
|---|---|---|---|---|
| 硬判决 0/1 | 1 bit | 只有结果 | 直接作为 r(x) 的系数 | 只能当作初始似然比 |
| 全精度 LLR | 浮点或多字节 | 方向 + 强度 | 幅值用于辅助判失败 | 直接作为先验消息 |
| 量化 LLR | 常见 3~6 bit | 方向保留,强度分级 | 按幅值加权或仅取符号 | 作为变量节点初始消息 |
| 仅符号加少量等级 | 2~3 bit | 最省的一种折中 | 基本等价硬判决 | 收益有限,接近硬判决 |
4.2 校验子计算
第一步是校验子(syndrome)。做法很直接:把接收到的码字 r(x) 代入生成多项式的那些根上求值,得到 2t 个域元素 S_1 到 S_2t。
S_i = r(alpha^i) = sum_j ( r_j 乘以 (alpha^i)^j ) i = 1, 2, ..., 2t
这一步的物理含义非常直白:把整个码字拿去问 2t 个检验问题,看它答得对不对。如果码字完全正确,所有 S_i 都是零;如果码字里有错误,S_i 会呈现一个由错误位置与错误幅值共同决定的特定模式。换句话说,校验子就是错误的指纹。
硬件上有两条实现路线:
- 余数表法:把 g(x) 的幂次预先算成查找表,输入 r(x) 后逐项查表异或。这是最常见的做法,代价是一张 ROM 与一组移位异或网络。
- 除法器法:直接做多项式除法,与余数表等价,但 t 较大时组合逻辑更深。
无论用哪种方法,这一步都是纯组合逻辑、无迭代、延迟确定的。这也是 BCH 译码延迟可预测的来源之一。
| 步骤 | 输入 | 运算 | 输出 | 硬件形态 | 是否迭代 |
|---|---|---|---|---|---|
| 校验子计算 | r(x) 的系数 | 在 2t 个根上求值 | S_1 到 S_2t | 查表加异或网络 | 否 |
| 关键方程 | 校验子序列 | 建立 S 与位置的关系 | 关键错误方程 | 寄存器加反馈 | 否 |
| BM 迭代 | 校验子序列 | 求最短递推 | 定位子 L(x) | 移位寄存器链 | 是 |
| Chien 搜索 | L(x) 的系数 | 逐位置代入求值 | 错误位置集合 | 并行求值网络 | 否 |
| Forney 求幅值 | L、Omega 与位置 | 计算错误大小 | 错误幅值 | 常数乘法加除法 | 否 |
4.3 关键错误方程
有了校验子,下一步要解决的是一个具体问题:什么样的错误模式,能恰好产生这一串 S_i。把这个关系写成方程,就是关键错误方程(key error equation):
关键错误方程: S(x) = Omega(x) * L(x) (mod x^(2t))
L(x) = 各个错误位置因子的乘积 只回答「错在哪里」
Omega(x) = 错误求值子 承担幅值相关信息
未知量:各错误位置,以及 Omega(x) 的各项系数
这条方程的意义在于它把「位置」和「幅值」分离了。L(x) 只关心错误在哪里,Omega(x) 承担幅值相关信息。这种分离是整个译码流程能拆成独立步骤的原因:先用 BM 把 L(x) 求出来(只依赖 S_i 的递推关系,不需要知道位置),再用 Chien 把位置解出来(只需要 L(x),不需要 Omega)。
从方程的形式也能看出 t 的代价:模 x^(2t) 意味着只保留前 2t 项。t 越大,方程越高,求解越复杂,但求解的确定性不变。这一点与 LDPC 形成鲜明对比 —— LDPC 求解的是一个固定点问题,t 增大只是让迭代次数变多,而 BCH 的 t 增大直接让代数运算量上升。
还要注意一个实践细节:关键错误方程的求解是「最多 t 个解」。如果真实错误数少于 t,方程有多余的自由度,BM 会找到其中一个满足校验子的解。这引出了 4.7 节要讲的一个隐患:它可能找到一个「不是真正错误」的解,从而把错误的数据「纠正」成另一串错误的数据。
4.4 Berlekamp-Massey 求错误定位子
BM 算法要解决的问题很纯粹:找一条最短的线性递推序列,能够生成给定的校验子序列 S_1 到 S_2t。这条最短递推的系数,就是错误定位子 L(x)。
为什么最短递推就是定位子?直觉是这样的:如果有 t 个错误位置,那么校验子序列必然满足一个长度为 t 的递推关系;反过来,如果找到了一条长度为 L 的递推能解释整个校验子序列,那么真实错误的个数就是 L。L 的长度直接给出了错误的个数。
BM 的迭代过程可以这样理解:维护两组寄存器,一组存当前的候选 L(x),一组存历史备份。每一步拿新来的一个校验子去试当前候选,如果对不上,就按失配程度把长度加一或加二,并从备份里补回相应的系数。一旦连续若干步都对得上,L 就稳定了,迭代可以退出。
工程上有三点必须注意:
- 迭代次数有确定上限:最多 2t 次,因为校验子一共就 2t 项。这与 LDPC 的「迭代多少次都行」形成鲜明对比。
- 提前退出能省时间:如果 L 连续若干轮不再变化,说明已经收敛,可以跳出。绝大多数低错误数的码字在这一步就结束了 —— 这是 BCH 延迟随实际错误数变化的原因。
- 长度超过 t 就是失败信号:如果 BM 结束时 L 的长度大于 t,说明错误数超出了纠正能力,应当直接判失败,不要往下走。
4.5 Chien 搜索定位错误位置
拿到 L(x) 之后,定位错误位置变成一个纯代入问题:对每一个可能的位置 j,计算 L 在 alpha 的 -j 次幂处的值。值为零,说明第 j 位就是错误位置,因为 L 的根恰好是各错误位置在域中的表示。
遍历全部 n 个位置并统计零的个数:
- 零的个数等于 t:定位成功,继续求幅值。
- 零的个数不等于 t:定位失败。真实错误数与 BM 判定的数量对不上,说明信道超出了纠正能力,必须判失败。
- 零的个数多于 t:说明 L 的因式分解超出了实际错误数,同样判失败。
这一步是 BCH 译码里最优雅的部分:它天然适合并行实现。n 个位置的求值彼此独立,硬件上可以铺成 n 路并行乘法与异或,一次算完所有判定结果。这解释了为什么 BCH 的译码延迟不随码长线性增长 —— Chien 搜索占的时间大约等于一次并行乘法器级数,而不是 n 次串行乘法。
当然,实际实现会在并行度和面积之间取折中:全并行面积大,串行面积小但延迟高。常见做法是分成若干组,组内并行、组间串行,组数按面积预算定。这个参数直接影响第 8 章讨论的面积与延迟取舍。
4.6 Forney 算法求错误幅值
最后一步是求每个错误位置上错得有多厉害,也就是错误幅值。对于二元域的 BCH 码,错误幅值只有 0 和 1 两种,因此这一步的实际功能是确认并给出需要翻转的位。Forney 公式把幅值作为域元素的比值算出来,非零即翻转。
E_j = Omega(X_j 的逆) / L 导数(X_j 的逆) j = 1, 2, ..., t
L 导数(x) 是 L(x) 的形式导数
X_j 是第 j 个错误位置的域表示
为什么二元域还需要 Forney,而不是简单地把根对应的位全部翻转?三个原因:
- 正确性:只有算出幅值非零才翻转,等于多了一道自检。如果某个位置算出的幅值为 0,说明它其实不是错误位,此时应当把它剔除,而不是盲目翻转。
- 可扩展性:同样的框架可以推广到多元码(幅值不止 0/1)与软判决的广义距离译码(幅值变成实数)。有了这一层结构,扩展时不需要重写流程。
- 验证友好:幅值可以作为译码流水线的中间量被观测,有助于定位是「定位错了」还是「幅值错了」。
翻转完成后,整个流程还差最后一件事:用新的校验子复算一遍,确认纠正后的码字确实是码字。这一步几乎是免费的(就是第一步的重算),但它把整个译码从「相信算法」升级为「可验证」。第 9 章的失败处置流程,正是建立在这一步之上。
| 步骤 | 运算 | 输出 | 验证方式 | 异常时的处理 |
|---|---|---|---|---|
| 求 L 的导数 | 形式导数 | L 导数的系数 | 检查 L 在根处导数非零 | 退化情形判失败 |
| 代入 Omega | Omega 在各错误位置求值 | Omega 取值 | 检查非零 | 为零则剔除该错误 |
| 相除得幅值 | 域除法 | E_j | 二元域下非零即为错 | 全零说明定位错误 |
| 翻转比特 | 按 E_j 翻转 | 纠正后的码字 | 逐位与原串比对 | 越界位说明定位越界 |
| 复算校验子 | 重跑第一步 | 新的 S_1 到 S_2t | 全部为零才算成功 | 非零则判失败 |
4.7 译码失败判据与误纠正风险
这是本章最重要的一节,也是全篇「确定性 vs 概率性」这条主线的第一次正面出现。BCH 译码有三种明确的失败形态,每一种都有确定的判据,没有第四种「不确定是否失败」的情况。
检出失败(detected failure):实际错误数超过 t,译码算出一个错的纠正结果,复算校验子发现不为零,于是判失败。这是安全的失败 —— 数据没有被悄悄改坏,系统知道自己出错了。注意与「漏检(undetected error)」区分:漏检专指错纠后校验子碰巧为零、错误数据被当作正确数据交付的情形,即上一段说的误纠正;检出失败时校验子非零、失败被抓住,两者的概率量级相差很远,检出失败远比漏检常见。
误纠正(mis-correction):实际错误数超过 t,但译码算出的那个纠正结果恰好也满足校验约束,于是复算校验子全零,系统判定成功。此时数据被改成了另一串错误的比特,而上层完全不知道。这是危险的失败,也是 BCH 唯一真正危险的失败模式。
溢出(overflow):BM 求出的定位子长度超过 t。这种情况在 BM 结束时就能立刻判定,没必要继续算下去 —— 提前退出能省下无谓的延迟。
误纠正的概率取决于两件事的组合:错误数超过 t 的概率,以及在错误的码字邻域里恰好还能找到另一个合法码字的概率。前者由信道决定,后者由码的距离决定。这两条正是第 8 章做 BCH 与 LDPC 对照时的核心指标。
降低误纠正风险有三条工程手段:一是加扩展位,把最小距离抬高一位,直接缩小错误码字的邻域;二是缩短码字长度,减少可能落入的码字邻域;三是配合重读,用多次独立读出的结果投票,把单次读出的误纠正变成难以同时发生的巧合。第 9 章会把这三条与失败上报流程串起来。
BCH 译码返回成功,只意味着纠正后的码字满足校验约束。在误纠正场景下,一个错误的码字同样满足约束。所以在系统设计上,「译码成功」只是一个必要条件,不是充分条件:第 11 章会讲为什么主机侧仍然需要自己的校验层,第 9 章会讲失败之后必须怎么处置。
| 失败形态 | 触发条件 | 判据 | 数据是否被破坏 | 系统应做的事 |
|---|---|---|---|---|
| 溢出 | BM 求出的 L 长度超过 t | 长度大于 t,BM 结束时即可判定 | 否 | 立即判失败,不纠正 |
| 定位不符 | Chien 根数与 t 不一致 | 根数不等于 t | 否 | 判失败,不纠正 |
| 检出失败 | 错误数超过 t,纠正结果不满足校验 | 复算校验子非零 | 否 | 判失败,触发重试或重映射 |
| 误纠正 | 错误数超过 t,纠正结果恰好满足校验 | 无法从译码本身判出 | 是 | 只能靠上层校验与统计发现 |