Наконец-то можно запустить в моём компьютере на электромагнитных реле программу длиннее одной инструкции. Сейчас в нём есть ПЗУ на 8 команд, процессор с АЛУ и 8 восьмибитных регистров (один из которых PC).
Всего процессор поддерживает 5 групп инструкций: Арифметико-логические операции (ALU), Загрузка числа в регистр (MOVI), пересылка между регистрами (MOV), Остановка работы (HALT), Работа с памятью (LDST). Но есть нюансы…
Кроме этих 5 групп, в описании зарезервирован код для операции NOP, хотя она никак и не декодируется процессором. В реальности все незадействованные комбинации будут работать как NOP.
Команды пересылки
Что можно сделать с таким набором команд, если там нет даже переходов? Так как PC входит в регистровый файл наравне с остальными регистрами (A, B, C, D, M, S, L), то и все операции с ним делаются так же, как и с другими регистрами. Тогда загрузка целого числа в PC — это переход по адресу.
Два поля инструкции MOVI — это регистр и 8-битный непосредственный операнд. Все команды у меня 16-битные для упрощения загрузки их из памяти, декодирования, а также программирования в машинном коде. Код инструкции MOVI выглядит так: 1cccLddd iiiiiiii
Поле i — записываемое в регистр значение, поле d — какой регистр изменяется, 1 — по этому биту мы понимаем, что перед нами именно инструкция MOVI.
Остаётся 4 бита. 3 из них ( c ) отданы под условия при которых инструкция выполняется. Всего задействованы 7 комбинаций: переход всегда, по включенному/выключенному флагу нуля, флагу переноса, флагу знака. Восьмая комбинация не используется.
Последний оставшийся бит (L) позволяет реализовать команду вызова подпрограммы. Если этот бит установлен, то инструкция записывает в регистр L адрес следующей инструкции. Получается как во всяких ARM и MIPS — вызов процедуры это просто переход с записью адреса следующей инструкции в специальный регистр. Чтобы из процедуры вернуться, надо это это значение снова скопировать в PC. Поэтому никакой отдельной команды RET нам не нужно.
Но тогда понадобится инструкция копирования одного регистра в другой — MOV. Вообще-то её можно было бы реализовать с помощью АЛУ (например, как ADD D, S, 0 или OR D, S, S), но я сразу об этом не подумал. Плюс в получившемся решении тоже есть — меньше изнашиваются реле в модуле АЛУ, так как оно задействуется только для арифметических и логических операций, а также не портятся флаги при обычных пересылках.
Сама команда MOV ничем не примечательна, кроме одного побочного эффекта, связанного с дизайном схемы компьютера. Алгоритм её работы такой: обнулить регистр-приёмник, подключить регистр-источник и приёмник к одной шине, чтобы у приёмника тоже включились реле там, где они уже включены в источнике. Из-за этого команды пересылки в регистр из самого себя будут работать не так, как NOP, а как обнуление регистра.
АЛУ
Самое интересное спрятано в этой категории — вся арифметика и логические операции, но здесь определением типа операции занимается непосредственно АЛУ. На входе у него код операции, 2 регистра или регистр + непосредственное значение, на выходе — один регистр. Чтобы не получилось как с MOV, АЛУ внутри содержит теневой регистр, в который записывается результат вычислений. Поэтому операндами арифметико-логических команд могут быть любые регистры в любых сочетаниях.
Общий формат команды такой:
01bbbddd ixxxryyy - бинарные операции ADD, ADC, SUB, SBC, AND, OR, XOR
01111ddd -xxxruu0 - унарные операции NOT, SHR, ROR, RCR
Здесь d — код выходного регистра, x, y — коды входных регистров, b — тип бинарной операции, u — тип унарной операции. Если установлен флаг i, то три бита yyy используются как непосредственный второй операнд команды. Так удобнее прибавлять/вычитать небольшие константы.
Сдвиговые операции работают как у Z80, а не как у i386 — сдвигают только на один бит. Сдвига влево нет, потому что можно обойтись сложением для всего, кроме циклического сдвига. Ну а циклический сдвиг влево делается за две инструкции:
ADD A, A, A
ADC A, A, 0
АЛУ хранит три флага последней операции — перенос, ноль, знак. Причём меняются они только тогда, когда АЛУ выполняет очередную команду и не портятся остальными инструкциями. При желании флаги можно вернуть даже из функции.
Не всегда удобно перезаписывать регистр, чтобы получить значения флагов. Чтобы сделать команду типа CMP или TST как у i386, надо сбросить бит r в коде инструкции АЛУ. Тогда результат вычисления останется только в теневом регистре (чтобы можно было посчитать флаги), а обычные регистры не поменяются.
Fire catch and
HALT — команда для остановки работы компьютера. Останавливается работа тактового генератора, всё замирает. Можно продолжить работу со следующей команды, нажав кнопку «старт».
Работа с памятью
Ещё есть пара инструкций для работы с памятью (чтение/запись по адресу в регистре и по непосредственному адресу). Но так как из памяти сейчас есть только 8 слов ПЗУ, то толку от этих инструкций пока немного.
ПЗУ
ПЗУ по задумке занимает 64 адреса из всей восьмибитной шины. По каждому адресу можно считывать 16 бит при выборке команды или 8 младших бит этой команды при выборке данных.
Ячейка ПЗУ состоит из двух DIP-переключателей. Так можно легко набирать программу, а также проверять что «записано» в ячейках. Напротив каждой инструкции есть светодиод для наблюдения за ходом выполнения программы.
Проблема «неожиданно» возникла, когда я забыл учесть, что ток может идти через ключ в обе стороны. Поэтому при выборке одной ячейки к шине могли подключиться сразу несколько других:
Пришлось заказать новый комплект плат, в которых я уже предусмотрел место под диоды. Правда пайка теперь занимает намного больше времени, потому что на каждые 8 ячеек ПЗУ надо припаять 128 SMD-диодов.
Что можно уместить в 8 инструкций?
Пока я написал только программу для вычисления чисел Фибоначчи:
1000 0001 0000 0001 00: movi B, 1
0100 1010 0001 1000 01: add C, B, A
0001 1000 0001 0000 02: mov A, B
0001 1001 0010 0000 03: mov B, C
1000 0111 0000 0001 04: jmp 01
Заняло всего 5 слов из имеющихся 8, а значит можно придумать программу и посложнее, но интересных идей пока нет.
» Страница проекта на github
» Подробное описание системы команд
» Первая часть описания
» Вторая часть описания
» Третья часть описания
Автор: Dovgaluk