Skip to content
VLSI Mentor

USB · Module 18

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.

Chapter 18.2 gave every port a SetPortFeature(PORT_POWER) command and an over-current input, and treated both as inputs to a state machine.

They are not inputs. They are a resource with a budget, and the budget does not balance.

1. The Arithmetic That Does Not Work

Write it out:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   a bus-powered hub may draw            500 mA   from its upstream port
   it must run its own controller from   that same 500 mA
   it must then supply                     4 ports
   at a full load each that would need  2000 mA

A bus-powered hub is given 500 mA and asked to supply what could be 2000 mA. There is no clever allocation that fixes this; the numbers are simply not there.

So the specification changes what a port is offered. A bus-powered hub does not offer a full load. It offers one unit load — 100 mA — to each port, and a device that needs more is refused.

Linux writes exactly this, with the specification reference attached to the line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
   hub->mA_per_port = unit_load;      /* 7.2.1 */

2. Unit Loads and Full Loads

The numbers are fixed by the specification, not derived from what happens to be spare:

unit loadfull load
USB 2.0100 mA500 mA (five unit loads)
SuperSpeed150 mA900 mA (six unit loads)

A device declares what it needs in its configuration descriptor and the host decides whether the port can supply it. A device asking for more than the port offers is not configured — it enumerates, it is visible, and it does not work.

Nothing is negotiated. The hub does not offer 300 mA because it happens to have 300 mA spare; it offers a unit load or a full load, and the choice between those two is made by how the hub itself is powered.

3. What the Hub Subtracts First

A bus-powered hub's own controller has to run on something, and that something is the same 500 mA.

bHubContrCurrent is the hub's declaration of its own consumption, and it comes out of the budget before any port sees a milliamp:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   available = upstream_mA − bHubContrCurrent

The subtraction must saturate. A hub whose descriptor claims a controller current larger than its upstream allowance is malformed — and the wrapped answer is the single worst response available: a 16-bit wrap turns a deficit into an enormous budget, and the hub would grant every port and brown out the whole tree.

Mutation P2 removes the saturation and dies 4076 times. It is not a subtle bug: it converts "I have nothing" into "I have 65 000 mA".

4. Insufficient Is a Warning, Not a Refusal

When the remaining budget cannot cover every port at a unit load each, Linux logs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   "insufficient power available to use all downstream ports"

Note what it does not do: refuse to work. The hub enumerates, the ports function, and devices attach — right up until the budget runs out, at which point the next port gets nothing.

So insufficient is a status output, not a gate. It says this hub cannot power every port simultaneously, which is useful to a user deciding where to plug something in, and is not a reason to disable the hub.

5. Three Refusals That Are Not the Same Refusal

A port can be refused power for three entirely independent reasons, and conflating them is the commonest bug in this block:

ReasonConditionWhat it means
over-currentthe port is drawing too muchit gets nothing, whatever it asked for
per-port offerrequest > mA_per_port§1's unit-load rule — the budget may be untouched
budgetrequest > what is leftthe hub has run out, though the request was legal

The second and third come apart constantly. A 500 mA device on a bus-powered hub with a completely empty budget is still refused — not because the hub is short of power, but because no port on a bus-powered hub is ever offered more than one unit load.

And the first outranks both. An over-current port gets nothing even if it asks for 0 mA and the budget is full, because over-current is a fault condition and not a request. Mutation P4 removes that precedence and dies 115 365 times — the largest count in Module 18.

6. The Budget, Drawn

A block diagram of a USB hub's power budget, arranged in four rows. At the top is the upstream port, which supplies a bus-powered hub with up to five hundred milliamps. Below it on the left is the hub controller, which draws its own current, declared in the descriptor field bHubContrCurrent. In the centre of the second row is the budget block, which computes what remains as the upstream allowance minus the controller's draw, using a saturating subtraction so that a malformed descriptor claiming more than the upstream supplies yields zero rather than wrapping to an enormous number. On the right of the second row, drawn dashed because it is present only on a self-powered hub, is the hub's own power supply, which bypasses the upstream budget entirely. The third row holds the per-port offer, which is one unit load of one hundred milliamps on a bus-powered hub and a full load of five hundred milliamps on a self-powered one; this is a fixed specification value, not a division of whatever happens to be spare. The bottom row holds three downstream ports, each of which is granted power only if it is not in over-current, its request does not exceed the per-port offer, and its request fits in what the budget has left after the ports before it.Upstream portup to 500 mA for a bus-powered hubHub controllerbHubContrCurrent — its own drawBudgetupstream − controller, saturatingOwn supplyself-powered hubs onlyPer-port offer100 mA bus-powered / 500 mAself-poweredPort 1Port 2Port 3500 mAdrawssubtractbypassesremaininggrantgrantgrant12
Figure 1 — where a hub's downstream current comes from and what is taken out of it first. The shaded path is the bus-powered case: the upstream allowance less the controller's own draw, divided into unit loads. A self-powered hub bypasses that path entirely, which is the whole architectural difference between the two.

The dashed edge is the whole difference between the two kinds of hub. A self-powered hub does not compute a smaller number; it takes a different path.

7. The Hardware, Before Any Language

mA_per_port is a constant chosen by the power source, not arithmetic. A unit load or a full load — there is nothing here to get wrong, and mutation P1 gets it wrong anyway by offering a full load unconditionally (83 784 errors).

The budget subtraction saturates (§3).

Allocation runs in port order, and the order is fixed rather than a policy. The host — not the hub — decides which devices matter, and a hub that reordered would make the host's decisions non-reproducible: the same set of devices would get different answers depending on hub-internal state the host cannot see.

The running sum is one bit wider than the budget, so it cannot wrap mid-loop.

Three independent refusal conditions, in the order of §5.

Everything is combinational except one sticky observability flag. A power budget is a question about now, and there is no state to carry: the same inputs must always give the same answer, which is what makes the 40 960-point sweep meaningful.

8. Verilog-2005

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// usb_hub_power_budget -- who gets how much current, and who is refused.
//
// Chapter 18.2 gave every port a PORT_POWER command and an over-current
// input and treated both as inputs to a state machine. They are not inputs;
// they are a resource with a budget, and the budget does not balance.
//
// The arithmetic that does not work:
//
//   a bus-powered hub may draw            500 mA from its upstream port
//   it must run its own controller from   that same 500 mA
//   it must then supply                   4 downstream ports
//   at a full load each that would need   2000 mA
//
// The resolution is that a bus-powered hub does NOT offer a full load. It
// offers ONE UNIT LOAD -- 100 mA -- to each port, and a device needing more
// is refused. Linux writes exactly this, with a specification reference
// attached to the line:
//
//     remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
//     hub->mA_per_port = unit_load;   /* 7.2.1 */
//
// A SELF-powered hub has its own supply and offers a full load per port.
// This is the entire practical difference between the two kinds of hub, and
// it is why a bus-powered hub cannot run a bus-powered hard drive.
module usb_hub_power_budget #(
  parameter integer NPORTS     = 4,
  parameter integer UNIT_LOAD  = 100,   // one unit load, mA (USB 2.0)
  parameter integer FULL_LOAD  = 500,   // five unit loads, mA
  parameter integer SELF_SUPPLY = 2500  // what a self-powered hub can source
) (
  input  wire                  clk,
  input  wire                  rst_n,

  input  wire                  self_powered,
  input  wire [15:0]           upstream_mA,     // what the hub may draw
  input  wire [15:0]           contr_current,   // bHubContrCurrent
  input  wire [NPORTS*16-1:0]  req_mA_flat,     // per-port request
  input  wire [NPORTS-1:0]     req_valid,
  input  wire [NPORTS-1:0]     overcurrent,

  output wire [15:0]           mA_per_port,     // what a port is OFFERED
  output wire [15:0]           available,       // what the hub can source
  output wire                  limited_power,   // ports get less than a full load
  output reg  [NPORTS-1:0]     grant,
  output reg  [15:0]           total_granted,
  output wire                  insufficient,    // cannot supply every port
  output reg                   ever_insufficient
);
  // A self-powered hub offers a FULL load; a bus-powered hub offers ONE
  // unit load. There is no arithmetic here to get wrong -- the number is
  // fixed by the specification, not derived from what happens to be spare.
  assign mA_per_port   = self_powered ? FULL_LOAD[15:0] : UNIT_LOAD[15:0];
  assign limited_power = !self_powered;

  // SATURATING subtraction. A hub whose controller is declared to draw more
  // than its upstream port supplies is a malformed descriptor, and the
  // wrapped answer -- an enormous budget -- is the single worst response
  // available: it would grant every port and brown out the whole tree.
  wire [15:0] bus_avail = (upstream_mA > contr_current)
                        ? (upstream_mA - contr_current) : 16'd0;
  assign available = self_powered ? SELF_SUPPLY[15:0] : bus_avail;

  // Linux warns "insufficient power available to use all downstream ports"
  // on exactly this condition. It is a WARNING, not a refusal: the hub
  // still works, it simply cannot power every port at once.
  assign insufficient = (available < (NPORTS * mA_per_port));

  // Allocation, in port order. A fixed order rather than a policy, because
  // the host -- not the hub -- decides which devices matter, and a hub that
  // reordered would make the host's decisions non-reproducible.
  integer i;
  reg [15:0] req_i;
  reg [16:0] running;          // 17 bits: the sum must not wrap mid-loop
  always @* begin
    grant   = {NPORTS{1'b0}};
    running = 17'd0;
    for (i = 0; i < NPORTS; i = i + 1) begin
      req_i = req_mA_flat[i*16 +: 16];
      // Three independent reasons to refuse, and they are not the same:
      //   * the port is in over-current -- it gets nothing, whatever it asked
      //   * the request exceeds what a port is OFFERED (the unit-load rule)
      //   * the request exceeds what the hub has LEFT (the budget)
      if (req_valid[i] && !overcurrent[i]
          && (req_i <= mA_per_port)
          && ((running + {1'b0, req_i}) <= {1'b0, available})) begin
        grant[i] = 1'b1;
        running  = running + {1'b0, req_i};
      end
    end
    total_granted = running[15:0];
  end

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)           ever_insufficient <= 1'b0;
    else if (insufficient) ever_insufficient <= 1'b1;
  end
endmodule

running is 17 bits against a 16-bit budget. Four ports at a full load each is 2000 mA, which fits 16 bits comfortably — so the extra bit is not needed for this parameterisation. It is needed for the general one, and a width that is correct only for the default generic is a latent bug with a configuration attached to it.

9. SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package usb_power_pkg;
  // Where a hub's downstream current comes from. This is not a status bit:
  // it decides what every port is OFFERED, and those two answers differ by
  // a factor of five.
  typedef enum logic {
    PWR_BUS,    // drawn from the upstream port -- one unit load per port
    PWR_SELF    // the hub has its own supply   -- a full load per port
  } power_src_e;
endpackage

// usb_hub_power_budget_sv -- who gets how much current, and who is refused.
//
// Chapter 18.2 gave every port a PORT_POWER command and an over-current
// input and treated both as inputs to a state machine. They are not inputs;
// they are a resource with a budget, and the budget does not balance.
//
// The arithmetic that does not work:
//
//   a bus-powered hub may draw            500 mA from its upstream port
//   it must run its own controller from   that same 500 mA
//   it must then supply                   4 downstream ports
//   at a full load each that would need   2000 mA
//
// The resolution is that a bus-powered hub does NOT offer a full load. It
// offers ONE UNIT LOAD -- 100 mA -- to each port, and a device needing more
// is refused. Linux writes exactly this, with a specification reference
// attached to the line:
//
//     remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
//     hub->mA_per_port = unit_load;   /* 7.2.1 */
module usb_hub_power_budget_sv
  import usb_power_pkg::*;
#(
  parameter int unsigned NPORTS      = 4,
  parameter int unsigned UNIT_LOAD   = 100,
  parameter int unsigned FULL_LOAD   = 500,
  parameter int unsigned SELF_SUPPLY = 2500
) (
  input  logic                 clk,
  input  logic                 rst_n,

  input  power_src_e           power_src,
  input  logic [15:0]          upstream_mA,
  input  logic [15:0]          contr_current,
  input  logic [NPORTS*16-1:0] req_mA_flat,
  input  logic [NPORTS-1:0]    req_valid,
  input  logic [NPORTS-1:0]    overcurrent,

  output logic [15:0]          mA_per_port,
  output logic [15:0]          available,
  output logic                 limited_power,
  output logic [NPORTS-1:0]    grant,
  output logic [15:0]          total_granted,
  output logic                 insufficient,
  output logic                 ever_insufficient
);
  initial begin
    if (UNIT_LOAD > FULL_LOAD)
      $fatal(1, "UNIT_LOAD=%0d exceeds FULL_LOAD=%0d: a unit load is a fraction of a full one",
             UNIT_LOAD, FULL_LOAD);
    if (NPORTS < 1)
      $fatal(1, "NPORTS=%0d: a hub with no downstream ports is not a hub",
             NPORTS);
  end

  wire self_powered = (power_src == PWR_SELF);

  // A self-powered hub offers a FULL load; a bus-powered hub offers ONE
  // unit load. There is no arithmetic here to get wrong -- the number is
  // fixed by the specification, not derived from what happens to be spare.
  assign mA_per_port   = self_powered ? 16'(FULL_LOAD) : 16'(UNIT_LOAD);
  assign limited_power = !self_powered;

  // SATURATING subtraction. A hub whose controller is declared to draw more
  // than its upstream port supplies is a malformed descriptor, and the
  // wrapped answer -- an enormous budget -- is the single worst response
  // available: it would grant every port and brown out the whole tree.
  wire [15:0] bus_avail = (upstream_mA > contr_current)
                        ? (upstream_mA - contr_current) : 16'd0;
  assign available = self_powered ? 16'(SELF_SUPPLY) : bus_avail;

  // Linux warns "insufficient power available to use all downstream ports"
  // on exactly this condition. It is a WARNING, not a refusal: the hub
  // still works, it simply cannot power every port at once.
  assign insufficient = (available < 16'(NPORTS * mA_per_port));

  // Declared at module scope rather than inside the process: `running` is
  // 17 bits so the running sum cannot wrap mid-loop, and its low half is
  // the reported total.
  logic [15:0] req_i;
  logic [16:0] running;
  // Allocation, in port order. A fixed order rather than a policy, because
  // the host -- not the hub -- decides which devices matter, and a hub that
  // reordered would make the host's decisions non-reproducible.
  always_comb begin
    grant   = '0;
    running = '0;
    for (int i = 0; i < int'(NPORTS); i++) begin
      req_i = req_mA_flat[i*16 +: 16];
      // Three independent reasons to refuse, and they are not the same:
      //   * the port is in over-current -- it gets nothing, whatever it asked
      //   * the request exceeds what a port is OFFERED (the unit-load rule)
      //   * the request exceeds what the hub has LEFT (the budget)
      if (req_valid[i] && !overcurrent[i]
          && (req_i <= mA_per_port)
          && ((running + 17'(req_i)) <= 17'(available))) begin
        grant[i] = 1'b1;
        running  = running + 17'(req_i);
      end
    end
  end
  // Continuous, so the 17-bit running sum is narrowed in one place and the
  // process has no part-select in it.
  assign total_granted = running[15:0];

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)            ever_insufficient <= 1'b0;
    else if (insufficient) ever_insufficient <= 1'b1;
  end
endmodule

power_src_e replaces a boolean, and the reason is §1. self_powered reads as a status bit; PWR_BUS versus PWR_SELF reads as what it is — a choice that changes every port's offer by a factor of five.

The UNIT_LOAD > FULL_LOAD guard catches a parameterisation that is nonsense rather than merely unusual: a unit load is a fraction of a full load by definition, and a build that inverts them would silently offer bus-powered ports more than self-powered ones.

10. VHDL-2008

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package usb_power_pkg is
  -- Where a hub's downstream current comes from. This is not a status bit:
  -- it decides what every port is OFFERED, and those two answers differ by
  -- a factor of five.
  type power_src_t is (
    PWR_BUS,    -- drawn from the upstream port -- one unit load per port
    PWR_SELF    -- the hub has its own supply   -- a full load per port
  );

  -- An UNCONSTRAINED array of per-port requests. The Verilog and
  -- SystemVerilog flatten this into one wide vector and slice it back out
  -- with a part-select, because neither has an array port that carries its
  -- own element width. Here the port is what it actually is.
  type ma_array_t is array (natural range <>) of unsigned(15 downto 0);
end package;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb_power_pkg.all;

-- usb_hub_power_budget_vhdl -- who gets how much current, and who is refused.
--
-- Chapter 18.2 gave every port a PORT_POWER command and an over-current
-- input and treated both as inputs to a state machine. They are not inputs;
-- they are a resource with a budget, and the budget does not balance.
--
-- The arithmetic that does not work:
--
--   a bus-powered hub may draw            500 mA from its upstream port
--   it must run its own controller from   that same 500 mA
--   it must then supply                   4 downstream ports
--   at a full load each that would need   2000 mA
--
-- The resolution is that a bus-powered hub does NOT offer a full load. It
-- offers ONE UNIT LOAD -- 100 mA -- to each port, and a device needing more
-- is refused. Linux writes exactly this, with a specification reference
-- attached to the line:
--
--     remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
--     hub->mA_per_port = unit_load;   /* 7.2.1 */
entity usb_hub_power_budget_vhdl is
  generic (
    NPORTS      : positive := 4;
    UNIT_LOAD   : positive := 100;
    FULL_LOAD   : positive := 500;
    SELF_SUPPLY : positive := 2500
  );
  port (
    clk               : in  std_logic;
    rst_n             : in  std_logic;

    power_src         : in  power_src_t;
    upstream_mA       : in  unsigned(15 downto 0);
    contr_current     : in  unsigned(15 downto 0);
    req_mA            : in  ma_array_t(0 to NPORTS-1);
    req_valid         : in  std_logic_vector(NPORTS-1 downto 0);
    overcurrent       : in  std_logic_vector(NPORTS-1 downto 0);

    mA_per_port       : out unsigned(15 downto 0);
    available         : out unsigned(15 downto 0);
    limited_power     : out std_logic;
    grant             : out std_logic_vector(NPORTS-1 downto 0);
    total_granted     : out unsigned(15 downto 0);
    insufficient      : out std_logic;
    ever_insufficient : out std_logic
  );
end entity;

architecture rtl of usb_hub_power_budget_vhdl is
  signal self_powered : boolean;
  signal mpp_i        : unsigned(15 downto 0);
  signal avail_i      : unsigned(15 downto 0);
  signal insuf_i      : std_logic;
  signal ever_r       : std_logic := '0';
begin
  assert UNIT_LOAD <= FULL_LOAD
    report "a unit load is a fraction of a full load, not more than one"
    severity failure;

  self_powered <= power_src = PWR_SELF;

  -- A self-powered hub offers a FULL load; a bus-powered hub offers ONE
  -- unit load. There is no arithmetic here to get wrong -- the number is
  -- fixed by the specification, not derived from what happens to be spare.
  mpp_i <= to_unsigned(FULL_LOAD, 16) when self_powered
           else to_unsigned(UNIT_LOAD, 16);
  mA_per_port   <= mpp_i;
  limited_power <= '0' when self_powered else '1';

  -- SATURATING subtraction. A hub whose controller is declared to draw more
  -- than its upstream port supplies is a malformed descriptor, and the
  -- wrapped answer -- an enormous budget -- is the single worst response
  -- available: it would grant every port and brown out the whole tree.
  avail_i <= to_unsigned(SELF_SUPPLY, 16) when self_powered else
             (upstream_mA - contr_current) when upstream_mA > contr_current
             else to_unsigned(0, 16);
  available <= avail_i;

  -- Linux warns "insufficient power available to use all downstream ports"
  -- on exactly this condition. It is a WARNING, not a refusal: the hub
  -- still works, it simply cannot power every port at once.
  insuf_i <= '1' when avail_i < resize(mpp_i * NPORTS, 16) else '0';
  insufficient <= insuf_i;

  -- Allocation, in port order. A fixed order rather than a policy, because
  -- the host -- not the hub -- decides which devices matter, and a hub that
  -- reordered would make the host's decisions non-reproducible.
  process (req_mA, req_valid, overcurrent, mpp_i, avail_i)
    variable running : unsigned(16 downto 0);  -- 17 bits: no mid-loop wrap
    variable g       : std_logic_vector(NPORTS-1 downto 0);
  begin
    g       := (others => '0');
    running := (others => '0');
    for i in 0 to NPORTS-1 loop
      -- Three independent reasons to refuse, and they are not the same:
      --   * the port is in over-current -- it gets nothing, whatever it asked
      --   * the request exceeds what a port is OFFERED (the unit-load rule)
      --   * the request exceeds what the hub has LEFT (the budget)
      if req_valid(i) = '1' and overcurrent(i) = '0'
         and req_mA(i) <= mpp_i
         and (running + ('0' & req_mA(i))) <= ('0' & avail_i) then
        g(i)    := '1';
        running := running + ('0' & req_mA(i));
      end if;
    end loop;
    grant         <= g;
    total_granted <= running(15 downto 0);
  end process;

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      ever_r <= '0';
    elsif rising_edge(clk) then
      if insuf_i = '1' then ever_r <= '1'; end if;
    end if;
  end process;
  ever_insufficient <= ever_r;
end architecture;

ma_array_t is the clearest language win in Module 18. The Verilog and SystemVerilog flatten four 16-bit requests into a 64-bit vector and slice them back out with req_mA_flat[i*16 +: 16], because neither has a port that carries an array of a known element width. The VHDL port is what the thing actually is, and the indexing req_mA(i) cannot be off by a factor of the element width — an error the flattened form makes available and the array form does not.

11. The Testbench: 40 960 Allocations, Exhaustively

The decision has four inputs that matter: what each port requests, which ports are in over-current, how the hub is powered, and how much budget there is.

Quantising the request to the four sizes that matter — nothing, one unit load, two unit loads (a device a bus-powered hub must refuse), and a full load — gives 4⁴ = 256 request mixes. Crossed with 16 over-current masks, 2 power sources and 5 budgets: 40 960 points, the entire decision domain.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    for (sp=0; sp<2; sp=sp+1)
     for (r=0; r<256; r=r+1)
      for (oc=0; oc<16; oc=oc+1)
       for (bg=0; bg<5; bg=bg+1) begin
        apply(r[7:0], {N{1'b1}}, oc[N-1:0], sp[0], UPS[bg], CCR[bg]);
        n_exh = n_exh + 1;
        ...
       end

Five safety properties are checked at every point, and none comes from the reference model:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // ---- SAFETY PROPERTIES, independent of the model ----
      // 1. THE one that matters: never promise more than you have.
      check(total_granted <= available,
            "the hub granted more current than it can source");
      // 2. No granted port was offered more than the per-port limit.
      for (j = 0; j < N; j = j + 1)
        if (grant[j])
          check(req_flat[j*16 +: 16] <= mA_per_port,
                "a port was granted more than a port is offered");
      // 3. A port in over-current is never granted anything.
      check((grant & overcurrent) === {N{1'b0}},
            "an over-current port was granted power");
      // 4. A bus-powered hub never offers a full load.
      check(self_powered || (mA_per_port <= UL),
            "a bus-powered hub offered more than one unit load");
      // 5. Nothing is granted to a port that did not ask.
      check((grant & ~req_valid) === {N{1'b0}},
            "a port that did not request power was granted some");

Property 1 is the one that matters. Everything else in this block is policy; never promise more current than you can source is the property whose violation damages hardware.

Measured reach, identical in all three languages:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  exhaustive power-budget sweep: 40960 of 40960 points verified

  REACH: exhaustive=40960 grants-in-sweep=59008 budget-exactly-spent=2048
  [Verilog] usb_hub_power_budget: 0 errors — PASS
  [SystemVerilog] usb_hub_power_budget_sv: 0 errors — PASS
  [VHDL] usb_hub_power_budget_vhdl: 0 errors — PASS

12. The Budget That Never Bound

The first version of this sweep had 8192 points and fixed the supply at 500 mA upstream with a 100 mA controller. Every point therefore had exactly 400 mA available — and 400 mA is exactly four ports at one unit load each.

Under that supply the budget never binds. In bus-powered mode the per-port cap of 100 mA means the largest possible total is 4 × 100 = 400, which always fits. In self-powered mode the 2500 mA supply dwarfs the largest possible total of 2000. In 8192 points, the budget constraint was never the reason for a single refusal.

Two mutations noticed:

Mutationbeforeafter
P3 — the budget bound becomes exclusive3846173
P7 — each request checked against the full budget, not the remainder631122
budget-exactly-spent12048

P7 is the serious one. It checks each request against the whole budget instead of what is left, so four ports each asking for 100 mA against a 150 mA budget are all granted — 450 mA promised from a 150 mA supply. That is a violation of §11's property 1, the one property in this block that can damage hardware, and it died 63 times, every one of them in the randomised phase. The exhaustive sweep contributed nothing.

The fix was to choose budgets that align with the sums the request alphabet can produce:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // available = 0, 100, 200, 300, 400. Chosen to ALIGN with the sums the
  // request alphabet can actually produce (multiples of 100 under the
  // bus-powered per-port cap), so the inclusive bound -- running + req
  // exactly equal to the budget -- is reached constantly rather than once.

And the boundary values matter as much as the range. Budgets of 0, 100, 200, 300 and 400 are not more numerous than 150 and 250 would be; they are commensurate with the request sizes, so running + req == available happens 2048 times instead of once. A sweep that never lands exactly on a boundary cannot distinguish an inclusive bound from an exclusive one, however many points it has.

13. The Insufficient Check That Was Wrong

One directed test failed on first run, and — for the fourth time in Module 18 — the design was right.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    apply(8'b00000011, 4'b0001, 4'b0000, 1'b0, 16'd500, 16'd100);
    check(mA_per_port === 16'd100, "a bus-powered hub offers ONE unit load");
    check(!grant[0], "so a 500 mA device is refused outright");
    check(insufficient, "and the hub warns it cannot supply every port");  // WRONG

With 400 mA available and four ports at 100 mA each, the hub is not short of power at all — 400 is exactly enough (§4). The 500 mA device was refused by the per-port offer, not by the budget, and §5 is precisely about those being different refusals. The check had reached for the wrong one of the three.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // 500 upstream less 100 for the controller leaves 400, which is EXACTLY
    // four unit loads -- so this hub is not short of power at all. The
    // device is refused by the PER-PORT offer, not by the budget, and those
    // are different refusals. The first version of this check asserted
    // `insufficient` here and was simply wrong about which rule applies.
    check(!insufficient, "this hub CAN supply every port -- 400 = 4 x 100");

    // a hub that genuinely cannot: a hungrier controller
    apply(8'b01010101, 4'b1111, 4'b0000, 1'b0, 16'd500, 16'd200);
    check(available === 16'd300, "500 upstream less 200 for the controller");
    check(insufficient, "300 mA cannot supply four ports at 100 mA each");
    check(grant === 4'b0111, "so the fourth port gets nothing");

Four times in one module a check has fired against correct hardware — 18.2 §12 (an invariant missing a qualifier), 18.2 §14 (a rule missing its exception), 18.3 §10 (a rule read at the wrong instant), and here (a rule confused with its neighbour). Every one was the testbench, and every one made the testbench better than it would have been if it had passed.

14. Mutation Testing — Across All Three Languages

MutationVerilogSystemVerilogVHDL
P1a bus-powered hub offers a full load837848378483969
P2the budget subtraction wraps instead of saturating407640764048
P3the budget bound becomes exclusive617361736159
P4over-current no longer blocks a grant115365115365115599
P5the per-port offer is not enforced213842138421376
P6the insufficient warning is off by one411141114104
P7each request checked against the full budget112211221119

P4 is the largest count in Module 18 at 115 365. An over-current port is granted power on almost every point where over-current is asserted at all — which, across 16 masks, is most of the sweep.

P1 is §1 as a one-line change, and it is the mutation that describes a real, shipped product category: a bus-powered hub that advertises full-load ports it cannot supply.

P2 and P6 score almost identically (4076 and 4111) and are unrelated — one wraps the subtraction, the other moves the warning boundary by one. Close counts are not evidence of duplication; 18.4 §13's duplicate showed identical counts across three languages, which is a different signal entirely.

15. A UVM Environment for a Budget

The budget is combinational, so the value UVM adds here is constrained-random generation of request mixes that sum near the boundary — which is exactly what §12 found the directed sweep was missing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class power_request_item extends uvm_sequence_item;
  `uvm_object_utils(power_request_item)
  rand bit [15:0] req_mA [4];
  rand bit [3:0]  req_valid;
  rand bit [3:0]  overcurrent;
  rand bit [15:0] upstream_mA, contr_current;
  rand power_src_e power_src;

  // Requests are drawn from the alphabet devices actually declare, not
  // uniformly over 16 bits -- a random 16-bit request is refused by the
  // per-port rule before the budget is ever consulted.
  constraint c_realistic {
    foreach (req_mA[i]) req_mA[i] inside {0, 100, 200, 500};
    contr_current inside {[0:300]};
    upstream_mA   inside {100, 200, 300, 400, 500};
  }

  // THE constraint section 12 is about: make the total land ON the budget,
  // not merely near it. A sweep that never lands exactly on the boundary
  // cannot tell an inclusive bound from an exclusive one.
  constraint c_on_the_boundary {
    solve upstream_mA, contr_current before req_mA;
    (upstream_mA > contr_current) ->
      (req_mA.sum() with (int'(item)) == int'(upstream_mA - contr_current));
  }
endclass

The scoreboard's job is the safety property, and it is worth stating as an inequality rather than a comparison:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class power_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(power_scoreboard)

  // Its OWN recomputation of the offer and the budget (17.3).
  function void write(power_txn t);
    int unsigned m_mpp   = (t.power_src == PWR_SELF) ? FULL_LOAD : UNIT_LOAD;
    int unsigned m_avail = (t.power_src == PWR_SELF) ? SELF_SUPPLY
                         : (t.upstream_mA > t.contr_current
                            ? t.upstream_mA - t.contr_current : 0);
    int unsigned total = 0;

    foreach (t.grant[i]) if (t.grant[i]) total += t.req_mA[i];

    // THE property. Not "the grant matched" -- this one has physical
    // consequences, and it is the reason the block exists.
    if (total > m_avail)
      `uvm_error("PWR/OVER", $sformatf(
        "granted %0d mA from a %0d mA budget", total, m_avail))

    // The unit-load rule, per port.
    foreach (t.grant[i])
      if (t.grant[i] && t.req_mA[i] > m_mpp)
        `uvm_error("PWR/OFFER", $sformatf(
          "port %0d granted %0d mA where a port is offered %0d",
          i, t.req_mA[i], m_mpp))

    // Over-current outranks everything.
    foreach (t.grant[i])
      if (t.grant[i] && t.overcurrent[i])
        `uvm_error("PWR/OC", $sformatf(
          "port %0d granted power while in over-current", i))
  endfunction
endclass

covergroup power_cg with function sample(
    power_src_e src, int unsigned avail, int unsigned total, bit oc_any);
  cp_src   : coverpoint src;
  cp_oc    : coverpoint oc_any { bins fault = {1}; }
  // The bin section 12's sweep did not have: the budget SPENT EXACTLY.
  // With the original fixed supply this bin would have read 1.
  cp_exact : coverpoint (total == avail && avail != 0) { bins spent = {1}; }
  // And the budget actually binding, rather than the per-port rule.
  cp_bound : coverpoint (total == avail) iff (avail != 0);
  x_src_exact : cross cp_src, cp_exact;
endgroup

16. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // THE property: never promise more current than you can source.
  property p_never_over_budget;
    @(posedge clk) disable iff (!rst_n)
      total_granted <= available;
  endproperty
  a_never_over_budget : assert property (p_never_over_budget)
    else $error("the hub granted more current than it can source");

  // A bus-powered hub never offers more than one unit load. Section 1.
  property p_unit_load_when_bus_powered;
    @(posedge clk) disable iff (!rst_n)
      (power_src == PWR_BUS) |-> (mA_per_port == UNIT_LOAD);
  endproperty
  a_unit_load : assert property (p_unit_load_when_bus_powered);

  // Over-current outranks every other consideration. Mutation P4.
  property p_overcurrent_blocks;
    @(posedge clk) disable iff (!rst_n)
      (grant & overcurrent) == '0;
  endproperty
  a_overcurrent_blocks : assert property (p_overcurrent_blocks);

  // The budget saturates rather than wrapping. Mutation P2, stated as the
  // consequence rather than the mechanism: a deficit yields nothing.
  property p_saturating_budget;
    @(posedge clk) disable iff (!rst_n)
      (power_src == PWR_BUS && contr_current >= upstream_mA)
        |-> (available == '0);
  endproperty
  a_saturating_budget : assert property (p_saturating_budget);

p_never_over_budget is the property to prove formally rather than simulate. It is a pure combinational relation over the whole input space, and a prover settles it for every request mix and every budget — including the parameterisations no testbench instantiated.

These were written but not simulated; Icarus supports no concurrent assertions.

17. Debugging: the Drive That Spins Up and Stops

The report: a bus-powered portable hard drive works on a laptop port. On a hub it spins up, clicks, and disconnects — repeatedly. A different, powered hub works fine.

The procedure:

1. Establish which kind of hub it is. limited_power — or, from software, whether the hub is bus-powered. If it is bus-powered, §1 has already answered the question: no port on it is offered more than one unit load, and a drive needs five.

2. Check whether the device was ever configured. A device refused power at configuration time is enumerated but not configured. It appears in the device list, which is why users report "it's detected but doesn't work" rather than "nothing happens."

3. Distinguish the three refusals of §5. If the hub is self-powered and the drive is still refused, read available and total_granted: a self-powered hub with other devices already drawing can run out of budget while still offering a full load per port. That is a different fix — unplug something — than the bus-powered case, which no rearrangement solves.

4. Explain the spin-up-then-stop. The drive's inrush at spin-up exceeds what the port supplies, the port trips over-current, 18.2's FSM takes it to Powered-off, and the device disappears. On the next power-on it tries again. The click is the drive's head parking as its supply collapses.

5. Confirm from the change bits. An over-current change on that port (18.2 §5) distinguishes this from a plain configuration refusal — the first means the device drew too much, the second means it asked for too much and never got the chance.

18. Common Misconceptions

"A hub splits its 500 mA between its ports." It does not divide anything. It offers a fixed unit load per port and refuses what does not fit (§1, §2).

"A bus-powered hub with nothing else plugged in can supply 500 mA to one port." No. The per-port offer is one unit load regardless of how empty the budget is (§5) — the two refusals are independent.

"insufficient means the hub is broken." It is a warning that the hub cannot supply every port at once (§4). It still works.

"400 mA available for four 100 mA ports is insufficient." It is exactly enough — the bound is inclusive (§4), and asserting otherwise failed against correct hardware (§13).

"Over-current is just another reason a request does not fit." It outranks the request entirely: an over-current port gets nothing even asking for 0 mA from a full budget (§5). Mutation P4, 115 365 errors.

"The subtraction can't underflow — descriptors are valid." Mutation P2, 4076 errors. A malformed descriptor turns a deficit into 65 000 mA of imaginary budget.

"Checking each request against the budget is the same as tracking the remainder." Mutation P7: four ports at 100 mA each all granted from a 150 mA supply. The design's most important safety property, violated (§12).

"All 8192 points passed, so the allocator is verified." The supply was constant, so the budget never bound once (§12). Exhaustive over the dimensions you sweep; blind to the ones you fixed.

19. Exercises

1. §12 fixed the supply and made one refusal condition unreachable. Identify every other input this chapter's sweep holds constant, and determine which of §5's conditions each one affects.

2. A self-powered hub has SELF_SUPPLY = 2500 and four ports. Compute the largest total the request alphabet can produce and determine whether the budget can ever bind. Then choose a SELF_SUPPLY that makes it bind, and say what that does to mutation P7's count.

3. Write the SVA property that catches mutation P7 — per-request rather than cumulative budgeting — using only grant, req_mA and available.

4. A SuperSpeed hub uses a 150 mA unit load and a 900 mA full load. Recompute §1's arithmetic and determine whether the imbalance is better or worse than USB 2.0's.

5. §14 notes that P2 and P6 score almost identically and are unrelated. Define a test that distinguishes "two mutations that happen to score alike" from "two mutations that are the same", and state what 18.4 §13's case would have produced under it.

6. The allocator grants in port order. Determine what changes if it granted smallest-request-first, which of §11's five safety properties would still hold, and why the specification does not require it.

20. Summary

The arithmetic does not balance (§1). A bus-powered hub draws 500 mA, runs its own controller from it, and must supply four ports that could each want 500 mA. The resolution is that its ports are offered one unit load — 100 mA — and a device needing more is refused.

That is the entire practical difference between a bus-powered and a self-powered hub (§2, §6), and it is why a bus-powered hub cannot run a bus-powered drive. The refusal is specification-conformant, not a defect.

bHubContrCurrent comes out first (§3), with a saturating subtraction — a wrap turns a deficit into 65 000 mA of imaginary budget (P2, 4076 errors).

insufficient is a warning, not a gate (§4), and its bound is inclusive: 400 mA for four 100 mA ports is exactly enough.

Three refusals are not one refusal (§5). Over-current outranks everything (P4, 115 365 errors — the largest in Module 18); the per-port offer and the budget are independent, and a 500 mA device is refused on a bus-powered hub with a completely empty budget.

All three HDL implementations were simulated (§21) and seven mutations died in all three (§14), with the allocation decision verified exhaustively over all 40 960 points (§11).

And the first sweep's budget never bound once (§12). 8192 points, a constant 500 mA supply, and available permanently equal to exactly four unit loads — so the most dangerous mutation in the chapter, granting 450 mA from a 150 mA supply, died 63 times, every one of them by luck in the randomised phase. Aligning five budgets with the request alphabet took P7 to 1122, P3 to 6173, and exact-boundary hits from 1 to 2048.

An exhaustive sweep is exhaustive over the dimensions you sweep and blind to the ones you fixed — and a sweep that never lands exactly on a boundary cannot distinguish an inclusive bound from an exclusive one, however many points it has.

A fourth testbench check fired against correct hardware (§13), this time confusing the budget refusal with the per-port one. Four for four in this module, and every one improved the bench.

21. Tooling, Honestly

LanguageDesignTestbenchAnalysed / compiledSimulatedMutations
Verilog-2005usb_hub_power_budgetpb_v_tb.v✅ Icarus -g2005✅ 0 errors, 40960/40960✅ all seven
SystemVerilogusb_hub_power_budget_svpb_sv_tb.sv✅ Icarus -g2012✅ 0 errors, 40960/40960✅ all seven
VHDL-2008usb_hub_power_budget_vhdlpb_vhdl_tb.vhd✅ nvc 1.23.0✅ 0 errors, 40960/40960✅ all seven
UVM (§15)——❌ no UVM-capable simulator here❌—
SVA (§16)——❌ unsupported by Icarus❌—

Icarus refused an enum-typed port driven by a conditional expression, so the testbench drives power_src from a signal assigned with an explicit power_src_e'(...) cast. That is the third distinct enum restriction this module hit in the same tool — after the enum-valued ternary in 18.2 and 18.3. The published code uses the forms that compile everywhere.

All three languages report identical reach (40 960 / 59 008 / 2048) because the exhaustive sweep is fully deterministic; only the randomised tail differs, and it is checked rather than counted.

22. Module 18 Complete

Five chapters, and the hub is built.

ChapterWhat it added
18.1Hub Architecturethe repeater: broadcast down, select one up
18.2Port Managementthe per-port FSM that drives port_enabled
18.3Downstream Device Discoverythe status-change endpoint and its poll race
18.4Cascading Hubsthe depth limit, and what it really encodes
18.5Hub Power Managementthe budget, and the arithmetic that does not balance

The chapters are connected rather than merely sequential. 18.1 left port_enabled undefined and 18.2 drove it. 18.2 produced change bits the host could not read and 18.3 reported them. 18.3's bitmap width is why 18.1 refuses to elaborate above 31 ports. 18.2's over-current input is 18.5's fault condition, and 18.1's babble isolation is 18.2's highest-priority transition.

Verification-wise, the module established one idea across five blocks: exhaustive where the domain is small — 512, 784, 32 768, 8192 and 40 960 points — and then, repeatedly, that exhaustive is a claim about a domain and not about importance. Three chapters found a critical mutation dying on a margin of single digits inside a sweep that had passed completely.

And four testbench checks accused correct hardware. A missing qualifier, a missing exception, a wrong instant, and a confused neighbour. None of them was a design bug, and all four left the testbench stronger — which is the honest case for writing checks that are specific enough to be wrong.

23. What Comes Next

Every port in this module could be powered, and nothing has yet asked what happens when the bus goes quiet.

Module 19 — USB Power Management takes that up: bus power and self power as device-level choices rather than hub-level ones, suspend and the 3 ms of idle that triggers it, resume and who is allowed to initiate it, remote wakeup — the one mechanism in USB by which a device may ask for the host's attention without being asked first — and the power budgeting that spans a whole tree rather than one hub.

That last one is the interesting inversion. Chapter 2.6 established that only the host initiates, and every chapter since has been built on it. Remote wakeup is the exception, and what it costs to have an exception to the single-master rule — in hardware, in latency, and in the number of things that can go wrong — is where Module 19 earns its place.

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.