Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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:

ConditionMeansFix
per portdelta > mA_per_portthis one device wants more than a port here offersmove it to a hub that offers more
whole hubremaining < 0the devices together exceed the hubremove 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

A block diagram of the host's power budget walk, arranged in four rows. At the top is what this hub may draw from its parent, which the hub power budgeting of chapter eighteen point five established. In the second row on the left, the hub's own controller current, declared in the descriptor field bHubContrCurrent, is subtracted from that using a saturating subtraction so a malformed descriptor yields zero rather than wrapping. On the right of the second row is the per-port charge decision, which classifies each attached device into one of four cases: an absent port costs nothing; a configured device costs its declared bMaxPower scaled by two milliamps, or eight on SuperSpeed; an unconfigured device on a root hub's OTG port costs eight milliamps; and, the case that matters most, any other present but unconfigured device costs a full unit load, because it is entitled to one from the moment it attached whether it is drawing it or not. In the third row those per-port charges are summed into a total charged, using an accumulator wider than the result so the sum cannot wrap. The bottom row holds the two independent verdicts. On the left, a whole-hub verdict fires when the devices together exceed what the hub can source, which means one should be removed. On the right, a per-port verdict fires when any single device wants more than a port on this hub is offered, which means that device should be moved elsewhere. Between them sits the remaining headroom, which saturates at zero rather than going negative, because a budget reporting headroom it does not have would cause the host to admit the next device too.What this hub may drawfrom its parent (18.5)− bHubContrCurrentsaturating: never wrapsPer-port chargeabsent 0 · configured bMaxPower ·else ONE UNIT LOADTotal chargedsummed at 32 bits, narrowed onceWhole-hub verdicttogether they exceed it → remove oneRemainingsaturates at zero, never negativePer-port verdictone device wants too much → move itbudgetdeltasavailableper portsubtractcompare12
Figure 1 — the host's per-hub accounting. The shaded path is §2: a present-but-unconfigured device contributes a full unit load, which is the term that makes the arithmetic conservative rather than optimistic. The two verdicts at the bottom are independent — either, neither, or both.

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

charged 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

budget_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // 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) begin

Four properties are checked against no model, and the first is the one with consequences:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // ---- 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 — PASS

with-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

MutationVerilogSystemVerilogVHDL
T1an unconfigured device costs nothing411154111540614
T2the two over-budget conditions conflated110151101510864
T3remaining wraps instead of saturating534385343853408
T4the available subtraction wraps730073007138
T5the OTG exception applied to every port772477247455
T6the per-port test becomes inclusive101171011710077
T7a configured device is charged a unit load574005740057548

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]}; }
endgroup

13. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_tree_power_walkpw_v_tb.v✅ Icarus -g2005✅ 0 errors, 8192/8192✅ all seven
SystemVerilogusb_tree_power_walk_svpw_sv_tb.sv✅ Icarus -g2012✅ 0 errors, 8192/8192✅ all seven
VHDL-2008usb_tree_power_walk_vhdlpw_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.

ChapterWhat it settled
19.1Bus Powerone unit load until SetConfiguration, then bMaxPower × 2
19.2Self Powera self-powered device must still watch VBUS, or it back-powers the host
19.3Suspend3 ms of continuous idle, measured against a heartbeat that exists for the purpose
19.4Resumethe spec says 20 ms; Linux drives 40, and explains why
19.5Remote Wakeupthe one exception to single-master, behind two fences
19.6Power Budgetingthe 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

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.