Ciclo de instrucción -- Fetch, Decode, Execute, Write Back

La CPU ejecuta cada instrucción siguiendo el mismo proceso de cuatro pasos, como un ritmo de pipeline interminable. Esta lección te llevará a comprender en profundidad qué hace cada uno de los cuatro pasos y usará Python para simular el proceso completo de ejecución de instrucciones de una CPU.


Analogía cotidiana: la cocina de un restaurante

Imagina que eres el chef principal de un restaurante. Cuando el cliente hace un pedido, sigues estos pasos para preparar el plato:

  1. Tomar el pedido (Fetch): tomar una nueva comanda del mostrador y ver qué ha pedido el cliente
  2. Leer la comanda (Decode): analizar el menú — el «cerdo estofado» necesita panceta, salsa de soja y azúcar de roca; el «salteado de verduras» necesita verduras y ajo picado.
  3. Cocinar (Execute): encender el fuego y cocinar de verdad: cortar, saltear y sazonar
  4. Servir el plato (Write Back): emplatar el plato cocinado y entregarlo al camarero para que lo lleve

Cada comanda debe pasar por estos cuatro pasos. Cuando terminas un plato, tomas la siguiente comanda. La CPU también funciona así: toma una instrucción, la decodifica, la ejecuta, escribe el resultado y repite el ciclo.

Este ciclo de cuatro pasos es el ritmo de trabajo más esencial de la CPU. Cada segundo, desde el encendido hasta el apagado del ordenador, la CPU repite continuamente el ciclo «Fetch-Decode-Execute-Write Back», miles de millones de veces por segundo.


Explicación detallada de las cuatro etapas

Etapa 1: Búsqueda (Fetch)

La CPU primero debe «tomar» de la memoria la siguiente instrucción a ejecutar.

Hay un registro especial llamadocontador de programa (PC, Program Counter), que siempre guarda la dirección en memoria de la siguiente instrucción.

¿Qué hace la etapa de búsqueda (Fetch)?

  1. La CPU coloca la dirección del PC en el bus de direcciones
  2. La CPU envía una señal de «lectura» por el bus de control
  3. La memoria devuelve los datos de la instrucción en esa dirección
  4. La instrucción se coloca en el registro de instrucción (IR, Instruction Register)
  5. El PC se incrementa automáticamente en 1, apuntando a la siguiente instrucción

Aquí hay un diseño clave:El PC se incrementa automáticamente. Esto significa que, por defecto, la CPU ejecuta las instrucciones en orden, a menos que encuentre una instrucción de salto (como if-else, bucles) que modifique el valor del PC.

Etapa 2: Decodificación (Decode)

La instrucción extraída es una cadena de números binarios (por ejemplo,0001 0010 0011), la CPU necesita «traducirla».

El trabajo principal de la etapa de decodificación:

  • Identificar el código de operación (Opcode): los primeros bits de la instrucción le dicen a la CPU qué hacer — ¿suma, resta, carga de datos o salto?
  • Extraer los operandos (Operands): los bits posteriores de la instrucción indican los objetos de la operación — ¿qué registro? ¿qué dirección de memoria?
  • Establecer las señales de control: el controlador, según el resultado de la decodificación, envía las señales de control correspondientes a la ALU, al banco de registros, a los buses, etc.

Por ejemplo, una instrucciónADD R1, R2, R3, tras decodificarla la CPU sabe: hacer que la ALU realice una suma, con entradas de los registros R2 y R3, y guardar el resultado en R1.

Etapa 3: Ejecución (Execute)

Esta es la etapa en la que la instrucción realmente «trabaja». Según el tipo de instrucción, las acciones en la etapa de ejecución son diferentes:

Tipo de instrucciónQué hace la etapa de ejecuciónHardware involucrado
Operaciones aritméticas (ADD, SUB, etc.)La ALU realiza la operación con los operandosALU, banco de registros
Operaciones lógicas (AND, OR, etc.)La ALU realiza operaciones de bits sobre los operandosALU, banco de registros
Lectura de memoria (LOAD)Accede a la memoria a través del bus de direcciones y lee los datosBus de direcciones, bus de datos, memoria
Escritura en memoria (STORE)Accede a la memoria a través del bus de direcciones y escribe los datosBus de direcciones, bus de datos, memoria
Salto (JUMP, BRANCH)Modifica el valor del PC y cambia el flujo de ejecuciónControlador, registro PC

Etapa 4: Escritura (Write Back)

El resultado producido en la etapa de ejecución debe «escribirse» en la posición de destino.

Los destinos de la escritura pueden ser:

  • Registro: guardar el resultado de la operación en un registro de propósito general (el más común)
  • Memoria: guardar el resultado en alguna dirección de la memoria
  • Registro de banderas: actualizar las banderas de estado (por ejemplo, si el resultado es cero, si hay desbordamiento, si es negativo, etc.)

Una vez completada la escritura, el ciclo de ejecución de la instrucción termina formalmente. Si el PC todavía apunta a una dirección de instrucción válida, la CPU vuelve automáticamente a la etapa de Fetch y comienza la siguiente instrucción.


Vista de línea de tiempo del ciclo de instrucción

La siguiente línea de tiempo muestra el proceso completo de ejecución secuencial de 3 instrucciones:

时钟周期:  1      2      3      4      5      6      7      8      9      10     11     12
指令1:    [取指] [译码] [执行] [写回]
指令2:                           [取指] [译码] [执行] [写回]
指令3:                                                  [取指] [译码] [执行] [写回]

         ↑ 指令1 完成后才开始指令2,完全串行执行

Observa que aquí cada instrucción usa 4 ciclos, y las 3 instrucciones usan 12 ciclos en total. Más adelante, cuando se trate el pipeline, verás cómo se puede reducir drásticamente ese tiempo.


Demostración interactiva: simulador SimpleCPU

El siguiente programa en Python simula el ciclo de instrucción completo de una CPU muy sencilla. Admite las instrucciones LOAD (cargar valor), ADD (sumar), MUL (multiplicar), CMP (comparar + salto condicional) y STORE (imprimir salida), e imprime el registro de cada etapa en detalle.

Ejemplo

"""
Simulador SimpleCPU — demostración del ciclo de instrucción completo
Instrucciones admitidas: LOAD, ADD, MUL, CMP+JGT, STORE, HALT
Cada instrucción pasa por: Fetch (IF) → Decode (ID) → Execute (EX) → Write Back (WB)
"""


class SimpleCPU:
    """Un simulador de CPU con fines educativos que muestra las cuatro etapas del ciclo de instrucción"""

    def __init__(self):
        # Registros de propósito general: R0~R3, valores iniciales todos 0
        self.registers = {'R0': 0, 'R1': 0, 'R2': 0, 'R3': 0}
        # Registro de banderas: registra el resultado de la comparación
        self.flags = {'ZERO': False, 'GREATER': False}
        # Contador de programa: apunta a la posición en memoria de la siguiente instrucción
        self.pc = 0
        # Registro de instrucción: almacena la instrucción que se está ejecutando actualmente
        self.ir = None
        # Memoria: almacena la secuencia de instrucciones del programa
        self.memory = []
        # Cuenta el número de veces que se ejecuta cada etapa
        self.stats = {'fetch': 0, 'decode': 0, 'execute': 0, 'writeback': 0}
        self.total_cycles = 0
        # Banderas de control
        self.running = False

    def load_program(self, instructions):
        """Carga la lista de instrucciones en la memoria"""
        self.memory = instructions
        self.pc = 0
        self.running = True
        print(f"[Sistema] Programa cargado correctamente, {len(instructions)} instrucciones en total")
        print(f"[Sistema] Registros iniciales: {self.registers}")
        print("=" * 60)

    def fetch(self):
        """Etapa 1: Búsqueda (Instruction Fetch)"""
        self.total_cycles += 1
        if self.pc >= len(self.memory):
            self.running = False
            return None

        # Leer de la memoria la instrucción a la que apunta el PC
        instruction = self.memory[self.pc]
        self.ir = instruction
        old_pc = self.pc
        self.pc += 1  # El PC se incrementa automáticamente
        self.stats['fetch'] += 1

        print(f"[Ciclo {self.total_cycles}] Fetch (IF)")
        print(f" PC={old_pc} → leer instrucción desde la dirección de memoria {old_pc}")
        print(f" Contenido de la instrucción:\"{instruction}\"")
        print(f" Actualización del PC: {old_pc} → {self.pc}")
        print(f"  IR ← \"{instruction}\"")
        return instruction

    def decode(self, instruction):
        """Etapa 2: Decodificación (Instruction Decode)"""
        self.total_cycles += 1
        self.stats['decode'] += 1

        # Analizar el código de operación y los operandos
        parts = instruction.split()
        opcode = parts[0].upper()  # Código de operación
        operands = parts[1:] if len(parts) > 1 else []  # Operandos

        print(f"[Ciclo {self.total_cycles}] Decodificación (ID)")
        print(f" Código de operación (Opcode): {opcode}")
        print(f" Operandos: {operands}")

        # Explicar el significado del código de operación
        opcode_meaning = {
            'LOAD': 'Cargar un valor inmediato en un registro',
            'ADD':  'Suma de registros',
            'MUL':  'Multiplicación de registros',
            'CMP':  'Comparar el valor de dos registros',
            'JGT':  'Salto condicional (salto cuando es mayor)',
            'STORE':'Mostrar el valor del registro',
            'HALT': 'Detener la ejecución',
        }
        if opcode in opcode_meaning:
            print(f" Significado: {opcode_meaning[opcode]}")

        return opcode, operands

    def execute(self, opcode, operands):
        """Etapa 3: Ejecución (Execute)"""
        self.total_cycles += 1
        self.stats['execute'] += 1

        print(f"[Ciclo {self.total_cycles}] Ejecución (EX)")

        if opcode == 'HALT':
            print(f" HALT: Terminar la ejecución del programa")
            self.running = False

        elif opcode == 'LOAD':
            # LOAD Rx, value → Almacena el valor inmediato value en el registro Rx
            reg = operands[0]
            value = int(operands[1])
            print(f" Operación: Cargar el valor inmediato {value} en el registro {reg}")
            print(f" Componentes involucrados: bus de datos (transmite el valor inmediato {value})")
            # Almacenar temporalmente el resultado; la fase de escritura actualizará el registro
            return ('LOAD', reg, value)

        elif opcode == 'ADD':
            # ADD Rx, Ry → Rx = Rx + Ry
            rx, ry = operands[0], operands[1]
            a = self.registers[rx]
            b = self.registers[ry]
            result = a + b
            print(f" Operación: La ALU ejecuta {rx}({a}) + {ry}({b}) = {result}")
            print(f" Componentes involucrados: ALU (unidad aritmético-lógica), banco de registros")
            return ('WRITE_REG', rx, result)

        elif opcode == 'MUL':
            # MUL Rx, Ry → Rx = Rx * Ry
            rx, ry = operands[0], operands[1]
            a = self.registers[rx]
            b = self.registers[ry]
            result = a * b
            print(f" Operación: La ALU ejecuta {rx}({a}) * {ry}({b}) = {result}")
            print(f" Componentes involucrados: ALU (unidad aritmético-lógica), banco de registros")
            return ('WRITE_REG', rx, result)

        elif opcode == 'CMP':
            # CMP Rx, Ry → Compara dos registros y establece las banderas
            rx, ry = operands[0], operands[1]
            a = self.registers[rx]
            b = self.registers[ry]
            self.flags['ZERO'] = (a == b)
            self.flags['GREATER'] = (a > b)
            print(f" Operación: Comparar {rx}({a}) y {ry}({b})")
            print(f" Resultado: ZERO={self.flags['ZERO']}, GREATER={self.flags['GREATER']}")
            print(f" Componentes involucrados: ALU (comparador), registro de banderas")
            return ('FLAGS', None, None)

        elif opcode == 'JGT':
            # JGT address → Si la bandera GREATER es verdadera, salta a address
            target = int(operands[0])
            if self.flags['GREATER']:
                old_pc = self.pc
                self.pc = target
                print(f" Condición cumplida (GREATER=True): PC salta {old_pc} → {target}")
                print(f" Componentes involucrados: unidad de control, registro PC")
            else:
                print(f" Condición no cumplida (GREATER=False): no salta, PC permanece en {self.pc}")
            return None

        elif opcode == 'STORE':
            # STORE Rx → Muestra el valor del registro
            reg = operands[0]
            value = self.registers[reg]
            print(f" Operación: Mostrar el valor del registro {reg}")
            return ('OUTPUT', reg, value)

        return None

    def writeback(self, action):
        """Etapa 4: Escritura (Write Back)"""
        self.total_cycles += 1
        self.stats['writeback'] += 1

        if action is None:
            print(f"[Ciclo {self.total_cycles}] Escritura (WB) - sin operación")
            return

        action_type = action[0]
        print(f"[Ciclo {self.total_cycles}] Escritura (WB)")

        if action_type == 'LOAD':
            reg, value = action[1], action[2]
            old_val = self.registers[reg]
            self.registers[reg] = value
            print(f" Destino: registro {reg}")
            print(f" Datos: {old_val} → {value}")
            print(f" Canal: bus de datos → banco de registros")

        elif action_type == 'WRITE_REG':
            reg, result = action[1], action[2]
            old_val = self.registers[reg]
            self.registers[reg] = result
            print(f" Destino: registro {reg}")
            print(f" Datos: {old_val} → {result}")
            print(f" Canal: salida de la ALU → banco de registros (bus interno)")

        elif action_type == 'OUTPUT':
            reg, value = action[1], action[2]
            print(f" Salida: registro {reg} = {value}")
            print(f" Canal: banco de registros → bus de datos → interfaz de E/S")

        print(f" Estado actual de los registros: {self.registers}")
        print(f" Banderas: {self.flags}")
        print("-" * 60)

    def run(self):
        """Ejecutar todo el programa, instrucción por instrucción"""
        print("=" * 60)
        print("Iniciando el simulador SimpleCPU — Demostración del ciclo de instrucción (ejemplo)")
        print("=" * 60)

        while self.running:
            # Bucle de las cuatro etapas
            instruction = self.fetch()          # Etapa 1: Captación (fetch)
            if instruction is None:
                break

            opcode, operands = self.decode(instruction)  # Etapa 2: Decodificación (decode)
            action = self.execute(opcode, operands)      # Etapa 3: Ejecución (execute)
            self.writeback(action)                       # Etapa 4: Escritura (write back)

        # Ejecución completada, imprimir estadísticas
        print("\n" + "=" * 60)
        print("¡Programa ejecutado! Estadísticas:")
        print(f" Número total de ciclos de reloj: {self.total_cycles}")
        print(f" Ejecuciones de la etapa de captación: {self.stats['fetch']} veces")
        print(f" Ejecuciones de la etapa de decodificación: {self.stats['decode']} veces")
        print(f" Ejecuciones de la etapa de ejecución: {self.stats['execute']} veces")
        print(f" Ejecuciones de la etapa de escritura: {self.stats['writeback']} veces")
        print(f" Registros finales: {self.registers}")
        print(f" Banderas finales: {self.flags}")
        print(f" PC final: {self.pc}")
        print("=" * 60)


# ===== Ejecutar programa de demostración =====
# Lógica del programa: calcular el resultado de 1+2, luego multiplicar por 3 y comparar con 10
# LOAD R0, 1   → R0 = 1
# LOAD R1, 2   → R1 = 2
# ADD R0, R1   → R0 = R0 + R1 = 1 + 2 = 3
# LOAD R2, 3   → R2 = 3
# MUL R0, R2   → R0 = R0 * R2 = 3 * 3 = 9
# CMP R0, R2 → Compara R0(9) y R2(3), establece GREATER=True
# STORE R0 → Muestra el valor de R0
# HALT → Detener

cpu = SimpleCPU()
program = [
    "LOAD R0 1",      # R0 = 1
    "LOAD R1 2",      # R1 = 2
    "ADD R0 R1",      # R0 = R0 + R1 = 3
    "LOAD R2 3",      # R2 = 3
    "MUL R0 R2",      # R0 = R0 * R2 = 9
    "CMP R0 R2",      # Compara 9 y 3, GREATER=True
    "JGT 8",          # Si GREATER es verdadero, salta a la instrucción 8
    "LOAD R3 0",      # (Esta no se ejecutará porque JGT la salta)
    "STORE R0",       # Muestra R0 = 9
    "HALT",           # Detener
]
cpu.load_program(program)
cpu.run()

El código anterior imprime registros detallados en cada etapa de cada instrucción. Puedes ver claramente qué hace la CPU internamente en cada ciclo de reloj. Presta atención a:

  • Incremento automático del PC: Cada vez que se ejecuta una instrucción, el PC se incrementa automáticamente en +1 (etapa de captación)
  • Salto condicional: La combinación CMP+JGT implementa una comprobación if: si R0 > R2 salta a la instrucción 8, omitiendo la instrucción 7
  • Los registros como intermediarios: Todos los datos deben cargarse primero en los registros; la ALU toma datos de los registros y, después de calcular, escribe el resultado de vuelta en los registros

Demostración interactiva: estructura interna de la CPU y reproducción paso a paso del ciclo de instrucción

A continuación se muestra un diagrama simplificado de la estructura interna de la CPU dibujado con vis-network. Haz clic en el botón «Siguiente» para observar qué componentes de hardware utiliza la instrucción en cada una de las cuatro etapas. Los componentes activados se resaltan con el color correspondiente a cada etapa.

Estructura interna de la CPU y demostración paso a paso del ciclo de instrucción (ejemplo)

Captación IF
Decodificación ID
Ejecución EX
Escritura WB
Haz clic en «Siguiente» para iniciar la demostración del ciclo de instrucción y observa qué componentes internos de la CPU se activan en cada etapa.
Otras extensiones