Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
    • Randomization Basics: rand, randc & randomize()
      • rand vs randc
      • The randomize() Call
      • Choosing a Randomize Tool
      • Hooks and Nested Objects
      • Null Handles and Object Arrays
      • Random Stability and Seeding
    • Constraint Operators & Expressions
    • Dynamic Constraint Control
    • Randomizing Arrays and Queues
    • How the Constraint Solver Works
    • Debugging Randomization Failures
    • Interview Drills — Constraints and Randomization Q&A
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Randomization Basics: rand, randc & randomize()
  1. Topics
  2. SystemVerilog
  3. Constraint Randomization — Interview-Critical Deep Dive
  4. Randomization Basics: rand, randc & randomize()
  5. Choosing a Randomize Tool
On this page

    Part 3 of 9, lesson 3 of 39Part 3 of 9/Constraint Randomization/Lesson 3 of 39Intermediate

    Choosing a Randomize Tool

    Class randomize, std::randomize with and $urandom_range: which tool for which draw, with PacketFIFO packets.

    A test drew a burst count and a mode with two separate $urandom calls, and the rule that stress mode needs more than 30 transactions was broken one run in three. Independent draws cannot hold a relationship.

    After this lesson you can: pick class randomize, std::randomize with or $urandom_range for a job, and write a first random PacketFIFO transaction.

    The previous lesson covered the class call and its contract. Two lighter tools exist beside it, and picking the lightest one that can express the rule keeps benches fast and failures easy to read.

    Which tool for which draw

    Class randomization suits transactions: reusable objects with intrinsic legality rules. Scope randomization suits local, one-off decisions where a class would be ceremony. Plain draws suit single bounded values.

    Pick the lightest tool that can express the rule.
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // not-run: fragment (bus_txn is the class from the previous lesson; clk and drive() are not defined here)
    // Typical test-level mix of all three tools:
    task run_traffic(bus_txn t);
      int n_txns, mode;
    
      // joint local decision: std::randomize
      assert (std::randomize(n_txns, mode) with {
                n_txns inside {[10:50]};
                mode   inside {[0:2]};
                (mode == 2) -> n_txns > 30;    // stress mode needs volume
              });
    
      repeat (n_txns) begin
        assert (t.randomize());                 // transaction: class solve
        drive(t);
        repeat ($urandom_range(0, 5))           // simple gap: plain draw
          @(posedge clk);
      end
    endtask

    Each call sits at the right weight class. The std randomize jointly picks count and mode under the implication, while the class call runs the transaction rules.

    $urandom_range draws a plain gap with no solver involved.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // not-run: fragment (clk and send_burst() are not defined here)
    task drive_idle_gap();
      int unsigned gap;
      bit [3:0]    burst;
    
      // Solve gap and burst TOGETHER under shared constraints
      if (!std::randomize(gap, burst) with {
            gap inside {[1:100]};
            burst inside {[1:8]};
            gap > burst * 10;          // relationship between the two
          })
        $error("std::randomize failed");
    
      repeat (gap) @(posedge clk);
      send_burst(burst);
    endtask

    The with-clause is solved jointly: the solver picks a legal pair, not gap then burst. Variables from the surrounding scope that appear in the clause but not in the argument list are state variables, read and never written.

    $urandom returns a 32-bit unsigned value and $urandom_range bounds it inclusively, tolerating swapped arguments. They are thread-stable system functions, not solver calls: no constraints, no failure mode, far cheaper than a solve.

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // not-run: illustration with no self-check; Icarus also rejects a $urandom seed that is not a variable
    module top;
      bit [31:0] raw;
      int unsigned d6;
      initial begin
        raw  = $urandom;                  // full 32-bit draw
        d6   = $urandom_range(6, 1);      // inclusive [1:6]
        d6   = $urandom_range(1, 6);      // same: arguments may be swapped
        raw  = $urandom(42);              // seed is an input expression: reseeds this
                                          // thread's RNG (unlike $random's inout seed)
    
        // What $urandom CANNOT do:
        //   pick (a,b) with a+b==100        -> needs std::randomize with
        //   honor class legality rules      -> needs class randomize
        //   cyclic no-repeat values         -> needs randc
      end
    endmodule

    $urandom_range

    std::randomize

    class randomize

    Constraints

    none

    with-clause

    class plus inline with

    Cross-variable

    no

    yes

    yes

    Can fail

    no

    yes, returns 0

    yes, returns 0

    Cost

    trivial

    solver call

    solver call

    Random stream

    calling thread

    calling thread

    the object

    Rule reuse

    none

    none, inline

    high, inside the class

    Typical use

    gaps, delays

    local joint picks

    transactions

    Testbench

    A joint std::randomize with an implication, and $urandom_range bounds. The class call was checked in the previous lesson.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module tb_tools;
      int ok, n_txns, mode, gap;
      initial begin
        // 1. std::randomize solves local variables jointly.
        repeat (100) begin
          if (!std::randomize(n_txns, mode) with { n_txns inside {[10:50]}; mode inside {[0:2]}; (mode == 2) -> n_txns > 30; })
            $fatal(1, "FAIL: std::randomize failed");
          if (mode == 2 && n_txns <= 30) $fatal(1, "FAIL: stress mode with only %0d txns", n_txns);
        end
        // 2. $urandom_range: bounds inclusive, arguments in either order.
        repeat (100) begin
          gap = $urandom_range(5, 0);
          if (gap < 0 || gap > 5) $fatal(1, "FAIL: gap %0d", gap);
        end
        $display("PASS: std::randomize and $urandom_range held their rules");
        $finish;
      end
    endmodule
    Terminal
    PASS: std::randomize and $urandom_range held their rules
    Check yourself Two locals must satisfy a plus b equals 100. Why can two $urandom_range calls not do it?

    Independent draws cannot express a relationship between variables. One std::randomize with a plus-equals constraint solves the pair jointly.

    Make pkt_txn random: rand kind, dest, len and a fixed eight-byte payload, with len limited to 1 to 8. Check every randomize() call, and over 500 calls confirm that every length and every kind appears.

    One way to write it:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    typedef enum bit [1:0] {DATA = 2'd0, CTRL = 2'd1, ERR = 2'd2} kind_e;
    
    // PacketFIFO transaction with rand fields: the solver now builds legal packets.
    class pkt_txn;
      rand kind_e     kind;
      rand bit [1:0]  dest;
      rand bit [3:0]  len;
      rand bit [7:0]  payload[8];          // fixed size; only the first len bytes are sent
      constraint c_len  { len inside {[1:8]}; }
    
      function bit [7:0] header();
        return {kind, dest, len};
      endfunction
    
      function bit [7:0] check_byte();
        bit [7:0] c = header();
        for (int i = 0; i < len; i++) c ^= payload[i];
        return c;
      endfunction
    endclass
    
    module tb_pkt_rand;
      pkt_txn p = new();
      int lens[int];
      int kinds[int];
      initial begin
        repeat (500) begin
          if (!p.randomize()) $fatal(1, "FAIL: randomize failed");   // every call checked
          if (p.len < 1 || p.len > 8) $fatal(1, "FAIL: illegal len %0d", p.len);
          lens[int'(p.len)]++;
          kinds[int'(p.kind)]++;
        end
        $display("lengths seen: %0d of 8, kinds seen: %0d of 3", lens.num(), kinds.num());
        if (lens.num() != 8 || kinds.num() != 3) $fatal(1, "FAIL: some legal value never appeared");
        $display("PASS: 500 checked solves gave legal packets covering every length and kind");
        $finish;
      end
    endmodule
    Terminal
    lengths seen: 8 of 8, kinds seen: 3 of 3
    PASS: 500 checked solves gave legal packets covering every length and kind
    • $urandom_range is a plain draw: no constraints, no solver, never fails.

    • std::randomize with solves local variables jointly and can return 0.

    • Class randomize suits transactions whose rules belong inside the class.

    Rule of thumb: use the lightest tool that can express the rule.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.

    Related topics

    • Next: Hooks and Nested Objects

    Previous

    66 / 275

    Next: Hooks and Nested Objects

    Up next

    Up nextHooks and Nested Objects

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • rand vs randc
    • Null Handles and Object Arrays
    • Random Stability and Seeding