PCIe · Module 15
NAK Packets — Delivering a Recovery Request Safely
A NAK shares its format with an ACK and differs in Type and consequence. What receive conditions produce one, what its sequence field names, and why a one-cycle pulse cannot deliver it to a retry controller that takes many cycles to act.
Chapter 14.3 explained what recovery must do: retire the prefix, replay the suffix, from one event naming the last correctly received packet. It built a controller that searches for that identity one position per cycle.
Chapter 15.3 built the packet that carries the good news, and its decoded event needed only a one-entry holding register.
This chapter's event cannot use one. The retry controller is busy for multiple cycles per event, and a second event can arrive while it works.
What does a NAK DLLP carry, what receive-side condition causes the Data Link Layer to request recovery, and how does the decoded NAK reach the replay machinery without being lost?
1. The Verified Packet and Its Conditions
2. Same Format, Opposite Consequence
| Ack | Nak | |
|---|---|---|
| Format | identical 4-byte core | identical |
| Distinguished by | Type | Type |
| Sequence field names | last correctly received | last correctly received |
| Transmitter retires | the prefix through it | the prefix through it |
| Transmitter also | — | replays the suffix after it |
| Downstream cost | one frontier update | a multi-cycle search and a replay walk |
3. It Does Not Point at the Bad Packet
The name invites the wrong reading, and Chapter 14.3 §1 flagged it. Here it matters at the bit level.
The field names the last packet received correctly — not the one that failed.
receiver's good history ends at 11
packet 12 arrives with a bad LCRC
Nak carries: 11 ← the last GOOD one
NOT: 12 ← the one that failedTwo consequences for a decoder.
The value is safe to retire against. Everything at or before it was genuinely received, so the transmitter releases that prefix — from a packet whose name suggests failure.
And replay starts at the value plus one. A design that replayed from the named value would resend a packet the receiver already has — wasteful, and it also would not have retired it, so occupancy would be wrong too (Chapter 14.3 §15).
This is why §7's decoder emits the field unmodified and lets Chapter 14.3's controller apply the semantics. A decoder that "helpfully" added one would put the interpretation in two places, and the two would eventually disagree.
4. Why a Pulse Is Not Enough
The structural argument, and it is specific to what Module 14 built.
Nak decoded → retry controller
S_SEARCH: up to DEPTH cycles
S_COMMIT: 1 cycle
then a replay walk: up to DEPTH moreevent_ready is low for that whole span. Meanwhile the Link keeps delivering control packets — acknowledgements for traffic that arrived before the trouble, and possibly another recovery request.
5. A Trace
Internal teaching signals, not PCIe wire signals. DEPTH = 4.
step 1 2 3 4 5 6 7 8 9 10
dllp_valid 1 0 1 0 0 0 0 0 0 0
dllp_type NAK - ACK - - - - - - -
dllp_seq 11 - 13 - - - - - - -
q_count 0 1 1 2 2 1 1 1 0 0
evt_valid 0 1 1 1 1 1 1 1 0 0
evt_ready 0 1 0 0 0 1 0 0 0 0
evt_kind - NAK NAK NAK NAK ACK ACK ACK - -
searching 0 0 1 1 1 0 1 1 0 0
scan_q - - 0 1 2 - 0 - - -
retire_valid 0 0 0 0 0 1 0 0 1 0
arm_replay 0 0 0 0 0 1 0 0 0 0Read steps 1–2. A Nak naming 11 is decoded and queued. The controller takes it at step 2.
Read step 3 — the reason the queue exists. An Ack arrives while the search is running. evt_ready is low, so the controller cannot take it. The queue accepts it into its second entry. With a one-entry structure it would be gone.
Read steps 3–5. The search walks positions 0, 1, 2 — one per cycle (Chapter 14.3 §11).
Read step 6. The Nak commits: the prefix through 11 retires, and the replay walk is armed. The queued Ack is taken in the same cycle the controller returns to idle.
Read steps 7–9. The Ack's own search runs and commits. Two events, both honoured, neither lost — and note q_count never exceeded 2.
6. RTL — NAK DLLP Decoder
// SYNTHESIZABLE. Extract a recovery event from a received DLLP.
// The 12-bit split AckNak_Seq_Num: verified layout (section 1). The Type
// value is a PARAMETER — this chapter does not publish the encoding.
// The event interface: ILLUSTRATIVE.
//
// The sequence field is emitted UNMODIFIED. It names the last correctly
// received packet (section 3); applying the "+1 for replay start" semantics
// is Chapter 14.3's, and doing it here would put one rule in two places.
module nak_dllp_decode #(
parameter int SEQ_W = 12,
parameter logic [7:0] NAK_TYPE = 8'h10 // supplied by the integrator
) (
input logic dllp_valid,
input logic [7:0] dllp_byte0,
input logic [7:0] dllp_byte2, // [3:0] = AckNak_Seq_Num[11:8]
input logic [7:0] dllp_byte3, // [7:0] = AckNak_Seq_Num[7:0]
input logic dllp_integrity_ok,
output logic nak_hit, // this DLLP is a Nak
output logic [SEQ_W-1:0] nak_seq,
output logic nak_bad_integrity
);
generate
if (SEQ_W != 12)
$error("This decoder implements the 12-bit AckNak_Seq_Num of section 1");
endgenerate
// SPLIT FIELD reassembly, written as an explicit concatenation for the
// reason Chapter 15.3 section 3 gives: a {byte2, byte3}[11:0] slice takes
// byte 2's HIGH nibble and is wrong by construction.
assign nak_seq = {dllp_byte2[3:0], dllp_byte3[7:0]};
// INTEGRITY FIRST. A corrupted DLLP's Type and sequence field are both
// untrustworthy, and a corrupted value fed to the retry controller would
// arm a replay from an arbitrary point.
wire usable = dllp_valid && dllp_integrity_ok;
assign nak_hit = usable && (dllp_byte0 == NAK_TYPE);
assign nak_bad_integrity = dllp_valid && !dllp_integrity_ok;
endmoduleClassification: synthesizable (combinational).
Architecture. Field reassembly and a Type comparison. It holds nothing — deliberately, because §8's queue is where ownership begins, and two holding stages in series would be one more place for an event to be dropped.
Failure — three. Slicing the field contiguously (Chapter 15.3 §3). Classifying before checking integrity — a corrupted sequence value would arm a replay from an arbitrary position, which is worse than the Ack case because it also loses the suffix. And applying +1 here, which duplicates a rule Chapter 14.3 already owns.
7. RTL — Reliability Event Queue
// SYNTHESIZABLE. Hold decoded reliability events until Chapter 14.3's
// controller can take them.
// That a decoded Nak must not be lost is a consequence of section 4's
// asymmetry. The DEPTH-2 choice, the merge policy and the overflow report:
// ILLUSTRATIVE IMPLEMENTATION POLICY.
package rel_evt_pkg;
typedef enum logic {
REL_ACK = 1'b0,
REL_NAK = 1'b1
} rel_kind_e;
// NORMALIZED INTERNAL EVENT. Once decoded, kind and identity stay attached
// — nothing downstream re-reads the parser (Chapter 11.6 section 7).
typedef struct packed {
rel_kind_e kind;
logic [11:0] seq;
} rel_evt_t;
endpackageimport rel_evt_pkg::*;
module reliability_event_queue (
input logic clk,
input logic rst_n,
// ---- ACK side: COALESCED and BACKPRESSURABLE --------------------------
// Chapter 15.3's ack_frontier_coalescer sits between the ACK decoder and
// this queue. So the ACK arriving here is a cumulative frontier that the
// coalescer will KEEP ADVANCING if this queue is not ready -- which means
// refusing it costs nothing, and a handshake is safe.
input logic ack_evt_valid,
output logic ack_evt_ready,
input logic [11:0] ack_evt_seq,
// ---- NAK side: UNBACKPRESSURABLE ---------------------------------------
// A Nak is not cumulative and cannot be re-derived (section 4). Nothing
// upstream can hold it, so this queue must.
input logic nak_hit,
input logic [11:0] nak_seq,
// ---- To Chapter 14.3's retry controller ------------------------------
output logic evt_valid,
input logic evt_ready,
output rel_evt_t evt,
output logic [1:0] q_count,
output logic evt_overflow // an event could not be held
);
// DEPTH IS FIXED AT TWO (see the callout). Two entries, explicit — no
// pointer arithmetic to get wrong at a depth this small.
rel_evt_t e0_q, e1_q;
logic [1:0] cnt_q;
logic ovf_q;
assign q_count = cnt_q;
assign evt_valid = (cnt_q != 2'd0);
assign evt = e0_q; // head
assign evt_overflow = ovf_q;
wire pop = evt_valid && evt_ready;
// EFFECTIVE capacity: a slot vacated this cycle is usable this cycle.
wire can_take = (cnt_q != 2'd2) || pop;
// ADMISSION PRIORITY: NAK FIRST, ALWAYS.
//
// The two sources can now present in the same cycle -- the coalescer holds
// its frontier until taken, so it may still be offering when a Nak
// arrives. The tie-break is not arbitrary and it is not fairness:
//
// a refused NAK is LOST and unrecoverable (section 4);
// a refused ACK is HELD by the coalescer, which keeps advancing it.
//
// So the unrecoverable event wins, and the coalescable one waits at
// zero cost. Round-robin here would be strictly worse.
wire take_nak = nak_hit && can_take;
wire take_ack = ack_evt_valid && !nak_hit && can_take;
// The ACK side is refused, not dropped. Nothing is lost by refusing it.
assign ack_evt_ready = take_ack;
wire push = take_nak || take_ack;
rel_evt_t push_evt;
assign push_evt.kind = take_nak ? REL_NAK : REL_ACK;
assign push_evt.seq = take_nak ? nak_seq : ack_evt_seq;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
e0_q <= '0; e1_q <= '0; cnt_q <= 2'd0; ovf_q <= 1'b0;
end else begin
// Four explicit arms. Shifting is written out because at depth two it
// is clearer than pointers and cannot alias.
unique case ({push, pop})
2'b10: begin // push only
if (cnt_q == 2'd0) e0_q <= push_evt;
else e1_q <= push_evt;
cnt_q <= cnt_q + 2'd1;
end
2'b01: begin // pop only
e0_q <= e1_q; // shift down
cnt_q <= cnt_q - 2'd1;
end
2'b11: begin // push AND pop
// The head leaves and the new event lands behind whatever
// remains. Occupancy is unchanged.
if (cnt_q == 2'd1) e0_q <= push_evt;
else begin e0_q <= e1_q; e1_q <= push_evt; end
end
default: ; // idle
endcase
// OVERFLOW IS A NAK-ONLY CONDITION NOW. A refused ACK is not an
// overflow -- the coalescer still owns it. A refused NAK is a lost
// recovery request, which must never be silent (section 4).
if (nak_hit && !can_take) ovf_q <= 1'b1;
end
end
endmoduleClassification: synthesizable.
Architecture. A two-entry shift queue. At this depth, explicit entries beat pointers — there is no wrap, no index width, and no aliasing to audit (Chapter 14.4 §10's bug cannot occur).
Cycle behaviour.
push | pop | Result |
|---|---|---|
| 1 | 0 | occupancy +1; lands at the tail |
| 0 | 1 | head leaves; e1 shifts down |
| 1 | 1 | both apply — occupancy unchanged, new event lands behind the remainder |
| NAK, no room | — | refused and reported — evt_overflow |
| ACK, no room or a NAK present | — | refused, not reported — the coalescer still owns it |
Contract. The ACK side obeys valid/ready and holds stable while refused (A1b). The NAK side is a bare pulse and must be absorbed. Chapter 14.3's controller takes events on a handshake and may be unready for many cycles.
Failure — six. A one-entry structure loses the second event during a search (§4). Dropping a Nak silently makes it undiagnosable. Giving pop priority over push on the same cycle loses the arriving event when the queue is full and draining. Re-reading the decoder outputs at pop time re-couples the queued event to live parser inputs (Chapter 11.6 §7). Giving the ACK side priority — or round-robin — trades a free wait for a lost recovery request. And coalescing Naks the way ACKs are coalesced discards a recovery request that cannot be re-derived.
Deliberately simplified: depth fixed at two; strict arrival order among admitted events, because reordering them would change which prefix retires first.
8. Assertions
// SVA over nak_dllp_decode and reliability_event_queue, bound against
// Chapter 14.3's controller. These assert the verified field layout of
// section 1 and the LOCAL delivery contract. Nothing here asserts Nak
// generation timing (not modelled, section 1), the replay algorithm
// (Chapters 14.3-14.4), or sequence arithmetic (14.5).
// ---- ENVIRONMENT -----------------------------------------------------
// A1: one received DLLP is an Ack or a Nak, never both. (The DECODERS are
// still mutually exclusive; the QUEUE INPUTS are not, because the coalesced
// ACK may still be offering when a Nak arrives -- which is exactly what the
// admission priority exists for.)
assume property (@(posedge clk) disable iff (!rst_n)
!(ack_hit && nak_hit));
// A1b: the ACK side obeys valid/ready -- the coalescer holds its frontier
// stable while refused (Chapter 15.3 P4d).
assume property (@(posedge clk) disable iff (!rst_n)
(ack_evt_valid && !ack_evt_ready) |=> ack_evt_valid);
// A2: the DLLP core bytes are stable while dllp_valid.
assume property (@(posedge clk) disable iff (!rst_n)
dllp_valid |-> !$isunknown({dllp_byte0, dllp_byte2, dllp_byte3}));
// ---- DECODE ----------------------------------------------------------
// P1: the split field is reassembled exactly. Independent restatement, so a
// wrong slice cannot pass by agreeing with itself.
property p_field_exact;
@(posedge clk) disable iff (!rst_n)
nak_hit |-> (nak_seq == {dllp_byte2[3:0], dllp_byte3});
endproperty
a_field : assert property (p_field_exact);
// P2: a corrupted DLLP never becomes a recovery event. Worse here than for
// an Ack: a corrupted value would arm a replay from an arbitrary point.
property p_corrupt_never_naks;
@(posedge clk) disable iff (!rst_n)
(dllp_valid && !dllp_integrity_ok) |-> !nak_hit;
endproperty
a_corrupt : assert property (p_corrupt_never_naks);
// P3: only a Nak-Type DLLP decodes as a Nak.
property p_type_exact;
@(posedge clk) disable iff (!rst_n)
(dllp_valid && (dllp_byte0 != NAK_TYPE)) |-> !nak_hit;
endproperty
a_type : assert property (p_type_exact);
// P4: THE OFFSET IS APPLIED EXACTLY ONCE, and downstream. Bound across the
// boundary against Chapter 14.3's controller: the first packet the replay
// walk re-sends is the named sequence PLUS ONE (section 3). A decoder that
// "helpfully" pre-added one, or a controller that added a second one, both
// fail this -- and neither could be caught inside a single module.
property p_offset_applied_once;
@(posedge clk) disable iff (!rst_n)
dut_retry_controller.arm_replay
|-> (dut_replay_buffer.first_resend_seq
== (dut_retry_controller.event_seq + 12'd1));
endproperty
a_offset_once : assert property (p_offset_applied_once);
// ---- QUEUE -----------------------------------------------------------
// P5: NO NAK IS EVER LOST. The chapter's central property, and the one a
// one-entry structure fails. A Nak is admitted or reported -- never
// silently absent.
property p_no_nak_lost;
@(posedge clk) disable iff (!rst_n)
nak_hit |-> (take_nak || evt_overflow_next);
endproperty
a_no_loss : assert property (p_no_nak_lost);
// P5b: NAK ADMISSION PRIORITY. Whenever both sources present and there is
// room, the NAK is the one admitted -- because refusing it is unrecoverable
// and refusing the ACK is free.
property p_nak_wins;
@(posedge clk) disable iff (!rst_n)
(nak_hit && ack_evt_valid && can_take) |-> (take_nak && !ack_evt_ready);
endproperty
a_nak_priority : assert property (p_nak_wins);
// P5c: NO ACK IS LOST EITHER -- it is REFUSED, not dropped. Bound against
// Chapter 15.3's coalescer: a refused frontier is still owned upstream.
property p_ack_refused_not_dropped;
@(posedge clk) disable iff (!rst_n)
(ack_evt_valid && !ack_evt_ready)
|=> (dut_ack_coalescer.out_valid);
endproperty
a_ack_held : assert property (p_ack_refused_not_dropped);
// P6: a NAK that could not be held is REPORTED, never silent. A refused
// ACK is NOT an overflow -- asserting otherwise would train an integrator
// to ignore a flag that does mean something.
property p_overflow_reported;
@(posedge clk) disable iff (!rst_n)
(nak_hit && !can_take) |=> evt_overflow;
endproperty
a_overflow : assert property (p_overflow_reported);
// P6b: an ACK refused for room, or deferred behind a NAK, NEVER sets it.
property p_ack_refusal_is_not_overflow;
@(posedge clk) disable iff (!rst_n)
(ack_evt_valid && !ack_evt_ready && !nak_hit && !$past(nak_hit))
|=> $stable(evt_overflow);
endproperty
a_ack_not_overflow : assert property (p_ack_refusal_is_not_overflow);
// P6c: NAKS ARE NEVER COALESCED. Two Naks are two events. Only the ACK
// side is cumulative (Chapter 15.3 section 4); applying that optimisation
// to recovery requests would discard one.
property p_naks_not_coalesced;
@(posedge clk) disable iff (!rst_n)
(take_nak && $past(take_nak)) |-> (nak_admit_count == $past(nak_admit_count) + 1);
endproperty
a_no_nak_coalesce : assert property (p_naks_not_coalesced);
// P7: IMMUTABILITY. A queued event's kind and identity do not change while
// it waits. Catches a design re-reading the parser at pop time.
property p_queued_event_immutable;
@(posedge clk) disable iff (!rst_n)
(evt_valid && !evt_ready) |=> (evt_valid && $stable(evt));
endproperty
a_immutable : assert property (p_queued_event_immutable);
// P8: SAME-CYCLE push and pop both apply — occupancy unchanged, and the
// arriving event is retained.
property p_simul_push_pop;
@(posedge clk) disable iff (!rst_n)
(push && pop) |=> (q_count == $past(q_count));
endproperty
a_simul : assert property (p_simul_push_pop);
// P9: STRICT ARRIVAL ORDER. Events reach the controller in the order they
// were decoded — reordering would change which prefix retires first.
// (exp_order is a testbench queue.)
property p_order_preserved;
@(posedge clk) disable iff (!rst_n)
(evt_valid && evt_ready) |-> (evt == exp_order[0]);
endproperty
a_order : assert property (p_order_preserved);
// P10: occupancy is bounded and never underflows.
property p_count_sane;
@(posedge clk) disable iff (!rst_n)
(q_count <= 2'd2) && !(pop && (q_count == 2'd0));
endproperty
a_count : assert property (p_count_sane);
// ---- INTEGRATION with Chapter 14.3 -----------------------------------
// P11: a decoded Nak reaching the controller arms a replay OR is reported
// unknown — it is never silently absorbed.
property p_nak_reaches_a_decision;
@(posedge clk) disable iff (!rst_n)
(evt_valid && evt_ready && (evt.kind == REL_NAK))
|-> ##[1:(DEPTH+2)] (arm_replay || event_unknown);
endproperty
a_nak_decided : assert property (p_nak_reaches_a_decision);
// P12: THE IDENTITY PROPERTY. A decoded Nak never creates a Transaction
// Layer acceptance — the replay it triggers resends existing packets
// (Chapter 14.3 section 6).
property p_nak_creates_no_tl_packet;
@(posedge clk) disable iff (!rst_n)
(evt_valid && evt_ready && (evt.kind == REL_NAK)) |-> !tl_accept;
endproperty
a_no_tl : assert property (p_nak_creates_no_tl_packet);
// P13: a decoded Nak is never a Completion-status event (Chapter 13.2).
property p_nak_not_completion_status;
@(posedge clk) disable iff (!rst_n)
nak_hit |-> !(tl_cpl_valid);
endproperty
a_not_cpl : assert property (p_nak_not_completion_status);
// P14: reset clears queue ownership.
property p_reset_clears;
@(posedge clk)
!rst_n |=> ((q_count == 2'd0) && !evt_valid);
endproperty
a_reset : assert property (p_reset_clears);P5 and P6 are a pair covering the two possible NAK outcomes, and both are needed: P5 says a holdable Nak is held, P6 says an unholdable one is reported. A design with only P5 can drop silently at full.
P5b, P5c and P6b are the integration properties, and they exist because the two sources are no longer symmetric. P5b fixes the tie-break — the unrecoverable event wins. P5c is bound against Chapter 15.3's coalescer and is what makes "refused, not dropped" a checkable claim rather than a hope: the refused frontier must still be owned upstream on the next cycle. P6b keeps evt_overflow meaningful by forbidding it on the benign path.
P6c is the one that catches the wrong generalisation. Coalescing is correct for ACKs and catastrophic for Naks, and a single property states the boundary.
P11 is bounded, not s_eventually — reusing Chapter 14.3 §13's bound, because the controller's search is a deterministic finite walk. A fairness assumption would be both weaker and unjustifiable.
P4 is deliberately a cross-boundary property rather than a local one. A property saying only "the decoder does not add one" would be redundant — P1 already pins nak_seq to the exact reassembly, so a decoder that added one fails P1 first. The useful claim is that the offset is applied exactly once, somewhere, and that can only be checked where the replay actually starts. It fails both ways: a decoder that pre-adds one makes the walk start two past the boundary, and a controller that adds a second makes it start one too late. Two copies of one rule is Chapter 11.7 §8's duplicated-decode problem, and P4 is where it surfaces.
P7 connects to Chapter 11.6 §7. A queued event is owned metadata; re-deriving it from live parser outputs at pop time would attach whatever the parser now holds to an event decoded cycles earlier.
9. Verification and Fault Injection
The scoreboard tracks four distinct quantities, and conflating any two is a real DV error:
received control packets ← what the wire carried
decoded events ← what the parser produced
controller decisions ← retire counts and arm pulses
Link send attempts ← how many times each packet was transmitted
Transaction Layer packets ← how many operations exist (Chapter 14.3 §6)The last two are the ones that must not be merged. A replay increases send attempts and not Transaction Layer packets.
Decode
- A Nak at sequence 0, 255, 256, 4095. The 255/256 pair catches an 8-bit truncation.
- Byte 2's upper nibble non-zero. Verify it is ignored (P1).
dllp_integrity_oklow. Verify no Nak (P2).- An Ack-Type DLLP. Verify
nak_hitlow (P3).
Queue and integration
- A Nak while the controller is idle. Straight through.
- A Nak, then an Ack during the search. Verify both are honoured (§5's trace) — the required test.
- An Ack offered and a Nak arriving in the same cycle, with one slot free. Verify the Nak is admitted,
ack_evt_readyis low, and the Ack is still offered next cycle (P5b, P5c). - The queue full while an Ack is offered. Verify no
evt_overflow(P6b) and that the coalescer still holds it. - The queue full when a Nak arrives. Verify
evt_overflowis set (P6). - Two Naks admitted back to back. Verify two events, never merged (P6c).
- Two events while the controller searches, then a third. Verify the third is refused and reported (P6), not dropped silently.
- Push and pop in the same cycle at occupancy 1 and 2 (P8).
- An Ack queued behind a Nak, and a Nak queued behind an Ack. Verify strict arrival order (P9).
- A Nak with an identity not retained. Verify
event_unknownand no replay (P11). - A Nak near the wrap. Verify the search and the replay start are correct (Chapter 14.5).
- Reset with events queued (P14).
Mutations
| # | Mutation | Caught by |
|---|---|---|
| 1 | field sliced contiguously | P1 |
| 2 | Nak decoded as Ack | P3, and the controller retires without arming |
| 3 | decoder adds one to the sequence field | P1 locally, P4 at the replay start |
| 4 | one-entry holding register instead of a queue | P5, with the Nak-then-Ack test |
| 5 | overflow dropped silently | P6 |
| 5a | ACK given admission priority over NAK | P5b, and a Nak is lost whenever both present |
| 5b | round-robin arbitration between the sources | P5b — a free wait traded for an unrecoverable loss |
| 5c | refused ACK also sets evt_overflow | P6b — the flag stops meaning anything |
| 5d | NAKs coalesced the way ACKs are | P6c — the second recovery request vanishes |
| 6 | queued sequence re-read from the parser at pop | P7 |
| 7 | pop given priority over push when full | P5, at occupancy 2 with a drain |
| 8 | events reordered — Naks prioritised | P9, and the wrong prefix retires first |
| 9 | replay begins at the named entry | P4, and the scoreboard: a packet the receiver already has is resent |
| 10 | replay creates a Transaction Layer acceptance | P12 |
| 11 | integrity checked after Type | P2, with a corrupted Type reading as Nak |
| 12 | Nak treated as a Completion error | P13 |
10. Debugging
A NAK is visible on the analyzer but no replay occurs
Walk the chain; each step eliminates the ones before.
- Did the decoder produce
nak_hit? If not, checknak_bad_integrity— a damaged DLLP is not a Nak — then the Type parameter. - Did the queue accept it? Check
evt_overflow. If it is set, the event was refused — and the fix is depth or controller latency, not the decoder. - Did the controller take it?
evt_valid && evt_ready. - Did the search find the identity?
event_unknownset means it did not — an identity-mapping problem (Chapter 14.3 §15). - Did
arm_replaypulse? If the search matched and this did not, the survivor count was zero — legitimate if the Nak named the newest retained packet. - Did the scheduler walk? Chapter 14.4 §7.
Step 2 is the one this chapter added, and it is the step a design without a queue cannot even report.
The replay starts at the wrong packet
Three candidates, and the sign of the error names one.
One too early — replay resends a packet the receiver already has: the decoder or the controller applied the named value directly instead of the next one (§3).
One too late — a packet is never resent: something added one twice, which is what P4 exists to catch.
Arbitrarily wrong — the field extraction (P1), or a modular comparison error (Chapter 14.5 §13) if it only happens near a wrap.
The Link recovers correctly at low load and stalls under load
The signature of §9's counterexample.
Under load, control packets arrive while the controller is busy — which at low load almost never happens. Check evt_overflow first: if it is set, events are being refused, and a dropped Nak is a permanent stall.
If it is clear, the events are being delivered and the fault is downstream — the search, the arm, or the walk.
11. Common Misconceptions
- "A NAK means a transaction failed." It means a Link-local delivery needs recovery. Transaction outcome is Completion status (Chapter 13.2).
- "A NAK carries a Completion Status." It carries
AckNak_Seq_Num. Different layer, different field (§1). - "A NAK tells software to retry." Software is never told. Hardware resends from storage it already holds (Chapter 14.3 §2).
- "A NAK creates a new Request." It causes existing retained packets to be transmitted again (P12).
- "The NAK sequence number is a Requester Tag." It is a Link-local sequence identity (Chapter 14.5 §2).
- "A NAK identifies the corrupt packet." It names the last correctly received one. Replay starts at that value plus one (§3).
- "A NAK is forwarded through a Switch." It terminates at the port that receives it (Chapter 15.1 §3).
- "A replayed packet gets a fresh Sequence Number." It keeps the one it was assigned (Chapter 14.5 §3).
- "A replayed packet can be regenerated from live Transaction Layer inputs." It is owned immutable storage (Chapter 14.4 §14).
- "A NAK parser may discard the event if the retry engine is busy." A lost Nak is unrecoverable — no later packet re-states it (§4).
- "ACK and NAK events can share one unbuffered pulse." They need ownership: a queue, because the controller is busy for multiple cycles per event (§4, §7).
- "A DLLP that fails its CRC gets NAK'd." DLLPs are discarded and reported; the Nak mechanism retransmits TLPs (§1).
12. Understanding Check
13. What's Next
This chapter delivered the recovery request safely. A Nak shares the Ack's format and differs in Type and consequence; its sequence field names the last correctly received packet, so one value drives both retirement and replay; and because Chapter 14.3's controller is busy for many cycles per event, the delivery path needs a queue, a handshake and a report — not a pulse.
Chapter 15.5 — Power Management DLLPs closes Module 15 with the last category: Link-local power-management control, and why a request/acknowledge exchange at this layer must never be satisfied by the reliability acknowledgement this chapter and 15.3 have been about.
Module 16 then takes the credit system.
The idea to carry forward: an event that cannot be re-sent must not be delivered by a pulse — and the structure that holds it must report what it could not hold.