AMBA CHI · Module 7 · CHI Transaction Model
The Transaction State Machine
The clean four-phase lifecycle is an abstraction; the real hardware runs two state machines, one on the requester and one on the home, and they must agree. The home's machine is the richer one, because the home is the point of serialization — it orders every access to a line and holds that ordering until the requester's CompAck. That hold is what makes conflicting transactions to one address safe: a second request is ordered strictly behind the first. Release the ordering too early — when the completion is sent, not acknowledged — and two transactions to a line can interleave and break coherence. Learn both sides' states and why the home holds serialization until CompAck. Representative model, not the specification.
Advanced17 min readAMBA CHITransaction FSMPoint of SerializationOrderingHome Node
Module 7 · Chapter 7.6 · CHI Transaction Model
Project thread — 7.5 gave the lifecycle as one clean loop. This chapter opens the two real FSMs — requester and home — that implement it, and closes Module 7. Module 8 walks concrete request flows.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Explain that the lifecycle is implemented as two FSMs — requester-side and home-side — kept consistent.
- Describe the home as the point of serialization (POS) for a line.
- Trace the home FSM's two paths — snoop and memory — to completion.
- State why the home must hold a line's ordering until CompAck.
- Diagnose how releasing ordering early lets conflicting transactions interleave.
- Implement a representative home-side transaction FSM in SystemVerilog, Verilog-2001, and VHDL.
2. Why Should I Learn This?
The lifecycle in Chapter 7.5 was a clean loop because it described one transaction in isolation. Real hardware runs two machines per transaction — the requester's and the home's — and they must stay in step across a fabric that reorders packets. More importantly, the home runs many such machines at once for different transactions, some to the same line, and it is the home's job to keep those from stepping on each other.
That is the heart of coherence ordering. The home is where a line's accesses are serialized, and the exact moment it considers one transaction "done enough" to let the next proceed decides whether two writers to a line are correctly ordered or silently interleaved. This chapter is where the abstract "the home orders things" becomes a concrete state machine with a specific, load-bearing rule about when ordering is released.
3. Key Terms
4. Previous Chapter Connection
Chapter 7.5 drew the lifecycle as a single loop: dispatch, in-flight, response, retirement, owned by a tracker slot. It deliberately showed one side and one transaction.
This chapter splits that loop into the two machines that really run it. The requester's is close to the 7.1 read FSM — issue, wait, CompAck. The home's is new and richer: it orders the request at the POS, drives the snoop or memory path, returns the completion, and — critically — holds the line's ordering until CompAck. The tracker pool of 7.5 lives on both sides; here we watch what each slot's FSM actually does, and why the home's retirement timing is the linchpin of coherence ordering.
5. Core Concept — two machines, and the home orders the line
Each transaction is driven by two cooperating state machines, and the home's carries the ordering responsibility.
- Requester-side FSM. The RN issues the REQ, waits through the in-flight phase (grants, snoop of its request, data), and closes with CompAck — essentially the 7.1 machine, generalized across read, write, atomic.
- Home-side FSM. The HN accepts and orders the request at the POS, then takes one of two paths: snoop holders (SnpShared/SnpUnique, Chapter 7.4) and collect their responses, or go straight to memory. It assembles the completion, sends CompData / Comp, and waits for CompAck.
- The ordering hold. From the moment it orders the request until it receives CompAck, the home keeps the line's serialization owned. A conflicting request — another transaction to the same line — is ordered strictly behind, not allowed to race.
- Consistency. The two FSMs advance on shared events: the home's snoop is the requester-of-the-snoop's input; the requester's CompAck is the home's retirement trigger. Neither side is "done" until both agree — and the binding event is CompAck.
The synthesis:
The lifecycle is two FSMs — requester and home — advancing in lockstep across the fabric. The home is the point of serialization: it orders each request to a line and holds that ordering until CompAck, so conflicting transactions queue behind rather than interleave. CompAck is the hinge: it closes the requester's transaction and releases the home's ordering. Retire the home's ordering on anything earlier and two transactions to one line can overlap.
6. Engineering Mental Model — two clerks with a shared ledger
A records office where two clerks handle one filing, and a master ledger orders all changes to a file.
- The front clerk (requester) takes your request, waits while it is processed, and signs off when the work is confirmed.
- The records clerk (home) owns the master ledger for each file. When your request comes in, the records clerk claims the file's next slot in the ledger — fixing your change's order — then does the work: either asking whoever currently holds the file to hand it over (snoop) or pulling it from the archive (memory).
- Crucially, the records clerk keeps the file's slot locked in the ledger until you sign off (CompAck). Anyone else wanting that file is queued after you.
If the records clerk releases the file's ledger slot the moment the work is sent — before you sign — the next person's change can be recorded against a file you have not finished with, and the ledger's order no longer matches reality. The lock has to hold until the signature.
7. Engineering Diagram — the home-side transaction FSM
Read it as a loop with a shortcut: the snoop path (Idle → Snoop → Data) when holders must be snooped, or the memory path (Idle → Data) when none do. Either way the machine returns to Idle — releasing the line's ordering — only on CompAck.
8. The Requester-Side and Home-Side States
The two machines, side by side.
| Phase | Requester-side (RN) | Home-side (HN) |
|---|---|---|
| Dispatch | issue REQ, reserve TxnID | order request at POS, allocate tracker |
| In-flight | await grant / snoop / data | snoop holders or fetch memory |
| Response | receive CompData / Comp | assemble and send completion |
| Retirement | send CompAck, free slot | receive CompAck, release ordering |
The rule to carry: the two FSMs mirror the 7.5 lifecycle but with different jobs — the requester waits and acknowledges; the home orders, resolves, and holds serialization. They meet at two events: the snoop (home drives, a holder responds) and the CompAck (requester drives, home retires). CompAck is the shared closing event — it must mean the same "done" to both sides.
9. Serialization at the Home — conflicts ordered by the POS
The home's extra responsibility, made explicit.
- One order per line. Every access to a line passes through its home (Chapter 4.7), which places them in a single order. That order is the coherence order for the line.
- In-flight means owned. While a transaction to a line is in-flight at the home — its FSM not yet back to Idle — the home owns that line's ordering. It will not start a conflicting transaction ahead of it.
- Conflicts queue. A second request to the same line is held (ordered behind) until the first releases. This is how two writers to one line are strictly ordered rather than raced.
- Release at CompAck. The home releases the ordering only when the first transaction retires on CompAck — because until the requester acknowledges, the transaction is not fully installed, and letting the next one proceed could interleave with the first's completion.
The point to carry:
The home is not just a router to memory; it is the serializer of a line. Its FSM does not return to Idle when the data is sent — it returns when the transaction is acknowledged, because that ordering hold is the mechanism that keeps conflicting transactions from overlapping. The requester's CompAck and the home's ordering release are the same event for a reason: coherence ordering is defined at the point where the home lets the next conflicting transaction begin.
10. Transaction Walkthrough — a read through both FSMs
RN0 reads a line homed at HN; RN1 holds it dirty. Watch both machines.
- RN dispatch / HN order. RN0's FSM issues ReadShared (→ its in-flight state). HN's FSM orders the request at the POS and, seeing RN1 as a holder, takes the snoop path (→ Snoop). The line's ordering is now owned by this transaction.
- Snoop. HN snoops RN1 (SnpShared); RN1 responds with data. HN's FSM moves Snoop → Data. (A second request to this line arriving now is queued behind.)
- Complete. HN's FSM sends CompData to RN0 and moves Data → Wait-ack. RN0's FSM receives the data (→ its response state) and installs the line.
- Acknowledge. RN0 sends CompAck (retiring its slot). HN receives it, moves Wait-ack → Idle, and releases the line's ordering.
- Next conflict proceeds. Only now does HN begin the queued conflicting transaction — strictly after the first, correctly ordered.
The two machines met at the snoop (step 2) and the CompAck (step 4). Note step 4: the home released ordering on CompAck, not on sending CompData in step 3 — the DebugLab shows why that gap matters.
11. RTL / Hardware View — a home-side transaction FSM
The home's machine orders a request, chooses the snoop or memory path, completes, and holds ordering until CompAck. pos_busy marks the ordering hold. Representative and sequential.
// Representative home-side transaction FSM (educational).
// Order the request, take the SNOOP path (need_snoop) or the MEMORY path, send
// the completion, then release ordering ONLY on CompAck. pos_busy marks the
// serialization hold: while asserted, a conflicting request to this line waits.
module chi_hn_txn (
input logic clk, rst_n,
input logic req, // an ordered request enters this tracker
input logic need_snoop, // holders must be snooped
input logic snoop_done, // all snoop responses collected
input logic data_ready, // data available to complete
input logic comp_ack, // requester's CompAck received
output logic issue_snoop, // drive snoops
output logic send_comp, // drive CompData / Comp
output logic pos_busy // line ordering owned (conflicts wait) until CompAck
);
typedef enum logic [1:0] { IDLE, SNOOP, DATA, WACK } state_t;
state_t state, next;
always_comb begin
next = state;
case (state)
IDLE: if (req) next = need_snoop ? SNOOP : DATA; // two paths
SNOOP: if (snoop_done) next = DATA; // self-loop: more holders
DATA: if (data_ready) next = WACK; // completion sent
WACK: if (comp_ack) next = IDLE; // release ordering on ACK
endcase
end
assign issue_snoop = (state == SNOOP);
assign send_comp = (state == DATA) && data_ready;
assign pos_busy = (state != IDLE); // held from order to CompAck
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
endmoduleThe same behavior in Verilog-2001:
// Representative home-side transaction FSM (Verilog-2001).
module chi_hn_txn (
input clk, rst_n, req, need_snoop, snoop_done, data_ready, comp_ack,
output issue_snoop, send_comp, pos_busy
);
localparam IDLE = 2'd0, SNOOP = 2'd1, DATA = 2'd2, WACK = 2'd3;
reg [1:0] state, next;
always @* begin
next = state;
case (state)
IDLE: if (req) next = need_snoop ? SNOOP : DATA;
SNOOP: if (snoop_done) next = DATA;
DATA: if (data_ready) next = WACK;
WACK: if (comp_ack) next = IDLE;
default: next = IDLE;
endcase
end
assign issue_snoop = (state == SNOOP);
assign send_comp = (state == DATA) && data_ready;
assign pos_busy = (state != IDLE);
always @(posedge clk or negedge rst_n)
if (!rst_n) state <= IDLE;
else state <= next;
endmoduleAnd in VHDL:
-- Representative home-side transaction FSM (VHDL).
library ieee;
use ieee.std_logic_1164.all;
entity chi_hn_txn is
port (
clk, rst_n : in std_logic;
req : in std_logic;
need_snoop : in std_logic;
snoop_done : in std_logic;
data_ready : in std_logic;
comp_ack : in std_logic;
issue_snoop : out std_logic;
send_comp : out std_logic;
pos_busy : out std_logic
);
end entity;
architecture rtl of chi_hn_txn is
type state_t is (IDLE, SNOOP, DATA, WACK);
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 req = '1' then
if need_snoop = '1' then state <= SNOOP; else state <= DATA; end if;
end if;
when SNOOP => if snoop_done = '1' then state <= DATA; end if;
when DATA => if data_ready = '1' then state <= WACK; end if;
when WACK => if comp_ack = '1' then state <= IDLE; end if;
end case;
end if;
end process;
issue_snoop <= '1' when state = SNOOP else '0';
send_comp <= '1' when (state = DATA and data_ready = '1') else '0';
pos_busy <= '0' when state = IDLE else '1';
end architecture;All three keep pos_busy asserted from the ordered request until CompAck — so the line's ordering is owned for the whole transaction, and a conflict cannot slip in. The DebugLab shows the coherence hazard of releasing it at send_comp instead.
12. Verification View — the home holds ordering until CompAck
The property that protects coherence ordering: the home owns the line (pos_busy) from the request until CompAck, and releases only on the ack.
// Bind to chi_hn_txn.
// 1. Ordering is held (pos_busy) continuously until CompAck — it does not drop at
// send_comp. pos_busy may fall only in the cycle after comp_ack.
property p_hold_until_ack;
@(posedge clk) disable iff (!rst_n)
($fell(pos_busy)) |-> $past(comp_ack);
endproperty
// 2. The completion is sent (send_comp) while still holding ordering — sending
// Comp must NOT itself release the line.
property p_comp_does_not_release;
@(posedge clk) disable iff (!rst_n)
send_comp |-> pos_busy;
endpropertyThe system point, beyond the checks:
Two transaction FSMs make one transaction, but the coherence guarantee lives in when the home lets go. Sending the completion is not the same as finishing the transaction — the requester has not yet acknowledged, so the line's new state is not yet settled everywhere. If the home released ordering at
send_comp, a conflicting transaction could be ordered against a line whose previous transaction is still landing, and the two would interleave. Holding until CompAck closes that gap: the requester's acknowledgement and the home's release are the same instant, so there is never a moment when a line's ordering is ambiguous. That single alignment is what makes the two-FSM system coherent.
- What it proves: ordering is held to CompAck, and sending Comp does not release it.
- What it does not prove: the requester's own FSM correctness — that is the RN-side machine (Chapter 7.1).
- Bug signature: pos_busy falling at send_comp — a line released for conflicts before its transaction retired.
13. Testbench — the home holds ordering to CompAck
Runs the snoop path and checks pos_busy stays asserted through send_comp, dropping only after CompAck.
module tb_chi_hn_txn;
logic clk = 0, rst_n = 0;
logic req = 0, need_snoop = 0, snoop_done = 0, data_ready = 0, comp_ack = 0;
logic issue_snoop, send_comp, pos_busy;
int errors = 0;
chi_hn_txn dut (.*);
always #5 clk = ~clk;
initial begin
@(posedge clk) rst_n = 1;
// Ordered request, snoop path.
@(posedge clk) begin req = 1; need_snoop = 1; end
@(posedge clk) begin req = 0; need_snoop = 0; end
if (!pos_busy || !issue_snoop) begin errors++; $display("FAIL not snooping/owned after order"); end
else $display("PASS ordering owned, snooping");
// Snoops complete -> data.
@(posedge clk) snoop_done = 1;
@(posedge clk) snoop_done = 0;
// Data ready -> send completion; ordering MUST still be held here.
@(posedge clk) data_ready = 1;
if (!send_comp) begin errors++; $display("FAIL no completion when data ready"); end
else if (!pos_busy) begin errors++; $display("FAIL ordering released at send_comp"); end
else $display("PASS completion sent, ordering still held");
@(posedge clk) data_ready = 0;
// CompAck -> release ordering.
@(posedge clk) comp_ack = 1;
@(posedge clk) comp_ack = 0;
if (pos_busy) begin errors++; $display("FAIL ordering not released after CompAck"); end
else $display("PASS ordering released on CompAck");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS ordering owned, snooping
PASS completion sent, ordering still held
PASS ordering released on CompAck
ALL TESTS PASSED14. DebugLab — the home releases ordering when it sends the completion
The home releases ordering when it sends the completion
HOME RELEASES ORDERING AT SEND_COMP, BEFORE COMPACK -> CONFLICT INTERLEAVESTwo cores hammering the same line occasionally observe a coherence violation — one reads a value inconsistent with the ordering both should have seen, or an update is lost. It only appears when transactions to one address overlap; spread-out access is clean.
A conflicting transaction begins before the first is acknowledged:
txn A (RN0 ReadUnique, line L):
HN sends CompData to RN0, HN FSM -> IDLE (ordering RELEASED) <-- too early
RN0 has NOT yet sent CompAck; still installing L
txn B (RN1 ReadUnique, line L) arrives:
HN, now IDLE for L, orders B and snoops -> races A's install
result: A and B interleave on L -> inconsistent orderingThe home let B proceed in the window between sending A's completion and receiving A's CompAck.
The home FSM's retirement was triggered by send_comp — sending the completion — rather than by comp_ack. From that point the line's ordering was released while transaction A was still landing, so a conflicting transaction B could be ordered into the gap.
The home is the point of serialization, and serialization must hold until the transaction retires. Sending the completion is not retirement; the requester's CompAck is. Between the two, transaction A's effect on the line is not yet settled everywhere, so releasing the ordering there lets a conflicting transaction interleave — exactly the overlap the POS exists to prevent. The requester-side early-retire bug of Chapter 7.1 aliased one requester's TxnID; this is its home-side twin, and it mis-orders two different transactions to one line.
Release the line's ordering only on CompAck: gate the home FSM's return to Idle on comp_ack, never on send_comp. The pos_busy signal must stay asserted through the completion and drop only at retirement, so a conflicting request is held behind the in-flight transaction until it is truly done. Send the completion when the data is ready, but hold serialization until the acknowledgement.
15. Common Mistakes
- Releasing ordering at send_comp. Assumption: completion sent means done. Bug: conflict interleave (the DebugLab). Prevention: hold ordering to CompAck.
- Modeling only one FSM. Assumption: one machine per transaction. Bug: requester/home views diverge. Prevention: model both, meeting at snoop and CompAck.
- Letting conflicts race. Assumption: independent trackers are independent. Bug: two writers to a line overlap. Prevention: order same-line conflicts behind the in-flight one.
- Skipping the memory path. Assumption: every request snoops. Bug: needless snoops / mis-modeled latency. Prevention: two paths — snoop or memory.
- Treating CompAck as optional at the home. Assumption: the home retires on its own. Bug: ordering released early or never. Prevention: CompAck retires the home FSM.
- Inconsistent shared events. Assumption: snoop/ack timing is loose. Bug: FSMs desynchronize. Prevention: advance both sides on the shared events.
16. Engineering Checklist
- Model a transaction as two FSMs — requester-side and home-side — kept consistent.
- Treat the home as the POS; it orders every access to a line.
- Support both paths in the home FSM — snoop and memory.
- Hold the line's ordering (pos_busy) from order to CompAck.
- Release serialization only on CompAck — never at send_comp.
- Order same-line conflicts strictly behind the in-flight transaction.
17. Key Takeaways
- The lifecycle is implemented as two FSMs — requester-side and home-side — that must stay consistent.
- The home is the point of serialization: it orders every access to a line into one coherence order.
- The home FSM has two paths — snoop holders or fetch memory — before completing.
- It holds a line's ordering until CompAck, so conflicting transactions queue behind, never interleave.
- Releasing ordering at send_comp instead of CompAck lets two transactions to one line overlap — a coherence hazard.
- Model both sides, hold serialization to CompAck, order conflicts behind; the model here is representative.
18. Quick Revision
The transaction state machine. The clean lifecycle is implemented as two FSMs: a requester-side machine (issue REQ, wait, send CompAck — the 7.1 machine generalized) and a richer home-side machine. The home is the point of serialization for a line: it orders each request, takes the snoop path (snoop holders, collect responses) or the memory path (fetch from the SN), sends the completion, and waits for CompAck. The load-bearing rule: the home holds the line's ordering until CompAck, not until it sends the completion — so a conflicting transaction to the same line is ordered strictly behind the in-flight one, never raced against it. Release the ordering at send_comp and the two interleave against a line still landing — a coherence hazard, the home-side twin of Chapter 7.1's early-retire. The two FSMs meet at the snoop and at CompAck; CompAck closes the requester and releases the home. Model both sides, hold serialization to CompAck. Representative model; Module 8 walks concrete request flows, starting with ReadShared.
Coming Next
Chapter 8.1 — ReadShared Flow. Module 7 built the transaction model — the four kinds, the lifecycle, and the two FSMs that run it. Module 8 puts it to work on concrete, named flows. Chapter 8.1 walks the ReadShared flow in full detail — every packet, state transition, and decision as a core fetches a line into Shared state — turning the general read of Chapter 7.1 into the specific, traceable sequence you will recognize on a waveform and in a coherence log.