Skip to content

AMBA CHI · Module 1 · Cache Coherency Foundations

The Read Visibility Problem

A load looks trivial: send an address, receive data. In a multi-core SoC the real question is where the latest value lives. Under write-back caching the newest data for a line is often a dirty copy in another core's private cache, not in memory, so a correct read cannot just go to memory — it must find the current owner and pull the value from there. This chapter shows the exact scenario where reading memory returns stale data while a peer core holds the truth, and foreshadows the two mechanisms CHI uses to fix it: the snoop that queries every cache and the cache-to-cache transfer that forwards a dirty line to the reader.

Foundation11 min readAMBA CHICache CoherencyRead VisibilityCache-to-CacheSnoop

Module 1 · Chapter 1.7 · Cache Coherency Foundations

Project thread — same running example: two CPUs (CPU0, CPU1), each with a private L1, sharing one memory. Chapter 1.6 asked how a write becomes visible; this chapter asks the mirror question — how a read finds it.

1. Learning Outcomes

By the end of this lesson you will be able to:

  • Explain read visibility as a per-address guarantee: a load must return the most recent write to a line, wherever that value currently lives.
  • Explain why, under write-back caching, memory is stale until a dirty line is evicted — so the newest value can live only in a peer core's cache.
  • Trace the scenario where a naive read of memory returns stale data while a peer core holds the true, dirty copy.
  • Name the two mechanisms that resolve a coherent read — the snoop (find the owner) and the cache-to-cache transfer (forward the owner's data) — and map them to CHI's snoop channel and Home Node.
  • Distinguish read visibility from write visibility (1.6) and from ordering / consistency (cross-address, Module 12).

2. Why Should I Learn This?

Every load in a coherent system carries a hidden question: of all the copies of this line, which one holds the latest value, and how do I get it? Under write-back caching the answer is often a dirty line in another core's cache — not memory. Getting the read to that copy is the read-visibility problem, and it is exactly why CHI has a snoop channel and cache-to-cache transfers.

In one production SoC a producer/consumer handoff failed intermittently: the consumer core loaded stale, pre-production bytes because the producer's freshly written data was still a dirty line in its private L1 while the consumer's read was answered by stale memory. The fix was not to flush on every store but to snoop the peer and forward its dirty line cache-to-cache — precisely what AMBA CHI provides at scale.

3. Core Concept — a read must locate the owner

Start from the running project: CPU0 and CPU1, each with a private L1, over one shared memory. Chapter 1.6 established how a write propagates outward. Read visibility is the mirror: when a core loads a line, where is the newest value, and how does the read reach it?

Why memory is not the answer.

  • Under write-back caching, a store updates the cache line and marks it dirty; memory is updated only later, on eviction or an explicit writeback.
  • So immediately after CPU0 stores line A, the newest value of A lives only in CPU0's L1. Memory still holds the old value.
  • A naive load by CPU1 that reads memory therefore returns stale data — memory is simply not current yet.

Where the read must go.

  • The latest value is held by whichever cache owns the dirty line — here, CPU0.
  • To be correct, CPU1's read must be routed to that owner, not to memory.
  • The owner then supplies the data directly to the reader — a cache-to-cache (direct data) transfer — and memory is refreshed as a side effect.

Two mechanisms this foreshadows.

  • The snoop — the read is sent as a question to the other caches: do you hold this line, and is your copy dirty? In CHI this is the snoop channel, arbitrated by a Home Node that knows who may hold copies (Module 4).
  • The cache-to-cache transfer — an owner that answers yes, dirty forwards its line as the read data, so the reader gets the true latest value without waiting on memory.

Read visibility is per-address: for one address, does a read return the most recent write no matter which cache holds it? That is distinct from write visibility (1.6) and from ordering / consistency — how accesses to different addresses are ordered across cores (Module 12).

4. Engineering Diagram

CPU1 loads line A via a coherence point which snoops CPU0; CPU0 holds A dirty and forwards the data cache-to-cache to CPU1 while memory is bypassedRead resolved by snoop and cache-to-cache forwardingCPU1 + L1 (reader)Coherence point(future Home Node)CPU0 + L1 (owner)Shared MemoryLoad A (needs latestvalue)Snoop A: do you ownit?Yes - dirty hit,here is the lineForward A = NEW(cache-to-cache)refresh memory (sideeffect)
Figure 1 — a read resolved by snoop and cache-to-cache transfer. CPU1 issues a Load for line A to the coherence point (the future Home Node), which knows CPU0 may hold a copy. It sends a snoop to CPU0. CPU0 holds A dirty, so it responds with the data — the line is forwarded cache-to-cache to CPU1. Memory is only refreshed as a side effect (dashed). The read finds the latest value in a peer cache, not in memory.

The read never had a fixed destination — the coherence point had to find the owner. That snoop-and-forward is the whole of read visibility, and it is why CHI dedicates a snoop channel and a direct data response to the job.

5. Worked Example — a read routed to the owner, not memory

Trace, at the value level, why memory is the wrong place to read and how routing to the owner fixes it. No state machine — just where the latest value lives:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Two private write-back caches over one shared memory. Initial: A = 5 everywhere.
# CPU0 stores A=6 into its OWN L1 and marks it DIRTY (memory NOT yet updated).
 
Step  Actor / action            L1(CPU0)     L1(CPU1)   Memory   Where is the latest A?
----  ------------------------  -----------  ---------  -------  ----------------------------
  1   CPU0 store A=6 (own L1)    A=6 dirty    -          A=5      only in CPU0's cache
  2   CPU1 load A (naive->mem)   A=6 dirty    A=5        A=5      READ MEMORY -> gets 5  <-- BUG
  ---------------------------------------------------------------------------------------------
  2'  CPU1 load A (snoop peer)   A=6 dirty    -          A=5      snoop asks: who owns A?
  3'  CPU0 answers: dirty hit    A=6 dirty    -          A=5      owner found = CPU0
  4'  forward A=6 to CPU1        A=6 (shared) A=6        A=6*     cache-to-cache; mem refreshed*

Path 2 reads memory and returns the stale 5 — memory was never updated after CPU0's dirty store. Path 2'..4' snoops CPU0, discovers it owns the dirty line, and forwards A=6 directly to CPU1 (memory may be refreshed as a side effect, marked with an asterisk). Read visibility is the guarantee that a load returns the value from path 2'..4', never path 2.

6. DebugLab

1

Stale read of data still dirty in a peer

READ ANSWERED BY STALE MEMORY -> MUST SNOOP AND FORWARD FROM OWNER
Symptom

A producer/consumer handoff intermittently fails: CPU1 loads a data block CPU0 just produced and reads stale, pre-production bytes. It never fails in single-core replay, and it never fails when CPU0 happens to have evicted its lines to memory before CPU1 reads. It fails only when CPU0's freshly written lines are still resident in its L1.

Root Cause

Under write-back caching the newest value lives only in the producer's dirty cache line, but the consumer's read is answered by memory, which is still stale. CPU0's stores updated its own L1 and marked the lines dirty; memory was not updated. CPU1's load went to memory — the only place a non-coherent read knows to look — and returned the old block. This is a read-visibility gap: the read had no way to discover that the latest value was owned by a peer cache. It is invisible single-core because there is only one copy, and invisible after eviction because eviction writes the dirty line back, masking the defect.

Fix

Route the read to the current owner: snoop every cache, and on a dirty hit forward the line directly to the reader (cache-to-cache transfer) instead of reading memory. When CPU1 loads A, the fabric asks the other caches whether any holds the line dirty; CPU0 answers yes and supplies the data, which is returned to CPU1 and used to refresh memory as a side effect. That snoop-and-forward is exactly what AMBA CHI implements — a Home Node that knows who may hold copies, a snoop channel that queries them, and a direct data response that forwards an owner's line to the reader (Module 4). The wrong fix — flush or writeback on every store — is correct but destroys the performance caches exist to provide.

7. Common Mistakes

  • Assuming memory has the latest value. Under write-back caching, a dirty line in a peer cache is newer than memory — reading memory returns stale data.
  • Treating a load as a fixed memory fetch. A coherent read has no single destination; it must locate the owner, which may be another core.
  • Flushing on every store to be safe. Correct but ruinously slow — it throws away the whole point of caching. Snoop-and-forward is the cheap fix.
  • Confusing read visibility with ordering. Read visibility is per-address (find the latest value); ordering / consistency is cross-address (Module 12).
  • Forgetting cache-to-cache is a feature, not just a fix. Forwarding from an on-die peer cache is often faster than DRAM — good protocols exploit it.

8. Interview Questions

9. Engineering Checklist

  • I can state read visibility: a read must return the most recent write to an address, wherever it lives — memory or a peer's dirty cache.
  • I can explain why memory is stale under write-back until a dirty line is written back.
  • I can name the two mechanisms that fix it — snoop (find the owner) and cache-to-cache transfer (forward the owner's data).
  • I can distinguish read visibility (find the latest value) from write visibility (make a write reach copies) and from ordering (cross-address).
  • I can explain why flush-on-every-store is correct but unacceptable, and why cache-to-cache forwarding is often faster than DRAM.

10. Key Takeaways

  • Memory is not the source of truth. Under write-back caching the newest value of a line is often a dirty copy in a peer core's cache, not in memory.
  • A read must locate the owner. A correct load returns the most recent write wherever it lives, so the read has to find the current owner, not assume memory.
  • Two mechanisms fix it. A snoop asks every cache who owns the line; a cache-to-cache transfer forwards the owner's dirty data directly to the reader.
  • The invariant: a load must never return a value older than the last committed write to that address — and a read that misses locally while a peer owns the dirty line must consult that owner, not memory.
  • This is why CHI has a snoop channel and direct data responses. They route a read to the latest value cheaply — often faster than DRAM — arbitrated by the Home Node.

11. Quick Revision

The read visibility problem. Under write-back caching, a store marks the writer's line dirty and leaves memory stale, so the newest value of a line may live only in a peer core's cache. A correct read must return the most recent write wherever it lives — which means the read has to find the owner, not just read memory. The fix: snoop every cache to locate the dirty owner, then forward that line cache-to-cache (direct data) to the reader, refreshing memory as a side effect. This is per-address read visibility — distinct from write visibility (1.6) and from ordering / consistency (cross-address, Module 12). It must be hardware (software cannot pull a dirty line from a peer's cache) and is often faster than DRAM. At SoC scale the snoop, the ownership tracking, and the direct data response are exactly what AMBA CHI's Home Node provides.

12. Coming Next

Next — 1.8 Why AXI Is Not Enough. You will see:

  • why AXI's read/write channels have no snoop, so a read can only target memory — never a peer cache,
  • why AXI's per-master model cannot express asking the other caches who owns a line and forwarding it,
  • what a coherent protocol must add on top — the snoop request and the direct data response this chapter demanded.

We keep the same two-CPU project — CPU0, CPU1, private L1s, one shared line — and ask why the bus you already know structurally cannot route the read this chapter needs to a peer's dirty cache, which is precisely the gap AMBA CHI was built to close.