mirror of https://github.com/bsnes-emu/bsnes.git
487 lines
12 KiB
C++
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;
|
|
};
|