GO WEST, часть 1

Я хочу попробовать собрать в максимально компактной форме более-менее всё, что нужно для того, чтобы либо портировать вашу программу на один из классических спектрумов, либо написать её с нуля сразу совместимой с классикой. Когда я говорю «классика», я имею в виду любую из следующих машин: ZX Spectrum 48K, 128K, +2, +2A, +2B или +3. На практике, многие из этих моделей очень похожи друг на друга с т.зр. программиста и реально важно отличать следующие три вида классических спектрумов: 48К или 128К/+2(«серый») или +2A/+2B/+3.

В целом, отечественные клоны обладают довольно высокой степенью совместимости с оригиналами и переделка вашей программы для Leningrad 48К на 48K классику или переделка вашей программы для Pentagon 128K на любую 128К классику скорее всего окажется возможной, зачастую даже необременительной. Тем не менее, различия есть, и если не принять их во внимание, можно очень легко получить спектрумовский софт, который ни на одном настоящем спектруме не заработает.

ПАМЯТЬ И ПОРТЫ


Про память нужно знать всего пару вещей. Во-первых, в общих чертах, расклад памяти вполне привычен. 48К — он и в Африке 48К. 128К и работа с младшими 5-ю битами порта #7FFD — всё как обычно, ничего нового (хотя у +2A, +2B и +3 есть две дополнительные страницы ПЗУ). У +2A, +2B и +3 есть также дополнительный порт #1FFD, который позволяет менять расклад спектрумовской памяти весьма кардинальным образом. Вот расклад этого порта по битам:

бит 0 режим адресации (0 — привычный, 1 — ненормальный) 
биты 1,2  формат памяти в ненормальных режимах, или…
бит 2 … старший бит выбора ПЗУ в привычном режиме
бит 3 мотор дискового привода
бит 4 строб порта принтера
Таблица 1: раскладка порта #1FFD.

Что реально делают биты 3 и 4 я не знаю и знать не хочу. Куда более интересны тут ненормальные режимы адресации. Выглядят они так (каждая строка начинается с состояния битов 2 и 1 порта #1FFD):

Бит 2 Бит 1 Страницы памяти (снизу вверх, т.е. страница с адреса #0000, страница с адреса #4000 и т.д.) 
00память составлена из страниц 0,1,2,3
01память составлена из страниц 4,5,6,7
10память составлена из страниц 4,5,6,3
11память составлена из страниц 4,7,6,3
Таблица 2: ненормальные режимы адресации для +2A/+2B/+3.

Экраны, как всегда, прибиты к страницам 5 и 7, таким образом, в конфигурации 00 у вас нет ни одного экрана, а в конфигурации 01 — их сразу 2. Т.к. чуть более старые спектрумы (128К и +2) не поддерживают порт #1FFD, на практике, очень мало какой-то софт использует эти дополнительные возможности по организации памяти. Но, если вы написали ваш софт на Скорпионе или Кае или одном из их родственников, есть ненулевая возможность что ваш софт полезет в порт #1FFD и попадёт в беду. Старые диспетчеры расширенной памяти из спектрум-прессы это не учитывают — будьте бдительны!

Вторая проблема при работе с памятью на классике заключается в том, что половина вашей памяти — «медленная»! Насколько медленная? смотря что с ней делать. Я хочу подробно описать методику расчёта тормозов медленной памяти во третьей части моей статьи (это отдельная большая и нетривиальная тема). Сейчас для меня важнее развеять некоторые расхожие стереотипы о медленной памяти.

  1. Самое главное: медленная память — медленная только во время рисования экрана чипом ULA. Если вы что-то пишете в экран во время отрисовки верхнего или нижнего бордера — не важно где и что вы делаете, любая память для вас работает одинаково. Даже во время рисования бордера слева и справа от экрана медленная память делается нормальной.

    Если с памятью очень плохо и есть эффект, который целиком выполняется во время верхнего или нижнего бордера — его можно положить в медленную память, и код, и данные, разницы на классике не будет. Например, даже если у вас бордер/мультиколор и полный фашизм по тактам, если вы зовёте муз. плейер в начале фрейма — его можно положить в медленную память и всё будет ок.

  2. Немного о природе задержек. Команды Z80 исполняются «кусочками». Z80 сначала берёт код команды из памяти, а потом делает что велит команда, по шагам. К примеру, POP HL выполняется за 3 шага:
    (a) чтение кода команды по адресу PC: 4 такта
    (b) чтение байта по адресу SP: 3 такта
    (c) чтение байта по адресу SP+1: 3 такта
    Если ULA рисует экран, процессор может влезть в память только в один или два конкретных такта на каждые 8 тактов (в зависимости от модели спектрума). Поэтому если положить и стек и сам код в медленную память — шаг (a) (чтение кода) будет ждать пока ULA освободит память, потом шаг (b) будет ждать пока ULA освободит память и потом ещё шагу (c) тоже придётся ждать пока освободится память. Именно поэтому 16K спектрум — это реально нешуточно тормозной компьютер (особенно когда он рисует экран).

    Если у нас какой-то более-менее стандартный код, без особой специальной подгонки, масштабы потерь скорости будут зависеть от вашего стиля работы с экраном. Пусть мы пишем данные в экран байтами, и пусть у нас более-менее случайный доступ (т.е. что-то типа эффекта где рисуется большое кол-во точек). На экране 192 строки, отрисовка каждой из которых занимает 128 тактов процессора, т.е. отображение экрана занимает примерно 192*128 = 24576 тактов, это где-то 35% общего времени в кадре. В среднем, при попытке писать в экран во время работы ULA, ваша команда байтового доступа рискует навлечь на себя 2.625 тактов задержки ((6+5+4+3+2+1+0+0)/8, подробнee — в другой раз). Т.е. в среднем, при случайных доступах к экрану байтовыми командами и кодом, работающим в быстрой памяти, следует ожидать примерно 0.35*2.625 = 0.92 «лишних» такта на каждом байте записанном в экран.

    При работе с двухбайтовыми командами средние задержки могут быть существенно больше. Kонкретный пример: стандартное копирование стеком теневого буфера в экран на 48K машине с медленной памятью, которое не привязано к инту и в итоге «ползёт» по кадру, даёт замедление в среднем где-то на 1.3 такта на каждый скопированный байт (это подразумевая что код и исходный буфер лежат в быстрой памяти). Это типичный пример замедления, при неоптимизированном доступе к медленной памяти, кодом в быстрой памяти, полагающимся на операции со стеком.

    Если учитывать особенности медленной памяти, потери тактов можно существенно уменьшить или даже совсем изничтожить. Если писать не думая о последствиях, можно потерять очень много тактов. Теоретически худший случай — потеря до 30% времени (код в медленной памяти непрерывно тычется в медленную память). Практически, я слышал о потерях до 15-20%. Не будьте одним из этих людей.

  3. Отсюда — правила гигиены. Если вам хочется, чтобы ваш эффект успевал на классике — старайтесь класть код в быструю память. Данные тоже очень желательно по возможности хранить в быстрой памяти. Сам экран находится в медленной памяти, и совсем избежать замедление сложно, поэтому — думайте, как постараться нарисовать побольше во время бордера и м.б. сделать что-то не трогающее медленную память во время отрисовки самого экрана.

Где находится быстрая память и где находится медленная? Ответ на этот вопрос зависит от машины. У 48K компьютеров #4000-#7FFF — медленная память, вся остальная — быстрая. У 128K/+2 «медленными» являются страницы 1,3,5 и 7. Создателям +2A/+2B/+3 не сиделось спокойно на месте, поэтому они сделали медленными страницы 4,5,6 и 7. Таким образом, страницы 0 и 2 — везде быстрые, 5 и 7 — везде медленные. По поводу остальных страниц приходится разбираться по ходу исполнения программы (я расскажу как программно отличить 128K/+2 от +2A/+2B/+3 в следующей части).

В дополнение к медленной памяти стоит упопомянуть, что ввод/вывод в порты тоже бывает медленным; это касается 48K/128K/+2. Более поздние модели, т.е. +2A/+2B/+3 устранили этот недостаток. В частности, медленным является порт #FE, что не учтено ни в одном классическом биперном движке, из-за чего почти все биперные движки звучат объективно чище на спектрумах от Amstrad или на отечественных безвейтовых клонах. Медленными также являются любые порты в адресном пространстве #4000-#7FFF (вы поняли, о каком порте речь), а также порты в окне #C000-#FFFF, если в данное окно в данный момент впечатана страница медленной памяти. Важно: задержки ввода/вывода, точно так же как задержки медленной памяти, проявляются только во время отображения экрана; на бордере никаких задержек нет. В рамках данной серии статей я не собираюсь расписывать задержки ввода/вывода (это слишком редкая, специальная задача). Возможно, я как-нибудь соберусь и расскажу об этих задержках в рамках разговора о биперных движках.

Специально для любителей насиловать железо в особо извращённых формах — вот табличка по неполной дешифрации портов на классических машинах:

ПортМаска для 48KМаска для 128K/+2 Маска для +2A/+2B/+3 
#FExxxxxxxx xxxxxxx0 xxxxxxxx xxxxxxx0 xxxxxxxx xxxxxxx0
#7FFDпорт отсутствует0xxxxxxx xxxxxx0x01xxxxxx xxxxxx0x
#1FFDпорт отсутствуетпорт отсутствует0001xxxx xxxxxx0x
#BFFDпорт отсутствует10xxxxxx xxxxxx0x10xxxxxx xxxxxx0x
#FFFD порт отсутствует11xxxxxx xxxxxx0х11xxxxxx xxxxxx0х
Таблица 3: неполная дешифрация портов на классике.

Примечание: если вам непонятно, что вам пытается сказать эта табличка, я думаю, вам лучше не знать, что вам пытается сказать эта табличка!

продолжение следует.

20 комментариев

avatar
Да! Это как раз тот материал, который я давно хотел собрать и систематизировать для себя. Спасибо:) Еще хотелось бы почитать про растактовку экранной области каждого компа: сколько тактов на линию, сколько в невидимой области бордюра (сверху, снизу, слева и справа), сколько в видимой, сколько до начала экранной области и т.п.

Очень интересно про программные методы определения типа компа, в т.ч. наших клонов (до сих пор не постигаю, например, как реализован универсальный фикс бордюрного эффекта в sage boot (cм.borndead#10) под любую машину)…

И еще: «У 48K компьютеров #4000-#7FFF — медленная память, вся остальная — медленная» — опечатка?
  • bfox
  • +4
avatar
Очень рад что «попал». Растактовка будет во второй части, я базирую на этом некоторые тесты. Детект наших клонов в моём исполнении не вполне идеален, я немного не дописал его в своём просмотрщике бордеров. Но что умею — расскажу.
avatar
И спасибо за замеченную опечатку — исправил!
avatar
о задержках для портами даже не слышал.

реально зоопарк.
так понимаю, в основном ориентироваться стоит на 128K/+2

в следующих частях здорово было-бы рассмотреть стандартный инструментарий для тестов/отладки кода в таких режимах.
ибо эмулей много…
  • VBI
  • +3
avatar
для портов :)
avatar
О, отлично, я об этом даже не думал! :)
avatar
Полезная статья, я раньше не сильно над этим задумывался, зато теперь знаю, как что планировать.
  • ShM
  • +2
avatar
Но надо больше скриншотов/картинок/иллюстраций. Текст давит на мозг. Снег например закриншотить в качестве иллюстрации, как бывает при медленной памяти. И т.п.
avatar
ждём второй серии
avatar
мне тоже интересно — как это, «снег на экране» из-за прерываний в нижней памяти
avatar
ибо влип как-то)
avatar
Картинок много не будет — за этим к бризу пожалуйста :)

Будет код. Жестокий и беспощадный. И гигантские таблицы.
avatar
Ууу. Расходимся, ребята. Картинок не будет.
  • sq
  • +4
avatar
Совсем избаловались! Мультики по телеку вам, а код есть код и пишут его — на коде!!!
avatar
книжка без картинок, не интересная книжка. ©Алиса
avatar
Дыа! Берите пример с моих статей!

avatar
У Алисы котировались книжки с картинками и диалогами.
introspec , добавь хотя бы диалогов?
avatar
Чото смеюсь!
avatar
хороший/плохой пример игрушка FantasyWorldDizzy.
мало того что код плейера/семплов жёстко на 3ей странице
(снег и заторможенное воспроизведение на сером+2),
так ещё и регистр I используется для временного хранения нужных значений.

а вот например в Ruff & Reddy страница для плейера назначается в зависимости от модели спека.
avatar
У меня все демы начинают с того, что грузят модуль определения железа. Вторая часть моей статьи как раз на коде из него и будет основана. Ядро моих дем работает по итогам не с реальными, а с виртуальными номерами страниц, так что я всегда знаю наверняка, какие страницы медленные, а какие — быстрые.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.