Диалектика разрядности
На размышления по теме меня сподвигла статья aa-dav под заголовком 8/16-битный компьютер мечты (процессор Simpleton). Статья, без дураков, замечательная. Понравилась своим рассудительным изложением материала. Мой особенный интерес вызвали мысли автора, оказавшись близкими по духу. Множество раз размышляя на подобную тему “с карандашом в руках” очень похоже изводил себя вопросами озвученными автором. Процесс созидания чего бы то ни было итеративный, наблюдения и заключения меняются по времени, в силу приобретения дополнительного опыта и знаний. Иногда достаточно попробовать сложившуюся на бумаге архитектуру “на зуб” написав в ней десяток другой строк кода и, казалось бы, с железобетонных утверждений приходится слезать. Неизменным всегда остается то, что любой вопрос, касающийся архитектуры, содержит в себе массу противоречий. По большому счету выбор решения в итоге заключается в том какие же из них являются меньшим злом. Так и в случае разрядности появляется масса вопросов и проблем. Для меня в итоге, при решении вопросов разрядности в разработке архитектуры (архитектуры мечты), потеря байта как единицы оперативной информации, оказалась болезненной.
Главная неразрешимая проблема состоит в том что адресное пространство для 8-битного или 16-битного вычислительного устройства ограничено одним и тем же числом ячеек — 65536. Я не рассматриваю сейчас способы преодоления данного ограничения, важно что они есть и они достаточно разнообразны, но при этом неизменны для платформ любой разрядности. А вот что действительно важно это “узость” имеющегося пространства в 64К слов. Особенно для Фон Неймановской архитектуры, когда код и данные зависят от одного и того же 64К слов адресного пространства.
Выбор системы команд RISC способен это противоречие только усугубить. Эволюцию размена простоты декодирования инструкции, а значит и частотного потенциала вычислительного ядра, на плотность машинного кода можно прочувствовать на примере системы команд встраиваемых ядер ARM. Изначально довольно изысканная по своей непротиворечивости и складности 32-битная инструкция довольно быстро скатилась к многочисленной разновидности THUMB. Переход к THUMB и уплотнению инструкции, ломает внутреннюю красоту, описательность и простоту первичной системы команд. Такое можно стерпеть, при условии, что ассемблер и оптимизация это удел компиляторов. В случае появления THUMB в АРМ первую скрипку играет ограниченность ресурсов в накристальных системах, прежде всего в ПЗУ и ОЗУ, а использование кросс средств и ЯВУ вопрос давно решенный. Допустимо ли это для “архитектуры мечты”? В некотором смысле система команд PDP-11 произведение искусства, а программирование в ассемблере для него, многим доставляет эстетическое удовольствие. Возможно ли этим пренебречь?
В любой архитектуре вычислительного ядра отказаться от индеректной константы крайне сложно. В RISC такая боль непринужденно вылезая из головы проектировщика декодера, превращается в мигрень программиста. Посудите сами, как одной 16 битной инструкцией, загрузить 16 битную же константу? Можно грузить слова из памяти по укороченному смещению или адресу, но на индиректную загрузку константы это совсем не похоже. Можно грузить что то меньшее 16 бит и операцию расширяющую это число. Что называется оба метода хуже — в первом случае рыхлый код становится еще рыхлее на адрес и данные к нему, а во втором случае в один прекрасный момент понадобится константа для получения которой потребуются затратить две инструкции. Впрочем, можно пойти на игнорирование принципов RISC и допустить команду с операндом в следующем слове (прощай мигрень программиста), но плотность кода все еще страдает. Как только начнется адресная арифметика со смещениями чьи значения по разрядности находятся в районе 5-4 бит, а для адреса в 64К трудно представить себе смещения между соседними элементами массивов больше чем 16 байт, так рыхлый код не заставит себя ждать. И это пока только код, нужно ли говорить какими аппетитами на адресное пространство будут обладать сами массивы с 16-битной грануляцией данных в них? Достаточно показательно, в качестве иллюстрации, хранение PCM сэмпла, да и вообще любых данных, чье сжатие основанное на учете приращения к предыдущему отсчету. Конечно всегда можно воспользоваться практикой сдвигов и маскировок битовых полей, но стоит ли это отказа от байта?
В системе команд почти всегда имеется группа инструкций применяющихся наиболее часто, но в случае с RISC где инструкции одинаковы по разрядности, использовать такую особенность на увеличение плотности бессмысленно. Другое дело CISC. Взамен CISC одаряет разработчика ядра сногсшибательной головной болью. Нерегулярная система команд требует совсем непростого автомата декодера, секвенсера и микрокода. Впрочем можно и без микрокода. А если уж так, то почему бы не усугубить, ввести очень короткую команду размером в байт, в которую засунуть наиболее часто используемые инструкции? Все остальные пойдут через префиксное расширение, которым могут являться бессмысленные инструкции, например пересылка из одного и того же регистра MOV Ri,Ri.
Чем короче инструкция и плотней код, тем более сложные и пространные программы можно писать для 64К не прибегая к оверлеям. Идя по этой дороге можно добраться до разновидности OISC — с инструкцией в 4 бита и арифметическим стеком, где мы снова окажемся на развилке противоречий. Стек заменяя многочисленные регистры общего назначения (в архитектуре мечты их может быть и более 16) расшивает узкое горло полей для их представительства в машинной инструкции, взамен награждает неторопливостью при вычислениях на нем. Дарит реверсивную стековую нотацию и сильно упрощает разбор выражений, однако лишает оптимизации по скорости вычисления.
Еще один шаг по тропе OISC и мыв области тьмы :) в области архитектуры ТТА (transport triggered architecture) состоящей из действительно одной инструкции, инструкции перемещения MOV. Команда шириной 8-бит тут к месту.
Использовать индиректно 8-битную константу пересылкой из источника CODE8, #200 -> R0, где #200 это 8-битная константа следующая за 8-битной инструкцией. 16-битную, меняем источник на CODE16, #2000h -> IP. Само собой IP при чтении с CODE8/CODE16 смещается на 1/2 байта, и указывает на следующую за константой инструкцию к моменту исполнения. Кодируя 16 источников и 16 приемников, легко и непринужденно можно их префиксировать не меняя основы, например так #1 -> RPAGE. Где RPAGE указатель страниц регистров.
Максимально простой декодер, нет микрокода. Внутренняя архитектура — мультиплексор данных из источников, управляемый полем машинного слова “SRC” и дешифратор записи управляемый полем “DST”. Быстра. Взамен… Совсем не простое кодирование, ведь инструкция сложения ADD #4, R0 превращается в набор пересылок: #4 -> ALU.A, R0 -> ALU.B, ADD -> R0. Опять здравствуй, хоть и небольшая, рыхлость кода. Продемонстрирую некоторые варианты использования:
Условное исполнение:
Авто инкремент и декремент адреса:
P.S. Пожалуй моделировать такой процессор в Digital, это идея для следующей статьи. :)
Главная неразрешимая проблема состоит в том что адресное пространство для 8-битного или 16-битного вычислительного устройства ограничено одним и тем же числом ячеек — 65536. Я не рассматриваю сейчас способы преодоления данного ограничения, важно что они есть и они достаточно разнообразны, но при этом неизменны для платформ любой разрядности. А вот что действительно важно это “узость” имеющегося пространства в 64К слов. Особенно для Фон Неймановской архитектуры, когда код и данные зависят от одного и того же 64К слов адресного пространства.
Выбор системы команд RISC способен это противоречие только усугубить. Эволюцию размена простоты декодирования инструкции, а значит и частотного потенциала вычислительного ядра, на плотность машинного кода можно прочувствовать на примере системы команд встраиваемых ядер ARM. Изначально довольно изысканная по своей непротиворечивости и складности 32-битная инструкция довольно быстро скатилась к многочисленной разновидности THUMB. Переход к THUMB и уплотнению инструкции, ломает внутреннюю красоту, описательность и простоту первичной системы команд. Такое можно стерпеть, при условии, что ассемблер и оптимизация это удел компиляторов. В случае появления THUMB в АРМ первую скрипку играет ограниченность ресурсов в накристальных системах, прежде всего в ПЗУ и ОЗУ, а использование кросс средств и ЯВУ вопрос давно решенный. Допустимо ли это для “архитектуры мечты”? В некотором смысле система команд PDP-11 произведение искусства, а программирование в ассемблере для него, многим доставляет эстетическое удовольствие. Возможно ли этим пренебречь?
В любой архитектуре вычислительного ядра отказаться от индеректной константы крайне сложно. В RISC такая боль непринужденно вылезая из головы проектировщика декодера, превращается в мигрень программиста. Посудите сами, как одной 16 битной инструкцией, загрузить 16 битную же константу? Можно грузить слова из памяти по укороченному смещению или адресу, но на индиректную загрузку константы это совсем не похоже. Можно грузить что то меньшее 16 бит и операцию расширяющую это число. Что называется оба метода хуже — в первом случае рыхлый код становится еще рыхлее на адрес и данные к нему, а во втором случае в один прекрасный момент понадобится константа для получения которой потребуются затратить две инструкции. Впрочем, можно пойти на игнорирование принципов RISC и допустить команду с операндом в следующем слове (прощай мигрень программиста), но плотность кода все еще страдает. Как только начнется адресная арифметика со смещениями чьи значения по разрядности находятся в районе 5-4 бит, а для адреса в 64К трудно представить себе смещения между соседними элементами массивов больше чем 16 байт, так рыхлый код не заставит себя ждать. И это пока только код, нужно ли говорить какими аппетитами на адресное пространство будут обладать сами массивы с 16-битной грануляцией данных в них? Достаточно показательно, в качестве иллюстрации, хранение PCM сэмпла, да и вообще любых данных, чье сжатие основанное на учете приращения к предыдущему отсчету. Конечно всегда можно воспользоваться практикой сдвигов и маскировок битовых полей, но стоит ли это отказа от байта?
В системе команд почти всегда имеется группа инструкций применяющихся наиболее часто, но в случае с RISC где инструкции одинаковы по разрядности, использовать такую особенность на увеличение плотности бессмысленно. Другое дело CISC. Взамен CISC одаряет разработчика ядра сногсшибательной головной болью. Нерегулярная система команд требует совсем непростого автомата декодера, секвенсера и микрокода. Впрочем можно и без микрокода. А если уж так, то почему бы не усугубить, ввести очень короткую команду размером в байт, в которую засунуть наиболее часто используемые инструкции? Все остальные пойдут через префиксное расширение, которым могут являться бессмысленные инструкции, например пересылка из одного и того же регистра MOV Ri,Ri.
Чем короче инструкция и плотней код, тем более сложные и пространные программы можно писать для 64К не прибегая к оверлеям. Идя по этой дороге можно добраться до разновидности OISC — с инструкцией в 4 бита и арифметическим стеком, где мы снова окажемся на развилке противоречий. Стек заменяя многочисленные регистры общего назначения (в архитектуре мечты их может быть и более 16) расшивает узкое горло полей для их представительства в машинной инструкции, взамен награждает неторопливостью при вычислениях на нем. Дарит реверсивную стековую нотацию и сильно упрощает разбор выражений, однако лишает оптимизации по скорости вычисления.
Еще один шаг по тропе OISC и мы
Использовать индиректно 8-битную константу пересылкой из источника CODE8, #200 -> R0, где #200 это 8-битная константа следующая за 8-битной инструкцией. 16-битную, меняем источник на CODE16, #2000h -> IP. Само собой IP при чтении с CODE8/CODE16 смещается на 1/2 байта, и указывает на следующую за константой инструкцию к моменту исполнения. Кодируя 16 источников и 16 приемников, легко и непринужденно можно их префиксировать не меняя основы, например так #1 -> RPAGE. Где RPAGE указатель страниц регистров.
Максимально простой декодер, нет микрокода. Внутренняя архитектура — мультиплексор данных из источников, управляемый полем машинного слова “SRC” и дешифратор записи управляемый полем “DST”. Быстра. Взамен… Совсем не простое кодирование, ведь инструкция сложения ADD #4, R0 превращается в набор пересылок: #4 -> ALU.A, R0 -> ALU.B, ADD -> R0. Опять здравствуй, хоть и небольшая, рыхлость кода. Продемонстрирую некоторые варианты использования:
Условное исполнение:
; псевдокод
; ADD = R0 + 8;
; IF ADD == 0 THEN R0 = ADD;
; IF ADD != 0 THEN goto M1;
#8 -> ALU.A
R0 -> ALU.B
ADD -> IFZ# ; передача Флагов сумматора в модуль условного исполнения по Z-флагу
ADD -> R0 ; Z флаг подключен на разрешение записи в приемник, действует в течении текущей инструкции
ADD -> IFNZ# ; передача Флагов сумматора в модуль условного исполнения по Z-флагу
IP -> R0 ; инверсия Z флага подключена на разрешение записи в приемник, действует в течении текущей инструкции
Авто инкремент и декремент адреса:
; addr = 2000
; mem[addr] = R0 + mem[addr];
; addr += 100;
#100 -> OFFS
#2000 -> ADDR
DATA -> ALU.A
R0 -> ALU.B
ADD -> DATA+ ; постинкремент адреса на OFFS
P.S. Пожалуй моделировать такой процессор в Digital, это идея для следующей статьи. :)
12 комментариев
По ссылке я новость написал с минимумом технических подробностей (но история сама, имхо, интересная), но сам же заинтересовался чипом SuperFX — это 16-битный процессор с архитектурой созданной под влиянием идей RISC который на 16-битной SNES позволял эффективно рисовать 3Д-графику — у ЦП SNES с этим есть ряд проблем. А у SuperFX прям как у Z80Next есть команда PutPixel и много прочего. Чип SuperFX впаивался в картридж SNES аки маппер в денди и рулил и педалил 3Д-графоний на 16 битах.
Думаю краткий обзор архитектуры SuperFX станет предметом моей следующей статьи, но тут распишу идею из него которая мне тоже понравилась и которую в таком виде нигде не видел.
Опкоды там как правило однобайтовые, но бывают префиксы.
Шестнадцать 16-битных регистров во многих инструкциях кодируются в четырёх битах как в статье: 0xAB, где A — код инструкции, а B — код регистра. Из этого правила есть немало исключений, но рассмотрим, например, операцию сложения:
В своей первичной форме она рассматривает R0 как аккумулятор и к нему прибавляет указанный в инструкции регистр R5. Т.е. берёт аккумулятор и R5, складывает их и результат записывает обратно в аккумулятор. Окей.
Однако можно временно на одну эффективную инструкцию сменить что будет являться приёмником операции — это делает инструкция TO:
Здесь эффект будет таков, что сумма R0 и R5 будет записана в R4. После выполнения ADD приёмник по умолчанию опять станет R0.
Точно так же есть однобайтовая инструкция FROM которая так же меняет временно какой регистр будет служить первым операндом:
Выполнит следующее: сумма R5 и R3 запишется в R4, а аккумулятор окажется вообще не при делах.
Для еще больше краткости есть однобайтовая же инструкция WITH которая сразу выставляет и FROM и TO в один и тот же указанный регистр.
Но и это еще не всё — WITH взводит еще один внутренний флаг который модифицирует поведение инструкций FROM и TO если они встречаются после неё. В SuperFX отсутствует специализированная команда MOVE, зато если после WITH сразу же идёт FROM, то происходит копирование из регистра указанного во FROM в регистре запомненном в WITH. И наоборот — инструкция TO скопирует из регистра запомненного по FROM в регистр указанный в себе.
Довольно забавная система команд — байтово-ориентированная, но постоянно на каких то полухаках, префиксах и сменах текущих целей и назначений.
Каждый боролся за плотность кода как мог. :)))
P.S. Статья про SuperFX будет крайне интересна!
И еще по вашему процессору я не понял как делается CALL — LR вроде есть, но непонятно что в него записывать.
Можно сохранять LINK как и в случае с LR в ARM либо в ОЗУ, либо один из регистров общего назначения. Допустим R4 это стек.
К слову — даже если посмотреть на Амигу, снятие ограничений пошло, на мой взгляд, ей непосредственно во вред. В среднем качество софта (демо и интро в первую очередь) сильно ниже, чем таковое у 8-битных платформ (того же C64).
Потому что адекватный человек (даже, хе-хе, демомейкер) не будет САМ СЕБЕ создавать сложности — он будет полностью использовать те возможности, которые ему даёт платформа. Это касается и памяти и отсутствий ограничения на число цветов на знакоместо и лёгкость проигрывания сэмплов и пр. Счастье, кстати, что REU для C64 не были широко доступны в своё время и не стали стандартом. Сейчас иногда делают демки для REU (ram expansion unit, до 512K обычно) но, как правило, бросается в глаза, что они написаны просто чтобы как-то использовать REU. Ну там картинку большего размера покрутить, к примеру. Или не возиться с непрерывной подгрузкой с диска, а загрузить сразу всё в буфер.
Однако действительно вопрос глубже — как «выдавить» при этом нормальную цветность.
Монохром как мы знаем в спектруме том же отнимает 6144 байта и раскраска знакоместами порождает клэшинг при попытке графонить графоний. Да и скроллинг в играх, а давайте уже не лукавить — игры это для нас очень важно — просто убивает и производительность и всё на свете — апдейтить 6Кб с приличным фпс — трудно для восьми бит.
Поэтому, имхо, оптимальность картинки в целом надо искать на пересечении текста и консольных принципов тайловой графики, причём одно плавно вытекает из другого, ибо не зря в Famicom/NES/Денди память тайлов офдоки называют CHR (character) ROM/RAM, ибо технически это действительно 256-значные символы квадратно-гнездовой сетки 8x8.
Однако Famicom, имхо, пример как немного перемудрили и ради призрачной экономии зарезали немало хорошего.
Главное — это аппаратный скроллинг по как минимум двум страницам видеотекста бесшовно перетекающий с прокрутной одна в другую.
Причём вот именно страницы текста имеют разрешение 32x32 символа, т.е. по вертикали виртуальное разрешение 256 пикселей, но оно используется чтобы организовывать бесшовный скроллинг с «проворотом» между границ.
Две экранных области текста пристыковани друг к другу слева-направо, над ними как бы «летает» видеоокно 256x192 на кинескоп управляемое одним 8-битным регистром прокрутки по вертикали и одним 9-битным регистром прокрутки по горизонтали (512 эффективных пикселей).
Но конечно чтобы игры на таком тексте делать еще нужны аппаратные спрайты.
В общем по лекалам денди я бы сделал так:
4Кб — память A изображений 256 тайлов 8x8 2bpp
4Кб — память B изображений 256 тайлов 8x8 2bpp
(одна область формирует тайлы для фона, другая — для спрайтов)
1Кб — текстовая видеостраница X 32x32 символа
1Кб — текстовая видеостраница Y 32x32 символа
(если произвольно скроллить, то видеостраницы пристыкованы прямоугольником «XY»)
256 байт — цветовые атрибуты видеостраницы X
256 байт — цветовые атрибуты видеостраницы Y
(2 бита на знакоместо/тайл выбирают ему палитру из 4-х возможных, в одном байте описывается 4 соседних символа/тайла квадратиком, что удобно для концепции метатайлов).
Но с таким потайловым и удобным для скроллинга фоном нужно конечно еще спрайты. Именно для них нужна память изображений Б ибо одной там реально маловато.
А чтобы всякие Elite делать нужно иметь второй видеорежим где первые 8Кб можно превратить в монохром, т.е. уже чистую графику.
Как Вы и отметили растра 4:3, получить количество точек по горизонтали можно умножив кол-во строк на 4/3. Для PAL и SECAM это 800 элементов в строке. Не спорю конечно кинескоп и качество исполнения видеотракта дадут свои ограничения, на эти возможности.
У меня есть несколько вопросов, буду признателен за ответы.
1. Почему именно телевизионный растр?
2. Чем вызваны Ваши предпочтения накладываемые на видеоадаптер по разрешению и цветности, только ли ограничениями ТВ растра?