Un multiplexor es un circuito que selecciona una de varias entradas según el valor de una señal de control.
En programación tradicional, cuando escribimos un if, el procesador evalúa una condición y salta a una línea de código u otra. Es un cambio en el flujo de ejecución.
En una FPGA, como ya hemos repetido (y repetiremos hasta la saciedad), no existe el flujo. Todo ocurre a la vez.
Entonces, ¿cómo tomamos decisiones? ¿Cómo hacemos un “Si pasa A, haz esto; si no, haz lo otro”? La respuesta es física: mediante Multiplexores.
Hoy vamos a ver cómo las estructuras de control clásicas (if-else, case) se traducen en hardware y por qué debes tener cuidado con la prioridad.
El multiplexor: el guardagujas digital
Antes de ver el código, visualicemos el componente. Un Multiplexor (MUX) es un conmutador digital. Tiene varias entradas de datos, una salida, y unas entradas de control (selector).
Si el selector vale 0, deja pasar la entrada A. Si vale 1, deja pasar la entrada B. No se “ejecuta” una opción u otra; ambas señales llegan a la puerta del selector, pero solo una cruza al otro lado.
La sentencia if-else
En Verilog, la sentencia if-else se utiliza dentro de bloques always (lógica procedimental).
Cuando el sintetizador ve un if-else combinacional, infiere una cadena de multiplexores.
reg [3:0] salida;
always @(*) begin
if (selector == 1'b1) begin
salida = dato_a;
end else begin
salida = dato_b;
end
endEsto es equivalente a: assign salida = (selector) ? dato_a : dato_b;
Lógica de prioridad
Aquí viene la trampa. Si anidamos muchos if-else, estamos creando una estructura de prioridad.
always @(*) begin
if (reset) begin
y = 0;
end else if (enable) begin
y = dato;
end else begin
y = antiguo_valor;
end
endEn hardware, esto significa:
- Comprueba
reset. - SOLO SI no es
reset, compruebaenable.
Físicamente, esto crea multiplexores en cadena (cascada). Si tienes una cadena de if-else muy larga (ej: 20 condiciones), estás poniendo 20 multiplexores uno detrás de otro. Esto aumenta el retardo de la señal (Path Delay) y puede hacer que tu diseño sea lento.
El if tiene coste.
Cada else if añade un nivel más de lógica que la señal debe atravesar. Úsalo cuando realmente necesites que una señal tenga prioridad sobre otra (como un Reset crítico).
La sentencia case
Cuando queremos elegir entre muchas opciones (como en un menú), la sentencia case es mucho más limpia y, a menudo, más eficiente.
A diferencia de una cadena prioritaria de if, un case con alternativas mutuamente excluyentes suele describir un multiplexor sin prioridad explícita. La estructura final depende de las condiciones y de la optimización del sintetizador.
reg [7:0] resultado;
input [1:0] operacion;
always @(*) begin
case (operacion)
2'b00: resultado = a + b; // Suma
2'b01: resultado = a - b; // Resta
2'b10: resultado = a & b; // AND
2'b11: resultado = a | b; // OR
default: resultado = 8'b0; // ¡IMPORTANTE!
endcase
endLa importancia de cubrir todas las alternativas
Ya lo mencionamos en el artículo sobre always, pero merece la pena repetirlo.
Si en un case no cubres todas las combinaciones posibles (y recuerda que un bus de 32 bits tiene 4 mil millones de combinaciones), y no pones un default, la FPGA pensará: “Ah, si viene un valor que no está en la lista, tengo que mantener el valor anterior”.
¡BUM! Latch generado. Diseño roto.
En un bloque combinacional, asigna valores por defecto antes del case o incluye un default para que todas las salidas queden definidas en cualquier camino.
Ejemplo práctico: una ALU sencilla
Vamos a crear el corazón de un procesador: la ALU (Arithmetic Logic Unit). Es el módulo encargado de calcular. Usaremos un case para seleccionar qué operación realizar.
module alu (
input wire [7:0] a,
input wire [7:0] b,
input wire [2:0] opcode, // Código de operación (3 bits -> 8 opciones)
output reg [7:0] y
);
// Definimos constantes para que el código sea legible (buena práctica)
localparam OP_SUMA = 3'b000;
localparam OP_RESTA = 3'b001;
localparam OP_AND = 3'b010;
localparam OP_OR = 3'b011;
localparam OP_XOR = 3'b100;
always @(*) begin
case (opcode)
OP_SUMA: y = a + b;
OP_RESTA: y = a - b;
OP_AND: y = a & b;
OP_OR: y = a | b;
OP_XOR: y = a ^ b;
// Si el opcode no es conocido, sacamos 0
default: y = 8'b0;
endcase
end
endmodule¿Cuál uso?
| Sentencia | Estructura Hardware | Uso recomendado |
|---|---|---|
if-else | Multiplexores en cascada (Prioridad) | Cuando una condición es crítica y debe “ganar” a las demás (ej: Reset, Parada de emergencia). O para decisiones binarias simples. |
case | Multiplexor ancho (Paralelo) | Cuando hay que elegir una opción entre muchas del mismo nivel (ej: Máquinas de estados, Decodificación de instrucciones, Tablas de verdad). |