简介:SCMA稀疏码多址接入技术PDF文档源自5G算法大赛赛题任务描述,适合通信工程学生、5G物理层研究人员及算法竞赛参赛者阅读。文档先阐述4G OFDMA正交多址的局限性,再引出SCMA在5G大容量、海量连接、低时延场景下的非正交接入优势,并说明发送端比特到多维码字的稀疏映射、接收端低复杂度多用户联合检测等核心流程。围绕赛题,文档明确了简化SCMA上行多接入系统的设计文档、Matlab/C仿真及BER性能瀑布曲线、FPGA逻辑设计与实现三项交付物,同时列出初赛与复赛评选标准,例如译码正确率超过99.9%、仿真与实测BER曲线误差小于1dB等。整包仅含1个PDF文件,大小3.46MB,内容系统且附带相关IEEE论文线索,便于进一步研读。已有354人学习下载,对于希望快速理解SCMA原理、梳理5G算法竞赛路线或规划FPGA实现的读者,是一份不错的入门与任务指引。
1. 设备密度一旦上去,正交资源就不够分了
正交多址的底层逻辑是“一个资源只服务一个用户”,这在 eMBB 场景下没有问题,但到了 mMTC 和 6G 的海量连接场景就扛不住了。智能电表、工业传感器、可穿戴设备都以短包、低占空比的方式涌进网络,如果每个终端独占一个时频资源块,频谱效率会低到一个不可接受的水平。SCMA(Sparse Code Multiple Access,稀疏码多址接入)绕开了“切资源”的思路,改为让多个用户在同一个资源块上发送信号,接收端利用码本的稀疏结构和迭代检测把混叠的信号拆出来。换句话说,它把干扰从需要回避的问题变成了解调时可以依赖的结构。这篇博文面向做物理层算法验证和链路仿真的工程师,也面向在 NOMA 方向选题的研究生,核心目标是把码本设计、MPA 检测、参数选型和仿真验证这条链路完整走通。
2. 码本结构怎么设计:稀疏性、因子图和比特映射
2.1 稀疏码字与叠加模型:从单用户码本到多用户叠加
SCMA 里每个用户有一个专属码本,码本里包含 M 个码字,每个码字对应 log2(M) 个比特。码字本身是一个 N 维复数向量,但只有 K 个非零分量(K 远小于 N),这就是“稀疏”二字的来源。非零分量的位置不是随机选的,而是经过设计后固定下来的,接收端正是靠这个位置关系把多用户的叠加信号分开。
一个教学化的 4 点码本(N=4,K=2,M=4)可以这样构造:从 QPSK 星座点 {0.707+0.707i, 0.707-0.707i, -0.707+0.707i, -0.707-0.707i} 中取两个不同相位,映射到预定义的两个非零资源位置上。下表给出其中一个用户码本的完整数值,注意这只是可复现的教学示例,工程使用的码本还会额外做旋转、功率归一化和互相关优化。
| 比特对 | 码字(4 维复数向量) |
|---|---|
| 00 | [0.0, 0.0, 0.707+0.707i, 0.707-0.707i] |
| 01 | [0.0, 0.0, -0.707+0.707i, -0.707-0.707i] |
| 10 | [0.0, 0.0, 0.707-0.707i, 0.707+0.707i] |
| 11 | [0.0, 0.0, -0.707-0.707i, 0.707+0.707i] |
[ \mathbf{y} = \sum_{j=1}^{J} \operatorname{diag}(\mathbf{h}_j) \mathbf{x}_j + \mathbf{n} ]
其中 J 是用户数,x_j 是用户 j 选择的码字向量,h_j 是该用户的信道增益向量,n 是高斯白噪声。这个式子的含义是:所有用户的码字在资源块上直接叠加,互不回避碰撞。非零分量只占 K/N 的比例,所以每个资源块上实际碰撞的用户数远小于总用户数,这让接收端的检测复杂度保持在可控范围内。
2.1.1 一个最小可用的 4 点码本示例
给每个用户分配一组不同的非零位置和非零值后,多用户系统才能工作。假设 4 个资源块、6 个用户的配置,每个用户占用 2 个资源块,可以得到如下因子图矩阵,行表示资源块,列表示用户,1 表示该用户在该资源块上有非零分量:
用户编号: 1 2 3 4 5 6 资源块1: 1 1 0 0 1 1 资源块2: 0 1 1 0 1 1 资源块3: 1 0 1 1 0 0 资源块4: 0 0 0 1 1 1这个矩阵是关键设计输入。每一列恰好有 2 个非零元,说明每个用户只干扰 2 个资源块;每一行有 3 个非零元,说明每个资源块上有 3 个用户叠加。因子图的行重和列重直接决定了 MPA 检测的复杂度,行重越小,接收端计算量越低,但用户可区分的空间也越小,具体怎么平衡放在第 4 章展开。
2.2 因子图矩阵:谁和谁在同一个资源块上冲突
因子图矩阵是稀疏性的直接体现,它也决定了干扰拓扑。还是用上面的矩阵举例:资源块 1 上有用户 1、2、5、6,做 MPA 检测时,资源块 1 这个功能节点只需要处理这 4 个用户的叠加信号,不需要关心其他资源块上发生了什么。这种局部性就是“稀疏”带来的核心收益。
因子图设计最常见的要求是收敛性和可辨识性。比如两个用户如果非零位置完全相同,它们对应同一组资源块,接收端只能依赖码字值的差异来区分,BER 性能会明显变差。我一般会遵循两个经验原则:第一,不同用户的非零位置集合尽量错开,让因子图呈循环结构但不存在长度过短的环;第二,每个资源块上的碰撞用户数控制在 3~4 个,再多的话 MPA 的每个功能节点就要枚举 4^4=256 种组合,迭代一轮的耗时立刻上涨一个量级。
3. 接收端怎么检测:MPA 消息传递算法的可运行实现
3.1 MPA 迭代的三条核心更新规则
SCMA 接收端的核心是多用户检测,而 MPA 是处理高维叠加检测的标准做法。它把因子图上的节点分成两类:变量节点(VN)代表用户码字的选择,功能节点(FN)代表资源块上的观测约束。算法在两类节点之间互相传递消息,迭代若干轮后,每个用户码字的后验概率会收敛到一个稳定值。
三条更新规则需要记牢。第一,VN 发给 FN 的消息:新消息等于该用户其他非零资源位置上 FN 消息的乘积,再乘以该码字的先验概率。第二,FN 发给 VN 的消息:对该资源块上所有其他用户的码字组合做全枚举,计算每个组合下叠加信号与接收值的高斯距离,把对应的 VN 消息乘进来再求和。第三,消息归一化:每一轮更新后消息都要归一化到和为 1,避免连乘导致概率值指数级下溢。
3.2 一个最小 MPA 解码器的 Python 实现
下面是一个教学化的 MPA 解码实现,输入是接收向量、信道、码本列表、噪声方差,输出是每个用户的软比特信息。代码刻意省略了复杂度优化,只保留算法骨架,方便对照更新公式检查:
import numpy as np from itertools import product def mpa_decode(y, h, codebooks, sigma2, max_iter=10): J = len(codebooks) # 用户数 N = y.shape[0] # 资源块数量 M = codebooks[0].shape[0] # 每个用户的码字个数 # 用户 j 的非零资源位置 user_res = [np.nonzero(np.abs(codebooks[j][0]) > 1e-12)[0] for j in range(J)] # v_msg[j][r] 表示用户 j 在资源 r 上的 vn->fn 消息 v_msg = [[np.ones(M) / M for _ in range(len(user_res[j]))] for j in range(J)] # u_msg[r][pos] 表示资源 r 上第 pos 个用户的 fn->vn 消息 res_users = [[] for _ in range(N)] for j in range(J): for r in user_res[j]: res_users[r].append(j) u_msg = [[np.ones(M) / M for _ in res_users[r]] for r in range(N)] for _ in range(max_iter): # ---- fn 更新 ---- for r in range(N): users = res_users[r] for idx_j, j in enumerate(users): local_msg = np.zeros(M) # 枚举当前资源 r 上所有用户的码字组合 for comb in product(range(M), repeat=len(users)): # 计算叠加信号与 y[r] 的高斯距离 sig = sum(codebooks[users[k]][comb[k]][r] * h[users[k]][r] for k in range(len(users))) weight = np.exp(-np.abs(y[r] - sig)**2 / sigma2) for k in range(len(users)): if k != idx_j: weight *= v_msg[users[k]][r][comb[k]] local_msg[comb[idx_j]] += weight u_msg[r][idx_j] = local_msg / local_msg.sum() # ---- vn 更新 ---- for j in range(J): for idx_r, r in enumerate(user_res[j]): local_msg = np.ones(M) for k in range(len(user_res[j])): if k != idx_r: local_msg *= u_msg[r][k] v_msg[j][idx_r] = local_msg / local_msg.sum() # ---- 软判决 ---- llr = np.zeros(J * int(np.log2(M))) for j in range(J): post = np.ones(M) for idx_r in range(len(user_res[j])): post *= u_msg[user_res[j][idx_r]][idx_r] post /= post.sum() for bit in range(int(np.log2(M))): llr[j * int(np.log2(M)) + bit] = np.log( post[1:].reshape(-1, 2).sum(axis=1)[bit] / post[::2].sum()) return llr这段代码把上一节的更新规则逐条落了地。功能节点更新的核心是内层循环:对当前资源块上的所有用户枚举码字组合,组合数量是 M^d,d 是该资源块的用户数。以 M=4、d=3 为例就是 64 次距离计算,单轮迭代还能接受;如果 d 涨到 4,就变成 256 次,复杂度上升明显。变量节点更新只需要做乘积和归一化,代价可以忽略。最后软判决阶段把所有功能节点消息乘起来,得到每个码字的后验概率,再换算成每个比特的对数似然比。
参数方面,sigma2 是噪声方差,取值直接来自信道估计模块。h[j] 是用户 j 的信道系数向量,在 AWGN 场景下是全 1 向量,在衰落场景下换成实际信道估计结果即可。max_iter 决定迭代轮数,经验值是 8~12 轮,继续增大对 BER 收益很小。
3.3 信道噪声方差怎么进消息
噪声方差进入消息的方式是通过高斯距离的指数项。看功能节点更新的 weight 计算,指数部分包含了 1/sigma2,这说明 sigma2 的取值直接影响消息的置信度。如果 sigma2 被低估,高噪声样本会被赋予过大的权重,迭代容易不收敛;如果被高估,消息趋近均匀分布,需要更多轮次才能收敛。
实际工程里,sigma2 通常由导频符号估计得到。如果你的链路仿真里噪声功率是逐帧变化的,我建议把 sigma2 作为参数传入 mpa_decode 而不是在函数内部写死,否则你很难排查究竟是检测器的问题还是噪声估计的问题。
4. SCMA 参数怎么调:过载率、码本大小与迭代次数的选择
4.1 过载率 150% 是怎么来的:N=4、J=6 的经典配置
过载率定义为用户数 J 除以资源块数 N,它是 SCMA 系统最重要的全局指标。经典配置 N=4、J=6 对应过载率 150%,意思是平均一个资源块要服务 1.5 个用户。这个数字不是拍脑袋定的,而是在译码性能、实现复杂度和资源利用率之间取了一个平衡点。继续往上加用户,比如 N=4、J=8,每个资源块上的碰撞用户数变成 4,MPA 的枚举复杂度会从 M^3 变成 M^4,性能损失也同步扩大。
| 配置 | 过载率 | 每资源碰撞用户数 | 每 FN 每轮组合数(M=4) | 复杂度量级 |
|---|---|---|---|---|
| N=4, J=4 | 100% | 2~3 | 64 | 低 |
| N=4, J=6 | 150% | 3 | 64 | 中 |
| N=4, J=8 | 200% | 4 | 256 | 高 |
| N=8, J=12 | 150% | 3 | 64 | 中(资源数翻倍) |
从实现角度,N=4、J=6 的配置还有一个实际好处:它恰好对应 6 个用户共享 4 个 RE,符合 LTE 一个资源块组的大小,方便和现有物理层调度框架对接。在 6G 研究中常见做法是把 N 做到 8 或 16,以获得更大的分集增益,但仿真时间会线性上升。
4.2 码本大小 M 和调制阶数的取舍
M 决定每个用户一次传输携带的比特数。M=4 对应 2 比特/用户/资源块组,M=8 对应 3 比特,M=16 对应 4 比特。M 越大,频带利用率越高,但 MPA 的枚举复杂度按指数增长。在每资源碰撞用户数 d=3 的情况下,M 从 4 涨到 8,单资源块组合数从 64 涨到 512,一轮迭代的计算量是原来的 8 倍。
我的建议是优先保持 M 不变,通过增加资源块数 N 来提升频谱效率,而不是简单调大 M。因为增大 M 同时会影响码本的设计自由度——需要保证 M 个码字之间的最小欧氏距离不随维度压缩而劣化,这在实际码本搜索时很快就会碰上性能墙。
4.3 迭代次数与收敛判据:看残差还是固定次数
MPA 的迭代次数是接收机最直接的复杂度控制旋钮。固定次数最简单,我一般在 AWGN 下用 8 轮,衰落信道下用到 10~12 轮。但不是所有帧都需要这么多轮次,高信噪比下往往 4 轮就已经收敛。
更好的做法是看消息残差:每次迭代后计算相邻两轮 v_msg 之间的平均绝对值差,当这个差小于 1e-6 时提前终止。这个判据在实现时只需要多保存一组上一轮的 v_msg,内存和计算开销都很小。注意残差阈值不能设得太小,否则高信噪比下也会白白多跑好几轮。
5. SCMA 链路仿真怎么做:从发端到 MPA 解码的全流程
5.1 仿真链路骨架:从比特到 BER 的完整流程
一个完整的 SCMA 链路仿真最少由六个环节组成:比特生成、码本映射、稀疏叠加、信道加噪、MPA 解码和误码率统计。下面是 AWGN 信道下的最小主循环实现:
def simulate_awgn(ebno_dB, J=6, N=4, M=4, n_frames=1000, iter_max=10): rng = np.random.default_rng(42) codebooks = generate_codebooks(J, N, M) # 生成码本列表 sigma2 = 1.0 / (10 ** (ebno_dB / 10)) total_bits = 0 err_bits = 0 for _ in range(n_frames): bits = rng.integers(0, 2, size=J * int(np.log2(M))) # 每个用户随机选码字 tx = np.zeros((J, N), dtype=complex) for j in range(J): idx = bits[j*2] * 2 + bits[j*2+1] tx[j] = codebooks[j][idx] y = tx.sum(axis=0) + np.sqrt(sigma2/2) * ( rng.standard_normal(N) + 1j*rng.standard_normal(N)) llr = mpa_decode(y, np.ones((J, N)), codebooks, sigma2, iter_max) hard = (llr > 0).astype(int) err_bits += np.sum(hard != bits) total_bits += len(bits) return err_bits / total_bits代码里的 sigma2 做了一次换算,把 Eb/N0 从 dB 值转成噪声方差,这个环节和信道编码是否存在强相关。如果链路里加了 LDPC 或 Turbo 码,这个函数输出的 LLR 还要除以 2 变成正确的对数似然比形式,再送给译码器,否则误码率会偏高。
跑这个仿真时,千万不要一上来就用大参数。先用 N=4、J=6、M=4、帧数 100 跑通流程,确认 BER 曲线的形状符合预期,再逐步加大帧数到 10000 以上,否则低误码率区间扫不出来。
5.2 验证 MPA 实现正确性的三个中间检查点
MPA 代码写完后,验证正确性和调试同样重要,直接看 BER 曲线不够直观。我建议按下面三个检查点逐项排查。
第一个检查点是退化测试:把用户数设为 1,此时 MPA 应该退化成 QPSK 的相干解调。你可以用 QPSK 理论误比特率曲线做对照,如果匹配,说明码本映射、噪声方差和 LLR 换算没有问题。
第二个检查点是概率归一化:在每一轮迭代结束后打印 v_msg 和 u_msg 的和,正常情况下应该严格等于 1(浮点误差在 1e-6 以内)。如果出现和值漂移,多半是归一化时用了除法而不是先做稳定化处理,比如给分母加一个小常数。
第三个检查点是无噪声重建:把 sigma2 设为极小值(如 1e-20),发送一组已知比特,MPA 应该在 2~3 轮迭代后完全恢复出所有用户的数据。如果连这个都过不了,问题一定出在功能节点更新的距离计算或者组合枚举逻辑上,不需要继续看 BER。
5.3 从 AWGN 到衰落信道:MPA 要改哪几行代码
切换到衰落信道时,接收向量变成 y[r] 等于所有用户码字乘上各自信道系数再叠加。需要改动的点很少:一是把 h 从全 1 数组换成每用户不同且逐资源块变化的复系数,二是在功能节点更新的 sig 计算里乘上 h[users[k]][r]。
需要注意,在深衰落场景下某些资源块上的等效信噪比会很低,MPA 可能不收敛。常见做法是在迭代前对每个资源块的信号做最小均方误差均衡,用均衡后的信号作为 MPA 的初始观测量,而不是直接把原始 y 塞进去。这种方式可让高信噪比段的 BER 曲线更陡,但要注意均衡本身会放大噪声。
6. 进阶玩法:SCMA 收敛提速、码本优化与联合迭代
6.1 对数域 MPA 与消息调度
概率域的 MPA 在低信噪比下容易出现消息值下溢,规避办法是转到对数域。对数域里乘法变成加法,权重计算变成累加距离项,数值稳定性更好。实现时每轮迭代只需要把高斯距离的实部累加到 local_llr 上,最后统一做减最大值操作再还原概率。实测在相同迭代轮数下,对数域实现可以比概率域降低一轮迭代所需的信噪比门限约 0.2~0.3 dB,在高阶码本下更明显。
消息调度的思路是优先更新残差大的功能节点。每次迭代完成后,记录每个功能节点消息的更新幅度,下一轮从幅度最大的节点开始。这种动态调度的代价是引入排序开销,但常常可以把平均迭代轮数从 10 降到 6 左右,信噪比越高收益越明显。
6.2 与信道编码联合迭代的接收机架构
SCMA 单独使用时的性能有限,真正发挥潜力的是把它当作内码,和外部的 LDPC 码做联合迭代。做法是:SCMA 检测器输出每个比特的 LLR,送入 LDPC 译码器;LDPC 译码若干轮后,把外信息反馈回 SCMA 检测器作为码字先验概率,然后重新做 MPA 迭代。整个过程形成一个 Turbo 型接收机。
实现时要注意外信息的换算:SCMA 输出的 LLR 减去上一轮的先验 LLR 才是真正的外信息,直接传递后验 LLR 会导致正反馈发散。我一般会在检测器和译码器之间接一个简单的减法节点,并用一个阻尼因子(0.7 左右)来稳定迭代。验证联合迭代是否有效,可以画一条 EXIT 曲线,看检测器曲线和译码器曲线之间是否有开口,有开口就说明迭代增益存在。
先固定发端码本跑单独的 MPA 链路,把 BER 工作点摸清楚之后,再挂上 LDPC 做联合迭代,注意力放在外信息交换和阻尼系数的调参上。
本文还有配套的精品资源,点击获取