A Little Verilog Knowledge Goes A Long Way in Understanding How SystemVerilog Constraints Work
In its simplest form, a constraint is nothing more than a Boolean expression with random variables where the solver is asked to find values that make the expression true. One way of thinking about how a constraint solver works is that it repeatedly tries different values until the values make the expression true, then it writes those values into the set of random variables. It’s actually much more complicated than that because we want the solver to quickly converge on a solution or tell us no solution is possible without wasting a lot of time. But let’s just focus on the expression evaluation.
A fundamental principle that drove SystemVerilog’s development was unifying semantics so that expression evaluation was identical across all facets of the language. This meant all the idiosyncrasies from Verilog’s weak type and expression evaluation systems got absorbed into constraint expressions. One of the unique things that distinguishes Verilog from most other programming languages is its ability to declare variables of different bit-widths and have expressions of mixed bit-widths. The weak type system means it silently truncates and pads different bit widths without any notice of overflow or underflow. This system also has to deal with signed and unsigned arithmetic, and real to integer truncation.
Let’s look at the following two seemingly equivalent expressions:
Expression A: (X * 4) == Y
Expression B: X == (Y /4)
If we remember our elementary algebra, the division property of equality says we should be able to divide both sides of the equality in Expression A by 4 and wind up with Expression B. You would think using either expression in a constraint gives the exact same results. NOT TRUE. Each expression has a complex series of steps for determining the bit width of an expression result and all the intermediate operations. We need to be aware of this even when we think everything has the same bit-widths.
For Expression A, suppose we declared X and Y as 32-bit unsigned variables (the numeric literal 4 is also considered 32-bits. Verilog has a table for every operator that specifies the resulting width its result. For all arithmetic operators the result width is the size of its largest operand, not the width of the largest possible result which would be 34 bits in this case. This means the result of 32’h40000001*32’d4
gets truncated to 32’h00000004
. So, the solver could choose either 32’h40000001
or 32’h00000001
for X when choosing the value 32'h00000004
for Y.
The solution space for (X * 32'd4) == Y
is shown in the following table.
X | Y |
---|---|
32'h00000000 |
32'h00000000 |
32'h00000001 |
32'h00000004 |
32'h00000002 |
32'h00000008 |
… | |
32’h3FFFFFFF |
32'hFFFFFFFC |
32'h40000000 |
32'h00000000 |
32'h40000001 |
32'h00000004 |
… | |
32’hFFFFFFFF |
32'hFFFFFFFC |
The fix for this is making sure the width of the multiplication result is large enough not to truncate using : (X * 34’d4) == Y
. That eliminates the overflow truncation and the second set of unexpected solutions.
For expression B, the bit-widths are not as precarious. But we do need to understand Verilog integer division truncates any fractional part towards 0, it does not round. That means there will be for consecutive values for Y associated with one value of X. The solution space for X == (Y / 4)
is shown in the following table.
X | Y |
---|---|
32'h00000000 |
32'h00000000 |
32'h00000000 |
32'h00000001 |
32'h00000000 |
32'h00000002 |
32’h00000000 |
32'h00000003 |
32'h40000001 |
32'h00000004 |
32'h40000001 |
32'h00000005 |
… | |
32’h3FFFFFFF |
32'hFFFFFFFF |
To summarize, I’ve show a simple example of how Verilog expression evaluation rules affect the solution space of your SystemVerilog constraints. So anytime you get unexpected values from the constraint solver, go back to your textbooks on Verilog or even Algebra to remember how expressions get evaluated.
Comments