Skip to content
VLSI Mentor

USB · Module 19

Suspend

USB has no idle — the host sends a frame marker every millisecond so a device can tell quiet from gone. Three milliseconds of continuous silence and it must suspend.

Chapters 19.1 and 19.2 both assumed an active bus — traffic flowing, a host issuing tokens, a device answering.

This chapter is about what happens when it stops, and it starts with a design decision that is easy to miss.

1. USB Has No Idle

The host transmits a start-of-frame every millisecond whether or not there is anything to say.

That is not wasted bandwidth. It exists so a device can distinguish two situations that are otherwise identical from the far end of a cable:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   "nothing is happening right now"     — SOFs still arriving
   "the bus is gone"                    — no SOFs at all

Without a heartbeat those are the same observation. A device with no traffic cannot tell whether the host is busy elsewhere, has crashed, has been unplugged, or has deliberately gone quiet. With one, silence becomes measurable — and once it is measurable, it can have a threshold.

The threshold is 3 ms. Three missed frames. At that point the device must begin suspending, and within 10 ms of the silence starting it must have cut its draw to a trickle.

2. The Word That Does All the Work

The rule is 3 ms of continuous idle, and "continuous" is not decoration.

The obvious implementation counts ticks where the bus was quiet. It is wrong, and it is wrong in a way no ordinary test catches:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   bus:    quiet quiet  BUSY  quiet quiet  BUSY  quiet quiet
   accumulating counter:  1     2     2      3     4     4     5     6
   correct counter:       1     2     0      1     2     0     1     2

That bus has had six quiet ticks and has never been silent for three in a row. An accumulating counter suspends the device on a busy bus — which then stops answering a host that is actively talking to it.

One line separates the two implementations:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     idle_count <= bus_activity ? 0 : idle_count + 1;           // correct
     idle_count <= bus_activity ? idle_count : idle_count + 1;  // MUT U1

Mutation U1 dies 309 825 times — the largest count in Module 19 — but only because the stimulus contains patterns with gaps in them. A realistic traffic generator produces very few, which is why §10's sweep enumerates every possible pattern in a window rather than sampling plausible ones.

3. What a Suspended Device May Draw

Microamps, not milliamps — and the exact figure depends on whether the device has been armed for remote wakeup (19.5).

suspended, not armedsuspended, armedoperating
Allowance500 µA2.5 mAup to 500 mA

An armed device is allowed more because it has more to do. It must keep enough of itself powered to notice a reason to wake — a keypress, a packet, a sensor — and a device asleep hard enough to draw 500 µA cannot notice anything.

This is the one place in USB where a capability costs standing current, and it is why enabling remote wakeup on every device in a system is not free.

4. Suspend Is Not the Absence of a Bus

A device with no VBUS is not suspended. It is off.

The distinction matters because the exits are different. A suspended device resumes on bus activity and retains its address, its configuration, and its state (19.4). A device that lost VBUS has been deconfigured (19.1 §4) and must enumerate again from nothing.

So the idle measurement only runs on a powered bus, and losing VBUS restarts it rather than continuing it — mutation U4, 63 653 errors.

5. The Sequence, Drawn

A sequence diagram with three participants: the host, the bus, and the device. The host periodically transmits a start-of-frame packet onto the bus, which the device receives; this happens every millisecond whether or not there is data to move, and its purpose is to let the device tell a quiet bus from an absent one. The host then goes quiet. The device begins measuring the silence, incrementing a counter of consecutive idle intervals. Before the counter reaches the threshold, the host transmits one more start-of-frame; the device receives it and restarts its measurement from zero rather than pausing it, which is the central rule of this chapter, because three milliseconds of continuous idle means continuous. The host then goes quiet again and stays quiet. The device counts one, two, and then three milliseconds of uninterrupted silence, at which point it enters the suspend state, cutting its current draw to five hundred microamps, or two and a half milliamps if the host had previously armed it for remote wakeup. It retains its address and its configuration throughout. Finally the host transmits again; the device observes activity on the very same interval, leaves suspend immediately, restores its full current allowance, and announces a resume.Silence, measured — and what one frame marker in the middle of it doesHostBus (D+/D−)DeviceSOF — every 1 ms,data or notactivity: thecounter restartshost goes quietcounting consecutiveidle intervalsone more SOF,mid-countRESTART — not apausequiet again, andstays quiet1 ms … 2 ms … 3 mscontinuousSUSPEND: draw → 500µA (2.5 mA if armed)traffic resumesactivity → leavessuspend at onceaddress andconfigurationretained
Figure 1 — a device entering suspend and leaving it. The shaded message is §2: a single frame marker arriving mid-count restarts the measurement rather than pausing it, so the two short silences never add up to a suspend.

The device never tells the host it has suspended. It cannot — Chapter 2.6's single-master rule holds here as everywhere. The host knows because it stopped transmitting and can count as well as the device can.

6. The Hardware, Before Any Language

One counter, and everything else derived from it.

suspended is a threshold on the counter, not a separate state bit. Deriving it means the two can never disagree, and it means activity un-suspends the device in the same tick the activity arrives — which is what 19.4 requires.

Activity restarts the count. VBUS loss restarts the count. Only uninterrupted silence advances it.

The entry pulse fires once, on the tick the threshold is first crossed — a level would re-announce the entry every tick the device stayed suspended, which is 18.2's change-bit lesson at 22 009 errors.

And there is no second copy of the threshold expression anywhere. §12 is about what happened when there was one.

7. Verilog-2005

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_suspend_detect -- when a device must stop drawing power, and why the
// word "continuous" in the rule is doing all the work.
//
// USB HAS NO IDLE. The host transmits a start-of-frame every millisecond
// whether or not there is anything to say, and that is not an accident of
// the design -- it exists so that a device can tell the difference between
//
//     "nothing is happening right now"      (SOFs still arriving)
//     "the bus is gone"                     (no SOFs at all)
//
// which are otherwise indistinguishable from the far end of a cable.
//
// So a device measures silence, and the threshold is 3 ms -- three missed
// frames. At that point it must begin suspending; within 10 ms of the idle
// starting it must have cut its draw to a trickle.
//
// THE WORD THAT MATTERS IS "CONTINUOUS"
//
// The rule is 3 ms of CONTINUOUS idle, and the obvious implementation --
// count ticks where the bus was quiet -- is wrong in a way that no ordinary
// test catches. A bus that is quiet, busy, quiet, busy, quiet has had
// plenty of quiet ticks and has never been idle for 3 ms. A counter that
// accumulates instead of restarting will suspend a device on a BUSY bus,
// which then stops responding to a host that is actively talking to it.
//
// One line separates the two implementations:
//
//     idle_count <= bus_activity ? 0 : idle_count + 1;      // correct
//     idle_count <= bus_activity ? idle_count : idle_count + 1;  // MUT U1
//
// and the difference is invisible unless the stimulus contains activity
// patterns with gaps in them -- which is why this chapter's sweep enumerates
// every possible pattern in a window rather than sampling realistic ones.
//
// SUSPEND CURRENT
//
// A suspended device is held to a few hundred microamps, not milliamps. The
// exact figure depends on whether it has been armed for remote wakeup
// (19.5): an armed device must keep enough of itself alive to notice a
// reason to wake, and is allowed more.
module usb_suspend_detect #(
  parameter integer SUSPEND_TICKS   = 3000,  // 3 ms, at one tick per us
  parameter integer SUSPEND_UA_BASE = 500,   // suspended, not armed
  parameter integer SUSPEND_UA_RW   = 2500,  // suspended, armed for wakeup
  parameter integer RUN_UA          = 500000 // operating: 500 mA in uA
) (
  input  wire        clk,
  input  wire        rst_n,

  input  wire        vbus_present,
  input  wire        bus_activity,           // ANY activity this tick
  input  wire        remote_wakeup_enabled,  // armed by the host (19.5)

  output reg  [31:0] idle_ticks,             // CONSECUTIVE idle ticks
  output wire        suspended,
  output wire [31:0] draw_limit_uA,          // what it may draw RIGHT NOW
  output wire [1:0]  suspend_state,          // the case, named
  output reg         suspend_entry,          // one-tick pulse on entry
  output reg         resume_detected,        // one-tick pulse on exit

  output reg  [31:0] suspends_entered,
  output reg  [31:0] max_idle_ticks          // the longest silence seen
);
  // Suspended is a THRESHOLD on the consecutive count, not a separate state
  // bit. Deriving it means it cannot disagree with the counter, and it means
  // any activity un-suspends the device in the same tick the activity
  // arrives -- which is what resume requires (19.4).
  assign suspended = vbus_present && (idle_ticks >= SUSPEND_TICKS[31:0]);

  // The same fact with the intermediate case named. Verilog-2005 has no
  // enumerated type, so the encoding is localparams -- the nearest thing
  // available, and still better than leaving a reader to infer the case
  // from a counter and two flags.
  localparam [1:0] SUSP_NOBUS     = 2'd0,   // no VBUS: absent, not suspended
                   SUSP_RUNNING   = 2'd1,   // the bus is active
                   SUSP_COUNTING  = 2'd2,   // quiet, not yet long enough
                   SUSP_SUSPENDED = 2'd3;   // the threshold was crossed
  assign suspend_state = !vbus_present ? SUSP_NOBUS
                       : bus_activity  ? SUSP_RUNNING
                       : (idle_ticks >= SUSPEND_TICKS[31:0]) ? SUSP_SUSPENDED
                                                             : SUSP_COUNTING;

  // A suspended device is held to microamps. An armed device is allowed
  // more, because it must keep enough alive to notice a reason to wake.
  assign draw_limit_uA = !vbus_present ? 32'd0
                       : !suspended    ? RUN_UA[31:0]
                       : remote_wakeup_enabled ? SUSPEND_UA_RW[31:0]
                                               : SUSPEND_UA_BASE[31:0];

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      idle_ticks       <= 32'd0;
      suspend_entry    <= 1'b0;
      resume_detected  <= 1'b0;
      suspends_entered <= 32'd0;
      max_idle_ticks   <= 32'd0;
    end else begin
      suspend_entry   <= 1'b0;
      resume_detected <= 1'b0;

      if (!vbus_present) begin
        // No bus at all. This is not suspend -- suspend is a state a device
        // enters ON a powered bus that has gone quiet. With VBUS gone there
        // is nothing to be suspended from.
        idle_ticks <= 32'd0;
      end else if (bus_activity) begin
        // THE LINE. Activity RESTARTS the measurement; it does not pause it.
        // 3 ms of continuous idle means continuous.
        if (suspended) resume_detected <= 1'b1;
        idle_ticks <= 32'd0;
      end else begin
        idle_ticks <= idle_ticks + 32'd1;
        if (idle_ticks + 32'd1 > max_idle_ticks)
          max_idle_ticks <= idle_ticks + 32'd1;
        // The entry pulse fires on the tick the threshold is first crossed,
        // and only then -- a level would re-announce the entry every tick
        // the device stayed suspended, which chapter 18.2 paid 22009 errors
        // to learn about change bits.
        if (idle_ticks + 32'd1 == SUSPEND_TICKS[31:0]) begin
          suspend_entry    <= 1'b1;
          suspends_entered <= suspends_entered + 32'd1;
        end
      end
    end
  end
endmodule

suspended is read directly inside the sequential block rather than recomputed. An earlier version had a separate was_suspended wire holding a byte-for-byte copy of the same expression — and §12 is the measurement of what that cost.

8. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package usb_suspend_pkg;
  // Where a device sits on the road to suspend. Three of these look alike
  // from outside -- the device is quiet in all of them -- and they mean
  // entirely different things about what it may draw and what will wake it.
  typedef enum logic [1:0] {
    SUSP_NOBUS,      // no VBUS: not suspended, just absent
    SUSP_RUNNING,    // the bus is active
    SUSP_COUNTING,   // quiet, but not yet for long enough
    SUSP_SUSPENDED   // the threshold was crossed
  } suspend_state_e;
endpackage

// usb_suspend_detect_sv -- when a device must stop drawing power, and why the
// word "continuous" in the rule is doing all the work.
//
// USB HAS NO IDLE. The host transmits a start-of-frame every millisecond
// whether or not there is anything to say, and that is not an accident of
// the design -- it exists so that a device can tell the difference between
//
//     "nothing is happening right now"      (SOFs still arriving)
//     "the bus is gone"                     (no SOFs at all)
//
// which are otherwise indistinguishable from the far end of a cable.
//
// So a device measures silence, and the threshold is 3 ms -- three missed
// frames. At that point it must begin suspending; within 10 ms of the idle
// starting it must have cut its draw to a trickle.
//
// THE WORD THAT MATTERS IS "CONTINUOUS"
//
// The rule is 3 ms of CONTINUOUS idle, and the obvious implementation --
// count ticks where the bus was quiet -- is wrong in a way that no ordinary
// test catches. A bus that is quiet, busy, quiet, busy, quiet has had
// plenty of quiet ticks and has never been idle for 3 ms. A counter that
// accumulates instead of restarting will suspend a device on a BUSY bus,
// which then stops responding to a host that is actively talking to it.
//
// One line separates the two implementations:
//
//     idle_count <= bus_activity ? 0 : idle_count + 1;      // correct
//     idle_count <= bus_activity ? idle_count : idle_count + 1;  // MUT U1
//
// and the difference is invisible unless the stimulus contains activity
// patterns with gaps in them -- which is why this chapter's sweep enumerates
// every possible pattern in a window rather than sampling realistic ones.
//
// SUSPEND CURRENT
//
// A suspended device is held to a few hundred microamps, not milliamps. The
// exact figure depends on whether it has been armed for remote wakeup
// (19.5): an armed device must keep enough of itself alive to notice a
// reason to wake, and is allowed more.
module usb_suspend_detect_sv
  import usb_suspend_pkg::*;
#(
  parameter int unsigned SUSPEND_TICKS   = 3000,  // 3 ms, one tick per us
  parameter int unsigned SUSPEND_UA_BASE = 500,   // suspended, not armed
  parameter int unsigned SUSPEND_UA_RW   = 2500,  // suspended, armed
  parameter int unsigned RUN_UA          = 500000 // operating: 500 mA in uA
) (
  input  logic        clk,
  input  logic        rst_n,

  input  logic        vbus_present,
  input  logic        bus_activity,           // ANY activity this tick
  input  logic        remote_wakeup_enabled,  // armed by the host (19.5)

  output logic [31:0] idle_ticks,             // CONSECUTIVE idle ticks
  output logic        suspended,
  output logic [31:0] draw_limit_uA,          // what it may draw RIGHT NOW
  output suspend_state_e suspend_state,
  output logic        suspend_entry,          // one-tick pulse on entry
  output logic        resume_detected,        // one-tick pulse on exit

  output logic [31:0] suspends_entered,
  output logic [31:0] max_idle_ticks          // the longest silence seen
);
  // Suspended is a THRESHOLD on the consecutive count, not a separate state
  // bit. Deriving it means it cannot disagree with the counter, and it means
  // any activity un-suspends the device in the same tick the activity
  // arrives -- which is what resume requires (19.4).
  assign suspended = vbus_present && (idle_ticks >= 32'(SUSPEND_TICKS));

  // The same fact with the intermediate case named. A reader seeing
  // SUSP_COUNTING knows the device is quiet AND that a clock is running on
  // it, which two separate signals leave them to infer.
  always_comb begin
    if      (!vbus_present)                  suspend_state = SUSP_NOBUS;
    else if (bus_activity)                   suspend_state = SUSP_RUNNING;
    else if (idle_ticks >= 32'(SUSPEND_TICKS)) suspend_state = SUSP_SUSPENDED;
    else                                     suspend_state = SUSP_COUNTING;
  end

  // A suspended device is held to microamps. An armed device is allowed
  // more, because it must keep enough alive to notice a reason to wake.
  assign draw_limit_uA = !vbus_present ? 32'd0
                       : !suspended    ? 32'(RUN_UA)
                       : remote_wakeup_enabled ? 32'(SUSPEND_UA_RW)
                                               : 32'(SUSPEND_UA_BASE);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      idle_ticks       <= 32'd0;
      suspend_entry    <= 1'b0;
      resume_detected  <= 1'b0;
      suspends_entered <= 32'd0;
      max_idle_ticks   <= 32'd0;
    end else begin
      suspend_entry   <= 1'b0;
      resume_detected <= 1'b0;

      if (!vbus_present) begin
        // No bus at all. This is not suspend -- suspend is a state a device
        // enters ON a powered bus that has gone quiet. With VBUS gone there
        // is nothing to be suspended from.
        idle_ticks <= 32'd0;
      end else if (bus_activity) begin
        // THE LINE. Activity RESTARTS the measurement; it does not pause it.
        // 3 ms of continuous idle means continuous.
        if (suspended) resume_detected <= 1'b1;
        idle_ticks <= 32'd0;
      end else begin
        idle_ticks <= idle_ticks + 32'd1;
        if (idle_ticks + 32'd1 > max_idle_ticks)
          max_idle_ticks <= idle_ticks + 32'd1;
        // The entry pulse fires on the tick the threshold is first crossed,
        // and only then -- a level would re-announce the entry every tick
        // the device stayed suspended, which chapter 18.2 paid 22009 errors
        // to learn about change bits.
        if (idle_ticks + 32'd1 == 32'(SUSPEND_TICKS)) begin
          suspend_entry    <= 1'b1;
          suspends_entered <= suspends_entered + 32'd1;
        end
      end
    end
  end
endmodule

SUSP_COUNTING is the state worth naming. A device that is quiet but not yet suspended looks exactly like a device that is suspended — from outside, both are silent — and they differ in what the device may draw and how fast it can respond. Two separate signals leave a reader to infer the case; one enum states it.

9. VHDL-2008

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_suspend_pkg is
  -- Where a device sits on the road to suspend. Three of these look alike
  -- from outside -- the device is quiet in all of them -- and they mean
  -- entirely different things about what it may draw and what will wake it.
  type suspend_state_t is (
    SUSP_NOBUS,      -- no VBUS: not suspended, just absent
    SUSP_RUNNING,    -- the bus is active
    SUSP_COUNTING,   -- quiet, but not yet for long enough
    SUSP_SUSPENDED   -- the threshold was crossed
  );
end package;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_suspend_pkg.all;

-- usb_suspend_detect_vhdl -- when a device must stop drawing power, and why
-- the word "continuous" in the rule is doing all the work.
--
-- USB HAS NO IDLE. The host transmits a start-of-frame every millisecond
-- whether or not there is anything to say, and that is not an accident of
-- the design -- it exists so that a device can tell the difference between
--
--     "nothing is happening right now"      (SOFs still arriving)
--     "the bus is gone"                     (no SOFs at all)
--
-- which are otherwise indistinguishable from the far end of a cable.
--
-- So a device measures silence, and the threshold is 3 ms -- three missed
-- frames. At that point it must begin suspending; within 10 ms of the idle
-- starting it must have cut its draw to a trickle.
--
-- THE WORD THAT MATTERS IS "CONTINUOUS"
--
-- The rule is 3 ms of CONTINUOUS idle, and the obvious implementation --
-- count ticks where the bus was quiet -- is wrong in a way that no ordinary
-- test catches. A bus that is quiet, busy, quiet, busy, quiet has had
-- plenty of quiet ticks and has never been idle for 3 ms. A counter that
-- accumulates instead of restarting will suspend a device on a BUSY bus,
-- which then stops responding to a host that is actively talking to it.
--
-- SUSPEND CURRENT
--
-- A suspended device is held to a few hundred microamps, not milliamps. The
-- exact figure depends on whether it has been armed for remote wakeup
-- (19.5): an armed device must keep enough of itself alive to notice a
-- reason to wake, and is allowed more.
entity usb_suspend_detect_vhdl is
  generic (
    SUSPEND_TICKS   : positive := 3000;   -- 3 ms, at one tick per us
    SUSPEND_UA_BASE : positive := 500;    -- suspended, not armed
    SUSPEND_UA_RW   : positive := 2500;   -- suspended, armed for wakeup
    RUN_UA          : positive := 500000  -- operating: 500 mA in uA
  );
  port (
    clk                   : in  std_logic;
    rst_n                 : in  std_logic;

    vbus_present          : in  std_logic;
    bus_activity          : in  std_logic;  -- ANY activity this tick
    remote_wakeup_enabled : in  std_logic;  -- armed by the host (19.5)

    idle_ticks            : out unsigned(31 downto 0);
    suspended             : out std_logic;
    draw_limit_uA         : out unsigned(31 downto 0);
    suspend_state         : out suspend_state_t;
    suspend_entry         : out std_logic;
    resume_detected       : out std_logic;

    suspends_entered      : out unsigned(31 downto 0);
    max_idle_ticks        : out unsigned(31 downto 0)
  );
end entity;

architecture rtl of usb_suspend_detect_vhdl is
  signal idle_r    : unsigned(31 downto 0) := (others => '0');
  signal entry_r   : std_logic := '0';
  signal resume_r  : std_logic := '0';
  signal count_r   : unsigned(31 downto 0) := (others => '0');
  signal maxidle_r : unsigned(31 downto 0) := (others => '0');
  signal susp_i    : std_logic;
begin
  -- Suspended is a THRESHOLD on the consecutive count, not a separate state
  -- bit. Deriving it means it cannot disagree with the counter, and it means
  -- any activity un-suspends the device in the same tick the activity
  -- arrives -- which is what resume requires (19.4).
  susp_i <= '1' when (vbus_present = '1'
                      and idle_r >= to_unsigned(SUSPEND_TICKS, 32))
            else '0';

  idle_ticks       <= idle_r;
  suspended        <= susp_i;
  suspend_entry    <= entry_r;
  resume_detected  <= resume_r;
  suspends_entered <= count_r;
  max_idle_ticks   <= maxidle_r;

  -- A suspended device is held to microamps. An armed device is allowed
  -- more, because it must keep enough alive to notice a reason to wake.
  draw_limit_uA <= to_unsigned(0, 32) when vbus_present = '0' else
                   to_unsigned(RUN_UA, 32) when susp_i = '0' else
                   to_unsigned(SUSPEND_UA_RW, 32)
                     when remote_wakeup_enabled = '1' else
                   to_unsigned(SUSPEND_UA_BASE, 32);

  -- The same fact with the intermediate case named. A reader seeing
  -- SUSP_COUNTING knows the device is quiet AND that a clock is running on
  -- it, which two separate signals leave them to infer.
  suspend_state <= SUSP_NOBUS     when vbus_present = '0' else
                   SUSP_RUNNING   when bus_activity = '1' else
                   SUSP_SUSPENDED when idle_r >= to_unsigned(SUSPEND_TICKS, 32)
                   else SUSP_COUNTING;

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      idle_r <= (others => '0'); entry_r <= '0'; resume_r <= '0';
      count_r <= (others => '0'); maxidle_r <= (others => '0');
    elsif rising_edge(clk) then
      entry_r  <= '0';
      resume_r <= '0';

      if vbus_present = '0' then
        -- No bus at all. This is not suspend -- suspend is a state a device
        -- enters ON a powered bus that has gone quiet. With VBUS gone there
        -- is nothing to be suspended from.
        idle_r <= (others => '0');
      elsif bus_activity = '1' then
        -- THE LINE. Activity RESTARTS the measurement; it does not pause it.
        -- 3 ms of continuous idle means continuous.
        if susp_i = '1' then resume_r <= '1'; end if;
        idle_r <= (others => '0');
      else
        idle_r <= idle_r + 1;
        if idle_r + 1 > maxidle_r then maxidle_r <= idle_r + 1; end if;
        -- The entry pulse fires on the tick the threshold is first crossed,
        -- and only then -- a level would re-announce the entry every tick
        -- the device stayed suspended, which chapter 18.2 paid 22009 errors
        -- to learn about change bits.
        if idle_r + 1 = to_unsigned(SUSPEND_TICKS, 32) then
          entry_r <= '1';
          count_r <= count_r + 1;
        end if;
      end if;
    end if;
  end process;
end architecture;

susp_i is one signal feeding both the output and the process, which is what made mutation U6 die here while it survived in the other two languages until they were restructured to match (§12).

wc -l "$DEST"

10. The Testbench: Every Activity Pattern in a Window

§2's bug is invisible to realistic stimulus. A traffic generator that models a plausible bus produces long runs of activity and long runs of silence; the patterns that separate an accumulating counter from a restarting one are the ragged ones, and those are exactly what a plausible generator makes rare.

So the sweep enumerates them all. Every one of the 4096 possible activity patterns in a 12-tick window, crossed with remote-wakeup armed or not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // Every one of the 4096 possible activity patterns in a 12-tick window,
    // crossed with remote-wakeup armed or not = 8192 scenarios, 98304
    // individual ticks. This is exhaustive over the TEMPORAL domain: every
    // arrangement of activity and silence, including every gapped one.
    for (cfgi=0; cfgi<2; cfgi=cfgi+1)
     for (pat=0; pat<(1<<W); pat=pat+1) begin
       hard_reset;
       if (gapped(pat[W-1:0])) n_gapped = n_gapped + 1;
       for (t=0; t<W; t=t+1)
         tick(pat[t], 1'b1, cfgi[0]);
       n_pat = n_pat + 1;
     end

The model keeps its own counter, and three properties are checked against no model at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. A suspended device is ALWAYS at microamps, never running current.
      if (suspended)
        check(draw_limit_uA <= UA_RW,
              "a suspended device is still drawing operating current");
      // 2. With no VBUS nothing is suspended and nothing is drawn.
      if (!vb) begin
        check(!suspended, "suspended with no VBUS");
        check(draw_limit_uA === 32'd0, "drawing from an unpowered bus");
      end
      // 3. THE property: a device is never suspended when the bus was
      //    active on this very tick. Activity restarts the measurement.
      if (a && vb)
        check(idle_ticks !== 32'd0 || !suspended,
              "suspended on a tick the bus was active");

And the directed case states §2 arithmetically:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // ST-1 idle ticks, then ONE activity tick, then ST-1 idle again.
    // Cumulatively that is 2*(ST-1) idle ticks -- more than the threshold --
    // and the device must NOT suspend, because it was never idle for ST
    // ticks in a row.
    for (t=0; t<ST-1; t=t+1) tick(1'b0, 1'b1, 1'b0);
    tick(1'b1, 1'b1, 1'b0);                 // one SOF
    check(!suspended, "the SOF restarts the measurement");
    check(idle_ticks === 32'd0, "and the count is back to zero");
    for (t=0; t<ST-1; t=t+1) tick(1'b0, 1'b1, 1'b0);
    check(!suspended,
          "still not suspended: 2x(ST-1) idle ticks were never CONSECUTIVE");
    check(suspends_entered === 32'd0, "and no suspend was ever entered");

Measured reach:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  exhaustive activity-pattern sweep: 8192 of 8192 patterns verified
  of which contained a gap (activity-idle-activity): 8034

  Verilog / SystemVerilog:
  REACH: patterns=8192 gapped=8034 suspended-ticks=17319 entries=5664
         resumes=4367 max-idle=30
  [Verilog] usb_suspend_detect: 0 errors — PASS

  VHDL:
  REACH: patterns=8192 gapped=8034 suspended-ticks=17514 entries=5747
         resumes=4435 max-idle=32
  [VHDL] usb_suspend_detect_vhdl: 0 errors — PASS

11. The Waveform

One frame marker mid-count, and the measurement starts again

10 cycles
A waveform of the suspend detector over ten ticks, with the suspend threshold reduced from three thousand to four so that an entire suspend and resume fits in one figure. At tick zero the bus is quiet and the consecutive idle count is zero. At tick one the bus is still quiet and the count has reached one. At tick two a start-of-frame arrives: bus activity is high while the count stands at two. Because activity restarts the measurement rather than pausing it, by tick three the count has returned to zero, not held at two. The bus then stays quiet: the count rises through one at tick four, two at tick five, three at tick six, and at tick seven reaches four, which is the threshold. At that tick the device is suspended, the suspend entry pulse fires for exactly one tick, and the current allowance collapses from five hundred thousand microamps to five hundred. At tick eight activity appears on the bus again while the device is still suspended. At tick nine the device has left suspend, the consecutive count is back to zero, the resume detected pulse fires, and the full current allowance is restored. The cumulative quiet time before the frame marker plus the quiet time after it exceeds the threshold, and the device correctly never suspended during that first stretch.SOF mid-count → RESTART, not pauseSOF mid-count → RESTART,not pausecount back to 0count back to 04 consecutive → SUSPEND, 500 µA4 consecutive → SUSPEND,500 µAactivity → resumeactivity → resumeclkbus_activityidle_ticks0120123450suspendedsuspend_entryresume_detdraw_limit500k500k500k500k500k500k500k500µA500µA500kstateCNTCNTRUNCNTCNTCNTCNTSUSPRUNCNTt0t1t2t3t4t5t6t7t8t9
Figure 2 — ten ticks from the simulator, with the threshold reduced to 4 so the whole story fits. Cycle 2 is the chapter: one frame marker arrives with the count at 2, and the count returns to 0 rather than holding at 2. The two silences never add up.

draw_limit is in microamps throughout; 500k is 500 mA of operating allowance and 500µA is the suspended trickle. The ratio is a thousand to one, which is the whole reason suspend exists.

Ticks 0–2 against ticks 3–7 are the comparison to hold on to. The first stretch accumulated two idle ticks and was interrupted; the second ran four in a row. Cumulatively the device was quiet for six of the first eight ticks and suspended on neither of them until the run was uninterrupted.

12. The Mutation That Survived on a Duplicated Condition

The first run of this matrix had U6 surviving outright in two languages:

MutationVerilogSystemVerilogVHDL
U6 — suspended ignores vbus_present001598

Zero errors. In a 98 304-tick sweep, with the design demonstrably correct in VHDL.

The cause was a duplicated expression. The Verilog and SystemVerilog designs had two copies of the threshold test:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign suspended   = vbus_present && (idle_ticks >= SUSPEND_TICKS[31:0]);
  ...
  wire was_suspended = vbus_present && (idle_ticks >= SUSPEND_TICKS[31:0]);

— and was_suspended was used in exactly one place: inside the else if (bus_activity) branch, which is reached only when vbus_present is already known true. So within that branch the vbus_present && term was redundant, and U6, which removes it, was genuinely equivalent.

The VHDL had one signal, susp_i, feeding both the output and the process — so removing the term changed the output too, and the mutation died.

The fix was not to change the mutation. It was to delete the duplicate:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        if (suspended) resume_detected <= 1'b1;

U6 went from 0 to 1566, matching VHDL's 1598.

13. The Verilog Bench Was Checking One Property Fewer — Again

The same run had three mutations scoring consistently low in Verilog alone:

MutationVerilog (before)afterSystemVerilog
U1282 532309 825309 825
U210 45312 18112 181
U460 21763 65363 653

This is Chapter 19.2 §11 repeating itself, one chapter later and with the same root cause: the SystemVerilog and VHDL designs exposed a named suspend_state and their benches checked it; the Verilog design did not.

The fix was the same — a localparam encoding — and the counts converged exactly.

Twice in two chapters, the tri-HDL comparison was measuring the testbenches rather than the designs, because one design exposed less than the others. The rule that falls out of it: every observable one implementation exposes, all three should expose, whatever syntax each language has to use to do it.

14. Mutation Testing — Across All Three Languages

MutationVerilogSystemVerilogVHDL
U1the counter accumulates instead of restarting309825309825309282
U2off-by-one on the threshold (> for >=)121811218112356
U3the entry pulse becomes a level572645726457381
U4VBUS loss does not restart the measurement636536365365109
U5an armed device gets the unarmed allowance804880488058
U6suspended ignores vbus_present156615661598
U7resume fires on any activity, not only out of suspend523285232852121

U1 is the largest count in Module 19 at 309 825, and it is entirely an artefact of the sweep: under realistic stimulus it would be near zero, because realistic stimulus rarely produces the ragged patterns that distinguish it. The number measures the sweep as much as the mutation.

U5 is the smallest at 8048 and is the only one with a physical consequence rather than a protocol one: a device armed for remote wakeup but held to the unarmed current budget cannot keep enough of itself alive to notice the event it was armed for — so it suspends, draws correctly, and never wakes.

15. A UVM Environment for a Temporal Property

The value UVM adds here is generating the sequences a plausible generator would not, which is §10's entire point.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Realistic traffic is the WRONG stimulus for this block. A generator that
// models a believable bus produces long runs of activity and long runs of
// silence; the patterns that matter are ragged, and section 10 measured how
// rare they are otherwise.
class ragged_traffic_seq extends uvm_sequence #(bus_tick_item);
  `uvm_object_utils(ragged_traffic_seq)
  rand int unsigned len;
  rand bit          pattern [];

  constraint c_len { len inside {[8:24]}; pattern.size() == len; }

  // Force gaps. Without this the solver produces mostly-quiet or
  // mostly-busy runs, which is precisely the distribution that hides
  // mutation U1.
  constraint c_ragged {
    // at least two activity ticks with at least one quiet tick between them
    pattern.sum() with (int'(item)) inside {[2:len-2]};
  }

  task body();
    bus_tick_item it;
    foreach (pattern[i])
      `uvm_do_with(it, { bus_activity == pattern[i]; vbus_present == 1; })
  endtask
endclass

class suspend_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(suspend_scoreboard)
  local int unsigned m_consecutive;   // its OWN count (chapter 17.3)

  function void write(suspend_txn t);
    // THE property, stated as the rule rather than as an expected value:
    // a device is suspended if and only if it has been quiet for the
    // threshold CONSECUTIVELY.
    if (t.bus_activity) m_consecutive = 0;
    else                m_consecutive++;

    if (t.suspended != (m_consecutive >= SUSPEND_TICKS))
      `uvm_error("SUSP/STATE", $sformatf(
        "suspended=%0b after %0d consecutive idle ticks (threshold %0d)",
        t.suspended, m_consecutive, SUSPEND_TICKS))

    // A suspended device is at microamps. Its violation is measurable on a
    // bench with a current probe, which is more than can be said for most
    // protocol errors.
    if (t.suspended && t.draw_limit_uA > SUSPEND_UA_RW)
      `uvm_error("SUSP/CURRENT", $sformatf(
        "suspended device allowed %0d uA", t.draw_limit_uA))
  endfunction
endclass

covergroup suspend_cg with function sample(
    int unsigned run_len, bit gapped, bit armed, bit suspended);
  // How long the silence ran before it was interrupted. The bins below the
  // threshold are the ones that must NOT suspend.
  cp_run : coverpoint run_len {
    bins short [] = {[0:SUSPEND_TICKS-1]};
    bins at_threshold = {SUSPEND_TICKS};
    bins beyond = {[SUSPEND_TICKS+1:$]};
  }
  // THE bin. A run that never contains a gap has not tested section 2 at
  // all, however many ticks it covers.
  cp_gap : coverpoint gapped { bins ragged = {1}; }
  cp_armed : coverpoint armed;
  x_gap_run : cross cp_gap, cp_run, cp_armed;
endgroup

16. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // THE property: suspended if and only if the silence has been CONTINUOUS.
  // Stated with $past rather than a counter, so it does not re-implement
  // the design it is checking.
  property p_activity_restarts;
    @(posedge clk) disable iff (!rst_n)
      (vbus_present && bus_activity) |=> (idle_ticks == 0);
  endproperty
  a_activity_restarts : assert property (p_activity_restarts)
    else $error("activity did not restart the idle measurement");

  // A suspended device is at microamps, always.
  property p_suspended_current;
    @(posedge clk) disable iff (!rst_n)
      suspended |-> (draw_limit_uA <= SUSPEND_UA_RW);
  endproperty
  a_suspended_current : assert property (p_suspended_current);

  // The entry pulse is a pulse: it never asserts twice in a row.
  property p_entry_is_a_pulse;
    @(posedge clk) disable iff (!rst_n)
      suspend_entry |=> !suspend_entry;
  endproperty
  a_entry_is_a_pulse : assert property (p_entry_is_a_pulse);

  // No VBUS, no suspend -- suspend is a state on a POWERED bus.
  property p_no_suspend_without_vbus;
    @(posedge clk) disable iff (!rst_n)
      (!vbus_present) |-> (!suspended);
  endproperty
  a_no_suspend_without_vbus : assert property (p_no_suspend_without_vbus);

p_activity_restarts is mutation U1 in one line, and it is the property to hand to a formal tool: it needs one cycle of history and no counter, so a prover settles it for every threshold value rather than the one the testbench instantiated.

These were written but not simulated; Icarus supports no concurrent assertions.

17. Debugging: the Device That Stops Responding Under Light Load

The report: a device works perfectly under heavy use. Under light use it intermittently stops responding and needs to be re-plugged. The busier the system, the more reliable it is — which is backwards from every other kind of fault.

The procedure:

1. Note that the symptom is inverted and take it seriously. Almost every bug gets worse under load. A bug that gets better under load is a bug about idleness, and on USB that means suspend.

2. Check whether the device is suspending when it should not. A device that suspends on a bus the host is still using stops answering — and the host, which knows it did not stop transmitting, reports a device that has gone away rather than one that is asleep.

3. Look for §2's bug specifically. A device with an accumulating idle counter suspends after enough total quiet time, regardless of gaps. Under light load the quiet ticks accumulate quickly; under heavy load they still accumulate, just more slowly. That is exactly the observed load dependence.

4. Distinguish it from a genuine suspend. A correct suspend is followed by a correct resume the moment traffic returns (19.4). The failure here is that the device suspends and the host never knows to resume it, because from the host's side nothing happened.

5. Confirm with a current probe. A device drawing 500 µA on a bus with traffic on it is suspended and should not be. This is one of the few USB faults that is directly measurable with a multimeter, which makes it far easier to confirm than to find.

18. Common Misconceptions

"An idle USB bus is silent." It is not — SOFs every millisecond (§1), which is what makes silence measurable at all.

"3 ms of idle means 3 ms of total quiet time." It means 3 ms continuous (§2). Mutation U1, 309 825 errors, and the field symptom in §17.

"A suspended device has been told to suspend." Nothing tells it. It measured the silence itself (§1, §5), and the host knows only because it stopped transmitting.

"Suspend and no-VBUS are the same low-power state." A suspended device keeps its address and configuration; a device that lost VBUS has been deconfigured (§4, 19.1 §4). Mutation U4, 63 653 errors.

"A suspended device draws nothing." 500 µA, or 2.5 mA if armed for remote wakeup (§3) — and the larger figure is what buys the ability to wake.

"Enabling remote wakeup everywhere is free." It raises the suspended current fivefold per device (§3).

"Three implementations of one design are redundancy." They are three opinions about what is essential, and §12 is what happens when two of them carry something the third does not.

19. Exercises

1. §10 sweeps a 12-tick window with a threshold of 4. Determine the smallest window that reaches every distinct behaviour of a threshold-N detector, and prove it.

2. §12 found a duplicated condition that made a mutation equivalent. Audit the remaining designs in Module 19 for expressions written more than once, and say which mutations each duplicate could neutralise.

3. A device implements the counter correctly but samples bus_activity one tick late. Determine which of §14's mutations would catch it, and design one that would.

4. Write the SVA property that catches U3 — the entry pulse becoming a level — without using $past.

5. §17's fault is load-dependent. Compute, for an accumulating counter with threshold N and a bus that is active a fraction p of ticks, the expected time to a spurious suspend, and confirm it is inversely proportional to 1−p.

6. The suspend current limits are 500 µA and 2.5 mA. Determine how many armed devices a 100 mA bus-powered hub (18.5) could support in suspend, and whether that is the binding constraint on such a hub.

20. Summary

USB has no idle (§1). The host sends a frame marker every millisecond whether or not it has anything to say, precisely so a device can tell quiet from gone — one mechanism doing a completely different job from 17.1's frame counter.

The threshold is 3 ms of continuous silence (§2), and continuous is the whole specification. An accumulating counter suspends a device on a busy bus, and the field symptom is a device that is more reliable under load (§17). Mutation U1, 309 825 errors — the largest in Module 19.

A suspended device draws 500 µA, or 2.5 mA if armed for remote wakeup (§3). The larger allowance is what buys the ability to notice a reason to wake, which is why arming every device in a system is not free.

Suspend is not the absence of a bus (§4). A suspended device keeps its address and configuration; one that lost VBUS has been deconfigured.

All three HDL implementations were simulated (§21) and seven mutations died in all three (§14), with the detector verified over every one of 4096 activity patterns in a 12-tick window — 8192 scenarios, 98 304 ticks, 8034 of them containing a gap (§10).

That sweep is exhaustive over a temporal space rather than an input space, and it has to be: §2's bug depends on the order inputs arrive in, not on their values, and no amount of input enumeration reaches it.

Mutation U6 survived outright in two of three languages (§12) — zero errors in 98 304 ticks — because Verilog and SystemVerilog carried a duplicated threshold expression whose redundant term made the mutation equivalent. VHDL, which had written the same design with one signal, killed it. Deleting the duplicate took U6 from 0 to 1566.

And for the second chapter running, the Verilog bench was checking one property fewer (§13) because its design exposed no named state. Adding a localparam encoding moved U1, U2 and U4 into exact agreement with the other two. Every observable one implementation exposes, all three should expose.

21. Tooling, Honestly

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_suspend_detectsu_v_tb.v✅ Icarus -g2005✅ 0 errors, 8192/8192✅ all seven
SystemVerilogusb_suspend_detect_svsu_sv_tb.sv✅ Icarus -g2012✅ 0 errors, 8192/8192✅ all seven
VHDL-2008usb_suspend_detect_vhdlsu_vhdl_tb.vhd✅ nvc 1.23.0✅ 0 errors, 8192/8192✅ all seven
UVM (§15)——❌ no UVM-capable simulator here❌—
SVA (§16)——❌ unsupported by Icarus❌—

The threshold is 4 in simulation and 3000 in the default parameterisation, so that an entire suspend-and-resume fits in a 12-tick pattern. The design must not care, and nothing in it does — the only place the number appears is one comparison and one equality.

VHDL's randomised tail differs (17 514 suspended ticks against 17 319; max idle 32 against 30) because the three benches draw from different generators. The 8192 exhaustive patterns are identical by construction.

22. What Comes Next

The device is asleep. Something has to wake it, and the interesting part is who.

Chapter 19.4 — Resume builds the exit, and its central number is one the specification and the real world disagree about. The spec says a host must drive resume signalling for at least 20 ms. Linux drives it for 40, and the comment explaining why is unusually candid — it names both a calibration problem in its own timing loop and the fact that "some (many) devices actually need more than 20 ms of resume signalling", with the observation that arguing about the specification does not help when a certification lab is using one of those devices.

That gap between 20 and 40 is the chapter, and it is a better lesson about specification compliance than any amount of conformance testing.

Browse the full path on the USB tutorials index.

Continue learning

Standards & specifications

Governing standard
USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)

Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the USB curriculum.