ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

5G NR中的LDPC码:从基矩阵构造到译码实现

5G NR中的LDPC码:从基矩阵构造到译码实现 1. LDPC码的前世今生5G为什么抛弃Turbo码很多刚接触5G NR物理层的小伙伴看到一堆LDPC、Polar、LDPC base graph的术语第一反应都是懵的。4G时代明明还在用Turbo码到了5G怎么突然全换了其实这个问题的答案恰恰是理解LDPC设计原理的一把钥匙。1.1 从Turbo到LDPC三大不可调和的矛盾先说Turbo码为什么被替换掉。Turbo码在3G、4G时代统治了将近二十年性能确实能逼近Shannon极限但它有两个致命的工程问题一是并行度受限二是错误平层error floor偏高。Turbo码的译码是基于BCJR算法的迭代译码但它依赖两个分量译码器相互交换软信息整个译码过程本质上是一个串行迭代结构。你可以把Turbo译码想象成两个人轮流猜一个密码一个人猜完交给另一个人接着猜速度上限受限于两个人的交接次数。即使做了并行窗口优化它的并行粒度也比LDPC的消息传递结构粗得多。到了5G的eMBB场景峰值速率要求10Gbps甚至20Gbps以上Turbo码的量级已经很难撑住这种吞吐需求。第二个问题是错误平层。Turbo码在中低信噪比区域表现极好但一旦误码率达到某个阈值曲线就会变平坦继续加大信噪比也压不下去。这对数据信道是致命的——很多业务要求误块率BLER做到1e-6甚至更低Turbo码的平层让这个指标很难保证。第三个矛盾是灵活性。5G NR要支持从几米到几百公里的覆盖、从物联网传感器到毫米波大带宽的各种场景码率和码长需要大范围可调。Turbo码的设计参数一旦定死换码率、换码长就得换交织器、换打孔模式实现成本很高。1.2 LDPC的翻盘稀疏矩阵带来的工程红利LDPC码是1963年Gallager在博士论文里提出的但被遗忘了三十多年直到1996年MacKay和Neal重新挖掘出来。它的核心定义不复杂一个线性分组码如果校验矩阵H是稀疏的绝大多数元素是0对应的码字集合{c | H·c^T 0}就是LDPC码。这里稀疏两个字是LDPC所有好处的源头。因为H矩阵稀疏每个校验方程只涉及少数码字比特译码器可以用置信传播Belief PropagationBP算法做并行消息传递每个校验节点和变量节点可以并行更新。并行度天然就是整个矩阵的规模这在硬件上意味着可以用大量简单处理单元同时干活吞吐能力远远超过Turbo码。而且LDPC在设计得当的情况下错误平层可以压得非常低。只要在设计阶段对环长girth和最小距离做约束把Tanner图中的短环排除掉LDPC码在低误码率区域的性能就能保持稳定下降不会出现Turbo码那种压不下去的尴尬。5G NR最终把LDPC用作数据信道的编码方案把Polar码用于控制信道就是基于不同信道对时延、吞吐、可靠性的需求做的分工。数据信道承载大块业务适合LDPC的高并行度控制信道短小且对时延和可靠性要求极高Polar码的极化结构更适合编译码的低时延需求。如果你把信道编码想象成一场接力赛Turbo码是两个人轮流跑LDPC是几百个人同时各跑一段再汇总结果——谁更适合高速公路一目了然。2. 5G NR的LDPC不是随便一个LDPCQC-LDPC与基矩阵设计前面说了LDPC的好处但稀疏矩阵这四个字太泛了。理论上任意一个稀疏矩阵都能定义一个LDPC码但在工程上随机生成的稀疏矩阵根本无法落地。你可能听说过随机LDPC码性能更好这种说法但在5G标准里最终选用的却是结构化程度极高的准循环LDPCQuasi-Cyclic LDPCQC-LDPC。这不是性能妥协而是对可实现性的深刻考量。2.1 为什么不用随机矩阵存储、寻址与并行的三重暴击随机LDPC码在理论性能上确实不错特别是码长很长的时候随机性带来的性能增益很明显。但一个真实的物理层芯片要去实现它会遇到三个问题。第一是存储。一个50000比特码长的LDPC码校验矩阵动辄上万行乘几万列哪怕只有1%的元素是1也要存几百上千万个索引。5G NR基带芯片里没有这么多存储去放一张随机表。第二是寻址。随机矩阵中每个校验节点的相邻变量节点在内存里是完全分散的译码时读内存会频繁跳地址缓存命中率极低。通信系统对时延敏感这种不可预测的内存访问模式对实时性是灾难。第三是并行度。随机矩阵的并行单元之间的连接关系也是随机的硬件布线的走线长度差别巨大某些路径成为关键路径整个译码器的时钟频率被拖慢。QC-LDPC的核心思路是把一个大矩阵组织成规则的块状结构每个块都是一个循环移位矩阵。这样存储只需要存一个很小的基矩阵Base Graph把所有循环移位值存下来硬件里用循环移位寄存器做展开寻址规律、布线规则全都能确定化。2.2 矩阵分块与循环移位QC-LDPC的机械美学一个QC-LDPC的校验矩阵H可以看作一个M×N的块矩阵每个块的大小是Z×ZZ被称为扩展因子lifting factor。每个Z×Z块要么是全零矩阵要么是单位矩阵的循环右移版本。举个例子Z4时一个循环移位量为1的块长这样0 1 0 0 0 0 1 0 0 0 0 1 1 0 0 0每个非零块的循环移位值就构成了一个M行N列的基矩阵表。这张表里有很多元素是-1表示对应的Z×Z块是全零矩阵非负数表示循环移位量。5G NR标准里的38.212协议5.3.2节就给出了两张基矩阵表BG1Base Graph 1和BG2Base Graph 2。这种设计的妙处在于编译码器只需要存储基矩阵这张小表和Z的取值运行时把每个元素展开成Z×Z的循环阵得到完整的校验矩阵。Z值可以动态变化基矩阵稍加调整就能适配不同信息长度——这就是5G NR支持超宽码长范围的核心机密。2.3 基矩阵BG1与BG2两种档位的设计哲学5G NR不像4G那样为每种码率单独设计一套码而是用两张基矩阵覆盖所有场景。你可以把BG1和BG2理解为两种预设档位。BG1用于大码块信息长度范围从几十比特到8448比特码率可以高到8/9设计目标是大块数据传输时的高吞吐和高码率性能。BG1的基矩阵大小是46行68列其中系统列信息比特对应列是22列校验列是46列整体码率上限大约在22/25附近。通过速率匹配还可以得到更低的码率。BG2用于小码块和低码率场景信息长度最大3840比特码率可以低到1/5适合物联网、控制信令等对鲁棒性要求高的场景。BG2的基矩阵大小是42行52列系统列是10列校验列是42列。选择规则在协议里写得很清楚如果信息比特数A ≤ 292或者 (A ≤ 3824 且 码率R ≤ 0.67)用BG2否则用BG1。这个规则本质上是在做性能与开销的取舍——码块小的时候用更大的冗余比例保住可靠性码块大的时候用更紧凑的结构撑住速率。用一句话总结BG1是为大水管准备的追求过水量BG2是为细水管准备的追求不漏水。3. 从设计图纸到可用的H矩阵实操构造流程理解了基矩阵的来龙去脉接下来进入真正的实操环节——把协议表变成可以直接跑仿真的校验矩阵。这一步看似简单里面全是细节稍不留神就会出现矩阵乘出来不为零的诡异现象。3.1 两件事先定下来扩展因子Z_c和偏移i_LS拿到一个物理层传输任务第一件事是从传输块大小TBS、码率R、资源分配等信息里确定信息比特数A和编码后的总比特数N然后根据BG选择规则决定用BG1还是BG2。接下来需要确定扩展因子Z_c。协议38.212的Table 5.3.2-1里有一张从Z_c到基矩阵列数范围的映射表。Z_c的取值不是随便选的它是一组特定整数的列表每一个都满足Z_c a × 2^j的形式其中a是某个奇数值比如2、3、5、7、9、11、13、15这样的数。列出部分Z_c取值你会发现规律非常清晰Z_c集合起始a值典型取值示例2, 4, 8, 16, ...a12, 4, 8, 16, 32, 64, 128, 2563, 6, 12, 24, ...a33, 6, 12, 24, 48, 96, 1925, 10, 20, 40, ...a55, 10, 20, 40, 80, 160每个可能的信息比特长度A都能匹配到某个Z_c使得系统列数量乘以Z_c能容纳下信息比特再取最小满足条件的Z_c。这个Z_c会决定基矩阵里每个循环移位块的尺寸也决定了最后H矩阵的总维度。第二步是确定基矩阵元素的偏移值i_LS。协议里给出的基矩阵表是5G NR专用表每个BG表有两套偏移值表——一套对应扩展因子集合的某个子集。Z_c不同同一位置的循环移位量可能不同。38.212里为每个BG提供了多组偏移值根据Z_c落在哪个集合来选偏移表。这段逻辑在协议里写得很隐晦但实际做的时候大部分实现库比如5G相关开源库都已经处理好了。如果你是第一次不看库源码自己撸一定要确认同一张基矩阵Z_c2和Z_c4对应的是不同的偏移表不能混用。3.2 核心步骤把基矩阵展开成H矩阵构造H矩阵的基本流程可以拆成四步读取选中的BG矩阵BG1或BG2矩阵大小记作M_b×N_b每个元素为V_{i,j}。根据信息长度选出Z_c。对每个非-1元素V_{i,j}生成一个Z_c×Z_c的循环单位阵循环右移V_{i,j} mod Z_c位对-1元素生成Z_c×Z_c全零阵。用生成的块矩阵替换基矩阵的每个元素最终得到完整校验矩阵H维度是(M_b·Z_c) × (N_b·Z_c)。这里最容易出错的地方是循环移位方向。5G NR协议里说的偏移值和很多教科书里说的左移方向可能不同。协议里的V_{i,j}意味着生成矩阵时把单位矩阵的第k行放到第(kV_{i,j}) mod Z_c行或者说把单位的行向右循环移V_{i,j}位。实际写代码时先用Z_c4的简单例子人工验证一遍再跑大规模仿真。下面给一段可用的Python构造代码生成BG对应的H矩阵import numpy as np def build_ldpc_matrix(base_graph, k_value, rows, cols): 根据基矩阵和扩展因子构造QC-LDPC校验矩阵 base_graph: list[list[int]], 基矩阵, -1表示零块 k_value: 扩展因子 Z_c H np.zeros((rows * k_value, cols * k_value), dtypenp.int8) for i in range(rows): for j in range(cols): v base_graph[i][j] if v -1: # 全零块 continue shift v % k_value for r in range(k_value): col_idx (r shift) % k_value H[i * k_value r, j * k_value col_idx] 1 return H构造完H矩阵后第一件事就是验证。随便取一个满足H·c^T0的码字c做乘法确认结果是零向量。这一步如果不对后面所有调试都会白做。3.3 速率匹配不是随便打孔NR里打孔的优先级规则基矩阵本身是固定码率的BG1最快的码率约8/9BG2约1/5。但实际传输需要对任意码率工作所以5G NR引入了基于比特级选择的速率匹配机制。简单说就是先打好一个完整码字再从码字里选比特发送选哪些、不选哪些这背后有讲究。速率匹配有两个层次。第一层是打孔puncturing对系统比特、校验比特按优先级排序优先打掉那些对译码最不重要的比特。NR里有个基本规律校验比特里前面若干个校验列在打孔时会被优先剔除因为这些比特对应的校验约束在构造时被设计成可以通过其他校验方程恢复。换言之打掉它们对性能影响最小。第二层是循环缓冲circular buffer与有限缓冲速率匹配FBRM。所有编码后的比特按顺序写入一个环形缓冲区根据实际分配的物理资源大小从某个起始位置连续读取所需比特。如果分配的比特数超过缓冲区长度就循环读取。在实际仿真或硬件实现里速率匹配的顺序和起点不是随便选的。起点由冗余版本RV, Redundancy Version决定RV值有0、1、2、3四种每次重传选不同的RV起点相当于从码字的不同位置取比特。这保证了HARQ重传时前后传的比特尽量不重复提升合并增益。你在看协议表的时候会发现系统比特在打孔段已经被保护好了所有RV的起点都设计在系统比特之后目的就是保证不管从哪个RV开始发系统信息都被完整保留。个人建议如果你要实现速率匹配模块先把哪个RV起点对应缓冲区的哪个偏移搞清楚这个映射在38.212的5.4.2.1节有表。很多人调试半天性能上不去就是RV起点对错了。4. 译码器视角H矩阵怎么用性能才能不翻车构造出H矩阵只是第一步真正的难点在译码器的设计与调试。很多同学在做LDPC译码仿真时会遇到一个尴尬情况H矩阵明明没问题但误码率就是压不下去。问题往往不出在编码而出在对H矩阵的使用方式上。4.1 置信传播译码H矩阵如何转化为性能LDPC译码最经典的方法是置信传播算法。它的本质可以理解成Tanner图上的节点之间互相传递靠谱程度的信息。先把H矩阵画成Tanner图每一列是一个变量节点对应一个码字比特每一行是一个校验节点对应一个校验方程。第i行第j列为1就在校验节点i和变量节点j之间连一条边。译码开始时变量节点从信道拿到初始软信息LLR对数似然比然后在边上反复传递。BP算法里的关键操作有两个校验节点更新对每个校验节点收集所有相邻变量节点的LLR通过校验和逻辑更新外信息本质上计算的是已知其他比特的取值倾向时这个比特应该取什么值。这一步可以用Box-plus运算也可以用最小和min-sum近似。变量节点更新对每个变量节点把信道信息和从所有相邻校验节点收集来的外信息加起来得到这个比特的后验LLR。迭代若干轮后把每个变量节点的LLR硬判决成0/1再验证是否满足所有校验方程。如果满足译码成功。这整个过程中H矩阵的稀疏性直接决定译码器的路由复杂度和每轮的运算量。稀疏性越高每个校验节点需要处理的信息越少每轮迭代的时延越低。这也是为什么基矩阵里大量-1元素是好事——它们意味着译码器里对应块之间的连线可以直接省掉。4.2 归一化最小和工程上用性能换复杂度严格的BP算法涉及tanh和atanh运算在硬件上开销极大。5G NR商用芯片的LDPC译码器几乎清一色用最小和算法的变体——归一化最小和Normalized Min-Sum。归一化最小和的核心思路是把校验节点更新的复杂运算替换成取最小值和次小值最后乘一个小于1的归一化因子。为什么参数叫归一化因子而不是缩放因子因为它在数学上是在补偿min-sum近似对BP外信息的系统性高估。实际常用的归一化因子大概取值在0.75左右适配不同码率时略有微调。用Python实现一个最简化的归一化最小和译码器也就几十行def normalized_min_sum_decode(H, received_llr, max_iter10, alpha0.75): num_vars H.shape[1] num_checks H.shape[0] # 初始化变量节点消息等于信道LLR var_msg received_llr.copy() check_msg np.zeros((num_checks, num_vars)) # 为每行记录校验节点连接的变量索引 row_indices [np.where(H[i, :] 1)[0] for i in range(num_checks)] col_indices [np.where(H[:, j] 1)[0] for j in range(num_vars)] for _ in range(max_iter): # 变量节点到校验节点消息 for i in range(num_checks): indices row_indices[i] msgs var_msg[indices] - check_msg[i, indices] # 计算最小值和次小值做归一化最小和 min1 np.inf min2 np.inf min_idx -1 for n_idx, m in enumerate(msgs): abs_m abs(m) if abs_m min1: min2 min1 min1 abs_m min_idx n_idx elif abs_m min2: min2 abs_m signs np.prod(np.sign(msgs)) for n_idx, m in enumerate(msgs): if n_idx min_idx: new_msg alpha * signs * np.sign(m) * min2 else: new_msg alpha * signs * np.sign(m) * min1 check_msg[i, indices[n_idx]] new_msg # 校验节点到变量节点消息合并 for j in range(num_vars): var_msg[j] received_llr[j] np.sum(check_msg[col_indices[j], j]) # 硬判决并验证 decoded (var_msg 0).astype(int) if np.all(H decoded % 2 0): return decoded, True return decoded, False这里为了做教学演示逻辑非常简单真实硬件实现要复杂得多。但请注意H decoded % 2 0这个验证步骤一定不能省它是判断译码是否收敛的唯一标准。4.3 实际调试中的三个隐藏性能杀手调试LDPC译码器时我发现新手最容易踩三个坑它们都发生在H矩阵之外的细节里。第一个是LLR的计算方式。很多教程直接用BPSK调制下的单一LLR公式但5G NR的LDPC映射到的是QPSK、16QAM等高阶调制一个符号承载多个比特LLR计算必须考虑所有的星座点。用简化的高斯近似去算高阶调制下的LLR会直接吃掉0.5到1dB的性能。第二个是迭代次数的设置。最小和算法在迭代到4到6次之后性能曲线趋于饱和继续增加次数对性能增益极小但功耗和时延直线上升。如果你的接收机预算很紧优先考虑把迭代次数压在8次以内。第三个是归一化因子alpha的取值。alpha0.75是很多论文的默认值但实际调测时最好对不同码率做一次小扫描。我自己的经验是低码率时alpha取大一点0.8附近高码率时取小一点0.7附近整体性能比固定值好不少。这个调优过程不复杂但效果很直接。5. 常见问题与排查技巧实录LDPC相关的报错和性能问题翻来覆去就那几类。我把自己这几年在仿真和调试中遇到过的问题整理成一张速查表方便你对着症状找原因。症状可能原因排查思路H·c^T 不等于0循环移位方向反了或Z_c选错偏移表先用Z_c4的小矩阵手工验证每个块的展开结果仿真性能比理论差3dB以上LLR计算错误或调制映射顺序不对用BPSK先跑一遍排除高阶调制影响高信噪比误码率不降最小和归一化因子不合适或迭代次数不足扫描alpha检查误码曲线是否出现平层高码率性能特别差速率匹配时打掉了系统比特或关键校验比特检查RV起点位置确认打孔优先级是否正确解码始终不收敛基矩阵读取行数/列数错误核对BG1是46行68列BG2是42行52列系统列别记错多个码长下性能不稳定Z_c的取值与偏移表搭配错误仔细核对z值和偏移表的对应关系不同z集合用不同偏移表再补充几个更隐蔽的细节。第一BG1和BG2的系统列数和校验列数必须背下来。BG1是22列系统位、46列校验位总计68列BG2是10列系统位、42列校验位总计52列。如果你在构造编码矩阵时用错了系统列数编码结果可能凑巧能解码但性能曲线一定不对。第二编码时校验位的计算要从H矩阵反推生成矩阵G不要直接去解方程。5G NR基矩阵结构是下三角加特殊结构可以通过后向替换back substitution高效编码这一步在软件仿真里可以直接用高斯消元求G矩阵但硬件实现时务必把编码流程改成分步求解否则复杂度会失控。第三注意系统比特打孔保护。有些自研速率匹配模块为了省事直接按均匀步长抽样比特导致关键的系统比特被丢掉性能断崖式下跌。正确做法是确保系统比特全部发送校验比特再按优先级打孔。你可以用一个简单的计数器统计每个输出的比特对应的是系统列还是校验列确认发送的集合里系统比特覆盖完整。6. 写在最后这个系列还会讲什么LDPC设计原理是进入5G NR信道编码的入口但只是第一步。拿到H矩阵之后还要解决编码器的快速实现、译码器的归一化参数调优、不同码率下速率匹配的工程适配、以及和HARQ流程的配合。我自己在调试一个LDPC链路时最耗时间的往往不是算法本身而是把协议里的各种细节对齐到仿真模型里比如Z_c的取值表、偏移表的切换条件、RV起点的计算这些一个错位性能曲线就是断崖式下跌。如果你正在做5G物理层仿真我建议你现在就动手做一件事把38.212的5.3.2节表格下载下来用文中的构造流程把BG1和BG2的H矩阵各展开一次再用零向量和随机码字分别验证一下。这个过程看起来枯燥但能帮你节省后面无数的调试时间。下一篇文章我会以编码侧为主题详细讲讲如何在工程上用后向替换做快速编码以及速率匹配的比特选择到底应该怎么实现。如果你在构造H矩阵时遇到了具体问题欢迎带着你的报错信息和仿真曲线来讨论——这类问题光看文档很难定位实际跑一下数据往往马上就有答案。
返回列表