Skip to content

PCIe · Module 3

Layer Responsibilities — Proving Which Layer Owns a Bug

How to determine which PCIe layer actually owns a behaviour: rigorous ownership boundaries, the ambiguous cases that mislead engineers, why protocol layers are not RTL modules, and a boundary-first method for localising failures.

Chapters 3.1, 3.2, and 3.3 defined each layer. Knowing the definitions is necessary and, in front of a real failure, insufficient — because a failure does not announce which layer produced it. It announces a symptom, usually at a place far from its cause.

So this chapter asks the question that actually arises in work:

When a PCIe function misbehaves, how do we determine which layer owns the behaviour?

The answer is not memorisation. It is a method, plus a clear enough sense of ownership that the method has something to bite on.

1. Ownership, Stated Rigorously

Definitions from 3.1–3.3, restated as owns / does not own — because the negative half is what makes the boundary usable.

PCIe layer ownership: the Transaction Layer owns operation semantics and destination classification, the Data Link Layer owns hop-local delivery and retry bookkeeping, and the Physical Layer owns transport and connection availability.Transaction LayerOperation meaning · destination · response association · transaction legalityOperation meaning · destination · response association · transaction legalityData Link LayerOne-hop delivery · retry bookkeeping · packet integrity at Link scopeOne-hop delivery · retry bookkeeping · packet integrity at Link scopePhysical LayerTransport across the connection · connection availabilityTransport across the connection · connection availability
Figure 1 — ownership by layer. Each band lists what that layer decides. A behaviour belongs to a layer only if answering the question requires information that layer has. The Transaction Layer knows what an operation means and nothing about wires; the Physical Layer knows how information crosses a connection and nothing about what it means.

Transaction Layer owns: what operation this is; where it is directed; how much data is involved; whether a response is expected; which local consumer an arriving transaction belongs to; whether an arriving transaction is legal for this component at all.

It does not own: whether a packet survived a hop; what to do when it did not; how information crosses a connection; whether the connection is usable.

Data Link Layer owns: whether a packet crossed one adjacent Link intact; retaining packets that may need resending; the bookkeeping that tracks which are unresolved; the local control information exchanged with its neighbour.

It does not own: what the packet means; whether the operation completed anywhere; whether the far end of a path received anything; how signalling works.

Physical Layer owns: turning logical information into signalling and back; whether a connection is established and usable.

It does not own: operation meaning; retry policy; anything about a transaction's destination.

The test that makes this operational:

A layer owns a question only if answering it requires information that layer has.

"Was this write directed at the right address?" needs the operation's meaning — Transaction Layer. "Did the packet arrive intact on this hop?" needs neither the meaning nor the signalling detail — Data Link Layer. "Is the connection usable?" needs neither of the above — Physical Layer.

2. Protocol Layers Are Not RTL Modules

Before any debugging method, one correction — because getting this wrong invalidates the whole approach.

The specification defines responsibilities, not module hierarchy. A real implementation may merge conceptual layers into one block, pipeline a single layer across several, place shared buffering between them, or distribute a responsibility across units that do not map cleanly onto the three-layer picture at all.

3. The Boundary-First Method

Here is the method the rest of the chapter applies. It works because a transaction crosses a small number of observable boundaries, and a bug manifests as a divergence between what should be at a boundary and what is.

Step 0 — state the expectation precisely. What operation, directed where, carrying what, expected to produce what effect. Vague expectations produce vague conclusions.

Then walk the boundaries, asking at each: is it correct here?

  1. Local source → Transaction Layer — was the request accepted?
  2. Transaction Layer → Data Link Layer — was a transaction emitted, and is it the right one?
  3. Data Link Layer → Physical Layer — was the packet handed down?
  4. Physical Layer → connection — was it transmitted?
  5. Connection → far-side Physical Layer — was it received and reconstructed?
  6. Far-side Physical → Data Link — was it accepted upward?
  7. Far-side Data Link → Transaction Layer — was it passed up?
  8. Far-side Transaction Layer → local consumer — was it delivered, and to the right one?
The observation chain across eight boundaries: local source to transmit Transaction Layer, to transmit Data Link Layer, to transmit Physical Layer, across the connection, to receive Physical Layer, to receive Data Link Layer, to receive Transaction Layer, to the local consumer.Local source1 · request accepted?TX TransactionLayer2 · right transactionout?TX Data LinkLayer3 · packet handeddown?TX Physical Layer4 · transmitted?Connection5 · received?RX Physical Layer6 · accepted upward?RX Data LinkLayer7 · passed up?RX TransactionLayer8 · right consumer?Local consumereffect observed?12
Figure 2 — the observation chain. Eight boundaries a transaction crosses between a local source and a remote consumer. Debugging means finding the last boundary where the transaction was correct and the first where it was not; the fault lies between them. Boundaries that are unobservable in a given implementation are bisected around rather than skipped.

Two refinements that make the method robust:

Bisect rather than walk linearly. Eight observations is a lot. Check the midpoint first — did the far-side Physical Layer receive it? — and halve the search space.

"Correct" means more than "present." A transaction can be present at every boundary and still be wrong at the first one, if what was emitted did not match the intent. Step 2 is where semantic corruption is caught, and skipping it because "the packet is clearly moving" is the most common way this method is misapplied.

4. The Ambiguous Cases

Clean cases need no method. These are the ones where intuition sends engineers to the wrong layer — and for each, what matters is the observation that proves ownership, not the answer.

Case 1 — the packet arrives intact but targets the wrong thing

Symptom: the operation completes, but the wrong address was written, or the wrong local consumer acted on it.

Ownership: Transaction Layer.

Proof: the packet arrived intact, so every layer below performed correctly — they moved what they were given, faithfully. The question is whether what they were given was right. Observe at boundary 2: if the emitted transaction already carried the wrong destination, it is a transmit-side classification or assembly fault. If boundary 2 was correct and boundary 8 delivered to the wrong consumer, it is receive-side classification.

What rules out the lower layers: they do not evaluate meaning. A well-formed packet expressing a wrong operation passes every check they perform, because there is nothing in it for them to object to.

Symptom: persistent retry activity, confined to one connection.

Ownership: Data Link or Physical, and the distinction is determinable.

Proof: ask whether packets are arriving corrupted or arriving fine while the accounting mishandles them. Integrity failures at boundary 6 point to transport — the information is not surviving the connection. Correct arrivals with entries that never resolve point to Data Link bookkeeping — resolution indications lost, ignored, or referencing the wrong identity.

What rules out the Transaction Layer: semantics are a property of the packet, not the path. A malformed operation would be malformed on every Link. Confinement to one connection implicates a Link-local mechanism.

Case 3 — correct at the transmit boundary, absent at the far side

Symptom: the packet was handed to the Physical Layer (boundary 3–4) and never appeared at the far-side Physical Layer (boundary 5).

Ownership: Physical Layer — transport or connection availability.

Proof: check availability first. If the connection was unavailable, the boundary should have refused the packet rather than accepting it, and the question becomes why availability was lost — an establishment matter. If it was available and the packet was accepted and never emerged, transport lost it.

What rules out the layers above: they had already done their work correctly, demonstrated by the packet being correct at boundary 3.

Case 4 — the packet arrives correctly and is retransmitted anyway

Symptom: the far side received a good packet, and the transmitter sent it again.

Ownership: Data Link bookkeeping, almost certainly on the transmitting side.

Proof: the receiver accepted the packet, so transport worked. The transmitter re-sent it, so it believed the packet unresolved. Either the resolution indication never arrived, or it arrived and failed to clear the entry — a wrong-identity association, or a guard condition rejecting a legitimate indication.

Why this matters: the far end now has the transaction twice. For a write, that may corrupt data — and the duplicate surfaces at a destination potentially several hops from the bookkeeping fault that caused it. This is the strongest argument for asserting bookkeeping invariants locally.

Symptom: traffic stops. No packets are being handed down.

Ownership: ambiguous by symptom, and resolvable by one observation.

Proof: look at the handshake at boundary 2. If the Transaction Layer is asserting valid and the Data Link Layer is not asserting ready, the Transaction Layer is fine and the stall is below — most likely retry storage full, which itself means resolution is not happening. If the Transaction Layer is not asserting valid at all, the stall is above — an internal resource exhausted, most commonly outstanding-transaction tracking.

The general shape: a stall is reported by whoever is waiting, which is never the layer causing it. Follow ready downward until you find the first stage that is not asserting it; that stage owns the problem.

Case 6 — the transaction appears at the destination twice

Symptom: one intended operation, two effects.

Ownership: Transaction Layer duplication or Data Link replay — genuinely ambiguous until observed.

Proof: count at boundary 2. If two transactions were emitted, the Transaction Layer duplicated it — typically a stall-handling bug that re-offered something already accepted. If one was emitted and two arrived, the Data Link retry path re-sent a packet that had already been delivered (Case 4's mechanism).

Why this pair is worth internalising: the symptom is identical, the causes are in different layers, and one observation at one boundary separates them completely. That is the method working.

5. A Layer Boundary in RTL

The boundaries in Figure 2 are where bugs are found, so it is worth having concrete hardware for one — particularly because packet boundaries introduce a failure mode the earlier chapters' interfaces did not have.

A layer hands down items that may be multi-beat: a packet larger than the datapath arrives as a sequence with a start and an end. That creates a new invariant — the beats of one packet must not interleave with another's, and metadata must stay associated with the right packet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative internal representation — NOT a normative PCIe packet format.
// One beat of a multi-beat item crossing a layer boundary.
typedef struct packed {
  logic [127:0] data;   // payload beat
  logic         sop;    // start of packet
  logic         eop;    // end of packet
  logic [3:0]   meta;   // per-packet metadata, valid on the sop beat
} boundary_beat_t;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative synthesizable layer-boundary pipeline — NOT PCIe interface RTL.
// One registered stage: accepts beats, holds them stable under backpressure,
// preserves packet framing, and tracks in-packet state so a partially
// transferred packet is visible to assertions and debug.
module layer_boundary #(
  parameter int unsigned DEPTH = 4,
  localparam int unsigned CNT_W = $clog2(DEPTH + 1)
) (
  input  logic           clk,
  input  logic           rst_n,
 
  // From the layer above
  input  logic           in_valid,
  output logic           in_ready,
  input  boundary_beat_t in_beat,
 
  // To the layer below
  output logic           out_valid,
  input  logic           out_ready,
  output boundary_beat_t out_beat,
 
  // Observability for debug and assertions
  output logic [CNT_W-1:0] occupancy,
  output logic             in_packet      // a packet is partially transferred
);
 
  boundary_beat_t          mem [DEPTH];
  logic [$clog2(DEPTH)-1:0] wr_ptr, rd_ptr;
  logic [CNT_W-1:0]         count;
  logic                     in_packet_q;
 
  wire accept = in_valid  && in_ready;
  wire emit   = out_valid && out_ready;
 
  assign in_ready  = (count != DEPTH[CNT_W-1:0]);
  assign out_valid = (count != '0);
  assign out_beat  = mem[rd_ptr];
  assign occupancy = count;
  assign in_packet = in_packet_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wr_ptr      <= '0;
      rd_ptr      <= '0;
      count       <= '0;
      in_packet_q <= 1'b0;
    end else begin
      if (accept) begin
        mem[wr_ptr] <= in_beat;
        wr_ptr      <= wr_ptr + 1'b1;
      end
      if (emit) begin
        rd_ptr <= rd_ptr + 1'b1;
      end
 
      // Occupancy: simultaneous accept and emit cancel out.
      case ({accept, emit})
        2'b10:   count <= count + 1'b1;
        2'b01:   count <= count - 1'b1;
        default: count <= count;
      endcase
 
      // Track framing on the OUTPUT side: a packet is in progress from the
      // beat where sop leaves until the beat where eop leaves.
      if (emit) begin
        if (out_beat.sop && !out_beat.eop)      in_packet_q <= 1'b1;
        else if (out_beat.eop)                  in_packet_q <= 1'b0;
      end
    end
  end
endmodule

What this models: a registered layer boundary carrying multi-beat items with framing, plus the observability (occupancy, in_packet) that makes the boundary debuggable.

What is deliberately simplified: no separate metadata channel; no error/status path; a single flat buffer rather than the per-class storage a real design may need; and no handling of ordering constraints between different kinds of transaction, which the layer above may impose.

What to notice:

  • in_packet is derived from the output side, so it answers "is a packet partially delivered downstream" — the state that matters when a stall occurs mid-packet.
  • occupancy is tracked explicitly rather than inferred from pointers, so full and empty are distinguishable and the conservation assertion in §6 has something to check.
  • A single-beat packet (sop && eop together) is handled by the else if (out_beat.eop) ordering: it never sets in_packet_q.

What production RTL would additionally require: error propagation, per-class buffering where ordering rules demand it, and — if the layers sit in different clock domains — proper CDC, which this module does not do.

6. Assertions That Localise Bugs

These properties distinguish a boundary bug from a layer-internal bug, which is exactly the discrimination this chapter is about.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the illustrative boundary module. These check this interface
// contract — they are not PCIe protocol requirements.
 
// P1 — CONSERVATION. Occupancy equals beats in minus beats out. If this
// fails, the boundary itself is losing or inventing beats; no upstream or
// downstream layer can be blamed.
logic [31:0] in_cnt, out_cnt;
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin in_cnt <= '0; out_cnt <= '0; end
  else begin
    if (in_valid && in_ready)   in_cnt  <= in_cnt  + 1;
    if (out_valid && out_ready) out_cnt <= out_cnt + 1;
  end
end
property p_conservation;
  @(posedge clk) disable iff (!rst_n)
  occupancy == (in_cnt - out_cnt);
endproperty
a_conservation : assert property (p_conservation);
 
// P2 — STABILITY. A beat offered downstream must not change while it waits.
// Catches the corruption that produces well-formed packets with mixed content.
property p_out_stable;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_ready) |=> $stable(out_beat);
endproperty
a_out_stable : assert property (p_out_stable);
 
// P3 — NO PHANTOM OUTPUT. An empty stage never claims to have a beat.
property p_no_phantom;
  @(posedge clk) disable iff (!rst_n)
  (occupancy == '0) |-> !out_valid;
endproperty
a_no_phantom : assert property (p_no_phantom);
 
// P4 — FRAMING INTEGRITY. A new packet cannot start while one is in
// progress. Catches interleaving, where two packets' beats become mixed.
property p_no_interleave;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && out_ready && out_beat.sop) |-> !in_packet;
endproperty
a_no_interleave : assert property (p_no_interleave);
 
// P5 — RESET OWNERSHIP. Reset leaves nothing claimed.
property p_reset_clears;
  @(posedge clk)
  !rst_n |=> (occupancy == '0 && !in_packet && !out_valid);
endproperty
a_reset_clears : assert property (p_reset_clears);

Each catches a bug that simulation alone can easily miss, and the reason is worth stating.

P1 (conservation) catches silent loss. Without it, a beat dropped at this boundary manifests as a transaction that never arrives — a symptom observed at boundary 8, seven boundaries away from its cause. A scoreboard would eventually catch the missing transaction; the assertion catches it here, which is the difference between a five-minute fix and a day of bisection.

P2 (stability) catches the corruption class 3.1 described: a well-formed packet carrying mixed content. Simulation will not flag it, because nothing downstream can tell the packet is wrong — every integrity mechanism computed after the corruption agrees with the corrupted data.

P4 (framing) catches interleaving, which is subtle because both packets may be individually well-formed and only their combination is wrong. A scoreboard comparing per-packet content may miss it entirely if it reassembles by arrival order.

P5 (reset) catches state surviving reset, which produces failures at the start of the next session that look like initialisation problems and are not.

Note what these properties do not claim. None encodes a PCIe requirement. They check the internal contract of one interface — and that limitation is the point: a boundary that satisfies them is eliminated as a suspect, which is what makes the method converge.

7. Layer-Isolated Verification

If layers own distinct responsibilities, they can be verified against distinct references — which is far more tractable than one monolithic end-to-end check.

Transaction Layer. Score-board intent versus emitted transaction. Build a model that, given an accepted local request, predicts the transaction that should be emitted — operation type, destination, size, response expectation — and compare. This catches semantic corruption, which no lower-layer check can see.

Data Link Layer. Score-board packets accepted for delivery versus packets successfully delivered to the neighbour, allowing for retries. This requires visibility on both sides of one Link and is the check that catches duplication: if the neighbour accepted a packet twice, the retry path re-sent something already delivered.

Physical Layer. Check data and control preservation across the digital boundary against an abstract model of the hardened portion. This scope proves your logic behaves given well-behaved inputs, and cannot prove anything about the electrical domain.

Then add the technique that ties them together:

Corner cases worth generating specifically: sustained backpressure at each boundary independently (most stability bugs need a hold); packets at datapath-width boundaries, including single-beat and maximum-length; back-to-back packets with no idle beats, which is where framing bugs appear; reset asserted mid-packet; and resource exhaustion at each stage, confirming backpressure rather than overwrite.

Coverage worth defining: occupancy extremes at every boundary including full; simultaneous accept-and-emit; single-beat packets; reset during in-packet state. Random stimulus spends very little time at extremes, and extremes are where these bugs live.

8. Failure Signatures and What They Rule Out

A reference table for the common signatures. The value is in the rule-out column — what evidence eliminates a layer from suspicion.

Semantic corruption — the operation arrives and does the wrong thing. Rules out: Data Link and Physical, because the packet arrived intact, which is the entirety of their responsibility. Investigate: boundary 2 first — was the emitted transaction already wrong?

Duplication — one intent, two effects. Rules out: nothing yet. Investigate: count at boundary 2. Two emitted means Transaction Layer; one emitted, two arrived means Data Link retry.

Loss — the transaction never arrives. Rules out: nothing yet. Investigate: bisect at boundary 5. Present there but absent at 8 means the receive stack; absent at 5 means transmit side or transport.

Persistent retry — sustained retry on one Link. Rules out: Transaction Layer, since semantics are path-independent. Investigate: integrity failures at boundary 6 mean transport; correct arrivals with unresolved entries mean Data Link bookkeeping.

Connection unavailable — nothing crosses at all. Rules out: Transaction and Data Link, which cannot cause unavailability. Investigate: establishment and maintenance. Determine whether it ever became available or was lost after working.

Backpressure deadlock — everything stops, nothing recovers. Rules out: nothing yet. Investigate: follow ready downward from the stalled source to the first stage not asserting it. That stage owns it. Chapter 3.5 develops the chain properly.

Boundary mismatch — beats lost, duplicated, or interleaved at one interface. Rules out: both adjacent layers' internal logic, if conservation fails at the boundary itself. Investigate: the boundary — P1 and P4 are exactly this check.

Receive misclassification — arrives correctly, delivered to the wrong consumer. Rules out: everything on the transmit side and in transit, provided boundary 2 was verified correct. Investigate: receive-side classification.

9. Common Misconceptions

10. Understanding Check

11. Summary

A layer owns a behaviour only if answering the question requires information that layer has. That single test resolves most ownership disputes: meaning belongs to the Transaction Layer, one-hop delivery to the Data Link Layer, transport and availability to the Physical Layer.

Debugging follows from ownership. Find the last boundary where the transaction was correct and the first where it was wrong; the fault is between them. Bisect rather than walking, and remember that "correct" means matching intent, not merely being present — semantic corruption passes every check below the layer that created it.

The ambiguous cases resolve to specific observations. Duplication is separated by counting at the Transaction Layer's output. Persistent retry is separated by asking whether packets arrive corrupted or arrive fine with mishandled accounting. A stall is located by following ready downward to the first stage not asserting it.

Two structural cautions. Protocol layers are not RTL modules — the specification defines responsibility, not hierarchy, so observation points may not exist where you expect. And a layer reports what it observes, not what it caused — which is why the method looks for divergence between boundaries rather than trusting where the symptom surfaced.

Hold the model: identify the last known-good boundary and the first known-bad one; the bug lives between them.

12. What Comes Next

This chapter treated the layers as separable, which is what makes ownership decidable. Chapter 3.5 — Layer Interactions treats them as composed: one transaction traced down a stack, across a Link, and up the far side, including a hop through a Switch where hop-local delivery state terminates and a new relationship begins.

It also develops the mechanism this chapter kept referring to — how a condition at the bottom of one stack becomes a stall at the top, and how to tell temporary backpressure from a genuine deadlock.

Revisit Transaction Layer, Data Link Layer, or Physical Layer for the individual responsibilities this chapter arbitrates between. Browse the full path on the PCIe tutorials index.