Автор Тема: Пытаюсь сделать свои мапперы для EverDrive N8 - вопросы чайника  (Прочитано 922 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Talking_Sword

  • Пользователь
  • Сообщений: 976
  • Happy Games Only!
    • Просмотр профиля
К сожалению, никто так и не ответил на большинство моих вопросов в теме по 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

  • Пользователь
  • Сообщений: 401
    • Просмотр профиля
Я правда не погромист, но можно попробовать так:
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

  • Пользователь
  • Сообщений: 976
  • Happy Games Only!
    • Просмотр профиля
В вашем примере вроде не сильно лучше, чем у меня - тоже 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

  • Пользователь
  • Сообщений: 401
    • Просмотр профиля
В вашем примере вроде не сильно лучше, чем у меня - тоже 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

  • Пользователь
  • Сообщений: 976
  • Happy Games Only!
    • Просмотр профиля
Какая разница сколько условий, хоть восемь если это нужно.
Как уже говорил, подозреваю, возможно это не самое "красивое" решение, возможно генерируется "менее оптимальный" код для 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

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

Оффлайн and1981

  • Пользователь
  • Сообщений: 131
    • Просмотр профиля
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

  • Пользователь
  • Сообщений: 976
  • Happy Games Only!
    • Просмотр профиля
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 состоялось всего две недели назад.
Хотя если у вас есть точная схема маппера, то можно хоть схемным вводом её нарисовать в квартусе, тогда у синтезатора точно не возникнет вопросов и 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

  • Пользователь
  • Сообщений: 131
    • Просмотр профиля
Вот пример модуля регистровых операций процессора 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

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

И еще небольшая ремарка:  Неблокирующее присвоение <= лучше и даже нужно использовать только в блоке always для построения триггеров. А для простой комбинаторики используйте блокирующее присвоение assign. В противном cлучае вы рискуете получить схему отличную от оригинала. Удачи!
« Последнее редактирование: 28 Май 2025, 10:33:30 от and1981 »

Оффлайн supremacy

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

Оффлайн Talking_Sword

  • Пользователь
  • Сообщений: 976
  • Happy Games Only!
    • Просмотр профиля
Так получилось сделать маппер?
Да, сделал маппер для игры Mi Hun Che, маппер Sachen SA8259A (запускает игры Q Boy, Popo Team, Rockball), а так же маппер для игры Glider. Но только руки не доходят выложить. Перед тем как выложить, хотелось бы еще кое-что спросить.

Еще делал маппер для игры Dr. Mario II (есть такой пиратский хак), но игра работает не совсем корректно, есть глюк на паузе, есть некоторые графические глюки. Причину пока не удалось выяснить.
save states
Вообще считаю это ненужным.

Оффлайн supremacy

  • Пользователь
  • Сообщений: 2350
  • Пол: Мужской
    • Просмотр профиля
Так и не удалось добиться, чтобы игры на 90 и 211 маппере не висли. Выложу код, может кто-то ещё захочет поковырять.
« Последнее редактирование: 05 Июль 2025, 16:17:09 от supremacy »

Оффлайн supremacy

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

Оффлайн Ti_

  • Пользователь
  • Сообщений: 3277
  • Пол: Мужской
    • ВКонтакте
    • Youtube
    • Просмотр профиля
Было бы неплохо 'расширить' VRC6 мапперы (#24 и #26)  с 256 кб до 512 кб, и 'разрешив' поддержку chr-ram, а не только chr-rom. Для обычного N8, не клона.

Оффлайн supremacy

  • Пользователь
  • Сообщений: 2350
  • Пол: Мужской
    • Просмотр профиля
Для обычного N8, не клона.
Маппер должен подходить и к обычной версии и к клону, железо одинаковое, вот только явно будет урезан по функционалу, т.к. неизвестно как из системы в маппер передаётся регулировка громкости, примеров нет. Ну и save states не будут работать. Попробовать собрать можно, но нужен ром на котором работу проверять.

Оффлайн Ti_

  • Пользователь
  • Сообщений: 3277
  • Пол: Мужской
    • ВКонтакте
    • Youtube
    • Просмотр профиля
Маппер должен подходить и к обычной версии и к клону, железо одинаковое, вот только явно будет урезан по функционалу, т.к. неизвестно как из системы в маппер передаётся регулировка громкости, примеров нет. Ну и save states не будут работать. Попробовать собрать можно, но нужен ром на котором работу проверять.
Megaman II (512kb + chr-ram) : https://disk.yandex.ru/d/UgQX8Q19OVpBEg
Megaman (chr-ram) : https://disk.yandex.ru/d/goZnVMeDL61o5Q
работают в fceux