8b/10b下的编码/加扰和组帧

8b/10b下的编码/加扰和组帧

为了更加清晰明确的表达出协议的意思,一些关键的术语采用英文,在第一次时会给出个人对于该英文的理解和翻译

8b/10b编码

8b/10b编码简介

8b/10b编码是将一个8b的数用10b来表示,其中高三位采用的3b/4b编码,低5位采用的5b/6b编码。8b/10b编码分为控制码编码和数据编码,控制码以kx.y表示,数据码以Dx.y表示,其中x为8比特数低5位的十进制编码,y为8比特数高三位的十进制编码。例如:一个8比特的数据6Ah,二进制表示为0110 1010,高三位为011,十进制表示为3,低五位为01010,十进制表示为10,所以编码前可以用D10.3表示。
8b/10b编码
8b/10b编码具有如下的特点:

  1. 3b/4b编码后的结果不会超过3个0或者3个1
  2. 5b/6b编码后的结果不会超过4个0或者4个1
  3. 8b/10b编码后的结果0,1个数差别最多是2个,即10比特的数中只能出现4个0+6个1,5个0加5个1,6个0加4个1三种情况。
  4. 数据而言,8b/10b编码后不会出现连续的5个0或者连续的5个1

8b/10b编码规则

  • 仅适用于链路速率为2.5GT/s和5.0GT/s
  • 编码前后的值可参考pcie 8b/10b编码表
  • 12种特殊(COM,STP,SDP,END,EDB,PAD,SKP,FTS,IDL,k28.4,k28.6,EIE)的符号在8b/10b种需要引入一个控制字符,用于表明编码后的数据是一个控制字符而不是数据
  • 第一次退出电气空闲时,disparity(失调)的选择
    • disparity就是symbol(符号)中0,1个数差异,如果1比0多,就是positive disparity(正失调),0比1多就是negative disparity(负失调)
    • 对于发射机而言,在电气空闲之后第一次传输差分数据时,如果没有特殊的要求,发射机允许选择任何disparity,此后发射机必须遵守合适的8b/10b编码规则直到下一次进入电气空闲
    • 对于接收机而言,检测到从电气空闲退出后(即收到了对端的数据),接收机的初始disparity被认为是第一个symbol的disparity,第一个symbol用来实现symbol lock(符号锁定)。如果在传输差分信息的过程中,由于一些错误导致symbol lock丢失,在重新实现symbol lock后,Disparity必须重新初始化。在初始化disparity设定好之后,后续所有接受到的symbol必须根据current running disparity(当前运行中的失调)来决定8b/10b解码后的值。running disparity(RD)是根据发送或者接受到Symbol的disparity实时计算得到。
  • 如果接受的symbol是在错误的running disparity一列(如Current RD+,但是收到的6Ah为010101 11000b)或者接收到的symbol既不在Current RD+一列也不再Current RD-一列,在Non-Flit模式下,则物理层必须向数据链路层报告接收到的symbol是无效的,并且是一个Receiver Error(接收机错误),该错误必须报告,且跟端口关联。在Flit模式下,symbol发生错误可以通过FEC逻辑纠正,如果在Flit边界发生了8b/10b解码错误或者检测到K码,接收机可以发送任何8比特的值给FEC逻辑。

8b/10编码状态机

  • pcie6.2协议无此部分,结合mindshare的个人理解
  • 8b/10b在positive disparity和negative disparity下可能编码值不一样
  • 初始disparity由发射机决定
  • 然后根据当前10比特数据中的0,1个数,决定下一个数据用何种disparity,如果当前发送的数据中0,1个数不等,则下一个数据采用另外一种running disparity一列的数据。如果0,1个数相等,则下一个数据仍然采用当前running disparity这一列的数据
    8b/10b编码状态机
    • 刚开始为RD+,下一个采用RD-表明当前发送的数据中0比1多,为了平衡1和0的个数,就需要多发送1,需要发送positive disparity的数。即Current RD+就发negative disparity或neutral disparity(10比特数中0,1个数相等)的数,Current RD-就发送positive disparity或者neutral disparity的数
  • 例如:本端发射方向需要发送K28.5(BCh)+K28.5+D10.3(6Ah)时
    8b/10b编码传输的例子
    • 本端选择的初始disparity为负,K28.5在Current RD-时的编码为001111 1010b,此时1比0多,为了平衡数据中0和1的个数,就需要多发一些0。
    • 下一个数选择Current RD+一列的编码,K28.5在Current RD+一列的数据为110000 0101b,此时0比1多,需要多发些0。
    • 但是D10.3在Current RD+和Current RD-的编码中0,1个数都是相等的,依然需要采用Current RD-一列的编码(因为只要0,1个数相等,就一直采用Current RD-一列的编码,直到出现0比1多的情况,而如果采用Curreent RD+一列,不会出现0比1的情况,可能会造成一直在积累1),其值为010101 1100,此时0,1个数相等。
    • 下一个需发送的数仍然采用RD+一列的编码。
  • 例如对端接受方向
    • 首先收到001111 1010b,此时1比0多,初始disparity为正,但是只有COM字符在8b/10b编码才可能出现连续5个0或者连续5个1,所以此处8b/10b应该可以解码出来为BC(Current RD+和Current RD-两列的编码是互补的,即加起来为11111 1111b)。下一个数采用Current RD+一列的编码
    • 下一个收到的数为110000 0101b,采用Current RD+一列的编码,解码的结果为BCh,此时0比1多,下一个数采用Current RD-一列的编码结果
    • 接着收到010101 1100b,该值在Current RD-一列解码出来的值为6A。此时0,1个数相等,下一个数仍然采用Current RD-一列的结果
  • MISC
    • Q. 既然接受方向是根据第一个symbol的disparity来作为初始disparity的,那会出现收到的第一个symbol中0,1个数相同的情况吗?
      • A. 这个地方理论上是不会发生的,因为8b/10b编码中发送的第一个symbol总是COM(K28.5)字符,而COM字符在Current RD-和Current RD+时的编码都0,1的个数都不相等,所以接收方从理论上而言总是可以计算出来一个初始的disparity。
    • Q. 接受端初始的disparity error跟发送端不一致,算receiver error吗?
      • A. 这个地方我觉得不应算,因为协议描述的是接受到的symbol不在正确的running disparity一列时才是receiver error,初始的disparity是根据收到的symbol来确定的,跟running disparity并没有关系,只有后面收到的symbol才会跟running disparity进行对比。

8b/10b编码作用

  • 维持直流平衡
    • 最重要的作用。尽可能保持一端时间内0和1的个数基本一致。如果在接收端出现长0或者长1,会影响接收端对0,1的判断。比如接受端一端时间都是收到的0,然后突然来一个1,接着又是0,由于本端的RX跟对端的TX之间存在耦合电容,长时间的0会被认为存在一定的直流分量,被耦合电容滤掉,然后出现1时,又出现0时,对电容来不及充电,导致RX端电压达不到电平1的标准,出现误识别位0。
  • 提高纠错能力
    • 8b可以表示256种情况,而10b有1024种可能。如果一个10b的数不在8b/10b编码表中,那就产生一个decode error来表明链路中出现了这种错误。
  • 将时钟方便的嵌入到数据中
    • 因为8b/10b编码中0和1的跳变比较频繁,方便CDR电路将时钟恢复出来,0,1跳变越频繁越容易恢复出来时钟。

8b/10b编码中的一些控制码(K Codes)

  • 又叫k码
  • 作用
    1. 用于lane和link的初始化和管理
    2. 在Non-Flit模式下(pcie6.0新增),让DLLPs和TLPs组成帧(frame),方便快速区分这两种类型的包。比如8b/10b编码,TLP以STP(K27.7)开头,DLLP以SDP(K28.2)开头
  • 12种特殊的控制码
12种特殊的控制码
编码 符号 全称 16进制表示 二进制(Current RD-) 二进制(Current RD+) 描述
K28.5 COM Comma BC 001111 1010 110000 0101 用于lane和link初始化和管理
K27.7 STP Start TLP FB 110110 1000 001001 0111 Non-Flit模式:表示TLP包的开始,Flit模式:保留
k28.2 SDP Start DLLP 5C 001111 0101 110000 1010 Non-Flit模式:表示DLLP包的开始,Flit模式:保留
K29.7 END End FD 101110 1000 010001 0111 Non-Flit模式:表示TLP包或者DLLP包的结束,Flit模式:保留
k30.7 EDB End Bad FE 011110 1000 100001 0111 Non-Flit模式:表示无效TLP包的结束,Flit模式:保留
k23.7 PAD Pad F7 111010 1000 000101 0111 在Non-Flit模式用于组成帧,在Non-Flit模式和Flit模式下用于链路宽度和lane顺序的协商
k28.0 SKP Skip 1C 001111 0100 110000 1011 当两个通信端口的比特率不同时,用于补偿两端的差异,在Flit模式和Non-Flit模式下用法相同
K28.1 FTS Fast Trainng Sequence 3C 001111 1001 110000 0110 Non-Flit模式,用于组成FTSOS,从L0s退出到L0时,用于快速实现符号锁定(symbol lock)
K28.3 IDL Idle 7C 001111 0011 110000 1100 用于组成EIOS,Flit模式和Non-Flit模式下用法相同
K28.4     9C 001111 0010 110000 1101 保留
K28.6     DC 001111 0110 110000 1001 保留
K28.7 EIE Electrical Idle Exit FC 001111 1000 110000 0111 2.5GT/s下保留,用于组成EIEOS,并且要先于FTS发送

Frame(组帧)

Lane上可以传输两种类型的frame(帧),第一类由OS((Ordered Sets,有序集,简称OS)组成;第二类在数据流中,由TLPs和DLLPs组成。有序集在每条lane上都会持续的发,因此如果一个link由多条lane构成,完整的有序集会同时出现所有lane上。Flit模式下没有framing相关的错误,因为Flit模式下可以通过FEC纠错。

Non-Flit模式下的数据流

  • Framing机制采用特殊的symbol来管理TLP和DLLP,用K28.2(SDP)来表明将要传输DLLP,用K27.7(STP)来表明将要传输TLP,用K29.7(END)表明TLP或者DLLP的结束。
  • 当没有包信息或者特殊的OS需要传输时,发射机处于逻辑空闲(Logical Idle)状态
  • 发射机处于逻辑空闲状态时,必须发Idle Data(空闲数据,由00h组成),Idle Data也需要经过加扰和编码
  • TLP和DLLP中的Data symbol需要经过加扰和编码
  • 当接收机没有收到包或者特殊的OS时,接收机也是处于Logical Idle(逻辑空闲)状态,并且也应该接受Idle Data
  • 在传输Idle Data期间,SKPOS也必须按照一定的规则持续传输

Framing规则

接收机可以检查下列规则(都是独立检查,并且是可选),如果接收机检查了,违反后会产生Receiver Error,并且上报错误时会关联到port。规则如下:

  • TLPs组成Framed必须要把STP符号放在TLP的开始,并且要把END符号或者EDB符号放在TLP的结束
  • 在STP和END或者EDB symbol之间,TLP最少包含18个symbol.如果接收到的序列,STP和END或者EDB符号之间的符号数小于18个,接收机允许将这视为一个Receiver Error(可选的功能,如果检查了则必须跟port关联起来)
  • DLLPs组成Frame必须要把SDP symbol放在DLLP的开头,并且把END symbol放在DLLP的末尾
  • 没有TLPs,DLLPs或者特殊的symbol需要发送或者接受时,这时的一个或者多个symbol time(符号时间)成为logical Idle,logical Idle需要发送和接受空闲数据(00h)
    • 当发射机处于逻辑空闲时,Idle data符号应该在所有lane上都传输,Idle data会被加扰(避免差分线上出现长的0)
    • 接收机必须忽略接收到的Idle data,并且不得依赖这些数据,除了一些加扰过的特定数据序列外(比如物理层的状态机在Configuration.Idle和Recovery.Idle需要判断收到的Idle data个数,这时就会依赖Idle data)
  • 如果链路宽度大于x1,当从Logical Idle准备开始传输TLP时,STP符号必须放在lane0上
  • 如果链路宽度大于x1,当从Logical Idle准备开始传输DLLP时,SDP符号必须放在lane0
  • 一个symbol time内,STP symbol只能放在链路上一次
  • 一个symbol time内,SDP symbol只能放在链路上一次
  • 在相同的符号时间,链路上可以出现一个STP符号和SDP符号(eg. x16 SDP(1symbol)+DLLP(6symbol)+END+STP+…)
    • 链路宽带大于x4时,STP和SDP symbol可以放在4*N(N是正整数),eg. x8的链路,STP和SDP symbol可以放在lane 0和lane 4,x16的链路,STP和SDP symbol可以放在lane0,4,8和12
  • xN(N>=8)的链路,如果END和EDB symbol放在lane k上,K不等于N-1,并且lane k+1没有跟STP symbol或者SDP symbol时(比如没有TLP或者DLLP需要发送时),必须在lane k+1到lane N-1上填充PAD
    • eg. x8的链路,END symbol或者EDB symbol放在lane3,当后面没有STP或者SDP symbol时,lane 4到lane 7必须用PAD填充

x4的例子
x4 frameing的例子

加扰

8b/10b加扰简介

加扰(scrambling)就是将数据流与特定模式序列(pattern)进行异或操作(XOR),使其发送出去的数据变得伪随机,特定模式序列用LFSR(Linear Feedback Shift Register,线性反馈移位寄存器)产生。gen1/2 LFSR的加扰多项式为G(x)=x16+x5+x4+X3+1。对于给定的初始值,LFSR后面的值都是确定的。gen1/2加扰电路如下图所示:
加扰电路
该电路是pcie spec给出的,实际不一定会采用这种,因为这种完成一个symbol的计算需要8个时钟周期。能保证最终实现的效果跟这个电路一致就可以。这里的Dx指的是D触发器,评论区的说法可能不是很严谨。具体加扰步骤如下:

  1. LFSR中D触发器D15的输出与数据端D触发器D0(数据的比特0)的输出进行异或
  2. LFSR和数据寄存器串行前进
  3. 对数据的其它比特也按照数据比特0的方式重复操作
  • 可以简单得理解加扰后的数据为输入数据与LFSR输出D8-D15相异或,第一个symbol与D8-D15相异或,第二个symbol与D7-D0相异或第二个symbol与D0-D7经过组合逻辑后的值相异或(因为D触发器D8-D14只是单纯的移位,没有经过组合逻辑,所以第一个symbol相当于与D8-D15相异或)

8b/10b编码和加扰顺序

  • 发射端:先加扰然后在进行8b/10b编码
    • 因为8b/10b编码可以保证直流平衡,如果是先编码,在加扰可能会导致0和1的个数相差较大
  • 接收端:先8b/10b解码然后在进行解扰(de-scramble)

8b/10b加扰规则

  • LFSR相关
    • LFSR的初始种子(即D0-D15的值)是FFFFh
    • COM 符号初始化 LFSR
    • COM字符发送出去之后,发送端的LFSR立即初始化
    • link上某条lane收到COM符号后,立刻初始化该条lane的LFSR
    • 除SKP外的每一个symbol,LFRS都是串行移位,每个symbol移动8次
    • 多lane的link,加扰多项式可以用一个或者多个LFSR实现
    • 发送端,如果link上有多个LFSR,它们必须协同工作, 每个LFSR中都要维持相同的同步值(lane与lane之间的输出skew(偏移))
    • 接收端,如果link上有多个LFSR时,它们必须协同工作,每个线性反馈移位寄存器需要维持相同的同步值(lane与lane之间的skew)
  • 是否需要加扰
    • Flit模式和Non-Flit都需要加扰
    • 除了有序集(eg.TS1,TS2,EIEOS)中数据、Compliance Pattern(合规性模式序列)和Modified Compliance Pattern(修改过的合规性测试序列)外的所有的数据symbol(D Codes)都需要加扰
    • 所有特殊symbol(K 码)不需要加扰
  • 是否使能加扰
    • 只有在Configuration状态结束后才可以禁止加扰
    • Loopback Follower不使用加扰
    • 在Detect状态默认使能加扰
    • 数据链路层可以通知物理层禁止加扰,具体实现方式协议没做说明,比如设置寄存器禁止加扰

8b/10b加扰代码参考

来源于pcie6.2 附录C
q(D0)表示D触发器初始的输出,首先需要确定当前D15的输出是多少,当前D15输出取决于上一时刻D15的输入

LFSR的输出
LFSR移动次数(时钟周期) D0输入(当前D15的输出) D0输出 D1输入(当前D0的输出) D1输出 D2输入(当前D1的输出) D2输出 D3输入(当前时刻D2输出异或当前时刻D15输出) D3输出 D4输入(当前时刻D3输出异或当前时刻D15输出) D4输出 D5输入(当前时刻D4输出异或当前时刻D15输出) D5输出 D6输入(当前D5的输出) D6输出 D7输入(当前D6的输出) D7输出 D8输入(当前D7的输出) D8输出 D9输入(当前D8的输出) D9输出 D10输入(当前D9的输出) D10输出 D11输入(当前D10的输出) D11输出 D12输入(当前D11的输出) D12输出 D13输入(当前D12的输出) D13输出 D14输入(当前D13的输出) D14输出 D15输入(当前D14的输出) D15输出
0 q(D15) q(D0) q(D0) q(D1) q(D1) q(D2) q(D2)^q(D15) q(D3) q(D3)^q(D15) q(D4) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8) q(D8) q(D9) q(D9) q(D10) q(D10) q(D11) q(D11) q(D12) q(D12) q(D13) q(D13) q(D14) q(D14) q(D15)
1 q(D14) q(D15) q(D15) q(D0) q(D0) q(D1) q(D1)^q(D14) q(D2)^q(D15) q(D2)^q(D15)^q(D14) q(D3)^q(D15) q(D3)^q(D15)^q(14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(8) q(D8) q(9) q(D9) q(10) q(D10) q(11) q(D11) q(12) q(D12) q(13) q(D13) q(D14)
2 q(D13) q(D14) q(D14) q(D15) q(D15) q(D0) q(D0)^q(D13) q(D1)^q(D14) q(D1)^q(D14)^q(D13) q(D2)^q(D15)^q(D14) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D9) q(D8) q(D9) q(D9) q(D10) q(D10) q(D11) q(D11) q(D12) q(D12) q(D13)
3 q(D12) q(D13) q(D13) q(D14) q(D14) q(D15) q(D15)^q(D12) q(D0)^q(D13) q(D0)^q(D13)^q(D12) q(D1)^q(D14)^(D13) q(D1)^q(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8) q(D8) q(D9) q(D9) q(D10) q(D10) q(D11) q(D11) q(D12)
4 q(D11) q(D12) q(D12) q(D13) q(D13) q(D14) q(D14)^q(D11) q(D15)^q(D12) q(D15)^q(D12)^q(D11) q(D0)^q(D13)^q(D12) q(D0)^q(D13)^q(D12)^q(D11) q(D1)^q(D14)^q(D13)^q(D12) q(D1)^q(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8) q(D8) q(D9) q(D9) q(D10) q(D10) q(D11)
5 q(D10) q(D11) q(D11) q(D12) q(D12) q(D13) q(D13)^q(D10) q(D14)^q(D11) q(D14)^q(D11)^q(D10)) q(D15)^q(D12)^q(D11) q(D15)^q(D12)^q(D11)^q(D10) q(D0)^q(D13)^q(D12)^q(D11) q(D0)^q(D13)^q(D12)^q(D11) q(D1)^q(D14)^q(D13)^q(D12) q(D1)^q(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8) q(D8) q(D9) q(D9) q(D10)
6 q(D9) q(D10) q(D10) q(D11) q(D11) q(D12) q(D12)^q(D9) q(D13)^q(D10) q(D13)^q(D10)^q(D9) q(D14)^q(D11)^q(D10) q(D14)^q(D11)^q(D10)^q(D9) q(D15)^q(D12)^q(D11)^q(D10) q(D15)^q(D12)^q(D11)^q(D10) q(D0)^q(D13)^q(D12)^q(D11) q(D0)^q(D13)^q(D12)^q(D11) q(D1)^a(D14)^q(D13)^q(D12) q(D1)^a(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) Q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8) q(D8) q(D9)
7 q(D8) q(D9) q(D9) q(D10) q(D10) q(D11) q(D11)^q(D8) q(D12)^q(D9) q(D12)^q(D9)^q(D8) q(D13)^q(D10)^q(D9) q(D13)^q(D10)^q(D9)^q(D8) q(D14)^q(D11)^q(D10)^q(D9) q(D14)^q(D11)^q(D10)^q(D9) q(D15)^q(D12)^q(D11)^q(D10) q(D15)^q(D12)^q(D11)^q(D10) q(D0)^q(D13)^q(D12)^q(D11) q(D0)^q(D13)^q(D12)^q(D11) q(D1)^q(D14)^q(D13)^q(D12) q(D1)^q(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7) q(D7) q(D8)
8 q(D7) q(D8) q(D8) q(D9) q(D9) q(D10) q(D10)^q(D7) q(D11)^q(D8) q(D11)^q(D8)^q(D7) q(D12)^q(D9)^q(D8) q(D12)^q(D9)^q(D8)^q(D7) q(D13)^q(D10)^q(D9)^q(D8) q(D13)^q(D10)^q(D9)^q(D8) q(D14)^q(D11)^q(D10)^q(D9) q(D14)^q(D11)^q(D10)^q(D9) q(D15)^q(D12)^q(D11)^q(D10) q(D15)^q(D12)^q(D11)^q(D10) q(D0)^q(D13)^q(D12)^q(D11) q(D0)^q(D13)^q(D12)^q(D11) q(D1)^q(D14)^q(D13)^q(D12) q(D1)^q(D14)^q(D13)^q(D12) q(D2)^q(D15)^q(D14)^q(D13) q(D2)^q(D15)^q(D14)^q(D13) q(D3)^q(D15)^q(D14) q(D3)^q(D15)^q(D14) q(D4)^q(D15) q(D4)^q(D15) q(D5) q(D5) q(D6) q(D6) q(D7)
int unscramble_byte(int inbyte)
{
  static int descrambit[8];
  static int bit[16];
  static int bit_out[16];
  static unsigned short lfsr = 0xffff;
  int outbyte,i;

  if (inbyte == COMMA)
  {
    lfsr = 0xffff;  // COM初始化LFRS
    return (COMMA);
  }

  if (inbyte == SKIP) // SKIP不会使LFSR变化
  {
    return {SKIP};
  }

  for (i=0; i<16; i++)  // 将LFSR的输出转化为数组, bit[15]表示D触发器D15 Q端的值
  {
    bit[i] = (lsfr >> i) & 1;
  }

  for (i=0; i<8; i++)  // 将需要加扰的数转化为数组
  {
    descrambit[i] = (inbyte >>i) & 1;
  }

  // K码跳过加扰,如果是K码,用单独的一比特来表示,比特[8]为1,1&1 = 1,取反为假,表示跳过加扰
  if (!(inbyte & 0x100)  &&
      !(TrainingSequence == TRUE) )
  {
    descrambit[0] ^= bit[15];  // 数据的比特0与LFSR比特15相异或
    descrambit[1] ^= bit[14];  // 数据的比特1与LFSR比特14相异或
    descrambit[2] ^= bit[13];  // 数据的比特2与LFSR比特13相异或
    descrambit[3] ^= bit[12];  // 数据的比特3与LFSR比特12相异或
    descrambit[4] ^= bit[11];  // 数据的比特4与LFSR比特11相异或
    descrambit[5] ^= bit[10];  // 数据的比特5与LFSR比特10相异或
    descrambit[6] ^= bit[ 9];  // 数据的比特6与LFSR比特9相异或
    descrambit[7] ^= bit[ 8];  // 数据的比特7与LFSR比特8相异或
  } 
  
  // 数据与LFSR异或完成后,整体移动8次
  // LFSR 移动8次后,第n个D触发器的输出,见上一个表格的分析
  bit_out[ 0] = bit[ 8];
  bit_out[ 1] = bit[ 9];
  bit_out[ 2] = bit[10];
  bit_out[ 3] = bit[11] ^ bit[ 8];
  bit_out[ 4] = bit[12] ^ bit[ 9] ^ bit[ 8];
  bit_out[ 5] = bit[13] ^ bit[10] ^ bit[ 9] ^ bit[ 8];
  bit_out[ 6] = bit[14] ^ bit[11] ^ bit[10] ^ bit[ 9];
  bit_out[ 7] = bit[15] ^ bit[12] ^ bit[11] ^ bit[10];
  bit_out[ 8] = bit[ 0] ^ bit[13] ^ bit[12] ^ bit[11];
  bit_out[ 9] = bit[ 1] ^ bit[14] ^ bit[13] ^ bit[12]; 
  bit_out[10] = bit[ 2] ^ bit[15] ^ bit[14] ^ bit[13];
  bit_out[11] = bit[ 3] ^ bit[15] ^ bit[14];
  bit_out[12] = bit[ 4] ^ bit[15];
  bit_out[13] = bit[ 5];
  bit_out[14] = bit[ 6];
  bit_out[15] = bit[ 7];

  // 更新LFSR各个触发器的输出值
  lfsr = 0;
  for (i=0; i<16; i++)
  {
    lfsr += (bit_out[i] << i);
  }

  outbyte = 0;
  for(i=0; i<8; i++)
  {
    outbyte += (descrambit[i] << i );
  }
  return outbyte;
}  

python实现

# 8b/10b lfsr移动8次后输出D触发器D8-D15的结果
# D8 D9 D10 D11 D12 D13 D14 D15
# 1  1  1   1   1   1   1   1
# 0  0  0   1   0   1   1   1
# 1  1  0   0   0   0   0   0
# 0  0  0   1   0   1   0   0

def lsfr_output(array = []):
  next_lfsr = [1,1,1,1,
               1,1,1,1,
               1,1,1,1,
               1,1,1,1]
  next_lfsr[0]  = cur_lfsr[8] 
  next_lfsr[1]  = cur_lfsr[9] 
  next_lfsr[2]  = cur_lfsr[10] 
  next_lfsr[3]  = cur_lfsr[11] ^ cur_lfsr[8] 
  next_lfsr[4]  = cur_lfsr[12] ^ cur_lfsr[9]  ^ cur_lfsr[8] 
  next_lfsr[5]  = cur_lfsr[13] ^ cur_lfsr[10] ^ cur_lfsr[9]  ^ cur_lfsr[8] 
  next_lfsr[6]  = cur_lfsr[14] ^ cur_lfsr[11] ^ cur_lfsr[10] ^ cur_lfsr[9] 
  next_lfsr[7]  = cur_lfsr[15] ^ cur_lfsr[12] ^ cur_lfsr[11] ^ cur_lfsr[10] 
  next_lfsr[8]  = cur_lfsr[0]  ^ cur_lfsr[13] ^ cur_lfsr[12] ^ cur_lfsr[11] 
  next_lfsr[9]  = cur_lfsr[1]  ^ cur_lfsr[14] ^ cur_lfsr[13] ^ cur_lfsr[12]  
  next_lfsr[10] = cur_lfsr[2]  ^ cur_lfsr[15] ^ cur_lfsr[14] ^ cur_lfsr[13] 
  next_lfsr[11] = cur_lfsr[3]  ^ cur_lfsr[15] ^ cur_lfsr[14] 
  next_lfsr[12] = cur_lfsr[4]  ^ cur_lfsr[15] 
  next_lfsr[13] = cur_lfsr[5] 
  next_lfsr[14] = cur_lfsr[6] 
  next_lfsr[15] = cur_lfsr[7] 
  return next_lfsr

def xor(data,skip=0,lfsr=[],):
  
  if skip:
    print("data(hex) : " + data + " cur lfsr[8:15] : " + str(lfsr[8:]) + " skip de-scramble : " + data)
    return 

  # note : 比如首先移动8次后, LFSR的输出为 0 0 0 1 0 1 1 1,其实指的是
  # 正常流程 : 数据的bit[0]与LFRS D15的输出异或,然后移动一次,直到数据的bit[7]与D15触发器相异或
  # 相当于LFSR移动8次 : bit[0]跟D7的输出相异或, bit[1]跟D8的输出相异或
  binary_data = bin(int(str(data),16))[2:].zfill(8)
  xor_out = [0,0,0,0,0,0,0,0]
  for i in range(0,8):
    xor_out[i] = int(binary_data[i]) ^ lfsr[i+8]
  
  # 将列表转换为二进制字符串
  binary_str = ''.join(str(x) for x in xor_out)

  # 将二进制字符串转换为整数
  decimal_value = int(binary_str, 2)

  # 将整数转换为 16 进制数
  hex_value = hex(decimal_value)[2:].upper()
  print("data(hex) : " + data + " cur lfsr[8:15] : " + str(lfsr[8:]) + " de-scramble : " + hex_value)

  # print(0x3a^lsfr_output(cur_lfsr)[8:15])

cur_lfsr = []
next_lfsr = []
init_lfsr = []

for i in range(0,15):
  if i in [0,1,2]:
    skip = 1
    if i == 0:
      data = 'COM'
      init_lfsr = ['x','x','x','x','x','x','x','x','x','x','x','x','x','x','x','x']
      xor(data,1,init_lfsr)
      cur_lfsr = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
    else:
      data = 'PAD'
      xor(data,1,cur_lfsr)
      next_lfsr = lsfr_output(cur_lfsr) 
      cur_lfsr = next_lfsr
  else:
    if i == 3:
      data = '3a'
    elif i == 4:
      data = '7e'
    elif i == 80:
      data = '7e'
    else:
      data = '4a'
    xor(data,0,cur_lfsr)
    next_lfsr = lsfr_output(cur_lfsr) 
    cur_lfsr = next_lfsr

perl实现,感谢@Alen的支持

#!/usr/bin/perl

use strict;
use warnings;

# 期望进行加扰的TS OS的16个symbol,此计算过程不包含对COM、PAD等特殊字符的处理,故前三位设置默认为0;
my @input_byte = (0x00,0x00,0x00,0x3a,0x7e,0x80,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a);

# lsfr_value 此处用来声明初始的LSFR的值;
my $lsfr_value = 0xffff;

# 定义该数组,用来存放 lsfr_value 的各个bit位,便于后续的移位计算;
my @bits;

# 定义该数组,用来存放将要进行加扰data的各个bit位;
my @scramble_bit;

# 定义该变量,用来存放每次for循环中,正在进行加扰处理的数据(1symbol,8bit);
my $data;

# 定义该数组,用来临时存放每次移位8次后,lsfr的各个bit位的值的变化;
my @bit_out;

# 定义该变量,用来存放加扰后需要输出的数据;
my $outbyte;

    for(my $j=1; $j<16 ;$j++){
        $data = $input_byte[$j];
        
        # Convert LFSR output to array.
        for my $i (0..15){
            $bits[$i] = ($lsfr_value >> $i) & 1;
        }

        # Convert input_byte to array.
        for my $i (0..7){
            $scramble_bit[$i] = ($data >> $i) & 1;
        }
        
        # Scrambling!!!
        if(1){
            $scramble_bit[0] ^= $bits[15];
            $scramble_bit[1] ^= $bits[14];
            $scramble_bit[2] ^= $bits[13];
            $scramble_bit[3] ^= $bits[12];
            $scramble_bit[4] ^= $bits[11];
            $scramble_bit[5] ^= $bits[10];
            $scramble_bit[6] ^= $bits[9];
            $scramble_bit[7] ^= $bits[8];
        }

        # Shift LFSR bits by 8 times
        @bit_out = (
            $bits[8],
            $bits[9],
            $bits[10],
            $bits[11] ^ $bits[8],
            $bits[12] ^ $bits[9] ^ $bits[8],
            $bits[13] ^ $bits[10] ^ $bits[9] ^ $bits[8],
            $bits[14] ^ $bits[11] ^ $bits[10] ^ $bits[9],
            $bits[15] ^ $bits[12] ^ $bits[11] ^ $bits[10],
            $bits[0] ^ $bits[13] ^ $bits[12] ^ $bits[11],
            $bits[1] ^ $bits[14] ^ $bits[13] ^ $bits[12],
            $bits[2] ^ $bits[15] ^ $bits[14] ^ $bits[13],
            $bits[3] ^ $bits[15] ^ $bits[14],
            $bits[4] ^ $bits[15],
            $bits[5],
            $bits[6],
            $bits[7]
        );

        # Update LFSR value.
        $lsfr_value = 0;
        for my $i (0..15){
            $lsfr_value += ($bit_out[$i] << $i);
        }

        # Update Scrambled data value.
        $outbyte = 0;
        for my $i (0..7){
            $outbyte += ($scramble_bit[$i] << $i);
        }

        printf "The symbol %d th after scramble data value is:%x .\n ",$j,$outbyte;

    }

数据serialization(串行化)和De-serialization(解串行化)

串行化就是将并行数据转换为串行数据,然后通过差分信号传输出去。在串行化之前,需要经过加扰和8b/10b编码,原始数据的bit[7:0]表示为HGFEDCBA,额外用一比特Z表示是否属于控制码,经过8b/10b编码后,bit[9:0]为jhgfiedcba。
8b/10b编码后的结果
pcie采用的是小端模式,即先传低位然后再传高位。8b/10b编码中一个symbol在差分线上是10个比特。

下图是链路为x1时,传输OS的例子,由于是小端模式,最开始传输的是最低位a,最后传输的是最高位j。传输完一个symbol的时间就是symbol time,传输完一个比特的时间就是Unit Interval(单元间隔,简称UI)。在2.5GT/s的串行速率下,symbol time计算如下:2.5GT/s速率,一秒可以传输2.5GT比特数据,传输1比特数据的时间为1×109/(2.5×109)=0.4ns,由于8b/10b编码中,差分线上的一个symbol为10比特。所以2.5GT/s下,一个UI为0.4ns,一个symbol time就是0.4ns×10=4nsx1串行化的例子
下图是链路宽度为x4时,传输OS的例子,与x1传输基本一致,只是在多lane的link上,需要在所有lane上同时传输OS。
x4串行化的例子

guest
7 评论
内联反馈
查看所有评论
seruze

你好,請問 8b/10b 和 Scrambler 的功能是不是類似的?
為什麼需要同時做這2種功能呢?
Gen3 之後只使用 Scrambler, 是否說明 8b/10b 不是很有必要呢?
剛開始讀 pci,對此很困惑😖

seruze

@重新开始 真是很感謝您這麼詳細的回應,我會再好好研讀這方面的資料,再次感謝您的回答,也希望您能繼續推出 pcie 的文章分享,真的很受用,謝謝:)

Nephalem郑

翻译的很准确易懂,加扰那我想请教一下。8b/10b的加扰码循环的时候是类似D3<= D15 ^ D2这么循环的是么,加扰码只和自身的循环时间有关?
之前只接触过以太网的自同步加扰,pcie这个加扰感觉对两边的comma检测要求很严啊,错一点就一直对不上了

Nephalem郑

我说的不太清楚,我说的D3<= D15 ^ D2都是指的LSFR的寄存器,我是想说LFSR寄存器移位的时候下一时刻的D3/D4/D5是不是都是前一个数据和D15异或来的?我看图里好像是这个意思。

按照我对以太网的加扰公式的理解,G(X)=X16+X5+X4+X3+1应该指的是G(X)是LSPR寄存器的[15]/[4]/[3]/[2]/[0]相互异或得出的,而G(X)和当前数据bit异或得出当前bit的加扰后数据

错一点就对不上我是指比如comma检测的不同步或者底层传输数据整体串了,比如提前或者滞后1bit什么的,后面的解扰就会全错,不过我仔细想了想应该不会出现这种问题,真出现了肯定会有各种报错