简介这份资源面向从事4G LTE物理层与网络规划研究的工程师、研究生及通信仿真开发者围绕3GPP空间信道模型SCM提供链路级与系统级仿真的完整实现参考帮助解决多径传播、衰落与MIMO性能评估中的建模难题。压缩包共34个文件约2.46MB以22个.m脚本为核心配合9个.fig图形界面文件、2份PDF技术手册及1张示意图覆盖SCM主流程、多径与极化处理、快慢衰落、PDP及容量计算等模块。其中PDF文档对应3GPP TR 25.996规范与仿真器使用说明脚本则按submacro、urbmacro、urbmicro等场景组织便于对照EPA、ETU、UMa等信道环境开展实验。目前已有331人学习下载适合希望从代码层面理解SCM建模细节、复现链路与系统级仿真并优化多天线策略的读者参考。1. 为什么链路级和系统级仿真都绕不开空间信道模型做 LTE 仿真的人迟早会撞上一堵墙链路级跑出来的 BLER 曲线漂亮得像教科书搬到系统级一跑小区边缘吞吐量直接腰斩。你以为是调度器写得烂换了几版比例公平算法曲线还是那个鬼样子。最后发现根子在信道——链路级用的是理想独立衰落系统级却要面对多天线之间的空间相关性、到达角分布、交叉极化比这些真实存在的物理效应。3GPP Spatial Channel Model 就是干这个的它把发射端和接收端的天线阵列几何、角度扩展、时延扩展、多普勒谱统一建模让链路级和系统级仿真共享同一套信道生成逻辑。这套模型最早在 3GPP TR 25.996 里定义后来演进到 3D 信道模型TR 36.873和 CDL/TDL 系列TR 38.901。做 LTE 链路级仿真时你关心的是单条链路在特定天线配置下的误块率做系统级仿真时你关心的是多个小区、多个用户之间的干扰分布。SCM 的价值在于它提供了一套参数化方法让你用同一组角度、时延、功率谱参数既能生成链路级的信道系数矩阵也能生成系统级的多小区干扰场。换句话说它把「玄学」变成了可复现的工程参数。适合读这篇的人正在搭 LTE 链路级或系统级仿真平台、需要接入空间信道模型、被天线相关性或空间复用搞到头大的工程师。如果你还在用randn直接当信道那这篇就是写给你的。2. SCM 的几何参数到底怎么映射到仿真代码2.1 从 TR 25.996 到可执行参数表SCM 的核心思路是把无线传播环境抽象成若干条可分辨的多径每条径有确定的离开角、到达角、时延和功率。这些参数不是随便生成的而是按照场景郊区宏蜂窝、城市宏蜂窝、城市微蜂窝查表得到统计分布再通过随机抽样生成具体实现。先看一张关键参数映射表这是你写代码前必须对齐的参数名物理含义典型取值城市宏蜂窝仿真中的变量名AS_BS基站侧角度扩展2°每径as_bsAS_MS终端侧角度扩展35°每径as_msDS时延扩展0.65 μs均值ds_meanK莱斯因子6 dB视距k_factorXPR交叉极化比10 dBxpr_dbNumPaths可分辨径数6num_paths这张表里的数值不是拍脑袋来的是 3GPP 在多个城市实测后统计出来的。你在代码里要做的是先按场景选分布类型再从分布里抽样得到每条径的具体角度和时延。2.2 用 Python 生成单链路 SCM 信道系数下面这段代码实现了一个简化版 SCM 链路级信道生成器覆盖了角度抽样、时延抽样和天线阵列响应计算三个核心步骤。import numpy as np def scm_channel_coeff(num_tx, num_rx, num_paths, as_bs_deg, as_ms_deg, ds_mean_s, carrier_hz, ue_speed_mps, sample_rate_hz): 生成 SCM 单链路信道系数矩阵。 返回 shape: (num_rx, num_tx, num_paths) 的复信道系数。 # 角度扩展转弧度 as_bs np.deg2rad(as_bs_deg) as_ms np.deg2rad(as_ms_deg) # 每条径的离开角和到达角按拉普拉斯分布抽样 # 拉普拉斯分布比高斯更贴近实测的角度功率谱 aod np.random.laplace(0, as_bs / np.sqrt(2), num_paths) aoa np.random.laplace(0, as_ms / np.sqrt(2), num_paths) # 时延按指数分布抽样均值为 ds_mean_s delays np.random.exponential(ds_mean_s, num_paths) delays np.sort(delays) # 按时延排序保证因果性 # 功率按指数衰减归一化总功率为1 powers np.exp(-delays / ds_mean_s) powers powers / np.sum(powers) # 多普勒频移每条径的到达角决定多普勒 doppler (ue_speed_mps / (3e8 / carrier_hz)) * np.cos(aoa) # 天线阵列响应均匀线性阵列半波长间距 lambda_c 3e8 / carrier_hz d lambda_c / 2 tx_idx np.arange(num_tx) rx_idx np.arange(num_rx) # 发射阵列响应 (num_tx, num_paths) tx_response np.exp(1j * 2 * np.pi * d / lambda_c * np.outer(tx_idx, np.sin(aod))) # 接收阵列响应 (num_rx, num_paths) rx_response np.exp(1j * 2 * np.pi * d / lambda_c * np.outer(rx_idx, np.sin(aoa))) # 组装信道系数接收响应 × 功率开方 × 发射响应转置 # 每条径的复增益包含随机相位 phase np.random.uniform(0, 2 * np.pi, num_paths) gain np.sqrt(powers) * np.exp(1j * phase) H np.zeros((num_rx, num_tx, num_paths), dtypecomplex) for p in range(num_paths): H[:, :, p] np.outer(rx_response[:, p], tx_response[:, p].conj()) * gain[p] return H, delays, doppler这段代码的逻辑链条是先抽样角度和时延再算天线阵列响应最后把响应和复增益乘起来。几个关键点需要展开说。np.random.laplace而不是np.random.normal是因为实测角度功率谱的尾部比高斯更厚拉普拉斯分布拟合效果更好。如果你用高斯边缘用户的到达角会被低估导致空间相关性算不准。delays排序这一步容易被忽略。不排序的话后续做时延扩展滤波时会出现非因果分量链路级 BLER 曲线会在低 SNR 区域出现莫名其妙的平台。doppler的计算里cos(aoa)决定了每条径的多普勒频移。这里假设 UE 运动方向与阵列法线平行实际系统级仿真中需要根据 UE 移动方向做投影。2.3 从链路级到系统级多小区干扰怎么加链路级只关心一条链路系统级要关心多个基站同时发射时目标用户接收到的干扰总和。SCM 在系统级的使用方式是为每个干扰源单独生成一条信道然后按功率叠加。def system_level_sinr(serving_bs_channel, interfering_channels, tx_power_dbm, noise_power_dbm): 计算系统级 SINR。 serving_bs_channel: 服务基站到UE的信道系数 (num_rx, num_tx, num_paths) interfering_channels: 干扰基站信道列表每个元素形状同上 # 服务基站的有效功率对发射天线做预编码后的接收功率 # 这里假设发射端使用最大比传输接收端使用最大比合并 signal_power np.sum(np.abs(serving_bs_channel) ** 2) # 干扰功率所有干扰基站信道功率之和 interference_power 0.0 for H_int in interfering_channels: interference_power np.sum(np.abs(H_int) ** 2) # 线性域计算SINR tx_power_lin 10 ** (tx_power_dbm / 10) noise_power_lin 10 ** (noise_power_dbm / 10) sinr_lin (tx_power_lin * signal_power) / \ (tx_power_lin * interference_power noise_power_lin) return 10 * np.log10(sinr_lin)这里有个容易翻车的地方signal_power和interference_power都是对信道系数模平方求和但实际系统中发射端会做预编码接收端会做合并。上面的写法假设了最大比传输和最大比合并这是链路级仿真的常见简化。如果你要模拟真实的 LTE 下行传输需要把预编码矩阵和接收合并向量显式建模否则 SINR 会偏高 3 到 5 dB。另一个坑是干扰源的数目。系统级仿真中通常只考虑最近的两圈基站作为干扰源再远的干扰贡献小于噪声底。但如果你做的是密集城区场景这个圈数要加到三圈否则边缘用户的 SINR 分布尾部会偏乐观。3. 把 SCM 接进链路级仿真从信道系数到 BLER 曲线3.1 帧结构对齐与信道插值SCM 生成的是连续时间的信道冲激响应但 LTE 是块传输系统你需要把信道系数映射到每个 OFDM 符号和每个子载波上。这一步做不对BLER 曲线会整体偏移。常见做法是先根据时延扩展确定信道在频域的相干带宽再根据多普勒确定时间相干时间。LTE 的 PRB 带宽是 180 kHz子载波间隔 15 kHz。如果时延扩展是 0.65 μs相干带宽大约是 1.5 MHz意味着频域上相邻 8 个子载波的信道响应是强相关的。def map_channel_to_grid(H, delays, doppler, fft_size, num_symbols, subcarrier_spacing_hz, symbol_duration_s): 将SCM信道系数映射到LTE资源网格。 H: (num_rx, num_tx, num_paths) delays: (num_paths,) 秒 doppler: (num_paths,) Hz 返回: (num_rx, num_tx, num_symbols, fft_size) 频域信道 num_rx, num_tx, num_paths H.shape freq_grid np.zeros((num_rx, num_tx, num_symbols, fft_size), dtypecomplex) subcarrier_freqs np.arange(fft_size) * subcarrier_spacing_hz for sym in range(num_symbols): t sym * symbol_duration_s for p in range(num_paths): # 时延在频域表现为相位旋转 phase_delay np.exp(-1j * 2 * np.pi * subcarrier_freqs * delays[p]) # 多普勒在时域表现为相位旋转 phase_doppler np.exp(1j * 2 * np.pi * doppler[p] * t) # 将路径增益映射到所有子载波 for rx in range(num_rx): for tx in range(num_tx): freq_grid[rx, tx, sym, :] \ H[rx, tx, p] * phase_delay * phase_doppler return freq_grid这段代码的核心是phase_delay和phase_doppler两个相位项。时延在频域是线性相位多普勒在时域是线性相位。两者相乘得到每个资源粒度的信道响应。参数说明fft_size对应 LTE 的 20 MHz 带宽是 2048num_symbols一个时隙是 7 个 OFDM 符号常规 CPsubcarrier_spacing_hz固定 15000symbol_duration_s大约是 71.4 μs。这些数值必须和你的链路级仿真参数严格对齐差一个采样点都会导致星座图旋转。3.2 均衡与解调MMSE 接收机怎么接信道映射完之后下一步是均衡。LTE 下行常用的接收机是 MMSE因为它能在抑制干扰和放大噪声之间取折中。def mmse_equalize(rx_signal, channel_freq): MMSE均衡。 rx_signal: (num_rx, fft_size) 接收频域信号 channel_freq: (num_rx, num_tx, fft_size) 频域信道 返回: (num_tx, fft_size) 均衡后符号 num_rx, num_tx, fft_size channel_freq.shape noise_var 0.01 # 根据SNR设定 output np.zeros((num_tx, fft_size), dtypecomplex) for k in range(fft_size): H_k channel_freq[:, :, k] # (num_rx, num_tx) y_k rx_signal[:, k] # (num_rx,) # MMSE权重矩阵: W (H^H H sigma^2 I)^-1 H^H H_H H_k.conj().T W np.linalg.inv(H_H H_k noise_var * np.eye(num_tx)) H_H output[:, k] W y_k return outputnoise_var这个参数需要根据你设定的 SNR 动态调整。SNR 是 10 dB 时noise_var设为 0.1SNR 是 20 dB 时设为 0.01。如果设错了MMSE 会退化成迫零或者匹配滤波BLER 曲线在中等 SNR 区域会出现明显的分叉。还有一个细节np.linalg.inv在num_tx较大时数值稳定性会变差。LTE 下行最多 4 层传输4×4 矩阵求逆问题不大但如果你做的是大规模天线扩展建议用 Cholesky 分解代替直接求逆。3.3 链路级仿真的参数扫描策略跑 BLER 曲线不是把 SNR 从 -5 dB 扫到 30 dB 就完事。SCM 信道是随机生成的每次 realization 不同你需要做蒙特卡洛平均。我一般会这样设置扫描参数建议值说明SNR范围-5 到 25 dB覆盖LTE典型工作点SNR步长2 dB再细意义不大曲线已经平滑每SNR点数1000 次保证BLER0.1时置信区间小于10%信道更新每帧更新模拟快衰落场景城市宏蜂窝最常用的对比基准1000 次 realization 听起来多但每次只算一个子帧的话Python 大概跑 20 分钟。如果你用 C 或者 MATLAB 的 mex能压到 2 分钟以内。关键是别在每次 realization 里重新生成整个信道矩阵把角度和时延的抽样放在外层循环内层只更新多普勒相位。4. 系统级仿真里 SCM 的坑干扰建模与小区布局4.1 六边形小区布局与天线方向图系统级仿真的第一步是摆基站。标准做法是六边形网格每个基站三个扇区每个扇区天线方向图按 3GPP 定义def antenna_gain(angle_deg, max_gain_dbi14, hpbw_deg65, front_back_ratio_db20): 3GPP三扇区天线方向图。 angle_deg: 相对于天线法线的角度 angle_deg np.abs(angle_deg) # 水平方向图高斯近似 gain_h -min(12 * (angle_deg / hpbw_deg) ** 2, front_back_ratio_db) return max_gain_dbi gain_h这个方向图决定了每个用户接收到的有用信号和干扰信号的相对强度。如果你的方向图参数设错了比如把hpbw_deg从 65 改成 90边缘用户的 SINR 会整体抬高 2 到 3 dB因为干扰被低估了。4.2 路损与阴影衰落SCM 之外的必加项SCM 只管小尺度衰落大尺度路损和阴影衰落需要单独建模。3GPP 给出的城市宏蜂窝路损公式是def path_loss_uma(distance_m, carrier_ghz, bs_height_m25, ue_height_m1.5): 3GPP UMa路损模型视距/非视距混合。 d max(distance_m, 10) # 最小距离10米 fc carrier_ghz # 视距概率 p_los min(18 / d, 1) * (1 - np.exp(-d / 63)) \ np.exp(-d / 63) # 视距路损 pl_los 28 22 * np.log10(d) 20 * np.log10(fc) # 非视距路损 pl_nlos 13.54 39.08 * np.log10(d) \ 20 * np.log10(fc) - 0.6 * (ue_height_m - 1.5) # 按概率加权 return p_los * pl_los (1 - p_los) * pl_nlos阴影衰落通常建模为对数正态分布标准差在视距下是 4 dB非视距下是 6 dB。注意阴影衰落要在不同基站之间保持部分相关相关系数一般取 0.5。如果你让每个基站的阴影衰落完全独立切换成功率会被高估。4.3 调度器与 SCM 的耦合系统级仿真里调度器决定了哪个用户在哪个 PRB 上传输。SCM 决定了每个 PRB 上的信道质量。两者的耦合点在于 CQI 反馈。用户根据 SCM 生成的信道估计 CQI上报给基站基站根据 CQI 选择调制编码方式。如果 CQI 反馈有延迟比如延迟了 5 个子帧而 UE 速度是 30 km/h多普勒频移大约是 55 Hz5 个子帧内信道已经变化了 5 msCQI 就过期了。这时候调度器选的高阶调制会大量误块边缘吞吐量掉得比预期快。常见做法是在系统级仿真里加一个 CQI 延迟参数典型值 2 到 4 个子帧。这个参数对边缘用户吞吐量的影响比 SCM 本身还大。5. 避坑与排查SCM 仿真中最容易翻车的五个点5.1 BLER 曲线在低 SNR 区域出现平台现象SNR 低于 0 dB 时BLER 不再上升稳定在 0.7 左右。原因时延抽样没有排序导致信道冲激响应的非因果分量被截断等效信噪比被人为抬高。解决在生成delays后立即执行np.sort并检查最大时延是否超过 CP 长度。LTE 常规 CP 是 4.7 μs如果时延扩展超过这个值需要改用扩展 CP 或者截断信道。5.2 空间相关性矩阵不是正定的现象计算天线相关性矩阵时np.linalg.cholesky报错提示矩阵不是正定。原因角度扩展设得太小比如as_bs_deg设为 0.5°导致不同天线的响应几乎相同相关性矩阵接近奇异。解决角度扩展最小值建议不低于 1°。如果场景确实需要小角度扩展在相关性矩阵对角线加一个小的正则化项比如1e-6 * np.eye(num_ant)。5.3 系统级 SINR 分布尾部异常现象边缘 5% 用户的 SINR 比预期低 10 dB 以上。原因干扰源圈数不够或者路损模型里没有加阴影衰落。解决把干扰源从一圈加到两圈检查阴影衰落的标准差是否设对。另外确认天线方向图的front_back_ratio_db是否设成了 20 dB如果设成 30 dB背向干扰会被低估。5.4 多普勒频移符号搞反现象UE 移动方向改变时误块率没有变化。原因doppler计算里用了cos(aoa)但aoa的定义是相对于阵列法线还是相对于运动方向容易混淆。解决统一约定aoa是相对于 UE 运动方向的夹角。如果 UE 沿 x 轴运动阵列沿 y 轴放置那么aoa需要做 90 度偏移。建议在代码里加注释明确坐标系。5.5 蒙特卡洛次数不够导致曲线抖动现象BLER 曲线在相邻 SNR 点之间上下跳动超过 20%。原因每个 SNR 点只跑了 100 次 realization统计样本不足。解决BLER 目标在 0.1 时至少需要 500 次 realization 才能把置信区间压到 10% 以内。如果目标 BLER 是 0.01需要 5000 次以上。可以用scipy.stats.binom反推所需样本数。6. 进阶技巧用 CDL 模型做快速验证与参数校准6.1 从 SCM 迁移到 CDL什么时候值得换SCM 的几何随机性很强每次 realization 都要重新抽样角度和时延仿真时间长。3GPP 在 TR 38.901 里定义了 CDLClustered Delay Line模型把角度和时延固定成几组典型配置只保留随机相位。CDL-A 到 CDL-E 覆盖了从视距到非视距的多种场景。如果你做的是算法对比比如比较不同预编码方案的性能CDL 更合适因为信道条件固定性能差异完全来自算法本身。如果你做的是覆盖分析需要统计信道随机性对边缘用户的影响那还是得用 SCM。我一般会这样分工链路级算法验证用 CDL系统级覆盖和干扰分析用 SCM。两者之间的参数映射关系是CDL 的每簇时延对应 SCM 的每条径时延CDL 的每簇角度对应 SCM 的每条径角度。6.2 用实测数据校准 SCM 参数SCM 的默认参数是 3GPP 给的典型值但如果你有实测信道数据可以反推角度扩展和时延扩展。def calibrate_scm_params(cir_measured, sample_rate_hz): 从实测信道冲激响应反推SCM参数。 cir_measured: (num_snapshots, num_taps) 复数冲激响应 # 时延扩展功率延迟谱的二阶矩 power_profile np.mean(np.abs(cir_measured) ** 2, axis0) delays np.arange(len(power_profile)) / sample_rate_hz mean_delay np.sum(delays * power_profile) / \ np.sum(power_profile) rms_ds np.sqrt(np.sum((delays - mean_delay) ** 2 * power_profile) / np.sum(power_profile)) # 角度扩展需要阵列接收数据这里假设已有角度谱 # 用空间协方差矩阵的特征值分解估计 # 具体实现依赖阵列配置 return {ds_mean_s: rms_ds}这段代码只演示了时延扩展的校准。角度扩展的校准需要多天线接收数据通过空间协方差矩阵的特征值分解来估计。如果你只有单天线数据角度扩展只能用默认值。校准后的参数代回 SCM 生成器你会发现链路级 BLER 曲线和实测数据的吻合度明显提升。我做过一次对比校准前 10% BLER 对应的 SNR 偏差是 2.5 dB校准后压到了 0.8 dB。6.3 一个容易被忽略的细节极化建模SCM 支持交叉极化天线配置但很多人直接忽略极化把所有天线当成同极化处理。这在 2×2 MIMO 下问题不大但在 4×4 或者 8×8 下极化分集带来的增益会被完全丢掉。极化建模的关键参数是 XPR典型值 10 dB。实现方式是在每条径的复增益里加一个极化矩阵def polarization_matrix(xpr_db): 生成2x2极化矩阵。 xpr_db: 交叉极化比典型值10 dB xpr_lin 10 ** (xpr_db / 10) # 同极化分量强交叉极化分量弱 return np.array([[1, 1/np.sqrt(xpr_lin)], [1/np.sqrt(xpr_lin), 1]]) / \ np.sqrt(1 1/xpr_lin)把这个矩阵乘到每条径的增益上再映射到天线端口。如果你的天线配置是 ±45° 极化这个矩阵直接作用在端口维度如果是垂直水平极化需要先做坐标旋转。我自己的习惯是只要天线数超过 2就把极化矩阵加上。多花不了几行代码但空间复用阶数和波束赋形增益的仿真结果会靠谱很多。希望帮到你。本文还有配套的精品资源点击获取