参考文献
[1] https://blog.csdn.net/qq_37654178/article/details/112391245
[2] https://ieeexplore.ieee.org/document/5390392
//8b10b编码原论文
前言
在高速串行通信中,信号可能会受到噪声、衰减和干扰等影响,在实际应用中可以使用8b10b等编码技术增加信号的抗干扰能力,提高了数据传输的可靠性,使得高速串行通信更加稳定和可靠。8b/10b方式最初由IBM公司于1983年发明并应用于ESCON (200M互连系统),由Al Widmer和Peter Franaszek在IBM的刊物“研究与开发”上描述。本文基于查找表实现8b10b编码。
一、8b10b编码
顾名思义,8b10b编码是在高速串行通信中将8bit的数据映射成10bit的码字进行传输。直观看来,8b10b编码带来了20%的数据冗余,那舍弃效率带来的好处是什么呢?
8b10b编码最直接的好处是实现了直流均衡。在高速串行传输中,通常采用交流耦合方式,发送和接收端之间串联电容,利用电容“通交隔直”的特性去除信号的直流偏置,使得接收和发送端处于同一电平标准。但万物有利弊,当串行数据流出现多个连续的0或1时,可以认为该时间段信号是直流的,直流信号被滤除,幅度降低,导致接收端无法正确识别0和1,使得解码失败。
这里回顾下电容“通交隔直”的原理。根据理想电容阻抗公式
Z
c
=
1
2
π
f
c
Z_c = \frac{1}{2{\pi}fc}
Zc=2πfc1
信号频率越高,电容阻抗越低,当信号为直流时,f=0,此时电容阻抗趋近于无穷,信号无法通过。
那8b10b编码又是如何实现这个直流均衡的呢?核心思路便是不让串行数据出现连续的5个0或1。首先我们先了解下8b10b编码的组成,8b10b编码包含256数据码和12控制码,8bit原始数据对应256个数据码加上12个控制码,而编码后的10bit数据有1024个码,因此8b10b实际上就是从1024个码中挑选合适的码来表示数据码和控制码,使得编码后的码字0和1的数量尽可能相等且不出现过长的0或1。
二、基本原理
假设原始8bit数据由高到低用HGFEDCBA表示,8b10b编码先将8bit数据分成高3位HGF和低5位EDCBA两部分,然后经过3b4b编码将高3位映射到fghj;同时低5位映射成abcdei。最后合并成abcdeifghj发送,发送为小端发送,低位a先发送。
由上文可知,8b10b编码可以分成控制码D.x.y和数据码K.x.y,其中x表示为低五位的十进制数值,y表示为高三位的十进制数值,例如D.1.0表示的时8bit数000_00001代表的原始数据码。顾名思义,数据码是对数据进行编码,而控制码是用来标识传输的开始、结束等状态,控制码也称为K码或者是Comma码,用于高速传输接收端的数据对齐和恢复。比如8b10b编码的协议通常使用K28.5(正码10’b1110000011,负码10’b0001111100)来作为对齐码。
以下表1是对低5位进行数据编码
以下表2是对高3位进行数据编码
以下表3是8b10b的部分编码
以下表4是12种控制码的编码
由上面三个编码表,我们可以发现同个8位的数据有可能会有两种不同的编码方式,下面将介绍如何判断应该选择哪种编码。首先啊我们先了解“disparity”和“running disparity(RD)”的概念。
disparity为不一致性或者极性,表示编码后的数据中“0”和“1”的个数差值。由上表可以的得到编码后的数据极性只存在三种情况,分别为“+2”(1比0多两个),“0”(0和1一样多),“-2”(1比0少两个)。我们称极性为0的编码称为平衡码。
RD为运行不一致性或者极性偏差,RD是数据流的极性特性,是当前时刻之前所有数据极性的统计,如果之前数据流中1比0多,则RD为正,否则为负。8b10b编码便是根据串行数据流的RD特性进行控制编码,改善编码的直流特性。举例说明,见表1,比如说当前时刻要传输数据的低5位为D.00,如果之前数据流1比0多RD为正时,此时编码应该增加0的个数,D.00应该编码为6’b011000,反之编码为6’b100111。下图为RD状态转移图。
如下表Rules for running disparity,也就是RD的选择原则。由该表格可以得到三种规律。一是当编码的极性Disparity为0时,previous RD和next RD保持不变。二是当编码的极性Disparity可为 ± 2 \pm2 ±2且previous RD为-1时,编码极性选择+2,next RD与previous RD取反。三是当编码的极性Disparity可为 ± 2 \pm2 ±2且previous RD为+1时,编码极性选择-2,next RD与previous RD取反。
对8b10b编码举例说明,当当前数据为D.0.00,先发送低5位,再发送高3位。初始化RD为负,对于低5位来说,previous RD = -1(0多),则低5位5’b00000编码6’b100111(disparity = +2,1多),此时next RD = +1(1多)。后续发送高3位,对于高3位来说,previous RD = +1,则高3位3’b000编码为4’b0100(disparity = -2,0多),此时next RD = -1(0多)。对比表3可以知道,此处推导D.0.00(RD-)的编码为10’b100111_0100,推导成功。
三、实现
8b10b编码
module encode_8bto10b(
input clk,
input rst_n,
input din_en, //输入使能
input is_k, //是否为k码
input [7:0] din_8b, //输入的8bit数据
output reg [9:0] dout_10b //输出的10bit数据
);
reg RD_pre,RD_next;
always @(posedge clk or negedge rst_n)begin
if(!rst_n) {RD_next,dout_10b} <= 'd0;
else if(din_en) {RD_next,dout_10b} <= code_10b(is_k,RD_pre,din_8b);
else {RD_next,dout_10b} <= {RD_next,dout_10b};
end
always @(posedge clk) RD_pre <= RD_next;
function [10:0] code_10b;
input is_k;
input RD_pre; //上一次的RD
input [7:0] din_8b;
//中间寄存器
reg RD_mid; //经过6b编码后的RD
reg is_k28;
reg [5:0] b6;
reg [3:0] b4;
reg is_P7,A7_cnd1,A7_cnd2;
integer i; //循环变量
//输出
reg [9:0] dout_10b;
reg RD_next; //经过4b编码后的RD
begin
//判断是否为控制码
is_k28 = is_k && (din_8b[4:0] == 5'd28);
//判断是用P7编码还是A7编码,数据码一般使用P7编码,只有在下列情况才用A7编码,目的是防止出现5个连0或者连1
//if RD=-1, 5b = 17,18,20
//if RD=+1, 5b = 11,13,14
A7_cnd1 = (!RD_pre) & (din_8b[7:3] == 5'b11110 & (din_8b[2:0] == 3'b001 | din_8b[2:0] == 3'b010 | din_8b[2:0] == 3'b100));
A7_cnd2 = (RD_pre) & (din_8b[7:3] == 5'b11101 & (din_8b[2:0] == 3'b011 | din_8b[2:0] == 3'b101 | din_8b[2:0] == 3'b110));
is_P7 = !(is_k | A7_cnd1 | A7_cnd2);
//5b to 6b code
if(is_k28) begin
b6 = (RD_pre)?6'b110000:6'b001111; //abcdei
RD_mid = ~RD_pre;
end
else
case(din_8b[4:0]) //input EDCBA output abcdei
5'd0 :begin b6 = (RD_pre)? 6'b011000:6'b100111; RD_mid = ~RD_pre; end //RD_mid是否反向的判断依据是,编码后有是否为完美编码(01平衡)
5'd1 :begin b6 = (RD_pre)? 6'b100010:6'b011101; RD_mid = ~RD_pre; end
5'd2 :begin b6 = (RD_pre)? 6'b010010:6'b101101; RD_mid = ~RD_pre; end
5'd3 :begin b6 = 6'b110001; RD_mid = RD_pre; end
5'd4 :begin b6 = (RD_pre)? 6'b001010:6'b110101; RD_mid = ~RD_pre; end
5'd5 :begin b6 = 6'b101001; RD_mid = RD_pre; end
5'd6 :begin b6 = 6'b011001; RD_mid = RD_pre; end
5'd7 :begin b6 = (RD_pre)? 6'b000111:6'b111000; RD_mid = RD_pre; end
5'd8 :begin b6 = (RD_pre)? 6'b000110:6'b111001; RD_mid = ~RD_pre; end
5'd9 :begin b6 = 6'b100101; RD_mid = RD_pre; end
5'd10:begin b6 = 6'b010101; RD_mid = RD_pre; end
5'd11:begin b6 = 6'b110100; RD_mid = RD_pre; end
5'd12:begin b6 = 6'b001101; RD_mid = RD_pre; end
5'd13:begin b6 = 6'b101100; RD_mid = RD_pre; end
5'd14:begin b6 = 6'b011100; RD_mid = RD_pre; end
5'd15:begin b6 = (RD_pre)? 6'b101000:6'b010111; RD_mid = ~RD_pre; end
5'd16:begin b6 = (RD_pre)? 6'b100100:6'b011011; RD_mid = ~RD_pre; end
5'd17:begin b6 = 6'b100011; RD_mid = RD_pre; end
5'd18:begin b6 = 6'b010011; RD_mid = RD_pre; end
5'd19:begin b6 = 6'b110010; RD_mid = RD_pre; end
5'd20:begin b6 = 6'b001011; RD_mid = RD_pre; end
5'd21:begin b6 = 6'b101010; RD_mid = RD_pre; end
5'd22:begin b6 = 6'b011010; RD_mid = RD_pre; end
5'd23:begin b6 = (RD_pre)? 6'b000101:6'b111010; RD_mid = ~RD_pre; end
5'd24:begin b6 = (RD_pre)? 6'b001100:6'b110011; RD_mid = ~RD_pre; end
5'd25:begin b6 = 6'b100110; RD_mid = RD_pre; end
5'd26:begin b6 = 6'b010110; RD_mid = RD_pre; end
5'd27:begin b6 = (RD_pre)? 6'b001001:6'b110110; RD_mid =~RD_pre; end
5'd28:begin b6 = 6'b001110; RD_mid = RD_pre; end
5'd29:begin b6 = (RD_pre)? 6'b010001:6'b101110; RD_mid = ~RD_pre; end
5'd30:begin b6 = (RD_pre)? 6'b100001:6'b011110; RD_mid = ~RD_pre; end
5'd31:begin b6 = (RD_pre)? 6'b010100:6'b101011; RD_mid = ~RD_pre; end
default:begin b6 = 6'bXXXXXX;RD_mid = RD_pre; end
endcase
//3b to 4b code
case(din_8b[7:5]) //input HGF output fghj
3'd0:begin b4 = (RD_mid)? 4'b0100:4'b1011; RD_next = ~RD_mid; end
3'd1:begin b4 = (!RD_mid & is_k28)? 4'b0110:4'b1001; RD_next = RD_mid; end
3'd2:begin b4 = (!RD_mid & is_k28)? 4'b1010:4'b0101; RD_next = RD_mid; end
3'd3:begin b4 = (RD_mid)? 4'b0011:4'b1100; RD_next = RD_mid; end
3'd4:begin b4 = (RD_mid)? 4'b0010:4'b1101; RD_next = ~RD_mid; end
3'd5:begin b4 = (!RD_mid & is_k28)? 4'b0101:4'b1010; RD_next = RD_mid; end
3'd6:begin b4 = (!RD_mid & is_k28)? 4'b1001:4'b0110; RD_next = RD_mid; end
3'd7:begin
RD_next = ~RD_mid;
if(is_P7) b4 = (RD_mid)?4'b0001:4'b1110;
else b4 = (RD_mid)?4'b0111:4'b1000;
end
default:begin b4 = 4'bXXXX; RD_next = RD_mid; end
endcase
code_10b = {RD_next,{b6,b4}};
end
endfunction
endmodule
8b10b解码
module decode_10bto8b(
input clk,
input rst_n,
input [9:0] din_10b,
output [7:0] dout_8b
);
reg [2:0] dout_3b_r;
reg [4:0] dout_5b_r;
reg is_k28 ;
wire [3:0] din_4b;
wire [5:0] din_6b;
assign din_4b = din_10b[3:0];
assign din_6b = din_10b[9:4];
initial is_k28 = 0;
//4b to 3b
//always @(posedge clk or negedge rst_n)begin
// if(!rst_n)
// dout_3b_r <= 'd0;
// else
always @(*)begin //6和1 2和5是编码重复但反向的
case(din_4b)
4'b1011:dout_3b_r = 3'd0;
4'b0100:dout_3b_r = 3'd0;
4'b1001:dout_3b_r = (is_k28)? 3'd6: 3'd1; //(D28)?
4'b0110:dout_3b_r = (is_k28)? 3'd1: 3'd6; //K码的编码,RD=-1
// 4'b0110:dout_3b_r = 3'd6;
// 4'b1001:dout_3b_r = 3'd6; //K码的编码,RD=-1
4'b0101:dout_3b_r = (is_k28)? 3'd5: 3'd2; //(D28)?
4'b1010:dout_3b_r = (is_k28)? 3'd2: 3'd5; //K码的编码,RD=-1
// 4'b1010:dout_3b_r = 3'd5;
// 4'b0101:dout_3b_r = 3'd5; //K码的编码,RD=-1
4'b1100:dout_3b_r = 3'd3;
4'b0011:dout_3b_r = 3'd3;
4'b1101:dout_3b_r = 3'd4;
4'b0010:dout_3b_r = 3'd4;
4'b1110:dout_3b_r = 3'd7;//P7
4'b0001:dout_3b_r = 3'd7;//P7
4'b0111:dout_3b_r = 3'd7;//A7,K
4'b1000:dout_3b_r = 3'd7;//A7,K
default: dout_3b_r = 3'bXXX;
endcase
end
//6b to 5b
// always @(posedge clk or negedge rst_n)begin
// if(!rst_n)
// dout_5b_r <= 'd0;
// else
always @(*)begin
case(din_6b)
6'b100111 : dout_5b_r = 5'd0 ;
6'b011101 : dout_5b_r = 5'd1 ;
6'b101101 : dout_5b_r = 5'd2 ;
6'b110001 : dout_5b_r = 5'd3 ;
6'b110101 : dout_5b_r = 5'd4 ;
6'b101001 : dout_5b_r = 5'd5 ;
6'b011001 : dout_5b_r = 5'd6 ;
6'b111000 : dout_5b_r = 5'd7 ;
6'b111001 : dout_5b_r = 5'd8 ;
6'b100101 : dout_5b_r = 5'd9 ;
6'b010101 : dout_5b_r = 5'd10;
6'b110100 : dout_5b_r = 5'd11;
6'b001101 : dout_5b_r = 5'd12;
6'b101100 : dout_5b_r = 5'd13;
6'b011100 : dout_5b_r = 5'd14;
6'b010111 : dout_5b_r = 5'd15;
6'b011011 : dout_5b_r = 5'd16;
6'b100011 : dout_5b_r = 5'd17;
6'b010011 : dout_5b_r = 5'd18;
6'b110010 : dout_5b_r = 5'd19;
6'b001011 : dout_5b_r = 5'd20;
6'b101010 : dout_5b_r = 5'd21;
6'b011010 : dout_5b_r = 5'd22;
6'b111010 : dout_5b_r = 5'd23;
6'b110011 : dout_5b_r = 5'd24;
6'b100110 : dout_5b_r = 5'd25;
6'b010110 : dout_5b_r = 5'd26;
6'b110110 : dout_5b_r = 5'd27;
6'b001110 : dout_5b_r = 5'd28;
6'b101110 : dout_5b_r = 5'd29;
6'b011110 : dout_5b_r = 5'd30;
6'b101011 : dout_5b_r = 5'd31;
6'b011000 : dout_5b_r = 5'd0 ;
6'b100010 : dout_5b_r = 5'd1 ;
6'b010010 : dout_5b_r = 5'd2 ;
6'b001010 : dout_5b_r = 5'd4 ;
6'b000111 : dout_5b_r = 5'd7 ;
6'b000110 : dout_5b_r = 5'd8 ;
6'b101000 : dout_5b_r = 5'd15;
6'b100100 : dout_5b_r = 5'd16;
6'b000101 : dout_5b_r = 5'd23;
6'b001100 : dout_5b_r = 5'd24;
6'b001001 : dout_5b_r = 5'd27;
6'b010001 : dout_5b_r = 5'd29;
6'b100001 : dout_5b_r = 5'd30;
6'b010100 : dout_5b_r = 5'd31;
6'b001111 : begin dout_5b_r = 5'd28; is_k28 = 1;end
6'b110000 : begin dout_5b_r = 5'd28; is_k28 = 1;end
default : begin dout_5b_r = 5'bXXXXX; is_k28 = 0;end
endcase
end
assign dout_8b = {dout_3b_r,dout_5b_r};
endmodule
四、仿真
testbench如下:
module tb_8bto10b( );
reg clk;
reg rst_n;
reg [7:0] din_8b,din_8b_d0;
reg is_k;
wire error;
wire [7:0] dout_8b;
wire [9:0] dout_10b;
integer i;
initial begin
clk = 0;rst_n = 1;din_8b='d0;is_k=1'b0;
#100 rst_n=0;
#100 rst_n=1;
for(i=0;i<255;i=i+1)begin
#10 din_8b = din_8b + 8'd1;
end
end
always #5 clk = ~clk;
always @(posedge clk) din_8b_d0 <= din_8b;
assign error = (din_8b_d0 != dout_8b);
encode_8bto10b encode_8bto10b(
.clk (clk ),
.rst_n (rst_n ),
.din_en (1'b1 ),
.is_k (is_k ), //是否为k码
.din_8b (din_8b ), //输入的8bit数据
.dout_10b (dout_10b) //输出的10bit数据
);
decode_10bto8b decode_10bto8b(
.clk (clk ),
.rst_n (rst_n ),
.din_10b (dout_10b),
.dout_8b (dout_8b )
);
endmodule
下图是数据编码
总结
本文基于查找表实现了8b10b编码,资源不是最优,可以使用门电路来实现达到资源最优,还是那句话实现逻辑为主,该模块也为后面serdes的基础。