AMBA CHI · Module 2 · Coherency Protocol Foundations
The Ownership Concept
Four protocols, one recurring idea. In MSI, MESI, MOESI, and MESIF, exactly one agent per line carries a special responsibility: hold the current value, supply it to readers, and eventually write it back to memory. That responsibility is ownership, and separating it from mere sharing is what makes a coherence protocol tractable. This chapter defines ownership precisely, shows how each protocol assigns it and what happens when it transfers, and builds the small per-line tracker a Home Node uses to remember who owns each line. Ownership is the mental model the rest of the CHI series — the Home Node, snoops, and directories — is built on. The tracker here is representative, not a complete CHI directory.
Foundation13 min readAMBA CHIOwnershipWritebackDirectoryCache CoherencyHome Node
Module 2 · Chapter 2.5 · Coherency Protocol Foundations
Project thread — the state letters of 2.1–2.4 all encode one recurring role. This chapter names that role — ownership — and its duties, the model CHI's Home Node and directory (Modules 4 and 11) implement.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Distinguish ownership (a responsibility) from sharing (holding a copy).
- Identify the three duties an owner carries: hold the current value, supply readers, perform the writeback.
- Explain how MSI, MESI, MOESI, and MESIF each assign ownership, and where memory owns the value.
- Trace how ownership transfers on a write and is released on a writeback, and why a read never transfers it.
- Implement a representative per-line ownership tracker in SystemVerilog, Verilog-2001, and VHDL.
- Verify the one-owner-per-line invariant and that a read of a dirty line is sourced from its owner.
2. Why Should I Learn This?
The four protocols look like different tables of state letters, but they are variations on a single question: who is responsible for this line's current value? Once you see ownership as a role — with duties and a single holder — the tables stop being trivia and become obvious.
It also matters directly for CHI. The Home Node's directory exists to track ownership: which cache holds each line, whether it is dirty, and who must be snooped to supply or write it back. Every snoop, forward, and writeback in the rest of the series is ownership machinery. Get the concept now and the protocol reads itself later.
3. Key Terms
4. Previous Chapter Connection
MSI, MESI, MOESI, and MESIF (2.1–2.4) each added a state to solve a specific inefficiency, but every one of them designates a single agent per line to carry the value and its writeback. MSI and MESI use Modified for it; MOESI adds Owned so that owner can also share; MESIF's Forward carries only the supply duty for clean data, leaving the value with memory.
This chapter pulls that common thread out of the state letters. Instead of asking "what does state O do," it asks "what is the owner responsible for" — and answers it once, for all four.
5. Core Concept — ownership is a responsibility, not a copy
Holding a copy and owning a line are different things. Ownership bundles three duties:
- Hold the current value — the owner's copy is authoritative; memory may be stale.
- Supply readers — a read miss is answered by the owner (cache-to-cache), not by stale memory.
- Perform the writeback — when the owner gives the line up, it writes the value back so memory becomes current.
And one rule:
Exactly one owner per line. A cache (in a dirty/owning state) or, when no cache is dirty, memory. Two owners would each assume the other writes back — a recipe for lost or doubled data.
A sharer (state S) carries none of these duties: it holds a read-only copy, answers nothing, writes back nothing. The whole point of the coherence states is to mark which one copy is the owner.
6. Engineering Mental Model — the deed to the house
A cached line is a house.
- Sharers hold photos of the house (read-only copies). Many people can have photos.
- The owner holds the deed: they have the authoritative current state of the house, they show it to anyone who asks (supply), and when they sell, they must file the paperwork so the county record (memory) is updated (writeback).
- There is exactly one deed. Selling (a write elsewhere) transfers it; if two people both held a deed, the county record could be updated twice or not at all.
- When nobody holds a private deed, the county record itself (memory) is authoritative.
The coherence protocol is the escrow process that keeps exactly one valid deed and makes every transfer file the right paperwork.
7. Engineering Diagram — who owns line A
One line, one owner (CPU0), one sharer (CPU1), stale memory, and a directory that knows the difference. Every arrow is a duty ownership imposes.
8. Worked Example — ownership across the four protocols
Same question for each protocol: for a line, who owns it (holds the value and owes the writeback), and who may supply a reader?
| Protocol | Owning state(s) | Owner also shares? | Supplies clean reads | Who owns a clean line |
|---|---|---|---|---|
| MSI | M | no | M (writes back first) | memory |
| MESI | M | no | M (writes back first); E is sole-clean | memory |
| MOESI | M, O | yes (O) | the O/M owner, cache-to-cache | memory |
| MESIF | M | no | the F holder (clean); memory otherwise | memory (F only forwards) |
Two readings of the table:
- Dirty ownership is where the value lives. Only M (and MOESI's O) hold a dirty value; that agent owes the writeback. In every protocol, a clean line is owned by memory.
- Supply is a separable duty. MOESI's O supplies dirty data without giving up ownership; MESIF's F supplies clean data while ownership stays with memory. F carries the supply duty but not the value or the writeback — it is a forwarder, not an owner.
That split — value ownership versus supply duty — is exactly what MOESI and MESIF each optimized, and what CHI's directory tracks separately.
9. Transaction Walkthrough — how ownership moves
Trace ownership of line A through a write, a read, and a writeback, mapped onto the CHI cast. Representative behavioral flow, not a byte-level trace.
- CPU0 writes A. CPU0 requests exclusive access via its RN to the Home Node. The HN grants it and records owner = CPU0, dirty. Ownership has transferred to CPU0 (memory, the prior owner, is now stale). Duty acquired: CPU0 owes the writeback.
- CPU1 reads A. CPU1's read miss reaches the HN. The directory shows CPU0 owns the dirty value, so the HN snoops CPU0, which supplies the data. CPU1 becomes a sharer — ownership does not move. CPU0 still owes the writeback.
- CPU0 evicts A (or is invalidated). The owner performs the writeback; memory becomes current. Ownership is released back to memory. If CPU1 still holds a copy, it is now backed by current memory.
Notice ownership changed hands exactly twice — memory→CPU0 on the write, CPU0→memory on the writeback — and never on the read. The read only added a sharer.
10. RTL / Hardware View — a per-line ownership tracker
A representative per-line ownership tracker — the bookkeeping a Home Node keeps for one line. It records the single owner and whether the line is dirty, transfers ownership on a write, releases it on a writeback, and never moves it on a read. Behavioral and simplified: one event per cycle, one line, no sharer vector, no data path.
// Representative single-line ownership tracker — a Home Node's per-line
// bookkeeping (educational, not a complete CHI directory).
module ownership_tracker #(
parameter int ID_W = 2 // agent-id width (clog2 of agent count)
)(
input logic clk,
input logic rst_n,
input logic req_valid,
input logic req_write, // 1 = write / gain exclusive, 0 = read
input logic req_wb, // the current owner writes back / evicts
input logic [ID_W-1:0] req_id, // requesting agent
output logic owner_valid, // a cache currently owns the dirty line
output logic [ID_W-1:0] owner_id,
output logic dirty,
output logic data_from_owner // a read must be sourced from the owner
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
owner_valid <= 1'b0;
owner_id <= '0;
dirty <= 1'b0;
end else if (req_valid) begin
if (req_wb && owner_valid && (req_id == owner_id)) begin
owner_valid <= 1'b0; // writeback: memory current, no owner
dirty <= 1'b0;
end else if (req_write) begin
owner_valid <= 1'b1; // the writer becomes the sole owner
owner_id <= req_id; // any prior owner is superseded
dirty <= 1'b1;
end
// A READ changes nothing here: it creates a sharer, not an owner.
end
end
// A read of a dirty line must be answered by its owner, not by stale memory.
assign data_from_owner = req_valid && !req_write && !req_wb && owner_valid && dirty;
endmoduleThe same behavior in Verilog-2001:
// Representative single-line ownership tracker (Verilog-2001).
module ownership_tracker #(
parameter ID_W = 2
)(
input clk,
input rst_n,
input req_valid,
input req_write,
input req_wb,
input [ID_W-1:0] req_id,
output reg owner_valid,
output reg [ID_W-1:0] owner_id,
output reg dirty,
output data_from_owner
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
owner_valid <= 1'b0; owner_id <= {ID_W{1'b0}}; dirty <= 1'b0;
end else if (req_valid) begin
if (req_wb && owner_valid && (req_id == owner_id)) begin
owner_valid <= 1'b0; dirty <= 1'b0; // writeback releases ownership
end else if (req_write) begin
owner_valid <= 1'b1; owner_id <= req_id; dirty <= 1'b1; // write transfers it
end
// read: no change (sharer only)
end
end
assign data_from_owner = req_valid && !req_write && !req_wb && owner_valid && dirty;
endmoduleAnd in VHDL:
-- Representative single-line ownership tracker (VHDL).
library ieee;
use ieee.std_logic_1164.all;
entity ownership_tracker is
generic ( ID_W : integer := 2 );
port (
clk, rst_n : in std_logic;
req_valid : in std_logic;
req_write : in std_logic; -- 1 = write, 0 = read
req_wb : in std_logic; -- owner writeback / evict
req_id : in std_logic_vector(ID_W-1 downto 0);
owner_valid : out std_logic;
owner_id : out std_logic_vector(ID_W-1 downto 0);
dirty : out std_logic;
data_from_owner : out std_logic
);
end entity;
architecture rtl of ownership_tracker is
signal ov : std_logic := '0';
signal oid : std_logic_vector(ID_W-1 downto 0) := (others => '0');
signal dty : std_logic := '0';
begin
process(clk, rst_n)
begin
if rst_n = '0' then
ov <= '0'; oid <= (others => '0'); dty <= '0';
elsif rising_edge(clk) then
if req_valid = '1' then
if req_wb = '1' and ov = '1' and req_id = oid then
ov <= '0'; dty <= '0'; -- writeback releases ownership
elsif req_write = '1' then
ov <= '1'; oid <= req_id; dty <= '1'; -- write transfers ownership
end if;
-- read: no change (sharer only)
end if;
end if;
end process;
owner_valid <= ov;
owner_id <= oid;
dirty <= dty;
data_from_owner <= '1' when (req_valid = '1' and req_write = '0' and req_wb = '0'
and ov = '1' and dty = '1') else '0';
end architecture;All three model the identical rule: a write transfers ownership, a writeback releases it, a read sources from the owner but never becomes one.
11. Verification View — one owner, and reads follow it
Three properties pin ownership down.
// Bind to ownership_tracker.
// 1. A read of a dirty line is sourced from the owner, never stale memory.
property p_read_from_owner;
@(posedge clk) disable iff (!rst_n)
(req_valid && !req_write && !req_wb && dirty) |-> data_from_owner;
endproperty
assert property (p_read_from_owner);
// 2. A read never transfers ownership (owner and dirty are unchanged next cycle).
property p_read_keeps_owner;
@(posedge clk) disable iff (!rst_n)
(req_valid && !req_write && !req_wb)
|=> ($stable(owner_valid) && $stable(owner_id) && $stable(dirty));
endproperty
assert property (p_read_keeps_owner);
// 3. The owner's writeback releases ownership (no owner, clean, next cycle).
property p_writeback_releases;
@(posedge clk) disable iff (!rst_n)
(req_valid && req_wb && owner_valid && (req_id == owner_id))
|=> (!owner_valid && !dirty);
endproperty
assert property (p_writeback_releases);The system invariant is structural here and a scoreboard rule in a full directory:
Exactly one owner per line — a single
owner_idregister enforces it in this model; a directory must guarantee it across all lines. Whenowner_validis false, memory is the owner.
- What it proves: ownership has a single holder, reads are sourced from it, and it is released exactly once on writeback.
- What it does not prove: the sharer set (not modeled here), that the interconnect actually routed the read to the owner (a Home Node job), or cross-line ordering (Module 12).
- Bug signature when it fails:
data_from_ownerlow on a dirty read (reader served stale memory), orowner_idchanging on a read (ownership wrongly transferred — the DebugLab).
12. Testbench — write transfers, read shares, writeback releases
Deterministic stimulus; outputs are combinational or registered, sampled after the clock edge settles — no sampling race.
module tb_ownership_tracker;
logic clk = 0, rst_n;
logic req_valid, req_write, req_wb;
logic [1:0] req_id;
logic owner_valid, dirty, data_from_owner;
logic [1:0] owner_id;
int errors = 0;
ownership_tracker #(.ID_W(2)) dut (.*);
always #5 clk = ~clk;
task automatic ev(input logic v, wr, wb, input logic [1:0] id,
input logic exp_ov, input logic [1:0] exp_oid,
input logic exp_dty, exp_dfo, input string tag);
logic dfo;
req_valid = v; req_write = wr; req_wb = wb; req_id = id;
#1;
dfo = data_from_owner; // combinational — valid this cycle
if (dfo !== exp_dfo) begin
errors++; $display("FAIL [%s] data_from_owner=%b exp=%b", tag, dfo, exp_dfo);
end
@(posedge clk); #1;
req_valid = 0; req_write = 0; req_wb = 0;
if (owner_valid !== exp_ov || owner_id !== exp_oid || dirty !== exp_dty) begin
errors++;
$display("FAIL [%s] ov/oid/dty = %b/%0d/%b (exp %b/%0d/%b)",
tag, owner_valid, owner_id, dirty, exp_ov, exp_oid, exp_dty);
end else
$display("PASS [%s] ov/oid/dty=%b/%0d/%b dfo=%b", tag, owner_valid, owner_id, dirty, dfo);
endtask
initial begin
rst_n = 0; ev(0,0,0, 2'd0, 0,0,0,0, "reset"); rst_n = 1;
// Agent 1 writes A -> owner=1, dirty; memory no longer owns.
ev(1,1,0, 2'd1, 1,1,1,0, "write by 1: owner=1");
// Agent 2 reads A -> sourced from owner 1; ownership unchanged.
ev(1,0,0, 2'd2, 1,1,1,1, "read by 2: from owner, still owner=1");
// Owner 1 writes back -> ownership released to memory.
ev(1,0,1, 2'd1, 0,0,0,0, "writeback by 1: released");
// Agent 3 reads a now-clean line -> from memory (data_from_owner=0).
ev(1,0,0, 2'd3, 0,0,0,0, "read by 3: from memory");
// Agent 3 writes -> becomes the new owner.
ev(1,1,0, 2'd3, 1,3,1,0, "write by 3: owner=3");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS [reset] ov/oid/dty=0/0/0 dfo=0
PASS [write by 1: owner=1] ov/oid/dty=1/1/1 dfo=0
PASS [read by 2: from owner, still owner=1] ov/oid/dty=1/1/1 dfo=1
PASS [writeback by 1: released] ov/oid/dty=0/0/0 dfo=0
PASS [read by 3: from memory] ov/oid/dty=0/0/0 dfo=0
PASS [write by 3: owner=3] ov/oid/dty=1/3/1 dfo=0
ALL TESTS PASSED13. DebugLab — a read that stole ownership
A read that stole ownership
READ TRANSFERRED OWNERSHIP -> WRONG WRITEBACK OWNER -> LOST DIRTY DATAOccasionally, dirty data is lost: a line that a producer wrote never reaches memory, and a later reader gets the old value. It reproduces when a line is read shortly after being written by a different core.
The directory owner field across the events:
event owner_id dirty note
write A by CPU0 0 1 CPU0 owns the dirty value
read A by CPU1 1 (!) 1 <-- read moved owner to CPU1
CPU1 evicts A (clean) - 0 CPU1 had no dirty data; nothing written back
CPU0 evicts A silently - - the real dirty value is droppedThe scoreboard flags it: after the read, owner_id changed although no write occurred; the dirty value now sits with a cache the directory no longer calls the owner.
The read by CPU1: owner_id moved from CPU0 to CPU1 with no write. That is the earliest wrong event — long before the data is actually lost at eviction.
The tracker transferred ownership on a read. Ownership is a write/writeback responsibility; a read must only add a sharer. Recording the reader as owner left the writeback owed by a cache that never held the dirty value, while the true owner was demoted and later dropped its data silently.
Make reads change nothing about ownership — exactly the read: no change branch of the tracker in Section 10. Ownership transfers only on a write (a new sole owner) and is released only on a writeback. Do not "fix" it by writing every read back to memory; keep ownership with the real dirty holder and source reads from it.
14. Common Mistakes
- Transferring ownership on a read. Assumption: whoever last touched the line owns it. Bug: the writeback is owed by the wrong cache; dirty data is lost (the DebugLab). Prevention: reads make sharers; writes and writebacks move ownership.
- Allowing two owners. Assumption: any holder of the current value can write it back. Bug: doubled or dropped writeback. Prevention: exactly one owner per line — a cache, or memory.
- Confusing supply duty with value ownership. Assumption: whoever supplies a read owns the line. Bug: mishandling MESIF's Forward (supplies clean data but memory owns it). Prevention: separate "who answers reads" from "who holds the value and owes the writeback."
- Forgetting memory is the default owner. Assumption: a line always has a cache owner. Bug: over-snooping clean lines, or missing that a clean read comes from memory. Prevention: when no cache is dirty, memory owns the value.
- Losing ownership silently. Assumption: an owner can drop a dirty line like a clean one. Bug: the deferred writeback never happens; memory stays stale. Prevention: surrendering ownership of dirty data always writes back.
- Treating this tracker as a full CHI directory. Assumption: one owner field is the directory. Bug: missing the sharer vector, states, and snoop-filter logic. Prevention: this is the ownership concept; the full directory is Module 11.
15. Engineering Checklist
- Every line has exactly one owner — a cache in a dirty/owning state, or memory.
- A write transfers ownership to the writer; a writeback releases it to memory.
- A read creates a sharer and never transfers ownership.
- A read of a dirty line is sourced from its owner, not stale memory.
- Surrendering ownership of dirty data always performs the writeback.
- "Who supplies a read" (a duty) is tracked separately from "who owns the value."
16. Key Takeaways
- Ownership is a responsibility, not a copy: hold the current value, supply readers, perform the writeback.
- Exactly one owner per line — a cache, or memory when no cache is dirty.
- Ownership transfers on a write and is released on a writeback; a read only adds a sharer.
- MSI/MESI own via M; MOESI's O owns and shares dirty; MESIF's F only forwards clean data — memory still owns it.
- The Home Node's directory exists to track ownership — the machinery behind every snoop and writeback in CHI.
- This tracker is representative — the ownership concept, not a complete CHI directory.
17. Quick Revision
Ownership. One agent per line is responsible for the current value: hold it, supply readers, write it back. Exactly one owner — a cache in
M(or MOESIO), else memory. Transfers on a write (writer becomes sole owner), releases on a writeback (back to memory); a read makes a sharer, never an owner. A dirty read is sourced from the owner, not stale memory. MESIF'sFcarries only the supply duty; memory still owns the clean value. The Home Node's directory tracks this. Representative model, not a complete CHI directory.
Coming Next
Chapter 2.6 — The Dirty Data Concept. Ownership answers who is responsible; the next chapter examines what they are responsible for — the distinction between Modified and Clean, why a dirty line makes memory stale, and the writeback contract that guarantees the newest value is never lost when a line is evicted or handed off.