01 · Punto de partida
Un intérprete en hardware
Si alguna vez escribiste un intérprete de bytecode, ya conocés la estructura de una CPU: un bucle que lee la próxima instrucción de una lista, decide qué operación representa y la ejecuta, y vuelve a empezar. La CPU de un microcontrolador hace exactamente eso, pero con puertas lógicas, flip-flops y contadores, los mismos bloques que construiste en los cursos anteriores.
En el tema 3 vimos sus componentes a vuelo de pájaro. Ahora los ponemos a trabajar: seguimos instrucción por instrucción un programa pequeño, con códigos de operación reales del AVR, y observamos cómo cambian el contador de programa, los registros y las banderas.
Retomamos el contador y los registros de los cursos anteriores, y la idea del tema 1: un programa es una secuencia de pasos que lee, decide y actúa.
02 · El mapa
El camino de una instrucción
Una instrucción es una palabra de 16 bits guardada en la Flash. Para ejecutarla recorre este camino:
El contador de programa (PC) apunta a la instrucción siguiente; la Flash la entrega; el registro de instrucción la retiene; el decodificador la interpreta y genera las señales de control que dicen a los registros y a la ALU qué hacer; la ALU calcula y deja marcas en el registro de estado.
Es un circuito secuencial síncrono más: el estado vive en el PC, en los registros y en el SREG, y cada flanco de reloj los actualiza.
03 · El estado de la CPU
Los registros de la CPU
| Registro | Tamaño | Función |
|---|---|---|
| R0 a R31 | 32 × 8 bits | Operandos y resultados; las operaciones con constantes usan R16 a R31 |
| PC (contador de programa) | 14 bits en el ATmega328P | Dirección, en palabras de 16 bits, de la próxima instrucción |
| SP (puntero de pila) | 16 bits | Dirección de la cima de la pila en SRAM |
| SREG (registro de estado) | 8 bits | Banderas del último resultado y habilitación de interrupciones |
| X, Y, Z | 16 bits (pares de registros) | Punteros para acceder a memoria: X = R27:R26, Y = R29:R28, Z = R31:R30 |
Los 32 registros de trabajo son la memoria más rápida del chip: la ALU opera directamente sobre ellos, sin pasar por la SRAM. Por eso el compilador intenta mantener ahí las variables que más se usan.
Las banderas del SREG
| Bit | Nombre | Se pone en 1 cuando… |
|---|---|---|
| 7 | I (interrupciones) | Las interrupciones están habilitadas globalmente |
| 6 | T (transferencia) | Almacén temporal de un bit, usado por instrucciones especiales |
| 5 | H (semiacarreo) | Hubo acarreo entre los bits 3 y 4; útil en aritmética BCD |
| 4 | S (signo) | Resulta de N ⊕ V: indica el signo verdadero en aritmética con signo |
| 3 | V (desbordamiento) | Una operación con signo se salió del rango |
| 2 | N (negativo) | El bit 7 del resultado es 1 |
| 1 | Z (cero) | El resultado fue exactamente cero |
| 0 | C (acarreo) | Hubo acarreo (suma) o préstamo (resta) fuera del bit 7 |
En este tema usamos tres: Z, N y C. Las banderas son la memoria a corto plazo de la CPU: guardan «cómo salió» la última operación para que la instrucción siguiente decida.
04 · El ritmo
El ciclo de instrucción
Cada instrucción pasa por tres fases:
- Buscar (fetch). La CPU envía el PC a la Flash, recibe la palabra de 16 bits y la guarda en el registro de instrucción. El PC se incrementa para apuntar a la siguiente.
- Decodificar (decode). Ciertos bits de la palabra identifican la operación y otros los operandos. El decodificador activa las señales correspondientes.
- Ejecutar (execute). La ALU opera, se escribe el resultado en un registro, en memoria o en un puerto, y se actualizan las banderas. Si la instrucción es un salto, el PC recibe una dirección nueva.
Veamos una instrucción real. La palabra 0x0F10 en binario es 0000 1111 0001 0000. El formato de ADD Rd, Rr es 0000 11rd dddd rrrr, donde d y r son números de registro de cinco bits (el bit alto de cada uno está en las posiciones 9 y 8). Aplicado:
- Los seis bits altos
000011identifican la operación ADD. - El destino es
d = 1 0001 = 17y el origenr = 1 0000 = 16. - Resultado:
ADD R17, R16, es decir, R17 = R17 + R16.
Una instrucción no es texto sino un patrón de bits: parte es el código de operación y parte son los operandos. El ensamblador (tema 7) convierte nombres como ADD en esos patrones.
Un pipeline de dos etapas
El AVR no espera a terminar una instrucción para buscar la siguiente: mientras ejecuta una, ya está buscando la que viene. Es posible gracias a la arquitectura Harvard del tema 3 (buses separados para programa y datos). Por eso la mayoría de las instrucciones se completa en un solo ciclo de reloj. Cuando hay un salto, la instrucción que se había buscado de más se descarta y el salto cuesta un ciclo extra.
05 · Calcular
La ALU y las banderas
La unidad aritmético-lógica combina dos operandos de 8 bits y produce un resultado de 8 bits más las banderas. Es un circuito combinacional: la suma se hace con sumadores completos encadenados, como en el curso de electrónica digital, y las operaciones lógicas con puertas.
Como el resultado tiene solo 8 bits, puede no caber. Sumemos 200 + 100:
+ 0110 0100 (100)
= 1 0010 1100 → resultado: 0010 1100 (44), acarreo C = 1
El noveno bit, el acarreo, no se pierde: queda en la bandera C. Con ella se encadenan sumas de más de 8 bits (la instrucción ADC suma con acarreo), que es lo que vimos en el tema 2 al sumar enteros de 32 bits en una CPU de 8 bits.
Otras banderas son más simples: Z vale 1 cuando el resultado es 0, y N copia el bit 7. La instrucción DEC (restar uno) pone Z en 1 cuando llega a cero, y con eso se construyen los bucles.
06 · Cambiar el rumbo
Saltos y bucles
Si el PC solo se incrementara, el programa correría de arriba abajo una vez y nada más. Las instrucciones de salto escriben el PC:
RJMP ksalta siempre, sumando un desplazamiento k (con signo) al PC. Cuesta 2 ciclos.BRNE k(«salta si no es igual») suma k solo si la bandera Z vale 0. Cuesta 2 ciclos si salta y 1 si no.
Un salto condicional lee una bandera que dejó la instrucción anterior. Así se traduce un while o un for. El bucle clásico es:
LDI R16, 5 ; contador = 5
bucle: ADD R17, R16 ; acumulador += contador
DEC R16 ; contador -= 1 (Z = 1 si llega a 0)
BRNE bucle ; si Z = 0, volver a «bucle»
Cuando el PC llega a BRNE con Z = 0, el desplazamiento negativo lo devuelve a bucle; cuando Z = 1, el PC sigue de largo. En C, ese fragmento equivale a do { acumulador += contador; } while (--contador != 0);.
El desplazamiento k se mide desde la instrucción siguiente al salto, porque el PC ya se incrementó en la búsqueda. Por eso, en el programa de este tema, BRNE en la dirección 4 con k = −3 va a la dirección 2: 4 + 1 − 3 = 2.
07 · Cuánto tarda
Ciclos y tiempo
Cada ciclo de reloj dura 1 / f. A 16 MHz son 62,5 ns. Como el AVR ejecuta aproximadamente una instrucción por ciclo, un reloj de 16 MHz equivale a unos 16 millones de instrucciones por segundo (16 MIPS).
| Instrucción | Ciclos | Observación |
|---|---|---|
| LDI, MOV, ADD, SUB, INC, DEC, OUT | 1 | Operaciones entre registros o con un puerto |
| BRNE (no salta / salta) | 1 / 2 | El salto efectivo cuesta uno más |
| RJMP | 2 | Salto relativo |
| Lecturas y escrituras en SRAM | 2 | Más lentas que operar con registros |
| MUL (multiplicación de 8 × 8 bits) | 2 | Existe en hardware |
| División | No existe | Se resuelve con una rutina de software de decenas de ciclos |
Con estos números se puede calcular el tiempo de un bucle. El de retardo más simple, LDI R16, N seguido de DEC R16 y BRNE, cuesta:
Con N = 255 son 765 ciclos, unos 47,8 µs a 16 MHz. Un contador de 8 bits cuenta como máximo 256 vueltas, así que un único bucle no llega a ni un milisegundo. Para retardos más largos hay que anidar bucles; la macro _delay_ms() del tema 5 hace precisamente eso, calculando los valores en tiempo de compilación. Una forma mejor de medir tiempos, sin tener la CPU ocupada, son los temporizadores, que veremos más adelante.
08 · Analogía con programación
El ciclo de instrucción como un intérprete
Este programa en Python es un intérprete mínimo con las instrucciones reales del ejemplo. Sus tres comentarios marcan las fases buscar, decodificar y ejecutar. El programa de prueba suma 5 + 4 + 3 + 2 + 1 y deja el resultado en PORTB.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
programa = [0xE005, 0xE010, 0x0F10, 0x950A, 0xF7E9, 0xB915]
r = [0] * 32 # registros R0 a R31
pc, z, ciclos, portb = 0, 0, 0, 0
def con_signo(valor, bits):
return valor - (1 << bits) if valor & (1 << (bits - 1)) else valor
while pc < len(programa):
direccion = pc
op = programa[pc] # 1. BUSCAR
pc += 1 # el PC ya apunta a la siguiente
c = 1 # 2. DECODIFICAR y 3. EJECUTAR
if op & 0xF000 == 0xE000:
d, k = 16 + ((op >> 4) & 0xF), ((op >> 4) & 0xF0) | (op & 0xF)
r[d] = k
texto = f"LDI R{d}, {k}"
elif op & 0xFC00 == 0x0C00:
d, s = (op >> 4) & 0x1F, (op & 0xF) | ((op >> 5) & 0x10)
r[d] = (r[d] + r[s]) & 0xFF
z = int(r[d] == 0)
texto = f"ADD R{d}, R{s}"
elif op & 0xFE0F == 0x940A:
d = (op >> 4) & 0x1F
r[d] = (r[d] - 1) & 0xFF
z = int(r[d] == 0)
texto = f"DEC R{d}"
elif op & 0xFC07 == 0xF401:
k = con_signo((op >> 3) & 0x7F, 7)
texto = f"BRNE {k:+d}"
if z == 0: # salto: el PC cambia
pc, c = pc + k, 2
elif op & 0xF800 == 0xB800:
s = (op >> 4) & 0x1F
portb = r[s]
texto = f"OUT PORTB, R{s}"
else:
raise ValueError(f"instrucción desconocida {op:#06x}")
ciclos += c
print(f"{direccion}: {op:#06x} {texto:<16} R16={r[16]} R17={r[17]} Z={z} ciclos={ciclos}")
print(f"PORTB = {portb:08b} -> {ciclos} ciclos = {ciclos / 16:.2f} µs a 16 MHz")
Resultado: 18 líneas de traza. El bucle recorre las direcciones 2, 3 y 4 cinco veces; al final, PORTB = 00001111 y 22 ciclos, unos 1,38 µs a 16 MHz. Probá cambiar el primer número a 0xE00A (LDI R16, 10) y predecí cuántos ciclos tardará.
La analogía tiene un límite: el intérprete de Python hace la decodificación con comparaciones, una detrás de otra. La CPU lo hace con puertas lógicas, en paralelo y en el mismo ciclo. Y mientras Python tarda microsegundos por instrucción simulada, el AVR tarda 62,5 ns.
09 · Exploración interactiva
Una CPU paso a paso
Este simulador ejecuta programas de verdad: las palabras de la columna «Código» son códigos de operación del AVR, que el simulador decodifica por sus bits. Avanzá fase por fase o instrucción por instrucción y observá el PC, el registro de instrucción, los registros, las banderas y el puerto B.
CPU didáctica
| Dirección | Código | Binario | Instrucción |
|---|
Bitácora (lo más reciente arriba)
Bucle de retardo: ¿cuántas vueltas?
El simulador implementa solo algunas instrucciones y trabaja con las banderas Z, N y C; el AVR real tiene unas 130 instrucciones y calcula también H, V y S. Los tiempos usan 16 MHz.
Recorrido guiado
- Con «Suma con bucle», presioná tres veces «Avanzar una fase» y leé la bitácora: buscar (el PC sube), decodificar (se reconoce
LDI R16, 5) y ejecutar (R16 = 5). - Presioná «Ejecutar una instrucción» hasta llegar a
BRNE. Observá que el PC salta de la dirección 5 de vuelta a la 2 y que el ciclo cuesta uno más. - Seguí hasta que Z valga 1: el
BRNEdeja de saltar y el PC avanza. Poco después aparece 00001111 en PORTB: 15 en binario. - Cambiá a «Acarreo». Ejecutá cuatro instrucciones: R16 queda en 44 y la bandera C en 1, porque 300 no cabe en 8 bits.
- Cambiá a «Cuenta regresiva». Contá los ciclos totales hasta que Z = 1 y compará con la fórmula 3·N del tema: para N = 3 son 9.
Desafío: el programa que no termina
Todos los programas terminan en RJMP −1, una instrucción que salta a sí misma. Ejecutá varias veces «una instrucción» al final: el PC no cambia y cada vuelta suma dos ciclos. Una CPU no tiene un «fin»: cuando se queda sin nada que hacer, o se detiene con una instrucción de reposo, o gira en un bucle. Este bucle infinito es la forma más simple de «parar» un programa.
10 · Método de trabajo
Cómo seguir un programa a mano
- Armá una tabla de seguimiento con columnas para PC, instrucción, los registros que cambian, las banderas y los ciclos acumulados.
- Empezá con el PC en 0 y todos los registros con el valor inicial que corresponda; no supongas que arrancan en cero, salvo que el programa los inicialice.
- Ejecutá una fila por instrucción. Anotá qué registro cambia, cuánto vale y cómo quedan Z, N y C.
- En cada salto condicional, mirá la bandera que dejó la instrucción anterior y decidí si el PC salta o sigue.
- Sumá los ciclos, recordando que el salto efectivo cuesta uno más.
- Comprobá el resultado final contra lo que esperabas, y repetí con un caso límite (contador en 1, en 0, valor máximo).
En la práctica, este mismo análisis se hace con un simulador o con el depurador por hardware del tema 5, que muestran el PC y los registros reales. Cuando el comportamiento depende del tiempo, el osciloscopio confirma lo que calculaste: por ejemplo, una salida que cambia cada 47,8 µs.
11 · Comprensión
Confusiones frecuentes
«La CPU ejecuta líneas de C»
Ejecuta instrucciones de máquina. Una línea de C puede convertirse en una instrucción, en varias o, con optimización, desaparecer. El tema 7 muestra la traducción.
«Una instrucción siempre dura un ciclo»
Muchas sí, pero los saltos efectivos, los accesos a SRAM, las llamadas a subrutinas y las multiplicaciones tardan dos o más ciclos. Para calcular tiempos hay que consultar la tabla de la hoja de datos.
«El PC contiene la instrucción que se está ejecutando»
Contiene la dirección de la siguiente instrucción a buscar. Cuando una instrucción se ejecuta, el PC ya fue incrementado. Por eso los desplazamientos de los saltos se miden desde la instrucción siguiente.
«Las banderas se conservan hasta que las borro»
Se actualizan con el resultado de cada operación aritmética o lógica (no todas las instrucciones las tocan). Si hay instrucciones entre la que genera la bandera y el salto que la consulta, la bandera puede haber cambiado.
«Una CPU de 8 bits no puede trabajar con números grandes»
Sí puede, en trozos de 8 bits y arrastrando el acarreo, pero cada operación consume varias instrucciones y varios ciclos. Es una cuestión de tiempo, no de posibilidad.
12 · Práctica guiada
Predecí antes de comprobar
1. Decodificar una instrucción
La palabra 0xE005 es 1110 0000 0000 0101. Con el formato de LDI Rd, K (1110 KKKK dddd KKKK), ¿qué instrucción es?
Ver solución
Los cuatro bits altos 1110 identifican LDI. Los bits dddd son 0000, que corresponde a R16 (el registro destino es 16 + dddd). Los bits KKKK son 0000 arriba y 0101 abajo: K = 0000 0101 = 5. Es LDI R16, 5.
2. Seguir una suma
R16 = 250 y R17 = 10. Se ejecuta ADD R16, R17. ¿Cuánto vale R16 y cómo quedan C, Z y N?
Ver solución
250 + 10 = 260, que no cabe en 8 bits: 260 − 256 = 4. C = 1 (hubo acarreo), Z = 0 (el resultado no es cero), N = 0 (el bit 7 de 4 es 0).
3. Contar ciclos
Un bucle LDI R16, 100 / DEC R16 / BRNE se ejecuta en un reloj de 8 MHz. ¿Cuántos ciclos y cuánto tiempo toma?
Ver solución
Ciclos = 3·N = 3 × 100 = 300. A 8 MHz cada ciclo dura 125 ns: 300 × 125 ns = 37,5 µs.
4. Calcular el destino de un salto
BRNE está en la dirección 10 y su desplazamiento es −4. Si Z = 0, ¿a qué dirección salta?
Ver solución
El PC ya vale 11 cuando se ejecuta la instrucción. Destino = 11 + (−4) = 7.
5. Un retardo de un milisegundo
Con un reloj de 16 MHz, ¿alcanza un único bucle de 8 bits para generar 1 ms? Si no, ¿cuántos ciclos hacen falta?
Ver solución razonada
1 ms a 16 MHz son 16 000 ciclos. Un bucle de 8 bits ofrece como máximo 3 × 256 = 768 ciclos, así que no alcanza. Hay que anidar bucles (aproximadamente 21 repeticiones del bucle más largo) o usar un temporizador de hardware.
13 · Síntesis
Ideas para recordar
- La CPU repite tres fases: buscar la instrucción, decodificarla y ejecutarla.
- El contador de programa guarda la dirección de la siguiente instrucción y se incrementa en cada búsqueda.
- Una instrucción es un patrón de bits: parte código de operación y parte operandos.
- La ALU opera sobre los registros R0 a R31 y deja marcas en el SREG: Z (cero), N (negativo), C (acarreo), V, S, H.
- Los saltos modifican el PC; los condicionales consultan una bandera, y así se construyen los bucles y las decisiones.
- En un AVR la mayoría de las instrucciones dura un ciclo y los saltos efectivos dos; a 16 MHz, un ciclo son 62,5 ns.
- Un bucle de retardo de 8 bits cuesta 3·N ciclos; tiene un máximo de 768 ciclos.
¿Listo para el tema 7?
Antes de avanzar, describí qué pasa en cada una de las tres fases cuando se ejecuta ADD R17, R16, calculá a qué dirección salta un BRNE con un desplazamiento dado y estimá cuántos ciclos tarda un bucle de N vueltas. Si podés justificar las tres respuestas, entendés cómo piensa la CPU.
En el próximo tema vamos a ver el conjunto de instrucciones y el lenguaje ensamblador: cómo se traduce una línea de C a instrucciones y cómo leer el código que genera el compilador.