While working with static methods from SystemVerilog I found a non-intuitive behaviour that I would like to share with you. I could not find it documented anywhere within the SystemVerilog LRM.
What do static and its automatic counterpart mean?
In SystemVerilog IEEE 1800-2012 LRM (Chapter 8.10 page 141), a static method is defined as:
“A static method is subject to all the class scoping and access rules, but behaves like a regular subroutine that can be called outside the class, even with no class instantiation“
whereas automatic is seen as (Chapter 6.21 page 90):
“Variables declared in an automatic task, function, or block are local in scope, default to the lifetime of the call or block, and are initialized on each entry to the call or block.”
We’ll use the following example to illustrate some static related aspects:
class my_class; function int foo(int a=1); foo = 3; endfunction static function int foo_static_1(int a=1); // the method is static, argument and internal variables are automatic foo_static_1 = 3; endfunction static function static int foo_static_2(int a=1); // the method is static, argument and internal variables are static int b; foo_static_2 = 3; endfunction endclass
Never call a static method using an object
Since the foo() method is non-static, we need to create an object, named my_object, in order to call it. Similarly, my_object can be used to call the other two methods, foo_static_1() and foo_static_2(), but this is NOT recommended since static fields/methods don’t “belong” to objects. For static methods it is highly recommended to use the class scope resolution operator (see next chapter).
module top; my_class my_object; initial begin my_object = new; $display("Calling methods via object"); $display("my_object.foo()=%0d",my_object.foo()); $display("my_object.foo_static_1()=%0d", my_object.foo_static_1());//not recommended, even though it's a legal syntax $display("my_object.foo_static_2()=%0d", my_object.foo_static_2());//not recommended, even though it's a legal syntax end endmodule
Calling methods via object my_object.foo()= 3 my_object.foo_static_1()=3 my_object.foo_static_2()=3
Calling a static method using the class scope resolution operator ::
Even if no object of type my_class is created, we can call the static methods by using class scope resolution operator :: like this:
module top; initial begin $display("Calling methods via class scope resolution operator"); $display("my_class::foo_static_1()=%0d",my_class::foo_static_1()); $display("my_class::foo_static_2()=%0d",my_class::foo_static_2()); $display("my_class::foo_static_2.foo_static_2=%0d",my_class::foo_static_2.foo_static_2);//foo_static_2 is the implicit return variable of the function foo_static_2 end endmodule
Calling methods via class scope resolution operator my_class::foo_static_1()= 3 my_class::foo_static_2()= 3 my_class::foo_static_2.foo_static_2= 3
You might have already asked yourself, what is the difference between foo_static_1 and foo_static_2. The only difference is that foo_static_2 has an extra static keyword interpolated between the function keyword and the int return type. What does that mean? The second static keyword from foo_static_2 says that both the arguments of the method and the variables inside the method are implicitly declared as static. Let’s see this in action:
module top; initial begin my_class::foo_static_2.a = 10;// a is the argument of the method my_class::foo_static_2.b = 20;// b is a field inside the method $display("my_class::foo_static_2.a=%0d",my_class::foo_static_2.a); $display("my_class::foo_static_2.b=%0d",my_class::foo_static_2.b); end endmodule
Using class scope operator, you can actually alter the value of the method’s argument. Moreover, that’s done from outside the method, without actually calling it. This is not a typical behavior than one might expect.
Each function has an implicit variable, named after the function’s name. The value of that implicit variable will be returned at the end of the function. Now, with the second static in place, we can also access this implicit variable. It’s interesting to notice that the implicit variable is set to the default value, 0, if the function has not been called yet. But, after calling the function, the implicit variable has changed to the value computed by the function. Check it out below:
module top; initial begin $display("my_class::foo_static_2.foo_static_2", my_class::foo_static_2.foo_static_2); $display("my_class::foo_static_2()", my_class::foo_static_2()); $display("my_class::foo_static_2.foo_static_2", my_class::foo_static_2.foo_static_2); end endmodule
my_class::foo_static_2.foo_static_2 0 my_class::foo_static_2() 3 my_class::foo_static_2.foo_static_2 3
What about tasks?
Tasks, usually, are time consuming methods, When they don’t consume time, they behave as functions that don’t return values. If simulation time is being consumed by the task as seen below, interesting scenarios may pop up. For example foo_static_task starts with a set of values for the argument a and variable b. The task itself does not change the values of a and b, but after a simulation delay #2 their values are not the same anymore. Argument a and variable b were changed from outside the body of the task. This is because both the method (foo_static_task) and the variables (a,b) are static:
class my_class; static task static foo_static_task(int a=1); // the method is static, argument and internal variables are static int b; $display("START TASK. At time %0d value of a is %0d value of b is %0d",$time, a, b); #2; $display("END TASK. At time %0d value of a is %0d value of b is %0d",$time, a, b); endtask endclass module top; initial begin fork my_class::foo_static_task(); join_none #1 my_class::foo_static_task.a = 100; my_class::foo_static_task.b = 200; end endmodule
START TASK. At time 0 value of a is 1 value of b is 0 END TASK. At time 2 value of a is 100 value of b is 200
A static function is useful when you need global functions encapsulated under a common scope (usually a class); e.g. mathematical functions, string processing functions, etc. Having a static task seems to be less useful compared to functions. An use case in which I’ve used a static task is to implement a wait task that waits as many simulation cycles as mentioned via the task’s argument. I consider these type of methods to be a kind of an utility library, stored within one utility class.
Having static function static or static task static is not that intuitive since the SystemVerilog IEEE 1800-2012 LRM doesn’t mention anything about it. Even if this distinction, between static function and static function static, is absent from the LRM, I find it important. What do you think? Do you have any scenarios where using static function static, is helpful? Share your thoughts on this.