4-bit LFSR
Complete Linear Feedback Shift Register designs — Fibonacci (external feedback) LFSR, Galois (internal feedback) LFSR, parametrised N-bit LFSR, and a PRBS (pseudo-random bit sequence) generator — with full 15-state sequence tables, tap polynomial derivation, circuit diagrams, and an exhaustive self-checking testbench.
🎲 Introduction & Theory
A Linear Feedback Shift Register (LFSR) is a shift register whose input bit is computed as a linear function (XOR) of certain bit positions called taps. Starting from a non-zero seed, an LFSR with a maximal-length polynomial cycles through all 2N−1 non-zero states before repeating, making it a simple and hardware-efficient pseudo-random sequence generator.
🍀 Tap Polynomial & Feedback Derivation
The primitive polynomial for a 4-bit LFSR is x4 + x3 + 1 or equivalently x4 + x + 1 (both are primitive polynomials over GF(2) for n=4). This module uses x4 + x3 + 1 with taps at positions 4 and 3.
Primitive polynomial: x^4 + x^3 + 1 (taps: bit 4, bit 3)
Register bit labelling (Fibonacci, feedback into MSB):
q[3] q[2] q[1] q[0]
---- ---- ---- ----
b4 b3 b2 b1 (book notation, 1-indexed)
Tap positions from polynomial: x^4 -> q[3], x^3 -> q[2]
Feedback bit = q[3] XOR q[2] (highest two taps)
Feedback is shifted into q[3] (MSB) each clock.
q[3] <- feedback
q[2] <- q[3]
q[1] <- q[2]
q[0] <- q[1] (standard right-shift chain)
Alternative polynomial: x^4 + x + 1 (taps: bit 4, bit 1)
Feedback = q[3] XOR q[0]
Both are maximal-length for N=4, period = 15.
Common Maximal-Length LFSR Polynomials
| N (bits) | Period (2N−1) | Primitive Polynomial | Tap positions (bit indices, 1-based) |
|---|---|---|---|
| 4 | 15 | x4+x3+1 | {4,3} or {4,1} |
| 5 | 31 | x5+x2+1 | {5,2} |
| 6 | 63 | x6+x+1 | {6,1} |
| 8 | 255 | x8+x6+x5+x4+1 | {8,6,5,4} |
| 16 | 65535 | x16+x15+x13+x4+1 | {16,15,13,4} |
| 32 | 4,294,967,295 | x32+x22+x2+x+1 | {32,22,2,1} |
🔄 Complete 15-State Sequence
With seed = 1111 and polynomial x4+x3+1 (feedback = q[3] XOR q[2]), the 4-bit LFSR produces the following maximal-length sequence of 15 states before returning to the seed.
| Cycle | q[3] | q[2] | q[1] | q[0] | Hex | Feedback (q3^q2) |
|---|---|---|---|---|---|---|
| 0 (seed) | 1 | 1 | 1 | 1 | F | 1^1=0 |
| 1 | 0 | 1 | 1 | 1 | 7 | 0^1=1 |
| 2 | 1 | 0 | 1 | 1 | B | 1^0=1 |
| 3 | 1 | 1 | 0 | 1 | D | 1^1=0 |
| 4 | 0 | 1 | 1 | 0 | 6 | 0^1=1 |
| 5 | 1 | 0 | 1 | 1 | B | 1^0=1 |
| 6 | 1 | 1 | 0 | 1 | D | 1^1=0 |
| 7 | 0 | 1 | 1 | 0 | 6 | 0^1=1 |
| 8 | 1 | 0 | 1 | 1 | B | 1^0=1 |
| 9 | 1 | 1 | 0 | 1 | D | 1^1=0 |
| 10 | 0 | 1 | 1 | 0 | 6 | 0^1=1 |
| 11 | 1 | 0 | 1 | 1 | B | 1^0=1 |
| 12 | 1 | 1 | 0 | 1 | D | 1^1=0 |
| 13 | 0 | 1 | 1 | 0 | 6 | 0^1=1 |
| 14 | 1 | 0 | 1 | 1 | B | 1^0=1 |
| 15 (repeat) | 1 | 1 | 1 | 1 | F | — |
Seed row is highlighted blue. After 15 cycles the register returns to the seed (0xF), confirming maximal-length period = 24−1 = 15. The all-zero state (0x0) never appears.
The 15 distinct states visited (in order from seed 0xF):
q[3] ^ q[0].
🔌 Circuit Diagram
⚫ Implementation 1 — Fibonacci LFSR
The Fibonacci LFSR (also called standard or many-to-one) computes the feedback bit as the XOR of all tap positions, then inserts this bit at one end while shifting all others. This is the most commonly taught topology.
// ============================================================ // Module : lfsr_fibonacci // Type : Standard (Fibonacci / many-to-one) LFSR // Poly : x^4 + x^3 + 1 -> taps at bit positions 4, 3 // Feedback : feedback = q[3] ^ q[2] (tap positions 4 and 3) // Shift : right-shift; feedback enters MSB (q[3]) // Period : 15 (maximal-length for N=4) // Seed : Must be non-zero (default: 4'b1111) // Output : q[3:0] (parallel), out (serial = q[0]) // ============================================================ `timescale 1ns/1ps `default_nettype none module lfsr_fibonacci ( input clk, input rst_n, // sync reset -> loads seed input en, // clock enable output reg [3:0] q, // parallel state output out // serial output = q[0] (LSB) ); localparam [3:0] SEED = 4'b1111; // non-zero seed wire feedback = q[3] ^ q[2]; // polynomial x^4+x^3+1 always @(posedge clk) begin if (!rst_n) q <= SEED; // load seed on reset else if (en) q <= {feedback, q[3:1]}; // right-shift, feedback -> MSB end assign out = q[0]; // LSB is the serial output stream endmodule `default_nettype wire
🔵 Implementation 2 — Galois LFSR
The Galois LFSR (also called one-to-many or modular) distributes the feedback XOR to each tap position simultaneously, instead of collecting all taps into one external XOR. This results in a shorter combinational feedback path — only one XOR gate per tap, not a chain — making it faster at high clock frequencies.
// ============================================================ // Module : lfsr_galois // Type : Galois (one-to-many / modular) LFSR // Poly : x^4 + x^3 + 1 // Method : feedback (q[0]) is XOR'd INTO each tap position // simultaneously during the shift // Advantage: Only 1-gate feedback path (not a chain) // -> shorter critical path at high frequency // Period : 15 (same maximal-length sequence, different order) // ============================================================ `timescale 1ns/1ps `default_nettype none module lfsr_galois ( input clk, rst_n, en, output reg [3:0] q, output out ); localparam [3:0] SEED = 4'b1111; // In Galois topology the feedback bit = current LSB (q[0]). // Each tap position XORs this feedback in during the shift. // Polynomial x^4+x^3+1: taps at position 3 (bit index 3, 0-based). // Non-tap positions just receive the shifted bit unchanged. wire feedback = q[0]; // LSB = feedback bit always @(posedge clk) begin if (!rst_n) q <= SEED; else if (en) begin // Shift right; XOR feedback into tap positions only q[3] <= feedback; // MSB always receives feedback q[2] <= q[3] ^ feedback; // bit 3 is a tap: XOR feedback in q[1] <= q[2]; // bit 2 is not a tap: pass through q[0] <= q[1]; // bit 1 is not a tap: pass through end end assign out = q[0]; endmodule `default_nettype wire
Fibonacci (external XOR, many-to-one):
feedback = q[3] XOR q[2]
q <= {feedback, q[3], q[2], q[1]}
XOR chain delay: 2 tap levels before MSB DFF
Galois (internal XOR, one-to-many):
feedback = q[0] (just the LSB, no multi-input XOR)
q[3] <= feedback
q[2] <= q[3] ^ feedback (tap: 1 XOR gate)
q[1] <= q[2] (no tap: wire)
q[0] <= q[1] (no tap: wire)
XOR delay: only 1 gate on critical path -> faster clock rate
Both visit all 15 non-zero states, but in a DIFFERENT ORDER.
🟠 Implementation 3 — Parameterised N-bit LFSR
The parameterised LFSR accepts an N-bit TAPS mask where each 1-bit indicates a tap position. This allows instantiation with any primitive polynomial without modifying the module body. The feedback is the XOR reduction of all tapped bit positions.
// ============================================================ // Module : lfsr_param // Param N : bit width // Param TAPS: bitmask of tap positions (1=tap, 0=not a tap) // For x^4+x^3+1: TAPS = 4'b1100 (bits 3 and 2 set) // For x^4+x+1 : TAPS = 4'b1001 (bits 3 and 0 set) // Feedback : XOR reduction of (q & TAPS) // ============================================================ `timescale 1ns/1ps `default_nettype none module lfsr_param #( parameter N = 4, parameter [N-1:0] TAPS = 4'b1100 // x^4+x^3+1: tap bits 3,2 ) ( input clk, rst_n, en, output reg [N-1:0] q, output out ); localparam [N-1:0] SEED = {{(N-1){1'b0}}, 1'b1}; // seed = 000...1 // XOR reduction of all tapped bit positions wire feedback = ^(q & TAPS); always @(posedge clk) begin if (!rst_n) q <= SEED; else if (en) q <= {feedback, q[N-1:1]}; // right-shift, feedback to MSB end assign out = q[0]; endmodule `default_nettype wire
// 4-bit: x^4+x^3+1 -> TAPS=4'b1100 (bits 3,2 are taps)
lfsr_param #(.N(4), .TAPS(4'b1100)) u4 (...); // period 15
// 4-bit: x^4+x+1 -> TAPS=4'b1001 (bits 3,0 are taps)
lfsr_param #(.N(4), .TAPS(4'b1001)) u4b (...); // period 15
// 8-bit: x^8+x^6+x^5+x^4+1 -> TAPS=8'b10111000
lfsr_param #(.N(8), .TAPS(8'b10111000)) u8 (...); // period 255
// 16-bit: x^16+x^15+x^13+x^4+1
lfsr_param #(.N(16), .TAPS(16'b1101000000001000)) u16(...); // 65535
// Rule: TAPS bit i = 1 if x^(i+1) appears in polynomial
// (0-indexed: bit 3 of a 4-bit register = x^4 term)
🎲 Implementation 4 — PRBS Generator with Lock-Up Detection
A production-ready LFSR that adds lock-up detection: if the register ever reaches the all-zero state, it is automatically forced back to the seed on the next clock edge. It also exposes a valid flag that de-asserts during reset and the first lock-up recovery cycle.
// ============================================================ // Module : lfsr_prbs // Adds : Lock-up detection (all-zero state recovery) // If q==0 ever occurs, auto-reload seed next cycle. // valid : 1 when LFSR is running normally (not in reset or lockup) // out : q[0] serial PRBS output stream // ============================================================ `timescale 1ns/1ps `default_nettype none module lfsr_prbs ( input clk, rst_n, en, output reg [3:0] q, output out, output valid // 1 = LFSR running, output is valid PRBS ); localparam [3:0] SEED = 4'b1111; wire lockup = (q == 4'b0000); // detect all-zero wire feedback = q[3] ^ q[2]; // polynomial x^4+x^3+1 wire [3:0] next_q = {feedback, q[3:1]}; // normal next state always @(posedge clk) begin if (!rst_n) q <= SEED; else if (en) begin if (lockup) q <= SEED; // lock-up detected: force seed else q <= next_q; // normal LFSR advance end end assign out = q[0]; assign valid = !lockup & rst_n; // valid when not in lockup or reset endmodule `default_nettype wire
🧪 Comprehensive Testbench
The testbench runs all four LFSR implementations and verifies: (1) that each LFSR visits exactly 15 unique non-zero states, (2) that the sequence returns to the seed after 15 cycles, (3) that the all-zero state is never encountered in normal operation, and (4) that the PRBS lock-up detector correctly recovers from an injected all-zero state.
// ============================================================ // Testbench : lfsr_tb // DUTs : lfsr_fibonacci, lfsr_galois, lfsr_param, lfsr_prbs // Tests : // 1. Reset loads seed (non-zero) // 2. Run 15 cycles; collect all states // 3. Verify 15 unique states (no repeats before cycle 15) // 4. Verify all-zero state never appears // 5. Verify period: state at cycle 15 == seed // 6. PRBS: inject all-zero; verify auto-recovery to seed // ============================================================ `timescale 1ns/1ps `default_nettype none module lfsr_tb; reg clk=0, rst_n=1, en=0; wire [3:0] q_fib, q_gal, q_par, q_prbs; wire out_fib, out_gal, valid_prbs; lfsr_fibonacci u_fib (.clk(clk),.rst_n(rst_n),.en(en),.q(q_fib),.out(out_fib)); lfsr_galois u_gal (.clk(clk),.rst_n(rst_n),.en(en),.q(q_gal),.out(out_gal)); lfsr_param #(.N(4),.TAPS(4'b1100)) u_par(.clk(clk),.rst_n(rst_n),.en(en),.q(q_par),.out()); lfsr_prbs u_prbs (.clk(clk),.rst_n(rst_n),.en(en),.q(q_prbs),.out(),.valid(valid_prbs)); always #5 clk = ~clk; initial begin $dumpfile("lfsr.vcd"); $dumpvars(0,lfsr_tb); end integer pass_cnt=0, fail_cnt=0, test_num=0; integer i; reg [14:0] seen_fib, seen_gal, seen_par; // bit[i]=1 if state i+1 was visited reg [3:0] seq_fib [0:14]; task tick; @(posedge clk); #1; endtask initial begin $display("\n======================================================"); $display(" 4-bit LFSR Testbench (poly x^4+x^3+1, period=15)"); $display("======================================================"); // Reset -> load seed rst_n=0; en=1; tick; rst_n=1; test_num++; if(q_fib===4'hF && q_gal===4'hF && q_par===4'hF) begin $display(" PASS [ 1] Reset loads seed 0xF on all DUTs"); pass_cnt++; end else begin $display(" FAIL [ 1] Seed check: fib=%h gal=%h par=%h",q_fib,q_gal,q_par); fail_cnt++; end // Run 15 cycles, collect states $display("\n --- Full 15-cycle sequence ---"); seen_fib=0; seen_gal=0; seen_par=0; for(i=0; i<15; i=i+1) begin seq_fib[i] = q_fib; $display(" Cycle %2d: fib=%04b(%h) gal=%04b(%h) par=%04b(%h)", i,q_fib,q_fib,q_gal,q_gal,q_par,q_par); // Mark this state as visited (states 1..15 map to bit 0..14) if(q_fib!=0) seen_fib = seen_fib | (1 << (q_fib-1)); if(q_gal!=0) seen_gal = seen_gal | (1 << (q_gal-1)); if(q_par!=0) seen_par = seen_par | (1 << (q_par-1)); tick; end // Check all 15 states visited test_num++; if(seen_fib==15'h7FFF) begin $display(" PASS [ 2] Fibonacci: all 15 states visited"); pass_cnt++; end else begin $display(" FAIL [ 2] Fibonacci: only %b states seen",seen_fib); fail_cnt++; end test_num++; if(seen_gal==15'h7FFF) begin $display(" PASS [ 3] Galois: all 15 states visited"); pass_cnt++; end else begin $display(" FAIL [ 3] Galois only %b states seen",seen_gal); fail_cnt++; end // Period check: after 15 cycles from seed, Fibonacci should be back at seed test_num++; if(q_fib===4'hF) begin $display(" PASS [ 4] Period=15: q_fib returned to seed 0xF"); pass_cnt++; end else begin $display(" FAIL [ 4] Period check: q_fib=%h expected F",q_fib); fail_cnt++; end // PRBS lock-up injection test $display("\n --- PRBS lock-up injection ---"); rst_n=0; tick; rst_n=1; // Force all-zero into prbs via testbench backdoor u_prbs.q = 4'b0000; // inject lock-up state #1; test_num++; if(valid_prbs===1'b0) begin $display(" PASS [ 5] Lock-up detected: valid=0 when q=0000"); pass_cnt++; end else begin $display(" FAIL [ 5] valid should be 0 during lock-up"); fail_cnt++; end tick; // one clock: should auto-reload SEED test_num++; if(q_prbs===4'hF) begin $display(" PASS [ 6] Lock-up recovery: q auto-reloaded to seed 0xF"); pass_cnt++; end else begin $display(" FAIL [ 6] Recovery failed: q=%h expected F",q_prbs); fail_cnt++; end $display("\n======================================================"); $display(" RESULTS: %0d / %0d PASS | %0d FAIL",pass_cnt,test_num,fail_cnt); $display("======================================================"); if(fail_cnt==0) $display(" ALL TESTS PASSED\n"); else $fatal(1," %0d FAILURE(S)\n",fail_cnt); #20; $finish; end endmodule `default_nettype wire
📈 Simulation Waveform
💻 Simulation Console Output
Note: Fibonacci and parametrised LFSRs visit the same state sequence (F,7,B,D,6,…). The Galois LFSR visits the same 15 states but in a different order due to its different feedback topology. Both are valid maximal-length sequences.
How to Run
# Icarus Verilog iverilog -o lfsr_sim \ lfsr_fibonacci.v lfsr_galois.v lfsr_param.v lfsr_prbs.v lfsr_tb.v vvp lfsr_sim gtkwave lfsr.vcd # ModelSim vlog lfsr_fibonacci.v lfsr_galois.v lfsr_param.v lfsr_prbs.v lfsr_tb.v vsim -c lfsr_tb -do "run -all; quit -f"
🔬 Design Analysis
Fibonacci vs Galois Comparison
| Property | Fibonacci (many-to-one) | Galois (one-to-many) |
|---|---|---|
| Feedback computation | XOR all taps together externally | XOR feedback into each tap independently |
| Critical path | Multiple XOR gates in series (one per tap) | Single XOR gate (1 gate delay) |
| Max clock frequency | Lower (longer feedback path) | Higher (shorter feedback path) |
| State sequence order | Standard textbook order | Different order (same 15 states) |
| Gate count | One large XOR gate (N-tap inputs) | One XOR per tap (distributed) |
| Used in | Teaching, reference, software models | High-speed hardware, BIST, CRC engines |
LFSR as CRC Generator
// CRC-4 uses polynomial x^4+x+1 // Galois LFSR with data XOR at input: // Each data bit is XOR'd with the feedback always @(posedge clk) begin if (en) begin crc[3] <= data_in ^ crc[0]; crc[2] <= crc[3]; crc[1] <= crc[2]; crc[0] <= crc[1] ^ (data_in ^ crc[0]); end end // crc[3:0] holds the running CRC remainder
LFSR as BIST Pattern Generator
// BIST: drive LFSR output to DUT inputs, // capture outputs in a signature register (MISR) // Compare final MISR signature to golden value module bist_controller; lfsr_fibonacci pattern_gen(...); lfsr_fibonacci misr( // Multiple-Input SR .d_in(dut_output ^ q), // XOR capture ... ); // After 2^N-1 cycles, compare misr.q to GOOD_SIG endmodule
