Construcción de circuitos de puertas lógicas
En la lección anterior conocimos cuatro puertas lógicas básicas. En esta lección, las conectaremos para construir circuitos que resuelvan problemas reales.
Una puerta lógica individual es como un tornillo: por sí sola tiene una función limitada. Pero al combinarlas, se pueden construir funciones de cualquier complejidad.
Analogía cotidiana: construir un castillo con bloques LEGO
Imagina que tienes bloques LEGO de cuatro colores:
- Bloque verde = puerta AND: solo conduce cuando ambos extremos están conectados
- Bloque naranja = puerta OR: conduce si cualquier extremo está conectado
- Bloque rojo = puerta NOT: lo encendido se apaga, lo apagado se enciende
- Bloque morado = puerta XOR: conduce cuando los dos extremos tienen estados diferentes
¿Qué puedes construir con estos bloques?
Un votador: tres personas votan y la mayoría gana. Este es un circuito lógico combinacional típico: se puede implementar conectando puertas AND/OR.
Una cerradura de contraseña: hay que pulsar a la vez varias teclas correctas para abrir. Es esencialmente una cascada de puertas AND — cada tecla corresponde a una entrada, y solo cuando todas las entradas son 1 la salida es 1.
Una alarma: cualquier sensor que se active hace sonar la alarma. Es esencialmente una aplicación de la puerta OR — basta con que un sensor emita 1 para que la alarma suene.
Lógica combinacional vs. lógica secuencial
Antes de profundizar en la construcción de circuitos, distinguimos dos conceptos importantes:
| Tipo | Característica | La salida depende de | ¿Tiene memoria? | Ejemplos |
|---|---|---|---|---|
| Lógica combinacional | La salida está determinada solo por la entrada actual | Valores de entrada actuales | No | Sumadores, votadores por mayoría, decodificadores |
| Lógica secuencial | La salida está determinada por la entrada actual + el estado previo | Entrada actual + valores almacenados previamente | tener | Contadores, registros, máquinas de estado |
Esta lección se centra en la lógica combinacional.La lógica secuencial se tratará en detalle en la lección 9.
Una forma sencilla de distinguirlas: la lógica combinacional es «lo que ingresas, eso devuelvo» (como una función matemática); la lógica secuencial es «todavía recuerdo lo que ingresaste antes» (como la memoria humana).
Metodología de construcción de circuitos: método de tres pasos
Para construir cualquier circuito lógico combinacional con puertas lógicas, hay un proceso general de tres pasos:
- Elaborar la tabla de verdad: enumera todas las combinaciones de entrada y escribe la salida esperada en cada caso.
- Escribir la expresión lógica: para cada fila donde la salida sea 1, escribe el «término producto» correspondiente (combinación AND de las entradas) y luego conecta todos los términos producto con OR. Esto se llama forma «suma de productos» (Sum of Products).
- Dibujar el diagrama del circuito / escribir código: traduce la expresión lógica a conexiones de circuito con puertas.
A continuación practicamos este método con tres ejemplos de dificultad creciente.
Caso 1: cerradura de contraseña de dos entradas
Requisito: diseñar un circuito simple de verificación de contraseña. Hay dos entradas A y B; solo cuando A=1 y B=1 la salida es 1 (desbloqueada), en los demás casos la salida es 0 (bloqueada).
Este requisito es en realidad una puerta AND. Pero podemos usarlo para demostrar el método de tres pasos:
Paso 1: tabla de verdad
| A | B | Salida esperada Y |
|---|---|---|
| 0 | 0 | 0 (bloqueado) |
| 0 | 1 | 0 (bloqueado) |
| 1 | 0 | 0 (bloqueado) |
| 1 | 1 | 1 (desbloqueado) |
Paso 2: expresión lógica
Solo la última fila tiene salida 1. El término producto correspondiente es: A AND B.
Por lo tanto: Y = A AND B
Paso 3: implementación del circuito
Solo se necesita una puerta AND, con A y B como entradas y Y como salida.
¿Demasiado simple? Veamos un ejemplo con mayor aplicación práctica.
Caso 2: votador por mayoría (caso central)
Requisito: tres personas (A, B, C) votan sobre una propuesta (1 = a favor, 0 = en contra). Cuando al menos dos votan a favor, la propuesta se aprueba (salida 1).
Este es un votador por mayoría clásico de tres entradas y un caso imprescindible en los libros de circuitos digitales.
Paso 1: elaborar la tabla de verdad
Con tres entradas hay 23= 8 combinaciones:
| A | B | C | Número de votos a favor | ¿Se aprueba? Y | Descripción |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | Nadie a favor |
| 0 | 0 | 1 | 1 | 0 | Solo C a favor |
| 0 | 1 | 0 | 1 | 0 | Solo B a favor |
| 0 | 1 | 1 | 2 | 1 | B y C a favor |
| 1 | 0 | 0 | 1 | 0 | Solo A a favor |
| 1 | 0 | 1 | 2 | 1 | A y C a favor |
| 1 | 1 | 0 | 2 | 1 | A y B a favor |
| 1 | 1 | 1 | 3 | 1 | Aprobado por unanimidad |
Paso 2: escribir la expresión lógica
Localiza las filas con salida 1 (filas 4, 6, 7, 8) y escribe un término producto para cada una:
- Fila 4: NOT(A) AND B AND C
- Fila 6: A AND NOT(B) AND C
- Fila 7: A AND B AND NOT(C)
- Fila 8: A AND B AND C
Conecta con OR: Y = (NOT(A) AND B AND C) OR (A AND NOT(B) AND C) OR (A AND B AND NOT(C)) OR (A AND B AND C)
Esta expresión, aunque parece un poco larga, es muy clara a nivel lógico:Y es 1 si y solo si cualquiera de dos o tres entradas es 1。
Paso 3: dibujar el diagrama del circuito con SVG
Las tres puertas AND detectan respectivamente si los pares «AB», «BC» y «AC» son 11, y luego la puerta OR resume el resultado.
Demostración interactiva: implementación en Python del votador por mayoría
Ejemplo
# Requisito: tres personas votan, se aprueba (salida 1) solo si al menos dos votan a favor (1)
# ----- Reutilizar las puertas lógicas básicas de la lección 6 -----
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def OR(a, b):
return 1 if a == 1 or b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
# ----- Método 1: forma estándar de suma de productos (SOP) -----
def majority_voter_sop(a, b, c):
"""
Implementación del votador por mayoría mediante «suma de productos».
Se deduce directamente de la tabla de verdad: localiza todas las filas con salida 1,
escribe un término producto para cada fila y luego conéctalos con OR.
"""
term1 = AND(AND(NOT(a), b), c) # ~A·B·C
term2 = AND(AND(a, NOT(b)), c) # A·~B·C
term3 = AND(AND(a, b), NOT(c)) # A·B·~C
term4 = AND(AND(a, b), c) # A·B·C
return OR(OR(term1, term2), OR(term3, term4))
# ----- Método 2: forma simplificada optimizada -----
def majority_voter_optimized(a, b, c):
"""
Implementación optimizada del votador por mayoría: Y = AB + BC + AC.
Esta forma requiere menos puertas, pero es lógicamente equivalente al método 1.
¿Por qué son equivalentes?
Al expandir la forma SOP del método 1, se puede simplificar usando la ley de absorción del álgebra de Boole:
~A·B·C + A·~B·C + A·B·~C + A·B·C
= (~A·B·C + A·B·C) + (A·~B·C + A·B·C) + (A·B·~C + A·B·C)
= B·C·(~A+A) + A·C·(~B+B) + A·B·(~C+C)
= B·C + A·C + A·B
"""
return OR(OR(AND(a, b), AND(b, c)), AND(a, c))
# ============================================
# Imprimir la tabla de verdad completa y comparar los dos métodos
# ============================================
print("=" * 60)
print(" Tabla de verdad del votador por mayoría de tres entradas (demo EJEMPLO)")
print("=" * 60)
print()
print("┌───┬───┬───┬────────┬──────────┬──────────┐")
print("│ A │ B │ C │ ¿Aprueba? │ Método SOP │ Método optimizado │")
print("├───┼───┼───┼────────┼──────────┼──────────┤")
for a in [0, 1]:
for b in [0, 1]:
for c in [0, 1]:
count = a + b + c
passed = "Aprobado" if count >= 2 else "No aprobado"
sop = majority_voter_sop(a, b, c)
opt = majority_voter_optimized(a, b, c)
print(f"│ {a} │ {b} │ {c} │ {passed} │ {sop} │ {opt} │")
print("└───┴───┴───┴────────┴──────────┴──────────┘")
# ============================================
# Estadísticas de uso de puertas lógicas
# ============================================
print("\n" + "=" * 60)
print(" Comparación del número de puertas utilizadas por los dos métodos")
print("=" * 60)
print()
print("Método 1 (forma SOP completa):")
print(" Puerta NOT: 3 (~A, ~B, ~C)")
print(Puerta AND: 4 (cuatro términos AND, cada uno necesita 2 AND de 2 entradas))
print(Puerta OR: 3 (combina los cuatro términos AND))
print(Total: aproximadamente 14 llamadas a puertas básicas)
print()
print(Método 2 (forma optimizada Y = AB + BC + AC):)
print(Puerta AND: 3 (AB, BC, AC))
print(Puerta OR: 2 (combina los tres términos AND))
print(Total: 5 llamadas a puertas básicas)
print()
print(¡La optimización reduce el uso de puertas lógicas en aproximadamente un 65 %!)
print(Este es el valor de ingeniería de la simplificación del álgebra de Boole.)
Presta atención al proceso de conversión de la tabla de verdad a la expresión lógica. Es una de las habilidades más esenciales en el diseño de circuitos digitales: al ver la tabla de verdad, puedes escribir la expresión; al ver la expresión, puedes dibujar el diagrama del circuito. Los tres se corresponden uno a uno.
Caso 3: comparador de un bit
Requisito: Comparar dos números binarios de 1 bit A y B. Produce tres señales: A > B, A = B, A < B Este es un circuito fundamental muy importante: los comparadores de múltiples bits y las evaluaciones de condiciones en la CPU se derivan todos de aquí. Observa la tabla de verdad: Observa que la detección de A = B está estrechamente relacionada con la puerta XOR. La salida XOR igual a 1 significa «diferente»; aplicarle NOT produce «igual». : Entrada de un número binario de 2 bits (00, 01, 10, 11); hace que una de las 4 líneas de salida correspondientes sea 1 y las demás sean 0.El decodificador es un componente central de la CPU: cuando la CPU va a seleccionar un registro, en esencia está usando un decodificador. Tabla de verdad Puertas universales: la particularidad de NAND y NOR Puerta NAND = AND + NOT , es decir, primero se realiza AND y luego se niega. NAND y NOR se denominan «puertas universales» (Universal Gates) porque cada una por sí sola puede implementar todas las funciones booleanas.¿Por qué los chips prefieren NAND/NOR? Porque en el proceso CMOS: La puerta NAND solo necesita 4 transistores, mientras que la AND necesita 6 (AND = NAND + NOT) Ejemplo Demostración interactiva: constructor de circuito de votación por mayoría (demo ejemplo vis-network) para visualizar un circuito de votación por mayoría de tres entradas (Y = AB + BC + AC).vis-networkHaz clic en los nodos de entrada A, B, C para alternar entre 0 y 1, el circuito se recalcula automáticamente y actualiza el color de todas las conexiones—Rojoindica que la señal es 1,Grisindica que la señal es 0.Votación por mayoría de tres entradas (haz clic en los nodos A/B/C para alternar)Paso 1: Tabla de verdad
A B A > B A = B A < B 0 0 0 1 0 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 Paso 2: Expresión lógica
Paso 3: Implementación en código
Ejemplo
# Compara dos números binarios de 1 bit A y B
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def OR(a, b):
return 1 if a == 1 or b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
def one_bit_comparator(a, b):
"""
Comparador de 1 bit: compara A y B.
Devuelve la tupla de tres elementos (Greater, Equal, Less)
"""
greater = AND(a, NOT(b)) # A > B: A=1 y B=0
less = AND(NOT(a), b) # A < B: A=0 y B=1
# A = B: no es ni greater ni less
# Equivalente a NOT(A XOR B); aquí se usa otra implementación
equal = AND(NOT(greater), NOT(less))
return (greater, equal, less)
# Imprime la tabla de verdad
print("=" * 50)
print(" Tabla de verdad del comparador de 1 bit (DEMO DE EJEMPLO)")
print("=" * 50)
print()
print("┌───┬───┬────────┬────────┬────────┐")
print("│ A │ B │ A > B │ A = B │ A < B │")
print("├───┼───┼────────┼────────┼────────┤")
for a in [0, 1]:
for b in [0, 1]:
gt, eq, lt = one_bit_comparator(a, b)
print(f"│ {a} │ {b} │ {gt} │ {eq} │ {lt} │")
print("└───┴───┴────────┴────────┴────────┘")
# Prueba algunos escenarios útiles
print("\n"Interpretación: A es mayor que B")
a_test, b_test = 1, 0
gt, eq, lt = one_bit_comparator(a_test, b_test)
print(f"A={a_test}, B={b_test}: A>B={gt}, A=B={eq}, A<B={lt}")
print(f"A es igual a B" if gt else ("A es menor que B" if eq else Caso 4: Decodificador 2-4))
Caso 4: decodificador 2 a 4
Expresión lógica
A1 A0 Y0 Y1 Y2 Y3 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 1 0 1 1 0 0 0 1 Ejemplo
Ejemplo
# Salida: exactamente una de las 4 líneas es 1
Decodificador de 2 líneas a 4 líneas.
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
def decoder_2to4(a1, a0):
"""
Devuelve la lista [Y0, Y1, Y2, Y3],
donde exactamente uno es 1 y los demás son 0.
# Imprime la tabla de verdad
"""
Y0 = AND(NOT(a1), NOT(a0)) # 00 → Y0
Y1 = AND(NOT(a1), a0) # 01 → Y1
Y2 = AND(a1, NOT(a0)) # 10 → Y2
Y3 = AND(a1, a0) # 11 → Y3
return [Y0, Y1, Y2, Y3]
" Tabla de verdad del decodificador 2-4 (DEMO DE EJEMPLO)"
print("=" * 50)
print(# Muestra un escenario de uso real)
print("=" * 50)
print()
print("┌────┬────┬────┬────┬────┬────┐")
print("│ A1 │ A0 │ Y0 │ Y1 │ Y2 │ Y3 │")
print("├────┼────┼────┼────┼────┼────┤")
for a1 in [0, 1]:
for a0 in [0, 1]:
Y = decoder_2to4(a1, a0)
print(f"│ {a1} │ {a0} │ {Y[0]} │ {Y[1]} │ {Y[2]} │ {Y[3]} │")
print("└────┴────┴────┴────┴────┴────┘")
"Escenario de aplicación real: usar un decodificador para seleccionar un registro"
print("\n# Encuentra qué línea de salida es 1)
registers = ["R0", "R1", "R2", "R3"]
for a1 in [0, 1]:
for a0 in [0, 1]:
Y = decoder_2to4(a1, a0)
idx = Y.index(1) " Dirección A1A0={a1}{a0} → seleccionar registro {registers[idx]}"
print(fEl decodificador es el circuito clásico de «uno a muchos». Su contraparte es el multiplexor (MUX), que es «muchos a uno». Ambos son muy comunes en el diseño de la ruta de datos de la CPU. El decodificador se usa para «seleccionar un destino» (por ejemplo, elegir en qué registro escribir), y el multiplexor para «seleccionar una fuente» (por ejemplo, elegir de qué registro lee la ALU).)
Puertas universales: lo especial de NAND y NOR
Ejemplo
"""Puerta NAND: invierte el resultado de AND.
def NAND(a, b):
Y = 0 si y solo si a=1 y b=1"""
# Implementar NOT con NAND: NOT(A) = NAND(A, A)
return 0 if a == 1 and b == 1 else 1
# Implementar AND con NAND: AND(A,B) = NOT(NAND(A,B)) = NAND(NAND(A,B), NAND(A,B))
def NOT_from_NAND(a):
return NAND(a, a)
# Implementar OR con NAND: OR(A,B) = NAND(NOT(A), NOT(B))
def AND_from_NAND(a, b):
nand_ab = NAND(a, b)
return NAND(nand_ab, nand_ab)
# Verificar
def OR_from_NAND(a, b):
return NAND(NOT_from_NAND(a), NOT_from_NAND(b))
# ============================================
" Demostración de la universalidad de la puerta NAND (DEMO DE EJEMPLO)"
# ============================================
print("=" * 60)
print("[Verificación: NOT implementado con NAND]")
print("=" * 60)
print("\n" A={a}: NOT_from_NAND = {actual}, esperado = {expected}, {'Correcto' if actual == expected else 'Incorrecto'}")
for a in [0, 1]:
expected = 1 if a == 0 else 0
actual = NOT_from_NAND(a)
print(f"[Verificación: AND implementado con NAND]")
print("\n" A={a}, B={b}: AND_from_NAND = {actual}, esperado = {expected}, {'Correcto' if actual == expected else 'Incorrecto'}")
for a in [0, 1]:
for b in [0, 1]:
expected = 1 if a == 1 and b == 1 else 0
actual = AND_from_NAND(a, b)
print(f"[Verificación: OR implementado con NAND]")
print("\n" A={a}, B={b}: OR_from_NAND = {actual}, esperado = {expected}, {'Correcto' if actual == expected else 'Incorrecto'}")
for a in [0, 1]:
for b in [0, 1]:
expected = 1 if a == 1 or b == 1 else 0
actual = OR_from_NAND(a, b)
print(fConclusión: con solo una puerta NAND se puede construir cualquier función booleana.)
print("\nPor eso el curso nand2tetris puede construir una computadora completa partiendo de la puerta NAND.)
print(nand2tetris (De la puerta NAND a Tetris) es un famoso curso de computación. Te reta a construir paso a paso la CPU, la memoria y el sistema operativo usando únicamente la puerta NAND, y finalmente ejecutar un juego de Tetris. Si te interesan los fundamentos internos de las computadoras, lo recomiendo encarecidamente.)
Demostración interactiva: constructor de circuito votador por mayoría (demo ejemplo vis-network)
Votador por mayoría de tres entradas (haz clic en los nodos A/B/C para alternar)