ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于MATLAB的Polar码与SCL译码仿真框架及误码率分析

基于MATLAB的Polar码与SCL译码仿真框架及误码率分析 简介本资源是一份面向通信工程专业高年级本科生及研究生的Polar码性能仿真教学材料聚焦极化码在实际通信链路中的误码率评估问题涵盖编码、QPSK调制、AWGN信道建模与SCL译码列表长度L1/2/4全流程实现。压缩包共16个文件含14个MATLAB源程序如主运行脚本Runme1_Polar_code_SCL.m、SCL_CRC译码核心函数、信道构造与CRC校验模块等、1张关键参数说明图及1段时长约10分钟的操作录屏AVI格式整体大小仅2.07MB轻量易部署。已有934人学习下载配套录像详细演示路径设置、参数调整与BER曲线绘制全过程并对核心代码逐段注释显著降低理解门槛特别适合开展课程设计、毕业设计或深入理解5G候选编码方案的实践需求。1. 仿真框架设计与核心原理1.1 系统模型与各模块选型先聊聊为什么选择这套组合。Polar编码是当前信道编码领域里少数能被严格证明达到香农极限的编码方案从理论提出到进入5G增强移动宽带场景的eMBB控制信道也就短短几年时间。它的核心思想是通过信道极化把物理信道拆分成一组容量各异的虚拟比特信道好的信道传信息比特坏的信道传冻结比特。这套编码的独特之处在于结构明确、编解码复杂度可控而且特别适合在MATLAB里做算法级验证。调制方式选用QPSK而不是更高阶的16QAM或64QAM主要考虑是QPSK在AWGN信道下每个符号携带2比特信息误码率曲线斜率清晰便于观察编码增益。而且Polar码在低阶调制下的匹配度更好能够直接对比未编码QPSK和编码后的性能差距。如果一上来就用高阶调制误码率曲线会受到符号判决错误模式的影响不容易把Polar码本身的增益从调制损失中分离出来。信道模型选定AWGN也是经过权衡的。AWGN是通信系统仿真中最标准的信道模型噪声服从零均值高斯分布只有一个参数——噪声方差。它虽然简单却能够直接反映编码方案在纯噪声环境下的性能上限。相比之下瑞利衰落、莱斯衰落这类信道模型引入了信道状态信息的变量会让仿真结果里混入分集增益和信道估计误差不适合单独评估Polar码和SCL译码算法本身的性能。SCL译码算法为什么是这个组合的压轴因为SCL(串行抵消列表)在Polar码译码的“性能-复杂度”权衡中处于甜点位置。基础的SC(串行抵消)译码在码长较短的场景下性能退化明显尤其是中等码长(比如512或1024比特)时SC译码会明显偏离最大似然性能。而SCL通过保留多条候选路径最终输出路径度量最优的结果性能能够逼近最大似然译码付出的代价只是路径数的线性增长。这套方案适合谁来参考正在做信道编码课程设计、通信系统仿真毕业设计、或者准备5G物理层编码相关面试的人都能从这套仿真框架里找到可以直接落地的内容。整个工程从编码构造、调制映射、AWGN信道建模到SCL译码器和误码率统计每个环节都可以独立修改验证这对理解Polar码的完整链路非常有帮助。1.2 Polar码、QPSK调制、AWGN信道与SCL译码的衔接逻辑这套仿真链路不是几个模块的简单拼接每个环节之间的接口设计都有讲究。首先是Polar编码的构造。码长N2^n信息位长度K码率RK/N。编码的核心是生成矩阵G F^⊗nF为2×2极化核然后从N个极化子信道中挑选出K个可靠度最高的位置承载信息比特剩余的位置放置冻结比特通常设为0。这个“可靠度排序”是Polar码构造里最关键的一步MATLAB实现时可以选择密度演进、高斯近似或直接查表。在实际工程中我通常用高斯近似来计算巴氏参数或者错误概率因为它在中等码长下精度足够且计算速度快。需要注意的是不同信噪比下计算出的最优信息位集合可能不同。在误码率仿真中如果瞄准的目标信噪比区间比较宽比如0dB到8dB建议以目标工作信噪比区间的中点为参考进行构造这样整个区间内性能都不会太差。然后是编码完成后的比特处理顺序。Polar编码器输出的是编码后的N个比特这些比特在送入QPSK调制器之前需要经过一个“比特交织”或“符号映射顺序”的处理。最简单的做法是按顺序每两个比特映射成一个QPSK符号映射关系采用格雷映射即00 → (1j)/√201 → (-1j)/√211 → (-1-j)/√210 → (1-j)/√2使用格雷映射的原因是相邻符号之间只有一个比特不同这样在AWGN信道下发生符号错误时大概率只有一个比特错误有利于后续译码器纠错。如果使用自然映射00、01、10、11顺序映射符号判决错误时可能同时翻转两个比特误码率会明显恶化这个细节在某些教科书代码里经常被忽略。AWGN信道的建模同样有讲究。发送端QPSK符号经过功率归一化后在接收端叠加复高斯白噪声噪声方差由信噪比决定。这里有个容易混淆的地方信噪比定义的是每符号信噪比(E_s/N_0)还是每比特信噪比(E_b/N_0)。在误码率仿真中我们通常需要的是E_b/N_0也就是每信息比特的能量与噪声功率谱密度之比因为不同码率R的编码方案要在相同E_b/N_0下比较才有意义。换算关系是E_s/N_0 E_b/N_0 10log10(Rm)其中m是每个调制符号的比特数QPSK下m2。比如码率R1/2时E_s/N_0比E_b/N_0低3dB。很多新手画误码率曲线时横轴用的还是E_s/N_0结果不同码率的曲线根本无法横向比较这是我见过最多的低级错误。最后是SCL译码器与QPSK解调器的接口。SCL译码器接收的是信道对数似然比(LLR)而不是硬判决比特。所以QPSK解调器要做的是“软解调”也就是根据接收到的复数符号计算每个比特的LLR。数学上说对于接收符号y h*x nAWGN下h1发送符号x的实部为x_I虚部为x_Q。每个比特位LLR的计算需要对所有可能的星座点进行边缘化求和。不过由于QPSK的I/Q两路完全独立计算可以简化为LLR(b_I) (2/σ²) * Re(y) * dLLR(b_Q) (2/σ²) * Im(y) * d其中d是星座点到原点的投影距离对归一化QPSKd 1/√2σ²是复噪声的方差。这个简化推导说明了一个重要事实QPSK软解调不需要遍历四个星座点因为I/Q路的对称性让计算量降到了最低。这个细节能显著简化MATLAB代码也让仿真速度提升不少。2. SCL译码器实现要点从路径分裂到性能权衡2.1 SCL“列表”机制的深度解读SCL译码器本质上是在SC译码的基础上增加了一个“候选路径列表”。SC译码每译一个比特只保留一条路径——如果这个比特判决错误后续所有比特都跟着错错误会快速传播。SCL的思路则是每个信息比特位置同时保留L条最优路径L是列表大小等全部比特译完后再从L条路径中挑一条路径度量值最好的作为最终结果。这个“同时保留L条路径”的实现方式非常精妙。每条路径实际上是一棵深度为N的二叉树的搜索路径叶子节点对应一个完整的译码结果。SCL是广度优先的树搜索策略在每一层保留L个最优节点其余的分支全部剪掉。这个做法和Turbo码迭代译码、LDPC置信传播在思想层面完全不同它是直接从“搜索”角度逼近最大似然性能的。在MATLAB实现中SCL的一个核心数据结构是路径度量值(Path Metric, PM)数组。每译一个比特所有现有路径都会分裂成两条候选路径一条判为0、一条判为1。对于冻结比特因为发送端已知固定为0所以只有判为0的分支保留判为1的分支对应的路径度量会被置为无穷大然后在下一轮排序时被剔除。路径度量的更新规则是当本次判决与LLR符号一致时路径度量不变PM_new PM_old当本次判决与LLR符号不一致时路径度量加上|LLR|作为惩罚PM_new PM_old |LLR|这个规则的直观理解是LLR的绝对值越大说明信道对这个比特的判决把握越强那么如果译码器偏偏选了另一个方向这条路径的不可信程度就越高路径度量惩罚也越大。最终路径度量值最小在LLR域或最大在概率域的路径就是译码结果。我习惯用LLR域的加性度量MATLAB实现时不需要反复做指数运算稳定性更好。2.2 CRC辅助如何让SCL性能质变SCL的列表机制虽然好但它存在一个“尾部效应”随着译码深度增加真正的正确路径可能在中途因为一次惩罚被淘汰。为了缓解这个问题实践中普遍采用CRC辅助的SCL译码也就是CA-SCL。这个方案的含义很直白在发送端的信息比特后面附加一段CRC校验比特比如CRC8或CRC16CRC比特也作为信息比特参与Polar编码。译码时SCL译完N个比特得到L条候选序列后先对每条序列进行CRC校验把校验不通过的路径直接剔除。如果有多条路径通过校验选路径度量最优的那条如果所有路径都校验失败只能在L条路径中选路径度量最好的。我在仿真中强烈建议加上CRC辅助。从性能曲线看没有CRC的SCL在列表大小L16时误码率大概在10^-3量级会出现一个平台加CRC后这个平台直接被压下去了。原因在于SCL最后的路径度量本质上是一个软信息它和真实的后验概率之间存在近似误差而CRC提供了一个硬约束强行纠正了软信息的偏差。CRC长度的选择也值得推敲。在K256信息比特、L16的场景下我用CRC8就能获得大部分增益用CRC16还能再提升0.1~0.2dB但增加8比特开销。实际中K越大CRC长度需要越长。一个工程经验是K在128~512比特区间CRC长度8~16比特都是合理选择K超过1024时建议直接用CRC24或更长的校验码。2.3 仿真复杂度的几个关键控制点SCL译码器的计算复杂度大约是O(L·N·logN)即列表大小乘上码长乘上极化层数。这个复杂度在短码仿真中完全可接受但真正拖慢仿真速度的往往是路径排序操作。每译一个信息比特需要把2L条路径按度量值排序保留L条如果你在MATLAB里直接调用sort函数对2L×1数组排序会产生大量的函数调用开销。我的做法是把路径管理分成两段对于冻结比特不需要排序直接在现有路径上扩展唯一分支即可。对于信息比特先对现有L条路径按度量值做一次预排序MATLAB的sort函数扩展时只需要把前L条路径的0分支和1分支分别放到数组的前后两半然后做一次部分排序截断即可。这个优化能让仿真速度提升两到三倍。另外MATLAB的for循环在译码器内部逐比特迭代是不可避免的但尽量把LLR数组操作向量化特别是极化层的组合运算用矩阵运算代替逐元素循环。内存管理同样需要注意。SCL译码器需要为每条路径保存每个极化层的LLR数据如果N1024、L32每个路径的LLR存储是N·logN级别的复数数组32个路径同时保存会占用不小的内存。我在仿真中采用“按需释放”的策略每条路径在分裂时只复制路径对应子树的LLR段而不是整棵树的完整副本。这样能显著减少内存碎片和复制开销实测在码长1024、列表32时单次译码的内存占用能降低约40%。3. MATLAB仿真实操过程从零搭建可复现的误码率平台3.1 代码架构与关键模块实现整个仿真程序我建议拆成四个文件主脚本、编码构造函数、SCL译码函数、误码率统计函数。这种拆分的好处是修改某个环节时不需要动其他代码尤其是构造函数和译码函数在算法调参时几乎是反复改的。先看主脚本的核心参数区%% 系统参数配置 N 256; % 码长 K 128; % 信息比特数含CRC比特 R K / N; % 码率 L 16; % SCL列表大小 crc_len 8; % CRC比特数 EbN0_dB 0:0.5:5; % 信噪比扫描范围 num_frame 1000; % 每个信噪比下的仿真帧数这里有个细节需要理解K包含了CRC比特那么实际的信息比特其实是K-crc_len。在计算码率R时也必须用K/N因为Polar编码器的输入确实包含了CRC比特它们同样要在信道上传输。有人会把R算成(K-crc_len)/N这是不对的那会高估系统的实际传输效率。编码构造函数我用高斯近似实现function [info_bits, frozen_bits] polar_design(N, K, design_ebn0) % 高斯近似计算各子信道错误概率 n log2(N); z zeros(1, N); % 初始化信道LLR的均值为 2/σ² sigma2 10^(-design_ebn0/10); z(:) 2 / sigma2; for layer 1:n half 2^(n-layer); for j 1:half % 极化更新规则 z_odd 1 - (1-z(j))^2; z_even 2*z(j) - z(j)^2; z(j) z_odd; z(jhalf) z_even; end end [~, idx] sort(z, descend); info_bits sort(idx(1:K)); frozen_mask true(1, N); frozen_mask(info_bits) false; frozen_bits find(frozen_mask); end这个构造函数的本质是用递归公式计算每个子信道的错误概率zz值越大说明这个子信道越差越应该放冻结比特。实际使用中design_ebn0通常选在目标工作点附近比如仿真区间是0~5dB构造信噪比取2~3dB就是不错的选择。编码器的实现则要简单得多核心用克罗内克积生成的生成矩阵function codeword polar_encode(msg, N) n log2(N); F [1 0; 1 1]; G 1; for i 1:n G kron(G, F); end % 比特反转置换 G G(bitrevorder(1:N), :); codeword mod(msg * G, 2); end这里需要注意比特反转。Polar码原始定义中的生成矩阵需要经过比特反转操作才能直接对应信道极化顺序如果跳过这一步编码结果看起来“能用”但和SCL译码器内部的极化结构是对不上的误码率会有一段莫名其妙的损失。QPSK调制和AWGN信道的关键代码%% QPSK调制 mapped (1-2*bits(:,1:2:end)) / sqrt(2) 1j*(1-2*bits(:,2:2:end)) / sqrt(2); %% AWGN信道 Es mean(abs(mapped(:)).^2); % 符号能量 EbN0_lin 10^(EbN0_dB/10); N0 Es / (R * 2 * EbN0_lin); % 噪声功率谱密度 noise sqrt(N0/2) * (randn(size(mapped)) 1j*randn(size(mapped))); rx mapped noise;这段代码里的Es计算我用的是实测均值而不是理论值。原因是虽然QPSK映射理论上符号能量是1但实际序列有限长度时功率可能有微小偏差。用实测值能保证不同统计帧之间噪声方差的一致性避免随机波动影响误码率曲线的平滑度。软解调模块也很直观%% QPSK软解调输出LLR llr zeros(size(bits)); llr(:,1:2:end) 2 * sqrt(2) * real(rx) / N0; llr(:,2:2:end) 2 * sqrt(2) * imag(rx) / N0;这个公式的推导隐含了一个前提发送符号经过能量归一化星座点坐标为(±1/√2, ±1/√2)。如果你的映射方式发生了变化这个系数也要相应调整否则LLR的幅度会整体缩放SCL路径度量的相对关系就不对了。3.2 SCL译码器的完整实现与注释SCL译码器是整个仿真工程里最复杂的部分我给出一个带充分注释的完整实现方便直接在工程里使用。这个版本针对N256、列表L16做了优化路径管理。function decoded_bits scl_decode(llr, L, info_bits, frozen_bits, N) % SCL译码器主函数 % 输入llr 1xN信道LLR, L列表大小, info_bits信息位索引, frozen_bits冻结位索引 % 输出decoded_bits 1xN译码结果 n log2(N); % 初始化路径列表 % 每条路径保存当前的部分译码结果、路径度量和各层LLR数组 paths struct(bits, zeros(1, N), pm, 0, llr_layer, []); paths repmat(paths, L, 1); paths(1).llr_layer cell(n1, 1); paths(1).llr_layer{n1} llr; % 最底层LLR就是信道软信息 num_paths 1; % 当前活跃路径数 for bit_idx 1:N % 逐比特进行SCL译码 is_frozen ~ismember(bit_idx, info_bits); if is_frozen % 冻结比特只能取0只扩展一条分支 for p 1:num_paths new_llr update_llr(paths(p).llr_layer, bit_idx, n); % 冻结比特判0如果LLR0说明判0正确否则加惩罚 if new_llr 0 paths(p).pm paths(p).pm abs(new_llr); end paths(p).bits(bit_idx) 0; end else % 信息比特每条路径分裂成两条候选 new_paths repmat(paths(1), num_paths*2, 1); for p 1:num_paths new_llr update_llr(paths(p).llr_layer, bit_idx, n); % 分支1判0 new_paths(2*(p-1)1) paths(p); new_paths(2*(p-1)1).bits(bit_idx) 0; if new_llr 0 new_paths(2*(p-1)1).pm paths(p).pm abs(new_llr); end % 分支2判1 new_paths(2*(p-1)11) paths(p); new_paths(2*(p-1)11).bits(bit_idx) 1; if new_llr 0 new_paths(2*(p-1)11).pm paths(p).pm abs(new_llr); end end % 按路径度量排序保留L条最优 [~, sort_idx] sort([new_paths(1:min(2*num_paths, L*2)).pm]); keep sort_idx(1:min(L, 2*num_paths)); if 2*num_paths L paths(1:L) new_paths(keep); num_paths L; else paths(1:2*num_paths) new_paths; num_paths 2*num_paths; end % 关键一步更新各路径的LLR层数据 for p 1:num_paths if ismember(p, keep) || 2*num_paths L update_llr_array(paths(p), bit_idx, n); end end end end % 返回路径度量最优的译码结果 [~, best] min([paths(1:num_paths).pm]); decoded_bits paths(best).bits; end这段代码中有一个简化update_llr函数和update_llr_array函数没有完整展开。它们的核心是递归计算极化网络中每个节点的LLR。在MATLAB实践中我建议把LLR更新函数单独写成独立的函数文件因为它在每次译码中会被调用N·logN次属于性能瓶颈需要单独优化。update_llr的核心公式是奇数位置节点f节点LLR_out sign(LLR_a) * sign(LLR_b) * min(|LLR_a|, |LLR_b|)偶数位置节点g节点LLR_out LLR_b (1-2*u) * LLR_a其中u是已经译出的上一层比特。这个近似最小和近似大大简化了计算性能损失在0.1dB以内在SCL里完全可接受。如果追求极致性能可以换成精确的tanh公式但仿真速度会慢很多。3.3 误码率统计与曲线绘制技巧误码率统计需要分清楚“误码率(BER)”和“误帧率(FER)”两个指标。BER是错误比特数占总传输比特数的比例FER是错误帧数占总传输帧数的比例。在短码场景下一帧通常包含多个比特一次误码事件往往会引起整帧错误所以FER的值通常比BER高一个量级左右。两个指标都有参考价值我建议都统计。统计的代码实现思路如下for ebno_idx 1:length(EbN0_dB) total_bit_err 0; total_bit_cnt 0; total_frame_err 0; for f 1:num_frame % 随机生成信息比特 msg randi([0 1], 1, K); % 加CRC msg_crc [msg, crc_encode(msg, crc_len)]; % Polar编码 codeword polar_encode(msg_crc, N); % QPSK调制、AWGN、软解调 % ...与3.1节代码一致 % SCL译码 decoded scl_decode(llr, L, info_bits, frozen_bits, N); % 提取译码后的信息位 decoded_msg decoded(info_bits); % 统计误码率 bit_err sum(decoded_msg(1:K-crc_len) ~ msg); total_bit_err total_bit_err bit_err; total_bit_cnt total_bit_cnt (K - crc_len); if bit_err 0 total_frame_err total_frame_err 1; end end ber(ebno_idx) total_bit_err / total_bit_cnt; fer(ebno_idx) total_frame_err / num_frame; end这个统计逻辑里有个细节CRC校验位在接收端提取译码结果时也需要参与运算。如果是CA-SCLCRC校验通过的候选路径优先如果是普通SCL不加CRC辅助直接从L条路径中选路径度量最优的那么统计误码率时只统计原始消息比特部分。画图时的技巧是使用semilogy对数坐标因为误码率在中等信噪比区间下降非常快线性坐标根本看不清楚。横轴建议用E_b/N_0单位dB这样可以公平比较不同码率的编码方案。如果同一个图里画多条曲线不同list大小的SCL最好用不同的标记样式和颜色并加上legend方便对比。还有一个我反复强调的点每个信噪比点的帧数设置。如果你只仿真几百帧误码率统计到10^-3量级就会有很大波动因为此时一帧里可能一个错误都没有也可能连续几帧都出错。合理的做法是每帧统计后记录错误帧数当错误帧数达到一定阈值比如100帧时提前结束该信噪比点的仿真这样既能保证置信度又能避免在低误码率区间无谓地跑几万帧。3.4 操作录像与演示流程设计这个项目附带的“程序操作录像”部分是把整个仿真从一个“静态代码”变成“可复现实验”的关键一环。录像不是随便录一段屏幕操作的而是要精心设计演示流程让看录像的人能够跟着操作做出同样的结果。我的建议是把录像分成四个阶段每个阶段控制在3~5分钟第一阶段展示代码工程的整体结构打开主脚本、构造函数、译码函数简要说明每个文件做什么以及它们之间的调用关系。这个阶段的核心目标是让观众建立整体认知不要一开始就钻进代码细节。第二阶段演示参数修改和单次运行先把码长改成128跑一次单帧仿真展示编码、调制、译码的中间变量输出。通过打印llr的分布、路径度量的变化让观众直观感受到信息在链路中是如何流动的。这里我习惯在MATLAB里设置几个断点展示关键步骤的变量值这比单纯跑完一个完整脚本更有教学效果。第三阶段跑完整误码率扫描从1dB开始跑到5dB观察曲线下降趋势。然后暂停改动一个参数比如把L从16改到4重新运行对比两组曲线的差距。这个对比演示一针见血地展示了SCL列表大小对性能的影响。第四阶段是常见报错处理演示故意把EbN0_dB和构造信噪比设置成极不合理的值展示误码率曲线出现异常时如何排查。我建议在录像中刻意演示一两个坑比如忘记加CRC、或者比特反转顺序写错了让观众看到错误现象并学会定位思路这种“示范错误”的教学效果往往比顺向演示更好。录制工具方面我习惯用MATLAB自带的实时脚本(live script)加屏幕录制软件实时脚本的好处是代码块和输出结果可以同时展示。不过如果你更习惯传统编辑器风格用普通的.m脚本配合任意屏幕录制工具也可以关键是讲清楚每个环节的“为什么”。4. 常见问题与排查技巧实录4.1 误码率平台效应的罪魁祸首我在调试这个仿真的过程中遇到的第一类典型问题是误码率曲线陷入“平台”也就是信噪比增加到一定程度后误码率不再下降曲线趋于水平。引起这个现象的原因有几个按概率排序如下最常见的原因是列表大小L太小。SCL译码在列表L1时就是SC译码性能天然有上限L2或4时短码的编译码增益已经接近饱和但中等码长下可能还差一些。如果L4且码长N1024误码率在10^-3左右就会出现一个明显的平台。解决方法是增大L到16或32观察曲线是否继续下降。第二个原因是冻结比特顺序与编码器不一致。如果发送端的冻结比特位置和接收端SCL译码器判断的冻结比特位置不一致会导致译码器在非法位置上进行路径分裂路径度量失真最终表现为高信噪比下仍然有很高的残差错误率。排查方法很简单构造一次全零输入对比编码器的输出和通过生成矩阵直接计算的结果是否一致。第三个原因是CRC处理错误。如果发送端加了CRC比特但接收端在SCL译码结束后没有做CRC筛选而是直接选最小路径度量的路径那性能会有所退化但不至于产生平台。反过来如果接收端CRC校验的逻辑写反了——把校验不通过的路径当成通过——那误码率必然居高不下。这个错误在代码里非常隐蔽因为程序不会报错但统计结果会让任何看曲线的人一眼就发现问题。4.2 仿真速度慢的优化方案SCL译码本身的计算量不低再加上误码率统计需要大量帧仿真速度很容易让人崩溃。我的一个实际经验是在N256、L16、每个信噪比点跑1000帧、从0dB扫到5dB共11个点的情况下用MATLAB默认的脚本运行需要大约2小时。这个时间对于调试来说太长了所以必须做性能优化。我的第一招是用MATLAB的代码分析器(profiler)定位热点。在SCL译码器里update_llr函数是绝对的热点它被调用的次数是N·logN·L量级。如果这个函数内部有一个不必要的循环就会成为整个流程的瓶颈。优化思路是把递归调用改成迭代版本用数组索引代替cell数组访问尽量减少每次函数调用的开销。第二招是使用parfor并行计算。误码率统计中不同信噪比点的仿真是相互独立的最直接的并行化就是把外层for循环改成parfor。但有个细节需要注意每个工作线程如果都调用randi和randn随机数流需要确保独立。MATLAB的parfor会自动处理这个问题但需要显式设置并行池的随机数流否则不同次运行的结果不可复现。第三招是减少不必要的CRC运算和数组复制。SCL译码过程中路径分裂时struct数组的复制在MATLAB中是值复制这意味着每分裂一次整条路径的bits数组和llr_layer都会被完整复制。如果N1024、L32一次分裂的数据复制量大约几十KB高频重复操作会显著拖慢速度。我的方案是把路径结构体拆成几个独立的数组分裂时只复制需要修改的列尽量避免整条路径的深拷贝。4.3 随机数设置与结果可复现性科研场景下的仿真结果需要可复现这就要保证随机数种子可控。MATLAB默认的随机数生成器在每次启动时会基于当前时间自动播种这会导致每次运行结果不同。为了得到可复现的结果我习惯在每个脚本开头显式设置rng(2024); % 固定随机数种子不过这只解决了“可复现”的问题还有一个更微妙的问题如何在保证可复现的前提下让不同信噪比点的仿真帧充分随机。我见过不少人把所有帧的随机数一次性生成然后每个信噪比点按序号取用。这个方法的缺点是如果某个信噪比点提前终止仿真后续信噪比点使用的随机序列会依赖于前面点的终止位置结果不稳定。更稳妥的做法是在每个信噪比点的for循环内分别设置子种子for ebno_idx 1:length(EbN0_dB) rng(2024 ebno_idx); % 该信噪比点下的所有帧 for f 1:num_frame % ... end end这样每个信噪比点之间完全独立单个点的仿真结果也不会受到其他点的影响。这在调参时非常有用比如你想单独提高某个信噪比点的帧数不需要重新跑整个扫描。4.4 常见问题速查表问题现象可能原因排查与解决方案误码率曲线在高信噪比出现平台列表L过小增大L到16或32观察曲线是否继续下降误码率始终在0.5附近发送端编码与译码端参数不一致用全零输入检查编码矩阵和比特反转顺序曲线在低信噪比下比理论差很多软解调LLR的幅值系数错误检查N0和映射系数确认LLR公式中系数正确加入CRC后性能反而变差CRC比特未参与编码或校验逻辑写反检查CRC比特是否在编码前拼接校验是否取反不同信噪比点曲线出现毛刺错误帧数统计不足增加每点帧数或限定最小错误帧数再停止仿真L从8增大到32性能几乎不变码长太短或已达到性能极限换更长的码长N并重新构造信息位集合仿真内存占用过高路径数据重复复制使用独立数组结构代替struct按需复制运行结果每次都不一样随机数种子未固定在脚本开头设置rng(固定种子)5. 仿真结果分析与后续扩展思路5.1 一组典型结果的深度解读以N256、K128、QPSK、AWGN、SCL列表L16为例我实测的误码率曲线有几个关键观察点在E_b/N_01.5dB左右误码率大约是10^-2量级到2.5dB时下降到10^-3量级到3.5dB附近可以到10^-5以下。对比未编码QPSK的误码率AWGN下需要约8.5dB才能达到10^-3Polar码在误码率10^-3处提供了大约6dB的编码增益。这个增益幅度与理论预期基本一致——码率1/2的Polar码在中等码长下恢复出约5~6dB的编码增益是合理的。两种不同列表大小L4和L16的曲线对比也有参考价值。L16比L4多出约0.3~0.5dB的增益但计算量增加了4倍。也就是说在误码率10^-3附近L从4增加到16带来的增益约0.3dB从16增加到32的增益就不到0.1dB了。这说明L16对于这个码长和码率已经是性价比很高的选择继续增大列表对性能的边际收益在递减。更深一层看Polar码的增益与码长N的关系也值得关注。如果把N从256变成1024保持码率1/2同样采用SCL-L16译码在误码率10^-3处的增益大约还能再提升1~1.5dB。这体现了Polar码的“极化效应”——码长越长信道极化越充分好坏信道的分化越彻底编译码增益越高。但代价是编码构造和译码的计算量都按N·logN增长。5.2 从仿真走向工程实践的三个扩展方向如果你已经完成了基础的误码率仿真以下几个扩展方向非常有助于加深对Polar码的理解也适合作为进一步研究的起点第一个方向是替换调制方式。把QPSK换成16QAM或更高阶的调制观察Polar码在不同调制阶数下的适配情况。你会发现软解调的LLR计算方式需要从简化的I/Q独立公式改为完整的星座点边缘化计算计算量上升但性能增益依然明显。这能帮助你理解Polar码和调制方式之间的交互。第二个方向是引入信道编码的级联结构。经典的级联方案是“Polar码作为外码 LDPC或卷积码作为内码”或者反过来。在仿真中实现级联可以让码率更灵活尤其是用打孔(puncturing)和缩短(shortening)技术匹配到实际的资源块大小这是从理论走向系统设计必须面对的问题。第三个方向是研究Polar码与自动请求重传(ARQ)或混合自动请求重传(HARQ)的结合。在无线通信系统中编码方案很少孤立使用它与重传策略的配合方式同样关键。Polar码支持速率兼容的编码结构你可以仿真不同重传合并策略下的吞吐量曲线这是一个很实际且有很多人关注的课题。5.3 关于这套仿真平台的一些实用心得在反复调试这套仿真代码的过程中我最大的感受是Polar码的MATLAB仿真中90%的时间花在“对代码细节的确认”而不是“算法本身”。比如比特反转顺序对不对、冻结比特位置有没有对齐、CRC校验逻辑有没有写反这些细节任何一个出错整条误码率曲线都会失真而且失真方式往往不那么明显。所以我强烈建议在跑完整误码率扫描之前先用一个极小的码长比如N8、K4做一次完整的单帧测试。用手工计算的期望结果去对比程序输出Polar码编码器在N8时完全可以手工用生成矩阵算出结果确认编码正确后再扩展到大码长。这个习惯能帮你避免在错误代码上浪费一整天的调试时间。我还有一个建议是在开发SCL译码器的过程中不要一开始就追求最优化性能。先用最简单的逐比特遍历方式和sort排序实现正确的功能再逐步做性能优化。因为优化的思路往往是针对特定码长和列表大小的一旦进行到一半发现问题逆向调试会非常痛苦。先把功能跑通再用profiler定位热点逐步优化这样既不会陷入过度工程也不会丢失性能目标。如果你后续要在这套代码的基础上扩展建议保留好每一个版本的实验记录包括每次修改的参数、曲线结果和时间。仿真实验的可复现性和可追溯性和数据本身一样重要。本文还有配套的精品资源点击获取
返回列表