Part 5 · Sequences · Intermediate

Constraints & randomize() with Blocks

Inline constraint blocks, per-call randomize() with, conflict handling, and directed corners inside randomized sequences.

Why constraints live in items

Randomization without constraints produces illegal transactions — misaligned addresses, out-of-range burst lengths, reserved opcodes. Constraints in the item class define the legal protocol space : every randomize() call produces a valid transaction unless the caller adds conflicting directed constraints. This keeps sequences thin — they call randomize(), not hand-pick legal values.

Separating legal space (inline constraints) from test intent (randomize() with blocks) is a key UVM pattern. Inline constraints always apply; with blocks add temporary constraints for one call — perfect for hitting corners inside a randomized stream.

When constraints conflict, randomize() returns 0. Unchecked failures are a top cause of 'mystery' stimulus bugs — the sequence proceeds with stale or null data.

diagram
[ITEM] constraint layers

  INLINE (in class)      always active — defines legal protocol space
  randomize()            explores full legal space
  randomize() with {}    adds TEMPORARY constraints for one call
  CONFLICT               returns 0 — MUST check

Inline constraints — the legal space

Inline constraint blocks in the class body define what randomize() may produce. They encode protocol rules from the spec — alignment, supported burst lengths, valid opcodes.

systemverilog
class apb_item extends uvm_sequence_item;
  rand bit [31:0] addr, data;
  rand bit        write;

  constraint word_aligned {
    addr[1:0] == 2'b00;
  }

  constraint addr_map {
    addr inside {[32'h0000:32'hFFFF]};
  }

  // Empty constraint block — placeholder for factory override
  constraint c_data {}
endclass
  • word_aligned encodes hardware rule — APB word accesses only.

  • addr inside {...} restricts to memory map range.

  • Empty blocks (c_data) allow factory/set_type_override to replace constraints.


randomize() with — directed corners in randomized streams

Inside a sequence body(), randomize() with { ... } adds temporary constraints for a single call. Use this for directed setup beats inside a mostly-random stress stream.

systemverilog
task body();
  apb_item req;
  req = apb_item::type_id::create("req");

  // Beat 1: fully random legal transaction
  start_item(req);
  if (!req.randomize())
    `uvm_fatal("RAND", "randomize failed")
  finish_item(req);

  // Beat 2: directed corner — fixed addr, random data
  start_item(req);
  if (!req.randomize() with { addr == 32'hDEAD0000; write == 1; })
    `uvm_fatal("RAND", $sformatf("with-block failed: %s", req.sprint()))
  finish_item(req);

  // Beat 3: read at same address
  start_item(req);
  if (!req.randomize() with { addr == 32'hDEAD0000; write == 0; })
    `uvm_fatal("RAND", "read corner failed")
  finish_item(req);
  `uvm_info("READ", $sformatf("rdata=0x%08x", req.rdata), UVM_MEDIUM)
endtask
diagram
[SEQ] randomize() with in stimulus flow

  [STIM] test wants: random traffic + one directed poke at 0xDEAD0000

  [SEQ] apb_mixed_seq:
    repeat(50) randomize();           ← explores legal space
    randomize() with { addr==DEAD; }; ← directed corner
    repeat(50) randomize();           ← back to random

  [ITEM] inline constraints still apply — cannot randomize misaligned addr

Failure handling — never ignore return value

When inline and with constraints conflict, randomize() returns 0. Common conflicts: directed addr outside addr_map, write==1 with a read-only constraint in a derived class, over-constrained id uniqueness.

systemverilog
// Always check — fatal in sequences, error in functions
if (!req.randomize() with { addr == 32'h1000; write == 1; })
  `uvm_fatal("RAND_FAIL", $sformatf(
    "constraint conflict on %s", req.sprint()))

// Debug aid: print constraint solver state (simulator-dependent)
void'(req.randomize() with { addr == bad_addr; });  // returns 0
req.print();  // shows last successful randomize state — misleading!
diagram
[ITEM] common randomize() failures

  addr == 32'h1001  +  word_aligned (addr[1:0]==0)   FAIL
  write == 0        +  wr_only { write==1; }        FAIL (derived item)
  id unique array full                              FAIL (soft constraint issue)

  Symptom: sequence hangs or drives stale data from previous randomize
  • Fatal in sequences — bad randomize means broken test, not soft warning.

  • Log req.sprint() on failure — shows field state at failure time.

  • Use rand_mode(OFF) + direct assignment for fully directed beats (no randomize).


Soft constraints and distribution

Soft constraints express preferences, not requirements — the solver may violate them if hard constraints demand it. dist constraints weight value distributions for stress.

systemverilog
class axi_item extends uvm_sequence_item;
  rand bit [31:0] addr;
  rand bit [3:0]  id;

  constraint c_addr {
    addr[1:0] == 2'b00;
    soft addr inside {[32'h0000:32'h0FFF]};  // prefer low mem, not required
  }

  constraint c_id_dist {
    id dist { [0:3] := 80, [4:15] := 20 };   // weight low IDs
  }
endclass

When to use soft vs with-block

  • Soft — default bias across all randomize calls (e.g., prefer aligned bursts).

  • with-block — explicit directed corner for one beat in one sequence.

  • Hard inline — protocol legality that must never be violated.


APB register programming pattern

Register sequences combine directed with-blocks for setup and random traffic for stress — the most common constraint pattern in block-level verification:

systemverilog
task program_reg(bit [31:0] reg_addr, bit [31:0] val);
  apb_item req = apb_item::type_id::create("req");
  start_item(req);
  assert(req.randomize() with {
    write == 1;
    addr  == reg_addr;
    data  == val;
  });
  finish_item(req);
endtask

task body();
  program_reg(CTRL_REG, 32'h1);   // directed setup
  repeat (100) begin              // random stress
    start_item(req);
    assert(req.randomize() with { write inside {0,1}; });
    finish_item(req);
  end
endtask

Key takeaways

  • Inline constraints = legal protocol space; with-blocks = per-call directed corners.

  • Always check randomize() return — silent failure produces stale or illegal data.

  • Combine random beats and directed with-blocks in one sequence for realistic tests.

Common pitfalls

  • Ignoring randomize() return value — #1 subtle stimulus bug.

  • Over-constraining with blocks that fight inline constraints — solver returns 0.

  • Using randomize for fully directed setup — use rand_mode(OFF) and assign instead.

  • Assuming last randomize state after failure — fields may be unchanged or partial.