Skip to content

SystemVerilog · Module 10

randcase Statement

Procedural weighted choice without classes or constraints; config-driven weights as an architectural power move; the safety-branch pattern that prevents all-zero-weight undefined behaviour.

Module 10 · Page 10.13

What randcase Does

randcase is a procedural statement — it runs inside tasks, functions, and initial/always blocks. It has no connection to the constraint solver or randomize(). On each execution, it picks exactly one branch based on relative weights and executes that branch's body.

Think of it as a case statement where the selector is chosen by a weighted coin flip, not by matching a value.

SystemVerilog — randcase syntax and first example
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    repeat (10) begin
 
        randcase
            6 : send_read();    // 6/10 = 60% of the time
            3 : send_write();   // 3/10 = 30% of the time
            1 : send_idle();    // 1/10 = 10% of the time
        endcase
 
    end
end
 
// Each iteration picks exactly one branch.
// Over many iterations the ratio converges to 6:3:1 = 60/30/10%.

Full Syntax

Each branch is a weight expression followed by a colon and one or more statements. Multiple statements in a branch are wrapped in begin ... end. There is no default — one branch is always chosen.

SystemVerilog — randcase syntax details
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randcase
    // weight : single_statement;
    5  : task_a();
 
    // weight : begin ... end  (multiple statements)
    3  : begin
             task_b();
             count_b++;
         end
 
    // weight can be any integral constant expression
    (max_weight / 2) : task_c();
 
    // weight 0 means this branch is never chosen
    0  : task_d();    // dead branch — excluded
endcase

Expression Weights

Weights do not have to be literal integers. Any integral constant expression or a variable readable at that point can be used. This lets you change the distribution at runtime without rewriting the randcase.

SystemVerilog — variable weights in randcase
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int read_weight  = 6;
int write_weight = 4;
 
// Phase 1: 60% reads, 40% writes
randcase
    read_weight  : do_read();
    write_weight : do_write();
endcase
 
// Flip the ratio for Phase 2 — no code change needed in the randcase
read_weight  = 2;
write_weight = 8;
 
// Phase 2: 20% reads, 80% writes
randcase
    read_weight  : do_read();
    write_weight : do_write();
endcase

randcase vs case vs dist

Featurecaserandcasedist (in constraint)
How branch selectedMatches a selector valueWeighted random pickWeighted random value assignment
Where it livesProcedural (task/function/initial)Procedural (task/function/initial)Constraint block or with clause
Requires a class?NoNoYes (or std::randomize)
Executes code?Yes — any statementsYes — any statementsNo — assigns values only
Use forDeterministic branching on known valuesProportional random operation sequencesShaping rand field distributions

Nested randcase

randcase branches can contain other randcase statements. Each inner randcase runs its own independent weighted selection when its branch is reached. This is useful for hierarchical operation selection.

SystemVerilog — nested randcase for hierarchical selection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randcase
    7 : begin   // 70% — normal operations
        randcase
            6 : do_read();      // within normal: 60% reads
            4 : do_write();     // within normal: 40% writes
        endcase
    end
    2 : begin   // 20% — burst operations
        randcase
            5 : do_burst_read();
            5 : do_burst_write();
        endcase
    end
    1 : do_idle();   // 10% — idle
endcase
 
// Net probabilities:
// do_read:         70% × 60% = 42%
// do_write:        70% × 40% = 28%
// do_burst_read:   20% × 50% = 10%
// do_burst_write:  20% × 50% = 10%
// do_idle:         10%

Practical Patterns

Pattern 1 — Proportional bus transaction generator

SystemVerilog — bus transaction mix with randcase
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
task automatic run_traffic(input int num_txn);
    BusTxn t = new();
 
    repeat (num_txn) begin
        randcase
            50 : begin           // 50% — single read
                     assert(t.randomize() with { write == 0; burst == 0; });
                     drive(t);
                 end
            30 : begin           // 30% — single write
                     assert(t.randomize() with { write == 1; burst == 0; });
                     drive(t);
                 end
            15 : begin           // 15% — burst read
                     assert(t.randomize() with { write == 0; burst > 0; });
                     drive_burst(t);
                 end
            5  : do_idle_cycle();  // 5%  — idle
        endcase
    end
endtask

Pattern 2 — Phase-adaptive traffic

SystemVerilog — dynamic weights for phase-driven traffic
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum { IDLE, NORMAL, STRESS } test_phase_e;
 
task automatic adaptive_traffic(test_phase_e phase, int n);
    int w_read, w_write, w_burst, w_idle;
 
    case (phase)
        IDLE:   begin w_read=0; w_write=0; w_burst=0; w_idle=1; end
        NORMAL: begin w_read=5; w_write=3; w_burst=1; w_idle=1; end
        STRESS: begin w_read=3; w_write=3; w_burst=4; w_idle=0; end
    endcase
 
    repeat (n) begin
        randcase
            w_read  : do_read();
            w_write : do_write();
            w_burst : do_burst();
            w_idle  : do_idle();
        endcase
    end
endtask
 
// Weight 0 branches are never chosen — cleanly excluded per phase

Common Pitfalls

All weights zero — fatal error

If every branch has weight 0, there is no valid branch to choose. The simulator either reports a fatal error or exhibits undefined behaviour. Always ensure at least one branch has a non-zero weight.

Expecting equal probability without thinking about totals

1 : a(); 1 : b(); 1 : c(); gives 33%/33%/33%. Adding 2 : d(); changes all probabilities to 20%/20%/20%/40%. Recheck all branch probabilities when you add or change a weight.

Using randcase where dist is the right tool

If you only need to assign a weighted value to a variable with no other side effects, use dist in a constraint instead. randcase is for when the branches have different code to run, not just different values.

Confusing randcase with a constraint

randcase has no interaction with the constraint solver. It does not affect rand variables or randomize(). It is purely a procedural control-flow statement.

Quick Reference

SystemVerilog — randcase cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Basic form ────────────────────────────────────────────────
randcase
    w1 : stmt1;
    w2 : stmt2;
    w3 : begin stmt3a; stmt3b; end
endcase
 
// ── Probability formula ───────────────────────────────────────
// P(branch i) = wi / (w1 + w2 + ... + wN)
 
// ── Weight 0 = excluded branch ────────────────────────────────
randcase
    enable_err ? 1 : 0  : inject_error();   // conditional weight
    9                    : normal_op();
endcase
 
// ── Variable weights ──────────────────────────────────────────
int wa = 7, wb = 3;
randcase
    wa : op_a();
    wb : op_b();
endcase
 
// ── Key facts ─────────────────────────────────────────────────
// ✓ Procedural — works in tasks, functions, initial, always
// ✓ No class or constraint solver needed
// ✓ Exactly one branch per execution
// ✓ No default keyword — one branch is always chosen
// ✓ No fall-through between branches
// ✗ All weights 0 → fatal/undefined

Verification Usage — Where randcase Earns Its Keep in Real Testbenches

randcase sits in a specific niche: weighted procedural choice without the ceremony of a class-and-constraint setup. Three patterns recur across production environments.

Sequence-level scenario picker

Inside a sequence's body task, pick a sub-scenario based on weighted probabilities — drive write, drive read, drive atomic, idle. The selected branch calls into a specific sub-sequence.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task my_seq::body();
  repeat (num_iterations) begin
    randcase
      70 : drive_write_scenario();
      25 : drive_read_scenario();
       4 : drive_atomic_scenario();
       1 : drive_idle_cycle();
    endcase
  end
endtask</code>

Inter-transaction idle gap distribution

A driver inserts a randomised idle delay between transactions. randcase lets you bias toward back-to-back, short-gap, or long-idle patterns without building a constraint class for what amounts to one scalar choice.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task apb_driver::drive_idle_gap();
  int gap_cycles;
  randcase
    40 : gap_cycles = 0;           // back-to-back
    30 : gap_cycles = $urandom_range(1, 5);
    25 : gap_cycles = $urandom_range(6, 100);
     5 : gap_cycles = $urandom_range(101, 1000);
  endcase
  repeat (gap_cycles) @(posedge clk);
endtask</code>

Test-layer weight overrides via configuration

The most powerful use: weights are expressions driven by config-DB values, so different test scenarios apply different distributions to the same randcase without modifying the sequence body.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task scenario_seq::body();
  randcase
    cfg.weight_write    : drive_write();
    cfg.weight_read     : drive_read();
    cfg.weight_atomic   : drive_atomic();
    cfg.weight_idle     : drive_idle();
    1                    : drive_default();   // safety net
  endcase
endtask
 
// In test:
cfg.weight_write  = 80; cfg.weight_read = 15;
cfg.weight_atomic = 5;  cfg.weight_idle = 0;
// Default branch fires the 1/101 (~1%) safety net</code>

Simulation Behavior — How the Simulator Executes randcase

Sequential weight evaluation

The simulator visits each branch in declaration order, evaluates its weight expression, and accumulates a running total. The weights can be literals, constants, variables, or any integral expression — each is evaluated once per randcase execution.

Weighted random selection

After summing all weights, the simulator draws a single uniform random integer from [0, total_weight). It walks the cumulative weights to find which branch's range contains the drawn number; that branch is the winner. Cost: O(N) for N branches — typically negligible against the executed branch body's cost.

Zero-weight branches are excluded

A branch with weight 0 cannot be selected — the cumulative-walk algorithm skips over it. This is what enables the config-driven pattern: setting a weight to 0 effectively disables that branch for the current test without modifying the randcase code.

All-zero weights are undefined behaviour

If every branch evaluates to 0, total weight is 0, and there is no valid range for the random draw. The LRM marks this as a fatal/undefined case. Defensive practice: include a literal-weight safety branch.

No solver, no constraints, no rand fields

randcase is pure procedural code. It has no relationship to the constraint solver. It can be called from any procedural context — initial blocks, always blocks, tasks, functions, sequences — without any class context required.

✅ Safe randcase with safety branch
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randcase
  cfg.w_write : drive_write();
  cfg.w_read  : drive_read();
  cfg.w_idle  : drive_idle();
  1            : drive_default();   // safety
endcase
// Sum always ≥ 1; safe behaviour guaranteed</code>
❌ Config-only weights — risk of 0 sum
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randcase
  cfg.w_write : drive_write();
  cfg.w_read  : drive_read();
  cfg.w_idle  : drive_idle();
endcase
// If test forgot to set all weights:
// total=0 → fatal / undefined / silent skip
// depending on simulator</code>

Waveform Analysis — Tracing Which Branch Fired

randcase produces no waveform signals on its own — it's procedural control flow. The way to make branch selection visible is to log the winning branch from inside the branch body.

The instrumentation pattern

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task my_seq::body();
  repeat (10) begin
    randcase
      70 : begin `uvm_info("BR", "drive_write",  UVM_HIGH) drive_write(); end
      25 : begin `uvm_info("BR", "drive_read",   UVM_HIGH) drive_read(); end
       4 : begin `uvm_info("BR", "drive_atomic", UVM_HIGH) drive_atomic(); end
       1 : begin `uvm_info("BR", "drive_idle",   UVM_HIGH) drive_idle(); end
    endcase
  end
endtask</code>

ASCII view — branch selection across iterations

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>  100ns  BR  drive_write
  200ns  BR  drive_write
  350ns  BR  drive_read
  500ns  BR  drive_write
  650ns  BR  drive_write
  750ns  BR  drive_atomicrare branch fired (4% probability)
  800ns  BR  drive_write
  ...
After 1000 iterations:
   drive_write:  712  (expected 700)  ✓
   drive_read:   247  (expected 250)  ✓
   drive_atomic:  35  (expected  40)  ✓
   drive_idle:    6   (expected  10)  ✓</code>

Verifying weight distribution after long runs

For long-running tests, accumulate branch-selection counts and compare against expected ratios. The actual frequencies should match expected within ±10% after 1000+ iterations; outside that range suggests either a wrong weight, a seed quirk, or a constraint elsewhere that's biasing the runs.

Industry Insights — Hard-Won Lessons About randcase

Debugging Academy — Five Real Bugs From randcase Misuse

1

"randcase hangs simulator with 'no matching branch'"

DEBUG
Symptom

A regression intermittently dies with the simulator reporting "all randcase weights are zero" or similar. Other runs are fine.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task body();
  randcase
    cfg.weight_write : drive_write();
    cfg.weight_read  : drive_read();
  endcase   // BUG: if both config fields are 0, total weight is 0
endtask
 
// In some tests, cfg is left at defaults (0, 0)
// → randcase hits undefined behaviour</code>
Root Cause

Config-driven weights can legitimately be 0; if every weight is 0, total is 0 and randcase has no valid range for selection. LRM marks this fatal/undefined.

Fix

Add a literal-weight safety branch: randcase cfg.weight_write : ...; cfg.weight_read : ...; 1 : drive_default(); endcase. The safety branch fires only when others are all 0; otherwise it's a 1/N+1 contribution to the distribution. Make this a mandatory pattern for any randcase with non-literal weights.

2

"Branch fires far less often than weight suggests"

DEBUG
Symptom

A randcase declares { 50 : write; 50 : read } but coverage shows reads happening 90% of the time.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task body();
  randcase
    weight_read  : drive_read();      // expects 50/50
    weight_write : drive_write();
  endcase
endtask
 
// Configuration code somewhere:
//   weight_write = $urandom_range(0, 10);  // small
//   weight_read  = $urandom_range(50, 100);  // big
// → distribution biased toward read</code>
Root Cause

Weights are expressions evaluated at runtime. The author thought "weight_read = 50" but somewhere upstream the field was randomised to a much larger value. The actual values at randcase time determine the distribution; the assumed values are irrelevant.

Fix

Either fix the upstream code so weights match the intended distribution, or use literal weights in the randcase if the distribution should be fixed at code-write time. Log the evaluated weights at runtime to verify they match your mental model: ``uvm_info("RC", $sformatf("w_read=%0d w_write=%0d", weight_read, weight_write), UVM_HIGH)before therandcase`.

3

"Branch body lacks braces, only first statement is conditional"

DEBUG
Symptom

A randcase branch with multiple statements appears to only execute one of them; subsequent statements seem to run unconditionally.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randcase
  50 : drive_write();
       start_scoreboard_check();   // BUG: outside the branch
  50 : drive_read();
endcase
 
// Compiler treats: branch 1 body = drive_write() only;
// start_scoreboard_check() is a top-level statement
// between branches → syntax error in some simulators,
// or attached to next branch in others
// Behaviour is implementation-defined</code>
Root Cause

A branch's body is a single statement by default. Multiple statements need a begin/end block. Without it, only the first statement is the branch body; subsequent statements have ambiguous attachment.

Fix

Always wrap multi-statement branches in begin/end: randcase 50 : begin drive_write(); start_scoreboard_check(); end 50 : drive_read(); endcase. Make this a style rule even for single-statement branches — the brace clarifies scope and prevents the bug class when someone later adds a second statement.

4

"randcase picks the same branch every run"

DEBUG
Symptom

A "random" test always executes the same randcase branch on every run; the test becomes effectively directed.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>module tb;
  initial begin
    // No seed plusarg passed to simulator
    repeat (100) begin
      randcase
        50 : do_a();
        50 : do_b();
      endcase
    end
  end
endmodule
 
// Every run prints exactly the same sequence:
// do_a, do_b, do_a, do_a, do_b, ...
// Because PRNG seed is fixed across runs</code>
Root Cause

randcase uses the same PRNG as the rest of the simulator. Without a seed plusarg, the simulator starts from a deterministic default — same seed, same random sequence, same randcase selections. This is correct behaviour (and desirable for reproducibility), but it surprises developers expecting per-run variation.

Fix

Pass a different seed per run from the regression dispatcher: simv +UVM_TESTNAME=my_test +ntb_random_seed=$RANDOM. Log the seed in the test's header. The next run with a different seed produces a different randcase sequence; the run with the same seed reproduces the original sequence bit-for-bit.

5

"Weights are correct but distribution looks skewed for small sample sizes"

DEBUG
Symptom

A randcase with weights 50/50 runs 20 times in a test and shows 14 "writes" and 6 "reads" — the developer thinks the weights are wrong.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>repeat (20) begin
  randcase
    50 : do_write();   // expected: ~10
    50 : do_read();    // expected: ~10
  endcase
end
// Actual: 14 writes, 6 reads
// Is this a bug?</code>
Root Cause

20 samples is too few to converge to the expected distribution. For a 50/50 distribution, the standard deviation is sqrt(0.25 × 20) ≈ 2.2 — so seeing 14/6 (deviation of 4) is well within normal random fluctuation. The weights are correct; the sample size is just small.

Fix

Run more iterations before judging. At 1000 iterations, the expected deviation is ~16, so 540/460 is normal but 700/300 is suspicious. Statistical convergence requires hundreds-to-thousands of samples; 20 is too few for any meaningful frequency analysis. Tools like $display("%0d/%0d", count_a, count_a + count_b) let you sanity-check distributions at the end of long runs.

Interview Q&A — Twelve Questions You Will Be Asked

Executes one of several branches based on weighted random choice. Each branch has a weight expression and a statement (or begin/end block). The simulator picks one branch with probability proportional to its weight, then executes that branch's body. randcase is procedural code — no constraint solver, no rand fields, no class context required.

Best Practices — Ten Rules for randcase Discipline

  1. Always include a literal-weight safety branch when using expression weights. Prevents the all-zero-weights fatal/undefined case.
  2. Use weights that sum to 100 for readability. Each weight reads directly as a percentage; no mental ratio computation required.
  3. Wrap multi-statement branch bodies in begin/end. Single-statement default has ambiguous attachment for subsequent statements; always use braces.
  4. Use randcase for procedural control-flow choice, dist for stimulus generation. Different tools for different jobs.
  5. Drive weights from config-DB whenever possible. One sequence body serves many test scenarios; refactor hard-coded weights to configurable ones at code-review time.
  6. Log branch selection during bring-up. Per-branch counters at report_phase verify the distribution matches expectations.
  7. Pass distinct seeds per regression run. Without seed variation, randcase picks the same branches every run — a CRV test degenerates into directed.
  8. Don't judge distribution correctness from small samples. Random distributions need hundreds-to-thousands of iterations to converge statistically.
  9. Document the meaning of each branch with a comment. A branch's purpose isn't always obvious from its statement; "// drive a normal write" pays back in code review.
  10. Never use randcase in synthesisable code. Strictly simulation-only construct; keep in verification directories or behind ``ifdef` guards.