Lesson 10: Tasks, Functions, and Directives
Tasks are like procedures in other programming languages, e.g., tasks may have zero or more arguments and do not return a value. Functions act like function subprograms in other languages. Tasks and functions serve different purposes in Verilog. We discuss tasks and functions in greater detail in the following sections. However, first, it is important to understand the differences between tasks and functions, as outlined in Table 10-1.
Table 10-1:Tasks and Functions
Functions | Tasks |
---|---|
A function can invoke (call, enable) another function, but not another task. | A task can call other tasks and functions. |
Functions always execute in 0 simulation time. | Tasks may execute in non-zero simulation time. |
Delay control (#), event control(@), and wait statements are not allowed in functions. | Tasks may contain delay control (#), event (@), or timing control statements. |
Functions must have at least one input argument. They can have more than one input. | Tasks may have zero or more arguments of type input, output, or inout. |
Functions always return a single value, and the function name is the return variable. They cannot have output or inout arguments. | Tasks do not return with a value but can pass multiple values through output and inout arguments. |
Functions are used when common Verilog code is purely combinational, executes in zero simulation time, and provides exactly one output. | Tasks are used for common Verilog code that contains delays, timing, event constructs, or multiple output arguments. |
Functions are typically used for conversions and commonly used calculations. |
The following lists are the same between tasks and functions:
- Both tasks and functions must be defined in a module and are local to the module.
- Tasks or functions cannot have wires.
- Tasks and functions contain behavioral statements only.
- Task and functions do not contain always or initial statements but are called from always blocks, initial blocks, or other tasks and functions.
- Tasks and functions can have local variables, registers, time variables, integers, real, or events.
Tasks
Tasks are defined with the keyword task and endtask. Tasks must be used if any one of the following conditions is true for the procedure:
- There are delay, timing, or event control constructs in the procedure.
- The procedure has zero or more than one output argument.
- The procedure has no input arguments.
The definition of a task is the following:
task [automatic] <task name>;
<argument ports>
<declarations>
<statements>
endtask
task [automatic] <task name> (<port list>);
<declarations>
<statements>
endtask
where <port list> is a list of expressions that correspond to the <argument ports> of the definition. Port arguments in the definition may be input, inout, or output. Since the <argument ports> in the task definition look like declarations, the programmer must be careful in adding declares at the beginning of a task.
input and inout parameters are passed by value to the task and output and inout parameters are passed back to invocation by value on return. A call by reference is not available.
Use of input and output arguments
The following code illustrates the use of input and output arguments in tasks.A task called bitwise_oper, computes the bitwise and, bitwise or, and bitwise xor of two 16-bit numbers. The two 16-bit numbers a and b are inputs and the three outputs are 16-bit numbers ab_and, ab_or, and ab_xor. A delay is also used in the task.
module operation; ... ... parameter DELAY = 10; reg [15:0] A, B; reg [15:0] AB_AND, AB_OR, AB_XOR; always @(A or B) begin bitwise_oper(A, B, AB_AND, AB_OR, AB_XOR); end // define task bitwise_oper task bitwise_oper; input [15:0] a, b; output [15:0] ab_and, ab_or, ab_xor; begin #DELAY ab_and = a & b; ab_or = a | b; ab_xor = a ^ b; end endtask ... endmodule
Another method of declaring arguments for tasks is the ANSI C style. The following code shows the bitwise_oper task defined with ANSI C style argument declaration.
// define task bitwise_oper task bitwise_oper (input [15:0] a, b, output [15:0] ab_and, ab_or, ab_xor); begin #DELAY ab_and = a & b; ab_or = a | b; ab_xor = a ^ b; end endtask
Asymmetric Sequence Generator
Tasks can directly operate on reg variables defined in the module. The following code directly operates on the reg variable clock to continuously produce an asymmetric sequence.
module sequence; ... reg clock; ... initial begin inti_sequence; // Call the task init_sequence end always begin asymmetric_sqeuence; // Call the task asymmetric_sqeuence end // Initialization sequence task inti_sequence; begin clock = 1'b0; end endtask // Define task to generate asymmetric sequence task asymmetric_sqeuence; begin #12 clock = 1'b0; #3 clock = 1'b1; #5 clock = 1'b0; #10 clock = 1'b1; end endtask ... endmodule
Automatic (Re-entrant) Tasks
Tasks are normally static in nature. All declared items are statically allocated and they are shared across all uses of the task executing concurrently. Therefore, if a task is called concurrently from two places in the code, these task calls will operate on the same task variables. it is highly likely that the results of such an operation will be incorrect.
To avoid this problem, a keyword automatic is added in front of the task keyword to make the task re-entrant. All items declared inside automatic tasks are allocated dynamically for each invocation. Each task call operates in an independent space. Thus, the task calls operate on independent copies of the task variables. The results are incorrect operations. It is recommended that automatic tasks be used if there is a chance that a task might be called concurrently from two locations in the code.
// cls2 runs at twice the frequency of clk and is synchronous module top (clk, clk2, ...); input clk, clk2; ... reg [15:0] ab_xor, cd_xor; reg [15:0] a, b, c, d; ... task automatic bitwise_xor; input [15:0] x, y; output [15:0] xy_xor; begin #10 xy_xor = x ^ y; end endtask // There two always blocks will call the bitwise_xor task concurrently at each positive edge of clk. // Since the task is re-entrant, these concurrent calls will work corrently. always @(posedge clk) begin bitwise_xor(a, b, ab_xor); end always @(posedge clk2) begin bitwise_xor(c, d, cd_xor); end ... endmodule
Functions
Functions are declared with the keyword function and endfunction. Functions are used if all of the following conditions are true for the procedure:
- There are no delay, timing, or event control constructs in the procedure.
- The procedure returns a single value.
- There is at least one input argument.
- There are no output or inout arguments.
- There are no nonblocking assignments.
The syntax for functions follows in below:
function [automatic] [signed] <rang or type> <function name>;
<argument ports>
<declarations>
<statements>
endfunction
function [automatic] [signed] <rang or type> <function name>;
<declarations>
<statements>
endfunction
Where <range or type> is the type of the results passed back to the expression where the function was called. If no range or type is specified, the default bit width is 1. When a function is declared, a register with the name <function name> is declared implicitly inside Verilog. There are no output arguments for functions because the implicit register <function name> contains the output value. Therefore, inside the function, one must assign the function name a value.
Parity Calculation
The following code uses a function that calculates the parity of a 32-bit address and returns the value. Here we assume the system is using even parity.
module even_parity; ... reg [31:0] addr; reg parity; ... // Compute new parity whenever address value changes always @(addr) begin parity = calc_parity(addr); // First invocation of calc_parity $display("Parity Calculated = %b", calc_parity(addr)); // Second invocation of calc_parity end ... // define the parity calculation function function calc_parity; input [31:0] address; begin calc_parity = ^address; // return the xor of all address bits. end endfunction ... endmodule
Note that in the first invocation of calc_parity, the returned value was used to set reg parity. In the second invocation, the value returned was directly used inside the $display task.
Another method of declaring arguments for functions is the ANSI C style. The code of below shows the calc_parity function defined with an ANSI C style argument declaration.
// define the parity calculation function using ANSI C Style Arguments function calc_parity (input [31:0] address); begin calc_parity = ^address; // return the xor of all address bits. end endfunction
1-to-4 DEMUX
Figure 10-1 shows the structure of a 1-to-4 demultiplexer built from three 1-to-2 demultiplexers. The 1-to-2 demultiplexer is implemented by the demux_1to2 function, which is called 3 times in the main code. The first call produces the X[1] and X[0] signals, which are sent to the next level 1-to-2 demultiplexer. The S signal is used to determine which bit in the Y output signal will be connected to Din.
Figure 10-1: 1-to-4 DeMultiplexer
The following code shows how to build a 1-to-4 demux using two 1-to-2 demux functions.
// 1-to-4 demultiplexer using 1-to-2 demultiplexers with function call module demux_1to4(Din, S, Y); input Din; // data input input [1:0] S; // 2-bit select line output [3:0] Y; // 4-bit output reg [3:0] Y; reg [3:0] X; always @(*) begin X = demux_1to2(S[1], Din); // The first 1-to-2 demux Y[1:0] = demux_1to2(S[0], X[0]); // the secpond 1-to-2 demux Y[3:2] = demux_1to2(S[0], X[1]); // the third 1-to-2 demux end function [1:2] demux_1to2; input s; input d; begin if (s == 1'b0) begin demux_1to2 = {1'b0, d}; end else begin demux_1to2 = {d, 1'b0}; end end endfunction
Automatic (Recursive) Functions
Functions are normally used non-recursively. If a function is called concurrently from two locations, the results are non-deterministic because both calls operate on the same variable space.
However, the keyword automatic can be used to declare a recursive (automatic) function where all function declarations are allocated dynamically for each recursive call. Each call to an automatic function operates in an independent variable space. Automatic function items cannot be accessed by hierarchical references. Automatic functions can be called through the use of their hierarchical name.
The following code shows how an automatic function is defined to compute a factorial.
// Define a factorial with a recursive function module demo; ... ... function automatic integer factorial; input [31:0] x; integer i; begin if (x >= 2) begin factorial = factorial(x - 1) * x; // recursive call end else begin factorial = 1; end end endfunction // Call the function integer result; initial begin result = factorial(4); // Call the factorial of 4 $display("Factorial of 4 is %0d.", result); // Display 24 end ... endmodule
System Tasks and Functions
System tasks are the standard of the build-in tasks in Verilog. All system tasks are preceded with $. A few of the more commonly used ones are described below. The Verilog Language Reference Manual has many more.
Compiler Directives
`include
`define and `undef
`timescale
`resetall
`ifdef, `else and `endif