391 lines
8.6 KiB
C#
391 lines
8.6 KiB
C#
using System;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common.Components;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|
{
|
|
//mapper 24 + 26
|
|
//If you change any of the IRQ logic here, be sure to change it in VRC 4/7 as well.
|
|
public sealed class VRC6 : NES.NESBoardBase
|
|
{
|
|
#region CHRLUT
|
|
// what did i do in a previous life to deserve this?
|
|
|
|
// given the bottom four bits of $b003, and a 1K address region in PPU $0000:$3fff,
|
|
static byte[] Banks = new byte[16 * 16]; // which of the 8 chr regs is used to determine the bank here?
|
|
static byte[] Masks = new byte[16 * 16]; // what is the resulting 8 bit chr reg value ANDed with?
|
|
static byte[] A10s = new byte[16 * 16]; // and then what is it ORed with?
|
|
|
|
static byte[] PTables = new byte[]
|
|
{
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
|
0x80,0xc0,0x81,0xc1,0x82,0xc2,0x83,0xc3,
|
|
0x00,0x01,0x02,0x03,0x84,0xc4,0x85,0xc5,
|
|
};
|
|
|
|
static void GetBankByte(int b003, int banknum, out byte bank, out byte mask, out byte a10)
|
|
{
|
|
if (banknum < 8) // pattern tables
|
|
{
|
|
int ptidx = b003 & 3;
|
|
if (ptidx == 3) ptidx--;
|
|
byte pt = PTables[ptidx * 8 + banknum];
|
|
|
|
bank = (byte)(pt & 7);
|
|
mask = (byte)(pt.Bit(7) ? 0xfe : 0xff);
|
|
a10 = (byte)(pt.Bit(7) && pt.Bit(6) ? 1 : 0);
|
|
}
|
|
else // nametables
|
|
{
|
|
banknum &= 3;
|
|
switch (b003 & 7)
|
|
{
|
|
case 0:
|
|
case 6:
|
|
case 7: // H-mirror, 6677
|
|
bank = (byte)(banknum >> 1 | 6);
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4: // V-mirror, 6767
|
|
bank = (byte)(banknum | 6);
|
|
break;
|
|
case 1:
|
|
case 5: // 4 screen, 4567
|
|
default:
|
|
bank = (byte)(banknum | 4);
|
|
break;
|
|
}
|
|
switch (b003)
|
|
{
|
|
case 0:
|
|
case 7: // V-mirror
|
|
mask = 0xfe;
|
|
a10 = (byte)(banknum & 1);
|
|
break;
|
|
case 3:
|
|
case 4: // H-mirror
|
|
mask = 0xfe;
|
|
a10 = (byte)(banknum >> 1);
|
|
break;
|
|
case 8:
|
|
case 15: // 1scA
|
|
mask = 0xfe;
|
|
a10 = 0;
|
|
break;
|
|
case 11:
|
|
case 12: // 1scB
|
|
mask = 0xfe;
|
|
a10 = 1;
|
|
break;
|
|
default: // no replacement
|
|
mask = 0xff;
|
|
a10 = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VRC6()
|
|
{
|
|
int idx = 0;
|
|
byte bank, mask, a10;
|
|
for (int b003 = 0; b003 < 16; b003++)
|
|
{
|
|
for (int banknum = 0; banknum < 16; banknum++)
|
|
{
|
|
GetBankByte(b003, banknum, out bank, out mask, out a10);
|
|
Banks[idx] = bank;
|
|
Masks[idx] = mask;
|
|
A10s[idx] = a10;
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//configuration
|
|
int prg_bank_mask_8k, chr_bank_mask_1k;
|
|
int chr_byte_mask;
|
|
bool newer_variant;
|
|
|
|
VRC6Alt VRC6Sound;
|
|
|
|
//state
|
|
int prg_bank_16k, prg_bank_8k;
|
|
ByteBuffer prg_banks_8k = new ByteBuffer(4);
|
|
ByteBuffer chr_banks_1k = new ByteBuffer(8);
|
|
bool irq_mode;
|
|
bool irq_enabled, irq_pending, irq_autoen;
|
|
byte irq_reload;
|
|
byte irq_counter;
|
|
int irq_prescaler;
|
|
|
|
bool chrA10replace;
|
|
bool NTROM;
|
|
int PPUBankingMode;
|
|
|
|
public override void Dispose()
|
|
{
|
|
base.Dispose();
|
|
prg_banks_8k.Dispose();
|
|
chr_banks_1k.Dispose();
|
|
}
|
|
|
|
public override void SyncState(Serializer ser)
|
|
{
|
|
base.SyncState(ser);
|
|
VRC6Sound.SyncState(ser);
|
|
ser.Sync("prg_bank_16k", ref prg_bank_16k);
|
|
ser.Sync("prg_bank_8k", ref prg_bank_8k);
|
|
ser.Sync("chr_banks_1k", ref chr_banks_1k);
|
|
ser.Sync("irq_mode", ref irq_mode);
|
|
ser.Sync("irq_enabled", ref irq_enabled);
|
|
ser.Sync("irq_pending", ref irq_pending);
|
|
ser.Sync("irq_autoen", ref irq_autoen);
|
|
ser.Sync("irq_reload", ref irq_reload);
|
|
ser.Sync("irq_counter", ref irq_counter);
|
|
ser.Sync("irq_prescaler", ref irq_prescaler);
|
|
|
|
ser.Sync("chrA10replace", ref chrA10replace);
|
|
ser.Sync("NTROM", ref NTROM);
|
|
ser.Sync("PPUBankingMode", ref PPUBankingMode);
|
|
|
|
SyncPRG();
|
|
SyncIRQ();
|
|
}
|
|
|
|
void SyncPRG()
|
|
{
|
|
prg_banks_8k[0] = (byte)(prg_bank_16k * 2);
|
|
prg_banks_8k[1] = (byte)(prg_bank_16k * 2 + 1);
|
|
prg_banks_8k[2] = (byte)(prg_bank_8k);
|
|
prg_banks_8k[3] = 0xFF;
|
|
}
|
|
|
|
void SyncIRQ()
|
|
{
|
|
IRQSignal = (irq_pending && irq_enabled);
|
|
}
|
|
|
|
public override bool Configure(NES.EDetectionOrigin origin)
|
|
{
|
|
switch (Cart.board_type)
|
|
{
|
|
case "MAPPER024":
|
|
newer_variant = false;
|
|
break;
|
|
case "MAPPER026":
|
|
newer_variant = true;
|
|
break;
|
|
case "KONAMI-VRC-6":
|
|
if (Cart.pcb == "351951")
|
|
newer_variant = false;
|
|
else if (Cart.pcb == "351949A")
|
|
newer_variant = true;
|
|
else throw new Exception("Unknown PCB type for VRC6");
|
|
AssertPrg(256); AssertChr(128, 256);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
AssertVram(0); AssertWram(0, 8);
|
|
|
|
prg_bank_mask_8k = Cart.prg_size / 8 - 1;
|
|
chr_bank_mask_1k = Cart.chr_size - 1;
|
|
chr_byte_mask = Cart.chr_size * 1024 - 1;
|
|
|
|
prg_bank_16k = 0;
|
|
prg_bank_8k = 0;
|
|
SyncPRG();
|
|
|
|
if (NES.apu != null) // don't start up sound when in configurator
|
|
VRC6Sound = new VRC6Alt((uint)NES.cpuclockrate, NES.apu.ExternalQueue);
|
|
|
|
return true;
|
|
}
|
|
public override byte ReadPRG(int addr)
|
|
{
|
|
int bank_8k = addr >> 13;
|
|
int ofs = addr & ((1 << 13) - 1);
|
|
bank_8k = prg_banks_8k[bank_8k];
|
|
bank_8k &= prg_bank_mask_8k;
|
|
addr = (bank_8k << 13) | ofs;
|
|
return ROM[addr];
|
|
}
|
|
|
|
int MapPPU(int addr)
|
|
{
|
|
int lutidx = addr >> 10 | PPUBankingMode << 4;
|
|
int bank = chr_banks_1k[Banks[lutidx]];
|
|
if (chrA10replace)
|
|
{
|
|
bank &= Masks[lutidx];
|
|
bank |= A10s[lutidx];
|
|
}
|
|
return addr & 0x3ff | bank << 10;
|
|
}
|
|
|
|
public override byte ReadPPU(int addr)
|
|
{
|
|
if (addr >= 0x2000 && !NTROM)
|
|
return NES.CIRAM[MapPPU(addr) & 0x7ff];
|
|
else
|
|
return VROM[MapPPU(addr) & chr_byte_mask];
|
|
}
|
|
|
|
public override void WritePPU(int addr, byte value)
|
|
{
|
|
if (addr >= 0x2000 && !NTROM)
|
|
NES.CIRAM[MapPPU(addr) & 0x7ff] = value;
|
|
}
|
|
|
|
public override void WritePRG(int addr, byte value)
|
|
{
|
|
if (newer_variant)
|
|
{
|
|
addr = (addr & 0xFFFC) | ((addr >> 1) & 1) | ((addr << 1) & 2);
|
|
}
|
|
switch (addr)
|
|
{
|
|
case 0x0000: //$8000
|
|
case 0x0001:
|
|
case 0x0002:
|
|
case 0x0003:
|
|
prg_bank_16k = value;
|
|
SyncPRG();
|
|
break;
|
|
|
|
case 0x1000: //$9000
|
|
VRC6Sound.Write9000(value);
|
|
break;
|
|
case 0x1001: //$9001
|
|
VRC6Sound.Write9001(value);
|
|
break;
|
|
case 0x1002: //$9002
|
|
VRC6Sound.Write9002(value);
|
|
break;
|
|
case 0x1003: //$9003
|
|
VRC6Sound.Write9003(value);
|
|
break;
|
|
|
|
case 0x2000: //$A000
|
|
VRC6Sound.WriteA000(value);
|
|
break;
|
|
case 0x2001: //$A001
|
|
VRC6Sound.WriteA001(value);
|
|
break;
|
|
case 0x2002: //$A002
|
|
VRC6Sound.WriteA002(value);
|
|
break;
|
|
|
|
case 0x3000: //$B000
|
|
VRC6Sound.WriteB000(value);
|
|
break;
|
|
case 0x3001: //$B001
|
|
VRC6Sound.WriteB001(value);
|
|
break;
|
|
case 0x3002: //$B002
|
|
VRC6Sound.WriteB002(value);
|
|
break;
|
|
|
|
case 0x3003: //$B003
|
|
PPUBankingMode = value & 15;
|
|
NTROM = value.Bit(4);
|
|
chrA10replace = value.Bit(5);
|
|
break;
|
|
|
|
case 0x4000: //$C000
|
|
case 0x4001:
|
|
case 0x4002:
|
|
case 0x4003:
|
|
prg_bank_8k = value;
|
|
SyncPRG();
|
|
break;
|
|
|
|
case 0x5000: //$D000
|
|
case 0x5001: //$D001
|
|
case 0x5002: //$D002
|
|
case 0x5003: //$D003
|
|
chr_banks_1k[addr - 0x5000] = value;
|
|
break;
|
|
|
|
case 0x6000: //$E000
|
|
case 0x6001: //$E001
|
|
case 0x6002: //$E002
|
|
case 0x6003: //$E003
|
|
chr_banks_1k[4 + addr - 0x6000] = value;
|
|
break;
|
|
|
|
case 0x7000: //$F000 (reload)
|
|
irq_reload = value;
|
|
break;
|
|
case 0x7001: //$F001 (control)
|
|
irq_mode = value.Bit(2);
|
|
irq_autoen = value.Bit(0);
|
|
|
|
if (value.Bit(1))
|
|
{
|
|
//enabled
|
|
irq_enabled = true;
|
|
irq_counter = irq_reload;
|
|
irq_prescaler = 341;
|
|
}
|
|
else
|
|
{
|
|
//disabled
|
|
irq_enabled = false;
|
|
}
|
|
|
|
//acknowledge
|
|
irq_pending = false;
|
|
|
|
SyncIRQ();
|
|
|
|
break;
|
|
|
|
case 0x7002: //$F002 (ack)
|
|
irq_pending = false;
|
|
irq_enabled = irq_autoen;
|
|
SyncIRQ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClockIRQ()
|
|
{
|
|
if (irq_counter == 0xFF)
|
|
{
|
|
irq_pending = true;
|
|
irq_counter = irq_reload;
|
|
SyncIRQ();
|
|
}
|
|
else
|
|
irq_counter++;
|
|
}
|
|
|
|
public override void ClockCPU()
|
|
{
|
|
VRC6Sound.Clock();
|
|
|
|
if (!irq_enabled) return;
|
|
|
|
if (irq_mode)
|
|
{
|
|
ClockIRQ();
|
|
}
|
|
else
|
|
{
|
|
irq_prescaler -= 3;
|
|
if (irq_prescaler <= 0)
|
|
{
|
|
irq_prescaler += 341;
|
|
ClockIRQ();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|