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 feature | Properties | Share of 843 |
|---|---|---|
Overlapping implication |-> | 534 | 63.35% |
Non-overlapping implication |=> | 129 | 15.30% |
disable iff reset guard | 769 | 91.22% |
$past history reference | 224 | 26.57% |
Edge function $rose / $fell / $stable / $changed | 51 | 6.05% |
Structural $onehot / $countones | 41 | 4.86% |
Cycle delay ## | 24 | 2.85% |
inside set membership | 7 | 0.83% |
Sequence operator throughout / within / intersect | 0 | 0.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 |=>:
| Shape | Properties | Share | Can pass without checking? |
|---|---|---|---|
| Implication | 662 | 78.53% | yes |
| Invariant | 181 | 21.47% | no |
| Total | 843 | 100.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:
| Modules | Assertions | Covers | Covers per assertion |
|---|---|---|---|
| 1–13 | 343 | 0 | 0.00 |
| 14–19 | 149 | 74 | 0.50 |
| 20–26 | 352 | 325 | 0.92 |
| Total | 844 | 399 | 0.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 occurrences | What the pass means |
|---|---|
| Zero | nothing — the property was never evaluated |
| Once | the consequent held in one situation |
| Many, varied | the 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 cyclesThree 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 cover | With the cover |
|---|---|
| “the property passed” | “the property passed and its antecedent occurred N times” |
| Vacuity is a possibility | Vacuity is excluded by evidence |
| A reviewer must trust the suite | A 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_refresh — the 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 anyway | no effect — the omission is invisible |
| false | fires 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 on | Example shape | What the checker needs |
|---|---|---|
| One cycle | `valid | -> !error` |
| Adjacent cycles | $past(cmd,1) | one register |
| Accumulated state | bank_open[b] | a model — 27.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.
| Class | Shape | Vacuity-exposed? | Corpus practice |
|---|---|---|---|
| Forbidden transition | illegal_attempt |-> !happened | yes — the worst case | the highest-value class, per 24.2 §15 |
| Invariant | expr alone | no | 181 of 843 |
| Partition | a + b + c == total | no | used for exhaustive classification |
| Monotone / sticky | $past(f) |-> f | yes, weakly | used for peaks and records |
| Derivation | out == f(params) | no | checks published arithmetic |
| Spacing | trigger |-> ##[n:$] … | yes | rare 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
// ---------------------------------------------------------------------
// 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;
endmoduleFour 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
// ---------------------------------------------------------------------
// 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
endclassAn 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
// ---------------------------------------------------------------------
// 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
endclassreached_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
// 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:
// 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");
endtask15. Corner Cases
| Case | Behaviour | Why |
|---|---|---|
Access at exactly SPACING cycles | No trip | §5's second sequence; the boundary an off-by-one breaks |
Access at SPACING − 1 | Trips | The covered boundary on the other side |
| Access before any activate | No spacing trip | P12 — the rule has no meaning yet |
| Activate during an open window | Window restarts | Covered; the obligation is per-activate |
| Both rules tripping in one cycle | Count advances once | P5 — one command, not two violations |
| Trip on an idle cycle | P11 fires | Would attribute a violation to no stimulus |
SPACING wider than the counter | $fatal at elaboration | The comparison would saturate and stop firing silently |
BANKS of 0 or 1 | $fatal at elaboration | A power-of-two test alone accepts both |
| Invariant registered, antecedent noted | Rejected | §2 — an invariant has no antecedent to reach |
| Invariant with zero antecedent hits | Not vacuous | P13 — structurally immune |
| Implication with zero antecedent hits | Vacuous; result unsupportable | §2, P15 |
| Implication firing more than reached | Inconsistent; unsupportable | P16 — an accounting error |
| Unguarded property registered | Reported; result still supportable | §7 — the harness cannot adjudicate |
| Duplicate property name | Rejected | Would merge two properties' counts |
| Antecedent hit 10,000 times, one situation | reached_but_monotonous | §13 — not vacuous, barely exercised |
| Variety along an unrecorded axis | Reads 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
// 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| Check | What it establishes |
|---|---|
| Register 200 mixed properties; compare all counts against the reference | Two representations of one verdict |
| Register an invariant, note an antecedent for it | Rejected by both; P13 |
| Register an implication, never note an antecedent | Vacuous in both; result unsupportable |
| Note a fire without an antecedent for an implication | Inconsistent; P16 |
| Register a duplicate name | Second rejected; totals unchanged |
| Register an unguarded property, everything else clean | Reported; result still supportable — P17 |
| Set every implication's antecedent to 1 | Not vacuous; supportable |
| Note 10,000 hits with one situation key | reached_but_monotonous true; not vacuous |
| Note 3 hits with 3 distinct keys, threshold 2 | Not monotonous |
| Drive §5's exact waveform against the property set | trip_spacing at cycle 3 only; n_trips ends at 1 |
Drive an access at exactly SPACING | No trip; P2 and the boundary cover |
Sweep SPACING from 1 to 15 | P6, P7 across the range |
Parameterise SPACING = 16 | Elaboration fails rather than saturating |
| Run a legal-only stimulus and inspect the covers | Both 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:
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
| Symptom | Likely cause | How to confirm |
|---|---|---|
| Everything passes on a design known to be broken | Vacuous implications — §2 | Register the harness; check count_vacuous() |
| A property fires at a cycle unrelated to the cause | §5 — the trip names the victim, not the trigger | Trace back to the antecedent cycle |
| A property fires on every reset release | Missing disable iff — §7 | One of the 74; add the guard |
| A property that worked now fires after a reset change | An accidentally guard-free property — §7 | The dangerous half of the 74 |
| Coverage says 100%, bugs still escape | Covers on consequents, not antecedents — §6 | Which condition each cover targets |
| A cover fills on cycle one, every run | Hit by construction — §6's callout | Ask whether it could plausibly stay unhit |
| Antecedent coverage good, bugs still escape | Monotonous antecedent — §13 | reached_but_monotonous; check variety |
| Spacing rule stops firing at long windows | Counter saturating — §15 | The $fatal should have prevented it; check SPACING |
| One command reported as several violations | Counting rules, not cycles | P5 |
| Harness reports 181 false alarms | Invariants flagged as vacuous | P13 — invariants are structurally immune |
| Harness says supportable with a vacuous property | result_supportable() ignoring vacuity | P15 |
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
-
Take §1's table and derive the number of properties using neither an implication nor
$pastnor##. State what shape those must have and why they are the safest class in the corpus. -
§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.
-
§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. -
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.
-
§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.
-
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
== SPACINGcover. -
§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.
-
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
Related tutorials
- Related topic
Senior Verification Strategy
Use UVM, add assertions, add coverage names three tools and zero obligations. Start from what must be true, then ask what evidence each kind of truth admits — and hold the eight reasons a green assertion proves nothing.
- Related topic
Why Timing Parameters Exist
A DDR command can be perfectly meaningful and target a bank in exactly the right state and still be illegal right now. That third refusal is what timing parameters are, and it is a different question from the first two.
- Related topic
Device Physics Behind Timing
The array's processes take absolute time and know nothing about any clock. A controller counts cycles. Converting between them is where a physical duration becomes a digital obligation — and where the same device needs more cycles the faster you run it.
- Related topic
Timing Constraints Catalogue
Four classes of timing constraint, the resource that owns each one, and the equation the whole subject reduces to: a command becomes legal at the maximum of every applicable deadline — never the minimum, and never the most recent.
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.
