382 lines
8.9 KiB
Go
382 lines
8.9 KiB
Go
// Package z80 implements a Z80 CPU emulator with support for all documented
|
|
// and undocumented opcodes, flags, and registers.
|
|
package z80
|
|
|
|
// ExecuteDDOpcode executes a DD-prefixed opcode and returns the number of T-states used
|
|
func (cpu *CPU) ExecuteDDOpcode(opcode byte) int {
|
|
switch opcode {
|
|
// Load instructions
|
|
case 0x09: // ADD IX, BC
|
|
oldIX := cpu.IX
|
|
result := cpu.add16IX(cpu.IX, cpu.GetBC())
|
|
cpu.MEMPTR = oldIX + 1
|
|
cpu.IX = result
|
|
return 15
|
|
case 0x19: // ADD IX, DE
|
|
oldIX := cpu.IX
|
|
result := cpu.add16IX(cpu.IX, cpu.GetDE())
|
|
cpu.MEMPTR = oldIX + 1
|
|
cpu.IX = result
|
|
return 15
|
|
case 0x21: // LD IX, nn
|
|
cpu.IX = cpu.ReadImmediateWord()
|
|
return 14
|
|
case 0x22: // LD (nn), IX
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.Memory.WriteWord(addr, cpu.IX)
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x23: // INC IX
|
|
cpu.IX++
|
|
return 10
|
|
case 0x24: // INC IXH
|
|
cpu.SetIXH(cpu.inc8(cpu.GetIXH()))
|
|
return 8
|
|
case 0x25: // DEC IXH
|
|
cpu.SetIXH(cpu.dec8(cpu.GetIXH()))
|
|
return 8
|
|
case 0x26: // LD IXH, n
|
|
cpu.SetIXH(cpu.ReadImmediateByte())
|
|
return 11
|
|
case 0x29: // ADD IX, IX
|
|
oldIX := cpu.IX
|
|
result := cpu.add16IX(cpu.IX, cpu.IX)
|
|
cpu.MEMPTR = oldIX + 1
|
|
cpu.IX = result
|
|
return 15
|
|
case 0x2A: // LD IX, (nn)
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.IX = cpu.Memory.ReadWord(addr)
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x2B: // DEC IX
|
|
cpu.IX--
|
|
return 10
|
|
case 0x2C: // INC IXL
|
|
cpu.SetIXL(cpu.inc8(cpu.GetIXL()))
|
|
return 8
|
|
case 0x2D: // DEC IXL
|
|
cpu.SetIXL(cpu.dec8(cpu.GetIXL()))
|
|
return 8
|
|
case 0x2E: // LD IXL, n
|
|
cpu.SetIXL(cpu.ReadImmediateByte())
|
|
return 11
|
|
case 0x34: // INC (IX+d)
|
|
return cpu.executeIncDecIndexed(true)
|
|
case 0x35: // DEC (IX+d)
|
|
return cpu.executeIncDecIndexed(false)
|
|
case 0x36: // LD (IX+d), n
|
|
displacement := cpu.ReadDisplacement()
|
|
value := cpu.ReadImmediateByte()
|
|
addr := uint16(int32(cpu.IX) + int32(displacement))
|
|
cpu.Memory.WriteByte(addr, value)
|
|
cpu.MEMPTR = addr
|
|
return 19
|
|
case 0x39: // ADD IX, SP
|
|
oldIX := cpu.IX
|
|
result := cpu.add16IX(cpu.IX, cpu.SP)
|
|
cpu.MEMPTR = oldIX + 1
|
|
cpu.IX = result
|
|
return 15
|
|
case 0x40: // LD B,B
|
|
return 8
|
|
|
|
// Load register from IX register
|
|
case 0x44: // LD B, IXH
|
|
cpu.B = cpu.GetIXH()
|
|
return 8
|
|
case 0x45: // LD B, IXL
|
|
cpu.B = cpu.GetIXL()
|
|
return 8
|
|
case 0x46: // LD B, (IX+d)
|
|
return cpu.executeLoadFromIndexed(0)
|
|
case 0x4C: // LD C, IXH
|
|
cpu.C = cpu.GetIXH()
|
|
return 8
|
|
case 0x4D: // LD C, IXL
|
|
cpu.C = cpu.GetIXL()
|
|
return 8
|
|
case 0x4E: // LD C, (IX+d)
|
|
return cpu.executeLoadFromIndexed(1)
|
|
case 0x54: // LD D, IXH
|
|
cpu.D = cpu.GetIXH()
|
|
return 8
|
|
case 0x55: // LD D, IXL
|
|
cpu.D = cpu.GetIXL()
|
|
return 8
|
|
case 0x56: // LD D, (IX+d)
|
|
return cpu.executeLoadFromIndexed(2)
|
|
case 0x5C: // LD E, IXH
|
|
cpu.E = cpu.GetIXH()
|
|
return 8
|
|
case 0x5D: // LD E, IXL
|
|
cpu.E = cpu.GetIXL()
|
|
return 8
|
|
case 0x5E: // LD E, (IX+d)
|
|
return cpu.executeLoadFromIndexed(3)
|
|
case 0x60: // LD IXH, B
|
|
cpu.SetIXH(cpu.B)
|
|
return 8
|
|
case 0x61: // LD IXH, C
|
|
cpu.SetIXH(cpu.C)
|
|
return 8
|
|
case 0x62: // LD IXH, D
|
|
cpu.SetIXH(cpu.D)
|
|
return 8
|
|
case 0x63: // LD IXH, E
|
|
cpu.SetIXH(cpu.E)
|
|
return 8
|
|
case 0x64: // LD IXH, IXH
|
|
// No operation needed
|
|
return 8
|
|
case 0x65: // LD IXH, IXL
|
|
cpu.SetIXH(cpu.GetIXL())
|
|
return 8
|
|
case 0x66: // LD H, (IX+d)
|
|
return cpu.executeLoadFromIndexed(4)
|
|
case 0x67: // LD IXH, A
|
|
cpu.SetIXH(cpu.A)
|
|
return 8
|
|
case 0x68: // LD IXL, B
|
|
cpu.SetIXL(cpu.B)
|
|
return 8
|
|
case 0x69: // LD IXL, C
|
|
cpu.SetIXL(cpu.C)
|
|
return 8
|
|
case 0x6A: // LD IXL, D
|
|
cpu.SetIXL(cpu.D)
|
|
return 8
|
|
case 0x6B: // LD IXL, E
|
|
cpu.SetIXL(cpu.E)
|
|
return 8
|
|
case 0x6C: // LD IXL, IXH
|
|
cpu.SetIXL(cpu.GetIXH())
|
|
return 8
|
|
case 0x6D: // LD IXL, IXL
|
|
// No operation needed
|
|
return 8
|
|
case 0x6E: // LD L, (IX+d)
|
|
return cpu.executeLoadFromIndexed(5)
|
|
case 0x6F: // LD IXL, A
|
|
cpu.SetIXL(cpu.A)
|
|
return 8
|
|
case 0x70: // LD (IX+d), B
|
|
return cpu.executeStoreToIndexed(cpu.B)
|
|
case 0x71: // LD (IX+d), C
|
|
return cpu.executeStoreToIndexed(cpu.C)
|
|
case 0x72: // LD (IX+d), D
|
|
return cpu.executeStoreToIndexed(cpu.D)
|
|
case 0x73: // LD (IX+d), E
|
|
return cpu.executeStoreToIndexed(cpu.E)
|
|
case 0x74: // LD (IX+d), H
|
|
return cpu.executeStoreToIndexed(cpu.H)
|
|
case 0x75: // LD (IX+d), L
|
|
return cpu.executeStoreToIndexed(cpu.L)
|
|
case 0x77: // LD (IX+d), A
|
|
return cpu.executeStoreToIndexed(cpu.A)
|
|
case 0x7C: // LD A, IXH
|
|
cpu.A = cpu.GetIXH()
|
|
return 8
|
|
case 0x7D: // LD A, IXL
|
|
cpu.A = cpu.GetIXL()
|
|
return 8
|
|
case 0x7E: // LD A, (IX+d)
|
|
return cpu.executeLoadFromIndexed(7)
|
|
|
|
// Arithmetic and logic instructions
|
|
case 0x84: // ADD A, IXH
|
|
cpu.add8(cpu.GetIXH())
|
|
return 8
|
|
case 0x85: // ADD A, IXL
|
|
cpu.add8(cpu.GetIXL())
|
|
return 8
|
|
case 0x86: // ADD A, (IX+d)
|
|
return cpu.executeALUIndexed(0)
|
|
case 0x8C: // ADC A, IXH
|
|
cpu.adc8(cpu.GetIXH())
|
|
return 8
|
|
case 0x8D: // ADC A, IXL
|
|
cpu.adc8(cpu.GetIXL())
|
|
return 8
|
|
case 0x8E: // ADC A, (IX+d)
|
|
return cpu.executeALUIndexed(1)
|
|
case 0x94: // SUB IXH
|
|
cpu.sub8(cpu.GetIXH())
|
|
return 8
|
|
case 0x95: // SUB IXL
|
|
cpu.sub8(cpu.GetIXL())
|
|
return 8
|
|
case 0x96: // SUB (IX+d)
|
|
return cpu.executeALUIndexed(2)
|
|
case 0x9C: // SBC A, IXH
|
|
cpu.sbc8(cpu.GetIXH())
|
|
return 8
|
|
case 0x9D: // SBC A, IXL
|
|
cpu.sbc8(cpu.GetIXL())
|
|
return 8
|
|
case 0x9E: // SBC A, (IX+d)
|
|
return cpu.executeALUIndexed(3)
|
|
case 0xA4: // AND IXH
|
|
cpu.and8(cpu.GetIXH())
|
|
return 8
|
|
case 0xA5: // AND IXL
|
|
cpu.and8(cpu.GetIXL())
|
|
return 8
|
|
case 0xA6: // AND (IX+d)
|
|
return cpu.executeALUIndexed(4)
|
|
case 0xAC: // XOR IXH
|
|
cpu.xor8(cpu.GetIXH())
|
|
return 8
|
|
case 0xAD: // XOR IXL
|
|
cpu.xor8(cpu.GetIXL())
|
|
return 8
|
|
case 0xAE: // XOR (IX+d)
|
|
return cpu.executeALUIndexed(5)
|
|
case 0xB4: // OR IXH
|
|
cpu.or8(cpu.GetIXH())
|
|
return 8
|
|
case 0xB5: // OR IXL
|
|
cpu.or8(cpu.GetIXL())
|
|
return 8
|
|
case 0xB6: // OR (IX+d)
|
|
return cpu.executeALUIndexed(6)
|
|
case 0xBC: // CP IXH
|
|
cpu.cp8(cpu.GetIXH())
|
|
return 8
|
|
case 0xBD: // CP IXL
|
|
cpu.cp8(cpu.GetIXL())
|
|
return 8
|
|
case 0xBE: // CP (IX+d)
|
|
return cpu.executeALUIndexed(7)
|
|
|
|
// POP and PUSH instructions
|
|
case 0xE1: // POP IX
|
|
cpu.IX = cpu.Pop()
|
|
return 14
|
|
case 0xE3: // EX (SP), IX
|
|
temp := cpu.Memory.ReadWord(cpu.SP)
|
|
cpu.Memory.WriteWord(cpu.SP, cpu.IX)
|
|
cpu.IX = temp
|
|
cpu.MEMPTR = temp
|
|
return 23
|
|
case 0xE5: // PUSH IX
|
|
cpu.Push(cpu.IX)
|
|
return 15
|
|
case 0xE9: // JP (IX)
|
|
cpu.PC = cpu.IX
|
|
return 8
|
|
case 0xF9: // LD SP, IX
|
|
cpu.SP = cpu.IX
|
|
return 10
|
|
|
|
// Handle DD CB prefix (IX with displacement and CB operations)
|
|
case 0xCB: // DD CB prefix
|
|
return cpu.executeDDCBOpcode()
|
|
|
|
case 0xfd:
|
|
return 8
|
|
case 0x00: // Extended NOP (undocumented)
|
|
// DD 00 is an undocumented instruction that acts as an extended NOP
|
|
// It consumes the DD prefix and the 00 opcode but executes as a NOP
|
|
// Takes 8 cycles total (4 for DD prefix fetch + 4 for 00 opcode fetch)
|
|
return 8
|
|
case 0xdd:
|
|
return 8
|
|
default:
|
|
return cpu.ExecuteOpcode(opcode)
|
|
//panic(fmt.Sprintf("DD unexpected code %x", opcode))
|
|
}
|
|
}
|
|
|
|
// executeIncDecIndexed handles INC/DEC (IX+d) instructions
|
|
func (cpu *CPU) executeIncDecIndexed(isInc bool) int {
|
|
displacement := cpu.ReadDisplacement()
|
|
addr := uint16(int32(cpu.IX) + int32(displacement))
|
|
value := cpu.Memory.ReadByte(addr)
|
|
var result byte
|
|
if isInc {
|
|
result = cpu.inc8(value)
|
|
} else {
|
|
result = cpu.dec8(value)
|
|
}
|
|
cpu.Memory.WriteByte(addr, result)
|
|
cpu.MEMPTR = addr
|
|
return 23
|
|
}
|
|
|
|
// executeLoadFromIndexed handles LD r, (IX+d) instructions
|
|
func (cpu *CPU) executeLoadFromIndexed(reg byte) int {
|
|
displacement := cpu.ReadDisplacement()
|
|
addr := uint16(int32(cpu.IX) + int32(displacement))
|
|
value := cpu.Memory.ReadByte(addr)
|
|
|
|
switch reg {
|
|
case 0:
|
|
cpu.B = value
|
|
case 1:
|
|
cpu.C = value
|
|
case 2:
|
|
cpu.D = value
|
|
case 3:
|
|
cpu.E = value
|
|
case 4:
|
|
cpu.H = value
|
|
case 5:
|
|
cpu.L = value
|
|
case 7:
|
|
cpu.A = value
|
|
}
|
|
|
|
cpu.MEMPTR = addr
|
|
return 19
|
|
}
|
|
|
|
// executeStoreToIndexed handles LD (IX+d), r instructions
|
|
func (cpu *CPU) executeStoreToIndexed(value byte) int {
|
|
displacement := cpu.ReadDisplacement()
|
|
addr := uint16(int32(cpu.IX) + int32(displacement))
|
|
cpu.Memory.WriteByte(addr, value)
|
|
cpu.MEMPTR = addr
|
|
return 19
|
|
}
|
|
|
|
// executeALUIndexed handles ALU operations with (IX+d) operand
|
|
func (cpu *CPU) executeALUIndexed(opType byte) int {
|
|
displacement := cpu.ReadDisplacement()
|
|
addr := uint16(int32(cpu.IX) + int32(displacement))
|
|
value := cpu.Memory.ReadByte(addr)
|
|
|
|
switch opType {
|
|
case 0: // ADD
|
|
cpu.add8(value)
|
|
case 1: // ADC
|
|
cpu.adc8(value)
|
|
case 2: // SUB
|
|
cpu.sub8(value)
|
|
case 3: // SBC
|
|
cpu.sbc8(value)
|
|
case 4: // AND
|
|
cpu.and8(value)
|
|
case 5: // XOR
|
|
cpu.xor8(value)
|
|
case 6: // OR
|
|
cpu.or8(value)
|
|
case 7: // CP
|
|
cpu.cp8(value)
|
|
}
|
|
|
|
cpu.MEMPTR = addr
|
|
return 19
|
|
}
|
|
|
|
// add16IX adds two 16-bit values for IX register and updates flags
|
|
func (cpu *CPU) add16IX(a, b uint16) uint16 {
|
|
result := a + b
|
|
cpu.SetFlagState(FLAG_C, result < a)
|
|
cpu.SetFlagState(FLAG_H, (a&0x0FFF)+(b&0x0FFF) > 0x0FFF)
|
|
cpu.ClearFlag(FLAG_N)
|
|
// For IX operations, we update X and Y flags from high byte of result
|
|
cpu.UpdateFlags3and5FromAddress(result)
|
|
return result
|
|
}
|