拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Turbo码EXIT图分析:Python实现指南

Turbo码EXIT图分析:Python实现指南

简介:Turbo码EXIT图迭代分析MATLAB资料包,面向通信工程、信息论方向的研究生、科研人员及无线通信系统设计者,聚焦Turbo码性能预测与迭代解码优化。压缩包共43个文件,以m脚本、mat数据、pdf文献和fig图形为主,总大小仅3.89MB;其中m脚本覆盖EXIT曲线计算、迭代轨迹追踪、LLR软信息生成、对数最大后验概率解码、互信息直方图测量与网格构建等核心环节,14个mat数据文件对应不同生成多项式、信噪比与码率下的仿真结果。10篇pdf文献汇集外信息转移经典理论文章,fig图形直观展示不同参数下EXIT特性与误码性能曲线。包内代码与数据构成完整实验环境,可复现Turbo码EXIT迭代全过程,帮助读者理解交织器设计、编码率选择及软判决解码的相互作用,也可用于验证算法收敛性并与理论曲线相互对照。已有253人学习下载,适合希望结合代码深入研究Turbo码原理与工程设计的进阶学习者。

1. Turbo码EXIT图:用一条曲线和45度线的缝隙判断迭代译码能不能收敛

Turbo码的迭代译码看着玄学:两个分量译码器来回传软信息,迭代次数加码到20次,BER还是掉不下去。EXIT_TURBO这类工具要解决的,就是把这个黑匣子摊开——用外信息转移图(EXIT图)可视化每次迭代到底交换了什么信息。横轴是分量译码器拿到的先验互信息IA,纵轴是它吐出来的外信息互信息IE。对称Turbo码看分量码EXIT曲线与45度对角线的缝隙:缝隙越宽,迭代收敛越快;曲线和对角线相交,迭代直接卡死。适合刚接手Turbo码链路、想快速定位迭代增益瓶颈的通信工程师,也适合做Turbo/LDPC选型对比的算法同学。下面按“理论→实现→验证→避坑”往下推,给出一份能直接照着跑的Python流程。

2. 先搞懂三个量:IA、IE和BCJR的外信息,这是EXIT图的地基

2.1 互信息不是信噪比:IA和IE到底在量什么

EXIT图里横轴和纵轴都不是信噪比,而是互信息这个统计量。给定一串LLR和它对应的发送比特b,互信息I(L;b)度量的是这串LLR对b的刻画能力:0表示完全没信息,1表示所有比特都能被判断。实现上不需要求分布解析式,直接蒙特卡洛均值就能估计。为什么要用互信息而不是直接用SNR或者LLR均值?因为分量译码器做的是非线性操作,LLR经过前向/后向递推后分布不再是高斯,均值、方差都描述不了它与真实比特的相关程度;互信息对分布形状不敏感,是少数能稳定排序的指标。这也是EXIT图从论文走向工程实现的立身之本。

符号上,IA表示某个分量译码器的输入先验LLR和“它对应的信息比特”之间的互信息;IE表示译码器输出外信息LLR和这些比特之间的互信息。第一次迭代时所有分量译码器都没有先验,IA=0;如果信道条件好,第一次迭代可能直接吐出IE=0.3,第二次这个0.3变成另一个分量译码器的IA=0.3,相当于信息在两个分量码之间交替放大。新手最容易混淆的一点:两个译码器交换的不是后验LLR,而是外信息Le,也就是从“别人的先验”里剥掉之后剩下来的新增信息。后验LLR里既包含信道信息又包含先验,直接拿它交换,相当于把别人说过的话又原封不动说回去,迭代会很快收敛到一个错误结果。

工程里还有一个常见误区:只盯着IA=0和IA=1两个端点看。实际上0.9到0.99之间互信息涨得特别慢,这段曲线对随机样本量最敏感,经常需要加大采样次数才能把毛刺压下去。EXIT曲线画出来如果抖得像牙签,先别怀疑算法,先看这段采样量是不是不够。

2.2 对数域BCJR:前向、后向、外信息怎么算

BCJR是最常用的软输入软输出译码算法,Turbo的分量译码器几乎都用它。EXIT计算关心的不是硬判决结果,而是每个信息比特的外信息Le。标准对数域BCJR分三步:前向递推计算alpha,后向递推计算beta,最后合并出每个比特的后验LLR。

给定递归系统卷积码RSC作为分量码,每个时刻k的状态转移由输入比特u_k决定。max-log域的递推写成:

alpha[k][s] = max( alpha[k-1][s'] + gamma[k][s'][s] ) beta[k][s'] = max( beta[k+1][s] + gamma[k+1][s'][s] )

分支度量gamma只和本时刻的系统比特u_k、校验比特p_k、信道LLR以及输入先验LLR有关:

gamma[k] = L_a(u_k) + L_ch_sys(u_k) + L_ch_par(p_k)

这里的L_a(u_k)在u_k=1时取先验LLR的值,u_k=0时取0;信道项同理。最后对u_k做max-log合并得到后验LLR,外信息就是再扣掉信道和先验的贡献:

L_e = L_app - L_ch_sys - L_a

这正是“外信息”名字的由来:它是这个分量译码器本轮新拿出来的信息。写代码时alpha、beta、gamma全部用对数域,路径不存在用一个大负数如-1e9代替。max-log不计算logsumexp,和log-MAP相比性能差零点几个dB,但实现简单得多,出问题也好排查。EXIT图本身就是看趋势和门限,用max-log足够。

2.3 把分量译码器当黑匣子:EXIT扫描的“合成先验”思路

把BCJR当黑匣子,EXIT扫描的核心思想就一句话:不仿真真实接收帧,而是人为构造一批先验LLR,让它们的互信息恰好等于想扫描的IA,然后喂给BCJR,统计输出外信息的互信息IE。先验LLR用高斯近似构造:给定标准差sigma_a,让LLR均值等于sigma_a平方的一半、符号随发送比特翻转,即L_a = (sigma_a^2/2)·(2b-1) + n。这样生成的LLR分布和真实AWGN信道下的LLR分布一致,I(L_a;b)大小完全由sigma_a决定,用数值方法求反函数就能得到每个IA对应的sigma_a。

对很多个IA重复一遍“构造先验、跑BCJR、统计IE”,就得到一整条EXIT曲线。这个做法把迭代译码的复杂行为拆成两次独立扫描:第一次扫分量1的IE曲线,第二次扫分量2的IE曲线,两条曲线之间的相对位置决定整体收敛行为。轨迹则是把两条曲线上的点交替连接起来,模拟真实迭代过程。我一般先扫曲线、再跑轨迹、最后才做完整误码率仿真,因为EXIT判断收敛门限的成本远低于误码率仿真:不需要跑几十上百帧,只要每点多取几个随机样本平均就行。黑匣子思路也方便换算法,以后把BCJR换成log-MAP或查表SISO,其他代码不用动,只换这个函数。

如果两个分量码完全相同,两条EXIT曲线会重合,图退化为一条曲线和45度对角线之间的开口。判断标准是曲线是否始终位于对角线之上:是,迭代能收敛;和对角线相交,交点就是信息交换停滞点。分量码不同、或对称Turbo做了打孔、或使用非对称交织时,两条曲线形状不同,轨迹在两条曲线之间走阶梯,隧道面积才是收敛速度的度量。这两种情形对应EXIT_TURBO里最常见的两类图画法,后面的实现同时兼容两者。

3. 自己动手复现EXIT_TURBO:分量码、BCJR和扫描脚本

3.1 参数表:生成多项式、交织器、信噪比怎么定

EXIT图能不能说明问题,一半取决于参数选得对不对。我常用的起点配置列在下面,照着抄能省很多调试时间。

参数项推荐值说明
分量码RSC(2,1,3)反馈多项式111(八进制7)、前向多项式101(八进制5),经典组合,曲线形状和论文可比
交织长度N1000~10000短帧看趋势,长帧验证定量门限;N太小时EXIT曲线毛刺多、轨迹偏散
交织器固定种子伪随机每帧换交织器会让结果是平均行为,固定种子便于复现和排错
信道AWGN、BPSK不要在EXIT扫描里直接加衰落,那是另一套统计,先确认AWGN下曲线再扩展
先验扫描点IA0.01~0.99,约10~15个点IA大于0.95的区间至少重复采样3次取平均
BCJR实现max-log和log-MAP差0.1~0.2dB,曲线定量够用,实现简单
采样量每点随机样本20000以上互信息是统计量,样本少了曲线会抖

选RSC(7,5)是因为它的反馈多项式让码字距离谱拉开,EXIT曲线在中等IA区有明显“台阶”,隧道的开闭变化看得清楚。交织长度别一上来就上十万,先拿N=1024把整个流程跑通。信道信噪比按Es/N0给,BPSK实信道下Es=1,码率不在这里折算。对称Turbo码码率1/2时,Es/N0约等于Eb/N0减3dB,扫图时直接扫Es/N0在0~2dB这一段,基本就能看到隧道从关闭到完全打开的全过程。

3.2 RSC编码器和max-log BCJR的最小实现

先写分量码。RSC编码器用状态转移表驱动,比逐位算反馈更直观,也方便后面BCJR直接复用同一张表。下面的实现不做尾比特收尾,EXIT扫描用的是无限长循环卷积近似,实际仿真再加尾比特就是。

import numpy as np def build_rsc_trellis(feedback_poly, forward_poly): """ 构造RSC分量码的状态转移表。 feedback_poly / forward_poly 是比特串,左边为最高位。 例: '111' 对应八进制7,'101' 对应八进制5。 返回 dict: 状态数, next[state][u], out[state][u]。 out 高比特=系统位,低比特=校验位;系统位=输入u。 """ m = len(feedback_poly) - 1 # 寄存器长度 n_states = 1 << m # 状态数 fb_mask = int(feedback_poly[1:], 2) # 反馈抽头,去掉最高位1 ff_mask = int(forward_poly[1:], 2) # 前向抽头 next_s = np.zeros((n_states, 2), dtype=np.int64) out_w = np.zeros((n_states, 2), dtype=np.int64) for s in range(n_states): for u in (0, 1): # 反馈位 = 输入 异或 寄存器反馈抽头的奇偶校验 fb = u ^ (bin(s & fb_mask).count("1") & 1) # 新状态:寄存器右移,反馈位进最高位 ns = (s >> 1) | (fb << (m - 1)) # 校验位 = 前向抽头的奇偶校验 par = bin(s & ff_mask).count("1") & 1 next_s[s, u] = ns out_w[s, u] = (u << 1) | par return {"states": n_states, "next": next_s, "out": out_w} def rsc_encode(info, trellis): """RSC编码,返回系统位和校验位。状态从0出发,不做尾比特收尾。""" sys_bits = np.zeros(len(info), dtype=int) par_bits = np.zeros(len(info), dtype=int) state = 0 for k, u in enumerate(info): sys_bits[k] = u par_bits[k] = trellis["out"][state, u] & 1 state = trellis["next"][state, u] return sys_bits, par_bits

说明:build_rsc_trellis里bin(s & fb_mask).count("1")取寄存器中和反馈抽头对应位的奇偶,这就是递归结构的关键。前向抽头ff_mask只决定校验位输出,不影响状态转移。两个函数合起来就是完整的分量码编码器。rsc_encode里sys_bits就是info本身,因为系统码的系统位直接透传,写出来是为了让BCJR侧理解信息比特和校验比特的对应关系。

接下来是核心的max-log BCJR。它吃进去系统位信道LLR、校验位信道LLR和先验LLR,吐出来外信息Le。符号约定在注释里写清楚:比特1对应发送符号-1,比特0对应+1;信道LLR按log(P(b=1)/P(b=0))定义。只要这个约定不换,下面所有公式都成立。

def bcjr_extrinsic(L_sys, L_par, L_prior, trellis): """ max-log BCJR,输出外信息 Le。 符号约定: 比特1 -> 发送符号 -1,比特0 -> +1。 信道LLR L = log(P(b=1)/P(b=0)),可由 L = -2*y/sigma_n^2 得到。 L_prior 是另一个分量译码器给过来的先验LLR。 """ n = len(L_sys) S = trellis["states"] nxt, out = trellis["next"], trellis["out"] NEG = -1e9 # 前向递推 alpha[k][s] alpha = np.full((n + 1, S), NEG) alpha[0, 0] = 0.0 for k in range(n): for s_prev in range(S): base = alpha[k, s_prev] if base <= NEG / 2: continue for u in (0, 1): s_cur = nxt[s_prev, u] # 分支度量:先验+系统信道+校验信道,u 和校验位为0时该项为0 gam = L_prior[k] * u + L_sys[k] * u + L_par[k] * (out[s_prev, u] & 1) v = base + gam if v > alpha[k + 1, s_cur]: alpha[k + 1, s_cur] = v # 后向递推 beta[k][s] beta = np.full((n + 1, S), NEG) beta[n, 0] = 0.0 for k in range(n - 1, -1, -1): for s_prev in range(S): for u in (0, 1): s_cur = nxt[s_prev, u] gam = L_prior[k] * u + L_sys[k] * u + L_par[k] * (out[s_prev, u] & 1) v = beta[k + 1, s_cur] + gam if v > beta[k, s_prev]: beta[k, s_prev] = v # 逐比特合并,输出外信息 Le = np.zeros(n) for k in range(n): num, den = NEG, NEG for s_prev in range(S): for u in (0, 1): s_cur = nxt[s_prev, u] gam = L_prior[k] * u + L_sys[k] * u + L_par[k] * (out[s_prev, u] & 1) metric = alpha[k, s_prev] + gam + beta[k + 1, s_cur] if u == 1: num = metric if metric > num else num else: den = metric if metric > den else den # 后验减掉系统信道和先验,留下外信息 Le[k] = (num - den) - L_sys[k] - L_prior[k] return Le

这段代码不是最优的,但它是这整篇文章里最值得抄的:alpha和beta用同一个trellis表,分支度量只依赖本时刻的LLR和输入/输出比特,不会有表驱动的索引错位。复杂度O(n·S·2),N=10000、S=4时一次BCJR在毫秒级。如果以后换log-MAP,只需要把max替换成logsumexp函数,接口和符号约定都不用动。

注意两个容易写错的地方:一是前向递推里alpha[k+1]可能被多条路径更新,必须取max而不是覆盖;二是最后外信息那里减的是L_sys[k]和L_prior[k],不是减gamma,因为gamma里面L_par[k]那部分是本次新增校验信息,不能减掉。我早期实现就是在这里多减了一项,结果IE曲线在高IA区整体往下塌,排查了两天才发现。

3.3 用高斯先验扫描一条IE曲线:互信息估计与J函数

互信息的估计不需要求概率密度,直接用蒙特卡洛期望。对每个样点(b, L),I = 1 - E[log2(1 + exp(-c·L))],其中c=1当b=1、c=-1当b=0。数值上要写成logaddexp,直接算exp很容易溢出成inf。

def estimate_mi(bits, llr): """蒙特卡洛估计 I(L;b)。bits 是0/1数组。""" c = 2.0 * bits - 1.0 # 1->+1, 0->-1 return 1.0 - np.mean(np.logaddexp(0.0, -c * llr) / np.log(2.0)) def j_of_sigma(sigma, rng, n_samples=20000): """给定高斯LLR的标准差sigma,估计互信息。""" bits = rng.integers(0, 2, n_samples) L = 0.5 * sigma**2 * (2 * bits - 1) + sigma * rng.standard_normal(n_samples) return estimate_mi(bits, L) def sigma_from_ia(ia, rng, sigma_max=20.0, n_samples=20000): """二分搜索,找到标准差sigma,使得 J(sigma)=ia。""" lo, hi = 1e-4, sigma_max for _ in range(40): mid = 0.5 * (lo + hi) if j_of_sigma(mid, rng, n_samples) < ia: lo = mid else: hi = mid return 0.5 * (lo + hi)

参数说明:estimate_mi中c=2·bits-1把0/1映射成-1/+1,和前面LLR的符号约定咬合。J函数是EXIT理论里的标准工具,很多实现直接用多项式近似,我这里用蒙特卡洛+二分,虽然慢一些但完全透明,不会因为近似公式参数抄错而翻车。n_samples=20000时J函数估计的标准差大约在千分位,足以支撑曲线判断;如果扫到IA>0.95,把n_samples提到50000更稳。

3.4 把两条曲线、对角线和轨迹画到一张图上

扫描主函数一次跑出两个分量译码器的IE曲线。关键点是第二分量译码器的系统LLR和先验LLR必须按交织器重排,校验LLR是交织后比特流的校验输出。这样才能反映Turbo译码器里真实的信号路径。

def scan_exit_pair(poly1, poly2, block_len, esno_db, ia_list, seed=2024): """ 两个分量码各扫一条IE曲线。 poly1/poly2 形如 (反馈多项式比特串, 前向多项式比特串)。 esno_db 是 Es/N0 的 dB 值,BPSK 实信道。 """ tr1 = build_rsc_trellis(*poly1) tr2 = build_rsc_trellis(*poly2) rng = np.random.default_rng(seed) info = rng.integers(0, 2, block_len) interleaver = rng.permutation(block_len) # 固定交织器 _, p1 = rsc_encode(info, tr1) _, p2 = rsc_encode(info[interleaver], tr2) esno_lin = 10 ** (esno_db / 10.0) var_llr = 8.0 * esno_lin # BPSK双测噪声下信道LLR方差 mu_llr = 0.5 * var_llr # LLR均值 # 一次生成整帧信道LLR,扫描多个IA时保持不变 L_sys = mu_llr * (2 * info - 1) + np.sqrt(var_llr) * rng.standard_normal(block_len) L_par1 = mu_llr * (2 * p1 - 1) + np.sqrt(var_llr) * rng.standard_normal(block_len) L_par2 = mu_llr * (2 * p2 - 1) + np.sqrt(var_llr) * rng.standard_normal(block_len) ies1, ies2 = [], [] for ia in ia_list: sigma_a = sigma_from_ia(ia, rng) L_prior = 0.5 * sigma_a**2 * (2 * info - 1) + sigma_a * rng.standard_normal(block_len) # 分量1 Le1 = bcjr_extrinsic(L_sys, L_par1, L_prior, tr1) ies1.append(estimate_mi(info, Le1)) # 分量2:系统LLR和先验都要按交织器重排 L_prior_i = L_prior[interleaver] L_sys_i = L_sys[interleaver] Le2 = bcjr_extrinsic(L_sys_i, L_par2, L_prior_i, tr2) ies2.append(estimate_mi(info[interleaver], Le2)) return np.array(ies1), np.array(ies2)

调用方式:ia_list取0.01到0.99的15个点,poly1和poly2都传('111','101')时就是对称Turbo码的扫描。执行一次大约几秒,瓶颈在sigma_from_ia的二分蒙特卡洛。嫌慢的话可以把J函数换成十Brink的多项式近似表,曲线会平滑一些,但先跑通这套再说。

绘图用matplotlib。对称Turbo码时两条IE曲线重合,只画一条曲线加对角线即可;非对称分量码才画两条曲线。

import matplotlib.pyplot as plt def draw_exit(ia_list, ies1, ies2, traj=None): """画EXIT曲线。对称分量码时曲线重合,另一分量由对角线隐含。""" plt.figure(figsize=(6, 6)) plt.plot(ia_list, ies1, "b-o", label="分量1 IE") if not np.allclose(ies1, ies2, atol=0.02): plt.plot(ia_list, ies2, "r-s", label="分量2 IE") plt.plot([0, 1], [0, 1], "k--", label="对角线") if traj is not None: tx, ty = zip(*traj) plt.plot(tx, ty, "g.-", lw=1, label="迭代轨迹") plt.xlabel("I_A1 / I_E2") plt.ylabel("I_E1 / I_A2") plt.xlim(0, 1) plt.ylim(0, 1) plt.grid(True) plt.legend() plt.show()

提示:画图前先用np.allclose判断两条曲线是否重合。很多对称Turbo码因为交织器和采样噪声,两条曲线会有千分位级别的差异,这不代表它们不同;阈值放0.02比较稳,不要看到两条几乎重叠的线就开始分析“隧道宽度”。

4. 从EXIT曲线到收敛门限:和蒙特卡洛仿真互相印证

4.1 EXIT图告诉你的三件事:门限、增益、错误地板

一张EXIT图能回答三个问题:能不能收敛、收敛多快、会不会卡死。曲线与对角线全部不相交且隧道明显打开,说明迭代译码能一路把互信息推到接近1;隧道在某处闭合,说明信息交换在那里停住,误码率会掉不下去。隧道越宽,完成收敛所需的迭代次数越少。第三个问题容易被忽略:有些曲线在IA接近1时和对角线靠得很近但没有相交,此时互信息推进极慢,实际表现就是BER曲线出现地板,迭代次数从6次加到20次几乎不涨。

把EXIT图和误码率曲线放在一起看才有工程意义。EXIT只说“有没有路”,不说“路上车多不多”。同一张图里,迭代4次和迭代8次的轨迹差异直接对应仿真里迭代次数的增益。我做链路预算时习惯先扫4张不同信噪比的EXIT图,把隧道开闭的Es/N0范围定下来,然后再只仿真这个范围内的少数几个点,蒙卡仿真时长能省一大半。

4.2 用隧道开闭预测Er/N0门限,再用仿真验证

预测门限的操作很固定:对一个Es/N0候选值扫描EXIT曲线,看曲线和对角线是否还有缝隙。二分这个Es/N0,就能把“理论收敛门限”卡到0.1dB内。下面是批量判断门限的骨架代码。

def predict_threshold(poly1, poly2, block_len, ia_list, esno_range, seed=2024): """ 扫描一系列Es/N0,返回每个点EXIT是否开口。 开口定义: 扫出的IE曲线在全部IA点上均不低于对角线-0.005。 """ results = [] for esno_db in esno_range: ies1, ies2 = scan_exit_pair(poly1, poly2, block_len, esno_db, ia_list, seed) # 对称分量码用IE曲线;非对称用两条曲线交叉判别,这里给对称版 opened = np.all(ies1 >= np.array(ia_list) - 0.005) results.append((esno_db, opened, float(np.min(ies1 - np.array(ia_list))))) return results

说明:判断“开口”不能只看最后一个IA点,要在所有扫描点上做比较。0.005的松弛量用来吸收蒙特卡洛估计噪声,设太小会把本来开口的曲线误判成闭合。跑完这个批量扫描后,再把门限附近两个Es/N0的轨迹画出来,确认轨迹不是贴在对角线上慢慢爬——那说明有限块长效应已经把实际门限推高了。

我常用的配置里,RSC(7,5)对称Turbo码、N=4096、码率1/2时,EXIT预测的门限大约落在Es/N0=-0.5dB到0.5dB之间;把Es/N0换算成Eb/N0还要加3dB。这个值和最终蒙卡仿真BER曲线开始明显下降的位置能对上0.3dB左右。对不上就先别怀疑EXIT,回头查信号功率归一化:BPSK的Es是1还是0.5,取决于你有没有把码率折算进符号能量,这一项最容易把门限整体平移。

4.3 轨迹为什么走“Z字”:有限块长和交织器的作用

轨迹是实际迭代过程在EXIT图上的投影。理想情况下,轨迹交替落在两条曲线上,形成规则阶梯。实际画出来却是一个个Z字:第一轮IE到了0.3,第二轮IA却只有0.25,反而退回去了。这不是算法错了,是有限块长下互信息估计的方差在起作用。EXIT曲线是几千帧的平均行为,真实译码每次只迭代一帧,这一帧的先验分布不可能正好落在平滑曲线上;交织器长度越短,Z字越明显,轨迹甚至可能穿到曲线外面。

交织器对轨迹形状的影响比很多人以为的都大。固定交织器时轨迹只有一条,换一个交织器轨迹会上下抖动,但抖动的包络仍然贴着EXIT曲线。用S-random交织器能减小抖动,因为它让交织前后的信息比特距离拉开,外信息在帧内更均匀。我这里给一个判断标准:轨迹的平均斜率明显小于曲线斜率时,说明分量译码器没有有效利用先验,优先检查交织器是否是纯随机排列、以及是不是忘了重排系统LLR。纯随机交织在块长小于1000时会让轨迹发散得很难看,这也是为什么我建议一开始就用N=10000而不是N=256。

5. Turbo码EXIT图避坑指南:5个常见翻车现场

5.1 现象一:曲线打开隧道,仿真却不收敛

现象:EXIT图上IE曲线明显高于对角线,按这个门限跑误码率仿真,BER却一直掉不下去。

原因:EXIT扫描用的是理想化高斯先验,每个比特的先验LLR独立同分布;真实译码器里的先验来自上一轮分量译码器,帧内各比特质量并不一致,交织器还会让相关性在迭代中累积。另一种可能是你把ES/N0和EB/N0搞混了:对称Turbo码码率1/2,Es/N0要减3dB才是Eb/N0,门限套错信号能量自然全对不上。

解决:先检查信噪比定义是否一致,再在EXIT图里叠加真实轨迹而不是只看曲线。轨迹如果钻到对角线下面,说明有限块长把实际收敛条件推高了,把块长加大或者换S-random交织器重扫。

5.2 现象二:IE曲线在低IA区域掉头向下

现象:IA从0开始增长时,IE曲线先上升,然后在IA大概0.1到0.2之间回落,形成一个小鼓包,接下来又重新上升。

原因:低IA区间通常出现在低信噪比,此时信道LLR的方差很大,max-log BCJR里alpha和beta的最大值接近且大量路径metric都接近,max近似损失严重。另一个常见原因是分支度量里漏了校验LLR项,只加系统信息,BCJR在高噪声下输出信息量反而被先验干扰。

解决:把max-log换成正则化后的log-MAP(logsumexp实现),或者把NEG从-1e9改到-1e4以下同时检查是否有路径被错误剪掉。如果换log-MAP后曲线正常,说明就是max近似问题;如果还掉头,回查gamma公式里L_par[k]那项有没有乘校验位。

5.3 现象三:轨迹和EXIT曲线对不上,轨迹明显偏低

现象:轨迹点和曲线差0.1以上,而且每次迭代都稳定偏低,不是随机抖动。

原因:大概率是第二分量译码器的LLR重排错了。Turbo码第一分量处理原序,第二分量处理交织序。分量2的系统LLR要取L_sys[interleaver],先验要取L_prior[interleaver],校验位是交织后比特流编码出来的。任何一处直接用了原序数组,第二分量等于在译另一段数据,轨迹必然塌。

解决:打印几个位置验证:L_sys_i[k]对应的真实比特是info[interleaver][k],p2[k]是info[interleaver][k]的校验输出。用一个几十比特的小帧人工核对前5个比特,确认重排关系再跑长帧。

5.4 现象四:曲线整体和论文对不上,形状一样但平移了

现象:曲线形状完全正常,整体上下平移,或者左右平移半个dB。

原因:除了信噪比定义外,最常出现在LLR符号约定。论文里常用比特0对应+1,也有用比特0对应-1的;你按比特1对应-1写了代码,去对比按反约定画的论文曲线,互信息是相同的(因为I(L;b)=I(-L;b)),但它们内部的信道LLR构造不同,导致Es/N0的换算差一个常数。此外,生成多项式比特串的最左位是寄存器输入还是反馈位的含义,不同教材画法也不一样,直接抄八进制数没核对抽头位置,曲线左右平移几个dB不奇怪。

解决:先用一个已知的简单RSC(7,5)和N=10000跑出曲线,和论文图肉眼对比开关隧道的位置;如果只是平移,查Es/N0和Eb/N0换算;如果是斜率不同,查抽头掩码是int(poly[1:],2)还是漏了最高位;如果完全错位,把比特符号约定和多项式定义打印出来逐项核对。

5.5 现象五:EXIT图说明能收敛,BER却卡在错误地板

现象:隧道打开得很明显,但仿真在BER=1e-4左右出现地板,增加迭代次数也不改善。

原因:EXIT图假设编码序列无限长且每个时刻等价。实际Turbo码有网格收尾问题,尾比特占的比例在短帧时不可忽略;另一个高频原因是分量码的最小距离不够,或者交织器让低重量码字成对出现,错误地板由码字距离谱决定,EXIT图不直接反映这部分。还有一种情况是校验位被打孔后第二分量码等效码率升高,而你扫描时没用打孔后的校验序列。

解决:先确认扫描和仿真用同一套打孔图样。再做一次不同块长的仿真,块长翻倍地板下降,说明是距离谱问题,这时EXIT图再怎么优化也没用,得改分量码或加CRC辅助。最后检查尾部比特:要仿真就做完整收尾,要扫描就用周期卷积近似,两者不要混用。

6. 进阶:用EXIT图做码率匹配和交织器设计,少走弯路

EXIT图最值钱的能力不是“事后验证”,而是“事前选型”。我在做Turbo码打孔设计时,会先用EXIT图扫不同打孔图样下的两条曲线,看它们是否仍然保持隧道开口。常见做法是固定分量码,把校验位按2:1的周期打孔,扫出来如果第二分量曲线明显下沉、和对角线提前相交,就换打孔位置或改用非对称分量码。这个工作放到误码率仿真里做,每一个打孔图样都要跑几十帧,时间成本差一个数量级。

交织器设计也可以用EXIT轨迹来挑。同一组分量码和打孔图样,换S-random交织器和纯随机交织器,轨迹的离散程度完全不同。我一般画轨迹云的包络:把同一帧长度下多个交织器的轨迹叠加,包络窄说明交织器对外信息的散布效果好。这个判断比单纯看交织距离谱更接近迭代译码的真实行为。

还有一个进阶用法是给高阶调制做比特度量匹配。Turbo码配16QAM或64QAM时,解映射器输出的是比特LLR,映射方式不同导致EXIT曲线形状差异很大。先在BPSK下确认分量码本身没问题,再换成目标调制解调器的LLR输入端做扫描,隧道还能不能开,直接决定要不要加比特交织器。这一步能避免把时间浪费在注定没有迭代增益的调制方案上。

这套流程走完,我有两个习惯值得保留:第一,任何新Turbo码结构先画EXIT再上板,曲线开口是仿真的入场券,省下的都是血泪时间;第二,所有扫描代码固定随机种子,论文图、仿真结果和回读复现都对同一份随机源,出问题时不至于怀疑是随机数在捣乱。EXIT图不是万能的,它在块长短、打孔重、距离谱差的场景会给出偏乐观的答案,但作为第一道检查关卡,它是我调试Turbo码链路时最后悔没有早点用上的工具。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表