The SystemVerilog constraint solver by default tries to give a uniform distribution of random values. Hence the probability of any legal value of being a solution to a given constraint is the same.
But the use of solve - before
can change the distribution of probability such that certain corner cases can be forced to be chosen more often than others. We'll see the effect of solve - before
by comparing an example with and without the use of this construct.
Random Distribution Example
For example, consider the example below where a 3-bit random variable b can have 8 legal values ( 23 combinations). The probability of b getting a value 0 is the same as that of all other possible values.
SystemVerilog provides the support to use foreach
loop inside a constraint so that arrays can be constrained.
The foreach
construct iterates over the elements of an array and its argument is an identifier that represents a single entity in the array.
Click here to refresh loops in SystemVerilog !
Example
The code shown below declares a static array called array with size 5. This array can hold 5 elements where each element can be accessed using an index from 0 to 4.
The constraint uses foreach
loop to iterate over all the elements and assign the value of each element to that of its index.
class ABC;
rand bit[3:0] array [5];
// This constraint will iterate through each of the 5 elements
// in an array and set each element to the value of its
// particular index
constraint c_array { foreach (array[i]) {
array[i] == i;
}
}
endclass
module tb;
initial begin
ABC abc = new;
abc.randomize();
$display ("array = %p", abc.array);
end
endmodule
The output given below shows that each element of the array was assigned to a value equal to that of its index.
ncsim> run
array = '{'h0, 'h1, 'h2, 'h3, 'h4}
ncsim: *W,RNQUIE: Simulation is complete.
Dynamic Arrays/Queues
Dynamic arrays and queues do not have a size at the time of declaration and hence the foreach
loop cannot be directly used. Therefore, the array's size has to be either assigned directly or constrained as part of the set of constraints.
Example
class ABC;
rand bit[3:0] darray []; // Dynamic array -> size unknown
rand bit[3:0] queue [$]; // Queue -> size unknown
// Assign size for the queue if not already known
constraint c_qsize { queue.size() == 5; }
// Constrain each element of both the arrays
constraint c_array { foreach (darray[i])
darray[i] == i;
foreach (queue[i])
queue[i] == i + 1;
}
// Size of an array can be assigned using a constraint like
// we have done for the queue, but let's assign the size before
// calling randomization
function new ();
darray = new[5]; // Assign size of dynamic array
endfunction
endclass
module tb;
initial begin
ABC abc = new;
abc.randomize();
$display ("array = %p
queue = %p", abc.darray, abc.queue);
end
endmodule
ncsim> run array = '{'h0, 'h1, 'h2, 'h3, 'h4} queue = '{'h1, 'h2, 'h3, 'h4, 'h5} ncsim: *W,RNQUIE: Simulation is complete.
Multidimensional Arrays
SystemVerilog constraints are powerful enough to be applied on multidimensional arrays as well. In the following example we have a multidimensional static array with a packed structure.

Here we attempt to assign the pattern 0xF0F0F to each element of the multidimensional array.
class ABC;
rand bit[4:0][3:0] md_array [2][5]; // Multidimansional Arrays
constraint c_md_array {
foreach (md_array[i]) {
foreach (md_array[i][j]) {
foreach (md_array[i][j][k]) {
if (k %2 == 0)
md_array[i][j][k] == 'hF;
else
md_array[i][j][k] == 0;
}
}
}
}
endclass
module tb;
initial begin
ABC abc = new;
abc.randomize();
$display ("md_array = %p", abc.md_array);
end
endmodule
ncsim> run md_array = '{'{'hf0f0f, 'hf0f0f, 'hf0f0f, 'hf0f0f, 'hf0f0f}, '{'hf0f0f, 'hf0f0f, 'hf0f0f, 'hf0f0f, 'hf0f0f}} ncsim: *W,RNQUIE: Simulation is complete.
Multidimensional Dynamic Arrays
Constraining a multi-dimensional dynamic array is a little more tricky and may not be supported by all simulators. In the example shown below, the size of X or Y elements of the 2D array md_array is not known.
class ABC;
rand bit[3:0] md_array [][]; // Multidimansional Arrays with unknown size
constraint c_md_array {
// First assign the size of the first dimension of md_array
md_array.size() == 2;
// Then for each sub-array in the first dimension do the following:
foreach (md_array[i]) {
// Randomize size of the sub-array to a value within the range
md_array[i].size() inside {[1:5]};
// Iterate over the second dimension
foreach (md_array[i][j]) {
// Assign constraints for values to the second dimension
md_array[i][j] inside {[1:10]};
}
}
}
endclass
module tb;
initial begin
ABC abc = new;
abc.randomize();
$display ("md_array = %p", abc.md_array);
end
endmodule
ncsim> run
md_array = '{'{'h9, 'h6, 'h7, 'h9, 'h1}, '{'h5, 'h9, 'h4, 'h2}}
ncsim: *W,RNQUIE: Simulation is complete.
Array Reduction Iterative Constraint
This is another pretty useful construct and technique supported in SystemVerilog.
Array reduction methods can produce a single value from an unpacked array of integral values. This can be used within a constraint to allow the expression to be considered during randomization.
For example, consider that an array of N elements have to be randomized such that the sum of all elements equal some value. An array reduction operator can be used with the with
clause such that it iterates over each element of the array and include it in the constraint solver.
class ABC;
rand bit[3:0] array [5];
// Intrepreted as int'(array[0]) + int'(array[1]) + .. + int'(array[4]) == 20;
constraint c_sum { array.sum() with (int'(item)) == 20; }
endclass
module tb;
initial begin
ABC abc = new;
abc.randomize();
$display ("array = %p", abc.array);
end
endmodule
ncsim> run array = '{'h4, 'h2, 'h2, 'h4, 'h8} ncsim: *W,RNQUIE: Simulation is complete.
SystemVerilog gives us two constructs to declare conditional relations - implication and if else.
The following code snippet shows both styles
// Implication operator "->" tells that len should be
// greater than 10 when mode is equal to 2
constraint c_mode { mode == 2 -> len > 10; }
// Same thing can be achieved with "if-else" construct
constraint c_mode { if (mode == 2)
len > 10;
}
Note that mode need not be 2 for all values of len greater than 10. However, the constraint says that len should be greater than 10 if mode is 2.
Example
class ABC;
rand bit [2:0] mode;
rand bit [3:0] len;
constraint c_mode { mode == 2 -> len > 10; }
endclass
module tb;
initial begin
ABC abc = new;
for(int i = 0; i < 10; i++) begin
abc.randomize();
$display ("mode=%0d len=%0d", abc.mode, abc.len);
end
end
endmodule
The simulation results show that mode need not have a value of 2 when len is greater than 10.
ncsim> run mode=1 len=11 mode=6 len=3 mode=3 len=9 mode=7 len=11 mode=3 len=15 mode=2 len=12 mode=3 len=6 mode=2 len=12 mode=4 len=9 mode=7 len=13 ncsim: *W,RNQUIE: Simulation is complete.
Implication Operator
An implication operator ->
can be used in a constraint expression to show conditional relationship between two variables.
If the expression on the LHS of ->
operator is true, then the constraint expression on the RHS will be satisfied. If the LHS is not true, then RHS constraint expression is not considered.
Example
class ABC;
rand bit [3:0] mode;
rand bit mod_en;
// If 5 <= mode <= 11, mod_en should be 1
constraint c_mode { mode inside {[4'h5:4'hB]} -> mod_en == 1; }
endclass
module tb;
initial begin
ABC abc = new;
for (int i = 0; i < 10; i++) begin
abc.randomize();
$display ("mode=0x%0h mod_en=0x%0h", abc.mode, abc.mod_en);
end
end
endmodule
Note that mod_en is 1 whenever the LHS expression for mode is inside 4'h5 and 4'hB. However, mod_en can be randomized to any value if the LHS evaluates to false.
ncsim> run mode=0xf mod_en=0x1 mode=0x9 mod_en=0x1 mode=0x3 mod_en=0x1 mode=0xe mod_en=0x1 mode=0x1 mod_en=0x1 mode=0x0 mod_en=0x0 mode=0x1 mod_en=0x0 mode=0xe mod_en=0x0 mode=0x5 mod_en=0x1 mode=0x0 mod_en=0x0 ncsim: *W,RNQUIE: Simulation is complete.
if-else Constraint
The if-else
constraint provides an option to specify the else part of a conditional expression. If the conditional expression is true, then all of the constraints specified in the the first constraint set shall be satisfied. Otherwise, all of the constraints in the optional else
part will be satisfied.
Nested if-else
blocks are allowed and multiple constraint statements require them to be enclosed in curly braces { }
. This is similar to the begin-end
used in a procedural block like initial
and always
. However, constraints are classified as declarative code and hence require curly braces instead.
Example
In the code shown below, the first if block checks whether mode is inside 5 and 11. If this condition is true, then mod_en should be constrained to 1 and if it is false, then the else
part is executed. There is another if-else
block within the else
part which checks if mode is 1 and tries to satisfy the constraints mentioned in the appropriate parts.
class ABC;
rand bit [3:0] mode;
rand bit mod_en;
constraint c_mode {
// If 5 <= mode <= 11, then constrain mod_en to 1
// This part only has 1 statement and hence do not
// require curly braces {}
if (mode inside {[4'h5:4'hB]})
mod_en == 1;
// If the above condition is false, then do the following
else {
// If mode is constrained to be 1, then mod_en should be 1
if ( mode == 4'h1) {
mod_en == 1;
// If mode is any other value than 1 and not within
// 5:11, then mod_en should be constrained to 0
} else {
mod_en == 0;
}
}
}
endclass
module tb;
initial begin
ABC abc = new;
for (int i = 0; i < 10; i++) begin
abc.randomize();
$display ("mode=0x%0h mod_en=0x%0h", abc.mode, abc.mod_en);
end
end
endmodule
ncsim> run mode=0xb mod_en=0x1 mode=0x1 mod_en=0x1 mode=0x6 mod_en=0x1 mode=0x7 mod_en=0x1 mode=0x2 mod_en=0x0 mode=0x2 mod_en=0x0 mode=0x2 mod_en=0x0 mode=0x9 mod_en=0x1 mode=0x7 mod_en=0x1 mode=0x8 mod_en=0x1 ncsim: *W,RNQUIE: Simulation is complete.
Verilog needs to represent individual bits as well as groups of bits. For example, a single bit sequential element is a flip-flop. However a 16-bit sequential element is a register that can hold 16 bits. For this purpose, Verilog has scalar and vector nets and variables.
Scalar and Vector
A net or reg
declaration without a range specification is considered 1-bit wide and is a scalar. If a range is specified, then the net or reg
becomes a multibit entity known as a vector.

wire o_nor; // single bit scalar net
wire [7:0] o_flop; // 8-bit vector net
reg parity; // single bit scalar variable
reg [31:0] addr; // 32 bit vector variable to store address
The range gives the ability to address individual bits in a vector. The most significant bit of the vector should be specified as the left hand value in the range while the least significant bit of the vector should be specified on the right.
wire [msb:lsb] name;
integer my_msb;
wire [15:0] priority; // msb = 15, lsb = 0
wire [my_msb: 2] prior; // illegal
A 16 bit wide net called priority will be created in the example above. Note that the msb and lsb should be a constant expression and cannot be substituted by a variable. But they can be any integer value - positive, negative or zero; and the lsb value can be greater than, equal to or less than msb value.
Bit-selects
Any bit in a vectored variable can be individually selected and assigned a new value as shown below. This is called as a bit-select. If the bit-select is out of bounds or the bit-select is x or z, then the value returned will be x.

reg [7:0] addr; // 8-bit reg variable [7, 6, 5, 4, 3, 2, 1, 0]
addr [0] = 1; // assign 1 to bit 0 of addr
addr [3] = 0; // assign 0 to bit 3 of addr
addr [8] = 1; // illegal : bit8 does not exist in addr
Part-selects

A range of contiguous bits can be selected and is known as a part-select. There are two types of part-selects, one with a constant part-select and another with an indexed part-select.
reg [31:0] addr;
addr [23:16] = 8'h23; // bits 23 to 16 will be replaced by the new value 'h23 -> constant part-select
Having a variable part-select allows it to be used effectively in loops to select parts of the vector. Although the starting bit can be varied, the width has to be constant.
[<start_bit> +: <width>] // part-select increments from start-bit [<start_bit> -: <width>] // part-select decrements from start-bit
module des;
reg [31:0] data;
int i;
initial begin
data = 32'hFACE_CAFE;
for (i = 0; i < 4; i++) begin
$display ("data[8*%0d +: 8] = 0x%0h", i, data[8*i +: 8]);
end
$display ("data[7:0] = 0x%0h", data[7:0]);
$display ("data[15:8] = 0x%0h", data[15:8]);
$display ("data[23:16] = 0x%0h", data[23:16]);
$display ("data[31:24] = 0x%0h", data[31:24]);
end
endmodule
ncsim> run data[8*0 +: 8] = 0xfe // ~ data [8*0+8 : 8*0] data[8*1 +: 8] = 0xca // ~ data [8*1+8 : 8*1] data[8*2 +: 8] = 0xce // ~ data [8*2+8 : 8*2] data[8*3 +: 8] = 0xfa // ~ data [8*3+8 : 8*3] data[7:0] = 0xfe data[15:8] = 0xca data[23:16] = 0xce data[31:24] = 0xfa ncsim: *W,RNQUIE: Simulation is complete.
Common Errors
module tb;
reg [15:0] data;
initial begin
$display ("data[0:9] = 0x%0h", data[0:9]); // Error : Reversed part-select index expression ordering
end
endmodule
The case
statement checks if the given expression matches one of the other expressions in the list and branches accordingly. It is typically used to implement a multiplexer. The if-else construct may not be suitable if there are many conditions to be checked and would synthesize into a priority encoder instead of a multiplexer.
Syntax
A Verilog case statement starts with the case
keyword and ends with the endcase
keyword. The expression within parantheses will be evaluated exactly once and is compared with the list of alternatives in the order they are written and the statements for which the alternative matches the given expression are executed. A block of multiple statements must be grouped and be within begin
and end
.
// Here 'expression' should match one of the items (item 1,2,3 or 4)
case (<expression>)
case_item1 : <single statement>
case_item2,
case_item3 : <single statement>
case_item4 : begin
<multiple statements>
end
default : <statement>
endcase
If none of the case items match the given expression, statements within the default
item is executed. The default
statement is optional, and there can be only one default
statement in a case statement. Case statements can be nested.
Execution will exit the case block without doing anything if none of the items match the expression and a default
statement is not given.
Example
The design module shown below has a 2-bit select signal to route one of the three other 3-bit inputs to the output signal called out . A case
statement is used to assign the correct input to output based on the value of sel . Since sel is a 2-bit signal, it can have 22 combinations, 0 through 3. The default statement helps to set output to 0 if sel is 3.
module my_mux (input [2:0] a, b, c, // Three 3-bit inputs
[1:0] sel, // 2-bit select signal to choose from a, b, c
output reg [2:0] out); // Output 3-bit signal
// This always block is executed whenever a, b, c or sel changes in value
always @ (a, b, c, sel) begin
case(sel)
2'b00 : out = a; // If sel=0, output is a
2'b01 : out = b; // If sel=1, output is b
2'b10 : out = c; // If sel=2, output is c
default : out = 0; // If sel is anything else, out is always 0
endcase
end
endmodule
Hardware Schematic
The rtl code is elaborated to get a hardware schematic that represents a 4 to 1 multiplexer.

See that output is zero when sel is 3 and corresponds to the assigned inputs for other values.
ncsim> run [0] a=0x4 b=0x1 c=0x1 sel=0b11 out=0x0 [10] a=0x5 b=0x5 c=0x5 sel=0b10 out=0x5 [20] a=0x1 b=0x5 c=0x6 sel=0b01 out=0x5 [30] a=0x5 b=0x4 c=0x1 sel=0b10 out=0x1 [40] a=0x5 b=0x2 c=0x5 sel=0b11 out=0x0 ncsim: *W,RNQUIE: Simulation is complete.
In a case statement, the comparison only succeeds when each bit of the expression matches one of the alternatives including 0, 1, x and z. In the example shown above, if any of the bits in sel is either x or z, the default
statement will be executed because none of the other alternatives matched. In such a case, output will be all zeros.
ncsim> run [0] a=0x4 b=0x1 c=0x1 sel=0bxx out=0x0 [10] a=0x3 b=0x5 c=0x5 sel=0bzx out=0x0 [20] a=0x5 b=0x2 c=0x1 sel=0bxx out=0x0 [30] a=0x5 b=0x6 c=0x5 sel=0bzx out=0x0 [40] a=0x5 b=0x4 c=0x1 sel=0bxz out=0x0 [50] a=0x6 b=0x5 c=0x2 sel=0bxz out=0x0 [60] a=0x5 b=0x7 c=0x2 sel=0bzx out=0x0 [70] a=0x7 b=0x2 c=0x6 sel=0bzz out=0x0 [80] a=0x0 b=0x5 c=0x4 sel=0bxx out=0x0 [90] a=0x5 b=0x5 c=0x5 sel=0bxz out=0x0 ncsim: *W,RNQUIE: Simulation is complete.
If the case statement in design has x and z in the case item alternatives, the results would be quite different.
module my_mux (input [2:0] a, b, c,
[1:0] sel,
output reg [2:0] out);
// Case items have x and z and sel has to match the exact value for
// output to be assigned with the corresponding input
always @ (a, b, c, sel) begin
case(sel)
2'bxz : out = a;
2'bzx : out = b;
2'bxx : out = c;
default : out = 0;
endcase
end
endmodule
ncsim> run [0] a=0x4 b=0x1 c=0x1 sel=0bxx out=0x1 [10] a=0x3 b=0x5 c=0x5 sel=0bzx out=0x5 [20] a=0x5 b=0x2 c=0x1 sel=0bxx out=0x1 [30] a=0x5 b=0x6 c=0x5 sel=0bzx out=0x6 [40] a=0x5 b=0x4 c=0x1 sel=0bxz out=0x5 [50] a=0x6 b=0x5 c=0x2 sel=0bxz out=0x6 [60] a=0x5 b=0x7 c=0x2 sel=0bzx out=0x7 [70] a=0x7 b=0x2 c=0x6 sel=0bzz out=0x0 [80] a=0x0 b=0x5 c=0x4 sel=0bxx out=0x4 [90] a=0x5 b=0x5 c=0x5 sel=0bxz out=0x5 ncsim: *W,RNQUIE: Simulation is complete.
How is a case different from if-else ?
The case
statement is different from if-else-if
in two ways:
- Expressions given in a
if-else
block are more general while in acase
block, a single expression is matched with multiple items case
will provide a definitive result when there are X and Z values in an expression