USB · Module 19
Power Budgeting
The host walks its whole tree — and a device that is enumerated but not configured still costs a full unit load, because it is entitled to one whether it uses it or not.
Module 18 asked what a hub can supply. Chapters 19.1 and 19.2 asked what a device may take.
Neither is the question a host actually has to answer, which is: given this tree, with these devices in these states, is the next thing anyone plugs in going to work?
1. The Host Walks
There is no central register holding the answer. The host computes it, by walking the tree and subtracting:
remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
if (udev->actconfig)
delta = usb_get_max_power(udev, udev->actconfig);
else if (port1 != udev->bus->otg_port || hdev->parent)
delta = unit_load;
else
delta = 8;
if (delta > hub->mA_per_port)
dev_warn(... "%dmA is over %umA budget!\n", ...);
remaining -= delta;
}
if (remaining < 0) {
dev_warn(... "%dmA over power budget!\n", -remaining);
remaining = 0;
}Start with what the hub may draw, take out its own controller (18.5 §3), then subtract every attached device in turn.
2. The Term That Surprises People
Look at the else branch.
A device that is present but NOT configured is charged a full unit load — not zero, and not what its descriptor asks for.
That is 19.1's floor seen from the host's side. The device is entitled to one unit load from the moment it attaches, whether it is drawing it or not, so the host must reserve it.
And it is the commonest state a device is in during the most fragile moment there is — enumeration, when several devices may be present and none configured yet.
3. Two Warnings, Two Different Fixes
The loop produces two over-budget conditions, and they are not the same:
| Condition | Means | Fix | |
|---|---|---|---|
| per port | delta > mA_per_port | this one device wants more than a port here offers | move it to a hub that offers more |
| whole hub | remaining < 0 | the devices together exceed the hub | remove one |
They are independent. A 500 mA device on a hub offering 100 mA per port trips the per-port warning while the hub has 2400 mA spare. Four unconfigured devices on a hub with 200 mA available trip the whole-hub warning while no single port is over.
A design reporting one flag for both cannot tell a user which to do, and mutation T2 — conflating them — dies 11 015 times.
The sweep in §7 reaches both independently: 17 812 per-port events and 17 445 whole-hub events across 8192 trees.
4. Saturating, Twice
Two subtractions in this block can go negative, and both must saturate.
bus_mA - contr_current — a hub whose descriptor claims a controller current larger than its upstream allowance (18.5 §3). Mutation T4, 7300 errors.
available - total_charged — the tree is over budget. Mutation T3, 53 438 errors.
An unsigned budget that wrapped would report an enormous amount of headroom at the exact moment it had none — which is the single worst answer available, because it is the one that causes the host to admit the next device too.
Linux does the same thing, in the same place, for the same reason: it warns, then clamps remaining to zero.
5. The Walk, Drawn
The two verdict blocks do not feed each other. That is §3, and it is the structural reason a single "over budget" bit would be a worse design rather than a simpler one.
6. Verilog-2005
// usb_tree_power_walk -- the host's whole-tree accounting, where chapter
// 18.5's per-hub rule and chapter 19.1's per-device rule finally meet.
//
// Module 18 asked what a HUB can supply. Chapters 19.1 and 19.2 asked what a
// DEVICE may take. Neither question is the one a host actually has to answer,
// which is: given this tree, with these devices in these states, is the next
// thing anyone plugs in going to work?
//
// The host answers it by WALKING. Linux does it in a loop:
//
// remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
// for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
// ...
// if (udev->actconfig)
// delta = usb_get_max_power(udev, udev->actconfig);
// else if (port1 != udev->bus->otg_port || hdev->parent)
// delta = unit_load;
// else
// delta = 8;
// if (delta > hub->mA_per_port)
// dev_warn(... "%dmA is over %umA budget!\n", ...);
// remaining -= delta;
// }
// if (remaining < 0) {
// dev_warn(... "%dmA over power budget!\n", -remaining);
// remaining = 0;
// }
//
// THE TERM THAT SURPRISES PEOPLE
//
// Look at the `else` branch. A device that is present but NOT CONFIGURED is
// charged a full UNIT LOAD -- not zero, and not what its descriptor asks for.
//
// That is chapter 19.1's floor seen from the host's side. The device is
// ENTITLED to one unit load from the moment it attaches, whether it is
// drawing it or not, so the host must reserve it. A budget that charged
// unconfigured devices nothing would be reserving power it has already
// promised away, and the first device to actually use its entitlement would
// take the tree over.
//
// TWO DIFFERENT WARNINGS
//
// The loop produces two, and they are not the same condition:
//
// PER PORT delta > mA_per_port -- this ONE device wants more than a port
// on this hub is offered (18.5). The hub may have plenty left.
// WHOLE HUB remaining < 0 -- the devices TOGETHER exceed what the hub can
// source, though each one individually fits.
//
// A design reporting one flag for both cannot tell a user whether to move a
// device or remove one, which are the two different fixes.
//
// And `remaining` SATURATES at zero after the warning rather than going
// negative -- an unsigned budget that wrapped would report an enormous
// amount of headroom at the exact moment it had none.
module usb_tree_power_walk #(
parameter integer NPORTS = 4,
parameter integer UNIT_LOAD_HS = 100, // one unit load, USB 2.0, mA
parameter integer UNIT_LOAD_SS = 150, // one unit load, SuperSpeed, mA
parameter integer OTG_UNCONF_MA = 8 // an unconfigured OTG port gets 8
) (
input wire clk,
input wire rst_n,
input wire [15:0] bus_mA, // what this hub may draw
input wire [15:0] contr_current, // bHubContrCurrent (18.5)
input wire [15:0] mA_per_port, // what it offers a port (18.5)
input wire superspeed,
input wire [NPORTS-1:0] present,
input wire [NPORTS-1:0] configured,
input wire [NPORTS*8-1:0] max_power_flat, // bMaxPower per port, in UNITS
input wire otg_port0, // port 0 is a root OTG port
output wire [15:0] unit_load_mA,
output reg [NPORTS-1:0] port_over_budget,
output reg [15:0] total_charged,
output wire [15:0] remaining_mA,
output wire tree_over_budget,
output wire [1:0] budget_verdict, // the two conditions, named
output reg [31:0] over_events,
output reg ever_over
);
// The unit load follows the speed, as in 19.1. A SuperSpeed tree reserves
// more per unconfigured device because a SuperSpeed device is entitled to
// more from the moment it attaches.
assign unit_load_mA = superspeed ? UNIT_LOAD_SS[15:0] : UNIT_LOAD_HS[15:0];
// What the hub has to give out at all, after its own controller. Saturating
// (18.5): a malformed descriptor claiming more than the upstream supplies
// must yield zero, not a wrapped enormous number.
wire [15:0] available = (bus_mA > contr_current)
? (bus_mA - contr_current) : 16'd0;
integer i;
reg [15:0] delta;
reg [31:0] charged; // 32 bits: the SUM must not wrap even if the
// individual deltas are all at their maximum
always @* begin
charged = 32'd0;
port_over_budget = {NPORTS{1'b0}};
for (i = 0; i < NPORTS; i = i + 1) begin
if (!present[i])
// Nothing attached costs nothing. This is the only case that does.
delta = 16'd0;
else if (configured[i])
// Configured: what it declared, scaled. 19.1's arithmetic.
delta = superspeed ? ({8'd0, max_power_flat[i*8 +: 8]} << 3)
: ({8'd0, max_power_flat[i*8 +: 8]} << 1);
else if (otg_port0 && i == 0)
// The OTG exception: an unconfigured device on a root hub's OTG
// port is charged 8 mA rather than a unit load.
delta = OTG_UNCONF_MA[15:0];
else
// THE TERM. Present but unconfigured still costs a full unit load,
// because 19.1 entitles it to one from the moment it attached.
delta = unit_load_mA;
// PER-PORT warning: this one device wants more than a port here is
// offered. Independent of whether the hub has room overall.
if (delta > mA_per_port) port_over_budget[i] = 1'b1;
charged = charged + {16'd0, delta};
end
total_charged = (charged > 32'hFFFF) ? 16'hFFFF : charged[15:0];
end
// WHOLE-HUB condition: the devices together exceed what the hub can source.
assign tree_over_budget = (total_charged > available);
// Saturating, and for the same reason the subtraction above is: a budget
// that wrapped would report enormous headroom at the moment it had none.
assign remaining_mA = tree_over_budget ? 16'd0 : (available - total_charged);
// The two conditions as one named verdict. They are INDEPENDENT -- either,
// neither, or both -- and a reader seeing BUDGET_PORT_OVER knows to move a
// device, where BUDGET_TREE_OVER means remove one. Verilog-2005 has no
// enumerated type, so the encoding is localparams; chapters 19.2 and 19.3
// both measured what happens when one language exposes less than the rest.
localparam [1:0] BUDGET_OK = 2'd0,
BUDGET_PORT_OVER = 2'd1, // one device wants too much
BUDGET_TREE_OVER = 2'd2, // the devices TOGETHER do
BUDGET_BOTH = 2'd3;
assign budget_verdict =
( tree_over_budget && (port_over_budget != {NPORTS{1'b0}})) ? BUDGET_BOTH
: ( tree_over_budget) ? BUDGET_TREE_OVER
: ((port_over_budget != {NPORTS{1'b0}})) ? BUDGET_PORT_OVER
: BUDGET_OK;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
over_events <= 32'd0;
ever_over <= 1'b0;
end else if (tree_over_budget || (port_over_budget != {NPORTS{1'b0}})) begin
over_events <= over_events + 32'd1;
ever_over <= 1'b1;
end
end
endmodulecharged is 32 bits against a 16-bit result, and narrowed once at the end. Four ports at the largest expressible request is 8160 mA, which fits 16 bits — so the extra width is not needed for this parameterisation and is needed for the general one. A width that is correct only for the default generic is a latent bug with a configuration attached to it.
7. SystemVerilog
package usb_budget_pkg;
// The two over-budget conditions, named. They are INDEPENDENT -- either,
// neither, or both -- and the distinction is the difference between two
// pieces of advice: BUDGET_PORT_OVER means move a device to a hub that
// offers more per port; BUDGET_TREE_OVER means remove one.
typedef enum logic [1:0] {
BUDGET_OK,
BUDGET_PORT_OVER, // one device wants more than a port here offers
BUDGET_TREE_OVER, // the devices TOGETHER exceed the hub
BUDGET_BOTH
} budget_verdict_e;
endpackage
// usb_tree_power_walk_sv -- the host's whole-tree accounting, where chapter
// 18.5's per-hub rule and chapter 19.1's per-device rule finally meet.
//
// Module 18 asked what a HUB can supply. Chapters 19.1 and 19.2 asked what a
// DEVICE may take. Neither question is the one a host actually has to answer,
// which is: given this tree, with these devices in these states, is the next
// thing anyone plugs in going to work?
//
// The host answers it by WALKING. Linux does it in a loop:
//
// remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
// for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
// ...
// if (udev->actconfig)
// delta = usb_get_max_power(udev, udev->actconfig);
// else if (port1 != udev->bus->otg_port || hdev->parent)
// delta = unit_load;
// else
// delta = 8;
// if (delta > hub->mA_per_port)
// dev_warn(... "%dmA is over %umA budget!\n", ...);
// remaining -= delta;
// }
// if (remaining < 0) {
// dev_warn(... "%dmA over power budget!\n", -remaining);
// remaining = 0;
// }
//
// THE TERM THAT SURPRISES PEOPLE
//
// Look at the `else` branch. A device that is present but NOT CONFIGURED is
// charged a full UNIT LOAD -- not zero, and not what its descriptor asks for.
//
// That is chapter 19.1's floor seen from the host's side. The device is
// ENTITLED to one unit load from the moment it attaches, whether it is
// drawing it or not, so the host must reserve it. A budget that charged
// unconfigured devices nothing would be reserving power it has already
// promised away, and the first device to actually use its entitlement would
// take the tree over.
//
// TWO DIFFERENT WARNINGS
//
// The loop produces two, and they are not the same condition:
//
// PER PORT delta > mA_per_port -- this ONE device wants more than a port
// on this hub is offered (18.5). The hub may have plenty left.
// WHOLE HUB remaining < 0 -- the devices TOGETHER exceed what the hub can
// source, though each one individually fits.
//
// A design reporting one flag for both cannot tell a user whether to move a
// device or remove one, which are the two different fixes.
//
// And `remaining` SATURATES at zero after the warning rather than going
// negative -- an unsigned budget that wrapped would report an enormous
// amount of headroom at the exact moment it had none.
module usb_tree_power_walk_sv
import usb_budget_pkg::*;
#(
parameter int unsigned NPORTS = 4,
parameter int unsigned UNIT_LOAD_HS = 100, // one unit load, USB 2.0, mA
parameter int unsigned UNIT_LOAD_SS = 150, // one unit load, SuperSpeed
parameter int unsigned OTG_UNCONF_MA = 8 // an unconfigured OTG port
) (
input logic clk,
input logic rst_n,
input logic [15:0] bus_mA, // what this hub may draw
input logic [15:0] contr_current, // bHubContrCurrent (18.5)
input logic [15:0] mA_per_port, // what it offers a port (18.5)
input logic superspeed,
input logic [NPORTS-1:0] present,
input logic [NPORTS-1:0] configured,
input logic [NPORTS*8-1:0] max_power_flat, // bMaxPower per port, in UNITS
input logic otg_port0, // port 0 is a root OTG port
output logic [15:0] unit_load_mA,
output logic [NPORTS-1:0] port_over_budget,
output logic [15:0] total_charged,
output logic [15:0] remaining_mA,
output logic tree_over_budget,
output budget_verdict_e budget_verdict,
output logic [31:0] over_events,
output logic ever_over
);
// The unit load follows the speed, as in 19.1. A SuperSpeed tree reserves
// more per unconfigured device because a SuperSpeed device is entitled to
// more from the moment it attaches.
assign unit_load_mA = superspeed ? 16'(UNIT_LOAD_SS) : 16'(UNIT_LOAD_HS);
// What the hub has to give out at all, after its own controller. Saturating
// (18.5): a malformed descriptor claiming more than the upstream supplies
// must yield zero, not a wrapped enormous number.
wire [15:0] available = (bus_mA > contr_current)
? (bus_mA - contr_current) : 16'd0;
initial begin
if (UNIT_LOAD_HS < 1 || UNIT_LOAD_SS < 1)
$fatal(1, "a unit load of zero reserves nothing for an attached device");
end
logic [15:0] delta;
logic [31:0] charged; // 32 bits: the SUM must not wrap even if the
// individual deltas are all at their maximum
always_comb begin
charged = 32'd0;
port_over_budget = '0;
for (int i = 0; i < int'(NPORTS); i++) begin
if (!present[i])
// Nothing attached costs nothing. This is the only case that does.
delta = 16'd0;
else if (configured[i])
// Configured: what it declared, scaled. 19.1's arithmetic.
delta = superspeed ? ({8'd0, max_power_flat[i*8 +: 8]} << 3)
: ({8'd0, max_power_flat[i*8 +: 8]} << 1);
else if (otg_port0 && i == 0)
// The OTG exception: an unconfigured device on a root hub's OTG
// port is charged 8 mA rather than a unit load.
delta = 16'(OTG_UNCONF_MA);
else
// THE TERM. Present but unconfigured still costs a full unit load,
// because 19.1 entitles it to one from the moment it attached.
delta = unit_load_mA;
// PER-PORT warning: this one device wants more than a port here is
// offered. Independent of whether the hub has room overall.
if (delta > mA_per_port) port_over_budget[i] = 1'b1;
charged = charged + {16'd0, delta};
end
end
// Continuous, so the 32-bit accumulator is narrowed in one place and the
// process contains no part-select.
assign total_charged = (charged > 32'hFFFF) ? 16'hFFFF : charged[15:0];
// WHOLE-HUB condition: the devices together exceed what the hub can source.
assign tree_over_budget = (total_charged > available);
// Saturating, and for the same reason the subtraction above is: a budget
// that wrapped would report enormous headroom at the moment it had none.
assign remaining_mA = tree_over_budget ? 16'd0 : (available - total_charged);
// The two conditions as one named verdict. They are INDEPENDENT -- either,
// neither, or both -- and a reader seeing BUDGET_PORT_OVER knows to move a
// device, where BUDGET_TREE_OVER means remove one.
always_comb begin
if (tree_over_budget && port_over_budget != '0) budget_verdict = BUDGET_BOTH;
else if (tree_over_budget) budget_verdict = BUDGET_TREE_OVER;
else if (port_over_budget != '0) budget_verdict = BUDGET_PORT_OVER;
else budget_verdict = BUDGET_OK;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
over_events <= '0;
ever_over <= 1'b0;
end else if (tree_over_budget || port_over_budget != '0) begin
over_events <= over_events + 1;
ever_over <= 1'b1;
end
end
endmodulebudget_verdict_e has four values, not three, because §3's two conditions are independent: BUDGET_BOTH is a real state a tree can be in, and a design with three values would have to pick one to report.
8. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb_budget_pkg is
-- The two over-budget conditions, named. They are INDEPENDENT -- either,
-- neither, or both -- and the distinction is the difference between two
-- pieces of advice: BUDGET_PORT_OVER means move a device to a hub that
-- offers more per port; BUDGET_TREE_OVER means remove one.
type budget_verdict_t is (
BUDGET_OK,
BUDGET_PORT_OVER, -- one device wants more than a port here offers
BUDGET_TREE_OVER, -- the devices TOGETHER exceed the hub
BUDGET_BOTH
);
-- An UNCONSTRAINED array of per-port bMaxPower values, in UNITS. The
-- Verilog and SystemVerilog flatten this into one wide vector and slice
-- it back out; here the port is what it actually is (chapter 18.5).
type mp_array_t is array (natural range <>) of unsigned(7 downto 0);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_budget_pkg.all;
-- usb_tree_power_walk_vhdl -- the host's whole-tree accounting, where chapter
-- 18.5's per-hub rule and chapter 19.1's per-device rule finally meet.
--
-- Module 18 asked what a HUB can supply. Chapters 19.1 and 19.2 asked what a
-- DEVICE may take. Neither is the question a host actually has to answer,
-- which is: given this tree, with these devices in these states, is the next
-- thing anyone plugs in going to work?
--
-- The host answers it by WALKING. Linux does it in a loop:
--
-- remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
-- for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-- if (udev->actconfig)
-- delta = usb_get_max_power(udev, udev->actconfig);
-- else if (port1 != udev->bus->otg_port || hdev->parent)
-- delta = unit_load;
-- else
-- delta = 8;
-- if (delta > hub->mA_per_port)
-- dev_warn(... "%dmA is over %umA budget!\n", ...);
-- remaining -= delta;
-- }
-- if (remaining < 0) {
-- dev_warn(... "%dmA over power budget!\n", -remaining);
-- remaining = 0;
-- }
--
-- THE TERM THAT SURPRISES PEOPLE
--
-- Look at the `else` branch. A device that is present but NOT CONFIGURED is
-- charged a full UNIT LOAD -- not zero, and not what its descriptor asks for.
--
-- That is chapter 19.1's floor seen from the host's side. The device is
-- ENTITLED to one unit load from the moment it attaches, whether it is
-- drawing it or not, so the host must reserve it.
--
-- TWO DIFFERENT WARNINGS
--
-- PER PORT delta > mA_per_port -- this ONE device wants more than a port
-- on this hub is offered (18.5). The hub may have plenty left.
-- WHOLE HUB remaining < 0 -- the devices TOGETHER exceed what the hub can
-- source, though each one individually fits.
entity usb_tree_power_walk_vhdl is
generic (
NPORTS : positive := 4;
UNIT_LOAD_HS : positive := 100; -- one unit load, USB 2.0, mA
UNIT_LOAD_SS : positive := 150; -- one unit load, SuperSpeed, mA
OTG_UNCONF_MA : positive := 8 -- an unconfigured OTG port gets 8
);
port (
clk : in std_logic;
rst_n : in std_logic;
bus_mA : in unsigned(15 downto 0);
contr_current : in unsigned(15 downto 0);
mA_per_port : in unsigned(15 downto 0);
superspeed : in std_logic;
present : in std_logic_vector(NPORTS-1 downto 0);
configured : in std_logic_vector(NPORTS-1 downto 0);
max_power : in mp_array_t(0 to NPORTS-1);
otg_port0 : in std_logic;
unit_load_mA : out unsigned(15 downto 0);
port_over_budget : out std_logic_vector(NPORTS-1 downto 0);
total_charged : out unsigned(15 downto 0);
remaining_mA : out unsigned(15 downto 0);
tree_over_budget : out std_logic;
budget_verdict : out budget_verdict_t;
over_events : out unsigned(31 downto 0);
ever_over : out std_logic
);
end entity;
architecture rtl of usb_tree_power_walk_vhdl is
constant ZERO : std_logic_vector(NPORTS-1 downto 0) := (others => '0');
signal ul_i : unsigned(15 downto 0);
signal avail_i : unsigned(15 downto 0);
signal chg_i : unsigned(15 downto 0);
signal pob_i : std_logic_vector(NPORTS-1 downto 0);
signal tob_i : std_logic;
signal evt_r : unsigned(31 downto 0) := (others => '0');
signal ever_r : std_logic := '0';
begin
-- The unit load follows the speed, as in 19.1. A SuperSpeed tree reserves
-- more per unconfigured device because a SuperSpeed device is entitled to
-- more from the moment it attaches.
ul_i <= to_unsigned(UNIT_LOAD_SS, 16) when superspeed = '1'
else to_unsigned(UNIT_LOAD_HS, 16);
-- What the hub has to give out at all, after its own controller.
-- Saturating (18.5): a malformed descriptor claiming more than the
-- upstream supplies must yield zero, not a wrapped enormous number.
avail_i <= (bus_mA - contr_current) when bus_mA > contr_current
else to_unsigned(0, 16);
process (present, configured, max_power, otg_port0, ul_i, mA_per_port,
superspeed)
variable delta : unsigned(15 downto 0);
variable charged : unsigned(31 downto 0);
variable pob : std_logic_vector(NPORTS-1 downto 0);
begin
charged := (others => '0');
pob := (others => '0');
for i in 0 to NPORTS-1 loop
if present(i) = '0' then
-- Nothing attached costs nothing. This is the only case that does.
delta := to_unsigned(0, 16);
elsif configured(i) = '1' then
-- Configured: what it declared, scaled. 19.1's arithmetic.
if superspeed = '1' then
delta := shift_left(resize(max_power(i), 16), 3);
else
delta := shift_left(resize(max_power(i), 16), 1);
end if;
elsif otg_port0 = '1' and i = 0 then
-- The OTG exception: an unconfigured device on a root hub's OTG
-- port is charged 8 mA rather than a unit load.
delta := to_unsigned(OTG_UNCONF_MA, 16);
else
-- THE TERM. Present but unconfigured still costs a full unit load,
-- because 19.1 entitles it to one from the moment it attached.
delta := ul_i;
end if;
-- PER-PORT warning: this one device wants more than a port here is
-- offered. Independent of whether the hub has room overall.
if delta > mA_per_port then pob(i) := '1'; end if;
charged := charged + resize(delta, 32);
end loop;
pob_i <= pob;
if charged > 65535 then chg_i <= to_unsigned(65535, 16);
else chg_i <= charged(15 downto 0); end if;
end process;
-- WHOLE-HUB condition: the devices together exceed what the hub can source.
tob_i <= '1' when chg_i > avail_i else '0';
unit_load_mA <= ul_i;
port_over_budget <= pob_i;
total_charged <= chg_i;
tree_over_budget <= tob_i;
over_events <= evt_r;
ever_over <= ever_r;
-- Saturating, and for the same reason the subtraction above is: a budget
-- that wrapped would report enormous headroom at the moment it had none.
remaining_mA <= to_unsigned(0, 16) when tob_i = '1'
else (avail_i - chg_i);
-- The two conditions as one named verdict. They are INDEPENDENT -- either,
-- neither, or both -- and a reader seeing BUDGET_PORT_OVER knows to move a
-- device, where BUDGET_TREE_OVER means remove one.
budget_verdict <= BUDGET_BOTH when (tob_i = '1' and pob_i /= ZERO) else
BUDGET_TREE_OVER when tob_i = '1' else
BUDGET_PORT_OVER when pob_i /= ZERO else
BUDGET_OK;
process (clk, rst_n)
begin
if rst_n = '0' then
evt_r <= (others => '0'); ever_r <= '0';
elsif rising_edge(clk) then
if tob_i = '1' or pob_i /= ZERO then
evt_r <= evt_r + 1;
ever_r <= '1';
end if;
end if;
end process;
end architecture;mp_array_t carries the per-port bMaxPower values as an actual array, as in 18.5 §10 — the Verilog and SystemVerilog flatten four 8-bit values into a 32-bit vector and slice them back with max_power_flat[i*8 +: 8]. The array form cannot be off by a factor of the element width.
And the VHDL testbench needed two renames the other two did not. bus is a reserved word, so the budget table could not be called BUS; and VHDL is case-insensitive, so a constant MP collided with the signal mp. Neither is a language defect — both are the compiler refusing something that would have read ambiguously.
9. The Testbench: 8192 Trees, Exhaustively
Each port is in one of four states — absent, present-but-unconfigured, configured-small, configured-large — which for four ports is 4⁴ = 256 tree shapes. Crossed with 8 budgets, both speeds, and the OTG flag: 8192 trees, the entire domain.
// The OTG dimension is swept rather than held at zero: an earlier
// version fixed it, and mutation T5 -- the OTG exception applied to
// every port instead of port 0 -- was then reachable only from the
// randomised phase. A dimension held constant is a dimension untested.
for (s=0; s<2; s=s+1)
for (cfgi=0; cfgi<256; cfgi=cfgi+1)
for (b=0; b<8; b=b+1)
for (o=0; o<2; o=o+1) beginFour properties are checked against no model, and the first is the one with consequences:
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE one that matters: remaining is never more than available.
// A budget reporting headroom it does not have is worse than one
// reporting none.
check(remaining_mA <= e_avail[15:0],
"the budget reported more headroom than the hub can source");
// 2. remaining and over_budget never both say the tree is fine.
check(!tree_over_budget || remaining_mA === 16'd0,
"an over-budget tree still reported headroom");
// 3. A port with nothing attached is never flagged.
check((port_over_budget & ~present) === {N{1'b0}},
"an empty port was flagged as over budget");And the model accumulates with a plain integer where the design uses a 32-bit register and saturates at the end — same answer, different arithmetic, so a mistake in one is not automatically a mistake in the other.
Measured reach:
exhaustive tree sweep: 8192 of 8192 trees verified
Verilog / SystemVerilog:
REACH: trees=8192 tree-over=17812 port-over=17445
budget-exactly-spent=606 with-unconfigured=5600
[Verilog] usb_tree_power_walk: 0 errors — PASS
VHDL:
REACH: trees=8192 tree-over=17802 port-over=17417
budget-exactly-spent=628 with-unconfigured=5600
[VHDL] usb_tree_power_walk_vhdl: 0 errors — PASSwith-unconfigured=5600 is §2's term reached 5600 times, and tree-over and port-over at roughly equal counts is §3's independence made measurable: if one implied the other they would not track separately.
10. A Dimension Held Constant Is a Dimension Untested
The first version of this sweep had 4096 trees and held otg_port0 at zero throughout.
Mutation T5 — the OTG exception applied to every port rather than port 0 — died 3142 times in Verilog and 2873 in VHDL, and every one of those kills came from the randomised phase and one directed test. The exhaustive sweep contributed nothing at all, because with otg_port0 fixed low the mutation is unreachable inside it.
Adding the dimension took the sweep to 8192 trees and T5 to 7724.
11. Mutation Testing — Across All Three Languages
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| T1 | an unconfigured device costs nothing | 41115 | 41115 | 40614 |
| T2 | the two over-budget conditions conflated | 11015 | 11015 | 10864 |
| T3 | remaining wraps instead of saturating | 53438 | 53438 | 53408 |
| T4 | the available subtraction wraps | 7300 | 7300 | 7138 |
| T5 | the OTG exception applied to every port | 7724 | 7724 | 7455 |
| T6 | the per-port test becomes inclusive | 10117 | 10117 | 10077 |
| T7 | a configured device is charged a unit load | 57400 | 57400 | 57548 |
T7 scores highest because it corrupts every configured device in every tree — and it is the mirror image of T1: one charges configured devices as though they were unconfigured, the other charges unconfigured devices as though they were absent. Between them they are the whole of §2.
T4 scores lowest at 7300 and needs a malformed hub descriptor to be reachable at all — contr_current > bus_mA, which the sweep supplies deliberately because real descriptors occasionally contain it.
T6 is the inclusive/exclusive boundary again, and unlike 19.1 §11 it needed no stimulus work: delta here takes a small set of values that the budget table was already built to straddle.
12. A UVM Environment for a Tree
The block is combinational; what UVM adds is generating tree shapes, and generating the ones that sit exactly on the boundary.
class tree_item extends uvm_sequence_item;
`uvm_object_utils(tree_item)
rand bit [3:0] present;
rand bit [3:0] configured;
rand bit [7:0] max_power [4];
rand bit [15:0] bus_mA, contr_current, mA_per_port;
rand bit superspeed, otg_port0;
// A device cannot be configured without being present. Without this the
// solver spends most of its time on trees that cannot exist.
constraint c_sane { (configured & ~present) == 0; }
// Requests come from the alphabet real descriptors contain, not uniformly
// over 8 bits.
constraint c_realistic {
foreach (max_power[i]) max_power[i] inside {0, 25, 50, 100, 250};
mA_per_port inside {100, 150, 500, 900};
}
// THE constraint: make the total land exactly ON the budget. Section 9
// reached that 606 times out of 8192 by luck; this reaches it on demand,
// and it is the only place an inclusive bound can be distinguished from
// an exclusive one.
constraint c_on_boundary {
solve present, configured, max_power before bus_mA, contr_current;
(bus_mA - contr_current) == charged_total();
}
function int unsigned charged_total();
int unsigned t = 0;
foreach (present[i])
if (present[i])
t += configured[i] ? max_power[i] * (superspeed ? 8 : 2)
: (superspeed ? 150 : 100);
return t;
endfunction
endclass
class budget_scoreboard extends uvm_scoreboard;
`uvm_component_utils(budget_scoreboard)
function void write(budget_txn t);
int unsigned avail = (t.bus_mA > t.contr_current)
? t.bus_mA - t.contr_current : 0;
int unsigned charged = 0;
int unsigned unit_load = t.superspeed ? 150 : 100;
foreach (t.present[i])
if (!t.present[i]) charged += 0;
else if (t.configured[i]) charged += t.max_power[i]
* (t.superspeed ? 8 : 2);
else if (t.otg_port0 && i == 0) charged += 8;
else charged += unit_load; // SECTION 2
// THE property: never report headroom the hub does not have. Its
// violation causes the host to admit the NEXT device too, which is how
// one accounting error becomes a brown-out.
if (t.remaining_mA > avail)
`uvm_error("BUDGET/PHANTOM", $sformatf(
"reported %0d mA of headroom from a %0d mA budget",
t.remaining_mA, avail))
// The two verdicts stay independent.
if (t.tree_over_budget != (charged > avail))
`uvm_error("BUDGET/TREE", "whole-hub verdict disagrees with the sum")
endfunction
endclass
covergroup budget_cg with function sample(
int unsigned charged, int unsigned avail, bit port_over, bit tree_over,
int unsigned n_unconfigured);
// The relationship between what is charged and what is available. The
// `exactly` bin is the one that distinguishes an inclusive bound.
cp_fit : coverpoint (charged == avail ? 0 : charged < avail ? 1 : 2) {
bins exactly = {0};
bins fits = {1};
bins over = {2};
}
// Section 3's independence, as a cross. All FOUR cells must be reached:
// a run that never hits port-over-without-tree-over has not shown the
// two conditions are separate, it has only assumed it.
x_independence : cross
coverpoint port_over { bins no = {0}; bins yes = {1}; },
coverpoint tree_over { bins no = {0}; bins yes = {1}; };
// Section 2's term: trees containing unconfigured devices.
cp_unconf : coverpoint n_unconfigured { bins none = {0}; bins some = {[1:4]}; }
endgroup13. Assertions
// THE property: never report headroom the hub does not have.
property p_no_phantom_headroom;
@(posedge clk) disable iff (!rst_n)
remaining_mA <= ((bus_mA > contr_current)
? (bus_mA - contr_current) : 16'd0);
endproperty
a_no_phantom_headroom : assert property (p_no_phantom_headroom)
else $error("the budget reported headroom the hub cannot source");
// An over-budget tree reports no headroom at all. Mutation T3.
property p_over_means_zero;
@(posedge clk) disable iff (!rst_n)
tree_over_budget |-> (remaining_mA == '0);
endproperty
a_over_means_zero : assert property (p_over_means_zero);
// An empty port is never charged and never flagged.
property p_absent_costs_nothing;
@(posedge clk) disable iff (!rst_n)
(port_over_budget & ~present) == '0;
endproperty
a_absent_costs_nothing : assert property (p_absent_costs_nothing);
// The two verdicts are independent: neither implies the other. Stated as
// the absence of an implication, which is what mutation T2 introduces.
property p_verdicts_independent;
@(posedge clk) disable iff (!rst_n)
(budget_verdict == BUDGET_PORT_OVER) |-> !tree_over_budget;
endproperty
a_verdicts_independent : assert property (p_verdicts_independent);p_no_phantom_headroom is the one to prove formally — a pure combinational inequality over the whole input space, which a prover settles for every tree shape and every budget at once.
These were written but not simulated; Icarus supports no concurrent assertions.
14. Debugging: the Dock That Works Until Everything Is Plugged In
The report: a dock works with any two devices attached. With four, one of them — not always the same one — fails to start. Unplugging any other device fixes the failing one.
The procedure:
1. Note that the failure moves. A defective device or port fails consistently. A failure that moves between devices depending on what else is attached is a shared-resource problem, and on USB the shared resource with a hard limit is current.
2. Get the two warnings apart. If the log says "NNNmA is over NNNmA budget!" the problem is one device wanting more than a port offers — move it. If it says "NNNmA over power budget!" the tree is over — remove one. §3 is why those are different messages.
3. Count the unconfigured devices. During enumeration, several devices may be present and none configured. Each is charged a full unit load (§2), so the budget is at its tightest exactly when the most devices are being brought up — which is why the failure clusters at plug-in time and not during steady use.
4. Check whether the dock is bus-powered. If so, 18.5 §1 has already decided the outcome: every port is offered one unit load, and any device wanting more is refused regardless of budget.
5. Explain "unplugging anything fixes it." Removing a device returns its charge — its bMaxPower if configured, a unit load if not — and the tree comes back under. The device that then succeeds is whichever is retried first, which is why it is not always the same one.
15. Common Misconceptions
"An unconfigured device draws nothing, so it costs nothing." It is entitled to a unit load from the moment it attaches, so the host reserves one (§2). Mutation T1, 41 115 errors.
"The host reads the remaining budget from the hub." It computes it by walking the tree (§1). No register holds it.
"Over budget is over budget." Per-port and whole-hub are different conditions with different fixes (§3). Mutation T2, 11 015 errors.
"A device failing on a hub means the hub is over budget." It may be the per-port offer, with the hub nearly empty (§3) — that is the 500 mA device on a 100 mA-per-port hub.
"A negative budget can just be clamped later." An unsigned subtraction that wraps reports enormous headroom at the moment it has none (§4), and the host then admits the next device too. Mutations T3 and T4.
"bMaxPower is what the device draws." It is what the device declared, in 2 mA units (19.1 §2) — and the host budgets the declaration, not the measurement.
"Sweeping 4096 trees covers the design." One input was tied low and a mutation hid behind it (§10).
16. Exercises
1. §10 found an input the sweep held constant. Audit every input of every design in Module 19 and list any the corresponding sweep does not vary.
2. A hub has 400 mA available and four ports. Compute the largest number of unconfigured devices it can hold, and the largest number of devices configured at 100 mA each, and explain why the answers differ.
3. Write the SVA property that catches T7 — a configured device charged a unit load — without referring to unit_load_mA.
4. §12's x_independence needs all four cells. Construct the minimal set of trees that reaches them, given the four per-port states this chapter uses.
5. Chapter 18.5 computes what a hub may supply and this chapter computes what the host charges against it. Determine whether the two can disagree for the same tree, and which is authoritative if so.
6. The OTG exception charges 8 mA rather than a unit load, and only on a root hub's OTG port. Determine what breaks if it is applied one tier down, and which of §11's mutations that resembles.
17. Summary
The host computes the budget by walking the tree (§1) — there is no register holding it — starting from what the hub may draw, subtracting its own controller, then every attached device in turn.
A present-but-unconfigured device costs a full unit load (§2). It is entitled to one from the moment it attaches, so the host reserves it; a budget that charged nothing would be describing a tree that could go over the instant those devices used what they are owed. Mutation T1, 41 115 errors, and its mirror T7 at 57 400.
Two over-budget conditions, two different fixes (§3): a per-port violation means move that device, a whole-hub violation means remove one. They are independent — either, neither or both — and the sweep reaches both alone thousands of times, which is what makes T2's 11 015 a kill rather than an equivalence.
Both subtractions saturate (§4). An unsigned budget that wrapped would report enormous headroom at the exact moment it had none, and the host would then admit the next device too.
All three HDL implementations were simulated (§18) and seven mutations died in all three (§11), with the walk verified exhaustively over all 8192 trees — every per-port state combination, every budget, both speeds, and the OTG flag.
And that last dimension was tied low in the first version of the sweep (§10). Mutation T5 died 3142 times, entirely from the randomised phase, with the 4096-point exhaustive sweep contributing nothing. Adding it took the sweep to 8192 and T5 to 7724.
That is the third sweep in two modules to be exhaustive over a smaller domain than it appeared — after 18.5 §12's fixed supply and 19.1 §11's fixed port offers. Each time the culprit was an input that looked like configuration rather than stimulus, and the check is mechanical: for every input, ask whether the sweep varies it, and if not, ask why not.
18. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb_tree_power_walk | pw_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 8192/8192 | ✅ all seven |
| SystemVerilog | usb_tree_power_walk_sv | pw_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 8192/8192 | ✅ all seven |
| VHDL-2008 | usb_tree_power_walk_vhdl | pw_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 8192/8192 | ✅ all seven |
| UVM (§12) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§13) | — | — | ❌ unsupported by Icarus | ❌ | — |
Icarus rejected a part-select inside always_comb — the 32-bit accumulator narrowed to 16 — as it did in 18.5. The published SystemVerilog narrows it with a continuous assignment instead.
The VHDL testbench needed two renames the other two did not: bus is a reserved word, and VHDL's case-insensitivity made a constant MP collide with a signal mp (§8).
VHDL's randomised tail differs (628 exact-budget hits against 606) because the three benches draw from different generators. The 8192 exhaustive trees are identical by construction, and every mutation count agrees to within 4 %.
19. Module 19 Complete
Six chapters, and USB's power model is built end to end.
| Chapter | What it settled | |
|---|---|---|
| 19.1 | Bus Power | one unit load until SetConfiguration, then bMaxPower × 2 |
| 19.2 | Self Power | a self-powered device must still watch VBUS, or it back-powers the host |
| 19.3 | Suspend | 3 ms of continuous idle, measured against a heartbeat that exists for the purpose |
| 19.4 | Resume | the spec says 20 ms; Linux drives 40, and explains why |
| 19.5 | Remote Wakeup | the one exception to single-master, behind two fences |
| 19.6 | Power Budgeting | the host's whole-tree walk, and the term for devices that are owed but not using |
The chapters interlock rather than merely follow. 19.1's one-unit-load floor reappears as 19.6's charge for unconfigured devices. 19.2's dynamic self-powered bit is what 19.6's arithmetic depends on. 19.3's suspend current is five times higher if 19.5 armed the device. 19.4's resume is what 19.5's wakeup provokes. And 18.5's per-hub budget is the input to 19.6's per-tree one.
Verification-wise, the module was about domains. Exhaustive over an input space (19.1's 5632 scenarios, 19.2's 8192 points, 19.6's 8192 trees), over a temporal space (19.3's 4096 activity patterns), and over an interleaving space (19.4's 65 536 start-and-abort pairs, 19.5's 4096 wake-and-suspend pairs). Different bugs live in different domains, and no amount of enumeration in one reaches another.
Three sweeps turned out to be exhaustive over less than they claimed — a fixed supply, fixed port offers, and an input tied low — and each was found the same way: a mutation dying by a suspiciously small number.
Two designs had gaps found by a safety property, both in 19.5, and the second only became visible after the first was fixed.
And two chapters had a testbench measuring the benches rather than the designs (19.2, 19.3), because one language's design exposed less than the other two. By 19.5 and 19.6 the observable was added to all three at once — the lesson applied rather than repeated.
20. What Comes Next
Everything in Modules 18 and 19 has been USB 2.0: one differential pair, a host that polls, a bus that is idle only in the sense that nothing is being said on it.
Module 20 — USB 3.x Architecture is not an increment on that. It is an architectural reset: a second, entirely separate set of wires in the same cable, a full-duplex link that trains itself before carrying anything, credit-based flow control in place of polling, and asynchronous notifications that would have been unthinkable under Chapter 2.6's single-master rule.
The two buses coexist physically and barely interact logically, which is the first thing that module has to explain — and why a USB 3 cable carries the USB 2 pair at all.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- 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
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.
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.
