Skip to content
VLSI Mentor

DDR · Module 27

DDR Assertions

662 of this curriculum's 843 named properties are implications, so 78.53% can pass by never arming. Built on the measured census, not on advice.

Chapter 27.1 treated the checking artefact as a primitive. Its rule registry records that an obligation is discharged “by an assertion” and says nothing about what makes an assertion adequate — whether it can fire, whether it fires for the right reason, or whether it is evaluated at all.

This chapter is about that, and it has an unusual amount of evidence to work with.

The DDR track has already written 843 named SystemVerilog properties, across 149 live chapters, over twenty-six modules. They were written to check protocol mechanisms, not to be studied — which makes them an honest sample of what assertion practice actually looks like when it grows alongside a design rather than being designed as a suite. §1 measures them, and the measurement produces a result worth the chapter:

662 of those 843 properties can pass without checking anything at all. Not because they are wrong, but because of their shape. §2 explains why, and §4 is about the only thing that prevents it.

1. The Corpus, Measured

MEASURED, by body structure. Percentages CURRICULUM-DERIVED from the counts and recomputed:

Structural featurePropertiesShare of 843
Overlapping implication |->53463.35%
Non-overlapping implication |=>12915.30%
disable iff reset guard76991.22%
$past history reference22426.57%
Edge function $rose / $fell / $stable / $changed516.05%
Structural $onehot / $countones414.86%
Cycle delay ##242.85%
inside set membership70.83%
Sequence operator throughout / within / intersect00.00%

Three of those rows are worth pausing on before the analysis.

The corpus is overwhelmingly implication-based. 534 plus 129 is 663 properties using an implication operator — and §2 shows that is exactly the shape that can pass without checking.

Reset discipline is near-universal but not universal. 769 of 843 carry disable iff. 74 do not, and §7 is about what that means.

And not one property uses a sequence operator. Zero throughout, zero within, zero intersect, across 843 properties. §9 is about why, and it is not an oversight.

2. Two Shapes, and Only One Can Be Vacuous

The single most important structural distinction in the corpus, and it partitions it cleanly.

An implication has an antecedent. A |-> B says if A holds, then B must. When A never holds, the property is satisfied — trivially, on every cycle, forever. The tool reports a pass.

An invariant has no antecedent. B alone says B must hold, evaluated on every cycle the property is enabled. There is no condition under which it can be satisfied without being checked.

MEASURED, partitioning the corpus by whether the body contains |-> or |=>:

ShapePropertiesShareCan pass without checking?
Implication66278.53%yes
Invariant18121.47%no
Total843100.00%

CURRICULUM-DERIVED: 662 + 181 = 843, exactly.

So 78.5% of this curriculum's assertions are exposed to vacuity and 21.5% are structurally immune. That is not a criticism of the 662 — an implication is the right shape for a conditional rule, and most protocol rules are conditional. It is a statement about where the risk lives, and it is concentrated.

3. What the Corpus's Own History Shows

The vacuity exposure is uniform; the mitigation is not, and its arrival is datable.

MEASURED, assertions and covers by module group:

ModulesAssertionsCoversCovers per assertion
1–1334300.00
14–19149740.50
20–263523250.92
Total8443990.47

CURRICULUM-DERIVED ratios, recomputed. Note 844 assert property statements against 843 named properties — one assertion is written inline without a named property.

Eighty-nine chapters contain no cover at all, and eighty-four of them are Modules 1 through 13 — every chapter in that range. Those thirteen modules wrote 343 assertions and zero covers, so every implication among them is vacuity-exposed with no instrument that would reveal it.

The practice changed at Module 14 and matured by Module 20. Covers per assertion goes 0.00 → 0.50 → 0.92, and the late modules write nearly as many covers as assertions.

That is the measured signature of a methodology being learned rather than designed. Nothing decided that Modules 1–13 needed no covers; the risk was not yet visible. And it is exactly 27.1 §6's argument arriving from a different direction — per-chapter checking is locally sound and globally silent, and the silence is where this kind of gap survives.

4. The Antecedent Is the Whole Problem

If 662 properties are exposed, the question is what makes one of them safe. It is not the consequent.

A property's checking value is bounded by how often its antecedent occurs. Three cases, and only the third is useful:

Antecedent occurrencesWhat the pass means
Zeronothing — the property was never evaluated
Oncethe consequent held in one situation
Many, variedthe consequent held across the antecedent's range

So an assertion has two coverage requirements and they are different. The consequent needs to be checked; the antecedent needs to be reached. A suite that satisfies the first without the second has an assertion that cannot fail.

And the antecedent's own structure matters. An antecedent like cmd_valid occurs constantly; one like cmd_valid && !bank_open && refresh_pending && init_complete may occur never. The more specific the rule, the more likely the antecedent is rare — which means the properties most worth having are the ones most likely to be vacuous.

That inversion is the reason this chapter exists. A trivial rule is easy to exercise and not worth checking; a subtle rule is worth checking and hard to exercise, and nothing in the assertion's text reveals which you have.

5. A Trip, Seen

Everything above is structural. Here is one property actually firing, so the antecedent, the window and the trip are visible in the same picture.

The rule, ILLUSTRATIVE: after an activate to a bank, a read to that bank must not be issued for three cycles. The real parameter and its name belong to Module 14; three is chosen here because it fits ten cycles.

An activate-to-read spacing property: one violating sequence and one legal one

10 cycles
Ten cycles of a command stream against a property requiring three cycles between an activate and a read to the same bank. At cycle one an activate to bank five is observed, which makes the property's antecedent true and opens a three-cycle window. At cycle three a read to bank five is issued, only two cycles after the activate, so the window is still open and the property trips — the violation output asserts for that cycle and the violation counter increments from zero to one. The window then closes with no further activity. At cycle six a second activate to bank five is observed, opening the window again, and this time the read is held until cycle nine, three cycles later, so the window has expired and no trip occurs. The counter remains at one, showing that the second sequence was legal.window open, violatedwindow open, violatedwindow open, respectedwindow open, respectedantecedent true: ACT observedantecedent true: ACTobservedRD at +2 cycles, rule needs +3RD at +2 cycles, rule needs+3RD at +3 cycles: legal, no tripRD at +3 cycles: legal, notripCKobs_cmd--ACT--RD----ACT----RDobs_bank--5--5----5----5antecedentwindowtripn_trips0001111111t0t1t2t3t4t5t6t7t8t9

Three things the trace makes visible that the property text does not.

The antecedent is a single cycle and the obligation outlives it. antecedent is high only at cycle 1, while window stays high through cycle 3. So the property is evaluating for longer than its trigger existed — which is why a cycle-by-cycle mental model of assertions misleads, and why 27.3 needs state rather than combinational decode.

The trip names a cycle, not a command. trip asserts at cycle 3, where the read was issued — not at cycle 1, where the activate that created the obligation was. A debug session reading only the trip cycle sees the victim and not the cause, and §17's first row is about that.

And the second sequence is the more valuable half of the trace. Cycles 6 through 9 exercise the antecedent and satisfy the consequent — which is what distinguishes a property that has been checked from one that has merely not failed. A waveform showing only the violation would demonstrate the trip and leave the vacuity question untouched.

6. Why the Cover Is Not Optional

§2's callout said the fix is a cover on the antecedent. It is worth being precise about what that cover does, because it is not a coverage-closure activity.

A cover on a forbidden condition converts a claim into a measurement.

Without the coverWith the cover
“the property passed”“the property passed and its antecedent occurred N times
Vacuity is a possibilityVacuity is excluded by evidence
A reviewer must trust the suiteA reviewer can read a number

And for a forbidden-transition property the cover must target the attempt, not the outcome. Chapter 24.2 §15 makes this explicit: the covers it adds are for err_refresh_with_bank_open, err_sre_with_bank_open and err_cmd_during_self_refreshthe error flags, meaning the testbench must actually try the illegal thing.

That is a stronger requirement than it sounds. A suite verifying a correct design must deliberately drive illegal stimulus to prove its illegality checks work — which means the testbench needs a mode that a functional suite would consider a bug.

7. Reset Discipline, and the Seventy-Four

MEASURED: 769 of 843 properties carry disable iff; 74 do not.

What disable iff does is suspend evaluation while a condition holds — conventionally reset. What its absence means depends entirely on whether the property happens to be true during reset.

During reset, the property is…Without disable iff
true anywayno effect — the omission is invisible
falsefires on every reset, in every test
indeterminate (x)tool-dependent, often fires

So the 74 split into two populations and the measurement cannot tell them apart. Some are properties whose invariant genuinely holds through reset — a counter being zero, a flag being low — and for those the guard is redundant. Others are latent failures that have never been exercised because no test releases reset in a state that violates them.

That is a real and bounded gap in this curriculum's corpus, and stating it honestly matters more than closing it here: 74 properties out of 843, about 8.8%, have an unstated reset assumption. §16's DV table includes the check that would separate them, and §20's exercises ask you to run it.

And note the direction of the risk. An unguarded property that is false during reset fires loudly and immediately — it is found the first time anyone runs a test. The dangerous case is the property that is accidentally true during reset, because it works today and will fire the day someone changes the reset state, in a module nobody associated with the assertion.

8. History Dependence Is the Norm

This is where DDR properties differ structurally from bus properties, and it is measurable.

MEASURED: 242 of 843 properties — 28.71% — reference $past or a cycle delay ##.

More than a quarter of the corpus checks a property of accumulated history rather than of a single cycle. And the real figure is higher: a property whose antecedent is a state bit — bank_open, init_complete, ratio_valid — is history-dependent without using either operator, because the state was accumulated by earlier cycles.

That is the structural reason a DDR assertion cannot be written from the pin list alone. A handshake property over valid and ready is a statement about two signals in one cycle. A DDR property over “a read to an open row” is a statement about every command since reset, and the thing that makes it checkable is a model of accumulated state.

Property depends onExample shapeWhat the checker needs
One cycle`valid-> !error`
Adjacent cycles$past(cmd,1)one register
Accumulated statebank_open[b]a model27.3

The third row is why 27.3 is a separate chapter rather than a section here. A bindable property set can reference state the DUT already computes; a passive monitor has to reconstruct it, and the two are different engineering problems with different failure modes.

9. What the Corpus Does Not Use, and Why

MEASURED: zero of 843 properties use throughout, within or intersect. Only 24 use a cycle delay ## at all — 2.85%.

So the corpus is almost entirely implication-plus-state rather than sequence-based. That is a real characteristic and it has two readings.

The charitable reading, which is mostly right: DDR rules are predominantly statements about state and spacing, and state is more directly expressed by a registered model plus an implication than by a sequence. bank_open[b] |-> … is clearer than a sequence describing every path by which a bank became open.

The critical reading, which is also right: a sequence operator expresses “this must hold for the whole interval” more directly than a per-cycle implication does, and its absence means those obligations are expressed as several properties instead of one. More properties is more surface for the vacuity problem §2 measures.

This chapter does not claim the corpus should have used them. It records that it did not, that the choice was consistent across twenty-six modules and 843 properties, and that the consequence is a corpus whose risk profile is dominated by antecedent reachability rather than by sequence-matching subtleties. The SystemVerilog track owns what those operators do; what is MEASURED here is that this body of work chose not to.

10. Property Classes Worth Naming

Pulling §1 through §9 together into the classes a DDR property set should contain, with the corpus's own practice noted.

ClassShapeVacuity-exposed?Corpus practice
Forbidden transitionillegal_attempt |-> !happenedyes — the worst casethe highest-value class, per 24.2 §15
Invariantexpr aloneno181 of 843
Partitiona + b + c == totalnoused for exhaustive classification
Monotone / sticky$past(f) |-> fyes, weaklyused for peaks and records
Derivationout == f(params)nochecks published arithmetic
Spacingtrigger |-> ##[n:$] …yesrare here — only 24 use ##

The first and second rows are the design decision. A rule expressible as an invariant should be written as one, because it cannot be vacuous. A rule that is genuinely conditional must be an implication and must carry a cover.

The partition class deserves a note too, because it is quietly the most reusable. A property asserting that a set of counters sums to a total is an invariant, cannot be vacuous, and catches an entire family of bugs — a lost event, a double-counted one, a category nothing falls into. This curriculum used it 55 times by name, and it is the cheapest non-vacuous check available: if a design classifies anything into buckets, the buckets have a sum.

And the derivation class is the one most often omitted. Modules 25 and 26 used it heavily — 25.4 asserts that a published duration equals a row count times a per-row cost, and 26.2 asserts that a face count equals a grid squared. Those properties check that the design agrees with the chapter's own arithmetic, which is a class of bug — a prose table disagreeing with the block beside it — that nothing else catches.

11. The Property Set

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ddr_spacing_property_set -- one property of each class from §10,
// written over a bound command interface, with an antecedent-reach
// output for every implication so vacuity is measurable.
//
// CLASSIFICATION: educational, synthesisable, BINDABLE.
//
// WHY EVERY IMPLICATION PUBLISHES ITS ANTECEDENT: §4. A property's
// checking value is bounded by how often its antecedent occurs, and
// nothing in the property's text reveals that. Publishing the
// antecedent as a signal lets §13's monitor count it.
//
// WHAT IT DOES NOT MODEL:
//   - the device, the array, or any real timing parameter. SPACING is
//     ILLUSTRATIVE; Module 14 owns the real values and their names.
//   - electrical behaviour (Module 22), which no property here reaches
//
// WHAT IT WOULD MISS:
//   - any rule whose antecedent this testbench never creates (§2)
//   - any rule not expressible over the bound signals
// ---------------------------------------------------------------------
module ddr_spacing_property_set #(
  parameter int BANKS   = 16,
  parameter int BANK_W  = $clog2(BANKS),
  parameter int SPACING = 3,            // ILLUSTRATIVE (§5)
  // A COUNT of trips up to N needs clog2(N+1), not clog2(N).
  parameter int CNT_W   = 20
) (
  input  logic              clk,
  input  logic              rst_n,

  input  logic              cmd_valid,
  input  logic [2:0]        cmd_code,   // 0 NOP 1 ACT 2 RD 3 WR 4 PRE 5 REF
  input  logic [BANK_W-1:0] cmd_bank,
  input  logic [BANKS-1:0]  bank_open,
  input  logic              init_complete,

  // ── Antecedent-reach outputs, one per implication class. §4: these
  //    exist so a suite can prove the property was evaluated.
  output logic              ant_spacing,     // an ACT was observed
  output logic              ant_forbidden,   // an illegal access attempted
  output logic              ant_monotone,    // the counter advanced

  // ── Observable results.
  output logic              trip_spacing,
  output logic              trip_forbidden,
  output logic [CNT_W-1:0]  n_trips,
  output logic [BANK_W-1:0] last_act_bank,
  output logic [3:0]        cycles_since_act
);

  localparam logic [2:0] C_NOP = 3'd0, C_ACT = 3'd1, C_RD = 3'd2,
                         C_WR  = 3'd3, C_PRE = 3'd4, C_REF = 3'd5;

  initial begin
    if (BANKS < 2 || (BANKS & (BANKS - 1)) != 0)
      // The >= 2 bound matters: a power-of-two test ALONE accepts 0
      // and 1, and $clog2(1) is 0, making [BANK_W-1:0] illegal.
      $fatal(1, "ddr_spacing_property_set: BANKS must be a power of two >= 2");
    if (BANK_W != $clog2(BANKS))
      $fatal(1, "ddr_spacing_property_set: BANK_W must address BANKS");
    if (SPACING < 1)
      $fatal(1, "ddr_spacing_property_set: SPACING must be at least 1");
    if ((1 << 4) <= SPACING)
      // cycles_since_act must be able to represent the whole window,
      // or the spacing check saturates and stops firing -- silently.
      $fatal(1, "ddr_spacing_property_set: cycles_since_act too narrow for SPACING");
  end

  logic [BANK_W-1:0] act_bank_q;
  logic [3:0]        since_q;
  logic              have_act;
  logic [CNT_W-1:0]  trips_q;

  // Declared above the continuous assigns that read them.
  logic is_acc;
  assign is_acc = cmd_valid && ((cmd_code == C_RD) || (cmd_code == C_WR));

  logic same_bank_acc;
  assign same_bank_acc = is_acc && have_act && (cmd_bank == act_bank_q);

  // §5's rule: an access to the activated bank inside the window.
  logic spacing_violated;
  assign spacing_violated = same_bank_acc && (since_q < 4'(SPACING));

  // §10's forbidden-transition class: an access to a bank that is not
  // open at all.
  logic forbidden_now;
  assign forbidden_now = is_acc && !bank_open[cmd_bank];

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      act_bank_q <= '0; since_q <= '0; have_act <= 1'b0; trips_q <= '0;
    end else begin
      if (cmd_valid && (cmd_code == C_ACT)) begin
        act_bank_q <= cmd_bank;
        since_q    <= '0;
        have_act   <= 1'b1;
      end else if (have_act && (since_q != 4'hF)) begin
        since_q <= since_q + 4'd1;
      end
      // Compare the INCREMENTED trip count combinationally where it
      // is published, not here -- trips_q's write lands on this edge.
      if ((spacing_violated || forbidden_now) && (trips_q != {CNT_W{1'b1}}))
        trips_q <= trips_q + CNT_W'(1);
    end
  end

  assign ant_spacing      = cmd_valid && (cmd_code == C_ACT);
  assign ant_forbidden    = is_acc;
  assign ant_monotone     = spacing_violated || forbidden_now;
  assign trip_spacing     = spacing_violated;
  assign trip_forbidden   = forbidden_now;
  assign n_trips          = trips_q;
  assign last_act_bank    = act_bank_q;
  assign cycles_since_act = since_q;
endmodule

Four decisions in that block are this chapter's argument rather than implementation taste.

Every implication publishes its antecedent as a signal. ant_spacing, ant_forbidden and ant_monotone exist for no functional reason — the properties would work without them. They exist because §4 establishes that a property's checking value is bounded by how often its antecedent occurs, and nothing in the property's text reveals that. Publishing the antecedent makes the bound measurable from outside, which is what §12's harness and §14's covers both consume.

The spacing window is a counter, not a delay. A sequence-based formulation would express the rule as ACT ##[1:SPACING-1] access, and §9 records that the corpus uses no sequence operators at all. The counter form is chosen here for the same reason the corpus chose it: the obligation is about accumulated state — how long ago the activate was — and a registered count expresses that directly, at the cost of the extra property surface §9's critical reading identifies.

The elaboration guard on SPACING against the counter width is the subtle one. A SPACING of 16 or more would make cycles_since_act < 4'(SPACING) compare against a truncated value, so the window would appear to close early and the rule would silently stop firing. That is the worst failure direction for a checker: it does not error, it does not warn, it simply agrees with everything. §15's seventh row names it, and the guard is what makes it impossible rather than unlikely.

And n_trips counts cycles rather than rules. Two rules tripping on one command is one bad command, and a counter that advanced twice would make a single defect look like two — inflating a failure count that a triage process reads as severity. P5 asserts the distinction and the trip_spacing && trip_forbidden cover reaches it.

12. The Vacuity Harness

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// vacuity_harness -- per-property antecedent and firing counts, and a
// verdict on which properties were never evaluated at all.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY IT EXISTS: §2 measures that 662 of this curriculum's 843
// properties are implications, and an implication whose antecedent
// never occurs PASSES. The tool reports success; the harness reports
// whether success meant anything.
//
// WHAT IT DOES NOT MODEL:
//   - the properties, the design, or any protocol
//   - whether a property is CORRECT. A wrong property with a reached
//     antecedent is measured as well-exercised.
//
// WHAT IT WOULD MISS:
//   - antecedent VARIETY. §6's callout: 1 and 10,000 are both
//     "covered" and are not the same evidence. This counts
//     occurrences, not distinct situations, so a property reached
//     only by one repeated stimulus scores as well-exercised.
//   - a property nobody registered. The harness can report every
//     registered property as evaluated and be silent about the rule
//     that has no property at all -- 27.1 §7's structural limit.
// ---------------------------------------------------------------------
typedef enum int {
  PROP_INVARIANT   = 0,   // §2: no antecedent, cannot be vacuous
  PROP_IMPLICATION = 1    // §2: exposed
} prop_shape_e;

class prop_record extends uvm_object;
  `uvm_object_utils(prop_record)

  string       prop_name;
  prop_shape_e shape;
  bit          has_reset_guard;   // §7: 74 of 843 lack one
  int unsigned antecedent_hits;
  int unsigned fire_count;

  function new(string name = "prop_record");
    super.new(name);
    shape = PROP_IMPLICATION;
    has_reset_guard = 1;
  endfunction

  // §2: an invariant is never vacuous, whatever its counts. An
  // implication is vacuous exactly when its antecedent never
  // occurred. Conflating the two would report 181 of this
  // curriculum's properties as at-risk when they structurally are not.
  function bit is_vacuous();
    if (shape == PROP_INVARIANT) return 0;
    return (antecedent_hits == 0);
  endfunction

  // A property that fired more often than its antecedent was reached
  // is an accounting error, not a very effective property.
  function bit counts_consistent();
    if (shape == PROP_INVARIANT) return 1;
    return (fire_count <= antecedent_hits);
  endfunction
endclass


class vacuity_harness extends uvm_component;
  `uvm_component_utils(vacuity_harness)

  protected prop_record m_props[string];
  protected int         m_rejected;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function bit register(string prop_name, prop_shape_e shape,
                        bit has_reset_guard);
    prop_record r;
    if (prop_name == "") begin
      `uvm_error("VAC", "property registered with an empty name")
      m_rejected++;
      return 0;
    end
    if (m_props.exists(prop_name)) begin
      // Duplicate names would merge two properties' counts and make
      // both verdicts meaningless.
      `uvm_error("VAC", $sformatf("duplicate property name '%s'", prop_name))
      m_rejected++;
      return 0;
    end
    r = prop_record::type_id::create(prop_name);
    r.prop_name       = prop_name;
    r.shape           = shape;
    r.has_reset_guard = has_reset_guard;
    m_props[prop_name] = r;
    return 1;
  endfunction

  function void note_antecedent(string prop_name);
    if (!m_props.exists(prop_name)) begin
      `uvm_error("VAC", $sformatf("antecedent noted for unregistered '%s'", prop_name))
      return;
    end
    // An invariant has no antecedent to reach. Accepting one would
    // make its is_vacuous() verdict depend on a count that should
    // not exist.
    if (m_props[prop_name].shape == PROP_INVARIANT) begin
      `uvm_error("VAC", $sformatf("antecedent noted for invariant '%s'", prop_name))
      return;
    end
    m_props[prop_name].antecedent_hits++;
  endfunction

  function void note_fire(string prop_name);
    if (!m_props.exists(prop_name)) begin
      `uvm_error("VAC", $sformatf("fire noted for unregistered '%s'", prop_name))
      return;
    end
    m_props[prop_name].fire_count++;
  endfunction

  function int total();      return m_props.size(); endfunction
  function int rejected();   return m_rejected;     endfunction

  function int count_vacuous();
    int n = 0;
    foreach (m_props[k]) if (m_props[k].is_vacuous()) n++;
    return n;
  endfunction

  function int count_implications();
    int n = 0;
    foreach (m_props[k]) if (m_props[k].shape == PROP_IMPLICATION) n++;
    return n;
  endfunction

  function int count_invariants();
    int n = 0;
    foreach (m_props[k]) if (m_props[k].shape == PROP_INVARIANT) n++;
    return n;
  endfunction

  // §7: an unguarded property is a separate risk from a vacuous one,
  // reported separately because the mitigations differ.
  function int count_unguarded();
    int n = 0;
    foreach (m_props[k]) if (!m_props[k].has_reset_guard) n++;
    return n;
  endfunction

  function int count_inconsistent();
    int n = 0;
    foreach (m_props[k]) if (!m_props[k].counts_consistent()) n++;
    return n;
  endfunction

  // A suite's assertion result is SUPPORTABLE when no implication
  // went unevaluated and no counts are inconsistent. Note an
  // unguarded property does NOT make it unsupportable -- §7: some of
  // the 74 are legitimately guard-free, and the harness cannot tell
  // which, so it reports rather than blocks.
  function bit result_supportable();
    return (count_vacuous() == 0) && (count_inconsistent() == 0)
           && (m_rejected == 0);
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("VAC", $sformatf(
      "props=%0d impl=%0d inv=%0d vacuous=%0d unguarded=%0d inconsistent=%0d rejected=%0d supportable=%0b",
      total(), count_implications(), count_invariants(), count_vacuous(),
      count_unguarded(), count_inconsistent(), m_rejected,
      result_supportable()), UVM_LOW)
    foreach (m_props[k])
      if (m_props[k].is_vacuous())
        `uvm_error("VAC", $sformatf(
          "property '%s' PASSED VACUOUSLY -- antecedent never reached", k))
  endfunction
endclass

An invariant is never reported vacuous, whatever its counts. §2 establishes that 181 of this curriculum's 843 properties are structurally immune, and a harness that flagged them on a zero antecedent count would raise 181 false alarms and train its users to ignore it.

And an unguarded property does not make the result unsupportable. §7 establishes that the 74 split into legitimately guard-free properties and latent failures, and the harness cannot tell which — so it reports the count and leaves the judgement to a reviewer, rather than blocking on a condition it cannot adjudicate.

13. The Antecedent Variety Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// antecedent_variety_monitor -- how many DISTINCT situations reached
// each antecedent, rather than how many times.
//
// CLASSIFICATION: educational, SIMULATION-ONLY.
//
// WHY IT EXISTS: §12's harness cannot see variety, and §6's callout
// establishes that 1 and 10,000 are both "covered" while being very
// different evidence. A property reached only by one repeated
// stimulus is exercised in one situation, however many times.
//
// WHAT IT DOES NOT MODEL:
//   - the property, the design, or the protocol
//   - whether a situation MATTERS. All distinct keys count equally,
//     and a dimension that does not affect the rule inflates the
//     variety figure without improving the evidence.
//
// WHAT IT WOULD MISS:
//   - a situation dimension nobody recorded. Variety is measured over
//     the supplied keys, so ten distinct situations along an
//     unrecorded axis read as one.
// ---------------------------------------------------------------------
class antecedent_variety_monitor extends uvm_component;
  `uvm_component_utils(antecedent_variety_monitor)

  // per property: set of distinct situation keys seen
  protected bit m_seen[string][string];
  protected int m_hits[string];
  protected int m_min_variety_required;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    m_min_variety_required = 2;   // ILLUSTRATIVE default
  endfunction

  function void set_min_variety(int n);
    if (n < 1) begin
      `uvm_error("VAR", "minimum variety must be at least 1")
      return;
    end
    m_min_variety_required = n;
  endfunction

  // situation_key encodes the dimensions a reviewer considers
  // relevant -- for example "bank=5,open=1,init=1". The monitor does
  // not interpret it; it only distinguishes.
  function void note(string prop_name, string situation_key);
    if (prop_name == "" || situation_key == "") begin
      `uvm_error("VAR", "note() needs a property name and a situation key")
      return;
    end
    m_hits[prop_name]++;
    m_seen[prop_name][situation_key] = 1;
  endfunction

  function int hits(string prop_name);
    return m_hits.exists(prop_name) ? m_hits[prop_name] : 0;
  endfunction

  function int variety(string prop_name);
    return m_seen.exists(prop_name) ? m_seen[prop_name].num() : 0;
  endfunction

  // The distinction the chapter is about: reached often, in one way.
  function bit reached_but_monotonous(string prop_name);
    return (hits(prop_name) > 0)
        && (variety(prop_name) < m_min_variety_required);
  endfunction

  function int count_monotonous();
    int n = 0;
    foreach (m_hits[k]) if (reached_but_monotonous(k)) n++;
    return n;
  endfunction

  function void report_phase(uvm_phase phase);
    foreach (m_hits[k])
      `uvm_info("VAR", $sformatf(
        "property '%s': hits=%0d variety=%0d monotonous=%0b",
        k, hits(k), variety(k), reached_but_monotonous(k)), UVM_HIGH)
    `uvm_info("VAR", $sformatf("monotonous properties: %0d of %0d",
      count_monotonous(), m_hits.size()), UVM_LOW)
  endfunction
endclass

reached_but_monotonous is the chapter's sharpest measurement. A property whose antecedent was hit ten thousand times in one situation is not vacuous by §12's test and is barely exercised by any useful standard — and no tool reports it, because every conventional metric says “covered”.

14. What the Assertions Prove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind unit note: P1-P12 reference ddr_spacing_property_set's
// internals. P13-P20 are immediate assertions over the SIMULATION-ONLY
// harness and monitor, which are classes and carry no clock.

// ── Bindable, over the property set.

// P1 -- FORBIDDEN. An access inside the spacing window always trips.
// §5's rule, as the property the waveform shows firing.
property p_spacing_violation_trips;
  @(posedge clk) disable iff (!rst_n)
    (cmd_valid && ((cmd_code == 3'd2) || (cmd_code == 3'd3))
     && (cmd_bank == last_act_bank) && (cycles_since_act < 4'(SPACING)))
      |-> trip_spacing;
endproperty
assert property (p_spacing_violation_trips);

// P2 -- FORBIDDEN. An access at or beyond the window never trips the
// spacing rule. §5's second sequence -- the legal half of the trace.
property p_legal_spacing_never_trips;
  @(posedge clk) disable iff (!rst_n)
    (cmd_valid && ((cmd_code == 3'd2) || (cmd_code == 3'd3))
     && (cycles_since_act >= 4'(SPACING))) |-> !trip_spacing;
endproperty
assert property (p_legal_spacing_never_trips);

// P3 -- FORBIDDEN. An access to a bank that is not open always trips
// the forbidden rule. §10's highest-value class.
property p_forbidden_access_trips;
  @(posedge clk) disable iff (!rst_n)
    (cmd_valid && ((cmd_code == 3'd2) || (cmd_code == 3'd3))
     && !bank_open[cmd_bank]) |-> trip_forbidden;
endproperty
assert property (p_forbidden_access_trips);

// P4 -- INVARIANT (§10's second class, structurally non-vacuous).
// The trip count never decreases.
property p_trip_count_monotone;
  @(posedge clk) disable iff (!rst_n)
    (n_trips >= $past(n_trips, 1));
endproperty
assert property (p_trip_count_monotone);

// P5 -- the count advances exactly once per tripping cycle, however
// many rules tripped in it. Counting rules would inflate one bad
// command into several.
property p_count_counts_cycles;
  @(posedge clk) disable iff (!rst_n)
    ((trip_spacing || trip_forbidden) && (n_trips != {CNT_W{1'b1}}))
      |=> (n_trips == $past(n_trips, 1) + CNT_W'(1));
endproperty
assert property (p_count_counts_cycles);

// P6 -- INVARIANT. The spacing counter never exceeds its width's
// maximum, so the window comparison cannot wrap and stop firing.
property p_since_never_wraps;
  @(posedge clk) disable iff (!rst_n)
    (cycles_since_act <= 4'hF);
endproperty
assert property (p_since_never_wraps);

// P7 -- an activate resets the spacing counter on the next cycle.
// §5: the antecedent is one cycle and the obligation outlives it, so
// the counter is what carries the obligation forward.
property p_act_restarts_the_window;
  @(posedge clk) disable iff (!rst_n)
    (cmd_valid && (cmd_code == 3'd1)) |=> (cycles_since_act == '0);
endproperty
assert property (p_act_restarts_the_window);

// P8 -- the counter advances on every non-activate cycle once an
// activate has been seen. A counter that stalled would let a late
// access appear to be inside the window forever.
property p_window_advances;
  @(posedge clk) disable iff (!rst_n)
    (!(cmd_valid && (cmd_code == 3'd1)) && ($past(cycles_since_act,1) < 4'hF)
     && $past(ant_spacing,1) == 1'b0 && $past(rst_n,1))
      |-> (cycles_since_act >= $past(cycles_since_act, 1));
endproperty
assert property (p_window_advances);

// P9 -- §4's requirement, as a property: every implication publishes
// an antecedent signal, and it is exactly the trigger condition.
property p_spacing_antecedent_is_the_activate;
  @(posedge clk) disable iff (!rst_n)
    ant_spacing == (cmd_valid && (cmd_code == 3'd1));
endproperty
assert property (p_spacing_antecedent_is_the_activate);

// P10 -- likewise for the forbidden class's antecedent.
property p_forbidden_antecedent_is_the_access;
  @(posedge clk) disable iff (!rst_n)
    ant_forbidden == (cmd_valid && ((cmd_code == 3'd2) || (cmd_code == 3'd3)));
endproperty
assert property (p_forbidden_antecedent_is_the_access);

// P11 -- FORBIDDEN. No trip without a command. A property firing on
// an idle cycle would attribute a violation to no stimulus.
property p_no_trip_without_command;
  @(posedge clk) disable iff (!rst_n)
    !cmd_valid |-> (!trip_spacing && !trip_forbidden);
endproperty
assert property (p_no_trip_without_command);

// P12 -- FORBIDDEN. No spacing trip before any activate has been
// observed. The rule has no meaning until its antecedent has occurred
// at least once, and firing earlier would be a false positive.
property p_no_spacing_trip_before_first_act;
  @(posedge clk) disable iff (!rst_n)
    (!$past(ant_spacing, 1) && (n_trips == '0)) |-> !trip_spacing;
endproperty
assert property (p_no_spacing_trip_before_first_act);

// ── Cover. §6: a property forbidding something is vacuous until the
// testbench attempts it, so every forbidden condition is covered --
// and the ANTECEDENTS are covered separately from the outcomes.

// The two trips, actually reached.
cover property (@(posedge clk) disable iff (!rst_n) trip_spacing);
cover property (@(posedge clk) disable iff (!rst_n) trip_forbidden);
// The two ANTECEDENTS, separately -- §4: a consequent that held and
// an antecedent that was reached are different evidence.
cover property (@(posedge clk) disable iff (!rst_n) ant_spacing);
cover property (@(posedge clk) disable iff (!rst_n) ant_forbidden);
// The LEGAL half of §5's trace: antecedent reached, consequent
// satisfied. Without this a suite could trip on everything and pass.
cover property (@(posedge clk) disable iff (!rst_n)
                  ant_forbidden && !trip_spacing && !trip_forbidden);
// The window at each interesting boundary: just inside, exactly at,
// and beyond. The exactly-at case is the one an off-by-one breaks.
cover property (@(posedge clk) disable iff (!rst_n)
                  ant_forbidden && (cycles_since_act == 4'(SPACING - 1)));
cover property (@(posedge clk) disable iff (!rst_n)
                  ant_forbidden && (cycles_since_act == 4'(SPACING)));
cover property (@(posedge clk) disable iff (!rst_n)
                  ant_forbidden && (cycles_since_act > 4'(SPACING)));
// Both rules tripping in one cycle -- the case P5 distinguishes.
cover property (@(posedge clk) disable iff (!rst_n) trip_spacing && trip_forbidden);
// An activate restarting a window that was already open.
cover property (@(posedge clk) disable iff (!rst_n)
                  ant_spacing && (cycles_since_act < 4'(SPACING)));
// Commands before and after initialisation, and both bank extremes.
cover property (@(posedge clk) disable iff (!rst_n) cmd_valid && !init_complete);
cover property (@(posedge clk) disable iff (!rst_n) cmd_valid && (cmd_bank == '0));
cover property (@(posedge clk) disable iff (!rst_n)
                  cmd_valid && (cmd_bank == BANK_W'(BANKS - 1)));

The harness and monitor are classes with no clock, so their invariants are immediate assertions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Immediate assertions over the harness and the
// variety monitor -- the class equivalents of P1-P12.
task automatic check_assertion_infrastructure(vacuity_harness vh,
                                              antecedent_variety_monitor avm);
  // P13 -- FORBIDDEN. An invariant is never reported vacuous. §2:
  // 181 of this curriculum's 843 properties are structurally immune,
  // and flagging them would produce 181 false alarms.
  assert (vh.count_vacuous() <= vh.count_implications())
    else $error("P13: %0d vacuous exceeds %0d implications -- an invariant was flagged",
                vh.count_vacuous(), vh.count_implications());

  // P14 -- the shape classes partition the registered properties.
  assert (vh.count_implications() + vh.count_invariants() == vh.total())
    else $error("P14: shapes do not partition %0d properties", vh.total());

  // P15 -- FORBIDDEN. A result is never supportable with a vacuous
  // property. §2: a passing implication whose antecedent never
  // occurred is the single most common false success.
  assert (!(vh.result_supportable() && (vh.count_vacuous() > 0)))
    else $error("P15: result supportable with %0d vacuous properties", vh.count_vacuous());

  // P16 -- FORBIDDEN. A result is never supportable with inconsistent
  // counts. Firing more often than the antecedent was reached is an
  // accounting error, not an effective property.
  assert (!(vh.result_supportable() && (vh.count_inconsistent() > 0)))
    else $error("P16: result supportable with %0d inconsistent properties",
                vh.count_inconsistent());

  // P17 -- §7: an unguarded property does NOT block the result. The
  // harness cannot tell a legitimately guard-free property from a
  // latent failure, so it reports rather than adjudicates.
  assert (vh.count_unguarded() >= 0)
    else $error("P17: unguarded count is not reportable");

  // P18 -- a rejected registration never enters the totals.
  assert (!(vh.result_supportable() && (vh.rejected() > 0)))
    else $error("P18: result supportable with %0d rejected registrations", vh.rejected());

  // P19 -- variety never exceeds hits. A distinct-situation count
  // above the occurrence count would be impossible.
  assert (avm.variety("p_forbidden_access_trips")
          <= avm.hits("p_forbidden_access_trips"))
    else $error("P19: variety exceeds hits -- distinct situations cannot outnumber occurrences");

  // P20 -- §13's distinction: a property can be reached and
  // monotonous at once, and that combination must be reportable
  // rather than collapsed into "covered".
  assert (!(avm.reached_but_monotonous("p_forbidden_access_trips")
            && (avm.hits("p_forbidden_access_trips") == 0)))
    else $error("P20: monotonous reported for a property never reached");
endtask

15. Corner Cases

CaseBehaviourWhy
Access at exactly SPACING cyclesNo trip§5's second sequence; the boundary an off-by-one breaks
Access at SPACING − 1TripsThe covered boundary on the other side
Access before any activateNo spacing tripP12 — the rule has no meaning yet
Activate during an open windowWindow restartsCovered; the obligation is per-activate
Both rules tripping in one cycleCount advances onceP5 — one command, not two violations
Trip on an idle cycleP11 firesWould attribute a violation to no stimulus
SPACING wider than the counter$fatal at elaborationThe comparison would saturate and stop firing silently
BANKS of 0 or 1$fatal at elaborationA power-of-two test alone accepts both
Invariant registered, antecedent notedRejected§2 — an invariant has no antecedent to reach
Invariant with zero antecedent hitsNot vacuousP13 — structurally immune
Implication with zero antecedent hitsVacuous; result unsupportable§2, P15
Implication firing more than reachedInconsistent; unsupportableP16 — an accounting error
Unguarded property registeredReported; result still supportable§7 — the harness cannot adjudicate
Duplicate property nameRejectedWould merge two properties' counts
Antecedent hit 10,000 times, one situationreached_but_monotonous§13 — not vacuous, barely exercised
Variety along an unrecorded axisReads as one situation§13's WHAT IT WOULD MISS

Rows ten and eleven are the pair this chapter exists to distinguish. Zero antecedent hits is fatal for an implication and meaningless for an invariant, and a harness that treated them alike would raise 181 false alarms against this curriculum's own corpus.

16. DV — Testing the Vacuity Detector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SIMULATION-ONLY. Independent reference for the harness's verdicts,
// holding records in a QUEUE and deciding vacuity by explicit
// case analysis rather than by the DUT's own method -- so agreement
// is evidence rather than a restatement.
class vacuity_reference;
  typedef struct {
    string       name;
    prop_shape_e shape;
    bit          guarded;
    int unsigned ant;
    int unsigned fires;
  } rec_t;
  rec_t recs[$];

  function bit would_accept(rec_t r);
    if (r.name == "") return 0;
    foreach (recs[i]) if (recs[i].name == r.name) return 0;
    return 1;
  endfunction

  function void add(rec_t r);
    if (would_accept(r)) recs.push_back(r);
  endfunction

  // Explicit case analysis, deliberately not the DUT's expression.
  function bit vacuous(rec_t r);
    case (r.shape)
      PROP_INVARIANT   : return 0;
      PROP_IMPLICATION : return (r.ant == 0);
      default          : return 0;
    endcase
  endfunction

  function int n_vacuous();
    int n = 0;
    foreach (recs[i]) if (vacuous(recs[i])) n++;
    return n;
  endfunction

  function int n_inconsistent();
    int n = 0;
    foreach (recs[i])
      if (recs[i].shape == PROP_IMPLICATION && recs[i].fires > recs[i].ant) n++;
    return n;
  endfunction

  function int n_unguarded();
    int n = 0;
    foreach (recs[i]) if (!recs[i].guarded) n++;
    return n;
  endfunction
endclass
CheckWhat it establishes
Register 200 mixed properties; compare all counts against the referenceTwo representations of one verdict
Register an invariant, note an antecedent for itRejected by both; P13
Register an implication, never note an antecedentVacuous in both; result unsupportable
Note a fire without an antecedent for an implicationInconsistent; P16
Register a duplicate nameSecond rejected; totals unchanged
Register an unguarded property, everything else cleanReported; result still supportable — P17
Set every implication's antecedent to 1Not vacuous; supportable
Note 10,000 hits with one situation keyreached_but_monotonous true; not vacuous
Note 3 hits with 3 distinct keys, threshold 2Not monotonous
Drive §5's exact waveform against the property settrip_spacing at cycle 3 only; n_trips ends at 1
Drive an access at exactly SPACINGNo trip; P2 and the boundary cover
Sweep SPACING from 1 to 15P6, P7 across the range
Parameterise SPACING = 16Elaboration fails rather than saturating
Run a legal-only stimulus and inspect the coversBoth trip covers unhit; antecedent covers hit

The last check is the report worth publishing, because it reproduces the curriculum's own historical failure with instrumentation attached:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  THE LEGAL-ONLY SUITE, WITH AND WITHOUT THE HARNESS

    stimulus : only legal command sequences. No access inside a
               spacing window, no access to a closed bank.

  (A) as Modules 1-13 would have run it -- assertions, no covers
        P1  spacing violation trips        PASS
        P2  legal spacing never trips      PASS
        P3  forbidden access trips         PASS
        P11 no trip without command        PASS
        P12 no spacing trip before act     PASS
        every property                     PASS
        reported result                    CLEAN

        what actually happened : P1, P3 and P12 were never
        evaluated. Their antecedents -- an access inside the window,
        an access to a closed bank -- never occurred. The design
        could flag nothing and this run would look identical.

  (B) the same stimulus, with §12's harness registered
        props=5 impl=5 inv=0
        vacuous=3                      <-- P1, P3, P12
        supportable=FALSE
        VAC ERROR: 'p_spacing_violation_trips' PASSED VACUOUSLY
        VAC ERROR: 'p_forbidden_access_trips'  PASSED VACUOUSLY
        VAC ERROR: 'p_no_spacing_trip_before_first_act' PASSED VACUOUSLY

    diagnosis : the assertions are correct and the suite is empty.
      §2 measures that 662 of this curriculum's 843 properties have
      this shape, and §3 measures that 75 of its chapters shipped
      with no instrument that would say so.

    what makes it dangerous : (A) is not a weak pass, it is a
      CONFIDENT one. Five properties reported success and three
      constrained nothing, and the only difference between the two
      runs is a harness that counts antecedents.

    the fix : the antecedent covers of §14, plus the harness's
      result_supportable() as a gate. A suite cannot be trusted to
      notice its own vacuity, because vacuity looks exactly like
      correctness from the inside.

17. Debugging

SymptomLikely causeHow to confirm
Everything passes on a design known to be brokenVacuous implications — §2Register the harness; check count_vacuous()
A property fires at a cycle unrelated to the cause§5 — the trip names the victim, not the triggerTrace back to the antecedent cycle
A property fires on every reset releaseMissing disable iff — §7One of the 74; add the guard
A property that worked now fires after a reset changeAn accidentally guard-free property — §7The dangerous half of the 74
Coverage says 100%, bugs still escapeCovers on consequents, not antecedents — §6Which condition each cover targets
A cover fills on cycle one, every runHit by construction — §6's calloutAsk whether it could plausibly stay unhit
Antecedent coverage good, bugs still escapeMonotonous antecedent — §13reached_but_monotonous; check variety
Spacing rule stops firing at long windowsCounter saturating — §15The $fatal should have prevented it; check SPACING
One command reported as several violationsCounting rules, not cyclesP5
Harness reports 181 false alarmsInvariants flagged as vacuousP13 — invariants are structurally immune
Harness says supportable with a vacuous propertyresult_supportable() ignoring vacuityP15

Row four is the one to fear. A property that is accidentally true during reset works today and fires the day someone changes the reset state — in a module that has no obvious connection to the assertion, which makes it one of the most confusing failures in a mature testbench.

18. Misconceptions

“The assertions passed, so the design obeys them.” §2. For an implication, a pass is consistent with the antecedent never occurring — and 662 of this curriculum's 843 properties are implications.

“A vacuous property is a badly written property.” §2's callout. The property is usually correct. What is missing is a stimulus that reaches its antecedent, and an instrument that says so.

“Invariants and implications carry the same risk.” §2. An invariant has no antecedent and is evaluated every enabled cycle — 181 of 843 are structurally immune.

“Covers are for coverage closure.” §6. A cover on a forbidden condition is what converts “it passed” into “it passed and was evaluated N times”. That is an assertion-quality instrument, not a coverage metric.

“A hit cover proves the property was exercised.” §6's callout, §13. A cover on the consequent fills on every clean cycle, and an antecedent hit ten thousand times in one situation is barely exercised.

disable iff is boilerplate.” §7. 74 of 843 properties omit it, and the dangerous ones are the properties that are accidentally true during reset today.

“A DDR assertion can be written from the pin list.” §8. 28.7% of the corpus references $past or ## explicitly, and far more depend on accumulated state — which is why 27.3 is a separate chapter.

“More properties is more checking.” §9. Expressing an interval obligation as several per-cycle implications creates more surface for the vacuity problem than one sequence property would.

“The corpus uses sequences where they help.” §9. Zero of 843 use throughout, within or intersect, and only 24 use ## at all. The choice was consistent and is recorded rather than defended.

“A subtle rule is the safest to assert.” §4. The more specific the antecedent, the rarer it is — so the properties most worth having are the ones most likely to be vacuous.

“This chapter contains DDR's timing rules.” The scope callout. Module 14 owns them. Every timing value here is ILLUSTRATIVE.

19. Interview Reasoning

What does it mean for an assertion to pass vacuously? Its antecedent never became true, so its consequent was never examined. The tool reports a pass and the property constrained nothing.

Which property shapes are exposed? Implications only. An invariant has no antecedent, so it is evaluated on every enabled cycle and cannot be satisfied without being checked.

How would you measure the exposure of a real corpus? Parse the property bodies and partition on whether they contain an implication operator. Across this curriculum's 843 named properties, 662 are implications and 181 are invariants — 78.5% exposed.

How do you prevent vacuity? Cover the antecedent, not the outcome — and for a forbidden-transition property that means the testbench must deliberately attempt the illegal thing.

Can a cover be vacuous? In the mirror image, yes. A cover on a condition that always holds, or on the property's consequent, fills immediately and measures nothing. The test is whether it could plausibly stay unhit.

Is a hit antecedent sufficient? No. An antecedent reached ten thousand times by one repeated stimulus is one situation. Occurrence count and situation variety are different measurements and only the second bounds the evidence.

What does a missing disable iff cost? It depends on whether the property is true during reset. The loud case fires immediately and gets fixed; the dangerous case is accidentally true today and fires when someone changes the reset state.

Why are DDR assertions unlike bus assertions? Because legality depends on accumulated history. A handshake rule is about two signals in one cycle; “a read to an open row” is about every command since reset, so the checker needs a model of state.

What surprised you about the corpus you measured? That not one of 843 properties uses a sequence operator, and that the first seventy-five chapters contain no covers at all — so every implication among them is exposed with no instrument that would reveal it.

Which properties are most at risk? The most valuable ones. A specific antecedent is a rare antecedent, so the subtle rules are both the most worth checking and the most likely to be unevaluated.

20. Exercises

  1. Take §1's table and derive the number of properties using neither an implication nor $past nor ##. State what shape those must have and why they are the safest class in the corpus.

  2. §3 shows covers arriving at Module 14. Pick any three properties from Modules 1–13 and determine, from their text alone, whether each is vacuity-exposed. Then state what stimulus each would need.

  3. §7 reports 74 properties without disable iff. Design the experiment that separates the legitimately guard-free from the latent failures, and say what it costs to run.

  4. Extend §12's harness to detect a property whose antecedent is reached only during reset release. Explain why that case is neither vacuous nor well-exercised.

  5. §13 measures variety over supplied keys. Construct two situation-key schemes for the same property such that one reports variety 2 and the other variety 200, and say which is honest.

  6. Remove the boundary covers from §14 and introduce an off-by-one into the spacing comparison. Which properties still pass? Explain why P1 and P2 are jointly insufficient without the == SPACING cover.

  7. §9 reports zero use of sequence operators. Rewrite §11's spacing rule as a single sequence property and compare its vacuity exposure against the implication-plus-counter form.

  8. A colleague reports “100% assertion pass, 100% functional coverage”. Using §2, §6 and §13, list the three distinct things that claim still does not establish.

21. Where This Goes

Assertion quality is now measurable rather than asserted. An implication can pass without being evaluated and an invariant cannot; 662 of this curriculum's 843 named properties are implications and 181 are invariants; 74 omit a reset guard; 28.7% reference accumulated history explicitly; and not one of the 843 uses a sequence operator.

Four results carry forward. A pass is not evidence for an implication — only a reached antecedent is, which makes the cover an assertion-quality instrument rather than a coverage metric. The most valuable properties are the most exposed, because a specific antecedent is a rare one. Antecedent variety is a separate measurement from antecedent count, and no conventional metric reports it. And this curriculum's own history is the evidence: seventy-five chapters shipped with 343 assertions and zero covers, and the practice changed measurably at Module 14 and matured by Module 20.

Two things are left open. The 74 unguarded properties were not individually adjudicated — §7 establishes that the measurement cannot distinguish a legitimately guard-free property from a latent failure, and §20's third exercise is the experiment that would. And nothing here measures whether a property is correct. §12's harness reports that a wrong property with a reached antecedent is well-exercised, which is true and unhelpful; correctness against a specification is 27.1's subject and it could not obtain the specification either.

What this chapter has assumed throughout is that the state its properties reference already exists. §11's property set reads bank_open and init_complete straight from the design, because a bindable checker sits inside the thing it checks.

A passive monitor does not have that luxury. §8 establishes that 28.7% of the corpus references history explicitly and that far more depends on accumulated state — and a monitor watching a DDR interface from outside must reconstruct every bit of that state from the command stream alone, with no access to the design's own registers.

Chapter 27.3 takes that up. The UVM track's protocol-monitoring owns what a monitor is“continuously verifying that the interface behavior obeys the protocol contract, flagging each violation at the exact cycle it happens” — and what it does not cover is the thing that makes DDR different: a bus monitor decodes, and a DDR monitor must model. The chapter is about what that model must contain, what it cannot know after a late start, and why a monitor that reconstructs state from the signals it is checking is a checker that cannot fail.

Continue learning

Standards & specifications

Governing standard
JEDEC JESD79 (DDR SDRAM)(opens JEDEC Solid State Technology Association in a new tab)

Defines the DDR SDRAM device itself — signals, command encoding, mode registers, timing parameters and the initialisation sequence — one document per generation. Memory-controller microarchitecture, address-mapping policy, PHY training algorithms and board-level design are not specified by it.

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