## 15. Семейства микропроцессоров

### 15.1. Семейство DEC Alpha

Процессор Alpha был лидером по производительности со времен своего производства в 1992 году. Процессор характеризовался непревзойденным к тому времени временем цикла, что обеспечивалось чистой RISC архитектурой, ведущими технологиями проектирования. Целью проекта было достижение абсолютного превосходства по производительности. Процессор Alpha и заложенные в нём концепции прямо или косвенно оказали влияние на конструкцию других процессоров и на развитие отрасли в целом.

Рассмотрим характеристики процессора Alpha 21264. Процессор имеет типичную RISC архитектуру, является суперскалярным, поддерживает выполнение не по порядку и спекулятивное выполнение. Выполнение не по порядку подразумевает, что инструкции могут выполняться в порядке, отличающемся от порядка выборки. Также используется спекулятивное выполнение: процессор спекулятивно выбирает и выполняет инструкции, даже не зная наверняка, потребуется ли эти инструкции в дальнейшем. Такой подход особенно эффективен в случае инструкций ветвления: процессор предсказывает направление перехода и начинает выполнять инструкции предсказанной ветви. Сложная логика предсказания ветвлений вместе с спекулятивным и динамическим выполнением извлекает большую часть параллелизма уровня команд.

На рис. 15.1 показан конвейер процессора 21264. Стадия 0 – это стадия выборки, которая передает за такт 4 инструкции из кэша. Стадия 1 распределяет инструкции по очередям целочисленных команд и команд с плавающей точкой. Стадия 2 (стадия переименования) отображает виртуальные регистры инструкций на физические регистры и выделяет новые регистры для результатов инструкций. Стадия выдачи (стадия 3) поддерживает очереди, из которых динамически извлекаются и передаются на выполнение до 6 инструкций. Здесь имеет место переупорядочение инструкций. Стадии 4, 5, 6 осуществляют выполнение инструкции.

Рис. 15.1 – Конвейер Alpha 21264

Стадия выборки – это начальная стадия конвейера. Здесь выбирается 4 инструкции. В процессоре использовано много архитектурных техник, направленных на увеличение эффективности данной стадии. Одна из техник – большой 64 Кб 2-входовой кэш инструкций, который позволяет повысить количество попаданий.

2-входовой кэш реализуется на основе кэша прямого отображения, при этом дополнительно используется предсказание подмножества и строки кэша. Каждый блок выборки содержит предсказание строки и подмножества. Это предсказание указывает, откуда должен быть выбран следующий блок инструкций. Тренировка 2-битовых предсказателей осуществляется динамически в случае промахов. Стоимость ошибочного предсказания – 1 такт.

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

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

На рис. 15.2 показана структура соревнующегося предсказателя.

Рис. 15.2 – Соревнующийся предсказатель ветвлений

Составные элементы: локальная таблица истории переходов на 1024 записи по 10 бит, локальный предсказатель (насыщающийся счетчик) на 1024 записи по 3 бита, глобальный предсказатель на 4096 записей по 2 бита, предсказатель выбора на 4096 записей по 2 бита (он выбирает локальную или глобальную информацию нужно использовать). Локальная история определяет индекс одного из счетчиков, например ветвь, всегда переходящая в одном направлении будет иметь историю 0000000000 и указывать на счетчик c индексом 0. Другой пример: ветвь (a%2 = 0) выбирается поочередно и имеет истории 0101010101 и 1010101010 в разные моменты времени, в результате счетчики по этим адресам будут «натренированы» для правильного предсказания в зависимости от предыдущего состояния. Аналогично отслеживается «путь» (глобальная история на 12 бит), и в результате выбирается 1 из 4096 счетчиков. Некоторые ветви лучше предсказываются локальными таблицами, другие глобальными, поэтому на основе «пути» выбирается ещё один счетчик, который обучен на выбор локальной или глобальной таблиц.

Переименование регистров и выполнение не по порядку

Для результата каждой инструкции отводится отдельный регистр, что позволяет запускать инструкции не по порядку и устраняет WAR- и WAW-зависимости. В результате повышается уровень использования параллелизма команд. На стадии завершения выполнения инструкции регистр становится частью архитектурных регистров.

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

Используется схема Томасуло. Инструкции, ожидающие операндов содержатся в очередях (20 в целочисленной, 15 – в очереди с плавающей точкой). По готовности операнды транслируются в эти очереди. За такт на выполнение может подаваться 4 целочисленные инструкции и 2 с плавающей точкой. При этом подаются те, которые ждали дольше остальных.

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

Выполнение

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

Решением стало дублирование регистрового файла из 80 записей на 2 кластера. К каждому кластеру обращаются по 2 конвейера. При этом в течение одного такта изменения из одного кластера транслируются в другой.

Очередь выдачи инструкций планирует выдачу инструкций так, чтобы минимизировать издержки копирования между кластерами.

Система памяти

Система памяти обрабатывает до 2 запросов от целочисленных конвейеров на каждом такте. Т.е. обращения к кэшу происходят дважды за такт. Это реализовано с помощью конвейеризации кэша.

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

При загрузке данных из кэша требуется 3 такта для определения попадания/промаха. За это время запускаются потребители данных инструкций, к которым данные в случае попадания будут доставлены по путям ускоренной пересылки. Более того, будут запущены даже потребители потребителей. В результате, если произойдет промах кэша, то несколько некорректных инструкций будут находиться на стадии выполнения. Чтобы не очищать весь конвейер используются микроспекуляции внутри конвейера: последние такты конвейера аннулируются, а выданные инструкции, использующие результат «промаха» заносятся обратно в очередь инструкций.

Предусмотрена не только аппаратная, но и программная предвыборка кэша: специальные инструкции могут загружать блоки, удалять блоки из кэша, заменять без считывания.

Виртуальная память

Alpha – это 64-битный процессор, в котором произвольное количество бит в пределах 43-64 может транслироваться в зависимости от реализации. Используются раздельные кэши данных и команд, раздельные TLB данных и команд. Управление TLB осуществляется программно с помощью Privilege Access Library (PAL предоставляет собой интерфейс к возможностям процессорной архитектуры, которые характерны для всего семейства, фактически это замена микрокода и ОС «не знает» о конкретной реализации).

Виртуальное адресное пространство разбито на 3 части: seg0, seg1, kseg, что определяется двумя старшими битами адреса. Seg0, seg1 – это пользовательские пространства, в них используется TLB для трансляции. Kseg – это пространство ядра, оно отображается напрямую. Старшие два бита физического адресного пространства в зависимости от реализации определяют поведение регионов: разрешение/запрет кэширования, пространство ввода/вывода и т. д.

В записи TLB есть поле ASID, определяющее процесс, которому разрешен доступ к странице. Кроме того, есть флаг Global bit, который указывает, что проверку ASID для данной страницы выполнять не надо, т.е. это разделяемая страница. Под поле ASID выделено мало бит (7), если процессов больше, чем ASID-ов, то приходится выполнять очистку TLB для предотвращения коллизий. GH (Granularity Hint) – 2-битовое поле, указывающее на гранулярность записи TLB (8^GH). В результате запись TLB может указывать на 1, 8, 64 или 512 последовательных страниц. Так организуются суперстраницы.

### 15.2. Семейство PA-RISC

С 1986 г. Фирма Hewlett-Packard Microprocessors выпускает процессоры семейства PA-RISC (Precision Architecture RISC). Основное отличие моделей этого семейства – высокая надежность и точность вычислений, которая достигается благодаря повсеместному использованию систем контроля и кодирования, обеспечивающих обнаружение и исправление ошибок (Error Correction Codes). Архитектура в первую очередь ориентирована на серверные системы. PA-RISC уникальна по способу организации адресного пространства и защиты памяти. Процесс может получить доступ к странице, если один из его 8 ID-ов совпадает с ID-ом страницы. Таким образом может осуществляться доступ к странице для небольшой группы процессов, что существенно отличается от распространенного подхода «один или все».

Рассмотрим характеристики последнего из процессоров семейства (PA-RISC 8900). Процессор включает 20 функциональных модулей: 4 целочисленных АЛУ, 4 модуля сдвига/объединения, 4 конвейера загрузки/сохранения, 4 устройства умножения/сложения чисел с плавающей точкой, 4 устройства деления/вычисления квадратного корня с чисел плавающей точкой. 2 адресных сумматора. У процессора два ядра. Полностью ассоциативный двухпортовой TLB на 240 записей в каждом ядре. По BTAC (branch target address cache) на 32 записи в каждом ядре. Статическое и динамическое предсказание ветвлений. Кэш данных и кэш инструкций L1 0.75 Мб на ядро. L2 кэш за пределами чипа 64 Мб. Контроль ошибок для L2 и тэгов. MMX2. 44-битный физический адрес, 64-битный виртуальный адрес, максимальный размер страницы – 4Гб. Тактовая частота до 1.1 ГГц.

Структурная схема процессора PA 8700 показана на рисунке 15.3.

Рис. 15.3 – Структура PA-RISC 8700.

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

Рассмотрим конвейер PA-RISC 8700. На стадии выборки за каждый такт из кэша извлекаются 4 инструкции. Отсюда инструкции передаются в устройство сортировки, которое размещает инструкции в зависимости от типа либо в буфере переупорядочения АЛУ на 28 записей, либо в буфере переупорядочивания памяти на 28 записей. Буфер переупорядочения постоянно сканирует свое содержимое на предмет наличия инструкций готовых к выдаче. Кроме того, буфер отслеживает зависимости между инструкциями. Здесь инструкции уже более не ограничены порядком программы. Вместо этого, как только определяется, что инструкция готова к выполнению и соответствующий функциональный модуль доступен, инструкция передается на этот модуль для выполнения. За каждый цикл по модулям распределяются до 4 инструкций. PA-8700 содержит 10 функциональных модулей для увеличения количества одновременно выполняемых инструкций: 4 целочисленных (2 АЛУ, 2 сдвиговых устройства), 4 модуля с плавающей точкой (2 умножителя с аккумуляцией, 2 устройства деления, вычисления квадратного корня), 2 устройства загрузки/сохранения. После завершения выполнения инструкции подаются в модуль Retire, где переупорядочиваются и обновляют состояние процессора.

Представители семейства PA-8x00 характеризуются большими высокопроизводительными кэшами. Преимущества очевидны: небольшие кэши могут хорошо работать для небольших тестовых наборов. Однако на более требовательных приложениях, например, обработке большого количества запросов, транзакций, технических вычислениях, их производительность оставляет желать лучшего. PA-8700 использует 4-входовые кэши инструкций и данных. Суммарный объем кэшей на кристалле – 2,25 Мб. В результате даже для требовательных приложений, в частности серверной логики, достигается высокая производительность.

Кэш данных представляет собой 4-входовой конвейеризированный кэш и содержит два независимых банка: один содержит слова с четными адресами, второй с нечетными. Каждый банк на 0,75 Мб реализован в виде 4 массивов, которые независимо выдают двойное слово данных и биты для исправления ошибок. Каждый массив предоставляет одну степень ассоциативности. Конвейеризированный 4-входовой кэш инструкций размером 0,75 Мб выдает 4 инструкции за такт устройству выборки.

Семейство PA-RISC всегда использовало логику обнаружения и исправления ошибок в кэшах с целью защиты данных. В кэше инструкций используется по одному биту четности, т.к. данные не изменяются, кроме загрузки из памяти. Если обнаруживается ошибка четности, то при выборке выдается сигнал промаха кэша и данные заново считываются из памяти. В кэше данных используется 6 проверочных битов, что позволяет исправлять 1 ошибку. Логика обнаружения и коррекции работает параллельно с основной логикой, не увеличивая критический путь, т.е. не снижая производительность.

В PA-RISC используется как статическое, так и динамическое предсказание ветвлений. В случае статического компилятор выставляет предполагаемый бит направления в инструкции перехода. Используется таблица истории переходов (Branch History Table) c двухбитовыми счетчиками. Из-за использования спекулятивного выполнения вместе с предсказанием переходов инструкции, которые были выполнены при ошибочном предсказании выбрасываются на этапе retire.

В случае промаха кэша осуществляется выборка не только нужной строки, но и следующей, которая, возможно понадобится в ближайшем будущем. При замене строки для выбора позиции используется квази-LRU алгоритм: строка для удаления выбирается среди 4 кандидатов – строк заданного адреса из 4 подмножеств кэша. В PA-RISC используется хэшированная таблица страниц с разрешением коллизий. При этом аппаратно осуществляется только первое обращение, при промахе управление передается программному обработчику.

###

### 15.3. Семейство SUN SPARC

Scalable Processor Architecture (SPARC) – спецификация архитектуры команд и регистровой организации процессора, не привязанная к аппаратной организации и предложенная компанией Sun Microsystems. Типичные RISC процессоры. Инструкции имеют одинаковую длину 32 бита. Первые модели были 32-разрядными, сейчас используется 64-битная адресация. При этом 64-битные машины могут выполнять старый 32-разрядный код.

Машина использует регистровые окна. Каждое окно содержит 24 регистра и общее количество окон варьируется в зависимости от реализации в пределах 2 – 32. На рис. 15.4 показан пример с 8 окнами, использующими в сумме 136 физических регистров. Физические регистры с 0-го по 7-ой – это глобальные регистры, которые разделяются всеми процедурами. Каждый процесс видит логические регистры с 0 по 31. Логические регистры 24 – 31 – используются для хранения входных параметров и разделяются с вызывающей процедурой, регистры с 8 по 15 – это выходные параметры, они разделяются с вызываемой процедурой (дочерней). Эти две области перекрываются с другими окнами. Логические регистры с 16 по 23, называемые локальными, не разделяются и не перекрываются с другими областями.

Рис. 15.4 – Регистровые окна в SPARC для трех процедур.

Процессор поддерживает указатель на текущее окно (CWP, current window pointer), маску валидности (WIM, window invalid mask), указывающую, какие окна не используются.

SPARC имеет типичную RISC архитектуру и характеризуется совсем небольшим количеством команд. Предусмотрены только простейшие режимы адресации (регистровый, непосредственный).

Реализация инструкций ветвления подразумевает использование 1 слота задержки. Используется 1 бит для сохранения предсказания направления ветвления компилятором. У инструкций имеется аннулирующий бит, указывающий, что инструкция в слоте задержки зависит от направления перехода: если ветвь выбрана, то результат инструкции из слота задержки сохраняется, если нет, то отбрасывается. Если бит равен 0, то инструкция не зависит от направления и результат сохраняется в любом случае.

Рассмотрим процессор Ultra SPARC III. Его структурная схема приведена на рис. 15.5.

Рис. 15.5. – Структурная схема Ultra SPARC III.

Блок вызова команд подготавливает для выполнения до четырех команд за такт. При обнаружении условного перехода происходит обращение к таблице переходов емкостью 16000 записей, на основе ее содержания принимается решение о вызове следующей команды или команды, расположенной по целевому адресу. Подготовленные команды поступают в 16-командный буфер. Из него команды попадают в блок исполнения целочисленных операций, блок исполнения операций с плавающей точкой и блок загрузки/сохранения. Блок исполнения целочисленных операций состоит из двух АЛУ и короткого конвейера для обработки команд перехода. Блок исполнения операций с плавающей точкой состоит из 32 регистров и трех независимых АЛУ, предназначенных для выполнения операций сложения/вычитания, умножения и деления, соответственно. Блок загрузки сохранения отвечает за работу с памятью. В нем обеспечено взаимодействие с тремя кэшами. Кэш данных представляет собой 4-входовой ассоциативный кэш емкостью 64 Кб и длиной строки 32 байта. Кэш предвыборки емкостью 2 Кбайт нужен для хранения данных, полученных с помощью упреждающей выборки. Кэш записи на 2 Кб используется для объединения результатов записи с целью оптимизации использования ресурсов шины кэша второго уровня.

Конвейер Ultra SPARC III состоит из 14 ступеней и в упрощенной форме показан на рис. 15.6.

Рис. 15.6 – Конвейер Ultra SPARC III

Открывает конвейер ступень A (Address Generation – формирование адреса). Здесь определяется адрес каждой последующей команды, которую предполагается выбрать.

Исходя из определенного ранее адреса на ступени P (Preliminary Fetch – предварительная выборка) вызываются команды из кэша команд первого уровня (до четырех за такт). Для выявления условных переходов и проверки правильности прогноза производятся обращения к таблице переходов. На ступени F (Fetch – выборка) завершается выборка команд и их передача в кэш команд.

На ступени B (Branch Target – обнаружение объекта перехода) происходит декодирование выбранных команд. Если среди них обнаруживаются спрогнозированные переходы, они передаются обратно на ступень А для непосредственной выборки соответствующих команд.

На ступени I (Instruction Group Formation – группировка команд) входящие команды сортируются по группам, в зависимости от того, к каким из шести функциональных блоков они обращаются: 2 целочисленных АЛУ, 2 АЛУ с плавающей точкой, конвейер переходов, устройство операций загрузки, сохранения, специальных операций.

На ступени J (Instruction Stage Grouping – извлечение команды из очереди) команды подготавливаются к отправке в блок выполнения во время следующего цикла. В течение одного цикла на ступень R можно передать до четырех команд. Выбор команд ограничен доступностью функциональных блоков. К примеру, единовременно можно вызвать две команды выполнения целочисленной операции, одну команду выполнения операции с плавающей точкой и одну команду загрузки или сохранения.

На ступени R (Register – регистр) производится поиск регистров, необходимых для обработки команд выполнения целочисленных операций, если регистр недоступен, то команда ожидает в очереди команд.

Ступень Е (Execution – выполнение) предназначена для непосредственного выполнения целочисленных команд. На ступени Е определяется направление команд условного перехода. В случае неверного прогноза сигнал отправляется обратно на ступень А и конвейер освобождается.

На ступени С (Cache – кэш) завершается доступ к кэш-памяти первого уровня. Здесь же определяются результаты команд загрузки.

На ступени M (Miss – промах) производится обработка слов, запрошенных, но не найденных в кэш-памяти первого уровня. Сначала выполняется поиск в кэше второго уровня, а в случае промаха осуществляется обращение к памяти, занимающее несколько циклов.

На ступени W (Write – запись) результаты извлекаются из специального блока и записываются в регистровый файл.

На ступени X (eXtend – продленное выполнение) завершается большинство графических команд и команд с плавающей точкой.

На ступени T (Trap – перехват) перехватываются исключения, связанные с целочисленными командами и командами с плавающей точкой.

На ступени D состояние целочисленных регистров и регистров с плавающей точкой фиксируется в соответствующих архитектурных регистровых файлах. Результаты завершенных команд сохранения записываются в кэш записи, затем они переносятся сразу в кэш второго уровня, минуя кэш первого уровня (его содержимое не пересекается с кэшем второго уровня).

Кэш инструкций индексируется физически, что позволяет избегать проблем синонимов. В случае кэша данных для предотвращения проблемы синонимов ОС размещает синонимы по границе 16 Мб, что превышает допустимый размер кэша. В организации виртуальной памяти как и в предыдущих процессорах используются ASID-ы для процессов.

###

### 15.4. Семейство Intel

Рассмотрим одну из ветвей этого семейства: IA-64. Разработка этой ветви связана с недостатками процессоров Pentium. Архитектура IA-32 – это типичная CISC-архитектура с командами разной длины, разнообразными режимами адресации, огромным количеством различных форматов, которые трудно декодировать «на лету».

IA-32 – это архитектура, ориентированная на двухадресные команды. Поскольку скорость работы процессора растет быстрее, чем памяти, положение IA-32 продолжает ухудшаться.

IA-32 содержит небольшой и нерегулярный набор регистров. Из-за этого промежуточные значения постоянно приходится записывать в память.

Из-за малого количества регистров постоянно возникают зависимости, в частности WAR, для их предотвращения используются скрытые регистры. Для сокращения числа кэш-промахов используется выполнение не по порядку. Однако IA-32 поддерживает точные прерывания, поэтому команды, выполненные не по порядку должны быть переупорядочены. Все это приводит к существенному усложнению оборудования.

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

Указанные недостатки привели к тому, что Intel предприняла попытку начать всё заново и предложить кардинально новую архитектуру.

Основной принцип организации архитектуры IA-64 сводится к переносу нагрузки с периода времени выполнения на период компиляции. Процессор Pentium 4 в ходе выполнения переупорядочивает команды, подменяет регистры, распределяет функциональные блоки и выполняет множество других функций, что ведет к максимальной загрузке всех аппаратных ресурсов. Модель, в которой аппаратный параллелизм является видимым для компилятора, называется EPIC (Explicitly Parallel Instruction Computing – вычисления с явным параллелизмом команд). EPIC представляет собой развитие идей RISC и VLIW.

Некоторые особенности IA-64 заметно повышают производительность. Среди них предикативное выполнение, спекулятивное выполнение, предсказание ветвлений (статическое и динамическое), стек регистров, вращающийся регистровый кадр.

Конвейер включает всего 8 стадий. На стадиях IPG (Instruction pointer generation and fetch) и Rot (Rotate) происходит выборка двух связок и предсказание ветвлений. На стадиях Exp (Expand) и Ren осуществляется декодирование шаблона связки, распределение инструкций по очередям, переименование регистров (для регистрового стека и вращающегося регистрового кадра) и декодирование. Reg (Register) – стадия доставки операндов. Exe (Execute), Det (Exception Detection), WRB (Write Back) – эти стадии осуществляют выполнение, перехват исключений и запись результатов в регистры, соответственно.

Параллелизм уровня команд используется в первую очередь благодаря статическому планированию. Компилятор объединяет инструкции в группы, при этом инструкции одной группы не содержат зависимостей, т.е. могут выполняться параллельно. Группы могут иметь произвольную длину. Инструкции также объединяются в 128-битные связки. Связка состоит из шаблона и 3 инструкций, при этом граница группы может располагаться внутри связки (рис. 15.7). Шаблон указывает используемые модули и положение границы групп. Инструкции делятся на 6 типов: целочисленные инструкции АЛУ, целочисленные инструкции, инструкции работы с памятью, инструкции с плавающей точкой, инструкции ветвления, расширенные инструкции.

Рис. 15.7 – Формат связки

Itanium содержит 128 регистров общего назначения, которые пронумерованы с gr0 по gr127, как показано на рис. 15.8. С каждым регистром связан бит, который указывает, свободен ли регистр или содержит валидные данные. Этот бит носит название NaT (Not-a-Thing). Он используется для спекулятивной загрузки.

Рис. 15.8 – Регистры в Itanium

Регистры общего назначения делятся на статические и стековые. Статические включают первые 32 регистра: с gr0 по gr31. Gr0 используется только для чтения и всегда возвращает 0. Попытка записи в него вызывает исключение.

Регистры с gr32 по gr127 используются в качестве регистрового стека.

Восемь 64-битных регистров ветвления, с br0 по br7, содержат целевой адрес для косвенных переходов. Эти регистры используются для указания целевых адресов инструкций условных переходов, вызовов процедур и возвратов из них.

Регистр пользовательской маски (UM) используется для управления выравниванием памяти, порядком байт (little-endian, big-endian) и т.д.

CFM (Current Frame Marker) – регистр с информацией о стеке регистров.

Itanium, следует архитектуре загрузки/сохранения, поэтому к памяти обращаются только инструкции load и store, остальные инструкции работают исключительно с регистрами. Для load и store используются три режима адресации, в них задействованы регистры r1, r2, r3:

Косвенная регистровая адресация – инструкции загрузки и сохранения используют содержимое регистра в качестве адреса памяти:

Адрес = содержимое r3.

Косвенная регистровая с использованием непосредственного значения – содержимое r3 суммируется с 9-битовой константой:

Адрес = содержимое r3 + imm9; r3 = адрес.

Косвенная регистровая с индексной – содержимое r3 суммируется с r2, в регистр r3 затем заносится полученный адрес:

Адрес = содержимое r3 + содержимое r2; r3 = адрес.

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

Для этой цели регистры делятся на статические (первые 32) и стековые (остальные). Статические регистры хранят глобальные переменные и доступны всем процедурам. Стековые регистры используются для передачи параметров и возвращаемых значений, хранения локальных переменных.

Регистровый стековый фрейм – аналог регистрового окна, содержащий локальную область (входные и временные переменные) и выходную область (возвращаемые значения). Как и в случае регистрового окна область выходных параметров вызывающей процедуры перекрывается с областью входных параметров вызываемой, т.е. не происходит физического копирования при вызовах.

Регистр CFM (Current Frame Marker) содержит два поля – размер стекового фрейма (Size of Frame, SoF) и размер области локальных переменных (Size of Locals, SoL).

Если регистров не достаточно, то используется Register Stack Engine (RSE), которая динамически перемещает не поместившиеся значения в память и загружает их при необходимости. В этом случае издержки сопоставимы с издержками Pentium при вызовах процедур.

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

1. Сокращение числа ветвлений. Лучшее решение заключается в удалении ветвлений. Однако удалить можно только определенные типы ветвлений. Удаление возможно только при поддержке со стороны архитектуры команд.

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

3. Предсказание ветвлений. Если можно предсказать, будет ли выбрана данная ветвь, то можно начать загрузку конвейера правильной последовательностью инструкций.

В Itanium сокращение числа ветвлений достигается с помощью техники, известной под названием предикативное выполнение (предикация). В ней выполнение каждой инструкции делается условным. Таким образом, инструкция выполняется только если условие истинно. Для этого каждой инструкции ставится в соответствие предикат. Если предикат имеет значение истина, то инструкция выполняется, иначе она рассматривается как пустая операция nop. Инструкции разных ветвей помечаются разными предикатами. Далее инструкции обеих ветвей выполняются одновременно, а на завершающей стадии проверяется значение предиката: если истина, то результат инструкции записывается в регистры, иначе отбрасывается, т.е. инструкция выполняет пустую операцию.

Спекулятивное выполнение – это сценарий, в котором инструкции выполняются в предположении, что они понадобятся при действительном выполнении программы. Целью является повышение производительности. Существуют две основные причины спекулятивного выполнения: поддержание заполнения конвейера и маскирование задержек обращения к памяти. В Itanium есть оба типа спекуляций (спекуляция по данным и спекуляция по управлению).

Спекуляция по данным позволяет компилятору распланировать инструкции рядом с некоторыми типами зависимостей по данным. В частности, неоднозначная зависимость по данным существует между указателями, используемыми для доступа к памяти. Обычно в таких случаях зависимость между инструкциями load и store или store и store не может быть разрешена статически во время компиляции, т.к. компилятор не знает значений указателей, поэтому для разрешения таких зависимостей используется поддержка времени выполнения.

Пример возможной зависимости между ячейками с адресами r9 и r5.

sub r6=r7, r8;; // такт 1

st8 [r9]=r6 // такт 2

ld8 r4=[r5];;

add r11=r12, r4;;// такт 4

st8 [r10]=r11 // такт 5

Пример использования спекулятивной загрузки:

ld8.a r4=[r5] // такт 0 или ранее

…

sub r6=r7, r8;; // такт 1

st8 [r9]=r6 // такт 2

ld8.c r4=[r5]

add r11=r12,r4;;

st8 [r10]=r11 // такт 3

В Itanium неоднозначность разрешается следующим образом: используются инструкции advance load и check load.

Advance load загружает данные спекулятивно, затем, если выясняется, что зависимость имеет место, то check load выполняет повторную загрузку.

Для отслеживания зависимостей используется ALAT (Advanced Load Address Table). Это таблица, индексируемая номером регистра и содержащая адрес записи. При спекулятивной загрузке туда заносится адрес, в случае записи в память по тому же адресу, ячейка очищается. Команда check load проверяет ячейку. При спекулятивной загрузке используется бит NaT (Not a Thing), если загрузка недействительна, то и все команды, использующие спекулятивный результат тоже объявляются недействительными.

Спекулятивное ветвление выполняется аналогично спекулятивной загрузке. При этом нежелательно чтобы в случае возникновения исключения, если ветвь ещё не выбрана, но спекулятивно выполняется, оно передавалось дальше. Для этого используется бит NaT, он выступает в качестве бита отравления (Poison Bit) и передается во все использующие операнд инструкции. Затем, если становится известно, что данная ветвь выбрана, то выбрасывается исключение и повторяется заданная инструкция.

Предсказание ветвлений в Itanium реализовано следующим образом. Подсказки относительно направления перехода явно задаются в инструкциях. Инструкция BWH (Branch with Hint) позволяет использовать следующие варианты предсказаний: статическое, переход; статическое, нет перехода; динамическое, переход; динамическое, нет перехода. В случае статического используется указание компилятора. В случае динамического используется как указание компилятора, так и аппаратура. Кроме того, используется двухуровневый алгоритм предсказания. Для оптимизации вызовов процедур реализован стек адресов возврата.

Для снижения потерь производительности на циклах может использоваться программная конвейеризация. Ее суть в перемещении независимых операций итерации i+1 в итерацию i. Т.е. параллелизм достигается путем группирования инструкций разных итераций. В «конвейере» выделяют три стадии: пролог, ядро и эпилог. Ядро загружено полностью. Конвейеризация требует усложнения компилятора и увеличивает размеры кода из-за дублирования.

Itanium поддерживает программную конвейеризацию аппаратно без расширения кода. Для этого используются:

1. Автоматическое переименование регистров (вращение регистров). Т.е. на 1-й итерации используется, например r32, на второй уже r33.

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

3. Специальные инструкции циклов, управляющие вращением регистров и уменьшением значения счетчика цикла.