USB · Module 19
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.
Chapter 19.3 put a device to sleep by measuring silence. This wakes it up, and the first thing to notice is that the wakeup is not a packet.
A suspended device is not listening for tokens. It is running on 500 µA with most of itself powered down, and there is no address to send anything to. So the host does not transmit a message — it holds an electrical state on the data lines long enough that a device running on microamps cannot miss it.
1. Three Phases, and They Are Not Interchangeable
1. DRIVE K the host holds the opposite of the idle state.
This is the wakeup itself.
2. EOP SE0 for two bit times — a low-speed end-of-packet.
This says the resume signalling has ENDED.
3. IDLE (J) the bus returns to idle and normal traffic resumes.The middle phase is the one people leave out, and mutation R2 — skipping it — dies 46 657 times.
Without the EOP the device cannot tell a finished resume from one still in progress. K is a level, not an edge: a device that sees K and then sees K removed has learned nothing about whether the host is done, because the host might simply be between symbols. The EOP is an explicit, unambiguous "that was the end."
And exactly one phase is driven at a time. The three outputs are decodes of one phase register, not three independent enables — because two enables asserted at once is not a protocol error, it is a short on a real bus. Mutation R5 asserts J during the EOP and dies 9024 times against a property that counts drivers rather than comparing values.
2. Twenty Milliseconds, or Forty
The specification requires the host to drive resume for at least 20 ms, and gives no upper bound.
Linux drives it for 40, and the comment explaining why is unusually candid:
(b) Some (many) devices actually need more than 20 ms of resume
signalling, and while we can argue that's against the USB
Specification, we don't have control over which devices a certification
laboratory will be using for certification. If CertLab uses a device
which was tested against Windows and that happens to have relaxed
resume signalling rules, we might fall into situations where we fail
interoperability and electrical tests.
#define USB_RESUME_TIMEOUT 40 /* ms */This is why the design has two parameters and not one:
parameter integer RESUME_TICKS = 40, // what this host drives (ms)
parameter integer SPEC_MIN_TICKS = 20, // the specification's floor (ms)RESUME_TICKS is what we do. SPEC_MIN_TICKS is what we must not go below. A design that hard-codes one number cannot express the difference, and the difference is the entire chapter.
Mutation R1 drives only the specification minimum. It is spec-conformant — and it dies 71 214 times, because the testbench is checking what the design promised rather than what the standard permits.
A design can be simultaneously standard-compliant and wrong for the population it has to work with. The only way to keep that visible in RTL is to carry both numbers.
3. What a Truncated Resume Costs
A resume cut short below the floor is a protocol violation, and the design reports it.
It has to, because nothing else will. The device that fails to wake stays asleep. It does not report an error — it is not awake to report one. The host, having driven what it thought was a resume, sees a device that has stopped responding and concludes the device has gone away.
spec_violation is sticky and violations counts. Mutation R4 removes the reporting and dies 469 343 times — the largest count in Module 19 — because once the model expects a violation the design never records, every subsequent tick disagrees.
And the EOP is the tell. Whatever else happens, the end-of-packet must never be reached with K held for less than the full duration, because a device seeing a complete-looking resume will act on it:
// 2. The EOP is only ever reached after a full-length K. A resume
// whose K was short must never reach the end-of-packet, because
// the device would treat a too-short wakeup as a complete one.
if (drive_se0)
check(k_ticks >= RT,
"the EOP was reached without holding K for the full duration");4. A Resume in Progress Is Not Restarted
A second start_resume arriving mid-sequence is ignored.
The reason is a livelock rather than a correctness subtlety. Restarting resets the K counter — so a host that retriggered every millisecond would drive K forever and never reach the EOP that ends it. The bus would sit in permanent resume signalling, which is indistinguishable from a stuck driver.
Mutation R3 makes a retrigger restart the count and dies 126 131 times, with the sweep in §7 supplying 71 456 retriggers inside the K phase to catch it with.
5. The Sequence, Drawn
Nothing in that diagram is a packet until the last arrow. Everything before it is a voltage held on two wires for a length of time.
6. Verilog-2005
// usb_resume_sequencer -- waking a suspended device, and the twenty
// milliseconds that turned into forty.
//
// Chapter 19.3 put a device to sleep by measuring silence. This wakes it,
// and the waking is not a packet -- there is nothing to address a packet to,
// because a suspended device is not listening for tokens. It is an
// ELECTRICAL signal held on the data lines long enough that a device running
// on microamps cannot miss it.
//
// The sequence has three phases and they are not interchangeable:
//
// 1. DRIVE K the host holds the opposite of the idle state on the bus.
// This is the wakeup itself, and it must be held for a long
// time by bus standards -- see the note below.
// 2. EOP a low-speed end-of-packet: SE0 for two bit times, which
// tells the device the resume signalling has ENDED and normal
// traffic is about to start. Without it the device cannot
// tell a finished resume from a resume still in progress.
// 3. IDLE (J) the bus returns to its idle state and traffic resumes.
//
// TWENTY MILLISECONDS, OR FORTY
//
// The specification requires the host to drive resume for AT LEAST 20 ms and
// gives no upper bound. Linux drives it for 40, and the comment explaining
// why is worth reading in full because it is unusually candid:
//
// (b) Some (many) devices actually need more than 20 ms of resume
// signalling, and while we can argue that's against the USB
// Specification, we don't have control over which devices a
// certification laboratory will be using for certification. If CertLab
// uses a device which was tested against Windows and that happens to
// have relaxed resume signalling rules, we might fall into situations
// where we fail interoperability and electrical tests.
//
// #define USB_RESUME_TIMEOUT 40 /* ms */
//
// So RESUME_TICKS is what this host actually drives and SPEC_MIN_TICKS is
// the floor the specification sets. They are separate parameters ON PURPOSE:
// a design that hard-codes one number cannot express the difference between
// "what we do" and "what we must not go below", and this block's whole
// reason for existing is that those are different numbers.
//
// A resume cut short below SPEC_MIN_TICKS is a protocol violation, and it is
// reported rather than silently tolerated -- because the device it fails to
// wake will simply stay asleep, and nothing else in the system will say why.
module usb_resume_sequencer #(
parameter integer RESUME_TICKS = 40, // what this host drives (ms)
parameter integer SPEC_MIN_TICKS = 20, // the specification's floor (ms)
parameter integer EOP_TICKS = 2 // SE0 for two bit times
) (
input wire clk,
input wire rst_n,
input wire start_resume, // the host decides to wake the bus
input wire abort, // something cut the sequence short
output reg [1:0] phase,
output wire drive_k, // the wakeup signal itself
output wire drive_se0, // the end-of-packet
output wire drive_j, // idle -- normal traffic may follow
output reg [15:0] k_ticks, // how long K has been held
output reg resume_complete,// one-tick pulse: the bus is awake
output reg spec_violation, // sticky: a resume below the floor
output reg [31:0] resumes_done,
output reg [31:0] violations
);
localparam [1:0] R_IDLE = 2'd0, R_K = 2'd1, R_EOP = 2'd2, R_DONE = 2'd3;
// The three drive states are mutually exclusive by construction: they are
// decodes of one phase register, not three independent enables. Three
// enables would admit the one state that must never occur -- two of them
// asserted at once, which on a real bus is a short.
assign drive_k = (phase == R_K);
assign drive_se0 = (phase == R_EOP);
assign drive_j = (phase == R_IDLE) || (phase == R_DONE);
reg [15:0] eop_ticks;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase <= R_IDLE;
k_ticks <= 16'd0;
eop_ticks <= 16'd0;
resume_complete <= 1'b0;
spec_violation <= 1'b0;
resumes_done <= 32'd0;
violations <= 32'd0;
end else begin
resume_complete <= 1'b0;
case (phase)
R_IDLE: begin
if (start_resume && !abort) begin
phase <= R_K;
k_ticks <= 16'd0;
eop_ticks <= 16'd0;
end
end
R_K: begin
if (abort) begin
// Cut short. If K was held for less than the specification's
// floor, this is a violation and it is RECORDED -- the device
// that fails to wake will not report anything, so if this
// block does not, nothing does.
if (k_ticks < SPEC_MIN_TICKS[15:0]) begin
spec_violation <= 1'b1;
violations <= violations + 32'd1;
end
phase <= R_IDLE;
end else if (k_ticks + 16'd1 >= RESUME_TICKS[15:0]) begin
k_ticks <= k_ticks + 16'd1;
phase <= R_EOP;
end else begin
// A start arriving mid-sequence is IGNORED. A resume already in
// progress must not be restarted: restarting resets k_ticks, and
// a host that retriggered every millisecond would drive K
// forever without ever reaching the EOP that ends it.
k_ticks <= k_ticks + 16'd1;
end
end
R_EOP: begin
if (abort) begin
// Aborting during the EOP is not a spec violation on the K
// duration -- K was already held long enough -- but the resume
// did not complete, so no completion is announced.
phase <= R_IDLE;
end else if (eop_ticks + 16'd1 >= EOP_TICKS[15:0]) begin
phase <= R_DONE;
end else begin
eop_ticks <= eop_ticks + 16'd1;
end
end
R_DONE: begin
resume_complete <= 1'b1;
resumes_done <= resumes_done + 32'd1;
phase <= R_IDLE;
end
default: phase <= R_IDLE;
endcase
end
end
endmoduledrive_k, drive_se0 and drive_j are decodes, not registers. That is what makes §1's electrical property true by construction rather than by care — there is no sequence of events that can assert two of them, because they are three mutually exclusive comparisons against one value.
7. SystemVerilog
package usb_resume_pkg;
// The three phases of a resume, plus the one-tick completion. They are
// not interchangeable and they are not independent enables: exactly one
// is driven at a time, because two at once is a short on a real bus.
typedef enum logic [1:0] {
R_IDLE, // driving J -- normal traffic may flow
R_K, // driving K -- this IS the wakeup
R_EOP, // driving SE0 -- the resume has ended
R_DONE // one tick: announce completion, return to idle
} resume_phase_e;
endpackage
// usb_resume_sequencer_sv -- waking a suspended device, and the twenty
// milliseconds that turned into forty.
//
// Chapter 19.3 put a device to sleep by measuring silence. This wakes it,
// and the waking is not a packet -- there is nothing to address a packet to,
// because a suspended device is not listening for tokens. It is an
// ELECTRICAL signal held on the data lines long enough that a device running
// on microamps cannot miss it.
//
// The sequence has three phases and they are not interchangeable:
//
// 1. DRIVE K the host holds the opposite of the idle state on the bus.
// This is the wakeup itself, and it must be held for a long
// time by bus standards -- see the note below.
// 2. EOP a low-speed end-of-packet: SE0 for two bit times, which
// tells the device the resume signalling has ENDED and normal
// traffic is about to start. Without it the device cannot
// tell a finished resume from a resume still in progress.
// 3. IDLE (J) the bus returns to its idle state and traffic resumes.
//
// TWENTY MILLISECONDS, OR FORTY
//
// The specification requires the host to drive resume for AT LEAST 20 ms and
// gives no upper bound. Linux drives it for 40, and the comment explaining
// why is worth reading in full because it is unusually candid:
//
// (b) Some (many) devices actually need more than 20 ms of resume
// signalling, and while we can argue that's against the USB
// Specification, we don't have control over which devices a
// certification laboratory will be using for certification. If CertLab
// uses a device which was tested against Windows and that happens to
// have relaxed resume signalling rules, we might fall into situations
// where we fail interoperability and electrical tests.
//
// #define USB_RESUME_TIMEOUT 40 /* ms */
//
// So RESUME_TICKS is what this host actually drives and SPEC_MIN_TICKS is
// the floor the specification sets. They are separate parameters ON PURPOSE:
// a design that hard-codes one number cannot express the difference between
// "what we do" and "what we must not go below", and this block's whole
// reason for existing is that those are different numbers.
//
// A resume cut short below SPEC_MIN_TICKS is a protocol violation, and it is
// reported rather than silently tolerated -- because the device it fails to
// wake will simply stay asleep, and nothing else in the system will say why.
module usb_resume_sequencer_sv
import usb_resume_pkg::*;
#(
parameter int unsigned RESUME_TICKS = 40, // what this host drives (ms)
parameter int unsigned SPEC_MIN_TICKS = 20, // the spec's floor (ms)
parameter int unsigned EOP_TICKS = 2 // SE0 for two bit times
) (
input logic clk,
input logic rst_n,
input logic start_resume, // the host decides to wake the bus
input logic abort, // something cut the sequence short
output resume_phase_e phase,
output logic drive_k, // the wakeup signal itself
output logic drive_se0, // the end-of-packet
output logic drive_j, // idle -- normal traffic may follow
output logic [15:0] k_ticks, // how long K has been held
output logic resume_complete,// one-tick pulse: the bus is awake
output logic spec_violation, // sticky: a resume below the floor
output logic [31:0] resumes_done,
output logic [31:0] violations
);
initial begin
if (SPEC_MIN_TICKS > RESUME_TICKS)
$fatal(1, "RESUME_TICKS=%0d is below the specification floor of %0d",
RESUME_TICKS, SPEC_MIN_TICKS);
if (EOP_TICKS < 1)
$fatal(1, "an end-of-packet with no duration does not end anything");
end
// The three drive states are mutually exclusive by construction: they are
// decodes of one phase register, not three independent enables. Three
// enables would admit the one state that must never occur -- two of them
// asserted at once, which on a real bus is a short.
assign drive_k = (phase == R_K);
assign drive_se0 = (phase == R_EOP);
assign drive_j = (phase == R_IDLE) || (phase == R_DONE);
logic [15:0] eop_ticks;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase <= R_IDLE;
k_ticks <= 16'd0;
eop_ticks <= 16'd0;
resume_complete <= 1'b0;
spec_violation <= 1'b0;
resumes_done <= 32'd0;
violations <= 32'd0;
end else begin
resume_complete <= 1'b0;
case (phase)
R_IDLE: begin
if (start_resume && !abort) begin
phase <= R_K;
k_ticks <= 16'd0;
eop_ticks <= 16'd0;
end
end
R_K: begin
if (abort) begin
// Cut short. If K was held for less than the specification's
// floor, this is a violation and it is RECORDED -- the device
// that fails to wake will not report anything, so if this
// block does not, nothing does.
if (k_ticks < 16'(SPEC_MIN_TICKS)) begin
spec_violation <= 1'b1;
violations <= violations + 32'd1;
end
phase <= R_IDLE;
end else if (k_ticks + 16'd1 >= 16'(RESUME_TICKS)) begin
k_ticks <= k_ticks + 16'd1;
phase <= R_EOP;
end else begin
// A start arriving mid-sequence is IGNORED. A resume already in
// progress must not be restarted: restarting resets k_ticks, and
// a host that retriggered every millisecond would drive K
// forever without ever reaching the EOP that ends it.
k_ticks <= k_ticks + 16'd1;
end
end
R_EOP: begin
if (abort) begin
// Aborting during the EOP is not a spec violation on the K
// duration -- K was already held long enough -- but the resume
// did not complete, so no completion is announced.
phase <= R_IDLE;
end else if (eop_ticks + 16'd1 >= 16'(EOP_TICKS)) begin
phase <= R_DONE;
end else begin
eop_ticks <= eop_ticks + 16'd1;
end
end
R_DONE: begin
resume_complete <= 1'b1;
resumes_done <= resumes_done + 32'd1;
phase <= R_IDLE;
end
default: phase <= R_IDLE;
endcase
end
end
endmoduleThe elaboration guard is the one worth having here: SPEC_MIN_TICKS > RESUME_TICKS is a parameterisation that says we drive less than the standard requires, which is not a configuration anyone should be able to build by accident.
8. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_resume_pkg is
-- The three phases of a resume, plus the one-tick completion. They are
-- not interchangeable and they are not independent enables: exactly one
-- is driven at a time, because two at once is a short on a real bus.
type resume_phase_t is (
R_IDLE, -- driving J -- normal traffic may flow
R_K, -- driving K -- this IS the wakeup
R_EOP, -- driving SE0 -- the resume has ended
R_DONE -- one tick: announce completion, return to idle
);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_resume_pkg.all;
-- usb_resume_sequencer_vhdl -- waking a suspended device, and the twenty
-- milliseconds that turned into forty.
--
-- Chapter 19.3 put a device to sleep by measuring silence. This wakes it,
-- and the waking is not a packet -- there is nothing to address a packet to,
-- because a suspended device is not listening for tokens. It is an
-- ELECTRICAL signal held on the data lines long enough that a device running
-- on microamps cannot miss it.
--
-- The sequence has three phases and they are not interchangeable:
--
-- 1. DRIVE K the host holds the opposite of the idle state on the bus.
-- 2. EOP a low-speed end-of-packet: SE0 for two bit times, which
-- tells the device the resume signalling has ENDED.
-- 3. IDLE (J) the bus returns to its idle state and traffic resumes.
--
-- TWENTY MILLISECONDS, OR FORTY
--
-- The specification requires the host to drive resume for AT LEAST 20 ms and
-- gives no upper bound. Linux drives it for 40, and the comment explaining
-- why is worth reading in full because it is unusually candid:
--
-- (b) Some (many) devices actually need more than 20 ms of resume
-- signalling, and while we can argue that's against the USB
-- Specification, we don't have control over which devices a
-- certification laboratory will be using for certification.
--
-- #define USB_RESUME_TIMEOUT 40 /* ms */
--
-- So RESUME_TICKS is what this host actually drives and SPEC_MIN_TICKS is
-- the floor the specification sets. They are separate generics ON PURPOSE:
-- a design that hard-codes one number cannot express the difference between
-- "what we do" and "what we must not go below".
entity usb_resume_sequencer_vhdl is
generic (
RESUME_TICKS : positive := 40; -- what this host drives (ms)
SPEC_MIN_TICKS : positive := 20; -- the specification's floor (ms)
EOP_TICKS : positive := 2 -- SE0 for two bit times
);
port (
clk : in std_logic;
rst_n : in std_logic;
start_resume : in std_logic;
abort_in : in std_logic;
phase : out resume_phase_t;
drive_k : out std_logic;
drive_se0 : out std_logic;
drive_j : out std_logic;
k_ticks : out unsigned(15 downto 0);
resume_complete : out std_logic;
spec_violation : out std_logic;
resumes_done : out unsigned(31 downto 0);
violations : out unsigned(31 downto 0)
);
end entity;
architecture rtl of usb_resume_sequencer_vhdl is
signal ph_r : resume_phase_t := R_IDLE;
signal k_r : unsigned(15 downto 0) := (others => '0');
signal eop_r : unsigned(15 downto 0) := (others => '0');
signal done_r : std_logic := '0';
signal sviol_r : std_logic := '0';
signal rcnt_r : unsigned(31 downto 0) := (others => '0');
signal vcnt_r : unsigned(31 downto 0) := (others => '0');
begin
assert SPEC_MIN_TICKS <= RESUME_TICKS
report "RESUME_TICKS is below the specification floor" severity failure;
-- The three drive states are mutually exclusive by construction: they are
-- decodes of one phase signal, not three independent enables. Three
-- enables would admit the one state that must never occur -- two of them
-- asserted at once, which on a real bus is a short.
drive_k <= '1' when ph_r = R_K else '0';
drive_se0 <= '1' when ph_r = R_EOP else '0';
drive_j <= '1' when (ph_r = R_IDLE or ph_r = R_DONE) else '0';
phase <= ph_r;
k_ticks <= k_r;
resume_complete <= done_r;
spec_violation <= sviol_r;
resumes_done <= rcnt_r;
violations <= vcnt_r;
process (clk, rst_n)
begin
if rst_n = '0' then
ph_r <= R_IDLE; k_r <= (others => '0'); eop_r <= (others => '0');
done_r <= '0'; sviol_r <= '0';
rcnt_r <= (others => '0'); vcnt_r <= (others => '0');
elsif rising_edge(clk) then
done_r <= '0';
case ph_r is
when R_IDLE =>
if start_resume = '1' and abort_in = '0' then
ph_r <= R_K; k_r <= (others => '0'); eop_r <= (others => '0');
end if;
when R_K =>
if abort_in = '1' then
-- Cut short. If K was held for less than the specification's
-- floor, this is a violation and it is RECORDED -- the device
-- that fails to wake will not report anything, so if this
-- block does not, nothing does.
if k_r < to_unsigned(SPEC_MIN_TICKS, 16) then
sviol_r <= '1';
vcnt_r <= vcnt_r + 1;
end if;
ph_r <= R_IDLE;
elsif k_r + 1 >= to_unsigned(RESUME_TICKS, 16) then
k_r <= k_r + 1;
ph_r <= R_EOP;
else
-- A start arriving mid-sequence is IGNORED. A resume already in
-- progress must not be restarted: restarting resets k_ticks, and
-- a host that retriggered every millisecond would drive K
-- forever without ever reaching the EOP that ends it.
k_r <= k_r + 1;
end if;
when R_EOP =>
if abort_in = '1' then
-- Aborting during the EOP is not a spec violation on the K
-- duration -- K was already held long enough -- but the resume
-- did not complete, so no completion is announced.
ph_r <= R_IDLE;
elsif eop_r + 1 >= to_unsigned(EOP_TICKS, 16) then
ph_r <= R_DONE;
else
eop_r <= eop_r + 1;
end if;
when R_DONE =>
done_r <= '1';
rcnt_r <= rcnt_r + 1;
ph_r <= R_IDLE;
end case;
end if;
end process;
end architecture;abort_in rather than abort — VHDL reserves abort in no standard sense, but the surrounding if … then syntax makes a bare abort read as a statement. A rename forced by readability rather than by the language.
And the case ph_r is over an enumerated type needs no when others: the four states are the whole type, so VHDL rejects an incomplete case at analysis time. The Verilog needs a default because [1:0] has four values and names four — but a two-bit register with three named states would silently latch on the fourth.
9. The Waveform
K, then the end-of-packet, then idle — and never two at once
10 cyclesAdd the three drive rows at any tick and the answer is always 1. That is §1's electrical property, and it is worth seeing as a picture because the failure it prevents — two drivers fighting — does not look like a protocol bug in a trace. It looks like a damaged transceiver.
10. The Testbench: Every Start-and-Abort Interleaving
The sequencer has two asynchronous inputs — wake the bus and something interrupted it — and the interesting behaviour is entirely in how they interleave.
So the sweep enumerates every interleaving. Every combination of start and abort events across an 8-tick window: 2⁸ start patterns × 2⁸ abort patterns = 65 536 scenarios, 524 288 ticks.
// Every combination of start and abort events over an 8-tick window:
// 2^8 start patterns x 2^8 abort patterns = 65536 scenarios, 524288
// ticks. This covers every possible interleaving of "wake the bus" and
// "something interrupted it", including every retrigger mid-sequence
// and every abort at every point in the sequence.
for (sp=0; sp<(1<<W); sp=sp+1)
for (ap=0; ap<(1<<W); ap=ap+1) begin
hard_reset;
for (t=0; t<W; t=t+1) tick(sp[t], ap[t]);
n_scen = n_scen + 1;
endThree properties are checked against no model:
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE electrical one: never two drive states at once. On a real
// bus that is a short, not a protocol error.
check((drive_k + drive_se0 + drive_j) == 1,
"two drive states asserted at once: that is a bus short");
// 2. The EOP is only ever reached after a full-length K. A resume
// whose K was short must never reach the end-of-packet, because
// the device would treat a too-short wakeup as a complete one.
if (drive_se0)
check(k_ticks >= RT,
"the EOP was reached without holding K for the full duration");
// 3. And therefore never below the specification's floor either.
if (drive_se0)
check(k_ticks >= SMIN,
"the EOP was reached with K held below the spec minimum");Property 1 counts drivers rather than comparing them to expected values — which is the right shape for a constraint whose violation is electrical rather than logical.
Measured reach:
exhaustive start/abort pattern sweep: 65536 of 65536 scenarios verified
Verilog / SystemVerilog:
REACH: scenarios=65536 completions=749 violations=205
aborts-in-K=71248 retriggers-in-K=71456
[Verilog] usb_resume_sequencer: 0 errors — PASS
VHDL:
REACH: scenarios=65536 completions=775 violations=173
aborts-in-K=71219 retriggers-in-K=71508
[VHDL] usb_resume_sequencer_vhdl: 0 errors — PASS11. The Fifth Check to Accuse Correct Hardware
The directed section failed on first run — three errors — and the design was right again.
for (t=0; t<EOPT-1; t=t+1) tick(1'b0, 1'b0);
check(drive_j, "and the EOP is followed by idle"); // FAILEDEOPT ticks are spent in the EOP phase, not EOPT-1: the first tick enters it and the last leaves it. After EOPT-1 ticks the sequencer was still driving SE0 — exactly as it should have been — and the check asserted the bus was already idle.
// EOPT ticks are spent IN the EOP phase -- the first enters it and the
// last leaves it. An earlier version of this loop ran EOPT-1 and then
// asserted the bus was idle, which failed against a correct design:
// the sequencer was still driving SE0, exactly as it should have been.
for (t=0; t<EOPT; t=t+1) tick(1'b0, 1'b0);The exhaustive sweep had already passed all 65 536 scenarios while this was broken, because the sweep never asserts where the sequencer should be after a hand-counted number of ticks — it compares against a model that counts the same way the design does.
This is the fifth testbench check in Modules 18 and 19 to fire against correct hardware, after 18.2 §12 (a missing qualifier), 18.2 §14 (a missing exception), 18.3 §10 (the wrong instant), and 18.5 §13 (the wrong one of three rules). All five were hand-written directed checks; none was a model comparison. Directed checks encode a human's arithmetic, and that is exactly what makes them worth having and what makes them wrong.
12. Mutation Testing — Across All Three Languages
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| R1 | drive only the specification minimum, not what was promised | 71214 | 71214 | 71545 |
| R2 | the EOP is skipped entirely | 46657 | 46657 | 46415 |
| R3 | a start mid-K restarts the sequence | 126131 | 126131 | 126730 |
| R4 | a truncated resume is not reported | 469343 | 469343 | 469585 |
| R5 | J is driven during the EOP — two drivers at once | 9024 | 9024 | 9102 |
| R6 | the EOP completes in one tick regardless of EOP_TICKS | 29956 | 29956 | 29945 |
| R7 | the completion becomes a level rather than a pulse | 61161 | 61161 | 61230 |
R4 is the largest count in Module 19 at 469 343, and the reason is structural rather than semantic: spec_violation is sticky, so once the model expects it and the design never sets it, every subsequent tick of the run disagrees. A sticky output makes its own mutation loud.
R5 is the smallest at 9024 and is the only one whose failure is electrical. It is reached only during the EOP phase, which is two ticks out of every eight-tick resume — rare, and caught every single time, because the property that catches it is checked unconditionally rather than only when something looks wrong.
R1 is the chapter. It produces a host that is standard-compliant and fails the devices §2 exists to accommodate — and it is caught only because the design carries both numbers and the testbench checks against the promise rather than the standard.
13. A UVM Environment for an Interleaving Problem
The value here is generating the interleavings a directed test would not think of, and §10 is the measurement of how many there are.
class resume_event_item extends uvm_sequence_item;
`uvm_object_utils(resume_event_item)
rand bit start_resume;
rand bit abort;
// Aborts are rare in the field and central to this block, so the
// generator over-weights them relative to reality. Realistic rates would
// reach section 12's R4 and R5 a handful of times per run.
constraint c_rates {
start_resume dist { 1 := 1, 0 := 5 };
abort dist { 1 := 1, 0 := 9 };
}
endclass
// Aborting at EVERY point in the sequence, deliberately, one point per
// iteration -- the directed complement to the random interleaving above.
class abort_walk_seq extends uvm_sequence #(resume_event_item);
`uvm_object_utils(abort_walk_seq)
rand int unsigned abort_at;
constraint c_at { abort_at inside {[0:RESUME_TICKS+EOP_TICKS+1]}; }
task body();
resume_event_item it;
`uvm_do_with(it, { start_resume == 1; abort == 0; })
for (int i = 0; i < abort_at; i++)
`uvm_do_with(it, { start_resume == 0; abort == 0; })
// THE point: whether this abort is a spec violation depends entirely on
// how far K had got, and that is the one thing the design must get
// right about aborts.
`uvm_do_with(it, { start_resume == 0; abort == 1; })
endtask
endclass
class resume_scoreboard extends uvm_scoreboard;
`uvm_component_utils(resume_scoreboard)
function void write(resume_txn t);
// THE electrical property. Counted, not compared -- its violation is a
// short, and there is no "expected value" that makes two drivers OK.
int unsigned drivers = t.drive_k + t.drive_se0 + t.drive_j;
if (drivers != 1)
`uvm_fatal("RESUME/SHORT", $sformatf(
"%0d drive signals asserted at once", drivers))
// A device seeing the EOP treats the resume as complete. It must
// therefore never follow a K that was too short.
if (t.drive_se0 && t.k_ticks < RESUME_TICKS)
`uvm_error("RESUME/SHORT_K", $sformatf(
"EOP after only %0d ticks of K (need %0d)", t.k_ticks, RESUME_TICKS))
// And the promise, which is stricter than the standard.
if (t.drive_se0 && t.k_ticks < SPEC_MIN_TICKS)
`uvm_error("RESUME/SUBSPEC", "K held below the specification floor")
endfunction
endclass
covergroup resume_cg with function sample(
int unsigned phase, int unsigned k_at_abort, bit retrigger);
// Where in the sequence the abort landed. The bins either side of the
// specification floor are the ones that decide whether it was a
// violation, and they are what section 12's R4 lives in.
cp_abort_point : coverpoint k_at_abort {
bins below_floor = {[0:SPEC_MIN_TICKS-1]};
bins at_floor = {SPEC_MIN_TICKS};
bins above_floor = {[SPEC_MIN_TICKS+1:RESUME_TICKS]};
}
// A start arriving mid-sequence. Section 4's livelock lives here.
cp_retrigger : coverpoint retrigger { bins mid_sequence = {1}; }
cp_phase : coverpoint phase;
x_abort_phase : cross cp_abort_point, cp_phase, cp_retrigger;
endgroup14. Assertions
// THE electrical property: exactly one driver, always.
property p_one_driver;
@(posedge clk) disable iff (!rst_n)
$countones({drive_k, drive_se0, drive_j}) == 1;
endproperty
a_one_driver : assert property (p_one_driver)
else $fatal(1, "two drive states asserted at once: bus short");
// The EOP never follows a short K. Mutations R1 and R2 together.
property p_eop_after_full_k;
@(posedge clk) disable iff (!rst_n)
drive_se0 |-> (k_ticks >= RESUME_TICKS);
endproperty
a_eop_after_full_k : assert property (p_eop_after_full_k);
// A resume in progress is never restarted. Mutation R3.
property p_no_restart;
@(posedge clk) disable iff (!rst_n)
(drive_k && start_resume && !abort) |=> (k_ticks == $past(k_ticks) + 1);
endproperty
a_no_restart : assert property (p_no_restart)
else $error("a start mid-K restarted the sequence");
// The completion is a pulse. Mutation R7.
property p_completion_is_a_pulse;
@(posedge clk) disable iff (!rst_n)
resume_complete |=> !resume_complete;
endproperty
a_completion_is_a_pulse : assert property (p_completion_is_a_pulse);p_one_driver is the property to prove formally: it is a combinational relation over three outputs of a four-state machine, so a prover settles it exhaustively and, unlike simulation, settles it for every parameterisation.
These were written but not simulated; Icarus supports no concurrent assertions.
15. Debugging: the Device That Wakes On One Machine and Not Another
The report: a device resumes correctly from suspend on one host and not on another. Same device, same cable, same OS version. On the failing host it stays asleep and is eventually reported as disconnected.
The procedure:
1. Measure the K duration on both hosts. This is a scope measurement, not a software one — the resume is an electrical level, and nothing in the software stack reports how long it was held.
2. Compare against 20 ms and against 40. A host driving 21 ms is standard-compliant. A device needing 30 will not wake, and neither party is reporting an error: the host drove a legal resume, the device never saw enough of one.
3. Recognise that this is §2 from the other side. Linux's 40 ms exists because this failure is common enough to affect certification. A host that drives the minimum is the failing host here, and it is the compliant one.
4. Check whether the EOP is present. A resume with no end-of-packet (mutation R2) leaves the device unable to tell the signalling ended. Some devices tolerate it; the ones that do not look identical to the duration failure from software.
5. Distinguish from a device that never armed. Chapter 19.5 covers device-initiated wakeup, which fails differently: there the device never signals at all, rather than signalling and being ignored.
16. Common Misconceptions
"Resume is a packet." It is an electrical level held on the data lines (§1) — a suspended device is not listening for packets.
"The K signalling is the whole resume." The EOP is what tells the device the signalling ended (§1). Mutation R2, 46 657 errors.
"20 ms is the resume duration." It is the minimum (§2). Linux drives 40, because many shipped devices need more than the legal floor.
"Driving the specification minimum is correct." It is compliant and it fails devices — and can fail certification (§2, §15). Mutation R1, 71 214 errors.
"A host can retrigger a resume to make it longer." A retrigger is ignored; making it restart creates a livelock in which the EOP is never reached (§4). Mutation R3, 126 131 errors.
"An aborted resume is harmless — just retry." The device that failed to wake reports nothing, so an unreported truncation is invisible (§3). Mutation R4, 469 343 errors.
"Two drive enables asserted together is a protocol error." It is a short (§1). Mutation R5 — and uvm_fatal, not uvm_error.
17. Exercises
1. §2 carries two parameters where most designs carry one. Identify every other place in Module 19 where "what we do" and "what we must not go below" are different numbers, and say which of them the RTL distinguishes.
2. §10 sweeps 4⁸ interleavings of two inputs over 8 ticks. Compute the window needed to cover every distinct behaviour of a RESUME_TICKS = N, EOP_TICKS = M sequencer, and the scenario count that implies.
3. Mutation R5 dies 9024 times, the fewest here, and is the only electrical failure. Explain why a low count and a severe consequence are uncorrelated, using R4 as the contrast.
4. Write the SVA property that catches R6 — an EOP that completes in one tick — without referring to EOP_TICKS.
5. §11 lists five directed checks across two modules that accused correct hardware, and notes none was a model comparison. Characterise what directed checks encode that model comparisons do not, and say why that makes them both valuable and error-prone.
6. A host drives resume for 40 ms to a device that needs 20. Determine what that costs, and whether any device is harmed by the longer signal.
18. Summary
A resume is not a packet (§1). It is an electrical level held long enough that a device running on 500 µA cannot miss it, followed by an end-of-packet that says the signalling has ended rather than paused, followed by idle. Exactly one driver is asserted at a time, and that holds by construction because the three outputs are decodes of one register.
The specification says at least 20 ms. Linux drives 40 (§2), and says plainly that many shipped devices need more than the legal minimum and that a certification laboratory may hand you one. So the design carries two numbers — what it drives and what it must not go below — because a design with one cannot express the difference.
Mutation R1 drives the specification minimum, is fully compliant, and dies 71 214 times against a testbench checking the promise rather than the standard. A design can be standard-compliant and wrong for the population it must work with.
A truncated resume is reported (§3) because nothing else will — the device that fails to wake is not awake to complain. Mutation R4, 469 343 errors, the largest in Module 19.
A resume in progress is not restarted (§4), or a host retriggering every millisecond would drive K forever and never reach the EOP that ends it.
All three HDL implementations were simulated (§19) and seven mutations died in all three (§12), with the sequencer verified over every one of 65 536 start-and-abort interleavings — 2⁸ × 2⁸ patterns, 524 288 ticks, supplying 71 248 aborts and 71 456 retriggers inside the K phase (§10).
And the directed section accused correct hardware for the fifth time in two modules (§11), miscounting the EOP by one tick. All five such failures were hand-written directed checks; none was a model comparison — which is precisely what directed checks are for and why they go wrong.
19. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_resume_sequencer | re_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 65536/65536 | ✅ all seven |
| SystemVerilog | usb_resume_sequencer_sv | re_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 65536/65536 | ✅ all seven |
| VHDL-2008 | usb_resume_sequencer_vhdl | re_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 65536/65536 | ✅ all seven |
| UVM (§13) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§14) | — | — | ❌ unsupported by Icarus | ❌ | — |
The durations in simulation are 4, 3 and 2 ticks against defaults of 40, 20 and 2 ms, so a whole resume fits inside the swept window. The design refers to each number exactly once, which is what makes that substitution safe.
VHDL's randomised tail differs (775 completions against 749, 173 violations against 205) because the three benches draw from different generators. The 65 536 exhaustive scenarios are identical by construction, and every mutation count agrees to within 1 %.
20. What Comes Next
This chapter's resume was host-driven throughout. The host decided to wake the bus, and everything followed from that decision.
Chapter 19.5 — Remote Wakeup inverts it, and in doing so breaks the rule the entire track has rested on: a suspended device may drive the bus unasked, and the host will wake up.
Chapter 2.6 established that only the host initiates. Chapter 18.3 built an entire polled reporting mechanism because of it. Remote wakeup is the one exception, and what it costs to have an exception to a single-master rule — in hardware, in standing current, and in the number of things that can go wrong — is the next chapter.
The fences around it are worth previewing: a wake event does nothing unless the device was explicitly armed by the host and the bus is actually suspended, and those two conditions fail in completely different ways.
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
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.
