Why SystemVerilog Randomization Fails (and How to Debug It)
In short: randomize() fails when no set of values can satisfy every active constraint at once. It returns 0, the solver prints the constraints that clash, and the object keeps its old values. Almost every failure comes from one of six causes: an inline with that fights a class constraint, a child class that fights its parent, a non-rand variable nobody set, a rand handle that was never constructed, a partial randomize(x) that freezes everything else, or a return value that was ignored. The last one is the worst: the error scrolls past in the log, the test keeps going, and it quietly sends old items again. This guide shows all six on one small class with real VCS logs.
Constraint failures come up again and again on the Verification Academy and Cadence forums (threads linked in the references). They are also easy to fix once you can read what the solver is telling you.
The analogy: an impossible food order
You order “a vegetarian burger with extra chicken”. The kitchen can’t make it, so it sends nothing back. Randomization works the same way:
- The menu rules are the class constraints.
- Your special request is the inline
with { }. - The kitchen is the constraint solver.
- “Sorry, we can’t make that” is
randomize()returning 0, plus the solver’s message listing the clashing rules.
The dangerous case is a waiter who doesn’t tell you. You assume the new burger arrived, but you’re eating yesterday’s order again. That’s what happens when you ignore the return value.
What actually happens when randomize() fails
Three things happen, and you need all three to debug well:
- It returns 0.
randomize()is a function that returns 1 on success and 0 on failure. That return value is the only reliable signal in your own code. - The rand variables keep their old values. Nothing is assigned on failure. A fresh object keeps its defaults, often 0, and an object you reuse keeps the last good values.
- The simulator prints the clash. Each tool lists the constraints it couldn’t satisfy together:
| Simulator | What it prints | Extra help |
|---|---|---|
| Synopsys VCS | Error-[CNST-CIF] Constraints inconsistency failure, then “Solver failed when solving following set of constraints” | Run options +ntb_solver_debug=trace +ntb_solver_debug_filter=<serial>; Verdi constraint debug |
| Cadence Xcelium | *W,SVRNDF “The randomize method call failed”, then RNDOCS lines listing the conflicting constraints | SimVision constraint debugger |
| Siemens Questa | A randomization-failed message | vsim -solvefaildebug |
The VCS messages and switches above are quoted from a real log posted on Verification Academy. The Xcelium ones come from the Cadence forum, and the Questa switch from this thread. The demo below shows the exact VCS output.
The assert() trap
// Don't: if assertions are switched off, randomize() may never be called
assert(req.randomize());
// Do: check the return value and say what you asked for
if (!req.randomize() with { burst == WRAP; len == 7; })
`uvm_error("RAND", $sformatf("randomize failed: %s", req.convert2string()))
Many teams switch off immediate assertions in fast regressions. When they do, the randomize() call inside assert() can disappear with them, because the tool may skip evaluating the whole expression. Every item then goes out unrandomized, and nothing complains. The Questa thread above gives the same advice: never wrap randomize() in assert().
The six causes of a randomization failure
| # | Cause | Food-order version | Clue | Fix |
|---|---|---|---|---|
| 1 | Inline with fights a class constraint | The menu says wraps come in 4 sizes, and you ask for a fifth | Fails every time, and only at that call site | Read the class constraints the solver lists. Change the request, or switch a constraint off on purpose with constraint_mode(0) |
| 2 | Child class fights its parent | A new branch adds a rule the head office forbids | Every randomize of the child fails, while the parent is fine | Override the parent constraint by giving it the same name in the child, instead of adding a second one |
| 3 | A non-rand variable nobody set | The order form has “max size” left blank, so it reads as zero | Constraints look right, but one side compares against 0 | Set state variables in new() or before randomize(); print them in the error message |
| 4 | A rand handle that was never constructed | Ordering a side dish for a table that doesn’t exist | A null-object error, or a sub-object that never changes, depending on the tool | rand doesn’t create objects. Call new() first |
| 5 | randomize(x) freezes everything else | Changing only the drink on a finished order, when the new drink doesn’t fit the tray | Fails only for some seeds | Pass every variable that must move: randomize(len, addr) |
| 6 | The return value is ignored | The waiter says nothing and serves yesterday’s order | The test keeps passing. Items repeat, or coverage stalls | Always check the return value and stop or report on 0 |
Two extra checks that catch subtle failures:
- Width and sign. A constraint like
addr[11:0] + bytes <= 4096is evaluated at the width of its operands. If a term is too narrow, it can wrap and make a legal value look illegal, or the other way round. Write the widths out (32'd1) in boundary maths. softversus hard. Asoftconstraint quietly gives way when a hard one disagrees. If two of your intentions must both hold, neither can besoft.
A 5-minute triage

- Find the first failure, with its seed. Note the seed, the call site and what you asked for in the
with. - Does it fail every time, or only sometimes? Every time: two constraints simply can’t both be true (causes 1 to 3). Go to step 3. Only for some seeds: something was frozen or chosen earlier (cause 5). Look for
randomize(x)with an argument list, two-step randomization, or values set inpre_randomize(). - Read the solver’s list, not your code. VCS, Xcelium and Questa all print the clashing constraints. Cross out every line that can’t involve your variable. The two or three left are the fight.
- Print the non-rand inputs. State variables, loop bounds and config values feed the solver as fixed numbers. One of them being 0 is cause 3.
- Shrink it. Switch off constraints one at a time with
constraint_mode(0)until it passes. The last one you switched off is half of the clash.
- Analogy: when an order can’t be made, don’t rewrite the whole menu. Ask the kitchen which two rules clash, then change one.
- If nothing failed but the coverage is stuck, suspect cause 6: a randomize somewhere fails with its return value unchecked, and its items are repeats.
The practical verification debug guide uses the same “first failure first” habit for every kind of bug.
One class, six planted failures, real VCS logs
The demo is plain SystemVerilog, with no UVM needed. It uses one AXI-style burst request class:
- Constraints: 32-bit bus sizes, beat alignment, legal wrap lengths (2, 4, 8 or 16 beats), and the 4KB boundary rule for INCR bursts.
- A rand sub-object for priority.
- A child class for a 64-bit bus variant.
+BUG=N switches on one cause at a time. The run goes through four phases and prints one summary line for each:
| Phase | What it does | Bugs it shows |
|---|---|---|
| P1 | 200 requests with a test-level with | 1, 3, 4 |
| P2 | 50 requests from the 64-bit child class | 2 |
| P3 | Pick a burst, then make it longer in a second step | 5 |
| P4 | 100 wrap bursts, checked for repeats | 6 |
Run it yourself
The whole demo is one file, rand_fail_demo.sv, with all six planted causes.
vcs -full64 -sverilog rand_fail_demo.sv -R +BUG=0
Then +BUG=1 to +BUG=6. On EDA Playground, put +BUG=N in Run Options. If it does not reach the run, compile with +define+BUG=N instead. The first line of every log prints the planted bug, so you can check.
Try the triage before reading the results below: run one bug, read only the first solver block, and name the cause.
What VCS printed
Every run below is the same file with a different +BUG=N. Each phase stops after 5 failures so the log stays readable (VCS prints a full solver report for every failed call).
| Run | What broke | P1 | P2 | P3 | P4 |
|---|---|---|---|---|---|
| BUG=0 | nothing | 200 ok | 50 ok | 200 ok | 100 new, 0 repeated |
| BUG=1 | inline with vs class rule | 5 of 5 failed | 50 ok | 200 ok | 100 new |
| BUG=2 | child vs parent | 200 ok | 5 of 5 failed | 200 ok | 100 new |
| BUG=3 | max_len never set | 5 of 5 failed | 50 ok | 5 of 5 failed | 5 sent, 4 repeated |
| BUG=4 | user never constructed | 5 of 5 failed | 5 of 5 failed | 5 of 5 failed | 5 sent, 4 repeated |
| BUG=5 | randomize(len) alone | 200 ok | 50 ok | 196 ok, 4 failed | 100 new |
| BUG=6 | return value ignored | 200 ok | 50 ok | 200 ok | 100 sent, 50 repeated |
What the logs tell you, run by run:
- BUG=1. VCS prints
Error-[CNST-CIF] Constraints inconsistency failureand lists exactly two rules:c_wrap(len inside {1, 3, 7, 15}) andWITH_CONSTRAINT(burst == WRAP; len == 8'h14). That’s 20, which a wrap burst can’t have. The object still holds its old values:addr=00000000 len=0 size=0 burst=FIXED. - BUG=2. The list shows
bit[0:0] wide64 = 1'h1(not rand, so fixed), thenc_sizefrom the parent (size <= 2) andc_widefrom the child (size == 3). Two files, one clash. - BUG=3. The list shows
bit[7:0] max_len = 8'h0. Norandin front, and the value is 0. That one line is the whole bug. It fails again in P3 and P4 with different inline rules, because every call hits the same frozen 0. - BUG=4. This one is a different error:
Warning-[CNST-PPRW] Member object 'user' was found to be NULL during randomization, thenError-[CNST-NPE] Accessing null pointer user.prio in constraints. VCS does flag it. The AMIQ post warns that not every simulator does, so don’t count on it. - BUG=5. 4 failures in 200, the first at request 6. The list shows
addr = 32'hfff4as a fixed value, notrand. The offset inside the 4KB page is0xff4, which leaves 12 bytes, room for 3 beats of 4 bytes.len >= 32can’t fit. On most seeds the first step picked an address with room, so the bug hid. - BUG=6. VCS still printed the solver error each time, but the test didn’t check the result, so it kept going. Every illegal request sent the previous burst again: 50 repeats in 100. Nothing in the test’s own summary says a single call failed.
The pattern is worth remembering. In five of the six runs, the answer was sitting in the first solver block. You only had to read the fixed values (the lines without rand) and the two rules VCS named.
Debug techniques that work on any simulator
These are short patterns. The runnable, tested code is the demo file.
1. Switch constraints off one at a time
req.c_4k.constraint_mode(0); // switch one constraint block off
if (req.randomize() with { len == 20; }) $display("c_4k was in the clash");
req.c_4k.constraint_mode(1); // always switch it back on
constraint_mode(0)on a named block is the fastest way to find which block is in the fight. That’s why every constraint block should have a clear name.- Analogy: taking one rule off the menu at a time until the kitchen says “yes, we can make that”.
2. Freeze or free variables on purpose
// Option A: freeze one variable, solve the rest
req.addr.rand_mode(0); // addr keeps its current value
ok = req.randomize();
req.addr.rand_mode(1);
// Option B: solve only the named variables, everything else stays fixed
ok = req.randomize(len, addr);
rand_mode(0)turns a rand variable into a fixed input.- An argument list on
randomize()solves only the named variables. That is what cause 5 gets wrong.
3. Make the error message do the triage
if (!req.randomize() with { burst == WRAP; len == want; })
`uvm_error("RAND", $sformatf("failed: asked burst=WRAP len=%0d, max_len=%0d, obj=%s",
want, req.max_len, req.convert2string()))
Print what you asked for and every non-rand input. That answers triage step 4 without rerunning.
4. Use soft constraints for defaults only
constraint c_len_default { soft len inside {[0:15]}; } // a default the test can override
A test’s inline with { len == 100; } beats a soft default instead of failing. Use soft for defaults that tests are allowed to override, and never for a protocol rule.
5. Use the simulator’s own debugger when the list is long
- VCS: run with
+ntb_solver_debug=trace +ntb_solver_debug_filter=<serial>, or step the solver in Verdi. - Xcelium: the SimVision constraint debugger.
- Questa:
vsim -solvefaildebug.
All three are taken from the forum threads in the references. Check your own tool’s manual for the current spelling.
Common mistakes, from real forum threads
1. “My constraint is ignored” on a variable that isn’t rand
- What happens: the variable in the constraint was never declared
rand, so the solver treats it as a fixed number. A reply on Verification Academy puts it simply: constraints do not work on non-rand values. - Fix 1: declare it
randif it should be chosen. - Fix 2: if it’s a setting, give it a value before
randomize()and print it in the error.
2. Calling randomize() on a null handle
- What happens: “Invalid call to class::randomize() via null class reference”.
randomize()doesn’t create objects (thread). - Fix: call
new()first, including for everyrandsub-object handle. AMIQ’s gotcha post notes that some simulators give no error at all for an uninitialised rand handle.
3. A dist that conflicts with a hard constraint
- What happens: a
distadds a set of allowed values, not just weights. If a hard constraint rules out every value in the set, randomization fails. The Cadence thread is exactly this case. - Fix 1: make sure each
distrange overlaps what the other constraints allow. - Fix 2: if the weights are only a preference, consider
softfor that rule.
You can see this exact dist versus hard constraint clash, and the constraint_mode(0) fix, in the write sequence of async FIFO verification part 1.
4. A constraint that “sometimes” fails
- What happens: it passes on most seeds and fails on a few. Usually something is frozen, or an indexed or modulo expression narrows the space more than expected (thread with a VCS log).
- Fix 1: rerun the failing seed and read the solver list.
- Fix 2: simplify the expression. In that thread, rewriting as a
foreachsolved it.
5. Wrapping randomize() in assert()
- What happens: the call can vanish when assertions are off (thread).
- Fix: use
if (!obj.randomize()) ..., as in the assert() trap above.
6. Randomizing a wrapper class and expecting the inner objects to change
- What happens: the outer object randomizes, but the inner object’s fields don’t, because the handle isn’t
rand, or isn’t constructed. - Fix 1: mark the handle
rand. - Fix 2: construct it in
new(). - Fix 3: check one inner value in a quick test.
How AI helps (and where it doesn’t)
- Reading the solver list. Paste the solver’s conflict block and the class’s constraints, and ask which two clash and why. An AI is quick at this, and it’s a good first pass.
- Proposing a minimal fix. Ask for the smallest change that keeps every protocol rule. Then check it: an AI will happily “fix” a failure by deleting the very rule that protects the protocol.
- Where it doesn’t help: deciding which rule is right. If the 4KB rule and the test’s request clash, only the spec decides which one gives way.
More in AI in ASIC verification, and a real experiment in AI-written vs hand-written UVM testbench.
FAQ
Why does randomize() return 0 instead of stopping the simulation? The standard makes randomize() a function that returns 1 on success and 0 on failure. Deciding what a failure means is your job, which is why every call needs a check.
What values does the object hold after a failed randomize()? The random variables keep the values they had before the call. That’s why an unchecked failure looks like a repeated transaction (planted bug 6 in the demo).
Can a constraint fail only on some seeds? Yes. If part of the object is frozen (a non-rand variable, or a variable left out of a randomize(arg) list), some earlier values leave no room for the new rule. Planted bug 5 shows this.
Does a soft constraint ever cause a failure? No. When a soft constraint conflicts with a hard one, the solver drops the soft one. That’s also its risk: it can be dropped without a message.
Is it safe to switch off a constraint to make a failure go away? Only to find the culprit. Once you know which rule clashes, fix the cause, or you may be sending illegal traffic.
Do I need UVM for any of this? No. The demo is plain SystemVerilog. In UVM, the same rules apply to seq_item.randomize() and the uvm_do_with macro.
References
- IEEE 1800-2017, SystemVerilog standard, clause 18 (constrained random value generation).
- Verification Academy: constraint solver issue, sometimes results in errors (VCS log,
+ntb_solver_debug). - Verification Academy: how to debug a randomization failed error in Questa.
- Verification Academy: randomization failing (null handle).
- Verification Academy: my randomization is failing (non-rand value).
- Cadence Community: randomization failure from TB.
- AMIQ Consulting: Gotcha: using the rand modifier for object handles is not enough.
Read next
- Weighted distribution in SystemVerilog
- Enable or disable a specific constraint
- Constraint override in SystemVerilog
- randomize() vs std::randomize()
- Generate randc behaviour from a rand variable
- How to generate an array of unique random values
- AXI protocol verification guide (where the 4KB rule comes from)
- UVM scoreboard mismatch debug
- Practical verification debug guide
