The previous lesson built the interface and the harness. This lesson fills the environment: the stimulus side that decides what to push and pop, and the environment class that builds every mailbox and starts every thread. The listings are fragments of one environment; they compile together with the interface, the monitors, the scoreboard and a FIFO.
The transaction carries two constraint layers. The legality layer encodes what is always true, and the scenario layer uses soft constraints and knob fields so tests dial traffic shape without new classes.
These listings are class declarations with no top module, so this course's checks do not run them.
// not-run: fragment: class declarations only, no top module
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 {
if (WIDTH > 1 && WIDTH < 30) {
soft data dist { 0 := 1, ((1 << WIDTH) - 1) := 1, [1:(1 << WIDTH) - 2] :/ 8 };
} else {
// WIDTH 1 has no middle values; very wide WIDTH skips the
// enumerated middle range (1 << WIDTH overflows an int).
soft data dist { 0 := 1, ((1 << WIDTH) - 1) := 1 };
}
}
function void display(string tag);
$display("[%0t] %s op=%s data=0x%0h gap=%0d",
$time, tag, op.name(), data, gap);
endfunction
endclass// not-run: fragment: needs fifo_txn from the listing above and has no top module
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 for free
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
endclassFILL 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 seenA flat 50/50 push/pop mix hovers around half-full forever. Phased scenarios walk the fill level to each boundary instead.
Re-shaping traffic is an assignment to knobs, not a new class. Directed tests pin data because the distribution is soft.
The generator checks every randomize call. A knob typo fails loudly instead of freezing stimulus.
The driver translates each transaction into pin wiggles on the driver clocking block. The environment builds every mailbox, starts every thread, and wires reset and end-of-test discipline.
These listings are class declarations that depend on the other listings, with no top module, so this course's checks do not run them.
// not-run: fragment: needs fifo_if and fifo_txn from the listings above and has no top module
class fifo_driver #(parameter int WIDTH = 8);
virtual fifo_if #(WIDTH).DRV vif;
mailbox #(fifo_txn #(WIDTH)) gen2drv;
function new(virtual fifo_if #(WIDTH).DRV v,
mailbox #(fifo_txn #(WIDTH)) mb);
vif = v;
gen2drv = mb;
endfunction
task run();
fifo_txn #(WIDTH) t;
forever begin
gen2drv.get(t);
repeat (t.gap) @(vif.drv_cb); // pacing gap: idle cycles before the op
if (!vif.rst_n) continue; // reset-skip: never drive into reset
case (t.op)
OP_PUSH: begin vif.drv_cb.in_valid <= 1'b1; vif.drv_cb.in_data <= t.data; vif.drv_cb.out_ready <= 1'b0; end
OP_POP: begin vif.drv_cb.in_valid <= 1'b0; vif.drv_cb.out_ready <= 1'b1; end
OP_BOTH: begin vif.drv_cb.in_valid <= 1'b1; vif.drv_cb.in_data <= t.data; vif.drv_cb.out_ready <= 1'b1; end
OP_IDLE: begin vif.drv_cb.in_valid <= 1'b0; vif.drv_cb.out_ready <= 1'b0; end
endcase
@(vif.drv_cb); // hold one cycle so the handshake can complete
vif.drv_cb.in_valid <= 1'b0; // return to idle: every op is one cycle wide
vif.drv_cb.out_ready <= 1'b0;
end
endtask
endclass// not-run: fragment: needs the generator, driver, monitor and scoreboard classes and has no top module
class fifo_env #(parameter int WIDTH = 8, parameter int DEPTH = 16);
virtual fifo_if #(WIDTH) vif;
mailbox #(fifo_txn #(WIDTH)) gen2drv;
mailbox #(logic [WIDTH-1:0]) mb_in, mb_out; // 4-state: an X word survives
generator #(WIDTH) gen;
fifo_driver #(WIDTH) drv;
fifo_in_mon #(WIDTH) in_mon;
fifo_out_mon #(WIDTH) out_mon;
fifo_sb #(WIDTH) sb;
int n_txns = 200;
function new(virtual fifo_if #(WIDTH) v);
vif = v;
gen2drv = new(1); // bounded: generator blocks when driver stalls
mb_in = new();
mb_out = new();
gen = new(gen2drv);
drv = new(vif, gen2drv);
in_mon = new(vif, mb_in);
out_mon = new(vif, mb_out);
sb = new(mb_in, mb_out);
gen.n_txns = n_txns;
endfunction
// reset asserted mid-traffic: the model drops in-flight data (spec rule 8)
task reset_watcher();
forever begin
@(negedge vif.rst_n);
sb.flush();
end
endtask
task run();
fork
drv.run();
in_mon.run();
out_mon.run();
sb.run();
reset_watcher();
join_none
gen.run(); // blocking: returns after n_txns
// num() is a method call, not a signal, so wait() on it may never wake: poll per clock
while (gen2drv.num() != 0 || mb_in.num() != 0 || mb_out.num() != 0) @(vif.mon_cb);
repeat (5) @(vif.mon_cb); // settle: last beats cross the scoreboard
// the pins carry no count, so the test supplies DUT occupancy
// with a hierarchical probe: the capstone FIFO exposes count
sb.report(tb_top.dut.count);
endtask
endclassEvery op maps to pins explicitly: push and pop each hold for one cycle, then return to idle.
The bounded mailbox couples generator and driver loosely. A slow DUT stalls the generator, not the test.
Reset has two wirings: the driver skips reset cycles, and the watcher flushes the scoreboard on reset assert.
Full. Sustained pushes with no pops park at full, targeting F-02 (full exact) and F-05 (push blocked).
data inside {'0, '1};. A directed test then pins data to A5 with an inline constraint and randomize fails. What is wrong?Scenario shaping went into a hard constraint, so it fights the directed test. Shaping must be soft, as c_data_interesting is, so directed tests override it without conflict.