После моей статьи про дампер картриджей (которую пока что оставили на Хабре почему-то), меня очень много раз просили рассказать, как собирать и записывать картриджи для Денди/Famicom самому. Да, это очень избитая тема, даже в древних номерах журнала «Радио» про это можно было почитать, но прогресс не стоит на месте. Рассмотрим эту тему с точки зрения современных компонентов. Тем более, по-моему, она идеально подходит для изучения азов работы с ПЛИС, именно на этом я и сам учился.


Как и в прошлый раз, эта публикация в двух форматах: развлекательное видео попроще (да, уже третья серия) и обычная статья с более детальной информацией. Кому как больше нравится, но лучше посмотрите и то, и другое.

Видео:




Статья:


Начать, наверное, нужно с того что, картриджи, которые продавались и продолжают продаваться в наших магазинах, перезаписать, увы, не получится (на самом деле некоторые можно, но об этом в другой раз). Связано это с тем, что в них установлена обычная EPROM память, которую можно записать только один раз. Однако, ничто не мешает собрать свой собственный картридж с нуля.

Напомню, что картридж включается прямо в шину CPU и в шину PPU, а соответственно в первую очередь содержит две микросхемы памяти с параллельным доступом: PRG — к ней обращается процессор, и она содержит непосредственно код игры, и CHR — с ней работает PPU (графический процессор), и она содержит изображения. При чём последняя запросто может быть не ПЗУ, а оперативной памятью, куда уже в процессе игры записываются данные.

Таким образом, самый простой картридж можно сделать из любых двух микросхем памяти с параллельным доступом, будь то хоть EPROM, хоть flash. При этом больше ничего из обвязки не нужно. Например, вот так выглядел мой первый самодельный картридж:



Микросхемы просто подключаются напрямую к соответствующим выводам на разъёме картриджа. Ноги /RD можно припаять напрямую к земле, ведь консоль всегда будет только читать данные, но я записывал данные уже после сборки картриджа, с помощью всё того же дампера, поэтому подключил все выводы как положено. Биты адреса и данных при этом перепутаны местами, но это абсолютно не имеет значения. Внизу можно увидеть перемычку, которая определяет «mirroring» — как будет зеркалироваться видеопамять: горизонтально или вертикально. Это зависит от игры, и в простейших играх определялось именно перемычкой на картридже.

И ещё очень важный момент — активировать нашу память нужно только тогда, когда консоль обращается к картриджу, иначе будет возникать конфликт на шине. Для этого у микросхем есть вывод /CE (chip enable), который включает память. Тут в ход идёт достаточно простая математика. Программная (PRG) память картриджа начинается с адреса $8000 и заканчивается $FFFF, это два в пятнадцатой степени. Графическая (CHR) память картриджа начинается с адреса $0000 и заканчивается $1FFF, имея объём в 8 килобайт, а это два в тринадцатой степени. Соответственно включать нашу память надо пятнадцатым и тринадцатым контактом на адресной шине. На разъёме картриджа уже есть специальные выводы, которые выдают необходимый нам сигнал. Более того, в случае с PRG памятью нужный нам контакт так и называется — /ROMSEL — сокращённо от ROM Select. Туда консоль выдаёт 0 вольт, когда обращается к памяти картриджа в районе между $8000 и $FFFF. Всё проще некуда.

Однако, на такой картридж можно будет записать только самые простейшие игры. Более серьёзные уже используют картриджи с мапперами, чтобы увеличить максимальный объём игры. Попробую объяснить, как они работали.

У памяти с параллельным доступом каждый бит адреса задаётся отдельным выводом у микросхемы. В разъёме для картриджа есть выводы A0-A14 (15 выводов) для PRG памяти. Это соответственно 15 бит адреса, которые дают 32768 комбинаций единиц и нолей, т.е. позволяют адресовать 32 килобайта. Для CHR памяти там соответственно выводы A0-A13, это 16384 комбинаций, т.е. 16 килобайт, но половина из них отдана памяти внутри консоли.

Уже в восьмидесятые годы таких объёмов стало не хватать. Конечно ничто не мешает поставить в картридж память бОльшего объёма, но у такой памяти и адресных выводов больше. Не трудно посчитать, что каждый дополнительный вывод увеличивает количество возможных адресов ровно в два раза. Но куда их подключать, если количество контактов в разъёме картриджа ограничено? Вот тут на помощь и приходят мапперы, именно они управляют дополнительными выводами в зависимости от различных условий. Почти всегда такими условиями является попытка запись в PRG-область памяти картриджа. Да, в ту, куда нельзя ничего записать.

Многие игры используют для этих целей простейшие логические микросхемы. Например, в картридже у Battletoads стоит четырёхбитный счётчик 74161, который используется как триггер. При записи по любому адресу от $8000 до $FFFF он запоминает записанное значение и выдаёт его на те самые дополнительные выводы у памяти, он же переключает мирроринг.

Но большинство игр использует для этого более сложные микросхемы, которые разработаны специально для этих целей.



Они, как правило, умеют уже переключать разные банки для разных областей памяти, управлять дополнительной памятью, генерировать прерывания, а иногда даже расширять вычислительные мощности консоли.

В качестве примера возьмём самый популярный маппер — MMC3. О нём во всех подробностях можно почитать тут: wiki.nesdev.com/w/index.php/MMC3

Первым делом читаем, как происходит взаимодействием с ним. А происходит оно через запись по определённым адресам, их 8 групп: $8000-$9FFE (чётные), $8001-$9FFF (нечётные), $A000-$BFFE (чётные), $A001-$BFFF (нечётные), $C000-$DFFE (чётные), $C001-$DFFF (нечётные), $E000-$FFFE (чётные) и $E001-$FFFF (нечётные). Запись по любому адресу внутри группы равнозначна. Видите закономерность? Регистр выбирается с помощью трёх адресных бит: A0, A13 и A14, остальные же значения не имеют.

Попробуем же имитировать работу маппера с помощью ПЛИС. Код я пишу на языке Verilog. Он тут не подсвечивается, прошу прощения за это.
Сначала описываем наши регистры, которые хранят текущее состояние:
reg [2:0] bank_select;
reg prg_mode;
reg chr_mode;
reg [7:0] r [0:7];
reg mirroring;
reg [7:6] ram_protect;
reg [7:0] irq_latch;
reg [7:0] irq_counter;
reg [2:0] a12_low_time;
reg irq_reload;
reg irq_reload_clear;
reg irq_enabled;


Описываем реакцию на запись по соответствующим адресам. Возрастающий сигнал /ROMSEL говорим о том что было обращение к памяти картриджа, т.е. по адресам $8000-$FFFF, нам надо реагировать именно в этот момент.
always @ (posedge romsel) 
begin
	// Но только если это была запись
	if (cpu_rw_in == 0)
	begin
		// Рассматриваем состояние A14, A13 и A0, обновляем соответствующие регистры
		case ({cpu_addr_in[14:13], cpu_addr_in[0]})
			3'b000: begin  // $8000-$9FFE, even
				bank_select <= cpu_data_in[2:0];
				prg_mode <= cpu_data_in[6];
				chr_mode <= cpu_data_in[7];
			end
			3'b001: r[bank_select] <= cpu_data_in; // $8001-$9FFF, odd
			3'b010: mirroring <= cpu_data_in[0]; // $A000-$BFFE, even
			3'b011: ram_protect <= cpu_data_in[7:6]; // $A001-$BFFF, odd
			3'b100: irq_latch <= cpu_data_in; // $C000-$DFFE, even
			3'b101: irq_reload <= 1; // $C001-$DFFF, odd
			3'b110: irq_enabled <= 0; // $E000-$FFFE, even
			3'b111: irq_enabled <= 1; // $E001-$FFFF, odd
		endcase
	end
	if (irq_reload_clear)
		irq_reload <= 0;
end


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



Где $8000 & #$40 — это у нас prg_mode, а -2 и -1 — это предпоследний и последний банк соответственно. Получается такой код:
// PRG banking
always @ (*)
begin
	case ({cpu_addr_in[14:13], prg_mode})
		// $8000-$9FFF
		3'b000: cpu_addr_out[18:13] <= r[6][5:0];
		3'b001: cpu_addr_out[18:13] <= 6'b111110; // Предпоследний банк
		// $A000-$BFFF
		3'b010,
		3'b011: cpu_addr_out[18:13] <= r[7][5:0];
		// $C000-$DFFF
		3'b100: cpu_addr_out[18:13] <= 6'b111110;  // Предпоследний банк
		3'b101: cpu_addr_out[18:13] <= r[6][5:0];
		// $E000-$FFFF - всегда является последним банком
		default: cpu_addr_out[18:13] <= 6'b111111;
	endcase
	// A12 у MMC3 на выходе всегда как на входе, он идёт напрямую в память
	cpu_addr_out[12] <= cpu_addr_in[12];
end


Теперь CHR. Там такая схема:



Где $8000 & #$40 — это chr_mode. Получается так:
// CHR banking
always @ (*)
begin
	if (ppu_addr_in[12] == chr_mode)		
		ppu_addr_out[17:10] <= {r[ppu_addr_in[11]][7:1], ppu_addr_in[10]};
	else
		ppu_addr_out[17:10] <= r[2+ppu_addr_in[11:10]];
	// Максимальный размер CHR у MMC3 - 256 килобайт, поэтому A18 всегда 0.
	ppu_addr_out[18] <= 0;
end


Режим зеркалирования описывается всего одной строкой. В зависимости от него мы замыкаем вывод картриджа CIRAM A10 либо на A10, либо на A11:
assign ppu_ciram_a10 = mirroring ? ppu_addr_in[11] : ppu_addr_in[10];


Дальше сложнее. MMC3 умеет генерировать прерывания, когда на экране рисуется определённая строка. Это весьма полезно, и игры часто это используют. Строки на экране считаются с помощью обращений к A12 у PPU. При типичных настройках сигнал на A12 переходит из логического 0 в логическую 1 ровно один раз за строку, если не считать кратковременные переходы в 0. А их надо не считать, это всё немного усложняет:

// Включаем прерывания только тогда, когда на A12 низкий уровень
always @ (*)
begin
	if (!irq_enabled)
	begin
		irq_ready = 0;
		irq <= 1'bZ;
	end else if (irq_enabled && !irq_value)
		irq_ready = 1;
	else if (irq_ready && irq_value)
		irq <= 1'b0;
end

// Сам счётчик
always @ (posedge ppu_addr_in[12])
begin
	if (a12_low_time == 3) // Время низкого уровня A12 должно быть не менее 3 циклов CPU
	begin
		if ((irq_reload && !irq_reload_clear) || (irq_counter == 0))
		begin
			irq_counter = irq_latch;
			if (irq_reload) irq_reload_clear <= 1;
		end else
			irq_counter = irq_counter-1;
		if (irq_counter == 0 && irq_enabled)
			irq_value = 1;
		else
			irq_value = 0;
	end
	if (!irq_reload) irq_reload_clear <= 0;		
end

// Время низкого уровня A12 должно быть не менее 3 циклов CPU
always @ (posedge m2, posedge ppu_addr_in[12])
begin
	if (ppu_addr_in[12])
		a12_low_time <= 0;
	else if (a12_low_time < 3)
		a12_low_time <= a12_low_time + 1;
end


Ах да, MMC3 поддерживает ещё подключение дополнительной оперативной памяти по адресу $6000-$7FFF! Надо не забыть и это описать:
assign cpu_wr_out = cpu_rw_in || ram_protect[6];
assign cpu_rd_out = ~cpu_rw_in;
assign cpu_sram_ce = !(cpu_addr_in[14] && cpu_addr_in[13] && m2 && romsel && ram_protect[7]);


Вот и всё, наш MMC3 готов! Полный код можно посмотреть тут: https://github.com/ClusterM/nes_mappers/blob/master/4%20(MMC3)/MMC3.v
В том же репозитории есть коды многих других мапперов.

На самом деле собрать картридж для какой-то одной определённой игры весьма просто, ведь нужно будет установить только необходимые компоненты. А вот сделать универсальный картридж гораздо сложнее.Если установить ПЛИС на 128 макроячеек, flash на 512 килобайт для PRG, flash на 512 килобайт для CHR, SRAM на 32 килобайта для CHR, SRAM на 32 килобайта в качестве дополнительной памяти, питание которой поддерживается батарейкой для игр, которые умеют сохраняться, то на нём пойдёт уже около 90%-95% игр. Схема получается весьма замороченная, я долго вручную рисовал плату под всё это дело. Кстати, при выборе компонентов не стоит забывать, что у Famicom/Dendy пятивольтовые уровни. Китайцы сейчас очень часто это игнорируют.

Первая ревизия моего универсального картриджа выглядела как-то так:



Ну и программу для записи игр написал конечно же:



Как видите, всё не так сложно, если немного посидеть и разобраться в принципах работы.

Комментарии (36)


  1. ice2heart
    07.08.2015 12:22

    А что с избытком печатных плат? Или нет избытка?


    1. ClusterM
      07.08.2015 14:12

      Не понял вопроса. О каком избытке речь?


      1. ice2heart
        07.08.2015 14:25
        +2

        На видео готовых печатных плат много. Тоже хочется.


        1. ClusterM
          07.08.2015 21:21

          А, пока точно не знаю. Несколько подарил уже друзьям на ДР. Подумывал продавать, но мне не очень нравится качество и удобство использования того, что у меня получилось.


          1. SoLRoN
            13.08.2015 09:08

            А где же комментарий «Заткнись и возьми мои деньги!»? первая мысль которая у меня возникла — я знаю людей которым такой картридж нужен в любом качестве и удобстве.


            1. ClusterM
              13.08.2015 09:29
              +2

              Я совсем не против, просто не хочу рекламировать продукт, в качестве которого не уверен :)


              1. ice2heart
                13.08.2015 09:32
                +1

                Я думаю ещё пара итераций и вполне можно продавать.


              1. Newbilius
                13.08.2015 12:07
                +2

                Надо же, не все значит поддались моде «итеративной разработки [за счет клиентов-бетатестеров]» :-)


  1. uterr
    07.08.2015 12:39
    +1

    Флеш-кард для NES, это круто, такие выпускались вообще когда-либо?


    1. ice2heart
      07.08.2015 12:43

      www.retrousb.com/index.php Сейчас выпускаются и продаются.


    1. goletsa
      07.08.2015 13:45

      Оставлю это здесь stuffpoint.ru
      Жаль из-за курса цены вверх улетели.


      1. ClusterM
        07.08.2015 13:56
        +1

        Мы собственно в видео размышляли, почему они такие дорогие.


        1. goletsa
          07.08.2015 13:57
          +2

          Не смотрел еще видео, но в принципе там по паре fpga, они вроде не дешевые.


          1. ClusterM
            07.08.2015 14:22
            +1

            Именно. Почему-то производители флешек обычно гонятся за максимализмом, когда многим пользователям не нужен такой функционал.


            1. uterr
              07.08.2015 15:45

              а потом еще и бросают поддержку флешки =)


              1. HardWrMan
                20.08.2015 19:06
                +1

                Ваш камень принят и расположен в своем месте нашего огорода.

                В любом случае, Кластер прав, суперкомбайны не нужны 90% населения. Поэтому есть хитрый план и Кластер в курсе. Может быть он даже будет реализован.


          1. 15432
            09.08.2015 09:54

            Если получится использовать CPLD xc2c128, то они дешевые, в районе $2.
            xc2c256 / xc2c512 подороже, $10-$30


      1. HardWrMan
        20.08.2015 19:04
        +1

        Я! Я тоже так могу!
        savepic.su/6094186.jpg
        savepic.su/6091114.jpg


        1. goletsa
          20.08.2015 19:12

          Я думал там тоже заказать, но что-то там с доступностью было не так или что-то типа того.


          1. HardWrMan
            20.08.2015 19:21
            +1

            Я «там» не заказывал. Я соразработчик, а хозяин того магазина соразработчик и издатель. К сожалению издатель действительно бросил проект. Поддержку уже проданных экземпляров пока я осуществляю сам.


            1. goletsa
              20.08.2015 19:23

              Я читал форум там, видел.
              Сейчас же сайт вообще недоступен.
              Жаль что проект по сути закрылся и развития можно не ожидать.


              1. HardWrMan
                20.08.2015 19:30
                +1

                Я скоро открою зеркало у себя. Мой тестер закончит тестирование игр по мапперам и я начну выкладывать проверенные версии. Ну и ОС еще переписывается на данный момент.

                В любом случае, действительно, развития не будет. Возможно будет новый проект и, так как тот проект не был для меня доходообразующим, возможно новый проект будет изначально открытым.


  1. Lsh
    07.08.2015 13:33

    Познавательно! А какой самый сложный (продаваемый, не самодельный) картридж для NES вам попадался? Давно где-то читал, что были картриджи с дополнительным cpu или типа того.


    1. ClusterM
      07.08.2015 13:57
      +2

      Маппер MMC5 очень крутой — там и дополнительные каналы звука, и вертикальный скроллинг, и умножение, и многое другое.
      Или картридж Nintendo World Championships — там много логики, про него как раз в видео рассказали немного. Но он мне не попадался в руки, слишком редкий и дорогой :)


  1. Newbilius
    07.08.2015 15:34

    Понимаю, как звучит мой вопрос, но всё-таки… видео пока не смотрел. Рассматривается ли там вопрос, как быть с играми, которые использовали доп.фичи, типа там музыкального сопроцессора (в Castlevania 3 вроде был, и где то ещё)? Искать полностью такой же чип, или есть какие-то аналоги?


    1. ClusterM
      07.08.2015 19:42
      +1

      В принципе при желании можно самому сделать аналог, люди как-то собирают.


  1. zomby
    07.08.2015 21:21

    > Как видите, всё не так сложно
    > После моей статьи про дампер картриджей (которую пока что оставили на Хабре почему-то)

    Вы настолько суровы, что считаете уровень сложности ваших статей ниже уровня Хабра и им самое место на ГТ? Да вы маньяк :)


    1. ClusterM
      07.08.2015 21:30
      +1

      Нет, просто все разделы про самоделки перенесли сюда как непрофессиональные. А ту статью оставили из-за хаба «реверс-инжиниринг». Хотя другие мои статьи из этого хаба удалили. Теперь «реверс-инжиниринг» — это только про софт.


      1. zomby
        07.08.2015 22:04

        Вы же не стол дубовый сделали, в конце концов. Программирование, микроконтроллеры там…


        1. ClusterM
          07.08.2015 22:22
          +1

          Администрация хабра разницы не видит. Это обсуждалось недавно тут: habrahabr.ru/company/tm/blog/139901


  1. rdc
    07.08.2015 23:37

    Интересно, а реально ли сделать картридж со всеми играми разом?
    Выбираем игру из менюхи, ПЛИС реконфигурируется, подключается нужная страничка флэша и поехали…


    1. ClusterM
      07.08.2015 23:57
      +1

      Смотря что подразумевается под «всеми». До сих пор не всё сдамплено, не все мапперы известны и эмулируются. А так полно же флеш-картриджей разных.


      1. rdc
        07.08.2015 23:58

        Всеми, которые можно залить в описанный выше картридж.


        1. ClusterM
          08.08.2015 00:18
          +1

          Реально, но лучше FPGA использовать. И загружать с SD карты в оперативку. Дорого выходит.


          1. HardWrMan
            20.08.2015 19:22
            +1

            И тем не менее есть реальная возможность сделать гибрид на основные мапперы и игры под них без использования жирной FPGA и дорогого ОЗУ.


  1. egoisto
    12.08.2015 16:10

    Интересно, спасибо и за статьи и за видео O Пилю потихоньку GB, там тоже мапперы, но не такие изощренные, и адресация попроще.

    Где $8000 & #$40 — это chr_mode

    тут опечатка похоже