A designer is frequently required to implement the same functionality at many places in a behavioral design. This means that the commonly used parts should be abstracted into routines and the r outines must be invoked instead of repeating the code. Most programming languages provide procedures or subroutines to accomplish this. Verilog provides tasks and functions to break up large behavioral designs into smaller pieces. Tasks and functions allow the designer to abstract Verilog code that is used at many places in the design.
Tasks have input
, output
, and inout
arguments; functions have input
arguments. Thus, values can be passed into and out from tasks and functions. Considering the analogy of FORTRAN, tasks are similar to SUBROUTINE and functions are similar to FUNCTION.
Tasks and functions are included in the design hierarchy. Like named blocks, tasks or functions can be addressed by means of hierarchical names.
Learning Objectives
Describe the differences between tasks and functions.
Identify the conditions required for tasks to be defined. Understand task declaration and invocation.
Explain the conditions necessary for functions to be defined. Understand function declaration and invocation.
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 differences between tasks and functions, as outlined in Table 8-1.
Table 8-1. Tasks and Functions
Functions | Tasks |
---|---|
A function can enable another function but not another task. | A task can enable other tasks and functions. |
Functions always execute in 0 simulation time. | Tasks may execute in non-zero simulation time. |
Functions must not contain any delay, event, or timing control statements. | Tasks may contain delay, 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. They cannot have output or inout arguments. | Tasks do not return with a value, but can pass multiple values through output and inout arguments. |
Both tasks and functions must be defined in a module and are local to the module. Tasks are used for common Verilog code that contains delays, timing, event constructs, or multiple output arguments. Functions are used when common Verilog code is purely combinational, executes in zero simulation time, and provides exactly one output. Functions are typically used for conversions and commonly used calculations.
Tasks can have input
, output
, and inout
arguments; functions can have input
arguments. In addition, they can have local variables, registers, time variables, integers, real, or events. Tasks or functions cannot have wires. Tasks and functions contain behavioral statements only. Tasks and functions do not contain always
or initial
statements but are called from always
blocks, initial
blocks, or other tasks and functions.
Tasks are declared with the keywords 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 arguments.
The procedure has no input arguments.
Task declaration and task invocation syntax are as follows.
Example 8-1. Syntax for Tasks
task_declaration ::= task [ automatic ] task_identifier ; { task_item_declaration } statement endtask | task [ automatic ] task_identifier ( task_port_list ) ; { block_item_declaration } statement endtask task_item_declaration ::= block_item_declaration | { attribute_instance } tf_input_declaration ; | { attribute_instance } tf_output_declaration ; | { attribute_instance } tf_inout_declaration ; task_port_list ::= task_port_item { , task_port_item } task_port_item ::= { attribute_instance } tf_input_declaration | { attribute_instance } tf_output_declaration | { attribute_instance } tf_inout_declaration tf_input_declaration ::= input [ reg ] [ signed ] [ range ] list_of_port_identifiers | input [ task_port_type ] list_of_port_identifiers tf_output_declaration ::= output [ reg ] [ signed ] [ range ] list_of_port_identifiers | output [ task_port_type ] list_of_port_identifiers tf_inout_declaration ::= inout [ reg ] [ signed ] [ range ] list_of_port_identifiers | inout [ task_port_type ] list_of_port_identifiers task_port_type ::= time | real | realtime | integer
I/O declarations use keywords input
, output,
or inout
, based on the type of argument declared. Input and inout arguments are passed into the task. Input arguments are processed in the task statements. Output and inout argument values are passed back to the variables in the task invocation statement when the task is completed. Tasks can invoke other tasks or functions.
Although the keywords input
, inout
, and output
used for I/O arguments in a task are the same as the keywords used to declare ports in modules, there is a difference. Ports are used to connect external signals to the module. I/O arguments in a task are used to pass values to and from the task.
We discuss two examples of tasks. The first example illustrates the use of input and output arguments in tasks. The second example models an asymmetric sequence generator that generates an asymmetric sequence on the clock signal.
Example 8-2 illustrates the use of input
and output
arguments in tasks. Consider a task called bitwise_oper, which computes the bitwise and, bitwise or, and bitwise ex-or 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, ab_xor. A parameter delay is also used in the task.
Example 8-2. Input and Output Arguments in Tasks
//Define a module called operation that contains the task bitwise_oper module operation; ... ... parameter delay = 10; reg [15:0] A, B; reg [15:0] AB_AND, AB_OR, AB_XOR; always @(A or B) //whenever A or B changes in value begin //invoke the task bitwise_oper. provide 2 input arguments A, B //Expect 3 output arguments AB_AND, AB_OR, AB_XOR //The arguments must be specified in the same order as they //appear in the task declaration. bitwise_oper(AB_AND, AB_OR, AB_XOR, A, B); end ... ... //define task bitwise_oper task bitwise_oper; output [15:0] ab_and, ab_or, ab_xor; //outputs from the task input [15:0] a, b; //inputs to the task begin #delay ab_and = a & b; ab_or = a | b; ab_xor = a ^ b; end endtask ... endmodule
In the above task, the input values passed to the task are A and B. Hence, when the task is entered, a = A and b = B. The three output values are computed after a delay. This delay is specified by the parameter delay, which is 10 units for this example. When the task is completed, the output values are passed back to the calling output arguments. Therefore, AB_AND = ab_and, AB_OR = ab_or, and AB_XOR = ab_xor when the task is completed.
Another method of declaring arguments for tasks is the ANSI C style. Example 8-3 shows the bitwise_oper task defined with an ANSI C style argument declaration.
Tasks can directly operate on reg
variables defined in the module. Example 8-4 directly operates on the reg
variable clock to continuously produce an asymmetric sequence. The clock is initialized with an initialization sequence.
Example 8-4. Direct Operation on reg Variables
//Define a module that contains the task asymmetric_sequence module sequence; ... reg clock; ... initial init_sequence; //Invoke the task init_sequence ... always begin asymmetric_sequence; //Invoke the task asymmetric_sequence end ... ... //Initialization sequence task init_sequence; begin clock = 1'b0; end endtask //define task to generate asymmetric sequence //operate directly on the clock defined in the module. task asymmetric_sequence; begin #12 clock = 1'b0; #5 clock = 1'b1; #3 clock = 1'b0; #10 clock = 1'b1; end endtask ... ... endmodule
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 tasks re-entrant. Such tasks are called automatic tasks. 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. This results in correct operation. 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.
Example 8-5 shows how an automatic task is defined and used.
Example 8-5. Re-entrant (Automatic) Tasks
// Module that contains an automatic (re-entrant) task // Only a small portion of the module that contains the task definition // is shown in this example. There are two clocks. // clk2 runs at twice the frequency of clk and is synchronous // with clk. module top; reg [15:0] cd_xor, ef_xor; //variables in module top reg [15:0] c, d, e, f; //variables in module top - task automatic bitwise_xor; output [15:0] ab_xor; //output from the task input [15:0] a, b; //inputs to the task begin #delay ab_and = a & b; ab_or = a | b; ab_xor = a ^ b; end endtask ... - // These two always blocks will call the bitwise_xor task // concurrently at each positive edge of clk. However, since // the task is re-entrant, these concurrent calls will work correctly. always @(posedge clk) bitwise_xor(ef_xor, e, f); - always @(posedge clk2) // twice the frequency as the previous block bitwise_xor(cd_xor, c, d); - - endmodule
Functions are declared with the keywords 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 is follows:
Example 8-6. Syntax for Functions
function_declaration ::= function [ automatic ] [ signed ] [ range_or_type ] function_identifier ; function_item_declaration { function_item_declaration } function_statement endfunction | function [ automatic ] [ signed ] [ range_or_type ] function_identifier (function_port_list ) ; block_item_declaration { block_item_declaration } function_statement endfunction function_item_declaration ::= block_item_declaration | tf_input_declaration ; function_port_list ::= { attribute_instance } tf_input_declaration {, { attribute_instance } tf_input_declaration } range_or_type ::= range | integer | real | realtime | time
There are some peculiarities of functions. When a function is declared, a register with name function_identifer is declared implicitly inside Verilog. The output of a function is passed back by setting the value of the register function_identifer appropriately. The function is invoked by specifying function name and input arguments. At the end of function execution, the return value is placed where the function was invoked. The optional range_or_type specifies the width of the internal register. If no range or type is specified, the default bit width is 1. Functions are very similar to FUNCTION in FORTRAN.
Notice that at least one input argument must be defined for a function. There are no output arguments for functions because the implicit register function_identifer contains the output value. Also, functions cannot invoke other tasks. They can invoke only other functions.
We will discuss two examples. The first example models a parity calculator that returns a 1-bit value. The second example models a 32-bit left/right shift register that returns a 32-bit shifted value.
Let us discuss a function that calculates the parity of a 32-bit address and returns the value. We assume even parity. Example 8-7 shows the definition and invocation of the function calc_parity.
Example 8-7. Parity Calculation
//Define a module that contains the function calc_parity module 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 //set the output value appropriately. Use the implicit //internal register calc_parity. 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 the reg
parity. In the second invocation, the value returned was directly used inside the $display
task. Thus, the returned value is placed wherever the function was invoked.
Another method of declaring arguments for functions is the ANSI C style. Example 8-8 shows the calc_parity function defined with an ANSI C style argument declaration.
Example 8-8. Function Definition using ANSI C Style Argument Declaration
//define the parity calculation function using ANSI C Style arguments function calc_parity (input [31:0] address); begin //set the output value appropriately. Use the implicit //internal register calc_parity. calc_parity = ^address; //Return the xor of all address bits. end endfunction
To illustrate how a range for the output value of a function can be specified, let us consider a function that shifts a 32-bit value to the left or right by one bit, based on a control signal. Example 8-9 shows the implementation of the left/right shifter.
Example 8-9. Left/Right Shifter
//Define a module that contains the function shift module shifter; ... //Left/right shifter `define LEFT_SHIFT 1'b0 `define RIGHT_SHIFT 1'b1 reg [31:0] addr, left_addr, right_addr; reg control; //Compute the right- and left-shifted values whenever //a new address value appears always @(addr) begin //call the function defined below to do left and right shift. left_addr = shift(addr, `LEFT_SHIFT); right_addr = shift(addr, `RIGHT_SHIFT); end ... ... //define shift function. The output is a 32-bit value. function [31:0] shift; input [31:0] address; input control; begin //set the output value appropriately based on a control signal. shift = (control == `LEFT_SHIFT) ?(address << 1) : (address >> 1); end endfunction ... ... endmodule
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 calls. 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 invoked through the use of their hierarchical name.
Example 8-10 shows how an automatic function is defined to compute a factorial.
Example 8-10. Recursive (Automatic) Functions
//Define a factorial with a recursive function module top; ... // Define the function function automatic integer factorial; input [31:0] oper; integer i; begin if (operand >= 2) factorial = factorial (oper -1) * oper; //recursive call else factorial = 1 ; end endfunction // Call the function integer result; initial begin result = factorial(4); // Call the factorial of 7 $display("Factorial of 4 is %0d", result); //Displays 24 end ... ... endmodule
A constant function[1] is a regular Verilog HDL function, but with certain restrictions. These functions can be used to reference complex values and can be used instead of constants.
Example 8-11 shows how a constant function can be used to compute the width of the address bus in a module.
Example 8-11. Constant Functions
//Define a RAM model module ram (...); parameter RAM_DEPTH = 256; input [clogb2(RAM_DEPTH)-1:0] addr_bus; //width of bus computed //by calling constant //function defined below //Result of clogb2 = 8 //input [7:0] addr_bus; -- -- //Constant function function integer clogb2(input integer depth); begin for(clogb2=0; depth >0; clogb2=clogb2+1) depth = depth >> 1; end endfunction -- -- endmodule
Signed functions allow signed operations to be performed on the function return values. Example 8-12 shows an example of a signed function.
In this chapter, we discussed tasks and functions used in behavior Verilog modeling.
Tasks and functions are used to define common Verilog functionality that is used at many places in the design. Tasks and functions help to make a module definition more readable by breaking it up into manageable subunits. Tasks and functions serve the same purpose in Verilog as subroutines do in C.
Tasks can take any number of input
, inout
, or output
arguments. Delay, event, or timing control constructs are permitted in tasks. Tasks can enable other tasks or functions.
Re-entrant tasks defined with the keyword automatic
allow each task call to operate in an independent space. Therefore, re-entrant tasks work correctly even with concurrent tasks calls.
Functions are used when exactly one return value is required and at least one input argument is specified. Delay, event, or timing control constructs are not permitted in functions. Functions can invoke other functions but cannot invoke other tasks.
A register with name as the function name is declared implicitly when a function is declared. The return value of the function is passed back in this register.
Recursive functions defined with the keyword automatic
allow each function call to operate in an independent space. Therefore, recursive or concurrent calls to such functions will work correctly.
Tasks and functions are included in a design hierarchy and can be addressed by hierarchical name referencing.
1: | Define a | ||||||||||||||||||
2: | Define a | ||||||||||||||||||
3: | Define a
| ||||||||||||||||||
4: | Define a | ||||||||||||||||||
5: | Define a | ||||||||||||||||||
6: | Using named events, tasks, and functions, design the traffic signal controller in Traffic Signal Controller on page 160. |
[1] See IEEE Standard Verilog Hardware Description Language document for details on constant function restrictions.
3.145.131.238