// 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 } }