PCIe · Module 18
Disabled — A Link That Was Told to Stop
Disabled is not a Link that failed. It is a Link that something deliberately took out of service — which means the partner has to be told, because silence alone is indistinguishable from a broken channel.
Every Module 18 chapter so far has described a Link trying to work: establishing itself, operating, repairing, or saving power between bursts of work.
Disabled is the one state where the Link is not trying.
Something — software, a management controller, a platform policy — decided this Link should stop participating. And that creates a problem the other states never face: the partner is still there, still trained, still expecting traffic. If this end simply went quiet, the partner would read the silence exactly as it reads any other unexplained silence — as a failure, and would begin trying to recover a Link that is working perfectly and has been told to stop.
What does it mean for a Link to be deliberately disabled rather than merely down, idle or untrained; how is that intention communicated; and how does RTL ensure nothing continues pretending the Link is operational?
1. The Verified Substates
2. Disabled Is Not a Failure
3. Disabled Is Not a Disabled Device
Six different things share the word, and they live at five different layers.
| Concept | Layer | Set by | Effect |
|---|---|---|---|
| LTSSM Disabled | Physical | LTSSM, directed or observed | the Link stops participating |
| Function disabled | Configuration | software | a Function stops responding |
| Memory Space Enable off | Configuration | software | Memory Requests to its BARs not claimed |
| Bus Master Enable off | Configuration | software | the Function may not initiate Requests |
| D3 | device power | PM software | the Function is in a low-power state |
| Driver disabled in the OS | software | the operating system | nothing in PCIe changes at all |
Only the first one is an LTSSM state, and it is the only one that stops the Link.
The others all assume a working Link. Clearing Bus Master Enable stops a Function from issuing Requests — over a Link that is in L0 and perfectly operational. Disabling a driver changes nothing on the wire whatsoever.
The failure mode this table prevents is a specific and common one: a device that "disappeared" after a configuration write. If the Link is in L0, the Link is fine and the change was above it. If the Link is in Disabled, something took the Link itself out of service. The LTSSM state distinguishes them in one reading — and they lead to investigations in different modules.
4. Why the Partner Must Be Told
5. Disabling Is Bilateral
§1's DISABLE2 is the half that gets overlooked: "Wait to receive an Electrical Idle Ordered Set and min time of TX_IDLE_MIN afterwards."
So the sequence is not announce-and-leave. It is:
DISABLE0 announce transmit TS1 with the Disable Link bit
DISABLE1 go quiet transition to Electrical Idle
DISABLE2 confirm WAIT for the partner's Electrical Idle Ordered Set,
then a minimum time afterwards
DISABLE3 remain until a valid exit conditionDISABLE2 is an acknowledgement in the only form available. There is no ACK for an ordered set (Chapter 18.3 §6) — the reliability layer needs a working Link, which is exactly what is being dismantled. So the partner acknowledges by doing the thing it was told about: announcing its own silence.
And the minimum time afterwards matters for the same reason Chapter 18.6 §1's L0_TX_IDLE_MIN does — an electrical condition needs to be held long enough to be unambiguous, not merely entered.
The design consequence for §12's controller: a design that reached DISABLE3 immediately after going idle would be claiming a bilateral agreement it never confirmed. Mutation 7 is that design, and its symptom is one end disabled and the other still training.
6. Sixteen to Thirty-Two
§1's DISABLE0 gives a range, not a number: "Transmit 16 to 32 TS1 Ordered Sets w/ Disable Link bit asserted."
The range is the interesting part, and it is characteristic of announcement mechanisms.
A lower bound exists because one transmission is not enough. Bit errors happen; the partner may miss an ordered set. §1's own "typically 2 consecutive" qualification rule (§9) means the announcement must survive long enough for two consecutive good ones to be received — and sixteen gives generous margin.
An upper bound exists because this is a departure, not a conversation. The transmitter is about to go idle, and there is no benefit in extending the announcement indefinitely.
And a range exists — rather than an exact count — because nothing depends on the precise number. The partner qualifies on a small consecutive count; anything in the range satisfies it. Fixing an exact value would over-constrain implementations for no benefit.
§11's counter is written accordingly: a parameterised threshold, counted on actual transmission, and §13 measured what happens when it is counted on offers instead.
7. Two Entry Paths, Only One of Which Can Be Wrong
§1 sources both, and separates them explicitly: "Directed (requested by software) state transitions are not affected by this register; only state transitions due to Rx TS OS are affected."
| Directed | Observed | |
|---|---|---|
| Origin | a local register write | received TS ordered sets |
| Evidence | a software command | "typically 2" consecutive TS with the bit set |
| Can a bit error cause it? | no | yes (§9) |
| Separately disableable? | no | yes — ltssm_prevent_rx_ts_entry_to[0] |
| RTL ownership | §10's request latch | §11's qualifier |
These are genuinely different ownership problems, which is why §§10–11 are separate blocks. A directed request is a local command that must not be lost — a one-cycle register write that arrives while the controller is busy. An observed request is remote evidence that must be qualified before it is believed.
Merging them loses both properties: the local command inherits a qualification requirement it does not need, and the remote evidence inherits a trust it has not earned.
8. What Must Stop, and What Must Not Survive
Two invariants, and the second is the one designs get wrong.
Normal packet traffic stops. Disabled → no TLP or DLLP transfer. But the Link is not silent during entry — DISABLE0 is actively transmitting (§4), so "nothing transmits" is wrong in exactly the way Chapter 18.5 §7 and Chapter 18.7 §4 warned about. Normal protocol traffic is disabled; physical signalling continues through the entry sequence.
And the operational configuration must not remain usable. A Link in Disabled still has registers holding a width, a lane map and a speed. They describe a Link that is no longer participating.
9. A Bit Error Can Disable Your Link
10. The Disabled Subtree
Four things to read out of the figure.
Two entry arrows, one of them coloured as a hazard — the observed path is the one a bit error can trigger (§9).
The announcement precedes the silence. DISABLE0 before DISABLE1, for §4's reason.
DISABLE2 is a wait on the partner — the only phase whose completion this end does not control.
And DISABLE3 is drawn as a terminal-looking state on purpose. It is "wait until a Disable exit condition occurs" — residency with no timeout, which is what distinguishes a destination from a transient.
11. Exit
§1 is deliberately unspecific and this chapter follows it: DISABLE3 is "Wait until a Disable exit condition occurs." The conditions are not enumerated in this evidence, so they are not published here.
What can be said architecturally is what an exit must not do.
It cannot simply reopen traffic. Both ends have been in electrical idle; symbol lock is gone; nothing about the previous configuration has been reconfirmed. Leaving Disabled is a bring-up, not a resumption — and §10's figure therefore shows the exit heading toward Detect rather than toward L0.
And stale state must not leak into it. Qualification history, candidate configurations and pending requests from before the disable describe a Link that has since been switched off and left idle for an unbounded time. Chapter 18.10 §13 measures exactly this class of bug — retained history producing premature qualification — and the same reasoning applies to any state re-entered after a long, deliberate gap.
12. RTL — Request Latch and Observed-Entry Qualifier
// SYNTHESIZABLE. Normalized Disable types.
// THE TWO ENTRY PATHS are sourced (section 7). The reason encoding is
// internal diagnostic metadata -- it appears on no PCIe wire.
package disable_pkg;
typedef struct packed {
logic sw_link_disable; // directed: Link Disable register write
logic policy_disable; // directed: platform/management policy
logic remote_observed; // observed: qualified TS with the Disable bit
} disable_reason_t;
function automatic bit any_reason(input disable_reason_t r); return (|r); endfunction
endpackageimport disable_pkg::*;
// SYNTHESIZABLE. Hold a DIRECTED disable request until the controller owns it.
// A CONFIGURATION WRITE IS A ONE-CYCLE PULSE. The Disabled controller may
// be busy -- finishing a previous sequence, or waiting on the ordered-set
// generator. A pulse wired straight into the entry condition is simply
// lost, and the software that wrote the register waits forever
// (section 16, mutation 1).
module disable_request_latch (
input logic clk,
input logic rst_n,
input logic sw_disable_pulse, // Link Disable register write
input logic policy_disable_pulse,
input logic request_taken, // the controller has ownership
input logic abort, // reset, or a dominating transition
output disable_reason_t reason,
output logic request_pending
);
disable_reason_t r_q;
logic p_q;
assign reason = r_q;
assign request_pending = p_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || abort) begin
r_q <= '0; p_q <= 1'b0;
end else if (request_taken) begin
// Consumed EXACTLY ONCE. The reason is NOT cleared -- it stays
// readable for diagnostics until the next request (section 14).
p_q <= 1'b0;
end else begin
// ACCUMULATE. Two sources can assert together, and a record that
// kept only one would misattribute the disable.
if (sw_disable_pulse) begin r_q.sw_link_disable <= 1'b1; p_q <= 1'b1; end
if (policy_disable_pulse) begin r_q.policy_disable <= 1'b1; p_q <= 1'b1; end
end
end
endmoduleimport disable_pkg::*;
// SYNTHESIZABLE. Qualify an OBSERVED disable instruction from the partner.
// SECTION 1 sources the mechanism -- "typically 2" consecutive TS OS with
// the control bit set -- AND the hazard: two consecutive corrupted TS can
// falsely assert it (section 9).
//
// THRESH IS PARAMETERISED AND ILLUSTRATIVE. Section 1 says "typically 2"
// for this implementation; this chapter publishes no normative count.
module disable_observed_qual #(
parameter int THRESH = 2,
parameter int CNT_W = (THRESH <= 1) ? 1 : $clog2(THRESH + 1)
) (
input logic clk,
input logic rst_n,
// ==============================================================
// SECTION 1's MITIGATION, as an input. A system that never needs a
// partner to disable its Link refuses the instruction entirely --
// and the DIRECTED path is unaffected, exactly as the source states.
// ==============================================================
input logic rx_ts_entry_enabled,
input logic obs_valid, // a training observation arrived
input logic obs_disable_bit, // ...with the Disable Link bit set
input logic restart, // state entry, or lost lock
output logic [CNT_W-1:0] count,
output logic observed_disable
);
generate
if (THRESH < 1) $error("THRESH must be at least 1");
if ((1 << CNT_W) < (THRESH + 1)) $error("CNT_W too narrow for THRESH");
endgenerate
logic [CNT_W-1:0] cnt_q;
assign count = cnt_q;
// Gated by the enable, so a disabled path cannot fire at all.
assign observed_disable = rx_ts_entry_enabled && (cnt_q >= CNT_W'(THRESH));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt_q <= '0;
else if (restart) cnt_q <= '0;
else if (obs_valid) begin
// CONSECUTIVE, not cumulative. An observation WITHOUT the bit
// resets to zero -- which is the entire defence against scattered
// bit errors accumulating into an instruction (section 9).
if (obs_disable_bit) begin
if (cnt_q < CNT_W'(THRESH)) cnt_q <= cnt_q + CNT_W'(1);
end else begin
cnt_q <= '0;
end
end
end
endmoduleClassification: both synthesizable.
The consecutive requirement is the defence (§9). Scattered corrupted ordered sets never accumulate; only consecutive ones do. A cumulative counter would eventually disable the Link on a noisy channel — the worst possible failure mode for this particular instruction.
And the enable input is architectural, not decorative. §1's register exists because qualification alone does not reduce the probability to zero. A system that does not need remote disable turns the path off, and the directed path keeps working.
Failure — five. Pulsing the directed request into the entry condition loses it. Cumulative rather than consecutive counting lets bit errors accumulate. Merging the two paths (§7) gives the local command an unnecessary qualification and the remote evidence unearned trust. Omitting the enable removes §1's own mitigation. And clearing the reason on request_taken destroys the record before anyone reads it.
13. RTL — Ordered-Set Send Command and Bounded Counter
// SYNTHESIZABLE. Command the ordered-set generator, and count what was
// ACTUALLY TRANSMITTED.
// SECTION 1's DISABLE0 is "Transmit 16 to 32 TS1 Ordered Sets w/ Disable
// Link bit asserted" -- a RANGE (section 6), so REQUIRED is parameterised
// within it rather than fixed.
module disable_os_sender #(
parameter int REQUIRED = 16,
parameter int CNT_W = (REQUIRED <= 1) ? 1 : $clog2(REQUIRED + 1)
) (
input logic clk,
input logic rst_n,
input logic start,
input logic abort,
// ---- To the ordered-set generator. Decoupled. --------------------------
output logic os_valid,
input logic os_ready,
output logic [1:0] os_kind, // normalized: TS1
output logic os_disable_bit, // the instruction being announced
output logic [CNT_W-1:0] sent_count,
output logic send_done
);
generate
if (REQUIRED < 1) $error("REQUIRED must be at least 1");
if ((1 << CNT_W) < (REQUIRED + 1)) $error("CNT_W too narrow");
endgenerate
logic [CNT_W-1:0] cnt_q;
logic busy_q;
assign sent_count = cnt_q;
assign send_done = (cnt_q >= CNT_W'(REQUIRED));
assign os_valid = busy_q && !send_done;
// DESCRIPTOR FIELDS ARE CONSTANT while valid is held. They describe the
// instruction, not the moment -- so there is nothing to become stale.
assign os_kind = 2'd1; // TS1
assign os_disable_bit = 1'b1;
// ==================================================================
// THE FIRE CONDITION. This is the whole block.
//
// os_fire = os_valid && os_ready
//
// NOT `os_valid` alone. Counting offers instead of transfers completes
// the announcement while the generator is stalled -- and section 15
// measured it: 83.6% of stall patterns complete EARLY, and at
// REQUIRED = 1 the count completes on a cycle where NOTHING WAS SENT.
// ==================================================================
wire os_fire = os_valid && os_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || abort) begin
cnt_q <= '0; busy_q <= 1'b0;
end else if (start && !busy_q) begin
cnt_q <= '0; busy_q <= 1'b1;
end else if (busy_q) begin
// SATURATES at REQUIRED -- and os_valid drops at send_done, so no
// further transfers are offered. The upper end of section 1's
// range is respected by construction.
if (os_fire && (cnt_q < CNT_W'(REQUIRED))) cnt_q <= cnt_q + CNT_W'(1);
if (send_done) busy_q <= 1'b0;
end
end
endmoduleClassification: synthesizable.
Verified exhaustively (§16): over 6,144 (stall pattern, threshold) cases — every stall pattern of length 10 against thresholds 1 through 6 — counting on fire agreed with an independent transfer model in every case. Counting on valid completed early in 83.6% of them.
Failure — four. Counting valid (§16's counterexample). Not saturating, so os_valid never drops and the announcement runs past §1's upper bound. Descriptor fields that vary while valid is held. And REQUIRED = 0, caught at elaboration.
14. RTL — Electrical-Idle Owner, Controller, and Diagnostics
import disable_pkg::*;
// SYNTHESIZABLE. Disabled-only controller.
// PHASES MAP ONTO SECTION 1's VERIFIED SUBSTATES and are named for them.
// This is not the LTSSM.
module disabled_controller (
input logic clk,
input logic rst_n,
input logic request_pending, // section 12, directed
input logic observed_disable, // section 12, qualified remote
input logic hot_reset_active, // section 17's declared priority
input logic send_done, // section 13: DISABLE0 complete
input logic idle_cmd_complete, // PHY reached electrical idle
input logic partner_eios_seen, // DISABLE2: the partner announced too
input logic min_idle_met, // ...and held it for TX_IDLE_MIN
input logic exit_condition, // section 11: not enumerated here
output logic request_taken,
output logic start_send,
output logic start_idle,
output logic in_disabled,
output logic disabled_resident,
output logic link_usable // section 8's contract
);
typedef enum logic [2:0] {
S_IDLE, S_D0_ANNOUNCE, S_D1_GO_IDLE, S_D2_AWAIT_PARTNER, S_D3_RESIDENT
} st_e;
st_e st_q;
assign request_taken = (st_q == S_IDLE) && (request_pending || observed_disable)
&& !hot_reset_active;
assign start_send = (st_q == S_D0_ANNOUNCE);
assign start_idle = (st_q == S_D1_GO_IDLE);
assign in_disabled = (st_q != S_IDLE);
assign disabled_resident = (st_q == S_D3_RESIDENT);
// ==================================================================
// SECTION 8's CONTRACT, AND IT IS ONE ASSIGNMENT.
//
// The configuration stops being USABLE the moment the sequence begins
// -- not when the announcement finishes, and not at D3. Consumers act
// on this continuously (Chapter 17.1 section 10), so a configuration
// that reads usable during the disable sequence is an instruction to
// transmit (section 16, mutation 8).
//
// Note this says nothing about whether the stored VALUES are cleared
// (section 8) -- only that nothing above may act on them.
// ==================================================================
assign link_usable = (st_q == S_IDLE);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) st_q <= S_IDLE;
else if (hot_reset_active) begin
// DECLARED PRIORITY, and section 17 explains why it is declared
// rather than claimed as normative.
st_q <= S_IDLE;
end else begin
unique case (st_q)
S_IDLE :
if (request_pending || observed_disable) st_q <= S_D0_ANNOUNCE;
// ---- DISABLE0: tell the partner FIRST (section 4) --------------
S_D0_ANNOUNCE : if (send_done) st_q <= S_D1_GO_IDLE;
// ---- DISABLE1: transition to Electrical Idle ------------------
S_D1_GO_IDLE : if (idle_cmd_complete) st_q <= S_D2_AWAIT_PARTNER;
// ---- DISABLE2: the partner confirms by going quiet too --------
// BOTH conditions. Seeing the EIOS is not enough; section 1
// requires a minimum time afterwards (section 5).
S_D2_AWAIT_PARTNER: if (partner_eios_seen && min_idle_met)
st_q <= S_D3_RESIDENT;
// ---- DISABLE3: residency, with NO TIMEOUT (section 10) --------
S_D3_RESIDENT : if (exit_condition) st_q <= S_IDLE;
default : st_q <= S_IDLE;
endcase
end
end
endmoduleimport disable_pkg::*;
// SYNTHESIZABLE. Lab diagnostics. Saturating, edge-driven.
module disabled_diag #(parameter int CYC_W = 32, parameter int EVT_W = 16) (
input logic clk,
input logic rst_n,
input logic in_disabled,
input logic entry_pulse,
input logic exit_pulse,
input disable_reason_t reason,
input logic clear,
output logic [EVT_W-1:0] disabled_entry_count,
output logic [EVT_W-1:0] disabled_exit_count,
output logic [CYC_W-1:0] cycles_disabled,
output disable_reason_t last_disable_reason
);
logic [EVT_W-1:0] e_q, x_q; logic [CYC_W-1:0] c_q; disable_reason_t r_q;
assign {disabled_entry_count, disabled_exit_count} = {e_q, x_q};
assign cycles_disabled = c_q; assign last_disable_reason = r_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin e_q<='0; x_q<='0; c_q<='0; r_q<='0; end
else begin
if (in_disabled && !(&c_q)) c_q <= c_q + CYC_W'(1);
// EDGE-driven (Chapter 18.6 section 15). A level would report
// residency cycles as entries -- and Disabled residency is
// unbounded, so the number would be spectacularly wrong.
if (entry_pulse && !(&e_q)) begin e_q <= e_q + EVT_W'(1); r_q <= reason; end
if (exit_pulse && !(&x_q)) x_q <= x_q + EVT_W'(1);
end
end
endmoduleClassification: both synthesizable.
last_disable_reason is the field §19's first question depends on. "Was it disabled on purpose, and by whom" is answered by sw_link_disable versus remote_observed — and the second, on a Link nobody intended to disable, is §9's bit-error hazard caught in the act.
The edge-driven entry counter matters more here than elsewhere, because Disabled residency has no timeout (§10). A level-driven counter on a Link disabled for an hour reports a saturated entry count.
15. Assertions
// SVA over the Disabled blocks. LOCAL contract only. Nothing asserts that
// Disabled is ever exited -- section 11 shows the exit conditions are not
// enumerated in this chapter's evidence, and DISABLE3 has no timeout.
// ---- 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)
sw_disable_pulse |=> !sw_disable_pulse);
// ---- REQUEST OWNERSHIP ------------------------------------------------
// P1: A DIRECTED DISABLE REQUEST IS NEVER LOST. A one-cycle register write
// arriving while the controller is busy must survive (section 12).
property p_request_held;
@(posedge clk) disable iff (!rst_n)
(request_pending && !request_taken && !abort) |=> request_pending;
endproperty
a_held : assert property (p_request_held);
// P1b: and it is consumed EXACTLY ONCE.
property p_request_once;
@(posedge clk) disable iff (!rst_n)
(request_pending && request_taken) |=> !request_pending;
endproperty
a_once : assert property (p_request_once);
// P2: THE OBSERVED PATH REQUIRES CONSECUTIVE EVIDENCE, and is gated by
// section 1's mitigation. This is the defence against a noisy channel
// accumulating scattered bit errors into a disable instruction (section 9).
property p_observed_needs_consecutive;
@(posedge clk) disable iff (!rst_n)
observed_disable |-> (rx_ts_entry_enabled && (count >= CNT_W'(THRESH)));
endproperty
a_obs : assert property (p_observed_needs_consecutive);
// P2b: a non-matching observation resets the count to ZERO, not down by one.
property p_reset_to_zero;
@(posedge clk) disable iff (!rst_n)
(obs_valid && !obs_disable_bit) |=> (count == '0);
endproperty
a_zero : assert property (p_reset_to_zero);
// ---- THE ANNOUNCEMENT -------------------------------------------------
// P3: THE COUNT TRACKS TRANSFERS, NEVER OFFERS. Section 15 measured the
// alternative: 83.6% of stall patterns complete early, and at REQUIRED = 1
// the announcement completes having sent nothing.
property p_count_on_fire;
@(posedge clk) disable iff (!rst_n)
(sent_count > $past(sent_count)) |-> ($past(os_valid) && $past(os_ready));
endproperty
a_fire : assert property (p_count_on_fire);
// P3b: and completion requires the full required count of ACTUAL transfers.
property p_done_needs_count;
@(posedge clk) disable iff (!rst_n)
send_done |-> (sent_count >= CNT_W'(REQUIRED));
endproperty
a_done : assert property (p_done_needs_count);
// P4: the descriptor is stable while the generator stalls. A partially
// announced instruction is not an instruction.
property p_descriptor_stable;
@(posedge clk) disable iff (!rst_n)
(os_valid && !os_ready) |=> (os_valid && $stable(os_kind)
&& $stable(os_disable_bit));
endproperty
a_desc : assert property (p_descriptor_stable);
// P4b: and the Disable Link bit is actually set. Omitting it makes the
// announcement indistinguishable from ordinary training (section 4).
property p_disable_bit_set;
@(posedge clk) disable iff (!rst_n) os_valid |-> os_disable_bit;
endproperty
a_bit : assert property (p_disable_bit_set);
// P5: the electrical-idle command is held under stall.
property p_idle_held;
@(posedge clk) disable iff (!rst_n)
(idle_req_valid && !idle_req_ready) |=> idle_req_valid;
endproperty
a_idle : assert property (p_idle_held);
// ---- SEQUENCE ORDERING ------------------------------------------------
// P6: RESIDENCY REQUIRES THE PARTNER'S CONFIRMATION -- both conditions.
// Section 1's DISABLE2 waits for an Electrical Idle Ordered Set AND a
// minimum time afterwards (section 5).
property p_residency_needs_partner;
@(posedge clk) disable iff (!rst_n)
$rose(disabled_resident) |-> ($past(partner_eios_seen) && $past(min_idle_met));
endproperty
a_partner : assert property (p_residency_needs_partner);
// P6b: THE ANNOUNCEMENT PRECEDES THE SILENCE. Going idle before the
// instruction is sent is section 4's whole failure mode.
property p_announce_before_idle;
@(posedge clk) disable iff (!rst_n)
$rose(start_idle) |-> $past(send_done);
endproperty
a_order : assert property (p_announce_before_idle);
// ---- TRAFFIC AND CONFIGURATION ----------------------------------------
// P7: NO NORMAL PACKET TRANSFER WHILE DISABLED. Note this says nothing
// about the transmitter being silent -- DISABLE0 is transmitting (section 8).
property p_no_transfer;
@(posedge clk) disable iff (!rst_n)
in_disabled |-> !(up_valid && up_ready);
endproperty
a_noxfer : assert property (p_no_transfer);
// P8: a packet offered during the sequence remains UPSTREAM-OWNED.
property p_packet_survives;
@(posedge clk) disable iff (!rst_n)
(up_valid && in_disabled) |=> (up_valid || $past(up_ready));
endproperty
a_survive : assert property (p_packet_survives);
// P8b: no loss, stated symmetrically so gating one direction cannot
// satisfy it (Chapter 18.6 P3).
property p_symmetric;
@(posedge clk) disable iff (!rst_n)
(up_valid && up_ready) <-> (down_valid && down_ready);
endproperty
a_sym : assert property (p_symmetric);
// P9: THE OPERATIONAL CONFIGURATION IS NOT USABLE FROM THE MOMENT THE
// SEQUENCE BEGINS. Section 8's declared contract -- and note it asserts
// USABILITY, not that the stored values were cleared.
property p_not_usable;
@(posedge clk) disable iff (!rst_n) in_disabled |-> !link_usable;
endproperty
a_usable : assert property (p_not_usable);
// P9b: and nothing downstream acts on a configuration that is not usable.
property p_consumer_gated;
@(posedge clk) disable iff (!rst_n)
!link_usable |-> (dut_distributor.active_mask == '0);
endproperty
a_consumer : assert property (p_consumer_gated);
// ---- DIAGNOSTICS AND RESET --------------------------------------------
// P10: ONE ENTRY COUNTED PER ENTRY, not per residency cycle. Disabled
// residency is UNBOUNDED (section 10), so a level-driven counter
// saturates almost immediately.
property p_entry_once;
@(posedge clk) disable iff (!rst_n)
(disabled_entry_count > $past(disabled_entry_count)) |-> $past(entry_pulse);
endproperty
a_cnt : assert property (p_entry_once);
// P11: the same for exits, keeping the pair within one of each other.
property p_counter_invariant;
@(posedge clk) disable iff (!rst_n)
(disabled_entry_count >= disabled_exit_count)
&& ((disabled_entry_count - disabled_exit_count) <= EVT_W'(1));
endproperty
a_inv : assert property (p_counter_invariant);
// P12: RESET CLEARS A PENDING DISABLE REQUEST. A request surviving reset
// would disable the Link immediately after it comes up (mutation 15).
property p_reset;
@(posedge clk)
!rst_n |=> (!request_pending && !in_disabled && link_usable);
endproperty
a_reset : assert property (p_reset);P3 is the property this chapter turns on, and it is stated against $past(os_valid) && $past(os_ready) rather than against the DUT's own fire signal — so a design that miswires os_fire fails the property instead of agreeing with itself.
P6 and P6b are the sequence-ordering pair. P6b forbids going idle before the announcement is out (§4); P6 forbids claiming residency before the partner confirms (§5). Either alone permits a Link that is disabled at one end and training at the other.
And P9 asserts usability, not clearing — §8 explains why the distinction is deliberate, and P9b binds it to the consumer that would otherwise act on it.
No liveness. "Disabled is eventually exited" would be false by construction: DISABLE3 waits "until a Disable exit condition occurs" with no timeout (§10).
16. Verification, Fault Injection, and Model Verification
Executed before publication.
The ordered-set counter — exhaustive
Every stall pattern of length 10 against thresholds 1 through 6 — 6,144 cases — compared against an independent model that counts only actual transfers:
| Implementation | Result |
|---|---|
count += (valid && ready) | 0 disagreements with the independent transfer model |
count += valid | 5,136 of 6,144 (83.6%) complete earlier than the required transfers |
A worked witness: stalls on the first three cycles, REQUIRED = 3. Correct: done at cycle 5. Buggy: done at cycle 2 — while the generator has been stalled the entire time and nothing has been transmitted at all.
And the REQUIRED = 1 case is the sharpest. The buggy version completes at cycle 0 while stalled — so the announcement phase finishes having sent zero ordered sets, and the Link goes to electrical idle without ever telling the partner (§4).
Directed tests
- Directed request while the controller is busy — verify it is held (P1). Required.
- Both directed sources asserting together — verify both recorded.
- Observed entry with exactly
THRESHconsecutive qualifying observations — verify it fires on theTHRESH-th, not earlier. THRESH − 1consecutive, then a non-disable observation, then more — verify no entry (the §9 defence). Required.rx_ts_entry_enabledlow with a full qualifying sequence — verify no entry, and that the directed path still works (§1's mitigation, §7).- Ordered-set generator stalls for 1, 2 and 50 cycles — verify the count tracks transfers, not offers (P3).
REQUIRED= 1, 2, 16, 32 — the range from §1, plus the boundary.- Electrical-idle macro stalls — verify hold (P5).
partner_eios_seenwithoutmin_idle_met— verify the controller does not reach residency (P6). Required.- Packet offered during every phase — verify no transfer and no acceptance (P7, P8).
- Long residency — verify no timeout and no spurious exit (§10).
- Reset during each phase; repeated disable/enable; Hot Reset during the sequence (§18).
The scoreboard runs its own announce/idle/confirm model and its own transfer counter, and never reads the DUT's counter, state enum or link_usable.
Mutations
| # | Mutation | Caught by | Lab symptom |
|---|---|---|---|
| 1 | directed request pulse wired straight to entry | P1 | software writes Link Disable and nothing happens |
| 2 | count increments on valid instead of fire | P3 | announcement completes with nothing sent; partner sees unexplained silence (§16) |
| 3 | Disable Link bit omitted from the descriptor | scoreboard | partner receives ordinary TS1; reads the silence as failure (§4) |
| 4 | descriptor fields change under stall | P4 | a partially-announced instruction |
| 5 | normal packet accepted during Disabled | P7, P8 | packet destroyed after the Link was told to stop |
| 6 | electrical-idle request pulsed and lost | P5 | transmitter never goes idle; Disabled entered while still driving |
| 7 | residency entered without waiting for the partner | P6 | one end disabled, the other still training (§5) |
| 8 | link_usable stays high during the sequence | P9 | traffic counter increments while Disabled |
| 9 | exit reuses stale configuration or qualification history | 18.10's P12 | post-exit bring-up qualifies on pre-disable evidence (§11) |
| 10 | entry counter driven by the level | P11 | unbounded residency saturates the entry count (§14) |
| 11 | observed path counted cumulatively, not consecutively | P2 | a noisy channel eventually disables the Link (§9) |
| 12 | rx_ts_entry_enabled ignored | P2 | §1's own bit-error mitigation silently removed |
| 13 | directed and observed paths merged | P1, P2 | local command gains a qualification it does not need; remote evidence gains trust it has not earned (§7) |
| 14 | counter off-by-one (>= REQUIRED-1) | P3 | one fewer announcement than intended; invisible at REQUIRED = 1 |
| 15 | reset leaves a pending disable request | P12 | the Link disables itself after a reset |
Same-cycle audit
| Case | Declared resolution |
|---|---|
| disable request + a packet mid-transfer | the gate is combinational off link_usable; the request is accepted and no transfer occurs that cycle (P7) |
| final announcement transfer + reset | reset wins (P12) |
idle_cmd_complete + exit_condition | the sequence continues — an exit condition during entry is not a shortcut; residency must be reached first |
| directed request + observed disable together | both recorded; entry happens once (P10) |
| disable request + Hot Reset | Hot Reset wins, by declared local priority (§18) |
partner_eios_seen + min_idle_met together | residency entered — both conditions are satisfied (P6) |
| exit + a new disable request | exit completes first; the new request is held for the next episode (P1) |
17. Debugging
Symptom → intentional or not → signal → distinguishing experiment.
The first question is never "what broke." It is "was this asked for" (§2) — and last_disable_reason (§14) answers it in one read.
The Link is stuck in Disabled
last_disable_reason | Reading |
|---|---|
sw_link_disable | someone wrote Link Disable. Working as instructed — find who. |
policy_disable | a platform or management policy. Also intentional. |
remote_observed | the partner instructed it — or §9's bit-error hazard fired |
That third row is the one worth dwelling on. If no one intended to disable this Link and the reason says remote_observed, two consecutive corrupted TS ordered sets may have spelled an instruction (§9).
The distinguishing experiment: check the channel's error rate and whether the partner's software actually requested a disable. If the partner did not, and the channel is marginal, set rx_ts_entry_enabled low — §1's own mitigation — and see whether it recurs.
The driver says the device disappeared right after a Link Disable write
Expected (§3). A disabled Link makes the device unreachable by design — Configuration Space is behind a Link that is no longer participating.
What would be a bug is the device disappearing after a write that should not disable the Link — clearing Bus Master Enable, or a D-state change. Read the LTSSM state: if it is L0, the Link is fine and the change was above it (§3's table). If it is Disabled, something took the Link out of service — and the two lead to investigations in different modules.
One end is Disabled and the partner keeps training
The announcement did not arrive (§4), and there are three candidates.
Mutation 2 — the count incremented on valid, so the announcement phase completed having sent nothing (§16). Distinguished by: comparing sent_count against ordered sets actually observed on the wire.
Mutation 3 — the Disable Link bit was omitted, so the partner received ordinary TS1. Distinguished by: an analyzer capture of the TS contents.
Or the partner's rx_ts_entry_enabled is low — it received the instruction and deliberately refused it (§1). Distinguished by: reading that register at the far end. This is not a fault, and it is a configuration choice someone made on purpose.
A traffic counter increments while Disabled
Severe traffic-gate ownership bug — mutation 8.
Either link_usable did not deassert at the start of the sequence (§8), or the gate is not reading it. Both mean packets were handed to a Link that was told to stop, and — as Chapter 18.6 §19 established — a packet accepted by a gate that cannot deliver it is destroyed before the replay buffer's retention point, unrecoverable by any layer above.
The distinguishing experiment: assert P7 and P8 continuously. A single transfer with in_disabled high is the whole bug.
disabled_entry_count is enormous
Almost certainly mutation 10, not a Link that was disabled thousands of times.
Compare it against cycles_disabled (§14). If they track each other, the counter is driven by the level — and because Disabled residency is unbounded, it saturates quickly.
18. Hot Reset Versus Disable
§14's controller gives hot_reset_active priority over the disable sequence, and it is important to be precise about what that is.
It is a declared local contract, not a normative claim. §1's evidence names both states and shows both can be entered from received TS ordered sets — it does not state a priority between them, and this chapter does not invent one.
The reason for the local choice: a reset is a stronger instruction than a disable. Disabling stops participation and leaves the Link's state intact; a reset invalidates protocol state and forces re-establishment (Chapter 18.10 §7). Completing a disable sequence and then resetting would perform work that the reset immediately discards — including a bilateral confirmation handshake (§5) whose result is about to become meaningless.
A design with different requirements may declare the opposite priority. What it may not do is leave it to if ordering — §16's same-cycle audit exists so the choice is visible rather than emergent, and Chapter 18.10 §17 audits it from the other side.
19. Common Misconceptions
- "Disabled means no device is present." That is Detect finding nothing (§2).
- "Disabled means the driver was disabled." Nothing in PCIe changes when a driver is disabled (§3).
- "Disabled means D3." Device power state, different axis (§3).
- "Disabled is the same as Detect." Detect is looking for a partner; Disabled has one and is ignoring it.
- "Disabled is always caused by an error." It is an instruction, not a diagnosis (§2).
- "No signalling occurs during Disabled entry."
DISABLE0transmits 16 to 32 ordered sets (§1, §8). - "A packet may be accepted and discarded after disabling." Destroyed before the retention point, unrecoverable (§17).
- "The configuration is still operational because the registers still hold values." Retained is not usable (§8).
- "Link Disable is Transaction Layer flow control." Different layer entirely; credits are Module 16.
- "Leaving Disabled can just reopen traffic." Both ends were idle; symbol lock is gone; it is a bring-up (§11).
- "Only software can cause a disable." The partner can instruct it — and so can two bit errors (§9).
- "Qualification makes the observed path safe." It reduces the probability; §1 ships a disable for the path because it does not eliminate it (§9).
- "The announcement count must be exact." §1 gives a range, 16 to 32 (§6).
- "Announcing is enough."
DISABLE2waits for the partner's own announced silence (§5).
20. Understanding Check
21. What's Next
Disabled is the state where nothing went wrong.
It has to be announced (§4), because silence already means failure and the partner cannot tell the difference — and the announcement must go out before the transmitter goes quiet, counted on actual transfers (§16).
It is bilateral (§5): DISABLE2 waits for the partner's own announced silence, because there is no acknowledgement mechanism for an ordered set.
And it can be entered by two bit errors (§9) — which is why §1 ships a register that turns the received-instruction path off entirely, and why "was this asked for" is the first debugging question rather than the last.
Chapter 18.10 — Hot Reset closes Module 18 with the strongest instruction the Link can carry. Not "stop participating" but "reset" — in-band, without removing power — and it raises the questions this chapter deliberately left alone: what state actually resets, what survives, and why releasing a reset is not the same as being ready.
The idea to carry forward: an intentional silence must be announced while you can still speak.