Qué significa "operador" en Verilog
Los operadores toman expresiones que portan señales y producen nuevas expresiones que portan señales. a + b es hardware - el sintetizador lo convierte en un sumador. a == b es hardware - construye un comparador. Nada de esto es "código que se ejecuta"; todo es "circuitos que el operador describe".
El menú completo de operadores es lo bastante pequeño como para memorizarlo. Este doc cubre los operadores aritméticos, de comparación, lógicos y condicional. Bitwise and Reduction cubre los operadores a nivel de bit que no tienen análogos directos en software.
Aritméticos: + - * / %
Tres cosas que saber:
- La división es entera.
7/2es3, no3.5. Verilog no tiene tipo float (bueno,real, pero eso es solo simulación). - División y módulo son caros en hardware. Un
/4está bien (el sintetizador lo convierte en un desplazamiento a la derecha), pero un/nparanvariable en runtime construye un divisor multi-ciclo que casi nunca quieres. - El desbordamiento envuelve. Sumar dos valores sin signo de N bits que exceden N bits trunca a N bits. Si quieres capturar el acarreo, declara el resultado un bit más ancho:
wire [7:0] a, b;
wire [8:0] sum = a + b; // 9-bit sum captures carry out
Comparación: == != < > <= >=
Los seis operadores de comparación devuelven un resultado de un solo bit: 1 si verdadero, 0 si falso, x si cualquier operando tiene un bit x o z. Para manejar el caso x/z, recurre a === y !== (los operadores de igualdad case-equality) cubiertos en X and Z Values.
<= aquí es el operador de comparación (menor o igual que). Los mismos caracteres también significan asignación no bloqueante en un bloque procedural (q <= d). El contexto los distingue: dentro de una expresión es una comparación; a la izquierda de un valor que se está asignando, es la asignación. El solapamiento es confuso pero inequívoco para el parser.
Lógicos: && || !
Los operadores lógicos tratan sus operandos como booleanos (cualquier cosa distinta de cero es verdadero, cero es falso) y producen un resultado de un solo bit:
La distinción crucial que interiorizar: && lógico no es & bit a bit.
4'b1100 && 4'b0011 // 1 (both are non-zero, logical AND is true)
4'b1100 & 4'b0011 // 4'b0000 (no bit positions are 1 in both)
Lo mismo para || vs |. Usa lógicos en condiciones if y predicados ?:; usa bit a bit cuando quieras que cada posición de bit haga AND/OR de forma independiente.
&& y || son short-circuit en el sentido de C: el lado derecho no se evalúa si el resultado ya está determinado por el izquierdo. Esto importa para testbenches que comprueban punteros o llamadas a funciones; rara vez importa para RTL sintetizable donde todo sucede en paralelo de todas formas.
El operador condicional: ?:
El ternario es tu mejor amigo para construir muxes y expresiones condicionales:
El patrón cond ? a : b es un multiplexor 2 a 1 con selección de 1 bit en hardware. Encadenarlos está bien para selectores de 3 y 4 vías, pero pasado de eso una sentencia case (cubierta en Case Statement) es drásticamente más legible.
El operador ?: también es la forma estándar de dar a un registro un valor por defecto que solo sobrescribes a veces:
next_value = update ? new_data : next_value;
Reglas de ancho: la trampa del principiante
Las reglas de ancho de Verilog para operadores aritméticos son notoriamente sorprendentes. La versión corta:
- El ancho del resultado es el máximo de los anchos de las entradas.
- Si el resultado se asigna a algo más ancho, se hace zero-extend (o sign-extend si tiene signo).
- El ancho de los cálculos intermedios también es el ancho del resultado, lo que significa que el desbordamiento ocurre en el intermedio.
La primera multiplicación desborda porque los anchos de los operandos son ambos de 8 bits, el ancho del resultado por tanto es de 8 bits, y 40000 no encaja. La segunda funciona porque ensanchamos a explícitamente antes de multiplicar.
Las técnicas para arreglarlo:
- Usa el operador de concatenación
{8'b0, a}para hacer zero-extend. - Usa
$unsigned(a)o$signed(a)para influir en la interpretación. - Haz cast de las expresiones intermedias a anchos mayores cuando dudes.
Precedencia de operadores
No vas a memorizar la tabla, pero te apoyarás en unas pocas reglas:
*,/,%enlazan más fuerte que+,-.- La comparación enlaza más fuerte que los lógicos (
&&,||). - El operador condicional
?:tiene precedencia muy baja - normalmente necesitas paréntesis alrededor de sus ramas en expresiones más grandes. - Cuando dudes, pon paréntesis. El simulador no cobra por carácter.
out = (mode == 2'd0) ? a + b : a - b; // clearer with parens
Qué viene a continuación
Has cubierto cada operador que se parece a su primo del software. El siguiente doc - Bitwise and Reduction - maneja los operadores que no tienen análogos directos en software: AND/OR/XOR bit a bit por bit, además de las formas de reducción que colapsan un vector entero a un solo bit.
Preguntas frecuentes
¿Qué operadores soporta Verilog?
Verilog tiene cuatro grandes familias de operadores: aritméticos (+, -, *, /, %), de comparación (==, !=, <, >, <=, >=), lógicos (&&, ||, !) y bit a bit / de reducción (&, |, ^, ~, ~&, ~|, ~^). Más operadores de desplazamiento (<<, >>, <<<, >>>), concatenación {}, replicación {N{...}} y el condicional ?:.
¿Cuál es la diferencia entre && y & en Verilog?
&& es AND lógico - trata cada operando como un booleano (cero o distinto de cero) y devuelve un resultado de 1 bit. & es AND bit a bit - empareja bits posición por posición. 4'b1100 && 4'b0011 es 1 (los dos son distintos de cero); 4'b1100 & 4'b0011 es 4'b0000. Usa && en condiciones, & para operaciones a nivel de bit.
¿Cómo funciona la división en Verilog?
División entera - la parte fraccionaria se descarta. 7 / 2 es 3, no 3.5. El operador % da el resto: 7 % 2 es 1. La división por cero produce x (desconocido) en simulación. Los dos operadores son técnicamente sintetizables pero producen hardware muy grande y lento en FPGAs y ASICs - evítalos en caminos sintetizables a menos que el divisor sea una potencia de 2.
¿Qué es el operador condicional en Verilog?
?: es el operador ternario o condicional, copiado de C. cond ? a : b evalúa a a si cond es verdadero (distinto de cero) y a b si no. Es la construcción más usada para lógica tipo multiplexor: out = sel ? in1 : in0 construye un mux 2 a 1. Cadenas de ?: construyen muxes más anchos, aunque case suele ser más claro pasados dos inputs.