☰
5G NR DL-SCH LDPC编码处理链详解与MATLAB仿真实战
2026/10/15 6:21:48 网站建设 项目流程

做5G物理层仿真的朋友,应该都绕不过LDPC编码这道坎。我最初接触5G LDPC的时候,对着协议文档里的基矩阵、循环移位、速率匹配这些概念折腾了很久,尤其是在MATLAB里把整个DL-SCH处理链跑通,踩的坑比想象中多得多。这篇内容就围绕5G NR下行共享信道(DL-SCH)的LDPC编码处理链,结合MATLAB 5G Toolbox里提供的函数,把从传输块(Transport Block)到最终码字的完整过程拆开讲清楚,包括每个环节的原理、工具箱里对应的函数怎么用、以及我在实际调试中遇到的一些容易让人卡住的问题。如果你正在做LDPC相关的链路级仿真,或者刚接触5G物理层想找一个能直接跑的编码参考实现,这篇文章应该能帮你省下不少时间。

1. 为什么5G数据信道偏偏选LDPC:从Turbo到LDPC的换代逻辑

1.1 高吞吐是5G的第一需求

在LTE时代,数据信道用的是Turbo码,性能其实不错,在中低码率下离香农限很近。但Turbo码有个天生的问题——迭代解码的延迟和吞吐量很难做到极致。5G的峰值速率目标比LTE高了一个数量级,要求单用户下行速率达到Gbps级别,Turbo码在这种吞吐压力下,要么需要超大并行度的迭代器,要么延迟会高到影响HARQ调度。

LDPC码在解码上天然适合高并行。它的校验矩阵是稀疏的,BP(Belief Propagation)解码算法可以在多个校验节点和变量节点之间并行更新,硬件实现上用分层调度或全并行架构,吞吐量可以做到很高。这也是3GPP在5G NR标准讨论时,最终选定LDPC作为数据信道编码方案的核心原因——不是Turbo码不够好,而是LDPC在高吞吐、低延迟场景下更有潜力。

1.2 LDPC码的并行解码优势

LDPC解码的核心是消息传递,每个变量节点和校验节点之间交换概率信息。这个结构天然是并行的:所有的校验节点可以同时更新,所有的变量节点也可以同时更新。相比之下,Turbo码的迭代结构是串行的,每次迭代要依次完成前后两个分量解码器的软输入软输出(SISO)计算,虽然也有并行实现,但复杂度高得多。

MATLAB 5G Toolbox里提供的LDPC解码函数,底层实现也充分体现了这种并行优势。不过软件仿真里体现的主要是功能正确性,吞吐量的优势主要还是在硬件实现上体现。但在仿真阶段,LDPC相对Turbo在帧错误率(FER)性能上的表现也是非常重要的参考——尤其是高码率场景下,LDPC的性能可以比Turbo好不少。

1.3 BG1和BG2的设计逻辑

5G NR标准里定义了两套LDPC基矩阵:BG1(Base Graph 1)和BG2(Base Graph 2)。BG1的基矩阵大小是46行68列,适用于大码块和高码率场景,最大码率可以支持到接近1;BG2的基矩阵大小是42行52列,适用于小码块和低码率场景,最低码率可以到1/5左右。

为什么需要两套基矩阵?因为码块长度和码率跨度太大,一套矩阵难以在所有场景下都保持良好性能。BG1在高信噪比、高码率场景下性能更优,BG2在低信噪比、低码率场景下性能更优,两者在中间区域性能接近。实际使用中,如果码块长度超过某个阈值或者码率较高,就选BG1;码块小或者码率低,就选BG2。这个选择逻辑在协议里有明确表格,MATLAB的nrDLSCH系统对象和nrCodeBlockSegmentLDPC函数都内置了自动选择逻辑,但你最好也清楚背后的判断依据,方便调试时排错。

2. 从传输块到码字:DL-SCH的LDPC处理链逐段拆解

2.1 传输块CRC和码块分段

在LDPC编码之前,物理层要先对来自MAC层的传输块(TB)做处理。第一步是加CRC(循环冗余校验),传输块级别的CRC长度为24比特,用于传输块整体的错误检测。CRC的生成多项式在协议里有明确规定,MATLAB里用nrCRCEncode函数就能完成。

加完TB级CRC之后,如果传输块加上CRC的长度超过最大码块长度,就需要做码块分段(Code Block Segmentation)。这里有个容易忽略的细节——分段的数量不是随便定的,要满足一定的约束条件,而且分段之后每个码块都要追加一个24比特的CRC,这是码块级CRC,用于解码时提前判断码块是否正确,方便HARQ合并。

LTE时代码块级CRC长度可以选择0、8、16或24比特,但5G NR统一为24比特。这是因为5G的码块大小跨度大,统一的24比特CRC可以在码块级提供足够可靠的错误检测能力。

2.2 码块级CRC和LDPC编码的衔接

完成码块分段和码块级CRC之后,每个码块就可以交给LDPC编码器了。这里需要注意的是,LDPC编码器的输入长度必须和基矩阵的信息列数匹配。BG1的信息列数是22列,BG2是10列。也就是说,无论码块的实际长度是多少,编码时都要先把数据填充到K个信息比特(BG1为22×Zc,BG2为10×Zc,Zc是提升因子)。

所以码块分段之后还要做一个关键操作——填充比特(Filler Bits)。如果码块长度加上CRC之后不到K,就需要在前面填充比特,这些填充比特在编码后会被打掉,不参与后续的速率匹配和传输。这个点很多人第一次做的时候容易疏忽,后面我会专门讲。

2.3 LDPC编码:系统比特和校验比特

LDPC编码本身在MATLAB里用nrLDPCEncode函数就能搞定。输入是填充后的信息比特序列,输出是完整的编码后比特序列。BG1编码后的码字长度是66×Zc,BG2是52×Zc。

编码后的码字包含两部分:系统比特和校验比特。系统比特就是原始信息比特的副本,校验比特是编码器计算出的冗余信息。5G LDPC的校验矩阵是双对角结构的,这使得编码可以像Turbo码一样用递推方式高效完成,避免了高复杂度的高斯消元,这是5G LDPC选型时的一个重要考虑——不仅要解码高效,编码也不能成为瓶颈。

2.4 速率匹配与比特选择

编码后的码字长度远大于实际可用的物理资源承载能力,所以必须做速率匹配。5G NR LDPC的速率匹配并不是简单的打孔(Puncturing)或重复(Repetition),而是基于循环缓冲区的比特选择。

具体来说,编码后的比特会按照协议规定的顺序写入一个循环缓冲区,这个顺序不是简单的先系统比特后校验比特,而是有一套特定的交织规则。BG1和BG2的比特选择顺序不同,而且要根据冗余版本(RV,Redundancy Version)从缓冲区不同位置开始读取。MATLAB的nrRateMatchLDPC函数封装了整个速率匹配过程,只需要传入编码后的码字、BG类型、速率匹配输出长度和RV值就可以。

速率匹配输出的比特再经过加扰、调制、层映射、预编码、资源映射,最后变成OFDM符号发射出去,这就是完整的下行数据链路。MATLAB 5G Toolbox的nrDLSCH系统对象把从传输块到速率匹配的全过程封装成了一个整体,方便做高层仿真;但如果你要深入学习或者做某些定制化处理,还是有必要自己把每个步骤拆开实现一遍。

3. MATLAB工具箱实操:核心函数用法与参数理解

3.1 nrCodeBlockSegmentLDPC:码块分割函数

nrCodeBlockSegmentLDPC的作用是把带CRC的传输块分割成码块,并完成LDPC编码前的填充。函数的输入是完整的传输块比特(包括TB级CRC),输出是分割后的码块集合,以及相关的元信息(比如每个码块的长度、是否有填充比特、选择的BG类型等)。

这里最关键的是搞清楚函数的两个判断逻辑:

  • 选择BG1还是BG2
  • 确定提升因子Zc

BG的选择依据是码块长度和码率。函数内部会根据输入传输块长度、目标码率等信息自动判断,但你可以在调用前显式指定'TargetCodeRate'参数来控制这个决策。Zc的选择则是基于码块长度查表得到的,标准里定义了8个Zc取值范围,每个范围对应一组提升因子。

3.2 nrLDPCEncode:编码函数

nrLDPCEncode接收的信息比特长度必须是K = 22×Zc(BG1)或K = 10×Zc(BG2),不能多也不能少。这也是为什么在分割时必须做填充操作——如果码块数据不够K比特,就必须填充到K后再编码。

一个常见的困惑是:填充比特填的是什么值?在5G NR中,填充比特的值为0,但它和普通的信息比特0是有区别的。填充比特在编码后会被打掉,它只参与编码计算、不参与传输,这个信息在nrCodeBlockSegmentLDPC的输出带了个参数cbsInfo.NumFillerBits会标注清楚。

编码输出的码字长度是N = 66×Zc(BG1)或N = 52×Zc(BG2)。这个输出是完整的系统比特加校验比特,还没有做速率匹配。

3.3 nrRateMatchLDPC:速率匹配函数

速率匹配函数的输入是编码后的码字、BG类型、期望的输出比特数(也就是这个码块实际能分配到的资源比特数),以及RV值。输出就是经过比特选择和交织后的待传输序列。

RV值有4个选择:0、1、2、3。不同RV值决定了从循环缓冲区的哪个位置开始读取比特。在HARQ重传中,第一次传输用RV=0,重传时按顺序使用后续RV。BG1和BG2的RV起始位置不同,这个函数内部已经处理好了,不用自己计算。

比较容易被忽略的是,nrRateMatchLDPC输出长度不一定是为码块分配的比特数——如果码块有填充比特,速率匹配时会跳过填充位置,所以实际输出比特数可能大于分配的资源比特数?不对,实际输出比特数就等于传入的Nref参数,但里面的内容有微妙之处。这个细节我建议你自己跑一下rateMatchOutput = nrRateMatchLDPC(encoded, bg, Nref, rv),对比一下输入输出长度,能更好地理解填充比特在速率匹配中的处理方式。

3.4 完整的编码循环:从TB到码字

把一个传输块完整处理成待调制比特序列的代码逻辑大致是:

% 1. TB级CRC tbCrc = nrCRCEncode(tbBits, '24A'); % 2. 码块分割 [cbs, cbsInfo] = nrCodeBlockSegmentLDPC(tbCrc, bgn, 'TargetCodeRate', r); % 3. 逐码块编码 modBits = []; for i = 1:cbsInfo.NumCodeBlocks cw = nrLDPCEncode(cbs{i}, bgn, cbsInfo.Zc{i}); % 4. 速率匹配 rmBits = nrRateMatchLDPC(cw, bgn, Nref, rv); % 5. 加扰(伪代码示意) scrambled = nrPuschScramble(rmBits, ...); % 6. 调制 sym = nrSymbolModulate(scrambled, 'QPSK'); modBits = [modBits; sym]; end

这个循环在功能上等价于nrDLSCH的step方法,但拆开写的好处是你可以随时插入调试代码,检查每步的输出是否正确。我自己在开发过程中,就经常在第三步和第四步之间停下来做中间结果校验。

3.5 nrLDPCDecode:解码函数

工具箱里的nrLDPCDecode配合nrRateRecoverLDPC一起使用,接收端做逆操作。nrRateRecoverLDPC把接收到的软比特恢复成完整的码字长度(包括填充位置填充的默认LLR值),然后nrLDPCDecode进行迭代解码。

解码函数最常用的参数是MaxLDPCIterationCount——迭代次数上限。增大迭代次数可以略微改善性能,但会线性增加仿真时间。在实际仿真中,我通常先设成8到10次看整体性能趋势,需要精确的最终性能曲线时再调大到20次左右。需要注意解码结果是软比特(LLR),要转为硬判决比特需要做符号判断。

4. 实战中最容易踩的坑:速率匹配长度、填充比特和RV选择

4.1 速率匹配输出长度怎么定

很多新手在这里犯的第一个错误就是:以为速率匹配输出长度等于编码后码字长度的一半或者某个固定比例。实际上,速率匹配输出长度完全取决于这个码块分配到的物理资源比特数,它和调制阶数、分配的物理资源块数量、符号数、层数都有关系。

在nrDLSCH封装里,这个长度是根据调度参数自动算出来的。手动实现时,你需要先算出总的有效资源比特数,再按比例分给各个码块。这里有个原则——前面的码块可以多分一个比特,后面的码块少分一个比特。这个规则在标准里有明确公式,简单说就是码块之间资源尽量平均、但允许最多相差1比特。

4.2 填充比特在速率匹配时被忽略

BG1的K是22×Zc,BG2的K是10×Zc,但码块数据大概率不等于K。比如一个码块实际数据是4000比特,对应的Zc选出来是256,BG1的K就是5632比特,要填充1632个填充比特。填充比特参与编码,但速率匹配时会跳过这些填充位置。接收端知道哪些位置是填充比特——就是把那些位置的LLR设成一个很大的值,表示"已知为0"。

如果接收端不知道填充比特的数量,把填充位置的LLR按普通数据来解,解码性能会严重劣化。nrRateRecoverLDPC函数返回的``中会标注填充位置,实际使用时要特别注意把这部分信息传给解码器。我自己就遇到过解码性能怎么调都上不去,最后发现是速率恢复时没有给填充位置赋对应的先验信息。

4.3 RV顺序与HARQ的配合

在HARQ重传场景下,RV的选择顺序有讲究。通常第一次传输用RV=0,因为它包含了系统比特和足够的校验比特,可以在信道条件好时一次解出。后续重传依次使用RV=2、RV=3、RV=1。为什么不是按0、1、2、3的顺序?因为RV=1和RV=2在循环缓冲区中的起始位置不同,按0、2、3、1的顺序可以最大化冗余信息的增量。

在MATLAB工具箱里,每次重传只需要改变传给nrRateMatchLDPC的RV值即可。但要注意,如果是模拟系统级调度,你需要自己维护每次传输的RV序号,工具箱不会自动帮你做这个状态管理。

4.4 解码端LLR的缩放问题

nrLDPCDecode的输入是软比特(LLR)。如果你从仿真链路里拿到的是接收符号,要先做解调得到LLR,再做速率恢复。一个非常容易踩的坑是:LLR的幅度没有归一化。不同的调制阶数、不同的噪声方差估计误差,会让LLR的绝对值相差很大,而LDPC解码对LLR幅度是敏感的。

解决方法是做LLR归一化:可以用噪声方差对LLR做缩放,也可以直接用nrSymbolDemodulate的输出并设置'NoiseVar'参数。工具箱里nrSymbolDemodulate支持传入噪声方差,这样输出的LLR就是归一化后的。这个细节在低信噪比、高阶调制(如256QAM)的场景下影响特别明显。

4.5 BG选择不一致导致解码失败

编码端用BG1编码,解码端如果默认选了BG2,结果会完全解不出来。这个错误在手动实现时很容易发生,尤其当你的链路里同时支持多种传输块长度时。

nrCodeBlockSegmentLDPC的bgn参数默认是2,这意味着如果你不加指定,它默认按BG2做分割和填充。例如输入的传输块比较大(超过BG2支持的最大码块尺寸),函数会自动切换到BG1,所以一般情况下自动选择是没问题的。但如果你在编码端显式指定了BG1,解码端的nrRateRecoverLDPC和nrLDPCDecode必须也指定BG1,这依赖你正确传递信号参数。建议在仿真代码里用同一个配置变量控制收发两端的bgn,避免写死。

5. 链路验证方法:从BLER曲线到吞吐量分析

5.1 搭建最小闭环验证链路

无论你最终要实现什么功能,第一步永远是搭一个最小闭环:随机生成传输块 → 编码 → 调制 → 加AWGN噪声 → 解调 → 解码 → 对比误码。这个闭环可以只用上述几个工具箱函数在几十行代码内完成。

验证的几个关键指标:

  • 无误码:加噪后没有任何比特错误
  • 编解码一致性:编码器输出经过解码器之后能还原原始传输块
  • 填充比特位置:确认速率恢复后填充比特位置正确

闭环跑通后,再逐步往上叠加信道模型、MIMO、HARQ等模块。

5.2 按照3GPP参数设置仿真环境

做链路级仿真性能评估时,建议尽量按3GPP定义的标准测试参数走。比如码率、调制方式、信道模型、载波频率、子载波间隔等。MATLAB工具箱里带了nrSimulateDLPerfectChannelEstimate这种参考案例,但代码比较长,初学者直接跑容易头晕。我建议是先用最简单的AWGN做全链路的性能基线,确认BLER曲线的趋势正确,再加入衰落信道。

一个参考的仿真流程:

snrVec = -2:0.5:6; blerResults = zeros(size(snrVec)); for snrIdx = 1:length(snrVec) numBlkErr = 0; numBlkTotal = 0; while numBlkErr < 20 && numBlkTotal < 500 % 生成TB、编码、调制、过信道、解调、解码、CRC校验 % 若CRC校验失败,numBlkErr加1 % numBlkTotal加1 end blerResults(snrIdx) = numBlkErr / numBlkTotal; end

注意BLER曲线统计时,要以传输块的CRC校验结果为准,而不是码块级CRC。因为有些场景下码块正确但传输块错误,这时仍然算一次传输块错误。

5.3 吞吐量和HARQ的仿真注意点

如果你要做HARQ系统的吞吐量仿真,重点不再是单次传输的BLER,而是多次重传合并后的有效吞吐量。MATLAB工具箱里模拟接收端可以保留每次重传的接收LLR,然后做合并解码。

HARQ合并有两种方式:Chase合并(每次发送相同的比特)和增量冗余(IR,每次发送不同的冗余比特)。5G NR默认支持IR合并,也就是每次重传用不同RV。仿真的时候,要注意:

  • 每次重传后LLR合并需要做符号级对齐
  • 速率恢复前要把多个版本的LLR按比特位置叠加
  • 填充位置的LLR始终设为高值

实现细节建议参照工具箱参考案例,不要自己造轮子,这个领域牵扯到的边界情况比想象的多。

6. 性能优化的实用经验:迭代次数、LLR精度与仿真加速

6.1 解码迭代次数的取舍

LDPC解码的迭代次数越多,性能越好,但仿真耗时也越大。我在实际仿真中发现,对于大多数场景,迭代16次和迭代20次的BLER差异小于0.1dB,但仿真时间差约20%。如果你只是做功能验证或算法对比,8次足够;做标准性能评估,建议12到16次,这是性能和速度的平衡点。

工具箱里nrLDPCDecode默认迭代次数是6次,这个值偏少。对于1/3码率以下的场景,6次迭代性能损失可能超过0.5dB。建议根据码率调整:码率越低,需要的迭代次数越多。

6.2 LLR量化精度对性能的影响

硬件实现中LLR通常用有限比特位宽表示(如4比特或6比特),但软件仿真默认用浮点。如果你要模拟硬件行为,可以对LLR做量化。量化对LDPC性能的影响在高阶调制时更明显,因为高阶调制的LLR动态范围更大。

比如256QAM下,如果只用4比特量化LLR,BLER性能可能损失0.3-0.5dB。但6比特量化损失就很小了,基本可以忽略。如果你的目标是评估硬件方案,建议在链路里加上量化模型,否则到硬件验证阶段会吃惊。

6.3 并行仿真与批量处理

LDPC仿真的一个优势是可以并行——多个信噪比点、多个传输块之间完全独立。MATLAB的并行计算工具箱(Parallel Computing Toolbox)可以把循环里的多次传输块处理改成parfor,在物理多核机器上可以线性加速。

需要注意:parfor中不能用带状态的对象,需要把每个迭代完全独立的计算封装成函数。我通常会把"传输块处理"写成一个独立函数:输入是随机种子、SNR、配置结构,输出是CRC校验结果。然后在外层做parfor调用,这样可以兼顾代码简洁和仿真效率。

另外有一个小建议:在仿真时,随机数流可以并行生成,但需要注意各并行任务间不要产生相同的随机序列。用RandStream配合子流分配,保证每个任务使用不同的随机种子,是避免"并行却重复"的关键。

6.4 浮点转定点的快速验证方法

在从算法仿真走向硬件实现之前,通常需要做定点仿真。MATLAB的Fixed-Point Designer工具箱可以和5G Toolbox配合,但直接对所有函数做定点化会很痛苦。我的建议是:先只对解调和LLR部分做定点模型,看性能损失;如果这个损失可以接受,再考虑LDPC解码器内部的定点化。

这是因为解调器和LLR计算的动态范围特性最容易影响性能,而且实现上也是独立模块。LDPC解码内部的消息传递值范围相对稳定,硬件实现通常比解调器还简单。从接口往外层推进,每次只改一个模块的定点模型,这样调试起来效率最高。

7. 仿真实战的技巧:如何排查"解不出来"的问题

7.1 二分法定位编解码链路问题

如果你搭了一个闭环链路,发现解码后CRC校验一直失败,第一反应不应该是怀疑LDPC解码器,而是先用二分法定位问题在哪一段。我自己的排查顺序通常是:

  1. 先检查编码前的CRC:把原始传输块的CRC校验结果打出来,确认CRC编码本身正确。
  2. 再检查编码后速率匹配输出的长度是否和调制/资源映射匹配,长度不匹配通常会直接报错或解调结果异常。
  3. 解调后检查LLR的均值符号是否和发送比特一致——如果在高SNR下LLR符号还经常判断错,说明解调或速率恢复出错。
  4. 最后检查解码器的输入维度是否和nrLDPCDecode要求一致,特别看是否有非法的NaN或Inf。

这个流程能覆盖90%以上的"解不出来"问题。不要一上来就调迭代次数或者怀疑信道模型。

7.2 用仿真日志记录关键中间变量

在实际开发中,调试信息的重要性不亚于代码本身。建议在每个模块边界打日志,记录输入输出长度、CRC是否通过、填充比特数量、RV编号。把这些工作做到位之后,排错基本就是看日志找哪一步不对的问题。

这里分享一个我用的日志模板:

[TB] TB length = 10000, CRC pass = yes [CBS] NumCodeBlocks = 3, BG = 1 [CBS #1] K = 4096, filler bits = 96, Zc = 128 [ENC #1] output length = 8448 [RM #1] Nref = 7000, RV = 0, output length = 7000 [MOD] QPSK, num symbols = 3500

通过这些信息,你可以很快定位到长度不匹配、填充错误、RV设置异常等问题。

7.3 对比工具箱参考实现的输出

如果你在实现过程中不确定某一个模块的输出是否正确,最直接的方法是调用nrDLSCH系统对象做相同参数的完整流程,然后对比你自己的分步实现的中间结果。nrDLSCH是经过验证的参考实现,它的分步处理结果可以作为ground truth。当然,这个对比只用于调试,仿真性能评估时还是要用自己拆开的实现。

对比时要注意比特序的问题——5G里CSI(码字-层映射)、加扰、调制过程中存在比特顺序的重新排列,容易造成"看起来相同、实际上顺序反了"的情况。对比之前先在发送端每个模块打点,确认每个模块的输出完全一致,再往下走。

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

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

立即咨询