PCIe · Module 13
Completion Status — How the Operation Actually Ended
A Completion arriving is not a Completion succeeding. The status field reports the Completer's outcome — successful, unsupported, configuration-retry, or aborted — and a design must normalise it once, retire the transaction exactly once, and keep recovery policy out of the decoder.
Chapter 13.1 settled what a Completion contains. It said nothing about whether the operation worked.
Module 12 quietly assumed it did. Every trace, every RTL block, every property treated an arriving Completion as a successful one — and Chapter 12.3 §7 carried an err bit it explicitly labelled an abstraction and refused to decode.
This is that field.
What does the Completion Status field actually tell the Requester about the result of a non-posted Request, and how should hardware distinguish successful completion from unsupported, retry, or aborted outcomes?
1. Module 12's Assumption, Named
Look back at what the previous module actually checked.
| Chapter | What it did with the outcome |
|---|---|
| 12.1 | credited returned DW and retired the context |
| 12.3 | carried an err bit through the pipeline, abstracted and undecoded |
| 12.4 | traced successful transactions throughout |
None of them asked whether the operation worked. That was the right scope — you cannot reason about failure handling before you can reason about the transaction — but it leaves a specific gap: a design built only from Module 12 will treat every arriving Completion as success, and the first Unsupported Request it receives will be delivered to a client as data.
This chapter closes it, and the closing has three parts: decode the status, keep the decoder free of policy, and retire the transaction exactly once whether it succeeded or failed.
2. The Verified Status Encodings
3. Four Outcomes, Not Two
The instinct is binary — worked, or did not. The field distinguishes four things, and the distinctions are the useful part.
| Status | The Completer is saying | The transaction is |
|---|---|---|
| SC | I performed the operation | finished, successfully |
| UR | this operation is not supported here | finished, unsuccessfully |
| CRS | I am not ready for this configuration access | finished — and the access may be reissued by software |
| CA | I could not complete an operation I understood | finished, unsuccessfully |
All four resolve the transaction. The Requester's context retires in every case (§9). What differs is what the local client is told, and — for CRS — what platform software may subsequently do.
4. SC — Successful Completion
The Completer reports that it successfully serviced the Request under PCIe semantics.
That is a precise and bounded claim, and it is worth being clear about what it does not extend to.
| SC means | SC does not mean |
|---|---|
| the Completer serviced the Request | the local client has consumed the returned data |
| the operation completed at the Completer | the effect is globally visible everywhere in the system |
| this Completion is a valid successful response | the higher-level software operation is complete |
The first row of the right column is Chapter 12.1 §12's distinction, arriving from the status side: protocol resolution and local delivery are different events, and SC is a statement about the former.
The second row matters because designs assume it. SC says the Completer did the work. What is observable, where, and when is governed by ordering rules (Chapter 13.4) and by the platform — not by the status field of one Completion.
And the third row is the software boundary. A driver's operation may involve many transactions; SC on one of them says one of them worked.
5. UR — Unsupported Request
The Request reached a Completer or context where the operation is not supported or not recognised under the applicable rules.
The practical shape of a UR, at the level this chapter can support: an operation arrived somewhere it is not supported. Retrying it unchanged will produce the same result, because nothing about the situation is temporary — which is exactly the property that distinguishes it from CRS.
6. CRS — Configuration Request Retry Status
The subtlest of the four, and the one most often over-generalised.
CRS exists only for Configuration Requests. It is not a legal status in response to a Memory or I/O Request.
What it means. A device is present but not yet ready to service configuration accesses. Rather than fail the access or leave it unanswered, the Function returns a Completion carrying CRS — a defined "not yet" for a specific, narrow class of access.
7. CA — Completer Abort
The Completer accepted and interpreted the Request sufficiently to generate a Completion, and could not complete the operation successfully.
The distinguishing feature is that the Completer got far enough to answer. It understood what was being asked; it could not deliver.
The status does not say why, and this chapter does not invent reasons. The exact conditions under which a Completer generates CA are specification-defined, and a design's response depends on what the Completer is and what the operation was — which is local knowledge, not protocol knowledge.
8. UR Versus CA
The interview question, and a genuinely useful distinction.
| UR | CA | |
|---|---|---|
| The Completer's position | this operation is not supported here | I could not complete this operation |
| About the operation | it should not have been made to this destination | it was a reasonable request that failed |
| Reissuing unchanged | will produce UR again — nothing is temporary | depends entirely on why, which the status does not say |
| Points the investigation at | addressing, enumeration, configuration, capability | the Completer and the operation |
9. Status Is Not a Local Error Code
A design will translate Completion Status into something local — a DMA descriptor status, a CPU load fault, a driver event, an internal error enum. That translation is implementation-defined and it should happen exactly once, in one place.
10. RTL — Completion Status Decoder
// SYNTHESIZABLE. Decode the PCIe Completion Status field into a normalized
// internal result.
// The 3-bit field width and the SC/UR/CRS/CA encodings: NORMATIVE (section 2).
// The result enum, the output names and the "reserved is not success"
// stance: ILLUSTRATIVE normalized internal metadata and local policy.
package cpl_status_pkg;
// NORMATIVE PCIe Completion Status encodings.
localparam logic [2:0] CS_SC = 3'b000; // Successful Completion
localparam logic [2:0] CS_UR = 3'b001; // Unsupported Request
localparam logic [2:0] CS_CRS = 3'b010; // Configuration Request Retry
localparam logic [2:0] CS_CA = 3'b100; // Completer Abort
// NORMALIZED INTERNAL RESULT — not a wire encoding.
typedef enum logic [2:0] {
RESP_OK = 3'd0,
RESP_UNSUPPORTED = 3'd1,
RESP_RETRY_CONFIG = 3'd2, // CRS — deliberately NOT named "retry"
RESP_ABORTED = 3'd3,
RESP_UNKNOWN = 3'd7 // anything else
} cpl_result_e;
endpackageimport cpl_status_pkg::*;
module cpl_status_decode (
input logic in_valid,
input logic [2:0] raw_status, // the PCIe Completion Status field
output cpl_result_e result,
// The encoding is one of the four defined values.
output logic status_recognised,
// Convenience flags, all derived from `result` so they cannot disagree
// with it. Each is a distinct condition for a distinct investigation
// (section 8) — NOT collapsed into one "error" bit.
output logic is_success,
output logic is_unsupported,
output logic is_config_retry,
output logic is_aborted,
output logic is_unknown
);
always_comb begin
// The default is RESP_UNKNOWN, never RESP_OK. An encoding this decoder
// does not recognise must NEVER become success: treating an unknown
// outcome as a successful one delivers whatever accompanied it to a
// client that asked for real data.
result = RESP_UNKNOWN;
status_recognised = 1'b0;
if (in_valid) begin
unique case (raw_status)
CS_SC: begin result = RESP_OK; status_recognised = 1'b1; end
CS_UR: begin result = RESP_UNSUPPORTED; status_recognised = 1'b1; end
CS_CRS: begin result = RESP_RETRY_CONFIG; status_recognised = 1'b1; end
CS_CA: begin result = RESP_ABORTED; status_recognised = 1'b1; end
default: begin
result = RESP_UNKNOWN;
status_recognised = 1'b0;
end
endcase
end
end
// Derived from `result`, not computed independently from raw_status.
// Two independent decodes of the same field is exactly the divergence
// Chapter 11.7 section 8 warns about.
assign is_success = in_valid && (result == RESP_OK);
assign is_unsupported = in_valid && (result == RESP_UNSUPPORTED);
assign is_config_retry = in_valid && (result == RESP_RETRY_CONFIG);
assign is_aborted = in_valid && (result == RESP_ABORTED);
assign is_unknown = in_valid && (result == RESP_UNKNOWN);
endmoduleClassification: synthesizable (package: compile-time).
Architecture. A pure decode with a fail-closed default. Every convenience flag is derived from result rather than recomputed, so the two representations cannot diverge.
State. None.
Contract. Consumers rely on result being the protocol interpretation and on RESP_UNKNOWN never being success. No consumer may read raw_status directly — that is §9's architecture, and this module is the one place the field is interpreted.
Failure — four, and the first is the dangerous one. A default: result = RESP_OK — or an if (raw_status != CS_UR && raw_status != CS_CA) result = RESP_OK written as a shortcut — turns every undefined encoding into success, and a corrupted status bit then delivers garbage as valid data. Computing is_success independently from raw_status creates a second decode that can disagree with the first. Collapsing UR, CRS and CA into one error output destroys the distinction §8 exists for. And naming RESP_RETRY_CONFIG "retry" invites a consumer to act on it as an instruction rather than as a report — which is why the name carries its scope.
Deliberately simplified: no request-class legality check (§11 is separate); no recovery policy; no error reporting or logging behaviour; no interaction with Completion Timeout.
Production implication: a real design also applies the specification's required error-reporting responses to each status. Those responses are policy layered on top of this decode, not part of it.
11. RTL — Request-Aware Status Legality
A second, distinct block, because status meaning depends on what was asked.
// SYNTHESIZABLE. Check a decoded Completion Status against the class of the
// Request it claims to answer.
// The CRS restriction (Configuration Requests only) is NORMATIVE (section 2).
// The anomaly outputs and the detect-and-report stance are LOCAL POLICY.
import cpl_status_pkg::*;
import cpl_form_pkg::*; // Chapter 13.1's Request classes
module cpl_status_legality (
input logic in_valid,
input req_kind_e req_kind, // the ORIGINAL Request's class
input cpl_result_e result, // from cpl_status_decode
input logic status_recognised,
// The status is a defined value AND is applicable to this Request class.
output logic combination_ok,
// A defined status appeared against a Request class it does not apply to.
// Distinct from an unrecognised encoding: this one is well-formed and
// wrong, which points at a different fault (section 12).
output logic protocol_anomaly,
// An unrecognised encoding, propagated for reporting.
output logic unrecognised_status
);
wire is_config_req = (req_kind == REQ_CFG_RD) || (req_kind == REQ_CFG_WR);
// NORMATIVE: CRS applies to Configuration Requests. It is not a legal
// status in response to a Memory or I/O Request (section 2, section 6).
wire crs_misapplied = (result == RESP_RETRY_CONFIG) && !is_config_req;
// A posted Request has no Completion at all (Chapter 13.1 section 8), so
// any status claiming to answer one is anomalous by construction.
wire posted_answered = (req_kind == REQ_MEM_WR);
assign unrecognised_status = in_valid && !status_recognised;
assign protocol_anomaly = in_valid && status_recognised
&& (crs_misapplied || posted_answered);
assign combination_ok = in_valid && status_recognised
&& !crs_misapplied && !posted_answered;
endmoduleClassification: synthesizable.
Architecture. A legality check consuming the normalized result and the original Request class — neither the raw status field nor the raw packet. It reports; it does not correct.
State. None.
Contract. Consumers rely on protocol_anomaly meaning a defined status appeared where it does not apply, and on unrecognised_status meaning an encoding outside the defined set — two different faults pointing at two different investigations (§12).
Failure — three, and the first is a design-stance error. "Correcting" a misapplied CRS to RESP_UNKNOWN inside this block hides where the problem was: the status was CRS, and something produced it against a Memory Request. Report the combination; do not rewrite the value. Beyond that: omitting the posted check lets a Completion for a posted write be processed as a normal answer; and treating an unrecognised encoding as an anomaly rather than as its own condition merges a corruption signature with a legality signature.
Deliberately simplified: the two legality rules this chapter can support normatively; no enumeration of the conditions under which a Completer must generate each status — those are specification-defined and beyond this chapter's verified scope.
Production implication: a real design applies the full set of legality and error-reporting rules. What does not change is that legality is checked against the Request class, because status applicability depends on it.
12. RTL — Result Retirement
// SYNTHESIZABLE. Retire an outstanding transaction exactly once, on success
// or on failure, and deliver an explicit local result either way.
// Retiring on both outcomes is a LOCAL CONTRACT; the payload-on-success
// restriction is a deliberate teaching simplification (see below).
import cpl_status_pkg::*;
module cpl_result_retire #(
parameter int DATA_W = 128,
parameter int CTX_W = 5
) (
input logic clk,
input logic rst_n,
// ---- A correlated, decoded, legality-checked Completion --------------
input logic in_valid,
input logic [CTX_W-1:0] in_ctx,
input cpl_result_e in_result,
input logic in_combination_ok,
input logic in_final, // completes the expected extent
input logic [DATA_W-1:0] in_data,
input logic in_has_data,
// ---- Local result out ------------------------------------------------
output logic out_valid,
input logic out_ready,
output logic [CTX_W-1:0] out_ctx,
output cpl_result_e out_result,
output logic [DATA_W-1:0] out_data,
output logic out_data_valid,
// ---- Context retirement ----------------------------------------------
// Pulses once per finished transaction, successful or not. A failed
// transaction that never retires holds a correlation resource forever.
output logic retire,
output logic [CTX_W-1:0] retire_ctx
);
// THE PAYLOAD RULE, and its scope. This teaching receiver consumes the
// returned payload ONLY when the decoded status is successful. Production
// behaviour must follow the Base Specification's exact status/data rules,
// which this chapter does not publish — so the model takes the
// conservative position rather than guessing a permissive one.
wire use_payload = (in_result == RESP_OK) && in_has_data;
// Retire on ANY resolved outcome. A failure is a completed transaction:
// the operation is over, and the context must not be held for it.
wire resolved = in_valid && in_final;
logic v_q, dv_q;
logic [CTX_W-1:0] ctx_q;
cpl_result_e res_q;
logic [DATA_W-1:0] data_q;
wire consume = v_q && out_ready;
// Single-entry decoupled stage: same shape as Chapter 13.1 section 9.
wire in_ready_int = !v_q || out_ready;
wire accept = resolved && in_ready_int;
// retire pulses on the ACCEPTED resolution, so it happens exactly once
// even if the local result path stalls for many cycles afterwards.
assign retire = accept;
assign retire_ctx = in_ctx;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_q <= 1'b0; ctx_q <= '0; res_q <= RESP_UNKNOWN;
data_q <= '0; dv_q <= 1'b0;
end else begin
if (accept) begin
v_q <= 1'b1;
ctx_q <= in_ctx;
// An anomalous combination is reported as UNKNOWN to the client, not
// as whatever the status happened to say. The anomaly itself is
// reported separately by section 11 — this module only makes sure a
// client never receives an outcome the design could not validate.
res_q <= in_combination_ok ? in_result : RESP_UNKNOWN;
data_q <= use_payload ? in_data : '0;
dv_q <= use_payload;
end else if (consume) begin
v_q <= 1'b0;
end
end
end
assign out_valid = v_q;
assign out_ctx = ctx_q;
assign out_result = res_q;
assign out_data = data_q;
assign out_data_valid = dv_q;
endmoduleClassification: synthesizable.
Architecture. A single-entry decoupled stage that produces exactly one local result per resolved transaction, and pulses retire on the same event.
State. One holding register plus the retirement pulse.
Cycle behaviour. retire fires on acceptance, not on delivery — so a stalled client cannot hold a correlation resource (Chapter 12.1 §12, applied to the failure path as well as the success path).
Contract. The client relies on receiving an explicit result for every transaction, successful or not, and on the payload being present only when the result is success. The context table relies on retire pulsing exactly once per finished transaction.
Failure — five. Retiring only on success leaks a context on every failure, and a device that returns UR to a probe sequence exhausts the table. Retiring on delivery rather than acceptance ties a protocol resource to a local stall. Delivering the payload on a failing status hands the client bytes whose validity the design could not establish. Reporting an anomalous combination's status verbatim tells the client an outcome the design flagged as impossible. And dropping a Completion whose status is unrecognised leaves the context outstanding forever — the transaction did resolve; the design simply did not understand how.
Deliberately simplified: payload consumed only on success — a deliberately conservative reading, stated as such; no per-status recovery; no error-reporting responses; no Completion Timeout interaction; one result at a time.
13. Assertions
// SVA over cpl_status_decode, cpl_status_legality and cpl_result_retire.
// These assert the NORMATIVE status encodings and applicability of section 2
// plus LOCAL retirement and ownership contracts. They assert nothing about
// system recovery behaviour, error reporting, or Completion Timeout.
// DECODE — P1: each defined encoding maps to exactly its result.
property p_sc_maps_ok;
@(posedge clk) disable iff (!rst_n)
(in_valid && (raw_status == CS_SC))
|-> (result == RESP_OK) && status_recognised && is_success;
endproperty
a_sc : assert property (p_sc_maps_ok);
property p_ur_maps_unsupported;
@(posedge clk) disable iff (!rst_n)
(in_valid && (raw_status == CS_UR))
|-> (result == RESP_UNSUPPORTED) && status_recognised;
endproperty
a_ur : assert property (p_ur_maps_unsupported);
property p_crs_maps_config_retry;
@(posedge clk) disable iff (!rst_n)
(in_valid && (raw_status == CS_CRS))
|-> (result == RESP_RETRY_CONFIG) && status_recognised;
endproperty
a_crs : assert property (p_crs_maps_config_retry);
property p_ca_maps_aborted;
@(posedge clk) disable iff (!rst_n)
(in_valid && (raw_status == CS_CA))
|-> (result == RESP_ABORTED) && status_recognised;
endproperty
a_ca : assert property (p_ca_maps_aborted);
// DECODE — P2: THE FAIL-CLOSED PROPERTY. An encoding outside the defined
// set NEVER decodes to success. The single most important property in the
// chapter: it is what stops a corrupted status bit from delivering garbage
// as valid data.
property p_undefined_never_success;
@(posedge clk) disable iff (!rst_n)
(in_valid && !(raw_status inside {CS_SC, CS_UR, CS_CRS, CS_CA}))
|-> (result == RESP_UNKNOWN) && !status_recognised && !is_success;
endproperty
a_fail_closed : assert property (p_undefined_never_success);
// DECODE — P3: exactly one convenience flag is asserted, and it agrees with
// `result`. Catches a second, independent decode of raw_status.
property p_flags_one_hot_and_consistent;
@(posedge clk) disable iff (!rst_n)
in_valid |-> ($countones({is_success, is_unsupported, is_config_retry,
is_aborted, is_unknown}) == 1);
endproperty
a_flags : assert property (p_flags_one_hot_and_consistent);
// LEGALITY — P4: THE CRS RESTRICTION. CRS against a non-Configuration
// Request is an anomaly, never an acceptable combination.
property p_crs_only_for_config;
@(posedge clk) disable iff (!rst_n)
(in_valid && (result == RESP_RETRY_CONFIG)
&& !(req_kind inside {REQ_CFG_RD, REQ_CFG_WR}))
|-> (protocol_anomaly && !combination_ok);
endproperty
a_crs_scope : assert property (p_crs_only_for_config);
// LEGALITY — P5: CRS against a Configuration Request IS acceptable. The
// converse, so a design cannot satisfy P4 by rejecting CRS everywhere.
property p_crs_ok_for_config;
@(posedge clk) disable iff (!rst_n)
(in_valid && (result == RESP_RETRY_CONFIG)
&& (req_kind inside {REQ_CFG_RD, REQ_CFG_WR}))
|-> combination_ok;
endproperty
a_crs_allowed : assert property (p_crs_ok_for_config);
// LEGALITY — P6: a status claiming to answer a POSTED Request is anomalous.
property p_posted_answer_anomalous;
@(posedge clk) disable iff (!rst_n)
(in_valid && (req_kind == REQ_MEM_WR)) |-> !combination_ok;
endproperty
a_posted_anomalous : assert property (p_posted_answer_anomalous);
// LEGALITY — P7: the two fault kinds are distinct. An unrecognised encoding
// is not a legality anomaly, and vice versa (section 12).
property p_fault_kinds_distinct;
@(posedge clk) disable iff (!rst_n)
in_valid |-> !(protocol_anomaly && unrecognised_status);
endproperty
a_faults_distinct : assert property (p_fault_kinds_distinct);
// RETIRE — P8: EXACTLY ONCE. Every resolved transaction retires, on success
// or on failure, and retires once.
// (retire_count / resolved_count are testbench counters.)
property p_retire_once_per_resolution;
@(posedge clk) disable iff (!rst_n)
retire |-> (retire_count + 1 <= resolved_count);
endproperty
a_retire_once : assert property (p_retire_once_per_resolution);
// RETIRE — P9: A FAILING STATUS STILL RETIRES. The property that catches a
// design which frees contexts only on success and leaks one per error.
property p_failure_retires;
@(posedge clk) disable iff (!rst_n)
(in_valid && in_final && in_ready_int
&& (in_result != RESP_OK)) |-> retire;
endproperty
a_failure_retires : assert property (p_failure_retires);
// RETIRE — P10: retirement is tied to ACCEPTANCE, not delivery. A stalled
// client must not hold a correlation resource.
property p_retire_independent_of_client;
@(posedge clk) disable iff (!rst_n)
(retire && !out_ready) |-> 1'b1; // permitted, and exercised in section 14
endproperty
// RETIRE — P11: a client never receives a failing outcome with payload.
property p_no_payload_on_failure;
@(posedge clk) disable iff (!rst_n)
(out_valid && (out_result != RESP_OK))
|-> (!out_data_valid && (out_data == '0));
endproperty
a_no_bad_payload : assert property (p_no_payload_on_failure);
// RETIRE — P12: an error is NEVER delivered as success. The client-facing
// form of P2.
property p_error_never_reported_success;
@(posedge clk) disable iff (!rst_n)
(accept && ((in_result != RESP_OK) || !in_combination_ok))
|=> (out_valid && (out_result != RESP_OK));
endproperty
a_no_false_success : assert property (p_error_never_reported_success);
// RETIRE — P13: OWNERSHIP. The delivered result is stable while the client
// stalls — result, context and payload together.
property p_result_stable;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready)
|=> (out_valid && $stable({out_ctx, out_result, out_data, out_data_valid}));
endproperty
a_result_stable : assert property (p_result_stable);
// RETIRE — P14: same-cycle consume and accept ends holding the NEW result.
property p_simultaneous_replace;
@(posedge clk) disable iff (!rst_n)
(v_q && out_ready && resolved)
|=> (out_valid && (out_ctx == $past(in_ctx)));
endproperty
a_replace : assert property (p_simultaneous_replace);
// RESET — P15: reset clears local result ownership.
property p_reset_clears;
@(posedge clk)
!rst_n |=> (!out_valid && !out_data_valid && !retire);
endproperty
a_reset : assert property (p_reset_clears);P2 is the chapter's most important property and it is worth stating why. A status decoder is a small block that looks trivially correct, and the one mistake it can make silently is a permissive default. default: RESP_OK — or the equivalent written as "if it is not one of the errors I know about, it worked" — means a single corrupted status bit delivers whatever accompanied that Completion to a client that asked for real data. P2 makes the fail-closed behaviour a checkable contract rather than a coding convention.
P4 and P5 are a pair, and P5 is the one that stops over-correction. P4 alone is satisfied by a design that rejects CRS unconditionally — which is wrong in the other direction, and would break configuration enumeration on any system where a device legitimately returns CRS. The pair pins the applicability exactly.
P9 is the leak property. Retiring only on success is a natural thing to write — the success path is the one you build first — and it costs one correlation identifier per failure. A probe sequence that walks unpopulated addresses returns UR repeatedly, and a design with this bug exhausts its outstanding table during enumeration and stops working before it has done anything.
P7 keeps the two fault kinds separable, for §14's reason: an unrecognised encoding points at corruption or a decode gap, while a well-formed status in the wrong place points at correlation or at a Completer. Merging them merges the investigations.
P11 and P12 are the client-facing guarantees, and both are stated over what the client actually receives rather than over internal state — because the property that matters is that a consumer cannot be misled, not that some register held the right value on the way.
14. Verification
Monitors observe: the raw status entering the decoder; the decoder's result and flags; the Request class and legality outputs; and the client-facing result interface with the retirement pulse.
The scoreboard holds its own status-to-result table, written from §2. It must not import cpl_status_pkg and must not reuse the decoder's case. It holds its own applicability rules for §11.
Status decoding
- Each defined encoding —
000b,001b,010b,100b. Verify the result,status_recognised, and the one-hot flags (P1, P3). - Every remaining 3-bit value —
011b,101b,110b,111b. VerifyRESP_UNKNOWN,!status_recognised, and!is_successfor each (P2). This is an exhaustive sweep of an 8-value field; there is no excuse for sampling it. - A corrupted status bit. Flip each bit of a valid SC and verify the result is never success unless the value is still
000b. - Back-to-back different statuses. Verify no state carries.
Request-aware legality
- Each status against each Request class, exhaustively — four statuses × six classes is 24 combinations, all of them cheap.
- CRS against Configuration Read and Configuration Write. Verify
combination_ok(P5). - CRS against Memory Read, Memory Write, I/O Read, I/O Write. Verify
protocol_anomalyand!combination_ok(P4). - Any status against a posted Memory Write. Verify
!combination_ok(P6). - An unrecognised encoding against every class. Verify
unrecognised_statusand notprotocol_anomaly(P7).
Retirement
- A successful final Completion. Verify one
retirepulse, resultRESP_OK, payload delivered. - A UR final Completion. Verify one
retirepulse (P9), resultRESP_UNSUPPORTED, and no payload (P11). - A CA final Completion. Same, with
RESP_ABORTED. - A CRS final Completion on a Configuration Request. Verify retirement and
RESP_RETRY_CONFIG— and verify no retry is initiated by this logic, which is §9's boundary. - An unrecognised status. Verify retirement still occurs and the client receives
RESP_UNKNOWN— the transaction resolved; the design merely did not understand how. - An anomalous combination (CRS on a Memory Read). Verify the client receives
RESP_UNKNOWNrather thanRESP_RETRY_CONFIG, and that §11 reported the anomaly. - A long client stall after a failure. Verify
retirealready pulsed and the context was freed (P10) — protocol resolution does not wait on the client. - Same-cycle consume and accept, alternating success and failure (P14).
- Reset while a result is held. Verify no stale result and no spurious retire (P15).
Error injection
Explicit fault switches, applied at the harness boundary.
| Injected | Expected | What must never happen |
|---|---|---|
| force UR on a Memory Read | context retires; client gets RESP_UNSUPPORTED; no data | payload delivered; context leaked |
| force CA on a Memory Read | context retires; client gets RESP_ABORTED | reported as success |
| inject CRS on a Configuration Read | context retires; client gets RESP_RETRY_CONFIG | this logic initiating a retry |
| inject CRS on a Memory Read | protocol_anomaly; client gets RESP_UNKNOWN | acted on as a retry hint |
| inject a reserved encoding | RESP_UNKNOWN; context retires | decoded as success (P2) |
| corrupt one status bit | per the resulting value | success from a non-000b value |
| deliver an error Completion to the wrong context | correlation reports it (Chapter 12.3) | the wrong transaction retiring |
| duplicate an error Completion | second one treated as unknown context | double retirement (P8) |
For each: which state retires, what the local client sees, and what must never happen. Those three questions are the point of the table — a fault injection that only checks "an error was reported" has verified the least interesting third of it.
Coverage should include: all eight status encodings; all four defined statuses against all six Request classes; success and each failure through retirement; the anomalous and unrecognised paths; client stalls spanning a retirement; and the same-cycle replacement with mixed outcomes.
15. Debugging
A Completion arrives but the client reports "unsupported"
Walk the chain in order — it is four stages and each one has a distinct signature.
- The raw status field. Is it actually
001b? If so, the decode is right and the question moves upstream: why did the Completer report UR? That is §8's investigation — addressing, enumeration, capability. - The decoder. If the raw field is
000band the result isRESP_UNSUPPORTED, the mapping is wrong. - The legality check. If
combination_okwas low, the client is seeingRESP_UNKNOWN, notRESP_UNSUPPORTED— check which one it actually reported, because they point at different things. - The local translation (§9). If everything above is right and the client's error code is still wrong, the translation from normalized result to local status is the fault.
The single most useful capture is the raw status alongside the normalized result. If they agree, stop looking at the receiver.
CRS is observed on an ordinary Memory Read
Do not retry. CRS is not a legal status in response to a Memory Request (§2, §6), so the first question is not "how long should I wait" but "why is this value here at all."
Three candidates, in order. The status decode is wrong — check the raw bits. The Completion was correlated to the wrong context — a genuine CRS answering a Configuration Request got matched against a Memory Read's entry, which is Chapter 12.3's correlation path, not this chapter's. Or the field is corrupted.
Acting on it as a retry hint is the worst available response, because it treats a symptom of one of those three faults as an instruction — and reissuing the Memory Read will not reproduce or diagnose any of them.
protocol_anomaly is the signal that should have fired, and it is why §11 exists as a separate block: the decoder alone cannot detect this, because the value is a perfectly valid encoding.
A UR becomes SC in the local status register
A normalization or translation bug, and the boundary tells you which.
Capture the normalized result at the decoder output. If it is RESP_UNSUPPORTED and the local register says success, the fault is in the local translation (§9) — one map, one place to look.
If the normalized result is already RESP_OK, the decoder is wrong, and the most likely cause is a permissive default: a case whose default produces success, or a comparison written as "not one of the errors I check for." P2 is the property that would have caught it, and §14's exhaustive eight-value sweep is the stimulus.
And check for a second decode. If some consumer reads raw_status directly instead of the normalized result (§9), it has its own opinion — and that opinion is what the register holds.
CA retires the context but no error reaches the client
Protocol state is correct; local result propagation is broken.
The retirement is right — a failed transaction is a finished transaction and must retire (§12, P9). What failed is the delivery of the outcome.
Two candidates. The result path dropped the entry — check whether out_valid ever rose for that context. Or the client's consumer ignores the result field for the failure case, which is a client bug rather than a Completion-path one.
The distinguishing observation: if retire pulsed and out_valid never rose for that context, the result stage lost it. If both happened and the client still saw nothing, the client is not reading out_result — and P11/P12 both passed, correctly, because the fault is past their boundary.
16. Common Misconceptions
- "Any Completion means success." A Completion reports an outcome. SC is one of four, and three of them are not success (§3).
- "UR means the packet was malformed." UR says the operation is not supported at its destination; the packet was well-formed. Malformed TLP is a different condition reported by different mechanisms (§5).
- "CRS means retry, for any Request." CRS exists only for Configuration Requests and is not a legal status in response to a Memory or I/O Request (§2, §6).
- "CA and UR are interchangeable." UR points at addressing, enumeration and capability; CA points at the Completer and the operation. Almost disjoint investigations (§8).
- "Completion Status is a software error code." It is a protocol field. Translating it into a local or software status is a separate, deliberate step (§9).
- "SC means the returned data has been consumed locally." SC is a statement about the Completer. Local delivery is a different event (Chapter 12.1 §12).
- "An error Completion should leave the Request outstanding." A failed transaction is a finished transaction. Not retiring it leaks a correlation resource per failure (§12, P9).
- "A reserved encoding can safely be treated as SC." Never. A fail-open default turns a corrupted bit into delivered garbage (§10, P2).
- "Status alone identifies which Request failed." The correlation fields identify the Request (Chapter 10.2 §6); status says how it ended.
- "Retry policy belongs in the status decoder." The decoder interprets a protocol field. Retry needs system knowledge it does not have (§9).
- "Any failed Memory Read should be automatically resent." Reissuing a UR reproduces it; reissuing a CA may not help. Retry without knowing why is guessing (§8).
- "Completion Status and Completion Type are the same thing." Type is fixed by the Request and known in advance; status reports an outcome and is not (Chapter 13.1 §6).
17. Understanding Check
18. What's Next
This chapter decoded the outcome. Four defined statuses, one of them narrowly scoped to Configuration Requests, four undefined encodings that must never become success — and an architecture that interprets the protocol field once, checks it against what was asked, retires the transaction exactly once whether it worked or not, and leaves recovery policy to a layer that knows the system.
Chapter 13.3 — Split Completions takes the fields this chapter named and refused to decode: Byte Count, Lower Address, the last-Completion indication, and the rules that constrain how a Completer may divide a response — including the Read Completion Boundary. Module 12 treated multi-Completion responses as a fact to accommodate; 13.3 owns why they happen and what the divisions must look like.
Chapter 13.4 — Ordering then owns the ordering rules: what may pass what, in which direction, and why the producer/consumer model depends on the defaults that Chapter 11.6's attributes are permitted to relax.
The idea to carry forward: a Completion arriving is not a Completion succeeding — and a design that cannot tell the difference between unsupported, not-yet, and aborted has thrown away the information that says where to look.