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
      • Flushing Before Power-Down
      • Leaving and Rejoining the Domain
      • Retention States
      • A Power-Down Race
    • Clocking and CDC on Coherence Channels
    • ECC, Poison and Recovery
  • Verification12
  • Debug and Bring-up12
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹Power State Transitions and Cache State Safety
  1. Topics
  2. Cache Coherency
  3. Power, Clocking and Reliability
  4. Power State Transitions and Cache State Safety
  5. Retention States

Part 10 · Power, Clocking and Reliability · All levels

Retention States

Retained bits are not a coherent copy: either the cache wakes to answer, or it leaves the domain first.

On this page

    If the array keeps its bits, why not stay in the domain?

    Retention lowers the array supply far enough to keep each bit but not to read or write it, and switches off the controller logic around it. The contents survive; the ability to look up a tag and answer an Inv or Fwd does not. Coherence needs answers, not bits. So a retained cache has exactly two safe choices: leave the domain first (flush and Leave, as in the previous lessons), or stay in it and wake up whenever a snoop arrives.

    Course-model extension for this topic: C0's cache can be powered down. A powered-off or retained controller answers nothing, not even 'I do not have it'. The topic adds four course-defined messages, Leave, Leave-Ack, Join and Join-Ack, for leaving and rejoining the coherence domain; they are not messages of any named protocol. Everything else follows the course model.

    Wake-on-snoop: the hardware

    An always-on snoop port stays powered. It accepts an Inv or Fwd into a reserved slot, raises a wake request to the power controller, and back-pressures further forwards (it never answers 'absent'). When the array and controller are back, the held message is processed normally and the cache returns to retention after an idle timer. Two rules make it safe: the held snoop sits in the forward class and its response in the response class, so it cannot queue behind core requests; and the wake completes in bounded time, which is now part of every requester's progress assumption.

    Worked case: a forward reaches a retained M line

    MSI. Warm-up: C0 acquired M on X and stored 1, then entered retention with wake-on-snoop and no flush. C1 sends GetS. Once C0 is awake, the remaining steps are the MSI dirty read of MSI Protocol: Data to H, H writes memory, Data to C1. Only the new part is shown.

    C1 requests X, H forwards to the retained C0, whose always-on port holds the forward and wakes the cache, which then answers with Data one, and H writes one to memory as in a normal dirty read.
    Wake-on-snoop: the forward is held at the always-on port until the cache can answer it.

    Step

    C0 state / value

    C1 state / value

    H directory

    Memory

    Newest data

    0. In retention

    M / 1 (retained)

    I / -

    exclusive(C0)

    0

    C0: 1

    1. GetS at H

    M / 1 (retained)

    IS^D / -

    exclusive(C0), pend GetS C1

    0

    C0: 1

    2. Fwd-GetS held at C0's port

    M / 1 (retained, waking)

    IS^D / -

    exclusive(C0), pend GetS C1

    0

    C0: 1

    Wake complete, Fwd-GetS processed

    S / 1

    IS^D / -

    exclusive(C0), pend GetS C1

    0

    C0: 1, Data(1) in flight to H

    3. Data at H

    S / 1

    IS^D / -

    exclusive(C0), pend GetS C1

    0

    C0 and H buffer: 1

    H writes memory

    S / 1

    IS^D / -

    S{C0}, pend GetS C1

    1

    C0, H buffer and memory: 1

    C1's miss is longer by the whole wake time: retention moves a cost from C0's leakage to C1's latency (compare Anatomy of a Miss Latency). The wake rate depends on how precise H's records are: with a full directory only lines C0 really holds wake it; with broadcast every snoop does.

    Three options

    Option

    Entry

    While idle

    Exit

    Snoops while idle

    Full power-off

    Flush and Leave

    Leakage near zero

    Power-up, Join, cold misses

    None: C0 is not in the domain

    Retention with flush and Leave

    Flush and Leave

    Retention leakage

    Faster wake, Join, but every line is I, so the same cold misses

    None

    Retention with wake-on-snoop

    Nothing to flush

    Retention leakage plus the always-on port

    Fast wake, cache still warm

    Each one wakes C0 and delays its requester

    Retention with flush keeps bits that are not coherent: after Leave they must be treated as I, so it saves only state outside coherence (configuration, replacement history) and a faster power-up. Its niche is short idle periods with a busy snoop stream. The numbers below are illustrative, not measurements of any product.

    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
    # Illustrative numbers only: energy in microjoules, idle time T in ms, snoop rate r per ms.
    FLUSH, REFILL = 2.0, 2.0          # write back dirty lines, refetch the working set
    WAKE_OFF, WAKE_RET = 1.0, 0.3     # power-up from off, wake from retention
    P_RET = 2.0                       # retention leakage, uJ per ms (2 mW)
    SNOOP_WAKE = 0.3                  # wake, answer one snoop, return to retention
    
    def energy(option, T, r, count_snoops=True):
        if option == "off":
            return FLUSH + WAKE_OFF + REFILL
        if option == "retention, flush and leave":
            return FLUSH + P_RET * T + WAKE_RET + REFILL
        wakes = r * T if count_snoops else 0
        return P_RET * T + WAKE_RET + wakes * SNOOP_WAKE      # retention, wake on snoop
    
    OPTIONS = ["off", "retention, flush and leave", "retention, wake on snoop"]
    def best(T, r, count_snoops=True):
        return min(OPTIONS, key=lambda o: energy(o, T, r, count_snoops))
    
    # Each option wins somewhere.
    assert best(0.5, 1) == "retention, wake on snoop"     # short idle, few snoops
    assert best(10, 1) == "off"                           # long idle
    assert best(0.2, 100) == "retention, flush and leave" # short idle, many snoops
    # General claim: beyond the break-even time, power-off wins at any snoop rate.
    t_even = (FLUSH + WAKE_OFF + REFILL - WAKE_RET) / P_RET
    for T in [t_even * k for k in (1.01, 2, 5, 50)]:
        for r in [0, 1, 10, 1000]:
            assert best(T, r) == "off", (T, r)
    # Broken accounting: ignoring snoop wake-ups picks wake-on-snoop for a busy line.
    naive = best(0.2, 100, count_snoops=False)
    assert naive == "retention, wake on snoop"
    assert energy(naive, 0.2, 100) > energy(best(0.2, 100), 0.2, 100)
    print("PASS: break-even %.2f ms, and ignoring snoop wake-ups picks the wrong state" % t_even)

    With these illustrative values, power-off wins for any idle period longer than about 2.35 ms whatever the snoop rate, wake-on-snoop wins for short quiet periods, and retention with flush wins only for idle periods under about 0.35 ms that would see more than about 13 snoops (r x T > 13.3, at T = 0.2 ms more than 67 per ms). The broken accounting, which ignores the cost of each wake, picks wake-on-snoop for a busy line and spends 6.7 µJ instead of 4.7 µJ. A real choice also weighs C1's added latency, which this energy model leaves out.

    Common pitfall

    Treating a retained M line as safe because its bits survive. If the cache cannot wake to answer, H's next Fwd-GetM hangs, and if the retention supply is lost the dirty value goes with it.

    Interview

    Separate 'bits kept' from 'requests answered'. Then compare the three options by entry cost, idle leakage, exit cost and the latency each imposes on other cores.

    Check yourself A retained cache was flushed and has left the domain. On wake, may it reuse its retained S lines without a miss?

    No. After Leave-Ack H no longer tracks or invalidates C0, so a line C0 kept may have been written by C1 since. The retained bits must be treated as I and refetched; only state outside coherence can be reused.

    Related topics

    • Shared course model

    • Leave and rejoin sequence

    • The MSI dirty read

    Previous

    112 / 167

    Next: A Power-Down Race

    Up next

    Up nextA Power-Down Race

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Flushing Before Power-Down