Цикл выборки-декодирования-исполнения

Урок подробно проходит главный цикл работы процессора, повторяющийся миллиарды раз в секунду.

Цикл выборки-декодирования-исполнения (fetch-decode-execute) — бесконечный цикл, в котором процессор берёт команду из памяти, расшифровывает её и выполняет, затем переходит к следующей.

Зачем понимать этот цикл

Вся работа процессора — это повторение одного цикла. Поняв его, вы понимаете, как «оживает» любая программа: от print до игрового движка. Этот же цикл объясняет, зачем нужен конвейер (следующий раздел) и почему переходы «дорогие».

Четыре фазы

   ┌──────────────────────────────────────────┐
   │  1. FETCH (выборка)                         │
   │     команда = ПАМЯТЬ[PC]                     │
   │     PC = PC + размер_команды                 │
   ├──────────────────────────────────────────┤
   │  2. DECODE (декодирование)                  │
   │     УУ разбирает: какая операция, операнды   │
   ├──────────────────────────────────────────┤
   │  3. EXECUTE (исполнение)                    │
   │     АЛУ считает / читается-пишется память     │
   ├──────────────────────────────────────────┤
   │  4. WRITEBACK (запись результата)           │
   │     результат -> регистр / память            │
   └────────────────────┬───────────────────────┘
                        │
                        └──→ обратно к шагу 1

Как работает под капотом: мини-процессор

Соберём крошечный учебный процессор, который реально исполняет программу в памяти, шаг за шагом проходя fetch-decode-execute. Программа — список команд; данные — словарь памяти:

program = [
    ("LOAD",  "R0", 10),     # R0 = 10
    ("LOAD",  "R1", 32),     # R1 = 32
    ("ADD",   "R2", "R0", "R1"),  # R2 = R0 + R1
    ("PRINT", "R2"),
    ("HALT",),
]

regs = {"R0": 0, "R1": 0, "R2": 0}
pc = 0
output = []

while True:
    instr = program[pc]          # FETCH
    op = instr[0]                # DECODE
    pc += 1
    if op == "LOAD":             # EXECUTE + WRITEBACK
        regs[instr[1]] = instr[2]
    elif op == "ADD":
        regs[instr[1]] = regs[instr[2]] + regs[instr[3]]
    elif op == "PRINT":
        output.append(regs[instr[1]])
    elif op == "HALT":
        break

print("Регистры:", regs)
print("Вывод программы:", output)

Вывод:

Регистры: {'R0': 10, 'R1': 32, 'R2': 42}
Вывод программы: [42]

Трассировка с показом PC

Покажем, как PC двигается по программе на каждом такте — это и есть «сердцебиение» процессора:

program = ["LOAD R0,5", "LOAD R1,7", "ADD R2,R0,R1", "JUMP 0", "..."]
pc = 0
for step in range(5):
    instr = program[pc]
    print(f"такт {step}: PC={pc} -> выбрана команда '{instr}'")
    if instr == "JUMP 0":
        pc = 0                   # переход = запись в PC
    else:
        pc += 1

Вывод:

такт 0: PC=0 -> выбрана команда 'LOAD R0,5'
такт 1: PC=1 -> выбрана команда 'LOAD R1,7'
такт 2: PC=2 -> выбрана команда 'ADD R2,R0,R1'
такт 3: PC=3 -> выбрана команда 'JUMP 0'
такт 4: PC=0 -> выбрана команда 'LOAD R0,5'

Почему переходы «дорогие»

В простом процессоре каждая команда проходит все четыре фазы по очереди. Команда перехода меняет PC, поэтому процессор не знает заранее, какую команду брать следующей, пока не выполнит переход. В конвейерном процессоре (следующий раздел) это создаёт проблему: уже начатые «не те» команды приходится отбрасывать.

Глубже в тему

Главная мысль этого урока стоит того, чтобы её осознать во всей широте: абсолютно вся работа процессора — это повторение одного простого цикла. От вызова print до игрового движка, от операционной системы до браузера — всё, что когда-либо исполнял компьютер, разложено на команды, и каждая из них прошла через fetch, decode, execute и writeback. В этой повторяемости и кроется сила компьютера: невероятно сложное поведение собирается из миллиардов повторений элементарного, предсказуемого шага. Понимание цикла снимает ореол волшебства с фразы «программа выполняется» — за ней стоит конечный автомат устройства управления, который такт за тактом гонит этот круг.

Разберём фазы чуть глубже, чем «взяли-расшифровали-посчитали». В фазе fetch процессор читает команду из памяти по адресу из PC и тут же увеличивает PC — обратите внимание, инкремент происходит сразу при выборке, ещё до того, как мы поняли, что это за команда. В фазе decode устройство управления разбирает биты команды: выделяет код операции и номера операндов, после чего выставляет нужные управляющие сигналы. В фазе execute работает АЛУ или происходит обращение к памяти. В фазе writeback результат защёлкивается в регистр или память. В простом (неконвейерном) процессоре эти фазы идут строго по очереди, и каждая занимает один или несколько тактов — то есть фазы вовсе не «мгновенны».

Почему ранний инкремент PC так важен и почему переход — это всего лишь запись в PC, стоит увязать вместе. Поскольку PC увеличивается уже в fetch, по умолчанию следующей выбирается команда, лежащая сразу за текущей, — так получается естественный линейный ход программы. Команда перехода просто перезаписывает PC новым адресом, и тогда следующая выборка пойдёт оттуда. Никакого отдельного механизма «прыжка» не существует: цикл, условие, вызов функции — это всё управляемые изменения одного регистра. Цикл при этом по своей природе бесконечен: процессор крутит его, пока есть что исполнять, а остановка — это специальная команда (HALT) или переход в состояние ожидания, а не «естественный конец».

И, наконец, зачем этот урок — фундамент для следующего раздела про конвейер. В простом процессоре, пока одна команда проходит все четыре фазы, остальные блоки простаивают: работает только сумматор, или только память, или только устройство управления. Это расточительно, и напрашивается идея конвейера — запускать новую команду в fetch, пока предыдущая ещё в execute, как на сборочном конвейере завода. Но команда перехода ломает эту красоту: пока переход не выполнен, процессор не знает наверняка, какую команду брать следующей, и уже начатые «не те» команды приходится отбрасывать. Именно поэтому переходы называют «дорогими», и именно отсюда вырастают такие приёмы, как предсказание переходов. Так этот скромный четырёхфазный цикл оказывается ключом к пониманию всего, что делает современные процессоры быстрыми.

Частые ошибки

  • Думать, что фазы выполняются мгновенно. В простом процессоре каждая фаза занимает один или несколько тактов.
  • Забывать про инкремент PC в фазе fetch. PC увеличивается сразу при выборке, ещё до исполнения.
  • Считать, что цикл когда-то «заканчивается». Он бесконечен, пока есть команды; остановка — это специальная команда HALT или ожидание.

Итог

  • Процессор бесконечно повторяет: fetch -> decode -> execute -> writeback.
  • Fetch берёт команду по адресу PC и увеличивает PC; переход меняет PC.
  • Этот цикл — основа исполнения любой программы; на нём строится конвейеризация.
Проверьте себя
1. Что происходит в фазе fetch (выборка)?
AАЛУ вычисляет результат
BКоманда читается из памяти по адресу PC, и PC увеличивается
CРезультат записывается в регистр
DКоманда декодируется
2. Как реализуется команда перехода (jump) в этом цикле?
AПерезапускает процессор
BЗаписывает новый адрес в счётчик команд PC
CОчищает все регистры
DУдваивает тактовую частоту
3. Сколько фаз обычно выделяют в классическом цикле команды?
AДве
BЧетыре (fetch, decode, execute, writeback)
CШесть
DОдну