_irq_user → _irq_chain[4]+_irq_chain_n (W2-BSS); трамплин tr_frame проходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропуск). API irq_chain_add (0/-1+ENOMEM) / irq_chain_remove(h); irq_install → обёртка chain_add (EBUSY исчез), irq_remove() рвёт всю цепь; refcount таблицы на первом/последнем слоте, мутации под IRQ_DISABLE. Лимит 4 кадровых + 1 CTC. All-modes: трамплин copy-safe (только jr/djnz + литерал jp 0x0038), _irq_table_ref копирует его в _irq_tramp_w2buf (W2) когда оригинал в W1 (small/huge), вектор → на копию; вокруг вызова хендлеров восстанавливает базовую W1-страницу (_irq_app_w1_page = IN 0xA2). Хендлер может лежать где угодно в плоском 0x4000-0xBFFF. Проверено в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain h1=h2 → remove h2 → h1 жив/h2=0; huge = код в W1, remap работает. Follow-up: CTC в small/huge = EINVAL (нужна W2-копия _irq_ctc_tramp); small для мелких программ (BSS в W1) = irq_install EINVAL, safe. Доки: im2_isr_design (цепочка), sprite-api §9е (делитель поверх цепи), fast_ram §8. rpgprof: gfx_sprite_ysort в профиль (+230 Б baseline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
20 KiB
Fast RAM (Быстрое ОЗУ / «КЭШ-ОЗУ») на Sprinter
Сводка по результатам изучения документации платформы. Источники:
docs/converted/Architecture.txt— официальное «Описание архитектуры» (раздел «Распределение основной памяти»).docs/converted/ARHITECT.txt— ранняя редакция того же документа (про загрузку конфигураций ППЛМ).docs/converted/IvanMak.txt/docs/converted/Parinov.txt/docs/converted/Forum.txtиdocs/part2/forum.txt— форумные ответы Дениса Паринова (Sprinter Team) и руководство Ивана Мака (раздел «7 КЭШ-ОЗУ»).docs/part2/accelerator_doc.txt— ограничение акселератора.docs/samples/sprinterIntLib.asm— практический пример temporary-off / restore.
Терминология. В документации одно и то же ОЗУ называется тремя именами: Fast RAM, Быстрое ОЗУ и «КЭШ-ОЗУ». Это не кэш в формальном смысле (нет автоматического заполнения/вытеснения) — это отдельный массив статической памяти, в котором процессор работает на полной частоте без тактов ожидания. Имя «КЭШ» — историческое, по аналогии с кэшем на КР537РУ10 в Pentagon-128.
1. Что это и зачем
- Объём: 64 КБ статической памяти (SRAM), отдельной от основного DRAM-SIMM (4 МБ) и от видео-ОЗУ (256 КБ).
- Скорость: процессор обращается к Fast RAM на полной тактовой частоте (21 МГц) без wait-state'ов. Основное ОЗУ (DRAM) требует тактов ожидания, поэтому код и данные в Fast RAM исполняются/читаются заметно быстрее.
- Назначение: разместить «горячий» код или данные (внутренние циклы, таблицы, буферы), которые критичны по скорости.
- Системная роль: Fast RAM также используется механизмом
переконфигурирования ППЛМ — именно в неё BIOS грузит данные новой
конфигурации и флаг
ACEX_30K_LOADING(старое имяFLEX_10K_LOADING) перед программным сбросом. Поэтому к Fast RAM нельзя относиться как к «своей» памяти, которая всегда сохраняется (см. §5).
2. Карта физических страниц
Память делится на 16 КБ-блоки с однобайтовым физическим номером:
| Тип памяти | Физические номера страниц |
|---|---|
| Основное ОЗУ | #00..#4F, видео-область #50..#5F, ... |
| ПЗУ (ROM) | #E0..#EF |
| Fast RAM | #F0..#FF |
Хотя диапазон номеров Fast RAM —
#F0..#FF(16 значений), реально используются только биты 1 и 2 номера страницы. То есть адресуются 4 страницы × 16 КБ = 64 КБ:#F0,#F2,#F4,#F6.
3. Как включать Fast RAM
Есть два способа подключить Fast RAM в адресное пространство Z80.
Способ A. Pentagon-style через порт #FB / #7B (в окно 0)
Включается «как кэш в Pentagon»: подключает 16 КБ Fast RAM в окно 0
(#0000..#3FFF) вместо ПЗУ. Переключение — побочный эффект чтения порта
(значение в A после IN — мусор, важен сам факт обращения):
DI
IN A,(#FB) ; включить Fast-RAM — 16 КБ в окно 0 (#0000..#3FFF)
; ... ваш код / работа с Fast RAM ...
IN A,(#7B) ; выключить Fast-RAM (вернуть ПЗУ в окно 0)
EI
IN A,(#FB)— включить.IN A,(#7B)— выключить.
Конфликт портов. Порт
#FB(и#4F) — это также порт COVOX/Blaster-а. Вывод (OUT) в#FBуправляет звуком, а чтение (IN) — переключает Fast RAM. Не путать направления обращения.
Способ B. Как ПЗУ — через PAGE0 (#82) + порт #1FFD
Fast RAM-страница (#F0..#FF) выбирается в PAGE0 и подключается на место ПЗУ
в окно 0 через спец-порт #1FFD:
; выбрать физическую страницу Fast RAM в PAGE0
LD A, #F0 ; номер страницы Fast RAM
OUT (#82), A ; PAGE0 = страница в окно 0
LD A,1 ; 1 → ОЗУ (выбранная страница) в #0000..#3FFF
LD BC,#1FFD
OUT (C),A
; ...
LD A,0 ; 0 → вернуть ПЗУ в #0000..#3FFF
LD BC,#1FFD
OUT (C),A
- Порты PAGE:
PAGE0=#82,PAGE1=#A2,PAGE2=#C2,PAGE3=#E2. Чтение порта PAGE возвращает текущий номер страницы. - Эти адреса портов формально могут отличаться в других конфигурациях ППЛМ —
правильнее запрашивать их у BIOS и сверять (см.
docs/part2/bios_doc.txt, ~строка 1033).
4. Преимущества
- Скорость без wait-state. Главное и единственное предназначение — код и данные исполняются на полной частоте 21 МГц без тактов ожидания, в отличие от основного DRAM.
- Идеально для горячих участков. Внутренние циклы, lookup-таблицы, временные буферы рендера — то, к чему обращаются интенсивно и многократно.
- Отдельный массив. Не отнимает страницы основного 4 МБ ОЗУ и не пересекается с видео-областью.
5. Ограничения и подводные камни ⚠️
Это самая важная часть — Fast RAM небезопасна в обращении и легко даёт «молча не работает».
-
Акселератор НЕ работает с Fast RAM. Акселератор поддерживает пересылку блоков только для основного ОЗУ и видео-ОЗУ. Пересылку ROM и FastRAM он не поддерживает. То есть нельзя использовать accel-Fill/Copy для заполнения или копирования в/из Fast RAM — только обычные
LD-циклы процессора. -
Содержимое не сохраняется между процессами. Fast RAM может быть использована другими программами. При запуске любого процесса через DSS (а также самим механизмом переконфигурирования ППЛМ) содержимое Fast RAM может быть затёрто. Нельзя рассчитывать на персистентность данных между вызовами системы.
-
Перед вызовами DSS и BIOS Fast RAM надо ОТКЛЮЧАТЬ. Системные функции рассчитывают на стандартную карту памяти (ПЗУ в окне 0). Вызывать
RST 10h(ESTEX/DSS) илиRST 8(BIOS) при включённой Fast RAM в окне 0 — нельзя. -
Прерывания. Fast RAM (способ A) подключается в окно 0, перекрывая ПЗУ и системный вектор. Если используются прерывания, программа обязана установить свой обработчик по адресу
#0038. На практике работу с Fast RAM ведут сDI, а на время ожидания кадра/haltFast RAM временно выключают и восстанавливают (см. §6). -
Окно 0 занято под DSS. В нашем C-toolchain'е окно 0 (
#0000..#3FFF) — это ESTEX/DSS система (см.release_docs/ru/platform_reference.md). Подключение Fast RAM в окно 0 вытесняет именно её, что усиливает требование п.3. -
Конфликт
#FBс COVOX. См. §3, способ A.
6. Канонический паттерн temporary-off / restore
Из реального резидента (docs/samples/sprinterIntLib.asm): перед ei: halt
(ожидание кадрового прерывания) Fast RAM временно выключается, после —
восстанавливается прежнее состояние:
_intWaitVsyncSys
call memCacheOffTemporary ; временно выключаем Fast RAM
ei
halt
jp memCacheRestoryState ; восстанавливаем прежнее состояние подключения
Идея паттерна: библиотека хранит флаг «было ли Fast RAM включено», умеет
безопасно его снять на время системных операций (прерывания, DSS/BIOS) и вернуть
обратно. При интеграции в C-toolchain эту логику следует обернуть так же:
сохранять состояние, отключать вокруг любого RST/halt, восстанавливать.
7. Выводы для нашего C-toolchain (SDCC + target-слой)
- Из коробки сейчас не используется. В
runtime/,lib/,libc/обращений к Fast RAM нет (порт#FB/#7Bнигде не задействован под эту задачу). - Где могло бы пригодиться: разместить «горячую» функцию или таблицу в Fast RAM для ускорения. Но 64 КБ перекрывают окно 0, конфликтуют с DSS и не переживают системные вызовы — это узкоспециализированный, ручной режим, не кандидат на общий механизм линковки.
- Реалистичный сценарий: короткий самодостаточный inner-loop без вызовов
системы, с
DI, со своим вектором#0038, скопированный в Fast RAM обычнымLD-циклом (не акселератором), исполняемый из окна 0, с гарантированным восстановлением карты памяти перед любымRST. - Несовместимость с акселератором означает, что для графики/блочных операций Fast RAM бесполезна — там выигрывает accel по основному/видео-ОЗУ.
Если будем добавлять поддержку — делать это отдельным opt-in механизмом (по аналогии с banked-режимами), с обязательной обёрткой off/restore вокруг всех точек входа в систему.
8. Разбор двух предложений по спрайтовому движку (обсуждение 2026-07-14)
Итог двух обсуждений (эмуляция FastRAM в dev-MAME подтверждена разработчиками MAME как полная).
8.1. Предложение «W0/FastRAM как код-банк __banked»
Идея: выделить 1-2 FastRAM-страницы в crt0, научиться размещать в них
__banked-код с целью W0 и контрактом «такой код не зовёт DSS/BIOS»;
критичным функциям указывать размещение в W0/FastRAM.
Framing правильный (переиспользовать трамплин __banked +
контракт), но W0-банк — НЕ «ещё один bank id», а тяжелее по трём осям:
- Своп W0 сносит всё окно 0 с RST-векторами (ESTEX RST10, BIOS
RST8, IM1 RST38, RST0). Пока FastRAM в W0: сисколлы невозможны (это
и есть контракт — ок), но прерывание фатально (
#0038→ мусор FastRAM) → обязателенDIна весь интервал.DIбьёт ISR-счётчик кадров (см. FPS-делитель, sprite-engine-perf). - Загрузку нельзя сделать как в
crt0_banked(тамESTEX READпрямо в окно, строки 165-173): сам READ вектрится через W0. Нужен 2-шаг — READ в DRAM-буфер, затемDI+ FastRAM в W0 +LD-копия (акселератор с FastRAM не работает!) + выкл. Плюс FastRAM затирается запуском дочернего DSS-процесса — нужна перезагрузка. - 16 КБ окно против 64 КБ: кросс-страничные вызовы внутри FastRAM тянут вложенный W0-своп. Вызовы в W1/W2/W3 — свободны (те окна замаплены).
Плюс пер-вызовная такса трамплина (~40-60Т: DI/toggle/EI/restore)
съедает выигрыш именно на горячих inner-листьях, которые от FastRAM
выигрывают больше всего. Per-function __banked — неправильная
гранулярность.
Выгода мала: FastRAM ускоряет только выборку инструкций. Из бюджета спрайта 19.5К (blit 9.7 + heal 6.4 + тик 3.0) blit и heal — акселератор/видео-ОЗУ, не выигрывают; выигрывает только тик+сорт по fetch: ~15-20К/кадр ≈ +1 спрайт (3-4% бюджета в самом узком месте).
Рекомендация: если вообще трогать — модель «горячий остров», а не
«функция-в-банке»: одна 16-КБ FastRAM-страница с самодостаточным
кластером (тик + сортировщик + их листья), вход один раз за кадр через
единственную обёртку DI / своп-в / вызов / своп-обратно / EI, внутри
острова обычные call, никаких RST. Убирает таксу, сводит связку с
прерываниями к одному DI-интервалу на кадр (мы и так HALT-ждём между
кадрами; ISR-счётчик кадров тикает во время HALT-ожидания ПОСЛЕ EI, а
не во время острова → совместимо с FPS-делителем, пока остров < 1
кадра).
8.2. Данные в FastRAM вместо кода?
Вывод неожиданный: для нашей нагрузки данные — более СЛАБЫЙ рычаг,
чем код. Wait-state вставляется на КАЖДЫЙ доступ к DRAM, а на Z80
большинство обращений к памяти — выборка инструкций, не операндов. Тик
= IX-индексный код (ld a,(ix+d) — DD-префикс: 3 байта fetch + 1 байт
данных) → ¾ штрафа на fetch, ¼ на операнде. Значит sprite_t в FastRAM
убирает лишь ¼, а код тика — ¾.
По данным движка:
- Пиксели/атласы — жёсткое НЕТ (их читает акселератор; с FastRAM не работает → блит откатился бы на LD-циклы).
- Массив
sprite_t— не стоит: тик его трогает, но выигрыш мал (см. выше), а хуже того — его читают и heal/blit под акселератор, которым нужна нормальная карта памяти.sprite_tв W0-FastRAM → весьsprite_updateподDI+ непроверенный вопрос «работает ли акселератор при W0=FastRAM». Не кандидат, пока это не подтверждено артефактом в MAME. - Таблица Y-сорта + чисто-CPU скретч — да, но не отдельное решение: живут в той же 16-КБ странице «горячего острова» с кодом сортировщика (сорт не трогает ни акселератор, ни сисколлы). Отдельного рычага «данные» тут нет.
Единственный сильный независимый кейс для данных-в-FastRAM: CPU-bound алгоритм с большим LUT случайного доступа (fetch внутреннего цикла крошечный, чтений таблицы — тьма). У движка такого нет (тяжёлая графика = акселератор). Но если добавим CPU-side эффект (программный per-pixel шейдинг, палитровый remap, софт-скейлер) — его LUT станет главным кандидатом на FastRAM-данные. На будущее.
Ограничения именно для FastRAM-данных: лежат в окне 0
(#0000-#3FFF) → буфер под BIOS/ESTEX туда нельзя (нужно #4000-#BFFF);
стек остаётся в W2; не переживают дочерний DSS-процесс; заполняются
только LD-копией (акселератор мимо).
8.3. Общий вердикт
Оба варианта реализуемы, но бьют по 3-4% бюджета в самом узком месте
(тик+сорт fetch), мимо доминирующих blit/heal. Остаётся последним
резервом (fast_ram в памяти). Перед любой реализацией — дешёвый
бенч в dev-MAME: один и тот же CPU-loop в DRAM vs FastRAM по
totalcycles, чтобы измерить реальный коэффициент wait-state, который
эмулятор моделирует, и подтвердить, что овчинка стоит выделки.