Part 5 · Sequences · Intermediate
Layered & Derived Items: Constraint Inheritance
Extend base items for write-only variants, add fields, tighten constraints without rewriting the base class.
Why derive items
A single monolithic item class with every field and constraint for every scenario becomes unmaintainable. Derived items add fields and tighten constraints for specific transaction types — write-only beats, burst descriptors, encrypted payloads — while inheriting base protocol rules from the parent class.
Layering items mirrors layering sequences: the base item defines the protocol's common address/data/write fields; derived items add strb, burst attributes, or security flags. Sequences typed to a derived item get stronger guarantees from randomize() without duplicate constraint code.
Factory overrides work on derived types independently — tests can swap axi_wr_item for axi_stress_wr_item without touching the base axi_item or the driver, as long as the driver accepts the base type.
[ITEM] derivation stack
axi_item addr, data, write, burst_len — base protocol
│
├── axi_wr_item write==1, strb — write-only variant
├── axi_rd_item write==0, rdata focus — read variant
└── axi_burst_item data[], len — multi-beat descriptorBase item — shared protocol fields
class axi_item extends uvm_sequence_item;
rand bit [31:0] addr, data;
rand bit write;
rand bit [2:0] burst_len;
constraint legal_addr { addr[1:0] == 2'b00; }
`uvm_object_utils_begin(axi_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(write, UVM_ALL_ON)
`uvm_field_int(burst_len, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "axi_item");
super.new(name);
endfunction
endclassNote: derived classes need their own uvm_object_utils_begin/end — field macros do not inherit automatically. Re-register base fields in derived macros or use uvm_field_utils if your flow supports it.
Derived write item — tightened constraints
axi_wr_item adds strobe and forces write==1. Sequences typed to axi_wr_item cannot accidentally randomize a read — the constraint solver enforces transaction kind at the type level.
class axi_wr_item extends axi_item;
rand bit [3:0] strb;
constraint wr_only { write == 1; }
constraint full_strb { strb == 4'hF; }
`uvm_object_utils_begin(axi_wr_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(write, UVM_ALL_ON)
`uvm_field_int(burst_len, UVM_ALL_ON)
`uvm_field_int(strb, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "axi_wr_item");
super.new(name);
endfunction
endclass[ITEM] constraint layering on derive
BASE axi_item: legal_addr { addr[1:0]==0; } ← inherited
DERIVED axi_wr_item: wr_only { write==1; } ← added
full_strb { strb==4'hF; } ← added
randomize() on axi_wr_item: BOTH base and derived constraints apply
Conflict if with-block says write==0 on axi_wr_item → FAILDerived constraints add to base — solver sees full constraint set.
Type-specific sequences: uvm_sequence #(axi_wr_item) cannot randomize reads.
Driver still uvm_driver #(axi_item) — polymorphism via base type handle.
APB read/write item split
APB agents often split read and write items for clarity. Each derived class removes randomization freedom that the sequence should not have:
class apb_wr_item extends apb_item;
constraint c_wr { write == 1; }
`uvm_object_utils(apb_wr_item)
endclass
class apb_rd_item extends apb_item;
constraint c_rd { write == 0; }
`uvm_object_utils(apb_rd_item)
endclass
class apb_rd_seq extends uvm_sequence #(apb_rd_item);
task body();
apb_rd_item req;
repeat (5) begin
req = apb_rd_item::type_id::create("req");
start_item(req);
assert(req.randomize() with { addr == 32'h4000; });
finish_item(req);
`uvm_info("RD", $sformatf("rdata=0x%08x", req.rdata), UVM_MEDIUM)
end
endtask
endclass[SEQ] typed sequence + [ITEM] derived item
apb_rd_seq → apb_rd_item → write==0 guaranteed
apb_wr_seq → apb_wr_item → write==1 guaranteed
[DRV] apb_driver #(apb_item) accepts both — reads req.write to branchConstraint override with factory
Replace constraint blocks in derived items via factory without editing VIP — useful for errata workarounds or project-specific address maps:
class apb_item_wide_map extends apb_item;
constraint addr_map {
addr inside {[32'h0000:32'hFFFF_FFFF]}; // full 32-bit map
}
`uvm_object_utils(apb_item_wide_map)
endclass
// In test build_phase:
set_type_override_by_type(apb_item::get_type(),
apb_item_wide_map::get_type());Layering guidelines
Base item = protocol-common fields + legality constraints.
Derived item = transaction-kind constraints (read vs write) + extra fields.
Do not deep-chain more than 2–3 levels — flat hierarchy is easier to debug.
Re-register all fields in derived uvm_object_utils for print/compare/copy.
post_randomize for computed fields
When a field depends on other randomized fields, compute it in post_randomize() rather than constraining it — checksums, derived lengths, encoded sizes:
class pkt_item extends uvm_sequence_item;
rand bit [7:0] payload[];
rand bit [15:0] pkt_len;
bit [15:0] checksum;
function void post_randomize();
checksum = calc_checksum(payload);
endfunction
endclassKey takeaways
Derive items to tighten constraints per transaction type — not copy-paste base classes.
Base + derived constraints compose — solver sees the full set.
Type sequences to derived items for compile-time transaction kind safety.
Common pitfalls
Forgetting field macros in derived class — compare skips new fields.
Conflicting derived constraint vs with-block — randomize returns 0, unchecked.
Deep inheritance tower — hard to predict which constraint block failed.
Derived item with different driver type — keep driver at base #(apb_item).