笔记:从Aurora 8b/10b 到Aurora 64b/66b (二):64b/66b 基本知识+GT收发器的64b/66b模式+Gearbox同步代码

参考搬运:

https://mp.weixin.qq.com/s/ZSNyjpZpimjyxyO9riIRNQ

Aurora 64B/66B (xilinx.com)

https://docs.amd.com/r/en-US/pg074-aurora-64b66b

8/10:SATA SRIO

64/66:10G以太网

值得注意:

64b/66b 编码在多LANE模式下,EOF(T)仅在一个LANE上出现;

直接使用GT收发器,配置参考时钟的时候根据选择可以通过MMCM产生用户时钟,但是aurora系列IP倾向于使用BUFG,这和GT收发器中PLLREFCLK这个选项有关系;

在GT收发器的Aurora 64b/66b模式下,需要手动完成的一些操作:

1.加扰/解扰(仅加扰数据字段);

2.gearbox的一些控制信号;

如果直接使用Aurora 64b/66b IP的话,我想是可以直接通过axi-stream接口输入数据的;

相较于8b/10b (直接使用GT收发器配置的情况):

1.不需要控制编码和同步头,而是纯粹以帧的形式发送数据(而不是字符);

2.需要手动对齐;

3.需要的不是接收起始SOF,结束EOF,而是接收起始帧和结束帧,以帧的单位去操作数据;

同步代码和注释:

/****************************
阶段1:不断拉高滑块,直到某一次滑动的有效头数量达到要求,无效头数量为0,拉高BLOCKSYNC_OUT;
阶段2:如果这个过程中检测到无效头,则将计数器清零,开启下一次对齐;
每次滑动完成以后,
****************************/
`timescale 1ns / 1ps
`define DLY #1
module gtwizard_0_BLOCK_SYNC_SM #
(
    parameter SH_CNT_MAX         = 64,
    parameter SH_INVALID_CNT_MAX = 16 
)
(
    // User Interface
    output reg          BLOCKSYNC_OUT    ,// 对齐完成指示信号;
    output reg          RXGEARBOXSLIP_OUT,// 滑块
    input  wire [2:0]   RXHEADER_IN,
    input  wire         RXHEADERVALID_IN,
    // System Interface
    input  wire         USER_CLK,
    input  wire         SYSTEM_RESET
);
    

//**************************** Wire Declarations ******************************

    wire           slip_pulse_i;    
    wire           next_begin_c;
    wire           next_sh_invalid_c;    
    wire           next_sh_valid_c;    
    wire           next_slip_c;    
    wire           next_sync_done_c;    
    wire           next_test_sh_c;    
    wire           sh_count_equals_max_i;    
    wire           sh_invalid_cnt_equals_max_i;    
    wire           sh_invalid_cnt_equals_zero_i;    
    wire           slip_done_i;    
    wire           sync_found_i;    

//***************************External Register Declarations*************************** 

    reg            begin_r                      ;
    reg            sh_invalid_r                 ;    
    reg            sh_valid_r                   ;    
    reg    [31:0]  slip_count_i                 ; 
    reg            slip_r                       ;    
    reg            sync_done_r                  ; // 已经过32周期
    reg    [9:0]   sync_header_count_i          ; 
    reg    [9:0]   sync_header_invalid_count_i  ;   
    reg            test_sh_r                    ;    

//**************************** Main Body of Code *******************************

    // 
    assign sync_found_i   = (RXHEADER_IN[1:0] == 2'b01) || (RXHEADER_IN[1:0] == 2'b10);

    //________________________________ State machine __________________________    
    
    // State registers
    always @(posedge USER_CLK)
        if(SYSTEM_RESET)
            {begin_r,test_sh_r,sh_valid_r,sh_invalid_r,slip_r,sync_done_r}  <=  `DLY    6'b100000;
        else
        begin
            begin_r          <=  `DLY    next_begin_c;
            test_sh_r        <=  `DLY    next_test_sh_c;
            sh_valid_r       <=  `DLY    next_sh_valid_c;
            sh_invalid_r     <=  `DLY    next_sh_invalid_c;
            slip_r           <=  `DLY    next_slip_c;
            sync_done_r      <=  `DLY    next_sync_done_c;
        end

    // Next state logic
    assign  next_begin_c     =   sync_done_r 
                                 | (slip_r && slip_done_i)
                                 | (sh_valid_r && sh_count_equals_max_i && !sh_invalid_cnt_equals_max_i)
                                 | (sh_invalid_r && sh_count_equals_max_i && !sh_invalid_cnt_equals_max_i && BLOCKSYNC_OUT);                    
    /***************************************************************************************************
    新的开始:清零两个头的计数器的标志信号;
    触发条件:
    1.对齐完成;
    2.等待对移位信号的操作间隔结束;
    3.接收了足够数量的有效头,且无效头未满足指定数量;
    4.接收了足够数量的有效头,但也接收到了错误头,且已经完成了锁定;(重置锁定,重新对齐)
    ***************************************************************************************************/
   assign  next_test_sh_c    =  begin_r
                                | (test_sh_r && !RXHEADERVALID_IN)
                                | (sh_valid_r && !sh_count_equals_max_i)
                                | (sh_invalid_r && !sh_count_equals_max_i && !sh_invalid_cnt_equals_max_i && BLOCKSYNC_OUT);
    /***************************************************************************************************
    对头检测的需求标志信号;需要检测头的时候会一直为高;
    最开始拉高是一切重新开始的时候,此后每次检测到头拉低一个周期,如果没检测到需要的数量会在下一个时钟周期拉高;
    锁定状态下检测到错误信号的时候就又需要检测头了,从而重新拉高;
    触发条件:
    1.未接收到头有效,且需要判断头数值;( test_sh_r 和 next_test_sh 会在等待期间持续拉到,直到头输入)
    2.接收到无效头但未达到约定值;
    4.锁定状态下接收到无效头;
    ***************************************************************************************************/

   assign  next_sh_valid_c   =  (test_sh_r && RXHEADERVALID_IN && sync_found_i);
   /*
    接收到了正确的对齐头;
   */

   assign  next_sh_invalid_c =  (test_sh_r && RXHEADERVALID_IN && !sync_found_i);
   /*
    接收到了无效头;
   */

   assign  next_slip_c       = (sh_invalid_r && (sh_invalid_cnt_equals_max_i || !BLOCKSYNC_OUT))
                               | (sh_valid_r && sh_count_equals_max_i && ! sh_invalid_cnt_equals_zero_i && (sh_invalid_cnt_equals_max_i || !BLOCKSYNC_OUT))
                               | (slip_r && !slip_done_i);

    /***************************************************************************************************
    重新对齐信号/滑块信号:
    这个信号会直接拉低对齐锁定,重新开始对齐;
    1.未对齐状态下检测到无效头;
    2.检测有效头达到数量,但无效头计数器不为0,或未对齐;
    3.操作一次滑块信号后,在等待操作间隔结束以后拉低,此时 slip_r 也会因此拉低,直到12两种情况再次发生,才再次需要操作重复信号;
    这个信号的拉高意味着下一次对齐的开始,因为他会复位掉已经锁定的对齐LOCK,这也意味着重新对齐相位;
    最终会导致 RXGEARBOXSLIP_OUT 的重新输出,对齐重新开始了。
    如果 slip_r 为高, next_slip_c 会一直拉高直到 slip_done_i 完成;
    这也导致了 RXGEARBOXSLIP_OUT 会 32 个周期拉高一次,直到对齐的结束;
    ***************************************************************************************************/

   assign  next_sync_done_c  =  (sh_valid_r && sh_count_equals_max_i && sh_invalid_cnt_equals_zero_i);
   // 检测到有效达一定数量,同时无效头数量为0
   // 此时驱动 BLOCKSYNC_OUT 的拉高,指示完成了锁定;
 
    // 对齐对齐头计数
    always @(posedge USER_CLK)
        if(begin_r)  
        begin
            sync_header_count_i   <=  `DLY    10'd0;
        end
        else if (sh_valid_r || sh_invalid_r)
        begin
            sync_header_count_i  <=  `DLY    sync_header_count_i + 10'd1;
        end

    assign sh_count_equals_max_i = (sync_header_count_i==SH_CNT_MAX);//检测到足够多有效头的时候拉高;
    
     
    // 无效对齐头计数
    always @(posedge USER_CLK)
        if(begin_r)  
        begin
            sync_header_invalid_count_i   <=  `DLY    10'd0;
        end
        else if (sh_invalid_r)
        begin
            sync_header_invalid_count_i  <=  `DLY    sync_header_invalid_count_i + 10'd1;
        end

    assign sh_invalid_cnt_equals_max_i  = (sync_header_invalid_count_i==SH_INVALID_CNT_MAX);
    assign sh_invalid_cnt_equals_zero_i = (sync_header_invalid_count_i==0);

    //_______ Counter wait for 16 cycles to ensure that slip is complete _______    
    // next_slip_c 信号的上升沿
    assign slip_pulse_i = next_slip_c && !slip_r;

    always @(posedge USER_CLK)
        RXGEARBOXSLIP_OUT   <=  slip_pulse_i;

    //_____________ Ouput assignment to indicate block sync complete  _________    
    // 操作 RXGEARBOXSLIP_OUT 的间隔是 32个时钟周期;
    always @(posedge USER_CLK)
        if(!slip_r) slip_count_i   <=  `DLY    32'h00000000;
        else        slip_count_i   <=  `DLY    {slip_count_i[30:0],RXGEARBOXSLIP_OUT};

    assign slip_done_i = slip_count_i[31];


    //_____________ Pulse GEARBOXSLIP port to slip the data by 1 bit  _________    

    always @(posedge USER_CLK)
        if(SYSTEM_RESET || slip_r)  BLOCKSYNC_OUT   <=  `DLY    1'b0;
        else if (sync_done_r)       BLOCKSYNC_OUT   <=  `DLY    1'b1;     
endmodule
View Code

介绍

8B10B的开销比较大,每传输10位数据,就需要发送2位无效数据。

为了减小8B10B编码的开销,同时保留编码方案的优点,提出了64B66B编码。

64B66B编码与8B10B编码方式有本质区别,8B10B编码可以从码表中获取一个数据编码结果,而64B66B在发送前需要通过白噪声对数据加扰,在接收数据时也要先对数据解扰。

用户在实际使用时,GTX的64B66B编码会比8B10B更简单,因为64B66B会提供控制帧和数据帧,相比8B10B会简单一点点。

64B66B编码原理

下图是万兆网的数据编码框图,将两个32位的TXD数据拼接为64位,

然后经过加扰(Scrambling)来保证零一均衡,避免直流失调和时钟恢复困难。

之后在64位数据之前加入2位的同步头(Sync header),用来指示后面的64位数据是数据帧还是控制帧,

用户每发送64位数据,高速收发器需要传输64位数据和2位同步头。

变速器(Gearbox)可以看成一个深度为64位的存储器,

每个用户时钟输入64位数据,高速收发器在一个用户时钟内也只能发送64位编码后的数据,就会导致每个时钟会有2位数据没有被发送,暂存在变速器(Gearbox)。

经过32个时钟后,变速器(Gearbox)就会存满64位数据,用户下个时钟周期需要暂停输入数据,高速收发器在下个时钟将变速器(Gearbox)的64位数据发送,之后用户就可以继续输入需要发送的数据了,就这样循环往复。

编码过程是由加扰器(Scrambling)完成的,因此相同的数据经过加扰后会得到不同的数据,编码后的数据是不能预测的。

2位同步头有两个作用,可以用来指示帧类型,接收端也可以根据同步头来实现数据对齐,

同步头只有2’b01和2’b10两种取值,其余两种为无效取值。

01类型是需要全部加扰的,而10类型的TYPE部分则是不需要加扰的,因为涉及了一些指示字符;

当同步头为2’b01时,表示后面的64位数据是纯数据,不包含任何控制字符,如下图所示。

图2 纯数据帧格式

如果同步头为2’b10,则表示后面的64位数据是控制帧,可能是起始帧,也可能是结束帧,

如下图所示,注意同步码后面的第一字节数据表示控制帧的类型,根据该数据的值确定该帧数据内容。

下图是上面几种帧的一些格式,

D表示数据,C表示空闲字符(7位数据)或控制字符Z,

S表示帧起始字符,T表示帧结束字符。

其中当同步码为2’b01时,8字节全部为数据。

64B66B编码的起始帧有两种格式,

一种是起始位位于第一个字节,对应的类型字符为8’h78,

另一种是起始位位于第五字节,对应的类型字符为8’h33。

但是控制字符的具体数值可能不需要关心,因为不同协议之间是有差异的;

黄色部分是无效区,填充0;

RS Trans指的是RS(协调子层)发送的数据,D Z T S E均是其中的标识符;

D 数据

Z 空闲

T 结束

S 起始

E 控制

因为控制字符都是七位的,所以需要补0;每有一个数据字符,就可以少补充一个0;

夹带控制字符的帧最多只能传递7个字节的D;

由于用户每次可以传输任意字节数据,导致停止位可能出现在数据的任何字节,因此结束帧会有8种类型,类型字符不相同,接收端可以根据结束帧的类型判断这帧有多少有效数据。

下表是这些控制字符的具体取值,比如起始位S的取值是8’hfb,停止位T的取值是8’hfd,空闲字符C为8’h07。

在不同的TYPE类型下,空闲字符C的填充是可能不同的;

IDLE类型下填充的是7'h0,

ERROR类型下填充的是7'h1e,

开始和结束字符填充的字符根据具体的类型来决定,这是因为

开始类型的包有两种,而结束的有8种;

官方加/解扰代码

加扰和解扰一般使用的表达式为X^58+X^19+1,这部分内容可以在后续的示例工程中直接获取,也比较简单,实现方式与M序列类似。

`timescale 1ns / 1ps
`define DLY #1

//***********************************Entity Declaration*******************************

module gtwizard_0_SCRAMBLER #
( 
    parameter TX_DATA_WIDTH = 32
)
(
    // User Interface
    input  wire  [(TX_DATA_WIDTH-1):0] UNSCRAMBLED_DATA_IN,
    input  wire                        DATA_VALID_IN,
    output reg   [(TX_DATA_WIDTH-1):0] SCRAMBLED_DATA_OUT,

    // System Interface
    input  wire          USER_CLK,
    input  wire          SYSTEM_RESET
);


//***************************Internal Register Declarations******************** 

    integer                        i;
    reg     [57:0]                 poly;
    reg     [(TX_DATA_WIDTH-1):0]  scrambled_data_i;
    reg     [57:0]                 scrambler;
    reg     [(TX_DATA_WIDTH-1):0]  tempData;
    reg                            xorBit;

//*********************************Main Body of Code***************************

    always @(scrambler,UNSCRAMBLED_DATA_IN)
    begin
        poly = scrambler;
        for (i=0;i<=(TX_DATA_WIDTH-1);i=i+1)
        begin
            xorBit = UNSCRAMBLED_DATA_IN[i] ^ poly[38] ^ poly[57];
            poly = {poly[56:0],xorBit};
            tempData[i] = xorBit;
        end
    end  

    //________________ Scrambled Data assignment to output port _______________    

    always @(posedge USER_CLK)
    begin
        if (SYSTEM_RESET)
        begin
            SCRAMBLED_DATA_OUT <= `DLY  'h0;
            scrambler          <= `DLY  58'h155_5555_5555_5555;
        end
        else if (DATA_VALID_IN)
        begin
            SCRAMBLED_DATA_OUT <= `DLY  tempData;
            scrambler          <= `DLY  poly;
        end
    end
         
endmodule
View Code
`timescale 1ns / 1ps
`define DLY #1

//***********************************Entity Declaration*******************************

module gtwizard_0_SCRAMBLER #
( 
    parameter TX_DATA_WIDTH = 32
)
(
    // User Interface
    input  wire  [(TX_DATA_WIDTH-1):0] UNSCRAMBLED_DATA_IN,
    input  wire                        DATA_VALID_IN,
    output reg   [(TX_DATA_WIDTH-1):0] SCRAMBLED_DATA_OUT,

    // System Interface
    input  wire          USER_CLK,
    input  wire          SYSTEM_RESET
);


//***************************Internal Register Declarations******************** 

    integer                        i;
    reg     [57:0]                 poly;
    reg     [(TX_DATA_WIDTH-1):0]  scrambled_data_i;
    reg     [57:0]                 scrambler;
    reg     [(TX_DATA_WIDTH-1):0]  tempData;
    reg                            xorBit;

//*********************************Main Body of Code***************************

    always @(scrambler,UNSCRAMBLED_DATA_IN)
    begin
        poly = scrambler;
        for (i=0;i<=(TX_DATA_WIDTH-1);i=i+1)
        begin
            xorBit = UNSCRAMBLED_DATA_IN[i] ^ poly[38] ^ poly[57];
            poly = {poly[56:0],xorBit};
            tempData[i] = xorBit;
        end
    end  

    //________________ Scrambled Data assignment to output port _______________    

    always @(posedge USER_CLK)
    begin
        if (SYSTEM_RESET)
        begin
            SCRAMBLED_DATA_OUT <= `DLY  'h0;
            scrambler          <= `DLY  58'h155_5555_5555_5555;
        end
        else if (DATA_VALID_IN)
        begin
            SCRAMBLED_DATA_OUT <= `DLY  tempData;
            scrambler          <= `DLY  poly;
        end
    end
         
endmodule
View Code

Gearbox

GTX的64B66B编码发送原理

GTX内部不能对待发送数据加扰,也不能对接收的数据解扰,需要用户在FPGA逻辑中自己完成加扰和解扰,此处加扰由LFSR实现。

由下图可知,8B10B经过蓝色走线后到达FIFO,而64B66B编码只经过了一个TX Gearbox,并没有经过什么编码模块,因此加扰和解扰相关操作需要用户在IP外部自己完成。

TX Gearbox的作用就是前文所说的变速器,工作方式如下图所示,

如果用户数据位宽设置成32位,并且PCS每次也只能传输32位数据,两个时钟才能发送一个64位数据。

因此需要两个时钟用户才能发送64位数据,

第一个时钟向GTX发送2位同步码和32位数据,变速箱先发送2位同步码和高30位数据,第2位数据留在TX Gearbox中。

第二个时钟用户发送剩余32位数据,TX Gearbox需要先把上个时钟剩余2位数据发送,然后发送本次接收的高30位数据,最后还是会剩余2位数据。

因此每经过2个时钟,TX Gearbox中就会增加2位数据,

当经过64个时钟后,TX Gearbox中存在64位数据,与一个用户数据位宽一致。

后两个时钟周期用户不能往GTX发送数据,TX Gearbox会将内部64位数据发送出去,完成清空。

在使用64B66B编码时,一般用户端口位宽使用64位,会更方便,原理都是一样的。

所以本质上是66个周期发送64个64位数据+2x64位gearbox,其中用户直接使用的是前64个。(用户数据位宽是32位的时候)

如果用户数据的位宽是64位,则是33个周期用户提供32次数据,最后一个周期由gearbox发送剩下的一个64位数据;(这对于接收来说也是要考虑的)

 gearbox显然具有计数器,使用计数器来标记何时将其中的数据取出;

这个计数器可以使用内部的也可以使用外部的,这个设置在GT收发器中是可以修改的;

外部计数器需要用户提供和数据调控;

 在GT后面的版本里内部计数器被版本迭代取消,推荐使用外部计数器;

 

上述会有一个问题,Gearbox如何知道是否该清除内部数据,用户怎么知道何时停止发送数据呢?
这就需要一个计数器,GTX内部可以提供这个计数器,用户也可以向GTX提供计数器,
但是后续GTH等高速收发器都不支持内部计数器的方案,
因此在使用时还是推荐使用外部计数器,与其他收发器统一。

 

如果把用户数据带宽设置成64,这种情况下GTX内部的数据带宽只有32,我觉得会出现之前描述的USERCLK是USERCLK2两倍频率的情况;(确实如此,在SUMMARY中可以看到)

 

 

 GTX的64B66B编码接收原理

GTX的接收通道也有一个RX Gearbox把接收的66位数据转换位2位同步头和64位数据输出给用户,

下图中红色走线是使用64B66B编码信号的路径,蓝色是不使用64B66B是的路径。注意接收通道内部是没有解扰器的,需要用户在GTX外部自己解扰。

GTX使用64B66B编码时,接收端与用户端口信号连接如下图所示,包含数据信号RXDATA、数据有效指示信号RXDATAVALID、同步头RXHEADER[2:0]、同步头有效指示信号RXHEADRVALID、RXSTARTOFSEQ、滑块对齐信号RXGEARBOXSLIP。

 

posted @ 2024-07-31 19:45  NoNounknow  阅读(20)  评论(0编辑  收藏  举报