bsnes/higan/fc/cartridge/chip/mmc5.cpp

487 lines
12 KiB
C++

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(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.irqLine(irqEnable && irqPending);
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 accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
uint bank;
if((addr & 0xe000) == 0x6000) {
bank = (ramSelect << 2) | ramBank;
addr &= 0x1fff;
} else if(prgMode == 0) {
bank = prgBank[3] & ~3;
addr &= 0x7fff;
} else if(prgMode == 1) {
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
addr &= 0x3fff;
} else if(prgMode == 2) {
if((addr & 0xe000) == 0x8000) bank = (prgBank[1] & ~1) | 0;
if((addr & 0xe000) == 0xa000) bank = (prgBank[1] & ~1) | 1;
if((addr & 0xe000) == 0xc000) bank = (prgBank[2]);
if((addr & 0xe000) == 0xe000) bank = (prgBank[3]);
addr &= 0x1fff;
} else if(prgMode == 3) {
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
if((addr & 0xe000) == 0xe000) bank = prgBank[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(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
board.prgram.write((bank << 13) | addr, data);
}
}
return 0x00;
}
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xfc00) == 0x5c00) {
if(exramMode >= 2) return exram[addr & 0x03ff];
return cpu.mdr();
}
if(addr >= 0x6000) {
return accessPRG(0, addr);
}
switch(addr) {
case 0x5204: {
uint8 result = (irqPending << 7) | (inFrame << 6);
irqPending = false;
return result;
}
case 0x5205: return (multiplier * multiplicand) >> 0;
case 0x5206: return (multiplier * multiplicand) >> 8;
}
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
if(exramMode == 2) exram[addr & 0x03ff] = data;
return;
}
if(addr >= 0x6000) {
accessPRG(1, addr, data);
return;
}
switch(addr) {
case 0x2000:
sprite8x16 = data & 0x20;
break;
case 0x2001:
//if BG+sprites are disabled; enter video blanking period
if((data & 0x18) == 0) blank();
break;
case 0x5100: prgMode = data & 3; break;
case 0x5101: chrMode = data & 3; break;
case 0x5102: prgramWriteProtect[0] = data & 3; break;
case 0x5103: prgramWriteProtect[1] = data & 3; break;
case 0x5104:
exramMode = data & 3;
break;
case 0x5105:
nametableMode[0] = (data & 0x03) >> 0;
nametableMode[1] = (data & 0x0c) >> 2;
nametableMode[2] = (data & 0x30) >> 4;
nametableMode[3] = (data & 0xc0) >> 6;
break;
case 0x5106:
fillmodeTile = data;
break;
case 0x5107:
fillmodeColor = data & 3;
fillmodeColor |= fillmodeColor << 2;
fillmodeColor |= fillmodeColor << 4;
break;
case 0x5113:
ramSelect = data & 0x04;
ramBank = data & 0x03;
break;
case 0x5114: prgBank[0] = data; break;
case 0x5115: prgBank[1] = data; break;
case 0x5116: prgBank[2] = data; break;
case 0x5117: prgBank[3] = data | 0x80; break;
case 0x5120: chrSpriteBank[0] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5121: chrSpriteBank[1] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5122: chrSpriteBank[2] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5123: chrSpriteBank[3] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5124: chrSpriteBank[4] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5125: chrSpriteBank[5] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5126: chrSpriteBank[6] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5127: chrSpriteBank[7] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5128: chrBGBank[0] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5129: chrBGBank[1] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512a: chrBGBank[2] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512b: chrBGBank[3] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5130:
chrBankHi = data & 3;
break;
case 0x5200:
vsEnable = data & 0x80;
vsSide = data & 0x40;
vsTile = data & 0x1f;
break;
case 0x5201:
vsScroll = data;
break;
case 0x5202:
vsBank = data;
break;
case 0x5203:
irqLine = data;
break;
case 0x5204:
irqEnable = data & 0x80;
break;
case 0x5205:
multiplicand = data;
break;
case 0x5206:
multiplier = data;
break;
}
}
auto chrSpriteAddr(uint addr) -> uint {
if(chrMode == 0) {
auto bank = chrSpriteBank[7];
return (bank * 0x2000) + (addr & 0x1fff);
}
if(chrMode == 1) {
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrSpriteBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
auto chrBGAddr(uint addr) -> uint {
addr &= 0x0fff;
if(chrMode == 0) {
auto bank = chrBGBank[3];
return (bank * 0x2000) + (addr & 0x0fff);
}
if(chrMode == 1) {
auto bank = chrBGBank[3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrBGBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
}
auto chrVSAddr(uint addr) -> uint {
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
}
auto blank() -> void {
inFrame = false;
}
auto scanline() -> void {
hcounter = 0;
if(inFrame == false) {
inFrame = true;
irqPending = false;
vcounter = 0;
} else {
if(vcounter == irqLine) irqPending = true;
vcounter++;
}
cpuCycleCounter = 0;
}
auto readCIRAM(uint addr) -> uint8 {
if(vsFetch && (hcounter & 2) == 0) return exram[vsVpos / 8 * 32 + vsHpos / 8];
if(vsFetch && (hcounter & 2) != 0) return exram[vsVpos / 32 * 8 + vsHpos / 32 + 0x03c0];
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.readCIRAM(0x0000 | (addr & 0x03ff));
case 1: return ppu.readCIRAM(0x0400 | (addr & 0x03ff));
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
}
}
auto readCHR(uint addr) -> uint8 {
chrAccess[0] = chrAccess[1];
chrAccess[1] = chrAccess[2];
chrAccess[2] = chrAccess[3];
chrAccess[3] = addr;
//detect two unused nametable fetches at end of each scanline
if((chrAccess[0] & 0x2000) == 0
&& (chrAccess[1] & 0x2000)
&& (chrAccess[2] & 0x2000)
&& (chrAccess[3] & 0x2000)) scanline();
if(inFrame == false) {
vsFetch = false;
if(addr & 0x2000) return readCIRAM(addr);
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
}
bool bgFetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vsHpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vsVpos = vcounter + vsScroll;
vsFetch = vsEnable && bgFetch && exramMode < 2
&& (vsSide ? vsHpos / 8 >= vsTile : vsHpos / 8 < vsTile);
if(vsVpos >= 240) vsVpos -= 240;
result = readCIRAM(addr);
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
exattr = exram[addr & 0x03ff] >> 6;
exattr |= exattr << 2;
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = readCIRAM(addr);
if(bgFetch && exramMode == 1) result = exattr;
} else {
if(vsFetch) result = board.chrrom.read(chrVSAddr(addr));
else if(sprite8x16 ? bgFetch : chrActive) result = board.chrrom.read(chrBGAddr(addr));
else result = board.chrrom.read(chrSpriteAddr(addr));
if(bgFetch && exramMode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
return result;
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.writeCIRAM(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.writeCIRAM(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
auto power() -> void {
for(auto& n : exram) n = 0xff;
prgMode = 3;
chrMode = 0;
for(auto& n : prgramWriteProtect) n = 0;
exramMode = 0;
for(auto& n : nametableMode) n = 0;
fillmodeTile = 0;
fillmodeColor = 0;
ramSelect = 0;
ramBank = 0;
prgBank[0] = 0x00;
prgBank[1] = 0x00;
prgBank[2] = 0x00;
prgBank[3] = 0xff;
for(auto& n : chrSpriteBank) n = 0;
for(auto& n : chrBGBank) n = 0;
chrBankHi = 0;
vsEnable = 0;
vsSide = 0;
vsTile = 0;
vsScroll = 0;
vsBank = 0;
irqLine = 0;
irqEnable = 0;
multiplicand = 0;
multiplier = 0;
cpuCycleCounter = 0;
irqCounter = 0;
irqPending = 0;
inFrame = 0;
vcounter = 0;
hcounter = 0;
for(auto& n : chrAccess) n = 0;
chrActive = 0;
sprite8x16 = 0;
exbank = 0;
exattr = 0;
vsFetch = 0;
vsVpos = 0;
vsHpos = 0;
}
auto serialize(serializer& s) -> void {
s.array(exram);
s.integer(prgMode);
s.integer(chrMode);
for(auto& n : prgramWriteProtect) s.integer(n);
s.integer(exramMode);
for(auto& n : nametableMode) s.integer(n);
s.integer(fillmodeTile);
s.integer(fillmodeColor);
s.integer(ramSelect);
s.integer(ramBank);
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrSpriteBank) s.integer(n);
for(auto& n : chrBGBank) s.integer(n);
s.integer(chrBankHi);
s.integer(vsEnable);
s.integer(vsSide);
s.integer(vsTile);
s.integer(vsScroll);
s.integer(vsBank);
s.integer(irqLine);
s.integer(irqEnable);
s.integer(multiplicand);
s.integer(multiplier);
s.integer(cpuCycleCounter);
s.integer(irqCounter);
s.integer(irqPending);
s.integer(inFrame);
s.integer(vcounter);
s.integer(hcounter);
for(auto& n : chrAccess) s.integer(n);
s.integer(chrActive);
s.integer(sprite8x16);
s.integer(exbank);
s.integer(exattr);
s.integer(vsFetch);
s.integer(vsVpos);
s.integer(vsHpos);
}
enum class Revision : uint {
MMC5,
MMC5B,
} revision;
uint8 exram[1024];
//programmable registers
uint2 prgMode; //$5100
uint2 chrMode; //$5101
uint2 prgramWriteProtect[2]; //$5102,$5103
uint2 exramMode; //$5104
uint2 nametableMode[4]; //$5105
uint8 fillmodeTile; //$5106
uint8 fillmodeColor; //$5107
bool ramSelect; //$5113
uint2 ramBank; //$5113
uint8 prgBank[4]; //$5114-5117
uint10 chrSpriteBank[8]; //$5120-5127
uint10 chrBGBank[4]; //$5128-512b
uint2 chrBankHi; //$5130
bool vsEnable; //$5200
bool vsSide; //$5200
uint5 vsTile; //$5200
uint8 vsScroll; //$5201
uint8 vsBank; //$5202
uint8 irqLine; //$5203
bool irqEnable; //$5204
uint8 multiplicand; //$5205
uint8 multiplier; //$5206
//status registers
uint cpuCycleCounter;
uint irqCounter;
bool irqPending;
bool inFrame;
uint vcounter;
uint hcounter;
uint16 chrAccess[4];
bool chrActive;
bool sprite8x16;
uint8 exbank;
uint8 exattr;
bool vsFetch;
uint8 vsVpos;
uint8 vsHpos;
};