Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations35
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
    • Procedural Code & Scheduling
      • always_comb, always_ff, always_latch
      • Blocking vs Nonblocking Assignments
      • Tasks vs Functions
      • The Event Scheduler & Stratified Regions
      • initial, final & Startup Ordering
      • Loops & Flow Control
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
  • Principal-Level Verification28
Loading lesson

‹Procedural Code & Scheduling
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Procedural Code & Scheduling
  5. Tasks vs Functions

Part 1 · Language Foundations · Intermediate

Tasks vs Functions

Tasks that consume time versus zero-time functions, automatic storage and argument passing.

On this page

    Why this matters

    Two parallel calls to one task returned each other's results. The task was static, so both calls shared one set of arguments and locals.

    After this lesson you can: choose a task or a function, make routines automatic where calls can overlap, and pick input, output, inout or ref arguments.

    Time divides tasks from functions

    A function must execute in zero simulation time. No # delays, no @ event controls, no wait, and no task calls, which is what lets functions appear inside expressions.

    A task may consume time and block on events, so bus drivers and monitors are tasks. A void function returns nothing but keeps the zero-time guarantee, which suits pure side effects such as printing or counting.

    Aspect

    Function

    Task

    May consume time

    Never

    Yes: delays, events, wait

    Callable from

    Expressions and other functions

    Procedural code, never from a function

    Typical use

    CRC, address math, void reporters

    Bus drivers, monitors, checkers

    Automatic versus static storage

    Routines in modules and interfaces default to static lifetime: one shared copy of every argument and local. Two concurrent calls overwrite each other's state with no warning of any kind.

    Declaring the routine automatic gives every call a private stack frame. Class methods are always automatic; module and interface routines are the danger zone.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet: two parallel calls share one frame, so the second call corrupts the first
    task send_pkt(input int id, input int len);
      int beats;
      beats = len;
      repeat (beats) begin
        @(posedge clk);
      end
    endtask

    The fix is one keyword. The body stays identical; only the storage class changes.

    How arguments pass

    Predict first A task counts beats into an output int got while it runs for 6 clocks. The caller reads its variable after 3 clocks. What does it see?

    Its old value. output arguments copy out only when the task returns, so the caller sees 6 at the end and nothing before.

    Inputs copy in at call time, while output and inout copy out at return. The caller sees nothing until the routine returns, so an output never streams live updates.

    A ref argument is a true alias: reads and writes touch the caller variable directly, and large arrays avoid an expensive copy. It requires an automatic routine. const ref keeps the no-copy efficiency with a read-only contract.

    Direction

    Caller sees the value

    Cost for large arrays

    input

    N/A: copies in at call

    One full copy

    output, inout

    Only at return

    One full copy each way for inout

    ref

    Immediately, it is an alias

    No copy

    const ref

    N/A: read-only

    No copy

    Common pitfall

    Expecting an output argument to update the caller while the task still runs. Copy-out happens at return; live updates need ref.

    Interview

    “Two forked branches call one module task and both get wrong data.”

    The task is static, so the calls share arguments and locals. Declare it automatic for a private frame per call.

    Check yourself A function needs to wait for a clock edge before returning its result. What is the structural fix?

    It cannot wait: functions run in zero time. Split the work so a task handles the waiting and the function handles the pure computation.

    Check yourself Why does the language allow ref arguments only in automatic routines?

    A ref names the caller's variable for one call. A static routine keeps one shared frame that outlives every call, so it has nowhere safe to hold a per-call alias.

    Testbench

    Concurrent automatic calls proving independent frames, plus a const ref checksum that avoids copying the array. The first bench uses fork ... join, which starts the two calls at the same time and waits for both; the OOP part teaches it properly.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    module tb_auto_task;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      int got0 = -1, got1 = -1;
      int errors = 0;
      task automatic send_pkt(input int id, input int len, output int got);
        int beats;
        beats = len;
        got = 0;
        repeat (beats) begin
          @(posedge clk);
          got = got + 1;
        end
      endtask
      initial begin
        fork
          send_pkt(0, 6, got0);
          send_pkt(1, 2, got1);
        join
        if (got0 !== 6) errors++;
        if (got1 !== 2) errors++;
        if (errors != 0) $fatal(1, "FAIL: automatic task calls show unexpected beat counts");
        $display("PASS: concurrent automatic calls return 6 and 2 beats with no crosstalk");
        $finish;
      end
    endmodule
    Terminal
    PASS: concurrent automatic calls return 6 and 2 beats with no crosstalk
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function automatic int unsigned checksum(const ref byte data[]);
      int unsigned total = 0;
      foreach (data[i]) total += int'(data[i]);
      return total;
    endfunction
    
    module tb_checksum;
      byte frame[];
      int unsigned got = 0;
      int errors = 0;
      initial begin
        frame = new[4];
        frame[0] = 8'h01; frame[1] = 8'h02;
        frame[2] = 8'h03; frame[3] = 8'h04;
        got = checksum(frame);
        if (got !== 10) errors++;
        if (errors != 0) $fatal(1, "FAIL: checksum gave an unexpected total");
        $display("PASS: const ref checksum of 01 02 03 04 is 10 with no array copy");
        $finish;
      end
    endmodule
    Terminal
    PASS: const ref checksum of 01 02 03 04 is 10 with no array copy

    Remember

    • Functions run in zero time and can sit inside expressions; tasks may wait.

    • Routines in modules are static by default; automatic gives each call its own copy.

    • output copies back at return; ref is a live alias and needs an automatic routine.

    Rule of thumb: anything called from parallel code is automatic.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the const ref checksum bench; the parallel-task bench runs on both.

    Related topics

    • Next: The Event Scheduler

    Previous

    21 / 363

    Next: The Event Scheduler & Stratified Regions

    Up next

    Up nextThe Event Scheduler & Stratified Regions

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • always_comb, always_ff, always_latch
    • initial, final & Startup Ordering
    • Loops & Flow Control