PCIe · Module 11
TLP Overview — The Packet That Carries an Operation
A TLP is the Transaction Layer's packet representation of an operation. Why a transaction and a TLP are not the same object, what structural regions a packet has, what a header must convey, and the packetiser RTL that owns a packet from first beat to last.
Module 10 closed with a claim worth repeating: you can now reason about a complete PCIe transaction without knowing a single header bit. Every packet in those five chapters was a black box carrying semantics.
This module opens the box.
What is a Transaction Layer Packet, why does PCIe need several packet categories, and what information must a packet carry to represent a transaction across the hierarchy?
1. From Operation to Packet
Chapter 10.5 §3's table had twelve stages and one of them was doing an enormous amount of unexamined work: "outstanding state allocated; Request launched."
Launched as what? The Requester holds an address, a direction, a length, its own identity, a correlation value, and a set of handling preferences. None of that is transmissible. Something must turn it into a sequence of bits that a device several hops away — with no knowledge of this Requester, its software, or its internal structures — can receive and act on correctly.
That object is the TLP, and constructing and disassembling it is the Transaction Layer's stated primary responsibility (Chapter 10.1 §2).
Module 10 asked what an operation means. Module 11 asks what an operation looks like when it has to survive a serial link and a stranger at the other end.
2. What a TLP Is
Three facts in that definition earn their place immediately.
A TLP conveys "a Request or Completion." That is the entire top-level taxonomy, and it is the same division Module 10 spent five chapters on — now seen as a property of packets rather than of operations.
The data payload is conditional. "For some types of packets" — a read Request asks and carries nothing; a write Request carries what it is writing. §6 develops why that is a per-packet property.
The digest is optional. A third region may or may not be present. §5 says what it is at the structural level and what this chapter deliberately will not say about it.
3. A TLP Is Not a Transaction
This distinction is the reason Module 11 exists as a separate module, and getting it backwards makes everything downstream confusing.
| Transaction | TLP | |
|---|---|---|
| What it is | a logical operation and its lifecycle | a packet |
| Where it lives | across devices, over time | on a Link, briefly |
| Who owns it | the Requester, until it resolves | whichever stage currently holds it |
| Ends when | the Sequence resolves | the last beat is transferred |
| Counted in | Sequences | packets |
The specification's own vocabulary makes the relationship exact. A Sequence is "a single Request and zero or more Completions associated with carrying out a single logical transfer by a Requester." A TLP is "a Packet generated in the Transaction Layer to convey a Request or Completion."
So the mapping between them is not one-to-one, and it varies by case:
| Operation | Packets involved |
|---|---|
| A posted Memory Write | one Request TLP, and nothing returns |
| A Memory Read | one Request TLP, plus one or more Completion TLPs |
| A Configuration Write | one Request TLP, plus a Completion TLP |
4. Categories of Packet
Different operation classes need different packet semantics, and the categories follow the address spaces of Chapter 10.1 §3 with one addition.
| Category | Carries | Relationship to a Sequence |
|---|---|---|
| Memory | Requests targeting memory-mapped locations, in 32-bit and 64-bit addressing forms | Request — starts a Sequence |
| I/O | Requests targeting I/O-mapped locations | Request — starts a Sequence |
| Configuration | Requests targeting a Function's configuration registers | Request — starts a Sequence |
| Message | in-band event and peer-to-peer communication | Request — starts a Sequence, and requires no Completion |
| Completion | the returning transaction for Requests that require one | terminates, or partially terminates, a Sequence |
The first four are Request categories; the fifth is not. A Completion is the other half of the top-level taxonomy — it never starts anything, and it "always corresponds to a preceding Request."
Why the categories are not merely labels. Each one needs different information in its header to be deliverable and interpretable:
- a memory Request needs an address, and the address may be 32 or 64 bits wide
- a configuration Request identifies a Function rather than an address
- a Message may be routed by a rule rather than by either
- a Completion must identify the Request it answers
That variation is the architectural reason the header is not one fixed thing — which is Chapter 11.2's subject, and it is why this chapter introduces the categories without enumerating their subtypes or encodings. Chapter 11.7 owns the full taxonomy.
5. The Three Structural Regions
A TLP has a header, may have a data payload, and may have a digest — in that order.
| Region | Present when | What it is |
|---|---|---|
| Header | always | describes the packet: what operation, where it goes, how it is to be handled |
| Data payload | for some packet types | the data the operation carries |
| TLP digest | optionally | an end-to-end integrity value covering the packet |
"All TLPs must start with one of the headers." There is no headerless TLP, and no region precedes the header in the base packet format.
6. Payload Presence Is a Packet Property
Whether a TLP carries data follows from the operation it represents, and the pattern is worth stating because it is not symmetric.
| Operation | Does the Request carry data? | Does the answer carry data? |
|---|---|---|
| Memory Write (posted) | yes | no answer exists |
| Memory Read | no — it asks | yes |
| Configuration Write | yes | the Completion confirms; it need not carry data |
| Configuration Read | no | yes |
Read the diagonal. A write carries data going out and needs nothing coming back; a read carries nothing going out and needs data coming back. The payload is attached to whichever half of the operation actually moves the bytes, which is obvious once stated and is the source of a persistent misconception in the other direction ("every packet has data").
And the payload is DW-granular. "TLP data must be four-byte naturally aligned and in increments of four-Byte Double Words." That is a structural constraint with real consequences for a datapath, and Chapter 11.2 §3 develops the DW-versus-beat distinction that RTL engineers actually have to get right.
What determines payload presence in the encoding, how a partial-dword access is expressed, and how large a payload may be are Chapters 11.3 and 11.4.
7. Where a TLP Lives
The figure's argument is where the TLP begins and ends. It is created at the second box and consumed at the second-from-last. Everything between is carrying it, and the layers doing the carrying do not interpret it — the Data Link Layer "does not process the TLP header" and determines packet boundaries from indications the Physical Layer gives it.
So the loose statement "the bits on the wire are the TLP" is wrong in a way that matters. What is on the wire is a TLP plus the Data Link Layer's own additions plus the Physical Layer's encoding. Confusing the layers here is what leads to the misconception that a Link-level acknowledgement resolves a transaction (Chapter 10.3 §4).
8. Microarchitecture — A Packet Builder Front End
Turning an internal operation into a packet stream is three jobs, and separating them is what makes each one verifiable.
| Stage | Decides | Owned by |
|---|---|---|
| Class selection | which packet category this operation is | the Transaction Layer's request path (Chapter 10.1 §12) |
| Header construction | the header's contents, field by field | Chapter 11.3 — not this chapter |
| Envelope assembly | framing the header and any payload into one packet | §9's RTL |
The third stage is a genuinely different hardware problem from the other two, and it is the one this chapter owns. It never inspects a field. Its concerns are entirely about ownership: when a packet starts, that no other packet may interleave, that the payload belongs to this packet and not the next one, and that nothing changes under back-pressure.
9. RTL — Owning a Packet From First Beat to Last
// CONCEPTUAL. Internal envelope metadata — NOT a PCIe TLP header, not the
// format or type encoding, and not any field. It says only how many header
// words this packet has and whether a payload follows.
package tlp_env_pkg;
typedef struct packed {
// Number of header DWs for this packet. A COUNT, not an encoded field.
logic [2:0] hdr_dw;
logic has_payload;
// Payload length in DW. Internal; the encoded Length field is 11.3's.
logic [10:0] len_dw;
} tlp_meta_t;
endpackage// SYNTHESIZABLE. Frames one packet: header words, then payload words if any.
// Owns the packet from first beat to last and refuses to start another until
// this one finishes. Constructs nothing and decodes nothing.
import tlp_env_pkg::*;
module tlp_packetizer #(
// FIXED AT 32 — one interface beat carries exactly one PCIe DW in this
// model, and the elaboration check below enforces it. The parameter exists
// to name the width, not to make it configurable.
parameter int BEAT_W = 32,
parameter int MAX_HDR = 4
) (
input logic clk,
input logic rst_n,
// ---- Descriptor: what packet to build -------------------------------
input logic desc_valid,
output logic desc_ready,
input tlp_meta_t desc_meta,
// ---- Header words, from a construction block (Chapter 11.3) ---------
input logic hdr_valid,
output logic hdr_ready,
input logic [BEAT_W-1:0] hdr_data,
// ---- Payload words --------------------------------------------------
input logic pl_valid,
output logic pl_ready,
input logic [BEAT_W-1:0] pl_data,
// ---- The framed packet stream ---------------------------------------
output logic pkt_valid,
input logic pkt_ready,
output logic [BEAT_W-1:0] pkt_data,
output logic pkt_sop,
output logic pkt_eop,
output logic pkt_is_header, // this beat belongs to the header
// A descriptor this model cannot frame. Reported, and no packet starts.
output logic meta_illegal
);
typedef enum logic [1:0] { S_IDLE, S_HDR, S_PAY } state_e;
state_e state_q;
tlp_meta_t meta_q;
logic [2:0] hdr_cnt_q;
logic [10:0] pl_cnt_q;
generate
if (MAX_HDR < 1) $error("MAX_HDR must be at least 1");
// EXACTLY one DW per beat. The counters below count beats and the
// metadata counts DW, and this module makes those the same quantity on
// purpose. Permitting a wider beat would make every count in it wrong —
// see the note after the module, and Chapter 11.2 section 3 for what a
// wider datapath actually requires.
if (BEAT_W != 32)
$error("This teaching packetizer models exactly one PCIe DW per beat");
endgenerate
// A descriptor this model refuses: no header words, more header words than
// the model supports, or a payload claim that contradicts the length.
// Refusing is the only safe answer — guessing either way frames a packet
// whose boundaries the receiver will not agree with.
wire meta_bad = (desc_meta.hdr_dw == 3'd0)
|| (desc_meta.hdr_dw > 3'(MAX_HDR))
|| (desc_meta.has_payload && (desc_meta.len_dw == 11'd0))
|| (!desc_meta.has_payload && (desc_meta.len_dw != 11'd0));
// PACKET ATOMICITY: a new descriptor is accepted only when no packet is in
// flight. desc_ready depends on STATE alone — never on desc_valid, never on
// pkt_ready — so nothing can interleave and nothing can be withdrawn.
assign desc_ready = (state_q == S_IDLE);
assign meta_illegal = desc_valid && desc_ready && meta_bad;
wire desc_take = desc_valid && desc_ready && !meta_bad;
// Source readies depend on STATE and the downstream ready only.
assign hdr_ready = (state_q == S_HDR) && pkt_ready;
assign pl_ready = (state_q == S_PAY) && pkt_ready;
// pkt_valid follows the CURRENT source's valid. It never consults
// pkt_ready: a beat exists or it does not.
assign pkt_valid = (state_q == S_HDR) ? hdr_valid
: (state_q == S_PAY) ? pl_valid
: 1'b0;
assign pkt_data = (state_q == S_HDR) ? hdr_data : pl_data;
assign pkt_is_header = (state_q == S_HDR);
wire beat = pkt_valid && pkt_ready;
wire last_hdr_beat = (state_q == S_HDR) && (hdr_cnt_q == meta_q.hdr_dw - 3'd1);
wire last_pl_beat = (state_q == S_PAY) && (pl_cnt_q == meta_q.len_dw - 11'd1);
// The final beat of the packet: the last header beat when there is no
// payload, otherwise the last payload beat.
assign pkt_sop = beat && (state_q == S_HDR) && (hdr_cnt_q == 3'd0);
assign pkt_eop = beat && ((last_hdr_beat && !meta_q.has_payload)
|| last_pl_beat);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_q <= S_IDLE;
meta_q <= '0;
hdr_cnt_q <= '0;
pl_cnt_q <= '0;
end else begin
case (state_q)
S_IDLE: if (desc_take) begin
// Metadata is captured ONCE and is immutable for the packet's
// lifetime. A design that re-read desc_meta mid-packet could
// change the framing of a packet already in flight.
meta_q <= desc_meta;
hdr_cnt_q <= '0;
pl_cnt_q <= '0;
state_q <= S_HDR;
end
S_HDR: if (beat) begin
if (last_hdr_beat) begin
state_q <= meta_q.has_payload ? S_PAY : S_IDLE;
end else begin
hdr_cnt_q <= hdr_cnt_q + 3'd1;
end
end
S_PAY: if (beat) begin
if (last_pl_beat) state_q <= S_IDLE;
else pl_cnt_q <= pl_cnt_q + 11'd1;
end
default: state_q <= S_IDLE;
endcase
end
end
endmoduleClassification: synthesizable (the package above it: conceptual).
Semantics — every dimension:
| Dimension | Behaviour |
|---|---|
| Reset | idle; no packet in flight; no beat offered |
desc_ready | idle state only — never desc_valid, never pkt_ready |
hdr_ready / pl_ready | the matching state and pkt_ready only |
pkt_valid | the current source's valid — never pkt_ready |
| Payload stability | inherited from the sources, which owe stability by contract |
| Packet atomicity | a descriptor is accepted only in idle, so no packet can interleave |
| Metadata lifetime | captured once at acceptance, immutable until the packet ends |
| Header-only packet | pkt_eop on the last header beat |
| Illegal metadata | reported on meta_illegal; no packet starts |
MAX_HDR | checked at elaboration; a descriptor exceeding it is refused at run time |
BEAT_W | fixed at 32 — one DW per beat, enforced at elaboration (see below) |
Trace a header-only packet with three header words. desc_take captures the metadata and enters S_HDR. Three beats pass from the header source; pkt_sop on the first, pkt_eop on the third because has_payload is low; back to idle. Four cycles minimum, and desc_ready is low for three of them — which is the atomicity guarantee visible as a waveform.
Trace a packet with payload under back-pressure. pkt_ready drops mid-header: hdr_ready drops with it, the counter holds, and the same beat stays offered. Nothing about the packet changes; the header source simply waits. When pkt_ready returns the count resumes exactly where it stopped.
What it teaches — four things:
- Atomicity is a state condition, not a rule.
desc_ready = (state == S_IDLE)is the entire mechanism. There is no way to start a second packet mid-flight because there is no path. - The metadata is captured, not sampled. A design that read
desc_metacontinuously would let the producer change a packet's framing after transmission began — and the receiver would already have committed to the old boundaries. - The end-of-packet condition is where header-only packets are usually broken.
pkt_eophas two sources: the last header beat when there is no payload, and the last payload beat otherwise. Implementing only the second leaves a header-only packet without an end, and §12's second scenario is that bug from the receiver's side. - Illegal metadata is refused, not framed. Both guesses are wrong: framing a payload nobody supplies stalls the stream, and dropping a claimed payload desynchronises the receiver. Refusing and reporting is the only answer that fails safely.
Deliberately simplified: exactly one DW per beat, enforced; one header source and one payload source; no digest region (Chapter 11.2 adds it); no flow-control gating before transmission (Module 16); and no support for aborting a packet in flight.
Production implication: a real packetiser handles a beat wider than a DW with the alignment consequences that brings, gates transmission on flow-control credit, may support a discontinue mechanism for aborting a packet, and receives its header words from the construction logic of Chapter 11.3.
10. Assertions
// SVA over tlp_packetizer. Implementation invariants for THIS design — not
// PCIe protocol rules, and not claims about any packet field.
// ENVIRONMENT ASSUMPTIONS. Both sources owe valid- and payload-stability, and
// must supply as many words as the accepted metadata claims; the packetiser
// cannot enforce either.
assume property (@(posedge clk) disable iff (!rst_n)
(hdr_valid && !hdr_ready) |=> (hdr_valid && $stable(hdr_data)));
assume property (@(posedge clk) disable iff (!rst_n)
(pl_valid && !pl_ready) |=> (pl_valid && $stable(pl_data)));
assume property (@(posedge clk) disable iff (!rst_n)
(desc_valid && !desc_ready) |=> (desc_valid && $stable(desc_meta)));
// ATOMICITY — P1: no descriptor is accepted while a packet is in flight.
// The single property that makes "packets do not interleave" structural.
property p_no_interleave;
@(posedge clk) disable iff (!rst_n)
(state_q != S_IDLE) |-> !desc_ready;
endproperty
a_atomic : assert property (p_no_interleave);
// ATOMICITY — P2: the metadata governing a packet cannot change once the
// packet has started. Catches a design that read desc_meta continuously.
property p_meta_frozen;
@(posedge clk) disable iff (!rst_n)
(state_q != S_IDLE) |=> ((state_q == S_IDLE) || $stable(meta_q));
endproperty
a_meta_frozen : assert property (p_meta_frozen);
// FRAMING — P3: exactly one start-of-packet per packet, on the first header
// beat and nowhere else.
property p_sop_once;
@(posedge clk) disable iff (!rst_n)
pkt_sop |-> ((state_q == S_HDR) && (hdr_cnt_q == 3'd0));
endproperty
a_sop_first : assert property (p_sop_once);
// FRAMING — P4: no payload beat is emitted before the header is complete.
property p_payload_after_header;
@(posedge clk) disable iff (!rst_n)
(beat && !pkt_is_header) |-> (state_q == S_PAY);
endproperty
a_payload_ordered : assert property (p_payload_after_header);
// FRAMING — P5: a packet whose metadata says no payload emits none. Catches
// the classic "header-only packet gained a data beat" bug.
property p_no_payload_when_unclaimed;
@(posedge clk) disable iff (!rst_n)
(!meta_q.has_payload && (state_q != S_IDLE)) |-> (state_q != S_PAY);
endproperty
a_no_stray_payload : assert property (p_no_payload_when_unclaimed);
// FRAMING — P6: the header beat count matches the accepted metadata exactly.
property p_header_count_exact;
@(posedge clk) disable iff (!rst_n)
(beat && last_hdr_beat) |-> (hdr_cnt_q == meta_q.hdr_dw - 3'd1);
endproperty
a_hdr_count : assert property (p_header_count_exact);
// FRAMING — P7: the payload beat count matches the accepted metadata exactly.
property p_payload_count_exact;
@(posedge clk) disable iff (!rst_n)
(beat && last_pl_beat) |-> (pl_cnt_q == meta_q.len_dw - 11'd1);
endproperty
a_pl_count : assert property (p_payload_count_exact);
// FRAMING — P8: end-of-packet occurs exactly once, and returns to idle.
property p_eop_ends_packet;
@(posedge clk) disable iff (!rst_n)
pkt_eop |=> (state_q == S_IDLE);
endproperty
a_eop_returns : assert property (p_eop_ends_packet);
// FRAMING — P9: a packet that started eventually ends, given source and sink
// fairness. DEPENDS ON THE ASSUMPTIONS BELOW; without them a source that
// never supplies its promised words holds the packet open forever, and this
// is unprovable.
property p_packet_terminates;
@(posedge clk) disable iff (!rst_n)
pkt_sop |-> s_eventually (pkt_eop);
endproperty
a_packet_ends : assert property (p_packet_terminates);
assume property (@(posedge clk) disable iff (!rst_n) s_eventually (pkt_ready));
assume property (@(posedge clk) disable iff (!rst_n)
(state_q == S_HDR) |-> s_eventually (hdr_valid));
assume property (@(posedge clk) disable iff (!rst_n)
(state_q == S_PAY) |-> s_eventually (pl_valid));
// STABILITY — P10: an offered beat is stable while stalled. Follows from the
// source assumptions plus the mux, and is asserted so a future buffering
// stage cannot break it silently.
property p_beat_stable;
@(posedge clk) disable iff (!rst_n)
(pkt_valid && !pkt_ready) |=> (pkt_valid && $stable(pkt_data)
&& $stable(pkt_is_header));
endproperty
a_beat_stable : assert property (p_beat_stable);
// LEGALITY — P11: illegal metadata starts no packet.
property p_illegal_starts_nothing;
@(posedge clk) disable iff (!rst_n)
meta_illegal |=> (state_q == S_IDLE);
endproperty
a_illegal_refused : assert property (p_illegal_starts_nothing);
// SAFETY — P12: no interface output is ever unknown.
property p_outputs_never_unknown;
@(posedge clk) disable iff (!rst_n)
!$isunknown({desc_ready, hdr_ready, pl_ready, pkt_valid, pkt_sop, pkt_eop,
pkt_is_header, meta_illegal});
endproperty
a_no_x : assert property (p_outputs_never_unknown);P1 and P2 are the packet-atomicity pair, and P2 is the one that gets forgotten. P1 stops a second packet starting; P2 stops the first packet's framing changing after it began. A design that satisfied P1 while continuously re-reading desc_meta would let a producer alter a length mid-transmission — and the receiver, having already committed to the original boundaries, would misparse everything after.
P5 exists because header-only packets are the case people under-test. Most traffic in a bring-up test has a payload, so a stray-payload bug hides. The property is one line and it fires on the first header-only packet.
P9 is the only liveness property and it carries three assumptions, not one. Sink fairness alone is insufficient: a header source that stops supplying words mid-packet holds the packet open forever with pkt_ready high the whole time. Stating all three is what makes the property mean "this module does not stall a packet" rather than "packets always finish."
11. Verification
Monitors observe: the descriptor handshake and its metadata; both source handshakes; every packet beat with its start, end and header indication; and meta_illegal.
The scoreboard independently predicts the packet shape from the accepted metadata — it computes the expected beat count as hdr_dw + (has_payload ? len_dw : 0) in its own arithmetic and checks the observed stream against it, including the positions of pkt_sop and pkt_eop. It must not read hdr_cnt_q, pl_cnt_q or state_q, which are the structures under test.
Packet shapes
- Header-only packet. Verify the beat count equals
hdr_dw,pkt_eoplands on the last header beat, and no payload beat is emitted (P5). - Header plus one payload DW. The minimum payload case, and the one where an off-by-one in
last_pl_beatis most visible. - Header plus many payload DWs. Verify the count is exact (P7).
- Every legal
hdr_dwvalue from 1 toMAX_HDR. Verify the header count for each. - Back-to-back packets. Verify each is framed independently,
pkt_sopoccurs once per packet, and the second cannot start before the first ends (P1, P8).
Back-pressure
pkt_readylow during the header. Verify the beat is held stable (P10), the counter does not advance, and both source readies drop.pkt_readylow during the payload. Verify the same.pkt_readytoggling every other cycle across a long packet. Verify the beat count is still exact.- A source stalling mid-packet. Hold
hdr_validlow inS_HDR. Verify the packet stays open, nothing is emitted, and no descriptor is accepted — then release and verify the packet completes.
Negative tests
- A descriptor offered while a packet is active. Verify it is not accepted (P1) and the active packet is unaffected.
- Illegal metadata: zero header words. Verify
meta_illegalpulses and no packet starts (P11). - Illegal metadata:
has_payloadwith zero length. Verify the same. - Illegal metadata: no payload claimed but a non-zero length. Verify the same — this catches a producer that set one field and forgot the other.
hdr_dwgreater thanMAX_HDR. Verify refusal rather than a truncated packet.- Reset mid-packet. Verify the state returns to idle, no further beats are emitted, and the next descriptor is framed cleanly. Verify explicitly that no
pkt_eopis fabricated — a reset is not a packet ending, and a receiver will see a truncated packet regardless of what this block does. - Liveness with the fairness assumptions disabled. Confirm P9 correctly fails; a liveness property that passes without its assumptions is not checking anything.
Parameter corners
MAX_HDR = 1. Verify a one-word header frames correctly and thathdr_dw = 2is refused.- Maximum
len_dw. Verify the payload counter reaches it without wrapping. BEAT_Wset to anything other than 32. Verify elaboration fails. This module's counters count beats while its metadata counts DW, and it is correct only because those are the same quantity here; a wider beat must be handled as Chapter 11.2 §3 describes, not by relaxing this check.
Coverage should include: every hdr_dw value; payload absent, one DW, and a large count; pkt_ready continuously high, continuously low and randomly toggled; each source stalled in its own state; every illegal-metadata case; and reset in each state.
12. Debugging
Symptom: the transaction class is right internally, but the receiver interprets the packet as a different class
The packet was framed correctly and read incorrectly, or framed incorrectly and read as something else — and the two are distinguishable.
Check the framing first, because it is cheaper. If the beat count matches the descriptor's hdr_dw plus its payload claim, and pkt_sop/pkt_eop land in the right places, this block did its job and the fault is in the header contents — which is Chapter 11.3's construction logic, not this one.
If the framing is wrong, the class confusion is a consequence. A receiver that started parsing at the wrong beat will read whatever happens to be there as a header, and the class it reports is arbitrary. The signature is that the misinterpretation changes with the previous packet's shape — which immediately points at framing rather than at construction.
The observation that separates them: does the receiver's idea of where the packet starts agree with pkt_sop? If yes, contents; if no, framing.
Symptom: a header-only operation unexpectedly carries one payload beat
This is payload-presence control, and there are only three ways to get here.
- The descriptor claimed a payload it should not have. Check
has_payloadandlen_dwat the accepted descriptor — the fault is upstream, in whatever built the metadata. - The end-of-packet condition ignores the header-only case. If
pkt_eopis derived only from the last payload beat, a header-only packet never ends inS_HDRand the state machine walks intoS_PAY. P5 catches it. - The metadata changed after acceptance. P2's failure — the packet started as header-only and became payload-bearing mid-flight.
One beat, and one beat only, is the useful detail. A single extra beat points at a boundary condition; a payload of the claimed length points at cause 1, which is not this block's problem at all.
Symptom: a second packet overwrites the first packet's framing under back-pressure
This is the atomicity failure, and it is almost always the same edit. Someone widened desc_ready to accept a descriptor while a packet was in flight — usually to remove the idle cycle between packets, which is a real and tempting optimisation.
What it produces. The captured metadata is replaced mid-packet, so the header count or payload length changes for a packet already partly transmitted. The receiver has committed to the original boundaries; from the next beat onward, everything is misaligned. The damage does not stop at this packet — the receiver's framing is now offset, and subsequent packets are also misparsed.
Why back-pressure is in the symptom. With no stall, packets are short-lived and the overlapping window barely exists. Under back-pressure a packet stays open for many cycles, and the probability that a new descriptor arrives during it approaches one.
What catches it: P1 and P2. And the legitimate way to remove the idle cycle is to pipeline the descriptor acceptance into a holding register while keeping the packet's captured metadata immutable — more logic, and it must preserve exactly the property the shortcut broke.
13. Common Misconceptions
- "A TLP and a transaction are the same thing." A transaction is a logical operation and its lifecycle; a TLP is a packet carrying part of it. A read Sequence is at least two TLPs.
- "Every TLP carries a payload." Data is present "for some types of packets." A read Request carries none — it asks.
- "A Completion is not a TLP." A TLP conveys "a Request or Completion." Both are TLPs; they are the two halves of the taxonomy.
- "A Data Link acknowledgement is a TLP." It is a different packet type generated at a different layer for Link management, and it never reaches the Transaction Layer as a transaction (Modules 14–15).
- "A TLP contains physical-layer information." The Physical Layer's encoding and the Data Link Layer's additions wrap a TLP; they are not part of it. What is on the wire contains a TLP (§7).
- "One transaction always equals one TLP." True only for the posted case, and true there because a posted Sequence contains exactly one packet. Every other case involves at least two.
- "An internal descriptor is a TLP header." §9's
tlp_meta_tis a count and a flag. It has no relationship to any PCIe field, and the real header is Chapter 11.3's. - "Every TLP routes by address." Configuration Requests identify a Function; Messages may be routed by a rule. Address routing is one of several mechanisms (Chapter 11.5).
- "All TLP headers are the same size." They are not, and the reason is architectural: different categories need different information. Chapter 11.2 is next for exactly this reason.
- "Payload length equals the size of the software operation." They are related, not equal. A software transfer may become several packets, and how payload size is bounded is Chapter 11.4's.
14. Understanding Check
15. What's Next
This chapter established what a TLP is, that it is not the same object as a transaction, which categories exist, what regions a packet has, and how a packet is owned from first beat to last.
It described the regions and did not size them.
Chapter 11.2 — TLP Structure does: why headers come in more than one length, what determines which, how payload length relates to packet length, the DW-versus-beat distinction that RTL engineers actually trip over, and a structural parser that counts a packet's regions without decoding a single field.
Chapter 11.3 then opens the header itself, and the rest of Module 11 takes payloads, routing, attributes and the full packet taxonomy.
The idea to carry forward: a TLP is a packet that carries part of a transaction — and how many packets a transaction takes depends on what it asked for.