VLSIChaps Academy · Track 01

Tasks, Reusable Stimulus and the Lifetime Trap

~12 min Hands-on lab 1 exercise Verilator
Page 14 of 50
01

A Routine That Takes Time

A task is a named routine you declare once and call as many times as you need, and it has one property that defines it: a task may consume simulation time. Delays, waits, and clock edges are all legal inside a task, which makes it the natural container for stimulus — drive a value, hold it, release it, all wrapped behind one call. Functions, which you will meet properly soon, are the opposite: instant, time-free computation, and a delay inside one is illegal. When the job involves time, the job belongs in a task.

02

Arguments, Copies by Default

A task receives its inputs as copies. When you call it, the values you pass are copied into the task's own storage, and whatever the task does with those copies, the caller's variables do not move. That is passing by value, and it is the default because it is safe: a task cannot accidentally reach back and disturb its caller. When you genuinely need a task to update a caller's variable, the language provides passing by reference, which hands the task the variable itself rather than a copy. Reach for it only when the task's purpose is to give something back; for driving stimulus, copies are exactly what you want.

03

Reuse Is Correctness

Look at any beginner testbench and you will find the same block of stimulus pasted three, five, ten times with small edits. The cost is not aesthetics. Ten pasted copies means ten places a bug can live and ten edits every time the protocol changes, and sooner or later one copy drifts from the others and the testbench lies to you. Refactoring the repetition into one task changes the arithmetic: one place to fix, one place to read, and every call guaranteed to behave the same way. This is why reusable tasks are the first step toward a maintainable testbench, and it is the step this lab makes you take.

04

Key Insight: Lifetime

Here is the trap that catches people years into the job. A task declared in a module is static by default: there is exactly one set of storage for its arguments and locals, shared by every call. Call the task, let it wait, and call it again while the first call is still waiting, and the second call overwrites the shared storage out from under the first. When the first call wakes and reads its arguments, it reads the second call's values. Nothing errors; the output is simply wrong, and the corruption only appears when calls overlap, which is exactly when you are least likely to suspect the task. The fix is one word: declare the task automatic, and every call gets its own private storage, making overlapping calls fully independent. In this lab, your task will be called twice at the same moment by code we provide, and the output will show you, at one exact line, whether your calls stayed independent. This whole mechanism rests on one earlier idea — a waiting call sleeps while time advances — because the corruption depends on the first call still being asleep when the second one runs.

Lab: Refactor the Repetition, Then Survive the Overlap

Part one: the testbench below drives three pulses with the same block of code pasted three times. Replace the repetition with a single task named drive_pulse, taking a value and a duration, that sets the bus, waits the duration, and prints the completion line, then call it three times. Part two is already written for you and must not be modified: it calls your task twice at the same moment with different values. Declare your task so overlapping calls stay independent. Run and submit. The engine checks the full printed sequence, and if the overlapping calls corrupted each other, it will show you the exact line where one call reported the other call's value.

The three pasted blocks differ in only two numbers each, the value and the duration. Those two numbers are your task's arguments; everything else is the task's body — set the bus, wait, print. And when the provided code calls your task twice at once, each call needs its own private copies of those arguments, which is exactly what the automatic keyword provides.
Compiling, linting, and simulating...
Correct. One task, five calls, and the two overlapping calls stayed fully independent, each reporting its own value at its own time. You have just built your first reusable stimulus routine and armoured it against the trap that catches even experienced engineers: shared storage under overlap. That one word, automatic, is why your fifth line says 5 and not 9.
Not quite. Your refactor works in sequence, but the overlapping calls corrupted each other: one call reported the other call's value at a named line. Your task's calls are sharing a single set of argument storage. The engine output and the mentor below show the exact line and the one-word fix.
Compile error.
Something went wrong reaching the engine. Try again in a moment.
AI Mentor
Thinking...
📲 Join 20K+ Engineers