ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

数字通信编码三层次:信源、信道与线路编码的工程顺序

数字通信编码三层次:信源、信道与线路编码的工程顺序 干了这行十几年我发现在“编码”这个词上翻车的人特别多而且翻车的姿势高度一致把哈夫曼编码和曼彻斯特编码放在同一张对比表里把纠错码和压缩码混着讲然后在一个项目里同时用到压缩、纠错和线路编码时顺序排错、参数标反。数字通信里的编码其实是一串分工明确、目标函数互相矛盾的工序你把它们的位置摆正了剩下的公式和表格都是查手册的事。这篇笔记把我自己在做数字通信链路、写仿真、调板子的过程中攒下来的东西完整摊开从信源编码、信道编码到线路编码一条线讲清楚重点落在“为什么这么做”“参数怎么算”“实现时哪里会崩”这三件事上。适合正在学信息论与编码、数字通信课程的在校生也适合需要把编码模块真正落到 FPGA 或单片机上的工程人员。1. 先把编码这个词的位置摆正一条链路里它出现了三次1.1 发射链路从上到下的三次编码我把一条典型的数字发送链路按顺序写下来你会发现“编码”这个词在同一根链路上出现了三次而且每次干的活完全不一样信源产生的原始比特文本、音频、图像、传感器采样值信源编码去掉数据本身的统计冗余让表示同样的信息所用的比特更少比如哈夫曼、算术编码、LZW音视频里的 MP3、H.264 也属于这一层信道编码主动往比特流里塞冗余让接收端在噪声和干扰下还能把错误纠正回来比如汉明码、RS 码、卷积码、Turbo 码、LDPC交织把比特顺序打乱把信道的突发错误摊成随机错误这一层经常被忽略但在无线和存储场景里非常关键线路编码把比特映射成适合在物理介质上传输的电平或光功率波形比如曼彻斯特、4B5B、8B/10B、64B/66B调制、上变频、发射。这个顺序不能乱。你要是把线路编码放在信道编码前面纠错码解码时面对的就已经是被线路编码打散过的比特流译码器根本没法工作你要是把信源编码和信道编码顺序反了那就是拿压缩算法去处理带冗余的纠错码字压缩率为负。1.2 三者的目标函数完全不同甚至互相打架这是我认为最值得先记住的一点信源编码和信道编码的目标是互相矛盾的。信源编码要去除冗余追求的是“用尽量少的比特表示同样的信息”衡量指标是压缩率和编码效率理论上限是信息熵。信道编码要添加冗余追求的是“在同样的信噪比下把误码率压得更低”衡量指标是编码增益和纠错能力理论上限是香农限。线路编码既不压缩也不纠错它追求的是“让信号在特定介质上好好活着”衡量指标是直流平衡、最大游程长度、跳变密度和带宽开销。三者放在一起看才能理解一个真实的工程折中手机语音通话里信源编码把语音压到 12 kbps 以下信道编码又把码率翻一倍甚至两倍看起来是白忙活实际上前者决定了你占多少频谱后者决定了你在信号弱的时候还能不能说清楚话。这两个目标在同一个预算里争夺比特谁多分一点都要付出代价。维度信源编码信道编码线路编码核心目标去冗余、压缩加冗余、纠错波形适配、便于同步冗余变化大幅减少显著增加增加表现为开销关键指标压缩率、编码效率编码增益、误码率直流平衡、游程、开销典型算法哈夫曼、算术、LZW汉明、RS、卷积、LDPC曼彻斯特、4B5B、8B/10B在链路中的位置信源之后信源编码之后调制之前出错后的后果一个比特毁一片数据超出纠错能力就整帧丢接收端时钟失锁、误码飙升1.3 挂着编码名字但根本不在通信链路里的东西学这块的时候特别容易被同名词干扰我自己就吃过亏。地理编码、省市区编码、手机号地区编码、天气城市编码这些是空间到编号的映射表本质是查字典跟信息论一点关系都没有。苹果开发者账号要申请邓白氏编码那是企业身份标识。工作流编码、项目编码是业务系统里的主键。数据库的 GBK 和 UTF-8 是字符集编码解决的是“同一个字节序列按哪本字典解释”的问题——这个坑我见过太多次了MySQL 里存中文乱码十有八九是连接层、表结构、字段三个地方的字符集不一致跟通信编码毫无关系。还有一类更值得单独提一句的是深度学习里的位置编码Positional Encoding。它和数字通信里的编码没有血缘关系但如果你的研究方向跨了这两个领域把正弦位置编码和正交频分复用的子载波分配放在一起看会发现它们都在解决“让模型区分顺序”和“让接收端区分频率”这类结构信息的问题思路上的相似性挺有意思。至于网络编码那是网络层的多播优化技术属于另一个知识体系这里不展开。2. 信源编码把冗余挤出去理论上限是信息熵2.1 信息量和熵码长的下界是怎么算出来的信源编码的所有理论都从自信息量这个定义长出来。一个概率为 p 的符号它携带的信息量定义为I(x) -log2 p(x)单位是比特概率越小的事件发生带来的信息量越大。把自信息量按概率加权平均就得到信源熵H(X) Σ p(x) · log2(1 / p(x))熵的物理意义是平均每个符号最少需要多少比特才能无损表示。这不是一个工程经验值而是一个硬下界任何无损编码的平均码长都不可能低于熵。对于二进制符号序列还有一条更实用的约束叫 Kraft 不等式。任何一组前缀码没有任何一个码字是另一个码字的前缀的码长必须满足Σ 2^(-lᵢ) ≤ 1这条式子是判断“我设计的码表能不能实现”的最快工具。我见过有人拍脑袋给 8 个符号分配长度为 2、2、2、3、3、3、3、3 的码字看起来最长的才 3 位挺划算代入 Kraft 一算2^-2 × 3 2^-3 × 5 0.75 0.625 1.375 1直接判死刑这组码长压根构造不出对应的前缀码实现的时候一定会出现某个码字被另一个码字吞掉的情况。2.2 手算一遍哈夫曼树比看十遍伪代码管用哈夫曼编码的思路只有一句话反复把概率最小的两个节点合并。我第一次学的时候觉得这也太朴素了跑了一遍手算才发现它的巧妙之处在于合并的顺序天然保证了高频符号的码长更短。拿一组具体数据算五个符号 A、B、C、D、E概率分别是 0.4、0.2、0.2、0.1、0.1。手算过程合并最小的两个 0.1 0.1 0.2形成节点 DE现在集合是 A(0.4)、B(0.2)、C(0.2)、DE(0.2)合并最小的两个 0.2 0.2 0.4形成节点 BC集合变成 A(0.4)、BC(0.4)、DE(0.2)合并 0.2 0.4 0.6形成节点 BCDE最后合并 A(0.4) 与 BCDE(0.6)得到根节点。沿树分配 0 和 1得到一组可行的码字A 0B 110C 111D 100E 101。码长分别是 1、3、3、3、3。算平均码长L 0.4×1 0.2×3 × 4 0.4 2.4 2.2 比特/符号。再算熵H 0.4×log2(1/0.4) 0.2×log2(1/0.2)×2 0.1×log2(1/0.1)×2 ≈ 0.5288 0.9288 0.6644 ≈ 2.122 比特/符号。编码效率 η H / L 2.122 / 2.2 ≈ 96.5%。用 Computer 验算一下 Kraft2^-1 4 × 2^-3 0.5 0.5 1.0刚好用满说明这组码长是紧的再压缩就没有可用的码字空间了。哈夫曼定理告诉我们最优前缀码的平均码长一定落在 H ≤ L H 1 这个区间里。这个 1 的余量听起来不多但当符号数很少、概率分布很不均匀的时候1 比特的浪费在比例上可能是灾难——比如只有两个符号、概率 0.99 和 0.01哈夫曼只能给 1 和 1平均码长 1 比特而熵只有 0.08 比特效率不到 8%。这个反直觉的结果是我当年第一次意识到“最优算法在有限分组下也会很烂”的起点。2.3 哈夫曼的三个现实问题概率失配、码表传输、分组扩哈夫曼在教科书里很漂亮落到工程上有三个绕不开的麻烦。第一个是概率失配。哈夫曼编码的最优性是建立在“编码端和译码端都知道符号的真实概率”这个前提上的。真实数据的统计特性会漂移文件开头是英文、中间是中文、末尾是一堆二进制用一份固定的码表从头用到尾压缩率会被吃掉不少。工程上的解法是自适应哈夫曼边编码边更新频率统计编码器和译码器用同一套更新规则同步地重建树。这套机制写起来不难但同步一旦被打断整个数据流就全废了所以一般会配合周期性的重同步点。第二个是码表传输。固定码表可以硬编码在协议里自适应码表不需要传输但半自适应统计完再发就必须把码表本身也传过去或者干脆把频率表传过去让接收端重建。小文件场景下码表本身可能比数据还大。我处理过一批平均长度 200 字节左右的小报文用全局哈夫曼压缩后总体积反而涨了原因就是每包都带了一份 60 多字节的码表。第三个是分组扩展的代价。想突破 H1 的上界可以做分组扩展一次编码两个或三个符号的组合。理论上分组长度趋于无穷时平均码长可以任意逼近熵但码表大小会按符号数的幂次爆炸。4 个符号一次编 1 个码表 4 项一次编 2 个码表 16 项一次编 4 个码表 256 项。我一般的原则是码表项数超过 4096 就不划算存储和查表开销带来的收益递减会盖过压缩率的提升。2.4 算术编码与 LZW冗余的结构不同工具就不同哈夫曼有个天生的短板它给每个符号分配的码长必须是整数比特。当符号概率接近 1 的时候熵可能只有 0.05 比特但你最少也得给 1 比特差距是 20 倍。算术编码解决的正是这个问题——它不再给单个符号分配码字而是把整个消息映射成 [0,1) 区间里的一个小数用区间长度来承载概率信息码长可以是任意小数。这也是为什么算术编码在概率高度倾斜的数据上能比哈夫曼好一大截。另一条路是 LZW它完全不依赖概率统计而是用一张动态字典去匹配重复出现的字符串。处理长重复序列时它的威力非常直观一段重复 100 次的文本LZW 编码后的长度几乎只跟字典索引的位数有关。经典应用就是 GIF 图像和早期的 UNIX compress 命令。LZW 的弱点也很明确字典一旦被填满就得重置或者停止增长对短且随机的数据几乎无效。所以选型的判断标准不是哪个算法更先进而是冗余以什么形式存在冗余形式特征适配算法符号频率高度不均少数符号占绝大多数哈夫曼、算术编码概率接近 0 或 1 的偏斜分布单符号熵远小于 1 比特算术编码、区间编码长串重复同一段内容反复出现LZW、LZ77局部相关性强相邻样本差异小差分编码 熵编码3. 信道编码主动加冗余换回抗干扰的余量3.1 从重复码到汉明码纠错能力的硬边界信道编码最直白的入门例子是三次重复码每个信息比特发三遍接收端三取二多数表决。码率 1/3能纠正 1 位错误。它的好处是几乎不需要计算坏处是把带宽和功率浪费了两倍。遥控器、一些低速工业总线到今天还在用它就图个简单可靠。重复码的效率太低于是有了汉明码。汉明码的设计思想是用校验位构成一组二进制地址正好把错误位置指出来。(7,4) 汉明码用 4 个信息位加 3 个校验位总共 7 位能纠正任意 1 位错误。它的生成矩阵写成 G [I₄ | P] 的形式G 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 1 1 1 0 0 0 1 1 0 1对应的校验矩阵 H [P | I]H 1 0 1 1 1 0 0 1 1 1 0 0 1 0 0 1 1 1 0 0 1接收端把收到的 7 位向量乘以 Hᵀ得到 3 位伴随式syndrome。伴随式是 0 表示没有检测到错误非 0 的话它的二进制值直接告诉你哪一位出错了——因为 H 的 7 列恰好是所有非零的 3 位组合。这个伴随式即错误地址的巧思是汉明码最漂亮的地方也是我在给同事讲纠错码时一定会拿出来讲的一段。纠错能力和最小距离的关系是一条铁律t floor((d_min - 1) / 2)(7,4) 汉明码的 d_min 3所以 t 1只能纠 1 位错想纠 2 位必须把 d_min 提到 5。这条公式的推导很直接两个合法码字之间至少要差 d_min 个比特收到的向量要能被唯一地归到离它最近的合法码字上纠 t 位错要求以每个码字为中心、半径 t 的球互不相交于是 2t 1 ≤ d_min。理解了这个几何图像后面所有的域扩张、缩短、打孔操作都是在同一个几何直觉上做手脚。3.2 卷积码与维特比译码把一个时刻的选择摊成一张网格分组码是一次处理一整个块卷积码不一样它的校验位是信息位经过移位寄存器后的卷积组合每个输出比特都依赖当前和前面若干个输入比特。约束长度 K 3、码率 1/2 的经典卷积码生成多项式是 g₀ 111、g₁ 101自由距离 d_free 5。理解卷积码最好的工具是网格图trellis。K 3 时寄存器有两级共 4 个状态。每来一个输入比特状态转移一次输出两个比特。译码时维特比算法在整张网格上做动态规划给每条分支算一个分支度量通常用欧氏距离或者汉明距离然后逐列累加每个状态只保留累计度量最小的那条路径。走完全程后回溯得到的就是最大似然序列。维特比译码的计算量正比于状态数 2^(K-1) 乘以序列长度。K 7 时是 64 个状态工程上很舒服K 9 就是 256 个状态FPGA 里的资源占用会明显上升。这是约束长度选择时最主要的权衡点。这里有个特别值得强调的差别硬判决和软判决。硬判决是先把接收信号判成 0 或 1再送进维特比软判决直接把解调器输出的欧氏距离或者对数似然比送进去。软判决比硬判决大约能多出 2 dB 的增益而代价只是多几根数据线传量化值。我在做接收机的时候只要 ADC 量化位宽够一律走软判决这 2 dB 是白捡的。3.3 RS、Turbo、LDPC、极化码逼近香农限的四条路再往后走几种主流编码各管一段RS 码是定义在伽罗华域 GF(2^m) 上的分组码特别擅长对付突发错误。RS(255,223) 每个符号 8 比特能纠正 16 个符号错误也就是最多 128 个连续比特出错。光盘的划痕、深空通信的衰落段都是这种错误形态。译码用 BM 算法或者欧几里得算法计算量在可接受范围内所以它活了四十多年还在服役。Turbo 码的核心是并行级联加迭代译码。两个卷积编码器中间夹一个交织器译码时两个分量译码器互相交换外信息反复迭代几轮。它第一次把性能推到了距香农限 1 dB 以内引发了 3G/4G 时代的全面替换。缺点也很明显迭代次数越多延迟越大而且要等到整块接收完才能开始译码这对实时语音不友好。LDPC 码用的是稀疏校验矩阵加置信度传播BP迭代译码。它的历史很有意思六十年代提出后被遗忘了几十年直到算力上来才重新被发现。现在的 LDPC 在码长足够的时候能跑到距香农限 0.1 到 0.5 dB 以内而且译码可以高度并行非常适合硬件实现。5G 的数据信道、Wi-Fi、固态硬盘的闪存控制器用的都是它。极化码是一条从理论上另辟蹊径的路线用信道极化现象构造出能达到容量的编码方案被 5G 的控制信道采用。编码类型冗余开销典型纠错能力译码复杂度常见场景三次重复码200%纠 1 位极低遥控、教学汉明码 (7,4)75%纠 1 位低内存 ECC、教学RS(255,223)约 14%纠 16 个符号中光盘、数字广播卷积码 (2,1,7)100%随信噪比变化中维特比2G/3G、卫星Turbo 码33%~100%距香农限约 1 dB高迭代3G/4GLDPC可调距香农限 0.1~0.5 dB高BP 迭代5G、Wi-Fi、SSD3.4 编码增益怎么算才不会被自己骗编码增益的定义是在达到同样误码率的前提下编码系统比未编码系统省下的 Eb/N0单位 dB。注意一定是 Eb/N0不是 Es/N0。这个区别是我见过最多的踩坑点。未编码 BPSK 的误比特率是 Q(sqrt(2·Eb/N0))。加了码率 R 的编码之后每个符号承载的能量 Es 和每比特能量 Eb 的关系变成 Es R · Eb。也就是说编码额外引入的带宽开销让每个符号分配给单个信息比特的能量变少了。算编码增益时必须把这个折算进去否则你会算出一个虚高的、看起来很爽的数字——把 Es/N0 当 Eb/N0 用凭空多出 10log10(1/R) 的增益码率 1/2 时就是 3 dB纯属自欺欺人。另一条经验是编码增益是相对于某个目标误码率说的。没人会关心误码率从 0.5 降到 0.49 省了多少功率工程上一般取 10⁻⁵ 或者 10⁻⁶ 作为参考点。同样的编码在不同误码率点上算出的增益可以差好几个 dB所以横向对比两个方案时先确认对方报的是哪个点上的数字。4. 线路编码让波形去适配信道而不是反过来4.1 裸比特上线会出什么事直流分量与跳变密度如果直接把 0 映射成低电平、1 映射成高电平NRZ推上一对双绞线或者一根光纤很快就会撞上三堵墙。第一堵墙是直流分量。很多链路是交流耦合的中间有变压器或者隔直电容直流分量根本传不过去。如果数据里长时间是1多0少信号的均值明显偏正经过耦合电容后基线会漂移判决门限就跑偏了。解决办法是让码流长期来看正负电平各占一半这叫直流平衡。第二堵墙是时钟恢复。接收端没有独立的时钟线只能从数据流本身的电平跳变里提取时钟这叫时钟数据恢复CDR。如果数据里出现一长串连续的 0 或者连续的 1信号在几百个比特周期里纹丝不动CDR 的锁相环就慢慢漂了采样点偏掉误码就来了。所以线路编码必须保证足够的跳变密度通常用一个指标衡量最大游程长度。第三堵墙是传输带宽。方波的频谱包络按频率衰减跳变越陡带宽占用越大。线路编码选择的时候传输效率、带宽开销、直流平衡、游程长度这四项没有全优解只能按介质特点做取舍。4.2 曼彻斯特编码用一倍带宽换回时钟曼彻斯特编码的思路非常粗暴有效每个比特周期中间强制发生一次跳变。一个比特周期分成前后两半用半周期的电平方向表示数据。这样两个直接的好处——时钟恢复永远有边沿可用每个比特至少一次跳变直流分量天然为零每个比特的高电平和低电平各占一半。代价是带宽开销 100%原来 1 比特占一个周期现在要占两个半周期来承载跳变信息基带带宽需求翻倍。这也是为什么曼彻斯特基本只用在低速场景10BASE-T 以太网、125 kHz 的 RFID 卡、一些两线制的工业总线和电力载波这些场景对速率不敏感但对可靠性和成本极度敏感。要特别提醒一句曼彻斯特编码有两套方向相反的约定。IEEE 802.3 定义的是0 用高到低的跳变表示另一套常见的约定正好相反。这两套东西在协议文档里都出现过改代码的时候不核对文档就凭记忆写结果就是收发两端一个都通信不上。我第一次做嵌入式曼彻斯特解码的时候就在这上面耗了整整一天示波器上波形看着完全正确就是解不出数据最后发现是约定反了。还有一类变形叫差分曼彻斯特用比特起始处有没有跳变来表示数据中间那次跳变只负责提供时钟。它的额外好处是抗极性反转——收发两端线序接反了照样能工作工业现场很吃这一套。4.3 4B5B 与 8B/10B查表法解决长连零的工程胜利比曼彻斯特省带宽的方案是分组线路编码。核心思路是把 n 位数据映射成 m 位码字m n从所有 2^m 个可能的码字里挑出一部分满足约束条件的编成表剩下的那些违反游程规则、直流不平衡的组合直接不用。4B5B 把 4 位数据映射成 5 位码字开销 25%。它的映射表是这样挑出来的每个 5 位码字最多只有一个前导零、最多只有一个后导零这样就保证任意两个码字拼接起来都不会出现超过 3 个连续相同比特。这是标准的 4B5B 映射表4 位数据5 位码字4 位数据5 位码字000011110100010010000101001100110011001010100101010110001110101101110111010001010110011010010101011110111011011001110111011100011101111111111101注意 4B5B 本身不解决直流平衡问题它需要配合 NRZI遇到 1 翻转、遇到 0 保持一起使用NRZI 加上 5 位码字的约束组合起来才能保证足够的跳变密度。100BASE-FX 和 FDDI 用的就是这套。另外 4B5B 还腾出了几个码字用作控制字符用来标记帧的起始和结束。8B/10B 是同一思路的升级版开销仍然是 25%但约束更强、表达能力更丰富。它有几个关键设计每个 10 位码字里 0 和 1 的数量差不超过 1同一个码字里连续相同比特不超过 5 个引入运行不一致性running disparity机制让编码器根据之前累计的 0/1 数量差别动态选择两种码字中的一种从而把长期直流分量牢牢钉在零附近。它还专门保留了 12 个 K 码控制字符其中最著名的 K28.5 具有独一无二的比特模式用来做接收端的字对齐comma detection。8B/10B 服役面非常广PCI Express 的前两代、SATA、千兆以太网、光纤通道、USB 3.0。这样一个 25% 开销的方案能在这么多协议里活下来说明它在约束强度、实现简单度和兼容性上找到的平衡点确实好。不过 25% 的开销在更高速率下就肉疼了。10G 及以上以太网换成了 64B/66B只加 2 位同步头开销压到约 3%。代价是它不再靠查表保证游程而是用扰码器生成多项式 x⁵⁸ x³⁹ 1把数据随机化。扰码的好处是长连零被概率性地打散了坏处是它不保证——理论上仍有可能出现长游程只是概率极低。这是一次典型的工程赌注用小概率的极端情况换回 22% 的带宽。线路编码开销自带时钟最大游程典型应用NRZ0%否无限制芯片间短距、UART曼彻斯特100%是半比特级10BASE-T、RFID、工业总线4B5B NRZI25%否5FDDI、100BASE-FX8B/10B25%否5PCIe 1/2、SATA、千兆以太网64B/66B 扰码约 3%否概率控制10G/40G/100G 以太网4.4 线路编码之后的事眼图、预加重与均衡线路编码解决了码流本身好不好的问题但信号真正上线之后还要过信道这一关。高速链路上介质对高频的衰减比低频大得多方波的边沿被磨圆眼图闭合误码率上升。工程上的第一招是预加重和去加重发送端有意把跳变沿的幅度抬高一点预加重或者把连续相同比特的幅度压低一点去加重用发送端频谱上的倾斜去抵消信道的低通特性。第二招是接收端的均衡连续时间线性均衡器CTLE在模拟域做高频补偿判决反馈均衡器DFE在数字域用之前判决过的比特去减掉当前符号受到的码间干扰。我调试高速链路时的一个体会是编码和均衡的关系很像前后两个阶段的接力。8B/10B 把游程压到 5其实也是在帮 DFE 干活——游程越短码间干扰的模式越少DFE 的自适应越容易收敛。反过来如果链路用的是 64B/66B 加扰码偶尔出现的长游程就会给 DFE 带来它没见过的模式这时候通常要靠更长的训练序列让均衡器把抽头系数学全。5. 从仿真跑通到板子上跑通中间隔着什么5.1 仿真里最容易写错的三处用 Python 或者 MATLAB 做编码仿真跑出来的误码率曲线光滑漂亮上板却完全不是那么回事绝大部分问题出在这三处。第一处是噪声功率的换算。给 BPSK 加高斯白噪声时符号功率归一化成 1噪声标准差必须是 sqrt(1/(2·Es/N0)) 而不是 sqrt(1/(Es/N0))因为噪声功率是方差而实高斯噪声的功率等于方差两个正交分量各分一半。写错了就是恒定偏 3 dB曲线形状对、数值全错很难通过肉眼发现。第二处是采样率与码元对齐。用脉冲成型滤波器之后滤波器的群延迟会让输出滞后若干采样点。如果不把这段延迟补偿掉判决时用的是错误的采样相位误码率会莫名其妙地变差。稳妥做法是先单独把滤波器的群延迟测出来或者在仿真里显式地把输出延迟截掉。第三处是 Eb/N0 与 SNR 的换算。这条式子我建议直接抄在代码注释里SNR(dB) Eb/N0(dB) 10·log10(R) 10·log10(fs / B)其中 R 是码率fs 是采样率B 是符号率。少任何一项曲线就会整体平移。下面是一段可以直接跑的验证代码用三次重复码加多数表决检查 Eb/N0 的换算是否正确——如果换算对了你会看到编码后的曲线在低误码率区明显比未编码的陡import numpy as np rng np.random.default_rng(42) N 400_000 info rng.integers(0, 2, N) def repeat3_ber(ebn0_db, rate1/3): tx np.repeat(info, 3) # 三次重复编码 sym 2 * tx - 1 # BPSK 映射0 - -1, 1 - 1 esn0 10 ** (ebn0_db / 10) * rate # Es/N0 Eb/N0 * R sigma np.sqrt(1 / (2 * esn0)) # 实噪声两个分量各占一半 rx sym sigma * rng.standard_normal(sym.size) hard (rx 0).astype(np.int8).reshape(-1, 3) dec (hard.sum(axis1) 2).astype(np.int8) # 三取二表决 return np.mean(dec ! info) for ebn0 in [0, 2, 4, 6, 8]: print(fEb/N0{ebn0:2} dB 累积误比特率{repeat3_ber(ebn0):.3e})跑完你会注意到一个挺反直觉的现象在 Eb/N0 比较低的时候三次重复码的表现反而不如不编码的 BPSK。原因是重复码把每比特的能量摊到了三个符号上低信噪比下三个符号全都不可靠表决结果就是抛硬币。只有跨过某个信噪比门槛之后编码的收益才开始显现。这个编码增益只在高信噪比区存在的事实和很多教科书上画的漂亮曲线给人的印象不太一样值得自己跑一遍体会。5.2 码元同步比编码算法本身更容易翻车的地方我调试数字通信链路时出问题的十次里有七八次不在编码算法上而在同步上。接收端要做三件事位同步找到每个比特的边界、帧同步找到码字的边界、载波同步把相位和频率对齐。任何一件没做好后面的译码器再完美也是白搭。位同步的经典做法是早迟门用一个略早和一个略晚的采样点分别计算能量比较两者大小把采样时刻往能量大的方向推。实现简单收敛速度也可以。帧同步的难点在于在哪个位置切分码字。8B/10B 用 K28.5 这种唯一模式做对齐4B5B 用专门的起始码字64B/66B 靠 2 位同步头的跳变模式。实际工程里这一步通常要先扫一遍可能的偏移找到一个能持续通过校验的相位再锁定。我踩过的一个真实的坑是接收端在数据开头用一段已知的训练序列完成了帧同步然后进入了长时间的空闲状态空闲期没有任何跳变CDR 慢慢漂走等真正的数据来了之后采样点已经偏了前几十个字节全错。后来处理办法是在空闲期插入周期性的空闲码字保证跳变密度始终够——这也是为什么协议里要专门定义空闲字符。5.3 查表实现的资源和延迟取舍线路编码的 4B5B、8B/10B 本质上就是查找表实现起来非常简单一个 ROM 或者几层组合逻辑就搞定。但查表法在高速场景下有两个隐藏成本。一个是寄存器到寄存器的路径延迟。8B/10B 的编码逻辑如果直接用一张 256 项的 LUT 实现综合出来的关键路径可能很长跑不到几百兆赫兹。常见做法是把编码拆成 5B/6B 和 3B/4B 两级中间打一拍流水线用面积换频率。这也是 8B/10B 那个看起来怪异的命名方式的来源——它本来就是分成两段设计的。另一个是运行不一致性带来的状态依赖。8B/10B 的码字选择依赖之前累计的 disparity每个码字有正负两种形式编码器里必须维护一个状态位。这意味着它不是纯粹的组合逻辑有反馈路径流水线设计时要特别注意。我见过一个在高速协议里跑的 8B/10B 编码器因为状态位没做同步处理在跨时钟域的时候偶发地输出错误码字误码率只有 10⁻⁹ 量级跑几分钟才出一帧错查了很久才定位到。LDPC 译码的资源取舍是另一个量级的问题。置信度传播算法可以在校验节点和变量节点之间全并行展开吞吐率极高但资源消耗巨大也可以部分并行用更少的计算单元分时复用代价是迭代轮数变多、延迟变大。固态硬盘里的 LDPC 引擎通常还要支持硬解码和软解码两种模式——硬解码只读 1 位判决结果速度快软解码需要从闪存单元读多个阈值下的量化信息得到对数似然比纠错能力更强但读延迟翻好几倍。这套机制的触发策略本身就是一个值得单独研究的课题。6. 这套东西怎么学才不白学6.1 手推一遍胜过跑十次仿真我自己的经验是学编码这块凡是能动手算的地方一定要动手算一遍。手算哈夫曼树手算码长和熵的差距手推汉明码的生成矩阵和校验矩阵手工对一个接收到 1 位错误的 7 位码字做伴随式译码。这四件事加起来花不了两个小时但对整块知识的理解深度提升是质变级的。原因很简单仿真会替你把所有中间步骤都藏起来。你调用一个库函数 huffman_encode看到的只有输入和输出看不见那个合并两个最小概率节点的过程也就不会真正理解为什么它对概率敏感、为什么分组扩展能提升效率。而手算一遍所有的中间状态都摊在纸面上。6.2 给自己出的几道验证题如果你跟着这篇笔记学到了这里可以拿下面几道题自测能独立做出来就说明这块知识扎实了一个信源有四个等概符号求熵再设计一组码长满足 Kraft 不等式的前缀码算编码效率。(7,4) 汉明码接收到 1011011前四位按信息位解释判断是否有错、错在哪一位。一段数据里连续出现 40 个 0说明它对哪几种线路编码是致命的对哪几种是可接受的。码率 2/3 的编码在 Eb/N0 5 dB 时工作折算成 Es/N0 是多少折算成 SNR采样率等于符号率是多少为什么 LDPC 的校验矩阵要求稀疏校验矩阵稠密之后BP 译码会发生什么第 5 题的答案值得说一下BP 译码的本质是在因子图上迭代传递消息稀疏的校验矩阵对应的因子图没有短环消息传递收敛得好矩阵一旦稠密短环大量出现消息在环里来回震荡迭代不仅不收敛还会产生错误的正反馈。这个理解一旦建立起来你对整个迭代译码体系的认识会清楚很多。6.3 我走过的弯路和现在的做法说几个我自己浪费过时间的地方。一开始我把注意力全放在算法上觉得 LDPC 比卷积码先进、Turbo 比汉明码高级于是到处找最新最强的编码方案。后来才明白工程上选编码方案第一看的是延迟预算和实现复杂度性能只是其中一维。一个实时语音系统里塞进需要迭代十几轮才能收敛的 LDPC性能再好也没用因为延时不达标。现在我评估任何编码方案第一件事是问这块数据的延迟预算是多少需要在多少毫秒内完成译码这个约束一旦明确可选范围立刻就缩小了一大半。第二个弯路是忽略交织。很长一段时间里我觉得交织就是个打乱顺序的小技巧不重要。直到接触到无线信道和闪存信道才明白突发错误和随机错误在纠错码眼里是两种完全不同的敌人。一个只能纠 1 位错的汉明码遇到 10 个连续比特出错是彻底没救的但同样 10 个错如果被打散到 10 个不同的码字里每个码字只错 1 位就全部能救回来。交织器的深度设计要打散到多远和信道突发长度直接相关这中间的计算是实打实的工程活。第三个是工具选择上的习惯。现在我写编码相关的代码基本固定是三件套Python 做算法验证和误码率曲线改得快、画图方便做好之后用 C 写一版定点实现验证量化误差和溢出边界最后才上 FPGA 或者 DSP。跳过中间那步定点验证直接用浮点结果去指导硬件设计几乎一定会出问题——尤其是 LDPC 的对数似然比计算浮点下随便做的归一化和近似定点实现里会带来明显的性能下降。最后分享一个我用了很多年的小习惯每学一个新的编码方案我会强制自己用一句话回答它用多少冗余换回了什么。汉明码用 42.9% 的冗余换回纠 1 位错曼彻斯特用 100% 的带宽换回自带的时钟和直流平衡8B/10B 用 25% 的冗余换回零直流和有限的游程LDPC 用可调的冗余换回逼近香农限的性能。把这句话写下来选型的时候脑子里就有一张清单不会被最先进这种标签带着跑。
返回列表