810 lines
19 KiB
Go
810 lines
19 KiB
Go
// Package z80 implements a Z80 CPU emulator with support for all documented
|
|
// and undocumented opcodes, flags, and registers.
|
|
package z80
|
|
|
|
import "fmt"
|
|
|
|
// ExecuteEDOpcode executes an ED-prefixed opcode and returns the number of T-states used
|
|
func (cpu *CPU) ExecuteEDOpcode(opcode byte) int {
|
|
switch opcode {
|
|
// Block transfer instructions
|
|
case 0xA0: // LDI
|
|
cpu.ldi()
|
|
return 16
|
|
case 0xA1: // CPI
|
|
cpu.cpi()
|
|
return 16
|
|
case 0xA2: // INI
|
|
cpu.ini()
|
|
return 16
|
|
case 0xA3: // OUTI
|
|
cpu.outi()
|
|
return 16
|
|
case 0xA8: // LDD
|
|
cpu.ldd()
|
|
return 16
|
|
case 0xA9: // CPD
|
|
cpu.cpd()
|
|
return 16
|
|
case 0xAA: // IND
|
|
cpu.ind()
|
|
return 16
|
|
case 0xAB: // OUTD
|
|
cpu.outd()
|
|
return 16
|
|
case 0xB0: // LDIR
|
|
return cpu.ldir()
|
|
case 0xB1: // CPIR
|
|
return cpu.cpir()
|
|
case 0xB2: // INIR
|
|
return cpu.inir()
|
|
case 0xB3: // OTIR
|
|
return cpu.otir()
|
|
case 0xB8: // LDDR
|
|
return cpu.lddr()
|
|
case 0xB9: // CPDR
|
|
return cpu.cpdr()
|
|
case 0xBA: // INDR
|
|
return cpu.indr()
|
|
case 0xBB: // OTDR
|
|
return cpu.otdr()
|
|
|
|
// 8-bit load instructions
|
|
case 0x40: // IN B, (C)
|
|
return cpu.executeIN(0)
|
|
case 0x41: // OUT (C), B
|
|
return cpu.executeOUT(0)
|
|
case 0x42: // SBC HL, BC
|
|
result := cpu.sbc16WithMEMPTR(cpu.GetHL(), cpu.GetBC())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x43: // LD (nn), BC
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.Memory.WriteWord(addr, cpu.GetBC())
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x44, 0x4C, 0x54, 0x5C, 0x64, 0x6C, 0x74, 0x7C: // NEG (various undocumented versions)
|
|
cpu.neg()
|
|
return 8
|
|
case 0x45, 0x55, 0x5D, 0x65, 0x6D, 0x75, 0x7D: // RETN (various undocumented versions)
|
|
cpu.retn()
|
|
return 14
|
|
case 0x46, 0x4E, 0x66: // IM 0 (various undocumented versions)
|
|
cpu.IM = 0
|
|
return 8
|
|
case 0x47: // LD I, A
|
|
cpu.I = cpu.A
|
|
return 9
|
|
case 0x48: // IN C, (C)
|
|
return cpu.executeIN(1)
|
|
case 0x49: // OUT (C), C
|
|
return cpu.executeOUT(1)
|
|
case 0x4A: // ADC HL, BC
|
|
result := cpu.adc16WithMEMPTR(cpu.GetHL(), cpu.GetBC())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x4B: // LD BC, (nn)
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.SetBC(cpu.Memory.ReadWord(addr))
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x4D: // RETI
|
|
cpu.reti()
|
|
return 14
|
|
case 0x4F: // LD R, A
|
|
// R register is only 7 bits, bit 7 remains unchanged
|
|
//cpu.R = (cpu.R & 0x80) | (cpu.A & 0x7F)
|
|
cpu.R = cpu.A // fix zen80 tests
|
|
return 9
|
|
case 0x50: // IN D, (C)
|
|
return cpu.executeIN(2)
|
|
case 0x51: // OUT (C), D
|
|
return cpu.executeOUT(2)
|
|
case 0x52: // SBC HL, DE
|
|
result := cpu.sbc16WithMEMPTR(cpu.GetHL(), cpu.GetDE())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x53: // LD (nn), DE
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.Memory.WriteWord(addr, cpu.GetDE())
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x56, 0x76: // IM 1 (various undocumented versions)
|
|
cpu.IM = 1
|
|
return 8
|
|
case 0x57: // LD A, I
|
|
cpu.ldAI()
|
|
return 9
|
|
case 0x58: // IN E, (C)
|
|
return cpu.executeIN(3)
|
|
case 0x59: // OUT (C), E
|
|
return cpu.executeOUT(3)
|
|
case 0x5A: // ADC HL, DE
|
|
result := cpu.adc16WithMEMPTR(cpu.GetHL(), cpu.GetDE())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x5B: // LD DE, (nn)
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.SetDE(cpu.Memory.ReadWord(addr))
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x5E, 0x7E: // IM 2 (various undocumented versions)
|
|
cpu.IM = 2
|
|
return 8
|
|
case 0x5F: // LD A, R
|
|
cpu.ldAR()
|
|
return 9
|
|
case 0x60: // IN H, (C)
|
|
return cpu.executeIN(4)
|
|
case 0x61: // OUT (C), H
|
|
return cpu.executeOUT(4)
|
|
case 0x62: // SBC HL, HL
|
|
result := cpu.sbc16WithMEMPTR(cpu.GetHL(), cpu.GetHL())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x63: // LD (nn), HL
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.Memory.WriteWord(addr, cpu.GetHL())
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x67: // RRD
|
|
cpu.rrd()
|
|
return 18
|
|
case 0x68: // IN L, (C)
|
|
return cpu.executeIN(5)
|
|
case 0x69: // OUT (C), L
|
|
return cpu.executeOUT(5)
|
|
case 0x6A: // ADC HL, HL
|
|
result := cpu.adc16WithMEMPTR(cpu.GetHL(), cpu.GetHL())
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x6B: // LD HL, (nn)
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.SetHL(cpu.Memory.ReadWord(addr))
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x6F: // RLD
|
|
cpu.rld()
|
|
return 18
|
|
case 0x70: // IN (C) (Undocumented - input to dummy register)
|
|
bc := cpu.GetBC() // Save BC before doing anything
|
|
value := cpu.inC()
|
|
cpu.UpdateSZXYFlags(value)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
// Set PV flag based on parity of the result
|
|
cpu.SetFlagState(FLAG_PV, cpu.parity(value))
|
|
// MEMPTR = BC + 1 (using the original BC value)
|
|
cpu.MEMPTR = bc + 1
|
|
return 12
|
|
case 0x71: // OUT (C), 0 (Undocumented)
|
|
cpu.outC(0)
|
|
// MEMPTR = BC + 1
|
|
cpu.MEMPTR = cpu.GetBC() + 1
|
|
return 12
|
|
case 0x72: // SBC HL, SP
|
|
result := cpu.sbc16WithMEMPTR(cpu.GetHL(), cpu.SP)
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x73: // LD (nn), SP
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.Memory.WriteWord(addr, cpu.SP)
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x78: // IN A, (C)
|
|
return cpu.executeIN(7)
|
|
case 0x79: // OUT (C), A
|
|
return cpu.executeOUT(7)
|
|
case 0x7A: // ADC HL, SP
|
|
result := cpu.adc16WithMEMPTR(cpu.GetHL(), cpu.SP)
|
|
cpu.SetHL(result)
|
|
return 15
|
|
case 0x7B: // LD SP, (nn)
|
|
addr := cpu.ReadImmediateWord()
|
|
cpu.SP = cpu.Memory.ReadWord(addr)
|
|
// MEMPTR = addr + 1
|
|
cpu.MEMPTR = addr + 1
|
|
return 20
|
|
case 0x80: // endefined NOP
|
|
return 8
|
|
case 0x6e:
|
|
return 8
|
|
|
|
default:
|
|
panic(fmt.Sprintf("ED unexpected code %x", opcode))
|
|
}
|
|
}
|
|
|
|
// executeIN handles the IN r, (C) instructions
|
|
func (cpu *CPU) executeIN(reg byte) int {
|
|
bc := cpu.GetBC()
|
|
value := cpu.inC()
|
|
|
|
// Update flags
|
|
cpu.UpdateSZXYFlags(value)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
// Set PV flag based on parity of the result
|
|
cpu.SetFlagState(FLAG_PV, cpu.parity(value))
|
|
// MEMPTR = BC + 1 (using the original BC value)
|
|
cpu.MEMPTR = bc + 1
|
|
|
|
// Set the appropriate register
|
|
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
|
|
}
|
|
|
|
return 12
|
|
}
|
|
|
|
// executeOUT handles the OUT (C), r instructions
|
|
func (cpu *CPU) executeOUT(reg byte) int {
|
|
var value byte
|
|
|
|
// Get the appropriate register value
|
|
switch reg {
|
|
case 0:
|
|
value = cpu.B
|
|
case 1:
|
|
value = cpu.C
|
|
case 2:
|
|
value = cpu.D
|
|
case 3:
|
|
value = cpu.E
|
|
case 4:
|
|
value = cpu.H
|
|
case 5:
|
|
value = cpu.L
|
|
case 7:
|
|
value = cpu.A
|
|
}
|
|
|
|
cpu.outC(value)
|
|
// MEMPTR = BC + 1
|
|
cpu.MEMPTR = cpu.GetBC() + 1
|
|
|
|
return 12
|
|
}
|
|
|
|
// ldi loads byte from (HL) to (DE), increments pointers, decrements BC
|
|
func (cpu *CPU) ldi() {
|
|
value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
cpu.Memory.WriteByte(cpu.GetDE(), value)
|
|
|
|
cpu.SetDE(cpu.GetDE() + 1)
|
|
cpu.SetHL(cpu.GetHL() + 1)
|
|
cpu.SetBC(cpu.GetBC() - 1)
|
|
|
|
// FIXED: Calculate X and Y flags FIRST, preserving S, Z, C
|
|
n := value + cpu.A
|
|
cpu.F = (cpu.F & (FLAG_S | FLAG_Z | FLAG_C)) | (n & FLAG_X) | ((n & 0x02) << 4)
|
|
|
|
// THEN set the other flags
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.SetFlagState(FLAG_PV, cpu.GetBC() != 0)
|
|
cpu.ClearFlag(FLAG_N)
|
|
}
|
|
|
|
// cpi compares A with (HL), increments HL, decrements BC
|
|
func (cpu *CPU) cpi() {
|
|
value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
result := cpu.A - value
|
|
|
|
cpu.SetHL(cpu.GetHL() + 1)
|
|
cpu.SetBC(cpu.GetBC() - 1)
|
|
|
|
cpu.SetFlag(FLAG_N, true)
|
|
cpu.UpdateSZFlags(result)
|
|
|
|
// Set H flag if borrow from bit 4
|
|
cpu.SetFlagState(FLAG_H, (cpu.A&0x0F) < (value&0x0F))
|
|
|
|
// For CPI, F3 and F5 flags come from (A - (HL) - H_flag)
|
|
// where H_flag is the half-carry flag AFTER the instruction
|
|
temp := result - boolToByte(cpu.GetFlag(FLAG_H))
|
|
cpu.SetFlagState(FLAG_3, (temp&0x08) != 0) // Bit 3
|
|
cpu.SetFlagState(FLAG_5, (temp&0x02) != 0) // Bit 1
|
|
|
|
if cpu.GetBC() != 0 {
|
|
cpu.SetFlag(FLAG_PV, true)
|
|
} else {
|
|
cpu.ClearFlag(FLAG_PV)
|
|
}
|
|
|
|
// Set MEMPTR = PC - 1
|
|
cpu.MEMPTR = cpu.PC - 1
|
|
}
|
|
|
|
// ini inputs byte to (HL), increments HL, decrements B
|
|
func (cpu *CPU) ini() {
|
|
value := cpu.IO.ReadPort(uint16(cpu.C) | (uint16(cpu.B) << 8))
|
|
cpu.Memory.WriteByte(cpu.GetHL(), value)
|
|
cpu.SetHL(cpu.GetHL() + 1)
|
|
origbc := cpu.GetBC()
|
|
cpu.B--
|
|
|
|
// Enhanced: Accurate flag calculation for INI
|
|
k := int(value) + int((cpu.C+1)&0xFF)
|
|
|
|
cpu.SetFlagState(FLAG_Z, cpu.B == 0)
|
|
cpu.SetFlagState(FLAG_S, cpu.B&0x80 != 0)
|
|
cpu.SetFlagState(FLAG_N, (value&0x80) != 0)
|
|
cpu.SetFlagState(FLAG_H, k > 0xFF)
|
|
cpu.SetFlagState(FLAG_C, k > 0xFF)
|
|
// P/V flag is parity of ((k & 0x07) XOR B)
|
|
cpu.SetFlagState(FLAG_PV, cpu.parity(uint8(k&0x07)^cpu.B))
|
|
// X and Y flags from B register
|
|
cpu.F = (cpu.F & 0xD7) | (cpu.B & (FLAG_3 | FLAG_5))
|
|
|
|
cpu.MEMPTR = origbc + 1
|
|
|
|
}
|
|
|
|
// Helper function to calculate parity
|
|
func parity(val uint8) bool {
|
|
count := 0
|
|
for i := 0; i < 8; i++ {
|
|
if val&(1<<i) != 0 {
|
|
count++
|
|
}
|
|
}
|
|
return count%2 == 0
|
|
}
|
|
|
|
// outi outputs byte from (HL) to port, increments HL, decrements B
|
|
func (cpu *CPU) outi() {
|
|
val := cpu.Memory.ReadByte(cpu.GetHL())
|
|
cpu.B--
|
|
cpu.IO.WritePort(cpu.GetBC(), val)
|
|
cpu.SetHL(cpu.GetHL() + 1)
|
|
|
|
// Enhanced: Accurate flag calculation for OUTI
|
|
// Note: Use L after HL increment
|
|
k := int(val) + int(cpu.L)
|
|
|
|
cpu.SetFlagState(FLAG_Z, cpu.B == 0)
|
|
cpu.SetFlagState(FLAG_S, cpu.B&0x80 != 0)
|
|
cpu.SetFlagState(FLAG_N, (val&0x80) != 0)
|
|
cpu.SetFlagState(FLAG_H, k > 0xFF)
|
|
cpu.SetFlagState(FLAG_C, k > 0xFF)
|
|
// P/V flag is parity of ((k & 0x07) XOR B)
|
|
pvVal := uint8(k&0x07) ^ cpu.B
|
|
cpu.SetFlagState(FLAG_PV, parity(pvVal))
|
|
// X and Y flags from B register
|
|
cpu.F = (cpu.F & 0xD7) | (cpu.B & (FLAG_X | FLAG_Y))
|
|
|
|
cpu.MEMPTR = cpu.GetBC() + 1
|
|
}
|
|
|
|
func (cpu *CPU) ldd() {
|
|
value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
cpu.Memory.WriteByte(cpu.GetDE(), value)
|
|
cpu.SetHL(cpu.GetHL() - 1)
|
|
cpu.SetDE(cpu.GetDE() - 1)
|
|
cpu.SetBC(cpu.GetBC() - 1)
|
|
|
|
// FIXED: Calculate X and Y flags FIRST, preserving S, Z, C
|
|
n := value + cpu.A
|
|
cpu.F = (cpu.F & (FLAG_S | FLAG_Z | FLAG_C)) | (n & FLAG_X) | ((n & 0x02) << 4)
|
|
|
|
// THEN set the other flags
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.SetFlagState(FLAG_PV, cpu.GetBC() != 0)
|
|
cpu.ClearFlag(FLAG_N)
|
|
|
|
}
|
|
|
|
// cpd compares A with (HL), decrements HL, decrements BC
|
|
func (cpu *CPU) cpd() {
|
|
// HUMAN:Working for fuse test, but failed on zexall
|
|
// value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
// result := cpu.A - value
|
|
|
|
// cpu.SetHL(cpu.GetHL() - 1)
|
|
// cpu.SetBC(cpu.GetBC() - 1)
|
|
|
|
// cpu.SetFlag(FLAG_N, true)
|
|
// cpu.UpdateSZFlags(result)
|
|
// // For CPD, X and Y flags come from (A - (HL) - H_flag)
|
|
// // where H_flag is the half-carry flag AFTER the instruction
|
|
// temp := result - boolToByte(cpu.GetFlag(FLAG_H))
|
|
// cpu.SetFlagState(FLAG_3, (temp&0x08) != 0) // Bit 3
|
|
// cpu.SetFlagState(FLAG_5, (temp&0x02) != 0) // Bit 1
|
|
|
|
// // Set H flag if borrow from bit 4
|
|
// cpu.SetFlagState(FLAG_H, (cpu.A&0x0F) < (value&0x0F))
|
|
|
|
// if cpu.GetBC() != 0 {
|
|
// cpu.SetFlag(FLAG_PV, true)
|
|
// } else {
|
|
// cpu.ClearFlag(FLAG_PV)
|
|
// }
|
|
// cpu.MEMPTR--
|
|
|
|
val := cpu.Memory.ReadByte(cpu.GetHL())
|
|
result := int16(cpu.A) - int16(val)
|
|
cpu.SetHL(cpu.GetHL() - 1)
|
|
cpu.SetBC(cpu.GetBC() - 1)
|
|
|
|
cpu.SetFlagState(FLAG_S, uint8(result)&0x80 != 0)
|
|
cpu.SetFlagState(FLAG_Z, uint8(result) == 0)
|
|
cpu.SetFlagState(FLAG_H, (int8(cpu.A&0x0F)-int8(val&0x0F)) < 0)
|
|
cpu.SetFlagState(FLAG_PV, cpu.GetBC() != 0)
|
|
cpu.SetFlagState(FLAG_N, true)
|
|
|
|
// Y flag calculation - preserve S, Z, H, PV, N, C flags
|
|
n := uint8(result)
|
|
if cpu.GetFlag(FLAG_H) {
|
|
n--
|
|
}
|
|
cpu.F = (cpu.F & (FLAG_S | FLAG_Z | FLAG_H | FLAG_PV | FLAG_N | FLAG_C)) | (n & FLAG_X) | ((n & 0x02) << 4)
|
|
cpu.MEMPTR--
|
|
|
|
}
|
|
|
|
// ind inputs byte to (HL), decrements HL, decrements B
|
|
func (cpu *CPU) ind() {
|
|
val := cpu.IO.ReadPort(cpu.GetBC())
|
|
cpu.Memory.WriteByte(cpu.GetHL(), val)
|
|
cpu.SetHL(cpu.GetHL() - 1)
|
|
cpu.MEMPTR = cpu.GetBC() - 1
|
|
cpu.B--
|
|
|
|
// Enhanced: Accurate flag calculation for IND
|
|
// Note: Based on Z80 documentation, k = val + C (not C-1)
|
|
// HUMAN: based on fuse test , ITS + C-1
|
|
//k := int(val) + int(cpu.C)
|
|
|
|
cpu.SetFlagState(FLAG_Z, cpu.B == 0)
|
|
cpu.SetFlagState(FLAG_S, cpu.B&0x80 != 0)
|
|
cpu.SetFlagState(FLAG_N, (val&0x80) != 0)
|
|
// HUMAN : here was error
|
|
// cpu.SetFlagState(FLAG_H, k > 0xFF)
|
|
// cpu.SetFlagState(FLAG_C, k > 0xFF)
|
|
// // P/V flag is parity of ((k & 0x07) XOR B)
|
|
// pvVal := uint8(k&0x07) ^ cpu.B
|
|
// cpu.SetFlagState(FLAG_PV, parity(pvVal))
|
|
|
|
diff := uint16(cpu.C-1) + uint16(val)
|
|
cpu.SetFlagState(FLAG_H, diff > 0xFF)
|
|
cpu.SetFlagState(FLAG_C, diff > 0xFF)
|
|
temp := byte((diff & 0x07) ^ uint16(cpu.B))
|
|
parity := byte(0)
|
|
for i := 0; i < 8; i++ {
|
|
parity ^= (temp >> i) & 1
|
|
}
|
|
cpu.SetFlagState(FLAG_PV, parity == 0)
|
|
|
|
// X and Y flags from B register
|
|
cpu.F = (cpu.F & 0xD7) | (cpu.B & (FLAG_X | FLAG_Y))
|
|
|
|
}
|
|
|
|
// outd outputs byte from (HL) to port, decrements HL, decrements B
|
|
func (cpu *CPU) outd() {
|
|
val := cpu.Memory.ReadByte(cpu.GetHL())
|
|
cpu.B--
|
|
cpu.IO.WritePort(uint16(cpu.C)|(uint16(cpu.B)<<8), val)
|
|
cpu.SetHL(cpu.GetHL() - 1)
|
|
|
|
k := uint16(val) + uint16(cpu.L)
|
|
|
|
cpu.SetFlagState(FLAG_Z, cpu.B == 0)
|
|
cpu.SetFlagState(FLAG_S, cpu.B&0x80 != 0)
|
|
cpu.SetFlagState(FLAG_N, (val&0x80) != 0)
|
|
cpu.SetFlagState(FLAG_H, k > 0xFF)
|
|
cpu.SetFlagState(FLAG_C, k > 0xFF)
|
|
// P/V flag is parity of ((k & 0x07) XOR B)
|
|
pvVal := uint8(k&0x07) ^ cpu.B
|
|
cpu.SetFlagState(FLAG_PV, parity(pvVal))
|
|
// X and Y flags from B register
|
|
cpu.F = (cpu.F & 0xD7) | (cpu.B & (FLAG_X | FLAG_Y))
|
|
|
|
cpu.MEMPTR = cpu.GetBC() - 1
|
|
}
|
|
|
|
// ldir repeated LDI until BC=0
|
|
func (cpu *CPU) ldir() int {
|
|
cpu.ldi()
|
|
|
|
// Add T-states for this iteration (21 for continuing, 16 for final)
|
|
if cpu.GetBC() != 0 {
|
|
cpu.PC -= 2
|
|
cpu.MEMPTR = cpu.PC + 1
|
|
return 21
|
|
} else {
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// cpir repeated CPI until BC=0 or A=(HL)
|
|
func (cpu *CPU) cpir() int {
|
|
|
|
cpu.cpi()
|
|
|
|
if cpu.GetBC() != 0 && !cpu.GetFlag(FLAG_Z) {
|
|
|
|
cpu.PC -= 2 // Repeat instruction
|
|
|
|
// Return T-states for continuing iteration
|
|
return 21
|
|
} else {
|
|
// Return T-states for final iteration
|
|
cpu.MEMPTR = cpu.PC
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// inir repeated INI until B=0
|
|
func (cpu *CPU) inir() int {
|
|
|
|
cpu.ini()
|
|
|
|
if cpu.B != 0 {
|
|
cpu.PC -= 2 // Repeat instruction
|
|
// Return T-states for continuing iteration
|
|
return 21
|
|
} else {
|
|
// Set MEMPTR to PC+1 at the end of the instruction
|
|
//cpu.MEMPTR = cpu.PC
|
|
// Return T-states for final iteration
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// otir repeated OUTI until B=0
|
|
func (cpu *CPU) otir() int {
|
|
|
|
cpu.outi()
|
|
|
|
if cpu.B != 0 {
|
|
cpu.PC -= 2 // Repeat instruction
|
|
// Return T-states for continuing iteration
|
|
return 21
|
|
} else {
|
|
// Return T-states for final iteration
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// lddr repeated LDD until BC=0
|
|
func (cpu *CPU) lddr() int {
|
|
|
|
// Execute one LDD operation
|
|
cpu.ldd()
|
|
|
|
// Add T-states for this iteration (21 for continuing, 16 for final)
|
|
if cpu.GetBC() != 0 {
|
|
cpu.PC -= 2
|
|
cpu.MEMPTR = cpu.PC + 1
|
|
return 21
|
|
} else {
|
|
return 16
|
|
|
|
}
|
|
}
|
|
|
|
// cpdr repeated CPD until BC=0 or A=(HL)
|
|
func (cpu *CPU) cpdr() int {
|
|
|
|
cpu.cpd()
|
|
|
|
if cpu.GetBC() != 0 && !cpu.GetFlag(FLAG_Z) {
|
|
cpu.PC -= 2 // Repeat instruction
|
|
// Return T-states for continuing iteration
|
|
cpu.MEMPTR = cpu.PC + 1
|
|
return 21
|
|
} else {
|
|
cpu.MEMPTR = cpu.PC - 2
|
|
// Return T-states for final iteration
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// indr repeated IND until B=0
|
|
func (cpu *CPU) indr() int {
|
|
cpu.ind()
|
|
|
|
if cpu.B != 0 {
|
|
cpu.PC -= 2 // Repeat instruction
|
|
// Return T-states for continuing iteration
|
|
return 21
|
|
} else {
|
|
// Return T-states for final iteration
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// otdr repeated OUTD until B=0
|
|
func (cpu *CPU) otdr() int {
|
|
|
|
cpu.outd()
|
|
|
|
if cpu.B != 0 {
|
|
cpu.PC -= 2 // Repeat instruction
|
|
// Return T-states for continuing iteration
|
|
return 21
|
|
} else {
|
|
return 16
|
|
}
|
|
}
|
|
|
|
// inC reads from port (BC)
|
|
func (cpu *CPU) inC() byte {
|
|
return cpu.IO.ReadPort(cpu.GetBC())
|
|
}
|
|
|
|
// outC writes to port (BC)
|
|
func (cpu *CPU) outC(value byte) {
|
|
cpu.IO.WritePort(cpu.GetBC(), value)
|
|
}
|
|
|
|
// sbc16 subtracts 16-bit value with carry from HL
|
|
func (cpu *CPU) sbc16(val1, val2 uint16) uint16 {
|
|
carry := int32(0)
|
|
if cpu.GetFlag(FLAG_C) {
|
|
carry = 1
|
|
}
|
|
result := int32(val1) - int32(val2) - carry
|
|
halfCarry := (int16(val1&0x0FFF) - int16(val2&0x0FFF) - int16(carry)) < 0
|
|
overflow := ((val1^val2)&0x8000 != 0) && ((val1^uint16(result))&0x8000 != 0)
|
|
|
|
res16 := uint16(result)
|
|
|
|
cpu.SetFlagState(FLAG_S, res16&0x8000 != 0)
|
|
cpu.SetFlagState(FLAG_Z, res16 == 0)
|
|
cpu.SetFlagState(FLAG_H, halfCarry)
|
|
cpu.SetFlagState(FLAG_PV, overflow)
|
|
cpu.SetFlagState(FLAG_N, true)
|
|
cpu.SetFlagState(FLAG_C, result < 0)
|
|
// FIX: Set X and Y flags from high byte of result
|
|
cpu.SetFlagState(FLAG_X, uint8(res16>>8)&FLAG_X != 0)
|
|
cpu.SetFlagState(FLAG_Y, uint8(res16>>8)&FLAG_Y != 0)
|
|
cpu.MEMPTR = val1 + 1
|
|
return res16
|
|
}
|
|
|
|
// sbc16WithMEMPTR subtracts 16-bit value with carry from HL and sets MEMPTR
|
|
func (cpu *CPU) sbc16WithMEMPTR(a, b uint16) uint16 {
|
|
result := cpu.sbc16(a, b)
|
|
cpu.MEMPTR = a + 1
|
|
return result
|
|
}
|
|
|
|
// adc16 adds 16-bit value with carry to HL
|
|
func (cpu *CPU) adc16(val1, val2 uint16) uint16 {
|
|
carry := uint32(0)
|
|
if cpu.GetFlag(FLAG_C) {
|
|
carry = 1
|
|
}
|
|
result := uint32(val1) + uint32(val2) + carry
|
|
halfCarry := (val1&0x0FFF + val2&0x0FFF + uint16(carry)) > 0x0FFF
|
|
overflow := ((val1^val2)&0x8000 == 0) && ((val1^uint16(result))&0x8000 != 0)
|
|
|
|
res16 := uint16(result)
|
|
|
|
cpu.SetFlagState(FLAG_S, res16&0x8000 != 0)
|
|
cpu.SetFlagState(FLAG_Z, res16 == 0)
|
|
cpu.SetFlagState(FLAG_H, halfCarry)
|
|
cpu.SetFlagState(FLAG_PV, overflow)
|
|
cpu.SetFlagState(FLAG_N, false)
|
|
cpu.SetFlagState(FLAG_C, result > 0xFFFF)
|
|
// FIX: Set X and Y flags from high byte of result
|
|
cpu.SetFlagState(FLAG_X, uint8(res16>>8)&FLAG_X != 0)
|
|
cpu.SetFlagState(FLAG_Y, uint8(res16>>8)&FLAG_Y != 0)
|
|
cpu.MEMPTR = val1 + 1
|
|
|
|
return res16
|
|
}
|
|
|
|
// adc16WithMEMPTR adds 16-bit value with carry to HL and sets MEMPTR
|
|
func (cpu *CPU) adc16WithMEMPTR(a, b uint16) uint16 {
|
|
result := cpu.adc16(a, b)
|
|
cpu.MEMPTR = a + 1
|
|
return result
|
|
}
|
|
|
|
// neg negates the accumulator
|
|
func (cpu *CPU) neg() {
|
|
value := cpu.A
|
|
cpu.A = 0
|
|
cpu.sub8(value)
|
|
}
|
|
|
|
// retn returns from interrupt and restores IFF1 from IFF2
|
|
func (cpu *CPU) retn() {
|
|
cpu.PC = cpu.Pop()
|
|
cpu.MEMPTR = cpu.PC
|
|
cpu.IFF1 = cpu.IFF2
|
|
}
|
|
|
|
// reti returns from interrupt (same as retn for Z80)
|
|
func (cpu *CPU) reti() {
|
|
cpu.PC = cpu.Pop()
|
|
cpu.MEMPTR = cpu.PC
|
|
cpu.IFF1 = cpu.IFF2
|
|
}
|
|
|
|
// ldAI loads I register into A and updates flags
|
|
func (cpu *CPU) ldAI() {
|
|
cpu.A = cpu.I
|
|
cpu.UpdateSZXYFlags(cpu.A)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
cpu.SetFlagState(FLAG_PV, cpu.IFF2)
|
|
}
|
|
|
|
// ldAR loads R register into A and updates flags
|
|
func (cpu *CPU) ldAR() {
|
|
// Load the R register into A
|
|
cpu.A = cpu.R
|
|
cpu.UpdateSZXYFlags(cpu.A)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
cpu.SetFlagState(FLAG_PV, cpu.IFF2)
|
|
}
|
|
|
|
// rrd rotates digit between A and (HL) right
|
|
func (cpu *CPU) rrd() {
|
|
value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
ah := cpu.A & 0xF0
|
|
al := cpu.A & 0x0F
|
|
hl := value
|
|
|
|
// A bits 3-0 go to HL bits 7-4
|
|
// HL bits 7-4 go to HL bits 3-0
|
|
// HL bits 3-0 go to A bits 3-0
|
|
cpu.A = ah | (hl & 0x0F)
|
|
newHL := ((hl & 0xF0) >> 4) | (al << 4)
|
|
cpu.Memory.WriteByte(cpu.GetHL(), newHL)
|
|
|
|
cpu.UpdateSZXYPVFlags(cpu.A)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
|
|
// Set MEMPTR = HL + 1
|
|
cpu.MEMPTR = cpu.GetHL() + 1
|
|
}
|
|
|
|
// rld rotates digit between A and (HL) left
|
|
func (cpu *CPU) rld() {
|
|
value := cpu.Memory.ReadByte(cpu.GetHL())
|
|
ah := cpu.A & 0xF0
|
|
al := cpu.A & 0x0F
|
|
hl := value
|
|
|
|
// A bits 3-0 go to HL bits 3-0
|
|
// HL bits 3-0 go to HL bits 7-4
|
|
// HL bits 7-4 go to A bits 3-0
|
|
cpu.A = ah | (hl >> 4)
|
|
newHL := ((hl & 0x0F) << 4) | al
|
|
cpu.Memory.WriteByte(cpu.GetHL(), newHL)
|
|
|
|
cpu.UpdateSZXYPVFlags(cpu.A)
|
|
cpu.ClearFlag(FLAG_H)
|
|
cpu.ClearFlag(FLAG_N)
|
|
|
|
// Set MEMPTR = HL + 1
|
|
cpu.MEMPTR = cpu.GetHL() + 1
|
|
}
|