
最近在做一个 LDPC 编解码器设计与仿真的项目前后折腾了差不多三周时间。从最开始确定码型参数、构造校验矩阵到把译码器从浮点 MATLAB 原型一路做到定点 C 模型最后上 FPGA 验证中间踩了不少坑也积累了一些还算系统的经验。趁着项目刚收尾把这套“怎么设计、怎么选算法、怎么搭仿真、怎么排查问题”的完整流程整理出来希望能让正在做 LDPC 相关工作的朋友少走点弯路。这个内容适合几类读者刚接触 LDPC 码、需要用 MATLAB 做链路仿真验证算法的人在搞 FPGA 或 ASIC 实现、需要把浮点译码算法改成定点模型的工程师以及做通信系统物理层设计想在 5G、WiFi、卫星链路里选型 LDPC 参数的技术人员。无论你是学生还是从业者文章里涉及的思路和避坑点基本都能直接用上。1. 项目整体设计与思路拆解1.1 为什么选择 LDPC 码它解决了什么问题LDPC 码全称低密度奇偶校验码核心思想是在 1962 年由 Gallager 博士提出简单说就是用一个非常稀疏的校验矩阵来约束信息比特然后在接收端利用这种约束关系进行迭代纠错。这个“稀疏”是关键它保证了译码器能用线性复杂度近似逼近香农极限这是 Turbo 码和卷积码在同样码长下很难做到的。我在选型阶段为什么坚持用 LDPC而不是继续用之前的卷积码或 Turbo 码原因有三点。第一LDPC 在高码率区间的性能优势特别明显5G NR 里 eMBB 场景的数据信道选的就是 LDPC码率可以做到 8/9 甚至更高而且没有 Turbo 码那种交织器延迟。第二LDPC 的校验矩阵构造灵活可以根据信道条件和码长需求调整度分布这让系统设计的自由度非常大。第三迭代译码算法的并行性很好在 FPGA 上可以同时更新大量节点的消息吞吐量能做到 Gbps 级别这对高速通信系统非常关键。但是也要说清楚LDPC 不是万能的。它的译码延迟比卷积码高一个量级短码长时可能因为校验矩阵的图结构问题出现错误平层另外硬件实现时校验节点更新单元的复杂度也明显高于 Viterbi 译码器。所以实际项目中选择 LDPC 的前提是你的系统对延迟有容忍度、对误码率底限有硬性要求并且有能力处理大规模迭代运算。1.2 方案选型的整体考量码型、参数与仿真平台动手写代码之前我先把整个系统方案梳理成了一张清单这步非常关键后面所有工作都围绕这张清单展开。第一个是确定码型。我做的项目基于 5G NR 的 LDPC 基图结构采用了准循环 LDPC 码也就是 QC-LDPC。选择 QC-LDPC 的原因很实际它的校验矩阵由若干个大小相同的循环移位子矩阵构成每个子矩阵只需要存一个循环移位值存储开销小而且译码时地址生成逻辑非常简单非常适合硬件实现。如果只是做算法验证也可以用随机构造的稀疏矩阵但到 FPGA 阶段就会痛苦布线资源会浪费在无规律的锯齿状连接上。第二个是参数选择。码长、码率、迭代次数、量化位宽这些参数不是孤立的它们直接决定编解码延迟和性能上限。我这次的参数定的是码长 N1944信息位 K1620码率 R5/6最大迭代次数 20 次。码长和码率直接决定了系统的频谱效率和纠错能力而迭代次数需要在性能和延迟之间做折中后面的章节我会用仿真数据说明具体怎么权衡。第三个是仿真平台的分工。我采用了两级仿真验证方案第一级用 MATLAB 做浮点链路仿真验证算法正确性和性能上限第二级用 C 语言实现定点模型模拟硬件量化后的行为为 FPGA RTL 编码提供参考。这个分工看起来很常规但正是因为这个分工明确后期硬件调参数时才有据可查。2. 校验矩阵构造与编码器实现细节2.1 校验矩阵怎么来从随机构造到准循环设计LDPC 码的性能好坏很大程度上取决于校验矩阵 H 的图结构质量而不是码字本身。设计 H 矩阵时最重要的约束是避免短环尤其是长度为 4 的环。所谓环就是 Tanner 图中从某个变量节点出发经过若干条边再回到该节点的闭合路径。为什么短环会导致性能下降因为迭代译码的核心假设是节点之间的消息相互独立而短环会破坏这个独立性导致消息在环中不断自我加强最终收敛到错误的结果。我在仿真里实际对比过一个含有大量 4 环的随机 H 矩阵和一个经过 girth 优化的 QC-LDPC 矩阵在同样信噪比条件下误码率差了接近一个数量级。所以设计阶段必须对 H 矩阵做 girth 检查至少保证最小围长为 6。5G NR 的基图本身已经优化好了直接用不会出大问题但如果你要自己构造矩阵建议在随机填充之后加一个环检测和修正的步骤我这里写了一个基于 BFS 的围长检测函数专门用来扫描所有变量节点找出长度小于设定值的环。QC-LDPC 矩阵的构造稍有不同。它把 H 矩阵分块每块是一个 Z×Z 的单位阵循环移位移位值构成一个基础矩阵。设计基础矩阵时要保证任意两行之间在同一个位置不会出现两个非零元素否则必然产生 4 环。我用的方法是参考 5G NR 的基图然后根据目标码长调整扩展因子 Z这样既保证了性能又让硬件地址生成逻辑变得非常简单。2.2 编码器实现两个比直接乘矩阵更聪明的做法LDPC 编码最朴素的做法是用生成矩阵 G 直接和输入信息比特相乘公式是 c u·G。但问题是LDPC 的 H 矩阵是稀疏的由 H 求出的 G 往往是稠密的编码复杂度会退化成 O(n²)这在码长为 1944 时还能忍但到码长接近 10 的 5 次方时完全不可接受。所以我用了两种更高效的编码方案。第一种是借助高斯消元把 H 矩阵变换成近似下三角形式然后用递推的方式逐块求解校验比特。具体做法是先把 H 做列置换使得一部分列形成下三角结构然后利用稀疏矩阵的递推关系逐行计算校验位。这种方法的复杂度大约是 O(n g²)其中 g 是下三角部分的宽度通常在几百以内相比 O(n²) 已经大幅降低。我在 MATLAB 里用 symbolic 的工具函数做了一次列置换预处理把结果存下来之后的每个码块编码都直接用这个预处理结果。第二种是结构化编码适用于 QC-LDPC。因为校验矩阵是分块循环结构可以利用循环移位矩阵计算校验比特编码器直接用移位寄存器和异或门就能搭建完全不需要做矩阵乘。我在 FPGA 里用的就是这种结构编码延迟只取决于基图的行数和列数和码长没关系能支持流水线处理。2.3 编码链路搭建的实操要点编码器仿真出来看起来简单但有几个细节特别容易忽略。第一是输入向量的格式。MATLAB 的 encode 函数默认输入是列向量如果你的数据链路上游给的是行向量处理不当就会出现维度不匹配的报错。我建议整个链路统一用列向量并写一个简单的尺寸断言函数在编解码入口都检查一下。第二是码率匹配问题。实际系统不会只传一个固定码率5G 里通过打孔和缩短来实现多码率因此在编码器之后一定要留一个码率匹配模块的接口否则后面换参数时整个编码链路都要重写。还有一个重要教训编码器仿真时一定要做“合法码字检查”即验证 c·Hᵀ 是否等于全零。这个检查在浮点仿真阶段几乎不做但到了定点阶段如果出现错误码字问题往往出在量化导致校验位计算溢出而不是逻辑错误。我后来把这个检查做成了编码器输出的强制断言一旦出错立即停止仿真并保存中间信号排查问题效率高了很多。3. 译码器算法拆解与仿真实现3.1 译码算法选型BP、最小和还是归一化最小和平衡的艺术译码算法是整个 LDPC 仿真里最核心的部分也是最容易让人困惑的地方。我先把几种主流的迭代译码算法按性能和实现复杂度排了个序方便选择。第一层是置信传播算法也叫和积算法。它在每个迭代中通过变量节点和校验节点之间交换概率信息逐步逼近最大后验概率译码。理论性能最好但计算量大涉及大量乘法和对数运算硬件实现成本高。第二层是对数域 BP把概率转成对数似然比将乘法变成加法是软件仿真的默认选择。第三层是最小和算法用最小值和次小值近似校验节点更新中的对数计算复杂度大幅下降但性能有损失。第四层是归一化最小和和偏移最小和分别通过乘以一个小于 1 的归一化因子或减去一个偏移量来补偿近似损失性能和复杂度折中得最好。我的结论是如果你在 MATLAB 里做算法验证直接用对数域 BP性能上限最清晰如果你在为 FPGA 实现做准备尽早切换到归一化最小和因为硬件实现时几乎不会直接用的对数域 BP两者性能差异在误码率 10⁻⁶ 量级时只有 0.1~0.2 dB但资源占用差好几倍。3.2 对数域 BP 的完整迭代实现步骤对数域 BP 的实现步骤不复杂但细节很多我把每一步的公式化简和代码要点整理出来。第一步是信道初始化。对于 BPSK 调制加 AWGN 信道每个比特的初始对数似然比 LLR 计算公式是 2y/σ²其中 y 是接收信号σ² 是噪声方差。这里有个很容易犯的错噪声方差的取值要和信噪比的定义一致Eb/N0、Es/N0 和 SNR 三个指标之间的换算关系必须理清否则整条 BER 曲线会偏移好几个 dB。第二步是变量节点更新。变量节点向校验节点传递的消息等于该变量节点的初始 LLR 加上所有其他校验节点传来的消息之和。这个步骤本质上就是一个加法运算同时也是高度并行的在 MATLAB 里可以直接用矩阵操作批量完成。第三步是校验节点更新。这是整个算法的热点公式是双曲正切的乘积形式。计算时先取每路输入消息的绝对值找出最小值和次小值再根据各路消息的符号乘积决定输出消息的符号。这个“最小和近似”就是最小和算法的由来。用归一化最小和时把计算得到的最小值乘以一个约 0.75 的因子修正效果很明显。第四步是后验概率更新和判决。累加所有消息和初始 LLR按符号判决为 0 或 1然后重新计算校验子。如果校验子全零或者达到最大迭代次数译码结束。我在实现时把变量节点更新和校验节点更新分别封装成了两个函数迭代主循环单独写一个脚本这样后期做定点化时只需要替换这两个函数的内核不需要动主循环。3.3 性能与复杂度的取舍我用仿真数据说话算法选型不能光靠感觉必须有数据支撑。我同样条件下对比了四种算法的 BER 曲线码长 1944码率 5/6最大迭代次数 20。整理出来的现象如下。对数域 BP 的性能最好在 BER 为 10⁻⁵ 时所需的 Eb/N0 大约是 2.8 dB接近香农限。最小和算法在同样 BER 下性能损失约 0.4 dB这个问题对于高码率码更明显。归一化最小和用 0.75 的归一化因子性能损失控制在 0.1 dB 以内几乎可以忽略。偏移最小和的表现类似但归一化因子调整更方便。所以从工程角度归一化最小和是最优选择这也是 5G 终端芯片里普遍采用的做法。迭代次数的选择也要有依据。我统计了不同迭代次数下的平均迭代次数和 BER 对比发现大多数正确译码的码块在前 5 次迭代就能收敛20 次迭代基本能覆盖所有能正确译码的情况。继续增加迭代次数不仅降低吞吐量还有可能让原本振荡的译码器产生更多误码。所以仿真时我加了早停机制一旦校验子全零就立即停止迭代既省时间又避免了无谓的消息更新。4. 仿真平台的搭建与完整验证流程4.1 MATLAB 快速原型验证搭链路像搭积木我把 MATLAB 链路分成四个模块发射端、信道、接收端、统计模块。发射端包含随机比特生成、LDPC 编码、BPSK 调制。信道是 AWGN只加噪声不做衰落。接收端包含软解调、LDPC 译码、判决输出。统计模块记录误码率和误块率。这部分的代码量不大但几个细节值得注意。第一随机比特生成要设置固定的随机种子否则每次跑出来的 BER 曲线波动很大无法对比不同算法的差异。第二蒙特卡洛仿真需要设置停止条件不能固定仿真帧数就结束。我用的规则是至少仿真 100 帧且累积误码数达到 100 个才能停止当前信噪比点的统计否则在低误码率区间统计结果不可靠。第三信噪比的扫描范围要覆盖从几乎百错到几乎零错的区间一般从 1 dB 到 4 dB步进 0.2 dB这样画出来的 BER 曲线才有完整的拐点。仿真跑起来之后每次出来的数据都要保存成 mat 文件包括所有信噪比点的 BER、平均迭代次数、每帧的译码结果。这些数据在写周报、和硬件团队对齐算法性能时非常有用不用重跑仿真。4.2 浮点到定点硬件实现前的关键一步MATLAB 验证通过后就要开始定点化了。定点化的核心是把浮点消息裁剪成固定位宽的定点数需要考虑位宽选择、小数位分配和饱和处理三个问题。我在这个项目里用的位宽方案是输入 LLR 用 8 bit 定点其中 1 位符号位、3 位整数位、4 位小数位校验节点更新过程中保留 8 bit 消息变量节点更新由于涉及多次累加临时结果用 12 bit 防止溢出。这个配置是我对比了几组不同位宽方案后确定的。位宽从 8 bit 降到 6 bit性能损失大约 0.3 dB但资源能省一半从 8 bit 升到 10 bit性能提升不到 0.05 dB资源增加却很明显。所以 8 bit 是一个性价比非常高的折中点。定点化中最大的雷区是溢出。LLR 的绝对值在迭代过程中可能快速增长如果不加饱和处理用补码表示的定点数会发生正数变负数、负数变正数的现象导致译码器直接发散。解决方案是在所有加减法输出处加入饱和逻辑超出最大值就钳位到最大值。另外归一化最小和中乘归一化因子的步骤也要先扩展位宽再截断否则误差会逐次累积。我用 C 语言做的定点模型有个好处是能和 MATLAB 定点工具箱的结果对照两者完全一致说明定点方案没问题之后给 RTL 工程师的参考模型就非常可信。4.3 性能指标怎么看BER、收敛速度与错误平层仿真结果出来之后怎么评估这套 LDPC 编解码器的好坏我认为有三个核心指标。第一是 BER 曲线与香农限的距离。在 BER 10⁻⁵ 时与理论极限差距在 1 dB 以内说明方案是合格的0.5 dB 以内说明算法选择比较好。第二是平均迭代次数这直接影响吞吐量。我统计了各信噪比点上的平均迭代次数在 2 dB 时大约是 12 次在 3 dB 及以上时降到 2 次左右说明早停机制起作用了。第三是错误平层。在高信噪比区间BER 曲线可能出现平台也就是不再随信噪比增加而下降这通常是由校验矩阵的低围长弱点引起的。我发现 5G 基图在高码率时错误平层很低但如果你用随机构造矩阵要特别关注这一点。除了这三个指标我还建议统计误块率和误码率的比值关系。在 LDPC 译码中错误通常以块为单位出现误块率除以误码率得到的平均每错误块错误比特数可以帮助判断译码失败的形态如果这个数值很大说明译码器经常输出完全错误的码字而不是个别比特错。5. 常见问题与排查技巧实录5.1 译码不收敛、振荡和发散从哪个环节找原因仿真中遇到最多的问题就是译码器不收敛。具体表现是迭代到最大次数后校验子仍然不是全零或者校验子在两个状态之间来回跳变。这种情况我从三个方向排查。第一检查初始 LLR 的符号是否正确。BPSK 调制时LLR 的正负含义和比特映射是否一致如果反了译码器会一直朝错误方向更新。这个问题在链路联调时特别容易出现尤其是调制和编码分成两个人做的时候。第二检查校验节点更新的符号逻辑。采用最小和近似时输出消息的符号是所有输入消息符号的异或如果不小心写成异或 后再取反整个译码器就废了。第三检查定点化后的饱和阈值。定点模型里我遇到过消息反复加到饱和值附近导致振荡的情况后来把饱和阈值调大了一点并在变量节点更新前加了一个简单的衰减问题就消失了。如果以上都排除了还有一个容易被忽略的点迭代顺序。LDPC 译码有分层译码和淹没译码两种调度方式分层译码中某个变量节点更新的消息会立即被同一层的校验节点使用这会加速收敛。但是如果分层分错了或者把分层译码的中间结果错误地当作淹没译码的结果保存仿真性能会异常差。5.2 错误平层问题怎样定位是矩阵问题还是量化问题错误平层是 LDPC 高信噪比区间的典型问题我这次也遇到了。现象是 BER 曲线在 10⁻⁶ 附近开始变平再增加信噪比也没明显改善。排查方法也很明确先在浮点仿真里看同样参数下是否还有平层。如果浮点没有了说明是定点量化带来的问题优先调整消息位宽和归一化因子。如果浮点依然有平层那就是 H 矩阵本身的问题需要检查短环和停止集。停止集是指一小部分变量节点集合它们对应的校验关系被错误值满足导致译码器认为自己已经正确解码了。解决办法通常是重新优化基图在 5G 基图的框架下可以直接用更短的码长或者调整打孔方式打破停止集的影响。这里有一个非常实用的技巧当你怀疑是某个特定变量节点导致错误平层时可以单独增大该节点的初始 LLR 幅值再跑仿真。如果性能明显改善说明该节点处于一个脆弱的结构中需要从矩阵层面优化。5.3 硬件实现阶段容易忽略的三个问题最后说一下我踩过的硬件实现相关的坑这部分是给做 FPGA 或 ASIC 的读者提个醒。第一个是存储器的冲突问题。LDPC 译码器需要频繁读写变量节点和校验节点的消息如果存储器设计不好会出现读写端口冲突导致译码吞吐量大幅下降。解决办法有二一是用双端口 RAM二是精心安排迭代顺序使冲突最小化QC-LDPC 的循环移位结构能让这种安排变得很简单。第二个是早停机制在硬件里的实现。硬件中检查校验子全零需要做一次异或树这本身会消耗周期。如果你的吞吐量要求很高可以选择每迭代 2 次检查一次而不是每次迭代都检查性能损失很小但时序压力小很多。第三个是硬件仿真时注意初始化状态。所有消息 RAM 和校验子寄存器在上电后必须是确定的初始值否则仿真和实际电路的行为会对不上我最开始忘了初始化变量节点消息跑出的波形完全乱套。写在最后的一点经验分享这个项目做完之后我自己最大的体会是LDPC 编解码器的设计不是一个孤立的编码问题它是一个从矩阵构造、算法选择、浮点验证、定点化到硬件实现的系统工程每一环的决策都会影响最终性能和实现成本。如果你只做算法仿真至少要把校验矩阵的 girth 检查和过早停止机制写好这是提升仿真效率和质量性价比最高的两个点。如果你后面要做硬件尽量从第一步仿真就使用 QC-LDPC 矩阵并且尽早从对数域 BP 切换到归一化最小和这样可以避免很多后期移植的返工。最后再分享一个小技巧仿真过程中给所有模块加上断言检查比如编码器输出校验子必须为零、译码器最大迭代结束时校验子尽量接近零虽然会增加一点点运行时间但在出问题时能少熬几个通宵。希望能对你在做的 LDPC 项目有所帮助也欢迎交流讨论具体实现细节。