Cartușele Self-made la Dendy

După articolul meu despre cartușe basculante (care sunt încă lăsate pe Habre pentru un motiv oarecare), am de multe ori mi-a cerut să-ți spun cum să colecteze și cartușe de discuri pentru NES / Famicom în sine. Da, acesta este un subiect foarte banal, chiar și în vechile numere ale revistei „Radio“, cu privire la aceasta poate fi citit, dar progresul nu este încă în picioare. Luați în considerare această problemă din punct de vedere al componentelor moderne. Mai ales, în opinia mea, este ideal pentru a explora elementele de bază de a lucra cu FPGA-uri, pe acest lucru, m-am studiat.

Start, probabil, este necesar să se dispună cartușele au fost vândute și să continue să vândă în magazinele noastre, suprascrie, din păcate, nu va funcționa (de fapt, unele pot, dar mai mult pe altă dată). Motivul este că au instalat o memorie obișnuită EPROM, care pot fi scrise doar o singură dată. Cu toate acestea, nimic nu împiedică construi propriul dvs. de la zero cartuș.

Permiteți-mi să vă reamintesc că cartușul este inclus direct în CPU și anvelopa de autobuz PPU, și, în consecință, în primul rând conține două cipuri de memorie pentru acces concurent: PRG - procesor îl accesează, și conține un cod detaliat al jocului, iar CHR - UPP funcționează cu ea (grafic procesor), și cuprinde o imagine. Ce face aceasta din urmă poate fi ușor ROM și RAM, în cazul în care datele sunt deja înregistrate în cursul jocului.

Astfel, cel mai ușor cartușul poate fi realizat din oricare două cipuri de memorie în conexiune paralelă, fie că este vorba de cel puțin EPROM, deși flash. În acest caz, nimic nu mai este nevoie de legat cu banda. De exemplu, se pare ca acesta este primul meu cartuș de self-made:

Cu toate acestea, un astfel de cartuș poate fi scris doar jocul cel mai simplu. Mai severă utilizează deja cartușe de cartografii pentru a crește cantitatea maximă de jocuri. Voi încerca să explice modul în care au lucrat.

Dar cele mai multe jocuri pe care le utilizează mai multe chips-uri complexe, care sunt special concepute pentru acest scop.

Ei tind să fie în măsură de a comuta între diferite bănci au diferite domenii de memorie pentru a gestiona memorie suplimentară, pentru a genera o întrerupere, și, uneori, chiar și pentru a extinde puterea de calcul a consolei.

Să încercăm să simuleze cartograful operație folosind FPGAs. Am scrie cod în limbajul Verilog. Nu e cu iluminare din spate, îmi cer scuze pentru asta.
Mai întâi descriem registrele noastre, care stochează starea curentă:

reg [2: 0] bank_select;
reg prg_mode;
reg chr_mode;
reg [7: 0] r [0: 7];
reg oglindire;
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;

întotdeauna @ (posedge romsel)
începe
// Dar numai dacă a fost un record
if (cpu_rw_in == 0)
începe
// ia în considerare starea A14, A13 și A0, să actualizeze registrele relevante
caz ()
3'b000: // începe $ 8000- $ 9FFE, chiar
bank_select <= cpu_data_in[2:0];
prg_mode <= cpu_data_in[6];
chr_mode <= cpu_data_in[7];
capăt
3'b001: r [bank_select] <= cpu_data_in; // $8001-$9FFF, odd
3'b010: oglindire <= 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
capăt
if (irq_reload_clear)
irq_reload <= 0;
capăt

Acum, descrie ceea ce ar trebui să fie ales banca cu referire la partea relevantă a memoriei în funcție de registrele noastre.
acestea sunt comutate în conformitate cu tabelul de mai jos:

În cazul în care $ 8.000 de # $ 40 - aceasta este prg_mode noastră, și -2 și -1 - este penultima și ultima bancă, respectiv. Se pare următorul cod:

// PRG bancar
întotdeauna @ (*)
începe
caz ()
// $ 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 - întotdeauna ultima banca
default: cpu_addr_out [18:13] <= 6’b111111;
endcase
// A12 la MMC3 de ieșire este întotdeauna atât la intrare, merge direct în memoria
cpu_addr_out [12] <= cpu_addr_in[12];
capăt

Acum CHR. Există un astfel de sistem:

În cazul în care $ 8.000 de # $ 40 - l chr_mode. Se obține după cum urmează:

// CHR bancar
întotdeauna @ (*)
începe
if (ppu_addr_in [12] == chr_mode)
ppu_addr_out [17:10] <= ;
altfel
ppu_addr_out [17:10] <= r[2+ppu_addr_in[11:10]];
// Dimensiunea maximă la CHR MMC3 - 256 kilobiți, astfel încât A18 este întotdeauna 0.
ppu_addr_out [18] <= 0;
capăt

Modul de oglindire este descrisă de o singură linie. În funcție de aceasta vom închide cartușul de ieșire CIRAM A10 sau la A10. sau pe A11:
aloca ppu_ciram_a10 = oglindire. ppu_addr_in [11]. ppu_addr_in [10];

Mai complicat. MMC3 poate genera o întrerupere atunci când o linie definită desenată pe ecran. Acest lucru este foarte util, și jocuri folosesc adesea. Linia de pe ecran sunt considerate de referință la A12 din UPP. În cazul în care semnalul de pe A12 modificările tipice setărilor dintr-un 0 logic la un 1 logic numai o dată pe linie, cu excepția tranzițiilor pe termen scurt la 0. Și ei nu ar trebui să presupună că totul un pic complicat:
// Activați întrerupe numai atunci când A12 la nivelul scăzut
întotdeauna @ (*)
începe
if (! irq_enabled)
începe
irq_ready = 0;
IRQ <= 1’bZ;
în cazul în care se încheie altceva (irq_enabled ! Irq_value)
irq_ready = 1;
else if (irq_ready irq_value)
IRQ <= 1’b0;
capăt

// contra Sam
întotdeauna @ (posedge ppu_addr_in [12])
începe
if (a12_low_time == 3) // scăzut A12 timp ar trebui să fie de cel puțin trei cicluri CPU
începe
if ((irq_reload ! Irq_reload_clear) || (Irq_counter == 0))
începe
irq_counter = irq_latch;
if (irq_reload) irq_reload_clear <= 1;
încheie altceva
irq_counter = irq_counter-1;
if (irq_counter == 0 irq_enabled)
irq_value = 1;
altfel
irq_value = 0;
capăt
if (! irq_reload) irq_reload_clear <= 0;
capăt

// Timpul scăzut A12 trebuie să fie de cel puțin 3 cicluri CPU
întotdeauna @ (posedge m2, posedge ppu_addr_in [12])
începe
if (ppu_addr_in [12])
a12_low_time <= 0;
else if (a12_low_time <3)
a12_low_time <= a12_low_time + 1;
capăt

aloca cpu_wr_out = cpu_rw_in || ram_protect [6];
aloca cpu_rd_out =

cpu_rw_in;
aloca cpu_sram_ce =! (cpu_addr_in [14] cpu_addr_in [13] m2 romsel ram_protect [7]);

De fapt, pentru a colecta cartușul pentru orice un joc special, este foarte simplu, deoarece numai componentele necesare necesare pentru a instala. Dar pentru a face un cartuș universal mult slozhnee.Esli set FPGA la 128 de macrocelule, flash 512 kilobytes pentru PRG, Flash 512 kilobytes pentru CHR, SRAM 32 Kbytes pentru CHR, SRAM 32 Kbyte ca memorie suplimentară, care este susținută de o baterie pentru putere jocuri care sunt în măsură să fie salvat, atunci va merge pentru aproximativ 90% -95% din jocuri. Conducerea zamorochennye a lua destul de mult timp am trage manual bord pentru tot. Apropo, alegerea componentelor nu este necesar să se uite că Famicom / Dendy pyativoltovye niveluri. Chinezii sunt acum foarte des ignorate.

Prima revizuire a cartușului meu universal uitat ceva de genul:

Ei bine, și un program de înregistrare jocuri, desigur, a scris:

După cum puteți vedea, nu este atât de dificil, dacă stai pentru un timp și să înțeleagă principiile de funcționare.