Disabling Threads in SystemVerilog: Be careful while using it…!!!

Many times we need to disable the active threads spawned by the fork. But while disabling a fork, one should always be careful, as it can cause an unexpected behaviour. This article explains the unexpected behaviour of disabling a fork, using the different scenarios.

Here, We have two threads, executes parallelly using fork-join_any and our intention is to disable a fork after completion of Thread 1.

Example 1] Disable fork and Disable label with single object of a class

Let’s start with a simple example, where we have a fork-join_any inside a class, which executes two threads parallelly. As in below example 1, a fork is disabled once Thread 1 is completed. This example will produce the same output if we use “disable kill_me” instead of “disable fork” at line number 9, where “kill_me” is a label of the fork (see line number: 4), which we want to disable.

Disable fork Ex 1

For a single object of a class, “disable fork” and “disable label” works fine. But what if the class has multiple objects. Let’s see in the following examples (2 and 3).

Example 2] Disable fork with two objects of the class

Here, the code in example 1 is modified for the two objects of the class. In the class disable_fork, an additional variable “obj_id” is taken to identify that the main method inside the class is called using which object.

Disable fork Ex 2

By looking at the output on the above image, both the objects complete Thread 1 and then disable their “own” fork. What if we use “disable label” (here, a label is kill_me) instead of disable fork?

Example 3] Disable label with two objects of the class

Here, at line 15, the “disable fork” is replaced by “disable kill_me”.

Disable fork Ex 3

Oops, the “disable kill_me” of object 0 has also killed the fork of object 1. This is not the output we were expecting.

So, after looking at all the three examples, it seems that “disable fork” is safer than “disable label”. But “disable fork” is really safe in this context…..??? Let’s see Example 4.

Example 4] Disable fork with multiple forks in the same task

Here, we have added one more fork (join_none) in the main() task of the class. This new fork should not be disabled.

Disable fork Ex 4

But, when we use disable fork, then it disables all the forks in the current scope.

From examples 3 and 4,

  1. if we use “disable label”, then it is visible across all the objects of the same class, so it disables all the forks with the same label in the different objects.
  2. If we use “disable fork”, then it disables all the forks in the current scope.

This problem is resolved in example 5.

Example 5] Safest way to use the disable fork

Here, we have wrapped the fork “kill_me” inside another fork-join.

Disable fork Ex 5

Conclusion:

Always use an additional fork to wrap-up all the forks, you want to diable. This is a safe way, as it limits the scope of a disable fork.

P.S.: Please follow the below link if you want to play with all these five examples 🙂

EDAPlaygound_disable_fork

Advertisements

Why is always block not allowed in Program Block?

The program block came from the Vera verification language that was donated to SystemVerilog. In Vera, a program was a single procedure that represented the “test”. Your test was started at time 0 and when the test terminated, the program terminated the simulation. If you needed multiple test threads, you either had to use the fork statement to start it, or use multiple programs. When the last program terminated, the simulation terminated.

As part of the integration with SystemVerilog, the program was turned into a module-like construct with ports and initial blocks are now used to start the test procedure. Because an always block never terminates, it was kept out of the program block so the concept of test termination would still be there.

Today, most people do not utilize this termination feature because the OVM/UVM have their own test termination mechanisms. The program block is no longer a necessary feature of the language other than to help people converting over from Vera to SystemVerilog.

See my blog: Are Program Blocks Necessary?

Dave Rich

Ref: https://verificationacademy.com/forums/systemverilog/why-system-verilog-does-not-allow-always-block-program-scope

Automatic Storage

Capture

Output:

Automatic Storage Output

Pass by Ref

Ref: System Verilog For Verification – Chris Spear, Greg TumBush

=> In SystemVerilog, you can specify that an argument is passed by reference, rather than copying its value. This argument type, ref, has several benefits over input, output, and inout.

1. Increased performance

=> you can pass an array into a routine, here one that prints the checksum.

Capture

=> SystemVerilog allows you to pass array arguments without the ref direction, but the array is copied onto the stack, this is an expensive operation.
=> Always use ref when passing arrays to a routine for best performance. If you don’t want the routine to change the array values, use the const ref type, which causes the compiler to check that your routine does not modify the array.

Note: The SystemVerilog LRM states that ref arguments can only be used in routines with automatic storage. If you specify the automatic attribute for programs and module, all the routines inside are automatic.

2. A task can modify a variable and is instantly seen by the calling function.

Capture

=> The data argument is passed as ref, and as a result, the @data statement triggers as soon as data changes in the task. If you had declared data as output, the @data statement would not trigger until the end of the bus transaction.

=> In other words, In exa 3.12, in initial block, the bus_read task and thread 2 are parallelly executed in fork-join loop. Thread 2 waits for a data change. In the bus_read task, data is changed inside a blue circle.

If data’s direction was not ref (it was not pass by ref), in that case, the bus_read task would be executed completely and after negedge of bus_grand, the thread 2 would get a changed value of data

But here data is passed by ref, so once data is assigned a value as in blue circle in bus_read task, at a time, thread 2 will get a changed value of data. It will not wait for completion of bus_read task.

Streaming Operators

Ref: System Verilog For Verification – Chris Spear, Greg TumBush

When used on the right side of an assignment, the streaming operators << and >> take an expression, structure, or array, and packs it into a stream of bits. The >>

The >> operator streams data from left to right while << streams from right to left, as shown in Sample 2.51.

You can also give a slice size, used to break up the source before being streamed. You can not assign the bit stream result directly to an unpacked array. Instead, use the streaming operators on the left side of an assignment to unpack the bit stream into an unpacked array.

Temp

You could do the same operations with many concatenation operators, {}, but the streaming operators are more compact and easier to read.

If you need to pack or unpack arrays, use the streaming operator to convert between arrays of different element sizes. For instance, you can convert an array of bytes to an array of words. You can use fixed size arrays, dynamic arrays, and queues.

Sample 2.52 converts between queues, but would also work with dynamic arrays. Array elements are automatically allocated as needed.

Temp

Note: A common mistake when streaming between arrays is mismatched array subscripts. The word subscript [ 256 ] in an array declaration is equivalent to [ 0:255 ], not [ 255:0 ]. Since many arrays are declared with the word subscripts [ high:low ], streaming them to an array with the subscript [ size ] would result in the elements ending up in reverse order. Likewise, streaming an unpacked array declared as bit [ 7:0 ] s rc[255:0] to the packed array declared as bit [ 7:0 ] [ 255:0 ]. Likewise, streaming an unpacked array declared as bit [ 7:0 ] s rc[255:0] to the packed array declared as bit [ 7:0 ] [ 255:0 ] dst will scramble the order of values. The correct declaration for a packed array of bytes is bit [255:0] [7:0] dst.

You can also use the streaming operator to pack and unpack structures, such as an ATM cell, into an array of bytes. In Sample 2.53 a structure is streamed into a dynamic array of bytes, then the byte array is streamed back into the structure.

Temp

SV interview question 3. How to choose a random element from the associative array?

To randomize elements for Fixed arrays, Queues, Dynamic Array and Associative Array:

$urandom_range($size(array) – 1);   or   $urandom_range(array.size() – 1);

For Associative array, it’s not easy to pick up a random element, as elements are stored in sparse manner.

We can use the below code to choose a random element in an associative array.

module main();
int assoc_array[int] = ‘{0:100, 1:200, 5:300, 15:400, 50:500, 1000:600};   // Assign values 100,200,300,400,500 at index 0,1,5,15,50,1000, respectively

int element_num, count, indx;

initial begin
element_num = $urandom_range(assoc_array.size()-1);
$display();
$display(“find %0d%0s element from total %0d elements”,element_num,((element_num==1)? “st”:((element_num==2)? “nd”:((element_num==3)? “rd”:”th”))),assoc_array.size());
foreach(assoc_array[i]) begin
if(count++ == element_num) begin
indx = i;
break;
end
end
count = 0;
$display(“The %0d%0s element is: %0d at index = %0d”,element_num,((element_num==1)? “st”:((element_num==2)? “nd”:((element_num==3)? “rd”:”th”))),assoc_array[indx],indx);
end
endmodule

Result:

find 2nd element from total 6 elements

The 2nd element is: 300 at index = 5

find 0th element from total 6 elements

The 0th element is: 100 at index = 0

SV interview question 2. Specify a width of address line to cover all the locations of a memory block of 8KB. (1 address for 1 byte)

We can calculate the address width of memory block using a system verilog function “$clog2()“.

The $clog2() function returns the number of address bits needed for a memory size. This function calculates the ceiling of log base 2.

For 8KB memory,  2^13=8192 bytes, means we need 13 bits for 8KB memory.

We can get address width using $clog2() function as shown in below example.

Example:

module main();
       parameter MEM = 8192;                          // 8KB Memory
       parameter address_width = $clog2(MEM);
       bit [address_width-1:0] addr;

       initial $display(“address_width = %0d”,address_width);
endmodule

Result: address_width = 13