Что такое soft процессор
1) Микроконтроллер – это микропроцессор, который управляет всеми или частью функций электронного устройства или системы. В основном, мироконтроллер это устройство, которое включает некоторые компоненты микропроцессорной системы в один микрочип, CPU ядро, память (и ROM, и RAM), несколько параллельных цифровых выводов I/O. Узкоспециализирован, в отличие от микропроцессора. В микроконтроллер входит множество переферийных блоков. Вычислительные блоки, порты ввода-вывода и прерываний дешевые и простые. Также микроконтроллер можно перепрошить в рамках программного решения : смена команд в памяти или использование некоторой ОС. Пример : PIC16А87X имеет RISC архитектуру c 35 однословными инструкциями и операционной скоростью в 20MHz, имеет 3 типа памяти : Flash Program Memory, Data Memory и EEPROM Data Memory. Дополнительно включены 5 специальных регистров с тремя таймерами.
2) Микропроцессор – это интегральная схема, которая содержит все функции центрального процессора компьютера. (Пример : Intel Pentium 4 Extreme Edition microprocessor operates at 3.2GHz)
3) Софт Процессор – логическая абстракция реального аппаратного процессора, которая существует как технческое или лингвистическое описание. Софт –процессоры могут быть описаны языком описания аппаратуры таким как VHDL, может быть промоделирован, синтезирован и даже реализован на устройствах программируемой логики таких, как CLDP или FPGA. Техническое описание (исходный код VHDL) ядра Soft-процессора называют “Intellectual Property” – ядро (IP-core).
Таким образом можно переписать сам процессор : изменить набор инструкций, изменить количество регистров, изменить количество процессоров. Т.е. достигается максимальная повторная используемость. Однако производительность ниже, чем у mP , т.к. это всего лишь эмуляция. Реализация софт-процессора – путь к конфигурируемым вычислениям. Возможна аппаратное перепрограммирование – статическое или динамическое. При динамической реконструкции можно управлять программой посредством инструкций (происходит перестройка АЛУ). (Пример : MicroBlaze soft processor from Xilinx – 32bit RISC processor with Harvard architecture.)
4) Встроенный процессор – физическая часть интегральной схемы, которая содержит ядро реального процессора. Встроенный процессор может быть центральной частью системы на чипе (System-On-a-Chip, или SOC), окруженной дополнительной аппаратурой и периферийными контроллерами. Такое встраивание позволяет быстро развести сигналы от CPU к другой встраиваемой аппаратуре. Производительность гораздо выше, чем у софт-процессора. Доступна многопроцессорность. Сам процессор может быть окружен произвольным набором вспомогательных блоков (mC).
5) ASIC – специализированная интегральная схема, которая производится по требованию для специальных назначений и высоких производительностей. Не используются повторно, т.к. специализированы для какой-то конкретной задачи. Применяются в конкретном устройстве и выполняют строго ограниченные функции, характерные только для данного устройства. (Пример : Helion is hardware core of AES crypto alghoritm).
Разработка или выбор управляющего контроллера для встраиваемой системы на ПЛИС –актуальная и не всегда тривиальная задача. Часто выбор падает в пользу широкораспространенных IP-ядер, обладающих развитой программно-аппаратной структурой – поддержка высокопроизводительных шин, периферийный устройств, прикладное программное обеспечение и, в ряде случаев, операционных систем (в основном Linux, Free-RTOS). Одними из причин данного выбора являются желание обеспечить достаточную производительность и иметь под рукой готовый инструментарий для разработки программного обеспечения.
В том случае, если применяемая в проекте ПЛИС не содержит аппаратных процессорных ядер, реализация полноценного процессорного ядра может быть избыточной, или вести к усложнению программного его обеспечения, а следовательно приведет к увеличению затрат на его разработку. Кроме того, универсальное софт-ядро будет, так или иначе, занимать дефицитные ресурсы программируемой логики. Специализированный софт-процессор будет более оптимальным решением в свете экономии ресурсов логики – за счет адаптированной системы команд, небольшого количества регистров, разрядности данных (вплоть до некратной 8битам). Согласование с периферийными устройствами – проблема в основном согласования шин и протоколов. Заменой сложной системы обработки прерываний может служить многопоточная архитектура процессора.
Стековые софт-процессоры и контекст потока
Обычно многопоточные процессоры имеют одно АЛУ и несколько наборов регистров (иногда называемых «теневыми» регистрами) для хранения контекста потока, следовательно, чем больше требуется потоков, тем будут больше накладные расходы логики и памяти. Среди разнообразия архитектур софт-процессорных ядер следует выделить стековую архитектуру. Такие процессоры часто называют еще Форт-процессорами, так как чаще всего их ассемблер естественным образом поддерживает подмножество команд языка Форт.
У стековых процессоров есть интересное свойство –это небольшой размер контекста потока. Поскольку роль регистров выполняет стек, при переключении на другой поток необязательно иметь полный комплект регистров общего назначения - достаточно переключить указатель стека [1]. В простейшем случае контекст потока можно ограничить небольшим набором указателей - стека, стека возвратов и счетчик команд потока. При наличии нескольких определяемых спецификой задач потоков вычислений компактность многопотоковой схемы будет важнее пиковой производительности, а задача реализации процессорной системы в ПЛИС минимального объема является актуальной в свете, например, нового семейства Spartan-7 число ячеек в устройствах которого невелико.
Идеи и методика реализации многопоточного софт-процессора изложены в работе [1]. Развитие работ в этом направлении привело к появлению свободного компилятора языка Python для процессоров стековой архитектуры [2]. Это решает проблему разработки системного программного обеспечения для софт-процессора. Более того, данный язык благодаря простому синтаксису и поддержке различных парадигм программирования является популярным и удобен для начинающих программистов.
Инструментальные средства для упрощенного проектирования IP-ядер
Известен также инструментарий MyHDL [3], позволяющий описывать аппаратные модули и узлы на языке Python. Синтаксис описания проще VHDL, сам инструментарий менее требователен к ресурсам системы, чем фирменные среды разработки. Помимо самого описания MyHDL позволяет описывать тестовые последовательности и логически симулировать работу модулей [4].
Потенциально, совместное применение инструментариев MyHDL и Uzh позволит вести разработку софт-процессора и компилятора языка высокого уровня для него на одном языке, что позволит снизить уровень сложности задачи. В частности это будет полезно в проектах, связанных с начальным обучением программированию, разработки цифровой техники, систем управления. На данный момент предлагаемые подходы к начальному обучению работе с программируемой логикой находятся на стадии поиска эффективных решений и не всегда методически выдержаны 6. Единый стиль описания может помочь сгладить сложные момент в процессе освоения ПЛИС, к тому же некоторые популярные конструкторы и наборы с программируемыми блоками используют Python для задания рабочей программы.
Комбинационные логические схемы и последовательные автоматы описываются и симулируются достаточно не сложно [8,9], откуда можно сделать вывод, что, придерживаясь определенной концепции описать прототип многопоточного софт-процессора на поведенческом уровне достаточно просто.
Реализация простого многопоточного процессора на MyHDL
Учитывая идеи и базовую архитектуру процессора, предложенные в [1], разрабатываемый процессор будет иметь следующие особенности: Гарвардская архитектура с раздельными памятями программ и данных, стеки физически отображаются на память данных, для хранения контекста потока предусмотрены наборы теневых регистров.
Однотактовые и конвейерные пути решения для начальной реализации не очевидны, и с учетом того, что для софт-процессора не требуется сверхвысокой производительности, командный цикл процессора можно сделать многотактным. В первом приближении достаточно четырех стадий выполнения: чтение контекста потока, выборка операндов в кеширующие регистры, выполнение команды, переключение на следующий поток.
Разрядность памяти программ, данных, размеры стеков и количество потоков задается параметрически при помощи ряда переменных:
Сама микроархитектура ядра реализуется через ряд функций, отвечающих за генерацию последовательных схем. Счетчик состояний процессора реализуется просто – установка в ноль при сигнале сброса, иначе – инкремент на каждый такт.
Логика работы узлов процессора на каждом из этапов выполнения описывается в отдельной функции:
Первый этап - переключение контекста - считываются нужные рабочие регистры из наборов для текущего потока:
Чтение операндов – считываются из памяти верхние элементы стеков, текущая команда потока и текущий внешний вход:
Третий этап - выполнение команд.
Состав команд может быть оптимизирован для каждой конкретной задачи, единственное требование – желательна поддержка команд абстрактной стековой машины [1] для построения более эффективного кода при компиляции с высокоуровнего языка. Некоторые идеи по реализации работы с константами взяты из работы [10].
Финальный этап - инкремент счетчика потоков:
Все описанные блоки возвращаются при завершении описании основной функции процессорного ядра:
return st_swt, st_sw, st_get, st_ex, st_sv
Результат симуляции автоматически выгружается в файл .vcd (в данном случае - test.vcd), который потом можно будет открыть в любом просмотрщике временных диаграмм.
На рисунке представлены временные диаграммы работы сгенерированного восьмипоточного 16-битного процессора. Все потоки начинают работу с одного адреса, но их стеки данных и возвратов находятся по разным адресам. Можно отследить изменения счетчиков команд потоков, загрузку констант, манипуляции с данными на стеках.
Рис. Результат симуляции работы софт-процессора.
При необходимости MyHDL код может быть транслирован в код налюбом из HDL языков – в Verilog или в VHDL. Полученные таким образом файлы в дальнейшем могут быть использованы в проектах сред разработки под выбранное семейство ПЛИС:
Заключение
Был продемонстрирован простой маршрут быстрого прототипирования софт-процессора, позволяющий при незначительных затратах времени и вычислительных ресурсов проверить концептуальную идею на работоспособность. Полученные результаты – транслированные HDL-файлы могут служить основой для дальнейшего развития и оптимизации проекта уже с учетом особенностей текущей серии ПЛИС. В частности, для представленного примера финальный VHDL код будет генерировать память на дефицитных для ПЛИС регистрах, а более предпочтительным является задействование ресурсов встроенных блоков памяти.
Библиографический список
Собственный софт-процессор на ПЛИС с компилятором языка высокого уровня или Песнь о МышЕ — опыт адаптации компилятора языка высокого уровня к стековому процессорному ядру.
Распространенной проблемой для софт-процессоров является отсутствие средств разработки для них, особенно, если их система команд не является подмножеством команд одного их популярных процессорных ядер. Разработчики в этом случае вынуждены будут решать эту проблему. Прямым её решением является создание компилятора языка ассемблера. Однако в современных реалиях не всегда удобно работать на Ассемблере, так как в процессе развития проекта может изменяться система команд в связи, например, с изменившимися требованиями. Поэтому задача легкой реализации компилятора языка высокого уровня (ЯВУ) для софт-процессора является актуальной.
Компилятор языка Python — Uzh представляется легким и удобным инструментарием для разработки программного обеспечения для софт-процессоров. Инструментарий определения примитивов и макросов как функций целевого языка позволяет критичные места реализовывать на ассемблере процессора. В данной работе рассмотрены основные моменты адаптации компилятора для процессоров стековой архитектуры.
Вместо эпиграфа:
Если взрослого мыша
Взять и, бережно держа,
Напихать в него иголок
Вы получите ежа.
Если этого ежа,
Нос заткнув, чтоб не дышал,
Где поглубже, бросить в речку
Вы получите ерша.
Если этого ерша,
Головой в тисках зажав,
Посильней тянуть за хвост
Вы получите ужа.
Если этого ужа,
Приготовив два ножа…
Впрочем, он наверно сдохнет,
Но идея хороша!
Введение
Во многих случаях при реализации измерительных приборов, научно-исследовательского оборудования в качестве основного ядра системы предпочтительнее применять реконфигурируемые решения на базе ПЛИС/FPGA. Данный подход имеет множество преимуществ, благодаря возможности легкого и быстрого внесения изменений в логику работы, а также за счет аппаратного ускорения операций обработки данных и сигналов управления.
Для широкого круга задач, таких, как цифровая обработка сигналов, встраиваемые системы управления, системы сбора и анализа данных, хорошо зарекомендовал себя подход, заключающийся в сочетании в одном решении блоков, реализуемых логикой ПЛИС для критических процессов, и элементов программного управления на основе одного или нескольких софт-процессоров для общего управления и координации, а также для реализации взаимодействия с пользователем или внешними устройствами/узлами. Применение софт-процессоров в данном случае позволяет несколько снизить временные затраты на отладку и верификацию алгоритмов управления системой или алгоритмов взаимодействия отдельных узлов.
Типовые «хотелки»
Зачастую от софт-процессоров в данном случае не требуется сверхвысокая производительность (т.к. её проще добиться, использую логические и аппаратные ресурсы ПЛИС). Они могут быть достаточно простыми (а с точки зрения современных микроконтроллеров – почти примитивными), т.к. они могут обойтись без сложной системы прерываний, работать только с определенными узлами или интерфейсами, нет необходимости поддерживать ту или иную систему команд. Их может быть много, при этом каждый из них может выполнять только определенный набор алгоритмов или подпрограмм. Разрядность софт-процессоров также может быть любой, в том числе не кратной байту – в зависимости от требований текущей задачи.
Типовыми целевыми показателями для софт-процессоров являются:
- достаточная функциональность системы команд, возможно оптимизированная под задачу;
- высокая плотность программного кода, т.к. это позволит экономить ресурсы памяти ПЛИС;
- компактность – не хотелось бы, чтобы вспомогательные элементы занимали дефицитные ресурсы программируемой логики.
Исходные компоненты
Этим требованиям с большим процентом соответствия удовлетворяют стековые процессоры, т.к. нет необходимости адресовать регистры, разрядность команды может быть небольшой.
Разрядность данных для них может варьироваться и не привязана к разрядности системы команд. Являясь де-факто (пусть и с небольшими оговорками) аппаратной реализацией промежуточного представления программного кода при компиляции (виртуальная стековая машина, или в терминах контекстно-свободных грамматик – магазинный автомат) позволяют с низкими трудозатратами перевести грамматику любого языка в исполнимый код. Кроме того, для стековых процессоров практически «родным» языком является язык Форт. Трудозатраты на реализацию Форт-компилятора для стекового процессора сравнимы с затратами на Ассемблер, при гораздо большей гибкости и эффективности в реализации программ в дальнейшем.
Имея задачу на построение системы сбора данных с интеллектуальных датчиков в режиме, близком к режиму реального времени, в качестве опорного решения (т.н. Reference Design) софт-процессора был выбран Форт-процессор, описанный в работах [1] (в дальнейшем будет иногда называться как процессор whiteTiger по нику его автора).
Его основные особенности:
- раздельные стеки данных и возвратов;
- гарвардская архитектура организации памяти (раздельные памяти программ и данных, включая и адресное пространство);
- расширение периферийными устройствами при помощи простой параллельной шины.
- В процессоре не используется конвейер, выполнение команд двухтактное:
- выборка команды и операндов;
- исполнение команды и сохранение результата.
С оглядкой на конфигурацию блочной памяти в ПЛИС разрядность команд установлена равной 9 бит. Разрядность данных задана в 32 бита, но может быть в принципе любой.
Код процессора написан на VHDL без применения каких-либо специфических библиотек, что позволяет работать с данным проектом на ПЛИС от любого производителя.
Для относительно широкого применения, снижения «входного порога», а также для повторного использования кода и применения наработок кода, целесообразнее перейти на ЯВУ, отличный от Форта (отчасти это связано с суевериями и заблуждениями майн-стрим программистов относительно сложностей данного языка и читабельности его кода (к слову, один из авторов данной работы аналогичного мнения о С-подобных языках)).
Исходя из ряда факторов для эксперимента по «привязке» софт-процессора и ЯВУ был выбран язык Питон (Python). Это высокоуровневый язык программирования общего назначения, ориентированный на повышение производительности разработчика и читаемости кода, поддерживающий несколько парадигм программирования, в том числе структурное, объектно-ориентированное, функциональное, императивное и аспектно-ориентированное [2].
Для начинающих разработчиков интересно его расширение MyHDL [3, 4], позволяющее описывать аппаратные элементы и структуры на Python и транслировать их в код на VHDL или Verilog.
Некоторое время назад был анонсирован компилятор Uzh [5] — небольшой компилятор для программного процессора FPGA Zmey (32-битная стековая архитектура с поддержкой многопоточности – если проследить цепочку версий/модификаций/верификаций – Zmey – дальний потомок процессора whiteTiger).
Uzh – это также статически скомпилированное подмножество Python, основывается на перспективном инструментарии raddsl (набор инструментов для быстрого создания прототипов DSL-компиляторов) [6, 7].
Таким образом, факторы, повлиявшие на выбор направления работ можно сформулировать примерно так:
- интерес к средствам, понижающим «порог вхождения» для начинающих разработчиков устройств и систем на ПЛИС (синтаксически Python не такой «страшный» для начинающего, как VHDL);
- стремление к гармонии и единому стилю в проекте (теоретически возможно описать требуемые аппаратные блоки и программное обеспечение софт-процессора на Python);
- случайное стечение обстоятельств.
Небольшие, «почти» ничего не значащие нюансы
Исходный код процессора Zmey не является открытым, но доступно описание принципов его работы и некоторых особенностей архитектуры. Хотя он и является также стековым, есть ряд ключевых отличий от процессора whiteTiger:
- стеки являются программными – т.е. представлены указателями и размещаются в памяти данных по разным адресам;
- в систему команд введен ряд команд, оптимизирующих процессор для выполнения кода С-подобных ЯВУ;
- отличаются способы загрузки и представления чисел и констант в памяти программ;
- процессор является многопоточным, но в контексте данной работы это не является существенным.
Для установки компилятора Uzh достаточно скачать его архив и распаковать в любую удобную папку (лучше придерживаться общих рекомендаций для специализированного программного обеспечения – избегать путей, содержащих кириллицу и пробелы). Также необходимо скачать и распаковать в основную папку компилятора инструментарий raddsl.
Папка test компилятора содержит примеры программ для софт-процессора, папка src – исходные тексты элементов компилятора. Для удобства работы лучше создать небольшой командный файл (расширение .cmd) с содержимым: c.py C:\D\My_Docs\Documents\uzh-master\tests\abc.py , где abc.py – имя файла с программой для софт-процессора.
Змея, кусающая себя за хвост или притирка железа и софта
Для адаптации Uzh-а к процессору whiteTiger потребуются некоторые изменения, также как и немного придется подкорректировать и сам процессор.
К счастью, мест, подлежащих корректировке в компиляторе не много. Основные «аппаратно-зависимые» файлы:
- asm.py – ассемблер и формирование чисел (литералов);
- gen.py – низкоуровневые правила формирования кода (функции, переменные, переходы и условия);
- stream.py – формирование загрузочного потока;
- macro.py – макроопределения, по факту – расширения базового языка аппаратно-специфичными функциями.
- по приему определенного управляющего байта загрузчик выставляет активный уровень на внутренней линии сброса процессора;
- по второй байтовой команде сбрасывается счетчик адреса памяти;
- далее следует последовательность тетрад передаваемого слова, начиная с младшей, комбинированные с тетрадой-номером ;
- после каждого байта с упакованной тетрадой следует пара управляющих байт первый из которых устанавливает активный уровень на линии разрешения записи памяти, второй сбрасывает его;
- по завершении последовательности упакованных тетрад управляющим байтом снимается активный уровень на линии сброса.
Поскольку память данных задействована в логике работы процессорного ядра, необходимо мультиплексировать её линии данных и управления. Для этого вводятся дополнительные сигналы DataDinBtemp, LoaderAddrB, DataWeBtemp – данные, адрес и разрешение записи для порта В памяти.
Код загрузчика теперь выглядит так:
При активном уровне сброса сигналы DataDinBtemp, LoaderAddrB, DataWeBtemp подключаются к соответствующим портам памяти данных.
В соответствии с алгоритмом работы загрузчика необходимо модифицировать модуль stream.py. Сейчас в нем две функции. Первая функция — get_val() — разбивает входное слова на нужное количество тетрад. Так, для 9-битных команд процессора whiteTiger они будут транформированы в группы по три тетрады, а 32-битные данные – в последовательности из восьми тетрад. Вторая функция make() формирует непосредственно загрузочный поток.
Финальный вид модуля stream:
Следующие изменения в компиляторе коснутся модуля asm.py в котором описывается система команд процессора (прописываются мнемоники команд и опкоды команд) и способ представления/компиляции числовых значений – литералов.
Если старший (9-ый) бит слова равен 1, то опкод интерпретируется как число – так, к примеру, четыре идущих подряд опкода с признаком числа формируют в итоге 32-битной число. Признаком окончания числа является наличие опкода команды – для определенности и обеспечения единообразия окончанием определения числа служит опкод команды NOP («нет операций»).
В итоге модифицированная функция lit() выглядит так:
Основные и самые ответственные изменения/определения – в модуле gen.py. Данный модуль определяет основную логику работы/исполнения высокоуровневого кода на уровне ассемблера:
- условные и безусловные переходы;
- вызов функций и передача им аргументов;
- возврат из функций и возвращение результатов;
- подстройки под размеры памяти программ, памяти данных и стеков;
- последовательность действий при старте процессора.
В процессоре Zmey для работы с локальными переменными и аргументами функций используется стек возвратов – аргументы функции переносятся на него и при дальнейшей работе к ним идет обращение через регистр-указатель стека возвратов (чтение, модификация в сторону увеличения/уменьшения, чтение по адресу указателя). Поскольку стек физически располагается в памяти данных, то такие операции по сути просто сводятся к операциям с памятью, в пределах этой же памяти располагаются и глобальные переменные.
В whiteTiger стеки возвратов и данных являются выделенными аппаратными стеками со своим адресным пространством и не имеет команд работы с указателями стеков. Следовательно, операции с передачей аргументов функциям и работу с локальными переменными необходимо будет организовывать через память данных. Увеличивать объемы стеков данных и возвратов для возможного хранения в них относительно больших массивов данных не имеет большого смысла, логичнее иметь несколько большую память данных.
Для работы с локальными переменными был добавлен выделенный регистр LocalReg, задача которого – хранить указатель на область памяти, отведенную для локальных переменных (своего рода heap). Добавлены также операции для работы с ним (файл cpu.vhd – область определения команд):
LOCAL – возвращает на стек данных текущее значение указателя LocalReg;
SETLOCAL – устанавливает новое значение указателя, принятое со стека данных;
LOCALadd – оставляет на стеке данных текущее значение указателя и увеличивает его на 1;
LOCALsubb — оставляет на стеке данных текущее значение указателя и уменьшает его на 1.
LOCALadd и LOCALsubb добавлены для уменьшения количества тактов при операциях передачи параметров функции и наоборот.
В отличие от оригинального whiteTiger немного были изменены подключения памяти данных – теперь порт В памяти постоянно адресуется выходом первой ячейки стека данных, на его вход подается выход второй ячейки стека данных:
Логика выполнения команд STORE и FETCH также немного подкорректировалась – FETCH принимает на вершину стека данных выходное значение порта В памяти, а STORE просто управляет сигналом разрешения записи порта В:
В рамках тренировки, а также для некоторой аппаратной поддержки циклов на низком уровне (и на уровне компилятора языка Форт) к ядру whiteTiger был добавлен стек счетчиков циклов (действия аналогичные, как при объявлении стеков данных и возвратов):
Были добавлены команды организации циклов со счетчиком.
DO – перемещающая число итераций цикла со стека данных на стек счетчиков и помещающая на стек возвратов инкрементированное на единицу значение счетчика команд.
LOOP – проверяет обнуление счетчика, если не достигнуто, верхний элемент стека счетчиков декрементируется, осуществляется переход по адресу на вершине стека возвратов. Если вершина стека счетчиков равна нулю, верхний элемент сбрасывается, сбрасывается также адрес возврата на начало цикла с вершины стека возвратов.
Теперь можно приступить к модификации кода модуля gen.py.
Переменные *_SIZE в комментариях не нуждаются и требуют только подстановки значений, заданных в проекте процессорного ядра.
Список STUB – временная заглушка для формирования места для адресов переходов с последующим их заполнением компилятором (текущие значения соответствуют 24-битному адресному пространству памяти кода).
Список STARTUP – задает последовательность действий, выполняемых ядром после сброса – в данном случае будет задан начальный адрес памяти локальных переменных – 900, и переход на точку старта (если ничего не менять, точка старта/входа в приложение прописывается компиляторов в ячейку памяти данных с адресом 2):
Определение func() прописывает действия, производимые при вызове функции, а именно – перенос аргументов функции в область локальных переменных, выделение памяти для собственных локальных переменных функции.
Epilog() определяет действия при возвращении из функции – освобождение памяти временных переменных, возврат на точку вызова.
Работа с переменными идет посредством их адресов, ключевое определение для этого – push_local(), оставляющее на стеке данных адрес «высокоуровневой» переменной.
Следующие ключевые моменты – это условный и безусловный переходы. Условный переход в процессоре whiteTiger проверяет на 0 второй элемент стека данных и переходит по адресу на вершине стека, если условие выполняется. Безусловный переход просто устанавливает значение счетчика команд равному значению на вершине стека.
Следующие два определения задают операции битового сдвига – как раз на низком уровне применены циклы (даст некоторый выигрыш в размере кода – оригинале компилятор просто помещает подряд требуемое количество элементарных операций сдвига.
И основное определение компилятора на низком уровне – набор правил для операций языка и работы с памятью:
Модуль macro.py позволяет несколько «расширить» словарь целевого языка за счет макроопределений на ассемблере целевого процессора. Для компилятора ЯВУ определения в macro.py не будут отличаться от «родных» операторов и функций языка. Так, к примеру, в оригинальном компиляторе были определены функции ввода-вывода значения во внешний порт. Были добавлены тестовые последовательности операций с памятью и локальными переменными и операция временной задержки.
Тестирование
Небольшая тестовая высокоуровневая программа для нашего процессора содержит определение функции для вычисления факториала, и основную функцию, реализующую последовательный вывод значений факториала от 1 до 7 в порт в бесконечном цикле.
Запуск её на компиляцию можно произвести, например, простым скриптом или из командной строки последовательностью:
c.py C:\D\My_Docs\Documents\uzh-master\tests\fact2.py
В результате будет сформирован загрузочный файл stream.bin, который можно передавать процессорному ядру в ПЛИС через последовательный порт (в современных реалиях через любой виртуальный последовательный порт, который предоставляют преобразователи интерфейсов USB-UART). Программа в итоге занимает 146 слов (9-битных) памяти программ и 3 в памяти данных.
Заключение
В целом, компилятор Uzh представляется легким и удобным инструментарием для разработки программного обеспечения для софт-процессоров. Является прекрасной альтернативой ассемблеру, по крайней мере, в плане удобства работы программисту. Инструментарий определения примитивов и макросов как функций целевого языка позволяет критичные места реализовывать на ассемблере процессора. Для процессоров стековой архитектуры процедура адаптации компилятора не является слишком сложной и долгой. Можно сказать, что это как раз тот случай, когда наличие исходных текстов компилятора помогает – изменяются ключевые участки компилятора.
Результаты синтеза процессора (разрядность 32 бита, 4К-слов памяти программ и 1К ОЗУ) для FPGA Altera серии Cyclone V дает следующее:
Теперь, когда мы знаем, как работают процессоры на высоком уровне, настало время углубиться в разбор процесса проектирования их внутренних компонентов. Это вторая статья из серии, посвящённой разработке процессоров. Рекомендую изучить для начала первую часть, чтобы вы понимать изложенные ниже концепции.
Часть 1: Основы архитектуры компьютеров (архитектуры наборов команд, кэширование, конвейеры, hyperthreading)
Часть 2: Процесс проектирования ЦП (электрические схемы, транзисторы, логические элементы, синхронизация)
Часть 3: Компонование и физическое производство чипа (VLSI и изготовление кремния)
Часть 4: Современные тенденции и важные будущие направления в архитектуре компьютеров (море ускорителей, трёхмерное интегрирование, FPGA, Near Memory Computing)
Как вы возможно знаете, процессоры и большинство других цифровых устройств состоят из транзисторов. Проще всего воспринимать транзистор как управляемый переключатель с тремя контактами. Когда затвор включён, электрический ток может течь по транзистору. Когда затвор отключён, ток течь не может. Затвор похож на выключатель света в комнате, только он гораздо меньше, быстрее и может управляться электрически.
Существует два основных типа транзисторов, используемых в современных процессорах: pMOS (PМОП) и nMOS (NМОП). nMOS-транзистор пропускает ток, когда затвор (gate) заряжен или имеет высокое напряжение, а pMOS-транзистор пропускает ток, когда затвор разряжен или имеет низкое напряжение. Сочетая эти типы транзисторов комплементарным образом, мы можем создавать логические элементы КМОП (CMOS). В этой статье мы не будем подробно разбирать особенности работы транзисторов, но коснёмся этого в третьей части серии.
Логический элемент — это простое устройство, получающее входные сигналы, выполняющее какую-то операцию, и выводящее результат. Например, элемент И (AND) включает свой выходной сигнал тогда и только тогда, когда включены все входы затвора. Инвертор, или элемент НЕ (NOT) включает свой выход, если вход отключён. Можно скомбинировать эти два затвора и получить элемент И-НЕ (NAND), который включает выход, тогда и только тогда, когда не включён ни один из входов. Существуют другие элементы со своей логической функциональностью, например ИЛИ (OR), ИЛИ-НЕ (NOR), исключающее ИЛИ (XOR) и исключающее ИЛИ с инверсией (XNOR).
Ниже показано, как из транзисторов собраны два простых элемента: инвертор и NAND. В инверторе pMOS-транзистор (сверху) соединён с питанием, а nMOS-транзистор (снизу) соединён с заземлением. На обозначении pMOS-транзисторов есть небольшой кружок, соединённый с затвором. Мы сказали, что pMOS-устройства пропускают ток, когда вход отключен, а nMOS-устройства пропускают ток, когда вход включен, поэтому легко заметить, что сигнал на выходе (Out) будет всегда противоположным сигналу на входе (In). Взглянув на элемент NAND, мы видим, что для него требуется четыре транзистора, и что выход всегда будет отключен, если выключен хотя бы один из входов. Соединение подобным образом транзисторов для образования простых сетей — это тот же процесс, который используется для проектирования более сложных логических элементов и других схем внутри процессоров.
Строительные блоки в виде логических элементов так просты, что трудно понять, как они превращаются в функционирующий компьютер. Процесс проектирования заключается в комбинировании нескольких элементов для создания небольшого устройства, способного выполнять простую функцию. Затем можно объединить множество таких устройств, чтобы создать нечто, выполняющее более сложную функцию. Процесс комбинирования отдельных компонентов для создания работающей структуры — это именно тот процесс, который используется сегодня для создания современных чипов. Единственное отличие заключается в том, что современный чип состоит из миллиардов транзисторов.
В качестве небольшого примера давайте возьмём простой сумматор — 1-битный полный сумматор. Он получает три входных сигнала — A, B, и Carry-In (входной сигнал переноса), и создаёт два выходных сигнала — Sum (сумма) и Carry-Out (выходной сигнал переноса). В простейшей схеме используется пять логических элементов, и их можно соединить вместе для создания сумматора любого размера. В современных схемах этот процесс усовершенствован оптимизацией части логики и сигналов переноса, но фундаментальные основы остаются теми же.
Выход Sum равен или A, или B, но никогда обоим, или есть входящий сигнал переноса, и тогда A и B или оба включены, или оба выключены. Выходной сигнал переноса немного сложнее. Он активен, когда или A и B включены одновременно, или есть Carry-in и один из A или B включен. Чтобы соединить несколько 1-битных сумматоров для создания более широкого сумматора, нам просто нужно соединить Carry-out предыдущего бита с Carry-in текущего бита. Чем сложнее становятся схемы, тем запутанней получается логика, но это самый простой способ сложения двух чисел. В современных процессорах используются более изощрённые сумматоры, но их схемы слишком сложны для подобного обзора. Кроме сумматоров процессоры также содержат устройства для деления, умножения и версий всех этих операций с плавающей точкой.
Подобное объединение последовательностей элементов для выполнения некой функции над входными сигналами называется комбинаторной логикой. Однако это не единственный тип логики, используемый в компьютерах. Не будет особой пользы, если мы не сможем хранить данные или отслеживать состояние. Для того, чтобы иметь возможность сохранять данные, нам нужна секвенциальная логика.
Секвенциальная логика строится аккуратным соединением инверторов и других логических элементов так, чтобы их выходы передавали сигналы обратной связи на вход элементов. Такие контуры обратной связи используются для хранения одного бита данных и называются статическим ОЗУ (Static RAM), или SRAM. Эта память называется статическим ОЗУ в противовес динамической (DRAM), потому что сохраняемые данные всегда напрямую соединены с положительным напряжением или заземлением.
Стандартный способ реализации одного бита SRAM — это показанная ниже схема из 6 транзисторов. Самый верхний сигнал, помеченный как WL (Word Line) — это адрес, и когда он включен, то данные, хранящиеся в этой 1-битной ячейке передаются в Bit Line, помеченную как BL. Выход BLB называется Bit Line Bar; это просто инвертированное значение Bit Line. Вы должны узнать два типа транзисторов и понять, что M3 с M1, как и M4 с M2, образуют инвертор.
SRAM используется для построения сверхбыстрых кэшей и регистров внутри процессоров. Эта память очень стабильна, но для хранения каждого бита данных требует от шести до восьми транзисторов. Поэтому по сравнению с DRAM она чрезвычайно затратна с точки зрения стоимости, сложности и площади на чипе. С другой стороны, Dynamic RAM хранит данные в крошечном конденсаторе, а не использует логические элементы. Она называется динамической, потому что напряжение на конденсаторе может значительно изменяться, так как он не подключён к питанию или заземлению. Есть только один транзистор, используемый для доступа к хранящимся в конденсаторе данным.
Поскольку DRAM требует всего по одному транзистору на бит и очень масштабируема, её можно плотно и дёшево упаковывать. Недостаток DRAM заключается в том, что заряд конденсатора так мал, что его необходимо постоянно обновлять. Именно поэтому после отключения питания компьютера все конденсаторы разряжаются и данные в ОЗУ теряются.
Такие компании, как Intel, AMD и Nvidia, не публикуют схем работы своих процессоров, поэтому невозможно показать подобных полных электрических схем для современных процессоров. Однако этот простой сумматор позволит вам получить представление о том, что даже самые сложные части процессора можно разбить на логические и запоминающие элементы, а затем и на транзисторы.
Теперь, когда мы знаем, как производятся некоторые компоненты процессора, нам нужно разобраться, как соединить всё вместе и синхронизировать. Все ключевые компоненты процессора подключены к синхронизирующему (тактовому) сигналу (clock signal). Он попеременно имеет высокое и низкое напряжение, меняя его с заданным интервалом, называемым частотой (frequency). Логика внутри процессора обычно переключает значения и выполняет вычисления, когда синхронизирующий сигнал меняет напряжение с низкого на высокое. Синхронизируя все части, мы можем гарантировать, что данные всегда поступают в правильное время, чтобы в процессоре не возникали «глюки».
Вы могли слышать, что для повышения производительности процессора можно увеличить частоту тактовых сигналов. Это повышение производительности происходит благодаря тому, что переключение транзисторов и логики внутри процессора начинает происходить чаще, чем предусмотрено. Поскольку в секунду происходит больше циклов, то можно выполнить больше работы и процессор будет иметь повышенную производительность. Однако это справедливо до определённого предела. Современные процессоры обычно работают с частотой от 3,0 ГГц до 4,5 ГГц, и эта величина почти не изменилась за последние десять лет. Точно так же, как металлическая цепь не прочнее её самого слабого звена, процессор может работать не быстрее его самой медленной части. К концу каждого тактового цикла каждый элемент процессора должен завершить свою работу. Если какие-то части ещё её не завершили, то тактовый сигнал слишком быстрый и процессор не будет работать. Проектировщики называют эту самую медленную часть критическим путём (Critical Path) и именно он определяет максимальную частоту, с которой может работать процессор. Выше определённой частоты транзисторы просто не успевают достаточно быстро переключаться и начинают глючить или выдавать неверные выходные значения.
Повысив напряжение питания процессора, мы можем ускорить переключение транзисторов, но это тоже срабатывает до определённого предела. Если подать слишком большое напряжение, то мы рискуем сжечь процессор. Когда мы повышаем частоту или напряжение процессора, он всегда начинает излучать больше тепла и потреблять бОльшую мощность. Так происходит потому, что мощность процессора прямо пропорциональна частоте и пропорциональна квадрату напряжения. Чтобы определить энергопотребление процессора, мы рассматриваем каждый транзистор как маленький конденсатор, который нужно заряжать или разряжать при изменении его значения.
Подача питания — это настолько важная часть процессора, что в некоторых случаях до половины физических контактов на чипе может использоваться только для питания или заземления. Некоторые чипы при полной нагрузке могут потреблять больше 150 амперов, и со всем этим током нужно управляться чрезвычайно аккуратно. Для сравнения: центральный процессор генерирует больше тепла на единицу площади, чем ядерный реактор.
Тактовый сигнал в современных процессорах отнимает примерно 30-40% от его общей мощности, потому что он очень сложен и должен управлять множеством различных устройств. Для сохранения энергии большинство процессоров с низким потреблением отключает части чипа, когда они не используются. Это можно реализовать отключением тактового сигнала (этот способ называется Clock Gating) или отключением питания (Power Gating).
Тактовые сигналы создают ещё одну сложность при проектировании процессора: поскольку их частоты постоянно растут, то на работу начинают влиять законы физики. Даже несмотря на чрезвычайно высокую скорость света, она недостаточно велика для высокопроизводительных процессоров. Если подключить тактовый сигнал к одному концу чипа, то ко времени, когда сигнал достигнет другого конца, он будет рассинхронизован на значительную величину. Чтобы синхронизировать все части чипа, тактовый сигнал распределяется при помощи так называемого H-Tree. Это структура, гарантирующая, что все конечные точки находятся на совершенно одинаковом расстоянии от центра.
Может показаться, что проектирование каждого отдельного транзистора, тактового сигнала и контакта питания в чипе — чрезвычайно монотонная и сложная задача, и это в самом деле так. Даже несмотря на то, что в таких компаниях, как Intel, Qualcomm и AMD, работают тысячи инженеров, они не смогли бы вручную спроектировать каждый аспект чипа. Для проектирования чипов такого масштаба они используют множество сложных инструментов, автоматически генерирующих конструкции и электрические схемы. Такие инструменты обычно получают высокоуровневое описание того, что должен делать компонент, и определяют наилучшую аппаратную конфигурацию, удовлетворяющую этим требованиям. Недавно возникло направление развития под названием High Level Synthesis, которое позволяет разработчикам указывать необходимую функциональность в коде, после чего компьютеры определяют, как оптимальнее достичь её в оборудовании.
Точно так же, как вы можете описывать компьютерные программы через код, проектировщики могут описывать кодом аппаратные устройства. Такие языки, как Verilog и VHDL позволяют проектировщикам оборудования выражать функциональность любой создаваемой ими электрической схемы. После выполнения симуляций и верификации таких проектов их можно синтезировать в конкретные транзисторы, из которых будет состоять электрическая схема. Хоть этап верификации может и не кажется таким увлекательным, как проектирование нового кэша или ядра, он значительно важнее их. На каждого нанимаемого компанией инженера-проектировщика может приходиться пять или более инженеров по верификации.
Сложно осмыслить то, что в одном чипе может быть несколько миллиардов транзисторов и понять, что все они делают. Если разбить чип на его отдельные внутренние компоненты, становится немного легче. Из транзисторов составляются логические элементы, логические элементы комбинируются в функциональные модули, выполняющие определённую задачу, а эти функциональные модули соединяются вместе, образуя архитектуру компьютера, которую мы обсуждали в первой части серии.
БОльшая часть работ по проектированию автоматизирована, но изложенное выше позволяет нам осознать, насколько сложен только что купленный нами новый ЦП.
Во второй части серии я рассказал о процессе проектирования ЦП. Мы обсудили транзисторы, логические элементы, подачу питания и синхронизирующих сигналов, синтез конструкции и верификацию. В третьей части мы узнаем, что требуется для физического производства чипа. Все компании любят хвастаться тем, насколько современен их процесс изготовления (Intel — 10-нанометровый, Apple и AMD — 7-нанометровый, и т.д.), но что же на самом деле означают эти числа? Об этом мы расскажем в следующей части.
IP-компоненты – компоненты интеллектуальной собственности.
Появление систем на кристалле (SoC) повлекло за собой создание рынка IP-компонентов.
Примером современной SoC является следующее:
Каждый IP-компонент – отдельный блок SoC. Данные блоки могут выполнять совершенно разные функции (Soft-processor, mpeg decoder, DRAM итд), но они взаимодействуют друг с другом, образуя целостную систему.
Компоненты интеллектуальной собственности фактически представляют собой описание готового компонента на языках HDL (Hardware description language)/VHDL/A-HDL (solution file). Каждый компонент разрабатывается отдельной компанией, является её интеллектуальной собственностью, и продаётся за деньги. Защита данных компонентов – важнейшая задача для компании-производителя.
Классификация IP-компонент:
1. Foundation IP-Cores (basic digital components as FIFOs).
2. Processor IP-Cores (application specific or standard processor).
3. Application IP-Cores (MPEG codecs, JPEG, USB, PCI etc.).
4. Embedded Memory IP-Cores (SRAM, ROM, EEPROM etc).
Защита компонент:
Человек, обладающий HDL, VHDL и т.д. способен полноценно синтезировать и воспроизводить компоненту, а в некоторых случаях даже модифицировать её. Самый простой и примитивный способ заполучить исходник – напрямую украсть его у фирмы-производителя J. Однако не всегда это возможно. В таких случаях применяют Reverse Engineering – попытка “скана” структуры компонента.
Для защиты могут применяться различные методики – обфускация, встраивание водяных знаков, элементы физической криптографии и др.
Soft-процессоры:
Soft-процессоры – IP компонент, либо VHDL\HDL описание схематехники процессора. Данные процессоры могут быть клонами реально существующего процессора (реализованного в терминах VHDL для SoC), модифицированной его версией, либо вообще могут представлять собой уникальный процессор, разработанный и специфичный для текущих задач.
В данный момент широкое распространение получили Application-Specific Soft Processor – версии процессоров с набором команд, адаптированных по функциональности и производительности под конкретные задачи (например, адаптированные для вычисления зоны распространения колебательной волны).
Альтернативой Soft processor-ов является Embedded Processor – что-то напоминающее “классические” процессоры, которые реализуются внутри SoC.
Читайте также: