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
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 state | Home action | Snoop | Writeback | Requester ends |
|---|---|---|---|---|
| Uncached (I) | fetch from memory | none | no | SC |
| Shared (SC) | return data, join sharers | none | no | SC |
| Unique Clean (UC) | downgrade holder | SnpShared | no | SC |
| Unique Dirty (UD) | downgrade holder, preserve data | SnpShared | yes | SC |
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.
- REQ. RN0 sends ReadShared, allocating a tracker. It wants a readable copy — SC.
- 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.
- SnpShared. HN sends SnpShared to RN1. RN1 downgrades UD → SC and, because it was dirty, returns the line as SnpRespData (plus SnpResp).
- 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.
- 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.
// 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;
endmoduleThe same behavior in Verilog-2001:
// 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;
endmoduleAnd in VHDL:
-- 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 SC — holder_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.
// 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;
endpropertyThe 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.
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
endmoduleExpected output:
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 PASSED14. DebugLab — a ReadShared that snoops SnpUnique
A ReadShared that snoops SnpUnique
READSHARED SNOOPS SNPUNIQUE -> HOLDER INVALIDATED, SHARING BROKENShared 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.
A ReadShared invalidated the holder:
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 staleRN1 lost a copy it should have kept; the read behaved like a write-intent transaction.
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.
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.
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 SnpShared — downgrade, 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.