前两天有个同行私信我,说自己按照教程搭了个 Simulink 卷积码编码加 BPSK 调制系统,跑完误码率曲线之后傻眼了——硬判决和软判决两条曲线几乎贴在一起,差的不到 0.5 个 dB,和他印象中“软判决最少多赚 2 个 dB”的经验完全对不上。我让他把模型截图发过来一看,问题立刻清楚了:他的所谓“软判决”,只是把 BPSK 解调器的输出从 0/1 换成了 ±1,然后照样接 Viterbi Decoder 的 Hard Decision 输入端口。这等于还是硬判决,软信息根本没送到译码器里。
这类问题在 Simulink 通信仿真里特别常见。原因也简单:硬判决和软判决在原理上就一句话的差别,但落到 Simulink 模块配置上,涉及解调器输出模式、量化方式、Viterbi 译码器的 Decision type、噪声方差参数等一系列联动。任何一环没对齐,结果都是错的,而且错得不一定离谱,反而更难察觉。
这篇文章我就拿“卷积码 + BPSK + AWGN + 误码率仿真”这套最经典的链路,把硬判决、软判决两种结构在 Simulink 里的完整搭法、参数配置、误码率统计和结果差异全部拆开讲一遍。适合正在学通信原理、做课程设计,或者刚开始接触 Simulink 通信系统仿真的读者。文章里所有模块和参数我都按实际可复现的方式写,版本以 MATLAB 2016 到 2021 之间为主,个别模块路径在不同版本里略有差异,但名称是一样的,搜索时直接搜模块名更快。
1. 卷积码与BPSK链路仿真的整体架构:先搞清楚信号流
1.1 为什么拿卷积码 + BPSK 这个组合做仿真
卷积码是信道编码里最经典的纠错码之一,它的核心思想是:编码器输出的每个比特不仅取决于当前输入比特,还取决于之前若干个比特。这些历史比特构成的状态形成了一个网格图,接收端用 Viterbi 算法在这个网格图上搜索最可能的路径,从而恢复原始比特序列。相比分组码,卷积码的译码延迟更可控,软判决信息用起来也更自然,所以在实际系统中应用极广。
BPSK 是所有数字调制里最基础的一种,星座点只有 +1 和 -1 两个。它看起来简单,但它是理解 QPSK、16QAM 等更高阶调制的基础,也是验证编码性能最直观的场景:信道是 AWGN,调制是二进制,误码率曲线可以直接和理论公式对照。把卷积码和 BPSK 放在一起仿真,可以清晰地观察到编码增益从哪来、硬判决和软判决的差距有多大。
很多人刚开始做这个仿真时有个误解,以为卷积码的误码率曲线越低越好,其实曲线要和未编码 BPSK 的理论曲线对照看才有意义。未编码 BPSK 的误码率公式是:
[
P_e = \frac{1}{2} \mathrm{erfc}\left(\sqrt{\frac{E_b}{N_0}}\right)
]
加了卷积码之后,同样的 Eb/N0 下误码率会显著降低,这就是编码增益。比如约束长度 7、码率 1/2 的卷积码,软判决在 Eb/N0 约 5.5 dB 时就能到 1e-5 量级,而未编码 BPSK 要达到同样误码率大约需要 9.6 dB,编码增益约 4 dB 以上。这个差距就是我们做这个仿真的目的——直观看到它,并且能复现它。
1.2 完整信号链路由哪些模块组成
整个仿真链路的信号流向可以用一条主线描述:
随机二进制信源 -> 卷积编码器 -> BPSK 调制器 -> AWGN 信道 -> BPSK 解调器 -> 维特比译码器 -> 误码率统计
每个环节在 Simulink 里对应具体的 Communications Toolbox 模块:
| 功能 | 模块名称 | 所在子库 |
|---|---|---|
| 随机信源 | Bernoulli Binary Generator | Comm Sources > Random Data Sources |
| 卷积编码 | Convolutional Encoder | Error Detection and Correction > Convolutional |
| BPSK 调制 | BPSK Modulator Baseband | Modulation > Digital Baseband Modulation |
| 加噪 | AWGN Channel | Channels |
| BPSK 解调 | BPSK Demodulator Baseband | Modulation > Digital Baseband Modulation |
| 维特比译码 | Viterbi Decoder | Error Detection and Correction > Convolutional |
| 误码统计 | Error Rate Calculation | Comm Sinks |
| 结果显示 | Display | Simulink > Sinks |
硬判决和软判决的差别就发生在“BPSK 解调器”和“Viterbi Decoder”这两个模块的衔接处。硬判决路径上,解调器输出 0/1,直接进 Viterbi 的 Hard Decision 输入;软判决路径上,解调器要输出携带信道置信度的软信息(通常是对数似然比 LLR),经过量化或者直接送进 Viterbi 的 Soft Decision/Unquantized 输入。
1.3 建议的初始参数
为了让后面每个章节的讨论都有明确的语境,我先把一套常用的初始参数列出来。这套参数的运行速度和性能都比较均衡,也方便和大多数教材上的曲线做对比。
| 参数项 | 取值 | 说明 |
|---|---|---|
| 卷积码约束长度 K | 7 | 编码器寄存器的级数加 1 |
| 码率 R | 1/2 | 每个信息比特输出 2 个编码比特 |
| 生成多项式 | [171 133](八进制) | 最经典的 1/2 码率卷积码多项式 |
| Traceback depth | 35 | Viterbi 译码回溯深度,通常取 5*(K-1) |
| BPSK 相位偏移 | 0 | 0 映射到 +1,1 映射到 -1 |
| 软判决量化比特数 | 3 | 8 电平软信息,性能和未量化差距很小 |
| 每帧比特数 | 1000 | 帧模式仿真,速度更快 |
你完全可以从约束长度 K=3、生成多项式 [7 5] 开始验证链路正确性,跑通了再换 K=7。K=3 的译码复杂度比 K=7 低一个量级,调试时反馈非常快,我后面讲调试技巧时还会回到这个点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 编码器与调制器的参数配置要点
2.1 卷积编码器的 trellis 结构怎么定
打开 Convolutional Encoder 模块的参数对话框,最核心的一项叫 Trellis structure。它不能直接填生成多项式,而是要填一个由 poly2trellis 函数生成的结构体。你需要在 MATLAB 工作区先执行:
matlab复制trellis = poly2trellis(7, [171 133]);
然后在模块参数里把 Trellis structure 填成 trellis,或者直接填 poly2trellis(7, [171 133]) 表达式(视版本而定,有的版本支持直接填表达式,有的必须在工作区定义变量后再填变量名,稳妥起见建议用变量名)。
poly2trellis 的两个参数,第一个是约束长度 K=7,第二个是生成多项式的八进制向量 [171 133]。这里的关键背景是:约束长度 7 意味着编码器有 6 个移位寄存器,状态数是 2^6 = 64。两个生成多项式分别对应两个输出比特,所以码率是 1/2。这个编码器的自由距离是 10,在 1/2 码率卷积码里性价比很高,802.11a、LTE 早期的基准卷积码都基于这一套参数。
如果你用 K=3、[7 5],那状态数只有 4,Viterbi 译码的速度会快非常多,但自由距离只有 5,编码增益明显不如 K=7。做调试时先用短约束长度验证全链路逻辑,再用长约束长度做正式性能仿真,是最稳的一条路。
2.2 BPSK 调制解调模块的工作模式
BPSK Modulator Baseband 模块默认相位偏移为 0,也就是输入比特 0 映射到 +1,输入比特 1 映射到 -1。如果你在别的教程里看到“0 映射到 -1,1 映射到 +1”的写法,那通常是把相位偏移设成了 pi。这两种定义都不影响误码率曲线,但要注意:解调器和调制器的相位偏移必须一致,否则符号映射错位,误码率会永久停留在 0.5 附近。
BPSK Demodulator Baseband 模块的参数对话框里有一个 Decision method 下拉框,里面通常有 Hard decision、Log-likelihood ratio、Approximate log-likelihood ratio 这几项。这一步是整个硬判决/软判决链条的分水岭:
- 选 Hard decision,输出就是 0/1 硬比特,只能接 Viterbi Decoder 的 Hard Decision 输入。
- 选 Log-likelihood ratio,输出的是浮点型 LLR,可以量化后接 Soft Decision,也可以直接接 Unquantized 输入。
- 选 Approximate log-likelihood ratio,输出近似 LLR,省去指数和对数运算,适合硬件实现仿真,性能和完整 LLR 差别很小。
需要特别注意的是,LLR 模式会要求你填一个 Noise variance 参数。这个参数不填对,LLR 的幅度就错,直接影响软判决增益。很多人的软判决曲线不理想,不是 Viterbi 的问题,是这里的噪声方差填错了。
2.3 帧模式与采样模式:为什么强烈建议用帧
搭建模型时很多人习惯用默认的 sample-based 模式,也就是每个采样时间往前推一个比特。仿真模型简单了,但跑起来极慢,尤其是 Eb/N0 到 6 dB、7 dB 以后,为了等到足够多的错误比特,仿真时间按分钟甚至小时算,非常痛苦。
更高效的方案是把整个链路切换到 frame-based 帧模式。做法是在 Bernoulli Binary Generator 里把 Samples per frame 设成 1000,这样每次往前推 1000 个比特,卷积编码器、调制器、AWGN 信道都会按帧处理。帧模式下同样的仿真步数,数据处理量高出几个量级,误码率统计的收敛速度快得多。
帧模式最常见的报错是 Simulink 提示“Dimensions mismatch”或者“Input port width mismatch”。根源通常是某个模块还是 sample-based,突然接到了一个帧信号。排查思路是看信号线上标注的尺寸,如果是 [1000x1] 就说明是帧信号,所有后续模块都要能接受这个维度的输入。BPSK 调制器和 AWGN Channel 默认都能直接处理帧信号,Viterbi Decoder 也支持帧输入,真正容易出问题的是后面的 Error Rate Calculation 和 Display 模块。只要保持全链路一致,很少会报错。
3. 硬判决与软判决:Simulink 中两条译码路径的实现差异
3.1 硬判决路径:最基础也最容易理解
硬判决路径的配置最直观。BPSK Demodulator Baseband 的 Decision method 选 Hard decision,输出就是 0 和 1。然后把它直接接到 Viterbi Decoder 的输入,Viterbi Decoder 的 Decision type 选 Hard Decision。Done,链路就通了。
硬判决的原理,是在接收端先对每个符号做一个二值化决策:接收值大于 0 判为 +1,对应比特 0;小于 0 判为 -1,对应比特 1。决策做完之后,所有关于“这个符号到底有多可信”的信息就全部丢失了。比如一个接收值是 0.01,勉强判成 +1,另一个接收值是 1.5,非常自信地判成 +1,两者在硬判决模块输出后都是同一个比特“0”,Viterbi 译码器完全不知道第一个判决其实非常脆弱。
这就是硬判决性能损耗的根源:信息被提前压缩成了 1 bit。Viterbi 算法本身虽然是最优路径搜索,但它拿到的路径度量是基于“二值判决”算出来的,等于是把原始信道信息做了有损压缩之后再做最大似然译码。
3.2 软判决路径之一:LLR 输出加量化器
软判决路径第一形态,是让 BPSK 解调器输出 LLR,经过量化后送给 Viterbi Decoder 的 Soft Decision 输入。步骤如下:
第一步,BPSK Demodulator Baseband 的 Decision method 选 Log-likelihood ratio,Noise variance 参数按当前仿真信噪比计算。第二步,在解调器和 Viterbi Decoder 之间插入一个量化环节。Viterbi Decoder 的 Decision type 选 Soft Decision,Number of soft decision bits 设为 3,表示输入是 0 到 7 的整数。第三步,量化器把浮点 LLR 映射到 0 到 7 的整数范围。
标准做法是使用 Communications Toolbox 里的 Quantizing Encoder 模块,设置量化区间和输出码本。不过它的配置相对繁琐,如果只是教学仿真,我建议用 MATLAB Function 模块写一小段量化逻辑,简洁且更容易控制:
matlab复制function y = soft_quant(u)
% 3-bit soft decision quantization for LLR input
% u: LLR from demodulator (float)
% y: quantized soft value (0 ~ 7)
y = uint8(min(7, max(0, round(u + 4))));
end
这段代码的逻辑是:把 LLR 加上 4 的偏移,四舍五入后裁剪到 0 到 7。假设 LLR 近似在 ±4 之间,那么 0 代表“极可能为 0”,7 代表“极可能为 1”,中间值代表不确定。Viterbi Decoder 对 Soft Decision 输入的解释就是这个约定,所以量化代码和译码器的语义必须对齐。
用这种结构跑出来,软判决相比硬判决大约有 2 dB 的增益,也就是同样误码率下软判决所需 Eb/N0 少了约 2 dB。这个数字对应的是 3 bit 量化的典型结果,量化比特数继续增加时增益会缓慢接近未量化上界,但超过 4 bit 后收益已经很小。
3.3 软判决路径之二:浮点软值直接给 Unquantized 译码器
如果你不想碰量化器,Simulink 的 Viterbi Decoder 里还有一个更省事的选项:Decision type 选 Unquantized。这种模式下,BPSK 解调器输出的浮点 LLR 可以直接连到 Viterbi Decoder,省掉中间的量化环节,也不需要 Number of soft decision bits 这个参数。
它的方便之处在于,不需要手动调整量化范围,也不用担心量化边界设置不当导致软信息畸变。从仿真结果看,Unquantized 模式对应的性能上限就是“无限精度软判决”,比 3 bit 量化通常好 0.1 到 0.2 dB,差距很小。因此,如果你只是想快速验证软判决相对硬判决的优势,直接走 Unquantized 是最快的路径。
不过 Unquantized 模式也有代价。一方面,浮点数据的处理量比整数量化数据大,帧长度很大时仿真速度会慢一些;另一方面,它掩盖了量化器的设计问题,如果你后续要在 FPGA 或硬件平台上实现软判决译码,量化是绕不开的一步。所以我的建议是:理解原理用 Unquantized 路径,做完整仿真和性能评估用 3 bit 或 4 bit 量化路径。
3.4 三条路径怎么选
| 译码方式 | 解调器模式 | Viterbi Decision type | 实现难度 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| 硬判决 | Hard decision | Hard Decision | 最低 | 基准 | 验证链路、对比参考 |
| 软判决(量化) | Log-likelihood ratio | Soft Decision(3-4 bit) | 中 | 接近最优 | 贴近工程实现 |
| 软判决(未量化) | Log-likelihood ratio | Unquantized | 最低 | 理论上限 | 快速验证、原理教学 |
实际工程项目里,软判决普遍采用 3 到 4 bit 量化,这个选择的依据我在第 6 章会专门说明。
4. 误码率统计的坑:延迟对齐、Eb/N0 换算、仿真长度
4.1 Receive Delay 不对,误码率永远 0.5
Error Rate Calculation 模块的 Receive delay 参数,是几乎所有初学者第一次跑出“误码率 0.5”的元凶。原因是:经过卷积编码和 Viterbi 译码之后,输出比特相比原始输入比特存在一个固定的延迟,如果误码率统计模块不知道这个延迟,它就会拿错位的两个比特去比较,结果当然接近 0.5。
这个延迟的大小和 Viterbi 译码器的 Traceback depth 有关,典型值是 traceback depth 本身,比如 35。但具体值还会受到模块内部流水线的影响,直接看文档不一定算得准。最有效的做法是先用一个短仿真、高 Eb/N0 的配置快速跑一遍,观察 Error Rate Calculation 的输出。如果输出不是 0 也不是一个极小的数,基本就是延迟没对齐。
定位延迟的直观方法是在模型中加一个 Scope,把发送端原始比特(延迟前)和译码器输出比特(延迟后)同时接进去,数一数两个波形差了几个采样点。注意要先做符号同步,再数采样点,否则容易数错。还有一种更省事的方式是直接在模型里加一个 Variable Integer Delay 模块,把发送端信号延迟一个变量 D,然后在仿真里扫 D 的值,找到误码率最小且稳定接近理论值的 D。
4.2 AWGN 模块里的 Eb/N0 和“信息比特 Eb/N0”差了一个码率
这个问题非常隐蔽,而且会导致曲线整体右移 3 dB 左右,很多人查了半天不知道错在哪。
AWGN Channel 模块参数对话框里的 Eb/N0 模式,其实是针对“输入到信道模块的每个符号”来定义的。BPSK 每个符号 1 个信道比特,所以如果你直接把 AWGN 模块的 Eb/N0 设为 5 dB,那它模拟的是“编码后信道比特的信噪比”,不是“信息比特的信噪比”。对于 1/2 码率,每个信息比特对应两个编码比特,总发射能量不变时,每个编码比特的能量只有信息比特的一半,也就是 3 dB 的差别。
换算公式很简单:
[
(E_b/N_0){\text{AWGN模块}} = (E_b/N_0){\text{信息比特}} - 10\log_{10}(1/R)
]
码率 R=1/2 时,10log10(2) ≈ 3.01 dB。所以如果你的横轴想标“信息比特 Eb/N0”,在 AWGN 模块里设置的值应该是信息比特 Eb/N0 减去 3.01。很多人不换算,直接把 AWGN 模块里的值当成了信息比特 Eb/N0,画出来的曲线比真实曲线整体偏右 3 dB,和理论值对不上。
我用一个具体的数值帮你理解。假设你希望仿真相位为 4 dB 的信息比特 Eb/N0,那么 AWGN 模块里应该设:
[
4 - 3.01 \approx 0.99\ \text{dB}
]
设完之后,Viterbi 译码器实际工作的信道符号信噪比是 0.99 dB,但因为存在编码冗余,信息比特的有效信噪比恢复到了 4 dB。这样画出来的曲线横轴才能和理论公式、其他文献一致。
4.3 仿真至少跑多少数据才算数
误码率本身是一个统计量。一次仿真得到 1 个错误比特,和得到 50 个错误比特,置信度完全不一样。经验做法是每个信噪比点至少累计 50 到 100 个错误比特,这样误码率的相对误差大约在 10% 到 20% 以内,曲线已经比较光滑了。
如果仿真时间有限,可以接受一个折中方案:每个信噪比点最多跑 N 个信息比特(比如 1e6),在这 N 个比特内如果错误数不足 50 个,那就以实际错误数除以 N 作为误码率的上限估计。这个点的误差会偏大,但曲线趋势仍然可信,至少告诉你“真实误码率低于这个值”。
这也解释了为什么高 Eb/N0 区域的仿真最耗时。K=7、1/2 码率卷积码在信息比特 Eb/N0 = 6 dB 时误码率大约在 1e-6 量级,要看到 100 个错误,需要跑 1e8 个信息比特。这时候帧模式仿真就是必需品,并且你通常只需要跑到 1e-5 或 1e-6 就已经能说明问题了,不必强行追求更低的误码率点。
5. 批量仿真与 BER 曲线绘制全流程
5.1 用脚本循环驱动 Simulink 模型
误码率性能仿真不能只跑一个信噪比点,你需要连续扫描多个 Eb/N0 值,把每个点的误码率收集起来画成曲线。在 Simulink 里做这件事,最主流的方式是用 MATLAB 脚本驱动模型循环仿真。
首先,在模型中把 AWGN Channel 的 Eb/N0 参数设置成一个 MATLAB 工作区变量,比如 EbNodB。这样脚本每次循环时只需改变量值,再调用 sim() 即可。
matlab复制% 初始化
trellis = poly2trellis(7, [171 133]);
EbN0_vec = 0:0.5:6; % 信息比特 Eb/N0,单位 dB
ber_soft = zeros(size(EbN0_vec));
ber_hard = zeros(size(EbN0_vec));
for idx = 1:length(EbN0_vec)
% 信息比特 Eb/N0 转换成 AWGN 模块需要的值
% 1/2 码率,BPSK 每个符号 1 bit
EbN0_info = EbN0_vec(idx);
EbN0_awgn = EbN0_info - 10*log10(2);
% 更新 AWGN 模块参数
set_param('conv_bpsk_awgn/AWGN Channel', 'EbNodB', num2str(EbN0_awgn));
% 更新 BPSK 解调器 LLR 模式下的噪声方差参数
EsN0_lin = 10^(EbN0_awgn/10);
noise_var = 1/(2*EsN0_lin);
set_param('conv_bpsk_awgn/BPSK Demodulator Baseband', ...
'NoiseVariance', num2str(noise_var));
% 运行仿真,仿真时间由工作区变量 simTime 控制
simOut = sim('conv_bpsk_awgn', 'StopTime', num2str(simTime));
% 从 To Workspace 模块收集误码率
ber = simOut.ber.Data;
ber = ber(end);
ber_soft(idx) = ber;
end
这段脚本是针对软判决路径的。硬判决路径可以复用同一个模型,把 Decision type 切到 Hard Decision 后重新跑一轮,也可以直接复制接收机部分做两条并行支路。并行支路的优势是两种判决方式共享同一份信道噪声,对比更公平;缺点是模型复杂度更高。我建议先跑完一条曲线,再切换配置跑另一条,省事且不容易出错。
5.2 从模型里把误码率拿出来
为了让脚本能拿到误码率,在模型中需要加一个 To Workspace 模块,连到 Error Rate Calculation 的 E 端口。To Workspace 的 Variable name 设成 ber,Save format 选 Timeseries 或 Array 都可以。脚本里取 ber.Data 的最后一个值即可。
注意一个细节:Error Rate Calculation 模块在仿真刚开始的几个采样周期里,由于延迟对齐尚未稳定,E 端口可能输出一个很大的瞬时值。取最后一个值的做法能避开这个问题,因为运行到仿真结束时,统计窗口已经覆盖了大量比特,最后一个值就是全过程的累计误码率。
帧模式下,ber.Data 的维度是 [numFrames x 1],取 end 索引拿到的就是最后一个帧的累计误码率。如果你用的是 sample-based 模式,同理取最后一行。
5.3 结果解读:硬软判决差出来的 2 dB 从哪来
跑完两组仿真后,用下面的脚本一次画出两条曲线:
matlab复制semilogy(EbN0_vec, ber_hard, 'o-', 'LineWidth', 1.5);
hold on;
semilogy(EbN0_vec, ber_soft, 's-', 'LineWidth', 1.5);
grid on;
xlabel('信息比特 E_b/N_0 (dB)');
ylabel('误码率 BER');
legend('硬判决', '软判决');
预期的曲线特征是:在 BER = 1e-3 量级,软判决比硬判决节省约 1.5 到 2.5 dB 的 Eb/N0。这条间距不受仿真噪声影响,是卷积码本身特性决定的。
从机理上看,这 2 dB 的本质是软判决把“符号置信度”保留下来传给了 Viterbi 译码器。硬判决在解调那一刻就把连续值压缩成了二值,等于是主动放弃了一部分信道观测信息;软判决则让译码器知道哪些符号可靠、哪些不可靠,在做网格路径搜索时能合理权衡。形象一点说,硬判决是把每个判罚都当成等权重的“有罪/无罪”投票,而软判决给每张选票附上了权重,可信度高的票权重更大。维特比算法本身不做这个加权,它的输入决定了它能看到多少信息。
6. 链路搭建与调试中的六个高频问题
6.1 软判决量化范围和位宽怎么定
我在第 3 章给了最简单的量化代码,把 LLR 加 4 后裁剪到 0 到 7。但如果你真的用这套代码在不同 Eb/N0 下跑全曲线,会发现低信噪比区域没问题,高信噪比区域却可能出现软判决反而变差的情况。原因是 LLR 的幅度和噪声方差直接相关,Eb/N0 升高后 LLR 的动态范围变大,固定加 4、裁剪到 0 到 7 会把很多本来置信度很高的值都压到同一个最大值 7,等于人为丢掉了部分软信息。
更稳妥的做法是让量化范围跟随 SNR 自适应。最简单的实现是在 MATLAB Function 里加一个噪声方差输入,把 LLR 先按噪声方差归一化再送量化器:
matlab复制function y = soft_quant_adaptive(u, noise_var)
% 自适应软判决量化
% 将 LLR 按噪声方差归一化,再映射到 0~7
norm_u = u / sqrt(noise_var);
scale = 2.0; % 经验缩放系数
y = uint8(min(7, max(0, round(norm_u / scale + 4))));
end
这里 scale 取 2.0 是经验值,目的是让归一化后的 LLR 在 ±4 附近,正好落在 3 bit 量化范围里。如果你希望更精确,可以先做一个短仿真,用 Scope 观察 LLR 的分布范围,再根据分布调整 scale。总之,软判决量化最忌讳的就是“量化范围和信噪比不匹配”,这会让高信噪比下的软判决增益退化,甚至不如硬判决。
6.2 Traceback depth 对性能和延迟的影响
Viterbi Decoder 的 Traceback depth 参数,决定了译码器在输出最终比特之前要“回溯”多少层网格路径。取值太小,幸存路径还没收敛就强制输出,性能损失明显;取值太大,译码延迟增加,硬件资源消耗上升。
工程上常用的经验值是约束长度 K 的 5 到 10 倍。K=7 时对应 35 到 70,取 35 已经能接近最优性能,取 42 更稳一些但差别不大。仿真中如果发现误码率曲线在高信噪比区域出现“平台效应”(即误码率不随 Eb/N0 增加而下降),第一反应就应该是把 traceback depth 调大一倍试试。
Traceback depth 还会影响 Error Rate Calculation 的 Receive delay 取值,原因我在 4.1 节已经说过。你每改一次 traceback depth,都应该重新检查延迟对齐,否则曲线会整体乱掉。
6.3 高信噪比下仿真时间太长怎么办
仿真到 Eb/N0 超过 5 dB 后,误码率降到 1e-5 以下,跑一个数据点可能就要很久。我常用的优化手段是同时用好几个层面的加速:
其一,改用帧模式并增大每帧比特数。Samples per frame 从 1000 提到 10000,Simulink 处理单帧的启动开销和调度开销会被摊薄,吞吐量明显提升。
其二,缩减不必要的信号可视化。Display 模块和 Scope 模块在仿真中会持续刷新数据,非常拖慢速度。正式批量仿真时,建议把它们从模型中暂时断开,只保留 To Workspace 采集数据。
其三,利用多核并行。如果你的机器有多核,可以用 parfor 替代 for 循环来扫描 Eb/N0 点。每个信噪比点的仿真彼此独立,天然适合并行。要注意 parfor 里 set_param 和 sim() 的用法有一些限制,需要先把模型和参数都准备好,再用 sim 的 Name-Value 方式传入参数。
6.4 LLR 模式下的 Noise variance 必须同步更新
这个坑我在第 2 章提过,但它是真实模型中最容易出现“软判决曲线对不上”的原因,值得单独强调。如果你在模型里手动给 BPSK Demodulator Baseband 填了一个固定的 Noise variance,比如 1,那么当 AWGN 模块的 Eb/N0 从 1 dB 扫到 6 dB 时,解调器算 LLR 时始终假设噪声方差是 1,LLR 的幅度就是错的。低噪声环境下 LLR 会被放大,高噪声环境下 LLR 会被压缩,结果软判决增益不稳定,曲线甚至可能出现非单调的抖动。
解决办法就是我在 5.1 节脚本里做的:每次循环更新 AWGN 模块参数的同时,用当前 Es/N0 计算出正确的噪声方差,再 set_param 更新解调器的 Noise variance 参数。噪声方差的计算公式在归一化 BPSK 信号功率下是:
[
\sigma^2 = \frac{1}{2 \cdot 10^{(E_s/N_0)/10}}
]
注意这里的 Es/N0 是 AWGN 模块实际的信道符号信噪比,也就是信息比特 Eb/N0 减掉码率损失之后的值,不要弄混。
6.5 模型里出现橙色警告“Input port width mismatch”怎么办
帧模式下最常见到这种警告。排查思路是先双击触发警告的连线,看 Simulink 提示的端口尺寸。通常是一个模块期望输入 [1000x1],但前一模块输出的是 [1x1000],或者反过来了。
解决办法是在两者之间加一个 Reshape 模块,或者在上游模块里把 Output data type 和 Signal Attributes 里的维度设置对齐。最省事的做法是在 Bernoulli Binary Generator 里确认 Output signal type 是 Frame-based,然后所有后续模块都用默认的“继承维度”模式,一般能自动对齐。手动指定维度的模块越多,出错的概率越大,教学仿真里保持默认继承是最省心的。
6.6 仿真结果完全正确但曲线不够光滑怎么办
曲线不够光滑通常不是模型问题,而是统计误差。在第 4.3 节我给了“至少 50 个错误比特”的经验法则。如果你的曲线在 Eb/N0 高区域剧烈抖动,把最大仿真时间翻倍,或者把停止条件改成“累计错误数达到某个阈值”再停。
这里有一个实用的改进技巧:不要对所有 Eb/N0 点用同样的仿真时间。低 Eb/N0 区域错误发生快,跑一小段时间就有足够的统计样本;高 Eb/N0 区域则要多跑几个量级。在脚本里根据当前 Eb/N0 动态设置 StopTime,能大幅缩短总耗时。
最后分享一点我的实际操作体会。第一次做这个仿真时,我也犯过把 AWGN 模块 Eb/N0 直接当信息比特 Eb/N0 用的错误,曲线左挪右挪反复对不上理论值,最后换单位换算后才意识到差了一个 3.01 dB。从那以后我给自己立了一条规矩:任何信噪比参数写进模型之前,先搞清楚它是“信道符号的”还是“信息比特的”,并且把换算关系写在工作区脚本的注释里。这个习惯帮我避开了后面很多类似的坑。如果你正准备亲手搭这套仿真,也建议从 K=3 的短约束长度开始跑通全链路,确认曲线单调下降且硬软判决的相对关系正确之后,
