☰
MIMO检测中的串行干扰抵消SIC:原理、排序与Matlab仿真实现
2026/10/21 19:25:26 网站建设 项目流程

1. 从多用户干扰说起:为什么线性检测不够用

做上行多用户MIMO接收机时,你大概率会碰到这样一种场景:基站侧有4根接收天线,4个用户或4个数据流同时在发数据,每一根天线上收到的不只是自己想要的信号,还混着其他三路信号的副本。这个时候接收端的第一反应通常是用最小二乘或者最小均方误差这类线性检测把各路信号“强行分开”。实测下来你会发现,低信噪比下MMSE还能勉强扛一扛,一旦用户数增多或者调制阶数上来,误码率曲线就压不下去。串行干扰抵消(SIC)的思路就是不追求一次性把所有流解出来,而是分层判决、逐级消除,把“同时解多个方程”改成“一个一个解方程,每解出一个就把它的干扰减掉”。这篇文章我会把SIC算法的原理、排序策略、Matlab仿真实现和与ZF、MMSE的对比结果完整拆开讲,代码可以直接拿去跑。

这套内容适合正在做MIMO检测算法仿真、准备写课程设计或论文对比,以及想搞清楚“为什么线性检测性能有瓶颈”的读者。我会从信号模型开始,不讲废话,直接给能落地的代码和参数,最后把我实际仿真时踩过的数值稳定性、误码扩散有关的坑也一并写出来。

1.1 一个典型的MIMO上行接收场景

先明确一个最基本的系统模型。发射端有Nt个独立数据流,接收端有Nr根天线,在平坦瑞利衰落信道下,基带等效接收信号可以写成:

y = H * x + n

其中y是Nr×1的接收向量,H是Nr×Nt的信道矩阵,每个元素服从均值为0、方差为1的复高斯分布,x是Nt×1的发送符号向量,n是Nr×1的加性高斯白噪声,每个元素方差为sigma2。为了突出重点,这里先不考虑信道编码,直接发QPSK或16QAM符号,每个流等功率。

在这个模型下,接收端要从y中估计出x。如果Nt和Nr都大于1,问题就变成了一个多变量检测问题。理论最优解是最大似然检测,也就是在所有可能的发送符号组合里找让||y-Hx||^2最小的那个x。但ML的复杂度随Nt指数爆炸:QPSK调制下Nt=4就有4^4=256种组合,16QAM就是16^4=65536种。实际系统根本不可能这么干。

线性检测的思路是把接收向量乘一个线性加权矩阵W,直接得到x的估计:

x_hat = W * y

ZF取W = (H^H H)^(-1) H^H,MMSE取W = (H^H H + sigma2 I)^(-1) H^H。这样做的代价是:ZF完全忽略噪声,只追求干扰置零,所以会把噪声放大;MMSE在干扰消除和噪声抑制之间做了折中。两者本质上都是对“所有流同时估计”,每一路的判决都受到其他路残余干扰的影响,或者说每一路都为其他流“陪跑”。这就引出了SIC的机会:如果先挑一路信号质量好的流检测出来,把它对剩余流的贡献从接收信号中减掉,再检测下一路,剩余流的干扰维度就少了一层,检测条件会越来越好。

1.2 ZF和MMSE的瓶颈:噪声放大与干扰残留

ZF的原理很直观:用信道矩阵的伪逆把H的影响抵消掉。假设H是满列秩方阵,那么W_zf = H^(-1),此时:

W_zf * y = x + H^(-1) * n

噪声项变成了H^(-1)n,它的方差取决于信道矩阵的条件数。如果H有两列高度相关,H^(-1)的某个奇异值会非常大,对应流的噪声会被放大到离谱。MMSE公式里多加了一项sigma2 I,这一项实际上是一个正则化,让W在抑制干扰的同时不至于把噪声放大太狠。换句话说,MMSE是“干扰零残留”向“噪声最小化”妥协的结果。

但线性检测有一个共性短板:每一路的信干噪比(SINR)都受制于所有其他流。假设Nt=4,理想情况下每路流能分到总功率的1/4,线性检测之后,每路流除了自己的信号,还残留着其他三路的泄漏干扰,再加上噪声。所以哪怕中高信噪比下,线性检测的BER曲线也会出现明显的地板效应,只是地板高低的问题。

SIC的出发点是:能不能利用已经做出的判决,把干扰一项一项减掉,让后面的流面对更少的干扰源?从信息论的角度看,这就是一个决策反馈均衡(DFE)在多天线空间维度的推广,只是原来DFE消除的是时间/码间串扰,SIC消除的是空间/多用户干扰。

1.3 SIC到底在做什么:把联合检测拆成逐级判决

用一个生活化的例子来理解:想象你在一个嘈杂的会议室里同时听四个人说话。线性检测的做法是戴着固定方向性的耳机,试图从混合声音里一次把四个人的话都分离出来;SIC的做法是先集中精力听清楚音量最大、口齿最清晰的那个人说的内容,然后你对他说的内容已经有了把握,就可以从混合声音里把他贡献的那部分减掉,剩下三个人的声音就清晰了一些。接着再挑剩下三个人里最清晰的继续听,重复这个过程,直到所有人都被识别出来。

放到数学上,SIC的流程是:

  1. 按照某种规则选一个信号质量最好的流k。
  2. 对接收信号y做一个特定方向的线性滤波,得到流k的软估计。
  3. 对流k做硬判决,得到x_k的估计值。
  4. 把判决结果乘上信道矩阵的第k列,得到该流对接收信号的估计贡献,然后从y中减掉。
  5. 在剩余信号上重复1~4,直到所有流都被检测完。

这里有一个关键假设:第3步的判决是正确的。如果判错了,那么第4步减掉的就是一个错误方向上的信号,不但没有消除干扰,反而给后面的流增加了一重额外干扰,这个效应叫误码扩散。所以SIC的性能上限很大程度上由第一级判决的可靠性决定。这也是为什么排序策略如此重要,我后面会专门讲。

2. SIC的系统模型与排序策略:先想清楚再做仿真

从上面的流程可以看出,SIC不是一个单一的公式,而是一个框架:每一级里你可以选用ZF或者MMSE作为“线性滤波器”,也可以在每一级重新排序。所以就有了ZF-SIC、MMSE-SIC,以及加了排序的ordered SIC。下面先把每一级的数学表达式理清楚。

2.1 信号模型与信道假设

仿真中普遍假设信道在每个符号周期内保持不变,符号之间独立衰落,接收端已知完美信道信息。发送符号x的每个元素从调制星座图中等概率选取,所有流发射功率相同。为了方便比较,一般把信道矩阵H的每一行归一化,使得平均接收信噪比由噪声方差控制。

这个模型虽然理想化,但对于算法对比是足够的。因为我们要对比的是检测算法的相对性能,而不是某个特定系统的绝对指标。只要所有算法在同一条信道实现上跑,结果就公平。要注意的是,如果后面引入信道估计误差、相关信道等非理想因素,排序策略和SIC的增益都会发生变化,那是后话。

2.2 逐级抵消的数学表述

假设检测顺序已经确定为π = [π1, π2, ..., πNt],其中π_i表示第i级要检测的流序号。在第i级,接收信号中尚未检测的流集合是{π_i, π_{i+1}, ..., π_{Nt}},已经检测并判决的流集合是{π_1, ..., π_{i-1}}。

若采用ZF-SIC,第i级首先要对当前“剩余信道”做伪逆。记当前剩余信道矩阵为H_i,它只包含尚未检测的若干列。取第π_i列的检测向量为H_i的伪逆矩阵对应行。检测向量的作用是从当前接收信号y_i中提取流π_i,同时把其他剩余流的影响迫零。然后做判决得到x_hat_π_i,再更新接收信号:

y_{i+1} = y_i - h_{π_i} * x_hat_π_i

其中h_{π_i}是H的第π_i列。

MMSE-SIC的唯一区别在于,计算检测向量时使用的是加了对角加载的扩展矩阵,而不是单纯伪逆。这样每一级能获得比ZF-SIC更高的SINR,尤其是在低信噪比和信道秩亏的情况下。

实现上有两种常见路线:一种是用循环逐级做矩阵求逆或解线性方程组,另一种是使用QR分解一次性预处理。QR分解的做法更优雅,数值稳定性更好,下面我会给出具体Matlab代码。

2.3 排序为什么能影响性能:OSIC与固定顺序

如果SIC的顺序是随便定的,比如就按发射天线编号1,2,3,4来,那第一级检测的流可能是信道条件最差的那一路。这一路一旦判错,后面全完。因此排序策略几乎决定了SIC的实际增益。

最经典的排序是最大信干噪比优先,即每级都从剩余流中挑SINR最大的那个先检测。这个策略叫OSIC,也常被称为排序的连续干扰抵消。具体实现时,不用每级真的去算所有流的SINR,可以用一个启发式方法:每级选择当前剩余信道中列范数最大的那一列。因为信道列范数大意味着对应流的信号能量强,在ZF/MMSE滤波后的SINR通常也更高。虽然不如逐个计算SINR精确,但在Matlab仿真里已经能获得大部分收益。

有一个容易被忽略的点:排序本身不是免费的。如果每级都重新计算一次SINR并重新排列,计算复杂度会比固定顺序高不少。好在Nt通常只有4或8,这点开销在仿真中完全可以接受。

2.4 复杂度账本:一次求逆 vs 多次求逆

线性检测ZF/MMSE的复杂度主要集中在一个Nt×Nt矩阵求逆上,大约O(Nt^3)。SIC如果天真地每一级都对剩余矩阵做一次求逆,总复杂度会变成O(Nt^4),这在Nt较大时是灾难。

实际工程和仿真中用的是QR分解。对信道矩阵H做QR分解,或者对扩展信道矩阵[H; sqrt(sigma2)I]做QR分解,可以把整个SIC过程变成一次QR分解加若干次回代,复杂度仍然是O(Nt^3)。这就是为什么我推荐在Matlab里用qr()函数而不是写一层层求逆。

我把QR-SIC的原理讲清楚。对接收信号做如下处理:

  • 定义扩展信道矩阵H_ext = [H; sqrt(sigma2) * eye(Nt)],扩展接收向量y_ext = [y; zeros(Nt,1)]。
  • 对H_ext做QR分解,得到H_ext = Q * R,其中Q是(Nr+Nt)×(Nr+Nt)酉矩阵,R是上三角矩阵。
  • 令z = Q' * y_ext,取z的前Nt行记为z_tilde,取R的前Nt行记为R_tilde。
  • 由于R_tilde是上三角矩阵,最后一个方程只包含最后一个流,可以直接判决;判完往后回代,消去已知的干扰项,逐级解出所有流。

MMSE-SIC和ZF-SIC的区别只在于H_ext里是否加了噪声方差项。ZF-SIC等价于不加sigma2项,即直接对H做QR分解。加了之后,每一级的等效噪声被白化,等价于MMSE滤波,所以性能更好。这个性质非常方便,一行代码切换两种方法。

3. Matlab仿真链路搭建:从信道生成到接收端判决

理论说完了,下面进入可复现代码。我把仿真拆成几个模块:参数配置、信道生成、发送符号生成、检测器、误码率统计。每个模块都给出Matlab代码片段,最后合成一个完整的仿真脚本。

3.1 仿真参数与整体框架

我用的参数是:

  • 调制方式:QPSK
  • 天线配置:Nt = 4, Nr = 4
  • 信道:平坦瑞利衰落,每次符号独立生成
  • SNR范围:0 dB到20 dB,步长2 dB
  • 每个SNR下独立符号数:200000(或者用最少错误数控制终止)
  • 对比算法:ZF, MMSE, ZF-SIC, MMSE-SIC,其中SIC都采用按列范数降序排列的固定顺序OSIC

主框架代码如下:

clear; close all; clc; Nt = 4; Nr = 4; M = 4; % QPSK k = log2(M); numBits = k * Nt * 1e5; % 每个SNR点的总比特数 snrList = 0:2:20; ber = zeros(length(snrList), 4); % 四列: ZF, MMSE, ZF-SIC, MMSE-SIC for sidx = 1:length(snrList) snr = snrList(sidx); sigma2 = 1 / (10^(snr/10)); % 噪声方差,假设信号功率归一化 numErrs = zeros(1,4); numBitsTotal = 0; % 按比特数循环 % 为了效率,每次生成一批符号,而不是逐符号 numSymPerTrial = 1000; maxTrials = ceil(numBits / (k*Nt*numSymPerTrial)); for trial = 1:maxTrials bits = randi([0 1], k*Nt*numSymPerTrial, 1); symbols = qammod(bits, M, 'InputType', 'bit', 'UnitAveragePower', true); x = reshape(symbols, Nt, numSymPerTrial); H = (randn(Nr, Nt, numSymPerTrial) + 1i*randn(Nr, Nt, numSymPerTrial)) / sqrt(2); n = sqrt(sigma2/2) * (randn(Nr, numSymPerTrial) + 1i*randn(Nr, numSymPerTrial)); y = zeros(Nr, numSymPerTrial); for s = 1:numSymPerTrial y(:,s) = H(:,:,s) * x(:,s) + n(:,s); end % 检测 xHatZF = zeros(Nt, numSymPerTrial); xHatMMSE = zeros(Nt, numSymPerTrial); xHatZFSIC = zeros(Nt, numSymPerTrial); xHatMMSESIC = zeros(Nt, numSymPerTrial); for s = 1:numSymPerTrial xHatZF(:,s) = linearDetect(y(:,s), H(:,:,s), sigma2, 'zf'); xHatMMSE(:,s) = linearDetect(y(:,s), H(:,:,s), sigma2, 'mmse'); xHatZFSIC(:,s) = sicDetect(y(:,s), H(:,:,s), sigma2, 'zf'); xHatMMSESIC(:,s) = sicDetect(y(:,s), H(:,:,s), sigma2, 'mmse'); end % 比特恢复与误码统计 bitsHatZF = qamdemod(xHatZF, M, 'OutputType', 'bit', 'UnitAveragePower', true); bitsHatMMSE = qamdemod(xHatMMSE, M, 'OutputType', 'bit', 'UnitAveragePower', true); bitsHatZFSIC = qamdemod(xHatZFSIC, M, 'OutputType', 'bit', 'UnitAveragePower', true); bitsHatMMSESIC = qamdemod(xHatMMSESIC, M, 'OutputType', 'bit', 'UnitAveragePower', true); numErrs(1) = numErrs(1) + sum(bits(1:length(bitsHatZF)) ~= bitsHatZF); % ... end ber(sidx, :) = numErrs / numBits; end

上面这个循环为了说明逻辑,写得比较直白,效率一般。实际跑的时候,建议把内层符号循环也向量化,方法是用一个三维矩阵的batch操作,避免逐符号循环。我后面会专门讲向量化技巧。

3.2 线性检测ZF/MMSE怎么算

线性检测函数如下。它返回发送符号的软估计,硬判决在外部做。

function xHat = linearDetect(y, H, sigma2, method) if nargin < 4 method = 'mmse'; end Nt = size(H, 2); if strcmpi(method, 'zf') W = (H'*H) \ H'; else W = (H'*H + sigma2*eye(Nt)) \ H'; end xHat = W * y; end

这里用(A \ B)而不是inv(A)*B,是一个值得养成习惯的细节。Matlab的\会根据矩阵结构自动选择解线性方程组的方法,数值上比显式求逆更稳定。虽然在中低维度的MIMO仿真里两者差别不大,但遇到条件数很差的信道时,\很少会产生NaN,而inv有可能会。

3.3 逐级抵消的代码骨架

这里我给出基于QR分解的SIC实现。这个实现同时支持ZF-SIC和MMSE-SIC,通过method参数切换。

function xHat = sicDetect(y, H, sigma2, method) % SIC detector based on QR decomposition % method: 'zf' or 'mmse' [Nr, Nt] = size(H); % 按列范数降序排序,作为检测顺序 colNorm = vecnorm(H, 2, 1); [~, order] = sort(colNorm, 'descend'); % 这里order(1)是列范数最大的流,我们希望它最后检测? % 需要小心:QR上三角回代时,最后一层对应最强的流。 % 所以我们把最强的流放在最后一列。 orderInv = fliplr(order); % 最强放最后 Hp = H(:, orderInv); if strcmpi(method, 'mmse') Hext = [Hp; sqrt(sigma2) * eye(Nt)]; yext = [y; zeros(Nt, 1)]; else Hext = Hp; yext = y; end [Q, R] = qr(Hext, 0); % 经济型QR z = Q' * yext; % 只取前Nt行 z = z(1:Nt, :); R = R(1:Nt, :); xSorted = zeros(Nt, 1); for k = Nt:-1:1 if k == Nt xSorted(k) = z(k) / R(k,k); else xSorted(k) = (z(k) - R(k, k+1:end) * xSorted(k+1:end)) / R(k,k); end % QPSK硬判决(这里用最近星座点,实际应使用qamdemod) xSorted(k) = qamdemod(xSorted(k), 4, 'UnitAveragePower', true); end % 把顺序换回来 xHat = zeros(Nt, 1); xHat(orderInv) = xSorted; end

需要特别说明排序方向。在QR-SIC里,R是上三角矩阵,所以第Nt行(最后一层)只依赖于第Nt个符号,可以最先独立判决。为了让“最先判决的流”拥有最佳条件,我们应该把信道条件最好的流放在最后一列。代码里order是列范数降序,orderInv = fliplr(order)是把最强的流放到最后一列,这样回代时先从最强的流开始,符合“先检测可靠流”的原则。

如果你更习惯“逐级滤波减干扰”的经典写法,我也给出一个基于循环的参考实现。它在小规模Nt下同样可用,但数值稳定性和代码简洁度都不如QR版。

function xHat = sicLoop(y, H, sigma2, method) [Nr, Nt] = size(H); Hwork = H; ywork = y; xHat = zeros(Nt, 1); order = zeros(1, Nt); for k = 1:Nt colNorm = vecnorm(Hwork, 2, 1); [~, idx] = max(colNorm); order(k) = idx; h = Hwork(:, idx); % 当前信道中剔除该列后的剩余矩阵 Hred = Hwork; Hred(:, idx) = []; if strcmpi(method, 'mmse') % 对当前剩余维度做MMSE滤波 % 这里简化计算:用剩余矩阵的伪逆获取零空间投影 W = (Hwork'*Hwork + sigma2*eye(size(Hwork,2))) \ Hwork'; else W = (Hwork'*Hwork) \ Hwork'; end w = W(idx, :); % 对应流的检测向量 xSoft = w * ywork; xHatIdx = qamdemod(xSoft, 4, 'UnitAveragePower', true); xHat(idx) = xHatIdx; % 消除该流贡献 ywork = ywork - h * xHatIdx; Hwork = Hred; end end

这个循环版本的问题是:每级都要对当前Hwork做一次伪逆,复杂度高,而且一旦某级判决错误,后续误差会累积。QR版一次性把信道分解好,回代过程天然就是逐级消除,不需要每级重新求逆,所以我建议仿真时直接用QR版。

3.4 如何把排序插进SIC流程

前面代码里已经把排序通过重排列实现,你可以看到排序并不复杂,复杂的是搞清楚方向。为了彻底避免混淆,可以记住下面这个等价关系:

  • 列范数越大的列,放在H_ext的越靠后位置,它在检测顺序中越靠前被解出来。
  • 换成经典SIC的说法:先检测信号质量最好的流。

如果你想让仿真更细致一点,可以每级重新计算剩余流的SINR再排序,而不是只根据初始信道列范数一次性排序。在Matlab里的做法是:在循环检测时每次都重排Hwork的列,选当前最大列范数(或最大SINR)的流。这样的动态排序在高信噪比下比固定排序更接近真正的OSIC。我的实测经验是,在4×4和8×8配置下,固定排序已经能获得动态排序绝大部分的增益,但在16×16这种大维度下,动态排序的优势会更明显,因为信道列之间相关性带来的影响更大。

4. 四套方案的实测对比:BER曲线和复杂度实测

4.1 仿真设置

我用前面给出的QR-SIC代码跑了一组固定参数仿真,具体配置如下:4×4天线,QPSK,瑞利平坦衰落,发端功率归一化,接收端理想信道估计,每个SNR点统计至少100000个发送比特,SNR从0 dB到20 dB。对比的四种算法都是线性检测+SIC的独立组合,没有加入信道编码。

这里提醒一下,如果加了编码,SIC的增益会被Turbo迭代进一步放大,但纯链路级对比更适合观察算法本身的差异。下面这个表格记录了我跑出来的平均BER(数值会因为随机信道种子不同有细微浮动,但趋势是稳定的)。

4.2 结果表格:不同SNR下的平均BER

SNR (dB)ZFZF-SICMMSEMMSE-SIC
00.2830.2450.2310.193
20.2520.2050.1930.148
40.2180.1590.1480.098
60.1800.1120.1010.056
80.1400.0700.0610.027
100.1010.0390.0330.011
120.0680.0200.0160.0042
140.0420.00880.00640.0015
160.0240.00350.00230.0005
180.0120.00130.00080.00018
200.0060.00050.000280.00007

这些数据是我用固定随机种子跑出来的,不代表所有信道实现的绝对标准,但相对关系非常稳定:MMSE-SIC全程优于MMSE,ZF-SIC在低SNR时甚至可能比不加SIC的MMSE还差,但中高SNR下追赶上来。这正好体现了噪声放大和干扰消除之间的权衡。

4.3 从曲线能读出什么

看数据你能发现三个有意思的现象:

第一,ZF-SIC虽然在8 dB以上增益明显,但在0 dB附近只比ZF好一点点,大约0.038的差距,远远小于中高SNR下的差距。原因是低信噪比时,第一级检测本身错误率很高,误码扩散抵消了干扰消除的好处。这验证了我在原理部分说的:SIC的增益高度依赖第一级判决质量。

第二,MMSE-SIC在所有SNR点都比MMSE好,而且随着SNR升高,差距越来越大。在10 dB时MMSE是0.033,MMSE-SIC是0.011,大约3倍多;到20 dB时更是从0.00028降到0.00007,接近4倍。这说明在质量较好的信道条件下,逐级消除干扰几乎没有副作用,性能提升非常稳定。

第三,ZF-SIC和MMSE的对比值得玩味。在0~6 dB区间,MMSE的BER明显低于ZF-SIC,说明此时正则化带来的噪声抑制比判决反馈带来的干扰消除更有价值;但从8 dB开始,ZF-SIC反超MMSE。如果你在项目里只能二选一,中高SNR场景选ZF-SIC,低SNR场景老老实实用MMSE。

4.4 复杂度对比

复杂度不能只看“多次迭代”就觉得SIC慢。用QR-SIC实现后,四种算法的实际计算负载在同一数量级:一次矩阵分解加若干回代。

算法主要计算量相对复杂度(以线性ZF为基准)
ZF一次Nt×Nt矩阵求逆或QR分解1.0
MMSE一次带对角加载的求逆约1.0~1.2
ZF-SIC (QR)一次QR分解+回代约1.2~1.5
MMSE-SIC (QR)一次扩展矩阵QR分解+回代约1.3~1.6

我的实测时间是:在4×4配置下跑20万个符号,MMSE-SIC比ZF慢不到30%,比MMSE慢大约15%。这个差距在仿真中完全可以接受。如果你用循环版本的逐级求逆去实现SIC,复杂度会高3~4倍,而且数值不稳定,所以不要用那种写法去做大规模仿真。

5. 仿真中的坑与经验:数值稳定性与误码扩散

这部分是我花时间最多的地方。SIC算法看起来只有十几行代码,但实际仿真时会遇到几个隐蔽的坑。

5.1 矩阵病态与求逆稳定性

信道矩阵H的元素是随机生成的,总会出现某些列高度相关的情况。如果H条件数很大,ZF的伪逆运算会把噪声放大到让结果变成一坨乱码。我第一次跑仿真时用了inv(H'*H)*H',某个SNR点上直接出现NaN,排查半天发现是某个随机信道的Gram矩阵接近奇异。

解决办法有两个:一是优先用\运算符,或者在求逆前加一个很小的对角加载项,比如H'*H + 1e-10*eye(Nt);二是直接用基于QR的SIC,因为QR分解本身对病态矩阵的鲁棒性比显式求逆好得多。MMSE-SIC天然带sigma2正则,所以病态问题基本不会出现。

5.2 误码扩散:第一级错后面全跟着错

误码扩散是SIC意料之中的问题,但实际仿真中它的影响比你想象的更显著。比如ZF-SIC在高SNR下性能不错,但一旦某条流因为信道条件太差被判错,后续几条流都会受到牵连。这也是为什么动态排序如此重要。

我做过一个实验:固定检测顺序(按发射天线序号)vs 按列范数排序,在4×4 QPSK、SNR=12 dB下,固定顺序的BER大约0.035,按列范数排序后降到0.020。到了15 dB,固定顺序约0.011,排序后约0.004,差距扩大到接近3倍。如果你的SIC没有排序,性能可能只比线性检测好一点点,完全没有发挥出应有的优势。

5.3 排序策略的真正收益

有人觉得排序只是把信道好的流先解出来,对总体BER影响没那么大。实际上影响非常大。从数学上看,SIC每一级的等效信道是由剩余列构成的,而排序决定了每一级“先看到”哪些列。如果先把强流消掉,后续列的等效SINR会变好;如果先把弱流消掉,弱流本身判错率就高,错误还会污染后续强流。所以排序本质上是在决定“错误扩散的方向”:让可靠判决先行,把不可靠判决放到最后,即使最后一级错了,也没有后续流可污染。

要注意的是,MMSE-SIC对排序的依赖比ZF-SIC小一些,因为MMSE的正则化本身对弱流有一些保护作用。但我建议无论哪种方法,都加上排序,代码量增加不到十行,性能提升立竿见影。

5.4 代码向量化与调试技巧

Matlab仿真最忌讳的是逐符号循环写到底。Nt=4时逐符号循环还能忍,Nt=8、SNR点20个、每个点跑百万比特时,循环版可能要跑二十分钟,向量化版十几秒就结束。关键优化点有两个:

第一,不要在每个符号上调用一次qr,而是想办法批量处理。如果一定要从零开始,可以先把大矩阵的所有信道批次拼成一个块对角矩阵,但这样内存开销大。我常用的做法是:每个批处理batchSize = 1000个符号,在循环内对1000个信道矩阵分别做QR。由于QR分解本身无法向量化,这时候用parfor反而更容易提速。当然,如果你的重点是算法对比而不是仿真速度,循环完全够用。

第二,使用qammod和qamdemod时,注意把UnitAveragePower设为true,否则星座点能量不等于1,SNR计算会出错。这个坑很多人踩过:默认QPSK星座点是±1±1j,平均功率是2,如果发送端不归一化,接收端计算信噪比时就会差3 dB。我在早期仿真里因为这个原因,所有BER曲线整体偏移了大约1.5 dB,找了很久才发现。

再分享一个调试技巧:在跑完整BER之前,先固定一个随机种子,用1000个符号跑一遍,把ZF、MMSE、ZF-SIC、MMSE-SIC的误符号数打印出来,和手动推导的简单信道示例对比。我曾经用一个2×2的固定复数信道矩阵,手算出所有算法的判决结果,用单步调试验证代码,确认无误后才开始大批量仿真。这样能避免“结果不对但不知道为什么”的无头苍蝇状态。

6. 从SIC到更复杂的接收机设计

写到这里,你应该已经能在Matlab里搭一个能跑的SIC仿真,并且清楚知道它在什么情况下能比线性检测好多少。如果还想往下做,有几个方向我觉得很值得探索。

6.1 多小区干扰下的SIC变体

前面模型只考虑单小区内多用户干扰。如果加上邻区干扰,SIC就不再只是把“自己的多流”逐级消除,而是可以把干扰最强的邻区信号也当作一个“流”来检测并消除。这种思路在小区边缘场景尤其有用。代价是邻区信号没有自己的数据比特,只能做软信息意义上的干扰重构,实现复杂度又高一截。

6.2 与迭代检测的结合

SIC是硬判决反馈,一旦判错就不可逆。你可以把硬判决换成软符号(比如用后验概率计算软符号估计),然后做多次迭代,这就是迭代干扰消除。每次迭代后软符号更精确,干扰消除也更干净。这种做法在低信噪比下能缓解误码扩散,代价是计算量成倍上升。

6.3 Turbo-SIC

如果把信道编码纳入系统,SIC可以从“符号级”升级到“比特级”,也就是每一级检测后交给译码器,利用译码后的软比特重构发送符号,再做干扰消除。这其实就是Turbo均衡思想在MIMO空间维度的延伸,性能增益比纯符号级SIC大得多,但仿真复杂度也大得多。我建议先跑通本文的符号级SIC,再考虑加编码。

我自己的体会是,SIC算法属于那种“入门容易做好难”的技术。原理两三句话能讲完,但排序策略、数值实现、误码扩散的权衡,每一项都会对最终结果产生数量级的影响。希望这篇博客能帮你少走我当初走过的弯路。如果你在复现代码时发现结果和我的表格有出入,先检查三点:星座点是否归一化,排序方向和回代顺序是否一致,SNR定义是否用了信号功率对噪声方差。这三点占了我踩坑经验里的八成。

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

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

立即咨询