Files
VGMPLAY.WMF/Debuger/Cpu/Processor/Z80Cpu.cs
T

353 lines
11 KiB
C#

/*
* Copyright 2007, 2015 Alex Makeev
*
* This file is part of ZXMAK2 (ZX Spectrum virtual machine).
*
* ZXMAK2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZXMAK2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZXMAK2. If not, see <http://www.gnu.org/licenses/>.
*
* Description: Z80 CPU Emulator
* Date: 13.04.2007
*
*/
using System;
using System.Linq;
namespace ZXMAK2.Engine.Cpu.Processor
{
public partial class Z80Cpu
{
public readonly CpuRegs regs = new();
public int RzxCounter;
public long Tact;
public bool HALTED;
public bool IFF1;
public bool IFF2;
public byte IM;
public bool BINT; // last opcode was EI or DD/FD prefix (to prevent INT handling)
public CpuModeIndex FX;
public CpuModeEx XFX;
public ushort LPC; // last opcode PC
public bool INT;
public bool NMI;
public bool RST;
public byte BUS = 0xFF; // state of free data bus
public Action RESET;
public Action NMIACK_M1;
public Action INTACK_M1;
public Func<ushort, byte> RDMEM_M1;
public Func<ushort, byte> RDMEM;
public Action<ushort, byte> WRMEM;
public Func<ushort, byte> RDPORT;
public Action<ushort, byte> WRPORT;
public Action<ushort> RDNOMREQ;
public Action<ushort> WRNOMREQ;
public Action SCANSIG;
private CpuType _type = CpuType.NEC_NMOS;
public CpuType Type
{
get { return _type; }
set { _type = value; InitOpcodeTables(); }
}
public Z80Cpu()
{
_pairGetters = [.. Enumerable.Range(0, 4).Select(regs.CreatePairGetter)];
_pairSetters = [.. Enumerable.Range(0, 4).Select(regs.CreatePairSetter)];
_regGetters = [.. Enumerable.Range(0, 8).Select(regs.CreateRegGetter)];
_regSetters = [.. Enumerable.Range(0, 8).Select(regs.CreateRegSetter)];
_alualg = CreateAluAlg();
_opcodes = new Action<byte>[256];
_opcodesFx = new Action<byte>[256];
_opcodesEd = new Action<byte>[256];
_opcodesCb = new Action<byte>[256];
_opcodesFxCb = new Action<byte, ushort>[256];
InitOpcodeTables();
regs.AF = 0xFF;
regs.BC = 0xFF;
regs.DE = 0xFF;
regs.HL = 0xFF;
regs._AF = 0xFF;
regs._BC = 0xFF;
regs._DE = 0xFF;
regs._HL = 0xFF;
regs.IX = 0xFF;
regs.IY = 0xFF;
regs.IR = 0xFF;
regs.PC = 0xFF;
regs.SP = 0xFF;
regs.MW = 0xFF;
}
private void InitOpcodeTables()
{
InitOpcodeTable(_opcodes, CreateOpcodes());
InitOpcodeTable(_opcodesFx, CreateOpcodesFx());
InitOpcodeTable(_opcodesEd, CreateOpcodesEd());
InitOpcodeTable(_opcodesCb, CreateOpcodesCb());
InitOpcodeTable(_opcodesFxCb, CreateOpcodesFxCb());
}
private static void InitOpcodeTable<T>(T[] opcodes, T[] actions)
{
if (opcodes.Length != actions.Length)
throw new ArgumentOutOfRangeException(nameof(opcodes));
for (var i = 0; i < opcodes.Length; i++)
{
opcodes[i] = actions[i];
}
}
public void ExecCycle()
{
byte cmd = 0;
if (XFX == CpuModeEx.None && FX == CpuModeIndex.None)
{
if (ProcessSignals())
return;
LPC = regs.PC;
cmd = RDMEM_M1(LPC);
}
else
{
if (ProcessSignals())
return;
cmd = RDMEM(regs.PC);
}
Tact += 3;
regs.PC++;
if (XFX == CpuModeEx.Cb)
{
BINT = false;
if (FX != CpuModeIndex.None)
{
// elapsed T: 4, 4, 3
// will be T: 4, 4, 3, 5
int drel = (sbyte)cmd;
regs.MW = FX == CpuModeIndex.Ix ? (ushort)(regs.IX + drel) : (ushort)(regs.IY + drel);
cmd = RDMEM(regs.PC); Tact += 3;
RDNOMREQ(regs.PC); Tact++;
RDNOMREQ(regs.PC); Tact++;
regs.PC++;
_opcodesFxCb[cmd](cmd, regs.MW);
}
else
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
_opcodesCb[cmd](cmd);
}
XFX = CpuModeEx.None;
FX = CpuModeIndex.None;
}
else if (XFX == CpuModeEx.Ed)
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
BINT = false;
_opcodesEd[cmd]?.Invoke(cmd);
XFX = CpuModeEx.None;
FX = CpuModeIndex.None;
}
else if (cmd == 0xDD)
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
FX = CpuModeIndex.Ix;
BINT = true;
}
else if (cmd == 0xFD)
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
FX = CpuModeIndex.Iy;
BINT = true;
}
else if (cmd == 0xCB)
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
XFX = CpuModeEx.Cb;
BINT = true;
}
else if (cmd == 0xED)
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
XFX = CpuModeEx.Ed;
BINT = true;
}
else
{
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
BINT = false;
(FX == CpuModeIndex.None ? _opcodes[cmd] : _opcodesFx[cmd])?.Invoke(cmd);
FX = CpuModeIndex.None;
}
}
private bool ProcessSignals()
{
if (RST) // RESET
{
// 3T
RESET();
//Refresh(); //+1T
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
FX = CpuModeIndex.None;
XFX = CpuModeEx.None;
HALTED = false;
IFF1 = false;
IFF2 = false;
regs.PC = 0;
regs.IR = 0;
IM = 0;
//regs.SP = 0xFFFF;
//regs.AF = 0xFFFF;
Tact += 2; // total should be 3T?
return true;
}
else if (NMI)
{
// 11T (5, 3, 3)
if (HALTED) // workaround for Z80 snapshot halt issue + comfortable debugging
regs.PC++;
// M1
NMIACK_M1();
Tact += 4;
//Refresh();
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
RzxCounter++;
IFF2 = IFF1;
IFF1 = false;
HALTED = false;
regs.SP--;
// M2
WRMEM(regs.SP, (byte)(regs.PC >> 8));
Tact += 3;
regs.SP--;
// M3
WRMEM(regs.SP, (byte)regs.PC);
regs.PC = 0x0066;
Tact += 3;
return true;
}
else if (INT && (!BINT) && IFF1)
{
// http://www.z80.info/interrup.htm
// IM0: 13T (7,3,3) [RST]
// IM1: 13T (7,3,3)
// IM2: 19T (7,3,3,3,3)
if (HALTED) // workaround for Z80 snapshot halt issue + comfortable debugging
regs.PC++;
INTACK_M1();
// M1: 7T = interrupt acknowledgement; SP--
regs.SP--;
//if (HALTED) ??
// Tact += 2;
Tact += 4 + 2;
//Refresh();
//RzxCounter--; // fix because INTAK should not be calculated
regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80));
Tact += 1;
IFF1 = false;
IFF2 = false; // proof?
HALTED = false;
// M2
WRMEM(regs.SP, (byte)(regs.PC >> 8)); // M2: 3T write PCH; SP--
regs.SP--;
Tact += 3;
// M3
WRMEM(regs.SP, (byte)regs.PC); // M3: 3T write PCL
Tact += 3;
if (IM == 0) // IM 0: execute instruction taken from BUS with timing T+2???
{
regs.MW = 0x0038; // workaround: just execute #FF
}
else if (IM == 1) // IM 1: execute #FF with timing T+2 (11+2=13T)
{
regs.MW = 0x0038;
}
else // IM 2: VH=reg.I; VL=BUS; PC=[V]
{
// M4
ushort adr = (ushort)((regs.IR & 0xFF00) | BUS);
regs.MW = RDMEM(adr); // M4: 3T read VL
Tact += 3;
// M5
regs.MW += (ushort)(RDMEM(++adr) * 0x100); // M5: 3T read VH, PC=V
Tact += 3;
}
regs.PC = regs.MW;
return true;
}
return false;
}
}
}