PCIe · Module 15
Power Management DLLPs — A Request That Can Be Refused
The only DLLP category that is a negotiation. A request repeated until answered, an acceptance that is not an ACK, a refusal that is not a DLLP at all — and an entry condition that cannot be met until the replay buffer is empty.
Every DLLP so far has been a report. An Ack reports progress (Chapter 15.3). A Nak reports a problem (Chapter 15.4). A flow-control update reports a counter (Chapter 15.2). None of them can be refused, and none of them expects an answer.
Power-management DLLPs are different in kind.
They form a Link-local control negotiation between two ports: one asks to bring the Link to a lower-power state, the other answers, and the answer may be no. Some PM DLLPs carry the request; others carry the acknowledgement that resolves it.
What are these packets, what makes a request different from a report, and what must a transmitter finish before it is even allowed to ask?
1. The Verified Packets
2. Report Versus Request
Module 15's four categories, compared by what the sender expects back.
| Category | Sender expects | May be refused? | Repeated? | Receiver's duty |
|---|---|---|---|---|
| Ack (15.3) | nothing | no | no — newer overwrites | advance a frontier |
| Nak (15.4) | nothing | no | no — must not be lost | retire and replay |
| FC update (15.2) | nothing | no | periodically, as a fresh total | update a baseline |
| PM request | an answer | yes | continuously, until answered | decide, and reply |
| PM acknowledgement | nothing | — | while the request persists | resolve the pending request |
3. Two Requests That End in the Same State
PM_Active_State_Request_L1 and PM_Enter_L1 both ask for L1. They are different packets because they arise from different authorities.
ASPM is autonomous. Hardware notices the Link has been idle, decides on its own that L1 is worth entering, and asks. Because nobody instructed it, the partner may reasonably say no — it has traffic queued, or L1 is not enabled at that port.
PCI-PM is a consequence of software. Configuration software has placed the function in a non-D0 state; the Link's power state follows from a decision already made. Source (a) describes the outcome as "the data link layer handshake" with no rejection path, and source (b)'s flow is PM_ENTER_L1 transmitted "until receiving a PM_REQ_ACK DLLP" — an acknowledgement, and no refusal in the sequence.
So the decoder must distinguish them (§8 emits a kind), because the two differ in what a refusal would even mean, and a design that mapped both onto one internal "please enter L1" event would have discarded the distinction at the exact point it is cheapest to keep.
The responding port's decision policy is out of scope. It depends on queued traffic and on configuration this chapter does not model, so §10 builds the requesting side and says plainly that the answer comes from somewhere it does not own.
4. A Request Arrives Hundreds of Times
This is the chapter's central structural point.
Source (a): "continuously transmits the PM_Active_State_Request_L1 DLLP until it receives a response." Source (b): "transmits PM_ENTER_L1 DLLPs until receiving a PM_REQ_ACK DLLP."
So the request is not sent once. It is sent, and sent, and sent, for as long as the requester waits — which may be a long time, because the responding port must first drain its own traffic and empty its own replay storage (§5).
5. The L1 Entry Sequence Waits for the Replay Buffer
The deepest integration point in Module 15, and it comes straight from source (b).
"Block further TLP transmissions · Wait for all in process TLPs to complete transmission · Wait for the Replay Buffer to empty (all transmitted TLPs acknowledged) · Core transmits
PM_ENTER_L1DLLPs until receiving aPM_REQ_ACKDLLP from remote device"
And symmetrically on the responding side, source (b): the Downstream Port receiving PM_ENTER_L1 "stops TLP transmissions, waits for all outstanding TLPs to be acknowledged, and then transmits PM_REQ_ACK DLLPs."
6. The Refusal Is Not a DLLP
PM_Active_State_Nak is a Message TLP. Source (a)'s message table lists it as a Msg with Message Code 0001_0100 and routing type 100 — a Transaction Layer packet.
request: PM_Active_State_Request_L1 Data Link Layer (DLLP)
acceptance: PM_Request_Ack Data Link Layer (DLLP)
refusal: PM_Active_State_Nak Transaction Layer (Message TLP)7. A Trace
Internal teaching signals, not PCIe wire signals.
step 1 2 3 4 5 6 7 8 9 10 11
pm_start 1 0 0 0 0 0 0 0 0 0 0
tx_inflight 1 1 0 0 0 0 0 0 0 0 0
retained_cnt 3 3 3 2 0 0 0 0 0 0 0
state IDL BLK BLK DRN DRN REQ REQ REQ ACK ACK IDL
block_tlps 0 1 1 1 1 1 1 1 1 1 0
pm_send 0 0 0 0 0 1 1 1 0 0 0
rx_req_ack 0 0 0 0 0 0 0 1 0 0 0
rx_as_nak 0 0 0 0 0 0 0 0 0 0 0
quiesce_req 0 0 0 0 0 0 0 0 1 1 0Read step 2. S_BLOCK first — new TLP transmission stops before anything waits (§5).
Read steps 4–5. S_DRAIN. retained_cnt falls 3 → 2 → 0 as acknowledgements arrive from the partner (Chapter 14.2). The port is waiting on a decision it does not make.
Read steps 6–8. S_REQUEST. pm_send is a level, not a pulse — the request is transmitted continuously (§1) until an answer arrives.
Read step 8. PM_Request_Ack is decoded. Note it goes nowhere near the retirement window (§6).
Read step 9. quiesce_req rises — the handoff, and the edge of this chapter. What happens next is Link training and low-power-state entry, which Module 18 owns.
And note what never happens: pm_send is never asserted while retained_cnt is non-zero. That is P5.
8. RTL — PM DLLP Decoder
// SYNTHESIZABLE. Classify a received power-management DLLP.
// The packet names and their roles: VERIFIED (section 1). The Type values
// are PARAMETERS — this chapter does not publish the encodings.
// PM_ENTER_L23 is VERIFIED as a generated DLLP by source (c); only its
// Type ENCODING is unpublished, like the others.
package pm_dllp_pkg;
typedef enum logic [2:0] {
PM_NONE = 3'd0,
PM_ASR_L1 = 3'd1, // PM_Active_State_Request_L1 (request, ASPM)
PM_ENTER_L1 = 3'd2, // PM_Enter_L1 (request, PCI-PM)
PM_ENTER_L23 = 3'd3, // PM_Enter_L23 (request, L2/L3)
PM_REQ_ACK = 3'd4 // PM_Request_Ack (acceptance)
} pm_kind_e;
// PM_REQ_ACK is deliberately EXCLUDED. It is the answer, not a request,
// and section 9 depends on that distinction.
function automatic bit pm_is_request(input pm_kind_e k);
return (k == PM_ASR_L1) || (k == PM_ENTER_L1) || (k == PM_ENTER_L23);
endfunction
endpackageimport pm_dllp_pkg::*;
module pm_dllp_decode #(
parameter logic [7:0] T_ASR_L1 = 8'h20, // supplied by the integrator
parameter logic [7:0] T_ENTER_L1 = 8'h21,
parameter logic [7:0] T_REQ_ACK = 8'h22,
parameter logic [7:0] T_ENTER_L23 = 8'h23
) (
input logic dllp_valid,
input logic [7:0] dllp_byte0, // Type
// Bytes 1..3 are deliberately NOT PORTS. A PM DLLP carries no information
// beyond its Type (section 1, corroborated tier), and the safest way to
// honour that is to make the payload UNREACHABLE from this module rather
// than available and unused. P1 asserts the consequence.
input logic dllp_integrity_ok,
output pm_kind_e pm_kind,
output logic pm_bad_integrity
);
// INTEGRITY FIRST, for the fourth time in this curriculum (Chapters 14.1,
// 14.5, 15.3, 15.4). Here the stake is a power-state negotiation started
// from a corrupted Type byte.
wire usable = dllp_valid && dllp_integrity_ok;
always_comb begin
pm_kind = PM_NONE;
if (usable) begin
unique case (dllp_byte0)
T_ASR_L1 : pm_kind = PM_ASR_L1;
T_ENTER_L1 : pm_kind = PM_ENTER_L1;
T_REQ_ACK : pm_kind = PM_REQ_ACK;
T_ENTER_L23 : pm_kind = PM_ENTER_L23;
default : pm_kind = PM_NONE;
endcase
end
end
assign pm_bad_integrity = dllp_valid && !dllp_integrity_ok;
endmoduleClassification: synthesizable (combinational).
Architecture. A Type comparison and nothing else. The payload bytes are not ports — the strongest available statement that they cannot influence the result, and structurally better than accepting them and promising not to look (Chapter 15.1 §6's argument).
It distinguishes the two L1 requests rather than merging them (§3).
Failure — three. Merging PM_ASR_L1 and PM_ENTER_L1 discards a distinction at the cheapest place to keep it. Classifying before checking integrity starts a power-state negotiation from a corrupted byte. And mapping PM_REQ_ACK onto a reliability-acknowledgement type — a name-driven mistake with the consequences §6 describes.
9. RTL — PM Request Holding State
import pm_dllp_pkg::*;
// SYNTHESIZABLE. Hold "a PM request of kind K is outstanding".
// That a request is transmitted repeatedly until answered is VERIFIED
// (section 1); that the receiver must therefore COLLAPSE repeats is the
// design consequence (section 4). The kind-change report is ILLUSTRATIVE.
module pm_request_hold (
input logic clk,
input logic rst_n,
input pm_kind_e pm_kind, // from pm_dllp_decode, combinational
// The local decision, from the responding port's policy (NOT modelled,
// section 3).
input logic resolve, // this request has been answered
input logic abort, // negotiation abandoned
output logic req_outstanding,
output pm_kind_e req_kind,
output logic pm_kind_change // a DIFFERENT request arrived
);
logic out_q;
pm_kind_e kind_q;
logic chg_q;
assign req_outstanding = out_q;
assign req_kind = kind_q;
assign pm_kind_change = chg_q;
wire incoming_req = pm_is_request(pm_kind);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_q <= 1'b0; kind_q <= PM_NONE; chg_q <= 1'b0;
end else begin
// Resolution has priority. A request answered this cycle is finished
// even if another copy of it arrives in the same cycle — the remote
// port has not seen the answer yet and is still repeating.
if (resolve || abort) begin
out_q <= 1'b0;
kind_q <= PM_NONE;
end else if (incoming_req) begin
// IDEMPOTENT. A repeat of the SAME kind sets a bit that is already
// set and rewrites a field with its own value — no counter, no
// queue, no restart. Five hundred arrivals, one outstanding request.
out_q <= 1'b1;
kind_q <= pm_kind;
end
// A DIFFERENT request while one is outstanding is a new negotiation,
// not a repeat. Reported rather than silently absorbed (section 4).
if (incoming_req && out_q && (pm_kind != kind_q)) chg_q <= 1'b1;
end
end
endmoduleClassification: synthesizable.
Architecture. One sticky bit and one kind field. No occupancy, no pointer, no depth — there is nothing to accumulate.
Cycle behaviour.
| Situation | Result |
|---|---|
| first request of kind K | req_outstanding set, kind K |
| repeat of kind K | no change — idempotent |
| different kind while outstanding | kind updated and pm_kind_change reported |
resolve or abort | cleared, and takes priority over a same-cycle repeat |
PM_REQ_ACK decoded | ignored here — it is not a request |
Failure — four. A FIFO re-runs the entry sequence once per arriving copy (§4). A counter of outstanding requests overflows and means nothing. Silently overwriting on a kind change hides a real protocol anomaly. And treating PM_REQ_ACK as a request — it is the answer, which pm_is_request excludes explicitly.
Deliberately simplified: the responding port's decision policy is not modelled (§3); no timeout, because §1 does not publish one.
10. RTL — PM Request Front End
import pm_dllp_pkg::*;
// SYNTHESIZABLE. Requesting-side power-management front end.
// The sequence -- block TLPs, drain in-flight, WAIT FOR THE REPLAY BUFFER
// TO EMPTY, then transmit the request repeatedly until answered -- is
// VERIFIED (section 1, source b). The two separate response ports follow
// from the refusal being a Message TLP (section 6). State encoding and
// the abort input: ILLUSTRATIVE.
module pm_request_frontend #(
parameter int CNT_W = 6 // width of the retained-occupancy count
) (
input logic clk,
input logic rst_n,
// ---- Local request ----------------------------------------------------
input logic pm_start,
input pm_kind_e pm_start_kind,
input logic abort,
// ---- Transmit-path status --------------------------------------------
input logic tx_tlp_inflight, // a TLP is mid-transmission
// Chapter 14.4's RETAINED OCCUPANCY. Not a pointer comparison -- the
// counter, because emptiness inferred from pointer equality is the bug
// that would let this FSM proceed with packets still retained.
input logic [CNT_W-1:0] retained_count,
// ---- Responses, from TWO DIFFERENT LAYERS (section 6) -----------------
input logic rx_pm_req_ack, // DLLP, from section 8
input logic rx_pm_as_nak, // Message TLP, from the Message
// decoder -- NOT a DLLP
// ---- Outputs ----------------------------------------------------------
output logic block_new_tlps,
output logic pm_send, // LEVEL: transmit continuously
output pm_kind_e pm_send_kind,
output logic quiesce_req, // handoff -- Module 18 owns what
// happens next
output logic pm_refused,
output logic pm_done
);
typedef enum logic [2:0] {
S_IDLE, S_BLOCK, S_DRAIN, S_REQUEST, S_ACCEPTED, S_REFUSED
} st_e;
st_e st_q;
pm_kind_e kind_q;
// THE ENTRY CONDITION (section 5). Both halves are required: no TLP may be
// mid-transmission, and NOTHING may remain unacknowledged.
wire tx_quiet = !tx_tlp_inflight;
wire replay_empty = (retained_count == '0);
assign block_new_tlps = (st_q != S_IDLE);
assign pm_send = (st_q == S_REQUEST);
assign pm_send_kind = kind_q;
assign quiesce_req = (st_q == S_ACCEPTED);
assign pm_refused = (st_q == S_REFUSED);
assign pm_done = (st_q == S_ACCEPTED) || (st_q == S_REFUSED);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= S_IDLE; kind_q <= PM_NONE;
end else if (abort && (st_q != S_IDLE)) begin
st_q <= S_IDLE; kind_q <= PM_NONE;
end else begin
unique case (st_q)
S_IDLE :
if (pm_start && pm_is_request(pm_start_kind)) begin
st_q <= S_BLOCK;
kind_q <= pm_start_kind;
end
// BLOCK FIRST. block_new_tlps is already asserted here, so the
// source is stopped before anything waits on it. Waiting for the
// buffer to drain while still accepting new work may never converge.
S_BLOCK :
if (tx_quiet) st_q <= S_DRAIN;
// WAIT FOR RETIREMENT. This FSM cannot make this happen -- it waits
// for the PARTNER's acknowledgements (Chapter 14.2).
S_DRAIN :
if (replay_empty) st_q <= S_REQUEST;
// TRANSMIT REPEATEDLY. pm_send is a level. The DLLP transmit path
// emits the request for as long as it is held (section 1).
S_REQUEST :
// Acceptance is checked first. Both answers arriving together is
// a protocol violation, and preferring the DLLP keeps the
// resolution deterministic rather than undefined.
if (rx_pm_req_ack) st_q <= S_ACCEPTED;
else if (rx_pm_as_nak) st_q <= S_REFUSED;
// REFUSAL RESUMES NORMAL OPERATION on the next cycle -- source (a):
// on a rejection the controller "aborts the ASPM L1 entry and
// continues to send TLPs normally". Holding block_new_tlps here
// would stall the transmit path over a routine answer.
S_REFUSED :
st_q <= S_IDLE;
// ACCEPTANCE is terminal. quiesce_req stays asserted, and the
// consumer -- Module 18's -- releases the FSM by pulsing abort.
S_ACCEPTED : ;
default : st_q <= S_IDLE;
endcase
end
end
endmoduleClassification: synthesizable.
Architecture. A six-state sequence whose order is the chapter's content: block, quiet, drain, ask, resolve.
Cycle behaviour.
| State | Waiting on | Leaves when |
|---|---|---|
S_BLOCK | the local transmit path | no TLP mid-transmission |
S_DRAIN | the partner's acknowledgements | retained_count == 0 |
S_REQUEST | the partner's answer | PM_Request_Ack or PM_Active_State_Nak |
S_REFUSED | nothing | next cycle — TLP transmission resumes |
S_ACCEPTED | the quiesce consumer | abort |
Contract. retained_count is Chapter 14.4's occupancy counter, not a derived comparison. rx_pm_req_ack comes from §8's DLLP decoder; rx_pm_as_nak comes from the Message decoder — two ports, because two layers (§6).
Note the asymmetry between the two outcomes. S_ACCEPTED is terminal — quiesce_req stays asserted and TLPs stay blocked, because the Link is about to go quiet. S_REFUSED returns to idle on the next cycle, because source (a) says a refused controller "continues to send TLPs normally" — and a design that held the block over a routine "no" would stall its own transmit path every time the partner was busy.
Failure — six. Draining before blocking may never converge on a busy Link (§5). Inferring emptiness from pointer equality lets the request go out with packets still retained. Pulsing pm_send instead of holding it sends one request where the protocol requires repetition (§1). One combined response port invites the Message TLP and the DLLP to be merged upstream, which is where the §6 confusions start. Asserting quiesce_req from S_REQUEST would begin quiescing before the partner agreed. And holding block_new_tlps after a refusal converts a normal answer into a transmit stall.
Deliberately simplified: no response timeout (§1 publishes none); no retry-after-refusal policy; no L-state modelling at all — quiesce_req is the boundary.
11. Assertions
// SVA over pm_dllp_decode, pm_request_hold and pm_request_frontend, bound
// against Chapter 14.4's replay buffer and 14.2's retirement window.
// These assert the verified sequencing of section 1 and the LOCAL
// negotiation contract. Nothing here asserts request repetition intervals or
// response timeouts (not published, section 1), anything about the LTSSM or
// link states (Module 18), or anything about credits (Module 16).
// ---- ENVIRONMENT -----------------------------------------------------
// A1: retained_count is Chapter 14.4's occupancy for THIS transmit path.
assume property (@(posedge clk) disable iff (!rst_n)
retained_count == dut_replay_buffer.occupancy);
// A2: the two responses never arrive in the same cycle -- one request has
// one answer. Violation is a protocol error, not a design case.
assume property (@(posedge clk) disable iff (!rst_n)
!(rx_pm_req_ack && rx_pm_as_nak));
// ---- DECODE ----------------------------------------------------------
// P1: PAYLOAD INDEPENDENCE. The decoded kind is a function of the Type byte
// and the integrity verdict alone (section 1, corroborated tier -- so the
// design is made correct either way).
property p_type_only;
@(posedge clk) disable iff (!rst_n)
(dllp_valid && $stable(dllp_byte0) && $stable(dllp_integrity_ok))
|-> $stable(pm_kind);
endproperty
a_type_only : assert property (p_type_only);
// P2: a corrupted DLLP never becomes a PM event. Here the stake is a
// power-state negotiation started from a corrupted byte.
property p_corrupt_never_decodes;
@(posedge clk) disable iff (!rst_n)
(dllp_valid && !dllp_integrity_ok) |-> (pm_kind == PM_NONE);
endproperty
a_corrupt : assert property (p_corrupt_never_decodes);
// P3: the two L1 requests stay distinct (section 3).
property p_l1_requests_distinct;
@(posedge clk) disable iff (!rst_n)
(dllp_valid && dllp_integrity_ok && (dllp_byte0 == T_ASR_L1))
|-> (pm_kind == PM_ASR_L1);
endproperty
a_distinct : assert property (p_l1_requests_distinct);
// ---- IDEMPOTENCE -----------------------------------------------------
// P4: THE CHAPTER'S CENTRAL PROPERTY. N identical requests produce ONE
// outstanding request. A queue fails this on the second arrival.
// (req_assert_count is a testbench counter of req_outstanding rising edges.)
property p_repeats_collapse;
@(posedge clk) disable iff (!rst_n)
(req_outstanding && $stable(req_kind) && !resolve && !abort)
|=> (req_outstanding && $stable(req_kind));
endproperty
a_idempotent : assert property (p_repeats_collapse);
// P4b: no restart. An outstanding request is not re-raised by a repeat.
property p_no_restart_on_repeat;
@(posedge clk) disable iff (!rst_n)
($rose(req_outstanding)) |-> !$past(req_outstanding);
endproperty
a_no_restart : assert property (p_no_restart_on_repeat);
// ---- THE ENTRY CONDITION ---------------------------------------------
// P5: THE RELIABILITY GATE (section 5). A request is NEVER transmitted while
// anything remains unacknowledged. Bound against Chapter 14.4 via A1.
property p_no_request_while_retained;
@(posedge clk) disable iff (!rst_n)
pm_send |-> (retained_count == '0);
endproperty
a_gate : assert property (p_no_request_while_retained);
// P6: ORDERING. New TLP transmission is blocked before the drain wait
// begins, or the wait may never converge.
property p_block_before_drain;
@(posedge clk) disable iff (!rst_n)
(st_q == S_DRAIN) |-> block_new_tlps;
endproperty
a_order : assert property (p_block_before_drain);
// P7: the request is a LEVEL, held until answered (section 1).
property p_request_held_until_answered;
@(posedge clk) disable iff (!rst_n)
(pm_send && !rx_pm_req_ack && !rx_pm_as_nak && !abort)
|=> (pm_send && $stable(pm_send_kind));
endproperty
a_level : assert property (p_request_held_until_answered);
// ---- THE TWO NAME TRAPS (section 6) ----------------------------------
// P8: PM_Request_Ack NEVER advances reliability retirement. It carries no
// sequence number; retiring against it would release packets whose delivery
// is unresolved (Chapter 14.1 section 2).
property p_req_ack_is_not_an_ack;
@(posedge clk) disable iff (!rst_n)
(pm_kind == PM_REQ_ACK) |-> !dut_retire_window.retire_valid;
endproperty
a_not_an_ack : assert property (p_req_ack_is_not_an_ack);
// P9: PM_Active_State_Nak NEVER arms a replay. It is a Message TLP meaning
// "no", not Chapter 15.4's Nak.
property p_as_nak_is_not_a_nak;
@(posedge clk) disable iff (!rst_n)
rx_pm_as_nak |-> !dut_retry_controller.arm_replay;
endproperty
a_not_a_nak : assert property (p_as_nak_is_not_a_nak);
// P10: SCOPE. A PM DLLP never touches flow-control state (Module 16 is not
// consumed by this chapter, and a design must not blur the categories).
property p_pm_touches_no_credits;
@(posedge clk) disable iff (!rst_n)
(pm_kind != PM_NONE) |-> !dut_fc_tracker.ret_valid;
endproperty
a_scope : assert property (p_pm_touches_no_credits);
// P11: quiesce is requested only after an ACCEPTANCE -- never from
// S_REQUEST, and never after a refusal.
property p_quiesce_only_after_accept;
@(posedge clk) disable iff (!rst_n)
quiesce_req |-> $past(rx_pm_req_ack || (st_q == S_ACCEPTED));
endproperty
a_quiesce : assert property (p_quiesce_only_after_accept);
// P12: a refusal ends the negotiation without quiescing (section 6).
property p_refusal_does_not_quiesce;
@(posedge clk) disable iff (!rst_n)
pm_refused |-> !quiesce_req;
endproperty
a_refusal : assert property (p_refusal_does_not_quiesce);
// P13: BOUNDED RESOLUTION. Once a response arrives, the FSM leaves
// S_REQUEST on the next cycle. A deterministic local transition, so this is
// a bound and not a fairness assumption (Chapter 14.3 section 13).
property p_response_resolves_next_cycle;
@(posedge clk) disable iff (!rst_n)
((st_q == S_REQUEST) && (rx_pm_req_ack || rx_pm_as_nak))
|=> (st_q inside {S_ACCEPTED, S_REFUSED});
endproperty
a_bounded : assert property (p_response_resolves_next_cycle);
// P14: reset clears the negotiation.
property p_reset_clears;
@(posedge clk)
!rst_n |=> (!pm_send && !quiesce_req && !req_outstanding);
endproperty
a_reset : assert property (p_reset_clears);P5 is the property to bind first in any real design, and note it is bound against Chapter 14.4's occupancy through A1, not against a local copy. A design with its own idea of emptiness would satisfy a local version of P5 while still asking with packets retained.
P8, P9 and P10 are all scope properties, and each is written as a cross-module non-interference claim. They are the only way to catch a wiring mistake, because every module in isolation behaves correctly — the fault is that an output was connected to the wrong consumer.
P4 and P4b together express idempotence. P4 says an outstanding request stays exactly as it is under repeats; P4b says it is never re-raised, which is the half a queue-based design fails.
P13 is bounded rather than s_eventually, reusing Chapter 14.3 §13's reasoning: the transition is deterministic and local, so a bound is both provable and stronger.
12. Verification and Fault Injection
The scoreboard tracks the negotiation independently, from observed DLLPs and observed Messages — never from st_q. And it counts four things separately:
requests transmitted ← wire observations, MANY per negotiation
negotiations started ← ONE per request/answer exchange
retirement events ← Chapter 14.2's, and PM must contribute ZERO
replay arms ← Chapter 14.3's, and PM must contribute ZEROThe last two are the point. A correct PM implementation adds nothing to either.
Decode
- Each PM Type in turn, plus an unknown Type. Verify the kind and that unknown decodes to
PM_NONE. dllp_integrity_oklow with a valid PM Type. VerifyPM_NONE(P2).- Payload bytes varied across their full range with a fixed Type — verify the decoded kind never changes (P1). Structurally impossible here, and tested anyway so that a future refactor that adds the ports is caught.
Idempotence
- One request repeated 500 times. Verify one outstanding request and one entry sequence (P4, P4b). The chapter's required test.
- Repeat arriving in the same cycle as
resolve. Verify the resolution wins. - A different kind while one is outstanding. Verify
pm_kind_changeis reported (§4).
Entry sequence
pm_startwithretained_count = 5. Verifyblock_new_tlpsrises immediately andpm_sendstays low until the count reaches zero (P5, P6).retained_countnever reaching zero. Verify the FSM waits inS_DRAINindefinitely and never transmits — the deadlock is correct behaviour here, and a design that gave up and asked anyway would be the bug.- A TLP still in flight. Verify
S_BLOCKholds. - Acceptance — verify
quiesce_reqand no retirement event (P8, P11). - Refusal via the Message port — verify
pm_refused, noquiesce_req(P12), no replay arm (P9), and thatblock_new_tlpsdrops so transmission resumes. abortin each state.- Reset mid-negotiation (P14).
Mutations
| # | Mutation | Caught by |
|---|---|---|
| 1 | PM request events queued in a FIFO | P4b — the sequence re-runs on every repeat |
| 2 | retained_count check removed | P5, with the 5-retained test |
| 3 | drain waited on before blocking | P6, and the busy-Link test never converges |
| 4 | emptiness inferred from pointer equality | P5 via A1, at a wrapped buffer |
| 5 | PM_Request_Ack routed to the retirement window | P8 — retained packets released with delivery unresolved |
| 6 | PM_Active_State_Nak routed to the retry controller | P9 — a replay from a power refusal |
| 7 | pm_send pulsed instead of held | P7; the partner sees one request and may miss it |
| 8 | PM_ASR_L1 and PM_ENTER_L1 merged | P3 |
| 9 | integrity checked after the Type comparison | P2 |
| 10 | quiesce_req asserted from S_REQUEST | P11 — quiescing before the partner agreed |
| 11 | refusal also asserts quiesce_req | P12 |
| 13 | block_new_tlps held after a refusal | the refusal test — transmission never resumes |
| 12 | PM decode nudges the flow-control tracker | P10 |
13. Debugging
The Link never enters a low-power state
Walk the sequence; each step eliminates the ones before.
- Is
block_new_tlpsasserted? If not,pm_startnever fired — a local policy problem, not a protocol one. - Is the FSM in
S_DRAIN? If it is stuck inS_BLOCK, a TLP is permanently in flight. - Is
retained_countfalling? If it is not, the problem is reliability, not power — acknowledgements are not arriving, so start at Chapter 15.3 §11 instead. - Is
pm_sendasserted? If yes, the request is going out and the partner is not answering. - Is
pm_refusedset? A refusal is a normal outcome, not a fault — the partner had traffic or L1 was not enabled (§3).
Step 3 is the one worth internalising. A power-management failure is very often a reliability failure wearing a different hat, and the counter says which in one glance.
A replay happens every time the Link tries to enter L1
Almost certainly PM_Active_State_Nak wired into the retry controller (§6).
Confirm it in one observation: check whether the replay arms coincide with a refusal on the Message path. If they do, the Message decoder's output is reaching Chapter 14.3's event input.
And note the tell: the replays are correlated with power transitions rather than with errors. A reliability fault correlates with LCRC failures; this one does not, which is what points at the wiring.
Retained packets are released with no acknowledgement on the wire
PM_Request_Ack reaching the retirement window (§6).
The Ack analyzer trace shows no acknowledgement, yet retained_count drops. A PM DLLP carries no sequence number, so whatever value the window retired against was whatever those bits happened to be — usually retiring far too much.
P8 catches it immediately. In its absence, the signature is retirement events with no corresponding Ack DLLP, and the fix is a wiring change rather than an algorithm change.
Entry works when idle and never when busy
The S_BLOCK / S_DRAIN ordering (§5).
If the drain wait began before new transmissions were blocked, retained_count is being refilled as fast as it drains. On an idle Link it reaches zero anyway; on a busy one it never does. P6 states the invariant.
14. Common Misconceptions
- "
PM_Request_Ackis an ACK DLLP." It is a PM DLLP with no sequence number. Feeding it to the retirement window releases retained packets against a meaningless value (§6, P8). - "
PM_Active_State_Nakis a NAK DLLP." It is a Message TLP at the Transaction Layer, meaning no. It triggers no replay (§6, P9). - "A PM DLLP is sent once." It is transmitted continuously until answered (§1).
- "Every arriving PM DLLP is a separate request." They are repeats of one request and must be collapsed (§4).
- "Backpressure always needs a FIFO." Chapter 15.4 needed one; this chapter must not have one. The structure follows from what the packet is (§2).
- "A PM request cannot be refused." ASPM L1 can be refused, and the refusal is a defined packet (§1, §3).
- "ASPM L1 and PCI-PM L1 use the same DLLP." They use
PM_Active_State_Request_L1andPM_Enter_L1respectively (§1, §3). - "Power management is independent of the reliability layer." The request may not be transmitted until the replay buffer is empty (§5).
- "You can wait for the replay buffer to drain without blocking new TLPs." On a busy Link the condition may never hold (§5, P6).
- "A PM DLLP returns flow-control credits." Different category, different engine (Chapter 15.2), and P10 asserts it.
- "A PM DLLP carries a payload worth parsing." It carries its Type; §8 does not even accept the other bytes.
- "PM DLLPs are forwarded through a Switch." Like every DLLP, they terminate at the port that receives them (Chapter 15.1 §3). The PM Messages —
PM_PME,PME_Turn_Off,PME_TO_Ack— are TLPs and are routed. - "This chapter explains the L1 state." It explains the request that precedes it. The link states and the LTSSM belong to Module 18.
15. Understanding Check
16. What's Next
Module 15 is complete, and its four categories turned out to differ in something more useful than their Type encodings — in what a receiver must structurally be.
A cumulative frontier is delivered by overwriting, so the sender keeps one value (15.3). A one-time event is delivered by queueing, because losing it is unrecoverable (15.4). A periodic total is delivered by replacing a baseline (15.2). And a request is delivered by repetition, so it must be collapsed, may be refused — through a different layer — and may not even be asked until the reliability layer has nothing outstanding.
Module 16 now takes the credit system that Chapter 15.2 deliberately fed and never spent: what a credit is, how availability is computed from the totals those updates carry, and why a transmitter may hold a packet that is otherwise ready to go.
Module 18 later takes the LTSSM and the link states — everything on the far side of the quiesce_req this chapter stopped at.
The idea to carry forward: the shape of a receiver is dictated by the shape of the message, not by the direction of the arrow — and a request is the one message that must be forgotten as fast as it is remembered.