AMBA CHI · Module 7 · CHI Transaction Model
The Read Transaction
Module 7 zooms out from packets and channels to the whole transaction — the ordered exchange that makes one operation happen end to end. The read is where to start. A Request Node issues a read; the Home Node consults its directory and either fetches the line from memory or snoops the cache that holds it; then it returns completion and data, and the requester closes the transaction with a CompAck. What trips people up: the read is not finished when the data arrives. CompAck is what closes it, and until the requester sends CompAck the home still owns the transaction. Learn the read's structure, its two paths, and why CompAck matters. Representative model, not the specification.
Foundation15 min readAMBA CHIRead TransactionReadSharedCompDataCompAck
Module 7 · Chapter 7.1 · CHI Transaction Model
Project thread — Module 6 dissected the four channels and how their packets cross the fabric. Module 7 reassembles them into whole transactions. This chapter starts with the read; 7.2 does the write.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Define a transaction as an ordered exchange of packets that completes one operation.
- Trace a read end to end: REQ → (snoop or memory) → CompData → CompAck.
- Distinguish the two paths a read takes — memory fetch versus snoop a holder.
- Name the common read opcodes and what coherence state each requests.
- Explain why CompAck closes the read, and why the requester must not retire the transaction before sending it.
- Implement a representative requester-side read-transaction FSM in SystemVerilog, Verilog-2001, and VHDL.
2. Why Should I Learn This?
Up to now you have studied the pieces: channels, opcodes, cache states, routing. A transaction is where those pieces do work together. The read is the simplest complete one, and once you can trace it, every other transaction — write, atomic, snoop — is a variation on the same shape: a requester asks, the home coordinates, a completion comes back, the requester acknowledges.
The read also teaches the single most misunderstood rule in CHI: the data is not the end. Getting CompData feels like completion, but the transaction stays open until the requester sends CompAck. Skip that step, or reuse the transaction's identity too early, and you corrupt the home's ordering. Understanding the read correctly means understanding when it is actually done.
3. Key Terms
4. Previous Chapter Connection
Chapter 6.7 closed Module 6 by showing how a packet crosses the mesh: routed by TgtID, hop by hop, on its channel's virtual channel. Before that, 6.1–6.6 detailed each channel and the fields every packet shares.
This chapter puts them in motion. A read uses all four channels in one coordinated flow — REQ to ask, SNP to snoop a holder, RSP and DAT to respond and acknowledge — all correlated by the (SrcID, TxnID) pair from Chapter 6.6 and routed as Chapter 6.7 described. The channels were the vocabulary; the transaction is the sentence.
5. Core Concept — the read, end to end
A read transaction moves a line of data to a requester and leaves both the requester and the directory in a consistent coherence state. It has four beats.
- Request. The Request Node (RN) sends a REQ with a read opcode — say ReadShared — targeting the Home Node (HN) that owns the address (Chapter 4.7). The RN allocates a transaction tracker, tagged with its TxnID.
- Resolve. The HN consults its directory. If a cache holds the line in a state that must be seen or downgraded, the HN sends a SNP to that holder and collects its response (the snoop path). If no cache holds it coherently, the HN fetches the line from the Subordinate Node (SN) — memory (the memory path).
- Complete with data. The HN returns CompData on the DAT channel — the requested data and the completion response, combined — to the RN, echoing the RN's TxnID. The RN installs the line, typically in SC (Shared Clean) for a ReadShared.
- Acknowledge and close. The RN sends CompAck on the RSP channel back to the HN. This closes the transaction. Only now may the RN retire the tracker and free the TxnID; only now does the HN consider the read ordered and complete.
The synthesis:
A read is request → resolve → CompData → CompAck. The RN asks by TgtID with its TxnID; the HN either snoops a holder or fetches from memory; the HN returns CompData (data plus completion); the RN answers with CompAck, which is what actually closes the transaction. The data arriving is not the end — the acknowledgement is.
6. Engineering Mental Model — ordering a book through a librarian
You want a book. You do not walk the stacks; you ask the librarian (the home node), who owns the catalog.
- You hand the librarian a request slip with your name and a ticket number (the REQ, your TxnID).
- The librarian checks the catalog (the directory). If another reader currently holds the book, the librarian asks them to hand it over or share it (the snoop). If no one holds it, the librarian fetches it from the archive (memory).
- The librarian gives you the book along with a receipt (CompData — data and completion together).
- You sign the receipt and hand it back (CompAck). Until you sign, the librarian's records show the loan as still being arranged — the ticket number is not free for reuse, and the transaction is not closed.
Getting the book is not the end of the paperwork. Signing the receipt is.
7. Engineering Diagram — a ReadShared across the four channels
Read top to bottom: the request goes out, the home snoops the holder, the data comes back, and the transaction closes only on the final CompAck.
8. Read Opcodes and What They Request
A read is not one opcode — the RN chooses the one that names the coherence state it wants.
| Read opcode | Requester wants | Typical resulting state |
|---|---|---|
| ReadShared | a readable copy, sharing allowed | SC (Shared Clean) |
| ReadClean | a clean readable copy | SC or UC |
| ReadUnique | an exclusive copy to modify | UC / UD (others invalidated) |
| ReadOnce | a snapshot, not cached coherently | no retained coherent copy |
| ReadNotSharedDirty | readable, but not responsible for dirty | SC (home keeps dirty duty) |
The rule to carry: the opcode encodes intent. ReadShared asks to read alongside others; ReadUnique asks for sole ownership so it can write (which forces the home to invalidate every other copy). Same four-beat shape; the opcode sets what the home must do to satisfy it.
9. The Two Paths — snoop a holder or fetch from memory
The middle beat — how the home resolves the request — takes one of two forms, chosen by the directory.
- Snoop path. The directory shows a cache holds the line (and, for a ReadShared, may hold it dirty). The HN sends a SnpShared to that holder; the holder returns SnpRespData (its data, since it was dirty) and updates its own state to SC. The HN forwards CompData to the requester and writes the dirty data back as needed. Data came from a cache.
- Memory path. The directory shows no cache holds the line coherently. The HN issues a read to the SN (memory), receives the line, and returns CompData to the requester. Data came from memory.
Either way the requester sees the same ending: CompData then it sends CompAck. The path is the home's concern; the requester's four-beat view is identical.
10. Transaction Walkthrough — a ReadShared, snoop path
RN0 (Node 0) reads an address homed at HN (Node 4); RN1 (Node 1) holds the line dirty (UD).
- REQ. RN0 allocates tracker TxnID 5 and sends ReadShared: SrcID 0, TgtID 4. The line is now outstanding at RN0.
- Directory lookup. HN sees RN1 holds the line in UD. It must snoop.
- SNP. HN sends SnpShared to RN1 (TgtID 1). RN1 downgrades UD → SC and returns SnpRespData (its dirty data) on DAT, with SnpResp on RSP.
- CompData. HN returns CompData to RN0 (TgtID 0), echoing TxnID 5. RN0 installs the line in SC. The dirty data is written back to memory as the home requires.
- CompAck. RN0 sends CompAck to HN. The transaction closes. RN0 retires tracker TxnID 5 and may now reuse it; HN marks the read ordered and complete.
Four beats, all four channels, correlated throughout by (SrcID 0, TxnID 5) — and closed, not by the data, but by the CompAck.
11. RTL / Hardware View — a requester-side read FSM
The requester's side of a read is a small state machine: issue the request, wait for CompData, send CompAck, and only then retire the tracker. Representative and sequential.
// Representative requester-side READ transaction FSM (educational).
// Issue REQ -> wait for CompData -> send CompAck -> retire. The transaction is
// NOT done at CompData; it closes only after CompAck is sent, and only then may
// the TxnID be freed for reuse.
module chi_rn_read_txn (
input logic clk, rst_n,
input logic start, // launch a read
input logic comp_data, // CompData arrived (data + completion)
output logic req_valid, // drive the REQ
output logic comp_ack, // drive the CompAck
output logic busy, // TxnID is in use — do not reuse
output logic data_valid // captured line is valid to consume
);
typedef enum logic [1:0] { IDLE, WAIT_DATA, SEND_ACK } state_t;
state_t state, next;
always_comb begin
next = state;
req_valid = 1'b0;
comp_ack = 1'b0;
case (state)
IDLE: if (start) begin req_valid = 1'b1; next = WAIT_DATA; end
WAIT_DATA: if (comp_data) next = SEND_ACK; // data in, not done yet
SEND_ACK: begin comp_ack = 1'b1; next = IDLE; end // CompAck closes it
endcase
end
// busy from IDLE-exit through CompAck: the TxnID must not be reused until closed.
assign busy = (state != IDLE);
assign data_valid = (state == SEND_ACK); // data usable once received
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
endmoduleThe same behavior in Verilog-2001:
// Representative requester-side READ transaction FSM (Verilog-2001).
module chi_rn_read_txn (
input clk, rst_n, start, comp_data,
output req_valid, comp_ack, busy, data_valid
);
localparam IDLE = 2'd0, WAIT_DATA = 2'd1, SEND_ACK = 2'd2;
reg [1:0] state, next;
always @* begin
next = state;
case (state)
IDLE: if (start) next = WAIT_DATA;
WAIT_DATA: if (comp_data) next = SEND_ACK;
SEND_ACK: next = IDLE;
default: next = IDLE;
endcase
end
assign req_valid = (state == IDLE) && start;
assign comp_ack = (state == SEND_ACK);
assign busy = (state != IDLE);
assign data_valid = (state == SEND_ACK);
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
endmoduleAnd in VHDL:
-- Representative requester-side READ transaction FSM (VHDL).
library ieee;
use ieee.std_logic_1164.all;
entity chi_rn_read_txn is
port (
clk, rst_n : in std_logic;
start : in std_logic;
comp_data : in std_logic;
req_valid : out std_logic;
comp_ack : out std_logic;
busy : out std_logic;
data_valid : out std_logic
);
end entity;
architecture rtl of chi_rn_read_txn is
type state_t is (IDLE, WAIT_DATA, SEND_ACK);
signal state : state_t := IDLE;
begin
process (clk, rst_n)
begin
if rst_n = '0' then
state <= IDLE;
elsif rising_edge(clk) then
case state is
when IDLE => if start = '1' then state <= WAIT_DATA; end if;
when WAIT_DATA => if comp_data = '1' then state <= SEND_ACK; end if;
when SEND_ACK => state <= IDLE;
end case;
end if;
end process;
req_valid <= '1' when (state = IDLE and start = '1') else '0';
comp_ack <= '1' when state = SEND_ACK else '0';
busy <= '0' when state = IDLE else '1';
data_valid <= '1' when state = SEND_ACK else '0';
end architecture;All three retire only after CompAck — busy stays asserted from the request until the acknowledgement, so the TxnID is never freed while the transaction is open. The DebugLab shows what breaks when it is freed early.
12. Verification View — CompAck closes the read
The property that defines completion: the transaction is busy from request until CompAck, and CompAck is asserted exactly when the read closes — never before CompData.
// Bind to chi_rn_read_txn.
// 1. CompAck is only ever sent after CompData has been received — never before.
property p_ack_after_data;
@(posedge clk) disable iff (!rst_n)
comp_ack |-> $past(comp_data);
endproperty
// 2. The transaction stays busy (TxnID reserved) until the cycle it sends CompAck.
property p_busy_until_ack;
@(posedge clk) disable iff (!rst_n)
(busy && !comp_ack) |=> busy; // cannot drop busy without having sent CompAck
endpropertyThe system point, beyond the checks:
The read's ordering hinges on one edge: CompAck. Data can arrive early, but the home cannot consider the read ordered — and cannot let a later conflicting transaction proceed against it — until it sees the requester's CompAck. So the requester carries an obligation: hold the transaction open, and its TxnID reserved, from the moment it issues the request until the moment it sends CompAck. Treat CompData as completion and you break that contract — the home's view and the requester's view of "done" diverge, and ordering unravels.
- What it proves: CompAck follows CompData, and busy holds until CompAck — the transaction closes on the acknowledgement.
- What it does not prove: correct data content or directory state — those are the home's and memory's concern.
- Bug signature: a TxnID freed or reused after CompData but before CompAck — aliasing with a still-open transaction.
13. Testbench — drive a read to completion
Launches a read, delivers CompData, and checks that CompAck fires only after data and that busy holds until it does.
module tb_chi_rn_read_txn;
logic clk = 0, rst_n = 0, start = 0, comp_data = 0;
logic req_valid, comp_ack, busy, data_valid;
int errors = 0;
chi_rn_read_txn dut (.*);
always #5 clk = ~clk;
initial begin
@(posedge clk) rst_n = 1;
// Launch the read.
@(posedge clk) start = 1;
@(posedge clk) start = 0;
if (!busy) begin errors++; $display("FAIL not busy after request"); end
else $display("PASS busy asserted while outstanding");
// No CompAck may appear before CompData.
repeat (2) @(posedge clk)
if (comp_ack) begin errors++; $display("FAIL CompAck before CompData"); end
// Deliver CompData.
@(posedge clk) comp_data = 1;
@(posedge clk) comp_data = 0;
// Now CompAck should fire, and only now.
if (!comp_ack) begin errors++; $display("FAIL no CompAck after CompData"); end
else $display("PASS CompAck sent after CompData");
@(posedge clk);
if (busy) begin errors++; $display("FAIL still busy after CompAck"); end
else $display("PASS transaction retired after CompAck");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS busy asserted while outstanding
PASS CompAck sent after CompData
PASS transaction retired after CompAck
ALL TESTS PASSED14. DebugLab — retiring the read on CompData, before CompAck
Retiring the read on CompData, before CompAck
TXNID REUSED AFTER COMPDATA, BEFORE COMPACK -> ALIASINGUnder back-to-back reads from the same requester, the home occasionally mis-correlates — a completion or a snoop lands against the wrong outstanding read, or a read that should have been ordered after another slips ahead. It never reproduces with idle gaps between reads.
A TxnID reappears on a new request before the previous one's CompAck:
t0 RN issues ReadShared, TxnID 5 (home: txn 5 OPEN)
t1 CompData for TxnID 5 arrives at RN
t2 RN retires txn 5, frees TxnID 5 <-- too early: CompAck not sent
t3 RN issues a NEW ReadShared, TxnID 5 (reused)
t4 RN finally sends CompAck for the FIRST read
home sees CompAck "for TxnID 5" -> which one? two reads share TxnID 5Between t1 and t4 the home still holds the first read open, so TxnID 5 names two transactions at once.
The requester's completion condition was CompData received, not CompAck sent. From that point the tracker retired and the TxnID returned to the free pool while the home still regarded the transaction as outstanding.
CompData is data delivery; CompAck is transaction closure. The home orders the read on CompAck, so the transaction is live at the home until CompAck arrives. Freeing the TxnID at CompData reuses an identifier the home still associates with an open transaction — and correlation by (SrcID, TxnID) (Chapter 6.6) collapses when one (SrcID, TxnID) names two live reads.
Make CompAck sent the retirement condition. Hold the tracker — and reserve the TxnID — from the request until CompAck is issued, then retire. The busy signal in the FSM above encodes exactly this: it drops only in the CompAck state, never at CompData. Consume the data as soon as it arrives, but do not consider the transaction done, and do not free its identity, until you have closed it with CompAck.
15. Common Mistakes
- Retiring on CompData. Assumption: data means done. Bug: TxnID aliasing (the DebugLab). Prevention: retire on CompAck sent.
- Skipping CompAck. Assumption: the read completes itself. Bug: the home never orders/retires it → tracker leak. Prevention: always close with CompAck.
- Choosing the wrong read opcode. Assumption: any read will do. Bug: wrong end state (e.g. SC when you needed UC to write). Prevention: ReadUnique to modify, ReadShared to read along.
- Assuming data always comes from memory. Assumption: reads hit the SN. Bug: mishandling cache-to-cache transfer. Prevention: account for the snoop path (SnpRespData from a holder).
- Correlating CompData by TxnID alone. Assumption: TxnID is unique. Bug: cross-source aliasing. Prevention: correlate by (SrcID, TxnID) (Chapter 6.6).
- Ignoring the write-back of snooped dirty data. Assumption: a read never writes memory. Bug: lost dirty data. Prevention: the home writes back dirty data the snoop returned.
16. Engineering Checklist
- Issue the read with the opcode that names the state you need (ReadShared / ReadUnique / …).
- Allocate a tracker and reserve the TxnID at the request.
- Accept that the home resolves via snoop or memory — both end in CompData.
- Install the line in the state the opcode requested on CompData.
- Send CompAck — and treat that as completion.
- Retire and free the TxnID only after CompAck — never at CompData.
17. Key Takeaways
- A transaction is an ordered exchange of packets that completes one operation; the read is the simplest.
- The read is four beats: REQ → resolve (snoop or memory) → CompData → CompAck.
- The opcode names the coherence state the requester wants — ReadShared, ReadUnique, ReadClean, ReadOnce.
- The home resolves by snooping a holder or fetching from memory; the requester's view is the same either way.
- CompAck closes the read — the data arriving is not the end; the acknowledgement is.
- Reserve the TxnID until CompAck is sent; freeing it at CompData aliases a still-open transaction. Representative model.
18. Quick Revision
The read transaction. A read is an ordered, four-beat exchange: the requester sends a REQ with a read opcode (ReadShared for a shared copy, ReadUnique for exclusive-to-write, plus ReadClean, ReadOnce) and reserves a TxnID; the Home Node consults its directory and resolves the request by either snooping the cache that holds the line (which returns SnpRespData) or fetching from memory; the home returns CompData — data and completion together — on the DAT channel; and the requester answers with CompAck on RSP. The rule that matters: CompAck closes the transaction, not CompData. The home orders the read on CompAck and holds it open until then, so the requester must reserve its TxnID from the request until CompAck is sent, and retire only afterward — freeing it at CompData aliases a still-open read and corrupts correlation. All four channels, one flow, correlated by (SrcID, TxnID). Representative model; 7.2 does the write.
Coming Next
Chapter 7.2 — The Write Transaction. The read pulled a line toward a requester; the write pushes one back. Chapter 7.2 walks the write end to end — how a WriteBack or WriteUnique flows from request, through the home's DBID grant that says where to send the data, to the data beats and the completion — and why writes invert the read's handshake: the home grants a buffer before the data moves. It builds directly on the read's four-beat shape, adding the write-data handshake you met at the channel level in Module 5.