TL;DR:
Статья — про вскрытие и разбор кристалла Intel 8087 с фокусом на его высокоскоростном сдвигателе, который занимал заметную площадь и сильно влиял на производительность операций с плавающей запятой. В ней показано, как двухступенчатая схема (сдвиг на 0–7 бит и на 0–7 байт) позволяет за один проход выполнять сдвиг на 0–63 бита, что нужно для выравнивания мантисс, нормализации и алгоритмов вроде CORDIC.
Автор объясняет, как это реализовано на проходных NMOS-транзисторах, почему сдвигатель сделали двунаправленным с помощью драйверов по краям, и как мультиплексоры/дешифраторы выбирают величину сдвига из разных источников. В финале узнаем, почему такие решения дали 8087 большой выигрыш и как аппаратная плавающая запятая позже стала частью самих процессоров.
Числа с плавающей запятой очень полезны �� научных вычислениях, но ранние микропроцессоры напрямую поддерживали только целые числа.(примеч.1) Хотя вычисления с плавающей запятой были обычным делом на мейнфреймах ещё в 1950–1960-х, лишь в 1980 году Intel представила сопроцессор 8087 для вычислений с плавающей запятой в микрокомпьютерах.(примеч.2) Установка этого чипа в микрокомпьютер вроде IBM PC ускоряла операции с плавающей запятой до 100 раз. Это давало огромный выигрыш для приложений вроде AutoCAD, электронных таблиц или авиасимуляторов.(примеч.3) Обратная сторона — 8087 стоил сотни долларов.(примеч.4)
Быстро и при этом точно реализовать операции с плавающей запятой непросто. Проблемы могут возникать из-за переполнений, округления, трансцендентных операций и множества пограничных случаев. До появления 8087 у каждого производителя была своя несовместимая, «на коленке» собранная реализация вычислений с плавающей запятой. Intel же привлекла эксперта по численным методам Уильяма Кэхэна, чтобы спроектировать точную арифметику с плавающей запятой на строгих принципах.(примеч.5) Результатом стала архитектура вычислений с плавающей запятой в 8087. Позже она легла в основу стандарта IEEE 754, который используется почти во всех современных компьютерах, поэтому я считаю 8087 одним из самых влиятельных чипов в истории.

Чтобы разобраться, как работает 8087, я вскрыл чип и снял кремниевый кристалл под микроскопом. 8087 содержит 40 000 транзисторов и по сути доводил технологию производства микросхем до предела; для сравнения, «соседний» микропроцессор 8086 содержал лишь 29 000 транзисторов. Чтобы сделать 8087 возможным, Intel разработала новые приёмы. В этой статье я сосредоточусь на высокоскоростном сдвигателе (он обведён красным выше). Сдвигатель занимает значительную часть площади кристалла, поэтому уменьшение его размеров было критически важным для того, чтобы 8087 вообще удалось реализовать.
Число с плавающей запятой состоит из из значащей части (мантиссы), порядка (показателя степени) и бита знака. (Всё это выражается в двоичном виде, но для аналогии с десятичной записью: в числе 6,02×10^23 величина 6,02 — это значащая часть (мантисса), а 23 — показатель степени (экспонента). Схемы обработки дробной части находятся в нижней части фотографии кристалла. Слева направо этот тракт включает ПЗУ констант, сдвигатель (выделен), сумматоры/вычитатели и стек регистров. Схемы обработки экспоненты расположены в середине чипа. Над ними микрокодовый «движок» и ПЗУ управляют работой всего чипа.
Сдвигатель
Задача сдвигателя — сдвигать двоичные числа влево или вправо, и в операциях с плавающей запятой это играет сразу несколько критически важных ролей. При сложении или вычитании двух чисел с плавающей запятой их нужно сдвинуть так, чтобы двоичные точки совпали. (Двоичная точка — это как десятичная, только для двоичного числа.) Трансцендентные инструкции 8087 построены вокруг операций сдвига и сложения и используют алгоритм под названием CORDIC. Сдвигатель также применяется, чтобы собрать число с плавающей запятой из 16-битных фрагментов, считываемых из памяти.(примеч.8)
Поскольку сдвиги настолько важны для производительности, в 8087 используется «бочкообразный сдвигатель» (barrel shifter), который умеет сдвигать число на любое количество бит за один шаг.(примеч.6) Intel применила двухступенчатую конструкцию сдвигателя: она удерживала размеры в разумных пределах, сохраняя при этом высокую скорость. Первая ступень сдвигает значение на 0–7 бит, а вторая — на 0–7 байт. В сумме эти две ступени позволяют сдвигать значение на любое число бит от 0 до 63.
Битовый сдвигатель
Начну с описания битового сдвигателя, который выполняет сдвиг на 0–7 битовых позиций. Диаграмма ниже показывает структуру битового сдвигателя, демонстрируя пять входов и выходов; полный сдвигатель имеет ширину 68 бит.(примеч.7) Идея в том, что при активации определённого столбца входные сигналы (значение) подключаются к выходам со сдвигом. Каждый кружок обозначает транзистор, который может выступать в роли ключа между входной и выходной линией. Вертикальные линии выбора используются, чтобы включать нужные транзисторы. Каждая входная линия по диагонали подключена к восьми транзисторам, что позволяет направить её на один из восьми выходов. Например, на диаграмме активирована линия выбора «сдвиг на 3», поэтому связанные с ней транзисторы (зелёные) открыты. Выделенный вход 20 (оранжевый) направляется на выход 23 (синий). Аналогично подключаются и остальные входы к соответствующим выходам — в итоге получается сдвиг на 3. Если активировать другую линию выбора, вход буд��т сдвинут на другое значение в диапазоне от 0 до 7 бит.

Чтобы объяснить внутреннее устройство сдвигателя, начну с NMOS-транзисторов, использованных в чипе 8087. Транзисторы формируются за счёт легирования участков кремниевой подложки примесями, создающими «диффузионные» области с разными электрическими свойствами. Транзистор можно рассматривать как ключ, который управляет током между двумя областями, называемыми истоком и стоком. Транзистор управляется затвором — он сделан из особого вида кремния, называемого поликремнием, и расположен над кремниевой подложкой. При подаче напряжения на затвор ток может течь между истоком и стоком; без этого ток блокируется. Транзисторы соединяются между собой верхним металлическим слоем, образуя сложную интегральную схему.

На фотографии ниже показан транзистор в 8087 таким, каким его видно ��од микроскопом. Его структура соответствует схеме выше, хотя форма у него более сложная. Исток, затвор и сток продолжаются за пределы кадра и подключены к другим транзисторам. Кроме того, проводники в металлическом слое соединяются с кремнием через круглые переходные отверстия (виа). (Для этой фотографии металлический слой был удалён кислотой.)

Если уменьшить масштаб, диаграмма ниже показывает часть битового сдвигателя в том виде, как он реализован на кристалле. На этой фотографии — около 48 транзисторов, похожих на показанный выше. Оранжево-жёлтая диагональ соответствует одному из входов: оранжевые области показывают транзисторы, соединённые через кремний, а жёлтые линии — соединения в металлическом слое. (Металлический слой используется, чтобы «перепрыгивать» через поликремниевые линии выбора.) Зелёным выделена поликремниевая линия управления «сдвиг на 3». В центре эта поликремниевая линия затвора открывает транзистор, соединяя вход с длинной жёлтой выходной линией и сдвигая выделенный вход на три позиции. (Остальные, не выделенные входы сдвигаются аналогично.) Таким образом, эта схема реализует сдвигатель так, как было описано в начале раздела. На фото показаны шесть из 68 входов, поэтому полный сдвигатель значительно выше.

Байтовый сдвигатель
Байтовый сдвигатель сдвигает входные данные (биты) на величину, кратную восьми. Его конструкция похожа на битовый сдвигатель, но каждый вход подключён к каждому восьмому выходу. Например, вход 20 подключён к выходам 20, 28, 36 и так далее — то есть сдвиг выполняется по байтам. Из-за этого диагональные соединения получаются более крутыми и расположены очень плотно: между каждым «ключом» проходят восемь линий. На диаграмме ниже выбрана линия «сдвиг на 4», и подсвечено соединение от входа 0 к выходу 32. Обратите внимание, что в правой половине диаграммы нет проводников: любой бит, который при сдвиге выходит за пределы диапазона входов, обнуляется. Например, при сдвиге влево на 4 байта в младшие разряды автоматически заносятся нули (т.е. младшие 32 бита обнуляются).

Фотография кристалла ниже показывает часть битового и байтового сдвигателей. Здесь более общий масштаб: отдельные транзисторы едва различимы. Область битового сдвигателя плотно набита транзисторами, тогда как байтовый сдвигатель состоит в основном из проводников, а между ними расположены столбцы транзисторов.(примеч.9) Также обратите внимание, что в верхней части байтовый сдвигатель частично «пустой», а ближе к низу заполняется дополнительной разводкой. Топология проводников устроена не так аккуратно, как на диаграмме выше, но оптимизирована под максимальную эффективность.

Двунаправленные драйверы
Пока что битовый и байтовый сдвигатели сдвигают биты только в одном направлении.(примеч.11) Но на практике сдвиг нужен в обе стороны. Одна из ключевых инноваций сдвигателя 8087 — двунаправленная конструкция: данные можно пропускать через сдвигатель «в обратную сторону», чтобы сдвигать биты в противоположном направлении. Это возможно потому, что сдвигатель построен на передающих транзисторах, а не на логических элементах. Логика на проходных транзисторах использует транзисторы как ключи, которые пропускают или блокируют сигнал, поэтому сигнал может идти в обе стороны. (В отличие от обычных логических элементов вроде NOR, у которых есть чётко определённые входы и выходы.)
Специальные драйверные схемы слева и справа от сдвигателя позволяют ему работать в обоих направлениях. Чтобы передать данные слева направо, левый драйвер считывает данные с шины мантиссы (значащей части) и подаёт их в сдвигатель. Правый драйвер принимает сдвинутые данные, временно захватывает их защёлкой, а затем записывает обратно на шину дробной части. Чтобы передавать данные в противоположном направлении, драйверы меняются ролями: правый драйвер подаёт данные с шины дробной части в сдвигатель, а левая схема принимает сдвинутые данные.(примеч.10)
Мультиплексоры / дешифраторы
Последняя особенность, о которой я расскажу, — это схемы управления сдвигателем. Сколько позиций сдвигать, задаётся из трёх разных источников. Во-первых, нужное число может напрямую задать блок микрокода. Во-вторых, число может приходить из счётчика цикла — это используется в трансцендентных алгоритмах CORDIC. Наконец, число может поступать из счётчика ведущих нулей; это позволяет нормализовать числа, убирая ведущие нули сдвигом. Каждый из источников выдаёт 6-битное значение сдвига; шесть мультиплексоров выбирают по одному биту из нужного источника.(примеч.12)

Далее дешифраторы активируют одну из восьми линий битового сдвига и одну из восьми линий байтового сдвига, чтобы управлять соответствующими проходными транзисторами в сдвигателе. (Каждый дешифратор получает 3-битный вход и активирует одну из 8 выходных линий). Поскольку каждая линия дешифратора управляет большим столбцом проходных транзисторов в сдвигателе, в дешифраторе используются сравнительно крупные силовые транзисторы.(примеч.13) Внизу из схемы выходят 16 управляющих линий.
Заключение
8087 — сложный чип со множеством функциональных блоков. Однако если внимательно рассматривать кристалл, схемы 8087 можно понять. В этой статье я описал быстрый «бочкообразный» сдвигатель 8087, способный за один раз выполнять сдвиг до 63 бит.(примеч.14) Intel получила патент на этот инновационный программируемый двунаправленный сдвигатель.
Сдвигатель был лишь одной из особенностей, благодаря которым 8087 выполнял операции с плавающей запятой намного быстрее, чем это мог 8086. 8087 обрабатывает вместо 16 сразу 80 бит. В 8087 есть регистры шириной 80 бит, что уменьшает число обращений к памяти во время вычислений. 8087 хранит константы для трансцендентных операций в ПЗУ, также снижая обращения к памяти. Аппаратная часть 8087 проверяет NaN, пониженное переполнение (underflow — выход за нижний предел диапазона), переполнение (overflow) и т. п., избавляя от медленных проверок в коде. Аппаратная реализация в 8087 ускоряет умножение и деление. Я не знаю, каков вклад каждого фактора по отдельности, но вместе они резко повышают производительность вычислений с плавающей запятой — вплоть до 100 раз.
Преимущества аппаратной поддержки вычислений с плавающей запятой настолько велики, что Intel начала встраивать блок вычислений с плавающей запятой прямо в процессор, начиная с 80486 (1989). Сейчас большинство процессоров включает такой блок, и покупка отдельного сопроцессора для плавающей запятой осталась в прошлом.

Примечания и источники (осторожно, много текста)
1. Даже без аппаратной поддержки плавающей запятой ранние микрокомпьютеры могли выполнять операции с числами с плавающей запятой. Такие операции раскладывались на множество целочисленных, при необходимости манипулируя порядком (экспонентой) и мантиссой (значащей частью). Иными словами, поддержка плавающей запятой не делала такие вычисления возможными — она просто делала их намного быстрее. (Ещё один способ представлять нецелые числа — фиксированная запятая, когда число знаков после десятичной точки фиксировано. Формат с фиксированной запятой проще, чем плавающая, но не способен охватить столь же широкий диапазон.)
2. 8087 не был первым чипом для вычислений с плавающей запятой. National Semiconductor представила MM57109 Number Cruncher Unit (да, это реальное название) в 1977 году. По сути это был перепакованный чип 12-значного научного калькулятора, работавший с двоично-кодированными десятичными (BCD) значениями, причём числа вводились в обратной польской записи. Этот чип был абсурдно медленным: например, вычисление тангенса могло занимать больше секунды. AMD представила свой чип для плавающей запятой Am9511 в 1978 году (подробности). Он поддерживал 32-битные числа с плавающей запятой и тратил на тангенс до 1,4 миллисекунды. (В итоге Intel лицензировала Am9511 у AMD и продавала его как 8231.) Для сравнения, 10-мегагерцевый 8087 мог вычислить тангенс за 54 микросекунды, работая с 80-битным числом с плавающей запятой. Таким образом, производительность и точность 8087 были существенно выше, чем у предыдущих чипов.
3. В исходном IBM PC (1981) на материнской плате было пустое посадочное место (сокет) под сопроцессор 8087 — это давало огромный выигрыш для приложений вроде AutoCAD. Большой пустой разъём виден вверху слева на фотографии ниже, над микропроцессором 8088. Список приложений с поддержкой 8087 приведён здесь.

4. Мне не удалось найти исходную цену 8087, но он был дорогим. Сначала Intel продавала 8087 только в виде подобранной и протестированной пары вместе с 8088 — из-за нестабильности по таймингам у 8087. К 1982 году Intel снизила цену 8087 до 230 долларов, что в пересчёте на сегодняшние деньги соответствует примерно 500 долларам. На фоне нынешнего open-source мира выглядит странно, что клиентам приходилось отдельно платить и за программную поддержку: использование 8087 с языком BASIC стоило ещё 150 долларов, а библиотека разработки Intel для 8087 — 1250 долларов.
5. Разработчики 8087 так прокомментировали советы профессора Кэхэна: «Мы справились не так хорошо, как он хотел, но лучше, чем он ожидал». Позднее Кэхэн получил премию Тьюринга за свою работу по арифметике с плавающей запятой.
6. Процессоры часто включают разные инструкции сдвига, включая циклические сдвиги (rotate), которые переносят биты с одного края слова на другой. 8087 выполняет только обычные сдвиги, без циклических.
7. Сдвигатель обрабатывает 64-битную мантиссу (значащую часть) 8087, а также три дополнительных бита для точности округления, то есть поддерживает 67 бит. Если я не ошибся в подсчёте, у сдвигателя есть ещё один дополнительный бит в позиции старшего разряда, так что его ширина составляет 68 бит.
8. Умножение и деление активно используют сдвиги: умножение выполняется сдвигами и сложениями, а деление — сдвигами и вычитаниями. Однако 8087 не использует для этих операций универсальный сдвигатель, а имеет специализированные сдвигатели, оптимизированные именно под умножение и деление.
9. Чтобы упаковать проводники как можно плотнее, в сдвигателе чередуются линии диффузии в кремнии и линии поликремния. На фотографии ниже диффузионные проводники выглядят розоватыми, а поликремний — желтоватым. 8087 был изготовлен по техпроцессу Intel HMOS III, который требовал расстояния 4 мкм для поликремния и 5 мкм для диффузии — вероятно, из-за ограничений разрешения фотолитографии того времени. Однако зазор между диффузионной линией и поликремниевой линией мог быть существенно меньше — вероятно потому, что они формировались разными масками и находились на разных слоях. Поэтому чередование линий диффузии и поликремния позволяло укладывать их очень плотно, экономя площадь.

10. В драйверных схемах есть несколько тонкостей. Вместо того чтобы подавать данные прямо в сдвигатель, биты передаются в два шага. Сначала линии сдвигателя предзаряжаются до высокого уровня. Затем любые входы с битом 1 заставляют соответствующие линии сдвигателя стягиваться вниз, к низкому уровню. Иными словами, линии сдвигателя работают в «активном нуле» (active-low): низкое напряжение означает 1. Поскольку все неиспользованные выходы сохраняют высокий уровень (то есть бит 0), нули автоматически «подмешиваются» в младшие разряды при сдвиге. Думаю, предзаряд также лучше соответствовал NMOS-схемотехнике, которая умеет тянуть сигнал вниз лучше, чем вверх, поэтому предзаряд линий помогал производительности — особенно с учётом их сравнительно большой ёмкости. Защёлка между сдвигателем и шиной дробной части предотвращает нежелательный цикл, при котором сдвинутые данные сразу же потекли бы обратно в сдвигатель и были бы сдвинуты повторно.
11. Это примечание поясняет разницу между физическим и логическим сдвигом. На кристалле тракт обработки дробной части расположен так, что старший бит находится внизу. При пропускании данных через сдвигатель слева направо биты физически смещаются вниз. Логически это соответствует ��двигу двоичного числа влево — биты переходят в более старшие разряды. В обратном направлении, при прохождении данных через сдвигатель справа налево, выполняется сдвиг вправо.
12. Направление «влево/вправо» также должно выбираться из одного из трёх источников значения сдвига, но я пока не нашёл на кристалле соответствующую схему.
13. Каждый дешифратор по сути состоит из восьми элементов NOR: семь из них будут стянуты к низкому уровню, и только тот, у которого все входы равны нулю, останется на высоком уровне. Однако реализовано это не как обычный логический элемент. Вместо этого все выходы сначала предзаряжаются до высокого уровня, а затем семь «ненужных» выходов стягиваются вниз. Такая динамическая логика с предзарядом используется и в современных схемах; см. книгу Synchronous Precharge Logic. Мультиплексоры тоже реализованы с использованием логики предзаряда.
14. В процессорах Intel x86 «бочкообразный» сдвигатель появился лишь в 80386 (1985): там был 64-битный barrel shifter. До этого 8086 и его потомки сдвигали по одному биту за раз, поэтому сдвиги на большое число позиций выполнялись заметно медленнее.
Читайте другие выпуски:
Невидимая оборона 386: как защищены входы и выходы процессора
Как сделать реверс-инжиниринг аналоговой микросхемы: FM-радиоприёмник TDA7000
Схемотехника стека сопроцессора Intel 8087 для чисел с плавающей запятой: реверс-инжиниринг
Два бита на транзистор: ПЗУ микрокода повышенной плотности в FPU-сопроцессоре Intel 8087
Если после разбора кристалла хочется перейти от «смотрю под микроскопом» к «понимаю и воспроизвожу», присмотритесь к курсу "Reverse engineering". Он учит читать поведение кода после компиляции, разбирать PE и распаковку, а затем применять это для поиска уязвимостей и анализа реального вредоноса в проектной работе. Готовы к обучению? Пройдите вступительный тест.
Для знакомства с форматом обучения и экспертами приходите на бесплатные демо-уроки:
29 января 20:00. «Как разыменовать NULL-указатель, если очень хочется». Записаться
4 февраля 20:00. «Eclipse Memory Analyzer (MAT): помощь в работе с heap». Записаться
9 февраля 20:00. «Lock-free в C++: Без блокировок к высокой производительности». Записаться
