简介这份 MATLAB 代码包面向通信与信道编码学习者聚焦 LDPC 编解码仿真与性能分析尤其适合正在学习 LDPC 原理、开展课程设计或需要参考实现的本科生、研究生及工程师。资源共 7 个 m 文件压缩包仅 8KB体积小巧、结构清晰便于快速阅读与二次修改。代码内包含校验矩阵生成、和积SPA、最小和MS、归一化最小和NMS、偏移最小和OMS等多种译码器以及误码率测试脚本覆盖常见迭代译码算法通过误码率测试可观察不同信噪比下的性能差异也能直接对比各译码策略在性能与复杂度上的折中。已有 280 人学习能帮助读者快速搭建从矩阵构造到仿真验证的完整 LDPC 链路深入理解不同迭代译码算法的收敛特性与实现细节作为算法教学演示、课程设计参考或工程预研脚本均很合适。1. LDPC译码为什么绕不开NMS做接收机链路的人都有这个经验LDPC译码器的吞吐率直接决定整条链路能不能跑满而SPA在AWGN信道下虽然能逼近香农限但tanh域的双向变换对硬件极其不友好FPGA上每周期只能处理很少几个校验节点。把校验节点更新换成min运算之后性能损耗大概在0.3 dB量级但逻辑资源直接省一半以上这让NMS成了吞吐敏感场景下的默认选择。ldpc-decode这个MATLAB包把SPA、MS、NMS、OMS四种译码算法和基矩阵生成、BER仿真链路放在一起适合需要快速对比算法差异、或者打算把译码器落成硬件实现的工程师。你不需要自己造校验矩阵也不需要从零搭蒙特卡洛仿真直接跑ldpcBER就能拿到四条对比曲线再按项目指标挑算法或做定点化改造。2. 从基矩阵到译码器LDPC核心模块的MATLAB拆解2.1 makeLdpc.m基矩阵扩展出校验矩阵LDPC的校验矩阵 (H) 决定了码字的性能上限。这个包里用base41作为基矩阵41对应基矩阵的列数扩展因子 (Z) 决定最终码长这是典型QC-LDPC结构的做法。QC-LDPC的优势在于校验矩阵可以由循环移位子矩阵拼装生成逻辑简单译码时也容易做并行调度。makeLdpc.m做的工作就是把基矩阵中每个元素扩展成 (Z \times Z) 的循环置换矩阵或全零矩阵。% makeLdpc.m 的典型扩展逻辑QC-LDPC function H makeLdpc(base, Z) [mb, nb] size(base); % 基矩阵行数、列数 H zeros(mb*Z, nb*Z); % 预分配扩展后的稀疏矩阵 for i 1:mb for j 1:nb shift base(i, j); % 移位值-1 表示全零块 if shift 0 P circshift(eye(Z), -shift); H((i-1)*Z1:i*Z, (j-1)*Z1:j*Z) P; end end end end这段代码里shift来自基矩阵控制单位阵循环移位多少位。circshift(eye(Z), -shift)生成一个每行只有1个1的置换矩阵负号是为了和译码时变量节点、校验节点的索引习惯对齐。全零块用-1标记这在QC-LDPC里是标准做法目的是避免围长太短导致错误平层。扩展完成后(H) 的行数等于校验位长度列数等于码长行重和列重都较小保持了稀疏性。实际调试时我一般会打印sum(H, 2)检查行重是否均匀如果某些行重差异过大说明基矩阵设计有缺陷后续译码时会出现错误平层提前出现的问题。2.2 makeParityChk.m校验关系的验证工具生成校验矩阵之后第一步要确认它能用。makeParityChk.m做的就是这件事对任意合法码字 (c)验证 (H \cdot c^T \bmod 2 0) 是否成立。% makeParityChk.m校验 LDPC 码字合法性 function s makeParityChk(H, c) s mod(H * c(:), 2); % 校验子向量 if all(s 0) fprintf(校验通过H*c 0\n); else fprintf(校验失败存在 %d 个不满足的校验方程\n, sum(s)); end end这个函数的用途不只是仿真前的自检。在调试译码器时如果译码结果始终不对先用它判断问题是出在编码端还是译码端把发送的码字和译码输出分别做一次校验哪边不为零就是哪边的bug。另外误码率仿真中判错帧的标准也是它——不是比较比特是否一致而是先看校验子是否为零因为LDPC译码器的输出有时会落在别的合法码字上这时候比特全错但校验通过属于不可检测错误统计误帧时应该单列一类。2.3 信道LLR初始化和译码主循环译码开始前要把信道观测值转成对数似然比。BPSK调制下(0 \to 1)、(1 \to -1)AWGN噪声方差为 (\sigma^2)LLR的计算式是[ L(c) \ln \frac{P(c0|y)}{P(c1|y)} \frac{2y}{\sigma^2} ]% 信道LLR初始化BPSK AWGN function llr initLLR(y, sigma2) llr 2 * y / sigma2; % L(c) 2y / σ² end这个式子看着简单但有一个经常踩的坑如果发送端做了能量归一化即把 (E_b) 归一为1那么噪声方差要根据实际信噪比换算直接套一个固定值会让高信噪比区域的误码率曲线出现错误平层。正确做法是在每个SNR点计算 (\sigma^2 1 / (2 \cdot R \cdot 10^{SNR/10}))其中 (R) 是码率。译码主循环的骨架是标准的迭代信念传播% 译码主循环迭代直到校验通过或达到最大次数 for iter 1:maxIter % 校验节点更新具体算法见第3章 % 变量节点更新 % 计算后验LLR c_hat double(llr_post 0); % 硬判决 if all(mod(H * c_hat(:), 2) 0) % 提前终止条件 break; end end每轮迭代里先更新校验节点消息再更新变量节点消息然后算后验LLR做硬判决。提前终止的意义在低信噪比区间特别明显大部分帧在第2、3轮迭代就能通过校验只有少数坏帧吃满最大迭代次数平均迭代次数远小于上限这对吞吐率仿真很重要。3. SPA、MS、NMS、OMS四种译码算法的实现差异3.1 SPA校验节点更新tanh域的完整消息传递和积算法是LDPC译码的理论基准校验节点的更新公式是[ L(r_{ji}) 2 \cdot \tanh^{-1} \left( \prod_{i \in N(j) \setminus i} \tanh \left( \frac{L(q_{ij})}{2} \right) \right) ]这里的 (N(j) \setminus i) 表示与校验节点 (j) 相连、除去变量节点 (i) 之外的所有邻居。MATLAB实现时为了防止小数值下溢一般先取对数再计算% decode_SPA.m校验节点更新对数域 function r_ji updateCheckSPA(q_ij) % q_ij: 变量节点到校验节点的消息矩阵 alpha sign(q_ij); beta abs(q_ij); % 用 log(tanh(|x|/2)) 形式计算避免下溢 phi -log(tanh(beta / 2)); for j 1:size(q_ij, 1) for i 1:size(q_ij, 2) if q_ij(j, i) ~ 0 prod_phi sum(phi(j, :)) - phi(j, i); prod_alpha prod(alpha(j, :)) / alpha(j, i); r_ji(j, i) prod_alpha * 2 * atanh(exp(-prod_phi)); end end end end这个双层循环只是演示消息更新逻辑实际项目中没人用纯循环实现SPA因为太慢了。MATLAB里用for遍历非零元素是常态但至少要预先把 (H) 转成稀疏矩阵并只遍历非零位置否则规模稍大就卡死。phi -log(tanh(beta/2))这一步是把乘法变成加法利用 (\tanh) 的性质把概率域的乘积转换到对数域求和这是数值稳定的关键。变量节点更新相对简单就是把信道LLR和所有校验节点传回的消息加起来再减去回传方向的那条[ L(q_{ij}) L(c_i) \sum_{j \in M(i) \setminus j} L(r_{ji}) ]3.2 MS算法用min运算替代tanh的双向变换最小和算法的核心观察是(\tanh) 域的消息乘积主要受绝对值最小的那一项支配。因此校验节点更新可以近似为[ L(\hat{r}{ji}) \left( \prod{i \in N(j) \setminus i} \text{sign}(L(q_{ij})) \right) \cdot \min_{i \in N(j) \setminus i} |L(q_{ij})| ]这个近似把原本需要做指数、对数、双曲函数的运算降到了只做比较和符号运算。实现上比SPA简单一个数量级% decode_MS.m最小和校验节点更新 function r_ji updateCheckMS(q_ij) % 符号项所有邻居符号的乘积 alpha sign(q_ij); % 幅度项找最小绝对值 [min_val, min_idx] min(abs(q_ij), [], 2); for j 1:size(q_ij, 1) for i 1:size(q_ij, 2) if q_ij(j, i) ~ 0 prod_alpha prod(alpha(j, :)) / alpha(j, i); if i min_idx(j) % 最小项排除后取第二小值 min_other min(abs(q_ij(j, [1:i-1, i1:end]))); r_ji(j, i) prod_alpha * min_other; else r_ji(j, i) prod_alpha * min_val(j); end end end end end注意当排除的是最小项本身时需要退而取第二小的绝对值。很多简化实现忽略了这个细节直接把最小值乘上去会导致对应边的消息被高估解码性能下降约0.1到0.2 dB。我在实际仿真中发现这个“第二小值”的处理对性能影响比归一化因子还大。3.3 NMS和OMS从近似到参数化修正MS算法的0.3到0.5 dB性能损失来源是min近似系统性地高估了校验节点消息的幅度。NMS和OMS分别用乘法和减法做修正思路都很直接[ \text{NMS:} \quad L(\hat{r}{ji}) \alpha \cdot \min{i} |L(q_{ij})| \cdot \prod_{i} \text{sign}(L(q_{ij})) ][ \text{OMS:} \quad L(\hat{r}{ji}) \max\left( \min{i} |L(q_{ij})| - \beta, 0 \right) \cdot \prod_{i} \text{sign}(L(q_{ij})) ]实现代码% decode_NMS.m归一化最小和 function r_ji updateCheckNMS(q_ij, alpha) % alpha 是归一化因子典型值 0.75 ~ 0.85 r_ji alpha * updateCheckMS(q_ij); % 在MS基础上乘系数 end% decode_OMS.m偏移最小和 function r_ji updateCheckOMS(q_ij, beta) % beta 是偏移量典型值 0.5 ~ 1.0 r_ms updateCheckMS(q_ij); r_ji max(r_ms - beta, 0); % 截断到非负 endNMS里 (\alpha) 的作用是压缩min近似产生的高估消息让译码器收敛更稳。(\alpha) 选太大等于没修正几乎退回MS的性能(\alpha) 太小会让消息衰减过度迭代次数明显增加。OMS的 (\beta) 类似。两者推荐的初始值分别是 (\alpha0.8)、(\beta0.5)后续按具体码型的行重做细调第5章具体说调法。四种算法的运算量对比如下算法校验节点运算相对复杂度性能损耗相对SPASPAtanh / artanh1.0基准MSmin 符号0.15~0.20.3~0.5 dBNMSmin × α0.15~0.20.1~0.2 dBOMSmin − β0.15~0.20.15~0.3 dB从表格看NMS在复杂度和性能之间平衡得最好。OMS的优势在于定点实现时减法比乘法更省资源而且 (\beta) 的取值对性能不如 (\alpha) 敏感这在实际调参时是个隐藏好处。4. 误码率仿真链路从脚本到曲线4.1 ldpcBER.m主脚本结构误码率仿真的主脚本是把前面所有模块串起来的纽带。标准框架是外层循环SNR内层循环随机发送帧每个SNR点统计到足够多的错误比特或错误帧才停。下面是典型结构% ldpcBER.m 主仿真框架 clear; clc; addpath(ldpc-decode); Z 81; % 扩展因子 H makeLdpc(base41, Z); % 生成校验矩阵 [N, K] size(H); % N码长K信息位长度 snr_list [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]; maxIter 20; % 最大迭代次数 nFrames 200; % 每SNR点帧数 for snr_idx 1:length(snr_list) snr snr_list(snr_idx); sigma2 1 / (2 * (K/N) * 10^(snr/10)); % 噪声方差 ber 0; fer 0; for frame 1:nFrames % 1. 随机构造合法码字全零码字编码或直接搜索零空间 % 2. BPSK调制 % 3. 加高斯白噪声 % 4. 译码decode_NMS / decode_MS / decode_OMS / decode_SPA % 5. 错帧和错比特统计 end end这里的 (K/N) 是码率(N) 是码长。全零码字是LDPC仿真中一个经常被问到的问题LDPC码是线性码全零码字是合法码字BPSK调制后对应全1序列由于AWGN信道是对称的误码率表现和随机码字完全一致。所以很多仿真直接发全零码字省去编码环节只测译码器。严谨的做法是用全零码字做性能下界再补几个随机码字点验证没有码字依赖。4.2 关键参数配置与运行环境实际跑仿真之前先确认这几个参数参数推荐值说明扩展因子 Z27~162由目标码长除以基矩阵列数决定Z越大码越长最大迭代次数10~30超过后强制输出当前硬判决SNIR扫描范围瀑布区±1 dB先用粗粒度找到瀑布区再加密每点帧数100~500误码率目标低于1e-4时需要更多帧归一化因子 α0.75初值按算法和H行重微调运行方式是在MATLAB命令行里切到项目目录直接执行脚本cd(ldpc-decode) ldpcBER如果发现低信噪比区间跑得极慢优先检查是不是没有做提前终止——最大迭代次数设成20但正常在第2、3轮就通过了只有坏帧才吃满次数。可以在主循环里加计数器统计平均迭代次数一般是5次以内。4.3 四种算法的一次性对比实验要做公平对比需要在完全相同的信道条件和迭代次数下切换译码函数。在仿真脚本里加一层算法选择% 在帧循环内切换译码算法 switch algo case SPA c_hat decode_SPA(y, H, maxIter); case MS c_hat decode_MS(y, H, maxIter); case NMS c_hat decode_NMS(y, H, maxIter, alpha); case OMS c_hat decode_OMS(y, H, maxIter, beta); end一次跑出四条误码率曲线后通常会看到这样的规律SPA在瀑布区领先NMS约0.1 dBMS掉得最多NMS和OMS在低信噪比区间几乎重叠但NMS在误码率低于1e-4后下降斜率更陡——因为OMS的 (\beta) 修正对小幅值消息的截断更激进在高信噪比区域反而成了限制因素。如果NMS的坡度不够陡问题多半出在 (\alpha) 偏大让校验节点消息高估导致的错误平层提前。5. 归一化因子的调优策略与硬件部署要点NMS的 (\alpha) 不是拍脑袋定的值得花时间用蒙特卡洛仿真去微调。做法是在目标SNR点通常是误码率1e-3那个点固定信道条件把 (\alpha) 从0.6以0.02步进扫到0.95然后对比误帧率。特别注意不要只看平均大概率要看最差帧的表现——有些 (\alpha) 值平均误码率好看但长尾效应严重在低密度奇偶校验码里这会表现为小概率的提前错误收敛。工程部署时(\alpha) 的乘法在硬件上不是直接用浮点乘而是定点化把 (\alpha0.78125) 近似为 (25/32 0.78125)乘法变成一次左移5位和一次加法代价可以忽略。如果选 (\alpha0.75)甚至可以写成 (3/4)用移位和减法实现连乘法器都省了。定点化后一定要在MATLAB里把浮点译码结果和定点结果逐轮对比LLR消息的差值差太多的位置往往是饱和截断造成的需要在变量节点更新阶段做钳位。最后可以用分层译码替换泛洪调度把收敛迭代次数从20压到8到10轮。从调试角度看这五个文件的顺序也很重要先跑通makeParityChk做校验验证把H矩阵确认无误再逐个验证译码函数。每次只改动一个模块性能曲线出现异常时才能快速定位是消息更新还是参数设置的问题。本文还有配套的精品资源点击获取
