fixed cycle times, implemented various illegal opcodes. NesTest now runs flawlessly

This commit is contained in:
Irmen de Jong 2020-02-20 02:59:03 +01:00
parent 35cbe4e3ca
commit 5667c00d85
6 changed files with 545 additions and 285 deletions

View File

@ -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)}")
}
}

View File

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

View File

@ -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!
}
}
}

View File

@ -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)}")
}
}

View File

@ -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

View File

@ -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?
}
}