Выбор набора инструкций.
Предлагаю вам перевод цикла The EightThirtyTwo ISA Part 2: Choosing the Instruction Set автора Alastair M. Robinson.
В части 1 я рассказал о том, почему я хотел создать свой собственный микропроцессор и как я остановился на архитектуре, использующей восьмибитные слова команд и восемь адресуемых тридцатидвухбитных регистров.
Поскольку в наших командных словах не будет места для кодирования двух регистров, каждая команда будет принимать только один операнд – поэтому нам нужен способ подачи второго операнда в такие инструкции, как add, xor, load immediate и т.д. Для этого я определил девятый регистр, который назову temp. Этот регистр не адресуется через слово инструкции – он просто используется неявно везде, где это необходимо. Одно из ключевых решений, которое нужно принять, будет заключаться в том, будет ли результат арифметических инструкций поступать в используемый или во временный регистр.
Не совсем ясно, не подумав об этом (и в идеале не написав несколько тестовых программ), какие именно инструкции нам нужно реализовать, поэтому сначала я перечислю все возможные инструкции, которые приходят на ум, а затем выясню, что является обязательным, что необязательным и что наиболее выгодно с точки зрения уменьшения размера кода, в надежде, что мы сможем достичь нашей цели в 25 инструкций.
Возможные инструкции включают в себя:
- Инструкции загрузки, перемещения и хранения: (12 штук)
- Загрузить непосредственно. (LI) Загрузить знаковое шестибитовое значение в регистр temp. Если предыдущая инструкция также была LI, содержимое регистра temp сдвинуть влево на шесть бит и сделать "OR" с новом непосредственным значением. Таким образом, мы можем загружать длинные значения с помощью каскадных инструкций LI.
- Move to Register (MR) – перенести значение из temp в выбранный регистр.
- Move to Temp (MT) – перенести значение из выбранного регистра в temp.
- EXchanGe (EXG) – обмен значениями выбранного регистра с temp.
- Load Data (LD) – загрузить данные из RAM по указателю в выбранном регистре.
- STore data (ST) – записать данные в RAM указатель в выбранном регистре.
- Load Data and Increment – (LDINC) загрузить данные из RAM указатель в выбранном регистре, с последующим приращением регистра.
- Load Data and Decrement – (LDDEC) загрузить данные из RAM указатель в выбранном регистре, с последующим декрементом регистра.
- STore data and Increment (STINC) – записать данные в RAM указатель в выбранном регистре, с последующим приращением регистра.
- STore data and Decrement (STDEC) – записать данные в RAM указатель в выбранном регистре, с последующим декрементом регистра.
- LoaD Byte (LDB) – загрузить байт из RAM, указатель в выбранном регистре.
- STore Byte (STB) – записать байт в RAM, указатель в выбранном регистре.
- Команды управлению потоком: (11 штук)
- JuMP (JMP) – записать в PC значение temp
- Jump to SubRoutine. (JSR) – записать в стэк адрес возврата, записать в PC значение из TEMP. Альтернативно, записать в PC значение temp, записать в temp PC+1 и сделать вызов отвечающий за запись / восстановление.
- RELative JuMP (RELJMP) – добавить содержимое temp к PC.
- RELative Jump to SubRoutine (RELJSR) – то-же,что и JSR только добавить temp к PC.
- Jump on Condition Code (JEQ, JNE, JLT, JGT, JLE, JGE) – условный переход в зависимости от флагов Zero and Carry.
- Ничего не делать (NOP).
- Арифметические и логические команды (19 штук)
- Add – сложить temp с выбранным регистром. (Еще не решил будет ли результат отправлен в temp или выбранный регистр.)
- Add with carry (ADDC) – сложить temp + флаг carry с выбранным регистром.
- Sub – вычесть temp from register.
- Reverse Sub (SUBR) – вычесть регистр из temp.
- Compare (CMP) – вычесть temp из регистра, отбросить результат.
- Sub with carry (SUBC) – вычесть temp + carry из регистра.
- Negate – (NEG) дополнить (инвертировать) выбранный регистр.
- Умножение – (MUL)
- And
- Or
- Xor
- Not
- Сдвиг влево
- Арифметический сдвиг вправо
- Логический сдвиг вправо
- Циклический сдвиг влево
- Циклический сдвиг вправо
- Циклический сдвиг влево через перенос (ROLC)
- Циклический сдвиг вправо через перенос (RORC)
Итак, это 42 инструкции – нам нужно потерять 17 из них. Итак, давайте посмотрим, что можно легко реализовать с точки зрения других инструкций.
- “Not rn” можно заменить на “li -1, xor rn”
- “Neg rn” можно заменить на “li -1, xor rn, li 1 add rn”
- Нам не нужно вращать одновременно влево и вправо, так как результат проворачивается; поворот влево на 8 бит - это то же самое, что поворот вправо на 24 бита.
- Sub может быть реализован путем отрицания одного из операндов и последующего сложения.
- “Subr rn” можно заменить на “exch rn, sub rn”
- Mul приятно иметь, но он необязателен для большого количества soft процессорных ядер.
- Load/Store данных с инструкциями инкремента/декремента могут быть удалены..
- Без сомнения, NOP будет необходим внутренне для борьбы с остановками конвейера и hazards данных, но нет необходимости раскрывать его на уровне ISA. Если мы действительно хотим ничего не делать, мы можем использовать пару инструкций exch.
Это экономит нам 12 инструкций – нам нужно потерять еще 5, так что нам нужно думать дальше, что выкинуть из коробки.
Предположим, мы сделаем PC одним из восьми адресуемых регистров: затем мы сможем реализовать переходы, манипулируя этим регистром, и обойтись без jmp, jsr, reljmp и reljsr.
Команда условного перехода может остаться, если мы сможем закодировать условия в три бита, но другой вариант, и весьма привлекательный, состоит в том, чтобы позаимствовать идею, которая восходит к 1950-м годам, но которая вышла из моды в последние годы: предикация.
Наборы команд с предикацией обычно отводят несколько битов команде кодировке для флагов условий, которые разрешают или предотвращают выполнение каждой команды на индивидуальном уровне в зависимости от того, соответствуют ли они текущим кодам условий процессора. PA-RISC, IA-64 и ARM32 имеют всестороннюю предикацию, но MIPS и x86 в основном ограничены условными ветвями и инструкцией условного перемещения.
Очевидно, что мы не можем выделить пространство кодирования для полной предикации, но то, что мы могли бы сделать, это определить инструкцию “Cond”, которая будет сравнивать соответствие кодов условий, а затем включать или отключать выполнение всех инструкций до дальнейшего уведомления. Результат может выглядеть следующим образом :
cmp r0
cond SGT ; выполнить следующие инструкции только в том случае, если temp был больше r0
li branchtarget
mv r7 ; r7-счетчик программы (PC)
cond EX ; Вернуться к безусловному выполнению
Самое привлекательное в этом то, что мы могли бы реализовать простой поток управления “If...else...” без необходимости ветвления вообще.
В следующий раз я напишу еще несколько фрагментов кода, посмотрю, как себя чувствует этот ISA, решу, какие инструкции сохранить и можно ли внести какие-то простые изменения, чтобы сделать его более удобным в использовании.
Адрес для контактов : imax9@narod.ru
Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.
При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.
© Максим Ильин 2022г.