Skip to main content

Operators

Status: Complete

This documentation is complete and ready to use.

Integer types (Bits<N>, U64) support most of the same operators as Verilog and use the same order of precedence. Notably excluded are many bitwise reduction operators (e.g., and-reduce, or-reduce).

Binary operators between operands of different bit widths extend the smaller operand to the size of the larger operand prior to the operation. When the smaller operand is signed, the extension is a sign extension; otherwise it is a zero extension.

The result of a binary operation is signed if both operands are signed; otherwise the result is unsigned.

Widening Operators

IDL includes widening variants of several binary operators, indicated by a backtick (`) prefix. These are unique to IDL and have no direct C or Verilog equivalent. They are useful when you need to preserve the full result of an operation that would otherwise overflow or lose bits.

In the table below, L(i) denotes the bit width of operand i, and value(j) denotes the compile-time constant value of j.

OperatorOperationResult width
i `* jWidening multiplyL(i) + L(j)
i `+ jWidening additionmax(L(i), L(j)) + 1
i `- jWidening subtractionmax(L(i), L(j)) + 1
i `<< jWidening left shift (j must be compile-time constant)L(i) + value(j)
Widening operator examples
Bits<32> a = 0xFFFFFFFF;
Bits<32> b = 0x00000002;

# Standard multiply: upper bits discarded, result stays 32 bits
Bits<32> narrow_mul = a * b; # 0xFFFFFFFE (truncated)

# Widening multiply: full 64-bit result
Bits<64> wide_mul = a `* b; # 0x00000001FFFFFFFE

# Standard addition: carry bit lost
Bits<32> wrapped_add = a + b; # 0x00000001 (truncation)

# Widening addition: carry preserved in extra bit, result is 33 bits
Bits<33> wide_add = a `+ b; # 0x100000001

# Widening left shift by compile-time constant 4: result is 36 bits
Bits<36> wide_shl = a `<< 4; # 0xFFFFFFFF0
note

`<< requires the shift amount to be known at compile time. Use << for variable shift amounts (discarding the upper bits).

Operator Precedence Table

For an operand i (which may be an expression), L(i) is the number of bits in i and typeof(i) is the exact type of i. Precedence 0 is highest.

PrecedenceOperatorResult TypeComments
0i[idx]Bits<1>Extract a single bit from bit position idx.
i must be an integral type or an array.
Result is unsigned, regardless of the sign of i.
i[msb:lsb]Bits<msb - lsb + 1>Extract a range of bits between msb and lsb, inclusive.
i must be an integral type.
Result is unsigned, regardless of the sign of i.
1(i)typeof(i)Grouping.
2!iBooleanLogical negation. i must be a Boolean type.
~itypeof(i)Bitwise negation. i must be an integral type.
3-itypeof(i)Unary minus in two's complement, i.e., 2N - i.
i must be an integral type.
4{i, j, ...}Bits<L(i) + L(j) + ...>Concatenation. All operands must be Bits<N> type. Result is always unsigned.
5{N{i}}Bits<N * L(i)>Replicates i N times. i must be a Bits<N> type.
N must be a literal or compile-time constant.
6i * jBits<max(L(i), L(j))>Multiply i times j. Result is the same width as the widest operand. Upper half of the result is discarded.
i `* jBits<L(i) + L(j)>Widening multiply i times j.
i / jBits<max(L(i), L(j))>Divide i by j. Result is the same width as the widest operand. Remainder is discarded.
Division by zero is undefined and must be avoided.
When both are signed, signed overflow is undefined and must be avoided.
i % jBits<max(L(i), L(j))>Remainder of the division of i by j. Quotient is discarded.
Division by zero is undefined and must be avoided.
When both are signed, signed overflow is undefined and must be avoided.
7i + jBits<max(L(i), L(j))>Addition. Carry bit is discarded. To preserve carry, widen operands before adding.
i `+ jBits<max(L(i), L(j)) + 1>Widening addition.
i - jBits<max(L(i), L(j))>Subtraction. Carry bit is discarded. To preserve carry, widen operands before subtracting.
i `- jBits<max(L(i), L(j)) + 1>Widening subtraction.
8i << jtypeof(i)Left logical shift.
i `<< jBits<L(i) + value(j)>Widening left logical shift. j must be known at compile time — otherwise a type error.
i >> jtypeof(i)Right logical shift.
i >>> jtypeof(i)Right arithmetic shift.
9i > jBooleanGreater than. i and j must be integral.
i < jBooleanLess than. i and j must be integral.
i >= jBooleanGreater than or equal. i and j must be integral.
i <= jBooleanLess than or equal. i and j must be integral.
10i == jBooleanEquality. i and j must be the same type: integral, boolean, or string.
i != jBooleanInequality. i and j must be the same type: integral, boolean, or string.
11i & jBits<max(L(i), L(j))>Bitwise AND. i and j must be integral.
12i ^ jBits<max(L(i), L(j))>Bitwise exclusive OR. i and j must be integral.
13i | jBits<max(L(i), L(j))>Bitwise OR. i and j must be integral.
14i && jBooleanLogical AND. i and j must be boolean.
i || jBooleanLogical OR. i and j must be boolean.
15c ? t : ftypeof(t)Ternary operator. Result is t if c is true, f otherwise.
c must be boolean; t and f must be compatible types.
When t and f are Bits types of different widths, the result is the larger of the two widths.
Division by zero and signed overflow are undefined behavior

The / and % operators produce undefined behavior when the divisor is zero. When both operands are signed, overflow (e.g., MIN_INT / -1) is also undefined. The IDL compiler does not insert runtime guards — it is the programmer's responsibility to ensure these cases cannot occur.

# Safe pattern: guard before dividing
if (b != 0) {
result = a / b;
}