Skip to content
VLSI Mentor

USB · Module 27

Senior Architecture Tradeoffs

USB, PCIe or Thunderbolt — the deciding axis is not bandwidth but whether the peer can read host memory, and when several buses fit, the honest answer is that the choice is not technical.

The last of the four senior questions, and the end of this track's interview tier.

1. The Question

"You are architecting an SoC. For this role, would you use USB, PCIe or Thunderbolt? Defend it."

Almost everybody answers with a table of bandwidths. The table is correct, it is available on any datasheet, and it does not answer the question — because the three buses are not ordered. There is no "better"; there are three different sets of things you are allowed to stop worrying about.

2. What Actually Separates Them

Not bandwidth. Bandwidth is the axis everybody reaches for and the one that decides fewest real cases, because all three have enough for most roles and the one that does not is usually obvious.

The axis that decides is whether the peer can read host memory.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   USB          a device CANNOT touch host memory. It has no
                bus-mastering capability at all. Every byte moves
                because the host asked for it.

   PCIe         a device IS a bus master with a view of host
                memory. That is the point of PCIe and it is why
                it is fast.

   Thunderbolt  PCIe, on a cable that leaves the enclosure.

   So: the thing that makes PCIe fast is the thing that makes
   it unsafe to expose, and the thing that makes USB slow is
   the thing that makes it safe to hand to a stranger.

That is the whole trade, and it is the same shape as chapter 27.5's: the property you want and the property you fear are the same property. Guaranteed bandwidth is no retries; bus mastering is unrestricted memory access.

USBPCIeThunderbolt
Peer can read host memorynoyesyes
Practical throughput~4 Gbps~8 Gbps/lane~16 Gbps
Worst-case latency125 µs (a frame)~2 µs~10 µs
Peers on one port1271 (without a switch)6
Leaves the enclosureyes, by designnoyes
Isochronous guaranteeyesnono
Hot-plugmaturepossible, awkwardyes

3. What We Are Building

link_selector takes a requirement set and reports, per bus, which requirement it fails. It does not score the buses and it does not pick a winner.

Its most valuable output is n_fit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   n_fit == 0   INFEASIBLE.    Somebody must relax a requirement,
                              and `fail_*` says which one is binding.

   n_fit == 1   A DECISION.    Exactly one bus can do this job.

   n_fit >  1   UNDETERMINED.  Several fit. The choice is now cost,
                              ecosystem, tooling or politics -- and
                              NOT a technical question. Reporting a
                              winner here is how architecture
                              reviews go wrong.

Requirements in, binding constraints out

A requirement set evaluated against three buses, each reporting the specific requirement that disqualifies it, with a verdict of infeasible, a single fit, or undeterminedRequirement setUSBPCIeThunderboltn_fit = 1n_fit > 1n_fit = 0fits?fits?fits?countcountcount12
The output is not a recommendation. It is per-bus: which requirement disqualifies it — and if several buses survive, the statement that the decision is not technical.

Four roles, four different binding constraints

Four requirement sets evaluated in turn: a keyboard fits USB only, an NVMe drive fits PCIe only, an external GPU with DMA fits nothing, and an easy set fits several busesunique fitsunique fitsinfeasible, then undeterminedinfeasible, then undeterminedkeyboard: USB alonekeyboard: USB aloneNVMe: PCIe aloneNVMe: PCIe aloneexternal DMA: infeasibleexternal DMA: infeasibleeasy: three fit, not technicaleasy: three fit, nottechnicalclkrolekbdkbdnvmenvmeeGPUeGPUeasyeasyeasyreq_mbps11700070001200012000101010req_dmafit_usbfit_pcien_fit010100030verdict0USBUSBPCIePCIeNONENONEUNDETUNDETt0t1t2t3t4t5t6t7t8
Notice that bandwidth decides only one of these. The external GPU is infeasible not because of throughput but because an external DMA peer is a security decision.

4. Seven Properties

#Property
1A bus fits if and only if no requirement of its is violated.
2A bus is rejected if and only if some requirement is violated.
3The reported reason is the most fundamental violation, not the last one found.
4n_fit is the number of fitting buses.
5infeasible ⟺ n_fit == 0.
6undetermined ⟺ n_fit > 1.
7With no query, nothing is asserted.

Property 3 is the one that makes the tool useful rather than merely correct. A requirement set that fails PCIe on four counts should be told the count that cannot be engineered around — and "PCIe fails on latency" sends somebody to optimise a link that was disqualified for leaving the enclosure.

5. Verilog-2005 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  link_selector -- "USB, PCIe or Thunderbolt for this role?" in
//  hardware.
//
//  The question sounds like a comparison and is not. Every candidate
//  answers it by listing bandwidths, and every listing is beside the
//  point, because the three buses are not ordered:
//
//      The question is never which bus is better. It is which
//      constraint you are unwilling to relax.
//
//  A requirement set either fits a bus or it does not, and when more
//  than one fits, the choice is decided by the constraint you refuse to
//  give up rather than by a number. So this module does not score the
//  buses. It takes a requirement set and reports, for each bus, WHICH
//  REQUIREMENT it fails -- and when several qualify, it reports that
//  several qualify rather than inventing a winner.
//
//  The most valuable output is n_fit. When it is zero the requirements
//  are infeasible and somebody has to relax one. When it is more than
//  one, the decision is not technical and pretending otherwise is how
//  architecture reviews go wrong.
// =====================================================================
module link_selector (
  input  wire        clk,
  input  wire        rst_n,

  // ---- a requirement set ----
  input  wire        req_valid,
  input  wire [15:0] req_mbps,        // sustained throughput needed
  input  wire [15:0] req_latency_us,  // worst-case latency tolerated
  input  wire        req_hotplug,     // must survive arbitrary insertion
  input  wire        req_external,    // the link leaves the enclosure
  input  wire        req_dma,         // the peer may read host memory
  input  wire        req_isoch,       // bounded delivery time required
  input  wire [7:0]  req_devices,     // how many peers on one port

  // ---- the answer, one cycle later ----
  output wire        ans_valid,

  // ---- which buses FIT, and if not, which requirement they fail ----
  output wire        fit_usb,
  output wire        fit_pcie,
  output wire        fit_tbolt,
  output wire [2:0]  fail_usb,
  output wire [2:0]  fail_pcie,
  output wire [2:0]  fail_tbolt,

  // ---- the shape of the answer, which is the real output ----
  output wire [1:0]  n_fit,
  output wire        infeasible,      // nothing fits: relax a requirement
  output wire        undetermined,    // several fit: not a technical choice

  // ---- observability ----
  output wire [31:0] n_queries,
  output wire [31:0] n_infeasible,
  output wire [31:0] n_undetermined,
  output wire [31:0] n_unique
);

  // ---- why a bus was ruled out ----
  localparam [2:0] F_NONE     = 3'd0,
                   F_BANDWIDTH= 3'd1,
                   F_LATENCY  = 3'd2,
                   F_HOTPLUG  = 3'd3,
                   F_EXTERNAL = 3'd4,
                   F_SECURITY = 3'd5,   // DMA across an external link
                   F_ISOCH    = 3'd6,
                   F_FANOUT   = 3'd7;

  // =================================================================
  //  THE CAPABILITY TABLE.
  //
  //  Deliberately conservative, and deliberately not a marketing figure.
  //  These are the numbers a design can actually hold across a real
  //  cable with real overhead, which is the only kind worth putting in
  //  a decision.
  // =================================================================
  localparam [15:0] USB_MBPS   = 16'd4000;   // USB 3.2 gen1, practical
  localparam [15:0] PCIE_MBPS  = 16'd8000;   // one gen3 lane, practical
  localparam [15:0] TBOLT_MBPS = 16'd16000;  // Thunderbolt 3, practical

  // Worst-case latency each bus can promise. USB's floor is its frame:
  // a device cannot be polled more often than once per microframe, so
  // 125 us is a hard bound and not an implementation detail.
  localparam [15:0] USB_LAT    = 16'd125;
  localparam [15:0] PCIE_LAT   = 16'd2;
  localparam [15:0] TBOLT_LAT  = 16'd10;

  // Peers on one port. PCIe needs a switch to exceed one and a switch
  // is not what "one port" means in this question.
  localparam [7:0]  USB_FANOUT   = 8'd127;
  localparam [7:0]  PCIE_FANOUT  = 8'd1;
  localparam [7:0]  TBOLT_FANOUT = 8'd6;

  reg        av_r;
  reg        fu_r, fp_r, ft_r;
  reg [2:0]  ru_r, rp_r, rt_r;
  reg [31:0] q_c, inf_c, und_c, uni_c;

  assign ans_valid    = av_r;
  assign fit_usb      = fu_r;
  assign fit_pcie     = fp_r;
  assign fit_tbolt    = ft_r;
  assign fail_usb     = ru_r;
  assign fail_pcie    = rp_r;
  assign fail_tbolt   = rt_r;

  wire [1:0] fits = {1'b0, fu_r} + {1'b0, fp_r} + {1'b0, ft_r};
  assign n_fit        = fits;
  assign infeasible   = av_r && (fits == 2'd0);
  assign undetermined = av_r && (fits >  2'd1);

  assign n_queries      = q_c;
  assign n_infeasible   = inf_c;
  assign n_undetermined = und_c;
  assign n_unique       = uni_c;

  // ---- the elimination rules, one bus at a time ----
  //
  // Ordered so the reported reason is the most fundamental one. A
  // requirement set that fails a bus on three counts should be told the
  // count that cannot be engineered around.
  function [2:0] why_usb;
    input [15:0] mbps;
    input [15:0] lat;
    input        dma;
    input        external;
    input [7:0]  devs;
    begin
      // Security first. A peer that may read host memory across a link
      // that leaves the enclosure is not a bandwidth question, and no
      // amount of throughput makes it acceptable.
      //
      // USB is the bus that PASSES this: a USB device cannot reach host
      // memory at all. It has no bus-mastering capability, which is
      // precisely why it is the right answer for anything a stranger
      // might plug in.
      if      (devs > USB_FANOUT)  why_usb = F_FANOUT;
      else if (mbps > USB_MBPS)    why_usb = F_BANDWIDTH;
      else if (lat  < USB_LAT)     why_usb = F_LATENCY;
      else                         why_usb = F_NONE;
    end
  endfunction

  function [2:0] why_pcie;
    input [15:0] mbps;
    input [15:0] lat;
    input        dma;
    input        external;
    input        hotplug;
    input [7:0]  devs;
    begin
      // PCIe's disqualifier is almost never bandwidth. It is that a PCIe
      // peer is a bus master with a view of host memory, so exposing it
      // outside the enclosure hands a stranger a DMA engine.
      if      (external && dma)      why_pcie = F_SECURITY;
      else if (external)             why_pcie = F_EXTERNAL;
      else if (devs > PCIE_FANOUT)   why_pcie = F_FANOUT;
      else if (mbps > PCIE_MBPS)     why_pcie = F_BANDWIDTH;
      else if (lat  < PCIE_LAT)      why_pcie = F_LATENCY;
      else                           why_pcie = F_NONE;
    end
  endfunction

  function [2:0] why_tbolt;
    input [15:0] mbps;
    input [15:0] lat;
    input        dma;
    input        hotplug;
    input        isoch;
    input [7:0]  devs;
    begin
      // Thunderbolt carries PCIe over an external cable, so it inherits
      // the same exposure -- mitigated by an IOMMU in practice, which is
      // a system requirement rather than a property of the link. Modelled
      // here as: acceptable only if DMA is NOT required.
      if      (dma)                   why_tbolt = F_SECURITY;
      else if (devs > TBOLT_FANOUT)   why_tbolt = F_FANOUT;
      else if (mbps > TBOLT_MBPS)     why_tbolt = F_BANDWIDTH;
      else if (lat  < TBOLT_LAT)      why_tbolt = F_LATENCY;
      else if (isoch)                 why_tbolt = F_ISOCH;
      else                            why_tbolt = F_NONE;
    end
  endfunction

  wire [2:0] w_usb   = why_usb  (req_mbps, req_latency_us, req_dma,
                                 req_external, req_devices);
  wire [2:0] w_pcie  = why_pcie (req_mbps, req_latency_us, req_dma,
                                 req_external, req_hotplug, req_devices);
  wire [2:0] w_tbolt = why_tbolt(req_mbps, req_latency_us, req_dma,
                                 req_hotplug, req_isoch, req_devices);

  wire nf_usb   = (w_usb   == F_NONE);
  wire nf_pcie  = (w_pcie  == F_NONE);
  wire nf_tbolt = (w_tbolt == F_NONE);
  wire [1:0] fits_now = {1'b0, nf_usb} + {1'b0, nf_pcie} + {1'b0, nf_tbolt};

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      av_r  <= 1'b0;
      fu_r  <= 1'b0;  fp_r <= 1'b0;  ft_r <= 1'b0;
      ru_r  <= F_NONE; rp_r <= F_NONE; rt_r <= F_NONE;
      q_c   <= 32'd0;
      inf_c <= 32'd0;
      und_c <= 32'd0;
      uni_c <= 32'd0;
    end else begin
      av_r <= req_valid;

      if (req_valid) begin
        fu_r <= nf_usb;
        fp_r <= nf_pcie;
        ft_r <= nf_tbolt;
        ru_r <= w_usb;
        rp_r <= w_pcie;
        rt_r <= w_tbolt;

        q_c <= q_c + 32'd1;
        // One add per class, computed from the combinational fit count.
        if (fits_now == 2'd0) inf_c <= inf_c + 32'd1;
        if (fits_now == 2'd1) uni_c <= uni_c + 32'd1;
        if (fits_now >  2'd1) und_c <= und_c + 32'd1;
      end else begin
        fu_r <= 1'b0;  fp_r <= 1'b0;  ft_r <= 1'b0;
        ru_r <= F_NONE; rp_r <= F_NONE; rt_r <= F_NONE;
      end
    end
  end

endmodule

6. SystemVerilog RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  link_selector -- SystemVerilog.
//
//  The failure reasons become a named enumeration, which is the entire
//  output of this module: "PCIe does not fit" is useless where
//  "F_SECURITY" tells an architect that no amount of engineering
//  effort will change the answer.
//
//  Originally: "USB, PCIe or Thunderbolt for this role?" in hardware.
//
//  The question sounds like a comparison and is not. Every candidate
//  answers it by listing bandwidths, and every listing is beside the
//  point, because the three buses are not ordered:
//
//      The question is never which bus is better. It is which
//      constraint you are unwilling to relax.
//
//  A requirement set either fits a bus or it does not, and when more
//  than one fits, the choice is decided by the constraint you refuse to
//  give up rather than by a number. So this module does not score the
//  buses. It takes a requirement set and reports, for each bus, WHICH
//  REQUIREMENT it fails -- and when several qualify, it reports that
//  several qualify rather than inventing a winner.
//
//  The most valuable output is n_fit. When it is zero the requirements
//  are infeasible and somebody has to relax one. When it is more than
//  one, the decision is not technical and pretending otherwise is how
//  architecture reviews go wrong.
// =====================================================================
module link_selector (
  input  logic       clk,
  input  logic       rst_n,

  // ---- a requirement set ----
  input  logic       req_valid,
  input  logic [15:0]req_mbps,        // sustained throughput needed
  input  logic [15:0]req_latency_us,  // worst-case latency tolerated
  input  logic       req_hotplug,     // must survive arbitrary insertion
  input  logic       req_external,    // the link leaves the enclosure
  input  logic       req_dma,         // the peer may read host memory
  input  logic       req_isoch,       // bounded delivery time required
  input  logic [7:0] req_devices,     // how many peers on one port

  // ---- the answer, one cycle later ----
  output logic       ans_valid,

  // ---- which buses FIT, and if not, which requirement they fail ----
  output logic       fit_usb,
  output logic       fit_pcie,
  output logic       fit_tbolt,
  output logic [2:0] fail_usb,
  output logic [2:0] fail_pcie,
  output logic [2:0] fail_tbolt,

  // ---- the shape of the answer, which is the real output ----
  output logic [1:0] n_fit,
  output logic       infeasible,      // nothing fits: relax a requirement
  output logic       undetermined,    // several fit: not a technical choice

  // ---- observability ----
  output logic [31:0]n_queries,
  output logic [31:0]n_infeasible,
  output logic [31:0]n_undetermined,
  output logic [31:0]n_unique
);

  // ---- why a bus was ruled out ----
  // The entire output of this module. "PCIe does not fit" is useless;
  // F_SECURITY tells an architect that no amount of engineering effort
  // will change the answer.
  typedef enum logic [2:0] {
    F_NONE      = 3'd0,
    F_BANDWIDTH = 3'd1,
    F_LATENCY   = 3'd2,
    F_HOTPLUG   = 3'd3,
    F_EXTERNAL  = 3'd4,
    F_SECURITY  = 3'd5,   // DMA across an external link
    F_ISOCH     = 3'd6,
    F_FANOUT    = 3'd7
  } fail_e;

  // =================================================================
  //  THE CAPABILITY TABLE.
  //
  //  Deliberately conservative, and deliberately not a marketing figure.
  //  These are the numbers a design can actually hold across a real
  //  cable with real overhead, which is the only kind worth putting in
  //  a decision.
  // =================================================================
  localparam [15:0] USB_MBPS   = 16'd4000;   // USB 3.2 gen1, practical
  localparam [15:0] PCIE_MBPS  = 16'd8000;   // one gen3 lane, practical
  localparam [15:0] TBOLT_MBPS = 16'd16000;  // Thunderbolt 3, practical

  // Worst-case latency each bus can promise. USB's floor is its frame:
  // a device cannot be polled more often than once per microframe, so
  // 125 us is a hard bound and not an implementation detail.
  localparam [15:0] USB_LAT    = 16'd125;
  localparam [15:0] PCIE_LAT   = 16'd2;
  localparam [15:0] TBOLT_LAT  = 16'd10;

  // Peers on one port. PCIe needs a switch to exceed one and a switch
  // is not what "one port" means in this question.
  localparam [7:0]  USB_FANOUT   = 8'd127;
  localparam [7:0]  PCIE_FANOUT  = 8'd1;
  localparam [7:0]  TBOLT_FANOUT = 8'd6;

  logic      av_r;
  logic      fu_r, fp_r, ft_r;
  fail_e     ru_r, rp_r, rt_r;
  logic [31:0] q_c, inf_c, und_c, uni_c;

  assign ans_valid    = av_r;
  assign fit_usb      = fu_r;
  assign fit_pcie     = fp_r;
  assign fit_tbolt    = ft_r;
  assign fail_usb     = ru_r;
  assign fail_pcie    = rp_r;
  assign fail_tbolt   = rt_r;

  wire [1:0] fits = {1'b0, fu_r} + {1'b0, fp_r} + {1'b0, ft_r};
  assign n_fit        = fits;
  assign infeasible   = av_r && (fits == 2'd0);
  assign undetermined = av_r && (fits >  2'd1);

  assign n_queries      = q_c;
  assign n_infeasible   = inf_c;
  assign n_undetermined = und_c;
  assign n_unique       = uni_c;

  // ---- the elimination rules, one bus at a time ----
  //
  // Ordered so the reported reason is the most fundamental one. A
  // requirement set that fails a bus on three counts should be told the
  // count that cannot be engineered around.
  function automatic fail_e why_usb(logic [15:0] mbps, logic [15:0] lat,
                                    logic dma, logic external,
                                    logic [7:0] devs);
    begin
      // Security first. A peer that may read host memory across a link
      // that leaves the enclosure is not a bandwidth question, and no
      // amount of throughput makes it acceptable.
      //
      // USB is the bus that PASSES this: a USB device cannot reach host
      // memory at all. It has no bus-mastering capability, which is
      // precisely why it is the right answer for anything a stranger
      // might plug in.
      if      (devs > USB_FANOUT)  why_usb = F_FANOUT;
      else if (mbps > USB_MBPS)    why_usb = F_BANDWIDTH;
      else if (lat  < USB_LAT)     why_usb = F_LATENCY;
      else                         why_usb = F_NONE;
    end
  endfunction

  function automatic fail_e why_pcie(logic [15:0] mbps, logic [15:0] lat,
                                     logic dma, logic external,
                                     logic hotplug, logic [7:0] devs);
    begin
      // PCIe's disqualifier is almost never bandwidth. It is that a PCIe
      // peer is a bus master with a view of host memory, so exposing it
      // outside the enclosure hands a stranger a DMA engine.
      if      (external && dma)      why_pcie = F_SECURITY;
      else if (external)             why_pcie = F_EXTERNAL;
      else if (devs > PCIE_FANOUT)   why_pcie = F_FANOUT;
      else if (mbps > PCIE_MBPS)     why_pcie = F_BANDWIDTH;
      else if (lat  < PCIE_LAT)      why_pcie = F_LATENCY;
      else                           why_pcie = F_NONE;
    end
  endfunction

  function automatic fail_e why_tbolt(logic [15:0] mbps, logic [15:0] lat,
                                      logic dma, logic hotplug,
                                      logic isoch, logic [7:0] devs);
    begin
      // Thunderbolt carries PCIe over an external cable, so it inherits
      // the same exposure -- mitigated by an IOMMU in practice, which is
      // a system requirement rather than a property of the link. Modelled
      // here as: acceptable only if DMA is NOT required.
      if      (dma)                   why_tbolt = F_SECURITY;
      else if (devs > TBOLT_FANOUT)   why_tbolt = F_FANOUT;
      else if (mbps > TBOLT_MBPS)     why_tbolt = F_BANDWIDTH;
      else if (lat  < TBOLT_LAT)      why_tbolt = F_LATENCY;
      else if (isoch)                 why_tbolt = F_ISOCH;
      else                            why_tbolt = F_NONE;
    end
  endfunction

  // Declared and ASSIGNED separately, deliberately. `fail_e w_usb = expr;`
  // is a variable declaration with an INITIALISER -- evaluated once at time
  // zero and never again -- where the Verilog `wire w_usb = expr;` is a
  // continuous assignment. The two look almost identical and behave
  // nothing alike: every requirement set was judged against the values
  // present at time 0, and 53533 buses were recommended that could not do
  // the job.
  fail_e w_usb, w_pcie, w_tbolt;

  assign w_usb   = why_usb  (req_mbps, req_latency_us, req_dma,
                             req_external, req_devices);
  assign w_pcie  = why_pcie (req_mbps, req_latency_us, req_dma,
                             req_external, req_hotplug, req_devices);
  assign w_tbolt = why_tbolt(req_mbps, req_latency_us, req_dma,
                             req_hotplug, req_isoch, req_devices);

  wire nf_usb   = (w_usb   == F_NONE);
  wire nf_pcie  = (w_pcie  == F_NONE);
  wire nf_tbolt = (w_tbolt == F_NONE);
  wire [1:0] fits_now = {1'b0, nf_usb} + {1'b0, nf_pcie} + {1'b0, nf_tbolt};

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      av_r  <= 1'b0;
      fu_r  <= 1'b0;  fp_r <= 1'b0;  ft_r <= 1'b0;
      ru_r  <= F_NONE; rp_r <= F_NONE; rt_r <= F_NONE;
      q_c   <= 32'd0;
      inf_c <= 32'd0;
      und_c <= 32'd0;
      uni_c <= 32'd0;
    end else begin
      av_r <= req_valid;

      if (req_valid) begin
        fu_r <= nf_usb;
        fp_r <= nf_pcie;
        ft_r <= nf_tbolt;
        ru_r <= w_usb;
        rp_r <= w_pcie;
        rt_r <= w_tbolt;

        q_c <= q_c + 32'd1;
        // One add per class, computed from the combinational fit count.
        if (fits_now == 2'd0) inf_c <= inf_c + 32'd1;
        if (fits_now == 2'd1) uni_c <= uni_c + 32'd1;
        if (fits_now >  2'd1) und_c <= und_c + 32'd1;
      end else begin
        fu_r <= 1'b0;  fp_r <= 1'b0;  ft_r <= 1'b0;
        ru_r <= F_NONE; rp_r <= F_NONE; rt_r <= F_NONE;
      end
    end
  end

endmodule

7. VHDL-2008 RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  link_selector -- VHDL-2008.
--
--  "USB, PCIe or Thunderbolt for this role?" sounds like a comparison and
--  is not. Every candidate answers it by listing bandwidths, and every
--  listing is beside the point, because the three buses are not ordered:
--
--      The question is never which bus is better. It is which
--      constraint you are unwilling to relax.
--
--  So this entity does not score the buses. It takes a requirement set
--  and reports, per bus, WHICH REQUIREMENT it fails -- and when several
--  qualify, it says that several qualify rather than inventing a winner.
--
--  The most valuable output is n_fit. Zero means the requirements are
--  infeasible and somebody must relax one. More than one means the
--  decision is not technical, and pretending otherwise is how
--  architecture reviews go wrong.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package bs_pkg is
  -- The entire output of this entity. "PCIe does not fit" is useless;
  -- FAIL_SECURITY tells an architect that no amount of engineering effort
  -- will change the answer.
  type fail_t is (FAIL_NONE, FAIL_BANDWIDTH, FAIL_LATENCY, FAIL_HOTPLUG,
                  FAIL_EXTERNAL, FAIL_SECURITY, FAIL_ISOCH, FAIL_FANOUT);

  function code_of(f : fail_t) return std_logic_vector;
end package;

package body bs_pkg is
  function code_of(f : fail_t) return std_logic_vector is
  begin
    case f is
      when FAIL_NONE      => return "000";
      when FAIL_BANDWIDTH => return "001";
      when FAIL_LATENCY   => return "010";
      when FAIL_HOTPLUG   => return "011";
      when FAIL_EXTERNAL  => return "100";
      when FAIL_SECURITY  => return "101";
      when FAIL_ISOCH     => return "110";
      when FAIL_FANOUT    => return "111";
    end case;
  end function;
end package body;

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

entity link_selector is
  port (
    clk            : in  std_logic;
    rst_n          : in  std_logic;

    req_valid      : in  std_logic;
    req_mbps       : in  std_logic_vector(15 downto 0);
    req_latency_us : in  std_logic_vector(15 downto 0);
    req_hotplug    : in  std_logic;
    req_external   : in  std_logic;
    req_dma        : in  std_logic;
    req_isoch      : in  std_logic;
    req_devices    : in  std_logic_vector(7 downto 0);

    ans_valid      : out std_logic;

    fit_usb        : out std_logic;
    fit_pcie       : out std_logic;
    fit_tbolt      : out std_logic;
    fail_usb       : out std_logic_vector(2 downto 0);
    fail_pcie      : out std_logic_vector(2 downto 0);
    fail_tbolt     : out std_logic_vector(2 downto 0);

    n_fit          : out std_logic_vector(1 downto 0);
    infeasible     : out std_logic;
    undetermined   : out std_logic;

    n_queries      : out std_logic_vector(31 downto 0);
    n_infeasible   : out std_logic_vector(31 downto 0);
    n_undetermined : out std_logic_vector(31 downto 0);
    n_unique       : out std_logic_vector(31 downto 0)
  );
end entity;

architecture rtl of link_selector is
  -- =================================================================
  --  THE CAPABILITY TABLE.
  --
  --  Deliberately conservative, and deliberately not a marketing figure.
  --  These are the numbers a design can actually hold across a real cable
  --  with real overhead, which is the only kind worth putting in a
  --  decision.
  -- =================================================================
  constant USB_MBPS   : natural := 4000;    -- USB 3.2 gen1, practical
  constant PCIE_MBPS  : natural := 8000;    -- one gen3 lane, practical
  constant TBOLT_MBPS : natural := 16000;   -- Thunderbolt 3, practical

  -- USB's latency floor is its frame: a device cannot be polled more often
  -- than once per microframe, so 125 us is a hard bound, not an
  -- implementation detail.
  constant USB_LAT   : natural := 125;
  constant PCIE_LAT  : natural := 2;
  constant TBOLT_LAT : natural := 10;

  -- Peers on one port. PCIe needs a switch to exceed one, and a switch is
  -- not what "one port" means in this question.
  constant USB_FANOUT   : natural := 127;
  constant PCIE_FANOUT  : natural := 1;
  constant TBOLT_FANOUT : natural := 6;

  signal av_r : std_logic := '0';
  signal fu_r, fp_r, ft_r : std_logic := '0';
  signal ru_r, rp_r, rt_r : fail_t := FAIL_NONE;
  signal q_c, inf_c, und_c, uni_c : unsigned(31 downto 0) := (others => '0');

  signal w_usb, w_pcie, w_tbolt : fail_t;
  signal nf_usb, nf_pcie, nf_tbolt : boolean;
  signal fits, fits_now : natural range 0 to 3;

  function b2n(b : boolean) return natural is
  begin
    if b then return 1; else return 0; end if;
  end function;

  -- ---- the elimination rules, one bus at a time ----
  --
  -- Ordered so the reported reason is the most fundamental one. A
  -- requirement set that fails a bus on three counts should be told the
  -- count that cannot be engineered around.
  function why_usb(mbps, lat : natural; dma, external : boolean;
                   devs : natural) return fail_t is
  begin
    -- USB is the bus that PASSES the security test: a USB device cannot
    -- reach host memory at all. It has no bus-mastering capability, which
    -- is precisely why it is the right answer for anything a stranger
    -- might plug in.
    if    devs > USB_FANOUT then return FAIL_FANOUT;
    elsif mbps > USB_MBPS   then return FAIL_BANDWIDTH;
    elsif lat  < USB_LAT    then return FAIL_LATENCY;
    else                         return FAIL_NONE;
    end if;
  end function;

  function why_pcie(mbps, lat : natural; dma, external, hotplug : boolean;
                    devs : natural) return fail_t is
  begin
    -- PCIe's disqualifier is almost never bandwidth. It is that a PCIe
    -- peer is a bus master with a view of host memory, so exposing it
    -- outside the enclosure hands a stranger a DMA engine.
    if    external and dma   then return FAIL_SECURITY;
    elsif external           then return FAIL_EXTERNAL;
    elsif devs > PCIE_FANOUT then return FAIL_FANOUT;
    elsif mbps > PCIE_MBPS   then return FAIL_BANDWIDTH;
    elsif lat  < PCIE_LAT    then return FAIL_LATENCY;
    else                          return FAIL_NONE;
    end if;
  end function;

  function why_tbolt(mbps, lat : natural; dma, hotplug, isoch : boolean;
                     devs : natural) return fail_t is
  begin
    -- Thunderbolt carries PCIe over an external cable, so it inherits the
    -- same exposure -- mitigated by an IOMMU in practice, which is a
    -- system requirement rather than a property of the link. Modelled here
    -- as: acceptable only if DMA is NOT required.
    if    dma                 then return FAIL_SECURITY;
    elsif devs > TBOLT_FANOUT then return FAIL_FANOUT;
    elsif mbps > TBOLT_MBPS   then return FAIL_BANDWIDTH;
    elsif lat  < TBOLT_LAT    then return FAIL_LATENCY;
    elsif isoch               then return FAIL_ISOCH;
    else                           return FAIL_NONE;
    end if;
  end function;
begin

  w_usb   <= why_usb(to_integer(unsigned(req_mbps)),
                     to_integer(unsigned(req_latency_us)),
                     req_dma = '1', req_external = '1',
                     to_integer(unsigned(req_devices)));
  w_pcie  <= why_pcie(to_integer(unsigned(req_mbps)),
                      to_integer(unsigned(req_latency_us)),
                      req_dma = '1', req_external = '1', req_hotplug = '1',
                      to_integer(unsigned(req_devices)));
  w_tbolt <= why_tbolt(to_integer(unsigned(req_mbps)),
                       to_integer(unsigned(req_latency_us)),
                       req_dma = '1', req_hotplug = '1', req_isoch = '1',
                       to_integer(unsigned(req_devices)));

  nf_usb   <= w_usb   = FAIL_NONE;
  nf_pcie  <= w_pcie  = FAIL_NONE;
  nf_tbolt <= w_tbolt = FAIL_NONE;

  fits_now <= b2n(nf_usb) + b2n(nf_pcie) + b2n(nf_tbolt);
  fits     <= b2n(fu_r = '1') + b2n(fp_r = '1') + b2n(ft_r = '1');

  ans_valid  <= av_r;
  fit_usb    <= fu_r;
  fit_pcie   <= fp_r;
  fit_tbolt  <= ft_r;
  fail_usb   <= code_of(ru_r);
  fail_pcie  <= code_of(rp_r);
  fail_tbolt <= code_of(rt_r);

  n_fit        <= std_logic_vector(to_unsigned(fits, 2));
  infeasible   <= '1' when (av_r = '1' and fits = 0) else '0';
  undetermined <= '1' when (av_r = '1' and fits > 1) else '0';

  n_queries      <= std_logic_vector(q_c);
  n_infeasible   <= std_logic_vector(inf_c);
  n_undetermined <= std_logic_vector(und_c);
  n_unique       <= std_logic_vector(uni_c);

  main : process(clk, rst_n)
  begin
    if rst_n = '0' then
      av_r  <= '0';
      fu_r  <= '0';  fp_r <= '0';  ft_r <= '0';
      ru_r  <= FAIL_NONE;  rp_r <= FAIL_NONE;  rt_r <= FAIL_NONE;
      q_c   <= (others => '0');
      inf_c <= (others => '0');
      und_c <= (others => '0');
      uni_c <= (others => '0');

    elsif rising_edge(clk) then
      av_r <= req_valid;

      if req_valid = '1' then
        if nf_usb   then fu_r <= '1'; else fu_r <= '0'; end if;
        if nf_pcie  then fp_r <= '1'; else fp_r <= '0'; end if;
        if nf_tbolt then ft_r <= '1'; else ft_r <= '0'; end if;
        ru_r <= w_usb;
        rp_r <= w_pcie;
        rt_r <= w_tbolt;

        q_c <= q_c + 1;
        if fits_now = 0 then inf_c <= inf_c + 1; end if;
        if fits_now = 1 then uni_c <= uni_c + 1; end if;
        if fits_now > 1 then und_c <= und_c + 1; end if;
      else
        fu_r <= '0';  fp_r <= '0';  ft_r <= '0';
        ru_r <= FAIL_NONE;  rp_r <= FAIL_NONE;  rt_r <= FAIL_NONE;
      end if;
    end if;
  end process;

end architecture;

8. The Testbench: A Different Formulation, Not a Copy

The design decides each bus with an if/elsif chain whose order encodes the priority of the reasons. The shadow builds a violation set — one boolean per requirement — and then scans it in priority order.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   Design:  a chain. The ORDER is the priority.

   Shadow:  a SET of violated requirements, which does not
            depend on order at all, plus an explicit priority
            list scanned over it.

   A chain with its arms in the wrong order reports the wrong
   REASON while still reporting the right FIT. A set cannot
   make that mistake, so the two disagree exactly there --
   which is mutation J5.

That is the point of writing the model differently rather than more carefully. A shadow that was also a chain would have the same order and the same bug.

Verilog-2005 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for link_selector.
//
//  The shadow is a DIFFERENT FORMULATION, not a copy. The design decides
//  each bus with a nested if/else chain whose order encodes the priority
//  of the reasons. The shadow builds a VIOLATION BITMASK -- one bit per
//  requirement -- and then scans it in priority order.
//
//  Those two agree on every input and fail differently: an if/else chain
//  with its arms in the wrong order reports the wrong reason while still
//  reporting the right fit, and a bitmask cannot make that mistake
//  because the mask does not depend on order at all.
// =====================================================================
`timescale 1ns/1ps
module tb_bs_v;

  localparam [2:0] F_NONE = 3'd0, F_BANDWIDTH = 3'd1, F_LATENCY = 3'd2,
                   F_HOTPLUG = 3'd3, F_EXTERNAL = 3'd4, F_SECURITY = 3'd5,
                   F_ISOCH = 3'd6, F_FANOUT = 3'd7;

  localparam [15:0] USB_MBPS = 16'd4000, PCIE_MBPS = 16'd8000,
                    TBOLT_MBPS = 16'd16000;
  localparam [15:0] USB_LAT = 16'd125, PCIE_LAT = 16'd2, TBOLT_LAT = 16'd10;
  localparam [7:0]  USB_FANOUT = 8'd127, PCIE_FANOUT = 8'd1,
                    TBOLT_FANOUT = 8'd6;

  reg         clk = 1'b0, rst_n = 1'b0;
  reg         req_valid = 1'b0;
  reg  [15:0] req_mbps = 16'd0;
  reg  [15:0] req_latency_us = 16'd0;
  reg         req_hotplug = 1'b0;
  reg         req_external = 1'b0;
  reg         req_dma = 1'b0;
  reg         req_isoch = 1'b0;
  reg  [7:0]  req_devices = 8'd1;

  wire        ans_valid, fit_usb, fit_pcie, fit_tbolt;
  wire [2:0]  fail_usb, fail_pcie, fail_tbolt;
  wire [1:0]  n_fit;
  wire        infeasible, undetermined;
  wire [31:0] n_queries, n_infeasible, n_undetermined, n_unique;

  link_selector dut (
    .clk(clk), .rst_n(rst_n),
    .req_valid(req_valid), .req_mbps(req_mbps),
    .req_latency_us(req_latency_us), .req_hotplug(req_hotplug),
    .req_external(req_external), .req_dma(req_dma), .req_isoch(req_isoch),
    .req_devices(req_devices),
    .ans_valid(ans_valid),
    .fit_usb(fit_usb), .fit_pcie(fit_pcie), .fit_tbolt(fit_tbolt),
    .fail_usb(fail_usb), .fail_pcie(fail_pcie), .fail_tbolt(fail_tbolt),
    .n_fit(n_fit), .infeasible(infeasible), .undetermined(undetermined),
    .n_queries(n_queries), .n_infeasible(n_infeasible),
    .n_undetermined(n_undetermined), .n_unique(n_unique)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;
  integer seed;

  function [31:0] urand;
    input dummy;
    begin urand = $random(seed) & 32'h3FFF_FFFF; end
  endfunction

  reg [31:0] x_q, x_inf, x_und, x_uni;

  // ---- the headline counter ----
  //
  // Every time a bus was reported as FITTING while one of its
  // requirements was violated. On a correct design this is unreachable,
  // and it is the only output of this bench that matters on its own: a
  // selector that recommends a bus which cannot do the job is worse than
  // no selector.
  integer n_bad_fit = 0;

  // ---- and its mirror ----
  integer n_bad_reject = 0;   // rejected with nothing violated

  // ---- exhaustive reach ----
  // 7 bandwidths x 5 latencies x 2^4 flags x 5 fanouts = 2800, not 5600.
  // Four binary flags give 16, not 32 -- and a denominator that is twice
  // the domain reports a complete sweep as 2800/5600, which looks like a
  // suite with a hole in it. Same trap as chapter 27.7's cycle parity.
  reg reach [0:2799];
  integer ri, n_reach;

  task ck(input cond, input [255:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step=%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---------------------------------------------------------------
  //  The shadow: a VIOLATION BITMASK per bus, scanned in priority
  //  order. Bit positions match the F_ codes.
  // ---------------------------------------------------------------
  function [7:0] viol_usb;
    input [15:0] mbps; input [15:0] lat; input [7:0] devs;
    reg [7:0] m;
    begin
      m = 8'd0;
      if (devs > USB_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > USB_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < USB_LAT)    m[F_LATENCY]   = 1'b1;
      viol_usb = m;
    end
  endfunction

  function [7:0] viol_pcie;
    input [15:0] mbps; input [15:0] lat; input dma; input external;
    input [7:0] devs;
    reg [7:0] m;
    begin
      m = 8'd0;
      if (external && dma)    m[F_SECURITY]  = 1'b1;
      if (external)           m[F_EXTERNAL]  = 1'b1;
      if (devs > PCIE_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > PCIE_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < PCIE_LAT)    m[F_LATENCY]   = 1'b1;
      viol_pcie = m;
    end
  endfunction

  function [7:0] viol_tbolt;
    input [15:0] mbps; input [15:0] lat; input dma; input isoch;
    input [7:0] devs;
    reg [7:0] m;
    begin
      m = 8'd0;
      if (dma)                 m[F_SECURITY]  = 1'b1;
      if (devs > TBOLT_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > TBOLT_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < TBOLT_LAT)    m[F_LATENCY]   = 1'b1;
      if (isoch)               m[F_ISOCH]     = 1'b1;
      viol_tbolt = m;
    end
  endfunction

  // The priority order each bus reports its reason in. Kept as explicit
  // lists so that the design's if/else ORDER is checked, not assumed.
  function [2:0] first_of;
    input [7:0] m;
    input [2:0] p0, p1, p2, p3, p4;
    begin
      if      (m[p0]) first_of = p0;
      else if (m[p1]) first_of = p1;
      else if (m[p2]) first_of = p2;
      else if (m[p3]) first_of = p3;
      else if (m[p4]) first_of = p4;
      else            first_of = F_NONE;
    end
  endfunction

  task query(input [15:0] mbps, input [15:0] lat, input hp, input ex,
             input dm, input iso, input [7:0] devs);
    reg [7:0] mu, mp, mt;
    reg       eu, ep, et;
    reg [2:0] cu, cp, ct;
    reg [1:0] e_fits;
    begin
      req_valid = 1'b1;
      req_mbps = mbps;  req_latency_us = lat;
      req_hotplug = hp; req_external = ex;
      req_dma = dm;     req_isoch = iso;  req_devices = devs;

      // ---- the shadow, from bitmasks ----
      mu = viol_usb(mbps, lat, devs);
      mp = viol_pcie(mbps, lat, dm, ex, devs);
      mt = viol_tbolt(mbps, lat, dm, iso, devs);

      eu = (mu == 8'd0);
      ep = (mp == 8'd0);
      et = (mt == 8'd0);
      e_fits = {1'b0, eu} + {1'b0, ep} + {1'b0, et};

      cu = first_of(mu, F_FANOUT, F_BANDWIDTH, F_LATENCY, F_NONE, F_NONE);
      cp = first_of(mp, F_SECURITY, F_EXTERNAL, F_FANOUT, F_BANDWIDTH, F_LATENCY);
      ct = first_of(mt, F_SECURITY, F_FANOUT, F_BANDWIDTH, F_LATENCY, F_ISOCH);

      x_q = x_q + 1;
      if (e_fits == 2'd0) x_inf = x_inf + 1;
      if (e_fits == 2'd1) x_uni = x_uni + 1;
      if (e_fits >  2'd1) x_und = x_und + 1;

      @(posedge clk);
      #1;
      steps = steps + 1;
      req_valid = 1'b0;

      // ---- PROPERTY 1: the answer is presented ----
      ck(ans_valid === 1'b1, "ans_valid was not asserted for a query");

      // ---- PROPERTY 2: each bus fits exactly when nothing is violated --
      ck(fit_usb   === eu, "fit_usb disagrees");
      ck(fit_pcie  === ep, "fit_pcie disagrees");
      ck(fit_tbolt === et, "fit_tbolt disagrees");

      // ---- PROPERTY 3: NO BAD FIT ----
      //
      // A bus reported as fitting while a requirement is violated. The
      // one output of this bench that matters on its own: a selector
      // that recommends a bus which cannot do the job is worse than no
      // selector at all.
      if (fit_usb   && (mu != 8'd0)) n_bad_fit = n_bad_fit + 1;
      if (fit_pcie  && (mp != 8'd0)) n_bad_fit = n_bad_fit + 1;
      if (fit_tbolt && (mt != 8'd0)) n_bad_fit = n_bad_fit + 1;
      ck(n_bad_fit == 0,
         "a bus was recommended while one of its requirements was violated");

      // ---- PROPERTY 4: NO BAD REJECTION ----
      if (!fit_usb   && (mu == 8'd0)) n_bad_reject = n_bad_reject + 1;
      if (!fit_pcie  && (mp == 8'd0)) n_bad_reject = n_bad_reject + 1;
      if (!fit_tbolt && (mt == 8'd0)) n_bad_reject = n_bad_reject + 1;
      ck(n_bad_reject == 0, "a bus was rejected with nothing violated");

      // ---- PROPERTY 5: the REASON is the highest-priority violation ----
      //
      // Checked against an order-independent mask plus an explicit
      // priority list, so a design whose if/else arms are in the wrong
      // order is caught even though its fit decision is right.
      ck(fail_usb   === cu, "fail_usb reports the wrong reason");
      ck(fail_pcie  === cp, "fail_pcie reports the wrong reason");
      ck(fail_tbolt === ct, "fail_tbolt reports the wrong reason");

      // ---- PROPERTY 6: the shape of the answer ----
      ck(n_fit        === e_fits, "n_fit disagrees");
      ck(infeasible   === (e_fits == 2'd0), "infeasible disagrees");
      ck(undetermined === (e_fits >  2'd1), "undetermined disagrees");
      ck(!(infeasible && undetermined),
         "the answer was both infeasible and undetermined");

      // ---- PROPERTY 7: the counters agree ----
      ck(n_queries      === x_q,   "query count disagrees");
      ck(n_infeasible   === x_inf, "infeasible count disagrees");
      ck(n_undetermined === x_und, "undetermined count disagrees");
      ck(n_unique       === x_uni, "unique count disagrees");
    end
  endtask

  task idle;
    begin
      req_valid = 1'b0;
      @(posedge clk);
      #1;
      steps = steps + 1;
      ck(ans_valid === 1'b0, "ans_valid was asserted with no query");
      ck(n_fit === 2'd0, "n_fit was non-zero with no query");
      ck(infeasible === 1'b0, "infeasible was asserted with no query");
      ck(undetermined === 1'b0, "undetermined was asserted with no query");
    end
  endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      req_valid = 0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      x_q = 0; x_inf = 0; x_und = 0; x_uni = 0;
      @(posedge clk); #1;
    end
  endtask

  integer bi, li, hi, ei, di, ii, vi, k;
  reg [15:0] mbs [0:6];
  reg [15:0] lts [0:4];
  reg [7:0]  dvs [0:4];

  initial begin
    for (ri = 0; ri < 2800; ri = ri + 1) reach[ri] = 1'b0;
    // Boundary values on purpose: each pair straddles a bus limit.
    mbs[0] = 16'd100;   mbs[1] = 16'd4000;  mbs[2] = 16'd4001;
    mbs[3] = 16'd8000;  mbs[4] = 16'd8001;  mbs[5] = 16'd16000;
    mbs[6] = 16'd16001;
    lts[0] = 16'd1;     lts[1] = 16'd2;     lts[2] = 16'd10;
    lts[3] = 16'd125;   lts[4] = 16'd1000;
    dvs[0] = 8'd1;      dvs[1] = 8'd6;      dvs[2] = 8'd7;
    dvs[3] = 8'd127;    dvs[4] = 8'd128;
    seed = 32'd27010;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every requirement set.
    //  7 bandwidths x 5 latencies x hotplug x external x dma x isoch
    //  x 5 fanouts = 2800.
    // =============================================================
    reset_dut;
    for (bi = 0; bi < 7; bi = bi + 1)
    for (li = 0; li < 5; li = li + 1)
    for (hi = 0; hi < 2; hi = hi + 1)
    for (ei = 0; ei < 2; ei = ei + 1)
    for (di = 0; di < 2; di = di + 1)
    for (ii = 0; ii < 2; ii = ii + 1)
    for (vi = 0; vi < 5; vi = vi + 1) begin
      query(mbs[bi], lts[li], hi[0], ei[0], di[0], ii[0], dvs[vi]);
      ri = (((((bi * 5 + li) * 2 + hi) * 2 + ei) * 2 + di) * 2 + ii) * 5 + vi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- the real roles, and their answers.
    //
    //  Each of these is a requirement set somebody actually has, and
    //  the point of each is the REASON rather than the winner.
    // =============================================================
    reset_dut;

    // A keyboard. Trivially slow, must be hot-pluggable, external, and a
    // stranger may plug it in -- so it must NOT be able to read memory.
    query(16'd1, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd1);
    ck(fit_usb === 1'b1, "USB does not fit a keyboard");
    ck(fit_pcie === 1'b0, "PCIe was offered for an external keyboard");
    ck(fail_pcie === F_EXTERNAL, "PCIe was not refused for being external");

    // An NVMe drive inside the enclosure. Needs bandwidth and low
    // latency, and DMA is the entire point.
    query(16'd7000, 16'd5, 1'b0, 1'b0, 1'b1, 1'b0, 8'd1);
    ck(fit_pcie === 1'b1, "PCIe does not fit an internal NVMe drive");
    ck(fit_usb === 1'b0, "USB was offered for a 7000 Mbps link");
    ck(fail_usb === F_BANDWIDTH, "USB was not refused for bandwidth");

    // An external GPU. The bandwidth needs Thunderbolt and the DMA is
    // what makes it a security decision rather than a technical one.
    query(16'd12000, 16'd50, 1'b1, 1'b1, 1'b1, 1'b0, 8'd1);
    ck(fit_usb   === 1'b0, "USB was offered for 12000 Mbps");
    ck(fit_pcie  === 1'b0, "PCIe was offered outside the enclosure");
    ck(fail_pcie === F_SECURITY, "PCIe was not refused on security grounds");
    ck(fit_tbolt === 1'b0, "Thunderbolt was offered for an unmitigated DMA peer");
    ck(fail_tbolt === F_SECURITY, "Thunderbolt was not refused on security grounds");
    ck(infeasible === 1'b1, "an external DMA peer at 12 Gbps was not called infeasible");

    // A USB audio interface. Isochronous, external, no DMA, modest rate.
    query(16'd50, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b1, 8'd1);
    ck(fit_usb   === 1'b1, "USB does not fit an isochronous audio device");
    ck(fit_tbolt === 1'b0, "Thunderbolt was offered for isochronous traffic");
    ck(fail_tbolt === F_ISOCH, "Thunderbolt was not refused for isochronous");

    // A hub of 40 peripherals on one port.
    query(16'd10, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd40);
    ck(fit_usb   === 1'b1, "USB does not fit 40 devices on one port");
    ck(fit_pcie  === 1'b0, "PCIe was offered for 40 devices on one port");
    ck(fail_pcie === F_SECURITY || fail_pcie === F_EXTERNAL,
       "PCIe was refused for the wrong reason");

    // A requirement set that NOTHING satisfies: 20 Gbps at 1 us.
    query(16'd20000, 16'd1, 1'b0, 1'b0, 1'b0, 1'b0, 8'd1);
    ck(infeasible === 1'b1, "20 Gbps at 1 us was not called infeasible");
    ck(n_fit === 2'd0, "a bus was offered for an impossible requirement set");

    // A requirement set that SEVERAL satisfy, which is not a technical
    // decision and must not be reported as one.
    query(16'd10, 16'd1000, 1'b0, 1'b0, 1'b0, 1'b0, 8'd1);
    ck(n_fit > 2'd1, "an easy requirement set was not satisfied by several buses");
    ck(undetermined === 1'b1, "several fitting buses were not reported as undetermined");
    ck(infeasible === 1'b0, "an undetermined answer was also called infeasible");

    // =============================================================
    //  PHASE 3 (DIRECTED, EXHAUSTIVE) -- each bus as the UNIQUE fit.
    //
    //  Three requirement sets, each satisfied by exactly one bus, so
    //  the "exactly one" state is reached for every bus rather than
    //  for one convenient one.
    // =============================================================
    reset_dut;
    // USB alone: many external devices, no DMA, relaxed latency
    query(16'd10, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd100);
    ck(n_fit === 2'd1 && fit_usb === 1'b1, "USB is not the unique fit here");
    // PCIe alone: internal, DMA, tight latency
    query(16'd7000, 16'd3, 1'b0, 1'b0, 1'b1, 1'b0, 8'd1);
    ck(n_fit === 2'd1 && fit_pcie === 1'b1, "PCIe is not the unique fit here");
    // Thunderbolt alone: too fast for USB, external so not PCIe, no DMA
    query(16'd12000, 16'd50, 1'b1, 1'b1, 1'b0, 1'b0, 8'd2);
    ck(n_fit === 2'd1 && fit_tbolt === 1'b1, "Thunderbolt is not the unique fit here");

    // =============================================================
    //  PHASE 4 (DIRECTED) -- no query means no answer.
    // =============================================================
    for (k = 0; k < 8; k = k + 1) idle;

    // =============================================================
    //  PHASE 5 (RANDOM) -- arbitrary requirement sets.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 20000; k = k + 1)
      // Bandwidth is biased LOW half the time. An unbiased draw over
      // 0..20000 makes most requirement sets impossible -- 17452 of 20000
      // infeasible against 67 undetermined -- so the two outcome classes
      // that need a satisfiable requirement set are barely exercised.
      query(((urand(0) % 2) == 0) ? (urand(0) % 4000) : (urand(0) % 20000),
            (urand(0) % 2000) + 1,
            (urand(0) % 2) == 0, (urand(0) % 2) == 0,
            (urand(0) % 3) == 0, (urand(0) % 4) == 0,
            ((urand(0) % 2) == 0) ? (urand(0) % 8) : (urand(0) % 200));
`endif

    n_reach = 0;
    for (ri = 0; ri < 2800; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;

    $display("steps=%0d checks=%0d reach=%0d/2800 errors=%0d",
             steps, checks, n_reach, errors);
    $display("[select] queries=%0d infeasible=%0d unique=%0d undetermined=%0d",
             n_queries, n_infeasible, n_unique, n_undetermined);
    $display("[the whole point] bad fits = %0d, bad rejections = %0d",
             n_bad_fit, n_bad_reject);
    if (n_reach != 2800) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

SystemVerilog testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for link_selector.
//
//  The shadow is a DIFFERENT FORMULATION, not a copy. The design decides
//  each bus with a nested if/else chain whose order encodes the priority
//  of the reasons. The shadow builds a VIOLATION BITMASK -- one bit per
//  requirement -- and then scans it in priority order.
//
//  Those two agree on every input and fail differently: an if/else chain
//  with its arms in the wrong order reports the wrong reason while still
//  reporting the right fit, and a bitmask cannot make that mistake
//  because the mask does not depend on order at all.
// =====================================================================
`timescale 1ns/1ps
module tb_bs_sv;

  localparam [2:0] F_NONE = 3'd0, F_BANDWIDTH = 3'd1, F_LATENCY = 3'd2,
                   F_HOTPLUG = 3'd3, F_EXTERNAL = 3'd4, F_SECURITY = 3'd5,
                   F_ISOCH = 3'd6, F_FANOUT = 3'd7;

  localparam [15:0] USB_MBPS = 16'd4000, PCIE_MBPS = 16'd8000,
                    TBOLT_MBPS = 16'd16000;
  localparam [15:0] USB_LAT = 16'd125, PCIE_LAT = 16'd2, TBOLT_LAT = 16'd10;
  localparam [7:0]  USB_FANOUT = 8'd127, PCIE_FANOUT = 8'd1,
                    TBOLT_FANOUT = 8'd6;

  logic       clk = 1'b0, rst_n = 1'b0;
  logic       req_valid = 1'b0;
  logic [15:0] req_mbps = 16'd0;
  logic [15:0] req_latency_us = 16'd0;
  logic       req_hotplug = 1'b0;
  logic       req_external = 1'b0;
  logic       req_dma = 1'b0;
  logic       req_isoch = 1'b0;
  logic [7:0] req_devices = 8'd1;

  logic       ans_valid, fit_usb, fit_pcie, fit_tbolt;
  logic [2:0] fail_usb, fail_pcie, fail_tbolt;
  logic [1:0] n_fit;
  logic       infeasible, undetermined;
  logic [31:0] n_queries, n_infeasible, n_undetermined, n_unique;

  link_selector dut (
    .clk(clk), .rst_n(rst_n),
    .req_valid(req_valid), .req_mbps(req_mbps),
    .req_latency_us(req_latency_us), .req_hotplug(req_hotplug),
    .req_external(req_external), .req_dma(req_dma), .req_isoch(req_isoch),
    .req_devices(req_devices),
    .ans_valid(ans_valid),
    .fit_usb(fit_usb), .fit_pcie(fit_pcie), .fit_tbolt(fit_tbolt),
    .fail_usb(fail_usb), .fail_pcie(fail_pcie), .fail_tbolt(fail_tbolt),
    .n_fit(n_fit), .infeasible(infeasible), .undetermined(undetermined),
    .n_queries(n_queries), .n_infeasible(n_infeasible),
    .n_undetermined(n_undetermined), .n_unique(n_unique)
  );

  always #5 clk = ~clk;

  integer errors = 0, checks = 0, steps = 0;
  integer seed;

  function automatic logic [31:0] urand(bit dummy);
    return $random(seed) & 32'h3FFF_FFFF;
  endfunction

  logic [31:0] x_q, x_inf, x_und, x_uni;

  // ---- the headline counter ----
  //
  // Every time a bus was reported as FITTING while one of its
  // requirements was violated. On a correct design this is unreachable,
  // and it is the only output of this bench that matters on its own: a
  // selector that recommends a bus which cannot do the job is worse than
  // no selector.
  integer n_bad_fit = 0;

  // ---- and its mirror ----
  integer n_bad_reject = 0;   // rejected with nothing violated

  // ---- exhaustive reach ----
  // 7 bandwidths x 5 latencies x 2^4 flags x 5 fanouts = 2800, not 5600.
  // Four binary flags give 16, not 32 -- and a denominator that is twice
  // the domain reports a complete sweep as 2800/5600, which looks like a
  // suite with a hole in it. Same trap as chapter 27.7's cycle parity.
  logic reach [0:2799];
  integer ri, n_reach;

  task ck(input logic cond, input logic [255:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step=%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---------------------------------------------------------------
  //  The shadow: a VIOLATION BITMASK per bus, scanned in priority
  //  order. Bit positions match the F_ codes.
  // ---------------------------------------------------------------
  function automatic logic [7:0] viol_usb(logic [15:0] mbps, logic [15:0] lat,
                                          logic [7:0] devs);
    logic [7:0] m;
    begin
      m = 8'd0;
      if (devs > USB_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > USB_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < USB_LAT)    m[F_LATENCY]   = 1'b1;
      viol_usb = m;
    end
  endfunction

  function automatic logic [7:0] viol_pcie(logic [15:0] mbps, logic [15:0] lat,
                                           logic dma, logic external,
                                           logic [7:0] devs);
    logic [7:0] m;
    begin
      m = 8'd0;
      if (external && dma)    m[F_SECURITY]  = 1'b1;
      if (external)           m[F_EXTERNAL]  = 1'b1;
      if (devs > PCIE_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > PCIE_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < PCIE_LAT)    m[F_LATENCY]   = 1'b1;
      viol_pcie = m;
    end
  endfunction

  function automatic logic [7:0] viol_tbolt(logic [15:0] mbps, logic [15:0] lat,
                                            logic dma, logic isoch,
                                            logic [7:0] devs);
    logic [7:0] m;
    begin
      m = 8'd0;
      if (dma)                 m[F_SECURITY]  = 1'b1;
      if (devs > TBOLT_FANOUT) m[F_FANOUT]    = 1'b1;
      if (mbps > TBOLT_MBPS)   m[F_BANDWIDTH] = 1'b1;
      if (lat  < TBOLT_LAT)    m[F_LATENCY]   = 1'b1;
      if (isoch)               m[F_ISOCH]     = 1'b1;
      viol_tbolt = m;
    end
  endfunction

  // The priority order each bus reports its reason in. Kept as explicit
  // lists so that the design's if/else ORDER is checked, not assumed.
  function automatic logic [2:0] first_of(logic [7:0] m, logic [2:0] p0,
                                          logic [2:0] p1, logic [2:0] p2,
                                          logic [2:0] p3, logic [2:0] p4);
    begin
      if      (m[p0]) first_of = p0;
      else if (m[p1]) first_of = p1;
      else if (m[p2]) first_of = p2;
      else if (m[p3]) first_of = p3;
      else if (m[p4]) first_of = p4;
      else            first_of = F_NONE;
    end
  endfunction

  task query(input logic [15:0] mbps, input logic [15:0] lat,
             input logic hp, input logic ex,
             input logic dm, input logic iso, input logic [7:0] devs);
    logic [7:0] mu, mp, mt;
    logic     eu, ep, et;
    logic [2:0] cu, cp, ct;
    logic [1:0] e_fits;
    begin
      req_valid = 1'b1;
      req_mbps = mbps;  req_latency_us = lat;
      req_hotplug = hp; req_external = ex;
      req_dma = dm;     req_isoch = iso;  req_devices = devs;

      // ---- the shadow, from bitmasks ----
      mu = viol_usb(mbps, lat, devs);
      mp = viol_pcie(mbps, lat, dm, ex, devs);
      mt = viol_tbolt(mbps, lat, dm, iso, devs);

      eu = (mu == 8'd0);
      ep = (mp == 8'd0);
      et = (mt == 8'd0);
      e_fits = {1'b0, eu} + {1'b0, ep} + {1'b0, et};

      cu = first_of(mu, F_FANOUT, F_BANDWIDTH, F_LATENCY, F_NONE, F_NONE);
      cp = first_of(mp, F_SECURITY, F_EXTERNAL, F_FANOUT, F_BANDWIDTH, F_LATENCY);
      ct = first_of(mt, F_SECURITY, F_FANOUT, F_BANDWIDTH, F_LATENCY, F_ISOCH);

      x_q = x_q + 1;
      if (e_fits == 2'd0) x_inf = x_inf + 1;
      if (e_fits == 2'd1) x_uni = x_uni + 1;
      if (e_fits >  2'd1) x_und = x_und + 1;

      @(posedge clk);
      #1;
      steps = steps + 1;
      req_valid = 1'b0;

      // ---- PROPERTY 1: the answer is presented ----
      ck(ans_valid === 1'b1, "ans_valid was not asserted for a query");

      // ---- PROPERTY 2: each bus fits exactly when nothing is violated --
      ck(fit_usb   === eu, "fit_usb disagrees");
      ck(fit_pcie  === ep, "fit_pcie disagrees");
      ck(fit_tbolt === et, "fit_tbolt disagrees");

      // ---- PROPERTY 3: NO BAD FIT ----
      //
      // A bus reported as fitting while a requirement is violated. The
      // one output of this bench that matters on its own: a selector
      // that recommends a bus which cannot do the job is worse than no
      // selector at all.
      if (fit_usb   && (mu != 8'd0)) n_bad_fit = n_bad_fit + 1;
      if (fit_pcie  && (mp != 8'd0)) n_bad_fit = n_bad_fit + 1;
      if (fit_tbolt && (mt != 8'd0)) n_bad_fit = n_bad_fit + 1;
      ck(n_bad_fit == 0,
         "a bus was recommended while one of its requirements was violated");

      // ---- PROPERTY 4: NO BAD REJECTION ----
      if (!fit_usb   && (mu == 8'd0)) n_bad_reject = n_bad_reject + 1;
      if (!fit_pcie  && (mp == 8'd0)) n_bad_reject = n_bad_reject + 1;
      if (!fit_tbolt && (mt == 8'd0)) n_bad_reject = n_bad_reject + 1;
      ck(n_bad_reject == 0, "a bus was rejected with nothing violated");

      // ---- PROPERTY 5: the REASON is the highest-priority violation ----
      //
      // Checked against an order-independent mask plus an explicit
      // priority list, so a design whose if/else arms are in the wrong
      // order is caught even though its fit decision is right.
      ck(fail_usb   === cu, "fail_usb reports the wrong reason");
      ck(fail_pcie  === cp, "fail_pcie reports the wrong reason");
      ck(fail_tbolt === ct, "fail_tbolt reports the wrong reason");

      // ---- PROPERTY 6: the shape of the answer ----
      ck(n_fit        === e_fits, "n_fit disagrees");
      ck(infeasible   === (e_fits == 2'd0), "infeasible disagrees");
      ck(undetermined === (e_fits >  2'd1), "undetermined disagrees");
      ck(!(infeasible && undetermined),
         "the answer was both infeasible and undetermined");

      // ---- PROPERTY 7: the counters agree ----
      ck(n_queries      === x_q,   "query count disagrees");
      ck(n_infeasible   === x_inf, "infeasible count disagrees");
      ck(n_undetermined === x_und, "undetermined count disagrees");
      ck(n_unique       === x_uni, "unique count disagrees");
    end
  endtask

  task idle;
    begin
      req_valid = 1'b0;
      @(posedge clk);
      #1;
      steps = steps + 1;
      ck(ans_valid === 1'b0, "ans_valid was asserted with no query");
      ck(n_fit === 2'd0, "n_fit was non-zero with no query");
      ck(infeasible === 1'b0, "infeasible was asserted with no query");
      ck(undetermined === 1'b0, "undetermined was asserted with no query");
    end
  endtask

  task reset_dut;
    begin
      rst_n = 1'b0;
      req_valid = 0;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      x_q = 0; x_inf = 0; x_und = 0; x_uni = 0;
      @(posedge clk); #1;
    end
  endtask

  integer bi, li, hi, ei, di, ii, vi, k;
  logic [15:0] mbs [0:6];
  logic [15:0] lts [0:4];
  logic [7:0] dvs [0:4];

  initial begin
    for (ri = 0; ri < 2800; ri = ri + 1) reach[ri] = 1'b0;
    // Boundary values on purpose: each pair straddles a bus limit.
    mbs[0] = 16'd100;   mbs[1] = 16'd4000;  mbs[2] = 16'd4001;
    mbs[3] = 16'd8000;  mbs[4] = 16'd8001;  mbs[5] = 16'd16000;
    mbs[6] = 16'd16001;
    lts[0] = 16'd1;     lts[1] = 16'd2;     lts[2] = 16'd10;
    lts[3] = 16'd125;   lts[4] = 16'd1000;
    dvs[0] = 8'd1;      dvs[1] = 8'd6;      dvs[2] = 8'd7;
    dvs[3] = 8'd127;    dvs[4] = 8'd128;
    seed = 32'd27010;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every requirement set.
    //  7 bandwidths x 5 latencies x hotplug x external x dma x isoch
    //  x 5 fanouts = 2800.
    // =============================================================
    reset_dut;
    for (bi = 0; bi < 7; bi = bi + 1)
    for (li = 0; li < 5; li = li + 1)
    for (hi = 0; hi < 2; hi = hi + 1)
    for (ei = 0; ei < 2; ei = ei + 1)
    for (di = 0; di < 2; di = di + 1)
    for (ii = 0; ii < 2; ii = ii + 1)
    for (vi = 0; vi < 5; vi = vi + 1) begin
      query(mbs[bi], lts[li], hi[0], ei[0], di[0], ii[0], dvs[vi]);
      ri = (((((bi * 5 + li) * 2 + hi) * 2 + ei) * 2 + di) * 2 + ii) * 5 + vi;
      reach[ri] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED) -- the real roles, and their answers.
    //
    //  Each of these is a requirement set somebody actually has, and
    //  the point of each is the REASON rather than the winner.
    // =============================================================
    reset_dut;

    // A keyboard. Trivially slow, must be hot-pluggable, external, and a
    // stranger may plug it in -- so it must NOT be able to read memory.
    query(16'd1, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd1);
    ck(fit_usb === 1'b1, "USB does not fit a keyboard");
    ck(fit_pcie === 1'b0, "PCIe was offered for an external keyboard");
    ck(fail_pcie === F_EXTERNAL, "PCIe was not refused for being external");

    // An NVMe drive inside the enclosure. Needs bandwidth and low
    // latency, and DMA is the entire point.
    query(16'd7000, 16'd5, 1'b0, 1'b0, 1'b1, 1'b0, 8'd1);
    ck(fit_pcie === 1'b1, "PCIe does not fit an internal NVMe drive");
    ck(fit_usb === 1'b0, "USB was offered for a 7000 Mbps link");
    ck(fail_usb === F_BANDWIDTH, "USB was not refused for bandwidth");

    // An external GPU. The bandwidth needs Thunderbolt and the DMA is
    // what makes it a security decision rather than a technical one.
    query(16'd12000, 16'd50, 1'b1, 1'b1, 1'b1, 1'b0, 8'd1);
    ck(fit_usb   === 1'b0, "USB was offered for 12000 Mbps");
    ck(fit_pcie  === 1'b0, "PCIe was offered outside the enclosure");
    ck(fail_pcie === F_SECURITY, "PCIe was not refused on security grounds");
    ck(fit_tbolt === 1'b0, "Thunderbolt was offered for an unmitigated DMA peer");
    ck(fail_tbolt === F_SECURITY, "Thunderbolt was not refused on security grounds");
    ck(infeasible === 1'b1, "an external DMA peer at 12 Gbps was not called infeasible");

    // A USB audio interface. Isochronous, external, no DMA, modest rate.
    query(16'd50, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b1, 8'd1);
    ck(fit_usb   === 1'b1, "USB does not fit an isochronous audio device");
    ck(fit_tbolt === 1'b0, "Thunderbolt was offered for isochronous traffic");
    ck(fail_tbolt === F_ISOCH, "Thunderbolt was not refused for isochronous");

    // A hub of 40 peripherals on one port.
    query(16'd10, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd40);
    ck(fit_usb   === 1'b1, "USB does not fit 40 devices on one port");
    ck(fit_pcie  === 1'b0, "PCIe was offered for 40 devices on one port");
    ck(fail_pcie === F_SECURITY || fail_pcie === F_EXTERNAL,
       "PCIe was refused for the wrong reason");

    // A requirement set that NOTHING satisfies: 20 Gbps at 1 us.
    query(16'd20000, 16'd1, 1'b0, 1'b0, 1'b0, 1'b0, 8'd1);
    ck(infeasible === 1'b1, "20 Gbps at 1 us was not called infeasible");
    ck(n_fit === 2'd0, "a bus was offered for an impossible requirement set");

    // A requirement set that SEVERAL satisfy, which is not a technical
    // decision and must not be reported as one.
    query(16'd10, 16'd1000, 1'b0, 1'b0, 1'b0, 1'b0, 8'd1);
    ck(n_fit > 2'd1, "an easy requirement set was not satisfied by several buses");
    ck(undetermined === 1'b1, "several fitting buses were not reported as undetermined");
    ck(infeasible === 1'b0, "an undetermined answer was also called infeasible");

    // =============================================================
    //  PHASE 3 (DIRECTED, EXHAUSTIVE) -- each bus as the UNIQUE fit.
    //
    //  Three requirement sets, each satisfied by exactly one bus, so
    //  the "exactly one" state is reached for every bus rather than
    //  for one convenient one.
    // =============================================================
    reset_dut;
    // USB alone: many external devices, no DMA, relaxed latency
    query(16'd10, 16'd1000, 1'b1, 1'b1, 1'b0, 1'b0, 8'd100);
    ck(n_fit === 2'd1 && fit_usb === 1'b1, "USB is not the unique fit here");
    // PCIe alone: internal, DMA, tight latency
    query(16'd7000, 16'd3, 1'b0, 1'b0, 1'b1, 1'b0, 8'd1);
    ck(n_fit === 2'd1 && fit_pcie === 1'b1, "PCIe is not the unique fit here");
    // Thunderbolt alone: too fast for USB, external so not PCIe, no DMA
    query(16'd12000, 16'd50, 1'b1, 1'b1, 1'b0, 1'b0, 8'd2);
    ck(n_fit === 2'd1 && fit_tbolt === 1'b1, "Thunderbolt is not the unique fit here");

    // =============================================================
    //  PHASE 4 (DIRECTED) -- no query means no answer.
    // =============================================================
    for (k = 0; k < 8; k = k + 1) idle;

    // =============================================================
    //  PHASE 5 (RANDOM) -- arbitrary requirement sets.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (k = 0; k < 20000; k = k + 1)
      // Bandwidth is biased LOW half the time. An unbiased draw over
      // 0..20000 makes most requirement sets impossible -- 17452 of 20000
      // infeasible against 67 undetermined -- so the two outcome classes
      // that need a satisfiable requirement set are barely exercised.
      query(((urand(0) % 2) == 0) ? (urand(0) % 4000) : (urand(0) % 20000),
            (urand(0) % 2000) + 1,
            (urand(0) % 2) == 0, (urand(0) % 2) == 0,
            (urand(0) % 3) == 0, (urand(0) % 4) == 0,
            ((urand(0) % 2) == 0) ? (urand(0) % 8) : (urand(0) % 200));
`endif

    n_reach = 0;
    for (ri = 0; ri < 2800; ri = ri + 1) if (reach[ri]) n_reach = n_reach + 1;

    $display("steps=%0d checks=%0d reach=%0d/2800 errors=%0d",
             steps, checks, n_reach, errors);
    $display("[select] queries=%0d infeasible=%0d unique=%0d undetermined=%0d",
             n_queries, n_infeasible, n_unique, n_undetermined);
    $display("[the whole point] bad fits = %0d, bad rejections = %0d",
             n_bad_fit, n_bad_reject);
    if (n_reach != 2800) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

VHDL-2008 testbench

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  Testbench for link_selector (VHDL-2008).
--
--  The shadow is a DIFFERENT FORMULATION, not a copy. The design decides
--  each bus with an if/elsif chain whose ORDER encodes the priority of the
--  reasons. The shadow builds a VIOLATION SET -- one boolean per
--  requirement -- and then scans it in priority order.
--
--  Those two agree on every input and fail differently: a chain with its
--  arms in the wrong order reports the wrong REASON while still reporting
--  the right FIT, and a violation set cannot make that mistake because the
--  set does not depend on order at all.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.bs_pkg.all;

entity tb_bs_vhdl is
  generic (DIRECTED_ONLY : boolean := false);
end entity;

architecture sim of tb_bs_vhdl is
  constant USB_MBPS   : natural := 4000;
  constant PCIE_MBPS  : natural := 8000;
  constant TBOLT_MBPS : natural := 16000;
  constant USB_LAT    : natural := 125;
  constant PCIE_LAT   : natural := 2;
  constant TBOLT_LAT  : natural := 10;
  constant USB_FANOUT   : natural := 127;
  constant PCIE_FANOUT  : natural := 1;
  constant TBOLT_FANOUT : natural := 6;

  signal clk            : std_logic := '0';
  signal rst_n          : std_logic := '0';
  signal req_valid      : std_logic := '0';
  signal req_mbps       : std_logic_vector(15 downto 0) := (others => '0');
  signal req_latency_us : std_logic_vector(15 downto 0) := (others => '0');
  signal req_hotplug    : std_logic := '0';
  signal req_external   : std_logic := '0';
  signal req_dma        : std_logic := '0';
  signal req_isoch      : std_logic := '0';
  signal req_devices    : std_logic_vector(7 downto 0) := x"01";

  signal ans_valid, fit_usb, fit_pcie, fit_tbolt : std_logic;
  signal fail_usb, fail_pcie, fail_tbolt : std_logic_vector(2 downto 0);
  signal n_fit : std_logic_vector(1 downto 0);
  signal infeasible, undetermined : std_logic;
  signal n_queries, n_infeasible, n_undetermined, n_unique
    : std_logic_vector(31 downto 0);

  signal done : boolean := false;
begin

  dut : entity work.link_selector
    port map (
      clk => clk, rst_n => rst_n,
      req_valid => req_valid, req_mbps => req_mbps,
      req_latency_us => req_latency_us, req_hotplug => req_hotplug,
      req_external => req_external, req_dma => req_dma,
      req_isoch => req_isoch, req_devices => req_devices,
      ans_valid => ans_valid,
      fit_usb => fit_usb, fit_pcie => fit_pcie, fit_tbolt => fit_tbolt,
      fail_usb => fail_usb, fail_pcie => fail_pcie, fail_tbolt => fail_tbolt,
      n_fit => n_fit, infeasible => infeasible, undetermined => undetermined,
      n_queries => n_queries, n_infeasible => n_infeasible,
      n_undetermined => n_undetermined, n_unique => n_unique);

  clk <= not clk after 5 ns when not done else '0';

  stim : process
    variable errors : natural := 0;
    variable checks : natural := 0;
    variable steps  : natural := 0;

    variable x_q, x_inf, x_und, x_uni : natural := 0;
    variable n_bad_fit, n_bad_reject  : natural := 0;

    variable reach   : std_logic_vector(0 to 2799) := (others => '0');
    variable n_reach : natural := 0;

    variable rnd : unsigned(31 downto 0) := x"000BF27D";
    variable ln  : line;

    -- A violation SET, one boolean per requirement, order-independent.
    type viol_t is record
      bandwidth : boolean;
      latency   : boolean;
      external  : boolean;
      security  : boolean;
      isoch     : boolean;
      fanout    : boolean;
    end record;

    procedure ck(cond : boolean; what : string) is
    begin
      checks := checks + 1;
      if not cond then
        errors := errors + 1;
        if errors <= 20 then
          write(ln, string'("  ERROR step=") & integer'image(steps)
                & string'(": ") & what);
          writeline(output, ln);
        end if;
      end if;
    end procedure;

    impure function nxt return natural is
    begin
      rnd := rnd xor (rnd sll 13);
      rnd := rnd xor (rnd srl 17);
      rnd := rnd xor (rnd sll 5);
      return to_integer(rnd(14 downto 0));
    end function;

    function sl_of(b : boolean) return std_logic is
    begin
      if b then return '1'; else return '0'; end if;
    end function;

    function b2n(b : boolean) return natural is
    begin
      if b then return 1; else return 0; end if;
    end function;

    function empty(v : viol_t) return boolean is
    begin
      return not (v.bandwidth or v.latency or v.external
                  or v.security or v.isoch or v.fanout);
    end function;

    function v_usb(mbps, lat, devs : natural) return viol_t is
      variable v : viol_t := (false, false, false, false, false, false);
    begin
      v.fanout    := devs > USB_FANOUT;
      v.bandwidth := mbps > USB_MBPS;
      v.latency   := lat  < USB_LAT;
      return v;
    end function;

    function v_pcie(mbps, lat : natural; dma, ext : boolean;
                    devs : natural) return viol_t is
      variable v : viol_t := (false, false, false, false, false, false);
    begin
      v.security  := ext and dma;
      v.external  := ext;
      v.fanout    := devs > PCIE_FANOUT;
      v.bandwidth := mbps > PCIE_MBPS;
      v.latency   := lat  < PCIE_LAT;
      return v;
    end function;

    function v_tbolt(mbps, lat : natural; dma, iso : boolean;
                     devs : natural) return viol_t is
      variable v : viol_t := (false, false, false, false, false, false);
    begin
      v.security  := dma;
      v.fanout    := devs > TBOLT_FANOUT;
      v.bandwidth := mbps > TBOLT_MBPS;
      v.latency   := lat  < TBOLT_LAT;
      v.isoch     := iso;
      return v;
    end function;

    -- The priority order each bus reports in, written out explicitly so the
    -- design's chain ORDER is checked rather than assumed.
    function reason_usb(v : viol_t) return fail_t is
    begin
      if    v.fanout    then return FAIL_FANOUT;
      elsif v.bandwidth then return FAIL_BANDWIDTH;
      elsif v.latency   then return FAIL_LATENCY;
      else                   return FAIL_NONE;
      end if;
    end function;

    function reason_pcie(v : viol_t) return fail_t is
    begin
      if    v.security  then return FAIL_SECURITY;
      elsif v.external  then return FAIL_EXTERNAL;
      elsif v.fanout    then return FAIL_FANOUT;
      elsif v.bandwidth then return FAIL_BANDWIDTH;
      elsif v.latency   then return FAIL_LATENCY;
      else                   return FAIL_NONE;
      end if;
    end function;

    function reason_tbolt(v : viol_t) return fail_t is
    begin
      if    v.security  then return FAIL_SECURITY;
      elsif v.fanout    then return FAIL_FANOUT;
      elsif v.bandwidth then return FAIL_BANDWIDTH;
      elsif v.latency   then return FAIL_LATENCY;
      elsif v.isoch     then return FAIL_ISOCH;
      else                   return FAIL_NONE;
      end if;
    end function;

    procedure query(mbps, lat : natural; hp, ex, dm, iso : boolean;
                    devs : natural) is
      variable vu, vp, vt : viol_t;
      variable eu, ep, et : boolean;
      variable e_fits     : natural;
    begin
      req_valid      <= '1';
      req_mbps       <= std_logic_vector(to_unsigned(mbps, 16));
      req_latency_us <= std_logic_vector(to_unsigned(lat, 16));
      req_hotplug    <= sl_of(hp);
      req_external   <= sl_of(ex);
      req_dma        <= sl_of(dm);
      req_isoch      <= sl_of(iso);
      req_devices    <= std_logic_vector(to_unsigned(devs, 8));

      vu := v_usb(mbps, lat, devs);
      vp := v_pcie(mbps, lat, dm, ex, devs);
      vt := v_tbolt(mbps, lat, dm, iso, devs);
      eu := empty(vu);  ep := empty(vp);  et := empty(vt);
      e_fits := b2n(eu) + b2n(ep) + b2n(et);

      x_q := x_q + 1;
      if e_fits = 0 then x_inf := x_inf + 1; end if;
      if e_fits = 1 then x_uni := x_uni + 1; end if;
      if e_fits > 1 then x_und := x_und + 1; end if;

      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
      req_valid <= '0';

      ck(ans_valid = '1', "ans_valid was not asserted for a query");

      ck((fit_usb   = '1') = eu, "fit_usb disagrees");
      ck((fit_pcie  = '1') = ep, "fit_pcie disagrees");
      ck((fit_tbolt = '1') = et, "fit_tbolt disagrees");

      -- NO BAD FIT -- the one output that matters on its own. A selector
      -- that recommends a bus which cannot do the job is worse than none.
      if fit_usb   = '1' and not empty(vu) then n_bad_fit := n_bad_fit + 1; end if;
      if fit_pcie  = '1' and not empty(vp) then n_bad_fit := n_bad_fit + 1; end if;
      if fit_tbolt = '1' and not empty(vt) then n_bad_fit := n_bad_fit + 1; end if;
      ck(n_bad_fit = 0,
         "a bus was recommended while one of its requirements was violated");

      if fit_usb   = '0' and empty(vu) then n_bad_reject := n_bad_reject + 1; end if;
      if fit_pcie  = '0' and empty(vp) then n_bad_reject := n_bad_reject + 1; end if;
      if fit_tbolt = '0' and empty(vt) then n_bad_reject := n_bad_reject + 1; end if;
      ck(n_bad_reject = 0, "a bus was rejected with nothing violated");

      -- The REASON is the highest-priority violation. Checked against an
      -- order-independent set plus an explicit priority list, so a design
      -- whose chain arms are in the wrong order is caught even though its
      -- fit decision is right.
      ck(fail_usb   = code_of(reason_usb(vu)),   "fail_usb reports the wrong reason");
      ck(fail_pcie  = code_of(reason_pcie(vp)),  "fail_pcie reports the wrong reason");
      ck(fail_tbolt = code_of(reason_tbolt(vt)), "fail_tbolt reports the wrong reason");

      ck(to_integer(unsigned(n_fit)) = e_fits, "n_fit disagrees");
      ck((infeasible = '1') = (e_fits = 0), "infeasible disagrees");
      ck((undetermined = '1') = (e_fits > 1), "undetermined disagrees");
      ck(not (infeasible = '1' and undetermined = '1'),
         "the answer was both infeasible and undetermined");

      ck(to_integer(unsigned(n_queries))      = x_q,   "query count disagrees");
      ck(to_integer(unsigned(n_infeasible))   = x_inf, "infeasible count disagrees");
      ck(to_integer(unsigned(n_undetermined)) = x_und, "undetermined count disagrees");
      ck(to_integer(unsigned(n_unique))       = x_uni, "unique count disagrees");
    end procedure;

    procedure idle is
    begin
      req_valid <= '0';
      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
      ck(ans_valid = '0', "ans_valid was asserted with no query");
      ck(to_integer(unsigned(n_fit)) = 0, "n_fit was non-zero with no query");
      ck(infeasible = '0', "infeasible was asserted with no query");
      ck(undetermined = '0', "undetermined was asserted with no query");
    end procedure;

    procedure reset_dut is
    begin
      rst_n <= '0';
      req_valid <= '0';
      wait until rising_edge(clk);
      wait until rising_edge(clk);
      rst_n <= '1';
      x_q := 0; x_inf := 0; x_und := 0; x_uni := 0;
      wait until rising_edge(clk);
      wait for 1 ns;
    end procedure;

    -- Boundary values on purpose: each pair straddles a bus limit.
    type nat7 is array (0 to 6) of natural;
    type nat5 is array (0 to 4) of natural;
    constant mbs : nat7 := (100, 4000, 4001, 8000, 8001, 16000, 16001);
    constant lts : nat5 := (1, 2, 10, 125, 1000);
    constant dvs : nat5 := (1, 6, 7, 127, 128);
    variable ri : natural;
    -- VHDL has no inline declare block inside a process body, so the
    -- random phase's temporaries live here rather than where they are used.
    variable rm, rl, rd : natural;
  begin
    -- PHASE 1 (DIRECTED, EXHAUSTIVE) -- every requirement set.
    -- 7 bandwidths x 5 latencies x 2^4 flags x 5 fanouts = 2800.
    reset_dut;
    for bi in 0 to 6 loop
      for li in 0 to 4 loop
        for hi in 0 to 1 loop
          for ei in 0 to 1 loop
            for di in 0 to 1 loop
              for ii in 0 to 1 loop
                for vi in 0 to 4 loop
                  query(mbs(bi), lts(li), hi = 1, ei = 1, di = 1, ii = 1, dvs(vi));
                  ri := (((((bi*5 + li)*2 + hi)*2 + ei)*2 + di)*2 + ii)*5 + vi;
                  reach(ri) := '1';
                end loop;
              end loop;
            end loop;
          end loop;
        end loop;
      end loop;
    end loop;

    -- PHASE 2 (DIRECTED) -- the real roles, and their answers. The point of
    -- each is the REASON rather than the winner.
    reset_dut;

    -- A keyboard: trivially slow, hot-pluggable, external, and a stranger
    -- may plug it in -- so it must NOT be able to read memory.
    query(1, 1000, true, true, false, false, 1);
    ck(fit_usb = '1', "USB does not fit a keyboard");
    ck(fit_pcie = '0', "PCIe was offered for an external keyboard");
    ck(fail_pcie = code_of(FAIL_EXTERNAL), "PCIe was not refused for being external");

    -- An internal NVMe drive: bandwidth, low latency, and DMA is the point.
    query(7000, 5, false, false, true, false, 1);
    ck(fit_pcie = '1', "PCIe does not fit an internal NVMe drive");
    ck(fit_usb = '0', "USB was offered for a 7000 Mbps link");
    ck(fail_usb = code_of(FAIL_BANDWIDTH), "USB was not refused for bandwidth");

    -- An external GPU: the bandwidth needs Thunderbolt and the DMA makes it
    -- a security decision rather than a technical one.
    query(12000, 50, true, true, true, false, 1);
    ck(fit_usb = '0', "USB was offered for 12000 Mbps");
    ck(fit_pcie = '0', "PCIe was offered outside the enclosure");
    ck(fail_pcie = code_of(FAIL_SECURITY), "PCIe was not refused on security grounds");
    ck(fit_tbolt = '0', "Thunderbolt was offered for an unmitigated DMA peer");
    ck(fail_tbolt = code_of(FAIL_SECURITY), "Thunderbolt was not refused on security grounds");
    ck(infeasible = '1', "an external DMA peer at 12 Gbps was not called infeasible");

    -- A USB audio interface: isochronous, external, no DMA, modest rate.
    query(50, 1000, true, true, false, true, 1);
    ck(fit_usb = '1', "USB does not fit an isochronous audio device");
    ck(fit_tbolt = '0', "Thunderbolt was offered for isochronous traffic");
    ck(fail_tbolt = code_of(FAIL_ISOCH), "Thunderbolt was not refused for isochronous");

    -- 40 peripherals on one port.
    query(10, 1000, true, true, false, false, 40);
    ck(fit_usb = '1', "USB does not fit 40 devices on one port");
    ck(fit_pcie = '0', "PCIe was offered for 40 devices on one port");
    ck(fail_pcie = code_of(FAIL_SECURITY) or fail_pcie = code_of(FAIL_EXTERNAL),
       "PCIe was refused for the wrong reason");

    -- A requirement set NOTHING satisfies: 20 Gbps at 1 us.
    query(20000, 1, false, false, false, false, 1);
    ck(infeasible = '1', "20 Gbps at 1 us was not called infeasible");
    ck(to_integer(unsigned(n_fit)) = 0,
       "a bus was offered for an impossible requirement set");

    -- A requirement set SEVERAL satisfy, which is not a technical decision
    -- and must not be reported as one.
    query(10, 1000, false, false, false, false, 1);
    ck(to_integer(unsigned(n_fit)) > 1,
       "an easy requirement set was not satisfied by several buses");
    ck(undetermined = '1', "several fitting buses were not reported as undetermined");
    ck(infeasible = '0', "an undetermined answer was also called infeasible");

    -- PHASE 3 (DIRECTED, EXHAUSTIVE) -- each bus as the UNIQUE fit, so the
    -- "exactly one" state is reached for every bus.
    reset_dut;
    query(10, 1000, true, true, false, false, 100);
    ck(to_integer(unsigned(n_fit)) = 1 and fit_usb = '1',
       "USB is not the unique fit here");
    query(7000, 3, false, false, true, false, 1);
    ck(to_integer(unsigned(n_fit)) = 1 and fit_pcie = '1',
       "PCIe is not the unique fit here");
    query(12000, 50, true, true, false, false, 2);
    ck(to_integer(unsigned(n_fit)) = 1 and fit_tbolt = '1',
       "Thunderbolt is not the unique fit here");

    -- PHASE 4 (DIRECTED) -- no query means no answer
    for k in 0 to 7 loop idle; end loop;

    -- PHASE 5 (RANDOM) -- arbitrary requirement sets.
    --
    -- Bandwidth is biased LOW half the time: an unbiased draw over 0..20000
    -- makes most requirement sets impossible, so the two outcome classes
    -- that need a satisfiable set are barely exercised.
    if not DIRECTED_ONLY then
      reset_dut;
      for k in 0 to 19999 loop
        if (nxt mod 2) = 0 then rm := nxt mod 4000; else rm := nxt mod 20000; end if;
        rl := (nxt mod 2000) + 1;
        if (nxt mod 2) = 0 then rd := nxt mod 8; else rd := nxt mod 200; end if;
        query(rm, rl, (nxt mod 2) = 0, (nxt mod 2) = 0,
              (nxt mod 3) = 0, (nxt mod 4) = 0, rd);
      end loop;
    end if;

    n_reach := 0;
    for i in 0 to 2799 loop
      if reach(i) = '1' then n_reach := n_reach + 1; end if;
    end loop;

    write(ln, string'("steps=") & integer'image(steps)
          & string'(" checks=") & integer'image(checks)
          & string'(" reach=") & integer'image(n_reach) & string'("/2800")
          & string'(" errors=") & integer'image(errors));
    writeline(output, ln);
    write(ln, string'("[select] queries=") & integer'image(x_q)
          & string'(" infeasible=") & integer'image(x_inf)
          & string'(" unique=") & integer'image(x_uni)
          & string'(" undetermined=") & integer'image(x_und));
    writeline(output, ln);
    write(ln, string'("[the whole point] bad fits = ") & integer'image(n_bad_fit)
          & string'(", bad rejections = ") & integer'image(n_bad_reject));
    writeline(output, ln);
    if n_reach /= 2800 then
      write(ln, string'("FAIL: exhaustive sweep incomplete"));
      writeline(output, ln);
      errors := errors + 1;
    end if;
    if errors = 0 then
      write(ln, string'("PASS: 0 errors in ") & integer'image(checks)
            & string'(" checks"));
    else
      write(ln, string'("FAIL: ") & integer'image(errors)
            & string'(" errors in ") & integer'image(checks) & string'(" checks"));
    end if;
    writeline(output, ln);

    done <= true;
    wait;
  end process;

end architecture;

9. Exhaustive Verification

MeasureVerilogSystemVerilogVHDL
(rate × latency × hotplug × external × DMA × isoch × fanout) reached2800 / 28002800 / 28002800 / 2800
…reached by directed stimulus alone2800 / 28002800 / 28002800 / 2800
real-role scenarios777
unique-fit scenarios (one per bus)3 / 33 / 33 / 3
Steps228182281822818
Checks executed387828387828387828
queries200002000020000
infeasible915391538753
exactly one fit774777478177
undetermined310031003070
bad fits000
bad rejections000
ResultPASSPASSPASS

All three verdict classes are well represented — and that took a deliberate change to the stimulus. An unbiased bandwidth draw over 0–20000 Mbps made 17,452 of 20,000 queries infeasible against 67 undetermined, so the two classes that need a satisfiable requirement set were barely exercised at all.

10. Mutation Testing

These are defects in judgement. Each one is an argument somebody has actually made in an architecture review.

#MutationVerilogSysVerVHDL
J7the PCIe fanout limit is dropped — one port for forty peripherals118699118699120510
J2USB's latency floor is treated as tunable950439504395091
J6a bus limit taken from the wire rate, not the practical one889938899389585
J5the reported reason is the least fundamental violation140951409513536
J4an infeasible requirement set is not flagged115091150911109
J1PCIe offered across an external link WITH DMA415841584001
J3an undetermined answer is reported as a decision317531753145
—unmutated baseline000

All seven die in all three languages.

J3 scores lowest at 3175 and is the one this chapter is really about. It suppresses undetermined, so a requirement set that several buses satisfy is reported as though one had won. Nothing else breaks: every fit is right, every reason is right, every rejection is correct. The tool simply stops saying "this is not a technical decision" — and that sentence is the most valuable thing it produces.

Directed against random

#V allV directedV randomVHDL allVHDL directedVHDL random
J14158702345640017023299
J2950431266282381950911266282429
J331757531003145753070
J411509235691531110923568753
J51409519751212013536197511561
J68899377448124989585774481841
J71186991345410524512051013454107056
—BASE 000000

Every directed column identical, and the directed-only baseline reaches 2800/2800 with 0 errors.

J5's directed columns read 1975 and 1974 on the first run — one apart. A single count, in a column where identical stimulus against identical logic must give the identical number. The cause was not the design: the VHDL bench was missing one assertion that the Verilog had, and the check totals differed by exactly one too (387,827 against 387,828).

11. The Answers, With Reasons Rather Than Winners

This is the transcript of the interview answer. The reason matters more than the choice in every row.

RoleAnswerThe binding constraint
Keyboard, mouseUSBexternal + a stranger plugs it in ⇒ the peer must not reach memory
Internal NVMe drivePCIeDMA is the point, and it never leaves the enclosure
External GPUThunderbolt, with an IOMMUnothing else has the bandwidth; the IOMMU is a system requirement, not a link property
USB audio interfaceUSBisochronous delivery, which neither of the others offers
WebcamUSBisochronous, and a late frame is worthless
40 peripherals on one portUSBfanout of 127 against PCIe's 1
Internal accelerator, 2 µs deadlinePCIeUSB's 125 µs frame floor rules it out on latency alone
Firmware update port on a shipped productUSBthe peer must not be able to read memory, at any speed
Docking stationThunderboltit carries PCIe, USB and DisplayPort on one cable
20 Gbps at 1 µsnoneinfeasible; go back and relax one

12. Follow-Ups the Interviewer Will Ask

"Why not just use Thunderbolt everywhere?" Cost, and the fact that it carries PCIe — so every Thunderbolt port is a memory-access surface that needs an IOMMU to be safe. Also no isochronous guarantee.

"Why can't USB do low latency?" Its frame. 125 µs at high speed is a floor no controller reduces, because a device cannot be polled more often than once per microframe.

"Isn't USB 3.2's 20 Gbps faster than a PCIe lane?" On the wire. After protocol overhead and with a real driver, the useful figure is much lower — which is why the table in this design uses practical numbers, and why mutation J6, which substitutes the wire rate, scores 88,993.

"When would you put USB inside an enclosure?" When you want the isolation rather than the connector: a USB-attached management controller cannot read main memory, which is sometimes exactly the property you want in a subsystem you do not fully trust.

"How do you decide when two buses both fit?" You do not decide it technically. Cost, ecosystem, tooling, existing driver stacks, the team's experience. Saying so is the correct answer, and pretending otherwise is mutation J3.

"What about USB4?" It carries PCIe tunnelled, like Thunderbolt — so it inherits the same memory-access exposure, and the same IOMMU requirement. The clean "a USB device cannot reach memory" property belongs to USB 2.0 and 3.x, and it is worth being explicit about losing it.

"What is the requirement people forget to state?" Who is allowed to plug something in. It decides more architecture than bandwidth does, and it almost never appears in a requirements document.

13. UVM: A Constraint Solver as a Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Architecture selection expressed as constrained random plus a checker.
//
// The value is not that it picks buses. It is that a constraint solver
// asked to produce a requirement set that ONLY ONE bus satisfies will
// fail when no such set exists -- which tells you two buses are
// interchangeable for your whole requirement space, and that is a much
// more useful thing to learn than a recommendation.
typedef enum { BUS_USB, BUS_PCIE, BUS_TBOLT } bus_e;

typedef enum {
  FAIL_NONE, FAIL_BANDWIDTH, FAIL_LATENCY, FAIL_HOTPLUG,
  FAIL_EXTERNAL, FAIL_SECURITY, FAIL_ISOCH, FAIL_FANOUT
} fail_e;

class requirement_set extends uvm_sequence_item;
  `uvm_object_utils(requirement_set)

  rand int unsigned mbps;
  rand int unsigned latency_us;
  rand bit          hotplug;
  rand bit          external;
  rand bit          dma;
  rand bit          isoch;
  rand int unsigned devices;

  function new(string name = "requirement_set"); super.new(name); endfunction

  // Boundary values on purpose: each straddles a bus limit. An unbiased
  // draw would spend its time far from every decision.
  constraint c_rate    { mbps inside {100, 4000, 4001, 8000, 8001, 16000, 16001}; }
  constraint c_latency { latency_us inside {1, 2, 10, 125, 1000}; }
  constraint c_fanout  { devices inside {1, 6, 7, 127, 128}; }

  // Biased LOW, because an unbiased rate makes most requirement sets
  // impossible: 17452 of 20000 infeasible against 67 undetermined in the
  // first version of this bench, so the two classes that need a
  // SATISFIABLE set were barely exercised.
  constraint c_mostly_feasible { mbps dist { [100:4000] := 60, [4001:16001] := 40 }; }
endclass


class link_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(link_scoreboard)

  uvm_analysis_imp #(requirement_set, link_scoreboard) ap;

  // Practical figures, not marketing ones. These are what a design can
  // hold across a real cable with real overhead, which is the only kind
  // worth putting in a decision -- substituting the wire rate is
  // mutation J6 and it overcommits every link built on the result.
  localparam int USB_MBPS = 4000,  PCIE_MBPS = 8000,  TBOLT_MBPS = 16000;
  localparam int USB_LAT  = 125,   PCIE_LAT  = 2,     TBOLT_LAT  = 10;
  localparam int USB_FAN  = 127,   PCIE_FAN  = 1,     TBOLT_FAN  = 6;

  int unsigned n_query, n_infeasible, n_unique, n_undetermined;
  int unsigned n_unique_by [bus_e];

  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction

  // A violation SET per bus -- order-independent, deliberately. The reason
  // is then chosen by an explicit priority scan, so a wrong priority
  // ORDER is a different bug from a wrong fit decision and is caught
  // separately.
  function fail_e reason(bus_e b, requirement_set r);
    bit bw, lat, fan, ext, sec, iso;

    case (b)
      BUS_USB: begin
        // USB is the bus that PASSES the security test: a USB device
        // cannot reach host memory at all, which is why it is the right
        // answer for anything a stranger might plug in.
        bw  = r.mbps > USB_MBPS;
        lat = r.latency_us < USB_LAT;
        fan = r.devices > USB_FAN;
        if (fan) return FAIL_FANOUT;
        if (bw)  return FAIL_BANDWIDTH;
        if (lat) return FAIL_LATENCY;
      end
      BUS_PCIE: begin
        // PCIe's disqualifier is almost never bandwidth. It is that a
        // PCIe peer is a bus master with a view of host memory, so
        // exposing it outside the enclosure hands a stranger a DMA engine.
        sec = r.external && r.dma;
        ext = r.external;
        fan = r.devices > PCIE_FAN;
        bw  = r.mbps > PCIE_MBPS;
        lat = r.latency_us < PCIE_LAT;
        if (sec) return FAIL_SECURITY;
        if (ext) return FAIL_EXTERNAL;
        if (fan) return FAIL_FANOUT;
        if (bw)  return FAIL_BANDWIDTH;
        if (lat) return FAIL_LATENCY;
      end
      BUS_TBOLT: begin
        // Thunderbolt carries PCIe over an external cable, so it inherits
        // the exposure -- mitigated by an IOMMU, which is a SYSTEM
        // requirement rather than a property of the link.
        sec = r.dma;
        fan = r.devices > TBOLT_FAN;
        bw  = r.mbps > TBOLT_MBPS;
        lat = r.latency_us < TBOLT_LAT;
        iso = r.isoch;
        if (sec) return FAIL_SECURITY;
        if (fan) return FAIL_FANOUT;
        if (bw)  return FAIL_BANDWIDTH;
        if (lat) return FAIL_LATENCY;
        if (iso) return FAIL_ISOCH;
      end
    endcase
    return FAIL_NONE;
  endfunction

  function void write(requirement_set r);
    int unsigned fits = 0;
    bus_e        sole;

    n_query++;
    foreach (n_unique_by[b]) ;   // touch, so the map is populated

    for (bus_e b = b.first(); ; b = b.next()) begin
      if (reason(b, r) == FAIL_NONE) begin fits++; sole = b; end
      if (b == b.last()) break;
    end

    if (fits == 0) begin
      n_infeasible++;
      // NOT a failure of the tool. It is the tool's most actionable
      // output: a requirement has to be relaxed, and `reason` says which
      // one is binding for each bus.
      `uvm_info("LINK/INFEASIBLE",
        $sformatf("%0d Mbps at %0d us, dma=%0b external=%0b, %0d peers: no bus satisfies this. USB fails on %s, PCIe on %s, Thunderbolt on %s.",
                  r.mbps, r.latency_us, r.dma, r.external, r.devices,
                  reason(BUS_USB, r).name(), reason(BUS_PCIE, r).name(),
                  reason(BUS_TBOLT, r).name()), UVM_LOW)
    end else if (fits == 1) begin
      n_unique++;
      n_unique_by[sole]++;
    end else begin
      n_undetermined++;
      // The sentence this whole component exists to produce. Suppressing
      // it is mutation J3, and it is how an architecture review ends up
      // picking by habit and calling it engineering.
      `uvm_info("LINK/UNDETERMINED",
        $sformatf("%0d buses satisfy this requirement set: the choice is cost, ecosystem or tooling, and NOT a technical decision",
                  fits), UVM_LOW)
    end
  endfunction

  function void check_phase(uvm_phase phase);
    super.check_phase(phase);

    `uvm_info("LINK",
      $sformatf("%0d queries | %0d infeasible | %0d unique | %0d undetermined",
                n_query, n_infeasible, n_unique, n_undetermined), UVM_LOW)

    // ---- the coverage checks, and the second is the interesting one ----
    //
    // A run in which nothing was ever infeasible or ever undetermined has
    // only exercised the easy middle of the requirement space.
    if (n_infeasible == 0)
      `uvm_error("LINK/COV",
        "no requirement set was ever infeasible: the tool's most actionable output was never produced")
    if (n_undetermined == 0)
      `uvm_error("LINK/COV",
        "no requirement set was ever satisfied by more than one bus: the 'not a technical decision' path was never exercised")

    // And this one is an architecture finding rather than a test result.
    // A bus that is NEVER the unique answer is a bus this product does
    // not need -- which is worth knowing before it appears on a schematic.
    foreach (n_unique_by[b])
      if (n_unique_by[b] == 0)
        `uvm_warning("LINK/REDUNDANT",
          $sformatf("%s was never the sole option across the whole requirement space: nothing in this product requires it",
                    b.name()))
  endfunction
endclass

14. Common Misconceptions

"Pick the bus with the most bandwidth." Bandwidth decides fewest real cases. Memory access decides most.

"PCIe is just faster USB." A PCIe peer is a bus master with a view of host memory. A USB device cannot touch memory at all. They are not the same kind of thing.

"Thunderbolt is USB-C." USB-C is a connector. Thunderbolt is PCIe tunnelled over it, and it inherits PCIe's exposure.

"USB is slow." USB 3.2 is faster than a single PCIe gen3 lane. What USB lacks is low latency and bus mastering.

"USB latency can be tuned." The frame is a floor. 125 µs at high speed, and no controller reduces it.

"Use the wire rate for budgeting." Use the practical rate. Mutation J6 substitutes the wire rate and scores 88,993.

"An IOMMU makes external PCIe safe." It makes it manageable, and it is a system requirement you have just created. Name it.

"USB4 keeps USB's isolation." It tunnels PCIe. The clean "cannot reach memory" property belongs to USB 2.0 and 3.x.

"If several buses fit, pick the best one." There is no best. Say the decision is not technical — that is the answer.

"Nothing fits, so the requirements are wrong." The requirements are infeasible, which is different and actionable: the output tells you which constraint is binding.

15. Exercises

1. For each of the ten roles in section 11, state the binding constraint without mentioning bandwidth. Where you cannot, say why bandwidth genuinely is the constraint.

2. J1 is the most consequential mutation in this module and scores second-lowest. Explain the relationship between a defect's cost and its detectability, using J1 and J7 as the two ends.

3. Construct a requirement set that only USB satisfies, one that only PCIe satisfies, and one that only Thunderbolt satisfies. Then construct one that none satisfies and name the constraint you would relax first.

4. The random phase was 87% infeasible before its distribution was biased. Explain what that cost, and design a self-check on the stimulus that would have flagged it.

5. J5 reverses the priority of the reasons and leaves every fit decision correct. Explain why a wrong reason is a real defect, with a scenario in which it costs a week.

6. Add USB4 as a fourth option. Give its capability row and say which existing answers in section 11 it changes.

7. The scoreboard warns when a bus is never the unique answer. Extend it to report the minimum set of buses that covers a given requirement space, and say what an architect does with that.

16. Summary

IdeaWhy it matters
Which constraint will you not relaxnot "which bus is better"
Memory access is the deciding axisbandwidth decides fewest real cases
A USB device cannot reach memorywhich is why a stranger may plug it in
A PCIe peer is a bus masterwhich is why it is fast, and why it stays inside
Thunderbolt is PCIe on a cableit inherits the exposure and needs an IOMMU
USB's 125 µs frame is a floorno controller reduces it
Use practical rates, not wire ratesJ6 substitutes the wire rate and scores 88,993
n_fit == 0 is actionableit says which constraint is binding
n_fit > 1 means not a technical decisionsaying so is the answer
Report the most fundamental reasonnot the last one the chain happened to reach
An answer that creates a requirement must name it"Thunderbolt, and now you need an IOMMU"
A model must be a different formulationa chain checked by a chain shares its bug
A typed variable with an initialiser is one-shot53,533 bad fits from one =
A random distribution's shape is part of the test87% infeasible tested almost nothing
A one-count directed difference is a real signalit was a missing assertion
2800 states, 7 mutations, 3 languages0 bad fits in 387,828 checks

Tooling

StepCommand
Verilog-2005iverilog -g2005 -o bs_v.out bs_v.v bs_v_tb.v && ./bs_v.out
SystemVerilogiverilog -g2012 -o bs_sv.out bs_sv.sv bs_sv_tb.sv && ./bs_sv.out
VHDL-2008 analysenvc --std=2008 -a bs_vhdl.vhd bs_vhdl_tb.vhd
VHDL-2008 elaboratenvc --std=2008 -e tb_bs_vhdl
VHDL-2008 runnvc --std=2008 -r tb_bs_vhdl
One mutationiverilog -g2005 -DMUT_J1 -o mm bs_v_mut.v bs_v_tb.v && ./mm
Directed only (Verilog)iverilog -g2005 -DDIRECTED_ONLY -o mm bs_v_mut.v bs_v_tb.v && ./mm
Directed only (VHDL)nvc --std=2008 -e -gDIRECTED_ONLY=true tb_bs_vhdl

All three implementations pass with 0 errors: all 2800 combinations of rate, latency, hot-plug, external, DMA, isochronous and fanout — reached by directed stimulus alone, using boundary values throughout; seven real-role scenarios checked for their reason rather than their answer; one scenario per bus in which it is the sole option; zero bad fits and zero bad rejections in 387,828 checks; and every one of the seven mutations killed by directed stimulus alone, with all seven directed scores identical across languages.


17. Module 27 in One Page

Ten questions, ten designs, and one thing underneath all of them.

ChapterThe questionThe answer that gets hired
27.1What is USB?it is host-scheduled; everything else follows
27.2Name the rolesa hub is a repeater, not a switch
27.3Walk an enumerationSET_ADDRESS applies after the status stage
27.4What is an endpoint?(number, direction) is the identifier
27.5Which transfer type?guaranteed bandwidth is no retries
27.6How is a frame scheduled?periodic first; bulk round-robin on the rest
27.7Draw an xHCI controllerthe cycle bit, and full-versus-empty without a counter
27.8Design a UVM environmenta checker that fails on weak stimulus
27.9Debug this tracenarrow by what the evidence excludes
27.10USB, PCIe or Thunderbolt?which constraint will you not relax

Every one of these questions has a stock answer that is correct and does not get the job. The stock answers are lists: four transfer types, sixteen endpoints, the UVM component set, the xHCI block diagram. Lists are what somebody who has read about USB produces.

What the senior answers have in common is that each replaces a list with one load-bearing idea and its consequences. Host scheduling explains polling, NAK, framing and the absent interrupt line. The cycle bit explains lock-free sharing and how full is told from empty. "Which constraint will you not relax" explains why there is no best bus.

And the module's verification has one thing in common too, which is the same idea pointed inward: a result is only a result if the run could have produced a different one. Six of the seventy mutations in these ten chapters had a random contribution of exactly zero. Three scored zero directed until a missing dimension, a missing property or a missing phase was found. Two benches were caught agreeing with a defect — one through a self-referential check, one through a shadow that shared the design's own formulation.

None of those was found by running longer.

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.