// 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 // using System; namespace Noddybox.Emulation.EightBit.Z80 { public partial class Z80Cpu { #region Fetch and helper operations /// /// Fetch the next word from the PC. /// /// The word. ushort FetchWord() { Register16 r = new Register16(0); r.low = memory.Read(PC++); r.high = memory.Read(PC++); return r.reg; } /// /// Swap two 16-bit registers. /// /// The first register. /// The second register. void Swap(ref Register16 a, ref Register16 b) { Register16 t = a; a = b; b = t; } /// /// Fetch the offset if the current opcode is shifted. /// /// The offset, or zero if the current opcode is not shifted. sbyte Offset() { if (shift == 0xdd || shift == 0xfd) { byte b = memory.Read(PC++); return (sbyte)b; } else { return 0; } } #endregion #region Status/special register helpers /// /// Set a flag in the status register. /// /// The flag. private void SetFlag(Z80Flags flag) { F |= flag; } /// /// Clear a flag in the status register. /// /// The flag. private void ClearFlag(Z80Flags flag) { F &= ~flag; } /// /// Add a value to the R (refresh) register. /// /// The value. private void AddR(byte v) { R = (byte)((R & 0x80) | (R + v) & 0x7f); } #endregion #region Stack commands /// /// Push a value on the stack. /// /// The value. private void PUSH(ushort val) { memory.Write(--SP, (byte)(val & 0xff)); memory.Write(--SP, (byte)(Binary.ShiftRight(val, 8) & 0xff)); } /// /// Pop a value from the stack. /// /// private ushort POP() { SP = (ushort)((SP + 2) & 0xffff); return (ushort)(memory.Read((ushort)(SP-2)) | (memory.Read((ushort)(SP - 1)) >> 8)); } #endregion #region ALU arithmetic and comparison /// /// Add an 8-bit value to the accumulator without carry. /// /// The vakue. private void ADD8(byte b) { int w = A + b; F = SZtable[w] | H35table[w & 0xff]; if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry) { F |= Z80Flags.HalfCarry; } if (((b ^ A) & (b ^ w) & 0x80) > 0) { F |= Z80Flags.PV; } A = (byte)(w & 0xff); } /// /// Add an 8-bit value to the accumulator with carry. /// /// The vakue. private void ADC8(byte b) { int w = A + b + (int)(F & Z80Flags.Carry); F = SZtable[w] | H35table[w & 0xff]; if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry) { F |= Z80Flags.HalfCarry; } if (((b ^ A) & (b ^ w) & 0x80) > 0) { F |= Z80Flags.PV; } A = (byte)(w & 0xff); } /// /// Subtract an 8-bit value from the accumulator without carry. /// /// The vakue. private void SUB8(byte b) { int w = A - b; if (w < 0) { w += 0x200; } F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg; if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry) { F |= Z80Flags.HalfCarry; } if (((b ^ A) & (b ^ w) & 0x80) > 0) { F |= Z80Flags.PV; } A = (byte)(w & 0xff); } /// /// Compare an 8-bit value with the accumulator. /// /// The vakue. private void CP(byte b) { int w = A - b; if (w < 0) { w += 0x200; } F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg; if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry) { F |= Z80Flags.HalfCarry; } if (((b ^ A) & (b ^ w) & 0x80) > 0) { F |= Z80Flags.PV; } } /// /// Subtract an 8-bit value from the accumulator with carry. /// /// The vakue. private void SBC8(byte b) { int w = A - b - (int)(F & Z80Flags.Carry); if (w < 0) { w += 0x200; } F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg; if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry) { F |= Z80Flags.HalfCarry; } if (((b ^ A) & (b ^ w) & 0x80) > 0) { F |= Z80Flags.PV; } A = (byte)(w & 0xff); } /// /// Add a 16-bit value to a register without carry. /// /// The vakue. private void ADD16(ref ushort reg, ushort b) { int w = reg + b; F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV; if (w > 0xffff) { F |= Z80Flags.Carry; } if ((reg ^ w ^ b) == 0x1000) { F |= Z80Flags.HalfCarry; } reg = (ushort)(w & 0xffff); F |= H35table[reg >> 8]; } /// /// Add a 16-bit value to a register with carry. /// /// The vakue. private void ADC16(ref ushort reg, ushort b) { int w = reg + b + (int)(F & Z80Flags.Carry); F = Z80Flags.None; if ((w & 0xffff) == 0) { F |= Z80Flags.Zero; } if ((w & 0x8000) == 0x8000) { F |= Z80Flags.Sign; } if (w > 0xffff) { F |= Z80Flags.Carry; } if (((b ^ reg ^ 0x8000) & ((reg ^ w) & 0x8000)) == 0x8000) { F |= Z80Flags.PV; } if ((reg ^ w ^ b) == 0x1000) { F |= Z80Flags.HalfCarry; } reg = (ushort)(w & 0xffff); F |= H35table[reg >> 8]; } /// /// Subtract a 16-bit value from a register with carry. /// /// The vakue. private void SBC(ref ushort reg, ushort b) { int w = reg - b - (int)(F & Z80Flags.Carry); F = Z80Flags.Neg; if (w < 0) { w += 0x10000; F |= Z80Flags.Carry; } if ((w & 0xffff) == 0) { F |= Z80Flags.Zero; } if ((w & 0x8000) == 0x8000) { F |= Z80Flags.Sign; } if (((b ^ reg) & ((reg ^ w) & 0x8000)) == 0x8000) { F |= Z80Flags.PV; } if ((reg ^ w ^ b) == 0x1000) { F |= Z80Flags.HalfCarry; } reg = (ushort)(w & 0xffff); F |= H35table[reg >> 8]; } /// /// Increment an 8-bit register. /// /// The register to increment. void INC8(ref byte reg) { reg++; F &= Z80Flags.Carry; if (reg == 0x80) { F |= Z80Flags.PV; } if ((reg & 0x0f) == 0x00) { F |= Z80Flags.HalfCarry; } F |= SZtable[reg] | H35table[reg]; } /// /// Decrement an 8-bit register. /// /// The register to decrement. void DEC8(ref byte reg) { reg--; F &= Z80Flags.Carry; F |= Z80Flags.Neg; if (reg == 0x7f) { F |= Z80Flags.PV; } if ((reg & 0x0f) == 0x0f) { F |= Z80Flags.HalfCarry; } F |= SZtable[reg] | H35table[reg]; } /// /// Decimally adjust the accumulator. A bugger of an opcode. /// Based on info from http://www.worldofspectrum.org/faq/reference/z80reference.htm /// void DAA() { byte add = 0; Z80Flags carry = Z80Flags.None; Z80Flags nf = F & Z80Flags.Neg; byte acc = A; if (acc>0x99 || (F & Z80Flags.Carry) == Z80Flags.Carry) { add |= 0x60; carry = Z80Flags.Carry; } if ((acc & 0xf) > 0x9 || (F & Z80Flags.HalfCarry) == Z80Flags.HalfCarry) { add|=0x06; } if (nf == Z80Flags.Neg) { A -= add; } else { A += add; } F = PSZtable[A] | carry | nf | ((Z80Flags)(acc ^ A) & Z80Flags.HalfCarry) | H35table[A]; } #endregion #region ALU rotate and shift operations /// /// Do RRCA. /// private void RRCA() { F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV; F |= (Z80Flags)(A & 1); A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(A, 7)); F |= H35table[A]; } /// /// Do RRA. /// private void RRA() { byte carry = (byte)(F & Z80Flags.Carry); F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV; F |= (Z80Flags)(A & 1); A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(carry, 7)); F |= H35table[A]; } /// /// Do RRC. /// /// The register to operate on. private void RRC(ref byte reg) { F = (Z80Flags)(reg & (int)Z80Flags.Carry); reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(reg, 7)); F |= PSZtable[reg] | H35table[reg]; } /// /// Do RR. /// /// The register to operate on. private void RR(ref byte reg) { byte carry = (byte)(F & Z80Flags.Carry); F = (Z80Flags)(reg & (int)Z80Flags.Carry); reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(carry, 7)); F |= PSZtable[reg] | H35table[reg]; } /// /// Do RLCA. /// private void RLCA() { F = (F & Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero) | (Z80Flags)Binary.ShiftRight(A, 7); A = (byte)(Binary.ShiftLeft(A, 1) | Binary.ShiftRight(A, 7)); F |= H35table[A]; } /// /// Do RLA. /// private void RLA() { byte carry = (byte)(F & Z80Flags.Carry); F = (F & Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero) | (Z80Flags)Binary.ShiftRight(A, 7); A = (byte)(Binary.ShiftRight(A, 1) | carry); F |= H35table[A]; } /// /// Do RLC. /// /// The register to operate on. private void RLC(ref byte reg) { byte carry = Binary.ShiftRight(reg, 7); reg = (byte)(Binary.ShiftLeft(reg, 1) | carry); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } /// /// Do RL. /// /// The register to operate on. private void RL(ref byte reg) { byte carry = Binary.ShiftRight(reg, 7); reg = (byte)(Binary.ShiftLeft(reg, 1) | (int)(F & Z80Flags.Carry)); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } /// /// Do SRL. /// /// The register to operate on. private void SRL(ref byte reg) { byte carry = (byte)(reg & 1); reg = Binary.ShiftLeft(reg, 1); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } /// /// Do SRA. /// /// The register to operate on. private void SRA(ref byte reg) { byte carry = (byte)(reg & 1); reg = (byte)(Binary.ShiftLeft(reg, 1) | (reg & 0x80)); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } /// /// Do SLL. /// /// The register to operate on. private void SLL(ref byte reg) { byte carry = Binary.ShiftRight(reg, 7); reg = (byte)(Binary.ShiftRight(reg, 1) | 0x01); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } /// /// Do SLA. /// /// The register to operate on. private void SLA(ref byte reg) { byte carry = Binary.ShiftRight(reg, 7); reg = Binary.ShiftRight(reg, 1); F = PSZtable[reg] | (Z80Flags)carry | H35table[reg]; } #endregion #region ALU boolean operations /// /// AND a value with the accumulator. /// /// The value. void AND(byte val) { A &= val; F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A]; } /// /// OR a value with the accumulator. /// /// The value. void OR(byte val) { A |= val; F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A]; } /// /// XOR a value with the accumulator. /// /// The value. void XOR(byte val) { A ^= val; F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A]; } /// /// Perform the BIT operation. /// /// The register to operate on. /// The bit to test. void BIT(ref byte reg, int bit) { F &= Z80Flags.Carry; F |= Z80Flags.HalfCarry; if ((reg & (1 << bit)) != 0) { if (bit == 7 && (reg & (int)Z80Flags.Sign) != 0) { F |= Z80Flags.Sign; } if (bit == 5 && (reg & (int)Z80Flags.Hidden5) != 0) { F |= Z80Flags.Hidden5; } if (bit == 3 && (reg & (int)Z80Flags.Hidden3) != 0) { F |= Z80Flags.Hidden3; } } else { F |= Z80Flags.Zero | Z80Flags.PV; } } /// /// Perform the bit set operation. /// /// The register to operate on. /// The bit to test. void BIT_SET(ref byte reg, int bit) { reg |= (byte)(1 << bit); } /// /// Perform the bit clear operation. /// /// The register to operate on. /// The bit to test. void BIT_RES(ref byte reg, int bit) { reg &= (byte)~(1 << bit); } #endregion #region Jump operations /// /// The call operation. /// private void CALL() { PUSH((ushort)(PC + 2)); PC = (ushort)(memory.Read(PC) | memory.Read((ushort)(PC+1)) >> 8); } /// /// The jump operation. /// private void JP() { PC = (ushort)(memory.Read(PC) | memory.Read((ushort)(PC+1)) >> 8); } /// /// The jump relative operation. /// private void JR() { PC = (ushort)(PC + (sbyte)memory.Read(PC) + 1); } /// /// Jump relative if the passed condition flag ANDed with the flag /// register equals the passed check value. /// /// The condition flag. /// The check value. private void JR_COND(Z80Flags cond, Z80Flags val) { if ((F & cond) == val) { clock.Add(12); JR(); } else { clock.Add(7); PC++; } } /// /// Jump if the passed condition flag ANDed with the flag /// register equals the passed check value. /// /// The condition flag. /// The check value. private void JP_COND(Z80Flags cond, Z80Flags val) { clock.Add(10); if ((F & cond) == val) { JP(); } else { PC+=2; } } /// /// Call if the passed condition flag ANDed with the flag /// register equals the passed check value. /// /// The condition flag. /// The check value. private void CALL_COND(Z80Flags cond, Z80Flags val) { if ((F & cond) == val) { clock.Add(17); CALL(); } else { clock.Add(10); PC+=2; } } /// /// Return if the passed condition flag ANDed with the flag /// register equals the passed check value. /// /// The condition flag. /// The check value. private void RET_COND(Z80Flags cond, Z80Flags val) { if ((F & cond) == val) { clock.Add(11); PC = POP(); } else { clock.Add(5); } } /// /// Reset the PC to an address. /// /// The address. private void RST(ushort addr) { clock.Add(11); PUSH(PC); PC = addr; } #endregion #region Block operations /// /// LDI instruction. /// private void LDI() { byte b = memory.Read(HL.reg); memory.Write(DE.reg, b); DE.reg++; HL.reg++; BC.reg--; if (BC.reg != 0) { ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg); SetFlag(Z80Flags.PV); } else { ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.PV); } F |= H35table[A + b]; } /// /// LDD instruction. /// private void LDD() { byte b = memory.Read(HL.reg); memory.Write(DE.reg, b); DE.reg--; HL.reg--; BC.reg--; if (BC.reg != 0) { ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg); SetFlag(Z80Flags.PV); } else { ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.PV); } F |= H35table[A + b]; } /// /// CPI instruction. /// private void CPI() { Z80Flags c = F & Z80Flags.Carry; byte b = memory.Read(HL.reg); CP(b); F |= c; HL.reg++; BC.reg--; if (BC.reg != 0) { SetFlag(Z80Flags.PV); } else { ClearFlag(Z80Flags.PV); } } /// /// CPD instruction. /// private void CPD() { Z80Flags c = F & Z80Flags.Carry; byte b = memory.Read(HL.reg); CP(b); F |= c; HL.reg--; BC.reg--; if (BC.reg != 0) { SetFlag(Z80Flags.PV); } else { ClearFlag(Z80Flags.PV); } } /// /// INI instruction. /// private void INI() { int w; byte b = device.Read(BC.reg); memory.Write(HL.reg, b); BC.high--; HL.reg++; F = SZtable[BC.high] | H35table[BC.high]; w = BC.low + b; if ((w & 0x80) == 0x80) { SetFlag(Z80Flags.Neg); } if ((w & 0x100) == 0x100) { SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry); } else { ClearFlag(Z80Flags.Carry | Z80Flags.HalfCarry); } } /// /// IND instruction. /// private void IND() { int w; byte b = device.Read(BC.reg); memory.Write(HL.reg, b); BC.high--; HL.reg--; F = SZtable[BC.high] | H35table[BC.high]; w = BC.low + b; if ((w & 0x80) == 0x80) { SetFlag(Z80Flags.Neg); } if ((w & 0x100) == 0x100) { SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry); } else { ClearFlag(Z80Flags.Carry | Z80Flags.HalfCarry); } } /// /// OUTI instruction. /// private void OUTI() { device.Write(BC.reg, memory.Read(HL.reg)); HL.reg++; BC.high--; F = SZtable[BC.high] | H35table[BC.high]; } /// /// OUTD instruction. /// private void OUTD() { device.Write(BC.reg, memory.Read(HL.reg)); HL.reg--; BC.high--; F = SZtable[BC.high] | H35table[BC.high] | Z80Flags.Neg; } #endregion } }