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. Bit Stuffing

Part 2 · Encoding and Framing · Working (W)

Bit Stuffing

Insert a bit after a run of identical bits, and remove it at the receiver: how USB and CAN guarantee transitions and keep flags unique, and what it costs.

On this page

    Note

    Level: Working (W) · Answers: Where does a message start and end?

    Before this: Line Codes, Framing Basics

    A serial protocol marks the start of each frame with a flag pattern, 01111110. Engineers soon notice that ordinary data can contain the same eight bits, and the receiver then sees a frame start in the middle of a payload. Separately, a long run of 1s in the data starves the receiver's clock recovery of transitions. Both problems come from the data being allowed to contain any pattern at all.

    How can a protocol stop data from forming certain patterns, without restricting what data can be sent?

    The obvious answer, and why it breaks

    One obvious answer is to forbid the patterns: tell users not to send six 1s in a row. That makes the link unable to carry arbitrary data, which is unacceptable. Another is to pick a flag so long and strange that data "will never" contain it, which only makes the failure rarer and harder to find. The protocol must accept every possible payload and still guarantee the forbidden patterns never appear on the wire.

    The idea: insert a bit, remove it at the other end

    Bit stuffing changes the data on the wire, not the data itself. The transmitter watches the bits it sends. Whenever a run of identical bits reaches a set length, it inserts one extra bit of the opposite value. The receiver follows the same rule: after the same run, it knows the next bit was inserted and throws it away. The payload arrives unchanged; only its on-wire form was altered.

    Two things follow at once. The wire can never carry a run longer than the limit, so transitions are guaranteed for clock recovery. And a flag that contains a run longer than the limit can never appear inside stuffed data, so it is unique by construction. A stuffed bit that is missing, or a run one longer than allowed, also tells the receiver something went wrong: a free error check.

    Eight 1s in the data become six 1s, a stuffed 0, then two more 1s on the wire. The receiver drops the bit that follows six 1s.

    Build it: stuff, unstuff, measure

    The model implements the two rules used in practice. USB inserts a 0 after six 1s. CAN inserts a bit of the opposite value after five identical bits of either value, and the inserted bit starts the next run. It checks that every stream comes back exactly, and measures overhead on random data, on all 1s, and on the pattern that is worst for CAN.

    example.py
    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
    """Bit stuffing, two ways.
    
    USB style: after six 1s in a row, insert a 0.
    CAN style: after five identical bits in a row, insert one bit of the opposite value
    (the inserted bit counts towards the next run).
    The receiver removes exactly the bits the rule says were inserted.
    """
    import random
    
    
    def usb_stuff(bits):
        out, ones = [], 0
        for b in bits:
            out.append(b)
            ones = ones + 1 if b else 0
            if ones == 6:
                out.append(0); ones = 0
        return out
    
    
    def usb_unstuff(bits):
        out, ones, skip = [], 0, False
        for b in bits:
            if skip:
                assert b == 0, "bit-stuff error: seventh 1"
                skip, ones = False, 0
                continue
            out.append(b)
            ones = ones + 1 if b else 0
            if ones == 6:
                skip = True
        return out
    
    
    def can_stuff(bits):
        out, last, run = [], None, 0
        for b in bits:
            out.append(b)
            run = run + 1 if b == last else 1
            last = b
            if run == 5:
                out.append(1 - b); last, run = 1 - b, 1
        return out
    
    
    def can_unstuff(bits):
        out, last, run, skip = [], None, 0, False
        for b in bits:
            if skip:
                assert b != last, "stuffing violation: six identical bits"
                skip, last, run = False, b, 1
                continue
            out.append(b)
            run = run + 1 if b == last else 1
            last = b
            if run == 5:
                skip = True
        return out
    
    
    rng = random.Random(7)
    data = [rng.randint(0, 1) for _ in range(10_000)]
    cases = {
        "random data": data,
        "all ones": [1] * 1200,
        "CAN worst case (11111 then 0000 1111 repeated)": [1] * 5 + [0, 0, 0, 0, 1, 1, 1, 1] * 150,
    }
    print(f"{'data':48} {'USB overhead':>13} {'CAN overhead':>13}")
    for name, bits in cases.items():
        u, c = usb_stuff(bits), can_stuff(bits)
        assert usb_unstuff(u) == bits and can_unstuff(c) == bits
        print(f"{name:48} {100 * (len(u) - len(bits)) / len(bits):12.1f}% {100 * (len(c) - len(bits)) / len(bits):12.1f}%")
    print("PASS: both schemes round-trip; overhead depends on the data, with a fixed worst case")

    It prints:

    Diagram
    data                                              USB overhead  CAN overhead
    random data                                               0.6%          3.2%
    all ones                                                 16.7%         20.0%
    CAN worst case (11111 then 0000 1111 repeated)            0.0%         25.0%
    PASS: both schemes round-trip; overhead depends on the data, with a fixed worst case

    Three lessons in three lines. On random data stuffing is nearly free: long runs are rare. Its cost depends on the data, so the length on the wire is not fixed, which complicates timing for any protocol that must know exactly when a frame ends. And the worst case is bounded and known: about 17% for USB on all 1s, 25% for CAN on a pattern where each stuffed bit itself starts a new run of the opposite value. A link's timing budget must allow for the worst case, not the average.

    The design space

    Approach

    Guarantees

    Cost

    Bit stuffing (this lesson)

    Bounded run length; unique flags; a free error check

    Variable frame length; worst-case overhead set by the run limit

    Byte stuffing (an escape byte before any byte that looks like a flag)

    Unique flags at byte level

    Variable length; worst case can double the size

    Block codes such as 8b/10b (lesson 2.6)

    Bounded runs, balance, and special control symbols

    Fixed 20% overhead

    Scrambling plus sync headers (lesson 2.7)

    Long runs become very unlikely; headers always carry a transition

    Almost none, but only statistical for the payload

    Stuffing suits low and medium rates where variable length is acceptable. At high rates, protocols prefer fixed-overhead codes, because a fixed-length block is much easier to pipeline than a frame whose length depends on its content.

    How real protocols chose

    Protocol

    Its stuffing rule

    USB 2.0

    Insert a 0 after six consecutive 1s, then encode with NRZI. In NRZI a 0 is a transition, so the stuffed 0 forces one; a seventh 1 in a row is a bit-stuff error.

    CAN

    Insert a bit of the opposite value after five identical bits, from the start of the frame through the CRC field. Six identical bits in a row is a stuffing violation, which receivers report as an error.

    The two rules differ for a reason: USB's NRZI already turns 0s into transitions, so only runs of 1s need breaking. CAN sends levels directly, so runs of either value must be broken.

    What breaks

    A designer sizes a receive buffer and a frame timeout from the payload length alone (illustrative). On normal traffic, stuffing adds well under 1% and everything fits. Then a customer sends a payload of all 1s: the frame on the wire is about 17% longer, the timeout fires before the frame ends, and the frame is discarded. Only that customer's data triggers it.

    • Symptom: frames lost only for particular data content.

    • Root cause: buffers and timeouts sized for the average stuffed length, not the worst case.

    • Fix: size everything that depends on frame length for the worst-case stuffing ratio, and add all-ones and worst-case patterns to the regression.

    Why this matters

    Senior lens. Stuffing makes frame length data-dependent, and that ripples into every timing promise: latency bounds, buffer sizes, timeouts, and bandwidth guarantees. When you review a stuffed protocol, check that each of those uses the worst-case expansion. Also notice what stuffing gives for free: a protocol violation (a missing stuffed bit) is detectable at the bit level, long before any CRC, which some protocols use for fast error signalling.

    Interview

    Asked: "Why does USB insert a zero after six ones?"

    Weak answer: To separate the bytes.

    Strong answer: Two reasons. USB uses NRZI, where a 1 is "no transition", so a long run of 1s would leave the receiver's clock recovery with no edges; a stuffed 0 forces a transition at least every seven bits. And it keeps special patterns, like the sync and end markers, from appearing inside data. The receiver removes the 0 after six 1s; a seventh 1 is a bit-stuff error. The cost is up to about 17% extra length on all-ones data.

    Follow-ups: (1) Why does CAN stuff after five identical bits of either value? (2) What does stuffing do to frame timing?

    Check yourself What happens at a USB receiver that sees seven 1s in a row?

    It is a bit-stuff error: the transmitter must have inserted a 0 after six. The receiver treats the packet as corrupted.

    Check yourself Why is CAN's worst case 25% and not 20%?

    The stuffed bit has the opposite value, so it can be the first bit of a new run. Data like 0000 1111 0000... combines with each stuffed bit to complete a five-bit run every four data bits.

    Check yourself Why do high-speed links prefer block codes over stuffing?

    Stuffing makes length depend on data, which is hard to pipeline at high rates. Block codes have a fixed overhead and fixed-size blocks.

    Key takeaways

    • Bit stuffing inserts an opposite bit after a run of identical bits; the receiver removes it by the same rule.

    • It bounds run length, keeps flags unique and gives a free error check, at a data-dependent cost.

    • Size every length-dependent buffer and timeout for the worst case: about 17% for USB, 25% for CAN.

    Keep going

    Next: Lesson 2.5, DC Balance and Running Disparity. Transitions are one requirement; balance is another. Why an AC-coupled link cares about the count of ones and zeros.

    Related topics

    • Part 2 overview: Encoding and Framing

    Previous

    29 / 234

    Next: DC Balance and Running Disparity

    Up next

    Up nextDC Balance and Running Disparity

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Line Codes
    • Framing Basics
    • Block Codes