# 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` — мусор, важен сам факт обращения): ```asm 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`: ```asm ; выбрать физическую страницу 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 временно выключается, после — восстанавливается прежнее состояние: ```asm _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, который эмулятор моделирует, и подтвердить, что овчинка стоит выделки.