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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
    • Sequence Items and the Driver Handshake
      • Item vs Driver Boundary: What Belongs Where
      • Constraints and randomize() with: Legal Space, Defaults and Corners
      • Derived Items: Narrower Stimulus Without Editing the Agent
      • Field Macros and Item Ownership: Who May Change an Item
      • The Driver Handshake: start_item, finish_item, get_next_item, item_done
      • Stalled Sequences: Hang Triage
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • Virtual Sequences and Coordination
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Sequence Items and the Driver Handshake
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Sequence Items and the Driver Handshake
  5. Constraints and randomize() with: Legal Space, Defaults and Corners

Part 5 · Sequences · Intermediate

Constraints and randomize() with: Legal Space, Defaults and Corners

The three layers of constraints in the DMA bench: class constraints for legality, a soft default in dma_job_cfg, and in-line constraints in sequences. local::, := versus :/, checked returns, rand_mode and constraint_mode.

On this page

    A code review comment on apb_rw_seq: "local:: is noise here, the names are obvious." The author deletes it from addr == local::addr. It compiles cleanly. That night every DMA test times out, and the APB monitor shows writes to addresses nobody asked for.

    This lesson explains the three layers of constraints a sequence item lives under, how a sequence aims one call at a corner, and why that deleted word turned a register write into a random access.

    The idea: building code, the architect's default, and the client's request

    A house is shaped by three kinds of rules. The building code applies to every house and cannot be waived: that is a class constraint, such as word_aligned_c. The architect's default applies unless someone asks otherwise: that is a soft constraint. The client's request applies to this one house: that is an in-line constraint, the block you pass to randomize() with { ... }. (The language standard reserves the word in-line for that block; constraints declared in the class are class constraints, or constraint blocks.)

    A request may override a default but never the code. If it tries, randomize() fails and returns 0, and the item's random fields keep their previous values. That is why every randomize call in the bench is checked.

    The picture: the three layers in the bench

    Layer

    Bench example

    Applies to

    Who can override it

    Class constraint (hard)

    word_aligned_c { addr[1:0] == 2'b00; } in apb_item

    Every randomize call

    Nobody, short of constraint_mode(0) or a derived class

    Class constraint with a distribution

    idle_c { idle dist {...}; } in axis_item

    Every call; shapes the odds

    An in-line constraint that pins idle

    Soft constraint

    irq_c { soft irq_en == 1; } in dma_job_cfg

    Every call, unless contradicted

    Any hard constraint, including an in-line one

    In-line constraint

    randomize() with { addr == local::addr; ... } in apb_rw_seq

    This call only

    Nothing: it is hard for this call

    Build it, layer by layer

    Class constraints: legality, and the odds

    apb_item forbids misaligned addresses. axis_item goes further and shapes how often each idle count appears: idle dist { 0 := 6, [1:3] := 3, [4:8] := 1 }. The operator matters, and it is a favourite interview question. := gives the weight to each value in a range; :/ splits the weight across the range. So the bench's [1:3] := 3 gives each of 1, 2 and 3 a weight of 3, a total of 9, and [4:8] := 1 totals 5. With 6 for zero, the odds are 30 / 45 / 25 percent, not the 60 / 30 / 10 the numbers might suggest. This model runs both versions:

    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
    // Same ranges, two weight operators: := weights each value, :/ splits the weight across the range.
    class beat_per_value;            // the bench's axis_item idle constraint
      rand int unsigned idle;
      constraint idle_c { idle dist { 0 := 6, [1:3] := 3, [4:8] := 1 }; }
    endclass
    
    class beat_per_range;
      rand int unsigned idle;
      constraint idle_c { idle dist { 0 :/ 6, [1:3] :/ 3, [4:8] :/ 1 }; }
    endclass
    
    module tb_dist;
      beat_per_value a = new();
      beat_per_range b = new();
      int a0, a13, a48, b0, b13, b48;
      initial begin
        for (int i = 0; i < 2000; i++) begin
          if (!a.randomize() || !b.randomize()) $fatal(1, "randomize failed");
          if (a.idle == 0) a0++; else if (a.idle <= 3) a13++; else a48++;
          if (b.idle == 0) b0++; else if (b.idle <= 3) b13++; else b48++;
        end
        $display(":=  idle=0 %0d%%  1..3 %0d%%  4..8 %0d%%", a0/20, a13/20, a48/20);
        $display(":/  idle=0 %0d%%  1..3 %0d%%  4..8 %0d%%", b0/20, b13/20, b48/20);
        // Expected: := gives 6 : 3*3 : 5*1 = 30/45/25; :/ gives 6:3:1 = 60/30/10.
        if (a0/20 inside {[27:33]} && a13/20 inside {[42:48]} && b0/20 inside {[57:63]} && b48/20 inside {[7:13]})
          $display("PASS: := weights each value, :/ splits the weight across the range");
        else
          $display("FAIL: unexpected distribution");
        $finish;
      end
    endmodule
    Diagram
    :=  idle=0 29%  1..3 46%  4..8 24%
    :/  idle=0 61%  1..3 29%  4..8 9%
    PASS: := weights each value, :/ splits the weight across the range

    Neither is wrong; they are different stimulus. The bench wants mostly back-to-back beats with a fair number of short gaps, and := gives exactly that. If someone "tidies" it to :/, long gaps nearly vanish and the backpressure corner gets a tenth of its traffic.

    Soft constraints: a default that a test may override

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet: from the course DMA bench
    // One DMA job as a randomizable object. A test can randomize it, or put one in
    // config_db so a test further down the hierarchy picks it up.
    class dma_job_cfg extends uvm_object;
      rand bit [31:0]   key;
      rand int unsigned len;
      rand bit          irq_en;
      constraint len_c { len inside {[1:16]}; }
      constraint irq_c { soft irq_en == 1; }
    
      `uvm_object_utils(dma_job_cfg)
      function new(string name = "dma_job_cfg");
        super.new(name);
      endfunction
    
      function string convert2string();
        return $sformatf("key=%08h len=%0d irq_en=%0b", key, len, irq_en);
      endfunction
    endclass

    Random jobs should normally enable the interrupt, because the tests wait for it. But one test needs the opposite: dma_timeout_test wants a job whose interrupt never fires. It does not edit the class; it overrides the default in one call:

    design.sv
    // snippet: from dma_timeout_test::run_phase
    if (!job.randomize() with { len == 4; irq_en == 0; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")

    A soft constraint is dropped whenever it conflicts with a hard one, and the in-line irq_en == 0 is hard. Had irq_c been a hard constraint, this call would fail and the fatal would fire. When two soft constraints conflict, the higher-priority one wins: an in-line soft constraint beats a class one, and a derived class's soft constraint beats its parent's.

    In-line constraints, and what local:: really does

    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
    // snippet: from the course DMA bench
    // One APB access. The test or a parent sequence fills addr/write/data.
    class apb_rw_seq extends uvm_sequence #(apb_item);
      `uvm_object_utils(apb_rw_seq)
      rand bit [7:0]  addr;
      rand bit        write;
      rand bit [31:0] data;
      bit      [31:0] rdata;
      bit             slverr;
    
      function new(string name = "apb_rw_seq");
        super.new(name);
      endfunction
    
      task body();
        apb_item it = apb_item::type_id::create("it");
        start_item(it);
        if (!it.randomize() with { addr == local::addr; write == local::write; data == local::data; })
          `uvm_fatal("RAND", "apb_item randomize failed")
        finish_item(it);                  // returns after the driver's item_done
        rdata  = it.rdata;
        slverr = it.slverr;
      endtask
    endclass

    apb_rw_seq has fields called addr, write and data, and so does the item it randomizes. Inside with { ... }, a bare name is looked up in the object being randomized first, and only then in the calling scope. So addr == addr compares the item's addr with itself: it is always true and constrains nothing. local::addr tells the compiler to resolve the name in the caller's scope, which here is the sequence. This model shows the difference:

    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
    // Why apb_rw_seq writes local::addr inside randomize() with.
    class item;                      // stands in for apb_item
      rand bit [7:0] addr;
      constraint word_c { addr[1:0] == 2'b00; }
    endclass
    
    class rw_seq;                    // stands in for apb_rw_seq: it has a field with the same name
      bit [7:0] addr = 8'h0C;        // the register the caller asked for (KEY)
      int hit_plain, hit_local;
    
      function void run(int n);
        item it = new();
        repeat (n) begin
          // Inside with { }, a bare name resolves to the object being randomized first:
          // this compares it.addr with itself, so it constrains nothing.
          if (!it.randomize() with { addr == addr; }) $fatal(1, "randomize failed");
          if (it.addr == 8'h0C) hit_plain++;
          // local:: makes the name resolve in the caller's scope: the sequence's addr.
          if (!it.randomize() with { addr == local::addr; }) $fatal(1, "randomize failed");
          if (it.addr == 8'h0C) hit_local++;
        end
      endfunction
    endclass
    
    module tb_local;
      rw_seq s = new();
      initial begin
        s.run(200);
        $display("addr == addr        hit KEY %0d of 200 times", s.hit_plain);
        $display("addr == local::addr hit KEY %0d of 200 times", s.hit_local);
        if (s.hit_local == 200 && s.hit_plain < 50)
          $display("PASS: only local::addr pins the item to the requested register");
        else
          $display("FAIL: plain=%0d local=%0d", s.hit_plain, s.hit_local);
        $finish;
      end
    endmodule
    Diagram
    addr == addr        hit KEY 6 of 200 times
    addr == local::addr hit KEY 200 of 200 times
    PASS: only local::addr pins the item to the requested register

    Six hits in 200 is chance: there are 64 aligned addresses. Now compare axis_pkt_seq, which writes last == (i == n - 1) with no local::. That is correct only because axis_item has no field called i or n, so the names fall through to the sequence. If someone later adds an n field to the item, the constraint silently changes meaning. Writing local:: for every caller variable costs nothing and removes the trap.

    Randomize late, and always check the result

    The bench randomizes after start_item returns, that is, after the sequencer has granted the sequence its turn. This is called late randomization, and it means the values are chosen at the last moment, when the sequence can still look at the current state of the test. Every call is wrapped in if (!...randomize()) uvm_fatal(...). Never write assert(it.randomize()): if someone disables assertions to silence a noisy check, the whole assert statement is skipped, including the call to randomize(), and every item goes out with stale values.

    Tool

    What it does

    Watch out for

    rand_mode(0) on a field

    Stops randomizing it; you assign it directly

    It stays off until rand_mode(1), across later calls

    constraint_mode(0) on a block

    Turns a named class constraint off, for example word_aligned_c in an error test

    Also persistent; turn it back on

    solve a before b

    Changes the odds of a combination, never which values are legal

    Not a fix for a failing randomize

    pre_randomize / post_randomize

    Hooks around the solver; post_randomize is the place to compute derived fields

    post_randomize does not run when randomize fails

    Watch it run: the default and the override

    dma_rand_test randomizes three jobs with no in-line constraint on irq_en, so the soft default holds every time (values differ by simulator and seed):

    Diagram
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1

    dma_timeout_test overrides the default with irq_en == 0. The interrupt never fires, the test waits forever for it, and the built-in timeout ends the run, which is exactly what that test is for:

    Diagram
    UVM_FATAL uvm_phase_hopper.svh(598) @ 20000000: reporter [PH_TIMEOUT] Explicit timeout of 20000000 hit, indicating a probable testbench issue

    What breaks: the review comment that randomized a register write

    With local:: removed, apb_rw_seq still compiled and still ran, but every reg_write became a write to a random aligned address, right only about one time in 64. The write of GO to CTRL almost never landed, so no job started, no interrupt came, and every test sat in wait (irq_vif.irq === 1'b1) until the timeout. The writes that fell into the holes came back with PSLVERR, which nobody checked. The scoreboard said nothing at all: there was no data to compare, and a PH_TIMEOUT fatal ends the run before check_phase can report that nothing was compared.

    The fix was to restore the three local:: prefixes and add one line to the team's review checklist: in a `with` block, every name that belongs to the caller gets `local::`. A cheaper safety net would have caught it on the first run: the sequence could check slverr after finish_item, or a register-aware scoreboard could flag writes to unmapped addresses.

    Interview

    "How do you make one test change a constraint without editing the item class?"

    A weak answer: "Edit the constraint in the class," or "turn off randomization and assign everything." The first breaks every other test and is impossible for third-party VIP; the second throws away the random coverage.

    A strong answer: "It depends on how the constraint was written. A soft default is overridden by an in-line constraint in that test's sequence, like irq_en == 0 in the timeout test. A hard rule that must be broken for an error test is switched off with constraint_mode(0) on that block, or replaced in a derived item that redeclares the block under the same name and is swapped in with a factory override. And I write local:: for every caller variable inside with, because a bare name resolves to the item first."

    Follow-ups: What is the difference between `:=` and `:/`? (per value versus per range; the bench's idle odds are 30/45/25, not 60/30/10). What state is the item in after a failed randomize? (its random fields keep their previous values and post_randomize does not run, so an unchecked failure sends the previous transaction again).

    Check yourself A test calls it.randomize() with { addr == 8'h0D; } on an apb_item. What happens?

    word_aligned_c requires addr[1:0] == 0, and 0x0D has its low bits set, so the constraints conflict and randomize() returns 0. The bench's check fires uvm_fatal("RAND", ...). Without the check, the item would keep its previous field values and the driver would send the previous access again.

    Check yourself axis_item gains a new rand int unsigned n field for packet metadata. What happens to axis_pkt_seq's constraint last == (i == n - 1)?

    n now resolves to the item's own new field, not the sequence's packet length, because names in a with block are looked up in the randomized object first. last becomes tied to a random value, and packets end at the wrong beat. Writing local::n (and local::i) restores the meaning and protects against this kind of change in future.

    Check yourself Test A writes job.irq_en = 0; job.randomize();. Test B writes job.randomize() with { irq_en == 0; }. Which one gets a job with the interrupt disabled?

    Only test B. In test A, irq_en is still a random variable, so randomize() overwrites the assigned 0 and the soft default makes it 1. To assign it by hand you would need job.irq_en.rand_mode(0) first, and remember that it stays non-random afterwards.

    Next: some tests need a different kind of item, not just a different value. Derived items narrows apb_item to the DMA's register map, and to its unmapped holes, without touching the agent.

    Related topics

    • Randomization basics

    • Solver deep dive

    • Configuring sequences

    Previous

    74 / 280

    Next: Derived Items: Narrower Stimulus Without Editing the Agent

    Up next

    Up nextDerived Items: Narrower Stimulus Without Editing the Agent

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Field Macros and Item Ownership: Who May Change an Item
    • The Driver Handshake: start_item, finish_item, get_next_item, item_done
    • Stalled Sequences: Hang Triage