Skip to content

PCIe · Module 11

Payloads — Sizing, Limits, and Moving the Data

Not every TLP carries data, and the ones that do are bounded by a negotiated limit rather than by the length field's reach. Why Max_Payload_Size is a transmit obligation and not a receive buffer size, why it does not bound a read request, and the datapath problem of moving a DW-counted payload across wider beats.

Chapter 11.3 decoded the field that says how much data a packet carries and stopped there. It produced a DW count and never moved a byte.

Two questions remain, and they are not the same question. Which packets carry data at all, and how much are they allowed to carry? The first is answered by a header bit. The second is answered by something that is not in the packet.

How does a TLP carry data, what limits its size, and how does hardware move a DW-counted payload across a datapath that is not one DW wide?

1. Not Every TLP Carries Data

The header's format field already answered this, and Chapter 11.3 §4 extracted it: two of the base Fmt encodings mean no data and two mean with data. The payload's existence is a header fact, decided before a single payload byte is examined.

That is not a small point. It means a receiver never has to infer whether data follows — the packet says so, unambiguously, in the first byte. And it means the four combinations of header form and payload presence are all legal and all common:

FmtHeaderDataA packet of this shape
000b3 DWnoa request that only asks
001b4 DWnothe same, needing a wider address
010b3 DWyesa write, or a completion returning data
011b4 DWyesa write with a wider address

Which families fall where is Chapter 11.7's taxonomy, but the pattern that matters here is already visible from Module 10:

  • A write carries the data it is writing. The operation and its payload travel together — that is what made it posted (Chapter 10.3).
  • A read carries no data. It carries a request for data. The data comes back in a Completion (Chapter 10.2) — which is a different packet, travelling in the other direction, and it is that packet that has a payload.
  • A completion may or may not carry data, depending on whether it answers an operation that returns any.

§6 turns on this asymmetry, so it is worth fixing now: the packet that asks for 4 KB has no payload at all, and the packets that deliver it have several.

2. The Verified Limits

3. Three Different Quantities, Routinely Confused

A payload has three numbers attached to it and they answer three different questions. Conflating any two produces a distinct bug.

QuantityWhat it isWhere it livesQuestion it answers
Lengththe size of this packet's data regionin this packet's headerhow many DW must the receiver consume before the packet ends
Byte enableswhich bytes of the first and last DW are meaningfulin this packet's headerhow much of that data the operation concerns
Max_Payload_Sizethe largest data payload this transmitter may sendin configuration spacehow large any packet is allowed to be

4. The Length Field Bounds the Packet, Not the Transfer

A 10-bit DW count reaches 1024 DW, so a single TLP can in principle carry 4096 bytes. Two independent facts prevent that from being the practical answer to "how much data can I move in one packet."

First, MPS. The operative Max_Payload_Size is set by software and is commonly far below the field's reach — 128 or 256 bytes on a great many real systems, because the operative value is constrained by what the participating Functions support and a single conservative device pulls the whole path down. The field can express 4096; the transmitter may only be permitted 128.

Second, the transfer is not the packet. Software asks for a region of memory. The Transaction Layer produces however many TLPs that region requires. Segmentation is normal, not exceptional (§7).

5. How the Operative Limit Is Established

The mechanism has two registers and one actor, and knowing which is which prevents a common misreading.

Register fieldWritten byMeaning
Max_Payload_Size Supported (Device Capabilities, bits 2:0)the device, as a capability report"I am capable of this much"
Max_Payload_Size (Device Control, bits 7:5)software, as a control setting"you are permitted this much"

The capability field is read by software; the control field is written by software. The device reports what it can do and then obeys what it is told, and those are different fields — a design that reports its capability and then transmits at that capability, ignoring the control field, has violated its transmit obligation on any system that configured it lower.

Why software may set it lower than a device supports. A packet crosses a path, and every participant on that path has to handle it. Software sets an operative value that does not exceed what the participating Functions report as supported — so one conservative device constrains the packets that traverse its path. That is why a system full of 512-byte-capable endpoints can end up operating at 128.

Why this is platform behaviour and not a packet rule. Exactly how a given platform's firmware and operating system choose the operative value — whether they optimise per-path, apply a uniform hierarchy-wide setting, or leave a conservative default in place — is platform software policy, and it varies. The rule a device must implement is unambiguous: transmit within the value in your Device Control register. What put that value there is not the device's concern.

6. MPS Does Not Bound a Read Request

This is the confusion that costs the most debugging time, and §1's asymmetry is the whole explanation.

Memory Read Requests are not restricted in length by Max_Payload_Size. They are restricted by Max_Read_Request_Size.

Why the two limits are separate is almost obvious once stated: a read request has no payload. It carries a length field saying how much data it wants, and no data of its own. MPS bounds data payloads, so it does not apply to a packet that has none.

But the requested data has to come back, and it comes back in Completions — which do carry payloads, and which therefore are bound by MPS.

7. Segmentation — One Transfer, Many Packets

A client asks the Transaction Layer to write a region. The region is larger than the operative MPS. The Transaction Layer produces a sequence of TLPs, and this is ordinary operation rather than an error path.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
one client transfer
   → divided at the operative MPS
   → a sequence of data-bearing TLPs
   → each independently framed, headered, and routed

What must hold across the sequence:

  • Every packet's payload is within the operative limit. Including the last one, which is usually smaller and is where off-by-one errors concentrate.
  • The bytes are conserved. The sum of the segments equals the transfer, with no byte sent twice and none dropped — §12's conservation property.
  • Each segment is a complete, independent TLP. It has its own header, its own length, and its own routing information. There is no "continuation packet" that inherits context from its predecessor.

8. The Datapath Problem MPS Does Not Solve

Here is where a payload stops being a number and becomes hardware.

The Length field counts DW. The datapath is not one DW wide. A modern link's Transaction Layer interface is 64, 128, 256 or 512 bits — 2, 4, 8 or 16 DW per beat. So a payload of N DW is not N transfers; it is ceil(N / DW_PER_BEAT) beats, and the last one is usually partial.

PayloadDatapathBeatsLast beat
32 DW4 DW/beat8full
33 DW4 DW/beat91 of 4 DW valid
1 DW4 DW/beat11 of 4 DW valid
1024 DW4 DW/beat256full

Three things the RTL must get right, and each has a characteristic failure:

  • The beat count. A truncating division instead of a ceiling drops the final partial beat — the payload is short by up to DW_PER_BEAT - 1 DW and the receiver's framing desynchronises.
  • The last-beat valid mask. A full mask on a partial beat sends garbage DW that the receiver counts as payload.
  • The single-beat case. A payload smaller than one beat is both the first and the last beat, and logic that treats first and last as mutually exclusive produces no valid mask at all.

§10's module is this arithmetic, and the reason it is worth a module is that all three failures are silent in a testbench whose payloads happen to be multiples of the datapath width.

9. RTL — Payload Beat Sequencing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Convert a DW-counted payload into a beat sequence on a
// datapath wider than one DW, with a correct final partial beat.
// DW granularity of the payload and the 10-bit/DW Length semantics:
// NORMATIVE. The beat width, the port names and the valid-mask
// representation: ILLUSTRATIVE — PCIe defines packet content, not a
// Transaction Layer interface width.
module tlp_payload_beats #(
  // DW per beat. A 128-bit datapath is 4.
  parameter int DW_PER_BEAT = 4,
  // Widest payload the Length field can represent: 1024 DW.
  parameter int MAX_DW      = 1024
) (
  input  logic clk,
  input  logic rst_n,
 
  // Start a payload. length_dw is the REPRESENTED count from Chapter 11.3
  // (zero already resolved to 1024) — never the raw encoded field.
  input  logic                     start,
  input  logic [$clog2(MAX_DW+1)-1:0] length_dw,
 
  input  logic                     beat_ready,   // downstream accepts
 
  output logic                     beat_valid,
  output logic                     beat_first,
  output logic                     beat_last,
  // Per-DW validity within this beat. All ones except on a partial last beat.
  output logic [DW_PER_BEAT-1:0]   beat_dw_valid,
 
  output logic                     busy
);
 
  generate
    if (DW_PER_BEAT < 1)
      $error("DW_PER_BEAT must be at least 1");
    if (DW_PER_BEAT & (DW_PER_BEAT - 1))
      $error("This model assumes a power-of-two DW_PER_BEAT");
  endgenerate
 
  localparam int CNT_W = $clog2(MAX_DW + 1);
 
  // Remaining DW in the current payload.
  logic [CNT_W-1:0] rem_q;
  logic             active_q;
  logic             first_q;
 
  // How many DW this beat carries. The min() is the whole partial-beat rule.
  wire [CNT_W-1:0] this_beat_dw =
        (rem_q >= CNT_W'(DW_PER_BEAT)) ? CNT_W'(DW_PER_BEAT) : rem_q;
 
  wire fire = active_q && beat_ready;
 
  assign beat_valid = active_q;
  assign beat_first = active_q && first_q;
  // Last when this beat consumes everything that remains. Note this is true
  // simultaneously with beat_first for a payload of one beat or less.
  assign beat_last  = active_q && (rem_q <= CNT_W'(DW_PER_BEAT));
  assign busy       = active_q;
 
  // Valid mask. Built from this_beat_dw rather than from beat_last, so the
  // single-beat case needs no special arm.
  always_comb begin
    for (int d = 0; d < DW_PER_BEAT; d++)
      beat_dw_valid[d] = active_q && (CNT_W'(d) < this_beat_dw);
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rem_q <= '0; active_q <= 1'b0; first_q <= 1'b0;
    end else if (start && !active_q) begin
      // A zero length_dw here is a caller error: the represented count is
      // never zero (Chapter 11.3 P3). Guarded rather than assumed.
      if (length_dw != '0) begin
        rem_q    <= length_dw;
        active_q <= 1'b1;
        first_q  <= 1'b1;
      end
    end else if (fire) begin
      first_q <= 1'b0;
      if (rem_q <= CNT_W'(DW_PER_BEAT)) begin
        rem_q    <= '0;
        active_q <= 1'b0;
      end else begin
        rem_q <= rem_q - CNT_W'(DW_PER_BEAT);
      end
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. One down-counter in DW, with the beat's DW count derived as a minimum against the datapath width. The minimum is the design decision — it makes the partial last beat fall out of the same expression as a full beat, so there is no separate final-beat arm to get wrong.

State. rem_q (DW remaining), active_q, first_q. Nothing derived from length_dw after the start cycle, so a caller changing it mid-payload cannot corrupt the sequence.

Cycle behaviour. start is accepted when idle. Beats present while active_q; each accepted beat consumes up to DW_PER_BEAT DW; the payload ends on the beat where the remainder is exhausted.

Contract. The caller supplies the represented DW count, never the raw encoded field — Chapter 11.3 §5's rule is applied once, upstream, and this module relies on it. It also relies on length_dw being stable during the start cycle.

Failure — the three from §8, and each is one character. rem_q / DW_PER_BEAT instead of the minimum-based counter truncates the final beat. beat_dw_valid = '1 unconditionally sends invalid DW as payload. And computing the mask from !beat_first instead of from this_beat_dw produces a full mask on a single-beat payload's only beat.

Deliberately simplified: no header interleaving, no digest (Chapter 11.2), no data content — this sequences beats, it does not carry them; no alignment or boundary rules beyond DW counting.

DV. §11's P1–P5.

10. RTL — MPS-Bounded Segmenter

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Divide a client transfer into TLP-sized segments bounded by
// the RUN-TIME operative Max_Payload_Size.
// The MPS encoding and the transmit obligation: NORMATIVE (section 2).
// The client interface, the segment descriptor and the port names:
// ILLUSTRATIVE. Alignment and boundary rules beyond the MPS bound are
// Module 12's and are NOT modelled here.
module tlp_mps_segmenter #(
  parameter int MAX_DW = 1024
) (
  input  logic clk,
  input  logic rst_n,
 
  // ---- Operative limit, from configuration space at RUN TIME -----------
  // The DECODED byte limit corresponding to the Device Control field
  // (section 2): 128, 256, 512, 1024, 2048 or 4096. Supplied as an input
  // rather than a parameter because software establishes it after
  // elaboration — see section 4.
  input  logic [12:0]              mps_bytes,
  input  logic                     mps_valid,
 
  // ---- Client transfer -------------------------------------------------
  input  logic                     req_valid,
  input  logic [$clog2(MAX_DW+1)+8:0] req_total_dw,   // may exceed one TLP
  output logic                     req_ready,
 
  // ---- Segment descriptors out ----------------------------------------
  output logic                     seg_valid,
  output logic [$clog2(MAX_DW+1)-1:0] seg_length_dw,
  output logic                     seg_first,
  output logic                     seg_last,
  input  logic                     seg_ready,
 
  // The operative limit was not available when a transfer was offered.
  output logic                     no_limit_error
);
 
  localparam int TOT_W = $clog2(MAX_DW+1) + 9;
  localparam int SEG_W = $clog2(MAX_DW+1);
 
  // Operative limit in DW. Four bytes per DW.
  wire [10:0] mps_dw = mps_bytes[12:2];
 
  logic [TOT_W-1:0] rem_q;
  logic             active_q, first_q, err_q;
 
  // Segment size: the operative limit, or whatever remains if that is less.
  // The SAME min() shape as section 9, for the same reason.
  wire [TOT_W-1:0] limit_ext = TOT_W'(mps_dw);
  wire [TOT_W-1:0] this_seg  = (rem_q >= limit_ext) ? limit_ext : rem_q;
 
  // Refuse a transfer rather than emit an unbounded packet. A transmitter
  // that does not know its limit must not guess one (section 3).
  wire accept = req_valid && !active_q && mps_valid && (mps_dw != 11'd0);
 
  assign req_ready      = !active_q && mps_valid && (mps_dw != 11'd0);
  assign seg_valid      = active_q;
  assign seg_length_dw  = SEG_W'(this_seg);
  assign seg_first      = active_q && first_q;
  assign seg_last       = active_q && (rem_q <= limit_ext);
  assign no_limit_error = err_q;
 
  wire fire = active_q && seg_ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rem_q <= '0; active_q <= 1'b0; first_q <= 1'b0; err_q <= 1'b0;
    end else begin
      if (req_valid && !active_q && !(mps_valid && (mps_dw != 11'd0)))
        err_q <= 1'b1;                       // sticky, for observability
 
      if (accept && (req_total_dw != '0)) begin
        rem_q    <= req_total_dw;
        active_q <= 1'b1;
        first_q  <= 1'b1;
      end else if (fire) begin
        first_q <= 1'b0;
        if (rem_q <= limit_ext) begin
          rem_q    <= '0;
          active_q <= 1'b0;
        end else begin
          rem_q <= rem_q - limit_ext;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. A DW remainder counter bounded by a run-time limit. mps_bytes is an input, not a parameter — §4's point made structurally, because a synthesis-time constant would fix the answer before software has decided it.

State. rem_q, active_q, first_q, and a sticky err_q.

Cycle behaviour. A transfer is accepted only when the limit is known and non-zero. Segment descriptors present while active; each accepted descriptor consumes up to the limit; the last is whatever remains.

Contract. The caller relies on the sum of seg_length_dw across a transfer equalling req_total_dw exactly, and on no segment exceeding the operative limit. The module relies on mps_bytes being stable while a transfer is in progress — reconfiguring MPS mid-transfer is a system-level question this model does not attempt.

Failure — and the first one is the reason no_limit_error exists. Defaulting to a limit when mps_valid is low emits packets sized by a guess, and on a link configured lower every one of them violates the transmit obligation. Using mps_bytes directly as a DW count sends packets four times too large. And a strict > in the last-segment test emits a final zero-length segment.

Deliberately simplified: no address generation, no alignment or boundary rules (Module 12), no ordering interaction (Chapter 10.3), no MPS reconfiguration, no per-Traffic-Class differentiation.

Production implication: a real segmenter also honours the alignment and boundary rules, generates the per-segment address and byte enables, and interacts with flow control (Chapter 10.1 §8) — none of which changes the MPS arithmetic above.

11. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over tlp_payload_beats and tlp_mps_segmenter. Local contracts plus the
// normative transmit obligation of section 2 — not claims about the full set
// of PCIe packet-size rules, which include alignment and boundary constraints
// this model does not implement.
 
// BEATS — P1: the beat count is a CEILING. Stated as a range check on the
// accepted-beat count rather than as a formula, so a truncating division
// fails rather than agreeing with itself.
// (dw_seen is a testbench accumulator of popcount(beat_dw_valid).)
property p_beat_dw_conserved;
  @(posedge clk) disable iff (!rst_n)
  (beat_valid && beat_last && beat_ready)
    |-> (dw_seen + $countones(beat_dw_valid) == length_at_start);
endproperty
a_beat_conserved : assert property (p_beat_dw_conserved);
 
// BEATS — P2: every beat except the last is FULL. Catches a mask derived
// from the wrong term.
property p_full_until_last;
  @(posedge clk) disable iff (!rst_n)
  (beat_valid && !beat_last) |-> (&beat_dw_valid);
endproperty
a_full_until_last : assert property (p_full_until_last);
 
// BEATS — P3: the last beat carries at least one DW and never more than the
// datapath width. Rules out both a zero-DW trailing beat and an overrun.
property p_last_beat_bounded;
  @(posedge clk) disable iff (!rst_n)
  (beat_valid && beat_last)
    |-> (($countones(beat_dw_valid) >= 1)
      && ($countones(beat_dw_valid) <= DW_PER_BEAT));
endproperty
a_last_bounded : assert property (p_last_beat_bounded);
 
// BEATS — P4: THE SINGLE-BEAT CASE. first and last may be simultaneous, and
// the mask must still be correct. Written positively so a design that treats
// them as exclusive fails here rather than silently.
property p_single_beat_ok;
  @(posedge clk) disable iff (!rst_n)
  (beat_valid && beat_first && beat_last)
    |-> (($countones(beat_dw_valid) >= 1)
      && ($countones(beat_dw_valid) <= DW_PER_BEAT));
endproperty
a_single_beat : assert property (p_single_beat_ok);
 
// BEATS — P5: no beat is presented outside a payload.
property p_no_beat_when_idle;
  @(posedge clk) disable iff (!rst_n)
  !busy |-> !beat_valid;
endproperty
a_no_stray_beat : assert property (p_no_beat_when_idle);
 
// SEGMENTER — P6: THE NORMATIVE TRANSMIT OBLIGATION. No segment exceeds the
// operative limit. This is the one property that is about PCIe rather than
// about this module.
property p_segment_within_mps;
  @(posedge clk) disable iff (!rst_n)
  (seg_valid && mps_valid) |-> (seg_length_dw <= mps_dw);
endproperty
a_within_mps : assert property (p_segment_within_mps);
 
// SEGMENTER — P7: no segment is empty. A zero-length segment is a packet
// that claims a payload and carries none.
property p_segment_nonzero;
  @(posedge clk) disable iff (!rst_n)
  seg_valid |-> (seg_length_dw != '0);
endproperty
a_segment_nonzero : assert property (p_segment_nonzero);
 
// SEGMENTER — P8: CONSERVATION across the transfer. Bytes are neither
// duplicated nor lost by the division.
// (seg_sum is a testbench accumulator.)
property p_transfer_conserved;
  @(posedge clk) disable iff (!rst_n)
  (seg_valid && seg_last && seg_ready)
    |-> (seg_sum + seg_length_dw == total_at_start);
endproperty
a_transfer_conserved : assert property (p_transfer_conserved);
 
// SEGMENTER — P9: every segment before the last is EXACTLY the limit. A
// segmenter that under-fills is not wrong per PCIe, but it is not this
// design's contract, and under-filling silently halves throughput.
property p_full_segments_until_last;
  @(posedge clk) disable iff (!rst_n)
  (seg_valid && !seg_last && mps_valid) |-> (seg_length_dw == SEG_W'(mps_dw));
endproperty
a_full_segments : assert property (p_full_segments_until_last);
 
// SEGMENTER — P10: a transfer is never accepted without a known limit.
// The property that makes "refuse rather than guess" checkable.
property p_no_transfer_without_limit;
  @(posedge clk) disable iff (!rst_n)
  (!mps_valid || (mps_dw == 11'd0)) |-> !req_ready;
endproperty
a_needs_limit : assert property (p_no_transfer_without_limit);
 
// SEGMENTER — P11: exactly one first and one last per transfer.
property p_one_first_per_transfer;
  @(posedge clk) disable iff (!rst_n)
  (seg_valid && seg_first && seg_ready && !seg_last)
    |=> (!seg_first throughout (seg_valid && seg_last)[->1]);
endproperty
 
// SAFETY — P12: no size output is ever unknown.
property p_sizes_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  seg_valid |-> !$isunknown({seg_length_dw, seg_first, seg_last});
endproperty
a_no_x : assert property (p_sizes_never_unknown);

P6 is the chapter's normative property and it is deliberately trivial to state. The transmit obligation is a single inequality, so the assertion is a single inequality, and any segmenter that satisfies it cannot emit an oversized packet. The reason it still needs writing is that it is the only check that fires when mps_bytes changes to a smaller value than the design was tested at.

P10 encodes a design stance rather than a specification rule. PCIe does not say what a transmitter should do when it does not know its limit — but there are only two options, guess or refuse, and one of them violates §2's obligation on every link configured below the guess. P10 makes the choice checkable.

P2 and P4 are a pair that catches opposite mistakes. P2 fails a design that under-fills an intermediate beat; P4 fails a design that treats first and last as mutually exclusive and produces no mask when they coincide. A single-beat payload is the only stimulus that distinguishes them, which is why §12 makes it a required scenario.

P1 and P8 are the conservation pair, one within a packet and one across a transfer. Both are stated as accumulator equalities rather than as recomputations of the RTL's arithmetic, so a design and a testbench that share the same wrong formula still fail.

12. Verification

Monitors observe: the beat stream with its valid masks and first/last markers; the segment descriptor stream; and the operative limit input with its valid qualifier.

The scoreboard computes expected beat counts, expected masks and expected segment sizes independently — its own ceiling division, its own minimum, its own MPS-byte-to-DW conversion. It must not instantiate the design's counters or reuse this_beat_dw. Sharing the arithmetic is how a truncating division passes: both sides truncate and agree.

Payload beat sequencing

  • A payload of exactly one DW. The single-beat case: first and last simultaneous, one DW valid (P4).
  • A payload of exactly DW_PER_BEAT DW. One full beat, first and last simultaneous, full mask.
  • A payload of DW_PER_BEAT + 1 DW. Two beats, the second carrying one DW. The minimum stimulus that catches a truncating beat count.
  • A payload of 2 × DW_PER_BEAT DW. Exact multiple — the case that passes even when everything about partial beats is wrong.
  • Sweep every payload length from 1 to 4 × DW_PER_BEAT. Verify beat count and every mask (P1–P3). This sweep is the chapter's highest-value test and it is cheap.
  • The maximum payload, 1024 DW. Verify the beat count and that the counter width holds it.
  • Backpressure on every beat, and on the last beat specifically. Verify no DW is consumed on a non-accepted beat.
  • start asserted while busy. Verify it is ignored and the in-flight payload is unaffected.

MPS segmentation

  • Each MPS encoding, decoded to bytes: 128, 256, 512, 1024, 2048, 4096. Verify the DW conversion and that no segment exceeds the limit (P6).
  • A transfer smaller than MPS. One segment, first and last simultaneous.
  • A transfer exactly equal to MPS. One segment at exactly the limit — the boundary where a strict-versus-inclusive comparison diverges.
  • A transfer of MPS + 1 DW. Two segments, the second of one DW (P7, P9).
  • A transfer of exactly N × MPS. Verify no trailing zero-length segment — the classic off-by-one.
  • Conservation across every transfer. Sum the segments and compare to the request (P8).

Negative and configuration

  • A transfer offered with mps_valid low. Verify req_ready stays low, no segment is produced, and no_limit_error sets (P10).
  • mps_bytes of zero with mps_valid high. Same — the design must not divide by it.
  • MPS changed between transfers. Verify the new limit applies from the next transfer and the previous one was unaffected.
  • A zero-length transfer request. Verify no segment is emitted.
  • Reset mid-transfer. Verify no partial segment escapes and rem_q clears.

Coverage should include: every MPS encoding; transfers below, at, and above the limit; exact multiples of the limit; payloads at every residue modulo DW_PER_BEAT; the single-beat and single-segment cases; the maximum payload; and the no-limit refusal path.

13. Debugging

The receiver reports a size violation but the header's Length looks correct

Length is a packet field and MPS is a configuration limit — a structurally valid Length can still exceed the operative permission (§3). So "the Length looks correct" is consistent with the violation, and checking the field harder will not find it.

Read the Device Control field on the transmitting Function and decode it to bytes. Compare against the actual payload. The common causes, in order of frequency: the transmitter hard-coded a limit at synthesis (§4); the transmitter used its own Capabilities value instead of the Control value it was given (§5); or the operative value changed after the transmitter latched it.

The tell that distinguishes the second cause: the packets are exactly the size the device advertises as supported, and the system configured it lower. That is not a segmentation bug — it is reading the wrong register.

A large read produces multiple completions and the requester mishandles the second one

This is §6 arriving as a bug, and it is not a payload problem at all.

The read asked for more than MPS permits in one packet — which is legal, because MRRS bounds the request and MPS bounds the answer. The Completer split the answer, correctly. The Requester's tracking retired the operation on the first Completion and freed the correlation identifier while the rest was still in flight.

The symptom is Chapter 10.4 §5's: a later Request reuses the identifier, the remaining Completions resolve against it, and data lands in the wrong consumer. The fix is in the Requester's completion tracking (Chapter 10.2 §9), not in the segmenter.

How to confirm in one observation: compare the read request's Length against the operative MPS. If the request exceeds it, multiple Completions were always going to happen, and the Requester was wrong from the first large read it ever issued.

Payload is short by a few DW on some transfers and not others

"Some and not others" is the whole diagnosis: the payload length is not a multiple of the datapath width.

A truncating beat count drops the final partial beat, so a payload of 4n DW on a 4 DW/beat datapath is perfect and a payload of 4n+1 is short by one. Sort the failing transfers by length_dw mod DW_PER_BEAT — if every failure has a non-zero residue and every success has zero, the ceiling is missing.

P1 catches it, and §12's sweep provokes it deterministically. Without the sweep, whether it appears depends entirely on whether anyone happened to test an awkward length.

Throughput is half of expected and every packet is well-formed

Nothing is wrong, structurally — which is why this one survives review.

Two candidates. The operative MPS is far below what the devices support, because one conservative Function on the path constrained it (§5) or platform firmware left a default in place. Or the segmenter under-fills: it emits segments smaller than the limit permits, so every transfer costs more headers than it needs to.

The distinguishing observation is a single number: the ratio of segment size to the operative limit. At 1.0, the segmenter is fine and the limit is the constraint — that is a configuration question. Below 1.0, P9 has been violated and the segmenter is the problem.

And note what does not distinguish them: packet validity. Both cases produce entirely legal traffic.

14. Common Misconceptions

  • "Every TLP has a payload." The header's Fmt field says whether data follows, and two of the four base encodings mean it does not (§1). A read request carries none.
  • "MPS limits how much data a read request can ask for." It does not. Read requests are bounded by Max_Read_Request_Size; MPS bounds data payloads, and a read request has none (§6).
  • "MPS is the size of the receive buffer." MPS is a transmit obligation established by configuration. How much a receiver can absorb is flow control's question, answered with credits (§3).
  • "A device transmits at its Max_Payload_Size Supported value." It transmits within its Max_Payload_Size — the Device Control field software wrote, which may be lower than the Device Capabilities field the device reports (§5).
  • "MPS can be a synthesis parameter." Software establishes it after the device is built. A hard-coded limit is a guess that is wrong on any system configured below it (§4).
  • "The Length field's maximum is the largest payload you can send." The field reaches 1024 DW. The operative MPS is commonly 128 or 256 bytes, and it is the binding constraint (§4).
  • "Segmentation means the receiver reassembles the transfer." Each segment is an independent, fully-headered TLP. Nothing in the packet marks it as part of a larger whole (§7).
  • "Segments arrive in the order they were sent." Ordering is governed by the ordering rules (Chapter 10.3), not by a segmenter's emission order (§7).
  • "A payload of N DW takes N transfers." It takes ceil(N / DW_PER_BEAT) beats on a datapath DW_PER_BEAT wide, and the last is usually partial (§8).
  • "The last beat can reuse the full valid mask." A partial last beat with a full mask sends DW the receiver will count as payload (§8).
  • "First and last beat are mutually exclusive." A payload of one beat or less is both, simultaneously — and logic that assumes otherwise produces no valid mask at all (P4).
  • "Byte enables and Length describe the same thing." Length is how many DW the packet carries. Byte enables say which bytes within the first and last DW the operation concerns (§3).

15. Understanding Check

16. What's Next

This chapter took the payload from a number in a header to bytes on a datapath: which packets carry data and which only ask for it, the negotiated limit that binds long before the length field's reach does, why that limit does not bound a read request, and the beat arithmetic whose failures are invisible to any test that uses round numbers.

It never asked where any of those packets were going.

Chapter 11.5 — Routing Information asks exactly that: why different packet families answer "where does this go" with entirely different fields, how a Switch decides which port a packet leaves by, and why an address-routed packet and an ID-routed packet cannot share one lookup.

Chapter 11.6 then covers the handling context — Traffic Class, the ordering attributes — that determines how the fabric treats a packet once it knows where it is headed, and Module 12 takes memory transactions in full, including the alignment and boundary rules §7 deliberately left out.

The idea to carry forward: the packet declares its size; configuration declares its permission; and a transmitter that reads only the packet has read only half of what it needs.