Skip to content

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.

ConceptLayerSet byEffect
LTSSM DisabledPhysicalLTSSM, directed or observedthe Link stops participating
Function disabledConfigurationsoftwarea Function stops responding
Memory Space Enable offConfigurationsoftwareMemory Requests to its BARs not claimed
Bus Master Enable offConfigurationsoftwarethe Function may not initiate Requests
D3device powerPM softwarethe Function is in a low-power state
Driver disabled in the OSsoftwarethe operating systemnothing 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:

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

DISABLE2 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."

DirectedObserved
Origina local register writereceived TS ordered sets
Evidencea software command"typically 2" consecutive TS with the bit set
Can a bit error cause it?noyes (§9)
Separately disableable?noyesltssm_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 entryDISABLE0 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.

10. The Disabled Subtree

The Disable sequence. Entry is either directed by software or observed from received training sets with the Disable Link bit set. Disable0 transmits TS1 ordered sets with the Disable Link bit asserted. Disable1 transitions to Electrical Idle. Disable2 waits to receive an Electrical Idle Ordered Set from the partner and a minimum time afterwards. Disable3 waits until a disable exit condition occurs, after which the link proceeds toward Detect.directed(registerwrite)observed(TS,qualified)DISABLE0-announceDISABLE1 - electrical idleDISABLE1-electri…DISABLE2 - await partner idleDISABLE2- awaitpartner…DISABLE3-residenttowardDetectrequest acceptedrequest accepted2 consecutive TS2 consecutive TS16 to 32 TS sent16 to 32 TS senttransmitter idletransmitter idlepartner idle + min timepartner idle + min timepartneridle + min…exit conditionexitcondition
Figure 1 — the Disable sequence, following the substates verified in §1. The announcement is transmitted before the transmitter goes idle, and the partner's own announced silence is awaited as confirmation before the Link settles into residency. Entry may be directed locally or observed from received training sets, and only the observed path is exposed to the bit-error hazard of §9. Exit conditions are not enumerated in this chapter's evidence.

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

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

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

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

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

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

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

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

ImplementationResult
count += (valid && ready)0 disagreements with the independent transfer model
count += valid5,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 THRESH consecutive qualifying observations — verify it fires on the THRESH-th, not earlier.
  • THRESH − 1 consecutive, then a non-disable observation, then more — verify no entry (the §9 defence). Required.
  • rx_ts_entry_enabled low 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_seen without min_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

#MutationCaught byLab symptom
1directed request pulse wired straight to entryP1software writes Link Disable and nothing happens
2count increments on valid instead of fireP3announcement completes with nothing sent; partner sees unexplained silence (§16)
3Disable Link bit omitted from the descriptorscoreboardpartner receives ordinary TS1; reads the silence as failure (§4)
4descriptor fields change under stallP4a partially-announced instruction
5normal packet accepted during DisabledP7, P8packet destroyed after the Link was told to stop
6electrical-idle request pulsed and lostP5transmitter never goes idle; Disabled entered while still driving
7residency entered without waiting for the partnerP6one end disabled, the other still training (§5)
8link_usable stays high during the sequenceP9traffic counter increments while Disabled
9exit reuses stale configuration or qualification history18.10's P12post-exit bring-up qualifies on pre-disable evidence (§11)
10entry counter driven by the levelP11unbounded residency saturates the entry count (§14)
11observed path counted cumulatively, not consecutivelyP2a noisy channel eventually disables the Link (§9)
12rx_ts_entry_enabled ignoredP2§1's own bit-error mitigation silently removed
13directed and observed paths mergedP1, P2local command gains a qualification it does not need; remote evidence gains trust it has not earned (§7)
14counter off-by-one (>= REQUIRED-1)P3one fewer announcement than intended; invisible at REQUIRED = 1
15reset leaves a pending disable requestP12the Link disables itself after a reset

Same-cycle audit

CaseDeclared resolution
disable request + a packet mid-transferthe gate is combinational off link_usable; the request is accepted and no transfer occurs that cycle (P7)
final announcement transfer + resetreset wins (P12)
idle_cmd_complete + exit_conditionthe sequence continues — an exit condition during entry is not a shortcut; residency must be reached first
directed request + observed disable togetherboth recorded; entry happens once (P10)
disable request + Hot ResetHot Reset wins, by declared local priority (§18)
partner_eios_seen + min_idle_met togetherresidency entered — both conditions are satisfied (P6)
exit + a new disable requestexit 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.

last_disable_reasonReading
sw_link_disablesomeone wrote Link Disable. Working as instructed — find who.
policy_disablea platform or management policy. Also intentional.
remote_observedthe 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.

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." DISABLE0 transmits 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." DISABLE2 waits 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.