Verilog 14: Tasks & Functions — in-depth

1. Introduction

In Verilog HDL (Hardware Description Language), designers frequently need to reuse code blocks, perform computations, or model specific behaviors. To achieve modularity and reduce repetitive coding, Verilog provides two procedural constructs: tasks and functions.

While both tasks and functions encapsulate reusable code, they serve different purposes:

This tutorial provides a detailed exploration of tasks and functions, their syntax, usage, examples, and best practices — equipping hardware designers and students to model and simulate Verilog designs effectively.


2. Understanding Tasks in Verilog

A task is a procedural block in Verilog that encapsulates code which can be executed multiple times across a design. Tasks are versatile, supporting multiple outputs, timing controls, and even calling other tasks or functions.

Tasks are ideal for:


2.1. Syntax of a Task

task task_name; input [size] input_var; output [size] output_var; reg [size] local_var; // optional local variables begin // Task statements end endtask

Key points:

  • Declared inside a module or included via `include from another file.

  • Inputs and outputs are declared after the task keyword.

  • Local variables are private to the task.

  • Can contain timing controls (@, #, wait) for sequential behavior.


2.2. Example 1: Simple Task

A common example is converting Celsius to Fahrenheit:

module simple_task(); task convert; input [7:0] temp_in; output [7:0] temp_out; begin temp_out = (temp_in * 9)/5 + 8'd32; // integer-safe formula end endtask endmodule

Explanation:

  • temp_in is the Celsius input, temp_out is the Fahrenheit output.

  • Tasks can be called multiple times with different arguments.

  • Integer arithmetic requires careful ordering: multiply first, then divide.


2.3. Task Using Global Variables

Tasks can also operate on global variables without explicit inputs/outputs:

module task_global(); reg [7:0] temp_in, temp_out; task convert; begin temp_out = (temp_in * 9)/5 + 8'd32; end endtask endmodule

Note:
While this approach works, using global variables reduces modularity and can cause unexpected side effects. Best practice: use explicit I/O wherever possible.


2.4. Calling a Task

Tasks are invoked with a call statement. They cannot be used inside expressions.

Example: calling a task from another module:

module task_calling(temp_a, temp_b, temp_c, temp_d); input [7:0] temp_a, temp_c; output [7:0] temp_b, temp_d; reg [7:0] temp_b, temp_d; `include "mytask.v" // including external task file always @(temp_a) convert(temp_a, temp_b); always @(temp_c) convert(temp_c, temp_d); endmodule

Explanation:

  • The task convert is included from mytask.v.

  • It is called twice, each time with different input/output arguments.

  • Tasks reduce repetitive code, making designs cleaner and maintainable.


2.5. Advanced Example: CPU Write/Read Task

Tasks are extremely useful in testbenches for simulating sequential bus transactions.

module bus_wr_rd_task(); reg clk, rd, wr, ce; reg [7:0] addr, data_wr, data_rd; reg [7:0] mem [0:255]; initial begin clk = 0; rd = 0; wr = 0; ce = 0; #1 cpu_write(8'h11, 8'hAA); #1 cpu_read(8'h11, data_rd); #1 cpu_write(8'h12, 8'hAB); #1 cpu_read(8'h12, data_rd); #100 $finish; end // Clock generator always #1 clk = ~clk; // CPU Write Task task cpu_write; input [7:0] address, data; begin $display("%0t: CPU Write addr=%h data=%h", $time, address, data); @ (posedge clk); addr = address; ce = 1; wr = 1; data_wr = data; @ (posedge clk); addr = 8'h00; ce = 0; wr = 0; end endtask // CPU Read Task task cpu_read; input [7:0] address; output [7:0] data; begin $display("%0t: CPU Read addr=%h", $time, address); @ (posedge clk); addr = address; ce = 1; rd = 1; @ (negedge clk); data = mem[addr]; @(posedge clk); addr = 8'h00; ce = 0; rd = 0; $display("%0t: Read data=%h", $time, data); end endtask // Memory behavior always @(addr or ce or rd or wr or data_wr) begin if (ce) begin if (wr) mem[addr] = data_wr; if (rd) data_rd = mem[addr]; end end endmodule

Explanation:

  • Tasks allow reusing write and read sequences multiple times.

  • Timing controls (@posedge clk) simulate realistic hardware behavior.

  • This approach is typical for testbench modularity.


3. Understanding Functions in Verilog

Functions are similar to tasks but with key restrictions:

Feature

Function

Task

Timing controls (@, #)

Not allowed

Allowed

Number of outputs

One (function return)

Multiple (output/inout)

Use in expressions

Yes

No

Can call

Other functions

Functions & tasks

Functions are ideal for combinational computations such as arithmetic operations, parity generation, or ALU calculations.


3.1. Syntax of a Function

function [size] function_name; input [size] var1, var2; reg [size] local_var; // optional begin function_name = var1 + var2; // assign return value end endfunction

Key points:

  • Must return a value using the function name.

  • No timing delays allowed.

  • Can only call other functions, not tasks.


3.2. Example: Simple Function

module simple_function(); function [7:0] add_subtract; input [7:0] a, b, c, d; begin add_subtract = (a + b) - (c + d); end endfunction endmodule
  • Computes (a+b) - (c+d) and returns it as output.

  • Can be used in continuous assignments.


3.3. Calling a Function

Functions can be called inside expressions:

module function_calling(a, b, c, d, e, f); input [7:0] a, b, c, d, e; output [7:0] f; wire [7:0] f; `include "myfunction.v" assign f = (myfunction(a,b,c,d)) ? e : 0; endmodule

Explanation:

  • myfunction(a,b,c,d) returns a value used in a ternary operator.

  • Functions enhance modularity and combinational logic design.


4. Differences Between Tasks and Functions



Feature

Task

Function

Outputs

Multiple (output/inout)

Single (return value)

Timing

Can include @, #, wait

Cannot include delays

Usage

Called as statement

Can be used inside expressions

Calls

Can call tasks/functions

Can call functions only

Typical usage

Testbench sequences, sequential operations

Combinational calculations


5. Practical Tips for Tasks and Functions

  1. Prefer I/O arguments over global variables for modularity.

  2. Integer arithmetic caution: (9/5) truncates to 1. Multiply first: (x*9)/5.

  3. Tasks can model delays, e.g., bus handshakes. Functions cannot.

  4. Use functions for combinational logic: parity, checksum, address calculation.

  5. Synthesis considerations: tasks with @/# are simulation-only, functions are synthesizable if purely combinational.

  6. Reentrancy caution: Classic Verilog tasks are not reentrant; recursive calls can cause conflicts.


6. Combined Example: Task + Function in a Testbench

module tb; reg [7:0] celsius; wire [7:0] fahrenheit; wire parity_bit; // Function for conversion function [7:0] c_to_f; input [7:0] c; begin c_to_f = (c * 9)/5 + 8'd32; end endfunction // Function for parity function parity_func; input [7:0] x; integer i; begin parity_func = 0; for (i=0;i<8;i=i+1) parity_func = parity_func ^ x[i]; end endfunction assign fahrenheit = c_to_f(celsius); assign parity_bit = parity_func(fahrenheit); // Task to display results task show_result; input [7:0] c; begin #1; // simulate propagation delay $display("C=%0d -> F=%0d Parity=%b", c, fahrenheit, parity_bit); end endtask initial begin celsius = 8'd0; show_result(celsius); celsius = 8'd25; show_result(celsius); celsius = 8'd100; show_result(celsius); $finish; end endmodule

Explanation:

  • c_to_f is a function for combinational conversion.

  • parity_func computes a parity bit combinationally.

  • show_result is a task that prints values and includes small simulation delays.

  • This design demonstrates modularity and correct use of tasks/functions in simulation.


7. Common Errors and Best Practices

Mistake

Explanation

Fix

Using timing delays in functions

Not allowed

Move timing to a task

Integer division error

(9/5) → truncation

Multiply before divide (x*9)/5

Omitting outputs in tasks

Task may not modify caller variables

Always pass output variables

Mixing tasks and functions incorrectly

Tasks cannot be used in expressions

Use functions for expressions

Using global variables excessively

Hard to maintain

Prefer explicit inputs/outputs




8. Summary

  • Tasks: Multiple outputs, can include timing, used in sequences and testbenches.

  • Functions: Single output, zero simulation delay, used in combinational expressions.

  • Tasks and functions reduce code repetition, improve readability, and promote modular design.

  • Careful use of integer arithmetic, timing controls, and scoping ensures correct simulation and synthesis behavior.

  • Together, tasks and functions are powerful tools for professional Verilog HDL modeling.


Comments

Popular posts from this blog

Fundamental of python : 1.Python Numbers