ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

5G极化码MATLAB仿真实现:SC译码工程拆解与避坑指南

5G极化码MATLAB仿真实现:SC译码工程拆解与避坑指南

简介:本资源面向5G物理层通信算法学习者与开发者,聚焦极化码(Polar Code)的编码与解码实现,适合已具备一定通信原理与MATLAB基础、希望深入理解5G NR控制信道编码方案的中高级读者。压缩包共19个文件,以12个.m脚本为核心,辅以4个.mat数据文件、1个.cpp源文件及mexw64、pdb等编译产物,整体约56KB,涵盖信道加噪、生成矩阵构造、CRC校验、SC译码与信道转移等模块。已有427人学习下载。通过分析这些源码与示例数据,读者可掌握极化变换矩阵的构造方式、可靠比特与冗余比特的选取策略,以及逐位取消解码与快速解码算法的实现思路,并借助测试用例验证编解码流程,为后续优化译码性能或移植到实际5G通信环境提供可复用的参考代码与排错依据。

1. 从一份 MATLAB 极化码解码包说起:它到底能跑出什么

如果你正在做 5G 物理层链路仿真,尤其是控制信道 Polar 码的译码验证,大概率会遇到一个尴尬:标准文档里公式齐全,但真要从零搭一套能跑通、能对拍、能改参数的 MATLAB 工程,光是 Gen_PM、CRC 交织、SC 译码这几块就够折腾好几天。我这次拆的polar_code_decode.rar就是一份能直接落地的参考实现,里面既有.m脚本,也有编译好的Gen_PM_C.mexw64,还配了G_256.mat、G_1024.mat这类生成矩阵数据,属于那种“拿到手就能先跑起来看波形”的资源。它适合两类人:一类是刚接触极化码、想通过可运行代码理解 SC 译码逐位判决流程的新手;另一类是做链路级仿真、需要一份可改可扩的基线工程来对比 Fast-SSC 或 BP 译码的老手。下面我按“先跑通、再拆解、后避坑”的顺序,把这份包里的东西一层层剥开。

2. 工程结构与核心文件:先认清每个 .m 在链路里的位置

2.1 从文件清单反推极化码编译码链路

拿到压缩包先别急着main,把文件按功能分堆会清晰很多。这份包里大致能分成四类:第一类是矩阵与序列生成,包括Gen_G.m、Gen_f.m、Gen_PM.m、Gen_PM_C.cpp和对应的mexw64,负责构造生成矩阵、极化矩阵和交织图案;第二类是信息与 CRC 处理,info_gen_CRC.m、cyclic_redundancy_code.m、cyclic_redundancy_decode.m管的是信息位加 CRC 以及译码端校验;第三类是信道与译码主体,add_noise.m、channel_transfer.m、SC_decoder.m分别对应加噪、信道传输和逐位取消译码;第四类是数据与入口,y_input.mat、all.mat、G_256.mat、G_1024.mat是预存数据和矩阵,main_polar_LYJ.m是主入口,travel.m看名字像是遍历或参数扫描脚本。这种分法不是官方规定,但按链路顺序摆一遍,后面调参时就知道该动哪个文件。

2.2 主入口 main_polar_LYJ.m 的调用顺序

先看主脚本的调用链,这决定了你改参数时会不会牵一发动全身。典型结构是:设定码长 N、信息位 K、信噪比范围,然后调info_gen_CRC生成带 CRC 的信息序列,接着Gen_G或直接加载G_256.mat拿生成矩阵,编码后经add_noise加高斯白噪声,再进SC_decoder译码,最后用cyclic_redundancy_decode做校验并统计误块率。这里有个细节:Gen_PM_C.mexw64是编译好的 MEX 文件,说明极化矩阵或交织图案的生成被加速过,如果你换 MATLAB 版本或换平台,这个 mex 可能直接报错,得回头用Gen_PM.m或Gen_PM_C.cpp重新编译。我一般会先把主脚本里所有mex调用注释掉,换成纯.m版本跑一遍,确认逻辑通了再开加速。

2.3 生成矩阵与极化矩阵:Gen_G、Gen_PM 与 G_256.mat 的关系

极化码的生成矩阵核心是克罗内克积,Gen_G.m干的就是这件事。以 N=256 为例,它会构造 (G = B_N F^{\otimes n}),其中 (F = [[1,0],[1,1]]),(B_N) 是比特反转置换矩阵。但这份包直接给了G_256.mat和G_1024.mat,说明作者更倾向“预计算+加载”而不是每次现算,毕竟 N=1024 时克罗内克积在 MATLAB 里循环写不好会明显拖慢仿真。Gen_PM.m和Gen_PM_C.cpp则偏向极化矩阵或交织图案的生成,Gen_PM_C.mexw64是它的加速版。实际用的时候,如果你只跑 256 和 1024 两个码长,直接load('G_256.mat')最省事;要跑其他码长,再回头调Gen_G,但注意它内部可能默认 N 是 2 的幂,传错会直接维度报错。

% 加载预计算生成矩阵并检查维度 load('G_256.mat'); % 假设变量名为 G [N, ~] = size(G); assert(log2(N) == round(log2(N)), 'N 必须是 2 的幂'); disp(['生成矩阵维度: ', num2str(N), 'x', num2str(N)]);

这段代码先加载矩阵,再用log2判断是否为 2 的幂,避免后面极化变换时维度对不上。参数上,G_256.mat里的变量名要确认,有的包存成G,有的存成G256,加载后先whos看一眼最稳妥。

3. 编码链路实操:从信息序列到 CRC 附着再到加噪

3.1 info_gen_CRC 与 cyclic_redundancy_code 的配合

5G Polar 码在控制信道里用 CRC 辅助译码,这份包把 CRC 生成和编码拆成了两个文件。info_gen_CRC.m一般负责生成随机信息比特并预留 CRC 位置,cyclic_redundancy_code.m则做实际的多项式除法。常见做法是:先确定 CRC 长度(比如 11 位或 24 位),把信息位后面补零,再用生成多项式做模 2 除法,余数就是 CRC 校验位。这里容易翻车的地方是比特顺序——有的实现是 MSB 在前,有的是 LSB 在前,如果编码端和解码端不一致,译码后 CRC 永远校验不过,但误块率曲线看起来又“像那么回事”,属于典型的玄学 bug。我一般会在info_gen_CRC里加一句打印前 16 个比特,和cyclic_redundancy_decode里的输入对一下,确认顺序一致再往下跑。

% 生成带 CRC 的信息序列(示例参数) K = 64; % 信息位长度 crc_len = 11; % CRC 长度 info = randi([0 1], K - crc_len, 1); % 随机信息比特 info_with_crc = info_gen_CRC(info, crc_len); disp(info_with_crc(1:16)'); % 检查前 16 位顺序

参数说明:K是信息位总长,crc_len决定校验能力,5G 里常见 11 和 24 两种。info_gen_CRC的第二个参数如果传错,后面cyclic_redundancy_decode会按错误长度截取,导致校验位错位。

3.2 极化变换与比特反转:Gen_G 和 Gen_PM 的调用时机

编码阶段的核心是 (x = u \cdot G),但u里哪些位置放信息、哪些放冻结位,取决于信道可靠度排序。这份包用Gen_PM或Gen_PM_C生成极化矩阵,本质上是在算每个比特信道的可靠度,然后按可靠度从高到低选信息位。实际调用时,Gen_PM的输出通常是一个排序向量,main_polar_LYJ.m会拿它去填u。这里有个参数边界:如果Gen_PM内部用的信噪比假设和add_noise里的实际信噪比差太多,选出来的信息位集合就不是最优的,误块率会偏高,但代码不会报错,只能靠对比曲线发现。我一般会固定一个信噪比点,分别用Gen_PM和预存的可靠度序列跑两次,看误块率是否一致,不一致就说明可靠度计算有偏差。

3.3 add_noise 与 channel_transfer:噪声功率和调制方式要对齐

add_noise.m和channel_transfer.m负责把编码后的比特送进信道。常见做法是 BPSK 调制后加高斯白噪声,噪声方差由信噪比换算。这里的关键参数是Eb/N0和Es/N0的换算关系,如果add_noise按Es/N0加噪,而main里传的是Eb/N0,曲线会整体平移,看起来像是译码算法性能差,其实是噪声功率算错了。我一般会在add_noise里加一行打印实际噪声方差,和理论值对一下。另外channel_transfer.m如果包含衰落或相位旋转,要和SC_decoder里的软信息计算方式匹配,否则 LLR 符号反了,译码直接崩。

% 加噪示例:BPSK + AWGN tx = 1 - 2*encoded_bits; % BPSK 映射:0->1, 1->-1 EbN0_dB = 2; % 信噪比 EbN0 = 10^(EbN0_dB/10); sigma = sqrt(1/(2*EbN0)); % 实噪声标准差 rx = tx + sigma*randn(size(tx));

参数说明:encoded_bits是极化编码后的比特列向量,EbN0_dB是仿真扫描变量。sigma的算法假设了 BPSK 且每比特能量为 1,如果换 QPSK 或带功率归一化,这个公式要相应调整。

4. SC 译码器拆解:SC_decoder.m 里的递归与判决

4.1 SC 译码的递归结构:f 函数与 g 函数

SC_decoder.m是整份包最值得细看的地方。SC 译码本质是在码树上做深度优先遍历,每个节点用 f 函数和 g 函数合并左右子树的 LLR。f 函数近似为sign(a)*sign(b)*min(|a|,|b|),g 函数则依赖已判决的比特。这份包大概率用递归实现,因为 N=256 时递归深度是 8,MATLAB 递归虽然慢但代码短。看的时候重点盯两个地方:一是 g 函数里对已判决比特的符号处理,如果符号取反,后面全错;二是叶子节点的判决门限,一般是 LLR 小于 0 判 1,大于等于 0 判 0,但有的实现反过来,要和编码端的映射对齐。

function L = f_function(a, b) % SC 译码 f 函数:min-sum 近似 L = sign(a).*sign(b).*min(abs(a), abs(b)); end function L = g_function(a, b, u_hat) % SC 译码 g 函数:依赖已判决比特 L = b + (1 - 2*u_hat).*a; end

逻辑说明:f_function用 min-sum 近似替代了精确的log(cosh)计算,这是 SC 译码里最常见的简化,损失很小但速度快很多。g_function里u_hat是已判决比特,(1-2*u_hat)把 0/1 映射成 +1/-1,如果u_hat传进来是逻辑型,要先转 double,否则 MATLAB 会报类型错误。

4.2 冻结位与信息位的处理:Gen_f 和可靠度序列

Gen_f.m看名字是生成冻结位集合或可靠度序列。SC 译码时,冻结位直接判 0,不参与 LLR 计算,所以SC_decoder里会有一个判断:当前索引如果在冻结位集合里,u_hat强制为 0。这里容易踩的坑是冻结位集合的索引基准——MATLAB 是 1-based,而有些理论文档是 0-based,如果Gen_f输出的是 0-based 索引,直接拿去索引 MATLAB 数组会整体偏移一位,导致冻结位和信息位完全错位,译码结果看起来像随机比特。我一般会在Gen_f输出后加一句freeze_set = freeze_set + 1;做转换,并打印前几个索引确认。

4.3 CRC 校验在译码后的作用:cyclic_redundancy_decode 的判决逻辑

译码完不是结束,cyclic_redundancy_decode.m要做 CRC 校验,决定这一块是否通过。5G 里 CRC 还参与早停和路径选择,但这份包看起来是 SC 单路径,所以 CRC 只做最终校验。常见做法是把译码后的信息位和 CRC 位一起做模 2 除法,余数为 0 则通过。这里有个细节:如果SC_decoder输出的比特顺序和cyclic_redundancy_decode期望的顺序不一致,比如一个是从左到右,一个是从右到左,CRC 永远不过。我一般会在main里把译码输出和编码前输入直接对比,先确认比特顺序,再看 CRC 校验结果。

% CRC 校验示例 decoded_info = SC_decoder(llr, freeze_set, N, K); crc_pass = cyclic_redundancy_decode(decoded_info, crc_len); if crc_pass block_error = 0; else block_error = 1; end

参数说明:llr是解调后的对数似然比,freeze_set是冻结位索引集合,N和K分别是码长和信息位长。crc_pass为逻辑值,统计误块率时直接累加block_error。

5. 避坑与排查:这份包最容易翻车的五个地方

5.1 mexw64 文件加载失败:现象、原因与解决

现象:运行main_polar_LYJ.m时报Invalid MEX-file或Undefined function 'Gen_PM_C'。原因通常是 MATLAB 版本不匹配或平台不对,.mexw64是 Windows 64 位下编译的,换 Linux 或 macOS 直接不能用,换 MATLAB 大版本也可能因 ABI 变化失效。解决:把Gen_PM_C.mexw64从路径里移除,改用Gen_PM.m纯脚本版本,或者用mex Gen_PM_C.cpp在本地重新编译。重新编译前确认mex -setup选好了 C++ 编译器。

5.2 生成矩阵维度不匹配:现象、原因与解决

现象:执行u * G时报Inner matrix dimensions must agree。原因一般是G_256.mat里的变量不是 256x256,或者加载后变量名不对,G被其他脚本覆盖了。解决:加载后立刻whos G看维度,如果不是 NxN,检查.mat文件里实际变量名,用load('G_256.mat', 'G256')指定变量加载,避免覆盖。

5.3 信噪比换算错误导致曲线平移:现象、原因与解决

现象:误块率曲线整体比预期差 2-3 dB,但译码算法逻辑检查无误。原因多半是add_noise里Eb/N0和Es/N0混用,或者 BPSK 映射时比特能量没归一化。解决:在add_noise里打印实际噪声方差,和理论值sigma = sqrt(1/(2*EbN0))对比,同时确认tx的功率是 1。如果用了 CRC 或编码,Eb/N0要按码率折算。

5.4 冻结位索引基准错位:现象、原因与解决

现象:译码输出全是随机比特,CRC 通过率接近 0。原因可能是Gen_f输出的冻结位索引是 0-based,而 MATLAB 数组是 1-based。解决:在Gen_f输出后统一加 1,并打印前 10 个索引,和SC_decoder里的判断逻辑对一遍。如果SC_decoder内部也有索引转换,确保只转一次。

5.5 CRC 校验永远不过:现象、原因与解决

现象:译码比特看起来有规律,但cyclic_redundancy_decode始终返回 0。原因可能是 CRC 生成多项式和校验多项式不一致,或者比特顺序在编码和译码之间反了。解决:先用无噪声条件跑一遍,确认编码后直接译码能通过 CRC,再逐步加噪。如果无噪声都不过,问题一定在 CRC 实现或比特顺序,和译码算法无关。

6. 进阶验证:用无噪声回环和参数扫描确认工程可信

6.1 无噪声回环:先证明编译码自洽

在跑误块率曲线之前,我习惯先做一个无噪声回环:编码后不加噪,直接送SC_decoder,看译码输出是否和原始信息比特完全一致。这一步能排除大部分索引错位、CRC 顺序、冻结位集合错误。具体做法是把add_noise的sigma设为 0,或者直接跳过加噪,把编码后的 BPSK 符号当 LLR 送进去。如果无噪声都过不了,后面加噪跑出来的曲线没有任何参考价值。

% 无噪声回环验证 encoded_bits = mod(info_with_crc' * G, 2); % 极化编码 llr_noiseless = 1 - 2*encoded_bits; % 无噪声 LLR decoded = SC_decoder(llr_noiseless, freeze_set, N, K); assert(isequal(decoded(1:K), info_with_crc), '无噪声回环失败'); disp('编译码自洽性验证通过');

这段代码把编码后的比特直接映射成 LLR,跳过信道。assert确保译码输出和原始信息完全一致,不一致会直接中断并报错,方便定位。

6.2 参数扫描:码长、码率和信噪比范围怎么设

确认自洽后,可以开始扫参数。这份包支持 256 和 1024 两个码长,码率由 K/N 决定,常见控制信道码率在 0.1 到 0.5 之间。信噪比范围建议从 0 dB 扫到 4 dB,步长 0.5 dB,每个点至少跑 1000 个块,否则高信噪比段误块率统计抖动很大。travel.m如果就是扫描脚本,可以直接改里面的循环变量;如果不是,自己写一个双层循环,外层信噪比,内层块数。注意每次换码长要重新加载对应的G_256.mat或G_1024.mat,并重新生成冻结位集合。

参数建议范围说明
码长 N256 / 1024与预存矩阵对应
信息位 K32 ~ 512码率 K/N 控制在 0.1~0.5
Eb/N00 ~ 4 dB步长 0.5 dB
每点块数≥ 1000高信噪比段建议 5000

6.3 对比 SC 与 Fast-SSC 的边界:这份包能改到什么程度

这份包的SC_decoder.m是基础 SC,如果你想对比 Fast-SSC,可以在它的递归结构上做剪枝:当某个节点对应的所有比特都是冻结位时,直接返回固定 LLR,不再往下递归;当所有比特都是信息位且码率特定时,用最大似然判决替代逐位判决。改的时候注意保持 f 和 g 函数的接口不变,否则上层调用全要动。我一般会复制一份SC_decoder.m改名Fast_SC_decoder.m,先在无噪声回环下验证剪枝逻辑,再跑误块率对比。从那以后我每次拿到新的极化码工程,都强制先跑无噪声回环和冻结位索引检查,这两步过了才敢看曲线。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进