简介:QC-LDPC.zip是一份面向通信与量子计算初学者的MATLAB编码仿真资源,聚焦准循环低密度奇偶校验码(QC-LDPC)的完整编码实现。压缩包非常轻量,仅4KB,共包含3个文件,其中QCEncode.m为主程序,QCLDPCBaseH.m用于构造基校验矩阵,另附asv自动备份文件,方便对照修改与调试。针对编码流程,资源依次展示码字生成、线性编码、信道噪声模拟与迭代解码等环节,代码内配有详细注释,可帮助读者快速理解QC-LDPC从原理到仿真的映射关系,也便于通过修改参数观察不同设置下的性能变化。已有1250人学习/下载,适合正在入门LDPC码、量子纠错码或需要MATLAB仿真参考的高年级本科生与研究生使用。 拿到QC-LDPC.zip这个压缩包的时候,我猜很多人第一反应和我一样:先解压,然后对着里面一堆MATLAB脚本、PDF文档和仿真图发愣。QC-LDPC的全称是Quasi-Cyclic Low-Density Parity-Check,准循环低密度奇偶校验码。你大概率已经在5G NR通信、Wi-Fi 6、DVB-S2X、企业级SSD主控甚至卫星通信里跟它打过照面,只是没有专门坐下来系统梳理一遍。这篇文章就从“手上有一个QC-LDPC.zip”这个实际场景出发,带你把准循环结构、基矩阵扩展、BP译码、链路仿真这些核心环节全部过一遍,既能当入门教程用,也能当调试验证手册翻,适合刚接触信道编码的工程师、相关方向的研究生,以及所有想搞明白“5G数据信道编码为什么选它”的人。
1. 先别急着跑代码,拆解包里的核心设计
1.1 这个包到底在解决什么问题
LDPC码1962年就被Gallager提出来了,但是当年硬件条件根本推不动,直到上世纪90年代末被重新发现,才真正走向商用。普通随机构造的LDPC码虽然理论性能很漂亮,但工程落地有两个现实的坎:第一,校验矩阵是随机稀疏矩阵,硬件实现时没法高效复用运算单元,存储也很头疼;第二,编码过程通常需要做高斯消元或者矩阵求逆,复杂度高得吓人。QC-LDPC要解决的就是这两个痛点。
它把校验矩阵H切分成固定大小的循环子矩阵块,每一块要么是全零矩阵,要么是单位矩阵循环移位后的结果。这样一来,编码可以用移位寄存器流水线完成,解码的校验节点更新也能并行化。5G NR选择它作为数据信道编码方案,就是看中了这个结构在性能和硬件友好度之间的平衡。你手上这个QC-LDPC.zip,本质上就是把这一整套设计思路打包成可复现的代码和文档。
拿到压缩包先别急着双击运行,理清楚里面的文件结构比什么都重要。按我的习惯,看一个QC-LDPC工程包会先划分出四类东西:基矩阵定义文件、校验矩阵生成脚本、编码器实现、译码器实现,最后才是链路仿真主程序。阅读顺序建议是“基矩阵 -> 校验矩阵 -> 编码 -> 译码 -> 整链路”,不要上来就死磕译码器代码,容易迷失。
读代码时重点盯三个变量:基矩阵大小、提升因子Z、循环移位值。这三个参数决定了一个QC-LDPC码的全部性格——码长多少、码率多大、性能落点在哪里、硬件复杂度高不高。很多仿真跑不出理想曲线,最后查下来都是这三个参数配对出了问题。
1.2 为什么选准循环结构而不是随机构造
一句话解释准循环:把校验矩阵看成积木拼图,宏观上H是一个mb行乘nb列的矩阵块阵列,每个块都是Z乘Z的方阵。每个方块要么是全零矩阵,要么是单位矩阵循环右移若干位。“循环右移”这个动作,就是“准循环”三个字的来由。
对应关系是这样的:一个很小的基矩阵B就可以完整描述整个大校验矩阵H。B中第i行第j列的元素,表示H中对应位置的子矩阵应该如何生成。值为-1代表全零块;值为2代表单位矩阵循环右移2列;值为0代表单位矩阵本身。5G NR的基矩阵也遵循这个规则,区别只是移位值集合和矩阵尺寸。
这种握手的精妙之处在于可编程性。想改变码长和码率,不需要重新设计矩阵,只调整提升因子Z就行。Z一变,同样的基矩阵就能扩展出不同码长的码字,这在5G基站需要灵活适配不同用户数和传输块大小的场景里,优势是决定性的。随机构造的LDPC码没法这样灵活变通,每换一个码长都要重新优化矩阵,工程上几乎没法接受。
2. 核心细节解析:基矩阵、提升因子和译码器选型
2.1 基矩阵和提升因子怎么配才有好性能
5G NR标准里有两套基矩阵:BG1适合大传输块、高码率场景,最大信息块长度支持到8448比特;BG2适合小传输块、低码率场景,最低码率可以做到1/5。BG1的基矩阵是46行乘68列,BG2是42行乘52列,行数远少于列数,意味着校验位数被压得非常紧凑。
提升因子Z不是随便取的,5G NR定义了从2到384的一组固定取值。Z和基矩阵的组合要满足两个硬约束:一是目标码长在范围内,二是不能出现短环。短环是LDPC码设计中最隐蔽的杀手,尤其是girth等于4的结构,会让译码器在小信噪比区域出现错误地板效应,瀑布区性能直接塌掉。工程上检查girth的土办法是把基矩阵里所有非-1元素两两拿出来看,如果存在两个元素能满足特定同余关系,就可能产生四环,必须避开。
还有一种常见误区:直接用Z=某个值时发现编码出来的码率不对。原因往往是基矩阵里的某些循环移位值在模Z之后会发生冲突,导致两个子矩阵完全相同,实际行重骤降。这种事我遇到不止一次,排查思路很简单,展开H矩阵后统计每行非零元素个数,如果发现行重明显偏离设计值,立刻回头查移位值表和Z的匹配关系。
2.2 编码侧的三条路线怎么选
QC-LDPC的编码实现大致有三条路线。第一条是系统G矩阵法,直接把校验矩阵做高斯消元得到生成矩阵,信息位乘G矩阵得到码字。这个方法实现最简单,问题在于G矩阵通常是稠密矩阵,码长一大存储开销就爆炸。
第二条是基于H矩阵的迭代编码,利用准循环结构的特殊性做分层编码。原理类似解线性方程组的迭代,把校验位逐段求出来。这种方法对QC-LDPC极其友好,复杂度是线性的,但需要校验矩阵具备特定的下三角或者近似下三角结构,不是所有基矩阵都适用。
第三条是RU算法,也叫近似下三角分解编码,把H矩阵通过行列置换转成近似下三角形式,然后分块求解。这是通用性最强、工程里用得最多的方案,5G NR的处理器实现很多都基于它。
新手指导原则很简单:仿真验证阶段用第一种,写代码三十分钟能搞定;到了做性能评估和复杂度受限的场景,再切换到第三种。我自己做协议验证时,G矩阵法跑通功能,RU算法做参考实现对比,两条腿走路最稳。
2.3 译码端为什么全线倒向Min-Sum
译码主流的算法是置信传播,也叫和积算法。它的本质是在Tanner图上做消息传递,把信道输出的对数似然比在每个变量节点和校验节点之间来回交换,若干次迭代后收敛到接近最大后验概率的判决结果。
和积算法的校验节点更新牵扯到双曲正切乘法,在定点硬件上很痛苦。于是工程实现几乎全部切换到Min-Sum近似,把校验节点更新简化为两次求最小值运算加符号判决。性能损失大致在0.2到0.3dB,但硬件复杂度下降一个数量级,这是非常划算的交易。
我做仿真时的习惯是:首次实现用标准Sum-Product,因为逻辑直观,方便和参考代码对结果;功能验证通过后,再加上Min-Sum和归一化修正因子,观察性能差异。这样既能确认译码器本身没问题,又能得到实际工程可用的快速版本。如果上来直接写Min-Sum,出了性能问题就很难定位是近似的锅还是代码的锅。
2.4 译码迭代次数和早停策略
迭代次数的选择直接影响性能和功耗。QC-LDPC译码通常迭代5到10次就够了,超过10次以后性能提升非常有限,但计算量线性增长。5G实网场景里,很多实现会把最大迭代次数设为8到12,配合早停机制来省电。
早停的实现方式很简单:每次迭代做完硬判决后,拿判决码字乘以校验矩阵的转置,检查校验子是否全零。如果全零说明译码成功,立即跳出循环。对QC-LDPC来说,校验矩阵是准循环稀疏的,用稀疏矩阵乘法算校验子非常快,几乎不增加额外负担。这个优化在低信噪比区域可能不明显,但在高信噪比区域能省掉一大半迭代,必须养成写进代码的习惯。
3. 实操全流程:从解压到拉出BER曲线
3.1 环境准备和工具链选型
QC-LDPC.zip解压之后,我推荐用Python加numpy自行实现核心模块来做仿真,而不是直接依赖MATLAB的通信工具箱。原因很朴素:通信工具箱把校验矩阵生成、编码、译码都封装成了黑盒,参数设置不透明,出问题时很难判断是算法问题还是调用问题。自己写能看到每一个比特的流动,调试起来心里有数。
环境版本就三个要求:Python 3.10以上、numpy、matplotlib。仿真场景用AWGN信道加BPSK调制,目标是把一个类似5G NR风格的QC-LDPC码跑出一条BER曲线,验证实测性能距离理论香农极限还有多远。
3.2 生成校验矩阵:基矩阵展开成H
定义一个基矩阵扩展函数是第一步。下面给出一个简洁可运行的示例,输入基矩阵和提升因子,输出完整校验矩阵H。
import numpy as np def expand_base_graph(bg, Z): mb, nb = bg.shape H = np.zeros((mb * Z, nb * Z), dtype=int) for i in range(mb): for j in range(nb): shift = bg[i, j] if shift < 0: continue block = np.eye(Z, dtype=int) block = np.roll(block, shift, axis=1) H[i*Z:(i+1)*Z, j*Z:(j+1)*Z] = block return H # 示例:一个3x6的小基矩阵,提升因子Z=4 bg = np.array([ [0, 1, -1, 2, -1, -1], [2, -1, 0, -1, 1, -1], [-1, 3, -1, 0, -1, 1] ]) Z = 4 H = expand_base_graph(bg, Z) print("H shape:", H.shape) print("Row weight:", H.sum(axis=1))这段代码会把每个非-1元素替换成单位矩阵循环右移shift列的Z乘Z块。跑完打印H的行重,确保每行非零个数一致且符合预期。如果某一行行重明显偏小,说明提升因子和移位值之间有冲突,优先检查模Z后的结果是否正确。
3.3 编码的落地实现
编码环节如果直接用G矩阵法,需要先对H做高斯消元得到系统形式的校验矩阵。高斯消元在二元域进行,numpy里可以把矩阵类型设为int后做模2运算,但要注意消元过程中可能出现列不满秩的情况。处理办法是记录列置换顺序,把消元得到的可逆列排到后面,信息位对应前面那些列。
下面给出一个简易的编码函数,假设H已经被处理成系统形式[H1 | H2],其中H2是可逆方阵:
def qc_ldpc_encode(info_bits, H1, H2_inv): # info_bits: 长度为k的0/1数组 # 校验位 p = H2^-1 * H1 * info_bits (模2) temp = H1 @ info_bits % 2 parity = H2_inv @ temp % 2 return np.concatenate([info_bits, parity])这个实现的复杂度主要集中在对H2求逆这一步,但准循环结构下H2通常做不了太大,仿真验证阶段完全够用。如果后续想提升编码速度,再切换到迭代编码或者RU算法不迟。
3.4 BP译码的完整实现要点
译码器实现是核心,代码框架如下,注意几个容易出错的细节。
def bp_decode(received_llr, H, max_iter=10, use_minsum=True, norm_factor=0.75): m, n = H.shape # 变量节点到校验节点的消息 v2c = np.zeros_like(H, dtype=float) # 先统计每个节点的度数 var_degree = np.sum(H, axis=0) check_degree = np.sum(H, axis=1) llr = received_llr.copy() for _ in range(max_iter): # 校验节点更新 for i in range(m): idx = np.where(H[i] == 1)[0] if len(idx) < 2: continue msgs = v2c[i, idx] # 加权消息 combined = llr[idx] - msgs # 这里做Sum-Product或Min-Sum近似 if use_minsum: sign = np.prod(np.sign(combined)) min_val = np.min(np.abs(combined)) new_msg = sign * min_val * norm_factor else: new_msg = 2 * np.arctanh( np.prod(np.tanh(combined / 2)) ) for j, new_m in zip(idx, new_msg): v2c[i, j] = new_m # 变量节点更新 for j in range(n): idx = np.where(H[:, j] == 1)[0] if len(idx) < 2: continue total = llr[j] + np.sum(v2c[idx, j]) for i in idx: v2c[i, j] = total - v2c[i, j] # 硬判决 hard = (llr + np.sum(v2c, axis=0) > 0).astype(int) syndrome = (H @ hard) % 2 if np.sum(syndrome) == 0: return hard, True return hard, False这个实现虽然写了两层循环,效率不高,但逻辑清晰,适合学习。真正追求速度时,可以把矩阵按校验节点和变量节点的度数预处理成邻接表,在numexpr或numba里做并行,速度能提两个数量级。
3.5 拉出BER曲线:参数和统计口径
链路仿真主程序按下面的参数来设置。调制方式用BPSK,映射规则是0对应+1,1对应-1。信道为AWGN,信噪比范围从0.5dB到4.5dB,每0.5dB一个点。每个信噪比点至少统计50个误码块,同时记录误块率FER和误比特率BER。
| 参数 | 取值 |
|---|---|
| 基矩阵 | BG1风格,3×6示例可替换 |
| 提升因子Z | 32 |
| 码长N | 192 |
| 信息位K | 96 |
| 调制方式 | BPSK |
| 译码算法 | Min-Sum,归一化因子0.75 |
| 最大迭代次数 | 10 |
| 信噪比范围 | 0.5 ~ 4.5 dB |
仿真时注意Eb/N0和Es/N0的换算。BPSK下Es = 2 * Eb,因为每个符号携带1个比特。如果拿Es/N0直接当Eb/N0画图,曲线会整体右移约3dB,这个错误非常隐蔽,我身边不止一个人栽在这里。正确做法是在添加噪声之前,根据目标Eb/N0计算噪声方差:
def awgn_llr(bits, eb_n0_db): eb_n0 = 10 ** (eb_n0_db / 10) noise_var = 1 / (2 * eb_n0) symbols = 1 - 2 * bits.astype(float) noise = np.sqrt(noise_var) * np.random.randn(len(bits)) received = symbols + noise return 2 * received / noise_var信道LLR初始化用2y/σ²,其中y是接收符号,σ²是噪声方差。这个换算关系在做联合信源信道编码或者高阶调制时尤其重要,建议直接封装成公共函数,每次仿真都调用。
3.6 跑了仿真怎么判断结果对不对
跑出BER曲线之后,第一件事不是看绝对性能,而是看曲线的趋势和斜率。在瀑布区,BER曲线应该随着信噪比增加出现明显加速下降,斜率越来越陡;如果曲线在某个信噪比之后趋平,说明有错误地板存在,优先怀疑短环或者LLR初始化问题。
第二件事是把译码成功率和迭代次数分布打出来。正常情况高信噪比时绝大多数数据包在第一次迭代就满足校验子全零退出,如果长时间高信噪比还消耗满迭代次数,大概率是早停逻辑写错了。
第三件事是和理论极限做粗略对比。BPSK调制下,码率1/2的LDPC码在BER等于1e-5时距离香农极限大约1到1.5dB,实测超出2.5dB以上就需要回头查实现。如果用的是示例里的3乘6基矩阵,性能会比5G NR标准码差一点,但趋势仍然清晰可辨。
4. 常见问题与排查技巧实录
4.1 解压报错:这个包本身打不开怎么办
先从最琐碎但最烦人的说起。下载的QC-LDPC.zip如果解压时报“invalid zip archive: could not find eocd”,大概率是下载不完整或者文件损坏。EOCD是ZIP文件尾部的结束记录,包含文件目录信息,没有它解压工具根本不知道去哪找文件索引。处理方法就三步:重新下载,换浏览器或下载工具,下载完先看文件大小和发布页是否一致;如果屡次下载都损坏,用带修复功能的解压工具修复试试;极端情况下试着用命令行解压,有时能绕过某些工具过于严格的校验。
这种情况在开源项目里特别常见,GitHub上直接下载大型zip包,网络波动稍微大一点就容易丢字节。我现在的习惯是下载后用sha256校验一下,标准做法是把哈希值贴到终端里比对,虽然多花半分钟,但能省掉后面一小时的排查时间。
4.2 仿真里的高频翻车点:LLR和码字映射
链路仿真翻车频率最高的是LLR符号方向和映射关系对不上。BPSK调制里,有人习惯用0映射+1、1映射-1,有人反过来。BP译码的硬判决规则是LLR大于0判为0,小于0判为1,如果你的映射用的是另一个方向,整个译码器会彻底失效,表现就是BER在0.5附近横盘,怎么调迭代次数都不动。
排查方法简单粗暴:把信噪比设得很高,比如15dB,发射一个全零码字,检查接收端硬判决输出是否全零。如果全零还出错,那问题一定在译码器本身;如果接收端已经全对了但译码后仍是错码,问题就在LLR极性或者校验矩阵行权重统计上。这种分而治之的定位方式,比对着代码发呆高效得多。
第二个高频坑是Min-Sum没有做归一化修正。Min-Sum的校验节点输出量级天然偏大,直接用于变量节点更新会让软信息过于激进,性能损失可能达到0.5dB以上。加一个0.75左右的归一化系数是最常见做法,也可以用偏移量修正方案。这个系数不是拍脑袋定的,和码率、列重、迭代次数都有关系,建议用扫描方式找最优值。
第三个坑是基矩阵索引从0开始还是从1开始。有些参考代码用-1表示全零块,有些用0表示全零块,还有些把基矩阵存成1-indexed形式。一旦混用,展开出来的H矩阵完全不对,但程序不会报错,只会静默地输出一个错码。唯一的救法是打印H矩阵的前几个块,人工对照基矩阵和展开结果,确认移位值方向一致。
4.3 性能调优的个人心得
说几个我踩过多次之后总结出来的经验。
迭代次数不是越多越好。迭代6次到8次是绝大多数QC-LDPC码的性价比甜区,超过12次的性能提升微乎其微,但在仿真里会明显拖慢总耗时,硬件实现更是线性增加功耗。真正想让性能提升,优先优化校验矩阵和提升因子,而不是加迭代。
Min-Sum的归一化因子在0.7到0.8之间扫描一遍,通常能找到比固定0.75更好的点。实测不同基矩阵差异明显,BG2风格的码对归一化因子更敏感,扫描步长设0.025比较稳妥。
短帧和长帧的优化策略完全不同。信息位只有几百比特时,信道编码的性能受有限长效应影响很大,译码器的边界效应也变得更明显,这时可以考虑增加迭代次数来弥补;信息位几万比特时,迭代次数的影响反而下降,重点要放在校验矩阵的环分布和最小距离上。
LLR量化是另一个常被忽略的点。仿真阶段用浮点看不出问题,但准备移植到定点平台时,量化位数直接从浮点降到8比特会让性能掉1dB以上。建议在做定点之前,先用浮点仿真里留一定信噪比余量,至少0.5dB,再开始量化设计。
最后自动检查一遍自己的工程习惯:每次修改参数,都要跑一条完整的基线曲线保存下来。QC-LDPC仿真有个特点,改动任何一个参数都可能引起连锁反应,没有基线曲线做对照,出了问题根本说不清楚是新代码引入的还是参数变化引起的。我现在每跑完一组仿真,都会把基矩阵、Z、迭代次数、归一化系数、信噪比范围和曲线文件绑在一起存档,几个月后再翻出来看,还能立刻复现当时的实验条件。
对于手上这个QC-LDPC.zip,我的建议是:把里面现有的代码跑通只是第一步,真正有收获的是自己动手把校验矩阵展开、译码器实现、链路口径这三块从头写一遍。跑通之后,找一个5G NR标准里真实的基矩阵替换掉示例矩阵,你会立刻感受到标准设计在环分布和性能微调上的功夫,那一步踩完,才算是真正入了QC-LDPC的门。
本文还有配套的精品资源,点击获取