Внутри процессора: регистры, АЛУ, УУ, ПК

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

Тракт данных (datapath) — совокупность блоков (регистры, АЛУ, шины, мультиплексоры), по которым движутся данные. Устройство управления дирижирует трактом, выдавая управляющие сигналы.

Зачем знать узлы процессора

Мы собрали кирпичики: регистры (память), АЛУ (вычисления), мультиплексоры (выбор), автоматы (управление). Теперь соберём из них процессор. Понимание этих узлов объясняет, почему ассемблер выглядит именно так, откуда берутся регистры в коде и что физически происходит при ADD R1, R2.

Главные узлы

УзелНазначение
Регистровый файлнабор быстрых регистров для операндов и результатов
АЛУарифметика и логика над операндами
Счётчик команд (PC)адрес следующей команды
Регистр команд (IR)хранит текущую выбранную команду
Устройство управления (УУ)декодирует команду, выдаёт управляющие сигналы
Регистр флаговхранит Z/N/C/V после операций АЛУ

Тракт данных (упрощённо)

   ┌─────┐    ┌───────────────┐
   │ PC  │───→│   ПАМЯТЬ       │
   └──┬──┘    │   команд       │
      │ +1    └──────┬────────┘
      └───────────┐  │ команда
                  │  ▼
              ┌───┴──────┐
              │   IR     │ (регистр команд)
              └────┬─────┘
                   │ декод
              ┌────▼─────┐    ┌──────────────┐
              │    УУ    │───→│ упр. сигналы │
              └──────────┘    └──────┬───────┘
                                     ▼
    ┌────────────┐   операнды   ┌────────┐
    │ Регистровый│─────────────→│  АЛУ   │──→ результат
    │   файл     │←─────────────└────────┘   (обратно в регистр)
    └────────────┘

Как работает под капотом: счётчик команд

Счётчик команд (Program Counter, PC) — крошечный, но важнейший регистр: он хранит адрес следующей команды. После выборки команды PC увеличивается (на размер команды), указывая на следующую. Команда перехода (jump) — это просто запись нового значения в PC. Промоделируем регистровый файл и PC:

class CPU:
    def __init__(self, nregs=4):
        self.regs = [0] * nregs   # регистровый файл
        self.pc = 0               # счётчик команд
        self.flags = {"Z": 0, "N": 0}
    def execute(self, op, dst, a, b):
        if op == "ADD":
            self.regs[dst] = self.regs[a] + self.regs[b]
        elif op == "SUB":
            self.regs[dst] = self.regs[a] - self.regs[b]
        self.flags["Z"] = int(self.regs[dst] == 0)
        self.flags["N"] = int(self.regs[dst] < 0)
        self.pc += 1              # переход к следующей команде

cpu = CPU()
cpu.regs[1] = 12
cpu.regs[2] = 5
cpu.execute("ADD", 0, 1, 2)      # R0 = R1 + R2
print("R0 =", cpu.regs[0], "| PC =", cpu.pc, "| флаги", cpu.flags)
cpu.execute("SUB", 3, 2, 1)      # R3 = R2 - R1
print("R3 =", cpu.regs[3], "| PC =", cpu.pc, "| флаги", cpu.flags)

Вывод:

R0 = 17 | PC = 1 | флаги {'Z': 0, 'N': 0}
R3 = -7 | PC = 2 | флаги {'Z': 0, 'N': 1}

Почему регистров мало

Регистры — самая быстрая, но и самая дорогая память: они физически рядом с АЛУ. Поэтому их немного (обычно 16–32 у архитектуры). Их адресуют несколькими битами прямо в команде (например, 5 бит = 32 регистра). Чем больше регистров, тем длиннее код команды и тем больше площадь кристалла — снова компромисс.

Глубже в тему

Здесь полезно осознать, что мы наконец собираем процессор из ранее изученных кирпичиков, и каждый узел — это знакомый блок. Регистровый файл — это набор регистров, то есть триггеров из раздела про память. АЛУ — это сумматор и логика, выбираемые мультиплексором, из раздела про комбинационные схемы. Устройство управления — это конечный автомат. А мультиплексоры пронизывают весь тракт данных, решая, что куда направить. Тракт данных (datapath) — это и есть «дороги», по которым движутся числа: регистры, шины, MUX и АЛУ. Устройство управления при этом само не считает — оно лишь дирижирует, выдавая в нужный такт управляющие сигналы: «прочитать такой-то регистр», «АЛУ, выполни сложение», «защёлкни результат вот сюда». Понимание этого разделения объясняет, почему ассемблер выглядит именно так, откуда в коде берутся регистры и что физически стоит за командой ADD R1, R2.

Связку «PC хранит адрес, IR хранит команду» стоит закрепить намертво, потому что их путают чаще всего. Счётчик команд (PC) — крошечный регистр, в котором лежит адрес следующей команды; регистр команд (IR) держит саму выбранную команду, пока устройство управления её декодирует. Это два совершенно разных объекта: один указывает, откуда брать, другой хранит то, что взяли. Команда перехода (jump) тогда перестаёт быть чем-то магическим — это просто запись нового значения в PC. Цикл, условие, вызов функции — всё это в конечном счёте сводится к управляемым изменениям одного маленького регистра PC.

Почему регистров мало — отличный пример инженерного компромисса, пронизывающего всю архитектуру. Регистры — самая быстрая память, потому что лежат вплотную к АЛУ, но именно поэтому и самая дорогая по площади кристалла. Вдобавок номер регистра кодируется битами прямо внутри команды: чтобы адресовать 32 регистра, нужно 5 бит в каждой команде, обращающейся к регистрам, а 64 регистра потребовали бы уже 6 бит — и команды распухнут, а код станет длиннее. Поэтому типичная архитектура останавливается на 16–32 регистрах: это баланс между скоростью, площадью и компактностью кодирования команд. Здесь видно, как, казалось бы, низкоуровневое решение о числе регистров напрямую отражается на формате машинных команд.

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

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

  • Путать PC и регистр команд (IR). PC хранит адрес следующей команды, IR — саму текущую команду.
  • Думать, что переход «прыгает» магически. Переход — это просто запись нового адреса в PC.
  • Считать регистры «переменными». Их физически мало и они фиксированы; переменные программы живут в памяти, а в регистры лишь временно загружаются.

Итог

  • Процессор = регистровый файл + АЛУ + УУ + PC + IR + регистр флагов.
  • PC хранит адрес следующей команды; переход = запись нового адреса в PC.
  • Регистров мало (дорогая быстрая память), их номера кодируются прямо в команде.
Проверьте себя
1. Что хранит счётчик команд (PC)?
AТекущую команду целиком
BАдрес следующей команды для выборки
CРезультат последней операции
DФлаги АЛУ
2. Чем регистр команд (IR) отличается от счётчика команд (PC)?
AНичем
BIR хранит саму текущую команду, PC — адрес следующей
CIR хранит данные, PC — команды
DPC быстрее IR
3. Почему в процессоре мало регистров?
AИх хватает для любых задач
BЭто самая быстрая и дорогая память; больше регистров — длиннее код команды и больше площадь кристалла
CТак требует операционная система
DРегистры медленные