Part 3 · Constraint Randomization · Intermediate
constraint_mode(): Toggling Constraint Blocks
Enabling and disabling named constraint blocks, structuring constraints for controllability, and error injection.
Named blocks are controllability handles
constraint_mode(0) deactivates a named constraint block so the solver ignores every expression inside it; constraint_mode(1) reactivates it. Like rand_mode, it has block-level and object-level forms plus a per-block query, and the setting is sticky instance state. The deeper point: constraint_mode is only as useful as your block organization . A class with one giant anonymous-purpose block gives tests nothing to grab; a class with constraints grouped by intent — validity, performance shaping, addressing policy — lets tests switch off exactly one concern.
class eth_pkt;
rand bit [13:0] len;
rand bit [47:0] dst;
rand bit bad_fcs;
// Grouped BY INTENT — each block is a test-facing knob:
constraint valid_c { len inside {[64:1500]}; bad_fcs == 0; }
constraint short_c { soft len < 256; } // perf shaping
constraint unicast_c { dst[40] == 0; } // addressing policy
endclass
module t;
initial begin
eth_pkt p = new();
p.short_c.constraint_mode(0); // allow full-size frames
void'(p.randomize());
if (p.valid_c.constraint_mode()) // query: is validity active?
$display("still generating legal frames");
end
endmoduleConvention matters: most teams suffix blocks with _c and name them for the rule they enforce (valid_c, align_c, no_err_c), precisely so test writers can disable them without reading the expressions.
Error injection by disabling valid_c
The canonical use case. The base transaction constrains itself to protocol-legal values in a clearly named block. An error test disables that one block — and usually adds an inline constraint to steer which illegality appears, since “anything goes” randomization mostly produces boring garbage rather than the interesting near-legal corners.
class err_test_seq;
task body(eth_pkt p);
// 1. drop legality
p.valid_c.constraint_mode(0);
// 2. steer toward an interesting illegal corner: runt frames
if (!p.randomize() with { len inside {[1:63]}; bad_fcs == 1; })
$fatal(1, "error-injection randomize failed");
// 3. ALWAYS restore — the next sequence expects legal frames
p.valid_c.constraint_mode(1);
endtask
endclassNote the inline with succeeds only because valid_c is off — with it on, len inside {[1:63]} contradicts len inside {[64:1500]} and randomize() returns 0. constraint_mode is the relax mechanism; inline with is the tighten mechanism; error injection typically needs both, in that order.
Block state model
CONSTRAINT BLOCK STATES (per object instance)
constraint_mode(0)
┌────────────────────────────────┐
▼ │
┌─────────┐ ┌─────────┐
│ DISABLED │ │ ENABLED │ ◄── default at new()
│ block │ │ block │
│ ignored │ │ solved │
└─────────┘ └─────────┘
│ ▲
└────────────────────────────────┘
constraint_mode(1)
object-level: p.constraint_mode(0) → ALL blocks DISABLED
p.constraint_mode(1) → ALL blocks ENABLED
query: p.valid_c.constraint_mode() → 0 or 1 (per block)
sticky: state persists across randomize() calls and across
sequences sharing the object — restore what you disable.Two scoping facts worth knowing: the mode belongs to the object instance, not the class (two objects of the same class can differ), and a subclass that overrides a block by name owns a single mode for that name — there is no separate toggle for the hidden parent version.
constraint_mode vs the alternatives
vs soft constraints — soft yields automatically on contradiction; constraint_mode is an explicit procedural switch. Defaults want soft; legality rules that error tests remove want a named hard block.
vs subclass override — override replaces a block's CONTENT permanently for that type; constraint_mode toggles existence per instance at runtime.
vs inline with — with can only add; it can never turn off a class constraint. Any “relax” requirement is constraint_mode or inheritance territory.
vs rand_mode — rand_mode freezes variables; constraint_mode silences rules. Freezing a field leaves its constraints checking the frozen value; disabling a block leaves its fields fully random.
Interview angle
What interviewers ask
“How do you inject protocol errors with a constrained-random transaction?” — name the legality block (valid_c), constraint_mode(0) it, add an inline with steering the illegality, restore afterward.
“Why organize constraints into multiple named blocks?” — controllability: blocks are the unit constraint_mode toggles and the unit subclasses override.
“constraint_mode vs rand_mode?” — constraints vs variables; disabling a block frees its fields, freezing a field keeps its constraints as checks.
“Is the mode shared across objects of the class?” — no; it is per-instance sticky state.
“Can inline with re-enable behavior a disabled block provided?” — with only adds expressions; it neither disables nor enables blocks.
Key takeaways
constraint_mode(0/1) toggles named blocks per object instance; default is enabled.
Organize constraints by intent into named blocks — that naming IS the test-control API.
Error injection = disable valid_c, then tighten with inline constraints toward interesting illegality.
Mode is sticky per-instance state — restore blocks you disable.
constraint_mode relaxes; inline with tightens; they compose and are not interchangeable.
Common pitfalls
One monolithic constraint block — tests must choose all-or-nothing, killing reuse.
Disabling valid_c and forgetting to re-enable it — later sequences silently generate illegal traffic.
Expecting object-level constraint_mode(1) to restore a custom mix — it enables ALL blocks, clobbering intent.
Trying to relax a constraint via inline with — impossible; with intersects, it never removes.
Assuming a disabled block in the parent stays disabled in a subclass override scenario — the override owns one mode for that block name.