AMBA CHI · Module 9 · Snoop Flows
Snoop Responses
Chapter 9.2 catalogued the snoops the home sends; this one covers what comes back. A snoop response is the home's only window into what the snooped cache did, and it encodes three things: the state the cache ended in, whether it returned the line's data, and whether dirty responsibility passed with that data. The home reads all three — updating its directory to the reported state and deciding who must write the line back from the PassDirty bit. That bit is where snoop responses get sharp: a cache giving up a dirty line must set PassDirty, or the recipient treats the returned data as clean and discards it, silently losing the modification. Representative model, not the specification.
Intermediate16 min readAMBA CHISnoop ResponsePassDirtySnpRespDataCoherency
Module 9 · Chapter 9.3 · Snoop Flows
Project thread — 9.2 named the snoops the home sends. This chapter reads what comes back — the response and its PassDirty bit. 9.4 focuses on snoop-driven invalidation.
1. Learning Outcomes
By the end of this chapter you should be able to:
- State that a snoop response encodes three things: final state, data or not, and PassDirty.
- Distinguish SnpResp (no data) from SnpRespData (returns the line).
- Explain that the response is the home's only window into what the snooped cache did.
- Define PassDirty — whether dirty responsibility moves to the recipient.
- Diagnose why returning dirty data with PassDirty clear loses the modification.
- Implement a representative snoop-response encoder in SystemVerilog, Verilog-2001, and VHDL.
2. Why Should I Learn This?
A snoop is a round trip: the home sends a request, and the snooped cache replies. The home cannot see inside that cache — it learns what happened only from the reply. So the response is not a formality; it is the sole channel by which the home updates its directory, forwards data, and decides who owes a writeback. Read it wrong, or encode it wrong, and the home's model of the system diverges from reality.
The load-bearing field is PassDirty — the one bit that says whether the responsibility for a dirty line moved along with its data. Coherence requires that a dirty line have exactly one owner obliged to write it back; the snoop response is where that ownership is handed off, and PassDirty is how. Get it wrong and dirty data ends up owned by no one — returned but marked clean, then discarded. This is the flow where a single bit decides whether a modification survives.
3. Key Terms
4. Previous Chapter Connection
Chapter 9.2 defined each snoop by two axes — the state it forces and the data it requires. The snooped cache carries those out and then must report back: this chapter is that report.
The response mirrors the request's axes and adds one field. It states the final state the cache reached (the state axis, confirmed), whether it returned data (the data axis, answered), and — new here — PassDirty, which resolves who owns the dirty line now. The request said what to do; the response says what was done and who holds the dirty responsibility afterward. The home relies entirely on this reply to finish the transaction the snoop served.
5. Core Concept — the response is what the home reads
A snoop response is the snooped cache's reply, and it carries three pieces of information the home acts on.
- Final state. The Resp state field reports the state the cache ended in — I, SC, UC, UD, or SD. The home updates its directory to match, so it knows the cache's new status.
- Data or not. SnpResp (on RSP) carries no data — the line was clean or not needed. SnpRespData (on DAT) returns the line — because the cache held it dirty, or the snoop asked for it.
- PassDirty. When the response carries dirty data, PassDirty says whether the dirty responsibility moves to the recipient. Set: the recipient (home or requester) now owns the writeback. Clear: the responder keeps it (it stayed a dirty owner, SD/UD).
- The home's only window. The home cannot inspect the cache; it acts solely on these fields — updating the directory, forwarding data to the requester, and deciding writeback from PassDirty.
The synthesis:
A snoop response encodes final state, data-or-not, and PassDirty, and it is the home's only source of truth about what the snooped cache did. SnpResp reports state with no data; SnpRespData returns the line. PassDirty hands off dirty ownership — set it when giving the dirty line up, clear it when keeping it. The home reads all three to complete the transaction.
6. Engineering Mental Model — the reply slip
The library recall again — but now watch the reader's reply slip come back to the librarian.
- The slip states what the reader did with their copy: "kept a read-only copy" (SC), or "surrendered it" (I). The librarian updates the checkout log to match.
- It says whether pages are attached: a plain slip (SnpResp) means nothing enclosed; a slip with the annotated pages (SnpRespData) means the reader returned their marked-up version.
- And it says who now owns the edits: "you take these edits" (PassDirty set — the librarian must now file them) or "I'm keeping and will file them myself" (PassDirty clear — the reader stays the owner).
The librarian acts only on the slip — it never sees the reader's desk. So if a reader hands over annotated pages but ticks "these are just a clean copy" (PassDirty clear) and keeps no copy, the edits arrive but no one is marked responsible for filing them — and they are eventually thrown out. The slip's ownership box is the load-bearing part.
7. Engineering Diagram — a snoop response passing dirty
Read the third message closely: it carries all three fields — final state I, the data itself, and PassDirty set. Because PassDirty is set, RN0 installs the line UD (dirty owner). Clear PassDirty and RN0 would install UC (clean) — and the modification would eventually be lost.
8. What a Snoop Response Encodes
The response fields, and what the home does with each.
| Response | Carries data | Reports state | PassDirty | Home action |
|---|---|---|---|---|
| SnpResp (RSP) | no | I / SC / UC | — | update directory; no data to move |
| SnpRespData (DAT), clean | yes | SC / UC | clear | forward clean data; no writeback owed |
| SnpRespData (DAT), dirty | yes | I / SC | set | forward data; recipient owns writeback |
| SnpRespData, keep-owner | yes | SD | clear | forward a copy; responder stays owner |
The rule to carry: the response is read as three fields at once. State updates the directory; data-or-not tells the home whether a line arrived; PassDirty decides who owes the writeback. The trap row is the third versus the fourth: both return dirty data, but one passes ownership (PassDirty set, responder went non-owning) and one keeps it (PassDirty clear, responder stayed SD). Getting that bit right is the whole game.
9. The PassDirty Bit — who owns the dirty data now
PassDirty deserves its own section because coherence turns on it.
- Exactly one owner, always. A dirty line must have one cache obliged to write it back — no more, no fewer (Chapter 8.4).
- A snoop can move the owner. When a dirty holder gives up the line (goes to I or a clean state) and returns the data, the ownership must move to the recipient — signalled by PassDirty set.
- Or keep the owner. When the holder stays a dirty owner (SD) and forwards only a copy, ownership does not move — PassDirty clear.
- The rule. PassDirty must be set exactly when the responder gives up dirty ownership while returning dirty data. Set it and the recipient takes the writeback duty (installs UD, or the home writes back). Fail to set it and the dirty data is returned but marked as clean — owned by no one.
The point to carry:
PassDirty is the handoff signal for dirty ownership. A dirty line's data can move freely, but its responsibility must be transferred deliberately, and PassDirty is the single bit that does it. The invariant is conservation: before the snoop, one cache owned the dirty line; after, exactly one entity must — the responder (kept it, PassDirty clear) or the recipient (took it, PassDirty set). Return dirty data with PassDirty clear while giving up your copy, and ownership evaporates: the data exists but nothing is obliged to preserve it.
10. Walkthrough — a SnpUnique response that passes dirty
RN0's ReadUnique targets a line RN1 holds in UD.
- Snoop. HN sends SnpUnique to RN1 — invalidate and forward.
- RN1 acts. RN1 transitions UD → I (gives up the line) and prepares to return its dirty data.
- Encode the response. RN1 sends SnpRespData with: Resp state = I (its new state), the data, and PassDirty = set — because it gave up dirty ownership while returning dirty data.
- Home reads it. HN updates the directory: RN1 is now I. It sees PassDirty set, so it forwards the dirty data to RN0 marked dirty.
- RN0 installs UD. RN0 installs the line UD — the new dirty owner, obliged to write it back later.
Ownership conserved: RN1 owned the dirty line before; RN0 owns it after; PassDirty carried the handoff. Had RN1 sent the same data with PassDirty clear, RN0 would install UC (clean) — and on eviction drop the modification silently. That is the DebugLab.
11. RTL / Hardware View — a snoop-response encoder
The snooped node builds its response from what it did: report the final state, include data if returning it, and set PassDirty exactly when it gives up dirty ownership. Representative.
// Representative snoop-response encoder (educational).
// The node reports its FINAL state, whether it returns DATA, and PassDirty.
// PassDirty is set exactly when the node returns dirty data AND gives up dirty
// ownership (final state is not a dirty-owning state UD/SD) -- handing the
// writeback duty to the recipient.
module chi_snoop_resp_encode (
input logic [2:0] final_state, // state after the snoop: INV, UC, UD, SC, SD
input logic was_dirty, // the node held the line dirty before the snoop
input logic returns_data, // the response carries the line (SnpRespData)
output logic [2:0] resp_state, // reported final state
output logic is_data_resp, // SnpRespData (1) vs SnpResp (0)
output logic pass_dirty // dirty responsibility passes to the recipient
);
localparam logic [2:0] INV = 3'd0, UC = 3'd1, UD = 3'd2, SC = 3'd3, SD = 3'd4;
// Report the state truthfully; the home updates its directory from it.
assign resp_state = final_state;
assign is_data_resp = returns_data;
// The node gives up dirty ownership if it was dirty and did NOT stay a dirty owner.
logic gives_up_dirty;
assign gives_up_dirty = was_dirty && (final_state != UD) && (final_state != SD);
// Pass dirty exactly when giving it up while returning the data.
assign pass_dirty = gives_up_dirty && returns_data;
endmoduleThe same behavior in Verilog-2001:
// Representative snoop-response encoder (Verilog-2001).
module chi_snoop_resp_encode (
input [2:0] final_state,
input was_dirty,
input returns_data,
output [2:0] resp_state,
output is_data_resp,
output pass_dirty
);
localparam INV = 3'd0, UC = 3'd1, UD = 3'd2, SC = 3'd3, SD = 3'd4;
wire gives_up_dirty = was_dirty && (final_state != UD) && (final_state != SD);
assign resp_state = final_state;
assign is_data_resp = returns_data;
assign pass_dirty = gives_up_dirty && returns_data;
endmoduleAnd in VHDL:
-- Representative snoop-response encoder (VHDL).
library ieee;
use ieee.std_logic_1164.all;
entity chi_snoop_resp_encode is
port (
final_state : in std_logic_vector(2 downto 0);
was_dirty : in std_logic;
returns_data : in std_logic;
resp_state : out std_logic_vector(2 downto 0);
is_data_resp : out std_logic;
pass_dirty : out std_logic
);
end entity;
architecture rtl of chi_snoop_resp_encode is
constant UD : std_logic_vector(2 downto 0) := "010";
constant SD : std_logic_vector(2 downto 0) := "100";
signal gives_up_dirty : std_logic;
begin
gives_up_dirty <= '1' when (was_dirty = '1' and final_state /= UD and final_state /= SD)
else '0';
resp_state <= final_state;
is_data_resp <= returns_data;
pass_dirty <= '1' when (gives_up_dirty = '1' and returns_data = '1') else '0';
end architecture;All three report the final state truthfully and set pass_dirty exactly when the node returns dirty data while relinquishing ownership. The DebugLab shows the data loss when that bit is left clear.
12. Verification View — PassDirty conserves the owner
The properties that keep the ownership handoff sound: pass dirty when giving it up, keep it clear when retaining, and carry the data when passing.
// Bind to chi_snoop_resp_encode.
// 1. Giving up dirty ownership while returning dirty data MUST set PassDirty.
property p_pass_when_giving_up;
@(*) (was_dirty && (final_state != 3'd2 /*UD*/) && (final_state != 3'd4 /*SD*/)
&& returns_data) |-> pass_dirty;
endproperty
// 2. Staying a dirty owner (UD/SD) must NOT pass dirty — the responder keeps it.
property p_keep_owner_no_pass;
@(*) ((final_state == 3'd2 /*UD*/) || (final_state == 3'd4 /*SD*/)) |-> !pass_dirty;
endproperty
// 3. If PassDirty is set, the response must carry the data being handed off.
property p_pass_implies_data;
@(*) pass_dirty |-> is_data_resp;
endpropertyThe system point, beyond the checks:
The snoop response is where data movement and ownership movement are separately declared, and coherence needs both stated correctly. The data fields say where the bytes went; PassDirty says where the duty went. They are independent: a response can carry data without passing ownership (a copy to a reader while the owner stays SD) or pass ownership with the data (a dirty holder invalidating). The home, blind to the cache, reconstructs the whole picture from these fields — so a wrong PassDirty does not merely misreport, it misassigns responsibility, and a dirty line with no owner is a modification with no future. One bit carries the entire handoff.
- What it proves: PassDirty is set exactly when ownership is relinquished with data, clear when retained, and implies data.
- What it does not prove: the recipient actually installed UD / wrote back — that is the recipient's handling.
- Bug signature: dirty data returned while giving up the line, but PassDirty clear — an ownerless dirty line.
13. Testbench — the response fields across cases
Drives the node's action and checks the reported state, data flag, and PassDirty.
module tb_chi_snoop_resp_encode;
logic [2:0] final_state, resp_state;
logic was_dirty, returns_data, is_data_resp, pass_dirty;
int errors = 0;
localparam INV = 3'd0, UC = 3'd1, UD = 3'd2, SC = 3'd3, SD = 3'd4;
chi_snoop_resp_encode dut (.*);
task automatic check(input logic [2:0] fs, input logic wd, rd,
input logic exp_pd, input string name);
final_state = fs; was_dirty = wd; returns_data = rd; #1;
if (resp_state !== fs || is_data_resp !== rd || pass_dirty !== exp_pd) begin
errors++; $display("FAIL %s: state=%0d data=%0b pd=%0b", name, resp_state, is_data_resp, pass_dirty);
end else $display("PASS %s: state=%0d data=%0b pd=%0b", name, resp_state, is_data_resp, pass_dirty);
endtask
initial begin
// Dirty holder invalidated, returns data -> PassDirty set.
check(INV, 1'b1, 1'b1, 1'b1, "UD->I, data -> PassDirty");
// Dirty holder downgraded to SC, returns data -> PassDirty set (gave up owner).
check(SC, 1'b1, 1'b1, 1'b1, "UD->SC, data -> PassDirty");
// Dirty holder keeps SD (owner retained), forwards copy -> PassDirty clear.
check(SD, 1'b1, 1'b1, 1'b0, "UD->SD, data -> keep owner, no PD");
// Clean holder downgraded, no data -> no PassDirty.
check(SC, 1'b0, 1'b0, 1'b0, "SC->SC, no data -> no PD");
// Clean holder invalidated, no data -> no PassDirty.
check(INV, 1'b0, 1'b0, 1'b0, "SC->I, no data -> no PD");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS UD->I, data -> PassDirty: state=0 data=1 pd=1
PASS UD->SC, data -> PassDirty: state=3 data=1 pd=1
PASS UD->SD, data -> keep owner, no PD: state=4 data=1 pd=0
PASS SC->SC, no data -> no PD: state=3 data=0 pd=0
PASS SC->I, no data -> no PD: state=0 data=0 pd=0
ALL TESTS PASSED14. DebugLab — returning dirty data with PassDirty clear
Returning dirty data with PassDirty clear
DIRTY DATA RETURNED WITH PASSDIRTY CLEAR -> MODIFICATION LOST ON EVICTIONA modified value is silently lost — a later read returns stale data — but only after the line has been snooped away from one cache to another and then evicted. Keeping the line resident hides it; the loss appears on eviction.
Dirty data arrived marked clean:
RN1 = UD (value = NEW), memory = OLD
HN SnpUnique to RN1 -> RN1: UD -> I, returns SnpRespData(NEW) with PassDirty = 0 <-- wrong
HN forwards CompData(NEW) to RN0 marked CLEAN
RN0 installs UC (clean), value = NEW, but believes memory has it
later: RN0 evicts UC -> silent Evict, NO writeback
memory still = OLD, NEW discarded -> lost modificationThe data moved correctly; the ownership did not — PassDirty was clear, so no one wrote it back.
RN1 returned dirty data while going to Invalid — giving up ownership — but with PassDirty clear. From that point the recipient treated the line as clean, and the dirty responsibility that should have moved with the data simply vanished.
PassDirty is the ownership handoff, and it must be set when dirty ownership is relinquished with the data. RN1 gave up the line (I) but signalled "clean," so RN0 installed UC and assumed memory was current. The dirty line now had no owner: RN1 discarded its copy, RN0 believed it was clean, and memory was stale. A dirty line with no owner is a modification nothing is obliged to preserve — lost at the next silent eviction. The data path was correct; the ownership bit was not.
Set PassDirty whenever the node returns dirty data while relinquishing dirty ownership — exactly pass_dirty = gives_up_dirty && returns_data. Then the home marks the forwarded data dirty, the requester installs UD, and it will write the line back on eviction. Ownership moves with the data, and the modification survives.
15. Common Mistakes
- Dirty data with PassDirty clear. Assumption: returning the data is enough. Bug: ownerless dirty line, lost on eviction (the DebugLab). Prevention: set PassDirty when giving up ownership.
- PassDirty set while staying SD. Assumption: always pass when dirty. Bug: two owners. Prevention: keep PassDirty clear when retaining SD.
- Misreporting the final state. Assumption: the home tolerates approximation. Bug: stale directory. Prevention: report the actual state.
- PassDirty with no data. Assumption: pass ownership without the bytes. Bug: nothing to write back. Prevention: PassDirty implies SnpRespData.
- Expecting data on SnpResp. Assumption: every response carries the line. Bug: waiting for data that never comes. Prevention: SnpResp is no-data.
- Ignoring the response's state field. Assumption: the snoop opcode determines the directory. Bug: directory drift. Prevention: update from the reported state.
16. Engineering Checklist
- Encode three fields: final state, data-or-not, and PassDirty.
- Use SnpResp for no data, SnpRespData when returning the line.
- Set PassDirty exactly when returning dirty data while giving up ownership.
- Keep PassDirty clear when the responder stays a dirty owner (SD).
- Report the actual final state so the home's directory stays accurate.
- On the receiving side, install UD / write back when PassDirty is set.
17. Key Takeaways
- A snoop response is the home's only window into what the snooped cache did.
- It encodes three fields: the final state, whether data is returned, and PassDirty.
- SnpResp carries no data; SnpRespData returns the line.
- PassDirty hands off dirty ownership — set when giving it up with the data, clear when keeping it (SD).
- Returning dirty data with PassDirty clear leaves the line ownerless — lost at the next eviction.
- Report state truthfully, pass dirty deliberately; the model here is representative.
18. Quick Revision
Snoop responses. A snoop response is the snooped cache's reply and the home's only window into what it did, encoding three fields: the final state (Resp state: I / SC / UC / UD / SD — the home updates its directory from it), whether data is returned (SnpResp = no data on RSP; SnpRespData = the line on DAT), and PassDirty. PassDirty is the dirty-ownership handoff: a dirty line must have exactly one owner obliged to write it back, so when a dirty holder gives up the line (goes to I or a clean state) and returns the data, it must set PassDirty — the recipient takes the writeback duty and installs UD. When it stays a dirty owner (SD) and forwards only a copy, PassDirty is clear. The trap: returning dirty data with PassDirty clear while relinquishing the copy leaves the line ownerless — the recipient installs UC (clean) and discards it on the next silent eviction, losing the modification. Report state truthfully; pass dirty exactly when you give it up. Representative model; 9.4 focuses on snoop-driven invalidation.
Coming Next
Chapter 9.4 — Snoop-Driven Invalidation. This chapter read the response in general; the next zooms in on the most consequential outcome — invalidation. Chapter 9.4 follows how a snoop forces another Request Node to drop its copy entirely: the exact sequence by which SnpUnique (and its relatives) take a holder to Invalid, how the holder confirms it in the response, and why invalidation is the mechanism that makes a single writer possible. It is the snoop outcome that underlies every write.