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.
| Operación | S1 S0 | Binv | Cin del primer bit | Resultado en cada bit |
|---|---|---|---|---|
| AND | 0 0 | 0 | no importa | A·B |
| OR | 0 1 | 0 | no importa | A + B |
| XOR | 1 0 | 0 | no importa | A ⊕ B |
| ADD | 1 1 | 0 | 0 | A + B |
| SUB | 1 1 | 1 | 1 | A + ~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:
| Función | Chip | Cantidad |
|---|---|---|
| AND de 4 bits | 74HC08 (cuatro AND) | 1 |
| OR de 4 bits | 74HC32 (cuatro OR) | 1 |
| XOR de 4 bits | 74HC86 (cuatro XOR) | 1 |
| Inversión de B (Binv) | 74HC86 (cuatro XOR) | 1 |
| Sumador de 4 bits | 74HC283 | 1 |
| Selección 4:1 por bit | 74HC153 (dos multiplexores 4:1) | 2 |
| Flags (Z con una NOR de 4 entradas, V con unas pocas puertas) | 74HC4002 y compuertas sueltas | 1 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.
| Flag | Cómo se calcula | En ADD y SUB | En AND, OR y XOR |
|---|---|---|---|
| Z | NOR de todos los bits de F | 1 si el resultado es 0 | igual: 1 si F es 0 |
| N | El bit más alto de F | signo (con signo, en Ca2) | igual: el bit más alto |
| C | Acarreo de salida del último bit | ADD: acarreo. SUB: 1 = «no hubo préstamo» (A ≥ B) | 0 (se enmascara) |
| V | Acarreo que entra al último bit XOR el que sale | 1 si el resultado con signo no entra | 0 (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):
| Condición | Significa | Lógica con los flags |
|---|---|---|
| EQ | A = B | Z = 1 |
| NE | A ≠ B | Z = 0 |
| HS | A ≥ B sin signo | C = 1 |
| LO | A < B sin signo | C = 0 |
| HI | A > B sin signo | C = 1 y Z = 0 |
| LS | A ≤ B sin signo | C = 0 o Z = 1 |
| GE | A ≥ B con signo | N = V |
| LT | A < B con signo | N ≠ V |
| GT | A > B con signo | Z = 0 y N = V |
| LE | A ≤ B con signo | Z = 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
unsignedfrente alintde 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:
| Quiero | Hago | Ejemplo con x = 0110 |
|---|---|---|
| NOT x | x XOR 1111 | 1001 |
| Poner bits en 0 | x AND (complemento de la máscara) | borrar los dos bits bajos: 0110 AND 1100 = 0100 |
| Poner bits en 1 | x OR máscara | 0110 OR 0001 = 0111 |
| Invertir bits | x XOR máscara | 0110 XOR 0011 = 0101 |
| Probar un bit | x AND máscara y mirar Z | bit 2: 0110 AND 0100 = 0100, Z = 0: está en 1 |
| Incrementar | ADD x, 1 | 0111 |
| Decrementar | ADD x, 1111 (que es −1) | 0101 |
| Cambiar de signo | SUB 0, x | 1010 (−6) |
| Multiplicar por 2 (desplazar a la izquierda) | ADD x, x | 1100, con C = el bit que salió (0 aquí) |
| Copiar | x OR 0 | 0110 |
| Poner a cero | x XOR x (o SUB x, x) | 0000, con Z = 1 |
| Comparar sin guardar | SUB x, y y mirar flags | ver 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:
| Código | Operación | S1 | S0 | Binv | Cin del primer bit |
|---|---|---|---|---|---|
| 000 | AND | 0 | 0 | 0 | 0 (no importa) |
| 001 | OR | 0 | 1 | 0 | 0 (no importa) |
| 010 | XOR | 1 | 0 | 0 | 0 (no importa) |
| 011 | ADD | 1 | 1 | 0 | 0 |
| 100 | SUB | 1 | 1 | 1 | 1 |
| 101, 110, 111 | sin asignar | X | X | X | X |
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.
Mini-ALU de 4 bits
ADD 9, 6 = 1111
ADD 9, 6: F = 1111.
Recorrido guiado (10 minutos)
- 1Mirá 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.
- 2Cambiá 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.
- 3Probá «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.
- 4Probá «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».
- 5Probá «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.
- 6Probá «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.
- 7Probá «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.
- 8Probá «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).
- 9Fijate 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.
- 10Probá «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
- Con A = 9 y B = 6, ¿qué dan AND, OR y XOR? ¿Cuál tiene Z = 1?
- Con SUB 6, 9, ¿cuánto valen C y V y qué dicen sobre sin signo y con signo?
- ¿Por qué ADD 15, 15 tiene C = 1 y V = 0?
- ¿Qué cambia en las señales de control entre ADD y SUB?
Ver respuestas sugeridas
- AND = 0000 (sin bits en común, Z = 1), OR = 1111 y XOR = 1111. Solo AND tiene Z = 1.
- 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.
- 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.
- 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
| En software | En la ALU |
|---|---|
def alu(op, a, b) con un match op | Las cuatro operaciones y el multiplexor que elige |
| Un opcode de una instrucción | El 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 je | SUB que descarta F y deja flags; la condición los combina |
int frente a unsigned al comparar | Mirar N y V (con signo) o C (sin signo) del mismo SUB |
x & mask, x | mask, x ^ mask | AND, OR y XOR de la ALU |
if (x & mask) | TST: AND que descarta F y mira Z |
x + x como x << 1 | ADD x, x, con el bit que sale en C |
| Un perfil que muestra el camino más lento | El camino crítico: el acarreo de la suma |
- 1La 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).
- 2Los 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.
- 3Una 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.