ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

加扰与解扰:LFSR白化原理与串行链路工程实现

加扰与解扰:LFSR白化原理与串行链路工程实现

简介:加扰与解扰是数字通信中提升信号质量与数据安全性的关键步骤。这套基于VHDL的FPGA工程面向数字通信开发者和FPGA学习者,可用于理解LTE、5G、卫星通信中的序列随机化处理与信道优化方法。压缩包共含132个文件、约1.37MB,以VHDL源码(.vhd)、工程约束与设计文件(.ucf/.xise)、仿真配置(.vho/.xmsgs)以及可下载到FPGA的比特流配置(.bit/.mcs/.ngc)为主,基本覆盖代码编写、仿真验证到硬件烧录的完整链路。目前已有2494人学习下载。资料内既有Modelsim环境下针对加扰/解扰的测试用例与仿真报告,也有可供JTAG下载的工程文件;对照代码、测试向量与综合/布线日志,能够清晰还原加扰与解扰算法的实现及验证过程,适合正在做FPGA课程设计或通信信号处理项目的读者参考。

1. 加扰与解扰:数字链路里那层没人注意的“白化”层

串行链路调试里,最让人头疼的不是速率上不去,而是数据里连续几百个0或连续几百个1。接收端的时钟恢复电路靠跳变沿锁相,一整段直流电平会让锁相环失锁,误码率瞬间飙升。加扰与解扰就是来解决这个问题的:发送端用一个伪随机序列和原始数据做异或,把缺少跳变的原始流变成跳变丰富的白噪声状序列;接收端拿同一个序列再做一次异或,无损还原出原始数据。整个过程不增加一个比特的冗余,成本只是一对线性反馈移位寄存器(LFSR)和一次复位对齐。这篇文章把加扰的两种形态、LFSR实现、参数调节和常见翻车点讲清楚,适合做数字基带、接口IP验证和链路调试的工程师。

2. 先分清两种加扰:通信白化与内容保护

2.1 可恢复加扰:不增加冗余的线路整形

加扰在数字通信里的本质是一个线性、可逆、无密钥的变换:发送端把原始序列和一段伪随机序列逐比特异或,接收端再用同一个序列异或回去。因为异或自反,两个操作电路几乎一模一样。它解决的从来不是保密问题,而是三个物理问题:一是时钟恢复,二是频谱整形,三是避免长游程带来的直流漂移。

拿最典型的NRZ链路举例。NRZ信号本身没有线编码冗余,一个比特的“1”或“0”就对应一段直流电平。如果数据流里恰好有一长串同电平,接收端CDR(时钟数据恢复)的鉴相器会拿不到边沿,锁相环进入“记忆”状态,稍微来点噪声就开始抖动,严重时直接失锁。加扰的作用就是让这种长游程不再出现:伪随机序列打散原始数据,无论源数据是周期模式还是恒定电平,加扰后的流里每个比特出现“0”和“1”的概率都接近1/2,游程被控制在统计范围内。

做接口调试的工程师对这套逻辑应该不陌生。USB 3.0、PCI Express、SDH、DVB-S这些体系在物理层或传输层都内嵌了加扰,目的就是避免“差数据模式”把链路的极限性能拉下来。和8B/10B这类编码比,加扰的好处是没有冗余开销、吞吐率不降;坏处是它不保证最长游程,只能保证统计意义上的游程分布。换句话说,加扰不是硬约束,是软整形。设计时如果要求“最坏情况可见、可预测”,还是得靠线编码;如果只要求“统计上能收敛”,加扰是性价比最高的选择。

2.2 不可恢复加扰:条件接收里的控制字机制

另一种“加扰”出现在内容保护场景,典型是数字电视的条件接收系统。这里的加扰不再是为了波形白化,而是让原始传输流在不解扰的情况下完全不可用。系统用一个控制字(CW)对TS流做加扰变换,接收端必须拿到CW、完成一系列鉴权处理后才能解扰。CW本身不直接下发,而是通过授权控制信息加密传输,再配合授权管理信息更新密钥,形成一套完整的密钥管理闭环。

这类加扰有两个显著特征。第一,它是有密钥的,没有正确控制字就解不开;第二,解密单元通常按包处理,加扰的不是单比特流序,而是188字节的TS包整体变换,因此错误影响范围往往是一个包而不是几个比特。工程师在文档里看到“加扰”这个词时,先确认它在协议栈的哪一层:物理层做的是可恢复加扰,传输流层做的是条件接收加扰。这两类设计目标完全不同,代码更不能混用。

我一般这样快速判别:凡是解扰不需要任何密钥、只要状态对齐就能还原的,是可恢复加扰;凡是文档里提到“控制字”“授权”“加密传输”的,是条件接收加扰。前者是数字电路问题,后者是密钥管理和安全边界问题。新手最容易在这个地方踩坑,拿LFSR的白化思路去套条件接收,结果发现怎么加扰都不“保密”,或者拿条件接收的密钥体系去套物理层,平白增加好几路密钥分发逻辑。

2.3 一张表看清两种加扰的选型边界

对比项可恢复加扰(通信白化)不可恢复加扰(条件接收)
核心目的频谱白化、时钟恢复内容保护、访问控制
是否需要密钥不需要必须用控制字
解扰条件加扰器和解扰器状态对齐拿到CW并完成鉴权
错误影响范围单比特至多项式阶数个比特通常影响整个数据包
典型工作层物理层、线路编码层传输流/节目流层
典型应用数字线路、存储接口付费电视、流媒体DRM

这张表不是用来背的,是用来做需求拆解的。项目里如果只说“要加扰”,第一件事就是把加扰的目的问清楚。是链路误码率过不了?是眼图张不开?还是内容要防未授权的设备接收?三个问题对应三种完全不同的方案。链路误码率问题走LFSR白化;眼图问题先看驱动器能力和通道损耗,加扰只是辅助;内容保护问题至少要引入密钥协商和鉴权链路,已经不是单纯信号处理能覆盖的了。

3. 从多项式到电路:LFSR帧同步加扰器的工程实现

3.1 生成多项式的选择:本原多项式与三项式的权衡

线性反馈移位寄存器的核心是生成多项式。多项式决定两件事:伪随机序列的周期长度,以及序列的自相关特性。一个N级LFSR最多能产生 2^N - 1 个非全零状态,如果生成多项式是本原多项式,这个最大值就能达到,输出的PN序列周期最长,相关性也最理想,这就是“m序列”。

工程里最常用的本原多项式有三项式形式,比如 x^15 + x^14 + 1、x^9 + x^5 + 1、x^23 + x^5 + 1。选三项式不是为了数学好看,而是为了硬件代价:三项式只有两个反馈抽头,异或链只需要一级门,关键路径短,在高速时钟下容易收敛。抽头多了,LFSR的反馈逻辑就变成多级异或串,时序压力直接反映在最高可运行频率上。

选多项式阶数时,先把链路的数据模式想清楚。如果源数据里存在周期性重复模式,比如视频流里连续的同步头、存储读出的固定Pattern,加扰后依然可能残留周期性分量,在频谱上打出一根谱线。多项式阶数越高,m序列周期越长,这种重复模式被“打穿”的概率越低,但代价是寄存器位宽变大、同步字检测窗口变长。SDH选x^15+x^14+1是为了在复杂度、误码传播和同步时延之间取平衡,不是阶数越高越好。我自己的习惯是先确认有没有成熟的行业先例,再用软件仿真去验证所选多项式对预期数据模式的统计效果,而不是拍脑袋定一个“看起来更复杂”的高阶多项式。

3.2 加扰器与解扰器的Verilog实现

帧同步加扰器的思路是:在每一帧的起始位置把LFSR复位到固定种子,然后逐比特将LFSR输出与数据异或。接收端只要在相同位置复位LFSR,就能生成完全相同的伪随机序列。下面的代码是这个方案的串行实现,适合位宽为1的链路。

// 帧同步加扰器:每帧起点将LFSR复位到SEED // 核心逻辑:data_out = data_in XOR LFSR最高位 module scrambler_lfsr #( parameter N = 15, // LFSR级数 parameter SEED = 15'h7FFF, // 初始种子,全部置1 parameter TAP_MSB = 14, // 反馈抽头1,对应x^15 parameter TAP_2ND = 13 // 反馈抽头2,对应x^14 )( input wire clk, input wire rst_n, input wire bit_valid, // 数据有效脉冲,1个时钟1位 input wire frame_sync, // 帧起点复位脉冲 input wire data_in, output reg data_out ); reg [N-1:0] lfsr; wire pn_bit = lfsr[TAP_MSB]; // 当前PN输出位 wire fb_bit = lfsr[TAP_MSB] ^ lfsr[TAP_2ND]; // 反馈位 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin lfsr <= SEED; data_out <= 1'b0; end else if (bit_valid) begin if (frame_sync) begin lfsr <= SEED; // 帧头复位,收发两端必须同一拍 end else begin lfsr <= {lfsr[N-2:0], fb_bit}; end data_out <= data_in ^ pn_bit; // 异或白化 end end endmodule

对应的解扰器和加扰器在结构上几乎完全一样,因为解扰本质就是再做一次异或。区别只在于输入数据变成了链路传来的加扰流。

// 帧同步解扰器:与加扰器结构相同,只要种子和复位时刻一致 module descrambler_lfsr #( parameter N = 15, parameter SEED = 15'h7FFF, parameter TAP_MSB = 14, parameter TAP_2ND = 13 )( input wire clk, input wire rst_n, input wire bit_valid, input wire frame_sync, // 帧头检测后的输出 input wire data_in, output reg data_out ); reg [N-1:0] lfsr; wire pn_bit = lfsr[TAP_MSB]; wire fb_bit = lfsr[TAP_MSB] ^ lfsr[TAP_2ND]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin lfsr <= SEED; data_out <= 1'b0; end else if (bit_valid) begin if (frame_sync) begin lfsr <= SEED; end else begin lfsr <= {lfsr[N-2:0], fb_bit}; end data_out <= data_in ^ pn_bit; // 同样的异或,还原原始数据 end end endmodule

需要强调的是,加扰器和解扰器的种子、多项式参数、复位时序必须完全一致,任何一个参数不一致,解扰输出就全是乱的。代码里SEED用了全1,这是工程惯例,因为全零状态会让LFSR锁死,后面第4章会详细讲。bit_valid信号是数据有效脉冲,在高吞吐链路里通常跟着数据总线走;frame_sync是帧头脉冲,必须在帧的第一个有效数据位同一拍拉高。这两个时序条件在集成时最容易出错,尤其是从帧同步器出来的frame_sync,往往会比数据晚一拍或者早一拍,必须仔细对齐。

3.3 同步与复位机制:在帧边界锁定两端状态

帧同步加扰的关键前提是收发两端LFSR状态完全对齐。接收端怎么知道该在哪一拍复位?通常靠帧结构里的同步字。成帧器检测到同步字后,产生一个帧头指示脉冲,这个脉冲就是frame_sync。加扰器在发送端用帧定位器产生的脉冲复位,解扰器在接收端用帧同步器输出的脉冲复位,两边只要对准一次,后续就持续对齐。

这里有一个容易被忽略的细节:如果同步字检测发生误锁,解扰器会在错误的比特位置复位LFSR,输出立刻变成乱码。所以帧同步器本身需要一定的保护逻辑,最常见的是连续检测多个同步字才进入锁定状态,连续丢失多个同步字才进入失锁状态。不要把加扰器的复位直接接到原始同步字检测输出上,至少要做一次“连续确认”再放行。

另一个常见做法是“自由运行加扰”,即LFSR不复位,一直往下跑。发送端不管帧结构,只把LFSR输出和每个数据比特异或;接收端也不复位,让LFSR自由运行。这种方式有个严重问题:两端LFSR初始相位如果不一致,解扰输出就永远不对。所以自由运行加扰必须配合帧同步字来实现相位捕获,复杂度更高。我个人的工程经验是,只要链路有成帧机制,就优先用帧同步加扰,复位逻辑简单,调试时一眼能看出对齐关系;只有链路没有帧结构、纯粹是连续比特流时,才考虑自同步加扰方案。

4. 参数怎么设:多项式、种子、同步字与自同步链路

4.1 三个必调参数及其对链路的影响

加扰器的参数看起来不多,真正影响链路行为的就三个:生成多项式、种子、同步机制。

生成多项式决定PN序列的周期和相关特性。周期越长的m序列在统计上越接近理想随机噪声,但这不代表阶数越高越好。高次多项式让LFSR寄存器变多,反馈抽头变多,逻辑延迟变大,在高串行速率下可能成为时序瓶颈。实际选型时,先确认链路类型:低速点对点用7级或9级足够,SDH级别的接口直接沿用行业标准的15级多项式,存储接口可能需要23级甚至更高来对抗重复Pattern。要提醒的是,多项式不能随意拼凑,必须保证是本原多项式,否则序列周期会大幅缩短,加扰效果断崖式下降。

种子决定PN序列的相位偏移。加扰器和解扰器用同一个种子,再配合同一时刻复位,才能生成一致的密钥流。如果种子变得可配置,那就要把种子的同步纳入链路建立流程。比如有的系统用“种子交换握手”来实现相位对齐,这在某些测试仪表里能看到,但对普通数据传输链路来说,额外引入握手开销并不划算。

同步字选择影响的是误锁概率和同步建立时间。同步字越长,误锁概率越低,但帧头开销越大;同步字太短,加扰器还没完成对齐就被噪声干扰误触发。若帧结构允许,同步字最好选择自相关特性好的码型,避开和数据区常见Pattern重复的组合,这样帧头检测器的相关峰值更尖锐,抗误锁能力更强。

参数影响面常见取值逻辑
生成多项式序列周期、频谱质量、时序成本按行业先例选,验证同频干扰
种子相位对齐、初始状态避免全零,收发一致
同步字误锁概率、同步时间选低自相关旁瓣的码型

4.2 种子陷阱:全零状态和相位错位

LFSR最经典的坑是全零状态。N级LFSR一旦进入全零,无论反馈函数是什么,移位后依然全零,输出永远是0,加扰器退化成直通。更隐蔽的是,如果多项式不是本原多项式,LFSR可能落入某个短周期循环,PN序列周期缩短,随机性大打折扣。所以种子选择要避开全零,工程上最省事的做法是用全1种子,这也是很多通信标准采用的方式。

种子还有一个容易被忽视的问题:加扰器和解扰器“各自用同一个种子”不等于“两边的序列状态一致”。如果复位信号到达两侧的时间差了一个时钟周期,哪怕种子相同,解扰输出的前几个比特也是错的。我曾遇到一个案例,接口IP集成在链路里,单测通过,联调时偶发失步,抓波形发现帧同步脉冲在接收链路里被插入了两个周期的流水线延迟,等到解扰器复位时,数据已经过去了两个比特,所有后续输出全部错位。解决方法是把解扰器的复位延迟对齐到数据的流水线深度,也就是给LFSR复位信号补上等量延迟。

如果你在调试时发现解扰输出“大部分正常、偶尔一段乱码”,先别怀疑多项式,先把帧同步脉冲和数据的对齐关系拉出来看。这个现象十有八九是同步时机抖动,不是算法问题。

4.3 自同步加扰:不需要帧同步的另一条路

自同步加扰不依赖帧结构,它利用多项式除法原理,把加扰输出反馈回移位寄存器,接收端用收到的加扰数据做同样的反馈,两边状态自动对齐。典型结构是发送端将输出反馈进移位寄存器,接收端同样将输入移入寄存器。下面是x^7+x^6+1的示例。

// 自同步加扰器:输出反馈回移位寄存器 module ss_scrambler #( parameter N = 7, parameter SEED = 7'h7F )( input wire clk, input wire rst_n, input wire bit_valid, input wire data_in, output reg data_out ); reg [N-1:0] sr; wire fb = sr[6] ^ sr[5]; // x^7+x^6+1的反馈 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sr <= SEED; data_out <= 1'b0; end else if (bit_valid) begin data_out <= data_in ^ fb; sr <= {sr[N-2:0], data_out}; // 注意:移入的是加扰后输出 end end endmodule
// 自同步解扰器:移入的是接收到的加扰数据 module ss_descrambler #( parameter N = 7, parameter SEED = 7'h7F )( input wire clk, input wire rst_n, input wire bit_valid, input wire data_in, output reg data_out ); reg [N-1:0] sr; wire fb = sr[6] ^ sr[5]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sr <= SEED; data_out <= 1'b0; end else if (bit_valid) begin data_out <= data_in ^ fb; sr <= {sr[N-2:0], data_in}; // 移入的是收到的加扰数据 end end endmodule

两个模块的核心差异只有一行:加扰器把输出移入寄存器,解扰器把输入移入寄存器。因为收发两端看到的是同一条加扰数据序列,所以寄存器状态自然同步,不需要帧复位。代价是错误传播:链路中翻转一位,这一位进入解扰器寄存器后,会在每个反馈抽头位置各输出一次错误,加上直接异或的那一位,最多污染后续N+1个比特。所以在要求误码性能极高的链路上,自同步加扰会用较低阶多项式来限制错误扩散范围,或者干脆改用帧同步加扰。

5. 加扰与解扰常见问题:从失锁到误码蔓延

5.1 解扰输出全乱码,链路已锁定却没数据

现象:接收端CDR已经锁定,帧同步器也报告同步,但解扰后的数据全是乱码,业务根本无法恢复。

原因:加扰器和解扰器的LFSR状态没对齐。常见诱因是种子不一致、复位信号差了一拍、或者解扰器复位时序没和帧头数据对齐。帧同步器报同步只能说明帧头找到,不代表加扰器状态已经复位,这是两个独立环节。

解决:先把两端的复位信号和种子配到完全一致,再用示波器或逻辑分析仪把发送端frame_sync、接收端frame_sync和数据总线一起抓出来,确认在帧头位置两侧复位精确发生在同一比特边界上。如果确认一致,再检查复位信号是否被其他逻辑插入流水延迟,补拍即可。

5.2 自同步链路突发误码,BER超过指标

现象:链路静态误码率很低,但说到突发误码测试就超标,而且突发长度和多项式阶数有对应关系。

原因:自同步解扰器的错误传播特性。链路里一个比特翻转,会在解扰器输出端产生多项式抽头数个错误,看起来就像一次突发误码。误码仪统计的是线路误码,但实际业务看到的误码被放大了好几倍。

解决:两个方向。一是确认链路里是否有物理层去加重、均衡参数没调好,先消除源头误码;二是评估是否能用帧同步加扰替代自同步加扰,切断错误传播。如果帧结构和同步字已经存在,优先换帧同步加扰。如果链路无法承担帧同步开销,就只能在FEC上补预算,把突发误码消化在纠错能力之内。

5.3 复位时序出错,上电后首帧不稳定

现象:设备上电后第一帧偶尔解出乱码,第二帧以后恢复正常;冷启动大概率必现,热复位概率低。

原因:上电时加扰器和解扰器的复位释放时刻不同步。有的模块用异步复位,释放时差被电路失配放大,导致一边先跑起来、另一边还在复位,前几帧的PN序列错位。热复位时两个模块可能处于相近的复位释放顺序,所以偶发性更高。

解决:把两个模块的复位改成同步释放,或者让帧同步器在检测到前几个正常帧之后再使能解扰器的数据输出,给状态建立留出几个帧的余量。更稳妥的做法是加一个“加扰状态建立计数器”,复位后至少计数若干个有效帧才允许解扰数据通过。这个逻辑在协议里不要求,但工程上能省掉一大半上电联调的痛苦。

5.4 位序不一致,看起来正常却全是废数据

现象:单端自测一切正常,两个设备对接时解扰输出偶尔能解出几个看起来合理的字符,但整体数据完全对不上,抓波形又看不出明显错误。

原因:收发两端对“第一个数据位”的定义不一致。有的系统按MSB先发,有的按LSB先发,而LFSR的反馈抽头和输出位取的是固定位置的比特。位序不一致时,LFSR产生的PN序列和数据位的对应关系错位,解扰结果就像被“逐比特倒序”处理过,单端自测永远测不出来。

解决:联调前先确认链路规范里对位序的定义。如果规范没写明,做一个全0/全1加交替模式的测试码,比如0xAAAA、0x5555,通过这个Pattern在示波器上核对首比特位置。这个坑在自研IP对接时出现频率很高,而且一旦出现就是“两边都觉得对方错了”的僵局,趁早把位序写进接口文档比调试省事得多。

5.5 频谱有尖峰,加扰没有达到白化效果

现象:加扰器已经工作,眼图也正常,但用频谱仪观察时输出依然在某几个频点出现明显尖峰,和纯粹的PN序列频谱有明显差异。

原因:源数据模式和PN序列之间存在相关性。最典型的是源数据里有一个固定频率分量,恰好和PN序列的某个谐波频率重叠;或者是加扰器没有把LFSR复位时机和数据模式对齐,导致周期性分量没有被完全打散。

解决:先收集实际业务的数据模式,在仿真环境里用真实数据激励加扰器,对比加扰前后的功率谱密度。如果尖峰仍然存在,考虑级联两个不同长度的LFSR做二级加扰,或者更换多项式阶数错开谐振频点。这一项应该放在设计阶段做,不要等到整机测试才去查频谱,那时候能调整的余地已经很有限了。

6. 验证加扰器:用游程统计和软件模型把好最后一道关

加扰器做得对不对,不能只看“解扰后数据对不对”。数据对,只能说明你做了一个可逆变换;数据不对,那你连可逆都没做到。更严格的验证要看统计特性,也就是加扰输出是否真的达到白化效果。最基本的定量指标是游程长度分布:在理想随机序列里,连续出现k个相同比特的概率约是 2^(-k)。我习惯在RTL到板级之前,先用一个几百行的Python脚本来做比特级参考模型,把LFSR、加扰、信道误码、解扰全链路跑一遍,同时统计游程分布。

# 验证加扰输出游程分布:长游程出现的概率应接近 2^{-k} def gen_prbs(nbits, seed=0x7FFF, taps=(14, 13)): lfsr = seed out = [] for _ in range(nbits): pn = (lfsr >> taps[0]) & 1 fb = ((lfsr >> taps[0]) ^ (lfsr >> taps[1])) & 1 lfsr = ((lfsr << 1) | fb) & ((1 << 15) - 1) out.append(pn) return out def run_stats(bits): cur = bits[0] run_len = 1 counts = {} for b in bits[1:]: if b == cur: run_len += 1 else: counts[run_len] = counts.get(run_len, 0) + 1 cur, run_len = b, 1 total = sum(counts.values()) for k in range(1, 12): observed = counts.get(k, 0) / total if total else 0 expected = 2 ** (-k) print(f"run_len={k:2d} observed={observed:.5f} expected={expected:.5f}") bits = gen_prbs(100000) run_stats(bits)

这段脚本里,gen_prbs用给定的种子和反馈抽头生成PN序列,run_stats统计游程长度并和理想概率对比。如果observed和expected差得不多,说明LFSR本身是合格的;如果差距很大,要么是抽头配错,要么是序列截断导致统计窗口不够。跑完统计脚本之后,我会再用同一个模型去生成加扰器和解扰器的激励,把RTL仿真里的输出和Python模型的输出逐比特比对,确认RTL在时序上完全复现了参考模型。

板级验证阶段的做法是:先用链路层产生PRBS测试码,经过加扰后打满整条物理链路,接收端解扰后把数据和原来的PRBS比较,误码仪统计BER。这样做能验证的不只是加扰器本身,还包括收发两端同步、位序、复位对齐这些集成问题。如果整个回环能连续跑24小时无误码,再上业务流量,加扰部分基本就算稳了。我现在的习惯是把上面的游程统计脚本每次都跑一遍,虽然它只能证明“LFSR统计上像随机序列”,但就这个简单的检查,已经帮我抓住好几次抽头配置和位序搞反的早期问题。希望这些经验能帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进