"Цветорасширитель для ZX-Spectrum" — так называлась статья, опубликованная в эхе fido7.zx.spectrum 3 августа 1997 года. Статья описывала идею решения одной из главных проблем платформы ZX-Spectrum — конфликта атрибутов (attribute clash). Публикация вызвала в то время определенный интерес, про технические детали и историю вопроса я и хотел бы рассказать.
Не буду залезать глубоко в технические подробности и просто структурно опишу идею и решение.
История разработки началась ранней весной 1994го года, когда страна все еще переживала "изменения в призыве", которые привели к массовому закрытию отсрочек и призыву студентов ПТУ и техникумов. Мне повезло - я был на последнем курсе Ленинградского радиотехнического техникума и таким государство согласилось дать кратковременную отсрочку на ускоренное получение диплома с заменой дипломного проектирования на экзамен по всем предметам. В армию ушел 1-го марта 1994го года уже дипломированным техником-радиотехником.
В вооруженных силах в это время был небывалый наплыв таких же специалистов средне-технического звена, правда это немного рушило осенне-весеннюю призывную систему в головах “дедушек” у которых уже была сформирована четкая градация в наименованиях, они этот нестандартный призыв окрестили “гоблинским”. Среди “загремевших” хватало людей увлекавшихся ZX-Spectrum и в моем подразделении таким был Александр Гумин (он писал игры на ZX-Spectrum под лейблом ANCCAN вместе с Денисом Маркеловым и стал известен своей адаптацией Mortal Kombat для этой платформы в 1997м году), было с кем поговорить о программировании и “железе”.
Ближе к середине апреля 1994го года, по окончании "курса молодого бойца", мы с Александром, были переведены в стройбатовскую учебную часть, расположенную в пригороде Санкт-Петербурга - поселке Стрельна. Там нам предстояло несколько месяцев осваивать нелегкую специальность кабельщика-спайщика. Жизнь в этой части текла неторопливо и учеба чередовалась с нарядами, что в основном напрягало руки, но давало мозгу время для размышлений. Так, в один из нарядов по кухне, моя пол и размышляя о ZX-Spectrum и его возможностях, мне пришла в голову идея - как победить "конфликт атрибутов".
Я обдумывал эту идею до конца службы и всё больше убеждался, что "может сработать". К сожалению мысль о жизнеспособности самой платформы, к которой я собирался применять эту идею, посещала меня реже. Впрочем, в России, ZX-Spectrum наиболее ярко вспыхнул на небосводе как раз примерно с 1991го по 1996й. Некоторые российские производители его клонов поднялись настолько, что спонсировали передачи на ТВ (например компания БиЭм какое то время спонсировала передачу “Джунгли зовут”). Но во время службы были другие проблемы, так что я решил отложить все вопросы связанные с коммерцией до своего увольнения в запас. Периодически и без раскрытия деталей, интересовался у разных специалистов на тему технической возможности и обоснованности подхода. Саму идею держал в глубоком секрете и не поделился ею даже с Александром Гуминым, только расплывчато указав ему, что нашел очень простое решение как увеличить количество цветов с сохранением обратной совместимости.
Выйдя на “гражданку” в 1997м году и устроившись работать инженером-программистом второй категории в питерской компании “Информационные Технологии и Модели”, я начал помаленьку интересоваться вопросом коммерциализации решения. У меня почему-то была уверенность, что стоит только заикнуться о найденном решении, как все начнут рвать с руками и потоком польются деньги. Я начал обзванивать известных, на тот момент в области "спектрумостроения", производителей и коммерсантов, таких как Сергей Зонов, Вячеслав Скутин (Nemo) и прочих. Сергей Зонов сказал мне просто, что "поезд уже ушел" и никакого коммерческого смысла в этой затее больше нет. Вячеслав Скутин, будучи ортодоксальным спектрумистом, воспринимал в штыки любую идею, в которой пытались что-то поменять в платформе и это был тоже совершенно мертвый вариант. Я решил, что разговоров мало и надо сделать хоть что-то и лучше всего начать с эмулятора, что-бы получить промо-материалы и экспериментальные данные.
В 1998м, взяв за основу один из существующих на тот момент open-source эмуляторов ZX-Spectrum написанных на Pascal, я сделал примитивный эмулятор работающих параллельно четырех компьютеров. Под него частично адаптировал игру After The War 2, раскрасив некоторые её спрайты. Система заработала и помимо наслаждения от работающей идеи, я получил в свое распоряжение скриншоты, подтверждающие идею расцветки существующих игр.
В 1998м году, я посетил компанию “Петерс”, которая разрабатывала в тот момент свою новую платформу Sprinter. Проделал попытку заинтересовать их директора Николая Носкова. Они сильно вложились в новую разработку и, по закону подлости, супер-гибкая архитектура Sprinter, способная эмулировать практически любую однопроцессорную платформу на Z80, не могла вытянуть четыре процессора. Однако посещение этой компании было очень полезным, так как познакомился с автором платформы Sprinter Иваном Макарченко и узнал про новые возможности в сфере разработок на ПЛИС.
Вскорости "жахнул" кризис 1998го года и появилась наивная надежда на то, что интерес к ZX-Spectrum может возродиться. В начале 1999го года у нас (с моим компаньоном на тот момент - Андреем Савичевым) даже были планы по созданию “Национального Фонда Спектрума” и на эту тему прошло одно собрание в новом офисе всё той же компании “Петерс”. Но в одну реку дважды не входят и конечно ничего не вышло. Уже в 1999м году, все идеи на тему аппаратной реализации платформы были отброшены (над этой разработкой мы работали вместе с Сергеем Егоровым, но дальше схем не ушло). До 2007го года, я практически не занимался платформой, но появилось время и решил переписать эмулятор и проработать детали платформы, проверив подходы пускай и "виртуально".
Стандартный видеорежим ZX-Spectrum, отображает 256 на 192 пикселя. В монохромном режиме это требует всего 6144 байт, что примерно 10% от общего поля памяти (против 50% на БК-0010). Цветовая информация хранится в дополнительных 768 байтах, расположенных сразу за данными пикселей. Палитра состоит из восьми цветов и двух оттенков. Цвет засвеченного и сброшенного пикселей определяется сразу для блока 8 на 8 пикселей, а оттенок определяется сразу и для цвета засвеченного и сброшенного пикселей.
Смотрится красочно и относительно небольшой объем данных пересылается очень быстро, но требует неимоверных усилий и искусства от художников и программистов при разработке цветных игр и заставок. Малейшая несогласованность в графике приводит к конфликту атрибутов. Большинство разработчиков, отлично делали свое дело и на фоне БК-0010, с её всего четырьмя цветами (но для каждой точки!), спектрумовская квази-цветность смотрелась очень выигрышно и свежо.
С развитием платформы до ZX-Spectrum 128, была добавлена возможность аппаратного переключения между двумя страницами видео-памяти. Программисты быстро нашли способ получать многоцветность при помощи очень быстрой смены отображаемых видео-страниц и динамической смены цвета атрибутов.
За счет этого удавалось даже программно поднимать разрешение экрана (что отлично показано например в деме "Dead Morose", где одновременно бежит текст с разрешением в 256, 512 и 768 точек по горизонтали).
Но любые программные решения, требующие увеличения потока видеоинформации, приводили к росту потребления процессора, что в случае динамических игр весьма критично. Какого-то неиспользованного источника резервов вычислительных мощностей в системе не было. На процессоре в ZX-Spectrum висит всё и небольшую разгрузку ему дает, разве что, музыкальный процессор в области звуковых эффектов.
Моя идея состояла в том, что можно добавить еще три процессора, перекинув на каждый из них обработку своего цветового компонента. Полученные данные из собственной видео-памяти каждого процессора должны были интегрироваться, формируя YRGB значение для каждого пикселя. Стандартные цветовые атрибуты при этом игнорируются. Параллельная обработка информации должна обеспечивать отсутствие проседания производительности.
Не могу сказать, что был оригинален в идее, так как на неё меня сподвигло прочтение переводной книги “Компьютер обретает разум” (изд-во "Мир", 1990), где описывалась некая графическая платформа Pixar разработанная в "Lucasfilm". Да и по ТРИЗ это просто переход от моно-системы к поли-системе.
Очень болезненный вопрос для любой разработки — а кто будет писать программы? (в частности на этот "подводный камень" натолкнулась платформа Sprinter). В моем случае, вопрос с программным обеспечением автоматически решался через то, что очень нечасто уже существующие программы проверяли какие именно данные они записывали в видеопамять и просто работали с ними "как почтальон с запечатанным письмом". По моим расчетам получалось, что большинство существующих игровых программ могли достаточно спокойно пережить адаптацию своих видео-данных без изменений в исполняемом коде. Конечно отсекались игры имеющие паковку графики или внутренние оптимизации в её выводе на экран. Адаптация таких программ потребовала бы исследования и переделки исполняемого кода. Разработка специализированного ПЗУ и вовсе не требовалась.
Думаю, что эта концепция применима к любой старой бытовой платформе (например АГАТ, Радио86РК, БК-0010 и т.п.), где отсутствует выделенный видео-акселератор и процессор напрямую работает с видеопамятью, формируя картинку.
В первой версии эмулятора, я просто сделал синхронно работающие четыре ZX-Spectrum 48. Но то, что легко имитировать на эмуляторе, на реальном железе повторить очень непросто. Достаточно сложно обеспечить загрузку данных в четыре вычислительных модуля и сделать синхронный старт с одного и того же адреса. Схожее решение Spec256 вводит для этого специализированный виртуальный 64 битный SIMD Z80, которого не существует в природе. В рамках более реалистичного (и более сложного) решения этой задачи и была сформирована платформа ZX-Poly.
От “цветорасширителя” к ZX-Poly
Процессорные модули
ZX-Poly это вычислительная платформа на базе ZX-Spectrum 128 содержащая четыре процессорных модуля. Каждый модуль имеет свои видимые извне адресуемые системные IO порты. Процессорные модули хоть и разделяют системные управляющие сигналы (RESET, NMI, CLK и INT), но работают независимо.
Присутствует некая ранжируемость зависящая от индекса модуля — чем меньше индекс, тем выше ранг, соответственно "модуль 0" это master в системе. Доступ по записи в системные порты модуля разрешен только модулю с таким же или более высоким рангом, ограничений на чтение нет. Это было сделано, так как присутствовали мысли о разработке специализированной ОС.
Очень важная деталь — все используемые процессорные устройства, должны быть одного производителя (и было бы неплохо — одной производственной серии), так как малейшая разница в их внутренней организации или оптимизация, может нарушить согласованность работы системы.
Сразу после старта системы, запускается только master-модуль (CPU0), остальные модули находятся в режиме WAIT, поэтому для пользователя всё проходит малозаметно.
В IO пространстве, ZX-Poly добавляет следующие порты доступные на запись и чтение:
- главный конфигурационный порт $3D00
- модуль 0 — $00FF, $10FF, $20FF, $30FF
- модуль 1 — $01FF, $11FF, $21FF, $31FF
- модуль 2 — $02FF, $12FF, $22FF, $32FF
- модуль 3 — $03FF, $13FF, $23FF, $33FF
Основной конфигурационный порт ZX-Poly — $3D00. В него может писать только модуль-master, но на чтение он доступен всем модулям и каждому возвращается его собственная специализированная информация. В частности модуль может узнать свой индекс, замаппирована ли его память на IO порты главного модуля, смещение своей памяти в куче и т.п. Так же, небольшие изменения претерпел конфигурационный порт базовой платформы $7FFD, у которого задействованы неиспользованные в оригинале биты.
Память
Как и в оригинальной архитектуре ZX-Spectrum 128, присутствуют ПЗУ и ОЗУ. Если организация и работа с ПЗУ практически не претерпела изменений, то ОЗУ превратилось в общую "кучу" размерностью 512 Кб, в которой каждый процессор работает с выделенным 128 Кб окном (это 8 страниц по 16 Кб в ZX-Spectrum 128). Смещение окна можно изменять с шагом в 64 Кб и можно спроецировать все процессорные модули на работу с полностью или частично перекрывающимся участком памяти в "куче". ПЗУ может быть отключено и на его место будет подключена страница ОЗУ RAM0 (это позволяет сделать "полноцветную" версию базовой ОС, например интерпретатор бейсика). После аппаратного RESET, все модули получают автоматические смещения на непересекающиеся окна памяти в "куче".
Процессорный модуль master (CPU0), имеет возможность маппировать адресные пространства других модулей (CPU1-3) в область своих IO портов. Т.е. он может писать в порт, меняя состояние ячейки памяти у заданного модуля, причем при этом возможна генерация NMI сигнала на маппированный модуль. При чтении из ячеек памяти маппированного модуля, возможна генерирация INT. Это было сделано для возможности эмуляции виртуальных устройств при помощи модулей 1-3.
Видео-контроллер
Основная "вишенка" это конечно видео-контроллер, ради него всё и затевалось. Всего платформой поддерживаются пять видео-режимов.
Видео-режимы ZX-Spectrum 128 (mode 0,1,2,3)
Эти видео-режимы ничем не примечательны и совершенно не отличаются от стандартного видеорежима ZX-Spectrum 128. Выводится текущая видео-страницу выбранного процессорного модуля с атрибутной раскраской. Сразу после старта системы, активирован mode 0, т.е. выводится видео-страница master-модуля (CPU0).
ZX-Poly 256x192 (mode 4)
Данный видео-режим вообще не использует каких либо данных из области атрибутов. Пиксель-бит каждого модуля комбинируется с пиксельными данными из других модулей и сформированные четыре бита, используются для формирования цвето-яркостного YRGB сигнала.
Каждый модуль отвечает за свой компонент:
- модуль 0 (master) за Green (зеленый).
- модуль 1 за Red (красный)
- модуль 2 за Blue (синий)
- модуль 3 за яркость
Если в таком режиме запустить неадаптированную игру, то она будет просто черно-белая.
ZX-Poly 256x192 с маскированием по INK+PAPER (mode 6)
Как и предыдущий, предоставляет 16 цветов для каждой точки, но имеется одна "хитрость". В некоторых программах ZX-Spectrum, графические элементы скрываются при помощи идентичных значений INK и PAPER в атрибутах, особенно это практикуется в играх-скроллерах. Если убрать такую возможность, то графические элементы начинают скапливаться на экране, ломая картинку. Поэтому анализируется состояние INK и PAPER из атрибута считанного из видео-памяти master-модуля (CPU0) и если они идентичны, то все точки засвечиваются цветом взятым из INK/PAPER (конечно с учетом яркости).
ZX-Poly 256x192 с маскированием по FLASH+INK+PAPER (mode 7)
Режим — комбинация mode 4 и mode 6. У считываемых атрибутов master-модуля (CPU0), анализируется бит FLASH и если он выставлен (что в динамических играх достаточно редко на игровом поле), то блок 8 на 8 пикселей выводится в режиме mode 6 (с маскированием при идентичных INK и PAPER). Если FLASH сброшен, то блок выводится как на обычном ZX-Spectrum. Бит FLASH не отрабатывается в его прямой роли и включенного режима мерцания не будет.
Этот режим очень удобен для избежания перекрашивания игровых информационных панелей и некоторых внутриигровых эффектов (например когда разработчики игры делают вспышку на игровом поле через атрибуты).
ZX-Poly 512x384 с атрибутами (mode 5)
Атрибуты используются практически так же, как в оригинальной платформе, без каких либо изменений (даже бит FLASH). Видео-данные пикселей каждого модуля раскрашиваются атрибутом из видео-памяти данного модуля и пройдя раскраску, выводятся на экран в шахматном порядке, из-за чего один блок знакоместа получается 16 на 16 точек.
Этот режим позволяет увеличить разрешение приложений вдвое, без изменений в исполняемом коде. Правда эксперименты с той же игрой «Буратино», показали, что на ярких играх с крупными деталями, эффект будет слабо заметен. Поскольку это своего рода виртуальные пиксели, то игры об этом ничего не знают и минимально-возможное перемещение игровых элементов идет на уровне двух виртуальных пикселей. Думаю, что этот режим хорош для игр с маленькими игровыми объектами в знакоместо.
Гораздо лучший эффект в этом видео-режиме был получен на текстовых приложениях, например на текстовом редакторе ZX-Word, где только обработан фонт без изменений в исполняемом коде.
Многозадачность
Несмотря на то, что процессоры в системе используют один и тот же источник управляющих сигналов, они работают независимо. Систему нельзя назвать полноценной SIMD, так как каждый процессор обрабатывает инструкции из своего блока памяти, просто используется возможность "подсовывать" одинаковые инструкции. Если один из процессоров встретит в памяти отличающуюся команду, то "его пути разойдутся" с остальными и в случае адаптированной игры — картинка расслоится.
Потенциально можно расчитывать пути выполнения потактово и добиваться как расхождения, так и схождения потоков выполнения, но это очень сложно и наверное интересно только "демосценерам". Для упрощения, были добавлены механизмы примитивной синхронизации.
Механизмы синхронизации
Локальный RESET
Этот механизм позволяет одновременно послать сигнал RESET на все процессорные модули, но первые три байта будут каждым модулем считаны из своих внутренних портов. Это позволяет выполнить команду JP ADDR переведя все модули синхронно на одну и туже ячейку памяти.
Стоп-адрес
Это механизм для всех модулей кроме master-модуля (CPU0). В порты модуля можно записать определенный адрес, при выполнении команды на котором (определяется по сигналу M1), на процессоре модуля будет установлен локальный сигнал WAIT, который будет активен до момента смены этого адреса в портах или прохождения общесистемного или локального RESET. Есть возможность считывать последний исполненный модулем адрес, но достаточно условно, так как 16 битный адрес пакуется в байт.
Эмулятор системы
В 2007м на JavaSE была написана новая версия эмулятора. В его рамках были написаны подсистемы процессора Z80 (с потактовой эмуляцией и покрытием команд юнит-тестами) и FDD контроллера К1818ВГ93. Для работы приложения требуется JDK 1.8+.
Эмулятор был разработан для проверки совместимости платформы с существующим программным обеспечением ZX-Spectrum и портами устройств и в целом он показал, что 80% запущенных программ работают, не конфликтуя с нововведениями. Так же он может использоваться как эмулятор обычного ZX-Spectrum 128, для этого надо включить режим Options->ZX 128 Mode, при этом подсистема ZX-Poly в эмуляторе блокируется для повышения совместимости.
В комплекте с эмулятором поставляется только тест-ПЗУ, поскольку официальное ПЗУ ZX-Spectrum — объект авторского права. Но его можно загрузить через интернет, выбрав в меню File->Options
Если игры не используют какие-то паковки спрайтов, то расцвечивание происходит достаточно просто. Требуется соблюдать осторожность при наличии оптимизаций в исполняемом коде вывода спрайтов, так как это может приводить к рассинхронизации процессорных модулей.
Был разработан загрузчик, для подгрузки данных в модули с диска и одновременного синхронного старта. Его код для TAP и TR-DOS представлен в проекте.
Вместе с эмулятором была разработана небольшая (тоже написанная на Java) утилита для раскраски приложений.
Весь проект опубликован на GitHub под лицензией GNU GPLv3.
Автор: Igor Maznitsa