做5G PDSCH信道级的链路仿真,可能是很多刚入坑物理层算法工程师的第一道坎。PDSCH承载了NR下行用户数据的主体,链路质量、调制编码方式、信道估计误差统统会汇聚成一张吞吐量曲线。我见过不少人拿官网示例跑通了,导师一问“为什么256QAM高码率下吞吐量上不去”,就答不上来,其实根子往往不是MCS不够高,而是信道模型设置或者信道估计那一步出了鬼。这篇博文,我完整复盘一套基于MATLAB的PDSCH吞吐量仿真实现,从信道生成、发射链路、接收解调到吞吐量统计,每个环节给出可复现的配置和调参思路,并且会把我调试验证过程中踩过的坑一并交代清楚。适合刚接触NR物理层仿真的人,也适合那些已经会跑样例、但想自己搭完整链路并分析性能瓶颈的同行。
1. PDSCH链路级仿真整体架构与参数规划
1.1 先想清楚仿真边界,才能避免后面跑偏
链路级仿真和系统级仿真的目标完全不同。系统级仿真关心小区吞吐量、调度公平性、干扰矩阵,而链路级仿真只聚焦单条物理链路:从传输块比特进来,到接收端把比特正确解出来,中间经过信道编码、调制、资源映射、OFDM波形、无线信道、同步、信道估计、均衡、解调、译码。这个边界划定以后,很多参数选择就水到渠成了。
在MATLAB里这套链路通常是用5G Toolbox来搭的。核心对象包括nrCarrierConfig、nrPDSCHConfig、nrDLSCH、nrPDSCH、nrTDLChannel和接收端的nrPDSCHDecode。其中nrPDSCH负责生成不带编码的调制符号,而nrDLSCH负责传输块级的编码和速率匹配。我习惯把链路分成三大块来管理:发射成型、信道注入、接收恢复。每一块都能独立打点输出中间变量,这样一旦吞吐量异常,就可以迅速定位是发端资源映射错了、信道生成参数不合理,还是接收端均衡算法有问题。
链路级仿真的优势在于可重复性和可控性。同一个信道快照可以反复测试不同MCS,同一份加噪数据可以比较LS和MMSE等不同均衡算法,这是外场测试给不了的便利。劣势则是仿真实时性和复杂度受限,所以仿真时长、RB数、帧数这些规模参数需要提前量好,后面我会专门讲怎么控制仿真规模。
1.2 参数集与帧结构配置:先把“地图”铺好
NR参数集的核心是子载波间隔(SCS)。PDSCH链路仿真里,SCS决定了时隙时长,进而决定了单个时隙能塞多少OFDM符号。常用配置是30 kHz,这样的话一个时隙是0.5 ms,包含14个OFDM符号,一个子帧2个时隙。另一个非常常见的配置是15 kHz,对应一个时隙1 ms。SCS还会影响信道时频选择性在网格上的表现:同样的多普勒频偏,在30 kHz下每个子载波间隔更宽,载波间干扰的影响相对更轻,所以高速场景往往需要更大的SCS。
MATLAB里配置一个载波非常简单:
carrier = nrCarrierConfig; carrier.SubcarrierSpacing = 30; carrier.NSizeGrid = 52; % 10 MHz @ 30kHz carrier.NSlot = 0; carrier.NCellID = 42;这里NSizeGrid=52表示52个资源块,对应624个子载波。10 MHz带宽、30 kHz SCS下正好52个RB,这个组合在仿真里很常用,因为计算量适中,频率选择性也可以体现出来。如果嫌频选不够明显,可以用20 MHz、106个RB,但每次仿真的内存消耗和LDPC解码耗时都会明显上涨。
接着配置PDSCH。我通常用一行nrPDSCHConfig来设置调制方式、层数和DM-RS参数:
pdsch = nrPDSCHConfig; pdsch.Modulation = '256QAM'; pdsch.NumLayers = 2; pdsch.MappingType = 'A'; pdsch.DMRS.DMRSAdditionalPosition = 2; pdsch.DMRS.DMRSLength = 1; pdsch.DMRS.DMRSConfigurationType = 2;这里有个容易忽略的点:调制阶数越高,对信道估计精度的要求越苛刻。256QAM的星座点间距很密,同样的信道估计误差可能导致完全不同的软比特LLR分布。我就见过用LS插值做256QAM仿真,性能比64QAM还差的情况,这不是算法不适合,而是导频密度或者插值窗口没跟上。后面信道估计那一节我会详细展开。
1.3 MCS与目标BLER:吞吐量曲线的主心骨
吞吐量不能只看调制阶数,码率同样关键。MCS索引在NR里对应一组调制阶数和目标码率。同一个256QAM下,码率0.53和码率0.85的吞吐量差很多,BLER表现也截然不同。实现时我不建议自己手写MCS表,直接用MATLAB内置表按索引取参数即可。
目标BLER的选择会影响仿真循环的设计。大多数PDSCH仿真以10% BLER为工作点,因为调度器会通过外环调节MCS去贴近这个值。在做吞吐量统计时,如果以初始传输统计,那么单次错误就直接丢弃该传输块;如果想模拟HARQ反馈,则需要把重传纳入平均传输次数,这会让仿真时间成倍增加。我通常先用初始传输BLER来定位趋势,等MCS点选差不多了,再对一个特殊MCS做带重传的精细统计,这样效率高且不会因为仿真帧数不够而抖动。
2. 信道生成模块实现:从TDL模型到时频响应
2.1 TDL模型参数选型的门道
信道生成是整个仿真里边最容易被低估的一环。很多初学者直接用默认TDL-C模型跑,结果发现曲线不管怎么调都不对,其实问题就出在延迟扩展和多普勒频移的设定上。
nrTDLChannel是MATLAB里非常方便的信道对象,它实现了3GPP TR 38.901中定义的一组抽头式延迟线模型。TDL-A、B、C是NLOS模型,D、E是LOS模型;C模型相比A、B有更大的延迟扩展和更明显的频率选择性,适合用来做城市宏站场景下的鲁棒性测试。如果做室分或者小站场景,延迟扩展通常比较小,我会将DelaySpread设为30 ns左右;做宏站直视距不明显的场景,设为300 ns比较合适。
多径效应的直观理解就是回声:你在山谷里喊一声,会听到一串到达时间不同、幅度各异的回声。无线信道同样是这样,每个抽头就是一条回声路径,携带不同的功率和时延。多个回声叠加后,不同频率上的增强和抵消是不同的,这就形成了频率选择性衰落。OFDM系统靠循环前缀来吸收这些时延,但如果延迟扩展超过循环前缀长度,前一个符号的“尾巴”会漏进下一个符号,造成符号间干扰,这是仿真结果异常的一个隐蔽源头。
channel = nrTDLChannel; channel.DelayProfile = 'TDL-C'; channel.DelaySpread = 300e-9; channel.MaximumDopplerShift = 30; channel.SampleRate = carrier.SampleRate; channel.NumTransmitAntennas = 2; channel.NumReceiveAntennas = 2;这里SampleRate必须和OFDM调制解调链路保持一致,否则滤波器会重采样,产生莫名其妙的延迟。另一个容易忽略的参数是MaximumDopplerShift,低速场景下30 Hz已经足够,若设成300 Hz且SCS为15 kHz,时变信道会严重破坏子载波正交性,可能误判为算法问题。
2.2 天线层数、预编码与信道矩阵维度
PDSCH的层数决定了空间复用的能力,也直接卡住了吞吐量的上限。单层传输在同样MCS下只有两层传输一半的容量。MATLAB里配置NumTransmitAntennas=2, NumReceiveAntennas=2,pdsch.NumLayers=2,就能生成2x2 MIMO信道。注意这里的“层”不等同于“天线口”,层映射和预编码是独立的两步。
信道矩阵的维度是[接收天线数, 发射天线数, 子载波数, OFDM符号数]。在接收端做均衡的时候,一定要搞清楚每个子载波上的维度。我见过不止一次有人把整个时频网格直接reshape成二维矩阵,结果维度搞反,导致MMSE均衡出来的软比特都是乱的,BLER完全没法看。在2x2配置里,每个资源单元上的信道矩阵是一个2x2复矩阵,均衡是逐资源单元处理的。
2.3 信道快照复用:让仿真结果可对比
这是我在实际调试中非常看重的一点。直接每次调用channel()生成信道,每个SNR点的信道都不一样,那么不同MCS之间的性能差异会混入信道随机性,导致吞吐量曲线抖得像心电图。
我的做法是预先固定一组信道实现作为“快照”。比如每个SNR点固定跑200个时隙,先随机生成200个独立的信道实现,存成四维数组;然后所有MCS、所有均衡算法都用这同一组信道来测试。这样测出来的BLER差异基本都来自算法本身,而不是信道抽签运气。当然,快照数量要足够多,否则频率选择性信道的坏点占一部分,统计置信度不够。
3. PDSCH发射与接收全流程实现:解码链路是吞吐量的底气
3.1 发射端流水线:比特到符号的每一步都要留痕
我通常把发射端拆成四个子步骤:传输块编码、加扰调制、层映射和预编码、资源映射和OFDM波形生成。传输块编码的部分我用nrDLSCH对象来模拟,它会自动根据传输块大小选择LDPC基图(BG1或者BG2)并完成CRC、速率匹配等步骤。
nrDLSCH的使用方法比较固定:先是setTransportBlock把二进制比特写入,然后调用encode生成编码后的码字。这个过程需要指定调制阶数和目标码率。这里有个细节:LDPC BG2通常用于较小的传输块或低码率场景,BG1用于较大传输块和高码率场景。如果传输块大小刚好在边界附近,实际解码性能会受码率匹配和打孔方式影响,这是正常现象,不必惊慌。
加扰调制和资源映射直接用nrPDSCH即可。在链路里我习惯生成一个空的时频网格,然后通过nrPDSCHResourceMapping把调制符号放到正确的RE上。配置DM-RS的时候要格外注意MappingType和DMRSPosition,Type A映射下DM-RS位置通常在第2个OFDM符号,Type B则位于PDSCH起始符号。DM-RS密度直接决定了信道估计能拿到多少观测样本,Type 2配置比Type 1有更多的频域密度,适合高MCS场景。
3.2 接收端同步与信道估计:导频是解调的锚点
接收端第一步是从收到的时域波形恢复出资源网格。如果仿真链路没有主动加整数频偏和小数频偏,就用简单的定时对准直接切出OFDM符号做FFT。但要测试链路鲁棒性的话,我觉得应该在时域加一个已知参考信号,先用同步头做相关对齐,再进OFDM解调,否则后面处理时频偏累积可能会让高MCS完全解不出来。
信道估计这一块有两条路可选:理想信道估计和基于参考信号的信道估计。理想信道估计在性能验证阶段很有用,直接拿信道矩阵对每RE做均衡,这样把链路中其他所有环节都验证一遍,只剩信道估计误差不掺和。很多版本问题排查时,我都会先跑一遍理想信道,确认收发链路无误,再切换到实用信道估计。
基于DM-RS的实用信道估计流程是这样的:先从资源网格中提取DM-RS所在位置的接收符号,和本地生成的DM-RS参考符号做最小二乘(LS)估计,得到导频点信道值,再用插值把整个PDSCH时频区域补齐。MATLAB里可以用nrChannelEstimate封装这个流程,但自己实现一遍更清楚参数影响。
高MCS场景下我建议关注两点。第一,DM-RS时域符号数目是否够用,如果DMRSAdditionalPosition设太低,在快速时变信道里插值误差会放大。第二,频域插值是否平滑,两端尤其是资源块边界可能会因为插值窗口溢出产生估计失真。有条件的话,把信道估计出来的幅频响应画出来,和真实信道做对比,一眼就能看出估计质量。
3.3 MMSE均衡与LDPC译码:最后一个关口不能拖后腿
均衡的经典选择是MMSE,公式本质上是:
[ \hat{x} = (H^H H + \sigma^2 I)^{-1} H^H y ]
这个式子看着简单,但实现时最容易忽略的是噪声方差(\sigma^2)。信道生成的噪声功率和SNR定义方式必须一致。如果定义SNR = 信号功率/噪声功率,那么仿真时给每个接收天线加噪声后,( \sigma^2 )就是每根天线的噪声方差。如果把噪声功率弄错,MMSE就退化成一个带偏置的ZF均衡,高SNR区域BLER曲线会下不去。
均衡完成后就是解映射得到LLR。NR解调需要根据调制方式计算每个比特的对数似然比,再用nrPDSCHDecode完成解扰和速率恢复,最后进LDPC译码。MATLAB的LDPC译码器支持早停,对于高SNR下大部分传输块都能一次译码成功的场景,速度会快很多。注意译码器输入的LLR如果带了符号翻转的问题,BLER会直接垮掉,这个我在后面常见问题速查表里单独列。
4. 吞吐量计算、仿真循环与结果分析
4.1 吞吐量统计的正确公式
吞吐量统计是个细节决定成败的环节。简单的“成功比特数除以仿真时长”只在纯数据信道且没有重传时才成立。我更推荐先算BLER,再折算吞吐量。
对于没有HARQ重传的简化链路,每个传输块的初始传输吞吐量可以这样算:
[ \text{Throughput} = \frac{N_{\text{info}} \times (1 - \text{BLER})}{T_{\text{slot}} \times N_{\text{slots}}} ]
其中(N_{\text{info}})是一个传输块里的信息比特数,(T_{\text{slot}} \times N_{\text{slots}})是这次传输占用的时间。如果有重传,则分母要乘以平均传输次数(1/(1-\text{BLER})),等价于吞吐量折损。这个公式的好处是直接把BLER和吞吐量挂钩,曲线可解释性强。
我一般用这个流程:对SNR点循环,每个SNR点固定跑足够多的传输块,统计译码成功和失败的个数,算BLER。如果BLER低于0.01,可以提前跳出,没必要把剩余帧跑完,能省不少时间。如果BLER高于0.5,说明这个SNR点离工作点太远,也可以提前减少帧数,节省算力。
4.2 仿真循环的控制变量:没有对照就没有结论
做吞吐量扫描时,我强烈建议把固定变量写清楚。每个SNR点固定信道快照、固定传输块大小、固定MCS,只变SNR。这样扫描出来的曲线是单变量曲线,定位问题时可以逐个因素排查。
MCS扫描是另一个维度。固定SNR之后,遍历不同MCS得到吞吐量包络,能够在一次仿真里看到从低阶低码率到高阶高码率的上限。这个包络的核心作用是找到“拐点”:当信道条件不允许更高MCS可靠工作时,吞吐量就会在某个MCS后不再上升。如果没有这个包络,只看单条MCS曲线很难判断是信道估计问题还是工作点没选对。
统计置信度这块,我一直坚持“至少统计到20个错误块,否则BLER点不算数”。在目标BLER 10%附近跑20个错误块只需要约200个传输块,几秒钟就能完成。但要统计到1% BLER,就需要1000个以上的传输块,耗时会明显增加。所以帧数设计要和目标精度挂钩,不要盲目加大帧数,也不要看到曲线波动就怀疑算法。
4.3 瓶颈定位的基本套路
吞吐量上不去,不要急着改信道估计,先画三组曲线:理想信道估计下的吞吐量、实用信道估计下的吞吐量、不同MCS的BLER曲线。这样一对比,所有问题基本都能找到归属。
理想信道和实用信道吞吐量差别很大,说明信道估计是瓶颈,优先优化插值策略或增加DM-RS密度。理想信道估计下吞吐量依然上不去,说明编码调制或者均衡环节有问题,重点查码率匹配、LLR符号、噪声方差。不同MCS的BLER曲线如果在中高SNR处出现平台,也就是随着SNR升高BLER不再下降,通常意味着存在不依赖信噪比的固定错误源,比如符号间干扰、频偏残留、信道估计误差地板。定位到这一步,就可以去信道生成和同步模块里找原因了。
5. 调试经验、常见问题与排查实录
5.1 高频问题速查表
下面这份速查表,基本覆盖了我在PDSCH吞吐量仿真里反复遇到的高频问题。每条都来自实际调试记录,可能比看几遍文档都有用。
| 现象 | 可能原因 | 解决建议 |
|---|---|---|
| 高SNR下BLER依然很高 | 噪声方差与SNR定义不一致 | 检查MMSE均衡里的(\sigma^2)是否等于实际加噪功率 |
| 吞吐量曲线剧烈抖动 | 信道快照不固定或帧数太少 | 固定信道实现,确保至少统计20个错误块 |
| 256QAM性能反不如64QAM | DM-RS密度不足或插值窗口过小 | 提高DMRSAdditionalPosition,检查信道估计输出 |
| 空口误码率正常但BLER很高 | 加扰ID或RNTI相关参数不匹配 | 核对NCellID、加扰初始化参数 |
| 高MCS码率达到1或超过1 | 传输块大小选择超出调制承载能力 | 减小传输块,检查TargetCodeRate |
| 仿真过程中偶尔出现维度报错 | 天线层数和信道矩阵维度不一致 | 打印H的size,确认[接收天线, 发射天线] |
| 译码输出比特顺序错乱 | 速率匹配参数或码块分段不一致 | 用单码块小传输块验证收发比特对齐 |
5.2 定位问题要用二分法,不要同时改三个变量
我最开始调试PDSCH吞吐量的时候,经常做一件事:发现问题后同时调DM-RS密度、换均衡算法、改信道参数,结果好了也不知道是哪个起的作用,坏了更是一头雾水。后来我给自己定了一个规矩:一次只改一个变量。
具体做法是这样:如果当前是256QAM高码率,BLER曲线出现平台,先保持调制方式和信道参数不变,把接收端换成理想信道估计。如果平台消失,那问题就在信道估计;如果平台还在,再换成无频偏的理想信道,逐个排除。这个过程看起来很笨,但实际上比“东一榔头西一棒槌”快得多。
我记得有一次调256QAM曲线,高SNR区域BLER一直停在0.15左右,理想信道估计也没改善。后来排查到信道生成环节,才发现延迟扩展设成了1微秒,而30 kHz SCS下循环前缀只能覆盖约2.3微秒的时延。这个延迟扩展本来没有超过CP上限,但和频域插值窗口结合后,边缘子载波上的信道估计会漏掉一部分能量,相当于引入了一个与SNR无关的底噪。把延迟扩展改到300纳秒后,平台立刻消失。
5.3 仿真性能优化:别让复杂度拖垮验证迭代
链路级仿真的迭代速度直接决定调试效率。我的经验是分三层优化。
第一层是减少无效帧。BLER太高或太低的SNR点无需统计太多帧,只有工作点附近需要密集统计。通过提前判断错误块数量来跳出循环,可以省掉大头计算。
第二层是信道快照和调制波形尽量缓存。很多仿真里发射端资源映射和信道生成都是可重复的,把中间结果缓存下来,每次循环直接加载,比重新生成快一个量级。尤其是OFDM调制和解调那一整块,重复计算特别浪费。
第三层是合理使用MATLAB的并行计算。对多个SNR点的仿真天然独立,可以用parfor并行跑。要注意的是随机数种子必须分开管理,否则每个并行任务得到的随机序列完全一样,统计结果就会失真。固定信道快照的方式恰好也解决了并行随机数一致性问题,因为信道已经事先全部生成了,各worker只是各自处理不同SNR点而已。
5.4 最后再分享一个关于“先跑通再调优”的心得
如果你刚开始搭这套链路,我的建议是先用最简单的配置跑通全流程:单层传输、QPSK、低码率、TDL-A信道、理想信道估计。把这个配置下的BLER曲线做到符合直觉之后,再逐步叠加复杂度,先加实用信道估计,再加多层MIMO,最后换到TDL-C信道和高阶调制。不要一开始就开满256QAM和4流传输,否则链路里面每个模块都可能出错,所有现象堆叠在一起,根本没法判断先调哪里。链路仿真的本质是建立可控的因果关系,听捷报不如看变量,看变量不如固定变量的对照实验。这套PDSCH吞吐量仿真搭起来以后,后面做CSI反馈、多用户调度甚至毫米波链路的扩展,都能复用同一个框架。