2c6f4e33c3
_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>
290 lines
20 KiB
Markdown
290 lines
20 KiB
Markdown
# 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, который
|
||
эмулятор моделирует, и подтвердить, что овчинка стоит выделки.
|