ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

线性反馈移位寄存器LFSR原理、Verilog实现与工程应用

线性反馈移位寄存器LFSR原理、Verilog实现与工程应用 线性反馈移位寄存器LFSR是数字电路和通信系统里一个特别有意思的结构。我第一次接触它是在做一个误码率测试仪的项目当时需要产生一个伪随机序列来模拟信道噪声同事扔给我一段Verilog代码说“用LFSR就行”。我打开一看就一个移位寄存器加几个异或门心想这玩意儿能产生随机数后来深入研究发现这个看似简单的结构背后有一套完整的数学理论支撑而且它的应用远不止产生伪随机序列这么简单——从CRC校验、数据加扰、扩频通信到内建自测试到处都有它的身影。这篇文章主要面向有一定数字电路基础、想真正搞懂LFSR工作原理并动手实现的读者。我会从最基本的移位寄存器讲起逐步深入到反馈多项式的数学本质、本原多项式的判定方法、不同抽头结构对性能的影响最后给出完整的Verilog和Python实现代码。中间还会分享一些我在实际项目中踩过的坑比如抽头接错导致序列周期骤减、并行输出时相位关系搞混等问题。不管你是做FPGA开发、通信基带设计还是芯片验证这些内容应该都能帮到你。1. 从一个最简单的移位寄存器说起1.1 移位寄存器的基本结构移位寄存器本质上就是一串串联的触发器每个时钟沿到来时数据从上一级传到下一级。假设我们有一个4位的移位寄存器初始值设为1001每个时钟周期左移一位最低位补0那么它的状态变化是这样的时钟0: 1 0 0 1 时钟1: 0 0 1 0 时钟2: 0 1 0 0 时钟3: 1 0 0 0 时钟4: 0 0 0 0可以看到如果没有反馈数据移出去就没了最终寄存器会变成全0。这种结构只能做简单的数据延迟周期是有限的最多N个时钟就全部移出。LFSR的核心思想就是把移出去的那一位“拿回来”经过某种运算后补到空出来的位置上让寄存器永远不会变成全0只要初始值不是全0从而产生周期性的序列。1.2 反馈的引入从线性到非线性反馈的引入方式决定了LFSR的性质。最简单的反馈就是把最高位直接接到最低位的输入这叫“环形计数器”但它只能产生N个状态的循环周期太短。如果我们在反馈路径上加入异或运算把寄存器的某些位异或后再反馈回去就得到了线性反馈移位寄存器。“线性”这个词在这里的含义是反馈函数是寄存器状态的线性函数在GF(2)域上也就是模2加/异或运算。用数学表达就是f(x) c1x1 XOR c2x2 XOR ... XOR cn*xn其中ci是反馈系数取0或1xi是寄存器第i位的值。如果ci1表示第i位参与反馈称为一个“抽头”如果ci0表示不参与。为什么用异或而不是与、或因为异或运算是GF(2)域上的加法具有线性性质这使得LFSR的数学分析变得可行——我们可以用多项式理论来精确描述它的行为。如果用与门或或门做反馈就变成了非线性反馈移位寄存器NFSR分析起来会复杂得多虽然在某些密码学场景下NFSR更有优势但大多数工程应用还是首选LFSR。1.3 一个具体的4位LFSR例子拿一个经典的4位LFSR来说反馈多项式是x^4 x^3 1。这意味着第4位和第3位参与异或结果反馈到第1位最低位。假设初始状态是1001从左到右是第4位到第1位每个时钟周期左移时钟Q4 Q3 Q2 Q1反馈值(Q4 XOR Q3)01 0 0 1-10 0 1 11 XOR 0 120 1 1 00 XOR 0 031 1 0 00 XOR 1 141 0 0 11 XOR 1 0可以看到到第4个时钟时状态回到了1001周期是15不包括全0状态。对于4位LFSR最大周期是2^4 - 1 15这个例子刚好达到了最大周期。不是所有的反馈多项式都能达到最大周期只有本原多项式才行这个后面会详细讲。2. 反馈多项式LFSR的DNA2.1 多项式表示法的由来把LFSR的反馈连接关系用多项式来表示这个想法最早来自通信理论中的序列分析。具体来说对于一个N位LFSR如果第i位参与反馈即第i位的输出经过异或后反馈到输入端那么对应的多项式项就是x^(i-1)。所有参与反馈的位对应的项加起来再加上常数项1代表输入端的“1”就构成了特征多项式。举个例子前面说的4位LFSR第4位和第3位参与反馈对应的特征多项式就是x^3 x^2 1不对这里要注意索引方式。通常我们把最高位记为x^(N-1)最低位记为x^0。如果第4位最高位和第3位参与反馈那么多项式是x^3 x^2 1。但很多文献里习惯写成x^4 x^3 1这是把最高位的指数定为N。两种写法本质一样只是差一个x的幂次不影响周期性分析。我在看不同资料时经常被这个搞混建议你选定一种约定后就一直用下去别中途换。2.2 特征多项式与状态转移LFSR的每一个状态可以看作GF(2)上的一个N维向量。状态转移就是乘以一个N×N的矩阵称为伴随矩阵。特征多项式就是这个矩阵的特征多项式。根据线性代数理论矩阵的阶即状态转移的周期与特征多项式的性质密切相关。具体来说如果特征多项式是本原多项式那么LFSR的周期就是2^N - 1最大周期。如果不是本原的周期会小于这个值具体取决于多项式的因式分解情况。这就引出了一个关键问题怎么判断一个多项式是不是本原的2.3 本原多项式的判定条件一个N次多项式f(x)是本原多项式需要满足两个条件第一f(x)是不可约的也就是说它不能分解成两个次数更低的多项式的乘积在GF(2)上。这类似于整数中的质数概念。第二f(x)能整除x^(2^N - 1) 1但不能整除任何x^M 1其中M 2^N - 1。第二个条件等价于说f(x)的根在GF(2^N)中的阶是2^N - 1。这个判定在实际操作中不太方便直接计算通常我们会查表。文献里有现成的本原多项式表比如N4时是x^4 x 1N8时是x^8 x^4 x^3 x^2 1等等。我个人的经验是做项目时直接查表最省事别自己推导。但如果需要动态配置LFSR长度比如做一个可配置的CRC模块那就需要在代码里内置一张本原多项式查找表。Xilinx的XAPP052应用笔记里有一张很全的表从3位到168位都有可以直接拿来用。2.4 常见本原多项式速查下面列出一些常用的本原多项式方便你快速查阅位宽N本原多项式最大周期3x^3 x 174x^4 x 1155x^5 x^2 1316x^6 x 1637x^7 x 11278x^8 x^4 x^3 x^2 125516x^16 x^14 x^13 x^11 16553532x^32 x^22 x^2 x 12^32 - 1注意同一个N可能有多个本原多项式上表只列了其中一个。选择哪个通常看哪个抽头少异或门少硬件资源省或者哪个在FPGA里布线方便。3. Fibonacci与Galois两种抽头结构的对比3.1 Fibonacci结构多对一Fibonacci结构是最直观的LFSR形式多个抽头异或后反馈到输入端。前面举的例子都是这种结构。它的特点是反馈逻辑在输入端所有抽头的信号汇聚到一个异或树上每个时钟周期只有一位发生变化移入的新位关键路径可能较长如果抽头很多异或树延迟大用Verilog实现Fibonacci LFSR非常直接module lfsr_fibonacci #( parameter WIDTH 8, parameter TAPS 8b10111000 // 抽头掩码 )( input wire clk, input wire rst_n, input wire load, input wire [WIDTH-1:0] seed, output reg [WIDTH-1:0] q ); wire feedback; assign feedback ^(q TAPS); // 抽头位异或 always (posedge clk or negedge rst_n) begin if (!rst_n) q 8h01; // 复位到非零初值 else if (load) q seed; else q {q[WIDTH-2:0], feedback}; // 左移并反馈 end endmodule这段代码里TAPS是一个掩码哪一位是1就表示该位参与反馈。^(q TAPS)是缩减异或操作把所有抽头位异或起来。注意复位值不能是全0否则LFSR会锁死在全0状态出不来。3.2 Galois结构一对多Galois结构把反馈分散到寄存器的各个级之间而不是集中在输入端。具体来说反馈信号从输出端引出同时注入到多个中间节点。它的特点是反馈路径分散关键路径短适合高速设计每个时钟周期可能有多个位同时变化相同多项式的Galois实现和Fibonacci实现产生的序列是相同的只是相位可能不同Galois结构的Verilog实现稍微复杂一点module lfsr_galois #( parameter WIDTH 8, parameter TAPS 8b10111000 )( input wire clk, input wire rst_n, output reg [WIDTH-1:0] q ); wire feedback; assign feedback q[WIDTH-1]; // 最高位作为反馈源 integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) q 8h01; else begin q[WIDTH-1] feedback; for (i WIDTH-2; i 0; i i - 1) begin if (TAPS[i]) q[i] q[i1] ^ feedback; else q[i] q[i1]; end end end endmodule3.3 两种结构的选型建议在实际项目中选哪种结构我一般考虑这几个因素速度要求高选Galois。因为Galois结构的反馈路径被分散了关键路径通常只有一级异或门延迟而Fibonacci结构如果抽头多异或树可能有好几级。我在一个250MHz的通信接口项目里Fibonacci结构时序怎么都收敛不了换成Galois后轻松跑到300MHz。资源紧张选Fibonacci。Galois结构每个抽头位置都需要一个异或门而Fibonacci只需要一个多输入异或门。在ASIC设计里多输入异或门可以用一个复合门实现面积更小。代码可读性选Fibonacci。Fibonacci结构的代码更直观调试时看波形也更容易理解。Galois结构的状态变化不太直观仿真波形看起来比较“乱”。需要并行输出选Galois。Galois结构天然支持从多个抽头同时输出而Fibonacci结构如果要并行输出多个位需要额外的逻辑。4. LFSR的周期性与统计特性4.1 最大长度序列m序列的性质当LFSR使用本原多项式时产生的序列称为最大长度序列简称m序列。m序列有几个非常重要的性质平衡性在一个完整周期2^N - 1个比特中1的个数比0的个数多1。具体来说1有2^(N-1)个0有2^(N-1) - 1个。这个性质在扩频通信中很重要因为它保证了序列的直流分量接近零。游程特性游程是指连续相同比特的段。在m序列的一个周期中长度为k的游程1≤k≤N-2有2^(N-k-1)个其中0游程和1游程各占一半。长度为N-1的游程只有一个全0长度为N的游程也只有一个全1。移位相加性m序列和它的任意移位版本异或得到的结果还是同一个m序列的某个移位。这个性质是扩频通信中多址接入的基础。自相关特性m序列的自相关函数在零延迟处为1在其他延迟处为-1/(2^N - 1)。这个接近冲激函数的特性使得它非常适合用于同步和测距。4.2 周期与初始状态的关系一个常见的误解是LFSR的周期取决于初始状态。实际上对于给定的反馈多项式所有非零初始状态产生的序列周期都是一样的前提是多项式不可约。不同的初始状态只是同一个序列的不同相位偏移。但如果多项式不是本原的情况就复杂了。不同的初始状态可能落入不同的循环周期可能不同。比如多项式x^4 x^2 1可以分解为(x^2 x 1)^2它的状态空间会分裂成多个循环最长周期只有3。我在调试一个CRC模块时遇到过这个问题换了一个生成多项式后仿真跑了几万个时钟周期都没回到初始状态一开始以为是代码有bug后来查了一下发现那个多项式根本不是本原的周期远小于2^N - 1。所以选多项式时一定要确认它是本原的。4.3 全零状态的死锁问题LFSR有一个著名的“死锁”问题如果初始状态是全0那么反馈值永远是0寄存器永远保持全0出不来。这在硬件上电时可能发生因为触发器上电后的初始值是随机的有可能恰好是全0。解决方法有两个一是复位时强制加载一个非零种子值二是在反馈逻辑里加一个额外的项使得全0状态的下一个状态不是全0。第二种方法会破坏线性性质但可以保证自启动。我一般用第一种方法简单可靠。注意如果你用LFSR做加扰器全0种子会导致加扰失效数据直接透传。所以在协议里通常会规定种子值不能为全0或者用协议规定的固定种子。5. 从理论到代码完整实现与验证5.1 Python快速验证在写Verilog之前我习惯先用Python快速验证多项式和抽头配置是否正确。下面是一个通用的LFSR仿真函数def lfsr_sim(width, taps, seed, cycles): width: 寄存器位宽 taps: 抽头掩码整数第i位为1表示第i位参与反馈 seed: 初始状态非零 cycles: 仿真时钟数 state seed max_state (1 width) - 1 seen {} sequence [] for i in range(cycles): if state in seen: print(f周期 {i - seen[state]}在第{i}个时钟回到之前的状态) return sequence, i - seen[state] seen[state] i sequence.append(state) # 计算反馈位 feedback bin(state taps).count(1) % 2 # 左移并插入反馈位 state ((state 1) | feedback) max_state if state 0: print(警告进入全零状态LFSR死锁) return sequence, 0 return sequence, None # 测试8位LFSR本原多项式x^8 x^4 x^3 x^2 1 # 对应抽头第8位、第4位、第3位、第2位从1开始计数 # 掩码第7位、第3位、第2位、第1位从0开始计数 taps (1 7) | (1 3) | (1 2) | (1 1) seq, period lfsr_sim(8, taps, 0x01, 300) print(f实测周期: {period}理论最大周期: {2**8 - 1})运行这段代码如果周期是255说明多项式选对了。如果周期小于255要么多项式不是本原的要么抽头掩码写错了。我强烈建议每次换多项式都跑一遍这个验证比直接上板子调试快得多。5.2 Verilog可综合实现下面是一个参数化的LFSR模块支持Fibonacci和Galois两种模式并且内置了本原多项式查找表module lfsr #( parameter WIDTH 8, parameter MODE 0, // 0: Fibonacci, 1: Galois parameter [WIDTH-1:0] SEED 1 )( input wire clk, input wire rst_n, input wire enable, output wire [WIDTH-1:0] q ); // 本原多项式抽头表部分 function [WIDTH-1:0] get_taps; input integer w; begin case (w) 3: get_taps 3b110; 4: get_taps 4b1100; 5: get_taps 5b10100; 6: get_taps 6b110000; 7: get_taps 7b1100000; 8: get_taps 8b10111000; 16: get_taps 16b1011100000000000; default: get_taps {WIDTH{1b0}}; endcase end endfunction wire [WIDTH-1:0] taps get_taps(WIDTH); reg [WIDTH-1:0] state; wire feedback; generate if (MODE 0) begin : fib assign feedback ^(state taps); always (posedge clk or negedge rst_n) begin if (!rst_n) state SEED; else if (enable) state {state[WIDTH-2:0], feedback}; end end else begin : gal assign feedback state[WIDTH-1]; integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) state SEED; else if (enable) begin state[WIDTH-1] feedback; for (i WIDTH-2; i 0; i i - 1) begin if (taps[i]) state[i] state[i1] ^ feedback; else state[i] state[i1]; end end end end endgenerate assign q state; endmodule这个模块可以直接综合到FPGA里。enable信号用来控制LFSR是否推进方便做门控时钟或者与其他逻辑同步。5.3 仿真验证要点写testbench时我一般会检查这几个点第一复位后状态是否等于SEED且SEED不为0。第二连续运行2^N - 1个时钟后状态是否回到SEED。第三中间是否出现全0状态。第四如果使能信号拉低状态是否保持不变。下面是一个简单的testbench片段initial begin rst_n 0; enable 0; #100 rst_n 1; #100 enable 1; // 记录初始状态 $display(初始状态: %b, q); // 运行足够多的周期 repeat (300) (posedge clk); // 检查是否回到初始状态 if (q 8h01) $display(PASS: LFSR周期正确); else $display(FAIL: 状态%b, q); $finish; end6. 实际项目中的踩坑记录6.1 抽头顺序搞反导致周期骤减这是我踩过的最坑的一个问题。当时用的是一个16位LFSR多项式是x^16 x^14 x^13 x^11 1。我在代码里写抽头掩码时把第14位和第13位的位置搞反了写成了第13位和第14位。结果仿真跑出来的周期只有几百远小于65535。排查过程是这样的先确认多项式本身是本原的查表确认然后检查代码逻辑移位方向、异或逻辑都没问题最后逐位打印状态发现状态变化的模式不对。把抽头掩码打印出来和预期对比才发现位序搞反了。这个问题的教训是抽头掩码的位序一定要和多项式的索引方式严格对应。我现在的习惯是在代码注释里明确写出每一位对应多项式的哪一项比如// x^16 x^14 x^13 x^11 1 // 对应位: 15, 13, 12, 10 (从0开始计数) localparam TAPS (115) | (113) | (112) | (110);6.2 并行输出时的相位关系LFSR经常需要同时输出多个位比如做并行加扰。如果直接从寄存器的不同位引出这些位之间是有固定相位关系的不是独立的。我在做一个并行加扰器时需要8路并行输出一开始直接从8位寄存器的每一位引出结果发现这8路信号高度相关加扰效果很差。正确的做法是用8个不同相位的LFSR或者一个LFSR的8个不同抽头组合确保各路输出之间的相关性尽可能低。具体来说如果LFSR的周期是2^N - 1那么间隔大约(2^N - 1)/8的抽头之间的相关性会比较低。6.3 复位值的选择前面提到过全0死锁问题但即使避免了全0复位值的选择也有讲究。如果复位值太“规律”比如只有最低位是1前几个时钟的输出可能不够随机。在安全相关的应用里比如生成密钥流建议用协议规定的固定种子或者从真随机源加载种子。我在一个加密模块里用过从环形振荡器采集的随机数作为LFSR种子效果不错。但要注意环形振荡器的输出需要经过后处理比如异或多个采样值才能作为种子否则可能不够随机。6.4 时序收敛问题在高频设计中Fibonacci LFSR的异或树可能成为关键路径。我遇到过一个案例32位LFSR抽头有4个在200MHz下时序余量为负。解决方法有两个一是换成Galois结构把异或分散到各级二是在异或树中间插入流水线寄存器但这会改变LFSR的数学性质需要重新推导状态转移方程。我选择了换Galois结构因为不想改变LFSR的序列特性。换完之后时序余量变成了正0.5ns满足了要求。7. LFSR的典型应用场景7.1 伪随机序列生成这是LFSR最直接的应用。在通信系统的误码率测试中需要用伪随机序列PRBS来模拟随机数据。ITU-T O.150标准定义了多种PRBS模式比如PRBS7、PRBS15、PRBS23、PRBS31分别对应不同长度的LFSR。PRBS31的周期是2^31 - 1大约21亿个比特在10Gbps的速率下需要200多秒才重复一次足够满足测试需求。实现PRBS生成器时注意标准里规定的抽头位置和初始状态。比如PRBS7的多项式是x^7 x^6 1初始状态通常是全1不是全0。不同标准可能用不同的初始状态做协议一致性测试时要严格按标准来。7.2 CRC校验CRC循环冗余校验本质上就是一个LFSR只是输入数据会异或到反馈路径中。CRC的生成多项式就是LFSR的特征多项式。常见的CRC-32多项式是x^32 x^26 x^23 x^22 x^16 x^12 x^11 x^10 x^8 x^7 x^5 x^4 x^2 x 1对应的LFSR有多个抽头。用LFSR实现CRC时有两种结构串行和并行。串行结构每个时钟处理1个比特适合低速接口并行结构每个时钟处理多个比特比如8位、16位、32位适合高速接口。并行CRC的实现需要对LFSR的状态转移矩阵进行幂运算推导起来比较繁琐但网上有现成的工具可以自动生成代码。7.3 数据加扰在高速串行接口中比如PCIe、SATA、USB 3.0数据在发送前会经过加扰处理目的是打散连续的0或1便于接收端恢复时钟。加扰器通常就是一个LFSR发送端把数据和LFSR的输出异或接收端用相同的LFSR做逆运算。加扰用的LFSR通常是Galois结构因为速度要求高。种子值在协议里有明确规定接收端需要先同步LFSR状态才能正确解扰。同步过程通常是发送端发送一段已知的同步序列接收端用滑动相关来找到LFSR的相位。7.4 内建自测试BIST在芯片测试中LFSR可以用来生成测试向量也可以用来做输出响应分析签名分析。用LFSR生成测试向量的好处是硬件开销小而且测试向量的覆盖率高。签名分析则是把电路的输出压缩成一个短签名通过比较签名来判断电路是否有故障。BIST里的LFSR通常需要支持多种模式测试向量生成模式、签名分析模式、扫描链模式等。设计时要注意模式切换时的状态初始化问题避免残留状态影响测试结果。8. 进阶话题LFSR的变体与扩展8.1 非线性反馈移位寄存器NFSR前面提到过NFSR用非线性函数做反馈比如与、或、乘法等。NFSR的周期分析比LFSR复杂得多但它的线性复杂度更高在密码学中更有优势。Grain、Trivium等轻量级密码算法都用了NFSR。如果你要做密码学相关的应用纯LFSR是不够的因为Berlekamp-Massey算法可以在2N个比特内恢复出LFSR的结构和初始状态。通常的做法是LFSR和NFSR结合使用或者用多个LFSR的输出经过非线性函数组合。8.2 多速率LFSR在某些应用中需要LFSR支持多种速率。比如一个LFSR需要同时产生1Gbps和10Gbps的PRBS。实现方式有两种一是用多个不同时钟域的LFSR二是用一个高速LFSR然后通过抽取decimation得到低速序列。第二种方式需要注意抽取后的序列周期可能变短需要重新验证。8.3 可配置LFSR可配置LFSR允许运行时改变多项式和位宽这在协议兼容性测试中很有用。实现方式通常是用查找表存储多组抽头掩码通过配置寄存器选择。设计时要注意切换多项式时的状态初始化避免切换瞬间产生非法状态。我在一个多协议测试仪项目里实现过可配置LFSR支持从PRBS7到PRBS31的切换。关键点是切换时要先复位到新多项式的合法种子然后再使能输出。如果直接切换抽头而不复位可能会进入一个非预期的循环。9. 调试LFSR的实用技巧9.1 用Python做快速原型验证每次换多项式或抽头配置我都会先用Python跑一遍确认周期和序列正确后再写Verilog。Python的优点是修改方便可以快速尝试不同的配置。我通常会把常用的本原多项式存在一个字典里用的时候直接查。9.2 用逻辑分析仪抓波形上板调试时逻辑分析仪是必不可少的。我一般会抓LFSR的时钟、复位、使能、状态输出这几个信号。如果发现状态不对先检查复位值是否正确再检查使能信号是否正常最后检查反馈逻辑。9.3 用SystemVerilog断言做自动检查在验证环境中可以用SystemVerilog断言自动检查LFSR的行为。比如// 检查复位值 property p_reset; (posedge clk) !rst_n | q SEED; endproperty // 检查周期 property p_period; (posedge clk) disable iff (!rst_n) (q SEED) |- ##[1:$] (q SEED); endproperty这些断言可以在仿真中自动捕获异常比手动看波形效率高得多。9.4 注意仿真时间对于大位宽的LFSR比如32位完整周期仿真需要2^32 - 1个时钟这在仿真器里跑完是不现实的。我一般只仿真前几百个时钟确认状态转移逻辑正确然后通过数学分析确认周期。或者用Python验证周期Verilog只验证逻辑功能。10. 写在最后LFSR这个结构入门容易精通难。表面上看就是移位寄存器加异或门但背后的多项式理论、序列分析、应用技巧有很多值得深挖的地方。我在实际项目中最大的体会是理论指导实践实践验证理论。每次选多项式之前先用Python验证每次写完Verilog之后先用仿真确认每次上板之前先用逻辑分析仪抓波形。这三步做好了基本不会出大问题。另外LFSR的应用场景非常广泛不同场景对LFSR的要求不同。做通信的关心序列的随机性和相关性做测试的关心覆盖率和故障检测率做安全的关心线性复杂度和抗攻击能力。理解这些差异才能在设计时做出正确的取舍。如果你正在做LFSR相关的项目建议先把本原多项式的概念搞清楚这是所有应用的基础。然后根据具体需求选择合适的抽头结构最后用仿真和实测验证。遇到问题不要慌大部分问题都是抽头配置错误或者复位值不对导致的逐位排查总能找到原因。
返回列表