Приставки > Картриджи / диски

Пытаюсь сделать свои мапперы для EverDrive N8 - вопросы чайника

(1/1)

Talking_Sword:
К сожалению, никто так и не ответил на большинство моих вопросов в теме по EverDrive N8. Но увы, мне больше некуда обратиться, кроме как на этом форуме. Пробовал уже задавать эти вопросы на другом форуме - там никто так и не ответил.

Есть тут люди, знакомые с Verilog?

Хочу реализовать маппер Sachen SA8259. У него есть регистры, расположенные по адресам $4100 и $4101. Мне нужно понять, как в Verilog сделать дешифрацию для этих адресов.

Можно конечно пойти "в лоб" и сделать полную дешифрацию:


--- Код: ---if(cpu_addr[14:0] == 15'h4100 & !cpu_rw)
--- Конец кода ---

Тогда регистр будет строго по адресу $4100. Но если перевести $4100 в двоичный вид, то получится 100000100000000. И такое число наталкивает на мысль, что дешифрация неполная, а только по битам A14, A8, A0, полная дешифрация усложнила бы микросхему, вряд ли так бы стали делать. И если взглянуть на распиновку, то точно, оказывается из адресных выводов у него только A14, A8, A0 и есть. То есть, у него точно дешифрация неполная.

Но вот я не знаю точно, как сделать дешифрацию только по отдельным битам в Verilog, причем, если эти биты не идут друг за другом.

Пока только такое придумалось:


--- Код: ---if(cpu_addr[14] & cpu_addr[8] & !cpu_addr[0] & !cpu_rw)
--- Конец кода ---

Но вот совсем не уверен, хорошее ли решение if с кучей условий. Есть опасение, что подобное сгенерирует слишком громоздкий код для FPGA. Конечно многие скажут, можно просто сделать полную и не заморачиваться, но мне кажется, полная дешифрация тоже будет слишком "громоздкой". Да и хочу просто в этом плане сделать ближе к оригиналу.

И еще. А как правильно бороться с конфликтами по шине?

Я пока придумал так:


--- Код: ---assign rom_ce = !cpu_ce & cpu_rw;
--- Конец кода ---

Ну то есть, /CE на PRG ROM (точнее, у EverDrive N8 это SRAM) подается только тогда, когда /ROMSEL (здесь называется cpu_ce) имеет низкий уровень, а CPU R/W - высокий. Ну это я так думаю, может на самом деле это так не работает.

JRBVZ:
Я правда не погромист, но можно попробовать так:

--- Код: ---if (cpu_rw == 0)
begin
   if (cpu_addr[14] == 1'b1 && cpu_addr[8] == 1'b1 && cpu_addr[0] == 1'b0)
   begin
   ...
   end
end
--- Конец кода ---

Talking_Sword:
В вашем примере вроде не сильно лучше, чем у меня - тоже if с несколькими условиями. Я просто думал уменьшить количество условий, чтобы их было не больше двух.

Но я попробовал сделать так:


--- Код: ---if(cpu_addr[14,8,0] & !cpu_rw)
--- Конец кода ---

Я знаю, что из шины можно выбрать отдельные биты, но они должны быть смежными, типа: cpu_addr[3:0], но вот можно или нельзя выбрать несмежные - в интернете информации не нашел. Пришлось попробовать методом тыка. Компилятор то, что написал выше не принял, значит нельзя.

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


--- Код: ---wire [2:0]sa8259_addr;
assign sa8259_addr[2:0] = cpu_addr[14], cpu_addr[8], cpu_addr[0];
--- Конец кода ---

Тоже компилятор не принял такое. Но потом догадался, что можно каждый бит назначить в отдельной строке, то есть так:


--- Код: ---wire [2:0]sa8259_addr;
assign sa8259_addr[2] = cpu_addr[14];
assign sa8259_addr[1] = cpu_addr[8];
assign sa8259_addr[0] = cpu_addr[0];
--- Конец кода ---

Так собралось, но пока еще не проверял, как работает.

Но думаю, может лучше уж cpu_addr[14] & cpu_addr[8] & cpu_addr[0] и правда в if засунуть? А то, наверно отдельная шина для этих битов тоже слишком "громоздко".

А еще, а как вы считаете, в if вложенный case нормально делать? То есть так:


--- Код: ---if(cpu_addr[14] & cpu_addr[8] & cpu_addr[0] & !cpu_rw)
begin
case (reg_select[2:0])
0: ... ;
1: ... ;
2: ... ;
3: ... ;
4: ... ;
5: ... ;
6: ... ;
7: ... ;
endcase
end
--- Конец кода ---

JRBVZ:

--- Цитата: Talking_Sword от 23 Май 2025, 21:24:11 ---В вашем примере вроде не сильно лучше, чем у меня - тоже if с несколькими условиями. Я просто думал уменьшить количество условий, чтобы их было не больше двух.

--- Конец цитаты ---
Какая разница сколько условий, хоть восемь если это нужно.


--- Цитата ---А еще, а как вы считаете, в if вложенный case нормально делать? То есть так:


--- Код: ---if(cpu_addr[14] & cpu_addr[8] & cpu_addr[0] & !cpu_rw)
begin
case (reg_select[2:0])
0: ... ;
1: ... ;
2: ... ;
3: ... ;
4: ... ;
5: ... ;
6: ... ;
7: ... ;
endcase
end
--- Конец кода ---

--- Конец цитаты ---
Можно и так, только select какого типа данных? Как вариант для boolean:

--- Код: ---if(cpu_addr[14] & cpu_addr[8] & cpu_addr[0] & !cpu_rw)
begin
case (reg_select[2:0])
3'b000: ... ;
3'b001: ... ;
3'b010: ... ;
3'b011: ... ;
3'b100: ... ;
3'b101: ... ;
3'b110: ... ;
3'b111: ... ;
endcase
end
--- Конец кода ---

Talking_Sword:

--- Цитата: JRBVZ от 24 Май 2025, 08:24:57 ---Какая разница сколько условий, хоть восемь если это нужно.
--- Конец цитаты ---
Как уже говорил, подозреваю, возможно это не самое "красивое" решение, возможно генерируется "менее оптимальный" код для FPGA.

Тем не менее, я здесь нашел еще и реализацию маппера Action 53 - файл "map_28.v". У него есть даже небольшое сходство с Sachen-овским маппером - например то, что у него по одному адресу записывается, какой из внутренних регистров изменить, а по другому записывается его значение, на SA8259 похоже.

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

Похоже нашел там, как можно использовать "несмежные" (в том смысле, что не идущие друг за другом) биты - можно перечислить их через запятую, взяв в фигурные скобки, пример:


--- Код: ---if(!cpu_rw & cpu_ce & cpu_addr[14:12] == 3'b101)reg_addr[1:0] <= {cpu_dat[7], cpu_dat[0]};
--- Конец кода ---

JRBVZ:
Прежде чем тыкаться, лучше поизучать синтаксис языка, например что такое конкатенация.

and1981:
Talking_Sword, можете глянуть в моем репозитории на гите, как реализуется дешифрация адресов АПУ (2а03) https://github.com/andkorzh/RP2A03-7-, я делал максимально просто, тупо комбинаторикой без всяких if и case, как оно и есть в реальном чипе.
Либо есть хорошая тема на https://zx-pk.ru/threads/27091-potaktovyj-klon-dendi-na-fpga.html где HardWareMan доходчиво объяснял низкоуровневую реализацию ППУ на языке верилог. Хотя если у вас есть точная схема маппера, то можно хоть схемным вводом её нарисовать в квартусе, тогда у синтезатора точно не возникнет вопросов и RTL схема будет максимально соответствовать  оригиналу. Надеюсь, что что-нибудь из вышеперечисленного вам точно поможет.

Talking_Sword:

--- Цитата: and1981 от 27 Май 2025, 09:20:57 ---Talking_Sword, можете глянуть в моем репозитории на гите, как реализуется дешифрация адресов АПУ (2а03) https://github.com/andkorzh/RP2A03-7-, я делал максимально просто, тупо комбинаторикой без всяких if и case, как оно и есть в реальном чипе.
Либо есть хорошая тема на https://zx-pk.ru/threads/27091-potaktovyj-klon-dendi-na-fpga.html где HardWareMan доходчиво объяснял низкоуровневую реализацию ППУ на языке верилог.
--- Конец цитаты ---
Ну APU\PPU - штуки довольно сложные. Чтобы понять, где там дешифрация, нужно сначала понять, как это все работает. Умножте это все еще на то, что мое первое знакомство с Verilog состоялось всего две недели назад.
--- Цитата: and1981 от 27 Май 2025, 09:20:57 ---Хотя если у вас есть точная схема маппера, то можно хоть схемным вводом её нарисовать в квартусе, тогда у синтезатора точно не возникнет вопросов и RTL схема будет максимально соответствовать  оригиналу. Надеюсь, что что-нибудь из вышеперечисленного вам точно поможет.
--- Конец цитаты ---
Схемы нет, но я бы смог ее придумать просто по описанию работы маппера на NESdev. Там в Quartus можно просто "нарисовать" схему, просто соединяя отдельные элементы? Для меня бы это было бы проще, чем писать код, но опять же, там была уже готовый пример кода маппера, в котором есть уже много чего готового, что не нужно писать самостоятельно. А свою схему пришлось бы еще с этим дружить.

Тем не менее, я уже успел разобраться сам. Там оказалось можно взять в фигурные скобки. Я сделал так:


--- Код: ---if({cpu_addr[14], cpu_addr[8], cpu_addr[0], cpu_rw} == 4'hC)
begin
reg_select[2:0] <= cpu_dat[2:0];
end
else
if({cpu_addr[14], cpu_addr[8], cpu_addr[0], cpu_rw} == 4'hE)
begin
case (reg_select[2:0])
0:chr_bank_l0[2:0] <= cpu_dat[2:0];
1:chr_bank_l1[2:0] <= cpu_dat[2:0];
2:chr_bank_l2[2:0] <= cpu_dat[2:0];
3:chr_bank_l3[2:0] <= cpu_dat[2:0];
4:chr_bank_h[2:0] <= cpu_dat[2:0];
5:prg_bank[2:0] <= cpu_dat[2:0];
7:reg_mirr[2:0] <= cpu_dat[2:0];
endcase
end
--- Конец кода ---
Я уже написал весь код маппера. Запускает такие игры, как Q Boy, Popo Team, Rockball. Правда реализован только вариант "A" (маппер 141 по классификации iNES). Нужно только выяснить некоторые моменты и можно выкладывать.

and1981:
Вот пример модуля регистровых операций процессора 2А03G, здесь еще дополнительно подмешивается тактовый сигнал процессора 6502 (PHI1), на него не обращайте внимание.
Также ниже привожу логическую схему этого модуля. Имеются порты(регистров) на чтение (Rxxx) и на запись (Wxxx). Порт выбирается комбинаторной схемой, она немного оптимизирована, но сути это не меняет, в оригинале на NOR-ах сделан декодер, а после оптимизации получилось на NOR и ОR. Можете рисовать как вам удобнее, синтезатор в любом случае будет оптимизировать вашу схему при компиляции проекта. Результат синтеза в виде логической схемы вы можете посмотреть на вкладке Tolls/ Netlist Viewers /RTL Viewer

--- Код: ---//===============================================================================================
// Register Operations Decoder Module
//===============================================================================================
module REG_SEL(
  // Clocks
  input PHI1,        // Phase PHI1 CPU
  //Inputs
  input RW,            // CPU read/write
  input [4:0]ADR,      // APU address space
  input [15:5]CPU_A,   // CPU address space
  // Outputs
  output W4000,        // Port $W4000
  output W4001,        // Port $W4001
  output W4002,        // Port $W4002
  output W4003,        // Port $W4003
  output W4004,        // Port $W4004
  output W4005,        // Port $W4005
  output W4006,        // Port $W4006
  output W4007,        // Port $W4007
  output W4008,        // Port $W4008
  output W400A,        // Port $W400A
  output W400B,        // Port $W400B
  output W400C,        // Port $W400C
  output W400E,        // Port $W400E
  output W400F,        // Port $W400F
  output W4010,        // Port $W4010
  output W4011,        // Port $W4011
  output W4012,        // Port $W4012
  output W4013,        // Port $W4013
  output W4014,        // Port $W4014
  output W4015,        // Port $W4015
  output W4016,        // Port $W4016
  output W4017,        // Port $W4017
  output nR4015,       // Port $nR4015
  output nR4016,       // Port $nR4016
  output nR4017        // Port $nR4017
);
// Combinatorics
wire REGRD;
wire REGWR;
assign REGRD = ~RW | CPU_A[5] | CPU_A[6] | CPU_A[7] | CPU_A[8] | CPU_A[9] | CPU_A[10] | CPU_A[11] | CPU_A[12] | CPU_A[13] | ~CPU_A[14] | CPU_A[15];
assign REGWR =  RW | CPU_A[5] | CPU_A[6] | CPU_A[7] | CPU_A[8] | CPU_A[9] | CPU_A[10] | CPU_A[11] | CPU_A[12] | CPU_A[13] | ~CPU_A[14] | CPU_A[15];
//write port decoder
assign  W4000 = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] |  ADR[2] |  ADR[3] |  ADR[4]));
assign  W4001 = ~( PHI1 | ( REGWR | ~ADR[0] |  ADR[1] |  ADR[2] |  ADR[3] |  ADR[4]));
assign  W4002 = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] |  ADR[2] |  ADR[3] |  ADR[4]));
assign  W4003 = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] |  ADR[2] |  ADR[3] |  ADR[4]));
assign  W4004 = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] | ~ADR[2] |  ADR[3] |  ADR[4]));
assign  W4005 = ~( PHI1 | ( REGWR | ~ADR[0] |  ADR[1] | ~ADR[2] |  ADR[3] |  ADR[4]));
assign  W4006 = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] |  ADR[4]));
assign  W4007 = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] |  ADR[4]));
assign  W4008 = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] |  ADR[2] | ~ADR[3] |  ADR[4]));
assign  W400A = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] |  ADR[2] | ~ADR[3] |  ADR[4]));
assign  W400B = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] |  ADR[2] | ~ADR[3] |  ADR[4]));
assign  W400C = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] | ~ADR[2] | ~ADR[3] |  ADR[4]));
assign  W400E = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] | ~ADR[2] | ~ADR[3] |  ADR[4]));
assign  W400F = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] | ~ADR[2] | ~ADR[3] |  ADR[4]));
assign  W4010 = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] |  ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4011 = ~( PHI1 | ( REGWR | ~ADR[0] |  ADR[1] |  ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4012 = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] |  ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4013 = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] |  ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4014 = ~( PHI1 | ( REGWR |  ADR[0] |  ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4015 = ~( PHI1 | ( REGWR | ~ADR[0] |  ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4016 = ~( PHI1 | ( REGWR |  ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4]));
assign  W4017 = ~( PHI1 | ( REGWR | ~ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4]));
//read port decoder
assign nR4015 =   REGRD | ~ADR[0] |  ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4] ;
assign nR4016 =   REGRD |  ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4] ;
assign nR4017 =   REGRD | ~ADR[0] | ~ADR[1] | ~ADR[2] |  ADR[3] | ~ADR[4] ;
// End of register decoder module
endmodule
--- Конец кода ---


--- Цитата: Talking_Sword от 28 Май 2025, 01:07:38 --- Там в Quartus можно просто "нарисовать" схему, просто соединяя отдельные элементы?
--- Конец цитаты ---
Да, такая возможность в Квартусе имеется и  для новичков это вполне себе хороший метод осваивания ПЛИС(FPGA). Я сначала так и начинал изучать, рисовал отдельные модули в схемнике, отлаживал, а потом переписывал в Верилог. Можете найти на Ютубе примеры схемного ввода для начинающих. Но Одной схемы в Квартусе конечно мало, нужно еще правильно сконфигурировать проект.  :)

И еще небольшая ремарка:  Неблокирующее присвоение <= лучше и даже нужно использовать только в блоке always для построения триггеров. А для простой комбинаторики используйте блокирующее присвоение assign. В противном cлучае вы рискуете получить схему отличную от оригинала. Удачи!

supremacy:
Так получилось сделать маппер?
Я тут тоже вечерком сел поковырять Quartus, чтобы расширить количество банков в 7 маппере. Получилось довольно быстро.
Воодушевлённый, решил собрать 90-й маппер, по совместительству 211. На github выложены мапперы под n8 pro, думал по-быстрому адаптирую. Но вот, не пойму чему соответствует map_cpu_dout в старой версии проекта, возможно это os_map_dout. А без выгрузки в него, игры запускаются, но виснут. Я тоже сегодня первый раз квартус запустил, надо разбираться.
Так-то, было бы неплохо все недостающие мапперы на старую китайскую n8 сделать, хоть и без save states (примеров со стейтами для старой версии не нашел).

Talking_Sword:

--- Цитата: supremacy от 04 Июль 2025, 23:48:31 ---Так получилось сделать маппер?
--- Конец цитаты ---
Да, сделал маппер для игры Mi Hun Che, маппер Sachen SA8259A (запускает игры Q Boy, Popo Team, Rockball), а так же маппер для игры Glider. Но только руки не доходят выложить. Перед тем как выложить, хотелось бы еще кое-что спросить.

Еще делал маппер для игры Dr. Mario II (есть такой пиратский хак), но игра работает не совсем корректно, есть глюк на паузе, есть некоторые графические глюки. Причину пока не удалось выяснить.
--- Цитата: supremacy от 04 Июль 2025, 23:48:31 ---save states
--- Конец цитаты ---
Вообще считаю это ненужным.

supremacy:
Так и не удалось добиться, чтобы игры на 90 и 211 маппере не висли. Выложу код, может кто-то ещё захочет поковырять.

supremacy:
Можно брать исходники мапперов из репозитория https://github.com/krikzz/edn8-pro-pub,
Если смотреть первый коммит, то там ещё код достаточно близок к N8
https://github.com/krikzz/edn8-pro-pub/tree/a854b2f1685288d52113fc309a4cd35648b9974b/mappers

Ti_:
Было бы неплохо 'расширить' VRC6 мапперы (#24 и #26)  с 256 кб до 512 кб, и 'разрешив' поддержку chr-ram, а не только chr-rom. Для обычного N8, не клона.

supremacy:

--- Цитата: Ti_ от 08 Июль 2025, 07:43:16 ---Для обычного N8, не клона.
--- Конец цитаты ---
Маппер должен подходить и к обычной версии и к клону, железо одинаковое, вот только явно будет урезан по функционалу, т.к. неизвестно как из системы в маппер передаётся регулировка громкости, примеров нет. Ну и save states не будут работать. Попробовать собрать можно, но нужен ром на котором работу проверять.

Ti_:

--- Цитата: supremacy от 08 Июль 2025, 08:40:10 ---Маппер должен подходить и к обычной версии и к клону, железо одинаковое, вот только явно будет урезан по функционалу, т.к. неизвестно как из системы в маппер передаётся регулировка громкости, примеров нет. Ну и save states не будут работать. Попробовать собрать можно, но нужен ром на котором работу проверять.

--- Конец цитаты ---
Megaman II (512kb + chr-ram) : https://disk.yandex.ru/d/UgQX8Q19OVpBEg
Megaman (chr-ram) : https://disk.yandex.ru/d/goZnVMeDL61o5Q
работают в fceux

Nikooone:

--- Цитата: Ti_ от 08 Июль 2025, 09:50:50 ---Megaman II (512kb + chr-ram) : https://disk.yandex.ru/d/UgQX8Q19OVpBEg
Megaman (chr-ram) : https://disk.yandex.ru/d/goZnVMeDL61o5Q
работают в fceux

--- Конец цитаты ---
Это те самые ромы с vrc музыкой?

ps - ДА!) сам спросил, сам ответил) Капец я найти пытался их после случайного видео на ютубе)
Правда пока почему-то явно слышу отличия от того что слышал ранее. И конечно да, было бы круто раздуть на обычном N8 26й rbf под них. первый то мега мужик запустился с битой графикой, второй просто серый экран у меня получился.

Навигация

[0] Главная страница сообщений

Перейти к полной версии