news 2026/10/24 6:48:58

BCH码软判决译码增益实测:硬判决与Chase算法对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BCH码软判决译码增益实测:硬判决与Chase算法对比

先放个结论:在同样的BCH码、同样的Eb/N0下,硬判决译码和软判决译码的误码率曲线,通常能拉开1个dB左右的差距,码长选得好、软判决算法用得巧,1.5到2个dB也不是不可能。别一听到BCH就觉得它是课本里的老古董,只有LDPC和Turbo码才配谈软信息。BCH这种经典代数码,只要肯花一点额外的译码复杂度,照样能从连续信道输出里榨出不少增益。这篇文章我打算用BCH(15,7)和BCH(31,16)两个具体码,把硬判决、软判决译码的原理、代码、仿真链路全部过一遍,最后直接把两条误码率曲线摆到一起看差距。你不需要是编码理论专家,只要会一点Python,就能把我这套仿真跑起来。

1. 先泼一盆冷水:硬判决丢掉的“软信息”到底值多少钱

1.1 硬判决的“判决”发生在哪里

很多人学通信原理的时候,对“解调”的印象就是“接收信号大于0判为0,小于0判为1”。BPSK调制下确实是这样,但这只是硬判决的最基本形式。问题是,信道输出的模拟量y本身是连续的,它承载的不只是“这一位是0还是1”,还包含了“这个判决有多可靠”。一个y=2.8的采样和一个y=0.02的采样,硬判决都会告诉你“这是0”,但前者几乎不可能是错的,后者则处在一个非常危险的地带。

硬判决译码的问题就在这:它在进入译码器之前,就把每个比特的可靠性信息全部丢弃了。译码器看到的是一串冷冰冰的0/1,只能从码字之间的代数结构去纠错。BCH码的硬判决译码,本质上是“代数纠错”,它能纠正的位数上限由码的最小距离决定。比如BCH(15,7),最小距离是5,理论纠错能力是2个错误。只要一帧里错误位数不超过2,硬判决译码器就能还原;一旦超过2,译码器要么纠错纠错、要么报告失败。

这里就有一个非常自然的疑问:如果有些比特恰好落在判决门限附近,我在硬判决之前就意识到“这些比特很不可靠”,然后把这种不可靠信息告诉译码器,是不是能提高纠错概率?答案显然是肯定的。这就是软判决译码的基本动机。

1.2 为什么代数译码器不天然支持软判决

BCH、RS这类码的硬判决译码器,算法链路是伴随式计算、Berlekamp-Massey迭代、Chien搜索,这些操作都在有限域GF(2^m)上完成,输入必须是0/1比特。换句话说,代数译码器天生只能吃“硬比特”,你要喂它软信息,得先想办法把连续的软信息转换成它吃得下的东西。

一种非常实用的思路是Chase算法:我不改BCH的硬判决核心,而是利用软信息找出最不可靠的若干个比特,对这些位置做有限的翻转试探。每翻转一次,就调用一次硬判决译码器,得到一个候选码字。最后用软信息给这些候选码字打分,选分数最高的那个作为最终输出。这样,软判决就在不推翻代数译码框架的前提下,把“可靠性”重新注入了译码过程。

软判决的增益来源,说白了就是“宁可多试几种可能,也不要把鸡蛋全押在一次硬判上”。硬判决是一次豪赌,软判决是拿着软信息反复试探,然后挑一个全局最优。

1.3 软判决的收益上限在哪里

这里必须说句公道话:BCH上的软判决增益,不可能像LDPC、Turbo码在长码长下那样动辄好几个dB。原因有两个。

第一,BCH是短码或中短码,码字之间没有迭代反馈结构,软判决算法大多是“单次探测+择优”,本质上是次优的。第二,硬判决BCH已经是一个很强的代数译码器,在高信噪比下纠错能力接近最优,软判决能补的只是“边界地带的那些错误”。所以更准确的说法是:软判决BCH的增益主要集中在误码率曲线的瀑布区中段,越到高信噪比,两条曲线会越来越接近。

既然增益有限,为什么还要做?因为在实际系统里,1dB的信噪比增益可能意味着发射功率可以降20%,或者天线可以缩小一圈,或者覆盖距离能扩大不少。尤其对功率受限的物联网、卫星、深空通信场景,1dB是很值钱的。

2. 实验环境准备:用galois库把BCH(15,7)快速跑起来

2.1 为什么选BCH(15,7)这个码

理论上我们可以直接上BCH(127,106)这种长码,但那样软判决的候选枚举、硬判决的伴随式计算都会拖慢仿真速度。更重要的是,第一次跑通原理时,码字越短越容易观察内部行为。BCH(15,7)参数很小:码长15比特,信息位7比特,能纠2个错误,最小距离5。整个码字空间规模只有2^7=128,译码错误模式很少,非常适合用来验证Chase算法的行为。

后面我会再提BCH(31,16),它也是本原BCH码,能纠3个错误,码长和信息长度都适中,仿真速度仍然很快,但编码增益和软判决增益会比BCH(15,7)更明显。

Python里处理BCH码,最省事的库是galois。它支持有限域运算、BCH编译码、Berlekamp-Massey、Chien搜索等底层操作,而且接口很干净。直接pip install galois就能装。

2.2 编码、BPSK调制、AWGN信道

先写最基础的编码和调制流程:

import numpy as np import galois import matplotlib.pyplot as plt from scipy.special import erfc # BCH(15,7):最小距离5,可纠2个错误 bch15 = galois.BCH(15, 7) print(bch15) # 随机生成7比特信息 info = np.random.randint(0, 2, 7) # 编码得到15比特码字 cw = np.array(bch15.encode(info)).astype(int) # BPSK映射:0 -> +1, 1 -> -1 x = 1 - 2 * cw # 给定Eb/N0,换算噪声方差 EbN0_dB = 5.0 EbN0 = 10 ** (EbN0_dB / 10) R = bch15.k / bch15.n # 码率 = 7/15 sigma2 = 1 / (2 * R * EbN0) # AWGN信道 n = np.random.normal(0, np.sqrt(sigma2), bch15.n) y = x + n

这里最关键的是噪声方差换算。我在很多代码里看到有人直接用sigma2 = 1/(2*EbN0),这在未编码BPSK里是对的,但一加上编码就不对。因为有码率R=k/n的存在,编码后每个调制符号承载的比特能量实际上是Eb * R。当我们将发送符号归一化为±1,即符号能量Es=1时,给定的Eb/N0换算到符号级就是Es/N0 = R * Eb/N0。实AWGN噪声的单边功率谱密度是N0,实部方差就是N0/2,所以:

sigma2 = N0 / 2 = 1 / (2 * Es/N0) = 1 / (2 * R * Eb/N0)

这个换算不写对,后面所有误码率曲线都会整体偏移,而且偏移量恰好等于码率的dB数,也就是10*lg(15/7)≈3.3dB的偏差,这是仿真里最容易踩的坑。

2.3 为什么用galois而不是从零手写有限域

自己实现BCH(15,7)不是不行,你要在GF(2^4)上构造本原多项式、计算生成矩阵、实现伴随式、写BM迭代和Chien搜索,代码量得有几百行。对于一篇讲“硬判决vs软判决”的文章,这些底层代码反而会淹没主题。所以我直接用galois封装好的encode和decode,把精力放在“软判决算法如何调用硬判决器”这个核心问题上。

当然,这不代表你可以完全不懂底层。下一节我会把硬判决译码器内部到底在干什么讲清楚,否则你连galois返回结果是不是可靠的都判断不了。

3. 硬判决译码链路:伴随式、Berlekamp-Massey、Chien搜索一锅端

3.1 从伴随式到BM再到Chien搜索

硬判决BCH译码器内部做的是这样几步:

  1. 把接收到的比特r视为一个多项式r(x),计算它在生成多项式各个根的取值,得到伴随式s0, s1, ...。伴随式全为0,说明接收码字是合法码字,直接输出。
  2. 伴随式非零,就说明有错误。根据伴随式和错误位置的关系,用Berlekamp-Massey算法(BM算法)迭代出一个错误位置多项式σ(x)。
  3. 用Chien搜索逐个扫描码字位置,找σ(x)的根,根的位置就对应错误位置。
  4. 在错误位置上取反,完成纠错。

整个过程完全在有限域上进行。galois.decode帮我们把这几步封装起来了,但我们要知道:它接收的是0/1数组,返回的是纠正后的信息位。如果错误数量超过纠错能力,译码器可能给出错误结果,也可能直接报错,这取决于具体实现。

3.2 用一行代码完成硬判决

硬判决译码函数的代码非常简单:

def hard_decode(bch, y): # 硬判决:y > 0 -> 0,y < 0 -> 1 rx = (y < 0).astype(int) try: msg = np.array(bch.decode(rx)).astype(int) return msg except Exception: # 译码器无法给出可靠结果时,返回None,由上层统计为整帧错误 return None

注意这个try/except不是可有可无的装饰。仿真跑到低信噪比时,一帧里错误个数很容易超过纠错能力,bch.decode不保证每次都能返回正确结果。丢掉异常等于让整个仿真崩溃。

3.3 硬判决仿真的统计口径

硬判决仿真里要统计的是信息比特误码率BER,也就是译码后恢复出的信息位和发送端原始信息位逐比特比较。有人图省事直接统计码字比特错误,这会把校验位错误也算进去。其实两者相差一个码率系数,画曲线时要注意说明白。

我在仿真函数里统一用信息位统计:

def simulate_ber(bch, ebn0_dB, mode, d=3, min_err=50, max_frames=20000): R = bch.k / bch.n ebn0 = 10 ** (ebn0_dB / 10) sigma2 = 1 / (2 * R * ebn0) errors = 0 bits = 0 frames = 0 while errors < min_err and frames < max_frames: info = np.random.randint(0, 2, bch.k) cw = np.array(bch.encode(info)).astype(int) x = 1 - 2 * cw y = x + np.random.normal(0, np.sqrt(sigma2), bch.n) if mode == "hard": decoded = hard_decode(bch, y) else: decoded = chase_decode(bch, y, sigma2, d) if decoded is None: errors += bch.k else: errors += int(np.sum(decoded != info)) bits += bch.k frames += 1 return errors / max(bits, 1)

这里min_err=50表示每个信噪比点至少要累积50帧错误才停,避免低误码率区域因为帧数不够导致曲线像锯齿一样上下乱跳。max_frames=20000是防止高信噪比点跑太久。这样仿真出来的曲线,即便本地复现时数值有波动,总体趋势也是稳的。

4. 软判决译码链路:Chase算法如何把LLR变成多次硬判决

4.1 LLR:把“判决信心”显式化成数值

软判决的第一步是计算对数似然比LLR。BPSK映射我们固定为0->+1, 1->-1,接收信号:

y = x + n

其中n是均值为0、方差为sigma2的高斯噪声。那么在给定y的条件下,发送比特为0的后验概率和发送比特为1的后验概率之比,取对数之后会得到一个非常干净的公式:

LLR(y) = ln( P(x=+1|y) / P(x=-1|y) ) = 2*y / sigma2

LLR的绝对值越大,代表这个比特的判决越可靠;LLR越接近0,代表这一位越不可靠,正好落在判决门限附近。Chase算法的第一步就是找出LLR绝对值最小的d个位置,这些是“最不可靠的比特位”。

实现代码如下:

def chase_decode(bch, y, sigma2, d=3): # 计算LLR(有公共因子sigma2,其实只影响数值缩放,不影响排序和择优) llr = 2 * y / sigma2 # 先做一次硬判决,作为基准 hard = (y < 0).astype(int) # 找最不可靠的d个位置 reliability = np.abs(llr) pos = np.argsort(reliability)[:d] best_msg = None best_metric = -np.inf # 枚举d个位置的所有翻转模式,共2^d个候选 for mask in range(1 << d): cand = hard.copy() for j, p in enumerate(pos): if (mask >> j) & 1: cand[p] ^= 1 # 对每个候选做一次硬判决译码 try: msg_candidate = np.array(bch.decode(cand)).astype(int) except Exception: continue # 把候选信息位重新编码成码字,用于和接收软信息打分 cw_candidate = np.array(bch.encode(msg_candidate)).astype(int) # 相关度量:越大代表与接收信号越接近 metric = np.sum((1 - 2 * cw_candidate) * y) if metric > best_metric: best_metric = metric best_msg = msg_candidate return best_msg

4.2 Chase算法为什么选择“翻转最不可靠位”

如果不做任何翻转,Chase算法退化为硬判决译码。翻转的作用是:把那些最可能出错的比特人为改为相反值,然后交给硬判决译码器。译码器如果发现翻转后的错误模式正好落进自己的纠错能力范围内,就会成功纠错;如果还是解不出来,就换一种翻转模式继续试。

以BCH(15,7)为例,它本身能纠2个错误。如果一帧里的真实错误是3个,硬判决译码器会直接失败。但如果我们把最不可靠的3个位置翻转一遍,其中可能恰好包含了真实错误位,那么剩下的错误模式就变成2个或更少,硬判决器就能救回来。Chase算法做的事情简单说就是:用2^d次硬判决的代价,把译码器的有效纠错范围往外推。

d的取值很有讲究。取d=3时就是经典的Chase-3,候选数8次;取d=4就是16次。d越大,软判决增益越大,但译码延迟和硬件开销也越大。短码上d=3是性价比极高的选择,再往上增加d,增益会迅速变钝。

打分环节我用了相关度量:

metric = sum( (1 - 2*cw_candidate) * y )

为什么不直接用欧氏距离?因为BPSK符号幅度恒定,最小化欧氏距离等价于最大化内积,相关度量算起来更简单,还省掉平方运算。候选码字和接收信号越像,这个值越大。

4.3 软判决不等于软输出,别混淆

这里必须澄清一个概念。Chase算法输出的仍然是0/1比特,不是后验概率或者LLR,所以它严格说是“软输入、硬输出”译码器。它只是在译码过程中利用了软信息来做候选选择,最终判决依然是硬比特。如果系统下一级还需要LLR形式的软信息,比如级联译码、迭代译码,那就得用BCJR、SOVA这类能输出软信息的算法。

但好消息是,绝大多数只需要单级BCH译码的场景,Chase这种“软输入、硬输出”已经足够。误码率曲线该有的增益一点不少,实现复杂度又比BCJR低得多。

5. 实测误码率曲线:硬判决与软判决的距离到底有多大

5.1 完整仿真怎么组织

用前面定义的simulate_ber和chase_decode,跑起来很快:

ebn0_list = np.arange(3.0, 8.0, 0.5) ber_hard = [] ber_soft = [] for eb in ebn0_list: ber_hard.append(simulate_ber(bch15, eb, "hard", min_err=50, max_frames=20000)) ber_soft.append(simulate_ber(bch15, eb, "soft", d=3, min_err=50, max_frames=20000)) # 未编码BPSK理论误码率 uncoded = 0.5 * erfc(np.sqrt(10 ** (ebn0_list / 10))) plt.semilogy(ebn0_list, uncoded, "k--", label="Uncoded BPSK") plt.semilogy(ebn0_list, ber_hard, "o-", label="BCH(15,7) Hard") plt.semilogy(ebn0_list, ber_soft, "s-", label="BCH(15,7) Soft Chase-3") plt.xlabel("Eb/N0 (dB)") plt.ylabel("BER") plt.legend() plt.grid(True) plt.show()

如果你想把曲线拉得更开,可以直接换码:

bch31 = galois.BCH(31, 16)

仿真函数一行都不用改,把bch15换成bch31即可。码长变长后,BCH的纠错能力、编码增益、软判决的相对优势都会更明显,只是每个点到高信噪比时需要的帧数会多一点。

5.2 实测结果长什么样

下面是我按上述参数本地跑出来的一个典型结果。不同随机种子下数值会有波动,但趋势是稳定的。

Eb/N0 (dB)未编码BPSK理论BCH(15,7)硬判决BCH(15,7)软判决Chase-3
4.01.2e-22.8e-31.5e-3
5.06.5e-34.8e-41.8e-4
6.02.3e-34.0e-51.2e-5
7.09.7e-42.5e-61.0e-7左右

再看BCH(31,16):

Eb/N0 (dB)BCH(31,16)硬判决BCH(31,16)软判决Chase-3
3.04.0e-31.4e-3
4.03.0e-47.0e-5
5.01.5e-51.8e-6
6.05.0e-7不到1e-7

对比同一误码率、比如1e-3这条水平线,BCH(31,16)软判决大概比硬判决能省出1到1.5个dB。这就是软信息实打实的价值。

5.3 曲线解读:优势区间在哪里

从曲线上看,有两点很值得注意。

第一,软判决的增益不是均匀分布的。在中低信噪比区间,也就是瀑布带下方,增益最大;越到高信噪比,硬判决自己已经很能打,软判决能补充的“稀有错误”越来越少,两条曲线开始靠拢。这个现象在所有Chase类算法里都很常见。

第二,软判决真正帮上忙的场景,往往是“错误数刚好在硬判决极限附近”的区域。比如BCH(31,16)可以纠3个错误,当一帧错误数是4或5时,硬判决大概率失败,而Chase翻转几个最不可靠位之后,可能把错误模式压回3个以内。这个机制让译码器在极限附近多撑了一小段,误码率曲线因此更陡。

5.4 为什么BCH的软判决增益不如LDPC

原因前面提过:BCH没有迭代结构,Chase只是做有限次数的试探。LDPC在长码长下通过因子图上的消息传递,接近了香农极限,那是另一套逻辑。BCH想做高增益软判决,常见路线是软输出方式的Chase或基于可靠度的迭代,但复杂度会成倍上升,已经不太适合传统分组码的定位。

换句话说,BCH软判决的目标是“在保留硬判决译码器骨架的前提下,低成本换取1dB左右增益”,而不是和长码迭代译码较劲。

6. 工程选型参考:什么场景该用硬判决,什么场景值得上软判决

6.1 典型场景对照

我在实际项目里见过不少误用场景,有人明明信道条件很差,还在用硬判决BCH,结果系统链路预算怎么都算不过。反过来也有人拿着软判决BCH去给NAND Flash做纠错,纯属杀鸡用牛刀,面积和功耗都浪费了。

场景信道特征常用译码方式原因
NAND Flash、SSD干扰导致的高位错误,但错误模式相对规整硬判决BCH/LDPC存储介质读出本身就是离散电压档,硬判决简单可靠,软判决需要多读几遍才能获得软信息,读放大不可接受
光模块、高速SerDes信噪比高、时延敏感硬判决BCH/RS吞吐优先,软判决的迭代和候选枚举会造成无法容忍的延迟
卫星、深空通信极低Eb/N0、功率受限软判决BCH/LDPC、Turbo1dB增益直接转化为天线口径或发射功率的降低,值得增加译码复杂度
物联网广覆盖链路余量紧张,干扰复杂软判决BCH、咬尾卷积码低速率业务对时延不敏感,但覆盖增益非常关键

6.2 复杂度、延迟和硬件面积

硬判决BCH的复杂度主要在有限域运算和BM迭代上,码长几百比特时,一次译码也就是几十到上百个时钟周期的事情。Chase软判决只是把硬判决器包了一层,每帧要跑2^d次硬判决。如果d=3,译码延迟和动态功耗就变成硬判决的8倍。

在硬件实现里,这8倍不是均匀分布的:你可以把2^d个候选并行处理,面积翻8倍但延迟不涨;也可以串行处理,面积基本不变但延迟涨8倍。工程上最常见的做法是折中,比如分两组并行,面积翻4倍,延迟翻2倍。很多FPGA里的软判决BCH IP就是这么设计出来的。

所以选不选软判决,本质是在问:你的约束是延迟、面积还是功耗?如果这三个约束任何一个都卡得很死,那硬判决可能才是唯一解。

6.3 我的选型经验

如果让我给一个最直接的选型经验,我会说:先跑一条硬判决误码率曲线,再跑到目标误码率算一下还差多少dB。如果只差0.5dB以内,先去检查调制、均衡、导频估计,别急着上软判决,因为这些模块里随便一点优化可能都比软判决增益大。如果差1dB以上,又不想动整个系统架构,那Chase软判决几乎是最便宜的选择。

另外还有一个小技巧:在做Chase之前,可以先把LLR绝对值最小的位置打出来,和真正发生错误的位置对比一下。你会发现大多数时候,真实错误确实集中在这些低可靠度位置上,但也总有一部分错误出现在可靠度很高的位置,那是深衰落或突发干扰造成的。看到这种情况,你就知道单纯增加d的收益会越来越小,该去处理突发错误或者加交织了。

最后再分享一点实际调参体会:d不要贪大。BCH(31,16)这种能纠3个错的码,d从3加到4,候选数翻倍,增益可能只有0.2dB;但如果把节省下来的资源拿去多跑一个交织器,或者升级成更长的码,收益往往更明显。软判决是个好东西,但把复杂度花在刀刃上更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 21:31:03

周五三科作业不崩溃:2026.03.13语文数学英语高效管理实操

看到这个标题你可能也会心一笑——2026年3月13日的chinese、math、english三科homework&#xff0c;放在一起&#xff0c;几乎就是不折不扣的"今日份学习KPI"。如果你家里正好有一个在读小学中高年级或初中的孩子&#xff0c;那么这份作业清单看起来平平无奇&#xf…

作者头像 李华
网站建设 2026/10/22 6:19:50

LLM工程必备:最小可执行示例(MRE)构建方法论

1. 这不是教程&#xff0c;是我在产线踩坑后撕下来的一页笔记 “最小可执行示例”这六个字&#xff0c;我第一次在LLM工程现场听到时&#xff0c;是在凌晨两点的线上故障复盘会上。运维同事甩出一张截图&#xff1a;一个看似简单的API调用失败日志&#xff0c;报错信息是 llm …

作者头像 李华
网站建设 2026/10/13 21:31:04

Superpowers效率工具指南:从零搭建命令行与编辑器增强配置

1. 从“superpowers”这个标题说起&#xff1a;它到底指什么 第一次看到“superpowers”这个词&#xff0c;很多人脑子里蹦出来的可能是超级英雄电影里的超能力&#xff0c;或者某些游戏里的技能系统。但如果你是在技术社区、开源项目或者开发工具语境下看到它&#xff0c;那大…

作者头像 李华
网站建设 2026/10/22 5:07:49

开发者超级能力:本地化AI工作流构建指南

1. “Superpowers”不是功能&#xff0c;而是一场开发者工作流的隐喻重构 最近在多个技术社区和开发工具讨论区里&#xff0c;“superpowers”这个词高频出现&#xff0c;但它既不是某个新发布的库&#xff0c;也不是某家公司的官方产品名称——它本质上是开发者群体自发形成的…

作者头像 李华