Skip to content

AMBA CHI · Module 8 · Request Flows

ReadClean Flow

ReadShared shared a copy; ReadUnique took sole ownership; ReadClean sits between them. A core issues a ReadClean to read a line without inheriting responsibility for dirty data. The requester ends clean: Shared Clean if it coexists with a holder, Unique Clean if it is the sole holder — never Dirty. Where ReadUnique forwards a modified line to the requester as dirty, ReadClean cleans it, writing the dirty data back to memory so the copies it leaves are genuinely clean. The obligation is easy to miss: cleaning a dirty holder means writing its data back to memory, or the latest value sits in caches marked clean and is dropped on eviction. Representative model, not the specification.

Intermediate15 min readAMBA CHIReadCleanClean CopySnpCleanWriteback

Module 8 · Chapter 8.3 · Request Flows

Project thread — 8.1 shared (end SC), 8.2 owned (end Unique). This chapter is the third read: ReadClean — a clean copy with no dirty responsibility. 8.4 covers ReadNotSharedDirty.

1. Learning Outcomes

By the end of this chapter you should be able to:

  • Place ReadClean between ReadShared and ReadUnique — a clean read without ownership of dirty data.
  • State the signature: the requester ends clean (SC or UC), never dirty (UD).
  • Explain how a dirty holder is cleaned — its data written back to memory, not forwarded.
  • Distinguish the cases by holder state — uncached, shared, unique clean, unique dirty.
  • Diagnose why skipping the writeback silently loses a modified line.
  • Implement a representative ReadClean outcome in SystemVerilog, Verilog-2001, and VHDL.

2. Why Should I Learn This?

ReadClean is the read for when you want the data but not the baggage. Sometimes a core needs to read a line and possibly cache it, but has no intention of writing it and does not want to become responsible for holding it dirty. ReadClean gives exactly that — a clean copy — and it is common enough (instruction fetches, read-mostly data, prefetches) that you will meet it whenever you trace real traffic.

Its lesson is subtle and specific: cleanliness is a claim about memory, not just about a cache line's flag. When ReadClean turns a dirty holder into clean copies, it is asserting that memory now holds the latest value — which is only true if the flow actually wrote that value back. Mark a line clean without cleaning memory and you have created the most dangerous kind of copy: one that looks disposable but holds the only current data. This is the flow where the writeback obligation is easiest to drop and most costly to miss.

3. Key Terms

4. Previous Chapter Connection

Chapters 8.1 and 8.2 gave the two poles: ReadShared ends the requester SC and downgrades holders (they coexist); ReadUnique ends it Unique and invalidates everyone. Both could leave the requester holding dirty data — ReadShared preserves it as shared-dirty responsibility, ReadUnique forwards it as UD.

ReadClean is defined by refusing that. It wants a copy that is clean — matching memory — so the requester carries no dirty responsibility. When it meets a dirty holder, it does not forward the dirty line; it cleans it, writing the data back to memory (Chapter 7.2's writeback path, driven by the home). The requester ends SC or UC, never UD. It is the read that takes the value while leaving the dirty-data duty behind.

5. Core Concept — a clean copy, no dirty responsibility

A ReadClean brings a line to a requester in a clean state — matching memory — and takes on no obligation to write dirty data back. The flow varies by holder state, but the requester's copy is always clean.

  • Uncached. No cache holds the line. The home fetches from memory and installs the requester in UC (Unique Clean) — sole holder, clean. No snoop.
  • Held shared. Sharers hold the line in SC. The requester joins as another SC holder; memory is already current. No snoop, no writeback.
  • Held unique clean (UC). A holder in UC. The home sends SnpClean, downgrading it to SC; the requester ends SC. The line was already clean, so no writeback.
  • Held unique dirty (UD). A holder in UD. The home sends SnpClean; the holder returns its dirty data and downgrades to SC, and the home writes the data back to memory. The requester installs a clean SC copy. The dirty data is cleaned, not forwarded.

Across all cases the requester ends clean — SC or UC, never UD — and any dirty data ends up in memory, so every remaining copy genuinely matches it.

The synthesis:

ReadClean fetches a clean copy and leaves the requester with no dirty responsibility. A dirty holder is cleaned — its data written back to memory via SnpClean — rather than forwarded. The requester ends SC (coexisting) or UC (sole), never UD. The invariant that names the flow: after ReadClean, memory is current and every cached copy is clean.

6. Engineering Mental Model — photocopy the master, file the original

Back to the office document, but now you want a read-only, up-to-date copy and none of the filing duties.

  • If nobody has it, you take the archive's copy — it is the current version, and you hold it (UC), clean.
  • If colleagues are reading copies, you make one more and read alongside them (SC). The master file is already current.
  • If a colleague had it out with handwritten edits (UD), you do not take their marked-up original and inherit the job of filing it. Instead, their edits are entered into the master file (writeback to memory), and everyone — including you — now reads clean copies of the updated master (SC). The edits are safely in the system of record; nobody is left holding the only annotated original.

The point is where the edits end up: in the master file, not in a stray photocopy someone might toss. ReadClean makes sure the latest data lands in memory, so the clean copies it hands out are safe to discard.

7. Engineering Diagram — ReadClean with a dirty holder

A ReadClean flow where RN1 holds the line dirty. RN0 sends a REQ ReadClean to the Home Node. The Home Node sends a SNP SnpClean to RN1. RN1 returns its dirty data as a DAT SnpRespData and downgrades from Unique Dirty to Shared Clean. The Home Node writes the data back to memory at the Subordinate Node, then returns a clean DAT CompData to RN0, which installs Shared Clean. RN0 sends a RSP CompAck. Both cores end Shared Clean and memory is current.ReadClean — clean the dirty holder, write back to memoryRN0 · requester(I)HN · homeRN1 · holder (UD)SN · memoryREQ: ReadCleanSNP: SnpCleanDAT: SnpRespData (UDto SC)DAT: WriteBack(clean memory)DAT: CompData(install SC)RSP: CompAck
Figure 1 — a ReadClean where RN1 holds the line dirty (UD). RN0 requests on REQ; the Home Node snoops RN1 with SnpClean; RN1 returns its dirty data and downgrades to SC; the Home Node writes the data back to memory (SN) — cleaning it — and returns a clean CompData to RN0, which installs SC. Both cores end Shared Clean and memory is current. The dirty data is cleaned to memory, not forwarded as dirty.

Read top to bottom: request, a cleaning snoop, dirty data returned, writeback to memory, then a clean completion. RN1 went UD → SC; RN0 installed SC; memory now current. The extra beat versus ReadShared is the writeback — that is what makes the copies clean.

8. The Cases by Holder State

The flow's middle depends on the holder, but the requester always ends clean.

Holder stateHome actionSnoopWritebackRequester ends
Uncached (I)fetch from memorynonenoUC
Shared (SC)join sharersnonenoSC
Unique Clean (UC)downgrade holderSnpCleannoSC
Unique Dirty (UD)downgrade + cleanSnpCleanyesSC

The rule to carry: the requester ends UC only when it is the sole holder (uncached), and SC whenever it coexists with a holder — but never UD. A writeback appears only in the UD row, and it is the defining beat: turning a dirty holder into clean copies requires cleaning memory. Every other row leaves memory already current.

9. Clean Means Memory Is Current — the writeback obligation

The property to isolate is what "clean" actually promises.

  • Clean is a claim about memory. A clean copy (SC/UC) asserts that memory holds the same value. That is what makes a clean line safe to evict silently — nothing is lost, because memory has it.
  • So cleaning a dirty line demands a writeback. If ReadClean downgrades a dirty holder to a clean state, it must first put that dirty data into memory. Otherwise the "clean" flag is a lie — the latest value is not in memory.
  • The requester inherits no dirty duty. Because the data went to memory, the requester's copy is genuinely clean; it will never have to write anything back. That is the whole point of ReadClean.
  • Contrast the siblings. ReadUnique forwards dirty (requester ends UD, owns the writeback duty). ReadClean cleans dirty (requester ends SC, owes nothing). Same dirty holder, opposite destination for the data.

The point to carry:

A clean line is a promise that memory is current. ReadClean's job is to keep that promise while handing out clean copies — so when it encounters dirty data, it must write that data back to memory, not merely relabel the copies as clean. Skip the writeback and you manufacture clean-looking copies of data memory does not have; the next silent eviction throws the only current value away. Cleanliness is earned by the writeback, not asserted by the flag.

10. Flow Walkthrough — ReadClean, dirty holder

RN0 (in I) issues a ReadClean for a line homed at HN; RN1 holds it in UD.

  1. REQ. RN0 sends ReadClean, allocating a tracker. It wants a clean copy — no dirty duty.
  2. Directory lookup. HN sees RN1 in UD. The dirty data must be cleaned — captured and written to memory — and RN1 downgraded, but not made to forward dirty responsibility to RN0.
  3. SnpClean. HN sends SnpClean to RN1. RN1 returns its dirty data (SnpRespData) and downgrades UD → SC.
  4. Writeback + CompData. HN writes the data back to memory, making it current, and returns a clean CompData to RN0, which installs SC. The directory lists RN0 and RN1 in SC; memory holds the latest value.
  5. CompAck. RN0 closes with CompAck.

End state: two clean co-sharers, memory current, and no cache holding dirty responsibility. Compare 8.2: the same dirty holder under ReadUnique would forward the dirty data to RN0 as UD — here it went to memory instead.

11. RTL / Hardware View — ReadClean outcome logic

The ReadClean flow reduces to a small function of the holder state: snoop a unique holder, write back if it was dirty, and end the requester clean. Representative — the requester is never UD, and a dirty holder triggers a writeback.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadClean outcome logic (educational).
// Fetch a CLEAN copy. A unique holder is downgraded (SnpClean). A DIRTY holder's
// data is WRITTEN BACK to memory -- cleaned, not forwarded -- so the requester
// ends clean and owes no writeback. req_final is never UD.
module chi_readclean_outcome (
  input  logic [2:0] holder_state,   // INV, SC, UC, UD
  output logic       do_snoop,       // SnpClean to a unique holder?
  output logic       writeback,      // dirty data cleaned to memory?
  output logic [2:0] holder_final,   // holder's state after the flow
  output logic [2:0] req_final       // requester's state after the flow (never UD)
);
  localparam logic [2:0] INV = 3'd0, SC = 3'd3, UC = 3'd1, UD = 3'd2;
 
  logic held, is_unique, is_dirty;
  assign held      = (holder_state != INV);
  assign is_unique = (holder_state == UC) || (holder_state == UD);
  assign is_dirty  = (holder_state == UD);
 
  // Snoop only a unique holder; shared/uncached needs none.
  assign do_snoop     = is_unique;
  // A dirty holder is CLEANED: its data is written back to memory.
  assign writeback    = is_dirty;
  // A unique holder downgrades to SC; shared/uncached is left as-is.
  assign holder_final = is_unique ? SC : holder_state;
  // Requester ends CLEAN: UC if it is the sole holder (uncached), else SC. Never UD.
  assign req_final    = held ? SC : UC;
endmodule

The same behavior in Verilog-2001:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadClean outcome logic (Verilog-2001).
module chi_readclean_outcome (
  input  [2:0] holder_state,
  output       do_snoop,
  output       writeback,
  output [2:0] holder_final,
  output [2:0] req_final
);
  localparam INV = 3'd0, SC = 3'd3, UC = 3'd1, UD = 3'd2;
 
  wire held      = (holder_state != INV);
  wire is_unique = (holder_state == UC) || (holder_state == UD);
  wire is_dirty  = (holder_state == UD);
 
  assign do_snoop     = is_unique;
  assign writeback    = is_dirty;
  assign holder_final = is_unique ? SC : holder_state;
  assign req_final    = held ? SC : UC;
endmodule

And in VHDL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Representative ReadClean outcome logic (VHDL).
library ieee;
use ieee.std_logic_1164.all;
 
entity chi_readclean_outcome is
  port (
    holder_state : in  std_logic_vector(2 downto 0);
    do_snoop     : out std_logic;
    writeback    : out std_logic;
    holder_final : out std_logic_vector(2 downto 0);
    req_final    : out std_logic_vector(2 downto 0)
  );
end entity;
 
architecture rtl of chi_readclean_outcome is
  constant INV : std_logic_vector(2 downto 0) := "000";
  constant SC  : std_logic_vector(2 downto 0) := "011";
  constant UC  : std_logic_vector(2 downto 0) := "001";
  constant UD  : std_logic_vector(2 downto 0) := "010";
  signal held, is_unique, is_dirty : boolean;
begin
  held      <= (holder_state /= INV);
  is_unique <= (holder_state = UC) or (holder_state = UD);
  is_dirty  <= (holder_state = UD);
 
  do_snoop     <= '1' when is_unique else '0';
  writeback    <= '1' when is_dirty else '0';
  holder_final <= SC when is_unique else holder_state;
  req_final    <= SC when held else UC;
end architecture;

All three write back on a dirty holder (writeback = is_dirty) and end the requester clean (req_final is SC or UC, never UD). That writeback is the cleanliness obligation, and the DebugLab shows what dropping it costs.

12. Verification View — requester ends clean, dirty is written back

The properties that define ReadClean: the requester never ends dirty, and a dirty holder is cleaned to memory.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind to chi_readclean_outcome.
// 1. The requester ends CLEAN — SC or UC, never dirty (UD) and never invalid.
property p_req_clean;
  @(*) (req_final == 3'd3 /*SC*/) || (req_final == 3'd1 /*UC*/);
endproperty
 
// 2. A dirty holder MUST be written back — cleaned to memory, not left uncleaned.
property p_dirty_writeback;
  @(*) (holder_state == 3'd2 /*UD*/) |-> writeback;
endproperty
 
// 3. Any holder ends clean (SC) or as it was — never left in a dirty state after the flow.
property p_holder_clean;
  @(*) (holder_final != 3'd2 /*UD*/);
endproperty

The system point, beyond the checks:

ReadClean's correctness is a conservation law for dirty data. A modified value must exist somewhere durable at all times — either in a cache that will write it back, or in memory. ReadClean chooses memory: it takes the requester out of the dirty-responsibility business by putting the data where it is always safe. So the writeback is not optional bookkeeping; it is the step that conserves the value as the flow strips the "dirty" flag off every copy. Omit it and the value exists only in caches that believe they may discard it — the flow has quietly created a leak. Clean copies are only sound if memory backs them.

  • What it proves: the requester ends clean, and a dirty holder triggers a writeback.
  • What it does not prove: the writeback actually reaches memory — that is the home/memory path (Chapter 7.2).
  • Bug signature: a dirty holder cleaned to SC with no writeback — the latest value stranded in disposable copies.

13. Testbench — every holder-state case

Drives each starting holder state and checks the snoop, writeback, and resulting states — especially that the requester is never dirty and a dirty holder is written back.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_chi_readclean_outcome;
  logic [2:0] holder_state, holder_final, req_final;
  logic do_snoop, writeback;
  int errors = 0;
  localparam INV = 3'd0, UC = 3'd1, UD = 3'd2, SC = 3'd3;
 
  chi_readclean_outcome dut (.*);
 
  task automatic check(input logic [2:0] hs, input logic exp_snoop, exp_wb,
                       input logic [2:0] exp_hf, exp_rf, input string name);
    holder_state = hs; #1;
    if (do_snoop !== exp_snoop || writeback !== exp_wb ||
        holder_final !== exp_hf || req_final !== exp_rf || req_final === UD) begin
      errors++; $display("FAIL %s: snoop=%0b wb=%0b hf=%0d rf=%0d",
                         name, do_snoop, writeback, holder_final, req_final);
    end else $display("PASS %s: snoop=%0b wb=%0b hf=%0d rf=%0d",
                      name, do_snoop, writeback, holder_final, req_final);
  endtask
 
  initial begin
    check(INV, 1'b0, 1'b0, INV, UC, "uncached  -> memory, req UC");
    check(SC,  1'b0, 1'b0, SC,  SC, "shared    -> join, req SC");
    check(UC,  1'b1, 1'b0, SC,  SC, "uniqueCln -> downgrade, req SC");
    check(UD,  1'b1, 1'b1, SC,  SC, "uniqueDty -> clean + wb, req SC");
 
    if (errors == 0) $display("ALL TESTS PASSED");
    else             $display("%0d FAILURE(S)", errors);
    $finish;
  end
endmodule

Expected output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS uncached  -> memory, req UC: snoop=0 wb=0 hf=0 rf=1
PASS shared    -> join, req SC: snoop=0 wb=0 hf=3 rf=3
PASS uniqueCln -> downgrade, req SC: snoop=1 wb=0 hf=3 rf=3
PASS uniqueDty -> clean + wb, req SC: snoop=1 wb=1 hf=3 rf=3
ALL TESTS PASSED

14. DebugLab — cleaning a dirty holder without the writeback

1

Cleaning a dirty holder without the writeback

READCLEAN DOWNGRADES DIRTY HOLDER WITHOUT WRITEBACK -> LOST UPDATE
Symptom

Data written by one core is silently lost — a later read returns a stale value — with no error. It happens only for lines that were dirty before a ReadClean, and only after the clean copies are evicted; keep the copies resident and the data survives.

Evidence

A dirty line became clean copies, but memory was never updated:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before: RN1 = UD (value = NEW),  memory = OLD
RN0 issues ReadClean -> HN SnpClean to RN1
  RN1: UD -> SC (marked clean), returns NEW
  HN installs RN0 = SC (marked clean, value NEW)
  HN does NOT write NEW back to memory   <-- missing writeback
state: RN0 = SC(NEW), RN1 = SC(NEW), memory = OLD
later: both SC copies evicted SILENTLY (clean -> no writeback)
  -> NEW is gone, memory still OLD -> lost update

Both caches held the only copy of NEW while believing it was safely in memory.

First Divergence

The flow marked the copies clean (SC) without performing the writeback that makes clean true. From that point memory was stale relative to the SC copies, so those copies held data that clean semantics allow to be discarded.

Root Cause

A clean copy asserts memory is current; ReadClean must make that assertion true. When it turns a dirty holder into clean copies, the dirty data has to go into memory via a writeback — otherwise the latest value lives only in caches that may silently drop it. This is a conservation failure: the dirty value was neither kept dirty nor written back, so nothing is obliged to preserve it. It differs from dropping data at the snoop-response level (Chapter 6.5): here the data is returned, but the flow forgets to land it in memory.

Fix

Write the dirty data back to memory whenever ReadClean downgrades a dirty holder — exactly the writeback = is_dirty term in the outcome logic. Only after the data is in memory may the copies be marked clean. Then a clean flag is honest: memory holds the value, and evicting a clean copy loses nothing.

15. Common Mistakes

  • Cleaning without writeback. Assumption: relabeling to SC is enough. Bug: lost update (the DebugLab). Prevention: write dirty data back to memory.
  • Forwarding dirty to the requester. Assumption: ReadClean is like ReadUnique. Bug: requester wrongly ends UD, owes a writeback. Prevention: ReadClean cleans; requester ends clean.
  • Ending the requester dirty. Assumption: any read state is fine. Bug: unexpected dirty responsibility. Prevention: ReadClean ends SC/UC, never UD.
  • Invalidating a holder. Assumption: clean means sole. Bug: needless loss of a sharer. Prevention: SnpClean downgrades; sharers may coexist.
  • Snooping when uncached or shared. Assumption: every case snoops. Bug: needless snoops. Prevention: snoop only unique holders.
  • Forgetting CompAck. Assumption: data ends the read. Bug: transaction never closes (Chapter 7.1). Prevention: close with CompAck.

16. Engineering Checklist

  • End the requester cleanUC if sole, SC if coexisting — never UD.
  • Snoop a unique holder with SnpClean — downgrade, not invalidate.
  • Write dirty data back to memory whenever a dirty holder is cleaned.
  • Mark copies clean only after memory holds the value.
  • Do not snoop when the line is uncached or already shared.
  • Close with CompAck; ReadClean grants no write permission and no dirty duty.

17. Key Takeaways

  • ReadClean fetches a clean copy without taking on dirty responsibility — it sits between ReadShared and ReadUnique.
  • The requester ends cleanSC when coexisting, UC when sole — and never UD.
  • A dirty holder is cleaned: its data is written back to memory via SnpClean, not forwarded.
  • After the flow, memory is current and every remaining copy genuinely matches it.
  • Marking copies clean without the writeback strands the latest value in disposable copies — a lost update.
  • Clean means memory is current; earn it with the writeback. The model here is representative.

18. Quick Revision

ReadClean flow. ReadClean reads a clean copy without dirty responsibility — it sits between ReadShared (share) and ReadUnique (own). The requester ends clean: UC if it is the sole holder (uncached), SC if it coexists with a holder — but never UD. Cases by holder: uncached → memory fetch, UC; shared → join, SC; unique cleanSnpClean downgrade to SC, requester SC; unique dirtySnpClean, the holder returns its dirty data and downgrades to SC, and the home writes the data back to memory, requester installs a clean SC. The defining obligation: turning a dirty holder into clean copies requires a writeback, because a clean flag promises memory is current. Skip it and the latest value sits only in caches marked clean, discarded silently on eviction — a lost update. ReadClean cleans dirty data to memory where ReadUnique forwards it to the requester. End clean, write dirty back, earn the clean flag. Representative model; 8.4 is ReadNotSharedDirty.

Coming Next

Chapter 8.4 — ReadNotSharedDirty Flow. ReadClean kept memory current; ReadNotSharedDirty is the specialist for a subtler case — a requester that will accept a shared copy but must not inherit the responsibility for shared-dirty data. Chapter 8.4 walks this variant: what "not shared dirty" means as an end state, how it differs from ReadShared and ReadClean, and the narrow but real situations where it is the correct read to issue — completing the family of read flows before Module 8 turns to writes.