This is a quick post about 8b/10b encoder in PCIE Gen1/2. Not that change in Gen3.

From Mindeshare PCIe book, encoder balances out the

Maintaining DC Balance. PCIe uses an AC‐coupled link, placing a capaci‐ tor serially in the path to isolate the DC part of the signal from the other end of the Link. This allows the Transmitter and Receiver to use different com‐ mon‐mode voltages and makes the electrical design easier for cases where the path between them is long enough that they’re less likely to have exactly the same reference voltages.

Basically changing the next symbol based on previous symbol, either increasing or decreasing number of ones in the encoding.

this drifting voltage degrades signal integrity at the Receiver. To compensate, the 8b/10b encoder tracks the “disparity” of the last Symbol that was sent. Disparity, or inequality, simply indicates whether the previ‐ ous Symbol had more ones than zeros (called positive disparity), more zeros than ones (negative disparity), or a balance of ones and zeros (neutral

This is break down of code calculation

Partition Link to heading

The first stage is partition the 8 bits into 5 and 3 sub-blocks.

def parition(i):
    bs = int2binstr(i)
    return (bs[3:], bs[0:3])

This is the lookup table(from Wiki) for 5 bits.

Example image

Disparity Link to heading

Disparity is the difference between number of ones and zeros. Dump implementation would be something like this:

def disparity(b):
    n_1 = len([o for o in list(b) if     o== '1'])
    n_0 = len([o for o in list(b) if     o== '0'])
    dis = n_1 - n_0
    return dis

Running disparity RD Link to heading

As mentioned before the RD, depends on disparity and current RD.

lookup = [
        # prev RD, disparity, disparity chosen, RD
        (-1, 0,  0, -1),
        (-1, 2, +2, +1),
        (+1, 0,  0, +1),
        (+1, 2, -2, -1),
        ]
def get_new_rd(rd, dis):
    assert (rd in [-1, 1])
    assert (dis in [0, 2, -2])

    l = [x for x in lookup if x[0] == rd]
    l = [x for x in l if x[1] == abs(dis)]
    assert(len(l))

    rd_next = l[0][3]

    return rd_next

Putting it all together Link to heading

I am too lazy to copy all rows, So, I put down rows I needed for this example. The rest is None as placeholder

def int2binstr(value):
    b = (bin(value)[2:]).zfill(8)
    return b

lookup = [
        # prev RD, disparity, disparity chosen, RD
        (-1, 0,  0, -1),
        (-1, 2, +2, +1),
        (+1, 0,  0, +1),
        (+1, 2, -2, -1),
        ]

encode_5_6 = [
        # (EDCBA, (RD=-1, RD=1))
        ("00000",  ("100111", "011000")), # D.00
        ("00001",  ("011101", "100010")), # D.01
        ("00010",  ("101101", "010010")), # D.02
        ("00011",  ("110001", "110001")), # D.03
        None,
        None,
        None,
        None,
        None,
        None,
        ("01010",  ("010101", "010101")), # 10
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        ("11011",  ("110110","001001")), # 27
        None,
        None,
        None,
        None,
        ]

def disparity(b):
    n_1 = len([o for o in list(b) if     o== '1'])
    n_0 = len([o for o in list(b) if     o== '0'])
    dis = n_1 - n_0
    return dis

def get_new_rd(rd, dis):
    assert (rd in [-1, 1])
    assert (dis in [0, 2, -2])

    l = [x for x in lookup if x[0] == rd]
    l = [x for x in l if x[1] == abs(dis)]
    assert(len(l))

    rd_next = l[0][3]

    return rd_next

def parition(i):
    bs = int2binstr(i)
    return (bs[3:], bs[0:3])

# main
data = [0x6a, 0x1b]
rd = -1 # start rd is -1

for d in data:
    p = parition(d)

    idx_5_6 = int(p[0],2)
    idx_5_6_rd = 1 if rd == 1 else 0 # TODO find better way to idex RD=1, RD=-1
    code = encode_5_6[idx_5_6][1][idx_5_6_rd]

    dis = disparity(code)

    rd, rd_current = (get_new_rd(rd, dis), rd)

    print(f'code:{code} disparity:{dis} current_rd:{rd_current} rd:{rd}')