Specman constraints solver process consists of a series of reductionsand assignments. It reducesthe range of the field value based on the constraints, and then assignsto it a random value from the reduced range. After assigning one field, the range of all connected fields is reduced accordingly, and the process continues until all fields are assigned.
A new feature added in Specman 18.03 – Range Generated Fields (RGF)- allows distinction between the reductionsand the assignments. With RGF, the constraint solver performs the reduction as usual but we have the option to do the assignment ourselves.
Let’s look at a simple example of the reduction and assignment process. The example defines three fields and some constraints.
val_x : uint;
val_y : uint;
val_z : uint;
keep val_x in [5000..10000];
keep val_z in [500..8000];
keep val_y >= val_x;
keep val_y <= val_z;
The steps of generating one item are shown below (this information can be easily retrieved using the “trace gen” command):
reducing: val_x -> [5000..10000] // applying constraint #1
reducing: val_z -> [500..8000] // applying constraint #2
reducing: val_y -> [5000..8000] // applying constraints #3 & #4
reducing: val_x -> [5000..8000] // applying constraints #3 & #4
reducing: val_z -> [5000..8000] // applying constraints #3 & #4
assigningval_z: [5000..8000] -> 6419 // picking randomly from range
reducing: val_y -> [5000..6419] // applying constraints #4
assigningval_y: [5000..6419] -> 5693 // picking randomly from range
reducing: val_x -> [5000..5693] // applying constraint #3
assigningval_x: [5000..5693] -> 5471 // picking randomly from range
As seen in the given example, we usually let the constraint solver to do all the work for us, including reducing all ranges and assigning values to all fields. However, in some cases we want to perform the last decision of assigning values ourselves by deciding which value from the reduced range to assign to the field.
In this blog post, we will look at some examples of flows in which the Range Generated Fields are helpful. In these examples, we will take the final decision of assigning a value from the reduced range to the field ourselves.
Rapid Creation of Altered Input
When testbench performance is crucial, for example in acceleration verification, we look for ways to boost the input generation. On one hand – we want to use the constraint solver, ensuring only legal data is created. On the other hand – using the constraint solver has a cost in performance.
Let’s change a bit the example shown above, and mark the val_x as an RGF field, by writing ~ before it:
~val_x : uint;
val_y : uint;
val_z : uint;
Now the constraint solver will not only generate the struct, it will also maintain the information of the reduced range of val_x ([5000..5693], for example). After marking the field as RGF, we can generate the struct once and create many copies of it, in each of them picking one value from the reduced range and assigning it to val_x. This way we create many instances that are almost identical, differ in one essential field. We know all instances are legal - we ensure this by picking values only from the reduced range as it was calculated by the constraint solver.
For getting the reduced range we use a new method of any_struct– rgf_get_range(). This method returns the reduced range of the field. Note that this method is valid only for fields marked as RGF. To assign the field we use the new assignment operator rgf= and not a regular assignment (=). Using rgf= Specman checks that the assigned value is in the reduced range as it was calculated by the constraint solver.
main_tcm() @clk is {
var trans : trans;
gen trans;
// get the reduced range of val_x
var reduced_range := rgf_get_range(trans.val_x);
var copied_trans : trans;
var one_val_x : uint;
// create many copies, in each of them give a different
// value picked from the reduced range
for i from 0 to 10000 {
copied_trans = trans.copy();
gen one_val_x keeping {
it in reduced_range;
};
copied_trans.val_x rgf= one_val_x;
send(copied_trans);
};
};
“Optional Assignment” - Does the user really want this value?
As described above, the constraint solver assigns values to all fields. If there are no constraints on the field, the constraint solver would pick a random value from the type initial range. In some cases, you wish to know if the field got its value as a result of user constraints, and if no constraints were applied to this field, we will apply some predefined policy. Until now, to implement this methodology you would add a designated field, e.g. “use _address”, and instruct the test writers to constrain this field to TRUE indicating “yes, I wrote constraints on address field”. If use_address is FALSE – it means that its value was assigned fully randomly by the constraint solver and in this case the testbench would apply some predefined policy.
Using RGF, we do not need such an auxiliary field. We mark the field as RGF and then we query whether the field range was reduced.
In the following example we use two new methods of any_struct– first is rgf_was_generated(), indicating whether the struct was evaluated by the constraint solver (and not created using ‘new’). After establishing that indeed it was generated – we call rgf_got_reduced(), returning TRUE if the struct’s final range differs from the original range (indicating that constrains were applied on it).
struct trans {
~address : address_t;
// other fields …
};
unit agent {
send(t : trans) is {
if rgf_was_generated(t.address) {
if rgf_was_reduced(t.address) {
// reduced, meaning there were user constraints
// on it, so send as is
} else {
// no user constraints were applied on the address field
// so execute my default addressing policy
t.address rgf= me.config.get_prefered_address();
};
};
//// continue with sending this trans ….
};
};
Combining Power of Constraints with Other Algorithms
The last use model we will describe here, is the one in which we want to employ the power of the constraint solver with some complex algorithm. Some algorithms can be expressed with constraints, but this is not always the truth. Today, you have to decide whether a field is to be constrained or to be assigned procedurally applying some smart algorithm. Using RGF, we can mix both by letting the constraint solver reduce the range of the field and then calling some smart algorithm (even one implemented in another language) to pick one value from the reduced range.
In the following example, mem_block kind field is marked as RGF. After the mem_block is generated, we allocate it in the memory using a C routine named alloc_memory(). We pick random values from the kind reduced range until the memory allocator succeeds allocating a block of the requested kind. (To make the code example short, we skip the code that is required to ensure we do not get into an endless loop).
routine alloc_memory(mem_block : mem_block): bool is C routine alloc_mem;
struct mem_block {
~kind : mem_kind;
size : uint;
// additional fields and constraints …
};
extend agent {
add_mem_block(block : mem_block) is {
if rgf_was_generated(block.kind) {
var a_kind : mem_kind;
var assigned : bool = FALSE;
while not assigned {
gen a_kind keeping {it in rgf_get_range(block.kind)};
if alloc_memory (a_kind, block) {
assigned = TRUE;
};
};
block.kind rgf= a_kind;
};
};
};
As described in the above examples, Range Generated Fields (RGF) provide extra power to the generation process. When analyzing your environment and methodology to decide which fields should be marked as RGF, keep in mind that there are a few issues to be considered:
- Firstly, there cannot be more than one RGF field in a constraint.
- Secondly, if there is an unconditional == constraint between an RGF field and a generative field (i.e. constraints such as "keep sz == len/3", when 'sz' is an RGF), then RGF's range will always be a single value. Therefore, in these cases there is no advantage in marking such a field as RGF.
We are sure you will find great ways to employ this new powerful capability of RGF and continue to enjoy verification using Specman J