8b/10b 编码是一种线编码,它将 8-bit 数据信息和 1-bit 控制信息编码为 1 个 10-bit 的符号。主要的作用在于保持数据流中的直流平衡,即 0 和 1 的数量差不多相等。目前常用的 8b/10b 编码表是 IBM 最早在 1983 年提出的(A DC-Balanced, Partitioned-Block, 8B/10B Transmission Code),目前仍然应用在很多通信总线中。经过编码之后再传输显而易见的好处有:
- 直流平衡:无论数据流中的 0 与 1 有多少,经过编码之后 0,1 数量几乎相等(<= 2)。直流平衡有助于减小传输时需要的带宽;
- 检错:接收方可以根据接收到的符号流是否符合编码规则知道其中是否有错误;
- 时钟恢复:连续的 0 或者 1 的数量不大于 5,因此易于从数据中恢复出波特率。
编码器
简单来说 8-bit 的信息 HGFEDCBA 被编码成 10-bit 的 abcdeifghj,并且按从 a 到 j 的顺序发送出去;其中低 5-bit EDCBA 被编码成 6-bit abcdei;高 3-bit HGF 被编码成 4-bit fghj。
8-bit 数据信息被记作 D.x.y, x 取值范围 0~31; y 取值范围 0~7。对应 256 个数据符号。IBM 8b/10b 还额外定义了 12 个控制符号,记作 K.x.y,可能的值有 K.28.0、K.28.1、K.28.2、K.28.3、K.28.4、K.28.5、K.28.6、K.23.7、K.27.7、K.28.7、K.29.7、K.30.7。
- 编码器的编码结果和编码器状态相关,即输出与以前的输入相关;
- 编码器分为两个状态,
RD = -1和RD = +1。RD 意为 Running Disparity,分别表示经过之前编码,线上的“0 比 1 多”和“1 比 0 多”; - 编码器初始状态为
RD = -1。
编码器的工作流程如下,注意这里 5b/6b 与 3b/4b 编码被合并成了一步::
rd = -1;
for i = 0:length(instream)
input = instream(i);
data = input.data;
isK = input.isK;
[sym, rd] = 8b10b_encode(data, isK, rd);
outstream[i] = sym;
end
8b/10b 编码可以被拆成两步:
function [sym, rd] = 8b10b_encode(data, isK, rd)
[sym(0:5), rd] = 5b6b_encode(data(4:0), isK, rd);
[sym(6:9), rd] = 3b4b_encode(data(4:0), data(7:5), isK, rd);
end
注意 3b/4b 编码并不是仅仅和 data(7:5) 相关,它还和 data(4:0) 有关。8b/10b 结果可以用 5b/6b 编码表和 3b/4b 编码表算出。
5b/6b 编码表
| INPUT(EDCBA) | Output(abcdei) |
| Name | BIN | RD=-1 | RD=+1 |
| ------ | ----- | ------ | ------ |
| D.00.y | 00000 | 100111 | 011000 |
| D.01.y | 00001 | 011101 | 100010 |
| D.02.y | 00010 | 101101 | 010010 |
| D.03.y | 00011 | 110001 |
| D.04.y | 00100 | 110101 | 001010 |
| D.05.y | 00101 | 101001 |
| D.06.y | 00110 | 011001 |
| D.07.y | 00111 | 111000 | 000111 |
| D.08.y | 01000 | 111001 | 000110 |
| D.09.y | 01001 | 100101 |
| D.10.y | 01010 | 010101 |
| D.11.y | 01011 | 110100 |
| D.12.y | 01100 | 001101 |
| D.13.y | 01101 | 101100 |
| D.14.y | 01110 | 011100 |
| D.15.y | 01111 | 010111 | 101000 |
| D.16.y | 10000 | 011011 | 100100 |
| D.17.y | 10001 | 100011 |
| D.18.y | 10010 | 010011 |
| D.19.y | 10011 | 110010 |
| D.20.y | 10100 | 001011 |
| D.21.y | 10101 | 101010 |
| D.22.y | 10110 | 011010 |
| A.23.y | 10111 | 111010 | 000101 |
| D.24.y | 11000 | 110011 | 001100 |
| D.25.y | 11001 | 100110 |
| D.26.y | 11010 | 010110 |
| A.27.y | 11011 | 110110 | 001001 |
| D.28.y | 11100 | 001110 |
| A.29.y | 11101 | 101110 | 010001 |
| A.30.y | 11110 | 011110 | 100001 |
| D.31.y | 11111 | 101011 | 010100 |
| ------ | ----- | ------ | ------ |
| K.28.y | 11100 | 001111 | 110000 |
注意其中的 A.23.y 等几行同时适用与 D 与 K。另外我们可以看出,编码之后的结果可以分为 3 种:1 比 0 多两个的(+2),0 比 1 多两个的(-2),0 和 1 一样多的(0)。
3b/4b 编码表
| Input(HGF) | Output(fghj) |
| Name | Bin | RD=-1 | RD=+1 |
| ------ | ----- | ------ | ------ |
| D.x.0 | 000 | 1011 | 0100 |
| D.x.1 | 001 | 1001 |
| D.x.2 | 010 | 0101 |
| D.x.3 | 011 | 1100 | 0011 |
| D.x.4 | 100 | 1101 | 0010 |
| D.x.5 | 101 | 1010 |
| D.x.6 | 110 | 0110 |
| D.x.P7 | 111 | 1110 | 0001 |
| D.x.A7 | 111 | 0111 | 1000 |
| ------ | ----- | ------ | ------ |
| K.x.0 | 000 | 1011 | 0100 |
| K.x.1 | 001 | 0110 | 1001 |
| K.x.2 | 010 | 1010 | 0101 |
| K.x.3 | 011 | 1100 | 0011 |
| K.x.4 | 100 | 1101 | 0010 |
| K.x.5 | 101 | 0101 | 1010 |
| K.x.6 | 110 | 1001 | 0110 |
| K.x.7 | 111 | 0111 | 1000 |
对于 D.x.7,有两种可以选的结果:D.x.P7 与 D.x.A7。这是因为 IBM 在这里的设计没有那么精巧,因此需要特别地处理,以防止出现连续 5 个 0 或是 1。D.x.A7 只在 x = 17,18,20; RD = −1 或 x = 11,13,14; RD = +1 时使用。同样的,3b/4b 编码之后的结果也分为 3 种(+2/-2/0)。
特别的,我们列出所有的控制符号,一共 12 种。
控制符号
| Input (HGF EDCBA) | Output (abcdei fghj) |
| Name | DEC | HEX | BIN | RD=-1 | RD=+1 |
| ------ | --- | --- | --------- | ----------- | ----------- |
| K.28.0 | 28 | 1C | 000 11100 | 001111 0100 | 110000 1011 |
| K.28.1 | 60 | 3C | 001 11100 | 001111 1001 | 110000 0110 |
| K.28.2 | 92 | 5C | 010 11100 | 001111 0101 | 110000 1010 |
| K.28.3 | 124 | 7C | 011 11100 | 001111 0011 | 110000 1100 |
| K.28.4 | 156 | 9C | 100 11100 | 001111 0010 | 110000 1101 |
| K.28.5 | 188 | BC | 101 11100 | 001111 1010 | 110000 0101 |
| K.28.6 | 220 | DC | 110 11100 | 001111 0110 | 110000 1001 |
| K.28.7 | 252 | FC | 111 11100 | 001111 1000 | 110000 0111 |
| K.23.7 | 247 | F7 | 111 10111 | 111010 1000 | 000101 0111 |
| K.27.7 | 251 | FB | 111 11011 | 110110 1000 | 001001 0111 |
| K.29.7 | 253 | FD | 111 11101 | 101110 1000 | 010001 0111 |
| K.30.7 | 254 | FE | 111 11110 | 011110 1000 | 100001 0111 |
编码器状态机
之前提到,编码器是有状态的,其状态为 RD,可以为 -1 或 +1。状态跳转的规则可以从名字看出来:初始时状态为 -1;经过了一次 5b/6b 或 3b/4b 编码之后,如果这次编码的结果导致 1 比 0 多了,那么状态跳转到 +1;如果编码导致 0 比 1 多了,那么状态跳转到 -1;如果这次编码是平衡编码,那么状态保持不变。关于编码器状态机跳转可能有 别的描述,但是都是等价的。
观察编码表,可以看出,如果编码前 RD = -1,那么编码肯定是 +2 或 0;如果编码前 RD = +1,那么编码肯定是 -2 或 0,这是 8b/10b 编码可以保持 0,1 数量平衡的一个解释。
有两个编码需要特别说明一下:D.07.y 和 D.x.3。它们都是平衡编码,却有两种输出。而其余的平衡编码都只有一个输出。
一个例子
例如我们对 0xDEAD 进行 8b/10b 编码,0xDE = 11011110 = D.30.6,0xAD = 10101101 = D.13.5。
- 首先初始
RD = -1; - 查 5b/6b 表
D.30.y一行得到编码后为011110,RD = +1; - 查 3b/4b 表
D.x.6一行得到编码后为0110,RD = +1; - 查 5b/6b 表
D.13.y一行得到编码后为101100,RD = +1; - 查 3b/4b 表
D.x.5一行得到编码后为1010,RD = +1; - 最终得到结果:
011110 0110 101100 1010(从左至右发送,空格只是为了方便看)。
解码器
解码器接收符号流,将其还原为字节流。
字节定位
K.28.1,K.28.5,以及 K.28.7 通常被用作“逗号符”,用于辅助字节定位。在很多采用 8b/10b 的协议中,这几个符号会被周期性的发送。
解码器状态机
解码器设计的复杂程度取决于需求,实际上解码器可以没有状态机,依旧可以通过反向查表还原出数据。但 RD 状态机可以帮助校验出违背 RD 规则的错误。