Skip to content
VLSI Mentor

USB · Module 29

Smartphones over USB

A phone is five device classes at once and negotiates which. None of it starts until two resistors answer three questions — and the reversible connector turns the answer into a symmetry, verified exhaustively over 81 transitions.

The fourth case study, and the one where the device refuses to be a single thing. A flash drive is one class. A webcam is one class. An audio device is one class with an awkward clock. A phone is a mass-storage-ish device, a camera, a serial console, possibly a display source, and either a power sink or a power source — and which of those it is on any given day is negotiated.

1. The Phone Is Five Devices And Has To Agree Which

Plug a phone into a laptop and the notification asks what the cable is for. That question is not a user-interface nicety; it is the visible end of a negotiation, and the options are genuinely different device classes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MTP / PTP       a media-transfer or camera class. NOT mass storage --
                    the phone keeps its filesystem and serves objects, which
                    is why you cannot chkdsk a phone
    ADB             a vendor-specific bulk interface: a serial console and a
                    file pipe for developers
    RNDIS / NCM     a network interface: tethering
    DisplayPort     an ALTERNATE MODE -- the USB pairs are handed over to a
                    completely different protocol
    power           sink, or source, or either, and at a negotiated voltage

A phone presents several of those at once, as a composite device, and changes which on request. All of it is negotiated over the wire.

What rests on the attach decision

A stack showing what depends on the USB-C configuration channel attach decision. At the bottom, CC attach and orientation detection, performed by measuring two resistors. Above it, the current advertisement, also a resistor value. Above that, the Power Delivery contract, the USB data role, and finally alternate modes such as DisplayPort. Each layer can only begin once the layer beneath it has completed.What a phone on a USB-C cable negotiates, and in what orderAlternate modeDisplayPort or Thunderbolt, over the same four high-speed pairsDisplayPort or Thunderbolt, over the same four high-speed pairsUSB data roleHost or device, and therefore which end enumerates the otherHost or device, and therefore which end enumerates the otherPower contractPower Delivery messages negotiate a voltage and a current limitPower Delivery messages negotiate a voltage and a current limitCurrent advertisementA resistor value: 500 mA, 1.5 A or 3.0 A, before any protocolA resistor value: 500 mA, 1.5 A or 3.0 A, before any protocolCC attach and orientationIs anything there, and which way round. This chapter.Is anything there, and which way round. This chapter.
Read upward. Every layer above the bottom one is a protocol that has to be spoken, and none of them can begin until the bottom row has decided that something is there and which way round it is. The current advertisement above it is still not a protocol — it is a resistor being measured.

2. Two Pins, Three Levels, Three Answers

A source pulls both CC lines up through Rp. A sink pulls its one connected CC line down through Rd. A powered cable identifies itself with Ra on the other line. The source reads each line as one of three levels:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    OPEN   nothing pulling down       -- nothing on this line
    Ra     pulled down weakly         -- a powered (e-marked) cable
    Rd     pulled down harder         -- a SINK is on this line

Two lines, three levels each, so nine combinations — and five of the nine are an attach:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CC1     CC2     means
    ----    ----    -----------------------------------------------------
    OPEN    OPEN    nothing
    OPEN    Ra      a powered cable with nothing on the end of it
    OPEN    Rd      a sink, on CC2
    Ra      OPEN    a powered cable with nothing on the end of it
    Ra      Ra      likewise, both ends marked
    Ra      Rd      a sink on CC2, through a powered cable
    Rd      OPEN    a sink, on CC1
    Rd      Ra      a sink on CC1, through a powered cable
    Rd      Rd      NOT a sink -- a debug accessory

And the orientation falls out of the same rule for free. Because the plug is reversible, the sink's Rd lands on CC1 or CC2 depending on which way the user inserted it — so which line it is is the orientation. There is no other way to learn it, and nothing else in the system needs to be told.

3. The Design (Verilog-2005)

From two voltages to three answers, with a debounce in the middle

The configuration channel attach datapath. The two CC line levels are decoded into whether each carries Rd or Ra, from which the attach condition, the orientation and the powered-cable answer are derived. Those three answers are bundled into a situation value, which is debounced over a fixed window of ticks, and only once the situation has been stable for the whole window are the three answers latched and reported.CC1, CC2OPEN / Ra / RdDecodeexactly one Rd?Situationdebug, cable, orientDebouncestable for the windowattachedand stays up on abreakorientationwhich line had the RdCountersattach, detach, glitch2 + 2XOR3 bitsbelievedlatchtally12
The decode on the left is combinational and instantaneous. The debounce in the middle is the entire reason this is a state machine rather than a lookup table: the answers are only believed once the SITUATION — all three answers together — has held still for the whole window.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  typec_cc_fsm -- the Configuration Channel state machine that decides
//  whether anything is plugged in, which way round it is, and how much
//  current it is allowed to draw.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL.
//  This is NOT a Type-C port controller. There is no VBUS switch, no
//  Power Delivery protocol engine, no BMC PHY, no VCONN switching and no
//  alternate-mode entry. It is the CC attach machine: the part that runs
//  before any of that exists, and that everything else depends on.
//
//  WHY THIS IS THE MECHANISM WORTH BUILDING FOR "PHONE ON A CABLE"
//  ---------------------------------------------------------------
//  A phone on a USB-C cable is simultaneously a mass-storage-ish device
//  (MTP), a camera (PTP), a serial console (ADB), possibly a display
//  source (DisplayPort alt mode), and either a power sink or a power
//  source. Which of those it is gets NEGOTIATED, and none of that
//  negotiation can start until three questions are answered:
//
//      1. IS ANYTHING THERE?      -- and is it stable, or is it a
//                                    contact bouncing as the plug seats
//      2. WHICH WAY ROUND IS IT?  -- the connector is reversible, so the
//                                    same pin is TX in one orientation
//                                    and RX in the other
//      3. HOW MUCH CURRENT?       -- advertised as a resistor value
//                                    before a single bit is exchanged
//
//  All three are answered by two pins and some resistors, and the answers
//  are read as VOLTAGES rather than as a protocol. That is deliberate: it
//  works with a dumb cable, a dumb charger and no firmware running.
//
//  THE CC LINES
//  ------------
//  A source (DFP) pulls both CC lines up through Rp. A sink (UFP) pulls
//  its one connected CC line down through Rd. A powered cable identifies
//  itself with Ra on the other line. The source reads each line as one of
//  three levels:
//
//      OPEN   nothing pulling down          -- nothing on this line
//      Ra     pulled down weakly            -- a powered (e-marked) cable
//      Rd     pulled down harder            -- a SINK is on this line
//
//  Because the plug is reversible, the sink's Rd lands on CC1 or CC2
//  depending on which way the user inserted it, and WHICH ONE IT IS is
//  the orientation. There is no other way to learn it.
// =====================================================================
module typec_cc_fsm #(
  // How many debounce ticks a CC state must persist before it is believed.
  // The specification calls this tCCDebounce and requires 100..200 ms. A
  // real port ticks at 1 ms and uses 100..200 here; this module uses 10 so
  // a simulation can sweep the whole window exhaustively.
  parameter integer DEBOUNCE_TICKS = 10
) (
  input  wire       clk,
  input  wire       rst_n,

  // One pulse per debounce tick. Everything time-related in this module
  // is counted in ticks, never in clock cycles, so the same RTL works at
  // any clock frequency.
  input  wire       tick,

  // What the comparators say about each CC line, sampled continuously.
  //   2'b00 OPEN   2'b01 Ra   2'b10 Rd
  input  wire [1:0] cc1_st,
  input  wire [1:0] cc2_st,

  // ---- the three answers ----
  output wire       attached,
  // 1'b0 = the sink is on CC1, 1'b1 = the sink is on CC2. Meaningless
  // unless `attached`, and held at its last value rather than cleared, so
  // that a detach does not momentarily steer a mux the wrong way.
  output wire       orientation,
  // A powered cable was detected alongside the sink. VCONN would be
  // supplied on the OTHER line -- the one carrying Ra.
  output wire       cable_powered,
  // Both CC lines pulled down by Rd. That is not a sink; it is a debug
  // accessory, and treating it as a sink is a real interoperability bug.
  output wire       debug_accessory,

  output wire [15:0] n_attach,
  output wire [15:0] n_detach,
  output wire [15:0] n_glitch      // attaches that failed to debounce
);

  localparam [1:0] L_OPEN = 2'b00, L_RA = 2'b01, L_RD = 2'b10;

  localparam [1:0] S_UNATTACHED = 2'd0,
                   S_ATTACHWAIT = 2'd1,
                   S_ATTACHED   = 2'd2,
                   S_DETACHWAIT = 2'd3;

  reg [1:0]  state;
  reg [15:0] ticks;
  // What is currently being debounced. See sit_now below.
  reg [2:0]  sit_lat;
  reg        orient_r, cable_r, debug_r;
  reg [15:0] att_c, det_c, gli_c;

  // ---- decode the two lines ----
  wire cc1_rd = (cc1_st == L_RD);
  wire cc2_rd = (cc2_st == L_RD);
  wire cc1_ra = (cc1_st == L_RA);
  wire cc2_ra = (cc2_st == L_RA);

  // EXACTLY ONE line pulled down by Rd is a sink. The exclusive-or is the
  // whole rule, and it is what makes the connector reversible: the machine
  // does not care which line it is, only that precisely one of them is.
  wire one_rd   = cc1_rd ^ cc2_rd;
  // BOTH lines pulled down by Rd is a debug accessory, not a sink. A port
  // that treats it as a sink will drive VBUS into a debug jig.
  wire both_rd  = cc1_rd & cc2_rd;

  wire attach_cond = one_rd | both_rd;

  // Which line the sink is on. Only meaningful when exactly one Rd is
  // present, which is exactly when it is sampled.
  wire orient_now  = cc2_rd;
  // Ra on the line the sink is NOT on. With both_rd there is no free line,
  // so there is no powered cable to find.
  wire cable_now   = one_rd ? (cc1_rd ? cc2_ra : cc1_ra) : 1'b0;

  // ---- the SITUATION, which is what actually gets debounced ----
  //
  // The specification debounces the CC STATE, not the fact that something
  // is attached. Those are different, and the difference is a real bug:
  // if Rd moves from CC1 to CC2 part-way through the window -- which is
  // exactly what a plug being rocked into place does -- then attach_cond
  // never drops, and a machine watching only attach_cond would accept the
  // attachment with an orientation that had been stable for one tick.
  //
  // Bundling the three decoded answers into one value makes "has anything
  // about this changed" a single comparison, and makes it impossible to
  // debounce one answer and forget another.
  wire [2:0] sit_now = {both_rd, cable_now, orient_now};

  assign attached        = (state == S_ATTACHED) || (state == S_DETACHWAIT);
  assign orientation     = orient_r;
  assign cable_powered   = cable_r;
  assign debug_accessory = debug_r;
  assign n_attach        = att_c;
  assign n_detach        = det_c;
  assign n_glitch        = gli_c;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state    <= S_UNATTACHED;
      ticks    <= 16'd0;
      sit_lat  <= 3'd0;
      orient_r <= 1'b0;
      cable_r  <= 1'b0;
      debug_r  <= 1'b0;
      att_c    <= 16'd0;
      det_c    <= 16'd0;
      gli_c    <= 16'd0;
    end else begin
      case (state)
        // -------------------------------------------------------------
        S_UNATTACHED: begin
          if (attach_cond) begin
            state   <= S_ATTACHWAIT;
            ticks   <= 16'd0;
            sit_lat <= sit_now;
          end
        end

        // -------------------------------------------------------------
        //  The condition has to SURVIVE tCCDebounce. This is not
        //  belt-and-braces: a plug being pushed home makes and breaks
        //  contact several times over a few milliseconds, and a port that
        //  believed the first edge would enumerate, drop, and enumerate
        //  again -- which is the "my phone keeps reconnecting" bug.
        // -------------------------------------------------------------
        S_ATTACHWAIT: begin
          if (!attach_cond) begin
            // it went away before it was believed
            state <= S_UNATTACHED;
            gli_c <= gli_c + 16'd1;
          end else if (sit_now != sit_lat) begin
            // Still attached, but it is not the same attachment any more.
            // The window restarts from here: the clock on "has this been
            // stable" runs from the LAST change, not from the first.
            ticks   <= 16'd0;
            sit_lat <= sit_now;
          end else if (tick) begin
            if (ticks == DEBOUNCE_TICKS[15:0] - 16'd1) begin
              state    <= S_ATTACHED;
              // Taken from the DEBOUNCED situation, which by construction
              // has been stable for the whole window. Note that this makes
              // the sampling instant irrelevant: sit_lat and sit_now are
              // equal here, so latching at entry and latching at
              // acceptance would give the same answer. That equivalence is
              // the point -- it is what a correct debounce buys, and
              // without the restart above it would not hold.
              orient_r <= sit_lat[0];
              cable_r  <= sit_lat[1];
              debug_r  <= sit_lat[2];
              att_c    <= att_c + 16'd1;
            end else begin
              ticks <= ticks + 16'd1;
            end
          end
        end

        // -------------------------------------------------------------
        S_ATTACHED: begin
          if (!attach_cond) begin
            state <= S_DETACHWAIT;
            ticks <= 16'd0;
          end
        end

        // -------------------------------------------------------------
        //  A detach is debounced too, and for the same reason in reverse:
        //  a momentary break while the cable is wiggled must not tear down
        //  a power contract and a display link.
        //
        //  `attached` stays HIGH throughout this state. A port that
        //  dropped it on the first missing sample would produce exactly
        //  the disconnect it is trying to avoid.
        // -------------------------------------------------------------
        S_DETACHWAIT: begin
          if (attach_cond) begin
            state <= S_ATTACHED;      // it came back: nothing happened
          end else if (tick) begin
            if (ticks == DEBOUNCE_TICKS[15:0] - 16'd1) begin
              state <= S_UNATTACHED;
              det_c <= det_c + 16'd1;
            end else begin
              ticks <= ticks + 16'd1;
            end
          end
        end

        default: state <= S_UNATTACHED;
      endcase
    end
  end

endmodule

The debounce is on the SITUATION, not on the attachment

The comment around sit_now is long because the distinction it draws was a design gap, and the thing that exposed it was two mutations scoring the same number.

The first version of this module debounced attach_cond — the single bit meaning "something is plugged in". That is the obvious reading of "the CC state must be stable for tCCDebounce", and it is wrong, because the CC state is not the same thing as the fact of attachment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    tick 1   CC1=Rd  CC2=OPEN     attach_cond = 1
    tick 2   CC1=Rd  CC2=OPEN     attach_cond = 1
    tick 3   CC1=OPEN CC2=Rd      attach_cond = 1   <-- never dropped
    ...
    tick 10  CC1=OPEN CC2=Rd      attach_cond = 1   -> ATTACHED, orientation 1

attach_cond held for the entire window, so a machine watching only that bit accepts the attachment — with an orientation that had been stable for one tick. A plug being rocked into place does exactly this.

The fix is to debounce the whole decoded situation, and to restart the window whenever any part of it changes. Three lines, and it buys something that is not obvious:

4. What The Machine Actually Does

Two scenarios, dumped cycle by cycle from the simulator. They are the same insertion, performed twice, with the plug turned over in between — which is the chapter's headline property made visible.

DEBOUNCE_TICKS is set to 3 for these two dumps so a whole reject-then-accept cycle fits in ten cycles. The design and the suite use 10.

A plug being pushed home: one bounce rejected, then the contact believed

A USB-C plug being pushed home. The sink resistor appears on CC2 at cycle one and the debounce window begins. At cycle three the contact bounces away before the window has completed, so the glitch counter increments to one and the partial window is discarded. The contact returns at cycle four and the window restarts from zero, completing three ticks at cycles five, six and seven, at which point attached is raised and the orientation is reported as one because the sink resistor is on CC2.first attemptfirst attemptrestarted windowrestarted windowattachedattachedbounced away: window abandonedbounced away: windowabandonedback, and it starts overback, and it starts overbelieved, on CC2believed, on CC2clkcc1_stOPENOPENOPENOPENOPENOPENOPENOPENOPENOPENcc2_stOPENRdRdOPENRdRdRdRdRdRdtickattachedorientationn_glitch0001111111n_attach0000000111t0t1t2t3t4t5t6t7t8t9
The contact appears at t1 and the window starts. It bounces away at t3 before the window completes — `n_glitch` goes to 1 and the window is abandoned, not paused. It returns at t4 and the window starts again from zero: three ticks at t5, t6, t7, and only then is `attached` raised, with `orientation` = 1 because the Rd landed on CC2.

The same insertion, plug turned over

The same USB-C insertion with the plug turned over, so the sink resistor lands on CC1 instead of CC2. The timing is identical: the contact appears at cycle one, bounces away at cycle three incrementing the glitch counter, returns at cycle four, and the restarted window completes at cycle seven. Attached rises on exactly the same cycle as before and the counters take the same values, and the only signal that differs anywhere in the trace is the orientation output, which is now zero instead of one.first attemptfirst attemptrestarted windowrestarted windowattached, orientation 0attached, orientation 0the same bouncethe same bouncebelieved, on CC1believed, on CC1clkcc1_stOPENRdRdOPENRdRdRdRdRdRdcc2_stOPENOPENOPENOPENOPENOPENOPENOPENOPENOPENtickattachedorientationn_glitch0001111111n_attach0000000111t0t1t2t3t4t5t6t7t8t9
Identical timing, identical bounce, Rd on CC1 instead of CC2. `attached` rises on the same cycle, `n_glitch` and `n_attach` take the same values on the same cycles, and the single difference in the whole trace is `orientation`, which is now 0. That is the property section 6 verifies over all 81 transitions rather than over this one.

5. The Testbench (Verilog)

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    PHASE 1  DIRECTED, EXHAUSTIVE   all 81 ordered pairs of CC states
    PHASE 2  DIRECTED, EXHAUSTIVE   the debounce threshold, every width
                                    0..DEBOUNCE+2 for all five attach
                                    combinations
    PHASE 3  DIRECTED, EXHAUSTIVE   THE SYMMETRY: all 81 pairs replayed
                                    flipped and compared sample by sample
    PHASE 4  DIRECTED               a detach that changes its mind, and the
                                    SECOND attach swept across the window
    PHASE 5  DIRECTED, EXHAUSTIVE   the situation changes mid-window: all 20
                                    ordered pairs of distinct attach
                                    situations, at three points in the window
    PHASE 6  RANDOM                 a plug being pushed home, both lines
                                    bouncing independently

Three choices in the bench are worth naming before the listing.

The model is a run-length threshold, not a state machine. The design is four states and a counter. The model counts how many consecutive ticks the decoded situation has held and compares against DEBOUNCE — it has no state names at all. A wrong state encoding in the design cannot produce the same mistake in the model, which is the entire point of having one.

CC changes land strictly between ticks. CC is asynchronous to the debounce tick in real hardware, and letting a change land on the same cycle as a tick makes "how many ticks has this held for" ambiguous by one — in the bench's bookkeeping, not in the design. Keeping them apart removes the ambiguity rather than papering over it with a tolerance.

The glitch rule is stated in English before it is coded. A glitch is an attach condition that was asserted and then withdrawn before it had held long enough to be believed. Note what is absent: any clause about lasting at least one tick. The design enters its debounce state on the clock edge after the condition appears, so a condition that comes and goes between two ticks is still an abandoned attach. Adding that clause to the model cost 60 failures against a design that was right.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for typec_cc_fsm.
//
//  THE HEADLINE PROPERTY IS A SYMMETRY, AND IT IS THE REASON THE
//  CONNECTOR CAN BE REVERSIBLE.
//
//  Every other chapter in this module asserts that the design computes
//  the right VALUE. This one asserts that it computes the SAME value
//  under a transformation of its inputs: exchange CC1 and CC2 -- which is
//  physically what happens when the user turns the plug over -- and
//  every output must be identical except `orientation`, which must
//  invert.
//
//  That is a stronger statement than "it works both ways round", and it
//  is checkable EXHAUSTIVELY over single transitions: 9 CC states x 9 CC
//  states = 81 ordered pairs, each replayed flipped and compared sample
//  by sample. A design that special-cased CC1 -- which is the natural way
//  to write it, and wrong -- fails the symmetry without failing any
//  single-orientation test.
//
//  THE MODEL IS A RUN-LENGTH THRESHOLD, NOT A STATE MACHINE. The design
//  is four states and a counter; the model counts how many consecutive
//  ticks the attach condition has held and compares against DEBOUNCE.
//  Same behaviour, different formulation, so a state-encoding mistake in
//  one cannot be reproduced by the other.
// =====================================================================
`timescale 1ns/1ps
module tb_tc_v;

  localparam integer DEBOUNCE = 10;

  localparam [1:0] L_OPEN = 2'b00, L_RA = 2'b01, L_RD = 2'b10;

  reg clk = 1'b0, rst_n = 1'b0;
  always #5 clk = ~clk;

  reg        tick = 1'b0;
  reg  [1:0] cc1 = L_OPEN, cc2 = L_OPEN;

  wire        attached, orientation, cable_powered, debug_accessory;
  wire [15:0] n_attach, n_detach, n_glitch;

  typec_cc_fsm #(.DEBOUNCE_TICKS(DEBOUNCE)) dut (
    .clk(clk), .rst_n(rst_n), .tick(tick),
    .cc1_st(cc1), .cc2_st(cc2),
    .attached(attached), .orientation(orientation),
    .cable_powered(cable_powered), .debug_accessory(debug_accessory),
    .n_attach(n_attach), .n_detach(n_detach), .n_glitch(n_glitch)
  );

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

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

  // 1024 bits, not 256. A `what` of [255:0] holds 32 characters, and every
  // message below is longer than that -- so the one artefact a reader gets
  // when a check fails would have arrived cut in half. A diagnostic that
  // cannot say what went wrong is not a diagnostic.
  task ck(input cond, input [1023:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step#%0d: %0s", $time, steps, what);
      end
    end
  endtask

  // ---- the model, formulated as a run-length threshold ----
  //
  // The design is a four-state machine with a tick counter. The model
  // holds no state names at all: it counts consecutive ticks during which
  // the attach condition has held its current value, and flips
  // `m_attached` when that run reaches DEBOUNCE. A wrong state encoding
  // in the design cannot produce the same mistake here.
  integer m_run;
  integer m_attached, m_orient, m_cable, m_debug;
  integer m_att_c, m_det_c, m_gli_c;
  reg     m_prev_cond;

  function integer f_one_rd(input [1:0] a, input [1:0] b);
    begin f_one_rd = ((a == L_RD) ^ (b == L_RD)) ? 1 : 0; end
  endfunction
  function integer f_both_rd(input [1:0] a, input [1:0] b);
    begin f_both_rd = ((a == L_RD) && (b == L_RD)) ? 1 : 0; end
  endfunction
  function integer f_cond(input [1:0] a, input [1:0] b);
    begin f_cond = (f_one_rd(a,b) || f_both_rd(a,b)) ? 1 : 0; end
  endfunction
  function integer f_orient(input [1:0] a, input [1:0] b);
    begin f_orient = (b == L_RD) ? 1 : 0; end
  endfunction
  function integer f_cable(input [1:0] a, input [1:0] b);
    begin
      if (f_one_rd(a,b) == 0) f_cable = 0;
      else if (a == L_RD)     f_cable = (b == L_RA) ? 1 : 0;
      else                    f_cable = (a == L_RA) ? 1 : 0;
    end
  endfunction

  // The decoded SITUATION -- the three answers bundled, which is what the
  // debounce window actually applies to. Written here as an integer so the
  // model can say "has anything changed" in one comparison, exactly as the
  // design does, without sharing a line of code with it.
  function integer f_sit(input [1:0] a, input [1:0] b);
    begin
      f_sit = f_both_rd(a,b) * 4 + f_cable(a,b) * 2 + f_orient(a,b);
    end
  endfunction

  task reset_dut;
    begin
      rst_n = 1'b0; tick = 1'b0; cc1 = L_OPEN; cc2 = L_OPEN;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      @(posedge clk); #1;
      m_run = 0; m_attached = 0; m_orient = 0; m_cable = 0; m_debug = 0;
      m_att_c = 0; m_det_c = 0; m_gli_c = 0;
      m_prev_cond = 1'b0;
    end
  endtask

  // ---- change CC strictly BETWEEN ticks ----
  //
  // CC is asynchronous to the debounce tick in real hardware, and letting
  // a change land on the same cycle as a tick makes "how many ticks has
  // this held for" ambiguous by one -- in the bench's bookkeeping, not in
  // the design. Keeping them apart removes the ambiguity rather than
  // papering over it.
  task set_cc(input [1:0] a, input [1:0] b);
    integer was_cond, now_cond, was_sit, now_sit;
    begin
      was_cond = f_cond(cc1, cc2);
      now_cond = f_cond(a, b);
      was_sit  = f_sit(cc1, cc2);
      now_sit  = f_sit(a, b);
      // ---- the model's glitch rule, stated in English ----
      //
      // A glitch is an attach condition that was asserted and then
      // WITHDRAWN before it had held long enough to be believed. Note
      // there is no "and it lasted at least one tick" clause: the design
      // enters its debounce state on the clock edge after the condition
      // appears, so a condition that comes and goes between two ticks is
      // still an abandoned attach and is still counted. Requiring a tick
      // here cost 60 failures and the design was right.
      if (!m_attached && was_cond && !now_cond)
        m_gli_c = m_gli_c + 1;
      // ---- what restarts the window ----
      //
      // While UNATTACHED, the window applies to the whole decoded
      // situation: a sink moving from one CC line to the other leaves
      // `attach_cond` true and still restarts the clock, because it is not
      // the same attachment any more.
      //
      // While ATTACHED, only the fact of attachment matters -- the design
      // holds its latched answers rather than re-deciding them, and a
      // physical change of orientation without an intervening detach is
      // not something a connector can do.
      if (m_attached) begin
        if (was_cond != now_cond) m_run = 0;
      end else begin
        if (was_cond != now_cond || was_sit != now_sit) m_run = 0;
      end
      cc1 = a; cc2 = b;
      @(posedge clk); #1;
      steps = steps + 1;
    end
  endtask

  // ---- one debounce tick, with CC held ----
  task do_tick;
    integer cond;
    begin
      cond = f_cond(cc1, cc2);
      tick = 1'b1;
      @(posedge clk); #1;
      tick = 1'b0;

      // advance the model
      if (cond != m_attached) begin
        m_run = m_run + 1;
        if (m_run == DEBOUNCE) begin
          if (cond) begin
            m_attached = 1;
            m_orient   = f_orient(cc1, cc2);
            m_cable    = f_cable(cc1, cc2);
            m_debug    = f_both_rd(cc1, cc2);
            m_att_c    = m_att_c + 1;
          end else begin
            m_attached = 0;
            m_det_c    = m_det_c + 1;
          end
          m_run = 0;
        end
      end else begin
        m_run = 0;
      end

      // ---- PROPERTY 1: attach follows the model exactly ----
      ck(attached === (m_attached ? 1'b1 : 1'b0),
         "attached disagrees with the run-length model");
      // ---- PROPERTY 2: the three latched answers are right ----
      //
      // Checked only while attached: outside an attachment the spec gives
      // them no meaning, and asserting a value the design is free to hold
      // would be asserting an implementation detail rather than a contract.
      if (m_attached) begin
        ck(orientation === (m_orient ? 1'b1 : 1'b0),
           "orientation disagrees with which line carried Rd");
        ck(cable_powered === (m_cable ? 1'b1 : 1'b0),
           "cable_powered disagrees with Ra on the free line");
        ck(debug_accessory === (m_debug ? 1'b1 : 1'b0),
           "debug_accessory disagrees with both lines pulled to Rd");
      end
      // ---- PROPERTY 3: the counters agree with the model's tally ----
      ck(n_attach == m_att_c[15:0], "n_attach disagrees with the model");
      ck(n_detach == m_det_c[15:0], "n_detach disagrees with the model");
      ck(n_glitch == m_gli_c[15:0], "n_glitch disagrees with the model");
      // Sample the trace once per tick. Without this call `tr_n` stays at
      // zero, the comparison loop in phase 3 has an empty range, and the
      // symmetry property -- the whole point of the chapter -- is never
      // evaluated while every one of its checks reports success.
      rec;
      steps = steps + 1;
    end
  endtask

  task ticks_n(input integer n);
    integer i;
    begin for (i = 0; i < n; i = i + 1) do_tick; end
  endtask

  // ---- the trace, for the symmetry comparison ----
  //
  // Four bits per tick: attached, orientation, cable_powered,
  // debug_accessory. Two runs of the same sequence -- one plain, one with
  // the two CC lines exchanged -- must agree on all of it except the
  // orientation bit, which must differ whenever a single-Rd sink is
  // attached.
  reg [3:0] tr_a [0:511];
  reg [3:0] tr_b [0:511];
  integer   tr_n;
  reg       recording;

  task rec;
    begin
      if (recording && tr_n < 512) begin
        tr_a[tr_n] = {debug_accessory, cable_powered, orientation, attached};
        tr_n = tr_n + 1;
      end
    end
  endtask

  // ---- exhaustive reach over ordered pairs of CC states ----
  reg reach [0:80];
  integer nr, ri;

  integer i, j, w, g, p, q;
  reg [1:0] ST [0:2];
  // The five CC combinations that constitute an attach, and they have five
  // DISTINCT decoded situations -- which is what makes the ordered pairs
  // below a meaningful sweep rather than a list with duplicates in it.
  reg [1:0] ATT1 [0:4];
  reg [1:0] ATT2 [0:4];

  // the glitch table: accepted (1) or not (0), per width, per combination
  integer tab_w [0:12];
  integer tab_acc [0:4][0:12];
  integer tab_n;

  integer a_idx, b_idx;

  initial begin
    ST[0] = L_OPEN; ST[1] = L_RA; ST[2] = L_RD;
    ATT1[0] = L_OPEN; ATT2[0] = L_RD;      // sink on CC2
    ATT1[1] = L_RA;   ATT2[1] = L_RD;      // sink on CC2, powered cable
    ATT1[2] = L_RD;   ATT2[2] = L_OPEN;    // sink on CC1
    ATT1[3] = L_RD;   ATT2[3] = L_RA;      // sink on CC1, powered cable
    ATT1[4] = L_RD;   ATT2[4] = L_RD;      // debug accessory
    for (ri = 0; ri < 81; ri = ri + 1) reach[ri] = 1'b0;
    seed = 32'd29004;
    recording = 1'b0;
    tr_n = 0;

    reset_dut;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every ordered pair of CC
    //  states, held long enough to be believed.
    //
    //  81 pairs. For each: settle in the `from` state, switch to the
    //  `to` state, hold it past the debounce window, and check the
    //  design against the model at every tick.
    // =============================================================
    for (i = 0; i < 9; i = i + 1)
    for (j = 0; j < 9; j = j + 1) begin
      reset_dut;
      set_cc(ST[i/3], ST[i%3]);
      ticks_n(DEBOUNCE + 2);
      set_cc(ST[j/3], ST[j%3]);
      ticks_n(DEBOUNCE + 2);
      reach[i*9 + j] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED, EXHAUSTIVE) -- the debounce threshold.
    //
    //  For each of the five CC combinations that constitute an attach,
    //  present it for every width from 0 to DEBOUNCE+2 ticks and then
    //  remove it. The design must accept the attach if and only if the
    //  width reached DEBOUNCE -- no earlier, and not never.
    //
    //  This is the measurement the chapter reports, and it is swept
    //  exhaustively because the interesting values are all adjacent:
    //  DEBOUNCE-1 must be rejected and DEBOUNCE must be accepted, and a
    //  suite that tested 0 and 100 would pass with either bound wrong.
    // =============================================================
    tab_n = 0;
    for (g = 0; g < 9; g = g + 1) begin
      if (f_cond(ST[g/3], ST[g%3])) begin
        for (w = 0; w <= DEBOUNCE + 2; w = w + 1) begin
          reset_dut;
          set_cc(ST[g/3], ST[g%3]);
          ticks_n(w);
          set_cc(L_OPEN, L_OPEN);
          ticks_n(DEBOUNCE + 2);
          // ---- PROPERTY 4: the threshold is exactly DEBOUNCE ----
          ck((n_attach == 16'd1) == (w >= DEBOUNCE),
             "an attach was accepted at the wrong debounce width");
          // ---- PROPERTY 5: a rejected attach is COUNTED as a glitch ----
          //
          // Silently discarding a bouncing contact is the same behaviour
          // as never seeing it, and a bring-up engineer cannot tell a
          // dead cable from a bouncing one without this counter.
          if (w > 0 && w < DEBOUNCE)
            ck(n_glitch == 16'd1, "a rejected attach was not counted as a glitch");
          if (tab_n < 5) tab_acc[tab_n][w] = (n_attach == 16'd1) ? 1 : 0;
          tab_w[w] = w;
        end
        tab_n = tab_n + 1;
      end
    end

    // =============================================================
    //  PHASE 3 (DIRECTED, EXHAUSTIVE) -- THE SYMMETRY.
    //
    //  Replay all 81 ordered pairs with CC1 and CC2 exchanged, and
    //  require the two traces to agree sample by sample on `attached`,
    //  `cable_powered` and `debug_accessory`, and to DISAGREE on
    //  `orientation` whenever a single-Rd sink is attached.
    //
    //  Why orientation is exempt when debug_accessory is set: with Rd on
    //  BOTH lines there is no "other" line, so exchanging them changes
    //  nothing and orientation is identical rather than inverted. That is
    //  not a hole in the property -- it is the property, stated exactly.
    // =============================================================
    for (i = 0; i < 9; i = i + 1)
    for (j = 0; j < 9; j = j + 1) begin
      // ---- the plain run ----
      reset_dut;
      recording = 1'b1; tr_n = 0;
      set_cc(ST[i/3], ST[i%3]);   ticks_n(DEBOUNCE + 2);
      set_cc(ST[j/3], ST[j%3]);   ticks_n(DEBOUNCE + 2);
      recording = 1'b0;
      for (p = 0; p < tr_n; p = p + 1) tr_b[p] = tr_a[p];
      q = tr_n;

      // ---- the same sequence, plug turned over ----
      reset_dut;
      recording = 1'b1; tr_n = 0;
      set_cc(ST[i%3], ST[i/3]);   ticks_n(DEBOUNCE + 2);
      set_cc(ST[j%3], ST[j/3]);   ticks_n(DEBOUNCE + 2);
      recording = 1'b0;

      ck(tr_n == q, "the flipped run produced a different number of samples");
      for (p = 0; p < q && p < tr_n; p = p + 1) begin
        // ---- PROPERTY 6: attach is orientation-INDEPENDENT ----
        ck(tr_a[p][0] === tr_b[p][0],
           "the plug turned over changed WHETHER something was attached");
        // ---- PROPERTY 7: so are the cable and debug answers ----
        ck(tr_a[p][2] === tr_b[p][2],
           "the plug turned over changed the powered-cable answer");
        ck(tr_a[p][3] === tr_b[p][3],
           "the plug turned over changed the debug-accessory answer");
        // ---- PROPERTY 8: orientation INVERTS, for a single-Rd sink ----
        if (tr_b[p][0] && !tr_b[p][3])
          ck(tr_a[p][1] !== tr_b[p][1],
             "the plug turned over did NOT change the reported orientation");
        // ---- PROPERTY 9: and does NOT invert for a debug accessory ----
        if (tr_b[p][0] && tr_b[p][3])
          ck(tr_a[p][1] === tr_b[p][1],
             "a debug accessory reported a different orientation when flipped");
      end
    end

    // =============================================================
    //  PHASE 4 (DIRECTED) -- a detach that changes its mind.
    //
    //  A momentary break while the cable is wiggled must NOT tear down
    //  the attachment. `attached` has to stay high across the whole
    //  detach-debounce window, and the attach counter must not advance
    //  when the condition returns -- nothing was re-attached.
    // =============================================================
    reset_dut;
    set_cc(L_RD, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached === 1'b1, "a fully debounced attach did not take");
    for (w = 0; w < DEBOUNCE; w = w + 1) begin
      set_cc(L_OPEN, L_OPEN);
      ticks_n(w);
      ck(attached === 1'b1,
         "a momentary break tore down an established attachment");
      set_cc(L_RD, L_OPEN);
      ticks_n(2);
      ck(attached === 1'b1, "the attachment did not survive the break");
      ck(n_attach == 16'd1,
         "a break that came back was counted as a second attach");
      ck(n_detach == 16'd0, "a break that came back was counted as a detach");
    end
    // and now let it go for real
    set_cc(L_OPEN, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached === 1'b0, "a fully debounced detach did not take");
    ck(n_detach == 16'd1, "the real detach was not counted");

    // ---- PROPERTY 10: the SECOND attach gets the same window ----
    //
    // Every other phase resets the DUT before each attach, so every one
    // of them is the first. That makes an entire class of bug invisible:
    // a debounce counter that is not cleared when the wait state is
    // re-entered still gives the first attach a full window and gives
    // every one after it whatever was left over.
    //
    // Here the port has already attached and detached once, so the
    // counter is wherever the previous attachment left it. A fresh attach
    // held one tick short must still be rejected.
    // Swept across the whole window rather than tested at one point: the
    // stale-counter bug leaves an ARBITRARY residue behind, so which width
    // exposes it depends on what the previous attachment happened to do.
    // A single probe at DEBOUNCE-1 would find some residues and miss others.
    for (w = 0; w <= DEBOUNCE; w = w + 1) begin
      set_cc(L_RD, L_OPEN);
      ticks_n(w);
      ck((attached === 1'b1) == (w >= DEBOUNCE),
         "the second attach used a different debounce window from the first");
      set_cc(L_OPEN, L_OPEN);
      ticks_n(DEBOUNCE + 2);
      ck(attached === 1'b0, "the port did not return to unattached");
    end

    // =============================================================
    //  PHASE 5 (DIRECTED, EXHAUSTIVE) -- the situation changes DURING the
    //  window, and the window restarts.
    //
    //  This is the phase that separates "debounce the fact of attachment"
    //  from "debounce the CC state", and they are not the same thing. A
    //  sink moving from CC1 to CC2 part-way through the window -- a plug
    //  being rocked into place -- never drops `attach_cond`. A machine
    //  watching only `attach_cond` would accept that attachment with an
    //  orientation that had been stable for a single tick.
    //
    //  Every ordered pair of distinct attach situations, at three points
    //  in the window: the clock must restart from the change, so
    //  acceptance lands DEBOUNCE ticks after it and not DEBOUNCE minus
    //  however long the first one was held.
    // =============================================================
    for (p = 0; p < 5; p = p + 1)
    for (q = 0; q < 5; q = q + 1)
    if (p != q) begin
      for (g = 0; g < 3; g = g + 1) begin
        w = (g == 0) ? 1 : (g == 1) ? 3 : DEBOUNCE - 1;
        reset_dut;
        set_cc(ATT1[p], ATT2[p]);
        ticks_n(w);
        ck(attached === 1'b0, "the first situation was accepted too early");
        set_cc(ATT1[q], ATT2[q]);
        // ---- PROPERTY 11: the window restarts from the CHANGE ----
        ticks_n(DEBOUNCE - 1);
        ck(attached === 1'b0,
           "a mid-window change of CC state did not restart the debounce");
        do_tick;
        ck(attached === 1'b1,
           "the restarted window did not complete one tick later");
        // ---- PROPERTY 12: the answers are the SECOND situation's ----
        //
        // Not the first's. The whole reason to restart is that the
        // attachment being accepted is the one that has been stable.
        ck(orientation === (f_orient(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted orientation was the one that had already gone away");
        ck(cable_powered === (f_cable(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted cable answer was the stale one");
        ck(debug_accessory === (f_both_rd(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted debug answer was the stale one");
        // ---- PROPERTY 13: no glitch was counted ----
        //
        // Nothing was abandoned. The attach condition held throughout; it
        // was only ever a different attachment.
        ck(n_glitch == 16'd0,
           "a change of situation was miscounted as an abandoned attach");
      end
    end

    // =============================================================
    //  PHASE 6 (RANDOM) -- a plug being pushed home.
    //
    //  Real insertion is not one clean edge. The contacts make and break
    //  several times over a few milliseconds, in whatever order the
    //  mechanics happen to produce, and both CC lines bounce
    //  independently.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (w = 0; w < 400; w = w + 1) begin
      a_idx = urand(0) % 3;
      b_idx = urand(0) % 3;
      set_cc(ST[a_idx], ST[b_idx]);
      // 1..16 ticks, not 1..4. Against a ten-tick window a hold of at most
      // four can NEVER complete one, so the phase produced 400 events and
      // zero attaches -- it exercised the glitch path and nothing else.
      // Widening the hold past the window is what lets the random phase
      // reach the attached state, and therefore the detach path too.
      ticks_n(1 + (urand(0) % 16));
    end
`endif

    nr = 0; for (ri = 0; ri < 81; ri = ri + 1) if (reach[ri]) nr = nr + 1;

    $display("steps=%0d checks=%0d reach=%0d/81 errors=%0d",
             steps, checks, nr, errors);
    $display("[typec] attaches=%0d detaches=%0d glitches=%0d",
             n_attach, n_detach, n_glitch);
    $display("--- was the attach accepted, by how long it was held ---");
    $write("    ticks held   ");
    for (w = 0; w <= DEBOUNCE + 2; w = w + 1) $write("%3d", w);
    $write("\n");
    for (p = 0; p < 5; p = p + 1) begin
      $write("    combo %0d      ", p);
      for (w = 0; w <= DEBOUNCE + 2; w = w + 1) $write("%3d", tab_acc[p][w]);
      $write("\n");
    end
    if (nr != 81) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

6. The Measurement

The threshold is exactly the window, for every combination

Each of the five CC combinations that constitute an attach, presented for every width from 0 to DEBOUNCE+2 ticks and then removed. 1 means the port believed it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    --- was the attach accepted, by how long it was held ---
        ticks held     0  1  2  3  4  5  6  7  8  9 10 11 12
        combo 0        0  0  0  0  0  0  0  0  0  0  1  1  1
        combo 1        0  0  0  0  0  0  0  0  0  0  1  1  1
        combo 2        0  0  0  0  0  0  0  0  0  0  1  1  1
        combo 3        0  0  0  0  0  0  0  0  0  0  1  1  1
        combo 4        0  0  0  0  0  0  0  0  0  0  1  1  1

        combo 0  CC2=Rd                 a sink on CC2
        combo 1  CC1=Ra  CC2=Rd         a sink on CC2, powered cable
        combo 2  CC1=Rd                 a sink on CC1
        combo 3  CC1=Rd  CC2=Ra         a sink on CC1, powered cable
        combo 4  CC1=Rd  CC2=Rd         a debug accessory

A step function, in the same place, five times. Nine ticks is rejected and ten is accepted, whichever line the sink is on and whether or not there is a powered cable in the way.

The sweep is exhaustive rather than sampled, and that is the point: the interesting values are adjacent. DEBOUNCE-1 must be rejected and DEBOUNCE must be accepted, and a suite that tested 0 and 100 would pass with either bound off by one. An off-by-one here is the difference between rejecting insertion bounce and enumerating in the middle of it.

The symmetry

All 81 ordered pairs of CC states, each run twice — once as given, once with CC1 and CC2 exchanged — and the two traces compared sample by sample, 24 samples per run:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    attached          MUST be identical
    cable_powered     MUST be identical
    debug_accessory   MUST be identical
    orientation       MUST INVERT   -- when a single-Rd sink is attached
    orientation       MUST be identical -- when a DEBUG ACCESSORY is attached

That last line is not an exemption carved out to make the property pass. With Rd on both lines there is no "other" line, so exchanging them changes nothing and the orientation is identical rather than inverted. Stating it as a separate clause rather than excluding the case is what makes the property exact: the debug-accessory rows are still checked, just against the correct expectation.

The mid-window restart

All 20 ordered pairs of distinct attach situations — the five combinations above have five distinct decoded situations, so every pair is a real change — presented at three points in the window: one tick in, three ticks in, and one tick from completion.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    hold situation A for k ticks     k = 1, 3, DEBOUNCE-1
    switch to situation B
    DEBOUNCE-1 more ticks            -> still NOT attached
    one more tick                    -> attached, with B's answers
    and n_glitch is still 0          -> nothing was abandoned

The last line is the one that distinguishes this from a detach. The attach condition never dropped; it was only ever a different attachment. A machine that restarted by bouncing through its unattached state would get the timing right and the glitch count wrong, and the counter is the only thing that would notice.

Run totals

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                        steps     checks   reach   errors
    Verilog-2005       12,564     64,920   81/81        0
    SystemVerilog      12,564     64,920   81/81        0
    VHDL-2008          12,777     65,760   81/81        0

    DIRECTED ONLY (-D DIRECTED_ONLY / -gDIRECTED_ONLY=true)
    Verilog-2005        8,918     46,746   81/81        0
    SystemVerilog       8,918     46,746   81/81        0
    VHDL-2008           8,918     46,746   81/81        0

The directed rows agree to the check across all three languages, which is what makes the directed mutation columns in section 11 comparable. The full rows do not, and should not: the random phase uses $random in the two Icarus benches and a VHDL-native generator in the third, so a different number of bounces happen to be produced and a different number of checks fire.

7. SystemVerilog

Same hardware contract: same ports, same widths, same reset values, same cycle-by-cycle behaviour. What changes is that the three-level CC reading and the four machine states become enums, and the decoded situation becomes a packed struct rather than three bits kept in the right order by hand.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Verilog                      SystemVerilog
    -------------------------    ----------------------------------
    localparam L_RD = 2'b10      typedef enum { CC_OPEN, CC_RA, CC_RD }
    2'b11 is representable       2'b11 is not a value of the type

    {both_rd, cable, orient}     typedef struct packed { debug; cable; orient; }
    sit_lat[2] is the debug bit  sit_lat.debug is the debug bit

The struct earns its place at the point of use: sit_lat[0] and sit_lat[2] are one transposition away from latching the cable answer into the orientation register, and sit_lat.orient is not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  typec_cc_fsm -- SystemVerilog.
//
//  Same hardware contract as the Verilog file: same ports, same widths,
//  same reset values, same cycle-by-cycle behaviour. What changes is that
//  the three-level CC reading and the four machine states become ENUMS,
//  so an illegal value is a compile error rather than a silent default,
//  and the decoded situation becomes a packed struct rather than three
//  bits that have to be kept in the right order by hand.
//
//  EVERY CONTINUOUS ASSIGNMENT IS `logic` + `assign` ON SEPARATE LINES.
//  `logic x = expr;` is a one-shot VARIABLE INITIALISER in SystemVerilog,
//  evaluated once at time zero and never again, while the Verilog
//  `wire x = expr;` it came from is a continuous assignment. Earlier in
//  this track the mechanical translation of five such lines produced
//  29,580 phantom failures against a design that was entirely correct.
//
//  typec_cc_fsm -- the Configuration Channel state machine that decides
//  whether anything is plugged in, which way round it is, and how much
//  current it is allowed to draw.
//
//  CLASSIFICATION: simplified synthesisable teaching RTL.
//  This is NOT a Type-C port controller. There is no VBUS switch, no
//  Power Delivery protocol engine, no BMC PHY, no VCONN switching and no
//  alternate-mode entry. It is the CC attach machine: the part that runs
//  before any of that exists, and that everything else depends on.
//
//  WHY THIS IS THE MECHANISM WORTH BUILDING FOR "PHONE ON A CABLE"
//  ---------------------------------------------------------------
//  A phone on a USB-C cable is simultaneously a mass-storage-ish device
//  (MTP), a camera (PTP), a serial console (ADB), possibly a display
//  source (DisplayPort alt mode), and either a power sink or a power
//  source. Which of those it is gets NEGOTIATED, and none of that
//  negotiation can start until three questions are answered:
//
//      1. IS ANYTHING THERE?      -- and is it stable, or is it a
//                                    contact bouncing as the plug seats
//      2. WHICH WAY ROUND IS IT?  -- the connector is reversible, so the
//                                    same pin is TX in one orientation
//                                    and RX in the other
//      3. HOW MUCH CURRENT?       -- advertised as a resistor value
//                                    before a single bit is exchanged
//
//  All three are answered by two pins and some resistors, and the answers
//  are read as VOLTAGES rather than as a protocol. That is deliberate: it
//  works with a dumb cable, a dumb charger and no firmware running.
//
//  THE CC LINES
//  ------------
//  A source (DFP) pulls both CC lines up through Rp. A sink (UFP) pulls
//  its one connected CC line down through Rd. A powered cable identifies
//  itself with Ra on the other line. The source reads each line as one of
//  three levels:
//
//      OPEN   nothing pulling down          -- nothing on this line
//      Ra     pulled down weakly            -- a powered (e-marked) cable
//      Rd     pulled down harder            -- a SINK is on this line
//
//  Because the plug is reversible, the sink's Rd lands on CC1 or CC2
//  depending on which way the user inserted it, and WHICH ONE IT IS is
//  the orientation. There is no other way to learn it.
// =====================================================================
// ---- the three levels a CC line can read ----
//
// A 2-bit vector would let 2'b11 through, and 2'b11 is not a thing a
// comparator can report. Naming the legal values makes the illegal one
// unrepresentable instead of merely unexpected.
typedef enum logic [1:0] {
  CC_OPEN = 2'b00,   // nothing pulling this line down
  CC_RA   = 2'b01,   // a powered (e-marked) cable
  CC_RD   = 2'b10    // a SINK is on this line
} cc_level_t;

module typec_cc_fsm #(
  // How many debounce ticks a CC state must persist before it is believed.
  // The specification calls this tCCDebounce and requires 100..200 ms. A
  // real port ticks at 1 ms and uses 100..200 here; this module uses 10 so
  // a simulation can sweep the whole window exhaustively.
  parameter integer DEBOUNCE_TICKS = 10
) (
  input  logic      clk,
  input  logic      rst_n,

  // One pulse per debounce tick. Everything time-related in this module
  // is counted in ticks, never in clock cycles, so the same RTL works at
  // any clock frequency.
  input  logic      tick,

  // What the comparators say about each CC line, sampled continuously.
  //   2'b00 OPEN   2'b01 Ra   2'b10 Rd
  input  cc_level_t cc1_st,
  input  cc_level_t cc2_st,

  // ---- the three answers ----
  output logic      attached,
  // 1'b0 = the sink is on CC1, 1'b1 = the sink is on CC2. Meaningless
  // unless `attached`, and held at its last value rather than cleared, so
  // that a detach does not momentarily steer a mux the wrong way.
  output logic      orientation,
  // A powered cable was detected alongside the sink. VCONN would be
  // supplied on the OTHER line -- the one carrying Ra.
  output logic      cable_powered,
  // Both CC lines pulled down by Rd. That is not a sink; it is a debug
  // accessory, and treating it as a sink is a real interoperability bug.
  output logic      debug_accessory,

  output logic [15:0] n_attach,
  output logic [15:0] n_detach,
  output logic [15:0] n_glitch      // attaches that failed to debounce
);

  typedef enum logic [1:0] {
    S_UNATTACHED = 2'd0,
    S_ATTACHWAIT = 2'd1,
    S_ATTACHED   = 2'd2,
    S_DETACHWAIT = 2'd3
  } state_t;

  // ---- the decoded situation, as a struct rather than three loose bits --
  //
  // The Verilog file packs these into a 3-bit vector and indexes it by
  // number at the point of use. That works and it is one transposition
  // away from latching the cable answer into the orientation register.
  typedef struct packed {
    logic debug;    // both lines pulled to Rd
    logic cable;    // Ra on the line the sink is not on
    logic orient;   // 0 = sink on CC1, 1 = sink on CC2
  } situation_t;

  state_t      state;
  logic [15:0] ticks;
  // What is currently being debounced. See sit_now below.
  situation_t  sit_lat;
  logic        orient_r, cable_r, debug_r;
  logic [15:0] att_c, det_c, gli_c;

  // ---- decode the two lines ----
  logic cc1_rd;
  assign cc1_rd = (cc1_st == CC_RD);
  logic cc2_rd;
  assign cc2_rd = (cc2_st == CC_RD);
  logic cc1_ra;
  assign cc1_ra = (cc1_st == CC_RA);
  logic cc2_ra;
  assign cc2_ra = (cc2_st == CC_RA);

  // EXACTLY ONE line pulled down by Rd is a sink. The exclusive-or is the
  // whole rule, and it is what makes the connector reversible: the machine
  // does not care which line it is, only that precisely one of them is.
  logic one_rd;
  assign one_rd = cc1_rd ^ cc2_rd;
  // BOTH lines pulled down by Rd is a debug accessory, not a sink. A port
  // that treats it as a sink will drive VBUS into a debug jig.
  logic both_rd;
  assign both_rd = cc1_rd & cc2_rd;

  logic attach_cond;
  assign attach_cond = one_rd | both_rd;

  // Which line the sink is on. Only meaningful when exactly one Rd is
  // present, which is exactly when it is sampled.
  logic orient_now;
  assign orient_now = cc2_rd;
  // Ra on the line the sink is NOT on. With both_rd there is no free line,
  // so there is no powered cable to find.
  logic cable_now;
  assign cable_now = one_rd ? (cc1_rd ? cc2_ra : cc1_ra) : 1'b0;

  // ---- the SITUATION, which is what actually gets debounced ----
  //
  // The specification debounces the CC STATE, not the fact that something
  // is attached. Those are different, and the difference is a real bug:
  // if Rd moves from CC1 to CC2 part-way through the window -- which is
  // exactly what a plug being rocked into place does -- then attach_cond
  // never drops, and a machine watching only attach_cond would accept the
  // attachment with an orientation that had been stable for one tick.
  //
  // Bundling the three decoded answers into one value makes "has anything
  // about this changed" a single comparison, and makes it impossible to
  // debounce one answer and forget another.
  situation_t sit_now;
  // Assigned as a vector rather than as an assignment pattern
  // ('{debug: ..., cable: ...}), which Icarus rejects in a continuous
  // assignment. The field ORDER in the typedef is therefore load-bearing
  // here -- which is exactly the hazard the struct was meant to remove, so
  // the fields are read back by NAME everywhere else in the module and
  // this is the one place the order matters.
  assign sit_now = {both_rd, cable_now, orient_now};

  assign attached        = (state == S_ATTACHED) || (state == S_DETACHWAIT);
  assign orientation     = orient_r;
  assign cable_powered   = cable_r;
  assign debug_accessory = debug_r;
  assign n_attach        = att_c;
  assign n_detach        = det_c;
  assign n_glitch        = gli_c;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state    <= S_UNATTACHED;
      ticks    <= 16'd0;
      sit_lat  <= '0;
      orient_r <= 1'b0;
      cable_r  <= 1'b0;
      debug_r  <= 1'b0;
      att_c    <= 16'd0;
      det_c    <= 16'd0;
      gli_c    <= 16'd0;
    end else begin
      case (state)
        // -------------------------------------------------------------
        S_UNATTACHED: begin
          if (attach_cond) begin
            state   <= S_ATTACHWAIT;
            ticks   <= 16'd0;
            sit_lat <= sit_now;
          end
        end

        // -------------------------------------------------------------
        //  The condition has to SURVIVE tCCDebounce. This is not
        //  belt-and-braces: a plug being pushed home makes and breaks
        //  contact several times over a few milliseconds, and a port that
        //  believed the first edge would enumerate, drop, and enumerate
        //  again -- which is the "my phone keeps reconnecting" bug.
        // -------------------------------------------------------------
        S_ATTACHWAIT: begin
          if (!attach_cond) begin
            // it went away before it was believed
            state <= S_UNATTACHED;
            gli_c <= gli_c + 16'd1;
          end else if (sit_now != sit_lat) begin
            // Still attached, but it is not the same attachment any more.
            // The window restarts from here: the clock on "has this been
            // stable" runs from the LAST change, not from the first.
            ticks   <= 16'd0;
            sit_lat <= sit_now;
          end else if (tick) begin
            if (ticks == DEBOUNCE_TICKS[15:0] - 16'd1) begin
              state    <= S_ATTACHED;
              // Taken from the DEBOUNCED situation, which by construction
              // has been stable for the whole window. Note that this makes
              // the sampling instant irrelevant: sit_lat and sit_now are
              // equal here, so latching at entry and latching at
              // acceptance would give the same answer. That equivalence is
              // the point -- it is what a correct debounce buys, and
              // without the restart above it would not hold.
              orient_r <= sit_lat.orient;
              cable_r  <= sit_lat.cable;
              debug_r  <= sit_lat.debug;
              att_c    <= att_c + 16'd1;
            end else begin
              ticks <= ticks + 16'd1;
            end
          end
        end

        // -------------------------------------------------------------
        S_ATTACHED: begin
          if (!attach_cond) begin
            state <= S_DETACHWAIT;
            ticks <= 16'd0;
          end
        end

        // -------------------------------------------------------------
        //  A detach is debounced too, and for the same reason in reverse:
        //  a momentary break while the cable is wiggled must not tear down
        //  a power contract and a display link.
        //
        //  `attached` stays HIGH throughout this state. A port that
        //  dropped it on the first missing sample would produce exactly
        //  the disconnect it is trying to avoid.
        // -------------------------------------------------------------
        S_DETACHWAIT: begin
          if (attach_cond) begin
            state <= S_ATTACHED;      // it came back: nothing happened
          end else if (tick) begin
            if (ticks == DEBOUNCE_TICKS[15:0] - 16'd1) begin
              state <= S_UNATTACHED;
              det_c <= det_c + 16'd1;
            end else begin
              ticks <= ticks + 16'd1;
            end
          end
        end

        default: state <= S_UNATTACHED;
      endcase
    end
  end

endmodule

The SystemVerilog testbench

Same seed and same phase order as the Verilog bench, deliberately: Icarus seeds $random identically for both, so the two drive identical stimulus and any difference between their mutation columns is a difference between the two designs. The independent-stimulus role belongs to VHDL.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  Testbench for typec_cc_fsm. -- SystemVerilog.
//
//  SAME SEED AND SAME PHASE ORDER AS THE VERILOG BENCH, deliberately.
//  Icarus seeds $random identically, so both drive identical stimulus and
//  any difference between the two mutation columns is a real difference
//  between the two DESIGNS. The independent-stimulus role is VHDL's.
//
//  THE HEADLINE PROPERTY IS A SYMMETRY, AND IT IS THE REASON THE
//  CONNECTOR CAN BE REVERSIBLE.
//
//  Every other chapter in this module asserts that the design computes
//  the right VALUE. This one asserts that it computes the SAME value
//  under a transformation of its inputs: exchange CC1 and CC2 -- which is
//  physically what happens when the user turns the plug over -- and
//  every output must be identical except `orientation`, which must
//  invert.
//
//  That is a stronger statement than "it works both ways round", and it
//  is checkable EXHAUSTIVELY over single transitions: 9 CC states x 9 CC
//  states = 81 ordered pairs, each replayed flipped and compared sample
//  by sample. A design that special-cased CC1 -- which is the natural way
//  to write it, and wrong -- fails the symmetry without failing any
//  single-orientation test.
//
//  THE MODEL IS A RUN-LENGTH THRESHOLD, NOT A STATE MACHINE. The design
//  is four states and a counter; the model counts how many consecutive
//  ticks the attach condition has held and compares against DEBOUNCE.
//  Same behaviour, different formulation, so a state-encoding mistake in
//  one cannot be reproduced by the other.
// =====================================================================
`timescale 1ns/1ps
module tb_tc_sv;

  localparam integer DEBOUNCE = 10;

  localparam [1:0] L_OPEN = 2'b00, L_RA = 2'b01, L_RD = 2'b10;

  logic clk = 1'b0, rst_n = 1'b0;
  always #5 clk = ~clk;

  logic        tick = 1'b0;
  logic [1:0] cc1 = L_OPEN, cc2 = L_OPEN;

  logic        attached, orientation, cable_powered, debug_accessory;
  logic [15:0] n_attach, n_detach, n_glitch;

  typec_cc_fsm #(.DEBOUNCE_TICKS(DEBOUNCE)) dut (
    .clk(clk), .rst_n(rst_n), .tick(tick),
    .cc1_st(cc1), .cc2_st(cc2),
    .attached(attached), .orientation(orientation),
    .cable_powered(cable_powered), .debug_accessory(debug_accessory),
    .n_attach(n_attach), .n_detach(n_detach), .n_glitch(n_glitch)
  );

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

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

  // 1024 bits, not 256. A `what` of [255:0] holds 32 characters, and every
  // message below is longer than that -- so the one artefact a reader gets
  // when a check fails would have arrived cut in half. A diagnostic that
  // cannot say what went wrong is not a diagnostic.
  task automatic ck(input cond, input [1023:0] what);
    begin
      checks = checks + 1;
      if (!cond) begin
        errors = errors + 1;
        if (errors <= 20)
          $display("  ERROR @%0t step#%0d: %s", $time, steps, what);
      end
    end
  endtask

  // ---- the model, formulated as a run-length threshold ----
  //
  // The design is a four-state machine with a tick counter. The model
  // holds no state names at all: it counts consecutive ticks during which
  // the attach condition has held its current value, and flips
  // `m_attached` when that run reaches DEBOUNCE. A wrong state encoding
  // in the design cannot produce the same mistake here.
  int m_run;
  int m_attached, m_orient, m_cable, m_debug;
  int m_att_c, m_det_c, m_gli_c;
  logic     m_prev_cond;

  function automatic integer f_one_rd(input [1:0] a, input [1:0] b);
    begin f_one_rd = ((a == L_RD) ^ (b == L_RD)) ? 1 : 0; end
  endfunction
  function automatic integer f_both_rd(input [1:0] a, input [1:0] b);
    begin f_both_rd = ((a == L_RD) && (b == L_RD)) ? 1 : 0; end
  endfunction
  function automatic integer f_cond(input [1:0] a, input [1:0] b);
    begin f_cond = (f_one_rd(a,b) || f_both_rd(a,b)) ? 1 : 0; end
  endfunction
  function automatic integer f_orient(input [1:0] a, input [1:0] b);
    begin f_orient = (b == L_RD) ? 1 : 0; end
  endfunction
  function automatic integer f_cable(input [1:0] a, input [1:0] b);
    begin
      if (f_one_rd(a,b) == 0) f_cable = 0;
      else if (a == L_RD)     f_cable = (b == L_RA) ? 1 : 0;
      else                    f_cable = (a == L_RA) ? 1 : 0;
    end
  endfunction

  // The decoded SITUATION -- the three answers bundled, which is what the
  // debounce window actually applies to. Written here as an integer so the
  // model can say "has anything changed" in one comparison, exactly as the
  // design does, without sharing a line of code with it.
  function automatic integer f_sit(input [1:0] a, input [1:0] b);
    begin
      f_sit = f_both_rd(a,b) * 4 + f_cable(a,b) * 2 + f_orient(a,b);
    end
  endfunction

  task automatic reset_dut;
    begin
      rst_n = 1'b0; tick = 1'b0; cc1 = L_OPEN; cc2 = L_OPEN;
      @(posedge clk); @(posedge clk);
      rst_n = 1'b1;
      @(posedge clk); #1;
      m_run = 0; m_attached = 0; m_orient = 0; m_cable = 0; m_debug = 0;
      m_att_c = 0; m_det_c = 0; m_gli_c = 0;
      m_prev_cond = 1'b0;
    end
  endtask

  // ---- change CC strictly BETWEEN ticks ----
  //
  // CC is asynchronous to the debounce tick in real hardware, and letting
  // a change land on the same cycle as a tick makes "how many ticks has
  // this held for" ambiguous by one -- in the bench's bookkeeping, not in
  // the design. Keeping them apart removes the ambiguity rather than
  // papering over it.
  task automatic set_cc(input [1:0] a, input [1:0] b);
    int was_cond, now_cond, was_sit, now_sit;
    begin
      was_cond = f_cond(cc1, cc2);
      now_cond = f_cond(a, b);
      was_sit  = f_sit(cc1, cc2);
      now_sit  = f_sit(a, b);
      // ---- the model's glitch rule, stated in English ----
      //
      // A glitch is an attach condition that was asserted and then
      // WITHDRAWN before it had held long enough to be believed. Note
      // there is no "and it lasted at least one tick" clause: the design
      // enters its debounce state on the clock edge after the condition
      // appears, so a condition that comes and goes between two ticks is
      // still an abandoned attach and is still counted. Requiring a tick
      // here cost 60 failures and the design was right.
      if (!m_attached && was_cond && !now_cond)
        m_gli_c = m_gli_c + 1;
      // ---- what restarts the window ----
      //
      // While UNATTACHED, the window applies to the whole decoded
      // situation: a sink moving from one CC line to the other leaves
      // `attach_cond` true and still restarts the clock, because it is not
      // the same attachment any more.
      //
      // While ATTACHED, only the fact of attachment matters -- the design
      // holds its latched answers rather than re-deciding them, and a
      // physical change of orientation without an intervening detach is
      // not something a connector can do.
      if (m_attached) begin
        if (was_cond != now_cond) m_run = 0;
      end else begin
        if (was_cond != now_cond || was_sit != now_sit) m_run = 0;
      end
      cc1 = a; cc2 = b;
      @(posedge clk); #1;
      steps = steps + 1;
    end
  endtask

  // ---- one debounce tick, with CC held ----
  task automatic do_tick;
    int cond;
    begin
      cond = f_cond(cc1, cc2);
      tick = 1'b1;
      @(posedge clk); #1;
      tick = 1'b0;

      // advance the model
      if (cond != m_attached) begin
        m_run = m_run + 1;
        if (m_run == DEBOUNCE) begin
          if (cond) begin
            m_attached = 1;
            m_orient   = f_orient(cc1, cc2);
            m_cable    = f_cable(cc1, cc2);
            m_debug    = f_both_rd(cc1, cc2);
            m_att_c    = m_att_c + 1;
          end else begin
            m_attached = 0;
            m_det_c    = m_det_c + 1;
          end
          m_run = 0;
        end
      end else begin
        m_run = 0;
      end

      // ---- PROPERTY 1: attach follows the model exactly ----
      ck(attached === (m_attached ? 1'b1 : 1'b0),
         "attached disagrees with the run-length model");
      // ---- PROPERTY 2: the three latched answers are right ----
      //
      // Checked only while attached: outside an attachment the spec gives
      // them no meaning, and asserting a value the design is free to hold
      // would be asserting an implementation detail rather than a contract.
      if (m_attached) begin
        ck(orientation === (m_orient ? 1'b1 : 1'b0),
           "orientation disagrees with which line carried Rd");
        ck(cable_powered === (m_cable ? 1'b1 : 1'b0),
           "cable_powered disagrees with Ra on the free line");
        ck(debug_accessory === (m_debug ? 1'b1 : 1'b0),
           "debug_accessory disagrees with both lines pulled to Rd");
      end
      // ---- PROPERTY 3: the counters agree with the model's tally ----
      ck(n_attach == m_att_c[15:0], "n_attach disagrees with the model");
      ck(n_detach == m_det_c[15:0], "n_detach disagrees with the model");
      ck(n_glitch == m_gli_c[15:0], "n_glitch disagrees with the model");
      // Sample the trace once per tick. Without this call `tr_n` stays at
      // zero, the comparison loop in phase 3 has an empty range, and the
      // symmetry property -- the whole point of the chapter -- is never
      // evaluated while every one of its checks reports success.
      rec;
      steps = steps + 1;
    end
  endtask

  task automatic ticks_n(input integer n);
    int i;
    begin for (i = 0; i < n; i = i + 1) do_tick; end
  endtask

  // ---- the trace, for the symmetry comparison ----
  //
  // Four bits per tick: attached, orientation, cable_powered,
  // debug_accessory. Two runs of the same sequence -- one plain, one with
  // the two CC lines exchanged -- must agree on all of it except the
  // orientation bit, which must differ whenever a single-Rd sink is
  // attached.
  logic [3:0] tr_a [0:511];
  logic [3:0] tr_b [0:511];
  int   tr_n;
  logic       recording;

  task automatic rec;
    begin
      if (recording && tr_n < 512) begin
        tr_a[tr_n] = {debug_accessory, cable_powered, orientation, attached};
        tr_n = tr_n + 1;
      end
    end
  endtask

  // ---- exhaustive reach over ordered pairs of CC states ----
  logic reach [0:80];
  int nr, ri;

  int i, j, w, g, p, q;
  logic [1:0] ST [0:2];
  // The five CC combinations that constitute an attach, and they have five
  // DISTINCT decoded situations -- which is what makes the ordered pairs
  // below a meaningful sweep rather than a list with duplicates in it.
  logic [1:0] ATT1 [0:4];
  logic [1:0] ATT2 [0:4];

  // the glitch table: accepted (1) or not (0), per width, per combination
  int tab_w [0:12];
  integer tab_acc [0:4][0:12];
  int tab_n;

  int a_idx, b_idx;

  initial begin
    ST[0] = L_OPEN; ST[1] = L_RA; ST[2] = L_RD;
    ATT1[0] = L_OPEN; ATT2[0] = L_RD;      // sink on CC2
    ATT1[1] = L_RA;   ATT2[1] = L_RD;      // sink on CC2, powered cable
    ATT1[2] = L_RD;   ATT2[2] = L_OPEN;    // sink on CC1
    ATT1[3] = L_RD;   ATT2[3] = L_RA;      // sink on CC1, powered cable
    ATT1[4] = L_RD;   ATT2[4] = L_RD;      // debug accessory
    for (ri = 0; ri < 81; ri = ri + 1) reach[ri] = 1'b0;
    seed = 32'd29004;
    recording = 1'b0;
    tr_n = 0;

    reset_dut;

    // =============================================================
    //  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every ordered pair of CC
    //  states, held long enough to be believed.
    //
    //  81 pairs. For each: settle in the `from` state, switch to the
    //  `to` state, hold it past the debounce window, and check the
    //  design against the model at every tick.
    // =============================================================
    for (i = 0; i < 9; i = i + 1)
    for (j = 0; j < 9; j = j + 1) begin
      reset_dut;
      set_cc(ST[i/3], ST[i%3]);
      ticks_n(DEBOUNCE + 2);
      set_cc(ST[j/3], ST[j%3]);
      ticks_n(DEBOUNCE + 2);
      reach[i*9 + j] = 1'b1;
    end

    // =============================================================
    //  PHASE 2 (DIRECTED, EXHAUSTIVE) -- the debounce threshold.
    //
    //  For each of the five CC combinations that constitute an attach,
    //  present it for every width from 0 to DEBOUNCE+2 ticks and then
    //  remove it. The design must accept the attach if and only if the
    //  width reached DEBOUNCE -- no earlier, and not never.
    //
    //  This is the measurement the chapter reports, and it is swept
    //  exhaustively because the interesting values are all adjacent:
    //  DEBOUNCE-1 must be rejected and DEBOUNCE must be accepted, and a
    //  suite that tested 0 and 100 would pass with either bound wrong.
    // =============================================================
    tab_n = 0;
    for (g = 0; g < 9; g = g + 1) begin
      if (f_cond(ST[g/3], ST[g%3])) begin
        for (w = 0; w <= DEBOUNCE + 2; w = w + 1) begin
          reset_dut;
          set_cc(ST[g/3], ST[g%3]);
          ticks_n(w);
          set_cc(L_OPEN, L_OPEN);
          ticks_n(DEBOUNCE + 2);
          // ---- PROPERTY 4: the threshold is exactly DEBOUNCE ----
          ck((n_attach == 16'd1) == (w >= DEBOUNCE),
             "an attach was accepted at the wrong debounce width");
          // ---- PROPERTY 5: a rejected attach is COUNTED as a glitch ----
          //
          // Silently discarding a bouncing contact is the same behaviour
          // as never seeing it, and a bring-up engineer cannot tell a
          // dead cable from a bouncing one without this counter.
          if (w > 0 && w < DEBOUNCE)
            ck(n_glitch == 16'd1, "a rejected attach was not counted as a glitch");
          if (tab_n < 5) tab_acc[tab_n][w] = (n_attach == 16'd1) ? 1 : 0;
          tab_w[w] = w;
        end
        tab_n = tab_n + 1;
      end
    end

    // =============================================================
    //  PHASE 3 (DIRECTED, EXHAUSTIVE) -- THE SYMMETRY.
    //
    //  Replay all 81 ordered pairs with CC1 and CC2 exchanged, and
    //  require the two traces to agree sample by sample on `attached`,
    //  `cable_powered` and `debug_accessory`, and to DISAGREE on
    //  `orientation` whenever a single-Rd sink is attached.
    //
    //  Why orientation is exempt when debug_accessory is set: with Rd on
    //  BOTH lines there is no "other" line, so exchanging them changes
    //  nothing and orientation is identical rather than inverted. That is
    //  not a hole in the property -- it is the property, stated exactly.
    // =============================================================
    for (i = 0; i < 9; i = i + 1)
    for (j = 0; j < 9; j = j + 1) begin
      // ---- the plain run ----
      reset_dut;
      recording = 1'b1; tr_n = 0;
      set_cc(ST[i/3], ST[i%3]);   ticks_n(DEBOUNCE + 2);
      set_cc(ST[j/3], ST[j%3]);   ticks_n(DEBOUNCE + 2);
      recording = 1'b0;
      for (p = 0; p < tr_n; p = p + 1) tr_b[p] = tr_a[p];
      q = tr_n;

      // ---- the same sequence, plug turned over ----
      reset_dut;
      recording = 1'b1; tr_n = 0;
      set_cc(ST[i%3], ST[i/3]);   ticks_n(DEBOUNCE + 2);
      set_cc(ST[j%3], ST[j/3]);   ticks_n(DEBOUNCE + 2);
      recording = 1'b0;

      ck(tr_n == q, "the flipped run produced a different number of samples");
      for (p = 0; p < q && p < tr_n; p = p + 1) begin
        // ---- PROPERTY 6: attach is orientation-INDEPENDENT ----
        ck(tr_a[p][0] === tr_b[p][0],
           "the plug turned over changed WHETHER something was attached");
        // ---- PROPERTY 7: so are the cable and debug answers ----
        ck(tr_a[p][2] === tr_b[p][2],
           "the plug turned over changed the powered-cable answer");
        ck(tr_a[p][3] === tr_b[p][3],
           "the plug turned over changed the debug-accessory answer");
        // ---- PROPERTY 8: orientation INVERTS, for a single-Rd sink ----
        if (tr_b[p][0] && !tr_b[p][3])
          ck(tr_a[p][1] !== tr_b[p][1],
             "the plug turned over did NOT change the reported orientation");
        // ---- PROPERTY 9: and does NOT invert for a debug accessory ----
        if (tr_b[p][0] && tr_b[p][3])
          ck(tr_a[p][1] === tr_b[p][1],
             "a debug accessory reported a different orientation when flipped");
      end
    end

    // =============================================================
    //  PHASE 4 (DIRECTED) -- a detach that changes its mind.
    //
    //  A momentary break while the cable is wiggled must NOT tear down
    //  the attachment. `attached` has to stay high across the whole
    //  detach-debounce window, and the attach counter must not advance
    //  when the condition returns -- nothing was re-attached.
    // =============================================================
    reset_dut;
    set_cc(L_RD, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached === 1'b1, "a fully debounced attach did not take");
    for (w = 0; w < DEBOUNCE; w = w + 1) begin
      set_cc(L_OPEN, L_OPEN);
      ticks_n(w);
      ck(attached === 1'b1,
         "a momentary break tore down an established attachment");
      set_cc(L_RD, L_OPEN);
      ticks_n(2);
      ck(attached === 1'b1, "the attachment did not survive the break");
      ck(n_attach == 16'd1,
         "a break that came back was counted as a second attach");
      ck(n_detach == 16'd0, "a break that came back was counted as a detach");
    end
    // and now let it go for real
    set_cc(L_OPEN, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached === 1'b0, "a fully debounced detach did not take");
    ck(n_detach == 16'd1, "the real detach was not counted");

    // ---- PROPERTY 10: the SECOND attach gets the same window ----
    //
    // Every other phase resets the DUT before each attach, so every one
    // of them is the first. That makes an entire class of bug invisible:
    // a debounce counter that is not cleared when the wait state is
    // re-entered still gives the first attach a full window and gives
    // every one after it whatever was left over.
    //
    // Here the port has already attached and detached once, so the
    // counter is wherever the previous attachment left it. A fresh attach
    // held one tick short must still be rejected.
    // Swept across the whole window rather than tested at one point: the
    // stale-counter bug leaves an ARBITRARY residue behind, so which width
    // exposes it depends on what the previous attachment happened to do.
    // A single probe at DEBOUNCE-1 would find some residues and miss others.
    for (w = 0; w <= DEBOUNCE; w = w + 1) begin
      set_cc(L_RD, L_OPEN);
      ticks_n(w);
      ck((attached === 1'b1) == (w >= DEBOUNCE),
         "the second attach used a different debounce window from the first");
      set_cc(L_OPEN, L_OPEN);
      ticks_n(DEBOUNCE + 2);
      ck(attached === 1'b0, "the port did not return to unattached");
    end

    // =============================================================
    //  PHASE 5 (DIRECTED, EXHAUSTIVE) -- the situation changes DURING the
    //  window, and the window restarts.
    //
    //  This is the phase that separates "debounce the fact of attachment"
    //  from "debounce the CC state", and they are not the same thing. A
    //  sink moving from CC1 to CC2 part-way through the window -- a plug
    //  being rocked into place -- never drops `attach_cond`. A machine
    //  watching only `attach_cond` would accept that attachment with an
    //  orientation that had been stable for a single tick.
    //
    //  Every ordered pair of distinct attach situations, at three points
    //  in the window: the clock must restart from the change, so
    //  acceptance lands DEBOUNCE ticks after it and not DEBOUNCE minus
    //  however long the first one was held.
    // =============================================================
    for (p = 0; p < 5; p = p + 1)
    for (q = 0; q < 5; q = q + 1)
    if (p != q) begin
      for (g = 0; g < 3; g = g + 1) begin
        w = (g == 0) ? 1 : (g == 1) ? 3 : DEBOUNCE - 1;
        reset_dut;
        set_cc(ATT1[p], ATT2[p]);
        ticks_n(w);
        ck(attached === 1'b0, "the first situation was accepted too early");
        set_cc(ATT1[q], ATT2[q]);
        // ---- PROPERTY 11: the window restarts from the CHANGE ----
        ticks_n(DEBOUNCE - 1);
        ck(attached === 1'b0,
           "a mid-window change of CC state did not restart the debounce");
        do_tick;
        ck(attached === 1'b1,
           "the restarted window did not complete one tick later");
        // ---- PROPERTY 12: the answers are the SECOND situation's ----
        //
        // Not the first's. The whole reason to restart is that the
        // attachment being accepted is the one that has been stable.
        ck(orientation === (f_orient(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted orientation was the one that had already gone away");
        ck(cable_powered === (f_cable(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted cable answer was the stale one");
        ck(debug_accessory === (f_both_rd(ATT1[q], ATT2[q]) ? 1'b1 : 1'b0),
           "the accepted debug answer was the stale one");
        // ---- PROPERTY 13: no glitch was counted ----
        //
        // Nothing was abandoned. The attach condition held throughout; it
        // was only ever a different attachment.
        ck(n_glitch == 16'd0,
           "a change of situation was miscounted as an abandoned attach");
      end
    end

    // =============================================================
    //  PHASE 6 (RANDOM) -- a plug being pushed home.
    //
    //  Real insertion is not one clean edge. The contacts make and break
    //  several times over a few milliseconds, in whatever order the
    //  mechanics happen to produce, and both CC lines bounce
    //  independently.
    // =============================================================
`ifndef DIRECTED_ONLY
    reset_dut;
    for (w = 0; w < 400; w = w + 1) begin
      a_idx = urand() % 3;
      b_idx = urand() % 3;
      set_cc(ST[a_idx], ST[b_idx]);
      // 1..16 ticks, not 1..4. Against a ten-tick window a hold of at most
      // four can NEVER complete one, so the phase produced 400 events and
      // zero attaches -- it exercised the glitch path and nothing else.
      // Widening the hold past the window is what lets the random phase
      // reach the attached state, and therefore the detach path too.
      ticks_n(1 + (urand() % 16));
    end
`endif

    nr = 0; for (ri = 0; ri < 81; ri = ri + 1) if (reach[ri]) nr = nr + 1;

    $display("steps=%0d checks=%0d reach=%0d/81 errors=%0d",
             steps, checks, nr, errors);
    $display("[typec] attaches=%0d detaches=%0d glitches=%0d",
             n_attach, n_detach, n_glitch);
    $display("--- was the attach accepted, by how long it was held ---");
    $write("    ticks held   ");
    for (w = 0; w <= DEBOUNCE + 2; w = w + 1) $write("%3d", w);
    $write("\n");
    for (p = 0; p < 5; p = p + 1) begin
      $write("    combo %0d      ", p);
      for (w = 0; w <= DEBOUNCE + 2; w = w + 1) $write("%3d", tab_acc[p][w]);
      $write("\n");
    end
    if (nr != 81) begin
      $display("FAIL: exhaustive sweep incomplete"); errors = errors + 1;
    end
    if (errors == 0) $display("PASS: 0 errors in %0d checks", checks);
    else             $display("FAIL: %0d errors in %0d checks", errors, checks);
    $finish;
  end

endmodule

8. VHDL-2008

The third implementation, and the independent one: the two Icarus benches share their stimulus, so a defect they both have cannot be found by comparing them to each other.

VHDL's specific contribution here is the state type. An enumerated state in VHDL has no numeric encoding at all unless one is asked for, so there is no illegal value to fall in from and no default arm to fall into:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Verilog          reg [1:0] state;      four states, and 2'b11 is one of
                                           them whether you want it or not.
                                           The `default:` arm is load-bearing.

    SystemVerilog    typedef enum ...      better: the enum names the legal
                                           values, but the underlying vector
                                           can still hold others

    VHDL             type state_t is (...)  there is no fifth value to have.
                                           A `when others` arm would be
                                           unreachable code, and the compiler
                                           says so.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  typec_cc_fsm -- VHDL-2008.
--
--  Same hardware contract as the Verilog and SystemVerilog files: same
--  ports, same widths, same reset values, same cycle-by-cycle behaviour.
--
--  THIS IS THE INDEPENDENT IMPLEMENTATION. The two Icarus benches share
--  their stimulus, because Icarus seeds $random identically for Verilog
--  and SystemVerilog; the VHDL bench derives its own. A defect that both
--  Icarus benches share cannot be found by comparing them to each other.
--
--  VHDL's contribution here is the state type. An enumerated state in
--  VHDL has no numeric encoding at all unless one is asked for, so there
--  is no `default:` arm to fall into and no illegal value to fall in from
--  -- the synthesiser picks an encoding and the source cannot express a
--  fifth state. The Verilog file needs a `default` arm to be safe; this
--  one cannot need it.
--
--  typec_cc_fsm -- the Configuration Channel state machine that decides
--  whether anything is plugged in, which way round it is, and how much
--  current it is allowed to draw.
--
--  CLASSIFICATION: simplified synthesisable teaching RTL.
--  This is NOT a Type-C port controller. There is no VBUS switch, no
--  Power Delivery protocol engine, no BMC PHY, no VCONN switching and no
--  alternate-mode entry. It is the CC attach machine: the part that runs
--  before any of that exists, and that everything else depends on.
--
--  A source pulls both CC lines up through Rp; a sink pulls its one
--  connected line down through Rd; a powered cable identifies itself with
--  Ra on the other. Because the plug is reversible, WHICH line carries
--  the Rd is the orientation, and there is no other way to learn it.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity typec_cc_fsm is
  generic (
    -- The specification calls this tCCDebounce and requires 100..200 ms.
    -- A real port ticks at 1 ms and uses 100..200 here; this module uses
    -- 10 so a simulation can sweep the whole window exhaustively.
    DEBOUNCE_TICKS : integer := 10
  );
  port (
    clk             : in  std_logic;
    rst_n           : in  std_logic;

    -- One pulse per debounce tick. Everything time-related here is
    -- counted in ticks, never in clock cycles, so the same RTL works at
    -- any clock frequency.
    tick            : in  std_logic;

    --   "00" OPEN   "01" Ra   "10" Rd
    cc1_st          : in  std_logic_vector(1 downto 0);
    cc2_st          : in  std_logic_vector(1 downto 0);

    attached        : out std_logic;
    -- '0' = the sink is on CC1, '1' = on CC2. Meaningless unless
    -- `attached`, and HELD rather than cleared on detach so that nothing
    -- downstream is momentarily steered the wrong way.
    orientation     : out std_logic;
    -- A powered cable alongside the sink. VCONN would be supplied on the
    -- OTHER line -- the one carrying Ra.
    cable_powered   : out std_logic;
    -- Both lines pulled down by Rd. That is not a sink; it is a debug
    -- accessory, and driving VBUS into one is a real interop failure.
    debug_accessory : out std_logic;

    n_attach        : out unsigned(15 downto 0);
    n_detach        : out unsigned(15 downto 0);
    n_glitch        : out unsigned(15 downto 0)
  );
end entity;

architecture rtl of typec_cc_fsm is

  constant L_OPEN : std_logic_vector(1 downto 0) := "00";
  constant L_RA   : std_logic_vector(1 downto 0) := "01";
  constant L_RD   : std_logic_vector(1 downto 0) := "10";

  -- No numeric encoding, so no illegal state exists to be defaulted from.
  type state_t is (S_UNATTACHED, S_ATTACHWAIT, S_ATTACHED, S_DETACHWAIT);
  signal state : state_t;

  signal ticks   : unsigned(15 downto 0);
  -- What is currently being debounced. See sit_now below.
  signal sit_lat : std_logic_vector(2 downto 0);

  signal orient_r : std_logic;
  signal cable_r  : std_logic;
  signal debug_r  : std_logic;
  signal att_c    : unsigned(15 downto 0);
  signal det_c    : unsigned(15 downto 0);
  signal gli_c    : unsigned(15 downto 0);

  signal cc1_rd, cc2_rd, cc1_ra, cc2_ra : std_logic;
  signal one_rd, both_rd, attach_cond   : std_logic;
  signal orient_now, cable_now          : std_logic;
  signal sit_now                        : std_logic_vector(2 downto 0);

begin

  cc1_rd <= '1' when cc1_st = L_RD else '0';
  cc2_rd <= '1' when cc2_st = L_RD else '0';
  cc1_ra <= '1' when cc1_st = L_RA else '0';
  cc2_ra <= '1' when cc2_st = L_RA else '0';

  -- EXACTLY ONE line pulled down by Rd is a sink. The exclusive-or is the
  -- whole rule, and it is what makes the connector reversible: the machine
  -- does not care which line it is, only that precisely one of them is.
  one_rd  <= cc1_rd xor cc2_rd;
  -- BOTH lines pulled down by Rd is a debug accessory, not a sink.
  both_rd <= cc1_rd and cc2_rd;

  attach_cond <= one_rd or both_rd;

  orient_now <= cc2_rd;
  -- Ra on the line the sink is NOT on. With both_rd there is no free line,
  -- so there is no powered cable to find.
  cable_now  <= cc2_ra when (one_rd = '1' and cc1_rd = '1') else
                cc1_ra when (one_rd = '1') else
                '0';

  -- ---- the SITUATION, which is what actually gets debounced ----
  --
  -- The specification debounces the CC STATE, not the fact that something
  -- is attached. Those are different, and the difference is a real bug: if
  -- Rd moves from CC1 to CC2 part-way through the window -- which is what a
  -- plug being rocked into place does -- then attach_cond never drops, and
  -- a machine watching only attach_cond would accept the attachment with
  -- an orientation that had been stable for a single tick.
  sit_now <= both_rd & cable_now & orient_now;

  attached        <= '1' when (state = S_ATTACHED or state = S_DETACHWAIT) else '0';
  orientation     <= orient_r;
  cable_powered   <= cable_r;
  debug_accessory <= debug_r;
  n_attach        <= att_c;
  n_detach        <= det_c;
  n_glitch        <= gli_c;

  process (clk, rst_n)
  begin
    if rst_n = '0' then
      state    <= S_UNATTACHED;
      ticks    <= (others => '0');
      sit_lat  <= (others => '0');
      orient_r <= '0';
      cable_r  <= '0';
      debug_r  <= '0';
      att_c    <= (others => '0');
      det_c    <= (others => '0');
      gli_c    <= (others => '0');
    elsif rising_edge(clk) then
      case state is

        when S_UNATTACHED =>
          if attach_cond = '1' then
            state   <= S_ATTACHWAIT;
            ticks   <= (others => '0');
            sit_lat <= sit_now;
          end if;

        -- The condition has to SURVIVE tCCDebounce. This is not
        -- belt-and-braces: a plug being pushed home makes and breaks
        -- contact several times over a few milliseconds, and a port that
        -- believed the first edge would enumerate, drop, and enumerate
        -- again -- which is the "my phone keeps reconnecting" bug.
        when S_ATTACHWAIT =>
          if attach_cond = '0' then
            -- it went away before it was believed
            state <= S_UNATTACHED;
            gli_c <= gli_c + 1;
          elsif sit_now /= sit_lat then
            -- Still attached, but it is not the same attachment any more.
            -- The window restarts from here: the clock on "has this been
            -- stable" runs from the LAST change, not from the first.
            ticks   <= (others => '0');
            sit_lat <= sit_now;
          elsif tick = '1' then
            if ticks = to_unsigned(DEBOUNCE_TICKS - 1, 16) then
              state    <= S_ATTACHED;
              -- Taken from the DEBOUNCED situation, which by construction
              -- has been stable for the whole window. That makes the
              -- sampling instant irrelevant -- which is what a correct
              -- debounce buys, and without the restart above it would not
              -- hold.
              orient_r <= sit_lat(0);
              cable_r  <= sit_lat(1);
              debug_r  <= sit_lat(2);
              att_c    <= att_c + 1;
            else
              ticks <= ticks + 1;
            end if;
          end if;

        when S_ATTACHED =>
          if attach_cond = '0' then
            state <= S_DETACHWAIT;
            ticks <= (others => '0');
          end if;

        -- A detach is debounced too, and for the same reason in reverse: a
        -- momentary break while the cable is wiggled must not tear down a
        -- power contract and a display link. `attached` stays HIGH
        -- throughout this state -- a port that dropped it on the first
        -- missing sample would produce exactly the disconnect it is trying
        -- to avoid.
        when S_DETACHWAIT =>
          if attach_cond = '1' then
            state <= S_ATTACHED;      -- it came back: nothing happened
          elsif tick = '1' then
            if ticks = to_unsigned(DEBOUNCE_TICKS - 1, 16) then
              state <= S_UNATTACHED;
              det_c <= det_c + 1;
            else
              ticks <= ticks + 1;
            end if;
          end if;

      end case;
    end if;
  end process;

end architecture;

The VHDL testbench

The directed phases are structurally identical to the other two benches, so the directed mutation columns must agree exactly. The random phase uses a VHDL-native generator.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- =====================================================================
--  Testbench for typec_cc_fsm -- VHDL-2008.
--
--  THE HEADLINE PROPERTY IS A SYMMETRY, AND IT IS THE REASON THE
--  CONNECTOR CAN BE REVERSIBLE.
--
--  Every other chapter in this module asserts that the design computes
--  the right VALUE. This one asserts that it computes the SAME value
--  under a transformation of its inputs: exchange CC1 and CC2 -- which is
--  physically what happens when the user turns the plug over -- and every
--  output must be identical except `orientation`, which must invert.
--
--  That is checkable EXHAUSTIVELY over single transitions: 9 CC states x
--  9 CC states = 81 ordered pairs, each replayed flipped and compared
--  sample by sample. A design that special-cased CC1 -- the natural way to
--  write it, and wrong -- fails the symmetry without failing any
--  single-orientation test.
--
--  THIS IS THE INDEPENDENT BENCH. The directed phases are structurally
--  identical to the Verilog and SystemVerilog benches, so the DIRECTED
--  mutation columns must agree EXACTLY. The random phase uses a
--  VHDL-native generator, and takes its value from bits 30 downto 15 of
--  the state rather than the low bits: an LCG's low bits are phase-locked,
--  and reading them once in this track produced a perfectly uniform
--  histogram alongside zero of the events the phase existed to create.
--
--  THE MODEL IS A RUN-LENGTH THRESHOLD, NOT A STATE MACHINE. The design is
--  four states and a counter; the model counts how many consecutive ticks
--  the decoded situation has held and compares against DEBOUNCE. Same
--  behaviour, different formulation.
-- =====================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;

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

architecture sim of tb_tc_vhdl is

  constant DEBOUNCE : integer := 10;

  constant L_OPEN : std_logic_vector(1 downto 0) := "00";
  constant L_RA   : std_logic_vector(1 downto 0) := "01";
  constant L_RD   : std_logic_vector(1 downto 0) := "10";

  signal clk   : std_logic := '0';
  signal rst_n : std_logic := '0';
  signal done  : boolean   := false;

  signal tick : std_logic := '0';
  signal cc1  : std_logic_vector(1 downto 0) := L_OPEN;
  signal cc2  : std_logic_vector(1 downto 0) := L_OPEN;

  signal attached        : std_logic;
  signal orientation     : std_logic;
  signal cable_powered   : std_logic;
  signal debug_accessory : std_logic;
  signal n_attach        : unsigned(15 downto 0);
  signal n_detach        : unsigned(15 downto 0);
  signal n_glitch        : unsigned(15 downto 0);

begin

  dut : entity work.typec_cc_fsm
    generic map (DEBOUNCE_TICKS => DEBOUNCE)
    port map (
      clk => clk, rst_n => rst_n, tick => tick,
      cc1_st => cc1, cc2_st => cc2,
      attached => attached, orientation => orientation,
      cable_powered => cable_powered, debug_accessory => debug_accessory,
      n_attach => n_attach, n_detach => n_detach, n_glitch => n_glitch
    );

  clkgen : process
  begin
    while not done loop
      clk <= '0'; wait for 5 ns;
      clk <= '1'; wait for 5 ns;
    end loop;
    wait;
  end process;

  main : process

    variable errors : integer := 0;
    variable checks : integer := 0;
    variable steps  : integer := 0;
    variable lo     : line;

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

    -- ---- the model, formulated as a run-length threshold ----
    variable m_run      : integer := 0;
    variable m_attached : integer := 0;
    variable m_orient   : integer := 0;
    variable m_cable    : integer := 0;
    variable m_debug    : integer := 0;
    variable m_att_c    : integer := 0;
    variable m_det_c    : integer := 0;
    variable m_gli_c    : integer := 0;

    function f_one_rd(a : std_logic_vector(1 downto 0);
                      b : std_logic_vector(1 downto 0)) return integer is
    begin
      if (a = L_RD) xor (b = L_RD) then return 1; else return 0; end if;
    end function;

    function f_both_rd(a : std_logic_vector(1 downto 0);
                       b : std_logic_vector(1 downto 0)) return integer is
    begin
      if a = L_RD and b = L_RD then return 1; else return 0; end if;
    end function;

    function f_cond(a : std_logic_vector(1 downto 0);
                    b : std_logic_vector(1 downto 0)) return integer is
    begin
      if f_one_rd(a,b) = 1 or f_both_rd(a,b) = 1 then return 1; else return 0; end if;
    end function;

    function f_orient(a : std_logic_vector(1 downto 0);
                      b : std_logic_vector(1 downto 0)) return integer is
    begin
      if b = L_RD then return 1; else return 0; end if;
    end function;

    function f_cable(a : std_logic_vector(1 downto 0);
                     b : std_logic_vector(1 downto 0)) return integer is
    begin
      if f_one_rd(a,b) = 0 then
        return 0;
      elsif a = L_RD then
        if b = L_RA then return 1; else return 0; end if;
      else
        if a = L_RA then return 1; else return 0; end if;
      end if;
    end function;

    -- The decoded SITUATION -- the three answers bundled, which is what the
    -- debounce window actually applies to.
    function f_sit(a : std_logic_vector(1 downto 0);
                   b : std_logic_vector(1 downto 0)) return integer is
    begin
      return f_both_rd(a,b) * 4 + f_cable(a,b) * 2 + f_orient(a,b);
    end function;

    procedure reset_dut is
    begin
      rst_n <= '0'; tick <= '0'; cc1 <= L_OPEN; cc2 <= L_OPEN;
      wait until rising_edge(clk);
      wait until rising_edge(clk);
      rst_n <= '1';
      wait until rising_edge(clk);
      wait for 1 ns;
      m_run := 0; m_attached := 0; m_orient := 0; m_cable := 0; m_debug := 0;
      m_att_c := 0; m_det_c := 0; m_gli_c := 0;
    end procedure;

    -- ---- change CC strictly BETWEEN ticks ----
    --
    -- CC is asynchronous to the debounce tick in real hardware, and letting
    -- a change land on the same cycle as a tick makes "how many ticks has
    -- this held for" ambiguous by one -- in the bench's bookkeeping, not in
    -- the design. Keeping them apart removes the ambiguity rather than
    -- papering over it.
    procedure set_cc(a : std_logic_vector(1 downto 0);
                     b : std_logic_vector(1 downto 0)) is
      variable was_cond, now_cond, was_sit, now_sit : integer;
    begin
      was_cond := f_cond(cc1, cc2);
      now_cond := f_cond(a, b);
      was_sit  := f_sit(cc1, cc2);
      now_sit  := f_sit(a, b);

      -- A glitch is an attach condition asserted and then WITHDRAWN before
      -- it had held long enough to be believed. There is no "and it lasted
      -- at least one tick" clause: the design enters its debounce state on
      -- the clock edge after the condition appears, so a condition that
      -- comes and goes between two ticks is still an abandoned attach.
      if m_attached = 0 and was_cond = 1 and now_cond = 0 then
        m_gli_c := m_gli_c + 1;
      end if;

      -- While UNATTACHED the window applies to the whole decoded situation:
      -- a sink moving from one CC line to the other leaves attach_cond true
      -- and still restarts the clock. While ATTACHED only the fact of
      -- attachment matters.
      if m_attached = 1 then
        if was_cond /= now_cond then m_run := 0; end if;
      else
        if was_cond /= now_cond or was_sit /= now_sit then m_run := 0; end if;
      end if;

      cc1 <= a; cc2 <= b;
      wait until rising_edge(clk);
      wait for 1 ns;
      steps := steps + 1;
    end procedure;

    -- ---- the trace, for the symmetry comparison ----
    type trace_t is array (0 to 511) of std_logic_vector(3 downto 0);
    variable tr_a : trace_t := (others => "0000");
    variable tr_b : trace_t := (others => "0000");
    variable tr_n : integer := 0;
    variable recording : boolean := false;

    procedure rec is
    begin
      if recording and tr_n < 512 then
        tr_a(tr_n) := debug_accessory & cable_powered & orientation & attached;
        tr_n := tr_n + 1;
      end if;
    end procedure;

    procedure do_tick is
      variable cond : integer;
    begin
      cond := f_cond(cc1, cc2);
      tick <= '1';
      wait until rising_edge(clk);
      wait for 1 ns;
      tick <= '0';

      if cond /= m_attached then
        m_run := m_run + 1;
        if m_run = DEBOUNCE then
          if cond = 1 then
            m_attached := 1;
            m_orient   := f_orient(cc1, cc2);
            m_cable    := f_cable(cc1, cc2);
            m_debug    := f_both_rd(cc1, cc2);
            m_att_c    := m_att_c + 1;
          else
            m_attached := 0;
            m_det_c    := m_det_c + 1;
          end if;
          m_run := 0;
        end if;
      else
        m_run := 0;
      end if;

      -- ---- PROPERTY 1: attach follows the model exactly ----
      if m_attached = 1 then
        ck(attached = '1', "attached disagrees with the run-length model");
      else
        ck(attached = '0', "attached disagrees with the run-length model");
      end if;
      -- ---- PROPERTY 2: the three latched answers are right ----
      --
      -- Checked only while attached: outside an attachment the spec gives
      -- them no meaning, and asserting a value the design is free to hold
      -- would be asserting an implementation detail rather than a contract.
      if m_attached = 1 then
        if m_orient = 1 then
          ck(orientation = '1', "orientation disagrees with which line carried Rd");
        else
          ck(orientation = '0', "orientation disagrees with which line carried Rd");
        end if;
        if m_cable = 1 then
          ck(cable_powered = '1', "cable_powered disagrees with Ra on the free line");
        else
          ck(cable_powered = '0', "cable_powered disagrees with Ra on the free line");
        end if;
        if m_debug = 1 then
          ck(debug_accessory = '1', "debug_accessory disagrees with both lines at Rd");
        else
          ck(debug_accessory = '0', "debug_accessory disagrees with both lines at Rd");
        end if;
      end if;
      -- ---- PROPERTY 3: the counters agree with the model's tally ----
      ck(to_integer(n_attach) = m_att_c, "n_attach disagrees with the model");
      ck(to_integer(n_detach) = m_det_c, "n_detach disagrees with the model");
      ck(to_integer(n_glitch) = m_gli_c, "n_glitch disagrees with the model");
      -- Sample the trace once per tick. Without this call tr_n stays at
      -- zero, the comparison loop in phase 3 has an empty range, and the
      -- symmetry property -- the whole point of the chapter -- is never
      -- evaluated while every one of its checks reports success.
      rec;
      steps := steps + 1;
    end procedure;

    procedure ticks_n(n : integer) is
    begin
      for i in 1 to n loop
        do_tick;
      end loop;
    end procedure;

    -- ---- exhaustive reach over ordered pairs of CC states ----
    type reach_t is array (0 to 80) of boolean;
    variable reach : reach_t := (others => false);
    variable nr    : integer := 0;

    type st_t is array (0 to 2) of std_logic_vector(1 downto 0);
    constant ST : st_t := (L_OPEN, L_RA, L_RD);

    -- The five CC combinations that constitute an attach, and they have
    -- five DISTINCT decoded situations.
    type att_t is array (0 to 4) of std_logic_vector(1 downto 0);
    constant ATT1 : att_t := (L_OPEN, L_RA,  L_RD,   L_RD,  L_RD);
    constant ATT2 : att_t := (L_RD,   L_RD,  L_OPEN, L_RA,  L_RD);

    variable w, q : integer;
    type accrow_t is array (0 to 4, 0 to 12) of integer;
    variable acc : accrow_t := (others => (others => 0));
    variable tab_n : integer := 0;

    variable rnd_state : unsigned(31 downto 0) := x"00009117";
    impure function urand return integer is
    begin
      -- resize is not optional: numeric_std's "*" on two 32-bit unsigneds
      -- returns SIXTY-FOUR bits, and assigning that back is a fatal length
      -- mismatch rather than the silent truncation Verilog would give.
      rnd_state := resize(rnd_state * to_unsigned(1103515245, 32), 32)
                 + to_unsigned(12345, 32);
      return to_integer(rnd_state(30 downto 15));
    end function;

  begin

    reset_dut;

    -- =============================================================
    --  PHASE 1 (DIRECTED, EXHAUSTIVE) -- every ordered pair of CC
    --  states, held long enough to be believed.
    -- =============================================================
    for i in 0 to 8 loop
      for j in 0 to 8 loop
        reset_dut;
        set_cc(ST(i/3), ST(i mod 3));
        ticks_n(DEBOUNCE + 2);
        set_cc(ST(j/3), ST(j mod 3));
        ticks_n(DEBOUNCE + 2);
        reach(i*9 + j) := true;
      end loop;
    end loop;

    -- =============================================================
    --  PHASE 2 (DIRECTED, EXHAUSTIVE) -- the debounce threshold.
    --
    --  For each CC combination that constitutes an attach, present it for
    --  every width from 0 to DEBOUNCE+2 ticks and then remove it. The
    --  design must accept it if and only if the width reached DEBOUNCE.
    --  Swept exhaustively because the interesting values are adjacent:
    --  DEBOUNCE-1 must be rejected and DEBOUNCE accepted, and a suite that
    --  tested 0 and 100 would pass with either bound wrong.
    -- =============================================================
    tab_n := 0;
    for g in 0 to 8 loop
      if f_cond(ST(g/3), ST(g mod 3)) = 1 then
        for wi in 0 to DEBOUNCE + 2 loop
          reset_dut;
          set_cc(ST(g/3), ST(g mod 3));
          ticks_n(wi);
          set_cc(L_OPEN, L_OPEN);
          ticks_n(DEBOUNCE + 2);
          -- ---- PROPERTY 4: the threshold is exactly DEBOUNCE ----
          if wi >= DEBOUNCE then
            ck(to_integer(n_attach) = 1,
               "an attach was accepted at the wrong debounce width");
          else
            ck(to_integer(n_attach) = 0,
               "an attach was accepted at the wrong debounce width");
          end if;
          -- ---- PROPERTY 5: a rejected attach is COUNTED as a glitch ----
          --
          -- Silently discarding a bouncing contact is the same behaviour
          -- as never seeing it, and a bring-up engineer cannot tell a dead
          -- cable from a bouncing one without this counter.
          if wi > 0 and wi < DEBOUNCE then
            ck(to_integer(n_glitch) = 1,
               "a rejected attach was not counted as a glitch");
          end if;
          if tab_n < 5 then
            if to_integer(n_attach) = 1 then
              acc(tab_n, wi) := 1;
            else
              acc(tab_n, wi) := 0;
            end if;
          end if;
        end loop;
        tab_n := tab_n + 1;
      end if;
    end loop;

    -- =============================================================
    --  PHASE 3 (DIRECTED, EXHAUSTIVE) -- THE SYMMETRY.
    --
    --  Replay all 81 ordered pairs with CC1 and CC2 exchanged, and require
    --  the two traces to agree sample by sample on `attached`,
    --  `cable_powered` and `debug_accessory`, and to DISAGREE on
    --  `orientation` whenever a single-Rd sink is attached.
    --
    --  Why orientation is exempt when debug_accessory is set: with Rd on
    --  BOTH lines there is no "other" line, so exchanging them changes
    --  nothing and orientation is identical rather than inverted. That is
    --  not a hole in the property -- it is the property, stated exactly.
    -- =============================================================
    for i in 0 to 8 loop
      for j in 0 to 8 loop
        reset_dut;
        recording := true; tr_n := 0;
        set_cc(ST(i/3), ST(i mod 3));  ticks_n(DEBOUNCE + 2);
        set_cc(ST(j/3), ST(j mod 3));  ticks_n(DEBOUNCE + 2);
        recording := false;
        for p in 0 to tr_n - 1 loop
          tr_b(p) := tr_a(p);
        end loop;
        q := tr_n;

        reset_dut;
        recording := true; tr_n := 0;
        set_cc(ST(i mod 3), ST(i/3));  ticks_n(DEBOUNCE + 2);
        set_cc(ST(j mod 3), ST(j/3));  ticks_n(DEBOUNCE + 2);
        recording := false;

        ck(tr_n = q, "the flipped run produced a different number of samples");
        for p in 0 to q - 1 loop
          if p < tr_n then
            -- ---- PROPERTY 6: attach is orientation-INDEPENDENT ----
            ck(tr_a(p)(0) = tr_b(p)(0),
               "the plug turned over changed WHETHER something was attached");
            -- ---- PROPERTY 7: so are the cable and debug answers ----
            ck(tr_a(p)(2) = tr_b(p)(2),
               "the plug turned over changed the powered-cable answer");
            ck(tr_a(p)(3) = tr_b(p)(3),
               "the plug turned over changed the debug-accessory answer");
            -- ---- PROPERTY 8: orientation INVERTS, for a single-Rd sink --
            if tr_b(p)(0) = '1' and tr_b(p)(3) = '0' then
              ck(tr_a(p)(1) /= tr_b(p)(1),
                 "the plug turned over did NOT change the reported orientation");
            end if;
            -- ---- PROPERTY 9: and does NOT invert for a debug accessory --
            if tr_b(p)(0) = '1' and tr_b(p)(3) = '1' then
              ck(tr_a(p)(1) = tr_b(p)(1),
                 "a debug accessory reported a different orientation flipped");
            end if;
          end if;
        end loop;
      end loop;
    end loop;

    -- =============================================================
    --  PHASE 4 (DIRECTED) -- a detach that changes its mind.
    --
    --  A momentary break while the cable is wiggled must NOT tear down the
    --  attachment. `attached` has to stay high across the whole
    --  detach-debounce window, and the attach counter must not advance
    --  when the condition returns -- nothing was re-attached.
    -- =============================================================
    reset_dut;
    set_cc(L_RD, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached = '1', "a fully debounced attach did not take");
    for wi in 0 to DEBOUNCE - 1 loop
      set_cc(L_OPEN, L_OPEN);
      ticks_n(wi);
      ck(attached = '1', "a momentary break tore down an established attachment");
      set_cc(L_RD, L_OPEN);
      ticks_n(2);
      ck(attached = '1', "the attachment did not survive the break");
      ck(to_integer(n_attach) = 1,
         "a break that came back was counted as a second attach");
      ck(to_integer(n_detach) = 0,
         "a break that came back was counted as a detach");
    end loop;
    -- and now let it go for real
    set_cc(L_OPEN, L_OPEN);
    ticks_n(DEBOUNCE + 2);
    ck(attached = '0', "a fully debounced detach did not take");
    ck(to_integer(n_detach) = 1, "the real detach was not counted");

    -- ---- PROPERTY 10: the SECOND attach gets the same window ----
    --
    -- Every other phase resets the DUT before each attach, so every one of
    -- them is the first. That makes an entire class of bug invisible: a
    -- debounce counter that is not cleared when the wait state is
    -- re-entered still gives the first attach a full window and gives
    -- every one after it whatever was left over.
    --
    -- Swept across the whole window rather than tested at one point: the
    -- stale-counter bug leaves an ARBITRARY residue behind, so which width
    -- exposes it depends on what the previous attachment happened to do.
    for wi in 0 to DEBOUNCE loop
      set_cc(L_RD, L_OPEN);
      ticks_n(wi);
      if wi >= DEBOUNCE then
        ck(attached = '1',
           "the second attach used a different debounce window from the first");
      else
        ck(attached = '0',
           "the second attach used a different debounce window from the first");
      end if;
      set_cc(L_OPEN, L_OPEN);
      ticks_n(DEBOUNCE + 2);
      ck(attached = '0', "the port did not return to unattached");
    end loop;

    -- =============================================================
    --  PHASE 5 (DIRECTED, EXHAUSTIVE) -- the situation changes DURING
    --  the window, and the window restarts.
    --
    --  This is the phase that separates "debounce the fact of attachment"
    --  from "debounce the CC state", and they are not the same thing. A
    --  sink moving from CC1 to CC2 part-way through the window -- a plug
    --  being rocked into place -- never drops `attach_cond`. A machine
    --  watching only `attach_cond` would accept that attachment with an
    --  orientation that had been stable for a single tick.
    -- =============================================================
    for p in 0 to 4 loop
      for q2 in 0 to 4 loop
        if p /= q2 then
          for g in 0 to 2 loop
            if    g = 0 then w := 1;
            elsif g = 1 then w := 3;
            else                 w := DEBOUNCE - 1;
            end if;
            reset_dut;
            set_cc(ATT1(p), ATT2(p));
            ticks_n(w);
            ck(attached = '0', "the first situation was accepted too early");
            set_cc(ATT1(q2), ATT2(q2));
            -- ---- PROPERTY 11: the window restarts from the CHANGE ----
            ticks_n(DEBOUNCE - 1);
            ck(attached = '0',
               "a mid-window change of CC state did not restart the debounce");
            do_tick;
            ck(attached = '1',
               "the restarted window did not complete one tick later");
            -- ---- PROPERTY 12: the answers are the SECOND situation's ---
            if f_orient(ATT1(q2), ATT2(q2)) = 1 then
              ck(orientation = '1',
                 "the accepted orientation was the one that had already gone");
            else
              ck(orientation = '0',
                 "the accepted orientation was the one that had already gone");
            end if;
            if f_cable(ATT1(q2), ATT2(q2)) = 1 then
              ck(cable_powered = '1', "the accepted cable answer was stale");
            else
              ck(cable_powered = '0', "the accepted cable answer was stale");
            end if;
            if f_both_rd(ATT1(q2), ATT2(q2)) = 1 then
              ck(debug_accessory = '1', "the accepted debug answer was stale");
            else
              ck(debug_accessory = '0', "the accepted debug answer was stale");
            end if;
            -- ---- PROPERTY 13: no glitch was counted ----
            --
            -- Nothing was abandoned. The attach condition held throughout;
            -- it was only ever a different attachment.
            ck(to_integer(n_glitch) = 0,
               "a change of situation was miscounted as an abandoned attach");
          end loop;
        end if;
      end loop;
    end loop;

    -- =============================================================
    --  PHASE 6 (RANDOM) -- a plug being pushed home.
    --
    --  Real insertion is not one clean edge. The contacts make and break
    --  several times over a few milliseconds, in whatever order the
    --  mechanics happen to produce, and both CC lines bounce
    --  independently.
    -- =============================================================
    if not DIRECTED_ONLY then
      reset_dut;
      for k in 0 to 399 loop
        set_cc(ST(urand mod 3), ST(urand mod 3));
        -- 1..16 ticks, not 1..4. Against a ten-tick window a hold of at
        -- most four can NEVER complete one, so the phase produced 400
        -- events and zero attaches -- it exercised the glitch path and
        -- nothing else.
        ticks_n(1 + (urand mod 16));
      end loop;
    end if;

    nr := 0;
    for rj in 0 to 80 loop
      if reach(rj) then nr := nr + 1; end if;
    end loop;

    write(lo, string'("steps=")); write(lo, steps);
    write(lo, string'(" checks=")); write(lo, checks);
    write(lo, string'(" reach=")); write(lo, nr); write(lo, string'("/81"));
    write(lo, string'(" errors=")); write(lo, errors);
    writeline(output, lo);
    write(lo, string'("[typec] attaches=")); write(lo, to_integer(n_attach));
    write(lo, string'(" detaches=")); write(lo, to_integer(n_detach));
    write(lo, string'(" glitches=")); write(lo, to_integer(n_glitch));
    writeline(output, lo);
    write(lo, string'("--- was the attach accepted, by how long it was held ---"));
    writeline(output, lo);
    write(lo, string'("    ticks held   "));
    for wi in 0 to DEBOUNCE + 2 loop
      write(lo, wi, right, 3);
    end loop;
    writeline(output, lo);
    for p in 0 to 4 loop
      write(lo, string'("    combo "));
      write(lo, p);
      write(lo, string'("      "));
      for wi in 0 to DEBOUNCE + 2 loop
        write(lo, acc(p, wi), right, 3);
      end loop;
      writeline(output, lo);
    end loop;

    if nr /= 81 then
      write(lo, string'("FAIL: exhaustive sweep incomplete"));
      writeline(output, lo);
      errors := errors + 1;
    end if;
    if errors = 0 then
      write(lo, string'("PASS: 0 errors in ")); write(lo, checks);
      write(lo, string'(" checks"));
    else
      write(lo, string'("FAIL: ")); write(lo, errors);
      write(lo, string'(" errors in ")); write(lo, checks);
      write(lo, string'(" checks"));
    end if;
    writeline(output, lo);

    done <= true;
    wait;
  end process;

end architecture;

9. Assertions

Four properties here are naturally temporal, written as SVA for a tool that supports it. Icarus does not — it rejects concurrent assertions outright — so each is enforced by the procedural check named beside it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---- P1: attached never rises without a full window beneath it ----
//
// The property the whole module exists to provide. Stated as: attached
// cannot rise unless the tick before it was inside a debounce window that
// had already run its course.
property p_no_attach_without_window;
  @(posedge clk) disable iff (!rst_n)
    $rose(attached) |-> $past(state) == S_ATTACHWAIT &&
                        $past(ticks) == DEBOUNCE_TICKS - 1;
endproperty
// enforced procedurally by: PROPERTY 4, which sweeps every hold width from
// 0 to DEBOUNCE+2 for all five attach combinations and requires acceptance
// if and only if the width reached DEBOUNCE

// ---- P2: attached survives the whole detach window ----
//
// A momentary break must not tear down a power contract and a display
// link. This is the property a naive implementation gets wrong by
// deasserting on the first missing sample.
property p_attached_survives_a_break;
  @(posedge clk) disable iff (!rst_n)
    (attached && !attach_cond) |=> attached until_with
      (state == S_UNATTACHED);
endproperty
// enforced procedurally by: PHASE 4, which breaks the contact for every
// width from 0 to DEBOUNCE-1 and requires attached to stay high throughout

// ---- P3: the latched answers never change while attached ----
//
// The three answers are decided once, at acceptance. A design that
// re-evaluated them continuously would track a CC glitch straight through
// to whatever is steering the high-speed mux.
property p_answers_are_stable;
  @(posedge clk) disable iff (!rst_n)
    attached |=> $stable(orientation) && $stable(cable_powered) &&
                 $stable(debug_accessory);
endproperty
// enforced procedurally by: PROPERTY 2, checked on every tick of every
// phase while attached, against a model that latches once

// ---- P4: the counters are monotonic ----
property p_counters_monotonic;
  @(posedge clk) disable iff (!rst_n)
    (n_attach >= $past(n_attach)) && (n_detach >= $past(n_detach)) &&
    (n_glitch >= $past(n_glitch));
endproperty
// enforced procedurally by: PROPERTY 3, comparing all three against a
// model tally that is monotonic by construction, once per tick

10. Where UVM Fits

For a port controller the trade genuinely flips, and for a specific reason: the attach machine is one agent in an environment that has to reach states no directed sequence would think to construct.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// =====================================================================
//  The item is a CC EVENT -- a new pair of line levels, and how long to
//  hold it. Not a transaction, because at this layer there are no
//  transactions yet: that is the point of the layer.
// =====================================================================
class cc_event_item extends uvm_sequence_item;
  `uvm_object_utils(cc_event_item)

  rand cc_level_t cc1;
  rand cc_level_t cc2;
  rand int unsigned hold_ticks;

  // ---- the constraint that makes this worth randomising ----
  //
  // Weighted toward the debounce boundary, because that is where every
  // interesting behaviour is and uniform random would spend almost all of
  // its time far away from it on both sides.
  constraint c_hold {
    hold_ticks dist {
      0                            :/ 5,
      [1 : DEBOUNCE-2]             :/ 20,
      DEBOUNCE-1                   :/ 20,
      DEBOUNCE                     :/ 20,
      [DEBOUNCE+1 : 3*DEBOUNCE]    :/ 20
    };
  }
  // 2'b11 is not a value of cc_level_t, so there is nothing to exclude.
endclass

// =====================================================================
//  The scoreboard holds the run-length model -- deliberately NOT a state
//  machine, so that a wrong state encoding in the DUT cannot be
//  reproduced by the thing checking it.
// =====================================================================
class cc_scoreboard extends uvm_scoreboard;
  `uvm_object_utils(cc_scoreboard)
  uvm_analysis_imp #(cc_event_item, cc_scoreboard) ap;

  int  m_run;
  bit  m_attached;
  bit  m_orient, m_cable, m_debug;

  function bit f_cond(cc_level_t a, cc_level_t b);
    return ((a == CC_RD) ^ (b == CC_RD)) || ((a == CC_RD) && (b == CC_RD));
  endfunction
  function bit [2:0] f_sit(cc_level_t a, cc_level_t b);
    bit one_rd = (a == CC_RD) ^ (b == CC_RD);
    return { (a == CC_RD) && (b == CC_RD),                         // debug
             one_rd ? ((a == CC_RD) ? (b == CC_RA) : (a == CC_RA)) : 1'b0,
             (b == CC_RD) };                                       // orient
  endfunction
  ...
endclass

// =====================================================================
//  THE COVERAGE MODEL IS WHERE THIS DUT ACTUALLY NEEDS UVM.
// =====================================================================
covergroup cg_cc @(posedge tick);
  cp_cc1 : coverpoint cc1 { bins open = {CC_OPEN}; bins ra = {CC_RA};
                            bins rd  = {CC_RD}; }
  cp_cc2 : coverpoint cc2 { bins open = {CC_OPEN}; bins ra = {CC_RA};
                            bins rd  = {CC_RD}; }

  // ---- the hold width, binned AROUND the boundary ----
  //
  // `just_short` and `exactly` are separate bins holding one value each.
  // A range bin spanning both would report full coverage while never
  // having distinguished them, which is the only distinction that matters.
  cp_hold : coverpoint hold_ticks {
    bins zero       = {0};
    bins short_hold = {[1 : DEBOUNCE-2]};
    bins just_short = {DEBOUNCE-1};
    bins exactly    = {DEBOUNCE};
    bins longer     = {[DEBOUNCE+1 : $]};
  }

  // ---- the transition coverage that phase 5 exists for ----
  //
  // A change from one attach situation to a DIFFERENT one, mid-window.
  // Neither endpoint is unusual; the transition between them is the event,
  // and a coverpoint on states rather than transitions cannot see it.
  cp_sit_change : coverpoint sit_now {
    bins mid_window_change[] = ([0:5] => [0:5]);
  }

  x_orientation : cross cp_cc1, cp_cc2;
endcovergroup

11. Mutation Testing

Nine mutations, each a plausible single mistake, each generated by a script that asserts its replacement applied.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    MUT       V-ALL     V-DIR     S-ALL     S-DIR     H-ALL     H-DIR
    BASE          0         0         0         0         0         0
    S1         5896      2042      5896      2042     12066      2042
    S2         8865      1352      8865      1352     10937      1352
    S3        12317      1456     12317      1456     12257      1456
    S4         1325       748      1325       748      1320       748
    S5        11537       658     11537       658     12911       658
    S6         8402       772      8402       772     10827       772
    S7          959       597       959       597       944       597
    S8         3855       832      3855       832      4285       832
    S9         8712      1040      8712      1040      1734      1040
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    S1  orientation hard-wired to CC1 -- the natural way to write it, right
        half the time, and the half it is wrong for is whichever way the
        engineer did not hold the plug during bring-up
    S2  the debounce window is one tick short
    S3  a detach is not debounced at all
    S4  `attached` drops while the detach is still debouncing
    S5  the debounce counter is not cleared on entry to the wait state
    S6  the window watches only WHETHER something is attached, not WHAT
    S7  a debug accessory is reported as an ordinary sink
    S8  an abandoned attach is not counted
    S9  the powered-cable check reads the line the SINK is on

Every DIRECTED column is identical across all three languages and BASE reads zero in all six. Every full column exceeds its directed column, and the two Icarus columns differ from the VHDL one throughout — the random phase driving different stimulus, which is expected and is the reason the decomposition exists.

The size of that gap is worth a glance. S9 is caught 8,712 times by the Verilog random phase and 1,734 times by the VHDL one: a factor of five, from the same design and the same property, purely because two generators happened to produce different numbers of powered-cable attachments. An ALL column is a fact about the stimulus at least as much as about the design, which is exactly why the directed columns are the ones required to match.

12. What This Does Not Cover

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    NOT MODELLED                      WHY IT IS OUT OF SCOPE
    -------------------------------   ------------------------------------
    the VBUS switch and its           a power path, not a decision
    inrush limiting
    the Rp level the port ADVERTISES  the same two pins read the other way;
    (500 mA / 1.5 A / 3.0 A)          the sink's side of this chapter
    Power Delivery: BMC encoding,     an entire protocol stack that runs
    message IDs, GoodCRC, hard reset  AFTER this machine has finished
    alternate-mode entry and the      negotiated in PD structured VDMs
    DisplayPort pin remap
    MTP / PTP / ADB themselves        class protocols over an ordinary bulk
                                      pipe; 29.1 builds that shape
    dual-role and role SWAP           the machine here is source-side only
    the analog comparators and        RTL simulation has no voltages. The
    their thresholds                  three levels are an INPUT here, and
                                      whether the comparator that produces
                                      them is right is not a question this
                                      testbench can ask

13. The Interview Answer

"USB-C is reversible. What does that actually cost the port controller, and how would you verify it?"

It costs one bit of state and a symmetry obligation on everything downstream.

The connector is reversible because the sink's Rd resistor lands on CC1 or CC2 depending on insertion, and the port learns the orientation by seeing which of its two CC lines is pulled down. Exactly one, not at least one — both pulled down is a debug accessory, and driving VBUS into one is a real interop failure. That single bit then steers the high-speed mux, so everything downstream inherits it.

The verification answer is the interesting half. The obvious approach is to test both orientations and check the answers, and that is weaker than it looks, because the natural buggy implementation — look at CC1, fall back to CC2 — gives a correct answer in one orientation. What you want instead is the symmetry: run any input sequence, run it again with the two CC lines exchanged, and require every output to be identical except the orientation bit, which must invert.

That is exhaustively checkable, because the transformation is finite and total: there are two orientations and every input has a mirror. Here it is 81 ordered pairs of CC states, each replayed flipped and compared sample by sample. It costs one extra run of a sequence you are already running, and it catches a class of bug no single-orientation test can.

Two things worth adding unprompted. First, the debounce is on the CC state, not on the fact of attachment — a sink moving from CC1 to CC2 mid-window never drops "something is attached", so a machine watching only that bit will accept an orientation that was stable for one tick. Second, the symmetry is not expressible in SVA: it relates two executions, and an assertion language can only talk about one. It has to be a testbench structure.

14. What Carries Forward

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE MECHANISM
    o  two pins, three levels, nine combinations, five of which are an attach
    o  EXACTLY one Rd is a sink; both is a debug accessory, and the difference
       is one character of RTL
    o  the orientation is not signalled anywhere -- it IS which line had the Rd
    o  the debounce applies to the CC STATE, not to the fact of attachment
    o  a detach is debounced too, and `attached` stays up throughout
    o  a correct debounce makes the sampling instant irrelevant, which turns a
       real question into a non-question

    THE RESULT
    o  the accept threshold is a step function at exactly the window width,
       identical for all five attach combinations
    o  the symmetry holds over all 81 ordered CC transitions in three
       languages: everything identical, orientation inverted, and identical
       again for a debug accessory because there is no other line to swap

    THE METHOD
    o  a SYMMETRY is exhaustively checkable when the transformation is finite
       and total, and is strictly stronger than any number of value checks
    o  a symmetry is NOT expressible in SVA -- it relates two executions
    o  IDENTICAL SCORES from two different mutations mean the mutants are
       behaviourally the same, or one check is doing all the work for both;
       either way it is a signal
    o  a loop whose bound is never incremented has an empty range, and every
       check inside it passes by not existing -- 6,507 of them here
    o  if every phase resets before the scenario, every scenario is the FIRST
       one, and an entire class of bug is unreachable
    o  sweep a boundary rather than probing it: a stale counter leaves an
       arbitrary residue, so one probe finds some and misses others
    o  VHDL declaration order asks "who calls this", and Verilog does not
    o  RTL cannot evaluate an analog decode -- say what the result is
       conditional on

The next chapter drops to the other end of the scale: no operating system, no class driver, a few kilobytes of RAM, and a USB stack that has to fit in it.

Continue learning

Standards & specifications

Governing standard
USB-IF (Universal Serial Bus Specification)(opens USB Implementers Forum (USB-IF) in a new tab)

Defines the USB bus — its electrical signalling, connectors, packet and transaction model, device framework and the descriptors a device must expose — together with the device-class specifications layered on it. It does not define host-controller register interfaces (xHCI and EHCI are separate documents) nor any operating system's driver architecture.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the USB curriculum.