AxibugEmuOnline/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs

472 lines
14 KiB
C#
Raw Normal View History

2024-07-25 11:03:58 +08:00
#undef DPCM_SYNCCLOCK
using System;
2024-07-25 18:34:52 +08:00
using UnityEngine.UI;
2024-07-25 11:03:58 +08:00
namespace VirtualNes.Core
2024-07-23 18:31:59 +08:00
{
public class CPU
{
2024-07-25 18:34:52 +08:00
private static int nmicount;
2024-07-25 11:03:58 +08:00
// 6502 status flags
public const byte C_FLAG = 0x01; // 1: Carry
public const byte Z_FLAG = 0x02; // 1: Zero
public const byte I_FLAG = 0x04; // 1: Irq disabled
public const byte D_FLAG = 0x08; // 1: Decimal mode flag (NES unused)
public const byte B_FLAG = 0x10; // 1: Break
public const byte R_FLAG = 0x20; // 1: Reserved (Always 1)
public const byte V_FLAG = 0x40; // 1: Overflow
public const byte N_FLAG = 0x80; // 1: Negative
// Interrupt
public const byte NMI_FLAG = 0x01;
public const byte IRQ_FLAG = 0x02;
public const byte IRQ_FRAMEIRQ = 0x04;
public const byte IRQ_DPCM = 0x08;
public const byte IRQ_MAPPER = 0x10;
public const byte IRQ_MAPPER2 = 0x20;
public const byte IRQ_TRIGGER = 0x40; // one shot(媽IRQ())
public const byte IRQ_TRIGGER2 = 0x80; // one shot(媽IRQ_NotPending())
public static readonly byte IRQ_MASK = unchecked((byte)(~(NMI_FLAG | IRQ_FLAG)));
// Vector
public const ushort NMI_VECTOR = 0xFFFA;
public const ushort RES_VECTOR = 0xFFFC;
public const ushort IRQ_VECTOR = 0xFFFE;
private NES nes;
private bool m_bClockProcess;
private int TOTAL_cycles;
private int DMA_cycles;
private Mapper mapper;
private APU apu;
2024-07-25 18:34:52 +08:00
private R6502 R = new R6502();
2024-07-25 11:03:58 +08:00
private byte[] ZN_Table = new byte[256];
2024-07-23 18:31:59 +08:00
public CPU(NES parent)
{
2024-07-25 11:03:58 +08:00
nes = parent;
m_bClockProcess = false;
}
public void Dispose() { }
internal long EXEC(int request_cycles)
{
byte opcode = 0;
int OLD_cycles = TOTAL_cycles;
int exec_cycles = 0;
byte nmi_request = 0, irq_request = 0;
ushort EA = 0;
ushort ET = 0;
ushort WT = 0;
byte DT = 0;
while (request_cycles > 0)
{
exec_cycles = 0;
if (DMA_cycles > 0)
{
if (request_cycles <= DMA_cycles)
{
DMA_cycles -= request_cycles;
TOTAL_cycles += request_cycles;
mapper.Clock(request_cycles);
#if DPCM_SYNCCLOCK
apu.SyncDPCM(request_cycles);
#endif
if (m_bClockProcess)
{
nes.Clock(request_cycles);
}
goto _execute_exit;
}
else
{
exec_cycles += DMA_cycles;
DMA_cycles = 0;
}
}
nmi_request = irq_request = 0;
opcode = OP6502(R.PC++);
2024-07-25 18:34:52 +08:00
if (R.INT_pending != 0)
2024-07-25 11:03:58 +08:00
{
2024-07-25 18:34:52 +08:00
if ((R.INT_pending & NMI_FLAG) != 0)
2024-07-25 11:03:58 +08:00
{
nmi_request = 0xFF;
byte temp = unchecked((byte)(~NMI_FLAG));
2024-07-25 18:34:52 +08:00
R.INT_pending &= temp;
2024-07-25 11:03:58 +08:00
}
2024-07-25 18:34:52 +08:00
else if ((R.INT_pending & IRQ_MASK) != 0)
2024-07-25 11:03:58 +08:00
{
byte temp = unchecked((byte)(~IRQ_TRIGGER2));
2024-07-25 18:34:52 +08:00
R.INT_pending &= temp;
2024-07-25 11:03:58 +08:00
if (
((R.P & I_FLAG) == 0)
&&
(opcode != 0x40)
)
{
irq_request = 0xFF;
temp = unchecked((byte)(~IRQ_TRIGGER));
2024-07-25 18:34:52 +08:00
R.INT_pending &= temp;
2024-07-25 11:03:58 +08:00
}
}
}
switch (opcode)
{
case 0x69:
2024-07-25 18:34:52 +08:00
MR_IM(ref DT); ADC(ref WT, ref DT);
2024-07-25 11:03:58 +08:00
ADD_CYCLE(2, ref exec_cycles);
break;
case 0x65:
2024-07-25 18:34:52 +08:00
MR_ZP(ref EA, ref DT); ADC(ref WT, ref DT);
2024-07-25 11:03:58 +08:00
ADD_CYCLE(3, ref exec_cycles);
break;
case 0x75:
2024-07-25 18:34:52 +08:00
MR_ZX(ref DT, ref EA); ADC(ref WT, ref DT);
2024-07-25 11:03:58 +08:00
ADD_CYCLE(4, ref exec_cycles);
2024-07-25 14:03:52 +08:00
break;
case 0x6D:
2024-07-25 18:34:52 +08:00
MR_AB(ref EA, ref DT); ADC(ref WT, ref DT);
2024-07-25 14:03:52 +08:00
ADD_CYCLE(4, ref exec_cycles);
break;
case 0x7D:
2024-07-25 18:34:52 +08:00
MR_AX(ref ET, ref EA, ref DT); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0x79:
MR_AY(ref ET, ref EA, ref DT); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0x61:
MR_IX(ref DT, ref EA); ADC(ref WT, ref DT);
ADD_CYCLE(6, ref exec_cycles);
break;
case 0x71:
MR_IY(ref DT, ref ET, ref EA); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0xE9:
MR_IM(ref DT); SBC(ref WT, ref DT);
ADD_CYCLE(2, ref exec_cycles);
break;
case 0xE5:
MR_ZP(ref EA, ref DT); SBC(ref WT, ref DT);
ADD_CYCLE(3, ref exec_cycles);
break;
case 0xF5:
MR_ZX(ref DT, ref EA); SBC(ref WT, ref DT);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0xED:
MR_AB(ref EA, ref DT); SBC(ref WT, ref DT);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0xFD:
MR_AX(ref ET, ref EA, ref DT); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0xF9: // SBC $????,Y
MR_AY(ref ET, ref EA, ref DT); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0xE1: // SBC ($??,X)
MR_IX(ref DT, ref EA); SBC(ref WT, ref DT);
ADD_CYCLE(6, ref exec_cycles);
break;
case 0xF1: // SBC ($??),Y
MR_IY(ref DT, ref ET, ref EA); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles);
ADD_CYCLE(5, ref exec_cycles);
break;
case 0xC6: // DEC $??
MR_ZP(ref EA, ref DT); DEC(ref DT); MW_ZP(EA, DT);
ADD_CYCLE(5, ref exec_cycles);
break;
case 0xD6: // DEC $??,X
MR_ZX(ref DT, ref EA); DEC(ref DT); MW_ZP(EA, DT);
ADD_CYCLE(6, ref exec_cycles);
break;
case 0xCE: // DEC $????
MR_AB(ref EA, ref DT); DEC(ref DT); MW_EA(EA, DT);
ADD_CYCLE(6, ref exec_cycles);
2024-07-25 11:03:58 +08:00
break;
}
}
_execute_exit:
#if !DPCM_SYNCCLOCK
apu.SyncDPCM(TOTAL_cycles - OLD_cycles);
#endif
return TOTAL_cycles - OLD_cycles;
}
internal void SetClockProcess(bool bEnable)
{
m_bClockProcess = bEnable;
}
internal byte OP6502(ushort addr)
{
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
2024-07-25 14:03:52 +08:00
internal ushort OP6502W(ushort addr)
{
var bytePage = MMU.CPU_MEM_BANK[addr >> 13];
return BitConverter.ToUInt16(bytePage, addr & 0x1FFF);
}
internal byte RD6502(ushort addr)
{
if (addr < 0x2000)
{
// RAM (Mirror $0800, $1000, $1800)
return MMU.RAM[addr & 0x07FF];
}
else if (addr < 0x8000)
{
// Others
return nes.Read(addr);
}
else
{
// Dummy access
mapper.Read(addr, MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]);
}
// Quick bank read
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
2024-07-25 18:34:52 +08:00
private void MR_IM(ref byte DT)
2024-07-25 11:03:58 +08:00
{
DT = OP6502(R.PC++);
}
2024-07-25 18:34:52 +08:00
private void MR_ZP(ref ushort EA, ref byte DT)
2024-07-25 11:03:58 +08:00
{
EA = OP6502(R.PC++);
2024-07-25 18:34:52 +08:00
DT = ZPRD(EA);
2024-07-25 11:03:58 +08:00
}
2024-07-25 18:34:52 +08:00
private byte ZPRD(ushort A)
2024-07-25 11:03:58 +08:00
{
return MMU.RAM[A];
}
2024-07-25 18:34:52 +08:00
private ushort ZPRDW(int A)
{
ushort ram1 = MMU.RAM[A];
ushort ram2 = MMU.RAM[A + 1];
ram2 <<= 8;
return (ushort)(ram1 + ram2);
}
private void ADC(ref ushort WT, ref byte DT)
2024-07-25 11:03:58 +08:00
{
WT = (ushort)(R.A + DT + (R.P & C_FLAG));
2024-07-25 18:34:52 +08:00
TST_FLAG(WT > 0xFF, C_FLAG);
2024-07-25 11:03:58 +08:00
var temp = ((~(R.A ^ DT)) & (R.A ^ WT) & 0x80);
2024-07-25 18:34:52 +08:00
TST_FLAG(temp != 0, V_FLAG);
2024-07-25 11:03:58 +08:00
R.A = (byte)WT;
2024-07-25 18:34:52 +08:00
SET_ZN_FLAG(R.A);
2024-07-25 11:03:58 +08:00
}
2024-07-25 18:34:52 +08:00
private void TST_FLAG(bool F, byte V)
2024-07-25 11:03:58 +08:00
{
byte temp = (byte)~V;
R.P &= temp;
if (F) R.P |= V;
}
2024-07-25 18:34:52 +08:00
private void SET_ZN_FLAG(byte A)
2024-07-25 11:03:58 +08:00
{
byte temp = unchecked((byte)(~(Z_FLAG | N_FLAG)));
R.P &= temp;
R.P |= ZN_Table[A];
}
private void ADD_CYCLE(int V, ref int exec_cycles)
{
exec_cycles += V;
}
2024-07-25 18:34:52 +08:00
private void MR_ZX(ref byte DT, ref ushort EA)
2024-07-25 11:03:58 +08:00
{
DT = OP6502(R.PC++);
EA = (ushort)(DT + R.X);
2024-07-25 18:34:52 +08:00
DT = ZPRD(EA);
2024-07-23 18:31:59 +08:00
}
2024-07-25 14:03:52 +08:00
2024-07-25 18:34:52 +08:00
private void MR_AB(ref ushort EA, ref byte DT)
2024-07-25 14:03:52 +08:00
{
EA = OP6502W(R.PC);
R.PC += 2;
DT = RD6502(EA);
}
2024-07-25 18:34:52 +08:00
private void MR_AX(ref ushort ET, ref ushort EA, ref byte DT)
{
ET = OP6502W(R.PC);
R.PC += 2;
EA = (byte)(ET + R.X);
DT = RD6502(EA);
}
private void CHECK_EA(ref ushort EA, ref ushort ET, ref int exec_cycles)
{
if ((ET & 0xFF00) != (EA & 0xFF00)) ADD_CYCLE(1, ref exec_cycles);
}
private void MR_AY(ref ushort ET, ref ushort EA, ref byte DT)
{
ET = OP6502W(R.PC);
R.PC += 2;
EA = (ushort)(ET + R.Y);
DT = RD6502(EA);
}
private void MR_IX(ref byte DT, ref ushort EA)
{
DT = OP6502(R.PC++);
EA = ZPRDW(DT + R.X);
DT = RD6502(EA);
}
private void MR_IY(ref byte DT, ref ushort ET, ref ushort EA)
{
DT = OP6502(R.PC++);
ET = ZPRDW(DT);
EA = (ushort)(ET + R.Y);
DT = RD6502(EA);
}
private void SBC(ref ushort WT, ref byte DT)
{
WT = (ushort)(R.A - DT - (~R.P & C_FLAG));
bool f = ((R.A ^ DT) & (R.A ^ WT) & (0x80)) != 0;
TST_FLAG(f, V_FLAG);
TST_FLAG(WT < 0x100, C_FLAG);
R.A = (byte)WT;
SET_ZN_FLAG(R.A);
}
private void DEC(ref byte DT)
{
DT--;
SET_ZN_FLAG(DT);
}
private void MW_ZP(ushort EA, byte DT)
{
ZPWR(EA, DT);
}
private void ZPWR(ushort a, byte v)
{
MMU.RAM[a] = v;
}
private void MW_EA(ushort EA, byte DT)
{
WR6502(EA, DT);
}
2024-07-25 14:03:52 +08:00
internal void ClrIRQ(byte mask)
{
byte temp = (byte)~mask;
2024-07-25 18:34:52 +08:00
R.INT_pending &= temp;
}
internal void WR6502(ushort addr, byte data)
{
if (addr < 0x2000)
{
// RAM (Mirror $0800, $1000, $1800)
MMU.RAM[addr & 0x07FF] = data;
}
else
{
// Others
nes.Write(addr, data);
}
}
internal void NMI()
{
R.INT_pending |= NMI_FLAG;
nmicount = 0;
}
internal void SetIRQ(byte mask)
{
R.INT_pending |= mask;
}
internal int GetTotalCycles()
{
return TOTAL_cycles;
}
internal void DMA(int cycles)
{
DMA_cycles += cycles;
2024-07-25 14:03:52 +08:00
}
2024-07-23 18:31:59 +08:00
}
public enum StatusFlag6502 : int
{
C_FLAG = 0x01,
Z_FLAG = 0x02,
I_FLAG = 0x04,
D_FLAG = 0x08,
B_FLAG = 0x10,
R_FLAG = 0x20,
V_FLAG = 0x40,
N_FLAG = 0x80
}
public enum Interrupt : int
{
NMI_FLAG = 0x01,
IRQ_FLAG = 0x02,
IRQ_FRAMEIRQ = 0x04,
IRQ_DPCM = 0x08,
IRQ_MAPPER = 0x10,
IRQ_MAPPER2 = 0x20,
IRQ_TRIGGER = 0x40,
IRQ_TRIGGER2 = 0x80,
IRQ_MASK = (~(NMI_FLAG | IRQ_FLAG)),
}
public enum Vector : int
{
NMI_VECTOR = 0xFFFA,
RES_VECTOR = 0xFFFC,
IRQ_VECTOR = 0xFFFE
}
2024-07-25 18:34:52 +08:00
public class R6502
2024-07-23 18:31:59 +08:00
{
2024-07-25 11:03:58 +08:00
public ushort PC;
public byte A;
public byte P;
public byte X;
public byte Y;
public byte S;
2024-07-25 18:34:52 +08:00
public byte INT_pending;
2024-07-23 18:31:59 +08:00
}
}