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.
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
endclassCode walkthrough
op covers all four cycle behaviors — OP_BOTH targets the simultaneous push+pop features (F-07/F-08) directly.
Weights are plain int knobs read inside dist — re-shaping traffic is an assignment, not a new class.
soft on the data distribution: a directed test can pin data == 'hA5 inline without a conflict.
gap randomizes pacing; max_gap == 0 yields full-rate back-to-back traffic.
Generator with weighted scenarios — complete code
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
endclassWhy 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.
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 seenKey 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.