☰
5G LDPC编码译码MATLAB仿真:从基矩阵到完整链路实现
2026/10/14 1:22:02 网站建设 项目流程

简介:本资源是面向通信工程专业学生、5G协议研究者及MATLAB开发者的技术实践包,聚焦5G NR标准下LDPC码的完整仿真实现,解决从理论参数理解到可运行代码落地的关键断点。压缩包共9个文件(7个.m脚本+2个.mat校验矩阵数据),总大小仅20KB,轻量但结构完整:包含3GPP基图选择、提升尺寸计算、H矩阵生成、 puncturing处理及端到端仿真主函数等核心模块,覆盖编码、信道加噪、BP迭代解码全流程。已有1360人学习下载,体现了其在教学演示与算法验证场景中的实用价值。用户可直接运行sim_ldpc_v1.m复现5G LDPC性能曲线,借助get_pcm.m和get_3gpp_base_graph.m深入理解3GPP Release 15定义的两类协议基图构造逻辑,并通过.mat文件快速加载标准化校验矩阵,显著降低协议级LDPC仿真的入门门槛与调试成本。 5G和LDPC这两个词放在一起,几乎就是现代移动通信物理层的代名词。做通信算法的人多半都绕不开MATLAB里的LDPC仿真——从最初在论文里看到校验矩阵构造方法,到真正把5G NR标准里的BG1/BG2基图跑通,这中间的坑比想象中多得多。我花了不少时间把5G标准协议下的LDPC编码译码链路在MATLAB里完整实现了一遍,从基矩阵生成、扩展成校验矩阵、编码、译码到整条仿真链路评估,现在把这些过程整理出来。这篇文章不是协议文档的复述,而是我踩过坑之后,梳理出的一套可以直接落地的实现思路,适合刚开始接触5G LDPC、想在MATLAB里跑通一个完整物理层编码仿真的同学,也适合那些已经在用通信工具箱、但想搞清楚底层到底发生了什么的人。

1. 5G为什么把LDPC扶上位:数据信道编码的选型逻辑

1.1 从Turbo到LDPC:一次迟来的替换

4G时代的Turbo码统治了十几年,到了5G NR标准制定时,数据信道最终没有继续用Turbo,而是选择LDPC作为PDSCH和PUSCH的信道编码方案。很多人以为这只是因为LDPC性能更好,实际上更关键的原因是——LDPC的并行译码结构天然适配高吞吐需求。Turbo码的迭代译码本质上是串行的软输入软输出交织,即便用并行MAP译码器,扩展性也受限;而LDPC的校验矩阵天生带并行度,译码器可以用大量处理单元同时更新变量节点和校验节点。

5G eMBB场景的峰值速率目标是20Gbps,这个吞吐量对译码器的并行度和时钟频率要求极高。LDPC的准循环结构(QC-LDPC)让硬件实现可以按块进行并行更新,这是Turbo很难做到的。另外,LDPC的码率灵活性更好,通过打孔和缩短可以很平滑地适配不同码率,不像Turbo码那样需要大量交织器设计。

1.2 5G NR里LDPC的职责边界

LDPC在5G NR中并不是唯一的信道编码。物理层控制信道PDCCH、PUCCH用的是Polar码,而数据信道PDSCH/PUSCH才用LDPC。这个分工在R15版本里就定死了。做MATLAB仿真时,先别急着上来就写编解码函数,想清楚你仿真的场景是数据信道还是控制信道很关键——这决定了你该用哪套协议参数。我见过不少人在PUCCH仿真里拿LDPC去凑,结果误码率曲线怎么调都下不来,大概率是编码方案选错了。

5G LDPC的协议定义在3GPP TS 38.212第5.3.2节,涉及基图选择、提升因子Zc的确定、位选择、交织等步骤。MATLAB通信工具箱里提供的nrLDPCEncode和nrLDPCDecode函数严格遵循这套流程,但前提是你得先正确调用nrDLSCH或自己完成传输块到码块的切分。

2. 协议里的关键参数:基图、提升尺寸与速率匹配

2.1 BG1/BG2的适用场景与矩阵结构

5G标准定义了两套基图:BG1和BG2。BG1面向大传输块和高码率场景,支持的最大码块长度是8448比特,码率上限接近8/9;BG2面向小传输块和低码率场景,最大码块长度是3840比特,码率下限到1/5。选择规则在TS 38.212里有明确表格:当传输块大小A大于292比特或者码率R大于1/4时选BG1,否则选BG2。这个条件在MATLAB里判断就两行,但非常容易忽略——选错基图直接导致编码结果对不上标准。

从矩阵结构看,BG1的基矩阵大小是46行68列,其中前22列是信息列;BG2是42行52列,前10列是信息列。基矩阵里的每个元素有两种含义:-1代表这个位置没有边连接,非负整数代表循环移位值。所谓的QC-LDPC,就是每个非-1元素被替换成一个Zc乘以Zc的单位阵的循环移位矩阵,-1被替换成全零矩阵,这样整个H矩阵就由Zc大小的循环子矩阵块拼接而成。

2.2 提升因子Zc的确定过程

Zc的取值并不是任意的,5G标准限定了51个合法值,形式为Zc = a × 2^j,其中a属于{2,3,5,7,9,11,13,15},j从0到7。选取的原则是:用当前码块长度K除以BG的信息列数(BG1是22,BG2是10),向上取整得到K',然后从Z值表里选一个大于等于这个比值的最小Zc。更准确地说,是先计算Kb,对于BG1固定Kb=22;对BG2,如果K大于640则Kb=10,否则需要根据K的取值查表确定Kb(可能是6到9之间的值),然后Zc取满足Kb×Zc≥K的最小合法值,最终码块填充为K'=Kb×Zc比特。

这个步骤在MATLAB里直接用nrLDPCEncode会自动处理,但如果自建H矩阵,Zc的选取是第一步且决定后续所有矩阵尺寸。我建议写一个独立的函数,把51个Z值存成一个常量数组,然后用循环查找,别用二分查找之类的花活——这个查找频率不算高,简单直接最不容易出错。

2.3 系统位打孔与速率匹配的工程含义

5G LDPC自带一个有意思的设计:编码完成后,前2×Zc个系统比特会被打孔,不直接送入信道。这听起来反直觉——明明是重要的系统位,为什么不发?原因是这2列在基图中参与核心校验节点连接,打孔之后译码端可以把它们当作未知变量,通过校验关系进行恢复,等价于提供了一种隐式的速率匹配,让高码率场景下的性能不会因为直接删掉校验位而崩塌。

速率匹配本身在TS 38.212里定义了一套完整的比特选择、交织、循环缓冲区生成流程。MATLAB里对应的是nrRateMatchLDPC和nrRateRecoverLDPC。自己做完整链路仿真时一定要注意:如果直接编码然后过AWGN,不做速率匹配,仿真出来的BLER曲线和标准场景完全对不上。因为标准流程里速率匹配输出的码率是实际的传输码率R,而不是基图本身设计的码率。

3. MATLAB环境准备与校验矩阵的生成

3.1 用5G Toolbox还是自建矩阵

两种路线我都走过,先说结论:想快速出结果验证算法,直接用通信工具箱;想深入理解协议细节或者做自定义改进(比如换一种基矩阵、改译码调度),就得自己生成H矩阵。

用工具箱的好处是省心。nrLDPCEncode输入码块二进制位和基图编号、Zc值,直接输出编码后的比特。nrLDPCDecode输入LLR,通过名称-值对设定迭代次数和译码算法,返回译码后的硬判决值。但工具箱是个黑盒,一旦性能不达标,你很难判断是编码的问题还是译码设置的问题。我自己是把两条路都打通了:先用工具箱跑通基线,再自建矩阵复现结果,两者对得上之后,才有底气去改算法细节。

3.2 从基矩阵扩展完整H矩阵的实现套路

5G基图的完整数据可以从MATLAB的通信工具箱里提取,函数名是ldpcQuasiCyclicMatrix(旧版本叫ldpcQuasiCyclicMatrix),输入BG编号返回基矩阵。也可以从3GPP的Excel表格里手动维护一份,但那个容易出错,不推荐。

核心扩展逻辑如下:

HG = ldpcQuasiCyclicMatrix(bgNumber); % 返回基矩阵 Zc = 确定好的提升因子; H = zeros(size(HG,1)*Zc, size(HG,2)*Zc); % 预分配,后面转稀疏 for r = 1:size(HG,1) for c = 1:size(HG,2) shift = HG(r,c); if shift >= 0 % 生成Zc×Zc的单位阵,循环右移shift位 block = circshift(eye(Zc), shift, 2); H((r-1)Zc+1:rZc, (c-1)Zc+1:cZc) = block; end end end H = sparse(H); % 必须转稀疏

这里有三个容易翻车的地方。第一,MATLAB索引从1开始,而协议里的移位值是从0开始计数的,circshift的第二个参数需要小心核对。第二,循环移位方向是右移还是左移,不同资料里可能不一样,以TS 38.212表5.3.2-2和ldpcQuasiCyclicMatrix输出的一致为准。第三,H矩阵即使在中等Zc下也很大,BG1在Zc=384时,矩阵维度是17664行乘以26112列,非稀疏表示直接内存溢出——我机器上跑过一次,内存16GB直接到顶,所以稀疏存储不是可选项,是必选项。

3.3 大矩阵的稀疏存储与内存控制

生成H矩阵时的预分配方式也值得说两句。用zeros预分配一个17664×26112的double矩阵,瞬间就是3.7GB内存占用量。更好的做法是直接构建稀疏矩阵的三元组形式(行号、列号、值),最后统一调用sparse。MATLAB的sparse对这种分块循环矩阵支持得不错,而且后续译码时的稀疏矩阵乘法效率很高。

如果机器内存吃紧,还有一个省钱的办法:不显式生成完整H矩阵,而是维护一份基矩阵加Zc,译码时按块索引去访问子矩阵。这种方案对内存友好,但需要把译码器里的消息传递逻辑从"矩阵运算"改成"基于块索引的循环更新"。代码复杂度会上升,但对大Zc仿真非常管用。我后来做64路并行仿真时,就是靠这个方式把内存峰值压下来的。

4. 编码器实现:基于QC结构的快速编码

4.1 借助nrLDPCEncode快速起步

工具箱的编码入口很简洁:

coded = nrLDPCEncode(cw, bgNumber, Zc);

这里的cw是信息比特向量(长度K',注意是填充后的长度),bgNumber是1或2,Zc是前面算好的提升因子。输出长度是BG的列数乘以Zc,即BG1是68×Zc,BG2是52×Zc。这个长度对应的是完整的LDPC码字长度,还没做速率匹配。

值得留意的是nrLDPCEncode要求的输入是K'比特,也就是填充之后的长度。如果块长K本身不是Kb×Zc,需要先补零到K',编码完再在速率匹配阶段处理。这个细节我最初没注意,直接把K长度的比特丢进去,报错还以为是工具箱版本问题。

4.2 自实现编码器的核心思路

自己写编码器时,最直观的思路是把H矩阵高斯消元成系统形式[P|I],然后用生成矩阵G=[I|P^T]直接做矩阵乘。但这个思路在5G LDPC场景下有个问题:H矩阵非常大,高斯消元过程会破坏准循环结构,消出的P矩阵不再具备分块循环特性,存储和计算开销都水涨船高。

工程上更实际的做法是利用QC-LDPC的块结构做高效编码。基本思路是:把系统位分成块,利用校验矩阵中双对角结构(BG1和BG2的后一部分校验位矩阵都是双对角形式)分层递推求解校验位。这个方法的计算复杂度是O(n)级别,远优于通用矩阵乘的O(n^2)。实现上需要把基矩阵按列分块成信息部分A和校验部分B,B里又分核心校验位和扩展校验位,然后逐块递推。

这部分实现我强烈建议先画一个基矩阵的非零分布图再动手,搞清楚了哪些列是系统列、哪些是核心校验列、哪些是扩展校验列,代码写起来心里就有底。BG1和BG2的列结构其实很有规律,前Kb列是系统位,紧接着两列是核心校验,再后面是扩展校验。

5. 译码器实现:从BP到min-sum的工程折中

5.1 置信传播译码的数学雏形

LDPC译码的核心是置信传播(Belief Propagation,BP),也叫和积算法(Sum-Product Algorithm)。在Tanner图上,信息在变量节点和校验节点之间来回传递。变量节点收集来自信道和各校验节点的信息,更新后发给校验节点;校验节点则基于外部信息的"校验约束"更新后发回变量节点。

log域BP的校验节点更新公式为:

L(r_ji) = 2 * atanh( ∏ tanh(L(q_ij)/2) )

这个公式看起来简洁,但在MATLAB里直接用会面临两个问题:一是atanh的参数精度问题,当tanh乘积趋近1时数值不稳定;二是大量求tanh运算的开销非常大。性能仿真里这个更新在每轮迭代中要执行成千上万次,纯BP跑一次中等规模的仿真,慢得让人怀疑人生。

5.2 归一化最小和译码的MATLAB实现

工程上几乎都不会用纯BP,而是用最小和(Min-Sum)及其改进版本。核心思想是把校验节点更新里的tanh/atanh替换成符号乘加最小值:

L(r_ji) = α * (∏ sign(L(q_ij))) * min|L(q_ij)|

其中α是归一化因子,典型值在0.7到0.8之间。这个近似保留了BP最核心的"最不可靠消息主导(最小LLR)"逻辑,同时把指数运算换成了比较运算,在MATLAB里用min函数一行的量,速度提升数倍,性能损失通常不到0.1dB。

我的译码器骨架是这样写的:

function [decbits, iters] = ldpc_min_sum_decode(LLR_in, H, Zc, max_iter, alpha) [m, n] = size(H); % 预处理校验矩阵的邻接列表 [row_idx, col_idx] = find(H); % 初始化变量节点到校验节点的消息为信道LLR M_vc = LLR_in; for iter = 1:max_iter % 校验节点更新 (用邻接列表做分组归约) for r = 1:m idx = find(col_idx == r); messages = M_vc(idx); signs = prod(sign(messages)); [min1, minpos] = min(abs(messages)); % 计算每个出边的外信息 ... end % 变量节点更新 for v = 1:n idx = find(row_idx == v); M_vc(idx) = LLR_in(v) + sum(M_cv(all_except_idx)); end % 后验硬判决与校验 ... end end

这个朴素的实现能跑通,但速度很一般。原因在于每次迭代都用find去扫描邻接,这非常浪费。应提前把邻接关系缓存成cell数组,每个变量节点一个列表、每个校验节点一个列表,更新时直接遍历cell。我实测这个改动带来的提速比是最明显的,能到5倍以上。

5.3 迭代停止条件与LLR输入的处理

迭代停止条件的设置直接关系到平均迭代次数,也就直接关系到吞吐。一般做法是:每轮迭代后做一次硬判决,如果H × hard_decision = 0(模2),提前终止。这个判据在误码率曲线瀑布区能显著降低平均迭代次数。

LLR输入的一个隐蔽细节是打孔位和填充位的处理。打孔的系统位没有对应的信道观测量,输入LLR应该置0(表示等概率)。填充位在编码前是0,在译码端不是简单置0,而是要把它们的先验信息设为一个很大的正值,告诉译码器"这个位确定是0"。如果填充位也按0处理,相当于给译码器提供了错误的先验信息,BLER会凭空高出一截。这个坑我在自建矩阵译码时踩得很深,后来逐一排查LLR设置才发现。

6. 仿真链路搭建与性能验证

6.1 完整链路示例与参数表

我搭的仿真链路是:信息比特生成 → 码块切分与填充 → LDPC编码 → 速率匹配 → QPSK调制 → AWGN信道 → 软解调(LLR计算) → 速率恢复 → LDPC译码 → 填充分离与误块率统计。

仿真参数如下:

参数取值
基图BG1
信息块长度K1408比特
Zc64
码率(速率匹配后)1/2
调制方式QPSK
信道AWGN
译码算法归一化最小和,α=0.75
最大迭代次数10
仿真样本数每SNR点至少2000个码块

这个参数组合在MATLAB里单机跑,速度还能接受。如果想看BLER的瀑布区,SNR范围可以设置在0dB到3dB区间,步进0.25dB或0.5dB。

6.2 结果分析:性能阈值与迭代次数观测

按上述参数,归一化最小和在1/2码率、QPSK下的性能仿真,BLER 1e-2对应的Eb/N0大约在1.6到1.8dB左右,相比未编码QPSK有一个很明显的编码增益。和香农极限的差距大概在1dB内。如果换用更大的Zc和更长的码块,差距还能进一步缩小。

另一个值得观察的指标是平均迭代次数。在低SNR时,大部分码块会耗尽10次迭代才能收敛;进入高SNR区,绝大多数码块在2到4次迭代内就满足了校验约束并提前终止。这种"SNR越高、时延越低"的特性是LDPC提前终止策略带来的直接红利,在系统级仿真里做时延预算时很有价值。

我建议仿真时除了画BER/BLER曲线,也把平均迭代次数打印出来,这个数据在给领导或同学汇报时特别能说明问题。MATLAB里直接在译码函数里累计每轮迭代次数即可,代价几乎为零。

7. 实践中的踩坑记录与调试链路

7.1 矩阵下标偏移与扩展规则不对齐

这是自建H矩阵时最容易踩的坑。5G标准表格里的移位值是0-based,MATLAB的索引是1-based,这两个偏移量差一位,效果就是整个校验矩阵的所有子块都错位了一个位置,译码器完全无法收敛。更迷惑的是,编码端如果也用了同样的错位逻辑,编出来的码字居然还能过校验,表面上看起来一切正常,只有和标准编码器对拍才暴露问题。我现在的做法是:每次生成H矩阵后,先用nrLDPCEncode的编码结果和自己实现的编码器输出对比一次,不一致就直接报警。这个自检步骤应该固化到仿真脚本里,而不是每次手工检查。

7.2 BLER/SNR性能不达标时的排查链路

我把我踩过的性能问题归纳成一条排查链路,按照出现频率排序:

  1. 先确认LLR输入没接反。QPSK软解调中,实部虚部对应关系弄反是最常见的低级错误,症状是性能曲线完全不对。
  2. 再确认填充位LLR设对了。把填充位当普通位设成0LLR,性能掉大约0.3dB,不仔细看曲线甚至不容易察觉。
  3. 检查速率匹配与恢复是否对称。速率匹配端做了比特选择,恢复端就得放回对应的LLR位置,放错了等于把信息打乱。
  4. 检查最大迭代次数。迭代次数设太少,比如3次,瀑布区会右移明显;设到10到20次左右性能趋于饱和,没必要盲目加大。
  5. 如果以上都没问题,再考虑归一化因子α的影响。α在0.7到0.8之间有一个性能平台,偏差太多会有0.1到0.2dB的劣化。

7.3 一个值得优化的细节:分层译码

最后分享一个我后来做的优化思路。前面说的译码器每次迭代是"先更新所有校验节点,再更新所有变量节点",这叫泛洪调度(flooding)。它的收敛速度比较慢,需要更多迭代次数。5G实际硬件实现和MATLAB工具箱默认算法都支持分层调度(layered scheduling),也就是按校验矩阵的行块逐层更新,每更新完一层立刻用新消息更新相关变量节点,信息在层与层之间流动更快,同样的迭代次数下能多收敛出0.2到0.3dB的增益,或者说达到同样BLER所需的迭代次数能减少将近一半。

在MATLAB里实现分层译码,本质上就是把H矩阵按Zc行块切分成多个子层,按照基矩阵的行顺序逐层处理。这个改动听起来不大,但涉及消息索引的全面重写,我前后调试了两周才稳定下来。作为过来人的建议是:先在泛洪调度上把BLER曲线调对,再动分层调优,不要一上来就追求最优调度,调试难度会指数级上升。

说了这么多,核心的东西就是这几件事:想清楚BG1/BG2的适用边界,搞定Zc和矩阵扩展,编码可以用工具箱快速起步但一定要有自实现对照,译码直接上归一化最小和,最后用BLER曲线验证一切。这套链路跑通之后,后面再接MIMO检测、信道估计、HARQ重传,都是在同一个框架上增加模块,不会再被编码这块卡住。

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

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

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

立即咨询