SystemVerilog Constraint Layering Via Reusable Randomization Policy Classes
SystemVerilog Constraint Layering Via Reusable Randomization Policy Classes
Or, we can specify inline constraints when randomizing the base object:
rw_txn t = new;
...
t.randomize with {
// Transaction addr range must fit within certain ranges
addr inside {['h00000000 : 'h0000FFFF - size]} ||
addr inside {['h10000000 : 'h1FFFFFFF - size]} ;
Although both of these techniques are usable, as more types or combinations of constraints are desired, it will be
cumbersome to manage the many derived classes or sets of inline constraints. It would help if there were a way to
package constraints into reusable building blocks.
We can more easily support multiple constraint classes in the top level by deriving all policies from a common
base class and using a queue to contain any number of constraint classes. Using a foreach constraint to constrain the
addr and size members will add the equality constraints for any number constraint containers.
class addr_constraint_base; class rw_constrained_txn extends rw_txn;
rand bit [31:0] addr; rand addr_constraint_base cnst[$];
rand int size;
endclass function new;
addr_permit permit = new;
class addr_permit extends addr_constraint_base; addr_prohibit prohibit = new;
constraint c_addr_permit { cnst = {permit, prohibit};
addr inside {['h00000000 : 'h0000FFFF - size]} || endfunction
addr inside {['h10000000 : 'h1FFFFFFF - size]} ;
} constraint c_all {
endclass foreach(cnst[i]) {
this.addr == cnst[i].addr;
class addr_prohibit extends addr_constraint_base; this.size == cnst[i].size;
constraint c_addr_prohibit { }
!(addr inside {['h13000000 : 'h130FFFFF - size]}); }
} endclass
endclass
It can be a maintenance chore to keep the top-level equality constraints in sync with the constraint objects. For
example, if we want to add a new constraint using rw_txn’s “op” member (READ or WRITE), we need to update
the top-level constraints to support this new member. If a constraint class doesn’t constrain one of the top-level
members, it still needs to declare it for the top-level equality constraints to be valid.
ELIMINATING TOP-LEVEL EQUALITY CONSTRAINTS
If the top-down equality constraints are problematic, perhaps can we use bottoms-up constraints instead? If the
constraint objects had a way to directly refer to members of the containing class instance, we would not need to
maintain the top-level equality constraints. SystemVerilog supports “upwards name referencing” which tries to
resolve variable names by scanning upwards through the module hierarchy (see [1] section 23.8). This is close to
what we want, except we want to scan upwards through the class instance hierarchy which is not supported.
What we can do, however, is declare an object handle in each constraint class which will contain a reference to
the top-level object being randomized. If we write our constraints using this handle, we have access to all of the top-
level objects members with requiring any top-level equality constraints. Figure 6 shows an example of this
technique. The constraint base class (addr_constraint_base) contains a variable “item” of the same type as the top-
level object (addr_txn). The constraints in each constraint class refer to the top level members by using the item
handle (item.addr, item.size, etc.)
One step remains for this technique to work: we need to set the “item” variable to point to the top-level object
before randomizing. This is conveniently done in the pre_randomize method of the top-level object.
class addr_constraint_base;
addr_txn item;
endclass
function new;
addr_permit permit = new;
addr_prohibit prohibit = new;
cnst = {permit, prohibit};
endfunction
endclass
Figure 6. Constraint class using item handle instead of top-level equality constraints
RANDOMIZATION POLICY CLASSES
In our examples, the constraint class has a hard-coded item class type. This can be made more generic by
defining a parameterized base class with a type parameter indicating the type of the top-level object being
randomized. We also add a function set_item for setting the item handle. We’ll call this new type of container a
“randomization policy”.
endclass
class addr_txn;
rand bit [31:0] addr;
rand int size;
rand policy_base#(addr_txn) policy[$];
addr_txn
ITEM:addr_txn
+addr: bit[31:0]
policy_base +size: int
+item: ITEM +policy[$]: policy_base#(addr_txn)
+set_item(item:ITEM) +pre_randomize()
Figure 7. Randomization Policy base class example and UML class diagram
It would be handy to be able to bundle multiple policies into a single object. We can do this by creating a
policy_list class which contains a queue of policies. The set_item method is overridden to set the item handle for all
policies in the list. This allows recursively setting the item handle with a single call to the top-level set_item. With
this technique, we can group interesting policies together and pass them around with a single assignment.
class policy_list#(type ITEM=uvm_object) extends policy_base #(ITEM);
rand policy_base#(ITEM) policy[$];
pcy.add(permit);
pcy.add(prohibit);
policy = {pcy};
endfunction
endclass
Figure 8. Bundling multiple policies into a policy_list object
These policy lists can be nested to any number of levels. For example we may want to bundle the default address
map policies (i.e. permitted and prohibited addresses) into a single default policy object and subsequently add that to
a higher level policy object.
class cache_evict extends policy_base#(addr_txn);
// constraints which cause cache evictions
endclass
test_pcy.add(default_pcy);
test_pcy.add(evict);
this.policy = {test_pcy};
endfunction
endclass
constraint c_addr_permit {
selection inside {[0 : ranges.size() - 1]};
foreach(ranges[i]) {
if(selection == i) {
item.addr inside {[ranges[i].min: ranges[i].max - item.size]};
}
}
}
endclass
permit.add('h00000000, 'h0000FFFF);
permit.add('h10000000, 'h1FFFFFFF);
pcy.add(permit);
prohibit.add('h13000000, 'h130FFFFF);
pcy.add(prohibit);
this.policy = {pcy};
endfunction
endclass
Figure 10. Configurable address permit/prohibit policies
Some policies may require the use of persistent state information. One example is generating a series of
transactions which cause cache eviction. In an N-way cache, accessing more than N addresses with certain address
bits the same (known as the index bits) but other address bits (the tag bits) different will cause one of the N entries
(cache lines) already in the cache to be evicted to make room for a new entry. We can create a cache_evict policy
class to generate this series of addresses. We will need to keep track of which cache line addresses have been used.
We can keep this state data with the policy class by adding a state variable – an array (queue) containing the last N
used addresses. This can be updated in the policy object’s post_randomize function – i.e. after the top-level object
has been randomized, the policy class’s post_randomize records whatever information is needed for future
randomizations. In this simple example, the state info is kept in the policy object, but it could also reference some
global state info maintained elsewhere in the environment.
class cache_evict_policy extends addr_policy_base;
addr_t line_hist[$];
int index;
function new;
super.new;
std::randomize(index) with { index inside {[0:'h3f]}; };
endfunction
constraint c_evict {
!((item.addr & 'hFFFFF000) inside {this.line_hist}); // different tag
(item.addr & 'h00000FC0) == (this.index << 6); // same index
}
task body;
//`uvm_do(req);
`uvm_create(req);
req.policy = {permit, prohibit};
`uvm_rand_send(req);
endtask
endclass
Figure 12. UVM sequence which sets policy before randomization
To simplify this process, we might create a new macro: “uvm_do_with_policy” which combines these three steps
and allows passing the policy objects as an additional macro argument.
`define uvm_do_with_policy(SEQ_ITEM, CONSTRAINTS="{}", POLICY="{}")\
`uvm_create(SEQ_ITEM)\
SEQ_ITEM.policy = POLICY;\
`uvm_rand_send_with(SEQ_ITEM, CONSTRAINTS)
task body;
`uvm_do_with_policy(req, {}, {permit, prohibit} );
endtask
endclass
Figure 13. UVM macro which sets policy before randomization
Another option is to use the UVM configuration database. This is handy if we want the policies applied in a top-
level sequence to apply to any sequence items launched by any child sequences launched by the top level sequence.
The top-level sequence/vsequence pokes a default policy object into the config_db and the sequence item’s
pre_randomize method gets the default policy from the config_db – but only if the object doesn’t already have a
policy. This scheme allows the convenience of setting the policy once at the possible expense of additional
config_db accesses. If necessary, performance may be improved by explicitly setting a sequence_item’s policy
before randomization as in the previous examples.
When writing to the config_db, we use the top sequence’s full hierarchical name with a “.*” wildcard appended.
When reading from the config_db, we use the sequence_item’s full hierarchical name. This scheme lets us set a
default policy for all items in a sequence hierarchy but allows overriding the default by adding additional config_db
entries with a more specific path name.
class my_seq extends uvm_sequence #(addr_txn);
...
policy_list#(addr_txn) default_pcy = new;
policy_list#(addr_txn) special_pcy = new;
task body;
default_pcy.add(permit);
default_pcy.add(prohibit);
// write default policy into config_db using top sequence full name + wildcard.
uvm_config_db#(policy_list#(addr_txn))::set(null,
{get_full_name, ".*”},
"default_policy",
default_pcy);
// Use special policy for items in the sub_seq.seq2 sequence (and below)
uvm_config_db#(policy_list#(addr_txn))::set(null,
{get_full_name, "sub_seq.sub2.*”},
"default_policy",
special_pcy);
// default_pcy will be used by all sequences items started by following uvm_do calls
// Except sub_seq.seq2 which will use special_pcy.
`uvm_do(req);
`uvm_do(sub_seq);
endtask
// If policy queue is empty, attempt to get default policy from the config db.
// Use item’s fullname to query the config_db
if(policy.size ==0) begin
policy_list#(addr_txn) default_pcy;
if(uvm_config_db#(policy_list#(addr_txn))::get(null,
get_full_name,
"default_policy",
default_pcy) ) begin
policy = { default_pcy };
end else begin
`uvm_error(get_type_name(), "could not get policy from config_db");
end
end
foreach(policy[i]) policy[i].set_item(this);
endfunction
endclass
ACKNOWLEDGMENTS
The author would like to thank John Rose from Cadence for reviewing and sanity checking the initial ideas in this
paper and for suggesting the “item” handle used in the policy classes.
REFERENCES
[1] IEEE Standard for SystemVerilog - Unified Hardware Design, Specification, and Verification Language, IEEE Std 1800-2012
[2] UVM Documentation http://www.accellera.org/downloads/standards/uvm