ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MATLAB实现NMS译码器:从QC-LDPC基矩阵展开到参数标定

MATLAB实现NMS译码器:从QC-LDPC基矩阵展开到参数标定 简介低密度奇偶校验码LDPC在MATLAB环境下的编译码仿真资源面向通信工程、电子信息类专业学生及从事信道编码研究的工程师解决LDPC码从构造、编码到多种译码算法对比验证的实际需求。压缩包共含7个M文件整体大小仅8KB全部为MATLAB函数或脚本不附带其他格式文件结构紧凑、便于直接阅读和二次开发。文件模块覆盖校验矩阵生成、奇偶校验检查、置信传播译码、最小和译码、归一化最小和译码、偏移最小和译码以及误码率性能测试可帮助读者在统一框架下比较不同译码算法的收敛速度与纠错性能。目前已有280人学习使用说明该资源在入门级LDPC仿真中具备一定参考价值。通过脚本调参可灵活切换算法适合课程设计、毕业设计或算法预研阶段快速搭建仿真链路。1. 从 base41c 说起手写 NMS 译码器之前要明白的三件事调试 802.11n 接收机物理层时LDPC 译码器是绕不开的一环。很多人第一反应是直接调工具箱里的ldpcDecode但一旦要往 FPGA 定点化、C 模型交叉验证或者自定义参数扫描方向走就发现自己手里没有一个能逐行解释消息传递的参考实现。这个标题其实就是一条完整的技术链路base41c 是 IEEE 802.11n 标准里 R1/2、扩展因子 Z81 的 LDPC 基矩阵NMS 是归一化最小和译码算法而 MATLAB 则是把两者快速粘合起来的环境。我给出的结论是用 NMS 实现这个基矩阵的译码器在浮点环境下比 SPA 损失不到 0.1 dB但计算量下降一个量级在定点化之后反而更接近硬件真实行为。这篇文章就沿着基矩阵结构 → 算法推导 → MATLAB 实现 → 参数标定这条线把每一步写成可以直接落地的代码和参数表。2. 从基矩阵到 H 矩阵解析 base41c 的 QC-LDPC 结构NMS 译码器需要一个完整的稀疏校验矩阵 H而 base41c 只是压缩后的基矩阵。做译码的第一步不是写循环而是先把基矩阵展开成真正参与运算的 H。2.1 base41c 的码参数与子矩阵约定base41c 在标题里的写法大概率是代码仓库作者对 IEEE 802.11n 中 R1/2、Z81 基矩阵的本地命名。这一类准循环 LDPCQC-LDPC矩阵有一个共同特征H 由 Mb 行乘 Nb 列的子块构成每个子块是 Z 乘 Z 的方阵。对 base41c 而言码长 N1944信息位 K972校验位 M972因此 Z81基矩阵维度是 12 行乘 24 列展开后就是 972 乘 1944 的稀疏矩阵。基矩阵中的每个整数只表示三种含义-1 代表全零子块0 代表单位阵正数 s 代表单位阵做了 s 位的循环移位。协议里约定移位方向和矩阵展开方式可能略有差异但绝大多数 MATLAB 实现采用行循环右移的约定。拿到一个基矩阵文件先别急着写展开代码而是确认它的元素范围和行列数是否符合预期。base 元素对应 Z×Z 子块备注-1全零矩阵该位置无边连接0单位阵无移位s1~80单位阵循环移位 s 位移位方向需和展开代码一致base41c 的校验位部分采用双对角加修正列的结构这是 802.11n 所有中等码长码字的共同设计目的是让 H 满秩同时允许编码端用线性复杂度递推校验比特。展开之后你可以通过full(H(1:81, 1:81))看第一个子块的样子正常情况下每一行和每一列都恰有一个 1。2.2 循环移位展开从 12×24 变成 972×1944我用一个通用函数处理基矩阵展开。输入是任意 Mb 乘 Nb 的基矩阵和扩展因子 Z输出是 sparse 格式的 H这样后续消息传递代码可以直接用逻辑索引取邻居。function H expand_base(base, Z) % base: Mb×Nb int8 矩阵-1 表示全零子块s0 表示循环移位量 % Z: 扩展因子如 81 [Mb, Nb] size(base); H sparse(Mb*Z, Nb*Z); I speye(Z); for m 1:Mb for n 1:Nb s double(base(m, n)); if s 0 % 单位阵按行循环右移 s 位 sub I([Z-s1:Z, 1:Z-s], :); row0 (m-1)*Z 1; col0 (n-1)*Z 1; H(row0:row0Z-1, col0:col0Z-1) sub; end end end end这个函数把基矩阵中每个非负元素替换成对应的循环移位单位阵。sub I([Z-s1:Z, 1:Z-s], :)从单位阵中按行重排实现右移当 s0 时索引序列退化为1:Z等于没移位。双循环虽然直观但 12×24288 个子块、每个 81×81对一次初始化来说完全够快。如果暂时拿不到 base41c 的原始数据文件可以自己按 12×24 的维度从标准里的 LDPC 矩阵表录入或者用较新版 Communications Toolbox 里的准循环矩阵对象导入标准基矩阵再转成稀疏 H。重点是展开函数本身就具备通用性未来换 Z27、Z54 的码字只要改传入的 base 和 Z 即可。展开之后用issparse(H)确认存储格式再用sum(H,2)检查行重分布802.11n 的 R1/2 码通常行重在 6 到 7 之间列重在 2 到 3 之间如果出现全零行说明基矩阵数据读错了。3. 归一化最小和算法从 SPA 到 NMS 的推导与参数表基矩阵展开之后译码器的主体就是消息传递。NMS 不是凭空出现的它是对 SPA 校验节点更新的近似理解这层近似关系才能解释为什么归一化因子 α 非要取 0.75 附近而不是随意拍一个数。3.1 SPA 的校验节点更新为什么在 tanh 域概率域的置信传播中校验节点向外发送的消息是它收到的所有变量节点消息的置信度融合。在 LLR 域写完整个更新就是r_mn 2·atanh( Π_{n∈N(m)\n} tanh( q_nm / 2 ) )其中 q 是变量节点发给校验节点的消息r 是校验节点返回的消息。tanh 和 atanh 这对变换的存在本质上是为了让概率相乘在 LLR 域变成tanh 值相乘。问题也很明显每一轮每个校验节点都要计算 6 到 7 次 tanh 和 1 次 atanh在 1944 码长下这个开销相当可观在硬件里要实现逼近 tanh 的查找表更是麻烦。3.2 Min-Sum 近似为什么过估NMS 为什么乘 α对形如2·atanh(tanh(x/2)·tanh(y/2))的双变量校验更新做泰勒展开主项就是sign(x)·sign(y)·min(|x|,|y|)其余项以指数速度衰减。推广到多个输入校验节点消息可以近似成符号乘积乘最小绝对值这就是 Min-Sum 算法。问题在于这个近似把所有非最小项直接扔掉了。两个最小绝对值相差不大时次小项对结果的贡献其实不可忽略丢掉它会让|r_mn|系统性偏大即外信息过度自信。在长码和高信噪比下这种过估会让误码率出现明显地板。NMS 的修正极其朴素给 Min-Sum 的输出乘一个小于 1 的归一化因子 α。从概率角度看这不是简单地把误差削掉一块而是把整条消息的置信度分布压缩让变量节点在后续迭代中不至于被过度修正的方向带偏。与之对应还有偏移最小和 OMS常数偏移对高 LLR 更友好但参数要随量化位宽重扫实际工程里 NMS 用得更多。3.3 NMS 参数表与量化影响α 的取值和量化位宽强相关浮点仿真里 0.75 到 0.8 都能用但定点模型里 LLR 被截断到 6 bit 或 8 bit 后过估的程度会改变α 需要重新标定。下面这张表是我在类似项目里的起点参数不是标准答案但足够让新实现跑出一个可用基线。参数典型取值说明归一化因子 α0.75浮点/6bit 量化0.7~0.85 之间逐点扫描最大迭代次数10~15低信噪比帧用满高信噪比靠提前终止LLR 量化位宽6~8 bit6bit 时 α 偏小8bit 时 α 可到 0.8提前终止条件H·x_hat 0避免无效迭代4. MATLAB 实现 NMS 译码器函数结构与关键循环浮点环境里 NMS 的实现不需要太多奇技淫巧真正容易写错的是消息表怎么存、校验节点更新怎么取最小值和次小值。这一章给出一个可以直接放进工程的实现结构上刻意靠近硬件数据流方便后续移植。4.1 消息存储与邻居表预处理消息传递中每个变量节点和校验节点之间都有一条双向边我用两个 M×N 矩阵存储校验消息和变量消息但只访问 H 中非零位置。为了避免每轮迭代都调find先建立行邻居和列邻居表这是整个函数性能的关键。M size(H, 1); N size(H, 2); row_nbrs cell(M, 1); col_nbrs cell(N, 1); for m 1:M row_nbrs{m} find(H(m, :)); end for n 1:N col_nbrs{n} find(H(:, n)); end Lr zeros(M, N); % 校验节点消息 r_mn Lv Lc(:); % 变量节点后验 LLR初始为通道 LLRrow_nbrs{m}返回第 m 个校验节点连接的所有变量节点索引col_nbrs{n}返回第 n 个变量节点连接的所有校验节点索引。Lr虽然开成 M×N但只有 H 非零位置有意义这是用内存换简单性。4.2 校验节点更新最小值、次小值与符号积校验节点更新是 NMS 的核心每个校验节点需要从所有输入消息中找最小绝对值和次小绝对值。我维护Lr矩阵避免每轮重新分配内存。for m 1:M nbrs row_nbrs{m}; if isempty(nbrs) continue; end % 去掉该边自身的外消息 q Lv(nbrs) - Lr(m, nbrs).; sg prod(sign(q)); ab abs(q); [min1, p] min(ab); min2 min([ab(1:p-1), ab(p1:end)]); for j 1:numel(nbrs) n nbrs(j); if j p Lr(m, n) alpha * sg * sign(q(j)) * min2; else Lr(m, n) alpha * sg * sign(q(j)) * min1; end end end这里的q计算对应标准公式q_nm Lv(n) - r_mn也就是从变量节点后验中剔除当前校验节点上一轮发来的消息避免信息回流。最小值位置p指向的那条边它的输出要用次小值min2其余边全部用全局最小值min1。符号位sg是这一轮所有输入符号的乘积但单条边的符号是sg · sign(q(j))等于除了该边外其余符号的乘积。这一步是 NMS 实现里最容易错的地方建议用小的 H 矩阵手工验算一次。4.3 变量节点更新与提前终止变量节点更新把通道 LLR 和所有校验消息累加起来得到新的后验 LLR然后做硬判决。提前终止条件用校验子全零判断能显著降低高信噪比下的平均迭代次数。for n 1:N nbrs col_nbrs{n}; if isempty(nbrs) continue; end Lv(n) Lc(n) sum(Lr(nbrs, n), 1); end x_hat double(Lv 0); if all(mod(H * x_hat, 2) 0) break; endH * x_hat得到校验子向量每个元素对应一个校验方程的值对 2 取模后全零说明所有校验方程满足译码提前结束。Lv(n)的更新没有显式减去任何边消息因为下一轮校验节点更新第一步会自动做Lv(nbrs) - Lr(m,nbrs)所以这里保存后验总和即可。把 4.1 到 4.3 拼进同一个 while 循环加上max_iter上限就是一个完整的浮点 NMS 译码函数。5. 用 α-迭代次数二维扫描定位 NMS 性能边界NMS 的可调参数只有 α 和最大迭代次数但两者相互耦合。α 太小收敛慢α 太大可能提前发散。我在拿到一个新基矩阵时不会只测一组参数而是直接做二维扫描把误码率和平均迭代次数放在同一张表里看。扫描脚本的核心是一个双层循环外层遍历 α内层遍历迭代上限每个组合统计相同帧数下的误码率和平均迭代次数。alphas 0.65:0.025:0.85; max_iters 5:5:30; ber_table zeros(numel(alphas), numel(max_iters)); avg_iter zeros(size(ber_table)); for i 1:numel(alphas) for j 1:numel(max_iters) % 在固定 EbN0 下跑 Nf 帧 % 记录误码帧数与平均迭代次数 end end判读结果时我习惯按三个指标选点。首先是误码率最低的区域如果 α 在 0.725 到 0.775 之间误码率几乎持平说明译码器对这个参数不敏感这是最理想的工程点硬件实现时量化误差不会造成性能突变。其次是平均迭代次数随着 α 减小迭代次数通常会单调上升如果在某个 α 以下迭代次数突然跳升说明已经逼近不收敛区不应该把工作点选在那里。最后是错误地板固定迭代上限下若增大迭代次数误码率没有明显下降说明代码里存在数值问题或符号处理错误回头检查校验节点更新。一个实用的进阶技巧是两阶段 α前 4 到 5 次迭代用 0.85 加速消除符号错误后续迭代切到 0.75 收敛细节。这个做法在部分帧长下能比固定 α 多压出 0.05 dB 左右实现上只需要在循环里加一个if iter 5, alpha alpha_floor; end代价几乎是零。用这个扫描方法标定完 α 之后再把 LLR 量化到 6 bit 重跑一遍相同表格就能看到定点化对参数边界的压缩幅度——这一步才是 NMS 在真实项目里最需要关注的性能边界。本文还有配套的精品资源点击获取
返回列表