The art of expressing hardware functionality through constraint language is often one of the trickiest parts of functional verification. Unlike procedural actions that are executed locally one by one, constraints are scattered declarative entities that define the rules for the gen actions. Therefore you don't necessarily see clearly how constraints are impacting the generation. This situation opens the door for occasional constraint modeling pitfalls. This blog highlights an especially common and destructive pitfall, one that concerns constraining both generative-fields and do-not-generate-fields in the same constraint. Through this blog, we will try to understand what this pitfall means, review what to look for in the debug process so as to catch the problematic code, and introduce a way to prevent this pitfall by using a dedicated message in the e-linter.
The Beginning
Consider the following example, where we want to generate x and constrain it to be equal to y.
1 2 3 4 5 6 7 8 | extend sys { !x:uint; y:uint; keep x == y; run() is also { gen x; }; }; |
Let's take a minute to understand this example. Our intent is for y to get a random value, and then generate x, assigning y's value to x. Only if we run this code, the "random" value of y will strangely be 0. Why? Because we made a mistake, and hit a pitfall. Can you see it?
The Pitfall Explained
When we write "keep x == y;" where x and y are generated in different scopes (because one of them is marked as do-not-generate), this constraint affects multiple scopes: Not only the scopes we intended, but also the scopes we did not intend.
So the constraint "keep x == y" constrains x to be equal to y when generating x (as intended), but also constrains y to be equal to x (which was 0) when generating y (which is not what we intended!). Confused? Let's dive into a more detailed explanation.
The Other Side of the Coin
Let's write another example to make things clearer, and see if we are luckier this time. Since in the previous example y was generated to be 0 because x was not initialized, we will now generate x in the pre_generate() phase, and constrain y to be equal to that value.
1 2 3 4 5 6 7 8 | extend sys { !x:uint; y:uint; keep x == y; pre_generate() is also { gen x; }; }; |
But yet again, when we run it, we get x=0, y=0.
Something looks suspicious here. We had two different intentions, so we wrote two examples in which we only needed to change "run()" to "pre_generate()". In the first example, we wanted x to get y's value, and in the second example, we wanted y to get x's value; but we ended up writing exactly the same code. Isn't that convenient? Well... as you already figured out, it isn't.
Let's DEBUG!
Through this trivial example, we can understand the pitfall of constraining variables that are generated in different scopes. But of course, it is not so easy to see this problem when you get bad results in a full-blown verification environment. Let us use the IntelliGen GenDebugger to illustrate the process of exposing what went wrong.
We begin by configuring "config gen -collect=ALL", "load" the first example, and "test".
Let's look at the generation of x using the command "show gen -instance sys.x". It will pop-up the following reduction step.
It says that the value of the generative variable x was reduced from [ 0..MAX-UINT ] to [0] because of the constraint "keep x == y", and the input "y" which has value "0".
But why was y 0?
Again, "show gen -instance sys.y", will show the generation of y.
and again, the GenDebugger says that the value of the generative variable y was reduced from [ 0..MAX-UINT ] to [0] because of the constraint "keep x == y", and the input "x" which has value "0".
Clearly, x is 0 in Example-1 because we only generate it in the run() phase, so at the moment of its evaluation it is not yet assigned.
So What Did We Learn?
While in Example-1, we wanted to generate y freely and assign its value to x, the constraint "keep x==y" affected both the generation of x and the generation of y, causing adverse results. This happens because the constraint can mean two different things (Example-1 vs. Example-2), and since the generator doesn't know any better, it does exactly as it's told, and this is to enforce the constraints it has.
Let's Fix It!
Luckily, the solution is as simple as ABC. We just need to tell the generator what we really want it to do with the constraint. This can be done using the "value()" directive on the field that we intend to not be affected by the constraint.
So we will write "keep x == value(y);" in Example-1, where we want the constraint to affect only x, and "keep value(x) == y;" in Example-2 where we want the constraint to affect only y. Our new code will look as follows:
1 2 3 4 5 6 7 8 | extend sys { !x:uint; y:uint; keep x == value(y); run() is also { gen x; }; }; |
1 2 3 4 5 6 7 8 | extend sys { !x:uint; y:uint; keep value(x) == y; pre_generate() is also { gen x; }; }; |
How Do We Protect Ourselves from this Pitfall?
Like for so many other pitfalls, running the code through the HAL e-linter will locate the hazards in your code. You can invoke HAL using the "hal -gui example_1.e" command, or via "irun -hal" utility. (To learn more, search Cadence Online Help for "Invoking HAL for e Linting".) The notification we are looking for has the code EAGNDX, so you can filter this tag in the GUI window, or run HAL with the "-check EAGNDX" option. There, you will find the explanation and what exactly you need to do to fix your code.
Happy constraint coding!
Marat Teplitsky