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.
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.
| USB | PCIe | Thunderbolt | |
|---|---|---|---|
| Peer can read host memory | no | yes | yes |
| Practical throughput | ~4 Gbps | ~8 Gbps/lane | ~16 Gbps |
| Worst-case latency | 125 µs (a frame) | ~2 µs | ~10 µs |
| Peers on one port | 127 | 1 (without a switch) | 6 |
| Leaves the enclosure | yes, by design | no | yes |
| Isochronous guarantee | yes | no | no |
| Hot-plug | mature | possible, awkward | yes |
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:
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
Four roles, four different binding constraints
4. Seven Properties
| # | Property |
|---|---|
| 1 | A bus fits if and only if no requirement of its is violated. |
| 2 | A bus is rejected if and only if some requirement is violated. |
| 3 | The reported reason is the most fundamental violation, not the last one found. |
| 4 | n_fit is the number of fitting buses. |
| 5 | infeasible ⟺ n_fit == 0. |
| 6 | undetermined ⟺ n_fit > 1. |
| 7 | With 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
// =====================================================================
// 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
endmodule6. SystemVerilog RTL
// =====================================================================
// 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
endmodule7. VHDL-2008 RTL
-- =====================================================================
-- 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.
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
// =====================================================================
// 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
endmoduleSystemVerilog testbench
// =====================================================================
// 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
endmoduleVHDL-2008 testbench
-- =====================================================================
-- 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
| Measure | Verilog | SystemVerilog | VHDL |
|---|---|---|---|
| (rate × latency × hotplug × external × DMA × isoch × fanout) reached | 2800 / 2800 | 2800 / 2800 | 2800 / 2800 |
| …reached by directed stimulus alone | 2800 / 2800 | 2800 / 2800 | 2800 / 2800 |
| real-role scenarios | 7 | 7 | 7 |
| unique-fit scenarios (one per bus) | 3 / 3 | 3 / 3 | 3 / 3 |
| Steps | 22818 | 22818 | 22818 |
| Checks executed | 387828 | 387828 | 387828 |
| queries | 20000 | 20000 | 20000 |
| infeasible | 9153 | 9153 | 8753 |
| exactly one fit | 7747 | 7747 | 8177 |
| undetermined | 3100 | 3100 | 3070 |
| bad fits | 0 | 0 | 0 |
| bad rejections | 0 | 0 | 0 |
| Result | PASS | PASS | PASS |
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.
| # | Mutation | Verilog | SysVer | VHDL |
|---|---|---|---|---|
| J7 | the PCIe fanout limit is dropped — one port for forty peripherals | 118699 | 118699 | 120510 |
| J2 | USB's latency floor is treated as tunable | 95043 | 95043 | 95091 |
| J6 | a bus limit taken from the wire rate, not the practical one | 88993 | 88993 | 89585 |
| J5 | the reported reason is the least fundamental violation | 14095 | 14095 | 13536 |
| J4 | an infeasible requirement set is not flagged | 11509 | 11509 | 11109 |
| J1 | PCIe offered across an external link WITH DMA | 4158 | 4158 | 4001 |
| J3 | an undetermined answer is reported as a decision | 3175 | 3175 | 3145 |
| — | unmutated baseline | 0 | 0 | 0 |
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 all | V directed | V random | VHDL all | VHDL directed | VHDL random |
|---|---|---|---|---|---|---|
| J1 | 4158 | 702 | 3456 | 4001 | 702 | 3299 |
| J2 | 95043 | 12662 | 82381 | 95091 | 12662 | 82429 |
| J3 | 3175 | 75 | 3100 | 3145 | 75 | 3070 |
| J4 | 11509 | 2356 | 9153 | 11109 | 2356 | 8753 |
| J5 | 14095 | 1975 | 12120 | 13536 | 1975 | 11561 |
| J6 | 88993 | 7744 | 81249 | 89585 | 7744 | 81841 |
| J7 | 118699 | 13454 | 105245 | 120510 | 13454 | 107056 |
| — | BASE 0 | 0 | 0 | 0 | 0 | 0 |
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.
| Role | Answer | The binding constraint |
|---|---|---|
| Keyboard, mouse | USB | external + a stranger plugs it in ⇒ the peer must not reach memory |
| Internal NVMe drive | PCIe | DMA is the point, and it never leaves the enclosure |
| External GPU | Thunderbolt, with an IOMMU | nothing else has the bandwidth; the IOMMU is a system requirement, not a link property |
| USB audio interface | USB | isochronous delivery, which neither of the others offers |
| Webcam | USB | isochronous, and a late frame is worthless |
| 40 peripherals on one port | USB | fanout of 127 against PCIe's 1 |
| Internal accelerator, 2 µs deadline | PCIe | USB's 125 µs frame floor rules it out on latency alone |
| Firmware update port on a shipped product | USB | the peer must not be able to read memory, at any speed |
| Docking station | Thunderbolt | it carries PCIe, USB and DisplayPort on one cable |
| 20 Gbps at 1 µs | none | infeasible; 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
// 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
endclass14. 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
| Idea | Why it matters |
|---|---|
| Which constraint will you not relax | not "which bus is better" |
| Memory access is the deciding axis | bandwidth decides fewest real cases |
| A USB device cannot reach memory | which is why a stranger may plug it in |
| A PCIe peer is a bus master | which is why it is fast, and why it stays inside |
| Thunderbolt is PCIe on a cable | it inherits the exposure and needs an IOMMU |
| USB's 125 µs frame is a floor | no controller reduces it |
| Use practical rates, not wire rates | J6 substitutes the wire rate and scores 88,993 |
n_fit == 0 is actionable | it says which constraint is binding |
n_fit > 1 means not a technical decision | saying so is the answer |
| Report the most fundamental reason | not 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 formulation | a chain checked by a chain shares its bug |
| A typed variable with an initialiser is one-shot | 53,533 bad fits from one = |
| A random distribution's shape is part of the test | 87% infeasible tested almost nothing |
| A one-count directed difference is a real signal | it was a missing assertion |
| 2800 states, 7 mutations, 3 languages | 0 bad fits in 387,828 checks |
Tooling
| Step | Command |
|---|---|
| Verilog-2005 | iverilog -g2005 -o bs_v.out bs_v.v bs_v_tb.v && ./bs_v.out |
| SystemVerilog | iverilog -g2012 -o bs_sv.out bs_sv.sv bs_sv_tb.sv && ./bs_sv.out |
| VHDL-2008 analyse | nvc --std=2008 -a bs_vhdl.vhd bs_vhdl_tb.vhd |
| VHDL-2008 elaborate | nvc --std=2008 -e tb_bs_vhdl |
| VHDL-2008 run | nvc --std=2008 -r tb_bs_vhdl |
| One mutation | iverilog -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.
| Chapter | The question | The answer that gets hired |
|---|---|---|
| 27.1 | What is USB? | it is host-scheduled; everything else follows |
| 27.2 | Name the roles | a hub is a repeater, not a switch |
| 27.3 | Walk an enumeration | SET_ADDRESS applies after the status stage |
| 27.4 | What is an endpoint? | (number, direction) is the identifier |
| 27.5 | Which transfer type? | guaranteed bandwidth is no retries |
| 27.6 | How is a frame scheduled? | periodic first; bulk round-robin on the rest |
| 27.7 | Draw an xHCI controller | the cycle bit, and full-versus-empty without a counter |
| 27.8 | Design a UVM environment | a checker that fails on weak stimulus |
| 27.9 | Debug this trace | narrow by what the evidence excludes |
| 27.10 | USB, 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
Related tutorials
- Related topic
What Is USB?
The opening interview question answered with one load-bearing idea instead of a list — USB is host-scheduled, and polling, NAK, the frame and the missing interrupt line are all consequences of it.
- Related topic
Host / Device / Hub Identification
Host and device take fifteen seconds; the hub is where the interview is decided — a hub is a repeater, not a switch, and no downstream port can ever reach another one.
- Related topic
The Enumeration Question
Attach to configured, with the one step almost everybody gets backwards — SET_ADDRESS takes effect after the status stage, and a device that switches early is invisible to the host.
- Related topic
The Endpoints Question
Endpoint 1 IN and endpoint 1 OUT are two different endpoints — separate buffers, toggles, halt states and packet sizes — and a table indexed by number alone is a device where halting a read kills its writes.
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.
