PSS 3.1: Simplifying Constraint Modeling
PSS language constructs fall into two main categories: declarative and procedural. When you see ‘declarative’, think ‘rules’ and ‘constraints’. A declarative description is captured in terms of boundaries and relationships that must hold for the description to be valid. Doing so gives tools the opportunity to select specific values and scenarios from within the ‘valid’ space – whether randomly or otherwise. PSS 3.1 adds several new capabilities to make constraint descriptions more compact, readable, and rich. These are exactly the types of features to review carefully in a public-review draft!
Making constraints reusable
Constraints are common in many verification languages, and PSS is no exception. Generally constraints are collected in groups – typically named. But, the individual statements with a constraint block are not named, and are not reusable except by cut/paste.
struct xfer_s {
rand bit[64] src_addr, dst_addr;
rand int access_sz;
constraint c {
access_sz in [1,2,4,8];
src_addr % access_sz == 0;
dst_addr % access_sz == 0;
}
}
The alignment relationship above is a great case in point. We can recognize that using mod and equality establish an alignment relationship, but there’s nothing in the constraint to highlight this unless we add a comment. Also, we have to reiterate the same expression every time we want to require an alignment relationship.
struct xfer_s {
rand bit[64] src_addr, dst_addr;
rand int access_sz;
constraint aligned(bit[64] a, int n) { a % n == 0; }
constraint c {
access_sz in [1,2,4,8];
aligned(src_addr, access_sz);
aligned(dst_addr, access_sz);
}
}
PSS 3.1 introduces “generic constraints” – constraint relationships that are named and parameterized. This name attaches semantic meaning to the relationship: I can see that the author’s intent has something to do with alignment without understanding that actual expression.
Generic constraints can be declared across the PSS description depending on intent. Constraints declared in global scope, such as package scope, are static and can only reference their parameters and static constants. Generic constraints declared in struct and action context can access fields of the containing type, and can be overridden via inheritance much like functions can be.
Constraints that yield a value
The alignment constraint is a boolean constraint. The expression must
evaluate to true in order for the constraint set to be valid.
constraint numeric max(numeric a, numeric b) (a < b) ? b : a;
struct S {
rand bit[8] j, k, l;
// Equivalent to: j == (k < l) ? l : k;
constraint j == max(k, l);
}
But, it’s also very useful to reuse individual expressions that result
in a value. The max generic constraint above selects the maximum
value from the specified inputs and yields it.
Generic constraints can be declared with concrete types such as int.
But, there are cases where that is limiting. Consider max. We might
want to apply the same logic to floating-point and integer types. The
numeric type acts as a flexible type that covers both integer
and floating-point types, allowing generic constraints to be even more
“generic”.
What about Dynamic constraints?
You might be thinking that generic constraints sound similar to a feature PSS already has: dynamic constraints. Deciding how to approach overlaps like this is a task that any growing language needs to tackle.
// PSS 3.0 spelling -- deprecated in 3.1
dynamic constraint dyn_addr1_c { addr in [0..0xFF]; }
// PSS 3.1 spelling -- a generic constraint with no parameters
constraint gen_addr1_c() { addr in [0..0xFF]; }
In the case of dynamic constraints, the PSWG decided to deprecate dynamic constraints and replace them with generic constraints since generic constraints are a strict superset of dynamic constraints. The code snippet above shows how code should be updated to replace existing dynamic constraints with generic constraints.
Soft constraints: prioritized defaults
PSS 3.1 adds support for soft constraints. This is both a critical capability
to have, and one to use with extreme care. SystemVerilog has long had support
for soft constraints. The LRM dedicates a significant section dedicated to elaborating
the priority scheme governing which constraint is disabled in the case of a
conflict. In SystemVerilog, soft is the only available tool to set a default
value for a random variable and later alter that default. PSS supports two
mechanisms, default and soft constraints, that have different feature and
cost trade-offs.
package dma_lib_pkg {
struct xfer_s {
rand bit[64] src_addr, dst_addr;
rand int access_sz;
rand bit[32] total_sz;
constraint legal_sizes {
access_sz in [1,2,4,8];
total_sz % access_sz == 0;
}
constraint house_style {
default access_sz == 4;
soft total_sz in [64..4096];
}
}
abstract action A {
rand bool is_large;
rand xfer_s;
constraint default xfer_s.access_sz == 2;
constraint (is_large) -> xfer_s.total_sz > 4096;
}
}
In short, default constraints are the best option when variables have
a single default value that either always applies or always does not
apply. access_sz is a case in point. We want a single default value,
not a range. And, when executing transfers, we unconditionally change
it – either disabling the default completely or setting a new default.
total_sz is a bit different. We want to use a default range. And,
we might want to conditionally change it based on randomly-selected
values. This is where soft really shines.
Solving soft constraints is complex, so it’s generally still best
to use default constraints whenever possible. But, PSS now supports
soft constraints for when they’re really needed.
Using array slices in constraints
struct desc_list_s {
rand bit[32] chan[8];
rand bit[32] active;
constraint distinct_low_channels {
unique chan[0..3];
active in chan[0..3];
}
}
PSS 3.1 also adds support for applying unique and in constraints to
slices of arrays. Previously, we would have needed to manually unroll
the in constraint(eg active == chan[0] || active == chan[1] || …) and
create a nested foreach constraint to ensure uniqueness. Now, these
relationships are expressed succinctly.
Conclusions and providing feedback
Constraints are central to a PSS scenario model, and PSS 3.1 adds several new features to help make constraint descriptions easier to express.
I have two types of LRM public review feedback to suggest (and request) based on this post. The first, and most important, is the qualitative type: how do these new constraint constructs impact the PSS models you can create? Do they help you write more-compact, easier-to-maintain constraints? But, most importantly: where are the corner cases? What enhancements should the working group consider going forward to build on these features? While those enhancements won’t make it into 3.1, that feedback is critical in shaping forward-looking priorities. The second is a request for extra eyes on the document. While updating the open source PSS parser to support 3.1, I realized that the ‘soft’ keyword wasn’t listed in the keyword table. It’s exactly this type of small oversight that many sets of independent eyes help to catch!