← Модуль 2/Картридж и мапперы
EN
Модуль 2 · Console Wars

Картридж, мапперы и тайл-движки

Процессор видит 64 КБ, а игра весит 512 КБ — как? Картридж оказался не «кассетой с данными», а активным железом, которое подкачивает ROM в адресное окно. Это console-side виртуальная память, отлитая в кремнии на самой игре.
~17 мин
Суть за 20 секунд
6502 в NES имеет 16-битную шину адреса → видит максимум 2¹⁶ = 64 КБ, из которых на ROM-окно отведено лишь 32 КБ. Но игры росли до сотен килобайт и мегабайтов. Решение — маппер (MMC, чип на картридже): он bank-switching, по записи в «магический» адрес перекидывает нужный 16/8-КБ кусок ROM в фиксированное окно процессора. Это ровно пейджинг/виртуальная память: маленькое окно над большим хранилищем + таблица переадресации. 16-битный скачок (Genesis/SNES) добавил адресного пространства, несколько фоновых слоёв (параллакс) и больше цветов; а в пределе картридж нёс сопроцессор (SuperFX), то есть буквально доставлял ускоритель вместе с задачей.

Механизм

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

Стена адресного пространства

У 6502 шина адреса 16 бит. Сколько разных адресов он вообще может назвать:

216 =65536 байт =64КБ

И это весь мир процессора: туда же ложатся RAM, регистры PPU и APU, ввод. На программный ROM картриджа отведено окно $8000–$FFFF — 32 КБ. Игра на 512 КБ физически не помещается в то, что CPU способен назвать одним адресом. Простой ROM (NROM, как у первого Super Mario Bros) и не пытается: он умещается в 32 КБ кода + 8 КБ графики, и точка. Чтобы расти, нужен трюк.

Bank switching — маппер как librarian

Картридж несёт собственный чип — маппер (Memory Management Controller, MMC). У него есть регистр банка. Игра записывает в него номер, и маппер перекоммутирует проводами, какой 16-КБ кусок физического ROM виден сейчас в окне $8000. Сколько банков в игре на 512 КБ при окне 16 КБ:

512КБ 16КБ =32банка (25=32,хватит 5-битного регистра)

Книга на десятки глав, но стол — на одну: librarian (маппер) подкладывает нужную главу в окно по требованию. Тот же приём отдельно для графики: CHR-banking листает страницы pattern table (наборы плиток/спрайтов), поэтому анимации и тайлсеты тоже перестают упираться в 8 КБ.

адрес 6502 (64 КБ) $0000 RAM/IO $8000 банк↺ $C000 фикс. окно — одно; ROM — много маппер регистр банка = 5 физический ROM банк 0 банк 1 … банк 5 ✓ … банк 31 записал «5» в регистр → банк 5 виден в $8000 «адресуй больше, чем держишь»: окно + таблица переадресации = пейджинг
Регистр банка маппера — это запись таблицы страниц: номер банка → какой кусок ROM лежит в окне. Точно как virtual page → physical frame.

Маппер участвует в рендере: scanline-IRQ

Поздние мапперы делали больше, чем листали страницы. MMC3 добавил счётчик строк развёртки: он подсматривает за линией A12 на шине PPU (она переключается по фиксированному паттерну, пока PPU тянет плитки каждую строку), считает строки и в выбранной дёргает IRQ процессора. Зачем: разрезать кадр. Прокручиваешь игровое поле сверху, а на середине экрана ловишь IRQ и фиксируешь нижнюю полосу — статус-бар (счёт, жизни) стоит как вкопанный, пока мир скроллится. Super Mario Bros 3 этим и живёт. Картридж тут — не хранилище, а активный соавтор кадра.

16-битный скачок и слои

Genesis и SNES расширили шину и память — и адресного пространства, и видеопамяти стало кратно больше. Сравнение поколения (числа сверены):

ПараметрNES (1983)Genesis (1988)SNES (1990)
CPU6502 @ 1.79 МГц68000 @ 7.67 МГц65C816 @ 3.58 МГц
RAM (work)2 КБ64 КБ128 КБ
Палитра / на экране56 / 25512 / 6132768 / 256
Фоновые слои12до 4
Картридж32–512 КБ256 КБ–5 МБ256 КБ–6 МБ

Главное здесь не «больше мегагерц», а несколько независимых фоновых слоёв. В m01 экран был одной тайловой картой (nametable). Теперь их несколько, у каждого свой регистр прокрутки и приоритет относительно спрайтов. Дальний слой (небо/горы) скроллишь медленнее ближнего — и почти бесплатно получаешь параллакс, иллюзию глубины. Спрайты вкладываются между слоями по приоритету: герой за столбом, но перед стеной.

слой 2 · небо (скролл ×0.25) слой 1 · горы (×0.5) слой 0 · земля (×1.0) → кадр: слои + спрайт по приоритету
Разная скорость прокрутки слоёв = глубина даром. Один nametable из m01 превратился в стек слоёв с приоритетом.

Числовой пример. MMC3 разрезает кадр scanline-IRQ. Видимых строк 240; ставим прерывание на строке 192 — верхние 192 строки прокручиваются (фон сдвигается каждый кадр), на 192-й IRQ обнуляет регистр прокрутки, и нижние 240 − 192 = 48 строк рисуются с нулевым сдвигом → статичный HUD высотой 48 px. Один счётчик на картридже разделил экран на «мир» и «приборную панель».

🕹 В какие игры поиграть — и что заметить

Один вопрос «как уместить большую игру в маленький процессор» — лесенка ответов: от «никак, влезли в 32 КБ» через мапперы и слои до «привезли на картридже целый GPU». По каждому: как сделано и что включить/заметить.

Super Mario Bros 1985 · NROM, маппера нет

Влез без банкинга: 32 КБ кода + 8 КБ CHR. Поэтому мир построен на жёстком переиспользовании одного набора плиток (куст = облако, перекрашенные палитрой) — банков нет, тайлсет один на всю игру. Это потолок «без маппера» во плоти.

🎮 Сыграй: в SMB замечай, как мало уникальных плиток и как агрессивно они повторяются между мирами. Это не стиль ради стиля — это 8 КБ CHR без подкачки.

Super Mario Bros 3 / Mega Man 1988–90 · MMC3, scanline-IRQ

MMC3 даёт тонкий банкинг (8-КБ страницы) и счётчик строк. SMB3 им режет кадр: верх скроллится, нижняя полоса-HUD стоит намертво. Mega Man листает CHR-банки ради анимации боссов и больших тайлсетов уровней. Игра внезапно «большая», хотя процессор тот же.

🎮 Сыграй: в SMB3 смотри на нижнюю панель (предметы/время): мир за ней едет, она — нет. Это IRQ маппера на конкретной строке. В эмуляторе с дебаггером увидишь точную строку прерывания.

Sonic the Hedgehog 1991 · Genesis · «blast processing» и 2 слоя

16-битный 68000 быстрее и с DMA толкает больше пикселей: высокая скорость скролла + два фоновых плана с параллаксом. Маркетинговое «blast processing» — это и есть «процессор быстрее, DMA шире», никакого тайного граф-чипа. Реальный эффект, выдуманное имя.

🎮 Сыграй: в Sonic на скорости гляди на задний план — он ползёт медленнее переднего (параллакс из двух плоскостей). Сравни ощущение скорости с NES-играми: разница — это шире шина и DMA, а не магия.

Star Fox 1993 · SNES · картридж с сопроцессором SuperFX

Предел идеи: маппер мало — положим на картридж целый процессор. SuperFX (GSU) от Argonaut — RISC-ускоритель, рисующий 3D-полигоны в свой кадровый буфер; SNES сам так не умеет. Картридж перестаёт быть памятью и становится железом-ускорителем, едущим вместе с игрой (так же SA-1, DSP, Cx4 в других играх).

🎮 Сыграй: запусти Star Fox — это «3D» на машине без 3D, потому что чип внутри картриджа. Сравни плавность с Mode-7-играми (F-Zero) на голом SNES: там аффинный трюк, тут — настоящие полигоны от привезённого GPU.

Хардкор · инженерия: почему окно фиксировано, а вектор прерываний — нетможно пропустить

Bank switching выглядит магией, пока не спросишь: а из какого банка исполняется код, который меняет банк?

Фиксированный банк — это «трамплин»

У MMC1 окно $8000–$BFFF переключаемое, а $C000–$FFFF жёстко прибито к последнему банку. Туда кладут: вектора reset/IRQ/NMI (они физически в $FFFA–$FFFF), процедуру смены банка и общий «диспетчер». Нельзя выдёргивать половицу, на которой стоишь: код, переключающий банк, обязан жить в немигрирующей части. Поэтому вызов «функции из банка 7» — это: записать 7 в регистр (находясь в фиксированном банке) → прыгнуть в окно. Ровно как trampoline/PLT в динамической линковке.

Откуда чип на картридже знает про строку развёртки

MMC3 не видит ТВ. Он слушает линию A12 шины PPU. Пока PPU рисует строку, он лезет в pattern table по адресам, где A12 предсказуемо щёлкает; маппер ловит фронты A12, фильтрует дребезг (низкий уровень в течение трёх спадов M2) и считает их как строки. То есть картридж подслушивает адресную шину видеочипа и реконструирует тайминг, к которому формально не подключён. Хрупко (зависит от паттерна обращений PPU), но работает.

Почему не «просто больше адресных линий»

Расширить шину 6502 до 24 бит нельзя — она часть ISA процессора, который в каждой консоли уже впаян. Менять можно только то, что на картридже. Отсюда вся философия: ум выносится в сменное железо, неизменное ядро не трогаем. Это и есть граница «платформа против расширения».

Хардкор · теория: bank switching = пейджинг, и где он ломаетсяможно пропустить

Регистр банка — это одна запись таблицы страниц: logical address → physical frame. Разница с настоящим MMU — в гранулярности и прозрачности.

Гранулярность

MMU процессора разбивает всё адресное пространство на страницы (4 КБ) с таблицей на сотни записей; маппер даёт 1–4 переключаемых окна по 8–16 КБ. Это «пейджинг для бедных»: меньше окон, грубее куски, но идея тождественна.

Прозрачность и кто отвечает за промах

В ОС промах страницы (page fault) ловит железо и прозрачно подгружает страницу — программа не знает. На NES прозрачности нет: сам код обязан знать, что нужный кусок не в окне, и явно переключить банк. Это ближе к overlay-системам мейнфреймов (ручная подкачка оверлеев), чем к demand paging. Цена ошибки — прыжок в чужой банк и краш; отладка таких багов на железе 80-х была адом.

Стоимость переключения

MMC1 принимает команду серийно — 5 записей со сдвигом на команду; MMC3 — 2 записи (быстрее). Частый банк-свич в горячем цикле дорог, поэтому раскладка кода по банкам — это оптимизация локальности: что вызывается вместе, держи в одном банке. Прямой предок «cache locality» и data-oriented раскладки (см. ECS).

Аналогия
Адресное пространство CPU — маленький стол на одну открытую книгу. ROM игры — огромная библиотека. Маппер — библиотекарь: ты не держишь все тома на столе, но называешь номер — и нужная глава мгновенно лежит перед тобой. Ты никогда не видишь библиотеку целиком, зато любая страница — в одном «запросе банка». А SuperFX-картридж — это когда к книге прилагается собственный переводчик, потому что сам ты этот язык не читаешь.
Почему это важно
Паттерн «адресовать больше, чем влезает в окно, через таблицу переадресации» — это не музей NES, а фундамент того, как машины вообще работают с памятью больше доступной: виртуальная память, пейджинг, mmap, своп. А «привезти ускоритель вместе с задачей» (SuperFX) — это вся история GPU, FPGA и smart-NIC. Понимая картридж, ты понимаешь, почему vLLM пейджит KV-кэш и почему MoE подгружает нужного эксперта: те же два хода, новый словарь.
🔁 За пределами игр — куда это переносится
Урок даёт два переносимых приёма: фиксированное окно + таблица переадресации (адресуй больше, чем держишь) и доставка ускорителя вместе с рабочей нагрузкой.

ОС / системы: bank switching — это виртуальная память и пейджинг в чистом виде: регистр банка = запись таблицы страниц, банк = physical frame, окно $8000 = страница виртуального адреса. Оверлеи мейнфреймов, mmap, своп, сегменты x86 real-mode — родня. CHR-banking ⇄ стриминг текстур из дальнего хранилища в видеопамять по требованию.

ML / AI (твой домен): vLLM PagedAttention — дословно bank switching для KV-кэша: логическая block-table проецирует последовательность на разбросанные физические блоки GPU-памяти, фрагментация падает с 60–80% до <4%. Model offloading / ZeRO-Offload — пейджинг весов диск→CPU→VRAM, когда модель не влезает в окно VRAM. Mixture-of-Experts — «подкачай нужного эксперта на токен» = CHR-banking для весов. А SuperFX = аппаратный ускоритель рядом с задачей: GPU/TPU/inference-чип — это «сопроцессор на картридже» для нейросети.

Бэкенд / данные: окно над большим стором — это пагинация запросов (LIMIT/OFFSET, курсоры), working-set кэши, CDN-edge как «банк», подкачиваемый из origin; indirection-таблица = слой адресации (consistent hashing, shard map).

Принцип: когда хранилище больше окна — не пытайся вместить всё; заведи маленькое окно и таблицу, что в нём сейчас лежит. А дорогую специальную работу выноси в ускоритель, едущий вместе с данными.

🔧 Запусти и поковыряй — на домашнем компе
Во что играть — выше (🕹). Здесь — залезть в маппер через эмулятор с дебаггером:
🔧 Поковырять (debug) ~40 мин, Mesen
Открой Mesen, загрузи Super Mario Bros 3 (MMC3). В отладчике найди scanline-IRQ: поставь брейкпоинт на IRQ-вектор — поймаешь его срабатывание ровно на строке разреза HUD. Открой просмотр PRG/CHR-банков: переключай уровни и смотри, как игра пишет в регистры маппера, листая CHR-страницы. Сравни с NROM-игрой (SMB1) — там банкинга нет вообще, регистры маппера мертвы.
🧪 Потестить (глазами QA) ~15 мин
Ищи следы банкинга и слоёв: «дрожание» строки разреза HUD при лагах (IRQ не успел), tearing/мигание на границе слоёв, повтор тайлов внутри банка vs смена набора между зонами. В Genesis-игре отдели параллакс-слои глазами: какой ползёт медленнее. Отметь, где экономия на банках стала видимым стилем.
Чеклист: увидел scanline-IRQ на разрезе HUD; поймал запись в регистр банка при смене зоны; отделил параллакс-слой по скорости.
Связи
основа
Железо-ограничения — тайлы, спрайты, nametable и палитра отсюда; этот урок их масштабирует: банкинг снимает потолок 8 КБ CHR, слои размножают единственный nametable.
основа
Игровой цикл — scanline-IRQ привязан к развёртке кадра; маппер вклинивается в тайминг рендера между обновлениями.
дальше
SNES Mode 7 — следующий шаг PPU: аффинное преобразование фонового слоя, псевдо-3D без сопроцессора (F-Zero), а Mario Kart — уже с DSP-1.
контраст
Doom и BSP — PC пошёл другим путём: плоская память, софт-рендер, без мапперов и фиксированных окон. Сравни философии «расширяй картридж» против «у нас просто много RAM».
Вопросы пытливого ума
Если 6502 видит лишь 64 КБ, как игра вообще исполняет код во всех 512 КБ — всегда же есть кусок, недостижимый прямо сейчас?
Да, и в этом весь смысл: процессор никогда не видит всё сразу. В каждый момент в окне — один банк. Код, который переключает банк, обязан жить в фиксированном (немигрирующем) банке вместе с векторами прерываний — иначе он выдернул бы половицу из-под себя. Вызов «функции из банка 7» = записать 7 в регистр (из фиксированного банка) и прыгнуть в окно. Достижим любой кусок — но через явное переключение, а не «всё доступно одновременно».
MMC3 «считает строки развёртки» — как чип на картридже знает, что рисует ТВ, к которому не подключён?
Он подслушивает шину PPU, конкретно линию адреса A12. Пока PPU тянет данные плиток для очередной строки, A12 щёлкает по предсказуемому паттерну; маппер ловит её рост (после фильтра дребезга — низкий уровень три спада M2) и считает как строки. Формально маппер к видеовыходу не подключён — он реконструирует тайминг из обращений к памяти. Гениально и хрупко: зависит от того, как именно PPU ходит в pattern table.
«Blast processing» у Genesis — это был реальный чип?
Нет. Это маркетинговый ярлык для банального «68000 быстрее NES-ного 6502, а DMA шире, поэтому можно гнать больше спрайтов, выше разрешение и быстрый скролл». Никакого выделенного граф-ускорителя «blast» не существовало. Эффект на экране настоящий (скорость Sonic), имя — выдуманное. Полезный урок: отличай измеримую способность железа от названия в рекламе.
Раз картридж мог нести сопроцессор (SuperFX), почему не совали CPU в каждую игру?
Экономика, не возможность. Чип сидит в каждом проданном картридже — это переменная себестоимость на единицу, а не разовая стоимость консоли, размазанная по всей базе. SuperFX заметно удорожал картридж, поэтому его ставили лишь туда, где он окупался эффектом (Star Fox). Тот же расчёт сегодня: не каждый запрос заслуживает GPU-инференса — дорогой ускоритель достают, лишь когда дешёвый путь принципиально не тянет.
CD-ROM давал сотни мегабайт дёшево — почему он не убил картридж сразу?
Потому что ёмкость — не единственная ось. CD — пассивное хранилище с большой задержкой: случайный доступ требует физического перемещения головки (десятки–сотни мс), и никакой логики на диске нет. Картриджный ROM — мгновенный random-access и может нести кремний (мапперы, сопроцессоры). Поэтому диск выиграл по объёму (FMV, аудио), но проигрывал по латентности и «активности», и ранние CD-консоли мучились загрузками. Тот же трейд возвращается всюду: быстрый+активный носитель против ёмкого+пассивного.
Что почитать