Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • How to Read the Diagrams
  • Part 0: What Is a Protocol?10
  • Part 1: Timing and Clocking11
  • Part 2: Encoding and Framing12
    • Line Codes
    • Framing Basics
    • Packet Anatomy
    • Bit Stuffing
    • DC Balance and Running Disparity
    • Block Codes
    • Scrambling
    • Alignment and Lock
    • Control Symbols and Ordered Sets
    • Flits, Phits and Packing
    • Protocols Compared: Encoding and Framing
    • Drills: Encoding and Framing
  • Part 3: Handshakes13
  • Part 4: Flow Control17
  • Part 5: Transactions19
  • Part 6: Concurrency and Performance17
  • Part 7: Ordering13
  • Part 8: Sharing and Arbitration12
  • Part 9: Deadlock and Liveness13
  • Part 10: Reliability and Recovery20
  • Part 11: Bring-Up and Link Management10
  • Part 12: Power8
  • Part 13: Shared State and Coherence9
  • Part 14: Security and Isolation7
  • Part 15: Specifying and Verifying11
  • Part 16: Protocols Dissected17
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 2: Encoding and Framing
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 2: Encoding and Framing
  4. Alignment and Lock

Part 2 · Encoding and Framing · Senior (S)

Alignment and Lock

How a receiver finds block boundaries in a raw bit stream by hunting for sync headers, why it needs several good ones in a row, and how it notices it has lost lock.

On this page

    Note

    Level: Senior (S) · Answers: Where does a message start and end?

    Before this: Block Codes, Scrambling

    A 64b/66b receiver powers up and starts collecting bits. It knows that every 66 bits there is a two-bit header that must be 01 or 10, but it has no idea which of the 66 possible positions is the right one. Somewhere in the stream, scrambled payload bits will also form 01 or 10 by chance, about half the time.

    How does a receiver find the true block boundary, avoid being fooled by payload that looks like a header, and notice if it ever loses its place?

    The obvious answer, and why it breaks

    The obvious answer is to check one position, and if the header looks valid, declare it the boundary. Since half of all random two-bit patterns are 01 or 10, the receiver would be fooled half the time at a wrong position. The opposite mistake is just as bad: once locked, giving up after a single bad header would make every bit error on the line throw away the alignment and start a long hunt. The receiver needs a rule with memory: several good headers in a row to believe a position, several bad ones in a short window to abandon it.

    The idea: hunt, confirm, then watch

    Block lock is a small state machine:

    • HUNT. Check the header at the current position. If it is invalid, this position is wrong: slip the boundary by one bit and try again. If it is valid, count it.

    • Confirm. A wrong position passes each check with probability ½, so N valid headers in a row happen by chance only with probability (½)ᴺ. After N, declare LOCKED.

    • LOCKED. Keep checking every header. Isolated errors are expected (bit errors happen), so tolerate a few. If too many bad headers arrive within a window, the boundary has probably moved: go back to HUNT.

    Two states, with memory in both: N good headers to lock, several bad ones in a window to unlock.

    Build it: lock on a 2-bit header

    To keep the waveform short, the listing shrinks 64b/66b to 10-bit blocks: a 2-bit header (01 or 10) and 8 payload bits. The receiver gets 10-bit words starting at an unknown bit offset (the true one is 3). In HUNT it slips one bit on every bad header and locks after four good ones in a row. While locked, three bad headers within a window of eight blocks drop it back to HUNT. The testbench later corrupts four headers in a row to show loss of lock and relock. Note that slip acts in the same cycle: a registered slip would act a cycle late, and the receiver would slip again on a word that was still from the old offset.

    block_lock.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // Block lock on a 2-bit sync header (the 64b/66b idea, shrunk to 10-bit blocks so
    // the waveform stays short). Each block is a header, 01 or 10, then 8 payload bits.
    // The receiver gets 10-bit words at an unknown bit offset. In HUNT it checks each
    // word's header: a bad header means "wrong offset", so it asks the deserialiser to
    // slip one bit and starts counting again. Four good headers in a row: LOCKED. While
    // locked, three bad headers within eight blocks mean lock was lost: back to HUNT.
    module block_lock (
      input  logic       clk, rst_n,
      input  logic [9:0] word,           // word[9:8] is where the header should be
      output logic       slip,           // move the word boundary by one bit at this edge
      output logic       locked,
      output logic       hdr_ok
    );
      logic [2:0] good;                  // consecutive good headers while hunting
      logic [2:0] seen, bad;             // blocks and bad headers in the current window (locked)
      assign hdr_ok = (word[9:8] == 2'b01) || (word[9:8] == 2'b10);
      // Combinational, so the deserialiser moves the boundary at this same edge and the
      // next word is already at the new offset (a registered slip would act a cycle late
      // and the FSM would slip again on the stale word).
      assign slip = !locked && !hdr_ok;
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          locked <= 0; good <= 0; seen <= 0; bad <= 0;
        end else begin
          if (!locked) begin
            if (!hdr_ok)                  good <= 0;
            else if (good == 3)     begin locked <= 1; seen <= 0; bad <= 0; end
            else                          good <= good + 1;
          end else begin
            seen <= seen + 1;
            if (!hdr_ok) bad <= bad + 1;
            if (!hdr_ok && bad == 2)  begin locked <= 0; good <= 0; end   // third bad header
            else if (seen == 7)             begin seen <= 0; bad <= 0; end // window of 8 ends
          end
        end
      end
    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
    
      // The bit stream: 3 junk bits, then 40 blocks. Headers alternate in a fixed mix of
      // 01 and 10; payloads come from a simple counter pattern.
      localparam int NB = 40, OFFSET = 3;
      logic stream [0:NB*10 + 30];
      logic [7:0] payload [0:NB-1];
      initial begin
        stream[0] = 1; stream[1] = 1; stream[2] = 1;       // junk before the first block
        for (int b = 0; b < NB; b++) begin
          logic [1:0] h;
          h = (b % 3 == 1) ? 2'b10 : 2'b01;
          payload[b] = 8'(b * 37 + 11);
          stream[OFFSET + 10*b]     = h[1];
          stream[OFFSET + 10*b + 1] = h[0];
          for (int k = 0; k < 8; k++) stream[OFFSET + 10*b + 2 + k] = payload[b][7-k];
        end
        for (int j = OFFSET + 10*NB; j < NB*10 + 31; j++) stream[j] = 0;
      end
    
      // Deserialiser model: delivers word c from bit position 10*c + k; a slip adds one to k.
      int c = 0, k = 0;
      logic [9:0] word;
      wire slip, locked, hdr_ok;
      wire [2:0] good = dut.good;                       // copy for the waveform
      always @* for (int n = 0; n < 10; n++) word[9-n] = stream[10*c + k + n];
      // Corrupt four headers in a row (blocks 30 to 33) so at least three fall in one window.
      wire [9:0] word_in = (c >= 30 && c <= 33) ? {2'b11, word[7:0]} : word;
      always @(posedge clk) if (rst_n) begin
        c <= c + 1;
        if (slip) k <= k + 1;
      end
      block_lock dut (.clk, .rst_n, .word(word_in), .slip, .locked, .hdr_ok);
    
      // Checker: once locked (and outside the corrupted blocks), every payload is the next expected one.
      int errors = 0, locks = 0, losses = 0, good_payloads = 0;
      logic locked_q = 0;
      always @(posedge clk) if (rst_n) begin
        if (locked && !locked_q) locks++;
        if (!locked && locked_q) losses++;
        locked_q <= locked;
        if (locked && hdr_ok && !(c >= 30 && c <= 33)) begin
          // A boundary that is a whole block later is also correct alignment.
          if ((k - OFFSET) % 10 != 0 || word_in[7:0] != payload[c + (k - OFFSET) / 10]) errors++;
          else good_payloads++;
        end
      end
      initial begin
        #400;
        if (errors == 0 && locks == 2 && losses == 1 && good_payloads > 20)
          $display("PASS: locked at offset %0d, lost lock on 3 bad headers, relocked; %0d payloads checked", k, good_payloads);
        else $display("FAIL: errors %0d, locks %0d, losses %0d, offset %0d", errors, locks, losses, k);
        $finish;
      end
    endmodule

    Watch it run

    How to read it, slot by slot:

    • Slots 0 to 2: the words start at offsets 0, 1 and 2 (k), so their top two bits are payload, not header, and happen to be 11 or 00: hdr_ok is low, slip is high, and k steps up each slot.

    • Slot 3: at offset 3 the header is valid. Slipping stops.

    • Slots 3 to 6: four valid headers in a row. The counter good climbs 0, 1, 2, 3.

    • Slot 7: after the fourth good header the receiver is LOCKED, at the true offset of 3.

    The run continues beyond the waveform. Four corrupted headers later drop the lock, the next good headers relock it at the same offset, and the checker confirms that every payload received while locked is the right one.

    Worked numbers: how many good headers to require

    At a wrong position, each header check passes by chance with probability ½ (scrambled bits are about equally likely to be 01, 10, 00 or 11). Requiring N good headers in a row bounds the chance of a false lock at each wrong position:

    Diagram
    N =  4:  (1/2)^4  = 1 in 16            -> false locks are common; recovered by the LOCKED-state check
    N = 16:  (1/2)^16 = 1 in 65,536
    N = 64:  (1/2)^64 = about 1 in 1.8 x 10^19
    
    Time to lock grows with N: up to (number of positions) slips plus N blocks.

    The listing uses N = 4 to keep the waveform short; real links use much larger counts, trading a slightly slower first lock for false-lock odds that are effectively zero.

    The design space

    Parameter

    Larger value

    Smaller value

    Good headers to lock (N)

    Fewer false locks; slower to lock

    Faster lock; false locks more likely

    Bad headers to unlock (and window)

    Survives bursts of bit errors without dropping lock; slower to notice a real loss

    Notices real loss quickly; drops lock on noise

    Search strategy

    Check several positions in parallel: faster lock, more logic

    One position at a time with slips: least logic

    How real protocols find alignment

    Protocol family

    What the receiver searches for

    64b/66b (for example 10GBASE-R)

    The 2-bit sync header, 01 for data or 10 for control, at the start of every 66-bit block, using a hunt-and-confirm state machine like the one above.

    PCI Express 128b/130b

    The 2-bit sync header (10 data, 01 ordered set) on every 130-bit block; it is left unscrambled so it can be found first.

    8b/10b links

    The comma pattern (0011111 or 1100000) inside the K28.5 control character, which cannot appear across data code groups, so a single comma gives the word boundary.

    8b/10b's comma gives alignment in one step, because the code is designed so data never produces it. Header-based codes cannot promise that, which is why they need hunt-and-confirm with memory.

    What breaks

    A receiver unlocks after one bad header (illustrative). On a clean bench it never matters. On a noisy channel with a bit error rate where a header is hit every few seconds, the link drops lock, hunts, relocks and loses the traffic in between, every few seconds, and reports it as link instability. The fix is a window: tolerate a few bad headers per window, so random errors are absorbed and only a real loss of alignment triggers a hunt.

    Why this matters

    Senior lens. Lock state machines encode two error budgets: the false-lock probability, set by N, and the false-unlock rate, set by the window, both against the channel's bit error rate. Ask for both numbers in review, and for the worst-case time to lock, which bounds link start-up and recovery time. Also ask what the receiver does with data while hunting (it must discard it) and whether a lock loss is visible to software as a counter, because silent relocking hides a degrading channel.

    Interview

    Asked: "How does a receiver know where a block starts?"

    Weak answer: It looks for the header.

    Strong answer: It hunts: check the header at the current bit position; if it isn't a valid 01 or 10, slip one bit and retry. Payload matches by chance half the time, so it requires N valid headers in a row before declaring lock, giving a false-lock chance of about (½)ᴺ per wrong position. Once locked it keeps checking and drops lock only after several bad headers in a window, so isolated bit errors don't trigger a re-hunt. 8b/10b avoids the hunt with a comma pattern data can never produce.

    Follow-ups: (1) How would you choose N? (2) Why must a slip take effect before the next word is checked?

    Check yourself A receiver requires 8 good headers in a row. What is the chance of a false lock at one wrong position?

    (½)⁸ = 1 in 256.

    Check yourself Why does the listing make slip combinational?

    So the deserialiser moves the boundary at the same edge that judged the header bad. A registered slip takes effect a cycle later, the FSM sees one more word at the old offset, judges it bad too, and slips twice.

    Check yourself Why can an 8b/10b receiver align from a single comma, while a 64b/66b receiver needs many headers?

    The comma pattern is designed never to appear in or across data code groups, so one sighting is conclusive. A 2-bit header pattern appears in scrambled payload half the time, so only a long run of matches is convincing.

    Key takeaways

    • Block lock hunts by slipping one bit at a time until headers are valid, then requires N good headers in a row.

    • N sets the false-lock odds, about (½)ᴺ per wrong position; a window of tolerated bad headers keeps noise from dropping lock.

    • Codes with unique comma patterns align in one step; header-based codes need hunt-and-confirm with memory.

    Keep going

    Next: Lesson 2.9, Control Symbols and Ordered Sets. How a link sends idle, alignment and clock-compensation information that can never be mistaken for data.

    Related topics

    • Part 2 overview: Encoding and Framing

    • Lesson 0.5: Protocols as State Machines

    Previous

    33 / 234

    Next: Control Symbols and Ordered Sets

    Up next

    Up nextControl Symbols and Ordered Sets

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Line Codes
    • Framing Basics
    • Packet Anatomy
    clkword_in3a138caa17a29f1c41e920e13315827d1a21c72ec111136hdr_okslipk0123good0123locked
    Simulation output: the receiver slips from offset 0 to 3, sees four good headers, and locks. One slot is one 10-bit word.