суббота, 11 июня 2016 г.

Проект "Янтарь"

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

Что такое "Янтарь"?

Если коротко - то это мой собственный компьютер, придуманный с нуля, и реализованный на базе отладочной платы Terasic DE0 с чипом Altera Cyclone III. Он имеет собственную архитектуру, набор команд и некоторый набор инструментария разработчика и уже написанных программ. 

Компьютер "Янтарь" на фоне Большого Компьютера


Каковы характеристики компьютера на данный момент?
  • Разрядность процессора - 16 бит.
  • ОЗУ - 65536 16-битовых слов.
  • Тактовая частота: 160 КГц.
  • VGA-адаптер с монохромным текстовым режимом 80х30.
  • PS/2-клавиатура.
  • USB UART-модуль Wavesahre FT232 для связи с "большим" компьютером.
  • Инструментарий разработки: ассемблер "ALASM" (написан на C#)
  • Программная оболочка: программа-монитор "M3004" 

Зачем это нужно?

Этот проект имеет исключительно развлекательные и самообразовательные цели. Во-первых, я давно хотел разобраться в том, как работает компьютерное железо. Шутка ли, больше двадцати лет программировал, а до конца этого не понимал. Во-вторых, меня всегда немного завораживала старая компьютерная техника. Чтобы убить двух зайцев, я решил разработать с нуля собственный компьютер в духе классических домашних компьютеров эпохи процессора Intel 8080, его брата - Zilog Z80 и операционной системы CP/M.

Система

Здесь я в двух словах опишу систему, как она есть сейчас. На этой схеме изображены основные блоки:




В принципе, тут должно быть всё понятно, отмечу лишь несколько моментов:
  1. Первые 16К ОЗУ реализованы на встроенных блоках памяти, находящихся на чипе ПЛИС. В них прямо в VHDL-коде "зашита" первоначальная программа, с котрой стартует компьютер. Для остальных 48К используется SDRAM-чип, расположенный на плате.
  2. Одна из кнопок на плате напрямую заведена на шину как сигнал аппаратного прерывания. Она используется для прерывания программы и вызова отладчика.
  3. На борту у платы есть микросхема электрического интерфейса для RS232, но у меня не нашлось под рукой нужных разъемов, поэтому я купил отдельный UART<->USB адаптер Waveshare FT232 и подсоединил его через GPIO-выходы на плате.
  4. Я пока не освоил запись в Flash-память, поэтому использую этот чип как ПЗУ. 
Системная шина устроена достаточно примитивно и состоит из следующих сигналов:
  • Данные - 16 бит.
  • Адрес - 16 бит.
  • Сигнал чтения.
  • Сигнал записи.
  • Сигнал выбора пространства ввода/вывода.
  • Сигналы запроса на прерывание - 16 бит.

Процессор

Процессор построен по одношинной архитектуре и управляется микрокодом. Пользователю напрямую доступны 8 регистров общего назначения (R0 - R7), а так же два спец. регистра: 
  • SP (stack pointer) 
  • BP (base pointer) 
последний указывает на фрейм текущей подпрограммы.

Система команд выполнена в духе так называемых "load/store" архитектур. Практически все операции выполняются над значениями в регистрах или над регистром и константой. Команды load/store поддерживают немного более широкий список режимов адресации:
  • Абсолютный адрес: ld R0, [0FC00]
  • Адресация через регистр: ld R0, [R1]
  • Адресация через регистр + константа: ld R0, [R1 + 100]
  • Адресация через регистр + регистр:     ld R0, [BP + R1]
Для косвенной адресации могут применяться любые регистры из вышеперечисленных.

Микрокод процессора похож на примитивный ассемблер и выполняется строго последовательно, поэтому такты расходуются достаточно неэффективно. Вот так выглядит мирокпрограмма операции сложения регистра с константой:
ADDC:
INI OA, OP_ADD; // Установка кода операции АЛУ
FETCH;          // Вычитка слова с константой
MOV O1, REG1;   // Инициализация первого операнда
MOV O2, DATA;   // 
Инициализация второго операнда
MOV REG1, RES;  // Запись результата
END;
Как видно, пять микрокоманд. С учётом дополнительных издержек - 7 тактов. Такая чудовищная неэффективность процессора есть результат сознательного выбора - чем проще, тем лучше.

Из интересных особенностей процессора могу отметить встроенный механизм переключения контекста: по команде или по прерыванию процессор может атомарно сохранить своё состояние в ОЗУ и считать новое состояние. Этот механизм применяется для обработки прерываний, так же для управления программами в мониторе.

Ассемблер

Ещё в самом начале работы над виртуальной версией процессора я быстро понял, что программирование в машинных кодах - это для меня слишком брутально и хардкорно. Тогда я написал первую версию ассемблера на VBA. С тех пор я два раза переписал его на C# и теперь он приобрел достаточно законченный и функциональный вид. Вот так выглядит процедура вывода строки на экран:
subroutine print(message)
    push    R0
    push    R1
    ld      R0, [BP + message]
loop:
    ld      R1, [R0]
    cmp     R1, 0
    je      [exit]
    out     R1, [TTY]
    inc     R0
    jmp     [loop]
exit:
    pop     R1
    pop     R0
    return
end
Ассемблер написан с использованием парсера Sprache, который позволил достаточно быстро и без заморочек описать синтаксис. Правда, как оказалось, для более сложных случаев он не годится, поэтому я сейчас гляжу в сторону Irony.

Монитор

Венцом работы на данный момент является программный монитор "M3004" (назван так потому что эту версию я начал разрабатывать 30 апреля). По мере того, как я переходил от разработки аппаратной части к программной, всё больше появлялась потребность в какой-то оболочке, которая бы облегчила отладку и обеспечила бы базовый инструментарий для управления компьютером. Так постепенно появился и стал развиваться Монитор. Что умеет монитор делать сейчас?
  • Загружать и запускать программы из ПЗУ, а так же из последовательного порта
  • Прерывать исполнение программы по аппаратной кнопке, исследовать и модифицировать состояние памяти и регистров.
  • Выполнять программу по шагам.
  • Ставить точки останова.
  • Читать и писать в порты ввода/вывода


На следующей картинке видно, как была загружена программа, и установлена точка останова. После того, как програма "налетает" на неё, управление  предаётся монитору, где можно проинспектировать состояние и продолжить отладку. Монитор занимает верхние 4К слов адресного пространства, оставляя всю нижнюю память для отлаживаемых программ. Для прерывания программы можно использовать аппаратную кнопку на плате.
Всего Монитор - это ~22 килобайта кода на ассемблере, которые в итоге собираются в ~3500 машинных слов. Потребление памяи машинным кодом оказалось несколько выше, чем я ожидал, но я пока не оптимизировал код.

Заключение

По результату полугодовой работы получилось достаточно функциональный компьютер, который демонстрирует свою работоспособность. Схема, конечно, получилась не без огрехов которые регулярно всплывают в ходе её доработки. Оно и понятно - я действую больше наугад, чем "по книге". Основной нерешённой проблемой для меня остаётся создание устройства долговоременного хранения "на борту" - я так и не смог пока научиться писать в Flash-чип, да и с ним есть определённые проблемы. Подумываю о том, чтобы прикупить дисковод и дискеты и научиться взаимодействовать с ними. Другое направление деятельности - развитие инструментария. Программировать на ассемблере не так уж и весело, хочется реализовать язык высокого уровня. Ну и, наконец, трансформировать Монитор в полноценную операционную систему по типу CP/M