using BizHawk.Emulation.Cores.Components.Z80A; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public class CPUMonitor { #region Devices private SpectrumBase _machine; private Z80A _cpu; public MachineType machineType = MachineType.ZXSpectrum48; #endregion #region Lookups public ushort[] cur_instr => _cpu.cur_instr; public int instr_pntr => _cpu.instr_pntr; public ushort RegPC => _cpu.RegPC; public long TotalExecutedCycles => _cpu.TotalExecutedCycles; public ushort BUSRQ { get { //if (_cpu.bus_pntr < _cpu.BUSRQ.Length - 1) return _cpu.BUSRQ[_cpu.bus_pntr]; //return 0; } } #endregion #region Construction public CPUMonitor(SpectrumBase machine) { _machine = machine; _cpu = _machine.CPU; } #endregion #region State public bool IsContending = false; public int ContCounter = -1; public int portContCounter = 0; public int portContTotalLen = 0; public bool portContending = false; public ushort lastPortAddr; public int[] portContArr = new int[4]; #endregion #region Methods /// /// Handles the ULA and CPU cycle clocks, along with any memory and port contention /// public void ExecuteCycle() { _machine.ULADevice.RenderScreen((int)_machine.CurrentFrameCycle); if (portContending) { RunPortContention(); } else { // is the next CPU cycle causing a BUSRQ? if (BUSRQ > 0) { // is the memory address of the BUSRQ potentially contended? if (_machine.IsContended(AscertainBUSRQAddress())) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { _cpu.TotalExecutedCycles += cont; } } } } _cpu.ExecuteOne(); /* else if (ContCounter > 0) { // still contention cycles to process IsContending = true; } else { // is the next CPU cycle causing a BUSRQ? if (BUSRQ > 0) { // is the memory address of the BUSRQ potentially contended? if (_machine.IsContended(AscertainBUSRQAddress())) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { ContCounter = cont + 1; IsContending = true; } } } } /* else { // no contention cycles to process (so far) on this cycle IsContending = false; ContCounter = 0; if (portContending) { // a port operation is still in progress portContCounter++; if (portContCounter > 3) { // we are now out of the IN/OUT operation portContCounter = 0; portContending = false; } else { // still IN/OUT cycles to process if (IsPortContended(portContCounter)) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { ContCounter = cont + 1; IsContending = true; // dont let this fall through // just manually do the first contention cycle ContCounter--; _cpu.TotalExecutedCycles++; return; } } } } // is the next CPU cycle causing a BUSRQ? if (BUSRQ > 0) { // is the memory address of the BUSRQ potentially contended? if (_machine.IsContended(AscertainBUSRQAddress())) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { ContCounter = cont + 1; IsContending = true; } } } /* // is the next CPU cycle an OUT operation? else if (cur_instr[instr_pntr] == Z80A.OUT) { portContending = true; lastPortAddr = (ushort)(_cpu.Regs[cur_instr[instr_pntr + 1]] | _cpu.Regs[cur_instr[instr_pntr + 2]] << 8); portContCounter = 0; if (IsPortContended(portContCounter)) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { ContCounter = cont; IsContending = true; } } } // is the next cpu cycle an IN operation? else if (cur_instr[instr_pntr] == Z80A.IN) { portContending = true; lastPortAddr = (ushort)(_cpu.Regs[cur_instr[instr_pntr + 2]] | _cpu.Regs[cur_instr[instr_pntr + 3]] << 8); portContCounter = 0; if (IsPortContended(portContCounter)) { var cont = _machine.ULADevice.GetContentionValue((int)_machine.CurrentFrameCycle); if (cont > 0) { ContCounter = cont; IsContending = true; } } } */ /* }*/ /* // run a CPU cycle if no contention is applicable if (!IsContending) { _cpu.ExecuteOne(); } else { _cpu.TotalExecutedCycles++; ContCounter--; } */ } /// /// Looks up the BUSRQ address that is about to be signalled /// /// private ushort AscertainBUSRQAddress() { ushort addr = 0; switch (BUSRQ) { // PCh case 1: addr = (ushort)(_cpu.Regs[_cpu.PCl] | _cpu.Regs[_cpu.PCh] << 8); break; // SPh case 3: addr = (ushort)(_cpu.Regs[_cpu.SPl] | _cpu.Regs[_cpu.SPh] << 8); break; // A case 4: addr = (ushort)(_cpu.Regs[_cpu.F] | _cpu.Regs[_cpu.A] << 8); break; // B case 6: addr = (ushort)(_cpu.Regs[_cpu.C] | _cpu.Regs[_cpu.B] << 8); break; // D case 8: addr = (ushort)(_cpu.Regs[_cpu.E] | _cpu.Regs[_cpu.D] << 8); break; // H case 10: addr = (ushort)(_cpu.Regs[_cpu.L] | _cpu.Regs[_cpu.H] << 8); break; // W case 12: addr = (ushort)(_cpu.Regs[_cpu.Z] | _cpu.Regs[_cpu.W] << 8); break; // Ixh case 16: addr = (ushort)(_cpu.Regs[_cpu.Ixl] | _cpu.Regs[_cpu.Ixh] << 8); break; // Iyh case 18: addr = (ushort)(_cpu.Regs[_cpu.Iyl] | _cpu.Regs[_cpu.Iyh] << 8); break; // I case 21: addr = (ushort)(_cpu.Regs[_cpu.R] | _cpu.Regs[_cpu.I] << 8); break; } return addr; } /// /// Perfors the actual port contention (if necessary) /// private void RunPortContention() { //return; bool lowBitSet = false; bool highByte407f = false; int offset = 0; // _machine.ULADevice.contentionOffset; // -5;// 57;// - 10; var c = _machine.CurrentFrameCycle; var t = _machine.ULADevice.FrameLength; int f = (int)c + offset; if (f >= t) f = f - t; else if (f < 0) f = t + f; if ((lastPortAddr & 0x0001) != 0) lowBitSet = true; portContCounter--; switch (machineType) { case MachineType.ZXSpectrum16: case MachineType.ZXSpectrum48: case MachineType.ZXSpectrum128: case MachineType.ZXSpectrum128Plus2: if ((lastPortAddr & 0xc000) == 0x4000) highByte407f = true; if (highByte407f) { // high byte 40-7f if (lowBitSet) { // high byte 40-7f // low bit set // C:1, C:1, C:1, C:1 switch (portContCounter) { case 3: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 1: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 0: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); portContCounter = 0; portContending = false; break; default: portContCounter = 0; portContending = false; break; } } else { // high byte 40-7f // low bit reset // C:1, C:3 switch (portContCounter) { case 3: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 1: break; case 0: portContCounter = 0; portContending = false; break; default: portContCounter = 0; portContending = false; break; } } } else { // high byte not 40-7f if (lowBitSet) { // high byte not 40-7f // low bit set // N:4 switch (portContCounter) { case 3: break; case 2: break; case 1: break; case 0: portContCounter = 0; portContending = false; break; default: portContCounter = 0; portContending = false; break; } } else { // high byte not 40-7f // low bit reset // N:1, C:3 switch (portContCounter) { case 3: break; case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetPortContentionValue(f); break; case 1: break; case 0: portContCounter = 0; portContending = false; break; default: portContCounter = 0; portContending = false; break; } } } break; case MachineType.ZXSpectrum128Plus2a: case MachineType.ZXSpectrum128Plus3: break; } } /// /// Starts the port contention process /// /// public void ContendPort(ushort port) { portContending = true; portContCounter = 4; lastPortAddr = port; } /// /// Called when the first byte of an instruction is fetched /// /// public void OnExecFetch(ushort firstByte) { // fetch instruction without incrementing pc //_cpu.FetchInstruction(_cpu.FetchMemory(firstByte)); } #endregion } }