// This file is part of the Noddybox.Emulation C# suite.
//
// Noddybox.Emulation 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.
//
// Noddybox.Emulation 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 Noddybox.Emulation. If not, see .
//
// Copyright (c) 2012 Ian Cowburn
//
// Some of the concepts and code in here are taken from FUSE, the open source UNIX
// Spectrum emulator Copyright (c) 1999-2003 Philip Kendall.
// See
//
// Any introduced bugs in that code are mine, and not the original authors.
//
using System;
namespace Noddybox.Emulation.EightBit.Z80
{
///
/// Provides an implementation of a Zilog Z80 processor.
///
public partial class Z80Cpu : ICpu
{
#region Private types
private enum EventType
{
HALT,
EDHook
}
#endregion
#region Private data
// Lookup tables
//
private Z80Flags[] PSZtable = new Z80Flags[512];
private Z80Flags[] SZtable = new Z80Flags[512];
private Z80Flags[] Ptable = new Z80Flags[512];
private Z80Flags[] Stable = new Z80Flags[512];
private Z80Flags[] Ztable = new Z80Flags[512];
private Z80Flags[] H35table = new Z80Flags[512];
// Whether a half carry occurred or not can be determined by looking at
// the 3rd bit of the two arguments and the result; these are hashed
// into this table in the form r12, where r is the 3rd bit of the
// result, 1 is the 3rd bit of the 1st argument and 2 is the
// third bit of the 2nd argument; the tables differ for add and subtract
// operations.
//
private readonly ReadOnlyArray halfcarry_add_table =
new ReadOnlyArray
(new Z80Flags[8]
{Z80Flags.None, Z80Flags.HalfCarry, Z80Flags.HalfCarry, Z80Flags.HalfCarry,
Z80Flags.None, Z80Flags.None, Z80Flags.None, Z80Flags.HalfCarry});
private readonly ReadOnlyArray halfcarry_sub_table =
new ReadOnlyArray
(new Z80Flags[8]
{Z80Flags.None, Z80Flags.None, Z80Flags.HalfCarry, Z80Flags.None,
Z80Flags.HalfCarry, Z80Flags.None, Z80Flags.HalfCarry, Z80Flags.HalfCarry});
// Similarly, overflow can be determined by looking at the 7th bits; again
// the hash into this table is r12
//
private readonly ReadOnlyArray overflow_add_table =
new ReadOnlyArray
(new Z80Flags[8]
{Z80Flags.None, Z80Flags.None, Z80Flags.None, Z80Flags.PV,
Z80Flags.PV, Z80Flags.None, Z80Flags.None, Z80Flags.None});
private readonly ReadOnlyArray overflow_sub_table =
new ReadOnlyArray
(new Z80Flags[8]
{Z80Flags.None, Z80Flags.PV, Z80Flags.None, Z80Flags.None,
Z80Flags.None, Z80Flags.None, Z80Flags.PV, Z80Flags.None});
// Machine accessors
//
private IMemory memory;
private IDevice device;
private Clock clock;
// Main registers
//
private byte A;
private Z80Flags F;
private Register16 BC = new Register16(0);
private Register16 DE = new Register16(0);
private Register16 HL = new Register16(0);
private Register16 IX = new Register16(0);
private Register16 IY = new Register16(0);
private ushort SP;
private ushort PC;
// Alternate registers
//
private Register16 AF_ = new Register16(0);
private Register16 BC_ = new Register16(0);
private Register16 DE_ = new Register16(0);
private Register16 HL_ = new Register16(0);
// Auxilliary registers and flags
//
private byte I;
private byte R;
private bool IFF1;
private bool IFF2;
private bool raise;
private bool nmi;
private byte devbyte;
private byte IM;
private bool HALT;
private int shift;
// Counters
//
private uint executedInstructions;
#endregion
#region Private members
private void CheckInterrupts()
{
if (raise)
{
if (nmi)
{
if (HALT)
{
HALT = false;
PC++;
}
clock.Add(2);
IFF1 = false;
nmi = false;
PUSH(PC);
PC = 0x66;
}
else if (IFF1)
{
if (HALT)
{
HALT = false;
PC++;
}
clock.Add(2);
IFF1 = false;
IFF2 = false;
switch(IM)
{
case 1:
PUSH(PC);
PC = 0x38;
break;
case 2:
PUSH(PC);
PC = (ushort)(((ushort)I << 8) + devbyte);
break;
default:
DecodeByte(devbyte);
break;
}
}
raise = false;
}
}
#endregion
#region ICpu Members
public void Initialise(IMemory memory, IDevice device, Clock clock)
{
this.memory = memory;
this.device = device;
this.clock = clock;
Reset();
}
public void Reset()
{
PC = 0;
A = 0xff;
F = (Z80Flags)0xff;
BC.reg = 0xffff;
DE.reg = 0xffff;
HL.reg = 0xffff;
AF_.reg = 0xffff;
BC_.reg = 0xffff;
DE_.reg = 0xffff;
HL_.reg = 0xffff;
IX.reg = 0xffff;
IY.reg = 0xffff;
SP = 0xffff;
IFF1 = false;
IFF2 = false;
IM = 0;
I = 0;
R = 0;
HALT = false;
shift = 0;
raise = false;
nmi = false;
devbyte = 0;
}
public void Step()
{
CheckInterrupts();
AddR(1);
shift = 0;
executedInstructions++;
DecodeByte(memory.Read(PC++));
}
public void Run()
{
while(!clock.FrameDone)
{
Step();
}
}
public void MaskableInterrupt(byte value)
{
raise = true;
devbyte = value;
nmi = false;
}
public void NonMaskableInterrupt(byte value)
{
raise = true;
nmi = true;
}
#endregion
#region Constructors
public Z80Cpu()
{
// Verify runtime
//
Register16.Verify();
// Setup lookup tables
//
for(int f = 0; f < 256; f++)
{
Z80Flags p = Z80Flags.None;
Z80Flags z = Z80Flags.None;
Z80Flags s = Z80Flags.None;
Z80Flags h3 = Z80Flags.None;
Z80Flags h5 = Z80Flags.None;
int parity = 0;
for(int b=0; b < 8; b++)
{
if ((f & (1<
/// Get/reset the number of executed instructions. If the reset is not zero, and expception is thrown.
///
public uint ExecutedInstructions
{
get {return executedInstructions;}
set
{
if (value == 0)
{
executedInstructions = 0;
}
else
{
throw new ArgumentOutOfRangeException("Can only reset opcode count to zero");
}
}
}
///
/// Set/get the current state of the accumulator.
///
public byte Acummulator
{
get {return A;}
set {A = value;}
}
///
/// Set/get the current state of the flag register.
///
public Z80Flags StatusFlags
{
get {return F;}
set {F = value;}
}
///
/// The current state of the BC register pair.
///
public Register16 BC_Register
{
get { return BC; }
set { BC = value; }
}
///
/// The current state of the DE register pair.
///
public Register16 DE_Register
{
get { return DE; }
set { DE = value; }
}
///
/// The current state of the HL register pair.
///
public Register16 HL_Register
{
get { return HL; }
set { HL = value; }
}
///
/// The current state of the IX register pair.
///
public Register16 IX_Register
{
get { return IX; }
set { IX = value; }
}
///
/// The current state of the IY register pair.
///
public Register16 IY_Register
{
get { return IY; }
set { IY = value; }
}
///
/// The current state of the stack pointer.
///
public ushort StackPointer
{
get { return SP; }
set { SP = value; }
}
///
/// The current state of the program counter.
///
public ushort ProgramCounter
{
get { return PC; }
set { PC = value; }
}
///
/// The alternate AF' register.
///
public Register16 AF_Alternate
{
get { return AF_; }
set { AF_ = value; }
}
///
/// The alternate BC' register.
///
public Register16 BC_Alternate
{
get { return BC_; }
set { BC_ = value; }
}
///
/// The alternate DE' register.
///
public Register16 DE_Alternate
{
get { return DE_; }
set { DE_ = value; }
}
///
/// The alternate HL' register.
///
public Register16 HL_Alternate
{
get { return HL_; }
set { HL_ = value; }
}
///
/// The state of the HALT line.
///
public bool HaltLine
{
get { return HALT; }
set { HALT = value; }
}
///
/// The state of the IFF1 register.
///
public bool IFF1_Register
{
get { return IFF1; }
set { IFF1 = value; }
}
///
/// The state of the IFF2 register.
///
public bool IFF2_Register
{
get { return IFF2; }
set { IFF2 = value; }
}
///
/// The state of the IM register.
///
public byte IM_Register
{
get { return IM; }
set { IM = value; }
}
///
/// The state of the RAM refresh register.
///
public byte Refresh
{
get { return R; }
set { R = value; }
}
///
/// The state of the interrupt vector register.
///
public byte InterruptVector
{
get { return I; }
set { I = value; }
}
#endregion
#region Events
///
/// The CPU has been halted.
///
public event EventHandler HaltEvent;
///
/// A no-operation ED opcode has been executed. Useful for placing
/// hooks into the Z80 code.
///
public event EventHandler EDNopEvent;
///
/// Called to raise
///
/// The event arguments.
protected void OnHaltEvent(Z80CpuEventArgs e)
{
EventHandler handler = HaltEvent;
if (handler != null)
{
handler(this, e);
}
}
///
/// Called to raise
///
/// The event arguments.
protected void OnEDNopEvent(Z80CpuEventArgs e)
{
EventHandler handler = EDNopEvent;
if (handler != null)
{
handler(this, e);
}
}
private void TriggerEvent(EventType type, byte opcode)
{
Z80CpuEventArgs e = new Z80CpuEventArgs
{
Opcode = opcode,
};
switch(type)
{
case EventType.HALT:
OnHaltEvent(e);
break;
case EventType.EDHook:
OnEDNopEvent(e);
break;
}
}
#endregion
#region Object overrides
public override string ToString()
{
return String.Format("A={0:x2} BC={1:x4} DE={2:x4} HL={3:x4} F={4}\nPC={5:x4} SP={6:x4} IX={7:x4} IY={8:x4} AF'={9:x4} BC'={10:x4} DE'={11:x4} HL'={12:x4}\nI={13:x2} R={14:x2} IFF1={15} IFF2={16} IM={17} HALT={18}",
A, BC.reg, DE.reg, HL.reg, F, PC, SP, IX.reg, IY.reg, AF_.reg, BC_.reg, DE_.reg, HL_.reg, I, R, IFF1, IFF2, IM, HALT);
}
#endregion
}
}