Turbo码迭代译码原理与MATLAB实现:从Log-MAP到外信息交换
简介这份资源面向通信工程、电子信息类专业学生及信道编码方向的研究者提供Turbo编码与译码技术的Matlab仿真实现帮助读者在仿真环境中理解并行级联卷积码的编码结构、交织器作用以及迭代译码流程。资源包共1个文件为docx文档大小约17KB内含可直接运行的Matlab脚本与参数说明涵盖两个码率1/2的RSC编码器、尾部比特处理、随机交织、1/-1调制与AWGN信道模拟并支持Log-MAP、MAX-Log-MAP及SOVA等译码算法的选择与对比。脚本通过交互方式设定帧长、生成多项式、打孔方式、迭代次数与停止准则便于评估不同信噪比下的误帧率性能。已有177人学习适合作为信道编码课程实验、毕业设计或科研仿真的入门参考帮助读者快速搭建Turbo编译码链路并观察迭代增益与算法复杂度之间的权衡。1. 迭代译码为什么两个“不完美”的译码器能逼近香农极限Turbo 码最反直觉的地方在于它的两个分量译码器单独拿出来看性能都平平无奇甚至因为递归卷积结构还带一点“自找麻烦”的毛病。可一旦让它们通过交织器交换软信息、来回迭代几轮误码率曲线就会在某个信噪比附近陡然下坠出现所谓的“瀑布区”。这份 MATLAB 实现把整条链路摊开在你面前随机信息比特、两个码率 1/2 的 RSC 编码器、随机交织器、AWGN 信道、Log-MAP 或 SOVA 译码参数全部可交互配置。搞懂它你就搞懂了“软信息”到底是怎么在两次译码之间流动的也就能理解为什么工程上宁愿多花几倍算力做迭代也不肯退回单次译码。下面从编码器的状态机讲起一路落到logmapo里那行max加修正项的写法。2. 编码端两个 RSC 编码器为什么一个收尾、一个不收尾2.1 递归系统卷积码的状态转移先看编码器本身。代码里g [1 1 1; 1 0 1][n,K] size(g)得到n2, K3于是m K-1 2状态数nstates 2^m 4。这两行生成多项式对应一个码率 1/2、约束长度 3 的 RSC 编码器第一行1 1 1是反馈多项式第二行1 0 1是前馈校验多项式。所谓“递归”就是反馈支路把寄存器里的历史状态又送回输入端参与异或这让编码器变成一个带记忆的有限状态机而不是简单的移位寄存器。系统码的含义是输出的一路直接就是输入信息比特本身另一路才是校验比特。这一点在译码端非常关键——L_e L_all - 2*rec_s(1,1:2:2*L_total) - L_a这行里减掉的2*rec_s(...)正是系统比特带来的信道先验必须扣除才能得到“纯外信息”。如果不理解系统码结构这行减法看起来就像玄学。递归结构带来的直接后果是编码器的终止不能靠简单补零。因为反馈存在输入 0 并不等于状态归零。所以第一个编码器要在信息比特后附加m个尾部比特把状态“拽”回全零这就是代码注释里说的 “First encoder is terminated with tails bits”。而第二个编码器接收的是交织后的比特它没有自己的尾比特处于“开环”状态——注释里 “second encoder is left open without tail bits of itself” 说的就是这件事。为什么允许它不收尾因为收尾要额外传比特、降低码率而 Turbo 码的迭代译码对第二个编码器的终态并不敏感这点损失换来的码率收益更划算。2.2 交织器把突发错误打散成独立错误[temp, alpha] sort(rand(1,L_total))这一行生成了每帧随机的交织图案。rand产生随机数sort返回排序后的值和原始索引alpha就是那个索引排列。编码时信息比特按alpha重排后送进第二个编码器译码时再用L_a(alpha) L_e把外信息按同样图案搬回去。随机交织的好处是如果第一个编码器产生的某个错误在信道上连续出现经过交织后在第二个编码器看来就变成了分散的、近似独立的错误两个分量码的纠错能力才能真正叠加。注意这里是“每帧重新生成”交织器而不是固定一个。仿真里这么做是为了让性能统计不依赖于某个特定交织图案的偶然性属于蒙特卡洛仿真的常规做法。工程实现中通常用固定交织器并把它写进标准因为收发两端必须严格同步同一张表。2.3 打孔与码率的关系puncture参数控制是否删余rate 1/(2puncture)。不删余时码率 1/2删余时 1/3。这里有个容易看反的地方——代码里puncture0打印的是 “Punctured, code rate 1/2”puncture1打印 “Unpunctured, code rate 1/3”。从公式看puncture0时分母是 2puncture1时分母是 3所以变量名和直觉相反值为 0 反而对应“已删余”的 1/2 码率。读这份代码时如果按字面理解puncture很容易在配置实验时把码率搞反这是血泪经验级别的细节。编码输出经encoderm打包后直接叠加高斯噪声r en_output sigma*randn(...)其中sigma 1/sqrt(2*rate*en)en 10^(EbN0db/10)。这里的L_c 4*a*en*rate是信道可靠性度量a1对应 AWGN。rec_s 0.5*L_c*yk把接收样点换算成对数似然比尺度供译码器使用。参数之间的换算关系必须自洽否则仿真出来的 BER 曲线会整体平移看起来像算法出了问题实际是信噪比标定错了。3. Log-MAP 的核心max 加一个修正项到底修了什么3.1 从 ln(e^xe^y) 到雅可比对数Log-MAP 的全部精髓可以压缩成一个恒等式ln(e^x e^y) max(x,y) ln(1 e^(-|x-y|))。代码注释里原样写了这一行。为什么需要它因为 MAP 译码要在网格图上做前向、后向递推每一步都要对多条分支的概率求和而在对数域里“求和”就变成了上面这个运算。直接算ln(e^xe^y)会溢出或下溢取max又丢掉了较小一项的贡献。这个恒等式把求和拆成“取大值 一个只依赖差值的修正项”修正项可以查表既稳又准。如果直接把修正项扔掉只保留max(x,y)就退化成 MAX-Log-MAP。代码里dec_alg选 0 走logmapo完整 Log-MAP选 1 走sova0SOVA另一种软输出算法。MAX-Log-MAP 的复杂度低很多但外信息偏“硬”迭代增益会打折扣通常差零点几 dB。这份实现选择保留完整修正是为了让读者看到性能上界。3.2 前向-后向递推与分支度量logmapo的输入是rec_s该分量译码器看到的接收样点、g生成多项式和L_a来自另一个译码器的先验外信息。内部做三件事算分支度量、前向递推alpha、后向递推beta最后合成每个比特的对数后验比。分支度量里同时包含系统比特项和校验比特项系统比特项会和L_a相加——这就是“先验信息进入译码”的入口。递推过程中每一步都要在进入同一状态的两条分支间做上面那个max 修正运算。状态数只有 4所以每比特的计算量是常数级但整帧L_total个比特、每帧niter次迭代、两个分量译码器轮流跑乘起来就是仿真慢的原因。代码里那句 “Please be patient. Wait a while to get the result.” 不是客套是实话。3.3 外信息的提取与交换译码器输出的L_all是后验对数似然比但它不能直接送给另一个译码器否则会把系统比特的信道信息和先验信息重复计算两遍导致“过度自信”。所以代码做了减法% 第一个分量译码器扣除系统比特信道先验和本次输入先验得到纯外信息 L_e L_all - 2 * rec_s(1, 1:2:2 * L_total) - L_a; % 按交织图案把外信息交给第二个译码器作为先验 L_a L_e(alpha);2*rec_s(1,1:2:2*L_total)取的是系统比特位置上的信道观测偶数索引步进 2乘 2 是对数似然比尺度换算。减掉L_a是因为它已经作为先验参与过本次计算。剩下的L_e才是“本次译码新学到的、关于该比特的信息”把它交织后送给第二个译码器第二个译码器再重复同样的扣除动作把新外信息送回第一个。如此往返niter次每次都在前一轮的基础上增加一点独立证据置信度逐步累积这就是迭代译码“越算越准”的机制来源。最后一轮结束后xhat(alpha) (sign(L_all)1)/2用后验比的符号做硬判决。注意这里用的是L_all而不是L_e因为判决要的是完整后验不是外信息。4. 仿真循环与停止准则帧错误计数为什么比误比特率更靠谱4.1 外层信噪比扫描与内层帧循环结构是两层循环外层for nEN 1:length(EbN0db)遍历每个信噪比点内层while nferr(nEN,niter) ferrlim持续产生新帧直到累计帧错误数达到ferrlim默认 15。每帧重新生成随机信息比特x round(rand(1,L_total-m))、重新生成交织图案、重新过信道。errs和nferr按迭代次数分别记录这样一次仿真就能画出不同迭代次数下的性能对比。为什么用帧错误数而不是固定帧数做停止条件因为在低信噪比下误码率很高固定帧数很快就能统计到足够错误而在高信噪比下误码稀疏固定帧数可能一个错误都碰不到统计结果毫无意义。以“攒够 15 个帧错误”为准则能保证每个信噪比点的统计置信度大致相当这是蒙特卡洛仿真的标准做法。代价是高信噪比点要跑很久这也是仿真时间随EbN0db增大而暴涨的原因。4.2 首帧可视化在验证什么if nframe 1那段画了三张图原始比特、编码后比特、译码后比特。它的价值不在美观而在快速自检——如果译码输出和原始比特肉眼可见地大面积不一致说明参数配置或索引映射有错不必等整轮仿真跑完才发现。我一般会先跑一个高信噪比、少迭代的配置看这三张图是否对齐确认链路通了再去做性能扫描。这个习惯能省下大量“跑了一小时发现全错”的时间。4.3 迭代次数与复杂度的权衡niter默认 5。迭代次数增加外信息累积更充分BER 下降但每增加一轮就多两次全帧 Log-MAP 递推计算量线性增长。实际系统里迭代次数往往卡在 4 到 8 之间再往上增益递减明显。代码把errs和nferr都存成1:niter的向量正是为了让读者直观看到“第 1 次迭代到第 5 次迭代各差多少”从而自己判断拐点在哪。5. 避坑与排查这份实现里最容易翻车的几处5.1 交织索引方向搞反BER 曲线整体抬高现象译码结果比预期差很多但高信噪比下又能部分恢复。原因L_a(alpha) L_e和L_e(alpha)这类赋值依赖alpha的方向约定编码端用alpha重排、译码端必须用同一方向还原一旦把alpha和它的逆排列混用外信息就被送到错误位置。解决固定一帧、关掉噪声检查编码前后和译码前后的比特是否逐位对齐确认索引映射自洽后再开噪声。5.2 信噪比标定与码率不匹配曲线平移现象换puncture设置后同一EbN0db下性能突变且变化幅度不合常理。原因L_c 4*a*en*rate和sigma 1/sqrt(2*rate*en)都含rate码率变了但EbN0的定义基准没跟着核对导致实际信噪比与标称值不符。解决每次改码率后用无噪声或极高信噪比跑一遍确认误码为零再逐步降信噪比观察瀑布区位置是否符合理论预期。5.3 尾比特长度与帧长参数混淆现象L_total设成信息比特数结果编码器状态收不回来高信噪比下仍有残留错误。原因L_total是“信息 尾比特”的总长信息比特实际是L_total - m代码里x round(rand(1,L_total-m))已经体现了这一点。若把L_total当纯信息长度用尾比特就侵占了信息位。解决明确L_total语义需要 N 个信息比特时把L_total设为Nm。5.4 停止准则设太小导致统计不可信现象高信噪比点跑得飞快但 BER 曲线抖动大、不可复现。原因ferrlim设得过小比如 1 或 2帧错误样本太少统计方差大。解决保持ferrlim在 10 以上或对高信噪比点额外增加最小帧数下限牺牲时间换置信度。5.5 把 SOVA 当 Log-MAP 用误判算法退化现象选dec_alg1后性能明显变差怀疑代码有 bug。原因SOVA 本身就是次优的软输出算法外信息精度低于 Log-MAP性能差距是算法固有属性而非实现错误。解决对比实验时固定其他参数只改dec_alg把差距归因到算法而非配置避免在错误方向上排查。6. 设计取舍为什么这份实现坚持“不量化、不近似”这份代码在注释里明确写了 “Log-MAP algorithm without quantization or approximation is used”。这是一个刻意的取舍它牺牲了运行速度换来了一个可以作为“性能基准”的参考实现。工程里的 Turbo 译码器几乎不会这么写——定点化、查表近似、MAX-Log-MAP 甚至更激进的简化都会被用上因为硬件资源和时延是硬约束。但做算法研究时你需要一个“理论上限”来对照否则无法判断某个近似到底损失了多少。取舍维度本实现的选择工程常见做法代价与收益修正函数完整 ln(1e^-x-y)数值表示浮点、不量化定点、位宽受限无量化误差不反映硬件真实表现交织器每帧随机固定、写入标准统计无偏但不可直接移植停止准则帧错误数帧数或时延上限统计可信高信噪比耗时我一般会这么理解这份代码的定位它是一把尺子不是一把锤子。你要验证自己的定点近似方案好不好就拿它的 BER 曲线当参照你要评估某个交织器设计也拿它当基线。真正上手做系统时反而要主动做减法——先把修正项换成查表再把浮点换成定点观察曲线偏移多少偏移可接受就说明简化成立。验证方法上有个习惯值得坚持任何改动之后先在EbN0db取一个较高值、niter取较小值的配置下跑首帧三图对齐检查确认链路没断再放开做全扫描。这个顺序能把“配置错误”和“算法性能”两类问题分开避免在错误的假设上浪费整晚的仿真时间。从那以后我每次动这类迭代译码的仿真脚本都强制先走一遍无噪声对齐再开信道希望帮到你。本文还有配套的精品资源点击获取