От кремниевого переключателя к логическим вентилям

В основе любого микропроцессора лежит полевой транзистор с изолированным затвором (MOSFET). В цифровой схемотехнике он работает обычным электронным ключом. Подавая напряжение на затвор, мы открываем канал между истоком и стоком, пропуская электрический ток. Отсутствие напряжения запирает канал. Состояние проводимости инженеры условились считать логической единицей, а закрытое состояние — логическим нулём.

Одиночный транзистор способен только размыкать цепь, поэтому их группируют в комплементарные пары n-типа и p-типа (технология КМОП). Такая связка позволяет собирать базовые логические вентили: НЕ (инвертор), И, ИЛИ, исключающее ИЛИ (XOR). Инвертор меняет высокий уровень сигнала на низкий. Соединяя четыре или шесть транзисторов, инженеры получают логический элемент 2И-НЕ (NAND), на базе которого можно реализовать абсолютно любую булеву функцию.

Объединение логических вентилей порождает функциональные узлы. Простейший сумматор складывает два бита с помощью элементов XOR и И. Если соединить тридцать два таких сумматора цепочкой, получается тридцатидвухбитный сумматор, способный складывать целые числа за один рабочий такт. Набор сумматоров, сдвигателей и логических блоков формирует арифметико-логическое устройство (АЛУ) — главный вычислительный узел ядра.

Архитектура команд на примере RISC-V

Чтобы заставить кремний выполнять конкретные действия, нужен стандарт взаимодействия между софтом и железом. Этот стандарт называют архитектурой набора команд (ISA). В последние годы стандартом де-факто для изучения схемотехники стала открытая архитектура RISC-V. В отличие от закрытой и сложной x86, архитектура RISC-V построена на философии сокращённого набора команд (RISC) и использует модель load-store.

В модели load-store арифметические инструкции работают исключительно с регистрами внутри процессора. Обращение к медленной оперативной памяти вынесено в отдельные команды загрузки (lw) и сохранения (sw). Процессор RISC-V базовой редакции RV32I содержит счётчик команд (Program Counter, PC) и тридцать два универсальных тридцатидвухбитных регистра, обозначенных от x0 до x31.

Любая инструкция в RV32I кодируется словом фиксированной длины в тридцать два бита. Например, команда сложения add x3, x1, x2 велит ядру взять значения из регистров x1 и x2, сложить их в АЛУ и записать сумму в регистр x3. В двоичном представлении эти 32 бита строго разделены на поля: код операции (opcode), номер целевого регистра (rd), номера исходных регистров (rs1, rs2) и биты модификатора функции (funct3, funct7). Декодеру процессора достаточно распределить проводники от этих бит к нужным блокам, чтобы схема поняла задачу.

Классический пятиступенчатый конвейер

Если бы процессор выполнял каждую инструкцию строго по очереди от начала до конца, большая часть его блоков простаивала бы. Пока работает АЛУ, память простаивает. Чтобы занять все узлы одновременно, ещё в 1980-х годах внедрили конвейерную обработку. Классический конвейер RISC делит обработку команды на пять этапов.

  1. Выборка инструкции (Instruction Fetch, IF). По адресу из регистра PC процессор забирает 32 бита команды из кэша памяти, после чего увеличивает счётчик PC на четыре байта.
  2. Декодирование (Instruction Decode, ID). Схема читает битовые поля команды, определяет операцию и параллельно считывает значения из регистрового файла.
  3. Выполнение (Execute, EX). АЛУ производит вычисление, сдвиг или проверяет условие условного перехода.
  4. Доступ к памяти (Memory Access, MEM). Выполняется только для инструкций загрузки или сохранения данных; остальные команды пропускают этот шаг.
  5. Обратная запись (Write Back, WB). Результат вычислений из АЛУ или прочитанные из памяти данные фиксируются в целевом регистре общего назначения.

Конвейерные конфликты и методы борьбы с ними

Идеальный конвейер выдаёт одну завершённую инструкцию за такт (значение IPC равно единице). На практике возникают ситуации, называемые конфликтами (hazards). Выделяют три типа таких препятствий.

Структурные конфликты появляются, когда двум разным ступеням конвейера одновременно требуется один и тот же аппаратный ресурс, например общая шина памяти. Этот вопрос решают разделением кэша команд и кэша данных на гарвардский манер.

Конфликты по данным происходят, когда следующая инструкция зависит от результата предыдущей, который ещё не дошёл до стадии обратной записи. Простейшее решение — вставить пустые такты задержки (пузыри конвейера, NOP). Более эффективный инженерный подход — пересылка данных (data forwarding). Специальные мультиплексоры перехватывают свежий результат прямо с выхода АЛУ стадии EX и передают его на вход АЛУ следующей команды в обход регистрового файла.

Конфликты управления вызваны инструкциями ветвления (beq, bne, jal). Процессор должен решить, куда двигаться дальше по коду, но результат проверки условия в АЛУ станет известен лишь на третьем этапе конвейера. Без специальных мер конвейер вынужден простаивать два такта на каждом ветвлении.

Иерархия памяти и кэширование

С 1980-х годов скорость вычислений процессоров росла гораздо быстрее скорости доступа к оперативной памяти DRAM. Этот разрыв в индустрии называют «стеной памяти» (memory wall). Если ядро работает на частоте три-четыре гигагерца, а оперативная память отвечает через шестьдесят наносекунд, процессор потратит около двухсот тактов в пустом ожидании данных на каждую операцию чтения.

Для преодоления этой задержки прямо на кремниевый кристалл процессора помещают быструю статическую память (SRAM) — кэш. Она потребляет больше транзисторов и занимает драгоценную площадь кристалла, но работает на частоте ядра. Кэш-память организуют в несколько уровней. Кэш первого уровня L1 самый маленький (обычно 32-64 КБ на ядро), но отвечает за 1-4 такта. Уровень L2 достигает сотен килобайт или нескольких мегабайт с задержкой около 10-14 тактов. Общий для всех ядер кэш L3 имеет объём от десятков до сотен мегабайт и задержку в 30-50 тактов.

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

Предсказание переходов и спекулятивное исполнение

В реальных программах ветвления встречаются каждые пять-шесть инструкций. Чтобы конвейер не простаивал в ожидании результата сравнения условий, инженеры разработали блоки предсказания ветвлений (Branch Prediction Unit, BPU). Простейшие статические схемы предполагают, что переходы назад (циклы) всегда выполняются, а переходы вперёд — нет. Современные динамические предсказатели запоминают историю конкретных инструкций ветвления.

Для этого используют таблицы насыщающихся двухбитных счётчиков, корреляционные предикторы и алгоритмы вроде TAGE. Счётчик может находиться в четырёх состояниях: уверенно выполняется, слабо выполняется, слабо не выполняется, уверенно не выполняется. Чтобы предсказание сменилось, условие должно нарушиться дважды подряд, благодаря чему случайный выход из длинного цикла не ломает общую статистику.

Опираясь на предсказание, процессор начинает вычислять инструкции по предполагаемой ветке заранее. Это называют спекулятивным исполнением. Вычислительные узлы производят действия, но складывают результаты во временный буфер переупорядочивания (Reorder Buffer, ROB). Если прогноз подтверждается, данные из ROB фиксируются в регистрах за один такт. Если предсказатель ошибся, процессор сбрасывает все спекулятивные результаты (pipeline flush), возвращает состояние регистров назад и начинает загрузку верной ветки с потерей пятнадцати-двадцати тактов.

Частые вопросы

Почему регистр x0 в RISC-V жестко привязан к нулю?

В кремнии аппаратный ноль упрощает схемотехнику ядра. С его помощью команда очистки регистра, запись безусловного перехода или операция NOP реализуются обычными командами сложения или записи без проектирования дополнительных инструкций под каждый частный случай.

В чем физическая разница между транзисторами в логике и транзисторами в памяти?

В логических вентилях и статической памяти SRAM транзисторы кремниевого ядра работают на высокой скорости и занимают значительную площадь. В оперативной памяти DRAM ячейка состоит всего из одного транзистора и миниатюрного конденсатора, который быстро теряет заряд и требует регулярной регенерации, поэтому DRAM компактнее и дешевле, но значительно медленнее.

Что происходит при аппаратном сбросе конвейера?

Процессор признает недействительными все команды, выбранные по ошибочно предсказанному пути. Инструкции внутри буфера ROB помечаются как аннулированные, результаты расчетов не попадают в архитектурные регистры, а счетчик команд принудительно переключается на верный адрес правильной ветки ветвления.

Зачем современным процессорам нужно внеочередное исполнение?

Внеочередное исполнение (Out-of-Order execution) позволяет процессору сканировать поток инструкций на десятки команд вперед. Если текущая команда ждет данные из оперативной памяти, планировщик находит независимые от нее инструкции дальше по коду и загружает ими свободные вычислительные блоки АЛУ.