Per-item solving leaves cross-item patterns to luck: a read after a write to one address, or back-to-back traffic at a FIFO mark. Scenario solving makes the collection the rand object, so relationships become constraints instead of accidents.
Pattern | Mechanism | Guarantee |
|---|---|---|
Read-after-write | First and last items share addr | Hazard pair by construction |
Burst then drain | Phase split on the index | Flood precedes drain |
Drive order | Unique permutation array | Contents decoupled from order |
Contiguous stream | Recurrence across items | Each addr follows the previous |
Scenarios stay tens of items: one solve covers tens of fields times tens of items. Bulk traffic windows one scenario per slice.
The next listing is not run in this course's checks. It is a fragment that uses bus_txn from elsewhere.
// not-run: fragment: bus_txn is defined elsewhere, no top module
class rw_hazard_scenario;
rand int unsigned n;
rand bus_txn q[];
rand bit [3:0] gap[];
rand int unsigned order[];
constraint shape_c {
n inside {[4:12]};
q.size() == n;
gap.size() == n;
order.size() == n;
}
constraint gap_c {
foreach (gap[i]) {
gap[i] inside {[0:12]};
(i < n / 2) -> gap[i] == 0; // burst first, then drain
}
}
constraint order_c {
unique {order};
foreach (order[i]) order[i] < n; // a permutation of 0..n-1
}
constraint hazard_c {
q[0].write == 1;
q[n-1].write == 0;
q[n-1].addr == q[0].addr;
foreach (q[i]) if (i > 0)
(int'(q[i].addr) - int'(q[i-1].addr)) inside {[-64:64]};
}
function void pre_randomize();
q = new[12];
foreach (q[i]) q[i] = new();
gap = new[12];
endfunction
endclass
class walking_addr;
rand bus_txn q[8];
function new();
foreach (q[i]) q[i] = new(); // the solver never constructs elements
endfunction
constraint walk_c {
q[0].addr == 32'h1000_0000;
foreach (q[i]) if (i > 0)
q[i].addr == q[i-1].addr + (32'(q[i-1].len) << 2);
}
endclassAllocation precedes solving: the solver never constructs objects, so pre_randomize (or the constructor, for a fixed-size array) builds every element first. With a null element, tools either error at randomize or leave it unsolved; never rely on either. Address neighbours use signed differences with an edge-safe band, never a variable-bound range that wraps near zero.
The solver resizes storage but never calls new on elements. Full construction up front keeps every indexed element solvable.
A constrained random permutation. Contents and drive order solve together, so ordering properties become constraints.
A procedural checker for the hazard_c, gap_c and order_c rules, plus a testbench that runs it on one legal scenario table and one broken table.
module tb_scenario;
localparam int N = 6;
reg [31:0] addr [0:N-1];
reg is_write [0:N-1];
reg [3:0] gap [0:N-1];
reg [2:0] order [0:N-1];
integer problems;
integer v;
// Counts broken scenario rules in the current table.
task automatic check_scen(output integer bad);
integer i, j, d;
begin
bad = 0;
if (!(is_write[0] == 1 && is_write[N-1] == 0)) bad = bad + 1; // hazard pair
if (addr[N-1] != addr[0]) bad = bad + 1;
for (i = 1; i < N; i = i + 1) begin
d = addr[i] - addr[i-1]; // signed neighbour step
if (d < -64 || d > 64) bad = bad + 1;
end
for (i = 0; i < N; i = i + 1) begin
if (gap[i] > 12) bad = bad + 1;
if (i < N / 2 && gap[i] != 0) bad = bad + 1; // burst, then drain
if (order[i] >= N) bad = bad + 1; // permutation of 0..N-1
for (j = i + 1; j < N; j = j + 1)
if (order[i] == order[j]) bad = bad + 1;
end
end
endtask
initial begin
problems = 0;
addr[0] = 32'h1000; is_write[0] = 1; gap[0] = 0; order[0] = 3;
addr[1] = 32'h1020; is_write[1] = 1; gap[1] = 0; order[1] = 0;
addr[2] = 32'h1040; is_write[2] = 1; gap[2] = 0; order[2] = 5;
addr[3] = 32'h1030; is_write[3] = 0; gap[3] = 3; order[3] = 1;
addr[4] = 32'h1010; is_write[4] = 1; gap[4] = 12; order[4] = 4;
addr[5] = 32'h1000; is_write[5] = 0; gap[5] = 5; order[5] = 2;
check_scen(v);
if (v != 0) problems = problems + 1; // legal table must pass
addr[3] = 32'h1100; // neighbour jump beyond 64
order[5] = 3; // duplicate drive slot
check_scen(v);
if (v != 3) problems = problems + 1; // two bad steps plus one duplicate
if (problems == 0) $display("PASS: legal scenario accepted; broken band and order caught");
else $fatal(1, "FAIL: %0d scenario checks went wrong", problems);
$finish;
end
endmodule