Scrambler & Descrambler in Verilog (LFSR) with Code
A scrambler is a circuit that XORs a serial data stream with a pseudo-random sequence from a linear-feedback shift register (LFSR) so the line carries few long runs of identical bits. SerDes links, PCIe, SATA, and Ethernet all scramble the payload to balance the number of ones and zeros, guarantee frequent transitions for clock recovery, and spread spectral energy so EMI is lower. This page gives working scrambler Verilog code for both common styles, each paired with its matching descrambler.
We cover the two families that appear in interviews and real standards: the self-synchronizing (multiplicative) scrambler, which needs no shared state, and the additive (frame-synchronous) scrambler, which seeds both ends with the same value. Every example below is a complete module plus a self-checking testbench that was compiled and run under Icarus Verilog in strict Verilog-2005 mode.
This self-synchronizing scrambler uses the polynomial x7+x4+1. The feedback for each bit is taken from the scrambled output, so the descrambler re-derives the same feedback purely from the bits arriving on the wire and locks on after at most 7 bits, with no shared seed.
// Self-synchronizing (multiplicative) scrambler / descrambler
// Polynomial: x^7 + x^4 + 1 (taps at positions 7 and 4)
module scrambler_mult (
input wire clk,
input wire rst_n,
input wire din,
output wire dout
);
reg [7:1] sr;
assign dout = din ^ sr[7] ^ sr[4];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) sr <= 7'b0;
else sr <= {sr[6:1], dout};
end
endmodule
module descrambler_mult (
input wire clk,
input wire rst_n,
input wire din,
output wire dout
);
reg [7:1] sr;
assign dout = din ^ sr[7] ^ sr[4];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) sr <= 7'b0;
else sr <= {sr[6:1], din};
end
endmodule
module tb;
reg clk, rst_n, data_in;
wire scr, rec;
scrambler_mult U_S (.clk(clk), .rst_n(rst_n), .din(data_in), .dout(scr));
descrambler_mult U_D (.clk(clk), .rst_n(rst_n), .din(scr), .dout(rec));
reg [0:39] stream = 40'b1011001000000000111101010101001100001111;
integer i, errors, checked;
initial begin clk = 0; forever #5 clk = ~clk; end
initial begin
rst_n = 0; data_in = 0; errors = 0; checked = 0;
@(negedge clk); rst_n = 1;
for (i = 0; i < 40; i = i + 1) begin
data_in = stream[i];
#1;
if (i >= 7) begin
checked = checked + 1;
if (rec !== data_in) begin
errors = errors + 1;
$display("FAIL i=%0d din=%b scr=%b rec=%b", i, data_in, scr, rec);
end else
$display("PASS i=%0d din=%b scr=%b rec=%b", i, data_in, scr, rec);
end
@(posedge clk);
end
if (errors == 0 && checked == 33)
$display("ALL TESTS PASSED (%0d bits recovered after sync)", checked);
else
$display("FAIL summary errors=%0d checked=%0d", errors, checked);
$finish;
end
endmodule
PASS i=7 din=0 scr=0 rec=0 PASS i=8 din=0 scr=1 rec=0 PASS i=9 din=0 scr=1 rec=0 PASS i=10 din=0 scr=1 rec=0 PASS i=11 din=0 scr=1 rec=0 PASS i=12 din=0 scr=1 rec=0 PASS i=13 din=0 scr=1 rec=0 PASS i=14 din=0 scr=1 rec=0 PASS i=15 din=0 scr=0 rec=0 PASS i=16 din=1 scr=1 rec=1 PASS i=17 din=1 scr=1 rec=1 PASS i=18 din=1 scr=1 rec=1 PASS i=19 din=1 scr=0 rec=1 PASS i=20 din=0 scr=0 rec=0 PASS i=21 din=1 scr=1 rec=1 PASS i=22 din=0 scr=1 rec=0 PASS i=23 din=1 scr=0 rec=1 PASS i=24 din=0 scr=1 rec=0 PASS i=25 din=1 scr=1 rec=1 PASS i=26 din=0 scr=1 rec=0 PASS i=27 din=1 scr=1 rec=1 PASS i=28 din=0 scr=0 rec=0 PASS i=29 din=0 scr=0 rec=0 PASS i=30 din=1 scr=0 rec=1 PASS i=31 din=1 scr=1 rec=1 PASS i=32 din=0 scr=1 rec=0 PASS i=33 din=0 scr=1 rec=0 PASS i=34 din=0 scr=1 rec=0 PASS i=35 din=0 scr=1 rec=0 PASS i=36 din=1 scr=0 rec=1 PASS i=37 din=1 scr=0 rec=1 PASS i=38 din=1 scr=1 rec=1 PASS i=39 din=1 scr=1 rec=1 ALL TESTS PASSED (33 bits recovered after sync)The log shows that from bit index 7 onward every recovered bit equals the original input bit, printing 33 consecutive PASS lines and
ALL TESTS PASSED. Notice the scrambled line (scr) toggles even during the all-zeros input run (indices 2–7), proving the data is whitened, while the descrambler still reconstructs the stream exactly.The additive scrambler runs a free-running LFSR (same x7+x4+1 polynomial, parameterized seed) to produce a keystream that is XORed with the data. Both ends load the identical
SEED, so descrambling is simply a second XOR with the same keystream and there is no sync delay.module lfsr7 #( parameter [7:1] SEED = 7'h7F ) ( input wire clk, input wire load, output wire ks ); reg [7:1] state; wire fb = state[7] ^ state[4]; assign ks = state[7]; always @(posedge clk) begin if (load) state <= SEED; else state <= {state[6:1], fb}; end endmodule module add_scrambler #(parameter [7:1] SEED = 7'h7F) ( input wire clk, input wire load, input wire din, output wire dout ); wire ks; lfsr7 #(.SEED(SEED)) U (.clk(clk), .load(load), .ks(ks)); assign dout = din ^ ks; endmodule module tb; reg clk, load, data_in; wire scr, rec; wire ks_mon; add_scrambler #(.SEED(7'h5B)) U_S (.clk(clk), .load(load), .din(data_in), .dout(scr)); add_scrambler #(.SEED(7'h5B)) U_D (.clk(clk), .load(load), .din(scr), .dout(rec)); lfsr7 #(.SEED(7'h5B)) U_K (.clk(clk), .load(load), .ks(ks_mon)); reg [0:31] stream = 32'b11010010000000000000111101011001; integer i, errors, ones_out, zero_run_ones; initial begin clk = 0; forever #5 clk = ~clk; end initial begin load = 1; data_in = 0; errors = 0; ones_out = 0; zero_run_ones = 0; @(negedge clk); @(posedge clk); load = 0; for (i = 0; i < 32; i = i + 1) begin data_in = stream[i]; #1; if (rec !== data_in) begin errors = errors + 1; $display("FAIL i=%0d din=%b scr=%b rec=%b", i, data_in, scr, rec); end else $display("PASS i=%0d din=%b scr=%b rec=%b ks=%b", i, data_in, scr, rec, ks_mon); if (scr) ones_out = ones_out + 1; if (i >= 2 && i <= 17 && scr) zero_run_ones = zero_run_ones + 1; @(posedge clk); end if (errors == 0) $display("PASS recovery: all 32 bits descrambled correctly"); if (zero_run_ones > 0) $display("PASS whitening: 16-bit all-zero run produced %0d ones on the wire", zero_run_ones); if (errors == 0 && zero_run_ones > 0) $display("ALL TESTS PASSED"); else $display("FAIL summary errors=%0d zero_run_ones=%0d", errors, zero_run_ones); $finish; end endmodulePASS i=0 din=1 scr=1 rec=1 ks=0
PASS i=1 din=1 scr=0 rec=1 ks=1
PASS i=2 din=0 scr=1 rec=0 ks=1
PASS i=3 din=1 scr=1 rec=1 ks=0
PASS i=4 din=0 scr=1 rec=0 ks=1
PASS i=5 din=0 scr=1 rec=0 ks=1
PASS i=6 din=1 scr=1 rec=1 ks=0
PASS i=7 din=0 scr=0 rec=0 ks=0
PASS i=8 din=0 scr=0 rec=0 ks=0
PASS i=9 din=0 scr=0 rec=0 ks=0
PASS i=10 din=0 scr=0 rec=0 ks=0
PASS i=11 din=0 scr=1 rec=0 ks=1
PASS i=12 din=0 scr=1 rec=0 ks=1
PASS i=13 din=0 scr=0 rec=0 ks=0
PASS i=14 din=0 scr=0 rec=0 ks=0
PASS i=15 din=0 scr=1 rec=0 ks=1
PASS i=16 din=0 scr=1 rec=0 ks=1
PASS i=17 din=0 scr=0 rec=0 ks=0
PASS i=18 din=0 scr=1 rec=0 ks=1
PASS i=19 din=0 scr=0 rec=0 ks=0
PASS i=20 din=1 scr=0 rec=1 ks=1
PASS i=21 din=1 scr=1 rec=1 ks=0
PASS i=22 din=1 scr=1 rec=1 ks=0
PASS i=23 din=1 scr=0 rec=1 ks=1
PASS i=24 din=0 scr=1 rec=0 ks=1
PASS i=25 din=1 scr=0 rec=1 ks=1
PASS i=26 din=0 scr=0 rec=0 ks=0
PASS i=27 din=1 scr=1 rec=1 ks=0
PASS i=28 din=1 scr=0 rec=1 ks=1
PASS i=29 din=0 scr=1 rec=0 ks=1
PASS i=30 din=0 scr=1 rec=0 ks=1
PASS i=31 din=1 scr=0 rec=1 ks=1
PASS recovery: all 32 bits descrambled correctly
PASS whitening: 16-bit all-zero run produced 9 ones on the wire
ALL TESTS PASSEDThe log confirms two independent properties. First, all 32 bits descramble back to the original stream (the
reccolumn matchesdinon every line). Second, a deliberate 16-bit all-zeros input stretch still produced 9 ones on the scrambled wire — the keystream (ks) whitened the dead run. Both facts yieldALL TESTS PASSED.Multiplicative vs additive scramblers
The two architectures differ in where the LFSR feedback comes from.
- Multiplicative (self-synchronizing): the shift register is clocked with the scrambled stream itself. Because both scrambler and descrambler feed on the same transmitted bits, the receiver needs no knowledge of the transmitter's state — it self-synchronizes within one register length. This is the scrambler used in PCIe Gen1/Gen2, SONET/SDH, and many SerDes PHYs.
- Additive (frame-synchronous): the LFSR free-runs independently of the data and simply generates a keystream. The transmitter and receiver must start from the same seed at the same bit position (usually reset at a frame or block boundary), hence "frame-synchronous." 10GBASE-R and USB use this style.
The LFSR polynomial
Both designs use x7+x4+1, a primitive polynomial of degree 7. "Primitive" means the LFSR cycles through all 27−1 = 127 non-zero states before repeating, giving the longest possible pseudo-random sequence for a 7-bit register. The exponents name the tap positions: feedback is the XOR of the bits at positions 7 and 4.
In the code the state is declared
reg [7:1] sr;and the feedback issr[7] ^ sr[4]. Longer registers (for example the x23+x18+1 used in some standards) give longer runs before repetition; the structure is identical — only the width and tap indices change.Error multiplication in self-sync descramblers
The self-synchronizing descrambler's convenience has a cost: error multiplication. Each received bit is XORed with two earlier received bits (taps 4 and 7). If a single bit flips on the channel, it corrupts the output when it is the current bit and again each time it later appears at a tap — so one channel error becomes N+1 output errors, where N is the number of feedback taps (here 3 errors per single-bit flip).
The additive scrambler does not multiply errors: a flipped bit on the wire produces exactly one wrong output bit, because the keystream is generated locally and is independent of the received data. The trade-off is that the additive scheme needs reliable seed/frame synchronization, which the self-sync scheme avoids entirely.
Why scrambling helps clock recovery and DC balance
Serial receivers recover the clock from data edges using a CDR (clock-data recovery) PLL. A long run of identical bits has no transitions, so the PLL drifts and the eye closes. Scrambling breaks up these runs statistically, guaranteeing frequent transitions.
Scrambling also improves DC balance — keeping the running count of ones and zeros roughly equal. AC-coupled links and transformers cannot pass a DC offset, so a persistent imbalance causes baseline wander. By whitening the data, the scrambler flattens the spectrum and pushes energy away from a single tone, which also lowers EMI. Note that scrambling is statistical: unlike 8b/10b line coding it does not guarantee a maximum run length, so some standards combine scrambling with a coding layer.
Sync behavior and common mistakes
Synchronization
The self-sync descrambler locks automatically: after L correctly received bits (L = register length = 7 here) its shift register holds the same history as the transmitter, and from then on output equals input. That is exactly why the Example 1 testbench only starts checking at bit index 7. The additive descrambler, by contrast, is correct from the first bit only if its LFSR was loaded with the right seed at the right position — a seed or phase mismatch garbles the entire frame.
Common mistakes
- Feeding the descrambler's own output back into its register instead of the received line — that turns a self-sync descrambler back into a scrambler.
- Tap-position off-by-one: indexing
sr[8]orsr[0]on a[7:1]register silently breaks the polynomial.- Seeding the LFSR to all zeros: an all-zero state is a lock-up state — the register stays at zero forever and produces no keystream. Always load a non-zero seed.
- Forgetting error multiplication when choosing a self-sync scrambler ahead of an FEC block; the multiplied errors can exceed the code's correction capacity.
Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres