我一直觉得MIMO检测器的BER仿真,是通信方向学生和工程师绕不开的一块硬骨头。最近我把ZF、MMSE、SIC、ML这四种检测器在瑞利衰落信道下的误码率性能从头到尾梳理了一遍,把整套Matlab仿真代码跑通,还整理成了一份完整报告。这篇文章就把我的完整思路、关键代码、调试经验和最终曲线对比一次性写清楚,给正在做MIMO仿真、或者准备写课程设计/毕业设计的同学一份可以直接照着做的参考。
先说这套方案能解决什么痛点:很多教材讲MIMO检测,公式推导一大堆,但落到Matlab实操时,大家最常卡在三个地方——信道矩阵怎么生成、四种检测器的核心公式怎么翻译成代码、蒙特卡洛仿真怎么调参数才能又快又准。我这份代码就是把“从零搭建2x2/4x4 MIMO系统模型 -> 实现四种检测器 -> 统计BER曲线 -> 生成对比分析报告”的完整链路跑通,你拿过去改改天线数、换换调制方式,就能直接用在你的场景里。
1. 仿真系统模型搭建:先把地基打牢
1.1 收发天线配置与系统模型
在动手写代码之前,我建议先把系统模型在纸上理清楚。一个典型的空间复用MIMO系统,可以用下面的复基带模型来表示:
[ \mathbf{y} = \mathbf{H}\mathbf{x} + \mathbf{n} ]
其中(\mathbf{x})是发射符号向量,维度是(N_t \times 1),(N_t)是发射天线数;(\mathbf{y})是接收符号向量,维度是(N_r \times 1),(N_r)是接收天线数;(\mathbf{H})是(N_r \times N_t)维的信道矩阵;(\mathbf{n})是独立同分布的复高斯白噪声向量,每个元素均值为0,方差为(\sigma_n^2)。
我在仿真里主要用了两组配置:一组是2发2收(2x2),另一组是4发4收(4x4)。选这两个配置的原因很直接:2x2系统计算量小,适合初学者验证算法逻辑是否正确;4x4系统则能明显看出不同检测器之间性能差距的拉大过程,特别是ZF和MMSE在高阶天线配置下的对比效果更清晰。
发射端每次发送一个符号向量之前,我会先把二进制比特流做QPSK调制,然后按“发射天线数”分组送入信道。这里有个细节容易踩坑,就是调制映射表的归一化处理。QPSK四个星座点如果直接用1+1j、1-1j这种坐标,发射功率就是2,而噪声方差是按单位功率设计的,这会导致仿真结果整体偏移。正确的做法是把星座点归一化,让平均功率为1,映射表我建议写死:
% QPSK调制映射,平均功率归一化为1 QPSK_MAP = [1+1j, 1-1j, -1+1j, -1-1j] / sqrt(2);这个除以根号2的细节,很多初学仿真的同学容易漏掉。漏掉之后,仿真出来的BER曲线整体往左偏移约3dB,看起来性能更好,实际上是发射功率变大导致的假象。
1.2 瑞利衰落信道建模与信噪比设定
瑞利衰落信道模拟的是城市密集环境、室内多径散射这类没有直射路径的传播场景。每个信道系数都是两个独立高斯随机变量的组合,也就是实部和虚部各服从标准正态分布,然后合成一个复高斯变量,再做幅度归一化。Matlab里面最常用的写法是:
H = (randn(Nr, Nt) + 1j*randn(Nr, Nt)) / sqrt(2);每次发送一个符号向量就重新生成一个独立的信道矩阵,这叫“快衰落”假设,仿真的是信道独立变化的极限情况。如果你需要仿真慢衰落或块衰落,可以把信道矩阵的生成放到符号块外面,每个块用同一个H。这两种方式没有绝对的对错,关键看你想模拟什么场景,我的代码默认是快衰落,因为这是比较标准的BER性能对比设置。
信噪比怎么定义也需要统一。我习惯用每个接收天线上的符号信噪比来定义,单位是dB。在Matlab里实现时,给定一个SNR_dB值,我这样计算噪声方差:
SNR_lin = 10^(SNR_dB / 10); N0 = 1 / SNR_lin; % 接收端每根天线上每个符号周期的噪声功率 noise_sigma = sqrt(N0 / 2); % 复噪声实部虚部各自的方差这里同样有一个归一化问题:既然发射符号平均功率已经归一化为1,信道矩阵平均增益为1,那么接收端平均符号功率就是(N_t)(多根发射天线叠加)。如果直接用N0 = 1/SNR_lin,定义的是单根接收天线上的符号信噪比,这在2x2和4x4之间对比时是一致的,物理含义也清晰。很多代码里写成N0 = Nt / SNR_lin也不是错,那是把总发射功率平摊到每根天线之后的单流信噪比,和我的定义差了(N_t)倍,对比不同天线配置时要注意口径统一。
2. 四种检测器的原理与选择逻辑
2.1 ZF检测:简单直接但噪声会被放大
ZF检测,零强迫检测,思路特别直白:既然(\mathbf{y} = \mathbf{H}\mathbf{x} + \mathbf{n}),那我直接把信道的影响除掉不就行了?对信道矩阵求伪逆,然后乘到接收信号上:
[ \hat{\mathbf{x}}_{ZF} = \mathbf{H}^{\dagger}\mathbf{y} = (\mathbf{H}^H\mathbf{H})^{-1}\mathbf{H}^H\mathbf{y} ]
这个方法的优点是复杂度极低,毕竟只有一次矩阵求逆。但它的致命缺点是噪声增强。信道矩阵的伪逆会把噪声也一起放大,特别是当信道矩阵接近奇异的时候——比如两根发射天线对应的信道向量相关性很高——求逆之后噪声可能被放大好几倍。
我在报告中画了一个对比:同样在20dB SNR下,2x2系统用ZF误码率大约在10^-3量级,而MMSE已经能到10^-4量级了。原因就是MMSE在求逆的时候,多了一项噪声方差的修正,把噪声抑制考虑进去了。
用生活化的类比来说,ZF就像你在嘈杂的会场里,靠一个精准的定向麦克风去听某个人的声音,但麦克风在放大目标声音的同时,也把旁边的杂音一起放大了;而MMSE就像麦克风内置了降噪算法,虽然对目标声音的增益略有损失,但最后的信噪比反而更高。
ZF检测在Matlab里的实现,很多人第一反应是写inv(H'*H)*H'*y,这没问题。但我更推荐用左除:
x_zf = H \ y;Matlab的\运算符会根据自己的算法选择最佳解法,数值稳定性更好,在矩阵接近奇异时不容易直接爆出inf或NaN。
2.2 MMSE检测:把噪声统计量用起来
MMSE检测,最小均方误差检测,名字听起来高大上,但它做的本质上是ZF和噪声抑制之间的一个折中。它的加权矩阵是:
[ \mathbf{W}_{MMSE} = (\mathbf{H}^H\mathbf{H} + \sigma_n^2 \mathbf{I})^{-1}\mathbf{H}^H ]
对比ZF的公式,差别就在括号里多了一项(\sigma_n^2\mathbf{I})。这一项的作用相当于在求逆时给对角线上加了正则化,让矩阵更“健康”,不那么容易因为奇异而放大噪声。
在高信噪比区域,(\sigma_n^2 \to 0),MMSE的加权矩阵趋近于ZF;在低信噪比区域,(\sigma_n^2)占主导,检测器会更倾向于抑制噪声而不是完全消除干扰。所以MMSE在任何信噪比下都不会比ZF差,这就是它“最小均方误差”的含义——它在每个信噪比点上都做了一次最优权衡。
Matlab实现里有个小坑:代码里计算MMSE权重时,噪声方差(\sigma_n^2)必须是复噪声的总方差,不是实部虚部各自的方差。如果你在生成噪声时用了complex(randn, randn) * sqrt(N0/2)这种方式,那么每个复噪声元素的总方差是(N0),所以代码里用sigma2 = N0,而不是N0/2。这个细节搞错了,MMSE曲线会奇怪地偏移,性能变得和ZF差不多甚至更差。
2.3 SIC/OSIC:逐层剥离信号
SIC,串行干扰消除,核心思想是“吃葡萄先吃最甜的”。它先把信噪比最高的那根发射天线的信号检测出来,然后从接收信号里把它减掉,再对剩下的信号重复这个过程。每次剥离一个信号,下一层的干扰就少一个,检测难度逐步降低。
但SIC有个致命问题:误差传播。第一层如果检测错了,后面全部跟着错。为了缓解这个问题,工程上更常用的是OSIC,也就是排序的串行干扰消除,每次选择当前信噪比最高的那一层先解码,以此尽量降低第一层的误判概率。
OSIC的实现步骤我总结成下面这张表:
| 步骤 | 操作内容 | 对应的Matlab操作 |
|---|---|---|
| 1 | 计算当前等效信道的ZF加权矩阵 | W = (H'*H) \ H' |
| 2 | 找出加权矩阵行范数最小的行,作为当前最优层 | [~, idx] = min(sum(abs(W).^2, 2))或类似排序 |
| 3 | 检测该层符号,硬判决得到估计值 | x_hat = qpsk_demod(W(idx,:) * y) |
| 4 | 从接收信号中减去该层贡献 | y = y - H(:,idx) * x_hat |
| 5 | 删除该列,更新信道矩阵 | H(:,idx) = [] |
| 6 | 循环直到所有层检测完毕 | 重复步骤1-5 |
为什么排序能让性能提升?因为信噪比高的层,检测正确的概率大,先剥离它们,误差传播的概率就小得多。我的仿真结果显示,在4x4 MIMO下,OSIC比固定顺序的SIC大约能带来1~2dB的增益,在高阶调制下这个差距还会扩大。
2.4 ML检测:性能天花板但要付出复杂度
ML检测,最大似然检测,是所有检测器的性能上限。它的思路是:我把所有可能发送的符号向量都穷举一遍,看哪一个最有可能产生当前接收到的(\mathbf{y})。用数学表达就是:
[ \hat{\mathbf{x}}{ML} = \arg\min{\mathbf{x} \in \Omega^{N_t}} |\mathbf{y} - \mathbf{H}\mathbf{x}|^2 ]
其中(\Omega)是星座点集合,(\Omega^{N_t})代表所有可能的符号向量组合。在QPSK、2x2系统下,总共只有(4^2 = 16)种组合,穷举毫无压力。但在4x4 QPSK下,组合数是(4^4 = 256),已经有点疼了;如果换成16QAM、4x4,那就是(16^4 = 65536),每跑一个信噪比点都要计算几万次欧氏距离,仿真一轮下来等你跑完,人都要麻了。
ML检测的意义不在于工程直接部署,而是给其他检测器提供了一个“标尺”。我在报告里就把ML曲线画在最下面当基准线,直观展示ZF、MMSE、SIC比最优性能差多少,这样才能量化每种次优检测器的损失。
Matlab实现ML检测,核心就是一个暴力遍历加距离比较,但由于Matlab的循环效率低,我建议把星座点组合的欧氏距离计算向量化。具体做法是提前生成所有可能的符号向量组合矩阵(每行是一个候选向量),然后用矩阵运算一次性算出所有距离:
% 生成所有候选符号向量组合 [X_all, ~] = meshgrid(QPSK_MAP, 1:Nt^2); % 需要组合,参考实际代码 % 将X_all整理成 Nt 列,每行一个候选 candidates = permute(reshape(repmat(QPSK_MAP, Nt, 1), [], Nt), [1 2 3]); % 距离计算:||y - H*x||^2 对所有候选并行计算 distances = sum(abs(y - H * candidates.').^2, 1); [~, best_idx] = min(distances); x_hat = candidates(best_idx, :);我在最终版本里用的是另一套基于全排列生成的实现,关键点是提前把组合矩阵算好缓存在工作区,避免每发一个符号向量就重新生成一次。这个优化能让4x4 QPSK的ML检测速度提升好几倍。
3. Matlab源码核心实现与运行参数
3.1 蒙特卡洛仿真框架设计
整套仿真的大框架是经典的蒙特卡洛误码率统计流程:外层循环遍历不同的SNR点,内层循环不断随机生成信道、发送符号、叠加噪声、进行检测、统计误比特数,直到误码率达到足够的置信度。我建议的仿真框架如下:
EbN0_dB = 0:2:20; Nt = 2; Nr = 2; maxBits = 1e6; % 每个SNR点最多仿真比特数 targetErrs = 100; % 或达到100个错误比特就停止,保证统计置信度 for snr_idx = 1:length(EbN0_dB) bitErrors = 0; totalBits = 0; % 循环发送符号向量,直到满足停止条件 while totalBits < maxBits && bitErrors < targetErrs H = (randn(Nr, Nt) + 1j*randn(Nr, Nt)) / sqrt(2); txBits = randi([0 1], Nt*2, 1); % QPSK每符号2比特 txSym = qpsk_mod(txBits, Nt); % 映射到Nt个QPSK符号 n = sqrt(N0/2) * (randn(Nr,1) + 1j*randn(Nr,1)); y = H * txSym + n; % 用四种检测器分别估计 rxBits_zf = qpsk_demod(H \ y, Nt); rxBits_mmse = qpsk_demod((H'*H + N0*eye(Nt)) \ H' * y, Nt); % ... SIC 和 ML 类似 % 统计误比特 bitErrors = bitErrors + sum(rxBits_zf ~= txBits); totalBits = totalBits + length(txBits); end BER_zf(snr_idx) = bitErrors / totalBits; end这个框架有一个很重要的设计细节,就是停止条件。我用了双重停止标准:仿真比特数达到上限,或者累积错误比特数达到目标值。为什么这样设计?因为BER曲线在低信噪比下很容易统计,发几千个比特就有几百个错误;但在高信噪比下,比如20dB、2x2 MMSE的情况下,误码率可能只有10^-4甚至更低,如果你固定只发1万个比特,很可能一个错误比特都没有,BER就是0,画在图上就变成了“完美的下降线”,这并不真实。设置targetErrs=100,意味着即使误码率很低,也要收集到足够多的错误样本,统计出来的BER才有置信度。这是一个非常关键也常被忽略的仿真设计。
另一个建议是每个信噪比点的BER,不要低于10^-6就停止。因为蒙特卡洛仿真的相对误差大概是(1/\sqrt{N_e}),(N_e)是统计到的错误比特数。100个错误比特对应的相对误差约10%,这个精度画曲线足够了。再往下跑就是纯浪费计算资源。
3.2 QPSK调制解调与检测器核心代码段
QPSK调制解调这块我写得比较朴素,但很稳定。调制函数把输入的比特向量分成每2比特一组,映射到星座点;解调函数则是计算接收符号到四个星座点的距离,取最近的那个。因为QPSK的特殊性,我直接用sign判断也能做,但通用性差,后面换16QAM还得重写。所以我的代码采用通用的最小距离判决:
function sym = qpsk_mod(bits, Nt) % bits是2*Nt行的列向量,每两比特映射一个QPSK符号 QPSK_MAP = [1+1j, 1-1j, -1+1j, -1-1j] / sqrt(2); bits = reshape(bits, 2, Nt).'; idx = bits(:,1)*2 + bits(:,2) + 1; % 00->1, 01->2, 10->3, 11->4 sym = QPSK_MAP(idx).'; end检测器核心代码,除了前面写过的ZF和MMSE,SIC部分我重点说一下排序的实现。OSIC每次迭代都要重新计算加权矩阵和选择当前最优层,很多人写出来的SIC其实是按天线顺序固定的,性能和排序版本差不少。我排序的核心代码是这样的:
function x_hat = osic_detect(H, y, N0, QPSK_MAP) Nt = size(H, 2); x_hat = zeros(Nt, 1); for i = 1:Nt W = (H'*H + N0*eye(size(H,2))) \ H'; % 用MMSE加权矩阵做排序和检测 [~, idx] = min(sum(abs(W).^2, 2)); % 找信噪比最高/误差最小的层 x_hat(idx) = qpsk_min_dist(W(idx,:) * y, QPSK_MAP); y = y - H(:,idx) * x_hat(idx); H(:,idx) = []; end end注意我这里排序用的是MMSE加权矩阵的行范数作为信噪比排序依据,而不是ZF。原因在于MMSE本身性能优于ZF,用它选出的最优层,检测正确的概率更高,误差传播的影响更小。实测下来,OSIC-MMSE比OSIC-ZF在高信噪比下大约多0.5~1dB的增益,这个收益在4x4系统里尤其明显。
ML检测的实现,我采用预生成码本的方式。比如2x2 QPSK,我先生成一个16x2的复数矩阵cand,每一行是一个可能的发射向量,然后检测的时候用sum(abs(repmat(y,1,16) - H*cand.').^2, 1)来一次性计算所有距离。这种方法在不使用并行工具箱的情况下,还是能比纯for循环快上不少。
3.3 仿真参数选择的经验值总结
我把自己调试过程中觉得最合理的参数组合放在这里,方便你直接参考:
| 参数 | 建议取值 | 理由 |
|---|---|---|
| 天线配置 | 2x2 和 4x4 各跑一遍 | 小配置验证逻辑,大配置拉开性能差距 |
| 调制方式 | QPSK | 组合数量可控,ML可实现,结果解释简单 |
| 信噪比范围 | 0~20dB,步进2dB | 覆盖BER从10^-1到10^-5的主要区间 |
| 蒙特卡洛停止条件 | 最大1e6比特且错误数≥100 | 兼顾速度和统计精度 |
| 信道模型 | 独立快衰落瑞利 | 验证算法性能的标尺,最常用假设 |
这套参数在普通笔记本电脑上,2x2跑完大概几分钟,4x4因为ML在256个候选里穷举,会明显慢一些,但也勉强可以接受。如果你要跑16QAM或者8x8,建议要么把ML去掉只画次优检测器的对比,要么投入更多时间等仿真跑完。
4. 实测结果分析与性能对比
4.1 2x2配置下的BER曲线规律
我在2x2配置下把四条BER曲线全部跑出来之后,第一个直观的感受是:检测器之间的性能梯度非常规律,ML最好,MMSE次之,ZF第三,SIC介于ZF和MMSE之间,具体位置取决于信噪比区域。
在低信噪比(0~6dB)区域,ZF曲线几乎和理论线上的“盲猜”贴在一起,说明噪声增强严重,即使信道被“零强迫”消除,噪声已经被放大了太多,检测性能几乎崩溃。MMSE此时比ZF好1~2个dB,因为正则项在低信噪比时起的作用最大。SIC在这个区域也表现一般,因为第一层检测本身就不可靠,误差传播雪上加霜。
到了10dB以上的高信噪比区域,四条曲线开始明显分层。ML在12dB左右已经跑到10^-4,MMSE在14dB左右跑到同样水平,ZF要到16dB以上。这说明在2x2配置下,ML比MMSE大约有2dB增益,MMSE比ZF大约有2dB增益,和很多教材里给出的理论结论基本吻合。
我还算了一个有意思的指标,就是在BER=10^-3这个目标下,ML比ZF大约节省了3dB的发射功率。在真实系统里,3dB功率增益意味着发射功率可以减半,这对终端功耗和基站覆盖的改善是很直观的。这也是为什么业界愿意花更大计算复杂度去逼近ML性能的原因所在。
4.2 天线数增加后检测器差距的变化
从2x2升级到4x4之后,我最关心的一个问题是:天线数多了,这四种检测器的相对性能排序会变吗?
实测结果是排序不变,但是差距变大了。ML依然是绝对优势,而ZF在4x4下恶化得比2x2更明显。原因在于ZF求逆操作中的噪声增强,在矩阵维度增加、信道条件数更容易变大时,被放大的噪声也就更多。换句话说,天线数越多,信道矩阵越容易“病态”,ZF的劣势被进一步放大。
SIC在4x4下表现出的增益则更明显。因为层数多了,逐层剥离的优势被放大,每一层正确检测都能让后续层受益,整个链条的效果比2x2时要可观。实测在4x4、16dB时,OSIC比固定顺序SIC大约好1.5dB,比ZF好大约3dB,已经逼近MMSE的水平。不过MMSE在4x4下依然领先OSIC大约1dB,原因是OSIC的误差传播在层数增多后仍然是个拖累因素,而MMSE是全局最优线性检测,不依赖“剥离的干净程度”。
ML在4x4 QPSK下依然是王者,它在16dB附近能达到10^-5以下,而MMSE要20dB才能接近这个水平。两者之间存在明显的“性能悬崖”,这也是MIMO检测领域这么多年还在持续研究逼近ML性能的低复杂度算法的原因。
4.3 复杂度量化对比
性能和复杂度是检测器选择的两个互相拉扯的指标。我把四种检测器在2x2 QPSK下的相对复杂度做了个粗略估测,方便你直观对比:
| 检测器 | 每符号向量所需主要计算量 | 相对复杂度 | 适合场景 |
|---|---|---|---|
| ZF | 一次矩阵求逆+一次矩阵乘 | 极低 | 实时性要求高、信道条件好的场景 |
| MMSE | 加正则项的一次矩阵求逆+乘 | 低 | 工程中最常用,性能复杂度平衡 |
| OSIC | 多次求逆+排序+逐层消去 | 中 | 需要比MMSE更好性能的系统中 |
| ML | 穷举所有候选向量 | 指数级 | 性能基准评估、小规模MIMO |
ML的复杂度是指数级的,所以实际工程里几乎不会直接用ML做在线检测,它的主要价值是当“裁判”,衡量其他检测器离最优还有多远。我看到很多论文里会用“ML性能损失的百分比”来标注自己提出的检测器有多接近最优,这也说明了ML在MIMO检测研究中的基准地位。
5. 实操中踩过的坑与排查技巧
5.1 误码率曲线在高信噪比下突然变平或跳零
这是我调试过程中遇到最多的现象。BER曲线跑到某个点之后,突然变成0,或者卡在一个很低的平台不再下降。绝大多数情况下,问题出在蒙特卡洛仿真的停止条件上。如果你固定只发送固定数量的比特,比如1万比特,在20dB时很可能一个错误都没有,画出的BER=0看似很漂亮,其实没有意义。另一个可能的原因是信道矩阵生成或者噪声方差计算不对,导致某个信噪比之后系统信噪比实际上并不再提升。排查方法是把噪声明文打印出来检查,信噪比从10dB增加到20dB时,噪声方差应该下降100倍,如果在Matlab代码里误用了N0 = 1/(sqrt(SNR_lin))这种写法,就会导致曲线整体偏移且不会持续下降。
5.2 程序跑得很慢,尤其是ML检测
ML检测的穷举计算是最大的性能瓶颈。我第一次在4x4 QPSK下跑ML,用的是一层一层嵌套for循环,一个信噪比点要跑十几分钟,整个仿真跑完天都黑了。后来优化成预生成码本+向量化距离计算,同样的仿真任务在几分钟内就能完成。如果你跑的是16QAM的4x4 MIMO,ML算法有65536个候选,向量化之后虽然内存占用变大了,但速度依然比for循环快得多。我建议用single precision来存储候选矩阵,能把内存占用降低一半,而精度损失对整个BER统计的影响可以忽略。
还有一个常见的提速方法是:对ML检测,每个信噪比点设置错误数停止条件之后,高信噪比区域跑得很久。如果你只是想知道ML在10dB和20dB的性能差距,其实可以只采样少数几个SNR点,并在低SNR区间把比特数上限放宽、在高SNR区间把错误数目标调低,这样可以明显缩短总仿真时间。
5.3 ZF和MMSE在高信噪比下曲线重叠
我一开始跑出来的图里,ZF和MMSE在14dB以上几乎完全重合,这让我怀疑自己代码写错了。排查之后发现一个细节:在高信噪比区域,噪声方差(\sigma_n^2)趋近于0,MMSE的加权矩阵确实趋近于ZF。这对2x2系统来说,二者的性能差异本来就在变小,所以曲线靠近是正常现象。但如果是4x4系统,这种重合不会出现得那么早。所以如果你在4x4仿真里发现ZF和MMSE重叠,那才需要检查代码。还有一个常见错误是MMSE权重公式里的噪声方差写成了实部方差N0/2,这会让MMSE在低信噪比区域退化得比ZF还差,属于必须排查的bug。
5.4 信道矩阵接近奇异导致数值不稳定
当信道矩阵接近奇异时,ZF求逆会出现很大的数值,甚至产生NaN或inf。我的建议是不要直接裸用inv(),而是使用\左除运算,Matlab会自动选择数值稳定的算法。另外,对MMSE来说,正则项(\sigma_n^2\mathbf{I})天然让矩阵更可逆,所以很少出现数值问题。如果你碰到极端情况下还是出现inf,可以在求逆之前给信道矩阵加上一个极小的正则量,比如H'*H + 1e-10*eye(Nt),这能起到数值稳定器的作用。
6. 结尾:一点个人体会和扩展建议
跑完这套MIMO检测器仿真,我最大的体会是:理论与仿真之间隔着太多细节,而成败往往就藏在几个看似不起眼的系数上。星座点归一化、噪声方差口径、蒙特卡洛停止条件、信道矩阵的生成方式——任何一处不对,最终画出来的曲线都会误导你的结论。我的建议是,拿到任何一份MIMO仿真代码,先别急着跑,把发射功率、噪声方差、信噪比定义一条一条捋清楚,再去看检测器算法。这条经验能帮你省下大量无意义的调试时间。
如果你想把这份工作继续扩展,我建议从三个方向入手:第一,把QPSK换成16QAM或者更高阶调制,看看四种检测器的性能差距如何随调制阶数扩大;第二,加入LDPC或卷积信道编码,把检测器输出的软信息做软判决译码,这会更贴近真实的通信系统;第三,把独立瑞利衰落换成相关信道或空间相关信道,观察信道相关性对ZF和ML性能的影响,这个方向在Massive MIMO研究里尤其常用。等你把这几条路都走通一遍,MIMO检测这块基本就吃透了。