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
    • ECC, Poison and Recovery
      • ECC on Tags, Data and Directory
      • Poison Propagation
      • Losing Directory State
      • Recovery Limits
  • Verification12
  • Debug and Bring-up12
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹ECC, Poison and Recovery
  1. Topics
  2. Cache Coherency
  3. Power, Clocking and Reliability
  4. ECC, Poison and Recovery
  5. Losing Directory State

Part 10 · Power, Clocking and Reliability · All levels

Losing Directory State

Rebuild an untrusted directory entry by recalling every possible holder, count what it costs, and recognise when the entry cannot be rebuilt.

On this page

    What can H do when it no longer knows who holds X?

    H reads X's directory entry and the ECC reports an uncorrectable error, or the entry was lost with a failed array bank. H must not guess. Treating the entry as U lets H serve the next reader from memory, which may be stale; treating it as the last value it remembers can skip a holder. The safe reading is unknown: any cache may hold X in any state, including M.

    That is the conservative rule already used for capacity. A Dir_i B entry in overflow means 'every cache may hold X' and its next GetM invalidates all of them (Limited Pointers and Coarse Vectors); a filter that cannot answer must use a conservative fallback (Snoop Filter Correctness). Rule: unknown means broadcast. Here the broadcast also has to recover a dirty value, as in Filter Eviction and Back-Invalidation, where H used Fwd-GetM on its own behalf.

    The recovery recall

    Model extension for this lesson. When an entry is unknown, H freezes new requests for X behind the pending entry and sends Fwd-GetM on its own behalf, called a recall, to every cache except the requester (which sent GetS, so it holds nothing). A cache holding X in M answers Data with its value and goes to I. A cache in S or I answers Inv-Ack and ends in I. When every target has answered, H knows exactly: no cache holds X, and memory is current once any recalled dirty data has been written. The entry is rebuilt as U, and the waiting request continues as an ordinary one. The table writes the untrusted entry as unknown, and recall k/2 counts the answers (Data or Inv-Ack) H has received out of the recalls it sent.

    To make the cost visible, extend the course model to three caches C0, C1, C2 for this lesson. MSI. Warm-up from X = 0: C0 acquired M and stored 1, so C0 M / 1, C1 and C2 I / -, H exclusive(C0), memory = 0. C1 sends GetS. Reading the entry for it, H's ECC reports an uncorrectable error.

    C1 sends GetS. H finds X's entry uncorrectable, sends recall Fwd-GetM to C0 and C2, gets an Inv-Ack from C2 and Data one from C0, writes one to memory and grants C1 S with Data one.
    Recovery recall: H cannot trust X's entry, recalls every cache except the requester, and resumes the GetS once all have answered.

    Step

    C0 state / value

    C1 state / value

    C2 state / value

    H directory

    Memory

    Newest data

    0 warm-up

    M / 1

    I / -

    I / -

    exclusive(C0)

    0

    C0: 1

    1. GetS from C1 at H, entry fails ECC

    M / 1

    IS^D / -

    I / -

    unknown, pend GetS C1, recall 0/2

    0

    C0: 1

    2. Recall at C0

    I / -

    IS^D / -

    I / -

    unknown, pend GetS C1, recall 0/2

    0

    Data(1) in flight to H

    3. Recall at C2, already I

    I / -

    IS^D / -

    I / -

    unknown, pend GetS C1, recall 0/2

    0

    Data(1) in flight to H

    4. Inv-Ack from C2 at H (wasted)

    I / -

    IS^D / -

    I / -

    unknown, pend GetS C1, recall 1/2

    0

    Data(1) in flight to H

    5. Data(1) from C0 at H

    I / -

    IS^D / -

    I / -

    unknown, pend GetS C1, recall 2/2

    0

    H buffer: 1

    H writes memory, entry rebuilt as U

    I / -

    IS^D / -

    I / -

    U, pend GetS C1

    1

    memory: 1, Data(1) in flight to C1

    6. Data(1) at C1

    I / -

    S / 1

    I / -

    U, pend GetS C1

    1

    C1 and memory: 1

    (assumed) grant delivered

    I / -

    S / 1

    I / -

    S{C1}

    1

    C1 and memory: 1

    C1 gets 1, the correct value. Had H treated the entry as U, it would have read memory and returned 0 while C0 still held M / 1: a stale read and, after C1 got S, a writer beside a reader. Messages 3 and 4 are the price of not knowing: C2 never held X.

    What recovery costs

    System (illustrative)

    Normal GetS to an M line

    Recovery recall for one entry

    3 caches, as above

    1 Fwd-GetS, 1 Data back to H

    2 recalls, 2 answers, then the normal grant

    16 caches

    1 forward, 1 answer

    15 recalls, 15 answers

    64 caches

    1 forward, 1 answer

    63 recalls, 63 answers, latency of the slowest answer

    Every copy is revoked too, so readers that had S miss again afterwards. A design can add a non-destructive probe that reports a cache's state and keeps its copy, so H can rebuild the sharer list instead of emptying it; that needs a message type the course vocabulary does not have. If a whole directory bank is lost, every line it tracked becomes unknown and recovery amounts to recalling that whole address range from every cache. Recovery is a rare slow path: it only has to be correct, not fast.

    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
    import random
    def truth(holders):
        dirty = [v for s, v in holders.values() if s == "M"]
        return dirty[0] if dirty else 0  # MSI: memory is current unless a cache holds M
    def recover(holders, requester, n, strategy, responsive=True):
        """Serve a GetS after the entry for X is lost. Returns (result, probes)."""
        if strategy == "assume_U":
            return ("data", 0), 0  # read memory, which holds 0
        mem, probes = 0, 0
        for c in range(n):
            if c == requester:
                continue
            probes += 1
            state, value = holders.get(c, ("I", None))
            if state == "M":
                if not responsive:
                    return ("poison", None), probes  # the only newest copy will not answer
                mem = value  # Data back to H, H writes memory
            holders[c] = ("I", None)
        return ("data", mem), probes
    rng = random.Random(10)
    stale_seen = 0
    for trial in range(2000):
        n = rng.choice([3, 8, 64])
        requester = rng.randrange(n)
        others = [c for c in range(n) if c != requester]
        if rng.random() < 0.5:
            holders = {rng.choice(others): ("M", rng.randrange(1, 100))}
        else:
            holders = {c: ("S", 0) for c in rng.sample(others, rng.randrange(0, len(others) + 1))}
        want = truth(holders)
        naive, _ = recover(dict(holders), requester, n, "assume_U")
        stale_seen += naive != ("data", want)
        result, probes = recover(dict(holders), requester, n, "recall")
        assert result == ("data", want) and probes == n - 1
        if want != 0:
            dead, _ = recover(dict(holders), requester, n, "recall", responsive=False)
            assert dead == ("poison", None)  # never stale data
    assert stale_seen > 0  # failing case: treating unknown as U returns stale memory
    print("PASS: recall rebuilds the value with n - 1 probes, assume-U returns stale data")

    The check builds random holder sets with a fixed seed: either one M holder with some value, or any number of clean S holders. Recalling every other cache always returns the true value with n - 1 probes; reading memory as if the entry were U returns stale data whenever a cache held M. If the M holder cannot answer, recall reports poison rather than a value.

    When the entry cannot be rebuilt

    Recall works only if every possible holder answers. Suppose C0 held M / 1 and was powered off without flushing (Flushing Before Power-Down is the rule it broke), or C0's own tag or state for X also failed. The only record of the dirty copy was H's entry, and the only copy will not answer. Memory holds 0, which is wrong. H must not grant 0 as good data.

    Two things are needed. First, H must notice: without a time-out on the recall, H's pending entry waits forever, and the progress argument of Stating Progress Assumptions does not cover a component that never answers. Second, H must contain the loss: if it still knows the address, it writes poison for X's line to memory and grants C1 Data(poisoned), so the error surfaces only at a consumer. If even the address is unknown, for example because the entry's own tag failed, H cannot tell which line to poison, and the error has to be escalated, as the next lesson classifies.

    Common pitfall

    Treating an unknown directory entry as U because 'most lines are not cached'. One M holder is enough to return stale data, and nothing downstream would notice.

    Common pitfall

    Recalling every holder but releasing the entry before the last answer. Until every target has answered, a dirty copy may still be in flight to H.

    Interview

    State the rule (unknown means every cache may hold X, in any state), the mechanism (freeze X, recall all other caches with a data-carrying probe, rebuild as U), the cost (n - 1 probes and answers, all copies lost) and the limit (a dirty holder that cannot answer).

    Check yourself In the trace, why does H send a recall to C2, which never held X?

    Because H's entry is unknown, not known-empty. C2 could have held X in any state, including M. Only C2's answer proves it holds nothing. That one wasted round trip is the cost of an untrusted entry.

    Check yourself Could H rebuild the entry as S{C0} instead of revoking C0's copy?

    Not with Fwd-GetM, which revokes. With a non-destructive state probe, H could learn that C0 holds M, keep it there and forward the GetS normally. That needs a new message and a careful race analysis, so the course recovers by revoking everything and rebuilding from U.

    Related topics

    • Shared course model

    • Overflow: unknown means broadcast

    • Conservative fallback

    • Recalling dirty data on H's behalf

    • Next: recovery limits

    Previous

    120 / 167

    Next: Recovery Limits

    Up next

    Up nextRecovery Limits

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • ECC on Tags, Data and Directory