Constraints define the reachable stimulus space. A bug here silently shrinks what the whole environment can ever test.
The review question is never whether the class randomizes. It is which legal stimulus the class can never produce.
Kind | Content | Form |
|---|---|---|
Legality | What the protocol forbids | Hard constraints, never disabled |
Typicality | What traffic is merely common | Soft or named blocks, tests may relax them |
A base class that hard-codes typical traffic makes corner cases unreachable for tests, unless a subclass overrides the whole named block and restates the legal rules. Typicality belongs in soft or test-layer constraints.
The listing below is not run in this course's checks: it needs the UVM library, which neither course tool (Icarus 13, Verilator 5.048) runs here.
// not-run: needs the UVM library (uvm_sequence_item, macros); neither Icarus 13 nor Verilator 5.048 here runs uvm_pkg
class bus_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [7:0] len;
rand bit write;
rand bit slverr; // response status
bit [31:0] data[];
constraint c_addr {
addr[1:0] == 2'b00; // keep it aligned
addr < 32'h0001_0000; // stay in the test RAM
len <= 8; // big bursts are slow
!slverr;
}
`uvm_object_utils(bus_txn)
function new(string name = "bus_txn");
super.new(name);
endfunction
function void do_copy(uvm_object rhs);
bus_txn t;
$cast(t, rhs);
addr = t.addr;
len = t.len;
write = t.write;
endfunction
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
bus_txn t;
$cast(t, rhs);
return (addr == t.addr) && (write == t.write);
endfunction
endclassBLOCK — legality and typicality share one block. Alignment and the address cap may encode real rules, but len <= 8 is pure typicality. No test can relax burst length without losing the rest. Split legality from typicality into named blocks.
BLOCK — slverr is rand on a request object. Response status belongs to the responder. Constraining it to zero makes error-response paths unreachable, unless a subclass overrides the whole block and restates the legal rules.
BLOCK — do_copy drops fields and skips super.do_copy. Payload, status, and base-class fields stop copying. Scoreboards that compare copies then pass on corrupted data.
BLOCK — do_compare ignores len and data. A fault in any data beat passes comparison. Every behavioral field belongs in the compare, with a comparer policy for intentional exclusions.
NOTE — task-form $cast in both functions. If rhs is the wrong type, the task form raises a run-time error at the cast (IEEE 1800 6.24.2), so it is not silent, but the message names nothing. The function form gives a named failure: if (!$cast(t, rhs)) $fatal(1, "bad cast");.
NOTE — data has no size tied to len. Generation must happen in a hand-written loop elsewhere. Prefer rand data with data.size() == len.
NOTE — the address cap hard-codes the map. A literal memory-map fact rots when the map grows. Such facts belong in config-derived constraints.
The corrected shape below is plain SystemVerilog without UVM. It runs under Verilator, which supports classes. The audit counts every length value instead of sampling the solver, so the reachability verdict is exact rather than statistical.
The testbench proves two things. Sweeping all 256 length values shows the typical cap hides 56 of the 64 legal burst lengths. Copy and compare track every field, including status.
class pkt;
rand bit [31:0] addr;
rand bit [7:0] len;
rand bit write;
bit slverr; // response: not rand, set by the responder
// Legality only: what the protocol forbids. Never disabled.
constraint c_legal {
addr[1:0] == 2'b00; // bus needs word alignment
len inside {[1:64]}; // spec maximum burst
}
// Typicality: named, so tests may relax it with constraint_mode(0).
constraint c_typical {
len <= 8;
}
// Solver-free mirrors of the two blocks, for the audit loop below.
// Each reads exactly like its constraint, so the counts transfer.
function bit legal_ok(bit [7:0] l);
return (l >= 8'd1 && l <= 8'd64);
endfunction
function bit typical_ok(bit [7:0] l);
return (l <= 8'd8);
endfunction
function void copy_from(pkt src);
addr = src.addr;
len = src.len;
write = src.write;
slverr = src.slverr;
endfunction
function bit same_as(pkt other);
if (addr != other.addr) return 1'b0;
if (len != other.len) return 1'b0;
if (write != other.write) return 1'b0;
if (slverr != other.slverr) return 1'b0;
return 1'b1;
endfunction
endclass
Every real randomize call site checks the return value. The idiom is one line, shown partial here:
// snippet
if (!req.randomize()) $fatal(1, "rand failed");
// A failed randomize keeps stale values, so an unchecked call lets the
// test run on garbage and stay green. The return value is the check.In UVM the same compare should route through the comparer argument. That keeps the exclusion policy for intentionally skipped fields in one place.
Letting keep it aligned style comments substitute for citing the spec rule being encoded.
Missing a skipped super.do_copy: base-class fields silently stop copying.
Treating a compare gap as a nit. It is the highest-severity finding in a transaction review.
Leaving randomize() unchecked: a failed call keeps stale values and the test runs on them silently.
Whether a second hard constraint still caps length, and whether the randomize call carries an inline with block that re-adds the cap. Then whether the call site checks the return value at all.
Copies silently lose the field, and compares pass when the field disagrees. Both failures are silent by construction.