USB · Module 20
Link Training
A SuperSpeed link must train itself before carrying anything — and cannot use the link to do it, so the earliest signalling runs with the high-speed transmitter off.
Chapter 20.2 treated ss_training_ok and ss_training_fail as inputs — signals that arrive from somewhere and decide which bus wins.
This is that somewhere.
1. A USB 2 Bus Just Works. A SuperSpeed Link Does Not.
Drive a USB 2 pair after a reset and both ends already agree on what the voltages mean and roughly when to sample them. Nothing has to be negotiated because nothing was ever in question.
A SuperSpeed link cannot carry one packet until it has:
1. found out whether there is a receiver at the far end AT ALL
2. agreed on bit timing, recovered a clock, aligned symbol boundaries
3. confirmed all of that in BOTH directionsThat is what "the link trains itself" means, and it is the deepest architectural difference between USB 2 and USB 3 — deeper than the speed, deeper than the extra wires.
2. The Chicken and the Egg
Steps 1 and 2 cannot use the trained link, because the link is what they are trying to train.
So the earliest signalling happens with the high-speed transmitter switched off, using LFPS — Low Frequency Periodic Signaling — which is slow enough that an untrained receiver can detect it without a recovered clock.
| LFPS | High speed | |
|---|---|---|
| Frequency | low, out of band | 5 Gb/s, in band |
| Needs a trained receiver? | no | yes |
| Used | before training, and in U3 | once the link is up |
3. Rx.Detect Is Not a Handshake
The first step is a DC measurement, not an exchange.
The transmitter briefly changes the common-mode voltage and watches how fast the line settles. A terminated receiver at the far end loads it differently from an open cable — so the answer comes from the electrical behaviour of the wire, not from anything the far end chooses to send.
Nothing is sent and nothing replies, which is why it works against a device that is not yet powered enough to answer.
And so there is no timeout in Rx.Detect. An empty port simply keeps looking, for ever, at almost no cost. A timeout there would be a timeout on a question nobody was asked.
4. Polling Times Out Into Compliance, Not Into Failure
This is the detail that surprises people.
A link that reaches Polling and never completes does not go to an error state. It goes to Compliance — a mode that exists for test equipment to drive specified patterns into.
The reasoning is sound and worth following. A port stuck in Polling with nothing at the far end is exactly what a compliance tester looks like: something is there electrically, it is exchanging LFPS, and it never completes training. A port that fell into an error state instead would be impossible to test, because the act of testing it would break it.
Mutation L2 sends the timeout to SS.Inactive instead and dies 14 944 times.
Recovery is different, and fails properly. A link that was up and cannot be retrained is broken rather than absent, and a tester is not the likely explanation — so Recovery times out into SS.Inactive, which is what 20.2's arbiter reads as give up on SuperSpeed.
5. Two Dead Ends, One Fallback Signal
training_fail covers both. SS.Inactive and Compliance are different states for different reasons — but from the arbiter's point of view they are identical: neither will ever carry data, and both need the same decision taken about them.
Mutation L6 covers only SS.Inactive and dies 7472 times — the smallest count in Module 20, because it is wrong only while the link sits in Compliance, which is one of ten states.
6. The State Machine, Drawn
The two timeout edges are the chapter. They leave states that look similar and arrive at states that mean opposite things.
7. The Hardware, Before Any Language
Ten states, one timer, and two transmitter enables that are decodes of the state.
LFPS is driven in Rx.Detect, Polling, Compliance and U3. The first three are before-or-instead-of training; U3 is the interesting one — it is SuperSpeed's suspend, the link is fully down, and LFPS is the only thing that can wake it. That is why lfps_tx is asserted there and hs_tx_enable is not.
An error outranks a low-power request in U0. A link that has gone bad must not be put to sleep — it would wake into the same fault, having lost the chance to recover from it. Mutation L3, 38 805 errors.
Every low-power state returns through Recovery, never straight to U0, because the receiver has to re-lock before data can flow. Mutation L4, 51 591 errors.
And a warm reset works from every state, not only the dead ones — a reset that only escaped SS.Inactive and Compliance could not recover a link that was merely confused. Mutation L7, 92 808 errors — the largest in Module 20 after 20.2's D3.
8. Verilog-2005
// usb3_ltssm -- the Link Training and Status State Machine, and the
// chicken-and-egg problem at the start of it.
//
// A USB 2 bus simply works after a reset. Drive the lines, and both ends
// already agree on what the voltages mean and roughly when to sample them.
//
// A SuperSpeed link does not work after a reset. Before one packet can be
// carried it must:
//
// 1. find out whether there is a receiver at the far end AT ALL;
// 2. agree on bit timing, recover a clock, and align symbol boundaries;
// 3. confirm all of that in BOTH directions;
//
// and only then carry data. That is what "the link trains itself" means, and
// it is the deepest architectural difference between USB 2 and USB 3.
//
// THE CHICKEN AND EGG
//
// Steps 1 and 2 cannot use the trained link, because the link is what they
// are trying to train. So the earliest signalling happens with the
// high-speed transmitter SWITCHED OFF, using LFPS -- Low Frequency Periodic
// Signaling -- which is slow enough that an untrained receiver can detect it
// without a recovered clock.
//
// LFPS out-of-band, low frequency, works before training
// HIGH SPEED in-band, 5 Gb/s, requires a trained receiver
//
// They are two different transmitters on the SAME wires, so the one property
// this block must never violate is that BOTH ARE NEVER ON AT ONCE. That is
// not a protocol error; it is two drivers fighting over a differential pair.
//
// AND Rx.Detect IS NOT A HANDSHAKE
//
// The first step is a DC measurement, not an exchange. The transmitter
// briefly changes the common-mode voltage and watches how fast the line
// settles; a terminated receiver at the far end loads it differently from an
// open cable. Nothing is sent and nothing replies -- which is why it works
// against a device that is not powered enough to answer yet.
//
// POLLING TIMES OUT INTO COMPLIANCE, NOT INTO FAILURE
//
// The detail that surprises people. A link that reaches Polling and never
// completes does NOT go to an error state -- it goes to COMPLIANCE, a mode
// that exists for test equipment to drive specified patterns into. The
// reasoning is that a port stuck in Polling with nothing at the far end is
// exactly what a compliance tester looks like, and a port that fell into an
// error state instead would be untestable.
module usb3_ltssm #(
parameter integer POLL_TIMEOUT = 12, // Polling -> Compliance
parameter integer RECOVERY_TIMEOUT = 8 // Recovery -> SS.Inactive
) (
input wire clk,
input wire rst_n,
input wire enable, // the port is enabled at all
input wire rx_detected, // a terminated receiver is out there
input wire training_done, // TS1/TS2 exchange completed
input wire link_error, // an established link went bad
input wire lp_request, // the host asked for a low-power state
input wire [1:0] lp_level, // which one: U1, U2 or U3
input wire wakeup, // leave the low-power state
input wire warm_reset, // the only way out of the dead states
output wire [3:0] ltssm_state,
output wire lfps_tx, // the LOW-frequency transmitter
output wire hs_tx_enable, // the HIGH-speed transmitter
output wire link_up, // U0: the link carries data
output wire training_ok, // --> chapter 20.2's arbiter
output wire training_fail,
output reg [15:0] timer,
output reg [31:0] poll_timeouts,
output reg [31:0] recoveries,
output reg ever_compliance
);
localparam [3:0] L_DISABLED = 4'd0,
L_RX_DETECT = 4'd1,
L_POLLING = 4'd2,
L_U0 = 4'd3, // the link is up
L_U1 = 4'd4,
L_U2 = 4'd5,
L_U3 = 4'd6,
L_RECOVERY = 4'd7,
L_INACTIVE = 4'd8, // failed; waits for a warm reset
L_COMPLIANCE = 4'd9; // test mode, NOT an error state
reg [3:0] state;
assign ltssm_state = state;
// THE MUTUAL EXCLUSION, and it is electrical rather than logical: these
// are two different transmitters on the SAME differential pair.
//
// LFPS is driven in the states that happen BEFORE the link is trained,
// plus Compliance, where a tester expects it. The high-speed transmitter
// is enabled only where a trained receiver exists to hear it.
assign lfps_tx = (state == L_RX_DETECT) || (state == L_POLLING)
|| (state == L_COMPLIANCE) || (state == L_U3);
assign hs_tx_enable = (state == L_U0) || (state == L_RECOVERY);
assign link_up = (state == L_U0);
assign training_ok = (state == L_U0);
// The arbiter of chapter 20.2 needs ONE signal meaning "give up on
// SuperSpeed", and both dead ends produce it: a link that timed out into
// Compliance and one that failed recovery are equally unusable for data.
assign training_fail = (state == L_INACTIVE) || (state == L_COMPLIANCE);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= L_DISABLED;
timer <= 16'd0;
poll_timeouts <= 32'd0;
recoveries <= 32'd0;
ever_compliance <= 1'b0;
end else if (!enable) begin
// Disabling the port outranks everything, from any state.
state <= L_DISABLED;
timer <= 16'd0;
end else if (warm_reset) begin
// THE ONLY WAY OUT of Inactive and Compliance -- and it works from
// every state, because a reset that only worked from the dead states
// could not recover a link that was merely confused.
state <= L_RX_DETECT;
timer <= 16'd0;
end else begin
case (state)
L_DISABLED: begin
state <= L_RX_DETECT;
timer <= 16'd0;
end
L_RX_DETECT: begin
// A DC measurement, not an exchange. Nothing is sent and nothing
// replies, so there is no timeout here -- an empty port simply
// keeps looking, for ever, at almost no cost.
if (rx_detected) begin
state <= L_POLLING;
timer <= 16'd0;
end
end
L_POLLING: begin
if (training_done) begin
state <= L_U0;
timer <= 16'd0;
end else if (timer + 16'd1 >= POLL_TIMEOUT[15:0]) begin
// NOT an error state. A port stuck here with nothing at the far
// end is indistinguishable from a port attached to a compliance
// tester, and a port that failed into an error state instead
// would be impossible to test.
state <= L_COMPLIANCE;
timer <= 16'd0;
poll_timeouts <= poll_timeouts + 32'd1;
ever_compliance <= 1'b1;
end else begin
timer <= timer + 16'd1;
end
end
L_U0: begin
// Precedence: an error outranks a low-power request. A link that
// has gone bad must not be put to sleep -- it would wake into the
// same fault having lost the chance to recover from it.
if (link_error) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 32'd1;
end else if (lp_request) begin
case (lp_level)
2'd0: state <= L_U1;
2'd1: state <= L_U2;
default: state <= L_U3;
endcase
timer <= 16'd0;
end
end
L_U1, L_U2: begin
// U1 and U2 differ only in how deeply the PHY is powered down and
// therefore in how long they take to leave. Both return through
// RECOVERY rather than straight to U0, because the receiver has
// to re-lock before data can flow.
if (wakeup) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 32'd1;
end
end
L_U3: begin
// U3 is SuperSpeed's suspend, and it is the deepest: the link is
// fully down and only LFPS can wake it, which is why lfps_tx is
// asserted here and hs_tx_enable is not.
if (wakeup) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 32'd1;
end
end
L_RECOVERY: begin
// Re-training an ESTABLISHED link. It keeps the configuration U0
// had -- which is what makes recovery cheaper than starting from
// Rx.Detect, and why a transient does not cost an enumeration.
if (training_done) begin
state <= L_U0;
timer <= 16'd0;
end else if (timer + 16'd1 >= RECOVERY_TIMEOUT[15:0]) begin
// Recovery DOES fail into a dead state, unlike Polling: a link
// that was up and cannot be retrained is broken rather than
// absent, and a tester is not the likely explanation.
state <= L_INACTIVE;
timer <= 16'd0;
end else begin
timer <= timer + 16'd1;
end
end
L_INACTIVE: begin
// Waits for the warm reset handled above. Chapter 20.2's arbiter
// sees training_fail from here and falls back to USB 2.
state <= L_INACTIVE;
end
L_COMPLIANCE: begin
// Also waits for the warm reset. A tester drives patterns at the
// port and the port answers with LFPS; nothing here is an error.
state <= L_COMPLIANCE;
end
default: state <= L_DISABLED;
endcase
end
end
endmoduletraining_fail is one expression covering both dead ends (§5) rather than two signals the arbiter would have to OR together. The arbiter needs one bit meaning give up; giving it two would move that OR across a module boundary for no benefit.
9. SystemVerilog
package usb3_ltssm_pkg;
// The LTSSM states this design distinguishes. Two of them are dead ends
// and they are NOT the same dead end: L_INACTIVE is a link that was up and
// could not be retrained, L_COMPLIANCE is a port that never found anything
// and is now assumed to be attached to a tester.
typedef enum logic [3:0] {
L_DISABLED,
L_RX_DETECT, // a DC measurement, not an exchange
L_POLLING, // LFPS handshake, then TS1/TS2
L_U0, // the link carries data
L_U1, // light sleep
L_U2, // deeper sleep
L_U3, // SuperSpeed's suspend -- only LFPS wakes it
L_RECOVERY, // re-train an ESTABLISHED link
L_INACTIVE, // failed; waits for a warm reset
L_COMPLIANCE // test mode -- NOT an error state
} ltssm_state_e;
endpackage
// usb3_ltssm_sv -- the Link Training and Status State Machine, and the
// chicken-and-egg problem at the start of it.
//
// A USB 2 bus simply works after a reset. Drive the lines, and both ends
// already agree on what the voltages mean and roughly when to sample them.
//
// A SuperSpeed link does not work after a reset. Before one packet can be
// carried it must:
//
// 1. find out whether there is a receiver at the far end AT ALL;
// 2. agree on bit timing, recover a clock, and align symbol boundaries;
// 3. confirm all of that in BOTH directions;
//
// and only then carry data. That is what "the link trains itself" means, and
// it is the deepest architectural difference between USB 2 and USB 3.
//
// THE CHICKEN AND EGG
//
// Steps 1 and 2 cannot use the trained link, because the link is what they
// are trying to train. So the earliest signalling happens with the
// high-speed transmitter SWITCHED OFF, using LFPS -- Low Frequency Periodic
// Signaling -- which is slow enough that an untrained receiver can detect it
// without a recovered clock.
//
// LFPS out-of-band, low frequency, works before training
// HIGH SPEED in-band, 5 Gb/s, requires a trained receiver
//
// They are two different transmitters on the SAME wires, so the one property
// this block must never violate is that BOTH ARE NEVER ON AT ONCE. That is
// not a protocol error; it is two drivers fighting over a differential pair.
//
// AND Rx.Detect IS NOT A HANDSHAKE
//
// The first step is a DC measurement, not an exchange. The transmitter
// briefly changes the common-mode voltage and watches how fast the line
// settles; a terminated receiver at the far end loads it differently from an
// open cable. Nothing is sent and nothing replies -- which is why it works
// against a device that is not powered enough to answer yet.
//
// POLLING TIMES OUT INTO COMPLIANCE, NOT INTO FAILURE
//
// The detail that surprises people. A link that reaches Polling and never
// completes does NOT go to an error state -- it goes to COMPLIANCE, a mode
// that exists for test equipment to drive specified patterns into. The
// reasoning is that a port stuck in Polling with nothing at the far end is
// exactly what a compliance tester looks like, and a port that fell into an
// error state instead would be untestable.
module usb3_ltssm_sv
import usb3_ltssm_pkg::*;
#(
parameter int unsigned POLL_TIMEOUT = 12, // Polling -> Compliance
parameter int unsigned RECOVERY_TIMEOUT = 8 // Recovery -> SS.Inactive
) (
input logic clk,
input logic rst_n,
input logic enable, // the port is enabled at all
input logic rx_detected, // a terminated receiver is out there
input logic training_done, // TS1/TS2 exchange completed
input logic link_error, // an established link went bad
input logic lp_request, // the host asked for a low-power state
input logic [1:0] lp_level, // which one: U1, U2 or U3
input logic wakeup, // leave the low-power state
input logic warm_reset, // the only way out of the dead states
output ltssm_state_e ltssm_state,
output logic lfps_tx, // the LOW-frequency transmitter
output logic hs_tx_enable, // the HIGH-speed transmitter
output logic link_up, // U0: the link carries data
output logic training_ok, // --> chapter 20.2's arbiter
output logic training_fail,
output logic [15:0] timer,
output logic [31:0] poll_timeouts,
output logic [31:0] recoveries,
output logic ever_compliance
);
ltssm_state_e state;
assign ltssm_state = state;
// THE MUTUAL EXCLUSION, and it is electrical rather than logical: these
// are two different transmitters on the SAME differential pair.
//
// LFPS is driven in the states that happen BEFORE the link is trained,
// plus Compliance, where a tester expects it. The high-speed transmitter
// is enabled only where a trained receiver exists to hear it.
assign lfps_tx = (state == L_RX_DETECT) || (state == L_POLLING)
|| (state == L_COMPLIANCE) || (state == L_U3);
assign hs_tx_enable = (state == L_U0) || (state == L_RECOVERY);
assign link_up = (state == L_U0);
assign training_ok = (state == L_U0);
// The arbiter of chapter 20.2 needs ONE signal meaning "give up on
// SuperSpeed", and both dead ends produce it: a link that timed out into
// Compliance and one that failed recovery are equally unusable for data.
assign training_fail = (state == L_INACTIVE) || (state == L_COMPLIANCE);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= L_DISABLED;
timer <= 16'd0;
poll_timeouts <= '0;
recoveries <= '0;
ever_compliance <= 1'b0;
end else if (!enable) begin
// Disabling the port outranks everything, from any state.
state <= L_DISABLED;
timer <= 16'd0;
end else if (warm_reset) begin
// THE ONLY WAY OUT of Inactive and Compliance -- and it works from
// every state, because a reset that only worked from the dead states
// could not recover a link that was merely confused.
state <= L_RX_DETECT;
timer <= 16'd0;
end else begin
case (state)
L_DISABLED: begin
state <= L_RX_DETECT;
timer <= 16'd0;
end
L_RX_DETECT: begin
// A DC measurement, not an exchange. Nothing is sent and nothing
// replies, so there is no timeout here -- an empty port simply
// keeps looking, for ever, at almost no cost.
if (rx_detected) begin
state <= L_POLLING;
timer <= 16'd0;
end
end
L_POLLING: begin
if (training_done) begin
state <= L_U0;
timer <= 16'd0;
end else if (timer + 16'd1 >= 16'(POLL_TIMEOUT)) begin
// NOT an error state. A port stuck here with nothing at the far
// end is indistinguishable from a port attached to a compliance
// tester, and a port that failed into an error state instead
// would be impossible to test.
state <= L_COMPLIANCE;
timer <= 16'd0;
poll_timeouts <= poll_timeouts + 1;
ever_compliance <= 1'b1;
end else begin
timer <= timer + 16'd1;
end
end
L_U0: begin
// Precedence: an error outranks a low-power request. A link that
// has gone bad must not be put to sleep -- it would wake into the
// same fault having lost the chance to recover from it.
if (link_error) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 1;
end else if (lp_request) begin
case (lp_level)
2'd0: state <= L_U1;
2'd1: state <= L_U2;
default: state <= L_U3;
endcase
timer <= 16'd0;
end
end
L_U1, L_U2: begin
// U1 and U2 differ only in how deeply the PHY is powered down and
// therefore in how long they take to leave. Both return through
// RECOVERY rather than straight to U0, because the receiver has
// to re-lock before data can flow.
if (wakeup) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 1;
end
end
L_U3: begin
// U3 is SuperSpeed's suspend, and it is the deepest: the link is
// fully down and only LFPS can wake it, which is why lfps_tx is
// asserted here and hs_tx_enable is not.
if (wakeup) begin
state <= L_RECOVERY;
timer <= 16'd0;
recoveries <= recoveries + 1;
end
end
L_RECOVERY: begin
// Re-training an ESTABLISHED link. It keeps the configuration U0
// had -- which is what makes recovery cheaper than starting from
// Rx.Detect, and why a transient does not cost an enumeration.
if (training_done) begin
state <= L_U0;
timer <= 16'd0;
end else if (timer + 16'd1 >= 16'(RECOVERY_TIMEOUT)) begin
// Recovery DOES fail into a dead state, unlike Polling: a link
// that was up and cannot be retrained is broken rather than
// absent, and a tester is not the likely explanation.
state <= L_INACTIVE;
timer <= 16'd0;
end else begin
timer <= timer + 16'd1;
end
end
L_INACTIVE: begin
// Waits for the warm reset handled above. Chapter 20.2's arbiter
// sees training_fail from here and falls back to USB 2.
state <= L_INACTIVE;
end
L_COMPLIANCE: begin
// Also waits for the warm reset. A tester drives patterns at the
// port and the port answers with LFPS; nothing here is an error.
state <= L_COMPLIANCE;
end
default: state <= L_DISABLED;
endcase
end
end
endmoduleTen named states in two bits fewer than you would guess. ltssm_state_e is logic [3:0] and names ten of sixteen encodings — which is exactly why the Verilog needs its default: arm and this does not: six encodings exist there that the case statement does not describe.
10. VHDL-2008
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package usb3_ltssm_pkg is
-- The LTSSM states this design distinguishes. Two of them are dead ends
-- and they are NOT the same dead end: L_INACTIVE is a link that was up and
-- could not be retrained, L_COMPLIANCE is a port that never found anything
-- and is now assumed to be attached to a tester.
type ltssm_state_t is (
L_DISABLED,
L_RX_DETECT, -- a DC measurement, not an exchange
L_POLLING, -- LFPS handshake, then TS1/TS2
L_U0, -- the link carries data
L_U1, -- light sleep
L_U2, -- deeper sleep
L_U3, -- SuperSpeed's suspend -- only LFPS wakes it
L_RECOVERY, -- re-train an ESTABLISHED link
L_INACTIVE, -- failed; waits for a warm reset
L_COMPLIANCE -- test mode -- NOT an error state
);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.usb3_ltssm_pkg.all;
-- usb3_ltssm_vhdl -- the Link Training and Status State Machine, and the
-- chicken-and-egg problem at the start of it.
--
-- A USB 2 bus simply works after a reset. Drive the lines, and both ends
-- already agree on what the voltages mean and roughly when to sample them.
--
-- A SuperSpeed link does not work after a reset. Before one packet can be
-- carried it must:
--
-- 1. find out whether there is a receiver at the far end AT ALL;
-- 2. agree on bit timing, recover a clock, and align symbol boundaries;
-- 3. confirm all of that in BOTH directions;
--
-- and only then carry data. That is what "the link trains itself" means, and
-- it is the deepest architectural difference between USB 2 and USB 3.
--
-- THE CHICKEN AND EGG
--
-- Steps 1 and 2 cannot use the trained link, because the link is what they
-- are trying to train. So the earliest signalling happens with the
-- high-speed transmitter SWITCHED OFF, using LFPS -- Low Frequency Periodic
-- Signaling -- which is slow enough that an untrained receiver can detect it
-- without a recovered clock.
--
-- LFPS out-of-band, low frequency, works before training
-- HIGH SPEED in-band, 5 Gb/s, requires a trained receiver
--
-- They are two different transmitters on the SAME wires, so the one property
-- this block must never violate is that BOTH ARE NEVER ON AT ONCE. That is
-- not a protocol error; it is two drivers fighting over a differential pair.
--
-- AND Rx.Detect IS NOT A HANDSHAKE
--
-- The first step is a DC measurement, not an exchange. The transmitter
-- briefly changes the common-mode voltage and watches how fast the line
-- settles; a terminated receiver at the far end loads it differently from an
-- open cable. Nothing is sent and nothing replies -- which is why it works
-- against a device that is not powered enough to answer yet.
--
-- POLLING TIMES OUT INTO COMPLIANCE, NOT INTO FAILURE
--
-- The detail that surprises people. A link that reaches Polling and never
-- completes does NOT go to an error state -- it goes to COMPLIANCE, a mode
-- that exists for test equipment to drive specified patterns into. The
-- reasoning is that a port stuck in Polling with nothing at the far end is
-- exactly what a compliance tester looks like, and a port that fell into an
-- error state instead would be untestable.
entity usb3_ltssm_vhdl is
generic (
POLL_TIMEOUT : positive := 12; -- Polling -> Compliance
RECOVERY_TIMEOUT : positive := 8 -- Recovery -> SS.Inactive
);
port (
clk : in std_logic;
rst_n : in std_logic;
enable : in std_logic;
rx_detected : in std_logic;
training_done : in std_logic;
link_error : in std_logic;
lp_request : in std_logic;
lp_level : in unsigned(1 downto 0);
wakeup : in std_logic;
warm_reset : in std_logic;
ltssm_state : out ltssm_state_t;
lfps_tx : out std_logic;
hs_tx_enable : out std_logic;
link_up : out std_logic;
training_ok : out std_logic;
training_fail : out std_logic;
timer : out unsigned(15 downto 0);
poll_timeouts : out unsigned(31 downto 0);
recoveries : out unsigned(31 downto 0);
ever_compliance : out std_logic
);
end entity;
architecture rtl of usb3_ltssm_vhdl is
signal st_r : ltssm_state_t := L_DISABLED;
signal tmr_r : unsigned(15 downto 0) := (others => '0');
signal pt_r : unsigned(31 downto 0) := (others => '0');
signal rec_r : unsigned(31 downto 0) := (others => '0');
signal comp_r : std_logic := '0';
begin
ltssm_state <= st_r;
timer <= tmr_r;
poll_timeouts <= pt_r;
recoveries <= rec_r;
ever_compliance <= comp_r;
-- THE MUTUAL EXCLUSION, and it is electrical rather than logical: these
-- are two different transmitters on the SAME differential pair.
--
-- LFPS is driven in the states that happen BEFORE the link is trained,
-- plus Compliance, where a tester expects it. The high-speed transmitter
-- is enabled only where a trained receiver exists to hear it.
lfps_tx <= '1' when (st_r = L_RX_DETECT or st_r = L_POLLING
or st_r = L_COMPLIANCE or st_r = L_U3) else '0';
hs_tx_enable <= '1' when (st_r = L_U0 or st_r = L_RECOVERY) else '0';
link_up <= '1' when st_r = L_U0 else '0';
training_ok <= '1' when st_r = L_U0 else '0';
-- The arbiter of chapter 20.2 needs ONE signal meaning "give up on
-- SuperSpeed", and both dead ends produce it.
training_fail <= '1' when (st_r = L_INACTIVE or st_r = L_COMPLIANCE)
else '0';
process (clk, rst_n)
begin
if rst_n = '0' then
st_r <= L_DISABLED; tmr_r <= (others => '0');
pt_r <= (others => '0'); rec_r <= (others => '0'); comp_r <= '0';
elsif rising_edge(clk) then
if enable = '0' then
-- Disabling the port outranks everything, from any state.
st_r <= L_DISABLED; tmr_r <= (others => '0');
elsif warm_reset = '1' then
-- THE ONLY WAY OUT of Inactive and Compliance -- and it works from
-- every state, because a reset that only worked from the dead
-- states could not recover a link that was merely confused.
st_r <= L_RX_DETECT; tmr_r <= (others => '0');
else
case st_r is
when L_DISABLED =>
st_r <= L_RX_DETECT; tmr_r <= (others => '0');
when L_RX_DETECT =>
-- A DC measurement, not an exchange. Nothing is sent and
-- nothing replies, so there is no timeout here -- an empty
-- port simply keeps looking, for ever, at almost no cost.
if rx_detected = '1' then
st_r <= L_POLLING; tmr_r <= (others => '0');
end if;
when L_POLLING =>
if training_done = '1' then
st_r <= L_U0; tmr_r <= (others => '0');
elsif tmr_r + 1 >= to_unsigned(POLL_TIMEOUT, 16) then
-- NOT an error state. A port stuck here with nothing at the
-- far end is indistinguishable from a port attached to a
-- compliance tester, and a port that failed into an error
-- state instead would be impossible to test.
st_r <= L_COMPLIANCE; tmr_r <= (others => '0');
pt_r <= pt_r + 1; comp_r <= '1';
else
tmr_r <= tmr_r + 1;
end if;
when L_U0 =>
-- Precedence: an error outranks a low-power request. A link
-- that has gone bad must not be put to sleep -- it would wake
-- into the same fault having lost the chance to recover.
if link_error = '1' then
st_r <= L_RECOVERY; tmr_r <= (others => '0');
rec_r <= rec_r + 1;
elsif lp_request = '1' then
case to_integer(lp_level) is
when 0 => st_r <= L_U1;
when 1 => st_r <= L_U2;
when others => st_r <= L_U3;
end case;
tmr_r <= (others => '0');
end if;
when L_U1 | L_U2 =>
-- U1 and U2 differ only in how deeply the PHY is powered down.
-- Both return through RECOVERY rather than straight to U0,
-- because the receiver has to re-lock before data can flow.
if wakeup = '1' then
st_r <= L_RECOVERY; tmr_r <= (others => '0');
rec_r <= rec_r + 1;
end if;
when L_U3 =>
-- U3 is SuperSpeed's suspend, and it is the deepest: the link
-- is fully down and only LFPS can wake it, which is why
-- lfps_tx is asserted here and hs_tx_enable is not.
if wakeup = '1' then
st_r <= L_RECOVERY; tmr_r <= (others => '0');
rec_r <= rec_r + 1;
end if;
when L_RECOVERY =>
-- Re-training an ESTABLISHED link. It keeps the configuration
-- U0 had -- which is what makes recovery cheaper than starting
-- from Rx.Detect, and why a transient does not cost an
-- enumeration.
if training_done = '1' then
st_r <= L_U0; tmr_r <= (others => '0');
elsif tmr_r + 1 >= to_unsigned(RECOVERY_TIMEOUT, 16) then
-- Recovery DOES fail into a dead state, unlike Polling: a
-- link that was up and cannot be retrained is broken rather
-- than absent, and a tester is not the likely explanation.
st_r <= L_INACTIVE; tmr_r <= (others => '0');
else
tmr_r <= tmr_r + 1;
end if;
when L_INACTIVE =>
-- Waits for the warm reset handled above. Chapter 20.2's
-- arbiter sees training_fail from here and falls back to USB 2.
st_r <= L_INACTIVE;
when L_COMPLIANCE =>
-- Also waits for the warm reset. A tester drives patterns at
-- the port and the port answers with LFPS; nothing is an error.
st_r <= L_COMPLIANCE;
end case;
end if;
end if;
end process;
end architecture;when L_U1 | L_U2 => collapses two states into one arm without the fall-through ambiguity Verilog's L_U1, L_U2: carries — and VHDL rejects the case at analysis time if any enumerator is left unhandled, so the ten arms are provably the whole machine.
11. The Waveform
The handover: LFPS off, high speed on, same pair
10 cyclesAdd lfps_tx and hs_tx_en at any tick and the answer is never 2. That is §2's property seen as a picture, and it is worth seeing because the failure it prevents does not look like a protocol bug in a trace — it looks like a transceiver that stopped working.
Cycle 8 is worth a second look. Recovery keeps the high-speed transmitter, not LFPS: it is re-training an established link, which retains the configuration U0 had. That is what makes a transient cost a few microseconds instead of a full enumeration.
12. The Testbench: 5120 Transitions, Exhaustively
Ten states × 512 input combinations — seven control signals plus a two-bit low-power level — is the entire one-step domain.
// 10 states x all 128 combinations of the seven control inputs
// (enable, rx_detected, training_done, link_error, lp_request, wakeup,
// warm_reset) x 4 low-power levels = 5120 transitions.
for (pos=0; pos<10; pos=pos+1)
for (ic=0; ic<512; ic=ic+1) begin
goto(pos);
tick(ic[0], ic[1], ic[2], ic[3], ic[4], ic[8:7], ic[5], ic[6]);goto drives to each of the ten states legitimately — through a real sequence, including counting out a full timeout to reach Compliance and SS.Inactive — so no state is reached by forcing.
Three properties are checked against no model:
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE one that matters, and it is electrical: the low-frequency
// and high-speed transmitters share a differential pair. Both on
// is two drivers fighting, not a protocol error.
check(!(lfps_tx && hs_tx_enable),
"LFPS and the high-speed transmitter both driving the same pair");
// 2. Data only ever flows on a trained link.
check(!link_up || hs_tx_enable,
"the link reported up with its high-speed transmitter off");
// 3. A link cannot be simultaneously trained and failed.
check(!(training_ok && training_fail),
"the link reported trained AND failed at once");And the model derives the transmitter enables from a list of states where the design uses a boolean expression — same answer, different route, so a mistake in one is not automatically a mistake in the other.
Measured reach:
exhaustive transition sweep: 5120 of 5120 transitions verified
Verilog / SystemVerilog:
REACH: lfps=28040 hs=18059 u0=11997 poll-timeouts=328 recoveries=1370
STATES: dis=6360 rxd=7228 poll=11017 u0=11997 u1=1418 u2=1377
u3=2324 rec=6062 inact=8854 comp=7471
VHDL:
REACH: lfps=27818 hs=19248 u0=12904 poll-timeouts=295 recoveries=1481
[VHDL] usb3_ltssm_vhdl: 0 errors — PASSEvery one of the ten states is visited over a thousand times, and the run asserts that explicitly rather than assuming it — a state never entered is a state never tested, however many transitions the sweep counted.
12.1 The complete Verilog testbench
The excerpts above are the parts worth arguing about. Here is the whole thing — the sweeps, the reference model, the safety properties and the reach assertions, exactly as simulated against the usb3_ltssm listing published in this chapter.
`timescale 1ns/1ps
module tb_lt_v;
localparam PT = 5, RT = 4; // small timeouts so a whole life fits
reg clk=0, rst_n=0;
reg en=0, rxd=0, tdone=0, lerr=0, lpr=0, wk=0, wrst=0;
reg [1:0] lpl=0;
wire [3:0] ltssm_state;
wire lfps_tx, hs_tx_enable, link_up, training_ok, training_fail;
wire [15:0] timer;
wire [31:0] poll_timeouts, recoveries;
wire ever_compliance;
always #5 clk=~clk;
usb3_ltssm #(.POLL_TIMEOUT(PT), .RECOVERY_TIMEOUT(RT)) dut (
.clk(clk), .rst_n(rst_n), .enable(en), .rx_detected(rxd),
.training_done(tdone), .link_error(lerr), .lp_request(lpr),
.lp_level(lpl), .wakeup(wk), .warm_reset(wrst),
.ltssm_state(ltssm_state), .lfps_tx(lfps_tx),
.hs_tx_enable(hs_tx_enable), .link_up(link_up),
.training_ok(training_ok), .training_fail(training_fail),
.timer(timer), .poll_timeouts(poll_timeouts), .recoveries(recoveries),
.ever_compliance(ever_compliance));
localparam [3:0] S_DIS=0, S_RXD=1, S_POLL=2, S_U0=3, S_U1=4, S_U2=5,
S_U3=6, S_REC=7, S_INACT=8, S_COMP=9;
integer errors=0, i, pos, ic;
integer n_trans=0, n_lfps=0, n_hs=0, n_u0=0;
integer n_vis [0:9];
// ---- INDEPENDENT MODEL: its own state, timer and counters ----
integer m_state, m_timer, m_pt, m_rec;
reg m_comp;
task check(input cond, input [639:0] msg);
begin if (!cond) begin errors=errors+1;
if (errors <= 25)
$display(" FAIL: %0s (en=%b rxd=%b td=%b err=%b lp=%b wk=%b wr=%b | st=%0d lfps=%b hs=%b tmr=%0d, t=%0t)",
msg, en, rxd, tdone, lerr, lpr, wk, wrst, ltssm_state,
lfps_tx, hs_tx_enable, timer, $time);
end end
endtask
// The model derives the two transmitter enables from a LIST of states
// rather than from the design's expression -- a different route to the
// same answer.
function e_lfps(input integer s);
begin e_lfps = (s==S_RXD)||(s==S_POLL)||(s==S_COMP)||(s==S_U3); end
endfunction
function e_hs(input integer s);
begin e_hs = (s==S_U0)||(s==S_REC); end
endfunction
task tick(input e, input rd, input td, input le, input lp,
input [1:0] lv, input w, input wr);
integer nst, ntm;
begin
en=e; rxd=rd; tdone=td; lerr=le; lpr=lp; lpl=lv; wk=w; wrst=wr; #1;
// ---- combinational contract ----
check(ltssm_state === m_state[3:0], "state matches the independent model");
check(timer === m_timer[15:0], "timer matches the model");
check(lfps_tx === e_lfps(m_state), "lfps_tx names the right states");
check(hs_tx_enable === e_hs(m_state), "hs_tx_enable names the right states");
check(link_up === (m_state == S_U0), "link_up is U0 and nothing else");
check(training_ok === (m_state == S_U0), "training_ok is U0");
check(training_fail === ((m_state == S_INACT) || (m_state == S_COMP)),
"training_fail covers BOTH dead ends");
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE one that matters, and it is electrical: the low-frequency
// and high-speed transmitters share a differential pair. Both on
// is two drivers fighting, not a protocol error.
check(!(lfps_tx && hs_tx_enable),
"LFPS and the high-speed transmitter both driving the same pair");
// 2. Data only ever flows on a trained link.
check(!link_up || hs_tx_enable,
"the link reported up with its high-speed transmitter off");
// 3. A link cannot be simultaneously trained and failed.
check(!(training_ok && training_fail),
"the link reported trained AND failed at once");
if (lfps_tx) n_lfps = n_lfps + 1;
if (hs_tx_enable) n_hs = n_hs + 1;
if (link_up) n_u0 = n_u0 + 1;
if (m_state >= 0 && m_state <= 9) n_vis[m_state] = n_vis[m_state] + 1;
// ---- advance the model ----
nst = m_state; ntm = m_timer;
if (!e) begin nst = S_DIS; ntm = 0; end
else if (wr) begin nst = S_RXD; ntm = 0; end
else case (m_state)
S_DIS: begin nst = S_RXD; ntm = 0; end
S_RXD: if (rd) begin nst = S_POLL; ntm = 0; end
S_POLL: begin
if (td) begin nst = S_U0; ntm = 0; end
else if (m_timer + 1 >= PT) begin
nst = S_COMP; ntm = 0; m_pt = m_pt + 1; m_comp = 1;
end else ntm = m_timer + 1;
end
S_U0: begin
if (le) begin nst = S_REC; ntm = 0; m_rec = m_rec + 1; end
else if (lp) begin
nst = (lv == 2'd0) ? S_U1 : (lv == 2'd1) ? S_U2 : S_U3;
ntm = 0;
end
end
S_U1, S_U2, S_U3:
if (w) begin nst = S_REC; ntm = 0; m_rec = m_rec + 1; end
S_REC: begin
if (td) begin nst = S_U0; ntm = 0; end
else if (m_timer + 1 >= RT) begin nst = S_INACT; ntm = 0; end
else ntm = m_timer + 1;
end
S_INACT: nst = S_INACT;
S_COMP: nst = S_COMP;
endcase
@(posedge clk); #1;
m_state = nst; m_timer = ntm;
check(poll_timeouts === m_pt[31:0], "poll_timeouts matches the model");
check(recoveries === m_rec[31:0], "recoveries matches the model");
check(ever_compliance === m_comp, "ever_compliance matches the model");
end
endtask
task hard_reset;
begin
rst_n=0; en=0; rxd=0; tdone=0; lerr=0; lpr=0; lpl=0; wk=0; wrst=0;
@(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
m_state=S_DIS; m_timer=0; m_pt=0; m_rec=0; m_comp=0;
end
endtask
// Drive to any of the ten states, legitimately.
task goto(input integer p);
integer j;
begin
hard_reset;
if (p == S_DIS) begin end
else begin
tick(1,0,0,0,0,0,0,0); // -> RX_DETECT
if (p == S_RXD) begin end
else begin
tick(1,1,0,0,0,0,0,0); // -> POLLING
if (p == S_POLL) begin end
else if (p == S_COMP) begin
for (j=0;j<PT;j=j+1) tick(1,1,0,0,0,0,0,0);
end else begin
tick(1,1,1,0,0,0,0,0); // -> U0
if (p == S_U0) begin end
else if (p == S_U1) tick(1,1,0,0,1,2'd0,0,0);
else if (p == S_U2) tick(1,1,0,0,1,2'd1,0,0);
else if (p == S_U3) tick(1,1,0,0,1,2'd2,0,0);
else begin
tick(1,1,0,1,0,0,0,0); // error -> RECOVERY
if (p == S_INACT)
for (j=0;j<RT;j=j+1) tick(1,1,0,0,0,0,0,0);
end
end
end
end
end
endtask
initial begin
for (i=0;i<10;i=i+1) n_vis[i]=0;
hard_reset;
check(ltssm_state === S_DIS, "the LTSSM comes up disabled");
check(!lfps_tx && !hs_tx_enable, "with neither transmitter on");
// ===== A. EXHAUSTIVE ONE-STEP TRANSITIONS =====
// 10 states x all 128 combinations of the seven control inputs
// (enable, rx_detected, training_done, link_error, lp_request, wakeup,
// warm_reset) x 4 low-power levels = 5120 transitions.
for (pos=0; pos<10; pos=pos+1)
for (ic=0; ic<512; ic=ic+1) begin
goto(pos);
tick(ic[0], ic[1], ic[2], ic[3], ic[4], ic[8:7], ic[5], ic[6]);
n_trans = n_trans + 1;
end
$display(" exhaustive transition sweep: %0d of %0d transitions verified",
n_trans, 10*512);
// ===== B. directed: the life of a link =====
hard_reset;
tick(1,0,0,0,0,0,0,0);
check(ltssm_state === S_RXD, "an enabled port looks for a receiver");
check(lfps_tx, "using LFPS, because nothing is trained yet");
check(!hs_tx_enable, "with the high-speed transmitter off");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === S_POLL, "a detected receiver moves it to Polling");
check(lfps_tx && !hs_tx_enable, "still LFPS: training is not finished");
tick(1,1,1,0,0,0,0,0);
check(link_up, "training completes and the link is up");
check(hs_tx_enable && !lfps_tx,
"and NOW the high-speed transmitter takes the pair");
check(training_ok && !training_fail, "reported trained");
// Polling times out into COMPLIANCE, not failure
hard_reset;
tick(1,0,0,0,0,0,0,0); tick(1,1,0,0,0,0,0,0);
for (i=0;i<PT-1;i=i+1) tick(1,1,0,0,0,0,0,0);
check(ltssm_state === S_POLL, "still polling one tick short of the timeout");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === S_COMP,
"Polling times out into COMPLIANCE -- a test mode, not an error");
check(lfps_tx, "which drives LFPS, because a tester expects it");
check(training_fail, "and the arbiter of 20.2 is told to give up");
check(poll_timeouts === 32'd1, "and the timeout is counted");
// an error outranks a low-power request
goto(S_U0);
tick(1,1,0,1,1,2'd0,0,0);
check(ltssm_state === S_REC,
"an error outranks a low-power request: recover, do not sleep");
// Recovery DOES fail into a dead state
goto(S_U0);
tick(1,1,0,1,0,0,0,0);
check(ltssm_state === S_REC, "an error enters Recovery");
for (i=0;i<RT-1;i=i+1) tick(1,1,0,0,0,0,0,0);
check(ltssm_state === S_REC, "still recovering");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === S_INACT,
"Recovery fails into SS.Inactive -- a link that WAS up is broken");
check(training_fail, "and that is also a fallback signal");
// U3 is the deep one and only LFPS wakes it
goto(S_U3);
check(lfps_tx, "U3 keeps LFPS alive: it is the only way to wake it");
check(!hs_tx_enable, "with the high-speed transmitter fully down");
tick(1,1,0,0,0,0,1,0);
check(ltssm_state === S_REC,
"a wakeup returns through RECOVERY, not straight to U0");
// a warm reset escapes both dead states
goto(S_INACT);
tick(1,0,0,0,0,0,0,1);
check(ltssm_state === S_RXD, "a warm reset escapes SS.Inactive");
goto(S_COMP);
tick(1,0,0,0,0,0,0,1);
check(ltssm_state === S_RXD, "and Compliance");
// ===== C. randomised =====
for (i=0;i<40000;i=i+1)
tick(({$random}%32)!=0, ({$random}%3)!=0, ({$random}%4)==0,
({$random}%16)==0, ({$random}%8)==0, {$random}%4,
({$random}%4)==0, ({$random}%64)==0);
for (i=0;i<10;i=i+1)
check(n_vis[i] > 0, "every LTSSM state was reached");
$display("");
$display(" REACH: transitions=%0d | lfps=%0d hs=%0d u0=%0d poll-timeouts=%0d recoveries=%0d",
n_trans, n_lfps, n_hs, n_u0, poll_timeouts, recoveries);
$display(" STATES: dis=%0d rxd=%0d poll=%0d u0=%0d u1=%0d u2=%0d u3=%0d rec=%0d inact=%0d comp=%0d",
n_vis[0], n_vis[1], n_vis[2], n_vis[3], n_vis[4], n_vis[5],
n_vis[6], n_vis[7], n_vis[8], n_vis[9]);
$display(" [Verilog] usb3_ltssm: %0d errors", errors);
$display(" [Verilog] %0s", errors==0 ? "PASS" : "FAIL");
$display("");
$finish;
end
endmodule12.2 The complete SystemVerilog testbench
Same structure, with the enumerated types doing the work that localparams do in the Verilog build — which is what makes a failure message name a state instead of printing a number.
`timescale 1ns/1ps
module tb_lt_sv;
import usb3_ltssm_pkg::*;
localparam PT = 5, RT = 4; // small timeouts so a whole life fits
reg clk=0, rst_n=0;
reg en=0, rxd=0, tdone=0, lerr=0, lpr=0, wk=0, wrst=0;
reg [1:0] lpl=0;
ltssm_state_e ltssm_state;
wire lfps_tx, hs_tx_enable, link_up, training_ok, training_fail;
wire [15:0] timer;
wire [31:0] poll_timeouts, recoveries;
wire ever_compliance;
always #5 clk=~clk;
usb3_ltssm_sv #(.POLL_TIMEOUT(PT), .RECOVERY_TIMEOUT(RT)) dut (
.clk(clk), .rst_n(rst_n), .enable(en), .rx_detected(rxd),
.training_done(tdone), .link_error(lerr), .lp_request(lpr),
.lp_level(lpl), .wakeup(wk), .warm_reset(wrst),
.ltssm_state(ltssm_state), .lfps_tx(lfps_tx),
.hs_tx_enable(hs_tx_enable), .link_up(link_up),
.training_ok(training_ok), .training_fail(training_fail),
.timer(timer), .poll_timeouts(poll_timeouts), .recoveries(recoveries),
.ever_compliance(ever_compliance));
localparam [3:0] S_DIS=0, S_RXD=1, S_POLL=2, S_U0=3, S_U1=4, S_U2=5,
S_U3=6, S_REC=7, S_INACT=8, S_COMP=9;
integer errors=0, i, pos, ic;
integer n_trans=0, n_lfps=0, n_hs=0, n_u0=0;
integer n_vis [0:9];
// ---- INDEPENDENT MODEL: its own state, timer and counters ----
integer m_state, m_timer, m_pt, m_rec;
reg m_comp;
task check(input cond, input [639:0] msg);
begin if (!cond) begin errors=errors+1;
if (errors <= 25)
$display(" FAIL: %0s (en=%b rxd=%b td=%b err=%b lp=%b wk=%b wr=%b | st=%0d lfps=%b hs=%b tmr=%0d, t=%0t)",
msg, en, rxd, tdone, lerr, lpr, wk, wrst, ltssm_state,
lfps_tx, hs_tx_enable, timer, $time);
end end
endtask
// The model derives the two transmitter enables from a LIST of states
// rather than from the design's expression -- a different route to the
// same answer.
function e_lfps(input integer s);
begin e_lfps = (s==S_RXD)||(s==S_POLL)||(s==S_COMP)||(s==S_U3); end
endfunction
function e_hs(input integer s);
begin e_hs = (s==S_U0)||(s==S_REC); end
endfunction
task tick(input e, input rd, input td, input le, input lp,
input [1:0] lv, input w, input wr);
integer nst, ntm;
begin
en=e; rxd=rd; tdone=td; lerr=le; lpr=lp; lpl=lv; wk=w; wrst=wr; #1;
// ---- combinational contract ----
check(ltssm_state === ltssm_state_e'(m_state[3:0]),
"state matches the independent model");
check(timer === m_timer[15:0], "timer matches the model");
check(lfps_tx === e_lfps(m_state), "lfps_tx names the right states");
check(hs_tx_enable === e_hs(m_state), "hs_tx_enable names the right states");
check(link_up === (m_state == S_U0), "link_up is U0 and nothing else");
check(training_ok === (m_state == S_U0), "training_ok is U0");
check(training_fail === ((m_state == S_INACT) || (m_state == S_COMP)),
"training_fail covers BOTH dead ends");
// ---- SAFETY PROPERTIES, independent of the model ----
// 1. THE one that matters, and it is electrical: the low-frequency
// and high-speed transmitters share a differential pair. Both on
// is two drivers fighting, not a protocol error.
check(!(lfps_tx && hs_tx_enable),
"LFPS and the high-speed transmitter both driving the same pair");
// 2. Data only ever flows on a trained link.
check(!link_up || hs_tx_enable,
"the link reported up with its high-speed transmitter off");
// 3. A link cannot be simultaneously trained and failed.
check(!(training_ok && training_fail),
"the link reported trained AND failed at once");
if (lfps_tx) n_lfps = n_lfps + 1;
if (hs_tx_enable) n_hs = n_hs + 1;
if (link_up) n_u0 = n_u0 + 1;
if (m_state >= 0 && m_state <= 9) n_vis[m_state] = n_vis[m_state] + 1;
// ---- advance the model ----
nst = m_state; ntm = m_timer;
if (!e) begin nst = S_DIS; ntm = 0; end
else if (wr) begin nst = S_RXD; ntm = 0; end
else case (m_state)
S_DIS: begin nst = S_RXD; ntm = 0; end
S_RXD: if (rd) begin nst = S_POLL; ntm = 0; end
S_POLL: begin
if (td) begin nst = S_U0; ntm = 0; end
else if (m_timer + 1 >= PT) begin
nst = S_COMP; ntm = 0; m_pt = m_pt + 1; m_comp = 1;
end else ntm = m_timer + 1;
end
S_U0: begin
if (le) begin nst = S_REC; ntm = 0; m_rec = m_rec + 1; end
else if (lp) begin
nst = (lv == 2'd0) ? S_U1 : (lv == 2'd1) ? S_U2 : S_U3;
ntm = 0;
end
end
S_U1, S_U2, S_U3:
if (w) begin nst = S_REC; ntm = 0; m_rec = m_rec + 1; end
S_REC: begin
if (td) begin nst = S_U0; ntm = 0; end
else if (m_timer + 1 >= RT) begin nst = S_INACT; ntm = 0; end
else ntm = m_timer + 1;
end
S_INACT: nst = S_INACT;
S_COMP: nst = S_COMP;
endcase
@(posedge clk); #1;
m_state = nst; m_timer = ntm;
check(poll_timeouts === m_pt[31:0], "poll_timeouts matches the model");
check(recoveries === m_rec[31:0], "recoveries matches the model");
check(ever_compliance === m_comp, "ever_compliance matches the model");
end
endtask
task hard_reset;
begin
rst_n=0; en=0; rxd=0; tdone=0; lerr=0; lpr=0; lpl=0; wk=0; wrst=0;
@(posedge clk); #1; @(posedge clk); #1; rst_n=1; #1;
m_state=S_DIS; m_timer=0; m_pt=0; m_rec=0; m_comp=0;
end
endtask
// Drive to any of the ten states, legitimately.
task goto(input integer p);
integer j;
begin
hard_reset;
if (p == S_DIS) begin end
else begin
tick(1,0,0,0,0,0,0,0); // -> RX_DETECT
if (p == S_RXD) begin end
else begin
tick(1,1,0,0,0,0,0,0); // -> POLLING
if (p == S_POLL) begin end
else if (p == S_COMP) begin
for (j=0;j<PT;j=j+1) tick(1,1,0,0,0,0,0,0);
end else begin
tick(1,1,1,0,0,0,0,0); // -> U0
if (p == S_U0) begin end
else if (p == S_U1) tick(1,1,0,0,1,2'd0,0,0);
else if (p == S_U2) tick(1,1,0,0,1,2'd1,0,0);
else if (p == S_U3) tick(1,1,0,0,1,2'd2,0,0);
else begin
tick(1,1,0,1,0,0,0,0); // error -> RECOVERY
if (p == S_INACT)
for (j=0;j<RT;j=j+1) tick(1,1,0,0,0,0,0,0);
end
end
end
end
end
endtask
initial begin
for (i=0;i<10;i=i+1) n_vis[i]=0;
hard_reset;
check(ltssm_state === ltssm_state_e'(S_DIS), "the LTSSM comes up disabled");
check(!lfps_tx && !hs_tx_enable, "with neither transmitter on");
// ===== A. EXHAUSTIVE ONE-STEP TRANSITIONS =====
// 10 states x all 128 combinations of the seven control inputs
// (enable, rx_detected, training_done, link_error, lp_request, wakeup,
// warm_reset) x 4 low-power levels = 5120 transitions.
for (pos=0; pos<10; pos=pos+1)
for (ic=0; ic<512; ic=ic+1) begin
goto(pos);
tick(ic[0], ic[1], ic[2], ic[3], ic[4], ic[8:7], ic[5], ic[6]);
n_trans = n_trans + 1;
end
$display(" exhaustive transition sweep: %0d of %0d transitions verified",
n_trans, 10*512);
// ===== B. directed: the life of a link =====
hard_reset;
tick(1,0,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_RXD), "an enabled port looks for a receiver");
check(lfps_tx, "using LFPS, because nothing is trained yet");
check(!hs_tx_enable, "with the high-speed transmitter off");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_POLL), "a detected receiver moves it to Polling");
check(lfps_tx && !hs_tx_enable, "still LFPS: training is not finished");
tick(1,1,1,0,0,0,0,0);
check(link_up, "training completes and the link is up");
check(hs_tx_enable && !lfps_tx,
"and NOW the high-speed transmitter takes the pair");
check(training_ok && !training_fail, "reported trained");
// Polling times out into COMPLIANCE, not failure
hard_reset;
tick(1,0,0,0,0,0,0,0); tick(1,1,0,0,0,0,0,0);
for (i=0;i<PT-1;i=i+1) tick(1,1,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_POLL), "still polling one tick short of the timeout");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_COMP),
"Polling times out into COMPLIANCE -- a test mode, not an error");
check(lfps_tx, "which drives LFPS, because a tester expects it");
check(training_fail, "and the arbiter of 20.2 is told to give up");
check(poll_timeouts === 32'd1, "and the timeout is counted");
// an error outranks a low-power request
goto(S_U0);
tick(1,1,0,1,1,2'd0,0,0);
check(ltssm_state === ltssm_state_e'(S_REC),
"an error outranks a low-power request: recover, do not sleep");
// Recovery DOES fail into a dead state
goto(S_U0);
tick(1,1,0,1,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_REC), "an error enters Recovery");
for (i=0;i<RT-1;i=i+1) tick(1,1,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_REC), "still recovering");
tick(1,1,0,0,0,0,0,0);
check(ltssm_state === ltssm_state_e'(S_INACT),
"Recovery fails into SS.Inactive -- a link that WAS up is broken");
check(training_fail, "and that is also a fallback signal");
// U3 is the deep one and only LFPS wakes it
goto(S_U3);
check(lfps_tx, "U3 keeps LFPS alive: it is the only way to wake it");
check(!hs_tx_enable, "with the high-speed transmitter fully down");
tick(1,1,0,0,0,0,1,0);
check(ltssm_state === ltssm_state_e'(S_REC),
"a wakeup returns through RECOVERY, not straight to U0");
// a warm reset escapes both dead states
goto(S_INACT);
tick(1,0,0,0,0,0,0,1);
check(ltssm_state === ltssm_state_e'(S_RXD), "a warm reset escapes SS.Inactive");
goto(S_COMP);
tick(1,0,0,0,0,0,0,1);
check(ltssm_state === ltssm_state_e'(S_RXD), "and Compliance");
// ===== C. randomised =====
for (i=0;i<40000;i=i+1)
tick(({$random}%32)!=0, ({$random}%3)!=0, ({$random}%4)==0,
({$random}%16)==0, ({$random}%8)==0, {$random}%4,
({$random}%4)==0, ({$random}%64)==0);
for (i=0;i<10;i=i+1)
check(n_vis[i] > 0, "every LTSSM state was reached");
$display("");
$display(" REACH: transitions=%0d | lfps=%0d hs=%0d u0=%0d poll-timeouts=%0d recoveries=%0d",
n_trans, n_lfps, n_hs, n_u0, poll_timeouts, recoveries);
$display(" STATES: dis=%0d rxd=%0d poll=%0d u0=%0d u1=%0d u2=%0d u3=%0d rec=%0d inact=%0d comp=%0d",
n_vis[0], n_vis[1], n_vis[2], n_vis[3], n_vis[4], n_vis[5],
n_vis[6], n_vis[7], n_vis[8], n_vis[9]);
$display(" [SystemVerilog] usb3_ltssm_sv: %0d errors", errors);
$display(" [SystemVerilog] %0s", errors==0 ? "PASS" : "FAIL");
$display("");
$finish;
end
endmodule12.3 The complete VHDL testbench
VHDL-2008 requires a shared variable to have a protected type, so all the bookkeeping lives in process variables inside the single stimulus process. The randomisation uses ieee.math_real.uniform, which is a genuinely different generator from either Verilog builtin — see Chapter 20.5 §9.2 for why that distinction turned out to matter across this whole module.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.usb3_ltssm_pkg.all;
entity tb_lt_vhdl is end entity;
architecture sim of tb_lt_vhdl is
constant PT : positive := 5; -- small timeouts so a whole life fits
constant RT : positive := 4;
signal clk, rst_n : std_logic := '0';
signal en, rxd, tdone, lerr, lpr, wk, wrst : std_logic := '0';
signal lpl : unsigned(1 downto 0) := (others => '0');
signal ltssm_state : ltssm_state_t;
signal lfps_tx, hs_tx_enable, link_up, training_ok, training_fail
: std_logic;
signal timer : unsigned(15 downto 0);
signal poll_timeouts, recoveries : unsigned(31 downto 0);
signal ever_compliance : std_logic;
signal done : boolean := false;
begin
clk <= not clk after 5 ns when not done else '0';
dut : entity work.usb3_ltssm_vhdl
generic map (POLL_TIMEOUT => PT, RECOVERY_TIMEOUT => RT)
port map (clk, rst_n, en, rxd, tdone, lerr, lpr, lpl, wk, wrst,
ltssm_state, lfps_tx, hs_tx_enable, link_up, training_ok,
training_fail, timer, poll_timeouts, recoveries,
ever_compliance);
stim : process
variable errors : natural := 0;
variable n_trans, n_lfps, n_hs, n_u0 : natural := 0;
type vis_t is array (ltssm_state_t) of natural;
variable n_vis : vis_t := (others => 0);
-- INDEPENDENT MODEL: its own state, timer and counters.
variable m_state : ltssm_state_t := L_DISABLED;
variable m_timer, m_pt, m_rec : natural := 0;
variable m_comp : boolean := false;
variable seed1 : positive := 103; variable seed2 : positive := 59;
variable r1, r2, r3, r4, r5, r6, r7, r8 : real;
variable icv : std_logic_vector(8 downto 0);
function sl (b : boolean) return std_logic is
begin
if b then return '1'; else return '0'; end if;
end function;
procedure chk (c : boolean; m : string) is
begin
if not c then
errors := errors + 1;
if errors <= 25 then report "FAIL: " & m severity warning; end if;
end if;
end procedure;
-- The model derives the two transmitter enables from a LIST of states
-- rather than from the design's expression.
function e_lfps (s : ltssm_state_t) return boolean is
begin
return s = L_RX_DETECT or s = L_POLLING
or s = L_COMPLIANCE or s = L_U3;
end function;
function e_hs (s : ltssm_state_t) return boolean is
begin
return s = L_U0 or s = L_RECOVERY;
end function;
procedure tick (e, rd, td, le, lp : std_logic;
lv : unsigned(1 downto 0); w, wr : std_logic) is
variable nst : ltssm_state_t;
variable ntm : natural;
begin
en <= e; rxd <= rd; tdone <= td; lerr <= le; lpr <= lp;
lpl <= lv; wk <= w; wrst <= wr;
wait for 1 ns;
-- ---- combinational contract ----
chk(ltssm_state = m_state, "state matches the independent model");
chk(timer = to_unsigned(m_timer, 16), "timer matches the model");
chk((lfps_tx = '1') = e_lfps(m_state), "lfps_tx names the right states");
chk((hs_tx_enable = '1') = e_hs(m_state),
"hs_tx_enable names the right states");
chk((link_up = '1') = (m_state = L_U0),
"link_up is U0 and nothing else");
chk((training_ok = '1') = (m_state = L_U0), "training_ok is U0");
chk((training_fail = '1')
= (m_state = L_INACTIVE or m_state = L_COMPLIANCE),
"training_fail covers BOTH dead ends");
-- ---- SAFETY PROPERTIES, independent of the model ----
-- 1. THE one that matters, and it is electrical.
chk(not (lfps_tx = '1' and hs_tx_enable = '1'),
"LFPS and the high-speed transmitter both driving the same pair");
-- 2. Data only ever flows on a trained link.
chk(link_up = '0' or hs_tx_enable = '1',
"the link reported up with its high-speed transmitter off");
-- 3. A link cannot be simultaneously trained and failed.
chk(not (training_ok = '1' and training_fail = '1'),
"the link reported trained AND failed at once");
if lfps_tx = '1' then n_lfps := n_lfps + 1; end if;
if hs_tx_enable = '1' then n_hs := n_hs + 1; end if;
if link_up = '1' then n_u0 := n_u0 + 1; end if;
n_vis(m_state) := n_vis(m_state) + 1;
-- ---- advance the model ----
nst := m_state; ntm := m_timer;
if e = '0' then nst := L_DISABLED; ntm := 0;
elsif wr = '1' then nst := L_RX_DETECT; ntm := 0;
else
case m_state is
when L_DISABLED => nst := L_RX_DETECT; ntm := 0;
when L_RX_DETECT =>
if rd = '1' then nst := L_POLLING; ntm := 0; end if;
when L_POLLING =>
if td = '1' then nst := L_U0; ntm := 0;
elsif m_timer + 1 >= PT then
nst := L_COMPLIANCE; ntm := 0;
m_pt := m_pt + 1; m_comp := true;
else ntm := m_timer + 1; end if;
when L_U0 =>
if le = '1' then
nst := L_RECOVERY; ntm := 0; m_rec := m_rec + 1;
elsif lp = '1' then
case to_integer(lv) is
when 0 => nst := L_U1;
when 1 => nst := L_U2;
when others => nst := L_U3;
end case;
ntm := 0;
end if;
when L_U1 | L_U2 | L_U3 =>
if w = '1' then
nst := L_RECOVERY; ntm := 0; m_rec := m_rec + 1;
end if;
when L_RECOVERY =>
if td = '1' then nst := L_U0; ntm := 0;
elsif m_timer + 1 >= RT then nst := L_INACTIVE; ntm := 0;
else ntm := m_timer + 1; end if;
when L_INACTIVE => nst := L_INACTIVE;
when L_COMPLIANCE => nst := L_COMPLIANCE;
end case;
end if;
wait until rising_edge(clk); wait for 1 ns;
m_state := nst; m_timer := ntm;
chk(poll_timeouts = to_unsigned(m_pt, 32),
"poll_timeouts matches the model");
chk(recoveries = to_unsigned(m_rec, 32),
"recoveries matches the model");
chk((ever_compliance = '1') = m_comp,
"ever_compliance matches the model");
end procedure;
procedure hard_reset is
begin
rst_n <= '0'; en <= '0'; rxd <= '0'; tdone <= '0'; lerr <= '0';
lpr <= '0'; lpl <= (others => '0'); wk <= '0'; wrst <= '0';
wait until rising_edge(clk); wait for 1 ns;
wait until rising_edge(clk); wait for 1 ns;
rst_n <= '1'; wait for 1 ns;
m_state := L_DISABLED; m_timer := 0;
m_pt := 0; m_rec := 0; m_comp := false;
end procedure;
procedure goto (p : ltssm_state_t) is
constant Z : unsigned(1 downto 0) := "00";
begin
hard_reset;
if p = L_DISABLED then null;
else
tick('1','0','0','0','0',Z,'0','0');
if p = L_RX_DETECT then null;
else
tick('1','1','0','0','0',Z,'0','0');
if p = L_POLLING then null;
elsif p = L_COMPLIANCE then
for j in 1 to PT loop tick('1','1','0','0','0',Z,'0','0'); end loop;
else
tick('1','1','1','0','0',Z,'0','0');
if p = L_U0 then null;
elsif p = L_U1 then tick('1','1','0','0','1',"00",'0','0');
elsif p = L_U2 then tick('1','1','0','0','1',"01",'0','0');
elsif p = L_U3 then tick('1','1','0','0','1',"10",'0','0');
else
tick('1','1','0','1','0',Z,'0','0');
if p = L_INACTIVE then
for j in 1 to RT loop
tick('1','1','0','0','0',Z,'0','0');
end loop;
end if;
end if;
end if;
end if;
end if;
end procedure;
type pos_arr is array (0 to 9) of ltssm_state_t;
constant POSN : pos_arr := (L_DISABLED, L_RX_DETECT, L_POLLING, L_U0,
L_U1, L_U2, L_U3, L_RECOVERY, L_INACTIVE,
L_COMPLIANCE);
constant Z : unsigned(1 downto 0) := "00";
begin
hard_reset;
chk(ltssm_state = L_DISABLED, "the LTSSM comes up disabled");
chk(lfps_tx = '0' and hs_tx_enable = '0',
"with neither transmitter on");
-- ===== A. EXHAUSTIVE ONE-STEP TRANSITIONS =====
-- 10 states x 512 input combinations = 5120 transitions.
for pos in 0 to 9 loop
for ic in 0 to 511 loop
goto(POSN(pos));
icv := std_logic_vector(to_unsigned(ic, 9));
tick(icv(0), icv(1), icv(2), icv(3), icv(4),
unsigned(icv(8 downto 7)), icv(5), icv(6));
n_trans := n_trans + 1;
end loop;
end loop;
report "exhaustive transition sweep: " & integer'image(n_trans)
& " of 5120 transitions verified";
-- ===== B. directed: the life of a link =====
hard_reset;
tick('1','0','0','0','0',Z,'0','0');
chk(ltssm_state = L_RX_DETECT, "an enabled port looks for a receiver");
chk(lfps_tx = '1', "using LFPS, because nothing is trained yet");
chk(hs_tx_enable = '0', "with the high-speed transmitter off");
tick('1','1','0','0','0',Z,'0','0');
chk(ltssm_state = L_POLLING, "a detected receiver moves it to Polling");
chk(lfps_tx = '1' and hs_tx_enable = '0',
"still LFPS: training is not finished");
tick('1','1','1','0','0',Z,'0','0');
chk(link_up = '1', "training completes and the link is up");
chk(hs_tx_enable = '1' and lfps_tx = '0',
"and NOW the high-speed transmitter takes the pair");
hard_reset;
tick('1','0','0','0','0',Z,'0','0'); tick('1','1','0','0','0',Z,'0','0');
for i in 1 to PT-1 loop tick('1','1','0','0','0',Z,'0','0'); end loop;
chk(ltssm_state = L_POLLING,
"still polling one tick short of the timeout");
tick('1','1','0','0','0',Z,'0','0');
chk(ltssm_state = L_COMPLIANCE,
"Polling times out into COMPLIANCE -- a test mode, not an error");
chk(lfps_tx = '1', "which drives LFPS, because a tester expects it");
chk(training_fail = '1', "and the arbiter of 20.2 is told to give up");
chk(poll_timeouts = to_unsigned(1, 32), "and the timeout is counted");
goto(L_U0);
tick('1','1','0','1','1',"00",'0','0');
chk(ltssm_state = L_RECOVERY,
"an error outranks a low-power request: recover, do not sleep");
goto(L_U0);
tick('1','1','0','1','0',Z,'0','0');
chk(ltssm_state = L_RECOVERY, "an error enters Recovery");
for i in 1 to RT-1 loop tick('1','1','0','0','0',Z,'0','0'); end loop;
chk(ltssm_state = L_RECOVERY, "still recovering");
tick('1','1','0','0','0',Z,'0','0');
chk(ltssm_state = L_INACTIVE,
"Recovery fails into SS.Inactive -- a link that WAS up is broken");
chk(training_fail = '1', "and that is also a fallback signal");
goto(L_U3);
chk(lfps_tx = '1', "U3 keeps LFPS alive: it is the only way to wake it");
chk(hs_tx_enable = '0', "with the high-speed transmitter fully down");
tick('1','1','0','0','0',Z,'1','0');
chk(ltssm_state = L_RECOVERY,
"a wakeup returns through RECOVERY, not straight to U0");
goto(L_INACTIVE);
tick('1','0','0','0','0',Z,'0','1');
chk(ltssm_state = L_RX_DETECT, "a warm reset escapes SS.Inactive");
goto(L_COMPLIANCE);
tick('1','0','0','0','0',Z,'0','1');
chk(ltssm_state = L_RX_DETECT, "and Compliance");
-- ===== C. randomised =====
for i in 1 to 40000 loop
uniform(seed1, seed2, r1); uniform(seed1, seed2, r2);
uniform(seed1, seed2, r3); uniform(seed1, seed2, r4);
uniform(seed1, seed2, r5); uniform(seed1, seed2, r6);
uniform(seed1, seed2, r7); uniform(seed1, seed2, r8);
tick(sl(r1 >= 0.03125), sl(r2 >= 0.3333), sl(r3 < 0.25),
sl(r4 < 0.0625), sl(r5 < 0.125),
to_unsigned(integer(floor(r6*4.0)), 2),
sl(r7 < 0.25), sl(r8 < 0.015625));
end loop;
for s in ltssm_state_t loop
chk(n_vis(s) > 0, "every LTSSM state was reached");
end loop;
report "REACH: transitions=" & integer'image(n_trans)
& " | lfps=" & integer'image(n_lfps)
& " hs=" & integer'image(n_hs)
& " u0=" & integer'image(n_u0)
& " poll-timeouts=" & integer'image(to_integer(poll_timeouts))
& " recoveries=" & integer'image(to_integer(recoveries));
report "STATES: dis=" & integer'image(n_vis(L_DISABLED))
& " rxd=" & integer'image(n_vis(L_RX_DETECT))
& " poll=" & integer'image(n_vis(L_POLLING))
& " u0=" & integer'image(n_vis(L_U0))
& " u1=" & integer'image(n_vis(L_U1))
& " u2=" & integer'image(n_vis(L_U2))
& " u3=" & integer'image(n_vis(L_U3))
& " rec=" & integer'image(n_vis(L_RECOVERY))
& " inact=" & integer'image(n_vis(L_INACTIVE))
& " comp=" & integer'image(n_vis(L_COMPLIANCE));
report "[VHDL] usb3_ltssm_vhdl: " & integer'image(errors) & " errors";
if errors = 0 then report "[VHDL] PASS";
else report "[VHDL] FAIL" severity error; end if;
done <= true;
wait;
end process;
end architecture;Run it with:
nvc --std=2008 -a lt_vhdl.vhd lt_vhdl_tb.vhd
nvc --std=2008 -e tb_lt_vhdl
nvc --std=2008 -r tb_lt_vhdl13. Mutation Testing — Across All Three Languages
| Mutation | Verilog | SystemVerilog | VHDL | |
|---|---|---|---|---|
| L1 | LFPS still driven while the link is up | 47768 | 47768 | 49234 |
| L2 | Polling times out into failure, not Compliance | 14944 | 14944 | 14980 |
| L3 | a low-power request outranks a link error | 38805 | 38805 | 41000 |
| L4 | low-power states wake straight to U0 | 51591 | 51591 | 49742 |
| L5 | Recovery times out into U0 | 76888 | 76888 | 73478 |
| L6 | training_fail covers only SS.Inactive | 7472 | 7472 | 7490 |
| L7 | a warm reset escapes only the dead states | 92808 | 92808 | 91223 |
L5 is the most dangerous of the seven and scores 76 888. It brings a link up that failed to retrain — so link_up asserts, hs_tx_enable asserts, and the device begins transmitting on a pair whose receiver never re-locked. Safety property 2 does not catch it, because U0 legitimately has the high-speed transmitter on; it dies on the model comparison, which is the case for having both kinds of check.
L7 is the largest because a warm reset that only works from two of ten states leaves the machine stuck almost everywhere the stimulus puts it.
L6 is the smallest at 7472 and is the one with the clearest field consequence: a link sitting in Compliance that never tells 20.2's arbiter to fall back is a device that stays silent on both buses for ever.
14. The Mutation That Deleted Instead of Demoting
The first run had L3 at 38 805 in Verilog and SystemVerilog against 71 329 in VHDL — a 1.8× divergence.
The Verilog mutation reorders the two branches:
`ifdef MUT_L3
if (lp_request) begin ... end
else if (link_error) begin ... endThe VHDL one had been written as if lp_request = '1' and false then on the error branch — which removed the error path entirely rather than demoting it below the low-power one. With no link_error handling at all, a link that went bad simply stayed in U0.
That is a strictly stronger mutation, not the same one, and the counts said so.
Rewriting it as a genuine reorder brought VHDL to 41 000, within 6 % of the other two.
15. A UVM Environment for a State Machine With Timeouts
The LTSSM's interesting behaviour is what happens at the boundaries of its timers, and that is a constrained-random problem rather than a directed one.
// A link partner, as an agent. Its QUALITY is a configuration: a good
// partner trains immediately, a marginal one trains late, and a dead one
// never trains at all -- and the third is the only one that reaches
// Compliance, which is the state section 4 is about.
typedef enum { PARTNER_GOOD, PARTNER_MARGINAL, PARTNER_ABSENT } partner_e;
class link_partner_cfg extends uvm_object;
`uvm_object_utils(link_partner_cfg)
rand partner_e kind;
rand int unsigned train_delay;
constraint c_kind {
// A marginal partner trains just either side of the timeout -- which is
// the only place the Polling/Compliance boundary is observable.
kind == PARTNER_MARGINAL -> train_delay inside {[POLL_TIMEOUT-2:
POLL_TIMEOUT+2]};
kind == PARTNER_GOOD -> train_delay inside {[0:2]};
kind == PARTNER_ABSENT -> train_delay == 1000; // never
}
endclass
// Walk the timeout: train at every tick from well inside to well past the
// limit. A directed test picks one; this picks all of them.
class timeout_walk_seq extends uvm_sequence #(ltssm_item);
`uvm_object_utils(timeout_walk_seq)
rand int unsigned train_at;
constraint c_at { train_at inside {[0:POLL_TIMEOUT+3]}; }
task body();
ltssm_item it;
`uvm_do_with(it, { enable == 1; rx_detected == 1; })
repeat (train_at)
`uvm_do_with(it, { enable == 1; rx_detected == 1; training_done == 0; })
// Either this lands inside the window and reaches U0, or it lands past
// it and the machine has already gone to Compliance. Both are correct;
// the boundary between them is what is being tested.
`uvm_do_with(it, { enable == 1; training_done == 1; })
endtask
endclass
class ltssm_scoreboard extends uvm_scoreboard;
`uvm_component_utils(ltssm_scoreboard)
local ltssm_state_e m_prev = L_DISABLED;
function void write(ltssm_txn t);
// THE property, and the severity reflects what it costs: two drivers
// on one differential pair is not a protocol error that a retry fixes.
if (t.lfps_tx && t.hs_tx_enable)
`uvm_fatal("LTSSM/CONTENTION",
"LFPS and the high-speed transmitter both driving the same pair")
// Section 13's L5: U0 is only ever entered from a state that actually
// completed training.
if (t.ltssm_state == L_U0 && m_prev != L_U0)
if (!(m_prev inside {L_POLLING, L_RECOVERY}))
`uvm_error("LTSSM/ENTRY", $sformatf(
"U0 entered from %s without completing training", m_prev.name()))
// Section 5: both dead ends must produce the fallback signal, or the
// arbiter of 20.2 waits for ever.
if (t.ltssm_state inside {L_INACTIVE, L_COMPLIANCE} && !t.training_fail)
`uvm_error("LTSSM/DEADEND",
"a dead-end state did not report training_fail")
m_prev = t.ltssm_state;
endfunction
endclass
covergroup ltssm_cg with function sample(
ltssm_state_e st, int unsigned timer, bit trained, partner_e partner);
// Every state, and the assertion that every one was reached. A state
// never entered is a state never tested.
cp_state : coverpoint st;
// The timeout boundary, from both sides. Section 4's whole distinction
// lives in the two bins either side of the limit.
cp_timeout : coverpoint timer {
bins inside_window = {[0:POLL_TIMEOUT-2]};
bins at_limit = {POLL_TIMEOUT-1};
bins past = {[POLL_TIMEOUT:$]};
}
cp_partner : coverpoint partner;
// The cross that matters: an ABSENT partner is the only way to reach
// Compliance, and a lab with only working cables never generates one.
x_partner_state : cross cp_partner, cp_state;
endgroup16. Assertions
// THE property: two transmitters, one pair, never both.
property p_no_contention;
@(posedge clk) disable iff (!rst_n)
!(lfps_tx && hs_tx_enable);
endproperty
a_no_contention : assert property (p_no_contention)
else $fatal(1, "LFPS and high-speed both driving the same pair");
// U0 is only ever entered from a state that completed training.
// Mutation L5.
property p_u0_entry;
@(posedge clk) disable iff (!rst_n)
($changed(ltssm_state) && ltssm_state == L_U0)
|-> ($past(ltssm_state) inside {L_POLLING, L_RECOVERY});
endproperty
a_u0_entry : assert property (p_u0_entry)
else $error("U0 entered without completing training");
// Both dead ends report the fallback. Mutation L6.
property p_deadend_reports;
@(posedge clk) disable iff (!rst_n)
(ltssm_state inside {L_INACTIVE, L_COMPLIANCE}) |-> training_fail;
endproperty
a_deadend_reports : assert property (p_deadend_reports);
// A warm reset works from EVERY state. Mutation L7, stated as the
// universal it is rather than as a list of the states it covers.
property p_warm_reset_universal;
@(posedge clk) disable iff (!rst_n)
(enable && warm_reset) |=> (ltssm_state == L_RX_DETECT);
endproperty
a_warm_reset_universal : assert property (p_warm_reset_universal);p_no_contention has no antecedent at all — it is true in every state, every cycle, for every input. That is the right shape for an electrical constraint, and it is the one to hand to a formal tool: a prover settles it over the whole state space in negligible time.
These were written but not simulated; Icarus supports no concurrent assertions.
17. Debugging: the Port That Enumerates at USB 2 on One Cable
The report: a SuperSpeed device enumerates at USB 2 speed with one cable and at SuperSpeed with another. Both cables are physically fine and both carry USB 2 data perfectly.
The procedure:
1. Establish that it fell back rather than never tried. 20.2's training_attempts distinguishes them: zero attempts means ss_rx_detect never asserted — the SuperSpeed pairs are not connected, which is a cable with only USB 2 wiring. Non-zero means training was attempted and failed, which is this chapter.
2. Read the LTSSM's resting state. A link that gave up sits in SS.Inactive or Compliance, and they mean different things (§4): Compliance means Polling never completed — nothing ever trained; SS.Inactive means a link that was up could not be retrained.
3. Compliance points at the cable's SuperSpeed pairs. Training exchanged LFPS and never converged, which is a signal-integrity problem in the high-speed pairs specifically — and it is entirely consistent with USB 2 working perfectly, because USB 2 does not use those wires.
4. SS.Inactive points at something intermittent. The link trained once, so the pairs are good enough to train. Something later broke it and recovery failed within its timeout — marginal margin, a connector under strain, or interference.
5. Check poll_timeouts and recoveries. A high recovery count with the link still up is a marginal cable that keeps being rescued; a single poll timeout and a rest in Compliance is a cable that never worked at SuperSpeed at all.
18. Common Misconceptions
"A link is up as soon as the cable is plugged in." It must be trained first, in both directions, before one packet can move (§1).
"Training uses the link to negotiate the link." It cannot — that is the chicken and egg (§2). The earliest signalling is LFPS, with the high-speed transmitter off.
"Rx.Detect is a handshake." It is a DC measurement (§3). Nothing is sent and nothing replies, which is why it works against an unpowered device and why it has no timeout.
"A training timeout is an error." Polling times out into Compliance, a test mode (§4) — a port stuck in Polling looks exactly like a port attached to a tester. Mutation L2.
"SS.Inactive and Compliance are the same dead end." Different causes, different meanings — and the same consequence for the arbiter (§5), which is why one signal covers both. Mutation L6.
"A low-power request should be honoured promptly." Not over a link error (§7) — a bad link put to sleep wakes into the same fault. Mutation L3.
"Waking from U1 goes straight back to U0." It returns through Recovery, because the receiver must re-lock (§7). Mutation L4.
"A warm reset is for recovering dead links." It works from every state (§7); one that only escaped the dead ends could not rescue a confused link. Mutation L7, 92 808 errors.
19. Exercises
1. §3 argues Rx.Detect needs no timeout. Construct the argument for why Polling does, and determine what the design would do if Polling had none.
2. Mutation L5 brings up an untrained link and is not caught by safety property 2. Write the property that would catch it without comparing against a model.
3. §14 lists five distinct causes of a mutation meaning different things across languages. Propose a mechanical check over a tri-HDL mutation matrix that would flag all five, and state its false-positive behaviour.
4. The design merges U1, U2 and U3 in its transition logic but keeps them as separate states. Determine what would have to change to give them different wake latencies, and which of §13's mutations would then become two.
5. A link reaches Compliance. Trace what 20.2's arbiter does, what 20.1's credit block does, and what the user sees.
6. Write the SVA property that catches L4 — waking straight to U0 — without naming L_RECOVERY.
20. Summary
A USB 2 bus works after a reset; a SuperSpeed link does not (§1). It must find a receiver, agree on timing, and confirm both directions before carrying one packet.
And it cannot use the link to do it (§2). The earliest signalling is LFPS, low enough in frequency for an untrained receiver — two transmitters on one differential pair, which must never both drive. That property has no antecedent and is checked every cycle (L1, 47 768 errors).
Rx.Detect is a DC measurement, not a handshake (§3) — nothing is sent, nothing replies, and there is no timeout, because nothing was asked.
Polling times out into Compliance, a test mode — not into failure (§4). A port stuck in Polling is indistinguishable from a port attached to a tester, and one that failed into an error state would be untestable. Recovery is different and fails properly, because a link that was up is broken rather than absent.
Two dead ends, one fallback signal (§5): SS.Inactive and Compliance mean different things and need the same decision (L6, 7472 — the smallest count and the clearest field consequence).
All three HDL implementations were simulated (§21) and seven mutations died in all three (§13), verified over all 5120 one-step transitions — ten states × 512 input combinations — with every state visited over a thousand times and asserted to have been (§12).
And a mutation deleted where it should have demoted (§14). VHDL's L3 removed the error path instead of ranking it below the low-power one, and scored 1.8× the other two. That is the fifth distinct cause in three modules of a mutation not meaning the same thing in every language — after a duplicate, identical ternary branches, a duplicated design condition, and last-assignment-wins. One detector has caught all five: a column out of line with its neighbours.
21. Tooling, Honestly
| Language | Design | Testbench | Analysed / compiled | Simulated | Mutations |
|---|---|---|---|---|---|
| Verilog-2005 | usb3_ltssm | lt_v_tb.v | ✅ Icarus -g2005 | ✅ 0 errors, 5120/5120 | ✅ all seven |
| SystemVerilog | usb3_ltssm_sv | lt_sv_tb.sv | ✅ Icarus -g2012 | ✅ 0 errors, 5120/5120 | ✅ all seven |
| VHDL-2008 | usb3_ltssm_vhdl | lt_vhdl_tb.vhd | ✅ nvc 1.23.0 | ✅ 0 errors, 5120/5120 | ✅ all seven |
| UVM (§15) | — | — | ❌ no UVM-capable simulator here | ❌ | — |
| SVA (§16) | — | — | ❌ unsupported by Icarus | ❌ | — |
The timeouts are 5 and 4 in simulation against defaults of 12 and 8, so an entire link life fits in a ten-tick figure. Each appears exactly once in the design, which is what makes the substitution safe.
This chapter models a faithful subset of the LTSSM, not the whole of it. The real machine has additional substates within Polling and Recovery, hot-reset handling, and loopback — omitted because they multiply the state count without adding a distinct idea. The ten states here are the ones with different consequences, and §6's diagram merges U1/U2/U3 on the same grounds.
22. What Comes Next
The link is trained and carrying data. What is it carrying?
Chapter 20.4 — USB 3.x Packets is about the four packet types SuperSpeed defines, and the structural decision behind all of them: the header is protected separately from the payload.
Every packet begins with a 14-byte header and its own CRC — and a corrupt header is a link-layer problem while a corrupt payload is a protocol-layer one. They are detected by different checks, reported to different layers, and recovered in completely different ways.
That separation is why a SuperSpeed link can retry a lost data packet without renegotiating anything, and it is the last piece of architecture this module has to describe.
Browse the full path on the USB tutorials index.
Continue learning
Related tutorials
- Related topic
SuperSpeed Concepts
USB 3 kept the single master and deleted the polling — credit-based flow control, announced readiness, and a sender bounded by the smallest of three limits.
- Related topic
Dual-Bus Architecture
A USB 3 cable carries two complete buses — physically parallel, logically exclusive — and the presence pull-up deliberately sits outside that exclusion.
- Related topic
USB 3.x Packets
Every SuperSpeed packet carries two independent CRCs, and that is not redundancy: a corrupt header is a link-layer problem while corrupt data is a protocol-layer one, so the header CRC must gate the type decode.
- Related topic
USB 3.x vs USB 2.0 Differences
A SuperSpeed-capable device behind a USB 2 hub is a USB 2 device: capability is a property of the link that trained, never of the descriptor the device published.
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.
