简介:极化码在高斯信道下的CA-SCL译码算法MATLAB实现,面向通信、电子信息工程及数学等相关专业学生的课程设计、期末大作业与毕业设计。代码提供完整可运行的仿真框架,支持MATLAB 2014/2019a/2024a,通过参数化编程方式方便修改码长、信噪比等关键参数,注释详细,适合新手快速上手。压缩包内共17个文件,以m脚本为主,包含极化编码、AWGN信道、CRC附加与校验、路径度量计算、CA-SCL译码等核心模块,另附fig与png便于查看波形及绘图结果,cpp与mexw64用于加速似然比计算。代码附带可直接运行的案例数据,替换数据即可验证算法性能,帮助理解信道极化与列表译码原理。资源包仅57KB,轻量易用,已有132人学习下载,是研究极化码译码技术的高性价比入门工具。
1. 极化码CA-SCL译码算法在高斯信道下的验证路径
极化码是第一个被证明容量可达的构造性信道编码,5G 的 eMBB 控制信道里已经用上 CA-SCL 这种“CRC 辅助 + 列表搜索”的译码算法。实际做链路验证时,AWGN 信道是第一个要过的关口:LLR 有解析表达式,误块率曲线可以和理论界对照,排错也比衰落信道直观。这个标题讲的正是“在高斯信道下把 CA-SCL 译码算法用 MATLAB 跑通并测出误块率”的完整链路,覆盖编码、BPSK 调制、加噪、LLR 计算、SCL 列表搜索和 CRC 选路六段。
对刚接触信道编码的人来说,极化码的难点往往不是原理,而是把递归译码器改成列表译码器时“每条路径的度量怎么累加、分裂后怎么裁剪”这些工程细节;对有经验的通信工程师来说,噪声方差换算和 CRC 位数选择这类小参数反而最容易让两套实现得到差 0.3 dB 的结果。下面沿一条最小可复现的链路展开,先立理论,再给代码骨架,最后落在参数权衡和加速技巧上,遇到坑的地方会直接点出来。
2. 高斯信道下CA-SCL译码算法的原理与路径度量
2.1 从SC到SCL:极化码译码算法的列表搜索
SC 译码器按比特顺序逐个硬判,前一个比特判错会沿递归结构向后扩散,中短码长下误块率曲线很难压下去。SCL 的做法是每遇到一个信息位就把路径分裂成两条,分别假设该位为 0 和 1,然后只保留度量最好的 L 条继续往下走;最后一次从 L 条完整候选里挑一条作为输出。
CA-SCL 的“CA”就是在 SCL 之上叠一个 CRC:编码前给 K 位信息追加 crcLen 位 CRC,SCL 译到最后一层后先对 L 条完整候选做 CRC 校验,再在通过的路径里选路径度量最优的一条。这里要特别注意一个容易混的点:极化码编码输入的实际信息位数量是 K+crcLen,不是 K。信息位集合 A 的尺寸、信道编码速率、以及最后算误块率时的“有效信息速率”是三套数字,分别对应不同用途。
SCL 的复杂度优势来自路径数量被限制在 L。每次信息位分裂后候选最多 2L 条,排序后截断到 L 条;整体复杂度大致是 O(L·N·logN),N 为码长。L=1 时退化为 SC,L 越大越接近最大似然译码,但增益递减,这个趋势到第 4 章会专门展开。
2.2 高斯信道下的LLR初始化与路径度量PM的递推
先固定调制。BPSK 映射把码字 c 变成 x=1-2c,接收端 y=x+n,n 是零均值、方差 σ² 的高斯噪声,σ² 与单边功率谱密度 N0 的关系是 σ²=N0/2。接收 LLR 为:
alpha = 2y / σ²
设每符号能量 Es=1,则 Es/N0 = 1/N0,于是 σ²=1/(2·10^(EsN0dB/10));若曲线横轴用 EbN0,则 Es/N0 = R·Eb/N0,换算为:
σ² = 1 / (2·R·10^(EbN0dB/10))
其中 R 按横轴定义取 K/N 或 (K+crcLen)/N。两种定义会让整条误块率曲线平移约 10·log10(K/(K+crcLen)) dB,这部分在第 4 章再具体算。
路径度量 PM 定义成“累计惩罚”,越小越好。精确递推是:
PM_i = PM_{i-1} + log(1 + exp(−(1−2u_i)·alpha_i))
其中 u_i 是该路径在当前比特假设的取值。实际 MATLAB 实现里几乎不会用 log 和 exp,而是用 min-sum 近似:如果 u_i 与当前 LLR 的硬判一致,惩罚为 0;否则惩罚加 |alpha|。写成可复现的函数:
function pm = updatePm(pmIn, alpha, uHat) pHard = double(alpha < 0); % alpha>=0 判为 0,alpha<0 判为 1 errBit = (uHat ~= pHard); % 假设值是否与硬判相反 pm = pmIn + errBit .* abs(alpha); % 相反则累加 |alpha| end代码把 pmIn、alpha、uHat 都当作行向量,一次算完一整组候选路径的 PM 更新。注意 pHard 来自 alpha 的符号,和调制映射严格绑定:上面定义里 LLR 为正对应发送比特 0,负对应发送比特 1。若把 x 的映射写反,PM 递推和最后候选排序会整体取反,但误块率曲线形状仍然正常,只有逐比特对照时才发现高位全错,这是最容易被漏掉的错误。
2.3 SCL列表剪枝:每比特后只保留L条
信息位分裂后候选变成 2L 条,需要按 PM 从小到大排序再截断。冻结位不分裂,直接按硬判扩展路径。常见做法是一次性把 2L 条分支的 PM 全部算出来,再排序取前 L 条;不要在递归调用里逐条比较后再决定,MATLAB 的向量化在批量计算上的优势远大于手动循环。
剪枝保留的是“PM 最小的 L 条路径”,这一步不依赖信道状态信息,因此也被称为无偏剪枝。也能用带阈值的提前剪枝:某条路径 PM 明显大于当前最小 PM 就直接丢弃。工程上我一般不做绝对阈值,因为阈值和信噪比强相关,换一个 EbN0 点就要重新标定,省下来的时间大概率不够填调参的坑。列表数量不大时,直接排序截断就是最稳妥、最不容易写错的选择。
3. 用MATLAB实现极化码CA-SCL译码算法的最小链路
3.1 工程文件划分与关键参数表
一条可复现的 CA-SCL 链路,我习惯拆成 5 个文件:主脚本控制蒙特卡洛循环;polar_encode.m 做极化码编码;awgn_llr.m 负责接收和 LLR 计算;scl_decode.m 做列表译码并返回 L 条候选;crc_utils.m 负责 CRC 追加和校验。模块拆开之后,误块率统计、单帧调试、性能剖析可以各自独立进行,排查问题时不用从头跑一遍。
编码前的参数可以先对照这张表定清楚:
| 参数 | 符号 | 典型值 | 说明 |
|---|---|---|---|
| 码长 | N | 256 | 必须是 2 的幂 |
| 有效信息位数 | K | 128 | 实际承载的用户比特 |
| CRC 位数 | crcLen | 8 | 决定错误漏检概率 |
| 列表大小 | L | 16 | 路径数,越大越接近 ML |
| 调制方式 | BPSK | 1 bit/符号 | AWGN 下 LLR 最简洁 |
| EbN0 范围 | EbN0dB | 1:0.5:3 | 覆盖 SC 和 CA-SCL 的分水岭 |
信息位集合 A 可以用高斯近似预先计算,也可以直接查一张 256 长的可靠度排序表;关键是 A 的编号必须和代码里生成矩阵 G 的构造顺序一致。下面生成矩阵用 kron(G,F) 自底向上堆叠,那 A 也必须按同一套编号定义,混用不同文献的索引表是复现代码时最常见的翻车点。
3.2 主脚本与编码器核心代码
先给可运行的主循环骨架:
% main_polar_ca_scl.m N = 256; K = 128; L = 16; crcLen = 8; EbN0dB = 2.0; R = K / N; % 横轴按 K 个有效信息比特定义 sigma2 = 1 / (2 * R * 10^(EbN0dB / 10)); % 噪声方差 A = designInfoBits(N, K + crcLen); % 高斯近似计算信息位集合 fz = setdiff(1:N, A); % 冻结位集合 info = randi([0 1], 1, K); u = crcAppend(info, crcLen); % 追加 CRC 后长度 K+crcLen c = polarEncode(u, A, N); % 码字,长度 N x = 1 - 2*c; % BPSK 调制 y = x + sqrt(sigma2) * randn(1, N); % AWGN 信道 alpha = 2*y / sigma2; % LLR list = sclDecode(alpha, A, fz, L); % 得到 L 条候选路径 out = crcSelect(list, crcLen); % 通过 CRC 的路径里选 PM 最小者sigma2是每维噪声方差,和单边功率谱密度 N0 的关系是 sigma2=N0/2,所以代码里完全不出现 N0 这个变量,避免和 LLR 公式里的 2 混淆。R=K/N表示横轴 Eb 按有效信息比特定义,若你想把 CRC 也算作信息比特,R 要换成 (K+crcLen)/N,两条曲线的相对关系见 4.2 的换算公式。
polarEncode的最小实现如下:
function c = polarEncode(u, A, N) n = log2(N); F = [1 0; 1 1]; G = 1; for j = 1:n G = kron(G, F); % 生成矩阵,顺序必须与 A 一致 end x = zeros(1, N); x(A) = u; % 信息位放 u,冻结位保持 0 c = mod(x * G, 2); % 线性变换得到 N 位码字 end生成矩阵用 kron(G,F) 逐级扩展,冻结位固定为 0。designInfoBits 和 sclDecode 是工程里最长的两个部分,前者约 40 行,后者递归部分约 120 行;designInfoBits 可以用高斯近似对每个子信道计算可靠性度量,取最大的 K+crcLen 个位置构成 A。crcAppend 和 crcSelect 用移位寄存器实现:发送端对信息序列做模 2 多项式除法,余数附在末尾;接收端对每条候选路径重算相同的校验值,丢弃不匹配路径。
3.3 SCL递归译码器的列表数据结构
SCL 递归每一层维护三样东西:路径对应的部分信息位序列 uList、每条路径当前的 PM、以及递归中要传下去的 LLR。推荐直接用数值矩阵而不是结构体数组:uList 是 L×m 的 0/1 矩阵,pm 是 1×L。信息位分裂时用 repmat 复制路径,再批量调 updatePm:
% sclDecode 中信息位分裂的一段 u2 = repmat(uList, 2, 1); % 每条父路径复制两份 newBit = [zeros(L, 1); ones(L, 1)]; % 前半补 0,后半补 1 u2 = [u2, newBit]; % 路径序列扩展一列 pm2 = updatePm(repmat(pm, 1, 2), ... repmat(alphaCurrent, 1, 2), ... [zeros(1, L), ones(1, L)]); [~, idx] = sort(pm2); % 按 PM 升序 keep = idx(1:L); uList = u2(keep, :); pm = pm2(keep);uList是 L×m,pm是 1×L,alphaCurrent是该比特位置上每条路径各自的 LLR,长度也是 1×L。repmat(pm,1,2) 把父路径的 PM 复制成 1×2L,newBit 的前 L 个 0 和后 L 个 1 正好对应复制出的前半段和后半段。注意 updatePm 里的 errBit 依赖 newBit 与 alpha 符号的一致性,这条约定必须贯穿整个译码器。冻结位处理更简单:直接按当前 LLR 硬判扩展 uList,不增加候选数量,pm 保持不变。
4. 列表大小L和CRC长度对极化码误块率的影响
4.1 L=8、16、32:增益递减与算力权衡
列表大小是 CA-SCL 最直接的旋钮。以 N=256、K=128、crcLen=8、BPSK 的典型配置为例,相对趋势如下表,不同实现会有差异但规律一致:
| 列表大小 L | 相对 L=8 的 BLER 改善 | 译码耗时相对值 |
|---|---|---|
| 8 | 1×(基准) | 1.0 |
| 16 | 约 0.3~0.5× | 约 1.8~2.2 |
| 32 | 约 0.1~0.2× | 约 3.2~3.8 |
L 翻倍在低信噪比区间增益明显,高信噪比下主要影响错误地板;但 L 从 16 到 32 的改善远小于从 8 到 16。原因是当 L 已经能覆盖“真正正确路径”时,继续加路径只是在重复枚举同一条正确路径周围的错误序列。
做蒙特卡洛仿真时我一般先在 L=8 上跑通链路,确认曲线趋势后再升到 16 或 32;不要一上来就跑 L=32,参数设错时一次仿真要跑几个小时才能发现。若目标是看性能上界,可以用 L=128 跑一次小样本估算“天花板”,再回来选一个性能和耗时都可接受的 L。
4.2 CRC长度怎么选:码率损失与漏检概率
CRC 的作用是帮 SCL 在 L 条候选里挑“PM 不是最小但确实正确”的那条。CRC 太短,错误路径恰好通过校验的概率高;CRC 太长,有效信息速率下降,增益可能被码率损失抵消。经验配置是 N=256 附近用 CRC-8,N≥512 或要求更低误码地板时用 CRC-16。8 位 CRC 的漏检概率大约在 2^-8 量级,16 位到 2^-16 量级;注意这里说的是“错误帧被误判为正确的概率”,不是误块率本身。
码率损失这样算:编码输入长度变成 K+crcLen,信道编码速率变成 (K+crcLen)/N,但有效信息速率仍是 K/N。例如 N=256、K=128、crcLen=8,两个速率的差异对应:
lossDb = 10 * log10(K / (K + crcLen));算出来约 -0.26 dB。也就是说,如果把 CRC 位也算进横轴对应的信息比特里,曲线会比按 K 定义的有效信息比特横轴虚高 0.26 dB。同一个译码器画出来的曲线不同,不代表性能不同,只是横轴标尺不一致。
4.3 高斯信道仿真里最容易错的三个参数设置
第一是噪声方差换算。sigma2 = 1/(2·R·10^(EbN0dB/10)),其中 R 必须和横轴定义一致;写错时整体曲线平移但形状不变,非常隐蔽。建议把换算公式作为注释挂在主脚本开头,每次改码率时同步改 R。
第二是信息位集合 A 与生成矩阵顺序不一致。A 来自高斯近似,G 来自 kron(G,F),两者编号不一致时编码端和译码端对同一位置的可靠性理解错位,表现是误块率在高信噪比下掉不下去。第三是 LLR 符号约定。正负号定义必须贯穿 BPSK 映射、PM 累加、CRC 选路三处,任何一处取反都会得到“看起来能跑、结果全错”的链路。
5. 让MATLAB里的CA-SCL译码算法跑得更快一点
5.1 用 mink 替换 sort 做路径修剪
递归每一层都要从 2L 条候选里选 L 条,sort 的复杂度是 O(2L·log(2L)),mink 只需要 O(2L)。L 较小时差别不大,L=32 开始变得可观。替换只需要一行:
[pm, idx] = mink(pm2, L); uList = u2(idx, :);mink 从 R2018a 开始可用,要求兼容旧版本时可以保留 sort。另一个等价写法是“先全部展开再剪枝”,也就是第 3.3 节的做法,而不是逐层只保留 L 条再进下一层;两者最终候选集合等价,但前者向量化更整齐,运行效率反而更高。
5.2 单帧调试:固定随机数和断点回放
把rng(2024)固定在主脚本开头,蒙特卡洛循环里每次加噪前再rng(i),这样任一帧出错都能用相同索引的接收向量完整复现。调试时在 sclDecode 入口保存 alpha 和 A 到 .mat 文件,出错帧直接读文件单步跟踪,不需要重跑整个仿真循环。
对递归函数,在“信息位分裂”和“冻结位扩展”两个分支各打一个断点,观察 uList 的尺寸是否按 L → 2L → L 变化,能快速定位列表管理问题。绝大多数“结果不对”的情况,在跟踪两次分裂后就能看出是排序方向反了还是 newBit 和 alpha 顺序对不上。
5.3 路径排序里隐藏的结构化加速
当 L 固定时,2L 条候选来自 L 对“同父分支”,可以先对每一对取局部最小值,再对 L 个局部最小值排序,最后补上对应的次小值。这样每层比较次数压缩到约 2L 量级,作为练习很适合;实际收益要看 MATLAB 的 JIT 是否把循环优化到位。更省事的优化是把所有父路径的分裂、PM 更新、mink 放在一层完成,避免在每比特里写 for 循环,这也是 3.3 代码强调向量化的原因。把列表数据结构从 struct 换成纯数值矩阵后,L=32 的单帧耗时常能再下降三分之一左右。
本文还有配套的精品资源,点击获取