A Verilog function is a reusable block of code that takes one or more inputs, computes a result and returns a single value, much like a function in C. Functions run in zero simulation time: they cannot contain delays or wait for events, so they are used for combinational computations that would otherwise be repeated in several places.
12 min read | Beginner Level
What You'll Learn
- How to declare, return a value from and call a Verilog function
- What hardware a function synthesizes into
- The rules functions must follow, and when to use
automatic - How to use recursive and constant functions, and avoid common function bugs
Syntax
function [automatic] [return_type] name ([port_list]);
[statements]
endfunction
automatic- Gives every call its own copy of the function's variables, which is required for recursion (optional)return_type- A range such as[7:0], or a type such asinteger. If left out, the function returns a single bit (optional)name- The function name, which also acts as the variable that holds the return value (required)port_list- One or moreinputarguments (at least one required)statements- The body; usebegin ... endto group several statements
Functions are declared inside a module, outside any always or initial block, and can be called from anywhere in that module.
Function declarations
There are two equivalent ways to declare a function's inputs. In the first, the inputs are declared inside the function body, one declaration per line. In the second, which is more common in newer code, they are declared in a port list after the name, like the ports of a module.
function [7:0] sum;
input [7:0] a, b;
begin
sum = a + b;
end
endfunction
function [7:0] sum (input [7:0] a, b);
begin
sum = a + b;
end
endfunction
Both versions describe the same function: it takes two 8-bit inputs and returns their 8-bit sum. Since the return type is [7:0], any carry out of bit 7 is dropped.
Returning a value from a function
The function declaration implicitly creates a variable with the same name and width as the function. Assigning a value to that variable sets the return value, and the value it holds when the function ends is what the caller receives.
sum = a + b;
Make sure every path through the function body assigns the return variable. If a path skips the assignment, the function returns whatever the variable held before, which is usually x in simulation and unpredictable logic in hardware.
Verilog uses the function name as the return variable. The return statement that some code uses was added in SystemVerilog and is not part of Verilog.
Calling a function
A function call is an expression, so it can appear anywhere a value is expected: on the right-hand side of an assignment, inside a condition, as an argument of another function, or in a continuous assign statement. The arguments are matched to the inputs in the order they were declared.
reg [7:0] result;
reg [7:0] a, b;
initial begin
a = 4;
b = 5;
#10 result = sum (a, b); // result becomes 9 at time 10
end
Quick Check
Q: A function is declared as function sum (input [7:0] a, b); with no return range. What does sum(8'd4, 8'd5) return?
Show Answer
1. Without a return range, the function returns a single bit. The sum is 9 (8'b0000_1001), and only its least significant bit is kept.
Functions in Synthesizable Design
Functions are fully synthesizable when they follow the rules below. This module converts two 4-bit binary values to Gray code using one function called twice:
module gray_pair (
input [3:0] bin_a,
input [3:0] bin_b,
output [3:0] gray_a,
output [3:0] gray_b
);
// Binary to Gray: each Gray bit is the XOR of two neighboring binary bits
function [3:0] bin2gray (input [3:0] b);
begin
bin2gray = b ^ (b >> 1);
end
endfunction
// The same function is called twice, for two independent inputs
assign gray_a = bin2gray(bin_a);
assign gray_b = bin2gray(bin_b);
endmodule
Synthesis result: A function is not a shared hardware block that calls are routed to. The synthesis tool inlines the function body at every call site, so each call becomes its own copy of the logic. Here, the two calls build two separate sets of three XOR gates (bit 3 of the Gray code is just a wire from bit 3 of the input). See Verilog Binary to Gray for the conversion itself.
PPA Impact: Because every call adds a copy of the logic, a function that contains a multiplier or a wide adder, called in five places, builds five multipliers or adders. If the calls never need to run at the same time, share one instance through a mux instead.
Where are Functions Used?
Error detection logic in network interfaces and storage controllers computes CRC checksums over every data word. The next-state equation of the CRC is written once as a function that takes the current CRC value and a data word, and the design calls it in the transmit path to generate the checksum and in the receive path to check it.
Function rules
Because a function must finish in zero time and return exactly one value, Verilog places these restrictions on it:
- No timing controls: no
#delays, no@event controls and nowaitstatements - It can call other functions, but it cannot call a task
- It must have at least one input, and it cannot have
outputorinoutarguments - It cannot use non-blocking assignments,
force/releaseorassign/deassign - It cannot trigger named events
When you need delays, timing, or more than one output, use a task instead. See Verilog Task.
Recursive Functions
By default, a Verilog function is static: all calls share one copy of its variables. If a function calls itself, the inner call overwrites the variables the outer call is still using. Declaring the function automatic gives every call its own copy, which makes recursion work.
module tb;
integer result;
initial begin
result = factorial(4);
$display("factorial(4) = %0d", result);
end
function automatic integer factorial (input integer i);
integer result; // Local: each call has its own copy because of "automatic"
begin
// This function is called within the body of this
// function with a different argument
if (i) begin
result = i * factorial(i-1);
$display("i=%0d result=%0d", i, result);
end else
result = 1; // Base case: 0! = 1
factorial = result; // Set the return value
end
endfunction
endmodule
xcelium> run i=1 result=1 i=2 result=2 i=3 result=6 i=4 result=24 factorial(4) = 24 xmsim: *W,RNQUIE: Simulation is complete
The call factorial(4) calls factorial(3), which calls factorial(2), and so on down to factorial(0), which returns 1 without printing. Each call prints only after the inner call returns, so the lines appear from i=1 upward as the calls unwind.
Recursion is for simulation and constant values only.
Hardware cannot grow or shrink at run time, so synthesis tools can only handle recursion when the depth is known at compile time, such as a call with a constant argument. A recursive call with a signal argument, like factorial(n) where n is an input port, cannot be synthesized.
Constant Functions
A function called with only constant arguments, such as parameters, is evaluated during elaboration, before simulation or synthesis. Such constant functions are commonly used to compute widths and sizes from parameters:
module fifo #(parameter DEPTH = 16) ( /* ports */ );
// Number of bits needed to address DEPTH entries
function integer clog2 (input integer value);
integer v;
begin
v = value - 1;
for (clog2 = 0; v > 0; clog2 = clog2 + 1)
v = v >> 1;
end
endfunction
localparam ADDR_W = clog2(DEPTH); // 4 for DEPTH = 16; computed at elaboration
reg [ADDR_W-1:0] wr_ptr, rd_ptr;
endmodule
The function builds no hardware. Its result only sets the width of wr_ptr and rd_ptr. Verilog-2005 added the built-in $clog2 for this exact calculation, but constant functions remain useful for any size that depends on parameters.
Parameterized IP
Configurable memories, FIFOs and bus fabrics derive their internal widths from a few top-level parameters, such as depth, data width and number of ports. Constant functions compute values like address widths and byte-enable counts, so the integrator changes only the top-level parameters and every internal width follows.
Quick Check
Q: What goes wrong if the factorial function above is declared without automatic?
Show Answer
All calls share one copy of i and result. Each recursive call overwrites the i of the calls that are still waiting, so when the calls unwind they read the wrong value. Depending on the simulator's evaluation order, the printed values, the final result, or both come out wrong.
Reference Models in Testbenches
Verification engineers write functions that compute the expected output of a design, such as the result of an ALU operation or a checksum. The testbench calls the function with the same inputs it drives into the design and compares the two results, which is how self-checking testbenches report mismatches automatically.
Common Beginner Mistakes
Mistake #1: Leaving out the return width
Wrong: function sum (input [7:0] a, b);
Right: function [7:0] sum (input [7:0] a, b);
Why? Without a range or type, a function returns 1 bit. The 8-bit sum is calculated inside the function, but only bit 0 reaches the caller. The code compiles cleanly, and many tools give no warning.
Mistake #2: Putting delays or event controls in a function
Wrong: function [7:0] read_data (input [7:0] addr); begin @(posedge clk); read_data = mem[addr]; end endfunction
Right: Use a task for anything that waits for time or events
Why? A function must complete in zero simulation time, so #, @ and wait are not allowed and the compiler reports an error. Tasks can contain timing controls; see Verilog Task.
Mistake #3: Recursion without automatic
Wrong: function integer factorial (input integer i); calling itself
Right: function automatic integer factorial (input integer i);
Why? A static function has one shared set of variables, so recursive calls overwrite each other's arguments and locals. The simulation completes without an error but with wrong values, which makes this bug hard to spot.
Mistake #4: Not assigning the return value on every path
Wrong: function [1:0] enc (input [3:0] r); if (r[3]) enc = 3; else if (r[2]) enc = 2; endfunction
Right: Assign a default first, enc = 0;, then the if statements
Why? When neither condition is true, enc keeps the value from the previous call in simulation. Synthesis may infer a latch or produce logic that does not match the simulation. Like a combinational always block, a function must assign its output on every path.
Practice Exercise
Challenge: Maximum of Four Values
Write a module max4 with four 8-bit unsigned inputs a, b, c and d, and an 8-bit output m that equals the largest of the four. Write a function max2 that returns the larger of two values, and use it in a single assign statement. How many comparators does the design build?
Hint
A function call can be an argument of another call. Compare a with b and c with d, then compare the two winners.
Solution
module max4 (
input [7:0] a, b, c, d,
output [7:0] m
);
// Return the larger of two unsigned 8-bit values
function [7:0] max2 (input [7:0] x, y);
begin
max2 = (x > y) ? x : y;
end
endfunction
// Two first-round comparisons, then one final comparison
assign m = max2(max2(a, b), max2(c, d));
endmodule
The function is called three times, so synthesis builds three copies of its body: three 8-bit comparators, each followed by an 8-bit 2 to 1 mux. The two first-round comparisons work in parallel, and the final one compares their results, so the longest path goes through two comparator and mux stages.
Summary
In this tutorial, you learned:
- A function takes one or more inputs, runs in zero time and returns one value through a variable named after the function
- Without a return range, a function returns a single bit
- A function call is an expression and can be used in
assignstatements, procedural code and other function calls - Synthesis inlines the function at every call, so each call builds its own copy of the logic
- Functions cannot contain timing controls, non-blocking assignments, outputs or task calls
automaticgives each call its own variables and is required for recursion- Constant functions compute sizes from parameters during elaboration and build no hardware