Skip to content

AMBA CHI · Module 9 · Snoop Flows

Snoop Ownership Transfer

Chapter 9.5 moved the data cache-to-cache; this one moves ownership the same way. Holding a dirty line and owning it differ — the owner is the one cache responsible for writing the line back, and there must be exactly one. A snoop can migrate that role from one Request Node to another without the line touching memory: the old owner relinquishes as the new owner assumes, carried by the PassDirty bit. Ownership behaves like a conserved quantity — one owner before, one after, never zero or two in between. That conservation is the discipline: keep-and-take gives two owners racing to write back, and slip-from-both leaves none obliged to save the modification. Representative model, not the specification.

Advanced16 min readAMBA CHIOwnershipPassDirtyConservationCoherency

Module 9 · Chapter 9.6 · Snoop Flows

Project thread — 9.5 sourced data from a peer cache. This chapter migrates the dirty-owner role between caches. 9.7 covers forwarded-snoop optimisations.

1. Learning Outcomes

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

  • Distinguish holding a dirty line from owning it — the writeback responsibility.
  • Describe how a snoop migrates ownership from one Request Node to another.
  • State the conservation invariant: exactly one owner before, during, and after.
  • Explain the handoff — old owner relinquishes as new owner assumes, via PassDirty.
  • Diagnose what breaks when a transfer duplicates (two owners) or drops (zero owners) the role.
  • Implement a representative ownership-conservation checker in SystemVerilog, Verilog-2001, and VHDL.

2. Why Should I Learn This?

Ownership is the subtlest thing that moves in a coherence protocol. Data is concrete — bytes you can see cross the fabric. Ownership is an obligation: the duty to eventually write a dirty line back, held by exactly one cache. It has no bytes of its own, yet mismanaging it corrupts data as surely as losing the data itself. When a snoop hands a dirty line from one core to another, it is not only copying bytes — it is moving that duty, and the move has to be exact.

The discipline is conservation. At every instant a dirty line must have one owner — no more, no fewer. A transfer is the delicate moment when that one owner changes identity, and it must do so without ever briefly having two (a double writeback) or none (a lost modification). If you build coherent caches, ownership transfer is where the trickiest bugs live, because the thing being conserved is invisible.

3. Key Terms

4. Previous Chapter Connection

Chapter 9.3 introduced PassDirty — the bit that hands off dirty responsibility. Chapter 9.5 moved the data cache-to-cache. This chapter combines them into a single idea: ownership migration.

When a snoop returns dirty data with PassDirty set, two things happen at once — the data moves (9.5) and the ownership moves (this chapter). But ownership deserves its own treatment because it is governed by a stricter rule than data: data can be copied (two caches can hold the same clean line), while ownership can only be moved (exactly one cache owns a dirty line). This chapter is about that move, and the conservation law that makes it correct.

5. Core Concept — ownership migrates, conserved at one

Ownership of a dirty line is a single, conserved role that a snoop can move from one cache to another. The move is a coordinated handoff.

  • One owner, always. A dirty line has exactly one owner — the cache in UD or SD obliged to write it back. Never zero, never two.
  • A snoop can migrate it. When the home snoops the owner on behalf of a new requester, the owner can relinquish the role (go to a non-owning state) and the requester can assume it (install a dirty-owning state) — ownership moves cache-to-cache.
  • PassDirty carries the handoff. The owner's response returns the dirty data with PassDirty set (Chapter 9.3), signalling "the responsibility is now yours." The recipient installs a dirty-owning state and is the new owner.
  • Memory is untouched. The whole migration happens between caches; memory never sees the line. The dirty value and its duty move together, directly.

The synthesis:

Ownership of a dirty line is one conserved role. A snoop migrates it: the old owner relinquishes as the new owner assumes, carried by PassDirty, without touching memory. The invariant is conservation — exactly one owner before, during, and after. Data can be copied; ownership can only be moved, and the move must leave the count at exactly one.

6. Engineering Mental Model — handing off the pager

A support team has an on-call pager: exactly one person carries it and is responsible for incidents. That responsibility is ownership.

  • When on-call changes, the pager is handed over — the outgoing person gives it up as the incoming person takes it. For that instant, the handoff must be clean.
  • If the outgoing person keeps a copy of the pager while the incoming one also has one, both think they are on call — two people respond to the same incident, or each assumes the other has it and neither does the follow-up. That is two owners.
  • If the outgoing person drops the pager and the incoming one never picks it up, no one is on call — an incident goes unhandled. That is zero owners.

The rule is simple and unforgiving: the pager exists in exactly one pair of hands at every moment. The handoff moves it; it never duplicates it and never drops it. Ownership of a dirty line is that pager.

7. Engineering Diagram — ownership moving from RN1 to RN0

Ownership migrating from RN1 to RN0. RN0 sends a REQ ReadUnique to the Home Node. The Home Node snoops RN1, the current dirty owner, with a SNP SnpUnique. RN1 relinquishes ownership, transitioning from Unique Dirty to Invalid and returning the line as a DAT SnpRespData with PassDirty set. The Home Node forwards the line to RN0 as a DAT CompData, and RN0 assumes ownership by installing Unique Dirty. Exactly one owner exists throughout and memory is not touched.Ownership transfer — RN1 relinquishes as RN0 assumesRN0 · new ownerHN · home (arbiter)RN1 · old owner (UD)REQ: ReadUniqueSNP: SnpUniqueDAT: SnpRespData (UDto I, relinquish,PassDirty)DAT: CompData(assume ownership,UD)
Figure 1 — ownership migrating from RN1 to RN0. RN0's ReadUnique makes the Home Node snoop RN1, the current dirty owner (UD). RN1 relinquishes — transitioning to Invalid and returning the line with PassDirty set. The Home Node forwards the data to RN0, which assumes ownership, installing Unique Dirty. Exactly one owner throughout: RN1 before, RN0 after, never both, never neither. Memory is never touched.

Read the middle two messages as a single handoff: RN1 relinquishes (UD → I, PassDirty) and RN0 assumes (installs UD). Ownership moved from RN1 to RN0, conserved at exactly one, and memory never saw the line.

8. Ownership Versus Data — they can move independently

Ownership and data are separate quantities; a snoop can move one, both, or neither.

FlowData movesOwnership
ReadUnique on a dirty holderforwardedmigrates (holder → requester)
ReadNotSharedDirtya copy forwardedstays (holder keeps SD)
ReadClean / WriteBackwritten backdissolves to memory
ReadShared on a clean holdera copy (or from memory)none (line is clean)

The rule to carry: data can be copied; ownership can only be moved. Two caches may hold the same clean line, but only one may own a dirty line. So a snoop that forwards dirty data must decide ownership's fate explicitly — migrate it (new owner assumes, old relinquishes), keep it (holder stays SD), or dissolve it to memory (writeback). The one thing it must never do is leave the owner count at anything but one.

9. Conservation — exactly one owner through the transfer

The invariant deserves its own statement, because it governs the whole transfer.

  • Before: one owner. The dirty line has a single owner (the old holder in UD/SD).
  • After: one owner. The line still has a single owner — now the new holder, or memory (if written back).
  • During: never two, never zero. The handoff must not create a window where both old and new own it, nor one where neither does.
  • The home arbitrates. The home mediates the transfer — it sees the old owner relinquish (PassDirty in the response) and the new owner assume (installs UD) — so it can enforce that exactly one owner results.

The point to carry:

Ownership is conserved like a physical quantity: it is neither created nor destroyed by a transfer, only moved. The transfer is correct exactly when the owner count stays at one across it — the old owner's relinquish and the new owner's assume are two halves of a single conserved handoff. Break the pairing and the count is wrong: keep-and-take gives two owners (a double writeback), drop-and-miss gives zero (a lost modification). The home's job is to ensure the two halves always happen together, so the count never leaves one.

10. Walkthrough — ownership moving RN1 → RN0

RN0 issues ReadUnique; RN1 owns the line in UD (the sole dirty owner).

  1. Before. One owner: RN1 (UD). Memory is stale; RN1 is responsible for the writeback.
  2. Snoop. HN snoops RN1 with SnpUnique — this flow moves ownership to the requester.
  3. Relinquish. RN1 transitions UD → I (a non-owning state) and returns the line as SnpRespData with PassDirty set — it has given up the role.
  4. Assume. HN forwards CompData (dirty) to RN0, which installs UD — it has taken the role. RN0 is now the sole owner, responsible for the writeback.
  5. After. One owner: RN0 (UD). The count never left one — RN1 relinquished exactly as RN0 assumed.

Ownership migrated RN1 → RN0, conserved throughout, memory untouched. Had RN1 stayed a dirty owner (SD) while RN0 assumed UD, there would be two owners — the DebugLab.

11. RTL / Hardware View — an ownership-conservation checker

The invariant reduces to a count: after a transfer, the owner count must be exactly one. This checker computes it from the old owner's relinquish and the new owner's assume. Representative.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ownership-transfer conservation checker (educational).
// A transfer MOVES ownership: the old owner relinquishes as the new owner assumes.
// The owner count after must be exactly ONE. Two owners = duplicated (double
// writeback); zero owners = dropped (lost modification).
module chi_ownership_transfer (
  input  logic       old_owns_before, // the old node owned the dirty line
  input  logic       old_relinquishes,// old node gave up ownership (went non-owning)
  input  logic       new_assumes,     // new node took ownership (installed UD/SD)
  output logic [1:0] owner_count,     // owners after the transfer
  output logic       transfer_ok,     // exactly one owner
  output logic       two_owners,      // duplicated ownership
  output logic       zero_owners      // dropped ownership
);
  logic old_after;
  // The old node still owns iff it owned before and did NOT relinquish.
  assign old_after   = old_owns_before && !old_relinquishes;
  // Count owners after: the old node (if it kept it) plus the new node (if it took it).
  assign owner_count = old_after + new_assumes;
 
  assign transfer_ok = (owner_count == 2'd1);
  assign two_owners  = (owner_count == 2'd2);
  assign zero_owners = (owner_count == 2'd0);
endmodule

The same behavior in Verilog-2001:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative ownership-transfer conservation checker (Verilog-2001).
module chi_ownership_transfer (
  input        old_owns_before,
  input        old_relinquishes,
  input        new_assumes,
  output [1:0] owner_count,
  output       transfer_ok,
  output       two_owners,
  output       zero_owners
);
  wire old_after = old_owns_before && !old_relinquishes;
  assign owner_count = old_after + new_assumes;
  assign transfer_ok = (owner_count == 2'd1);
  assign two_owners  = (owner_count == 2'd2);
  assign zero_owners = (owner_count == 2'd0);
endmodule

And in VHDL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Representative ownership-transfer conservation checker (VHDL).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity chi_ownership_transfer is
  port (
    old_owns_before  : in  std_logic;
    old_relinquishes : in  std_logic;
    new_assumes      : in  std_logic;
    owner_count      : out unsigned(1 downto 0);
    transfer_ok      : out std_logic;
    two_owners       : out std_logic;
    zero_owners      : out std_logic
  );
end entity;
 
architecture rtl of chi_ownership_transfer is
  signal old_after : std_logic;
  signal cnt       : unsigned(1 downto 0);
begin
  old_after <= '1' when (old_owns_before = '1' and old_relinquishes = '0') else '0';
  cnt       <= ("0" & old_after) + ("0" & new_assumes);
 
  owner_count <= cnt;
  transfer_ok <= '1' when cnt = 1 else '0';
  two_owners  <= '1' when cnt = 2 else '0';
  zero_owners <= '1' when cnt = 0 else '0';
end architecture;

All three compute the owner count after a transfer: a correct move (old relinquishes, new assumes) yields exactly one. Two owners flags a duplicated role; zero flags a dropped one. The DebugLab is the two-owner case.

12. Verification View — the owner count stays at one

The properties that enforce conservation: a proper move yields one owner, and duplicate/drop are flagged.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind to chi_ownership_transfer.
// 1. A proper move — old relinquishes, new assumes — yields exactly one owner.
property p_move_conserves;
  @(*) (old_owns_before && old_relinquishes && new_assumes) |-> transfer_ok;
endproperty
 
// 2. New assumes while old keeps ownership -> two owners (a duplicated role).
property p_keep_and_take_duplicates;
  @(*) (old_owns_before && !old_relinquishes && new_assumes) |-> two_owners;
endproperty
 
// 3. Old relinquishes but no one assumes -> zero owners (a dropped role).
property p_drop_loses;
  @(*) (old_owns_before && old_relinquishes && !new_assumes) |-> zero_owners;
endproperty

The system point, beyond the checks:

Conservation makes ownership auditable: at any point you can count owners, and the count must be exactly one for every dirty line. That is a stronger guarantee than "the data arrived," because the data can be perfectly delivered while the responsibility is duplicated or lost. The transfer is the only operation that changes an owner's identity, so it is the only place the count can go wrong — which is why it must be treated as an atomic move, two halves that always occur together. A design that reasons about ownership as a counted, conserved role catches the duplicate and the drop that a design reasoning only about data movement will miss.

  • What it proves: a proper move yields one owner; keep-and-take flags two; drop flags zero.
  • What it does not prove: the transfer happened atomically in time — ordering is the home's job (Chapter 7.6).
  • Bug signature: owner count of two (double writeback) or zero (lost modification) after a transfer.

13. Testbench — conservation across transfer outcomes

Drives the relinquish/assume combinations and checks the owner count.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_chi_ownership_transfer;
  logic old_owns_before, old_relinquishes, new_assumes;
  logic [1:0] owner_count;
  logic transfer_ok, two_owners, zero_owners;
  int errors = 0;
 
  chi_ownership_transfer dut (.*);
 
  task automatic check(input logic ob, orl, na,
                       input logic [1:0] exp_cnt, input logic exp_ok, input string name);
    old_owns_before = ob; old_relinquishes = orl; new_assumes = na; #1;
    if (owner_count !== exp_cnt || transfer_ok !== exp_ok) begin
      errors++; $display("FAIL %s: count=%0d ok=%0b", name, owner_count, transfer_ok);
    end else $display("PASS %s: count=%0d ok=%0b", name, owner_count, transfer_ok);
  endtask
 
  initial begin
    check(1'b1, 1'b1, 1'b1, 2'd1, 1'b1, "move   (relinquish + assume) -> 1 owner");
    check(1'b1, 1'b0, 1'b1, 2'd2, 1'b0, "keep+take (old keeps, new takes) -> 2 owners");
    check(1'b1, 1'b1, 1'b0, 2'd0, 1'b0, "drop   (relinquish, none assume) -> 0 owners");
    check(1'b1, 1'b0, 1'b0, 2'd1, 1'b1, "no transfer (old keeps) -> 1 owner");
 
    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 move   (relinquish + assume) -> 1 owner: count=1 ok=1
PASS keep+take (old keeps, new takes) -> 2 owners: count=2 ok=0
PASS drop   (relinquish, none assume) -> 0 owners: count=0 ok=0
PASS no transfer (old keeps) -> 1 owner: count=1 ok=1
ALL TESTS PASSED

14. DebugLab — the old owner keeps ownership (two owners)

1

The old owner keeps ownership (two owners)

TRANSFER KEEPS OLD OWNER + NEW OWNER -> TWO OWNERS, DOUBLE WRITEBACK
Symptom

A dirty line is written back twice — sometimes an older value clobbers a newer one — or, less often, a modification is mishandled after being transferred between caches. It only appears after a line's ownership moves from one core to another.

Evidence

Both the old and new holder ended up owners:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before: RN1 = UD (sole owner)
RN0 ReadUnique -> HN SnpUnique to RN1
RN1 forwards dirty data (PassDirty) BUT downgrades to SD (keeps owner role)  <-- wrong
RN0 installs UD (assumes owner role)
after: RN1 = SD (owner?), RN0 = UD (owner?)  -> owner_count = 2
both eventually write back -> the later writeback may be clobbered by the earlier

The role was copied, not moved — RN1 kept it while RN0 took it.

First Divergence

The transfer moved the data (PassDirty, forwarded) but the old owner did not relinquish — it went to SD, a dirty-owning state, while the new owner assumed UD. From that point two caches held the writeback duty for one line.

Root Cause

Ownership can only be moved, never copied, and a transfer must conserve exactly one owner. The flow forwarded the dirty line to a new owner (correct) but let the old owner keep the role (wrong), producing owner_count = 2. Two owners for one writeback duty is as broken as zero: duplicated writebacks race, or each defers. The relinquish and the assume are two halves of one conserved handoff — omit the relinquish and the count is wrong. This differs from a PassDirty-clear drop (Chapter 9.3), which gives zero owners; here the data moved and PassDirty was set, but the old owner failed to step down.

Fix

Make the transfer a move: the old owner relinquishes to a non-owning state (I on SnpUnique, or SC/UC as the flow requires) exactly as the new owner assumes the dirty-owning state. Then owner_count stays at one — the role migrated, not duplicated. If the flow is meant to keep the old owner (ReadNotSharedDirty), the new holder must not assume ownership; the two must always sum to one.

15. Common Mistakes

  • Old owner keeps the role on a transfer. Assumption: forwarding data is the whole job. Bug: two owners (the DebugLab). Prevention: the old owner relinquishes.
  • Dropping the role on a transfer. Assumption: PassDirty is optional. Bug: zero owners, lost data (Chapter 9.3). Prevention: the new owner assumes with PassDirty.
  • Copying ownership like data. Assumption: two caches can own a dirty line. Bug: duplicated duty. Prevention: ownership moves, never copies.
  • New owner assumes on a keep flow. Assumption: any dirty data makes you owner. Bug: two owners on RNSD. Prevention: on keep flows the holder stays owner.
  • Ignoring the home's arbitration. Assumption: caches transfer directly with no oversight. Bug: unsynchronized handoff. Prevention: the home mediates the move.
  • Not auditing the owner count. Assumption: correct data implies correct ownership. Bug: silent duplicate/drop. Prevention: verify exactly one owner.

16. Engineering Checklist

  • Treat ownership as a single conserved role — exactly one owner per dirty line.
  • On a transfer, the old owner relinquishes as the new owner assumes.
  • Carry the handoff with PassDirty (Chapter 9.3).
  • Keep memory untouched — ownership migrates cache-to-cache.
  • On a keep flow (RNSD), the holder stays owner and the requester does not assume.
  • Audit the owner count — never two, never zero, across the transfer.

17. Key Takeaways

  • Ownership — the writeback responsibility — is a single conserved role, held by exactly one cache.
  • A snoop migrates it cache-to-cache: the old owner relinquishes as the new owner assumes.
  • PassDirty carries the handoff; memory is never touched during the migration.
  • Data can be copied; ownership can only be moved — two caches may share a clean line, one owns a dirty line.
  • A transfer must conserve the count at one — two owners double the writeback, zero owners lose the data.
  • Move, do not copy; audit the count; the model here is representative.

18. Quick Revision

Snoop ownership transfer. Ownership — the duty to write a dirty line back — is a single conserved role, held by exactly one cache (the UD/SD owner). A snoop can migrate it from one Request Node to another cache-to-cache, without touching memory: the old owner relinquishes (moves to a non-owning state) exactly as the new owner assumes (installs a dirty-owning state), carried by PassDirty and arbitrated by the home. The invariant is conservation — exactly one owner before, during, and after; never zero, never two. Data can be copied (two caches may share a clean line) but ownership can only be moved. The failure to avoid: a transfer that copies the role — the old owner keeps ownership (stays SD) while the new owner assumes (UD) — giving two owners and a double writeback that can clobber a newer value; its mirror is dropping the role (PassDirty clear), giving zero owners and lost data. Move, never copy; relinquish as you assume; audit the count at one. Representative model; 9.7 covers forwarded-snoop optimisations.

Coming Next

Chapter 9.7 — Snoop Forwarding. So far the home has relayed snoop data: holder → home → requester. Chapter 9.7 covers the optimisation that shortens that path — forwarded snoops (Direct Cache Transfer), where the home tells the holder to send its data straight to the requester, bypassing the extra hop through the home. It builds on the ownership and data transfers you now understand to show how CHI cuts snoop latency, and the coherence bookkeeping that direct forwarding still requires.