Gotcha: “static” function/task in SystemVerilog

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.”

Simple example

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

OUTPUT:

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

OUTPUT:

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

Twice static?

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

OUTPUT:

my_class::foo_static_2.a=10
my_class::foo_static_2.b=20

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

OUTPUT:

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

OUTPUT:

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

Conclusions

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.

Comments

9 Responses

  1. You seem to be confusing concepts. There is the concept of ‘static’ and ‘dynamic’ (i.e. being called on classes or objects) and ‘static’ and ‘automatic’ (i.e. lifetime). It’s unfortunate that SV uses the same keyword for two different concepts. Normal ‘staticness’ (the one we’re used to in OOP) is defined in section 8.10 Static methods. Lifetime is defined in 13.3.1 Static and automatic tasks.

    The ‘double static’ you talk of is a static class method with static lifetime. The behavior you see (variables carrying over across invocations and allowed to be referenced) is as per the LRM for statically allocated methods.

    1. I think that most would agree that ‘static function static’ looks strange and this is what I tried to illustrate and explain. First ‘static’ is for the function, second ‘static’ is for the variables.

      Speaking of confusion, how do you see static class variables? They are ‘static’ as opposed to ‘dynamic’ (to paraphrase you “being accessed on classes, not on objects”)? Or do they have a ‘static’ lifetime? Both?

    2. Well, the whole concept of static (as opposed to automatic) is pretty strange for a programmer. I guess it has something to do with RTL modeling and it’s leftover baggage from Verilog, but I’m just speculating here.

      Since this concept (of static/automatic lifetime) is defined for methods, I don’t think we can can reason about it when talking about static class variables.

    3. It’s not very strange for a C++ programmer since these are exactly the same as in C++ (as far as I can tell). It also has the same overloading of `static` to mean two slightly different things. The only difference with SV is the crazy ability to access arguments and return values directly, and the weird syntax to change all variable declarations to static or automatic. In C++ they’re all automatic unless you explicitly set each one to static.

      The C++ equivalent would be:

      class my_class {
      public:
        int foo(int a=1) {
          return 3;
        }
      
        static int foo_static_1(int a=1) { // the method is static, argument and internal variables are automatic
          return 3;
        }
      
        static int foo_static_2(int a=1) { // the method is static, argument and internal variables are static
          static int b;
          return = 3;
        }
      };

      Note that you can’t access `b` at all from outside its function (this is the correct behaviour; SV is insane here).

      The reason for static functions is obvious. The reason for static variables is less obvious. They are equivalent to global variables in all ways except scope. So instead of writing

      class my_class {
        int foo() {
          static int b;
          return b;
        }
      };

      You could easily just write:

      int b;
      class my_class {
        int foo() {
          return b;
        }
      };

      The reasons to use the former approach are:

      1. It logically groups `b` into the place where it is actually used, and (in C++, but apparently not SV!) it prevents other bits of code from accessing it.
      2. It avoids stack allocation. This is a crazy crazy not at all important stupid optimisation that you should never care about.

      Maybe there are more subtleties in SV but in C++ you should *almost* never use `static` variables. They are a huge red flag.

  2. Also, I have to nit-pick and say that you’re making an “unfair” side-by-side comparison right in the beginning of the post where you define what static (as in static/dynamic) and automatic (as in static/automatic) are.

    1. Well, the standard allows for such comparisons since it uses the same keyword for 2 separate concepts. If the standard would be clear and it would separate them in a clear way, such ambiguities would go away.

      If you would ask me, I would rename the keyword for defining static/automatic lifetime with the keywords static_lifetime and automatic_lifetime.

      Also keep in mind that there is no such “dynamic” as opposed to static the standard only talks about static and non-static (which is not a keyword). The standard defines the class_item_qualifier::= static | protected | local. So, when it talks about non-static most probably it refers to the keywords protected and local.

  3. The twice static function foo_static_2 is illegal in the IEEE 1800-2012 standard. Class methods always have automatic lifetimes and you cannot change that anymore.

    1. In 13.3.1, only “tasks” within class are always automatic lifetime. In other word, it is still possible to define “function” within class with static lifetime. So twice static function within class is legal.

    2. @Aldo Tamaela, I think you missed 13.4.2 which says the same thing as 13.3.1 w.r.t. functions. Section 8.6 clarifies that “The lifetime of methods declared as part of a class type shall be automatic. It shall be illegal to declare a
      class method with a static lifetime.”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to our newsletter

Do you want to be up to date with our latest articles?