Skip to content

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 A from 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

Two CPUs with private L1 caches over a shared L2 and memory; CPU0 holds a dirty line A equals 6 while L2 and memory hold the stale A equals 5, and CPU1 reads the stale value because no coherency action routes it to the dirty holderCPU0write-back store A = 6CPU1later reads AL1 (CPU0)A = 6 DIRTY (newest)L1 (CPU1)A = 5 or miss (stale)Shared L2A = 5 (old)MemoryA = 5 (NOT source of truth)12
Figure 1 — the stale-copy lifecycle with write-back caches. CPU0 stores A and its L1 line becomes DIRTY (A=6); memory and L2 still hold the OLD value (A=5), so memory is NOT the source of truth. CPU1 reads A and either hits its own OLD copy or misses and is served the OLD value from memory — it never sees CPU0's dirty 6. The missing coherency action must route CPU1's read to the dirty holder.

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):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 update

At 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
endmodule

Drive 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"

1

The reader keeps getting last frame's value

DIRTY VALUE TRAPPED IN A WRITE-BACK CACHE -> MEMORY IS STALE, NEEDS COHERENCY
Symptom

A 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.

Root Cause

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.

Fix

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 ready flag 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.