15 de marzo de 2026
En el siguiente tema veremos algunos ejemplos de circuitos combinacionales. En SystemVerilog un circuito combinacional es un bloque de lógica digital cuya salida depende únicamente de los valores actuales de sus entradas, sin memoria ni almacenamiento de estados anteriores (no usa flip-flops ni registros). Tampoco necesita de una señal de reloj; la salida cambia inmediatamente cuando cambian las entradas. Este tipo de circuitos se describe normalmente con always_comb.
Una LUT es el bloque combinacional fundamental de una FPGA. Puede implementa cualquier función lógica de n entradas almacenando la tabla de verdad en una pequeña memoria ROM.
Por jemplo, suponer la función lógica de cuatro entradas y = (A & B) | (C & D). En SystemVerilog se escribiría así:
module logic_function(
input logic A,B,C,D,
output logic Y
);
assign Y = (A & B) | (C & D);
endmodule
La tabla de la verdad de la función y = (A & B) | (C & D) queda como sigue:
El sintetizador de la FPGA reconocerá que la función tiene 4 variables y la implementará en una LUT como una ROM de 4 bits y 16 direcciones.
La dirección de la ROM son las entradas A B C D y el valor de la función es el dato que guarda esa dirección, por ejemplo si A B C D = 1 1 0 0, la dirección es 1100 = 12
y = memoria[1100] = 1
Los multiplexores son mucho más que simples interruptores; son los verdaderos "directores de tráfico" de los datos dentro de un chip. Sin ellos, sería imposible gestionar la complejidad de los procesadores modernos.
En una CPU, el Datapath depende totalmente de los multiplexores. Por ejemplo, la Unidad Aritmético Lógica (ALU) recibe operandos de diferentes registros. Un MUX actúa como árbitro del Datapath, decidiendo qué registro se conecta a la entrada de la ALU en cada ciclo de instrucción.
module mux4_1_32bits (
input logic [31:0] a, b, c, d,
input logic [1:0] sel,
output logic [31:0] out);
always_comb begin
case (sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
2'b11: out = d;
default: out = 32'b0; // Valor por defecto
endcase
end
endmodule
Otra aplicación de los multiplexores es la implementación de Lógica Booleana. Un dato curioso es que un MUX 2-1 puede actuar como una compuerta AND, OR o NOT, permitiendo implementar cualquier función lógica usando solo multiplexores.
Los comparadores son bloques de lógica combinacional que determinan la relación aritmética entre dos números binarios (A = B), (A > B) o (A < B). Si los multiplexores son los "directores de tráfico", los comparadores son los "tomadores de decisiones".
La importancia de un comparador radica en su capacidad para convertir datos cuantitativos (números) en señales de control (flags). Sin ellos, un procesador no podría ejecutar un simple "if-else" o un bucle "for".
En el siguiente ejemplo utilizaremos los switches [3:0] y los ledes [3:0] para introducir y visualizar el dato a respectivamente y los switches [7:4] y los ledes [7:4] para introducir y visualizar para el dato b. El resultado de la comparación lo mostramos en los ledes 15 (a_gt_b), 14 (a_eq_b) y 13 (a_lt_b).
module comparador_4bits(
input logic [3:0] a, b, // Entradas
output logic a_gt_b, // A > B
output logic a_lt_b, // A < B
output logic a_eq_b, // A == B
output logic [7:0] led
);
assign led [3:0] = a;
assign led [7:4] = b;
always_comb begin
// Inicializamos las salidas para evitar "latches"
a_gt_b = 1'b0;
a_lt_b = 1'b0;
a_eq_b = 1'b0;
if (a > b)
a_gt_b = 1'b1;
else if (a < b)
a_lt_b = 1'b1;
else
a_eq_b = 1'b1;
end
endmodule
Un registro de desplazamiento convencional (shift register) requiere n ciclos para desplazar n posiciones mientras que un Barrel Shifter tiene un retardo constante e independiente de la magnitud del desplazamiento, lo que le permite desplazar o rotar un bus de datos un número n de bits en un solo ciclo de reloj.
En arquitecturas de 32 o 64 bits, esperar 31 ciclos para alinear un dato sería inaceptable para el rendimiento del sistema, de ahí la importancia del Barrel shifter; es un componente crítico que marca la diferencia entre los procesadores básicos y los procesadores de alto rendimiento.
Este es su uso más crítico. Para sumar dos números en punto flotante primero hay que alinear las mantisas igualando sus exponentes. El Barrel Shifter desplaza la mantisa del número más pequeño de forma instantánea para que la suma pueda proceder.
Muchos lenguajes de programación y algoritmos de criptografía requieren desplazamientos (<<, >>) o rotaciones. En arquitecturas como ARM, existe un Barrel Shifter integrado en el Datapath de la ALU, lo que permite realizar desplazamientos y operaciones aritméticas (como una suma) en la misma instrucción sin costo adicional de tiempo.
En los DSP (Digital Signal Processors), los Barrel Shifters se usan para el empaquetado y desempaquetado de píxeles, así como para el escalado rápido de valores de color, donde multiplicar o dividir por potencias de 2 es una operación constante.
Para multiplicar un número por una constante, el hardware puede usar un Barrel Shifter para desplazar el número 3 posiciones a la izquierda y luego restarle el valor original. Esto es mucho más rápido y eficiente que usar un multiplicador completo.
En el siguiente ejemplo vemos un Barrel Shifter de 8 bits. Con los switches [15:8] introducimos el dato y lo visualizamos en los ledes [15:8] . Con los switches [2:0] seleccionamos el tamaño del desplazamiento y el resultado del desplazamiento se visualiza en los ledes [7:0] de la BASYS 3.
El circuito es puramente combinacional; tan pronto como cambiamos el dato de entrada o el tamaño de desplazamieno, el resultado se visualiza inmediatamente en los ledes de salida.
module barrel_shifter(
input logic [7:0] data_in,
input logic [2:0] shift, // desplazamiento (0-7)
output logic [7:0] data_out,
output logic [7:0] led);
assign led = data_in; // para visualizar el dato de entrada
always_comb begin
case (shift)
3'd0: data_out = data_in;
3'd1: data_out = data_in << 1;
3'd2: data_out = data_in << 2;
3'd3: data_out = data_in << 3;
3'd4: data_out = data_in << 4;
3'd5: data_out = data_in << 5;
3'd6: data_out = data_in << 6;
3'd7: data_out = data_in << 7;
endcase
end
endmodule
Barrel Shifter - Schematic
Una ALU o (Unidad Aritmético Lógica) es el núcleo computacional de cualquier microprocesador. Es la parte del hardware que realiza las operaciones que dictan las instrucciones del software.
Para terminar este tema dedicado a los circuitos combinacionales, a continuación vamos a ver cómo describir una ALU con un tamaño de bus de 8 bits y 3 bits de control, lo que nos permitirá hacer hasta 8 operaciones distintas.
module alu_8bit (
input logic [7:0] a, b, // Entradas de datos
input logic [2:0] op, // Código de operación (3 bits)
output logic [7:0] result, // Resultado de la operación
output logic zero // Zero Flag
);
always_comb begin
case (op)
3'b000: result = a + b; // Suma
3'b001: result = a - b; // Resta
3'b010: result = a & b; // AND lógico
3'b011: result = a | b; // OR lógico
3'b100: result = a ^ b; // XOR lógico
3'b101: result = ~a; // NOT (de la entrada A)
3'b110: result = a << 1; // Desplazamiento lógico izquierda
3'b111: result = a >> 1; // Desplazamiento lógico derecha
default: result = 8'b0; // Valor por defecto
endcase
end
// flag cero: se activa si result = 0
assign zero = (result == 8'b0);
endmodule
Con los switches [7:0] introducimos el dato a, con los switches [15:8] introducimos el dato b.
El resultamos de la operación lo visualizamos en los ledes [7:0] y el zero flag en el LED 15.
La selección de operación se hace con un switch externo conectado en los pines PMOD JA1, JA2 y JA3.
8 bit ALU
Hasta aquí hemos visto cinco ejemplos de circuitos combinacionales descritos en SystemVerilog. Eso es todo por el momento, espero que os haya gustado.
¡Hasta la próxima!