8b/10b 编码是一种 线编码,它将 8-bit 数据编码为 10 个有序的 1-bit 符号(或者,为了表述简单,1 个 10-bit 的符号)。因此,它将 N 字节的字节流编码为 10N 个符号的符号流。

目的

  • 直流平衡:即便数据流中的 0 明显多与 1,或者反之,经过编码之后 0,1 数量相差不会太大(<= 2);
  • 检错:接收方可以根据接收到的符号流是否符合编码规则知道其中是否有错误;
  • 时钟恢复:连续的 0 或者 1 的数量不大于 5,因此易于从数据中恢复出波特率。

IBM 版本的 8b/10b 编码

IBM 版本的 8b/10b 编码非常常用,最早在 1983 年被提出,原始的 Paper 名为《A DC-Balanced, Partitioned-Block, 8B/10B Transmission Code》。

编码器

编码器基本思路如下:

  • 简单来说 8-bit 的信息 HGFEDCBA 被编码成 10-bit 的 abcdeifghj,并且按从 aj 的顺序发送出去;

  • 8-bit “数据”信息输入对应的 256 种可能性,10-bit 输出对应 1024 种可能性。IBM 8b/10b 还额外定义了 12 种控制信息,因此可以视为 9-bit 输入;

  • 8-bit 信息分为低 5-bit 与高 3-bit。低 5-bit 采用 5b/6b 编码,高 3-bit 进行 3b/4b 编码;

  • 编码器的输入记为 A.x.y,其中 A 可以为 D (数据)或者 K(控制,Kontrol)。x 为低 5-bit 的十进制值,y 为高 3-bit 的十进制值;

  • A = D 时,x = 0~31y = 0~7

  • A = K 时,x = 23,27,28,29,30y = 0~7

  • 编码器的编码结果和编码器状态相关,即输出与以前的输入相关;

  • 编码器分为两个状态,RD = -1RD = +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 等几行同时适用与 DK。另外我们可以看出,编码之后的结果可以分为 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.P7D.x.A7。这是因为 IBM 在这里的设计没有那么精巧,因此需要特别的处理,以防止出现连续 5 个 0 或是 1。D.x.A7 只在 x = 17,18,20; RD = −1x = 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,那么编码肯定是 +20;如果编码前 RD = +1,那么编码肯定是 -20,这是 8b/10b 编码可以保持 0,1 数量平衡的一个解释。

有两个编码需要特别说明一下:D.07.yD.x.3。它们都是平衡编码,却有两种输出。而其余的平衡编码都只有一个输出。

一个例子

例如我们对 0xDEAD 进行 8b/10b 编码,0xDE = 11011110 = D.30.60xAD = 10101101 = D.13.5

  1. 首先初始 RD = -1
  2. 查 5b/6b 表 D.30.y 一行得到编码后为 011110RD = +1
  3. 查 3b/4b 表 D.x.6 一行得到编码后为 0110RD = +1
  4. 查 5b/6b 表 D.13.y 一行得到编码后为 101100RD = +1
  5. 查 3b/4b 表 D.x.5 一行得到编码后为 1010RD = +1
  6. 最终得到结果:011110 0110 101100 1010(从左至右发送,空格只是为了方便看)。

解码器

解码器接收符号流,将其还原为字节流。

字节定位

K.28.1K.28.5,以及 K.28.7 通常被用作“逗号符”,用于辅助字节定位。在很多采用 8b/10b 的协议中,这几个符号会被周期性的发送。

解码器状态机

解码器设计的复杂程度取决于需求,实际上解码器可以没有状态机,依旧可以通过反向查表还原出数据。但 RD 状态机可以帮助校验出违背 RD 规则的错误。