Matlab LDPC编码译码仿真:校验矩阵、LLR与迭代译码实战

发布时间:2026/9/1 17:43:31
Matlab LDPC编码译码仿真:校验矩阵、LLR与迭代译码实战 简介本资源是一套面向本科及硕士阶段通信工程、信息与信号处理方向学习者的LDPC码原理验证与仿真实践材料聚焦信道编码核心环节解决低密度奇偶校验码的编码构造、迭代译码实现与校验过程可视化等关键教学难点。压缩包共含若干Matlab源文件.m为主涵盖校验矩阵生成、比特翻转/置信度传播解码算法、误码率仿真及BER曲线绘制等功能模块全部代码基于MATLAB 2019a编写并完成调试可直接运行观察编码增益与收敛特性。资源大小为2.3MB结构紧凑、注释清晰适合作为课程设计、毕业设计或科研入门的参考范例。目前已有162人下载学习读者可获得完整可执行的LDPC端到端仿真流程、典型参数配置方案及常见问题调试提示显著降低从理论公式到代码实现的理解门槛。 LDPCLow-Density Parity-Check低密度奇偶校验码在通信系统里已经不算“新东西”了但从课程实验到实际项目大家还是会反复用到这套编码——DVB-S2、5G NR数据信道、Wi-Fi 6甚至部分有线传输标准背后都有LDPC的身影。用Matlab做LDPC编码、解码和校验仿真最大的价值不是把工具箱里的函数跑一遍而是把“校验矩阵怎么设计、LLR怎么算、迭代译码怎么收敛、校验方程怎么验证”这条链路亲手走通之后再看协议文档或者做工程落地都会轻松很多。这篇文章适合正在做通信课程设计、准备保研项目、或者刚接触信道编码的工程师。我会把我在Matlab里做LDPC仿真的完整思路、可运行代码和踩过的坑写出来。你不需要先精通抽象代数只要知道矩阵乘法、mod 2运算和一点概率论基础就能跟着复现。1. LDPC编码原理与Matlab中最容易忽略的底层概念1.1 稀疏校验矩阵到底在“校验”什么LDPC的“PC”指的就是Parity-Check奇偶校验。但和传统汉明码那种单个校验位不同LDPC用的是一个规模很大的校验矩阵H而且这个矩阵必须是稀疏的——也就是每行每列里的“1”个数远小于矩阵维度。码字c要满足的核心约束只有一个H * c^T 0 (所有运算都在GF(2)域也就是模2)假设有一个非常小的H矩阵H [1 1 0 1 1 0 0 0; 0 1 1 0 0 1 1 0; 1 0 1 0 1 0 1 1];那么每一行对应的就是一条校验方程。比如第一行要求c1 c2 c4 c5 0 (mod 2)。也就是说码字里这4个比特的“1”的个数必须是偶数。H矩阵的行数就是校验方程数量列数就是码字长度N。信息比特数量K N - 行数码率R K / N。我当时第一次看这个定义时觉得太抽象后来把H矩阵画成Tanner图才真正理解列是变量节点variable node行是校验节点check node矩阵里每个“1”就是一条连接边。译码的过程就像在这张图上来回传递消息每次校验节点都在做“我这边看到的比特是不是满足偶数个1”的判断。低密度意味着每个节点只连接少数其他节点消息之间的相关性弱迭代才有意义——这是LDPC能逼近香农限的根本原因。1.2 生成矩阵G与LDPC编码的工程权衡既然码字必须满足Hc^T 0那编码就是把信息比特映射成满足这个条件的码字。教科书里通常的做法是把H通过高斯消元化成系统形式[P | I]然后得到生成矩阵G [I | P^T]编码就是简单的c u * G。但实际工程上很少有人直接这么干原因有两个高斯消元会破坏H的稀疏性生成的G往往很稠密矩阵乘法复杂度变成O(N^2)码长几千几万比特时根本算不动。消元过程遇到非满秩矩阵还会很麻烦。所以实际系统里用的几乎都是准循环LDPCQC-LDPCH矩阵由循环移位子矩阵构成编码可以用移位寄存器线性复杂度完成。Matlab的ldpcEncoderConfig内部就处理了这套逻辑你只需要把H交给它不需要手动去求G。这里有一个很多资料没解释清楚的点Matlab 里ldpcEncoderConfig接受的H不要求你预先做系统化底层会自己处理。但H的行数必须等于N - K而且行秩要足够否则编码器可能给出错误结果。所以拿到一个H矩阵的第一件事是检查它的尺寸和秩。2. LDPC编码的Matlab实操步骤2.1 怎么拿到一个靠谱的校验矩阵H自建LDPC仿真第一个卡住人的地方就是H矩阵从哪里来我之前见过有同学用随机伯努利分布生成一个0/1矩阵结果编出来的码性能差得离谱原因在于随机矩阵的“小环”太多迭代译码完全不收敛。可用的渠道有三个第一种用标准协议矩阵。Matlab的dvbs2ldpc函数可以直接生成DVB-S2标准里的校验矩阵这个矩阵是经过精心设计的性能有保障。% 生成DVB-S2标准中码率2/3的校验矩阵码长64800 H dvbs2ldpc(2/3); K size(H, 2) - size(H, 1);dvbs2ldpc接受的参数是码率返回的是sparse矩阵。这个矩阵尺寸很大但因为是稀疏矩阵Matlab里存储和运算都还好。第二种用Matlab的准循环矩阵生成函数。R2021b及以上版本提供了ldpcQuasiCyclicMatrix可以自己定义块大小和循环移位值生成自定义尺寸的QC-LDPC矩阵。这种方式最灵活适合做算法对比实验。% blocksize是循环块大小rows中每行代表校验矩阵的一个块行 % 其中-1代表全零子块非负整数代表循环移位值 blocksize 27; rows [16 17 22 24 9 3 14 -1 4 2 7 -1 26 -1 21 -1 1 0 -1 -1 -1 -1 -1 -1]; H ldpcQuasiCyclicMatrix(blocksize, rows);第三种自己构造。比如手动设计一个规则LDPC矩阵每列固定有d_v个1每行固定有d_c个1然后用随机置换打乱位置。这种方式适合学习研究和验证译码算法但需要检查有没有四环即两个变量节点同时连接到两个校验节点这会直接导致迭代译码性能崩塌。对新手我不推荐一上来就自己构造。2.2 用ldpcEncoderConfig配置编码器拿到H之后编码就变得非常直观encCfg ldpcEncoderConfig(H); infoBits randi([0 1], K, 1); % 信息比特长度必须是K codeword ldpcEncode(infoBits, encCfg);这里有几个细节必须注意infoBits必须是列向量长度必须等于encCfg.NumInformationBits不是你想发多长就发多长。编码输出的长度是N即H的列数。旧版本Matlab用comm.LDPCEncoder对象新版本更推荐ldpcEncoderConfig加ldpcEncode函数的方式代码更清晰也更容易和后面的译码配置形成对称。实测下来配置对象一旦建好可以重复使用编码一个长度43200的信息块在普通PC上大概只需要几毫秒到几十毫秒完全可接受。2.3 我建议你把H矩阵存成.mat文件这是一个很实用的习惯。像DVB-S2那种64800码长的H矩阵虽然生成只花几秒钟但如果每次仿真脚本重新跑都要生成一遍累积起来很浪费时间。我第一次跑批量仿真时没注意这一点每个SNR点都重新生成矩阵白白等了很久。save(H_dvbs2_r23.mat, H, K);下次直接load脚本启动快很多。如果你的仿真脚本要反复调试这一步非常值得。3. LDPC解码算法与Matlab解码器配置3.1 硬判决和软判决差的不只是一点点LDPC译码的核心是迭代译码。最简单的思路是把接收信号硬判决成0/1然后反复用校验方程纠错。这种硬判决迭代性能很差因为判决过程中丢掉了可靠性信息。真正实用的译码算法都是软判决的也就是每个比特不再只提供一个0/1的值而是给出一个对数似然比LLR表示这个比特是0还是1的置信程度。LLR的定义是LLR ln( P(c0|y) / P(c1|y) )LLR为正说明比特更可能是0为负说明更可能是1绝对值越大置信度越高。对于BPSK调制在AWGN信道下LLR有一个非常简洁的闭式解映射: 0 - 1, 1 - -1 LLR 2 * r / sigma^2其中r是接收符号sigma^2是噪声方差。这里有个很常见的坑噪声方差算错整个LLR的尺度就错了译码性能会大打折扣。我在仿真时先确认过无编码情况下BPSK的理论BER曲线和仿真曲线对得上再叠加LDPC编码这样能确保噪声模型没有错。3.2 BP译码与Min-Sum译码的关系LLR有了之后就进入迭代译码阶段。最经典的算法是置信传播Belief PropagationBP也叫和积算法Sum-Product Algorithm。它在Tanner图上来回传递两类消息变量节点到校验节点除了目标校验节点外其他所有校验节点返回的消息加上信道LLR求和。校验节点到变量节点根据“校验和必须为0”的约束用tanh域乘积更新等价于在所有相邻变量节点中做“软校验”。BP的校验节点更新公式在数学上很漂亮但要算双曲正切和反双曲正切工程实现里计算量大。因此出现了Min-Sum近似把tanh乘积近似为取最小绝对值符号则由参与校验的比特的符号乘积决定。它的好处是计算简单实现代价低代价是性能会比完整BP差大约0.1到0.3dB具体取决于码长和度数分布。在Matlab里你不需要自己手动实现这套公式ldpcDecoderConfig已经封装好了decCfg ldpcDecoderConfig(H); decodedBits ldpcDecode(llr, decCfg, maxIter);第三个参数maxIter是最大迭代次数。Matlab默认配置可用的算法包括BP和Min-Sum等变体具体用哪种可以通过配置对象设置。我一般先用默认配置跑通流程再对比不同配置的收敛速度和误码率。3.3 调试阶段强烈建议先用小矩阵第一次跑通LDPC译码我建议你不要直接用64800码长的DVB-S2矩阵而是构造一个几十行、几百列的H矩阵配合BPSK调制走一遍完整链路。这样有几个好处编解码速度极快迭代过程可以打印出来逐帧看。可以手动把某个接收符号改坏直观观察LLR变化和译码纠错过程。H矩阵可以打印成普通矩阵检查校验方程方便理解。等小链路完全通了再切换到标准大矩阵跑性能曲线。否则一开始就上大码长出了问题根本不知道是编码、调制、LLR还是译码哪个环节的锅。4. 校验机制、BER评估与完整仿真链路4.1 译码后如何用校验方程验证结果LDPC的“校验”有两个层面的含义。一个层面的校验体现在译码迭代过程每次迭代结束译码器都会检查当前估计的码字是否满足H * c_hat^T 0如果满足就提前终止迭代这就是“早停”。另一个层面是我们自己仿真时做的syndrome check——译码算法返回了结果但结果不一定真的满足校验尤其是在错误平层区域。Matlab里校验一个码字是否合法写起来非常简单syndrome mod(double(H) * double(decodedBits), 2); syndromeOk ~any(syndrome);注意double(H) * double(decodedBits)是普通矩阵乘法但结果需要mod 2。如果syndromeOk为true说明译码结果是一个合法码字但不一定等于发送码字。因为LDPC码的码字集合有多个合法码字如果错误足够大译码器可能收敛到另一个合法码字上这时候syndrome校验通过了但信息比特依然是错的。所以syndrome check只能用来判断“是否合法”不能判断“是否正确”。真正统计误码必须把译码后的信息位和原始信息位逐比特对比。4.2 完整仿真主循环从比特生成到BER统计下面这段代码我实测过能用结构也是比较标准的通信仿真框架。它包含了编码、BPSK调制、AWGN信道、LLR计算、LDPC译码、校验和误码统计clear; close all; clc; rng(2024); % ---- 校验矩阵与编解码配置 ---- H dvbs2ldpc(2/3); % DVB-S2 码率2/3码长64800 K size(H, 2) - size(H, 1); encCfg ldpcEncoderConfig(H); decCfg ldpcDecoderConfig(H, Algorithm, min-sum); % 如果只需要功能验证建议先换小矩阵 % blocksize 27; % rows [16 17 22 24 9 3 14 -1 4 2 7 -1 26 -1 21 -1 1 0 -1 -1 -1 -1 -1 -1; % 25 12 12 3 3 26 6 21 -1 24 0 23 14 -1 13 -1 -1 1 0 -1 -1 -1 -1 -1]; % H ldpcQuasiCyclicMatrix(blocksize, rows); % K size(H, 2) - size(H, 1); % encCfg ldpcEncoderConfig(H); % decCfg ldpcDecoderConfig(H, Algorithm, min-sum); % ---- 仿真参数 ---- snrVec 1.0:0.4:3.4; % Eb/N0, 单位dB numFramesPerSnr 20; % 每个SNR点最多跑多少帧 maxBitErrors 200; % 每个SNR点累积误码数 berSim zeros(size(snrVec)); for sIdx 1:length(snrVec) EbN0Lin 10^(snrVec(sIdx) / 10); % 线性值 codeRate K / size(H, 2); noiseVar 1 / (2 * codeRate * EbN0Lin); % BPSK实信道噪声方差 bitErrors 0; totalBits 0; frameIdx 1; while bitErrors maxBitErrors frameIdx numFramesPerSnr infoBits randi([0 1], K, 1); codeword ldpcEncode(infoBits, encCfg); txSig 1 - 2 * double(codeword); % 0 - 1, 1 - -1 rxSig txSig sqrt(noiseVar) * randn(size(txSig)); llr 2 * rxSig / noiseVar; % AWGN信道下的LLR decodedBits ldpcDecode(llr, decCfg, 30); % 校验方程检查 syndrome mod(double(H) * double(decodedBits), 2); if any(syndrome) % 有校验不满足的帧可以在这里统计校验失败率 end errs sum(decodedBits ~ infoBits); bitErrors bitErrors errs; totalBits totalBits K; frameIdx frameIdx 1; end berSim(sIdx) bitErrors / totalBits; fprintf(Eb/N0 %.2f dB, BER %.4e\n, snrVec(sIdx), berSim(sIdx)); end % ---- 画图 ---- figure; semilogy(snrVec, berSim, bo-, LineWidth, 1.5); hold on; berUncoded berawgn(snrVec, psk, 2, nondiff); semilogy(snrVec, berUncoded, r*-, LineWidth, 1.5); grid on; xlabel(Eb/N0 (dB)); ylabel(BER); legend(LDPC (R2/3), Uncoded BPSK);跑这个脚本的时候你的机器需要配有Communications Toolbox。如果是老版本Matlab把ldpcEncode/ldpcDecode换成comm.LDPCEncoder和comm.LDPCDecoder即可逻辑一样。4.3 结果怎么解读瀑布区、错误平层和编码增益跑完这个仿真你会看到两条曲线无编码BPSK是一条比较“平”的下滑曲线而LDPC曲线会在某个SNR点附近突然大幅下降这一段叫“瀑布区”。瀑布区是LDPC典型特征——迭代译码在信噪比越过某个阈值后纠错能力迅速显现。如果继续往更高SNR推曲线会逐渐平坦呈现“地板效应”也就是错误平层error floor。它主要来自两个因素一是H矩阵的环结构导致局部消息循环加强二是少数低重量码字始终存在。DVB-S2矩阵设计时考虑了错误平层问题所以在常用工作区间内表现稳定。编码增益方面码率2/3、码长64800的DVB-S2 LDPC在BER约为10^-5时对比无编码BPSK大概有6-7dB的增益距香农限大约0.6-1dB。实际值跟译码算法、迭代次数都有关系不必死记数字重要的是理解这个增益从哪里来冗余校验比特迭代软信息处理。5. 常见问题排查与性能优化技巧5.1 误码率不降反升怎么办这是我见过最多的问题包括我自己第一次也栽在这上面。现象是加LDPC之后BER反而比无编码还差或者曲线完全乱跑。排查顺序如下现象可能原因解决方法BER接近0.5LLR符号取反了检查BPSK映射0对应1LLR用2r/σ²r正对应0负对应1BER比无编码还差噪声方差算错用无编码BPSK仿真校准确保噪声方差公式正确后再加LDPC译码报维度错误信息比特长度不等于K打印encCfg.NumInformationBits核对曲线在低SNR“翘起来”迭代次数太少调大ldpcDecode的最大迭代次数比如50或100校验总是失败H矩阵存在短环或非满秩换标准矩阵或使用ldpcQuasiCyclicMatrix生成这里最值得说的是噪声方差。很多教程直接用y awgn(s, snr, measured)这种做法在纯演示时问题不大但做LDPC仿真时你拿不到准确的噪声功率LLR就没有正确尺度译码性能会莫名其妙地差。我的习惯是手动生成噪声并显式计算noiseVar这样LLR和SNR完全自洽。5.2 仿真速度太慢的优化思路直接跑64800码长的LDPC矩阵每个SNR点重复几百帧会发现脚本慢到怀疑人生。优化思路从高到低排列先用小矩阵验证功能再用大矩阵出最终曲线。功能验证阶段可以构造几百比特的QC-LDPC跑得飞快。配置对象提到循环外面。ldpcEncoderConfig和ldpcDecoderConfig只需要创建一次不要放在SNR循环或帧循环内部。合理设置循环终止条件。我习惯同时设置maxBitErrors和maxNumFrames两个上限避免高SNR区域长时间撞不上误码而空转。并行加速。如果机器有多核用parfor把不同SNR点的循环拆开。注意parfor里要确保随机数流独立用parfor前给每轮循环设置不同的随机种子或使用RandStream。降迭代次数。如果只是粗略看趋势30次迭代和50次迭代差别不算大但速度差很多。5.3 迭代收敛判断和“过迭代”问题LDPC迭代译码不是迭代次数越多越好。虽然Matlab默认会做早停但在某些高SNR或特定码字模式下超过一定迭代次数后误码率可能不再下降甚至轻微上升。这是因为迭代消息中未消除的相关性慢慢累积导致振荡。实操中我建议在仿真里记录以下信息[decodedBits, itersUsed] ldpcDecode(llr, decCfg, 50);itersUsed可以告诉你每一帧实际用了多少次迭代。如果大量帧的迭代次数都顶到上限说明当前SNR点还处于瀑布区边缘可以适当调大上限如果普遍只用了几次迭代就收敛了说明已经进入稳定区。5.4 独家经验把“LLR绝对值均值”画出来这是我很喜欢用的调试手段。译码质量好不好不一定非得看BER因为BER在低SNR时波动大看不出细节。我习惯在每个SNR点统计一下接收LLR绝对值的均值以及译码前后LLR均值的变化量。在瀑布区译码前LLR很小译码后LLR明显拉开在错误平层译码前后差距可能很小。这个指标比BER更早暴露出LLR尺度、噪声方差等问题做算法调试时特别有用。最后再分享一点个人体会做LDPC仿真最大的门槛不是Matlab语法而是把数学表达式和代码里的每个变量一一对应起来。H矩阵行数、列数、秩、信息位长度这些数字很容易搞混但搞混一次就会浪费大量排查时间。我的办法是先不急着写大循环而是把一个小H矩阵打印出来手算一遍校验方程再跟Matlab结果对比。另一个建议是不要只跑出一条BER曲线就结束。试着改码率、改迭代次数、改矩阵大小看曲线怎么移动。这样才能真正理解LDPC的“规律”而不是只会调函数。后续如果想扩展可以尝试高阶调制、衰落信道或者自己实现一遍Min-Sum译码器你会发现整个系统的理解又上一个台阶。本文还有配套的精品资源点击获取