Electrónica Digital · Tema 27

ALU elemental

Un sumador-restador, unas puertas lógicas, un multiplexor y cuatro banderas. Con esas piezas del curso se arma la unidad aritmético-lógica: el bloque que calcula dentro de cualquier procesador. Un código de operación elige qué resultado sale, y las banderas avisan cómo leerlo. Es el músculo de la CPU, y el sitio donde nacen los if.

≈ 60 minutos Sin materiales: solo el navegador Incluye simulador y Python

01 · Punto de partida

Una calculadora de cinco botones

Imaginá una calculadora con dos entradas de 4 bits, A y B, un selector de operación y una salida F. El selector elige entre cinco operaciones: AND, OR, XOR, suma (ADD) y resta (SUB). Junto a F salen cuatro bits de estado que describen el resultado: Z (¿es cero?), N (¿es negativo?), C (¿hubo acarreo?) y V (¿desbordó con signo?).

Una escena típica. Un programa quiere saber si a < b. La CPU le pide a la ALU la operación SUB con esos dos valores, descarta el resultado y se queda solo con las banderas. Un salto condicional mira esos bits y decide qué rama ejecutar. Todo if de comparación que escribiste alguna vez termina en un circuito como este.

Lo bueno es que no hay nada nuevo: la suma y la resta son el sumador-restador del Tema 24, las banderas son las del Tema 24, la selección es un multiplexor (Tema 18) y las lógicas son las puertas de los primeros temas. La ALU es el momento en que las piezas se juntan.

  • ¿Cómo es una celda de un bit que hace cinco operaciones?
  • ¿Qué chips hacen falta para una ALU de 4 bits?
  • ¿Cómo se calculan Z, N, C y V, y cómo se transforman en condiciones de salto?
  • ¿Qué otras operaciones salen de estas cinco?
  • ¿Cómo se convierte un código de operación en señales de control?
  • ¿Qué parte de la ALU es la lenta?

02 · Una celda de 1 bit

Calcular todo y elegir

Una celda de un bit tiene entradas A, B y el acarreo Cin, y calcula en paralelo cuatro resultados:

  • AND: A·B
  • OR: A + B
  • XOR: A ⊕ B
  • SUMA: A ⊕ B ⊕ Cin, con acarreo de salida Cout = mayoría(A, B, Cin) (el sumador completo del Tema 23)

Un multiplexor 4:1 (Tema 18) elige uno de los cuatro con los bits de selección S1 y S0. La resta aparece sin agregar un quinto resultado: se usa la SUMA con una señal extra, Binv, que pasa B por un XOR para invertirlo (Tema 10), y con el acarreo inicial en 1.

Señales de control de la ALU de cinco operaciones.
OperaciónS1 S0BinvCin del primer bitResultado en cada bit
AND0 00no importaA·B
OR0 10no importaA + B
XOR1 00no importaA ⊕ B
ADD1 100A + B
SUB1 111A + ~B + 1 (Tema 24)
Resta = suma con dos señales distintas
SUB y ADD comparten el mismo selector (11) y el mismo sumador. Lo único que cambia son Binv y el acarreo inicial. No hace falta ningún circuito restador.
Calcular todo y elegir
Los cuatro resultados existen siempre; el multiplexor deja pasar uno. Es la misma idea que la programación sin saltos (branchless): se calculan todas las opciones y se selecciona al final, sin decidir antes qué calcular.
Un detalle del acarreo
El sumador sigue calculando aunque el multiplexor no lo haya elegido. Si la operación es lógica, su acarreo de salida no significa nada, así que hay que enmascararlo para que la bandera C sea 0 (sección 04).
Más operaciones, mismas piezas
Muchas ALU agregan señales para invertir también A o el resultado (se obtienen NAND, NOR y A·~B). Es la idea de las ALU de los libros de arquitectura: unas pocas señales de control y las mismas piezas dan muchas funciones.

03 · N bits

Chips, 74181 y retardo

Una ALU de N bits son N celdas con el acarreo encadenado (el ripple del Tema 24). Con chips estándar, los bloques son los que ya conocés:

Una ALU de 4 bits con chips estándar. Confirmá los pines en la hoja de datos de cada uno.
FunciónChipCantidad
AND de 4 bits74HC08 (cuatro AND)1
OR de 4 bits74HC32 (cuatro OR)1
XOR de 4 bits74HC86 (cuatro XOR)1
Inversión de B (Binv)74HC86 (cuatro XOR)1
Sumador de 4 bits74HC2831
Selección 4:1 por bit74HC153 (dos multiplexores 4:1)2
Flags (Z con una NOR de 4 entradas, V con unas pocas puertas)74HC4002 y compuertas sueltas1 o 2

Son unos 8 o 9 chips para una ALU de 4 bits. Contando puertas de dos entradas, cada celda lleva unas 9 (AND, OR, XOR, el sumador completo de 5 y el XOR de Binv) más su multiplexor 4:1.

La 74181, la ALU de los años 70

La 74181 es un circuito integrado TTL de 4 bits que se usó en las minicomputadoras. Elegía con cuatro entradas de selección y una de modo entre 16 funciones lógicas y 16 funciones aritméticas, y daba salidas de generate y propagate (P y G) para combinar varias con un generador de acarreo anticipado (Tema 24). Hoy se reemplaza por lógica programable, pero ilustra lo mismo: una ALU es una tabla de funciones elegida por unas pocas señales (confirmá los detalles en su hoja de datos).

El retardo: manda el sumador

Con el modelo de «un nivel por puerta» de los temas anteriores:

  • Una operación lógica pasa por una puerta y el multiplexor (unos 2 niveles): 3 niveles en total, sin importar el ancho.
  • Una suma o resta pasa por el XOR de Binv, la cadena del acarreo (2 niveles por bit) y el multiplexor: 2N + 3 niveles en un ripple de N bits.
  • Las banderas salen después de F: Z necesita un árbol de NOR sobre todos los bits, que agrega unos log4 N niveles; C sale junto con el último acarreo.

La ALU es tan rápida como su sumador. Por eso las ALU rápidas usan acarreo anticipado: las lógicas ya eran casi gratis, y el sumador es lo que se puede mejorar (Paso 6).

04 · Flags

Los ojos del programa

Además de F, la ALU entrega cuatro bits que describen el resultado. Son los mismos que viste en el Tema 24, ahora con una regla para cuando la operación es lógica.

Flags de la ALU y cómo se calculan.
FlagCómo se calculaEn ADD y SUBEn AND, OR y XOR
ZNOR de todos los bits de F1 si el resultado es 0igual: 1 si F es 0
NEl bit más alto de Fsigno (con signo, en Ca2)igual: el bit más alto
CAcarreo de salida del último bitADD: acarreo. SUB: 1 = «no hubo préstamo» (A ≥ B)0 (se enmascara)
VAcarreo que entra al último bit XOR el que sale1 si el resultado con signo no entra0 (se enmascara)

Las dos filas de la derecha (C y V en las lógicas) son una decisión de diseño: algunas CPU las ponen en 0, otras las dejan como estaban. Lo importante es que la operación lógica no deje en C y V un resto de la suma que corrió en paralelo sin ser elegida.

De los flags a las condiciones

Para comparar A con B se hace SUB y se descarta F (CMP): solo importan las banderas. Con ellas se arman las diez condiciones de un salto condicional (los nombres son los de ARM, que se leen bien):

Condiciones tras A − B (SUB). El nombre es una convención; lo importante es la lógica.
CondiciónSignificaLógica con los flags
EQA = BZ = 1
NEA ≠ BZ = 0
HSA ≥ B sin signoC = 1
LOA < B sin signoC = 0
HIA > B sin signoC = 1 y Z = 0
LSA ≤ B sin signoC = 0 o Z = 1
GEA ≥ B con signoN = V
LTA < B con signoN ≠ V
GTA > B con signoZ = 0 y N = V
LEA ≤ B con signoZ = 1 o N ≠ V
El mismo SUB, dos lecturas
Un solo cálculo responde a la vez «¿es menor sin signo?» (C) y «¿es menor con signo?» (N y V). El programa elige qué condición mirar según el tipo de dato: es el unsigned frente al int de los lenguajes (Tema 25).
Igualdad y prueba de bits
EQ y NE usan solo Z. Una variante de CMP es TST: un AND que descarta el resultado y deja Z, para ver si algún bit de una máscara está en 1.
Sin flags no hay if
Cada salto condicional de una CPU lee uno o dos de estos bits. Los lenguajes de alto nivel solo disfrazan esa lógica.

05 · Operaciones derivadas

De cinco operaciones salen muchas

Con AND, OR, XOR, ADD y SUB (y unas constantes) se obtienen las demás operaciones comunes sin agregar hardware:

Operaciones derivadas con la ALU de 4 bits.
QuieroHagoEjemplo con x = 0110
NOT xx XOR 11111001
Poner bits en 0x AND (complemento de la máscara)borrar los dos bits bajos: 0110 AND 1100 = 0100
Poner bits en 1x OR máscara0110 OR 0001 = 0111
Invertir bitsx XOR máscara0110 XOR 0011 = 0101
Probar un bitx AND máscara y mirar Zbit 2: 0110 AND 0100 = 0100, Z = 0: está en 1
IncrementarADD x, 10111
DecrementarADD x, 1111 (que es −1)0101
Cambiar de signoSUB 0, x1010 (−6)
Multiplicar por 2 (desplazar a la izquierda)ADD x, x1100, con C = el bit que salió (0 aquí)
Copiarx OR 00110
Poner a cerox XOR x (o SUB x, x)0000, con Z = 1
Comparar sin guardarSUB x, y y mirar flagsver sección 04

Sumar un número a sí mismo duplica su valor, que en binario es desplazar un lugar a la izquierda: el bit que se pierde por arriba es el acarreo C. Los desplazamientos más largos y las rotaciones suelen hacerse con otro bloque, el shifter, que es una red de multiplexores (Tema 18).

06 · Control

Decodificar el código de operación

El programa no manda S1, S0, Binv y Cin por separado: manda un código de operación (opcode) y un decodificador lo traduce en señales de control. Con tres bits (o2 o1 o0) alcanzan para las cinco operaciones:

Del código de operación a las señales de control. X = código sin asignar (no importa).
CódigoOperaciónS1S0BinvCin del primer bit
000AND0000 (no importa)
001OR0100 (no importa)
010XOR1000 (no importa)
011ADD1100
100SUB1111
101, 110, 111sin asignarXXXX

Es un problema de diseño combinacional como los del Tema 17, con condiciones de «no importa» (Tema 6). Los mapas de Karnaugh (Tema 16) dan unas ecuaciones muy cortas:

  • El decodificador completo son dos puertas OR; Binv y el acarreo inicial son el cable de o2.
  • Los tres códigos sin asignar, al usar «no importa», terminan comportándose como SUB (S1 = S0 = Binv = Cin = 1). Es una consecuencia de minimizar: si alguna vez se ejecutara un código ilegal, haría una resta, sin avisar.

07 · Simulador

Laboratorio: la mini-ALU de 4 bits

El simulador muestra las cuatro celdas con el bit más alto a la izquierda. Cada celda calcula AND, OR, XOR y SUMA a la vez, y el multiplexor resalta cuál sale. Con dos deslizadores elegís A y B, con los botones elegís la operación, y a la derecha ves las cuatro banderas y las señales de control. Además, una tarjeta enumera qué condiciones de salto serían verdaderas si hubieras hecho una resta.

  • Fila verde en cada celda: el resultado elegido por el multiplexor (el que pasa a F).
  • Acarreo: los cables entre celdas solo importan en ADD y SUB; en las lógicas el sumador sigue calculando pero su acarreo se ve atenuado.
  • Flags: Z, N, C y V; en naranja si V indica que el resultado con signo no entra.
  • Condiciones: EQ, NE, HS, LO, HI, LS, GE, LT, GT y LE. Tienen sentido después de SUB; con otras operaciones se muestran solo a título ilustrativo.
SIMULADOR 27

Mini-ALU de 4 bits

ADD 9, 6 = 1111

ALU de cuatro bits Cuatro celdas, una por bit, desde el bit 3 a la izquierda. Cada celda calcula AND, OR, XOR y la suma y un multiplexor elige una. Los cables de acarreo unen las celdas. A la derecha están las banderas Z, N, C y V y las señales de control. CUATRO CELDAS: SE CALCULA TODO Y UN MULTIPLEXOR ELIGE
F (resultado)—
Flags Z N C V—
Control: S1S0 Binv Cin0—
Condiciones verdaderas—

ADD 9, 6: F = 1111.

Recorrido guiado (10 minutos)

  1. 1
    Mirá el estado inicial: ADD 9, 6.

    Cada celda calcula sus cuatro resultados; el multiplexor deja pasar la fila SUMA. F = 1111 (15 sin signo, −1 con signo). Z = 0, N = 1, C = 0 y V = 0: −7 + 6 = −1 cabe.

  2. 2
    Cambiá a AND sin tocar A ni B.

    El multiplexor ahora elige la fila AND: 1001 AND 0110 = 0000. Z = 1. Los acarreos siguen calculándose pero se ven atenuados y C vale 0: la ALU enmascara lo que no corresponde.

  3. 3
    Probá «XOR 7, 7» y luego «ADD 7, 7».

    XOR da 0000 (Z = 1): dos valores iguales se anulan. ADD da 1110 con carries encadenados, y con signo desborda: 7 + 7 = 14 no entra en −8..7, así que V = 1 y N = 1.

  4. 4
    Probá «SUB 5, 5».

    F = 0000, Z = 1, C = 1 (no hubo préstamo). En condiciones verdaderas aparecen EQ, HS, LS, GE y LE: son iguales, y también «mayor o igual» y «menor o igual».

  5. 5
    Probá «SUB 6, 9».

    F = 1101, C = 0: préstamo, sin signo 6 < 9 (LO, LS). Pero N y V valen 1 los dos: con signo 9 es −7, y 6 > −7 (GE, GT). El mismo cálculo responde distinto según el tipo de dato.

  6. 6
    Probá «SUB 9, 6».

    F = 0011, C = 1: sin signo 9 > 6 (HS, HI). Con signo es −7 < 6: V = 1 y N = 0, N ≠ V, así que LT y LE. Al revés del caso anterior.

  7. 7
    Probá «ADD 15, 1».

    F = 0000 y C = 1: sin signo el resultado se pasó. Z también vale 1. Con signo es −1 + 1 = 0 y V = 0: no hay overflow con signo.

  8. 8
    Probá «AND 9, 8», y después cambiá B a 2.

    9 AND 8 = 1000, Z = 0: el bit 3 de A está en 1. Con B = 2 (0010) da 0000 y Z = 1: el bit 1 de A está en 0. Es la prueba de bit con una máscara (TST).

  9. 9
    Fijate en la tarjeta de control con ADD y con SUB.

    Las dos usan S1S0 = 11. La resta cambia Binv y Cin0 de 0 a 1: B se invierte en cada celda y el acarreo inicial entra en 1. Es el sumador-restador del Tema 24.

  10. 10
    Probá «ADD 15, 15» y mirá V, C y N.

    F = 1110 (con signo −2), C = 1 (sin signo se pasó: 30), pero V = 0: −1 + −1 = −2 cabe. C y V cuentan historias distintas del mismo resultado.

Preguntas para pensar

  1. Con A = 9 y B = 6, ¿qué dan AND, OR y XOR? ¿Cuál tiene Z = 1?
  2. Con SUB 6, 9, ¿cuánto valen C y V y qué dicen sobre sin signo y con signo?
  3. ¿Por qué ADD 15, 15 tiene C = 1 y V = 0?
  4. ¿Qué cambia en las señales de control entre ADD y SUB?
Ver respuestas sugeridas
  1. AND = 0000 (sin bits en común, Z = 1), OR = 1111 y XOR = 1111. Solo AND tiene Z = 1.
  2. C = 0 (préstamo: 6 < 9 sin signo) y V = 1 (con signo 9 = −7 y 6 − (−7) = 13 no entra). Con signo, N = V y 6 es mayor que −7.
  3. 1111 + 1111 = 11110 en 5 bits: el acarreo de salida es 1 (sin signo 30 no entra en 4 bits). Con signo es −1 + (−1) = −2, que sí entra en −8..7, así que no hay overflow.
  4. Binv pasa de 0 a 1 y el acarreo inicial pasa de 0 a 1. S1S0 queda igual (11): el mismo sumador, con B invertido y el +1.

08 · ALU en código

Celda, ALU de 4 bits, condiciones, operaciones derivadas, control y retardo

Los bloques arman la celda de 1 bit con señales de control y la verifican, construyen la ALU de 4 bits con las cuatro banderas y la prueban contra la aritmética de enteros en las 256 parejas y las cinco operaciones, comprueban las diez condiciones de salto contra los operadores de comparación, derivan operaciones comunes, decodifican el opcode con dos puertas OR y calculan el camino crítico con el modelo de niveles.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

Paso 1: la celda de un bit

from itertools import product

# (S1, S0, Binv, Cin del primer bit)
CONTROL = {"AND": (0, 0, 0, 0), "OR": (0, 1, 0, 0), "XOR": (1, 0, 0, 0),
           "ADD": (1, 1, 0, 0), "SUB": (1, 1, 1, 1)}


def celda(a, b, cin, s1, s0, binv):
    be = b ^ binv                                   # XOR de inversión de B
    suma = a ^ be ^ cin
    cout = (a & be) | (a & cin) | (be & cin)
    elegido = (a & be, a | be, a ^ be, suma)[2 * s1 + s0]    # el multiplexor 4:1
    return elegido, cout


print("operación  control (S1 S0 Binv Cin0)  con A=1, B=0")
for op, (s1, s0, binv, cin0) in CONTROL.items():
    f, c = celda(1, 0, cin0, s1, s0, binv)
    print(f"{op:9s}  {s1} {s0}  {binv}    {cin0}                  F={f} Cout={c}")

ok = True
for a, b in product((0, 1), repeat=2):
    ok = ok and celda(a, b, 0, 0, 0, 0)[0] == (a & b)
    ok = ok and celda(a, b, 0, 0, 1, 0)[0] == (a | b)
    ok = ok and celda(a, b, 0, 1, 0, 0)[0] == (a ^ b)
    ok = ok and celda(a, b, 0, 1, 1, 0) == ((a + b) % 2, (a + b) // 2)
    ok = ok and celda(a, b, 1, 1, 1, 1) == ((a + (1 - b) + 1) % 2, (a + (1 - b) + 1) // 2)
print()
print("la celda coincide con AND, OR, XOR, ADD y SUB (A + ~B + 1) en las 4 combinaciones:", ok)

Con A = 1 y B = 0: AND da 0, OR 1, XOR 1, ADD 1 sin acarreo y SUB 1 con acarreo 1 (1 + 1 + 1 = 11). SUB es la misma fila de la suma con Binv y Cin0 en 1.

Paso 2: la ALU de 4 bits con banderas

from itertools import product

CONTROL = {"AND": (0, 0, 0, 0), "OR": (0, 1, 0, 0), "XOR": (1, 0, 0, 0),
           "ADD": (1, 1, 0, 0), "SUB": (1, 1, 1, 1)}


def alu(op, a, b, n=4):
    mascara = (1 << n) - 1
    s1, s0, binv, cin = CONTROL[op]
    be = (b ^ (mascara if binv else 0)) & mascara
    f, car = 0, []
    for i in range(n):
        ai, bi = (a >> i) & 1, (be >> i) & 1
        suma = ai ^ bi ^ cin
        cout = (ai & bi) | (ai & cin) | (bi & cin)
        f |= ((ai & bi, ai | bi, ai ^ bi, suma)[2 * s1 + s0]) << i
        car.append(cout)
        cin = cout
    sumador = (s1, s0) == (1, 1)
    msb = n - 1
    return {"F": f, "Z": int(f == 0), "N": (f >> msb) & 1,
            "C": car[msb] if sumador else 0,
            "V": (car[msb - 1] ^ car[msb]) if sumador else 0}


con_signo = lambda x: x - 16 if x >= 8 else x


def esperado(op, a, b):
    if op == "AND":
        f, c, v = a & b, 0, 0
    elif op == "OR":
        f, c, v = a | b, 0, 0
    elif op == "XOR":
        f, c, v = a ^ b, 0, 0
    elif op == "ADD":
        t = a + b
        f, c, v = t & 15, t >> 4, int(not -8 <= con_signo(a) + con_signo(b) <= 7)
    else:
        f, c, v = (a - b) & 15, int(a >= b), int(not -8 <= con_signo(a) - con_signo(b) <= 7)
    return {"F": f, "Z": int(f == 0), "N": (f >> 3) & 1, "C": c, "V": v}


for op in CONTROL:
    bien = all(alu(op, a, b) == esperado(op, a, b) for a, b in product(range(16), repeat=2))
    print(f"{op:3s} coincide con la aritmética de enteros en las 256 parejas (F, Z, N, C, V): {bien}")

print()
for op, a, b in (("ADD", 9, 6), ("SUB", 6, 9), ("AND", 9, 6), ("ADD", 7, 7), ("XOR", 7, 7), ("SUB", 5, 5)):
    r = alu(op, a, b)
    print(f"{op:3s} {a:2d},{b:2d} -> F={r['F']:04b} ({r['F']:2d})  Z={r['Z']} N={r['N']} C={r['C']} V={r['V']}")

Las cinco operaciones coinciden con los enteros en las 256 parejas, banderas incluidas. ADD 9, 6 da 1111 sin carry ni overflow; SUB 6, 9 da 1101 con C = 0 (préstamo) y V = 1; ADD 7, 7 da 1110 con V = 1; AND 9, 6 y XOR 7, 7 dan 0 con Z = 1; SUB 5, 5 da 0 con Z = 1 y C = 1.

Paso 3: las diez condiciones de salto

from itertools import product


def sub_flags(a, b):
    be = (~b) & 15
    cin, f, car = 1, 0, []
    for i in range(4):
        ai, bi = (a >> i) & 1, (be >> i) & 1
        f |= (ai ^ bi ^ cin) << i
        cin = (ai & bi) | (ai & cin) | (bi & cin)
        car.append(cin)
    return {"Z": int(f == 0), "N": (f >> 3) & 1, "C": car[3], "V": car[2] ^ car[3]}


CONDICIONES = {
    "EQ": lambda r: r["Z"] == 1,
    "NE": lambda r: r["Z"] == 0,
    "HS": lambda r: r["C"] == 1,
    "LO": lambda r: r["C"] == 0,
    "HI": lambda r: r["C"] == 1 and r["Z"] == 0,
    "LS": lambda r: r["C"] == 0 or r["Z"] == 1,
    "GE": lambda r: r["N"] == r["V"],
    "LT": lambda r: r["N"] != r["V"],
    "GT": lambda r: r["Z"] == 0 and r["N"] == r["V"],
    "LE": lambda r: r["Z"] == 1 or r["N"] != r["V"],
}
con_signo = lambda x: x - 16 if x >= 8 else x
REFERENCIA = {
    "EQ": lambda a, b: a == b, "NE": lambda a, b: a != b,
    "HS": lambda a, b: a >= b, "LO": lambda a, b: a < b,
    "HI": lambda a, b: a > b, "LS": lambda a, b: a <= b,
    "GE": lambda a, b: con_signo(a) >= con_signo(b), "LT": lambda a, b: con_signo(a) < con_signo(b),
    "GT": lambda a, b: con_signo(a) > con_signo(b), "LE": lambda a, b: con_signo(a) <= con_signo(b),
}

for nombre in CONDICIONES:
    bien = all(CONDICIONES[nombre](sub_flags(a, b)) == REFERENCIA[nombre](a, b)
               for a, b in product(range(16), repeat=2))
    print(f"{nombre}: {bien}", end="   ")
print()
print()
for a, b in ((5, 5), (6, 9), (9, 6), (8, 1)):
    r = sub_flags(a, b)
    verdaderas = [n for n, f in CONDICIONES.items() if f(r)]
    print(f"SUB {a:2d}, {b:2d}  flags Z={r['Z']} N={r['N']} C={r['C']} V={r['V']}  -> {', '.join(verdaderas)}")

Las diez condiciones coinciden con los operadores de comparación (sin signo y con signo) en las 256 parejas. SUB 5, 5 cumple EQ, HS, LS, GE y LE; SUB 6, 9 cumple NE, LO, LS, GE y GT (sin signo menor, con signo mayor porque 9 es −7); SUB 9, 6 y SUB 8, 1 cumplen NE, HS, HI, LT y LE.

Paso 4: operaciones derivadas

CONTROL = {"AND": (0, 0, 0, 0), "OR": (0, 1, 0, 0), "XOR": (1, 0, 0, 0),
           "ADD": (1, 1, 0, 0), "SUB": (1, 1, 1, 1)}


def alu(op, a, b):
    s1, s0, binv, cin = CONTROL[op]
    be = (b ^ (15 if binv else 0)) & 15
    f, car = 0, []
    for i in range(4):
        ai, bi = (a >> i) & 1, (be >> i) & 1
        suma = ai ^ bi ^ cin
        cin = (ai & bi) | (ai & cin) | (bi & cin)
        f |= ((ai & bi, ai | bi, ai ^ bi, suma)[2 * s1 + s0]) << i
        car.append(cin)
    sumador = (s1, s0) == (1, 1)
    return {"F": f, "Z": int(f == 0), "C": car[3] if sumador else 0}


R = range(16)
comprobaciones = {
    "NOT x = XOR x, 1111": all(alu("XOR", x, 15)["F"] == (~x) & 15 for x in R),
    "poner bits en 0: AND x, ~máscara": all(alu("AND", x, ~m & 15)["F"] == x & ~m & 15 for x in R for m in R),
    "poner bits en 1: OR x, máscara": all(alu("OR", x, m)["F"] == x | m for x in R for m in R),
    "invertir bits: XOR x, máscara": all(alu("XOR", x, m)["F"] == x ^ m for x in R for m in R),
    "probar bits: AND x, máscara y mirar Z": all(alu("AND", x, m)["Z"] == int(x & m == 0) for x in R for m in R),
    "INC: ADD x, 1": all(alu("ADD", x, 1)["F"] == (x + 1) & 15 for x in R),
    "DEC: ADD x, 1111": all(alu("ADD", x, 15)["F"] == (x - 1) & 15 for x in R),
    "NEG: SUB 0, x": all(alu("SUB", 0, x)["F"] == (-x) & 15 for x in R),
    "2x: ADD x, x (y C es el bit que sale)": all(alu("ADD", x, x)["F"] == (2 * x) & 15 and alu("ADD", x, x)["C"] == x >> 3 for x in R),
    "COPIAR: OR x, 0": all(alu("OR", x, 0)["F"] == x for x in R),
    "CERO: XOR x, x (con Z = 1)": all(alu("XOR", x, x)["F"] == 0 and alu("XOR", x, x)["Z"] == 1 for x in R),
}
for nombre, bien in comprobaciones.items():
    print(f"{nombre:<40} {bien}")

print()
x = 0b0110
print(f"x = {x:04b}: NOT={alu('XOR', x, 15)['F']:04b}  NEG={alu('SUB', 0, x)['F']:04b}  2x={alu('ADD', x, x)['F']:04b} (C={alu('ADD', x, x)['C']})")
x = 0b1001
print(f"x = {x:04b}: 2x={alu('ADD', x, x)['F']:04b} (C={alu('ADD', x, x)['C']})  -> el 1 que sale por arriba queda en C")

Las once identidades se cumplen para todos los valores. Con x = 0110: NOT da 1001, NEG da 1010 (−6) y 2x da 1100 sin acarreo; con x = 1001, 2x da 0010 con C = 1: el bit más alto que se pierde queda en el carry.

Paso 5: decodificar el código de operación

CONTROL = {"AND": (0, 0, 0, 0), "OR": (0, 1, 0, 0), "XOR": (1, 0, 0, 0),
           "ADD": (1, 1, 0, 0), "SUB": (1, 1, 1, 1)}
CODIGO = {"AND": 0, "OR": 1, "XOR": 2, "ADD": 3, "SUB": 4}


def decodificar(op3):
    o2, o1, o0 = (op3 >> 2) & 1, (op3 >> 1) & 1, op3 & 1
    return (o2 | o1, o0 | o2, o2, o2)              # S1 = o2+o1, S0 = o0+o2, Binv = Cin = o2


print("código  operación  (S1, S0, Binv, Cin0)   esperado")
for nombre, c in CODIGO.items():
    print(f"  {c:03b}   {nombre:9s}  {decodificar(c)}       {CONTROL[nombre]}")
print("el decodificador de dos OR coincide con la tabla:", all(decodificar(c) == CONTROL[n] for n, c in CODIGO.items()))
print()
for c in (5, 6, 7):
    print(f"código sin asignar {c:03b} -> {decodificar(c)}  (se comporta como SUB: {decodificar(c) == CONTROL['SUB']})")

print()
print("bits  puertas de las celdas  multiplexores 4:1  puertas de flags")
for n in (4, 8, 16, 32):
    print(f"{n:4d}  {9 * n:21d}  {n:17d}  {n + 2:15d}")

Las ecuaciones S1 = o2 + o1, S0 = o0 + o2 y Binv = Cin = o2 reproducen la tabla: el decodificador son dos puertas OR. Los códigos 101, 110 y 111, no asignados, se comportan como SUB porque se minimizó con «no importa». Los costos crecen linealmente con el ancho (9 puertas por celda).

Paso 6: el camino crítico

def niveles_arbol4(n):
    k, niveles = 1, 0
    while k < n:
        k *= 4
        niveles += 1
    return niveles


print("modelo: un nivel por puerta; multiplexor 4:1 = 2 niveles; acarreo = 2 niveles por bit")
print()
print("bits  F lógica  F suma/resta  acarreo C  Z (tras F)")
for n in (4, 8, 16, 32):
    f_logica = 1 + 2                              # una puerta y el multiplexor
    f_suma = 1 + 2 * n + 2                        # XOR de Binv, cadena de acarreo, multiplexor
    c = 1 + (1 + 2 * n)                           # XOR de Binv y acarreo final
    z = f_suma + niveles_arbol4(n)                # árbol de NOR de 4 entradas
    print(f"{n:4d}  {f_logica:8d}  {f_suma:12d}  {c:9d}  {z:10d}")

print()
print("la suma domina: la fórmula es 2N + 3 niveles para F y 2N + 2 para C; las lógicas no dependen de N")

Las operaciones lógicas tardan 3 niveles sin importar el ancho; la suma y la resta, 2N + 3 (11 niveles con 4 bits y 67 con 32). Z agrega unos pocos niveles de árbol después de F. La ALU es tan rápida como su sumador.

09 · Analogía de programador

Una función con un switch y un registro de estado

El mismo concepto, en dos mundos.
En softwareEn la ALU
def alu(op, a, b) con un match opLas cuatro operaciones y el multiplexor que elige
Un opcode de una instrucciónEl código de operación que se decodifica en S1, S0, Binv y Cin
Calcular las dos ramas y elegir al final (branchless, cmov)La ALU calcula todo en paralelo y el mux deja pasar uno
Un registro de estado (eflags, NZCV)Las banderas Z, N, C y V
cmp seguido de jl, jb o jeSUB que descarta F y deja flags; la condición los combina
int frente a unsigned al compararMirar N y V (con signo) o C (sin signo) del mismo SUB
x & mask, x | mask, x ^ maskAND, OR y XOR de la ALU
if (x & mask)TST: AND que descarta F y mira Z
x + x como x << 1ADD x, x, con el bit que sale en C
Un perfil que muestra el camino más lentoEl camino crítico: el acarreo de la suma
  1. 1
    La ALU no decide, ejecuta.

    El opcode elige qué hacer; la ALU calcula sin saber para qué. Decidir qué instrucción viene es trabajo del control, que se arma con circuitos secuenciales (los temas que siguen).

  2. 2
    Los flags son la salida secundaria.

    Como un código de retorno que el siguiente comando consulta, pero sin que nadie lo pida: se calcula siempre, y el que quiere lo mira.

  3. 3
    Una comparación es una resta que tira el resultado.

    El cálculo de un if (a < b) es el de una resta; lo que importa son los bits de estado, no la diferencia.

Una diferencia con el software: en un programa, match evalúa solo la rama elegida y las otras no cuestan nada. En la ALU todas las ramas trabajan siempre, aunque sus resultados se descarten. Eso es lo que la hace simple, y también lo que explica por qué consume energía aunque no la uses.

10 · Comprensión

Confusiones frecuentes

«La ALU es el procesador»

Es solo el bloque que calcula. Faltan los registros, el contador de programa y el control que decide qué instrucción ejecutar. La ALU es el músculo, no el cerebro.

«Restar necesita un circuito restador»

Se resta con el mismo sumador: un XOR para invertir B (Binv) y el acarreo inicial en 1. Dos señales de control distintas y nada más.

«Los flags son decorativos»

Cada salto condicional lee uno o dos de ellos. Sin flags no habría comparaciones, y por lo tanto, ningún if, ningún bucle con condición.

«C en una resta es un acarreo como en la suma»

En SUB, C = 1 significa «no hubo préstamo» (A ≥ B sin signo), y C = 0, que sí lo hubo. Leerlo igual que en ADD es el error clásico. (Algunas arquitecturas invierten esta convención: mirá el manual de la que uses.)

«C y V avisan lo mismo»

C habla del resultado sin signo y V del resultado con signo. Con ADD 15, 15 el resultado se pasó sin signo (C = 1) y cabe con signo (V = 0). Para comparar con signo se mira N y V; sin signo, C.

«Una operación lógica deja los flags como estaban»

Depende de la CPU: algunas limpian C y V, otras los conservan. Lo que no debe pasar es que un resto del sumador (que corre sin ser elegido) quede en C o V. Hay que decidir y documentar la política.

«Las lógicas son rápidas y la suma lenta porque sí»

Es la cadena del acarreo: cada bit espera al anterior. Una operación lógica pasa por una puerta y el multiplexor; una suma, por 2N niveles más. Se acelera con acarreo anticipado, no con nada especial en las lógicas.

11 · Práctica guiada

Ejercicios de operación

Ejercicio 1: la celda con A = 1, B = 0

Para cada operación de la tabla de control, con A = 1 y B = 0 (usá el Cin0 de la tabla), ¿cuánto valen F y Cout?

Ver solución razonada

AND 0, OR 1, XOR 1 (el acarreo de salida no se usa). ADD (Cin0 = 0): 1 + 0 + 0 → F = 1, Cout = 0. SUB (Binv = 1, Cin0 = 1): B pasa a 1 y se suma 1 + 1 + 1 = 11, así que F = 1 y Cout = 1. Verificalo con el Paso 1.

Ejercicio 2: los flags de 5 − 5

Calculá SUB 5, 5. ¿Cuánto valen F, Z, N, C y V? ¿Qué condiciones del cuadro se cumplen?

Ver solución razonada

F = 0000, Z = 1, N = 0, C = 1, V = 0. Se cumplen EQ (Z = 1), HS (C = 1), LS (C = 0 o Z = 1), GE (N = V) y LE (Z = 1). Un salto «si es igual» tomaría la rama.

Ejercicio 3: decodificar con «no importa»

Con la tabla de control de la sección 06, escribí S1, S0 y Binv en función de o2 o1 o0, usando los códigos 101 a 111 como «no importa». ¿Cuántas puertas hacen falta?

Ver solución razonada

S1 vale 1 en 010, 011 y 100 y 0 en 000 y 001: S1 = o2 + o1. S0 vale 1 en 001, 011 y 100 y 0 en 000 y 010: S0 = o0 + o2. Binv y el acarreo inicial valen 1 solo en 100: o2 directo. Son 2 puertas OR. Los códigos 101 a 111 quedan como SUB.

Ejercicio 4: probar un bit

¿Cómo se prueba, con la ALU, si el bit 2 de A = 1011 está en 1? ¿Y cómo se pone en 1, o se lo invierte?

Ver solución razonada

Máscara 0100. AND A, 0100 da 0000, así que Z = 1: el bit 2 está en 0. Para ponerlo en 1: OR A, 0100 da 1111. Para invertirlo: XOR A, 0100 también da 1111 (estaba en 0). Es el if (a & 4), a |= 4 y a ^= 4 de C.

Ejercicio 5: tres operaciones derivadas

Con x = 0110, obtené 2x, −x y NOT x usando solo las cinco operaciones.

Ver solución razonada

2x = ADD x, x = 1100. −x = SUB 0, x = 0000 − 0110 = 1010 (que es −6 en complemento a dos). NOT x = XOR x, 1111 = 1001. Comprobalo con el Paso 4.

Ejercicio 6: condiciones tras 8 − 1

Hacé SUB 1000, 0001 (8 − 1 sin signo, o −8 − 1 con signo). ¿Cuánto valen F y las banderas? ¿Qué condiciones se cumplen?

Ver solución razonada

F = 0111 (7), Z = 0, N = 0, C = 1, V = 1. Sin signo: 8 ≥ 1, se cumplen HS y HI (y NE). Con signo: −8 − 1 = −9 no entra, V = 1 con N ≠ V: LT y LE (−8 < 1). El F = 0111 que se vería como «7» es engañoso con signo: por eso hay que mirar V. Verificalo con el Paso 3.

12 · Síntesis

Ideas para recordar

  • La celda de un bit calcula AND, OR, XOR y SUMA a la vez; un multiplexor 4:1 elige con S1 y S0. Se calcula todo y se elige.
  • SUB = la suma con B invertido (XOR con Binv) y Cin del primer bit en 1. Un solo sumador hace ADD y SUB; solo cambian Binv y el acarreo inicial.
  • Una ALU de 4 bits son unos 8 o 9 chips: AND, OR, XOR, el XOR de Binv, un 283 y dos 153 más los flags. Cada celda lleva unas 9 puertas más su multiplexor.
  • Flags: Z (resultado cero), N (bit más alto), C (acarreo; en SUB, 1 = no hubo préstamo) y V (acarreo que entra XOR el que sale). En las lógicas, C y V se enmascaran a 0.
  • Un CMP es un SUB sin guardar F; con Z, N, C y V se arman diez condiciones (EQ, NE, HS, LO, HI, LS, GE, LT, GT, LE). C decide sin signo, N y V con signo.
  • Con cinco operaciones salen NOT, poner bits en 0 o 1, invertirlos, probar un bit, incrementar, decrementar, negar, duplicar y poner a cero.
  • El opcode se decodifica con minimización: S1 = o2 + o1, S0 = o0 + o2 y Binv = Cin = o2. Los códigos sin asignar terminan siendo SUB.
  • El camino crítico es el acarreo: las lógicas tardan 3 niveles y la suma o resta, 2N + 3.

¿Listo para el Tema 28?

Podés avanzar si sos capaz de responder esto sin mirar:

  • Dibujar una celda de 1 bit que calcula AND, OR, XOR y suma, y decir cómo se obtiene SUB.
  • Listar los chips de una ALU de 4 bits.
  • Calcular Z, N, C y V de una operación, y explicar qué pasa con C y V en las lógicas.
  • Armar las condiciones EQ, HS, LO, GE y LT con los flags.
  • Obtener NOT, 2x y −x con las cinco operaciones.
  • Decodificar un código de operación de tres bits con dos puertas.

Hasta acá asumimos que cada puerta responde al instante. En la realidad no: las señales llegan por caminos distintos y la salida puede parpadear un momento antes de estabilizarse. En el próximo tema, hazards y glitches, vas a ver cuándo ocurre y cómo evitarlo.