PRBS的数学原理:多项式与反馈移位寄存器
说实话,很多人刚开始接触PRBS时,就被“多项式”、“本原多项式”这些词吓住了。我当时也是。但后来做了几个高速串行通信的项目,才发现这东西其实挺直观的——说白了就是一组移位寄存器,再加几个异或门,就能跳出伪随机数。好,今天咱们就把它扒开看看。
线性反馈移位寄存器 LFSR:PRBS的发动机
PRBS的核心结构叫线性反馈移位寄存器,简称LFSR。它长什么样?n个寄存器排成一排,每个时钟沿把数据向右移一位,最后根据某些抽头进行异或,再把结果喂回最左边。
举个例子,一个4级LFSR,多项式是 \( x^4 + x^3 + 1 \)。这里的指数对应寄存器的编号(从1开始),系数1表示有抽头。寄存器编号与多项式幂次的关系,我习惯画张图帮助理解。
图里画的还是挺清楚的:四个寄存器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做同步了。