11:42 05.03.2013
Пилим восьмибитный процессор. Часть пятая: ассемблер, тесты, запуск. Небольшое предисловие, которое должно было быть в самой первой статье по этой теме:
Я понятия не имею, как правильно проектировать процессоры. И как неправильно - тоже. Я просто играюсь в конструктор, делаю ошибки, перекраиваю схемы на лету и наслаждаюсь этим забавным и увлекательным процессом.
Перечитывая предыдущие части, я нахожу в них заметное количество косяков и откровенных глупостей. Скажем, я так и не определился с архитектурой процессора, вроде сначала задумав сделать что-то RISC-подобное, но временами замахиваясь на CISC, и огребая от сложности реализации откатывался обратно. В итоге же получается, что я во многом копирую единственную знакомую мне архитектуру x86.
Поэтому: эти статьи рассказывают о том, как делаю я, а не о том, как надо.
Но окей, перейду к делу.
Поскольку теперь у нас есть контроллер памяти и минимально необходимые для функционирования ядра схемы, нужно попытаться собрать их все вместе и оттестировать. Пока набор инструкций крайне мал, тестирование процессора можно производить так: подать ему на вход все возможные наборы инструкций и посмотреть, как он их обработает. При этом следует учесть, что большая часть ошибочных инструкций не отлавливается - подай мы на вход несуществующую команду или несовместимый набор параметров - процессор поведёт себя непредсказуемо. Но отлов ошибок оставляю на потом, сейчас важнее и интереснее проверить работу на корректном наборе инструкций.
Итак, как выглядит вся схема в сборе:
Не очень-то впечатляет =). Но если раскрыть схемы, и представить всё это добро вместе, получится вот так:
Клик для увеличения
Я не стал раскрывать все подсхемы (в которых, в свою очередь, есть ещё подсхемы) - по-моему и так внушительно. Даже Шмигирилову показать не стыдно.
Несколько комментариев к схеме.
- Выход Execute end сделан на будущее. В дальнейшем он будет сигнализировать о том, что процессор выполнил очередь команд и ждёт новых инструкций. Пока же все инструкции выполняются за один так, так что смысла в подобной сигнализации нет.
- Выход ERR - это зачатки системы контроля ошибок. Сейчас на него подаётся сигнал в единственной ситуации: если попытаться работать с памятью на ввод и вывод одной инструкцией (пример: MOV [addr],[addr]).
- В процессе отладки (который я ещё буду описывать) была найдена незначительная ошибка: в блоке команд не стояло несколько согласующих резисторов, отчего невозможны были операции с памятью. Эта ошибка присутствует на схемах, приведённых в предыдущей статье.
- Индикаторы, если кто ещё не догадался, показывают значения регистров и предназначены только для увеличения удобства.
- Память и ядро работают на одной частоте, несмотря на то, что тактовые генераторы у них разные. Теоретически, ничто не должно мешать работать им на разных частотах (если память будет работать быстрее - нужно будет ввести простенький механизм ожидания выполнения), но logisim не позволяет задавать разные частоты двум элементам.
Пожалуй, можно переходить к тестированию, которое, как решено выше, должно заключаться в передаче процессору всех вариантов корректных команд. Такой набор можно сгенерировать вручную, а можно - и даже нужно - написать ассемблер, переводящий читаемые команды в машинный код.
На реализацию этого ассемблера на PHP ушло около часа, побаловаться можно здесь
Meighty assembler (название "Meighty" у меня получилось из объединения слов might и eight, так я и назвал свой процессор). Команды вводить в чёрное окошко, жать compile. При удачной трансляции слева от окна появится двоичный байткод, снизу - его шестнадцатеричное представление, которое можно копипастить в logisim (буквально, его компонент ОЗУ корректно воспримет вставку таких данных), при ошибке выдастся описание проблемы.
Клик для увеличения
Ассемблер имеет intel-like синтаксис, регистр значения не имеет, все лишние пробелы игнорируются. Числовые значения задаются только в шестнадцатеричной системе счисления, десятичная и двоичная, возможно, будут сделаны потом. Операнды перечисляются через запятую, и задаются следующим образом:
- константа: значениеH, например 10H, FDH, 7h;
- регистр: идентификатор регистра от A до H;
- порт: @номер портаH, например @03H, @88h;
- адрес в памяти: [адресH], например [12h], [FFH], [0H];
Ассемблер пытается отлавливать ошибки, как синтаксические, так и логические, можете поиграться. Комментарии не реализованы. Порты, как понимаете, тоже отсутствуют, выполнение команд чтения-записи для них смысла не имеет.
Пример программы (абсолютно бессмысленной, её цель только продемонстрировать работу):
MOV A,10h
MOV B,A
SUM B,3h
MOV [20h],B
MOV C,[20h]
SHL [20h],2h
XOR A,[20h]
NOP
и т.д.
Меня КРАЙНЕ ломает делать гифку, которая продемонстрировала бы, что эта, да и все другие допустимые программы, выполняются корректно. Гифкоклепание займёт не меньше часа, так что предлагаю просто
скачать logisim, взять
мою схему и посмотреть всё самим.
Что дальше? Примерно вот что:
- реализация восьми дополнительных служебных регистров вдобавок к восьми имеющимся, которые останутся пользовательскими. Служебные регистры будут предназначены, прежде всего, для организации работ программ в памяти (требуется разделять код и данные - значит нужны аналоги CS и DS) и возможного её увеличения (для адресации за пределами двух килобайт можно ввести сегментацию, тогда понадобится регистр, указывающий на текущий сегмент), а также для выполнения команд условного перехода (команда будет сравнивать два определённых регистра), ну и давно задуманный флаговый регистр. Возможно - счётчик (это будет зависеть от того, как я реализую циклы).
- стек. Его я, как и было задумано ранее, всё-таки реализую на отдельной схеме - пусть будет хоть какое-то значимое отличие от x86. На реализацию четырёх стековых команд (стандартные POP, PUSH, не очень стандартная PEEK и совсем не стандартная POKE) понадобится всего один опкод (т.к. параметр у этих команд будет всегда один, два неиспользуемых бита можно отдать под команду).
- команды условного и безусловного перехода.
- дополнительные команды обработки данных (SUB, MUL, DIV, NOT).
- доработка ассемблера.
- отлов и обработка ошибок.
- ASCII-терминал.
Дальше можно будет думать о простой системе прерываний (например, наборе подпрограмм, зашитом в ПЗУ), но я уже не уверен, что меня на это хватит.
Продолжение, скорее всего, следует.
read more at
Журнал Великого Позитроника rss2lj