Цикл выборки-декодирования-исполнения
Урок подробно проходит главный цикл работы процессора, повторяющийся миллиарды раз в секунду.
Цикл выборки-декодирования-исполнения (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.
- Этот цикл — основа исполнения любой программы; на нём строится конвейеризация.