Part 8 · Senior & Interview Prep · Intermediate

Step 3: Transactions, Constraints & Generator

fifo_txn with layered constraints (legality + scenario knobs: burst/drain/mixed rates), and a generator with weighted scenarios — complete code.

Layered constraints — legality vs scenario shaping

The transaction carries two constraint layers. The legality layer encodes what is always true (hard, never overridden). The scenario layer uses soft constraints and knob fields so tests dial traffic shape — burst, drain, mixed — without subclassing or editing the transaction. The knobs are not random themselves; the generator sets them per scenario phase.

systemverilog
typedef enum bit [1:0] { OP_PUSH, OP_POP, OP_BOTH, OP_IDLE } op_e;

class fifo_txn #(parameter int WIDTH = 8);
  // randomized fields
  rand op_e             op;
  rand bit [WIDTH-1:0]  data;
  rand int unsigned     gap;        // idle cycles before this op

  // scenario knobs — set by the generator, not randomized
  int push_wt  = 5;                 // relative op weights
  int pop_wt   = 5;
  int both_wt  = 2;
  int idle_wt  = 1;
  int max_gap  = 3;

  // legality layer (hard)
  constraint c_gap_legal { gap <= max_gap; }

  // scenario layer (weights via knobs; soft data shaping)
  constraint c_op_dist {
    op dist { OP_PUSH := push_wt, OP_POP  := pop_wt,
              OP_BOTH := both_wt, OP_IDLE := idle_wt };
  }
  constraint c_data_interesting {
    soft data dist { '0 := 1, '1 := 1, [1:(2**WIDTH)-2] :/ 8 };
  }

  function void display(string tag);
    $display("[%0t] %s op=%s data=0x%0h gap=%0d",
             $time, tag, op.name(), data, gap);
  endfunction
endclass

Code walkthrough

  1. op covers all four cycle behaviors — OP_BOTH targets the simultaneous push+pop features (F-07/F-08) directly.

  2. Weights are plain int knobs read inside dist — re-shaping traffic is an assignment, not a new class.

  3. soft on the data distribution: a directed test can pin data == 'hA5 inline without a conflict.

  4. gap randomizes pacing; max_gap == 0 yields full-rate back-to-back traffic.


Generator with weighted scenarios — complete code

systemverilog
typedef enum { SCN_SMOKE, SCN_BURST_FILL, SCN_DRAIN,
               SCN_MIXED, SCN_BACKPRESSURE } scenario_e;

class generator #(parameter int WIDTH = 8);
  mailbox #(fifo_txn #(WIDTH)) gen2drv;
  int n_txns = 200;

  function new(mailbox #(fifo_txn #(WIDTH)) mb);
    gen2drv = mb;
  endfunction

  // configure one txn's knobs for a scenario phase
  function void shape(fifo_txn #(WIDTH) t, scenario_e s);
    case (s)
      SCN_BURST_FILL:   begin t.push_wt=10; t.pop_wt=0;  t.both_wt=0; t.idle_wt=0; t.max_gap=0; end
      SCN_DRAIN:        begin t.push_wt=0;  t.pop_wt=10; t.both_wt=0; t.idle_wt=0; t.max_gap=0; end
      SCN_MIXED:        begin t.push_wt=5;  t.pop_wt=5;  t.both_wt=3; t.idle_wt=1; t.max_gap=2; end
      SCN_BACKPRESSURE: begin t.push_wt=8;  t.pop_wt=1;  t.both_wt=0; t.idle_wt=1; t.max_gap=1; end
      default:          begin t.push_wt=6;  t.pop_wt=4;  t.both_wt=1; t.idle_wt=1; t.max_gap=3; end
    endcase
  endfunction

  task run_scenario(scenario_e s, int count);
    fifo_txn #(WIDTH) t;
    repeat (count) begin
      t = new();
      shape(t, s);
      if (!t.randomize())
        $fatal(1, "generator: randomize failed in scenario %0d", s);
      gen2drv.put(t);              // bounded mailbox → backpressure
    end
  endtask

  // default mix: fill, drain, then sustained mixed traffic
  task run();
    run_scenario(SCN_BURST_FILL,   n_txns/4);  // drive toward full (F-02)
    run_scenario(SCN_DRAIN,        n_txns/4);  // drive toward empty (F-03)
    run_scenario(SCN_MIXED,        n_txns/2);  // hammer simultaneity (F-07)
  endtask
endclass

Why scenario phases instead of one flat distribution

A flat 50/50 push/pop mix hovers around half-full forever — the full and empty boundaries are statistically unreachable in any reasonable run. Phased scenarios deliberately walk the fill level to each boundary, then hammer it with mixed traffic. This is the difference between random stimulus and directed-random stimulus: randomness inside a scenario, intent across scenarios.

diagram
FILL LEVEL OVER ONE DEFAULT RUN

  DEPTH ┤        ╭────╮ burst-fill parks at full ── F-02, F-05 hit
        │       ╱      ╲
        │      ╱        ╲ drain to empty ── F-03, F-06 hit
        │     ╱          ╲      ╭─╮ ╭──╮
        │    ╱            ╲    ╱   ╲╱   ╲   mixed: boundary crossings,
      0 ┤───╯              ╰──╯          ╰─ simultaneity, wrap-around
        └────────────────────────────────────────► time
        flat 50/50 instead: ~~~~~ half-full noise, boundaries never seen

Key takeaways

  • Two constraint layers: hard legality, soft + knob scenario shaping.

  • Knob-driven dist weights re-shape traffic per phase without new classes.

  • Phased scenarios walk the fill level to the boundaries that flat randomness never reaches.

  • A bounded mailbox keeps the generator loosely coupled to the driver.

Common pitfalls

  • Hard constraints on data shape — directed tests then conflict instead of overriding.

  • One flat op distribution — full/empty corners statistically unreachable.

  • Ignoring randomize() return — a knob typo silently freezes stimulus.