PCIe · Module 11
Headers — The Packet's Transaction Contract
A TLP header tells the receiver what kind of packet this is, how much data goes with it, and which transaction-specific fields to interpret next. Why Fmt and Type must be decoded together, why the Length field's encoded value is not its DW count, why the Tag's width is not a constant, and a two-stage parser that classifies before it extracts.
Chapter 11.2 found the end of the packet without decoding a single field. It needed four numbers — header form, payload present, payload length, digest present — and it got them from a side-band input produced by "a header field decode this chapter does not implement."
This is that decode.
What information does a PCIe TLP header carry, how do the major fields cooperate, and how should RTL parse them without confusing structural framing with transaction semantics?
1. Two Decodes, and Why They Stay Apart
Chapter 11.2 §7 drew the line and this chapter takes the other side of it.
| Structural decode (11.2) | Semantic decode (this chapter) | |
|---|---|---|
| Asks | how many DW, where do the regions end | what does this packet mean |
| Needs | four numbers | the header's fields, interpreted per class |
| Failure cost | the receiver desynchronises; every later packet corrupts | one operation is wrong |
| Timing | on the critical path with the link | can be pipelined |
The framing decoder answers where the header ends. The semantic decoder answers what the header means. They consume overlapping bits and they are not the same logic, and §10's microarchitecture keeps them separate for the reason 11.2 gave: a framing block that inspects no field can be verified exhaustively against packet shapes, and then a field bug cannot hide behind a framing bug.
2. The Verified Field Map
3. Fields Grouped by Responsibility
Learning the header as a sequence of positions is how it becomes trivia. Grouped by the job each field does, it becomes a structure you can reason about.
| Group | Fields | What it answers | Owner |
|---|---|---|---|
| Classification | Fmt, Type | what kind of packet is this, and how is the rest of the header laid out | this chapter (§4) |
| Size | Length | how much data is associated with this packet | this chapter (§5) |
| Origin and correlation | Requester ID, Tag | who issued this, and which operation is it | this chapter (§6), correlation from 10.2 |
| Byte granularity | First / Last DW Byte Enable | which bytes within the first and last DW are meaningful | this chapter structurally (§7); mechanics in Module 12 |
| Handling context | Traffic Class, Attributes, Address Type, processing hints | how the fabric and the Completer should treat this packet | Chapter 11.6 |
| Integrity annotations | digest-present, poisoned-data indications | is a digest appended; is the data known bad | structurally in 11.2; semantics later |
| Routing context | address, or a routing identity, or an implicit route | where does this packet go | Chapter 11.5 |
| Completion context | status, byte count, lower address | how did the operation end | Module 13 |
Read the Owner column as the chapter's boundary. This chapter teaches the first four groups and identifies the rest structurally — enough for a parser to know a field is there and skip it, not enough to interpret it.
4. Fmt and Type — the Classification Pair
Neither field classifies a packet alone. That is the single most important fact in the chapter, and it is why §10's parser has two stages.
What Fmt carries is the structural answer: how many DW the header occupies, and whether data follows. Its five encodings (§2) are exactly two independent bits of information — form and payload — plus the prefix indication.
What Type carries is the semantic answer: which family of operation this is.
What this chapter deliberately does not do is enumerate the Type encodings. There are many, they interact with Fmt, and the full taxonomy is Chapter 11.7's entire subject. §11's RTL uses a small verified subset sufficient to demonstrate the decode structure and says so.
5. Length — the Encoded Value Is Not the Count
The Length field is 10 bits and is expressed in DW. That much is unremarkable. The encoding has one special case, and it is the source of a classic and expensive bug.
An encoded Length of zero represents 1024 DW — the maximum — not zero DW.
Why the encoding works that way. Ten bits can express 1024 distinct values. A length of zero DW is not a useful thing to express for a packet whose Fmt already says whether data is present, so the encoding spends the zero codepoint on the value it could not otherwise reach: 1024.
| Encoded value | Represented DW |
|---|---|
1 | 1 DW |
2 | 2 DW |
| … | … |
1023 | 1023 DW |
0 | 1024 DW |
6. Requester ID and Tag — Identity and Correlation
Chapter 10.2 §6 established the concept: Requester ID and Tag combined form a global identifier for each Transaction within a Hierarchy, and that identifier is what lets a returning Completion find the operation it answers. This chapter adds where those fields live and what their widths actually are.
Requester ID is 16 bits and identifies the Requester — the same Bus/Device/Function coordinate the configuration mechanism uses (Chapters 7.4–7.6). Its interpretation is subject to ARI, which changes how the 16 bits are divided; Chapter 11.5 §5 says what that means for routing and the ARI chapter owns the mechanism.
Tag width is where a naive parser breaks.
And a naming caution carried from Module 10. The Tag is a protocol field that correlates a Completion to a Request. It is not a routing address (Chapter 11.5 §6), and it is not the internal correlation index a design allocates for its own outstanding table (Chapter 10.2 §6). A design maps its internal index onto the Tag; they are different objects.
7. Byte Enables — Why DW Granularity Is Not Enough
The Length field counts DW. The payload is DW-aligned and in DW increments (Chapter 11.2 §2). So why does the header carry byte-level information at all?
Because software does not write in dwords. A driver storing a single byte to a device register produces an operation that concerns one byte. The packet structure cannot express a one-byte payload — the smallest payload region is one DW — so the packet carries a DW and says which bytes within it are meaningful.
Two fields, four bits each, one describing the first DW of the data region and one the last:
| Case | What the two fields describe |
|---|---|
| One-DW payload | a single DW, with one field describing its byte validity |
| Multi-DW payload | the first DW's valid bytes and the last DW's valid bytes; the DW between them are wholly valid |
The architectural point, and it generalises: a protocol whose transfer granularity is coarser than its addressing granularity needs a validity mask, and the mask lives with the packet rather than with the data. Chapter 9.6 §11's front end propagated byte enables for the same reason at the resource boundary.
Detailed mechanics — the legal combinations, what a zero-enable case means, and how a Completer must interpret them — are Module 12's. This chapter needs only that the fields exist, are four bits each, and describe the first and last DW of the data region.
8. The Header, Grouped
The figure's argument is the fork. DW0 is the only part of the header whose meaning is unconditional. From DW1 onward the same bit positions do not necessarily mean the same thing for every packet family, and the arrows out of the classification block are the only thing that decides which reading applies.
Which is why the diagram is not a bit map. A poster showing every field at every position invites exactly the wrong mental model — that a parser can slice all of them at once and sort it out afterwards.
9. Interpretation Depends on Class
The correct decode order, stated as a pipeline:
raw header words
→ classify from Fmt and Type together
→ select the field interpretation for that class
→ extract the fields that class defines
→ produce a normalized internal descriptorEach arrow is a dependency, not a convenience. Reversing any two of them produces a parser that extracts fields from positions that do not hold them.
10. Microarchitecture — A Two-Stage Parser
The temptation is one enormous combinational block that slices every field from every position and lets downstream logic pick. It is smaller to write, it fails timing, and it makes the §9 bug unrepresentable in the assertions.
| Stage | Consumes | Produces | Why separate |
|---|---|---|---|
| 1 — classify | DW0 only | header form, packet class, payload-present, length, illegal indication | DW0 is available first, and everything downstream depends on it |
| 2 — extract | the remaining DW, under stage 1's class | the fields that class defines | the field map is selected, so wrong-class extraction has no path |
Stage 1 is small and on the critical path; stage 2 is wider and is not. Splitting them is what lets the fast decision be fast.
Illustrative microarchitecture — PCIe defines wire semantics, not pipeline stages. A design may fuse or further split these; what does not vary is the dependency order of §9.
11. RTL — Base Header Classifier
// SYNTHESIZABLE. Stage 1 — classify a TLP from its first header DW.
// Fmt width (3 bits), Type width (5 bits), the Fmt encodings and the Length
// field's width and DW semantics: NORMATIVE (PCIe r7.0 section 2.2.1 and the
// Base Specification's packet definition rules).
// The packet-family enum, the subset supported, and the port names:
// ILLUSTRATIVE. Conventional non-Flit header representation only.
package tlp_hdr_pkg;
// Internal classification. NOT a PCIe encoding — Chapter 11.7 owns the
// Type taxonomy, and this is a family selector for choosing a field map.
typedef enum logic [2:0] {
FAM_MEM_REQ = 3'd0, // memory request family
FAM_CFG_REQ = 3'd1, // configuration request family
FAM_COMPL = 3'd2, // completion family
FAM_MSG = 3'd3, // message family
FAM_UNSUP = 3'd7 // not represented by this teaching model
} tlp_family_e;
// NORMATIVE Fmt encodings.
localparam logic [2:0] FMT_3DW_ND = 3'b000; // 3 DW header, no data
localparam logic [2:0] FMT_4DW_ND = 3'b001; // 4 DW header, no data
localparam logic [2:0] FMT_3DW_D = 3'b010; // 3 DW header, with data
localparam logic [2:0] FMT_4DW_D = 3'b011; // 4 DW header, with data
localparam logic [2:0] FMT_PREFIX = 3'b100; // a TLP Prefix is present
endpackageimport tlp_hdr_pkg::*;
module tlp_hdr_classify (
// The first header DW, first wire byte in bits [31:24].
input logic [31:0] dw0,
input logic dw0_valid,
// ---- Structural outputs — what Chapter 11.2's parser needs -----------
output logic uses_4dw,
output logic has_payload,
output logic [9:0] length_enc, // the ENCODED field, not the count
output logic [10:0] length_dw, // the REPRESENTED DW count
// ---- Semantic output — what stage 2 needs ---------------------------
output tlp_family_e family,
// A prefix is present, or the encoding is outside this model's subset.
output logic prefix_present,
output logic unsupported
);
// NORMATIVE field positions in the conventional representation.
wire [2:0] fmt = dw0[31:29];
wire [4:0] typ = dw0[28:24];
wire [9:0] len = dw0[9:0];
assign length_enc = len;
// NORMATIVE: an encoded Length of zero represents 1024 DW (section 5).
// Written as an 11-bit result so 1024 is representable — a 10-bit result
// here is the bug that turns the maximum packet into a header-only one.
assign length_dw = (len == 10'd0) ? 11'd1024 : {1'b0, len};
// Fmt decodes structure. Both bits of information come from this one field.
assign prefix_present = dw0_valid && (fmt == FMT_PREFIX);
assign uses_4dw = (fmt == FMT_4DW_ND) || (fmt == FMT_4DW_D);
assign has_payload = (fmt == FMT_3DW_D) || (fmt == FMT_4DW_D);
wire fmt_is_base = (fmt == FMT_3DW_ND) || (fmt == FMT_4DW_ND)
|| (fmt == FMT_3DW_D) || (fmt == FMT_4DW_D);
// Family selection uses Fmt AND Type together (section 4). The Type values
// matched here are a REPRESENTATIVE VERIFIED SUBSET chosen to demonstrate
// the decode structure; the complete taxonomy is Chapter 11.7's, and
// anything outside the subset is reported rather than guessed.
always_comb begin
family = FAM_UNSUP;
unsupported = 1'b1;
if (dw0_valid && fmt_is_base) begin
unique case (typ)
5'b00000: begin family = FAM_MEM_REQ; unsupported = 1'b0; end
5'b00100,
5'b00101: begin family = FAM_CFG_REQ; unsupported = 1'b0; end
5'b01010: begin family = FAM_COMPL; unsupported = 1'b0; end
default: begin
// Message and the remaining families exist and are real; this
// model simply does not represent them. "Unsupported by this
// model" is NOT "malformed per PCIe" — see section 14.
family = FAM_UNSUP; unsupported = 1'b1;
end
endcase
end
end
endmoduleClassification: synthesizable (package: compile-time).
Architecture. One small combinational block on the first header DW, producing everything the framing path and the extraction path each need.
State. None — deliberately. Classification is a pure function of DW0, which is what makes it reusable by both consumers without a second copy.
Cycle behaviour. Valid the cycle DW0 is valid; nothing is registered here. §13's extractor registers what it needs.
Contract. Downstream logic relies on family being stable for as long as DW0 is held, and on unsupported meaning this model does not represent it rather than PCIe forbids it.
Failure. Two to note. Computing length_dw in ten bits silently maps the maximum packet to zero. And deciding family from typ alone — without fmt_is_base — classifies a prefix-bearing header as a normal packet and applies a field map to the wrong DW.
DV. §14's P1–P4.
12. RTL — The Normalized Descriptor
// CONCEPTUAL. NORMALIZED IMPLEMENTATION METADATA — this is NOT a wire-format
// TLP header. It is what downstream blocks consume so that no block after
// this one ever re-slices raw header bits.
typedef struct packed {
logic valid;
tlp_family_e family;
logic uses_4dw;
logic has_payload;
logic [10:0] length_dw; // REPRESENTED count, section 5 applied once
logic unsupported;
} hdr_meta_t;Why this struct is the chapter's most reusable idea. Every later stage — payload segmentation (Chapter 11.4), routing (Chapter 11.5), dispatch (Chapter 10.1 §12) — needs to know what kind of packet this is and how big it is. If each of them re-slices dw0, then each of them has its own copy of §5's zero-means-1024 rule, and one of them will get it wrong.
Normalize once, at the boundary, and carry semantics downstream. That is the architectural continuity Chapter 11.5 §5 depends on and the reason its route classifier takes a descriptor rather than a raw header.
13. RTL — Request Header Extractor
// SYNTHESIZABLE. Stage 2 for the memory-request family only.
// Field widths and positions: NORMATIVE for the conventional representation
// (section 2). BASELINE 8-BIT TAG ONLY — see the limitation below.
import tlp_hdr_pkg::*;
module tlp_req_hdr_extract #(
// Baseline. Wider Tags are CONFIGURED, not encoded in the packet
// (section 6), so a parser cannot widen this from the header alone.
parameter int TAG_W = 8
) (
input logic clk,
input logic rst_n,
input hdr_meta_t meta, // from stage 1
input logic hdr_valid, // all header DW captured
input logic [31:0] dw1,
input logic [31:0] dw2,
input logic [31:0] dw3, // meaningful only when meta.uses_4dw
output logic out_valid,
output logic [63:0] address,
output logic [15:0] requester_id,
output logic [TAG_W-1:0] tag,
output logic [3:0] first_be,
output logic [3:0] last_be,
// The extraction was not attempted because the class does not define
// these fields. NOT an error — see section 14.
output logic not_applicable
);
generate
if (TAG_W != 8)
$error("This model implements the baseline 8-bit Tag only; wider Tags "
"require the configured tag mode, which is not in the packet");
endgenerate
// PREDICATION, and it is the point of the module. Every extraction below
// happens only for the family whose field map these positions belong to.
wire applicable = hdr_valid && meta.valid && !meta.unsupported
&& (meta.family == FAM_MEM_REQ);
// DW1 — origin, correlation, byte granularity (section 2's positions).
wire [15:0] rid_w = dw1[31:16];
wire [7:0] tag_w = dw1[15:8];
wire [3:0] lastbe_w = dw1[7:4];
wire [3:0] firstbe_w = dw1[3:0];
// Address. The 4 DW form carries the upper half in DW2 and the lower in
// DW3; the 3 DW form carries a 32-bit address in DW2 and DW3 is not part
// of this packet at all. Reading DW3 in the 3 DW case would import the
// NEXT packet's first word — section 16's second scenario.
wire [63:0] addr_w = meta.uses_4dw ? {dw2, dw3}
: {32'h0000_0000, dw2};
logic v_q;
logic [63:0] addr_q;
logic [15:0] rid_q;
logic [7:0] tag_q;
logic [3:0] fbe_q, lbe_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_q <= 1'b0; addr_q <= '0; rid_q <= '0; tag_q <= '0;
fbe_q <= '0; lbe_q <= '0;
end else begin
v_q <= applicable;
if (applicable) begin
addr_q <= addr_w;
rid_q <= rid_w;
tag_q <= tag_w;
fbe_q <= firstbe_w;
lbe_q <= lastbe_w;
end
end
end
assign out_valid = v_q;
assign address = addr_q;
assign requester_id = rid_q;
assign tag = tag_q;
assign first_be = fbe_q;
assign last_be = lbe_q;
assign not_applicable = hdr_valid && meta.valid && (meta.family != FAM_MEM_REQ);
endmoduleClassification: synthesizable.
Architecture. The field map for one family, applied only when that family was classified. A second family needs a second instance with its own map, not a wider case inside this one.
State. One registered output set, so downstream sees a stable descriptor rather than combinational slices of a header that may already have been replaced.
Cycle behaviour. Extraction is combinational on the captured words; the result is registered on the cycle after hdr_valid.
Contract. The caller guarantees dw3 is meaningful when and only when meta.uses_4dw is set. The module relies on that and does not read dw3 otherwise.
Failure — three, and they are the chapter's debugging scenarios. Using the 3 DW address path for a 4 DW header truncates every 64-bit address to its upper half. Using the 4 DW path for a 3 DW header concatenates the next packet's first word as the low half. And dropping the family term from applicable extracts an "address" from every Completion that passes.
Deliberately simplified: one family; baseline Tag only; no Traffic Class, Attributes, Address Type or processing hints (Chapter 11.6); no prefix handling; conventional representation only.
Production implication: a real parser instantiates a map per family, consults the configured tag mode, handles prefixes, and — for a Flit-Mode link — uses a different header format entirely (§2's scope note).
14. Assertions
// SVA over tlp_hdr_classify and tlp_req_hdr_extract. Local decode contracts
// plus the normative field semantics they implement — not claims about PCIe
// packet legality beyond the verified subset.
// CLASSIFICATION — P1: the structural outputs agree with the Fmt encoding.
// This is the tie back to Chapter 11.2: the framing path and the semantic
// path must derive the same shape from the same field.
property p_fmt_structure_agrees;
@(posedge clk) disable iff (!rst_n)
(dw0_valid && fmt_is_base)
|-> (uses_4dw == ((fmt == FMT_4DW_ND) || (fmt == FMT_4DW_D)))
&& (has_payload == ((fmt == FMT_3DW_D) || (fmt == FMT_4DW_D)));
endproperty
a_fmt_structure : assert property (p_fmt_structure_agrees);
// LENGTH — P2: the represented count applies the zero-means-1024 rule.
// Written as the rule rather than as a repetition of the RTL expression, so
// a 10-bit result or a missing special case fails.
property p_length_encoding;
@(posedge clk) disable iff (!rst_n)
dw0_valid |-> (length_dw == ((length_enc == 10'd0) ? 11'd1024
: 11'(length_enc)));
endproperty
a_length_encoding : assert property (p_length_encoding);
// LENGTH — P3: the represented count is never zero and never exceeds the
// field's reach. Catches a truncated computation directly.
property p_length_in_range;
@(posedge clk) disable iff (!rst_n)
dw0_valid |-> ((length_dw >= 11'd1) && (length_dw <= 11'd1024));
endproperty
a_length_range : assert property (p_length_in_range);
// CLASSIFICATION — P4: exactly one outcome. A supported family or the
// unsupported indication, never both and never neither.
property p_family_exclusive;
@(posedge clk) disable iff (!rst_n)
dw0_valid |-> (unsupported == (family == FAM_UNSUP));
endproperty
a_family_exclusive : assert property (p_family_exclusive);
// CLASSIFICATION — P5: a prefix-bearing header is never classified as a
// normal packet family. Catches a decode that switched on Type alone.
property p_prefix_not_a_family;
@(posedge clk) disable iff (!rst_n)
prefix_present |-> unsupported;
endproperty
a_prefix_unsupported : assert property (p_prefix_not_a_family);
// PREDICATION — P6: THE CHAPTER'S CENTRAL PROPERTY. Request fields are
// produced only for the family whose field map defines them. Catches the
// unconditional extractor of section 9.
property p_extract_only_for_family;
@(posedge clk) disable iff (!rst_n)
out_valid |-> $past(meta.family == FAM_MEM_REQ);
endproperty
a_predicated : assert property (p_extract_only_for_family);
// PREDICATION — P7: an unsupported classification never reaches the normal
// extraction path.
property p_unsupported_not_extracted;
@(posedge clk) disable iff (!rst_n)
(hdr_valid && meta.unsupported) |=> !out_valid;
endproperty
a_unsupported_isolated : assert property (p_unsupported_not_extracted);
// ADDRESS — P8: the 4 DW form uses both address words and the 3 DW form uses
// only one, with the upper half zero. Catches both directions of section
// 16's address bug.
property p_address_form;
@(posedge clk) disable iff (!rst_n)
out_valid |-> ($past(meta.uses_4dw)
? (address == {$past(dw2), $past(dw3)})
: ((address[63:32] == 32'h0)
&& (address[31:0] == $past(dw2))));
endproperty
a_address_form : assert property (p_address_form);
// ADDRESS — P9: DW3 never influences the result in the 3 DW form. Stated
// separately because P8 could be satisfied by a design that happened to
// produce the right value from the wrong source.
property p_dw3_not_used_in_3dw;
@(posedge clk) disable iff (!rst_n)
(out_valid && !$past(meta.uses_4dw)) |-> (address[63:32] == 32'h0);
endproperty
a_dw3_ignored : assert property (p_dw3_not_used_in_3dw);
// STABILITY — P10: the classification is stable while the header is held.
// Catches a decode that acquired a dependence on something other than DW0.
property p_class_stable;
@(posedge clk) disable iff (!rst_n)
$stable(dw0) |-> $stable({family, uses_4dw, has_payload, length_dw});
endproperty
a_class_stable : assert property (p_class_stable);
// CONSERVATION — P11: one accepted header produces one descriptor.
property p_one_descriptor;
@(posedge clk) disable iff (!rst_n)
out_valid |=> !out_valid or $past(hdr_valid, 1);
endproperty
// SAFETY — P12: no decode output is ever unknown.
property p_outputs_never_unknown;
@(posedge clk) disable iff (!rst_n)
dw0_valid |-> !$isunknown({uses_4dw, has_payload, length_dw, unsupported,
prefix_present});
endproperty
a_no_x : assert property (p_outputs_never_unknown);P6 is the property this chapter exists to make writable. Every other assertion checks a value; P6 checks that a value was produced under the right precondition. A parser that extracted an address from every packet would satisfy P8 for the memory requests it saw and still be catastrophically wrong for everything else — and P6 is the only check that fires on the first Completion.
P2 and P3 are a pair and P3 is the one that catches the width bug. P2 restates the rule against the encoded field; a design computing length_dw in ten bits would compute 0 for the maximum and fail P2 — but a design that also got the property width wrong would pass both. P3's range check is independent of the encoding and cannot be satisfied by any truncated result.
P9 exists because P8 is satisfiable by accident. A 3 DW header whose following packet happens to start with zeros produces the right address from the wrong source. P9 asserts the absence of DW3's influence rather than the presence of a correct value.
15. Verification
Monitors observe: DW0 with its decoded outputs; the captured header words; the descriptor; and both the extraction outputs and not_applicable.
The scoreboard slices the raw header words itself, in the testbench, from §2's field map — its own dw0[31:29] for Fmt, its own zero-means-1024 rule, its own address concatenation. It must not call the design's classifier or import tlp_hdr_pkg's localparams as its expected values. The Fmt encodings are normative constants both sides may legitimately instantiate independently; everything derived from them must be derived twice.
Classification
- Each supported family, in both 3 DW and 4 DW forms where the family has both, with and without payload. Verify
family,uses_4dwandhas_payloadfor each. - All five Fmt encodings, including the prefix encoding. Verify the prefix case is reported unsupported and never classified as a family (P5).
- A Type value outside the modelled subset. Verify
unsupportedand that nothing is extracted (P7). Verify the report says unsupported-by-this-model, not malformed. - The same Type with different Fmt values. Verify the classification differs — the test that a design switching on Type alone fails.
Length
- Encoded 1. The minimum.
- Encoded 1023. The largest value that means what it says.
- Encoded 0 — the maximum-length packet. Verify
length_dwis 1024. This is the mandatory scenario, because every test with a payload below 1024 DW passes without it and the bug reaches silicon. - Sweep encoded values across the field. Verify the represented count tracks (P2, P3).
Extraction
- 3 DW memory request. Verify the 32-bit address, the upper half zero, and that DW3's contents are irrelevant (P9) — drive DW3 with a distinctive non-zero pattern and confirm the address is unaffected.
- 4 DW memory request. Verify the full 64-bit address and the word order (P8).
- A Completion presented to the request extractor. Verify
out_validstays low,not_applicableasserts, and no address is produced (P6). - Byte-enable patterns. All sixteen values on each field, verified through unchanged.
- Requester ID and Tag. Distinctive patterns, verified through; and confirm the elaboration check rejects
TAG_W != 8.
Negative and boundary
- An incomplete 4 DW header. Assert
hdr_validwith only three words captured. Verify nothing is extracted — the model must not read a word it was not given. - Reset between header words. Verify no partial descriptor emerges.
- Back-to-back headers of different families. Verify each is classified independently and no state carries.
- The unsupported path under backpressure. Verify it does not stall the classifier.
Coverage should include: every Fmt encoding; every supported family in every form it has; encoded lengths at 1, 1023 and 0; both address forms; all byte-enable values; the unsupported family; and the prefix indication.
16. Debugging
The packet boundary is correct but downstream sees the wrong transaction type
Framing is right, so Chapter 11.2 is exonerated — the length was correct and the next packet started where it should. The fault is in classification.
Three candidates. The decode switched on Type alone and matched a value that means something different under a different Fmt. The Fmt slice is wrong — and note that a slice taken from an older revision's 2-bit field would read Fmt[1:0] from the wrong bits entirely, misclassifying everything. Or the family map has an encoding assigned to the wrong family.
The observation: print {Fmt, Type} as a pair for the misclassified packet and compare against §2. If the pair is right and the family is wrong, it is the map; if the pair itself looks implausible, it is the slice.
Every 64-bit-address request targets the wrong address
The signature names the cause: only 4 DW packets are affected, which eliminates classification, length and byte enables — all of which are shared with the working 3 DW packets.
Two directions. Using the 3 DW path for a 4 DW header truncates the address to its upper word — the symptom is that every access lands at a wildly wrong high address. Using the wrong word order concatenates the halves reversed, which usually produces an obviously absurd address rather than a subtly wrong one.
And a third that is nastier. Using the 4 DW path for a 3 DW header reads DW3, which is not part of that packet at all — it is the next packet's first word. The resulting address changes depending on what follows, so the failure is intermittent and load-dependent. P9 is the check for it, and §15's "distinctive non-zero DW3" test is how you provoke it.
The packet is classified correctly but Length is zero or maximum incorrectly
This is §5, and it is one of two directions. A represented count of zero for a packet that carries data means the zero-means-1024 rule was not applied. A represented count of 1024 for a small packet means it was applied to a value that was not zero — a comparison against the wrong constant, or a truncated encoded field.
The check is arithmetic and immediate: read the encoded field and apply the rule by hand. If they disagree, the bug is in the transformation; if they agree and the payload still does not match, the fault is downstream in Chapter 11.4.
A Completion parser interprets origin fields as an address
The classification never reached the extractor, or the extractor was not predicated on it.
What makes this recognisable. The "address" produced is stable, plausible and wrong, and it correlates with the Requester of the original operation rather than with anything the system ever accessed. An address that looks like an identity is the signature.
P6 catches it at the first Completion. Without predication, the only symptom is memory traffic to addresses no one allocated — which sends the investigation to Chapter 9.5 and wastes it there.
17. Common Misconceptions
- "Each header bit can be interpreted independently." From DW1 onward the same positions carry different fields for different packet families. Interpretation is selected by classification, not applied uniformly.
- "Fmt alone identifies the packet type." Fmt gives header form and payload presence. The family needs Fmt and Type together (§4).
- "Type means the same thing regardless of Fmt." The same family appears in different forms — a memory request exists in both a 3 DW and a 4 DW variant — and a Type value must be read in the context of the Fmt that accompanies it.
- "Fmt is a 2-bit field." It is 3 bits in current PCIe. The 2-bit field is an older revision's, and a slice taken from it reads the wrong bits — which is why §2 states its source revision explicitly.
- "Every header contains an address." Address-bearing headers are one family among several. A Completion does not carry a destination address, and a configuration request identifies a Function rather than an address.
- "Requester ID appears in the same position with the same meaning in every header." It is defined for the families that carry it, and its interpretation is further subject to ARI (Chapter 11.5 §5).
- "The Tag is 8 bits." 8 bits is the baseline every device must be able to receive. Whether a Requester generates 8, 10 or 14 bits is configured, and the width is not in the packet (§6).
- "The Tag is a local RTL sequence counter." It is a protocol field that correlates a Completion to a Request. A design maps its internal correlation index onto it; they are different objects (Chapter 10.2 §6).
- "Length zero means zero bytes." An encoded zero represents 1024 DW (§5). Reading it as zero declares a 4 KB payload to be header-only and desynchronises the receiver.
- "3 DW means three bytes." A DW is 32 bits. A 3 DW header is 12 bytes (Chapter 11.2 §3).
- "A 4 DW header is always a memory request." The form indicates header length, which several families can require. Form and family are independent questions answered by different fields.
- "A header parser should be one big
caseover 128 bits." It should classify from DW0 and then select a field map (§10). One flat case makes wrong-class extraction unrepresentable in the assertions and fails timing besides.
18. Understanding Check
19. What's Next
This chapter decoded the header: the classification pair that selects every later interpretation, the length field whose encoding hides its maximum in its zero, the identity and correlation fields whose widths are not constants, and the byte enables that let a DW-granular packet describe a byte-granular operation.
It said what the length is and nothing about moving the data it counts.
Chapter 11.4 — Payloads takes the data region: when a payload exists, how Maximum Payload Size constrains it, and the hardware problem of moving a DW-counted payload across a datapath whose beats are wider than a DW. Chapter 11.5 — Routing Information then asks where the packet goes, and why different families answer that question with different fields.
Chapter 11.6 owns the handling context this chapter only identified — Traffic Class, the ordering attributes, and the rest — and 11.7 owns the complete packet taxonomy that §11's subset stands in for.
The idea to carry forward: classify first, then extract — because from DW1 onward, the bits do not mean anything until you know what kind of packet you are holding.