A link with 1.5% coding overhead carries a long stretch of zero bytes, the most common content in memory and files. On the wire, those zeros must still produce transitions every few bits and stay roughly balanced, with no 8b/10b table to enforce it. The block code adds only two header bits per 128. Something else must make zeros look busy.
How can a link make any data look random on the wire, and still deliver it unchanged?
One obvious idea is to invert every other bit, which turns long runs of zeros into alternating patterns. But some data, alternating bits for example, then turns into long runs. Any fixed, simple transformation maps some common pattern onto a bad one. The transformation must be one that no realistic data pattern can line up with, which means it must look random, while both ends can still reproduce it exactly.
A scrambler XORs each data bit with the next bit of a pseudo-random sequence: a sequence that looks random but is generated by a small, deterministic circuit, usually a linear-feedback shift register (LFSR). The receiver generates the same sequence and XORs again; since x XOR s XOR s = x, the data comes back exactly. Long runs and imbalance in the data become, on the wire, the sequence's own pattern, which has plenty of transitions and is balanced on average.
There are two ways to keep the receiver's sequence in step with the transmitter's:
Type | How the sequence is generated | Keeping in step | Error behaviour |
|---|---|---|---|
Additive (frame-synchronous) | The LFSR runs on its own, independent of the data | Both LFSRs must start from the same state at the same moment: the protocol resets or resynchronises them at known points | A bit error on the wire stays one bit error |
Self-synchronising (multiplicative) | The LFSR is fed by the scrambled bits themselves | Automatic: after as many bits as the register is long, the receiver's register holds the same scrambled bits as the transmitter's | One bit error on the wire becomes several errors after descrambling (one per tap), which the CRC must catch |
The listing uses a 7-bit LFSR with polynomial x⁷ + x⁶ + 1, a common test-pattern generator, at both ends. The data is sixteen zeros followed by a few ones and zeros. A checker confirms the descrambled output equals the input and counts transitions in the data and on the line during the run of zeros.
// Additive scrambling with a 7-bit LFSR (polynomial x^7 + x^6 + 1, a common test
// pattern generator). The transmitter XORs each data bit with the LFSR's output; the
// receiver runs an identical LFSR from the same seed and XORs again, which restores
// the data. A long run of zeros in the data becomes a busy pattern on the line.
module lfsr7 (input logic clk, input logic rst_n, output logic out);
logic [6:0] s;
assign out = s[6];
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) s <= 7'h24; // seed: any non-zero value works
else s <= {s[5:0], s[6] ^ s[5]}; // taps for x^7 + x^6 + 1
endmodule
module tb;
logic clk = 0;
always #5 clk = ~clk;
logic rst_n = 0;
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
#12 rst_n = 1;
end
// Data: sixteen zeros, then a few ones and zeros.
localparam logic [23:0] DATA = 24'b1010_0110_0000_0000_0000_0000;
int i = 0;
always @(posedge clk) if (rst_n && i < 23) i <= i + 1;
wire din = DATA[i];
wire prbs_tx, prbs_rx;
lfsr7 tx_lfsr (.clk, .rst_n, .out(prbs_tx));
lfsr7 rx_lfsr (.clk, .rst_n, .out(prbs_rx)); // same seed, same clock: in lockstep
wire line = din ^ prbs_tx; // what travels on the wire
wire dout = line ^ prbs_rx; // descrambled at the receiver
int errors = 0, din_edges = 0, line_edges = 0;
logic din_q, line_q;
always @(posedge clk) if (rst_n) begin
if (dout != din) errors++;
if (i > 0 && i <= 16) begin din_edges += (din != din_q); line_edges += (line != line_q); end
din_q <= din; line_q <= line;
end
initial begin
#250;
if (errors == 0)
$display("PASS: data recovered exactly; during the zero run the data had %0d transitions, the line %0d", din_edges, line_edges);
else $display("FAIL: %0d errors", errors);
$finish;
end
endmoduleHow to read it, slot by slot:
Slots 0 to 15: din is all zeros, a run with no transitions at all. prbs_tx is the LFSR's output, and since 0 XOR s = s, line equals the sequence: it changes level eleven times in those sixteen slots.
Slots 16 to 21: din carries some ones and zeros. line is now din XOR the sequence, and no longer equals either.
Every slot: dout equals din exactly, because the receiver's LFSR produced the same sequence and the second XOR cancelled the first.
This tiny LFSR repeats every 127 bits and can produce runs of up to seven identical bits; real links use much longer registers whose sequences repeat after billions of bits.
It gives no hard guarantee. Data that happens to equal the scrambling sequence becomes all zeros on the wire. Such patterns are vanishingly rare in real traffic but can be constructed on purpose. Protocols therefore pair scrambling with something unconditional, such as a sync header that always contains a transition.
It is not encryption. The sequence is public and short-lived; anyone can descramble.
It does not mark boundaries. The receiver must already know where blocks start (lesson 2.8), which is why sync headers are left unscrambled.
Choice | Trade-off |
|---|---|
Additive vs self-synchronising | Additive: no error multiplication, but the protocol must resynchronise both ends. Self-synchronising: no resync needed, but each line error becomes several data errors. |
LFSR length | Longer registers repeat less often and look more random; cost is a few more flip-flops (per lane, and per parallel bit in wide datapaths) |
What to leave unscrambled | Headers and control fields the receiver needs before it can descramble |
Protocol | Scrambling |
|---|---|
10 Gb/s Ethernet (64b/66b) | A self-synchronising scrambler with polynomial 1 + x³⁹ + x⁵⁸ over the 64-bit payload; the 2-bit sync header is not scrambled, so each block's header always delivers a transition. |
PCI Express Gen3–5 (128b/130b) | Scrambled data blocks with an unscrambled 2-bit sync header. |
The 64b/66b choice of a self-synchronising scrambler avoids any resynchronisation protocol, and its error multiplication (each line error becomes three data errors, one per tap and the original) is covered by the frame's CRC.
A link's two ends reset their additive scramblers at slightly different times after a link restart (illustrative): the transmitter at the end of training, the receiver one block later. The two sequences are now one block apart, so every descrambled bit is data XOR s(n) XOR s(n − 128), which is garbage. Every block fails its CRC, and the link retrains forever.
Symptom: the link trains, then fails every packet, then retrains.
Root cause: the additive scramblers were not synchronised at the same symbol.
Fix: define the exact point at which both ends reset or resynchronise the LFSR, and check it in a test that restarts the link many times.
line equal prbs_tx during the run of zeros?Because 0 XOR s = s: with zero data, the line carries the scrambling sequence itself.
Its register is fed by received scrambled bits, so a wrong bit sits in the register and corrupts the output once for each tap it passes, in addition to the original position.
The receiver uses it to find block boundaries before it can descramble, and its guaranteed transition must not depend on the scrambler.
A scrambler XORs data with a pseudo-random LFSR sequence; the receiver XORs with the same sequence to recover it.
Additive scramblers need both ends resynchronised; self-synchronising ones resynchronise themselves but multiply errors.
Scrambling is statistical, not a guarantee, not encryption, and not framing; pair it with unscrambled headers.