AMBA CHI · Module 1 · Cache Coherency Foundations
The Stale Data Problem
A write-back cache makes stale data inevitable. When Core A stores to a cacheable line, the new value lands only in Core A's private L1 and the line is marked dirty, while memory and the shared L2 still hold the old value — so the newest value lives inside a cache and memory is no longer the source of truth. Core B, reading the same address, hits its own stale copy or fetches the old value from memory, never seeing Core A's write. Building on the two-CPU project, this chapter traces the stale-copy lifecycle, shows why write-back caching makes memory untrustworthy, and names the missing action a coherency protocol must add.
Foundation12 min readAMBA CHICache CoherencyWrite-BackDirty DataStale Data
Module 1 · Chapter 1.5 · Cache Coherency Foundations
Project thread — two CPUs (CPU0, CPU1), each with a private L1, over a shared L2 and one memory. Chapter 1.1 established that private caches create copies that can diverge; here we make that divergence precise using write-back caching and dirty data.
1. Learning Outcomes
By the end of this lesson you will be able to:
- Define write-back vs write-through and state exactly when each policy updates memory.
- Explain why memory is not the source of truth once a line is dirty in a cache.
- Trace the stale-copy lifecycle — clean fetch, write-back store (dirty), stale memory, stale peer read — cycle by cycle.
- Distinguish the two stale-read paths: a peer miss served old data from memory, and a peer hit on its own old line.
- Name the missing action coherency must add: invalidate on write, and route the read to the dirty holder.
2. Why Should I Learn This?
Chapter 1.1 already told you private caches create copies that can go stale — take that as given. What it did not pin down is the mechanism, and the mechanism is sharper than "two copies disagree": with write-back caches, a store leaves the newest value inside one cache while memory keeps the old one. Understand this one lifecycle and every later CHI mechanism — dirty tracking, ownership, snoops that pull data out of a cache — becomes an obvious consequence.
From silicon: a dual-core automotive controller intermittently acted on a stale sensor sample because CPU0's write-back store to the shared word stayed dirty in its own L1 while memory kept the old value. The fix was not a better handshake but making the region coherent — at SoC scale, that mechanism is AMBA CHI.
3. Core Concept — the lifecycle of a stale copy
Two policies decide when a store reaches memory:
- Write-through: every store updates the cache and memory. Memory stays current, but the store still does not touch other cores' cached copies — and the memory traffic is expensive.
- Write-back: a store updates only the cache line and sets a dirty bit. Memory is written later, on eviction. This is the default in real caches because it is far faster — and it is exactly what lets the newest value live inside a cache.
Trace one line, address A, under write-back:
1 — Clean and shared. CPU0 loads A, misses, fetches from memory, caches a clean copy (A = 5). Memory, L2, and CPU0's L1 all agree.
2 — The write-back store. CPU0 stores A = 6. CPU0's L1 line becomes 6 and is marked dirty. Memory and L2 still hold 5. The newest value now exists only in CPU0's cache.
3 — Memory is no longer the source of truth. Anyone reading memory for A gets 5, the stale value. The single authoritative copy is the dirty line in CPU0.
4 — The stale read. CPU1 reads A and goes stale two ways:
- Stale miss: CPU1 has no copy, misses, and fetches
Afrom memory/L2 — returning the old 5, because CPU0's dirty 6 was never written back. - Stale hit: CPU1 already cached an old copy and simply hits it, returning its own old value.
Either way, CPU1 never sees 6. That is the stale data problem.
The missing action. Coherency must make CPU0's write invalidate other copies and route CPU1's later read to the dirty holder (or forward CPU0's dirty data directly). Memory being stale is fine as long as reads are steered to wherever the newest value lives. This is still a per-address contract — coherency — distinct from consistency/ordering (many addresses, Module 12) and from visibility (when a write becomes observable, 1.6 and 1.7).
4. Engineering Diagram
5. Worked Example — the write-back store hides the new value
Trace the stale read at the value level, no coherency yet. A = 5 everywhere at reset. Caches are write-back, so a store sets a dirty bit and does not update memory (D = dirty bit, - = no valid copy):
Cycle Action L1(CPU0) L1(CPU1) L2/MEM Note
----- --------------------------- ----------- --------- ------- ------------------------------
1 CPU0 load A (miss -> fill) A=5 D=0 - A=5 clean copy fetched from memory
2 CPU0 store A=6 (write-back) A=6 D=1 - A=5 new value ONLY in CPU0; mem OLD
3 -- memory now stale -- A=6 D=1 - A=5 newest value lives DIRTY in cache
4 CPU1 load A (miss -> fill) A=6 D=1 A=5 D=0 A=5 CPU1 fetches STALE 5 <-- BUG
5 CPU1 uses A A=6 D=1 A=5 D=0 A=5 acts on stale 5 -> lost updateAt cycle 2 the value 6 becomes real but invisible outside CPU0 — memory is no longer the source of truth. At cycle 4 CPU1 does the "obvious safe thing," reads memory, and still gets the old value. No lock helps: the caches disagree and memory itself is stale. Coherency is the layer that makes cycle 4 return 6 — by steering CPU1's read to CPU0's dirty line (or invalidating and forwarding on the store).
6. RTL Illustration — a write-back dirty-bit model
A behavioral, simplified demonstrator (not a coherency controller): two 1-entry private write-back caches over a shared memory. CPU0's store lands only in its own line and sets dirty; memory is untouched, so CPU1's read miss is served the stale memory value.
// chi_writeback_stale_demo.sv — SIMPLIFIED / BEHAVIORAL. Two private write-back
// 1-entry caches over one memory. A store in core0 sets dirty and does NOT update
// memory, so core1's read miss is served the stale memory value. NOT a coherency IP.
module chi_writeback_stale_demo (
input logic clk,
input logic rst_n,
input logic c0_rd, // core0 read (fill clean on miss)
input logic c0_wr, // core0 write-back store
input logic [7:0] c0_wdata,
output logic [7:0] c0_rdata, // writer's copy (newest value)
output logic c0_dirty, // proof the newest value lives in a cache
input logic c1_rd, // core1 read (fill from memory on miss)
output logic [7:0] c1_rdata // reader's copy (stale)
);
logic [7:0] mem_A, c0_cache_A, c1_cache_A;
logic c0_valid, c1_valid, c0_dirty_r;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mem_A <= 8'd5; // A = 5 in memory at reset
c0_cache_A <= 8'd5; c0_valid <= 1'b0; c0_dirty_r <= 1'b0;
c1_cache_A <= 8'd5; c1_valid <= 1'b0;
end else begin
if (c0_rd && !c0_valid) begin // miss -> fill a CLEAN copy
c0_cache_A <= mem_A; c0_valid <= 1'b1; c0_dirty_r <= 1'b0;
end
if (c0_wr) begin // write-back: local + dirty, mem UNCHANGED
c0_cache_A <= c0_wdata; c0_valid <= 1'b1; c0_dirty_r <= 1'b1;
end
if (c1_rd && !c1_valid) begin // miss -> fill STALE value from memory
c1_cache_A <= mem_A; c1_valid <= 1'b1;
end
end
end
assign c0_rdata = c0_cache_A;
assign c1_rdata = c1_cache_A;
assign c0_dirty = c0_dirty_r;
endmoduleDrive CPU0 read → CPU0 write-back A=6 → CPU1 read: c1_rdata reports the stale 5 while c0_rdata holds 6 and c0_dirty is high — the newest value is trapped, dirty, in CPU0. There is no path in this model to route CPU1's read to that dirty line; supplying that path is exactly what coherency (and later CHI) adds.
7. DebugLab — "the reader keeps getting last frame's value"
The reader keeps getting last frame's value
DIRTY VALUE TRAPPED IN A WRITE-BACK CACHE -> MEMORY IS STALE, NEEDS COHERENCYA producer core writes a shared value; a consumer core intermittently reads the previous value, as if the write never happened. The producer can prove it wrote — its own read returns the new value. Single-core runs never fail, and reads that go all the way to memory also return the old value, which makes memory look "authoritative" and misleads the investigation.
The caches are write-back, so the producer's store committed the new value only to its own L1 and marked the line dirty; memory and the shared L2 still hold the old value, and nothing forces the dirty line out to the reader. The newest value physically lives inside the writer's cache — memory is not the source of truth. When the consumer misses, it fetches the stale value from memory; when it hits an old copy, it reads its own stale line. It appears only when a second agent reads the same address while the line is dirty elsewhere.
Make the region coherent so a read is always served the newest value wherever it lives, and a write reconciles other copies. On the write, invalidate (or update) other cached copies; on the read, route the request to the dirty holder and either forward its data directly or force a write-back first. That machinery is exactly what AMBA CHI provides — a Home Node tracks which agent holds the dirty line and issues snoops (Module 4); dirty/owned state is tracked with cache-state bits (Module 2). Flushing on every store "works" but destroys the performance write-back exists to give; locking orders access but never moves the dirty data.
8. Common Mistakes
- Trusting memory. Under write-back, memory holds the old value while a dirty cache holds the new one. "Just read from memory" returns stale data.
- Assuming write-through fixes it. Write-through keeps memory current but still does not update other cores' cached copies — a peer that hit its own old line is still stale.
- Blaming the flag / handshake. A
readyflag can be perfectly correct and the data it guards can still be stale — the value and its visibility are separate problems. - Confusing dirty with visible. A dirty line means memory is behind; it says nothing about whether another core can see the new value. Only coherency provides that.
- Reaching for a lock. Locks order access; they do not pull a dirty line out of one cache into another. A stale read is a coherency gap, not a locking gap.
9. Interview Questions
10. Engineering Checklist
- I can define write-back vs write-through and say exactly when each updates memory.
- I can state why memory is not the source of truth once a line is dirty in a cache.
- I can trace the stale-copy lifecycle: clean fetch, write-back store (dirty), stale memory, stale peer read.
- I can name both stale-read paths: a peer miss served old data from memory, and a peer hit on its own old line.
- I can name the missing actions — invalidate on write and route/forward the read to the dirty holder.
11. Key Takeaways
- Write-back makes the newest value live in a cache. A store commits only to the writer's L1 and marks it dirty; memory and L2 keep the old value.
- Memory is not the source of truth. Once a line is dirty, reading memory returns stale data — the authoritative copy is the dirty cache line.
- A stale read has two paths. A peer hit on its own old copy, or a peer miss served the old value from memory. Both miss the dirty write.
- The fix is coherency, not flushing or locking. A write must invalidate other copies; a read must be routed to the dirty holder (forward or write-back first). Only hardware can do this, and it must stay cheap.
- Invariant to carry forward: no read may return a value older than the last committed write to that address, and a dirty owner must supply the data. This is the foundational coherency bug CHI eliminates.
12. Quick Revision
The stale data problem. Caches are write-back: a store lands only in the writer's L1 and sets a dirty bit — memory and L2 keep the old value, so the newest value lives in a cache and memory is not the source of truth. A peer read goes stale two ways: a stale hit (reads its own old copy) or a stale miss (fetches the old value from memory). No lock or flag fixes the value; write-through keeps memory current but still ignores other caches. Coherency is the per-address hardware guarantee that a read returns the newest write wherever it lives — invalidate other copies on a write, route/forward the read to the dirty holder. Distinct from visibility (when/where a write is seen, 1.6–1.7) and consistency/ordering (many addresses, Module 12). At SoC scale, that machinery is AMBA CHI.
13. Coming Next
Next — 1.6 The Write Visibility Problem. We keep the same project — CPU0, CPU1, private L1s, a shared L2 — and pivot from "the newest value is trapped in a cache" to when a write actually becomes observable to another core: the difference between a store completing locally and being globally visible, and the write-side obligations — propagate, invalidate, acknowledge — a coherency protocol owes before a write can be called done.