Функциональная симуляция картриджных PCB (печатных плат) NES/Famicom/Dendy.
Картридж симулируется как физическая плата: набор компонентов (чипы памяти, чипы-мапперы, дискретная логика) и проводов (сетей, nets), соединяющих их друг с другом и с краевым разъёмом картриджа (edge connector). Описание платы — это данные, JSON-документ, а не код на C++.
CartPcb — преемник выведенного из эксплуатации компонента Mappers (issue #509). Он
полностью заменил Mappers и решил все проблемы, перечисленные в старом
Mappers/Readme.md.
1. Мотивация
У выведенного из эксплуатации компонента Mappers накопились следующие проблемы, и все их
CartPcb решает:
| # | Проблема (из старого Mappers/Readme.md) | Как это решает CartPcb |
|---|---|---|
| 1 | Работа с дампами памяти (PRG/CHR) непонятна: область CHR называется «CHR-ROM», а PRG вообще не поддерживается. | CartPcb работает с явными дампами образов: CartImage несёт образы PRG и CHR с размерами и именами из определения PCB. CHR — это просто образ CHR; «CHR-ROM» больше ничем не называется. |
| 2 | Эмуляция .nes-мапперов хаотична: перевод номера iNES-маппера в компоненты платы без возможности выразить множество аппаратных вариаций одного и того же маппера. | CartPcb ориентирован на PCB и управляется данными. Плата — это JSON-документ со списком компонентов и разводки. Один и тот же чип-маппер (например, MMC1), разведённый иначе, — это другой board JSON, а не форк кода на C++. |
| 3 | Нет поддержки чипов ROM — всё это сырой байтовый массив. | CartPcb использует RomChip (ROM в стиле JEDEC, Common/BaseBoardLib) и переработанный SRAM (Common/BaseBoardLib) с семантикой /CE / /OE / /WE / адрес / данные / dz. |
| 4 | Эмуляцию MMC1 нужно отлаживать; делитель, вероятно, неверен. | MMC1 был выделен из Mappers в Chips как самостоятельный класс чипа со своими юнит-тестами. CartPcb только разводит чипы на платах; он не реализует чипы. |
Кроме того, issue #508 задаёт следующие архитектурные мотивы:
- Никакой зависимости от iNES для идентификации. Заголовки iNES и номера iNES-мапперов жёсткие и теряют информацию (у многих реальных плат общий номер маппера, но разная электрика).
- Идентификация через nescartdb. Тип картриджа/PCB определяется по CRC32 образов PRG/CHR через базу nescartdb (сконвертирована в JSON; см.
Nescartdb/Readme.md). - JSONES становится подмножеством. Пользовательский PCB JSON (формат одного картриджа, см. §6.5) — валидный вход CartPcb, поэтому исследователи могут описывать нелицензионные и недокументированные PCB (например, коллекцию krzysiobal), не касаясь iNES.
Mappersполностью выведен из эксплуатации. Контракт картриджного порта (бывшийMappers::AbstractCartridge) теперь живёт в CartPcb (см. §7.5).- MMC1 живёт в Chips. CartPcb предназначен только для симуляции PCB.
2. Охват
Что входит в охват
- Загрузка описания платы (JSON) и создание экземпляра платы: компоненты + разводка.
- Симуляция чипов памяти на шинах CPU и PPU (ROM и RAM — с батарейным питанием или без).
- Разводка дискретной логики и чипов-мапперов (реализованных в другом месте, например в
Chips) на плату. - Mirroring (жёсткая разводка или управление от маппера), гейтинг WRAM, конфликты шин — там, где они выразимы.
- Интерфейс edge-коннектора картриджа (сигнальный контракт нынешнего
Mappers::AbstractCartridge, теперь — часть CartPcb). - Идентификация: компонент
NesCartDb, сопоставляющий CRC образов PRG/CHR с типом PCB. - Поддержка отладки: дескрипторы карты памяти и провайдеры отладочной информации для каждой платы (как и раньше).
Вне охвата (осознанно)
- Реализация крупных чипов (MMC1, MMC3 и т.д.) — это работа
Chips. CartPcb потребляет чипы. - Чипы звукового расширения изначально вне охвата (разъём по-прежнему несёт звуковую линию Famicom; авторы плат могут добавить простую сквозную разводку).
- Электрическое поведение каждой экзотической платы с точностью до цикла. CartPcb моделирует функциональную схему; платы добавляются постепенно.
3. Терминология
- PCB / board — печатная плата картриджа; объект симуляции.
- Component — деталь на плате: чип ROM, чип RAM, чип-маппер, CIC, подтяжка и т.п.
- Pin — точка подключения компонента (именованная, например
n_CS,A13,D0). - Net — провод, соединяющий выводы (пины) между собой. Сети несут значения
TriState. - Edge connector — интерфейс картриджного порта с материнской платой (шина CPU, шина PPU, управляющие сигналы, звук, порт расширения).
- CartImage — сырые дампы, загружаемые в чипы ROM: образ PRG, образ CHR (и опционально батарейная RAM).
- ROM chip — микросхема постоянной памяти; большинство NES-ROM — устройства стандарта JEDEC или аналогичные (например, семейство EPROM 27xx). Моделируется классом
RomChipвCommon/BaseBoardLib. - JEDEC — индустриальное семейство распиновок микросхем памяти (
A0..An,D0..D7,/CE,/OE, опциональный/WE);RomChipиSRAMему следуют. - Board type — имя платы в nescartdb, например
HVC-SGROM,NES-NROM-256,NES-UNROM. - nescartdb — база картриджей bootgod (см.
Nescartdb/Readme.md). - JSONES — JSON-формат метаинформации одного картриджа (issue #189); подмножество формата плат CartPcb.
4. Принципы проектирования
- Данные вместо кода. Семейство плат описывается один раз, в JSON. Добавление новой ревизии PCB не должно требовать изменений в C++.
- Компоненты — это чипы; платы — это разводка. CartPcb реализует модель сетей и сборку плат. Чипы памяти (
RomChip,SRAM) живут вBaseBoardLib; всё остальное (MMC1, ...) — классы чипов, инжектируемые изChips. - Явные образы. PRG и CHR — именованные области
CartImageс заданным размером. Образы загружаются в экземплярыRomChip, а разводка платы подключает эти чипы ROM к шинам — и никогда «байтовый массив CHR-ROM». - Идентификация по содержимому, а не по заголовку. CRC32 образов PRG/CHR выбирают плату через nescartdb.
- Расширяемость пользователем. Небольшие JSON-файлы в пользовательском каталоге дополняют или переопределяют встроенные платы (путь JSONES).
- Один контракт симуляции. Сигнальный интерфейс edge-коннектора такой же, как у нынешнего
AbstractCartridge, поэтому материнские платы (NESBoard,FamicomBoard) продолжают работать без изменений.
5. Формат описания PCB (CartPcb JSON)
Плата описывается JSON-документом из двух частей:
components— состав компонентов (соответствие 1:1 содержимому платы в nescartdb:prg,chr,wram,vram,chip,cic,pad).circuit— расширение CartPcb: как компоненты разведены на шины и друг с другом. nescartdb не описывает разводку, поэтому эту часть пишут вручную (см. §5.3).
5.1 Пример: NROM-256
{
"schemaVersion": 1,
"board": {
"type": "NES-NROM-256",
"pcb": "NES-NROM-256-02",
"mapper": 0,
"system": ["NES-NTSC", "NES-PAL"],
"components": {
"prg": { "kind": "rom", "bus": "cpu", "size": 32768 },
"chr": { "kind": "rom", "bus": "ppu", "size": 8192 }
},
"circuit": {
"mirroring": { "mode": "scroll" },
"cpu": {
"prg": { "chip": "prg", "n_cs": "nROMSEL", "addr": "cpu_addr[13:0]" }
},
"ppu": {
"chr": { "chip": "chr", "n_cs": "!nPA13", "addr": "ppu_addr[12:0]" }
},
"nets": []
}
}
}
Примечания:
- Размеры задаются в байтах (
"256k"из nescartdb конвертируется в32768инструментом конвертации). cpu_addr,ppu_addrи сигналы разъёма (nROMSEL,nPA13,RnW,nRD,nWR,M2, ...) — предопределённые источники сетей; полный набор сигналов определён в §7.2.prgсn_cs = nROMSELвоспроизводит классическую NROM-дешифрацию PRG; чип CHR включается сигналом!nPA13(на платах NROM/CEчипа CHR подключён кA13PPU — низкий уровень для$0000-$1FFF;nPA13 = !A13) — ровно как в текущей реализацииNROM, но выражено данными.- Компоненты с
kind: romинстанцируются какBaseBoardLib::RomChip(в стиле JEDEC), а сkind: ram— какBaseBoardLib::SRAM.PcbFactoryзагружает образы PRG/CHR в чипы ROM; разводка подключает выводы чипов. "mirroring": { "mode": "scroll" }объявляет, что на плате есть паяная перемычка scroll (см. §5.3 и §7.4): конкретное значение scroll (H или V) — параметр экземпляра картриджа, а не платы. Загрузчик выводит его из заголовка.nes(iNES Flags6, бит 0).
5.2 Пример: плата на MMC1 (SGROM, маппер 1)
{
"schemaVersion": 1,
"board": {
"type": "HVC-SGROM",
"pcb": "HVC-SGROM-03",
"mapper": 1,
"system": ["Famicom"],
"components": {
"prg": { "kind": "rom", "bus": "cpu", "size": 262144 },
"chr": { "kind": "rom", "bus": "ppu", "size": 8192 },
"mmc1": { "kind": "chip", "chip": "MMC1" }
},
"circuit": {
"mirroring": { "mode": "mapper", "net": "mmc1.VRAM_A10" },
"cpu": {
"prg": {
"chip": "prg",
"n_cs": "mmc1.PRG_nCE",
"addr": "mmc1.PRG_A17..PRG_A14 | cpu_addr[13:0]"
}
},
"ppu": {
"chr": {
"chip": "chr",
"n_cs": "!nPA13",
"addr": "mmc1.CHR_A16..CHR_A12 | ppu_addr[11:0]"
}
},
"nets": [
{ "name": "mmc1.M2", "from": "M2" },
{ "name": "mmc1.nROMSEL", "from": "nROMSEL" },
{ "name": "mmc1.CPU_RnW", "from": "RnW" },
{ "name": "mmc1.CPU_A13", "from": "cpu_addr[13]" },
{ "name": "mmc1.CPU_A14", "from": "cpu_addr[14]" },
{ "name": "mmc1.CPU_D0", "from": "cpu_data[0]" },
{ "name": "mmc1.CPU_D7", "from": "cpu_data[7]" },
{ "name": "mmc1.PPU_A10", "from": "ppu_addr[10]" },
{ "name": "mmc1.PPU_A11", "from": "ppu_addr[11]" },
{ "name": "mmc1.PPU_A12", "from": "ppu_addr[12]" }
]
}
}
}
Это ровно та аппаратная структура, которую старый MMC1_Based реализовывал на C++ —
теперь это данные: экземпляр чипа (MMC1 из Chips), разведённый на
шины CPU/PPU, а результирующие линии адреса питают чипы ROM.
5.3 Пример: UNROM со своей клейкой логикой (74LS161 + 74LS32)
Issue #525: описание платы максимально приближено к реальной PCB — регистр банка — это чип
74LS161, а мультиплексор адреса PRG — 74LS32, счетверённый элемент
ИЛИ (ровно та разводка, что была в старом Mappers::UNROM):
{
"schemaVersion": 1,
"board": {
"type": "UNROM",
"components": {
"prg": { "kind": "rom", "bus": "cpu" },
"chr": { "kind": "ram", "bus": "ppu", "size": 8192 },
"ls161": { "kind": "chip", "chip": "LS161" },
"ls32": { "kind": "chip", "chip": "LS32" }
},
"circuit": {
"mirroring": { "mode": "scroll" },
"cpu": {
"prg": {
"chip": "prg",
"n_cs": "nROMSEL",
"addr": "ls32.Y3 | ls32.Y0 | ls32.Y1 | cpu_addr[13:0]"
}
},
"ppu": {
"chr": { "chip": "chr", "n_cs": "!nPA13", "n_oe": "nRD", "n_we": "nWR", "addr": "ppu_addr[12:0]" }
},
"nets": [
{ "name": "ls161.CLK", "from": "nROMSEL" },
{ "name": "ls161.nRST", "from": "vdd" },
{ "name": "ls161.nLD", "from": "RnW" },
{ "name": "ls161.EN_T", "from": "gnd" },
{ "name": "ls161.EN_P", "from": "gnd" },
{ "name": "ls161.P0", "from": "cpu_data[0]" },
{ "name": "ls161.P1", "from": "cpu_data[1]" },
{ "name": "ls161.P2", "from": "cpu_data[2]" },
{ "name": "ls161.P3", "from": "gnd" },
{ "name": "ls32.A0", "from": "ls161.Q1" },
{ "name": "ls32.B0", "from": "cpu_addr[14]" },
{ "name": "ls32.A1", "from": "ls161.Q0" },
{ "name": "ls32.B1", "from": "cpu_addr[14]" },
{ "name": "ls32.A2", "from": "gnd" },
{ "name": "ls32.B2", "from": "gnd" },
{ "name": "ls32.A3", "from": "cpu_addr[14]" },
{ "name": "ls32.B3", "from": "ls161.Q2" }
]
}
}
}
Примечания:
LS161тактируется сигналом/ROMSELпри/LD = R/W: запись CPU в$8000-$FFFFзагружает шину данных в регистр банка по нарастающему фронту/ROMSEL— в тот же момент, когда текущая модель на latch захватывала банк.LS32собирает биты банка сA14CPU (классический трюк UxROM): в окне$8000биты банка доходят до адреса PRG без изменений, а в окне$C000операция ИЛИ сA14 = 1переводит биты банка в «все единицы», то есть выбирает последний 16-КиБ банк.- Обобщённый вид компонента
latchпо-прежнему поддерживается как упрощение для пользовательских (JSONES) плат, но встроенные платы UxROM/AxROM теперь используют настоящие чипы.
5.4 Секция circuit (расширение CartPcb)
Так как nescartdb описывает только состав компонентов, CartPcb добавляет секцию circuit.
Её точный язык выражений финализируется в issue по реализации; концепции таковы:
- Подключения к шинам (
cpu/ppu): для каждого компонента памяти (RomChipилиSRAM) задаются источники chip-select (n_cs), output-enable (n_oe), write-enable (n_we) и адреса. Источники адреса могут комбинировать биты шины и выходы чипов (mmc1.PRG_A17..PRG_A14 | cpu_addr[13:0]) — так выражается переключение банков. Конкатенация идёт от старших битов (MSB-first): левый операнд занимает самые старшие биты адреса, поэтомуPRG_A17..PRG_A14MMC1 (по убыванию) отображаетPRG_A14на бит адреса 14. - Mirroring (Scroll): у платы нет атрибута «Mirroring» (issue #525). Плата либо
- имеет паяную перемычку scroll —
"mirroring": { "mode": "scroll" }— физическую перемычку, выбирающую H Scroll (VRAM_A10 = PA10) или V Scroll (VRAM_A10 = PA11). Значение scroll — параметр экземпляра картриджа, извлекаемый из заголовка.nes(iNES Flags6, бит 0, инвертированный) или из контактных площадок nescartdb; либо - ведёт
VRAM_A10с выхода чипа-маппера —"mirroring": { "mode": "mapper", "net": "mmc1.VRAM_A10" }— и параметр Scroll ей не нужен; либо - оставляет mirroring незаданным (платы без разводки VRAM).
"mode": "hardwired"(с полямиh/v) принимается как алиас совместимости для"scroll"; егоh/vстановятся значением по умолчанию фабрики. - имеет паяную перемычку scroll —
- Nets: именованные соединения выводов чипов с сигналами шин — плоский список пар
name/from. Любая сеть может использоваться как источникn_cs/n_oe/n_we/ адреса / mirroring, поэтому произвольная дискретная логика (например, дешифраторы на LS139, дешифрацияnROMSEL/A14в UNROM) выразима без нового кода на C++.
Образы памяти загружаются в чипы ROM; подключения к шинам описывают, как выводы каждого чипа ROM
(JEDEC: A0..An, /CE, /OE, D0..D7) соединяются с
шиной и выходами чипов.
Модель списка соединений намеренно проста (сети несут TriState; тайминги фронтов
posedge/negedge живут внутри классов чипов). Это сохраняет описания плат декларативными, а тайминги
чипов — в симуляторах чипов.
6. Архитектура выполнения
6.1 Модули
CartPcb/ (new top-level component, C++/native)
CartPcb.h public header
Pcb.h / Pcb.cpp the simulated board
PcbFactory.h/.cpp JSON -> Pcb
PcbLoader.h/.cpp locate & parse PCB JSON (built-in Nescartdb + user dir)
NesCartDb.h/.cpp CRC32 -> board type lookup (nescartdb index)
InesTranslator.h/.cpp iNES-header fallback for "wild" dumps (issue #514)
CartImage.h PRG/CHR dumps + battery RAM
Readme.md this specification
Зависимости: Common/BaseLogicLib (TriState), Common/JsonLib (разбор JSON),
Common/BaseBoardLib (RomChip — ROM в стиле JEDEC, SRAM —
статическая RAM, клейкая логика LS161/LS32), Nescartdb/
(данные) и Chips/ — для классов чипов-мапперов.
6.2 Ключевые классы
Pcb— владеет экземплярами компонентов и картой сетей;sim()ведёт все компоненты от входов edge-коннектора и выдаёт выходы. Тот же стиль «подай сигналы на вход — что-то произойдёт», что и в остальном эмуляторе.PcbFactory— собираетPcbиз board JSON иCartImage(выделяет чипы, разводит сети, проверяет размеры образов на соответствие компонентам).PcbLoader— находит JSON-документ по типу платы: сначала встроенные данныеNescartdb, затем пользовательские JSON (которые могут дополнять или переопределять).NesCartDb— один раз загружает сконвертированный JSON nescartdb и отвечает на вопрос «по данным образам PRG/CHR, какие типы плат подходят?» по CRC32 дампов PRG и CHR. Это тот компонент, который переводит CRC образов PRG/CHR в тип PCB.InesTranslator— fallback для «диких» дампов (issue #514): когда CRC образов PRG/CHR нет в nescartdb, переводит заголовок iNES (номер маппера + размеры PRG/CHR) в тип платы, чтобы homebrew шёл тем же путём плат.RomChip— находится вCommon/BaseBoardLib, а не в CartPcb. ROM-чип в стиле JEDEC: выводыA0..An,D0..D7,/CE,/OE; запись по/WEROM игнорирует.PcbFactoryзагружает образы PRG/CHR в экземплярыRomChip.- RAM —
BaseBoardLib::SRAM, переработанный так, чтобы быть явным симулятором типичных статических RAM-чипов 80–90-х (стандартные функции выводов, семантикаn_CS/n_WE/n_OE, состояние после включения питания). Используется для компонентов WRAM/VRAM, в том числе с батарейным питанием. CartImage— явные дампы PRG/CHR (плюс содержимое батарейной RAM), загружаемые в чипы ROM черезPcbFactory. Заменяет сыройuint8_t* nesImageи разбор заголовка iNES внутри классов картриджей.
6.3 Интерфейс чипа для инжектируемых чипов
Чипы-мапперы (например, MMC1) — это обычные классы чипов, следующие образцу сегодняшнего
Mappers::MMC1:
class MMC1 {
void sim(BaseLogic::TriState inputs[], BaseLogic::TriState outputs[]);
};
Список nets в board JSON определяет, какие выводы существуют и что ими управляет; класс
чипа на C++ должен предоставлять согласованную карту выводов (имя ↔ индекс входа/выхода). Контракт
имён выводов между JSON и классами чипов — часть issue по реализации.
6.4 Поток загрузки и поиска
CartImage (PRG/CHR dumps loaded into ROM chips)
|
v
NesCartDb.FindBoard(crc32(PRG), crc32(CHR)) --> board type (e.g. "HVC-SGROM")
|
v
PcbLoader.Load(board type) --> board JSON (built-in Nescartdb, or user override)
|
v
PcbFactory.Create(board JSON, CartImage) --> Pcb
|
v
CartPcbCartridge (CartPcb cartridge port) --> Board::InsertCartridge
Если совпадения в nescartdb нет и тип платы не принуждён, iNES-fallback (§8) переводит дамп из его заголовка — номер iNES-маппера и размеры PRG/CHR, — чтобы homebrew и прочие «дикие» дампы проходили тем же путём плат (issue #514).
6.5 Пользовательские PCB JSON (подмножество JSONES)
Пользователь может предоставить любой board JSON одного картриджа (§5):
- Формат — ровно формат плат CartPcb («JSONES»-файл — это просто board JSON с добавленной метаинформацией
game/cartridge, повторяющей вложенность nescartdbgame→cartridge→board). - Расположение: пользовательский каталог данных (решается в issue по реализации; кандидаты: рядом с исполняемым файлом,
%APPDATA%или папкаCustomBoards/, переданная через командную строку). - Порядок загрузки: сначала встроенные данные
Nescartdb; пользовательские JSON объединяются поверх (файл пользователя с тем жеboard.typeпереопределяет встроенный; файл с новым типом добавляется). - Сценарий использования: описание нелицензионных и недокументированных PCB (коллекция krzysiobal, Famiclone'ы и т.п.), которые нельзя выразить через номера iNES-мапперов.
- Обход идентификации: для платы, которой нет в индексе nescartdb (совпадение по CRC невозможно), хост-приложение может принудительно задать тип платы (
CartPcb::SetForcedBoardType, в Core API экспортируется какSetForcedBoardType); тогда определение платы загружается напрямую из встроенных или пользовательских board JSON.
7. Модель симуляции
7.1 Контракт edge-коннектора
Определяет картриджный порт — сигнальный контракт бывшего Mappers::AbstractCartridge,
который теперь живёт в CartPcb (CartPcb::Cartridge):
- Входы (
CartInput):M2,nROMSEL,RnW,nRD,nWR,nPA13, а также только для NES —SYSTEM_CLK,CIC_CLK,CIC_TO_CART. - Выходы (
CartOutput):VRAM_A10,VRAM_nCS,nIRQ, а также только для NES —CIC_RST,CIC_TO_MB. - Звуковой выход Famicom (
CartAudioOutSignal) и порт расширения NES (exp).
CartPcbCartridge владеет Pcb и пробрасывает в него сигналы edge-коннектора.
Материнские платы (Breaknes/BreaksCore/NESBoard.cpp, FamicomBoard.cpp)
используют CartPcb::Cartridge*.
7.2 Источники сетей
Предопределённые источники, доступные board JSON:
| Источник | Значение |
|---|---|
cpu_addr[n], cpu_addr[a:b] | биты шины адреса CPU |
ppu_addr[n], ppu_addr[a:b] | биты шины адреса PPU |
cpu_data[n] | биты шины данных CPU |
M2, nROMSEL, RnW, nRD, nWR, nPA13 | управляющие сигналы edge-коннектора |
<chip>.<PIN> | любой выходной вывод чипа (например, mmc1.PRG_A14, mmc1.VRAM_A10) |
gnd, vdd | константы |
Производные выражения (| — конкатенация, булевы операторы &/|/!)
формируют линии адреса и chip-select'ы.
7.3 Семантика чипов памяти
RomChip (в стиле JEDEC) и SRAM следуют классическому протоколу микросхем
памяти (/CE — chip enable, /OE — output enable, /WE — write
enable, A0..An, D0..D7):
- Чтение:
/CE == 0 && /OE == 0→ выставить данные на шину (обработкаdzидентична SRAM). - Запись (только RAM):
/CE == 0 && /WE == 0→ захват данных. ROM игнорирует/WE; на платах с CHR-RAM вместо ROM на шине PPU используетсяSRAM. - Ширина адреса выводится из
sizeкомпонента (например, 32 КиБ →A0..A14). - Отладка:
Dbg_ReadByte/Dbg_WriteByte/Dbg_GetSizeдля каждого чипа, доступные через отладочный интерфейс платы.
7.4 Mirroring → Scroll
Для декларативного описания PCB не существует определения «Mirroring» (issue #525). Термин «Mirroring» принадлежит заголовку iNES; на плате есть только паяная перемычка и выбираемая ею scroll-схема (nesdev «arrangement»):
| Перемычка PCB (Scroll) | Разводка | Заголовок iNES (Flags6, бит 0) |
|---|---|---|
| H Scroll | VRAM_A10 = PA10 | 1 («vertical mirroring») |
| V Scroll | VRAM_A10 = PA11 | 0 («horizontal mirroring») |
Эти два термина взаимоисключающие: H Scroll (PCB) == vertical mirroring (iNES).
"mode": "scroll": плата имеет перемычку scroll. Конкретное значение scroll — параметр экземпляра картриджа, применяемый загрузчиком из заголовка.nes(Pcb::ApplyScrollFromHeader, бит 0 = 1 → H Scroll) или из контактных площадок nescartdb (ApplyPadScroll). Платы, ведущиеVRAM_A10с выхода чипа-маппера, параметра Scroll не имеют."mode": "mapper":VRAM_A10ведётся сетью (например,mmc1.VRAM_A10) — ровно как на платах MMC1.- Платы без разводки VRAM (например, некоторые дискретные) могут оставить mirroring незаданным.
Внутренние 2 КиБ видеопамяти PPU вместе с её линией VRAM_A10 называются просто VRAM.
7.5 Связь с существующими компонентами
| Компонент | Роль |
|---|---|
CartPcb::Cartridge | Контракт картриджного порта (входы/выходы/отладка); бывший Mappers::AbstractCartridge. Реализуется классом CartPcbCartridge. |
компонент Mappers | Выведен из эксплуатации (issue #509). Вся папка удалена; этот документ заменяет старый Mappers/Readme.md; файлы сборки (CMakeLists.txt, VS-проекты) обновлены. |
Chips/MMC1 | Класс чипа MMC1 (перенесён из Mappers), с юнит-тестами. |
Chips, BaseBoardLib | Предоставляют классы чипов (MMC1, RomChip, SRAM, LS161, LS32), которые потребляет CartPcb. |
Common/JsonLib | Разбор JSON для board JSON и JSON nescartdb. |
Nescartdb/ | Сконвертированная база (данные идентификации). |
8. Идентификация (NesCartDb)
- Вход: образ PRG, образ CHR. Идентификация — по CRC32 дампов PRG и CHR; SHA1 не используется.
- Поиск: при загрузке один раз строится карта PRG CRC + CHR CRC → картридж →
board.type; находятся все картриджи, чьи CRC образов PRG и CHR совпадают. - Неоднозначность: некоторые дампы соответствуют нескольким картриджам/платам (одинаковые ROM на разных PCB).
NesCartDbвозвращает полный список кандидатов; вызывающий берёт первое совпадение или то, чьяsystemподходит материнской плате. Эта неоднозначность — фича по сравнению с iNES: информация выводится наружу, а не молча теряется. - Если совпадений нет, iNES-fallback (
InesTranslator, issue #514) переводит дамп из его заголовка: номер iNES-маппера (Flags6/7) и размеры PRG/CHR выбирают тип платы (маппер 0 → NROM-128/256 по размеру PRG, 1 → MMC1 (SGROM для CHR-ROM / SHROM для CHR-RAM), 2 → UNROM, 3 → CNROM, 7 → AOROM). Переведённый дамп проходит тем же путёмPcbLoader→PcbFactory, в лог пишется предупреждение. Неизвестные мапперы (например, MMC3, пока чип не реализован) не загружаются. Плату, которой нет в базе, можно принудить черезSetForcedBoardType(§6.5).
9. План покрытия плат
Платы добавляются постепенно, каждая как: board JSON + юнит-тест + (пока существовала старая реализация) parity-тест:
- NROM (
NES-NROM-128/256,HVC-NROM-*,IREM-NROM-*, ...) — без чипа-маппера: PRG + CHR + перемычка scroll. Заменяет старыйNROM. - UxROM (
NES-UNROM,NES-UOROM, ...) — дискретное переключение банков PRG (A14), 8 КиБ CHR-RAM. Описывается реальной клейкой логикой: регистр банка 74LS161 и адресный мультиплексор 74LS32 (issue #525). Заменяет старыйUNROM. - AxROM (
NES-AOROM,NES-ANROM, ...) — одноэкранный mirroring через регистр банка 74LS161 (Q3 →VRAM_A10). Заменяет старыйAOROM. - Семейство MMC1 (SGROM, SLROM, ...) — чип
MMC1изChips, разведённый по §5.2. Заменяет старыйMMC1_Based. - Запасные платы (issue #514):
CNROM(переключение банков CHR на latch) иSHROM(MMC1 + CHR-RAM) существуют, чтобы iNES-fallback мог переводить дампы маппера 3 и маппер-1 с CHR-RAM. - Следующие кандидаты: семейство MMC3, платы дискретной логики (BxROM, ...).
10. Чек-лист миграции (Mappers → CartPcb)
Выполнено в issue #509:
- Контракт картриджного порта (бывший
Mappers::AbstractCartridge) переехал в CartPcb (CartPcb::Cartridge). CartImageзаменил сыройuint8_t* nesImage; PRG и CHR — явные именованные дампы заданного размера, загружаемые в экземплярыRomChip.RomChip(в стиле JEDEC, вBaseBoardLib) и переработанныйSRAMзаменили сырые байтовые массивы.NesCartDb(только CRC32) +PcbLoader+PcbFactoryзаменилиswitchпо iNES-мапперам в старомCartridgeFactory.MMC1переехал вChips(с юнит-тестами); исправлено устаревшее чтение PRG без маски.- NROM/UNROM/AOROM/SGROM/SLROM перевыражены как board JSON и прошли A/B parity-тесты против старых реализаций.
- Весь компонент
Mappersудалён (включаяAbstractCartridgeиCartridgeFactory); файлы сборки обновлены. - Все потребители ищут JSON nescartdb и каталоги пользовательских плат согласно
Nescartdb/Readme.md.
11. Отладка и тестирование
- Карта памяти: каждая плата регистрирует свои области памяти (PRG, CHR, WRAM) как
MemDescriptorс почиповымиDbg_ReadByte/Dbg_WriteByte, сохраняя вид памяти в отладчике (и добавляя поддержку PRG, которой не хватало). - Отладочная информация: платы публикуют отладочные записи по каждой сети/чипу (категория = тип платы), например текущий банк PRG, состояние
nROMSEL, режим scroll. - Юнит-тесты (
UnitTest/): разбор JSON → инстанцирование платы; поведение чипов/CE//OE//WE; mirroring; адресная математика переключения банков для каждой поддерживаемой платы; тесты чипа MMC1; загрузка пользовательских плат JSONES. Dbg_ReadPRGByteостаётся на картриджном порту CartPcb (используется отладчиком и дизассемблером лога Nintendulator); он выводится из разметки чипа PRG.
12. Не-цели / дальнейшая работа
- Попиновые тайминги и задержки распространения в v1 не моделируются: сети комбинаторные, тайминги живут в классах чипов.
- Сложные платы дискретной логики выразимы через сети, но будут добавляться по мере развития языка списка соединений.
- Звуковое расширение Famicom (VRC6, FDS, ...) — тема на потом; разъём уже несёт звуковую линию.
Источник: CartPcb/Readme.md · страница входит в сайт документации.