[FPGA 实现及PCIe IP 核知识点] 手把手教你如何加扰数据(伪随机)-FPGA常见问题社区-FPGA CPLD-ChipDebug

[FPGA 实现及PCIe IP 核知识点] 手把手教你如何加扰数据(伪随机)

为什么PCIe数据要加扰?

既然是数据,就有可能是重复的数据,例如长时间传输某个数据,这个数据通过8b/10b转换得到PCIe链路上最终传输的01信号。如果数据长期不变,01信号的形状也不会改变,这将产生一系列比较固定的频率点。这些频率点将会产生比平时更大的对外噪声或者EMI。解决这个问题的办法就是让PCIe传输的数据与一动态改变的伪随机码进行异或,这个动作被称为数据加扰(Data Scrambling)。这样一来,即使数据不改变,因伪随机码不断变化而导致加扰后的数据不断变化,从而不会导致某个固定形状的信号(Pattern)持续重复。PCIe采用了线性反馈移位寄存器(LFSR)来实现伪随机码。

什么是线性反馈移位寄存器?

线性反馈移位寄存器(LinearFeedback Shift Register)是用来产生伪随机码的一种设计。线性反馈的含义是指移位寄存器的输入来源于输出,或输出与某些数据位的线性运算。通常这个线性运算是异或运算(XOR)。

移位寄存器的概念和平时数电概念一致,这是伪随机码能能够一直变化的原因。

伪随机中‘伪’的含义是这一系列变化且不重复的码流,实际上经过比较长的时间,码流仍然会重复,并非真正意义上的随机。

什么是移位寄存器?

在了解线性反馈移位寄存器之前,我们可以先了解一下移位寄存器(Shift Register)。移位寄存器是一种常用的数字逻辑电路。由若干个以相同时钟为输入的触发器串联组合而成。

4位移位寄存器

1.jpg

如果我们把输出端和输入端连接起来,那么移位寄存器就已经是一种最简单的线性反馈移位寄存器了。如果需要设置初始值,只需要提前4个时钟周期准备好初始值在输入端。或者提前1个时钟周期通过并行方式直接设置。这个初始值也称为种子(Seed)。

4位移位寄存器并行初始化:

2.jpg

4位移位寄存器5个时钟周期

3.jpg

线性反馈移位寄存器工作原理

线性反馈移位寄存器有两种主流设计,一种是

多到一(Many-to-One)结构,一种是一到多(One-to-Many)结构。

两种结构对比:

4.jpg

其特点都是以一个移位寄存器为基础,将输出数据通过异或门反馈到移位寄存器中去。其中,Many-to-One结构是将输出数据与移位寄存器不同位置的数据异或后一起反馈到输入端口。而One-to-Many结构则是将输出数据与不同位置的数据进行异或输出给下级触发器。输入端口直接接收输出端口的数据。

多到一结构

在结构中,能够影响寄存器下一个状态的数据位被称为Taps,如上图(a)所示,这个结构中的Taps有:[2, 3, 4, 8]。

需要注意的是Taps是从1开始8结束。

所有的Taps将会通过异或门最后反馈到输入端。最右端的输出口的输出被称为输出流(Output Stream)。

如果Seed为0,即所有的数据位都是0,那么随着时钟的变化,LFSR的输出和数据位将不会有任何变化。所以,采用异或门设计的LFSR,通常Seed将不会是全0。

异或门的真值表:

0 ^ 0 = 0

1 ^ 0 = 1

0 ^ 1 = 1

1 ^ 1 = 0

那现在我们以1000 0000b为Seed列举接下来8个时钟周期:

T0:1000 0000:0x80

T1:0100 0000:0x40

T2:1010 0000:0xA0

T3:1101 0000:0xD0

T4:0110 1000:0x68

T5:0011 0100:0x34

T6:0001 1010:0x1A

T7:1000 1101:0x8D

T8:1100 0110:0xC6

相关的python验证程序为:

a=[0]*8

a[0]=1

for i inrange(8):

tmp = a[7]^a[3]^a[2]^a[1]

a[7]=a[6]

a[6]=a[5]

a[5]=a[4]

a[4]=a[3]

a[3]=a[2]

a[2]=a[1]

a[1]=a[0]

a[0]=tmp

print i, "=", a

大家可以在python环境中验证。

根据枚举,最多我们将得到2^8 – 1(255)个不同的8位二进制数(除0以外)。

需要注意的是,如果该结构采用不是异或门而是同或门,则Seed将不能为全1。

以上结构的Taps如果采用二进制多项式来表示,可以写为:

5.jpg

多项式中最后的1不是Tap,而是输入端的初始值。

需要注意的是,并不是所有的Taps都会得到最多的变化值。所以需要得到最大变化的值,Taps需要经过筛选。当然,你也可以参考现成的设计。许多百科网站都有介绍。

一到多结构

一到多的结构如上图(b)所示,移位寄存器中不是Tap的数据位将在下一个时钟周期无改变的位移到下一个位置,而对于Tap数据位而言,则需要与当前输出端的数据异或后,再移位到下一个位置。而输出端直接与输入端连接在一起。对于图(b)的结果,其多项式表达为:

6.jpg

我们仍然以1000 0000b为Seed来推演前16个时钟周期的值:

T0:1000 0000:0x80

T1:0100 0000:0x40

T2:0001 0000:0x10

T3:0000 1000:0x08

T4:0000 0100:0x04

T5:0000 0010:0x02

T6:0000 0001:0x01

T7:1011 1000:0xB8

T8:0101 1100:0x5C

T9:0010 1110:0x2E

TA:0001 0111:0x17

TB:1011 0011:0xB3

TC:1110 0001:0xE1

TD:1100 1000:0xC8

TE:0110 0100:0x64

TF:0011 0010:0x32

相关的python验证程序:

a=[0]*8

a[0]=1

for i inrange(16):

tmp=a[7]

a[7]=a[6]

a[6]=a[5]

a[5]=a[4]

a[4]=a[3]^tmp

a[3]=a[2]^tmp

a[2]=a[1]^tmp

a[1]=a[0]

a[0]=tmp

print i, "=", a 

LFSR输出数据流使用

一般来说,如果LFSR被用在串行数据接口的话,可以直接使用输出数据位与串行数据进行异或操作,也可以每8个数据异或一次。这个要看具体的设计。

在接收端,如果我们有一个与对面发送端一样的LFSR,则可以与已经加扰过的数据再异或,则真实数据就能够还原,这个过程被称为解扰(de-scrambling)。

例如,我们需要传输的数据是0x11,在发送端与LFSR的T8时刻的值0x5C进行异或,则异或完的结果为0x4D。当该数据抵达对面的接收端口后,如果LFSR也‘正好’产生0x5C数据,则0x4D与0x5C进行异或,结果就是0x11。

0001 0001

0101 1100 (XOR)


0100 1101

0101 1100 (XOR)


0001 0001

如何让接收端也能够同时启动LFSR则需要接收端能够辨别COM标识(K28.5)。

PCIe Gen1/Gen2 LFSR

根据PCIe规范,PCIe Gen1/Gen2的数据加扰在8b/10b转换前完成,然后再通过8b/10b转换,发送到PCIe链路上。而接收端则先通过8b/10b解码,然后再进行数据解扰。

在加扰端,8bit的数据(D0-D7)将会和一个16位的FLSR的输出进行异或操作。如下图所示:

7.jpg

需要注意的是LFSR的D0-D15与数据D0-D7是没有关系的,如果要好理解,你可以认为LFSR的D0-D15是LD0-LD15。

从结构上可以看出,PCIe的数据扰码采用的是One-to-Many的结构。根据PCIe规范,其多项式为:

8.jpg

即D15,D4,D3和D2的输出需要参与反馈。

每当8个时钟周期,PCIe8bit的数据就会和LFSR的LD8-LD15完成8bit的异或。从上图来看,基本上就是每个时钟周期异或一个bit。

PCIe规范的附录C有介绍数据加扰的范例。其中列举了前128个变化值。

需要注意的是每两个变化值之间间隔了8个时钟周期。

9.jpg

然后,规范还提供了与实际8bit数据异或用的LD8-LD15的值:

10.jpg

那么这两张表有什么关系呢?

表一提供了从LD15-LD0的值,例如0xE817指的是LFSR经过第一个8时钟后的变化值。但与PCIe实际数据异或的时候,LD15需要和D0进行异或,LD14需要和D1进行异或…

那么实际的异或值,如果站在PCIe数据角度来看,则需要LD8-LD15,0xE8等于1110 1000b,翻转过来,则是0001 0111b,这就是0x17的来源。下一个变化是0x0328,其中将0x03翻转,0000 0011b就变成了1100 0000b(0xC0)。我们看到表二中0x17的后面跟着的就是0xC0。

接下来,我们介绍一下如何得到表一变化值的。

根据多项式,我们可以先列出一个时钟周期后的变化,然后再列出第二时钟周期后的变化,以此类推,最后得到T8时刻的LFSR的变化值。

11.jpg

12.jpg

经过8个时钟周期,LFSR的LD0将会是原来LD8的值。而LD3则是原本LD11和LD8的异或结果…

如果采用python语言来表达的话,则可以写成:

13.jpg

这是python程序计算的结果:

14.jpg

可以看出,与PCIe规范给出的结果一致。

PCIe Gen3加扰

PCIe Gen3加扰相对Gen1和Gen2来说要复杂一点,但计算方法是一致的,所以如果你已经了解Gen1和Gen2的原理,Gen3并没有太大的困难。根据PCIe规范介绍,Gen3采用的是23位LFSR,其多项式为:

15.jpg

我们仍然可以列出T8时刻的LFSR变化值:

16.jpg

根据附录介绍:如果采用0x1DBFBC为Seed话,可以得到如下变化:

17.jpg

18.jpg

我们可以编写类似的Python脚本进行计算:

19.jpg

参考资料:

PCIe Gen3 Spec4.2.1.3 Data Scrambling
Mindshare PCIe Gen3Technology chapter 11. Scrambler
https://en.wikipedia.org/wiki/Linear-feedback_shift_register

请登录后发表评论

    没有回复内容