Skip to content

SystemVerilog · Module 10

randsequence Statement

Compositional grammar generator that mirrors a protocol's BNF; weighted alternatives, repeat-bounded iteration, expansion-time vs execution-time semantics; coexistence with UVM sequences.

Module 10 · Page 10.14

What randsequence Does

randsequence is a procedural statement that generates a random sequence of operations using a context-free grammar. You define named productions — rules that expand into sequences of other productions or direct statements. On each execution the grammar expands from a start production, making random choices at each alternative, until it reaches terminal statements (actual code to execute).

While randcase (page 10.13) picks one branch per execution, randsequence can produce arbitrary-length sequences with structured, grammar-governed ordering — more powerful for modelling protocol flows.

SystemVerilog — randsequence first example
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
 
    randsequence (bus_op)    // start production: bus_op
 
        // bus_op expands to one of three alternatives (equal weight)
        bus_op : read_txn | write_txn | idle_txn ;
 
        // read_txn: send a read, then optionally a burst follow-up
        read_txn  : { send_read(); }  read_follow ;
 
        // write_txn: always just a single write
        write_txn : { send_write(); } ;
 
        // idle_txn: one or more idle cycles
        idle_txn  : { send_idle(); } ;
 
        // read_follow: either nothing, or another read (recursive bursting)
        read_follow : rand join
                      7 := { }                   // 70%: stop here
                    | 3 := { send_read(); } ;   // 30%: one more read
 
    endsequence
 
end

Syntax — Productions and Alternatives

The randsequence block contains one or more production rules. Each production has a name, a colon, and a list of alternatives separated by |. The first production listed is the start production, or you can name it explicitly in the opening parentheses.

SystemVerilog — randsequence syntax anatomy
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randsequence (start_prod)   // ← named start production (optional)
 
    // ── Production rule ────────────────────────────────────────────
    // name : alternative1 | alternative2 | ... ;
 
    // ── Alternative: sequence of items separated by spaces ─────────
    // Items can be:
    //   { statements }     — code to execute (terminal)
    //   production_name    — reference to another production (non-terminal)
    //   if (cond) alt      — conditional expansion
    //   repeat (expr) item — repeat an item N times
 
    start_prod : op_a  op_b ;     // sequence: always op_a then op_b
 
    op_a : { task_x(); }          // terminal: executes task_x
         | op_c op_d ;            // or: expands op_c then op_d
 
    op_b : { task_y(); } ;        // terminal only
 
    op_c : { task_z(); } ;
 
    op_d : { task_w(); } ;
 
endsequence

Weighted Alternatives — := Operator

Add a weight := alternative prefix to give some alternatives higher probability. The weights follow the same relative-probability rule as dist and randcase.

SystemVerilog — weighted alternatives with :=
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randsequence (txn)
 
    txn : 6 := { send_read();  }   // 60%: read
        | 3 := { send_write(); }   // 30%: write
        | 1 := { send_idle();  }   // 10%: idle
        ;
 
endsequence
 
// For sequences (not just single alternatives), weight the whole sequence:
// txn : 7 := read_txn write_txn   // 70%: read followed by write
//       | 3 := idle_txn           // 30%: idle only
//       ;

repeat and if Inside Productions

Two special constructs inside production alternatives extend the grammar's expressive power without requiring recursive productions.

repeat — execute an item N times

SystemVerilog — repeat inside randsequence
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randsequence (burst)
 
    // Generate 2–8 consecutive reads, then a single write
    burst : repeat ($urandom_range(2,8)) { send_read(); }
            { send_write(); }
            ;
 
endsequence

if — conditional expansion

SystemVerilog — if inside randsequence
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int error_mode = 0;   // set by the test before running
 
randsequence (op)
 
    op : { setup(); }
         if (error_mode) { inject_error(); }
                    else { normal_op(); }
         { teardown(); }
         ;
 
endsequence
// Sequence is always: setup → (inject_error OR normal_op) → teardown

break — Early Termination

The break statement inside a randsequence block terminates the entire sequence immediately — no further productions are expanded. It is useful for cutting off recursion once a termination condition is met.

SystemVerilog — break to stop recursion on a condition
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int txn_count = 0;
int max_txn   = 20;
 
randsequence (stream)
 
    // Keep generating transactions until the limit is reached
    stream : { send_txn();
               txn_count++;
               if (txn_count >= max_txn) break; }
             stream                     // recurse: generate another
           | { }                        // or stop randomly (base case)
           ;
 
endsequence

Practical Pattern — AXI Burst Sequence Generator

A realistic protocol sequence: setup a handshake, generate 1–4 data beats, optionally inject a stall, then close with an acknowledge. The grammar enforces ordering while randomising content and count.

SystemVerilog — AXI-style burst sequence grammar
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randsequence (axi_burst)
 
    // Top level: address phase → data phase → response phase
    axi_burst : { send_addr(); }
                data_phase
                { recv_response(); }
                ;
 
    // data_phase: 1 to 4 beats, possibly stalled
    data_phase : data_beat                          // exactly 1 beat
               | data_beat stall data_beat          // 2 beats with a stall
               | data_beat data_beat data_beat      // 3 beats
               | repeat (4) data_beat             // 4 beats, no stall
               ;
 
    // data_beat: weighted — most are normal, some trigger errors
    data_beat : 9 := { send_data();         }   // 90%: normal beat
              | 1 := { send_data_err();     }   // 10%: error beat
              ;
 
    // stall: 1–3 idle cycles
    stall : repeat ($urandom_range(1,3)) { send_idle(); } ;
 
endsequence

randsequence vs randcase — Choosing the Right Tool

ScenarioUse
Pick one operation from a weighted set per iterationrandcase
Generate a structured sequence with ordering rulesrandsequence
Model a protocol with setup → data → teardown flowrandsequence
Drive proportional traffic (60% reads, 40% writes)randcase or dist
Generate recursive or variable-length sequencesrandsequence
Quick one-liner weighted branchrandcase

Common Pitfalls

Infinite recursion without a base case

A production that only refers to itself with no non-recursive alternative will loop forever. Always include a base case (an alternative with only terminal statements or an empty body { }) or use break to cut off recursion.

Confusing randsequence with constraints

randsequence has no interaction with the constraint solver or rand variables. It is purely procedural — it selects and executes code, it does not assign values to rand fields.

Using := when | (equal weight) is sufficient

When you just want equal-probability alternatives, plain | is cleaner. Only add := weights when the distribution is intentionally non-uniform.

Forgetting that sequence items execute left-to-right

In a production like A B C, the items execute strictly in order: A fully expands, then B, then C. There is no parallelism. If A is recursive, B and C do not start until A is complete.

Quick Reference

SystemVerilog — randsequence cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
randsequence (start)
 
    // ── Unweighted alternatives (equal probability) ──────────────
    start : alt_a | alt_b | alt_c ;
 
    // ── Weighted alternatives ─────────────────────────────────────
    start : 6 := alt_a | 3 := alt_b | 1 := alt_c ;
 
    // ── Sequence (items execute left-to-right) ────────────────────
    start : item_x item_y item_z ;
 
    // ── Terminal (code to execute) ────────────────────────────────
    item_x : { my_task(); } ;
 
    // ── Conditional expansion ─────────────────────────────────────
    item_y : if (cond) { task_a(); } else { task_b(); } ;
 
    // ── repeat ────────────────────────────────────────────────────
    item_z : repeat ($urandom_range(1,4)) { task_c(); } ;
 
    // ── break — stops the whole randsequence immediately ─────────
    item_z : { if (done) break; task_c(); } item_z  // recursion
           | { }                                       // base case
           ;
 
endsequence
 
// ── Key facts ─────────────────────────────────────────────────
// ✓ Procedural — runs in tasks/functions/initial/always
// ✓ Grammar expands from start production recursively
// ✓ | separates alternatives (equal weight without :=)
// ✓ := assigns weight to an alternative
// ✓ Items in a sequence execute strictly left-to-right
// ✗ No interaction with constraint solver or rand fields

Verification Usage — Where randsequence Fits in Real Testbenches

randsequence shows up in three reliable architectural roles. Each addresses a need that simpler weighted choice (randcase, dist) cannot express cleanly: structured stimulus with compositional grammar.

Protocol scenario grammars

The canonical use: a protocol allows transaction patterns like "address phase → 1-N data beats → response → optional idle." The grammar mirrors the spec's allowed phrase structure, and randsequence generates legal sequences by walking the grammar.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task axi_seq::body();
  randsequence (transaction)
    transaction : addr_phase data_beats resp_phase;
    addr_phase  : { drive_addr(); };
    data_beats  : repeat($urandom_range(1, 16)) one_beat;
    one_beat    : { drive_beat(); };
    resp_phase  : 8 := { drive_resp(); }
                | 2 := { drive_error_resp(); };
  endsequence
endtask</code>

Error-injection scenario libraries

Different error scenarios share most of the protocol grammar but diverge at specific branches — "happy path" picks normal responses; "error injection" picks corrupted responses. randsequence with weighted alternatives encodes both within one grammar.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (xact)
  xact : header payload trailer;
  payload : normal_payload | corrupted_payload;
  // Weights adjustable per scenario
  normal_payload    : 95 := { drive_normal_payload(); };
  corrupted_payload :  5 := { drive_bad_crc_payload(); };
endsequence</code>

Test-layer grammar overrides via embedded code blocks

Embedded code blocks let test-layer configuration influence grammar choices — weights, repeat counts, conditional branches.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (test_seq)
  test_seq : setup body_pattern teardown;
  body_pattern : repeat (cfg.burst_count) one_xact;
  one_xact : cfg.write_weight := drive_write()
           | cfg.read_weight  := drive_read()
           | 1                  := drive_idle();   // safety
endsequence</code>

Simulation Behavior — How the Simulator Walks the Grammar

Grammar expansion at runtime

When randsequence (start_symbol) begins, the simulator walks the grammar starting from the named symbol. At each alternative, it evaluates the weight expressions and picks one branch via weighted random choice. The chosen branch may itself be a sequence of sub-productions, which are expanded recursively. The result is a derivation tree of leaves (code blocks) in the order they should execute.

Execution order

After expansion, the simulator visits each leaf code block in tree-traversal order (left-to-right, depth-first) and executes it. Code blocks are arbitrary procedural code — function/task calls, assignments, control flow. They execute synchronously in the calling process.

repeat as bounded iteration

The repeat (N) production form expands the production exactly N times at grammar-expansion time. N is evaluated once when the repeat node is reached; subsequent code-block executions don't re-evaluate it. This gives the simulator bounded knowledge of grammar size — critical for avoiding stack overflow from unbounded recursion.

if-else within productions

The if (cond) production1 else production2 form is evaluated at grammar-expansion time. cond is read once when the if node is reached; based on its value, only one production is added to the derivation tree. The other is never expanded or executed.

No constraint solver involvement

randsequence is procedural — like randcase, it doesn't go through the constraint solver. Weighted choices use the simulator's main PRNG. This makes it fast and class-context-free.

✅ Bounded repeat form
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (s)
  s : repeat ($urandom_range(1, 8)) item;
  item : { drive_one(); };
endsequence
// Always produces 1-8 items; bounded.</code>
❌ Left-recursive — risk of stack overflow
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (s)
  s : 1 := s item
    | 1 := item;
  item : { drive_one(); };
endsequence
// Recursive expansion: simulator may
// stack-overflow before reaching the
// base case. Use repeat instead.</code>

Waveform Analysis — Tracing Generated Sequences

randsequence produces a stream of code-block executions. Logging from each code block makes the generated sequence visible.

The instrumentation pattern

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task body();
  randsequence (xact)
    xact : addr_phase data_phase resp_phase;
    addr_phase  : { `uvm_info("SEQ", "addr_phase", UVM_HIGH) drive_addr(); };
    data_phase  : repeat ($urandom_range(1, 4)) one_beat;
    one_beat    : { `uvm_info("SEQ", "one_beat", UVM_HIGH) drive_beat(); };
    resp_phase  : 8 := { `uvm_info("SEQ", "ok_resp", UVM_HIGH) drive_resp(); }
                | 2 := { `uvm_info("SEQ", "err_resp", UVM_HIGH) drive_err(); };
  endsequence
endtask</code>

ASCII view — generated sequences on the wire

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>  100ns  SEQ  addr_phase
  150ns  SEQ  one_beat
  200ns  SEQ  one_beat
  250ns  SEQ  one_beat
  300ns  SEQ  ok_resphappy path (80% probability)
  ...
  900ns  SEQ  addr_phase
  950ns  SEQ  one_beat
 1000ns  SEQ  one_beat
 1050ns  SEQ  err_resperror path (20% probability)
        ↑                ↑
   transaction      structured pattern visible:
   boundary         addr1-4 beatsresp</code>

Coverage validation per production

Pair each production with a coverpoint that bins by which alternative was chosen. The coverage proves each alternative actually fires; bins firing at unexpected frequencies surface weight or grammar bugs early.

Industry Insights — Hard-Won Lessons About randsequence

Debugging Academy — Five Real Bugs From randsequence Misuse

1

"Left-recursive production crashes the simulator"

DEBUG
Symptom

A test runs the first transaction successfully then the simulator dies with stack overflow. The trace points at randsequence grammar expansion.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (list)
  list : 1 := list item   // BUG: left recursion
       | 1 := item;
  item : { drive_one(); };
endsequence</code>
Root Cause

The simulator expands list by either taking the recursive branch (which calls list again) or the base branch (terminating). With equal weights, it may recurse 1000 times before terminating — eventually exhausting the stack.

Fix

Use repeat for bounded iteration: randsequence (list) list : repeat ($urandom_range(1, 16)) item; item : { drive_one(); }; endsequence. The simulator knows up-front how many iterations to expand; no recursion, no stack risk.

2

"Weighted alternative fires far less than expected"

DEBUG
Symptom

A randsequence declares 80 := happy | 20 := error but coverage shows happy:error ratio of 99:1.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (xact)
  xact : happy | error;
  happy : 80 := { drive_normal(); };    // expected 80%
  error : 20 := { drive_error(); };     // expected 20%
                                          // actual: ~99% happy
endsequence
 
// Issue: weights belong on the alternatives in `xact`, not on the productions</code>
Root Cause

Weights are written on the wrong side. In xact : happy | error;, the alternatives between happy and error are equally weighted (no weight = weight 1). The "80" and "20" on production bodies are not weights — they may be silently ignored or interpreted as something else depending on the simulator.

Fix

Put weights on the alternatives in the parent production: randsequence (xact) xact : 80 := happy | 20 := error; happy : { drive_normal(); }; error : { drive_error(); }; endsequence. Now the 80/20 weighting applies as intended.

3

"Code block updates variable, later branch reads stale value"

DEBUG
Symptom

A grammar where one code block sets a flag and a later branch checks the flag appears to never see the updated value.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>int err_count = 0;
randsequence (s)
  s : set_err check_err;
  set_err   : { err_count = 5; };
  check_err : if (err_count > 0)
                { drive_error_handler(); }
              else
                { drive_normal(); };
endsequence
 
// drive_normal() runs even though set_err just set err_count = 5</code>
Root Cause

The if in the grammar is evaluated at expansion time, before any code blocks run. err_count is still 0 at expansion; the grammar tree commits to drive_normal() before set_err ever executes.

Fix

Move the conditional logic into the code block: check_err : { if (err_count > 0) drive_error_handler(); else drive_normal(); };. Now the condition is evaluated at execution time, after the earlier code block has run. The if at grammar level is for structural decisions made before any code runs; runtime decisions belong inside code blocks.

4

"Production name collision with task name causes wrong behaviour"

DEBUG
Symptom

A grammar uses a production named drive_xact which happens to also be the name of an external task. The compiler accepts it but behaviour is unexpected.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task drive_xact();
  ...
endtask
 
task body();
  randsequence (xact)
    xact : repeat (5) drive_xact;   // BUG: collides with task name
    drive_xact : { drive_one(); };   // production
  endsequence
endtask</code>
Root Cause

The production name drive_xact shadows the task with the same name in the local scope. Some simulators warn; others silently choose one or the other; behaviour is unpredictable across tools.

Fix

Use distinct names for productions vs external tasks. Production naming convention that helps: prefix productions with "p_" or "seq_" so they're visually distinguishable from regular task/function names. seq_drive_xact or p_drive_xact can never collide with a task named drive_xact.

5

"All weights evaluate to 0 — production fails to select"

DEBUG
Symptom

A grammar with config-driven weights occasionally fails with "no production selected" or silent fall-through.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>randsequence (xact)
  xact : cfg.write_w := write_seq
       | cfg.read_w  := read_seq;
  // If both cfg fields are 0 → no selection possible
  write_seq : { drive_write(); };
  read_seq  : { drive_read(); };
endsequence</code>
Root Cause

Same as the randcase all-zero-weights case: when every alternative's weight evaluates to 0, the simulator cannot select. Behaviour is implementation-defined.

Fix

Always include a literal-weight safety alternative: xact : cfg.write_w := write_seq | cfg.read_w := read_seq | 1 := idle_seq;. The safety branch fires when others are all 0; otherwise it's a small uniform contribution. Defensive practice for any grammar with expression weights.

Interview Q&A — Twelve Questions You Will Be Asked

Generates structured random stimulus by walking a grammar of production rules. Each production names a sequence of sub-productions and/or code blocks; weighted alternatives let the simulator pick one of several paths at each branching point. The result is a sequence of code-block executions in tree-traversal order.

Best Practices — Ten Rules for randsequence Discipline

  1. Use randsequence for compositional grammar structure; use randcase for single weighted choices. Different tools for different complexity levels.
  2. Always prefer repeat (N) production over left-recursive productions. Bounded expansion avoids stack overflow risk entirely.
  3. Always include a literal-weight safety alternative when weights are expressions. Prevents the all-zero-weights implementation-defined case.
  4. Mirror the protocol spec's BNF in your grammar. The verification grammar then reads like the spec grammar — direct review, fast updates.
  5. Keep productions small and composable. Each production should be a short sequence of sub-productions plus optionally one code block. Factor longer productions into sub-grammars.
  6. Use distinct names for productions vs external tasks/functions. Name collisions cause implementation-defined behaviour. Prefix productions (p_, seq_) to make them visually distinct.
  7. Remember grammar-level if and repeat evaluate at expansion time. Runtime decisions must live inside code blocks, not in the grammar structure.
  8. Log production names from code blocks during bring-up. Production names in logs make grammar-path diagnosis a one-line log scan.
  9. Pair every interesting alternative with a coverage bin. The coverage proves each grammar path is actually exercised.
  10. Don't synthesise randsequence. Strictly simulation-only; keep in verification directories or behind ``ifdef` guards.