From 5667c00d85cd3053dade0770a8c979a0a3725618 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 20 Feb 2020 02:59:03 +0100 Subject: [PATCH] fixed cycle times, implemented various illegal opcodes. NesTest now runs flawlessly --- src/main/kotlin/razorvine/ksim65/Cpu6502.kt | 394 ++++++++++++------ src/main/kotlin/razorvine/ksim65/Cpu65C02.kt | 235 +++++++---- src/test/kotlin/FunctionalTestsBase.kt | 9 - src/test/kotlin/Test6502CpuBasics.kt | 59 ++- .../kotlin/Test6502TestSuiteC64Specific.kt | 3 +- .../Test6502TestSuiteIllegalInstructions.kt | 130 +++--- 6 files changed, 545 insertions(+), 285 deletions(-) diff --git a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt index 7fa51ce..5ff1c45 100644 --- a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt +++ b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt @@ -7,11 +7,10 @@ import razorvine.ksim65.components.UByte /** * 6502 cpu simulation (the NMOS version) including the 'illegal' opcodes. - * TODO: actually implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes or https://sourceforge.net/p/moarnes/code/ci/master/tree/src/6502.c */ open class Cpu6502 : BusComponent() { open val name = "6502" - var tracing: ((state: String) -> Unit)? = null + var tracing: ((state: State) -> Unit)? = null var totalCycles = 0L protected set private var resetTime = System.nanoTime() @@ -165,6 +164,9 @@ open class Cpu6502 : BusComponent() { */ override fun clock() { if (instrCycles == 0) { + + tracing?.invoke(snapshot()) + if(nmiAsserted || (irqAsserted && !regP.I)) { handleInterrupt() return @@ -176,8 +178,6 @@ open class Cpu6502 : BusComponent() { currentOpcode = read(regPC) currentInstruction = instructions[currentOpcode] - // tracing and breakpoint handling - tracing?.invoke(snapshot().toString()) breakpoints[regPC]?.let { if (breakpoint(it)) return } @@ -557,7 +557,9 @@ open class Cpu6502 : BusComponent() { val relative = readPc() fetchedAddress = if (relative >= 0x80) { regPC-(256-relative) and 0xffff - } else regPC+relative and 0xffff + } else { + regPC+relative and 0xffff + } false } AddrMode.Abs -> { @@ -581,6 +583,7 @@ open class Cpu6502 : BusComponent() { (fetchedAddress and 0xff00) != hi shl 8 } AddrMode.Ind -> { + val extraCycle: Boolean var lo = readPc() var hi = readPc() fetchedAddress = lo or (hi shl 8) @@ -589,13 +592,15 @@ open class Cpu6502 : BusComponent() { // not able to fetch an address which crosses the page boundary. lo = read(fetchedAddress) hi = read(fetchedAddress and 0xff00) + extraCycle = true } else { // normal behavior lo = read(fetchedAddress) hi = read(fetchedAddress+1) + extraCycle = false } fetchedAddress = lo or (hi shl 8) - false + extraCycle } AddrMode.IzX -> { // note: not able to fetch an address which crosses the (zero)page boundary @@ -603,15 +608,17 @@ open class Cpu6502 : BusComponent() { val lo = read((fetchedAddress+regX) and 0xff) val hi = read((fetchedAddress+regX+1) and 0xff) fetchedAddress = lo or (hi shl 8) - false + // if this address is a different page, extra clock cycle: + (fetchedAddress and 0xff00) != hi shl 8 } AddrMode.IzY -> { // note: not able to fetch an address which crosses the (zero)page boundary - fetchedAddress = readPc() - val lo = read(fetchedAddress) - val hi = read((fetchedAddress+1) and 0xff) + val fetchedAddress1 = readPc() + val lo = read(fetchedAddress1) + val hi = read((fetchedAddress1+1) and 0xff) fetchedAddress = regY+(lo or (hi shl 8)) and 0xffff - false + // if this address is a different page, extra clock cycle: + (fetchedAddress and 0xff00) != hi shl 8 } AddrMode.Zpr, AddrMode.Izp, AddrMode.IaX -> { // addressing mode used by the 65C02 only @@ -621,7 +628,7 @@ open class Cpu6502 : BusComponent() { } protected open fun dispatchOpcode(opcode: Int): Boolean { - when (opcode) { + return when (opcode) { 0x00 -> iBrk() 0x01 -> iOra() 0x02 -> iInvalid() @@ -878,15 +885,14 @@ open class Cpu6502 : BusComponent() { 0xfd -> iSbc() 0xfe -> iInc() 0xff -> iIsc() - else -> { /* can't occur */ } + else -> false /* can't occur */ } - return false // TODO determine if instructions can cause extra clock cycle } // official instructions - protected open fun iAdc() { + protected open fun iAdc(): Boolean { val operand = getFetched() if (regP.D) { // BCD add @@ -916,15 +922,18 @@ open class Cpu6502 : BusComponent() { regP.C = tmp > 0xff regA = tmp and 0xff } + + return true } - protected fun iAnd() { + protected fun iAnd(): Boolean { regA = regA and getFetched() regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return true } - protected fun iAsl() { + protected fun iAsl(): Boolean { if (currentInstruction.mode == AddrMode.Acc) { regP.C = (regA and 0b10000000) != 0 regA = (regA shl 1) and 0xff @@ -938,44 +947,82 @@ open class Cpu6502 : BusComponent() { regP.Z = shifted == 0 regP.N = (shifted and 0b10000000) != 0 } + return false } - protected fun iBcc() { - if (!regP.C) regPC = fetchedAddress + protected fun iBcc(): Boolean { + if (!regP.C) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iBcs() { - if (regP.C) regPC = fetchedAddress + protected fun iBcs(): Boolean { + if (regP.C) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iBeq() { - if (regP.Z) regPC = fetchedAddress + protected fun iBeq(): Boolean { + if (regP.Z) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected open fun iBit() { + protected open fun iBit(): Boolean { val operand = getFetched() regP.Z = (regA and operand) == 0 regP.V = (operand and 0b01000000) != 0 regP.N = (operand and 0b10000000) != 0 + return false } - protected fun iBmi() { - if (regP.N) regPC = fetchedAddress + protected fun iBmi(): Boolean { + if (regP.N) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iBne() { - if (!regP.Z) regPC = fetchedAddress + protected fun iBne(): Boolean { + if (!regP.Z) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iBpl() { - if (!regP.N) regPC = fetchedAddress + protected fun iBpl(): Boolean { + if (!regP.N) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected open fun iBrk() { + protected open fun iBrk(): Boolean { // handle BRK ('software interrupt') regPC++ if(nmiAsserted) - return // if an NMI occurs during BRK, the BRK won't get executed on 6502 (65C02 fixes this) + return false // if an NMI occurs during BRK, the BRK won't get executed on 6502 (65C02 fixes this) pushStackAddr(regPC) regP.B = true pushStack(regP) @@ -984,6 +1031,7 @@ open class Cpu6502 : BusComponent() { regPC = readWord(IRQ_vector) // TODO prevent NMI from triggering immediately after IRQ/BRK... how does that work exactly? + return false } protected open fun handleInterrupt() { @@ -1008,123 +1056,154 @@ open class Cpu6502 : BusComponent() { // TODO prevent NMI from triggering immediately after IRQ/BRK... how does that work exactly? } - protected fun iBvc() { - if (!regP.V) regPC = fetchedAddress + protected fun iBvc(): Boolean { + if (!regP.V) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iBvs() { - if (regP.V) regPC = fetchedAddress + protected fun iBvs(): Boolean { + if (regP.V) { + if(fetchedAddress and 0xff00 != regPC and 0xff00) + instrCycles++ + regPC = fetchedAddress + instrCycles++ + } + return false } - protected fun iClc() { + protected fun iClc(): Boolean { regP.C = false + return false } - protected fun iCld() { + protected fun iCld(): Boolean { regP.D = false + return false } - protected fun iCli() { + protected fun iCli(): Boolean { regP.I = false + return false } - protected fun iClv() { + protected fun iClv(): Boolean { regP.V = false + return false } - protected fun iCmp() { - val fetched = getFetched() + protected fun iCmp(operandOverride: Int? = null): Boolean { + val fetched = operandOverride ?: getFetched() regP.C = regA >= fetched regP.Z = regA == fetched regP.N = ((regA-fetched) and 0b10000000) != 0 + return true } - protected fun iCpx() { + protected fun iCpx(): Boolean { val fetched = getFetched() regP.C = regX >= fetched regP.Z = regX == fetched regP.N = ((regX-fetched) and 0b10000000) != 0 + return false } - protected fun iCpy() { + protected fun iCpy(): Boolean { val fetched = getFetched() regP.C = regY >= fetched regP.Z = regY == fetched regP.N = ((regY-fetched) and 0b10000000) != 0 + return false } - protected open fun iDec() { + protected open fun iDec(): Boolean { val data = (read(fetchedAddress)-1) and 0xff write(fetchedAddress, data) regP.Z = data == 0 regP.N = (data and 0b10000000) != 0 + return false } - protected fun iDex() { + protected fun iDex(): Boolean { regX = (regX-1) and 0xff regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return false } - protected fun iDey() { + protected fun iDey(): Boolean { regY = (regY-1) and 0xff regP.Z = regY == 0 regP.N = (regY and 0b10000000) != 0 + return false } - protected fun iEor() { + protected fun iEor(): Boolean { regA = regA xor getFetched() regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return true } - protected open fun iInc() { + protected open fun iInc(): Boolean { val data = (read(fetchedAddress)+1) and 0xff write(fetchedAddress, data) regP.Z = data == 0 regP.N = (data and 0b10000000) != 0 + return false } - protected fun iInx() { + protected fun iInx(): Boolean { regX = (regX+1) and 0xff regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return false } - protected fun iIny() { + protected fun iIny(): Boolean { regY = (regY+1) and 0xff regP.Z = regY == 0 regP.N = (regY and 0b10000000) != 0 + return false } - protected fun iJmp() { + protected fun iJmp(): Boolean { regPC = fetchedAddress + return false } - protected fun iJsr() { + protected fun iJsr(): Boolean { pushStackAddr(regPC-1) regPC = fetchedAddress + return false } - protected fun iLda() { + protected fun iLda(): Boolean { regA = getFetched() regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return true } - protected fun iLdx() { + protected fun iLdx(): Boolean { regX = getFetched() regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return true } - protected fun iLdy() { + protected fun iLdy(): Boolean { regY = getFetched() regP.Z = regY == 0 regP.N = (regY and 0b10000000) != 0 + return true } - protected fun iLsr() { + protected fun iLsr(): Boolean { if (currentInstruction.mode == AddrMode.Acc) { regP.C = (regA and 1) == 1 regA = regA ushr 1 @@ -1138,39 +1217,47 @@ open class Cpu6502 : BusComponent() { regP.Z = shifted == 0 regP.N = (shifted and 0b10000000) != 0 } + return false } - protected fun iNop() {} + protected fun iNop(): Boolean { + return currentOpcode in listOf(0x1c, 0x3c, 0x5c, 0x7c, 0xdc, 0xfc) + } - protected fun iOra() { + protected fun iOra(): Boolean { regA = regA or getFetched() regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return true } - protected fun iPha() { + protected fun iPha(): Boolean { pushStack(regA) + return false } - protected fun iPhp() { + protected fun iPhp(): Boolean { val origBreakflag = regP.B regP.B = true pushStack(regP) regP.B = origBreakflag + return false } - protected fun iPla() { + protected fun iPla(): Boolean { regA = popStack() regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return false } - protected fun iPlp() { + protected fun iPlp(): Boolean { regP.fromInt(popStack()) - regP.B = true // break is always 1 except when pushing on stack + regP.B = false + return false } - protected fun iRol() { + protected fun iRol(): Boolean { val oldCarry = regP.C if (currentInstruction.mode == AddrMode.Acc) { regP.C = (regA and 0b10000000) != 0 @@ -1185,9 +1272,10 @@ open class Cpu6502 : BusComponent() { regP.Z = shifted == 0 regP.N = (shifted and 0b10000000) != 0 } + return false } - protected fun iRor() { + protected fun iRor(): Boolean { val oldCarry = regP.C if (currentInstruction.mode == AddrMode.Acc) { regP.C = (regA and 1) == 1 @@ -1202,21 +1290,24 @@ open class Cpu6502 : BusComponent() { regP.Z = shifted == 0 regP.N = (shifted and 0b10000000) != 0 } + return false } - protected fun iRti() { + protected fun iRti(): Boolean { regP.fromInt(popStack()) - regP.B = true // break is always 1 except when pushing on stack + regP.B = false regPC = popStackAddr() + return false } - protected fun iRts() { + protected fun iRts(): Boolean { regPC = popStackAddr() regPC = (regPC+1) and 0xffff + return false } - protected open fun iSbc() { - val operand = getFetched() + protected open fun iSbc(operandOverride: Int? = null): Boolean { + val operand = operandOverride ?: getFetched() val tmp = (regA-operand-if (regP.C) 0 else 1) and 0xffff regP.V = (regA xor operand) and (regA xor tmp) and 0b10000000 != 0 if (regP.D) { @@ -1240,142 +1331,205 @@ open class Cpu6502 : BusComponent() { regP.C = tmp < 0x100 regP.Z = (tmp and 0xff) == 0 regP.N = (tmp and 0b10000000) != 0 + + return true } - protected fun iSec() { + protected fun iSec(): Boolean { regP.C = true + return false } - protected fun iSed() { + protected fun iSed(): Boolean { regP.D = true + return false } - protected fun iSei() { + protected fun iSei(): Boolean { regP.I = true + return false } - protected fun iSta() { + protected fun iSta(): Boolean { write(fetchedAddress, regA) + return false } - protected fun iStx() { + protected fun iStx(): Boolean { write(fetchedAddress, regX) + return false } - protected fun iSty() { + protected fun iSty(): Boolean { write(fetchedAddress, regY) + return false } - protected fun iTax() { + protected fun iTax(): Boolean { regX = regA regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return false } - protected fun iTay() { + protected fun iTay(): Boolean { regY = regA regP.Z = regY == 0 regP.N = (regY and 0b10000000) != 0 + return false } - protected fun iTsx() { + protected fun iTsx(): Boolean { regX = regSP regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return false } - protected fun iTxa() { + protected fun iTxa(): Boolean { regA = regX regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return false } - protected fun iTxs() { + protected fun iTxs(): Boolean { regSP = regX + return false } - protected fun iTya() { + protected fun iTya(): Boolean { regA = regY regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + return false } // unofficial/illegal 6502 instructions + // see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes + // or https://github.com/quietust/nintendulator/blob/master/src/CPU.cpp (search for LogBadOps) + // TODO: actually implement the illegal opcodes - private fun iAhx() { - TODO("\$${hexB(currentOpcode)} - ahx - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + + private fun iAhx(): Boolean { + val addrHi = 0xff // TODO get the correct byte from the instruction (=last byte read) + val value = regA and regX and (addrHi+1) + write(fetchedAddress, value) + return false } - private fun iAlr() { - TODO("\$${hexB(currentOpcode)} - alr=asr - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iAlr(): Boolean { + iAnd() + iLsr() + return false } - private fun iAnc() { - TODO("\$${hexB(currentOpcode)} - anc - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iAnc(): Boolean { + iAnd() + regP.C = regP.N + return false } - private fun iArr() { - TODO("\$${hexB(currentOpcode)} - arr - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iArr(): Boolean { + iAnd() + iRor() + return false } - private fun iAxs() { - TODO("\$${hexB(currentOpcode)} - axs - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iAxs(): Boolean { + val oldA = regA + regA = regA and regX + regP.C = false + iSbc() + regX = regA + regA = oldA + return false } - private fun iDcp() { - TODO("\$${hexB(currentOpcode)} - dcp - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iDcp(): Boolean { + val data = (read(fetchedAddress)-1) and 0xff + write(fetchedAddress, data) + iCmp(data) + return false } - private fun iIsc() { - TODO("\$${hexB(currentOpcode)} - isc=isb - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iIsc(): Boolean { + val data = (read(fetchedAddress)+1) and 0xff + write(fetchedAddress, data) + iSbc(data) + return false } - private fun iLas() { - TODO("\$${hexB(currentOpcode)} - las=lar - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iLas(): Boolean { + regA = regSP and getFetched() + regX = regA + regSP = regA + regP.Z = regA == 0 + regP.N = (regA and 0b10000000) != 0 + return false } - private fun iLax() { - TODO("\$${hexB(currentOpcode)} - lax - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iLax(): Boolean { + iLda() + regX = regA + return true } - private fun iRla() { - TODO("\$${hexB(currentOpcode)} - rla - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iRla(): Boolean { + iRol() + iAnd() + return false } - private fun iRra() { - TODO("\$${hexB(currentOpcode)} - rra - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iRra(): Boolean { + iRor() + iAdc() + return false } - private fun iSax() { - TODO("\$${hexB(currentOpcode)} - sax - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iSax(): Boolean { + write(fetchedAddress, regA and regX) + return false } - private fun iShx() { - TODO("\$${hexB(currentOpcode)} - shx - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iShx(): Boolean { + val addrHi = 0xff // TODO get the correct byte from the instruction (=last byte read) + write(fetchedAddress, regX and (addrHi+1)) + return false } - private fun iShy() { - TODO("\$${hexB(currentOpcode)} - shy - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iShy(): Boolean { + val addrHi = 0xff // TODO get the correct byte from the instruction (=last byte read) + write(fetchedAddress, regY and (addrHi+1)) + return false } - private fun iSlo() { - TODO("\$${hexB(currentOpcode)} - slo=aso - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iSlo(): Boolean { + iAsl() + iOra() + return false } - private fun iSre() { - TODO("\$${hexB(currentOpcode)} - sre=lse - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iSre(): Boolean { + iLsr() + iEor() + return false } - private fun iTas() { - TODO("\$${hexB(currentOpcode)} - tas - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iTas(): Boolean { + regSP = regA and regX + val addrHi = 0xff // TODO get the correct byte from the instruction (=last byte read) + write(fetchedAddress, regSP and addrHi) + return false } - private fun iXaa() { - TODO("\$${hexB(currentOpcode)} - xaa - ('illegal' instruction) @ \$${hexW(currentOpcodeAddress)}") + private fun iXaa(): Boolean { + regA = (regX and fetchedData) + return false } // invalid instruction (JAM / KIL / HLT) - private fun iInvalid() { + private fun iInvalid(): Boolean { throw InstructionError("invalid instruction encountered: opcode=${hexB(currentOpcode)} instr=${currentInstruction.mnemonic} @ ${hexW(currentOpcodeAddress)}") } } diff --git a/src/main/kotlin/razorvine/ksim65/Cpu65C02.kt b/src/main/kotlin/razorvine/ksim65/Cpu65C02.kt index e1f77b5..83ecace 100644 --- a/src/main/kotlin/razorvine/ksim65/Cpu65C02.kt +++ b/src/main/kotlin/razorvine/ksim65/Cpu65C02.kt @@ -103,7 +103,7 @@ class Cpu65C02 : Cpu6502() { } override fun dispatchOpcode(opcode: Int): Boolean { - when (opcode) { + return when (opcode) { 0x00 -> iBrk() 0x01 -> iOra() 0x02 -> iNop() @@ -360,9 +360,8 @@ class Cpu65C02 : Cpu6502() { 0xfd -> iSbc() 0xfe -> iInc() 0xff -> iBbs7() - else -> { /* can't occur */ } + else -> false /* can't occur */ } - return false // TODO determine if instructions can cause extra clock cycle } // opcode list: http://www.oxyron.de/html/opcodesc02.html @@ -624,7 +623,7 @@ class Cpu65C02 : Cpu6502() { /* fe */ Instruction("inc", AddrMode.AbsX, 7), /* ff */ Instruction("bbs7", AddrMode.Zpr, 5)).toTypedArray() - override fun iBrk() { + override fun iBrk(): Boolean { // handle BRK ('software interrupt') regPC++ pushStackAddr(regPC) @@ -635,6 +634,7 @@ class Cpu65C02 : Cpu6502() { regPC = readWord(IRQ_vector) // TODO prevent NMI from triggering immediately after IRQ/BRK... how does that work exactly? + return false } override fun handleInterrupt() { @@ -642,16 +642,17 @@ class Cpu65C02 : Cpu6502() { regP.D = false // this is different from NMOS 6502 } - override fun iBit() { + override fun iBit(): Boolean { val data = getFetched() regP.Z = (regA and data) == 0 if (currentInstruction.mode != AddrMode.Imm) { regP.V = (data and 0b01000000) != 0 regP.N = (data and 0b10000000) != 0 } + return false } - override fun iAdc() { + override fun iAdc(): Boolean { val value = getFetched() if (regP.D) { // BCD add @@ -680,13 +681,14 @@ class Cpu65C02 : Cpu6502() { regP.C = tmp > 0xff regA = tmp and 0xff } + return false } - override fun iSbc() { + override fun iSbc(operandOverride: Int?): Boolean { // see http://www.6502.org/tutorials/decimal_mode.html // and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/65c02core.c#l1205 // (the implementation below is based on the code used by Vice) - val value = getFetched() + val value = operandOverride ?: getFetched() var tmp = (regA-value-if (regP.C) 0 else 1) and 0xffff regP.V = (regA xor tmp) and (regA xor value) and 0b10000000 != 0 if (regP.D) { @@ -698,230 +700,323 @@ class Cpu65C02 : Cpu6502() { regP.Z = (tmp and 0xff) == 0 regP.N = (tmp and 0b10000000) != 0 regA = tmp and 0xff + return false } - override fun iDec() { - if (currentInstruction.mode == AddrMode.Acc) { + override fun iDec(): Boolean { + return if (currentInstruction.mode == AddrMode.Acc) { regA = (regA-1) and 0xff regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + false } else super.iDec() } - override fun iInc() { - if (currentInstruction.mode == AddrMode.Acc) { + override fun iInc(): Boolean { + return if (currentInstruction.mode == AddrMode.Acc) { regA = (regA+1) and 0xff regP.Z = regA == 0 regP.N = (regA and 0b10000000) != 0 + false } else super.iInc() } - private fun iBra() { + private fun iBra(): Boolean { // unconditional branch regPC = fetchedAddress + return false } - private fun iTrb() { + private fun iTrb(): Boolean { val data = getFetched() regP.Z = data and regA == 0 write(fetchedAddress, data and regA.inv()) + return false } - private fun iTsb() { + private fun iTsb(): Boolean { val data = getFetched() regP.Z = data and regA == 0 write(fetchedAddress, data or regA) + return false } - private fun iStz() { + private fun iStz(): Boolean { write(fetchedAddress, 0) + return false } - private fun iWai() { + private fun iWai(): Boolean { waiting = Wait.Waiting + return false } - private fun iStp() { + private fun iStp(): Boolean { waiting = Wait.Stopped + return false } - private fun iPhx() { + private fun iPhx(): Boolean { pushStack(regX) + return false } - private fun iPlx() { + private fun iPlx(): Boolean { regX = popStack() regP.Z = regX == 0 regP.N = (regX and 0b10000000) != 0 + return false } - private fun iPhy() { + private fun iPhy(): Boolean { pushStack(regY) + return false } - private fun iPly() { + private fun iPly(): Boolean { regY = popStack() regP.Z = regY == 0 regP.N = (regY and 0b10000000) != 0 + return false } - private fun iBbr0() { + private fun iBbr0(): Boolean { val data = getFetched() - if (data and 1 == 0) regPC = fetchedAddressZpr + if (data and 1 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr1() { + private fun iBbr1(): Boolean { val data = getFetched() - if (data and 2 == 0) regPC = fetchedAddressZpr + if (data and 2 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr2() { + private fun iBbr2(): Boolean { val data = getFetched() - if (data and 4 == 0) regPC = fetchedAddressZpr + if (data and 4 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr3() { + private fun iBbr3(): Boolean { val data = getFetched() - if (data and 8 == 0) regPC = fetchedAddressZpr + if (data and 8 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr4() { + private fun iBbr4(): Boolean { val data = getFetched() - if (data and 16 == 0) regPC = fetchedAddressZpr + if (data and 16 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr5() { + private fun iBbr5(): Boolean { val data = getFetched() - if (data and 32 == 0) regPC = fetchedAddressZpr + if (data and 32 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr6() { + private fun iBbr6(): Boolean { val data = getFetched() - if (data and 64 == 0) regPC = fetchedAddressZpr + if (data and 64 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbr7() { + private fun iBbr7(): Boolean { val data = getFetched() - if (data and 128 == 0) regPC = fetchedAddressZpr + if (data and 128 == 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs0() { + private fun iBbs0(): Boolean { val data = getFetched() - if (data and 1 != 0) regPC = fetchedAddressZpr + if (data and 1 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs1() { + private fun iBbs1(): Boolean { val data = getFetched() - if (data and 2 != 0) regPC = fetchedAddressZpr + if (data and 2 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs2() { + private fun iBbs2(): Boolean { val data = getFetched() - if (data and 4 != 0) regPC = fetchedAddressZpr + if (data and 4 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs3() { + private fun iBbs3(): Boolean { val data = getFetched() - if (data and 8 != 0) regPC = fetchedAddressZpr + if (data and 8 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs4() { + private fun iBbs4(): Boolean { val data = getFetched() - if (data and 16 != 0) regPC = fetchedAddressZpr + if (data and 16 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs5() { + private fun iBbs5(): Boolean { val data = getFetched() - if (data and 32 != 0) regPC = fetchedAddressZpr + if (data and 32 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs6() { + private fun iBbs6(): Boolean { val data = getFetched() - if (data and 64 != 0) regPC = fetchedAddressZpr + if (data and 64 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iBbs7() { + private fun iBbs7(): Boolean { val data = getFetched() - if (data and 128 != 0) regPC = fetchedAddressZpr + if (data and 128 != 0) { + regPC = fetchedAddressZpr + instrCycles++ + } + return false } - private fun iSmb0() { + private fun iSmb0(): Boolean { val data = getFetched() write(fetchedAddress, data or 1) + return false } - private fun iSmb1() { + private fun iSmb1(): Boolean { val data = getFetched() write(fetchedAddress, data or 2) + return false } - private fun iSmb2() { + private fun iSmb2(): Boolean { val data = getFetched() write(fetchedAddress, data or 4) + return false } - private fun iSmb3() { + private fun iSmb3(): Boolean { val data = getFetched() write(fetchedAddress, data or 8) + return false } - private fun iSmb4() { + private fun iSmb4(): Boolean { val data = getFetched() write(fetchedAddress, data or 16) + return false } - private fun iSmb5() { + private fun iSmb5(): Boolean { val data = getFetched() write(fetchedAddress, data or 32) + return false } - private fun iSmb6() { + private fun iSmb6(): Boolean { val data = getFetched() write(fetchedAddress, data or 64) + return false } - private fun iSmb7() { + private fun iSmb7(): Boolean { val data = getFetched() write(fetchedAddress, data or 128) + return false } - private fun iRmb0() { + private fun iRmb0(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11111110) + return false } - private fun iRmb1() { + private fun iRmb1(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11111101) + return false } - private fun iRmb2() { + private fun iRmb2(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11111011) + return false } - private fun iRmb3() { + private fun iRmb3(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11110111) + return false } - private fun iRmb4() { + private fun iRmb4(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11101111) + return false } - private fun iRmb5() { + private fun iRmb5(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b11011111) + return false } - private fun iRmb6() { + private fun iRmb6(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b10111111) + return false } - private fun iRmb7() { + private fun iRmb7(): Boolean { val data = getFetched() write(fetchedAddress, data and 0b01111111) + return false } } diff --git a/src/test/kotlin/FunctionalTestsBase.kt b/src/test/kotlin/FunctionalTestsBase.kt index 4cc0436..0a42071 100644 --- a/src/test/kotlin/FunctionalTestsBase.kt +++ b/src/test/kotlin/FunctionalTestsBase.kt @@ -72,13 +72,4 @@ abstract class FunctionalTestsBase { } fail("test failed") } - - protected fun runTestExpectNotImplemented(testprogram: String) { - try { - runTest(testprogram) - fail("expected to crash with NotImplementedError") - } catch(nx: NotImplementedError) { - // okay! - } - } } diff --git a/src/test/kotlin/Test6502CpuBasics.kt b/src/test/kotlin/Test6502CpuBasics.kt index 818abb9..2fa03f8 100644 --- a/src/test/kotlin/Test6502CpuBasics.kt +++ b/src/test/kotlin/Test6502CpuBasics.kt @@ -230,6 +230,24 @@ class Test6502CpuBasics { totalCycles = cycles instrCycles = 0 } + + override fun iAdc(): Boolean { + // NES cpu doesn't have BCD mode + val decimal = regP.D + regP.D = false + val result = super.iAdc() + regP.D = decimal + return result + } + + override fun iSbc(operandOverride: Int?): Boolean { + // NES cpu doesn't have BCD mode + val decimal = regP.D + regP.D = false + val result = super.iSbc(operandOverride) + regP.D = decimal + return result + } } val cpu = NesCpu() @@ -245,21 +263,23 @@ class Test6502CpuBasics { bus.reset() cpu.resetTotalCycles(7) // that is what the nes rom starts with cpu.regPC = 0xc000 + var tracingSnapshot = cpu.snapshot() + cpu.tracing = { tracingSnapshot=it } val neslog = javaClass.getResource("nestest.log").readText().lineSequence() for(logline in neslog) { - val s = cpu.snapshot() // TODO use cpu.tracing instead + if(logline.isEmpty()) + break + + cpu.step() + val nesAddressHex = logline.substring(0, 4).toInt(16) - assertEquals(nesAddressHex, s.PC) + assertEquals(nesAddressHex, tracingSnapshot.PC) - println("NES: $logline") - val disassem = disassembler.disassembleOneInstruction(ram.data, s.PC, 0).first.substring(1) - val spaces = " ".substring(disassem.length-1) - println("EMU: $disassem $spaces A:${hexB(s.A)} X:${hexB(s.X)} Y:${hexB(s.Y)} P:${hexB(s.P.asInt())} SP:${hexB(s.SP)} PPU: 0, 0 CYC:${s.cycles}") - - // TODO use cpu.tracing, as per https://forums.nesdev.com/viewtopic.php?t=19117 (i.e. BEFORE instruction gets executed): - // "before fetching the first operation code byte, make an internal record of the program counter and other registers; - // after reading the final operand byte, log all the values you stored back in step (1) plus the full instruction and its disassembly." +// println("NES: $logline") +// val disassem = disassembler.disassembleOneInstruction(ram.data, tracingSnapshot.PC, 0).first.substring(1) +// val spaces = " ".substring(disassem.length-1) +// println("EMU: $disassem $spaces A:${hexB(tracingSnapshot.A)} X:${hexB(tracingSnapshot.X)} Y:${hexB(tracingSnapshot.Y)} P:${hexB(tracingSnapshot.P.asInt())} SP:${hexB(tracingSnapshot.SP)} PPU: 0, 0 CYC:${tracingSnapshot.cycles}") val nesRegsLog = logline.substring(48).split(':') val nesA = nesRegsLog[1].substring(0, 2).toShort(16) @@ -268,17 +288,16 @@ class Test6502CpuBasics { val nesP = nesRegsLog[4].substring(0, 2).toInt(16) val nesSP = nesRegsLog[5].substring(0, 2).toInt(16) val nesCycles = nesRegsLog[7].toLong() - assertEquals(nesA, s.A) - assertEquals(nesX, s.X) - assertEquals(nesY, s.Y) - assertEquals(nesP, s.P.asInt()) - assertEquals(nesSP, s.SP) - assertEquals(nesCycles, s.cycles) - cpu.step() + assertEquals(nesA, tracingSnapshot.A) + assertEquals(nesX, tracingSnapshot.X) + assertEquals(nesY, tracingSnapshot.Y) + assertEquals(nesP, tracingSnapshot.P.asInt()) + assertEquals(nesSP, tracingSnapshot.SP) + assertEquals(nesCycles, tracingSnapshot.cycles) } - // TODO test $02 and $03 for test results see http://www.qmtpro.com/~nes/misc/nestest.txt - - fail("todo: test success condition") + val two = ram[0x02] + val three = ram[0x03] + assertEquals(0, two, "test failed, code ${hexB(two)} ${hexB(three)}") } } diff --git a/src/test/kotlin/Test6502TestSuiteC64Specific.kt b/src/test/kotlin/Test6502TestSuiteC64Specific.kt index 365ec73..937d9b0 100644 --- a/src/test/kotlin/Test6502TestSuiteC64Specific.kt +++ b/src/test/kotlin/Test6502TestSuiteC64Specific.kt @@ -182,10 +182,11 @@ class Test6502TestSuiteC64Specific { runTest("cnto2") } + @Test @Disabled("tests c64 specific hardware") fun testCputiming() { - runTest("cputiming") // TODO fix this test once the cycle times are correct? + runTest("cputiming") } @Test diff --git a/src/test/kotlin/Test6502TestSuiteIllegalInstructions.kt b/src/test/kotlin/Test6502TestSuiteIllegalInstructions.kt index d3fffd8..7f40405 100644 --- a/src/test/kotlin/Test6502TestSuiteIllegalInstructions.kt +++ b/src/test/kotlin/Test6502TestSuiteIllegalInstructions.kt @@ -2,328 +2,328 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import kotlin.test.* -// TODO: implement the illegal instructions and replace these tests with the 'real' runTest +// TODO: implement the still missing illegal instructions and replace these tests with the 'real' runTest @Execution(ExecutionMode.CONCURRENT) class Test6502TestSuiteIllegalInstructions: FunctionalTestsBase() { @Test fun testAlrb() { - runTestExpectNotImplemented("alrb") + runTest("alrb") // TODO fix? } @Test fun testAncb() { - runTestExpectNotImplemented("ancb") + runTest("ancb") } @Test fun testAneb() { - runTestExpectNotImplemented("aneb") + runTest("aneb") // TODO fix? } @Test fun testArrb() { - runTestExpectNotImplemented("arrb") + runTest("arrb") // TODO fix? } @Test fun testAsoa() { - runTestExpectNotImplemented("asoa") + runTest("asoa") } @Test fun testAsoax() { - runTestExpectNotImplemented("asoax") + runTest("asoax") } @Test fun testAsoay() { - runTestExpectNotImplemented("asoay") + runTest("asoay") } @Test fun testAsoix() { - runTestExpectNotImplemented("asoix") + runTest("asoix") } @Test fun testAsoiy() { - runTestExpectNotImplemented("asoiy") + runTest("asoiy") } @Test fun testAsoz() { - runTestExpectNotImplemented("asoz") + runTest("asoz") } @Test fun testAsozx() { - runTestExpectNotImplemented("asozx") + runTest("asozx") } @Test fun testAxsa() { - runTestExpectNotImplemented("axsa") + runTest("axsa") } @Test fun testAxsix() { - runTestExpectNotImplemented("axsix") + runTest("axsix") } @Test fun testAxsz() { - runTestExpectNotImplemented("axsz") + runTest("axsz") } @Test fun testAxszy() { - runTestExpectNotImplemented("axszy") + runTest("axszy") } @Test fun testDcma() { - runTestExpectNotImplemented("dcma") + runTest("dcma") } @Test fun testDcmax() { - runTestExpectNotImplemented("dcmax") + runTest("dcmax") } @Test fun testDcmay() { - runTestExpectNotImplemented("dcmay") + runTest("dcmay") } @Test fun testDcmix() { - runTestExpectNotImplemented("dcmix") + runTest("dcmix") } @Test fun testDcmiy() { - runTestExpectNotImplemented("dcmiy") + runTest("dcmiy") } @Test fun testDcmz() { - runTestExpectNotImplemented("dcmz") + runTest("dcmz") } @Test fun testDcmzx() { - runTestExpectNotImplemented("dcmzx") + runTest("dcmzx") } @Test fun testInsa() { - runTestExpectNotImplemented("insa") + runTest("insa") } @Test fun testInsax() { - runTestExpectNotImplemented("insax") + runTest("insax") } @Test fun testInsay() { - runTestExpectNotImplemented("insay") + runTest("insay") } @Test fun testInsix() { - runTestExpectNotImplemented("insix") + runTest("insix") } @Test fun testInsiy() { - runTestExpectNotImplemented("insiy") + runTest("insiy") } @Test fun testInsz() { - runTestExpectNotImplemented("insz") + runTest("insz") } @Test fun testInszx() { - runTestExpectNotImplemented("inszx") + runTest("inszx") } @Test fun testLasay() { - runTestExpectNotImplemented("lasay") + runTest("lasay") } @Test fun testLaxa() { - runTestExpectNotImplemented("laxa") + runTest("laxa") } @Test fun testLaxay() { - runTestExpectNotImplemented("laxay") + runTest("laxay") } @Test fun testLaxix() { - runTestExpectNotImplemented("laxix") + runTest("laxix") } @Test fun testLaxiy() { - runTestExpectNotImplemented("laxiy") + runTest("laxiy") } @Test fun testLaxz() { - runTestExpectNotImplemented("laxz") + runTest("laxz") } @Test fun testLaxzy() { - runTestExpectNotImplemented("laxzy") + runTest("laxzy") } @Test fun testLsea() { - runTestExpectNotImplemented("lsea") + runTest("lsea") } @Test fun testLseax() { - runTestExpectNotImplemented("lseax") + runTest("lseax") } @Test fun testLseay() { - runTestExpectNotImplemented("lseay") + runTest("lseay") } @Test fun testLseix() { - runTestExpectNotImplemented("lseix") + runTest("lseix") } @Test fun testLseiy() { - runTestExpectNotImplemented("lseiy") + runTest("lseiy") } @Test fun testLsez() { - runTestExpectNotImplemented("lsez") + runTest("lsez") } @Test fun testLsezx() { - runTestExpectNotImplemented("lsezx") + runTest("lsezx") } @Test fun testLxab() { - runTestExpectNotImplemented("lxab") + runTest("lxab") // TODO fix something? } @Test fun testRlaa() { - runTestExpectNotImplemented("rlaa") + runTest("rlaa") } @Test fun testRlaax() { - runTestExpectNotImplemented("rlaax") + runTest("rlaax") } @Test fun testRlaay() { - runTestExpectNotImplemented("rlaay") + runTest("rlaay") } @Test fun testRlaix() { - runTestExpectNotImplemented("rlaix") + runTest("rlaix") } @Test fun testRlaiy() { - runTestExpectNotImplemented("rlaiy") + runTest("rlaiy") } @Test fun testRlaz() { - runTestExpectNotImplemented("rlaz") + runTest("rlaz") } @Test fun testRlazx() { - runTestExpectNotImplemented("rlazx") + runTest("rlazx") } @Test fun testRraa() { - runTestExpectNotImplemented("rraa") + runTest("rraa") } @Test fun testRraax() { - runTestExpectNotImplemented("rraax") + runTest("rraax") } @Test fun testRraay() { - runTestExpectNotImplemented("rraay") + runTest("rraay") } @Test fun testRraix() { - runTestExpectNotImplemented("rraix") + runTest("rraix") } @Test fun testRraiy() { - runTestExpectNotImplemented("rraiy") + runTest("rraiy") } @Test fun testRraz() { - runTestExpectNotImplemented("rraz") + runTest("rraz") } @Test fun testRrazx() { - runTestExpectNotImplemented("rrazx") + runTest("rrazx") } @Test fun testSbxb() { - runTestExpectNotImplemented("sbxb") + runTest("sbxb") // TODO fix? } @Test fun testShaay() { - runTestExpectNotImplemented("shaay") + runTest("shaay") // TODO fix? } @Test fun testShaiy() { - runTestExpectNotImplemented("shaiy") + runTest("shaiy") // TODO fix? } @Test fun testShsay() { - runTestExpectNotImplemented("shsay") + runTest("shsay") // TODO fix? } @Test fun testShxay() { - runTestExpectNotImplemented("shxay") + runTest("shxay") // TODO fix? } @Test fun testShyax() { - runTestExpectNotImplemented("shyax") + runTest("shyax") // TODO fix? } }