Виртуальный процессор Simpleton 4
В позапрошлом уже году я писал тут про выдуманную под впечатлением от Gigatron TTL архитектуру процессора Simpleton 3.
Однако в итоге я пришёл к выводу, что Simpleton 3 как архитектура сложнее того чем оно заслуживает быть.
В ходе обсуждения на другом ресурсе родилась архитектура Simpleton 4 которая, имхо, проще, ёмче и всячески приятственнее для программирования.
Исходники эмулятора и ассемблера можно посмотреть тут: github.com/aa-dav/SimpX (там же можно увидеть описание ISA на английском языке)
Прежде всего — эта ISA точно является субоптимальной по плотности команд.
Главная цель здесь — это сделать формат инструкций как можно более простым и ортогональным сохраняя программирование достаточно гибким и далёким от эзотеричности.
Плотность кода точно можно повысить получив в итоге что-то типа MSP-430, но это не цель и не вариант. :)
Как и в предыдущем поколении всё — и ячейки памяти и регистры 16-битные для простоты.
В отличие от Simpleton 3.x который каждую команду рассматривал как команду Си вида if (cond) Y ?= X, где? — это код операции Simpleton 4 смотрит на вещи еще более просто.
Он рассматривает все инструкции как одну и ту же операцию: R = Y * X, где опять таки * — это код операции. Т.е. безусловная трёхоперандная система.
Все инструкции имеют один и тот же формат:
Теперь совсем чётко формат инструкции распался на нибблы и последние три ниббла — это коды регистров и флаги их индирекции (косвенного чтения). Первый же ниббл — это код операции.
Таким образом всё что умеет делать этот процессор в каждой операции — это считывание аргументов X и Y, запись их в ALU вместе с кодом операции и запись полученного результата из ALU в R.
— Всё (включая ячейки памяти) 16-битное
— 8 регистров (нельзя сказать что «общего назначения», т.к. других тут просто нет) R0-R7
— последние три имеют псевдонимы:
R5 — SP — указатель стека — при косвенном считывании пост-инкрементируется, а при косвенной записи пре-декрементируется
R6 — PC — счётчик инструкций — автоматически инкрементируется при косвенном считывании. Косвенная запись в PC является делом абсурдным, поэтому закреплена (спасибо за идею Lethargeek!) за отменой записи. Т.е. АЛУ проводит операцию и обновляет флаги, но если назначение — [ PC ], то запись результата из АЛУ никуда не производится.
R7 — PSW — processor status word — регистр флагов (включая, например, бит запрета IRQ). Косвенное чтение/запись по этому регистру просто не имеют смысла, поэтому «косвенный PSW» включает особый режим адресации — считывается непосредственное данное из [ PC++ ] и служит адресом ячейки памяти с которой мы работаем. Это режим адресации 'immediate address'.
Непосредственные данные реализуются как косвенное чтение PC, т.к. после считывания опкода PC уже смотрит на следующее слово в памяти и после его косвенного чтения продвинется дальше (как и всегда). Если нужно считывать операнды из памяти при косвенных чтениях, то в машинных циклах это происходит в следующей последовательности: X, Y и затем R.
Некоторые инструкции воспринимают поле X (включая бит индирекции XI) как 4-битное непосредственное данное. Чаще всего со знаком: -8..+7, но иногда и без знака: 0..16. Этот режим считывания операнда X называется 'inplace immediate' и обозначается в ассемблере литерой 'i' в инструкции.
Рассмотрим на примере типовой инструкции add возможные режимы адресации Simpleton 4:
Обратите внимание, что ассемблер придерживается немного нестандартных соглашений:
— все лексемы (включая даже скобки!) обязательно разделяются пробелами
— поэтому не используются запятые для разделения аргументов
— поэтому сложные compile-time выражения (сейчас они не реализованы) надо окружать круглыми скобками, за исключением когда выражение уже находится в квадратных, т.е.:
Всего возможно 16 опкодов для АЛУ. Из них сейчас использовано 14:
00 ADDI — сложить Y с INPLACE данным в XI+X (-8..+7) (и как всегда результат поместить в R)
01 ADDIS — сложить Y с INPLACE данным in XI+X втихую/Silent (без обновления флагов)
02 ADDS — сложить Y с X втихую/Silent (без обновления флагов)
03 ADD — сложение
04 ADC — сложение с переносом
05 SUB — вычитание
06 SBC — вычитание с переносом
07 AND — логическое И
08 OR — логическое ИЛИ
09 XOR — логическое исключающее ИЛИ
0A CADD — условное сложение. Не обновляет флаги. См. ниже.
0B RRCI — циклический сдвиг Y вправо на INPLACE (0..15) число бит. Во флаге переноса копируется последний «прокрученный» бит.
0C RRC — циклический сдвиг Y вправо на на X бит. Во флаге переноса копируется последний «прокрученный» бит.
1. В этой ISA нет отдельного опкода MOVE (но есть такая мнемоника), т.к. он осуществляется через опкод ADDIS с нулём в INPLACE-аргументе X. Этот аргумент в ассемблере можно опускать:
2. Запись значения в PC это переход (jump), а adds pc pc offset — это относительный переход:
3. Условное сложение главным образом нужно для реализации условных переходов, но в принципе может быть использовано и много для чего еще.
Работает оно следующим образом: аргумент X (16 бит) разделяется на две части: верхние 4 бита воспринимаются как код условия, а нижние 12 бит расширяются со знаком в слагаемое (-2048..+2047).
Т.е. код условия это не часть опкода, но часть данных! Это навскидку уже может послужить для каких то сумрачных хаков и полухаков. :)
Если условие срабатывает, то АЛУ возвращает в качестве результата Y+X, иначе Y.
Таким образом чтобы реализовать условный (и всегда относительный) переход мы делаем:
Ассемблер для простоты имеет псевдокоды «j** метка», где ** — это мнемоника кода условия, например:
Если следующие коды условий (довольно классические):
4. Вызов процедуры не может быть реализован в один опкод и требуется две инструкции и три слова:
Для привычности и краткости я посчитал полезным иметь call как псевдоинструкцию в ассемблере:
Однако возврат из процедуры — уже одна инструкция в одно слово:
5. Инструкция ADDI может быть использована (с 0 в immediate X) как «тестирующий move» (псевдоинструкция 'movet'):
6. Включение/выключение прерываний, т.е. выставление соответствующего бита в регистре флагов может быть сделано как:
7. Если в качестве результата ® указывать ключевое слово 'void', то это означает запись в [ PC ] которая приводит к тому, что запись результата из АЛУ никуда не производится. Так в Simpleton 4 можно сделать сравнение:
или побитовые тесты:
или сравнение числа с константой в диапазоне -8..+7 за инструкцию в одно слово (inplace immediate):
или проверка i-ого бита операнда через занос его в CF операцией RRCI:
Если «замапить» в эмуляторе на адрес $FFFF порт ввода-вывода консоли (пока — Windows), то следующая программа уже (давно) собирается и работает:
Что меня с самого детства отталкивало в ассемблере — это его неинтуитивность и некраткость.
Очень часто ловил себя за комментариями к коду вида 'HL = BC + 16'.
Поэтому в Simpleton 3 я реализовал математическую нотацию где операции писались в стиле Си как 'R0 += [ label ]'.
Simpeton 4 сразу оказался не совсем тривиальным к реализации такого синтаксиса простым переписыванием, поэтому я в нём пошёл на такой шаг как реализацию классического ассемблерного синтаксиса. Все эти addis, move и так далее.
Но решимости сделать мат-нотацию не убавилось и я её реализовал тоже.
В ассемблере инструкциями 'mode classic' и 'mode new' можно переключаться между режимами синтаксиса.
Новый «математический» синтаксис большую часть операций выражает как 'R = Y op X' где 'op' это символ операции.
Если X пропущен, то он становится литералом '0', если же опущен 'op', то он становится символом '+'.
Следующие инструкции попадают под этот шаблон целиком и полностью (приведён пример для R=R0, Y=R1 и X=[ label ]) (новый синтаксис написано после двоеточия):
Но три опкода (на сейчас) выпадают из этой схемы и имеют следующий синтаксис:
Все команды с immediate получают символ '<' справа от равенства, плюс addis не обновляющее флаги использует знак '-' вместо '='.
Заметьте, что 'addis a b -1' (отрицательный immediate) может быть в новом синтаксисе написано как 'a < — b + -1' и как 'a < — b — 1' (как будто это вычитание).
Move в новом синтаксисе пишется просто как 'a < — b', что уже является сокращением от 'a < — b + 0'.
Заметьте так же, что псевдоинструкции введённые в ассемблер обычного синтаксиса для простоты сохраняют свою силу и в новом синтаксисе — jnz/call/ret и т.д.
Таким образом пример выше может быть переписан на новый манер так:
Однако в итоге я пришёл к выводу, что Simpleton 3 как архитектура сложнее того чем оно заслуживает быть.
В ходе обсуждения на другом ресурсе родилась архитектура Simpleton 4 которая, имхо, проще, ёмче и всячески приятственнее для программирования.
Исходники эмулятора и ассемблера можно посмотреть тут: github.com/aa-dav/SimpX (там же можно увидеть описание ISA на английском языке)
Прежде всего — эта ISA точно является субоптимальной по плотности команд.
Главная цель здесь — это сделать формат инструкций как можно более простым и ортогональным сохраняя программирование достаточно гибким и далёким от эзотеричности.
Плотность кода точно можно повысить получив в итоге что-то типа MSP-430, но это не цель и не вариант. :)
Как и в предыдущем поколении всё — и ячейки памяти и регистры 16-битные для простоты.
В отличие от Simpleton 3.x который каждую команду рассматривал как команду Си вида if (cond) Y ?= X, где? — это код операции Simpleton 4 смотрит на вещи еще более просто.
Он рассматривает все инструкции как одну и ту же операцию: R = Y * X, где опять таки * — это код операции. Т.е. безусловная трёхоперандная система.
Все инструкции имеют один и тот же формат:
Теперь совсем чётко формат инструкции распался на нибблы и последние три ниббла — это коды регистров и флаги их индирекции (косвенного чтения). Первый же ниббл — это код операции.
Таким образом всё что умеет делать этот процессор в каждой операции — это считывание аргументов X и Y, запись их в ALU вместе с кодом операции и запись полученного результата из ALU в R.
— Всё (включая ячейки памяти) 16-битное
— 8 регистров (нельзя сказать что «общего назначения», т.к. других тут просто нет) R0-R7
— последние три имеют псевдонимы:
R5 — SP — указатель стека — при косвенном считывании пост-инкрементируется, а при косвенной записи пре-декрементируется
R6 — PC — счётчик инструкций — автоматически инкрементируется при косвенном считывании. Косвенная запись в PC является делом абсурдным, поэтому закреплена (спасибо за идею Lethargeek!) за отменой записи. Т.е. АЛУ проводит операцию и обновляет флаги, но если назначение — [ PC ], то запись результата из АЛУ никуда не производится.
R7 — PSW — processor status word — регистр флагов (включая, например, бит запрета IRQ). Косвенное чтение/запись по этому регистру просто не имеют смысла, поэтому «косвенный PSW» включает особый режим адресации — считывается непосредственное данное из [ PC++ ] и служит адресом ячейки памяти с которой мы работаем. Это режим адресации 'immediate address'.
Непосредственные данные реализуются как косвенное чтение PC, т.к. после считывания опкода PC уже смотрит на следующее слово в памяти и после его косвенного чтения продвинется дальше (как и всегда). Если нужно считывать операнды из памяти при косвенных чтениях, то в машинных циклах это происходит в следующей последовательности: X, Y и затем R.
Некоторые инструкции воспринимают поле X (включая бит индирекции XI) как 4-битное непосредственное данное. Чаще всего со знаком: -8..+7, но иногда и без знака: 0..16. Этот режим считывания операнда X называется 'inplace immediate' и обозначается в ассемблере литерой 'i' в инструкции.
Рассмотрим на примере типовой инструкции add возможные режимы адресации Simpleton 4:
add r0 r1 r2 ; сложение регистров r0 = r1 + r2
add [ r2 ] 100 R4 ; сумма R4 с непосредственным данным (PC+indirect) 100 записывается в память по адресу R2
add [ R3 ] R4 [ $200 ] ; сумма R4 c [ $200 ] (immediate address) записывается в ячейку памяти по адресу R3
add [ 100 ] [ 20 ] [ 30 ] ; не загрязняющая регистры (кроме флагов) команда работающая непосредственно с тремя ячейками памяти
Обратите внимание, что ассемблер придерживается немного нестандартных соглашений:
— все лексемы (включая даже скобки!) обязательно разделяются пробелами
— поэтому не используются запятые для разделения аргументов
— поэтому сложные compile-time выражения (сейчас они не реализованы) надо окружать круглыми скобками, за исключением когда выражение уже находится в квадратных, т.е.:
add [ label1 + offset2 ] ( label2 + offset3 ) ( $1000 / 2 ) ; три "сложных" аргумента
Всего возможно 16 опкодов для АЛУ. Из них сейчас использовано 14:
00 ADDI — сложить Y с INPLACE данным в XI+X (-8..+7) (и как всегда результат поместить в R)
01 ADDIS — сложить Y с INPLACE данным in XI+X втихую/Silent (без обновления флагов)
02 ADDS — сложить Y с X втихую/Silent (без обновления флагов)
03 ADD — сложение
04 ADC — сложение с переносом
05 SUB — вычитание
06 SBC — вычитание с переносом
07 AND — логическое И
08 OR — логическое ИЛИ
09 XOR — логическое исключающее ИЛИ
0A CADD — условное сложение. Не обновляет флаги. См. ниже.
0B RRCI — циклический сдвиг Y вправо на INPLACE (0..15) число бит. Во флаге переноса копируется последний «прокрученный» бит.
0C RRC — циклический сдвиг Y вправо на на X бит. Во флаге переноса копируется последний «прокрученный» бит.
Пояснения
1. В этой ISA нет отдельного опкода MOVE (но есть такая мнемоника), т.к. он осуществляется через опкод ADDIS с нулём в INPLACE-аргументе X. Этот аргумент в ассемблере можно опускать:
addis r0 r1 1 ; добавляем 1 к r1 и помещаем результат в r0
addis r0 r1 0 ; добавляем 0 к r1 и помещаем результат в r0
move [ r2 ] r1 ; для самоописуемости мнемоника move кодирует addis с нулём в inplace X
move [ r3 ] [ 100 ] ; копируем из ячейки памяти с адресом 100 в ячейку памяти куда указывает регистр r3
2. Запись значения в PC это переход (jump), а adds pc pc offset — это относительный переход:
move pc address ; прыжок
adds pc pc offset ; относительный прыжок
3. Условное сложение главным образом нужно для реализации условных переходов, но в принципе может быть использовано и много для чего еще.
Работает оно следующим образом: аргумент X (16 бит) разделяется на две части: верхние 4 бита воспринимаются как код условия, а нижние 12 бит расширяются со знаком в слагаемое (-2048..+2047).
Т.е. код условия это не часть опкода, но часть данных! Это навскидку уже может послужить для каких то сумрачных хаков и полухаков. :)
Если условие срабатывает, то АЛУ возвращает в качестве результата Y+X, иначе Y.
Таким образом чтобы реализовать условный (и всегда относительный) переход мы делаем:
cadd pc pc код_условия_со_смещением
Ассемблер для простоты имеет псевдокоды «j** метка», где ** — это мнемоника кода условия, например:
jnz label
Если следующие коды условий (довольно классические):
z, nz, c, nc, s, ns, o, no - тест на вкл и выкл соответствующих флагов
a - "above" - беззнаковое "выше"
be - "below or equal" - беззнаковое "ниже или равно"
ge - "greater or equal" - знаковое "больше или равно"
l - "less" - знаковое "меньше"
g - "greater" - знаковое "больше"
le - "less or equal" - знаковое "меньше или равно"
4. Вызов процедуры не может быть реализован в один опкод и требуется две инструкции и три слова:
addis [ sp ] pc 2 ; вычисляем адрес возврата с занесением его в стек
move pc proc_address ; переходим на процедуру (просто jmp)
Для привычности и краткости я посчитал полезным иметь call как псевдоинструкцию в ассемблере:
call proc_address ; раскрывается в код приведённый выше
Однако возврат из процедуры — уже одна инструкция в одно слово:
move pc [ sp ] ; Опять таки для краткости можно записать типичным 'ret'
5. Инструкция ADDI может быть использована (с 0 в immediate X) как «тестирующий move» (псевдоинструкция 'movet'):
StrCpy: ; R1 - указатель на src, R2 - указатель на dst
movet [ r2 ] [ r1 ] ; опкод addi x y 0
jz Exit
addis r1 r1 1 ; инкремент r1
addis r2 r2 1 ; инкремент r2
move pc StrCpy ; прыгаем в начало цикла
Exit:
ret ; опкод move pc [ sp ]
6. Включение/выключение прерываний, т.е. выставление соответствующего бита в регистре флагов может быть сделано как:
and psw psw маска_флага ; включить
or psw psw ~маска_флага ; выключить (~маска_флага это побитовая инверсия маски флага)
7. Если в качестве результата ® указывать ключевое слово 'void', то это означает запись в [ PC ] которая приводит к тому, что запись результата из АЛУ никуда не производится. Так в Simpleton 4 можно сделать сравнение:
sub void A B ; экивалентно cmp A B во многих других ISA
jnz ...
или побитовые тесты:
and void r0 $0001
jz ...
или сравнение числа с константой в диапазоне -8..+7 за инструкцию в одно слово (inplace immediate):
addi void r0 -3
jz ... ; r0 равен 3
или проверка i-ого бита операнда через занос его в CF операцией RRCI:
rrci void r0 3 ; третий бит r0 попадёт в CF
jc ... ; переход если CF=1
Если «замапить» в эмуляторе на адрес $FFFF порт ввода-вывода консоли (пока — Windows), то следующая программа уже (давно) собирается и работает:
PORT_CONSOLE = $FFFF
move sp $70 ; Setup stack
move r0 str0 ; shortcut for addis r0 str0 0
call str_print ; shortcut for addis [ sp ] pc 2 : addis pc str_print 0
move r0 str0
call str_print
dw 0 ; STOP
str_print movet r1 [ r0 ]
jz .exit ; shortcut for cadd pc pc mix_of_offset_and_condition_code
move [ PORT_CONSOLE ] r1 ; output char to console
addi r0 r0 1 ; increment r0
move pc str_print ; jump to the beginning of procedure
.exit ret ; shortcut for move pc [ sp ]
str0 dw "Hello, world!" 10 0
Новый синтаксис ассемблера
Что меня с самого детства отталкивало в ассемблере — это его неинтуитивность и некраткость.
Очень часто ловил себя за комментариями к коду вида 'HL = BC + 16'.
Поэтому в Simpleton 3 я реализовал математическую нотацию где операции писались в стиле Си как 'R0 += [ label ]'.
Simpeton 4 сразу оказался не совсем тривиальным к реализации такого синтаксиса простым переписыванием, поэтому я в нём пошёл на такой шаг как реализацию классического ассемблерного синтаксиса. Все эти addis, move и так далее.
Но решимости сделать мат-нотацию не убавилось и я её реализовал тоже.
В ассемблере инструкциями 'mode classic' и 'mode new' можно переключаться между режимами синтаксиса.
Новый «математический» синтаксис большую часть операций выражает как 'R = Y op X' где 'op' это символ операции.
Если X пропущен, то он становится литералом '0', если же опущен 'op', то он становится символом '+'.
Следующие инструкции попадают под этот шаблон целиком и полностью (приведён пример для R=R0, Y=R1 и X=[ label ]) (новый синтаксис написано после двоеточия):
02 ADDS : r0 = r1 +s [ label ] ; сложение без обновления флагов
03 ADD : r0 = r1 + [ label ] ; сложение
04 ADC : r0 = r1 +c [ lavel ] ; сложение с переносом
05 SUB : r0 = r1 - [ label ] ; вычитание
06 SBC : r0 = r1 -c [ label ] ; вычитание с переносом
07 AND : r0 = r1 & [ label ] ; логическое И
08 OR : r0 = r1 | [ label ] ; логическое ИЛИ
09 XOR : r0 = r1 ^ [ label ] ; логическое исключающее ИЛИ
0A CADD : r0 = r1 +? [ label ] ; условное сложение
0C RRC : r0 = r1 >> [ label ] ; прокрутка вправо
Но три опкода (на сейчас) выпадают из этой схемы и имеют следующий синтаксис:
00 - ADDIS : r0 <- r1 - 1 ; сложение с immediate без обновления флагов
01 - ADDI : r0 <= r1 + 3 ; сложение с immediate
0B - RRCI : r0 <= r1 >> 15 ; прокрутка вправо на immediate число бит
Все команды с immediate получают символ '<' справа от равенства, плюс addis не обновляющее флаги использует знак '-' вместо '='.
Заметьте, что 'addis a b -1' (отрицательный immediate) может быть в новом синтаксисе написано как 'a < — b + -1' и как 'a < — b — 1' (как будто это вычитание).
Move в новом синтаксисе пишется просто как 'a < — b', что уже является сокращением от 'a < — b + 0'.
Заметьте так же, что псевдоинструкции введённые в ассемблер обычного синтаксиса для простоты сохраняют свою силу и в новом синтаксисе — jnz/call/ret и т.д.
Таким образом пример выше может быть переписан на новый манер так:
mode new
PORT_CONSOLE = $FFFF
sp <- $70 ; Setup stack
r0 <- str0 ; shortcut for addis r0 str0 0
call str_print
r0 <- str0
call str_print
dw 0 ; STOP
str_print r1 <= [ r0 ] ; testing move (addi r1 [ r0 ] 0)
jz .exit
[ PORT_CONSOLE ] <- r1 ; output char to console
r0 <- r0 + 1 ; increment r0
pc <- str_print ; jump to the beginning of procedure
.exit ret ; shortcut for move pc [ sp ]
str0 dw "Hello, world!" 10 0
10 комментариев
1) куча пробелов выглядит хуже пары запятых и занимает больше места в исходнике
2) интересный вопрос, как устанавливаются флаги, когда приёмник — psw (как регистр) :)
3) не хватает прокрутки ЧЕРЕЗ перенос, а также сдвигов — для ускорения арифметики и возможной битовой графики
4) хорошо бы еще один регистр с автоинкрементами — для шитого кода (мб включать автоинкременты для регистров спецфлагами?)
5) зачем тратить опкод на отдельную команду cmp? когда можно принять запись в [psw] за игнор результата, и она появляется «бесплатно», так же как test и прочие логические аналоги (плюс тогда мб даже имеет смысл сделать сохраняющие результат обычные and/or/xor не устанавливающими флаги)
И думал что в режиме теста так проковыляю какое то время, а потом заменю на типовую схему. Однако уже и есть функция extraсtLexem, но… мне так понравилось! :)
Во первых я всегда следую (и в данном случае — всем рекомендую) орфографической традиции — после знаков препинания типа точек или запятых ставить пробелы. Это естественно как дышать и видно даже на этом самом тексте. Поэтому запятые только добавляют один символ к разделителю-пробелу в массе случаев. Их настоящая нужда в мейнстриме — это помощь с отделением друг от друга аргументов со сложными выражениями, что встречается относительно нечасто и может быть разрулено явными скобками.
Ну и просто попробовав я как то втянулся. Функцию extractLexem конечно можно переписать и получить более привычное поведение хотя бы со скобками, но мне после такой практики на это совершенно не хочется тратить время. :)
Действительно отличный вопрос! Сейчас в эмуляторе АЛУ сперва обновляет биты арифметико-логических флагов как бы «напрямую» как если имеет в регистр флагов прямой вход через заднюю дверь (что весьма правдоподобно звучит для схемотехники, не зря он находится «с краю» регистрового файла), а уже после этого основное ядро извлекает результат из АЛУ и пишет его в регистровый файл. Т.е. запись в PSW ведёт себя довольно практично — изменение арифметических флагов будет забыто и операция вида «and psw psw маска» изменит только желаемый бит.
Однако возможно что ради скорости в схемотехнике эти операции могут происходить параллельно и результат сохранения в арфметико-логические флаги будет неопределён. Это может иметь некоторые последствия при обработке прерываний, но обдумав я нахожу что реальных проблем не будет. Поэтому на данный момент наверное лучше исходить из такого предположения.
> 3) не хватает прокрутки ЧЕРЕЗ перенос, а также сдвигов — для ускорения арифметики и возможной битовой графики
Я поэтому замер пока на операциях прокрутки и думаю какие лучше инструкции разместить в оставшейся части.
Во первых — по заветам MSP-430 операции ADD и ADC можно использовать как сдвиг и прокрутку влево:
add r0 r0 r0; сложение само с собой это умножение на 2 и сдвиг на 1 бит влево со вновь входящим нулевым битом
adc r0 r0 r0; сложение с переносом это сдвиг на 1 бит влево со входящим Carry и тут же исходящим Carry, т.е. идёт прокрутка влево через Carry, причём _через_ перенос.
Поэтому тот же MSP-430 не вводил аналогичных команд прокрутки и я не собираюсь.
Но вот прокрутка вправо уже должна быть в явном виде и чтобы побороть несколько проблем я пока решил сделать число прокручиваемых бит настраиваемым, а саму прокрутку — циклической с занесением в Carry, но не ЧЕРЕЗ Carry.
Почему так — потому что можно имитировать еще полезную команду SWAP BYTES просто прокрутив на 8 бит. Байты в слове поменялись местами.
Может быть полезно для упаковки и совместимости с типовыми файлами.
С другой стороны прокрутка вправо на 15 бит это же и прокрутка влево на 1 бит. Появляется взаимозаменяемость.
Далее — если кровь из носа нужен сдвиг влево/вправо на программируемое число бит мы можем подстроить счётчик чтобы нужные биты прокрутились куда требуется, а потом обнулением по «AND маска» занулить ненужные биты.
Так что как минимум беззнаковое деление на степени двойки делается. А если предварительно сделать условный переход по биту знака и в этой ветке сделать «OR маска», то и знаковые числа тоже можно будет делить и сдвигать таким образом.
Вот на этих размышлениях я пока подвис и пока еще думаю. Нужна практика наверное реального программирования на Simpleton 4 чтобы почувствовать что нужно остро и кровь из носу, а что достаточно сэмулировать серией базовых команд.
Тот же Gigatron вообще сдвиги делал через лукап-таблицы (!).
Регистров в духе 8-биток тут немного, поэтому специализированного регистра точно не будет. А вот сделать в регистре флагов на первые 4 регистра по два флажка — «пост-инкремент» и «пре-декремент» — это интересная мысль, за неё спасибо. Её надо обдумать, хотя есть минусы. Например при входе в прерывания эти флаги надо обнулять и потом восстанавливать. Но! То как я планирую сделать вход в прерывания с этим похоже что прекрасно справляется. Да да да… Интересно…
А вот это супер-идея! Спасибо! Только и косвенное чтение и косвенная запись в PSW в Simpleton 4 уже заняты — это режим адресации immediate address с помощью которого можно вообще не загрязнять регистры:
Однако! Еще с Simpleton 3 была запрещена косвенная запись в PC! Здесь этот запрет сохранился, но я его не упомянул.
Пытаться писать прямо в поток выполняемых команд — это как минимум странно. :) Поэтому именно за [ PC ] в поле R и логично закрепить новый смысл — «игнорирование записи результата из АЛУ в файловый регистр или память». Гениально! :D
Действительно высвобождается специализированная инструкция CMP с одной стороны, а с другой стороны появляются булевы тесты и, например, такая экзотичка, как с помощью команды из одного слова — RRCI поместить в Carry любой на выбор бит из операнда. xD
Блин, это прикольная идея! Прям спасибо!
Реализовал псевдорегистр 'void' и поведение с косвенной записью в PC. Теперь освободился еще один опкод — CMP.
Теперь сравнение делается так:
побитовые тесты (любые в OR/XOR/AND):
… или сравнение числа с константой в диапазоне -8..+7 за инструкцию в одно слово (inplace immediate):
… или проверка i-ого бита операнда через занос его в CF операцией RRCI:
Ляпота! Еще раз спасибо за идею! :)
по сдвигам надо бы всё тщательно взвесить, одинарный сдвиг для быстрой арифметики маловато
насчёт асма, на мой взгляд, задятые достаточно заметно отделяют операнды (особенно на шрифтах фиксированной ширины), ну, и если хочется, никто же после них пробелы не запрещает! и второе, нужны ли вообще квадратные скобки? префикс в большинстве случаев лаконичней:
скобки только для сложных выражений:
хотя при наличии запятых необязательны и они, префикc действует на всё выражение:
также, void как по мне длинновато, может, применять какой-то другой значок или даже «пустое выражение»?
ну, или просто псевдами выражать (те же cmp, test...)
Как я понял это дело привычки, поэтому тратить на это время уже не хочу.
Чем буду в свободное от других хобби время далее заниматься — это миграцией под SDL чтобы сделать прообраз виртуального ПК с клавиатурой и дисплеем и на нём реально пробовать что там со сдвигами применительно к скроллингам всяким и парсер сложных compile-time выражений.
Хотя еще наверное преобразую парсер самого ассемблера к варианту с паттерн-матчингом — при нём проще будет новые синтаксисы внедрять, кстати.
Есть желание перебросить процессор в Verilog, откатать на визуальном симуляторе цепей Digital и потом в FPGA?! Для Digital можно даже IDE с боку прицепить что бы красиво отлаживать с точками останова и пошагово.
Но лично сам — если только много лет спустя на пенсии, ибо сейчас изучение нового мира схемотехники для меня неподъёмный груз в рамках хобби.
Но если кому то опытному будет любопытно реализовать — я только за. В принципе архитектура open source-ная так что хоть форки делать со своими расширениями команд. :)
Если Вы уж взялись за проектирование архитектур процессоров, то считай катитесь по стенкам воронки. Да и грех себе отказывать в таком удовольствие до самой пенсии. Может оформите как продолжение статьи? Я бы помог разобраться :)
В любом случае я сперва напишу эмулятор, оформлю архитектуру видеочипа и т.п., а главное — софт разный под это дело напишу — тогда только буду задумываться всерьёз насчёт физических реализаций. У меня в самом деле с детства очень напряжённые отношения с паяльником и сидеть травить платы и подпаивать сотни проводков — не вызывает энтузиазма вообще. Я не железячник, я больше по софту.
Чтобы запустить тестовый пример надо выделить в списке файлов test0.asm и выбрать меню Emulator->Compile and run.
Обзорно об таймингах SimpX можно почитать тут: gamedev.ru/flame/forum/?id=249067&page=22&m=5403677#m318
О раскладке памяти (MMU) тут: gamedev.ru/flame/forum/?id=249067&page=20&m=5395740#m287
Об его единственном пока видеорежиме который элегантно сочетает воедино и тайловую и линейную битмапную графику здесь: gamedev.ru/flame/forum/?id=249067&page=19&m=5394557#m281
Видеорежим и тайминги реализованы, нет ввода с клавиатуры так что пока только потыкать палкой, но тыкать уже вовсю можно — файл simple_lib.inc какбы реализует минимальную среду для вывода текста в консоль.