极化码SC/SCL译码MATLAB仿真全解析:从信道极化到避坑实践
简介面向极化码研究与学习的一套MATLAB仿真包围绕编码与译码展开涵盖SC逐位取消与SCL逐位取消列表两类译码策略。资源适用于通信方向研究生、工程师及编码理论初学者可用于理解极化码原理、对比不同译码算法性能并搭建或扩展自己的仿真链路。压缩包共34个文件以31个m源码文件为主辅以2个txt说明文档和1个pdf技术文档总大小346KBm文件分工明确覆盖极化码构造、编码、信道输出、SC/SCL译码、误码率统计与性能曲线绘制txt文件提供运行示例pdf对仿真过程作进一步说明。目前已有111人学习下载。实际运行后读者能掌握极化矩阵与比特反转等基础操作熟悉对数似然比更新等关键环节可在不同信道条件下对比SC与SCL的性能增益并通过修改参数完成自定义实验是快速入门极化码仿真的一份实用代码包。1. 极化码仿真SC、SCL译码的MATLAB落地先搞懂编码约定再谈性能做极化码仿真SC、SCL译码的MATLAB工程最怕的不是算法本身有多难而是跑出来的BLER曲线和论文对不上。我见过不少同学拿现成代码改个N和K结果高信噪比下出现误码平台或者SCL译码性能比SC还差——这类翻车大多不在算法推导而在编码端的信息位集合、生成矩阵约定、路径度量更新这些容易被当成“黑匣子”的细节。这篇文章带着你从信道极化原理开始把MATLAB里SC译码和SCL译码的完整落地链路拆开源码能跑、参数能改、坑能提前避开适合正在做课程设计或起步做极化码研究的开发者。2. 信道极化与编码端落地生成矩阵、信息位排序与参数约定2.1 信道极化的两条主线可靠性分化与信息位选择极化码的基本思想是把N个独立信道通过合并与分裂变成N个可靠性两极分化的子信道一部分信道容量趋近1一部分趋近0。趋近1的用来传信息比特趋近0的固定发已知值冻结位。仿真时真正需要量化的不是信道容量本身而是每个子信道的可靠度排序。排序方法里巴氏参数是最早的度量但它在AWGN信道下没有闭式解需要数值积分或者蒙特卡洛估计写起来麻烦。我一般直接用极化权重Polarization WeightPW这也是目前工程实现里最省事的选择对索引i的二进制展开权重按β^j累积β取2^(1/4)值越大代表子信道越可靠。这个近似在N不超过1024时和密度进化结果非常接近做课程设计完全够用。参数约定上仿真链路一般固定成这样参数取值说明码长N64 / 128 / 256仿真入门建议从N64或128开始信息位KN/2码率RK/N通常取0.5调制方式BPSK单符号映射便于LLR初始化信道AWGN加性高斯白噪声信噪比指标Eb/N0需结合码率换算噪声方差这一段定下来后面编码和译码的所有索引逻辑都围绕它走。2.2 生成矩阵构造与信息位选择N8的完整算例编码端最容易出问题的是生成矩阵的约定。Arikan原始论文里的生成矩阵是G_N B_N F^{⊗n}其中B_N是比特反序置换矩阵而很多MATLAB工程为了直接用kron函数实际用的是G F^{⊗n}两者差了B_N的置换。这不是实现差异是同一码字集合的两种输入排列。我采用的做法是生成矩阵用G F^{⊗n}然后对信息位索引做一次比特反序这样编码端不需要额外乘B_N矩阵。function [G, infoIdxRev] polar_setup(N, K) % 构造生成矩阵 G F^(⊗n) n log2(N); F [1 0; 1 1]; G 1; for k 1:n G kron(G, F); % 逐级克罗内克积 end % 用PW方法估计子信道可靠度 beta 2^(1/4); pw zeros(1, N); for i 0:N-1 for j 0:n-1 if bitget(i, j1) % 第j位为1则累加权重 beta^j pw(i1) pw(i1) beta^j; end end end [~, idx] sort(pw, ascend); infoIdx idx(end-K1:end); % PW值最大的K个位置 % 比特反序置换让编码直接使用 G F^(⊗n) revIdx bitrev_indices(N); infoIdxRev revIdx(infoIdx); % 用于编码与译码的信息位位置 end function r bitrev_indices(N) n log2(N); r zeros(1, N); for i 1:N x i - 1; rev 0; for j 1:n rev rev * 2 mod(x, 2); x floor(x / 2); end r(i) rev 1; end end逻辑说明kron(G, F)循环n次得到F的n次克罗内克幂对应nlog2(N)。PW部分对每个索引i做二进制位遍历bitget(i, j1)取第j位累加权重beta^j这样索引7二进制111的PW值最大自然被选为最可靠信道。排序后取末K个作为信息位集合再通过bitrev_indices把自然序索引映射到反序域保证编码端用GF^{⊗n}时与原始论文定义等价。参数说明N必须是2的幂K取N/2时R0.5。beta取2^(1/4)是固定经验值不要改。bitrev_indices返回的是1-based置换向量输入1会返回1输入2二进制10在n3时返回3二进制011反序后110即6加1为7……这里所有索引统一1-basedMATLAB里数组索引别混用。以N8、K4为例PW排序后最可靠4个信道是索引7、6、5、30-based计数对应MATLAB的8、7、6、4。经过比特反序后编码时信息位落在反序位置。这个顺序直接影响第3章译码判决时哪些位是信息位、哪些是冻结位编码和译码必须共用同一个infoIdxRev。建议把这一步单独抽出来每次跑仿真前先用一个简单测试随机生成信息比特编码再译码检查能否完全还原能通过再开始蒙特卡洛仿真。3. SC译码的LLR递归实现f/g节点、部分和与数值边界3.1 从根到叶子的判决顺序f函数与g函数的角色SC译码本质是在一棵二叉树上前向计算、后向判决。根节点接收信道LLR然后逐层向下每个长度为L的节点根据父节点的两条输入消息先算出左子节点的消息递归完成左子树判决再用左子树的判决结果去算右子节点的消息递归右子树。叶子节点长度为1做硬判决判断该位是0还是1如果该位置是冻结位直接强制为冻结值0。两个核心节点函数在LLR域最常用min-sum近似实现f函数用于计算左子消息。对两个输入(a,b)它的含义是“合并信道分裂后左侧子信道的对数似然比”完整形式是2·atanh(tanh(a/2)·tanh(b/2))但工程上直接用近似sign(a)·sign(b)·min(|a|,|b|)性能损失约0.1到0.2dB对课程级仿真完全可接受。g函数用于计算右子消息它依赖左子树的判决结果u。公式是b (1-2u)·a。当u0时是ab当u1时是b-a。这个“左子树判决反馈”就是SC译码“逐次消除”名称的来源——后面比特的判决依赖于前面所有比特的结果一旦前面判错错误会传播。这也就解释了为什么SC对中短码性能有限第一个信息比特只有一次估计机会没有后续纠正机制。SCL正是在这里引入了路径保留第4章会展开。3.2 可抄作业的SC译码框架与数据约定下面这个实现是递归结构N在64到512时都能直接跑。保存为sc_decode.m配合polar_setup生成的infoIdxRev使用。function u_hat sc_decode(llr_ch, infoMask, frozenVal) % llr_ch: 1xN 信道LLR % infoMask: 1xN 逻辑向量1信息位0冻结位 N numel(llr_ch); u_hat zeros(1, N); part zeros(1, N); % 部分和数组存叶子判决 function decode_rec(lo, hi, msg) len hi - lo 1; if len 1 u double(msg(1) 0); % LLR0判1 if infoMask(lo) 0 u frozenVal(lo); % 冻结位强制置固定值 end part(lo) u; u_hat(lo) u; return; end half len / 2; % 计算左子节点消息 left zeros(1, half); for j 1:half left(j) f_node(msg(j), msg(jhalf)); end decode_rec(lo, lohalf-1, left); % 用左子树判决结果计算右子节点消息 right zeros(1, half); for j 1:half right(j) g_node(msg(j), msg(jhalf), part(loj-1)); end decode_rec(lohalf, hi, right); end decode_rec(1, N, llr_ch); end function y f_node(a, b) % min-sum近似 y sign(a) * sign(b) * min(abs(a), abs(b)); if y 0 y sign(a) * sign(b) * 1e-15; % 避免完全置零导致下游NaN end end function y g_node(a, b, u) y b (1 - 2*u) * a; end逻辑说明decode_rec用[lo, hi]表示当前节点对应u序列的区间msg是该节点接收到的LLR向量。叶子节点判决后写入part数组这个part在回溯阶段被g函数读取。f_node中的1e-15处理是为了防止min-sum出现严格0值后sign信息丢失实际AWGN仿真里LLR几乎不会严格为0但加了不影响结果。参数说明infoMask由infoIdxRev生成1表示该位置是信息位frozenVal是1xN的0向量。信道LLR的初始化在仿真主循环里完成BPSK调制下x1-2*bitsyxnoiseLLR2·y/σ²。噪声方差根据Eb/N0换算σ² 1/(2·R·10^(EbN0_dB/10))RK/N。很多曲线偏移1.8dB的问题就出在这里后面避坑章专门提。译码前务必做一次自检把编码后的码字加很小噪声或直接无噪声传入sc_decode看u_hat是否等于编码输入u。不通过就直接检查infoMask和frozenVal的生成逻辑。4. SCL译码的路径分裂与剪枝列表大小、路径度量与CRC4.1 SCL和SC的区别硬判决改成路径分裂SC在叶子节点做硬判决后就没有回头路SCL则把“唯一判决”换成“多条候选路径”。每到一个叶子每条存活路径分裂成两条一条判0、一条判1然后从总共2L条候选路径里挑LLR路径度量最好的L条继续跑。最终在最后一个叶子选出唯一路径输出。路径度量Path MetricPM是SCL的核心。常用的简化形式是对每条候选路径如果选的路与该位置LLR的硬判决一致PM不变不一致PM加上|LLR|。累加PM越小代表路径越可信。这个近似在性能上几乎无损还避免了指数和对数运算是MATLAB实现里最稳的选择。冻结位的处理也变了SC里直接强制为固定值SCL里冻结位同样不允许分裂成两条只允许走冻结值方向。如果LLR硬判决和冻结值冲突这条路径的PM要加上|LLR|作为惩罚保留下来但会排在后面。4.2 路径度量的更新与Top-L选择下面这段是SCL译码在单个叶子处的核心操作放在一个循环里对L条路径做分裂和筛选。% 每个叶子位置i执行的伪代码块 % paths: LxN 已判决的比特路径 % pm: 1xL 当前路径度量越小越好 % llr_i: 1xL 当前叶子位置各路径的LLR % infoMask(i)0表示冻结位 L size(paths, 1); candNum L * 2; candPM inf(1, candNum); candPaths zeros(candNum, N); for p 1:L hardBit double(llr_i(p) 0); if infoMask(i) 0 % 冻结位只允许 frozenVal candPaths(2*p-1, :) paths(p, :); candPaths(2*p-1, i) frozenVal(i); candPM(2*p-1) pm(p) abs(llr_i(p)) * (hardBit ~ frozenVal(i)); else % 信息位正常分裂成0和1 candPaths(2*p-1, :) paths(p, :); candPaths(2*p-1, i) 0; candPM(2*p-1) pm(p) abs(llr_i(p)) * (hardBit ~ 0); candPaths(2*p, :) paths(p, :); candPaths(2*p, i) 1; candPM(2*p) pm(p) abs(llr_i(p)) * (hardBit ~ 1); end end [~, idx] sort(candPM, ascend); keep idx(1:L); paths candPaths(keep, :); pm candPM(keep);逻辑说明每条路径在叶子处生成候选冻结位只产生一个候选代码里占两个位置但第二个一个设为inf代码实际上对冻结位只写了一个候选但candNum还是2L也就是说candPM里第二个候选留了inf排序后自然被筛掉。为了可读性这里冻结位写单候选后面排序前要把未填充的候选PM设为inf上面代码已经用inf初始化所有候选。信息位产生0和1两个候选度量更新用hardBit判断是否与LLR硬判决一致不一致加|LLR|惩罚。参数说明L是列表大小常用4/8/16。L越大性能越好但内存和计算量线性增长。N128时L8的SCL耗时大约是SC的8到15倍主要开销在路径复制和排序。sort的候选数最多2L规模很小瓶颈其实在每一路径还要同步复制左/右消息缓存。我这里只贴了路径选择部分完整SCL必须把每条路径的节点消息一起存下来——常见做法是维护三维数组cache(2N, maxLen, L)每个叶子分裂时把旧路径的缓存复制给新路径。4.3 CRC辅助带来的增益把校验比特放进冻结位SCL真正在中短码上超越SC靠的是CRC辅助。原理不复杂编码时在信息比特末尾附加r位CRC校验这r位也参与极化编码作为信息位的一部分译码时SCL不再直接选PM最小的路径而是先看哪条路径能通过CRC校验再在通过校验的路径里选PM最小的输出。如果所有路径都不过CRC才退化为选PM最小路径。实现时注意CRC位穿插的位置。常见做法是CRC比特放在信息位序列末尾然后一起映射到极化信道的信息位集合。由于信息位集合是按可靠度选的CRC位会被分配到相对靠后的可靠信道这让SCL译码器在路径竞争中把校验当作额外的约束条件错误路径被剪枝的概率大幅提高。一个容易忽略的点CRC长度不是越大越好。r8时增益明显r16在中短码下增益有限但计算量增加还会占用原本属于信息比特的信道位置。仿真对比时不要只换L要同时列CRC配置否则结果没法横向比较。5. 避坑SC/SCL仿真里最容易翻车的五个细节5.1 误码率平台高信噪比下BLER掉不下去现象仿真跑到Eb/N03dB以上时BLER曲线不再下降形成一个水平平台无论增加多少帧都没有改善。原因信息位集合和编码矩阵的约定不一致。最常见的是直接把PW排序选出的自然序索引当作编码输入位置而生成矩阵用的是F⊗n缺少比特反序步骤。两种约定混用时译码端永远无法正确恢复信息位误码率被冻结在固定水平。另一个常见原因是信息位数量K和码率R在噪声方差换算里没统一导致仿真工作在错误的等效信噪比下。解决把polar_setup里的infoIdxRev单独打印出来和手工计算一遍的比特反序结果核对。然后做无噪声自检随机生成K个信息比特编码后直接译码u_hat必须和u完全一致。这个测试跑不过先不要看任何性能曲线。5.2 LLR出现NaN或Inf高信噪比下数值爆炸现象Eb/N0大于5dB后仿真报NaN或Inf译码结果全错或部分错。原因BPSK解调里LLR2y/σ²高信噪比时σ²非常小一个小的y噪声波动除以σ²后数值可能远超double类型的稳定范围尤其N较长时递归计算会放大。SCL路径度量里如果用了exp/log形式的完整PM同样会溢出但|LLR|近似形式不容易出现这个问题。解决LLR初始化时加截断常见做法是限制在[-20, 20]区间超过就按边界处理。高信噪比下这个截断对性能影响几乎为零但能彻底消除NaN源头。SCL统一用|LLR|近似的PM不要混用完整形式。5.3 SCL性能比SC还差路径选择或消息复制错了现象L从1增加到8BLER曲线反而变差或者L1时SCL结果和直接跑SC不一致。原因SCL在路径分裂后新路径的左右消息缓存没有正确复制只是把路径比特序列复制了导致后续叶子处算出的LLR是旧路径的新路径的消息错位。另一个原因是排序和索引映射混乱candPaths矩阵的行号排序后没有同步更新对应的消息缓存和部分和数组。解决先做一个小规模验证——在同一帧上同时调用SC和SCL(L1)输出必须一致这是SCL正确性的最低门槛。然后在L2时对比PM排序结果理清每条路径的下标映射。消息缓存用三维数组且随路径复制这个内存开销不能省。5.4 仿真时间不可接受嵌套循环和重复计算现象N256、L16时跑一帧要几十毫秒十万帧根本不敢想。原因SCL实现里每个叶子分裂时都从根节点重新计算所有祖先节点的消息复杂度从O(N log N)恶化到接近O(N² log N)。或者每条路径独立调用一次完整SC译码递归导致L倍的重复计算。解决改成树形消息缓存每个叶子分裂只更新该叶子到根路径上的节点。用迭代从底向上处理叶子替代每次从根做递归。如果只是验证算法先跑N64、L4确认性能和趋势没问题再放大参数。真需要大规模蒙特卡洛建议把核心译码函数改写成MEX或直接用C语言。5.5 Eb/N0曲线整体偏移约1.8dB噪声方差换算错误现象所有性能曲线相对论文整体向右或向左平移形状不变但横坐标偏移。原因Eb/N0和Es/N0混用。BPSK下EsEb·R噪声方差σ²N0/21/(2·10^(EsN0_dB/10))。如果把Eb/N0直接当成Es/N0代入就少了R的换算R1/2时会差3dB反过来也会偏。有些代码用EsN0EbN010·log10(R)R1/2时约为-3.01dB。解决仿真主循环里统一按σ² 1/(2·R·10^(EbN0_dB/10))计算。如果要画横轴是Es/N0的图再单独换算。这个检查放在最前面不然后面所有参数调整都在错误前提下进行。6. 进阶仿真加速、内存优化与BLER曲线可信度验证SCL在MATLAB里的性能瓶颈往往不是算法本身而是路径消息的复制方式。我习惯把缓存从double降到singleN256、L8时内存能省一半速度提升明显性能损失在BLER曲线上几乎看不见。另一个常用技巧是把f_node和g_node的min-sum计算向量化同一个节点层内所有位置的消息更新没有数据依赖可以用矩阵运算一次性算完整个左子树或右子树递归循环次数从N量级降到log2(N)量级。我还会在译码器里加一个早期剪枝已经确定所有信息位都判完时提前结束递归。这个优化在CRC辅助的SCL里尤其有效因为最后一个信息位之后只剩冻结位路径不需要继续分裂。还有一种做法是把SCL的sort改成部分排序因为只需要保留top L不需要把2L个候选全部排好序MATLAB里可以用maxk的变体实现实测能省10%到20%的时间。BLER曲线可信度验证我通常按三步走。第一先跑SC和已知论文或标准参考仿真对比趋势不要求完全重合但在同参数下相差不能超过0.5dB。第二跑SCL L4和L8确认L增加后性能单调变好如果哪个点变差了优先回去查路径复制逻辑。第三最后一个数据点帧数要足够估计BLER约为1e-3时至少要跑10万帧才能让曲线尾部不发散。每次换参数后先跑一个小规模比如100帧确认不报错再上大规模。按这个流程我把N128、K64、BPSK的仿真从一版总出NaN、曲线乱飞的Demo改到能稳定复现SC与SCL的增益差前后花了一天多不算长但代价是踩完上面五个坑。从那以后我每次跑仿真前都强制走一遍“编码—译码自检—SC对照—SCL对照”的固定流程这套习惯帮我省掉了大量排错时间。希望帮到你。本文还有配套的精品资源点击获取