Lesson 08: 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, it is important to understand the differences between tasks and functions, as outlined in Table 10-1.
Table 8-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 prohibited 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; 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.
- Tasks 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.
8.1 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 corresponding to the definition's <argument ports>. 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.
The 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. All declared items are statically allocated, shared across all task uses, and executed concurrently. Therefore, if a task is called concurrently from two places in the code, these task calls will operate on the same task variables. the results of such an operation will likely be incorrect.
To avoid this problem, a keyword automatic is added after 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
8.2 Functions
Functions are declared with the keyword function and endfunction. Functions are used if all of the following conditions are true for the procedure:
- The procedure has no delay, timing, or event control constructs.
- 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 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, one must assign the function name a value inside the function.
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 uses 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. The value returned was directly used inside the $display task in the second invocation.
Another method of declaring arguments for functions is the ANSI C style. The code 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 sent to the next level 1-to-2 demultiplexer. The S signal determines which bit in the Y output signal will be connected to Din.
Figure 8-1: 1-to-4 DeMultiplexer
The following code shows building 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 by their hierarchical name.
The following code defines an automatic function 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
8.3 System Tasks and Functions
System tasks and functions in Verilog HDL are built-in procedures that provide a wide range of capabilities for simulation control, displaying information, file operations, and more. These are predefined by Verilog and typically start with a $ symbol. Here, we will explore some of the most commonly used system tasks and functions, their syntax, and examples of how to use them.
- $display
- $monitor
- $write
- $strobe
- $stop and $finish
- $time, $realtime, and $stime
- $random
- $readmemb and $readmemh
- $signed and $unsigned
- $clog2
- $bits
- $time, $realtime, $stime
- $random
- $fopen, $fclose
- $fdisplay, $fwrite, $fscanf
System Tasks
$display
The $display task prints messages to the console during simulation. It is similar to print() in C.
Syntax:
$display("format_string", argument1, argument2, ...);
Example:
module test_display;
initial begin
$display("Hello, World!");
$display("The value of a is: %d", 5);
end
endmodule
$monitor
The $monitor task continuously monitors the values of specified variables and prints them whenever they change.
Syntax:
$monitor("format_string", argument1, argument2, ...);
Example:
module test_monitor; reg clk; reg [3:0] a; initial begin $monitor("At time %0t: clk = %b, a = %d", $time, clk, a); clk = 0; a = 0; #5 a = 1; #5 clk = 1; #5 a = 2; #5 clk = 0; #5 $finish; end endmodule
$write
The $write task is similar to $display but does not automatically print a newline at the end.
Syntax:
$write(format_string, argument1, argument2, ...);
Example:
module test_write; initial begin $write("The values are: "); $write("a = %d, ", 5); $write("b = %d\n", 10); // Add explicit newline end endmodule
$strobe
The $strobe task is similar to $display, but it schedules the output to occur at the end of the current simulation time step.
Syntax:
$strobe(format_string, argument1, argument2, ...);
Example:
module test_strobe;
initial begin
$strobe("End of time step: a = %d", 5);
end
endmodule
$stop and $finish
The $stop task suspends the simulation, allowing you to inspect the state of the simulation. The $finish task ends the simulation.
Syntax:
$stop;
$finish;
Example:
module test_stop_finish; initial begin $display("Simulation starts"); #10 $stop; // Suspend simulation #10 $finish; // End simulation end endmodule
$time, $realtime, and $stime
These tasks return the current simulation time.
- $time: Returns the current time as an integer.
- $realtime: Returns the current time as a real number.
- $stime: Returns the current time as a short integer.
Example:
module test_time; initial begin $display("Current time: %0d", $time); $display("Current time (real): %0t", $realtime); end endmodule
$random
The $random function generates a random integer.
Syntax
random_value = $random;
Example
module test_random; initial begin $display("Random number: %0d", $random); end endmodule
$readmemb and $readmemh
These tasks read memory initialization data from a binary or hexadecimal format file, respectively.
Syntax:
$readmemb("filename", memory_array);
$readmemh("filename", memory_array);
Example:
module test_readmem; reg [7:0] memory [0:15]; initial begin $readmemb("memory_data.mem", memory); $display("Memory[0] = %b", memory[0]); end endmodule
Summary of System Tasks:
- $display: Prints messages to the console.
- $monitor: Continuously monitors and prints variable values when they change.
- $write: Prints messages without adding a newline.
- $strobe: Prints messages at the end of the current simulation time step.
- $stop: Suspends the simulation.
- $finish: Ends the simulation.
- $time, $realtime, $stime: Return the current simulation time.
- $random: Generates random numbers.
- $readmemb, $readmemh: Read memory initialization data from files.
System Functions
$signed and $unsigned
These functions are used to convert a value to signed or unsigned.
Syntax:
$signed(expression)
$unsigned(expression)
Example:
module test_signed_unsigned; reg [3:0] a = 4'b1010; // -6 in signed 2's complement initial begin $display("Signed value of a: %0d", $signed(a)); $display("Unsigned value of a: %0d", $unsigned(a)); end endmodule
$clog2
This function calculates the ceiling of the logarithm base 2 of the given value. It is useful for determining the number of bits required to represent a value.
Syntax:
$clog2(expression)
Example:
module test_clog2; initial begin $display("Number of bits required for 10: %0d", $clog2(10)); $display("Number of bits required for 256: %0d", $clog2(256)); end endmodule
$bits
This function returns the number of bits needed to represent a given variable.
Syntax:
$bits(expression)
Example:
module test_bits; reg [7:0] byte_var; reg [31:0] word_var; initial begin $display("Number of bits in byte_var: %0d", $bits(byte_var)); $display("Number of bits in word_var: %0d", $bits(word_var)); end endmodule
$fopen and $fclose
These functions are used for file operations. $fopen opens a file and returns a file descriptor, while $fclose closes the file.
Syntax:
integer fd;
fd = $fopen("filename", "mode");
$fclose(fd);
Example:
module test_file_operations; integer fd; initial begin fd = $fopen("output.txt", "w"); if (fd) begin $fdisplay(fd, "Writing to file at time %0t", $time); $fclose(fd); end else begin $display("Error: Could not open file"); end end endmodule
$fdisplay, $fwrite, and $fscanf
These functions are used for formatted file I/O operations.
- $fdisplay: Writes formatted output to a file with a newline.
- $fwrite: Writes formatted output to a file without a newline.
- $fscanf: Reads formatted input from a file.
Example:
module test_formatted_file_io; integer fd; reg [7:0] data; initial begin fd = $fopen("data.txt", "w"); if (fd) begin $fdisplay(fd, "Data value: %0d", 123); $fwrite(fd, "Raw data: %b", 8'b10101010); $fclose(fd); end else begin $display("Error: Could not open file"); end fd = $fopen("data.txt", "r"); if (fd) begin $fscanf(fd, "Data value: %d\n", data); $display("Read data value: %0d", data); $fclose(fd); end else begin $display("Error: Could not open file"); end end endmodule
Summary of System Functions:
- $signed, $unsigned: Convert values to signed or unsigned.
- $clog2: Calculate the ceiling of the logarithm base 2.
- $bits: Return the number of bits in a variable.
- $fopen, $fclose: Open and close files.
- $fdisplay, $fwrite, $fscanf: Formatted file I/O operations.
8.4 Compiler Directives
`include
`define and `undef
`timescale
`resetall
`ifdef, `else and `endif