Если TI=1, то общая схема инструкции следующая:
1. SRC загружается в X
2. DST загружается в Y
это можно делать одновременно, и TI не нужен
если не понадобится Y, так не понадобится

Так и есть — это пожалуй единственная «бесполезная» комбинация аргументов которая осталась и действительно сделать её каким то особым случаем выглядит привлекательно.
Но лично мне не нравится, что ломается общая схема работы Simpleton-а.
почему ломается? считай просто, что схема общая у тебя такая:
1) может быть, записать PC в стек
2) DST ?= SRC
и нет проблем :D

Ну зависит от задачи. Например, если первый элемент нам подходит — переходим к его хвосту, а если нет — пропускаем до следующего элемента.
это только если по первому слову ясно (кстати, у тебя какой порядок слов-байтов? little/big endian?)
но если нужно элемент проверить целиком и условно пропустить следующий, то inc1+inc2 ничем не лучше add3
  • avatar aa-dav
  • 0
также не совсем понимаю смысл необходимости флага TI — почему бы всегда не производить копирование в X,Y?
Если TI=0, то общая схема инструкции следующая:
1. SRC загружается в X
2. X пропускается через АЛУ с кодом инструкции и TI=0 (т.е. полный код инструкции это TI+INSTR — оба поля)
3. результат сохраняется в DST
Если TI=1, то общая схема инструкции следующая:
1. SRC загружается в X
2. DST загружается в Y
3. X и Y пропускаются через АЛУ с кодом инструкции и TI=1 (т.е. полный код инструкции это TI+INSTR — оба поля)
3. результат сохраняется в DST
Т.е. TI — это признак двухоперандной инструкции — в таковой нужно больше действий в первой фазе по загрузке второго входного аргумента в Y и поэтому лучше по одному биту сразу понимать надо или не надо этот шаг делать.
Но можно так же воспринимать это так, что TI+INSTR формируют 5-битный код инструкции половина из которых однооперандная, а половина — двухоперандная.

Подумай, так ли тебе нужно писать что-то в следующий опкод.
Так и есть — это пожалуй единственная «бесполезная» комбинация аргументов которая осталась и действительно сделать её каким то особым случаем выглядит привлекательно.
Но лично мне не нравится, что ломается общая схема работы Simpleton-а.
Однако есть еще одно соображение — генерация исключений. Сколько ни думаю, но получается что при генерации исключений реально схемотехника должна быть вот настолько замороченной — нужно уметь и PC и FLAGS сохранить в стек да еще и переход совершить. Надо еще подумать, возможно из-за того что такая схемотехника просто нужна для прерываний, то и команда такая не будет обременением по итогу. Посмотрим, когда будет интерес и время подумать еще над этим помозгую. Немного смотрел в ARM-ы и вроде как там генерация прерываний не сохраняет никак флаги, типа это должно быть первой инструкцией обработчика прерываний… В общем возможны варианты.

(а еще лучше адресацию [R+imm], но она не лезет в такую схему)
Да, нифига не лезет. Да и норм. На деле дотянуться до произвольной переменной в стеке не так уж и трудно:
R5 = offset
R5 += SP
и вот тут уже мы можем совершать с параметром действия типа R0 += [R5] — мы получили на него указатель двумя командами и тремя словами.

ИТЕРИРОВАНИЕ подразумевает использование каждого элемента
Ну зависит от задачи. Например, если первый элемент нам подходит — переходим к его хвосту, а если нет — пропускаем до следующего элемента.
и кстати, на z80 очень огорчает отсутствие операций ex sp,rr
из-за этого двухстековые схемы и шитый код намного реже применимы, чем могло быть
Главное откуда оно возникло — это минимизация и ускорение реализации CALL,
Кстати, лучшим вариантом именно минимизации с ускорением может быть отдать под однокомандный call в принципе небессмысленный, но на практике вряд ли нужный случай «op [PC],src» — то есть dst=PC всегда прямая адресация, а ID=1 в этом случае будет означать «перед выполнением сохранить PC в стек». Подумай, так ли тебе нужно писать что-то в следующий опкод.

помимо этого может быть применён для, допустим, быстрой адресации переменных близких к вершине стека
переменные нормально надо располагать, и не понадобятся лишние инкременты :D
(а еще лучше адресацию [R+imm], но она не лезет в такую схему)

может быть полезен при итерировании по массивам с таким размером ячейки и тому подобное.
ИТЕРИРОВАНИЕ подразумевает использование каждого элемента, то есть последовательный доступ ко всем его частям, то есть одинарный инкремент :P

также не совсем понимаю смысл необходимости флага TI — почему бы всегда не производить копирование в X,Y?
  • avatar aa-dav
  • 0
в какой массе? перечисли несколько хотя бы разумных случаев?
Главное откуда оно возникло — это минимизация и ускорение реализации CALL, как я писал, но помимо этого может быть применён для, допустим, быстрой адресации переменных близких к вершине стека (наряду с =+1) и может быть полезен при итерировании по массивам с таким размером ячейки и тому подобное.
ну, в одном хотя бы смог убедить :) тем не менее…

Не стоит того чтобы из-за этого ломать систему команд.
почему «ломать»? обобщить!

Даже в ЯВУ swap как правило записывается проброской данного через tmp,
что крайне бесит, так же как отсутствие циклических сдвигов

ну пробросим через регистр, несущественно
это инкремент на 2 несущественно, а нерациональный расход регистра — существенно

Это довольно эзотерично чтобы мне захотелось таким пользоваться в жизни. ;D
это в том числе вызов по таблице, рядовой случай

Как и inc1 это экономия на imm в ряде случаев когда это часто нужно.

inc2 лично мне приятно, экономит код, данные и скорость в массе случаев
в какой массе? перечисли несколько хотя бы разумных случаев? вот как раз на 8-битках иногда могло еще быть полезно, потому что основные типы данных разных размеров, но здесь-то одинаковые они! притом польза, даже если где-то она и будет, не «в два раза», а всего-то навсего в (N+1)/N от всех циклов доступа в итерации
  • avatar aa-dav
  • 0
второй случай можно закодировать через номер регистра флагов
(потому что, ну кому и зачем мб нужен косвенный доступ через флаги))
А вот это очень прикольная идея! Действительно косвенная индексация через FLAGS не просто ненужная операция, но по сути своей — UB в чистом виде. Вообще работать с регистром флагов надо очень осторожно во избежание проблем с будущими совместимостями и главная задача вынесения его в РОН — это упрощение системы команд при соблюдении легкости сохранения через [SP] = FLAGS и восстановления через FLAGS = [SP], а так же манипуляцией бита Enable Interrups через FLAGS ~= mask и FLAGS |= mask. Но схемотехнически и архитектурно как чтение так и запись неиспользуемых его битов — это Undefined Behaviour для возможностей будущих расширений, поэтому ни о какой разумной индирекции в таких условиях речи быть не может.
Логично!
Тогда действительно ничего не мешает сказать, что R7 это и есть наш FLAGS (остальные регистры сохраняют нумерацию описанную в посте) и когда indirect=0, то ведет себя как этот регистр.
Но когда встречаются indirect=1 и R7 (т.е. все биты ответственные за DST и/или SRC единичны), то схема переключается в режим immediate+indirect в точности как описано в посте.
И действительно тогда чистый immediate лучше реализовать как чтение PC+indirect, тогда остаётся только заметить, что любое косвенное чтение через PC приводит всегда к его инкременту — это и логично и схемотехнически резонно, а не какой то «исключительный случай». Т.е. fetch через PC — он и в африке fetch через PC откуда бы действие не происходило.
И внезапно выходит, что действительно, у нас высвободился регистр R4 и неисключительных регистров теперь пять штук — R0-R4. При этом исчезло понятие «запрещенной комбинации DST», но и на этом фоне еще и высвободился регистр.
Очень удачная идея! Спасибо!
Самое интересное, что чисто схемотехнически такое усовершенствование насколько я понимаю вообще не несёт практически никакого пенальти по сравнению с первоначальным вариантом — добавочных линий и логических элементов похоже что вообще не нужно вводить в схему — они уже все и в первом проекте были, просто немного по другому затусованы.

например, обмен координат в однонаправленном алгоритме
сортировка пузырьком с условным обменом
Не стоит того чтобы из-за этого ломать систему команд. Даже в ЯВУ swap как правило записывается проброской данного через tmp, так и тут — ну пробросим через регистр, несущественно и нужно очень редко чтобы из-за этого огород целый городить.

load R3,adr1
(вычисления)
(условный переход)
load R3,adr2
(вычисления)
swap PC,R3
Это довольно эзотерично чтобы мне захотелось таким пользоваться в жизни. ;D

тем, что повторяет функцию универсального сложения с любой константой
Как и inc1 это экономия на imm в ряде случаев когда это часто нужно.

собс-но, даже инкремент на 1 — пережиток неортогональных восьмибитных процов
Так это и есть по духу 8-битный проц, но такой чтобы как можно более приятнее. inc2 лично мне приятно, экономит код, данные и скорость в массе случаев. Но, например, у этого проца очень слабая поддержка локальных переменных на стеке, а это вполне в духе 8-битных процессоров — в этом я даже нахожу какое то очарование, что система команд тяготеет к тому чтобы побольше всего вытаскивать в глобалки и минимизировать по настоящему комплексные адресации. Потому что по духу он и должен быть таким вот «восьмибитником».
Когда load [PC] (на одном операнде X рассмотрим) отрабатывает он сперва в X грузит PC, а уже потом из-за бита indirect замещает X на [X]. Это совсем не то же самое что сперва загрузить в X [PC++], а потом заменить X на [X].
что совсем не повод так бездарно тратить номер регистра
load imm = load [pc++]
load [imm] = load [[pc++]]
второй случай можно закодировать через номер регистра флагов
(потому что, ну кому и зачем мб нужен косвенный доступ через флаги))

Swap регистров обычно нужен когда они существенно неортогональны и ради аккумулятора надо перестановки делать. Тут такой острой необходимости нет.
например, обмен координат в однонаправленном алгоритме
сортировка пузырьком с условным обменом
(ты же хочешь операции с памятью?)

Регистров на самом деле хватает чтобы сделать memmove/memcopy полностью на регистрах, а это я считаю эталонным кодом для проверки на нехватку регистров.
а я считаю это разновидностью подхода «и так сойдёт» :P

Такую оптимизацию можно проделать в существующей системе команд и без swap:
; вызов процедуры
R3 =+2 PC; адрес возврата (inc2) (команда без immediate)
PC = proc_addr; вызов процедуры
; возврат из процедуры
PC = R3
нет! это совершенно не то же самое! например:
load R3,adr1
(вычисления)
(условный переход)
load R3,adr2
(вычисления)
swap PC,R3

Чем же он уродлив?
тем, что повторяет функцию универсального сложения с любой константой
собс-но, даже инкремент на 1 — пережиток неортогональных восьмибитных процов
(но он хотя бы нужен бывает часто, и его наличие мб оправданным)
у обмена же гораздо шире функционал

В эти годы уже просто для поддержки CP/M могли целый процессор Z80 засадить в какой нибудь Commodore XXL (не помню точно модели) — какая уж тут гонка за примитивизмом?
засадить могли, а в нормальную производительность z80 не шмогли
и это тормозное в cp/m режиме угрёбище справедливо было непопулярно
тем более, что и стоило вдвое дороже c64 того же года

Я уже говорил — команда типа SWAP BYTES внутри регистра (опять таки с логикой Y = swap bytes of X) делает такой сценарий маловероятным. Ну и вообще соединять комп с устройством которое плюётся данными быстрее чем или даже соизмеримо чем скорость с которой процессор отрабатывает memfill — это сомнительно и вряд ли такое вообще есть/было на практике.
на практике поддерживали 8 и 16 бит девайсы одновременно очень недешёвые песюки
причём в них не было полного отказа от 8 бит
  • avatar aa-dav
  • 0
а ну да, собственно 128Кб и есть, точно! :)
  • avatar nyuk
  • 1
>Commodore XXL (не помню точно модели)

Commodore 128
  • avatar aa-dav
  • 0
только частных случаев очень много и эффект заметный от ускорения
Такую оптимизацию можно проделать в существующей системе команд и без swap:
; вызов процедуры
R3 =+2 PC; адрес возврата (inc2) (команда без immediate)
PC = proc_addr; вызов процедуры
; возврат из процедуры
PC = R3
Как видно никакого SWAP и стека в таких хвостовых функциях можно реально не использовать.
Теоретически можно было бы использовать какую нибудь из запрещенных комбинаций регистров в операндах или псевдо-nop-ы как расширители команд, но повторюсь — мне здесь нравится именно простота.

а для «компа мечты» должно быть эффективней и удобнее, а не «проще»
В том то и дело, что я нахожу такую систему команд довольно эффективной и удобной. Тут само мышление прямолинейно как стрела — всё есть пересылка данных с опциональной операцией с высокой степенью ортогональности. Не нужно задумываться над перекладыванием данных по регистрам как в Z80 или тем какие режимы адресации есть а каких нет и в каких операциях как в 6502.

ну, уродливый inc2 протащил же :)
Чем же он уродлив? inc2 (реализовать можно как ADD с загрузкой константы 2 в Y на первых фазах выполнения команды) часто нужен, потому и сделан — экономит код программы хорошо. Он полностью укладывается в парадигму X ?= Y и уродливым в ней быть не может — inc1 разве чем то уродлив? Тоже часто нужен, потому и полезен — абсолютно та же фигня и даже в тот же профиль.

с чего такой вывод?
В эти годы уже просто для поддержки CP/M могли целый процессор Z80 засадить в какой нибудь Commodore XXL (не помню точно модели) — какая уж тут гонка за примитивизмом?

например, девайс подкидывает байты высоким темпом, нужно успевать выгребать

Я уже говорил — команда типа SWAP BYTES внутри регистра (опять таки с логикой Y = swap bytes of X) делает такой сценарий маловероятным. Ну и вообще соединять комп с устройством которое плюётся данными быстрее чем или даже соизмеримо чем скорость с которой процессор отрабатывает memfill — это сомнительно и вряд ли такое вообще есть/было на практике.
  • avatar aa-dav
  • 0
P.S. блин, какая то комбинация клавиш сработала на enter и запостила посередине набора, сейчас еще будет продолжение ответа.
  • avatar aa-dav
  • 0
это почему еще?
Когда load [PC] (на одном операнде X рассмотрим) отрабатывает он сперва в X грузит PC, а уже потом из-за бита indirect замещает X на [X]. Это совсем не то же самое что сперва загрузить в X [PC++], а потом заменить X на [X]. Система команд очень прямолинейна.

поэтому нужен swap
Swap регистров обычно нужен когда они существенно неортогональны и ради аккумулятора надо перестановки делать. Тут такой острой необходимости нет.
Регистров на самом деле хватает чтобы сделать memmove/memcopy полностью на регистрах, а это я считаю эталонным кодом для проверки на нехватку регистров.
Immediate сам может быть тоже indirect и таким образом операции можно проводить непосредственно над памятью не загрязняя регистры. С чтением через PC так уже не получится
это почему еще? сам же пишешь:
значение в X замещается значением ячейки памяти по адресу X
то есть load [PC],[PC] это X=[PC++]; Y=[PC++]; X=[X]; [Y]=X
что эквивалентно load [imm1],[imm2]
Т.к. регистров действительно немного,
вооот, в том числе еще поэтому нужен swap, а у тебя и регистров меньше, и свопа нет
а в самом обобщённом случае Link Register таки нужно пихать в стек,
только частных случаев очень много и эффект заметный от ускорения
плюс содержимое линк-регистра может служить указателем на данные процедуры
(после обработки данных с инкрементом получается корректный адрес возврата)
то делать так всегда имхо проще для системы команд
а для «компа мечты» должно быть эффективней и удобнее, а не «проще»
В ней есть только одна операция X ?= Y с лёгкими вариациями. Swap уже не пролазит и не знаю как его протащить без вреда для внутренней красоты.
ну, уродливый inc2 протащил же :)
Только до тех лет когда начали делать 128Кб-ные машины.
с чего такой вывод? это просто посадить на ту же шину больше микросхем
а не разрабатывать весь микропроцессорный комплект, а не только проц
Да ну, не вижу ни трудностей ни тем более невозможностей.
например, девайс подкидывает байты высоким темпом, нужно успевать выгребать и складывать их, а складывать ты можешь только словами; будешь выделять блок вдвое больше, а потом корячиться с утаптыванием? + удорожание в любом случае
Вот можно было бы и послушать, если есть конкретные образы и идеи. :)
пока некогда :)
  • avatar aa-dav
  • 0
Зря лишил себя одного регистра из-за непосредственного операнда, всё равно его читаешь через PC, вот и кодируй (с индиректом) тоже через PC.
Immediate сам может быть тоже indirect и таким образом операции можно проводить непосредственно над памятью не загрязняя регистры. С чтением через PC так уже не получится и поэтому такой лишний регистр, имхо, только усложнит в итоге программы.

И зачем адрес возврата обязательно пихать в память?
Т.к. регистров действительно немного, а в самом обобщённом случае Link Register таки нужно пихать в стек, то делать так всегда имхо проще для системы команд. В ней есть только одна операция X ?= Y с лёгкими вариациями. Swap уже не пролазит и не знаю как его протащить без вреда для внутренней красоты. :D

Для 80-х все шины ширины 16 (и все вспомогательные микрухи!) в домашней машине дороговато
Только до тех лет когда начали делать 128Кб-ные машины.
Далее получилось что истинным 16-биткам уже не хватало и этого и в них перекочевали банкинги и свитчинги в лучшем случае реализованные как сегментные регистры. Байты, правда, конечно, уже к тому времени стали вещью безальтернативной.

плюс доступную восьмибитную периферию трудно или невозможно использовать

Да ну, не вижу ни трудностей ни тем более невозможностей.
Несомненно раз уж есть мощная 8-битная предыстория, то разумно предусмотреть команды типа обмена/сдвига/зануления в слове байт, но уж куда подвести проводки от 8-битной периферии к 16-битной шине данных — по моему дело просто вкуса, но никак не трудностей.

Это уже был бы компик моей мечты))
Вот можно было бы и послушать, если есть конкретные образы и идеи. :)
  • avatar aa-dav
  • 2
На форуме откуда я пришёл — gamedev.ru есть энтузиасты написавшие продолжение игры Gradius: gamedev.ru/projects/forum/?id=244656
Это само по себе было бы не особо интересно даже, если бы не список платформ на которые это продолжение было портировано.
Кроме очевидных Windows и Linux есть порт на DOS (!) (причём использующий DOS4/GW о чём отдельная песня у них есть: gamedev.ru/flame/forum/?id=245611)
Но кроме этого есть два порта на самодельные (!) портативные игровые консоли Black Prism и EMU Pocket! :D

Не удивлюсь если это широко известные люди в узких кругах. :D
Зря лишил себя одного регистра из-за непосредственного операнда, всё равно его читаешь через PC, вот и кодируй (с индиректом) тоже через PC. И зачем адрес возврата обязательно пихать в память? В рисках, например, он пишется в (обычно специально выделенный) регистр, это дело вызываемой процедуры, сохранять в памяти его или нет. Можно сделать и через РОН, тут тебе пригодилась бы команда обмена, то есть вызов процедуры выглядит так load R,adr + swap PC,R и возврат load/swap PC,R (если обошёлся без стека).

Отказ полный от 8 бит может выглядеть восхитительно удачным только сегодня. Для 80-х все шины ширины 16 (и все вспомогательные микрухи!) в домашней машине дороговато, плюс доступную восьмибитную периферию трудно или невозможно использовать. И по мне, тогда уж лучше Форт-процессор на них сажать. Это уже был бы компик моей мечты))
Согласен про клоны, достойны отдельного разбора и списка. На PC Engine я как-то скачивал близкий к полному архив игр и просто по вечерам сидел и по алфавиту пробовал одну за другой. Их немного, штук 500-600 всего уникальных, так вот там действительно безумное количество шмапов, особенно вертикальных.
Я думал сделать еще пост, где подразобрать как раз клоны, о которых упомянул Shiru, да накидать материалов и упоминаний. Постараюсь собраться еще с силами и сделать, там есть чего рассказать любопытного.
Очень интересно, я не знал что у R-Type было столько сиквелов (хотя сам я никогда по-настоящему не проникся этой игрой — мне было слишком сложно). Отличная статья, спасибо!

Кроме того, думаю что ссылку на книжку Bob Pape нужно добавить в основной пост; да, конечно она на английском, но как книжка о реальной жизни разработчика для спектрума в 1990-е лучше её я ничего ещё не читал.