
简介这是一份面向通信工程、电子信息类学生及研究人员的MATLAB极化码系统仿真代码包聚焦5G极化码从构造、编码、信道模拟到SC/SCL/BP译码与误码率分析的全流程实现。压缩包共44个文件其中39个m脚本覆盖序列生成、信息位选择、速率匹配、CRC校验、子块交织、SCL译码、UCI编解码等核心模块另有2个txt序列参数、2个mat数据文件及1个说明文档便于直接运行与对照学习。资源包仅32KB体量轻但功能完整。目前已有526人学习下载。通过该资源可掌握5G NR极化码的完整MATLAB实现思路获得可直接调用或二次开发的仿真脚本适合以此为基础展开性能对比、算法优化或毕业设计实验。1. 极化码系统仿真前先把编码、可靠度、SC 译码这三件事对齐极化码是少数能在理论上严格证明逼近香农极限的编码方案但“理论简单”和“仿真能跑通”经常是两回事。很多人看论文知道要用生成矩阵和 SC 译码真到 MATLAB 里写系统仿真时却容易被比特顺序、LLR 符号、可靠度排序和蒙特卡洛帧数这些细节拖住。这篇博文适合准备做编码研究或通信链路验证的工程师目标很明确用 MATLAB 从零搭出一条可复现的极化码系统仿真链路先按 N256、码率 1/2 跑通再平滑扩展到更长码长、CRC-SCL 和码率匹配。这里不依赖额外通信工具箱核心代码用基础 MATLAB 函数就能运行方便你拆开看每一步的输入输出。2. 生成矩阵与可靠度计算MATLAB 里搭极化码编码器2.1 为什么编码不用查表而要先写一个 Kron 递归极化码的编码器本质是一个二进制线性变换码字x u * G_N其中G_N是N x N的生成矩阵。Arikan 的原始构造从核矩阵F [1 0; 1 1]出发通过克罗内克幂得到G_N F^{⊗n}这里N 2^n。也就是说编码部分并不需要提前查 3GPP 的表格写一个递归的 Kronecker 积循环就够了。使用 MATLAB 的kron函数时递归方向要固定。常见做法是G kron(F, G)也就是每次把新的核矩阵放在左边。如果换成kron(G, F)得到的生成矩阵在比特排列上是等价的但信息位集合的索引会变化。所以我的建议是选定一种方向后在所有脚本里保持一致不要在编码和译码之间混用。2.2 用 kron 函数生成 N 阶生成矩阵下面这个函数生成N 2^n阶生成矩阵function G polar_gen_matrix(n) % n: 极化层数N 2^n % 返回 N x N 的二进制生成矩阵 F [1 0; 1 1]; G 1; for k 1:n G kron(F, G); % 按 F 在左、旧矩阵在右的方式递归 end end调用方式N 256; G polar_gen_matrix(log2(N));这段代码的核心是kron(F, G)。第一次循环得到F第二次得到F ⊗ F第三次得到F ⊗ F ⊗ F。由于克罗内克积满足结合律这个循环其实就是计算F^{⊗n}。对于N256G是256 x 256的 double 矩阵占内存约 0.5 MB直接做mod(u * G, 2)没问题但如果把N拉到 4096就要考虑用逻辑矩阵或分块运算否则中间乘法会很慢。2.3 BEC 巴氏参数用递归挑选信息位生成矩阵只解决了“怎么编码”接下来要解决“哪些位置放信息比特”。极化码把N个原始信道分成可靠和不可靠两组信息位应放在可靠度最高的K个位置上。可靠度可以用 BEC 假设下的巴氏参数Z来估计也可以在高斯信噪比下用高斯近似。这里先用 BEC 递推因为逻辑最直观、可复现性最强。% 以 BEC 擦除概率 eps0.5 为例计算 N256 各子信道可靠度 N 256; K 128; eps 0.5; Z eps; for n 1:log2(N) Znew zeros(2*numel(Z), 1); Znew(1:2:end) 2*Z - Z.^2; % 分裂出的“坏”信道 Znew(2:2:end) Z.^2; % 分裂出的“好”信道 Z Znew; end [~, idx] sort(Z); info_bits sort(idx(1:K)); % 选最可靠的 K 个位置递归公式很简洁上支路Z1 2Z - Z²下支路Z2 Z²。BEC 下Z越小信道越可靠所以排序后取前K个即可。这里info_bits是 1-based 索引和 MATLAB 的数组下标一致后面编码和译码都用它定位信息位。需要说明的是这个可靠度集合是在eps0.5下算出来的相当于一种“中等擦除”假设。实际 AWGN 信道下不同Eb/N0的最优冻结位集合并不完全相同。初级仿真可以固定一套做精细化仿真时建议用高斯近似在每个工作信噪比下重新生成info_bits。2.4 编码函数、参数表与自洽性检查有了生成矩阵和信息位集合编码函数可以写成function x polar_encode(u_all, G) % u_all: 长度 N 的 0/1 向量冻结位已经填 0 % G: N x N 生成矩阵 x mod(u_all * G, 2); end注意u_all是完整的N比特向量而不是只把K个信息位塞进去。信息位只占据info_bits位置其余位置为 0。这样设计的好处是编码和后面 SC 译码的索引完全一致。下表是初始仿真推荐的一组参数参数值说明N256码长必须是 2 的幂K128信息比特数码率 R0.5eps0.5BEC 可靠度估计用擦除概率Gpolar_gen_matrix(8)256x256 生成矩阵frozen_bits非 info_bits 位置冻结位固定填 0写完之后建议先做一次自洽检查随机生成K个信息比特填充成u_all编码后马上做一次无噪声 SC 译码看是否能恢复出u_all(info_bits)。如果这里都不对后面加噪声没有任何意义。2.5 三个容易踩的坑比特序、冻结位集合和固定可靠度第一个坑是比特反序。部分教材里的G_N B_N F^{⊗n}带了比特反序矩阵B_N而我们这里直接用kron(F, G)构造没有额外反序。这不是错误是等价表示下选择了不同的信息位集合。只要编码、可靠度递归、译码器三者使用同一套索引曲线就是对的。第二个坑是冻结位集合弄反。信息位选Z最小的K个冻结位是剩下的如果反了系统会直接崩掉。第三个坑是用一套固定可靠度跑所有信噪比。对入门仿真是可以的但如果你发现低 SNR 区域误码率平台下不来先检查是不是冻结位集合对当前 SNR 太不匹配。3. AWGN 信道下用 SC 译码跑通最小极化码系统仿真3.1 系统仿真链路与 LLR 的符号约定实际的极化码系统仿真可以拆成发射端、信道和接收端三部分。发射端先做可靠度排序把信息比特放到info_bits位置然后乘生成矩阵得到码字x调制采用 BPSK映射关系固定为0 - 1, 1 - -1信道模型用 AWGN接收端把接收符号转成对数似然比 LLR交给 SC 译码器恢复所有比特。LLR 的符号约定是整个链路的命门。接收信号y s n其中s是 BPSK 符号n ~ N(0, σ²)。对等概率二进制输入LLR 定义为log(P(b0|y)/P(b1|y))在 AWGN 下可以直接写为LLR 2y/σ²。LLR 为正代表译码器倾向 0为负倾向 1。这里的σ²不是随意设的它和码率R、Eb/N0的关系是σ² 1 / (2 * R * 10^(EbN0_dB/10))推导思路不复杂BPSK 每个码字符号能量归一化为 1每个信息比特等价到1/R个符号所以Eb/N0 1/(2Rσ²)。很多仿真结果对不上就是因为直接用σ² 1/(2*SNR)而漏掉了码率因子。3.2 min-sum SC 译码的递归实现SC 译码器不需要遍历整棵信道极化树它用两个核心运算递归完成f(a, b) sign(a)·sign(b)·min(|a|, |b|)对应左子节点g(a, b, u_left) b (-1)^u_left · a对应右子节点这里用的是 min-sum 近似比精确 LLR 计算少很多对数和指数运算性能损失在 0.1 dB 量级适合先把链路跑通。下面是一个可直接保存为sc_decode.m的递归实现function u_hat sc_decode(alpha, info_bits) % alpha: 接收端对数似然比长度 N % info_bits: 逻辑向量1 表示信息位0 表示冻结位 N numel(alpha); u_hat zeros(1, N); decode_node(1, N, alpha); function beta decode_node(start_pos, node_len, node_llr) if node_len 1 if info_bits(start_pos) beta node_llr 0; % LLR 为负判为 1 else beta false; % 冻结位固定为 0 end u_hat(start_pos) beta; return; end half node_len / 2; llr1 node_llr(1:half); llr2 node_llr(half1:end); % f 运算给左子节点 alpha_left sign(llr1) .* sign(llr2) .* min(abs(llr1), abs(llr2)); beta_left decode_node(start_pos, half, alpha_left); % g 运算给右子节点 s 1 - 2 * double(beta_left); alpha_right llr2 s .* llr1; beta_right decode_node(start_pos half, half, alpha_right); beta [beta_left, beta_right]; end end这段代码的逻辑是先把当前节点的N个 LLR 分成左右两半用f算出左子节点的输入递归译码得到左子节点所有比特估计beta_left再把这些估计代入g运算得到右子节点的输入继续递归。叶子节点是最终硬判决位置信息位按 LLR 符号做判决冻结位直接返回 0。注意嵌套函数decode_node可以直接读写外层u_hat和info_bits但 MATLAB 的嵌套函数在递归调用时会有额外开销。N256时单帧递归调用深度为 8速度还能接受如果跑到N2048以上建议改成迭代式 SC 或直接用coder生成 C 代码。3.3 可复现的蒙特卡洛仿真脚本下面的脚本把编码、AWGN 信道、SC 译码和误码统计串在一起。每个信噪比点先用少量帧验证稳定性遇到错误帧就累积统计BLER和BER。N 256; K 128; R K / N; G polar_gen_matrix(log2(N)); % 用 BEC 递归计算信息位 eps 0.5; Z eps; for n 1:log2(N) Znew zeros(2*numel(Z), 1); Znew(1:2:end) 2*Z - Z.^2; Znew(2:2:end) Z.^2; Z Znew; end [~, idx] sort(Z); info_bits sort(idx(1:K)); frozen_idx setdiff(1:N, info_bits); info_bits_flag false(1, N); info_bits_flag(info_bits) true; EbN0_dB 0:0.5:4; min_frame_errors 100; max_frames 2e5; for snr_idx 1:length(EbN0_dB) EbN0 10^(EbN0_dB(snr_idx)/10); sigma2 1 / (2 * R * EbN0); sigma sqrt(sigma2); frame_err 0; bit_err 0; frames 0; while frame_err min_frame_errors frames max_frames u randi([0 1], 1, K); u_all zeros(1, N); u_all(info_bits) u; x polar_encode(u_all, G); s 1 - 2 * x; % BPSK: 0 - 1, 1 - -1 noise sigma * randn(1, N); y s noise; llr 2 * y / sigma2; u_hat_all sc_decode(llr, info_bits_flag); u_hat u_hat_all(info_bits); if ~isequal(u_hat, u) frame_err frame_err 1; bit_err bit_err sum(u_hat ~ u); end frames frames 1; end BLER(snr_idx) frame_err / frames; BER(snr_idx) bit_err / (frames * K); end semilogy(EbN0_dB, BLER, o-, EbN0_dB, BER, s-); xlabel(Eb/N0 (dB)); ylabel(BLER / BER); grid on;脚本里最值得注意的变量是info_bits_flag。它在进入sc_decode前被转成逻辑向量1 表示这一位是信息位冻结位在u_all中本来就是 0SC 译码也会把冻结位强制判为 0。这样编码和译码共享同一套索引不会出现英文文献里常见的A^c集合混淆。3.4 仿真配置表先按这些值跑再改参数配置项建议值说明N256编码矩阵和 SC 树的规模刚好合适K128R0.5信息位占一半调制BPSK方便 LLR 公式验证译码算法min-sum SC先验证链路再换 SCL每 SNR 错误帧数100减少蒙特卡洛抖动max_frames2e5防止低 SNR 点死循环SNR 步长0.5 dB曲线足够平滑又不至于太慢如果电脑性能有限可以先把min_frame_errors改成 30 跑通全流程确认没有索引错误后再加帧数。直接跑 100 个错误帧在低 SNR 时很快但在高 SNR 区域可能非常慢用max_frames做保护是必要的。3.5 如何确认译码和编码顺序没有错位最简单的方法是把脚本里的noise设为全 0也就是做一个无噪声极限测试。这时 LLR 的绝对值很大符号完全由发送比特决定SC 译码应该能逐比特恢复u_all。如果无噪声测试都出现错误问题几乎一定出在生成矩阵递归方向、BEC 可靠度索引或 SC 译码的左右子节点顺序上。建议先调试这一关再加噪声。4. 码率匹配、CRC-SCL 与仿真参数的调优方向4.1 从 SC 到 SCL路径度量 PM 与列表剪枝SC 译码器在译码一个比特时只保留一条路径一旦判错后续比特可能跟着错。CRC 辅以 SCL 译码的思路是在海选阶段保留L条候选路径最后用 CRC 校验挑出真正合法的信息比特序列。这里的L也叫列表大小常用值有 2、4、8、16、32。每条路径需要一个路径度量 PM 来度量它和接收信号之间的距离。路径度量更新的核心思想很朴素如果当前候选比特和该位置的硬判决不一致就给这条路径加上一个惩罚一致则不增加惩罚。用近似表达式写就是% 在当前路径 pm 上根据当前比特的 LLR 分裂出两条新路径 for b 0:1 if b (llr 0) % 与硬判决一致 pm_new(b 1) pm; else pm_new(b 1) pm abs(llr); end end % 每处理完一个信息位按 pm_new 升序保留前 L 条这个近似在实际工程中足够稳定。更精确的形式是pm_new pm log(1 exp(-(1-2b)·llr))在|llr|较大时退化成上面的近似。要注意的是SCL 不只是简单地在每个叶子节点保留L个硬判决它需要维护每条路径的中间节点 LLR 和部分和。在上面的代码片段里llr只是当前叶子的软信息完整的 SCL 实现还会在f/g运算时按路径分别存储。4.2 CRC 附加的两种写法CRC 的主要作用是从 SCL 保留的L条路径中选出正确路径。如果所有保留路径都没通过 CRC则退而选择 PM 最小的一条。为了不让仿真依赖通信工具箱可以先写一个最简 CRC-8 计算函数function crc_bits crc8_bits(bits) % bits: 0/1 行向量返回 8 位 CRC 余数 % 生成多项式 0x07初始值为 0MSB-first reg uint8(0); poly uint8(7); for i 1:numel(bits) fb xor(logical(bits(i)), bitand(reg, uint8(128)) ~ 0); reg bitand(bitshift(reg, 1), uint8(255)); if fb reg bitxor(reg, poly); end end crc_bits zeros(1, 8); for i 1:8 crc_bits(i) double(bitand(bitshift(reg, -(8-i)), 1)); end end调用方式是在信息比特u_msg后面直接拼接 CRCu_crc [u_msg, crc8_bits(u_msg)]; K_total length(u_crc); % 注意此时 K 要包含 CRC 位这段 CRC 只用于 SCL 选路演示不是 3GPP 标准里的 24 位 CRC。做标准对比时请替换成目标系统规定的 CRC 多项式。如果你装了 Communications Toolbox也可以直接用comm.CRCGenerator但手写版本能让你更清楚 CRC 是在哪个环节影响码率。4.3 速率匹配打孔和缩短的取舍极化码天然要求码长是 2 的幂但实际系统需要的码长往往不是。速率匹配常见做法有打孔和缩短。打孔是删掉某些码字比特不发送缩短是固定某些码字比特为 0 再删掉。两者都会影响可靠度排序缩短通常保留更好的距离特性。N 256; % 编码器码长 target_len 192; % 实际发送的码长 shorten_mask true(1, N); shorten_mask(end - (N - target_len) 1 : end) false; x_tx x(shorten_mask); % 通过缩短得到目标码长这里的shorten_mask把后半段码字屏蔽掉属于一种比较粗糙但能跑通的缩短方式。工程上更常用的是按可靠度顺序构造的 QUP 算法但实现复杂度高很多。如果只是验证系统仿真流程先用尾部缩短或均匀打孔把误码率对比出来再细化。4.4 仿真参数表L、错误帧数、CRC 长度和 SNR 步长参数入门值调优方向L8先 4 后 16对比 BLER 下降幅度CRC 长度8选 16 或 24 可减少误检概率每 SNR 错误帧数100曲线平滑后再提到 300SNR 步长0.5 dB接近瀑布区可改 0.1 dB可靠度更新固定 eps0.5随 SNR 做高斯近似并行单核多 SNR 点用 parfor列表大小L从 4 增到 8 通常能带来明显增益但从 16 增到 32 时收益递减同时仿真时间几乎线性上涨。CRC 长度也不能盲目加长因为它会挤占有效信息位码率会下降。做曲线对比时需要固定有效信息位数不变而不是固定总编码长度不变。4.5 调优的先后顺序我一般会按照这样的顺序调先确认无噪声极限下的 SC 译码完全正确再在低 SNR 区域用少帧数跑通 SC 基线接着加 CRC-SCL观察相同 SNR 下 BLER 是否下降最后才做速率匹配和可靠度随 SNR 更新。如果在 SCL 跑完后 BLER 没有明显改善先检查 CRC 是否真的参与了编码。常见的错误是编码时把 CRC 位放在冻结位里译码时又把 CRC 位当信息位这样 SCL 最后无法选出正确路径。另一个常见问题是每条路径的 PM 没有随f/g运算同步更新导致剪枝剪掉了正确路径。5. 验证与收尾无噪极限检查、单帧时间预算和 BER 曲线坐标5.1 用无噪 LLR 做 one-shot 自检每次改码长或改变可靠度计算方式后都应该先跑一个极端的自检让接收端直接使用发射端已知的码字构造 LLR验证译码器能在无噪声条件下完全恢复信息位。% 假设 u_all 是填充好的编码器输入x 是编码输出 llr_clean 20 * (1 - 2*x); % 正数对应 0负数对应 1 u_clean sc_decode(llr_clean, info_bits_flag); assert(isequal(u_clean(info_bits), u), 无噪声自检失败);这里使用20代替无穷大 LLR既避免数值溢出又能保证硬判决符号绝对可靠。如果这个断言失败检查方向集中在生成矩阵方向、信息位索引和 SC 译码的左右子节点顺序上。5.2 单帧耗时与蒙特卡洛时间预算极化码仿真的最大风险不是代码错误而是跑了一晚上才发现帧数不够。建议在正式扫描前用 10 帧估算单帧耗时tic; for i 1:10 noise sigma * randn(1, N); y s noise; llr 2 * y / sigma2; u_hat_all sc_decode(llr, info_bits_flag); end t_frame toc / 10; fprintf(单帧耗时 %.3f s\n, t_frame);有了单帧耗时就能快速估算整个 SNR 扫描需要的总时间。比如 6 个 SNR 点、每点 200 个错误帧、高 SNR 区可能需要 3000 帧那么总帧数可能到两万帧。如果单帧 20 ms总时间就是 400 秒可以接受如果单帧 1 秒就要立刻降低帧数或改用并行。5.3 画曲线时的横纵坐标画 BER/BLER 曲线时横坐标用EbN0_dB纵坐标用semilogy别把码符号 SNR 当成信息比特 SNR。两者相差10*log10(R)也就是 3 dB 左右。如果你发现自己的极化码曲线和文献差一个固定偏移优先检查这个公式。帧数足够后把每个 SNR 点的错误帧数提到 300曲线会比 100 时平滑很多但耗时约三倍先用少量帧跑通全流程确认索引和 SNR 映射都对再放帧数这是极化码系统仿真最稳妥的推进方式。本文还有配套的精品资源点击获取