Skip to content

AMBA CHI · Module 8 · Request Flows

ReadShared Flow

Module 7 built the transaction model in general; Module 8 walks concrete named flows, and ReadShared is the first. A core issues a ReadShared to fetch a line it only intends to read, and the flow always ends the same way: the requester installs the line in Shared Clean, alongside any other readers. That is the property that defines it — a ReadShared never invalidates a holder, it downgrades one. A unique or dirty holder is snooped down to Shared Clean, its dirty data preserved; an uncached line is fetched from memory; an already-shared line just gains one more reader, so readers coexist. Learn the three cases and why ReadShared downgrades rather than invalidates. Representative model, not the specification.

Intermediate15 min readAMBA CHIReadSharedShared CleanSnoopCoherency

Module 8 · Chapter 8.1 · Request Flows

Project thread — Module 7 built the transaction model. Module 8 walks named flows in full. This chapter is ReadShared; 8.2 is ReadUnique — the two are best understood as a contrast.

1. Learning Outcomes

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

  • Trace the ReadShared flow to its end state — the requester in Shared Clean (SC).
  • State the defining property: ReadShared downgrades, it never invalidates a holder.
  • Distinguish the three cases by holder state — uncached, shared, unique/dirty.
  • Explain how a unique/dirty holder is snooped to SC and its dirty data preserved.
  • Diagnose why snooping a ReadShared as SnpUnique breaks sharing.
  • Implement a representative ReadShared outcome in SystemVerilog, Verilog-2001, and VHDL.

2. Why Should I Learn This?

ReadShared is the most common transaction on a coherent fabric — every ordinary load of shared data is one. It is also the cleanest illustration of what "shared" means in hardware: many caches holding the same line at once, all reading, none writing. If you cannot trace a ReadShared, you cannot reason about the steady state of a multi-core system.

It earns its own chapter because its behavior is defined by a restraint: it takes a copy without taking it away from anyone. A holder is downgraded, not evicted; sharers coexist. That restraint is the difference between ReadShared and ReadUnique, and getting it wrong — invalidating where you should downgrade — quietly destroys sharing and can leave a requester believing it owns a line it only borrowed. This is the flow where the sharing half of coherence lives.

3. Key Terms

4. Previous Chapter Connection

Chapter 7.1 walked the general read — request, resolve by snoop or memory, CompData, CompAck — and Chapter 7.4 showed the snoop that resolves it against a holder. This chapter makes it concrete for one opcode: ReadShared.

The general read left the end state open; ReadShared pins it down. The requester ends in SC, and — the point of the chapter — the flow reaches that end without invalidating anyone. It uses SnpShared (Chapter 7.4), which downgrades a holder to SC rather than clearing it. So this is the read of Module 7, specialized to the sharing case: everyone who wants to read, can, at the same time.

5. Core Concept — fetch into Shared Clean, without invalidating

A ReadShared brings a line to a requester in Shared Clean (SC) — readable, not writable — and leaves every existing holder still able to read. The flow varies by what the directory finds, but the end state is invariant.

  • Uncached. No cache holds the line. The home fetches it from memory (SN) and returns CompData; the requester installs SC. No snoop, no writeback.
  • Held shared. One or more caches hold the line in SC. The home returns the data (from memory or a sharer) and the requester joins as another SC holder. Existing sharers are untouched.
  • Held unique or dirty. A cache holds the line in UC or UD. The home sends SnpShared, which downgrades that holder to SC; if it was dirty (UD), the holder returns the data as SnpRespData and the home writes it back. The requester installs SC; the former unique holder is now a co-sharer.

Across all three, two things never happen: no holder is invalidated, and the requester never gets write permission. The requester ends in SC; so does any downgraded holder.

The synthesis:

ReadShared fetches a line into SC and lets readers coexist. It resolves by memory fetch, by joining existing sharers, or by downgrading a unique/dirty holder with SnpShared — never by invalidating. Dirty data is preserved on the downgrade. The invariant that names the flow: ReadShared downgrades, it does not invalidate, and every participant ends readable, not writable.

6. Engineering Mental Model — a reference book others can also read

Think of a shared reference book in an office.

  • You want to read it, not take it home. If no one has it, you fetch a copy from the archive (memory) and read it (SC).
  • If colleagues are already reading their own copies, you simply make another copy and read alongside them — nobody gives theirs up (coexisting sharers).
  • If one colleague had checked it out exclusively and scribbled notes (unique, dirty), they do not have to surrender it — they just downgrade to read-only, hand back a copy of their annotated version (the dirty data), and keep reading. Now you both read the same up-to-date text.

Nobody is ever forced to give the book up for a read. That is ReadShared: a copy for you, without taking anyone else's away. Forcing a colleague to surrender their copy just so you can read would be needless — and it is exactly the bug at the end of this chapter.

7. Engineering Diagram — ReadShared with a dirty holder

A ReadShared flow where RN1 holds the line dirty. RN0 sends a REQ ReadShared to the Home Node. The Home Node sends a SNP SnpShared to RN1. RN1 downgrades from Unique Dirty to Shared Clean and returns a RSP SnpResp and a DAT SnpRespData carrying the dirty data. The Home Node returns a DAT CompData to RN0, which installs Shared Clean. RN0 sends a RSP CompAck. Both RN0 and RN1 end in Shared Clean, coexisting; neither copy is invalidated.ReadShared — downgrade to SC, both cores coexistRN0 · requester (I)HN · homeRN1 · holder (UD)REQ: ReadSharedSNP: SnpSharedDAT: SnpRespData (UDto SC)DAT: CompData(install SC)RSP: CompAck
Figure 1 — a ReadShared where RN1 holds the line dirty (UD). RN0 requests on REQ; the Home Node snoops RN1 with SnpShared, which downgrades it from UD to SC; RN1 returns its dirty data as SnpRespData; the Home Node returns CompData to RN0, which installs SC, and writes the dirty data back. Both cores end in Shared Clean — coexisting readers. No copy is invalidated.

Read top to bottom: request, a downgrading snoop, dirty data returned, completion. RN1 went UD → SC; RN0 went I → SC. Two readers, coexisting, no invalidation.

8. The Three Cases by Holder State

The flow's middle depends entirely on what the directory finds.

Holder stateHome actionSnoopWritebackRequester ends
Uncached (I)fetch from memorynonenoSC
Shared (SC)return data, join sharersnonenoSC
Unique Clean (UC)downgrade holderSnpSharednoSC
Unique Dirty (UD)downgrade holder, preserve dataSnpSharedyesSC

The rule to carry: the requester always ends in SC, and the only thing that varies is how the home sources the data and whether it must snoop. A snoop appears only when a holder had unique permission (UC/UD), and it is always a SnpShared — a downgrade. A writeback appears only when the snooped line was dirty. No row invalidates a holder.

9. ReadShared Never Invalidates — the defining property

This is the property worth isolating, because it is what separates ReadShared from ReadUnique (Chapter 8.2).

  • Downgrade, not invalidate. A unique holder becomes a sharer (SC), keeping a valid copy. It loses only write permission, not the line.
  • Sharers are untouched. Existing SC holders stay exactly as they were; the requester is simply added to the sharer set.
  • The requester gets read permission only. SC is not writable. To write, a core must later obtain unique ownership (a ReadUnique or MakeUnique) — a ReadShared never grants that.
  • Coexistence is the goal. The whole point is many readers at once. Invalidating a holder to satisfy a read would defeat the purpose and thrash the line.

The point to carry:

ReadShared is defined by what it will not do: it will not invalidate, and it will not grant write permission. It downgrades unique holders to SC and adds the requester as one more reader, so the line can be read by many caches at once. Every read-sharing property of the system rests on this restraint — break it and either sharing collapses (needless invalidation) or a reader silently gains write rights it was never granted.

10. Flow Walkthrough — ReadShared, dirty holder

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

  1. REQ. RN0 sends ReadShared, allocating a tracker. It wants a readable copy — SC.
  2. Directory lookup. HN sees RN1 in UD. A holder has it uniquely and dirty; the home must downgrade it and rescue the dirty data — but not invalidate it.
  3. SnpShared. HN sends SnpShared to RN1. RN1 downgrades UD → SC and, because it was dirty, returns the line as SnpRespData (plus SnpResp).
  4. CompData + writeback. HN returns CompData to RN0, which installs SC, and writes the dirty data back to memory. The directory now lists RN0 and RN1 both in SC.
  5. CompAck. RN0 closes with CompAck.

End state: two co-sharers, RN0 and RN1, both SC; memory clean; nobody invalidated; nobody writable. Contrast the next chapter, where the same starting point under ReadUnique would send RN1 to I.

11. RTL / Hardware View — ReadShared outcome logic

The ReadShared flow's decisions reduce to a small combinational function of the current holder state: does the home snoop, must it write back, and what states result. Representative — and note the requester always ends SC and the holder is never invalidated.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadShared outcome logic (educational).
// From the current holder state, decide: snoop needed? writeback? resulting states.
// Invariants of ReadShared: the requester ALWAYS ends in SC, and a holder is NEVER
// invalidated -- a unique/dirty holder is DOWNGRADED to SC (via SnpShared).
module chi_readshared_outcome (
  input  logic [2:0] holder_state,   // INV, SC, UC, UD
  output logic       do_snoop,       // SnpShared to the holder?
  output logic       writeback,      // snooped dirty data written back?
  output logic [2:0] holder_final,   // holder's state after the flow
  output logic [2:0] req_final       // requester's state after the flow
);
  localparam logic [2:0] INV = 3'd0, SC = 3'd3, UC = 3'd1, UD = 3'd2;
 
  logic is_unique, is_dirty;
  assign is_unique = (holder_state == UC) || (holder_state == UD);
  assign is_dirty  = (holder_state == UD);
 
  // A unique holder must be snooped (downgraded); shared/uncached needs none.
  assign do_snoop  = is_unique;
  // Only a dirty snooped line is written back.
  assign writeback = is_dirty;
  // Downgrade a unique holder to SC; leave shared/uncached holders as they were.
  assign holder_final = is_unique ? SC : holder_state;
  // ReadShared always installs the requester in SC -- read-only, shareable.
  assign req_final    = SC;
endmodule

The same behavior in Verilog-2001:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ReadShared outcome logic (Verilog-2001).
module chi_readshared_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 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    = SC;
endmodule

And in VHDL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Representative ReadShared outcome logic (VHDL).
library ieee;
use ieee.std_logic_1164.all;
 
entity chi_readshared_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_readshared_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 is_unique, is_dirty : boolean;
begin
  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;
end architecture;

All three end the requester in SC and, for a unique/dirty holder, downgrade it to SCholder_final is never INV. That "never INV" is the ReadShared invariant, and the DebugLab shows what breaks when the flow violates it.

12. Verification View — ends SC, never invalidates

The two properties that define ReadShared: the requester ends shareable, and no holder is invalidated.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind to chi_readshared_outcome.
// 1. The requester always ends in Shared Clean — never a writable state.
property p_req_ends_shared;
  @(*) req_final == 3'd3 /*SC*/;
endproperty
 
// 2. ReadShared NEVER invalidates a holder — a holder is downgraded, not cleared.
property p_never_invalidates;
  @(*) (holder_state != 3'd0 /*INV*/) |-> (holder_final != 3'd0 /*INV*/);
endproperty
 
// 3. A dirty holder's data is preserved via writeback on the downgrade.
property p_dirty_writeback;
  @(*) (holder_state == 3'd2 /*UD*/) |-> writeback;
endproperty

The system point, beyond the checks:

ReadShared's correctness is a pair of restraints, and both are permissions. The requester ends in SC, which grants reading but not writing — so a ReadShared can never let a core silently modify shared data. And a holder ends no lower than SC — it keeps a readable copy, so a read never destroys another cache's ability to read. Coherence for shared data is precisely the coexistence of many readers with no writer; ReadShared is the operation that adds a reader while preserving that invariant. Grant write permission, or invalidate a holder, and you have written a different transaction — ReadUnique — under ReadShared's name.

  • What it proves: the requester ends SC, no holder is invalidated, dirty data is written back.
  • What it does not prove: ordering against conflicting transactions — that is the home FSM (Chapter 7.6).
  • Bug signature: a holder ending in INV on a ReadShared — sharing destroyed, or write permission wrongly implied.

13. Testbench — every holder-state case

Drives each starting holder state and checks the snoop, writeback, and resulting states — especially that no holder is invalidated.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_chi_readshared_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_readshared_outcome dut (.*);
 
  task automatic check(input logic [2:0] hs, input logic exp_snoop, exp_wb,
                       input logic [2:0] exp_hf, input string name);
    holder_state = hs; #1;
    if (do_snoop !== exp_snoop || writeback !== exp_wb ||
        holder_final !== exp_hf || req_final !== SC) 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", name, do_snoop, writeback, holder_final);
  endtask
 
  initial begin
    check(INV, 1'b0, 1'b0, INV, "uncached  -> memory, req SC");
    check(SC,  1'b0, 1'b0, SC,  "shared    -> join, req SC");
    check(UC,  1'b1, 1'b0, SC,  "unique    -> downgrade SC, req SC");
    check(UD,  1'b1, 1'b1, SC,  "uniqueDty -> downgrade SC + 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 SC: snoop=0 wb=0 hf=0
PASS shared    -> join, req SC: snoop=0 wb=0 hf=3
PASS unique    -> downgrade SC, req SC: snoop=1 wb=0 hf=3
PASS uniqueDty -> downgrade SC + wb, req SC: snoop=1 wb=1 hf=3
ALL TESTS PASSED

14. DebugLab — a ReadShared that snoops SnpUnique

1

A ReadShared that snoops SnpUnique

READSHARED SNOOPS SNPUNIQUE -> HOLDER INVALIDATED, SHARING BROKEN
Symptom

Shared read-mostly data thrashes — cores keep re-fetching a line they should all be holding — and, more rarely, a core reads stale data after another core writes a line everyone thought was shared. It only appears when a line is actively read-shared across cores.

Evidence

A ReadShared invalidated the holder:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before: RN0 = I,  RN1 = SC (happily sharing)
RN0 issues ReadShared -> HN sends SnpUnique to RN1   <-- wrong snoop type
RN1: SC -> I (invalidated), returns response
after:  RN0 = SC (or worse, treated as unique),  RN1 = I
RN1 must re-fetch to read again -> thrash
if RN0 is treated as unique: RN0 writes silently -> other readers stale

RN1 lost a copy it should have kept; the read behaved like a write-intent transaction.

First Divergence

The flow selected the snoop opcode by habit or by copying the ReadUnique path, emitting SnpUnique where ReadShared requires SnpShared. From that point the holder was invalidated instead of downgraded, and the sharing set collapsed to one.

Root Cause

ReadShared must downgrade, not invalidate. Its whole purpose is coexisting readers, so it snoops with SnpShared (holder UC/UD → SC), never SnpUnique (holder → I). Invalidating on a read defeats sharing and, if the directory then records a single owner, can imply write permission the read never granted — the exact coherence hazard ReadShared's restraint exists to avoid. The holder_final in the outcome logic is never INV precisely to encode this.

Fix

Issue SnpShared for a ReadShared, downgrading the holder to SC so it remains a reader. Reserve SnpUnique (and invalidation) for write-intent flows — ReadUnique (Chapter 8.2). Keep the invariant the outcome logic encodes: on a ReadShared, a holder ends at worst in SC, never in I, and the requester ends readable, not writable.

15. Common Mistakes

  • Snooping SnpUnique on a ReadShared. Assumption: any snoop clears the way. Bug: sharing destroyed (the DebugLab). Prevention: ReadShared uses SnpShared.
  • Installing the requester as writable. Assumption: a fetched line can be written. Bug: silent write on a shared line. Prevention: ReadShared ends SC; write needs ReadUnique.
  • Dropping dirty data on the downgrade. Assumption: a downgrade needs no data. Bug: lost dirty value (Chapter 6.5). Prevention: SnpRespData + writeback for a UD holder.
  • Snooping when the line is only shared. Assumption: every case snoops. Bug: needless snoops / latency. Prevention: snoop only unique holders (UC/UD).
  • Invalidating existing sharers. Assumption: the requester must be sole reader. Bug: thrash. Prevention: sharers coexist; just add the requester.
  • Forgetting CompAck. Assumption: data ends the read. Bug: transaction never closes (Chapter 7.1). Prevention: close with CompAck.

16. Engineering Checklist

  • End the requester in SC — read-only, shareable.
  • Snoop a unique/dirty holder with SnpShareddowngrade, never invalidate.
  • Preserve dirty data via SnpRespData + writeback when the holder was UD.
  • Do not snoop when the line is uncached or only shared.
  • Let existing sharers coexist — just add the requester.
  • Close with CompAck; obtain write permission via ReadUnique, not ReadShared.

17. Key Takeaways

  • ReadShared fetches a line into Shared Clean (SC) — readable, not writable.
  • Its defining property: it downgrades holders (SnpShared, UC/UD → SC) and never invalidates them.
  • Three cases by holder state — uncached (memory), shared (join), unique/dirty (downgrade, writeback if dirty).
  • Multiple readers coexist in SC; that coexistence is the whole point of read-sharing.
  • Snooping a ReadShared as SnpUnique invalidates a holder and breaks sharing — a coherence hazard.
  • End SC, downgrade with SnpShared, preserve dirty data; the model here is representative.

18. Quick Revision

ReadShared flow. A ReadShared fetches a line into Shared Clean (SC) — the requester ends readable, not writable — and its defining restraint is that it downgrades, never invalidates. Three cases by what the directory finds: uncached → the home fetches from memory, requester installs SC; already shared → the requester joins the sharer set, no snoop; held unique/dirty (UC/UD) → the home sends SnpShared, downgrading the holder to SC, and if it was dirty the holder returns SnpRespData and the home writes back. In every case the requester ends SC and no holder is invalidated — sharers coexist, which is what read-sharing means. Snoop a ReadShared with SnpUnique instead of SnpShared and you invalidate a holder, thrash sharing, and can imply write permission the read never granted. End SC, downgrade with SnpShared, preserve dirty data. Representative model; 8.2 is the ReadUnique flow — the same start, but it invalidates.

Coming Next

Chapter 8.2 — ReadUnique Flow. ReadShared took a copy without taking anyone else's; ReadUnique is its opposite. Chapter 8.2 walks the flow a core uses to fetch a line it intends to write — how the home issues SnpUnique to invalidate every other copy, gathers any dirty data, and grants the requester the line in a Unique state it may modify. Read the two flows as a pair: same request shape, opposite effect on other caches — downgrade versus invalidate, share versus own.