Files
Sprinter-SDCC/docs/fast_ram.md
T
snark13 2c6f4e33c3 libc/irq: цепочка кадровых обработчиков + all-modes W1-remap
_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>
2026-07-15 10:23:39 +03:00

20 KiB
Raw Blame History

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. Преимущества

  1. Скорость без wait-state. Главное и единственное предназначение — код и данные исполняются на полной частоте 21 МГц без тактов ожидания, в отличие от основного DRAM.
  2. Идеально для горячих участков. Внутренние циклы, lookup-таблицы, временные буферы рендера — то, к чему обращаются интенсивно и многократно.
  3. Отдельный массив. Не отнимает страницы основного 4 МБ ОЗУ и не пересекается с видео-областью.

5. Ограничения и подводные камни ⚠️

Это самая важная часть — Fast RAM небезопасна в обращении и легко даёт «молча не работает».

  1. Акселератор НЕ работает с Fast RAM. Акселератор поддерживает пересылку блоков только для основного ОЗУ и видео-ОЗУ. Пересылку ROM и FastRAM он не поддерживает. То есть нельзя использовать accel-Fill/Copy для заполнения или копирования в/из Fast RAM — только обычные LD-циклы процессора.

  2. Содержимое не сохраняется между процессами. Fast RAM может быть использована другими программами. При запуске любого процесса через DSS (а также самим механизмом переконфигурирования ППЛМ) содержимое Fast RAM может быть затёрто. Нельзя рассчитывать на персистентность данных между вызовами системы.

  3. Перед вызовами DSS и BIOS Fast RAM надо ОТКЛЮЧАТЬ. Системные функции рассчитывают на стандартную карту памяти (ПЗУ в окне 0). Вызывать RST 10h (ESTEX/DSS) или RST 8 (BIOS) при включённой Fast RAM в окне 0 — нельзя.

  4. Прерывания. Fast RAM (способ A) подключается в окно 0, перекрывая ПЗУ и системный вектор. Если используются прерывания, программа обязана установить свой обработчик по адресу #0038. На практике работу с Fast RAM ведут с DI, а на время ожидания кадра/halt Fast RAM временно выключают и восстанавливают (см. §6).

  5. Окно 0 занято под DSS. В нашем C-toolchain'е окно 0 (#0000..#3FFF) — это ESTEX/DSS система (см. release_docs/ru/platform_reference.md). Подключение Fast RAM в окно 0 вытесняет именно её, что усиливает требование п.3.

  6. Конфликт #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», а тяжелее по трём осям:

  1. Своп W0 сносит всё окно 0 с RST-векторами (ESTEX RST10, BIOS RST8, IM1 RST38, RST0). Пока FastRAM в W0: сисколлы невозможны (это и есть контракт — ок), но прерывание фатально (#0038 → мусор FastRAM) → обязателен DI на весь интервал. DI бьёт ISR-счётчик кадров (см. FPS-делитель, sprite-engine-perf).
  2. Загрузку нельзя сделать как в crt0_banked (там ESTEX READ прямо в окно, строки 165-173): сам READ вектрится через W0. Нужен 2-шаг — READ в DRAM-буфер, затем DI + FastRAM в W0 + LD-копия (акселератор с FastRAM не работает!) + выкл. Плюс FastRAM затирается запуском дочернего DSS-процесса — нужна перезагрузка.
  3. 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, который эмулятор моделирует, и подтвердить, что овчинка стоит выделки.