mirror of https://github.com/bsnes-emu/bsnes.git
498 lines
12 KiB
C++
498 lines
12 KiB
C++
struct MMC5 : Chip {
|
|
|
|
enum class Revision : unsigned {
|
|
MMC5,
|
|
MMC5B,
|
|
} revision;
|
|
|
|
uint8 exram[1024];
|
|
|
|
//programmable registers
|
|
|
|
uint2 prg_mode; //$5100
|
|
uint2 chr_mode; //$5101
|
|
|
|
uint2 prgram_write_protect[2]; //$5102,$5103
|
|
|
|
uint2 exram_mode; //$5104
|
|
uint2 nametable_mode[4]; //$5105
|
|
uint8 fillmode_tile; //$5106
|
|
uint8 fillmode_color; //$5107
|
|
|
|
bool ram_select; //$5113
|
|
uint2 ram_bank; //$5113
|
|
uint8 prg_bank[4]; //$5114-5117
|
|
uint10 chr_sprite_bank[8]; //$5120-5127
|
|
uint10 chr_bg_bank[4]; //$5128-512b
|
|
uint2 chr_bank_hi; //$5130
|
|
|
|
bool vs_enable; //$5200
|
|
bool vs_side; //$5200
|
|
uint5 vs_tile; //$5200
|
|
uint8 vs_scroll; //$5201
|
|
uint8 vs_bank; //$5202
|
|
|
|
uint8 irq_line; //$5203
|
|
bool irq_enable; //$5204
|
|
|
|
uint8 multiplicand; //$5205
|
|
uint8 multiplier; //$5206
|
|
|
|
//status registers
|
|
|
|
unsigned cpu_cycle_counter;
|
|
unsigned irq_counter;
|
|
bool irq_pending;
|
|
bool in_frame;
|
|
|
|
unsigned vcounter;
|
|
unsigned hcounter;
|
|
uint16 chr_access[4];
|
|
bool chr_active;
|
|
bool sprite_8x16;
|
|
|
|
uint8 exbank;
|
|
uint8 exattr;
|
|
|
|
bool vs_fetch;
|
|
uint8 vs_vpos;
|
|
uint8 vs_hpos;
|
|
|
|
void main() {
|
|
while(true) {
|
|
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
|
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
}
|
|
|
|
//scanline() resets this; if no scanlines detected, enter video blanking period
|
|
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
|
|
|
cpu.set_irq_line(irq_enable && irq_pending);
|
|
tick();
|
|
}
|
|
}
|
|
|
|
void scanline(unsigned y) {
|
|
//used for testing only, to verify MMC5 scanline detection is accurate:
|
|
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
|
|
}
|
|
|
|
uint8 prg_access(bool write, unsigned addr, uint8 data = 0x00) {
|
|
unsigned bank;
|
|
|
|
if((addr & 0xe000) == 0x6000) {
|
|
bank = (ram_select << 2) | ram_bank;
|
|
addr &= 0x1fff;
|
|
} else if(prg_mode == 0) {
|
|
bank = prg_bank[3] & ~3;
|
|
addr &= 0x7fff;
|
|
} else if(prg_mode == 1) {
|
|
if((addr & 0xc000) == 0x8000) bank = (prg_bank[1] & ~1);
|
|
if((addr & 0xe000) == 0xc000) bank = (prg_bank[3] & ~1);
|
|
addr &= 0x3fff;
|
|
} else if(prg_mode == 2) {
|
|
if((addr & 0xe000) == 0x8000) bank = (prg_bank[1] & ~1) | 0;
|
|
if((addr & 0xe000) == 0xa000) bank = (prg_bank[1] & ~1) | 1;
|
|
if((addr & 0xe000) == 0xc000) bank = (prg_bank[2]);
|
|
if((addr & 0xe000) == 0xe000) bank = (prg_bank[3]);
|
|
addr &= 0x1fff;
|
|
} else if(prg_mode == 3) {
|
|
if((addr & 0xe000) == 0x8000) bank = prg_bank[0];
|
|
if((addr & 0xe000) == 0xa000) bank = prg_bank[1];
|
|
if((addr & 0xe000) == 0xc000) bank = prg_bank[2];
|
|
if((addr & 0xe000) == 0xe000) bank = prg_bank[3];
|
|
addr &= 0x1fff;
|
|
}
|
|
|
|
bool rom = bank & 0x80;
|
|
bank &= 0x7f;
|
|
|
|
if(write == false) {
|
|
if(rom) {
|
|
return board.prgrom.read((bank << 13) | addr);
|
|
} else {
|
|
return board.prgram.read((bank << 13) | addr);
|
|
}
|
|
} else {
|
|
if(rom) {
|
|
board.prgrom.write((bank << 13) | addr, data);
|
|
} else {
|
|
if(prgram_write_protect[0] == 2 && prgram_write_protect[1] == 1) {
|
|
board.prgram.write((bank << 13) | addr, data);
|
|
}
|
|
}
|
|
return 0x00;
|
|
}
|
|
}
|
|
|
|
uint8 prg_read(unsigned addr) {
|
|
if((addr & 0xfc00) == 0x5c00) {
|
|
if(exram_mode >= 2) return exram[addr & 0x03ff];
|
|
return cpu.mdr();
|
|
}
|
|
|
|
if(addr >= 0x6000) {
|
|
return prg_access(0, addr);
|
|
}
|
|
|
|
switch(addr) {
|
|
case 0x5204: {
|
|
uint8 result = (irq_pending << 7) | (in_frame << 6);
|
|
irq_pending = false;
|
|
return result;
|
|
}
|
|
case 0x5205: return (multiplier * multiplicand) >> 0;
|
|
case 0x5206: return (multiplier * multiplicand) >> 8;
|
|
}
|
|
}
|
|
|
|
void prg_write(unsigned addr, uint8 data) {
|
|
if((addr & 0xfc00) == 0x5c00) {
|
|
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
|
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
|
|
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
|
return;
|
|
}
|
|
|
|
if(addr >= 0x6000) {
|
|
prg_access(1, addr, data);
|
|
return;
|
|
}
|
|
|
|
switch(addr) {
|
|
case 0x2000:
|
|
sprite_8x16 = data & 0x20;
|
|
break;
|
|
|
|
case 0x2001:
|
|
//if BG+sprites are disabled; enter video blanking period
|
|
if((data & 0x18) == 0) blank();
|
|
break;
|
|
|
|
case 0x5100: prg_mode = data & 3; break;
|
|
case 0x5101: chr_mode = data & 3; break;
|
|
|
|
case 0x5102: prgram_write_protect[0] = data & 3; break;
|
|
case 0x5103: prgram_write_protect[1] = data & 3; break;
|
|
|
|
case 0x5104:
|
|
exram_mode = data & 3;
|
|
break;
|
|
|
|
case 0x5105:
|
|
nametable_mode[0] = (data & 0x03) >> 0;
|
|
nametable_mode[1] = (data & 0x0c) >> 2;
|
|
nametable_mode[2] = (data & 0x30) >> 4;
|
|
nametable_mode[3] = (data & 0xc0) >> 6;
|
|
break;
|
|
|
|
case 0x5106:
|
|
fillmode_tile = data;
|
|
break;
|
|
|
|
case 0x5107:
|
|
fillmode_color = data & 3;
|
|
fillmode_color |= fillmode_color << 2;
|
|
fillmode_color |= fillmode_color << 4;
|
|
break;
|
|
|
|
case 0x5113:
|
|
ram_select = data & 0x04;
|
|
ram_bank = data & 0x03;
|
|
break;
|
|
|
|
case 0x5114: prg_bank[0] = data; break;
|
|
case 0x5115: prg_bank[1] = data; break;
|
|
case 0x5116: prg_bank[2] = data; break;
|
|
case 0x5117: prg_bank[3] = data | 0x80; break;
|
|
|
|
case 0x5120: chr_sprite_bank[0] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5121: chr_sprite_bank[1] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5122: chr_sprite_bank[2] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5123: chr_sprite_bank[3] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5124: chr_sprite_bank[4] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5125: chr_sprite_bank[5] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5126: chr_sprite_bank[6] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
case 0x5127: chr_sprite_bank[7] = (chr_bank_hi << 8) | data; chr_active = 0; break;
|
|
|
|
case 0x5128: chr_bg_bank[0] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
|
case 0x5129: chr_bg_bank[1] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
|
case 0x512a: chr_bg_bank[2] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
|
case 0x512b: chr_bg_bank[3] = (chr_bank_hi << 8) | data; chr_active = 1; break;
|
|
|
|
case 0x5130:
|
|
chr_bank_hi = data & 3;
|
|
break;
|
|
|
|
case 0x5200:
|
|
vs_enable = data & 0x80;
|
|
vs_side = data & 0x40;
|
|
vs_tile = data & 0x1f;
|
|
break;
|
|
|
|
case 0x5201:
|
|
vs_scroll = data;
|
|
break;
|
|
|
|
case 0x5202:
|
|
vs_bank = data;
|
|
break;
|
|
|
|
case 0x5203:
|
|
irq_line = data;
|
|
break;
|
|
|
|
case 0x5204:
|
|
irq_enable = data & 0x80;
|
|
break;
|
|
|
|
case 0x5205:
|
|
multiplicand = data;
|
|
break;
|
|
|
|
case 0x5206:
|
|
multiplier = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned chr_sprite_addr(unsigned addr) {
|
|
if(chr_mode == 0) {
|
|
auto bank = chr_sprite_bank[7];
|
|
return (bank * 0x2000) + (addr & 0x1fff);
|
|
}
|
|
|
|
if(chr_mode == 1) {
|
|
auto bank = chr_sprite_bank[(addr / 0x1000) * 4 + 3];
|
|
return (bank * 0x1000) + (addr & 0x0fff);
|
|
}
|
|
|
|
if(chr_mode == 2) {
|
|
auto bank = chr_sprite_bank[(addr / 0x0800) * 2 + 1];
|
|
return (bank * 0x0800) + (addr & 0x07ff);
|
|
}
|
|
|
|
if(chr_mode == 3) {
|
|
auto bank = chr_sprite_bank[(addr / 0x0400)];
|
|
return (bank * 0x0400) + (addr & 0x03ff);
|
|
}
|
|
}
|
|
|
|
unsigned chr_bg_addr(unsigned addr) {
|
|
addr &= 0x0fff;
|
|
|
|
if(chr_mode == 0) {
|
|
auto bank = chr_bg_bank[3];
|
|
return (bank * 0x2000) + (addr & 0x0fff);
|
|
}
|
|
|
|
if(chr_mode == 1) {
|
|
auto bank = chr_bg_bank[3];
|
|
return (bank * 0x1000) + (addr & 0x0fff);
|
|
}
|
|
|
|
if(chr_mode == 2) {
|
|
auto bank = chr_bg_bank[(addr / 0x0800) * 2 + 1];
|
|
return (bank * 0x0800) + (addr & 0x07ff);
|
|
}
|
|
|
|
if(chr_mode == 3) {
|
|
auto bank = chr_bg_bank[(addr / 0x0400)];
|
|
return (bank * 0x0400) + (addr & 0x03ff);
|
|
}
|
|
}
|
|
|
|
unsigned chr_vs_addr(unsigned addr) {
|
|
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
|
|
}
|
|
|
|
void blank() {
|
|
in_frame = false;
|
|
}
|
|
|
|
void scanline() {
|
|
hcounter = 0;
|
|
|
|
if(in_frame == false) {
|
|
in_frame = true;
|
|
irq_pending = false;
|
|
vcounter = 0;
|
|
} else {
|
|
if(vcounter == irq_line) irq_pending = true;
|
|
vcounter++;
|
|
}
|
|
|
|
cpu_cycle_counter = 0;
|
|
}
|
|
|
|
uint8 ciram_read(unsigned addr) {
|
|
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
|
|
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
|
|
|
|
switch(nametable_mode[(addr >> 10) & 3]) {
|
|
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
|
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
|
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
|
|
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
|
}
|
|
}
|
|
|
|
uint8 chr_read(unsigned addr) {
|
|
chr_access[0] = chr_access[1];
|
|
chr_access[1] = chr_access[2];
|
|
chr_access[2] = chr_access[3];
|
|
chr_access[3] = addr;
|
|
|
|
//detect two unused nametable fetches at end of each scanline
|
|
if((chr_access[0] & 0x2000) == 0
|
|
&& (chr_access[1] & 0x2000)
|
|
&& (chr_access[2] & 0x2000)
|
|
&& (chr_access[3] & 0x2000)) scanline();
|
|
|
|
if(in_frame == false) {
|
|
vs_fetch = false;
|
|
if(addr & 0x2000) return ciram_read(addr);
|
|
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
|
|
}
|
|
|
|
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
|
|
uint8 result = 0x00;
|
|
|
|
if((hcounter & 7) == 0) {
|
|
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
|
|
vs_vpos = vcounter + vs_scroll;
|
|
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
|
|
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
|
|
if(vs_vpos >= 240) vs_vpos -= 240;
|
|
|
|
result = ciram_read(addr);
|
|
|
|
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
|
|
exattr = exram[addr & 0x03ff] >> 6;
|
|
exattr |= exattr << 2;
|
|
exattr |= exattr << 4;
|
|
} else if((hcounter & 7) == 2) {
|
|
result = ciram_read(addr);
|
|
if(bg_fetch && exram_mode == 1) result = exattr;
|
|
} else {
|
|
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
|
|
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
|
|
else result = board.chrrom.read(chr_sprite_addr(addr));
|
|
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
|
|
}
|
|
|
|
hcounter += 2;
|
|
return result;
|
|
}
|
|
|
|
void chr_write(unsigned addr, uint8 data) {
|
|
if(addr & 0x2000) {
|
|
switch(nametable_mode[(addr >> 10) & 3]) {
|
|
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
|
|
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
|
|
case 2: exram[addr & 0x03ff] = data; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void power() {
|
|
}
|
|
|
|
void reset() {
|
|
for(auto& n : exram) n = 0xff;
|
|
|
|
prg_mode = 3;
|
|
chr_mode = 0;
|
|
for(auto& n : prgram_write_protect) n = 0;
|
|
exram_mode = 0;
|
|
for(auto& n : nametable_mode) n = 0;
|
|
fillmode_tile = 0;
|
|
fillmode_color = 0;
|
|
ram_select = 0;
|
|
ram_bank = 0;
|
|
prg_bank[0] = 0x00;
|
|
prg_bank[1] = 0x00;
|
|
prg_bank[2] = 0x00;
|
|
prg_bank[3] = 0xff;
|
|
for(auto& n : chr_sprite_bank) n = 0;
|
|
for(auto& n : chr_bg_bank) n = 0;
|
|
chr_bank_hi = 0;
|
|
vs_enable = 0;
|
|
vs_side = 0;
|
|
vs_tile = 0;
|
|
vs_scroll = 0;
|
|
vs_bank = 0;
|
|
irq_line = 0;
|
|
irq_enable = 0;
|
|
multiplicand = 0;
|
|
multiplier = 0;
|
|
|
|
cpu_cycle_counter = 0;
|
|
irq_counter = 0;
|
|
irq_pending = 0;
|
|
in_frame = 0;
|
|
vcounter = 0;
|
|
hcounter = 0;
|
|
for(auto& n : chr_access) n = 0;
|
|
chr_active = 0;
|
|
sprite_8x16 = 0;
|
|
|
|
exbank = 0;
|
|
exattr = 0;
|
|
|
|
vs_fetch = 0;
|
|
vs_vpos = 0;
|
|
vs_hpos = 0;
|
|
}
|
|
|
|
void serialize(serializer& s) {
|
|
s.array(exram);
|
|
|
|
s.integer(prg_mode);
|
|
s.integer(chr_mode);
|
|
for(auto& n : prgram_write_protect) s.integer(n);
|
|
s.integer(exram_mode);
|
|
for(auto& n : nametable_mode) s.integer(n);
|
|
s.integer(fillmode_tile);
|
|
s.integer(fillmode_color);
|
|
s.integer(ram_select);
|
|
s.integer(ram_bank);
|
|
for(auto& n : prg_bank) s.integer(n);
|
|
for(auto& n : chr_sprite_bank) s.integer(n);
|
|
for(auto& n : chr_bg_bank) s.integer(n);
|
|
s.integer(chr_bank_hi);
|
|
s.integer(vs_enable);
|
|
s.integer(vs_side);
|
|
s.integer(vs_tile);
|
|
s.integer(vs_scroll);
|
|
s.integer(vs_bank);
|
|
s.integer(irq_line);
|
|
s.integer(irq_enable);
|
|
s.integer(multiplicand);
|
|
s.integer(multiplier);
|
|
|
|
s.integer(cpu_cycle_counter);
|
|
s.integer(irq_counter);
|
|
s.integer(irq_pending);
|
|
s.integer(in_frame);
|
|
|
|
s.integer(vcounter);
|
|
s.integer(hcounter);
|
|
for(auto& n : chr_access) s.integer(n);
|
|
s.integer(chr_active);
|
|
s.integer(sprite_8x16);
|
|
|
|
s.integer(exbank);
|
|
s.integer(exattr);
|
|
|
|
s.integer(vs_fetch);
|
|
s.integer(vs_vpos);
|
|
s.integer(vs_hpos);
|
|
}
|
|
|
|
MMC5(Board& board) : Chip(board) {
|
|
revision = Revision::MMC5;
|
|
}
|
|
|
|
};
|