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 Path
  • Cache Coherency Glossary
  • The Shared Course Model
  • Cache Coherency Revision Sheet
  • Why Coherence Exists8
  • States and Protocols12
  • Transactions12
  • Races and Controller Design8
  • Progress and the Fabric16
  • Ordering and Synchronization12
  • Performance and Scalability12
  • Real Protocols12
  • SoC Integration12
  • Power, Clocking and Reliability12
    • Power State Transitions and Cache State Safety
    • Clocking and CDC on Coherence Channels
      • Coherence Channels Across Clocks
      • Credits Across Clock Domains
      • Quiesce and Link Reset
      • CDC Bugs in Coherence
    • ECC, Poison and Recovery
  • Verification12
  • Debug and Bring-up12
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹Clocking and CDC on Coherence Channels
  1. Topics
  2. Cache Coherency
  3. Power, Clocking and Reliability
  4. Clocking and CDC on Coherence Channels
  5. CDC Bugs in Coherence

Part 10 · Power, Clocking and Reliability · All levels

CDC Bugs in Coherence

Torn messages, unsynchronized credit counters, stale credits after a one-sided reset and merged classes: symptom and the check that finds each.

On this page

    Which crossing bugs look like protocol bugs?

    A crossing bug rarely looks like a CDC problem from the outside. It shows up as a coherence symptom: a message that should not exist, a hang, a lost value. Four bug classes cover most cases. The examples are illustrative, not reports of real designs.

    Bug class

    What is wrong

    Symptom on the course model

    Check that finds it

    Torn message

    Message fields (type, address, transaction id) cross as separately synchronized bits instead of through a FIFO slot.

    H receives a mix of two messages, for example Inv-Ack type with the id of a different transaction: an ack for a transaction with no pending entry, or a GetM for the wrong address.

    CDC structural check: a multi-bit crossing with no recognised scheme. Protocol checker: response with no matching pending entry.

    Credit counter without synchronization

    A binary credit count crosses directly.

    Count 3 to 4 is 011 to 100. Sampled mid-change, the sender may see 111 = 7 credits, sends too many, and the receiver overwrites a slot: a lost message.

    CDC structural check on the counter. Credit-conservation assertion. Overflow assertion on the receiver buffer.

    Reset domain crossing with stale credits

    One side of the link is reset, the other keeps its credit count.

    Receiver reset: buffered requests vanish, the sender's credits stay low, MSHRs wait forever. Sender reset: credits jump to full while slots are still occupied, so the next burst overflows.

    RDC structural check. Credit-conservation assertion. Reset-aware simulation that resets one domain mid-traffic.

    Class merged at the crossing

    Two classes share one in-order FIFO.

    The P/Q deadlock of Part 5, inside the crossing.

    Not a CDC check: review the class-to-FIFO map. Protocol-level deadlock search on a small model, or a stress test with a progress check.

    The last row matters most for this course: CDC and RDC tools check bits and resets, not message classes. Only a protocol view finds a merged class.

    Decision tree: impossible field combinations point to a torn message, buffer overflow after a reset to stale credits, overflow without reset to an unsynchronized counter, and a response stuck behind a request in a shared FIFO to a merged class.
    Illustrative triage from a coherence symptom to a crossing bug class.

    The credit-conservation assertion

    One invariant catches both credit bugs: for every crossing, the credits the sender holds, plus messages in the FIFO, plus credits on their way back, equals the depth. It is checked in simulation from a testbench view that sees both domains at once; it is not synthesizable logic across the crossing.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    // Simulation-only check: the testbench samples counts from both clock domains.
    property p_credit_conservation;
      @(posedge tb_clk) disable iff (!link_up)
        (tx_credits + fifo_occupancy + credits_returning) == DEPTH;
    endproperty
    a_credit_conservation: assert property (p_credit_conservation);
    a_no_overflow: assert property (@(posedge tb_clk) disable iff (!link_up)
      fifo_occupancy <= DEPTH);

    Worked case: the cluster-to-H request crossing has depth 4. C0 has sent GetS(X) and GetS(Y), both still in the FIFO, so C0 holds 2 credits: 2 + 2 + 0 = 4. Now the H-side link logic alone takes a warm reset. The FIFO is cleared, and C0's two MSHRs (IS^D for X and Y) wait for Data that will never come. C0 still holds 2 credits: 2 + 0 + 0 = 2, and the assertion fires at the reset, long before any hang timeout. In the opposite bug, the cluster side alone resets: C0 gets 4 credits while 2 slots are still full, 4 + 2 + 0 = 6, and the next burst of four overflows the FIFO.

    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
    import random
    DEPTH = 4
    def new_link():
        return {"credits": DEPTH, "fifo": [], "returning": 0}
    def total(l):
        return l["credits"] + len(l["fifo"]) + l["returning"]
    def conserved(l):
        return total(l) == DEPTH
    def send(l, msg):
        if l["credits"] == 0:
            return False
        l["credits"] -= 1
        l["fifo"].append(msg)
        return True
    def receive(l):
        l["fifo"].pop(0)
        l["returning"] += 1
    def credit_arrives(l):
        l["returning"] -= 1
        l["credits"] += 1
    def traffic_step(l, rng):
        r = rng.random()
        if r < 0.4:
            send(l, "GetS")
        elif r < 0.7 and l["fifo"]:
            receive(l)
        elif l["returning"]:
            credit_arrives(l)
    def reset_receiver(l):
        l["fifo"], l["returning"] = [], 0      # FIFO and credits on the way back are cleared
    def reset_sender(l):
        l["credits"] = DEPTH                   # sender believes every slot is free
    def reset_both(l):
        reset_receiver(l)
        reset_sender(l)
    # correct traffic alone keeps the invariant
    rng = random.Random(7)
    l = new_link()
    for _ in range(2000):
        traffic_step(l, rng)
        assert conserved(l) and len(l["fifo"]) <= DEPTH
    # each reset at a random point in seeded traffic, then traffic continues
    seen = {"empty": 0, "busy": 0, "overflow": 0, "lost_for_good": 0}
    for reset in (reset_receiver, reset_sender, reset_both):
        for run in range(300):
            rng = random.Random(1000 * len(reset.__name__) + run)
            l = new_link()
            for _ in range(rng.randrange(1, 40)):
                traffic_step(l, rng)
            idle = not l["fifo"] and l["returning"] == 0
            seen["empty" if idle else "busy"] += 1
            reset(l)
            after = total(l)
            if reset is reset_both:
                assert conserved(l)
            else:
                assert conserved(l) == idle    # one-sided reset is safe if and only if the link was idle
            overflow = False
            for _ in range(200):
                traffic_step(l, rng)
                assert total(l) == after       # traffic never repairs a broken count
                overflow = overflow or len(l["fifo"]) > DEPTH
            if reset is reset_both:
                assert conserved(l) and not overflow
            if reset is reset_sender and overflow:
                seen["overflow"] += 1
            if reset is reset_receiver and total(l) < DEPTH:
                seen["lost_for_good"] += 1
    assert seen["empty"] and seen["busy"]
    assert seen["overflow"] > 0 and seen["lost_for_good"] > 0
    print("PASS: credit conservation survives reset_both, a one-sided reset is safe only on an idle link,",
          "sender resets overflowed", seen["overflow"], "runs and receiver resets lost credits for good in", seen["lost_for_good"])

    The check first runs 2,000 seeded steps of correct traffic with the invariant holding throughout. It then applies each reset (receiver only, sender only, both) at a random point in 300 seeded runs and keeps the traffic going. Resetting both sides always restores the invariant. A one-sided reset keeps it if and only if the FIFO was empty and no credit was returning, and traffic never repairs a broken count: sender-only resets let more messages into the FIFO than it has slots, and receiver-only resets leave credits lost for good, the MSHR hang of the worked case.

    Common pitfall

    Waiving a CDC or RDC finding on a coherence channel because the protocol has timeouts. A timeout turns a lost request into a hang report at best, and a lost Data into stale data at worst.

    Interview

    For each bug class give the mechanism, the coherence symptom and the check: CDC structural for torn messages and raw counters, RDC structural and credit conservation for one-sided resets, a class-to-FIFO review and a progress check for merged classes.

    Check yourself All CDC and RDC structural checks are clean. Which of the four bug classes can still be present?

    The merged class. Its bits cross through a correct FIFO and its resets are fine, so CDC and RDC tools have nothing to report. Only a review of which classes share which FIFO, or a protocol-level deadlock search, finds it.

    Related topics

    • Shared course model

    • Safe link reset

    • CDC course: bus tearing

    • CDC course: RDC structural checks

    • CDC course: FIFO pointer bug

    Previous

    117 / 167

    Next: ECC, Poison and Recovery

    Up next

    Up nextECC, Poison and Recovery

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Coherence Channels Across Clocks
    • Credits Across Clock Domains