using BizHawk.Emulation.CPUs.M6502; using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace BizHawk.Emulation.Computers.Commodore64.MOS { // an extension of the 6502 processor public class MOS6510 { // ------------------------------------ private MOS6502X cpu; private List disposeList = new List(); private bool freezeCpu; private bool pinCassetteButton; // note: these are only private bool pinCassetteMotor; // latches! private bool pinCassetteOutput; private bool pinCharen; private bool pinLoram; private bool pinHiram; private bool pinNMILast; private byte portDir; private bool unusedPin0; private bool unusedPin1; private uint unusedPinTTL0; private uint unusedPinTTL1; private uint unusedPinTTLCycles; public Func PeekMemory; public Action PokeMemory; public Func ReadAEC; public Func ReadCassetteButton; public Func ReadIRQ; public Func ReadNMI; public Func ReadRDY; public Func ReadMemory; public Action WriteCassetteLevel; public Action WriteCassetteMotor; public Action WriteMemory; // ------------------------------------ public MOS6510() { cpu = new MOS6502X(); // configure cpu r/w cpu.DummyReadMemory = Read; cpu.ReadMemory = Read; cpu.WriteMemory = Write; // todo: verify this value (I only know that unconnected bits fade after a number of cycles) unusedPinTTLCycles = 40; // perform hard reset HardReset(); } ~MOS6510() { foreach (GCHandle handle in disposeList) { handle.Free(); } } public void HardReset() { // configure CPU defaults cpu.Reset(); cpu.FlagI = true; cpu.BCD_Enabled = true; if (ReadMemory != null) cpu.PC = (ushort)(ReadMemory(0xFFFC) | (ReadMemory(0xFFFD) << 8)); // configure data port defaults portDir = 0x00; SetPortData(0x1F); // NMI is high on startup (todo: verify) pinNMILast = true; // reset unused IO pin TTLs unusedPinTTL0 = 0; unusedPinTTL1 = 0; } // ------------------------------------ public void ExecutePhase1() { } public void ExecutePhase2() { if (ReadAEC() && !freezeCpu) { // the 6502 core expects active high // so we reverse the polarity here bool thisNMI = ReadNMI(); if (!thisNMI && pinNMILast) cpu.NMI = true; else cpu.NMI = false; pinNMILast = thisNMI; cpu.IRQ = !ReadIRQ(); cpu.ExecuteOne(); } // unfreeze cpu if BA is high if (ReadRDY()) freezeCpu = false; // process unused pin TTL if (unusedPinTTL0 == 0) unusedPin0 = false; else unusedPinTTL0--; if (unusedPinTTL1 == 0) unusedPin1 = false; else unusedPinTTL1--; } // ------------------------------------ public ushort PC { get { return cpu.PC; } } public byte Peek(int addr) { if (addr == 0x0000) return PortDirection; else if (addr == 0x0001) return PortData; else return PeekMemory(addr); } public void Poke(int addr, byte val) { if (addr == 0x0000) SetPortDir(val); else if (addr == 0x0001) SetPortData(val); else PokeMemory(addr, val); } public byte Read(ushort addr) { // cpu freezes after first read when RDY is low if (!ReadRDY()) freezeCpu = true; if (addr == 0x0000) return PortDirection; else if (addr == 0x0001) return PortData; else return ReadMemory(addr); } public void Write(ushort addr, byte val) { if (addr == 0x0000) PortDirection = val; else if (addr == 0x0001) PortData = val; WriteMemory(addr, val); } // ------------------------------------ public bool CassetteButton { get { return pinCassetteButton; } set { pinCassetteButton = value; } } public bool CassetteMotor { get { return pinCassetteMotor; } } public bool CassetteOutputLevel { get { return pinCassetteOutput; } } public bool Charen { get { return pinCharen; } } public bool HiRam { get { return pinHiram; } } public bool LoRam { get { return pinLoram; } } public byte PortData { get { //byte result = 0x00; byte result = (byte)(~portDir & 0xEF); result |= pinLoram ? (byte)0x01 : (byte)0x00; result |= pinHiram ? (byte)0x02 : (byte)0x00; result |= pinCharen ? (byte)0x04 : (byte)0x00; result |= pinCassetteOutput ? (byte)0x08 : (byte)0x00; result |= pinCassetteButton ? (byte)0x10 : (byte)0x00; result |= pinCassetteMotor ? (byte)0x20 : (byte)0x00; result |= unusedPin0 ? (byte)0x40 : (byte)0x00; result |= unusedPin1 ? (byte)0x80 : (byte)0x00; return result; } set { byte val = Port.CPUWrite(PortData, value, portDir); SetPortData(val); } } public byte PortDirection { get { return portDir; } set { SetPortDir(value); } } public byte ReadPortData() { return PortData; } private void SetPortData(byte val) { pinCassetteOutput = ((val & 0x08) != 0); pinCassetteButton = ((val & 0x10) != 0); pinCassetteMotor = ((val & 0x20) != 0); pinLoram = ((val & 0x01) != 0) || ((portDir & 0x01) == 0); pinHiram = ((val & 0x02) != 0) || ((portDir & 0x02) == 0); pinCharen = ((val & 0x04) != 0) || ((portDir & 0x04) == 0); unusedPin0 = ((val & 0x40) != 0); unusedPin1 = ((val & 0x80) != 0); unusedPinTTL0 = unusedPinTTLCycles; unusedPinTTL1 = unusedPinTTLCycles; } private void SetPortDir(byte val) { portDir = val; SetPortData(PortData); } public void SyncState(Serializer ser) { cpu.SyncState(ser); ser.Sync("freezeCpu", ref freezeCpu); ser.Sync("pinCassetteButton", ref pinCassetteButton); ser.Sync("pinCassetteMotor", ref pinCassetteMotor); ser.Sync("pinCassetteOutput", ref pinCassetteOutput); ser.Sync("pinCharen", ref pinCharen); ser.Sync("pinLoram", ref pinLoram); ser.Sync("pinHiram", ref pinHiram); ser.Sync("pinNMILast", ref pinNMILast); ser.Sync("portDir", ref portDir); ser.Sync("unusedPin0", ref unusedPin0); ser.Sync("unusedPin1", ref unusedPin1); ser.Sync("unusedPinTTL0", ref unusedPinTTL0); ser.Sync("unusedPinTTL1", ref unusedPinTTL1); ser.Sync("unusedPinTTLCycles", ref unusedPinTTLCycles); } public void WritePortData(byte data) { PortData = data; } // ------------------------------------ } }