简介:这份资源是面向通信工程、信息论学习者与科研人员的极化码基础仿真代码包,基于Matlab实现,适合希望理解信道极化原理、验证编码解码性能的初学者与进阶开发者。压缩包共23个文件,约6MB,以20个.m脚本为核心,涵盖编码、调制、加噪、SC解码及蝶形运算等模块,另附1份PDF学位论文、1份README说明与1个mat数据文件,便于对照理论阅读代码。目前已有225人学习下载。通过运行主脚本,读者可完成从冻结比特初始化、输入变换到误码率统计的完整链路,并借助论文与说明文档理解SC解码流程及参数含义,为后续优化解码算法或探索5G控制信道等应用场景提供可复用的实验基础。
1. 从一份 Polar 码 Matlab 工程包说起:它到底能跑出什么
如果你手里拿到的是一个名为Polar-Codes-Software-Matlab--master.zip的压缩包,第一反应大概率是:这玩意儿解压之后能不能直接跑,跑出来是误码率曲线还是只有一堆函数文件。Polar 码(极化码)从 2016 年被 3GPP 确定为 5G 控制信道 eMBB 场景的上行和下行短码方案之后,Matlab 仿真就成了很多人入门信道编码的第一站。这个工程包通常包含极化码的构造、编码、SC 译码、SCL 译码以及 AWGN 信道下的 BER/FER 仿真脚本,适合通信工程专业做课设、写论文复现,或者做 5G 物理层链路级仿真的工程师拿来当参考基线。它解决的核心问题是:你不用从零推导 Arikan 的信道极化递归公式,直接改信噪比、码长、列表大小就能看到性能变化。但前提是你得知道每个.m文件在链路里的位置,以及 Matlab 版本、工具箱依赖这些容易翻车的地方。
2. Polar 码的 Matlab 工程结构:先认清每个 .m 文件在链路里的位置
拿到压缩包之后不要急着run,先花十分钟把目录结构看清楚。Polar 码的 Matlab 实现通常不会只有一个入口脚本,而是按「构造—编码—译码—仿真」四层拆开。你如果直接点开一个叫main.m或者simulation.m的文件就运行,很可能报Undefined function 'polar_encode',因为路径没加进去。
2.1 典型目录布局与文件职责
一个能跑通的 Polar 码 Matlab 工程,常见结构大致是这样:
| 文件/目录 | 职责 | 你需要注意什么 |
|---|---|---|
main.m/simulation.m | 仿真入口,设 SNR 范围、码长、码率 | 先看这里,确认调用链 |
polar_encode.m | 极化码编码,含比特反转、冻结位映射 | 输入是信息比特+冻结位索引 |
polar_construct.m/ga_construct.m | 信道构造,算可靠度排序 | 高斯近似还是巴氏参数,决定性能 |
sc_decode.m | 串行抵消译码 | 递归实现,码长大会慢 |
scl_decode.m | 串行抵消列表译码 | 列表大小 L 是核心参数 |
awgn_channel.m | BPSK + AWGN 加噪 | 看 Eb/N0 还是 SNR 定义 |
crc_check.m | CRC 辅助校验(CA-SCL 用) | 多项式选错会误检 |
提示:如果目录里还有
+polar这种带加号的文件夹,那是 Matlab 的包(package)机制,调用时要写polar.encode(...),不能直接写encode(...)。
2.2 把工程加进 Matlab 路径的最小命令
解压之后,在 Matlab 命令窗口里先做这一步,比双击文件靠谱得多:
% 假设解压到 D:\work\Polar-Codes-Software-Matlab--master projRoot = 'D:\work\Polar-Codes-Software-Matlab--master'; addpath(genpath(projRoot)); % 递归添加所有子文件夹 savepath; % 可选:保存路径,下次不用再加 which polar_encode % 验证函数能否被找到genpath会把所有子目录都加进来,避免手动一层层addpath。which是排查「函数找不到」最快的手段,如果返回空,说明文件名或路径不对。这里不要用cd进目录就完事,Matlab 的当前目录和搜索路径是两回事,很多新手在这里翻车。
2.3 先跑通最小闭环:N=8 的编码译码
在动辄 N=1024 的仿真之前,先用最短码长验证链路是否通:
N = 8; % 码长,必须是 2 的幂 K = 4; % 信息比特数,码率 R = K/N = 0.5 F = [1 2 3 5]; % 冻结位索引(示例,实际由构造算法给出) info = randi([0 1], 1, K); % 随机信息比特 u = zeros(1, N); u(setdiff(1:N, F)) = info; % 把信息比特放到非冻结位 x = polar_encode(u, N); % 编码,得到发送比特 y = awgn_channel(x, 2.0); % Eb/N0 = 2 dB u_hat = sc_decode(y, N, F); % SC 译码 disp(u_hat);这段代码的逻辑是:先确定冻结位集合,把信息比特填进非冻结位置,编码后过信道,再译码。参数上,N必须是 2 的幂,这是极化码递归结构决定的;F的选取直接决定性能,示例里随便写的,真实场景要用构造算法生成。如果这一步能跑通且译码结果和u接近,说明工程基本可用。
3. 极化码构造与编码:可靠度排序怎么算、冻结位怎么选
Polar 码的性能上限,很大程度上在构造阶段就定死了。编码本身只是异或和比特反转,真正有技术含量的是「哪些位置放信息比特」。这一章把构造和编码讲透,你才能知道改哪个参数有意义。
3.1 巴氏参数法和高斯近似法怎么选
构造的本质是给 N 个极化子信道算可靠度,然后挑最可靠的 K 个放信息。常见两种做法:
- 巴氏参数(Bhattacharyya)法:递归计算每个子信道的巴氏参数 Z,Z 越小越可靠。优点是解析、快,缺点是只对 BEC 信道严格最优,AWGN 下是近似。
- 高斯近似(GA)法:假设 LLR 服从高斯分布,递推均值和方差,AWGN 下更准。5G 标准里用的就是 GA 的工程化版本。
我一般会先用 GA 构造,因为 AWGN 是绝大多数仿真的默认信道。如果工程包里只有巴氏参数版本,短码下差别不大,长码下 GA 明显更好。
function F = ga_construct(N, K, EbN0_dB) % 高斯近似构造:返回冻结位索引 F sigma = 1 / sqrt(2 * 10^(EbN0_dB/10)); % BPSK 下噪声标准差 L = 2 * 10^(EbN0_dB/10); % 初始 LLR 均值 llr = zeros(1, N); llr(1) = L; for stage = 1:log2(N) step = 2^stage; for i = 1:step:N % 递归更新 LLR 均值,具体公式依实现而定 llr(i) = phi_inv(1 - (1 - phi(llr(i)))^2); end end [~, order] = sort(llr, 'descend'); % 可靠度从高到低 F = sort(order(K+1:end)); % 最不可靠的 N-K 个作为冻结位 endphi和phi_inv是高斯近似的辅助函数,工程包里一般会单独给。EbN0_dB是设计信噪比,注意构造用的信噪比和仿真扫描的信噪比可以不同,通常取一个中间值(比如 2~3 dB)做设计点。sort之后取后 N-K 个,这是冻结位。参数上,N和K必须和后续编码译码一致,否则索引越界。
3.2 编码的递归实现与比特反转
极化码编码公式是x = u * G,其中G是生成矩阵,但实际实现不会真的去乘大矩阵,而是用递归的异或结构:
function x = polar_encode(u, N) % 极化码编码:递归蝶形异或 if N == 1 x = u; return; end n = log2(N); % 比特反转置换 u = bit_reverse(u, n); % 分两半递归 u1 = u(1:N/2); u2 = u(N/2+1:end); x1 = polar_encode(xor(u1, u2), N/2); x2 = polar_encode(u2, N/2); x = [x1, x2]; endbit_reverse是比特反转置换,把索引按二进制位倒序重排,这是极化码编码的标准步骤。xor(u1, u2)对应生成矩阵里的异或操作。递归到 N=1 返回。这个实现的时间复杂度是 O(N log N),比矩阵乘法 O(N²) 快得多。参数上,u长度必须是 N,且 N 是 2 的幂。如果你发现编码结果和参考对不上,先检查bit_reverse的实现,这是最容易写错的地方。
3.3 冻结位索引的两种表示:0-based 还是 1-based
血泪经验:Matlab 是 1-based 索引,但很多论文和 C 实现是 0-based。工程包里如果构造函数返回的是 0-based 冻结位,直接拿去索引 Matlab 数组会整体偏移一位,译码结果全是错的,而且不会报错,只是 BER 一直 0.5,非常玄学。
% 如果构造返回 0-based,转成 1-based F_0based = [0 1 2 4]; F_1based = F_0based + 1; % 加 1 再用排查方法:用 N=8、K=4 跑一次,看译码输出和信息比特是否一致。如果完全随机,先怀疑索引基准。这个坑我在复现别人代码时踩过不止一次。
4. SC 与 SCL 译码:列表大小 L 和 CRC 怎么配才不白跑
译码是 Polar 码仿真的性能瓶颈,也是参数最多的地方。SC 译码简单但性能一般,SCL 译码靠列表搜索逼近最大似然,CA-SCL 再加 CRC 做路径筛选。这一章把译码实现和参数配置讲清楚。
4.1 SC 译码的递归结构与 LLR 计算
SC 译码按比特顺序逐个判决,每个比特的 LLR 由递归的 f 和 g 函数算出:
function u_hat = sc_decode(y, N, F) % SC 译码:递归实现 if N == 1 u_hat = double(y < 0); % LLR < 0 判为 1 return; end % f 函数:两个 LLR 的软合并 L_left = f_llr(y(1:N/2), y(N/2+1:end)); u_left = sc_decode(L_left, N/2, F(F <= N/2)); % g 函数:利用已判决比特更新右半 LLR L_right = g_llr(y(1:N/2), y(N/2+1:end), u_left); u_right = sc_decode(L_right, N/2, F(F > N/2) - N/2); u_hat = [u_left, u_right]; endf_llr是sign(a)*sign(b)*min(|a|,|b|)的近似,g_llr是b + (1-2u)*a。冻结位处强制判 0。这个递归实现清晰但慢,N=1024 时一次译码要递归十层。参数上,F要按左右子树拆分,注意索引偏移。如果译码结果和编码对不上,先检查f_llr和g_llr的符号约定,LLR 正负定义反了会导致全错。
4.2 SCL 译码:列表大小 L 的取舍
SCL 译码维护 L 条候选路径,每步扩展后按路径度量排序,保留最好的 L 条:
function u_hat = scl_decode(y, N, F, L) % SCL 译码:列表搜索 paths = struct('u', [], 'llr', [], 'pm', 0); % 路径度量 pm paths(1).llr = y; for i = 1:N newPaths = []; for p = 1:length(paths) if ismember(i, F) % 冻结位:只扩展 0 newPaths = extend(paths(p), 0); else % 信息位:扩展 0 和 1 newPaths = [newPaths, extend(paths(p), 0), extend(paths(p), 1)]; end end % 按路径度量排序,保留 L 条 [~, idx] = sort([newPaths.pm]); paths = newPaths(idx(1:min(L, end))); end [~, best] = min([paths.pm]); u_hat = paths(best).u; endpm是路径度量,每扩展一次累加 LLR 的惩罚。L越大性能越好但复杂度线性增长。常见取值:L=4 接近 SC 的 1.5 倍复杂度但增益明显,L=8 是性能和复杂度的平衡点,L=32 用于对性能要求高的场景。参数上,L必须是正整数,且内存随 L 和 N 增长,N=1024、L=32 时要注意内存占用。
4.3 CA-SCL:CRC 多项式选错等于白加
CA-SCL 在 SCL 基础上用 CRC 校验筛选路径,只有通过 CRC 的路径才被保留:
% CRC-24 示例(5G 常用) poly = '0x1864CFB'; % CRC24C 多项式 crcLen = 24; % 编码端:信息比特后附 CRC infoWithCRC = [info, crc_generate(info, poly)]; % 译码端:SCL 输出后校验 if crc_check(u_hat_info, poly) % 通过,接受 else % 不通过,选次优路径 endCRC 多项式必须和编码端一致,否则校验永远失败,CA-SCL 退化成普通 SCL,性能增益全丢。5G 里控制信道用 CRC24C,广播信道用 CRC24A/B,别混。参数上,crcLen要和多项式匹配,CRC 越长误检率越低但开销越大。如果发现 CA-SCL 和 SCL 性能一样,先查 CRC 多项式。
5. 避坑与排查:Polar 码 Matlab 仿真里最容易翻车的 5 个点
这一章全是踩坑记录,每条按「现象 → 原因 → 解决」写,你照着排查能省不少时间。
5.1 现象:BER 曲线一直停在 0.5,怎么调 SNR 都不降
原因:最常见的是冻结位索引基准错了(0-based 当 1-based 用),或者 LLR 符号约定反了。其次是编码和译码用的 N 不一致,比如编码 N=1024 译码 N=512。
解决:先用 N=8、K=4 跑单次,打印编码输入u、发送x、译码输出u_hat,看是否一致。如果u_hat完全随机,查索引基准;如果u_hat是u的反,查 LLR 符号。确认最小闭环通了再上长码。
5.2 现象:SCL 译码比 SC 还慢,且性能没提升
原因:列表扩展后没有正确排序和裁剪,路径数指数增长;或者路径度量pm没累加,排序失效。
解决:在每步扩展后打印length(paths),正常应该始终 ≤ L。如果一直增长,检查sort和裁剪逻辑。pm的更新要在扩展时累加 LLR 惩罚,不能只在最后算。
5.3 现象:CA-SCL 和 SCL 性能曲线完全重合
原因:CRC 多项式不匹配,或者 CRC 校验函数写错,导致所有路径都通过或都不通过。
解决:单独测 CRC 模块,随机信息比特生成 CRC 再校验,必须通过;改一位再校验,必须失败。确认 CRC 模块无误后再接入 SCL。
5.4 现象:Matlab 报Out of memory,N=1024、L=32 时
原因:SCL 每步保存所有路径的 LLR 和比特,内存随 N×L 增长;递归实现还会产生大量临时数组。
解决:改用迭代实现,或者只保存必要的路径信息;降低 L 到 8 或 16;用single代替double存 LLR。如果还不行,分段仿真,别一次扫完所有 SNR。
5.5 现象:换台电脑跑,结果和之前不一样
原因:Matlab 版本差异导致randi、awgn行为变化;或者路径里有同名函数覆盖。
解决:固定随机种子rng(42);用which -all查同名函数;记录 Matlab 版本,5G 相关仿真建议 R2020b 以上。如果工程包依赖通信工具箱,确认目标机器装了。
6. 进阶技巧:用 Monte Carlo 加速和性能上界验证你的 Polar 码仿真
跑 Polar 码仿真最耗时间的是 Monte Carlo:每个 SNR 点要跑几万帧才能看到低 BER。我一般会用两个技巧加速,同时验证结果是否可信。
技巧一:帧数自适应停止。不要固定帧数,而是设一个错误帧数下限,达到就停:
targetErrors = 100; % 至少 100 个错误帧 maxFrames = 1e6; errors = 0; frames = 0; while errors < targetErrors && frames < maxFrames frames = frames + 1; % ... 编码、信道、译码 ... if ~isequal(u_hat_info, info) errors = errors + 1; end end ber = errors / (frames * K);这样在高 SNR 点不会浪费时间去凑固定帧数,低 SNR 点也能保证统计置信度。targetErrors取 100 时,BER 的相对误差大约 10%,够看趋势;要发论文就取 500 以上。
技巧二:用理论界验证。Polar 码在 BEC 下有解析的误块率上界,AWGN 下可以用有限码长界(normal approximation)做粗略校验:
| 验证项 | 方法 | 预期 |
|---|---|---|
| 编码正确性 | N=8 单次编解码 | u_hat == u |
| 构造正确性 | 对比 GA 和巴氏参数排序 | 高 SNR 下 GA 更好 |
| 译码正确性 | SC vs SCL L=8 | SCL 在中等 SNR 有 0.5~1 dB 增益 |
| 仿真可信度 | 错误帧数 ≥ 100 | BER 相对误差 ~10% |
如果 SCL 比 SC 差,或者 CA-SCL 没有增益,先别怀疑算法,回头查第 5 章的坑。我自己的习惯是:每换一个码长或码率,先用 N=256、K=128 跑一遍 SC 和 SCL,确认增益方向对,再上 N=1024。这样即使翻车,也能快速定位是参数问题还是实现问题。希望帮到你。
本文还有配套的精品资源,点击获取