A knob is control state that shapes solving without being payload. State knobs enter the solver as constants, while variable dist weights act as runtime dials read at each call.
Knob kind | Mechanism | Example |
|---|---|---|
Gate | Implication fixes a field | allow_err == 0 turns err off |
Weight | Variable dist share | err_pct sets the injected share |
Ceiling | Range bound | len_max caps burst length |
Config | Randomized knob object | stim_cfg applied per phase |
Knobs randomize per phase while payload randomizes per item. That cadence gives each run coherent character instead of white noise.
The next listing is not run in this course's checks. It is a fragment that uses bus_txn and txn_policy from elsewhere.
// not-run: fragment: bus_txn and txn_policy are defined elsewhere, no top module
class knobbed_txn;
rand bit [31:0] addr;
rand bit [3:0] len;
rand bit err;
bit [3:0] len_max = 4'd15;
bit allow_err = 1'b0;
int unsigned err_pct = 0;
int unsigned w_low = 8;
int unsigned w_high = 2;
constraint len_c { len inside {[1:len_max]}; }
constraint err_c {
allow_err == 0 -> err == 0;
err dist { 1 := err_pct, 0 := (100 - err_pct) };
}
constraint addr_c {
addr dist { [32'h0000_0000:32'h0000_FFFF] :/ w_low,
[32'hF000_0000:32'hFFFF_FFFF] :/ w_high };
}
endclass
class policed_txn;
rand bus_txn txn;
rand txn_policy pol[$];
function new();
txn = new();
endfunction
function void add(txn_policy p);
if (p == null) $fatal(1, "null policy added");
p.bind_txn(txn);
pol.push_back(p);
endfunction
endclassThe container relies on handle aliasing: each policy constrains through a handle to the same transaction, and one call solves all blocks together. Rand class handles are standard IEEE 1800 (clause 18.4), not a vendor idiom, but free simulators support them only in part. Bind every policy before solving and guard nulls at insertion.
Policies compose orthogonal axes at runtime with no new classes and no base edits.
Policies only AND: replacing or widening a block still needs named-block override or soft bases.
One axis recurring suits a subclass, one call site suits inline, and a matrix suits policies.
The distribution is degenerate and the solve cannot proceed. Knob objects need sanity constraints such as a nonzero weight total.
When intent spans orthogonal axes such as region by burst by injected share. Subclasses explode combinatorially while policies mix at runtime.
A deterministic stand-in for a weight knob and a gate knob, plus a testbench that checks both. The stand-in fixes the share exactly. A real dist gives about 80 percent, so a check on real solves needs a tolerance band.
module tb_knobs;
localparam int N = 100;
localparam int WR_PCT = 80;
localparam int INJ_PCT = 5;
// Weight stand-in: i*mult mod 100 is a permutation of 0..99 when
// mult shares no factor with 100, so exactly pct items fall below pct.
function automatic bit pick(int i, int mult, int pct);
return ((i * mult) % 100) < pct;
endfunction
// Gate knob: allow == 0 forces injection off, whatever the weight.
function automatic bit gated_inject(bit allow, int i);
return allow && pick(i, 53, INJ_PCT);
endfunction
int writes, inj_closed, inj_open;
initial begin
writes = 0; inj_closed = 0; inj_open = 0;
for (int i = 0; i < N; i++) begin
writes += pick(i, 37, WR_PCT);
inj_closed += gated_inject(1'b0, i);
inj_open += gated_inject(1'b1, i);
end
if (writes == WR_PCT && inj_closed == 0 && inj_open == INJ_PCT)
$display("PASS: weight gives 80 of 100 writes; gate closed injects 0, gate open injects 5");
else
$fatal(1, "FAIL: %0d writes, %0d injected with gate closed, %0d with gate open",
writes, inj_closed, inj_open);
$finish;
end
endmodule