Микропроцессор «из гаража»

Наверняка каждый, имеющий дело с электроникой и ПЛИС, знаком с сайтом opencores.org, где собрано множество полезных (и не очень) решений для электроники — десятки, может быть и сотни, реализаций процессоров и периферии — как оригинальных реализаций уже существующих устройств, так и новых разработок. В этой статье пойдёт речь о 32-битном микропроцессоре с оригинальной системой команд, созданном на основе платы «Марсоход2».Наша команда в течение 10 лет занимается микроядром L4 и в какой-то момент пришло понимание, что само микроядро можно реализовать в виде блока процессора. Причём, если полноценное микроядро реализовать в «железе» весьма трудно, то можно хотя бы помочь программной части, переложив часть функций на железо. Сперва мы решили пойти по лёгкому и оптимальному пути — изучить существующие решения, выбрать подходящее и доработь напильником добавить возможности, полезные для микроядра. Работа заняла около месяца и былы изучены почти что все решения, найденные на opencores. Взятие за основу готового решения открывало неплохие возможости, в виде готовых компиляторов и различных библиотек. В какой-то момент нам перестали нравиться существующие решения — что-то оказалось сложным, что-то неоптимальным, что-то просто недоделанным, что-то, чего уж скрывать, имело не очень подходящую лицензию и условия использования. Набравшись смелости и скрепя зубами мы начали авантюру, решив разрабатывать процессор с самого начала.С чего начинается микропроцессор? Спросите у системного программиста, и он ответит вам что это система команд. Несмотря на тотальную моду на RISC архитектуру, мы решили не привязывать длину инструкций к размеру машинного слова. Поэтому мы провели несколько экспериментов. Как ни странно, но очень удобным инструментом для проектирования системы команд оказался… Microsoft Excel. Прежде всего мы выделили несколько стобцов, использовав их для нумерации инструкций в трёх системах исчисления — десятичной, шестнадцетричной и двоичной. В итоге получилось 256 строк, по числу состояний, которые можно описать одним байтом. Затем мы попытались логически сгруппировать инструкции таким образом, чтобы схема их декодирования была максимально проста. Первый блок инструкций заняли однобайтовые инструкции — префиксы, модификаторы и простые инструкции. Следующтй блок инструкций выглядит так:

image

На следующем этапе пришлось определиться с количеством и типами регистров. Сколько регистров, по-вашему, будет оптимально для большинства задач? Ответы на этот вопрос могут сильно отличаться в зависимости от личности отвечающего — кому-то и 32-х нехватает, как есть и приверженцы безрегистровой архитектуры. Мы решили остановиться на 16-ти регистрах общего назначения. Это количество вполне комфортно как программирования на ассемблере, вполне удачно ложатся на нашу архитектуру и несложно реализуются в HDL.

Определившись с регистрами, мы решили сделать полностью позиционно независимую систему команд — в архитектуре нет ни одной команды перехода по абсолютному адресу — все переходы осуществляются относительно текущей команды. Мы просто помешаны на компактности, поэтому все команды переходов имеют три формы — знаковые смещения размерностью 1, 2 и 3 байта. Например, ниже показаны переходы с 16-битным смешением:

image

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

Позиционная независимость кода вносит ещё одну особенность — обращение к константым данным. Поскольку код может быть расположен по произвольному адресу, то и константные данные могут расположены произвольно вместе с кодом. Решение оказалось весьма простым — использование того же самого префикса NOTCH при загрузке в регистр константы, использует константу как смещение относительное исполняемой инструкции — таким образом решается проблема адресации данных в позиционно независимом коде.

После проектирования системы команд, которое в целом заняло около года, мы вооружились средой Qauartus и Icarus Verilog и… поняли что поспешили. Реализовать систему команд на языке Verilog оказалось чертовски сложным. Знающие люди посоветоли обкатать решения на софтмодели, написав декоодер команд и другие функциональные устройства на обычном Си. После реализации эмулятора несуществующего процессор и прогона на нём тестовых программ — дела пошли лучше. Ещё полгода понадобились на реализацию процессора на Верилоге. Тут следует сказать, что для новичка программрование ПЛИС может оказаться невероятно сложным, а многолетний опыт программирования на языках высого уровня может даже усложнить задачу. В таком случае на помощь приходят средства моделирования. На первом этапе чрезвычайно полезным оказался Icarus Verilog — бесплатный инструмент для моделирования схем, в комплекте с которым идёт GTKWave — программа для отображения сигналов. Используя эти интсрументы можно уввидеть что происходит с устройством в каждый момент времени. На каком-то этапе возможноестей Icarus Verilog стало мало и мы воспользовались симулятором ModelSim компании MentorGraphis — это очень мощный коммерчсеий инструмент, урезанную версию которого можно бесплатно установить совместно со средой Altera Quartus.

О процессе отладки можно рассказывать долго. И вот в какой-то момент, когда ресурсы ПЛИС были заняты на целую треть, неожиданно появилось понимание, что полученный процессор уже можно использовать для каких-то проектов.

73fcfc319b8ac2db4f6cfe64f60c5b2d.png

Для демонстрации возможностей процессора мы написали простейшую микропрограмму, которая при старте выводит следующее меню на экране удалённого терминала:

┌────────────────────────> Welcome to Everest core <───────────────────────────┐ │ 1 - Load binary file via X-modem protocol │ │ 2 - Run previously loaded binary file │ │ 3 - Show RAM (0x100000-0x100140) │ │ 4 - Test of message registers │ │ 5 - Show previously loaded ANSI picture │ │ 6 - Show built-in ANSI pic #1 │ │ 7 - Show built-in ANSI pic #2 │ └──────────────────────────────────────────────────────────────────────────────┘ Если нажать клавишу 1 и если ваш терминал поддерживает передачу файлов по протоколу X-modem, то в устройство можно загрузить файл размером до 4 Кб. Это может быть текст или ANSI-картинка — в этом случае нажатие на клавишу 5 выведет текст или картинку на экран. Но стоило бы ради этого писать статью? Конечно нет, поэтому при нажатии в терминале на клавишу 2 управление передаётся коду, загруженному с помощью первого пункта меню. Если передать управление загруженному тексту или ansi-картинке, что через несколько шагов процессор наткнётся на несуществующую (ещё неопределённую команду) или обратится к несуществующей памяти. При этом произойдёт переход процессора в пошаговый режим — каждый код, приный с терминала, вызовет выполнение одной инструкции процессора с выводом состояния шин на удалёный терминал.

image

Самое время нажать клавишу «Сброс». Мы назвали «сбросом» левую кнопку на плате Марсоход2.

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

function user_main load r14, 0×2000 push r15 loop: call _get_sysclock load r2, 0×05F5E100 call _div64 call _print_dec lea r1, $shw_str call _puts call _uart_status rcr r0, 2; Бит RCV_RDY в перенос jc done; Выход из цикла если была нажата клавиша load r0, 0×01000000 call _delay jmp loop done: pop r15 return end

include tty.asm include delay.asm include mul.asm include div.asm include print_dec.asm include sysclock.asm

$shw_str db ' seconds since boot',13,10,0 Эта программа в цикле, до нажатия на любую клавишу в терминале, выводит в удалённый терминал информацию о количестве секунд с момента старта или сброса устройства. Чтобы проверить её в деле, понадобится сгенерированный файл usr_demo2.bin.

Небольшое пояснение к программе. Подпрограмма _get_sysclock возвращает количество импульсов кварцевого генератора с момента включения или сброса устройства. Пример дампа подрограммы:

; ------------------- _get_sysclock ------------------ 0198: 37 e3; DEC R14, 4 019a: 60 e3; MOV (R14), R3 019c: e3 ff fe ff f8; LOAD R3, 0xfffefff8 01a1: 68 03; MOV R0, (R3) 01a3: 36 33; INC R3, 4 01a5: 68 13; MOV R1, (R3) 01a7: 68 3e; MOV R3, (R14) 01a9: 36 e3; INC R14, 4 01ab: 05; RETURN При выходе из подпрограмммы _get_sysclock регистр R0 содержит младшие 32 бита, а регистр R1 старшие 32-бита результата.Константа 0×05F5E100 это число импульсов тактового генератора за одну секунду.

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

Если вы не слышите новостей от нашего проекта, то знайте — мы работаем над переносом микроядра L4 в ПЛИС.Спасибо за внимание.

© Habrahabr.ru