USB · Module 18
Port Management
Three uncoordinated sources move a hub port and collide in the same cycle — and the host, whose information is stale by construction, is the one that loses.
Chapter 18.1 built the repeater and left one input conspicuously undefined. port_enabled decides which ports are on the bus in either direction — and nothing in that chapter said where it comes from.
It comes from here: a state machine, one instance per downstream port, living inside the hub controller.
A port is not a bit. It is unpowered, empty, attached but silent, being reset, carrying traffic, or idled — and it is pushed between those by three sources that do not coordinate with each other and can act in the same cycle. Deciding who wins is the whole chapter.
1. Six States, and What Each One Means
| State | Powered | Passes traffic | Meaning |
|---|---|---|---|
| Powered-off | no | no | no power on the port; nothing can be sensed |
| Disconnected | yes | no | powered, nothing attached |
| Disabled | yes | no | a device is attached and the hub will not carry its traffic |
| Resetting | yes | no | the hub is driving reset downstream |
| Enabled | yes | yes | the only state that carries packets |
| Suspended | yes | no | enabled, but idled |
Exactly one state passes traffic. port_enabled to 18.1's repeater is asserted in Enabled and nowhere else — notably not in Resetting. A port being reset is emphatically not carrying packets, and a design that leaves it enabled through the reset will broadcast host traffic into a device that is being held in reset.
Powered-off is not merely "off". With no power on the port there is no pull-up on the device side to sense, so attached means nothing in that state. This is why the FSM treats detection as meaningful only while powered, and why powering a port that already has a device in it must report a connect event — the host has not seen that device yet.
2. There Is No Enable Command
Here is the rule that surprises people, and it is worth stating before the diagram.
A host cannot command a port to become enabled. There is no such operation. A port reaches Enabled by exactly two routes:
- completing a reset, or
- resuming from Suspended.
The host asks for a reset and waits; the enable is a consequence of the reset finishing. The reason is that a reset is what puts the device into a known state — address zero, default configuration — and enabling a port whose device has not been reset would put an unknown device on the bus with an unknown address.
§14 is about how this rule, stated slightly too strongly in the testbench, produced eight failures against a correct design.
3. Three Sources, and Why the Host Loses
Three things move a port, and they do not coordinate:
The host, issuing port commands — SetPortFeature / ClearPortFeature control transfers to the hub controller (18.1 §1).
The device below, attaching and detaching whenever a human moves a cable.
The hub itself, disabling a port for cause — 18.1's babble isolation, and over-current.
They can all act in the same cycle, and the precedence is not "host wins":
detach > over-current > babble > host commandThe reason is informational, not political. The host's command was composed from a port status it read some time ago: it issued a control transfer, which took microseconds to arrive, based on a status poll that happened before that. The host is the only one of the three that can be wrong about the present. A detach is happening now. An over-current is happening now. Babble was detected this cycle. A host command is a statement about a port as it was.
So a host suspend arriving in the same cycle the repeater isolates the port for babbling loses. The port goes to Disabled, not Suspended — and the host finds out through the change bits (§6) on its next poll.
4. The State Machine, Drawn
The two routes into Enabled are the diagram's main assertion. Nothing enters Enabled from Disabled, Disconnected or Powered-off directly, and §13 checks that as a history invariant on every transition of a 20 000-cycle run.
5. Change Bits: Set by Hardware, Cleared by the Host
A hub cannot interrupt the host. Chapter 2.6's single-master model means the hub only ever answers, so everything that happens on a port must wait in a register until the host asks.
Five change bits, each a distinct fact: connect, enable, suspend, reset, over-current. Set by hardware when the event occurs; cleared only when the host explicitly acknowledges. Chapter 18.3 builds the reporting mechanism on top of them.
Connect-change is an edge, not a level — and getting this wrong is mutation M7's 22 009 errors. Driving it from "the port is empty" re-sets the bit every cycle an empty port sits idle, so the host can never clear it. A change bit that cannot be cleared reports nothing: it is indistinguishable from a stuck signal.
And the collision that matters: what if an event sets a bit in the same cycle the host clears it?
change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;SET wins, structurally — the clear is applied first and the set after, so a same-cycle event survives. The alternative loses the event with no trace anywhere. This ordering loses an acknowledgement instead, and the host discovers that on its next poll and clears it again. Losing an ack costs one poll; losing an event costs a device that never gets enumerated.
6. The Hardware, Before Any Language
State retained: the port state, a reset duration counter, the previous attached value, and the five change bits.
Reset duration is a parameter, not a constant. The real value is milliseconds of wall time; the testbenches use 4 cycles so a 784-point sweep finishes quickly, and the design must not care.
port_enabled is asserted in Enabled only. Not Resetting, not Suspended.
attached is qualified by power. With the port unpowered, attached is not merely ignored — it is meaningless, and the stored history resets so that powering a populated port reports a connect event.
The precedence chain runs before the state table, in the order of §3, so that every state inherits it without restating it six times. Chapter 16.2 is the reason: a condition written once is a condition that cannot disagree with itself.
7. Verilog-2005
// usb_port_fsm -- the per-port state machine inside a hub's controller.
//
// Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
// module is what drives it: one instance per downstream port.
//
// Three UNCOORDINATED sources move a port, and the whole difficulty of the
// block is that they can act in the same cycle:
//
// 1. the HOST, issuing port commands as control transfers to the hub
// controller (SetPortFeature / ClearPortFeature);
// 2. the DEVICE below, attaching and detaching whenever it likes;
// 3. the HUB ITSELF, disabling a port for cause -- chapter 18.1's babble
// isolation, and over-current.
//
// The host is the slowest of the three and the only one that can be wrong
// about the present, because its command was composed from a port status it
// read some time ago. So the precedence is NOT "host wins":
//
// detach > over-current > babble > host command
//
// Physical reality outranks the hub's own faults, which outrank software.
//
// The other rule worth stating before the code: THERE IS NO ENABLE COMMAND.
// A port becomes Enabled only by completing a RESET. A host that wants a
// port enabled asks for a reset and waits; the enable is a side effect of
// the reset finishing, never something it can write directly.
module usb_port_fsm #(
parameter integer RESET_CYCLES = 20 // duration of the drive-reset
) (
input wire clk,
input wire rst_n,
// --- the host, through the hub controller (chapter 18.3) ---
input wire cmd_valid,
input wire [2:0] cmd,
// --- the device below ---
input wire attached, // physical presence detect
// --- the hub itself ---
input wire babble, // 18.1: repeater isolated this port
input wire overcurrent,
output reg [2:0] state,
output wire port_powered,
output wire port_enabled, // --> chapter 18.1's repeater
output wire resetting,
// --- sticky change bits, set by hardware, cleared by the host (18.3) ---
input wire chg_clear, // host acknowledges
input wire [4:0] chg_clear_mask,
output reg [4:0] change // {oc, reset, suspend, enable, connect}
);
// Port states. USB 2.0 names them in section 11.5; these are the six that
// carry distinct behaviour here.
localparam [2:0] P_POWERED_OFF = 3'd0,
P_DISCONNECTED = 3'd1, // powered, nothing attached
P_DISABLED = 3'd2, // attached, not passing traffic
P_RESETTING = 3'd3,
P_ENABLED = 3'd4,
P_SUSPENDED = 3'd5;
localparam [2:0] CMD_NONE = 3'd0,
CMD_SET_POWER = 3'd1,
CMD_CLR_POWER = 3'd2,
CMD_SET_RESET = 3'd3,
CMD_SET_SUSPEND = 3'd4,
CMD_CLR_SUSPEND = 3'd5, // resume
CMD_CLR_ENABLE = 3'd6; // disable, by request
// change bit positions
localparam integer C_CONNECT = 0, C_ENABLE = 1, C_SUSPEND = 2,
C_RESET = 3, C_OC = 4;
assign port_powered = (state != P_POWERED_OFF);
// ONLY the Enabled state passes traffic. Suspended does not, Resetting
// does not -- a port being reset is emphatically not carrying packets.
assign port_enabled = (state == P_ENABLED);
assign resetting = (state == P_RESETTING);
reg [31:0] rst_cnt;
// A command is only a command when it is valid. Decoding `cmd` without
// qualifying it makes CMD_NONE (3'd0) indistinguishable from an idle bus.
wire c = cmd_valid;
wire is_pwr_off = c && (cmd == CMD_CLR_POWER);
wire is_pwr_on = c && (cmd == CMD_SET_POWER);
wire is_reset = c && (cmd == CMD_SET_RESET);
wire is_susp = c && (cmd == CMD_SET_SUSPEND);
wire is_resume = c && (cmd == CMD_CLR_SUSPEND);
wire is_disable = c && (cmd == CMD_CLR_ENABLE);
// A detach is only observable while the port is powered: with the port
// powered off there is no pull-up to sense, so `attached` means nothing.
wire detached = port_powered && !attached;
// Connect-change is an EDGE, not a level. Setting it from `detached`
// directly would re-set it every cycle the port sits empty, and the host
// would never be able to clear it -- a change bit that cannot be cleared
// is a change bit that reports nothing.
reg attached_q;
wire conn_changed = port_powered && (attached != attached_q);
// The change register: set by hardware here, cleared by the host through
// chg_clear/chg_clear_mask. Chapter 18.3 is about what happens when both
// occur in the same cycle -- SET WINS, and that is built in below.
reg [4:0] set_chg;
always @* begin
set_chg = 5'd0;
if (state != P_POWERED_OFF) begin
if (overcurrent) set_chg[C_OC] = 1'b1;
if (conn_changed) set_chg[C_CONNECT] = 1'b1;
// a port leaving Enabled for a reason the host did not ask for
if (state == P_ENABLED && babble && !overcurrent && !detached)
set_chg[C_ENABLE] = 1'b1;
if (state == P_RESETTING && rst_cnt == RESET_CYCLES-1 && !detached
&& !overcurrent) set_chg[C_RESET] = 1'b1;
if (state == P_SUSPENDED && is_resume && !detached && !overcurrent)
set_chg[C_SUSPEND] = 1'b1;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= P_POWERED_OFF;
rst_cnt <= 32'd0;
change <= 5'd0;
attached_q <= 1'b0;
end else begin
// While the port is unpowered there is nothing to sense, so the
// history resets: powering a port that already has a device attached
// must report a connect change, because the host has not seen it yet.
attached_q <= (state != P_POWERED_OFF) ? attached : 1'b0;
// ---- change bits: SET beats CLEAR, always (chapter 18.3) ----
// An event that arrives in the same cycle the host clears the bit must
// survive. Clearing first and setting after would lose the event with
// no trace; this ordering loses an acknowledgement instead, which the
// host discovers on its next poll.
change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;
// ---- the state, in strict precedence order ----
if (detached) begin
// Physical reality first. A device that is gone is gone, whatever
// the host just asked for and whatever the repeater thinks.
state <= P_DISCONNECTED;
rst_cnt <= 32'd0;
end else if (overcurrent && state != P_POWERED_OFF) begin
// The port is drawing more than it may. Removing power is not a
// policy choice; it is the only way to stop it.
state <= P_POWERED_OFF;
rst_cnt <= 32'd0;
end else begin
case (state)
P_POWERED_OFF:
if (is_pwr_on) state <= P_DISCONNECTED;
P_DISCONNECTED: begin
if (is_pwr_off) state <= P_POWERED_OFF;
else if (attached) state <= P_DISABLED;
end
P_DISABLED: begin
if (is_pwr_off) state <= P_POWERED_OFF;
else if (is_reset) begin
state <= P_RESETTING;
rst_cnt <= 32'd0;
end
// NOTE: no command moves a port from Disabled to Enabled. The
// only route is through a reset.
end
P_RESETTING: begin
if (is_pwr_off) begin
state <= P_POWERED_OFF; rst_cnt <= 32'd0;
end else if (rst_cnt == RESET_CYCLES-1) begin
// Reset complete: THIS is where a port becomes enabled.
state <= P_ENABLED;
rst_cnt <= 32'd0;
end else begin
rst_cnt <= rst_cnt + 32'd1;
end
end
P_ENABLED: begin
if (is_pwr_off) state <= P_POWERED_OFF;
// BABBLE OUTRANKS THE HOST'S COMMAND. If the repeater isolated
// this port in the same cycle the host asked to suspend it, the
// port goes to Disabled, not Suspended -- the host composed that
// command from a status read before the fault existed.
else if (babble) state <= P_DISABLED;
else if (is_disable) state <= P_DISABLED;
else if (is_susp) state <= P_SUSPENDED;
end
P_SUSPENDED: begin
if (is_pwr_off) state <= P_POWERED_OFF;
else if (is_resume) state <= P_ENABLED;
else if (is_disable) state <= P_DISABLED;
end
default: state <= P_POWERED_OFF;
endcase
end
end
end
endmoduleThe c = cmd_valid qualification is not cosmetic. CMD_NONE is 3'd0, which is also what an idle command bus reads as. Decoding cmd without qualifying it makes "the host sent nothing" and "the host sent NONE" the same event — harmless here only because CMD_NONE does nothing, and a latent bug the moment the encoding changes.
8. SystemVerilog
package usb_port_pkg;
// The six port states that carry distinct behaviour. USB 2.0 section 11.5
// names more; these are the ones a repeater-facing FSM must distinguish.
typedef enum logic [2:0] {
P_POWERED_OFF, // no power on the port; nothing can be sensed
P_DISCONNECTED, // powered, nothing attached
P_DISABLED, // attached, not passing traffic
P_RESETTING, // driving reset downstream
P_ENABLED, // the ONLY state that passes traffic
P_SUSPENDED // enabled but idled
} port_state_e;
// Port commands, as the hub controller decodes them from the host's
// SetPortFeature / ClearPortFeature control transfers.
typedef enum logic [2:0] {
CMD_NONE,
CMD_SET_POWER,
CMD_CLR_POWER,
CMD_SET_RESET,
CMD_SET_SUSPEND,
CMD_CLR_SUSPEND, // resume
CMD_CLR_ENABLE // disable, by request
} port_cmd_e;
// Change-bit positions, shared with chapter 18.3's status-change endpoint.
typedef enum int unsigned {
CHG_CONNECT = 0, CHG_ENABLE = 1, CHG_SUSPEND = 2,
CHG_RESET = 3, CHG_OC = 4
} port_chg_e;
endpackage
// usb_port_fsm_sv -- the per-port state machine inside a hub's controller.
//
// Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
// drives it: one instance per downstream port.
//
// Three UNCOORDINATED sources move a port and can act in the same cycle: the
// HOST issuing port commands, the DEVICE attaching and detaching, and the
// HUB ITSELF disabling a port for cause (18.1's babble, and over-current).
// The host is the slowest of the three and the only one that can be wrong
// about the present, because its command was composed from a status it read
// some time ago. So the precedence is NOT "host wins":
//
// detach > over-current > babble > host command
//
// And: THERE IS NO ENABLE COMMAND. A port becomes Enabled only by completing
// a reset, or by resuming from Suspended.
module usb_port_fsm_sv
import usb_port_pkg::*;
#(
parameter int unsigned RESET_CYCLES = 20
) (
input logic clk,
input logic rst_n,
input logic cmd_valid,
input port_cmd_e cmd,
input logic attached,
input logic babble,
input logic overcurrent,
output port_state_e state,
output logic port_powered,
output logic port_enabled,
output logic resetting,
input logic chg_clear,
input logic [4:0] chg_clear_mask,
output logic [4:0] change
);
initial if (RESET_CYCLES < 1)
$fatal(1, "RESET_CYCLES=%0d: a reset with no duration is not a reset",
RESET_CYCLES);
assign port_powered = (state != P_POWERED_OFF);
// ONLY Enabled passes traffic. Suspended does not; a port being reset
// emphatically does not.
assign port_enabled = (state == P_ENABLED);
assign resetting = (state == P_RESETTING);
int unsigned rst_cnt;
logic attached_q;
// A detach is only observable while powered: with no power there is no
// pull-up to sense, so `attached` means nothing.
wire detached = port_powered && !attached;
// Connect-change is an EDGE, not a level. Driving it from `detached`
// would re-set it every cycle an empty port sat idle, and a change bit
// the host can never clear reports nothing.
wire conn_changed = port_powered && (attached != attached_q);
logic [4:0] set_chg;
always_comb begin
set_chg = '0;
if (state != P_POWERED_OFF) begin
if (overcurrent) set_chg[CHG_OC] = 1'b1;
if (conn_changed) set_chg[CHG_CONNECT] = 1'b1;
if (state == P_ENABLED && babble && !overcurrent && !detached)
set_chg[CHG_ENABLE] = 1'b1;
if (state == P_RESETTING && rst_cnt == RESET_CYCLES-1
&& !detached && !overcurrent)
set_chg[CHG_RESET] = 1'b1;
if (state == P_SUSPENDED && cmd_valid && cmd == CMD_CLR_SUSPEND
&& !detached && !overcurrent)
set_chg[CHG_SUSPEND] = 1'b1;
end
end
wire is_pwr_off = cmd_valid && (cmd == CMD_CLR_POWER);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= P_POWERED_OFF; rst_cnt <= '0; change <= '0; attached_q <= 1'b0;
end else begin
attached_q <= (state != P_POWERED_OFF) ? attached : 1'b0;
// SET beats CLEAR, always (chapter 18.3). An event arriving in the
// same cycle the host clears the bit must survive: this ordering
// loses an acknowledgement, which the host sees on its next poll,
// rather than losing an event, which leaves no trace at all.
change <= (change & ~(chg_clear ? chg_clear_mask : 5'd0)) | set_chg;
if (detached) begin
// Physical reality first, whatever the host just asked for.
state <= P_DISCONNECTED; rst_cnt <= '0;
end else if (overcurrent && state != P_POWERED_OFF) begin
state <= P_POWERED_OFF; rst_cnt <= '0;
end else if (is_pwr_off && state != P_POWERED_OFF) begin
state <= P_POWERED_OFF; rst_cnt <= '0;
end else begin
case (state)
P_POWERED_OFF:
if (cmd_valid && cmd == CMD_SET_POWER) state <= P_DISCONNECTED;
P_DISCONNECTED:
if (attached) state <= P_DISABLED;
P_DISABLED:
// No command moves a port from Disabled to Enabled: the only
// route is through a reset.
if (cmd_valid && cmd == CMD_SET_RESET) begin
state <= P_RESETTING; rst_cnt <= '0;
end
P_RESETTING:
if (rst_cnt == RESET_CYCLES-1) begin
state <= P_ENABLED; rst_cnt <= '0; // THIS is where a port
end else begin // becomes enabled
rst_cnt <= rst_cnt + 1;
end
P_ENABLED:
// BABBLE OUTRANKS THE HOST'S COMMAND: the host composed that
// command from a status read taken before the fault existed.
if (babble) state <= P_DISABLED;
else if (cmd_valid && cmd == CMD_CLR_ENABLE) state <= P_DISABLED;
else if (cmd_valid && cmd == CMD_SET_SUSPEND) state <= P_SUSPENDED;
P_SUSPENDED:
if (cmd_valid && cmd == CMD_CLR_SUSPEND) state <= P_ENABLED;
else if (cmd_valid && cmd == CMD_CLR_ENABLE) state <= P_DISABLED;
default: state <= P_POWERED_OFF;
endcase
end
end
end
endmoduleHoisting the power-off command out of the case statement is the structural difference from the Verilog. Both designs behave identically; the SystemVerilog states the §3 exception once, next to the other two precedence rules, instead of repeating if (is_pwr_off) in five branches. Five copies of a rule is five chances to omit one, and the omission would be invisible in any test that never issues a power-off from the state that forgot it.
9. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_port_pkg is
-- The six port states that carry distinct behaviour. USB 2.0 section 11.5
-- names more; these are the ones a repeater-facing FSM must distinguish.
type port_state_t is (
P_POWERED_OFF, -- no power on the port; nothing can be sensed
P_DISCONNECTED, -- powered, nothing attached
P_DISABLED, -- attached, not passing traffic
P_RESETTING, -- driving reset downstream
P_ENABLED, -- the ONLY state that passes traffic
P_SUSPENDED -- enabled but idled
);
-- Port commands, as the hub controller decodes them from the host's
-- SetPortFeature / ClearPortFeature control transfers.
type port_cmd_t is (
CMD_NONE, CMD_SET_POWER, CMD_CLR_POWER, CMD_SET_RESET,
CMD_SET_SUSPEND, CMD_CLR_SUSPEND, CMD_CLR_ENABLE
);
-- Change-bit positions, shared with chapter 18.3's status-change endpoint.
constant CHG_CONNECT : natural := 0;
constant CHG_ENABLE : natural := 1;
constant CHG_SUSPEND : natural := 2;
constant CHG_RESET : natural := 3;
constant CHG_OC : natural := 4;
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_port_pkg.all;
-- usb_port_fsm_vhdl -- the per-port state machine inside a hub's controller.
--
-- Chapter 18.1 built the repeater and left `port_enabled` as an INPUT. This
-- drives it: one instance per downstream port.
--
-- Three UNCOORDINATED sources move a port and can act in the same cycle: the
-- HOST issuing port commands, the DEVICE attaching and detaching, and the
-- HUB ITSELF disabling a port for cause (18.1's babble, and over-current).
-- The host is the slowest of the three and the only one that can be wrong
-- about the present, because its command was composed from a status it read
-- some time ago. So the precedence is NOT "host wins":
--
-- detach > over-current > babble > host command
--
-- And: THERE IS NO ENABLE COMMAND. A port becomes Enabled only by completing
-- a reset, or by resuming from Suspended.
entity usb_port_fsm_vhdl is
generic ( RESET_CYCLES : positive := 20 );
port (
clk : in std_logic;
rst_n : in std_logic;
cmd_valid : in std_logic;
cmd : in port_cmd_t;
attached : in std_logic;
babble : in std_logic;
overcurrent : in std_logic;
state : out port_state_t;
port_powered : out std_logic;
port_enabled : out std_logic;
resetting : out std_logic;
chg_clear : in std_logic;
chg_clear_mask : in std_logic_vector(4 downto 0);
change : out std_logic_vector(4 downto 0)
);
end entity;
architecture rtl of usb_port_fsm_vhdl is
signal st : port_state_t := P_POWERED_OFF;
signal rst_cnt : natural range 0 to RESET_CYCLES := 0;
signal attached_q : std_logic := '0';
signal chg_r : std_logic_vector(4 downto 0) := (others => '0');
signal powered_i : std_logic;
signal detached : std_logic;
signal conn_chg : std_logic;
signal set_chg : std_logic_vector(4 downto 0);
begin
powered_i <= '0' when st = P_POWERED_OFF else '1';
port_powered <= powered_i;
-- ONLY Enabled passes traffic. Suspended does not; a port being reset
-- emphatically does not.
port_enabled <= '1' when st = P_ENABLED else '0';
resetting <= '1' when st = P_RESETTING else '0';
state <= st;
change <= chg_r;
-- A detach is only observable while powered: with no power there is no
-- pull-up to sense, so `attached` means nothing.
detached <= '1' when (powered_i = '1' and attached = '0') else '0';
-- Connect-change is an EDGE, not a level. Driving it from `detached`
-- would re-set it every cycle an empty port sat idle, and a change bit
-- the host can never clear reports nothing.
conn_chg <= '1' when (powered_i = '1' and attached /= attached_q) else '0';
process (st, rst_cnt, attached, babble, overcurrent, cmd, cmd_valid,
detached, conn_chg)
variable r : std_logic_vector(4 downto 0);
begin
r := (others => '0');
if st /= P_POWERED_OFF then
if overcurrent = '1' then r(CHG_OC) := '1'; end if;
if conn_chg = '1' then r(CHG_CONNECT) := '1'; end if;
if st = P_ENABLED and babble = '1' and overcurrent = '0'
and detached = '0' then
r(CHG_ENABLE) := '1';
end if;
if st = P_RESETTING and rst_cnt = RESET_CYCLES-1
and detached = '0' and overcurrent = '0' then
r(CHG_RESET) := '1';
end if;
if st = P_SUSPENDED and cmd_valid = '1' and cmd = CMD_CLR_SUSPEND
and detached = '0' and overcurrent = '0' then
r(CHG_SUSPEND) := '1';
end if;
end if;
set_chg <= r;
end process;
process (clk, rst_n)
variable clr : std_logic_vector(4 downto 0);
begin
if rst_n = '0' then
st <= P_POWERED_OFF; rst_cnt <= 0; chg_r <= (others => '0');
attached_q <= '0';
elsif rising_edge(clk) then
if st /= P_POWERED_OFF then attached_q <= attached;
else attached_q <= '0'; end if;
-- SET beats CLEAR, always (chapter 18.3). An event arriving in the
-- same cycle the host clears the bit must survive: this ordering
-- loses an acknowledgement, which the host sees on its next poll,
-- rather than losing an event, which leaves no trace at all.
if chg_clear = '1' then clr := chg_clear_mask;
else clr := (others => '0'); end if;
chg_r <= (chg_r and (not clr)) or set_chg;
if detached = '1' then
-- Physical reality first, whatever the host just asked for.
st <= P_DISCONNECTED; rst_cnt <= 0;
elsif overcurrent = '1' and st /= P_POWERED_OFF then
st <= P_POWERED_OFF; rst_cnt <= 0;
elsif cmd_valid = '1' and cmd = CMD_CLR_POWER and st /= P_POWERED_OFF then
st <= P_POWERED_OFF; rst_cnt <= 0;
else
case st is
when P_POWERED_OFF =>
if cmd_valid = '1' and cmd = CMD_SET_POWER then
st <= P_DISCONNECTED;
end if;
when P_DISCONNECTED =>
if attached = '1' then st <= P_DISABLED; end if;
when P_DISABLED =>
-- No command moves a port from Disabled to Enabled: the only
-- route is through a reset.
if cmd_valid = '1' and cmd = CMD_SET_RESET then
st <= P_RESETTING; rst_cnt <= 0;
end if;
when P_RESETTING =>
if rst_cnt = RESET_CYCLES-1 then
st <= P_ENABLED; rst_cnt <= 0; -- THIS is where a port
else -- becomes enabled
rst_cnt <= rst_cnt + 1;
end if;
when P_ENABLED =>
-- BABBLE OUTRANKS THE HOST'S COMMAND: the host composed that
-- command from a status read taken before the fault existed.
if babble = '1' then
st <= P_DISABLED;
elsif cmd_valid = '1' and cmd = CMD_CLR_ENABLE then
st <= P_DISABLED;
elsif cmd_valid = '1' and cmd = CMD_SET_SUSPEND then
st <= P_SUSPENDED;
end if;
when P_SUSPENDED =>
if cmd_valid = '1' and cmd = CMD_CLR_SUSPEND then
st <= P_ENABLED;
elsif cmd_valid = '1' and cmd = CMD_CLR_ENABLE then
st <= P_DISABLED;
end if;
end case;
end if;
end if;
end process;
end architecture;case st is over an enumerated type needs no default, and VHDL will refuse to compile if a state is left unhandled. The Verilog needs default: state <= P_POWERED_OFF; because [2:0] has eight values and only six are named — two encodings exist that the state table does not describe, and a design without that default would latch on them.
rst_cnt : natural range 0 to RESET_CYCLES makes the counter's bound part of its type. An overflow is a runtime error naming the signal, not a silent wrap.
10. The Waveform
A port from powered-off to enabled, then disabled by a babble that outranked the host
10 cyclesThis waveform is in the controller clock domain. Nothing here is a shape on D+/D−; cmd is a decoded control transfer, not a packet.
Cycles 5 and 6 are the point most often got wrong. The port is Resetting and port_enabled is low throughout. A design that enabled the port at the start of the reset would pass any test that only checks the port ends up enabled.
Cycle 8 is the chapter. Two things arrive in one cycle and the design's answer is visible one cycle later: state is DIS, not SUSP, and change has gained bit 1 — the port left Enabled for a reason the host did not ask for, so the host is told.
11. The Testbench: 784 Transitions, Exhaustively
The FSM's one-step domain is small enough to enumerate, so — following 18.1 §12 and 17.3 — it is enumerated.
7 positions × 14 command scenarios × 8 environment combinations = 784 transitions.
The seven positions are the six states plus a split of Resetting into early (the counter has just started) and final (the counter is about to expire), because those two behave differently and a single "Resetting" position would test only one of them. The fourteen command scenarios are cmd_valid crossed with all seven commands — which is what proves cmd_valid actually qualifies the decode. The eight environment combinations are attached, babble and overcurrent.
for (pos=0; pos<7; pos=pos+1)
for (cs=0; cs<14; cs=cs+1)
for (st=0; st<8; st=st+1) begin
goto(POSN[pos]);
n_exh = n_exh + 1;
step(cs[0], cs[3:1], st[0], st[1], st[2], 0, 5'd0);
endgoto re-runs a hard reset before every point, so the 784 transitions are independent of each other and of their order.
The model keeps its own state, per 17.3's lesson, and it is written as the precedence chain of §3 rather than as a copy of the RTL's case statement:
function [2:0] m_next(input [2:0] s, input [31:0] cnt, input cv,
input [2:0] c, input att, input bab, input oc);
reg det;
begin
det = (s != P_OFF) && !att;
if (det) m_next = P_DISC;
else if (oc && (s != P_OFF)) m_next = P_OFF;
else if (cv && c==C_PWR_OFF && s != P_OFF) m_next = P_OFF;
else case (s)
...Three invariants are checked on every one of the ~26 000 transitions, and none of them comes from the model:
check(penabled === (state == P_EN),
"port_enabled is asserted in the Enabled state and nowhere else");
...
// A port may become Enabled only by COMPLETING A RESET or by RESUMING
// from Suspended. There is no enable command, so a port must never
// reach Enabled straight from Disabled, Disconnected or Powered-off.
if (state == P_EN && prev_state != P_EN) begin
n_enter_en = n_enter_en + 1;
check(prev_state === P_RST || prev_state === P_SUSP,
"a port entered Enabled other than by reset-complete or resume");
end
// A change bit never clears unless the host asked for it.
check(((~change) & m_chg & ~(clr ? clrm : 5'd0)) === 5'd0,
"a change bit cleared without the host clearing it");Measured reach, all three languages:
exhaustive transition sweep: 784 of 784 transitions verified
Verilog / SystemVerilog:
REACH: exhaustive=784 enabled-entries=1396 (reset=1088 resume=308) babble+cmd collisions=378
[Verilog] usb_port_fsm: 0 errors — PASS
VHDL:
REACH: exhaustive=784 enabled-entries=1398 (reset=1091 resume=307) babble+cmd collisions=378
[VHDL] usb_port_fsm_vhdl: 0 errors — PASS12. The Invariant That Was Wrong
The history invariant in §11 did not start out in that form. It started as the rule §2 states:
// A port may become Enabled ONLY by completing a reset.
check(prev_state === P_RST, "a port entered Enabled from somewhere else");It reported 8 failures against a design that was correct.
All eight were resume. A port in Suspended, given ClearPortFeature(SUSPEND), returns to Enabled — which is exactly what resume is for, and involves no reset at all.
"There is no enable command" forbids Disabled → Enabled. It says nothing about a port coming back from suspend. The invariant had taken a true statement and dropped a qualifier, and the bench dutifully reported the design breaking a rule that was never a rule.
A failing check is a disagreement between the design and the testbench. Which of the two is wrong is a separate question, and assuming it is always the design is how correct behaviour gets "fixed" out of a design.
The count is what made this obvious. Eight failures — not zero, not thousands. A design that could reach Enabled by an illegal route would do it constantly; eight occurrences, all with the same predecessor, describes a specific legal transition the invariant had not allowed for.
The corrected invariant is strictly more useful than the original, because it now distinguishes the two legal routes and counts them separately: reset=1088 resume=308. That split is how §13's stimulus gap was noticed.
13. Mutation Testing — Across All Three Languages
Seven mutations, each a plausible alternative design, run against all three benches.
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| M1 | the host's command outranks babble | 103 | 103 | 103 |
| M2 | a host command outranks a detach | 4368 | 4368 | 4429 |
| M3 | on the change register, clear beats set | 1925 | 1925 | 1944 |
| M4 | a command enables a port directly, no reset | 1317 | 1317 | 1676 |
| M5 | the reset finishes one cycle early | 13400 | 13400 | 13958 |
| M6 | a suspended port still passes traffic | 879 | 879 | 868 |
| M7 | connect-change is a level, not an edge | 22009 | 22009 | 22341 |
M7 scores highest because its failure is continuous. A level-driven connect-change re-sets itself every cycle an empty port sits idle, so it diverges from the model on essentially every transition where a port is not populated.
M4 is §2's rule as a mutation — a command that enables a port with no reset. It dies 1317 times, but the check that specifically catches it is §11's history invariant, which is also the check §12 had to correct before it was usable.
M6 is the Resetting/Suspended distinction. A suspended port that still asserts port_enabled would let 18.1's repeater broadcast host traffic to a device that has been told to idle.
14. M1 Died by Three
M1 is the mutation this chapter exists for — it makes the host's command outrank babble, which is §3 inverted. In the first run it scored 3.
Three errors, in all three languages, for the chapter's central claim.
The reason is that M1's distinguishing condition is far narrower than it looks. M1 and the correct design differ only when all four of these hold:
- the port is in Enabled, and
babbleis asserted, andcmd_validis asserted, and- the command is specifically
SET_SUSPEND.
CLR_ENABLE does not distinguish them — both designs go to Disabled, by different routes, and agree. Every other command is either outranked anyway or irrelevant. So the whole mutation hangs on one command in one state with one fault present.
In the 784-point sweep that is exactly one point: one position out of seven, one command scenario out of fourteen, one environment combination out of eight. The directed test added a second. The randomised phase supplied the third.
The fix is a directed phase that collides babble with every command, fifty times each:
// This is the behaviour the chapter is about, and the sweep reaches it
// exactly once: it needs Enabled, babble, cmd_valid, and specifically
// CMD_SET_SUSPEND -- one point in 784. A claim this central cannot rest
// on a single transition.
for (pos=0; pos<50; pos=pos+1)
for (i=0;i<7;i=i+1) begin
goto(P_EN);
step(1, i[2:0], 1, 1, 0, 0, 5'd0);
// CLR_POWER is not a COMPETING command, it is a stronger one:
// removing power takes the port past Disabled altogether. Babble
// outranks every command that would leave the port powered.
if (i == C_PWR_OFF)
check(state===P_OFF, "a power-off command outranks babble");
else
check(state===P_DIS,
"babble outranks every host command that leaves it powered");
endThat phase immediately reported 50 failures of its own — one per repetition, all on CLR_POWER. The check as first written said "babble outranks every host command", and power-off is the exception §3 describes: it is not competing with babble, it is doing more than babble. The testbench had over-stated the rule in exactly the way §12's invariant had.
With the exception written in: M1 went from 3 to 103, and babble+cmd collisions from 28 to 378.
15. The Random Stimulus That Could Not Clear Three Bits
The first matrix had a second problem, visible only as a cross-language disagreement: M3 scored 1925 in Verilog and SystemVerilog but 1190 in VHDL, and M5 scored 13 400 against 28 676.
The cause was in the VHDL bench's randomisation:
step(sl(r2 < 0.3333), CMDS(integer(floor(r3*7.0))),
sl(r4 < 0.75), sl(r5 < 0.0625), sl(r6 < 0.015625),
sl(r7 < 0.125), -- clear?
std_logic_vector(to_unsigned(integer(floor(r7*32.0)), 5)));r7 gates the clear and generates the mask. So a clear only ever happened when r7 < 0.125, which forces floor(r7*32) < 4 — the mask was never more than 3, and change bits 2, 3 and 4 were never cleared at all in twenty thousand cycles.
M3 is a mutation of the clear/set ordering. A bench that only ever clears two of five bits exercises it at a fraction of its strength.
One extra random draw fixed it, and the effect was not confined to M3:
| Mutation | VHDL before | VHDL after | Verilog / SV |
|---|---|---|---|
| M2 | 6188 | 4429 | 4368 |
| M3 | 1190 | 1944 | 1925 |
| M5 | 28676 | 13958 | 13400 |
All three converged on the other two languages at once, which is the strongest available evidence that the divergence was the stimulus and not the designs.
Two independent random values must come from two independent draws. Reusing one for a gate and a payload correlates them perfectly, and the resulting hole is invisible in a passing run — a green testbench that simply never asked some of its questions.
18.1 §15 used a cross-language disagreement to find a mis-written mutation. This one used the same signal to find a mis-written testbench. Neither would have been visible from a single language.
16. A UVM Environment for the Port FSM
The three sources of §3 map naturally onto three independent agents driving one DUT, which is what makes this block a better UVM example than 18.1's repeater: the agents are not symmetric copies, they are genuinely different actors.
// One agent per SOURCE, not per signal. The whole design problem is that
// these three are uncoordinated, so the environment must be able to make
// them collide without any of them knowing about the others.
class port_env extends uvm_env;
`uvm_component_utils(port_env)
host_cmd_agent host; // SetPortFeature / ClearPortFeature
device_agent device; // attach / detach
fault_agent fault; // babble (from 18.1) and over-current
port_scoreboard sb;
port_coverage cov;
endclassThe collision is a virtual sequence, because it is a statement about several agents at once:
class babble_vs_suspend_vseq extends uvm_sequence;
`uvm_object_utils(babble_vs_suspend_vseq)
port_vsequencer vsqr;
task body();
host_cmd_item h;
fault_item f;
// The SAME cycle. Section 14 measured what happens when this scenario
// is left to chance: the mutation that inverts this precedence died by
// THREE errors in a 26000-transition run.
fork
begin h = host_cmd_item::type_id::create("h");
start_item(h, null, vsqr.host_sqr);
h.cmd = CMD_SET_SUSPEND; h.valid = 1;
finish_item(h, null); end
begin f = fault_item::type_id::create("f");
start_item(f, null, vsqr.fault_sqr);
f.babble = 1;
finish_item(f, null); end
join
endtask
endclassThe scoreboard checks the history invariant, which is the part no reference model supplies:
class port_scoreboard extends uvm_scoreboard;
`uvm_component_utils(port_scoreboard)
local port_state_e m_state, m_prev; // its OWN state (chapter 17.3)
function void write(port_txn t);
// A port may become Enabled only by completing a reset or by resuming.
// The first version of this check named P_RESETTING alone and reported
// 8 failures against a correct design -- every one of them a legal
// resume. See section 12.
if (t.state == P_ENABLED && m_prev != P_ENABLED)
if (!(m_prev inside {P_RESETTING, P_SUSPENDED}))
`uvm_error("PORT/ENTRY", $sformatf(
"entered Enabled from %s; only reset-complete and resume are legal",
m_prev.name()))
// Exactly one state carries traffic.
if (t.port_enabled !== (t.state == P_ENABLED))
`uvm_error("PORT/EN", "port_enabled asserted outside the Enabled state")
m_prev = m_state; m_state = t.state;
endfunction
endclassAnd the coverage model encodes §14's lesson directly:
covergroup port_cg with function sample(
port_state_e s, port_cmd_e c, bit cv, bit bab, bit oc, bit att);
cp_state : coverpoint s;
cp_cmd : coverpoint c;
cp_bab : coverpoint bab { bins fault = {1}; }
// THE cross that matters. Section 14: the one-step sweep reaches
// Enabled x babble x SET_SUSPEND exactly ONCE in 784 transitions, and a
// mutation inverting that precedence survived on a margin of three.
// This cross makes the gap a coverage hole instead of a lucky catch.
x_babble_vs_cmd : cross cp_state, cp_cmd, cp_bab {
ignore_bins not_enabled = binsof(cp_state) intersect
{P_POWERED_OFF, P_DISCONNECTED, P_DISABLED, P_RESETTING, P_SUSPENDED};
}
endgroup17. Assertions
// Exactly one state carries traffic.
property p_enabled_only_in_enabled;
@(posedge clk) disable iff (!rst_n)
port_enabled == (state == P_ENABLED);
endproperty
a_enabled_only : assert property (p_enabled_only_in_enabled);
// THE precedence: babble, with the port powered and staying powered,
// always lands the port in Disabled -- whatever the host asked for.
property p_babble_outranks_host;
@(posedge clk) disable iff (!rst_n)
(state == P_ENABLED && babble && !overcurrent && attached
&& !(cmd_valid && cmd == CMD_CLR_POWER)) |=> (state == P_DISABLED);
endproperty
a_babble_wins : assert property (p_babble_outranks_host)
else $error("a host command overrode a babble fault");
// There is no enable command: Enabled is reachable only from Resetting
// or Suspended. Section 12 is why the second term is here.
property p_enable_entry;
@(posedge clk) disable iff (!rst_n)
(state == P_ENABLED && $past(state) != P_ENABLED)
|-> ($past(state) == P_RESETTING || $past(state) == P_SUSPENDED);
endproperty
a_enable_entry : assert property (p_enable_entry);
// A change bit never clears unless the host cleared it.
property p_change_sticky;
@(posedge clk) disable iff (!rst_n)
($past(change) & ~change & ~($past(chg_clear) ? $past(chg_clear_mask) : '0))
== '0;
endproperty
a_change_sticky : assert property (p_change_sticky);
// A detach outranks everything except nothing: the port always lands in
// Disconnected, whatever else arrived in the same cycle.
property p_detach_wins;
@(posedge clk) disable iff (!rst_n)
(state != P_POWERED_OFF && !attached) |=> (state == P_DISCONNECTED);
endproperty
a_detach_wins : assert property (p_detach_wins);p_babble_outranks_host is M1 as a single line, and it carries the power-off exception §14 discovered — the same qualifier, in the same place, for the same reason.
These were written but not simulated. Icarus does not support concurrent assertions; §21 says so plainly.
18. Debugging: the Device That Enumerates, Then Vanishes
The report: a device attached to a hub port enumerates correctly, works for a few seconds, then disappears. The host logs a disconnect. Re-plugging it works, for a few seconds. The device works perfectly on a direct host port.
The procedure:
1. Read the port state. If it is Disabled rather than Disconnected, the device did not detach — the hub disabled it. The host's "disconnect" is what a disabled port looks like from above.
2. Read the change bits. Enable-change set with no matching host command means the port left Enabled for a reason the host did not ask for — §5's whole purpose. That is 18.1's babble path.
3. Check port_isolated on the repeater (18.1 §18). If two bits are set, the fault involved two ports, and this device may be the innocent one.
4. Distinguish over-current from babble. Over-current lands the port in Powered-off, not Disabled, and sets a different change bit. Two different faults with two different destinations — which is why §5 keeps five separate bits rather than one "something happened" flag.
5. Explain "works on a direct host port." A root port has no repeater in front of it, so there is nothing to detect a multi-talker condition and nothing to disable the port. The device may be misbehaving in exactly the way the hub is built to catch, and the direct connection simply has no detector.
19. Common Misconceptions
"The host enables a port." There is no enable command (§2). The host requests a reset and the enable follows from its completion.
"A port is enabled while it is being reset." port_enabled is low throughout Resetting (§1, §10) — mutation M6's neighbour, and visible at cycles 6–8 of Figure 2.
"The host's command always wins." It is outranked by a detach, by over-current, and by babble (§3). The host is the only actor that can be wrong about the present.
"Babble outranks every host command." Every command that leaves the port powered (§3). ClearPortFeature(PORT_POWER) does more than babble does, not less — a distinction that cost 50 test failures (§14).
"Change bits should clear when the condition ends." Then the host, which polls, would miss every event shorter than its polling interval (§5).
"If the host clears a bit as an event sets it, the clear should win." Then the event is lost with no trace. Set wins; an acknowledgement is recoverable, an event is not (§5).
"Connect-change means a device is connected." It means the connection status changed (§5). Driven as a level it can never be cleared — 22 009 errors.
"A disabled port means a broken device." It may be the innocent half of a two-port fault (§18, 18.1 §3).
"All 784 transitions passed, so the FSM is verified." The single most important transition among them contributed one error, and the mutation testing that behaviour died by three (§14).
20. Exercises
1. §14 found that CLR_ENABLE does not distinguish M1 from the correct design, because both reach Disabled. Identify every other mutation in §13 that has a similarly narrow distinguishing condition, and compute how many of the 784 sweep points reach each.
2. The design resets attached_q to 0 while the port is unpowered. Determine what changes if it holds its value instead, and which change bit misbehaves.
3. Write the SVA property that catches M5 — a reset finishing one cycle early — without referring to RESET_CYCLES.
4. §15's correlated random draw made three mutations diverge at once. Describe a check over a tri-HDL mutation matrix that flags this class of problem automatically, and state its false-positive rate against the matrix in §13.
5. Extend the FSM with the USB Restart_S and Restart_E states from section 11.5 and determine where they sit in §3's precedence chain.
6. A hub has 4 ports and one instance of this FSM each. Chapter 18.3 must report all of their change bits in one bitmap. Determine what happens when two ports set a change bit in the same cycle the host clears the bitmap, and what §5's ordering rule becomes at the hub level.
21. Summary
A port is six states, and exactly one of them carries traffic (§1). port_enabled to 18.1's repeater is asserted in Enabled only — not in Resetting, not in Suspended.
There is no enable command (§2). A port becomes enabled by completing a reset, or by resuming from suspend, and by no other route — checked as a history invariant on every transition (§11).
Three uncoordinated sources move a port and collide in the same cycle (§3), and the host loses: detach > over-current > babble > host command. The host is the only one of the three whose information is stale by construction. The exception is ClearPortFeature(PORT_POWER), which is not a competing command but a stronger one (§3, §14).
Change bits are set by hardware and cleared only by the host (§5), connect-change is an edge rather than a level (M7: 22 009 errors), and set beats clear in the same cycle — losing an acknowledgement costs one poll, losing an event costs an enumeration.
All three HDL implementations were simulated (§22) and seven mutations died in all three (§13), with the transition function verified exhaustively over all 784 one-step transitions (§11).
The history invariant was wrong before the design was (§12). Stated as "only from Resetting" it reported 8 failures against correct hardware, every one a legal resume. A failing check is a disagreement; which side is wrong is a separate question.
M1 — the chapter's central claim — first died by 3 (§14). Its distinguishing case needs Enabled, babble, a valid command, and specifically SET_SUSPEND: one point in 784. An exhaustive sweep is exhaustive over its domain, not over the importance of what is in it. A directed collision phase took it to 103 — after that phase itself over-stated the rule and reported 50 failures of its own.
And a single reused random draw made the VHDL bench unable to clear three of five change bits (§15). Fixing it moved M2, M3 and M5 onto the other two languages simultaneously. Two independent values need two independent draws.
22. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_port_fsm | pm_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 784/784 | ✅ all seven |
| SystemVerilog | usb_port_fsm_sv | pm_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 784/784 | ✅ all seven |
| VHDL-2008 | usb_port_fsm_vhdl | pm_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 784/784 | ✅ all seven |
| UVM (§16) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§17) | — | — | ❌ unsupported by Icarus | ❌ | — |
Icarus rejected two constructs that the published SystemVerilog would otherwise use: unique if on the precedence chain, and inside in the testbench. Both were rewritten rather than left in as untested code.
The UVM environment and assertions in §16 and §17 were written and reviewed, not simulated. No tool in this environment runs either.
23. What Comes Next
This chapter produced five change bits per port and gave the host no way to read them.
That is the next problem, and it is larger than it sounds. A hub cannot interrupt the host (Chapter 2.6), so every event on every port waits in a register until the host asks. The asking is an interrupt transfer from the hub's status-change endpoint — one bitmap covering all ports, polled at whatever interval the host chose.
Chapter 18.3 — Downstream Device Discovery builds that mechanism, and the collision §5 introduced per-port becomes a harder problem at hub level: the host reads a bitmap, decides which ports to investigate, and clears what it read — while ports it has not looked at yet are still setting bits. A device that attaches during that window must not be lost, and the ordering rule that guarantees it is the same one this chapter built into change.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
Hub Architecture
A hub is three devices in one package, and its repeater is deliberately asymmetric: downstream is a broadcast, upstream is a select of exactly one — and two talkers connects neither.
- Related topic
Downstream Device Discovery
A hub cannot interrupt the host, so every port event waits to be asked for — and the window between the poll and the acknowledgement is where devices are silently lost.
- Related topic
Cascading Hubs
The seven-tier rule is a timing constraint wearing a topology costume — and the limit applies to hubs, not to devices, which is one comparison operator almost everyone gets wrong.
- Related topic
Hub Power Management
A bus-powered hub gets 500 mA and must supply four ports that could each want 500 mA — so its ports are offered one unit load, and a device needing more is refused.
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.
