news 2026/11/3 13:24:39

卷积码编译码与Viterbi译码的MATLAB误码率仿真

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
卷积码编译码与Viterbi译码的MATLAB误码率仿真

简介:围绕卷积码编译码算法与MATLAB性能仿真展开的毕业论文文档,面向学习数字通信、信道编码及维特比译码的学生与工程人员,可帮助梳理卷积码编码原理、译码流程及误码率分析思路。压缩包仅含1个doc文件,大小约991KB,内容以理论阐述、算法说明和仿真结果为主,适合按章节查阅或作为课程设计、毕业设计参考。已有349人学习下载,说明其在相关主题下具有一定参考价值。文档重点比较不同码率、约束长度、回溯深度以及硬判决、软判决对Viterbi译码性能的影响,并给出MATLAB仿真结论;读者可据此理解参数选择与纠错能力、计算复杂度之间的关系,获取实验设计、结果分析和结论提炼的参考。

1. 从一条误码率曲线说起:这套卷积码编译码方案要解决什么

答辩现场最容易被问住的一句话是:你这条误码率曲线,凭什么说明是 Viterbi 译码带来的增益,而不是 Eb/N0 恰好给够了。这个问题没法靠口头解释糊过去,只能靠一套能复现的实验设计回答——同一信源、同一信道、同一帧长,只换译码方式和参数,曲线自己会说话。这套卷积码编译码算法方案要落地的就是四件事:把 (n,k,N) 编码器建成移位寄存器模型,把网格图枚举成可查表的状态转移结构,把 Viterbi 的最大似然搜索写成加比选加回溯的循环,最后用 MATLAB 仿真扫出误码率随信噪比变化的完整曲线。它适合通信、信息工程方向做毕业设计或课程设计的人,也适合需要一份能直接改参数复用的链路级仿真脚本的从业者。码率、约束长度、回溯深度、硬软判决这四组变量必须各自给出结论,否则整套仿真就只是一堆图。

2. 参数选型:码率、约束长度与生成多项式怎么定

卷积码方案的第一步不是写代码,而是把三个参数钉死,因为它们直接决定了后面编码器的寄存器级数、网格图的状态数、译码器每步的比较次数。很多人上来就抄一段poly2trellis(7,[171 133])跑通出图,参数怎么来的说不清楚,一旦导师让换成 2/3 码率或者缩短约束长度,整份代码就得推倒重来。我一般的做法是先把这三个参数的物理含义和代价算清楚,再决定取哪一组,这样即使中途改参数,改的也只是配置文件里的一行。

2.1 码率 R=k/n:先算带宽账,再谈纠错能力

码率是编码后码元速率与原始信息速率的比值,k 个信息比特编成 n 个码元,R=k/n。它是整个方案里最先要妥协的量:R 越小,冗余越多,纠错能力越强,但同一份信息占用的带宽和发射时间成比例增加。1/2 码率的卷积码在 GSM、CDMA2000 这类标准里被大量采用,原因就在于这些系统本身有带宽余量,愿意拿出接近一半的带宽换可靠性;而对带宽敏感的场合,会改用 2/3、3/4、5/6、7/8 的删余卷积码,从 1/2 母码里周期性删掉一部分输出比特,用编码效率换误码性能。

做方案时我通常把 1/2 作为基准码率,因为它最干净:每输入一位,输出两位,网格图上每个状态恰好两条出边,加比选循环不需要处理变长输出,调试阶段能少掉一大类时序错位的问题。删余码则必须额外实现一张删余图案表,收发两端严格同步,一旦图案错位,误码率会直接跳到 0.5 附近,看起来像信道坏了,其实是图案对不上。

码率每输入比特输出数相对带宽开销典型适用场景
1/222.0 倍基准方案、低信噪比链路
2/31.51.5 倍带宽受限、信道质量较好
3/41.331.33 倍高速数据传输
7/81.141.14 倍信道条件优良、追求吞吐

提示:删余码不要一开始就上,先在 1/2 母码上把编码器和译码器的状态机跑对,再套删余图案表,否则一旦出错,你分不清是 ACS 逻辑写错了还是图案同步错了。

2.2 约束长度 N:状态数按 2^(N-1) 指数涨

约束长度 N 表示编码输出与当前及前 N-1 个输入段相关,编码过程中相互关联的码元数为 N·n。它决定了编码器的记忆深度,也决定了译码器的搜索规模:网格图的状态数是 2^(N-1),Viterbi 每一步要对每个状态的两个候选分支做比较,单步计算量随 N 指数增长。N 从 3 加到 7,状态数从 4 涨到 64,单步比较次数翻了 16 倍,但误码率曲线的斜率会明显变陡,这就是编码增益的来源。

这个取舍必须写进方案里。N 太小时纠错能力有限,曲线在高信噪比段下降很慢;N 太大时计算开销涨得离谱,而且受回溯长度限制,实际增益很快就饱和,再往上加只是徒增复杂度。工程上 3 到 9 是常用区间,(2,1,7) 是经典配置,64 个状态用软件实现毫无压力,硬件实现也只需要几百个门。我一般把 N 作为对比实验的核心变量,跑 N=3、5、7、9 四组,画在同一张误码率图上,让曲线自己证明「约束长度增加带来的增益在哪个区间开始收敛」,这比文字推导有说服力得多。

要注意的是,约束长度并不等于纠错位数。卷积码在连续 N 段码流内可以纠正的随机错误数大致与最小自由距离相关,不能简单套用分组码的 (dmin-1)/2 公式。方案里如果照抄分组码那套说法,很容易被追问住。

2.3 生成多项式:为什么要用八进制写抽头

生成多项式描述编码器每个输出端对移位寄存器各抽头的连接关系,系数为 1 表示该级参与模 2 加,为 0 表示不参与。习惯上用八进制书写,因为约束长度 N 位的抽头向量正好能用 ⌈N/3⌉ 位八进制数紧凑表示。(2,1,7) 的经典生成多项式是 (171, 133)₈,拆成二进制分别是 1111001 和 1011011,两个向量长度都是 7,对应 7 级抽头、6 级寄存器。

选生成多项式不是随便挑两个奇数就行。衡量一组多项式好坏的核心指标是自由距离 d_free,即在网格图上从全零路径出发、回到全零路径的所有非零路径中,输出码重最小的那条路径的重量。d_free 越大,渐近编码增益越高。方案里至少应该给出所选多项式的 d_free 值,并说明它是在什么约束长度下通过搜索得到的,而不是直接抄一组数字了事。

为了让代码里的多项式表达不至于变成一堆魔法数字,我把抽头直接写成逻辑矩阵,每一行对应一个输出端,每一列对应一个寄存器抽头:

% (2,1,3) 卷积码,生成多项式 (7,5) 八进制 % 7 = 111, 5 = 101,两行分别对应两个输出端 G = [1 1 1; 1 0 1]; % (2,1,7) 卷积码,生成多项式 (171,133) 八进制 % 171 = 1111001, 133 = 1011011 G7 = [1 1 1 1 0 0 1; 1 0 1 1 0 1 1];

矩阵的行数就是 n,列数就是 N。这种写法最大的好处是可读:任何一个抽头改没改,一眼就能看出来,不像八进制字符串那样需要心算转换。后续编码器和译码器的网格图构建都直接吃这个矩阵,改约束长度只需要改矩阵列数,其余代码不用动。

注意:G 矩阵的列顺序要和移位寄存器的移位方向严格对应。约定第一列对应当前输入位、最后一列对应最老的一位,全篇代码统一,否则编码器和译码器的网格图会对不上,表现为译码输出全是垃圾数据但代码不报错。

3. 编码器与网格图建模:从多项式到状态转移表

编码器本身只有十几行代码,真正决定方案能不能跑通的是网格图建模——译码器不认识多项式,它只认识「当前状态 + 输入 → 下一状态 + 输出」这张表。这一步做错,后面 Viterbi 写得再漂亮也是白搭。我在方案里把这两部分拆成独立模块:编码模块只负责把信息比特流变成码元流,网格构建模块只负责把 G 矩阵变成状态转移表,两者之间的唯一契约就是状态的定义方式。

3.1 移位寄存器结构:把多项式翻译成硬件动作

(n,k,N) 卷积码编码器本质上是一个 k 路输入、n 路输出、m=N-1 级移位寄存器的有限状态有记忆系统。每来一个输入比特,寄存器整体右移一位,新比特进入最高位,最老的一位被挤出丢弃;n 个输出端各自把当前寄存器窗口与自己的抽头向量做模 2 加,得到 n 个输出码元。整个过程没有任何分支判断,纯粹是移位加异或,所以软件实现极快,硬件实现只需要触发器加异或门。

下面的实现把「当前输入位 + 历史状态」拼成一个长度为 N 的窗口,再用抽头矩阵一次性算出全部 n 个输出:

function [coded, stateOut] = conv_encode(info, G) % info : 输入信息比特行向量,元素为 0/1 % G : n x N 抽头矩阵,第 i 行是第 i 个输出端的生成多项式 % coded : 编码输出,长度为 length(info) * n % stateOut : 编码结束后的寄存器状态,用于校验收尾 [n, N] = size(G); m = N - 1; % 寄存器级数 state = zeros(1, m); % 初态全零,与译码端约定一致 coded = zeros(1, numel(info) * n); p = 1; for k = 1:numel(info) reg = [info(k), state]; % 长度 N 的滑动窗口 coded(p:p+n-1) = mod(G * reg.', 2).'; % 一次算出 n 个输出 p = p + n; state = reg(1:m); % 右移:保留最新的 m 位 end stateOut = state; end

关键点在于reg的拼装顺序。[info(k), state]把当前输入放在第一位,历史状态依次后延,这样G的第一列天然对应当前输入,最后一列对应最老的一位,和上一节的约定完全一致。G * reg.'得到的是 n×1 的列向量,转置后写进输出流。state = reg(1:m)这一步是移位动作的等价实现,取窗口前 m 位作为新的寄存器内容,等价于右移并把最老的一位挤出去。

参数方面,唯一需要外部决定的就是G和输入长度。G的列数决定了m,也决定了状态数 2^m;输入长度决定了输出长度。输出长度必须是 n 的整数倍,这一点在后续接信道和译码器时要反复校验,任何一个环节的长度算错,都会导致帧边界错位,误码率直接飙到 0.5。

3.2 状态转移表:2^(N-1) 行的枚举生成

译码器需要知道两件事:从状态 s 输入比特 b 会到哪个状态,以及这条分支会输出什么码元。这两件事在编码器定型之后就完全确定了,所以最省事的做法是开局枚举一遍,存成两张表,译码时直接查表,避免在 ACS 循环里反复做位运算。

状态数固定为 nState = 2^(N-1),每个状态两条出边(对应输入 0 和 1),表的规模就是 nState×2。下面的函数把 G 矩阵翻译成这两张表:

function [nextState, outBit] = build_trellis(G) % 输入 G : n x N 抽头矩阵 % 输出 nextState : nState x 2,nextState(s+1, b+1) 表示状态 s 输入 b 的后继 % 输出 outBit : nState x 2 x n,对应的 n 个输出码元 [n, N] = size(G); nState = 2^(N-1); nextState = zeros(nState, 2); outBit = zeros(nState, 2, n); for s = 0:nState-1 stateBits = de2bi(s, N-1); % 当前寄存器内容,低位在前 for b = 0:1 reg = [b, stateBits]; % 与编码器完全相同的窗口拼装 outBit(s+1, b+1, :) = mod(G * reg.', 2).'; nextState(s+1, b+1) = bi2de(reg(1:N-1)); end end end

这段代码的核心思想是「穷举 + 复用」:不推导闭合表达式,直接把所有状态和所有输入组合都跑一遍,用和编码器一模一样的窗口拼装方式算出结果。好处是绝对不会出现编码器和译码器约定不一致的问题,因为两边共享同一个reg = [b, stateBits]表达式。代价是一次性 2^(N-1)×2 次计算,N=7 时是 128 次,N=9 时是 512 次,相对于整帧译码的计算量完全可以忽略。

de2bi和bi2de的默认位序是低位在前,这一点必须和编码器的state = reg(1:m)对齐:最新的输入位放在状态的最低位。如果位序搞反,状态转移表内部的循环结构会被打乱,网格图上会出现大量不该存在的长环,译码器仍然能跑,但错误率明显高于理论值,而且极难定位——这类问题属于典型的「代码不报错、结果不对」。

3.3 网格图与自由距离:方案能纠多少错的底牌

有了状态转移表,网格图就是一张随时间展开的有向图:横轴是时刻,纵轴是状态,每条分支标注输入比特和输出码元。Viterbi 译码做的事情,就是在这张图上找一条与接收序列最匹配的路径。

方案里必须给出的一个数字是自由距离 d_free。它决定渐近编码增益的上限,也解释了为什么约束长度增加到一定程度后收益就饱和。计算方法是从全零状态出发,做一次广度优先搜索,记录每条非零路径的输出码重,找到第一条返回全零状态的路径对应的重量。N=3 的 (7,5) 码 d_free=5,N=7 的 (171,133) 码 d_free=10,这两组数字可以直接用来验证网格图构建得对不对:如果枚举出来的 d_free 和文献值对不上,说明状态转移表本身就有问题,不用往下查了。

渐近编码增益可以用 d_free 和码率估算,硬判决下的近似式为 10·log10(R·d_free/2) dB。1/2 码率、d_free=10 的情况下大约是 4 dB 量级。这个数只能作为趋势参考,低信噪比段的实际增益会低于它,方案里如果把渐近值当成全信噪比范围的结论,曲线一画出来就会自相矛盾。

提示:把 d_free 校验写成单元测试,改任何参数后先跑一遍这个测试,比直接跑完整误码率仿真快几个数量级。

4. Viterbi 译码实现:路径度量、加比选与回溯

译码是整套方案里代码量最大、也最容易出问题的部分。Viterbi 算法的思路本身很清晰:利用网格图逐步计算路径度量,每个状态只保留度量最小的那条幸存路径,最后从最优终态沿幸存路径回溯,得到最大似然译码输出。真正上手写才发现,坑几乎全在细节上——度量怎么定义、幸存路径怎么存、回溯从哪个状态开始、帧尾怎么收。

4.1 分支度量:硬判决数汉明距,软判决算欧氏距

分支度量衡量的是「这条分支的输出码元」和「实际收到的码元」有多接近。硬判决译码器收到的已经是 0/1 判决结果,直接用汉明距离,也就是两个码元序列对应位不同的个数;软判决译码器收到的是解调后的多电平量化值,保留幅度信息,用欧氏距离的平方衡量。

两者的差别不是精度问题,而是信息量问题。硬判决在解调时就把每个码元压缩成一个比特,丢掉了「这个比特有多可信」的信息;软判决把这份可信度带进译码器,在低信噪比段能多换出 1 到 2 dB 的增益。方案里把这组对比做出来,曲线的间距就是软判决价值的直接证据。

% 分支度量计算,sym 为当前时刻收到的 n 个码元 if strcmp(mode, 'hard') bm = sum(xor(refBits, sym)); % 汉明距离,整数 0..n else bm = sum((2*refBits - 1 - sym).^2); % 欧氏距离平方 end

软判决那行里,2*refBits - 1把参考码元从 {0,1} 映射到 {-1,+1},再和接收值相减取平方。接收值sym必须是和这个映射同一量纲的实数,常见的做法是归一化到 ±1 附近,或者用 3 bit 量化后的整数再乘一个比例因子。量纲不统一是软判决里最常见的错误:参考码元在 ±1,接收值却是 0 到 7 的量化整数,算出来的欧氏距离全部偏大,ACS 的比较结果实际上退化成了随机选择,误码率曲线看起来「软判决反而更差」。

4.2 加比选循环:一次遍历完成 2^nState 次比较

加比选是 Viterbi 的核心循环。每个时刻,对每个状态的两条入边分别计算「前一状态路径度量 + 分支度量」,取小的那条作为该状态的新度量,同时记录下这条胜出分支来自哪个前驱状态。这一步叫「加-比-选」:先加,再比,最后选。

nStep = floor(numel(rx) / n); pm = inf(nState, 1); pm(1) = 0; % 全零状态起步,度量归零 surv = zeros(nState, nStep); % surv(s,t) 记录 t 时刻状态 s 的前驱 for t = 1:nStep sym = rx((t-1)*n+1 : t*n); newPm = inf(nState, 1); preState = zeros(nState, 1); for s = 1:nState if isinf(pm(s)), continue; end % 不可达状态跳过 for b = 0:1 s2 = nextState(s, b+1); refBits = squeeze(outBit(s, b+1, :)).'; if strcmp(mode, 'hard') bm = sum(xor(refBits, sym)); else bm = sum((2*refBits - 1 - sym).^2); end cand = pm(s) + bm; % 加 if cand < newPm(s2+1) % 比 newPm(s2+1) = cand; % 选 preState(s2+1) = s - 1; end end end pm = newPm; surv(:, t) = preState; end

外层遍历时刻,内层遍历状态,每个状态展开两条分支,总比较次数是 nStep×nState×2。N=7 时 nState=64,一帧 10000 个信息比特就是 128 万次比较,MATLAB 里跑几秒,属于可接受范围。如果嫌慢,可以把内层状态循环向量化,但第一版不要这么做——向量化之后一旦结果不对,你根本不知道是算法错了还是索引对错了。

isinf(pm(s))这个跳过判断不能省。虽然理论上所有状态在若干步之后都可达,但在帧起始的强约束下(全零初态),前几步大量状态确实不可达,跳过能省掉不少无效计算,也避免 inf 参与加法产生 NaN。

4.3 回溯深度:5N 这条经验线的由来

上面这段代码是帧末统一回溯的写法,需要把整帧每个时刻的前驱状态都存下来,内存占用是 nState×nStep。工程上更常用的是滑动窗口回溯:只保留最近 tbDepth 个时刻的前驱,每收满一个窗口就回溯输出最老的一位信息比特。tbDepth 就是回溯深度。

回溯深度的作用是让幸存路径「合并」。Viterbi 的结论是:两条不同的幸存路径在回溯足够多步之后必然合并到同一状态,此后它们的前缀完全相同,所以只要回溯深度足够,输出的早期比特就是可靠的,不需要等到帧末。tbDepth 一般取 5N 左右,N=7 时取 35 到 40,此时回溯深度不足带来的性能损失已经小到可以忽略。

这一条必须用实验验证,不能只引用经验值。做法是固定其他所有参数,只扫 tbDepth = 5、10、20、35、50、100,画一组误码率曲线。典型结果是:tbDepth 从 5 加到 20 时曲线明显下移,从 20 到 35 时改善变小,超过 35 之后几条曲线基本重叠。把这组图放进方案,回溯深度的取值就有了依据。

帧末统一回溯的收尾相对简单,从度量最小的状态开始往回走,每一步读取surv(s,t)得到前驱状态,当前状态的最低位就是该时刻的输入比特:

[~, s] = min(pm); % 最优终态 decBits = zeros(1, nStep); for t = nStep:-1:1 decBits(t) = bitget(s-1, 1); % 当前状态的最低位即输入比特 s = surv(s, t) + 1; % 跳到前驱状态 end

bitget(s-1,1)取的是状态的最低位,这依赖第 3.2 节的状态定义:最新输入位放在最低位。两处必须一致,否则回溯出来的比特会整体错位,译码结果看起来像被循环移位过。判断是不是这个问题很简单,把译码输出和原始信息序列做一次互相关,如果峰值出现在滞后若干位的位置而不是零位,基本就是位序弄反了。

4.4 尾比特收尾与帧结构约定

卷积码有记忆,一帧结束时寄存器里还残留着 N-1 位历史信息,如果不处理,最后几个比特的译码可靠性会明显低于前面。标准做法是在信息比特后面追加 N-1 个零比特作为尾比特,把编码器强制拉回全零状态,译码端就可以从固定的全零状态开始逆向回溯,收尾变成确定性的。

代价是每帧多传 N-1 位冗余。N=7 时每帧多 6 位,帧长 10000 位的情况下开销可以忽略;帧长只有几十位时这个开销就不能不算了。方案里如果做短帧实验,要么增大帧长,要么用咬尾卷积码(tail-biting)避免尾比特开销,但咬尾的译码需要先估计环形初始状态,复杂度会上去一截,不建议在毕设阶段引入。

帧结构上,我在方案里固定约定:每帧 = 信息比特 + (N-1) 个零尾比特,编码后长度为 (infoLen + N - 1) × n。信道模块按这个长度加噪,译码模块按同样长度接收,误码统计只统计前 infoLen 位。这三处长度口径必须完全统一,只要有一处把尾比特算进误码统计,误码率就会被系统性低估,而且低信噪比时看不出来,高信噪比时误差占比很大。

5. 仿真对比与常见问题排查:四个变量怎么扫、翻车点在哪

仿真部分看着最像体力活,实际上是整个方案里最考验设计的地方。同样跑出一堆曲线,有的人能从中读出四个参数各自的边际收益,有的人只能得到一张花花绿绿的图然后被问「所以呢」。差别在于实验设计是否做到了控制变量,以及有没有在代码层面把每一次实验的随机种子固定住。

5.1 仿真主循环:从随机信源到误码计数

主循环的骨架很短:生成随机比特、编码、调制映射、加高斯白噪声、解调、译码、比对误码。真正要设计的部分是循环结构的组织方式——信噪比扫点、误码计数停止条件、随机种子的管理。

EbN0dB = 0:1:8; % 信噪比扫描点 frameLen = 10000; % 每帧信息比特数 targetErr = 200; % 每个信噪比点至少积累的误码数 maxFrame = 5000; % 帧数上限,防止低信噪比点死循环 ber = zeros(size(EbN0dB)); for i = 1:numel(EbN0dB) rng(20240601); % 固定种子,保证各点可比 errCnt = 0; bitCnt = 0; f = 0; while errCnt < targetErr && f < maxFrame f = f + 1; info = randi([0 1], 1, frameLen); tail = zeros(1, N-1); [coded, ~] = conv_encode([info, tail], G); % 调制:BPSK,0 -> -1,1 -> +1 tx = 2*coded - 1; % 噪声功率按 Eb/N0 换算,注意码率已在 Eb 的定义里扣除 snr = 10^(EbN0dB(i)/10) * R; rx = awgn(tx, 10*log10(snr), 'measured'); decBits = viterbi_decode(2*(rx>0), G, tbDepth, 'hard'); errCnt = errCnt + sum(decBits(1:frameLen) ~= info); bitCnt = bitCnt + frameLen; end ber(i) = errCnt / bitCnt; end

这段代码里有三个地方值得单独说。第一,rng放在信噪比循环内部而不是外部,保证每个信噪比点用的是同一批随机数据,曲线之间的差异才纯粹来自信噪比,而不是数据本身的随机波动。第二,targetErr和maxFrame共同构成停止条件,低信噪比点靠误码数停、高信噪比点靠帧数停,避免在误码率 1e-5 量级上跑一整天。第三,snr的换算里乘了码率 R,因为 Eb/N0 定义的是每信息比特的能量,而awgn函数按符号信噪比工作,这个换算漏掉的话,所有曲线会整体平移,硬判决和软判决的相对关系看起来还是对的,绝对数值全错。

5.2 四组对比实验:码率、约束长度、回溯深度、判决方式

单个参数扫出来的曲线说明不了什么,四组对比实验组织起来,才能构成一份完整的结论。我在方案里固定一张基准配置表(R=1/2,N=7,tbDepth=35,硬判决),每次只改一个变量,其余保持不变。

实验组变量取值观测目标
第一组码率 R1/2、2/3、3/4冗余换增益的边际收益
第二组约束长度 N3、5、7、9状态数增加带来的增益何时收敛
第三组回溯深度 tbDepth5、10、20、35、50经验值 5N 的实际依据
第四组判决方式硬判决、软判决量化信息带来的增益量级

第一组的预期结论是:码率越低,同一信噪比下误码率越低,但曲线之间的间距不均匀,低信噪比段间距大、高信噪比段间距反而被压缩,原因是高信噪比时低码率的冗余变成了纯开销。第二组的预期结论是:N 从 3 加到 5 提升最明显,5 到 7 仍有明显改善,7 到 9 改善幅度明显收窄,说明在这个码率下,继续加大约束长度性价比不高。第三组按 4.3 节说的方法读,找到曲线开始重叠的拐点。第四组的预期结论是软判决在低信噪比段比硬判决低 1 到 2 dB。

这四组结论不需要都做得很漂亮,有一两组趋势不明显也正常,关键是每条曲线都要有对应的解释。如果某组实验结果和理论预期完全相反,大概率是代码有 bug,而不是发现了新规律。

5.3 六个最容易翻车的点

现象一:译码输出和原始信息完全不相关,误码率稳定在 0.5 附近。原因通常是帧边界错位。编码器输出长度是 (infoLen+N-1)×n,译码器如果按 infoLen×n 去切接收序列,从第一个码元开始就错位了。解决方式是在编码端和译码端各打一条长度日志,逐帧核对,确认两边对「一帧有多少个码元」的理解一致。

现象二:低信噪比段曲线正常,高信噪比段误码率突然不再下降,卡在某个平台值上。这种「错误平台」在卷积码里很典型,但也要先排除代码因素。检查两点:一是回溯深度是否太短,tbDepth 小于 3N 时,幸存路径没有充分合并,会形成人为的平台;二是信息比特和尾比特的边界是否算错,把尾比特算进误码统计会让平台值虚高。如果两者都排除,那才是码本身的自由距离特性导致的,属于正常现象。附带说明:如果前面加的是删余卷积码,先核对删余图案的同步,图案错位同样会制造出假的平台。

现象三:软判决的误码率反而高于硬判决。九成是量纲问题。参考码元用 ±1 表示,接收值却是 0 到 7 的量化整数,欧氏距离的量级完全失衡。统一映射关系,或者把量化后的接收值线性映射回 ±1 附近,问题立刻消失。

现象四:同一个信噪比点跑两次,结果差一个数量级。随机种子没固定,或者固定位置不对。种子应该在每个信噪比点重新设置,而不是全程只设一次;否则各点的数据样本量不同,统计涨落会掩盖真实差异。

现象五:约束长度加大后误码率不降反升。先查网格图的状态定义和编码器的移位方向是否一致。N 变大时状态位序若有偏差,编码端和译码端理解的「状态」根本不是同一个东西,N 越大偏差的影响越严重。用 3.3 节的 d_free 校验能快速定位这类问题。

现象六:仿真跑得极慢,一帧要几十秒。检查 ACS 循环里有没有在每个状态内重复调用函数、有没有把squeeze放在最内层。把输出码元表预先展开成 nState×2×n 的定长数组,在内层循环里只做数值索引,速度能差出十倍。

6. 验收与答辩:用三条曲线自证方案成立

一套卷积码编译码方案做完,判断它是否合格,不需要看代码有多少行,看三样东西就够了:一条能复现的基准误码率曲线、一组控制变量的对比曲线、一份能对上理论值的自由距离校验结果。

基准曲线是 R=1/2、N=7、tbDepth=35、硬判决这套配置下,误码率随 Eb/N0 从 0 dB 扫到 8 dB 的完整结果。验收时要检查两件事:曲线是否单调下降,以及 Eb/N0 在 4 到 5 dB 区间误码率是否落进 1e-3 到 1e-4 这个量级。如果曲线在某个信噪比之后走平,按 5.3 节的顺序排查。对比曲线按 5.2 节的四组实验组织,每组一张图,图例标清楚变量取值,横纵坐标单位写明,对数坐标轴不要漏。

自由距离校验是最容易被忽略但最能体现功底的一项。把 G 矩阵对应的 d_free 算出来,和文献值对照,N=3 的 (7,5) 码应该是 5,N=7 的 (171,133) 码应该是 10。这个数字对了,网格图构建基本就没问题;对不上,后面所有曲线都不用看了。

答辩环节我一般建议准备一张表,把「被问到的问题」和「对应的实验证据」一一列出来:

可能的提问用哪份材料回答
编码增益具体是多少 dB基准曲线与未编码理论的间距
为什么选 N=7 而不是 N=9第二组对比曲线的收敛拐点
回溯深度 35 是怎么定的第三组对比曲线的重叠位置
软判决值不值得做第四组对比曲线的间距
你的网格图建对了吗d_free 校验结果

回答的时候不要绕。问增益就给 dB 数,问参数依据就给对应曲线的拐点位置,问正确性就给 d_free 的比对。全部指向一份可以当场翻开的实验记录,比任何形容词都管用。

有一个我自己踩过的坑值得说:早期做这套仿真时,我把awgn的 SNR 参数直接当成 Eb/N0 传进去,跑出来的曲线形状很漂亮,四条对比曲线也符合预期,直到答辩前一周重新核对公式才发现所有横坐标整体偏了 3 dB,码率那一项没扣。形状对、趋势对、相对关系对,唯独绝对值全错——这种错误最难自查,因为所有直觉判断都会告诉你「看起来没问题」。从那以后我每次搭误码率仿真,第一件事都是拿一个未编码的 BPSK 理论曲线叠在图上做标定,只有理论线和仿真点重合了,才继续往下加编码和译码。这个习惯花不了十分钟,能省掉后面所有的返工。希望这套结构和这份检查清单,能帮你在自己的方案里少走一遍弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 13:23:05

社交平台广告投放风控机制与优化策略

1. 社交平台广告投放的常见风控机制解析 最近在帮几个电商客户优化海外社交平台的广告投放时&#xff0c;发现一个普遍现象&#xff1a;刚开的新账户没跑几天就被封了&#xff0c;老账户跑得好好的突然就被限制。这种情况在业内其实很常见&#xff0c;今天我就结合自己这些年操…

作者头像 李华
网站建设 2026/11/3 13:21:39

从Python脚本到生产级AI系统:多语言协同的AI工程化实践

1. 什么是“从零构建AI工程体系”——不是搭模型&#xff0c;而是建生产线“ai-engineering-from-scratch”这个标题乍看像教人写个Transformer&#xff0c;但实际踩过坑的人都知道&#xff1a;它根本不是在Jupyter里跑通一个MNIST就算完事。我带过6支AI落地团队&#xff0c;从…

作者头像 李华
网站建设 2026/11/3 13:21:30

数字货币跨市场套利策略与实现

1. 跨市场套利的基本原理与挑战 数字货币市场的套利机会主要来源于不同交易所之间存在的价格差异。这种差异的形成通常由以下几个因素导致&#xff1a; 市场流动性差异&#xff1a;主流交易所深度好、买卖价差小&#xff0c;小交易所可能出现较大价差 资金流动限制&#xff1…

作者头像 李华
网站建设 2026/11/3 13:21:30

蓝桥杯软件测试模拟赛解析与实战技巧

1. 赛题概览与核心考察点 第十七届蓝桥杯软件测试模拟赛1期延续了该赛事一贯的实战导向风格&#xff0c;题目设计紧密贴合当前企业级测试场景。整套试题包含功能测试、自动化测试、性能测试和安全测试四大模块&#xff0c;其中渗透测试环节首次引入OWASP Top 10最新漏洞模型&am…

作者头像 李华
网站建设 2026/11/3 13:21:29

MPC二自由度车辆轨迹跟踪与车道保持Simulink建模

简介&#xff1a;面向汽车工程专业学生、自动驾驶横向控制研发人员及控制系统工程师的MPC学习资料&#xff0c;聚焦二自由度车辆动力学建模、轨迹跟踪与车道保持的Simulink实现与优化。内容涵盖质心侧偏角、横摆角速度状态方程及线性化处理&#xff0c;预测时域、控制时域与Q/R…

作者头像 李华
网站建设 2026/11/3 13:20:42

PL/SQL Developer 导入 Excel 到 Oracle 全攻略:避坑与优化

Excel 表格导入 Oracle 数据库这件事&#xff0c;说大不大&#xff0c;说小也不小。但凡做过数据迁移、系统初始化、批量补录的人&#xff0c;几乎都绕不开这个场景&#xff1a;业务方甩过来一个几百上千行的 Excel&#xff0c;字段对得上、格式看着也正常&#xff0c;但真要落…

作者头像 李华