5G NR LDPC码MATLAB仿真:从基图展开到性能曲线验证

发布时间:2026/10/14 4:51:21
5G NR LDPC码MATLAB仿真:从基图展开到性能曲线验证 5G物理层算法仿真的热度这两年明显起来了尤其LDPC码这块几乎成了从4G转向5G的通信工程师必啃的一关。做MATLAB仿真实现时很多人的第一反应是翻开3GPP标准文档定位到TS 38.212的相关章节然后面对一屏幕的-1、0和乱七八糟的两位数表格发呆——这玩意叫基图Base Graph既不是可以直接拿来算的0-1校验矩阵也不是Turbo码的生成多项式根本没法直接丢给MATLAB去验证mod(H*c,2)。这篇文章就是我基于自己做5G LDPC链路仿真的实操经验整理出来的会从基图翻译、H矩阵构造、编码器实现、min-sum解码一路讲到性能曲线验证目标是让你照着这篇就能跑通一个不依赖工具箱的5G NR LDPC编解码Demo并且每一步都知道它在干什么、为什么这么干。1. 5G NR LDPC和LTE Turbo到底差在哪搞懂这三件事再写代码1.1 为什么5G不沿用Turbo而是选择LDPCLTE时代Turbo码用了十多年性能确实不差但在5G的高吞吐场景下有一个致命问题并行度难做。Turbo译码依赖前后向递推BCJR/log-MAP天然是串行结构想提高吞吐要么增加迭代次数换延迟要么堆硬件资源做多个SISO模块拼接复杂度会迅速失控。而LDPC是线性分组码只要校验矩阵具有准循环QC-LDPC结构译码器就能把整个矩阵切成若干个子块并行更新硬件实现友好得多。更重要的是5G标准的应用范围更宽从eMBB的大包、高码率到URLLC的小包、低码率都要覆盖。LDPC天然支持“删余打孔”“重复发送”这些速率匹配手段3GPP又专门设计了BG1、BG2两张基图来应对不同块长一张面向大码块一张面向小码块和低速率场景。所以从仿真角度讲你不能再像LTE那样背一个固定生成矩阵而要学会“按参数动态生成校验矩阵”的套路。1.2 准循环结构让“校验矩阵”变成了一张数字表格LDPC的本质还是一组线性校验方程对于码字c要求H乘以c的转置在GF(2)上等于全零。但5G的H矩阵不是随便设计的一堆0和1而是用“基图BG矩阵数值”压缩表示的。基图里每个元素p对应一个Z×Z的子矩阵p -1表示一个全零子矩阵p 0表示单位阵p 0表示单位阵循环右移p位得到的置换阵。整个校验矩阵H的规模是mb×Z行、nb×Z列。比如BG1有68列如果Z384展开后就是17664×26112的稀疏矩阵。这个量级用全元素矩阵存肯定不行但用循环移位子块来构造就非常方便MATLAB里只需要对eye(Z)做一次circshift再放进sparse大矩阵的对应位置即可。1.3 信息比特、校验比特、码字的摆放顺序很多人第一次写LDPC仿真默认“把K个信息比特直接丢给编码函数得到N个码字比特”就完事了。5G NR不是这种教科书逻辑。标准里码字结构是前部分为系统比特即信息比特后面接校验比特但因为输入K不一定刚好等于信息列数乘以扩展因子Z编码前需要先做填充操作。假设你选BG1信息列数是22扩展因子定为Z那么编码器实际处理的信息比特长度是22×Z。如果实际K小于它就需要在系统比特的开头或特定位置补F个已知比特通常补0让总长度对齐22×Z。这些填充比特参与编码但不会进入速率匹配也就是说它们不占实际传输资源。如果仿真时忘了这一步你会发现编码器报错、输出长度对不上、解码后信息比特全部错位。最坑的是解码端填充比特的LLR不是从信道得到的而是应该直接设成一个绝对值很大的已知值比如±1000告诉译码器“这些位我有十足把握”。如果你把填充位当成信道比特来算LLR那某几个SNR点上的增益可能会莫名其妙丢失。2. 从3GPP TS 38.212到MATLAB基图、扩展因子和索引的“翻译”过程2.1 BG1和BG2怎么选仿真刚开始先别纠结哪个基图性能更好先确定你用哪一张。实践中主要看信息比特长度K大码块、高码率优先用BG1支持最大信息比特8448校验列多、码字最长可达68×Z小块、低码率优先用BG2支持最大信息比特3840系统列只有10列基图规模小很多。标准文档里有更细致的选择条件涉及K和码率的组合但我们做单链路仿真时完全可以固定一张基图来验证算法流程。我更推荐调试阶段先用BG2因为基图小、展开后的H矩阵也小跑一次译码很快逻辑通顺后再切到BG1验证大块场景。这里直接给一张参数对照表方便跟你手里的标准表格对应参数BG1BG2系统列数 kb2210校验行数 mb4642总列数 nb6852最大信息比特84483840适用场景大块、高速率小块、低速率2.2 扩展因子Z的确定与填充比特计算确定Z的规则说简单也简单从标准给定的候选集合里选一个最小的Z使得kb × Z ≥ K。举个例子假设K1000使用BG1kb22。22×45990不够22×461012又太大实际候选集合里Z48就是满足条件的最小扩展因子22×481056。那填充比特数就是1056-100056。标准里的Z候选集合是设计过的比如Z7、8、9、10……一直延伸到384中间不是连续整数而是按2的幂次和一些乘积关系排出来的。实操中你不需要背表直接把候选集合当作数组存在脚本里然后从小到大遍历找第一个满足条件的即可。Zcand [2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 20 22 24 26 28 30 32 36 40 44 48 52 56 60 64 72 80 88 96 104 112 120 128 144 160 176 192 208 224 240 256 288 320 352 384]; function [Z, F] selectZ(K, kb, Zcand) idx find(Zcand * kb K, 1); Z Zcand(idx); F kb * Z - K; % 填充比特个数 end注意如果你用BG2kb10那么K3840时恰好Z384F0没有填充。但K比较小比如K100时10×Z要覆盖100Z至少要10填充20比特解码端这些位置要“人工置信”。2.3 把基图展开成完整校验矩阵MATLAB实现展开H矩阵的核心代码其实很短。假设你已经把基图存成了二维数组bgMat元素是标准表格里的数字那么这样就能得到完整的稀疏校验矩阵function H buildH(bgMat, Z) [mb, nb] size(bgMat); H sparse(mb*Z, nb*Z); for i 1:mb for j 1:nb p bgMat(i, j); if p 0 sub circshift(eye(Z), p, 2); % 循环右移p位 rowIdx (i-1)*Z (1:Z); colIdx (j-1)*Z (1:Z); H(rowIdx, colIdx) sub; end end end end这里必须提醒你注意方向问题。3GPP标准里定义的是循环移位但具体是循环左移还是右移不同文档翻译不一样最可靠的验证方式是构造一个全零输入编码后检查mod(H*c, 2)是否全为0。如果方向反了校验方程百分之百不满足你就能立刻发现。索引也是我踩过坑的地方。基图表格里行号、列号通常从0开始俗称C语言习惯MATLAB是1开始。你要是把表格数字直接当作MATLAB下标去填少说也会偏一行。稳妥做法是写完buildH后立即对几个小Z值做单元测试用手算的方式确认子矩阵位置。3. 编码器实现扩展H矩阵与快速编码两种路线怎么选3.1 路线一直接利用校验矩阵求校验位拿到完整的H矩阵后理论上编码可以这样表述已知信息比特向量s要求校验比特p满足H × [s; p]^T 0在GF(2)域上。如果把H写成[A B; C D]的分块形式那么可以通过高斯消元解出p。这个思路直观适合验证但实际工程中我不会拿它编码。原因很简单BG1完整展开后有17664行、26112列做一个GF(2)高斯消元复杂度接近O(N³)MATLAB跑小规模还行跑到8448比特的码块会非常吃力内存也紧张。所以直接法只适合一个场景调试验证你拿一个很小的Z构造H手动验证编码器输出是否满足校验方程。实际仿真里请直接用下一节的快速编码思路或者先调用官方5G Toolbox的编码器跑通链路再回头替换成自己的快速实现。3.2 路线二利用5G基图的双对角结构做递推编码5G LDPC基图的设计是有深层考虑的。校验矩阵的设计使得前两列或者对应特定列具有较高的列重而后面的校验部分带有明显的双对角/阶梯结构。利用这个结构可以逐列递推得到校验位计算复杂度从O(N³)降到O(N)而且不需要显式构造完整H矩阵参与运算内存占用小得多。快速编码的基本思路可以这样理解把基图按系统列和校验列分块利用右下角接近下三角的结构从最后一行往上回溯每次求出一个Z长度的校验比特子块。前面那两个特殊列相当于“启动变量”先要利用它们的约束关系求出初值再进入主递推循环。我不建议你在没有理解基图索引的情况下直接抄递推公式因为下标错一位整个结果就乱。我更推荐的做法是先用buildH展开一个小的H矩阵然后在MATLAB调试器里对比你的递推中间变量和直接解方程得到的校验比特逐列对齐。等小规模完全一致再放开到BG1全尺寸。3.3 速率匹配不是“删几个校验比特”那么简单编码器的输出码长是nb×Z但5G实际发送长度E一般由目标码率R决定RK/E。5G采用循环缓冲区速率匹配编码比特进入一块长度为Ncb的缓冲区根据冗余版本RV确定起始点k0然后从k0开始连续读取E个比特如果不够长就循环绕回继续读。这个机制跟LTE的“先读系统、再读校验、按比例打孔”完全不同。初学仿真时最省事的做法是固定RV0k00目标码率对应E取整后直接从编码输出开头截取E个比特如果E超过N就循环取。但你要知道这只是简化没有反映5G真正的前2Z比特打孔策略。5G LDPC设计里有一部分系统比特在速率匹配中被有意置于循环缓冲区的特定位置实际发送时不一定完整发送所有系统比特。这是我最初实现时最容易翻车的地方直接发完整码字低码率下观察到的BLER曲线始终比预期差0.5dB以上后来对照标准速率匹配规则修正后才恢复正常。所以我的建议是链路仿真可以把速率匹配先做“无打孔传输”版本编码器输出全码字通过AWGN信道到译码器先把编解码本身的正确性验证掉等这一步没问题再引入循环缓冲区和RV机制性能曲线的瀑布区位置才可信。4. 解码器实现LLR怎么进、消息怎么传、迭代怎么停4.1 信道模型与LLR初始化仿真的信道模型我从最简单的开始BPSK调制AWN信道。编码比特c∈{0,1}映射成符号x如果c0映射为1c1映射为-1。接收信号yxn噪声方差σ²N0/2。LLR的定义是后验概率的对数比对BPSK-AWGN有非常紧凑的公式LLR 2y / σ² 4y / N0因为N0是单边噪声功率谱密度σ²N0/2。这里特别容易犯的错是符号映射方向反了比如0映射-1、1映射1这时公式要整体取负号。我建议在代码里显式写清楚映射关系而不是依赖脑内约定。% 发送端 x 1 - 2*c; % 0 - 1, 1 - -1 % 接收端 snrLin 10^(EbNo_dB/10); N0 1 / (snrLin * codeRate); % 注意这里用的是每信息比特 sigma2 N0/2; llrIn 2*y / sigma2; % 即4y/N0解码输入需要一个完整码字长度nb×Z的LLR向量。这里有个关键步骤速率匹配时没发送的比特译码器并不知道这些位置的LLR要置0表示“等概率、无信息”。填充比特则相反要设置成带符号的大值比如±1000。如果打孔位置不置0而噪声累加了一个随机值相当于给译码器喂了假信息性能会非常差。4.2 校验节点更新的min-sum实现完整的置信传播需要使用tanh域或对数域消息更新计算涉及双曲函数MATLAB跑起来慢而且存在数值溢出风险。工程实现几乎都用Min-Sum近似。我先解释一下校验节点更新的物理含义一个校验节点告诉相邻变量节点“我这边的校验方程是否满足以及你偏离可靠值的程度”。Min-Sum近似后的规则是输出消息的符号 所有输入消息符号的连乘输出消息的幅度 所有输入消息幅度中的最小值。这个近似比标准BP损失一点点性能通常0.2~0.4dB但计算量小、实现稳定。实际仿真里可以再加一个归一化因子α把输出幅度乘上0.75~0.83再继续传递能在很大程度上弥补Min-Sum的损失。以下是校验节点更新的核心骨架% vMsg: 变量节点发给校验节点的消息矩阵尺寸 MxN非连接位置为0 function cMsg cnodeUpdate(vMsg, H, alpha) [M, N] size(H); cMsg zeros(M, N); for m 1:M idx find(H(m,:)); % 该校验节点连接的变量节点 if isempty(idx), continue; end msgs vMsg(m, idx); signsProd prod(sign(msgs)); minAbs min(abs(msgs)); for j idx otherMin getSecondMin(vMsg(m, idx), find(idxj)); cMsg(m, j) alpha * signsProd * sign(vMsg(m, j)) * otherMin; end end end这里一个小技巧处理器取最小值时如果最小值恰好出现在当前要更新的边上不能直接用全局最小值而要取次小值否则会把当前边的信息重复反馈给自己造成正反馈错误。这也是新手最容易忽略的“隐形坑”。4.3 变量节点更新与停止准则变量节点更新的逻辑简单很多某一变量节点接收到信道LLR和所有连接校验节点的消息把除了回传方向之外的所有外部消息加起来再作为新消息发出去。totalLlr llrIn sum(cMsg(:, j), 1); vMsg repmat(llrIn, M, 1) (sum(cMsg, 2) - cMsg); % 对应列广播这里建议用翻转图Tanner图的矩阵表达让消息沿H的非零位置流动。消息初始化时变量节点发给每个校验节点的消息都等于该比特的信道LLR。迭代一次之后再做硬判决cHat double(totalLlr 0); % 若映射为0-1则LLR0判1 if mod(H * cHat, 2) 0 break; % 全部校验方程满足提前收敛 end迭代次数的经验值方面我测试的结论是高码率大码块通常8到15次就能收敛低码率小码块需要30到40次。仿真时最大迭代次数设50比较稳妥再往上对BLER曲线影响很小只是平白增加耗时。如果加了归一化因子α曲线整体会往下掉零点几个dB值得调整到最佳值。5. 仿真结果验证与调试画性能曲线时最容易栽的几个坑5.1 先把Eb/N0换算搞清楚否则噪声根本没对准很多人在这一步出错导致曲线整体平移好几个dB却不自知。仿真中通常先确定Eb/N0每信息比特噪声比再换算成符号信噪比。BPSK下每个符号承载一个比特但因为有速率匹配一个重要关系是Es/N0 Eb/N0 × R其中RK/E是实际编码码率。你用目标码率RK/E所以加噪时噪声方差N0 1 / (10^(Eb_N0_dB/10) × R)注意如果你把E误用成完整码字长度nb×Z而实际发送比特E小于它那噪声会被加小曲线就虚高。我测试时发现这种错误会让性能曲线比真实情况好0.5~1dB非常有欺骗性。画BLER曲线的蒙特卡洛统计也需要有点耐心每个SNR点至少要统计到几十上百帧错误否则曲线起起伏伏根本看不出瀑布区。我习惯设一个“至少100帧错误或最多10万帧”的双重停止条件既保证精度又控制仿真时间。5.2 我踩过的三个“伪正确”陷阱现象根因对策译码器明明收敛校验方程全零但信息比特仍错把填充比特算进了信息比特统计或打孔位置LLR没置0译码输出后先删除F个填充位再做BER/BLER统计编码器输出和标准参考始终对不上但自己的校验方程却通过circshift方向或索引与标准不一致用全零码字测试mod(H*c,2)0再对单个子块手算移位方向性能曲线低SNR区域很好高SNR区域却出现地板效应速率匹配未对齐或者迭代次数过少先跑全码字无打孔版本验证链路正确后再引入速率匹配最大的“伪正确”是只看自己人收敛。LDPC校验方程全零只能说明“这是一组合法码字”不代表它等于发送的码字。如果填充比特位置的小概率事件处理不当或者打孔位置被误当成已知信息就可能出现多组合法码字冲突的假象。所以我最终都以CRC或直接比对该信息比特是否一致作为成功判据而不单单看校验和。5.3 调试技巧从最小例子开始逐级验证具体到MATLAB调试我推荐按这样的顺序做先用BG2、Z7这样的小参数跑一遍基图小H矩阵只有几十行中间变量随便打印出问题一眼能看出来。编码器自检随机生成信息比特编码后验证mod(H*c,2)0这一步过了才有资格谈性能。无噪声验证把LLR设成极大值译码器应该第一次硬判决就能得到原码字。同参数对比如果手边有5G Toolbox或者可用的参考实现把BLER曲线叠在一起画正常情况差异应在0.2dB以内如果差异大于0.5dB优先怀疑速率匹配和填充比特的LLR处理而不是解码算法本身。固定随机数种子调试时我习惯把随机种子固定下来确保每次跑同一参数结果可复现这样改一行代码就能对比前后差异。最后再分享一个实践经验。我在做LDPC仿真时习惯把编码、速率匹配、信道加噪、解速率匹配、译码几个模块接口定义成统一的“LDPC链路函数”输入参数只有K、E、基图编号和SNR点。这样换参数跑批量仿真非常方便也方便后续引入HARQ重传机制时做模块化扩展。LDPC这东西没有太多花哨技巧把每一环的比特流对齐了性能曲线自然会落在该在的位置。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询