简介:QPSK+LDPC通信链路仿真是数字通信领域常见的学习与验证场景,这套基于MATLAB 2022a的误码率仿真方案面向通信专业学生与科研人员,完整覆盖QPSK调制解调与LDPC编译码全流程。压缩包共13个文件,包含8个M源程序、4个MAT数据文件和1个TXT说明(内含视频演示地址),整体仅109KB;其中main1/main2/main3为主程序,H2G、getG、func_Dec等为辅助函数。程序可输出QPSK各环节波形并绘制误码率曲线,LDPC部分利用稀疏校验矩阵实现编译码,中文注释配合操作演示视频便于复现仿真过程,可直接对照学习。使用时将MATLAB当前文件夹切换到程序所在路径即可运行,目前已有210人学习下载,适合需要深入理解通信链路仿真或研究LDPC编码增益的读者,也可用于课程设计或毕业设计参考。
1. QPSK+LDPC链路仿真:为什么误码率曲线值得你亲手复现
在通信系统仿真里,QPSK调制解调加LDPC编译码这套组合,是误码率仿真中最常见也最容易出“假结果”的方向。你以为自己在测编码增益,实际可能测的是随机数种子、编码矩阵的构造方式,甚至是译码迭代次数的上限。很多人拿着现成代码一跑,看到曲线掉下去就觉得完成了,但换一个码长或信噪比区间,曲线立刻露出马脚。
这套链路要解决的实际问题是:在AWGN信道下,量化地观察LDPC编码带来的编码增益,并借助误码率曲线判断不同码率、码长和迭代次数对系统性能的影响。适合做通信系统课程设计、毕业设计,以及刚接触信道编码又想快速建立链路级仿真概念的工程师。它不复杂,但每个模块都有必须较真的参数——软解调的LLR计算、信道能量的换算、误码统计的停止条件,任何一个偷懒都会让结论失真。这篇文章就把整条链路拆开,从QPSK收发到LDPC编译码,最后落在误码率统计的可靠性上。
2. 先跑通QPSK调制解调:星座映射、AWGN信道与软判决LLR的三个关键参数
2.1 QPSK调制在MATLAB里的最小实现:从比特映射到星座点
QPSK的本质是把两个比特映射到一个复数符号上,常见的映射方式是Gray编码。Gray码的好处是相邻星座点只差一个比特,误判到邻近点时只错一个比特,这对后续LDPC译码的软信息质量很重要。直接用qammod函数也能做QPSK,但为了后续接LDPC软解调,我习惯自己写映射,把比特到符号的对应关系完全握在手里。
function sym = qpsk_mod(bits) % QPSK调制,输入为0/1比特流,长度需为偶数 % 映射规则:00 -> exp(1j*pi/4), 01 -> exp(1j*3*pi/4) % 11 -> exp(1j*5*pi/4), 10 -> exp(1j*7*pi/4) % 对应Gray码:相邻象限仅1比特差异 bits = bits(:); if mod(length(bits), 2) ~= 0 error('比特数必须为偶数'); end % 两两分组,第一个比特决定实部符号,第二个决定虚部符号 even_idx = 1:2:length(bits)-1; odd_idx = 2:2:length(bits); % 映射公式:s = (1-2*b1)/sqrt(2) + 1j*(1-2*b2)/sqrt(2) sym = (1 - 2*bits(even_idx))/sqrt(2) + ... 1j * (1 - 2*bits(odd_idx))/sqrt(2); sym = sym(:); end这段代码按Gray映射把00、01、11、10依次映射到四个象限。注意归一化因子sqrt(2),它保证每个符号的功率为1,也就是E_s = 1。这个细节决定了后面信噪比换算是否准确。很多初学翻车就是栽在这里——星座点模值不是1,导致Eb/N0和实际加噪功率对不上。我一般会在写完映射后立刻验证:mean(abs(sym).^2)应该等于1,偏差超过1e-10就说明归一化写错了。
2.2 AWGN信道加噪:功率换算与awgn函数的隐藏坑
信道加噪看似简单,但awgn函数的信噪比参数默认是SNR而不是Eb/N0,很多人直接在循环里写awgn(rx, snr_db),结果整条曲线横轴标的是SNR,却拿Eb/N0去解释,数值全对不上。正确做法是先明确系统模型,再用符号信噪比或比特信噪比显式加噪。
function rx = awgn_channel(tx, EbN0_dB, code_rate, M) % AWGN信道加噪 % EbN0_dB: 比特信噪比(dB) % code_rate: LDPC编码码率,无编码时传1 % M: 调制阶数,QPSK即4 EsN0_dB = EbN0_dB + 10*log10(log2(M)) + 10*log10(code_rate); % 符号能量Es = 1,噪声功率 N0 = 10^(-EsN0_dB/10) N0 = 10^(-EsN0_dB/10); noise_var = N0 / 2; % 实部虚部各占一半 noise = sqrt(noise_var) * (randn(size(tx)) + 1j*randn(size(tx))); rx = tx + noise; end这里把编码码率也考虑进去了。因为LDPC编码后每传输一个符号对应不到log2(M)个信息比特,比特能量被摊薄,如果不在加噪时折算,LDPC引入的编码增益会被错误地高估或低估。注意噪声方差是N0/2拆给实部和虚部,这是复基带AWGN信道的标准建模方式。randn每次调用会重置随机数,所以仿真开始时一定要先rng(固定种子),否则同一次仿真跑两遍结果不同,没法调试。
2.3 软解调LLR计算:对数似然比才是LDPC译码的口粮
QPSK硬解调直接把接收符号判到最近的星座点,但LDPC的BP译码需要的是每个比特属于0或1的软信息,也就是LLR(对数似然比)。软解调的计算公式是基于高斯噪声假设下的后验概率比。QPSK因为是正交调制,I路和Q路独立,LLR可以分别算,不需要二维联合概率。
function llr = qpsk_soft_demod(rx, N0) % QPSK软解调,输出每个比特的对数似然比 % 利用QPSK正交性:I路对应第一个比特,Q路对应第二个比特 % LLR = (2/sqrt(2)) * real(rx) * 2 / N0 的推导见说明 rx = rx(:); % 第一个比特的LLR,I路 llr_i = 2 * sqrt(2) * real(rx) / N0; % 第二个比特的LLR,Q路 llr_q = 2 * sqrt(2) * imag(rx) / N0; llr = zeros(length(rx)*2, 1); llr(1:2:end) = llr_i; llr(2:2:end) = llr_q; end这个公式推导一句话:发射符号实部是±1/sqrt(2),接收实部y_I = x_I + n_I,在等概先验下LLR等于2*x_I*y_I/(N0/2),代入x_I = ±1/sqrt(2)得到2*sqrt(2)*y_I/N0。它的符号决定硬判,绝对值决定置信度。这个LLR直接喂给后面的BP译码器,绝对值太小说明信道质量差,译码器会倾向于不更新这个比特。实测中如果LLR算错,LDPC译码性能可能比不编码还差,这是最隐蔽的翻车点。
3. 接入LDPC编译码:稀疏校验矩阵、置信传播译码与编码增益验证
3.1 校验矩阵的两种获取方式:现成标准码字与随机构造
LDPC码的核心是稀疏校验矩阵H,行代表校验方程,列代表变量节点。H的构造决定了码的性能上限。常见做法有两条路:一是直接用MATLAB通信工具箱里的dvbs2dvbs2相关函数或ldpcQuasiCyclicMatrix生成标准码字,性能有保障但需要额外工具箱;二是自己随机构造,理解更深但容易踩短环的坑。我在课程设计场景里推荐随机构造加短环检测,因为能顺便搞懂原理。
function H = random_ldpc_H(n, j, k) % 随机构造规则LDPC校验矩阵 % n: 码长, j: 列重, k: 行重 % 构造方式:按列均匀分配1,再对行重做约束 if mod(n*j, k) ~= 0 error('n*j必须能被k整除'); end m = n*j / k; % 校验行数 H = zeros(m, n); % 逐列放置j个1,保证列重一致 for col = 1:n % 随机选择j个不重复的行位置 rows = randperm(m, j); H(rows, col) = 1; end % 行重约束:统计每行1的个数,超出k的行随机删减 row_weights = sum(H, 2); for r = 1:m if row_weights(r) > k % 找出该行所有1的列,随机删除多余 ones_cols = find(H(r, :)); del = ones_cols(randperm(length(ones_cols), row_weights(r)-k)); H(r, del) = 0; end end % 检查是否出现长度为4的短环(girth=4) % 短环会严重降低BP译码性能,必须检测 % 检测方法:任取两列,计算内积,若大于1则存在4环 for c1 = 1:n-1 for c2 = c1+1:n if sum(H(:, c1) .* H(:, c2)) > 1 warning('检测到4环,建议重新生成'); return; end end end end随机构造的风险在于可能生成不可逆的矩阵,导致无法直接求生成矩阵G。所以更稳妥的做法是采用结构化构造,比如QC-LDPC的循环移位构造,保证H的满秩性。上面这段代码故意保留了短环检测但只是警告,实战中我会把它改成拒绝生成并重新随机,直到没有4环为止。列重j一般取3到4,行重k由码率决定:码率R = 1 - j/k,想要1/2码率就配j=3, k=6。
3.2 编码矩阵生成:高斯消元与系统码形式的取舍
有了H矩阵,接下来要求生成矩阵G。对系统码,我们想要G = [I | P],这样编码后码字前段是原始信息位,后段是校验位。求G的标准做法是对H做高斯消元,把它变成 [P^T | I] 的形式。这个步骤在MATLAB里用gf对象处理模2运算最省事,但要注意列交换会改变码字结构,消元完之后要记录交换的列号,编码和解码都要用同一个约定。
function G = get_gen_matrix(H) % 由校验矩阵H求系统码生成矩阵G = [I | P] % 输入H为m*n,需要先通过列置换化为 [P' | I] 形式 [m, n] = size(H); % 使用gf对象进行模2高斯消元 H_gf = gf(H); % 目标是消成 [P' | I_m] 形式,前n-m列是P转置,后m列是单位阵 % 对H做初等行变换 H_aug = [H_gf, gf(eye(m))]; % 扩展矩阵记录行变换 % 高斯消元主过程(简化版,实际需处理列主元) for col = 1:m % 找第col行到m行中第col列非零的行 pivot = find(H_aug(col:end, col) == 1, 1); if isempty(pivot) error('矩阵不满秩,无法求生成矩阵'); end pivot = pivot + col - 1; if pivot ~= col H_aug([col pivot], :) = H_aug([pivot col], :); end % 消去其他行第col列的1 for r = 1:m if r ~= col && H_aug(r, col) == 1 H_aug(r, :) = H_aug(r, :) + H_aug(col, :); end end end % 此时H_aug = [P' | I],但列顺序是原H的顺序,需转换为系统形式 % 系统码要求G = [I | P],需要再对列做置换 % 这里返回一个简化结果:直接用双射变换后的P P = H_aug(:, 1:n-m); G = [gf(eye(n-m)), P.']; G = double(G.x); end这段代码是教学版,实际跑还会遇到列主元不足的问题,需要列置换介入。很多现成代码干脆绕开G矩阵,直接用H矩阵做编码——方法是把信息位放在系统位,然后用H的约束关系反解校验位。这样避免求逆,但编码复杂度从O(n^2)变成O(n*m)。对小尺寸仿真够用,对大码长建议直接用工具箱的ldpcEncoderConfig,内部有高效编码算法。
3.3 BP译码实现:对数域置信传播的三个核心公式
LDPC译码最常见的是对数域BP算法,也叫min-sum近似或SPA(和积算法)。BP译码在变量节点和校验节点之间迭代传递消息,消息是LLR。对数域的SPA包括校验节点更新、变量节点更新和硬判决三步。min-sum近似把校验节点更新里的tanh运算换成取最小值和符号相乘,性能损失很小但实现简单很多。
function decoded_bits = bp_decode(llr_in, H, max_iter) % 对数域BP译码器,min-sum近似 % llr_in: 信道LLR输入 % H: 稀疏校验矩阵, m*n % max_iter: 最大迭代次数 [m, n] = size(H); % 初始化:变量节点消息 = 信道LLR v_msg = llr_in(:); % 校验节点消息矩阵,稀疏存储 % 为简化,用全矩阵存储,仅用于教学 u_msg = zeros(m, n); % 记录校验节点连接的变量节点索引 [row_idx, col_idx] = find(H); % 迭代主循环 for iter = 1:max_iter % 校验节点更新:对每个校验节点 for r = 1:m connected = find(H(r, :)); if isempty(connected), continue; end for c = connected % 收集除c外所有变量节点的符号和最小绝对值 others = connected(connected ~= c); signs = sign(v_msg(others)); abs_vals = abs(v_msg(others)); % min-sum近似:消息 = 符号积 * 最小绝对值 u_msg(r, c) = prod(signs) * min(abs_vals); end end % 变量节点更新:信道LLR加上所有校验节点消息 old_v = v_msg; for c = 1:n connected = find(H(:, c)); if isempty(connected), continue; end total = llr_in(c) + sum(u_msg(connected, c)); for r = connected v_msg(c) = total - u_msg(r, c); % 外信息 end end % 硬判决 hard = double(v_msg < 0); % LLR < 0 判为1 % 校验是否满足所有校验方程 if mod(H * hard, 2) == zeros(m, 1) decoded_bits = hard; return; end end % 达到最大迭代仍未收敛,返回当前硬判决 decoded_bits = double(v_msg < 0); end变量节点更新的关键是“外信息”原则:给某个校验节点回传消息时,必须扣除该校验节点刚发给自己的消息,否则信息会自带正反馈,造成误收敛。硬判决条件是所有校验方程同时满足,这个叫“停止准则”。我实测过,对短码,min-sum和完整SPA的性能差在0.2dB以内,但min-sum的迭代速度几乎快一倍,仿真规模大时建议用min-sum。注意这段代码是O(m*n)全矩阵实现,仅适合学习验证,码长超过2000后就该改成稀疏矩阵运算。
4. 误码率统计的完整流程:蒙特卡洛仿真、Eb/N0换算与停止条件设置
4.1 完整链路搭建:从比特生成到误码统计的函数级框架
链路仿真最忌讳把所有代码堆在一个脚本里,我习惯按模块拆函数,用主脚本串联。主脚本负责设置参数、开循环、存结果,子函数管调制、加噪、译码。下面这个框架可以直接抄,业务函数用前面章节的实现填空。
%% 主仿真脚本:QPSK+LDPC链路误码率 % 参数区 clear; clc; rng(42); % 固定随机种子,保证可复现 EbN0_dB_list = 0:0.5:4; % 比特信噪比扫描区间 n = 1024; % LDPC码长 j = 3; k = 6; % 列重3,行重6,码率0.5 max_iter = 30; % BP译码最大迭代次数 num_trials = 200; % 每个信噪比点的帧数,越多曲线越平滑 % 生成校验矩阵和生成矩阵 H = random_ldpc_H(n, j, k); G = get_gen_matrix(H); info_len = n - size(H, 1); % 信息位长度 % 结果存储 ber_results = zeros(size(EbN0_dB_list)); for idx = 1:length(EbN0_dB_list) EbN0_dB = EbN0_dB_list(idx); bit_errors = 0; total_bits = 0; for trial = 1:num_trials % 生成随机信息比特 info_bits = randi([0 1], info_len, 1); % LDPC编码 code_bits = mod(info_bits.' * G, 2).'; % QPSK调制 tx_sym = qpsk_mod(code_bits); % AWGN信道加噪 rx_sym = awgn_channel(tx_sym, EbN0_dB, info_len/n, 4); % 计算噪声方差并软解调 EsN0_dB = EbN0_dB + 10*log10(log2(4)) + 10*log10(info_len/n); N0 = 10^(-EsN0_dB/10); llr = qpsk_soft_demod(rx_sym, N0); % BP译码 dec_bits = bp_decode(llr, H, max_iter); % 误码统计:只统计信息位 bit_errors = bit_errors + sum(dec_bits(1:info_len) ~= info_bits); total_bits = total_bits + info_len; end ber_results(idx) = bit_errors / total_bits; fprintf('Eb/N0 = %.1f dB, BER = %.2e\n', EbN0_dB, ber_results(idx)); end semilogy(EbN0_dB_list, ber_results, 'b-o'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER');主脚本有几个关键约定:第一,随机种子必须在最前面固定,否则换一次信噪比点结果就变一次,曲线可能出现锯齿状抖动,那不是信道特性而是随机性没控制住;第二,误码统计只统计信息位,不要把校验位算进去,很多人直接把译码输出和解调输出比,把校验位错误也算进去,BER被系统性抬高;第三,每一帧的编码、调制、加噪过程相互独立,同一信道参数下每帧的噪声样本都不同,这是蒙特卡洛仿真的基础。
4.2 Eb/N0与Es/N0换算:为什么编码率必须参与计算
误码率曲线的横轴是Eb/N0,但信道加噪实际使用的是符号信噪比。两者换算关系是Es/N0 = Eb/N0 + 10*log10(log2(M)) + 10*log10(code_rate)。编码率参与换算的原因很直接:LDPC编码后,每传一个符号只承载code_rate * log2(M)个信息比特,若要比较编码和不编码的性能,必须在相同信息比特能量下加噪。我在前面做加噪函数时就把code_rate作为参数传入,目的就是逼着你在主循环里显式换算。很多现成代码直接在awgn函数里填EbN0,然后对编码和未编码都用同一横轴画BER,曲线会显示编码后性能变差——这就是横轴没对齐造成的假象。
要注意的是,当code_rate = 1(即无编码)时,这个换算退化为普通的QPSK Es/N0关系。所以调试时可以先跑一条无编码QPSK理论曲线做基准:BER = Q(sqrt(2*Eb/N0))。如果你的LDPC链路在低信噪比段还不如这个理论曲线,大概率是译码器或LLR计算有bug,而不是编码没生效。
4.3 停止条件与帧数控制:仿真时长和精度怎么平衡
误码率仿真最耗时的部分是低信噪比区域。高信噪比下误码率极低,可能出现跑几百帧仍然零误码的情况,这时BER算出来是0,在log坐标下画不出来。反过来,如果每个信噪比点固定帧数,高信噪比点可能只统计到几十个比特错误,置信度很低。我常用的做法是双阈值停止:每个点要么跑满最大帧数,要么累计到最小错误比特数(比如100个),先到先停。
min_errors = 100; % 最小错误比特数 max_frames = 500; % 最大帧数 for trial = 1:max_frames % ... 链路处理 ... bit_errors = bit_errors + ... total_bits = total_bits + info_len; if bit_errors >= min_errors break; % 达到足够统计量,提前结束 end end ber_results(idx) = bit_errors / total_bits;这种停止条件的逻辑是:BER估计值的相对误差大约等于1/sqrt(N_errors),要相对误差小于10%,至少需要100个错误比特。我在高信噪比点经常遇到跑满500帧仍只有20个错误的情况,这时曲线点会抖动,但不影响整体趋势判断。如果你的仿真目标是精确读出某个特定BER值(比如BER=1e-5对应的信噪比),那就要把min_errors提到1000以上,代价是仿真时间成倍增加。另外,如果某点BER长时间降不下去,出现平台期,那可能是译码迭代次数不够,而不是信道太差——这是后面要讲的误码平层问题。
5. LDPC参数调试避坑:误码平层、短码退化和迭代上限的三类翻车现场
5.1 误码平层:曲线到了10^-4级别突然变平
现象:BER曲线在低信噪比区域随Eb/N0增加而稳定下降,但到大信噪比段下降速度突然放缓,甚至变成水平线。这个“平层”通常在BER = 1e-4到1e-6之间出现,取决于码字设计和译码算法。
原因:LDPC码的码字中存在一些“近邻码字”,即与正确码字汉明距离很小的其他码字。当噪声把接收信号推向近邻码字时,BP译码器会错误收敛到这个近邻码字,而且由于它们距离太近,即使继续加大信噪比也改变不了这个错误事件。随机构造的小码长码字特别容易出平层,因为随机码本质上无结构,无法保证最小距离足够大。
解决:换用有结构的校验矩阵,比如QC-LDPC或原模图LDPC,它们的设计目标之一就是抬高低信噪比区的平层。如果只是做课程设计,可以在结论里承认平层存在并分析原因,反而比假装曲线完美更可信。实际项目中,平层不足可以通过级联BCH外码来纠掉,但这是另一个工程课题。
5.2 短码性能退化:码长256和1024差出2dB
现象:把码长从1024改成256后,同样码率下要达到BER=1e-4,所需的Eb/N0比长码高2dB以上。有人会怀疑是自己的译码器写错了。
原因:LDPC码的性能由码长和列重共同决定。码长越长,校验矩阵对应的因子图中环越长,BP译码的“近似最优”效果越好。短码的因子图里不可避免地存在短环,min-sum算法在这些环上反复传播消息会引入相关性,相当于译码器在做次优推断。这是信道编码的理论边界,不是代码bug。
解决:如果必须用短码,把迭代次数加大到50甚至100,能部分补偿短环带来的损失。另一个有效手段是采用非规则LDPC——变量节点的列重不是均匀的,让少数变量节点有高列重(比如5到6),它们能更快收敛。但非规则码的编码复杂度更高,实操中我一般先上规则码,性能不够再考虑非规则。这里有个血泪经验:不要指望增大迭代次数能把短码性能拉齐长码,能拉回1dB算不错,再往上就是结构问题。
5.3 迭代上限设置:30次和100次的结果差异比你想的大
现象:同一套链路,max_iter从30改成100,BER曲线在中等信噪比段改善了将近1dB,但仿真时间涨了3倍。于是有人得出“迭代越多越好”的结论,直接把迭代拉满。
原因:BP译码在迭代初期收敛速度快,后期增速放缓。对大部分码字,20到30次迭代已经能收敛;但少数困难码字确实需要50次以上才能解出。这些困难码字在高信噪比下贡献了绝大部分错误,所以迭代上限直接影响高信噪比区域的BER。但不加限制地增加迭代会导致两个问题:一是译码延迟变高,实时系统受不了;二是超过某个阈值后,继续迭代会开始出现“振荡”——消息在环上来回传播不收敛,反而增加误判风险。
解决:一个折中做法是把迭代上限设成40或50,并加上“提前终止”机制——一旦所有校验方程满足就立即停止。我在前面的bp_decode里已经写了这个停止准则,实测大部分帧在10到15次内就终止了,只有少数困难帧吃满上限。这样平均迭代次数远小于上限,仿真时间只增加20%左右,性能却接近满迭代的水平。如果做实时仿真,还可以再加一个“连续N次迭代硬判决结果不变”的条件来提前退出。
6. 让误码率曲线可信:置信区间验证、断点续跑与交付规范
6.1 用理论曲线锚定仿真结果
拿到BER曲线后,第一步不是看LDPC性能多好,而是先验证无编码QPSK那条参考线是否和理论曲线吻合。QPSK在AWGN下的理论误码率是Q(sqrt(2*EbN0_linear)),MATLAB里用qfunc直接算。把仿真曲线和理论曲线画在同一张图上,偏差超过0.3dB就说明加噪或LLR计算有问题。这一步能过滤掉一大半低级错误,比如归一化系数错、噪声功率算错、Eb/N0换算漏了码率项。
我用过最笨但最有效的验证办法:把LDPC编码去掉,直接用随机比特做QPSK调制解调,BER应该和理论曲线重合。重合之后再逐步加LDPC编码、加译码器,每加一层验证一次。这个习惯帮我避免过“一次性写完整个链路,错了不知道错在哪”的处境。如果你发现编码后的曲线在低信噪比区域比未编码理论曲线还差,那不是编码没用,而是LLR或译码实现有bug。
6.2 大信噪比区间的断点续跑技巧
高信噪比点的单点仿真可能耗时几十分钟,万一中途死机或停电,从头跑代价太大。我的做法是把每个Eb/N0点的结果实时存入.mat文件,下次运行前先检查文件里已有哪些点,只补跑缺失的点。这个技巧在码长2048以上、帧数超500的仿真里几乎是必需品。
% 断点续跑示例 save_file = 'ber_results_cache.mat'; if exist(save_file, 'file') loaded = load(save_file); ber_results = loaded.ber_results; done_list = loaded.done_list; else ber_results = zeros(size(EbN0_dB_list)); done_list = false(size(EbN0_dB_list)); end for idx = 1:length(EbN0_dB_list) if done_list(idx) continue; % 这个点已跑完,跳过 end % ... 跑这个信噪比点 ... ber_results(idx) = bit_errors / total_bits; done_list(idx) = true; % 每个点跑完立即存盘 save(save_file, 'ber_results', 'done_list'); end把done_list和结果一起存是关键——否则程序重启后不知道哪些点没跑完。我自己吃过一次亏:跑了一整夜的仿真因为没做断点保存,早上发现某帧的随机数生成器状态没同步,所有点的结果其实都是同一组噪声样本做的,整条曲线作废。从那以后我不仅存结果,还存每个点对应的随机数种子,保证续跑时能精确复现之前的状态。
6.3 交付一份别人能复现的仿真工程
最后说说交付规范。你提到的这个标题含程序、中文注释、操作视频,实际交作业或给同事用的时候,工程组织决定别人能不能快速上手。我的习惯是分四个目录:src放所有函数和主脚本,results放生成的图片和.mat数据,doc放一份简短的README说明参数含义和运行顺序,fig放论文用的插图。主脚本必须是唯一的入口,一键运行到底,内部不要有需要手动修改的断点。
中文注释的密度也有讲究:关键公式处必须注释推导来源,参数设置处必须说明取值范围和影响,每个函数头部写清楚输入输出格式。但不要每一行都注释,那会让代码淹没在文字里反而难读。操作视频如果自己录制,按“跑通主脚本→看曲线→改参数→重新跑”四步来,时长控制在十分钟内。参数修改的部分尤其要演示清楚——读者真正想学的不是你跑了一遍默认参数,而是知道哪个参数改了之后,曲线会发生什么变化。
做了这么多轮链路仿真,我最大的一个教训是:宁可先花一个小时做理论曲线验证,也不要省这个时间直接跑LDPC全链路。后者一旦出错,排查的时间是前者的十倍不止。现在的习惯是每次都从无编码QPSK起步,确认基线没问题了再叠加编码,这也成了我带新人时的第一课。希望这套从QPSK软解调到LDPC译码、再到可信误码率统计的路径,能帮你在自己的仿真里少踩几个坑。
本文还有配套的精品资源,点击获取