In Register Model, we have seen how to create a model that represents actual registers in a design. Now we'll look at the different components in a register environment required to perform register accesses such as read and write operations.
There are essentially four components required for a register environment :
- A register model based on UVM classes that accurately reflect values of the design registers
- An agent to drive actual bus transactions to the design based on some protocol
- An adapter to convert the read and write statements from the model to protocol based bus transactions
- A predictor to understand bus activity and update the register model to match the design contents

Now let's take a look at some of the common ways of writing constraint expressions inside a constraint block.
Simple expressions
Note that there can be only one relational operator < <= > >=
in an expression.
class MyClass;
rand bit [7:0] min, typ, max;
// Valid expression
constraint my_range { 0 < min;
typ < max;
typ > min;
max < 128; }
// Use of multiple operators in a single expression is not allowed
constraint my_error { 0 < min < typ < max < 128; }
// This will set min to 16 and randomize all others
constraint my_min { min == 16; }
// This will set max to a random value greater than or equal to 64
constraint my_max { max >= 64; }
endclass
Constraint blocks are class members just like variables, functions and tasks. They have unique names within a class. These blocks of expressions are typically used to limit the values of random variables within certain values as specified within the constraint block.
Syntax
constraint [name_of_constraint] { [expression 1];
[expression N]; }
Expressions listed within the curly braces specify the conditions a solver has to take into account when assigning random values for the variables. It is not necessary to have a single constraint for every variable nor is it required to restrict a constraint block to have conditions pertaining only to a single variable. However you cannot have conflicting constraints spread out in multiple blocks, unless they are turned off using constraint_mode()
method which we will see in Disable Constraints.
Variables are declared random using the rand
or randc
keyword. They can be used on normal variables, arrays, dynamic arrays or queues.
rand
class Packet;
rand int count;
rand byte master [$];
rand bit [7:0] data [];
...
endclass
What are direct tests ?
Verification engineers will first create something known as a verification plan that details every feature of the design required to be tested in RTL simulations and how each test will create independent scenarios that target a particular feature.
For example, if there's a peripheral that needs its registers to be configured such that it starts an AXI bus transaction, then we would have different tests to configure those registers differently and achieve a good coverage.
These are direct tests where each test does a particular task to accomplish something.
What are randomized tests ?
Complex designs have a lot of scenarios and many corner cases that are better verified by randomized tests and take much less effort and time. Taking the same example from above, a test will configure the peripheral registers with random values every time the test is run with a different seed thereby achieving different scenarios for every run. This will ensure that we hit corner cases and uncover any hidden bugs.
Constraints
SystemVerilog allows users to specify constraints in a compact, declarative way which are then processed by an internal solver to generate random values that satisfy all conditions. Basically constraints are nothing more than a way to let us define what legal values should be assigned to the random variables. A normal variable is declared to be random by the keyword rand
.
class Pkt;
rand bit [7:0] addr;
rand bit [7:0] data;
constraint addr_limit { addr <= 8'hB; }
endclass
The example above declares a class
called Pkt with a constraint on its address field. Note that both its properties are prefixed with the rand
keyword which tells the solver that these variables should be randomized when asked to. The constraint is called addr_limit and specifies that the solver can allot any random value for the address that is below or equal to 8'h8. Since the 8-bit variable addr is of type bit
, it can have any value from 0 to 255, but with the constraint valid values will be limited to 11.
As you can see, this powerful feature will allow us to create variables that are constrained within ranges that are valid for the design and will have a much better verification impact. In the next few sessions, we'll see how to effectively use different constructs within SystemVerilog that allow us to describe constraints in a better way.
- SystemVerilog Constraint Examples
- SystemVerilog Randomization
- SystemVerilog rand Variables
- SystemVerilog Constraint Blocks
- SystemVerilog Implication Constraint
- SystemVerilog Constraint 'inside'
- SystemVerilog Static Constraints
- SystemVerilog foreach Constraint
- SystemVerilog Inline Constraints
- SystemVerilog Soft Constraints
- SystemVerilog Disable Constraints
- SystemVerilog pre_randomize & post_randomize
- SystemVerilog Disable Randomization
- SystemVerilog randcase
- SystemVerilog Array Randomization
- Bus Protocol Constraints
- Memory Partitioning Example