Add OPL4 playback and analyzer support
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user