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:
"nothing is happening right now" — SOFs still arriving
"the bus is gone" — no SOFs at allWithout 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:
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 2That 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:
idle_count <= bus_activity ? 0 : idle_count + 1; // correct
idle_count <= bus_activity ? idle_count : idle_count + 1; // MUT U1Mutation 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 armed | suspended, armed | operating | |
|---|---|---|---|
| Allowance | 500 µA | 2.5 mA | up 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
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
// 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
endmodulesuspended 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
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
endmoduleSUSP_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
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:
// 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;
endThe model keeps its own counter, and three properties are checked against no model at all:
// ---- 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:
// 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:
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 — PASS11. The Waveform
One frame marker mid-count, and the measurement starts again
10 cyclesdraw_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:
| Mutation | Verilog | SystemVerilog | VHDL |
|---|---|---|---|
U6 — suspended ignores vbus_present | 0 | 0 | 1598 |
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:
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:
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:
| Mutation | Verilog (before) | after | SystemVerilog |
|---|---|---|---|
| U1 | 282 532 | 309 825 | 309 825 |
| U2 | 10 453 | 12 181 | 12 181 |
| U4 | 60 217 | 63 653 | 63 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
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| U1 | the counter accumulates instead of restarting | 309825 | 309825 | 309282 |
| U2 | off-by-one on the threshold (> for >=) | 12181 | 12181 | 12356 |
| U3 | the entry pulse becomes a level | 57264 | 57264 | 57381 |
| U4 | VBUS loss does not restart the measurement | 63653 | 63653 | 65109 |
| U5 | an armed device gets the unarmed allowance | 8048 | 8048 | 8058 |
| U6 | suspended ignores vbus_present | 1566 | 1566 | 1598 |
| U7 | resume fires on any activity, not only out of suspend | 52328 | 52328 | 52121 |
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.
// 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;
endgroup16. Assertions
// 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
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_suspend_detect | su_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 8192/8192 | ✅ all seven |
| SystemVerilog | usb_suspend_detect_sv | su_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 8192/8192 | ✅ all seven |
| VHDL-2008 | usb_suspend_detect_vhdl | su_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
Related tutorials
- Related topic
Bus Power
A device's current allowance changes exactly once during enumeration — and bMaxPower is counted in 2 mA units, not milliamps.
- Related topic
Self Power
A self-powered device draws nothing from VBUS and must still watch it — a pull-up driven with VBUS absent pushes current back into a host that deliberately removed power.
- Related topic
Resume
The specification says drive resume for at least 20 ms; Linux drives 40 and says why — a design can be standard-compliant and still wrong for the devices it must work with.
- Related topic
Remote Wakeup
The one exception to USB's single-master rule — a suspended device may drive the bus unasked, behind two independent fences that fail in completely different ways.
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.
