MATLAB实现NMS译码器:从QC-LDPC基矩阵展开到参数标定
2026/9/14 14:40:35 网站建设 项目流程

简介:低密度奇偶校验码(LDPC)在MATLAB环境下的编译码仿真资源,面向通信工程、电子信息类专业学生及从事信道编码研究的工程师,解决LDPC码从构造、编码到多种译码算法对比验证的实际需求。压缩包共含7个M文件,整体大小仅8KB,全部为MATLAB函数或脚本,不附带其他格式文件,结构紧凑、便于直接阅读和二次开发。文件模块覆盖校验矩阵生成、奇偶校验检查、置信传播译码、最小和译码、归一化最小和译码、偏移最小和译码以及误码率性能测试,可帮助读者在统一框架下比较不同译码算法的收敛速度与纠错性能。目前已有280人学习使用,说明该资源在入门级LDPC仿真中具备一定参考价值。通过脚本调参可灵活切换算法,适合课程设计、毕业设计或算法预研阶段快速搭建仿真链路。

1. 从 base41c 说起:手写 NMS 译码器之前要明白的三件事

调试 802.11n 接收机物理层时,LDPC 译码器是绕不开的一环。很多人第一反应是直接调工具箱里的ldpcDecode,但一旦要往 FPGA 定点化、C 模型交叉验证或者自定义参数扫描方向走,就发现自己手里没有一个能逐行解释消息传递的参考实现。这个标题其实就是一条完整的技术链路:base41c 是 IEEE 802.11n 标准里 R=1/2、扩展因子 Z=81 的 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 中 R=1/2、Z=81 基矩阵的本地命名。这一类准循环 LDPC(QC-LDPC)矩阵有一个共同特征:H 由 Mb 行乘 Nb 列的子块构成,每个子块是 Z 乘 Z 的方阵。对 base41c 而言,码长 N=1944,信息位 K=972,校验位 M=972,因此 Z=81,基矩阵维度是 12 行乘 24 列,展开后就是 972 乘 1944 的稀疏矩阵。

基矩阵中的每个整数只表示三种含义:-1 代表全零子块,0 代表单位阵,正数 s 代表单位阵做了 s 位的循环移位。协议里约定移位方向和矩阵展开方式可能略有差异,但绝大多数 MATLAB 实现采用"行循环右移"的约定。拿到一个基矩阵文件,先别急着写展开代码,而是确认它的元素范围和行列数是否符合预期。

base 元素对应 Z×Z 子块备注
-1全零矩阵该位置无边连接
0单位阵无移位
s(1~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 表示全零子块,s>=0 表示循环移位量 % 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-s+1:Z, 1:Z-s], :); row0 = (m-1)*Z + 1; col0 = (n-1)*Z + 1; H(row0:row0+Z-1, col0:col0+Z-1) = sub; end end end end

这个函数把基矩阵中每个非负元素替换成对应的循环移位单位阵。sub = I([Z-s+1:Z, 1:Z-s], :)从单位阵中按行重排实现右移,当 s=0 时索引序列退化为1:Z,等于没移位。双循环虽然直观,但 12×24=288 个子块、每个 81×81,对一次初始化来说完全够快。

如果暂时拿不到 base41c 的原始数据文件,可以自己按 12×24 的维度从标准里的 LDPC 矩阵表录入,或者用较新版 Communications Toolbox 里的准循环矩阵对象导入标准基矩阵,再转成稀疏 H。重点是展开函数本身就具备通用性,未来换 Z=27、Z=54 的码字,只要改传入的 base 和 Z 即可。展开之后用issparse(H)确认存储格式,再用sum(H,2)检查行重分布,802.11n 的 R=1/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_n'm / 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,初始为通道 LLR

row_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(p+1: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; end

H * 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 在真实项目里最需要关注的性能边界。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询