verilog-operadores-aritmeticos-relacionales-logicos

Operators in Verilog: arithmetic, relational and logical

  • 6 min

Los Verilog operators are symbols that describe arithmetic operations, comparisons, and bit transformations that the synthesizer must convert into hardware.

We have seen how to use logic gates (AND, OR, XOR). But we don’t want to be thinking about individual bits all the time. We want to say: “If sensor A is greater than 100, turn on the alarm.”

Fortunately, Verilog allows us to work at a higher level of abstraction. It understands numbers and knows how to operate with them.

Today we are going to look at the math inside the FPGA. And watch out, because although the symbols are similar to those in C++, the hardware cost is very different.

Arithmetic operators

Verilog supports basic mathematical operations. When the synthesizer sees these symbols, it attempts to build the equivalent digital circuit (adders, subtractors, multipliers…).

OperatorDescriptionExample (A=7, B=2)
+AdditionA + B -> 9
-SubtractionA - B -> 5
*MultiplicationA * B -> 14
/DivisionA / B -> 3 (Integer)
%Modulus (Remainder)A % B -> 1
**Power2 ** 3 -> 8

The cost of arithmetic in hardware

A division is also more expensive than an addition in a processor, but in an FPGA the difference in area and delay can be much larger.

  1. Addition (+) and Subtraction (-): They are “cheap”. They are implemented with carry logic, which FPGAs have optimized.
  2. Multiplication (*): It is “expensive”. It consumes dedicated DSP blocks. If your FPGA has few DSPs, you will run out quickly.
  3. Division (/) and Modulus (%): It is best to avoid them with variable operands unless the cost is justified.
    • A combinational divider circuit is huge and extremely slow.
    • If the divisor is a power of 2 (e.g., / 4), the synthesizer is smart and converts it into a bit shift (free).
    • If the divisor is variable (e.g., A / B), avoid using the / operator. You will need to design a sequential divider module that takes several clock cycles.

Whenever possible, multiply and divide by powers of 2. It is as simple as shifting wires to the left or right.

Relational operators (comparators)

They are used to compare two values. The result of these operations is always a single bit: 1 (True) or 0 (False).

They are identical to C/C++:

OperatorDescription
>Greater than
<Less than
>=Greater than or equal to
<=Less than or equal to
==Logical equality
!=Logical inequality
wire es_mayor;
// If a (10) is greater than b (5), es_mayor will be 1
assign es_mayor = (a > b); 
Copied!

The == operator compares logical equality but returns X when it cannot decide due to the presence of X or Z values.

The === variant (case equality) treats X and Z as comparable values and is mainly used in testbenches.

Logical and bitwise operators

This is the number one mistake for beginners. Confusing logical operators with the bitwise operators we saw in the previous article.

  • Bitwise (&, |, ~): Operate bit by bit and return a vector of the same size.
  • Logical (&&, ||, !): Treat the entire vector as a boolean value (0 is False, anything else is True) and return a single bit.

Let’s see the difference with a clear example: Suppose A = 4'b1010 and B = 4'b0000.

OperationTypeResultExplanation
A & BBitwise4'b00001&0, 0&0, 1&0, 0&0.
A && BLogical1'b0 (False)Is A true? Yes. Is B true? No. -> False.
~ABitwise4'b0101Inverts each bit.
!ALogical1'b0 (False)A is “true”, so NOT A is false.

In if statements, we usually use logical ones (if (a && b)).

In signal assignments, we usually use bitwise ones (assign y = a & b).

Shift operators

They are very useful for multiplying or dividing by powers of 2 and for bit manipulation.

  • << : Left shift (Fills with zeros). Multiplies by 2.
  • >> : Logical right shift (fills with zeros). Divides an unsigned value by 2.
assign y = a << 2; // Equivalent to multiplying 'a' by 4.
Copied!

Concatenation operator { }

This one is exclusive to HDLs and is wonderful. It allows us to join individual bits or buses to form a larger bus. Curly braces { , } are used.

wire a;           // 1 bit
wire [3:0] b;     // 4 bits
wire [4:0] bus_c; // 5 bits

// We join 'a' (high part) with 'b' (low part)
assign bus_c = {a, b}; 
Copied!

We can also use it to replicate bits using the syntax {times{value}}.

// We want to extend a 4-bit number to 8 bits by filling with zeros
assign byte_completo = { 4'b0000, dato_4bits };

// Or by filling with ones repeated 4 times
assign byte_unos = { {4{1'b1}}, dato_4bits }; 
Copied!

Integrative example: a comparator with threshold

Let’s use arithmetic and comparisons. We will make a module that activates an output if the input exceeds a threshold. It is not real hysteresis, because it does not preserve state nor use two different thresholds.

module comparador_simple (
    input wire [7:0] sensor,     // Value from 0 to 255
    input wire [7:0] umbral,     // Reference value
    output wire alarma_critica,
    output wire aviso
);

    // 1. Relational: Is it strictly greater?
    assign aviso = (sensor > umbral);

    // 2. Arithmetic + Relational
    // Critical alarm if it exceeds the threshold + 10 units
    assign alarma_critica = (sensor > (umbral + 8'd10));

endmodule
Copied!