ARTICLE DETAIL

资讯详情

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

LFSR线性反馈移位寄存器:从原理到Verilog实现与避坑指南

LFSR线性反馈移位寄存器:从原理到Verilog实现与避坑指南 1. 从一个“伪随机数”的坑说起几年前做一个嵌入式项目需要给传感器数据加一点随机扰动避免多个节点同时上报造成信道拥堵。当时图省事直接用了标准库的rand()结果发现每次上电后节点发出的序列完全一样——因为没播种种子固定为1。后来改成用 ADC 采集悬空引脚的噪声做种子效果是好了一些但悬空引脚在某些板子上读出来是稳定的直流电平序列又退化了。那会儿我才认真去翻线性反馈移位寄存器LFSR的资料。LFSR 这个东西说白了就是一个用移位和异或搭起来的“伪随机序列发生器”它不需要乘法器、不需要查表、不需要复杂的状态机几个触发器加一两个异或门就能跑出周期极长的 0/1 序列。在通信扰码、CRC 校验、BIST 内建自测试、扩频通信、白噪声生成这些场景里LFSR 几乎是标配。它的核心就两个东西反馈多项式和初始状态种子。多项式选得好周期能到 2^n - 1选得不好跑几十拍就循环了。这篇文章我打算把 LFSR 从数学原理到 Verilog 实现、从多项式选取到实际踩过的坑完整地捋一遍。不管你是刚学数字电路的学生还是正在做通信基带、做 FPGA 的工程师只要你想把 LFSR 用对、用稳这篇内容应该都能帮上忙。我会尽量少堆公式多用“人话”和实际代码来说明问题。2. LFSR 到底在做什么核心原理拆解2.1 移位寄存器 反馈 状态机先抛开“线性反馈”这个听起来很唬人的词。LFSR 的本质就是一个有限状态机状态由寄存器里当前存的那几个 bit 决定。每个时钟沿到来时所有 bit 向右或向左移动一位空出来的那一位由“反馈函数”算出来填进去。最简单的形式是这样的假设有一个 4 位寄存器初始值1001。每个时钟周期右移一位最高位由最低位和某个中间位异或得到。比如反馈抽头在第 0 位和第 1 位next_bit reg[0] ^ reg[1] reg {next_bit, reg[3:1]}跑几拍看看周期寄存器状态反馈位01001-111001^01201100^00300110^11410011^10第 4 拍回到了1001周期只有 4。4 位寄存器理论上最多有 15 个非零状态全零状态是死循环后面会讲但这里只用了 4 个说明反馈抽头选得不好。这就引出了 LFSR 最核心的问题抽头怎么选才能让周期最长答案就在反馈多项式上。2.2 反馈多项式把电路翻译成数学把上面的电路用多项式表示寄存器的每一位对应 x 的幂次最低位是 x^0最高位是 x^3。反馈抽头在第 0 位和第 1 位对应的多项式就是f(x) x^4 x^1 x^0一般写成f(x) x^4 x 1。这个多项式的“系数”就决定了哪些位参与异或。常数项 1 是必须有的它代表反馈回路始终存在。LFSR 的周期由这个多项式决定。如果多项式是本原多项式primitive polynomial那么 n 位 LFSR 的周期就是 2^n - 1。比如x^4 x 1就是一个 4 次本原多项式用它做反馈周期是 15。为什么是 2^n - 1 而不是 2^n因为全零状态是吸收态。如果寄存器全是 0反馈位算出来还是 0状态永远停在 0出不来。所以有效的非零状态只有 2^n - 1 个本原多项式能让 LFSR 遍历所有这些状态。2.3 斐波那契 vs 伽罗瓦两种拓扑结构LFSR 有两种常见的实现结构名字听起来很学术其实区别就在异或门放的位置。斐波那契结构Fibonacci LFSR也叫 Many-to-One。所有抽头位的值异或后反馈到最高位或最低位的输入端。上面那个例子就是斐波那契结构。它的特点是异或门集中在一个地方抽头多的时候组合逻辑路径会比较长影响最高工作频率。伽罗瓦结构Galois LFSR也叫 One-to-Many。异或门分散在寄存器链中间每个抽头位置放一个异或门反馈位同时送到多个位置。它的特点是组合逻辑路径短适合高速设计但状态和斐波那契结构不是一一对应的同样的多项式在两种结构下产生的序列是“镜像”关系。我用一个 4 位、多项式x^4 x 1的例子对比一下斐波那契右移抽头 0 和 1always (posedge clk) begin if (rst) reg 4b1001; else reg {reg[0] ^ reg[1], reg[3:1]}; end伽罗瓦右移抽头对应 x^1 和 x^0always (posedge clk) begin if (rst) reg 4b1001; else begin reg[3] reg[0]; reg[2] reg[3]; reg[1] reg[2] ^ reg[0]; reg[0] reg[1] ^ reg[0]; end end伽罗瓦结构里reg[0]是反馈源它同时异或到reg[1]和reg[0]的下一拍值上。实际工程中如果跑几百 MHz 以上我一般优先选伽罗瓦结构时序更容易收敛。注意两种结构的多项式表示方式有细微差别。斐波那契结构的多项式直接对应抽头位置伽罗瓦结构的多项式需要做一点“翻译”通常是把斐波那契多项式反过来写。选型时别把两者的抽头搞混否则周期会完全不对。3. 本原多项式怎么选从查表到验算3.1 为什么必须是本原多项式前面说了本原多项式保证周期最大。那什么是本原多项式严格定义是在 GF(2) 上一个 n 次不可约多项式如果它的根是 GF(2^n) 的本原元它就是本原多项式。这话太绕了换个说法不可约不能分解成两个次数更低的多项式乘积。比如x^4 x^2 1 (x^2 x 1)^2可约不能用。本原以它为特征多项式的 LFSR周期达到 2^n - 1。不是所有不可约多项式都是本原的。比如x^4 x^3 x^2 x 1是不可约的但它的周期只有 5不是本原多项式。3.2 常用本原多项式速查实际工程里没人每次都从头推导都是查表。下面这张表是我常用的几个位宽对应的本原多项式格式是十六进制掩码方便直接写进代码位宽 n本原多项式抽头掩码含 x^n 项周期4x^4 x 10x13158x^8 x^6 x^5 x^4 10x17125516x^16 x^14 x^13 x^11 10x1D8006553532x^32 x^22 x^2 x 10x80200003约 4.29e9掩码的用法最低位对应 x^0最高位对应 x^n。比如 0x13 二进制10011对应 x^4 x^1 x^0正好是x^4 x 1。对于 32 位0x80200003展开是 bit31、bit21、bit1、bit0 置位对应 x^32 x^22 x^2 x 1。这个多项式在软件实现里很常用因为抽头少异或操作快。3.3 自己验算周期的方法如果你需要的位宽不在表里或者想验证某个多项式是不是本原的可以写个小脚本暴力验算。思路很简单用该多项式跑 LFSR看多少拍后回到初始状态。def lfsr_period(taps, n, seed1): state seed period 0 while True: feedback 0 for t in taps: feedback ^ (state t) 1 state ((state 1) | feedback) ((1 n) - 1) period 1 if state seed: return period # x^4 x 1抽头 0 和 1 print(lfsr_period([0, 1], 4)) # 输出 15这段代码跑 4 位瞬间出结果。16 位最多跑 65535 次也很快。32 位就不适合暴力跑了得用数学方法判断本原性或者直接查权威表格。实操心得我曾经用过一个网上抄来的 16 位多项式跑出来周期只有 255查了半天才发现那个多项式是可约的。后来养成习惯任何新多项式上线前先用小脚本验一遍周期确认是 2^n - 1 再用。4. 从零实现一个可配置的 LFSR 模块4.1 接口设计与参数化下面这个 Verilog 模块是我在多个项目里复用过的支持斐波那契和伽罗瓦两种模式位宽和抽头都参数化。先看接口module lfsr #( parameter WIDTH 16, parameter TAPS 16hD800, // 抽头掩码不含 x^n 项 parameter SEED 16hACE1, parameter GALOIS 1 // 1伽罗瓦0斐波那契 )( input wire clk, input wire rst_n, input wire enable, output wire [WIDTH-1:0] dout );TAPS参数用掩码表示比如 16 位多项式x^16 x^14 x^13 x^11 1去掉 x^16 项后抽头是 bit14、bit13、bit11、bit0掩码就是0x6801。这样写比用数组更紧凑综合工具也能直接优化。4.2 斐波那契结构的实现斐波那契结构逻辑最直观把所有抽头位异或结果移入最高位。generate if (!GALOIS) begin : fib wire feedback; assign feedback ^(dout TAPS); // 按位与后做归约异或 always (posedge clk or negedge rst_n) begin if (!rst_n) reg_fib SEED; else if (enable) reg_fib {reg_fib[WIDTH-2:0], feedback}; end assign dout reg_fib; end^(dout TAPS)这个写法很妙dout TAPS把非抽头位清零然后归约异或^把所有位异或起来等价于只对抽头位做异或。综合出来就是一棵异或树比写一堆^更简洁。4.3 伽罗瓦结构的实现伽罗瓦结构需要逐位处理因为异或门分散在链中间。下面是一个通用的写法else begin : gal reg [WIDTH-1:0] reg_gal; wire fb reg_gal[0]; // 反馈源是最低位 integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) reg_gal SEED; else if (enable) begin reg_gal[WIDTH-1] fb; for (i 0; i WIDTH-1; i i 1) begin if (TAPS[i]) reg_gal[i] reg_gal[i1] ^ fb; else reg_gal[i] reg_gal[i1]; end end end assign dout reg_gal; end endgenerate注意伽罗瓦结构的抽头掩码和斐波那契不完全一样。对于同一个多项式伽罗瓦结构通常把抽头位置“镜像”过来。比如斐波那契用 bit14、bit13、bit11、bit0伽罗瓦可能用 bit1、bit3、bit4、bit15。具体对应关系取决于移位方向实际使用时最好用仿真确认周期。4.4 仿真验证周期和序列正确性写完模块第一件事是跑仿真确认周期。下面是一个简单的 testbench 片段initial begin rst_n 0; #10 rst_n 1; enable 1; #1000000; $finish; end // 在仿真中记录状态检查是否回到 SEED always (posedge clk) begin if (dout SEED past_reset) $display(Period detected at time %t, $time); end对于 16 位 LFSR周期应该是 65535。仿真跑 70000 个周期看是否在 65535 拍后回到种子。如果提前回到种子说明多项式不是本原的或者抽头掩码写错了。踩坑记录伽罗瓦结构的抽头掩码我曾经直接照搬斐波那契的结果周期只有几百。后来用 Python 脚本把两种结构的抽头对应关系算出来才发现伽罗瓦的抽头是斐波那契的“反向”。具体来说如果斐波那契抽头是 T伽罗瓦的抽头掩码是 T 的位反转再右移一位。这个细节很多资料都不提但实际写代码时非常关键。5. 常见问题与排查技巧实录5.1 为什么我的 LFSR 周期不对这是最常见的问题原因通常有三个第一多项式不是本原的。很多人从网上随便抄一个多项式没验证就用。比如x^8 x^4 x^3 x^2 1看起来挺像样但它不是本原的周期只有 51。解决办法用 3.3 节的脚本验一遍或者查权威的本原多项式表。第二抽头掩码写错。比如把 bit0 漏掉了或者把 x^n 项也算进去了。掩码里不应该包含 x^n 项因为 x^n 对应的是反馈位本身不是寄存器里的位。检查方法把掩码打印出来数一数置位的个数和多项式里的项数对比。第三全零状态。如果种子设成了 0LFSR 永远出不来。解决办法种子必须非零。可以在复位时加一个判断如果种子是 0自动改成 1。5.2 伽罗瓦和斐波那契的序列为什么不一样同一个多项式两种结构产生的序列是“镜像”关系不是完全一样。具体来说斐波那契结构从最高位输出伽罗瓦结构从最低位输出两者输出的序列在时间上是反的。如果你需要和某个标准协议对接一定要确认协议用的是哪种结构否则对不上。5.3 高速设计下的时序问题斐波那契结构在抽头多的时候异或树的延迟会成为关键路径。比如 32 位 LFSR 有 4 个抽头异或树是 3 级在 28nm 工艺下大概能跑 500 MHz 左右。如果要求更高频率有两个办法换成伽罗瓦结构异或门分散关键路径只有一级异或。在斐波那契结构里插入流水线寄存器把异或树切成两级。代价是输出序列会延迟一拍需要做相位对齐。5.4 常见问题速查表现象可能原因排查方法解决措施周期远小于 2^n-1多项式非本原用脚本验算周期换本原多项式周期只有 1种子为 0检查复位值种子设为非零序列和预期不符抽头掩码错误打印掩码对比多项式修正掩码伽罗瓦结构周期不对抽头未做镜像对比两种结构抽头按镜像关系重算高速下时序违例异或树太长看时序报告换伽罗瓦或插流水线输出全 1 或全 0反馈逻辑被综合优化掉检查综合日志加keep属性或改写法独家技巧在 FPGA 上调试 LFSR 时可以用 ILA集成逻辑分析仪抓取寄存器状态设置触发条件为“状态等于种子”这样能直接看到周期。比跑仿真快得多尤其适合在板级调试阶段快速定位问题。6. 几个真实场景下的 LFSR 应用6.1 通信扰码让数据频谱更“白”在串行通信里如果发送的数据长期是 0 或 1接收端的时钟恢复电路会失锁。解决办法是用 LFSR 生成一个伪随机序列和发送数据异或把数据“打散”。接收端用同样的 LFSR 再异或一次就能恢复原始数据。这个场景对 LFSR 的要求是收发双方必须严格同步。通常的做法是发送端在帧头里插入一个已知的同步字接收端检测到同步字后把 LFSR 复位到约定种子之后就能逐位对齐。如果中间丢了一位整个后续数据都会错所以通信协议里通常还有 CRC 做校验。6.2 CRC 校验LFSR 的“变体”CRC 的本质就是一个带输入数据的 LFSR。普通 LFSR 的反馈只来自寄存器内部CRC 的反馈还异或了输入数据位。多项式选得好CRC 能检测出所有单比特错、双比特错、奇数个错以及大部分突发错。以 CRC-16/CCITT 为例多项式是x^16 x^12 x^5 1对应掩码0x1021。实现时每个时钟周期把数据位异或进反馈路径wire fb crc[15] ^ data_bit; crc {crc[14:0], 1b0} ^ (fb ? 16h1021 : 16h0000);这种写法比逐位异或更高效综合出来是一条异或链加一个条件异或。实际工程中CRC 通常用查表法在软件里实现但在高速硬件里LFSR 形式的 CRC 更常见。6.3 BIST 内建自测试用 LFSR 生成测试向量芯片流片后要做自测试需要给被测电路灌入大量测试向量。用 LFSR 生成伪随机向量比用 ROM 存储固定向量省面积。LFSR 的周期足够长能覆盖大部分故障模型。这个场景对 LFSR 的要求是种子和多项式要选得让向量覆盖率高。通常会用多个不同种子的 LFSR 并行或者用“加权 LFSR”调整 0/1 的比例。如果被测电路对某些位有偏置要求还会用“相位偏移”技术从同一个 LFSR 的不同抽头取输出生成多个不相关的序列。6.4 白噪声生成音频和射频里的应用在音频处理里LFSR 生成的伪随机序列经过低通滤波可以做成“白噪声”用于测试或音效。在射频里LFSR 用于生成扩频码比如 GPS 的 C/A 码就是一个 10 位 LFSR多项式是x^10 x^3 1周期 1023。这个场景对 LFSR 的要求是序列的统计特性要好。本原多项式生成的序列在周期内 0 和 1 的个数只差 1自相关函数接近冲激函数互相关也小。如果多项式选得不好序列会有周期性成分听起来像“嗡嗡”声而不是“沙沙”声。7. 写在最后一些个人体会LFSR 这个结构看起来简单但真正用起来细节非常多。我踩过的坑包括多项式抄错、抽头掩码漏位、伽罗瓦和斐波那契搞混、种子设成 0、高速下时序不收敛。每一个坑都让我多花半天到一天去排查。现在我的习惯是任何 LFSR 上线前先用 Python 脚本验周期再用 Verilog 仿真跑 2^n 个周期确认最后在板子上用 ILA 抓一次实际波形。三步走完基本不会出问题。另外如果你要做的是安全相关的应用比如加密或认证LFSR 本身是不够的。它的线性特性意味着只要知道 2n 个连续输出位就能通过解线性方程组推算出整个序列。所以 LFSR 通常只用于扰码、测试、扩频这些非安全场景。真要做加密得用非线性反馈移位寄存器NFSR或者分组密码。最后分享一个小技巧如果你需要多个不相关的伪随机序列不用实例化多个 LFSR可以从同一个 LFSR 的不同抽头取输出。比如一个 32 位 LFSR取 bit0、bit7、bit15、bit23 作为四个独立输出它们之间的互相关很小足够应付大多数测试场景。这样省面积也省功耗。
返回列表