4. PRBS 生成器的硬件实现(LFSR)

讲 PRBS 的硬件实现,绕不开线性反馈移位寄存器。说白了,LFSR 就是一堆触发器串成链,再通过异或门把某些抽头反馈回去。我刚开始接触这玩意儿时,总觉得它就是个高级计数器,后来发现没那么简单——计数器的状态是固定的,LFSR 的状态却像随机游走,这才是伪随机序列的根源。

4.1 LFSR 的核心结构

一个标准的 LFSR 由三部分组成:

  • 移位寄存器链 — N 个 D 触发器级联,每个时钟上升沿向右移动一位
  • 抽头位置 — 根据本原多项式选择哪些级参与反馈
  • 异或网络 — 将抽头输出异或后馈送到第一级输入

举个例子你就明白了。PRBS7 的生成多项式是 x⁷ + x⁶ + 1,意思就是第 7 级和第 6 级的输出做异或,结果反馈到第 1 级。为什么是这两级?因为这是个本原多项式,能保证最大周期 2⁷ - 1 = 127 位。我建议初学时先拿 PRBS7 练手,周期短,仿真快,波形一眼就能看出规律。

小提示: 抽头位置(Tap)的选择不能拍脑袋。必须查表确认多项式是本原的,否则序列周期会缩短,甚至卡在全 0 状态出不来。

4.2 常用 PRBS 阶数与多项式

不同测试场景用不同阶数。下表列出我项目中常用的几种:

名称级数 (N)本原多项式序列长度典型应用
PRBS77x⁷ + x⁶ + 11278B/10B 编码链路,低速 SerDes
PRBS1515x¹⁵ + x¹⁴ + 132767PCIe 3.0/4.0,10G 以太网
PRBS2323x²³ + x¹⁸ + 18388607光纤通道,25G 速率
PRBS3131x³¹ + x²⁸ + 12147483647100G/400G 以太网,高速背板

选阶数时有个原则:序列长度要远大于码间干扰的持续时间。你想想看,如果被测链路的记忆效应只有几个 UI,用 PRBS7 就够了;但要测 CDR 抖动跟踪能力,PRBS31 更合适。我记得有一次项目里用 PRBS7 测 25G 链路,眼图很干净,换 PRBS31 立马发现低频抖动超标——说白了,短序列自相关太好,掩盖了问题。

4.3 硬件实现 —— 串行与并行

最直接的方式就是串行 LFSR。下图是一个 PRBS7 串行生成器的典型结构:

PRBS7 串行 LFSR 结构图 D1 D2 D3 D4 D5 D6 D7 XOR 反馈到D1输入 输出 抽头:D7 和 D6 输出异或后反馈至 D1

串行实现很简单:每个时钟移一位,输出就是序列。但速率一高(比如 28Gbps),单周期时间不到 36ps,这时串行 LFSR 的异或门传播延时就会成为瓶颈。我曾在 32G NRZ 设计里吃过亏,仿真能过,流片回来发现高频段序列质量变差。后来改成并行 N 路结构,每路同时计算多个位的反馈,才把问题解决。

4.4 Verilog 代码实现

下面是一个 PRBS7 的串行生成模块。我个人习惯用 always_ff + always_comb 分开写,更直观。

module prbs7_gen (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       en,
    output wire       prbs_out
);

    reg [6:0] shift_reg;

    // 反馈逻辑:x^7 + x^6 + 1
    wire feedback = shift_reg[6] ^ shift_reg[5];

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            shift_reg <= 7'b1;      // 避免全0
        else if (en)
            shift_reg <= {shift_reg[5:0], feedback};
    end

    assign prbs_out = shift_reg[6];

endmodule
注意: 初始值不能为全 0 或全 1(对于某些多项式),否则 LFSR 会陷入死锁。我习惯把初始值设为 7'b1,简单可靠。

4.5 并行 LFSR 设计思路

当数据路径宽度为 M 位时,需要在一个时钟内计算出 M 个新的序列位。做法是先把串行反馈逻辑展开 M 拍。举个例子,PRBS7 每拍输出 8 位:

// 伪代码:每个周期同时产生 8 个 bit
always_comb begin
    next_reg[0] = shift_reg[6] ^ shift_reg[5];
    next_reg[1] = shift_reg[5] ^ shift_reg[4];
    // ... 展开 8 步
    next_reg[7] = ... ;
end

展开时要注意抽头位置随着移位而改变。你可以用数学归纳法推导,也可以用 Python 脚本自动生成展开后的逻辑。我个人推荐写个脚本,手动展开容易漏项。

4.6 硬件实现中的避坑指南

  • 初始状态:上电时要保证至少一个寄存器为 1。我曾经用全 0 复位,仿真死得悄无声息。
  • 扇出过大:高位数的 PRBS(比如 PRBS31)反馈网络有 31 级异或门,组合逻辑路径很长。建议拆成两级异或树。
  • 跨时钟域:如果 LFSR 工作在高速时钟域,输出到低速域采样时要加同步器,否则亚稳态会破坏序列相位。
  • 自同步问题:某些接收端需要 LFSR 与发射端对齐。发送端的初始相位必须可配置,不能写死。我习惯把 seed 值做成寄存器,方便调试。

经验总结: 串行 LFSR 适合速率低于 10Gbps 的场景;高于 10Gbps 必须用并行结构。并行结构的关键是正确展开反馈多项式,并用综合工具检查最大频率是否达标。

4.7 测试与验证要点

写完了代码怎么确认它是对的?我的做法是三步:

  1. 周期检查:用 testbench 跑满一个周期(PRBS7 跑 127 拍),看序列是否重复。如果提前重复,说明多项式写错了。
  2. 自相关检验:计算序列的自相关函数,除了 0 偏移处为 1,其余位置应该接近 0。这个用 Python 算很方便。
  3. 眼图测试:把生成的 PRBS 信号灌入实际通道,用示波器捕获眼图。如果眼图轮廓有固定的“眼线”而不是均匀的噪声,说明序列中长连 0 / 长连 1 太多——可能是抽头没选对。

好了,这一章的核心就是 LFSR 的硬件实现。说白了,结构不难,细节却不少。你要是也打算在 FPGA 里拉一条 PRBS 链路,建议先从 PRBS7 串行开始,把仿真跑通,再挑战并行和更高阶数。