USB · Module 19
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.
Every chapter of this track has rested on one fact: the host initiates everything.
Chapter 2.6 established it. Chapter 18.3 built an entire polled reporting mechanism because of it — a hub with news has to wait to be asked. Chapter 19.3's suspend detector exists because a device cannot even be told to sleep; it has to work it out by measuring silence.
Remote wakeup is the exception. A suspended device may drive the bus unasked, and the host will wake up.
1. What an Exception to a Single-Master Rule Costs
Allowing one device to speak first on a bus designed around exactly one initiator is not a small concession, and the specification fences it accordingly.
A wake event does nothing at all unless both of these hold:
| Fence | Condition | What it prevents |
|---|---|---|
| armed | the host issued SetFeature(DEVICE_REMOTE_WAKEUP) | a device waking a machine its owner put to sleep |
| suspended | the bus is actually asleep | K driven into a live transaction |
Neither is redundant. A device may be armed on a running bus, and a device may be suspended having never been armed. Only the conjunction licenses driving the bus.
And the two refusals are different bugs. "The device tried to wake the host and was never armed" is a firmware defect. "The device tried to wake a bus that was already awake" is a state-tracking defect. A design that reports one number for both tells a debugger nothing, which is why the design carries two counters and mutation W6 — conflating them — dies 100 483 times.
2. Arming Does Not Survive a Bus Reset
SetFeature(DEVICE_REMOTE_WAKEUP) is feature selector 1 in Linux's ch9.h:
#define USB_DEVICE_SELF_POWERED 0 /* (read only) */
#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */A bus reset clears it. The device returns to its default state and the arming goes with it, so the host must re-issue SetFeature after every reset.
That is not bookkeeping — it is consent. A device that kept the bit across a reset would be armed without the current host ever having agreed to it. And 19.3 §3 measured what arming costs: five times the suspended current, 2.5 mA instead of 500 µA, on every device that has it.
Mutation W3 keeps the arming across a reset and dies 132 595 times.
3. Two Places a Bus Reset Had To Be Made To Win
The design did not start with a bus reset outranking the wakeup, and the safety property found both gaps — one at a time.
First gap: a reset did not abort a drive in progress. A device that was legitimately armed and suspended, already driving K, kept driving it straight through a bus reset. A reset is the host taking the bus back, and a device driving K through one is fighting the host for the line.
Fixing that exposed the second: a reset arriving in the same cycle as a wake event still let the wakeup start. The device would come out of the reset already driving.
// A bus reset outranks the wake event as well as an in-progress
// drive. Gating only the drive was not enough: a reset arriving in
// the SAME cycle as a wake event would still let the wakeup start,
// and the device would come out of the reset already driving.
if (wake_event && !bus_reset) beginBoth are 18.2 §3's precedence rule in its device-side form: physical reality outranks anything the device was in the middle of. Mutations W4 and W5 are the two gaps, and they die 67 507 and 116 405 times.
Finding the second gap only after fixing the first is the normal shape of this work. The property was right both times; what changed was how much of the design it could see once the louder violation stopped masking the quieter one.
4. Once Started, the K Is Not Retracted
A wake event that disappears mid-signal does not stop the drive.
The host has already begun responding. Chapter 19.4 built the sequence it runs — a K, an end-of-packet, a return to idle — and a device that stopped driving partway leaves a resume half-finished on a bus with nobody else to complete it.
So the K runs for its programmed duration, and only a bus reset cuts it short.
The duration itself has a floor and a ceiling — 1 ms and 15 ms — and the design carries all three numbers: what it drives, and the two bounds it must stay inside. Mutation W7 drives the ceiling instead of the programmed duration: legal, five times longer than promised, and it dies 177 735 times.
5. The Sequence, Drawn
Count the arrows out of Device that the host did not solicit. There is exactly one, and this chapter is the fence around it.
6. Verilog-2005
// usb_remote_wakeup -- the one exception to the single-master rule, and the
// two independent fences around it.
//
// Every chapter of this track has rested on one fact: THE HOST INITIATES
// EVERYTHING. Chapter 2.6 established it, chapter 18.3 built an entire
// polled reporting mechanism because of it, and chapter 19.3's suspend
// detector exists because a device cannot even be TOLD to sleep -- it has to
// work it out by measuring silence.
//
// Remote wakeup is the exception. A suspended device may drive the bus
// unasked, and the host will wake up.
//
// That is a large thing to allow on a single-master bus, and it is fenced
// accordingly. A wake event does nothing at all unless BOTH of these hold:
//
// 1. THE DEVICE IS ARMED. The host must have issued
// SetFeature(DEVICE_REMOTE_WAKEUP) -- feature selector 1 in Linux's
// ch9.h -- and a device that was never armed must stay silent no
// matter what happens to it. This is not a formality: an unarmed
// device that wakes the bus wakes a machine its owner deliberately
// put to sleep, and does it from inside a closed bag.
//
// 2. THE BUS IS SUSPENDED. Driving K on a running bus is not a wakeup,
// it is corruption -- it collides with whatever transaction the host
// was conducting. There is nothing to wake, so there is nothing to do.
//
// The two refusals are DIFFERENT and both are counted, because "the device
// tried to wake the host and was not armed" and "the device tried to wake a
// bus that was already awake" are different bugs with different fixes.
//
// ARMING DOES NOT SURVIVE A BUS RESET
//
// A bus reset returns the device to its default state, and the arming goes
// with it. The host must re-issue SetFeature after every reset. A device
// that kept the bit across a reset would be armed without the current host
// ever having agreed to it -- and chapter 19.3 measured what arming costs:
// five times the suspended current, on every device that has it.
module usb_remote_wakeup #(
parameter integer K_HOLD_TICKS = 5, // how long this device drives K
parameter integer K_MIN_TICKS = 1, // the specification's floor
parameter integer K_MAX_TICKS = 15 // the specification's ceiling
) (
input wire clk,
input wire rst_n,
// --- the host, through a control transfer ---
input wire set_feature_rw, // SetFeature(DEVICE_REMOTE_WAKEUP)
input wire clear_feature_rw,
input wire bus_reset,
// --- the device's own state ---
input wire suspended, // from chapter 19.3
input wire wake_event, // a keypress, a packet, a sensor
output reg rw_enabled, // the armed bit -- GetStatus bit 1
output wire drive_k, // the device driving the bus UNASKED
output reg [15:0] k_ticks,
output wire wakeup_active,
output wire [1:0] wake_outcome, // what happened to this attempt
output reg [31:0] wakeups_issued,
output reg [31:0] refused_not_armed,
output reg [31:0] refused_not_suspended
);
localparam [1:0] W_IDLE = 2'd0, W_DRIVE = 2'd1;
reg [1:0] state;
assign drive_k = (state == W_DRIVE);
assign wakeup_active = (state == W_DRIVE);
// What this cycle's wake attempt came to. A combinational decode of the
// same conditions the sequential block uses -- not a second opinion.
// Verilog-2005 has no enumerated type, so the encoding is localparams:
// chapters 19.2 and 19.3 both measured what happens when one language's
// design exposes less than the others, and the answer both times was that
// the tri-HDL comparison stopped measuring the designs.
localparam [1:0] WK_ISSUED = 2'd0,
WK_REFUSED_UNARMED = 2'd1,
WK_REFUSED_AWAKE = 2'd2,
WK_NONE = 2'd3;
assign wake_outcome = (!wake_event || bus_reset) ? WK_NONE
: (state != W_IDLE) ? WK_NONE
: (!rw_enabled) ? WK_REFUSED_UNARMED
: (!suspended) ? WK_REFUSED_AWAKE
: WK_ISSUED;
// THE GATE is the pair of conditions below, tested SEPARATELY rather than
// as one conjunction. A combined `rw_enabled && suspended` would be
// shorter and would lose the thing that matters: "not armed" and "the bus
// is awake" are different refusals with different fixes, and a design that
// reports one number for both tells a debugger nothing.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rw_enabled <= 1'b0;
state <= W_IDLE;
k_ticks <= 16'd0;
wakeups_issued <= 32'd0;
refused_not_armed <= 32'd0;
refused_not_suspended <= 32'd0;
end else begin
// ---- the armed bit ----
if (bus_reset) begin
// A reset returns the device to its default state, and the arming
// goes with it. The host must arm again.
rw_enabled <= 1'b0;
end else if (clear_feature_rw) begin
rw_enabled <= 1'b0;
end else if (set_feature_rw) begin
rw_enabled <= 1'b1;
end
// ---- the wakeup itself ----
case (state)
W_IDLE: begin
// A bus reset outranks the wake event as well as an in-progress
// drive. Gating only the drive was not enough: a reset arriving in
// the SAME cycle as a wake event would still let the wakeup start,
// and the device would come out of the reset already driving.
if (wake_event && !bus_reset) begin
if (!rw_enabled)
// Not armed. The device stays silent, and the attempt is
// RECORDED -- a device trying to wake a host it was never
// permitted to wake is a defect, and an unrecorded refusal
// looks exactly like an event that never happened.
refused_not_armed <= refused_not_armed + 32'd1;
else if (!suspended)
// Armed, but the bus is awake. Nothing to wake.
refused_not_suspended <= refused_not_suspended + 32'd1;
else begin
state <= W_DRIVE;
k_ticks <= 16'd0;
wakeups_issued <= wakeups_issued + 32'd1;
end
end
end
W_DRIVE: begin
// A BUS RESET ABORTS THE DRIVE, immediately and unconditionally.
// A reset is the host taking the bus back, and a device driving K
// through one is fighting the host for the line. This is chapter
// 18.2's precedence rule in its device-side form: physical
// reality outranks anything the device was in the middle of.
if (bus_reset) begin
state <= W_IDLE;
k_ticks <= 16'd0;
end
// Otherwise the K is driven for its full duration. A wake event
// that disappears mid-signal does NOT retract the wakeup: the
// host has already begun responding, and a device that stopped
// driving early would leave the resume half-finished.
else if (k_ticks + 16'd1 >= K_HOLD_TICKS[15:0]) begin
k_ticks <= k_ticks + 16'd1;
state <= W_IDLE;
end else begin
k_ticks <= k_ticks + 16'd1;
end
end
default: state <= W_IDLE;
endcase
end
end
endmoduleThe two refusal conditions are tested separately rather than as one rw_enabled && suspended. The combined form is shorter and loses the only thing a debugger needs — which is why an earlier draft that declared exactly that wire had to delete it: it was never used, and using it would have cost the distinction.
7. SystemVerilog
package usb_wakeup_pkg;
// Why a wake attempt did or did not reach the bus. Three outcomes, and
// collapsing the two refusals into one "refused" loses the only thing a
// debugger needs: an unarmed device is a firmware bug, and a device
// signalling on a running bus is a state-tracking bug.
typedef enum logic [1:0] {
WK_ISSUED, // armed and suspended: the bus was driven
WK_REFUSED_UNARMED, // the host never armed this device
WK_REFUSED_AWAKE, // armed, but there was nothing to wake
WK_NONE // no wake event this cycle
} wake_outcome_e;
endpackage
// usb_remote_wakeup_sv -- the one exception to the single-master rule, and the
// two independent fences around it.
//
// Every chapter of this track has rested on one fact: THE HOST INITIATES
// EVERYTHING. Chapter 2.6 established it, chapter 18.3 built an entire
// polled reporting mechanism because of it, and chapter 19.3's suspend
// detector exists because a device cannot even be TOLD to sleep -- it has to
// work it out by measuring silence.
//
// Remote wakeup is the exception. A suspended device may drive the bus
// unasked, and the host will wake up.
//
// That is a large thing to allow on a single-master bus, and it is fenced
// accordingly. A wake event does nothing at all unless BOTH of these hold:
//
// 1. THE DEVICE IS ARMED. The host must have issued
// SetFeature(DEVICE_REMOTE_WAKEUP) -- feature selector 1 in Linux's
// ch9.h -- and a device that was never armed must stay silent no
// matter what happens to it. This is not a formality: an unarmed
// device that wakes the bus wakes a machine its owner deliberately
// put to sleep, and does it from inside a closed bag.
//
// 2. THE BUS IS SUSPENDED. Driving K on a running bus is not a wakeup,
// it is corruption -- it collides with whatever transaction the host
// was conducting. There is nothing to wake, so there is nothing to do.
//
// The two refusals are DIFFERENT and both are counted, because "the device
// tried to wake the host and was not armed" and "the device tried to wake a
// bus that was already awake" are different bugs with different fixes.
//
// ARMING DOES NOT SURVIVE A BUS RESET
//
// A bus reset returns the device to its default state, and the arming goes
// with it. The host must re-issue SetFeature after every reset. A device
// that kept the bit across a reset would be armed without the current host
// ever having agreed to it -- and chapter 19.3 measured what arming costs:
// five times the suspended current, on every device that has it.
module usb_remote_wakeup_sv
import usb_wakeup_pkg::*;
#(
parameter int unsigned K_HOLD_TICKS = 5, // how long this device drives K
parameter int unsigned K_MIN_TICKS = 1, // the specification's floor
parameter int unsigned K_MAX_TICKS = 15 // the specification's ceiling
) (
input logic clk,
input logic rst_n,
// --- the host, through a control transfer ---
input logic set_feature_rw, // SetFeature(DEVICE_REMOTE_WAKEUP)
input logic clear_feature_rw,
input logic bus_reset,
// --- the device's own state ---
input logic suspended, // from chapter 19.3
input logic wake_event, // a keypress, a packet, a sensor
output logic rw_enabled, // the armed bit -- GetStatus bit 1
output logic drive_k, // the device driving the bus UNASKED
output logic [15:0] k_ticks,
output logic wakeup_active,
output wake_outcome_e wake_outcome, // what happened to this attempt
output logic [31:0] wakeups_issued,
output logic [31:0] refused_not_armed,
output logic [31:0] refused_not_suspended
);
initial begin
if (K_HOLD_TICKS < K_MIN_TICKS || K_HOLD_TICKS > K_MAX_TICKS)
$fatal(1, "K_HOLD_TICKS=%0d is outside the permitted range %0d..%0d",
K_HOLD_TICKS, K_MIN_TICKS, K_MAX_TICKS);
end
localparam logic [1:0] W_IDLE = 2'd0, W_DRIVE = 2'd1;
logic [1:0] state;
// What this cycle's wake attempt came to. A combinational decode of the
// same conditions the sequential block uses -- not a second opinion.
always_comb begin
if (!wake_event || bus_reset) wake_outcome = WK_NONE;
else if (state != W_IDLE) wake_outcome = WK_NONE;
else if (!rw_enabled) wake_outcome = WK_REFUSED_UNARMED;
else if (!suspended) wake_outcome = WK_REFUSED_AWAKE;
else wake_outcome = WK_ISSUED;
end
assign drive_k = (state == W_DRIVE);
assign wakeup_active = (state == W_DRIVE);
// THE GATE is the pair of conditions below, tested SEPARATELY rather than
// as one conjunction. A combined `rw_enabled && suspended` would be
// shorter and would lose the thing that matters: "not armed" and "the bus
// is awake" are different refusals with different fixes, and a design that
// reports one number for both tells a debugger nothing.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rw_enabled <= 1'b0;
state <= W_IDLE;
k_ticks <= 16'd0;
wakeups_issued <= '0;
refused_not_armed <= '0;
refused_not_suspended <= '0;
end else begin
// ---- the armed bit ----
if (bus_reset) begin
// A reset returns the device to its default state, and the arming
// goes with it. The host must arm again.
rw_enabled <= 1'b0;
end else if (clear_feature_rw) begin
rw_enabled <= 1'b0;
end else if (set_feature_rw) begin
rw_enabled <= 1'b1;
end
// ---- the wakeup itself ----
case (state)
W_IDLE: begin
// A bus reset outranks the wake event as well as an in-progress
// drive. Gating only the drive was not enough: a reset arriving in
// the SAME cycle as a wake event would still let the wakeup start,
// and the device would come out of the reset already driving.
if (wake_event && !bus_reset) begin
if (!rw_enabled)
// Not armed. The device stays silent, and the attempt is
// RECORDED -- a device trying to wake a host it was never
// permitted to wake is a defect, and an unrecorded refusal
// looks exactly like an event that never happened.
refused_not_armed <= refused_not_armed + 1;
else if (!suspended)
// Armed, but the bus is awake. Nothing to wake.
refused_not_suspended <= refused_not_suspended + 1;
else begin
state <= W_DRIVE;
k_ticks <= 16'd0;
wakeups_issued <= wakeups_issued + 1;
end
end
end
W_DRIVE: begin
// A BUS RESET ABORTS THE DRIVE, immediately and unconditionally.
// A reset is the host taking the bus back, and a device driving K
// through one is fighting the host for the line. This is chapter
// 18.2's precedence rule in its device-side form: physical
// reality outranks anything the device was in the middle of.
if (bus_reset) begin
state <= W_IDLE;
k_ticks <= 16'd0;
end
// Otherwise the K is driven for its full duration. A wake event
// that disappears mid-signal does NOT retract the wakeup: the
// host has already begun responding, and a device that stopped
// driving early would leave the resume half-finished.
else if (k_ticks + 16'd1 >= 16'(K_HOLD_TICKS)) begin
k_ticks <= k_ticks + 16'd1;
state <= W_IDLE;
end else begin
k_ticks <= k_ticks + 16'd1;
end
end
default: state <= W_IDLE;
endcase
end
end
endmodulewake_outcome_e names the four cases, and WK_NONE is deliberately one of them: no wake event this cycle is not the same as a wake event that was refused, and a design with three states would conflate them.
8. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_wakeup_pkg is
-- Why a wake attempt did or did not reach the bus. Three outcomes, and
-- collapsing the two refusals into one "refused" loses the only thing a
-- debugger needs: an unarmed device is a firmware bug, and a device
-- signalling on a running bus is a state-tracking bug.
type wake_outcome_t is (
WK_ISSUED, -- armed and suspended: the bus was driven
WK_REFUSED_UNARMED, -- the host never armed this device
WK_REFUSED_AWAKE, -- armed, but there was nothing to wake
WK_NONE -- no wake event this cycle
);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_wakeup_pkg.all;
-- usb_remote_wakeup_vhdl -- the one exception to the single-master rule, and
-- the two independent fences around it.
--
-- Every chapter of this track has rested on one fact: THE HOST INITIATES
-- EVERYTHING. Chapter 2.6 established it, chapter 18.3 built an entire
-- polled reporting mechanism because of it, and chapter 19.3's suspend
-- detector exists because a device cannot even be TOLD to sleep.
--
-- Remote wakeup is the exception. A suspended device may drive the bus
-- unasked, and the host will wake up.
--
-- That is a large thing to allow on a single-master bus, and it is fenced
-- accordingly. A wake event does nothing at all unless BOTH of these hold:
--
-- 1. THE DEVICE IS ARMED. The host must have issued
-- SetFeature(DEVICE_REMOTE_WAKEUP) -- feature selector 1 in Linux's
-- ch9.h -- and a device that was never armed must stay silent. An
-- unarmed device that wakes the bus wakes a machine its owner
-- deliberately put to sleep, from inside a closed bag.
--
-- 2. THE BUS IS SUSPENDED. Driving K on a running bus is not a wakeup,
-- it is corruption -- it collides with whatever transaction the host
-- was conducting.
--
-- The two refusals are DIFFERENT and both are counted, because "not armed"
-- and "the bus was already awake" are different bugs with different fixes.
--
-- ARMING DOES NOT SURVIVE A BUS RESET, and a bus reset outranks both an
-- in-progress drive and a wake event arriving in the same cycle.
entity usb_remote_wakeup_vhdl is
generic (
K_HOLD_TICKS : positive := 5; -- how long this device drives K
K_MIN_TICKS : positive := 1; -- the specification's floor
K_MAX_TICKS : positive := 15 -- the specification's ceiling
);
port (
clk : in std_logic;
rst_n : in std_logic;
set_feature_rw : in std_logic;
clear_feature_rw : in std_logic;
bus_reset : in std_logic;
suspended : in std_logic;
wake_event : in std_logic;
rw_enabled : out std_logic;
drive_k : out std_logic;
k_ticks : out unsigned(15 downto 0);
wakeup_active : out std_logic;
wake_outcome : out wake_outcome_t;
wakeups_issued : out unsigned(31 downto 0);
refused_not_armed : out unsigned(31 downto 0);
refused_not_suspended : out unsigned(31 downto 0)
);
end entity;
architecture rtl of usb_remote_wakeup_vhdl is
type wk_state_t is (W_IDLE, W_DRIVE);
signal st_r : wk_state_t := W_IDLE;
signal arm_r : std_logic := '0';
signal k_r : unsigned(15 downto 0) := (others => '0');
signal iss_r : unsigned(31 downto 0) := (others => '0');
signal rna_r : unsigned(31 downto 0) := (others => '0');
signal rns_r : unsigned(31 downto 0) := (others => '0');
begin
assert K_HOLD_TICKS >= K_MIN_TICKS and K_HOLD_TICKS <= K_MAX_TICKS
report "K_HOLD_TICKS is outside the permitted range" severity failure;
drive_k <= '1' when st_r = W_DRIVE else '0';
wakeup_active <= '1' when st_r = W_DRIVE else '0';
rw_enabled <= arm_r;
k_ticks <= k_r;
wakeups_issued <= iss_r;
refused_not_armed <= rna_r;
refused_not_suspended <= rns_r;
-- What this cycle's wake attempt came to. A combinational decode of the
-- same conditions the sequential process uses -- not a second opinion.
wake_outcome <= WK_NONE when (wake_event = '0'
or bus_reset = '1') else
WK_NONE when st_r /= W_IDLE else
WK_REFUSED_UNARMED when arm_r = '0' else
WK_REFUSED_AWAKE when suspended = '0' else
WK_ISSUED;
process (clk, rst_n)
begin
if rst_n = '0' then
arm_r <= '0'; st_r <= W_IDLE; k_r <= (others => '0');
iss_r <= (others => '0'); rna_r <= (others => '0');
rns_r <= (others => '0');
elsif rising_edge(clk) then
-- ---- the armed bit ----
if bus_reset = '1' then
-- A reset returns the device to its default state, and the arming
-- goes with it. The host must arm again.
arm_r <= '0';
elsif clear_feature_rw = '1' then
arm_r <= '0';
elsif set_feature_rw = '1' then
arm_r <= '1';
end if;
-- ---- the wakeup itself ----
case st_r is
when W_IDLE =>
-- A bus reset outranks the wake event as well as an in-progress
-- drive. Gating only the drive is not enough: a reset arriving in
-- the SAME cycle as a wake event would still let the wakeup start,
-- and the device would come out of the reset already driving.
if wake_event = '1' and bus_reset = '0' then
if arm_r = '0' then
-- Not armed. The device stays silent, and the attempt is
-- RECORDED -- an unrecorded refusal looks exactly like an
-- event that never happened.
rna_r <= rna_r + 1;
elsif suspended = '0' then
-- Armed, but the bus is awake. Nothing to wake.
rns_r <= rns_r + 1;
else
st_r <= W_DRIVE;
k_r <= (others => '0');
iss_r <= iss_r + 1;
end if;
end if;
when W_DRIVE =>
-- A BUS RESET ABORTS THE DRIVE, immediately and unconditionally.
-- A reset is the host taking the bus back, and a device driving K
-- through one is fighting the host for the line.
if bus_reset = '1' then
st_r <= W_IDLE; k_r <= (others => '0');
-- Otherwise the K is driven for its full duration. A wake event
-- that disappears mid-signal does NOT retract the wakeup: the
-- host has already begun responding, and a device that stopped
-- driving early would leave the resume half-finished.
elsif k_r + 1 >= to_unsigned(K_HOLD_TICKS, 16) then
k_r <= k_r + 1;
st_r <= W_IDLE;
else
k_r <= k_r + 1;
end if;
end case;
end if;
end process;
end architecture;type wk_state_t is (W_IDLE, W_DRIVE); is declared in the architecture, not the package — it is internal, nothing outside needs it, and VHDL lets that distinction be expressed. The Verilog's localparam encoding is visible to anything that includes the file.
9. The Waveform
The same wake event, three different outcomes
10 cyclesTicks 0, 2 and 4 carry an identical wake_event and produce three different answers. That is §1 in one picture: the event is never the thing that decides.
And ticks 8 and 9 are §2. The reset disarms, so an event that would have been issued at tick 4 is refused at tick 9 — same device, same bus state, same event.
10. The Testbench: 384 Transitions and 4096 Interleavings
Two exhaustive sweeps, because the block has two different kinds of domain.
The first is over state and input, jointly:
// 12 distinct positions (armed or not, idle or partway through K) x all
// 32 input combinations = 384 transitions: the entire one-step domain.
for (pos=0; pos<12; pos=pos+1)
for (ic=0; ic<32; ic=ic+1) begin
goto(pos);
tick(ic[0], ic[1], ic[2], ic[3], ic[4]);
n_trans = n_trans + 1;
endTwelve positions — armed or not, crossed with idle or partway through a K of each possible length — against all 32 combinations of the five control inputs. That is every one-step transition the design has.
The second is temporal, over the two inputs that §1's gate is built from:
// Every interleaving of "the device has a reason to wake" and "the bus
// is suspended" across a 6-tick window: 2^6 x 2^6 = 4096 scenarios.
// These are the two conditions section 2's gate is built from, and the
// whole point is that neither alone is sufficient.Four properties are checked against no model, and the first is stated more carefully than it first was:
// 1. THE one that matters, stated precisely. A wakeup must never
// START unless the device was armed -- that is the property that
// stops a device waking a machine its owner put to sleep. Note it
// is about the START: a device that was legitimately armed and is
// then disarmed MID-SIGNAL keeps driving, because the host has
// already begun responding and a half-finished resume is worse
// than a completed one.Measured reach:
exhaustive transition sweep: 384 of 384 transitions verified
exhaustive wake/suspend interleaving sweep: 4096 of 4096 verified
Verilog / SystemVerilog:
REACH: transitions=384 interleavings=4096 issued=5597
refused-unarmed=3397 refused-awake=4313 driving-ticks=21395
[Verilog] usb_remote_wakeup: 0 errors — PASS
VHDL:
REACH: transitions=384 interleavings=4096 issued=5568
refused-unarmed=3321 refused-awake=4345 driving-ticks=21316
[VHDL] usb_remote_wakeup_vhdl: 0 errors — PASSAll three outcomes are reached thousands of times each, which is what makes the matrix below meaningful: 5597 issued, 3397 refused for being unarmed, 4313 refused because the bus was awake.
11. Mutation Testing — Across All Three Languages
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| W1 | the arming fence is gone — an unarmed device wakes the host | 146900 | 146900 | 144884 |
| W2 | the suspended fence is gone — K driven onto a running bus | 170744 | 170744 | 170288 |
| W3 | a bus reset does not disarm | 132595 | 132595 | 130272 |
| W4 | a bus reset does not abort a drive in progress | 67507 | 67507 | 62572 |
| W5 | a bus reset does not block the start of a wakeup | 116405 | 116405 | 114036 |
| W6 | the two refusals are conflated into one counter | 100483 | 100483 | 100555 |
| W7 | K is driven for the ceiling, not the programmed duration | 177735 | 177735 | 178876 |
W4 and W5 are §3's two gaps, now permanent tests. They score differently — 67 507 against 116 405 — because they are reached differently: a reset during a drive needs the drive to be in progress, which is rarer than a reset coinciding with a wake event.
W7 scores highest at 177 735 and is the most benign: driving K for 15 ms instead of 5 is legal. It wastes time and standing current and wakes the host perfectly well. The largest number in the table belongs to the mutation with the mildest consequence, which is the same inversion 19.2 §10 found.
Every mutation here is reached by the exhaustive sweeps rather than by luck, and none needed a stimulus change — the second chapter in Module 19 where that is true, and for the same reason: the sweeps enumerate an interaction rather than a single decision.
12. A UVM Environment for an Exception
The interesting stimulus here is adversarial: a device that tries to wake the host at every moment it is not supposed to.
// The attack sequence. Its job is to try a wakeup at every point where the
// device is NOT entitled to one -- which is exactly what a firmware bug
// does by accident, and exactly what a malicious device would do on purpose.
class unarmed_wake_attack_seq extends uvm_sequence #(wakeup_item);
`uvm_object_utils(unarmed_wake_attack_seq)
task body();
wakeup_item it;
// Never arm. Then try to wake from every bus state there is.
foreach_state: for (int s = 0; s < 4; s++) begin
`uvm_do_with(it, { set_feature_rw == 0; clear_feature_rw == 0;
suspended == s[0]; bus_reset == s[1];
wake_event == 1; })
end
endtask
endclass
// The RACE sequence: a bus reset arriving in the same cycle as a wake
// event. Section 3 found this by hand; here it is deliberate.
class reset_wake_race_seq extends uvm_sequence #(wakeup_item);
`uvm_object_utils(reset_wake_race_seq)
rand int unsigned offset; // where in the K the reset lands
constraint c_off { offset inside {[0:K_HOLD_TICKS]}; }
task body();
wakeup_item it;
`uvm_do_with(it, { set_feature_rw == 1; }) // arm
`uvm_do_with(it, { suspended == 1; wake_event == 1; }) // start a K
repeat (offset)
`uvm_do_with(it, { suspended == 1; wake_event == 0; })
// THE race. offset == 0 is the same-cycle case that section 3's second
// gap lived in; every other offset is the in-progress case of the first.
`uvm_do_with(it, { bus_reset == 1; wake_event == 1; })
endtask
endclass
class wakeup_scoreboard extends uvm_scoreboard;
`uvm_component_utils(wakeup_scoreboard)
local bit m_armed, m_was_driving;
function void write(wakeup_txn t);
// THE property, and the severity is deliberate: a device that wakes a
// host it was never permitted to wake is not a protocol error with a
// retry path. It is a machine coming out of a bag hot.
if (!m_was_driving && t.drive_k && !m_armed)
`uvm_fatal("WAKE/UNARMED",
"a device that was never armed drove the bus")
// A bus reset outranks everything the device was doing.
if (t.bus_reset && t.drive_k)
`uvm_error("WAKE/RESET",
"the device kept driving K through a bus reset")
// The two refusals stay distinguishable.
if (t.wake_event && !t.drive_k) begin
if (!m_armed && t.outcome != WK_REFUSED_UNARMED)
`uvm_error("WAKE/REASON", "an unarmed refusal reported as something else")
if (m_armed && !t.suspended && t.outcome != WK_REFUSED_AWAKE)
`uvm_error("WAKE/REASON", "an awake-bus refusal reported as something else")
end
if (t.bus_reset || t.clear_feature_rw) m_armed = 0;
else if (t.set_feature_rw) m_armed = 1;
m_was_driving = t.drive_k;
endfunction
endclass
covergroup wakeup_cg with function sample(
bit armed, bit suspended, bit wake, bit reset, bit driving);
// The 2x2 that section 1 is entirely about. Three of these four must
// produce nothing, and a run that misses any of them has not tested the
// gate -- it has tested one side of it.
x_fences : cross
coverpoint armed { bins no = {0}; bins yes = {1}; },
coverpoint suspended { bins no = {0}; bins yes = {1}; }
iff (wake);
// Section 3's two gaps, as bins. cp_reset_start is the same-cycle race
// and cp_reset_mid is the reset landing inside a drive.
cp_reset_start : coverpoint {reset, wake, driving} { bins race = {3'b110}; }
cp_reset_mid : coverpoint {reset, driving} { bins mid = {2'b11}; }
endgroup13. Assertions
// THE property: a wakeup never STARTS unarmed. On the edge, because that
// is the only instant where "was it allowed to begin?" is meaningful.
property p_no_unarmed_start;
@(posedge clk) disable iff (!rst_n)
$rose(drive_k) |-> $past(rw_enabled);
endproperty
a_no_unarmed_start : assert property (p_no_unarmed_start)
else $fatal(1, "a device that was never armed drove the bus");
// And never on a bus that is awake.
property p_no_start_while_awake;
@(posedge clk) disable iff (!rst_n)
$rose(drive_k) |-> $past(suspended);
endproperty
a_no_start_while_awake : assert property (p_no_start_while_awake);
// A bus reset outranks both the drive and the start. Mutations W4 and W5.
property p_reset_wins;
@(posedge clk) disable iff (!rst_n)
bus_reset |=> !drive_k;
endproperty
a_reset_wins : assert property (p_reset_wins)
else $error("a bus reset did not stop the device driving");
// Arming does not survive a reset. Mutation W3.
property p_reset_disarms;
@(posedge clk) disable iff (!rst_n)
bus_reset |=> !rw_enabled;
endproperty
a_reset_disarms : assert property (p_reset_disarms);p_reset_wins covers both of §3's gaps in one line — it does not care whether the drive was starting or continuing, only that a reset ends it. That is the property I should have written first, and writing it as two separate design conditions is what made finding them sequential rather than simultaneous.
These were written but not simulated; Icarus supports no concurrent assertions.
14. Debugging: the Laptop That Wakes in the Bag
The report: a laptop wakes from sleep on its own, in a bag, repeatedly, and flattens its battery. It happens with one particular peripheral attached and not otherwise.
The procedure:
1. Establish that it is a wakeup and not a failure to sleep. Chapter 19.2 §14's back-powering prevents sleep; this ends one. The distinction is whether the machine ever went to sleep, and every OS logs that separately from what woke it.
2. Read what the OS says woke it. Most name the device. If a USB device is named, the question becomes whether it was entitled to.
3. Check whether the host armed it. GetStatus bit 1 reports the armed state, and OS tooling generally exposes it as a per-device wakeup-enabled flag. A device waking the host while that flag is clear is mutation W1 in the field — the device is signalling without permission.
4. Distinguish it from a device that was armed and is over-eager. An armed device waking on spurious input is a sensitivity problem in the device's own wake logic. An unarmed device waking at all is a conformance failure, and the fixes are completely different: one is tuning, the other is a firmware bug.
5. Confirm the disarm-across-reset behaviour. A device that stays armed across a bus reset (mutation W3) will be armed after a reconnect the host never re-armed it through — which presents exactly as "it only does this after it has been unplugged and replugged."
6. The workaround is real and worth knowing. Clearing the wakeup-enabled flag for that device is ClearFeature(DEVICE_REMOTE_WAKEUP), and it is exposed by most systems. It disables a legitimate capability to work around a device that misuses it, which is the right trade until the firmware is fixed.
15. Common Misconceptions
"USB devices can interrupt the host." One mechanism can, under two conditions, after explicit permission (§1). Everything else in the protocol is polled.
"Remote wakeup is always available." The host must arm it with SetFeature(DEVICE_REMOTE_WAKEUP) (§1, §2). Mutation W1, 146 900 errors.
"Arming is a one-time configuration." It is cleared by every bus reset (§2). Mutation W3, 132 595 errors.
"Enabling remote wakeup is free." It costs five times the suspended current on every armed device (19.3 §3) — 2.5 mA instead of 500 µA.
"A device can signal a wakeup whenever it has something to report." Only while the bus is suspended (§1). On a running bus it is a collision. Mutation W2, 170 744 errors.
"A wake event disappearing should stop the signalling." The host has already begun responding; stopping leaves a half-finished resume (§4).
"A bus reset only needs to clear the armed bit." It must also abort a drive in progress and block a start in the same cycle (§3). Mutations W4 and W5.
"One 'wake refused' counter is enough." Unarmed and bus-awake are different bugs with different fixes (§1). Mutation W6, 100 483 errors.
16. Exercises
1. §10 found a property that was slightly too strong and hid two defects. Construct the general rule for when "X must never happen" should be rewritten as "X must never start", and apply it to the properties in 19.2 §13.
2. §3's two gaps were found sequentially. Determine whether a single property — bus_reset |=> !drive_k — would have found both at once, and say why the design was written as two conditions rather than one.
3. W7 drives K for 15 ms instead of 5 and scores highest in the matrix. Compute the extra energy that costs on a device suspended at 2.5 mA, and say whether it is ever observable.
4. Write the SVA property that catches W6 — conflated refusal counters — without referring to either counter by name.
5. A hub has four armed devices below it. Using 18.5's budget and 19.3's suspend currents, determine whether a bus-powered hub can support them all in suspend, and what happens if it cannot.
6. The Linux kernel exposes usb_wakeup_enabled_descendants, which counts armed devices at or below a node. Determine what a hub must do differently when that count is non-zero, and which chapter of this module the answer belongs to.
17. Summary
Remote wakeup is the single exception to USB's single-master rule (§1), and it is fenced by two independent conditions: the host must have armed the device, and the bus must actually be suspended. Neither alone is sufficient, and the two refusals are different bugs that the design reports separately (W6, 100 483 errors).
An unarmed device that wakes the host wakes a machine its owner deliberately put to sleep (§1) — the failure behind every laptop that comes out of a bag hot. Mutation W1, 146 900 errors, and uvm_fatal rather than uvm_error in §12.
Arming does not survive a bus reset (§2). It is consent, not bookkeeping: a device that kept the bit would be armed without the current host having agreed — and arming costs five times the suspended current on every device that has it.
A bus reset had to be made to win in two separate places (§3): aborting a drive in progress, and blocking a wakeup that would otherwise start in the same cycle. The second was found only after the first was fixed, because the louder violation was masking it.
All three HDL implementations were simulated (§18) and seven mutations died in all three (§11), verified over 384 one-step transitions — twelve positions against all 32 input combinations — and 4096 interleavings of the two conditions the gate is built from (§10).
The central property was initially too strong (§10). "An unarmed device never drives the bus" fired on a device that was legitimately armed when it started and disarmed mid-signal, which §4 says is correct. Rewriting it to check the start is what let it see the two real gaps underneath. A property that is slightly too strong does not merely produce noise — it hides what is beneath it.
And the largest count belongs to the mildest mutation again (§11): W7 drives K for the specification's ceiling instead of the programmed duration, which is legal, wasteful, and works.
18. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_remote_wakeup | rw_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 384 + 4096 | ✅ all seven |
| SystemVerilog | usb_remote_wakeup_sv | rw_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 384 + 4096 | ✅ all seven |
| VHDL-2008 | usb_remote_wakeup_vhdl | rw_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 384 + 4096 | ✅ all seven |
| UVM (§12) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§13) | — | — | ❌ unsupported by Icarus | ❌ | — |
The wake_outcome output was added to all three designs at once, rather than to SystemVerilog and VHDL first and Verilog later. Chapters 19.2 §11 and 19.3 §13 each cost a round of mutation re-measurement to discover that one design exposing less than the others makes the tri-HDL comparison measure the testbenches. Doing it up front here is that lesson applied rather than repeated.
VHDL's randomised tail differs (5568 issued against 5597) because the three benches draw from different generators. The 384 transitions and 4096 interleavings are identical by construction.
19. What Comes Next
Five chapters have been about one device, or one port, or one link.
Chapter 19.6 — Power Budgeting is about all of them at once. The host walks its entire tree, subtracts each device's declared draw from the hub above it, and decides what it can still afford — and the arithmetic has a term this module has not needed yet: a device that is enumerated but not configured still costs one unit load, because it is entitled to one whether it is using it or not.
Linux does this walk in a loop with a saturating floor and a warning, and the edge cases are exactly where 19.1's per-device rule and 18.5's per-hub rule meet. Where the two disagree is the last thing this module has to settle.
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
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.
- 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.
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.
