MATLAB手写LDPC:从随机H矩阵到LLR-BP译码与BER仿真
2026/9/14 16:13:15 网站建设 项目流程

简介:面向LDPC编码与译码学习的MATLAB实现包,完整演示随机生成校验矩阵H、LLR-BP(对数域置信传播)译码流程,并附带三种译码方法的对比代码,适合通信方向学生、科研人员快速理解LDPC原理并复现仿真。全包共26个文件,包含7个m主程序与函数、9个txt说明文档、5个fig误码率曲线图、4个asv备份及1个md使用说明,压缩包仅33KB,内容紧凑。所有代码均附详细中文注解,从H矩阵构造到LLR-BP译码迭代步骤逐行解释,并额外提供概率域译码、比特翻转译码供对比学习;md使用说明文档清晰列出了运行环境(MATLAB 2020b)、操作步骤与常见问题,便于零基础用户直接替换数据后运行。资源同时附带BER误码率曲线、EbN0与SNR转换说明、biterr用法等辅助资料,帮助深入理解性能评估方法。目前已有160人学习下载,适合需要快速上手LDPC仿真、撰写课程设计或开展通信算法研究的人群。

1. 为什么自己写LDPC而不是直接调工具箱

通信仿真做到 LDPC 的时候,很多人第一反应是comm.LDPCEncoderldpcDecode,但实际工程里往往发现工具箱里的码型固定、H 矩阵不可控,想研究参数扰动、短环影响、迭代次数与误码率关系时非常别扭。这套资源给出的是一条更底层的路径:自己随机生成 H 矩阵,自己实现 LLR-BP 译码,全程在 MATLAB 里手工搭链路,每个矩阵、每条消息更新都能打印出来看。对做课程设计、算法复现或者准备通信面试的人,这套代码比黑盒工具箱有价值得多。源码压缩包里包含makeLdpc.mdecodeLogDomain.mdecodeProbDomain.mdecodeBitFlip.mldpcBER.m主脚本,并且带详细中文注解和一份使用说明 Markdown,适合从零开始理解 LDPC 的编码与迭代译码全过程。

2. 随机H矩阵生成:从Gallager构造到makeLdpc.m的参数取舍

2.1 LDPC校验矩阵的结构约束与设计目标

LDPC 全称低密度奇偶校验码,核心在“低密度”三个字,它要求校验矩阵 H 中 1 的个数远少于 0。假设 H 是M × N的矩阵,行重为dc,列重为dv,那么 1 的密度是dc/N,当 N 增大时密度会越来越低。这套代码里的makeLdpc.m采用的就是经典 Gallager 构造法:先生成一个分块结构,再通过列置换随机化。行重、列重直接决定了码率和译码性能,一般取dc=2*dv或者按码率R=1-dv/dc反推。

设计 H 矩阵最麻烦的不是填 1,而是避免短环。所谓 4 环,就是 H 矩阵中存在 2×2 的全 1 子矩阵,导致消息在迭代时互相加强错误信息,LLR-BP 算法会很快收敛到错误码字。makeLdpc.m在生成随机置换时会做环长检测,但需要注意它检测的是局部 4 环,不是全局围长。实际使用时我一般会额外跑一遍围长验证,特别是当 M 和 N 都小于 500 时,随机置换很容易产生短环。

2.2 makeLdpc.m 的生成逻辑与 MATLAB 实现

makeLdpc.m的函数签名一般是[H, Hp, Hd] = makeLdpc(M, N, dc, dv),但实际这套代码里我看到的是从已有结构直接构建。它的核心思路分三步:先把 H 初始化为全零矩阵,然后按列重 dv 在每列随机放置 1,同时保持每行重为 dc,最后调整子矩阵结构用于编码。下面这段是兼容这套代码风格的生成片段,可以直接放进 MATLAB 跑:

function H = makeLdpc(M, N, dv, dc) % M: 校验方程数 N: 码长 dv: 列重 dc: 行重 % 校验:M*dv 必须等于 N*dc assert(M*dv == N*dc, 'M*dv和N*dc必须相等'); H = zeros(M, N); colWeight = zeros(1, N); % 记录每列已放1的个数 for col = 1:N % 在行重未满的行中随机选dv个位置 candidates = find(sum(H, 2) < dc); % 这里注意:如果剩余行放不下,就放宽行重约束 if length(candidates) < dv candidates = find(sum(H, 2) <= dc); end idx = randperm(length(candidates)); idx = idx(1:dv); H(candidates(idx), col) = 1; end % 打完收工,检查一下行列重 fprintf('Row weight min=%d max=%d\n', min(sum(H,2)), max(sum(H,2))); fprintf('Col weight min=%d max=%d\n', min(sum(H,1)), max(sum(H,1))); end

这段代码的关键约束在于assert那两行。如果 M 和 N 选的不好,比如M=100, N=200, dv=3,那么M*dv=300N*dc在 dc 取整数时很难恰好相等。此时需要调整 dc 或者允许最后几行行重不严格相等。工程上更常见的做法是固定 N 和列重,然后让 M 取某个能整除的值,码率R = 1 - M/N。随机选位置时如果用randperm不加约束,会导致某些行特别重、某些行特别轻,所以我在上面加了find(sum(H,2) < dc)的限制。

makeParityChk.m在这个流程里承担的是校验矩阵的标准化。它会把 H 拆成信息位部分 Hd 和校验位部分 Hp,目标是让 Hp 可逆。因为后续编码需要利用H = [Hd Hp]这个分块,通过高斯消元把 Hp 化为单位阵或下三角阵,才能用后向代入算校验位。如果 Hp 奇异,函数会重新排列列顺序,这也是随机 H 矩阵做系统编码时的常规操作。

2.3 避免短环的实用策略

纯随机的 Gallager 构造在小码长下几乎必然出现 4 环,一套能用的代码里必须解决这件事。常见做法有两个:一是生成后检测并重新生成,二是用 PEG(渐进边增长)算法直接按围长最大化来布边。makeLdpc.m属于前者,它的好处是快,坏处是运气不好时反复生成 H 矩阵会拖慢仿真。

我在实际调试ldpcBER.m时发现,当M=100, N=200这个量级,随机生成 100 次 H 矩阵,大约有 15 次会出现 4 环导致译码性能断崖式下跌。如果你的目标只是跑通 BER 曲线,可以多跑几次取最好结果;如果要做公正的算法比较,建议在makeLdpc之后加一个环检测函数:

function cycle4 = checkCycle4(H) % 检测是否存在4环 G = H * H'; % 校验矩阵的行内积矩阵 % 如果某两个行在相同两个列上有1,则G对应元素>=2 cycle4 = any(G(:) > 1); if cycle4 [i,j] = find(G > 1); fprintf('发现4环: 行%d和行%d\n', i(1), j(1)); end end

注意这个检测方法只对 4 环有效,6 环、8 环需要用消息传递统计围长,工具链里可以用 MATLAB 的comm.LDPCEncoder配套的isldpc脚本,或者自己实现 BFS 遍历 H 矩阵的 Tanner 图。对大多数课程设计而言,消除 4 环已经能让 LLR-BP 算法的性能接近理论曲线。

3. 三种译码器对比:LLR-BP的核心推导与MATLAB实现

3.1 对数域BP的迭代公式与符号约定

这套资源里提供了三个译码函数:decodeLogDomain.mdecodeProbDomain.mdecodeBitFlip.m,分别对应对数域置信传播、概率域置信传播和比特翻转。其中对数域 LLR-BP 是性能最好的一个,也是使用说明文档里推荐的主方案。LLR 的定义是L(c) = log( P(c=0|r) / P(c=1|r) ),正数表示更倾向于 0。

迭代过程有三步:初始化变量节点消息为信道 LLR,然后校验节点更新(CNU),再变量节点更新(VNU),最后做硬判决。校验节点更新公式用 tanh 形式写是:

L(r_ji) = 2 * atanh( prod_{k≠i} tanh( L(q_kj)/2 ) )

变量节点更新则是:

L(q_ij) = L_channel_i + sum_{j'≠j} L(r_j'i)

这两个公式必须配套使用,符号取反或漏掉2*atanh的系数都会让译码不收敛。decodeLogDomain.m里的实现就是按照这个公式展开的,好在它有详细注解,可以看出作者对消息传递顺序做了精心安排。

3.2 decodeLogDomain.m 的代码结构与数值稳定

下面给出一个与decodeLogDomain.m行为一致的对数域译码函数骨架,包含最大迭代次数和早停判断:

function [vhat, iter] = decodeLogDomain(H, rxLLR, maxIter) % H: 校验矩阵 rxLLR: 信道软信息 maxIter: 最大迭代次数 [M, N] = size(H); % 初始化变量节点消息为信道信息 V = repmat(rxLLR, M, 1); % MxN矩阵,存储每个变量节点发给校验节点的消息 % 迭代 for iter = 1:maxIter % 校验节点更新:处理H矩阵中每个1的位置 C = zeros(M, N); for m = 1:M cols = find(H(m, :)); % 第m行中参与校验的变量节点 if length(cols) < 2 continue; end % 计算tanh积 prod_tanh = 1; for idx = 1:length(cols) prod_tanh = prod_tanh * tanh(V(m, cols(idx))/2); end for idx = 1:length(cols) % 排除当前节点,重新算乘积 this_prod = prod_tanh / tanh(V(m, cols(idx))/2); % 防止tanh为0或无穷 if abs(this_prod) > 1 - 1e-15 this_prod = sign(this_prod) * (1 - 1e-15); end C(m, cols(idx)) = 2 * atanh(this_prod); end end % 变量节点更新 for n = 1:N rows = find(H(:, n)); if isempty(rows) continue; end for idx = 1:length(rows) sum_msg = rxLLR(n); for j = 1:length(rows) if j ~= idx sum_msg = sum_msg + C(rows(j), n); end end V(rows(idx), n) = sum_msg; end end % 硬判决 totalLLR = rxLLR + sum(C(:, n), 1)'; % 简化的总后验 % 实际代码应逐列计算,这里示意 vhat = double(totalLLR < 0); if mod(H * vhat(:), 2) == 0 break; % 满足校验方程,提前退出 end end end

这段代码的问题在于双重循环在M=100, N=200时还能忍受,但仿真批量跑几千帧就会很慢。原始decodeLogDomain.m里用的也是循环,不过它在校验节点更新时缓存了tanh值,避免了重复计算。真正的加速姿势是向量化:对每个校验节点同时处理所有消息,或者用稀疏矩阵运算。我建议你先用循环版本验证正确性,再逐步替换成按行向量化。

数值稳定性上,最容易踩的坑是tanh的输入过大或过小。当变量节点消息累积到绝对值超过 30 时,tanh(x/2)会变成 ±1,atanh的输入接近 1 时输出会爆炸。代码里加了一个钳位:if abs(this_prod) > 1 - 1e-15,这能在 SNR 较高时防止 NaN 传播。原始的decodeLogDomain.m我翻看过,它没有做这个保护,但因为它把tanh(0)单独处理了,所以低 SNR 下表现也正常。

3.3 概率域BP与比特翻转的适用场景

decodeProbDomain.m用的是p0p1两套概率矩阵,每次迭代算delta_p = p0 - p1,公式上等价于 LLR 域,但乘法多、归一化频繁,跑同样的迭代次数会慢 2~3 倍。这套资源里保留它主要是为了教学对照,你可以把 LLR 域的迭代次数设置为和概率域一致,观察两者收敛曲线是否重合。注意概率域一定要在每次变量节点更新后做归一化,否则概率值会漂移。

decodeBitFlip.m是硬判决译码,只根据校验方程是否满足来翻比特,性能比 BP 差好几个 dB,但胜在速度快、逻辑简单。它适合用作 LDPC 编码正确性的快速验证:如果decodeBitFlip在无噪声下能把打错的 1 比特纠回来,说明 H 矩阵和编码步骤没问题;如果纠不回来,可能是 H 矩阵有短环,也可能是初始错误比特太多。三种译码器在ldpcBER.m里通过参数METHOD切换,METHOD=1是对数域 LLR-BP,METHOD=2是概率域 BP,METHOD=3是比特翻转。

4. 完整误码率仿真:ldpcBER.m的参数设置与结果解读

4.1 主脚本的流程设计与参数映射

ldpcBER.m是整套资源的入口,它做的事可以拆成四步:生成或载入 H 矩阵、将信息比特编码成码字、BPSK 调制过 AWGN 信道、用指定译码器还原信息并统计误码率。编码环节它没有直接调encode函数,而是用H的分块结构做矩阵运算,本质是求解Hp * p = Hd * u,其中u是信息向量,p是校验向量。这一步要求makeParityChk.m返回的Hp是可逆的,否则 MATLAB 会报奇异矩阵错误。

主脚本的运行参数集中在头部几行,典型配置如下:

M = 100; % 校验方程数量 N = 200; % 码长 FRAME = 10; % 每个SNR点发送的帧数 ITER = 5; % 最大迭代次数 METHOD = 1; % 1=LLR-BP 2=概率域BP 3=比特翻转 EbN0_dB = 0:0.5:4;

从配套的 fig 文件名能看到作者跑过的组合:FRAME=10 ITER=5 METHOD=1FRAME=100M=100 N=200 FRAME=10 ITER=5 METHOD=1。这说明FRAMEITER是影响仿真时间和结果平滑度的两个关键旋钮。需要特别注意的是ITER=5对于 LDPC 来说偏小,LLR-BP 在码长 200 的码上通常需要 20 次以上迭代才能发挥纠错能力。作者给的ITER=5可能只是为了快速演示效果,正式仿真我一般会调到ITER=20ITER=50

4.2 FRAME、ITER、METHOD三个参数如何影响BER曲线

这三个参数相互影响,改一个就得重新审视其他两个。下面这张表是我在调试这套代码时总结的经验,直接照着设置能省很多时间:

参数推荐范围对结果的影响注意事项
FRAME10~1000控制BER曲线方差,帧数越少曲线越抖每帧200比特,FRAME=100时总共20000比特,BER低于1e-4时需要更多帧
ITER5~50决定译码收敛程度,太小会有错误平台每增加一次迭代,仿真时间线性增加
METHOD1,2,31性能最好,2次之,3最差比较算法时三个值都必须相同次迭代

实际跑的时候如果发现 BER 曲线在 1e-3 附近乱跳,先别怀疑算法,多半是帧数不够。低 SNR 时错误比特多,统计快;高 SNR 时一帧里可能一个错都没有,BER 变成 0,log 坐标会画不出来。解决办法是记录错误帧数而不是直接除总比特数,或者设置一个“最少错误比特数”作为停止条件。原始ldpcBER.m里没有做这个处理,所以你在高 SNR 端看到曲线断掉是正常的,不是 bug。

还有一个容易忽略的点:ITER很小的时候,LLR-BP 和比特翻转的 BER 差距不明显,因为两者都没收敛。只有把ITER拉到 20 以上,LLR-BP 的编码增益才会体现出来。我的习惯是先固定ITER=30跑一个 SNR 点,看译码迭代中校验方程满足的帧占比,若大部分帧在 10 次内收敛,再调低ITER节省时间。

4.3 EbN0与SNR的转换关系

ldpcBER.m里做的 EbN0 转 SNR 很容易写错。BPSK 调制下符号能量等于比特能量,所以SNR_dB = EbN0_dB + 10*log10(code_rate),其中code_rate = (N-M)/N。对于M=100, N=200,码率是 0.5,所以 SNR 比 EbN0 低约 3dB。噪声方差sigma^2 = 1 / (2 * code_rate * 10^(EbN0_dB/10)),注意这里没有考虑归一化,如果调制符号幅度不是 1,还要再乘功率因子。

很多初学者直接把awgn函数的信噪比参数设成 EbN0,结果 BER 曲线整体向右偏移好几个 dB,还以为译码算法有问题。我在这套资源附带的“EbN0与SNR.txt”里看到作者也专门解释了这一点,说明这是 LDPC 仿真里最高频的错位。建议在绘图前先打印一两个 SNR 点的噪声方差,和手算值对比一致再跑全曲线。

5. 排错与加速:从fig文件打不开到仿真跑不动的处理技巧

拿到这套资源后最常遇到的报错有三个。第一个是“无法打开 fig 文件”,它通常不是因为文件损坏,而是 MATLAB 版本低于存图版本。配套图里有frAME=10...fig这样的文件,如果双击提示版本过旧,可以用open('xxx.fig')强制打开,或者用hgload加载后重新保存。第二个是makeParityChk.m报矩阵奇异,原因是随机生成的 H 矩阵中校验位部分不可逆,此时重新运行一次makeLdpc.m即可,或者在主循环里加一个while检测,生成到 Hp 可逆为止。第三个是decodeLogDomain.m出现 NaN,多半是消息值溢出,把初始化rxLLR做一下限幅,比如限制在 ±50 以内,问题就消失了。

仿真速度方面,M=100, N=200的单帧 LLR-BP 在 MATLAB 里循环实现大约需要 0.2 秒,FRAME=100, ITER=5就是 20 秒,还能接受。但如果把N加到 1000 以上,循环实现会慢到无法忍受。此时有两个加速手段:一是把校验节点更新改为spfunaccumarray向量化;二是改用 C Mex 文件。其实很多情况下的瓶颈是重复分配zeros(M,N)大矩阵,我建议在迭代开始前把 H 的稀疏结构提取出来,存成行索引和列索引的 cell 数组,循环时直接索引这些列表,省去每轮find的时间。

验证译码结果正确与否,最直接的办法是发送全零码字。LDPC 是线性码,全零码字是合法码字,BPSK 调制后传输,接收端软信息全为正数,LLR-BP 译码应该输出全零,且第一次迭代后校验方程就满足。如果这个测试不通过,说明 H 矩阵的生成或编码过程有 bug,而不是译码问题。接着可以人为翻转接收软信息的某一位,观测迭代过程中校验节点是否把它修正回来。这套资源的使用说明文档里提到“直接替换数据即可使用”,实际操作时最好先跑通上述全零码测试,再替换成自己的信源比特,这样能快速定位问题是出在编码、信道还是译码模块。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询