PCIe · Module 18
L1 — Deeper Power, Coordinated Entry, and a Drain That Must Complete First
L0s leaves too much active for long idle intervals; retraining from Detect costs far too much. L1 is the middle ground — and unlike L0s it is negotiated, refusable, and requires the replay buffer to be empty before it can begin.
Chapter 18.7 built a mechanism for short idle intervals: quiet one transmitter, keep everything else, pay a short synchronization sequence to return.
But L0s is a poor fit for long idleness. It leaves clocks, PLLs and most of the PHY fully active, so the saving is bounded — and a Link that is idle for milliseconds is paying almost full power for almost no transport.
And the obvious alternative is far worse. Tearing the Link down and rebuilding from Detect would save the most power and cost microseconds of re-establishment plus a full bilateral negotiation — catastrophic for any workload that might need the Link again soon.
L1 is the middle ground: how does a Link reduce substantially more PHY activity than L0s, without discarding the negotiated connection — and what has to be true before it is even allowed to start?
1. The Verified Sources
2. L1 Is Not L0s
Built entirely from §1's sourced lists.
| L0s (18.7) | L1 | |
|---|---|---|
| PHY | (not powered down) | "Significant portion of PHY powered down" |
| Transmitter | electrical idle | electrical idle |
| Receiver | — | also electrical idle |
| Who re-locks | "Remote PHY receiver" | "Remote and local PHY" |
| Entry decision | local — "when the transmit side is idle" | negotiated — request, ack, or refusal |
| Precondition | idle transmit path | replay buffer empty (§7) |
| Direction | two independent machines (18.7 §2) | Link-level, both directions |
| Exit latency | "Less than 64 ns" … "More than 4 μs" | "Less than 1 μs" … "More than 64 μs" |
Every row follows from the first one. Powering down more of the PHY means the receiver goes idle too; that means both ends lose lock; that means exit costs more; and because both ends are affected, entry cannot be a unilateral decision.
The latency ranges quantify it. L1's floor — "less than 1 μs" — is roughly where L0s's ceiling is. They are not competing options for the same idle interval; they address different durations.
3. L1 Is Not Device D1
4. L1 Is Not Electrical Idle
Reinforcing Chapter 18.7 §4, because it keeps mattering.
Electrical Idle is a physical signalling condition. L1 is a Link power state that uses it — in both directions rather than one.
This curriculum has now met Electrical Idle in six states: Recovery's speed change, L0's low-power entry substates, L0s, L1, Disable (Chapter 18.9 §1), and L2.
So electrical_idle has never once identified a state, and a design that derives L1 from an idle detector has made its state machine a function of a PHY output — mutation 13.
5. L1.1 and L1.2 — the Bounded Version
§1 sources their existence and their owner, and stops there: L1_SUBSTATE is "L1.1 or L1.2 depending upon higher level Power Management State Machine control."
Three things that phrase establishes, and this chapter claims no more:
They are refinements within L1, not alternatives to it — the LTSSM major state is still L1.
They are driven from above the LTSSM. "Higher level Power Management State Machine control" — the LTSSM follows a decision made elsewhere, which is why enumerating their entry conditions here would be describing the wrong machine.
And they are optional. §1's feature list names "L1 PM Substates" as a separately-enabled capability, so a conforming Link may implement L1 with no substates at all — mutation 12 is a design that assumes otherwise.
On CLKREQ#: platform configurations may involve a CLKREQ# signal in appropriate deeper-substate operation. This chapter names it and does not teach it — the exact substate entry and exit mechanics are outside its evidence, and the curriculum has no chapter that owns them.
What matters architecturally is the layering:
L1 (LTSSM major state, this chapter)
└── L1.1 / L1.2 (optional deeper refinements, driven by the PM state machine)6. Two Entry Paths, and They Are Not the Same Mechanism
§1 sources both, and §13's warning about conflating them is exactly right.
| ASPM L1 | Software-directed (PCI-PM) | |
|---|---|---|
| Trigger | link idle for "the configured idle period" | "writing… Power State Configuration Register to a value other than 00 == D0" |
| Decided by | hardware, autonomously | software, with the driver participating |
| Device state | stays D0 | moves to a non-D0 state |
| Initiated by | "only by Upstream Ports (Endpoint)" | software on the device's behalf |
| Can be refused | yes (§8) | the flow is a request/ack sequence |
The shared part is the ending. Both paths converge on the same drain and the same DLLP exchange (§7), and both end with "Core directs LTSSM state machine to L1."
But the beginning is completely different, which matters in a lab: a Link entering L1 tells you nothing about why until you know which path ran. §15's reason capture exists for exactly this, and §22's first debugging question depends on it.
7. The Drain Must Complete First
Then, and only then, the negotiation:
4. Transmit PM_ENTER_L1 DLLPs UNTIL PM_REQ_ACK is received
5. Core directs LTSSM state machine to L1Note until. The requesting end transmits repeatedly until acknowledged — a retransmission-until-acknowledged pattern, on a Link whose ordinary reliability machinery has just been drained. Chapter 15.5 owns the DLLPs themselves.
And the responding end mirrors it: it drains too, then transmits PM_REQ_ACK "until remote device idles its transmitter." Both ends drain. Both ends confirm. Neither goes quiet unilaterally.
8. Entry Can Be Refused
§1 is explicit, and this is the deepest structural difference from L0s:
"A Downstream Port (Root Port)… may choose to accept or reject the request. The core accepts ASPM L1 requests when there are no TLP or ACK/NAK DLLP pending transmit and otherwise rejects the request."
So L1 entry is a proposal. The Endpoint has been idle long enough to want L1; the Root Port may have work the Endpoint cannot see — a TLP queued, an acknowledgement owed — and it says no.
Compare Chapter 18.7 §3's L0s, where a transmitter enters "when the transmit side of the link is idle" — its own transmit side, its own judgement, no one to ask.
Why the difference is forced rather than chosen. L0s quiets one direction, so the decision affects only what that end was going to send. L1 quiets both, so entering it unilaterally would silence a direction the other end was about to use.
And a refusal is not an error. §15's reason capture and §19's diagnostics therefore treat a rejected request as an ordinary outcome — mutation 16 is a design that escalates it.
9. What Is Retained, Stated Carefully
§1 supports a narrow, precise claim, and this chapter makes only that one.
Retained: "Powered. Clock & PLLs active; core clock active." And the Link's negotiated identity — speed, width, lane map, Link number — because nothing renegotiated it. The Link does not restart from device discovery merely because it entered L1.
Not retained: "Remote and local PHY must re-establish symbol lock during L1 exit." Both directions, where L0s loses only one.
Explicitly not asserted here: which PHY blocks are in the "significant portion" that powers down, whether any CDR or equalization adaptation state survives, and whether a PLL is ever retuned. §1 does not say, so this chapter does not. These are implementation and mode properties, and a design that assumed them would be assuming a PHY it has not read the datasheet for.
The distinction to hold onto:
logical Link context — negotiated identity; survives L1 (§1)
analog PHY state — implementation- and mode-dependent; do not assume10. Exit
§1 gives the trigger and the handshake.
The trigger, verbatim: "When a TLP is pending or the LTSSM state machine indicates L1 state has been exited due to link partner activity, the core returns to L0." Two sources — local work, or the partner waking us — which is why §17 captures a wake cause.
And the handshake: L1_TO_L0 is "Exiting L1; wait for PHY to indicate exit from L1 complete." The same pattern as Chapter 18.7's TX_L0S_TO_L0 — asking the PHY to come back is not the same as it having come back, and §18 keeps them as separate states.
Then symbol lock must be re-established in both directions (§1), before normal traffic is legal again.
11. Latency Is the Design Constraint
§1's encodings put numbers on the trade, and the comparison with Chapter 18.7 §11 is the whole story:
| L0s Exit Latency | L1 Exit Latency | |
|---|---|---|
| Floor | "Less than 64 ns" | "Less than 1 μs" |
| Ceiling | "More than 4 μs" | "More than 64 μs" |
L1's best case is roughly L0s's worst case.
And the software computation is the same shape (§1, quoting the Base Specification): acceptable latency — "an indirect measure of the Endpoint's internal buffering" — compared against exit latencies "reported… by all components comprising the data path."
One slow component anywhere on the path can disable L1 for everything behind it, exactly as for L0s. And the clock architecture matters again: "Exit latencies may be significantly increased if the PCI Express reference clocks used by the two devices in the link are common or separate."
The engineering conclusion is not "deeper is better." A device whose acceptable latency is 4 μs cannot use an L1 path costing 32 μs, no matter how much power it would save — and a design that enabled it anyway would trade a small power win for a large, load-dependent latency penalty.
12. The L1 Subtree
Four things to read out of the figure.
The drain and the DLLP exchange are inside L1.Entry, which is inside L0 — so all of §7 happens before the LTSSM is in L1 at all.
There is an arrow back to L0 from entry. A refused request is a normal outcome (§8), and a diagram without that arrow would imply entry always succeeds.
Exit is two steps, matching §1's L1_TO_L0 — the PHY handshake, then symbol lock in both directions.
And L1.1 / L1.2 is drawn muted and off to the side, because §5's evidence puts it under a machine above the LTSSM.
13. A Trace
Internal teaching signals, not PCIe pins.
step 1 2 3 4 5 6 7 8 9 10 11 12
policy_l1_req 1 1 0 0 0 0 0 0 0 0 0 0
tlp_tx_blocked 0 1 1 1 1 1 1 1 1 1 1 0
inflight_tlps 2 2 1 0 0 0 0 0 0 0 0 0
replay_occupancy 3 3 3 2 0 0 0 0 0 0 0 0
drain_done 0 0 0 0 1 1 1 1 1 1 1 1
pm_enter_l1_tx 0 0 0 0 1 1 0 0 0 0 0 0
pm_req_ack_rx 0 0 0 0 0 1 0 0 0 0 0 0
in_l1 0 0 0 0 0 0 1 1 1 1 0 0
up_valid 0 0 0 0 0 0 0 1 1 1 1 1
up_ready 0 0 0 0 0 0 0 0 0 0 0 1
wake_pending 0 0 0 0 0 0 0 1 1 1 1 0
phy_exit_done 0 0 0 0 0 0 0 0 0 1 1 1
link_operational 1 1 1 1 1 1 0 0 0 0 0 1Read steps 2–4 — the drain. TLP transmission is blocked, in-flight TLPs finish, and replay_occupancy falls to zero at step 5. Only then does drain_done assert.
Note that replay_occupancy reaches 0 after inflight_tlps does. They are different quantities: a TLP finishes transmitting at step 4 and remains in the replay buffer until acknowledged at step 5. §7's third condition is the slower one, and a design that waited only for the transmit path to go idle would enter L1 with unacknowledged TLPs outstanding.
Read steps 5–6 — the negotiation. PM_ENTER_L1 is transmitted, and repeated — at step 5 nothing comes back. At step 6 PM_REQ_ACK arrives.
Read step 7. in_l1 rises and link_operational drops. Everything before this was still L0.
Read step 8 — the ownership question. A packet becomes valid in L1. up_ready stays 0 and wake_pending rises. Not the same thing (§19).
Read step 10 versus 12. phy_exit_done at step 10, but link_operational does not rise until step 11 — lock must be restored in both directions first. The packet finally transfers at step 12, five cycles after it arrived, having never been at risk.
14. RTL — Types and Entry Request Owner
// SYNTHESIZABLE. Normalized L1 types.
// THE TWO ENTRY PATHS are sourced (section 6). The enum encoding is
// internal -- it appears on no PCIe wire.
package l1_pkg;
typedef enum logic [1:0] {
L1R_NONE = 2'd0,
L1R_ASPM = 2'd1, // hardware-autonomous, link idle for the period
L1R_PM_DIRECTED= 2'd2, // software wrote a non-D0 power state
L1R_REMOTE = 2'd3 // the partner requested; we are responding
} l1_entry_reason_e;
// Wake causes as a BITMASK -- several can be true at once, the same
// reasoning as Chapter 18.5's Recovery cause register.
typedef struct packed {
logic local_tx_pending; // section 1: "a TLP is pending"
logic remote_wake; // section 1: "exited due to link partner activity"
logic pm_exit_request; // software moved the device back toward D0
logic link_event; // link-down, reset, directed transition
} l1_wake_t;
function automatic bit any_wake(input l1_wake_t w); return (|w); endfunction
// THE DRAIN CONTRACT (section 7). All three, in this order of strictness.
function automatic bit drain_complete(input logic tx_blocked,
input logic no_inflight,
input logic replay_empty);
return tx_blocked && no_inflight && replay_empty;
endfunction
endpackageimport l1_pkg::*;
// SYNTHESIZABLE. Own one L1 entry request.
// THE REQUEST IS NOT ISSUED UNTIL THE DRAIN COMPLETES (section 7). A
// request issued earlier would ask the partner to sleep while this end
// still has unacknowledged TLPs -- and the acknowledgement can never
// arrive once both directions are idle.
module l1_entry_owner (
input logic clk,
input logic rst_n,
input logic policy_enable,
input logic idle_period_met, // ASPM path
input logic pm_non_d0, // software-directed path
input logic remote_request, // we are the responder
// ---- Drain status, from the Data Link Layer (Chapter 15.1) -------------
input logic tx_blocked,
input logic no_inflight_tlps,
input logic replay_empty,
input logic abort, // link-down, wake, reset
output logic entry_req_valid,
input logic entry_req_ready,
output l1_entry_reason_e entry_reason,
output logic entry_committed,
// Sourced in section 8: the partner may say no, and that is NORMAL.
input logic entry_refused,
output logic refused_sticky
);
logic req_q, done_q, refused_q;
l1_entry_reason_e reason_q;
assign entry_req_valid = req_q;
assign entry_reason = reason_q; // CAPTURED, stable while requesting
assign entry_committed = done_q;
assign refused_sticky = refused_q;
wire drained = drain_complete(tx_blocked, no_inflight_tlps, replay_empty);
// PRIORITY IS DECLARED: a software-directed entry outranks an autonomous
// one, and responding to the partner outranks initiating.
wire [1:0] want = remote_request ? 2'd3
: pm_non_d0 ? 2'd2
: (policy_enable && idle_period_met) ? 2'd1 : 2'd0;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
req_q <= 1'b0; done_q <= 1'b0; refused_q <= 1'b0; reason_q <= L1R_NONE;
end else if (abort) begin
// Checked first. Clears only an UN-ACCEPTED request -- once the
// macro takes it, entry is happening and the exit path reverses it.
req_q <= 1'b0; done_q <= 1'b0;
end else begin
if (!req_q && !done_q) begin
// ==========================================================
// THE DRAIN GATES THE REQUEST. Note `drained` is required even
// for the remote-responder path: section 1's Downstream Port
// flow drains BEFORE transmitting PM_REQ_ACK, exactly as the
// initiator drains before PM_ENTER_L1.
// ==========================================================
if ((want != 2'd0) && drained) begin
req_q <= 1'b1;
reason_q <= l1_entry_reason_e'(want); // captured once
end
end else if (req_q && entry_req_ready) begin
// Dropped on the HANDSHAKE, never on a later "entry done".
req_q <= 1'b0; done_q <= 1'b1;
end
// A REFUSAL IS RECORDED, NOT ESCALATED (section 8, mutation 16).
if (entry_refused) begin
req_q <= 1'b0;
refused_q <= 1'b1;
end
// NOTE: policy_enable or idle_period_met dropping does NOT withdraw
// an outstanding request -- Chapter 18.7 section 14's rule.
end
end
endmoduleClassification: synthesizable.
Architecture. Issue once after the drain, hold until the handshake, record a refusal.
Requiring the drain on the responder path too follows §1 exactly: the Downstream Port flow drains before transmitting PM_REQ_ACK. Both ends drain — a responder that acknowledged immediately would agree to sleep with its own replay buffer non-empty.
Failure — five. Requesting before the drain (mutation 11 in spirit) asks the partner to sleep while acknowledgements are still owed. Waiting only for the transmit path — not the replay buffer — is §13's trace step 4 versus 5. Pulsing the request loses it under stall. Withdrawal on policy change races the acceptor. And treating a refusal as an error escalates a normal outcome.
15. RTL — PHY Power-State Command Owner
import l1_pkg::*;
// SYNTHESIZABLE. One power-state transaction with the PHY macro.
// SECTION 1's L1_TO_L0 is the evidence for the exit half: "wait for PHY to
// indicate exit from L1 complete". Asking is not arriving.
//
// THIS IS THE SAME OWNERSHIP PATTERN as Chapter 18.2's detection owner,
// Chapter 18.5's rate owner and Chapter 18.7's electrical-idle owner --
// deliberately identical, because the problem is identical.
module phy_pm_owner #(
parameter int PM_W = 3
) (
input logic clk,
input logic rst_n,
input logic start,
input logic [PM_W-1:0] target, // requested PHY power state
output logic req_valid,
input logic req_ready,
output logic [PM_W-1:0] req_target,
input logic done, // CANNOT be backpressured
input logic success,
output logic result_valid,
output logic result_success,
output logic unowned_done
);
typedef enum logic [1:0] { S_IDLE, S_ISSUE, S_WAIT, S_REPORT } st_e;
st_e st_q;
logic [PM_W-1:0] tgt_q;
logic ok_q, unowned_q;
// ==================================================================
// req_valid IS TRUE ONLY IN S_ISSUE, and S_ISSUE is left on the
// HANDSHAKE. Section 20 measured the alternative over 2,396,744
// sequences: 452,512 double-accepts plus 588,768 results with no
// accepted request.
// ==================================================================
assign req_valid = (st_q == S_ISSUE);
assign req_target = tgt_q; // captured, never re-read
assign result_valid = (st_q == S_REPORT);
assign result_success = ok_q;
assign unowned_done = unowned_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= S_IDLE; tgt_q <= '0; ok_q <= 1'b0; unowned_q <= 1'b0;
end else begin
unique case (st_q)
S_IDLE : begin
if (start) begin tgt_q <= target; st_q <= S_ISSUE; end
if (done) unowned_q <= 1'b1; // reported, never consumed
end
S_ISSUE : if (req_ready) st_q <= S_WAIT;
S_WAIT : if (done) begin ok_q <= success; st_q <= S_REPORT; end
S_REPORT: st_q <= S_IDLE;
default : st_q <= S_IDLE;
endcase
end
end
endmoduleClassification: synthesizable.
Verified exhaustively (§20): 0 violations of accepts ≤ starts and results ≤ accepts over 2,396,744 sequences; the held-until-done variant violates on 1,041,280.
16. RTL — Wake Latch, L1 Controller, and Traffic Gate
import l1_pkg::*;
// SYNTHESIZABLE. Capture every wake cause; hold until the controller owns it.
// SECTION 1 sources two triggers: "a TLP is pending" and "exited due to
// link partner activity". A lost wake means a packet waits FOREVER --
// nothing at this layer times it out.
module l1_wake_latch (
input logic clk,
input logic rst_n,
input logic in_l1,
input logic pending_packet, // LEVEL, from the gate below
input logic remote_wake, // PULSE, from the PHY
input logic pm_exit_request, // LEVEL, software heading back to D0
input logic link_event, // PULSE
input logic exit_taken,
output l1_wake_t wake_reason,
output logic wake_pending
);
l1_wake_t r_q; logic p_q;
assign wake_reason = r_q; assign wake_pending = p_q;
wire now = pending_packet | remote_wake | pm_exit_request | link_event;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
r_q <= '0; p_q <= 1'b0;
end else if (exit_taken) begin
p_q <= 1'b0; // consumed exactly once; reason kept readable
end else if (in_l1 && now) begin
// ACCUMULATE, never replace -- local and remote wakes coincide often
// on a bidirectional workload (Chapter 18.7 section 16).
r_q.local_tx_pending <= r_q.local_tx_pending | pending_packet;
r_q.remote_wake <= r_q.remote_wake | remote_wake;
r_q.pm_exit_request <= r_q.pm_exit_request | pm_exit_request;
r_q.link_event <= r_q.link_event | link_event;
p_q <= 1'b1;
end
end
endmoduleimport l1_pkg::*;
// SYNTHESIZABLE. L1-only controller.
// THESE ARE IMPLEMENTATION-FACING NORMALIZED PHASES, not PCIe LTSSM
// substate encoding. Section 1 verifies L1.Idle and the exit handshake;
// it does not publish the transition criteria a normative encoding needs.
module l1_controller (
input logic clk,
input logic rst_n,
input logic entry_committed,
input logic pm_low_power_done, // PHY reached the low-power state
input logic wake_pending,
input logic pm_active_done, // section 1's "PHY exit from L1 complete"
input logic resync_done, // symbol lock, BOTH directions (section 9)
input logic link_down,
output logic in_l1,
output logic exit_taken,
output logic start_pm_low,
output logic start_pm_active,
output logic ready_for_l0
);
typedef enum logic [2:0] {
S_L0, S_ENTER_PHY, S_RESIDENT, S_EXIT_PHY, S_RESYNC, S_DONE
} st_e;
st_e st_q;
// in_l1 EXCLUDES S_DONE and S_RESYNC's completion, so the traffic gate
// cannot reopen before lock is restored in both directions.
assign in_l1 = (st_q inside {S_ENTER_PHY, S_RESIDENT,
S_EXIT_PHY, S_RESYNC});
assign exit_taken = (st_q == S_RESIDENT) && wake_pending;
assign start_pm_low = (st_q == S_ENTER_PHY);
assign start_pm_active = (st_q == S_EXIT_PHY);
assign ready_for_l0 = (st_q == S_DONE);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) st_q <= S_L0;
else if (link_down) st_q <= S_L0; // dominates everything
else begin
unique case (st_q)
S_L0 : if (entry_committed) st_q <= S_ENTER_PHY;
// A wake during entry is honoured only AFTER the PHY command
// completes -- interrupting it leaves the macro state unknown.
S_ENTER_PHY : if (pm_low_power_done) st_q <= S_RESIDENT;
S_RESIDENT : if (wake_pending) st_q <= S_EXIT_PHY;
S_EXIT_PHY : if (pm_active_done) st_q <= S_RESYNC;
// BOTH DIRECTIONS must re-lock (section 9). Returning to L0 on
// pm_active_done alone is mutation 10.
S_RESYNC : if (resync_done) st_q <= S_DONE;
S_DONE : st_q <= S_L0;
default : st_q <= S_L0;
endcase
end
end
endmodule// SYNTHESIZABLE. The same gate as Chapters 18.6 and 18.7, sixth instance.
// A PACKET ARRIVING IN L1 MUST REMAIN UPSTREAM-OWNED. Section 20
// enumerated it: 1 of 8 state/valid/ready combinations destroys the packet
// when up_ready is ungated -- and in L1 the window is the longest yet,
// because L1 residency is measured in microseconds.
module l1_traffic_gate #(parameter int W = 256) (
input logic link_operational,
input logic up_valid,
input logic [W-1:0] up_payload,
output logic up_ready,
output logic down_valid,
output logic [W-1:0] down_payload,
input logic down_ready,
output logic pending_packet
);
assign down_valid = up_valid && link_operational;
assign up_ready = down_ready && link_operational;
assign down_payload = up_payload;
// An OUTPUT observation. Nothing flows back into up_ready -- no
// combinational loop, and no path by which wanting to wake could
// cause the packet to be accepted.
assign pending_packet = up_valid && !link_operational;
endmoduleClassification: all three synthesizable.
Failure — six across the group. Not latching a pulsed remote_wake — a packet waits forever. Replacing rather than accumulating the wake cause misattributes coincident wakes. Returning to L0 on pm_active_done alone skips the both-direction resync (mutation 10). Including S_DONE in in_l1's complement too early reopens the gate before lock. up_ready = down_ready destroys the packet. And feeding wake_pending into up_ready closes a combinational loop.
17. RTL — Diagnostics
import l1_pkg::*;
// SYNTHESIZABLE. Silicon-debug instrumentation.
// SATURATING, and driven by EDGES -- Chapter 18.6 section 15's rule. A
// counter driven by the level reports residency cycles as entries, which
// is one of the most common metrics bugs in any state machine.
module l1_diag #(parameter int CYC_W = 32, parameter int EVT_W = 16) (
input logic clk,
input logic rst_n,
input logic in_l1,
input logic l1_entry_pulse,
input logic l1_exit_pulse,
input logic exit_failed,
input l1_entry_reason_e entry_reason,
input l1_wake_t wake_reason,
input logic refused_pulse,
input logic clear,
output logic [EVT_W-1:0] l1_entry_count,
output logic [EVT_W-1:0] l1_exit_count,
output logic [EVT_W-1:0] l1_refused_count,
output logic [EVT_W-1:0] l1_exit_fail_count,
output logic [CYC_W-1:0] l1_cycles,
output l1_entry_reason_e last_entry_reason,
output l1_wake_t last_wake_reason
);
logic [EVT_W-1:0] ent_q, exi_q, ref_q, fail_q;
logic [CYC_W-1:0] cyc_q;
l1_entry_reason_e er_q;
l1_wake_t wr_q;
assign {l1_entry_count, l1_exit_count} = {ent_q, exi_q};
assign {l1_refused_count, l1_exit_fail_count} = {ref_q, fail_q};
assign l1_cycles = cyc_q;
assign last_entry_reason = er_q;
assign last_wake_reason = wr_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
ent_q <= '0; exi_q <= '0; ref_q <= '0; fail_q <= '0; cyc_q <= '0;
er_q <= L1R_NONE; wr_q <= '0;
end else begin
if (in_l1 && !(&cyc_q)) cyc_q <= cyc_q + CYC_W'(1);
if (l1_entry_pulse && !(&ent_q)) begin
ent_q <= ent_q + EVT_W'(1);
er_q <= entry_reason; // WHY this episode began (section 6)
end
if (l1_exit_pulse && !(&exi_q)) begin
exi_q <= exi_q + EVT_W'(1);
wr_q <= wake_reason;
end
// A REFUSAL IS COUNTED, NOT ESCALATED (section 8).
if (refused_pulse && !(&ref_q)) ref_q <= ref_q + EVT_W'(1);
if (exit_failed && !(&fail_q)) fail_q <= fail_q + EVT_W'(1);
end
end
endmoduleClassification: synthesizable (instrumentation).
l1_refused_count is the one that pays for itself. §22's first scenario — a Link that never enters L1 — is answered instantly by whether requests are being made and refused (the partner is busy) or never made at all (the drain never completes, or policy is off). Without it, the two look identical from outside.
18. Assertions
// SVA over the L1 blocks. LOCAL contract only. Nothing asserts that L1 is
// ever entered, that a wake succeeds, or that the partner cooperates.
// ---- ENVIRONMENT ------------------------------------------------------
assume property (@(posedge clk) disable iff (!rst_n)
(up_valid && !up_ready) |=> (up_valid && $stable(up_payload)));
assume property (@(posedge clk) disable iff (!rst_n) remote_wake |=> !remote_wake);
// ---- ENTRY, AND THE DRAIN ---------------------------------------------
// P1: THE REQUEST IS NEVER ISSUED BEFORE THE DRAIN COMPLETES. Section 7's
// precondition, and the property that keeps L1 compatible with Chapter
// 15.1's replay machinery.
property p_no_request_before_drain;
@(posedge clk) disable iff (!rst_n)
$rose(entry_req_valid) |-> (tx_blocked && no_inflight_tlps && replay_empty);
endproperty
a_drain : assert property (p_no_request_before_drain);
// P1b: specifically, the replay buffer. Stated separately because waiting
// only for the transmit path to idle is the plausible wrong version.
property p_replay_empty;
@(posedge clk) disable iff (!rst_n)
entry_req_valid |-> replay_empty;
endproperty
a_replay : assert property (p_replay_empty);
// P2: request held under stall; P3: reason stable throughout the episode.
property p_held;
@(posedge clk) disable iff (!rst_n)
(entry_req_valid && !entry_req_ready && !abort && !entry_refused)
|=> entry_req_valid;
endproperty
a_held : assert property (p_held);
property p_reason_stable;
@(posedge clk) disable iff (!rst_n)
(entry_req_valid || entry_committed) |=> $stable(entry_reason)
|| $past(abort) || $past(entry_refused);
endproperty
a_reason : assert property (p_reason_stable);
// P4: PHY PM target stable under stall.
property p_target_stable;
@(posedge clk) disable iff (!rst_n)
(req_valid && !req_ready) |=> (req_valid && $stable(req_target));
endproperty
a_target : assert property (p_target_stable);
// P5: ONE START, AT MOST ONE ACCEPTANCE.
property p_single_issue;
@(posedge clk) disable iff (!rst_n)
(req_valid && req_ready) |=> !req_valid until_with start;
endproperty
a_single : assert property (p_single_issue);
// P6: an unowned PM completion cannot advance state.
property p_unowned;
@(posedge clk) disable iff (!rst_n)
(done && (st_q == S_IDLE)) |=> (unowned_done && !result_valid);
endproperty
a_unowned : assert property (p_unowned);
// ---- PACKET OWNERSHIP -- the central pair -----------------------------
// P7: no normal packet transfer in L1.
property p_no_transfer;
@(posedge clk) disable iff (!rst_n) in_l1 |-> !(up_valid && up_ready);
endproperty
a_noxfer : assert property (p_no_transfer);
// P8: a packet valid in L1 stays upstream-owned.
property p_survives;
@(posedge clk) disable iff (!rst_n)
(up_valid && in_l1) |=> (up_valid || $past(up_ready));
endproperty
a_survive : assert property (p_survives);
// P9: no loss, stated symmetrically.
property p_symmetric;
@(posedge clk) disable iff (!rst_n)
(up_valid && up_ready) <-> (down_valid && down_ready);
endproperty
a_sym : assert property (p_symmetric);
// P10: and it raises a wake. SEPARATE from P7 -- wanting to wake is not
// accepting the packet.
property p_wakes;
@(posedge clk) disable iff (!rst_n)
(up_valid && in_l1) |-> pending_packet;
endproperty
a_wake : assert property (p_wakes);
// ---- WAKE PRESERVATION ------------------------------------------------
// P11: a wake cannot disappear before the controller owns it.
property p_wake_held;
@(posedge clk) disable iff (!rst_n)
(wake_pending && !exit_taken) |=> wake_pending;
endproperty
a_wheld : assert property (p_wake_held);
// P12: consumed exactly once; P13: cause accumulates, never substitutes.
property p_wake_once;
@(posedge clk) disable iff (!rst_n)
(wake_pending && exit_taken) |=> !wake_pending;
endproperty
a_wonce : assert property (p_wake_once);
property p_wake_monotonic;
@(posedge clk) disable iff (!rst_n)
(wake_pending && !exit_taken)
|=> ((wake_reason & $past(wake_reason)) == $past(wake_reason));
endproperty
a_wmono : assert property (p_wake_monotonic);
// P14: STALE WAKE FROM A PRIOR EPISODE IS CLEARED. A wake left set would
// make the next L1 entry exit immediately (mutation 17).
property p_no_stale_wake;
@(posedge clk) disable iff (!rst_n)
$rose(in_l1) |-> !$past(wake_pending);
endproperty
a_stale : assert property (p_no_stale_wake);
// ---- EXIT AND STATE INTEGRITY -----------------------------------------
// P15: BOTH-DIRECTION RESYNC IS REQUIRED before returning to L0.
property p_resync_required;
@(posedge clk) disable iff (!rst_n)
$rose(ready_for_l0) |-> $past(resync_done);
endproperty
a_resync : assert property (p_resync_required);
// P16: L1 never reports normal traffic enabled.
property p_not_operational;
@(posedge clk) disable iff (!rst_n) in_l1 |-> !link_operational;
endproperty
a_notop : assert property (p_not_operational);
// P17: THE COMMITTED CONFIGURATION SURVIVES AN L1 EPISODE UNCHANGED, and
// cannot partially mutate. Section 9's teaching contract.
property p_config_intact;
@(posedge clk) disable iff (!rst_n)
(in_l1 && !link_down) |=> ($stable(active_speed) && $stable(active_width)
&& $stable(active_lane_map));
endproperty
a_cfg : assert property (p_config_intact);
// P18: L1 and L0s are mutually exclusive in the normalized local model.
property p_not_both;
@(posedge clk) disable iff (!rst_n) !(in_l1 && dut_l0s.in_l0s);
endproperty
a_excl : assert property (p_not_both);
// P19: link-down dominates a completed exit.
property p_linkdown;
@(posedge clk) disable iff (!rst_n)
link_down |=> (!in_l1 && !entry_req_valid);
endproperty
a_down : assert property (p_linkdown);
// P20: entry counted once per entry, not per residency cycle.
property p_entry_count;
@(posedge clk) disable iff (!rst_n)
(l1_entry_count > $past(l1_entry_count)) |-> $past(l1_entry_pulse);
endproperty
a_cnt : assert property (p_entry_count);
// P21: reset.
property p_reset;
@(posedge clk) !rst_n |=> (!in_l1 && !wake_pending && !entry_req_valid);
endproperty
a_reset : assert property (p_reset);P1 and P1b are this chapter's distinctive properties. No other LTSSM state has a precondition that reaches into the Data Link Layer, and P1b is stated separately from P1 on purpose: a design that waited only for the transmit path to idle satisfies the intuitive version and still enters L1 with unacknowledged TLPs outstanding.
P14 is the one that bites in a lab. A stale wake makes the next L1 entry exit immediately — so the Link appears to enter and leave L1 constantly, which reads as a policy problem rather than a latch bug.
No liveness. "L1 is eventually entered" is false whenever the partner refuses (§8); "a wake eventually succeeds" depends on the PHY.
19. Verification, Fault Injection, and Model Verification
Executed before publication.
The PHY PM command owner — exhaustive
All (start, req_ready, done) sequences of length ≤ 7 — 2,396,744 sequences:
| Implementation | Violations of accepts ≤ starts and results ≤ accepts |
|---|---|
§15 as written (leave S_ISSUE on the handshake) | 0 |
req_valid held until done | 1,041,280 — split into 452,512 double-accepts and 588,768 phantom results |
Smallest double-accept witness: (1,0,0) (0,1,0) (0,1,0) — one start, two accepted power-state changes. The PHY is commanded twice for one request, and in a power-state context the second command can arrive while the first transition is still in progress.
The traffic gate over {L0, L1} — exhaustive
| Implementation | Asymmetric-transfer cases |
|---|---|
| §16 as written | 0 of 8 |
up_ready = down_ready | 1 of 8 — state = L1, up_valid = 1, down_ready = 1 |
The wake latch — exhaustive
All 15 non-empty cause masks × 6 controller-stall depths: 0 causes lost or misrecorded.
Directed tests
- ASPM entry and software-directed entry — verify the reason is captured and distinguishes them (§14).
- Entry attempted with a non-empty replay buffer — verify no request is issued (P1b). Required, and the test that distinguishes this chapter from every other power state.
- Replay buffer drains after the transmit path idles — §13's steps 4–5; verify the request waits for the later condition.
- Entry refused — verify it is recorded, the request drops, and nothing escalates (§8).
- PHY PM command stalls for 1, 2 and 50 cycles — verify hold and target stability (P4).
donebefore anystart— verifyunowned_done(P6).- Stable L1 residency with no traffic (P7, P16).
- Local packet arrives in L1 — verify
up_readylow and wake raised (P7, P10). Required pair. - Remote wake pulse while the controller is busy — verify it is latched (P11).
- Local and remote wake together — verify both recorded (P13).
pm_active_donewithoutresync_done— verify the Link does not return to L0 (P15). Required.- A wake left set from a previous episode — verify it is cleared (P14).
- Link-down during each phase (P19); reset in residency (P21).
- Repeated L0 ↔ L1, and policy disabled.
The scoreboard runs its own L1-episode and wake model and never reads in_l1, wake_pending, the controller enum, or the diagnostic counters.
Mutations
| # | Mutation | Caught by | Lab symptom |
|---|---|---|---|
| 1 | L1 treated identically to L0s (receiver left active) | P15 + scoreboard | exit skips the local re-lock; first packets corrupt |
| 2 | device D1 conflated with Link L1 | scoreboard | ASPM L1 in D0 reported as a driver fault (§3) |
| 3 | packet accepted while in L1 | P7, P9 | packet destroyed; Completion times out at an innocent completer |
| 4 | wake pulse lost | P11 | a packet waits forever; nothing times it out |
| 5 | wake cause overwritten | P13 | coincident local+remote wakes misattributed |
| 6 | entry request pulsed under stall | P2 | L1 silently never entered; power saving absent |
| 7 | PM request accepted twice | P5 | PHY commanded mid-transition (measured above) |
| 8 | PM target re-read under stall | P4 | PHY sent to a power state nobody asked for |
| 9 | stale done advances a new episode | P6 | previous transition's completion attributed to this one |
| 10 | return to L0 on pm_active_done alone | P15 | traffic before both directions re-lock |
| 11 | entry requested before the replay buffer drains | P1b | replay timer expires against a sleeping Link; retransmission into a quiet PHY |
| 12 | L1.2 behaviour assumed for every L1 | review + P17 | design depends on substates the platform may not implement (§5) |
| 13 | Electrical Idle used directly as the L1 state | P16, P18 | state follows a PHY output; fires in Recovery and L0s too (§4) |
| 14 | entry counter driven by the level | P20 | residency cycles reported as entries |
| 15 | reset loses priority to a successful exit | P21 | L1 state survives reset |
| 16 | a refusal treated as an error and escalated | diagnostics | normal Root Port behaviour reported as a fault (§8) |
| 17 | stale wake not cleared between episodes | P14 | every L1 entry exits immediately; reads as a policy problem |
| 18 | drain waits only for the transmit path, not the replay buffer | P1b | intermittent — fails only when acknowledgements are slow |
Same-cycle audit
| Case | Declared resolution |
|---|---|
| entry accepted + local packet arrives | entry proceeds; the packet is not accepted and raises a wake immediately (P7, P10) |
| wake + PHY low-power completion | completion first — interrupting a half-issued PHY command leaves the macro state unknown (§16) |
| local + remote wake | both recorded (P13); neither preferred |
| exit done + Recovery request | Recovery wins — it is a superset of what exit was trying to achieve |
| reset + wake | reset wins (P21) |
| refusal + acceptance in the same cycle | structurally impossible: the acceptor drives one or the other |
| drain completes + abort | abort wins; no request is issued |
Declared priority: reset / link-down > exit or wake > remain low-power.
20. Debugging
Symptom → which entry path → signal → distinguishing experiment.
The Link never enters L1 despite long idle periods
Read l1_refused_count first (§17), because it splits the problem in one number.
Refusals climbing: requests are being made and the partner is saying no — §1 shows a Root Port rejects "when there are TLP or ACK/NAK DLLP pending transmit." The Endpoint is idle; the Root Port is not. Nothing is broken.
Refusals at zero and entries at zero: no request was ever made. Three candidates, checked in order: is ASPM L1 enabled and advertised? is the idle period longer than the actual idle gaps? — and the one specific to this chapter, does the drain ever complete? A Link with a persistently non-empty replay buffer never satisfies §7's precondition.
The distinguishing experiment for the last one: sample replay_empty over an idle window. If it never asserts, the drain is the blocker — and the real question moves to why acknowledgements are slow, one layer down.
The Link enters L1 but never wakes
Walk the chain; each stage has a different owner:
pending_packet / remote_wake did anything ask? -> §16 gate, PHY
wake_pending was it LATCHED? -> §16, P11
exit_taken did the controller own it? -> §16
start_pm_active was the PHY commanded? -> §15
pm_active_done did the PHY return? -> the macro
resync_done did BOTH ends re-lock? -> §9
link_operational did the gate reopen? -> §16pending_packet high with wake_pending low is mutation 4 — a lost wake, and the most dangerous failure here because nothing at this layer times it out. pm_active_done never arriving is the macro; resync_done never arriving points at the far end, since both directions must re-lock.
The first packets after L1 are corrupt
Suspect the gate opening before resync — mutation 10.
The signature: corruption only in the first packets after a wake, clean thereafter, correlated with L1 exits rather than load or channel.
The distinguishing experiment: sample link_operational against resync_done across a wake. If link_operational rose on pm_active_done, the design skipped the both-direction re-lock — and note this bug is invisible in an L0s design, because L0s only ever needed one direction (§2). A team porting L0s exit logic to L1 introduces it naturally.
Latency is unexpectedly large
Not necessarily a fault (§11). Check the advertised L1 Exit Latency against the application's tolerance — the encodings reach "More than 64 μs", and a device with 4 μs of buffering cannot absorb that.
Then check which state is actually being used. A workload with idle gaps in the microsecond range being served by L1 rather than L0s pays L1's exit cost on every gap. The fix is policy, not hardware.
And check the clock architecture — §1: "Exit latencies may be significantly increased if the… reference clocks… are common or separate."
Software reports D0 but the analyzer shows L1
Not a contradiction, and not a bug (§3). This is exactly what ASPM L1 looks like: hardware entered L1 autonomously while the Function remained in D0.
The distinguishing experiment is last_entry_reason (§17). L1R_ASPM confirms it. L1R_PM_DIRECTED with software reporting D0 would be the real contradiction — and that is a genuine bug worth chasing.
21. Common Misconceptions
- "L1 is device D1." Different axes — though a non-D0 write can cause L1 entry (§3).
- "L1 is just a deeper L0s." It is negotiated, refusable, and requires a drain (§2, §7, §8).
- "L1 is Electrical Idle." Six states in this curriculum use Electrical Idle (§4).
- "Every L1 entry uses the same DLLP flow." §1 sources two entry paths (§6).
- "L1 always means L1.2." Substates are optional and driven from above the LTSSM (§5).
- "Link context is destroyed in L1." The negotiated identity survives; symbol lock does not (§9).
- "Returning from L1 requires Detect." That is the cost L1 exists to avoid.
- "The packet that triggered the wake has been accepted." P7 and P10 are separate properties.
- "ASPM L1 means software executes each transition." Software enables; hardware acts autonomously (§6).
- "A refused L1 request is an error." §1 shows refusal is normal Root Port behaviour (§8).
- "PLL and CDR behaviour is identical across implementations." §1 says "significant portion" and no more (§9).
- "Deeper power is always better." A 32 μs exit on a device with 4 μs of buffering is a net loss (§11).
- "L1 latency does not matter to system design." It is compared against every component on the path (§11).
- "The drain is just politeness." It is what makes L1 compatible with the replay machinery (§7).
- "L1 can be entered whenever the transmit path is idle." The replay buffer must be empty (§19's counterexample).
22. Understanding Check
23. What's Next
L1 is the deep end of active-state power management.
More PHY powers down, both directions go idle, and both ends must re-establish symbol lock — which is why exit costs an order of magnitude more than L0s and why entry cannot be a local decision.
Entry is negotiated and refusable (§8), and it is gated by a precondition no other LTSSM state has: the replay buffer must be empty (§7). A power state that quiets a communication path must first settle everything depending on that path staying alive.
Chapter 18.9 — Disabled turns to a completely different reason for a Link to stop working. Not power, not failure — intent. A Link that has been deliberately taken out of service, how that intention is communicated to a partner who would otherwise read the silence as a fault, and why "stuck in Disabled" may be exactly what someone asked for.
The idea to carry forward: before quieting a path, settle everything that was counting on it.