Why raw binary can’t be sent directly on a PCIe link — the three problems 8b/10b solves, how a 5b/6b + 3b/4b split works, Current Running Disparity, special control characters (K-codes), the 20% overhead and why it matters, and what Gen 3+ replaced it with.
It seems logical to just send raw binary data directly onto the PCIe differential pair — if the byte is 0xFF (all ones), drive D+ high for eight consecutive bit times. But this creates three serious problems for the Physical Layer that make reliable high-speed communication impossible without encoding.
8b/10b encoding was designed in the early 1980s for fibre channel and adopted by many high-speed serial standards including PCIe Gen 1 and Gen 2. It solves all three raw binary problems simultaneously through a single encoding transformation:
8b/10b takes an 8-bit input byte and produces a 10-bit output symbol. The transformation is not a straightforward expansion — it is a split encoding: the 8-bit input is divided into a 5-bit sub-block and a 3-bit sub-block, each encoded separately.
Each 5-bit sub-block has two possible encodings: one with more ones (positive disparity) and one with more zeros (negative disparity). Some sub-blocks have only one encoding — their encoding is disparity-neutral and looks the same regardless of CRD. The encoder selects between the two options to maintain DC balance.
8b/10b uses a shorthand notation for naming characters. The 8-bit input byte is described in a specific format rather than just its hex value:
This notation uniquely identifies every 8b/10b character. There are 256 data characters (D0.0 through D31.7) and 12 defined control (K) characters. Not all Dxx.y combinations are legal — some are reserved or used only with specific disparity states.
Disparity refers to the imbalance of ones versus zeros within a 10-bit symbol. A symbol with more ones than zeros has positive disparity (+). A symbol with more zeros has negative disparity (–). A symbol with equal ones and zeros has neutral disparity.
The Current Running Disparity (CRD) is a single bit maintained by both transmitter and receiver. It tracks the ongoing balance of ones and zeros in the serial stream. The CRD starts at either + or – and flips each time a non-neutral symbol is sent.
The CRD drives the encoding choice. The transmitter and receiver independently track the same CRD state — they stay synchronised because they apply the same rules to the same symbol stream. If they ever disagree, a disparity error is flagged.
| CRD before symbol | Symbol disparity | CRD after symbol | Legal? |
|---|---|---|---|
| Negative (–) | Positive (+) — more ones | Positive (+) — flips | ✓ Legal — balances previous negative |
| Negative (–) | Neutral — equal ones and zeros | Negative (–) — unchanged | ✓ Legal — doesn’t worsen balance |
| Negative (–) | Negative (–) — more zeros | Would be even more negative | ✗ Illegal — disparity error |
| Positive (+) | Negative (–) — more zeros | Negative (–) — flips | ✓ Legal — balances previous positive |
| Positive (+) | Neutral — equal ones and zeros | Positive (+) — unchanged | ✓ Legal |
| Positive (+) | Positive (+) — more ones | Would be even more positive | ✗ Illegal — disparity error |
The rule is simple: the disparity of each symbol must be opposite to or neutral with respect to the current CRD. Sending a positive-disparity symbol when CRD is already positive would make the link more unbalanced — that encoding is illegal and would never be produced by a correct encoder.
Let’s encode the byte 0x6A (D10.3) with CRD = negative. The 5-bit sub-block EDCBA = 01010 (decimal 10) and the 3-bit sub-block HGF = 011 (decimal 3).
A 10-bit symbol produced by correct 8b/10b encoding always satisfies all of the following constraints. Any received symbol violating these is an immediate code violation error:
Of the 2¹⁰ = 1024 possible 10-bit patterns, the encoding uses only 512 as valid data symbol encodings (2 per byte × 256 bytes). Adding the K-code symbols brings the total to approximately 530 valid patterns. The remaining 494+ are illegal and instantly detectable as transmission errors.
Besides the 256 data characters (D-codes), 8b/10b defines 12 special control characters called K-codes. K-codes do not represent data bytes — they are used for link management, framing, and ordered set signalling. The receiver distinguishes K-codes from D-codes because K-codes map to their own separate lookup table.
| Symbol name | 8b/10b name | Hex byte | Purpose in PCIe |
|---|---|---|---|
| COM | K28.5 | 0xBC | First symbol of every ordered set. Used for symbol lock — the receiver detects COM to find symbol boundaries in the serial bitstream. Also resets the scrambler state. |
| STP | K27.7 | 0xFB | Start of TLP. Marks the beginning of a TLP in the serial stream. The Data Link Layer looks for STP to delineate incoming TLPs. |
| SDP | K28.2 | 0x5C | Start of DLLP. Marks the beginning of a DLLP in the serial stream. |
| END | K29.7 | 0xFD | End of packet (good). Appended to the last byte of an error-free TLP or DLLP. |
| EDB | K30.7 | 0xFE | End of bad packet. Replaces END when a switch in cut-through mode detects a packet error mid-stream and must nullify the in-flight packet. |
| SKP | K28.0 | 0x1C | Skip. Part of the SKIP ordered set used for clock tolerance compensation. Receiver may add or delete SKP characters to prevent elastic buffer overflow/underflow. |
| FTS | K28.1 | 0x3C | Fast Training Sequence. Used in FTS ordered sets to exit the L0s low-power link state and return to L0. The required number of FTS symbols is negotiated during link training. |
| IDL | K28.3 | 0x7C | Idle. Part of the Electrical Idle Ordered Set (EIOS). Signals the receiver to prepare for electrical idle. |
| PAD | K23.7 | 0xF7 | Padding. On multi-lane links, if a packet doesn’t fill all lanes and no new packet is ready, PAD fills the remaining lanes. |
| EIE | K28.7 | 0xFF | Electrical Idle Exit. Part of the EIEOS ordered set added in PCIe 2.0 to provide a reliable signal for detecting exit from electrical idle at speeds above 2.5 GT/s. |
An ordered set is a specific sequence of symbols sent simultaneously on all active lanes at the same time. Ordered sets always begin with the COM (K28.5) symbol on every lane, followed by additional symbols that define the specific ordered set type. They are called “ordered” because their structure is tightly defined — the receiver can identify them by pattern, not just by header.
Key ordered sets in PCIe Gen 1/2:
8b/10b provides Physical Layer error detection through two complementary mechanisms. Neither is as strong as the LCRC (which covers the full TLP), but they catch errors that LCRC cannot — specifically errors within the Physical Layer itself before bits are assembled into TLPs.
Any received 10-bit symbol that does not appear in either the D-code or K-code lookup tables is a code violation. Immediately detectable. Causes: signal corruption on the link, loss of symbol lock, bit errors that happen to create an unrecognised pattern.
The receiver maintains its own CRD alongside the transmitter’s CRD. After receiving each symbol, the receiver checks that the symbol’s disparity is consistent with the current CRD value. If a transmitted symbol had a positive disparity and the receiver’s CRD was already positive, this is a disparity error — it could not have been produced by a correct encoder.
For every 8 bits of useful data, the transmitter sends 10 bits on the wire. The overhead is exactly 2/10 = 20%. This is significant and directly reduces the usable data throughput of the link.
The 20% overhead means that a Gen 1 x16 link with 2.5 GT/s per lane has a peak data throughput of 2.5 GT/s × 0.8 × 16 lanes / 8 bits per byte = 4000 MB/s = ~3.9 GB/s. The 20% is the primary reason PCIe Gen 3 abandoned 8b/10b entirely in favour of the far more efficient 128b/130b encoding.
Even with 8b/10b balancing ones and zeros, repetitive data patterns can produce periodic spectral peaks in the transmitted bitstream. For example, transmitting a long sequence of 0x55 bytes would create a regular alternating 1-0-1-0 pattern — a single strong frequency component that creates EMI and may stress PLL clock recovery circuits.
The Physical Layer scrambles data bytes before 8b/10b encoding by XORing them with a pseudo-random sequence generated by a Linear Feedback Shift Register (LFSR). The scrambling sequence is deterministic — both transmitter and receiver use the same LFSR initialised to the same value — so the receiver can trivially descramble by applying the same XOR sequence again. Scrambling spreads the spectral energy evenly across frequencies, eliminating strong periodic tones.
Starting with Gen 3 (8 GT/s), PCIe abandoned 8b/10b encoding entirely. The 20% overhead was simply too costly as data rates increased. Gen 3 introduced 128b/130b encoding — a fundamentally different approach that achieves the same goals with only ~1.54% overhead.
Instead of encoding individual bytes, 128b/130b groups 128 data bits together as a single block and prepends a 2-bit sync header. The header encodes exactly two states: 01 meaning “this is a data block” and 10 meaning “this is an ordered set block”. DC balance is achieved entirely through scrambling — there is no CRD mechanism. K-codes and the COM character are no longer used for framing — the sync header replaces them. SKP ordered sets are still used for clock compensation, but their encoding changes. There is no longer a single special character for symbol lock — block alignment is achieved differently.
This is why Gen 3 hardware is more complex than Gen 1/2: the scrambler is more sophisticated (24-bit LFSR vs 16-bit), the framing detection is different, and equalization is mandatory rather than optional. The reward is nearly 100% efficiency instead of 80%.
| Item | Value / Rule |
|---|---|
| What 8b/10b does | Encodes each 8-bit input byte into a 10-bit output symbol. Simultaneously maintains DC balance, guarantees transition density, and enables code violation error detection. |
| Structure | 5b→6b encoding for lower 5 bits (EDCBA) + 3b→4b encoding for upper 3 bits (HGF). Outputs concatenated as abcdei·fghj. |
| Overhead | 20% — 10 bits transmitted per 8 bits of data. Reduces effective bandwidth to 80% of raw line rate. |
| Character notation | Dxx.y for data characters (D0.0–D31.7) · Kxx.y for control characters. xx = decimal of 5-bit field, y = decimal of 3-bit field. |
| Disparity | Positive (+): symbol has more 1s than 0s · Negative (–): more 0s · Neutral: equal. Every symbol has 4, 5, or 6 ones. |
| CRD | Current Running Disparity — single bit tracking the DC balance of the serial stream. Maintained identically by both transmitter and receiver. |
| CRD rule | A new symbol must have disparity opposite to or neutral with the current CRD. Sending a same-polarity symbol when CRD matches = disparity error. |
| Two encodings per character | Most non-neutral characters have two 10-bit encodings: one for CRD=+ and one for CRD=–. The encoder chooses based on the current CRD to maintain balance. |
| Max run length | No more than 5 consecutive bits of the same polarity in the serial stream — guaranteed by the encoding, even across symbol boundaries. |
| Legal symbol space | ~530 valid patterns out of 1024 possible 10-bit patterns. The other 494+ are detectable code violations. |
| COM (K28.5) | The unique ordered set start character. Not scrambled. Used by receiver for symbol lock. Resets scrambler LFSR. Always recognisable by its 0011111010 or 1100000101 pattern. |
| STP (K27.7) | Start of TLP marker. Enables the Data Link Layer to find packet boundaries in the symbol stream. |
| SDP (K28.2) | Start of DLLP marker. |
| END (K29.7) | End of good packet. |
| EDB (K30.7) | End of nullified (bad) packet — inverted LCRC follows. Used by switches in cut-through mode. |
| SKP (K28.0) | Skip character — part of SKIP ordered set for clock tolerance compensation. Sent every 1180–1538 symbol times. Receiver may add/delete SKP characters. |
| K-codes not scrambled | Control characters pass through the scrambler unchanged. Data characters are scrambled before 8b/10b encoding. |
| Error detection | Code violations: illegal 10-bit pattern. Disparity errors: symbol disparity conflicts with CRD. Both reported as Receiver Error to DLL. |
| Generations using 8b/10b | Gen 1 (2.5 GT/s) and Gen 2 (5 GT/s) only. |
| Gen 3+ replacement | 128b/130b encoding — 2-bit sync header per 128-bit block. Only 1.54% overhead. No CRD, no K-codes for framing. Scrambling provides DC balance. |
| Gen 6 | No character encoding at all. PAM4 at 32 Gbaud. RS(544,514) FEC per 256-byte flit. Flit header for framing. No 8b/10b anywhere in the stack. |