Skip to content

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

AckNak
Formatidentical 4-byte coreidentical
Distinguished byTypeType
Sequence field nameslast correctly receivedlast correctly received
Transmitter retiresthe prefix through itthe prefix through it
Transmitter alsoreplays the suffix after it
Downstream costone frontier updatea 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 failed

Two 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Nak decoded          →  retry controller
                          S_SEARCH: up to DEPTH cycles
                          S_COMMIT: 1 cycle
                        then a replay walk: up to DEPTH more

event_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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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    0

Read 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
 
endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
 
endmodule

Classification: 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.

pushpopResult
10occupancy +1; lands at the tail
01head leaves; e1 shifts down
11both apply — occupancy unchanged, new event lands behind the remainder
NAK, no roomrefused and reportedevt_overflow
ACK, no room or a NAK presentrefused, 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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_ok low. Verify no Nak (P2).
  • An Ack-Type DLLP. Verify nak_hit low (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_ready is 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_overflow is 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_unknown and 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

#MutationCaught by
1field sliced contiguouslyP1
2Nak decoded as AckP3, and the controller retires without arming
3decoder adds one to the sequence fieldP1 locally, P4 at the replay start
4one-entry holding register instead of a queueP5, with the Nak-then-Ack test
5overflow dropped silentlyP6
5aACK given admission priority over NAKP5b, and a Nak is lost whenever both present
5bround-robin arbitration between the sourcesP5b — a free wait traded for an unrecoverable loss
5crefused ACK also sets evt_overflowP6b — the flag stops meaning anything
5dNAKs coalesced the way ACKs areP6c — the second recovery request vanishes
6queued sequence re-read from the parser at popP7
7pop given priority over push when fullP5, at occupancy 2 with a drain
8events reordered — Naks prioritisedP9, and the wrong prefix retires first
9replay begins at the named entryP4, and the scoreboard: a packet the receiver already has is resent
10replay creates a Transaction Layer acceptanceP12
11integrity checked after TypeP2, with a corrupted Type reading as Nak
12Nak treated as a Completion errorP13

10. Debugging

A NAK is visible on the analyzer but no replay occurs

Walk the chain; each step eliminates the ones before.

  1. Did the decoder produce nak_hit? If not, check nak_bad_integrity — a damaged DLLP is not a Nak — then the Type parameter.
  2. 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.
  3. Did the controller take it? evt_valid && evt_ready.
  4. Did the search find the identity? event_unknown set means it did not — an identity-mapping problem (Chapter 14.3 §15).
  5. Did arm_replay pulse? If the search matched and this did not, the survivor count was zero — legitimate if the Nak named the newest retained packet.
  6. 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 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.