2026/10/18 23:44:05

LDPC与64QAM联合仿真:软解调与置信传播译码的MATLAB实现

LDPC与64QAM联合仿真:软解调与置信传播译码的MATLAB实现 简介这是一份面向通信工程、信号处理方向学生与工程师的MATLAB仿真资源聚焦LDPC信道编码与64-QAM调制解调联合系统的性能验证。资源包共7个文件以5个.m脚本和2个.mat数据文件为主压缩包约13KB脚本承担编码、软判决解码、调制映射与主仿真流程mat文件则保存校验矩阵与生成矩阵等关键参数便于直接加载复现。已有250人学习下载说明其在信道编码入门与课程设计场景中具有一定参考价值。读者可借助完整编解码源码观察LDPC码在64-QAM系统中的误码率表现理解置信传播类迭代解码的实现细节并在此基础上调整信道条件、码长与调制参数完成从理论到仿真的对照分析适合作为通信系统课程实验或毕业设计的起步工具。1. 从一次“星座图糊成一团”说起LDPC 与 64QAM 的仿真到底在做什么第一次把 LDPC 编码和 64QAM 调制串到一条链路里跑仿真很多人会盯着星座图发愣明明编码增益理论上有好几个 dB可误码率曲线就是不肯往下掉星座点糊成一团像被谁用手抹过。问题往往不在 LDPC 本身也不在 64QAM 映射而在两者中间那层“软信息”没接上。LDPC 译码吃的是对数似然比64QAM 解调吐出来的也应该是软信息一旦中间被硬判决截断编码增益就凭空蒸发。这套 LDPC 64QAM 的 MATLAB 仿真程序本质上就是搭一条从信源、LDPC 编码、64QAM 调制、AWGN 信道、软解调、置信传播译码到误码统计的完整链路用误码率曲线回答一个工程问题在给定码率、码长和星座映射下这套组合到底能换来多少 dB。它适合正在做链路级仿真、想验证自己 LDPC 译码器或想搞清楚软解调怎么落地的人新手能照着把曲线跑出来熟手能顺着参数表看到边界在哪。2. 链路骨架怎么搭从信源到 LDPC 编码器的第一段2.1 为什么先定码率、码长和基矩阵而不是先写调制搭仿真最忌讳一上来就写调制映射。LDPC 的码率决定了每帧里信息位和校验位的比例码长决定了译码复杂度和迭代收敛速度基矩阵决定了校验矩阵的结构。这三者定不下来后面 64QAM 每符号 6 bit 的映射就没法对齐。常见做法是先选一个规则或准循环的基矩阵比如码长 648、码率 1/2 这类在链路仿真里被反复验证过的配置再往上叠调制。我一般会先把码率、码长、每帧符号数写成变量放在脚本开头后面所有维度都从这几个变量推出来避免硬编码导致改一个参数全盘崩。% 链路基础参数先定码率、码长、每帧信息比特数 R 1/2; % LDPC 码率 N 648; % 码长编码后比特数 K round(N * R); % 信息比特数 qamOrder 64; % 64QAM每符号 6 bit bitsPerSym log2(qamOrder); numFrame 2000; % 仿真帧数决定曲线平滑度 snrDb 0:0.5:6; % 信噪比扫描范围这段代码把码率、码长、调制阶数和信噪比扫描范围集中定义。N和K必须满足K N * R的整数关系否则后面信息位和校验位对不上。numFrame直接决定误码率曲线在低误码率区是否平滑2000 帧是个折中想看到 1e-5 量级建议加到 1e4 以上。snrDb的步长 0.5 dB 足够看出瀑布区斜率太粗会漏掉拐点。2.2 用准循环结构生成校验矩阵别手搓稀疏矩阵LDPC 的核心是稀疏校验矩阵 H。手搓一个随机稀疏矩阵不是不行但译码时列重和行重不可控容易出现短环置信传播迭代几次就震荡。工程上更稳的是准循环结构把 H 拆成若干个子块每个子块是单位矩阵的循环移位。这样既保证稀疏性又天然抑制短环MATLAB 里用循环移位生成非常直接。% 准循环 LDPC 校验矩阵生成以子块循环移位构造 blockSize 27; % 子块大小N 需能被其整除 numBlockCol N / blockSize; % 子块列数 numBlockRow numBlockCol - K/blockSize; % 子块行数 H zeros(numBlockRow*blockSize, N); shiftBase [0 1 2; 3 4 5; 6 7 8]; % 移位基数按子块行列索引取模 for br 1:numBlockRow for bc 1:numBlockCol shift mod(shiftBase(mod(br-1,3)1, mod(bc-1,3)1), blockSize); I eye(blockSize); H((br-1)*blockSize1:br*blockSize, ... (bc-1)*blockSize1:bc*blockSize) circshift(I, shift, 2); end end这里blockSize取 27是因为 648 能被 27 整除得到 24 个子块列。shiftBase是一个 3×3 的移位基数表通过取模让不同子块获得不同移位量避免所有子块移位相同导致矩阵秩亏。circshift(I, shift, 2)沿列方向循环移位生成循环置换子块。生成后建议检查rank(H)是否接近N-K如果秩不足说明移位设计有重复需要调整shiftBase。2.3 编码用高斯消元还是近似下三角取决于你要不要实时LDPC 编码的难点在于 H 不是系统形式直接解H * c 0计算量大。常见做法是对 H 做高斯消元把它化成[I | P]形式然后c [m, m*P]。但高斯消元会破坏稀疏性码长一长就慢。另一种是近似下三角编码利用准循环结构做分块回代复杂度线性于码长。仿真阶段如果只跑几千帧高斯消元够用如果要做上万帧扫描建议用下三角回代。% 高斯消元法将 H 化为系统形式生成编码矩阵 G [H_rref, pivots] rref(H); % 行化简pivots 记录主元列 P H_rref(:, setdiff(1:N, pivots)); % 非主元列构成 P G [eye(K), mod(P, 2)]; % 系统编码矩阵模 2 运算 % 编码信息比特 m 乘以 G msgBits randi([0 1], K, 1); codeBits mod(msgBits * G, 2);rref返回行最简形和主元列索引。setdiff(1:N, pivots)取出非主元列构成校验部分 P。G [eye(K), mod(P,2)]是系统形式前 K 位就是信息位后 N-K 位是校验位。mod(msgBits * G, 2)完成模 2 矩阵乘法。注意rref在码长较大时可能很慢648 长度尚可再长就要换下三角法。编码后建议抽查mod(H * codeBits, 2)是否全零这是校验矩阵和编码矩阵一致性的后悔药。3. 64QAM 映射与软解调软信息怎么从星座点里抠出来3.1 格雷映射不是可选项是 LDPC 能吃上增益的前提64QAM 每个符号承载 6 bit星座图是 8×8 的格点。映射方式直接决定相邻星座点之间有几个比特翻转。如果不用格雷映射相邻点可能翻转多个比特解调时一个符号判错就带出多个比特错误LDPC 译码器再强也救不回来。格雷映射保证相邻星座点只差 1 bit这样符号错误退化成单比特错误LDPC 的纠错能力才能发挥。MATLAB 里可以用qammod配合gray选项也可以自己按比特到 I/Q 的映射表生成。% 64QAM 格雷映射比特到星座点的映射 M 64; % 生成格雷码索引保证相邻星座点只差 1 bit grayMap bitxor(0:M-1, floor((0:M-1)/2)); % 将 6 bit 分组映射到 I/Q 两路每路 3 bit iIdx floor(grayMap / 8); % 高 3 bit 决定 I 路 qIdx mod(grayMap, 8); % 低 3 bit 决定 Q 路 iLevel 2*iIdx - 7; % I 路电平范围 -7 到 7 qLevel 2*qIdx - 7; % Q 路电平 constellation iLevel 1j*qLevel; constellation constellation / sqrt(mean(abs(constellation).^2)); % 归一化grayMap用异或和右移生成格雷码序列这是经典构造。iIdx和qIdx把 6 bit 拆成两路 3 bit分别映射到 8 个电平。2*iIdx - 7把 0 到 7 映射到 -7 到 7 的奇数电平。最后除以均方根做归一化保证平均符号能量为 1这样信噪比的定义才准确。归一化这步经常被忽略导致 SNR 和实际信噪比差一个常数曲线整体平移。3.2 软解调输出 LLR硬判决在这里必须让路LDPC 译码器要的是每个比特的对数似然比不是 0 或 1。64QAM 软解调的任务就是根据接收符号和信道噪声方差算出每个比特为 0 和为 1 的概率比。精确 LLR 要对星座点做全搜索复杂度是 64 次距离计算简化做法是只取最近的两个星座点一个对应比特 0一个对应比特 1用距离差近似。仿真里建议先用精确 LLR 验证性能上限再换简化算法看损失多少。% 64QAM 软解调计算每个比特的 LLR noiseVar 10^(-snrDb(1)/10); % 噪声方差假设符号能量归一 rxSym codeBitsToSym sqrt(noiseVar/2)*(randn(size(...)) 1j*randn(size(...))); llr zeros(N, 1); for b 1:bitsPerSym % 找出该比特为 0 和为 1 的星座点集合 bitMask bitget(grayMap, b); sym0 constellation(bitMask 0); sym1 constellation(bitMask 1); % 计算到两组星座点的最小距离 d0 min(abs(rxSym - sym0).^2); d1 min(abs(rxSym - sym1).^2); llr(b:bitsPerSym:end) (d1 - d0) / noiseVar; endbitget(grayMap, b)取出格雷映射索引第 b 位的值把星座点分成两组。min(abs(rxSym - sym0).^2)是接收符号到最近 0 比特星座点的距离平方。(d1 - d0) / noiseVar是近似 LLR符号约定为正值倾向 0负值倾向 1。llr(b:bitsPerSym:end)按比特位置交错填入保证 LLR 顺序和编码比特顺序一致。这里最容易翻车的是 LLR 符号约定和译码器不一致译码器如果按相反约定实现曲线会完全不收敛。3.3 置信传播译码迭代次数和归一化因子是两个命门LDPC 译码用置信传播在变量节点和校验节点之间来回传 LLR。每次迭代校验节点根据相邻变量节点的 LLR 更新校验信息变量节点再汇总。迭代次数太少译码不充分太多延迟高且可能震荡。另一个关键是归一化因子校验节点更新时乘一个小于 1 的系数能抑制过冲通常取 0.75 到 0.9。这两个参数直接决定瀑布区位置和错误平底高度。% 置信传播译码最小和算法加归一化 maxIter 50; alpha 0.8; % 归一化因子 Lq llr; % 变量节点初始 LLR for iter 1:maxIter % 校验节点更新最小和近似 for cn 1:size(H,1) idx find(H(cn,:)); signs sign(Lq(idx)); mags abs(Lq(idx)); [~, minPos] min(mags); min1 mags(minPos); mags(minPos) inf; min2 min(mags); for k 1:length(idx) if k minPos Lr alpha * prod(signs) * signs(k) * min2; else Lr alpha * prod(signs) * signs(k) * min1; end Lq(idx(k)) Lq(idx(k)) Lr; end end % 硬判决并校验 hardBits Lq 0; if mod(H * hardBits, 2) 0 break; end endmaxIter 50是迭代上限实际仿真里大部分帧在 10 到 20 次内收敛设 50 是兜底。alpha 0.8是归一化因子取值太小收敛慢太大容易震荡0.75 到 0.9 是常见区间。校验节点更新用最小和近似找最小和次小幅度对每个变量节点用不同的最小值。Lq(idx(k)) Lq(idx(k)) Lr是变量节点更新。mod(H * hardBits, 2) 0是校验方程全满足的提前退出条件能省不少时间。注意prod(signs)在符号乘积上可能溢出长码建议用对数域累加符号。4. 避坑与排查曲线不下降时先查这五处4.1 误码率曲线在某个 SNR 后不再下降现象是曲线到了 1e-4 附近就压平继续加 SNR 也没用。原因通常是 LDPC 校验矩阵存在短环置信传播在环上反复传同一信息无法收敛到正确码字。解决方法是检查 H 的围长准循环结构里调整移位基数避免 4 环和 6 环。可以用girth检测脚本扫一遍或者直接换一组已知围长较大的移位表。4.2 译码器完全不收敛误码率接近 0.5现象是无论 SNR 多高误码率都在 0.5 附近说明译码器根本没在工作。最常见原因是 LLR 符号约定反了译码器把正 LLR 当成比特 1而软解调输出正 LLR 表示比特 0。解决方法是统一约定在软解调输出后加一个符号翻转或者改译码器里的硬判决条件。另一个可能是 H 矩阵和编码矩阵不匹配用mod(H * codeBits, 2)抽查几帧就能确认。4.3 星座图归一化漏了SNR 定义整体偏移现象是曲线形状对但整体比预期差几个 dB。原因是星座点没归一化平均符号能量不是 1导致实际信噪比和设定值差一个常数。解决方法是映射后除以sqrt(mean(abs(constellation).^2))并在噪声方差计算里用归一化后的能量。这个坑很隐蔽因为曲线趋势看起来正常只是位置不对。4.4 迭代次数设太大仿真时间爆炸现象是跑一帧要好几秒扫完整个 SNR 范围要几个小时。原因是maxIter设了 100 甚至 200而大部分帧其实 10 次内就收敛了。解决方法是加提前退出条件校验方程全满足就 break同时把maxIter降到 50。另外可以把校验节点更新的内层循环向量化MATLAB 里用矩阵操作替代逐元素循环速度能快好几倍。4.5 帧数太少低误码率区曲线抖动现象是 1e-5 附近的曲线上下跳看不出真实趋势。原因是帧数不够错误事件太少统计涨落大。解决方法是把numFrame加到 1e4 以上或者在低误码率区用重要性采样。仿真时间不够就先用高 SNR 少帧数粗扫找到瀑布区后再在关键点加密帧数。5. 把仿真变成能复用的工具参数化扫描与结果验证跑到这里链路已经能出曲线了但真正让这套 LDPC 64QAM 仿真有价值的是把它变成可复用的扫描工具。我一般会把码率、码长、迭代次数、归一化因子抽成配置结构体外层套一个循环一次跑出多组曲线对比。比如固定码长 648扫码率 1/2、2/3、3/4看编码增益怎么随码率变化或者固定码率扫alpha从 0.7 到 0.9看归一化因子对错误平底的影响。这样跑一轮参数边界就清楚了。% 参数化扫描多码率对比 rates [1/2, 2/3, 3/4]; snrRange 0:0.5:8; berCurves cell(length(rates), 1); for r 1:length(rates) cfg.R rates(r); cfg.N 648; cfg.K round(cfg.N * cfg.R); cfg.maxIter 50; cfg.alpha 0.8; berCurves{r} runLinkSim(cfg, snrRange, 5000); end % 绘图对比 figure; for r 1:length(rates) semilogy(snrRange, berCurves{r}, -o); hold on; end grid on; xlabel(SNR (dB)); ylabel(BER); legend(R1/2,R2/3,R3/4);runLinkSim是把前面所有步骤封装成的函数输入配置和 SNR 范围输出误码率数组。cell存不同码率的曲线避免维度不一致。semilogy画对数纵轴这是误码率曲线的标准画法。5000帧是每个 SNR 点的帧数低码率区可以少一点高码率区建议加到 1e4。跑完对比三条曲线能看到码率越高瀑布区越靠右但斜率更陡这是编码增益和频谱效率的权衡。验证结果是否可信我习惯做两件事。一是把仿真曲线和理论香农限对比64QAM 在码率 1/2 时的理论门限大概在 3 dB 出头如果仿真曲线在 5 dB 才降下来说明链路里有额外损失得回去查软解调或译码。二是固定一组参数换不同随机种子跑三遍看曲线是否重合重合说明帧数够不重合就得加帧数。这两个习惯帮我省过很多次返工。最后说个我自己的教训早期做这套仿真时我总想一次把参数调到最优结果在alpha和maxIter上反复试浪费了一周。后来改成先固定一组保守参数把曲线跑通再单变量扫描效率高得多。仿真这东西先让它跑起来再让它跑得准最后才让它跑得快。希望帮到你。本文还有配套的精品资源点击获取