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
$clog2calculates 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.
| Expression | Result | Why |
|---|---|---|
$clog2(1) | 0 | A single item needs no address bits |
$clog2(5) | 3 | 22 = 4 is too small, 23 = 8 is enough |
$clog2(7) | 3 | 7 items are numbered 0 to 6, which fit in 3 bits |
$clog2(8) | 3 | 8 items are numbered 0 to 7, which fit in 3 bits |
$clog2(9) | 4 | Item number 8 needs a fourth bit |
$clog2(1024) | 10 | A 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
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.
| Function | Description |
|---|---|
| $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
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$clog2on 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,$floorand the trigonometric functions take and returnrealvalues - Trigonometric functions use radians, and an inaccurate value of π causes visible errors
- Real math is for testbenches and models, not for synthesizable hardware