Skip to content

AMBA CHI · Module 8 · Request Flows

ReadNotSharedDirty Flow

ReadClean kept memory current by writing dirty data back; ReadNotSharedDirty avoids that write. A core issues it to read-share a line without becoming the Shared Dirty owner — the one sharer that also carries the duty to write the line back. The requester ends clean-shared, never Shared Dirty and never Dirty; and when a holder has the line dirty, that holder keeps its dirty data and its ownership, forwarding only a clean shared copy. No writeback is forced, because the dirty value stays cached in the owner that still answers for it. It is the read for a requester that wants shared data cheaply but will not take on the dirty-writeback duty. Representative model, not the specification.

Advanced17 min readAMBA CHIReadNotSharedDirtyShared DirtyOwnershipCoherency

Module 8 · Chapter 8.4 · Request Flows

Project thread — 8.1 shared, 8.2 owned, 8.3 cleaned. This chapter closes the read family with the subtlest variant: read-share without taking the dirty-owner role. 8.5 turns to writes.

1. Learning Outcomes

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

  • Define the Shared Dirty (SD) state — a shared copy that also owns the dirty-writeback duty.
  • State the signature of ReadNotSharedDirty: the requester ends clean-shared, never SD.
  • Explain how a dirty holder retains its SD ownership and forwards a clean copy — with no writeback.
  • Contrast it with ReadShared (may accept SD) and ReadClean (forces a writeback).
  • Diagnose why installing the requester as SD creates two dirty owners.
  • Implement a representative ReadNotSharedDirty outcome in SystemVerilog, Verilog-2001, and VHDL.

2. Why Should I Learn This?

Not every cache wants to be responsible for dirty data. In a coherent system, exactly one cache among the sharers of a dirty line is the owner — the one holding it in Shared Dirty, obliged to write it back before it is lost. That role has a cost: extra state, an eviction that must write back, and the risk of getting it wrong. Some requesters — a small cache, a read-mostly agent, one that will drop the line soon — would rather not take it on.

ReadNotSharedDirty exists for exactly that requester. It reads a shared copy while explicitly declining the owner role, leaving the dirty data with whoever already holds it. It is Advanced because it turns on the distinction between having a dirty copy and owning the duty to write it back — a distinction that, once you see it, clarifies the whole shared-dirty side of coherence. And getting it wrong hands the owner role to a cache that never agreed to it.

3. Key Terms

4. Previous Chapter Connection

The read family so far: ReadShared (8.1) ends SC and may let the requester take SD — accepting dirty ownership among sharers; ReadUnique (8.2) invalidates all and ends the requester Unique; ReadClean (8.3) forces a writeback, cleaning memory so every copy is clean.

ReadNotSharedDirty carves out the case in between ReadShared and ReadClean. Like ReadShared, it read-shares and leaves other copies alive; unlike ReadShared, it refuses to become the SD owner. Unlike ReadClean, it does not force a writeback — the dirty data stays cached in whoever already owns it. It needs the SD state from the coherence model to be understood at all, which is why it comes last in the family: it is the flow defined entirely by which cache carries the dirty-owner duty.

5. Core Concept — read-share without taking the owner role

A ReadNotSharedDirty brings a line to a requester as a clean share — SC, or UC if it is sole — and never as SD or UD. Its distinctive move is what it does with dirty data: it leaves the ownership where it is.

  • Uncached. No cache holds the line. The home fetches from memory; the requester installs UC. No snoop.
  • Held shared clean (SC). Sharers hold clean copies; memory is current. The requester joins as SC. No snoop, no writeback.
  • Held unique clean (UC). The home downgrades the holder to SC; the requester ends SC. Nothing was dirty.
  • Held dirty (UD or SD). The home sends SnpNotSharedDirty. The holder keeps the dirty data and its ownership — a UD holder becomes SD (still owns the dirty), an SD holder stays SD — and forwards a clean SC copy to the requester. No writeback to memory.

Across all cases the requester ends clean-shared (SC/UC), and any dirty data stays cached in the existing owner as SD. Exactly one cache owns the dirty line, and it is never the requester.

The synthesis:

ReadNotSharedDirty read-shares a line while declining the SD owner role. A dirty holder retains its ownership — UD becomes SD, SD stays SD — and forwards a clean copy; the requester ends SC (or UC), never SD, and no writeback is forced. Where ReadShared might make the requester the dirty owner and ReadClean would write the dirty data to memory, RNSD keeps the dirty value cached in the cache that already answers for it.

6. Engineering Mental Model — read the annotated copy, decline to be its keeper

The office document again — a colleague holds the master with handwritten edits they are responsible for filing (SD).

  • You want to read the current text, including their edits, but you do not want to become the person who has to file it.
  • So you ask for a read-only photocopy of their annotated version (a clean SC copy). Your colleague keeps the annotated original and remains the one responsible for filing it (stays SD).
  • Nothing gets filed into the master cabinet right now (no writeback) — the edits stay safely with the colleague who owns them. You simply read your copy and owe nothing.

Compare the neighbours: ReadShared might hand you the annotated original and make you the keeper; ReadClean would file the edits into the cabinet immediately (writeback). ReadNotSharedDirty does neither — it leaves the keeper as they were and gives you a copy to read.

7. Engineering Diagram — ReadNotSharedDirty with a dirty holder

A ReadNotSharedDirty flow where RN1 holds the line dirty. RN0 sends a REQ ReadNotSharedDirty to the Home Node. The Home Node sends a SNP SnpNotSharedDirty to RN1. RN1 downgrades from Unique Dirty to Shared Dirty, keeping the dirty data and remaining the owner, and forwards a clean copy as a DAT SnpRespData. The Home Node returns a clean DAT CompData to RN0, which installs Shared Clean. RN0 sends a RSP CompAck. There is no writeback to memory; the dirty data stays cached in RN1 as Shared Dirty, and RN0 ends clean-shared, never the owner.ReadNotSharedDirty — holder keeps SD, requester ends SCRN0 · requester (I)HN · homeRN1 · holder (UD)REQ:ReadNotSharedDirtySNP:SnpNotSharedDirtyDAT: SnpRespData (UDto SD, keeps dirty)DAT: CompData(install SC)RSP: CompAck
Figure 1 — a ReadNotSharedDirty where RN1 holds the line dirty (UD). RN0 requests on REQ; the Home Node snoops RN1 with SnpNotSharedDirty; RN1 keeps the dirty data and its ownership — downgrading from UD to SD, still the owner — and forwards a clean copy as SnpRespData; the Home Node returns a clean CompData to RN0, which installs SC. No writeback to memory occurs; the dirty stays cached in RN1 as SD. RN0 ends clean-shared, never the owner.

Read top to bottom, and compare the ReadClean figure: there is no writeback beat. RN1 went UD → SD (kept the dirty), RN0 installed SC. The dirty data never touched memory — it stayed with its owner.

8. The Cases by Holder State

The flow's middle depends on the holder, but the requester always ends clean-shared and never owns dirty.

Holder stateHome actionSnoopWritebackRequester ends
Uncached (I)fetch from memorynonenoUC
Shared Clean (SC)join sharersnonenoSC
Unique Clean (UC)downgrade holderSnpNotSharedDirtynoSC
Unique Dirty (UD)holder keeps dirty as SDSnpNotSharedDirtynoSC
Shared Dirty (SD)holder stays SDSnpNotSharedDirtynoSC

The rule to carry: the requester never ends SD or UD, and there is never a writeback. A dirty holder retains ownership — UD → SD, SD → SD — so exactly one cache still owns the dirty line, and it is the original holder, not the requester. That retained ownership is what lets RNSD skip the memory write.

9. Not Shared Dirty — refusing the owner role

The property to isolate is ownership, and how RNSD differs from both neighbours.

  • Requester declines SD. It ends SC (or UC) — a reader, never the owner. It carries no writeback duty.
  • The dirty owner is retained. The cache that had the dirty data keeps it as SD. Ownership does not move and does not vanish — it stays put.
  • No writeback, by design. Because a cache still owns the dirty line, memory need not be updated. This is the optimization over ReadClean: avoid a memory write when a cache is willing to keep owning the dirty data.
  • Exactly one owner, always. Before and after, one cache owns the dirty line. RNSD adds a reader without adding — or moving — an owner.

The point to carry:

The coherence model allows a dirty line to be shared, but only if exactly one sharer owns the duty to write it back — that sharer is SD. ReadNotSharedDirty adds a reader to such a line without disturbing the ownership: the requester takes a clean copy and the owner keeps its SD. So the invariant is conservation of ownership — one owner before, the same one owner after. ReadShared risks moving that role to the requester; ReadClean dissolves it into memory; RNSD leaves it exactly where it was.

10. Flow Walkthrough — dirty holder retains SD

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

  1. REQ. RN0 sends ReadNotSharedDirty, allocating a tracker. It wants a readable copy but not the owner role.
  2. Directory lookup. HN sees RN1 in UD — it owns the dirty line. The requester must get a copy without becoming the owner, and RN1 must keep ownership.
  3. SnpNotSharedDirty. HN snoops RN1. RN1 downgrades UD → SD — it stays a sharer and remains the dirty owner — and forwards a clean copy (SnpRespData). It does not relinquish the dirty data.
  4. CompData, no writeback. HN returns a clean CompData to RN0, which installs SC. Memory is not written — RN1 still owns the dirty value. The directory lists RN1 in SD (owner) and RN0 in SC (reader).
  5. CompAck. RN0 closes with CompAck.

End state: one owner (RN1, SD), one reader (RN0, SC), memory untouched. Compare 8.3, where the same holder would have been cleaned to SC with a writeback — here RN1 kept the dirty and its ownership.

11. RTL / Hardware View — ReadNotSharedDirty outcome logic

The flow reduces to a function of the holder state: snoop a holder, keep dirty ownership where it is, and end the requester clean-shared — with no writeback ever. Representative.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadNotSharedDirty outcome logic (educational).
// Read-share WITHOUT taking the Shared Dirty (SD) owner role. A dirty holder
// RETAINS ownership: UD -> SD (keeps dirty), SD stays SD; a unique-clean holder
// downgrades to SC. The requester ends CLEAN-share (SC, or UC if sole) -- never
// SD or UD -- and NO writeback is forced (the dirty stays cached in the owner).
module chi_readnsd_outcome (
  input  logic [2:0] holder_state,   // INV, SC, UC, UD, SD
  output logic       do_snoop,       // SnpNotSharedDirty to a holder?
  output logic       writeback,      // never asserted -- dirty stays cached
  output logic [2:0] holder_final,   // holder's state after the flow
  output logic [2:0] req_final       // requester's state (never SD, never UD)
);
  localparam logic [2:0] INV = 3'd0, UC = 3'd1, UD = 3'd2, SC = 3'd3, SD = 3'd4;
 
  logic held, is_dirty, is_unique_clean;
  assign held            = (holder_state != INV);
  assign is_dirty        = (holder_state == UD) || (holder_state == SD);
  assign is_unique_clean = (holder_state == UC);
 
  // Snoop any non-shared-clean holder (UC/UD/SD); SC/uncached need none.
  assign do_snoop  = held && (holder_state != SC);
  // ReadNotSharedDirty never forces a writeback -- the owner keeps the dirty.
  assign writeback = 1'b0;
  // Dirty holder RETAINS ownership as SD; unique-clean downgrades to SC; else as-is.
  assign holder_final = is_dirty        ? SD :
                        is_unique_clean ? SC :
                                          holder_state;
  // Requester ends clean-share: UC if sole (uncached), else SC. Never SD/UD.
  assign req_final = held ? SC : UC;
endmodule

The same behavior in Verilog-2001:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadNotSharedDirty outcome logic (Verilog-2001).
module chi_readnsd_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, UC = 3'd1, UD = 3'd2, SC = 3'd3, SD = 3'd4;
 
  wire held            = (holder_state != INV);
  wire is_dirty        = (holder_state == UD) || (holder_state == SD);
  wire is_unique_clean = (holder_state == UC);
 
  assign do_snoop     = held && (holder_state != SC);
  assign writeback    = 1'b0;
  assign holder_final = is_dirty        ? SD :
                        is_unique_clean ? SC :
                                          holder_state;
  assign req_final    = held ? SC : UC;
endmodule

And in VHDL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Representative ReadNotSharedDirty outcome logic (VHDL).
library ieee;
use ieee.std_logic_1164.all;
 
entity chi_readnsd_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_readnsd_outcome is
  constant INV : std_logic_vector(2 downto 0) := "000";
  constant UC  : std_logic_vector(2 downto 0) := "001";
  constant UD  : std_logic_vector(2 downto 0) := "010";
  constant SC  : std_logic_vector(2 downto 0) := "011";
  constant SD  : std_logic_vector(2 downto 0) := "100";
  signal held, is_dirty, is_unique_clean : boolean;
begin
  held            <= (holder_state /= INV);
  is_dirty        <= (holder_state = UD) or (holder_state = SD);
  is_unique_clean <= (holder_state = UC);
 
  do_snoop     <= '1' when (held and holder_state /= SC) else '0';
  writeback    <= '0';
  holder_final <= SD when is_dirty else
                  SC when is_unique_clean else
                  holder_state;
  req_final    <= SC when held else UC;
end architecture;

All three keep a dirty holder as SD (holder_final = SD when dirty), end the requester SC/UC (never SD/UD), and never assert writeback. That retained ownership is the whole point, and the DebugLab shows what breaks when the requester is made SD instead.

12. Verification View — requester never SD, ownership retained, no writeback

The properties that define ReadNotSharedDirty: the requester never becomes an owner, a dirty holder retains SD, and no writeback occurs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind to chi_readnsd_outcome.
// 1. The requester NEVER ends Shared Dirty or Unique Dirty — it is a reader, not an owner.
property p_req_never_owns_dirty;
  @(*) (req_final != 3'd4 /*SD*/) && (req_final != 3'd2 /*UD*/);
endproperty
 
// 2. A dirty holder RETAINS ownership — it ends SD, keeping the dirty data.
property p_owner_retained;
  @(*) ((holder_state == 3'd2 /*UD*/) || (holder_state == 3'd4 /*SD*/))
       |-> (holder_final == 3'd4 /*SD*/);
endproperty
 
// 3. This flow never forces a writeback — the dirty stays cached.
property p_no_writeback;
  @(*) writeback == 1'b0;
endproperty

The system point, beyond the checks:

There is a difference between holding dirty data and owning it. Owning means being the one cache obliged to write it back — and coherence requires exactly one owner per dirty line, no more and no fewer. ReadNotSharedDirty is built around not disturbing that count: it adds a reader (SC) and leaves the owner (SD) exactly where it was. So the requester must not end SD, and the holder must not shed its SD — either mistake changes the owner count. Zero owners and the dirty value has nothing obliged to save it; two owners and the writeback is duplicated or, worse, each defers to the other. The flow's correctness is the conservation of a single owner.

  • What it proves: the requester never owns dirty, a dirty holder retains SD, and no writeback occurs.
  • What it does not prove: the holder actually forwarded correct data — that is the snoop path (Chapter 7.4).
  • Bug signature: the requester ending SD — a second owner for one dirty line.

13. Testbench — every holder-state case

Drives each starting holder state and checks the snoop, the retained ownership, no writeback, and that the requester never owns dirty.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_chi_readnsd_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, SD = 3'd4;
 
  chi_readnsd_outcome dut (.*);
 
  task automatic check(input logic [2:0] hs, input logic exp_snoop,
                       input logic [2:0] exp_hf, exp_rf, input string name);
    holder_state = hs; #1;
    if (do_snoop !== exp_snoop || writeback !== 1'b0 ||
        holder_final !== exp_hf || req_final !== exp_rf ||
        req_final === SD || 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 hf=%0d rf=%0d", name, do_snoop, holder_final, req_final);
  endtask
 
  initial begin
    check(INV, 1'b0, INV, UC, "uncached  -> memory, req UC");
    check(SC,  1'b0, SC,  SC, "sharedCln -> join, req SC");
    check(UC,  1'b1, SC,  SC, "uniqueCln -> downgrade SC, req SC");
    check(UD,  1'b1, SD,  SC, "uniqueDty -> holder keeps SD, req SC");
    check(SD,  1'b1, SD,  SC, "sharedDty -> holder stays SD, 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 hf=0 rf=1
PASS sharedCln -> join, req SC: snoop=0 hf=3 rf=3
PASS uniqueCln -> downgrade SC, req SC: snoop=1 hf=3 rf=3
PASS uniqueDty -> holder keeps SD, req SC: snoop=1 hf=4 rf=3
PASS sharedDty -> holder stays SD, req SC: snoop=1 hf=4 rf=3
ALL TESTS PASSED

14. DebugLab — installing the requester as Shared Dirty

1

Installing the requester as Shared Dirty

RNSD INSTALLS REQUESTER AS SD -> TWO DIRTY OWNERS
Symptom

For lines that were dirty and then read-shared, one of two rare faults appears: the same value is written back twice (sometimes clobbering a newer write), or a modified value is silently lost after both sharers evict. It never happens for clean lines.

Evidence

The requester ended up an owner it never asked to be:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before: RN1 = UD (owner of dirty)
RN0 issues ReadNotSharedDirty -> HN SnpNotSharedDirty to RN1
  RN1: UD -> SD (still owner)   RN0 installed as SD   <-- wrong: two owners
after: RN0 = SD (owner?), RN1 = SD (owner?)  -> single-owner rule broken
case A: both evict -> two writebacks (later value clobbered)
case B: each defers to the other -> no writeback -> data lost

RNSD was supposed to leave one owner; it created two.

First Divergence

The flow installed the requester in SD instead of SC. From that point the dirty line had two caches claiming the owner role, so the count of owners — which must be exactly one — was wrong.

Root Cause

ReadNotSharedDirty exists precisely so the requester does not become the owner. Ownership of a dirty line must be held by exactly one cache; the flow adds a reader, not an owner. Installing the requester as SD manufactures a second owner, and two owners for one writeback duty is as broken as zero — duplication or mutual deferral. The requester's end state must be SC (clean-share), leaving the original holder as the sole SD, which is what the outcome logic's req_final never being SD encodes.

Fix

Install the requester in SC (or UC if it is the sole holder) — never SD. Leave the original holder as the single SD owner: UD → SD, or SD unchanged. The opcode's whole purpose is to add a clean reader without moving or duplicating ownership, so the requester ends clean-shared and exactly one cache continues to own the dirty line.

15. Common Mistakes

  • Installing the requester as SD. Assumption: forwarding dirty means owning it. Bug: two dirty owners (the DebugLab). Prevention: requester ends SC/UC, never SD.
  • Forcing a writeback. Assumption: dirty must go to memory. Bug: needless memory write (that is ReadClean). Prevention: RNSD keeps dirty cached; no writeback.
  • Stripping the holder's SD. Assumption: the holder must clean up. Bug: zero owners, dirty lost. Prevention: the holder retains SD.
  • Confusing SD with SC. Assumption: all shared copies are equal. Bug: mismanaged ownership. Prevention: SD owns the writeback duty; SC does not.
  • Choosing RNSD when you will write. Assumption: any read works. Bug: no write permission, immediate upgrade needed. Prevention: use ReadUnique to write.
  • Forgetting CompAck. Assumption: data ends the read. Bug: transaction never closes (Chapter 7.1). Prevention: close with CompAck.

16. Engineering Checklist

  • End the requester clean-sharedSC (or UC if sole) — never SD or UD.
  • Leave the dirty owner retained: UD → SD, SD → SD.
  • Force no writeback — the dirty stays cached in its owner.
  • Snoop non-shared-clean holders with SnpNotSharedDirty; skip SC / uncached.
  • Keep exactly one dirty owner — the original holder, not the requester.
  • Close with CompAck; use ReadUnique if you actually intend to write.

17. Key Takeaways

  • Shared Dirty (SD) is a shared copy that also owns the dirty-writeback duty — one owner per line.
  • ReadNotSharedDirty read-shares a line while refusing the SD owner role.
  • A dirty holder retains ownership — UD → SD, SD → SD — and forwards a clean copy; no writeback.
  • The requester ends clean-shared (SC/UC), never SD or UD.
  • Installing the requester as SD creates two owners — duplicated writeback or lost data.
  • It sits between ReadShared (may take SD) and ReadClean (writes back); the model here is representative.

18. Quick Revision

ReadNotSharedDirty flow. RNSD read-shares a line while declining the Shared Dirty (SD) owner role — SD being a shared copy that also owns the duty to write the line back, of which there must be exactly one per dirty line. The requester ends clean-shared (SC, or UC if sole), never SD or UD. When a holder has dirty data, that holder retains ownership — UD → SD, SD stays SD — and forwards a clean copy via SnpNotSharedDirty; no writeback to memory occurs, because the dirty value stays cached in its owner. That distinguishes it from ReadShared (which may make the requester the SD owner) and ReadClean (which writes the dirty data back, cleaning memory). The failure to avoid: installing the requester as SD, which creates two owners for one line — either a duplicated writeback that clobbers a newer value, or mutual deferral that loses the data. Requester SC, holder keeps SD, no writeback, one owner. Representative model; 8.5 turns to writes with the WriteUnique flow.

Coming Next

Chapter 8.5 — WriteUnique Flow. The read family is complete; Module 8 now turns to writes. Chapter 8.5 walks WriteUnique — a write that does not assume the writer already holds the line cached. It shows how the home invalidates other copies, grants the DBID (Chapter 7.2), and lands the new data, all in one flow — the write counterpart to ReadUnique, and the natural first write to study now that you have the full set of reads.