[Edit of Image1]
Introduction
Hey it's a me again @drifter1!
Today we continue with the Logic Design series on SystemVerilog in order to talk about the various Constraint Types. This is part 2. You can check out the 1st part here.
So, without further ado, let's get straight into it!
Conditional Relations
SystemVerilog provides two styles for declaring conditional relations: implication and if-else. These are basically ways of declaring that constraints should only be active when specific conditions are met.
Implication Operator
The implication operator ->
is used for specifying a conditional relationship between two expressions. The expressions in that case are the condition and constraint. So, the constraint expression (RHS) is only considered (or active) when the condition expression (LHS) is true.
The syntax is as follows:
constraint c_impl { cond_expr -> const_expr }
For example:
constraint c_impl { a == 1 -> b > 10; }
The constraint (b > 10
) is only active when a equals 1.
Of course, it's possible to specify multiple constraints by enclosing them in {}
.
A big limitation of the implication operator though is that it doesn't provide a way of specifying what happens when the condition is false.
If-Else Constraint
When a different set of constraints should be active when a condition is true or false then the if-else constraint needs to be used instead.
Let's note that multiple statements have to be enclosed in {}
and not in begin end
as in the usual if-else conditional statement. When only one statement follows the if
or else
, the {}
can be omitted.
Nesting multiple if-else statements or adding else if
sections is of course also allowed.
For example:
constraint c_if {
if (a == 1)
b > 10;
else
b < 10;
}
Iterative Constraints
Up to this point, we only saw how normal variables can be constrained. In order to specify constraints for arrays, some form of iterative constraints is required. That's exactly what SystemVerilog provides!
Static Arrays
In the case of static arrays, a simple foreach
loop is sufficient for specifying a constraint for every single element.
For example, consider a static array named arr
. Specifying that the value of each element should be equal to its index is as simple as:
constraint c_array {
foreach (arr[i])
arr[i] == i;
}
Dynamic Structures
The same foreach loop can also be used in the case of dynamic structures (arrays or queues). But, because such structures don't have a size at the time of declaration, their size must be set directly or as a part of some set of constraints before randomization.
A queue can be constrained like this:
rand int queue [$]; // size unknown
// size constraint
constraint c_qsize { queue.size() == 10; }
// iterative constraint
constraint c_queue {
foreach (queue[i])
queue[i] == i;
}
A dynamic array can be constrained like this:
rand int darr []; // size unknown
// iterative constraint
constraint c_array {
foreach (darr[i])
darr[i] == i;
}
...
// assign size
darr = new[10];
The last statement has to be called sometime before calling randomize()
. This, it could even be part of the class constructor:
function new()
darr = new[10];
endfunction
Multidimensional Arrays
Nesting multiple foreach
loops constraining static multidimensional arrays is a piece of cake. A loop variable is specified for each direction.
For example:
constraint c_mdarray {
foreach (mdarr[i][j])
mdarr[i][j] inside {[1 : 10]};
}
But, that's not the case for dynamic multidimensional arrays. For dynamic arrays, a size has to be assigned for each dimension. This can easily become tricky!
Solve - Before (Variable Ordering)
The solver always assures that the random values are selected uniformly. In other words, by default, the probability of each and any legal value is the same as all values are determined together.
Last time we covered the dist
operator which allows us to weight the legal value set. Additionally, SystemVerilog also provides the solve before
feature. Using it we basically specify some form of variable ordering. In other words, the variables specified in the solve before
are not determined together but one after the other. The value given to the first one affects the legal range of the next one. Only rand
is allowed, as randc
variables are always solved first. Lastly, circular dependencies are also forbidden.
Without Solve - Before
For example, consider the following random variables and their corresponding implication constraint:
rand bit a;
rand bit [1 : 0] b;
constraint c_ab { a -> b == 1; }
When a
is 0, b
can take any of the 4 legal values. But, when a
is 1, b
can only be equal to 1. So, there's a total of 5 combinations. Each choice having an equal probability of 20%.
With Solve - Before
Let's now include solve before
, so that the value of a
is chosen independently and before b
.
constraint c_ab2 {
a -> b == 1;
solve a before b;
}
Now, the probability of a
being either 0 or 1 is 50%. The value of b
depends on the value of a
and thus b
now has a 62.5% chance of being 1, and 12.5% of being 0, 2 or 3.
So, having a = 1
and b = 1
is of 50% probability instead of the 20% that we had before. And the remaining 4 choices now each have a probability of 12.5%.
Random Case
Sometimes, we may want to pick one random statement out of many statements. In that case the randcase
construct can be useful. Each case is also given a weight, allowing us to specify the probability of each choice.
The syntax is:
randcase
weight : statement;
...
endcase
Of course, this is not a constraint by itself, but constraints and randomization can be defined in the individual choices!
RESOURCES:
References
- https://www.chipverify.com/systemverilog/systemverilog-tutorial
- https://www.asic-world.com/systemverilog/tutorial.html
Images
Block diagrams and other visualizations were made using draw.io
Previous articles of the series
Verilog
- Introduction → Basic Syntax, Data Types, Operators, Modules
- Combinational Logic → Assign Statement, Always Block, Control Blocks, Gate-Level Modeling and Primitives, User-Defined Primitives
- Combinational Logic Examples → One Circuit - Four Implementations, Encoder, Decoder, Multiplexer
- Sequential Logic → Procedural Blocks (Initial, Always), Blocking and Non-Blocking Assignments, Statement Groups
- Sequential Logic Examples → Flip Flops (DFF, TFF, JKFF, SRFF), N-bit Counter, Single-Port RAM
- Finite-State Machines → Finite-State Machine (FSM), FSM Types, State Encoding, Modeling FSMs in Verilog
- Finite-State Machine Examples → Moore FSM Example (1 and 2 always blocks), Mealy FSM Example (1, 2 and 3 always blocks)
- Testbenches and Simulation → Testbenches (DUT / UUT, Syntax, Test Cases), System Tasks, Simulation Tools
- Combinational Logic Testbench Example → Half Adder Implementation, Testbench and Simulation
- Sequential Logic Testbench Example → Sequence Detector FSM Implementation, Testbench and Simulation
- Functions and Tasks → Function and Task Syntax, Calling, Rules, Examples
- Module Parameters and Generate Block → Parameterized Module (Parameters, Instantiation and Overriding Parameters), Generate Blocks (For, If, Case)
- Compiler Directives → Summary of Verilog's Compiler Directives (Include, Macros, Timescale, Conditional Compilation, etc.)
- Switch Level Modeling → Transistors, Switch Primitives (NMOS, PMOS, CMOS, Bidirectional, Resistive), Signal Strengths
SystemVerilog
- From Verilog To SystemVerilog → Data Types, Arrays, Structures, Operators and Expressions
- Control Flow → Additional Procedural Blocks, Loops, Conditional Statements, Functions and Task Features
- Processes → Fork - Join in Verilog and SystemVerilog, Process Control (wait fork, disable fork)
- Events → Interprocess Communication, Events (Definition, Triggering, Waiting, Sequencing, Merging, as Arguments)
- Semaphores and Mailboxes → Semaphores (Creation, Methods), Mailboxes (Definition, Methods)
- Interfaces (part 1) → Interfaces (Definition, Port and Signal Lists, Instantiation), Modports
- Interfaces (part 2) → Parameters, Tasks and Functions (Importing, Exporting), Clocking Blocks (Input and Output Skews)
- Classes (part 1) → Classes (Definition, Constructor Function, Creating Objects, Accessing Class Members, Static and Constant Class Members, Arrays)
- Classes (part 2) → Copying Objects (Shallow and Deep Copy), Inheritance, Polymorphism, Virtual Methods
- Classes (part 3) → Parameterized Classes, Out-of-Block Method Declaration, Data Accessibility, Abstract / Virtual Classes
- Program Blocks → Design and Testbench, Program Block (Reactive Region, Allowed Constructs)
- Packages → Design Hierarchy, Packages (Definition, Importing, Definition Collision)
- Constraints and Randomization → Testing and Verification, Random Variables (Standard, Random-Cyclic), Randomize Method (Constraint and Random Mode, Pre / Post Randomize)
- Constraint Blocks → Constraint Blocks (Syntax, Rules), External (Explicit, Implicit), Static, Soft and In-Line Constraints
- Constraint Types (part 1) → Simple Expressions, Set Membership (Inside Operator, Range, Set, Inverted), Weighted Distributions (Dist Operator, := and :/)
Final words | Next up
And this is actually it for today's post!
Now we only have Assertions and Functional Coverage left...in terms of bigger concepts that is. And there are also some smaller topics such as Type Casting or Command-Line Input.
Of course, I also haven't forgotten about Examples! There will be quite a few after we introduce all the features that SystemVerilog provides us with.
See Ya!
Keep on drifting!