struct MMC5 : Chip { MMC5(Board& board) : Chip(board) { revision = Revision::MMC5; } auto main() -> void { //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.irqLine(irq_enable && irq_pending); tick(); } auto scanline(uint y) -> void { //used for testing only, to verify MMC5 scanline detection is accurate: //if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n"); } auto prg_access(bool write, uint addr, uint8 data = 0x00) -> uint8 { uint 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; } } auto prg_read(uint addr) -> uint8 { 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; } } auto prg_write(uint addr, uint8 data) -> void { 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 : (uint8)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; } } auto chr_sprite_addr(uint addr) -> uint { 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); } } auto chr_bg_addr(uint addr) -> uint { 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); } } auto chr_vs_addr(uint addr) -> uint { return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7); } auto blank() -> void { in_frame = false; } auto scanline() -> void { 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; } auto ciram_read(uint addr) -> uint8 { 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] : (uint8)0x00; case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color; } } auto chr_read(uint addr) -> uint8 { 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; } auto chr_write(uint addr, uint8 data) -> void { 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; } } } auto power() -> void { } auto reset() -> void { 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; } auto serialize(serializer& s) -> void { 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); } enum class Revision : uint { 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 uint cpu_cycle_counter; uint irq_counter; bool irq_pending; bool in_frame; uint vcounter; uint hcounter; uint16 chr_access[4]; bool chr_active; bool sprite_8x16; uint8 exbank; uint8 exattr; bool vs_fetch; uint8 vs_vpos; uint8 vs_hpos; };