
简介卷积码及维特比译码研究毕业论文面向通信工程及相关专业学生、研究人员系统讲解纠错编码原理与仿真实现方法。全文从纠错码基本理论入手详细阐述卷积码编码器的记忆结构、转移关系再深入剖析维特比译码的初始化、路径度量更新、回溯等核心步骤并基于MATLAB平台对不同码率、约束长度、回溯深度以及硬/软判决方式下的译码性能进行了对比分析为实际通信系统中的参数选取和误码率优化提供了具体参考。资源为1个doc格式文档压缩包约991KB包含摘要、目录、正文、参考文献等完整结构既适合系统阅读也可直接作为相关课程设计或毕业设计的参考资料。目前已有346人学习是理解卷积码及维特比算法并开展仿真验证的实用材料。1. 卷积码至今没被淘汰从 GSM 到 CDMA2000 的约束编码主线卷积码和译码这套东西听起来像是通信原理课本里的老古董但翻开 GSM、CDMA2000、IS-95 的物理层规范你会发现卷积码至今仍是这些标准里咬合最紧的齿轮之一。语音数据在无线信道上被噪声打成一片之前先经过一次卷积编码接收端再用维特比译码把正确的比特路径找回来——这一收一放之间就是差错控制编码最经典的实战场景。这份资料是一份完整的卷积码编译码算法研究论文加 MATLAB 仿真资源包面向的是三类人正在做信道编码课程设计或毕业论文的通信专业学生、想快速把 Viterbi 译码从公式落到代码的入门工程师、以及需要一份可复现的误码率仿真基线来验证自研算法的从业者。它能帮你解决的核心问题很直接卷积码为什么能纠错、维特比译码在参数选择上到底怎么权衡、以及不同码率、约束长度、回溯长度和判决方式对误码率的真实影响有多大。2. 编码器与生成多项式从 (2,1,7) 结构看懂 n、k、N 三个参数2.1 移位寄存器与模2加编码器到底在做什么卷积码编码器的硬件结构非常简单核心就是移位寄存器、模 2 加法器和一个输出开关。以最经典的 (2,1,7) 卷积码为例每个时刻送入 1 个信息比特k1输出 2 个编码比特n2编码器的记忆深度为 6 级移位寄存器加上当前输入共 7 个比特参与当前输出计算所以约束长度 N7。这里的“卷积”不是卷积神经网络的卷积而是指输出序列是输入序列与生成多项式系数序列的离散卷积。一个编码周期内的计算过程是当前输入比特与移位寄存器中前 6 个比特分别被抽头取出送到两个不同的模 2 加法器。每个加法器的抽头连接方式不同就对应不同的生成多项式。输出开关轮流取两个加法器的结果串成一路 2 倍速率的编码比特流。由于寄存器的状态会保留到下一时刻所以编码输出不仅依赖当前输入还依赖前 6 个输入比特——这正是卷积码与分组码最本质的区别分组码每组的校验位只与本组信息有关而卷积码的每组输出是连续信息序列的加权叠加。理解这段结构时有个关键点约束长度 N 决定了一帧中相互关联的码元数量是 n×N 个。N 越大编码器能参考的历史信息越长纠错潜力越大但译码器的状态数也会按 2^(k×(N-1)) 指数膨胀。这就是为什么 (2,1,7) 结构的译码器状态数是 2^664 个而 (2,1,3) 只有 4 个状态——后者译码极快但纠错能力远不如前者。2.2 生成多项式的两种写法从八进制到 poly2trellis 的参数对应生成多项式是卷积码编码器的“灵魂”它的写法有两种等价形式。一种是抽头向量比如 g1(1111001)、g2(1011011)每一位表示移位寄存器级联的对应位置是否被抽头另一种是八进制简写把二进制的抽头向量从低位开始每 3 位合成一个八进制数。在 MATLAB 通信工具箱里poly2trellis 函数接收的就是这种八进制写法% 卷积码参数定义 constraintLen 7; % 约束长度 N7对应 6 级移位寄存器 当前输入 generatorPoly [171 133]; % 八进制生成多项式二进制分别为 1111001 和 1011011 trellis poly2trellis(constraintLen, generatorPoly); % 生成一段随机信息比特进行编码 data randi([0 1], 10000, 1); % 10000 个随机 0/1 比特 encoded convenc(data, trellis); % 卷积编码输出长度 10000 * 2 % 验证编码输出长度 disp(size(encoded)); % 期望输出 [20000 1]码率 1/2 输出比特翻倍这段代码里最需要留意的是 poly2trellis 第一个参数。它传入的不是移位寄存器级数 m而是约束长度 N等于 m1。生成多项式的二进制位宽也必须等于 N7171 转二进制是 1111001133 转二进制是 1011011都是 7 位一一对应移位寄存器的 7 个抽头位置。如果生成多项式位宽与约束长度不一致MATLAB 会直接报错这是一个非常容易翻车的地方。另外convenc 默认把输入按列处理data 必须是列向量否则编码输出顺序会和你心里预期的比特顺序错位。2.3 码率与约束长度的取舍先定性能还是先定复杂度码率 Rk/n 直接决定了编码的效率。R1/2 意味着每 1 个信息比特要发 2 个编码比特带宽开销翻倍R3/4 则只多出 1/3 的开销。但码率越高监督位越少纠错能力越弱。这是一个物理层面的矛盾冗余度是纠错能力的来源你把冗余抽走了性能必然下降。约束长度的取舍则更微妙。理论上 N 每增加 1自由距离近似增大误码率会指数下降但代价是维特比译码的网格状态数翻倍。实际工程中(2,1,7) 是一个黄金平衡点64 个状态的译码复杂度在现代 DSP 上完全可以实时跑性能又远好于 N3 或 N5 的短约束码。这也解释了为什么 GSM 和 CDMA2000 这类标准里大量使用约束长度 7 左右的卷积码作为语音信道的编码方案。我在实际仿真中体会到码率和约束长度是必须先于其他参数确定的两个“主参数”因为它们决定了整个系统的带宽预算和译码器硬件规模。回溯深度、判决方式都是在它们定下来之后才能调的“次参数”。如果一上来就纠结回溯长度选 35 还是 50而码率和约束长度还没想清楚后面所有对比实验都会失去参照系。3. Viterbi 译码原理路径度量、幸存路径与回溯长度怎么配合3.1 网格图与最大似然路径为什么维特比是全局最优卷积码编码器是一个有限状态机状态就是移位寄存器的内容。把状态随时间展开就形成了一张网格图trellis横轴是时间纵轴是 2^(k×(N-1)) 个状态每个时刻的状态转移都对应一组输出比特。维特比译码做的事情本质上是在这张网格图上找一条从起始状态到终止状态的路径使得这条路径对应的编码输出与接收序列的累积度量最小——这就是最大似然序列估计。译码器的核心操作是“加-比-选”Add-Compare-Select。每个时刻每个状态都会收到来自两条前置路径的度量值译码器把前置度量加上当前分支度量比较两条候选路径的累积度量只保留较小的那条作为该状态的幸存路径。关键性质在于对于给定状态丢弃较大度量路径不会影响全局最优性因为未来的分支度量只与当前状态有关与历史路径无关。这个动态规划剪枝操作把暴力搜索的指数复杂度降到了与时间成正比、与状态数成正比。为了把这一步讲透我写了一个最小可运行的简化版本import numpy as np def viterbi_update(prev_metrics, branch_metrics, next_metrics): 单步维特比更新的最小实现add-compare-select prev_metrics: 上个时刻各状态的累积度量 [2^(N-1)] branch_metrics: 当前时刻各状态转移的分支度量矩阵 next_metrics: 更新后的累积度量 survivors: 每个目标状态选中的前置状态编号 num_states len(prev_metrics) next_metrics np.full(num_states, np.inf) survivors np.zeros(num_states, dtypeint) for next_state in range(num_states): # 每个目标状态对应两个前置状态k1时 candidates [] for prev_state in range(num_states): if np.isfinite(prev_metrics[prev_state]): # 总度量 前置累积度量 当前分支度量 total prev_metrics[prev_state] branch_metrics[prev_state, next_state] candidates.append((total, prev_state)) # 选最小度量路径作为幸存路径 best_metric, best_prev min(candidates, keylambda x: x[0]) next_metrics[next_state] best_metric survivors[next_state] best_prev return next_metrics, survivors这个函数里最重要的一句话是prev_metrics[prev_state] branch_metrics[prev_state, next_state]它就是“加”。在硬判决场景下分支度量是汉明距离——接收比特与期望编码比特逐位比较不同则加 1在软判决场景下分支度量换成欧氏距离直接累加接收符号幅度与期望符号幅度的平方差。np.inf用于标记尚未到达的状态保证无效路径不会被误选。实际工程里不会用 Python 两层循环跑完整网格MATLAB 的 vitdec 内部也是基于 C 优化过的实现但这个简化版本用来理解原理和调试自己的算法足够了。3.2 硬判决与软判决量化位数与信道信息的使用方式判决方式决定了分支度量怎么算。硬判决把接收信号先限幅成 0/1再算汉明距离软判决则保留接收符号的多级量化信息直接用欧氏距离做度量。前者实现简单只需 1 比特量化但丢弃了接收信号中“这个比特有多大把握”的置信度信息后者通常量化成 3~4 比特输入保留的软信息能让译码器在靠近理论极限的信噪比下多获得约 2dB 的编码增益——这是卷积码仿真结论里最值得记住的一条经验规律。软判决的原理可以这样理解如果接收符号幅度是 0.1硬判决会把它判成 0但这一点都不自信如果幅度是 0.9同样判成 0 就非常笃定。硬判决把这两种情况完全等同对待软判决则让幅度离判决门限近的路径获得更大的度量惩罚从而让最可信的路径更容易胜出。维特比译码器接收到的软信息越丰富网格图上正确路径的度量优势就越明显。在 MATLAB 的 vitdec 函数里软判决输入必须量化成 0 到 2^nbits-1 的整数。nbits3 时输入范围是 0~70 表示最可信的比特 07 表示最可信的比特 1中间的 1~6 表示不同程度的不确定。这个量化映射方向如果搞反了译码性能会直接崩溃到比硬判决还差——这是后面避坑章要展开的经典翻车现场。3.3 回溯长度选多少traceback 与性能/延迟的三组数据回溯长度traceback depth决定了译码器在找到当前最优路径后要往回追溯多少级来确定最终输出。这里存在一个延迟和性能的权衡回溯长度太短路径还没收敛到全局最优前段译码错误会成串回溯长度太长译码延迟增大还需要额外的存储空间保存幸存路径历史。工程上最常用的经验值是约束长度的 5 倍对 (2,1,7) 卷积码回溯长度取 35某些对延迟敏感的系统会降到 15~20相似性能下以误码率少量增加换取实时性。我从仿真数据里看到的规律是回溯长度从 5 增加到 20误码率改善非常明显因为 5 对于 64 状态的网格来说还没走完状态融合周期从 20 增加到 35改善趋于平缓超过 50 之后误码率曲线基本不再变化只剩延迟在增加。所以调参时不必迷信大回溯先用 5×(N-1) 作为基准值再根据实际误码率和延迟预算来回调。对实时语音通信这类延迟敏感场景缩短回溯长度比降低码率划算得多。4. MATLAB 仿真从编码到误码率曲线的完整链路与四组参数对比4.1 完整仿真链路convenc、awgn、quantiz、vitdec 的参数对应拿到这份资源后我最建议你做的第一件事不是改参数而是把仿真链路完整跑通一遍。整个系统分成四个环节信源与编码、BPSK 调制与信道加噪、硬/软判决量化、维特比译码与误码统计。每一环节对应一个关键函数参数错一个位最后出来的误码率曲线就可能偏移好几 dB。下面的代码是一个完整的硬判决仿真主循环% 系统参数定义 constraintLen 7; % 约束长度 genPoly [171 133]; % 生成多项式八进制 trellis poly2trellis(constraintLen, genPoly); codeRate 1/2; % 码率 R k/n tbDepth 35; % 回溯长度取约束长度的 5 倍 frameLen 10000; % 每帧信息比特数 EbN0dB 0:0.5:5; % 信噪比扫描范围单位 dB ber zeros(size(EbN0dB)); % 存储各信噪比下的误码率 % 主仿真循环 for idx 1:length(EbN0dB) % 1. 生成随机信息比特并编码 data randi([0 1], frameLen, 1); encoded convenc(data, trellis); % 2. BPSK 调制0 - 11 - -1 tx 1 - 2 * encoded; % 3. 加性高斯白噪声信道信噪比换算见下面的避坑说明 snr EbN0dB(idx) 10*log10(codeRate); rx awgn(tx, snr, measured); % 4. 硬判决接收符号 0 判为 0否则判为 1 rxBits double(rx 0); % 5. 维特比译码硬判决模式 decoded vitdec(rxBits, trellis, tbDepth, hard, cont); % 6. 忽略译码延迟部分统计误码率 offset tbDepth * 2; % 回溯导致的输出延迟 [~, ber(idx)] biterr(data(1:end-offset), decoded(offset1:end)); end % 绘制误码率曲线 figure; semilogy(EbN0dB, ber, b-o); grid on; xlabel(Eb/N0 (dB)); ylabel(BER);这段代码里有几个参数对应关系必须说清楚。awgn 函数里的 snr 参数指的是符号信噪比而我们要扫描的是比特信噪比 Eb/N0。对 BPSK 调制且码率为 1/2 的系统每个符号承载 1 个编码比特而每个编码比特对应信息比特的 1/R 倍能量所以符号信噪比和比特信噪比的换算关系是snr EbN0dB 10*log10(codeRate)。如果你直接用 EbN0dB 作为 awgn 的 snr 参数BER 曲线会整体右移约 3dB看起来系统性能比真实差了整整一倍信噪比。另外 vitdec 使用 cont 模式时译码输出有固定延迟末尾和开头的比特需要对齐后才能算误码率截断方式见代码注释。4.2 码率与约束长度对比同 EbN0 下的 BER 差异码率对比的实验思路是保持约束长度和回溯深度不变只改生成多项式的码率结构。典型的对比组是约束长度同为 7 时码率 1/2、2/3、3/4 三套系统的 BER 曲线。实测趋势是在 BER10^-3 这个常用参考点上码率 1/2 比 2/3 大约好 0.6~0.8dB比 3/4 好 1.2~1.5dB。代价很直白码率 1/2 需要双倍的传输带宽码率 3/4 只需多花 33% 带宽。这里没有绝对最优只有带宽预算与性能指标的平衡。约束长度对比则要公平很多相同码率 1/2 下分别用 (2,1,3)、(2,1,5)、(2,1,7) 三套编码器生成多项式按最优距离谱选择。实测结果是约束长度每增加 2同信噪比下误码率大约改善一个数量级。但这句话有个隐蔽前提数据帧长度必须足够长。如果帧长只有几百比特长约束码的网格还没完全展开就被强制终止优势根本发挥不出来。我建议你跑对比时把几条 BER 曲线画在同一张图上横轴用 Eb/N0纵轴用对数坐标。不同码率或约束长度的曲线会呈现近似平行的衰减趋势但斜率略有不同——约束长度更大的码在高信噪比区间的曲线更陡这说明它的编码增益主要来自高信噪比区域而不是在低信噪比区间的“地板效应”区域。这个特点在做链路预算时非常有用。4.3 回溯深度与判决方式对比硬判决和软判决的实测差距回溯深度对比最简单固定其他参数不变分别设置 tbDepth5、20、35、50观察 BER 曲线变化。我实测的结果是tbDepth5 时误码率明显偏高曲线在低信噪比区域会出现一个平台tbDepth20 基本接近收敛性能tbDepth35 和 50 的曲线几乎重合。这个实验能直观说明一个道理——回溯长度不是越长越好超过某阈值之后纯属浪费存储和延迟。硬判决与软判决的对比是整套仿真里最有说服力的一张图。保持相同卷积码参数硬判决输入只取 1 比特量化软判决输入用 3 比特量化两者的 BER 曲线比较结果非常稳定在 BER10^-3 参考点上软判决比硬判决好约 2dB。这意味着在相同误码率要求下采用软判决的系统可以把发射功率降低约 2dB或者把传输距离拉长。对于一个实际通信系统2dB 的增益通常比增加半个 dB 的带宽更值钱。软判决的 MATLAB 实现比硬判决多一个量化步骤接收符号先经过 quantiz 或手动映射到 0~2^nbits-1 的整数范围再送入 vitdec。我一般用 3 比特量化nbits3起步这个档位性价比最高——再往上加比特增益增量变得非常有限。量化电平的门限用等间隔划分即可通常按接收符号幅度的 95% 覆盖范围来设定上下界。5. 仿真避坑信噪比换算、网格终止与软判决量化的四个常见问题5.1 误码率曲线整体右移EbN0 与 awgn 的 SNR 参数没对齐现象跑出来的 BER 曲线和资料里给出的参考曲线形状完全一致但整体向右偏移约 2~3dB。无论怎么调回溯深度或判决方式曲线都贴着参考曲线平移。原因这是我在新手代码里见得最多的一个问题。awgn 函数的输入参数是符号信噪比 SNR而误码率曲线的横轴是每信息比特能量与噪声功率谱密度之比 Eb/N0。对于码率 R 的编码系统两者关系是 SNR Eb/N0 × R。忘记乘码率就相当于用硬判决的横轴标尺去画软判决的曲线。解决在调用 awgn 之前先做换算snr EbN0dB 10*log10(codeRate)。码率越小偏移量越大R1/2 时偏移 3dBR3/4 时偏移 1.25dB。如果你同时对比多种码率而不做这个换算不同码率的曲线之间还会出现交叉造成“高码率反而性能更好”的假象。从那以后我每次写仿真链路第一行就定义 codeRate并统一用换算后的 snr 变量名。5.2 译码输出前段出现成片误码网格终止与补零没做现象误码统计时把译码输出与原始数据直接对齐比较发现开头和结尾几十个比特的误码率明显比中间高整体 BER 被拉高一个数量级。原因维特比译码是流式处理vitdec 在 cont 模式下输出延迟等于回溯长度。开头部分的路径度量是从零初始化开始累积的还没有形成充分的路径竞争误码率高是正常的。如果不做任何处理这部分错误会被统计进整体 BER。解决两种做法选一种即可。第一种是数据末尾人为补 (constraintLen-1) 个 0让编码器回到全零状态译码时用 term 模式这样网格有明确的终止状态首尾都能正确收敛。第二种就是代码里常见的截断法统计误码时忽略前 tbDepth 个输出比特发送的数据也对应截短。我通常两种都做——先用截断法快速看趋势最后用补零法得到精确的 BER 值。5.3 软判决输入范围错误直接送浮点导致译码性能崩溃现象软判决模式的 BER 曲线不仅没有比硬判决好反而差到完全不可用曲线几乎贴近 0.5 的随机猜测水平。原因vitdec 的软判决模式要求输入是 0 到 2^nbits-1 的整数而且语义固定——0 代表最可信的比特 0最大值代表最可信的比特 1。如果直接把带正负符号的接收幅度送进去或者量化映射方向反了译码器内部的分支度量计算就会完全混乱等于让路径度量在错误的方向上累积。解决先用 min-max 归一化把接收符号映射到 [0, 1] 区间再乘上 (2^nbits-1) 取整。举一个 3 比特量化的标准做法先把接收符号 rx 归一化到 [-1, 1]然后softInt round((1 - rx) / 2 * 7)这样 rx1对应发送比特 0映射到 0rx-1对应发送比特 1映射到 7。最后再检查一遍边界超出范围的值强制 clamp 到 [0, 7]。5.4 对比实验结论颠倒数据长度与回溯深度不匹配现象对比不同约束长度时短约束长度的系统在某些信噪比点反而优于长约束长度违背了理论预期。原因长约束码的网格收敛需要更长的路径来稳定度量的竞争关系。当数据帧长只有几百比特时约束长度 7 的码还没来得及发挥优势而约束长度 3 的码早已收敛此时比较两者是不公平的。解决对比实验前先确认帧长至少是回溯长度的 20 倍。对约束长度 7 的码回溯长度 35帧长至少 700~1000 比特我一般直接设 10000 比特。另外不同约束长度应该各自用最优的回溯深度而不是统一用同一个值。短约束码可以用较小的回溯长度长约束码必须用更深的回溯否则长约束码的优势会被欠回溯抵消掉。6. 验证编码增益把仿真曲线和未编码 BPSK 理论曲线放在同一张图上跑通所有对比实验之后我强烈建议你多做一步验证把未编码 BPSK 的理论误码率曲线叠加到仿真图上。理论公式是 BER 0.5 × erfc(sqrt(Eb/N0))这里的 Eb/N0 是信息比特信噪比和你仿真扫描的横轴严格对应。把这个理论曲线作为“基线”你就能一眼看出编码系统到底带来了多大增益也能反向判断仿真链路本身有没有算错。% 未编码 BPSK 理论误码率 EbN0dB_theory 0:0.5:8; EbN0_linear 10.^(EbN0dB_theory/10); ber_theory 0.5 * erfc(sqrt(EbN0_linear)); % 叠加到之前的仿真曲线上 semilogy(EbN0dB_theory, ber_theory, k--, LineWidth, 1.5); legend(卷积码硬判决 (R1/2, N7), 卷积码软判决 (3bit), 未编码 BPSK 理论);在 BER10^-3 这个点硬判决卷积码相对未编码 BPSK 的增益约为 3.5~4dB软判决再叠加约 2dB总共约 5.5~6dB。如果实测增益明显小于这个范围就回头检查信噪比换算、量化映射或者帧长设置——这套“对照理论曲线”的验证方法能帮你快速定位是系统性能没发挥出来还是仿真代码本身出了毛病。验证时还有一个小习惯值得养成每条仿真曲线至少跑 5 个信噪比点每个点保证有至少 50 个误码事件才记录。这样做是为了避免高信噪比区域的 BER 曲线因为误码数太少而剧烈抖动。当年我做课程设计时就是图快每个点只跑一帧数据结果曲线像锯齿一样上下乱跳还一度怀疑是随机数种子的问题后来才发现只是统计样本不够。从那以后我每次跑 BER 仿真都强制走一遍这个流程先画未编码理论曲线当基准再跑编码曲线对比增益和理论经验值最后确认误码统计样本量大于 50。希望这套方法能帮你在做卷积码仿真时少走一些弯路。本文还有配套的精品资源点击获取