4. PRBS15详解:多项式 x¹⁵ + x¹⁴ + 1 与应用场景

好,咱们今天来聊聊 PRBS15。很多刚接触高速串行测试的工程师觉得多项式这东西很抽象,不就是 x¹⁵ + x¹⁴ + 1 嘛,但在实际项目中,选择哪种 PRBS 可不是随便拍脑袋定的。

4.1 从多项式说起:x¹⁵ + x¹⁴ + 1 到底在表达什么?

PRBS15 的核心是这个多项式:x¹⁵ + x¹⁴ + 1。什么意思呢?它其实是在描述一个 15 位移位寄存器的反馈逻辑。

具体来说:

  • 最高次项 x¹⁵:说明寄存器长度为 15 位。
  • x¹⁴:表示我们要把第 14 位抽出来做反馈。
  • 常数项 1:代表反馈异或运算的起点。

你想想看,15 位寄存器能产生多少种状态?排除全零状态,正好是 2¹⁵ - 1 = 32767 个。这就是 PRBS15 的序列长度。每次时钟沿到来时,寄存器向左移一位,同时根据 x¹⁵ 和 x¹⁴ 的输出做异或,再反馈回最低位。

我的经验之谈:很多工程师以为 PRBS 序列越长越好。但在实际调试 10Gbps 以上链路时,PRBS15 的 32767 位长度是个很好的折中——足够长来测试通道衰减,又不至于让误码测试因为等待序列同步而拖慢进度。

4.2 LFSR 实现:用代码把公式说清楚

理论讲完了,咱们看看怎么用代码实现。LFSR(线性反馈移位寄存器)是 PRBS 的物理基础。我习惯用 Python 来快速验证算法,下面是个标准实现:

def prbs15_generator(seed=0x7FFF, n_bits=32):
    """
    PRBS15 生成器(多项式:x^15 + x^14 + 1)
    
    Args:
        seed: 初始种子,不能为 0。常见起始值 0x7FFF
        n_bits: 需要生成的比特数
        
    Returns:
        二进制序列(list of int)
    """
    lfsr = seed & 0x7FFF
    sequence = []
    
    for _ in range(n_bits):
        # 输出当前最高位
        out_bit = (lfsr >> 14) & 1
        sequence.append(out_bit)
        
        # 计算反馈:取第 15 位和第 14 位异或
        feedback = ((lfsr >> 14) ^ (lfsr >> 13)) & 1
        
        # 左移一位,将反馈结果放入最低位
        lfsr = ((lfsr << 1) | feedback) & 0x7FFF
    
    return sequence

# 验证前 16 位输出
seq = prbs15_generator(seed=0x0001, n_bits=16)
print('输出序列:', ''.join(str(b) for b in seq))
# 期望结果:根据初始种子不同而不同

这段代码本身不复杂。但我在项目中踩过一个坑——种子不能为零。全零状态下,LFSR 会永远输出 0,序列就断了。

参数说明常见值
多项式x¹⁵ + x¹⁴ + 1FB = [15, 14]
序列长度2¹⁵ - 1 = 32767小于 PRBS23,大于 PRBS7
初始种子15 位非零二进制0x7FFF, 0x0001
最长连续 115 位可用于测 DC 平衡
最长连续 014 位考验锁相环保持能力

4.3 PRBS15 的用武之地:哪些场景离不开它?

在高速测试中,PRBS7 太短,PRBS31 又太长。PRBS15 刚好卡在中间:

  1. 10Gbps 以上串行链路测试:比如 10GBASE-KR、25G/50G/100G 以太网物理层。这些标准里明确要求用 PRBS15 做压力测试。
  2. PCIe 4.0 / 5.0 接收端抖动容限测试:PCIe 规范要求在特定测试模式下注入 PRBS15 来评估接收器的 CDR 和均衡能力。
  3. 光模块 BER 测试:短距离光互联场景,PRBS15 能平衡测试时间和通道评估深度。
  4. 高速背板通道验证:我曾经调试一块 28Gbps 背板,PRBS7 打过去眼图还行,但换成 PRBS15 马上暴露出一个 15 英寸走线处的反射问题。
为什么会这样? 因为 PRBS15 的低频分量比 PRBS7 更丰富,对通道低频衰减更敏感。同时它又比 PRBS31 更容易同步——同步窗口不那么大,测试时间可以压缩。

4.4 误码注入与比特错误定位

测试不光要产生 PRBS15,更要学会破坏它。准确注入误码,才能验证接收端纠错性能。

4.4.1 定点错误注入

在序列的特定比特位置翻转一个比特:

def inject_single_bit_error(sequence, error_pos):
    """在指定位置翻转比特"""
    seq_list = list(sequence)
    seq_list[error_pos] ^= 1
    return ''.join(str(b) for b in seq_list)

# 在 PRBS15 序列中第 10000 位注入一个错误
seq_str = ''.join(str(b) for b in prbs15_generator(seed=0x7FFF, n_bits=32767))
corrupted = inject_single_bit_error(seq_str, 10000)
# 此时第 10000 位与原始序列不同

4.4.2 突发错误注入

模拟实际链路上的突发噪声:

def inject_burst_errors(sequence, start_pos, burst_len):
    """从 start_pos 开始连续翻转 burst_len 个比特"""
    seq_list = list(sequence)
    for i in range(start_pos, min(start_pos + burst_len, len(seq_list))):
        seq_list[i] ^= 1
    return ''.join(str(b) for b in seq_list)

这里要注意:你注入的错误用什么模式,接收端的误码检测器就要用相应的参考序列做比对。说白了就是收端得知道发端长什么样,不然没法定位误差。

我曾经吃过这个亏:有一次测试 25Gbps 光模块,我没注意发端和收端的种子必须同步。结果误码仪报了一大堆错误,其实好多是参考序列没对齐导致的假误码。后来我改成固定种子 0x7FFF 发端收端同时初始化,问题就解决了。

4.5 同步过程与误码率计算

收端怎么知道序列从哪里开始的?这就需要同步机制:

  • 滑动相关法:收端用已知的 PRBS15 序列与接收信号做滑动互相关,峰值位置就是起始点。这种方法硬开销大,不适合实时。
  • 本地 LFSR 自同步:收端也运行一个 LFSR,检测到连续 N 个比特匹配后认为同步成功。N 一般取序列长度的 5~10%,也就是 1600~3200 比特。

同步成功后,逐比特比较就能计算 BER:

ber = 错误比特总数 / 比较的总比特数
# 一般要求 BER < 1e-12 才算链路合格

在 25Gbps 下要达到 1e-12 的置信度,需要连续测 10¹³ 个比特以上——也就是大约 6 分钟不间断测试。

4.6 知识结构图

下面这张图梳理了 PRBS15 的核心要素与测试流程之间的关系。我建议你把它打印出来贴在工位上。

PRBS15 知识体系框架 多项式定义 x¹⁵ + x¹⁴ + 1 LFSR 生成器 种子 ≠ 0 | 15 位寄存器 初始种子:0x7FFF 或 0x0001 序列特性 长度 32767 | 最大连 1:15 应用场景 10G/25G/100G 以太网 | PCIe 4.0/5.0 | 光模块 BER 误码注入 定点 / 突发 同步与误码检测 滑动相关法 | 本地 LFSR 自同步 关键测试参数要求 链路类型 测试序列 BER 目标 25G 以太网 PRBS15 <1e-12 PCIe 5.0 PRBS15 <1e-12

4.7 常见问题与避坑指南

最后分享几个我在实际项目中反复遇到的坑:

  • 种子不同步:发端用 0x7FFF,收端默认初始化用了 0x0001——明明发的是 PRBS15,收端却开始匹配 PRBS7 的模板。这种低级错误在测试报告里特别难查。
  • 序列截断问题:有些仪表在注入 PRBS15 时,不做整序列循环,而是反复截前 10000 位重复。这种非自然序列会让接收端的 CDR 误判。务必确认仪表能完整产生 32767 位序列。
  • 误码计数乌龙:别忘了串行链路有 8b/10b 或者 64b/66b 编码。你在编码后的比特流里注误码,真正起作用的可能是编码符号。我个人习惯先在编码前(原始的 PRBS15 比特流)做错误注入,这样定位更准。
总结一句话:PRBS15 是高速测试中平衡性与适用性最好的序列之一。理解了它的多项式结构和 LFSR 实现,你就能在以太网、PCIe 和光模块测试里踩坑少一半。