Verilog provides built-in math system functions that can be used in place of hand-calculated constants. Instead of working out how many address bits a memory needs and typing the number, you can let the compiler calculate it from a parameter. There are functions for integer values, mainly $clog2, and a set of functions for real numbers, such as $sqrt, $pow and $sin. They were added to Verilog in the IEEE 1364-2005 revision.

9 min read | Beginner Level

What You'll Learn

  • How $clog2 calculates the number of bits needed for a value or an address
  • How to size ports and signals automatically from parameters
  • Which real math functions exist and how to use them in testbenches and models
  • Which math functions synthesis accepts, and the common off-by-one mistakes

Integer Math Functions

The function $clog2 returns the ceiling of the base-2 logarithm of its argument: the smallest number n such that 2n is greater than or equal to the argument. This is exactly the number of bits needed to give a unique number to each of that many items, which is why it is used to calculate address widths from the size of a memory or the number of units.

ExpressionResultWhy
$clog2(1)0A single item needs no address bits
$clog2(5)322 = 4 is too small, 23 = 8 is enough
$clog2(7)37 items are numbered 0 to 6, which fit in 3 bits
$clog2(8)38 items are numbered 0 to 7, which fit in 3 bits
$clog2(9)4Item number 8 needs a fourth bit
$clog2(1024)10A 1024-entry memory needs a 10-bit address

For example, if a design contains 7 parallel adders and a signal selects which one is active, $clog2(7) gives 3, the minimum number of bits needed to number all 7 units.


module des
  #(parameter NUM_UNITS = 7)

  // Use of this system function helps to reduce the
  // number of input wires to this module
  (input [$clog2(NUM_UNITS)-1:0] active_unit);

  initial
    $monitor("active_unit = %b", active_unit);
endmodule

`define NUM_UNITS 5

module tb;
  integer i;
  reg [`NUM_UNITS-1:0]  active_unit;

  des #(.NUM_UNITS(`NUM_UNITS)) u0(active_unit);

  initial begin
    active_unit     = 1;
    #10 active_unit = 7;
    #10 active_unit = 8;
  end
endmodule
Output
xcelium> run
active_unit = 001
active_unit = 111
active_unit = 000
xmsim: *W,RNQUIE: Simulation is complete.

The testbench overrides NUM_UNITS with 5, so the port active_unit of des is $clog2(5) = 3 bits wide, enough to select one of 5 units. The testbench, however, drives it from a 5-bit reg, so only the lowest 3 bits arrive at the port. Values 1 and 7 fit and are printed as 001 and 111. The value 8 is 01000 in binary, its lowest 3 bits are 000, and the module sees 0. Most simulators print a port size mismatch warning for this connection, which is a useful hint that the widths on the two sides were calculated differently.

Synthesis result: $clog2 with a constant argument, such as a parameter, is evaluated by the compiler before synthesis, so it is fully synthesizable and produces no hardware itself. It only decides how wide the port, register or counter is. Here, des gets a 3-bit input bus.

Quick Check

A FIFO has DEPTH = 16 entries. How wide must the read and write pointers be, and how wide must a counter be that holds the number of entries, from 0 to 16?

Show Answer

The pointers need $clog2(16) = 4 bits, to address entries 0 to 15. The counter must also hold the value 16 itself, so it needs $clog2(16 + 1) = 5 bits.

Parameterized IP Blocks

Reusable blocks such as FIFOs, memories, arbiters and multiplexers are written once with parameters for their depth or number of inputs. All internal widths are derived with $clog2, for example localparam AW = $clog2(DEPTH);. The same RTL is then used with a depth of 4 in one place and 4096 in another, without anyone editing a width by hand.

Real Math Functions

The real math functions take real arguments and return a real result. They behave like the equivalent functions of the C math library.

FunctionDescription
$ln(x)Natural logarithm of x
$log10(x)Base-10 logarithm of x
$exp(x)e raised to the power x
$sqrt(x)Square root of x
$pow(x, y)x raised to the power y
$floor(x)Largest integer not greater than x
$ceil(x)Smallest integer not less than x
$sin(x)Sine of x, with x in radians
$cos(x)Cosine of x, with x in radians
$tan(x)Tangent of x, with x in radians
$asin(x)Inverse sine of x, in radians
$acos(x)Inverse cosine of x, in radians
$atan(x)Inverse tangent of x, in radians
$atan2(y, x)Inverse tangent of y/x, using the signs of both to find the correct quadrant
$hypot(x, y)Hypotenuse, the square root of x2 + y2
$sinh(x)Hyperbolic sine of x
$cosh(x)Hyperbolic cosine of x
$tanh(x)Hyperbolic tangent of x
$asinh(x)Inverse hyperbolic sine of x
$acosh(x)Inverse hyperbolic cosine of x
$atanh(x)Inverse hyperbolic tangent of x

The testbench below calls most of these functions and prints the results. The trigonometric functions work in radians, so the example converts degrees to radians with an approximation of π as 22/7.


module tb;
  real x, y;

  initial begin
    x = 10000;
    $display("$log10(%0.3f) = %0.3f", x, $log10(x));

    x = 1;
    $display("$ln(%0.3f) = %0.3f", x, $ln(x));

    x = 2;
    $display("$exp(%0.3f) = %0.3f", x, $exp(x));

    x = 25;
    $display("$sqrt(%0.3f) = %0.3f", x, $sqrt(x));

    x = 5;
    y = 3;
    $display("$pow(%0.3f, %0.3f) = %0.3f", x, y, $pow(x, y));

    x = 2.7813;
    $display("$floor(%0.3f) = %0.3f", x, $floor(x));

    x = 7.1111;
    $display("$ceil(%0.3f) = %0.3f", x, $ceil(x));

    x = 30 * (22.0/7.0) / 180;   // convert 30 degrees to radians
    $display("$sin(%0.3f) = %0.3f", x, $sin(x));

    x = 90 * (22.0/7.0) / 180;
    $display("$cos(%0.3f) = %0.3f", x, $cos(x));

    x = 45 * (22.0/7.0) / 180;
    $display("$tan(%0.3f) = %0.3f", x, $tan(x));

    x = 0.5;
    $display("$asin(%0.3f) = %0.3f rad, %0.3f deg", x, $asin(x), $asin(x) * 7.0/22.0 * 180);

    x = 0;
    $display("$acos(%0.3f) = %0.3f rad, %0.3f deg", x, $acos(x), $acos(x) * 7.0/22.0 * 180);

    x = 1;
    $display("$atan(%0.3f) = %0.3f rad, %f deg", x, $atan(x), $atan(x) * 7.0/22.0 * 180);
  end
endmodule
Output
xcelium> run
$log10(10000.000) = 4.000
$ln(1.000) = 0.000
$exp(2.000) = 7.389
$sqrt(25.000) = 5.000
$pow(5.000, 3.000) = 125.000
$floor(2.781) = 2.000
$ceil(7.111) = 8.000
$sin(0.524) = 0.500
$cos(1.571) = -0.001
$tan(0.786) = 1.001
$asin(0.500) = 0.524 rad, 29.988 deg
$acos(0.000) = 1.571 rad, 89.964 deg
$atan(1.000) = 0.785 rad, 44.981895 deg
xmsim: *W,RNQUIE: Simulation is complete.

Most results are exact, but the trigonometric lines show small errors: $cos of 90 degrees gives -0.001 instead of 0, and the angles come back as 29.988, 89.964 and 44.98 degrees instead of 30, 90 and 45. The functions themselves are accurate; the error comes from 22/7, which is 3.142857 instead of π = 3.141593. Calculating π as $acos(-1.0), or writing it with more digits, removes these errors. The last line also uses %f instead of %0.3f, which is why it prints six decimal places.

Synthesis result: Real variables and real math functions are not synthesizable as hardware. Some synthesis tools evaluate them when they only calculate a constant at elaboration time, for example a parameter derived from $ln or $sqrt, but that support varies between tools. Real math belongs in testbenches and behavioral models.

Quick Check

Why does $floor(-2.5) give -3.0 and not -2.0?

Show Answer

$floor returns the largest integer that is not greater than its argument. For -2.5, that is -3, because -2 is greater than -2.5. Rounding toward zero would give -2, but that is not what $floor does.

Reference Models for Signal Processing

A digital filter or a sine-wave generator in RTL works with fixed-point numbers, but its testbench can compute the ideal result with real math, such as $sin(2.0 * PI * f * t). The checker compares the RTL output with this ideal value and accepts a small difference, so the test measures both correctness and the precision of the fixed-point design.

Common Beginner Mistakes

Mistake #1: Using $clog2(N) for a counter that must reach N

Wrong: reg [$clog2(8)-1:0] count; for a counter that counts from 0 to 8
Right: reg [$clog2(8+1)-1:0] count;

Why? $clog2(8) is 3, which is enough to number 8 items, 0 to 7, but not to store the value 8 itself. The counter then wraps around to 0 instead of reaching 8.

Mistake #2: A zero-width vector when N is 1

Wrong: input [$clog2(N)-1:0] sel; used with N = 1
Right: Guard the corner case, for example localparam SW = (N > 1) ? $clog2(N) : 1;

Why? $clog2(1) is 0, so the range becomes [-1:0], a 2-bit vector with an odd index, instead of the intended "no select needed". A minimum width of 1 keeps the code legal for every value of the parameter.

Mistake #3: Passing degrees to trigonometric functions

Wrong: $sin(30.0), expecting 0.5
Right: $sin(30.0 * PI / 180.0)

Why? All trigonometric functions take and return radians. $sin(30.0) is the sine of 30 radians, about -0.988.

Mistake #4: Mismatched widths between a parameterized port and its connection

Wrong: Driving a $clog2(N)-bit port from a signal sized with a different formula, as the 5-bit active_unit in the example
Right: Size both sides with the same expression, for example reg [$clog2(`NUM_UNITS)-1:0] active_unit;

Why? The upper bits are silently dropped at the port, as the value 8 shows in the log. Calculating both widths the same way keeps them in step when the parameter changes.

Practice Exercise

Challenge: Parameterized Memory

Write a single-port memory module with parameters DEPTH and WIDTH, whose address port width is calculated with $clog2. Then write a testbench line that prints the address width for DEPTH = 100, and calculate it by hand.

Hint

Define a localparam for the address width inside the module, and use it for the port. In the testbench, $clog2 can be called directly in a $display.

Solution

module mem #(parameter DEPTH = 100, parameter WIDTH = 8)
  (input                      clk, we,
   input  [$clog2(DEPTH)-1:0] addr,
   input  [WIDTH-1:0]         wdata,
   output reg [WIDTH-1:0]     rdata);

  reg [WIDTH-1:0] ram [0:DEPTH-1];

  always @(posedge clk) begin
    if (we) ram[addr] <= wdata;
    rdata <= ram[addr];
  end
endmodule

// In the testbench:
initial $display("Address width for 100 entries = %0d", $clog2(100));

The testbench prints 7: 26 = 64 is too small for 100 entries, and 27 = 128 is enough. Addresses 100 to 127 exist on the port but have no storage, so a real design should not use them. For a depth that is a power of 2, such as 128, every address is used.

Summary

In this tutorial, you learned:

  • $clog2(N) returns the number of bits needed to number N items, and is used to size addresses, selects and pointers from parameters
  • $clog2 on constants is evaluated at compile time and is fully synthesizable
  • A counter that must reach N needs $clog2(N+1) bits, and $clog2(1) = 0 needs a guard
  • Real math functions such as $ln, $sqrt, $pow, $floor and the trigonometric functions take and return real values
  • Trigonometric functions use radians, and an inaccurate value of π causes visible errors
  • Real math is for testbenches and models, not for synthesizable hardware
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5