PCIe · Module 13
Completion Types — The Form Follows the Request
PCIe defines Completions with and without data because different non-posted Requests need different answers. The form is a consequence of the original Request's semantics, not a choice the Completer makes — and a data-less Completion is still carrying a great deal of information.
Module 12 used Completions constantly and never took one apart. Chapter 12.1 received them, 12.3 generated and routed them, and both said the same thing: the packet details are Module 13's.
This is Module 13.
Why does PCIe define more than one Completion form, and how does the Request type determine whether the returning Completion carries data or only status and context?
1. Two Forms, and Why Two Is the Right Number
Every non-posted Request needs an answer. What differs is whether the answer contains anything besides the answer.
| The Request asked | The answer must convey | Form |
|---|---|---|
| give me these bytes | the outcome and the bytes | Completion with Data |
| do this, and tell me it happened | the outcome | Completion without Data |
Both are Completions. Both resolve a transaction, both are ID-routed home by the Requester ID, both carry the correlation context that lets the Requester match them. The difference is one region of the packet (Chapter 11.2).
Why not one universal form with a length of zero? Because Chapter 11.4 §1 established that payload presence is declared in the header's Fmt field, unambiguously, before any payload byte is examined. A receiver never has to infer whether data follows — and that property is worth more than the uniformity of a single form.
2. The Verified Mapping
3. The Form Is Not the Completer's Choice
This is the chapter's central architectural claim, and it has direct RTL consequences.
When a Completer finishes servicing a Memory Read, it does not decide whether to attach the data. The Request was a Memory Read; a Memory Read's successful resolution carries the requested bytes; therefore the Completion is a CplD. The decision was made at the Requester, when the operation was chosen.
4. A Cpl Is Not an Empty Packet
"Completion without Data" describes one absent region, not an absent packet.
A Cpl still carries a full header, and that header conveys everything the Requester needs to resolve the transaction:
| It carries | Why the Requester needs it |
|---|---|
| that this is a Completion | to route it to the completion engine rather than a request engine (Chapter 11.7 §9) |
| the Requester ID | it is the return route (Chapter 11.5 §7) |
| the correlation context | to identify which outstanding Request this resolves (Chapter 10.2 §6) |
| the completion status | the outcome — Chapter 13.2 |
| completer identity and progress context | fields owned by 13.2 and 13.3 |
So a Cpl for a Configuration Write is doing real work. It tells the Requester that a specific outstanding operation finished and how it finished. Without it, the Requester would have no way to know either fact, and its context would sit outstanding until it timed out.
No data payload ≠ no transaction information.
5. A Completion Is Not a Data Link ACK
Brief, because Chapter 12.2 §7 made the point already — but the abbreviation "Cpl" invites exactly this confusion, and a chapter about Completions without data is where it lands hardest.
| Completion (Cpl / CplD) | Data Link ACK/NAK | |
|---|---|---|
| Layer | Transaction Layer | Data Link Layer |
| Resolves | one non-posted transaction | reliable transfer between adjacent components |
| Travels | end to end, across the hierarchy | one link only |
| Visible to | the Requester's transaction logic | the link's own reliability machinery |
| Applies to | non-posted Requests | every TLP, including posted writes |
A posted Memory Write is covered by the link-layer mechanism and receives no Completion (Chapter 12.2). A Memory Read is covered by both, and they answer different questions: one says the packet arrived at the next component, the other says the operation was performed.
Module 14 owns the link-layer mechanism, including its packet format, which is deliberately not described here.
6. Form and Status Are Different Axes
A misconception worth killing early, because both are properties of the same packet and both describe "what happened."
| Completion form | Completion status | |
|---|---|---|
| Answers | does this packet carry data | how did the operation end |
| Determined by | the original Request's class | what the Completer encountered |
| Known to the Requester in advance | yes | no |
| Owned by | this chapter | Chapter 13.2 |
They are independent in the direction that matters: a Completion's form is fixed by the Request, and its status reports an outcome that could be anything. Whether a failing status changes what the payload region means is Chapter 13.2 §11's question, and this chapter deliberately does not answer it.
7. The Two Forms, Structurally
The figure's argument is the missing box. Both forms have the same header content; only CplD has the second region. A Cpl is not a smaller kind of answer — it is the same answer without a payload it was never going to need.
8. RTL — Completion Kind Selector
// SYNTHESIZABLE. Derive the Completion form from the ORIGINAL Request's
// class. The mapping is NORMATIVE (section 2); the enum and interface are
// ILLUSTRATIVE normalized internal metadata.
package cpl_form_pkg;
// Normalized Request classes this model represents. NOT PCIe encodings —
// Chapter 11.7 owns the Fmt/Type taxonomy and this consumes its output.
typedef enum logic [2:0] {
REQ_MEM_RD = 3'd0,
REQ_MEM_WR = 3'd1, // POSTED — present so it can be rejected explicitly
REQ_IO_RD = 3'd2,
REQ_IO_WR = 3'd3,
REQ_CFG_RD = 3'd4,
REQ_CFG_WR = 3'd5,
REQ_OTHER = 3'd7
} req_kind_e;
typedef enum logic [1:0] {
CPL_NO_DATA = 2'd0, // Cpl
CPL_WITH_DATA = 2'd1, // CplD
CPL_UNSUPPORTED = 2'd3 // not represented by this model
} cpl_kind_e;
endpackageimport cpl_form_pkg::*;
module cpl_kind_select (
input logic in_valid,
input req_kind_e req_kind, // the ORIGINAL Request's class
output cpl_kind_e cpl_kind,
// The Request class does not call for a Completion at all. Distinct from
// "unsupported": a posted write is perfectly legal and simply has no
// Completion, and conflating the two produces a design that either emits
// a Completion for a posted write or reports a legal packet as an error.
output logic no_completion_expected,
output logic unsupported
);
always_comb begin
cpl_kind = CPL_UNSUPPORTED;
no_completion_expected = 1'b0;
unsupported = 1'b1;
if (in_valid) begin
unique case (req_kind)
// Every READ returns the requested bytes.
REQ_MEM_RD,
REQ_IO_RD,
REQ_CFG_RD: begin
cpl_kind = CPL_WITH_DATA; unsupported = 1'b0;
end
// Non-posted WRITES return the outcome and nothing else.
REQ_IO_WR,
REQ_CFG_WR: begin
cpl_kind = CPL_NO_DATA; unsupported = 1'b0;
end
// POSTED. No Completion is owed, and none must be produced.
REQ_MEM_WR: begin
cpl_kind = CPL_UNSUPPORTED;
no_completion_expected = 1'b1;
unsupported = 1'b0;
end
default: begin
cpl_kind = CPL_UNSUPPORTED; unsupported = 1'b1;
end
endcase
end
end
endmoduleClassification: synthesizable (package: compile-time).
Architecture. A pure function of the Request class. It does not consult the target's result — deliberately, because §3's claim is that the form is fixed by the Request and a target that produced no data has a status problem, not a form one.
State. None.
Contract. The caller supplies the normalized Request class from the header decode (Chapter 11.7), retained alongside the Request context (Chapter 12.3 §4). Downstream relies on cpl_kind being stable while in_valid and req_kind are held.
Failure — four, and the first is the one this chapter exists to prevent. Deriving the form from whether the target produced data means a failed read produces a Cpl — a form the Requester was not expecting for that Request, which turns a reportable status into an unrecognisable packet. Mapping I/O Write to CPL_WITH_DATA because "writes carry data" confuses the request's payload with the completion's. Treating REQ_MEM_WR as unsupported rather than no-Completion-expected reports a legal posted write as an error. And omitting no_completion_expected entirely lets a posted write fall to the default arm and, in a design that emits something for every arm, produce a Completion for a transaction that must not have one.
Deliberately simplified: the verified subset of §2; no Locked-Read Completion path; no atomic operations; no status — Chapter 13.2 owns it; no split decision — Chapter 13.3 owns it.
9. RTL — Completion TX Holding Stage
// SYNTHESIZABLE. A single-entry decoupled stage that owns a Completion
// descriptor and, when the form calls for it, its payload — until the
// return TX path accepts them.
// The form/payload coupling is a NORMATIVE consequence of section 2; the
// interface, depth and payload width are ILLUSTRATIVE.
import cpl_form_pkg::*;
module cpl_tx_holding #(
parameter int DATA_W = 128,
parameter int CORR_W = 16
) (
input logic clk,
input logic rst_n,
// ---- From the Completion generator -----------------------------------
// Descriptor and payload arrive on ONE handshake, for Chapter 12.2
// section 4's reason: a response whose identity and data can drift apart
// is a response that can be delivered wrong.
input logic in_valid,
output logic in_ready,
input cpl_kind_e in_kind,
input logic [15:0] in_requester_id,
input logic [CORR_W-1:0] in_corr,
input logic [DATA_W-1:0] in_data,
input logic in_data_valid, // must track the form
// ---- To the return TX path -------------------------------------------
output logic out_valid,
input logic out_ready,
output cpl_kind_e out_kind,
output logic [15:0] out_requester_id,
output logic [CORR_W-1:0] out_corr,
output logic [DATA_W-1:0] out_data,
output logic out_data_valid,
// The offered descriptor's form and payload presence disagree. Reported,
// and the offer is refused rather than silently corrected.
output logic form_mismatch
);
// ---- Form/payload consistency, checked BEFORE acceptance -------------
// A CplD without data and a Cpl with data are both malformed in this
// model's terms, and neither may be taken into ownership.
wire form_ok = (in_kind == CPL_WITH_DATA) ? in_data_valid
: (in_kind == CPL_NO_DATA) ? !in_data_valid
: 1'b0;
logic hold_valid_q;
cpl_kind_e kind_q;
logic [15:0] rid_q;
logic [CORR_W-1:0] corr_q;
logic [DATA_W-1:0] data_q;
logic dv_q;
logic mism_q;
wire consume = hold_valid_q && out_ready;
// in_ready is independent of in_valid — no combinational loop — and is
// additionally gated on form_ok so an inconsistent offer is never owned.
assign in_ready = (!hold_valid_q || out_ready) && form_ok;
wire accept = in_valid && in_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
hold_valid_q <= 1'b0; kind_q <= CPL_UNSUPPORTED;
rid_q <= '0; corr_q <= '0; data_q <= '0; dv_q <= 1'b0;
mism_q <= 1'b0;
end else begin
// accept has priority over consume, so a same-cycle replacement ends
// holding the NEW descriptor rather than going empty.
if (accept) begin
hold_valid_q <= 1'b1;
kind_q <= in_kind;
rid_q <= in_requester_id;
corr_q <= in_corr;
// Payload is captured ONLY for the form that carries one. A Cpl
// therefore cannot present stale data from a previous CplD, which
// is section 11's second debugging scenario.
data_q <= (in_kind == CPL_WITH_DATA) ? in_data : '0;
dv_q <= (in_kind == CPL_WITH_DATA);
end else if (consume) begin
hold_valid_q <= 1'b0;
end
if (in_valid && !form_ok) mism_q <= 1'b1;
end
end
assign out_valid = hold_valid_q;
assign out_kind = kind_q;
assign out_requester_id = rid_q;
assign out_corr = corr_q;
assign out_data = data_q;
assign out_data_valid = dv_q;
assign form_mismatch = mism_q;
endmoduleClassification: synthesizable.
Architecture. A single-entry decoupled stage — the same shape as Chapter 11.6 §10's policy stage — with one addition that is specific to this chapter: the form and the payload presence are checked for consistency before the offer can be accepted.
State. One holding register: validity, form, identity, correlation and payload.
Cycle behaviour.
hold_valid_q | out_ready | form_ok | in_ready |
|---|---|---|---|
| 0 | x | 1 | 1 — accept |
| 1 | 0 | 1 | 0 — held descriptor untouchable |
| 1 | 1 | 1 | 1 — same-cycle replacement |
| x | x | 0 | 0 — refused and reported |
Contract. Downstream relies on out_valid staying asserted until out_ready, and on the form, identity, correlation and payload being stable for the whole of that time. It relies on out_data_valid agreeing with out_kind — which this module enforces on capture rather than trusting on input.
Failure — four. Capturing in_data unconditionally lets a Cpl carry the previous CplD's payload — stale data on a packet that has no data region, which downstream logic may or may not ignore. Omitting the form_ok gate accepts an inconsistent descriptor and propagates it. Giving consume priority over accept loses a descriptor on same-cycle replacement. And driving out_* from the inputs when empty re-couples the generator's live bus to an in-flight offer.
Deliberately simplified: one payload beat; no split decision (Chapter 13.3); no status field (Chapter 13.2); no arbitration against other outbound traffic; no flow control.
Production implication: a real return path also carries status, byte count and address progress, arbitrates, and honours flow control. The form/payload coupling and the ownership discipline above are unchanged by all of it.
10. Assertions
// SVA over cpl_kind_select and cpl_tx_holding. These assert the NORMATIVE
// Request-to-form mapping of section 2 and the LOCAL ownership contract of
// section 9. They do NOT assert how many Completion packets a Request
// produces — Chapter 13.3 owns that — and they say nothing about status.
// FORM — P1: THE MAPPING PROPERTY. Every read-style Request maps to CplD.
property p_reads_map_to_cpld;
@(posedge clk) disable iff (!rst_n)
(in_valid && (req_kind inside {REQ_MEM_RD, REQ_IO_RD, REQ_CFG_RD}))
|-> (cpl_kind == CPL_WITH_DATA) && !unsupported;
endproperty
a_reads_cpld : assert property (p_reads_map_to_cpld);
// FORM — P2: every non-posted write maps to Cpl.
property p_np_writes_map_to_cpl;
@(posedge clk) disable iff (!rst_n)
(in_valid && (req_kind inside {REQ_IO_WR, REQ_CFG_WR}))
|-> (cpl_kind == CPL_NO_DATA) && !unsupported;
endproperty
a_np_writes_cpl : assert property (p_np_writes_map_to_cpl);
// FORM — P3: a POSTED write yields no Completion at all, and is not an
// error. The property that separates "no Completion owed" from
// "unsupported" (section 8).
property p_posted_no_completion;
@(posedge clk) disable iff (!rst_n)
(in_valid && (req_kind == REQ_MEM_WR))
|-> (no_completion_expected && !unsupported
&& (cpl_kind != CPL_WITH_DATA) && (cpl_kind != CPL_NO_DATA));
endproperty
a_posted : assert property (p_posted_no_completion);
// FORM — P4: an unsupported Request class never yields a real form.
property p_unsupported_no_form;
@(posedge clk) disable iff (!rst_n)
(in_valid && unsupported) |-> (cpl_kind == CPL_UNSUPPORTED);
endproperty
a_unsupported : assert property (p_unsupported_no_form);
// FORM — P5: the form is derived from the REQUEST ONLY. Stated as
// stability against everything else the environment can change while the
// request class is held — the checkable form of section 3's claim.
property p_form_depends_only_on_request;
@(posedge clk) disable iff (!rst_n)
($stable(req_kind) && in_valid && $past(in_valid))
|-> $stable({cpl_kind, no_completion_expected, unsupported});
endproperty
a_form_pure : assert property (p_form_depends_only_on_request);
// HOLDING — P6: A Cpl NEVER PRESENTS PAYLOAD. The chapter's central
// ownership property, and the one that catches a stale-data leak.
property p_cpl_has_no_payload;
@(posedge clk) disable iff (!rst_n)
(out_valid && (out_kind == CPL_NO_DATA))
|-> (!out_data_valid && (out_data == '0));
endproperty
a_cpl_no_payload : assert property (p_cpl_has_no_payload);
// HOLDING — P7: a CplD always presents payload in the teaching subset.
property p_cpld_has_payload;
@(posedge clk) disable iff (!rst_n)
(out_valid && (out_kind == CPL_WITH_DATA)) |-> out_data_valid;
endproperty
a_cpld_payload : assert property (p_cpld_has_payload);
// HOLDING — P8: an inconsistent offer is never accepted, and is reported.
property p_inconsistent_refused;
@(posedge clk) disable iff (!rst_n)
(in_valid && !form_ok) |-> (!in_ready ##1 form_mismatch);
endproperty
a_inconsistent : assert property (p_inconsistent_refused);
// HOLDING — P9: OWNERSHIP. Everything held is stable while the return TX
// path stalls — form, identity, correlation AND payload together.
property p_held_stable;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready)
|=> (out_valid && $stable({out_kind, out_requester_id, out_corr,
out_data, out_data_valid}));
endproperty
a_held_stable : assert property (p_held_stable);
// HOLDING — P10: an offer is never withdrawn without a handshake.
property p_offer_not_withdrawn;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready) |=> out_valid;
endproperty
a_no_withdraw : assert property (p_offer_not_withdrawn);
// HOLDING — P11: SAME-CYCLE REPLACEMENT ends holding the NEW descriptor.
property p_simultaneous_replace;
@(posedge clk) disable iff (!rst_n)
(out_valid && out_ready && in_valid && form_ok)
|=> (out_valid && (out_kind == $past(in_kind))
&& (out_corr == $past(in_corr)));
endproperty
a_replace : assert property (p_simultaneous_replace);
// HOLDING — P12: PAYLOAD STAYS ATTACHED TO ITS DESCRIPTOR. Keyed on the
// correlation field, so a payload that arrived with a different descriptor
// cannot satisfy it. (exp_data is a testbench map keyed by corr.)
property p_payload_matches_descriptor;
@(posedge clk) disable iff (!rst_n)
(out_valid && out_ready && (out_kind == CPL_WITH_DATA))
|-> (out_data == exp_data[out_corr]);
endproperty
a_payload_attached : assert property (p_payload_matches_descriptor);
// CONSERVATION — P13: one accepted response event produces one descriptor
// handshake. (in_count/out_count are testbench counters.)
property p_one_out_per_in;
@(posedge clk) disable iff (!rst_n)
(out_valid && out_ready) |-> (out_count + 1 <= in_count);
endproperty
a_conserved : assert property (p_one_out_per_in);
// RESET — P14: reset clears local ownership.
property p_reset_clears;
@(posedge clk)
!rst_n |=> (!out_valid && !out_data_valid);
endproperty
a_reset : assert property (p_reset_clears);P1–P3 are the normative mapping split into three properties because they fail on three different confusions. P1 catches a design that derives the form from the target's result. P2 catches "writes carry data, so a write's Completion carries data" — the confusion between the Request's payload and the Completion's. P3 catches the more damaging one: treating a posted write as an unsupported class, which either reports legal traffic as an error or, worse, lets it fall through to a default arm that emits something.
P5 is §3's claim made checkable. The form must be a function of the Request class and of nothing else — not of the target's result, not of the status, not of anything that can vary while the class is held. A design that consulted the target would fail it the first time two Requests of the same class got different results.
P6 is the property this chapter's holding stage exists to satisfy. A Cpl presenting a stale payload from a previous CplD is not obviously wrong to a receiver that ignores the data region for that form — so the bug can survive indefinitely until something downstream does look. Asserting out_data == '0 as well as !out_data_valid makes the leak structurally impossible rather than merely flagged.
P12 is keyed on the correlation field rather than on position. A payload/descriptor mismatch under backpressure would satisfy a positional check; keying on corr means the property can only pass if the payload genuinely belongs to the descriptor carrying it.
11. Verification
Monitors observe: the Request class entering the selector; the selector's outputs; the holding stage's input and output handshakes with form, correlation and payload.
The scoreboard holds its own Request-to-form mapping table, written from §2. It must not import cpl_form_pkg or call cpl_kind_select. It maintains its own corr → expected payload map for P12.
And it must check the posted case positively: for every Memory Write in the run, assert that zero Completion descriptors were generated. A scoreboard that merely fails to look cannot distinguish correct silence from an unchecked path.
Form selection
- Each read class — Memory, I/O, Configuration. Verify
CPL_WITH_DATAand!unsupported(P1). - Each non-posted write class — I/O, Configuration. Verify
CPL_NO_DATA(P2). - Memory Write. Verify
no_completion_expected,!unsupported, and no descriptor produced (P3). - An unsupported class. Verify
unsupportedand no form (P4), and that the report says unsupported-by-this-model, not malformed per PCIe (Chapter 11.7 §13). - The same class with different target results. Verify the form is identical (P5) — the direct test that the form does not depend on the result.
- Every class back to back, in every ordered pair. Verify no state carries.
Holding stage
- A CplD offered and accepted. Verify payload presented and stable.
- A Cpl offered and accepted. Verify
out_data_validlow andout_datazero (P6). - A CplD immediately followed by a Cpl. Verify the Cpl carries no residue of the CplD's payload — the stale-data test, and the one a design that captures
in_dataunconditionally fails. - The return TX path stalled for a long run. Verify form, identity, correlation and payload all stable together (P9), and that the offer is not withdrawn (P10).
- Same-cycle consume and accept. Verify the stage ends holding the new descriptor (P11); repeat with alternating Cpl/CplD so a form leak across the replacement is visible.
- A long alternating Cpl/CplD stream at full rate with independent backpressure. Verify P12 throughout.
Negative
CPL_WITH_DATAoffered within_data_validlow. Verify refusal andform_mismatch(P8).CPL_NO_DATAoffered within_data_validhigh. Same.CPL_UNSUPPORTEDoffered. Verify it is never accepted.- An inconsistent offer immediately followed by a consistent one. Verify the good one is accepted and the bad one left no residue.
- Reset while a descriptor is held. Verify
out_validandout_data_validclear (P14). - Reset between the two halves of a same-cycle replacement. Verify no partial descriptor emerges.
Coverage should include: every Request class in the subset; both forms; the posted and unsupported paths; both directions of form/payload inconsistency; the stall, replacement and reset cases; and Cpl-after-CplD specifically.
12. Debugging
A Memory Read produces a Cpl without data
Form selection, and the likely cause is that it was derived from the wrong thing.
A Memory Read's normal successful Completion is a CplD (§2). A Cpl arriving for one is not a valid successful answer.
Two candidates. The selector derived the form from whether the target produced data — so a read that returned nothing produced a Cpl. That is the §3 error, and the fix is that a read which produced no data is a status condition (Chapter 13.2), reported in a CplD or handled per the status rules, not silently reshaped into a different packet form.
Or the retained Request class was wrong: the Completer recorded the wrong class at Request time, so it built the form for a different operation.
The observation: print the retained Request class alongside the emitted form. If the class is REQ_MEM_RD and the form is CPL_NO_DATA, the mapping is wrong; if the class is itself wrong, the fault is upstream in the Request capture (Chapter 12.3 §4).
A Configuration Write produces a CplD with stale data
A form error and a payload leak, and they may be one bug or two.
A Configuration Write's normal Completion is a Cpl (§2), so the form is wrong. And it is carrying data, which means the payload path was not gated on the form.
Check form_mismatch first. If it is set, an inconsistent descriptor was offered and refused — so the generator is the problem and the holding stage did its job. If it is clear, the generator offered a self-consistent CPL_WITH_DATA descriptor, and the form selection is wrong rather than the payload gating.
The stale value itself is the second clue: if the data matches the previous CplD's payload, the capture is unconditional (§9's first failure). If it matches nothing, it is uninitialised.
The form is right but the payload belongs to a previous response
A holding-stage ownership bug, and the correlation field identifies it immediately.
Compare out_corr against the correlation of the response the payload belongs to. If they differ, descriptor and payload came apart — either separate handshakes with no association mechanism (Chapter 12.2 §17), or a bypass path presenting live input data alongside a stored descriptor.
The signature that distinguishes them: does it only happen under backpressure? A bypass path is invisible when offers are consumed immediately. Separate handshakes fail whenever the two paths run at different rates, backpressure or not.
P12 catches both, because it is keyed on corr rather than on position.
A Data Link ACK is observed but the Requester is still outstanding
Nothing is wrong yet, and this is a layer confusion rather than a bug.
The link-layer acknowledgement says the packet reached the adjacent component. It says nothing about whether the operation was performed or whether a Completion has been generated (§5).
The Requester's context stays outstanding until a Completion resolves it — which requires the target to have executed the operation, the Completer to have built a descriptor, the descriptor to have been queued, and the packet to have traversed the fabric back (Chapter 12.3 §1's seven boundaries).
So the correct next observation is not at the link layer at all. Walk Chapter 12.3 §13's ladder: did the target respond, was a descriptor built, did it leave the generation queue?
13. Common Misconceptions
- "Every Completion carries data." Non-posted writes are resolved by a Cpl, which carries no payload (§2).
- "Cpl is just an ACK." It is a Transaction Layer packet resolving one non-posted Request end to end. The link-layer acknowledgement is a different mechanism at a different layer covering different traffic (§5).
- "CplD is only for Memory Read." I/O Reads and Configuration Reads are also answered with data (§2).
- "The Requester chooses whether the Completion contains data." It chooses the Request; the form follows from that. Neither side picks the form independently (§3).
- "A data-less Completion carries no useful information." It carries the routing identity, the correlation context and the status — everything needed to resolve the transaction (§4).
- "Completion form and Completion Status are the same concept." Form is fixed by the Request and known in advance; status reports what the Completer encountered and is not (§6).
- "One Request always produces one Completion packet." A response larger than one payload may carry is divided (Chapter 12.1 §8); Chapter 13.3 owns the rules.
- "A CplD's payload can be regenerated later." It is owned packet state from the moment the descriptor is accepted, exactly like an address or an attribute (Chapter 11.6 §7).
- "Completion type is determined by arrival order." Nothing about a packet is determined by arrival order (Chapter 12.4 §4). The form was fixed when the Request was issued.
- "Completion form and packet routing are the same thing." Both Completion forms are ID-routed identically (Chapter 11.5 §7). The form says what the packet contains; routing says where it goes.
- "A posted Memory Write gets a Cpl confirming it." It gets no Completion at all (Chapter 12.2), which is why §8 distinguishes no Completion expected from unsupported.
14. Understanding Check
15. What's Next
This chapter took the Completion apart along one axis: does the packet carry data, and who decided. The answer turned out to be that nobody decides at Completion time — the Request already did, and the Completer's job is to derive the form from retained context rather than from what the target happened to produce.
Chapter 13.2 — Completion Status takes the other axis: how did the operation end. Successful, unsupported, retry, aborted — what each means, which Requests each can apply to, and how a design should normalise a protocol status into a local result without smuggling recovery policy into a decoder.
Chapter 13.3 then owns the fields and rules this chapter kept deferring — Byte Count, Lower Address, and the constraints on dividing a response across several Completions — and 13.4 owns the ordering rules that govern how Completions may move relative to other traffic.
The idea to carry forward: the form of an answer is fixed by the question, and a Completion without data is still a complete answer.