PRBS生成实现:线性反馈移位寄存器(LFSR)原理

PRBS怎么做出来的?说白了,核心就是线性反馈移位寄存器——LFSR。很多同学一听“线性”“反馈”就觉得难,其实它就是个会自己“算”的移位器。我刚开始接触时也觉得神秘,后来自己搭过一次电路,立马就明白了。

1. 什么是LFSR?

LFSR由一组移位寄存器和一组反馈逻辑组成。寄存器存储当前状态,每个时钟周期向右移一位,同时按照预设的多项式把某些位的值异或(XOR)后反馈到输入端。结果就是一串看起来随机的比特流。

关键点:LFSR生成的序列是确定性的——同样的初始状态(种子)永远得到同样的序列。它只是“看起来随机”,不是真随机。

2. 多项式与抽头位置

每个LFSR对应一个本原多项式(primitive polynomial)。比如我常用的PRBS7,多项式就是 \(x^7 + x^6 + 1\)。这里的7表示寄存器级数,\(x^6 + 1\) 表示从第6级和最后一级的输出做异或,反馈回去。

PRBS阶数 寄存器级数 n 本原多项式(常见) 最大序列长度
PRBS7 7 x⁷ + x⁶ + 1 2⁷ − 1 = 127 bits
PRBS9 9 x⁹ + x⁵ + 1 2⁹ − 1 = 511 bits
PRBS15 15 x¹⁵ + x¹⁴ + 1 2¹⁵ − 1 = 32767 bits
PRBS23 23 x²³ + x¹⁸ + 1 2²³ − 1 ≈ 8.39M bits
PRBS31 31 x³¹ + x²⁸ + 1 2³¹ − 1 ≈ 2.15G bits
选多项式的小经验:我个人习惯用本原多项式表里确认过的抽头位置。别自己瞎猜,猜错了序列周期会缩短,甚至卡死。之前有个同事自己改了抽头,结果周期从2^n-1缩到几百位,查了半天才发现。

3. 两种实现结构:Fibonacci vs Galois

同样是LFSR,电路结构有两种流派。你能在教科书里找到,但实际写代码时我只用其中一种。

  • Fibonacci(外部反馈型):多个抽头值异或后从输入端进入。寄存器是串行移位,反馈逻辑在外部。好处是直观,坏处是反馈路径较长,高频下时序容易出问题。
  • Galois(内部反馈型):异或门插在每个抽头对应的寄存器之间。反馈信号从最后一节出来,直接“跳”到中间各级。我以为这更难理解,但实际做高速设计时Galois性能更好,寄存器之间的关键路径更短。

我个人推荐用Galois结构写RTL代码——真的不容易出现时序违例。你也试试看。

4. 初始状态(种子)与全零陷阱

不管哪种结构,初始值不能全零。为什么?如果所有位都是0,异或来异或去还是0,序列卡住了。这点在调试时坑过不少人。

曾经有个项目,FPGA上电后PRBS输出全是0,查了两天。最后发现代码里初始化写死了全0,导致LFSR不工作。改成0x7F(PRBS7)就活了。记住:任何时候都要避开全零态!

选种子有讲究吗?除了避开全零,任何非零种子产生的序列只是相位不同,伪随机统计特性完全一样。你想想看,长度、游程分布都不变。

5. 序列长度与最大周期

n级LFSR最大周期是2ⁿ − 1。为什么减1?因为全零状态不出现。比如PRBS7有127个比特后开始重复。这就是它的“帧长度”。在实际误码率测试中,我们通常让PRBS连续跑,等接收端同步后再统计误码。

6. 实战:用Python生成PRBS7

不说虚的,上代码。这是我最常用的Galois型LFSR实现,用来验证链路性能。

def prbs7(seed=0x7F, n_bits=127):
    """
    生成 PRBS7 序列(Galois 结构)。
    多项式: x^7 + x^6 + 1  → 抽头位置 6,7
    """
    lfsr = seed & 0x7F          # 只保留低7位
    output_bits = []
    for _ in range(n_bits):
        # 输出最低位
        out_bit = lfsr & 1
        output_bits.append(out_bit)
        # 反馈计算:抽头 (bit6) XOR (bit6 与 bit7 的异或)
        # Galois: 移位后,若移出的位为1,则异或到抽头位置
        lsb = lfsr & 1
        lfsr >>= 1
        if lsb:
            lfsr ^= 0x43      # 0x43 = 0b1000011 对应抽头7和6
    return output_bits

# 试一下
seq = prbs7()
print(' '.join(str(b) for b in seq[:20]), '...')

跑一下,输出前20位:1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 1 0 1 0 1。你自己试试看,是不是和眼图仪上测到的一样?

注意:这段代码里0x43代表从高位算起的抽头位置。不同参考资料的多项式表示方向可能相反,写代码时务必确认你的位序。

7. LFSR结构图

下面这张图是PRBS7(Fibonacci结构)的示意图。红色方框是寄存器,蓝色是异或门,箭头表示数据流向。

PRBS7 LFSR 结构 (Fibonacci型) - 多项式 x⁷ + x⁶ + 1 D7 D6 D5 D4 D3 D2 D1 XOR 输出序列 抽头取自 D7 和 D6,异或后反馈到 D1 输入端。移位方向从左到右。 最高位 D7 为输出。
怎么用这张图? 对照代码看就清楚了。箭头方向是数据流动,异或门的位置就是反馈注入点。我最初画图时总把顺序画反,后来直接在纸上推演了几轮才彻底搞对。

8. 避坑清单

  • 种子不能全零——上面说了,反复说
  • 多项式务必用本原多项式,否则序列周期会短很多,误码率测试结果失效
  • 注意位序:文档里写“抽头7和6”,但实际硬件寄存器索引定义可能相反。我吃过这个亏,仿真和实测对不上。
  • Gilois结构里异或门的插入位置要算对。有个取巧的办法:移位后如果移出的最低位是1,才去异或抽头掩码。
  • 序列同步:接收端必须知道种子和多项式才能恢复出参考序列。通常发送端先发一段同步头(比如全1),接收端用相同LFSR锁定相位。

9. 回头看:为什么LFSR这么流行?

因为它硬件成本极低,几个触发器和异或门就能生成很长很长的伪随机码。相比查表存序列,LFSR不占存储。相比纯软件计算,它天生适合并行。你在FPGA或ASIC里用,一个时钟出一位,吞吐量极高。

我自己做高速SerDes测试时,就靠一个PRBS31的LFSR模块压链路。跑了三天三夜,误码率测到10⁻¹²级别,稳得很。你想想看,如果不是LFSR,光存32G比特的序列就要多少内存?

一句话总结:LFSR = 移位寄存器 + 本原多项式反馈 + 非零种子。记住这三样,任何PRBS都能徒手写出来。