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
    • Memory Models and Protocols
    • Barriers, Fences and Completions
    • Speculation, Replay and Recovery
      • Speculative Loads and Invalidations
      • Load Replay on Snoop
      • LR/SC and Reservations
      • Ordering Bugs in Out-of-Order Cores
  • Performance and Scalability12
  • Real Protocols12
  • SoC Integration12
  • Power, Clocking and Reliability12
  • Verification12
  • Debug and Bring-up12
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹Speculation, Replay and Recovery
  1. Topics
  2. Cache Coherency
  3. Ordering and Synchronization
  4. Speculation, Replay and Recovery
  5. Load Replay on Snoop

Part 6 · Ordering and Synchronization · All levels

Load Replay on Snoop

Trace the load-queue check on MP: Inv arrival, immediate Inv-Ack, the replay decision and the replay.

On this page

    Scope: the MP example from the previous lesson, MSI on the course model, with C0 as the reader and C1 as the writer.

    What happens when the Inv hits the load queue?

    Warm-up: both caches read X earlier and hold S / 0, H has S{C0,C1}, memory = 0. C0's older R Y has missed and waits for its own GetS(Y); the transactions for Y are not traced. C0's younger R X hits and performs with 0, and its load-queue entry records X's line.

    C1's store to X is an upgrade from S, the same four legs as the upgrade trace (roles reversed from that lesson: C1 upgrades, C0 is the sharer). The only new part is at C0: the Inv matches the load queue, C0 marks R X for replay and sends Inv-Ack straight away. It never delays the Inv-Ack until the replay, because C1's write must complete before C0's R Y can see Y = 1. After R Y performs, the replayed R X misses and becomes the MSI dirty read from the MSI lesson, which is not redrawn here.

    C0 performs the payload load early, C1's upgrade sends Inv to C0, the load queue matches and marks a replay, C0 acknowledges at once, C1 writes, C0's flag load returns one, and the replayed payload load refetches one.
    The Inv-Ack goes out at once. The replay is local work, and the refetch is an ordinary MSI dirty read.

    Step

    C0 state / value

    C1 state / value

    H directory

    Memory

    Newest data

    Warm-up

    S / 0

    S / 0

    S{C0,C1}

    0

    C0, C1 and memory: 0

    L0 R X performs early (load queue: X's line)

    S / 0

    S / 0

    S{C0,C1}

    0

    C0, C1 and memory: 0

    1 GetM(X) at H

    S / 0

    SM^D / 0

    S{C0,C1}, pend GetM C1, acks 0/1

    0

    C0, C1 and memory: 0

    2 Inv(X) at C0, replay marked

    I / -

    SM^D / 0

    S{C0,C1}, pend GetM C1, acks 0/1

    0

    C1 and memory: 0

    3 Inv-Ack at H

    I / -

    SM^D / 0

    S{C1}, pend GetM C1, acks 1/1

    0

    C1 and memory: 0

    4 Data(M permission, no refill) at C1

    I / -

    M / 0

    S{C1}, pend GetM C1, acks 1/1

    0

    C1 and memory: 0

    (assumed) H learns C1 delivery

    I / -

    M / 0

    exclusive(C1)

    0

    C1 and memory: 0

    L1 C1 stores X=1

    I / -

    M / 1

    exclusive(C1)

    0

    C1: 1

    L2 C0's R Y returns 1

    I / -

    M / 1

    exclusive(C1)

    0

    C1: 1

    5-6 replay GetS(X): MSI dirty read as in the MSI lesson with roles reversed

    S / 1

    S / 1

    S{C0,C1}

    1

    C0, C1 and memory: 1

    L3 replayed R X commits 1

    S / 1

    S / 1

    S{C0,C1}

    1

    C0, C1 and memory: 1

    The committed pair is (1, 1). Had R Y returned 0 instead, the replay would still have been taken (the decision is made when the Inv arrives) and the result would be (0, 0) or (0, 1), both allowed. The load-queue entry and the cache state are separate records: the line went to I at step 2 whatever the replay decision.

    What is compared

    Event

    Compared against

    Replay?

    Inv or Fwd-GetM for a line

    Line address of every performed, uncommitted load

    Yes, if an older load is still unperformed

    C0's own eviction of a line

    Line address

    Yes, if an older load is still unperformed: C0 is no longer a sharer and will see no later Inv for it

    Fwd-GetS

    Nothing

    No: no read permission is removed, so no other core can write yet

    Write to another word in the same line

    Line address (the Inv covers the whole line)

    Yes, a false replay: correct but wasted work

    The load at the head of the reorder buffer has no older unperformed load, so an invalidation never squashes it. It always commits, and replays cannot livelock that core.

    Check it by enumeration

    The model below enumerates every interleaving of C1's two stores with C0's flag load, its payload load (early or in order) and at most one eviction of X. It compares the committed outcomes with SC's, with and without the load-queue check.

    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
    # MP, roles as in this lesson: C1 writes X=1 then Y=1, C0 reads flag Y then payload X.
    # Outcomes are (flag, payload) = (r1, r2). C0 starts with X's line in S / 0.
    def sc_outcomes():
        found = set()
        def walk(w, r, mem, regs):
            if w == 2 and r == 2:
                found.add(regs)
                return
            if w < 2:
                walk(w + 1, r, mem[:w] + (1,) + mem[w + 1:], regs)
            if r < 2:
                loc = 1 - r                   # first Y (index 1), then X (index 0)
                walk(w, r + 1, mem, regs[:r] + (mem[loc],) + regs[r + 1:])
        walk(0, 0, (0, 0), (0, 0))
        return found
    
    def ooo_outcomes(snoop_inv, snoop_evict):
        found, seen = set(), set()
        todo = [(0, 0, 0, True, None, None, False)]  # wpc, X, Y, C0 holds X, flag, payload, evicted
        while todo:
            s = todo.pop()
            if s in seen:
                continue
            seen.add(s)
            wpc, x, y, held, flag, pay, ev = s
            def squash(p):                   # replay rule: older flag load still unperformed
                return None if (p is not None and flag is None) else p
            if wpc == 2 and flag is not None and pay is not None:
                found.add((flag, pay))
                continue
            if wpc == 0:                     # C1's GetM(X): Inv reaches C0 only if C0 holds X
                todo.append((1, 1, y, False, flag, squash(pay) if (held and snoop_inv) else pay, ev))
            if wpc == 1:
                todo.append((2, x, 1, held, flag, pay, ev))
            if flag is None:
                todo.append((wpc, x, y, held, y, pay, ev))
            if pay is None:                  # early (before flag) or in-order payload load
                todo.append((wpc, x, y, True, flag, x, ev))
            if held and not ev:              # C0 evicts X once (PutS, so H drops C0)
                todo.append((wpc, x, y, False, flag, squash(pay) if snoop_evict else pay, True))
        return found
    
    SC = sc_outcomes()
    assert SC == {(0, 0), (0, 1), (1, 1)}
    assert ooo_outcomes(snoop_inv=True, snoop_evict=True) == SC
    assert (1, 0) in ooo_outcomes(snoop_inv=False, snoop_evict=False)
    assert (1, 0) in ooo_outcomes(snoop_inv=True, snoop_evict=False)
    print("PASS: snooping Inv and own evictions keeps MP at SC's outcomes, missing either allows (1, 0)")

    The third assertion is the missing check. The fourth is the eviction hole: C0 performs R X early, evicts X, C1's GetM sends it no Inv, and (1, 0) commits.

    Common pitfall

    Holding back the Inv-Ack until the replayed load completes. C1's store to Y waits behind its store to X, which waits for this Inv-Ack, and C0's R Y may be waiting for that Y: a deadlock. The Inv-Ack never depends on the replay. (The bounded deferral of the atomics lesson differs: nothing inside its window waits on another line.)

    Interview

    Say what is discarded (the tentative result and younger work) and what is not (the Inv-Ack, the cache state change and any data responsibility). Then name the event list in the comparison table.

    Check yourself Did the Inv prove C0's early 0 was illegal?

    No. The check is conservative: it squashes because an older load was still unperformed, not because 0 was proven wrong. If R Y had returned 0, keeping 0 would have been legal, but the replay is still correct.

    Related topics

    • Shared course model

    • The upgrade trace (roles reversed)

    • The MSI dirty read the replay uses

    Previous

    71 / 167

    Next: LR/SC and Reservations

    Up next

    Up nextLR/SC and Reservations

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Ordering Bugs in Out-of-Order Cores