← Índice del curso

Microcontroladores · Tema 6

La CPU: buscar, decodificar y ejecutar

Debajo de cada línea de C hay una CPU repitiendo el mismo ritmo: buscar una instrucción, entender qué pide y hacerlo. Es un intérprete en silicio. Entender ese ciclo explica cuánto tarda un programa, por qué existen las banderas y qué significa que un bucle «consume» la CPU.

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:

Camino de una instrucción dentro de la CPU El contador de programa envía una dirección a la memoria de programa. La palabra leída se guarda en el registro de instrucción. El decodificador la interpreta y activa señales de control hacia los registros y la ALU. La ALU opera con los registros y actualiza el registro de estado. Contador deprograma (PC) Memoria deprograma (Flash) Registro deinstrucción Decodificadory control RegistrosR0 a R31 ALU Registro deestado (SREG) direccióncódigobits señales de controldatosbanderas BUSCAR Y DECODIFICAREJECUTAR PC + 1 en cada búsqueda

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

Registros principales de un AVR.
RegistroTamañoFunción
R0 a R3132 × 8 bitsOperandos y resultados; las operaciones con constantes usan R16 a R31
PC (contador de programa)14 bits en el ATmega328PDirección, en palabras de 16 bits, de la próxima instrucción
SP (puntero de pila)16 bitsDirección de la cima de la pila en SRAM
SREG (registro de estado)8 bitsBanderas del último resultado y habilitación de interrupciones
X, Y, Z16 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

Bits del registro de estado, del 7 al 0.
BitNombreSe pone en 1 cuando…
7I (interrupciones)Las interrupciones están habilitadas globalmente
6T (transferencia)Almacén temporal de un bit, usado por instrucciones especiales
5H (semiacarreo)Hubo acarreo entre los bits 3 y 4; útil en aritmética BCD
4S (signo)Resulta de N ⊕ V: indica el signo verdadero en aritmética con signo
3V (desbordamiento)Una operación con signo se salió del rango
2N (negativo)El bit 7 del resultado es 1
1Z (cero)El resultado fue exactamente cero
0C (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:

  1. 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.
  2. Decodificar (decode). Ciertos bits de la palabra identifican la operación y otros los operandos. El decodificador activa las señales correspondientes.
  3. 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 000011 identifican la operación ADD.
  • El destino es d = 1 0001 = 17 y el origen r = 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:

1100 1000 (200)
+ 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 k salta 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).

Duración de algunas instrucciones del AVR, en ciclos de reloj.
InstrucciónCiclosObservación
LDI, MOV, ADD, SUB, INC, DEC, OUT1Operaciones entre registros o con un puerto
BRNE (no salta / salta)1 / 2El salto efectivo cuesta uno más
RJMP2Salto relativo
Lecturas y escrituras en SRAM2Más lentas que operar con registros
MUL (multiplicación de 8 × 8 bits)2Existe en hardware
DivisiónNo existeSe 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:

ciclos = 1 (LDI) + N·1 (DEC) + (N − 1)·2 (BRNE salta) + 1 (BRNE no salta) = 3·N

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

Programa en la memoria Flash (la fila resaltada es la instrucción en curso).
DirecciónCódigoBinarioInstrucción
Contador de programa (PC)0x0000
Registro de instrucción—
Fase—
Registros—
Banderas del SREG—
PORTB00000000
Tiempo acumulado—

Bitácora (lo más reciente arriba)

    Bucle de retardo: ¿cuántas vueltas?

    Vueltas N (LDI R16, N)—
    Ciclos del bucle—
    Retardo real obtenido—

    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

    1. 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).
    2. 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.
    3. Seguí hasta que Z valga 1: el BRNE deja de saltar y el PC avanza. Poco después aparece 00001111 en PORTB: 15 en binario.
    4. Cambiá a «Acarreo». Ejecutá cuatro instrucciones: R16 queda en 44 y la bandera C en 1, porque 300 no cabe en 8 bits.
    5. 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

    1. Armá una tabla de seguimiento con columnas para PC, instrucción, los registros que cambian, las banderas y los ciclos acumulados.
    2. 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.
    3. Ejecutá una fila por instrucción. Anotá qué registro cambia, cuánto vale y cómo quedan Z, N y C.
    4. En cada salto condicional, mirá la bandera que dejó la instrucción anterior y decidí si el PC salta o sigue.
    5. Sumá los ciclos, recordando que el salto efectivo cuesta uno más.
    6. 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.