简介:《卷积码及译码》是一份面向信息工程、通信专业学生及信道编码初学者的毕业论文文档,围绕卷积码编译码原理与维特比译码性能展开,重点解决不同参数设置下纠错能力与译码复杂度如何权衡的问题。压缩包内为1个doc文件,大小约991KB,内容包含绪论、纠错码基本理论、卷积码编码与译码原理、MATLAB仿真及结论等章节,形成从理论推导到实验分析的完整脉络。已有349人学习,适合用于课程设计、毕业设计选题参考或Viterbi算法入门。文档通过MATLAB仿真比较码率、约束长度、回溯深度以及硬判决与软判决对误码率的影响,并给出参数选择与性能优化的参考结论,读者可据此理解卷积码系统建模、仿真流程和结果分析方法,也可为后续通信链路仿真与纠错编码实验提供可借鉴的思路。
1. 一条 BER 曲线卡在 1e-4 时,翻书不如翻这份包
有人拿着一组仿真数据来问:卷积码配维特比译码,(2,1,7),Eb/N0 都到 6 dB 了,误码率怎么还在 1e-4 附近不动。查了两小时教材没结论,最后一查是把码率 1/2 的换算系数漏了,把 Es/N0 当成了 Eb/N0 在画。这类问题,一份能跑、能改参数、带对照实验的编译码仿真包,比十页推导管用。
手头这份包围绕卷积码与维特比译码展开:从 (n,k,N) 编码器结构、生成多项式与网格图,到 Viterbi 的加比选与回溯,再到 MATLAB 下四组误码率对照——不同码率、不同约束长度、不同回溯深度、不同判决方式。它适合正在做课程设计、毕业设计,或者要给链路预算找一个可复现 BER 参考点的人。下面按“编码器怎么搭、译码器怎么写、参数怎么扫、坑在哪”四步拆。
2. 拆开 (2,1,7) 编码器:生成多项式、64 个状态和一张喂给译码器的网格图
卷积码和分组码最本质的差别,是输出码元不只跟当前这 k 个信息位有关,还跟前 N-1 段输入有关。所以编码器本质就是一个带记忆的有限状态机:k 个输入端、n 个输出端、m=N-1 级移位寄存器。R=k/n 是码率,N 是约束长度,工程上几乎只用 k=1,因为状态数是 2^(k(N-1)),k 一大就爆炸。
(2,1,7) 是最经典的一组:1 位进、2 位出、6 级寄存器、64 个状态、码率 1/2。网格图上每个时刻 64 个状态、每个状态 2 条出边,一步就是 128 条分支。维特比译码每步要做的加比选次数,就是按这个量级算的——这决定了它在什么速率下能实时跑。
2.1 冲激响应、生成矩阵、状态转移表:三种描述,工程上只认最后一种
教材一般给三种等价描述。第一种是冲激响应/生成多项式:把输入序列看成多项式 u(D),两个输出分别是 u(D)g1(D) 和 u(D)g2(D),这里的乘法是模 2 卷积,卷积码的名字就是这么来的。第二种是半无限生成矩阵,把 g1、g2 的系数交叉排进去,编码写成 c = u·G。第三种是状态转移表加网格图。
看论文和教材时前两种有助于理解“为什么叫卷积”,但真正落到代码,只有第三种有用。因为译码器需要的是两个查表:给定当前状态 s 和输入 u,下一个状态是谁、输出两位是什么。生成矩阵在译码端一次都用不上。很多人第一次写译码器写不出来,就是卡在“我明明会算生成矩阵,怎么写不出 ACS”,原因就在这里。
(2,1,7) 这组抽头:g1 = 1 + D + D² + D³ + D⁶,g2 = 1 + D² + D³ + D⁵ + D⁶,八进制写作 171 和 133。它的自由距离 d_free = 10,渐近纠错能力 t = ⌊(10-1)/2⌋ = 4。注意这是渐近值,短帧、尾比特归零的场合实际表现要看具体重量分布,不能直接拿 4 去套“能纠 4 个错”。
提示:不同资料对约束长度的记号不统一。有的把 N 定义成“包含当前段的段数”(这份包就是这么写的,相关码元 nN 个),有的用记忆级数 m=N-1。看别人的推导前先确认他用的是哪套,否则状态数会差一倍。
2.2 手写编码器:MATLAB 三十行,加一段冲激响应自检
不依赖通信工具箱,直接按寄存器移位写,好处是每个比特怎么来的都看得见。约定:抽头矩阵 G 的第 1 列对应当前输入 b(n),第 K 列对应 b(n-K+1)。
function code = conv_encode(info, G) % info : 1×L 输入比特(0/1) % G : M×K 抽头矩阵,第 1 列对应当前输入,第 K 列对应 b(n-K+1) % 返回 : 1×(L*M) 编码输出,同一时刻的 M 个输出位连续排列 K = size(G, 2); M = size(G, 1); reg = zeros(1, K); % 寄存器,reg(1) 恒为当前输入 code = zeros(1, numel(info) * M); for i = 1:numel(info) reg = [info(i), reg(1:end-1)]; % 右移一位,新比特压到最左 for m = 1:M code((i-1)*M + m) = mod(sum(reg .* G(m, :)), 2); end end end逻辑很直白:每来一个信息位,整个寄存器右移一格,新比特进最左;然后拿 M 组抽头分别做一次“按位与再模 2 加”,得到同一时刻的 M 个输出。K 和 M 都从 G 的尺寸自动推出,换码型只改矩阵,函数不用动。
调用和自检:
G = [1 1 1 1 0 0 1; % g1 = 1 + D + D^2 + D^3 + D^6,即八进制 171 1 0 1 1 0 1 1]; % g2 = 1 + D^2 + D^3 + D^5 + D^6,即八进制 133 u = [1 0 0 0 0 0 0 0]; % 单比特冲激,后面补零把寄存器冲干净 c = conv_encode(u, G); reshape(c, 2, []).' % 每行一个时刻的两个输出位跑出来应该是 8 行 2 列:[1 1] [1 0] [1 1] [1 1] [0 0] [0 1] [1 1] [0 0]。前 7 行拆开看,第一列是 1111001,第二列是 1011011,正好就是两个生成多项式的系数展开。这一段自检花不到一分钟,但能挡掉后面 90% 的“译码器怎么都译不对”——冲激响应对不上,译码器一定错,不用往下查。
参数上有两点要注意。一是八进制写法 171 转成比特向量时,展开顺序是“当前输入→最早输入”,跟有些资料从高位往低位读的习惯相反,转换时容易整体倒过来,倒过来之后码的自由距离可能还是 10,但和你参考的那组曲线就对不上了。二是编码完必须补 K-1 = 6 个 0 做尾比特归零,把状态拉回全零;不归零,译码器不知道终点状态,只能靠猜,回溯出来的序列在尾部就是错的,帧一短误码率会明显偏高。
再给一段生成状态转移表的代码,译码器直接吃这张表:
function [nextState, outBits] = build_trellis(G) K = size(G, 2); M = size(G, 1); S = 2^(K-1); nextState = zeros(S, 2); outBits = zeros(S, 2, M); for s = 0:S-1 hist = bitget(s, K-1:-1:1); % 6 位历史,最左是 b(n-1) for u = 0:1 reg = [u, hist]; % 拼成 K 位完整寄存器 outBits(s+1, u+1, :) = mod(reg * G.', 2); nextState(s+1, u+1) = bin2dec(num2str(reg(1:K-1))); end end endhist是把状态整数按“最近的历史在最高位”展开,reg拼上当前输入后做矩阵乘再模 2,就是该分支的两个输出位。次态取reg的前 K-1 位——因为最老那一位被移出寄存器了。状态编号 0 对应全零寄存器,这个约定后面译码器的初始状态和终止状态都要跟着用,不能一半用 0 一半用 1。
3. 维特比译码的四步骨架:从路径度量递推到 5N 回溯深度
维特比译码做的事,用一句话讲:在每个时刻对每个状态只保留一条“到目前为止最像”的路径,把指数级的序列搜索压成状态数乘时间的线性递推。它成立的前提是路径度量可加,而可加性来自信道是无记忆的——这也是为什么它在 AWGN 下是最大似然,到了有记忆的衰落信道就退化成次优。
四个步骤:初始化、分支度量计算、加比选(ACS)更新、回溯输出。初始化把 0 状态度量置 0、其余置无穷大(已知从全零状态出发);ACS 每步对每个状态比较所有入边;回溯从终止状态往回走,把沿途的输入位取出来。
3.1 加比选递推:硬判决算汉明距离,软判决算相关值
记 PM_k(s) 是第 k 步状态 s 的累积路径度量,BM_k(s',u) 是从 s' 走输入 u 这条分支的度量,next(s',u)=s,则
PM_{k+1}(s) = min over (s',u) [ PM_k(s') + BM_k(s',u) ]
同时把取到最小值的那条入边记下来,就是幸存分支。全过程只有加法、比较、选择,没有乘法、没有除法,这是它能做成专用硬件的原因。
硬判决下 BM 就是汉明距离:把接收到的 0/1 序列和分支输出逐位异或再求和。软判决下 BM 用欧氏距离平方:sum((r_i - x_i)²),其中 x_i = 1-2·out_i 映射成 ±1。这里有个能省一半运算的等价变换:平方展开后,|r|² 与分支无关、|x|² 恒等于 M,都可以从比较里扔掉,所以最小化欧氏距离等价于最大化相关 sum(r_i · x_i)。实际代码里软判决直接算负相关就行,别真的去算平方。
三种判决方式的差距是可以量化的:硬判决相对无限精度软判决损失约 2 dB;3 bit 量化软判决相对无限精度只损失 0.25 dB 以内。也就是说,从硬判决换到 3 bit 软判决,是这套系统里性价比最高的一次改动,代价只是 ADC 多两位。
3.2 用 Python 把 ACS 写出来,再跑一遍已知答案
MATLAB 那版是为了看清抽头卷积,跑批量仿真我一般用 Python,调试方便、跑得也快。编解码共用同一张 trellis 表,这一点很关键——两套独立实现的状态定义稍有出入,误码率就会永远卡在 0.5。
import numpy as np def build_trellis(G): G = np.asarray(G, dtype=np.int32) M, K = G.shape S = 1 << (K - 1) nxt = np.zeros((S, 2), dtype=np.int32) out = np.zeros((S, 2, M), dtype=np.int32) for s in range(S): hist = [(s >> (K - 2 - i)) & 1 for i in range(K - 1)] # 最近的历史在最高位 for u in (0, 1): reg = np.array([u] + hist, dtype=np.int32) # K 位完整寄存器 out[s, u] = (G @ reg) % 2 nxt[s, u] = int(''.join(str(b) for b in reg[:K - 1]), 2) return nxt, out def encode(info, nxt, out): state, code = 0, [] for u in info: code.extend(out[state, u]) state = nxt[state, u] return np.array(code, dtype=np.int8)nxt[s,u]是次态,out[s,u]是该分支的 M 个输出位。编码用查表而不是移位,原因只有一个:保证和译码器用的是同一份状态定义,杜绝两边不一致。
译码器:
def viterbi(rx, nxt, out, mode='soft'): S, _, M = out.shape K = int(np.log2(S)) + 1 n_step = len(rx) // M pm = np.full(S, np.inf); pm[0] = 0.0 # 已知从全零状态出发 dec = np.zeros((S, n_step), dtype=np.int32) # 每个状态每步的幸存前驱 for k in range(n_step): r = rx[k * M:(k + 1) * M] new_pm = np.full(S, np.inf) for s in range(S): if not np.isfinite(pm[s]): continue for u in (0, 1): y = 1 - 2 * out[s, u] # 0 -> +1, 1 -> -1 if mode == 'hard': bm = np.sum(r != out[s, u]) # 汉明距离 else: bm = -np.dot(r, y) # 负相关,等价于最小化欧氏距离 t = nxt[s, u] if pm[s] + bm < new_pm[t]: new_pm[t] = pm[s] + bm dec[t, k] = s # 只记前驱状态,输入位可反推 pm = new_pm # 尾比特归零,终点必为 0 状态,从这儿往回走 state, bits = 0, np.zeros(n_step, dtype=np.int8) for k in range(n_step - 1, -1, -1): bits[k] = (nxt[state, 0] == state) * 0 + ((state >> (K - 2)) & 1) state = dec[state, k] return bits参数说明:mode='hard'时rx传 0/1 整型数组,mode='soft'时传 ±1 加噪后的浮点数组,两种模式共用一套骨架,只换分支度量那一行——这也是把度量写成函数、不要写死在循环里的理由。dec表是 S×n_step,64 状态、一万步也只有 64 万个数,内存完全够。上面那套bits[k]的写法依赖“次态的 MSB 就是输入位”这一性质,换码型时K-2这个移位量要跟着变,所以我把 K 从 S 反推出来了,别写死成 6。
工程实现里不会存全表,而是用环形缓冲只保留最近 tb 步的dec,每步输出 k-tb 时刻的判决位,解码延迟就等于 tb。tb 取多少?经验值是 5N,对 (2,1,7) 就是 35。这个 5 不是玄学,是幸存路径合并概率决定的:回溯到 5 倍约束长度之前,两条路径还没合并的概率已经小到不影响误码率,再加深纯属浪费内存。第 4 章的对照实验会把这条经验值测出来。
跑通的第一件事不是画曲线,是跑无噪声回归:随机 200 bit、补 6 个尾比特、编码、不加
本文还有配套的精品资源,点击获取