Why Learn Functions? Checksum logic, Gray-code conversion, parity checks and bit-field decoding often appear in several places in one design. Copying the same expression into each place makes the code longer and every bug fix a search-and-replace job. A function describes the computation once and lets you call it wherever you need it. In this tutorial, you'll learn to write Verilog functions, what hardware they create, and the rules that keep them synthesizable.

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 as integer. 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 more input arguments (at least one required)
  • statements - The body; use begin ... end to 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 no wait statements
  • It can call other functions, but it cannot call a task
  • It must have at least one input, and it cannot have output or inout arguments
  • It cannot use non-blocking assignments, force/release or assign/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
Output
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 assign statements, 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
  • automatic gives each call its own variables and is required for recursion
  • Constant functions compute sizes from parameters during elaboration and build no hardware
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5