多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

802.11n LDPC 校验矩阵、编码与最小和译码实现完全指南

802.11n LDPC 校验矩阵、编码与最小和译码实现完全指南 简介面向802.11n无线局域网标准中的LDPC低密度奇偶校验码技术这套MATLAB/C仿真源码包专为无线通信方向学生、算法研究者及需要快速搭建LDPC编解码仿真环境的工程师设计。包内共25个文件以m脚本、c源码、mat数据文件及README说明为主涵盖校验矩阵生成、LDPC编码、置信传播译码、收发链路仿真与误码率测试等核心环节同时提供不同码长648、1296、1944与多种码率1/2、2/3、3/4、5/6的protoH参数文件便于对照实验压缩后仅22KB结构紧凑清晰。目前已有338人浏览学习。这套源码包可直接用于理解802.11n中LDPC在GF(2)上的编译码实现、MIMO场景下的并行解码机制并通过修改码长码率观察不同配置下的纠错性能既适合作为LDPC课程设计的参考工程也可作为优化无线通信系统性能的实验基础省去从零编写核心算法的功夫。1. 802.11n LDPC一块码率自适应芯片为什么要把校验矩阵焊死在协议里802.11n LDPC 是 Wi-Fi 4 时代从“卷积码 可选 RS 码”跨到“逼近香农限”的关键一跳。它把低密度奇偶校验码从实验室搬进了路由器靠一组 648、1296、1944 的准循环码字支撑 20/40MHz 下的高吞吐。很多从业者拿到带“802.11n LDPC”字样的源码包后第一反应是找 .m 文件跑仿真结果不是译码器不收敛就是误码率曲线在 1e-3 附近掉不下去。问题往往不在译码算法本身而在校验矩阵没对齐、LLR 符号约定写反或者打孔位和缩短位没按协议处理。这篇笔记就从校验矩阵的结构讲起把编码、译码、参数设置和最容易翻车的几个环节一次说透。2. 802.11n LDPC 码结构三种码长、四种码率与子块 Z 的对应关系2.1 先抄参数表码长、码率、子块大小与信息位长度802.11n 的 LDPC 码是准循环 LDPCQC-LDPC不是随便给一个稀疏校验矩阵就能跑。协议固定了 12 种码率码长组合码率取 1/2、2/3、3/4、5/6码长取 648、1296、1944。整个码字的列被分成 24 个列块每个列块的大小记为 Z所以三种码长对应的 Z 分别是 27、54、81。校验位对应的行块数由码率决定基矩阵行数 mb 24×(1-R)。明白这个关系你打开源码包时就不会被一堆 .txt 矩阵文件吓到——文件名里通常会带 R 和 Z比如“12_24_81”就是在说 1/2 码率、1944 码长。码长 N子块大小 ZR1/2R2/3R3/4R5/6648273244324865401296546488649721080194481972129614581620表格里每个格子是该码字的信息位长度 k校验位长度就是 N-k。选码型时有一个容易忽略的约束如果 PSDU 长度折算出来的信息比特不是表格里的标准 k协议规定用“缩短 打孔”来适配而不是自己去改 H。这点后面专门说。仿真阶段最省事的做法是先按标称 k 跑误码率确认链路没问题再引入缩短和打孔逻辑。2.2 QC-LDPC 的基矩阵与循环移位从 24 列小表展开成完整 HQC-LDPC 的核心是“基矩阵 循环移位”。基矩阵是一个 mb×24 的整数矩阵每个元素只告诉你怎么展开-1 表示全零子块非负整数 s 表示单位阵按某个固定方向循环移位 s 列。真正参与译码的校验矩阵 H 尺寸是 (mb×Z) 行、24×Z 列对 1944 码长、1/2 码率来说就是 972×1944。用 Python 展开基矩阵的代码很短但方向约定必须一致。import numpy as np def qc_expand(Bm, Z): 将基矩阵 Bm 展开为校验矩阵 H。 Bm 的形状是 (mb, 24)元素 -1 表示全零块s 0 表示循环移位量。 这里按单位阵每一行向右循环移位 s 列展开。 mb, nb Bm.shape H np.zeros((mb * Z, nb * Z), dtypenp.int8) for i in range(mb): for j in range(nb): s Bm[i, j] if s 0: for r in range(Z): H[i * Z r, j * Z (r s) % Z] 1 return H这段代码的逻辑很直白单位阵的第 r 行原本在列 r 有个 1向右移 s 列之后落到了 (rs) mod Z于是 H 里对应位置填 1。为什么非要强调方向因为不同版本的源码包可能把 s 解释成“向左移”或者把“循环移位”定义在列上而不是行上。若基矩阵表是从某个参考实现里抠出来的而展开方向与译码器不一致编译码两端对不上结果是 BER 一直在 0.5 附近。我一般会在拿到别人的基矩阵表之后先取第一个非 -1 元素单独展开成小矩阵目测一眼确认方向再继续。2.3 多码长与移位表别指望一份基矩阵打天下802.11n 的 LDPC 基矩阵并不是“一张表适配所有 Z”。协议里对同一码率不同码长可能使用不同的循环移位值这是因为大 Z 和小 Z 的循环移位模式需要分别设计才能保证短环少、最小距离不塌。你从压缩包里解出来的基矩阵文件如果只有 3 个很可能分别对应 Z27、54、81如果只有 1 个那多半是某个特定码长的硬套到别的 Z 上会出问题。判断方法是看矩阵里是否有大于等于 Z 的元素——如果有说明这张表不是给当前 Z 用的展开前要单独处理或直接换文件。3. 用校验矩阵 H 编码从乘 G 矩阵到 RU 递推仿真与硬件各走各的路3.1 编码方案选型为什么仿真阶段可以先乘稠密生成矩阵LDPC 是线性分组码理论上编码就是 c G×sG 是 H 的零空间基。802.11n 的 H 本身是稀疏的但 G 通常不是乘 G 矩阵的复杂度是 O(N²)硬件上没人这么干协议里的编码算法利用校验位部分的双对角结构做递推复杂度只有 O(N)。可是在 MATLAB 或 Python 仿真里1944 码长乘 G 矩阵只需要几百微秒一次蒙特卡洛仿真要编几百万个码字这个开销仍然可以接受所以我的做法是协议帧格式调试阶段先用 G 矩阵编码把“编码过程本身”当作已知正确专心查调制和信道等链路通了再换成结构化编码。3.2 直接从 H 求系统生成矩阵可直接运行的最小实现下面这段代码接受一个展开好的 H先检查校验位对应的右侧 mb 列是否满秩然后做 GF(2) 行化简把右侧化为单位阵左侧就得到系统形式的 P最终拼出生成矩阵 G。编码结果满足 H×c ≡ 0。def systematic_g_from_H(H): 输入 Hmb × n 的二元校验矩阵列顺序必须为 [系统位列 | 校验位列]。 返回 Gn × k 的系统生成矩阵k n - mb。 A H.copy() % 2 mb, n A.shape k n - mb # 把右测 mb 列化简为单位阵 for i in range(mb): col k i pivot np.nonzero(A[i:, col])[0] if len(pivot) 0: raise ValueError(校验位部分奇异无法构造系统 G) pivot pivot[0] i A[[i, pivot]] A[[pivot, i]] for r in range(mb): if r ! i and A[r, col] 1: A[r] (A[r] A[i]) % 2 # 此时 A [P | I_mb]系统码校验位 p P s P A[:, :k] % 2 G np.vstack([np.eye(k, dtypenp.int8), P]) return G def encode_ldpc(H, s): G systematic_g_from_H(H) n, k G.shape s np.asarray(s).reshape(k, 1) c (G s) % 2 assert np.allclose((H c) % 2, 0), 编码结果不满足 H c 0 return c.flatten()这段代码里有三个地方要留意。第一列顺序必须是 [系统位 | 校验位]802.11n 的基矩阵展开后天然是这个顺序但如果你从别处拿到一个打乱过的 H要先做列重排。第二行化简只看校验位列找主元如果校验位部分本身奇异说明 H 不是系统码形式或者基矩阵 Z 不匹配直接抛异常比带病跑仿真好。第三生成矩阵 G 是稠密的n1944、k972 时大约 1.9MB 内存完全够用不要拿 10 万码长的矩阵来套这个函数。3.3 打孔与缩短从标准信息位到任意包长的一步实际 WiFi 帧长不可能永远等于标准表中的 k协议通过缩短和打孔把 LDPC 码字“裁剪”成需要的长度。常见做法是信息比特不足 k 时在系统位前面补 0编码后这些补的 0 不发送对应缩短位如果缩短后码字仍然太长再从校验位里按协议规则打掉若干比特。接收端必须知道哪些位置被缩短、哪些被打孔把打孔位置的 LLR 置 0把缩短位置当作已知 0 参与译码。我见过最隐蔽的 bug 是发射端缩短和打孔的顺序与接收端相反导致译码器拿错软信息误码率平台在 1e-2 下不来。建议仿真时用一个 dict 把缩短位索引和打孔位索引随码字一起传给接收端先别省这个传参。4. 译码实现从和积 BP 到最小和 NMS收敛速度与实现代价怎么换算4.1 变量节点与校验节点的消息传递先写对 LLR 的符号约定LDPC 译码的本质是因子图上的置信传播。变量节点保存信道软信息 LBPSK 下 L 2y/σ²校验节点负责把“其它变量节点的消息”融合成约束。和积算法用 tanh 规则精确计算校验消息硬件实现几乎都换成最小和近似因为 tanh 的查表和乘法太贵。最小和近似的校验节点更新规则是消息符号等于所有输入符号的乘积幅度等于所有输入幅度里最小的那一个。归一化最小和NMS在此基础上乘一个 0.75 左右的系数用来补偿近似误差。LLR 符号约定是第一个坑。我习惯用 0→1、1→-1 映射也就是 LLR 为负表示判决为 1。有些源码包用相反的约定译码器内部符号统一还好但如果你把外部模块算好的 LLR 直接灌进来符号反了的表现很典型编码正确、信道无误译码输出却全反BER 在 0.5 附近一动不动。4.2 可直接运行的最小和译码函数NMS 与参数说明下面这段实现是给理解消息更新顺序用的没有做向量化适合 N 在几百到几千的码字。工程上要提速的话把 H 的稀疏结构预先存成邻接表再用 numpy 对校验节点批量更新。def min_sum_decode(H, llr, max_iter20, alpha0.75): Hmb × n 校验矩阵 llr长度为 n 的信道对数似然比 max_iter最大迭代次数 alpha归一化最小和系数通常取 0.75~0.8 返回硬判决比特0/1 mb, n H.shape vn_nb [np.nonzero(H[:, j])[0] for j in range(n)] # 每个变量节点连接的校验节点 cn_nb [np.nonzero(H[i, :])[0] for i in range(mb)] # 每个校验节点连接的变量节点 v2c np.zeros((mb, n)) # 变量节点发给校验节点的消息 c2v np.zeros((mb, n)) # 校验节点发给变量节点的消息 for _ in range(max_iter): # 变量节点更新信道 LLR 除目标校验节点外的所有 c2v for j in range(n): for i in vn_nb[j]: msg llr[j] c2v[np.delete(vn_nb[j], np.where(vn_nb[j] i)[0]), j].sum() v2c[i, j] msg # 校验节点更新归一化最小和 for i in range(mb): for j in cn_nb[i]: others np.delete(cn_nb[i], np.where(cn_nb[i] j)[0]) signs np.sign(v2c[others, i]) mags np.abs(v2c[others, i]) prod_sign np.prod(signs) min_mag np.min(mags) if len(mags) else 0 c2v[i, j] alpha * prod_sign * min_mag # 总后验 LLR 与硬判决 decisions np.zeros(n, dtypenp.int8) for j in range(n): total llr[j] c2v[vn_nb[j], j].sum() decisions[j] 1 if total 0 else 0 return decisions参数上最需要关心的是 max_iter 和 alpha。max_iter 在浮点仿真里取 20 左右就够继续加大收益很小但会让仿真时间翻倍。alpha 取 0.75 是经验值取值太小在高 SNR 区会出现平台期取值太大则退化成标准最小和性能损失约 0.2dB。如果你的误码率曲线比理论曲线差出 0.5dB 以上先怀疑 alpha 而不是信道模型。4.3 迭代终止条件与量化位宽浮点仿真和定点实现的差距从哪来浮点仿真里判断译码成功有两种做法一是达到最大迭代次数就停二是每次迭代后做一次硬判决并用 H 校验满足 H×c0 提前终止。仿真阶段用第二种能省不少时间也不会影响误码率曲线。定点实现则是另一套逻辑LLR 和消息都要量化常见做法是 Q5.2 或者 Q4.3符号位加整数位加小数位总共 6~8 bit。位宽每减少 1 bit性能可能掉 0.1~0.3dB但 FPGA 资源能省下不少这个权衡要靠扫位宽决定别听别人说 6bit 够就照抄。5. 802.11n LDPC 落地避坑从 H 矩阵校验到性能曲线的 5 条排查记录5.1 现象一译码输出全错BER 稳定在 0.5原因是最常见的“H 矩阵和码字没对齐”。可能是基矩阵展开方向反了也可能是信息位长度 k 没对上系统码列位置错了一位。解决方法是先做一个确定性测试构造一个全 0 信息位序列用编码函数生成码字然后直接过译码器不断信道LLR 设成很大看输出是否全 0。这一步不过后面所有统计都没意义。5.2 现象二迭代次数增加但误码率出现平台期这通常不是迭代次数不够而是 LLR 初始化符号反了或者幅度算错。BPSK 下如果 y 的符号和映射反了译码器会把高置信度的错误比特当成可靠信息迭代越多错误越固化。排查时把 SNR 设到 -5dB 以下这时候信道信息很弱符号错误的影响不大误码率应接近 0.5再把 SNR 提到 10dB正确符号的 LLR 幅度足够大误码率应迅速掉到 1e-4 以下。如果趋势不对先把 LLR 计算独立出来单测。5.3 现象三C 代码仿真速度比 MATLAB 快不起来很多人以为换成 C 就一定快结果发现瓶颈在译码器里对 H 的二维数组扫描而不是算法本身。原因是没有把 H 预处理成邻接表每条消息更新都在重复遍历全零元素。解决方法是编译码前把 H 转成 CSR 格式或者三张索引表分别存列索引、行起始、非零值位置迭代循环只访问非零位置1944 码长下速度能提升一个数量级。5.4 现象四FPGA 上性能比仿真差 1dB 以上先查打孔位和缩短位有没有按协议对齐。仿真里你可能随手删了最后几个校验比特但协议规定的打孔位置往往在固定列块删错位置等于给校验方程挖洞。另一个原因是定点溢出校验节点消息在迭代中可能超过量化范围NMS 结果被截断后性能会掉。建议把定点译码器的每级消息最大值打出来和浮点对比确认饱和点。5.5 现象五同一个 H 和相同 SNR两个版本源码性能曲线不一致这类玄学问题九成出在归一化因子 alpha 被改过或者校验节点更新的“除自身外”逻辑写错。标准最小和更新时目标变量节点自己的消息必须排除在外有些实现图省事把自身消息也放进去参与 min 运算性能会莫名其妙掉 0.3~0.5dB。逐行对比更新公式再在 H 退化为单校验节点时做单元测试最容易暴露这类问题。6. 进阶验证技巧一套自建 802.11n LDPC 链路怎么判断实现是懂行的验证 LDPC 实现是否真正按 802.11n 协议工作不能只看 BER 曲线好看。我习惯按三步走。第一步先做无信道测试编码 → 过 AWGN 之前的 LLR 直接置大数 → 译码输出必须和输入信息位完全一致这一步排除编译码逻辑错误。第二步把 H 矩阵的每一行单独拎出来随机生成满足该行校验关系的码字验证译码器在单校验节点下能正确更新这能抓住消息索引错位的隐蔽 bug。第三步才上完整 AWGN 链路至少跑 100 帧、统计到 1e-4 以下看瀑布区斜率是否正常。链路中值得额外检查的是缩短和打孔位置的记录。我会把缩短位索引和打孔位索引作为元数据随帧传递接收端构造 LLR 时对打孔位填 0对缩短位填一个很大的正 LLR 表示已知 0。这个细节没做对短帧的误码率会比长帧差很多但长帧曲线还正常很容易被当成信道波动忽略掉。最后再核对一次 H×c0 的恒等式assert np.allclose((H c) % 2, 0), H c ! 0这个断言我每次换 H 矩阵或者换基矩阵展开方向都会跑一遍。跑完这些检查你的实现至少是自洽的接下来再去调 alpha 和迭代次数才有意义。这些年调 LDPC我最大的教训就是不要先动译码器参数先把矩阵方向和 LLR 约定查清楚——前者是协议问题后者是习惯问题定下来之后误码率曲线往往自己就对了。希望帮到你。本文还有配套的精品资源点击获取
返回列表