笔记:从Aurora 8b/10b 到Aurora 64b/66b (二):64b/66b 基本知识+GT收发器的64b/66b模式+Gearbox同步代码
参考搬运:
https://mp.weixin.qq.com/s/ZSNyjpZpimjyxyO9riIRNQ
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,而是接收起始帧和结束帧,以帧的单位去操作数据;
同步代码和注释:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/**************************** 阶段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
介绍
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序列类似。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
`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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
`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
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。