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:

TipoCaracterísticaLa salida depende de¿Tiene memoria?Ejemplos
Lógica combinacionalLa salida está determinada solo por la entrada actualValores de entrada actualesNoSumadores, votadores por mayoría, decodificadores
Lógica secuencialLa salida está determinada por la entrada actual + el estado previoEntrada actual + valores almacenados previamentetenerContadores, 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:

  1. Elaborar la tabla de verdad: enumera todas las combinaciones de entrada y escribe la salida esperada en cada caso.
  2. 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).
  3. 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

ABSalida esperada Y
000 (bloqueado)
010 (bloqueado)
100 (bloqueado)
111 (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:

ABCNúmero de votos a favor¿Se aprueba? YDescripción
00000Nadie a favor
00110Solo C a favor
01010Solo B a favor
01121B y C a favor
10010Solo A a favor
10121A y C a favor
11021A y B a favor
11131Aprobado 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

Tres líneas de entrada A B C Puerta AND 1: A AND B AND Conectar a AND1 Puerta AND 2: B AND C AND Puerta AND 3: A AND C AND Puerta OR (combina las salidas de las tres AND) OR Línea de salida Y Etiqueta Figura: estructura del circuito votador por mayoría de tres entradas

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

# Votador por mayoría de tres entradas (demo 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í.

Paso 1: Tabla de verdad

ABA > BA = BA < B
00010
01001
10100
11010

Paso 2: Expresión lógica

Observa la tabla de verdad:

  • A > B es 1 si y solo si: A=1 AND B=0. Por lo tanto: Greater = A AND NOT(B)
  • A = B es 1 si y solo si: A y B son iguales. Por lo tanto: Equal = NOT(A XOR B), o bien Equal = (A AND B) OR (NOT(A) AND NOT(B))
  • A < B es 1 si y solo si: A=0 AND B=1. Por lo tanto: Less = NOT(A) AND B

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».

Paso 3: Implementación en código

Ejemplo

# Comparador de 1 bit (demostración de 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

: 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

Expresión lógica

A1A0Y0Y1Y2Y3
001000
010100
100010
110001

Ejemplo

  • Y0 = NOT(A1) AND NOT(A0)
  • Y1 = NOT(A1) AND A0
  • Y2 = A1 AND NOT(A0)
  • Y3 = A1 AND A0

Ejemplo

# Entrada: número binario de 2 bits A1 A0
# 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: la particularidad de NAND y NOR


Puertas universales: lo especial 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)

  • La velocidad de conmutación de la puerta NAND es más rápida
  • Por lo tanto, en los chips reales, la función AND se implementa internamente con NAND + NOT.
  • A continuación, demostremos la «universalidad» de la puerta NAND:

Ejemplo

Ejemplo

# La puerta NAND por sí sola puede implementar las tres funciones básicas NOT, AND y OR
"""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 de votación por mayoría (demo ejemplo vis-network)


Demostración interactiva: constructor de circuito votador 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)

Votador por mayoría de tres entradas (haz clic en los nodos A/B/C para alternar)

Nodo de entrada A
Nodo de entrada B Nodo de entrada C Puerta AND Puerta OR Salida Y Otras extensiones
IA pensando...