Static vs Automatic Lifetime in SystemVerilog (With Side-by-Side Output)

In short: Lifetime decides how many copies of a variable exist. A static variable has one copy, created once and shared by every call; it remembers its value between calls. An automatic variable gets a fresh copy every time the task or function is called, and it disappears when the call ends. Tasks and functions in modules are static by default, while class methods are always automatic. Use automatic for anything recursive or called from more than one thread at once.

This is one of those topics that looks academic.

Until two threads call the same driver task and both report the same packet ID.

Then it’s the most practical thing you’ll learn this week.

Think of it like a notepad

  • Static = one notepad pinned to the office wall. Everyone who does the job writes on the same notepad. It keeps whatever was written last, even after you walk away.
  • Automatic = a fresh sticky note for each person. You get your own note when you start the job, and it goes in the bin when you finish.

One person working alone? Both work fine.

Two people doing the job at the same time, or one person asking a colleague to do part of the same job (recursion)? With the wall notepad, they overwrite each other.

Static lifetime is one shared notepad that two calls overwrite; automatic lifetime gives each call its own sticky note
Figure 1. Static is one notepad on the wall. Automatic is a fresh sticky note per call.

Example 1: does the variable remember?

// The same function, static vs automatic: does the local variable remember?
module tb;
  function static int count_static();
    static int n = 0;          // set to 0 once, at time 0
    n++;
    return n;
  endfunction

  function automatic int count_auto();
    int n = 0;                 // set to 0 on every call
    n++;
    return n;
  endfunction

  initial begin
    repeat (3) $display("static: %0d   automatic: %0d", count_static(), count_auto());
    $finish;
  end
endmodule

Output (tested on Icarus Verilog 12.0, 11 Nov 2026):

static: 1   automatic: 1
static: 2   automatic: 1
static: 3   automatic: 1
  • The static n is the wall notepad. It counts up: 1, 2, 3.
  • The automatic n is a fresh sticky note. It’s 1 every time.

Notice the static keyword on static int n = 0;. In a static function, a local variable with an initializer must say static out loud. Without it, the code is illegal (IEEE 1800-2017, 6.21), and Icarus warns about it. That rule exists because of mistake 2 below.

Example 2: recursion

// Recursion: a static function shares one n across all calls.
module tb;
  function static int fact_static(int n);
    int rest;
    if (n <= 1) return 1;
    rest = fact_static(n - 1);   // the inner calls overwrite the shared n
    return n * rest;
  endfunction

  function automatic int fact_auto(int n);
    int rest;
    if (n <= 1) return 1;
    rest = fact_auto(n - 1);     // each call has its own n
    return n * rest;
  endfunction

  initial begin
    $display("fact_static(5) = %0d", fact_static(5));
    $display("fact_auto(5)   = %0d", fact_auto(5));
    $finish;
  end
endmodule

Output (Icarus Verilog 12.0):

fact_static(5) = 1
fact_auto(5)   = 120

The static version calls itself with 4, then 3, 2, 1, all writing into the same n. When the calls unwind, n is still 1 everywhere, so every multiply is 1 x 1. The automatic version keeps a separate n for each call and gets 120.

Static recursion shares one n so fact_static(5) returns 1; automatic recursion keeps n per call so fact_auto(5) returns 120
Figure 2. Recursion: one shared n gives 1, one n per call gives 120 (tested on Icarus).

Think of it as a chain of helpers:

  • You ask a colleague for 5!. They need 4!, so they ask another colleague, who asks another, down to 1!.
  • Static: the whole chain shares one whiteboard. Each helper wipes it and writes their own number. By the time the answers come back up, the board says n = 1, so everyone multiplies by 1.
  • Automatic: each helper has their own sticky note. Their number is still there when the answer comes back, so the chain builds 2, 6, 24, 120.

Example 3: two threads, one driver task

This is the one that bites in real testbenches.

// Two threads call the same task at once. Static: they share 'id'.
module tb;
  task static send_static(int id);
    #10 $display("%0t ns: static task finished packet %0d", $time, id);
  endtask

  task automatic send_auto(int id);
    #10 $display("%0t ns: automatic task finished packet %0d", $time, id);
  endtask

  initial begin
    fork
      send_static(1);
      begin #2 send_static(2); end
    join
    fork
      send_auto(1);
      begin #2 send_auto(2); end
    join
    $finish;
  end
endmodule

Output (Icarus Verilog 12.0):

10 ns: static task finished packet 2
12 ns: static task finished packet 2
22 ns: automatic task finished packet 1
24 ns: automatic task finished packet 2
  • Packet 1 started at 0 ns. At 2 ns the second call wrote id = 2 into the same variable. So at 10 ns, the first call reports packet 2. Packet 1 is lost.
  • The automatic task gives each call its own id, so both packets are reported correctly.
Timeline: call 1 starts at 0 ns, call 2 at 2 ns overwrites the shared id, so both calls report packet 2
Figure 3. Two threads, one static task: packet 1 is overwritten at 2 ns.

Think of two cashiers sharing one receipt pad:

  • Cashier 1 writes “Order 1” and turns away to bag the groceries.
  • Cashier 2 writes “Order 2” on the same pad.
  • Cashier 1 turns back and prints the receipt. It says “Order 2”.

That’s the static task. The automatic task gives each cashier their own pad.

Picture a scoreboard fed by this static task: one packet counted twice, one never counted, and a mismatch that looks like a DUT bug.

Why Icarus and not Verilator? Verilator 5.020 printed the automatic-style result (120, and packets 1 then 2) even for the static versions of examples 2 and 3. That’s handy for speed, but it hides this exact bug, so test lifetime questions on a simulator that follows the standard.

Static vs automatic, side by side

StaticAutomatic
Copies of each local variableOne, shared by every callA new one per call
CreatedOnce, at time 0When the call starts
Remembers its value between callsYesNo
Safe for recursionNoYes
Safe when two threads call it at onceNoYes
Notepad pictureOne notepad on the wallA fresh sticky note each time
Good forCounters, “has this run before?” flagsDrivers, monitors, helpers, recursion

Every idea on this page, in one picture

IdeaPicture it as
Static variableOne notepad pinned to the office wall
Automatic variableA fresh sticky note for each job, binned when done
RecursionA chain of helpers: one shared whiteboard (static) vs a note each (automatic)
Two threads, one taskTwo cashiers sharing one receipt pad
Static initializerA form filled in before the shop opened, so it can’t know the customer
Module task defaultAn old office: notepads on the wall unless you ask for sticky notes
Class method defaultA hot-desk office: everyone always gets sticky notes
module automaticRenovating the old office so sticky notes become the default
static class methodA head-office phone line: anyone can call without visiting a branch, but whoever answers still uses sticky notes
Race on a shared propertyTwo people writing on the same whiteboard in the same second: last pen wins
Semaphore or mailboxA talking stick: only the holder may write

What’s the default?

Where it’s declaredDefault lifetime
Task or function in a module, interface or programStatic
Same, inside module automatic (or program/interface automatic)Automatic
Method of a classAutomatic, always
Variable in an initial or always blockStatic
Loop variable declared in for (int i ...)Automatic
Local variable in an automatic task or functionAutomatic, unless you write static

Picture two offices. A module is an old office: tasks write on the wall notepad unless you ask for sticky notes. A class is a hot-desk office: every method always gets sticky notes. Writing module automatic is renovating the old office so sticky notes become the default.

This is IEEE 1800-2017, sections 6.21 and 13.3.1. The safe habit: write task automatic / function automatic in every module, or declare the whole module module automatic.

**Don’t mix up the two meanings of static in a class.** static function void f(); (static before function) makes a class method you can call without an object. It does not change the lifetime: class methods are still automatic. That’s a different topic, covered in static properties and methods.

Common mistakes (and the fix for each)

These come from real Verification Academy threads, retold in my own words with links.

1. Forking the same module task twice

  • What happened: in 2019 an engineer forked printme(0,3) and printme(1,4). The first call waited on an event, and when it woke up it printed the second call’s inputs (Verification Academy, 2019).
  • Why: a task in a module is static by default, so both calls share the same argument variables. Same story as example 3.
  • Fix: task automatic printme(...).

2. Initializing a local variable in a static function

  • What happened: in 2016 someone wrote int d = d_in + 1; inside a static function and called it with 100. It printed d = 1, not 101 (Verification Academy, 2016).
  • Why: a static variable is initialized once, at time 0, before any call, when d_in was still 0. The standard now makes this code illegal without an explicit static, to stop exactly this confusion.
  • Picture it: a form filled in before the shop opened. It was written once, before any customer walked in, so it can’t know who the customer is.
  • Fix: make the function automatic, or set the value with a separate assignment inside the function.

3. Recursion in a module function

  • What happened: in 2020 an engineer wrote a recursive factorial in a module and got 1 for every input (Verification Academy, 2020). The same question came back in 2022 (Verification Academy, 2022).
  • Why: example 2: one shared n for every level of the recursion.
  • Fix: function automatic.

4. Thinking a static class method has static locals

  • What happened: in 2015 someone asked whether the local variables of a static class method are shared between calls (Verification Academy, 2015).
  • Why: no. static before function in a class means “no object needed”, not “static lifetime”. The locals are still automatic.
  • Picture it: a head-office phone line. Anyone can call it without visiting a branch (no object needed). But whoever answers still takes notes on a fresh sticky note.
  • Fix: nothing to fix; just read static function and function static as two different things.

5. Blaming lifetime for a race

  • What happened: in 2022 an engineer called a class’s static task from two forks at the same time and expected both to print the same value (Verification Academy, 2022).
  • Why: the task’s locals were automatic; the shared thing was a static class property, and two threads writing it in the same time step is a plain race.
  • Picture it: two people writing on the same whiteboard in the same second. Whoever’s pen lands last wins. Giving them their own sticky notes doesn’t help when the thing they share is the whiteboard. They need a talking stick, and a semaphore is exactly that.
  • Fix: don’t share a variable between threads without a handshake (a mailbox or semaphore). See fork join vs join_any vs join_none.

FAQ

What is the difference between static and automatic in SystemVerilog?

Static: one shared copy that remembers its value. Automatic: a new copy for each call that disappears when the call ends.

Are tasks in a module static or automatic?

Static, unless you write task automatic or the module is declared module automatic.

Are class methods static or automatic?

Always automatic.

Why do recursive functions need automatic?

Each level of the recursion needs its own copy of the arguments. Static gives them all the same one.

Can an automatic function have a static variable?

Yes: static int calls = 0; inside an automatic function keeps one shared count.

Does lifetime matter if only one thread calls the task?

Only for recursion and for initialized locals. But code gets reused, so automatic is the safe default.

References

  • IEEE 1800-2017, SystemVerilog standard: section 6.21 (scope and lifetime), 13.3.1 (static and automatic tasks), 13.4.2 (static and automatic functions), 8.10 (static methods).
  • Verification Academy forum threads linked in the mistakes section (accessed 11 Nov 2026).
  • All examples tested on Icarus Verilog 12.0 on 11 Nov 2026.

What to read next