USB · Module 31
“USB Devices Initiate Transfers”
A mouse appears to send, and the transfer type is literally called interrupt — so the belief has two strong supports. Its prediction is that a device with data can put a transaction on an idle bus, and a sixty-line block makes that impossible.
1. The Belief
"When a device has something to report, it sends it. That is what an interrupt transfer is — the device interrupts the host. Otherwise how would a mouse work?"
2. Why An Intelligent Engineer Believes It
Two supports, and the second is a naming accident that the specification is stuck with.
THE OBSERVATION
A mouse moves and the cursor moves. Nothing visible asked it to.
Every user-facing description says the mouse "sends" its position,
and every operating system exposes it as an event that arrives.
THE NAME
The transfer type is called an INTERRUPT transfer. In every other
context an engineer meets that word -- a CPU, a peripheral, a
signal line -- an interrupt is raised BY the thing that has news,
and it arrives unbidden at the thing that must react.3. The Prediction It Makes
IF DEVICES INITIATED TRANSFERS, THEN:
1 an idle bus with a device that has data would not stay idle
2 a device would need a way to contend for the bus with other
devices -- so there would be arbitration, collision detection or
a grant mechanism somewhere in the protocol
3 NAK would be meaningless. A device that speaks when it has
something to say never needs to answer "not yet"
4 the host would need to be ready to receive at any moment, rather
than at moments it chose
5 bandwidth could not be reserved at configuration time, because
the host would not control when anything happenedPrediction 3 is the sharpest, and it is the one to hold on to: the existence of NAK is by itself almost a proof.
4. The Counterexample
Put an analyser on an idle bus with a mouse attached and stop moving the mouse. Then move it.
OBSERVED
o the bus is NOT idle. The host is issuing IN tokens to the
mouse's endpoint at its configured interval, and the mouse is
answering NAK to every one of them -- "nothing for you"
o when the mouse moves, the NEXT token gets a data packet. Not the
instant of the movement: the next token
o the latency between moving the mouse and the data appearing is
therefore bounded by the POLLING INTERVAL, and is visible as
such on the trace
o nothing the mouse does changes when the tokens arrivePrediction 1 fails on the first line. Prediction 3 fails on the second: NAK is not an error and not an edge case — on an idle mouse it is the overwhelmingly common answer, and it exists precisely because the host asks whether or not there is anything to collect.
What actually happens between a device event and the data appearing
5. The Corrected Model
HAVING WORK is a property of the DEVICE
STARTING A TRANSACTION is an authority held by the HOST
They are different things, and the device has exactly one of them.
What a device controls:
o WHETHER it has data -- the pending bit
o WHICH answer it gives -- DATA, NAK or STALL
o HOW FAST it can be polled -- negotiated in the descriptors
at configuration time
What a device does not control:
o WHEN anything happens on the busThat last line is what makes USB's scheduling possible at all. A host that controls every transaction's timing can reserve bandwidth, guarantee service intervals and plan a frame — which is 31.4's subject. A bus where devices spoke when they liked could do none of it.
6. The Hardware Contract
Small enough to hold in your head, and its job is to make the distinction physical.
PURPOSE hold the device's readiness, and answer the host when
asked. Nothing else.
INPUTS clk, rst_n
ev, ev_data the function produced a byte
token_valid the host addressed this endpoint
token_is_in IN (host wants data) or OUT
OUTPUTS resp_valid we are driving a response
resp_is_data DATA if 1, a handshake if 0
resp_data
pending observable device state
five counters observation only
AUTHORITATIVE STATE
pend_r one bit: is there something to give
data_r the byte
DERIVED resp_valid = token_valid <-- THE LINE
resp_is_data = token_valid && token_is_in && pend_r
RESET rst_n clears everything
PRIORITY, SAME CYCLE
an event and a token together: the device answers with
the state it HAD when the token arrived, and the event
is kept. Data produced after the request was sampled
does not appear in the response to that request.
LATENCY the response is combinational in the token; the state
is registered.
BOUNDARY a token with nothing pending is answered, with NAK.
A token while pending is answered with data and
consumes it. An event and a consume together leave the
endpoint pending with the NEW byte.
ASSUMPTIONS the layer below delivers exactly one token per cycle
and the response is consumed in the same cycle.
OMISSIONS the PHY, the serial interface engine, packet framing,
CRC, the data toggle (29.5), endpoint 0, descriptors,
and any notion of a real token on a real wire.
MISCONCEPTION DEMONSTRATED
"a device with data can start a transaction"usb_ep_pending.v — the design, Verilog-2005
// =====================================================================
// usb_ep_pending -- the device side of "who starts a transaction".
//
// CLASSIFICATION: simplified synthesisable teaching RTL, built for one
// purpose: to make the difference between HAVING WORK and BEING ABLE
// TO START A TRANSACTION a fact about hardware rather than a sentence
// in a tutorial.
//
// It is NOT a USB device controller. There is no PHY, no serial
// interface engine, no packet framing, no CRC, no data toggle (29.5
// owns that), no endpoint 0 machine and no descriptors. Everything is
// reduced to two questions asked once per cycle:
//
// does this endpoint have something to say? ev / pending
// has the host addressed it? token_valid
//
// THE INVARIANT THIS MODULE EXISTS TO MAKE VISIBLE
// ------------------------------------------------
// resp_valid |-> token_valid
//
// The device puts NOTHING on the bus unless the host addressed it.
// Not when it has data. Not when it has a lot of data. Not when the
// bus is idle. The `pending` bit and the `resp_valid` output are wired
// to different things ON PURPOSE, and n_unsolicited counts the cycles
// in which that separation was violated -- which, in a correct build,
// is structurally impossible and therefore always zero.
//
// A zero that is structurally guaranteed looks like a useless counter.
// It is the whole point: section 8 turns the misconception into a
// one-line mutation and the counter is what fires.
// =====================================================================
module usb_ep_pending (
input wire clk,
input wire rst_n,
// ---- the device side: work arrives here ----
// One cycle: firmware (or the function logic) has produced a byte it
// would like the host to collect.
input wire ev,
input wire [7:0] ev_data,
// ---- the host side: permission arrives here ----
// One cycle: the host has issued a token addressed to this endpoint.
input wire token_valid,
// 1 = IN (the host is asking this endpoint for data)
// 0 = OUT (the host is delivering data to this endpoint)
input wire token_is_in,
// ---- what this endpoint puts on the bus ----
output wire resp_valid, // we are driving a response, this cycle
output wire resp_is_data, // DATA if 1, a handshake if 0
output wire [7:0] resp_data,
// ---- observable device state ----
output wire pending,
output wire [15:0] n_events,
output wire [15:0] n_tokens,
output wire [15:0] n_data,
output wire [15:0] n_nak,
// Responses driven without a token. Structurally impossible here.
output wire [15:0] n_unsolicited
);
reg pend_r;
reg [7:0] data_r;
reg [15:0] c_ev, c_tok, c_data, c_nak, c_uns;
// The response is a function of the TOKEN, and of the endpoint state
// only insofar as it decides WHAT to answer -- never WHETHER to.
//
// Read the two lines below as the architecture: the first says the
// host decides that something happens; the second says the device
// decides what it is.
assign resp_valid = token_valid;
assign resp_is_data = token_valid && token_is_in && pend_r;
assign resp_data = data_r;
assign pending = pend_r;
// The device answers with the state it HAD when the token arrived.
// An event landing in the same cycle as an IN token does not rescue
// it: the data was not ready when the host asked, so the honest
// answer is NAK and the event is kept for the next token. Making the
// event win instead would mean a byte produced after the request was
// sampled appearing in the response to that request.
wire take_in = token_valid && token_is_in;
wire take_out = token_valid && !token_is_in;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pend_r <= 1'b0;
data_r <= 8'd0;
c_ev <= 16'd0;
c_tok <= 16'd0;
c_data <= 16'd0;
c_nak <= 16'd0;
c_uns <= 16'd0;
end else begin
// An IN token that found data consumes it. The event, if one is
// arriving this cycle, is captured regardless -- so a consume and
// a produce in the same cycle leave the endpoint pending again,
// with the NEW byte.
if (ev) begin
pend_r <= 1'b1;
data_r <= ev_data;
c_ev <= c_ev + 16'd1;
end else if (take_in && pend_r) begin
pend_r <= 1'b0;
end
if (token_valid) c_tok <= c_tok + 16'd1;
if (take_in && pend_r) c_data <= c_data + 16'd1;
if (take_in && !pend_r) c_nak <= c_nak + 16'd1;
if (resp_valid && !token_valid) c_uns <= c_uns + 16'd1;
if (take_out) ; // accepted; 29.5 owns what happens next
end
end
assign n_events = c_ev;
assign n_tokens = c_tok;
assign n_data = c_data;
assign n_nak = c_nak;
assign n_unsolicited = c_uns;
endmodule7. The Testbench, And Its First Phase
Phase 1 is unlike any other phase in this curriculum and it is why the specimen exists. It does not compare the design to a reference model. It states the claim and requires it:
fill the endpoint with sixteen bytes of work
then do nothing for two hundred cycles
and require, EVERY CYCLE, that the bus stays silent task intent_check; begin
bump;
if (resp_valid && !token_valid) begin
err = err + 1;
$display(" ** INTENT VIOLATED: the endpoint drove the bus with no token ...");
end
end endtaskThat check consults no model, which is the point. 30.2 established why: a reference model written by the design's author from the design's own sentence is one artefact wearing two hats, and 30.4's specimen agreed with its own bug for 39,108 checks. A claim stated in its own terms can disagree with both.
tb_usb_ep_pending.v — the testbench, Verilog-2005
// =====================================================================
// tb_usb_ep_pending -- Verilog-2005 testbench for usb_ep_pending.
//
// PHASE 1 is unlike any other phase in this curriculum and it is the
// reason the module exists. It does not compare the design to a model.
// It states the architectural claim directly and requires it:
//
// however much work this endpoint has, and for however long,
// NOTHING APPEARS ON THE BUS until the host addresses it.
//
// That check cannot be satisfied by a reference model that shares the
// design's structure, because it does not consult a model at all. 30.2
// established why that matters: a model written by the design's author
// from the design's sentence is one artefact wearing two hats, and
// 30.4's specimen agreed with its own bug for 39,108 checks.
//
// PHASES
// 1 INTENT the silence claim, stated and required directly
// 2 EXHAUSTIVE 16 combinations: 4 binary axes, all reachable
// 3 SCENARIO the named cases, including the same-cycle collision
// 4 RANDOM supplementary, and audited for what it reaches
// =====================================================================
`timescale 1ns/1ps
module tb_usb_ep_pending;
reg clk = 1'b0;
reg rst_n;
reg ev;
reg [7:0] ev_data;
reg token_valid, token_is_in;
wire resp_valid, resp_is_data, pending;
wire [7:0] resp_data;
wire [15:0] n_events, n_tokens, n_data, n_nak, n_unsolicited;
usb_ep_pending dut (
.clk(clk), .rst_n(rst_n),
.ev(ev), .ev_data(ev_data),
.token_valid(token_valid), .token_is_in(token_is_in),
.resp_valid(resp_valid), .resp_is_data(resp_is_data),
.resp_data(resp_data), .pending(pending),
.n_events(n_events), .n_tokens(n_tokens), .n_data(n_data),
.n_nak(n_nak), .n_unsolicited(n_unsolicited)
);
always #5 clk = ~clk;
// ---- the independent reference model ---------------------------
// Written from the ARCHITECTURAL RULES, in the order a specification
// would state them, not from the RTL's expressions:
// R1 a response happens if and only if the host issued a token
// R2 an IN token is answered with DATA if the endpoint held data
// WHEN THE TOKEN ARRIVED, and with NAK otherwise
// R3 an event makes the endpoint pending and replaces its byte
// R4 a consumed byte clears pending unless an event replaces it
reg rm_pend;
reg [7:0] rm_data;
reg [15:0] rm_ev, rm_tok, rm_dat, rm_nak, rm_uns;
integer chk_dir, chk_rnd, err, in_random;
integer m_ev, m_tok, m_in, m_out, m_data_resp, m_nak_resp,
m_collide, m_pend_cycles, m_silent_cycles, m_setupfail;
integer i, j, a, b, c, d;
task bump; begin
if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
end endtask
task ck;
input [255:0] what;
input [31:0] got;
input [31:0] exp;
begin
bump;
if (got !== exp) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %0s: got %0d expected %0d (t=%0t)", what, got, exp, $time);
end
end
endtask
task ref_step;
reg t_in, t_out, consumed;
begin
if (!rst_n) begin
rm_pend = 0; rm_data = 0;
rm_ev = 0; rm_tok = 0; rm_dat = 0; rm_nak = 0; rm_uns = 0;
end else begin
t_in = token_valid && token_is_in;
t_out = token_valid && !token_is_in;
// R2 uses the value of pending BEFORE this edge
consumed = t_in && rm_pend;
if (token_valid) rm_tok = rm_tok + 1;
if (consumed) rm_dat = rm_dat + 1;
if (t_in && !rm_pend) rm_nak = rm_nak + 1;
// R3 beats R4: an event in the same cycle as a consume leaves
// the endpoint pending with the NEW byte
if (ev) begin
rm_pend = 1; rm_data = ev_data; rm_ev = rm_ev + 1;
end else if (consumed) begin
rm_pend = 0;
end
// tallies
if (ev) m_ev = m_ev + 1;
if (t_in) m_in = m_in + 1;
if (t_out) m_out = m_out + 1;
if (consumed) m_data_resp = m_data_resp + 1;
if (t_in && !consumed) m_nak_resp = m_nak_resp + 1;
if (ev && token_valid) m_collide = m_collide + 1;
end
end
endtask
// What the endpoint put on the bus THIS cycle, captured while the
// token is still driven. The scenarios inspect these rather than the
// live outputs, which are only meaningful before the edge.
reg cap_valid, cap_isdata;
reg [7:0] cap_data;
// Pre-edge: the response, compared against the model's state AS IT IS
// WHEN THE TOKEN ARRIVES -- which is the architectural rule.
task cmp_comb;
reg exp_valid, exp_isdata;
begin
exp_valid = token_valid;
exp_isdata = token_valid && token_is_in && rm_pend;
cap_valid = resp_valid;
cap_isdata = resp_is_data;
cap_data = resp_data;
ck("resp_valid", {31'd0, resp_valid}, {31'd0, exp_valid});
ck("resp_is_data", {31'd0, resp_is_data}, {31'd0, exp_isdata});
ck("pending", {31'd0, pending}, {31'd0, rm_pend});
if (exp_isdata)
ck("resp_data", {24'd0, resp_data}, {24'd0, rm_data});
else bump;
if (rm_pend) m_pend_cycles = m_pend_cycles + 1;
if (!token_valid) m_silent_cycles = m_silent_cycles + 1;
end
endtask
// Post-edge: the registered tallies.
task cmp_regs; begin
ck("n_events", {16'd0, n_events}, {16'd0, rm_ev});
ck("n_tokens", {16'd0, n_tokens}, {16'd0, rm_tok});
ck("n_data", {16'd0, n_data}, {16'd0, rm_dat});
ck("n_nak", {16'd0, n_nak}, {16'd0, rm_nak});
ck("n_unsolicited",{16'd0, n_unsolicited},32'd0);
end endtask
// The INTENT check, run every cycle of every phase. It consults no
// model: it is the architectural claim, asserted directly.
task intent_check; begin
bump;
if (resp_valid && !token_valid) begin
err = err + 1;
$display(" ** INTENT VIOLATED: the endpoint drove the bus with no token (t=%0t, pending=%0d)",
$time, pending);
end
end endtask
task step; begin
#1;
cmp_comb;
intent_check;
@(posedge clk);
ref_step;
#1;
cmp_regs;
ev = 0; ev_data = 0; token_valid = 0; token_is_in = 0;
end endtask
task idle; begin step; end endtask
task hard_reset; begin
rst_n = 0; ev = 0; ev_data = 0; token_valid = 0; token_is_in = 0;
repeat (3) begin @(posedge clk); ref_step; end
#1; rst_n = 1;
@(posedge clk); ref_step; #1; cmp_regs;
end endtask
task device_event; input [7:0] v;
begin ev = 1; ev_data = v; step; end
endtask
task in_token; begin token_valid = 1; token_is_in = 1; step; end endtask
task out_token; begin token_valid = 1; token_is_in = 0; step; end endtask
// -----------------------------------------------------------------
// PHASE 1 -- THE SILENCE CLAIM.
//
// Fill the endpoint with work and then do nothing for a long time.
// If the misconception were true -- if a device with data could
// start a transaction -- something would appear. Nothing does, and
// this phase requires it every cycle rather than inferring it.
// -----------------------------------------------------------------
integer silent_run;
task phase_intent;
begin
hard_reset;
silent_run = 0;
// sixteen events, no tokens at all
for (i = 0; i < 16; i = i + 1) begin
device_event(8'hA0 + i[7:0]);
ck("still pending", {31'd0, pending}, 32'd1);
ck("and still silent", {31'd0, cap_valid}, 32'd0);
end
// then two hundred idle cycles with the endpoint full
for (i = 0; i < 200; i = i + 1) begin
idle;
ck("silent while pending", {31'd0, cap_valid}, 32'd0);
silent_run = silent_run + 1;
end
ck("nothing unsolicited", {16'd0, n_unsolicited}, 32'd0);
ck("no data was sent", {16'd0, n_data}, 32'd0);
ck("no tokens arrived", {16'd0, n_tokens}, 32'd0);
// and the moment the host asks, the data is there
in_token;
ck("now it answers", {31'd0, cap_valid}, 32'd1);
ck("and it is DATA", {31'd0, cap_isdata}, 32'd1);
ck("the newest byte", {24'd0, cap_data}, {24'd0, 8'hA0 + 8'd15});
// The scenario must have HAPPENED: 216 cycles of held work.
bump;
if (silent_run < 200) begin
err = err + 1;
$display(" ** intent: only %0d silent cycles", silent_run);
end
end
endtask
// -----------------------------------------------------------------
// PHASE 2 -- the exhaustive sweep.
//
// AXES, named so that what is absent is visible:
// pending before the edge 0, 1 2
// a device event this cycle 0, 1 2
// a token this cycle 0, 1 2
// token direction OUT, IN 2
// ------------------------------------------------------------
// 2^4 = 16
//
// All sixteen are reachable: the four inputs come from two agents
// that do not constrain each other, which is the architectural fact
// the chapter is about.
//
// WHAT THIS SWEEP DOES NOT CONTAIN: history longer than one edge.
// Two tokens in a row, and an event between two tokens, are phase 3.
// An exhaustive sweep is exhaustive over the axes it has -- 30.2 §10.
// -----------------------------------------------------------------
task setup_pending;
input want;
begin
hard_reset;
if (want) begin
device_event(8'h5A);
bump;
if (pending !== 1'b1) begin
err = err + 1; m_setupfail = m_setupfail + 1;
$display(" ** setup: pending was not established");
end
end else begin
bump;
if (pending !== 1'b0) begin
err = err + 1; m_setupfail = m_setupfail + 1;
$display(" ** setup: pending was not clear");
end
end
end
endtask
task phase_sweep;
begin
for (a = 0; a < 2; a = a + 1) // pending
for (b = 0; b < 2; b = b + 1) // event
for (c = 0; c < 2; c = c + 1) // token
for (d = 0; d < 2; d = d + 1) // direction
begin
setup_pending(a[0]);
ev = b[0];
ev_data = 8'hC0 + {4'd0, a[0], b[0], c[0], d[0]};
token_valid = c[0];
token_is_in = d[0];
step;
idle;
end
end
endtask
// -----------------------------------------------------------------
// PHASE 3 -- the named scenarios.
// -----------------------------------------------------------------
task phase_scenarios;
begin
// S1 a token with nothing pending is answered, and answered NAK.
// The misconception has no account of this: if devices spoke
// when they had something to say, there would be no reason to
// have a way of saying "not yet".
hard_reset;
in_token;
ck("S1 the device answered", {31'd0, cap_valid}, 32'd1);
ck("S1 but not with data", {31'd0, cap_isdata}, 32'd0);
ck("S1 counted as a NAK", {16'd0, n_nak}, 32'd1);
// S2 an event and an IN token in the SAME cycle. The device
// answers with the state it had when the token arrived, so the
// answer is NAK -- and the byte is kept for the next token.
hard_reset;
ev = 1; ev_data = 8'h77; token_valid = 1; token_is_in = 1; step;
ck("S2 answered NAK", {31'd0, cap_isdata}, 32'd0);
ck("S2 the event was kept", {31'd0, pending}, 32'd1);
in_token;
ck("S2 next token gets data", {31'd0, cap_isdata}, 32'd1);
ck("S2 and it is the byte", {24'd0, cap_data}, {24'd0, 8'h77});
// S3 back-to-back IN tokens: the first gets the data, the second
// gets a NAK. The host asks twice; the device has one byte.
hard_reset;
device_event(8'h11);
in_token;
ck("S3 first token: data", {31'd0, cap_isdata}, 32'd1);
in_token;
ck("S3 second token: NAK", {31'd0, cap_isdata}, 32'd0);
ck("S3 one data, one NAK", {16'd0, n_data + n_nak}, 32'd2);
// S4 consume and produce in the same cycle: the endpoint is
// pending again immediately, with the NEW byte.
hard_reset;
device_event(8'h22);
ev = 1; ev_data = 8'h33; token_valid = 1; token_is_in = 1; step;
ck("S4 data was sent", {31'd0, cap_isdata}, 32'd1);
ck("S4 still pending", {31'd0, pending}, 32'd1);
in_token;
ck("S4 the new byte", {24'd0, cap_data}, {24'd0, 8'h33});
// S5 an OUT token is also the host's move. The direction changes
// who carries the data; it does not change who starts.
hard_reset;
out_token;
ck("S5 the device answered", {31'd0, cap_valid}, 32'd1);
ck("S5 not an IN response", {31'd0, cap_isdata}, 32'd0);
ck("S5 no NAK counted", {16'd0, n_nak}, 32'd0);
// S6 a long run of events with no tokens, then one token.
// Sixty-four bytes of work produce exactly zero bus activity.
hard_reset;
for (i = 0; i < 64; i = i + 1) device_event(8'h40 + i[7:0]);
ck("S6 64 events", {16'd0, n_events}, 32'd64);
ck("S6 zero bus activity", {16'd0, n_tokens}, 32'd0);
ck("S6 nothing unsolicited", {16'd0, n_unsolicited},32'd0);
in_token;
ck("S6 one token, one answer", {16'd0, n_data}, 32'd1);
end
endtask
// -----------------------------------------------------------------
// PHASE 4 -- random, audited.
// -----------------------------------------------------------------
task phase_random;
integer r;
begin
in_random = 1;
hard_reset;
for (j = 0; j < 4000; j = j + 1) begin
r = {$random} % 100;
if (r < 30) begin
device_event({$random} % 256);
end else if (r < 55) begin
in_token;
end else if (r < 65) begin
out_token;
end else if (r < 78) begin
// the same-cycle collision, steered: unsteered, an event and a
// token landing together is rare, and it is the case the
// architecture has an opinion about
ev = 1; ev_data = {$random} % 256;
token_valid = 1; token_is_in = (({$random} % 100) < 70);
step;
end else begin
idle;
end
end
in_random = 0;
end
endtask
initial begin
chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
m_ev=0; m_tok=0; m_in=0; m_out=0; m_data_resp=0; m_nak_resp=0;
m_collide=0; m_pend_cycles=0; m_silent_cycles=0; m_setupfail=0;
phase_intent;
$display(" phase 1 intent : %0d checks, %0d errors (%0d silent cycles while pending)",
chk_dir, err, silent_run);
phase_sweep;
$display(" phase 2 exhaustive : %0d checks, %0d errors (16 combinations)", chk_dir, err);
phase_scenarios;
$display(" phase 3 scenarios : %0d checks, %0d errors", chk_dir, err);
$display(" ---- DIRECTED-ONLY : %0d checks, %0d errors ----", chk_dir, err);
phase_random;
$display("");
$display(" measured reachability (all phases)");
$display(" device events .......... %0d", m_ev);
$display(" IN tokens .............. %0d", m_in);
$display(" OUT tokens ............. %0d", m_out);
$display(" answered with DATA ..... %0d", m_data_resp);
$display(" answered with NAK ...... %0d", m_nak_resp);
$display(" event+token same cycle . %0d", m_collide);
$display(" cycles holding work .... %0d", m_pend_cycles);
$display(" cycles with no token ... %0d", m_silent_cycles);
$display(" setup failures ......... %0d", m_setupfail);
$display("");
$display(" directed checks ........ %0d", chk_dir);
$display(" random checks .......... %0d", chk_rnd);
$display(" TOTAL checks ........... %0d", chk_dir + chk_rnd);
$display(" ERRORS ................. %0d", err);
if (err == 0) $display(" PASS"); else $display(" FAIL");
$finish;
end
endmodule VERILOG SYSTEMVERILOG VHDL-2008
phase 1 intent 2,414 2,414 2,414
phase 2 exhaustive 2,910 2,910 2,910
phase 3 scenarios 3,710 3,710 3,710
---- DIRECTED 3,710 3,710 3,710
errors 0 0 0
TOTAL 43,715 43,715 43,715
measured reachability, Verilog run
silent cycles while holding work ...... 200
device events ....................... 1,798
IN tokens ........................... 1,368
OUT tokens ............................ 574
answered with DATA .................... 877
answered with NAK ..................... 491
event and token in the same cycle ..... 535
cycles holding work ................. 2,821
cycles with no token ................ 2,390
responses with no token ................. 0Two of those rows carry the argument. 2,390 cycles with no token, and 2,821 cycles holding work — overlapping heavily, and zero responses in any of them. And 491 NAKs, which is the answer prediction 3 said would not need to exist.
The exhaustive sweep, and what it does not contain
AXES
pending before the edge 0, 1 2
a device event this cycle 0, 1 2
a token this cycle 0, 1 2
token direction OUT, IN 2
------------------------------------------------------
2^4 = 16All sixteen are reachable, and the reason is itself the chapter's point: the four inputs come from two agents that do not constrain each other. What the sweep does not contain is history — two tokens in a row, an event between two tokens. Those are phase 3. An exhaustive sweep is exhaustive over the axes it has, which 30.2 §10 measured the hard way.
8. SystemVerilog
usb_ep_pending_sv.sv — the design, SystemVerilog
// =====================================================================
// usb_ep_pending_sv -- the same contract in SystemVerilog. Same ports,
// same rule, same reset, same latency.
//
// The reason this file exists in a MISCONCEPTION chapter is that the
// invariant can be written down as an assertion here, in one line, in
// the language of the architecture:
//
// resp_valid |-> token_valid
//
// A belief that a device can start a transaction is, in this module,
// a property that fails. That is a stronger statement than any amount
// of prose, and it is why the SystemVerilog version carries the SVA.
//
// ------------------------------------------------------------------
// The device side of "who starts a transaction".
//
// CLASSIFICATION: simplified synthesisable teaching RTL, built for one
// purpose: to make the difference between HAVING WORK and BEING ABLE
// TO START A TRANSACTION a fact about hardware rather than a sentence
// in a tutorial.
//
// It is NOT a USB device controller. There is no PHY, no serial
// interface engine, no packet framing, no CRC, no data toggle (29.5
// owns that), no endpoint 0 machine and no descriptors. Everything is
// reduced to two questions asked once per cycle:
//
// does this endpoint have something to say? ev / pending
// has the host addressed it? token_valid
//
// THE INVARIANT THIS MODULE EXISTS TO MAKE VISIBLE
// ------------------------------------------------
// resp_valid |-> token_valid
//
// The device puts NOTHING on the bus unless the host addressed it.
// Not when it has data. Not when it has a lot of data. Not when the
// bus is idle. The `pending` bit and the `resp_valid` output are wired
// to different things ON PURPOSE, and n_unsolicited counts the cycles
// in which that separation was violated -- which, in a correct build,
// is structurally impossible and therefore always zero.
//
// A zero that is structurally guaranteed looks like a useless counter.
// It is the whole point: section 8 turns the misconception into a
// one-line mutation and the counter is what fires.
// =====================================================================
module usb_ep_pending_sv (
input logic clk,
input logic rst_n,
// ---- the device side: work arrives here ----
// One cycle: firmware (or the function logic) has produced a byte it
// would like the host to collect.
input logic ev,
input logic [7:0] ev_data,
// ---- the host side: permission arrives here ----
// One cycle: the host has issued a token addressed to this endpoint.
input logic token_valid,
// 1 = IN (the host is asking this endpoint for data)
// 0 = OUT (the host is delivering data to this endpoint)
input logic token_is_in,
// ---- what this endpoint puts on the bus ----
output logic resp_valid, // we are driving a response, this cycle
output logic resp_is_data, // DATA if 1, a handshake if 0
output logic [7:0] resp_data,
// ---- observable device state ----
output logic pending,
output logic [15:0] n_events,
output logic [15:0] n_tokens,
output logic [15:0] n_data,
output logic [15:0] n_nak,
// Responses driven without a token. Structurally impossible here.
output logic [15:0] n_unsolicited
);
logic pend_r;
logic [7:0] data_r;
logic [15:0] c_ev, c_tok, c_data, c_nak, c_uns;
// The response is a function of the TOKEN, and of the endpoint state
// only insofar as it decides WHAT to answer -- never WHETHER to.
//
// Read the two lines below as the architecture: the first says the
// host decides that something happens; the second says the device
// decides what it is.
assign resp_valid = token_valid;
assign resp_is_data = token_valid && token_is_in && pend_r;
assign resp_data = data_r;
assign pending = pend_r;
// The device answers with the state it HAD when the token arrived.
// An event landing in the same cycle as an IN token does not rescue
// it: the data was not ready when the host asked, so the honest
// answer is NAK and the event is kept for the next token. Making the
// event win instead would mean a byte produced after the request was
// sampled appearing in the response to that request.
logic take_in, take_out;
assign take_in = token_valid && token_is_in;
assign take_out = token_valid && !token_is_in;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pend_r <= 1'b0;
data_r <= 8'd0;
c_ev <= 16'd0;
c_tok <= 16'd0;
c_data <= 16'd0;
c_nak <= 16'd0;
c_uns <= 16'd0;
end else begin
// An IN token that found data consumes it. The event, if one is
// arriving this cycle, is captured regardless -- so a consume and
// a produce in the same cycle leave the endpoint pending again,
// with the NEW byte.
if (ev) begin
pend_r <= 1'b1;
data_r <= ev_data;
c_ev <= c_ev + 16'd1;
end else if (take_in && pend_r) begin
pend_r <= 1'b0;
end
if (token_valid) c_tok <= c_tok + 16'd1;
if (take_in && pend_r) c_data <= c_data + 16'd1;
if (take_in && !pend_r) c_nak <= c_nak + 16'd1;
if (resp_valid && !token_valid) c_uns <= c_uns + 16'd1;
// an OUT token is accepted; 29.5 owns what happens to the byte
end
end
assign n_events = c_ev;
assign n_tokens = c_tok;
assign n_data = c_data;
assign n_nak = c_nak;
assign n_unsolicited = c_uns;
`ifdef SVA_ON
// ---------------------------------------------------------------
// The misconception, and its refutation, as properties. Icarus
// Verilog 13.0 rejects concurrent assertions, so under Icarus each
// is enforced by the named procedural check in the testbench.
// ---------------------------------------------------------------
// THE ONE. If this fails, devices initiate transactions. It is the
// entire chapter in one line, and mutation P-M1 is written to break
// exactly it.
property p_no_unsolicited;
@(posedge clk) disable iff (!rst_n) resp_valid |-> token_valid;
endproperty
a_no_unsolicited: assert property (p_no_unsolicited);
// SAFETY. Data is only ever offered when there is data. The device
// decides WHAT, and this is the what.
property p_data_needs_pending;
@(posedge clk) disable iff (!rst_n) resp_is_data |-> (pend_r && token_is_in);
endproperty
a_data_needs_pending: assert property (p_data_needs_pending);
// SAFETY. The pending bit changes for exactly two reasons, and a
// token that found nothing is not one of them.
property p_pending_changes_for_a_reason;
@(posedge clk) disable iff (!rst_n)
##1 (pend_r != $past(pend_r)) |-> ($past(ev) || $past(take_in && pend_r));
endproperty
a_pending_changes_for_a_reason: assert property (p_pending_changes_for_a_reason);
// PROGRESS. A byte the endpoint is holding IS delivered on the next
// IN token. The device never withholds -- it only ever waits.
//
// Note the antecedent: it requires the host to ask. An unconditional
// progress claim would be FALSE for a correct design, because a
// device that is never polled correctly never sends, and a property
// that blames the device for the host's silence would be switched off
// within a week.
property p_held_data_is_delivered;
@(posedge clk) disable iff (!rst_n)
(pend_r && token_valid && token_is_in) |-> resp_is_data;
endproperty
a_held_data_is_delivered: assert property (p_held_data_is_delivered);
// COVER, so none of the above can pass by never happening.
c_pending_idle: cover property (@(posedge clk) pend_r && !token_valid);
c_nak: cover property (@(posedge clk) token_valid && token_is_in && !pend_r);
c_data: cover property (@(posedge clk) resp_is_data);
c_out: cover property (@(posedge clk) take_out);
c_collide: cover property (@(posedge clk) ev && token_valid);
c_consume_produce: cover property (@(posedge clk) ev && take_in && pend_r);
`endif
endmoduletb_usb_ep_pending_sv.sv — the testbench, SystemVerilog
// =====================================================================
// tb_usb_ep_pending_sv -- SystemVerilog testbench for usb_ep_pending_sv.
//
// Phases 1-3 present the SAME directed stimulus, in the same order, as
// the Verilog bench, so their directed counts must agree to the digit.
//
// PHASE 1 is unlike any other phase in this curriculum and it is the
// reason the module exists. It does not compare the design to a model.
// It states the architectural claim directly and requires it:
//
// however much work this endpoint has, and for however long,
// NOTHING APPEARS ON THE BUS until the host addresses it.
//
// That check cannot be satisfied by a reference model that shares the
// design's structure, because it does not consult a model at all. 30.2
// established why that matters: a model written by the design's author
// from the design's sentence is one artefact wearing two hats, and
// 30.4's specimen agreed with its own bug for 39,108 checks.
//
// PHASES
// 1 INTENT the silence claim, stated and required directly
// 2 EXHAUSTIVE 16 combinations: 4 binary axes, all reachable
// 3 SCENARIO the named cases, including the same-cycle collision
// 4 RANDOM supplementary, and audited for what it reaches
// =====================================================================
`timescale 1ns/1ps
module tb_usb_ep_pending_sv;
logic clk = 1'b0;
logic rst_n;
logic ev;
logic [7:0] ev_data;
logic token_valid, token_is_in;
wire resp_valid, resp_is_data, pending;
wire [7:0] resp_data;
wire [15:0] n_events, n_tokens, n_data, n_nak, n_unsolicited;
usb_ep_pending_sv dut (
.clk(clk), .rst_n(rst_n),
.ev(ev), .ev_data(ev_data),
.token_valid(token_valid), .token_is_in(token_is_in),
.resp_valid(resp_valid), .resp_is_data(resp_is_data),
.resp_data(resp_data), .pending(pending),
.n_events(n_events), .n_tokens(n_tokens), .n_data(n_data),
.n_nak(n_nak), .n_unsolicited(n_unsolicited)
);
always #5 clk = ~clk;
// ---- the independent reference model ---------------------------
// Written from the ARCHITECTURAL RULES, in the order a specification
// would state them, not from the RTL's expressions:
// R1 a response happens if and only if the host issued a token
// R2 an IN token is answered with DATA if the endpoint held data
// WHEN THE TOKEN ARRIVED, and with NAK otherwise
// R3 an event makes the endpoint pending and replaces its byte
// R4 a consumed byte clears pending unless an event replaces it
logic rm_pend;
logic [7:0] rm_data;
logic [15:0] rm_ev, rm_tok, rm_dat, rm_nak, rm_uns;
int chk_dir, chk_rnd, err;
bit in_random;
int m_ev, m_tok, m_in, m_out, m_data_resp, m_nak_resp,
m_collide, m_pend_cycles, m_silent_cycles, m_setupfail;
int i, j, a, b, c, d;
task bump; begin
if (in_random) chk_rnd = chk_rnd + 1; else chk_dir = chk_dir + 1;
end endtask
task ck(string what, logic [31:0] got, logic [31:0] exp);
begin
bump;
if (got !== exp) begin
err = err + 1;
if (!in_random && err <= 40)
$display(" ** %s: got %0d expected %0d (t=%0t)", what, got, exp, $time);
end
end
endtask
task ref_step;
logic t_in, t_out, consumed;
begin
if (!rst_n) begin
rm_pend = 0; rm_data = 0;
rm_ev = 0; rm_tok = 0; rm_dat = 0; rm_nak = 0; rm_uns = 0;
end else begin
t_in = token_valid && token_is_in;
t_out = token_valid && !token_is_in;
// R2 uses the value of pending BEFORE this edge
consumed = t_in && rm_pend;
if (token_valid) rm_tok = rm_tok + 1;
if (consumed) rm_dat = rm_dat + 1;
if (t_in && !rm_pend) rm_nak = rm_nak + 1;
// R3 beats R4: an event in the same cycle as a consume leaves
// the endpoint pending with the NEW byte
if (ev) begin
rm_pend = 1; rm_data = ev_data; rm_ev = rm_ev + 1;
end else if (consumed) begin
rm_pend = 0;
end
// tallies
if (ev) m_ev = m_ev + 1;
if (t_in) m_in = m_in + 1;
if (t_out) m_out = m_out + 1;
if (consumed) m_data_resp = m_data_resp + 1;
if (t_in && !consumed) m_nak_resp = m_nak_resp + 1;
if (ev && token_valid) m_collide = m_collide + 1;
end
end
endtask
// What the endpoint put on the bus THIS cycle, captured while the
// token is still driven. The scenarios inspect these rather than the
// live outputs, which are only meaningful before the edge.
logic cap_valid, cap_isdata;
logic [7:0] cap_data;
// Pre-edge: the response, compared against the model's state AS IT IS
// WHEN THE TOKEN ARRIVES -- which is the architectural rule.
task cmp_comb;
logic exp_valid, exp_isdata;
begin
exp_valid = token_valid;
exp_isdata = token_valid && token_is_in && rm_pend;
cap_valid = resp_valid;
cap_isdata = resp_is_data;
cap_data = resp_data;
ck("resp_valid", {31'd0, resp_valid}, {31'd0, exp_valid});
ck("resp_is_data", {31'd0, resp_is_data}, {31'd0, exp_isdata});
ck("pending", {31'd0, pending}, {31'd0, rm_pend});
if (exp_isdata)
ck("resp_data", {24'd0, resp_data}, {24'd0, rm_data});
else bump;
if (rm_pend) m_pend_cycles = m_pend_cycles + 1;
if (!token_valid) m_silent_cycles = m_silent_cycles + 1;
end
endtask
// Post-edge: the registered tallies.
task cmp_regs; begin
ck("n_events", {16'd0, n_events}, {16'd0, rm_ev});
ck("n_tokens", {16'd0, n_tokens}, {16'd0, rm_tok});
ck("n_data", {16'd0, n_data}, {16'd0, rm_dat});
ck("n_nak", {16'd0, n_nak}, {16'd0, rm_nak});
ck("n_unsolicited",{16'd0, n_unsolicited},32'd0);
end endtask
// The INTENT check, run every cycle of every phase. It consults no
// model: it is the architectural claim, asserted directly.
task intent_check; begin
bump;
if (resp_valid && !token_valid) begin
err = err + 1;
$display(" ** INTENT VIOLATED: the endpoint drove the bus with no token (t=%0t, pending=%0d)",
$time, pending);
end
end endtask
task step; begin
#1;
cmp_comb;
intent_check;
@(posedge clk);
ref_step;
#1;
cmp_regs;
ev = 0; ev_data = 0; token_valid = 0; token_is_in = 0;
end endtask
task idle; step; endtask
task hard_reset; begin
rst_n = 0; ev = 0; ev_data = 0; token_valid = 0; token_is_in = 0;
repeat (3) begin @(posedge clk); ref_step; end
#1; rst_n = 1;
@(posedge clk); ref_step; #1; cmp_regs;
end endtask
task device_event(logic [7:0] v);
ev = 1; ev_data = v; step;
endtask
task in_token; token_valid = 1; token_is_in = 1; step; endtask
task out_token; token_valid = 1; token_is_in = 0; step; endtask
// -----------------------------------------------------------------
// PHASE 1 -- THE SILENCE CLAIM.
//
// Fill the endpoint with work and then do nothing for a long time.
// If the misconception were true -- if a device with data could
// start a transaction -- something would appear. Nothing does, and
// this phase requires it every cycle rather than inferring it.
// -----------------------------------------------------------------
int silent_run;
task phase_intent;
begin
hard_reset;
silent_run = 0;
// sixteen events, no tokens at all
for (i = 0; i < 16; i = i + 1) begin
device_event(8'hA0 + 8'(i));
ck("still pending", {31'd0, pending}, 32'd1);
ck("and still silent", {31'd0, cap_valid}, 32'd0);
end
// then two hundred idle cycles with the endpoint full
for (i = 0; i < 200; i = i + 1) begin
idle;
ck("silent while pending", {31'd0, cap_valid}, 32'd0);
silent_run = silent_run + 1;
end
ck("nothing unsolicited", {16'd0, n_unsolicited}, 32'd0);
ck("no data was sent", {16'd0, n_data}, 32'd0);
ck("no tokens arrived", {16'd0, n_tokens}, 32'd0);
// and the moment the host asks, the data is there
in_token;
ck("now it answers", {31'd0, cap_valid}, 32'd1);
ck("and it is DATA", {31'd0, cap_isdata}, 32'd1);
ck("the newest byte", {24'd0, cap_data}, {24'd0, 8'hA0 + 8'd15});
// The scenario must have HAPPENED: 216 cycles of held work.
bump;
if (silent_run < 200) begin
err = err + 1;
$display(" ** intent: only %0d silent cycles", silent_run);
end
end
endtask
// -----------------------------------------------------------------
// PHASE 2 -- the exhaustive sweep.
//
// AXES, named so that what is absent is visible:
// pending before the edge 0, 1 2
// a device event this cycle 0, 1 2
// a token this cycle 0, 1 2
// token direction OUT, IN 2
// ------------------------------------------------------------
// 2^4 = 16
//
// All sixteen are reachable: the four inputs come from two agents
// that do not constrain each other, which is the architectural fact
// the chapter is about.
//
// WHAT THIS SWEEP DOES NOT CONTAIN: history longer than one edge.
// Two tokens in a row, and an event between two tokens, are phase 3.
// An exhaustive sweep is exhaustive over the axes it has -- 30.2 §10.
// -----------------------------------------------------------------
task setup_pending(logic want);
begin
hard_reset;
if (want) begin
device_event(8'h5A);
bump;
if (pending !== 1'b1) begin
err = err + 1; m_setupfail = m_setupfail + 1;
$display(" ** setup: pending was not established");
end
end else begin
bump;
if (pending !== 1'b0) begin
err = err + 1; m_setupfail = m_setupfail + 1;
$display(" ** setup: pending was not clear");
end
end
end
endtask
task phase_sweep;
begin
for (a = 0; a < 2; a = a + 1) // pending
for (b = 0; b < 2; b = b + 1) // event
for (c = 0; c < 2; c = c + 1) // token
for (d = 0; d < 2; d = d + 1) // direction
begin
setup_pending(a[0]);
ev = b[0];
ev_data = 8'hC0 + {4'd0, a[0], b[0], c[0], d[0]};
token_valid = c[0];
token_is_in = d[0];
step;
idle;
end
end
endtask
// -----------------------------------------------------------------
// PHASE 3 -- the named scenarios.
// -----------------------------------------------------------------
task phase_scenarios;
begin
// S1 a token with nothing pending is answered, and answered NAK.
// The misconception has no account of this: if devices spoke
// when they had something to say, there would be no reason to
// have a way of saying "not yet".
hard_reset;
in_token;
ck("S1 the device answered", {31'd0, cap_valid}, 32'd1);
ck("S1 but not with data", {31'd0, cap_isdata}, 32'd0);
ck("S1 counted as a NAK", {16'd0, n_nak}, 32'd1);
// S2 an event and an IN token in the SAME cycle. The device
// answers with the state it had when the token arrived, so the
// answer is NAK -- and the byte is kept for the next token.
hard_reset;
ev = 1; ev_data = 8'h77; token_valid = 1; token_is_in = 1; step;
ck("S2 answered NAK", {31'd0, cap_isdata}, 32'd0);
ck("S2 the event was kept", {31'd0, pending}, 32'd1);
in_token;
ck("S2 next token gets data", {31'd0, cap_isdata}, 32'd1);
ck("S2 and it is the byte", {24'd0, cap_data}, {24'd0, 8'h77});
// S3 back-to-back IN tokens: the first gets the data, the second
// gets a NAK. The host asks twice; the device has one byte.
hard_reset;
device_event(8'h11);
in_token;
ck("S3 first token: data", {31'd0, cap_isdata}, 32'd1);
in_token;
ck("S3 second token: NAK", {31'd0, cap_isdata}, 32'd0);
ck("S3 one data, one NAK", {16'd0, n_data + n_nak}, 32'd2);
// S4 consume and produce in the same cycle: the endpoint is
// pending again immediately, with the NEW byte.
hard_reset;
device_event(8'h22);
ev = 1; ev_data = 8'h33; token_valid = 1; token_is_in = 1; step;
ck("S4 data was sent", {31'd0, cap_isdata}, 32'd1);
ck("S4 still pending", {31'd0, pending}, 32'd1);
in_token;
ck("S4 the new byte", {24'd0, cap_data}, {24'd0, 8'h33});
// S5 an OUT token is also the host's move. The direction changes
// who carries the data; it does not change who starts.
hard_reset;
out_token;
ck("S5 the device answered", {31'd0, cap_valid}, 32'd1);
ck("S5 not an IN response", {31'd0, cap_isdata}, 32'd0);
ck("S5 no NAK counted", {16'd0, n_nak}, 32'd0);
// S6 a long run of events with no tokens, then one token.
// Sixty-four bytes of work produce exactly zero bus activity.
hard_reset;
for (i = 0; i < 64; i = i + 1) device_event(8'h40 + 8'(i));
ck("S6 64 events", {16'd0, n_events}, 32'd64);
ck("S6 zero bus activity", {16'd0, n_tokens}, 32'd0);
ck("S6 nothing unsolicited", {16'd0, n_unsolicited},32'd0);
in_token;
ck("S6 one token, one answer", {16'd0, n_data}, 32'd1);
end
endtask
// -----------------------------------------------------------------
// PHASE 4 -- random, audited.
// -----------------------------------------------------------------
task phase_random;
int r;
begin
in_random = 1;
hard_reset;
for (j = 0; j < 4000; j = j + 1) begin
r = $urandom_range(99);
if (r < 30) begin
device_event(8'($urandom_range(255)));
end else if (r < 55) begin
in_token;
end else if (r < 65) begin
out_token;
end else if (r < 78) begin
// the same-cycle collision, steered: unsteered, an event and a
// token landing together is rare, and it is the case the
// architecture has an opinion about
ev = 1; ev_data = 8'($urandom_range(255));
token_valid = 1; token_is_in = (($urandom_range(99)) < 70);
step;
end else begin
idle;
end
end
in_random = 0;
end
endtask
initial begin
chk_dir = 0; chk_rnd = 0; err = 0; in_random = 0;
m_ev=0; m_tok=0; m_in=0; m_out=0; m_data_resp=0; m_nak_resp=0;
m_collide=0; m_pend_cycles=0; m_silent_cycles=0; m_setupfail=0;
phase_intent;
$display(" phase 1 intent : %0d checks, %0d errors (%0d silent cycles while pending)",
chk_dir, err, silent_run);
phase_sweep;
$display(" phase 2 exhaustive : %0d checks, %0d errors (16 combinations)", chk_dir, err);
phase_scenarios;
$display(" phase 3 scenarios : %0d checks, %0d errors", chk_dir, err);
$display(" ---- DIRECTED-ONLY : %0d checks, %0d errors ----", chk_dir, err);
phase_random;
$display("");
$display(" measured reachability (all phases)");
$display(" device events .......... %0d", m_ev);
$display(" IN tokens .............. %0d", m_in);
$display(" OUT tokens ............. %0d", m_out);
$display(" answered with DATA ..... %0d", m_data_resp);
$display(" answered with NAK ...... %0d", m_nak_resp);
$display(" event+token same cycle . %0d", m_collide);
$display(" cycles holding work .... %0d", m_pend_cycles);
$display(" cycles with no token ... %0d", m_silent_cycles);
$display(" setup failures ......... %0d", m_setupfail);
$display("");
$display(" directed checks ........ %0d", chk_dir);
$display(" random checks .......... %0d", chk_rnd);
$display(" TOTAL checks ........... %0d", chk_dir + chk_rnd);
$display(" ERRORS ................. %0d", err);
if (err == 0) $display(" PASS"); else $display(" FAIL");
$finish;
end
endmoduleThe reason this version matters to a misconception chapter is that the belief becomes a property that fails:
property p_no_unsolicited;
@(posedge clk) disable iff (!rst_n) resp_valid |-> token_valid;
endpropertyOne line, in the language of the architecture. And beside it, the progress property — with an antecedent worth reading twice:
property p_held_data_is_delivered;
@(posedge clk) disable iff (!rst_n)
(pend_r && token_valid && token_is_in) |-> resp_is_data;
endpropertytoken_valid is in the antecedent. An unconditional claim — "data the device
holds is eventually delivered" — would be false for a correct design, because a
device that is never polled correctly never sends. A progress property that
blames the device for the host's silence gets switched off within a week, and
30.2 §9 made that a standing item.
9. VHDL-2008
usb_ep_pending.vhd — the design, VHDL-2008
-- =====================================================================
-- usb_ep_pending (VHDL-2008) -- the same contract. Same ports, same
-- rule, same reset, same latency.
--
-- In VHDL the architectural separation the chapter is about becomes a
-- statement about DEPENDENCIES: resp_valid is a concurrent assignment
-- whose right-hand side mentions token_valid and nothing else. A
-- reader checking the claim "a device cannot start a transaction" has
-- to read one line and check what is in it.
--
-- CLASSIFICATION: simplified synthesisable teaching RTL. It is not a
-- USB device controller; see the Verilog header for what is omitted.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity usb_ep_pending is
port (
clk : in std_logic;
rst_n : in std_logic;
ev : in std_logic;
ev_data : in std_logic_vector(7 downto 0);
token_valid : in std_logic;
token_is_in : in std_logic;
resp_valid : out std_logic;
resp_is_data : out std_logic;
resp_data : out std_logic_vector(7 downto 0);
pending : out std_logic;
n_events : out unsigned(15 downto 0);
n_tokens : out unsigned(15 downto 0);
n_data : out unsigned(15 downto 0);
n_nak : out unsigned(15 downto 0);
n_unsolicited : out unsigned(15 downto 0)
);
end entity usb_ep_pending;
architecture rtl of usb_ep_pending is
signal pend_r : std_logic := '0';
signal data_r : std_logic_vector(7 downto 0) := (others => '0');
signal c_ev, c_tok, c_data, c_nak, c_uns : unsigned(15 downto 0)
:= (others => '0');
signal take_in, take_out : std_logic;
signal rv_i, rd_i : std_logic;
begin
-- The host decides THAT something happens.
rv_i <= token_valid;
-- The device decides WHAT it is.
rd_i <= '1' when (token_valid = '1' and token_is_in = '1' and pend_r = '1')
else '0';
resp_valid <= rv_i;
resp_is_data <= rd_i;
resp_data <= data_r;
pending <= pend_r;
take_in <= token_valid and token_is_in;
take_out <= token_valid and (not token_is_in);
seq : process (clk, rst_n)
begin
if rst_n = '0' then
pend_r <= '0';
data_r <= (others => '0');
c_ev <= (others => '0');
c_tok <= (others => '0');
c_data <= (others => '0');
c_nak <= (others => '0');
c_uns <= (others => '0');
elsif rising_edge(clk) then
-- An event beats a consume: the endpoint is pending again with
-- the new byte.
if ev = '1' then
pend_r <= '1';
data_r <= ev_data;
c_ev <= c_ev + 1;
elsif take_in = '1' and pend_r = '1' then
pend_r <= '0';
end if;
if token_valid = '1' then
c_tok <= c_tok + 1;
end if;
if take_in = '1' and pend_r = '1' then
c_data <= c_data + 1;
end if;
if take_in = '1' and pend_r = '0' then
c_nak <= c_nak + 1;
end if;
if rv_i = '1' and token_valid = '0' then
c_uns <= c_uns + 1;
end if;
end if;
end process seq;
n_events <= c_ev;
n_tokens <= c_tok;
n_data <= c_data;
n_nak <= c_nak;
n_unsolicited <= c_uns;
end architecture rtl;tb_usb_ep_pending.vhd — the testbench, VHDL-2008
-- =====================================================================
-- tb_usb_ep_pending -- VHDL-2008 testbench for usb_ep_pending.
-- Phases 1-3 present the SAME directed stimulus as the Verilog and
-- SystemVerilog benches, so their directed counts must agree.
--
-- Phase 1 states the architectural claim directly and requires it,
-- rather than comparing against a model. See the Verilog header.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity tb_usb_ep_pending is
end entity tb_usb_ep_pending;
architecture sim of tb_usb_ep_pending is
constant HALF : time := 10 ns;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal ev : std_logic := '0';
signal ev_data : std_logic_vector(7 downto 0) := (others => '0');
signal token_valid : std_logic := '0';
signal token_is_in : std_logic := '0';
signal resp_valid, resp_is_data, pending : std_logic;
signal resp_data : std_logic_vector(7 downto 0);
signal n_events, n_tokens, n_data, n_nak, n_unsolicited : unsigned(15 downto 0);
signal done_flag : boolean := false;
function b2i (s : std_logic) return integer is
begin
if s = '1' then return 1; else return 0; end if;
end function b2i;
begin
dut : entity work.usb_ep_pending
port map (clk => clk, rst_n => rst_n, ev => ev, ev_data => ev_data,
token_valid => token_valid, token_is_in => token_is_in,
resp_valid => resp_valid, resp_is_data => resp_is_data,
resp_data => resp_data, pending => pending,
n_events => n_events, n_tokens => n_tokens, n_data => n_data,
n_nak => n_nak, n_unsolicited => n_unsolicited);
clkgen : process
begin
while not done_flag loop
clk <= '0'; wait for HALF;
clk <= '1'; wait for HALF;
end loop;
wait;
end process clkgen;
stim : process
variable chk_dir, chk_rnd, errs, shown : natural := 0;
variable in_random : boolean := false;
variable rm_pend : std_logic := '0';
variable rm_data : std_logic_vector(7 downto 0) := (others => '0');
variable rm_ev, rm_tok, rm_dat, rm_nak : natural := 0;
variable m_ev, m_in, m_out, m_data_resp, m_nak_resp : natural := 0;
variable m_collide, m_pend_cycles, m_silent_cycles : natural := 0;
variable m_setupfail, silent_run : natural := 0;
variable cap_valid, cap_isdata : std_logic := '0';
variable cap_data : std_logic_vector(7 downto 0) := (others => '0');
variable seed1 : positive := 551_213;
variable seed2 : positive := 81_907;
procedure bump is
begin
if in_random then chk_rnd := chk_rnd + 1; else chk_dir := chk_dir + 1; end if;
end procedure bump;
procedure ck (what : string; got : integer; exp : integer) is
begin
bump;
if got /= exp then
errs := errs + 1;
if (not in_random) and shown < 40 then
shown := shown + 1;
report " ** " & what & ": got " & integer'image(got) &
" expected " & integer'image(exp) severity warning;
end if;
end if;
end procedure ck;
procedure ref_step is
variable t_in, t_out, consumed : boolean;
begin
if rst_n = '0' then
rm_pend := '0'; rm_data := (others => '0');
rm_ev := 0; rm_tok := 0; rm_dat := 0; rm_nak := 0;
else
t_in := (token_valid = '1') and (token_is_in = '1');
t_out := (token_valid = '1') and (token_is_in = '0');
consumed := t_in and (rm_pend = '1');
if token_valid = '1' then rm_tok := rm_tok + 1; end if;
if consumed then rm_dat := rm_dat + 1; end if;
if t_in and rm_pend = '0' then rm_nak := rm_nak + 1; end if;
if ev = '1' then
rm_pend := '1'; rm_data := ev_data; rm_ev := rm_ev + 1;
elsif consumed then
rm_pend := '0';
end if;
if ev = '1' then m_ev := m_ev + 1; end if;
if t_in then m_in := m_in + 1; end if;
if t_out then m_out := m_out + 1; end if;
if consumed then m_data_resp := m_data_resp + 1; end if;
if t_in and not consumed then m_nak_resp := m_nak_resp + 1; end if;
if ev = '1' and token_valid = '1' then m_collide := m_collide + 1; end if;
end if;
end procedure ref_step;
procedure cmp_comb is
variable exp_valid, exp_isdata : integer;
begin
exp_valid := b2i(token_valid);
if token_valid = '1' and token_is_in = '1' and rm_pend = '1' then
exp_isdata := 1;
else
exp_isdata := 0;
end if;
cap_valid := resp_valid;
cap_isdata := resp_is_data;
cap_data := resp_data;
ck("resp_valid", b2i(resp_valid), exp_valid);
ck("resp_is_data", b2i(resp_is_data), exp_isdata);
ck("pending", b2i(pending), b2i(rm_pend));
if exp_isdata = 1 then
ck("resp_data", to_integer(unsigned(resp_data)),
to_integer(unsigned(rm_data)));
else
bump;
end if;
if rm_pend = '1' then m_pend_cycles := m_pend_cycles + 1; end if;
if token_valid = '0' then m_silent_cycles := m_silent_cycles + 1; end if;
end procedure cmp_comb;
procedure cmp_regs is
begin
ck("n_events", to_integer(n_events), rm_ev);
ck("n_tokens", to_integer(n_tokens), rm_tok);
ck("n_data", to_integer(n_data), rm_dat);
ck("n_nak", to_integer(n_nak), rm_nak);
ck("n_unsolicited",to_integer(n_unsolicited), 0);
end procedure cmp_regs;
-- The claim, asserted directly. No model consulted.
procedure intent_check is
begin
bump;
if resp_valid = '1' and token_valid = '0' then
errs := errs + 1;
report " ** INTENT VIOLATED: the endpoint drove the bus with no token"
severity warning;
end if;
end procedure intent_check;
procedure step is
begin
wait for 1 ns;
cmp_comb;
intent_check;
wait until rising_edge(clk);
ref_step;
wait for 1 ns;
cmp_regs;
ev <= '0'; ev_data <= (others => '0');
token_valid <= '0'; token_is_in <= '0';
end procedure step;
procedure idle is begin step; end procedure;
procedure hard_reset is
begin
rst_n <= '0'; ev <= '0'; ev_data <= (others => '0');
token_valid <= '0'; token_is_in <= '0';
for i in 0 to 2 loop wait until rising_edge(clk); ref_step; end loop;
wait for 1 ns; rst_n <= '1';
wait until rising_edge(clk); ref_step; wait for 1 ns; cmp_regs;
end procedure hard_reset;
procedure device_event (v : natural) is
begin
ev <= '1'; ev_data <= std_logic_vector(to_unsigned(v, 8)); step;
end procedure;
procedure in_token is begin token_valid <= '1'; token_is_in <= '1'; step; end procedure;
procedure out_token is begin token_valid <= '1'; token_is_in <= '0'; step; end procedure;
procedure setup_pending (want : std_logic) is
begin
hard_reset;
if want = '1' then
device_event(16#5A#);
bump;
if pending /= '1' then
errs := errs + 1; m_setupfail := m_setupfail + 1;
report " ** setup: pending was not established" severity warning;
end if;
else
bump;
if pending /= '0' then
errs := errs + 1; m_setupfail := m_setupfail + 1;
report " ** setup: pending was not clear" severity warning;
end if;
end if;
end procedure setup_pending;
impure function rnd (n : positive) return natural is
variable x : real;
begin
uniform(seed1, seed2, x);
return natural(real(n - 1) * x);
end function rnd;
variable r : natural;
begin
-- ---- PHASE 1 : the silence claim ----
hard_reset;
silent_run := 0;
for i in 0 to 15 loop
device_event(16#A0# + i);
ck("still pending", b2i(pending), 1);
ck("and still silent", b2i(cap_valid), 0);
end loop;
for i in 0 to 199 loop
idle;
ck("silent while pending", b2i(cap_valid), 0);
silent_run := silent_run + 1;
end loop;
ck("nothing unsolicited", to_integer(n_unsolicited), 0);
ck("no data was sent", to_integer(n_data), 0);
ck("no tokens arrived", to_integer(n_tokens), 0);
in_token;
ck("now it answers", b2i(cap_valid), 1);
ck("and it is DATA", b2i(cap_isdata), 1);
ck("the newest byte", to_integer(unsigned(cap_data)), 16#AF#);
bump;
if silent_run < 200 then
errs := errs + 1;
report " ** intent: too few silent cycles" severity warning;
end if;
report " phase 1 intent : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors (" &
integer'image(silent_run) & " silent cycles while pending)";
-- ---- PHASE 2 : 2^4 = 16 ----
for a in 0 to 1 loop
for b in 0 to 1 loop
for c in 0 to 1 loop
for d in 0 to 1 loop
if a = 1 then setup_pending('1'); else setup_pending('0'); end if;
if b = 1 then ev <= '1'; else ev <= '0'; end if;
ev_data <= std_logic_vector(to_unsigned(
16#C0# + a*8 + b*4 + c*2 + d, 8));
if c = 1 then token_valid <= '1'; else token_valid <= '0'; end if;
if d = 1 then token_is_in <= '1'; else token_is_in <= '0'; end if;
step;
idle;
end loop;
end loop;
end loop;
end loop;
report " phase 2 exhaustive : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors (16 combinations)";
-- ---- PHASE 3 : the named scenarios ----
hard_reset;
in_token;
ck("S1 the device answered", b2i(cap_valid), 1);
ck("S1 but not with data", b2i(cap_isdata), 0);
ck("S1 counted as a NAK", to_integer(n_nak), 1);
hard_reset;
ev <= '1'; ev_data <= x"77"; token_valid <= '1'; token_is_in <= '1'; step;
ck("S2 answered NAK", b2i(cap_isdata), 0);
ck("S2 the event was kept", b2i(pending), 1);
in_token;
ck("S2 next token gets data", b2i(cap_isdata), 1);
ck("S2 and it is the byte", to_integer(unsigned(cap_data)), 16#77#);
hard_reset;
device_event(16#11#);
in_token;
ck("S3 first token: data", b2i(cap_isdata), 1);
in_token;
ck("S3 second token: NAK", b2i(cap_isdata), 0);
ck("S3 one data, one NAK", to_integer(n_data) + to_integer(n_nak), 2);
hard_reset;
device_event(16#22#);
ev <= '1'; ev_data <= x"33"; token_valid <= '1'; token_is_in <= '1'; step;
ck("S4 data was sent", b2i(cap_isdata), 1);
ck("S4 still pending", b2i(pending), 1);
in_token;
ck("S4 the new byte", to_integer(unsigned(cap_data)), 16#33#);
hard_reset;
out_token;
ck("S5 the device answered", b2i(cap_valid), 1);
ck("S5 not an IN response", b2i(cap_isdata), 0);
ck("S5 no NAK counted", to_integer(n_nak), 0);
hard_reset;
for i in 0 to 63 loop device_event(16#40# + i); end loop;
ck("S6 64 events", to_integer(n_events), 64);
ck("S6 zero bus activity", to_integer(n_tokens), 0);
ck("S6 nothing unsolicited", to_integer(n_unsolicited), 0);
in_token;
ck("S6 one token, one answer", to_integer(n_data), 1);
report " phase 3 scenarios : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors";
report " ---- DIRECTED-ONLY : " & integer'image(chk_dir) &
" checks, " & integer'image(errs) & " errors ----";
-- ---- PHASE 4 : random ----
in_random := true;
hard_reset;
for j in 0 to 3999 loop
r := rnd(100);
if r < 30 then
device_event(rnd(256));
elsif r < 55 then
in_token;
elsif r < 65 then
out_token;
elsif r < 78 then
ev <= '1';
ev_data <= std_logic_vector(to_unsigned(rnd(256), 8));
token_valid <= '1';
if rnd(100) < 70 then token_is_in <= '1'; else token_is_in <= '0'; end if;
step;
else
idle;
end if;
end loop;
in_random := false;
report " measured reachability (all phases)";
report " device events .......... " & integer'image(m_ev);
report " IN tokens .............. " & integer'image(m_in);
report " OUT tokens ............. " & integer'image(m_out);
report " answered with DATA ..... " & integer'image(m_data_resp);
report " answered with NAK ...... " & integer'image(m_nak_resp);
report " event+token same cycle . " & integer'image(m_collide);
report " cycles holding work .... " & integer'image(m_pend_cycles);
report " cycles with no token ... " & integer'image(m_silent_cycles);
report " setup failures ......... " & integer'image(m_setupfail);
report " directed checks ........ " & integer'image(chk_dir);
report " random checks .......... " & integer'image(chk_rnd);
report " TOTAL checks ........... " & integer'image(chk_dir + chk_rnd);
report " ERRORS ................. " & integer'image(errs);
if errs = 0 then report " PASS"; else report " FAIL" severity failure; end if;
done_flag <= true;
wait;
end process stim;
end architecture sim;In VHDL the claim becomes a statement about dependencies: rv_i is a
concurrent assignment whose right-hand side mentions token_valid and nothing
else. A reviewer checking "can a device start a transaction" reads one line and
checks what is in it.
10. The Misconception As Hardware
The mutations are not arbitrary edits. Each one is the belief, written as RTL.
MUT THE BELIEF ENCODED V-DIR SV-DIR VH-DIR
P-M1 a device with data can start a
transaction 1,098 1,098 1,098
P-M2 data produced now can answer a
request already made 3 3 3BASE reads zero in all six columns and both directed columns are identical
across the three languages.
P-M1 is one character of change:
assign resp_valid = token_valid; // architecture
assign resp_valid = token_valid || pend_r; // the misconceptionIt is caught 1,098 times in the directed suite. The intent phase alone accounts for 862 of those, and the rest come from the ordinary output comparison, because a device that speaks unbidden is wrong in a great many cycles.
P-M2 is the subtler one and scores 3. It lets a byte produced in the same cycle as the token answer that token — data that did not exist when the host asked, appearing in the response to the request. Three checks catch it, all in the scenario written for the same-cycle case, and its total of 130 (against P-M1's 7,700) is the measure of how much rarer the situation is.
11. Where UVM Would And Would Not Help
Not here, and saying why is more useful than an environment that does not earn its place.
This block has two agents but no shared state: the function produces, the host asks, and the endpoint holds one bit between them. The interesting cases are a four-axis product with sixteen members, and sixteen is a sweep, not a constrained-random space.
Where UVM becomes right for this subject is one level up, and it is worth naming so the boundary is clear:
WORTH A UVM ENVIRONMENT
a host-poll SEQUENCE across several endpoints with different
intervals, against device-side event generators with independent
rates -- the question being whether any endpoint's events are
collected within its configured interval under load
that is a scheduling question with a large scenario space, it is
31.4's subject, and its reference model is a service calendar
rather than a copy of any RTL
NOT WORTH IT
this block. The item would be a pair of bits.12. What The Wrong Model Does To Debugging
This is the most valuable part of the chapter, because the belief costs days rather than producing a bug.
SYMPTOM "the device isn't sending its data"
WRONG MODEL the device decides when to send
WRONG QUESTION why isn't the device sending?
WASTED ON device firmware, the function logic, the buffer,
the interrupt handler inside the device -- all of
which may be perfectly correct
CORRECT MODEL the device answers; the host asks
THE FIRST QUESTION, and it is one trace away:
IS THE HOST POLLING THE ENDPOINT AT ALL?
and the three answers lead to three different halves of the tree:
no tokens at all -> the endpoint is not in the host's
schedule. Enumeration, the interface
setting, or the driver never opened
the pipe. NOT a device problem.
tokens, all NAKed -> the host is asking and the device
says it has nothing. The bug is
inside the device, between the
function and the pending bit.
tokens, data flowing -> the bug is above USB entirely: the
host software is receiving and not
using it.Three outcomes, one observation, and the wrong model does not suggest making it. It suggests looking inside the device, which is right in exactly one of the three cases.
13. Interview Reasoning
"A USB mouse sends the host its movements. Walk me through what actually happens on the bus."
The premise has the verb wrong, and correcting it precisely is the answer.
The mouse does not send. At configuration time the host read the mouse's endpoint descriptor, which asks to be polled at some interval, and the host put that endpoint into its schedule. From then on the host issues an IN token to that endpoint every interval, whether or not the mouse has moved. Most of those tokens are answered NAK — "nothing for you" — and that is the normal state of an idle mouse, not an error.
When the mouse moves, its firmware makes a byte pending. Nothing appears on the bus at that moment. The next scheduled token gets a data packet, the host ACKs it, and the pending state clears. So the latency between the physical movement and the data is bounded by the polling interval, which is the number the endpoint descriptor negotiated.
Worth adding, because it is where the belief comes from: the transfer type is called an interrupt transfer, and that name describes the service guarantee — polled at least this often — rather than the initiator. It is not related to a CPU interrupt, and an engineer who imports that meaning will be right about everything except who started the transaction.
And the consequence I would want a team to take from it: when somebody says "the device isn't sending", the first observation is not inside the device. It is whether the host is issuing tokens to that endpoint at all, because the three possible answers lead to three unrelated investigations and two of them are not about the device.
14. Exercises
1 PREDICTION
Prediction 3 in section 3 says NAK would be meaningless if
devices initiated. Explain why, in your own words, and then say
what else in the protocol would be unnecessary.
2 THE TRACE
Sketch what a bus trace of an idle mouse looks like. How would
you tell, from that trace alone, what its polling interval is?
3 VERILOG
Add STALL as a third possible answer. What state does it need,
what clears it, and does it change the resp_valid line?
4 SYSTEMVERILOG
Implement the same, and write the property that says a stalled
endpoint never answers with data.
5 VHDL
Implement it, and say which of the three languages made the
three-way answer clearest.
6 TESTBENCH
Write the directed scenario that distinguishes "the device
withheld data it had" from "the device was never asked". Then
say which of the two the intent phase already covers.
7 SVA
The progress property in section 8 has token_valid in its
antecedent. Write the version without it, and describe the
design that satisfies the weak version and not the strong one.
8 MUTATION
Write a third mutation encoding a different version of the
belief -- one where the device initiates only SOMETIMES. Predict
its directed score before running it.
9 DEBUG
A device enumerates and its interrupt endpoint never delivers.
Give the single first observation and the three branches.
10 INTERVIEW
Answer "does a USB device ever talk without being asked?" in one
sentence, then qualify it honestly in two more.15. What Carries Forward
THE CORRECTION
o HAVING WORK is the device's; STARTING A TRANSACTION is the
host's, and a device holds exactly one of the two
o "interrupt transfer" names the SERVICE GUARANTEE -- polled at
least this often -- not the initiator
o the existence of NAK is nearly a proof on its own: a device that
spoke when it had news would never need to say "not yet"
o latency from event to data is bounded by the POLLING INTERVAL,
and that bound is negotiated in a descriptor
THE HARDWARE
o resp_valid = token_valid, and pend_r does not appear in it
o a counter for the impossible case, so a build in which it became
possible would say so
o an event arriving with a token does not rescue that token's
answer: the device answers with the state it HAD
THE METHOD
o an INTENT CHECK states the claim and consults no model, which is
the only kind of check that can disagree with a model that
shares the design's mistake
o a progress property's antecedent must exclude the environment --
"if the host asks" -- or it is false for a correct design
o a misconception encoded as hardware is wrong almost EVERYWHERE,
so removing its dedicated scenario takes 1,098 to 236 rather
than to zero. The danger of a false belief is not a subtle bug;
it is a confident wrong question.
THE DEBUG CONSEQUENCE
o "why isn't the device sending" has no answer. "Is the host
polling the endpoint" has three, and they lead to three
different investigations.The next belief is about the word endpoint, and it is the one most reinforced by ordinary English.
Continue learning
Related tutorials
- Related topic
“Enumeration Is Optional”
Nobody says this out loud; they act on it — by testing a data path before the device has an address, and by debugging an endpoint the host never opened a pipe to. A state machine with one gated output is the whole refutation.
- Related topic
USB in Embedded Devices
On a microcontroller the controller is a peripheral, and the protocol can be perfectly correct while the device goes deaf. Everything turns on one question — who owns this buffer right now — answered by one bit per buffer and exhausted over 132 transitions in three languages.
- Related topic
“Endpoints Are Physical Ports”
Ordinary English makes an endpoint sound like a place, and every neighbouring bus has ports that are sockets. The prediction is that endpoint 2 is one thing. A four-line decoder shows that an endpoint is a number, a direction, and a declaration.
- Related topic
“Bulk Transfers Are Always Fastest”
Usually true, which is what makes it dangerous. Bulk has the largest packets and no rate limit, and on a quiet bus it wins every benchmark. A frame-budget allocator shows whose property throughput actually is.
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.
