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.
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
nis the wall notepad. It counts up: 1, 2, 3. - The automatic
nis 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.

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 = 2into 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.

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
| Static | Automatic | |
|---|---|---|
| Copies of each local variable | One, shared by every call | A new one per call |
| Created | Once, at time 0 | When the call starts |
| Remembers its value between calls | Yes | No |
| Safe for recursion | No | Yes |
| Safe when two threads call it at once | No | Yes |
| Notepad picture | One notepad on the wall | A fresh sticky note each time |
| Good for | Counters, “has this run before?” flags | Drivers, monitors, helpers, recursion |
Every idea on this page, in one picture
| Idea | Picture it as |
|---|---|
| Static variable | One notepad pinned to the office wall |
| Automatic variable | A fresh sticky note for each job, binned when done |
| Recursion | A chain of helpers: one shared whiteboard (static) vs a note each (automatic) |
| Two threads, one task | Two cashiers sharing one receipt pad |
| Static initializer | A form filled in before the shop opened, so it can’t know the customer |
| Module task default | An old office: notepads on the wall unless you ask for sticky notes |
| Class method default | A hot-desk office: everyone always gets sticky notes |
module automatic | Renovating the old office so sticky notes become the default |
static class method | A head-office phone line: anyone can call without visiting a branch, but whoever answers still uses sticky notes |
| Race on a shared property | Two people writing on the same whiteboard in the same second: last pen wins |
| Semaphore or mailbox | A talking stick: only the holder may write |
What’s the default?
| Where it’s declared | Default lifetime |
|---|---|
| Task or function in a module, interface or program | Static |
Same, inside module automatic (or program/interface automatic) | Automatic |
| Method of a class | Automatic, always |
Variable in an initial or always block | Static |
Loop variable declared in for (int i ...) | Automatic |
| Local variable in an automatic task or function | Automatic, 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)andprintme(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 printedd = 1, not 101 (Verification Academy, 2016). - Why: a static variable is initialized once, at time 0, before any call, when
d_inwas still 0. The standard now makes this code illegal without an explicitstatic, 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
nfor 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
staticclass method are shared between calls (Verification Academy, 2015). - Why: no.
staticbeforefunctionin 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 functionandfunction staticas 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
staticclass 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
- Static properties and methods: the other meaning of
static - fork join vs join_any vs join_none: where two threads calling one task comes from
Part of the learning path SystemVerilog for verification
