Skip to content

Verilog · Chapter 15.2 · Tasks & Functions

Tasks in Verilog — Procedures with Any I/O and Optional Timing

A task packages a reusable procedure, a sequence of statements with any number of inputs, outputs, and bidirectional ports, or none at all. Where a function returns a single value instantly, a task is the more general tool. It can produce several outputs at once, it can contain delays and event controls so it can consume simulation time, and it is called as a standalone statement rather than inside an expression. That makes it the natural fit for multi-step or timed work such as driving a bus transaction across several clock cycles or applying a reset sequence. A task is synthesizable only when it contains no timing. Add timing and it becomes a simulation and testbench construct. This lesson covers defining a task, calling it, the timing rule, and its use for procedures and stimulus.

Foundation12 min readVerilogtaskProcedureTestbenchStimulus

Chapter 15 · Section 15.2 · Tasks & Functions

1. The Engineering Problem

A repeated procedure — a multi-cycle bus write, a reset sequence, a computation with several outputs — needs packaging, and a function can't do it (one value, no timing). A task can:

A task is a procedure with any number of inputs/outputs/inouts and optional timing, called as a statement — the tool for multi-step and timed operations.

This page drills the task.

2. Mental Model — A Procedure with Arbitrary I/O and Optional Timing

3. Defining a Task

task-def.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // a task with inputs and outputs (no timing → synthesizable):
   task minmax;
       input  [7:0] a, b;
       output [7:0] mn, mx;        // multiple outputs
       begin
           mn = (a < b) ? a : b;
           mx = (a < b) ? b : a;
       end
   endtask
 
   // a timed task (testbench bus write — simulation-only):
   task do_write;
       input [7:0] addr, data;
       begin
           bus_addr = addr; bus_data = data; we = 1;
           @(posedge clk);         // timing → simulation-only
           we = 0;
       end
   endtask
  • A task declares its inputs, outputs, and inouts; results come back through the output arguments.
  • The body is a sequence of statements (in begin...end).
  • Timing (@(posedge clk)) makes a task simulation-only; without it, the task is synthesizable.

4. Calling a Task

task-call.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // as a statement, with arguments mapped to the task's I/O:
   minmax(x, y, low, high);        // low, high receive the outputs
 
   // a timed task in a testbench:
   initial begin
       do_write(8'h10, 8'hA5);
       do_write(8'h11, 8'h3C);
       $finish;
   end

A task is called as a statement; its arguments map to the task's inputs (passed in) and outputs (received back). Timed tasks are the backbone of testbench stimulus — a do_write / do_read pair turns a bus protocol into reusable calls (Chapter 9.5).

Visual A — a task is a callable procedure

task — any I/O, optional timing, a statement

data flow
task — any I/O, optional timing, a statementinputsaddr, datatask (may havetiming)procedure, multi-stepoutputsvia output argscalled as astatementdo_write(addr, data);
A task is a callable procedure: it takes any number of inputs, produces results through output arguments, can contain timing (so it consumes simulation time), and is called as a statement. It is the tool for multi-step and timed operations — testbench transactions, reset sequences, multi-output computations.

5. Task vs Function — Recap

  • Function (15.1) — one return value, no timing, in an expression; combinational reuse.
  • Task — any I/O, optional timing, a statement; procedure reuse.
  • A task can call functions and tasks; a function can call only functions.
  • A synthesizable task has no timing; a timed task is testbench-only.

Choose a task when you need multiple outputs, timing, or a multi-step procedure; choose a function for a single value computed instantly.

6. Common Mistakes

  1. Using a function where multiple outputs are needed — use a task (§5).
  2. Expecting a synthesizable task with timing — timing makes it simulation-only (§3).
  3. Calling a task in an expression — tasks are statements, not expressions (§4).
  4. Static task corrupted by concurrent calls — use automatic for reentrancy (15.3/15.4).

7. Debugging Lab

One task debug post-mortem

Pitfall — two concurrent callers corrupt a static task's shared locals
Buggy Code
// A task that drives one channel: latch the data, wait a cycle, drive the bus.
// It is STATIC by default, so it has ONE shared set of locals for ALL calls.
task drive_channel;                 // static (default)
  input integer ch;
  input [7:0]   data;
  reg   [7:0]   local_buf;        // ONE shared copy across every call
  begin
      local_buf = data;
      @(posedge clk);            // <-- the task suspends here, in time
      bus[ch] = local_buf;
  end
endtask

// The testbench drives several channels CONCURRENTLY:
initial fork
  drive_channel(0, 8'hA1);
  drive_channel(1, 8'hB2);
  drive_channel(2, 8'hC3);
join

// Because the task is static, all three calls share the SAME 'local_buf'.
// Call 0 sets local_buf = A1, then suspends at @(posedge clk). While it is
// suspended, call 1 runs and overwrites local_buf = B2, then call 2 sets
// local_buf = C3. When the clock edge arrives, ALL THREE resume and each
// writes the (now shared) local_buf — so every channel gets C3 instead of
// its own data. The channels corrupt each other through the shared local.
Symptom

A testbench that drives several channels (or transactions) in parallel produces garbled results — channels receive each other's data, or all get the last caller's value. The task looks correct in isolation and works when called one at a time, but fails the moment two callers overlap. The bug is intermittent and depends on how the concurrent calls interleave.

Root Cause

The task is STATIC (the default), so it has exactly ONE set of storage for its locals, shared by every call. That is fine only when calls never overlap. Here the task suspends at '@(posedge clk)', and several calls are launched concurrently from the fork — so multiple invocations are ACTIVE at the same time, all reading and writing the single shared 'local_buf'. While one call is suspended waiting for the clock, another call overwrites the shared local, so when they resume they no longer see their own data. The task is not re-entrant: overlapping calls clobber each other's "private" state because it isn't actually private.

The fix is to declare the task 'automatic', which gives EACH call its own fresh storage (like a stack frame), so concurrent invocations are independent.

Fix
task automatic drive_channel;       // automatic → per-call storage (re-entrant)
  input integer ch;
  input [7:0]   data;
  reg   [7:0]   local_buf;        // a SEPARATE copy for every call
  begin
      local_buf = data;
      @(posedge clk);
      bus[ch] = local_buf;
  end
endtask

// With 'automatic', each of the three concurrent calls gets its own
// 'local_buf', so channel 0 keeps A1, channel 1 keeps B2, channel 2 keeps C3
// across the suspension — no cross-corruption. Any task that can be called
// concurrently (from fork...join, multiple always blocks, or recursively)
// must be 'automatic'. (Static vs automatic storage — 15.3; re-entrancy — 15.4.)

8. Interview Q&A

9. Exercises

Exercise 1 — Write a task

Write a synthesizable task swap that takes two 8-bit inputs and returns them swapped via two outputs.

Exercise 2 — Testbench task

Write a timed task do_read that drives a read on a bus and captures the result one clock edge later.

Exercise 3 — Task or function?

For (a) computing a CRC value and (b) applying a 3-cycle reset, which construct fits each?

10. Summary

A task is a reusable procedure:

  • Any I/O — multiple inputs/outputs/inouts; results via output arguments.
  • Optional timing — can consume simulation time (#/@/wait); synthesizable only without timing.
  • Called as a statementdo_write(addr, data);.
  • Use for procedures, multi-output operations, and testbench stimulus.

The next sub-topic covers storage behaviour: Chapter 15.3 Static & Automatic Behaviour drills how task/function locals are shared (static) or per-call (automatic).