[Перевод] HP Nanoprocessor, часть II: реверс-инжиниринг цепей на основе фотошаблонов

Первая часть

В 1974 году Hewlett-Packard разработала микропроцессор для управления различными функциями в собственных продуктах — они дисководов для гибких магнитных дисков до вольтметров. Этот простой процессор не дотягивал до обычных микропроцессоров — он даже не поддерживал сложение или вычитание — поэтому его назвали «нанопроцессор». Ключевыми особенностями Nanoprocessor были низкая стоимость и высокая скорость работы: по сравнению с современным ему Motorola 6800 стоимостью $360, Nanoprocessor стоил $15, а операции управления выполнял на порядок быстрее.

Хоть у него и не было операции сложения, Nanoprocessor мог (медленно) складывать числа путём повторного инкремента или декремента (операций, которые он поддерживал). В иных случаях, например, с вольтметром от Hewlett-Packard, к продукту добавляли чипы АЛУ (74LS181), которые занимались быстрым сложением — доступ к ним осуществлялся, как к устройствам ввода/вывода. Естественно, будучи полным по Тьюрингу, Nanoprocessor теоретически мог делать всё — от вычисления функций с плавающей запятой до запуска игры Crysis; это просто было бы очень медленно.

Фотошаблон процессора можно скачать по ссылке (122 МБ PSD).

a2d168e0c9ca183d2798580f99580f33.jpg

HP Nanoprocessor, номер части 1820–1691. Напряжение смещения, -2,5 В, написано от руки — оно меняется от экземпляра к экземпляру. Последняя цифра номера части тоже написана от руки, и обозначает скорость работы чипа.
В последующие десятилетия процессор оставался неизвестным, до тех пор, пока его разработчик, Ларри Бауэр, не поделился недавно фотошаблонами и документацией на чип с проектом The CPU Shack. Там отсканировали фотошаблоны и написали статью про Nanoprocessor. После того, как Антуан Берковичи сшил изображения в одно, я написал обзор Nanoprocessor на его основе. Это вторая часть статьи, где я обсуждаю некоторые подробности схемы Nanoprocessor, проводя реверс-инжиниринг на основе фотошаблонов. Функциональные блоки Nanoprocessor интересно изучать, поскольку он обходится минимальной реализацией необходимых функций, оставаясь при этом полезным микропроцессором.

Внутри Nanoprocessor


Как и большинство процессоров той эпохи, Nanoprocessor — восьмибитный. Однако он не поддерживает память произвольного доступа, а код исполняет из внешнего двухкилобайтного ПЗУ. У него есть 16 8-битных регистров — больше, чем у большинства процессоров, и достаточно, чтобы восполнить отсутствие памяти для многих приложений. У Nanoprocessor было 48 команд — значительно меньше, чем 72 команды у Motorola 6800. Однако у Nanoprocessor был удобный набор операций по установке, очистке и проверке битов, которых не хватало другим процессорам того времени. Также у него было несколько команд ввода/вывода, поддерживавших как порты ввода/вывода, так и контакты ввода/вывода общего назначения, благодаря чему с его помощью было легко управлять другими устройствами.

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

dc6ae08e90096b443ca24bb8e6a40272.png
Комбинированные фотошаблоны Nanoprocessor (кликабельно)

По изображению фотошаблона, приведённому выше, можно сделать вывод о простоте Nanoprocessor. Синие линии — это металлические проводники сверху чипа, зелёные — кремний с примесями. Чёрные квадраты по периметру — 40 площадок для связи с внешними контактами ИС. Небольшие чёрные участки внутри — транзисторы. Если как следует приглядеться, можно насчитать их 4639 штук.

Если учитывать, что декодер команд состоит из пар мелких транзисторов, что сделано для удобства расположения компонентов, и считать эти пары за один, то получится 3829 транзисторов. Из них 1061 — подтягивающие вверх, а 2668 — активные. Для сравнения, у микропроцессора 6502 было 4237 транзисторов, 3218 из которых активные. У 8008-го было 3500 транзисторов, а у Motorola 6800 — 4100.

На блок-схеме снизу показана внутренняя структура Nanoprocessor. В середине находятся 16 регистров хранения. Компаратор позволяет сравнивать две величины для обеспечения условного ветвления. Устройство управляющей логики занимается инкрементом, декрементом, сдвигом и битовыми операциями аккумулятора. У него нет арифметических и логических операций стандартного АЛУ. Счётчик программ (справа) извлекает команду из регистра команд (слева); у прерываний и вызовов подпрограмм есть свои стеки длиной в один элемент для хранения адресов возврата.

6f776b82f9814eccdfe30293c86defa9.jpg
Блок-схема работы из инструкции к Nanoprocessor

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

Интересным проектом для развлечения может стать создание FPGA-версии Nanoprocessor — поскольку Nanoprocessor, пожалуй, является наиболее простым вариантом настоящего коммерческого процессора. В инструкции к нему описаны все команды и даются примеры кода, который можно выполнять.

Регистры


На фото кристалла ниже видно, что значительную часть Nanoprocessor занимают его 16 регистров. С остальными компонентами они общаются посредством шины данных. Цепи сверху выбирают конкретный регистр. Регистр R0, справа, рядом с компаратором.

835493f829c92f33639a5126f14e3fc0.jpg
Значительную часть Nanoprocessor занимают его 16 регистров

Строительный блок регистра — два инвертора в цепи обратной связи, хранящие один бит так, как показано ниже. Если на верхнем проводнике 0, правый инвертор выдаст на нижний проводник 1. Тогда левый инвертор выдаст 0 на верхний проводник, завершая цикл. Цепь остаётся стабильной, «помня» 0. Таким же образом, если на верхнем проводнике 1, он инвертируется в 0 на нижнем, и обратно в 1 на верхнем. Цепь может хранить 0 или 1 таким способом, формируя 1-битовую ячейку памяти.

5c1b221470dc988927c9af8214c3cb0d.png
Два инвертора в стабильной цепи, хранящей бит

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

04c48733be79284db8b825f011fe9d42.jpg
Хранение одного бита в Nanoprocessor. Каждый бит реализован на 6 транзисторах (ячейка 6T SRAM).

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

Декодирование команд


Декодирующие цепи занимаются тем, что принимают двоичный код команды (к примеру, 01101010), и определяют, что это за команда (в данном случае — «загрузить аккумулятор из регистра 10»). По сравнению с многими процессорами, команды у Nanoprocessor довольно просты: у него их относительно немного (48), а код команды всегда — один байт. На диаграмме ниже показано, что логика декодирования команд (красная) занимает значительную часть чипа. Регистр команд (зелёный) — это набор из восьми защёлок, в которых хранится текущая инструкция. Командный регистр находятся рядом с контактами данных, на которые приходит команда из ПЗУ. В данном разделе мы разберём цепь декодирования, показанную жёлтым.

6209fcb0ddca3ab2c694bf237abe299f.jpg

Декодирование осуществляется NOR-вентилями. Каждый NOR-вентиль распознаёт определённую команду или группу команд. NOR-вентиль принимает на вход биты команды или их дополнения. Когда все входящие биты нулевые, NOR-вентиль сообщает о совпадении. Это позволяет искать совпадения как во всей команде целиком, так и в части команды. К примеру, команда «загрузить аккумулятор из регистра R» имеет двоичный формат 0110rrrr, в котором четыре последних бита обозначают нужный регистр. NOR-вентиль (bit7 + bit6' + bit5' + bit4)' совпадёт с такой командой.

Структурированный таким образом декодер команд хорош тем, что его можно собрать из компактных и повторяющихся цепей. Его часто называют ПЛМ (программируемая логическая матрица). Идея в том, что входящие сигналы на матрицу подаются по горизонталям, а выходящие — по вертикалям. На каждом пересечении может находиться транзистор, и тогда входящий сигнал является частью затвора; если транзистора нет, этот входящий сигнал игнорируется. В итоге получаются компактно расположенные NOR-вентили. В ранних микропроцессорах декодер часто делали из матрицы NOR-вентилей — к примеру, так было у 6502-го.

На диаграмме ниже с правой стороны показаны три увеличенных декодера, которые обведены жёлтым на диаграмме выше. Эта схема соответствует самому левому декодеру. Обратите внимание на соответствие транзисторов на схеме розовым пятнам транзисторов на раскладке. Идея в том, что если любой входящий сигнал активирует транзистор, то транзистор подтягивает выходной сигнал к земле. Иначе выход подтягивается вверх резистором. Инверторы снизу усиливают сигнал, благодаря чему тока оказывается достаточно, чтобы питать все восемь частей аккумулятора. Интересно, что в данной раскладке используются пары транзисторов с соединёнными землёй и выходом — я не вижу преимуществ перед простым использованием единственного транзистора. В любом случае, обратите внимание, как ПЛМ обеспечивает плотное расположение декодеров.

Обратите внимание, что инвертор в декодере команд подтягивается до 12 В, а не до 5 В. Это оттого, что в Nanoprocessor используются транзисторы с металлическим затвором вместо более передовых транзисторов с кремниевым затвором, как у других микропроцессоров той эпохи. Недостаток транзистора с металлическим затвором — повышенное пороговое напряжение, поэтому выходное напряжение транзистора получается значительно ниже, чем напряжение на затворе. Выход с обычного инвертора слишком мал для того, чтобы питать затвор проходного транзистора, поскольку у него на выходе напряжение опять упадёт. Решение — использовать для инвертеров декодера, управляющего проходными транзисторами аккумулятора, питание с 12 В. Тогда у сигналов будет достаточно напряжения для активации проходных транзисторов. Иначе говоря, Nanoprocessor нужно дополнительные 12+ В, поскольку в нём используются транзисторы с металлическим затвором вместо более передовых транзисторов с кремниевым затвором.

9519e07677244ef332fa311242fa5bd3.jpg
Одна из цепей декодера Nanoprocessor. Схема слева соответствует самому левому декодеру из трёх, показанных справа.

Данная цепь генерирует сигнал инкремента/декремента, который подаётся в цепь аккумулятора. Цепь обнаруживает совпадение, когда уровень сигналов генератора тактовой частоты, запроса, 6-го бита команды и 2-го бита команды низкий — совпадение обнаруживается в виде x0xxx0xx во время фазы выполнения. Среди таких команд — «Increment Binary» (00000000), «Increment BCD» (00000010), «Decrement Binary» (00000001) и «Decrement BCD» (00000011).

Показанная на диаграмме цепь ищет совпадения с командами вида x0xxx0xx, поэтому совпадение находится с гораздо большим количеством команд, чем только инкремент и декремент. Почему же не ищется полное совпадение? Причина в том, что если аккумулятор не используется, то активация сигнала инкремента/декремента значения не имеет. Расширяя список вариантов совпадений, разработчики могли избавиться от некоторых транзисторов в схеме. Важно, что цепь отвергает другие команды, связанные с аккумулятором — вроде «Clear accumulator» (00000100) или «Load accumulator from register» (0110rrrr).

Компаратор


Важная цепь Nanoprocessor — компаратор, сравнивающий хранящееся в аккумуляторе значение со значением из регистра R0. Компаратор использует единственную, но хитрую цепь для их сравнения. По сути, алгоритм сравнивает два числа, начиная со старших битов. Если биты равны, продолжаем двигаться к более младшим. Первое различие в битах определяет, какое значение больше (к примеру, в случае с 10101010 и 10100111 это определяет 4-й бит справа).

Алгоритм реализован в восемь ступеней, по этапу на бит, начиная с самого старшего бита внизу. Каждая ступень состоит из двух симметричных частей — одна определяет, выполняется ли неравенство A > R0, а её дополнительная часть проверяет неравенство A < R0. Если числа пока были равны, но на этой ступени обнаружилась разница, ступень генерирует сигнал «больше» или «меньше». Иначе она передаёт решение на более нижнюю ступень. Итоговое решение принимает самая верхняя ступень. Обратите внимание, что сравнение на равенство в компараторе происходит «нахаляву» – если на выходе нет сигналов «больше» или «меньше», значит, значения равны.

530625d922ea317d61337d87b882fd42.png
Одна из ступеней 8-битного компаратора

На диаграмме ниже показана физическая раскладка двух ступеней компаратора. Одна хитрость раскладки компаратора в том, что он находится между регистром 0 слева и аккумулятором справа, что минимизирует длину проводников. Компаратор обращается к регистру 0 напрямую, минуя обычный путь выбора регистра и шины данных.

98581047bf7697ec7d966f630b0af98b.jpg
Две ступени компаратора — так, как это задаётся на фотошаблоне

Команды условного ветвления Nanoprocessor могут проверять выходные данные компаратора. Цепи условного ветвления устроены довольно просто: несколько битов команды ветвления выбирают определённую проверку через мультиплексор. Затем 7-й бит команды решает, выбрать «эту ветку, если истина» или «эту ветку, если ложь». В отличие от большинства процессоров, Nanoprocessor не позволяет проводить ветвление на любой адрес. Он просто пропускает два байта команд, если условие удовлетворено (а обычно в двух этих байтах содержится команда перехода к нужной цели, но иногда там бывают и другие команды). Схема пропуска проста: программный счётчик вызывается повторно, при этом увеличивает значение не на 1, а на 2, пропуская две команды. Получается, что в Nanoprocessor реализован широкий спектр условных проверок на относительно небольшом количестве цепей.

У Nanoprocessor есть большой набор условий для ветвления — удивительно большой для такого простого процессора. Можно проверять следующие условия: A > R0, A >= R0, A < R0, A <= R0, A == R0 или A != R0. Кроме того, условное ветвление может зависеть от того, нулевое значение в аккумуляторе, или нет, равен ли нулю конкретный бит хранящегося в аккумуляторе значения, взведён ли флаг переполнения, установлен ли определённый бит регистра ввода/вывода.

Аккумулятор и устройство управляющей логики


Аккумулятор — особый 8-битный регистр, в котором хранится байт, обрабатывающийся в данный момент. Операции с аккумулятором проводит устройство управляющей логики (УЛУ), который в инструкции к процессору называют «сердцем Nanoprocessor». УЛУ — эквивалент арифметико-логического устройства (АЛУ) в большинстве процессоров, только оно не проводит арифметических или логических операций. При этом УЛУ не такое бесполезное, как кажется с первого взгляда. Оно может увеличивать или уменьшать значение в аккумуляторе, как в двоичном, так и в двоично-десятичном коде (BCD). В BCD в одном байте хранятся два десятичных разряда. Это очень полезный режим для ввода/вывода или дисплеев. Также УЛУ может находить двоичное дополнение аккумулятора или сбрасывать его, а также устанавливать и сбрасывать определённый бит. Наконец, оно поддерживает операции сдвига влево и вправо.

9eda5f7414cf2920f7a8cd39c319b20f.jpg
Цепи, относящиеся к аккумулятору

На диаграмме выше показаны схемы аккумулятора и УЛУ. На первом участке расположены различные цепи, определяющие нулевое значение, поддерживающие BCD, и обеспечивающие проскок переноса — быструю генерацию переноса из 4-х младших битов. На втором участке находится основной аккумулятор и цепи УЛУ. Третий участок распределяет управляющие сигналы от декодирующей логики выше к восьми частям аккумулятора. Последний участок содержит логику декодирования команд, которая декодирует битовые операции и отправляет сигнал нужной части аккумулятора.

Основная часть аккумулятора/УЛУ состоит из 8 частей, по одной на бит, и младший бит расположен сверху. Мы рассмотрим четыре цепи из каждой части: генератор переноса для операций инкремента/декремента, генератор бита для операций инкремента/декремента, мультиплексор для выбора нового значения аккумулятора, и защёлку, где хранится значение аккумулятора.

Каждая часть устройства инкремента/декремента (ниже) реализуется при помощи полусумматора. Направление цепи инкремента/декремента определяет код операции: 0 в младшем бите кода операции говорит, что нужно делать инкремент, а 1 — декремент. Цепь переноса слева генерирует сигнал переноса. Для инкремента нужно создавать выходной сигнал переноса, если поступил входной сигнал переноса, и текущий бит равен 1 (поскольку тогда он будет увеличен до двоичного 10). Для декремента линия переноса сигнализирует о заёме, поэтому выходной сигнал переноса генерируется, когда есть входной сигнал переноса (то есть, заём), а текущий бит равен 0.

207720b65893a075b50eab42749bf886.png
Одна часть цепи инкремента/декремента

Цепь справа обновляет текущий бит при инкременте или декременте. Текущий бит переключается при наличии входного сигнала переноса — по сути, реализация XOR через три NOR-вентиля. Одна из сложностей — подстройка под BCD. Для операции инкремента BCD перенос происходит при инкременте цифры 9, а для операции декремента BCD цифра 0 уменьшается до 9, а не до двоичной 1111.

Различными операциями аккумулятора заведует мультиплексор. В зависимости от операции активируется один проходной транзистор, выбирающий нужную величину. К примеру, для операции инкремента/декремента верхний транзистор выбирает выходной сигнал с цепи инкремента/декремента, описанной выше. Транзистор активирует описанный ранее декодер команд, нашедший соответствующую команду инкремента/декремента. Сходным образом команда сдвига вправо активирует проходной транзистор сдвига вправо, подавая n+1 бит аккумулятора в каждую из частей аккумулятора для сдвига значения.

a6f6a3cc5d2cd6685da1746a14f8f4a8.png
Схема защёлки, хранящей один бит аккумулятора, и мультиплексора, выбирающего входной сигнал для аккумулятора

Защёлка хранит один бит для аккумулятора. При активации транзистора удержания аккумулятора два NOR-вентиля формируют петлю, удерживающую значение. Если вместо этого активируется транзистор загрузки аккумулятора, аккумулятор загружает нужное значение из мультиплексора. Линии сброса бита n и установки бита n позволяют командам изменять отдельные биты аккумулятора; при этом мультиплексор обновляет все биты аккумулятора сразу.

Счётчик и адресация программ


Ещё один большой блок цепей — 11-битный счётчик программ, расположенный в левом нижнем углу Nanoprocessor. Также в этом блоке находится защёлка, хранящая адрес возврата из подпрограммы, и ещё одна защёлка, хранящая счётчик программы после прерывания. Их можно представлять себе, как стек длиной в один элемент. В программном счётчике есть устройство инкремента, отвечающее за переход к следующей команде. Оно также умеет делать инкремент сразу на два, позволяя командам условного ветвления пропускать две команды (такое устройство инкремента реализовано просто через увеличение 1-го бита вместо 0-го). Для ускорения работы устройства инкремента у него есть функция проскока переноса; если все шесть младших битов равны 1, он увеличит сразу 6-й бит, не ожидая, пока перенос пройдёт через все младшие биты.

Контроль и тактовая частота


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

Заключение


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

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

e492654dd889f5012273282fad9c7a17.jpg

Функциональные компоненты Nanoprocessor на основе моего реверс-инжиниринга

Nanoprocessor — процессор необычный. По первому впечатлению он даже показался мне «ненастоящим процессором», из-за отсутствия базовых арифметических операций. Однако, изучив его подробнее, я всё же впечатлился. Его простая конструкция позволяет ему работать быстрее других процессоров того времени. Набор команд умеет больше, чем кажется на первый взгляд. Hewlett-Packard использовала Nanoprocessor во многих своих продуктах в 1970-х и 1980-х, на более сложных ролях, чем можно было бы ожидать — к примеру, для разбора строк и выполнения вычислений. После того, как были опубликованы его маски, мы можем узнать все секреты цепей, благодаря которым Nanoprocessor работал.

d95da394b187f24097beaabefd0e9a6b.jpg

Nanoprocessor (белый чип) как часть модуля точного времени Hewlett-Packard. Обратите внимание на написанное вручную напряжение; каждому чипу требовалось своё напряжение смещения.

© Habrahabr.ru