Приставки > Картриджи / диски
Пытаюсь сделать свои мапперы для 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 под них. первый то мега мужик запустился с битой графикой, второй просто серый экран у меня получился.
Навигация
Перейти к полной версии