简介:这份资源围绕极化码(Polar Code)与CA-SCL解码器展开,面向无线通信、信道编码方向的学习者与研究人员,帮助理解从信道极化理论到级联SCL解码的完整实现思路。压缩包共16个文件,约19KB,以12个MATLAB脚本(.m)为核心,涵盖极化编码、SCL/CA-SCL解码、CRC校验、路径度量与AWGN信道仿真等模块;另有2个C++源文件及配套mexw64动态库,用于加速似然率计算,并附一份运行日志便于排查环境问题。已有216人学习下载。读者可据此复现极化码编码、冻结位构造、列表解码与回溯判决等关键流程,对照代码理解列表大小与回溯深度对性能的影响,并借助CRC辅助机制观察误码率变化,适合作为5G NR极化码入门实践与算法验证的参考素材。
1. 从 polar.zip 说起:CA-SCL 译码为什么值得你花一个周末跑通
如果你最近在搜polar.zip_CA SCL_polar code,大概率是手里已经有一份 Polar 码的仿真工程,或者正准备从 SC 译码往 CA-SCL 方向迁移。Polar 码在 5G 控制信道里被采用之后,SC(Successive Cancellation)译码的短板就暴露得很明显:码长一长、信噪比一低,误块率曲线掉得不够快。CA-SCL(CRC-Aided Successive Cancellation List)就是在这个背景下成为工程默认选项的——它把 CRC 校验位当作“后悔药”,在列表里保留多条候选路径,最后用 CRC 挑出最像的那条。
这个标题对应的不是一篇论文,而是一类可复现的工程包:一个能跑通 Polar 编码、AWGN 信道、SC/SCL/CA-SCL 译码对比的仿真目录。适合谁?适合正在做链路级仿真、需要给 Polar 码选译码方案、或者要复现 CA-SCL 性能曲线的工程师和学生。它解决的核心问题是:让你在本地用几十行 Python 或 C 代码,把 CA-SCL 相对 SCL 的增益跑出来,而不是只停留在公式推导。下面我按“先立住原理、再动手复现、最后避坑”的顺序,把这条路走一遍。
2. CA-SCL 译码的工程拆解:从 SC 到列表再到 CRC 筛选
2.1 SC 译码的递归结构决定了后面所有优化
SC 译码的本质是按比特逐个判决,每个比特的判决依赖前面所有已判决比特。工程上通常用递归的 f 和 g 函数在因子图上做软信息传递。f 函数处理两个似然比的合并,g 函数在已知一个比特后修正另一个分支的软信息。这个结构决定了 SC 的复杂度是 O(N log N),也决定了它一旦前面判错,后面无法回退。
我一般会先写一个最朴素的 SC 递归版本,确认因子图索引和冻结位位置没错,再往上叠列表。因为 SCL 的所有路径扩展都建立在 SC 的软信息计算之上,SC 写错,SCL 的曲线会整体偏移,后面调参数就是玄学。
import numpy as np def f_func(a, b): # 两个似然比的 f 合并,对应因子图上半部分 return np.sign(a) * np.sign(b) * min(abs(a), abs(b)) def g_func(a, b, u): # u 为已判决比特,对下分支做修正 return b + (1 - 2 * u) * a def sc_decode(llr, frozen_pos): # llr: 信道对数似然比,frozen_pos: 冻结位索引集合 N = len(llr) u_hat = np.zeros(N, dtype=int) # 这里省略递归展开,实际工程用迭代或递归树实现 # 关键点:冻结位强制判 0,信息位按 llr 符号判 for i in range(N): if i in frozen_pos: u_hat[i] = 0 else: u_hat[i] = 0 if llr[i] >= 0 else 1 return u_hat上面这段是骨架,真实工程里 llr 会随递归层级更新,不能直接用信道 llr 判所有位。参数说明:frozen_pos来自 Polar 码构造,常用高斯近似或巴氏参数法生成;llr的符号约定要统一,我习惯正数判 0、负数判 1,换约定会让曲线完全对不上。
2.2 SCL 的列表扩展:路径数 L 不是越大越好
SCL 在每个信息位同时保留 0 和 1 两个分支,路径数翻倍,然后按路径度量(PM)排序,保留最好的 L 条。PM 的更新和 llr 的绝对值有关:判决与 llr 符号一致时 PM 不变,不一致时 PM 增加 |llr|。这个设计让 PM 越小路径越可靠。
L 的选取是工程折中。L=1 退化成 SC;L=4 或 8 在短码下已经能拿到大部分增益;L=32 以上收益递减,但内存和排序开销线性涨。我一般先在 L=8 上把曲线跑通,再扫 L=1/2/4/8/16/32 看拐点。
def scl_decode(llr, frozen_pos, L=8): # 路径用 (pm, u_hat, llr_state) 表示,pm 越小越好 paths = [(0.0, [], llr.copy())] N = len(llr) for i in range(N): new_paths = [] for pm, u_hat, state in paths: if i in frozen_pos: # 冻结位只扩展 0 分支 new_paths.append((pm, u_hat + [0], state)) else: # 信息位扩展 0 和 1 两个分支 for bit in (0, 1): # 简化 PM 更新:按 llr 符号与 bit 是否一致累加 penalty = 0 if (state[i] >= 0) == (bit == 0) else abs(state[i]) new_paths.append((pm + penalty, u_hat + [bit], state)) # 按 PM 升序保留前 L 条 new_paths.sort(key=lambda x: x[0]) paths = new_paths[:L] return min(paths, key=lambda x: x[0])[1]这段代码把路径扩展和排序逻辑写清楚了,但真实工程里state要随递归更新,不能一直用初始 llr。参数说明:L是列表宽度,直接决定复杂度和内存;penalty的计算依赖 llr 的数值尺度,如果信道 llr 做了归一化,PM 的绝对值会变,但排序相对关系不变。
2.3 CRC 辅助:把列表里的候选路径用校验位筛一遍
CA-SCL 的关键增量是在信息位后面附上 CRC 校验位,译码结束后对每条候选路径做 CRC 校验,第一条通过校验的路径就是输出。如果所有路径都没通过,通常回退到 PM 最小的那条。CRC 长度常见 6、11、16、24 位,长度越长虚警概率越低,但占用的信息位也越多。
工程上要注意:CRC 校验的是译码后的信息比特,不是编码前的。所以路径的u_hat里要先把信息位提取出来,再做 CRC。我见过有人直接对全零填充后的码字做校验,曲线看着对,实际误块率偏高,这就是黑匣子没打开。
def crc_check(u_hat, info_pos, crc_len, poly=0x1021): # 提取信息位和 CRC 位,做模 2 除法校验 info_bits = [u_hat[i] for i in info_pos] msg = info_bits[:-crc_len] crc_recv = info_bits[-crc_len:] # 简化 CRC 计算,实际用查表或位运算 crc_calc = 0 for bit in msg: crc_calc = ((crc_calc << 1) | bit) & ((1 << crc_len) - 1) return crc_calc == int(''.join(map(str, crc_recv)), 2) def ca_scl_decode(llr, frozen_pos, info_pos, L=8, crc_len=16): paths = scl_decode(llr, frozen_pos, L) # 这里应返回多条路径 # 实际工程中 scl_decode 要返回排序后的路径列表 for u_hat in paths: if crc_check(u_hat, info_pos, crc_len): return u_hat return paths[0] # 全部失败则回退 PM 最小参数说明:poly是 CRC 生成多项式,不同标准用的不一样,选错会导致校验永远失败;info_pos是信息位索引,必须和冻结位集合互补且不重叠。这段代码的scl_decode需要改成返回多条路径,否则 CA-SCL 没有意义。
3. 在本地跑通 CA-SCL:从 polar.zip 到误块率曲线
3.1 目录结构和依赖:先把环境钉死
拿到一个polar.zip类似的工程包,我第一件事不是跑主程序,而是看依赖和目录。常见结构是src/放编码译码,sim/放仿真脚本,config/放码长、码率、信噪比扫描配置。依赖通常只有 numpy 和 matplotlib,少数用 C 加速的会带 pybind11 或 ctypes。
我一般会建一个干净的虚拟环境,把 numpy 版本钉在 1.24 左右,避免新版本 API 变动导致np.sign行为差异。然后先跑一个最小用例:N=64,K=32,SNR=2dB,L=8,看能不能出比特。这一步过了,再上误块率扫描。
python -m venv polar_env source polar_env/bin/activate # Windows 用 polar_env\Scripts\activate pip install numpy==1.24.3 matplotlib==3.7.1 python -c "import numpy; print(numpy.__version__)"逻辑说明:虚拟环境隔离系统包,避免和已有工程冲突;版本钉死是为了复现曲线,numpy 大版本升级有时会改随机数生成器默认行为,导致 SNR 扫描结果不可比。
3.2 编码和构造:冻结位位置决定性能上限
Polar 编码本身很简单:把信息位放在可靠度高的位置,冻结位放 0,然后做比特反转和异或。难点在构造——怎么知道哪些位置可靠。工程上常用高斯近似(GA)或巴氏参数法。GA 在 AWGN 下更准,但计算量大;巴氏参数法快,短码下够用。
我一般先用巴氏参数法生成冻结位,跑通后再换 GA 对比。构造错了,SCL 的曲线会整体差 1dB 以上,而且 L 越大差距越明显,因为列表在错误的位置上扩展。
def construct_frozen(N, K, design_snr=2.0): # 巴氏参数法简化版:按信道容量排序,选 K 个最可靠的位置 # 实际工程用递归计算巴氏参数或高斯近似 reliabilities = np.zeros(N) for i in range(N): # 这里用索引的二进制表示近似可靠度,仅作示例 reliabilities[i] = bin(i).count('1') # 占位逻辑,真实实现需替换 order = np.argsort(reliabilities)[::-1] info_pos = set(order[:K]) frozen_pos = set(range(N)) - info_pos return frozen_pos, info_pos参数说明:design_snr是构造时假设的信噪比,设高了会偏向高可靠位置,设低了会保守;K是信息位数量,包括 CRC 位。这段的可靠度计算是占位,真实工程必须用 GA 或巴氏参数递推,否则曲线不可信。
3.3 误块率扫描:SNR 点怎么取、多少帧才够
跑误块率曲线,SNR 点一般从 0dB 到 4dB,步长 0.5dB。每个点至少 1000 个块,误块率到 1e-3 以下要 10000 块以上。我习惯先跑 200 块看趋势,曲线形状对了再补帧数。帧数不够时曲线会抖,尤其是 CA-SCL 在低误块率区,CRC 筛选让有效样本变少。
def simulate_fer(N, K, snr_db, L, crc_len, frames=1000): frozen_pos, info_pos = construct_frozen(N, K) errors = 0 for _ in range(frames): msg = np.random.randint(0, 2, K - crc_len) # 编码、加噪、译码流程省略,核心是调用 ca_scl_decode # 比较译码结果和原始 msg,统计误块 pass return errors / frames逻辑说明:frames是每个 SNR 点的块数,直接决定曲线平滑度;snr_db是符号信噪比,换算成 llr 时要乘 2/方差。这段没写完整编码,是因为编码部分和构造强相关,建议先单独验证编码器输出和标准一致。
4. CA-SCL 调参和排错:那些让曲线对不上的坑
4.1 现象:CA-SCL 曲线和 SCL 完全重合
原因通常是 CRC 校验没生效。检查info_pos是否包含了 CRC 位,crc_check是否真的在筛选路径。如果scl_decode只返回一条路径,CA-SCL 就退化成 SCL。解决:让scl_decode返回排序后的路径列表,逐条做 CRC,打印通过校验的路径索引。
4.2 现象:L 增大后误块率反而变差
一般是 PM 更新符号反了。PM 应该越小越好,如果排序时取了降序,保留的是最差路径。检查new_paths.sort(key=lambda x: x[0])是否升序,以及penalty是否在判决与 llr 不一致时增加。另一个可能是 llr 符号约定和 PM 更新不匹配。
4.3 现象:高 SNR 下曲线出现错误平台
CRC 长度不够或生成多项式选错。CRC-16 在块长 64 时虚警概率约 1/65536,如果曲线在 1e-4 附近压不下去,先换 CRC-24 试试。另外检查 CRC 计算是否覆盖了所有信息位,漏掉最后几位会导致校验形同虚设。
4.4 现象:仿真速度慢到跑不完
Python 递归实现 SCL 在 N=1024、L=32 时每帧要几百毫秒。常见优化是把递归改成迭代,用 numpy 向量化路径扩展,或者把译码核心用 C 重写。我一般先用 N=256 跑通逻辑,再上大码长。如果只是验证算法,不必一上来就 N=1024。
5. 把 CA-SCL 用对:从仿真到链路级验证的几个习惯
CA-SCL 的仿真跑通只是第一步,真正要用到链路里,我习惯做三件事。第一,固定随机种子,把编码、加噪、译码的随机源分开,这样换 L 或 CRC 长度时曲线可比。第二,单独验证编码器:全零信息位编码后应该得到全零码字,全一信息位编码后和标准表对一遍。第三,画曲线时同时画 SC、SCL、CA-SCL 三条,确认增益方向符合预期——CA-SCL 应该在 SCL 基础上再降,而不是重合或更差。
进阶用法上,可以试 CRC 长度和 L 的联合扫描。我一般列一个表,行是 L=4/8/16/32,列是 CRC=6/11/16/24,每个格子跑 2000 帧,看哪个组合在目标误块率下复杂度最低。常见结论是 L=8、CRC=16 在短码下性价比最高,但码长到 512 以上时 L=16 开始有优势。
| L \ CRC | 6 | 11 | 16 | 24 |
|---|---|---|---|---|
| 4 | 增益小 | 可用 | 推荐 | 过设计 |
| 8 | 可用 | 推荐 | 推荐 | 可用 |
| 16 | 一般 | 可用 | 推荐 | 推荐 |
| 32 | 一般 | 可用 | 可用 | 推荐 |
验证方法上,除了误块率,我还会看平均列表路径数和 CRC 通过率。如果 CRC 通过率在高 SNR 下不到 90%,说明 CRC 太短或路径排序有问题。这些指标比单看曲线更能定位问题。
最后说个血泪经验:别在没验证编码器的情况下调译码参数。我见过太多人花两天调 L 和 CRC,最后发现是冻结位构造错了。先让 SC 在无噪下误块率为零,再上 SCL,再上 CA-SCL,每一步都确认前一步是对的。这个习惯帮我省了至少三个周末。希望帮到你。
本文还有配套的精品资源,点击获取