USB · Module 19
Bus Power
A device's current allowance changes exactly once during enumeration — and bMaxPower is counted in 2 mA units, not milliamps.
Module 18 asked what a hub can supply. This module asks what a device may take, and they are not the same question with the answer written from the other side.
A hub's budget is a property of the hub. A device's allowance is a property of a negotiation — it changes during enumeration, it changes exactly once, and until it changes the device is entitled to far less than it asked for.
1. The Allowance Changes Exactly Once
A device attaches. It is not yet configured. How much current may it draw?
One unit load. 100 mA. However much it declared in its descriptor, however much the port has spare.
Then the host reads its descriptors, decides the port can supply what the device wants, and issues SetConfiguration. Now — and not before — the device may draw what it declared.
before SetConfiguration | after | |
|---|---|---|
| Allowance | one unit load | bMaxPower, scaled |
| Depends on the descriptor? | no | yes |
| Depends on the port's spare capacity? | no | yes |
And the floor is a floor, not a default. A device that never gets configured stays there permanently.
2. bMaxPower Is Not Milliamps
The descriptor field is one byte, and it counts in units, not milliamps — and the unit depends on the speed.
/* SuperSpeed power is in 8 mA units; others are in 2 mA units */
unsigned mul = (udev->speed >= USB_SPEED_SUPER ? 8 : 2);
return c->desc.bMaxPower * mul;A USB 2.0 device asking for 500 mA writes 250. A SuperSpeed device asking for 900 mA writes 113 — because 900 is not a multiple of 8, so it writes 113 and gets 904, or 112 and gets 896.
| Speed | unit | one byte expresses | full load |
|---|---|---|---|
| USB 2.0 | 2 mA | 0 – 510 mA | 500 mA (250) |
| SuperSpeed | 8 mA | 0 – 2040 mA | 900 mA (113 → 904 mA) |
The 2 mA unit is why 500 mA is the USB 2.0 ceiling in practice: one byte times 2 reaches 510, so the field can express the full load and almost nothing beyond it. The scaling is not an encoding convenience — it is what sets the maximum a device can even ask for.
Mutation B2 applies the 2 mA scale to SuperSpeed too and dies 35 897 times: every SuperSpeed device would request a quarter of what it meant.
3. What "Detected But Doesn't Work" Actually Is
SetConfiguration can fail, and the failure has a very specific shape.
If the port cannot supply what the device declared, the host does not configure it. The device is enumerated, addressed, visible in the device list, and stuck at one unit load.
This is the hardware behind the most common USB support complaint there is. The device shows up. It has a name. It has a serial number. It does nothing — because a configuration that was never accepted means an allowance that was never granted, and the device is sitting at 100 mA waiting for permission that is not coming.
config_refused is a sticky output for exactly this reason. Without it, a refused configuration and a device that was never asked look identical from outside: both unconfigured, both at one unit load, both silent.
4. Losing VBUS Is Not a Transition to Negotiate
A configured device that loses VBUS does not stay configured.
This looks like defensive coding and is not. A device that retained configured across a power loss would, on the next attach, resume drawing its full allowance immediately — before the host had read a descriptor, before it had issued anything, before it knew what had just been plugged in. The one-unit-load floor of §1 would be bypassed entirely by unplugging and replugging.
Mutation B5 removes the VBUS term from the deconfigure condition and dies 7100 times.
SetConfiguration(0) does the same thing deliberately: it returns the device to the Address state, and the allowance drops back to one unit load with it (mutation B6, 1708 errors).
5. The Negotiation, Drawn
6. The Hardware, Before Any Language
Two combinational quantities and one bit of state.
requested_mA is bMaxPower scaled, widened before the shift so the product cannot wrap: 255 × 8 is 2040, which does not fit eight bits.
request_fits is inclusive — the same convention as 16.4, 17.4, 17.5 and 18.5.
allowance_mA selects between the unit load and the request on one bit: configured.
The unit load follows the speed, not the request — a SuperSpeed device gets a larger floor as well as a larger ceiling (mutation B7, 44 184 errors).
Precedence on the state bit: detach or VBUS loss outranks SetConfiguration(0), which outranks SetConfiguration. Physical first, as in 18.2 §3.
7. Verilog-2005
// usb_bus_power_negotiate -- what a bus-powered device may draw, and when.
//
// Module 18 asked what a HUB can supply. This asks what a DEVICE may take,
// and the two are different questions with different answers at different
// times.
//
// The rule that surprises people is that a device's allowance CHANGES
// during enumeration, and it changes exactly once:
//
// BEFORE SetConfiguration one unit load. Always. However much the
// device declared it wants, however much the
// port has spare. The host has not yet agreed
// to anything, and a device that helped itself
// to 500 mA while merely attached could brown
// out a port before anyone had read a
// descriptor from it.
//
// AFTER SetConfiguration bMaxPower, scaled -- and only if the host
// accepted the configuration. If the port
// cannot supply it, SetConfiguration FAILS and
// the device stays at one unit load, addressed
// and enumerated and unusable.
//
// bMaxPower is stored in UNITS, not milliamps, and the unit depends on the
// speed: SuperSpeed counts in 8 mA, everything else in 2 mA. Linux:
//
// /* SuperSpeed power is in 8 mA units; others are in 2 mA units */
// unsigned mul = (udev->speed >= USB_SPEED_SUPER ? 8 : 2);
// return c->desc.bMaxPower * mul;
//
// An 8-bit field times 2 is 510 mA, which is why a USB 2.0 device can ask
// for the full 500 mA in one byte and not a milliamp more.
module usb_bus_power_negotiate #(
parameter integer UNIT_LOAD_HS = 100, // one unit load, USB 2.0, mA
parameter integer UNIT_LOAD_SS = 150 // one unit load, SuperSpeed, mA
) (
input wire clk,
input wire rst_n,
input wire vbus_present,
input wire superspeed, // scales BOTH the unit load and bMaxPower
input wire [7:0] b_max_power, // the descriptor field, in UNITS
input wire [15:0] port_offers_mA, // what the hub said this port supplies
input wire set_config, // host issues SetConfiguration
input wire set_config_zero, // SetConfiguration(0) -- deconfigure
input wire detach,
output wire [15:0] unit_load_mA,
output wire [15:0] requested_mA, // bMaxPower scaled into milliamps
output wire request_fits, // the port can supply the request
output reg configured,
output wire [15:0] allowance_mA, // what the device may draw RIGHT NOW
output reg config_refused, // sticky: a SetConfiguration failed
output reg [31:0] refusal_count
);
// The unit load is a property of the SPEED, not of the request. A
// SuperSpeed device gets a bigger floor as well as a bigger ceiling.
assign unit_load_mA = superspeed ? UNIT_LOAD_SS[15:0] : UNIT_LOAD_HS[15:0];
// bMaxPower is in units. Widened to 16 bits BEFORE the multiply so the
// product cannot wrap: 255 * 8 is 2040, which does not fit 8 bits and
// does fit 16.
assign requested_mA = superspeed ? ({8'd0, b_max_power} << 3) // x8
: ({8'd0, b_max_power} << 1); // x2
// Inclusive, as everywhere else in this track: a request that exactly
// consumes what the port offers is legal.
assign request_fits = (requested_mA <= port_offers_mA);
// THE POINT OF THE MODULE. Before configuration the device is entitled to
// one unit load and nothing else, whatever it declared. After a SUCCESSFUL
// SetConfiguration it is entitled to what it declared.
assign allowance_mA = configured ? requested_mA : unit_load_mA;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
configured <= 1'b0;
config_refused <= 1'b0;
refusal_count <= 32'd0;
end else if (detach || !vbus_present) begin
// Losing VBUS is not a state transition to negotiate, it is the end
// of the conversation. A device that stayed "configured" across a
// detach would resume drawing its full allowance on the next attach
// before the host had agreed to anything.
configured <= 1'b0;
config_refused <= 1'b0;
end else if (set_config_zero) begin
// SetConfiguration(0) returns the device to the Address state, and
// with it to one unit load.
configured <= 1'b0;
end else if (set_config) begin
if (request_fits) begin
configured <= 1'b1;
config_refused <= 1'b0;
end else begin
// The request does not fit. The device is NOT configured, and it
// stays at one unit load -- enumerated, addressed, visible in the
// device list, and unusable. This is what "the device is detected
// but doesn't work" looks like from inside the hardware.
configured <= 1'b0;
config_refused <= 1'b1;
refusal_count <= refusal_count + 32'd1;
end
end
end
endmoduleallowance_mA is one line and it is the whole module. Everything else computes the two candidate values; that line chooses between them, and the choice is the specification.
8. SystemVerilog
package usb_devpower_pkg;
// What a device is entitled to draw, as a named state rather than a flag.
// The distinction is not cosmetic: the two states differ by up to a factor
// of five, and "configured" is the only word that licenses the larger one.
typedef enum logic [1:0] {
PWR_UNPOWERED, // no VBUS -- the device may draw nothing at all
PWR_ONE_UNIT, // attached or addressed: ONE unit load, no more
PWR_CONFIGURED // SetConfiguration succeeded: bMaxPower, scaled
} dev_power_e;
endpackage
// usb_bus_power_negotiate_sv -- what a bus-powered device may draw, and when.
//
// Module 18 asked what a HUB can supply. This asks what a DEVICE may take,
// and the two are different questions with different answers at different
// times.
//
// The rule that surprises people is that a device's allowance CHANGES
// during enumeration, and it changes exactly once:
//
// BEFORE SetConfiguration one unit load. Always. However much the
// device declared it wants, however much the
// port has spare. The host has not yet agreed
// to anything, and a device that helped itself
// to 500 mA while merely attached could brown
// out a port before anyone had read a
// descriptor from it.
//
// AFTER SetConfiguration bMaxPower, scaled -- and only if the host
// accepted the configuration. If the port
// cannot supply it, SetConfiguration FAILS and
// the device stays at one unit load, addressed
// and enumerated and unusable.
//
// bMaxPower is stored in UNITS, not milliamps, and the unit depends on the
// speed: SuperSpeed counts in 8 mA, everything else in 2 mA. Linux:
//
// /* SuperSpeed power is in 8 mA units; others are in 2 mA units */
// unsigned mul = (udev->speed >= USB_SPEED_SUPER ? 8 : 2);
// return c->desc.bMaxPower * mul;
module usb_bus_power_negotiate_sv
import usb_devpower_pkg::*;
#(
parameter int unsigned UNIT_LOAD_HS = 100,
parameter int unsigned UNIT_LOAD_SS = 150
) (
input logic clk,
input logic rst_n,
input logic vbus_present,
input logic superspeed,
input logic [7:0] b_max_power,
input logic [15:0] port_offers_mA,
input logic set_config,
input logic set_config_zero,
input logic detach,
output logic [15:0] unit_load_mA,
output logic [15:0] requested_mA,
output logic request_fits,
output logic configured,
output logic [15:0] allowance_mA,
output dev_power_e power_state,
output logic config_refused,
output logic [31:0] refusal_count
);
initial begin
if (UNIT_LOAD_HS < 1 || UNIT_LOAD_SS < 1)
$fatal(1, "a unit load of zero entitles a device to nothing");
// 255 units x 8 mA is 2040, which must fit the arithmetic below.
if (UNIT_LOAD_SS > 2040)
$fatal(1, "UNIT_LOAD_SS=%0d exceeds what bMaxPower can express",
UNIT_LOAD_SS);
end
// The unit load is a property of the SPEED, not of the request. A
// SuperSpeed device gets a bigger floor as well as a bigger ceiling.
assign unit_load_mA = superspeed ? 16'(UNIT_LOAD_SS) : 16'(UNIT_LOAD_HS);
// bMaxPower is in units. Widened to 16 bits BEFORE the multiply so the
// product cannot wrap: 255 * 8 is 2040, which does not fit 8 bits and
// does fit 16.
assign requested_mA = superspeed ? (16'(b_max_power) << 3)
: (16'(b_max_power) << 1);
// Inclusive, as everywhere else in this track: a request that exactly
// consumes what the port offers is legal.
assign request_fits = (requested_mA <= port_offers_mA);
// THE POINT OF THE MODULE. Before configuration the device is entitled to
// one unit load and nothing else, whatever it declared.
assign allowance_mA = configured ? requested_mA : unit_load_mA;
// The same fact as a named state. `configured` is a bit a reader must
// interpret; PWR_ONE_UNIT says what the device may actually do.
always_comb begin
if (!vbus_present) power_state = PWR_UNPOWERED;
else if (configured) power_state = PWR_CONFIGURED;
else power_state = PWR_ONE_UNIT;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
configured <= 1'b0; config_refused <= 1'b0; refusal_count <= '0;
end else if (detach || !vbus_present) begin
// Losing VBUS is not a state transition to negotiate, it is the end
// of the conversation. A device that stayed "configured" across a
// detach would resume drawing its full allowance on the next attach
// before the host had agreed to anything.
configured <= 1'b0; config_refused <= 1'b0;
end else if (set_config_zero) begin
// SetConfiguration(0) returns the device to the Address state, and
// with it to one unit load.
configured <= 1'b0;
end else if (set_config) begin
if (request_fits) begin
configured <= 1'b1; config_refused <= 1'b0;
end else begin
// The request does not fit. The device is NOT configured, and it
// stays at one unit load -- enumerated, addressed, visible in the
// device list, and unusable. This is what "the device is detected
// but doesn't work" looks like from inside the hardware.
configured <= 1'b0; config_refused <= 1'b1;
refusal_count <= refusal_count + 1;
end
end
end
endmoduledev_power_e gives the three cases names, and the middle one is the chapter: PWR_ONE_UNIT is not "not yet configured", it is a specific entitlement with a specific number. A waveform showing configured = 0 tells a reader nothing about what the device may draw; a waveform showing PWR_ONE_UNIT tells them exactly.
9. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_devpower_pkg is
-- What a device is entitled to draw, as a named state rather than a flag.
-- The distinction is not cosmetic: the two states differ by up to a factor
-- of five, and "configured" is the only word that licenses the larger one.
type dev_power_t is (
PWR_UNPOWERED, -- no VBUS -- the device may draw nothing at all
PWR_ONE_UNIT, -- attached or addressed: ONE unit load, no more
PWR_CONFIGURED -- SetConfiguration succeeded: bMaxPower, scaled
);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_devpower_pkg.all;
-- usb_bus_power_negotiate_vhdl -- what a device may draw, and when.
--
-- Module 18 asked what a HUB can supply. This asks what a DEVICE may take,
-- and the two are different questions with different answers at different
-- times.
--
-- The rule that surprises people is that a device's allowance CHANGES
-- during enumeration, and it changes exactly once:
--
-- BEFORE SetConfiguration one unit load. Always. However much the
-- device declared it wants, however much the
-- port has spare. The host has not yet agreed
-- to anything, and a device that helped itself
-- to 500 mA while merely attached could brown
-- out a port before anyone had read a
-- descriptor from it.
--
-- AFTER SetConfiguration bMaxPower, scaled -- and only if the host
-- accepted the configuration. If the port
-- cannot supply it, SetConfiguration FAILS and
-- the device stays at one unit load, addressed
-- and enumerated and unusable.
--
-- bMaxPower is stored in UNITS, not milliamps, and the unit depends on the
-- speed: SuperSpeed counts in 8 mA, everything else in 2 mA. Linux:
--
-- /* SuperSpeed power is in 8 mA units; others are in 2 mA units */
-- unsigned mul = (udev->speed >= USB_SPEED_SUPER ? 8 : 2);
-- return c->desc.bMaxPower * mul;
entity usb_bus_power_negotiate_vhdl is
generic (
UNIT_LOAD_HS : positive := 100;
UNIT_LOAD_SS : positive := 150
);
port (
clk : in std_logic;
rst_n : in std_logic;
vbus_present : in std_logic;
superspeed : in std_logic;
b_max_power : in unsigned(7 downto 0);
port_offers_mA : in unsigned(15 downto 0);
set_config : in std_logic;
set_config_zero : in std_logic;
detach : in std_logic;
unit_load_mA : out unsigned(15 downto 0);
requested_mA : out unsigned(15 downto 0);
request_fits : out std_logic;
configured : out std_logic;
allowance_mA : out unsigned(15 downto 0);
power_state : out dev_power_t;
config_refused : out std_logic;
refusal_count : out unsigned(31 downto 0)
);
end entity;
architecture rtl of usb_bus_power_negotiate_vhdl is
signal ul_i : unsigned(15 downto 0);
signal req_i : unsigned(15 downto 0);
signal fits_i : std_logic;
signal cfg_r : std_logic := '0';
signal ref_r : std_logic := '0';
signal cnt_r : unsigned(31 downto 0) := (others => '0');
begin
assert UNIT_LOAD_SS <= 2040
report "UNIT_LOAD_SS exceeds what bMaxPower can express"
severity failure;
-- The unit load is a property of the SPEED, not of the request. A
-- SuperSpeed device gets a bigger floor as well as a bigger ceiling.
ul_i <= to_unsigned(UNIT_LOAD_SS, 16) when superspeed = '1'
else to_unsigned(UNIT_LOAD_HS, 16);
-- bMaxPower is in units. Resized to 16 bits BEFORE the shift so the
-- product cannot wrap: 255 * 8 is 2040, which does not fit 8 bits and
-- does fit 16. Written as a shift rather than a multiply because
-- numeric_std's "unsigned * natural" returns a result as wide as both
-- operands together, which is a runtime length error rather than a
-- truncation (chapter 18.4 section 9).
req_i <= shift_left(resize(b_max_power, 16), 3) when superspeed = '1'
else shift_left(resize(b_max_power, 16), 1);
-- Inclusive, as everywhere else in this track: a request that exactly
-- consumes what the port offers is legal.
fits_i <= '1' when req_i <= port_offers_mA else '0';
unit_load_mA <= ul_i;
requested_mA <= req_i;
request_fits <= fits_i;
configured <= cfg_r;
config_refused <= ref_r;
refusal_count <= cnt_r;
-- THE POINT OF THE MODULE. Before configuration the device is entitled to
-- one unit load and nothing else, whatever it declared.
allowance_mA <= req_i when cfg_r = '1' else ul_i;
-- The same fact as a named state. `configured` is a bit a reader must
-- interpret; PWR_ONE_UNIT says what the device may actually do.
power_state <= PWR_UNPOWERED when vbus_present = '0' else
PWR_CONFIGURED when cfg_r = '1' else
PWR_ONE_UNIT;
process (clk, rst_n)
begin
if rst_n = '0' then
cfg_r <= '0'; ref_r <= '0'; cnt_r <= (others => '0');
elsif rising_edge(clk) then
if detach = '1' or vbus_present = '0' then
-- Losing VBUS is not a state transition to negotiate, it is the end
-- of the conversation. A device that stayed "configured" across a
-- detach would resume drawing its full allowance on the next attach
-- before the host had agreed to anything.
cfg_r <= '0'; ref_r <= '0';
elsif set_config_zero = '1' then
-- SetConfiguration(0) returns the device to the Address state, and
-- with it to one unit load.
cfg_r <= '0';
elsif set_config = '1' then
if fits_i = '1' then
cfg_r <= '1'; ref_r <= '0';
else
-- The request does not fit. The device is NOT configured, and it
-- stays at one unit load -- enumerated, addressed, visible in the
-- device list, and unusable. This is what "the device is detected
-- but doesn't work" looks like from inside the hardware.
cfg_r <= '0'; ref_r <= '1';
cnt_r <= cnt_r + 1;
end if;
end if;
end if;
end process;
end architecture;shift_left(resize(...), 3) rather than * 8 is Chapter 18.4 §9's lesson applied before it could bite: numeric_std's unsigned * natural returns a result as wide as both operands together, which is a runtime length error rather than a truncation. The shift states both the intent and the width.
10. The Testbench: 5632 Scenarios, Exhaustively
The decision has three inputs: the entire bMaxPower field (256 values), the speed (2), and what the port offers.
for (s=0; s<2; s=s+1)
for (p=0; p<256; p=p+1)
for (o=0; o<11; o=o+1) begin
hard_reset;
ss=s[0]; bmp=p[7:0]; #1;
offers = offer_of(o, requested_mA); #1;
check_comb; // unconfigured allowance
if (offers == requested_mA) n_exact = n_exact + 1;
step(1, 0, 0, 1); // the host tries SetConfiguration
n_exh = n_exh + 1;
endThree safety properties are checked at every point, and none comes from the model:
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE one that matters: a configured device never has an allowance
// larger than its port offered it.
if (configured)
check(allowance_mA <= offers,
"a configured device may draw more than its port supplies");
// 2. An unconfigured device draws exactly one unit load, never more.
if (!configured)
check(allowance_mA === e_ul,
"an unconfigured device's allowance is not one unit load");
// 3. Being configured implies the request fitted.
check(!configured || request_fits,
"the device is configured with a request that does not fit");And the model multiplies where the design shifts — b * m against << 1 / << 3. Same answer, different arithmetic, so a mistake in one is not automatically a mistake in the other.
exhaustive bus-power sweep: 5632 of 5632 scenarios verified
REACH: exhaustive=5632 configured=2860 refused=2772 offer-exactly-request=522
[Verilog] usb_bus_power_negotiate: 0 errors — PASS
[SystemVerilog] usb_bus_power_negotiate_sv: 0 errors — PASS
[VHDL] usb_bus_power_negotiate_vhdl: 0 errors — PASS11. The Boundary That Was Reached Eight Times
The first version of this sweep had 4096 points and eight fixed port offers: 0, 100, 150, 250, 500, 510, 900 and 2040 mA — chosen because each is a real boundary in the specification.
Mutation B3 — the fit test made exclusive — died 212 times in Verilog and 82 in VHDL.
B3 differs from the correct design only when the request exactly equals the offer. With bMaxPower sweeping 0–255 and eight fixed offers, that happens when bMaxPower × 2 or × 8 lands precisely on one of those eight numbers:
| Speed | offers hit exactly | bMaxPower |
|---|---|---|
| USB 2.0 (×2) | 0, 100, 150, 250, 500, 510 | 0, 50, 75, 125, 250, 255 |
| SuperSpeed (×8) | 0, 2040 | 0, 255 |
Eight points out of 4096. Everything else in the sweep was strictly above or strictly below the line, where inclusive and exclusive agree.
The fix is to compute three of the offers from the request rather than fixing them:
// Offer modes 8, 9 and 10 are computed FROM the request: exactly the
// request, one milliamp under, one over. Without them the sweep lands on
// the inclusive boundary only where a fixed offer happens to equal
// bMaxPower x 2 -- eight times in 4096 points, which is not enough to
// distinguish an inclusive bound from an exclusive one.
function [15:0] offer_of(input integer mode, input [15:0] req);
begin
case (mode)
8: offer_of = req;
9: offer_of = (req == 16'd0) ? 16'd0 : (req - 16'd1);
10: offer_of = req + 16'd1;
default: offer_of = OFF[mode];
endcase
end
endfunctionExact-boundary hits went from 8 to 522, and B3 from 212 to 2782 (VHDL: 82 → 2652).
12. Mutation Testing — Across All Three Languages
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| B1 | no one-unit-load floor — the device draws what it declared | 52299 | 52299 | 53179 |
| B2 | SuperSpeed scaled by 2 as well | 35897 | 35897 | 39602 |
| B3 | the fit test becomes exclusive | 2782 | 2782 | 2652 |
| B4 | a refused SetConfiguration configures anyway | 25902 | 25902 | 28560 |
| B5 | losing VBUS does not deconfigure | 7100 | 7100 | 7439 |
| B6 | SetConfiguration(0) does not deconfigure | 1708 | 1708 | 1434 |
| B7 | the unit load ignores the speed | 44184 | 44184 | 47260 |
B1 is §1 as a one-line change and the largest count: it removes the floor entirely, so every unconfigured device draws whatever it felt like declaring.
B4 is the dangerous one in practice. It configures a device whose request the port cannot meet — the hardware equivalent of a host that ignores its own budget check. It dies 25 902 times, and the check that kills it is safety property 1: a configured device never has an allowance larger than its port offered it.
B6 scores lowest (1708) because SetConfiguration(0) is issued only in the randomised phase — the exhaustive sweep configures once and stops. That is a legitimate rarity rather than a hole: the mutation is reached thousands of times and dies every time.
13. A UVM Environment for Enumeration Power
The negotiation is a protocol over time — attach, read descriptors, configure, maybe fail — which is what sequences are for.
class enum_power_item extends uvm_sequence_item;
`uvm_object_utils(enum_power_item)
rand bit [7:0] b_max_power;
rand bit superspeed;
rand bit [15:0] port_offers_mA;
// Bias HARD toward the boundary. Section 11 measured what uniform
// stimulus does here: the inclusive bound is reached by coincidence,
// eight times in four thousand.
constraint c_boundary {
port_offers_mA dist {
(superspeed ? b_max_power*8 : b_max_power*2) := 10, // exactly
(superspeed ? b_max_power*8 : b_max_power*2) - 1 := 5, // just under
(superspeed ? b_max_power*8 : b_max_power*2) + 1 := 5, // just over
[0:2040] := 3 // anywhere
};
}
endclass
// The whole enumeration, as a sequence. The POINT is the window between
// attach and SetConfiguration, where the device is entitled to one unit
// load and a badly-behaved device would take more.
class enumerate_seq extends uvm_sequence #(enum_power_item);
`uvm_object_utils(enumerate_seq)
rand int unsigned descriptor_delay;
constraint c_delay { descriptor_delay inside {[1:20]}; }
task body();
enum_power_item it;
`uvm_do_with(it, { }) // attach: VBUS appears
repeat (descriptor_delay) @(posedge cfg.vif.clk); // host reads descriptors
// THE window. Everything above happens while the device is entitled to
// exactly one unit load, and the scoreboard checks that continuously.
`uvm_do_with(it, { }) // SetConfiguration
endtask
endclassThe scoreboard's central check is a temporal one, and it is the reason this block is worth a UVM environment rather than a directed test:
class bus_power_scoreboard extends uvm_scoreboard;
`uvm_component_utils(bus_power_scoreboard)
local bit m_configured; // its OWN copy (chapter 17.3)
function void write(power_txn t);
int unsigned unit_load = t.superspeed ? 150 : 100;
int unsigned request = t.b_max_power * (t.superspeed ? 8 : 2);
// THE property, and it is about a WINDOW rather than an instant: from
// attach until SetConfiguration succeeds, the allowance is one unit
// load. A device that exceeded it even for one cycle could brown out a
// port before the host knew what was attached.
if (!m_configured && t.allowance_mA != unit_load)
`uvm_error("PWR/FLOOR", $sformatf(
"unconfigured device allowed %0d mA; one unit load is %0d",
t.allowance_mA, unit_load))
// A configured device never exceeds what its port offered.
if (m_configured && t.allowance_mA > t.port_offers_mA)
`uvm_error("PWR/OVER", $sformatf(
"configured at %0d mA from a port offering %0d",
t.allowance_mA, t.port_offers_mA))
if (t.set_config) m_configured = (request <= t.port_offers_mA);
if (t.set_config_zero || !t.vbus_present) m_configured = 0;
endfunction
endclass
covergroup bus_power_cg with function sample(
int unsigned request, int unsigned offer, bit ss, bit configured);
cp_speed : coverpoint ss;
// The bin section 11 is about. Without deriving the offer from the
// request, this bin reads 8 out of 4096.
cp_rel : coverpoint (request == offer ? 0 : request < offer ? 1 : 2) {
bins exactly_fits = {0};
bins fits = {1};
bins too_big = {2};
}
x_speed_rel : cross cp_speed, cp_rel;
endgroup14. Assertions
// THE property: an unconfigured device draws exactly one unit load.
property p_unconfigured_floor;
@(posedge clk) disable iff (!rst_n)
(!configured) |-> (allowance_mA == unit_load_mA);
endproperty
a_unconfigured_floor : assert property (p_unconfigured_floor)
else $error("an unconfigured device's allowance is not one unit load");
// A configured device never exceeds what its port offered.
property p_within_offer;
@(posedge clk) disable iff (!rst_n)
configured |-> (allowance_mA <= port_offers_mA);
endproperty
a_within_offer : assert property (p_within_offer);
// Configuration only ever follows a request that fitted. Mutation B4.
property p_config_implies_fit;
@(posedge clk) disable iff (!rst_n)
($rose(configured)) |-> $past(request_fits);
endproperty
a_config_implies_fit : assert property (p_config_implies_fit);
// Losing VBUS deconfigures, next cycle, unconditionally. Mutation B5.
property p_vbus_deconfigures;
@(posedge clk) disable iff (!rst_n)
(!vbus_present) |=> (!configured);
endproperty
a_vbus_deconfigures : assert property (p_vbus_deconfigures);p_unconfigured_floor is the one to prove formally — it is a pure combinational implication, and a prover settles it for every bMaxPower and every offer at once, including the parameterisations no testbench instantiated.
These were written but not simulated; Icarus supports no concurrent assertions.
15. Debugging: the Device That Enumerates and Does Nothing
The report: a USB microphone appears in the device list with its correct name. It is never usable. On another machine it works. On a powered hub it works.
The procedure:
1. Check whether it is configured, not whether it is detected. Those are different states (§3), and every OS tool shows the second more prominently than the first. An enumerated, addressed, unconfigured device is the signature.
2. Read bMaxPower and remember it is in units. A device declaring 250 wants 500 mA, not 250. This is the single commonest misreading of a descriptor dump (§2).
3. Compare against what the port offers, which for a bus-powered hub is one unit load (18.5 §1). A 500 mA device on a bus-powered hub is refused with the budget completely empty — the per-port offer is the binding constraint, not the budget.
4. Explain "it works on another machine." A different host, or a root port rather than a hub, offers a full load. The device did not change and neither did its descriptor; the offer did.
5. If it fails on a powered hub too, check config_refused against the refusal count. A device that is refused repeatedly is being retried by the host; a device refused once and left alone has been given up on.
16. Common Misconceptions
"A device can draw its bMaxPower as soon as it is plugged in." One unit load until SetConfiguration succeeds (§1). Mutation B1, 52 299 errors.
"bMaxPower is in milliamps." It is in 2 mA units — 8 mA on SuperSpeed (§2). A device wanting 500 mA writes 250.
"A USB 2.0 device could ask for 1 A if it wanted." One byte × 2 mA tops out at 510 mA (§2). The encoding sets the ceiling.
"SetConfiguration always succeeds." It fails when the port cannot supply the request, and the device stays enumerated and unusable (§3).
"An unconfigured device is one the host has not got to yet." It may be one the host refused (§3) — which is why config_refused is a separate, sticky output.
"A configured device stays configured until told otherwise." Losing VBUS deconfigures it (§4), and must, or the floor could be bypassed by replugging.
"The unit load is always 100 mA." 150 mA on SuperSpeed (§2, §6). Mutation B7, 44 184 errors.
"4096 points covering the whole descriptor field is a thorough test." It reached the inclusive boundary eight times (§11).
17. Exercises
1. A SuperSpeed device needs exactly 900 mA. Determine what it must write in bMaxPower, what it will actually be granted, and whether any byte value yields exactly 900.
2. §11 derived three offers from the request. Identify every other relational comparison in this design and say which of them the fixed-offer sweep tested adequately and which it did not.
3. Mutation B6 scores lowest at 1708. Construct the smallest directed test that would kill it, and say why the exhaustive sweep does not contain one.
4. Write the SVA property that catches B2 — the wrong SuperSpeed multiplier — without referring to the constant 8.
5. Chapter 18.5 computed what a hub may supply; this chapter computes what a device may take. Determine what happens when the two disagree — a hub offering 100 mA to a device configured for 500 — and which module is responsible for preventing it.
6. The design deconfigures on !vbus_present in the same branch as detach. Determine whether they could be separated, what would have to change, and whether any mutation in §12 would notice.
18. Summary
A device's allowance changes exactly once (§1): one unit load from attach until SetConfiguration succeeds, then bMaxPower scaled. The floor exists because at attach time the host has not read a descriptor and has agreed to nothing.
bMaxPower is in units, not milliamps (§2) — 2 mA normally, 8 mA on SuperSpeed — and one byte times 2 is why 510 mA is the USB 2.0 ceiling.
SetConfiguration can fail (§3), and the result is a device that is enumerated, addressed, listed, and unusable. That is the hardware behind "it's detected but it doesn't work."
Losing VBUS deconfigures (§4), and must: otherwise the one-unit-load floor could be bypassed by unplugging and replugging.
All three HDL implementations were simulated (§19) and seven mutations died in all three (§12), with the negotiation verified exhaustively over all 5632 attach-and-configure scenarios (§10) — the entire 256-value descriptor field, both speeds, eleven offers.
And the inclusive boundary was reached eight times in the first 4096-point sweep (§11). Eight fixed offers, each a real specification boundary, and bMaxPower × 2 landed on one of them only by coincidence. Deriving three offers from the request took exact hits from 8 to 522 and mutation B3 from 212 to 2782.
To test a boundary you must land on it, and fixed stimulus values land on a relational boundary only by accident — 18.5 §12's lesson in a different disguise.
19. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_bus_power_negotiate | bp_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 5632/5632 | ✅ all seven |
| SystemVerilog | usb_bus_power_negotiate_sv | bp_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 5632/5632 | ✅ all seven |
| VHDL-2008 | usb_bus_power_negotiate_vhdl | bp_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 5632/5632 | ✅ all seven |
| UVM (§13) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§14) | — | — | ❌ unsupported by Icarus | ❌ | — |
The three languages report identical reach (5632 / 2860 / 2772 / 522) because the exhaustive sweep is deterministic; only the randomised tail differs, and it is checked rather than counted.
VHDL's counts run consistently higher than the other two on B1, B2, B4, B5 and B7 — by roughly 5–10 % — because its randomised phase draws from a different generator and reaches the configured state slightly more often. The exhaustive portion is identical by construction.
20. What Comes Next
This chapter assumed the device takes its power from the bus. Many do not.
A printer, a powered hub, a desktop drive — each has its own supply and draws nothing meaningful from VBUS. Their configuration descriptor says so, with a bit Linux names USB_CONFIG_ATT_SELFPOWER, and the host's budget arithmetic skips them entirely.
Chapter 19.2 — Self Power is about those devices, and the interesting thing is what they still owe the bus. A self-powered device must keep watching VBUS even though it does not draw from it — and a device that ignores this does something genuinely destructive: it drives its pull-up, and current, back into a host that has deliberately removed power.
That failure has a name, a mechanism, and a one-line fix in RTL, and it is where the next chapter starts.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
Power Issues
The same voltage and the same current are a healthy device at one instant and a fault a second later — in-rush is legal, sag is not, and the only thing separating them is a time window.
- 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
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.
- 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.
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.
