Skip to content
VLSI Mentor

CXL · Module 30

Verification Review Checklist

A working review document for the verification environment itself. Nine review dimensions — oracle independence, unknown-value vacuity, checker reachability, pulse observation, transaction identity, duplicate responses, exact versus bound checking, timeout authority and fairness — each with the escape, the executable contrast, and the campaign discipline that makes a passing regression mean something.

30.2 reviewed the design. This chapter reviews the thing that judges the design, and it is the only review in Module 30 whose subject is not the product.

The review question this chapter turns on, asked once per dimension:

What would have to be true for this test to fail?

If the answer is "nothing", the test is not evidence. It is a green square.

A passing testbench becomes credible evidence only after the verification environment itself has been challenged.

That sentence is the whole chapter. Every dimension below is a way a competent, well-intentioned environment stops being able to fail — and every one of them reports the same colour as an environment that works.

1. How To Use This Chapter

Each of the nine review dimensions below is a working review item, and every one answers the same eight questions:

FacetWhat it settles
Under reviewthe part of the environment being examined
Evidence at riskthe claim that becomes worthless if it is wrong
Where it livesthe specific construct, not the file
Evidence to demandwhat the reviewer should ask to see
What escapesthe design bug that reaches silicon
How to falsify itthe experiment that proves the checker can fail
Telemetrythe environment's own instrumentation
Misleading evidencewhat makes the broken environment look thorough

Every model in this chapter has two builds, and both are checkers. The lenient build is not a caricature: it is a check an experienced engineer would write, which is correct on the cases it was written for and cannot fail on the case that matters.

2. The One-Sentence Model

A verification review is sound when the regression passes, when the oracle reaches its answer by a different route than the design, when every check is reachable and has evaluated, when unknown values are rejected rather than walked through, when every identity is qualified by its era, and when the mutation baseline was green before the campaign ran — and "the testbench passes" is bit 0.

3. What This Chapter Owns

GroundOwner
Reviewing the architecture before RTL exists30.1
Reviewing the RTL against the architecture30.2
Reviewing coherency invariants across agents30.4
What a deployment committed to29.5
Reviewing the environment that judges the RTLthis chapter

The boundary with 30.2 is the one to hold clearly. A defect 30.2 calls "the counter counts the wrong event" becomes here "the checker never asked which event the counter counted." Same escape, opposite side of the testbench — and the fix is in a different file, owned by a different person, found in a different review.

4. Teaching-Model Boundary And Source Discipline

Every model in this chapter is a teaching model. Each isolates one property of a verification environment so it can be examined, mutated and broken on purpose. None is a production checker, a UVM scoreboard, or an implementation of any specification flow.

Nothing here states a normative CXL detail. No opcode, packet layout, bit position, field width, response encoding, snoop encoding, retry rule, timeout constant, latency figure or register definition from the specification appears anywhere in this chapter. The review dimensions — oracle independence, vacuity, reachability, identity qualification — are general properties of any verification environment, and they are examined in their general form deliberately, so the technique transfers to any testbench a reviewer is handed.

Claim classHow it is marked
General verification reasoningstated plainly
Teaching abstractiondeclared in the model header
Illustrative parameterevery concrete figure in a model or table
Simulator-derived resultquoted from a run and asserted
Derived arithmeticshown with its inputs

5. Review Item 1 — Does The Oracle Reach Its Answer By A Different Route?

Under review. Every scoreboard, reference model, predictor and expected-value computation.

Evidence at risk. All of it. An oracle that agrees with the design unconditionally converts the entire regression into a tautology.

Where it lives. The line that computes the expected value.

The failure. A scoreboard that recomputes the design's own expression, imports the design's own function, or reads a net from inside the design agrees with the design whatever the design does. Every test passes. The coverage report is full. Nothing can fail.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 1 - the oracle that is a copy of the design.
//
// A scoreboard proves something only if it reaches its expected value by a
// DIFFERENT route than the design does. An oracle that recomputes the design's
// own expression, or reads the design's own internal net, agrees with the
// design unconditionally - including when the design is wrong.
//
//   BAD  : exp = dut_internal_result;      // or the same expression, copied
//   GOOD : exp = reasoned from the specification, computed independently
//
// TEACHING MODEL. Isolates one verification-environment invariant; it is not a
// production checker, a UVM scoreboard, or an implementation of any flow.
module oracle_independence #(parameter int ORACLE_COPIES_DUT = 0) (
  input  logic clk, rst_n,
  input  logic        evaluate, defect_live,
  input  logic [15:0] a, b,
  output logic [15:0] dut_out, oracle_out,
  output logic        agrees, flagged,
  output logic [7:0]  n_evals, n_flagged, n_escapes,
  output logic        orc_err
);
  logic [15:0] correct_out;

  // The specification: the output is the larger operand plus one.
  // The INDEPENDENT oracle computes that directly.
  assign correct_out = ((a > b) ? a : b) + 16'd1;

  // The design. With `defect_live` high it drops the plus one - a real,
  // small, plausible defect.
  assign dut_out = ((a > b) ? a : b) + (defect_live ? 16'd0 : 16'd1);

  // The whole review point. A copying oracle reads the design's own output.
  assign oracle_out = (ORACLE_COPIES_DUT != 0) ? dut_out : correct_out;

  assign agrees   = (dut_out === oracle_out);
  assign flagged  = evaluate && !agrees;
  // SAFETY-OF-EVIDENCE VIOLATION: the design is known wrong and the checker
  // agreed with it. A checker that cannot disagree is not evidence.
  assign orc_err  = evaluate && defect_live && agrees;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_evals <= 8'd0; n_flagged <= 8'd0; n_escapes <= 8'd0;
    end else if (evaluate) begin
      n_evals <= n_evals + 8'd1;
      if (!agrees)                n_flagged <= n_flagged + 8'd1;
      if (defect_live && agrees)  n_escapes <= n_escapes + 8'd1;
    end
  end
endmodule

The measurement. With the defect injected — the design drops a +1 it is specified to apply:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
defect live : dut=100 indep_oracle=101 copy_oracle=100

The independent oracle is unmoved, because it never read the design. The copying oracle follows the design down to 100 and reports agreement. Over the run: the independent environment flags one of four evaluations and records zero escapes; the copying environment flags none and records one escape.

Evidence to demand. Ask where the expected value comes from, and follow it back until you reach either the specification or the design. If it reaches the design, the review is over and the finding is written. Ask specifically about shared packages — a function used by both the design and its predictor is a copying oracle wearing a different hat.

What escapes. Every design bug in the checked path, silently, for the lifetime of the project.

How to falsify it. Inject a defect into the design on purpose and confirm the environment fails. This is a five-minute experiment and almost nobody runs it. An environment that has never failed has never been shown capable of failing.

Telemetry. The environment's own flagged count. An environment that has raised nothing in six months is not a quiet design; it is an untested claim.

Misleading evidence. A scoreboard with a rich transaction class, a full coverage model, and a clean regression. All three are compatible with an oracle that cannot disagree.

A block diagram of two scoreboards judging one design. The independent oracle computes the expected value from the specification and disagrees when a defect is injected. The copying oracle reads the design's own output and agrees with a design known to be wrong.specificationlarger plus onedesign with adefectdrops the plus oneoracle reads thedesigncopyingoracle reasonsfrom specindependentagrees0 flagged, 1 escapedisagrees1 flagged, 0 escapes12

Figure 1 — the two arrows into the oracles are the whole review. The upper oracle's input is the design's output; the lower oracle's input is the specification. Both scoreboards are otherwise identical, both are wired to the same design, and only one of them has an input that can carry a disagreement.

6. Review Item 2 — Does An Unknown Value Walk Through This Comparison?

Under review. Every comparison in every checker, monitor, scoreboard and assertion.

Evidence at risk. Every check that runs on a cycle where any input is X.

Where it lives. The choice between != and !==.

The mechanism. == and != return X when either operand contains an X. An if treats X as false. So a checker written

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (got != exp) raise_error;

does not raise on an unknown. The condition is X, the branch is not taken, and the check records a pass it never made. !== is defined for X and returns 1.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 2 - the comparison that an unknown value walks straight through.
//
// `!=` and `==` return X when either operand is X. A checker written
// `if (got != exp) fail;` therefore does NOT fail on an X - the condition is
// X, `if` takes the false branch, and the check reports a pass it never made.
//
//   BAD  : if (got != exp)  raise;         // X -> condition X -> no raise
//   GOOD : if (got !== exp) raise;         // X -> condition 1 -> raise
//
// The same escape appears as `if (!ok) ...` where `ok` is X, and as any
// boolean check that treats "not true" and "false" as the same thing.
//
// TEACHING MODEL.
module unknown_vacuity #(parameter int LAX_COMPARE = 0) (
  input  logic clk, rst_n,
  input  logic        check_now, make_unknown,
  input  logic [15:0] got, exp_val,
  output logic [15:0] obs,
  output logic        flagged, unknown_seen,
  output logic [7:0]  n_checks, n_flagged, n_unknown, n_escapes,
  output logic        vac_err
);
  logic lax_flag, tight_flag;

  // The observed value. `make_unknown` models any source of X: an
  // uninitialised register, an unconnected port, a tri-state, a clock-domain
  // escape, a memory read before a write.
  assign obs = make_unknown ? 16'hxxxx : got;

  // The two comparisons, both computed so the model can detect its own
  // lenient build. `lax_flag` is X-valued when `obs` is X.
  assign lax_flag   = (obs != exp_val);
  assign tight_flag = (obs !== exp_val);

  // A checker's `if` treats an X condition as false. `=== 1'b1` reproduces
  // that exactly, which is what makes the lenient build's escape observable.
  assign flagged = (LAX_COMPARE != 0) ? (lax_flag === 1'b1) : tight_flag;

  assign unknown_seen = (^obs === 1'bx);
  // SAFETY-OF-EVIDENCE VIOLATION: the observed value was unknown and the
  // checker raised nothing. The test reports a pass it did not make.
  assign vac_err = check_now && unknown_seen && !flagged;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_checks <= 8'd0; n_flagged <= 8'd0; n_unknown <= 8'd0; n_escapes <= 8'd0;
    end else if (check_now) begin
      n_checks <= n_checks + 8'd1;
      if (flagged)                     n_flagged <= n_flagged + 8'd1;
      if (unknown_seen)                n_unknown <= n_unknown + 8'd1;
      if (unknown_seen && !flagged)    n_escapes <= n_escapes + 8'd1;
    end
  end
endmodule

The measurement. The same observed value, made unknown:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
observed value is unknown : tight_flag=1 lax_flag=0

Both comparisons are correct on defined values, and the run proves it: on a real mismatch both raise. The difference appears only on the X, where the tight comparison raises and the lenient one raises nothing. Over three checks the tight build raises twice — the mismatch and the unknown — and the lenient build raises once and records one escape.

The same escape wears other clothes. if (!ok) where ok is X. if (valid && ...) where valid is X. A ternary whose condition is X. Every one of them silently takes the branch that does nothing, and the family is large enough that a review should look for the pattern, not the operator.

Evidence to demand. For every checker, what it does when its input is X — demonstrated, not asserted. And an explicit X-injection test: force an input unknown and confirm the environment complains.

What escapes. Every bug that presents as an unknown: an uninitialised register, an unconnected port, a clock-domain escape, a memory read before its write, a tri-state with no driver. These are among the easiest bugs to find and this makes them invisible.

How to falsify it. Force an X and require a failure. If the test still passes, the checker is not checking.

Telemetry. Count unknowns seen separately from mismatches flagged. A design with a high unknown count and no failures has a checker problem, and the two numbers are the only way to see it.

Misleading evidence. A green run on a design that is full of X. This is the most reassuring possible output and it is produced by the least capable possible checker.

7. Review Item 3 — Did This Check Actually Evaluate?

Under review. Every check behind a guard, every assertion with a disable condition, every monitor gated on a mode.

Evidence at risk. The claim that a check passed, which is a different claim from the check having run.

Where it lives. The guard.

The distinction that matters. A regression log cannot tell these apart:

Check ran and passedCheck never ran
Failures reported00
Log linenonenone
Coverage of the checkcompletenothing
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 3 - the checker that never ran.
//
// A check behind a guard that never holds reports no failures, which is
// indistinguishable in a regression log from a check that ran and passed. The
// only defence is to count how often the check was ELIGIBLE and how often it
// actually EVALUATED, and to require the second number to be non-zero.
//
//   BAD  : if (sample && arm) if (val !== exp) raise;   // `arm` never high
//   GOOD : the same check, plus a published evaluation count that is asserted
//
// TEACHING MODEL.
module checker_reach #(parameter int GUARDED_CHECK = 0) (
  input  logic clk, rst_n,
  input  logic        sample, arm, report_now,
  input  logic [15:0] val, exp_val,
  output logic        eligible, ran, fired,
  output logic [7:0]  n_eligible, n_ran, n_fired,
  output logic [15:0] reached_pct,
  output logic        rch_err
);
  logic [31:0] r_q;

  // The check is ELIGIBLE whenever a transaction is sampled.
  assign eligible = sample;
  // Whether it actually RUNS depends on the guard the build under review uses.
  assign ran      = (GUARDED_CHECK != 0) ? (sample && arm) : sample;
  assign fired    = ran && (val !== exp_val);

  assign r_q = (n_eligible == 8'd0) ? 32'd0
             : (({24'd0, n_ran} * 32'd100) / {24'd0, n_eligible});
  assign reached_pct = r_q[15:0];

  // SAFETY-OF-EVIDENCE VIOLATION: transactions were sampled and the check
  // never evaluated once. Zero failures, zero evidence.
  assign rch_err = report_now && (n_eligible != 8'd0) && (n_ran == 8'd0);

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_eligible <= 8'd0; n_ran <= 8'd0; n_fired <= 8'd0;
    end else begin
      if (eligible) n_eligible <= n_eligible + 8'd1;
      if (ran)      n_ran      <= n_ran + 8'd1;
      if (fired)    n_fired    <= n_fired + 8'd1;
    end
  end
endmodule

The measurement. Four transactions sampled, with the guard's arm never asserted:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
arm never asserted : eligible=4 unguarded_ran=4 guarded_ran=0

Both builds agree that four transactions were eligible. The unguarded checker ran four times and reached 100 percent. The guarded checker ran zero times, reached 0 percent, and reported exactly the same number of failures. When the arm is later raised for two of eight, the reach is 25 percent — and a reviewer who never computes that number sees a clean run either way.

Evidence to demand. For every check, the count of times it evaluated. Not the pass count — the evaluation count. A check with an evaluation count of zero is not a passing check, and no coverage model that measures stimulus rather than checking will notice.

What escapes. Whatever that check was written to catch, for as long as its guard stays false — which can be the entire project if the guard depends on a mode nobody enables.

How to falsify it. Publish and assert the evaluation count. This chapter's own mutation campaign turned up the zero-eligible case as a missing checker for exactly this reason: the case was driven and the ratio was never asserted.

Telemetry. Evaluation counts per check, reviewed as a distribution. The interesting number is the count of checks whose count is zero.

Misleading evidence. Functional coverage at 100 percent. Coverage measures what the stimulus reached, not what the checkers examined, and the two are routinely conflated in exactly this situation.

A block diagram of four sampled transactions reaching two checkers. The unguarded checker evaluates four times and reaches full coverage. The guarded checker evaluates zero times because its arm is never asserted. Both report zero failures, and the regression log cannot tell them apart.4 transactionsall eligiblecheck behind anarmarm never assertedcheck on thesampleunguardedran 0 times0 failures reportedran 4 times0 failures reported12

Figure 2 — the two results are the same number. Both checkers saw four eligible transactions and both reported zero failures, which is every line the regression log will carry. The only thing that separates them is the evaluation count in the middle column, and nothing publishes it unless somebody asks.

8. Review Item 4 — Can This Monitor See A Single-Cycle Occurrence?

Under review. Every monitor that observes a pulse, a handshake, an error strobe, or any signal that is high for one cycle.

Evidence at risk. Everything the monitor claims not to have seen.

Where it lives. The condition under which the monitor writes its observation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 4 - observing a single-cycle occurrence.
//
// A monitor that reads a pulse only when the environment happens to look at it
// sees the pulse only if the two coincide. A sticky observer latches the pulse
// on every clock and holds it until the environment reads and clears it.
//
//   BAD  : if (look) saw <= pulse;         // misses every pulse outside `look`
//   GOOD : if (pulse) sticky <= 1'b1;      // latched on every clock
//
// The truth count is maintained identically in both builds so the model can
// detect its own lenient build.
//
// TEACHING MODEL. Sequential.
//   State remembered : the sticky observation bit and the lenient sample bit.
//   Reset semantics  : both observers clear; the truth counter clears too.
module pulse_observer #(parameter int LOOK_ONLY = 0) (
  input  logic clk, rst_n,
  input  logic pulse, look, clear, report_now,
  output logic sticky_q, look_q, observed,
  output logic [7:0] n_pulses, n_observed, n_missed,
  output logic obs_err
);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      sticky_q <= 1'b0; look_q <= 1'b0;
      n_pulses <= 8'd0; n_observed <= 8'd0; n_missed <= 8'd0;
    end else begin
      // The truth: every pulse, counted on every clock, in both builds.
      if (pulse) n_pulses <= n_pulses + 8'd1;
      if (pulse && !(LOOK_ONLY != 0 ? look : 1'b1))
        n_missed <= n_missed + 8'd1;

      // The sticky observer: latched on every clock, cleared only on request.
      if (clear)      sticky_q <= 1'b0;
      else if (pulse) sticky_q <= 1'b1;

      // The lenient observer: written only while the environment is looking.
      if (clear)     look_q <= 1'b0;
      else if (look) look_q <= pulse;

      if (pulse && (LOOK_ONLY != 0 ? look : 1'b1))
        n_observed <= n_observed + 8'd1;
    end
  end

  assign observed = (LOOK_ONLY != 0) ? look_q : sticky_q;
  // SAFETY-OF-EVIDENCE VIOLATION: occurrences happened and none was observed.
  assign obs_err = report_now && (n_pulses != 8'd0) && (n_observed == 8'd0);
endmodule

The measurement. One occurrence, raised while the environment is not looking:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pulse while not looking : sticky=1 look_bit=0 truth=1

Both builds count the occurrence — the truth counter is maintained identically, which is what lets the model detect its own lenient build. The sticky observer latches it on the clock. The look-only observer writes its bit only while look is high and never sees it at all: zero observed, one missed, and silence.

The part that is not obvious

A sticky observer is necessary and not sufficient. It samples on a clock edge, so it can only observe what is present at an edge inside the window it is watching. 30.2 recorded a monitor that was entirely correct and had no clock edge in the window it was meant to watch, and this chapter's own baseline produced the same failure twice on its first run — recorded in section 19 as a testbench defect, because that is what it was.

A monitor needs three things and two of them are usually reviewed: the right condition, a latch, and a clock inside the window.

Delta-cycle discipline, measured

The other half of pulse observation is when the environment reads. A value read in the active region of a clock edge is the value from before that edge, because non-blocking assignments update in the NBA region afterwards. Both reads below are of the same net at the same edge:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
same net, same edge : read_in_active=3 read_after_delta=4

This is deterministic, not a race, and it is the mechanism behind most "my monitor saw the old value" reports. Its dangerous cousin is a race: driving a stimulus in the same delta as the design's own sampling edge leaves the outcome to simulator scheduling. Every stimulus change in both of this chapter's testbenches lands #1 after the active edge, never on it, and that discipline is a reviewable property of the environment rather than a matter of taste.

Evidence to demand. For every monitored pulse, where it is latched and what clears it. And for the environment as a whole, whether stimulus is driven on the active edge or after it.

What escapes. Every single-cycle error strobe in the design, which is most of them.

Telemetry. Publish the occurrence count and the observation count separately. A design in which those two numbers differ has a monitor problem, and a monitor that reports its own misses is worth the flop it costs.

Misleading evidence. A monitor that works perfectly in a directed test, because in a directed test the environment is looking exactly when the event happens.

9. Review Item 5 — Is This Identity Qualified By Its Era?

Under review. Every scoreboard that matches a response to a request by tag, id, address or sequence number.

Evidence at risk. Every completion the scoreboard has ever matched.

Where it lives. The match condition.

The distinction. A tag says which transaction. It does not say which era. After a flush, an error recovery, a mode change or a reset of the outstanding set, a response issued before the event can still arrive, still carry a tag that is outstanding again, and still match.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 5 - matching a response to a transaction by identity alone.
//
// A tag identifies WHICH transaction. It does not identify WHICH ERA. After a
// flush, an error recovery or a mode change, a response issued before the
// event can still arrive, still carry a tag that is outstanding again, and
// still match. The generation - an epoch stamp advanced by the event - is what
// makes the match safe.
//
//   BAD  : accept if (tag is outstanding)
//   GOOD : accept if (tag is outstanding AND the response's era is current)
//
// TEACHING MODEL. Sequential.
//   State remembered : one outstanding bit per tag, plus the current era.
//   Safety           : a response from a previous era is never accepted.
module response_identity #(parameter int MATCH_TAG_ONLY = 0) (
  input  logic clk, rst_n,
  input  logic       issue, flush, resp_valid,
  input  logic [1:0] issue_tag, resp_tag,
  input  logic [3:0] resp_gen,
  output logic [3:0] gen_now,
  output logic [3:0] outstanding,
  output logic       accepted, stale,
  output logic [7:0] n_accepted, n_stale, n_stale_ok,
  output logic       id_err
);
  logic [3:0] out_q, out_next, gen_q;
  logic       tag_live, era_ok;

  // ONE assignment to the outstanding set, computed from ALL THREE events.
  // STATED PRIORITY: a flush clears everything; otherwise an issue wins its own
  // tag over a retirement of the same tag, because the issue is the newer fact.
  always_comb begin
    out_next = out_q;
    if (flush) out_next = 4'd0;
    else begin
      if (accepted) out_next[resp_tag]  = 1'b0;
      if (issue)    out_next[issue_tag] = 1'b1;
    end
  end

  assign outstanding = out_q;
  assign gen_now     = gen_q;
  assign tag_live    = out_q[resp_tag];
  assign era_ok      = (resp_gen == gen_q);
  assign stale       = resp_valid && tag_live && !era_ok;

  // The whole review point.
  assign accepted = (MATCH_TAG_ONLY != 0) ? (resp_valid && tag_live)
                                          : (resp_valid && tag_live && era_ok);
  // SAFETY VIOLATION: a response from a previous era was accepted.
  assign id_err = accepted && !era_ok;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      out_q <= 4'd0; gen_q <= 4'd0;
      n_accepted <= 8'd0; n_stale <= 8'd0; n_stale_ok <= 8'd0;
    end else begin
      out_q <= out_next;
      if (flush) gen_q <= gen_q + 4'd1;   // the era advances on the event
      if (accepted) n_accepted <= n_accepted + 8'd1;
      if (stale)               n_stale    <= n_stale + 8'd1;
      if (accepted && !era_ok) n_stale_ok <= n_stale_ok + 8'd1;
    end
  end
endmodule

The measurement. Tag 1 issued in era 0; a flush advances the era to 1 and clears the outstanding set; tag 1 is issued again; a response arrives stamped era 0:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stale response : era_now=1 resp_era=0 tight_acc=0 lax_acc=1

Both builds detect that the response is stale. That is the part worth dwelling on: the information is present in the lenient build, and it does nothing with it. The era-qualified build refuses; the tag-only build accepts, retires the wrong transaction, and leaves its outstanding set empty while the real transaction is still in flight.

The damage is not the accepted response. It is that the scoreboard now believes a transaction completed that did not, so the real completion — when it arrives — will look like a duplicate, and a transaction that never completes will look completed. One stale acceptance corrupts two later judgements, which is why this defect is hard to trace back.

Evidence to demand. For every matching rule, the complete key. If the key is the tag alone, ask what makes the tag unique across time rather than only across the outstanding set.

What escapes. Responses matched to the wrong request, mis-scored, silently.

How to falsify it. Drive a response from a previous era and require a failure. A four-bit era wraps after sixteen flushes, which is the storage decision behind the mechanism and is asserted in this chapter's run.

Telemetry. Stale responses detected and stale responses accepted. The first is a fabric property. The second must read permanently zero.

Misleading evidence. A scoreboard that matches every response and reports no orphans. A tag-only matcher matches everything; the orphan count is zero because the wrong things were matched.

A waveform over eight cycles of a response matcher. A transaction is issued in era zero, a flush advances the era to one and clears the outstanding set, the same tag is issued again, and a response stamped era zero arrives. The era-qualified matcher refuses it; the tag-only matcher accepts it and retires the wrong transaction.tag 1 issued, era 0tag 1 issued, era 0flush: era advancesflush: era advancesresponse stamped era 0response stamped era 0clkera00011111live_tags02202222resp_era00000000qualifiedtag_onlyt0t1t2t3t4t5t6t7
Figure 3 — a teaching waveform, not normative CXL timing. The live_tags row returns to the same value after the flush, so a matcher watching only which tags are outstanding sees nothing change between cycles 2 and 6. The era row is the difference: it advances once, at the flush, and never again. At cycle 6 a response arrives stamped era 0 — issued before the flush. The qualified row stays low because the era no longer matches; the tag_only row goes high because tag 1 is indeed outstanding. The accepted response retires a transaction that is still in flight, and nothing in the outstanding set records that it happened.

10. Review Item 6 — Would This Environment Notice A Duplicate?

Under review. Every retirement rule, and every conservation claim built on one.

Evidence at risk. Every completion count, every outstanding figure, every throughput number.

Where it lives. The condition on the retirement.

The mechanism. A responder that answers twice is not obviously wrong from either side. The requester sees a response it was waiting for; the responder sees work it did. What breaks is the accounting, and only an equation computed from independent lifetime totals can see it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
issued == completed + outstanding
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 6 - the duplicate response, and the conservation equation that finds it.
//
// A responder that answers twice is not obviously wrong from either side: the
// requester sees a response it was waiting for, and the responder sees work it
// did. What breaks is the accounting - and only an equation computed from
// INDEPENDENT lifetime totals can see it.
//
//   issued == completed + outstanding        // must hold at every instant
//
//   BAD  : retire on every response
//   GOOD : retire only a response whose transaction is still outstanding, and
//          count the rest as duplicates
//
// TEACHING MODEL. Sequential.
//   Safety : the conservation equation holds continuously.
module duplicate_conservation #(parameter int NO_DUP_CHECK = 0) (
  input  logic clk, rst_n,
  input  logic       issue, resp_valid,
  input  logic [1:0] issue_id, resp_id,
  output logic [3:0] live,
  output logic [7:0] n_issued, n_completed, n_dup, outstanding,
  output logic       is_dup, retires, conserved,
  output logic       dup_err
);
  logic [3:0] live_q, live_next;
  logic [7:0] out_q;

  // ONE assignment to the live vector, computed from BOTH events. STATED
  // PRIORITY: an issue wins its own id over a retirement of the same id in the
  // same cycle, because the id is live again from that cycle onward.
  always_comb begin
    live_next = live_q;
    if (retires) live_next[resp_id]  = 1'b0;
    if (issue)   live_next[issue_id] = 1'b1;
  end

  assign live        = live_q;
  assign outstanding = out_q;
  assign is_dup      = resp_valid && !live_q[resp_id];
  // The whole review point. The lenient build retires on any response.
  assign retires     = (NO_DUP_CHECK != 0) ? resp_valid
                                           : (resp_valid && live_q[resp_id]);
  assign conserved   = (n_issued == (n_completed + out_q));
  // SAFETY VIOLATION: the conservation equation is open.
  assign dup_err     = !conserved;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      live_q <= 4'd0; out_q <= 8'd0;
      n_issued <= 8'd0; n_completed <= 8'd0; n_dup <= 8'd0;
    end else begin
      // One assignment to the outstanding counter, computed from both events.
      case ({issue, retires})
        2'b10:   out_q <= out_q + 8'd1;
        2'b01:   out_q <= (out_q == 8'd0) ? 8'd0 : out_q - 8'd1;
        default: out_q <= out_q;
      endcase
      live_q <= live_next;
      if (issue)   n_issued    <= n_issued + 8'd1;
      if (retires) n_completed <= n_completed + 8'd1;
      if (is_dup)  n_dup <= n_dup + 8'd1;
    end
  end
endmodule

The measurement. One issue, one response, then the same response again:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
duplicate response : is_dup=1 tight_retires=0 lax_retires=1
after the duplicate : tight i=1 c=1 o=0 | lax i=1 c=2 o=0

Both builds detect the duplicate. The detection is identical; the action is not. The outstanding-checked build refuses to retire it and the equation still holds. The lenient build retires it anyway and records two completions for one issue — and 1 != 2 + 0 is the only thing in the entire environment that notices.

The equation is also the simultaneity check. A simultaneous issue and retirement moves the outstanding counter by zero and both lifetime totals by one, and the run asserts that the equation survives it — which is 30.2 section 7 evaluated from the environment's side.

Evidence to demand. The conservation equation, evaluated continuously rather than at end of test. The window in which it is false is the window that matters, and an end-of-test check on a design that self-corrects finds nothing.

What escapes. Double-counted work, an outstanding figure that drifts, and eventually a structure that reports full while empty.

Telemetry. All four numbers — issued, completed, duplicates, outstanding — published and compared in hardware. The equation is evaluable in the field, which makes it one of the few environment-side checks that survives into silicon.

Misleading evidence. A duplicate counter that increments correctly in the failing build. It does. Counting a duplicate and refusing to act on it are different things, and a reviewer checking the counter has checked the wrong one.

11. Review Item 7 — Is This A Bound Or An Expected Value?

Under review. Every check written with <=, >=, within, or a tolerance.

Evidence at risk. Everything inside the margin.

Where it lives. The comparison operator.

The claim. result <= limit is a real check, and it is a much weaker one than result == expected. The margin is exactly the size of the escape.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 7 - checking a bound where the specification gives an exact value.
//
// `result <= limit` is a real check and it is a much weaker one than
// `result == expected`. A design that is always comfortably under the bound
// passes the bound check with any wrong value it likes, and the checker's
// margin is exactly the size of the escape.
//
//   BAD  : raise unless (result <= limit)
//   GOOD : raise unless (result == exp_result), and keep the bound as well
//
// TEACHING MODEL.
module exact_versus_bound #(parameter int BOUND_ONLY = 0) (
  input  logic clk, rst_n,
  input  logic        check_now, defect_live,
  input  logic [15:0] operand, limit,
  output logic [15:0] result, exp_result, margin,
  output logic        bound_ok, exact_ok, flagged,
  output logic [7:0]  n_checks, n_flagged, n_escapes,
  output logic        exa_err
);
  // The specification: the result is twice the operand.
  assign exp_result = {operand[14:0], 1'b0};
  // The design. With `defect_live` high it shifts by one place too few - a
  // result that is wrong by half and still far below any sane bound.
  assign result = defect_live ? operand : {operand[14:0], 1'b0};

  assign bound_ok = (result <= limit);
  assign exact_ok = (result == exp_result);
  assign margin   = (limit >= result) ? (limit - result) : 16'd0;

  // The whole review point.
  assign flagged = check_now && ((BOUND_ONLY != 0) ? !bound_ok : !exact_ok);
  // SAFETY-OF-EVIDENCE VIOLATION: the result is not the specified value and
  // the checker raised nothing.
  assign exa_err = check_now && !exact_ok && !flagged;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_checks <= 8'd0; n_flagged <= 8'd0; n_escapes <= 8'd0;
    end else if (check_now) begin
      n_checks <= n_checks + 8'd1;
      if (flagged)              n_flagged <= n_flagged + 8'd1;
      if (!exact_ok && !flagged) n_escapes <= n_escapes + 8'd1;
    end
  end
endmodule

The measurement. The specification says the result is twice the operand. The design produces half that — wrong by a factor of two, and far below any sane bound:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
defect live : result=1000 expected=2000 limit=50000 margin=49000

The exact checker raises. The bound checker raises nothing, because 1000 is comfortably inside a limit of 50000. The margin is 49,000, and every wrong answer in that range passes.

The bound is not useless. The run proves it: with the correct result of 2000 against a limit of 1500, the bound checker raises and the exact checker does not, because the value is the specified one. A bound catches a different class of failure — one where the design is right and slow, or right and too large. Keep both; do not let the bound stand in for the exact value when the exact value is known.

When a bound is the honest choice. When the specification genuinely gives one — a maximum latency, a capacity, a rate. Then the bound is the expected value, and the review question becomes whether the bound is the specified one or a number somebody chose to make the test pass.

Evidence to demand. For every bound check, why the exact value is not available. "It is easier" is the answer that produces this defect, and this chapter's own campaign produced it: a starvation counter checked with >= 1 where the exact value was 1, written for convenience, and an inverted counter satisfied it too. Section 18 records it.

What escapes. Any wrong value inside the margin, which on a generous bound is most wrong values.

Telemetry. Publish the distribution against the bound, not a pass rate. A design whose results cluster at one value and whose bound is far away is telling you the bound was never the check.

12. Review Item 8 — Can This Timeout Fire While The Fabric Is Busy?

Under review. Every timeout, watchdog, hang detector and progress monitor.

Evidence at risk. The claim that the design does not hang.

Where it lives. What resets the age counter.

The failure. A timeout monitor must age the oldest outstanding transaction. A monitor that resets its age on any activity anywhere never fires while the fabric is busy — which is exactly when transactions get stuck.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 8 - the timeout monitor that a busy fabric keeps alive.
//
// A timeout monitor must age the OLDEST OUTSTANDING TRANSACTION. A monitor
// that resets its age counter on any activity anywhere never fires while the
// fabric is busy - which is exactly when transactions get stuck.
//
//   BAD  : if (any_activity) age <= 0;      // a busy fabric is never timed out
//   GOOD : age the oldest outstanding entry; only its own completion clears it
//
// TEACHING MODEL. Sequential.
//   Safety   : a transaction older than the limit is reported.
//   Liveness : a transaction eventually completes or is reported - ASSUMING
//              the limit is non-zero and the clock runs.
module timeout_authority #(parameter int RESET_ON_ANY_ACTIVITY = 0) (
  input  logic clk, rst_n,
  input  logic       start_op, finish_op, other_activity,
  input  logic [7:0] limit,
  output logic [7:0] age, oldest_age, n_fired, n_stuck_cycles,
  output logic       active, fired, exhausted,
  output logic       tmo_err
);
  logic [7:0] age_q, old_q;
  logic       act_q;
  logic       truly_stuck;

  assign age        = age_q;
  assign oldest_age = old_q;
  assign active     = act_q;
  assign fired      = act_q && (limit != 8'd0) && (age_q >= limit);
  assign exhausted  = (old_q == 8'hFF);
  // The truth, computed the same way in BOTH builds: the oldest outstanding
  // age, cleared only by that transaction's own completion.
  assign truly_stuck = act_q && (limit != 8'd0) && (old_q >= limit);
  // SAFETY VIOLATION: a transaction is older than the limit and nothing fired.
  assign tmo_err = truly_stuck && !fired;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      age_q <= 8'd0; old_q <= 8'd0; act_q <= 1'b0;
      n_fired <= 8'd0; n_stuck_cycles <= 8'd0;
    end else begin
      if (start_op)       act_q <= 1'b1;
      else if (finish_op) act_q <= 1'b0;

      // The age the build under review uses.
      if (start_op)                         age_q <= 8'd0;
      else if (finish_op)                   age_q <= 8'd0;
      else if ((RESET_ON_ANY_ACTIVITY != 0) && other_activity)
                                            age_q <= 8'd0;
      else if (act_q && (age_q != 8'hFF))   age_q <= age_q + 8'd1;

      // The truth, maintained identically in both builds.
      if (start_op)                         old_q <= 8'd0;
      else if (finish_op)                   old_q <= 8'd0;
      else if (act_q && (old_q != 8'hFF))   old_q <= old_q + 8'd1;

      if (fired)       n_fired        <= n_fired + 8'd1;
      if (truly_stuck) n_stuck_cycles <= n_stuck_cycles + 8'd1;
    end
  end
endmodule

The measurement. One transaction that never finishes, with unrelated activity every cycle and a limit of 5:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
5 cycles stuck, fabric busy : tight_age=5 lax_age=0 oldest=5

The truth counter reads 5 in both builds. The activity-reset monitor's own age reads zero, forever, because something in the fabric moves every cycle. It never fires. The information is in the design and the monitor has arranged not to be able to use it.

Evidence to demand. What clears the age, stated as a list. The only acceptable entry is this transaction's own completion. Anything else — a neighbouring completion, a credit return, a link event — turns the monitor off under load.

What escapes. Hangs, under exactly the conditions that produce hangs. A timeout that only fires on an idle fabric is a timeout for a machine that was not going to hang anyway.

The liveness withdrawal. With the limit set to zero, the transaction ages indefinitely and nothing fires, and no safety property is violated. The run asserts that explicitly. Liveness claims carry environmental assumptions; a monitor with its limit disabled has withdrawn one, and that is a configuration state a review must ask about.

Telemetry. The oldest-outstanding age, as a distribution. A liveness failure is not an event, so there is no counter for it — the only evidence is the continued presence of something old.

Misleading evidence. A watchdog that has never fired in two years of regression. That is either a design that never hangs or a watchdog that cannot fire, and the regression report looks identical either way.

13. Review Item 9 — Does "Somebody Was Served" Prove Anything?

Under review. Every fairness, arbitration and quality-of-service check.

Evidence at risk. The claim that every requester makes progress.

Where it lives. The quantifier. Some requester was served, or every requester was.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 9 - a fairness monitor that only checks that SOMEBODY was served.
//
// "Every cycle, a requester was granted" is true of a fixed-priority arbiter
// serving requester 0 forever while 1 and 2 starve. Throughput is perfect,
// utilisation is perfect, and two of three requesters never move.
//
//   BAD  : raise unless (any grant this cycle)
//   GOOD : age each waiting requester and raise when any age exceeds a bound
//
// TEACHING MODEL. Sequential.
//   Safety   : no requester waits longer than the starvation bound.
//   Liveness : every requester is eventually served - ASSUMING the arbiter
//              rotates. Fixed priority withdraws the assumption.
module fairness_monitor #(parameter int ANY_SERVICE_COUNTS = 0) (
  input  logic clk, rst_n,
  input  logic [2:0] req,
  input  logic [7:0] starve_limit,
  output logic [1:0] served_id,
  output logic       any_served,
  output logic [7:0] waited0, waited1, waited2, max_waited, n_starved,
  output logic       starved, reported,
  output logic       fair_err
);
  logic [7:0] w0, w1, w2;
  logic [1:0] sid;
  logic       srv;

  // A fixed-priority arbiter. Requester 0 wins whenever it asks.
  assign srv       = (req != 3'd0);
  assign sid       = req[0] ? 2'd0 : (req[1] ? 2'd1 : 2'd2);
  assign served_id = sid;
  assign any_served= srv;

  assign waited0 = w0;
  assign waited1 = w1;
  assign waited2 = w2;
  assign max_waited = (w0 >= w1) ? ((w0 >= w2) ? w0 : w2)
                                 : ((w1 >= w2) ? w1 : w2);
  // The truth, computed the same way in both builds.
  assign starved = (starve_limit != 8'd0) && (max_waited >= starve_limit);
  // The whole review point: what the monitor under review actually reports.
  assign reported = (ANY_SERVICE_COUNTS != 0) ? !any_served : starved;
  // SAFETY VIOLATION: a requester is starving and the monitor reports nothing.
  assign fair_err = starved && !reported;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      w0 <= 8'd0; w1 <= 8'd0; w2 <= 8'd0; n_starved <= 8'd0;
    end else begin
      if (req[0] && (sid != 2'd0) && (w0 != 8'hFF)) w0 <= w0 + 8'd1;
      else if (!req[0] || (sid == 2'd0))            w0 <= 8'd0;
      if (req[1] && (sid != 2'd1) && (w1 != 8'hFF)) w1 <= w1 + 8'd1;
      else if (!req[1] || (sid == 2'd1))            w1 <= 8'd0;
      if (req[2] && (sid != 2'd2) && (w2 != 8'hFF)) w2 <= w2 + 8'd1;
      else if (!req[2] || (sid == 2'd2))            w2 <= 8'd0;
      if (starved) n_starved <= n_starved + 8'd1;
    end
  end
endmodule

The measurement. Three requesters asking continuously, a fixed-priority arbiter, and a starvation bound of 4:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fixed priority, all asking : served=0 w0=0 w1=4 w2=4

Requester 0 wins every cycle. Throughput is perfect. Utilisation is perfect. Somebody was served on every single cycle, which is exactly what the lenient monitor checks, and it reports nothing while two of three requesters sit at the starvation bound.

The lenient monitor also produces a false positive, which the run asserts: on a completely idle bus, with nobody asking, it raises — because nobody was served. A monitor that is silent when it should speak and speaks when it should be silent is not half right. It is measuring a different quantity.

Evidence to demand. Per-requester wait ages, and the bound each is checked against. A single aggregate number cannot express a fairness property.

What escapes. Starvation, which does not corrupt anything and does not hang anything and simply means one agent never gets its turn — a class of failure that reaches customers because every aggregate metric looks healthy.

The withdrawal again. With the starvation limit at zero the claim is withdrawn: the longest wait keeps growing, the monitor reports nothing, and no violation is recorded, correctly, because no claim was being made.

Telemetry. Per-requester wait distributions, and an explicit starvation counter per requester. The aggregate is the one number that cannot show this.

14. The Review Assembled

Nine dimensions, one summary — and the same trap 30.1 and 30.2 found at their own levels.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RTL 10 - a verification review assembled. Nine review dimensions, one
// summary. "The testbench passes" is bit 0: a green regression, and one sixth
// of a review.
module dv_review_signoff #(parameter int PASSING_IS_PROOF = 0) (
  input  logic clk, rst_n,
  input  logic        review,
  input  logic        tb_passes, oracle_independent, checks_reachable,
  input  logic        unknowns_rejected, identity_qualified, baseline_green,
  output logic [5:0]  fail_mask,
  output logic [15:0] conditions_met, sound_pct,
  output logic        sound,
  output logic [7:0]  n_reviews, n_sound, n_claimed,
  output logic        rev_err
);
  logic [31:0] s_q;
  logic        truly_sound, claimed;
  assign fail_mask[0] = ~tb_passes;
  assign fail_mask[1] = ~oracle_independent;
  assign fail_mask[2] = ~checks_reachable;
  assign fail_mask[3] = ~unknowns_rejected;
  assign fail_mask[4] = ~identity_qualified;
  assign fail_mask[5] = ~baseline_green;
  assign conditions_met = {15'd0, tb_passes} + {15'd0, oracle_independent}
                        + {15'd0, checks_reachable} + {15'd0, unknowns_rejected}
                        + {15'd0, identity_qualified} + {15'd0, baseline_green};
  assign s_q = ({16'd0, conditions_met} * 32'd100) / 32'd6;
  // No clamp: six one-bit values over six cannot exceed a hundred.
  assign sound_pct = s_q[15:0];
  assign truly_sound = (fail_mask == 6'd0);
  assign claimed = (PASSING_IS_PROOF != 0) ? tb_passes : truly_sound;
  assign sound = claimed;
  assign rev_err = review && !truly_sound && claimed;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      n_reviews <= 8'd0; n_sound <= 8'd0; n_claimed <= 8'd0;
    end else if (review) begin
      n_reviews <= n_reviews + 8'd1;
      if (truly_sound) n_sound <= n_sound + 8'd1;
      if (claimed)     n_claimed <= n_claimed + 8'd1;
    end
  end
endmodule

The measurement. Two views of the same verification environment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
oracle copies the design : mask=000010 met=5 sound=83%
a green regression and nothing else : mask=111110 met=1 sound=16%

The first line is a real review with one finding open. Five of six conditions met, one bit set — the oracle is not independent. That single bit invalidates the regression that satisfies bit 0, which is why it is the one worth finding.

The second line is what this module exists to prevent. The testbench passes. A regression ran, it was green, and not one of the other five conditions was established. Sixteen percent of a review, reported as a review.

A flowchart for a verification review. The regression passes, then the oracle is independent, every check has evaluated, unknowns are rejected, identities are era-qualified, and the mutation baseline was green. Any failure ends in a review that is not sound; passing all six ends in a sound review.yesyesyesyesyesregression greenoracleindependent?every checkevaluated?unknownsrejected?identitiesera-qualified?mutationbaselinegreen?review soundany no: green, notproven
Figure 4 — the verification review as a flow. The first decision is the weak one and the only one many reviews reach: the regression is green. The five below it are ordered by how much of the environment each carries — oracle independence first, because an oracle that cannot disagree makes every other check below it decorative, then reachability and unknown handling, then identity qualification, and finally the campaign-validity question that decides whether the mutation score means anything at all.

15. Quantitative Reasoning

Every figure here is a teaching parameter or a value derived from one and asserted by the testbench. None is a measurement of a real system, and none is a CXL specification number.

The escape rate of a copying oracle is one hundred percent. Four evaluations, one with a defect injected, and the copying environment flags zero of them. There is no partial credit available: an oracle that reads the design agrees with every defect, not most of them.

The reach ratio is the number this chapter exists to make people compute. Four transactions eligible and four checks run is 400 / 4 = 100 percent. Four eligible and zero run is 0 percent — and both report zero failures. When the guard is later armed for two of eight, 200 / 8 = 25 percent. A reviewer who does not compute this number cannot distinguish the three cases, because the regression log is identical in all of them.

The bound-check escape is the margin. A limit of 50,000 against a specified result of 2,000 leaves 49,000 — so 48,999 distinct wrong answers pass the bound check, and one passes the exact check. The ratio of escapes to the correct answer is the margin itself.

The era field wraps. A four-bit era admits sixteen values, so a response stale by exactly sixteen flushes aliases onto the current era and is accepted by a correct matcher. The run asserts the wrap. Widening to eight bits costs four flops per tracked identity and pushes the alias to 256 flushes, which is the storage decision behind the mechanism.

Scoreboard storage, derived. For N outstanding identities with an E-bit era and a V-bit valid marker, the outstanding table costs N × (E + V) bits. At N=64, E=4, V=1 that is 64 × 5 = 320 bits; at E=8 it is 64 × 9 = 576 bits. The era is four-fifths of the second figure, which is the honest price of the qualification.

The starvation arithmetic. Three requesters, fixed priority, all asking: requester 0 waits 0 and requesters 1 and 2 each reach the bound of 4 after four cycles. Two of three agents — sixty-seven percent of the requesters — are at the starvation bound on a bus running at 100 percent utilisation.

The timeout ratio is unbounded. The transaction-aged monitor reads 5 at five cycles stuck. The activity-reset monitor reads 0, and would read 0 at five thousand cycles stuck. The error is not a factor; the monitor's reading is independent of the quantity it claims to measure.

An eight-bit age saturates at 255 rather than wrapping, and the run drives 262 cycles to prove it. A wrapping age counter would report a small number for a very old transaction, which is the same failure as section 11's truncation one chapter earlier.

The sign-off arithmetic. Six conditions; five met is 5 × 100 / 6 = 83 percent under integer division, and one met is 100 / 6 = 16 percent.

16. Verification Method

This chapter's subject is its own method, which makes the section below a worked example rather than a preamble.

Order of work

compile → inspect warnings → legal baseline → reset → boundaries → simultaneous events → abuse and error cases → configuration contrasts → structural gates → PASS → mutation campaign

The structural gates run before the campaign, not after. In this chapter they found three classes of gap the campaign would otherwise have surfaced one mutation at a time: 33 unasserted output nets, one even counter split, and two displayed-but-unasserted values. Section 19 records all three.

Independent oracles

Expected values are reasoned from the specification of the model, never copied from its implementation — which is section 5's review item, applied to this chapter's own testbenches.

ModelOracle
oracle independencespec says larger operand plus one; 100 and 40 → 101; with the defect → 100
unknown vacuity!= on an X yields X; if takes the false branch; !== yields 1
reachabilityfour samples with the arm low → four eligible, zero run, 0 percent
pulse observationone pulse outside the look window → sticky sees 1, look-only sees 0
response identityissue in era 0, flush to era 1, re-issue; a response stamped 0 is dead
duplicate and conservation1 issued, 1 completed, 0 outstanding; a second response is a duplicate
exact versus boundspec says twice the operand; 1000 → 2000; the defect gives 1000
timeout authoritylimit 5, five stuck cycles, oldest age 5, the monitor must fire
fairnessfixed priority, all asking → requester 0 waits 0, the others reach 4
sign-offfive of six → 83 percent; one of six → 16 percent

chkv prints got against expected, which is what lets a wrong oracle be wrong out loud. In this chapter it caught one, recorded in section 19: the design was right and my expectation was wrong.

X and Z rejected explicitly

chk(c, …) tests c !== 1'b1, so an X-valued condition fails rather than passing. chkv(got, exp, …) reduces the result and reports an explicit X/Z failure before comparing.

This is section 6's review item applied to the harness that checks section 6. A testbench that teaches about unknown-value vacuity and then uses == in its own checks would be the purest possible instance of the defect.

Pulses are latched, never sampled

Every safety output — orc_err, vac_err, rch_err, obs_err, id_err, dup_err, exa_err, tmo_err, fair_err, rev_err — is caught by a continuous always @(posedge clk) monitor into a sticky bit, asserted outside any conditional.

And the window must contain a clock edge. This chapter's first baseline run failed on exactly that, twice, and section 19 records it as a testbench defect. Section 8's review item, met on the way past.

Stimulus never lands on the active edge

step_clk is @(posedge clk); #1;. Every stimulus change lands one delta after the edge, never on it. Driving an input in the same delta as the design's sampling edge is a race whose outcome depends on simulator scheduling rather than on the design, and a testbench with that property produces results that change when the simulator is upgraded.

Both builds are always instantiated

Every model has both its measured and lenient checker wired to the same stimulus and contrasted in the same simulation.

Safety, liveness and performance kept apart

Safety — the environment can disagree with the design; no unknown passes a comparison; no check reports a pass it did not make; no stale or duplicate answer is counted. None requires an assumption.

Liveness — a stuck transaction is eventually reported, assuming the timeout limit is non-zero; every requester is eventually served, assuming the arbiter rotates. The model proves both withdrawals: at limit zero the transaction ages forever and nothing fires, and at starvation limit zero the waits grow and nothing is reported — and no safety property is violated in either case.

Performance — a four-deep identity space, a starvation bound of 4, a timeout of 5. These are targets.

17. Assertions

The testbenches carry 393 checks177 across the first five models and 216 across the last five.

Every output of every model is asserted as a value, in both builds. The output-listing gate reports zero unasserted output nets across the ten models — after closing 33 of them, which section 19 records.

Every simulator-derived value printed to the reader is asserted. The displayed-value gate scans 43 printed references and reports zero unasserted derived values. Two printed names are testbench-driven inputs and are reported apart.

Reset is verified with live state, not only at time zero: with an evaluation in progress and a defect live, with transactions eligible, with a round in flight, with an identity outstanding, and with reviews recorded.

Simultaneous events are driven: a pulse with a clear, a flush with an issue, a start with a finish, a round start with an acknowledgement, and an issue with a retirement of the very same identity — the case that found the defect in section 19.

Abuse cases are driven and asserted to be no-ops or refusals: an unknown with no check running, an arm with nothing sampled, a defect live with no evaluation, a response for an unissued identity while a different identity is outstanding, a response for an identity that was never issued, a finish with nothing active, and a fairness check with nobody requesting.

Boundaries are driven at the edge and one step past it: an era at its wrap point, an age counter at saturation, a wait counter at saturation, a starvation limit of zero, a timeout limit of zero, zero eligible transactions, and equal operands where the maximum is unambiguous.

Configuration contrasts are explicit. Every model instantiates both builds and asserts the observable differs — which is section 13 of 30.2 applied here, and it is the discipline most environments lack.

18. Mutation Testing

96 mutations attempted. 1 withdrawn as equivalent. 95 non-equivalent mutations injected, 95 killed. Zero unexplained survivors.

Reported separatelyCount
Mutants attempted96
Withdrawn as equivalent1
Non-equivalent mutants95
Killed95
Unexplained survivors0

By model:

ModelDimensionMutations
m1oracle independence9
m2unknown-value vacuity9
m3checker reachability9
m4pulse observation8
m5response identity and era10
m6duplicates and conservation9
m7exact versus bound9
m8timeout authority10
m9fairness and starvation9
m10review sign-off13

Five survivors on the first run, every one classified before anything was changed.

Survivor 1 — missing checker

m3, the zero-eligible case: (n_eligible == 0) ? 32'd0 changed to ? 32'd100. The separating question has an answer — after reset nothing is eligible and the reach would read 100 instead of 0. It survived because the eligibility counter was asserted after reset and the reach ratio was not. Checker added.

Survivor 2 — stimulus gap, with the right shape and the wrong state

m5, the liveness lookup: out_q[resp_tag] changed to out_q[issue_tag]. The separating question has an answer — a response bearing an identity that was never issued would be accepted whenever the issue identity happened to be outstanding.

It survived because no stimulus ever presented a response identity different from the issue identity while the issue identity was outstanding. The testbench did have an abuse case using a different response tag — and it ran at a moment when both bits were clear, so the two expressions agreed.

An abuse case with the right shape and the wrong state is the failure mode that makes abuse testing feel finished when it is not. The case was moved to a point where a different identity is genuinely live.

Survivor 3 — the equivalent mutant, and why

m6, the conservation equation: n_issued == (n_completed + out_q) changed to >=.

The separating question has no answer. The outstanding counter increments only alongside the issue tally and decrements only alongside the completion tally, and its decrement is clamped at zero — so completed + outstanding >= issued holds in every reachable state. The mutated >= is therefore true exactly when == is true. Withdrawn. Not counted as a kill.

This is a different species from 30.2's equivalent mutant. That one was equivalent because of Verilog's width rules — a property of the language. This one is equivalent because of a structural invariant of the design: one counter can only ever be on one side of the inequality. Proving it requires reasoning about the reachable state space, not about the line.

A mutation that cannot be killed is sometimes telling you the design is tighter than the expression looks, and the correct response is a written proof of the invariant, not another assertion bolted on until the number goes up.

Survivor 4 — this chapter's own lesson, turned on its author

m9, the starvation counter inverted. The separating question has an answer — the inverted counter reaches 4 where the correct one reaches 1.

It survived because the check was written n_starved >= 1a bound, on a counter whose exact value was known. Both the correct counter and the inverted one satisfy it.

Section 11 of this chapter is about the difference between a bound and an exact value. The checker that let this live was written by the person writing that section, for the reason that section names: the exact number needed a hand trace and the bound did not. Replaced with the exact value, hand-traced to 1.

Survivor 5 — stimulus gap, and a family of them

m10, mask bit 3: ~unknowns_rejected changed to ~identity_qualified. The separating question has an answer — any state where those two conditions differ.

It survived because the stimulus only ever drove the six sign-off conditions all-high or all-low, so no two of them ever differed. That is a gap in a whole family, not one mutation: any mask bit reading any other condition would have survived the same way.

The fix is a sweep, not an assertion. Each condition is now lowered alone, and the mask is asserted to have exactly that bit set — six cases that kill the entire family at once.

Campaign validity

Every campaign in this chapter ran against a green baseline, and the baseline was re-run after every testbench and checker modification before the campaign was re-run. This is the rule 30.2 established the hard way, where a broken baseline returned 45 of 45 killed — a better-looking number than the honest campaign returned.

Mutation testing is invalid unless the unmutated baseline passes first. A failing baseline fails every mutation for the same reason it fails unmutated, and every one is recorded as a kill it did not earn. A higher kill count is not automatically stronger evidence.

Survivor classification comes before any fix

Never add an assertion for a survivor before classifying it. Adding one first destroys the evidence that would have told you which class it was.

ClassMeans, and what to do
Equivalentno input tells the two apart — withdraw it, never count a kill
Stimulus gapthe case is never driven — extend the stimulus
Missing checkerthe case is driven and nothing looks — add the checker
Vacuous checkerthe check cannot fail — fix the check, not the design
Unreachableits guard never holds — fix the guard
Maskedanother mechanism hides it — expose it, or say why you cannot
Coincidentalthe arithmetic happens to agree — change the stimulus
Missing configthe build that differs is never built — instantiate it
Otheranything else — state it precisely

Three of this chapter's five survivors were gaps in the stimulus or the checker, one was the environment's own bound-check defect, and one was equivalent. None was a design fault. That distribution is normal and it is the point: a mutation campaign on a correct design measures the environment, which is this chapter's subject.

19. Baseline Defects Found Before Mutation

What the first baseline run found, and what it missed

The ten teaching models compiled and ran clean on their first attempt, and that was recorded at the time as "no RTL defects". It was true of the stimulus and false of the models, and the two defects above are the correction. The sequence is worth keeping: a green first run is evidence about the tests that existed, not about the design.

RTL defects — two, both found in the final adversarial review

The earlier statement that this chapter's models were defect-free was wrong, and the correction is recorded here rather than quietly folded away.

Defect 1 — an issue and an accepted response for the same identity. The outstanding set was written by two independent statements: a bit-set under the issue and a bit-clear under the retirement. They name different indices — until the indices are equal, at which point the retirement came last and cleared a tag that had just been issued, leaking it from the outstanding set.

Defect 2 — an issue and a retirement of the same identity, in the conservation model, with exactly the same shape and the same consequence.

Fix, in both. One assignment computed from all events, with the priority written down: a flush clears everything, and otherwise the issue wins its own identity, because the issue is the newer fact.

Verification. Both simultaneous cases are now driven and asserted, and a mutation that swaps either priority is killed. The conservation equation is asserted to survive the case — 2 issued, 1 completed, 1 outstanding.

How it was found. Not by the stimulus, not by the campaign, and not by any of the structural gates. It was found by a static sweep of every model in the batch for a register written by two independent top-level statements — the exact shape this batch teaches — run as part of the final adversarial review. Five of the thirty models in the batch had it, and in every one the simultaneous same-target case was undriven and the priority unwritten.

The general lesson. A bit-set and a bit-clear on the same vector look independent because they usually name different indices. They stop being independent the moment the two indices are equal, and nothing in the source says whether that can happen. The rule from 30.2 section 7 — one assignment computed from all events, or a written priority — applies to indexed writes exactly as it does to whole registers.

Testbench defect — a report window with no clock edge in it

Symptom. Two checks failed on the first baseline run — the reachability monitor and the pulse-observation monitor both reported clean when they should have reported a violation.

Root cause. Both safety outputs are report-gated. The stimulus raised the report signal, checked the combinational output, and lowered it again inside a window containing no positive clock edge. The continuous sticky monitor samples on posedge clk, so it never latched anything.

Fix. The report window now contains a clock edge.

Why it matters. The monitor was correct, the sticky bit was correct, and the window it watched contained nothing to sample. This is the third appearance of this exact shape in the batch, and it is precisely why section 8 says a sticky observer is necessary and not sufficient.

Wrong oracle — one, mine

Symptom. chkv reported got 0, expected 1 twice on the stuck-cycle counter.

Root cause. The stuck condition is combinational and became true on the edge being examined. The counter is registered and moves on the next edge. I had expected a counter to reflect a condition that had existed for zero clocks.

The design was right and my expectation was wrong, and it was caught because chkv prints both numbers. A bare equality would have looked like a design failure, and the natural repair would have been to change the design.

Fix. The expectation is 0 at that instant and 1 one edge later, and both are now asserted — strictly more evidence than the original wrong check.

Coverage gaps found by the structural gates, before any mutation ran

GateFindingClosed by
outscan33 unasserted output nets across all ten models, mostly the lenient build's counterpart of an asserted netvalue assertions on every one, in both builds
splitcheck1 even counter split — one flagged of two evaluated, where an inverted counter reaches the same totala third evaluation, making the ratio one of four
displaycheck2 displayed values with no assertion — printed to the reader and never checkedassertions added

All three were found before the campaign and none by it. That ordering is the argument for running the structural gates first: each of these would otherwise have arrived as a survivor, one at a time, with the classification work repeated.

Compiler-warning findings

Under -Wall the ten models produce no truncation, select-width, signedness, cast, multiplication-width, shift-width or loop-width warnings. The only warning is a missing timescale on models containing no delay constructs at all — inspected and recorded as benign rather than silenced.

As in 30.2, the absence is recorded as a checked result rather than assumed. Compiler silence proves nothing on its own.

Simulator constraints

Icarus Verilog 13.0 rejects ref task arguments, carried forward from 29.5, 30.1 and 30.2. The check harness passes values rather than references as a result.

20. Coverage Reasoning

Functional coverage measures what the stimulus reached. It does not measure what the checkers examined, and this chapter contains four defects that a 100-percent-covered environment would still have.

QuestionWhat coverage answersWhat it does not
Did we drive this case?yes
Did a checker look at it?no
Could that checker have failed?no
Was the expected value independent?no

Three coverage models are worth adding to any environment reviewed with this chapter:

Checker-evaluation coverage. A bin per check, hit when that check evaluates — not when the stimulus that should trigger it is generated. The interesting report is the list of bins at zero, and it is usually shorter than people fear and never empty.

Contrast coverage. A bin per parameterised configuration, crossed with the observable that is supposed to differ. The bin a silent configuration cannot hit is the one that proves it is silent — which is 30.2 section 13 expressed as coverage.

Identity-era coverage. A cross of response era against current era. The off-diagonal bins are the stale cases. An environment that has never hit an off-diagonal bin has never tested the qualification, however many responses it has scored.

The bin the failing design cannot hit is the most valuable bin in any model. In section 5 it is "oracle disagreed". In section 7 it is "check evaluated". In section 12 it is "timeout fired while the fabric was busy". Each of those is unreachable in the broken environment and trivially reachable in the working one, which makes the coverage report a direct test of the review item.

21. Silicon Observability

Most of this chapter lives before tapeout. Four of its items leave something behind in silicon, and those are the ones worth building.

TelemetryWhat it exposes
unknowns observed, counted separately from mismatchesa checker that walks through X; a design that produces it
the conservation equation's four totalsduplicates and leaks, evaluable in the field
stale identities detected, and stale identities acceptedthe second must read permanently zero
oldest-outstanding age, as a distributiona liveness failure, which has no event to count
per-requester wait distributionsstarvation, which every aggregate metric hides
retries per transaction, as a distributionlivelock, which the retry total cannot distinguish
saturation counts on every age and wait countera counter reporting a small number for a very old thing

Two are "must be permanently zero" counters — stale identities accepted, and duplicate responses retired. Each costs almost nothing and captures an escape that would otherwise present as mis-scored work far from its cause.

The pattern to read on the unknown counter: a high count with no failures is a checker finding, not a design one. The design is producing X and the environment is not looking.

Oldest-outstanding age is the only evidence of a hang, because a liveness failure is the continued presence of something rather than an event. There is nothing to count; there is only an age that keeps growing.

Per-requester waits are the only evidence of starvation. Utilisation, throughput and aggregate latency are all healthy in a starving system, and that is the entire difficulty.

22. DebugLabs

Lab 1 — A regression has never failed and a bug reaches silicon

Symptom. A design bug in a thoroughly verified block reaches the lab. The regression covering that path has passed on every run for eight months.

Evidence. The scoreboard has a full transaction class and a complete coverage model. Its flagged count is zero — and has always been zero.

Hypothesis. The oracle cannot disagree.

Investigation. Follow the expected value back. It is computed by a function in a shared package, imported by both the design and the predictor.

Root cause. A copying oracle. Any change to the shared function changes both sides identically.

Fix. Reason the expected value from the specification, by a different route, in the environment's own code.

Prevention. Inject a defect on purpose and require a failure. An environment that has never failed has never been shown able to fail, and the experiment costs five minutes.

Silicon observability. None for this one. The environment's own flagged count is the telemetry, and a permanent zero is the signature.

Lab 2 — A block full of X passes every test

Symptom. A waveform review shows an output unknown for thousands of cycles. Every regression on that block is green.

Evidence. The scoreboard compares with == and reports no mismatches.

Hypothesis. The comparison is returning X and the if is taking the false branch.

Investigation. Force a known-wrong defined value and the checker fails immediately. Force an X and it passes.

Root cause. != instead of !==. Every check on every X cycle recorded a pass it never made.

Fix. Case comparison throughout, and an explicit unknown counter.

Prevention. An X-injection test that must fail. Reviewing the operator is not enough — the same escape appears as if (!ok) and as a ternary with an unknown condition.

Silicon observability. Count unknowns separately from mismatches. High unknowns with zero failures is a checker finding.

Lab 3 — A checker has never evaluated

Symptom. A protocol violation reaches the lab on a path that has a dedicated assertion.

Evidence. The assertion has never fired. Functional coverage on that path is 100 percent.

Hypothesis. The assertion is guarded by something that is never true.

Investigation. Add an evaluation counter beside the assertion. It reads zero across the whole regression.

Root cause. The assertion is gated on a mode the regression never enables.

Fix. Enable the mode, or remove the gate, and publish the evaluation count.

Prevention. Checker-evaluation coverage, reviewed as a list of bins at zero. Stimulus coverage cannot see this, and the two are routinely conflated.

Silicon observability. None. The gate is the evaluation count in regression.

Lab 4 — An error strobe is reported as never occurring

Symptom. A design asserts an error strobe in the lab. The environment's monitor reports zero occurrences across the whole regression.

Evidence. The monitor's condition is correct. The strobe is one cycle wide.

Hypothesis. The monitor is sampling rather than latching.

Investigation. The monitor writes its observation only while the environment's sampling task is active, which is a small fraction of the time.

Root cause. A look-only observer on a single-cycle event.

Fix. A sticky bit latched on every clock, cleared only by an explicit read.

Prevention. And then check the second half: does the window the monitor watches contain a clock edge? This chapter's own baseline failed on exactly that after the sticky bit was correct.

Silicon observability. Publish the occurrence count and the observation count separately. A design where they differ has a monitor problem.

Lab 5 — A scoreboard reports a completion for a transaction still in flight

Symptom. A scoreboard reports a transaction complete. The design is still waiting for its response. Later the real response arrives and is reported as an orphan.

Evidence. Both events carry the same identity. An error-recovery flush happened between them.

Hypothesis. A response from before the flush was matched after it.

Investigation. Instrument the response era. The accepted response carries the previous one.

Root cause. The matcher keys on the identity alone. The identity was reused after the flush, and the old response matched the new transaction.

Fix. An era stamp advanced by every flush, and a match rule requiring both.

Prevention. Drive a response from a previous era and require a refusal. One stale acceptance corrupts two later judgements — the real completion looks like a duplicate — which is why the symptom appears twice, far apart.

Silicon observability. Stale detected and stale accepted. The second must read permanently zero.

Lab 6 — Throughput counts exceed the work actually done

Symptom. A completion count exceeds the issue count. Nothing else is wrong and the system runs.

Evidence. The outstanding figure reads zero and looks healthy.

Hypothesis. Something is being retired twice.

Investigation. Evaluate issued == completed + outstanding continuously. It opens by one, once, and stays open.

Root cause. The retirement rule fires on any response, so a duplicate response retired a transaction that was already gone.

Fix. Retire only a response whose transaction is still outstanding; count the rest as duplicates.

Prevention. The conservation equation, evaluated every cycle. An end-of-test check finds nothing on a design that self-corrects, and the window where it is false is the window that matters.

Silicon observability. All four totals in hardware. The equation is evaluable in the field.

Lab 7 — A watchdog has never fired and the design hangs under load

Symptom. A hang reproduces under sustained load and never in regression. The watchdog does not fire, in the lab or anywhere.

Evidence. The fabric is extremely busy during the hang.

Hypothesis. The watchdog's age is being reset by traffic that is not the stuck transaction.

Investigation. Instrument the age. It reads zero throughout, while the oldest outstanding transaction is thousands of cycles old.

Root cause. The age resets on any activity, so a busy fabric keeps it at zero permanently.

Fix. Age the oldest outstanding entry; clear it only on that entry's own completion.

Prevention. Enumerate what clears the age. The only acceptable entry is this transaction's own completion.

Silicon observability. The oldest-outstanding age as a distribution. There is no event to count — a liveness failure is the continued presence of something old.

Lab 8 — Every metric is healthy and one requester never gets service

Symptom. One agent reports unusable latency. Utilisation is 100 percent, throughput is at specification, and aggregate latency is nominal.

Evidence. The fairness monitor reports nothing. Its check is that some requester was granted each cycle.

Hypothesis. A higher-priority requester is taking every slot.

Investigation. Add per-requester wait ages. Two of three sit at the bound continuously; one waits zero.

Root cause. A fixed-priority arbiter, and a monitor that checks the wrong quantifier — some requester served rather than every requester served.

Fix. Per-requester ages against a starvation bound, and an arbiter that rotates.

Prevention. A single aggregate number cannot express a fairness property. Any monitor that produces one number for three requesters is measuring something else.

Silicon observability. Per-requester wait distributions. Every aggregate metric is healthy in a starving system, which is the entire difficulty.

23. The Review, As A Working Checklist

AskAccept only
Where does the expected value come from?a path back to the specification that never touches the design
Has this environment ever failed?an injected defect and a recorded failure
What does this comparison do on an X?a case comparison, or a demonstration
How many times did this check evaluate?a published, asserted, non-zero count
Where is this pulse latched?a sticky bit on the clock — and a clock inside the window
Is stimulus driven on the active edge?a delay after it, stated as a convention
What is the complete matching key?identity and era
What happens on a second identical response?a duplicate count, and no second retirement
Is this a bound or an expected value?the exact value, unless the specification gives a bound
What clears the timeout's age?this transaction's own completion, and nothing else
Does every requester make progress, or some?per-requester ages against a bound
Was the mutation baseline green?the baseline run, before the campaign
Why did that mutant survive?a class, named, before any fix
A structural tool reported zero — did it read its input?independent confirmation

Every row is a question with a wrong answer that sounds fine. "The scoreboard checks it", "the regression is green", "that assertion covers it", "the watchdog would catch it", "somebody is always served", "we're at 100 percent coverage" — each of those ends a review, and each is in this chapter as a defect.

24. How This Appears In Real Engineering

The copying oracle is almost always an accident of reuse. Nobody writes exp = dut_out. They import a shared function, or reuse a model the designer wrote, and the sharing is a virtue everywhere except here.

The unknown-value escape survives because == is what people type. It is correct on every defined value, which is every value in every test that works, and the operator is reviewed as a style question rather than a correctness one.

Unreachable checks accumulate by mode. A check written for a configuration that is later descoped stays in the environment, contributing a zero to the failure count forever, and nothing distinguishes it from a check that runs.

The look-only monitor appears in environments that started as directed tests, where the environment genuinely knows when to look — and it stops being true the moment stimulus becomes random.

Tag-only matching survives because it works until the first flush. Environments that never model error recovery never exercise identity reuse across an era, and error recovery is usually the last thing modelled.

Bound checks are written under deadline. The exact value needs a hand trace; the bound needs a guess. This chapter's own campaign produced one, written by the author of the section warning against it.

Activity-reset watchdogs are written to stop false positives. Somebody saw the watchdog fire during a legitimate slow period, added a reset on activity to quieten it, and turned it off under exactly the conditions it exists for.

"Somebody was served" is the natural thing to write because it is one signal and one comparison, and fairness needs N ages and N comparisons.

Mutation campaigns get run against red baselines constantly, because the number that comes back is higher and nobody questions a rising score.

25. Common Misconceptions

"The regression is green, so the design is correct." The regression is green. That is bit 0, and it is worth one sixth of a review.

"We have 100 percent functional coverage." Coverage measures what the stimulus reached. It says nothing about whether a checker looked, whether that checker could fail, or whether the expected value was independent.

"The scoreboard checks that." Follow the expected value back. If it reaches the design, the scoreboard checks nothing.

"That assertion covers it." An assertion that has never evaluated reports the same number of failures as one that has. Ask for the evaluation count.

"== is fine, we don't have X in this design." If that were provable you would not need the checker. The X you do not have is the one the comparison is about to hide.

"The monitor is sticky, so it can't miss anything." It can miss anything that happens in a window containing no clock edge. This chapter's own baseline proved it, twice.

"The tag is unique." Across the outstanding set, yes. Across time, no — and a flush is exactly the event that makes those two statements differ.

"A bound is a check." It is. It is a check that passes for every wrong answer inside the margin, and the margin is usually enormous.

"The watchdog would have caught it." Not if a busy fabric resets its age. A watchdog that has never fired is either a design that never hangs or a watchdog that cannot, and the report is identical.

"Everybody gets served eventually." That is a liveness claim with an environmental assumption. Name the assumption or withdraw the claim.

"The mutation score went up, so the testbench improved." Not if the baseline was failing. A red baseline kills every mutation for free.

"A survivor means we need another assertion." A survivor means you do not yet know why it survived. Classify first — an equivalent mutant withdrawn is worth more than an assertion that proves nothing.

"The tool reported zero, so we're clean." Or the tool could not read its input. Four distinct times in this batch a structural tool has reported a confident zero on something it could not parse.

26. Interview And Design-Review Questions

Oracles and evidence

1. What makes a scoreboard evidence rather than decoration? That it reaches its expected value by a different route than the design. An oracle that recomputes the design's expression agrees with every defect.

2. How do you prove an environment can fail? Inject a defect and watch it fail. An environment that has never failed has never been shown able to.

3. A predictor imports a function the designer wrote. What is the finding? A copying oracle. Sharing is a virtue everywhere except in the path from specification to expected value.

4. The scoreboard's flagged count has been zero for six months. What does that tell you? Nothing about the design, and something worth investigating about the environment.

5. Why is a copying oracle worse than no scoreboard at all? No scoreboard is a known gap. A copying oracle is a gap that reports coverage, occupies review time, and is believed.

Vacuity and reachability

6. What does if (got != exp) do when got is X? Nothing. != returns X, if takes the false branch, and the check records a pass it never made.

7. Name three other forms of the same escape. if (!ok) with ok unknown; a ternary with an unknown condition; any boolean check that treats "not true" and "false" as equivalent.

8. Why is this defect worse than a missing check? Because the bugs it hides — uninitialised registers, unconnected ports, clock-domain escapes — are among the easiest to find by any other means.

9. How do you distinguish a check that passed from one that never ran? You cannot, from the regression log. You need a published evaluation count.

10. Coverage is at 100 percent and a checker has never evaluated. Is that contradictory? No. Coverage measures what the stimulus reached. The checker's guard is a separate condition.

11. What is the most useful report from a checker-evaluation coverage model? The list of bins at zero.

12. What is a vacuous check? One that cannot fail. The X-comparison is one form; a check nested inside a condition that never holds is another; a check placed one delta after the pulse it observes is a third.

Observation and sampling

13. Why does a monitor need more than the right condition? It needs a latch, and a clock edge inside the window it watches. This chapter's baseline had the first two and not the third.

14. What does a registered value read if you sample it in the active region of its clock edge? The value from before the edge. Non-blocking assignments update in the NBA region afterwards.

15. Is that a race? No — it is deterministic. The race is driving stimulus in the same delta as the design's sampling edge, where the outcome depends on simulator scheduling.

16. Why does a look-only monitor work in directed tests? Because a directed test knows when the event happens. It stops being true the moment stimulus becomes random.

17. What two counts prove a monitor is working? The occurrence count and the observation count, published separately. A difference is a monitor finding.

Identity and accounting

18. What does a tag identify, and what does it not? Which transaction. Not which era.

19. Give the event that makes those two differ. A flush, an error recovery, a mode change — anything that clears the outstanding set so identities can be reused.

20. A stale response is accepted. Name both consequences. The wrong transaction is retired, and the real completion will later look like a duplicate. One acceptance corrupts two judgements.

21. Which of the two stale counters must read zero? Accepted. Detected is a fabric property; accepted is an escape.

22. What single equation catches duplicate retirement? issued == completed + outstanding, evaluated continuously from independent lifetime totals.

23. Why continuously rather than at end of test? Because the window in which it is false is the window that matters, and a design that self-corrects reads clean at the end.

24. A duplicate counter increments correctly in the failing build. What went wrong? Detecting a duplicate and refusing to act on it are different things. The counter is the wrong thing to check.

Bounds, timeouts and fairness

25. What is the size of the escape in a bound check? The margin. A limit of 50,000 on a specified result of 2,000 lets 48,999 wrong answers through.

26. When is a bound the honest check? When the specification gives a bound — a maximum latency, a capacity, a rate. Then the bound is the expected value.

27. What question exposes a dishonest bound? Why is the exact value not available? "It is easier" is the answer that produces the defect.

28. What may clear a timeout's age counter? That transaction's own completion. Nothing else.

29. Why does an activity-reset watchdog fail exactly when needed? Because it is quiet only when the fabric is quiet, and a hang under load happens while the fabric is busy.

30. A watchdog has never fired in two years. What are the two explanations? The design never hangs, or the watchdog cannot fire. The regression report is identical.

31. What is the only evidence of a liveness failure? The continued presence of something old. There is no event to count, so the telemetry is an age distribution.

32. Why is "somebody was served" not a fairness check? It is true of a fixed-priority arbiter serving one requester forever while the others starve at 100 percent utilisation.

33. The lenient fairness monitor also produces a false positive. Where? On an idle bus, where nobody was served because nobody asked. It is measuring a different quantity, not measuring the right one badly.

34. What telemetry proves fairness? Per-requester wait distributions. Every aggregate metric is healthy in a starving system.

Campaign discipline

35. What single condition invalidates a mutation campaign? A failing baseline. Every mutation then fails for the same reason it fails unmutated.

36. Why is that error hard to notice? The number that comes back is higher. A rising kill rate reads as progress.

37. A mutant survives. What comes first? Classification. Adding an assertion first destroys the evidence that would have named the class.

38. Give two distinct reasons a mutant can be equivalent. A language rule — an operand widened by its assignment context regardless of the source. And a structural invariant — a counter that can only ever be on one side of an inequality.

39. What is the right response to a proven equivalent mutant? Withdraw it, and write down the invariant that makes it equivalent. Not another assertion.

40. Your stimulus drives six conditions only all-high or all-low, and a mask bit reads the wrong one. Which class, and what is the fix? Stimulus gap — and the fix is a sweep lowering each condition alone, which kills the whole family rather than one member.

41. An abuse case has the right shape and never separates a mutation. What happened? It ran in the wrong state. This chapter had exactly that: a response with a different identity, driven at a moment when every identity was free.

42. A structural checking tool reports zero. What must you confirm? That it read its input. Four distinct times in this batch a tool has reported a confident zero on something it could not parse.

43. How do you settle whether a tool's findings against published work are false positives? Inject the corresponding mutations and watch them die. Arguing is cheaper and proves nothing.

44. What does a mutation campaign on a correct design measure? The environment. Which is why this chapter's survivors were all environment defects and none was a design fault.

45. Why report attempted, equivalent, non-equivalent, killed and survivors separately? Because a single percentage lets an equivalent mutant be counted as a kill, and the kill rate is then an inflated number that nobody can decompose.

46. If you could keep three pieces of environment telemetry, which? Unknowns observed against mismatches flagged, the conservation equation's four totals, and the oldest-outstanding age. The first catches a vacuous checker, the second catches a whole class of accounting escape, and the third is the only evidence of a hang.

27. Exercises

1 — Environment review. You are handed a scoreboard whose expected value is produced by pkg_common::compute_result(a, b), and the design calls the same function. Write the review finding: name the evidence at risk, the experiment that proves your claim, what escapes, and the change you would require.

2 — Checker design. Write the reachability instrumentation for an assertion guarded by if (cfg_mode == AGGRESSIVE). Specify what you publish, what you assert about it, and what the regression report should show when the mode is never enabled.

3 — Mutation classification. A mutation changes if (age >= limit) to if (age > limit) in a timeout monitor and survives. Give the separating question, classify the survivor, and state whether you would change the stimulus, the checker, or neither — and why.

4 — Quantitative. A scoreboard tracks 128 outstanding identities. Compute the outstanding-table storage with a 4-bit era and an 8-bit era, state the aliasing window for each in flushes, and argue which you would choose given a worst-case flush rate of one per 2,000 cycles and a maximum response lifetime of 3,000 cycles.

5 — Waveform diagnosis. Using Figure 3, state what a matcher watching only the live-tags row would conclude about cycles 2 through 6, what the era row adds, which single cycle contains the safety violation, and what the second consequence of that acceptance will be, later in the run.

6 — Coverage planning. Define a checker-evaluation coverage model for an environment with twelve assertions, four of which are mode-gated. Specify the bins, the cross with configuration, and identify which bin a never-enabled mode can never hit.

7 — Campaign validity. You inherit a regression reporting a 100 percent mutation kill rate on 200 mutants. Describe, in order, the three checks you would run to establish whether that number means anything, what each would look like if the campaign were invalid, and what you would report if one mutant turned out to be equivalent.

8 — Bound versus exact. Find one bound check in an environment you own where the exact value is available. State why the bound was written, compute the size of the escape, and write the exact check that replaces it — including the hand trace that produces the expected value.

28. Summary

A passing testbench becomes credible evidence only after the verification environment itself has been challenged.

An oracle that reaches its answer by the design's own route agrees with every defect, and the regression it produces is a tautology with a coverage report attached.

== and != return X on an unknown, and an if treats X as false — so the checker records a pass it never made, on exactly the bugs that are easiest to find by any other means.

A check that never evaluated reports the same number of failures as one that passed. The only defence is a published, asserted evaluation count.

A single-cycle occurrence needs a sticky observer, and the window it watches needs a clock edge in it. Two of those three are usually reviewed.

A tag identifies which transaction, not which era — and one stale acceptance corrupts two later judgements, which is why the symptom appears twice and far apart.

A duplicate is detected by an equation computed from independent totals, evaluated continuously, because the window where it is false is the window that matters.

A bound is a check whose escape is exactly the size of the margin, and the margin is usually enormous.

A timeout that resets on any activity cannot fire while the fabric is busy, which is when transactions get stuck.

"Somebody was served" is true of a fixed-priority arbiter starving two of three requesters at full utilisation. Fairness needs per-requester ages, and every aggregate metric is healthy in a starving system.

Mutation testing is invalid unless the unmutated baseline passes first, and a higher kill count is stronger evidence only if the baseline it was measured against was green.

Classify every survivor before fixing anything. Equivalent mutants are withdrawn, never counted — and this chapter's equivalent came from a structural invariant of the design, which is a finding rather than a nuisance.

Six conditions, and "the testbench passes" is one of them. A real review with one finding open is 83 percent. A green regression and nothing else is 16.

Continue learning

Related tutorials

Standards & specifications

Governing standard
CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)

Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.

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 CXL curriculum.