目录
1.8B/10B介绍
2.原理
3.Verilog实现
4.实例:在JESD204B中的应用
参考资料:
8B/10B编码的目的是防止串行的数据出现长时间的连0连1,因为这会使得信号直流电压不稳定,换句话说就是 直流平衡keep DC-balance;另一个目的是提供给接收端‘清晰的边界’,时钟恢复:接收端能在串行数据流中确定某个10B的起始边界;而且接收端还能判断数据是否出错,能检错不能纠错。
值得一提的是,除了检错外,还有上述前2个功能的是扰码:scramble,扰码没有冗余比特出现,基本原理是使用线性反馈移位寄存器LFSR实现,这里不做详细介绍哈。
8B/10B编码广泛应用于串行数据总线, 例如SATA,PCIE,USB,JESD204接口中,后来升级成冗余更小的64/66B,128/132B,128/130B,升级后的版本也或应用于上述总线的新版本中。
简单来说就是5B映射成6B,3B映射成4B,分为数据码和控制码的映射,在映射的过程中进行查表。
8b/10b编码输出比特数目总共是10个比特,但只有“+2”“+0”“-2”三种组合。其中“+2”是指4个比特0,与6个比特1;“+0”是指5个比特“0”,与5个比特“1”;“-2”是指6个比特“0”,与4个比特“1”,利用这种“不均等性— Disparity”的特性而具有强大的直流平衡(DC Balance)功能,可使得发送的“0”、“1”数量保持一致,连续的“1”或“0”基本上不超过5位。[1]
数据值可以统一的表示为DX.Y或KX.Y,其中D表示为数据代码,K表示为特殊的命令代码,X表示输入的原始数据的低5位EDCBA,Y 表示输入的原始数据的高3位HGF。[2]
1)running disparity
running disparity简称RD,表示当前的0与1的不均等性,进行数据映射时要根据 input 的 RD去映射,使映射后的数据具有parity,并且还要计算映射后的RD,以作为下一次映射的input RD。
因此在代码实现时,一次8Bto10B的过程中要计算两次RD:5Bto6B后,3Bto4B后。计算方式如表1[1]。
2)映射
数据码和控制码的都是互不相同的,即映射后的10B要么是数据码,要么是控制码。
对于8bit数据,它在表中的位序为HGFEDCBA,即H为最高位,A为最低位,EDCBA经过5B/6B编码为abcdei,HGF经过3B/4B编码为fghj。传送10bit编码的顺序为abcdeifghj。[3]
表2-4为映射查找表。下面解释一下,
“+”代表也用于K.x.7的映射,但从表4可以看出来其fhg的映射是D.x.A7,而数据码的映射是Dx.P7,因此不会与数据码相同。
“++”表示只用于控制码的映射,保证了与数据码的不同性。
“+”:B数据码Dx.7的映射一般都用Dx.P7,只有下列情况才用D.x.A7,为的是防止出现5个连0或连1:
‡ Only K.28.1, K.28.5, and K.28.7 generate comma symbols, that contain a bit sequence of five 0s or 1s.
The symbol has the format 110000 01xx or 001111 10xx.[1]
“+”K.28.1, K.28.5, and K.28.7 are "comma symbols",会出现5个连0或连1,就是用这个comma symbols来从串行的比特流中同步/定位一个10B的开始位。K.28.7 不用的话 ,正常情况下00111110 or 11000001 是不会出现在比特流中的。
注意到K.23.7,K.27.7,K.29.7,K.30.7用的是D.x.A7,与防止连0或连1的x并不相同,因此不会与数据码相同。
‡ If K.28.7 is allowed in the actual coding, a more complex definition of the synchronization pattern than suggested by † needs to be used, as a combination of K.28.7 with several other codes forms a false misaligned comma symbol overlapping the two codes. A sequence of multiple K.28.7 codes is not allowable in any case, as this would result in undetectable misaligned comma symbols.(看不懂,用了28.7之后要定义新的同步方式,否则会产生错误?)
K.28.7 is the only comma symbol that cannot be the result of a single bit error in the data stream.[1]
主要分为两阶段 。每一阶段映射的时候都要根据RD进行选择,还要判断是否是控制字is_k。
(1)5Bto6B,算RD;
(2)3Bto4B,算RD。
代码主要参考Xilinx的vivado的JESD204B核的example design的仿真代码,我提取出来了写成一个function,读者可以把function中的组合逻辑拆分一下变成流水线,易于综合。
module code8to10(
input clk,
input rstn,
input [7:0] i_dat,
input i_is_k,
output reg [9:0] o_dat
);
reg dp_out;
always@(posedge clk,negedge rstn)
if(~rstn){dp_out, o_dat} <= 0;
else {dp_out, o_dat} <= dp_q10(i_dat,i_is_k,dp_out);
function [10:0] dp_q10;
input [7:0] d8;
input is_k;
input disparity_pos_in;
reg [5:0] b6;
reg [3:0] b4;
reg k28, pdes6, a7, l13, l31, a, b, c, d, e;
reg [9:0]q10;
reg disparity_pos_out;
integer I;
begin // encode_8b10b
// precalculate some common terms
a = d8[0];
b = d8[1];
c = d8[2];
d = d8[3];
e = d8[4];
k28 = is_k && d8[4:0] === 5'b11100;
l13 = (((a ^ b) & !(c | d)) //1个1,3个0
| ((c ^ d) & !(a | b)));
l31 = (((a ^ b) & (c & d)) //3个1,1个0
| ((c ^ d) & (a & b)));
a7 = is_k | ((l31 & d & !e & disparity_pos_in) //3B/4B编码时用来判断使用DX.P7还是DX.A7
| (l13 & !d & e & !disparity_pos_in));
/***************************************************************************************************************/
//----------------------------------------------------
// Do the 5B/6B conversion (calculate the 6b symbol)
//----------------------------------------------------
if (k28) //K.28
if (!disparity_pos_in)//0代表RD=-1
b6 = 6'b111100;
else
b6 = 6'b000011;
else
case (d8[4:0])
5'b00000 : //D.0
if (disparity_pos_in)
b6 = 6'b000110;
else
b6 = 6'b111001;
5'b00001 : //D.1
if (disparity_pos_in)
b6 = 6'b010001;
else
b6 = 6'b101110;
5'b00010 : //D.2
if (disparity_pos_in)
b6 = 6'b010010;
else
b6 = 6'b101101;
5'b00011 :
b6 = 6'b100011; //D.3
5'b00100 : //-D.4
if (disparity_pos_in)
b6 = 6'b010100;
else
b6 = 6'b101011;
5'b00101 :
b6 = 6'b100101; //D.5
5'b00110 :
b6 = 6'b100110; //D.6
5'b00111 : //D.7
if (!disparity_pos_in)
b6 = 6'b000111;
else
b6 = 6'b111000;
5'b01000 : //D.8
if (disparity_pos_in)
b6 = 6'b011000;
else
b6 = 6'b100111;
5'b01001 :
b6 = 6'b101001; //D.9
5'b01010 :
b6 = 6'b101010; //D.10
5'b01011 :
b6 = 6'b001011; //D.11
5'b01100 :
b6 = 6'b101100; //D.12
5'b01101 :
b6 = 6'b001101; //D.13
5'b01110 :
b6 = 6'b001110; //D.14
5'b01111 : //D.15
if (disparity_pos_in)
b6 = 6'b000101;
else
b6 = 6'b111010;
5'b10000 : //D.16
if (!disparity_pos_in)
b6 = 6'b110110;
else
b6 = 6'b001001;
5'b10001 :
b6 = 6'b110001; //D.17
5'b10010 :
b6 = 6'b110010; //D.18
5'b10011 :
b6 = 6'b010011; //D.19
5'b10100 :
b6 = 6'b110100; //D.20
5'b10101 :
b6 = 6'b010101; //D.21
5'b10110 :
b6 = 6'b010110; //D.22
5'b10111 : //D/K.23
if (!disparity_pos_in)
b6 = 6'b010111;
else
b6 = 6'b101000;
5'b11000 : //D.24
if (disparity_pos_in)
b6 = 6'b001100;
else
b6 = 6'b110011;
5'b11001 :
b6 = 6'b011001; //D.25
5'b11010 :
b6 = 6'b011010; //D.26
5'b11011 : //D/K.27
if (!disparity_pos_in)
b6 = 6'b011011;
else
b6 = 6'b100100;
5'b11100 :
b6 = 6'b011100; //D.28
5'b11101 : //D/K.29
if (!disparity_pos_in)
b6 = 6'b011101;
else
b6 = 6'b100010;
5'b11110 : //D/K.30
if (!disparity_pos_in)
b6 = 6'b011110;
else
b6 = 6'b100001;
5'b11111 : //D.31
if (!disparity_pos_in)
b6 = 6'b110101;
else
b6 = 6'b001010;
default :
b6 = 6'bXXXXXX;
endcase // case(d8[4:0])
// reverse the bits
for (I = 0; I < 6; I = I + 1)
q10[I] = b6[I];
// calculate the running disparity after the 5B6B block encode
//计算previous RD 加上现在的6B后的RD
if (k28)
pdes6 = !disparity_pos_in;
else
case (d8[4:0])
5'b00000 : pdes6 = !disparity_pos_in;
5'b00001 : pdes6 = !disparity_pos_in;
5'b00010 : pdes6 = !disparity_pos_in;
5'b00011 : pdes6 = disparity_pos_in;
5'b00100 : pdes6 = !disparity_pos_in;
5'b00101 : pdes6 = disparity_pos_in;
5'b00110 : pdes6 = disparity_pos_in;
5'b00111 : pdes6 = disparity_pos_in;
5'b01000 : pdes6 = !disparity_pos_in;
5'b01001 : pdes6 = disparity_pos_in;
5'b01010 : pdes6 = disparity_pos_in;
5'b01011 : pdes6 = disparity_pos_in;
5'b01100 : pdes6 = disparity_pos_in;
5'b01101 : pdes6 = disparity_pos_in;
5'b01110 : pdes6 = disparity_pos_in;
5'b01111 : pdes6 = !disparity_pos_in;
5'b10000 : pdes6 = !disparity_pos_in;
5'b10001 : pdes6 = disparity_pos_in;
5'b10010 : pdes6 = disparity_pos_in;
5'b10011 : pdes6 = disparity_pos_in;
5'b10100 : pdes6 = disparity_pos_in;
5'b10101 : pdes6 = disparity_pos_in;
5'b10110 : pdes6 = disparity_pos_in;
5'b10111 : pdes6 = !disparity_pos_in;
5'b11000 : pdes6 = !disparity_pos_in;
5'b11001 : pdes6 = disparity_pos_in;
5'b11010 : pdes6 = disparity_pos_in;
5'b11011 : pdes6 = !disparity_pos_in;
5'b11100 : pdes6 = disparity_pos_in;
5'b11101 : pdes6 = !disparity_pos_in;
5'b11110 : pdes6 = !disparity_pos_in;
5'b11111 : pdes6 = !disparity_pos_in;
default : pdes6 = disparity_pos_in;
endcase // case(d8[4:0])
/***************************************************************************************************************/
//3Bto4B
case (d8[7:5])
3'b000 : //D/K.x.0
if (pdes6)
b4 = 4'b0010;
else
b4 = 4'b1101;
3'b001 : //D/K.x.1
if (k28 && !pdes6)
b4 = 4'b0110;
else
b4 = 4'b1001;
3'b010 : //D/K.x.2
if (k28 && !pdes6)
b4 = 4'b0101;
else
b4 = 4'b1010;
3'b011 : //D/K.x.3
if (!pdes6)
b4 = 4'b0011;
else
b4 = 4'b1100;
3'b100 : //D/K.x.4
if (pdes6)
b4 = 4'b0100;
else
b4 = 4'b1011;
3'b101 : //D/K.x.5
if (k28 && !pdes6)
b4 = 4'b1010;
else
b4 = 4'b0101;
3'b110 : //D/K.x.6
if (k28 && !pdes6)
b4 = 4'b1001;
else
b4 = 4'b0110;
3'b111 : //D.x.P7
if (!a7)
if (!pdes6)
b4 = 4'b0111;
else
b4 = 4'b1000;
else //D/K.y.A7
if (!pdes6)
b4 = 4'b1110;
else
b4 = 4'b0001;
default :
b4 = 4'bXXXX;
endcase
// Reverse the bits
for (I = 0; I < 4; I = I + 1)
q10[I+6] = b4[I];
// Calculate the running disparity after the 4B group
//计算6B加上4B之后的 RD
case (d8[7:5])
3'b000 : disparity_pos_out = ~pdes6;
3'b001 : disparity_pos_out = pdes6;
3'b010 : disparity_pos_out = pdes6;
3'b011 : disparity_pos_out = pdes6;
3'b100 : disparity_pos_out = ~pdes6;
3'b101 : disparity_pos_out = pdes6;
3'b110 : disparity_pos_out = pdes6;
3'b111 : disparity_pos_out = ~pdes6;
default : disparity_pos_out = pdes6;
endcase
/***************************************************************************************************************/
dp_q10 = {disparity_pos_out,q10 };//输出
end
endfunction // encode_8b10b
endmodule
testbench:
module code8to10_sim;
parameter CLK_PERIOD = 10; //仿真周期10ns=100M
parameter RST_CYCLE = 5; //复位周期数
parameter RST_TIME = RST_CYCLE * CLK_PERIOD;
// Fixed symbols
localparam pK_is_r = 8'h1C; // K28_0
localparam pK_is_a = 8'h7C; // K28_3
localparam pK_is_q = 8'h9C; // K28_4
localparam pK_is_k = 8'hBC; // K28_5
reg clk;
reg rstn;
reg [7:0] sim_idat;
reg i_is_k;
initial
begin
clk = 0;
rstn = 1;
#RST_TIME rstn = 0;
#RST_TIME rstn = 1;
end
always #(CLK_PERIOD/2) clk = ~clk;
initial
begin
sim_idat = 0;
i_is_k = 0;
#(RST_TIME*2) i_is_k = 1;
#(CLK_PERIOD*4)i_is_k = 0;
end
always@(posedge clk)
begin
if(rstn)
begin
if(!i_is_k)
sim_idat <= {$random}%256;
else
sim_idat <= pK_is_k;
end
end
wire [9:0] o_dat;
code8to10 code8to10_u (
.clk(clk ),
.rstn(rstn ),
.i_dat(sim_idat ),
.i_is_k(i_is_k ),
.o_dat(o_dat)
);
JESD204B的数据链路层中,是串行单比特流,RX通过拉高信号SYNC向TX请求发送数据,这时TX需要先连续发送K28.5控制码(称为codegroup synchronization,CGS代码组同步),RX在接收到至少4个连续的K28.5后在特定的时间将SYNC拉低,证明收发同步,RX能在比特流中正确定位10B的起始位置。
JESD204B的串行比特流不是只有1条,是有多条的,为了让多个比特流(称为lane,通道)之间同步,需要使所有的通道都发送一组同样的数据,称为initial lane alignment ILA,初始化通道对齐序列。ILA包括一些控制数据,他们之间就是用特定的控制码做间隔的,具体是这样做的:因为不同lane的数据到达有先有后,将接收到的lane缓存下来,当所有lane都接收到同样的数据时,在一个特定的时间点,同时读取缓存中的数据。最后给出ILA的数据内容图。
有用的话就点个赞吧!
[1]https://en.wikipedia.org/wiki/8b/10b_encoding
[2]线路/信道编码技术(1)——8B/10B编码
[3]8B/10B编码