Прибиваем гвоздями набор инструкций.
Предлагаю вам перевод цикла The EightThirtyTwo ISA Part 3: Nailing Down the Instruction Set автора Alastair M. Robinson.
Мой первоначальный план для EightThirtyTwo ISA состоял в том, чтобы определить восемь регистров общего назначения и использовать девятый регистр “temp” в качестве аккумулятора. Непосредственные значения должны пересылаться сюда, как и результат любых арифметических операций. Лучший способ проверить эту концепцию - это, конечно, написать какой-нибудь реальный код. Итак, давайте посмотрим, как выглядит простой цикл с этой концепцией:
li 99 // (требуется две инструкции li из-за того что 99 не вписывается в рамки 6-bit со знаком)
mr r0
.bottles_of_beer:
// отнять еденицу, и дальше по циклу
sub r0
mr r0
cond NEQ // Остановить выполнение, если достигнем 0
li .bottles_of_beer
mr r7 // если нет, выполнять следующую итерацию
cond EX // возвратиться к нормальному выполнению
Это не так уж плохо – объем в 10 байт – намного лучше, чем MIPS, но 68K легко побеждает нас благодаря своей инструкции dbf. Давайте попробуем что-нибудь более интересное…
Первое, что я хочу сделать с этой ISA, если мне удастся превратить ее в настоящее ядро процессора, - это построить SOC, подобный тому, который использовался в проекте ZPU Demos. Этот SOC определяет UART по адресу 0xffffffc0. Этот регистр имеет флаги " tx ready’ и ‘rx ready’ , а также фактические данные. Запись в этот регистр выглядит примерно так:
li 0xffffffc0 // регистр UART (требуется две инструкции li)
mr r0
li msg
mr r1
.loop
ld r0 // прочитать состояние UART
mr r2
li 0x100 // tx ready? (требуется две инструкции li)
and r2
cond EQ
im .loop
mr r7
cond EX
ld r1 ; На данный момент, игнорируем извлечение данных для упрощения.
cond NEQ
st r0
li 1
add r1
mr r1
im .loop
mr r7
cond EX
.done // 23 инструкции (байт)
Мы проигнорировали извлечение данных в приведенном выше фрагменте кода, но это явно то, что нам нужно. Это выглядело бы примерно так:
ld r0 // прочесть 32-бит, предполагая, что SOC будет игнорировать биты 0 и 1 адреса.
mr r1 // 32-битное слово
im 3
and r0 // замаскировать все, кроме двух младших битов
mr r0
im 3
exg r0
sub r0 // вычесть из 3 - на сколько нам требуется сдвигать
mr r0
im 3
shl r0 // умножить на8
lsr r1 // сдвинуть слово на нужное число бит
im 255
and r1 // 14 инструкций
Это довольно много кода, и поскольку манипуляция байтами является обычной операцией, это говорит о том, что добавление инструкций загрузки и хранения байтов было бы целесообразно, если бы я мог найти для них место. Поскольку доступ к байтам по отдельности редко используют, я буду реализовывать загрузку и хранение с пост инкрементом, поскольку это наиболее распространенный вариант использования.
Я также замечаю, что каждый раз, когда я использую арифметическую или логическую инструкцию, я немедленно записываю результат в регистр, потому что я не могу использовать непосредственное значение с помощью "li", не перезаписывая то, что находится в аккумуляторе. Единственный способ избежать этого-предварительно сгенерировать непосредственные объекты и заранее записать их в регистры. В этом и некоторых других экспериментальных фрагментах кода плотность кода и простота программирования, по-видимому, немного улучшаются, если результат поступает непосредственно в назначенный регистр вместо временного регистра. (Есть одно важное исключение из этого правила, о котором я расскажу в следующей части.)
По этой причине я решил, что результат арифметических и логических инструкций теперь будет поступать в регистр, указанный в инструкции, а не в временный регистр.
Полуфинальный набор инструкций, который, как мне кажется, сейчас более или менее выбран (если только я не столкнусь с серьезными проблемами реализации!), выглядит следующим образом:
- cond – включить условное выполнение, 3-х битный операнд задает условие EQual, NotEQual, StrictlyLessThan, LessthanorEqual, StrictlyGreatherThan, GreaterthanorEqual, EX(ecute) или N(o)EX(ecute)
- mt – переслать регистр в temp
- mr – переслать temp в регистр
- exg – обменять регистр с temp
- ldi – относительная загрузка (выбранный регистр + r5) [может быть удален – я еще не пробовал его использовать]
- st – сохранить в память по указателю в rn
- ld – считать из памяти по указателю в rn
- sti – относительная запись (выбранный регистр + r5) [может быть удален – я еще не пробовал его использовать]
- add
- cmp
- ldinc – загрузка с пост-инкрементом (для стека)
- sub
- stdec – запись с пост-декрементом (для стека)
- and
- or
- xor
- addt – добавление и перенос старого содержимого региста в temp. (Использается для PC-независимых ветвлений, но может также использоваться в индексировании.)
- shl – сдвиг влево
- asr – арифметический сдвиг вправо
- lsr – логический сдвиг вправо
- ror – циклический сдвиг вправо
- rorc – циклический сдвиг вправо через перенос [может быть удален – я еще не пробовал его использовать]
- stbinc – записать байт с пост-инкрементом
- ldbinc – загрузить байт с пост-инкрементом
- li – немедленная загрузка
Адрес для контактов : imax9@narod.ru
Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.
При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.
© Максим Ильин 2022г.