PRBS的数学原理:多项式与反馈移位寄存器

说实话,很多人刚开始接触PRBS时,就被“多项式”、“本原多项式”这些词吓住了。我当时也是。但后来做了几个高速串行通信的项目,才发现这东西其实挺直观的——说白了就是一组移位寄存器,再加几个异或门,就能跳出伪随机数。好,今天咱们就把它扒开看看。

线性反馈移位寄存器 LFSR:PRBS的发动机

PRBS的核心结构叫线性反馈移位寄存器,简称LFSR。它长什么样?n个寄存器排成一排,每个时钟沿把数据向右移一位,最后根据某些抽头进行异或,再把结果喂回最左边。

举个例子,一个4级LFSR,多项式是 \( x^4 + x^3 + 1 \)。这里的指数对应寄存器的编号(从1开始),系数1表示有抽头。寄存器编号与多项式幂次的关系,我习惯画张图帮助理解。

4级LFSR结构示意图(多项式 x⁴ + x³ + 1) D₃ D₂ D₁ D₀ 输出 XOR 反馈 多项式 x⁴ + x³ + 1 → 抽头在 D₃(4) 和 D₂(上一位,实际对应 x³),再与 D₀(1) 异或 全1初始态后,序列周期 = 2⁴ – 1 = 15

图里画的还是挺清楚的:四个寄存器D₃~D₀,时钟沿来的时候右移一位,D₀的内容送出去,同时D₃和D₁的输出进异或门,结果再从左边补进来。嗯,这就是最简单的PRBS发生器了。

本原多项式:决定序列“好”与“坏”

不是随便找个多项式都能生成PRBS的。如果你挑错了多项式,序列周期可能很短,甚至卡死在某个值上。项目里最怕的就是这个——你以为在测试误码率,结果序列自己就重复了,测出来的全是假数据。

本原多项式就是能生成最大周期(2n-1)的那类多项式。什么样的多项式才是本原的?有一个数学判断办法:它得是既约的,并且能整除x2n-1+1,但不能整除任何更小的同类式子。这概念有点绕,但我一般直接查表。

阶数 n 本原多项式(二进制表示) 最大周期 常用PRBS类型
3 [3,1] → 0xB 7 PRBS7
7 [7,1] → 0x81 127 PRBS7
9 [9,5] → 0x211 511 PRBS9
15 [15,14] → 0x8001 32767 PRBS15
23 [23,18] → 0x8000 01 8,388,607 PRBS23
31 [31,28] → 0x8000 0001 2,147,483,647 PRBS31

我个人习惯把常用本原多项式记成一张卡片贴在工位旁边。尤其是PRBS7(x⁷ + x⁶ + 1)和PRBS31(x³¹ + x²⁸ + 1),这两款在10G/25G串行链路测试里几乎天天用。

注意一个坑:阶数n的本原多项式虽然能给出2n-1长度的序列,但初始值不能是全0。全0的话,异或永远出0,LFSR就卡死了。我在项目中遇到过一次,代码里初始化寄存器忘记赋非零值,结果眼图测出来全是直流电平……排查了整整半天,最后发现是初始化问题。

PRBS序列特性:为什么它能替代真随机?

你可能会问,又不是真随机,凭什么用它做测试?我用过几次之后就明白了——PRBS虽然周期可预测,但它在周期内的统计特征非常接近白噪声。

  • 游程特性:长度为k的连续相同比特出现的概率约为1/2k。这和真随机序列基本一致。
  • 自相关性:周期内自相关函数近似冲击函数,只在整数倍周期处有峰值。这意味着它能很好地激发出信道中的码间干扰。
  • 直流平衡:序列中0和1的数量几乎相等(差1个)。对交流耦合链路很友好。

我当时在调试一个25G NRZ链路时,先用PRBS7跑了一遍,眼图张开得不错;换成PRBS31之后,眼图立刻收缩了不少。为什么?因为长序列包含了更多低频分量,更容易触发CDR的抖动跟踪极限。你看,选哪种PRBS也是有讲究的。

工程实现:写一个LFSR到底有多简单?

直接用Verilog写一个PRBS7生成器,也就十几行代码。

module prbs7_gen (
    input  clk,
    input  rst_n,
    output reg prbs_out
);

    reg [6:0] lfsr = 7'd1;   // 初始值不能全0

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            lfsr <= 7'd1;
        else begin
            // 多项式 x^7 + x^6 + 1
            lfsr <= {lfsr[5:0], lfsr[6] ^ lfsr[5]};
        end
    end

    assign prbs_out = lfsr[6];
endmodule

这里有个容易被忽略的细节:XOR馈入最低位时,一定要用抽头位置的异或结果,而不是直接把最高位移出去。我见过有人写错反馈拼位,结果序列周期缩水了四分之一,折腾好久才发现。

如果你喜欢用软件模拟,Python搞一个生成函数更直白。下面这段代码输出PRBS7序列的头64比特,一眼就能看到规律。

def prbs7(poly=0b1100000, init=0b0000001):
    lfsr = init & 0x7F
    while True:
        yield (lfsr >> 6) & 1
        bit = ((lfsr << 1) ^ ( ( (lfsr >> 6) ^ (lfsr >> 5) ) & 1 )) & 0x7F
        lfsr = bit

gen = prbs7()
print([next(gen) for _ in range(64)])

避坑提醒:软件仿真时,多级LFSR的反馈计算要注意时序。如果在同一时钟内既移位又反馈,容易写错组合逻辑顺序。最好的做法是先计算反馈值,再移位赋值,两步分开。

回到实践层面,我个人建议你在设计板级测试时,先把LFSR的初始状态设成可配置的,这样方便复现问题。我在某个项目中就把PRBS的种子做成寄存器,运行时可以动态切换,调试效率提升不少。

好,这一章讲了LFSR的原理、本原多项式的选择、序列特性以及怎么用代码快速实现。说白了PRBS并不神秘,它就是一个带反馈的移位寄存器,配上合适的抽头,就能生成既随机又可控的测试序列。掌握了这些,后面讲实时眼图捕获时你就能理解为什么接收端要跟PRBS做同步了。