8/16-битный компьютер мечты (процессор Simpleton)

Американский видеоблоггер 8-bit-guy давненько уже поднял и периодически ворошит такую тему как «какой мог бы быть 8-битный компьютер моей мечты» — со своим видением вопроса. Эти его видео напомнили мне что сама эта идея «а как бы я хотел чтобы было устроено нутро компьютера» сопровождает меня и, я верю, что и вас с самого детства. И так как мы тут рассуждаем в рамках ретро, то вопрос стоит именно как в заголовке.
Тему эту в вялотекущем режиме я уже обсуждал тут: gamedev.ru/flame/forum/?id=242499 (читать совсем необязательно) и раз уж здесь у нас возникло какое то летнее затишье, то решил немного перенести оттуда сюда уже в виде некоторых выжимок для «просто поболтать».


Сердцем «компьютера мечты» является процессор и я тут решил не размениваться по мелочам и прежде всего «изобрести» именно некую систему команд «8-битного процессора мечты» для 8-битного компьютера мечты, чтобы уж совсем рубить под корень. Да ладно — все мы наверняка в своё время и не раз задумывались над «идеальным процессором» так чего тянуть кота за хвост? Мечтать так мечтать!
Сразу обозначу — что в схемотехнике познания мои весьма скромны — на уровне институтского курса про триггеры, счётчики и сумматоры, так что всё это, повторюсь, лишь фантазии для помечтать, пообсудить и, возможно, придумать что-то своё.

Из последнего на меня произвел определенное впечатление Gigatron TTL в котором преследовалась цель простоты и минималистичности в ущерб всему. Но впечатление произвела совсем не эта цель (напротив, она мне тут неинтересна — не о том мечты), а довольно забавная концепция, когда команда LOAD является всего лишь частным случаем обобщенной арифметико-логической команды в которой при пересылке источника в приёмник попутно всегда проводится операция пропускаемого данного с аккумулятором, просто в случае с LOAD эта субоперация — NOP. Тем не менее в Gigatron есть разные форматы команд — STORE и переходы уже не подчиняются той же логике, а вот я подумал — а что если такой совмещенный с опциональными вычислениями «MOV» будет единственной командой в процессоре вообще?
Итак родилась и проэволюционировала из Simpleton 1.0 в Simpleton 3.0 следующая архитектура:
1. Все инструкции имеют один и тот же бинарный формат и декодируются совершенно одинаково
2. Команда берет аргумент1 и (опционально, если инструкция двухоперандная) аргумент2, проводит над ними операцию (возможно NOP) и записывает результат в аргумент2.
3. Все команды условные (включая условие always/всегда)
Т.е. суть одной команды Simpleton 3 можно выразить в языке Си такой строчкой:
if (cond) Y ?= X;

где знак? — это операция наподобие Y += X или Y <<= X в том числе и просто Y = X, а X и Y — это регистры или ячейки памяти.
например:

R0 = data1 ; непосредственное данное
[mem2] += R4 ; запись в память
R1 =+1 [R2] ; косвенная адресация, а =+1 - это операция инкремента аргумента

Когда в инструкцию нужно запаковать и два аргумента и код операции и код условия, то одного байта становится решительно мало. Бит в инструкции вообще решительно не хватало, поэтому чтобы уместить всё самое необходимое я наступил себе на горло и превозмог все 8-битные инстинкты и сделал еще одно ключевое решение:
Архитектура Simpleton 3 полностью 16-битная и не знает понятие 8-битного байта вообще.
Т.е. и регистры и ячейки памяти и адресное пространство Simpleton 3 — всё 16-битное.
И чем больше я об этом думаю тем больше восхищаюсь тем сколько удачна идея отказаться от 8 бит. 8-битные системы всегда стояли нараскоряку между 8-битными данными и 16-битным адресным пространством. Адресное пространство уже требовало 16-битных операций для вычислений адресов, да и сами 16-битные числа в вычислениях были зачастую востребованней — в результате всё это мялось с ноги на ногу какими то полухаками и расширениями, Z80 так вообще имел 16-битные сложения/вычитания с переносом — просто кишмиш какой то. Более того когда созрели 16-битные микропроцессоры уже даже 8-битки успели вылезть за рамки 64Кб и начались метания с банкингом, свитчингом и сегментацией памяти — всё это уродливо, напряжно и прочно ассоциируется с 16 битами.
Так вот нахрен. Нахрен 8-битный байт и всё тут. 16 бит адреса — значит и 16 бит данных. Без исключений. 16-битный процессор пусть будет 16 битным во всём. И но пасаран! Мы просто зашорили свой мозг 8-битным байтом как чем то безусловно обязательным и лежащим в фундаменте всей информации. Но это вообще не так.

Заранее еще напишу какие цели Simpleton 3 НЕ преследует:
— он не является сверхпростым как Gigatron — сложность на уровне Z80 или чуть выше
— он не пытается выполнить команду за такт в четырёхстадийном конвеере
— он не является сверхпроизводительным RISC-ядром с высоким потенциалом к реализации спекулятивного предсказания переходов, внеочередного исполнения команд и трёх уровней кеша для использования в качестве современного и актуального микроконтроллера для обработки изображения в нейронных сетях четвёртого поколения

Итак,

Архитектура Simpleton 3


Девиз: «не такой уж я и тупица!» (кстати, «simpleton» с английского — типа «недалёкий»)

Все шины и данные — 16 битные. Т.е. адресное пространство: 65536 16-битных ячеек. Байтов в системе в принципе нет.
В процессоре 7 регистров общего назначения R0-R6.
При этом R6 является счётчиком инструкций и имеет еще псевдоним PC.
R5 имеет особое поведение при косвенной адресации (автоинкремент при чтении и автодекремент при записи) и имеет псевдоним SP (стек).
R4 содержит флаги процессора и имеет псевдоним FLAGS.
При этом в нём же содержится такая штука как бит запрещения/разрешения прерываний поэтому команд типа EI/DI как в Z80 тоже нет, нужно просто произвести запись во FLAGS.
В FLAGS есть 4 бита арифметико-логических флагов:
— флаг нуля (zero)
— флаг переноса (carry)
— флаг знака (sign)
— флаг переполнения (overflow) (carry для знаковых)
Вполне себе стандартный набор.
Когда инструкция считывается из памяти сперва считывается её код по текущему PC и PC тут же инкрементируется. Т.е. на момент фазы парсинга инструкции PC уже указывает на следующую команду.
Если у инструкции есть непосредственные данные (от 0 до 2), то их считывание происходит в процессе выполнения команды с тем же эффектом — считывается данное по адресу PC и последний тут же инкрементируется.

У всех инструкций один и тот же формат:



Рассмотрим битовые поля в таком порядке в каком их обрабатывает логика микропроцессора после считывания инструкции:
SRC — код регистра-источника (R0-R6) или численное значение 7 для обозначения непосредственного операнда следующего за инструкцией. Первым делом процессор копирует это значение в во внутренний регистр X.
IS — (Indirect Source) признак косвенной адресации источника. Если равен 1, то значение в X замещается значением ячейки памяти по адресу X. При этом, если SRC является SP, то SP инкрементируется (аналог POP).
Флаг TI (two operand instruction) — признак двухоперандной инструкции. Если TI = 1, то данное DST, т.е. то куда будет в конце операции записан результат из АЛУ нужно перед выполнением операции еще поместить во второй промежуточный регистр Y. X и Y пойдут в АЛУ как раз как входы.
ID — (Indirect Destination) признак косвенной адресации приёмника. Если он включен и инструкция предполагает использование двух операндов (взведённый флаг TI), то значение Y вычисленное на предыдущем шаге помещается в регистр STORE_ADDR и замещается значением ячейки памяти с этим адресом. В конце инструкции именно в адрес STORE_ADDR запишется результат.
COND — (condition) — условие. Восемь возможных условий таковы:
Always — всегда
Z/NZ — ноль/не ноль
C/NZ — перенос/не перенос
G/GE — больше/больше или равно (знаковые сравнения)
восьмое пока не придумал куда кинуть
В общем на этом этапе, если условие не выполнилось, то остаток инструкции можно пропустить и перейти к следующей. Вообще по здравому размышлению условие надо проверять первым действием на входе в инструкцию и пропускать сразу всё, но не забывать инкрементировать PC на количество непосредственных данных, но по моему это усложнит логику и поэтому лучше поместить проверку условия технически в это место, где immediate-данные уже считаны и PC на них выправлен.
INSTR — код инструкции. Т.е. получается возможно 16 одноперандных инструкций и 16 двухоперандных.
Однооперандные: mov/neg/inc/dec/incf/decf/…
(incf/decf меняют флаги в то время как inc/dec — нет)
Двухоперандные: add/adc/sub/sbc/and/or/xor/cp/…
Пока лень обдумывать конкретику. Но, например, уже ясно, что инструкция сравнения должна принимая SRC и DST на входе и выставив флаги возвращать из АЛУ именно DST чтобы его не испортить по сравнению, как полагается.
На последнем шаге процессор сохраняет результат на выходе из АЛУ в DST и если это была индирекция через через регистр SP, то декрементирует его.

В общем как я и писал выше общая схема инструкций в этом процессоре это:
Y ?= X
где X может быть одним из 7 регистров R0-R6, непосредственным данным imm16 или ячейкой памяти по адресу любого из ранее перечисленного, т.е. [R0-R6] или [addr16]
тем же самым может быть Y, кроме варианта imm16 который не имеет смысла в операции записи
а знак? — это одна из арифметико-логических операций

Существует одна запрещенная комбинация инструкции когда DST является imm16, а бит индирекции ID не зажжён. Она зарезервирована под гипотетические будущие расширения команд.
Так же запрещено в рабочем коде использовать mov из регистра в него самого же, если это не R0 (т.е. явный NOP) тоже для возможности будущих расширений и префиксов.

Небольшой FAQ

Какие у Simpleton 3.x есть необычности или подводные камни:

1. Если простой переход (jump) это:

PC = addr16

а относительный переход как:

PC += offset16

то с вызовом процедуры (call) эта «однокомандная» система в одну команду не пролазит.
тут нужно делать примерно таким образом:

[SP] = адрес возврата
PC = адрес процедуры

и тут вот какая неприятность — адресы тут являются immediate-данными и таким образом эти две инструкции будут занимать целых четыре слова. не очень хорошо.
т.к. чаще всего мы хотим вернуться на код следующий за этими инструкциями, то в [SP] надо занести значение PC+2 (на момент выполнения первой инструкции PC указывает на следующую, а надо еще пропустить immediate-данное). И вот чтобы это стало возможным нужна двухоперандная инструкция не только inc, но и inc2. Тогда да — в две инструкции:

[SP] =+2 PC
PC = адрес процедуры

мы делаем вызов процедуры
А вот совершить возврат из неё очень просто:

PC = [SP]

тут уже всё делается в одно действие.
конечно в ассемблере call можно будет сделать макросом.

2. Как разрешить/запретить прерывания не затрагивая арифметико-логические флаги в FLAGS?
Да очень просто:

FLAGS &= константа ; для сброса 
FLAGS |= константа ; для взведения

Т.е. не просто перезаписью, а через логические операции с операндом-константой.

3. Что именно делают прерывания?
Как и в классических процессорах помещают в стек сперва PC, потом FLAGS и совершается переход по заранее определенному адресу.
Таким образом возврат из прерывания должен совершаться так:

FLAGS = [SP] ; заодно разрешаеются прерывания, т.к. восстанавливается старый FLAGS
PC = [SP]

43 комментария

avatar
Идея процессорных архитектур с единственной операцией существует довольно давно. en.wikipedia.org/wiki/One_instruction_set_computer

Касательно восьми бит, это свойство конкретно ранних микропроцессоров, возникшее из ограничений технологии того времени. До них были архитектуры с самым разным количеством бит в слове, и вовсе не обязательно это была степень двойки или чётное число. Примеры: en.wikipedia.org/wiki/Word_(computer_architecture)#Table_of_word_sizes
avatar
Ну, строго говоря тут не совсем «процессор с одной инструкцией». Если смотреть под немного другим углом — то в нём столько инструкций сколько есть кодов у поля INSTR + бит TI, просто все инструкции соблюдают строго один и тот же принцип — они берут X и возможно Y, проводят над ними операцию и записывают в Y. Ввиду того, что всё состояние процессора находится полностью в его РОН — такая схема достаточна для реализации практически всего что машине как правило надо — а то что прямо в это не укладывается (как CALL) вполне можно реализовать двумя операциями.
Т.е. любая команда это взятие операнда X или операндов X и Y, заталкивание их в ALU и записывание результата обратно во операнд Y. С аскетичностью и эзотеричностью даже One Instruction Set Computer это имеет мало чего общего. Здесь скорее преследуется цель сделать всё как можно более прямолинейным и простым для программирования — как можно более полная ортогональность (за исключением регистра SP) и никаких особых случаев и команд которые надо специально помнить.
Насчёт старых компьютеров у меня есть статья так что я в курсе. :)
avatar
Есть, кстати, у такого процессора еще одна забавная особенность — на нём можно оперировать ячейками памяти практически избегая «загрязнения регистров», т.е. делать команды вида:
[addr1] = [addr2]
или:
[addr1] += [addr2]
вообще минуя регистры общего назначения. за счёт того, что машинный цикл команды допускает сразу и многократные чтения из памяти и запись в память в конце команды — нет крайне часто встречаемого запрета в процессорах на то чтобы оба аргумента инструкции были в памяти.
казалось бы это должно сильно усложнить логику процессора, но как по мне — так нет. Тут как говорится «всё познаётся в сравнении».

Ведь возьмём такой популярный 8-битный процессор как i8080 на котором базирована командная система Z80.
В нём есть такая забавная инструкция как LHLD imm16, которая в синтаксисе Z80 выглядит как LD HL,(addr). Т.е. в регистровую пару HL считывается слово лежащее по адресу указанному в самой инструкции (imm16).
А что это с точки зрения машины состояний процессора? Во первых процессор 8-битный и все считывания 8-битные.
Поэтому сперва считывается код инструкции, потом считываются последовательно два байта imm16, потом считываются последовательно 2 байта лежащие по этому адресу и они уже рассовываются по регистрам H и L. Т.е., на минуточку, кроме считывания опкода в одной инструкции происходит 4 считывания из которых 2 меняют PC. Это уже по нагрузке на схемотехнические разбивки на шаги чтения/записи равно по сложности самому сложному возможному варианту возможных шагов чтения/записи единственной команды у Simpleton (только одно чтение меняется на запись), только вот у последнего команда может делать гораздо больше действий.
avatar
Зря лишил себя одного регистра из-за непосредственного операнда, всё равно его читаешь через PC, вот и кодируй (с индиректом) тоже через PC. И зачем адрес возврата обязательно пихать в память? В рисках, например, он пишется в (обычно специально выделенный) регистр, это дело вызываемой процедуры, сохранять в памяти его или нет. Можно сделать и через РОН, тут тебе пригодилась бы команда обмена, то есть вызов процедуры выглядит так load R,adr + swap PC,R и возврат load/swap PC,R (если обошёлся без стека).

Отказ полный от 8 бит может выглядеть восхитительно удачным только сегодня. Для 80-х все шины ширины 16 (и все вспомогательные микрухи!) в домашней машине дороговато, плюс доступную восьмибитную периферию трудно или невозможно использовать. И по мне, тогда уж лучше Форт-процессор на них сажать. Это уже был бы компик моей мечты))
avatar
Зря лишил себя одного регистра из-за непосредственного операнда, всё равно его читаешь через PC, вот и кодируй (с индиректом) тоже через PC.
Immediate сам может быть тоже indirect и таким образом операции можно проводить непосредственно над памятью не загрязняя регистры. С чтением через PC так уже не получится и поэтому такой лишний регистр, имхо, только усложнит в итоге программы.

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

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

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

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

Это уже был бы компик моей мечты))
Вот можно было бы и послушать, если есть конкретные образы и идеи. :)
avatar
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
это почему еще?
Когда load [PC] (на одном операнде X рассмотрим) отрабатывает он сперва в X грузит PC, а уже потом из-за бита indirect замещает X на [X]. Это совсем не то же самое что сперва загрузить в X [PC++], а потом заменить X на [X]. Система команд очень прямолинейна.

поэтому нужен swap
Swap регистров обычно нужен когда они существенно неортогональны и ради аккумулятора надо перестановки делать. Тут такой острой необходимости нет.
Регистров на самом деле хватает чтобы сделать memmove/memcopy полностью на регистрах, а это я считаю эталонным кодом для проверки на нехватку регистров.
avatar
P.S. блин, какая то комбинация клавиш сработала на enter и запостила посередине набора, сейчас еще будет продолжение ответа.
avatar
только частных случаев очень много и эффект заметный от ускорения
Такую оптимизацию можно проделать в существующей системе команд и без 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
>Commodore XXL (не помню точно модели)

Commodore 128
avatar
а ну да, собственно 128Кб и есть, точно! :)
avatar
Когда 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
второй случай можно закодировать через номер регистра флагов
(потому что, ну кому и зачем мб нужен косвенный доступ через флаги))
А вот это очень прикольная идея! Действительно косвенная индексация через 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-битных процессоров — в этом я даже нахожу какое то очарование, что система команд тяготеет к тому чтобы побольше всего вытаскивать в глобалки и минимизировать по настоящему комплексные адресации. Потому что по духу он и должен быть таким вот «восьмибитником».
avatar
ну, в одном хотя бы смог убедить :) тем не менее…

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

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

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

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

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

inc2 лично мне приятно, экономит код, данные и скорость в массе случаев
в какой массе? перечисли несколько хотя бы разумных случаев? вот как раз на 8-битках иногда могло еще быть полезно, потому что основные типы данных разных размеров, но здесь-то одинаковые они! притом польза, даже если где-то она и будет, не «в два раза», а всего-то навсего в (N+1)/N от всех циклов доступа в итерации
avatar
в какой массе? перечисли несколько хотя бы разумных случаев?
Главное откуда оно возникло — это минимизация и ускорение реализации CALL, как я писал, но помимо этого может быть применён для, допустим, быстрой адресации переменных близких к вершине стека (наряду с =+1) и может быть полезен при итерировании по массивам с таким размером ячейки и тому подобное.
avatar
Главное откуда оно возникло — это минимизация и ускорение реализации CALL,
Кстати, лучшим вариантом именно минимизации с ускорением может быть отдать под однокомандный call в принципе небессмысленный, но на практике вряд ли нужный случай «op [PC],src» — то есть dst=PC всегда прямая адресация, а ID=1 в этом случае будет означать «перед выполнением сохранить PC в стек». Подумай, так ли тебе нужно писать что-то в следующий опкод.

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

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

также не совсем понимаю смысл необходимости флага TI — почему бы всегда не производить копирование в X,Y?
avatar
также не совсем понимаю смысл необходимости флага 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] — мы получили на него указатель двумя командами и тремя словами.

ИТЕРИРОВАНИЕ подразумевает использование каждого элемента
Ну зависит от задачи. Например, если первый элемент нам подходит — переходим к его хвосту, а если нет — пропускаем до следующего элемента.
avatar
Если TI=1, то общая схема инструкции следующая:
1. SRC загружается в X
2. DST загружается в Y
это можно делать одновременно, и TI не нужен
если не понадобится Y, так не понадобится

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

Ну зависит от задачи. Например, если первый элемент нам подходит — переходим к его хвосту, а если нет — пропускаем до следующего элемента.
это только если по первому слову ясно (кстати, у тебя какой порядок слов-байтов? little/big endian?)
но если нужно элемент проверить целиком и условно пропустить следующий, то inc1+inc2 ничем не лучше add3
avatar
это можно делать одновременно, и TI не нужен
если не понадобится Y, так не понадобится
Одновременно считывать из памяти не получится. Заполнение Y реально может быть не просто ненужным, но и времязатратным процессом.
(кстати, у тебя какой порядок слов-байтов? little/big endian?)
А тут это исключительно как захочет программист. Ячейка памяти 16-битная, регистры — 16-битные, если нужно компоновать 32-битные, то это пользовательский код целиком.
то inc1+inc2 ничем не лучше add3
Я сильно над этим не задумывался еще, но т.к. есть 16 однооперандных инструкций (и они никак не обязаны быть теми же что и 16 двухоперандных), то возможно там будет и dec/inc-1/2/3/4 и возможно что-то еще.
avatar
P.S.
Вообще однооперандные инструкции это такие в которых X пропускаясь через АЛУ перед записью в Y никак не зависит от Y.
Т.е. (используется синтаксис операторов похожий на синтаксис Си-подобных языков):
R0 = R1; move
R0 =+1 R1; inc1
R0 =<< R2; сдвиг влево на 1 бит
R0 =+2 R3; inc2
R0 ~= R4; побитовая инверсия
и так далее. т.е. над SRC производится операция и записывается в DST

Двухоперандные инструкции берут и SRC и DST и пропустив их оба через АЛУ записывают результат в DST:

R0 += R1; сумма R0 и R1 записывается в R0
R0 <<= R2; R0 сдвигается влево на R2 бит (существенное отличие с однооперандным аналогом!)
R0 cmp R3; R0 и R3 сравниваются — неизменность R0 достигается за счёт того, что АЛУ именно его (Y) выдаёт на выходе
и так далее.
avatar
Заполнение Y реально может быть не просто ненужным, но и времязатратным процессом.
вроде как [DST] читать не нужно только для load, что определяется по опкоду (лучше нулевому) и без TI
и второй раз в операциях типа [DST=SRC],[SRC=DST] — ну так там уже прочитано в первый раз

Я сильно над этим не задумывался еще, но т.к. есть 16 однооперандных инструкций (и они никак не обязаны быть теми же что и 16 двухоперандных), то возможно там будет и dec/inc-1/2/3/4 и возможно что-то еще.
лично я между любым кол-вом дополнительных инкрементов и обменом всегда выберу обмен
уже только ради одного обмена со стековым указателем, даже если больше не понадобится никак 8)
avatar
вроде как [DST] читать не нужно только для load
Я только что выше подробнее пояснил.
avatar
а я пояснил, что тратить целый бит опкода на то не нужно
во всех случаях «существенного отличия» экономнее использовать другой instr
avatar
а я пояснил, что тратить целый бит опкода на то не нужно
во всех случаях «существенного отличия» экономнее использовать другой instr
Эм… Так и есть. Просто по одному биту кода инструкции можно тут же понять нужен Y на входе или нет.
А в чём проблема то?
avatar
в том, что ты на ровном месте потерял до половины нумерации для инструкций
у тебя сейчас возможно лишь 16 различных instr, а могло быть 32-(x<16)
avatar
Так их и есть 32, бит TI просто для простоты определяет какие из них однооперандные, а какие — двухоперандные. Они не обязаны быть одинаковыми, это просто удобство для декодера.
avatar
P.S.
Появился большой искус еще 1 бит поля INSTR откусить под «режим адресации immediate», когда поле SRC есть immediate-данное от 0 до 7, а TI — его знак. Тогда не нужны будут отдельные inc/dec 2/3/4/5/6/7, но надо подумать.
avatar
рукалицо.жпг

ну, а здесь-то почему не судьба применить отдельную ОПЕРАЦИЮ инкремента на число, закодированнное в опкоде??

вместо того, чтобы сокращать количество доступных операций еще в два раза))))))
avatar
ты уж определись — либо у тебя бит TI определяет что-то отдельно, а само ДЕЙСТВИЕ, производимое в АЛУ с операндами, кодируется битами INSTR и тогда у тебя возможно, повторяю, ЛИШЬ 16 УНИКАЛЬНЫХ ОПЕРАЦИЙ АЛУ; либо у тебя 32 разных действия кодируются 5 битами, но тогда нет фактически никакого отдельного TI, просто нет
avatar
Бит TI это прямой флаг для декордера инструкции нужно подгатавливать для АЛУ аргумент Y или нет. Всего инструкций 32, половина из них с зажжённым битом TI — половина нет. Но действительно бит TI уходит в АЛУ вместе с INSTR выступая как полный 5-битовый код инструкцуии, т.к. двухоперандные и однооперандные инструкции с одинаковым полем INSTR могут делать совершенно разные вещи.
avatar
а зачем вообще в опкод пихать такой флаг? опкод считай просто индекс для декодера, который вытащит из таблицы полную формулу команды/микропрограммы в общем виде со всеми флагами, где что муксить
avatar
да просто для простоты. нет тут никаких микропрограмм — вся логика прямолинейна и однообразна.
avatar
простоты ЧЕГО? «комп мечты» железячника или программиста? их мечты часто вступают в противоречие
avatar
вы что не понимаете, что один заранее готовый бит на блюдечке это гораздо гораздо проще, чем микропрограммы с декодером по таблицам и так далее? что за бред?
avatar
КОМУ проще? Аппаратчику, который будет разводить проц — да, проще; и у производителя, возможно, будет чуть меньше брака. Программисту — нет, ничуть не проще, ему нах не нужно думать про этот бит. Программист вовсе не мечтает забивать голову мусорной инфой про формат опкода и из-за удобства разводки и копеечной разницы в цене потерять в удобстве программирования. Так вот я и спрашиваю поэтому — «комп мечты» КОГО ты хочешь изобрести?
avatar
«Так вот я и спрашиваю поэтому — «комп мечты» КОГО ты хочешь изобрести?»

Всё это было написано — для всех и всях. Для программиста этот проц удобен тем что в нём довольно широкие команды, не надо ворочаться с аккумулятором, можно напрямую работать с ячейками памяти не загрязняя регистры, команды сразу совмещают и вычисление в АЛУ и пересылку данных откуда и куда угодно. Для аппаратчика — простая система команд когда есть одна команда в глобальном смысле — пересылка данных через АЛУ с лёгкими вариациями не влияющими на общую схему работы.
avatar
Да что-то не выходит пока для всех, а для железячника в основном. Программисту наплевать на «одну команду в глобальном смысле» и «не загрязняя регистры», ему важно, чтоб команды были быстрыми и удобными, и свободных регистров (или быстрой памяти) побольше (и насрать, сколько несвободных «грязных» при этом будет), и чтоб специализированных поменьше. Например, быстрая команда R=R+n с кодировкой n=1..16 в опкоде полезнее жалкой пары инкрементов, но в предложенную схему не вписывается (не в одностадийное декодирование, во всяком случае). Например, программисту были бы удобны два и более указателя стека, как в 6809, или хотя бы быстрое переключение между ними, а еще лучше автоинкремент любого адресующего регистра, что решается добавкой операций чтения/записи, но ты ради упрощения декодера сокращаешь количество возможных разных команд или ограничиваешь их тип.
avatar
Имхо такой проц гораздо удобнее и прямолинейнее в программировании чем тот же Z80 при том оставаясь всё-таки на уровне 8-биток своей скупостью команд и отсутствием сложных адресаций. Не забываем как звучит заголовок поста — это не попытка создать идеальный процессор вообще, но именно 8-битный по своим суммарным характеристикам и ощущениям. Взять 16-битный i8086 — это уже совсем другой зверь с огромным количеством удобных для ЯВУ адресаций и схем. Вот для меня это уже не то.
avatar
не такое уж большое достижение, намечтать «удобней z80»
но сравнительно с 6809 (а тем более 6309) уже не всё так однозначно
avatar
У меня есть краткий обзор на «ветку» процессоров Motorola/MOS. Мне, если честно, их архитектура аккумулятор-память в принципе не нравится. Это такое что-то из 50/60-х и вот не моё. От MOS 6502 вообще плакать хочется. 6809 несомненно намного круче и богаче на сложные адресации, откуда и опкоды уже двухбайтные начинают вылазить ну и простым не назовёшь. Но много лучше конечно 6502.
avatar
программисту на опкоды тоже начхать обычно, важен только полный (со всеми дополнительными данными) размер команд, если он у всех одинаков (как в исходном арме) или можно подобрать аналоги нужного размера — тогда это еще можно с толком использовать, если нет — ну не похрен ли, сколько там опкод занимает
avatar
в данном случае я и не говорил про программиста.
avatar
и кстати, на z80 очень огорчает отсутствие операций ex sp,rr
из-за этого двухстековые схемы и шитый код намного реже применимы, чем могло быть
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.