news 2026/9/16 11:05:28

极化码SC编译码MATLAB实现:从递归核到误码率仿真

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
极化码SC编译码MATLAB实现:从递归核到误码率仿真

简介:面向通信与编码学习者的极化码SC编译码MATLAB实现包,聚焦SC逐位取消算法在极化码编解码流程中的完整落地,适合需要理解信道极化理论、动手进行编码仿真或开展算法改进的初学者与研究人员。压缩包共9个文件,其中8个为m源码文件,1个为mat数据文件,整体仅7KB,代码量非常精简。源码涵盖了初始化参数配置、编码映射、SC解码、LLR计算与比特级处理等核心模块,并配有可直接运行的主测试脚本,能够快速观察从编码到解码的完整流程与输出结果。数据文件用于保存信道参数或仿真中间量,有助于减少重复计算。目前已有305人学习下载,说明该小型实现具备较好的参考价值。通过阅读和运行这些MATLAB代码,读者可以逐步掌握极化码编码比特选择、递归信道合成以及SC逐位判决的实现细节,并在此基础上尝试改进译码算法或向5G通信等实际场景扩展,是一份兼顾教学演示与二次开发参考的小型代码包。

1. 看到SC.zip,先别急着解压:这是一套极化码SC编译码的MATLAB起点

看到“SC.zip_极化码 matlab_极化码 sc_极化码MATLAB_极化码代码_极化码编译码”这串标题,老手的第一反应通常是:里面是一个“极化码 + Successive Cancellation 连续消除译码”的MATLAB工程包。这里的SC不是字体,而是信道编码里最常见的逐位连续消除;zip里装的八成是编码器、SC译码器、误码率仿真三个脚本。它解决的实际问题是:在AWGN信道上把随机比特按指定码长编码,再递归对数似然比译回原始比特,最后画出BER/FER。适合通信方向研究生、5G NR信道编码预研工程师,以及想从零确认信道极化理论的从业者。我拿到这类包不会先逐行读论文,而是先找主函数、冻结集构造和LLR更新三块,再跑一个N=256的最小链路。

2. 极化码编译码的最小原理:一个递归核敲掉整个SC

2.1 从编码侧的递归核到MATLAB里的Kron构造

极化码的基本核是A = [1 0; 1 1]。码长 N=2^n 的生成矩阵是 A 的 n 次 Kronecker 幂。通信老手看到这个核就能想起信道极化的本质:两个独立信道合成一个近乎无噪的信道和一个几乎全噪的信道。在MATLAB里,用kron函数构造生成矩阵非常短,初学者可以用它验证编码逻辑:

function G = polar_gen_matrix(n) A = [1 0; 1 1]; G = A; for i = 1:n-1 G = kron(G, A); end end

这段代码每跑一次,G 的尺寸翻一倍,最终是 N×N 的 0/1 矩阵。优点是直观:编码就是x = mod(u * G, 2),冻结位在 u 里置 0。缺点是 N 到 1024 时矩阵乘法略慢。我一般只在 N<=512 时用这种矩阵写法,更大换成递归编码。

递归编码也有多种等价写法,我习惯用前后分裂的版本,因为它和后面 SC 译码的左右分支一一对应:

function x = polar_encode(u, N) if N == 1 x = u; return; end half = N / 2; u1 = u(1:half); u2 = u(half+1:end); c1 = polar_encode(u1, half); c2 = polar_encode(u2, half); x = [mod(c1 + c2, 2), c2]; end

这里把输入 u 切成左右两半,分别递归编码,再把左半与右半异或后放在输出前半,右半原样放在后半。这个递归不做比特逆序重排,所以冻结集的位置也按同一套前后顺序定义;只要编码器和译码器用同一套顺序,误码率曲线就是对的。和kron(G)矩阵写法相比,只是子信道排序不同,不影响SC的性能结论。

2.2 SC译码的f/g函数:比你想的更像LDPC的可加项

SC译码是在二叉树上做消息传递。左子节点拿到两个LLR的合并值,右子节点拿到一个合并值,并附加上左子返回的判决位。LLR约定为log(P0/P1),那么 f 和 g 的 min-sum 近似是:

function L = f_llr(L1, L2) s = sign(L1) * sign(L2); L = s * min(abs(L1), abs(L2)); end function L = g_llr(L1, L2, u_hat) L = (1 - 2 * u_hat) * L1 + L2; end

第一直觉是在MATLAB里用L1 * L2做符号判断,但LLR来自16位定点时乘积可能溢出,所以写成sign(L1) * sign(L2)更安全。另一种做法是保留精确 boxplus:L = 2*atanh(tanh(L1/2)*tanh(L2/2)),但MATLAB里atanh在输入接近 ±1 时损耗很大,除非做数值保护,否则不推荐在SC里用精确版本。

递归译码核心如下,这个函数统计下来不到40行,但包含了SC最关键的左右分支顺序:

function u_hat = sc_decode_rec(alpha, frozen) n = length(alpha); if n == 1 if frozen(1) u_hat = 0; else u_hat = double(alpha < 0); end return; end half = n / 2; alpha_l = zeros(1, half); for i = 1:half alpha_l(i) = f_llr(alpha(i), alpha(half+i)); end u_l = sc_decode_rec(alpha_l, frozen(1:half)); alpha_r = zeros(1, half); for i = 1:half alpha_r(i) = g_llr(alpha(i), alpha(half+i), u_l(i)); end u_r = sc_decode_rec(alpha_r, frozen(half+1:end)); u_hat = [u_l, u_r]; end

说明一下:frozen是长度 n 的逻辑向量,1 表示冻结位,0 表示信息位。叶子节点如果是冻结位,无论 LLR 是什么都判 0,这是SC编译码能工作的前提:收端知道哪些位置不背书。左右分支的索引配对是(i, half+i),对应编码时c1 + c2c2的关系,不是相邻配对。

2.3 叶子的判决与冻结位处理:SC最容易写错的地方

很多初版SC代码出错的点不是f/g,而是frozen的切割与编码顺序不一致。上面代码使用前后分裂,frozen(1:half)对应输入序列前半的冻结位,frozen(half+1:end)对应后半;而实际的左右子树索引通过half映射。如果改成奇偶分裂,这两处都要换成frozen(1:2:end)frozen(2:2:end),并且拼接也要用奇偶插值。记住一条经验:编码怎么拆,译码就怎么切,两边任何一处不对称都会导致深层错误被逐层放大。

LLR的数值安全也要注意。接收端若把纯零符号的LLR算成0,sign(0)在旧版MATLAB会返回0,可能导致f函数输出错误。我一般会先对LLR做一次alpha(alpha==0) = eps的饱和处理。另一个常见问题是double(alpha < 0)在叶子节点返回0/1,而冻结位分支返回整数0,两者类型统一,后续xor才不会报错。到这里,SC的原理和代码骨架已经立住,下一步是把它绑成一条能出曲线的仿真链路。

3. 用MATLAB跑通N=256、K=128的SC编译码最小链路

3.1 参数表:码长、信息位、码率、冻结集构造

仿真前先把参数钉死,避免后面因为信噪比定义乱而浪费半天。下面的表是我常用的起点配置:

参数说明
N256码长,必须是2的幂
K128信息比特数,码率0.5
调制BPSK0映射为+1,1映射为-1
LLR格式double先浮点跑通,再转定点
EbN0范围0dB ~ 2.5dBSC在这个范围能明显看到BER下降
每帧仿真帧数1000低SNR下足够看趋势,高SNR需要更多

冻结集构造不能随机取。常见做法是极化权重(Polarization Weight),它比纯巴氏参数更简单,在码率0.5附近已经够用:

function frozen = build_frozen_mask(N, K) n = log2(N); ind = (0:N-1)'; bits = bitget(ind, 1:n); weights = (2.^(0:n-1)).^0.5; W = bits * weights(:); [~, order] = sort(W, 'ascend'); frozen = false(1, N); frozen(order(1:N-K)) = true; end

这段代码把最不可靠的 N-K 个位置设为冻结位。注意ind从0开始,因为比特位置索引在极化码中习惯用0基;bitget取每一位时,最低位对应 j=0,权重是2^(j*0.5)。如果码率不是0.5,或者想再抠0.1dB,可以把极化权重换成高斯近似(GA)构造,但前者做最小链路已经足够。

3.2 主函数run_sc_polar.m:编码、AWGN、递归译码

有了冻结集和前面两个递归函数,主函数就只剩封装。我一般把它写成一次一帧的独立函数,方便外层循环控制信噪比和统计误码:

function [ber, fer] = run_sc_polar(N, K, EbN0dB) frozen = build_frozen_mask(N, K); info = randi([0 1], 1, K); u = zeros(1, N); u(~frozen) = info; x = polar_encode(u, N); tx = 1 - 2 * x; % BPSK映射 R = K / N; EbN0 = 10^(EbN0dB / 10); N0 = 1 / (R * EbN0); sigma = sqrt(N0 / 2); rx = tx + sigma * randn(1, N); llr = 2 * rx / sigma^2; u_hat = sc_decode_rec(llr, frozen); info_hat = u_hat(~frozen); errs = sum(info_hat ~= info); ber = errs / K; fer = double(errs > 0); end

这里最关键的是噪声功率与LLR刻度。BPSK每符号能量 Es=1,码率 R=K/N,所以 N0 = 1/(R*EbN0),每维噪声方差 σ²=N0/2。LLR用2*rx/sigma^2,这是高斯信道下的精确对数似然比。如果把 σ² 写成 N0,LLR整体放大2倍,译码结果不一定错,但信噪比曲线会横移约0.3dB。

外层仿真循环可以这样写:

EbN0dB_list = 0:0.5:2.5; for snr = EbN0dB_list ber_acc = 0; fer_acc = 0; frames = 1000; for frame = 1:frames [ber, fer] = run_sc_polar(256, 128, snr); ber_acc = ber_acc + ber; fer_acc = fer_acc + fer; end fprintf('EbN0=%4.2f dB, BER=%.3e, FER=%.3e\n', ... snr, ber_acc/frames, fer_acc/frames); end

ber_acc累加的是每帧的误码率,最后除以帧数是平均BER;fer_acc累加误帧次数,除以帧数是平均FER。这种写法比直接累加错误比特数简单,但高SNR时方差大。经验法则是至少累计到100个错误比特再停,否则误码率曲线尾部会抖动。

3.3 排错:递归深度、索引对不上、LLR出现NaN

跑最小链路时最容易遇到三个问题。第一个是递归深度:MATLAB默认递归深度足够上千层,而log2(N)只有8~10层,所以SC本身不会爆栈;但如果你在译码函数里不小心用了循环引用,就会提示“Maximum recursion limit exceeded”。这时先查函数名是否和脚本文件名冲突。

第二个是索引对不上,症状是误码率在0.5徘徊。我一般会先用全零信息测试:信息位全0,发送端编码,LLR设为全正大数,译码输出必须全0。如果输出有1,说明frozen切分或u(~frozen) = info的赋值顺序有错。

第三个是LLR出现NaN,常见于sigma^2计算为0,或EbN0dB给到10以上导致N0极小时浮点下溢。处理办法是在LLR计算后加一句llr(~isfinite(llr)) = sign(llr(~isfinite(llr))) * 1e6,把无穷大饱和到一个足够大的有限值。到这里,你已经能拿到一条完整的SC误码率曲线,但这条曲线离工程可用还有一段距离。

4. 误码率曲线背后的三个必调参数:量化、冻结集、噪声方差

4.1 SC的性能边界:错误平层来自哪里

SC译码在码长趋于无穷时渐进最优,但有限码长下性能比BP译码差不少。原因不是f/g函数近似,而是每一层都做硬判决,一旦某一位选错,错误会通过右分支的g函数传播到后面所有比特。这就是SC的“错误传播”现象。实际仿真里,N=256、K=128的SC在EbN0=2.5dB左右FER还停在1e-2量级,但BP或SCL已经在1e-3以下。因此SC适合作为基线,不适合直接当最终方案。

参数调优对SC来说,本质是在缓解错误传播,而不是消除它。量化位数决定LLR的精度,冻结集决定哪些位置不传信息,噪声方差则决定LLR刻度是否可信。三者互相耦合,改一个要把另外两个重测一遍。

4.2 三个必调参数:量化位数、冻结集构造、噪声方差

下面这张表是我在工程里常用的调参方向,默认值针对N=256浮点仿真:

参数起点值调整方向对误码率的影响
LLR量化位数 q6 bit增大到8 bit高SNR区域错误平层下降,复杂度上升
冻结集构造极化权重 β=0.5换成高斯近似码率高于0.5时能获得0.1~0.2dB增益
噪声方差估计精确σ²固定为典型值偏差超过±0.2dB时曲线明显右移
SC的LLR饱和阈值无饱和饱和到±16防止极端LLR影响f/g函数

量化这一步在MATLAB里写起来很短,但能提前看到定点成本:

q = 6; % 6 bit量化,1符号位 scale = 2^(q-1); % 取值范围 [-32, 31] llr_q = round(llr * scale); llr_q = max(-scale, min(scale-1, llr_q));

注意量化因子scale与LLR的幅度直接相关。如果接收端LLR本身在0.1量级,乘scale后会变成小数附近的值,再round会丢失大量信息。常见的做法是先统计一段浮点LLR的均方根,再把scale设为均方根的2~4倍。这里仅仅演示固定点格式,实际使用时需要先做一段校准。

噪声方差参数往往被忽视。很多人直接按理想σ²仿真,但接收端并不知道真实噪声功率。把σ²高估10%,LLR整体缩小,SC的f函数倾向保守,误码率在高SNR区域会变差。一个稳妥做法是把σ²当作未知量,测试区间设为0.8*sigma_hat1.2*sigma_hat,看曲线上哪一段最敏感。通常SC对σ²的敏感度在0.3dB以内。

4.3 用白盒测试确认译码器正确性

调参之前先做两轮白盒测试,能省下大量排错时间。第一轮是全零测试,第二轮是单比特图样测试。全零测试的做法是固定信息位全0,LLR设为足够大的正数:

u = zeros(1, 256); x = polar_encode(u, 256); frozen = build_frozen_mask(256, 128); u_hat = sc_decode_rec(100 * ones(1, 256), frozen); assert(all(u_hat == 0), '冻结位或递归索引错误');

单比特图样测试是让信息位里只有一个1,其它全0,例如info(37) = 1。这时你应该去检查译码输出的第37个信息位是否也只在37位有1。如果出现两个1,说明冻结集与编码顺序不匹配。通过这两轮测试后,再进入4.2的调参就不会被底层错误干扰。

5. 把SC改成SCL:只加路径复制与PM更新,误码率再降0.5dB

5.1 路径度量PM:SCL和SC的唯一分叉

SCL(Successive Cancellation List)和SC的唯一区别是:SC在每层只保留一条判决路径,SCL保留L条。每一条路径都带着自己的路径度量PM,PM是这条路径的累积负对数似然。LLR约定log(P0/P1),那么判决选0但LLR为负时,PM惩罚-LLR;判决选1但LLR为正时,PM惩罚+LLR。SCL就是在每次叶子判决时把1条路变成2条路,更新PM,然后按PM排序保住最小的L条。

5.2 最小改动代码:从单路径到L路径

如果你已经写好了递归SC,改成SCL不需要动f/g函数,只需要把叶子判决部分换成路径扩展。下面的MATLAB片段是路径扩展的最小实现:

function [new_paths, new_pm] = path_extend(paths, pm, llr_leaf, L) new_paths = []; new_pm = []; for l = 1:length(pm) for u = 0:1 new_paths(end+1, :) = [paths(l, :), u]; penalty = max(0, (2*u - 1) * llr_leaf(l)); new_pm(end+1) = pm(l) + penalty; end end [~, order] = sort(new_pm, 'ascend'); keep = order(1:L); new_paths = new_paths(keep, :); new_pm = new_pm(keep); end

这里llr_leaf(l)是第 l 条路径当前叶子节点的LLR。选择 u=0 时,LLR为正没有惩罚,LLR为负则惩罚-LLR;选择 u=1 时相反。路径矩阵new_paths的每一行是一条完整的上层判决序列,列数会随着递归深度增加。注意冻结位节点不调用这个函数,直接把所有路径追加0,保持路径数不变,否则冻结位会被错误地扩展成两个分支。

提示:SCL完整实现还需要把LLR按路径组织成 L×N 的矩阵,并且逐层传递LLR时也要对L条路径分别调用f/g函数。否则把递归SC直接套在这个函数上,时间复杂度会变成L倍的SC,但复用性不高。

5.3 实测对比:L=2、L=4的变化

用N=256、K=128验证,SCL-2在EbN0=1.5dB时FER大约比SC低三分之一;SCL-4在2.0dB附近比SC低0.5dB左右。增益来自路径多样性,但路径数翻倍并不线性改善性能,L=4到L=8的增益已经很小。如果要在有限码长下继续提升,必须在编码时追加CRC校验,让SCL在最后用CRC从保留路径中挑最可靠的,否则SCL-16在高SNR可能因为路径度量累计误差反而不如SC。加CRC时用CRC-8配L=4即可,L=8配CRC-16收益更明显。

工程落地的下一步是把递归f/g调用改成循环栈,再把LLR从double切成16位定点;顺序是先改SCL,再改量化,不要同时改两个变量。跑一次256长度、1000帧的仿真,SCL-4和SC的差异会立刻出现在BER曲线上。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 11:04:55

从服务在线到消息可信:私有化IM可靠性设计实战解析

凌晨两点半&#xff0c;手机在床头柜上震得像个疯子。我迷迷糊糊接起来&#xff0c;对面是驻场运维的急促声音&#xff1a;“客户那边IM系统看起来是正常的&#xff0c;所有账号都在线&#xff0c;但有个群里有人发消息&#xff0c;只有一部分人收到了。”这句话我到现在还记得…

作者头像 李华
网站建设 2026/9/16 11:04:53

开源合规视角下的编程语言选型与工程实践

这几年技术社区里&#xff0c;开源合规和编程语言这两个词几乎每天都能在热榜上撞见。打开任何一个技术媒体&#xff0c;编程语言排行榜、语言推荐、Dart 教程的帖子永远是流量担当&#xff0c;但大家聊得最多的始终是语法好不好用、生态全不全、招人好不好招&#xff0c;很少有…

作者头像 李华
网站建设 2026/9/16 11:02:21

XTR111电压转电流电路调试:5V输入为何无输出?

有个同行发来一张XTR111应用电路的截图&#xff0c;问了个特别典型的问题&#xff1a;输入给到5V&#xff0c;负载端死活没有电流&#xff1b;可同一张电路放到仿真软件里&#xff0c;却能跑出“正常”的波形。他最后补了一句&#xff1a;我这个实物电路&#xff0c;到底能不能…

作者头像 李华